---
phase: 04-progetti-multi-project
plan: "03"
type: execute
wave: 2
depends_on:
  - "04-01"
files_modified:
  - src/app/admin/projects/[id]/page.tsx
  - src/app/admin/timer-actions.ts
  - src/components/admin/tabs/TimerTab.tsx
  - src/components/admin/ProfitabilityCard.tsx
  - src/app/admin/impostazioni/page.tsx
autonomous: true
requirements:
  - PROJ-01
  - PROJ-03
  - PROJ-05

must_haves:
  truths:
    - "La pagina /admin/projects/[id] mostra il workspace con tabs Fasi, Pagamenti, Documenti, Commenti, Preventivo, Timer"
    - "Il tab Timer mostra il totale ore lavorate e un bottone play/stop funzionante"
    - "Il tab Timer mostra la ProfitabilityCard con €/h reale, costo ideale, delta guadagno/perdita"
    - "timer-actions.ts usa project_id invece di client_id per startTimer e stopTimer"
    - "La pagina /admin/impostazioni esiste e permette di impostare target_hourly_rate"
  artifacts:
    - path: "src/app/admin/projects/[id]/page.tsx"
      provides: "Workspace progetto con tabs"
      contains: "getProjectFullDetail"
    - path: "src/app/admin/timer-actions.ts"
      provides: "Timer actions con project_id"
      contains: "project_id: projectId"
    - path: "src/components/admin/tabs/TimerTab.tsx"
      provides: "Tab timer con TimerCell + ProfitabilityCard"
      contains: "ProfitabilityCard"
    - path: "src/components/admin/ProfitabilityCard.tsx"
      provides: "Card analytics profittabilità"
      contains: "totalTrackedSeconds / 3600"
    - path: "src/app/admin/impostazioni/page.tsx"
      provides: "Pagina impostazioni admin con target hourly rate"
      contains: "target_hourly_rate"
  key_links:
    - from: "src/app/admin/projects/[id]/page.tsx"
      to: "src/lib/admin-queries.ts"
      via: "getProjectFullDetail(id)"
      pattern: "getProjectFullDetail"
    - from: "src/components/admin/tabs/TimerTab.tsx"
      to: "src/components/admin/ProfitabilityCard.tsx"
      via: "ProfitabilityCard component"
      pattern: "ProfitabilityCard"
    - from: "src/app/admin/impostazioni/page.tsx"
      to: "src/lib/settings.ts"
      via: "updateSetting / getTargetHourlyRate"
      pattern: "getTargetHourlyRate\|updateSetting"
---

<objective>
Admin project workspace (/admin/projects/[id]) e analytics profittabilità. Clona il workspace di /admin/clients/[id] adattandolo al livello progetto, refactora il timer per usare project_id, crea il TimerTab con ProfitabilityCard, e aggiunge /admin/impostazioni per il target_hourly_rate.

Può girare in PARALLELO con 04-02 perché non tocca nessuno degli stessi file.

Purpose: Consegna il workspace completo per progetto (PROJ-01 e PROJ-03) e le analytics profittabilità (PROJ-05). Dopo questo piano l'admin ha un workspace funzionale per ogni progetto incluso il timer e le analytics.

Output: /admin/projects/[id] funzionale, timer migrato a project_id, analytics card, settings page.
</objective>

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

<context>
@/Users/simonecavalli/IAMCAVALLI/.planning/PROJECT.md
@/Users/simonecavalli/IAMCAVALLI/.planning/ROADMAP.md
@/Users/simonecavalli/IAMCAVALLI/CLAUDE.md
@/Users/simonecavalli/IAMCAVALLI/.planning/phases/04-progetti-multi-project/04-01-SUMMARY.md

<interfaces>
<!-- Tipi e funzioni disponibili da 04-01. -->

Da src/lib/admin-queries.ts:
```typescript
export type ProjectFullDetail = {
  project: Project & { client: { id: string; name: string; brand_name: string; slug: string | null } };
  phases: Array<Phase & { tasks: Array<Task & { deliverables: Deliverable[] }> }>;
  payments: Payment[];
  documents: Document[];
  notes: Note[];
  comments: Comment[];
  quoteItems: QuoteItemWithLabel[];
  activeServices: ServiceCatalog[];
  activeTimerEntryId: string | null;
  activeTimerStartedAt: Date | null;
  totalTrackedSeconds: number;
};

export async function getProjectFullDetail(id: string): Promise<ProjectFullDetail | null>;
```

