Files
clienthub/.planning/milestones/v1.0-phases/04-progetti-multi-project/04-03-SUMMARY.md
T
simone 23cb057f0b docs(07): phase 7 planning complete — 2 plans (schema migration + admin crud)
Wave 1: 07-01-PLAN.md — Unified services table (audit trail, backfill, validation)
Wave 2: 07-02-PLAN.md — Admin catalog CRUD rewire (preserve historical references)

Ready for execution: /gsd-execute-phase 7

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-11 06:17:29 +02:00

52 lines
3.0 KiB
Markdown

---
plan: "04-03"
phase: "04-progetti-multi-project"
status: complete
completed_at: "2026-05-22"
---
# Summary: 04-03 — Project Workspace + Timer Analytics + Settings
## What Was Built
**`/admin/projects/[id]`** — full project workspace with 7 tabs: Fasi & Task, Pagamenti, Documenti, Note, Commenti, Preventivo, Timer. Reuses all existing tab components (PhasesTab, PaymentsTab, DocumentsTab, CommentsTab, QuoteTab) unchanged by passing `projectId` as `clientId`.
**`ProfitabilityCard`** (`src/components/admin/ProfitabilityCard.tsx`) — pure display component (no `"use client"`). Shows: ore lavorate, importo accettato, €/h reale, €/h target, costo ideale, delta (verde = guadagno, rosso = perdita). Delta shown only when both hours > 0 and accepted > 0.
**`TimerTab`** (`src/components/admin/tabs/TimerTab.tsx`) — client component wrapping TimerCell + ProfitabilityCard. Passes `projectId` as `clientId` to TimerCell (prop name is legacy; the underlying startTimer already uses project_id).
**`/admin/impostazioni`** — settings page with target_hourly_rate form. Inline server action calls `updateSetting(SETTINGS_KEYS.TARGET_HOURLY_RATE, ...)`. Default value 50€/h from `getTargetHourlyRate()`.
## Key Architectural Decision: resolveEntity()
The tab components (PhasesTab, PaymentsTab, etc.) hardcode imports from `@/app/admin/clients/[id]/actions`. Rather than duplicating tab components or injecting actions as props, added `resolveEntity(id)` helper to both `actions.ts` and `quote-actions.ts`:
```typescript
async function resolveEntity(id: string): Promise<{ projectId: string | null; path: string }> {
// Checks if id is a projects.id directly (one PK lookup)
// Falls back to client_id lookup if not found
// Returns correct revalidatePath for either context
}
```
This makes all existing tab actions work transparently with both clientId and projectId. Zero changes to tab components.
**`updateAcceptedTotal` in `actions.ts`** — special case: detects project vs client context and updates the correct table (`projects.accepted_total` vs `clients.accepted_total`), with per-project payment stub splitting.
**`timer-actions.ts`** — added `revalidatePath("/admin/projects")` and `revalidatePath(\`/admin/projects/${projectId}\`)` so the project list and workspace refresh after timer start/stop.
## Files Changed
| File | Action |
|------|--------|
| `src/app/admin/clients/[id]/actions.ts` | Updated — added resolveEntity(), all functions now work with projectId |
| `src/app/admin/clients/[id]/quote-actions.ts` | Updated — same resolveEntity pattern |
| `src/app/admin/timer-actions.ts` | Updated — added /admin/projects revalidation |
| `src/components/admin/ProfitabilityCard.tsx` | Created |
| `src/components/admin/tabs/TimerTab.tsx` | Created |
| `src/app/admin/projects/[id]/page.tsx` | Created |
| `src/app/admin/impostazioni/page.tsx` | Created |
## Notes Tab
No `NotesTab` component exists — notes rendered inline in the project workspace page. Simple read-only display (no add/edit/delete — consistent with how notes appear in the client dashboard).