diff --git a/app/(dashboard)/projects/[id]/project-detail-client.tsx b/app/(dashboard)/projects/[id]/project-detail-client.tsx
index 0253def..bb4af79 100644
--- a/app/(dashboard)/projects/[id]/project-detail-client.tsx
+++ b/app/(dashboard)/projects/[id]/project-detail-client.tsx
@@ -6,7 +6,11 @@ import {
deleteProjectPlanningSectionRecord,
updateProjectPlanningSectionRecord,
} from "@/app/(dashboard)/projects/actions";
-import { completeTaskRecord, createTaskRecord } from "@/app/(dashboard)/tasks/actions";
+import {
+ completeTaskRecord,
+ createTaskRecord,
+ updateTaskStatusRecord,
+} from "@/app/(dashboard)/tasks/actions";
import { Badge, Button, Card, CardContent, Input, Label, Textarea } from "poyraz-ui/atoms";
import {
Dialog,
@@ -29,6 +33,8 @@ import {
ClipboardList,
FileText,
FolderKanban,
+ KanbanSquare,
+ LayoutList,
Palette,
Pencil,
Plus,
@@ -37,7 +43,7 @@ import {
Wallet,
} from "lucide-react";
import Link from "next/link";
-import { useState } from "react";
+import { useState, useTransition, type DragEvent } from "react";
export type ProjectDetail = {
id: string;
@@ -507,6 +513,24 @@ function TaskPanel({
clientId: string | null;
tasks: ProjectDetailTaskItem[];
}) {
+ const [view, setView] = useState<"list" | "kanban">("list");
+ const [localTasks, setLocalTasks] = useState(tasks);
+ const [, startTransition] = useTransition();
+
+ function handleTaskStatusChange(taskId: string, status: ProjectDetailTaskItem["status"]) {
+ const previousTasks = localTasks;
+
+ setLocalTasks((currentTasks) =>
+ currentTasks.map((task) => (task.id === taskId ? { ...task, status } : task)),
+ );
+
+ startTransition(() => {
+ void updateTaskStatusRecord(taskId, status, projectId).catch(() => {
+ setLocalTasks(previousTasks);
+ });
+ });
+ }
+
return (
@@ -517,10 +541,32 @@ function TaskPanel({
Bu proje ile bağlantılı görevler aynı task modülünden beslenir.
-
+
+
+
+
+
+
+
- {tasks.length > 0 ? (
+ {localTasks.length > 0 && view === "list" ? (
Görev
@@ -529,7 +575,7 @@ function TaskPanel({
İşlem
- {tasks.map((task) => (
+ {localTasks.map((task) => (
- ) : (
+ ) : null}
+
+ {localTasks.length > 0 && view === "kanban" ? (
+
+ ) : null}
+
+ {localTasks.length === 0 ? (
- )}
+ ) : null}
);
}
+function ProjectTaskKanban({
+ projectId,
+ tasks,
+ onTaskStatusChange,
+}: {
+ projectId: string;
+ tasks: ProjectDetailTaskItem[];
+ onTaskStatusChange: (taskId: string, status: ProjectDetailTaskItem["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: ProjectDetailTaskItem["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)}
+ >
+
+
+ {getTaskStatusLabel(status)}
+
+ {columnTasks.length}
+
+
+ {columnTasks.map((task) => (
+
handleDragStart(event, task.id)}
+ onDragEnd={() => setDraggedTaskId(null)}
+ >
+
+
+
{task.title}
+
+ {task.due_at ? formatDateTime(task.due_at) : "Son tarih yok"}
+
+
+
+ {task.priority}
+ {task.status !== "done" ? (
+
+ ) : null}
+
+
+
+ ))}
+
+
+ );
+ })}
+
+ );
+}
+
function ProjectTaskDialog({
projectId,
clientId,
@@ -611,7 +763,7 @@ function ProjectTaskDialog({
Görev ekle
-
+
-
+
@@ -853,6 +1005,12 @@ function formatDateTime(value: string) {
}).format(new Date(value));
}
+function getTaskStatusLabel(status: ProjectDetailTaskItem["status"]) {
+ if (status === "done") return "Tamamlandı";
+ if (status === "in_progress") return "Devam ediyor";
+ return "Yapılacak";
+}
+
function formatCurrency(value: number, currency: string) {
return new Intl.NumberFormat("tr-TR", {
style: "currency",
diff --git a/app/(dashboard)/tasks/actions.ts b/app/(dashboard)/tasks/actions.ts
index b355d7d..6053c7f 100644
--- a/app/(dashboard)/tasks/actions.ts
+++ b/app/(dashboard)/tasks/actions.ts
@@ -140,7 +140,7 @@ export async function completeTaskRecord(formData: FormData) {
}
}
-export async function updateTaskStatusRecord(taskId: string, status: string) {
+export async function updateTaskStatusRecord(taskId: string, status: string, projectId?: string) {
const { supabase, userId } = await getCurrentUserId();
const nextStatus = readStatus(status);
@@ -159,6 +159,10 @@ export async function updateTaskStatusRecord(taskId: string, status: string) {
}
revalidatePath("/tasks");
+
+ if (projectId) {
+ revalidatePath(`/projects/${projectId}`);
+ }
}
export async function deleteTaskRecord(formData: FormData) {