Da src/lib/settings.ts:
```typescript
export const SETTINGS_KEYS: { TARGET_HOURLY_RATE: "target_hourly_rate" };
export async function getSetting(key: string): Promise<string | null>;
export async function updateSetting(key: string, value: string): Promise<void>;
export async function getTargetHourlyRate(): Promise<number>;
```

Template da replicare per workspace (da src/app/admin/clients/[id]/page.tsx — da leggere in read_first):
- Tabs: PhasesTab, PaymentsTab, DocumentsTab, CommentsTab, QuoteTab
- PhasesViewToggle per toggle kanban/list
- ClientActions (ora diventano ProjectActions)

Nota: TimerCell usa il prop `clientId` per la compatibilità con il nome storico — in realtà passiamo il projectId. Il componente TimerCell chiama startTimer(clientId) e stopTimer(entryId) dalle timer-actions.
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Refactoring timer-actions.ts (client_id → project_id) + ProfitabilityCard + TimerTab</name>
  <files>
    src/app/admin/timer-actions.ts
    src/components/admin/ProfitabilityCard.tsx
    src/components/admin/tabs/TimerTab.tsx
  </files>

  <read_first>
    - src/app/admin/timer-actions.ts — leggere interamente: startTimer, stopTimer, le loro dipendenze da client_id nel DB
    - src/components/admin/TimerCell.tsx — leggere le props interface e come chiama startTimer/stopTimer
    - src/components/admin/tabs/QuoteTab.tsx — pattern "use client" + server action + useTransition per TimerTab
    - src/app/admin/clients/[id]/page.tsx — vedere come TimerCell è attualmente passato (per capire dove compare il timer e cosa props riceve)
  </read_first>

  <action>
**A. Aggiornare src/app/admin/timer-actions.ts**

Riscrivere startTimer per usare project_id. Leggere prima l'intero file corrente.

Il cambiamento principale:
1. Parametro `clientId: string` → `projectId: string`
2. `db.insert(time_entries).values({ id, client_id: clientId })` → `db.insert(time_entries).values({ id, project_id: projectId })`
3. La query "stop any running session" rimane GLOBALE (non per progetto) — D-15: solo un timer attivo alla volta
4. Aggiornare `revalidatePath` per includere `/admin/projects`

```typescript
"use server";

import { revalidatePath } from "next/cache";
import { db } from "@/db";
import { time_entries } from "@/db/schema";
import { eq, isNull, and } from "drizzle-orm";
import { nanoid } from "nanoid";

export async function startTimer(projectId: string): Promise<{ entryId: string }> {
  // Stop ALL currently running sessions (global: only one timer active at a time — D-15)
  const running = await db
    .select({ id: time_entries.id, started_at: time_entries.started_at })
    .from(time_entries)
    .where(isNull(time_entries.ended_at));

  for (const r of running) {
    const now = new Date();
    const secs = Math.round((now.getTime() - new Date(r.started_at).getTime()) / 1000);
    await db
      .update(time_entries)
      .set({ ended_at: now, duration_seconds: secs })
      .where(eq(time_entries.id, r.id));
  }

  // Create new entry scoped to PROJECT (not client) — D-19
  const id = nanoid();
  await db.insert(time_entries).values({ id, project_id: projectId });
  revalidatePath("/admin/projects");
  revalidatePath("/admin");
  return { entryId: id };
}

export async function stopTimer(entryId: string): Promise<void> {
  const rows = await db
    .select({ started_at: time_entries.started_at })
    .from(time_entries)
    .where(eq(time_entries.id, entryId))
    .limit(1);

  if (!rows[0]) return;

  const now = new Date();
  const secs = Math.round((now.getTime() - new Date(rows[0].started_at).getTime()) / 1000);
  await db
    .update(time_entries)
    .set({ ended_at: now, duration_seconds: secs })
    .where(eq(time_entries.id, entryId));

  revalidatePath("/admin/projects");
  revalidatePath("/admin");
}
```

