diff --git a/src/app/api/client/comment/route.ts b/src/app/api/client/comment/route.ts index 68c7285..cdbd66c 100644 --- a/src/app/api/client/comment/route.ts +++ b/src/app/api/client/comment/route.ts @@ -6,7 +6,7 @@ import { clients, comments, tasks, phases, deliverables } from "@/db/schema"; const commentSchema = z.object({ token: z.string().min(1), - entity_type: z.enum(["task", "deliverable"]), + entity_type: z.enum(["task", "deliverable", "general"]), entity_id: z.string().min(1), body: z.string().min(1, "Il commento non può essere vuoto").max(2000), }); @@ -38,59 +38,50 @@ export async function POST(request: NextRequest) { const clientId = clientRows[0].id; - // Verify entity belongs to this client (prevent cross-client comment injection) - if (entity_type === "task") { + if (entity_type === "general") { + // General messages: entity_id must be the client's own id + if (entity_id !== clientId) { + return NextResponse.json({ error: "Entity non valida" }, { status: 403 }); + } + } else if (entity_type === "task") { const phasesForClient = await db .select({ id: phases.id }) .from(phases) .where(eq(phases.client_id, clientId)); const phaseIds = phasesForClient.map((p) => p.id); - if (phaseIds.length === 0) { return NextResponse.json({ error: "Nessuna fase trovata" }, { status: 404 }); } - const taskRows = await db .select({ id: tasks.id }) .from(tasks) .where(inArray(tasks.phase_id, phaseIds)); - - const taskCheck = taskRows.find((r) => r.id === entity_id); - - if (!taskCheck) { + if (!taskRows.find((r) => r.id === entity_id)) { return NextResponse.json({ error: "Task non trovato" }, { status: 404 }); } } else { - // deliverable — verify via task → phase → client chain + // deliverable const phasesForClient = await db .select({ id: phases.id }) .from(phases) .where(eq(phases.client_id, clientId)); const phaseIds = phasesForClient.map((p) => p.id); - if (phaseIds.length === 0) { return NextResponse.json({ error: "Nessuna fase trovata" }, { status: 404 }); } - const taskRows = await db .select({ id: tasks.id }) .from(tasks) .where(inArray(tasks.phase_id, phaseIds)); - const taskIds = taskRows.map((r) => r.id); - if (taskIds.length === 0) { return NextResponse.json({ error: "Nessun task trovato" }, { status: 404 }); } - const delivRows = await db .select({ id: deliverables.id }) .from(deliverables) .where(inArray(deliverables.task_id, taskIds)); - - const delivCheck = delivRows.find((r) => r.id === entity_id); - - if (!delivCheck) { + if (!delivRows.find((r) => r.id === entity_id)) { return NextResponse.json({ error: "Deliverable non trovato" }, { status: 404 }); } } diff --git a/src/app/c/[token]/page.tsx b/src/app/c/[token]/page.tsx index 545d612..761ba24 100644 --- a/src/app/c/[token]/page.tsx +++ b/src/app/c/[token]/page.tsx @@ -42,12 +42,12 @@ export default async function ClientPage({ notFound(); } - // Fetch comments for all tasks and deliverables in this client's data + // Fetch comments: tasks, deliverables, and general (entity_id = clientId) const allTaskIds = view.phases.flatMap((p) => p.tasks.map((t) => t.id)); const allDeliverableIds = view.phases.flatMap((p) => p.tasks.flatMap((t) => t.deliverables.map((d) => d.id)) ); - const allEntityIds = [...allTaskIds, ...allDeliverableIds]; + const allEntityIds = [view.client.id, ...allTaskIds, ...allDeliverableIds]; const allComments: Comment[] = allEntityIds.length > 0 diff --git a/src/components/client-dashboard.tsx b/src/components/client-dashboard.tsx index 3aa75d3..3ca72d1 100644 --- a/src/components/client-dashboard.tsx +++ b/src/components/client-dashboard.tsx @@ -6,6 +6,7 @@ import { PaymentStatus } from './payment-status'; import { DocumentsSection } from './documents-section'; import { NotesSection } from './notes-section'; import { PhaseViewToggle } from './client/kanban/PhaseViewToggle'; +import { ChatSection } from './client/ChatSection'; interface ClientDashboardProps { view: ClientView; @@ -42,39 +43,24 @@ export function ClientDashboard({ view, token, comments }: ClientDashboardProps) - {/* Layout principale: sidebar sinistra + contenuto destro */} + {/* Layout principale */}
{/* ── Sidebar sinistra ── */} {/* ── Contenuto principale ── */} -
- {/* Brief progetto */} +
{view.client.brief && (

{view.client.brief}

)} - {/* Fasi — toggle timeline/kanban */} - } + timelineView={} phases={view.phases} token={token} - comments={comments} /> + + {/* Chat revisioni */} +
+

Messaggi & Revisioni

+ +
- {/* Footer */}