Files
simone b3670d164b docs(phase-18): create phase plan — cleanup & consolidamento
3 piani in 2 wave: 18-01 (rimozioni Forecast+QuoteBuilder),
18-02 (fusione analytics→dashboard), 18-03 (CLEAN-04 verify + build check).
ROADMAP.md aggiornato con plan list Phase 18.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 11:51:03 +02:00

13 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
18-cleanup-consolidamento 02 execute 1
src/app/admin/page.tsx
src/app/admin/analytics/page.tsx
src/components/admin/YearSelector.tsx
true
CLEAN-03
truths artifacts key_links
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
path provides contains
src/app/admin/page.tsx Dashboard con sezione Statistiche integrata getAnalyticsByYear
path provides
src/app/admin/analytics/page.tsx File eliminato
path provides contains
src/components/admin/YearSelector.tsx YearSelector che naviga a /admin?year=Y router.push(`/admin?year=${y}`)
from to via pattern
src/app/admin/page.tsx lib/analytics-queries import getAnalyticsByYear, getMonthlyCollected, getAvailableYears, getTimeByClient, getTotalTrackedHours analytics-queries
from to via pattern
YearSelector /admin router.push router.push.*admin
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.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md

Imports da copiare in admin/page.tsx:

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:

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:

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):

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.

Task 1: Integra statistiche annuali nella Dashboard (CLEAN-03)

<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>

src/app/admin/page.tsx, src/components/admin/YearSelector.tsx 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).
grep "getAnalyticsByYear\|getMonthlyCollected\|getAvailableYears" /Users/simonecavalli/Vault/IAMCAVALLI/src/app/admin/page.tsx | wc -l 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".

<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>

La Dashboard mostra le statistiche annuali con selettore anno; il selettore naviga correttamente a /admin?year=X. Task 2: Elimina rotta /admin/analytics (CLEAN-03)

<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>

src/app/admin/analytics/page.tsx 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).
test -f /Users/simonecavalli/Vault/IAMCAVALLI/src/app/admin/analytics/page.tsx && echo "ESISTE_ANCORA" || echo "OK_RIMOSSO" 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.

<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>

La rotta /admin/analytics è eliminata. Navigarci restituisce 404. Tutte le statistiche sono accessibili alla Dashboard /admin.

<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>
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

<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>
After completion, create `.planning/phases/18-cleanup-consolidamento/18-02-SUMMARY.md`