**B. Creare src/components/admin/ProfitabilityCard.tsx**

Implementare il componente analytics (D-20). Calcolo:
- ore = totalTrackedSeconds / 3600
- €/h reale = accepted_total ÷ ore (se ore > 0, altrimenti mostrare "—")
- costo ideale = targetHourlyRate × ore
- delta = accepted_total - costo_ideale (positivo = guadagno, negativo = perdita)

```typescript
// src/components/admin/ProfitabilityCard.tsx
// NO "use client" — questo è un componente server-renderable (solo display, no interactivity)

type ProfitabilityCardProps = {
  acceptedTotal: string; // e.g., "1500.00"
  totalTrackedSeconds: number;
  targetHourlyRate: number; // e.g., 50
};

export function ProfitabilityCard({
  acceptedTotal,
  totalTrackedSeconds,
  targetHourlyRate,
}: ProfitabilityCardProps) {
  const hours = totalTrackedSeconds / 3600;
  const accepted = parseFloat(acceptedTotal || "0");
  const realHourlyRate = hours > 0 ? accepted / hours : null;
  const idealCost = targetHourlyRate * hours;
  const delta = accepted - idealCost;
  const deltaIsProfit = delta >= 0;

  return (
    <div className="bg-white rounded-lg border border-[#e5e7eb] p-4 space-y-3">
      <h3 className="font-medium text-[#1a1a1a]">Profittabilità</h3>

      <div className="grid grid-cols-2 gap-3 text-sm">
        <div>
          <p className="text-[#71717a] text-xs">Ore lavorate</p>
          <p className="font-mono font-semibold text-[#1a1a1a]">{hours.toFixed(1)}h</p>
        </div>
        <div>
          <p className="text-[#71717a] text-xs">Importo accettato</p>
          <p className="font-mono font-semibold text-[#1a1a1a]">
            {accepted > 0 ? `€${accepted.toFixed(2)}` : "Non impostato"}
          </p>
        </div>
      </div>

      <div className="border-t border-[#f4f4f5] pt-3 space-y-2 text-sm">
        <div className="flex justify-between">
          <span className="text-[#71717a]">€/h reale</span>
          <span className="font-mono font-semibold text-[#1a1a1a]">
            {realHourlyRate !== null ? `€${realHourlyRate.toFixed(2)}/h` : "—"}
          </span>
        </div>
        <div className="flex justify-between">
          <span className="text-[#71717a]">€/h target</span>
          <span className="font-mono font-semibold text-[#71717a]">€{targetHourlyRate.toFixed(2)}/h</span>
        </div>
        <div className="flex justify-between">
          <span className="text-[#71717a]">Costo ideale ({hours.toFixed(1)}h × €{targetHourlyRate}/h)</span>
          <span className="font-mono font-semibold text-[#1a1a1a]">€{idealCost.toFixed(2)}</span>
        </div>
      </div>

      {hours > 0 && accepted > 0 && (
        <div className="border-t border-[#f4f4f5] pt-3 flex justify-between items-center">
          <span className="text-[#71717a]">Delta (guadagno/perdita)</span>
          <span className={`font-mono font-bold ${deltaIsProfit ? "text-green-600" : "text-red-600"}`}>
            {deltaIsProfit ? "+" : ""}€{delta.toFixed(2)}
          </span>
        </div>
      )}

      {hours === 0 && (
        <p className="text-xs text-[#71717a] border-t border-[#f4f4f5] pt-3">
          Avvia il timer per iniziare a tracciare le ore.
        </p>
      )}
    </div>
  );
}
```

**C. Creare src/components/admin/tabs/TimerTab.tsx**

Il TimerTab mostra il timer (TimerCell) e la ProfitabilityCard. È un Client Component perché TimerCell è "use client".

