"use client";
import {
completeTaskRecord,
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,
} from "poyraz-ui/molecules";
import {
CalendarDays,
CheckCircle2,
KanbanSquare,
LayoutList,
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 [, startTransition] = useTransition();
useEffect(() => {
setLocalTasks(tasks);
}, [tasks]);
function handleTaskStatusChange(taskId: string, status: TaskListItem["status"]) {
const previousTasks = localTasks;
setLocalTasks((currentTasks) =>
currentTasks.map((task) => (task.id === taskId ? { ...task, status } : task)),
);
startTransition(() => {
void updateTaskStatusRecord(taskId, status).catch(() => {
setLocalTasks(previousTasks);
});
});
}
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ünlük operasyon
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.
{filteredTasks.length > 0 ? (
view === "list" ? (
) : (
)
) : (
)}
);
}
function TaskList({
tasks,
clients,
projects,
}: {
tasks: TaskListItem[];
clients: TaskRelationOption[];
projects: TaskRelationOption[];
}) {
return (
Görev
Bağlantı
Öncelik
Son tarih
İşlem
{tasks.map((task) => (
))}
);
}
function TaskRow({
task,
clients,
projects,
}: {
task: TaskListItem;
clients: TaskRelationOption[];
projects: TaskRelationOption[];
}) {
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,
onTaskStatusChange,
}: {
tasks: TaskListItem[];
clients: TaskRelationOption[];
projects: TaskRelationOption[];
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,
}: {
task: TaskListItem;
clients: TaskRelationOption[];
projects: TaskRelationOption[];
compact?: boolean;
}) {
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);
} finally {
setIsSubmitting(false);
}
}
return (
{mode === "create" ? : }
{mode === "create" ? "Görev ekle" : "Düzenle"}
);
}
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 (
Başlık
Açıklama
Yapılacak
Devam ediyor
Tamamlandı
Düşük
Orta
Yüksek
Acil
Müşteri
{shouldLockClient ? : null}
Müşteri yok
{clients.map((client) => (
{client.name}
))}
Proje
Proje yok
{filteredProjects.map((project) => (
{project.name}
))}
);
}
function SelectField({
name,
label,
defaultValue,
children,
}: {
name: string;
label: string;
defaultValue: string;
children: React.ReactNode;
}) {
return (
{label}
{children}
);
}
function StatCard({ label, value }: { label: string; value: string }) {
return (
);
}
function EmptyState({ hasQuery }: { hasQuery: boolean }) {
return (
{hasQuery ? "Aramana uygun görev yok" : "Henüz görev eklenmedi"}
{hasQuery
? "Arama metnini sadeleştirerek tekrar deneyebilirsin."
: "İlk görevini ekleyerek proje ve müşteri operasyonunu takip etmeye başlayabilirsin."}
);
}
function isOverdue(task: TaskListItem) {
return Boolean(task.due_at && task.status !== "done" && new Date(task.due_at) < new Date());
}
function formatDateTime(value: string) {
return new Intl.DateTimeFormat("tr-TR", {
day: "2-digit",
month: "short",
hour: "2-digit",
minute: "2-digit",
}).format(new Date(value));
}
function toDateTimeLocal(value: string) {
const date = new Date(value);
const offsetDate = new Date(date.getTime() - date.getTimezoneOffset() * 60_000);
return offsetDate.toISOString().slice(0, 16);
}