Due cose che il cliente non poteva sapere guardando il portale.
**Task cancellati.** Fino a ieri un'attività tolta dal lavoro poteva solo
sparire (cancellata dal DB) o restare lì a far finta di essere ancora da
fare. Ora `cancelled` è il quinto stato: X dentro il cerchio, titolo barrato,
pill "Cancellata" — l'unico stato chiuso che la porta, perché "fatto" e
"cancellato" sono entrambi barrati e confonderli significa credere consegnato
qualcosa che non esiste.
Esce da tutti i denominatori — fase, progetto, board di consegna, riepilogo
admin — con un unico `countsTowardProgress()` in task-status.ts invece di
cinque `!== "cancelled"` sparsi. Contarlo terrebbe la fase sotto il 100% per
un lavoro che nessuno farà; contarlo come fatto racconterebbe una consegna
mai avvenuta. `recomputePhaseStatus` lo ignora allo stesso modo: senza questo,
cancellare l'ultima voce lasciava la fase "in corso" per sempre.
Nel kanban cliente la colonna compare solo se ha dentro qualcosa — le quattro
che raccontano il lavoro si tengono la larghezza — ma mai se è piena, quindi
nessun task sparisce dalla board. Nell'admin la colonna c'è sempre: è così che
si cancella un task, trascinandocelo.
**Date dei pagamenti** (migration 0023, già applicata in produzione).
`payments` sapeva solo quando una rata era stata incassata, mai quando era
attesa: il portale non poteva rispondere a "quando devo pagare?" e non c'era
niente su cui agganciare il promemoria email. Ora c'è `due_date`, nullable —
una rata senza data concordata è normale, e il portale la mostra solo se c'è.
Il cliente vede in cima al box la prossima scadenza col conto alla rovescia
("tra 12 giorni", "domani", "scaduto da 3 giorni" in rosso), e su ogni riga
la data: "Scade il…" se aperta, "Pagato il…" se saldata. Nessun importo per
riga — LOCKED #2 resta dov'era, le date non sono cifre.
I giorni si contano in `src/lib/payment-dates.ts`, sui giorni civili a Roma e
non sugli istanti: il container gira a UTC e "manca una settimana" non deve
cambiare risposta a seconda del fuso. È lo stesso modulo che userà il
promemoria email, così la mail e il portale non si contraddicono.
Lato admin ogni rata ha il campo Scadenza, e "Incassato nel mese" diventa
"Incassato il" — precisione al giorno, che le analytics (raggruppate per mese)
non notano. Il warning sul cambio schema ora conta anche le scadenze: una rata
"da saldare" con una data è già sotto gli occhi del cliente, e sparirebbe in
silenzio.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mettere una rata su "saldato" la faceva saltare in fondo. Non era un'impressione:
payments non aveva NESSUNA colonna d'ordine (ne' sort_order ne' created_at) e
nessuna delle 13 query che la leggono aveva un ORDER BY. Postgres fa seq-scan e
restituisce l'ordine fisico; una UPDATE in MVCC riscrive la tupla in coda, quindi
la riga aggiornata tornava ultima.
In produzione 3 progetti su 5 mostravano gia' l'ordine sbagliato (uno 30/20/50,
uno del tutto rovesciato 20/30/50, e la coppia legacy con Saldo prima di Acconto).
Per questo la migration 0019 NON fa il backfill per ctid, che avrebbe fotografato
lo scombinamento: ordina per percent DESC con tie su label, che ricostruisce
l'intento di tutti gli schemi esistenti. Verificato: rimette a posto tutti e 5.
Aggiunto anche l'indice su (project_id, sort_order): una FK non crea indice sul
lato referenziante, e payments non ne aveva alcuno oltre alla PK.
Ora le rate si rinominano e gli importi si sovrascrivono a mano (EditableCell +
updatePaymentField, con la stessa normalizzazione it-IT di updateServiceField).
L'importo scritto a mano e' legge: amount_locked lo esclude dal ricalcolo. Quando
la somma delle rate non corrisponde al totale il tab lo dice, con la cifra esatta,
invece di aggiustare di nascosto.
Lo schema a 3 rate passa da 50/30/20 a 50/25/25 (le righe gia' esistenti non
cambiano: vale solo quando lo si riseleziona).
Due bug trovati per strada e chiusi:
- rescalePayments decideva con some(percent !== null): bastava UNA riga con
percent per far entrare tutto il progetto nel ramo percentuale, che calcolava
newTotal * 0 e azzerava ogni riga con percent NULL. splitPayment inserisce la
Rata 2 proprio cosi', quindi splittare una rata e poi toccare il totale la
portava a zero. Ora la regola e' per riga, non per progetto.
- Il selettore di schema fa DELETE+INSERT e cancellava in silenzio anche status e
paid_at, con due rate gia' saldate in produzione. Ora chiede conferma, ma solo
quando c'e' davvero storico da perdere.
Migration 0019 applicata a prod prima del push.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 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>
- 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>
- 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
- 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