```typescript
"use client";

import { TimerCell } from "@/components/admin/TimerCell";
import { ProfitabilityCard } from "@/components/admin/ProfitabilityCard";

type TimerTabProps = {
  projectId: string;
  acceptedTotal: string;
  activeTimerEntryId: string | null;
  activeTimerStartedAt: Date | null;
  totalTrackedSeconds: number;
  targetHourlyRate: number;
};

export function TimerTab({
  projectId,
  acceptedTotal,
  activeTimerEntryId,
  activeTimerStartedAt,
  totalTrackedSeconds,
  targetHourlyRate,
}: TimerTabProps) {
  return (
    <div className="space-y-6">
      <div className="bg-white rounded-lg border border-[#e5e7eb] p-4">
        <h3 className="font-medium text-[#1a1a1a] mb-4">Timer</h3>
        <TimerCell
          clientId={projectId}
          activeEntryId={activeTimerEntryId}
          activeStartedAt={activeTimerStartedAt}
          totalTrackedSeconds={totalTrackedSeconds}
        />
      </div>

      <ProfitabilityCard
        acceptedTotal={acceptedTotal}
        totalTrackedSeconds={totalTrackedSeconds}
        targetHourlyRate={targetHourlyRate}
      />
    </div>
  );
}
```

NOTA: TimerCell usa il prop `clientId` ma nel contesto progetto gli passiamo `projectId`. Questo è intentionale per mantenere la compatibilità con TimerCell senza modificarlo. TimerCell chiamerà `startTimer(projectId)` — che ora è il parametro corretto.

Verificare che TimerCell importi da `@/app/admin/timer-actions` e non da un path relativo. Se usa path relativo, assicurarsi che la risoluzione sia corretta.
  </action>

  <verify>
    <automated>npx tsc --noEmit 2>&1 | head -20</automated>
  </verify>

  <acceptance_criteria>
    - src/app/admin/timer-actions.ts contains `project_id: projectId` nella insert (grep: `grep "project_id: projectId" src/app/admin/timer-actions.ts`)
    - src/app/admin/timer-actions.ts does NOT contain `client_id:` nella insert (grep: vecchio pattern rimosso)
    - src/components/admin/ProfitabilityCard.tsx exists e contains `totalTrackedSeconds / 3600` (grep)
    - src/components/admin/tabs/TimerTab.tsx exists e contains `ProfitabilityCard` (grep)
    - src/components/admin/tabs/TimerTab.tsx contains `clientId={projectId}` (passa project id a TimerCell) (grep)
    - TypeScript compila senza errori
  </acceptance_criteria>

  <done>Timer migrato a project_id, ProfitabilityCard e TimerTab creati</done>
</task>

<task type="auto">
  <name>Task 2: /admin/projects/[id] workspace + /admin/impostazioni settings page</name>
  <files>
    src/app/admin/projects/[id]/page.tsx
    src/app/admin/impostazioni/page.tsx
  </files>

  <read_first>
    - src/app/admin/clients/[id]/page.tsx — LEGGERE INTERAMENTE: questo è il template esatto che cloniamo per projects/[id]; capire tutti i component imports, i pattern params, la struttura Tabs
    - src/lib/settings.ts — import getTargetHourlyRate, updateSetting, SETTINGS_KEYS
    - src/components/admin/tabs/TimerTab.tsx — props interface appena creato in Task 1
    - src/app/admin/catalog/page.tsx — pattern per la settings page (form con server action inline)
  </read_first>

  <action>
**A. Creare src/app/admin/projects/[id]/page.tsx**

Clonare src/app/admin/clients/[id]/page.tsx sostituendo:
- `getClientFullDetail(id)` → `getProjectFullDetail(id)` (import da @/lib/admin-queries)
- Le props dei tab components: sostituire clientId con projectId dove necessario
- Aggiungere il tab Timer (nuovo) usando TimerTab
- Header: mostrare nome progetto + "← Progetti" come breadcrumb, sottotitolo = nome cliente

