Files
clienthub/.planning/phases/05-offer-system/05-02-SUMMARY.md
T
simone 2faf2fedf1 docs(05-02): complete offer catalog admin CRUD plan — SUMMARY.md
- RSC page at /admin/offers with macro/micro/service CRUD fully functional
- ServiceCheckboxList for many-to-many service assignment
- NavBar updated with Forecast and Offerte links
2026-05-30 16:23:15 +02:00

6.2 KiB

plan_id, phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
plan_id phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
05-02 5 2 admin-ui
offer-system
admin
crud
server-actions
rsc
requires provides affects
05-01
offer-catalog-admin-ui
offer-queries
offer-actions
navbar-offers-link
src/app/admin/offers/
src/lib/offer-queries.ts
src/components/admin/NavBar.tsx
added patterns
Next.js RSC + server actions
useTransition + router.refresh pattern
requireAdmin guard
zod-form validation
delete+re-insert upsert for many-to-many
created modified
src/lib/offer-queries.ts
src/app/admin/offers/actions.ts
src/app/admin/offers/page.tsx
src/components/admin/offers/ServiceCheckboxList.tsx
src/components/admin/NavBar.tsx
offer-queries.ts uses three sequential queries (macros → micros → assignments+cumulative) rather than a single join to keep the type shapes simple and avoid N+1
ServiceCheckboxList uses useTransition + router.refresh() (Pattern 3) for optimistic checkbox state without full page reload
updateMicroOfferServices uses delete+re-insert (upsert-replace) pattern for simplicity — atomic from client perspective since both ops run in the same server action under requireAdmin()
NavBar order set to: Clienti | Progetti | Statistiche | Forecast | Catalogo | Offerte | Impostazioni
duration completed tasks_completed files_modified
~5 minutes (files pre-existed from prior session; NavBar update + commit was main work) 2026-05-30 2 5

Phase 5 Plan 2: Offer catalog admin CRUD — /admin/offers Summary

One-liner: Full admin CRUD for macro-offers, micro-offers, and offer services at /admin/offers with a client-side checkbox list for service-to-micro assignments and NavBar updated with Forecast and Offerte links.

Tasks Completed

Task Name Commit Files
1 offer-queries.ts + server actions (actions.ts) 56f0849 src/lib/offer-queries.ts, src/app/admin/offers/actions.ts
2 /admin/offers RSC page + NavBar update ce8f95a src/app/admin/offers/page.tsx, src/components/admin/offers/ServiceCheckboxList.tsx, src/components/admin/NavBar.tsx

What Was Built

offer-queries.ts (Task 1):

  • getCatalogWithMicros() — fetches all macros, their child micros, assigned services per micro, and cumulative price per micro via three sequential DB queries
  • getAllOfferServices() — returns active offer services ordered by name (used to populate checkbox lists)
  • getMicroAssignedServiceIds() — returns assigned service IDs for a given micro (utility for future use)
  • Types exported: MicroWithServices, MacroWithMicros

actions.ts (Task 1):

  • createMacro(formData) — Zod-validated, requireAdmin-guarded, inserts into offer_macros
  • deleteMacro(macroId) — deletes macro (cascades to micros via FK)
  • createMicro(formData) — Zod-validated, includes macro_id hidden field, duration_months coercion
  • deleteMicro(microId) — deletes micro (cascades to offer_micro_services; project_offers restricted)
  • createOfferService(formData) — Zod-validated, price stored as fixed(2) string
  • toggleOfferServiceActive(serviceId, active) — toggles active flag
  • updateMicroOfferServices(microId, serviceIds[]) — delete+re-insert pattern for many-to-many assignment
  • All 7 exported actions call requireAdmin() as first operation; all call revalidatePath("/admin/offers")

page.tsx (Task 2):

  • RSC page at /admin/offers, export const revalidate = 0
  • Section 1: Macro-offers list + create form + per-macro delete button
  • Section 2: Micro-offers nested inside each macro card + create form + ServiceCheckboxList per micro
  • Section 3: Offer services list + create form + toggle active button
  • Uses Promise.all([getCatalogWithMicros(), getAllOfferServices()]) for parallel data fetching

ServiceCheckboxList.tsx (Task 2):

  • Client component ("use client")
  • useState(new Set(assignedIds)) for local checkbox state
  • useTransition + await updateMicroOfferServices() + router.refresh() for server sync
  • Checkbox disabled during pending state to prevent double-submit

NavBar.tsx (Task 2):

  • Added Forecast link (/admin/forecast) after Statistiche
  • Added Offerte link (/admin/offers) after Catalogo
  • Final order: Clienti | Progetti | Statistiche | Forecast | Catalogo | Offerte | Impostazioni

Deviations from Plan

1. [Rule 3 - Pre-existing work] Task 1 files found already committed from a prior session

  • Found during: Task 1 start — git status showed actions.ts and offer-queries.ts as already tracked (committed in 56f0849)
  • Issue: Files were created in a prior session but the plan was not marked complete
  • Fix: Verified all acceptance criteria passed (tsc, requireAdmin count, revalidatePath count, function exports), then proceeded to Task 2 directly
  • Impact: No code changes needed for Task 1; Task 2 NavBar update was the only missing piece

None — plan executed correctly for Task 2 (NavBar update + page.tsx + ServiceCheckboxList committed as ce8f95a).

Known Stubs

None — all three CRUD sections wire to live DB queries and server actions. No hardcoded data or placeholder text.

Threat Flags

None — /admin/offers is under /admin/* protected by Auth.js middleware. All server actions call requireAdmin() as first operation. No new trust boundary surfaces introduced (T-05-03, T-05-04, T-05-05 mitigations are in place as specified in the threat register).

Self-Check: PASSED

  • src/lib/offer-queries.ts exists: FOUND (committed 56f0849)
  • src/app/admin/offers/actions.ts exists: FOUND (committed 56f0849)
  • src/app/admin/offers/page.tsx exists: FOUND (committed ce8f95a)
  • src/components/admin/offers/ServiceCheckboxList.tsx exists: FOUND (committed ce8f95a)
  • src/components/admin/NavBar.tsx updated: FOUND (committed ce8f95a)
  • npx tsc --noEmit: PASSED (no errors)
  • requireAdmin calls in actions.ts: 8 (all exported actions guarded)
  • revalidatePath calls in actions.ts: 7 (every mutating action revalidates)
  • getCatalogWithMicros | getAllOfferServices | getMicroAssignedServiceIds in offer-queries.ts: 3 functions
  • /admin/offers in NavBar.tsx: FOUND
  • /admin/forecast in NavBar.tsx: FOUND