diff --git a/app/(dashboard)/projects/[id]/page.tsx b/app/(dashboard)/projects/[id]/page.tsx index 51430bd..da2069d 100644 --- a/app/(dashboard)/projects/[id]/page.tsx +++ b/app/(dashboard)/projects/[id]/page.tsx @@ -33,6 +33,7 @@ type TaskRow = { status: string | null; priority: string | null; due_at: string | null; + is_public_to_client: boolean | null; }; type FinanceRow = { @@ -60,7 +61,7 @@ export default async function ProjectDetailPage({ return null; } - const [{ data: projectRow }, { data: sectionRows }, { data: taskRows }, { data: financeRows }] = + const [{ data: projectRow }, { data: sectionRows }, { data: taskRows }, { data: financeRows }, { data: revisionRows }] = await Promise.all([ supabase .from("projects") @@ -79,7 +80,7 @@ export default async function ProjectDetailPage({ .order("created_at", { ascending: true }), supabase .from("tasks") - .select("id, title, status, priority, due_at") + .select("id, title, status, priority, due_at, is_public_to_client") .eq("project_id", id) .eq("user_id", user.id) .order("created_at", { ascending: false }), @@ -89,6 +90,11 @@ export default async function ProjectDetailPage({ .eq("project_id", id) .eq("user_id", user.id) .order("transaction_date", { ascending: false }), + supabase + .from("project_revisions") + .select("id, description, status, created_at, requested_by") + .eq("project_id", id) + .order("created_at", { ascending: false }), ]); if (!projectRow) { @@ -129,7 +135,9 @@ export default async function ProjectDetailPage({ status: normalizeTaskStatus(task.status), priority: normalizeTaskPriority(task.priority), due_at: task.due_at, + is_public_to_client: task.is_public_to_client || false, })); + const revisions = revisionRows || []; const financeTransactions: ProjectFinanceItem[] = ((financeRows || []) as FinanceRow[]).map( (transaction) => ({ id: transaction.id, @@ -148,6 +156,7 @@ export default async function ProjectDetailPage({ sections={sections} tasks={tasks} financeTransactions={financeTransactions} + revisions={revisions} /> ); } diff --git a/app/(dashboard)/projects/[id]/project-detail-client.tsx b/app/(dashboard)/projects/[id]/project-detail-client.tsx index bb4af79..5681aa4 100644 --- a/app/(dashboard)/projects/[id]/project-detail-client.tsx +++ b/app/(dashboard)/projects/[id]/project-detail-client.tsx @@ -87,6 +87,7 @@ export type ProjectDetailTaskItem = { status: "todo" | "in_progress" | "done"; priority: "low" | "medium" | "high" | "urgent"; due_at: string | null; + is_public_to_client: boolean; }; export type ProjectFinanceItem = { @@ -104,6 +105,7 @@ type ProjectDetailClientProps = { sections: ProjectPlanningSectionItem[]; tasks: ProjectDetailTaskItem[]; financeTransactions: ProjectFinanceItem[]; + revisions: any[]; }; const typeLabels = { @@ -168,8 +170,9 @@ export function ProjectDetailClient({ sections, tasks, financeTransactions, + revisions, }: ProjectDetailClientProps) { - const [activeTab, setActiveTab] = useState<"planning" | "design" | "tasks" | "finance">( + const [activeTab, setActiveTab] = useState<"planning" | "design" | "tasks" | "finance" | "revisions">( "planning", ); const planningSections = sections.filter((section) => @@ -290,6 +293,14 @@ export function ProjectDetailClient({ setActiveTab("finance")}> Finans + {revisions.length > 0 && ( + setActiveTab("revisions")}> + Revizyonlar + + {revisions.filter(r => r.status === 'pending').length} + + + )} {activeTab === "planning" ? ( @@ -316,10 +327,66 @@ export function ProjectDetailClient({ ) : null} {activeTab === "finance" ? : null} + {activeTab === "revisions" ? : null} ); } +function RevisionsPanel({ projectId, revisions }: { projectId: string; revisions: any[] }) { + const [isUpdating, setIsUpdating] = useState(false); + + async function handleStatusChange(id: string, status: string) { + setIsUpdating(true); + try { + const { updateRevisionStatus } = await import("@/app/(dashboard)/projects/actions"); + await updateRevisionStatus(id, projectId, status); + } catch (err: any) { + console.error(err); + } finally { + setIsUpdating(false); + } + } + + return ( + + +

Müşteri Revizyon Talepleri

+ {revisions.length === 0 ? ( +

Bu proje için henüz bir revizyon talebi oluşturulmamış.

+ ) : ( +
+ {revisions.map(rev => ( +
+
+
+ {new Date(rev.created_at).toLocaleString('tr-TR')} +
+ +
+

{rev.description}

+
+ ))} +
+ )} +
+
+ ); +} + function SectionGrid({ projectId, title, @@ -581,14 +648,19 @@ function TaskPanel({ className="grid gap-4 px-4 py-4 lg:grid-cols-[1.5fr_0.8fr_0.8fr_0.8fr] lg:items-center" >
-
- {task.title} +
+
+ {task.title} +
+ {task.is_public_to_client && ( + Müşteriye Açık + )}
{task.status === "done" @@ -822,6 +894,27 @@ function ProjectTaskDialog({
+ +
+ +
+ +

+ Eğer müşteri hesabı varsa, bu görev müşteri portalındaki proje detayında da görünür olur. +

+
+
+
diff --git a/app/(dashboard)/projects/actions.ts b/app/(dashboard)/projects/actions.ts index f498af7..282cdff 100644 --- a/app/(dashboard)/projects/actions.ts +++ b/app/(dashboard)/projects/actions.ts @@ -320,3 +320,19 @@ export async function deleteProjectPlanningSectionRecord(formData: FormData) { revalidatePath("/projects"); revalidatePath(`/projects/${projectId}`); } + +export async function updateRevisionStatus(id: string, projectId: string, status: string) { + const { supabase } = await getCurrentUserId(); + + const { error } = await supabase + .from("project_revisions") + .update({ status }) + .eq("id", id) + .eq("project_id", projectId); + + if (error) { + throw new Error(`Revizyon durumu güncellenemedi: ${error.message}`); + } + + revalidatePath(`/projects/${projectId}`); +} diff --git a/app/(dashboard)/tasks/actions.ts b/app/(dashboard)/tasks/actions.ts index 6053c7f..c63c0cb 100644 --- a/app/(dashboard)/tasks/actions.ts +++ b/app/(dashboard)/tasks/actions.ts @@ -60,6 +60,7 @@ function readPayload(formData: FormData) { due_at: cleanText(formData.get("due_at")), estimated_minutes: readMinutes(formData.get("estimated_minutes")), actual_minutes: readMinutes(formData.get("actual_minutes")), + is_public_to_client: formData.get("is_public_to_client") === "on", }; }