```typescript
import { notFound } from "next/navigation";
import { getProjectFullDetail } from "@/lib/admin-queries";
import { getTargetHourlyRate } from "@/lib/settings";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { PhasesTab } from "@/components/admin/tabs/PhasesTab";
import { PaymentsTab } from "@/components/admin/tabs/PaymentsTab";
import { DocumentsTab } from "@/components/admin/tabs/DocumentsTab";
import { CommentsTab } from "@/components/admin/tabs/CommentsTab";
import { QuoteTab } from "@/components/admin/tabs/QuoteTab";
import { NotesTab } from "@/components/admin/tabs/NotesTab"; // se esiste
import { TimerTab } from "@/components/admin/tabs/TimerTab";
import { PhasesViewToggle } from "@/components/admin/kanban/PhasesViewToggle";
import Link from "next/link";

export const revalidate = 0;

export default async function ProjectDetailPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const [detail, targetHourlyRate] = await Promise.all([
    getProjectFullDetail(id),
    getTargetHourlyRate(),
  ]);

  if (!detail) notFound();

  const {
    project,
    phases,
    payments,
    documents,
    notes,
    comments,
    quoteItems,
    activeServices,
    activeTimerEntryId,
    activeTimerStartedAt,
    totalTrackedSeconds,
  } = detail;

  return (
    <div>
      <div className="mb-4">
        <Link href="/admin/projects" className="text-sm text-[#71717a] hover:text-[#1a1a1a]">
          ← Progetti
        </Link>
      </div>

      <div className="mb-6 flex items-start justify-between gap-4 flex-wrap">
        <div>
          <h1 className="text-2xl font-bold text-[#1a1a1a]">{project.name}</h1>
          <p className="text-sm text-[#71717a]">
            <Link href={`/admin/clients/${project.client.id}`} className="hover:text-[#1a1a1a] hover:underline">
              {project.client.name}
            </Link>
          </p>
        </div>
      </div>

      <Tabs defaultValue="phases" className="w-full">
        <TabsList className="mb-6">
          <TabsTrigger value="phases">Fasi &amp; Task</TabsTrigger>
          <TabsTrigger value="payments">Pagamenti</TabsTrigger>
          <TabsTrigger value="documents">Documenti</TabsTrigger>
          <TabsTrigger value="notes">Note</TabsTrigger>
          <TabsTrigger value="comments">Commenti</TabsTrigger>
          <TabsTrigger value="quote">Preventivo</TabsTrigger>
          <TabsTrigger value="timer">Timer</TabsTrigger>
        </TabsList>

        <TabsContent value="phases">
          <PhasesViewToggle
            listView={<PhasesTab phases={phases} clientId={id} />}
            phases={phases}
            clientId={id}
          />
        </TabsContent>

        <TabsContent value="payments">
          <PaymentsTab payments={payments} clientId={id} />
        </TabsContent>

        <TabsContent value="documents">
          <DocumentsTab documents={documents} clientId={id} />
        </TabsContent>

        {/* Render NotesTab solo se il component esiste — altrimenti inline */}
        <TabsContent value="notes">
          <div className="space-y-4">
            {notes.length === 0 && (
              <p className="text-sm text-[#71717a]">Nessuna nota ancora.</p>
            )}
            {notes.map((note) => (
              <div key={note.id} className="bg-white rounded-lg border border-[#e5e7eb] p-4">
                <p className="text-sm text-[#1a1a1a] whitespace-pre-wrap">{note.body}</p>
                <p className="text-xs text-[#71717a] mt-2">
                  {new Date(note.created_at).toLocaleDateString("it-IT")}
                </p>
              </div>
            ))}
          </div>
        </TabsContent>

        <TabsContent value="comments">
          <CommentsTab comments={comments} clientId={id} />
        </TabsContent>

        <TabsContent value="quote">
          <QuoteTab
            quoteItems={quoteItems}
            activeServices={activeServices}
            clientId={id}
            acceptedTotal={project.accepted_total ?? "0"}
          />
        </TabsContent>

        <TabsContent value="timer">
          <TimerTab
            projectId={id}
            acceptedTotal={project.accepted_total ?? "0"}
            activeTimerEntryId={activeTimerEntryId}
            activeTimerStartedAt={activeTimerStartedAt}
            totalTrackedSeconds={totalTrackedSeconds}
            targetHourlyRate={targetHourlyRate}
          />
        </TabsContent>
      </Tabs>
    </div>
  );
}
```

