import { useState, useCallback, useMemo } from "react"; import { createRoute, useNavigate } from "@tanstack/react-router"; import { Route as appRoute } from "../_app"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { api, useApiQuery, useApiMutation } from "@/lib/api"; import { useApiDomain } from "@/lib/stores/use-active-domain-store"; import { useRealtime } from "@/hooks/use-realtime"; import { useOpenCreateDialog } from "@/hooks/use-open-create-dialog"; import { DndContext, DragOverlay, closestCorners, KeyboardSensor, PointerSensor, useSensor, useSensors, useDroppable, type DragEndEvent, type DragStartEvent } from "@dnd-kit/core"; import { SortableContext, verticalListSortingStrategy, useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { Plus, GripVertical, Pencil, Trash2, Calendar, Clock, ListTodo, Layout as LayoutIcon, Search, Filter, MoreHorizontal } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent } from "@/components/ui/card"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { EntityDetailPanel } from "@/components/entities/entity-detail-panel"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { LoadingState, EmptyState, ErrorState } from "@/components/state"; import { CustomFieldInputs } from "@/components/custom-fields/custom-field-inputs"; import { TASK_STATUS, PRIORITY } from "@/lib/status-colors"; import type { Task, PaginatedResponse } from "@/lib/types"; import { cn } from "@/lib/utils"; import { parseTaskInput } from "@/lib/nlp"; import { RecurrencePicker } from "@/components/tasks/recurrence-picker"; const STATUS_COLUMNS = [ { id: "todo", label: "Todo" }, { id: "in_progress", label: "In Progress" }, { id: "done", label: "Done" }, { id: "cancelled", label: "Cancelled" }, ]; function SortableTaskCard({ task, onClick, onEdit }: { task: Task; onClick: () => void; onEdit?: () => void }) { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: task.id }); const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.5 : 1, }; return (

{task.title}

{task.dueDate && ( {new Date(task.dueDate).toLocaleDateString()} )} {PRIORITY[task.priority]?.label ?? task.priority} {task.tags?.slice(0, 2).map((tag) => ( {tag.name} ))}
{onEdit && ( )}
); } function ColumnDroppable({ id, className, children }: { id: string; className?: string; children: React.ReactNode }) { const { setNodeRef, isOver } = useDroppable({ id }); return (
{children}
); } function TaskForm({ task, onClose }: { task?: Task; onClose: () => void }) { const queryClient = useQueryClient(); const activeDomainId = useApiDomain(); const [title, setTitle] = useState(task?.title || ""); const [description, setDescription] = useState(task?.description || ""); const [status, setStatus] = useState(task?.status || "todo"); const [priority, setPriority] = useState(task?.priority || "medium"); const [dueDate, setDueDate] = useState(task?.dueDate ? task.dueDate.slice(0, 10) : ""); const [recurrenceRule, setRecurrenceRule] = useState(task?.recurrenceRule || ""); const [customFieldValues, setCustomFieldValues] = useState>(() => ({ ...(task?.customFields ?? {}) })); const parsed = !task ? parseTaskInput(title) : null; const createMutation = useMutation({ mutationFn: (data: any) => api.post("/tasks", data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["tasks"] }); onClose(); }, }); const updateMutation = useMutation({ mutationFn: (data: any) => api.patch("/tasks/" + task!.id, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["tasks"] }); onClose(); }, }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!title.trim()) return; let finalTitle = title.trim(); let finalDueDate = dueDate ? new Date(dueDate).toISOString() : null; let finalPriority = priority; let tagNames: string[] = []; if (!task) { const p = parseTaskInput(title); finalTitle = p.title; if (p.dueDate && !dueDate) finalDueDate = p.dueDate; if (p.priority) finalPriority = p.priority; tagNames = p.tags; } const data: any = { title: finalTitle, description: description || null, status, priority: finalPriority, tagNames }; if (finalDueDate) data.dueDate = finalDueDate; if (recurrenceRule) data.recurrenceRule = recurrenceRule; const customFields = { ...customFieldValues }; if (Object.keys(customFields).length > 0) data.customFields = customFields; if (task) { updateMutation.mutate(data); } else { createMutation.mutate({ ...data, ...(activeDomainId ? { domain: activeDomainId } : {}) }); } }; return (
setTitle(e.target.value)} placeholder='e.g. Report due tomorrow 5pm #work p1' required /> {parsed && (parsed.dueDate || parsed.priority || parsed.tags.length > 0) && (
{parsed.dueDate && Due {new Date(parsed.dueDate).toLocaleDateString()} {new Date(parsed.dueDate).toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'})}} {parsed.priority && Priority {parsed.priority}} {parsed.tags.map(t => #{t})}
)}