"use client"; import { useMemo, useState } from "react"; import { DndContext, type DragEndEvent, DragOverlay, type DragStartEvent, PointerSensor, useDroppable, useSensor, useSensors, closestCorners, } from "@dnd-kit/core"; import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable"; import { Loader2, Plus, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { deleteTaskAction, moveTaskAction } from "@/lib/appwrite/task-actions"; import { cn } from "@/lib/utils"; import { TaskCard } from "./task-card"; import { TaskFormSheet } from "./task-form-sheet"; import { COLUMNS, type Customer, type TaskRow, type TaskStatus, type TeamMember, } from "./types"; import { useTransition } from "react"; type Props = { tasks: TaskRow[]; customers: Customer[]; teamMembers: TeamMember[]; }; function Column({ status, title, tasks, onAdd, onEdit, onDelete, }: { status: TaskStatus; title: string; tasks: TaskRow[]; onAdd: (status: TaskStatus) => void; onEdit: (task: TaskRow) => void; onDelete: (task: TaskRow) => void; }) { const { setNodeRef, isOver } = useDroppableColumn(status); return (

{title}

{tasks.length}
t.id)} strategy={verticalListSortingStrategy} > {tasks.map((task) => ( ))} {tasks.length === 0 && (

Boş

)}
); } function useDroppableColumn(status: TaskStatus) { return useDroppable({ id: `col-${status}`, data: { type: "column", status }, }); } export function TasksBoard({ tasks: initialTasks, customers, teamMembers }: Props) { const [tasks, setTasks] = useState(initialTasks); const [activeId, setActiveId] = useState(null); const [formOpen, setFormOpen] = useState(false); const [formStatus, setFormStatus] = useState("todo"); const [editing, setEditing] = useState(null); const [deleting, setDeleting] = useState(null); const [busy, startTransition] = useTransition(); const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 6 }, }), ); const grouped = useMemo(() => { const map: Record = { backlog: [], todo: [], in_progress: [], done: [], }; for (const t of tasks) { map[t.status].push(t); } return map; }, [tasks]); const activeTask = useMemo( () => tasks.find((t) => t.id === activeId) ?? null, [tasks, activeId], ); const onDragStart = (e: DragStartEvent) => { setActiveId(String(e.active.id)); }; const onDragEnd = (e: DragEndEvent) => { const { active, over } = e; setActiveId(null); if (!over || active.id === over.id) return; const activeData = active.data.current as { type?: string; status?: TaskStatus } | undefined; const overData = over.data.current as { type?: string; status?: TaskStatus } | undefined; if (activeData?.type !== "task") return; let targetStatus: TaskStatus | undefined; if (overData?.type === "column" && overData.status) { targetStatus = overData.status; } else if (overData?.type === "task" && overData.status) { targetStatus = overData.status; } if (!targetStatus) return; const sourceTask = tasks.find((t) => t.id === active.id); if (!sourceTask) return; // Compute new order: place after the over item, or end of column const targetTasks = tasks.filter( (t) => t.status === targetStatus && t.id !== active.id, ); let newOrder: number; if (overData?.type === "task") { const overIndex = targetTasks.findIndex((t) => t.id === over.id); if (overIndex === -1) { newOrder = (targetTasks[targetTasks.length - 1]?.order ?? 0) + 1000; } else { const before = targetTasks[overIndex - 1]?.order ?? 0; const at = targetTasks[overIndex].order; newOrder = (before + at) / 2; } } else { newOrder = (targetTasks[targetTasks.length - 1]?.order ?? 0) + 1000; } // Optimistic update setTasks((prev) => prev.map((t) => (t.id === sourceTask.id ? { ...t, status: targetStatus!, order: newOrder } : t)), ); startTransition(async () => { const result = await moveTaskAction(sourceTask.id, targetStatus!, newOrder); if (!result.ok) { // Rollback setTasks((prev) => prev.map((t) => t.id === sourceTask.id ? { ...t, status: sourceTask.status, order: sourceTask.order } : t, ), ); toast.error(result.error ?? "Görev taşınamadı."); } }); }; // Keep local state in sync when server data changes (e.g., after revalidate) useMemo(() => setTasks(initialTasks), [initialTasks]); const handleAdd = (status: TaskStatus) => { setEditing(null); setFormStatus(status); setFormOpen(true); }; const handleDelete = () => { if (!deleting) return; startTransition(async () => { const fd = new FormData(); fd.set("id", deleting.id); const result = await deleteTaskAction(fd); if (result.ok) { toast.success("Görev silindi."); setDeleting(null); } else { toast.error(result.error ?? "Silme başarısız."); } }); }; return ( <>
{COLUMNS.map((col) => ( { setEditing(t); setFormOpen(true); }} onDelete={(t) => setDeleting(t)} /> ))}
{activeTask && ( {}} onDelete={() => {}} isOverlay /> )}
{ setFormOpen(v); if (!v) setEditing(null); }} task={editing} defaultStatus={formStatus} customers={customers} teamMembers={teamMembers} /> !v && setDeleting(null)}> Görevi sil {deleting?.title} kalıcı olarak silinecek. ); }