docs(planning): archivia v2.1/v2.2/v2.3 e documenta v2.4

.planning/ documentava in dettaglio cio che era vecchio e per niente cio
che e in produzione: le fasi 11-22 (v2.1 e v2.2, chiuse a giugno) erano
ancora in phases/ mentre v1.0 e v2.0 stavano gia in milestones/, e il
lavoro degli ultimi due mesi - gate OTP e ciclo di vita dei retainer, cioe
quello che gira su hub.iamcavalli.net - non aveva nessuna cartella.

- phases/{11,12,14} -> milestones/v2.1-phases/, phases/{18..22} ->
  milestones/v2.2-phases/. Ora phases/ contiene solo la milestone in
  corso, che e quello che state.cjs conta per il progresso
- v2.1-ROADMAP.md ricostruito: era l'unica milestone senza archivio,
  interrotta dal reset del 19/06 e mai chiusa formalmente
- v2.3-ROADMAP.md + v2.3-REQUIREMENTS.md: v2.3 e stata eseguita fuori dal
  ciclo GSD, non esistono PLAN/SUMMARY per fase. L'archivio E la doc
- REQUIREMENTS.md riscritto per v2.4 con il backlog reale
- phases/13 e phases/26: SUMMARY ricostruiti da commit, migration e
  STATUS.md. 26 e il primo numero libero