NOTA CRITICA: I tab components (PhasesTab, PaymentsTab, DocumentsTab, CommentsTab, QuoteTab) potrebbero avere prop `clientId` che originariamente si riferivano al client.id. In questo contesto, passiamo il project.id come `clientId` — i tab usano quel valore per le loro server actions (addPhase, addPayment, ecc.). Le server actions di fase/pagamento/documento potrebbero ancora cercare client_id nel DB. VERIFICARE leggendo ogni actions file:

- Se le actions usano ancora `client_id` nel DB, bisogna aggiornare le actions dei tab per usare `project_id`. Questo è parte dello stesso task.
- Leggere src/app/admin/clients/[id]/phase-actions.ts (o simile) e src/app/admin/clients/[id]/payment-actions.ts per capire se fanno insert con client_id.
- Aggiornare TUTTI i file di actions che fanno insert/update con client_id su tabelle che ora usano project_id.

Specificamente, cercare tutti i file di actions:
```bash
grep -r "client_id" src/app/admin/clients/[id]/ --include="*actions*"
```
Per ogni occorrenza che fa insert su phases, payments, documents, notes, quote_items: cambiare il campo da client_id a project_id e aggiornare i revalidatePath da /admin/clients/[id] a /admin/projects/[id].

**B. Creare src/app/admin/impostazioni/page.tsx**

