- Create src/app/admin/catalog/actions.ts with createService, updateService, toggleServiceActive
- Each action includes requireAdmin() guard via getServerSession
- Zod validation: name (min 1), unit_price (coerce.number min 0.01)
- Add getAllServices() to src/lib/admin-queries.ts ordered by name
- Import service_catalog and ServiceCatalog types in admin-queries.ts
- Remove .notNull() from service_id to allow free-form items without catalog ref
- Add custom_label: text("custom_label") for free-form item label storage
- TypeScript compiles with zero errors (QuoteItem.service_id now string | null)
Schema:
- clients.archived boolean (default false)
- time_entries table (client_id, started_at, ended_at, duration_seconds)
Client management:
- /admin/clients/[id]/edit — form pre-compilato con nome, brand, brief
- ClientActions: Modifica / Archivia / Elimina con doppia conferma
- setClientArchived: toggle archiviazione senza perdere dati
- deleteClient: elimina con cascade, redirect a /admin
- Admin list: toggle "Mostra archiviati" via ?archived=1, righe archiviate opache
Time tracker:
- startTimer: crea sessione, ferma automaticamente quella precedente
- stopTimer: chiude sessione, calcola duration_seconds
- TimerCell: ▶/⏹ per ogni cliente, contatore live in secondi, totale cumulativo
- Una sola sessione attiva alla volta su tutta la lista
Analytics:
- Sezione "Fatturato" (invariata) + sezione "Tempo tracciato" separata
- Ore totali per anno + barre orizzontali per cliente
- getTotalTrackedHours, getTimeByClient queries
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Chat revisioni: rimuovi commenti inline da timeline/kanban, aggiungi
ChatSection con feed cronologico + selector task opzionale (Invio per inviare)
Bolle stile chat: Tu (destra, giallo) / iamcavalli (sinistra, verde)
Tag task su ogni messaggio quando il messaggio non è generale
- API /api/client/comment: supporto entity_type "general" (entity_id = clientId)
- Pagina /admin/analytics: year selector ←→, 4 metric card (contrattualizzato,
incassato, da incassare, clienti acquisiti), bar chart mensile incassato via CSS
- NavBar: link "Statistiche"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Provides getAnalyticsByYear, getMonthlyCollected, and getAvailableYears
to power the admin dashboard analytics view (not yet wired to a page).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removed per-task/deliverable CommentList/CommentForm from PhaseTimeline and
ClientKanban. Replaced with a single ChatSection at the bottom of the dashboard
that handles general, task, and deliverable messages in a unified chat UI.
Added "general" entity_type to the comment API (entity_id = client UUID).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix button contrast: add all missing shadcn tokens (primary-foreground,
ring, input, muted, destructive) aligned to iamcavalli brand
- NavBar: #1A463C green bar with white text
- Login page: clean brand layout with iamcavalli wordmark
- Admin pages: brand colors on headings, borders, links
- Admin ClientRow: semantic payment badges (green/yellow/red)
- Admin phases tab: Lista ↔ Kanban toggle with @dnd-kit drag & drop
between Da fare / In corso / Fatto columns (optimistic updates)
- Client dashboard: Timeline ↔ Kanban toggle, expandable task cards
with approve button + comment form inline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ApproveButton: 'use client', POSTs to /api/client/approve with token + deliverableId, calls router.refresh(); shows immutable "Approvato il [date]" badge once approved_at is set
- CommentForm: 'use client', POSTs to /api/client/comment, calls router.refresh() on success; clears textarea after submit
- CommentList: presentational Server Component, labels client author as "Tu" and admin as "iamcavalli"
- page.tsx: fetches all comments server-side (scoped to client's task/deliverable ids), passes token + comments to ClientDashboard; revalidate=0 ensures approvals and comments always fresh
- client-dashboard.tsx: passes token + comments down to PhaseTimeline
- phase-timeline.tsx: renders ApproveButton on each deliverable (pending/submitted/approved), CommentList + CommentForm below each deliverable and each task
- approve: validates token, checks deliverable ownership via phase→client join, sets status=approved + approved_at=now() only if approved_at is currently null (CLAUDE.md immutability rule enforced)
- comment: validates token, checks entity ownership (task or deliverable) via phase→client chain, inserts comment with author='client'
- both routes return 404 on invalid token or unknown entity
- neither route references quote_items (CLAUDE.md constraint enforced)
- Zod validation on comment body: min 1 char, max 2000 chars (T-02-20 DoS mitigation)
- Create /admin/clients/[id]/page.tsx — Server Component using Radix Tabs (Fasi & Task, Pagamenti, Documenti, Commenti)
- Create PhasesTab: phases list with add-phase form, task lists with add-task form, status selects for phases and tasks
- Create PaymentsTab: accepted_total editor (splits to 50% on each payment), payment status selects with paid_at on saldato
- Create DocumentsTab: add document (label + URL) form, document list with delete action
- Create CommentsTab: chronological comment display (admin vs cliente style), admin reply form with entity selector
- All mutations via inline Server Action closures bound to action= props; revalidatePath ensures fresh data
- /admin page: Server Component fetching all clients with payment badges
- ClientRow component with Acconto/Saldo status badges and secret link
- /admin/clients/new: form wired to createClient Server Action
- createClient action: Zod validation, inserts client + 2 payment stubs (Acconto 50%, Saldo 50%)
- Token auto-generated server-side via nanoid $defaultFn
- Redirects to /admin/clients/[id] after creation; revalidates /admin
- src/lib/admin-queries.ts: getAllClientsWithPayments() and getClientById() for admin DB reads
- src/components/admin/NavBar.tsx: minimal nav with Clienti link and Esci (logout) button
- src/app/admin/layout.tsx: wraps all /admin/* pages with NavBar + centered main content area
- scripts/seed.ts: inserts one complete test client with 3 phases,
6 tasks, 4 deliverables, 2 payments, 2 documents, 2 notes; prints
shareable URL to console
- globals.css: add @source not directives to exclude .01_projects/
and .claude/ — Tailwind v4 was scanning the SparklingOrbit .venv
Python files and generating invalid CSS class [-:|] from a regex
pattern in a markdown-it table parser, causing dev server 500s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DocumentsSection: link clickabili con target="_blank" rel="noopener noreferrer"
icone SVG inline per documento ed external link, hover state con colore accent
- NotesSection: note read-only con timestamp in locale it-IT (D-12: cliente legge, admin scrive)
empty state informativo per entrambi i componenti
- SVG inline al posto di lucide-react per compatibilita' massima
- Mostra accepted_total formattato in EUR (unico importo permesso — LOCKED)
- Righe pagamento: solo label + badge stato (da_saldare/inviata/saldato)
- Nessun importo singolo visibile al cliente (T-04-001 mitigato)
- Dot colorato + badge per ogni riga: blu=da_saldare, giallo=inviata, verde=saldato
- Layout a due colonne: indicatore cerchio (sinistra) + card fase (destra)
- Icone SVG inline per stato fase (done/active/upcoming) e task (done/in_progress/todo)
- Badge stato fase con colori semantici (verde/blu/grigio)
- Progress bar per fase con contatore task done/totale
- Task list con line-through per done, testo grigio chiaro
- Deliverable annidati sotto ogni task con badge "Approvato" se approved
- Linea verticale tra fasi (non sull'ultima)
- Connected to postgresql://178.104.27.55:5432/clienthub
- 10 tables created: clients, phases, tasks, deliverables, comments,
payments, documents, notes, service_catalog, quote_items
- UNIQUE constraint on clients.token active at DB level
- All FK constraints and CASCADE rules applied
- Verified via information_schema.tables query
- 10 tables: clients, phases, tasks, deliverables, comments, payments,
documents, notes, service_catalog, quote_items
- UNIQUE constraint on clients.token enforced at DB level
- All FK cascades correct (deliverables->tasks->phases->clients)
- approved_at: timestamp with time zone (nullable, immutable)
- drizzle.config.ts already correct from Plan 01 (no changes needed)
- clients: token as separate text field (notNull, unique, nanoid) — never PK
- accepted_total denormalized on clients — client API never touches quote_items
- deliverables.approved_at immutable timestamp (TIMESTAMPTZ) — audit trail
- payments: label (Acconto 50% / Saldo 50%), status (da_saldare/inviata/saldato)
- comments: polymorphic entity_type+entity_id pattern
- service_catalog + quote_items: admin-only, never exposed to client API
- Full relations defined for all FK chains
- TypeScript types exported: Client, Phase, Task, Deliverable, etc.
- ID strategy: text + nanoid() via $defaultFn (cryptographically secure, URL-safe)