"use client"; import { createTaskRecord, deleteTaskRecord, updateTaskStatusRecord, updateTaskRecord, } from "@/app/(dashboard)/tasks/actions"; import { Badge, Button, Card, CardContent, Input, Label, Textarea } from "poyraz-ui/atoms"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast, } from "poyraz-ui/molecules"; import { CheckCircle2, KanbanSquare, LayoutList, Loader2, Pencil, Plus, Trash2, } from "lucide-react"; import { useEffect, useState, useTransition, type DragEvent } from "react"; export type TaskRelationOption = { id: string; name: string; client_id?: string | null; }; export type TaskListItem = { id: string; title: string; description: string | null; status: "todo" | "in_progress" | "done"; priority: "low" | "medium" | "high" | "urgent"; due_at: string | null; estimated_minutes: number | null; actual_minutes: number | null; client_id: string | null; clientName: string | null; project_id: string | null; projectName: string | null; created_at: string; }; const statusLabels = { todo: "Yapılacak", in_progress: "Devam ediyor", done: "Tamamlandı", }; const priorityLabels = { low: "Düşük", medium: "Orta", high: "Yüksek", urgent: "Acil", }; const priorityClasses = { low: "border-zinc-200 bg-zinc-50 text-zinc-700", medium: "border-blue-200 bg-blue-50 text-blue-700", high: "border-amber-200 bg-amber-50 text-amber-700", urgent: "border-rose-200 bg-rose-50 text-rose-700", }; type TasksClientProps = { tasks: TaskListItem[]; clients: TaskRelationOption[]; projects: TaskRelationOption[]; }; export function TasksClient({ tasks, clients, projects }: TasksClientProps) { const [localTasks, setLocalTasks] = useState(tasks); const [query, setQuery] = useState(""); const [projectFilter, setProjectFilter] = useState("__all"); const [view, setView] = useState<"list" | "kanban">("list"); const [pendingTaskIds, setPendingTaskIds] = useState>(new Set()); const [, startTransition] = useTransition(); useEffect(() => { setLocalTasks(tasks); }, [tasks]); function handleTaskStatusChange(taskId: string, status: TaskListItem["status"]) { const previousTasks = localTasks; setPendingTask(taskId, true); setLocalTasks((currentTasks) => currentTasks.map((task) => (task.id === taskId ? { ...task, status } : task)), ); startTransition(() => { void updateTaskStatusRecord(taskId, status) .catch((error) => { setLocalTasks(previousTasks); toast.error( error instanceof Error ? error.message : "Görev durumu güncellenemedi.", ); }) .finally(() => { setPendingTask(taskId, false); }); }); } function handleTaskDelete(taskId: string) { const previousTasks = localTasks; const task = localTasks.find((item) => item.id === taskId); const formData = new FormData(); formData.set("id", taskId); if (task?.project_id) { formData.set("project_id", task.project_id); } setPendingTask(taskId, true); setLocalTasks((currentTasks) => currentTasks.filter((item) => item.id !== taskId)); startTransition(() => { void deleteTaskRecord(formData) .catch((error) => { setLocalTasks(previousTasks); toast.error( error instanceof Error ? error.message : "Görev silinemedi.", ); }) .finally(() => { setPendingTask(taskId, false); }); }); } function setPendingTask(taskId: string, pending: boolean) { setPendingTaskIds((current) => { const next = new Set(current); if (pending) { next.add(taskId); } else { next.delete(taskId); } return next; }); } const normalizedQuery = query.trim().toLowerCase(); const filteredByProject = projectFilter === "__all" ? localTasks : projectFilter === "__none" ? localTasks.filter((task) => !task.project_id) : localTasks.filter((task) => task.project_id === projectFilter); const filteredTasks = normalizedQuery ? filteredByProject.filter((task) => [task.title, task.description, task.clientName, task.projectName] .filter(Boolean) .some((value) => value!.toLowerCase().includes(normalizedQuery)), ) : filteredByProject; const doneCount = localTasks.filter((task) => task.status === "done").length; const overdueCount = localTasks.filter((task) => isOverdue(task)).length; const urgentCount = localTasks.filter((task) => task.priority === "urgent").length; return (

Görevler

Proje ve müşteri bağlantılı işleri liste veya basit kanban ile takip et.

Görev listesi

{filteredTasks.length} kayıt görüntüleniyor.

setQuery(event.target.value)} placeholder="Görev, proje veya müşteri ara" className="sm:w-80" />
{filteredTasks.length > 0 ? ( view === "list" ? ( ) : ( ) ) : ( )}
); } function TaskList({ tasks, clients, projects, pendingTaskIds, onTaskDelete, onTaskStatusChange, }: { tasks: TaskListItem[]; clients: TaskRelationOption[]; projects: TaskRelationOption[]; pendingTaskIds: Set; onTaskDelete: (taskId: string) => void; onTaskStatusChange: (taskId: string, status: TaskListItem["status"]) => void; }) { return (
Görev Bağlantı Öncelik Son tarih İşlem
{tasks.map((task) => ( ))}
); } function TaskRow({ task, clients, projects, isPending, onTaskDelete, onTaskStatusChange, }: { task: TaskListItem; clients: TaskRelationOption[]; projects: TaskRelationOption[]; isPending: boolean; onTaskDelete: (taskId: string) => void; onTaskStatusChange: (taskId: string, status: TaskListItem["status"]) => void; }) { return (
{task.title}
{statusLabels[task.status]}
{task.projectName || "Proje yok"}
{task.clientName || "Müşteri yok"}
{priorityLabels[task.priority]}
{task.due_at ? formatDateTime(task.due_at) : "Yok"}
); } function TaskKanban({ tasks, clients, projects, pendingTaskIds, onTaskDelete, onTaskStatusChange, }: { tasks: TaskListItem[]; clients: TaskRelationOption[]; projects: TaskRelationOption[]; pendingTaskIds: Set; onTaskDelete: (taskId: string) => void; onTaskStatusChange: (taskId: string, status: TaskListItem["status"]) => void; }) { const columns = ["todo", "in_progress", "done"] as const; const [draggedTaskId, setDraggedTaskId] = useState(null); function handleDragStart(event: DragEvent, taskId: string) { setDraggedTaskId(taskId); event.dataTransfer.effectAllowed = "move"; event.dataTransfer.setData("text/plain", taskId); event.dataTransfer.setDragImage(event.currentTarget, 24, 24); } function handleDrop(status: TaskListItem["status"]) { if (!draggedTaskId) return; onTaskStatusChange(draggedTaskId, status); setDraggedTaskId(null); } return (
{columns.map((status) => { const columnTasks = tasks.filter((task) => task.status === status); return (
{ event.preventDefault(); event.dataTransfer.dropEffect = "move"; }} onDrop={() => handleDrop(status)} >

{statusLabels[status]}

{columnTasks.length}
{columnTasks.map((task) => ( handleDragStart(event, task.id)} onDragEnd={() => setDraggedTaskId(null)} >
{task.title}
{task.projectName || task.clientName || "Bağlantı yok"}
{priorityLabels[task.priority]}
))}
); })}
); } function TaskActions({ task, clients, projects, compact = false, isPending, onTaskDelete, onTaskStatusChange, }: { task: TaskListItem; clients: TaskRelationOption[]; projects: TaskRelationOption[]; compact?: boolean; isPending: boolean; onTaskDelete: (taskId: string) => void; onTaskStatusChange: (taskId: string, status: TaskListItem["status"]) => void; }) { return (
{task.status !== "done" ? ( ) : null}
); } function TaskDialog({ mode, task, clients, projects, }: { mode: "create" | "edit"; task?: TaskListItem; clients: TaskRelationOption[]; projects: TaskRelationOption[]; }) { const [open, setOpen] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const action = mode === "create" ? createTaskRecord : updateTaskRecord; async function handleSubmit(formData: FormData) { setIsSubmitting(true); try { await action(formData); setOpen(false); toast.success(mode === "create" ? "Görev eklendi." : "Görev güncellendi."); } catch (error) { toast.error( error instanceof Error ? error.message : "Görev kaydedilirken beklenmeyen bir hata oluştu.", ); } finally { setIsSubmitting(false); } } return (
{task ? : null} {mode === "create" ? "Yeni görev" : "Görevi düzenle"} Görevi proje, müşteri, öncelik ve son tarih bilgileriyle kaydet.
); } function TaskFormFields({ task, clients, projects, }: { task?: TaskListItem; clients: TaskRelationOption[]; projects: TaskRelationOption[]; }) { const [clientId, setClientId] = useState(task?.client_id || "__none"); const [projectId, setProjectId] = useState(task?.project_id || "__none"); const selectedProject = projectId === "__none" ? null : projects.find((project) => project.id === projectId) || null; const shouldLockClient = Boolean(selectedProject); const filteredProjects = clientId === "__none" || shouldLockClient ? projects : projects.filter((project) => project.client_id === clientId); function handleClientChange(nextClientId: string) { setClientId(nextClientId); if ( projectId !== "__none" && nextClientId !== "__none" && !projects.some((project) => project.id === projectId && project.client_id === nextClientId) ) { setProjectId("__none"); } } function handleProjectChange(nextProjectId: string) { setProjectId(nextProjectId); if (nextProjectId === "__none") { return; } const nextProject = projects.find((project) => project.id === nextProjectId); setClientId(nextProject?.client_id || "__none"); } return (