- research/: cancellate 4 varianti dello stesso PITFALLS e FEATURES/
  SUMMARY, superati da PROJECT.md. Diverse anti-feature erano ormai
  contraddette dai fatti (il Kanban e stato costruito in Phase 19,
  l'email in v2.3, il time tracking esiste)
- cancellati UI-RULES.md e DESIGN-SYSTEM.md (CLAUDE.md li dichiara
  superseded: impongono l'inverso della regola attuale) e HANDOFF.md,
  fermo al 13/06
- SECURITY-*.md -> security/: audit chiuso, ma i report restano la doc di
  cosa e stato ruotato e perche
- PROJECT.md/MILESTONES.md/ROADMAP.md allineati: milestone corrente v2.4,
  sessione OTP 90gg non 30, migrazioni fino alla 0016

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-08 22:38:36 +02:00
parent 187550fedf
commit f7eb7eec23
86 changed files with 1659 additions and 2859 deletions
@@ -0,0 +1,233 @@
---
phase: 18-cleanup-consolidamento
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- src/components/admin/AdminSidebar.tsx
- src/app/admin/forecast/page.tsx
- src/app/admin/quotes/new/page.tsx
- src/components/admin/leads/SendQuoteModal.tsx
- src/app/admin/projects/project-actions.ts
autonomous: true
requirements:
- CLEAN-01
- CLEAN-02
must_haves:
truths:
- "La rotta /admin/forecast non è più raggiungibile (404 o redirect)"
- "La voce 'Forecast' non appare più nella sidebar admin"
- "La rotta /admin/quotes/new non è più raggiungibile"
- "SendQuoteModal non ha più il bottone che naviga a /admin/quotes/new"
- "project-actions.ts non chiama più revalidatePath('/admin/forecast')"
artifacts:
- path: "src/components/admin/AdminSidebar.tsx"
provides: "Sidebar senza voce Forecast"
contains: "NON deve contenere href: \"/admin/forecast\""
- path: "src/app/admin/forecast/page.tsx"
provides: "File eliminato (rotta rimossa)"
- path: "src/app/admin/quotes/new/page.tsx"
provides: "File eliminato (rotta rimossa)"
- path: "src/components/admin/leads/SendQuoteModal.tsx"
provides: "Modal senza bottone navigazione a quotes/new"
- path: "src/app/admin/projects/project-actions.ts"
provides: "Server actions senza revalidatePath forecast"
key_links:
- from: "AdminSidebar.tsx NAV_ITEMS"
to: "/admin/forecast"
via: "href nel NAV_ITEMS array"
pattern: "forecast"
- from: "SendQuoteModal.tsx"
to: "/admin/quotes/new"
via: "window.location.href"
pattern: "quotes/new"
---
<objective>
Rimuovere le rotte /admin/forecast e /admin/quotes/new e tutti i loro entry point.
Purpose: CLEAN-01 e CLEAN-02 — eliminare feature obsolete prima del Sales Loop. Il forecast è sostituito dal loop AI; il quote builder manuale è sostituito dall'agente AI in Phase 21.
Output: Due rotte rimosse, sidebar pulita, SendQuoteModal senza entry point obsoleto, project-actions.ts senza revalidatePath forecast.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
</context>
<tasks>
<task type="auto">
<name>Task 1: Rimuovi rotta Forecast — file, sidebar, revalidatePath (CLEAN-01)</name>
<read_first>
- src/components/admin/AdminSidebar.tsx — array NAV_ITEMS, riga con href: "/admin/forecast"
- src/app/admin/forecast/page.tsx — conferma contenuto prima di eliminare
- src/app/admin/projects/project-actions.ts — righe 138, 145, 161 con revalidatePath("/admin/forecast")
</read_first>
<files>
src/components/admin/AdminSidebar.tsx,
src/app/admin/projects/project-actions.ts
</files>
<action>
1. In AdminSidebar.tsx: rimuovi la riga dell'oggetto Forecast dall'array NAV_ITEMS:
`{ href: "/admin/forecast", label: "Forecast", icon: TrendingUp },`
Rimuovi anche l'import `TrendingUp` da lucide-react se non è usato altrove nel file.
2. In project-actions.ts: rimuovi le tre chiamate `revalidatePath("/admin/forecast")` alle righe ~138, ~145, ~161.
Ogni funzione che la contiene (addProjectOffer, removeProjectOffer, updateProjectOfferTotal)
mantiene le proprie altre revalidatePath (es. `/admin/projects/${projectId}`) — togli SOLO le righe forecast.
3. Elimina il file della rotta forecast:
- src/app/admin/forecast/page.tsx
Elimina anche la directory se è rimasta vuota:
- src/app/admin/forecast/ (directory)
NON toccare: tabelle DB, lib/forecast-queries.ts (deadweight tollerabile, non blocca nulla),
nessun'altra pagina o componente.
</action>
<verify>
<automated>
grep -r "admin/forecast" /Users/simonecavalli/Vault/IAMCAVALLI/src --include="*.tsx" --include="*.ts" | grep -v "node_modules" | grep -v ".next" | wc -l
</automated>
Risultato atteso: 0 righe (nessun riferimento a /admin/forecast rimasto nel codice sorgente).
Verifica aggiuntiva sidebar:
grep "TrendingUp\|forecast" /Users/simonecavalli/Vault/IAMCAVALLI/src/components/admin/AdminSidebar.tsx
Risultato atteso: nessun output (zero match).
</verify>
<acceptance_criteria>
- grep -r "admin/forecast" src/ produce 0 risultati (esclusi node_modules e .next)
- Il file src/app/admin/forecast/page.tsx non esiste più
- AdminSidebar.tsx non contiene la stringa "forecast" né "TrendingUp" (se rimosso)
- project-actions.ts non contiene revalidatePath("/admin/forecast")
</acceptance_criteria>
<done>
La voce Forecast è assente dalla sidebar, la rotta /admin/forecast è 404, nessun server action
chiama revalidatePath su quella rotta.
</done>
</task>
<task type="auto">
<name>Task 2: Rimuovi rotta Quote Builder manuale e il suo entry point (CLEAN-02)</name>
<read_first>
- src/app/admin/quotes/new/page.tsx — conferma contenuto prima di eliminare
- src/components/admin/leads/SendQuoteModal.tsx — riga ~120 con window.location.href = /admin/quotes/new
- src/components/admin/quotes/QuoteBuilderForm.tsx — identifica che è usato solo da quotes/new
- src/components/admin/quotes/OfferSelector.tsx — identifica che è usato solo da QuoteBuilderForm
- src/components/admin/quotes/PriceOverrideInput.tsx — stesso
- src/components/admin/quotes/QuotePreview.tsx — stesso
</read_first>
<files>
src/app/admin/quotes/new/page.tsx,
src/components/admin/leads/SendQuoteModal.tsx,
src/components/admin/quotes/QuoteBuilderForm.tsx,
src/components/admin/quotes/OfferSelector.tsx,
src/components/admin/quotes/PriceOverrideInput.tsx,
src/components/admin/quotes/QuotePreview.tsx
</files>
<action>
1. In SendQuoteModal.tsx: trova il blocco che naviga a /admin/quotes/new:
`window.location.href = /admin/quotes/new?lead_id=${leadId};`
Rimuovi il pulsante / branch che lo contiene. Se il modal ha una logica "crea nuovo preventivo"
che porta a quella rotta, rimuovi quella branch. Mantieni tutto il resto del modal intatto
(il flusso "preventivo esistente" non va toccato).
Prima di modificare, leggi il file per capire la struttura esatta del branch da eliminare.
2. Elimina i file della rotta e i componenti esclusivi:
- src/app/admin/quotes/new/page.tsx
- src/components/admin/quotes/QuoteBuilderForm.tsx
- src/components/admin/quotes/OfferSelector.tsx
- src/components/admin/quotes/PriceOverrideInput.tsx
- src/components/admin/quotes/QuotePreview.tsx
Elimina le directory rimaste vuote:
- src/app/admin/quotes/new/ (directory)
- src/components/admin/quotes/ (directory, solo se completamente vuota dopo la rimozione)
NON toccare: src/app/admin/quotes/ se esistono altre sotto-rotte (verifica prima),
lib/admin-queries.ts (getAllOfferMacrosWithMicros — usata altrove), DB.
ATTENZIONE — verifica preventiva: prima di eliminare src/components/admin/quotes/,
esegui: grep -r "quotes/" src/components --include="*.tsx" --include="*.ts"
per confermare che i quattro componenti sopra non sono importati da altri file al di fuori di quotes/new.
Se ci sono altri consumer, NON eliminare il componente e segnala in SUMMARY.
</action>
<verify>
<automated>
grep -r "quotes/new" /Users/simonecavalli/Vault/IAMCAVALLI/src --include="*.tsx" --include="*.ts" | grep -v "node_modules" | grep -v ".next" | wc -l
</automated>
Risultato atteso: 0 righe.
Verifica esistenza file:
test -f /Users/simonecavalli/Vault/IAMCAVALLI/src/app/admin/quotes/new/page.tsx && echo "ESISTE_ANCORA" || echo "OK_RIMOSSO"
Risultato atteso: OK_RIMOSSO
Verifica SendQuoteModal:
grep "quotes/new" /Users/simonecavalli/Vault/IAMCAVALLI/src/components/admin/leads/SendQuoteModal.tsx
Risultato atteso: nessun output.
</verify>
<acceptance_criteria>
- grep -r "quotes/new" src/ produce 0 risultati
- src/app/admin/quotes/new/page.tsx non esiste più
- src/components/admin/quotes/QuoteBuilderForm.tsx non esiste più
- SendQuoteModal.tsx non contiene la stringa "quotes/new"
</acceptance_criteria>
<done>
La rotta /admin/quotes/new è 404, il pulsante di navigazione nel SendQuoteModal è rimosso,
i componenti esclusivi del quote builder sono eliminati.
</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| admin UI → file system | Operazioni di eliminazione file irreversibili |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-18-01 | Tampering | project-actions.ts | accept | Rimozione di sole righe revalidatePath inerte — nessun path di dati toccato; le funzioni restano funzionali |
| T-18-02 | Denial of Service | SendQuoteModal.tsx | mitigate | Leggere il file prima di modificare per evitare di rompere il flusso "preventivo esistente" che resta necessario |
</threat_model>
<verification>
Al termine del piano:
1. `npm run build` deve completare senza errori TypeScript (nessun import rotto)
2. `grep -r "admin/forecast" src/` → 0 risultati
3. `grep -r "quotes/new" src/` → 0 risultati
4. La sidebar admin non mostra la voce "Forecast"
</verification>
<success_criteria>
- CLEAN-01: /admin/forecast → 404, sidebar senza voce Forecast, project-actions.ts senza revalidatePath forecast
- CLEAN-02: /admin/quotes/new → 404, SendQuoteModal senza navigazione a quotes/new, componenti quotes/ rimossi
- Build TypeScript pulita (nessun import broken)
</success_criteria>
<output>
After completion, create `.planning/phases/18-cleanup-consolidamento/18-01-SUMMARY.md`
</output>
@@ -0,0 +1,156 @@
---
phase: 18-cleanup-consolidamento
plan: "01"
subsystem: ui
tags: [next.js, admin, cleanup, sidebar, server-actions]
requires: []
provides:
- Forecast route removed (/admin/forecast → 404)
- Admin sidebar without Forecast entry
- project-actions.ts without revalidatePath forecast calls
- Quote builder route removed (/admin/quotes/new → 404)
- SendQuoteModal without navigation to quotes/new
- QuoteBuilderForm, OfferSelector, PriceOverrideInput, QuotePreview components deleted
affects: [phase-21-ai-quote-agent, any future admin nav changes]
tech-stack:
added: []
patterns:
- "Dead-code deletion: remove page files + directories + all entry points atomically before Sales Loop phase"
key-files:
created: []
modified:
- src/components/admin/AdminSidebar.tsx
- src/app/admin/projects/project-actions.ts
- src/components/admin/leads/SendQuoteModal.tsx
- src/lib/admin-queries.ts
deleted:
- src/app/admin/forecast/page.tsx
- src/app/admin/quotes/new/page.tsx
- src/app/admin/quotes/new/actions.ts
- src/components/admin/quotes/QuoteBuilderForm.tsx
- src/components/admin/quotes/OfferSelector.tsx
- src/components/admin/quotes/PriceOverrideInput.tsx
- src/components/admin/quotes/QuotePreview.tsx
key-decisions:
- "Kept lib/forecast-queries.ts and lib/quote-actions.ts as tolerable deadweight — no broken imports, no consumer"
- "SendQuoteModal Tabs UI replaced with flat single-form layout (only 'existing' flow remains)"
- "quotes/new/actions.ts (re-export shim) deleted along with page since its only consumer was QuoteBuilderForm"
- "Stale JSDoc comment in admin-queries.ts referencing /admin/quotes/new cleaned up (Rule 1 auto-fix)"
patterns-established:
- "Cleanup tasks: always grep for all entry points (revalidatePath, navigation, JSDoc) before marking done"
requirements-completed: [CLEAN-01, CLEAN-02]
duration: 12min
completed: 2026-06-19
---
# Phase 18 Plan 01: Cleanup — Remove Forecast and Manual Quote Builder Routes Summary
**Deleted /admin/forecast and /admin/quotes/new routes with all entry points (sidebar, revalidatePath, navigation button, exclusive components) to clear the codebase for the Sales Loop AI phase.**
## Performance
- **Duration:** ~12 min
- **Started:** 2026-06-19T10:04:00Z
- **Completed:** 2026-06-19T10:16:00Z
- **Tasks:** 2
- **Files modified/deleted:** 11
## Accomplishments
- Removed Forecast route: deleted `src/app/admin/forecast/page.tsx`, removed sidebar nav entry and `TrendingUp` import, stripped 3 `revalidatePath("/admin/forecast")` calls from project-actions.ts
- Removed Quote Builder route: deleted `quotes/new/page.tsx`, `quotes/new/actions.ts`, and all 4 exclusive components (QuoteBuilderForm, OfferSelector, PriceOverrideInput, QuotePreview)
- Simplified SendQuoteModal from two-tab layout to single flat form — "Crea Nuovo" tab and `window.location` navigation removed entirely; "existing quote" flow unchanged
- Build passes clean: 0 TypeScript errors, 0 broken imports
## Task Commits
1. **Task 1: Rimuovi rotta Forecast** - `7d88409` (chore)
2. **Task 2: Rimuovi rotta Quote Builder** - `268f56c` (chore)
**Plan metadata:** (see final commit below)
## Files Created/Modified
- `src/components/admin/AdminSidebar.tsx` - Removed Forecast NAV_ITEMS entry and TrendingUp import
- `src/app/admin/projects/project-actions.ts` - Removed 3 revalidatePath("/admin/forecast") calls
- `src/components/admin/leads/SendQuoteModal.tsx` - Removed "Crea Nuovo" tab and quotes/new navigation
- `src/lib/admin-queries.ts` - Removed stale JSDoc comment referencing /admin/quotes/new
**Deleted:**
- `src/app/admin/forecast/page.tsx`
- `src/app/admin/quotes/new/page.tsx`
- `src/app/admin/quotes/new/actions.ts`
- `src/components/admin/quotes/QuoteBuilderForm.tsx`
- `src/components/admin/quotes/OfferSelector.tsx`
- `src/components/admin/quotes/PriceOverrideInput.tsx`
- `src/components/admin/quotes/QuotePreview.tsx`
## Decisions Made
- Kept `lib/forecast-queries.ts` and `lib/quote-actions.ts` intact — they have no broken imports and are tolerable deadweight; removing them was explicitly out of scope per the plan
- Deleted `quotes/new/actions.ts` (not listed in plan) because it was a server re-export shim with `QuoteBuilderForm` as its only consumer — Rule 3 auto-fix (would have blocked clean directory removal)
- Stale JSDoc comment in `admin-queries.ts` updated — Rule 1 auto-fix (stale reference counts as dead code)
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] Removed stale JSDoc comment referencing deleted route**
- **Found during:** Task 2 (final grep verification)
- **Issue:** `admin-queries.ts` line 862 had a `* Used by: /admin/quotes/new form (OfferSelector dropdown)` comment — the route no longer exists
- **Fix:** Removed the stale line from the JSDoc block
- **Files modified:** src/lib/admin-queries.ts
- **Verification:** `grep -r "quotes/new" src/` → 0 results
- **Committed in:** 268f56c (Task 2 commit)
**2. [Rule 3 - Blocking] Deleted quotes/new/actions.ts (unlisted file)**
- **Found during:** Task 2 — directory was non-empty after deleting page.tsx
- **Issue:** `src/app/admin/quotes/new/actions.ts` was a re-export shim (`export { createQuote } from "@/lib/quote-actions"`) with QuoteBuilderForm as its only consumer. It would leave a broken directory if not removed.
- **Fix:** Confirmed no other consumers via grep, then deleted the file
- **Files modified:** src/app/admin/quotes/new/actions.ts (deleted)
- **Verification:** `grep -r "createQuote" src/` shows only lib/quote-actions.ts (the source) — no broken references
- **Committed in:** 268f56c (Task 2 commit)
---
**Total deviations:** 2 auto-fixed (1 Rule 1 stale comment, 1 Rule 3 blocking unlisted file)
**Impact on plan:** Both auto-fixes necessary for completeness and clean directory state. No scope creep.
## Issues Encountered
- A linter hook reverted `SendQuoteModal.tsx` after the first Write. On re-read, the linter had actually applied the intended changes (removed Tabs imports, removed tab state, flattened to single form). Confirmed the file was correct before staging.
## User Setup Required
None — no external service configuration required.
## Next Phase Readiness
- CLEAN-01 and CLEAN-02 complete: codebase is clear of forecast and manual quote builder dead routes
- Ready for Phase 21 (AI Quote Agent) which will provide the replacement quote creation flow
- `lib/forecast-queries.ts` and `lib/quote-actions.ts` remain as inert deadweight — can be cleaned in a future pass if desired
---
*Phase: 18-cleanup-consolidamento*
*Completed: 2026-06-19*
## Self-Check: PASSED
- `src/components/admin/AdminSidebar.tsx` — FOUND (modified)
- `src/app/admin/projects/project-actions.ts` — FOUND (modified)
- `src/components/admin/leads/SendQuoteModal.tsx` — FOUND (modified)
- `src/lib/admin-queries.ts` — FOUND (modified)
- `src/app/admin/forecast/page.tsx` — MISSING (deleted as intended)
- `src/app/admin/quotes/new/page.tsx` — MISSING (deleted as intended)
- Commit 7d88409 — FOUND
- Commit 268f56c — FOUND
- `grep -r "admin/forecast" src/` → 0 results
- `grep -r "quotes/new" src/` → 0 results
- Build: 0 errors
@@ -0,0 +1,345 @@
---
phase: 18-cleanup-consolidamento
plan: 02
type: execute
wave: 1
depends_on: []
files_modified:
- src/app/admin/page.tsx
- src/app/admin/analytics/page.tsx
- src/components/admin/YearSelector.tsx
autonomous: true
requirements:
- CLEAN-03
must_haves:
truths:
- "La Dashboard /admin mostra le statistiche annuali (MetricCard fatturato, grafico mensile, time tracking)"
- "La rotta /admin/analytics non esiste più (404)"
- "Il selettore anno naviga a /admin?year=X, non a /admin/analytics?year=X"
- "Non esiste più il file src/app/admin/analytics/page.tsx"
artifacts:
- path: "src/app/admin/page.tsx"
provides: "Dashboard con sezione Statistiche integrata"
contains: "getAnalyticsByYear"
- path: "src/app/admin/analytics/page.tsx"
provides: "File eliminato"
- path: "src/components/admin/YearSelector.tsx"
provides: "YearSelector che naviga a /admin?year=Y"
contains: "router.push(`/admin?year=${y}`)"
key_links:
- from: "src/app/admin/page.tsx"
to: "lib/analytics-queries"
via: "import getAnalyticsByYear, getMonthlyCollected, getAvailableYears, getTimeByClient, getTotalTrackedHours"
pattern: "analytics-queries"
- from: "YearSelector"
to: "/admin"
via: "router.push"
pattern: "router.push.*admin"
---
<objective>
Fondere le statistiche di /admin/analytics nella Dashboard /admin ed eliminare la rotta duplicata.
Purpose: CLEAN-03 — unica vista admin per statistiche. Elimina il doppione /admin/analytics e porta
tutto il suo contenuto (MetricCard economiche + MonthlyChart + time tracking per cliente) nella dashboard
esistente, sotto le KPI card e il feed attività già presenti.
Output: /admin mostra statistiche annuali, /admin/analytics → 404, YearSelector aggiornato.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
</context>
<interfaces>
<!-- Estratto da src/app/admin/analytics/page.tsx — contratti da portare nella dashboard -->
Imports da copiare in admin/page.tsx:
```typescript
import {
getAnalyticsByYear,
getMonthlyCollected,
getAvailableYears,
getTimeByClient,
getTotalTrackedHours,
} from "@/lib/analytics-queries";
import { YearSelector, MonthlyChart } from "@/components/admin/YearSelector";
```
Funzioni helper da copiare in admin/page.tsx:
```typescript
function fmtEur(n: number) { /* formatta in EUR it-IT */ }
function fmtSeconds(s: number): string { /* h m format */ }
```
ATTENZIONE: admin/page.tsx ha già una funzione `fmtEur` (ma accetta `string`, non `number`).
Rinominare una delle due o unificarle — preferire la versione che accetta `number` (più robusta)
e aggiornare i consumer nel file.
Componente MetricCard (da analytics/page.tsx) — da spostare in admin/page.tsx:
```typescript
function MetricCard({ label, value, sub, accent }: {
label: string; value: string; sub?: string; accent?: boolean;
}) { ... }
```
Questo componente è diverso da KpiCard già presente in admin/page.tsx:
KpiCard ha icona + colore; MetricCard ha stile accent verde o bianco con bordo.
Mantieni ENTRAMBI (scopi diversi): KpiCard per le 4 metriche operative in cima,
MetricCard per le statistiche economiche annuali nella nuova sezione.
Firma del componente analytics/page.tsx (async, searchParams):
```typescript
export default async function AnalyticsPage({
searchParams,
}: {
searchParams: Promise<{ year?: string }>;
}) {
const { year: yearParam } = await searchParams;
const year = parseInt(yearParam ?? "") || new Date().getFullYear();
...
}
```
La stessa logica `year` va aggiunta al componente AdminDashboard in admin/page.tsx.
</interfaces>
<tasks>
<task type="auto">
<name>Task 1: Integra statistiche annuali nella Dashboard (CLEAN-03)</name>
<read_first>
- src/app/admin/page.tsx — struttura attuale (KpiCard, FollowUpWidget, activity feed, fmtEur esistente)
- src/app/admin/analytics/page.tsx — tutto il contenuto da portare (già in contesto da discovery)
- src/components/admin/YearSelector.tsx — YearSelector e MonthlyChart (già in contesto da discovery)
</read_first>
<files>
src/app/admin/page.tsx,
src/components/admin/YearSelector.tsx
</files>
<action>
STEP 1 — Aggiorna AdminDashboard in src/app/admin/page.tsx:
a) Aggiungi alla firma del componente il parametro searchParams (come in analytics/page.tsx):
```typescript
export default async function AdminDashboard({
searchParams,
}: {
searchParams: Promise<{ year?: string }>;
}) {
const { year: yearParam } = await searchParams;
const year = parseInt(yearParam ?? "") || new Date().getFullYear();
```
b) Aggiungi gli import mancanti in cima al file:
```typescript
import {
getAnalyticsByYear,
getMonthlyCollected,
getAvailableYears,
getTimeByClient,
getTotalTrackedHours,
} from "@/lib/analytics-queries";
import { YearSelector, MonthlyChart } from "@/components/admin/YearSelector";
```
c) Aggiungi le query analytics al Promise.all esistente (o crea un secondo await per separare
le query di diversa frequenza — è accettabile avere due await distinti per chiarezza):
```typescript
const [data, monthly, availableYears, timeByClient, totalHours] = await Promise.all([
getAnalyticsByYear(year),
getMonthlyCollected(year),
getAvailableYears(),
getTimeByClient(year),
getTotalTrackedHours(year),
]);
```
d) Copia la funzione MetricCard da analytics/page.tsx nel file (accanto a KpiCard).
e) Risolvi il conflitto fmtEur:
- La versione attuale in admin/page.tsx accetta `string` e fa `parseFloat(val)`.
- Quella in analytics/page.tsx accetta `number`.
- Unifica in una sola funzione che accetta `number`, aggiorna i consumer esistenti
nel file che passavano una stringa (es. `fmtEur(kpi.revenueTotale)` e
`fmtEur(kpi.pagamentiInSospeso)`) convertendo a number dove necessario,
oppure mantieni entrambe con nomi distinti (`fmtEurStr` / `fmtEurNum`).
Scegli l'approccio più pulito senza rompere i KPI card esistenti.
f) Aggiungi la sezione Statistiche DOPO il feed attività esistente (mantieni tutto quello
che c'era prima intatto — FollowUpWidget, KPI card, activity feed):
```tsx
{/* ── SEZIONE STATISTICHE ANNUALI ── */}
<div className="mt-10 space-y-10">
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-bold text-gray-900">Statistiche</h2>
<p className="text-sm text-gray-400 mt-0.5">Panoramica per anno</p>
</div>
<YearSelector currentYear={year} availableYears={availableYears} />
</div>
{/* Sezione economica */}
<div className="space-y-4">
<h3 className="text-sm font-bold text-[#71717a] uppercase tracking-wider">Fatturato</h3>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<MetricCard label="Contrattualizzato" value={fmtEur(data.contracted)}
sub={`${data.clientsAcquired} client${data.clientsAcquired === 1 ? "e" : "i"}`} accent />
<MetricCard label="Incassato" value={fmtEur(data.collected)}
sub={`${collectedPct}% del contrattualizzato`} />
<MetricCard label="Da incassare" value={fmtEur(data.pending)} sub="Tutti gli anni" />
<MetricCard label="Clienti acquisiti" value={String(data.clientsAcquired)} sub={`Anno ${year}`} />
</div>
<MonthlyChart data={monthly} year={year} />
</div>
{/* Sezione time tracking */}
<div className="space-y-4">
<h3 className="text-sm font-bold text-[#71717a] uppercase tracking-wider">Tempo tracciato</h3>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<MetricCard label="Ore totali" value={`${totalHours}h`} sub={`Anno ${year}`} accent />
</div>
{/* ... tabella ore per cliente (copia da analytics/page.tsx) ... */}
</div>
</div>
```
Ricopia fedelmente il blocco "ore per cliente" da analytics/page.tsx (righe 127-160)
nella sezione time tracking della dashboard.
Calcola collectedPct prima del return:
```typescript
const collectedPct = data.contracted > 0
? Math.round((data.collected / data.contracted) * 100) : 0;
const maxClientSeconds = timeByClient[0]?.totalSeconds ?? 1;
```
STEP 2 — Aggiorna YearSelector per navigare verso /admin:
In src/components/admin/YearSelector.tsx, riga ~18:
```typescript
// PRIMA:
router.push(`/admin/analytics?year=${y}`);
// DOPO:
router.push(`/admin?year=${y}`);
```
NON modificare: MonthlyChart (rimane nello stesso file), struttura esistente della dashboard
(FollowUpWidget, KPI card, activity feed — tutto invariato sopra la nuova sezione).
</action>
<verify>
<automated>
grep "getAnalyticsByYear\|getMonthlyCollected\|getAvailableYears" /Users/simonecavalli/Vault/IAMCAVALLI/src/app/admin/page.tsx | wc -l
</automated>
Risultato atteso: 3 (tutte e tre le query importate e usate).
Verifica YearSelector:
grep "router.push" /Users/simonecavalli/Vault/IAMCAVALLI/src/components/admin/YearSelector.tsx
Risultato atteso: contiene "/admin?year=" e NON "/admin/analytics".
</verify>
<acceptance_criteria>
- src/app/admin/page.tsx importa da @/lib/analytics-queries
- src/app/admin/page.tsx importa YearSelector e MonthlyChart
- YearSelector.tsx naviga a /admin?year=Y (non a /admin/analytics)
- Il file compila senza errori TypeScript
</acceptance_criteria>
<done>
La Dashboard mostra le statistiche annuali con selettore anno; il selettore naviga
correttamente a /admin?year=X.
</done>
</task>
<task type="auto">
<name>Task 2: Elimina rotta /admin/analytics (CLEAN-03)</name>
<read_first>
- src/app/admin/analytics/page.tsx — conferma che non è più necessario (Task 1 ha già portato
tutto il contenuto nella dashboard)
</read_first>
<files>
src/app/admin/analytics/page.tsx
</files>
<action>
1. Verifica preventiva — assicurati che nessun altro file nel progetto importi da
src/app/admin/analytics/page.tsx direttamente (è una page, non un modulo, quindi
non dovrebbe avere consumer diretti — ma controlla):
grep -r "admin/analytics" src/ --include="*.tsx" --include="*.ts"
Se trovi riferimenti diversi da YearSelector (già aggiornato in Task 1), risolvili prima.
2. Elimina:
- src/app/admin/analytics/page.tsx
- Directory src/app/admin/analytics/ (se vuota dopo la rimozione del file)
NON eliminare: lib/analytics-queries.ts (ancora importata dalla dashboard),
src/components/admin/YearSelector.tsx (ancora usata dalla dashboard).
</action>
<verify>
<automated>
test -f /Users/simonecavalli/Vault/IAMCAVALLI/src/app/admin/analytics/page.tsx && echo "ESISTE_ANCORA" || echo "OK_RIMOSSO"
</automated>
Risultato atteso: OK_RIMOSSO
Verifica nessun riferimento rimasto:
grep -r "admin/analytics" /Users/simonecavalli/Vault/IAMCAVALLI/src --include="*.tsx" --include="*.ts" | grep -v "node_modules" | grep -v ".next"
Risultato atteso: 0 righe.
</verify>
<acceptance_criteria>
- src/app/admin/analytics/page.tsx non esiste più
- grep -r "admin/analytics" src/ produce 0 risultati
- npm run build completa senza errori
</acceptance_criteria>
<done>
La rotta /admin/analytics è eliminata. Navigarci restituisce 404.
Tutte le statistiche sono accessibili alla Dashboard /admin.
</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| admin/page.tsx → analytics-queries | Aggiunta di query server-side già esistenti — nessuna nuova superficie |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-18-03 | Information Disclosure | admin/page.tsx | accept | Le statistiche erano già accessibili a /admin/analytics — stessa autenticazione Auth.js, nessuna nuova esposizione |
| T-18-04 | Tampering | YearSelector.tsx | accept | Il cambio da /admin/analytics a /admin nella navigazione è una semplice redirect di path — nessun dato mutato |
</threat_model>
<verification>
Al termine del piano:
1. `npm run build` completa senza errori TypeScript
2. `grep -r "admin/analytics" src/` → 0 risultati
3. La Dashboard /admin mostra le sezioni Statistiche con selettore anno
4. Navigare a /admin/analytics restituisce 404
5. YearSelector porta a /admin?year=X quando si cambia anno
</verification>
<success_criteria>
- CLEAN-03: /admin mostra le statistiche annuali (MetricCard + MonthlyChart + time tracking)
- /admin/analytics → 404 (rotta eliminata)
- YearSelector naviga a /admin?year=X
- Build TypeScript pulita
</success_criteria>
<output>
After completion, create `.planning/phases/18-cleanup-consolidamento/18-02-SUMMARY.md`
</output>
@@ -0,0 +1,113 @@
---
phase: 18-cleanup-consolidamento
plan: "02"
subsystem: ui
tags: [nextjs, analytics, dashboard, react, tailwind]
# Dependency graph
requires:
- phase: analytics-queries
provides: getAnalyticsByYear, getMonthlyCollected, getAvailableYears, getTimeByClient, getTotalTrackedHours
provides:
- Admin dashboard at /admin integrates annual statistics (MetricCard, MonthlyChart, time tracking per client)
- /admin/analytics route deleted — returns 404
- YearSelector navigates to /admin?year=Y
affects: [18-cleanup-consolidamento, admin-ui, analytics]
# Tech tracking
tech-stack:
added: []
patterns:
- Server page accepts searchParams for year filtering
- Analytics queries co-located with dashboard queries in same async page
key-files:
created: []
modified:
- src/app/admin/page.tsx
- src/components/admin/YearSelector.tsx
deleted:
- src/app/admin/analytics/page.tsx
key-decisions:
- "Unified fmtEur to accept number (analytics version); KPI card callers wrapped with parseFloat() for string DB values"
- "Two separate awaits in AdminDashboard: getDashboardStats() first, then analytics Promise.all — acceptable for clarity"
- "Stale .next cache (OfferSelector.tsx reference) cleared before final build — pre-existing issue, not introduced by this plan"
patterns-established:
- "Statistics section appended after activity feed — existing dashboard structure preserved above new section"
requirements-completed:
- CLEAN-03
# Metrics
duration: 15min
completed: 2026-06-19
---
# Phase 18 Plan 02: Consolidate Analytics Dashboard Summary
**Merged /admin/analytics statistics (MetricCard, MonthlyChart, per-client time tracking) into /admin dashboard and deleted the duplicate route**
## Performance
- **Duration:** ~15 min
- **Started:** 2026-06-19T10:04:00Z
- **Completed:** 2026-06-19T10:19:00Z
- **Tasks:** 2
- **Files modified:** 3 (2 modified, 1 deleted)
## Accomplishments
- AdminDashboard now shows annual statistics section below activity feed: MetricCard economic metrics, MonthlyChart, and per-client time tracking bars
- /admin/analytics route deleted — navigating there returns 404
- YearSelector updated to navigate to /admin?year=Y instead of /admin/analytics?year=Y
- fmtEur unified to number-accepting version (more robust); existing KPI card string values wrapped with parseFloat()
## Task Commits
1. **Task 1: Integra statistiche annuali nella Dashboard** — included in `14bdbab` (feat)
2. **Task 2: Elimina rotta /admin/analytics** — included in `14bdbab` (feat)
**Plan metadata:** (docs commit below)
## Files Created/Modified
- `src/app/admin/page.tsx` — Added analytics imports, searchParams, analytics queries, MetricCard, fmtSeconds, fmtEur(number), statistics section
- `src/components/admin/YearSelector.tsx` — Updated router.push from /admin/analytics to /admin
- `src/app/admin/analytics/page.tsx` — Deleted
## Decisions Made
- Unified `fmtEur` to the `number` version from analytics (cleaner, no `parseFloat` internally). Updated the two KPI card callers that received string values from DB to pass `parseFloat(kpi.revenueTotale)` and `parseFloat(kpi.pagamentiInSospeso)`.
- Used two sequential awaits (getDashboardStats first, then analytics Promise.all) rather than one large combined Promise.all — avoids mixing query concerns and follows plan guidance for acceptable two-await pattern.
- Preserved all existing dashboard content (FollowUpWidget, KPI cards, activity feed) exactly; statistics section appended at the bottom with `mt-10`.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] Cleared stale .next cache referencing deleted OfferSelector.tsx**
- **Found during:** Build verification after Task 2
- **Issue:** Stale `.next/tsconfig.tsbuildinfo` incremental cache listed `src/components/admin/quotes/OfferSelector.tsx` as a root file. That file had been deleted in a prior session. TypeScript errored with "File not found — root file specified for compilation." The pre-existing cache was masking this because prior builds hit cached output.
- **Fix:** Ran `rm -rf .next` to clear the stale incremental cache, then rebuilt clean.
- **Files modified:** .next/ (cache only — not tracked in git)
- **Verification:** Clean build passes with TypeScript check and all routes compiled correctly.
- **Committed in:** n/a (cache not committed)
---
**Total deviations:** 1 auto-fixed (Rule 3 - blocking)
**Impact on plan:** Required to get a clean build. Pre-existing issue unrelated to this plan's scope. .next/ is gitignored so no source changes.
## Issues Encountered
- Stale `.next` incremental TypeScript cache referenced `OfferSelector.tsx` (deleted in a prior session). Cleared cache, rebuilt clean. Build passes correctly.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- /admin is the single analytics entry point — ready for 18-03 and any future admin UI work
- No stale routes remain in the analytics path
- Build is clean
---
*Phase: 18-cleanup-consolidamento*
*Completed: 2026-06-19*
@@ -0,0 +1,197 @@
---
phase: 18-cleanup-consolidamento
plan: 03
type: execute
wave: 2
depends_on:
- "18-01"
- "18-02"
autonomous: false
files_modified:
- .planning/ROADMAP.md
- .planning/REQUIREMENTS.md
requirements:
- CLEAN-04
must_haves:
truths:
- "Le fasi 13/15/16/17 sono marcate esplicitamente come cancellate/congelate/ri-scopate nel ROADMAP.md"
- "REQUIREMENTS.md riflette lo stato aggiornato dei requirement corrispondenti"
- "CLEAN-04 è marcato Done in REQUIREMENTS.md"
- "La build del progetto passa dopo le rimozioni dei piani precedenti"
artifacts:
- path: ".planning/ROADMAP.md"
provides: "Fasi 13/15/16/17 con status esplicito cancellate/congelate"
contains: "CONGELATA\|ABBANDONATA\|RI-SCOPATA"
- path: ".planning/REQUIREMENTS.md"
provides: "CLEAN-04 marcato Complete"
key_links:
- from: "ROADMAP.md fasi 13/15/16/17"
to: "STATUS field"
via: "checklist e label espliciti"
pattern: "Congelata|Abbandonata|Ri-scopata"
---
<objective>
Verificare formalmente il completamento di CLEAN-04 e validare la build post-cleanup.
Purpose: CLEAN-04 — le fasi v2.1 residue sono già state archiviate/marcate nel reset 2026-06-19.
Questo piano le verifica, le sigla formalmente nel planning e valida che i piani 01/02
non abbiano introdotto errori di build.
Output: CLEAN-04 marcato Done, build TypeScript verificata, check umano che la dashboard funziona.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/ROADMAP.md
@.planning/REQUIREMENTS.md
@.planning/STATE.md
</context>
<tasks>
<task type="auto">
<name>Task 1: Verifica CLEAN-04 e aggiorna planning docs</name>
<read_first>
- .planning/ROADMAP.md — sezione fasi 13/15/16/17, verifica che abbiano label/status esplicito
- .planning/REQUIREMENTS.md — sezione CLEAN-04, verifica stato attuale
</read_first>
<files>
.planning/ROADMAP.md,
.planning/REQUIREMENTS.md
</files>
<action>
STEP 1 — Verifica ROADMAP.md:
Leggi le voci delle fasi 13, 15, 16, 17 nel ROADMAP.md.
Devono avere tutte uno status esplicito. Controlla che la checklist mostri:
- Phase 13: [~] con label "❌ CONGELATA (reset 2026-06-19)"
- Phase 15: [~] con label "❌ ABBANDONATA (reset 2026-06-19)"
- Phase 16: [~] con label "❌ RI-SCOPATA in v2.2"
- Phase 17: [~] con label "❌ RI-SCOPATA in v2.2"
Se uno di questi status manca o è incompleto, aggiungilo/completalo.
Aggiungi anche una nota nella tabella Progress in fondo con la data di chiusura 2026-06-19
dove mancante.
STEP 2 — Aggiorna REQUIREMENTS.md:
Nella tabella "Cleanup & Consolidamento (Phase 18 / R1)":
- CLEAN-01: cambia Status da "Pending" a "Complete"
- CLEAN-02: cambia Status da "Pending" a "Complete"
- CLEAN-03: cambia Status da "Pending" a "Complete"
- CLEAN-04: cambia Status da "In corso (questo reset)" a "Complete"
Nella tabella Traceability in fondo:
- CLEAN-01 | Phase 18 (R1) | Complete
- CLEAN-02 | Phase 18 (R1) | Complete
- CLEAN-03 | Phase 18 (R1) | Complete
- CLEAN-04 | Phase 18 (R1) | Complete
STEP 3 — Build verification:
Esegui: cd /Users/simonecavalli/Vault/IAMCAVALLI && npm run build
Se la build fallisce:
- Leggi l'output dell'errore
- Identifica il file con import rotto
- Risolvi (probabilmente un import di componente eliminato o una funzione non trovata)
- Ri-esegui npm run build fino a successo
Registra nel SUMMARY se ci sono stati fix necessari.
</action>
<verify>
<automated>
grep "CLEAN-04" /Users/simonecavalli/Vault/IAMCAVALLI/.planning/REQUIREMENTS.md
</automated>
Risultato atteso: riga contenente "CLEAN-04" e "Complete".
Verifica fasi archiviate:
grep -E "Phase 13|Phase 15|Phase 16|Phase 17" /Users/simonecavalli/Vault/IAMCAVALLI/.planning/ROADMAP.md | grep -E "CONGELATA|ABBANDONATA|RI-SCOPATA" | wc -l
Risultato atteso: 4 (tutte e quattro le fasi con label esplicito).
</verify>
<acceptance_criteria>
- REQUIREMENTS.md: CLEAN-04 è "Complete"
- REQUIREMENTS.md: CLEAN-01, CLEAN-02, CLEAN-03 sono "Complete"
- ROADMAP.md: fasi 13/15/16/17 hanno label di stato esplicito (CONGELATA/ABBANDONATA/RI-SCOPATA)
- npm run build completa senza errori
</acceptance_criteria>
<done>
Tutti i requisiti CLEAN-01..04 sono marcati Complete nei planning docs.
La build TypeScript è pulita. Phase 18 è formalmente chiusa.
</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>Checkpoint: Verifica visuale Dashboard e assenza rotte rimosse</name>
<what-built>
Piano 01 ha rimosso /admin/forecast e /admin/quotes/new.
Piano 02 ha fuso le statistiche di /admin/analytics nella Dashboard /admin
e rimosso la rotta /admin/analytics.
Il selettore anno nella dashboard naviga a /admin?year=X.
</what-built>
<how-to-verify>
1. Avvia il dev server: `npm run dev` (o `npx next dev`)
2. Vai su http://localhost:3000/admin — verifica:
- Sidebar: NON compare la voce "Forecast"
- Dashboard: in fondo alla pagina compare la sezione "Statistiche" con selettore anno,
MetricCard economiche (Contrattualizzato / Incassato / Da incassare / Clienti acquisiti),
grafico mensile a barre, sezione ore per cliente
- KPI card esistenti (Clienti attivi, Revenue totale, Progetti in corso, Pagamenti in sospeso)
sono ancora presenti in cima
- FollowUpWidget e feed attività sono ancora presenti
3. Vai su http://localhost:3000/admin/forecast → deve restituire 404
4. Vai su http://localhost:3000/admin/analytics → deve restituire 404
5. Vai su http://localhost:3000/admin/quotes/new → deve restituire 404
6. Clicca sul selettore anno nella sezione Statistiche → cambia anno e verifica che
l'URL diventa /admin?year=XXXX (non /admin/analytics)
7. Nel dettaglio di un lead, apri il modal "Invia preventivo" → verifica che NON
compare più il bottone per creare un nuovo preventivo manuale
</how-to-verify>
<resume-signal>
Scrivi "ok" se tutto funziona correttamente.
Se trovi problemi, descrivi cosa non funziona e l'esecuzione correggerà prima di chiudere la fase.
</resume-signal>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| planning docs → stato del progetto | Aggiornamento documenti di tracking — nessuna superficie di attacco |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-18-05 | Repudiation | ROADMAP.md / REQUIREMENTS.md | accept | I planning docs sono in git — ogni modifica è tracciata e reversibile |
</threat_model>
<verification>
Al termine del piano:
1. REQUIREMENTS.md: CLEAN-01, CLEAN-02, CLEAN-03, CLEAN-04 → tutti "Complete"
2. ROADMAP.md: fasi 13/15/16/17 con status esplicito
3. npm run build → 0 errori
4. Checkpoint umano superato (Dashboard mostra statistiche, rotte rimosse → 404)
</verification>
<success_criteria>
- CLEAN-04: fasi v2.1 residue formalmente chiuse nei planning docs
- Planning docs aggiornati con stati finali Phase 18
- Build pulita confermata
- Verifica visuale umana completata
</success_criteria>
<output>
After completion, create `.planning/phases/18-cleanup-consolidamento/18-03-SUMMARY.md`
</output>
@@ -0,0 +1,27 @@
---
plan: 18-03
status: complete
completed_at: 2026-06-19
---
# Plan 18-03 Summary — Cleanup Verification & Planning Docs
## Tasks completed
**Task 1 (auto):**
- REQUIREMENTS.md: CLEAN-01, CLEAN-02, CLEAN-03, CLEAN-04 → Complete (requirements table + traceability table)
- ROADMAP.md: fasi 13/15/16/17 già avevano label espliciti da reset 2026-06-19 (CONGELATA/ABBANDONATA/RI-SCOPATA)
- Build: `npm run build` → 0 errori, 0 warning. Route /admin/forecast, /admin/analytics, /admin/quotes/new assenti dalla route list.
**Task 2 (checkpoint umano):**
- Verifica visuale superata dall'utente dopo deploy su Coolify.
- Dashboard /admin mostra statistiche con MetricCard e grafico mensile.
- Rotte rimosse restituiscono 404.
- Selettore anno naviga a /admin?year=X.
- SendQuoteModal senza bottone preventivo manuale.
## Files modified
- `.planning/REQUIREMENTS.md` — CLEAN-01..04 marcati Complete
## Deviations
Nessuna.