import { zodResolver } from '@hookform/resolvers/zod' import { type ColumnDef } from '@tanstack/react-table' import { useMemo, useState } from 'react' import { useForm } from 'react-hook-form' import { z } from 'zod' import { DataTable } from '@/components/data-table/data-table' import { DataTableToolbar } from '@/components/data-table/data-table-toolbar' import { TaskStatusPill } from '@/components/crm/task-status-pill' import { Button } from '@/components/ui/button' import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' import { Input } from '@/components/ui/input' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '@/components/ui/sheet' import { Switch } from '@/components/ui/switch' import { Textarea } from '@/components/ui/textarea' import { useToast } from '@/components/ui/use-toast' import { useCreateTaskMutation, useTasksQuery } from '@/features/tasks/hooks' import { useDealsQuery } from '@/features/deals/hooks' import { useDebounce } from '@/hooks/use-debounce' import { formatDate, formatRelativeDate } from '@/lib/utils' import type { Deal, Task } from '@/types/crm' const taskFormSchema = z .object({ title: z.string().min(3, 'Минимум 3 символа'), dealId: z.string().min(1, 'Укажите ID сделки'), description: z.string().max(500, 'Описание до 500 символов').optional(), dueDate: z.string().optional(), }) .refine((values) => { if (!values.dueDate) return true const selected = new Date(values.dueDate) const today = new Date() selected.setHours(0, 0, 0, 0) today.setHours(0, 0, 0, 0) return selected >= today }, { message: 'Дата не может быть в прошлом', path: ['dueDate'] }) type TaskFormValues = z.infer const defaultTaskValues: TaskFormValues = { title: '', dealId: '', description: '', dueDate: '' } const TasksPage = () => { const [search, setSearch] = useState('') const [dealFilter, setDealFilter] = useState('') const [onlyOpen, setOnlyOpen] = useState(true) const [dueAfter, setDueAfter] = useState('') const [dueBefore, setDueBefore] = useState('') const [drawerOpen, setDrawerOpen] = useState(false) const debouncedDealId = useDebounce(dealFilter, 300) const { toast } = useToast() const { data: tasks = [], isLoading } = useTasksQuery({ dealId: debouncedDealId ? Number(debouncedDealId) : undefined, onlyOpen, dueAfter: dueAfter || undefined, dueBefore: dueBefore || undefined, }) const filteredTasks = useMemo(() => { const query = search.trim().toLowerCase() if (!query) return tasks return tasks.filter((task) => task.title.toLowerCase().includes(query)) }, [tasks, search]) const createTask = useCreateTaskMutation() const { data: deals = [], isLoading: dealsLoading } = useDealsQuery({ pageSize: 100, orderBy: 'updated_at', order: 'desc' }) const columns = useMemo[]>( () => [ { accessorKey: 'title', header: 'Задача', cell: ({ row }) => (

{row.original.title}

Сделка #{row.original.deal_id}

), }, { accessorKey: 'due_date', header: 'Срок', cell: ({ row }) => (

{row.original.due_date ? formatDate(row.original.due_date) : '—'}

{row.original.due_date ? formatRelativeDate(row.original.due_date) : ''}

), }, { accessorKey: 'is_done', header: 'Статус', cell: ({ row }) => , }, { accessorKey: 'created_at', header: 'Создана', cell: ({ row }) => {formatDate(row.original.created_at)}, }, ], [], ) return (

Задачи

Контролируйте follow-up по сделкам и создавайте напоминания.

setDrawerOpen(true)}> + Новая задача } > setDealFilter(event.target.value)} className="w-[140px]" />
setOnlyOpen(value === true)} id="only-open" />
setDueAfter(event.target.value)} className="w-[170px]" /> setDueBefore(event.target.value)} className="w-[170px]" /> } /> { const payload = { deal_id: Number(values.dealId), title: values.title, description: values.description ? values.description : undefined, due_date: values.dueDate || undefined, } try { await createTask.mutateAsync(payload) toast({ title: 'Задача создана', description: 'Добавлено напоминание для сделки.' }) setDrawerOpen(false) } catch (error) { toast({ title: 'Ошибка создания', description: error instanceof Error ? error.message : 'Попробуйте позже', variant: 'destructive' }) } }} />
) } interface TaskDrawerProps { open: boolean onOpenChange: (open: boolean) => void onSubmit: (values: TaskFormValues) => Promise isSubmitting: boolean deals: Deal[] dealsLoading: boolean } const TaskDrawer = ({ open, onOpenChange, onSubmit, isSubmitting, deals, dealsLoading }: TaskDrawerProps) => { const form = useForm({ resolver: zodResolver(taskFormSchema), defaultValues: defaultTaskValues, }) const handleSubmit = async (values: TaskFormValues) => { await onSubmit(values) form.reset(defaultTaskValues) } return ( Новая задача Запланируйте следующий шаг для сделки.
( Название )} /> ( Сделка {deals.length ? 'Задача появится в таймлайне выбранной сделки.' : 'Сначала создайте сделку в разделе «Сделки».'} )} /> ( Срок выполнения )} /> ( Описание