diff --git a/src/app/admin/clients/[id]/page.tsx b/src/app/admin/clients/[id]/page.tsx new file mode 100644 index 0000000..6913b1f --- /dev/null +++ b/src/app/admin/clients/[id]/page.tsx @@ -0,0 +1,72 @@ +import { notFound } from "next/navigation"; +import { getClientFullDetail } from "@/lib/admin-queries"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { PhasesTab } from "@/components/admin/tabs/PhasesTab"; +import { PaymentsTab } from "@/components/admin/tabs/PaymentsTab"; +import { DocumentsTab } from "@/components/admin/tabs/DocumentsTab"; +import { CommentsTab } from "@/components/admin/tabs/CommentsTab"; +import Link from "next/link"; + +export const revalidate = 0; + +export default async function ClientDetailPage({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const { id } = await params; + const detail = await getClientFullDetail(id); + if (!detail) notFound(); + + const { client, phases, payments, documents, notes, comments } = detail; + + return ( +
+
+ + ← Clienti + +
+
+
+

{client.name}

+

{client.brand_name}

+
+ + Link cliente → + +
+ + + + Fasi & Task + Pagamenti + Documenti + Commenti + + + + + + + + + + + + + + + +
+ ); +} \ No newline at end of file diff --git a/src/components/admin/tabs/CommentsTab.tsx b/src/components/admin/tabs/CommentsTab.tsx new file mode 100644 index 0000000..baaa383 --- /dev/null +++ b/src/components/admin/tabs/CommentsTab.tsx @@ -0,0 +1,105 @@ +import { postAdminComment } from "@/app/admin/clients/[id]/actions"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import type { Comment } from "@/db/schema"; +import type { ClientFullDetail } from "@/lib/admin-queries"; + +type Props = { + comments: Comment[]; + phases: ClientFullDetail["phases"]; + clientId: string; +}; + +export async function CommentsTab({ comments, phases, clientId }: Props) { + // Build entity label map for display + const entityLabels: Record = {}; + for (const phase of phases) { + for (const task of phase.tasks) { + entityLabels[task.id] = `Task: ${task.title}`; + for (const d of task.deliverables) { + entityLabels[d.id] = `Deliverable: ${d.title}`; + } + } + } + + // Build list of entities the admin can reply on + const entities: Array<{ id: string; type: string; label: string }> = []; + for (const phase of phases) { + for (const task of phase.tasks) { + entities.push({ id: task.id, type: "task", label: `Task: ${task.title}` }); + for (const d of task.deliverables) { + entities.push({ + id: d.id, + type: "deliverable", + label: `Deliverable: ${d.title}`, + }); + } + } + } + + return ( +
+ {/* Comment list */} + {comments.length === 0 && ( +

Nessun commento ancora.

+ )} +
+ {comments.map((c) => ( +
+
+

+ {c.author === "admin" ? "iamcavalli" : "Cliente"} —{" "} + {entityLabels[c.entity_id] ?? c.entity_id} +

+

{c.body}

+
+
+ ))} +
+ + {/* Admin reply form */} + {entities.length > 0 && ( +
{ + "use server"; + await postAdminComment(clientId, fd); + }} + className="bg-white border border-gray-200 rounded-lg p-4 space-y-3" + > +

+ Rispondi come admin +

+ +