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
This commit is contained in:
@@ -0,0 +1,111 @@
|
|||||||
|
---
|
||||||
|
plan_id: 05-02
|
||||||
|
phase: 5
|
||||||
|
plan: 2
|
||||||
|
subsystem: admin-ui
|
||||||
|
tags: [offer-system, admin, crud, server-actions, rsc]
|
||||||
|
dependency_graph:
|
||||||
|
requires: [05-01]
|
||||||
|
provides: [offer-catalog-admin-ui, offer-queries, offer-actions, navbar-offers-link]
|
||||||
|
affects: [src/app/admin/offers/, src/lib/offer-queries.ts, src/components/admin/NavBar.tsx]
|
||||||
|
tech_stack:
|
||||||
|
added: []
|
||||||
|
patterns: [Next.js RSC + server actions, useTransition + router.refresh pattern, requireAdmin guard, zod-form validation, delete+re-insert upsert for many-to-many]
|
||||||
|
key_files:
|
||||||
|
created:
|
||||||
|
- src/lib/offer-queries.ts
|
||||||
|
- src/app/admin/offers/actions.ts
|
||||||
|
- src/app/admin/offers/page.tsx
|
||||||
|
- src/components/admin/offers/ServiceCheckboxList.tsx
|
||||||
|
modified:
|
||||||
|
- src/components/admin/NavBar.tsx
|
||||||
|
decisions:
|
||||||
|
- "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"
|
||||||
|
metrics:
|
||||||
|
duration: "~5 minutes (files pre-existed from prior session; NavBar update + commit was main work)"
|
||||||
|
completed: "2026-05-30"
|
||||||
|
tasks_completed: 2
|
||||||
|
files_modified: 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
|
||||||
Reference in New Issue
Block a user