356 Commits

Author SHA1 Message Date
simone c9b5cd7451 docs(state): Coolify configurato + mailer verificato; deploy fermo sul record DKIM
- RESEND_API_KEY e RESEND_FROM create su Coolify (production + preview)
- sendEmail() verificato con il template OTP reale: {ok:true}
- unico blocco residuo: TXT resend._domainkey.iamcavalli.net ha una chiave
  vecchia, dominio Resend status=failed, invio dal dominio rifiutato 403
- valore DKIM corretto e sequenza di ripresa annotati in STATE.md
- note API Coolify: envs non accetta is_build_time (422)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 23:20:47 +02:00
simone 8158038145 feat(auth): gate OTP email sul portale cliente (v2.3 Phases 23-25)
Il portale /client/<slug> era protetto dal solo token in URL: chiunque
ricevesse o intercettasse il link entrava, per sempre, senza identificarsi.
Ora l'admin registra le email autorizzate per cliente e il cliente si
identifica con un codice usa-e-getta prima di vedere qualsiasi dato.

- Resend 6.18.1 + src/lib/mailer.ts (Result tipizzato, mai catch silenzioso)
- migration 0015 (gia applicata a prod): client_emails, otp_codes,
  clients.sessions_valid_from. Additiva pura, conteggi verificati pre/post
- admin: sezione "Accessi al portale" in /admin/clients/[id] con whitelist
  e revoca sessioni in blocco
- gate: codice 6 cifre CSPRNG, hash SHA-256 (mai il codice in chiaro),
  TTL 15 min, max 5 tentativi, rate limit su entrambi gli endpoint,
  risposta identica per email in whitelist e non (no enumeration)
- sessione: cookie HMAC per-cliente, 90 giorni, httpOnly/secure/lax

Il gate sta in cima alla page, NON nel layout: nell'App Router il segmento
page viene renderizzato in parallelo al layout, quindi gattare nel layout
nascondeva la dashboard a schermo ma lasciava fasi, task e pagamenti nel
payload RSC dell'HTML (46907 byte -> 17594 dopo il fix). Verificato.

Verifica: build OK, 9/9 test E2E in locale contro il DB di produzione.

NON DEPLOYARE prima di: RESEND_API_KEY+RESEND_FROM su Coolify e whitelist
popolata per i 3 clienti reali (oggi vuota) - altrimenti il gate li chiude
fuori dal loro portale. Checklist in .planning/STATE.md.

SEND-01/02 (invio preventivo via email) rinviati a v2.4 su richiesta.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 12:10:33 +02:00
simone b27b9d07ac chore: svuota il cestino e allinea CLAUDE.md alla nuova struttura
Verificato prima di cancellare: le 10 cartelle di fasi in
planning-fasi-duplicate/ erano byte-per-byte identiche alle copie in
.planning/milestones/ (diff -rq su ognuna), i 13 script one-off erano
gia eseguiti su fasi chiuse, e CLAUDE-SECURITY-*/ conteneva solo i
metadati di una run interrotta. Tutto resta comunque in git fino a
94b3b2f^.

CLAUDE.md citava ancora gli scripts/push-*.ts come vecchio metodo per
le migrazioni: quei file non esistono piu, quindi il riferimento
puntava a fantasmi. Ora la procedura SSH+docker exec e l'unica indicata.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 14:30:19 +02:00
simone 94b3b2f766 chore: riorganizzazione e pulizia della cartella di progetto
Rimossi i doppioni e gli artefatti accumulati, senza cancellare nulla di
definitivo: tutto cio' che serviva una revisione e' parcheggiato in cestino/
(gitignored), documentato in cestino/LEGGIMI.md.

- .planning/phases/01-10: 10 cartelle identiche byte-per-byte alle copie in
  .planning/milestones/v1.0-phases e v2.0-phases. HANDOFF.md:33 documentava che
  furono copiate e non spostate, lasciando la pulizia 'facoltativa in futuro'.
  Verificata l'identita' con diff -rq prima di spostare ciascuna.
