---
plan: "04-02"
phase: "04-progetti-multi-project"
status: complete
completed_at: "2026-05-22"
---

# Summary: 04-02 — Admin Projects List + Client Detail Project Cards

## What Was Built

**NavBar** — added Progetti (`/admin/projects`) and Impostazioni (`/admin/impostazioni`) links in the correct order: Clienti → Progetti → Statistiche → Catalogo → Impostazioni.

**ProjectRow** (`src/components/admin/ProjectRow.tsx`) — new table row component cloned from ClientRow, adapted for projects. Columns: Nome+Cliente, Valore, Acconto badge, Saldo badge, TimerCell, €/h. The €/h is computed inline: `accepted_total / (totalTrackedSeconds / 3600)`, showing `—` when no hours tracked.

**`/admin/projects`** (`src/app/admin/projects/page.tsx`) — table listing all projects via `getAllProjectsWithPayments()`. Empty state shows helpful message.

**`/admin/projects/new`** (`src/app/admin/projects/new/page.tsx`) — creation form with client `<select>`. Accepts `?client_id` query param to pre-select the client (used when clicking "+ Nuovo Progetto" from a client's detail page). On submit calls `createProject` server action and redirects to the new project's workspace.

**project-actions** (`src/app/admin/projects/project-actions.ts`) — server actions: `createProject`, `archiveProject`, `unarchiveProject`, `updateProjectAcceptedTotal`. Each guards with local `requireAdmin()` (same pattern as other actions files — not imported from `@/lib/auth` which doesn't export it).

**`/admin/clients/[id]`** — fully rewritten from tabbed workspace to project cards grid. Active projects shown as clickable cards linking to `/admin/projects/[id]`. Archived projects shown below with reduced opacity. Header retains "Link cliente →" and `ClientActions` component.

**`getAllClientsWithPayments`** — extended to also fetch project names/amounts per client, computing:
- `projectBrands: string[]` — non-archived project names, used as secondary labels under client name
- `ltv: string` — sum of all projects' `accepted_total` (including archived), replaces single `accepted_total` in the LTV column

**ClientRow** — shows `projectBrands.join(" | ")` under client name (falls back to `brand_name` if no projects). LTV column now uses `client.ltv`.

**`/admin/page.tsx`** — column header renamed from "Totale" to "LTV".

## Key Implementation Decisions

- **`?client_id` pre-selection in new project form**: used `defaultValue={client_id ?? ""}` on the `<select>` — React controlled default, works with server components.
- **No `requireAdmin` import**: all server actions files define it locally with the same `getServerSession(authOptions)` pattern — consistent with existing codebase.
- **`clientProjects` query extended**: instead of a separate parallel query for projectBrands/ltv, the existing `clientProjects` fetch was extended to also select `name`, `accepted_total`, `archived` — avoids an extra DB round-trip.

## Files Modified/Created

| File | Action |
|------|--------|
| `src/components/admin/NavBar.tsx` | Updated — added 2 links |
| `src/components/admin/ProjectRow.tsx` | Created |
| `src/app/admin/projects/page.tsx` | Created |
| `src/app/admin/projects/new/page.tsx` | Created |
| `src/app/admin/projects/project-actions.ts` | Created |
| `src/app/admin/clients/[id]/page.tsx` | Rewritten |
| `src/app/admin/page.tsx` | Updated — column header |
| `src/lib/admin-queries.ts` | Updated — projectBrands + ltv |
| `src/components/admin/ClientRow.tsx` | Updated — projectBrands + ltv |