From 3582e26970bff6309b4b48bf7cefb9b3b5448206 Mon Sep 17 00:00:00 2001 From: Simone Cavalli Date: Sat, 16 May 2026 12:24:49 +0200 Subject: [PATCH] feat: document edit inline + client dashboard sidebar layout - actions.ts: add updateDocument server action (label + url, Zod validated) - DocumentRow: Client Component with hover-reveal edit/remove buttons, inline edit form with pre-filled fields and cancel/save - DocumentsTab: use DocumentRow, remove variant dependency - client-dashboard: two-column layout (sidebar left on lg+): sidebar = payments + documents + notes (sticky top) main = brief + phases toggle (timeline / kanban) mobile: main first, sidebar below (order-1/order-2) Co-Authored-By: Claude Sonnet 4.6 --- src/app/admin/clients/[id]/actions.ts | 17 +++ src/components/admin/DocumentRow.tsx | 114 +++++++++++++++++++++ src/components/admin/tabs/DocumentsTab.tsx | 38 ++----- src/components/client-dashboard.tsx | 114 +++++++++++---------- 4 files changed, 199 insertions(+), 84 deletions(-) create mode 100644 src/components/admin/DocumentRow.tsx diff --git a/src/app/admin/clients/[id]/actions.ts b/src/app/admin/clients/[id]/actions.ts index 2bf835a..15ad379 100644 --- a/src/app/admin/clients/[id]/actions.ts +++ b/src/app/admin/clients/[id]/actions.ts @@ -117,6 +117,23 @@ export async function addDocument(clientId: string, formData: FormData) { revalidatePath(`/admin/clients/${clientId}`); } +export async function updateDocument( + documentId: string, + clientId: string, + formData: FormData +) { + const parsed = docSchema.safeParse({ + label: formData.get("label"), + url: formData.get("url"), + }); + if (!parsed.success) throw new Error(parsed.error.issues[0].message); + await db + .update(documents) + .set(parsed.data) + .where(eq(documents.id, documentId)); + revalidatePath(`/admin/clients/${clientId}`); +} + export async function deleteDocument(documentId: string, clientId: string) { await db.delete(documents).where(eq(documents.id, documentId)); revalidatePath(`/admin/clients/${clientId}`); diff --git a/src/components/admin/DocumentRow.tsx b/src/components/admin/DocumentRow.tsx new file mode 100644 index 0000000..b306026 --- /dev/null +++ b/src/components/admin/DocumentRow.tsx @@ -0,0 +1,114 @@ +"use client"; + +import { useState, useTransition } from "react"; +import { useRouter } from "next/navigation"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { updateDocument, deleteDocument } from "@/app/admin/clients/[id]/actions"; +import type { Document } from "@/db/schema"; + +export function DocumentRow({ + doc, + clientId, +}: { + doc: Document; + clientId: string; +}) { + const [editing, setEditing] = useState(false); + const [error, setError] = useState(null); + const [, startTransition] = useTransition(); + const router = useRouter(); + + function handleSave(fd: FormData) { + setError(null); + startTransition(async () => { + try { + await updateDocument(doc.id, clientId, fd); + setEditing(false); + router.refresh(); + } catch (e) { + setError(e instanceof Error ? e.message : "Errore nel salvataggio"); + } + }); + } + + function handleDelete() { + startTransition(async () => { + await deleteDocument(doc.id, clientId); + router.refresh(); + }); + } + + if (editing) { + return ( +
+ + + {error &&

{error}

} +
+ + +
+
+ ); + } + + return ( +
+ + {doc.label} + +
+ + +
+
+ ); +} \ No newline at end of file diff --git a/src/components/admin/tabs/DocumentsTab.tsx b/src/components/admin/tabs/DocumentsTab.tsx index 036bdec..89757a5 100644 --- a/src/components/admin/tabs/DocumentsTab.tsx +++ b/src/components/admin/tabs/DocumentsTab.tsx @@ -1,4 +1,5 @@ -import { addDocument, deleteDocument } from "@/app/admin/clients/[id]/actions"; +import { addDocument } from "@/app/admin/clients/[id]/actions"; +import { DocumentRow } from "@/components/admin/DocumentRow"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -14,9 +15,9 @@ export async function DocumentsTab({ documents, clientId }: Props) { "use server"; await addDocument(clientId, fd); }} - className="bg-white border border-gray-200 rounded-lg p-4 space-y-3" + className="bg-white border border-[#e5e7eb] rounded-lg p-4 space-y-3" > -

Aggiungi documento

+

Aggiungi documento

{documents.length === 0 && ( -

Nessun documento ancora.

+

Nessun documento ancora.

)}
{documents.map((doc) => ( -
- - {doc.label} - -
{ - "use server"; - await deleteDocument(doc.id, clientId); - }} - > - -
-
+ ))}
diff --git a/src/components/client-dashboard.tsx b/src/components/client-dashboard.tsx index 3581fec..3aa75d3 100644 --- a/src/components/client-dashboard.tsx +++ b/src/components/client-dashboard.tsx @@ -16,21 +16,16 @@ interface ClientDashboardProps { export function ClientDashboard({ view, token, comments }: ClientDashboardProps) { return (
- {/* Header: logo iamcavalli (piccolo) + brand_name cliente (prominente) */} + {/* Header */}
-
+
- {/* iamcavalli — piccolo, angolo sinistro */} iamcavalli - - {/* Brand name cliente — prominente, centrato */}

{view.client.brand_name}

- - {/* Spacer bilanciatore */}
@@ -38,7 +33,7 @@ export function ClientDashboard({ view, token, comments }: ClientDashboardProps) {/* Barra progresso globale */}
-
+

Avanzamento Progetto

{view.global_progress_pct}%

@@ -47,58 +42,73 @@ export function ClientDashboard({ view, token, comments }: ClientDashboardProps)
- {/* Contenuto principale */} -
- {/* Brief progetto */} - {view.client.brief && ( -
-

- {view.client.brief} -

-
- )} + {/* Layout principale: sidebar sinistra + contenuto destro */} +
+
- {/* Fasi — toggle timeline/kanban */} -
- } - phases={view.phases} - token={token} - comments={comments} - /> -
+ {/* ── Sidebar sinistra ── */} +
+ {/* Note — solo se presenti */} + {view.notes.length > 0 && ( +
+

+ Note & Decisioni +

+ +
+ )} +
+ + + {/* ── Contenuto principale ── */} +
+ {/* Brief progetto */} + {view.client.brief && ( +

+ {view.client.brief} +

+ )} + + {/* Fasi — toggle timeline/kanban */} + + } + phases={view.phases} + token={token} + comments={comments} + /> +
+ +
+
{/* Footer */}