Commit Graph

44 Commits

Author SHA1 Message Date
simone 2c67e6fd72 feat(19-01): add LeadsKanbanBoard with @dnd-kit drag-drop between 6 stage columns
- 6-column Kanban board: contacted, qualified, proposal_sent, negotiating, won, lost
- DroppableColumn with useDroppable, isOver highlight (border-[#1A463C])
- DraggableLeadCard with useDraggable, opacity-30 on drag, name/company/next_action
- Optimistic update via setLeadStatuses + startTransition + updateLeadField + router.refresh
- Client-side guard: VALID_STAGES check before calling server action (T-19-01)
- DragOverlay dropAnimation={null} ghost card with rotate-1 shadow-xl
- overflow-x-auto wrapper with grid-cols-6 min-w-[1080px]
2026-06-19 17:48:00 +02:00
simone 268f56ccd2 chore(18-01): remove quote builder route, exclusive components, and SendQuoteModal entry point (CLEAN-02)
- Delete src/app/admin/quotes/new/page.tsx and actions.ts re-export
- Delete QuoteBuilderForm, OfferSelector, PriceOverrideInput, QuotePreview components
- Rewrite SendQuoteModal: remove "Crea Nuovo" tab and window.location navigation to quotes/new
- Remove stale JSDoc comment referencing /admin/quotes/new from admin-queries.ts
2026-06-19 12:07:48 +02:00
simone 14bdbab880 feat(phase-18): plan 02 — consolidate analytics into admin dashboard
- Merge /admin/analytics content (MetricCard, MonthlyChart, time tracking) into /admin
- AdminDashboard now accepts searchParams and year query param
- Add getAnalyticsByYear, getMonthlyCollected, getAvailableYears, getTimeByClient, getTotalTrackedHours queries
- Unify fmtEur to accept number (more robust); update KPI card callers with parseFloat()
- Add MetricCard and fmtSeconds helpers to admin/page.tsx
- YearSelector navigates to /admin?year=Y (was /admin/analytics?year=Y)
- Delete src/app/admin/analytics/page.tsx — /admin/analytics now returns 404
2026-06-19 12:07:31 +02:00
simone 7d884099aa chore(18-01): remove forecast route, sidebar entry, and revalidatePath calls (CLEAN-01)
- Remove Forecast NAV_ITEMS entry and TrendingUp import from AdminSidebar
- Delete src/app/admin/forecast/page.tsx and empty directory
- Remove revalidatePath("/admin/forecast") from assignOfferToProject, removeProjectOffer, updateProjectOfferTotal
2026-06-19 12:05:05 +02:00
simone 43238341c1 feat: service offer tags, catalog cleanup, offer editor filter
- Import 58 offer membership tags for all 55 services (entity_type="services")
  via scripts/import-service-offer-tags.ts (already run on prod)
- ServiceTable: remove Categoria column, rename Tag → Offerta; QuickAddRow
  no longer has a category field (offer tags set post-creation)
- offer-queries: getOfferEditorData fetches offerTags per service (join on
  tags WHERE entity_type="services") and exposes them in OfferEditorData
- OfferEditorClient: filter chips above "Servizi Inclusi" — Tutti / Entry
  Offer / Signature Offer / Retainer Offer, derived from actual tag data

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 21:58:34 +02:00
simone 27e8706e39 fix(offer-editor): show all services regardless of offer category
offer_macros.category ("Entry Offer") and services.category are different
taxonomies — filtering one by the other always returns empty. Remove the
client-side category filter; all active services are always shown.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 18:35:37 +02:00
simone 4f7e0033c4 fix(offer-editor): fix category bug, add ripristina, always-saveable
- Bug: remove server-side category pre-filter from getOfferEditorData;
  services now loaded unfiltered, client-side filter handles display →
  changing category no longer wipes the service list
- Fix tierTotals to use full service map (not filtered subset) so totals
  stay correct when category is edited mid-session
- Add Ripristina button for archived offers (calls toggleOfferArchived false)
- Remove canSave gate: always allow saving; button is "Salva Bozza"
  (secondary style, stays on page) when no services assigned, "Salva
  Offerta" (primary, redirects to list) when at least one tier has services
- Show "Bozza salvata." inline feedback after draft save

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 18:25:50 +02:00
simone 791a6e64b1 chore: merge executor worktree (worktree-agent-a44f854f45ac76582) 2026-06-15 10:28:07 +02:00
simone 7df4b9c74a feat(12-04): build OfferListClient with category filter, archive toggle, card grid
- Category filter chips ("Tutti" + dynamic categoryOptions), active/inactive
  styling per UI-SPEC (#1A463C / #e5e7eb)
- "Mostra offerte archiviate" toggle (default off), filters via useMemo
- Responsive card grid (1/2/3 cols) linking to /admin/offers/[id]/edit,
  with category badge and "Archiviata" indicator
- "+ Nuova Offerta" inline form calling createOfferMacro via useTransition
  + router.refresh()
- Empty state with "Nessuna offerta" / "Inizia creando la tua prima offerta"
2026-06-15 10:25:15 +02:00
simone 8c5c918304 feat(12-05): build offer editor client component
- Add OfferEditorClient: full editor for /admin/offers/[id]/edit
- Categoria/Ticket single-select (OptionSelect) + Tipo/Obiettivo
  multi-select with on-the-fly creation (OptionMultiSelect)
- Services matrix (A/B/C checkboxes) pre-filtered by macro.category,
  with live "Totale Servizi" recalculation via useMemo (OFFER-11)
- Independent manual "Prezzo Pubblico" per tier (OFFER-16)
- 5-field "Promessa di Trasformazione" block via EditableCell (OFFER-17)
- Salva Offerta / Annulla / Archivia actions wired to saveOfferEditor,
  toggleOfferArchived and renameOfferOption (OFFER-15)
2026-06-15 10:25:02 +02:00
simone c8d53134ba fix(14): remove dead generate_new branch from assignQuoteToLead (CRM-12)
assignQuoteToLead's generate_new check was unreachable since
SendQuoteModal always passed false. Removes the field from both
the server schema and the modal's local schema/defaultValues/call site.
2026-06-14 15:17:48 +02:00
simone ab7fa62059 feat(14-02): add LeadsSearch, rewire leads pages, surface tags in LeadDetail (CRM-08/09)
- New LeadsSearch.tsx: client-side instant filter across name/email/
  company/status/tags (mirrors CatalogSearch.tsx)
- page.tsx: fetch via getLeadsWithTags()/getLeadFieldOptions(), render
  LeadsSearch + CreateLeadModal, revalidate=0
- [id]/page.tsx: fetch getLeadsWithTags()/getLeadFieldOptions(), find
  lead by id, pass tags/tagOptions to LeadDetail
- LeadDetail.tsx: add OptionMultiSelect for lead tags in the "Profilo"
  card, wired to addLeadTag/removeLeadTag/renameLeadTag with error display
2026-06-14 13:21:55 +02:00
simone 4887a319f7 feat(14-02): rewrite LeadTable as raw-table database view (CRM-08/09)
Replace shadcn Table-based read-only LeadTable with an Attio-style raw
table database view (1:1 structural port of ServiceTable.tsx):
- EditableCell for name/email/phone/company/next_action, persisted via
  updateLeadField
- Local StatusCell: closed click-to-open dropdown over the 6 fixed
  LEAD_STAGES values, preserving semantic STAGE_COLOR badges (won=green,
  lost=red, etc.) without modifying shared option-select.tsx
- OptionMultiSelect for lead tags, wired to addLeadTag/removeLeadTag/
  renameLeadTag
- Per-row error display and "Nessun lead trovato" empty state
2026-06-14 13:21:23 +02:00
simone a495d84511 fix(14-03): remove dead onSubmit branch in SendQuoteModal (CRM-12)
- Remove unreachable if (tab === "new") branch in onSubmit - the new-tab button has its own onClick navigation and never triggers form submit
- Escape unescaped double quotes in JSX text (react/no-unescaped-entities)
- generate_new field left as-is per plan (optional cleanup, out of scope)
- Log pre-existing useForm<any>/onSubmit(data: any) no-explicit-any lint errors to deferred-items.md (out of scope: fixing requires resolving a zodResolver/Resolver generic incompatibility with assignQuoteSchema's optional/default fields)
2026-06-14 12:46:55 +02:00
simone ee509cd5fb fix(14-03): type LeadForm useForm with CreateLeadInput (CRM-11)
- CreateLeadModal and EditLeadModal use useForm<CreateLeadInput>() instead of useForm<any>()
- Import CreateLeadInput from lead-validators, remove local z.infer redeclaration
- Narrow EditLeadModal status default value to CreateLeadInput["status"], remove 'as any'
- Fix FormField generic signature in shared ui/form.tsx (was ControllerProps<any, any>, broke Control<T> assignability once useForm<T> became concrete) - Rule 3 blocking issue
2026-06-14 12:43:00 +02:00
simone 272e363f4d fix(14-03): translate FollowUpWidget to Italian (CRM-10)
- Replace 6 hardcoded English strings with Italian equivalents
- Remove pluralization ternary (Italian "lead" is invariant)
2026-06-14 12:38:12 +02:00
simone e858a8f577 feat(catalog): Notion-style shared select fields (tag/pacchetto/categoria/fase)
Turn the catalog into Notion/Airtable select properties:
- Tag + Pacchetto: multi-select with a SHARED pool — created values persist and
  are selectable from a dropdown across all services (no more re-typing)
- Categoria + Fase: single-select chips with the same dropdown + create-on-the-fly
- Rename an option once -> propagates to every row (renameServiceOption)
- Deterministic colors per value (shared option-colors util)
- Quick-add row now sets name+description+categoria+fase+prezzo then Enter (active)
- Search broadened to name/categoria/fase/tag/pacchetto

Data model (additive): tag/pacchetto in polymorphic tags table (entity_type
services / services.pacchetto); categoria/fase as single-select columns on
services (new: services.fase). Pools derived from distinct values.

New: OptionSelect, OptionMultiSelect, option-colors. Removed tag-multi-select.
Migration 0007_add_services_fase.sql must be applied before runtime.

tsc + eslint + next build clean. CSV bulk import (OFFER-12) stays Phase 12.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:40:34 +02:00
simone 42c16e1bab fix(11): address code-review findings in catalog inline-edit
- WR-01: remove blur-commit on EditableCell toggle (onChange already saves+closes; prevents double updateServiceField)
- WR-02: skip server action when cell value unchanged (commit + new commitOnBlur dirty-check)
- WR-03: on blur, revert required-empty field via cancel() instead of leaving the cell stuck in error state
- WR-04: normalize it-IT price input (1.234,50) before parse; Number() rejects trailing garbage
- IN-01: render row error in its own <tr> instead of an invalid 7th <td> in a 6-column row

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 21:02:50 +02:00
simone 912a892ba5 feat(11-04): add client-side search bar to catalog page, remove ServiceForm
- New CatalogSearch client component: filters ServiceWithTags[] by name/tag, case-insensitive, instant (no reload)
- page.tsx now passes filtered services through CatalogSearch -> ServiceTable
- ServiceForm.tsx deleted — quick-add row in ServiceTable replaces its create-service UX
2026-06-13 16:01:17 +02:00
simone c0bedf300e feat(11-04): rewrite ServiceTable as database-view (inline edit, tags, quick-add, active/inactive split)
- 6-column table (Nome, Descrizione, Categoria, Prezzo, Tag, Stato), no Azioni column (D-14)
- Every field cell uses EditableCell (text/textarea/number/toggle), saving via updateServiceField
- Tags column uses TagMultiSelect, wired to addTagToService/removeTagFromService
- QuickAddRow creates new services (unit_price=0) via quickAddService on Enter (D-12)
- Inactive services sink below a "Servizi disattivati" divider with opacity-50 (D-13)
- All mutations trigger router.refresh()
2026-06-13 15:58:36 +02:00
simone 008a43469d feat(10-redo-C): CRM leads module — schema, services, UI (Phase 10 redo)
Stage C of staged Phase 10 redo. Restores dangling phase10-wip work:
- schema.ts: leads expansion + activities/reminders tables + relations
- lead-service.ts / lead-validators.ts service layer
- /admin/leads list + detail + actions, LeadTable/LeadDetail/LeadForm
- LogActivityModal, SendQuoteModal, FollowUpWidget on dashboard
- migration 0005 (applied to prod DB in Stage B, along with
  previously-missing 0001/0003/0004 — root cause of all post-deploy
  500s: prod DB had no migrations applied since 0000)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 20:58:14 +02:00
simone 614cf0114f feat(09-02): implement admin quote builder UI with server actions
- src/lib/quote-actions.ts: createQuote server action with nanoid tokens and atomic DB transaction
- src/components/admin/quotes/QuoteBuilderForm.tsx: Two-column form (left: client/offer/price inputs, right: live preview)
- src/components/admin/quotes/OfferSelector.tsx: Grouped dropdown for macro/micro offer selection
- src/components/admin/quotes/PriceOverrideInput.tsx: Price input with server validation feedback
- src/components/admin/quotes/QuotePreview.tsx: Live preview card showing offer details and calculated total
- src/app/admin/quotes/new/page.tsx: Quote builder page rendering form with data from DB
- src/app/admin/quotes/new/actions.ts: Re-export of createQuote for page-level action imports
- src/lib/admin-queries.ts: Added getAllOfferMacrosWithMicros() query for form population

Features:
- Admin selects client and offer (required fields)
- Form validates on blur and displays success/error states
- Server action validates data and rejects if total mismatch
- On success, displays public /quote/[token] link with copy button
- Quote saved as draft state with immutable accepted_total

Completes Phase 9 Plan 2 Wave 1: Admin UI Layer

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-11 07:34:16 +02:00
simone 4ed2f8b105 feat(07-unified-service-catalog): rewire /admin/catalog CRUD + query layer to services table
- Task 1: Update getAllServices() and activeServices queries to read from services table instead of service_catalog
  - Both ClientFullDetail and ProjectFullDetail now return Service[] (not ServiceCatalog[])
  - Quote items label join remains unchanged — still references service_catalog for historical quote_items.service_id FK integrity

- Task 2: Rewire /admin/catalog actions and components to operate on services table
  - src/app/admin/catalog/actions.ts: createService/updateService/toggleServiceActive now write to services with optional category field
  - ServiceTable.tsx: Updated to Service[] type, added category column rendering
  - ServiceForm.tsx: Added optional category input field
  - QuoteTab.tsx: Updated activeServices type annotation to Service[]

- New services created via /admin/catalog have migrated_from=null, migrated_id=null (not migrated)
- TypeScript compiles successfully
- npm run build: PASS

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-11 06:26:03 +02:00
simone 4d52d87341 chore(06-01): delete NavBar.tsx — replaced by AdminSidebar
- NavBar is no longer imported anywhere (layout.tsx updated in prior task)
- tsc --noEmit and npm run build both pass clean
2026-05-31 20:09:32 +02:00
simone 2283740d20 feat(06-01): create AdminSidebar component with 7 nav links + logout
- Client component using usePathname for active route highlighting
- 7 nav links: Dashboard, Clienti, Progetti, Offerte, Forecast, Catalogo, Impostazioni
- Logout button at bottom calls signOut with /admin/login callback
- Green sidebar bg #1A463C matching brand
2026-05-31 20:08:33 +02:00
simone 0c09d44b68 feat(05-03): add OffersTab component + Offerte tab in project workspace
- Create OffersTab.tsx client component with assign form and offer list
- Inline accepted_total editing via onBlur handler
- Add Offerte tab trigger and content to project workspace page
- Destructure projectOffers and availableMicros from getProjectFullDetail result
2026-05-30 16:27:07 +02:00
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 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 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 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 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 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 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 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 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 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