- 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>
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>
- 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>
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>
- 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>
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>
- 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>
DDL for client_transcripts table: id (nanoid PK), lead_id/client_id
(nullable FKs with ON DELETE CASCADE), title, content (NOT NULL),
call_date (date NOT NULL), created_at (timestamptz). Three indexes on
lead_id, client_id, call_date DESC. APPLY TO PROD via SSH before
pushing schema-dependent code (Plan 20-02/20-03).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- LeadsViewToggle: client wrapper with useState<list|kanban>, useMemo filtered
- Search input (left) + pill toggle Lista/Kanban (right) on single row
- Filters leads by name/email/company/status/tags across both views
- Search state preserved when switching between Lista and Kanban
- LeadsSearch.tsx: replaced with re-export of LeadsViewToggle (backward compat)
- 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>
scripts/reset-and-import-services.ts — deletes 3 legacy test services,
imports 55 services from Notion "DB Offerta: Attività SC" CSV export
(Signature Offer / Entry Offer / Retainer Offer, with fase metadata).
scripts/import-services-notion.ts — standalone idempotent version.
Run via SSH tunnel with DATABASE_URL pointed at 127.0.0.1:54321.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
- 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>