```typescript
import { getTargetHourlyRate, updateSetting, SETTINGS_KEYS } from "@/lib/settings";
import { revalidatePath } from "next/cache";

export const revalidate = 0;

export default async function ImpostazioniPage() {
  const targetRate = await getTargetHourlyRate();

  async function handleSave(fd: FormData) {
    "use server";
    const newRate = fd.get("target_hourly_rate");
    if (!newRate || isNaN(parseFloat(String(newRate)))) return;
    await updateSetting(SETTINGS_KEYS.TARGET_HOURLY_RATE, String(parseFloat(String(newRate)).toFixed(2)));
    revalidatePath("/admin/impostazioni");
  }

  return (
    <div>
      <h1 className="text-2xl font-bold text-[#1a1a1a] mb-6">Impostazioni</h1>

      <div className="bg-white rounded-xl border border-[#e5e7eb] p-6 max-w-md">
        <h2 className="text-base font-semibold text-[#1a1a1a] mb-4">Analytics Profittabilità</h2>

        <form action={handleSave} className="space-y-4">
          <div>
            <label
              htmlFor="target_hourly_rate"
              className="block text-sm font-medium text-[#1a1a1a] mb-1"
            >
              Tariffa oraria target (€/h)
            </label>
            <p className="text-xs text-[#71717a] mb-2">
              Usata per calcolare il costo ideale e il delta profitto/perdita per ogni progetto.
            </p>
            <div className="flex items-center gap-2">
              <span className="text-sm text-[#71717a]">€</span>
              <input
                id="target_hourly_rate"
                name="target_hourly_rate"
                type="number"
                step="0.01"
                min="0"
                defaultValue={targetRate.toFixed(2)}
                className="border border-[#e5e7eb] rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-[#1A463C]/20 w-32"
              />
              <span className="text-sm text-[#71717a]">/h</span>
            </div>
          </div>

          <button
            type="submit"
            className="bg-[#1A463C] text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-[#1A463C]/90 transition-colors"
          >
            Salva
          </button>
        </form>
      </div>
    </div>
  );
}
```
  </action>

  <verify>
    <automated>npm run build 2>&1 | tail -30</automated>
  </verify>

  <acceptance_criteria>
    - src/app/admin/projects/[id]/page.tsx exists e contains `getProjectFullDetail` (grep)
    - src/app/admin/projects/[id]/page.tsx contains `TimerTab` import e usage (grep)
    - src/app/admin/projects/[id]/page.tsx contains `getTargetHourlyRate` (grep)
    - src/app/admin/impostazioni/page.tsx exists e contains `SETTINGS_KEYS.TARGET_HOURLY_RATE` (grep)
    - src/app/admin/impostazioni/page.tsx contains `updateSetting` (grep)
    - Tutte le actions di fase/pagamento/documento/note/quote che facevano insert con client_id sono state aggiornate a project_id (grep: `grep -r "client_id" src/app/admin/clients/\[id\]/ --include="*actions*"` non deve avere insert su tabelle migrate)
    - `npm run build` completa senza errori TypeScript
    - Navigando /admin/projects/[id] (con un progetto esistente) la pagina carica senza 500 errors
    - Il tab Timer mostra TimerCell e ProfitabilityCard renderizzati
  </acceptance_criteria>

  <done>/admin/projects/[id] workspace completo con timer e analytics; /admin/impostazioni funzionale</done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| Admin session → timer-actions | startTimer e stopTimer non hanno requireAdmin perché chiamati da TimerCell lato client; il guard è il middleware Auth.js su /admin/* che blocca accesso non autenticato |
| Admin session → impostazioni | handleSave inline server action in pagina /admin/impostazioni — il guard Auth.js su /admin/* blocca utenti non autenticati |
| project workspace → quote_items | QuoteTab viene passato quoteItems da getProjectFullDetail — non accessibile via client API (D-02 / CLAUDE.md constraint) |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-04-09 | Information Disclosure | getProjectFullDetail — quoteItems | mitigate | quoteItems inclusi solo nella risposta admin (questo workspace); la funzione client-view (Wave 3, 04-04) non deve includere quote_items — invariante CLAUDE.md |
| T-04-10 | Tampering | timer-actions.ts — startTimer | accept | Auth.js middleware su /admin/* impedisce accesso anonimo; timer actions non espongono dati sensibili, solo time tracking |
| T-04-11 | Information Disclosure | ProfitabilityCard — accepted_total visibile | accept | accepted_total è il totale accettato dal cliente (non il dettaglio dei singoli servizi) — corretto mostrarlo all'admin nel workspace progetto |
| T-04-12 | Tampering | updateSetting — target_hourly_rate | accept | Setting è solo un numero (tariffa oraria); nessun rischio sicurezza; Auth.js middleware blocca accesso non autenticato a /admin/impostazioni |
| T-04-13 | Tampering | phase-actions / payment-actions migrazione project_id | mitigate | Dopo aggiornamento actions: insert usa project_id con FK constraint → DB rifiuta project_id non validi con constraint violation |
</threat_model>

<verification>
```bash
# 1. Timer uses project_id
grep "project_id: projectId" src/app/admin/timer-actions.ts

# 2. No client_id insert in timer
grep -v "project_id" src/app/admin/timer-actions.ts | grep "client_id"

# 3. Analytics card exists
grep "totalTrackedSeconds / 3600" src/components/admin/ProfitabilityCard.tsx

# 4. TimerTab imports ProfitabilityCard
grep "ProfitabilityCard" src/components/admin/tabs/TimerTab.tsx

# 5. Project workspace uses new query
grep "getProjectFullDetail" src/app/admin/projects/\[id\]/page.tsx

# 6. Settings key constant used
grep "SETTINGS_KEYS" src/app/admin/impostazioni/page.tsx

# 7. Build
npm run build
```
</verification>

<success_criteria>
- /admin/projects/[id] carica senza errori e mostra tutti i tab (Fasi, Pagamenti, Documenti, Note, Commenti, Preventivo, Timer)
- Il tab Timer mostra TimerCell (play/stop) e ProfitabilityCard (con ore, €/h reale, costo ideale, delta)
- /admin/impostazioni carica e mostra il form con il valore corrente della tariffa (default 50.00 se non impostata)
- Salvando un nuovo valore in /admin/impostazioni il valore viene persistito e la pagina mostra il nuovo valore
- `npm run build` passa senza errori
</success_criteria>

<output>
After completion, create `.planning/phases/04-progetti-multi-project/04-03-SUMMARY.md` following the template at `@/Users/simonecavalli/.claude/get-shit-done/templates/summary.md`.

Key items to document:
- Quali actions file sono stati aggiornati da client_id a project_id (lista esaustiva)
- Come TimerCell è stato adattato per usare project_id (prop naming)
- Se NotesTab esiste come component o se le note sono state implementate inline
- Valore default inizializzato per target_hourly_rate
</output>