Commit Graph

107 Commits

Author SHA1 Message Date
simone ce8f95a163 feat(05-02): /admin/offers RSC page + ServiceCheckboxList + NavBar update
- Create src/app/admin/offers/page.tsx — RSC with macro/micro/service CRUD sections
- Create src/components/admin/offers/ServiceCheckboxList.tsx — client checkbox UI for service-to-micro assignment
- Update src/components/admin/NavBar.tsx — add Forecast and Offerte links (order: Statistiche | Forecast | Catalogo | Offerte | Impostazioni)
2026-05-30 16:22:19 +02:00
simone 56f084912a feat(05-02): add offer-queries.ts and server actions for offer catalog CRUD
- Create src/lib/offer-queries.ts with getCatalogWithMicros, getAllOfferServices, getMicroAssignedServiceIds
- Create src/app/admin/offers/actions.ts with createMacro, deleteMacro, createMicro, deleteMicro, createOfferService, toggleOfferServiceActive, updateMicroOfferServices
- All exported server actions guard with requireAdmin() + revalidatePath(/admin/offers)
2026-05-30 15:35:54 +02:00
simone f0034410ff feat(05-01): append five offer tables + Drizzle relations to schema.ts
- Add primaryKey to drizzle-orm/pg-core import
- Define offer_macros, offer_micros, offer_services, offer_micro_services, project_offers
- Add Drizzle relations for all five new tables (+ projectOffers in projectsRelations)
- Export TypeScript infer types for all new tables
2026-05-30 15:26:02 +02:00
simone 75097ccfa4 feat: init payments + split payment in project workspace
- initProjectPayments: crea Acconto 50%/Saldo 50% se il progetto non ha pagamenti
- splitPayment: divide un pagamento in due rate con importi custom
- SplitPaymentForm: form client interattivo con anteprima live della rata 2
- PaymentsTab: stato vuoto con CTA + pulsante Dividi per ogni riga
- projectId prop opzionale per attivare le feature solo nel contesto progetto
2026-05-23 10:18:06 +02:00
simone 5efb433c23 fix(04-07): remove debug code — public page restored
Root cause: INTERNAL_SECRET was on same line as HOST= in .env (no trailing
newline when appended), so proxy middleware got empty secret and rejected
all /api/internal/validate-* calls → rewrote every /client/* request to
/not-found without running the page component.
2026-05-23 09:03:06 +02:00
simone 25ae19c9d2 debug: page-level file logging 2026-05-22 20:34:19 +02:00
simone d67be3b6b4 debug: file-based logging to /tmp/clienthub-debug.log 2026-05-22 20:18:31 +02:00
simone bc2fcc8670 debug: add generateMetadata log
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:54:58 +02:00
simone fca86fde73 debug: log token param in ClientPage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:44:56 +02:00
simone 78a65f6ced debug: log tokenOrSlug in getClientWithProjectsByToken
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:40:18 +02:00
simone 7f9150b75a debug: add error logging to getClientWithProjectsByToken
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:38:41 +02:00
simone a478462aa4 security: full hardening pass — auth guards, rate limiting, headers, internal secret
- Add requireAdmin() to all unprotected admin server actions
  (clients/new, clients/[id], timer-actions — 17 functions total)
- Protect /api/internal/* endpoints with X-Internal-Secret header
  (proxy.ts sends it; routes reject requests without it)
- Randomize auto-generated client slugs with 4-char suffix
  to prevent enumeration via predictable name-based slugs
- Add in-memory rate limiting to /api/client/approve (20/min)
  and /api/client/comment (10/min) per IP
- Add security headers: X-Frame-Options, X-Content-Type-Options,
  Referrer-Policy, Permissions-Policy, X-DNS-Prefetch-Control
- Reduce JWT session from 30 days to 7 days with daily rotation
- Remove hardcoded NEXTAUTH_URL from Dockerfile (pass via Coolify env)
- Genericize client API error messages to not leak data structure
- Update .env.example with all required variables including INTERNAL_SECRET

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 14:36:16 +02:00
simone eab88c9f63 fix(04-06): five post-deploy fixes
- Middleware (proxy.ts): switch internal API calls to localhost instead of
  request.url — avoids Docker hairpin NAT issues where the container can't
  reach its own external hostname via Traefik
- ProjectRow timer: pass projectId={project.id} to TimerCell so it calls
  startTimer(projectId) directly instead of startTimerForClient(project.id)
- Admin client list: add slug field to ClientWithPayments + show slug link
  when available (falls back to token) — so admins see the human-readable URL
- Analytics contracted: sum projects.accepted_total (authoritative) instead
  of clients.accepted_total (always 0 in multi-project architecture)
- createClient: auto-generate slug from client name at creation time
  (e.g. "Mario Rossi" → "mario-rossi"), with -2/-3 suffix on conflict

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 13:42:38 +02:00
simone e6a9774cfe fix(04-05): three post-deploy bug fixes
- Admin client list: remove Timer column, rename Acconto→Importo incassato
  and Saldo→Importo da saldare (calculated from payment amounts, not badges)
- Project workspace timer: pass projectId prop to TimerCell so it calls
  startTimer(projectId) directly instead of startTimerForClient(projectId)
  which was failing with "nessun progetto trovato"
- Public client page: pass client.token (real DB token) not the URL path
  segment (which may be a slug) to ClientDashboard → fixes approve/chat APIs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 12:13:31 +02:00
simone 5d785a1c1c feat(04-04): slug resolution + multi-project client dashboard + slug edit
- validate-slug API route: resolves clients.slug for Edge middleware
- proxy.ts: slug-first resolution (D-06) — tries slug then falls back to token
- client-view.ts: complete rewrite — getClientWithProjectsByToken + getProjectView
  - No quote_items, no payment amounts in client API (CLAUDE.md security invariants)
- client/[token]/page.tsx: multi-project dashboard — 1 project = direct view,
  2+ projects = shadcn Tabs with project names (D-09/D-10)
- edit/page.tsx: slug field with link preview + unique constraint error handling
- actions.ts: updateClient now persists slug, redirects on success/slug_taken

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 11:31:32 +02:00
simone 1d9fa10961 feat(04-03): project workspace + timer analytics + settings page
- /admin/projects/[id]: full workspace with 7 tabs (Fasi, Pagamenti,
  Documenti, Note, Commenti, Preventivo, Timer)
- TimerTab: TimerCell + ProfitabilityCard with €/h real, ideal cost, delta
- ProfitabilityCard: hours worked, €/h real vs target, profit/loss delta
- /admin/impostazioni: target_hourly_rate setting (default 50€/h)
- actions.ts: resolveEntity() helper — actions now work transparently
  with both clientId and projectId (same tab components reused)
- quote-actions.ts: same resolveEntity pattern for addQuoteItem,
  removeQuoteItem, updateAcceptedTotal
- timer-actions.ts: revalidate /admin/projects and /admin/projects/[id]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 11:05:31 +02:00
simone ef05d647fe feat(04-02): admin projects list + client detail project cards
- NavBar: add Progetti (/admin/projects) and Impostazioni links
- ProjectRow: project table row with €/h calc (accepted_total / tracked hours)
- /admin/projects: table listing all projects with value, payments, timer, €/h
- /admin/projects/new: create form with client select + ?client_id pre-selection
- project-actions: createProject, archiveProject, unarchiveProject, updateProjectAcceptedTotal
- /admin/clients/[id]: rewritten to show project cards grid instead of tabbed workspace
- getAllClientsWithPayments: add projectBrands[] and ltv (sum of project accepted_totals)
- ClientRow: show project brand names under client name, LTV column replaces Totale

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 08:45:10 +02:00
simone b92b1c447b fix(db): skip DATABASE_URL check during Next.js build phase
NEXT_PHASE=phase-production-build is set by Next.js during static analysis.
Route handlers are analyzed but not executed, so the DB client is never
actually used — the placeholder URL never connects to anything.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 22:11:36 +02:00
simone 63c9f750df feat(04-01): multi-project schema migration — projects, settings, FK pivot
- schema.ts: add projects table, settings kv table, slug on clients;
  migrate 6 FK from client_id to project_id (phases, payments, documents, notes,
  time_entries, quote_items); update all relations and TypeScript types
- admin-queries.ts: fix getAllClientsWithPayments + getClientFullDetail to aggregate
  through projects; add getAllProjectsWithPayments, getProjectFullDetail,
  getClientWithProjects, ClientWithProjects type
- settings.ts: new file — getSetting, updateSetting, getTargetHourlyRate, SETTINGS_KEYS
- Fix all downstream files: actions.ts, quote-actions.ts, new/actions.ts,
  timer-actions.ts, approve/route.ts, comment/route.ts, TimerCell.tsx,
  analytics-queries.ts, client-view.ts, seed.ts
- DB migration applied to Coolify Postgres (all test data cleared, schema rebuilt)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 21:58:15 +02:00
simone 5bf5dfce71 infra(04-00): route /c/ → /client/, Dockerfile, Gitea deploy
- Rename src/app/c/[token] → src/app/client/[token]
- Update proxy.ts, ClientRow, admin client detail with /client/ path
- Add output: "standalone" to next.config.ts for Docker build
- Add Dockerfile (multi-stage, node:20-alpine) and .dockerignore
- Push schema to Coolify Postgres via SSH tunnel (drizzle-kit push ✓)
- Update CLAUDE.md constraint 4 to reflect /client/ route
- Add Phase 4 planning artifacts (04-00, 04-RESEARCH, 04-PATTERNS)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 16:12:05 +02:00
Simone Cavalli 2aade028b8 fix(css): exclude .planning/ from Tailwind v4 source scan — prevents [-:|] regex comment from generating invalid CSS 2026-05-19 23:04:34 +02:00
Simone Cavalli 511c7d1d43 fix: resolve merge conflict in admin-queries.ts — keep both quote_items and service_catalog imports 2026-05-17 11:50:38 +02:00
Simone Cavalli 48f81e7110 feat(03-03): QuoteTab component + Preventivo tab in client detail page
- Create QuoteTab.tsx: catalog dropdown + freeform toggle + items table + accepted total editor
- Wire QuoteTab as 5th tab (Preventivo) in /admin/clients/[id]/page.tsx
- Destructure quoteItems + activeServices from getClientFullDetail result
- TypeScript clean, build passes
2026-05-17 11:44:57 +02:00
Simone Cavalli 4aae2e0d0f feat(03-02): catalog page + ServiceTable + ServiceForm + NavBar link
- Create src/app/admin/catalog/page.tsx: server component, fetches all services, renders ServiceForm + ServiceTable
- Create src/components/admin/catalog/ServiceForm.tsx: add-new-service form with open/collapse toggle
- Create src/components/admin/catalog/ServiceTable.tsx: per-row inline edit (DocumentRow pattern), Attivo/Disattivato badges, opacity-50 for inactive
- Modify src/components/admin/NavBar.tsx: add Catalogo link after Statistiche
- TypeScript clean, npm run build compiled successfully
2026-05-17 11:43:49 +02:00
Simone Cavalli db81829b85 feat(03-03): Server Actions quote CRUD + extend getClientFullDetail
- Create quote-actions.ts: addQuoteItem, removeQuoteItem, updateAcceptedTotal
- All three actions guarded by requireAdmin() + Zod validation
- Extend admin-queries.ts: QuoteItemWithLabel type, quoteItems + activeServices queries
- quote_items NEVER exposed via client-facing routes (security constraint enforced)
2026-05-17 11:42:44 +02:00
Simone Cavalli efbc235c6e feat(03-02): server actions + getAllServices query for service catalog
- 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
2026-05-17 11:41:55 +02:00
Simone Cavalli 9ddb69929a feat(03-01): make quote_items.service_id nullable and add custom_label column
- 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)
2026-05-17 11:36:43 +02:00
Simone Cavalli e4d4dfa5be fix: remove brief from public client dashboard (internal data only)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 21:38:56 +02:00
Simone Cavalli 0f48570cd7 feat: client edit/delete/archive + time tracker + analytics time section
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>
2026-05-16 21:28:01 +02:00
Simone Cavalli d322162c0a feat: chat globale revisioni + pagina statistiche admin
- 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>
2026-05-16 12:52:25 +02:00
Simone Cavalli 457656a2a9 feat: add analytics query helpers for admin financial reporting
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>
2026-05-16 12:49:21 +02:00
Simone Cavalli 549cf0b592 feat: unify client messaging into ChatSection + add general comment type
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>
2026-05-16 12:49:14 +02:00
Simone Cavalli 3582e26970 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 <noreply@anthropic.com>
2026-05-16 12:24:49 +02:00
Simone Cavalli 7af917fe80 feat: brand color system + Kanban view (admin + client)
- 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>
2026-05-15 23:14:29 +02:00
Simone Cavalli dc512ec758 feat(02-04): add ApproveButton, CommentForm, CommentList; wire interactive elements into client dashboard
- 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
2026-05-15 21:50:07 +02:00
Simone Cavalli c24bdde603 feat(02-04): add POST /api/client/approve and POST /api/client/comment API routes
- 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)
2026-05-15 21:39:32 +02:00
Simone Cavalli 59a46d37fa feat(02-03): build /admin/clients/[id] workspace with tabbed layout and all tab components
- 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
2026-05-15 21:16:10 +02:00
Simone Cavalli 7733566f5b feat(02-03): install @radix-ui/react-tabs, add getClientFullDetail, create Server Actions
- Add shadcn tabs component (src/components/ui/tabs.tsx) backed by @radix-ui/react-tabs
- Extend admin-queries.ts with getClientFullDetail() — fetches client + phases + tasks + deliverables + payments + documents + notes + comments in one call
- Create src/app/admin/clients/[id]/actions.ts with all mutations: addPhase, updatePhaseStatus, addTask, updateTaskStatus, addDeliverable, addDocument, deleteDocument, updatePaymentStatus, updateAcceptedTotal, postAdminComment
- All actions include server-side allowlist validation and revalidatePath
- approved_at immutability enforced by omission in addDeliverable
2026-05-15 20:10:10 +02:00
Simone Cavalli f77051a3fc feat(02-02): add admin client list page and create-client flow
- /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
2026-05-15 18:18:22 +02:00
Simone Cavalli 7029583475 feat(02-02): add admin-queries, NavBar, and admin layout
- 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
2026-05-15 10:45:59 +02:00
Simone Cavalli 69f8a7eae3 feat(02-01): extend proxy.ts with admin session guard, add login page
- Extend src/proxy.ts to guard /admin/* routes with getToken() JWT check
- /admin/login and /api/auth/* exempted from session guard (pass-through)
- Unauthenticated /admin/* requests redirect to /admin/login?callbackUrl=...
- /c/:path* client token validation logic preserved unchanged
- matcher updated: ["/admin/:path*", "/c/:path*"]
- Create src/app/admin/login/page.tsx: email+password form, signIn('credentials'), error on failure, redirect on success
- Fix: Next.js 16 requires export named 'proxy' not 'middleware'
- Fix: useSearchParams wrapped in Suspense boundary (Next.js App Router requirement)
2026-05-15 10:42:21 +02:00
Simone Cavalli 5d363a633d feat(02-01): install next-auth@4, configure CredentialsProvider auth
- Add next-auth@4 dependency (stable v4, not beta v5)
- Create src/lib/auth.ts with CredentialsProvider validating ADMIN_EMAIL/ADMIN_PASSWORD env vars
- Create src/app/api/auth/[...nextauth]/route.ts catch-all handler (GET + POST)
- JWT session strategy — stateless, no DB users table
- Custom sign-in page set to /admin/login
- Add NEXTAUTH_URL, NEXTAUTH_SECRET, ADMIN_EMAIL, ADMIN_PASSWORD to .env.local
2026-05-15 10:40:30 +02:00
Simone Cavalli 073eec7193 feat(seed): add seed script + fix Tailwind scanning adjacent projects
- 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>
2026-05-14 23:33:03 +02:00
Simone Cavalli 8602bfa92f feat(01-04): DocumentsSection + NotesSection — link esterni e log decisioni read-only
- 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
2026-05-14 22:13:33 +02:00
Simone Cavalli a4e2de0611 feat(01-04): PaymentStatus — totale accettato + righe stato pagamento (senza importi)
- 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
2026-05-14 22:13:27 +02:00
Simone Cavalli 5d5c8eaa7d feat(01-04): PhaseTimeline — timeline laterale con indicatori, progress bar per fase, task list
- 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)
2026-05-14 22:13:20 +02:00
Simone Cavalli debd3916db feat(01-04): ClientDashboard wrapper con header, progress bar globale, layout sezioni
- Header sticky: logo iamcavalli (xs, angolo sinistro) + brand_name cliente (prominente, centrato)
- Progress bar globale con percentuale visibile
- Brief cliente con accent bar sinistra
- Sezioni: PhaseTimeline, PaymentStatus (sempre), Documents/Notes (condizionali)
- Footer con avviso link privato
- Server Component puro, nessun 'use client' necessario
2026-05-14 22:13:11 +02:00
Simone Cavalli 4e703d7068 feat(01-04): design tokens Tailwind v4 + wire ClientDashboard in page.tsx
- globals.css: aggiunto @theme con palette light & clean (primary, secondary,
  tertiary, bg-subtle, border-light, accent, success, warning, info)
- app/c/[token]/page.tsx: import ClientDashboard, generateMetadata dinamico,
  React.cache() per deduplicare DB call tra metadata e render
2026-05-14 21:43:41 +02:00
Simone Cavalli 8b5e723f81 feat(01-03): add /c/[token] Server Component route + layout
- src/app/c/[token]/page.tsx: Server Component calls getClientView(), notFound() on null
- src/app/c/[token]/layout.tsx: layout with metadata for client portal
- [Rule 1 - Bug] Renamed middleware.ts → proxy.ts and export middleware → proxy
  (Next.js 16 deprecated 'middleware' file convention; requires 'proxy' export name)
- params typed as Promise<{ token: string }> per Next.js 15+ breaking change
- npm run build: SUCCESS (no TypeScript errors)
2026-05-14 21:11:32 +02:00
Simone Cavalli 14787bab10 feat(01-03): add ClientView type system and getClientView() query function
- ClientView interface enforces admin data exclusion: no quote_items, no service prices
- getClientView() queries clients.token, phases, tasks, deliverables, payments, documents, notes
- inArray() scoping prevents full table scan on tasks and deliverables
- accepted_total: client.accepted_total ?? '0' null coalescing
- Progress percentages calculated server-side (per-phase + global)
- Payment amount intentionally excluded — only label and status returned to client
2026-05-14 20:58:30 +02:00