b7203c3da2
- Add 12-05-SUMMARY.md documenting OfferEditorClient + edit route
121 lines
7.1 KiB
Markdown
121 lines
7.1 KiB
Markdown
---
|
|
phase: 12-offer-composition-drag-drop-csv-import
|
|
plan: 05
|
|
subsystem: ui
|
|
tags: [next.js, react, tailwind, server-actions, drizzle]
|
|
|
|
# Dependency graph
|
|
requires:
|
|
- phase: 12-offer-composition-drag-drop-csv-import (Plan 03)
|
|
provides: getOfferEditorData/getOfferFieldOptions query layer + saveOfferEditor/toggleOfferArchived/renameOfferOption/addOfferTag/removeOfferTag server actions
|
|
provides:
|
|
- "/admin/offers/[id]/edit route: full offer editor page (server component + client component)"
|
|
- OfferEditorClient — editable name, Categoria/Ticket/Tipo/Obiettivo tags, services x tier (A/B/C) checkbox matrix with live totals, independent Prezzo Pubblico, Promessa di Trasformazione block, Salva/Annulla/Archivia actions
|
|
affects: [12-04-offer-list-page]
|
|
|
|
# Tech tracking
|
|
tech-stack:
|
|
added: []
|
|
patterns:
|
|
- "Tier padding to exactly 3 (A/B/C) via padTiers() so the matrix always renders 3 columns even for offers with <3 persisted tiers"
|
|
- "Client-side useMemo for category-filtered services + live per-tier Totale Servizi (OFFER-11), zero network calls on checkbox toggle"
|
|
- "Batched save: all macro/tier/tag edits held in local state, persisted in one saveOfferEditor() call on 'Salva Offerta' (no per-keystroke server calls for tags)"
|
|
|
|
key-files:
|
|
created:
|
|
- src/app/admin/offers/[id]/edit/page.tsx
|
|
- src/components/admin/offers/OfferEditorClient.tsx
|
|
modified: []
|
|
|
|
key-decisions:
|
|
- "EditableCell.onSave is synchronous (value: string) => void in this codebase (not Promise-returning as the plan's interface sketch suggested) — all macro-field onSave handlers call setMacro synchronously, consistent with the actual component signature"
|
|
- "renameOfferOption calls (Categoria/Ticket/Tipo/Obiettivo rename) are fired via startTransition with optimistic local-state updates to options/tags, matching ServiceTable's existing rename pattern from Phase 11"
|
|
|
|
patterns-established: []
|
|
|
|
requirements-completed: [OFFER-11, OFFER-15, OFFER-16, OFFER-17]
|
|
|
|
# Metrics
|
|
duration: 18min
|
|
completed: 2026-06-15
|
|
---
|
|
|
|
# Phase 12 Plan 05: Offer Editor Detail Page Summary
|
|
|
|
**Full-page Offer Editor at `/admin/offers/[id]/edit`: 4-dimension tags, category-filtered A/B/C services checkbox matrix with live "Totale Servizi", independent "Prezzo Pubblico" per tier, 5-field transformation promise, and Salva/Annulla/Archivia actions.**
|
|
|
|
## Performance
|
|
|
|
- **Duration:** 18 min
|
|
- **Started:** 2026-06-15T08:07:00Z (approx)
|
|
- **Completed:** 2026-06-15T08:25:22Z
|
|
- **Tasks:** 2 completed
|
|
- **Files modified:** 2 (both new)
|
|
|
|
## Accomplishments
|
|
|
|
- Created `src/app/admin/offers/[id]/edit/page.tsx`: async server component using Next.js 16's `Promise<{ id: string }>` params pattern, fetches `getOfferEditorData(id)` + `getOfferFieldOptions()` via `Promise.all`, calls `notFound()` for unknown ids, delegates to `OfferEditorClient`
|
|
- Created `src/components/admin/offers/OfferEditorClient.tsx`: client component covering the full UI-SPEC section 2 contract:
|
|
- Header with "← Indietro" link, "Modifica Offerta" heading, editable offer name via `EditableCell`
|
|
- "Tag" section: Categoria/Ticket via `OptionSelect` (single-select, with rename), Tipo/Obiettivo via `OptionMultiSelect` (multi-select, creatable on the fly via "+ Crea Tipo"/"+ Crea Obiettivo")
|
|
- "Servizi Inclusi" matrix: services filtered by `macro.category` (live-recomputed via `useMemo` when Categoria changes), one checkbox column per tier (A/B/C), "Totale Servizi" row recalculated instantly client-side on every toggle (OFFER-11), independent "Prezzo Pubblico" numeric input per tier (OFFER-16, D-5)
|
|
- "Promessa di Trasformazione" block: 5 `EditableCell` fields (Aiuto/A ottenere/In/Senza/Grazie a) mapped to `cliente_ideale`/`risultato`/`tempo`/`pain`/`metodo` (OFFER-17)
|
|
- "Salva Offerta" (disabled with tooltip unless at least one tier has an assigned service) → `saveOfferEditor` → redirect to `/admin/offers`; "Annulla" → plain link back; "Archivia" (visible only when not already archived) → confirm dialog → `toggleOfferArchived(id, true)` → redirect
|
|
- All literal Italian copy strings from the UI-SPEC copywriting contract table implemented verbatim
|
|
|
|
## Task Commits
|
|
|
|
Each task was committed atomically:
|
|
|
|
1. **Task 1: Create editor route + page.tsx server component** - `8e0e4b9` (feat)
|
|
2. **Task 2: Build OfferEditorClient — tags, services matrix with live totals, promise block, actions** - `8c5c918` (feat)
|
|
|
|
**Plan metadata:** (this commit, follows)
|
|
|
|
## Files Created/Modified
|
|
|
|
- `src/app/admin/offers/[id]/edit/page.tsx` - New server component: fetches editor data + field options, 404s on missing offer, renders `OfferEditorClient`
|
|
- `src/components/admin/offers/OfferEditorClient.tsx` - New client component: the entire offer editor UI and its Salva/Annulla/Archivia/rename interactions
|
|
|
|
## Decisions Made
|
|
|
|
- `EditableCell.onSave` in this codebase is `(value: string) => void` (synchronous), not `(value: string) => void | Promise<void>` as the plan's interface sketch suggested — implemented all macro-field handlers as synchronous `setMacro` updates, matching the real signature (verified via `npx tsc --noEmit`)
|
|
- Tier padding: `padTiers()` always produces exactly 3 entries (A, B, C), filling missing tiers with empty placeholders (`id: ""`, `assignedServiceIds: []`, `servicesTotal: "0"`) so the matrix always renders 3 columns regardless of how many tiers exist in the DB
|
|
- Tag rename (Categoria/Ticket/Tipo/Obiettivo) follows the existing `ServiceTable`/`renameServiceOption` optimistic-update pattern: local state updates immediately, `renameOfferOption` server action fires via `startTransition`
|
|
|
|
## Deviations from Plan
|
|
|
|
None - plan executed exactly as written. The only adjustment was using the actual (synchronous) `EditableCell.onSave` signature instead of the plan's sketch signature, which is a same-behavior implementation detail (Rule 1 — code correctness), not a scope change.
|
|
|
|
## Issues Encountered
|
|
|
|
None.
|
|
|
|
## User Setup Required
|
|
|
|
None - no external service configuration required. This plan is pure UI (server + client components) consuming the already-deployed Plan 03 query layer and server actions against the live production schema (migration 0008).
|
|
|
|
## Next Phase Readiness
|
|
|
|
- `/admin/offers/[id]/edit` is fully functional and type-checks/lints clean across the whole project (`npx tsc --noEmit` and `npx eslint` both pass with zero errors)
|
|
- Plan 04's list page (`/admin/offers` + `OfferListClient.tsx`, built in parallel in a separate worktree) can link directly to `/admin/offers/[id]/edit` — no shared files were touched, no merge conflicts expected
|
|
- All four target requirements (OFFER-11, OFFER-15, OFFER-16, OFFER-17) are implemented end-to-end: UI -> `saveOfferEditor`/`toggleOfferArchived`/`renameOfferOption` (Plan 03) -> `offer_macros`/`offer_micros`/`offer_tier_services`/`tags` (Plan 01/02, live on prod)
|
|
|
|
---
|
|
|
|
*Phase: 12-offer-composition-drag-drop-csv-import*
|
|
*Completed: 2026-06-15*
|
|
|
|
## Self-Check: PASSED
|
|
|
|
All created files verified present on disk:
|
|
|
|
- FOUND: src/app/admin/offers/[id]/edit/page.tsx
|
|
- FOUND: src/components/admin/offers/OfferEditorClient.tsx
|
|
- FOUND: .planning/phases/12-offer-composition-drag-drop-csv-import/12-05-SUMMARY.md
|
|
|
|
All task commits verified present in git log:
|
|
|
|
- FOUND: 8e0e4b9 (Task 1)
|
|
- FOUND: 8c5c918 (Task 2)
|