- scripts/: 13 script one-off gia' eseguiti (push-*, migrate-*, validate-*,
  verify-12-03-*) piu' reset-and-import-services.ts, che cancella dati.
  Restano i 3 riutilizzabili: seed, import-services-notion, import-service-offer-tags.
- CLAUDE-SECURITY-20260727-210226/: cartella di lavoro della run interrotta.

Cancellati subito, senza revisione: 6 .DS_Store, le due cache .impeccable/
(una era dentro src/) e tsconfig.tsbuildinfo.

.gitignore: aggiunti cestino/, .impeccable/ e CLAUDE-SECURITY-*/ per evitare
che si riformino.

src/ non e' stato toccato: la struttura e' dettata dall'App Router di Next.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 14:13:32 +02:00
simone fb6ab92fd0 docs(security): correzione severita finding 1 + chiusura punti Coolify
CORREZIONE: la password Postgres trovata in git NON era attiva. La verifica
iniziale si limitava a constatare che la stringa comparisse in .env.local e ne
deduceva che fosse quella viva. Il confronto del verifier SCRAM-SHA-256 di
pg_authid contro i due candidati mostra che quella committata non combacia:
era gia stata ruotata. La voce DATABASE_URL porta 5432 di .env.local e' stale.
Severita' reale: BASSA, non CRITICA. Nessuna rotazione necessaria.

