- 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
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 |
|
|
|
|
|
|
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 queriesgetAllOfferServices()— 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_macrosdeleteMacro(macroId)— deletes macro (cascades to micros via FK)createMicro(formData)— Zod-validated, includes macro_id hidden field, duration_months coerciondeleteMicro(microId)— deletes micro (cascades to offer_micro_services; project_offers restricted)createOfferService(formData)— Zod-validated, price stored as fixed(2) stringtoggleOfferServiceActive(serviceId, active)— toggles active flagupdateMicroOfferServices(microId, serviceIds[])— delete+re-insert pattern for many-to-many assignment- All 7 exported actions call
requireAdmin()as first operation; all callrevalidatePath("/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 stateuseTransition+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 statusshowed actions.ts and offer-queries.ts as already tracked (committed in56f0849) - 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