Chiusi via API Coolify: INTERNAL_SECRET creata (le route /api/internal/*
rispondono ora 403 invece di 404, oracolo di enumerazione token chiuso) e
ADMIN_PASSWORD portata da 15 a 32 caratteri. Redeploy verificato.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 11:06:48 +02:00
simone bcff4aad48 docs(security): slug cliente ruotati in produzione
I 4 slug esistenti avevano suffissi da 4 caratteri generati con Math.random();
ora 12 caratteri CSPRNG. Lo slug risolve prima del token, quindi era il vero
anello debole dell'accesso alla dashboard cliente (C-2).

Lo script SQL NON e' committato di proposito: contiene gli slug in chiaro, che
sono credenziali bearer verso /client/<slug> — committarlo ripeterebbe la fuga
di credenziali trovata al punto 1 dell'audit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 10:55:38 +02:00
simone e2bd1d95ed fix(security): audit completo — secondo gate admin, hardening slug, XSS, CSP/HSTS, update CVE
Audit di sicurezza su tutta l'app. Report in .planning/SECURITY-SCAN.md (codice),
.planning/SECURITY-AUDIT-INFRA.md (dipendenze/segreti/deploy) e piano in
.planning/SECURITY-REMEDIATION-PLAN.md.

CRITICO — l'autorizzazione admin era un unico punto di rottura: nessuna delle 21
pagine /admin controllava la sessione e admin/layout.tsx renderizzava comunque i
figli quando mancava. L'unico guard era proxy.ts, su un Next.js affetto da
GHSA-6gpp-xcg3-4w24 (proxy bypass). Ora il layout è un secondo gate indipendente;
proxy.ts marca il path con un token derivato da NEXTAUTH_SECRET, così il gate non
è aggirabile forgiando header e fallisce chiuso se il proxy non gira.

ALTO — gli slug cliente avevano 4 caratteri casuali da Math.random() (~20 bit,
1.7M tentativi) e risolvono prima del token: ora 12 caratteri via nanoid
(CSPRNG, ~62 bit). Aggiunto rate limit al ramo /client/, che ne era privo.

ALTO — src/lib/quote-actions.ts esponeva due server action pubbliche senza
autenticazione, una delle quali scriveva su DB. Codice morto, zero chiamanti:
rimosso.

MEDIO — i quattro dangerouslySetInnerHTML nelle sezioni proposta rendevano output
AI come HTML grezzo su pagina pubblica, alimentato da transcript di terzi. Sostituiti
con RichText (whitelist di emphasis, nessun HTML al DOM). I transcript ora sono
recintati in tag che il system prompt dichiara essere dati, non istruzioni.

Inoltre: next 16.2.6 -> 16.2.12 e next-auth 4.24.14 -> 4.24.15 (chiude 9 CVE Next
piu GHSA-xmf8-cvqr-rfgj su getToken, raggiungibile dal proxy); HSTS e CSP;
potatura della Map di rate-limit.ts, che cresceva senza limite; espunta la password
Postgres di produzione dai due 07-01-SUMMARY.md.

Verificato: tsc pulito, build OK, smoke test su login/redirect/header forgiati.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 23:35:46 +02:00
simone dd2d148457 feat: pagina Impostazioni redesign Quiet Luxury — sezioni tokenizzate + PoolManager tassonomie dual-mode
- impostazioni/page.tsx: sezione Analytics con input tariffa bordato (€/h, font-mono) + header uppercase
- TaxonomyManager: card sezione tokenizzata, griglie Offerte (2col) / Catalogo (3col)
- PoolManager: box bg-muted, badge conteggio mono, chip valori, add su Enter, delete cascade confermata
- DESIGN-SYSTEM.md: note pagina Impostazioni

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 12:14:52 +02:00
simone d444bd6064 feat: pagina Progetti redesign Quiet Luxury — tabella tokenizzata + badge pagamento/timer dual-mode
- projects/page.tsx: tabella in contenitore bg-card/shadow-card, header uppercase muted
- ProjectRow: badge pagamento pill rounded-full dual light/dark (saldato/da_saldare/inviata)
- TimerCell: pill rounded-full mono, idle neutro + running emerald con contatore live
- ConversationsView: bordo item attivo 3px per coerenza
- DESIGN-SYSTEM.md: note pagina Progetti

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 12:14:31 +02:00
simone dd4ae42542 feat: pagina Preventivi redesign Quiet Luxury — tabella tokenizzata + badge stato dual-mode
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 11:53:41 +02:00
simone c29fab8975 feat: pagina Catalogo redesign Quiet Luxury — griglia servizi tokenizzata + footer conteggio
- ServiceTable: contenitore bg-card/shadow-card, header uppercase muted, righe hover:bg-muted/40, footer "Visualizzazione di N servizi"
- CatalogSearch: usa il primitivo SearchInput
- page: sottotitolo sezione
- tokenizza OptionSelect ed EditableCell (accent-primary) — erano rimasti con hex hardcoded

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 11:37:09 +02:00
simone 1fc0650dcf feat: pagina Offerte redesign Quiet Luxury — grid card tokenizzata + badge categoria
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 16:36:34 +02:00
simone c1ff5a7e67 feat: pagina Clienti redesign Quiet Luxury — tabella tokenizzata + copia link
- /admin/clients replica design-reference/pagina-clienti (dual light/dark via token)
- ClientRow: colonne numeriche font-mono right-aligned, incassato come pill emerald,
  cella "Link profilo" con pill mono troncata + CopyLinkButton
- nuovo primitivo CopyLinkButton (copia URL assoluto, feedback check emerald)
- PageHeader con sottotitolo + footer conteggio clienti
- DESIGN-SYSTEM.md: inventory (CopyLinkButton, ClientRow) + note pagina Clienti

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 16:32:34 +02:00
simone a1d8d18902 feat: dashboard box "Messaggi Clienti" — chat clienti in attesa di risposta
Nuovo MessagesWidget nella colonna 1/3 della dashboard: riusa
getConversations() filtrando le conversazioni unread, mostra le prime 4
con anteprima e deep-link "Rispondi" a /admin/conversazioni?c=<clientId>.
Pill emerald "N Nuovi" + pallino pulsante, empty state quando non c'è nulla
in attesa. Stessa fonte di verità (clients.admin_last_read_at) del badge
sidebar. Aggiornato DESIGN-SYSTEM.md (inventory + note Conversazioni/dashboard)
e il mock design-reference/pagina-dashboard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 16:23:43 +02:00
simone c1cc13a99a feat: pagina Conversazioni — inbox unificata messaggi clienti
Nuova pagina admin /admin/conversazioni: vista WhatsApp-style (lista
conversazioni a sinistra, thread + risposta a destra) che aggrega i
messaggi di tutti i clienti dalla tabella comments, cross-cliente.

- Migration additiva 0014: clients.admin_last_read_at per tracciare
  letto/non-letto (pallini + badge in sidebar). Applicata a prod.
- conversations-queries.ts: aggregazione entity_id → cliente
  (general/phase/task/deliverable), getConversations /
  getConversationThread / getUnreadConversationsCount.
- Risposte admin salvate come commento "general" (visibili anche nella
  chat cliente e nel CommentsTab della scheda).
- Voce di menu + badge non-letti (AdminSidebar/AdminShell/layout).
- Token semantici per dual light/dark; refresh manuale (no polling).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 16:10:14 +02:00
simone a20a9de2d7 style: rifinitura Quiet Luxury dashboard — bordi soft, badge, colori
Allinea la dashboard alla rifinitura del mock (finezza bordi/colori/badge):

- Card su border-border-light (slate-100) per il bordo sottile
- KPI: valore verde su Incassato Reale, delta verde "+N questo mese" su
  Clienti Attivi (nuova metrica getDashboardStats.clientiNuoviMese)
- Redditività: badge "Ottimo" con tint emerald soft (non brand primary)
- Follow-up: righe come box bordati (no avatar), link "Apri →" primary
- Forecast: selettore anno (pill interattiva) spostato nell'header della card,
  barre non-picco grigie (bg-border), rimosso il numero "mese prossimo"
- Offerte Più Richieste: rimossa la pill totale nell'header

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 12:18:10 +02:00
simone 08aadc1d97 feat: Dashboard admin redesign to Quiet Luxury + client profitability widget
Replica il mock design-reference/pagina-dashboard: layout condensato a
schermata singola (4 KPI + griglia 2/3-1/3), token semantici per dual-theme.

- Nuova query getClientProfitability(year): valore orario reale per cliente
  (contrattualizzato / ore tracciate) con badge margine, target 100 €/h
- Nuovo widget ClientProfitability "Analisi Oraria & Redditività Clienti"
- Rewrite /admin: KPI strip + Cashflow + Redditività | Follow-up + Offerte
  Più Richieste. Rimossi chart mensile, card extra, barre ore/cliente
- Tokenizzati ForecastChart, OffersSoldChart, FollowUpWidget, YearSelector
  (ora pill interattiva); rimosso MonthlyChart e prop availableYears inutile
- DESIGN-SYSTEM.md: inventario + note dashboard (applied 2026-07-11)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 11:57:10 +02:00
simone e5fa07bba3 feat: Lead Details redesign to Quiet Luxury + unified interaction timeline
Rewrite /admin/pipeline/[id] LeadDetail with token-based dual-theme layout:
bespoke header with inline StatusBadge, asymmetric 1/3 profile + 2/3 content
columns, and a unified reverse-chronological timeline merging activities and
transcripts (transcript node highlighted + expandable + deletable).

Restyle modal triggers (Registra Attività, Aggiungi Transcript, Modifica-as-icon)
to the mock outline style. Remove the confusing "Invia Preventivo" button (it only
linked a token + advanced stage, no email). Keep Converti in cliente for won leads.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 11:33:18 +02:00
simone c110689b6a fix: chat drawer above header + vertical "Chiudi" tab on left edge
Panel z-index raised to z-[60] so it sits above the sticky page header
(z-50) that was covering the close control. Replaced the header X with a
notebook-divider-style vertical "Chiudi" tab protruding from the left edge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 08:34:25 +02:00
simone 11870e15d3 feat: Client Portal redesign to Quiet Luxury + milestone stepper
- New MilestoneStepper primitive (horizontal per-phase progress)
- Token-migrate portal shell, phase cards, sidebar cards, kanban to dual-theme
- Soft-tint status pills (emerald/amber/muted) replacing solid badges
- embedded prop on ClientDashboard fixes double-header in multi-project tabs

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 22:54:58 +02:00
simone 6d5e04bf9c style: refine Pipeline fidelity to Quiet Luxury reference
- Sidebar: revert to white palette (drop emerald pass), remove right border
- Table: softer slate-100 borders, remove min-height gap, "Mostrando N di M
  lead" footer, empty Tag cell = dashed-circle add button
- SegmentedToggle: more visible track (slate-200/60), neutral active text
- Kanban: roomy fixed-width columns (280px, horizontal scroll), sober column
  headers (no colored dots), airy cards matching mock (name+badge/email/
  company+"Tag +"); 6 stages + @dnd-kit intact

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 22:33:26 +02:00
simone 26d752892a style: align light palette to Quiet Luxury reference
- Page background now #F8F9FA (was white); tokens matched to reference:
  foreground slate-900, muted slate-50/slate-500, border slate-200,
  card pure white, tertiary slate-400, border-light slate-100
- Sidebar colors matched to mock: emerald-200/70 inactive, white/8 active
  with emerald-400 active icon, emerald-50 logo, red-300 logout, right border
- Content wrapped in max-w-[1400px] centered container (px-8 lg:px-10)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:33:00 +02:00
simone 9eb2d45c67 fix: sidebar width/centering + kanban card badge & table details
- Sidebar: expanded 256->200px, collapsed 80->64px; collapsed state now
  renders icon-only (label removed from flow) so icons are centered, not
  pushed left by an invisible text span
- Kanban cards: add StatusBadge + email line to match design reference
- Lead table: add "Mostrando N lead" footer and center/right-align
  Stato/Tag/Azioni headers per reference

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:28:43 +02:00
simone 86e1499e8f feat: Quiet Luxury design system + Pipeline (ex-Lead) redesign
- Design system foundations: Plus Jakarta Sans font, shadow-card/radius
  tokens in @theme, design-reference/DESIGN-SYSTEM.md with component inventory
- Collapsible admin shell (AdminShell): smooth w-64<->w-20 sidebar, new top
  header with "Admin" + avatar placeholder, localStorage-persisted state
- Rename route /admin/leads -> /admin/pipeline (redirect stubs preserved),
  nav label Lead -> Pipeline; DB table unchanged
- Reusable primitives: StatusBadge, SearchInput, SegmentedToggle (dual-theme)
- Luxury restyle of lead table, kanban (6 stages + @dnd-kit intact), PageHeader
- Tokenize editable-cell / option-multi-select for dark-mode legibility

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 16:16:44 +02:00
simone 43cb7e7469 feat: dark/light theming system + design tokens (OMC design system port)
- Restructure design tokens into :root/.dark raw vars mapped via @theme inline
- Add light/dark/system theming: useTheme hook + FOUC guard + ThemeToggle
- Keep iamcavalli palette (primary #1A463C, accent #DEF168) and Geist fonts
- Derive brand-consistent dark palette (verde-nero bg, lightened primary)
- Add scrollbar utilities (.no-scrollbar/.thin-scrollbar)
- Install tailwindcss-animate, register via @plugin (Tailwind v4)
- Mount ThemeToggle in admin sidebar footer

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 08:08:56 +01:00
simone add2176a6b feat: tier names + per-tier prices on offer cards, unified admin UI
- offer editor: per-tier name input (mirrors public_name/internal_name)
- offer list cards: show 3-tier services total + manual public price
- shared PageHeader component, full-width layout across all admin pages
- UI-RULES.md design conventions doc

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 16:58:18 +02:00
simone 9abe1fe4bb fix: offer name in client detail, retainer forecast, LTV with offers, offers-sold chart
- Client detail offers: show offer macro name ("Mantenimento") instead of tier letter
- Forecast: retainers project the monthly fee across every month from start_date
  (no longer capped by duration_months); una_tantum unchanged
- Clients list LTV: per project max(accepted_total, sum of assigned offers) so a
  retainer with unset quote still counts
- Dashboard: new "Offerte vendute" chart (count by offer + tier)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 14:14:13 +02:00
simone ae355c33a6 feat: offer badges, unified dashboard, income forecast, Pipedrive-style follow-up
- Client detail: category + tier badges on active offers
- Dashboard: remove top KPI cards + recent activity; fold current KPIs into
  bottom MetricCard style; add 12-month income forecast chart
- Forecast query: branch by offer_type (retainer = monthly fee, una_tantum =
  spread over duration), filter archived projects
- Payments: set the month a payment was collected (paid_at) from PaymentsTab
- Redesign FollowUpWidget in clean Pipedrive style (brand green, no orange)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 22:28:12 +02:00
simone fc766ca1ee feat: add iamcavalli logo as favicon
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 21:51:05 +02:00
simone 0b8a7b3809 feat: add Opus/Sonnet model selector to "Genera preventivo" page
Adds a "Modello AI" toggle (Opus default, Sonnet option) styled like the
existing Soggetto toggle. The server action validates the keyword and maps
it to the real model id; the agent receives the resolved id and uses it in
the Anthropic messages.create call. The proposals DB record stores the
actual model used.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 15:34:04 +02:00
simone e10d1f70cb fix: cumulative price from offer_tier_services, offers accordion, retainer phase placeholder
- client-view.ts: read cumulative_price + services list from offer_tier_services→services
  (Phase 12 catalog); legacy fallback to offer_micro_services→offer_services when no
  new-style rows exist. Extend activeOffers type with services[] in both ProjectView and ClientView.
- OffersSection.tsx: extract OfferCard client component with useState accordion;
  hide "Valore incluso" row when price is 0; add "Cosa è compreso" chevron toggle
  listing included services (name + description when present).
- client-dashboard.tsx: hide progress bar and PhaseViewToggle when hasRetainer;
  render polished empty-state placeholder with RefreshCw icon instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 14:53:25 +02:00
simone 64030afef3 feat: sidebar CTA removal, delete phase/task, public dashboard UX, chat panel
1. AdminSidebar: removed yellow "Genera preventivo" CTA (duplicated Preventivi tab nav item)
2. Delete phase/task: new deletePhase/deleteTask server actions with FK cascade and phase-status recompute; DeletePhaseTaskButton client component with window.confirm; trash icons in PhasesTab per phase and per task
3. Public dashboard: extend activeOffers query with offer_macros join (offer_name, offer_type); reorder sidebar 1°Offerte 2°Pagamenti 3°Documenti; OffersSection shows macro public_name as heading (never tier letter/internal_name); PaymentStatus gains totalLabel/overrideAmount/hideRows props — retainer=monthly label + no Acconto/Saldo rows
4. Offer editor: Descrizione textarea (nota interna) and Modalità toggle moved directly under title; Tags section now contains only Categoria/Ticket/Tipo/Obiettivo
5. Basecamp chat: API route supports entity_type="phase" with authorization scoping; comments scope in client-view broadened to include phaseIds and client_id (general); CommentsTab updated with phase/general entities; ChatProvider React context; ChatPanel fixed slide-in panel with FAB (bottom-right) and composer tag selector (Generale + phases); PhaseCard gets chat-bubble icon pre-tagging that phase; inline ChatSection block removed from dashboard main column

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 14:11:55 +02:00
simone 4b28f254ba docs: correct DB-access procedure (direct SSH+docker exec, remote is gitea)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 10:39:45 +02:00
simone 186bb9ea19 feat: payments plans, phase auto-cascade, transcripts, manual timer
- Pagamenti: totale ereditato dalla somma accepted_total delle offerte
  attive (con override) + selettore schema rate 1/2/3 step; nuova colonna
  payments.percent per rescalare gli importi preservando label/stato
- Fasi & Task: recomputePhaseStatus auto-cascade (task -> fase) su
  updateTaskStatus/addTask, fix UI stale, etichette Da iniziare/In corso/Completata
- Pagina pubblica: colori fasi (grigio/blu/#1A463C) + fasi collassabili
- Transcript del cliente visibili in tab Documenti admin e dashboard pubblica
- Timer: inserimento manuale (+30/+60, minuti liberi, data) + elimina entry
- CLAUDE.md: procedura Deploy & DB Access; push su main automatico

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 10:38:56 +02:00
simone 988f6f425a docs: add STATUS.md session handoff 2026-06-22 14:34:17 +02:00
simone f98828f75e feat: offer_type (una tantum/retainer), dedup tiers, redesigned Offerte tab
- offer_macros.offer_type ('una_tantum'|'retainer') + editor "Modalità" toggle
  (migration 0012, applied to prod)
- migration 0012 also adds UNIQUE(macro_id, tier_letter) — prevents duplicate
  tiers; ran after a one-time dedup of Web Domination's duplicate A/B/C tiers
- OffersTab redesigned: two-step assign (Offer → Tier cards with price), type
  badge "Una tantum/Ricorrente" instead of misleading "X mesi", no redundant
  "· A" when public_name == tier letter, cleaner active-offers cards
- getProjectFullDetail: availableMicros grouped by macro + defensive dedup;
  projectOffers/availableMicros carry offer_type/category/price
- proposal deck PricingSection shows offer type (fallback to duration for
  pre-existing proposals)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:32:59 +02:00
simone 1824cb643f feat: wire offer→project phases/tasks, redo Offerte tab, cleanup project tabs
- importOfferIntoProject(): materializes project phases/tasks from the assigned
  tier's services grouped by services.fase (merge-by-title, dedup tasks)
- assignOfferToProject: optional import_phases flag triggers the import
- getProjectFullDetail: projectOffers/availableMicros now include macro name +
  tier_letter (dropdown shows "Offerta — Tier X"); availableMicros filters
  non-archived macros
- OffersTab redone: shows macro + tier badge, "Importa fasi e task" checkbox
- removed project "Preventivo" tab + deleted QuoteTab.tsx (legacy quote_items)
- sidebar: Lead before Clienti
- no DB migration (reuses existing tables)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 09:08:28 +02:00
simone f5f90cd643 feat: lead → client conversion + client contact fields (email/phone)
Blocco A+B (milestone v2.3). Migration 0011 is additive (ADD COLUMN only):
clients.email, clients.phone, leads.archived.

- createClientCore() extracted from createClient and reused by conversion
- clients.email/phone added to create + edit forms and shown on client header
  (never exposed on the public /client/[token] page)
- convertLeadToClient(): reuses core, carries over lead transcripts
  (client_transcripts.client_id), links project.created_from_lead_id,
  archives the lead while keeping its "won" status; idempotent
- "Converti in cliente" button on won leads; "Convertito → Apri cliente"
  once converted (clientId resolved via created_from_lead_id)
- archived leads hidden from list/kanban; detail page fetches by id incl. archived

Migration must be applied to the prod DB BEFORE this is deployed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 08:18:41 +02:00
simone e1b3e8c3d5 style: refine settings taxonomy UI (field cells, icon headers, 2-col grid)
- PoolManager: defined field cells with value count, refined chips,
  inline ghost + button, focus ring, pending/empty states
- TaxonomyManager: icon-badged section headers, ordered 2-col grid,
  sync explainer note
- Settings page: wider container (max-w-4xl) for the grid to breathe

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 14:46:13 +02:00
simone e80c95f838 feat: centralized Notion-style taxonomy management in settings
Single persistent option pool per taxonomy (7 fields: offer
categoria/ticket/tipo/obiettivo + catalog fase/offerta/pacchetto),
stored as JSON in the settings table (no DB migration).

- src/lib/taxonomy.ts: pools with lazy seed from in-use values,
  add/remove(cascade)/rename helpers
- Inline creation anywhere registers into the pool (save offer,
  addServiceOption, updateServiceField fase, quickAdd, create macro)
- Deselecting on a row never touches the pool; global delete only
  from settings (cascade-strips tags / nulls columns)
- getOfferFieldOptions + getCatalogFieldOptions read from pools
- Settings: TaxonomyManager (offer + catalog groups), confirm on delete

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 14:24:47 +02:00
simone 320827e13a fix: flag A/B/C persistence, catalog sort+reorder, offer category pools
- Fix duplicate tier INSERT bug (useState not syncing after router.refresh)
- Add column sort by clicking headers in service catalog
- Add drag-and-drop column reordering (persisted in localStorage)
- Add Categorie Offerta section in Impostazioni (tipo/obiettivo/categoria pools)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 12:12:49 +02:00
simone ba3e824157 docs: create milestone v2.3 roadmap (3 phases, 9 requirements mapped)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 11:23:56 +02:00
simone 19a7ffb6a6 docs: define milestone v2.3 requirements (9 reqs — OTP gate + email send)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 11:01:28 +02:00
simone bc9051c899 docs: start milestone v2.3 Email & Accesso
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 10:54:14 +02:00
simone 55a2da5faf chore: remove REQUIREMENTS.md for v2.2 milestone — fresh for v2.3 v2.2 2026-06-20 18:31:13 +02:00
simone fb1ef95c96 chore: archive v2.2 Sales Loop milestone
- ROADMAP.md: collassato a milestone summary, v2.2 in <details>
- PROJECT.md: full evolution review — What This Is, Validated v2.2, Active v2.3, Key Decisions aggiornate
- MILESTONES.md: entry v2.2 + entry v2.1 aggiunti
- milestones/v2.2-ROADMAP.md: archivio completo fasi 18-22
- milestones/v2.2-REQUIREMENTS.md: archivio requirements con outcomes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 18:31:08 +02:00
simone cbfd959c36 docs(v2.2): close planning docs — SUMMARY.md 21+22, req Complete
- 21-01-SUMMARY.md: agente AI Opus 4.8, schema Zod, proposals table
- 22-01-SUMMARY.md: deck 20+ slide 100vh, accept/reject, PUB-03 deferred
- REQUIREMENTS.md: PIPE/KB/AI-01/02 + PUB-01/02 → Complete; PUB-03 deferred
- ROADMAP.md: Phase 20/21/22 → [x] con date
- STATE.md: status complete, percent 100, 5/5 fasi

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 18:24:00 +02:00
simone c803efe059 fix(preventivo): slide deck 100vh — nessuno scroll di pagina
- ProposalDeck: outer div h-screen overflow-hidden, content wrapper h-full
- Rimosso il div interno ridondante min-h-screen
- useEffect blocca body overflow mentre la deck è aperta
- Tutti i 20 componenti slide: min-h-screen → h-full

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 18:08:40 +02:00
simone fdcc938252 feat(21-22): agente AI generazione preventivo + pagina pubblica deck
- Migration 0010: tabella proposals (id, slug, lead_id, client_id,
  offer_macro_id, content jsonb, state, selected_tier, accepted_at)
  applicata a prod via SSH tunnel
- @anthropic-ai/sdk@0.105.0 installato; ANTHROPIC_API_KEY in .env.local
- src/lib/proposal/: schema Zod ProposalContent, agente Claude Opus 4.8,
  assemble (AI + offerta DB + config consulente), queries, profile.ts
- Admin: /admin/preventivi lista + /genera (pre-fill ?lead_id=X) + /[id] review
- Sidebar: voce Preventivi + CTA globale lime "Genera preventivo"
- LeadDetail: pulsante "Genera preventivo" → /admin/preventivi/genera?lead_id=X
- Pagina pubblica /preventivo/[slug]: deck 20+ slide light-mode iamcavalli,
  navigazione frecce + dot + keyboard, accept/reject con guard immutabilità
- STATE.md aggiornato (80%), 21-PLAN.md scritto nel formato GSD

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 14:37:05 +02:00
simone 86c86cd420 docs(21): capture phase context — AI preventivo struttura sezioni fisse, 3 tier 2026-06-20 12:24:52 +02:00