--- phase: 11-catalog-database-view-ux-legacy-consolidation plan: 04 subsystem: ui tags: [react, nextjs, tailwind, server-actions, catalog, inline-edit, tags, search] # Dependency graph requires: - phase: 11-catalog-database-view-ux-legacy-consolidation plan: "11-02" provides: "ServiceWithTags type, getAllServices(), updateServiceField/quickAddService/addTagToService/removeTagFromService server actions" - phase: 11-catalog-database-view-ux-legacy-consolidation plan: "11-03" provides: "EditableCell (text/number/textarea/toggle) and TagMultiSelect shared UI primitives" provides: - "Database-view /admin/catalog: 6-column inline-editable ServiceTable (Nome, Descrizione, Categoria, Prezzo, Tag, Stato), no Azioni column" - "QuickAddRow — creates services via quickAddService(name) on Enter, unit_price=0 (D-12)" - "Active/inactive split with 'Servizi disattivati' divider + opacity-50 (D-13)" - "CatalogSearch client component — instant client-side name/tag filter, no reload (OFFER-10)" affects: ["12"] # Tech tracking tech-stack: added: [] patterns: - "Co-located client filter wrapper (CatalogSearch) between an async Server Component page and a presentational table — useMemo filter over already-fetched data, zero new fetches on query change" - "ServiceRow per-row error display via a conditional colSpan=6 trailing , keeping row height stable in the success path" key-files: created: - src/app/admin/catalog/CatalogSearch.tsx modified: - src/components/admin/catalog/ServiceTable.tsx - src/app/admin/catalog/page.tsx deleted: - src/components/admin/catalog/ServiceForm.tsx key-decisions: - "Left createService (and its serviceSchema) in src/app/admin/catalog/actions.ts as unused dead code rather than editing actions.ts, which was outside this plan's files_modified scope — logged to deferred-items.md for a future cleanup pass." patterns-established: - "ServiceTable/CatalogSearch is now the canonical /admin/catalog database-view UX — any future catalog-adjacent admin table (Phase 12+) should follow the same EditableCell/TagMultiSelect/QuickAddRow/search-filter pattern per DESIGN-SYSTEM.md" requirements-completed: [OFFER-07, OFFER-08, OFFER-09, OFFER-10] # Metrics duration: 12min completed: 2026-06-13 --- # Phase 11 Plan 4: Catalog Database-View UX — ServiceTable Rewrite + Search Summary **`/admin/catalog` is now a Notion/Airtable-style database view: every `services` cell (name, description, category, price, tags, active status) is click-to-edit via `EditableCell`/`TagMultiSelect`, a quick-add row creates new services on Enter, a search bar filters client-side by name/tag instantly, and inactive services sink below a "Servizi disattivati" divider at reduced opacity — with zero "Azioni" column anywhere.** ## Performance - **Duration:** ~12 min - **Started:** 2026-06-13T13:55:00Z - **Completed:** 2026-06-13T14:02:06Z - **Tasks:** 2 of 2 - **Files modified:** 3 (1 created, 1 deleted, 2 modified — counting page.tsx + ServiceTable.tsx as modified, CatalogSearch.tsx as created, ServiceForm.tsx as deleted) ## Accomplishments - `ServiceTable.tsx` fully rewritten as a database-view table: 6 columns (Nome, Descrizione, Categoria, Prezzo, Tag, Stato), every cell rendered via `EditableCell` (text/textarea/number/toggle) or `TagMultiSelect`, no "Azioni" column (D-14). - `ServiceRow` wires each field's `onSave` to `updateServiceField(service.id, field, value)` and the tags column to `TagMultiSelect`'s `onTagsChanged`; both call `router.refresh()` after the server action resolves. Per-row save errors render inline via a `colSpan={6}` trailing cell without affecting row height in the success path. - `QuickAddRow` renders a borderless `Input` with placeholder "+ Aggiungi servizio"; pressing Enter with non-empty text calls `quickAddService(name)`, clears the input, and refreshes (D-12). - Services are partitioned into `activeServices`/`inactiveServices`; active services render first, then `QuickAddRow`, then (if any) a "Servizi disattivati" divider row followed by inactive services with `opacity-50` (D-13). - New `src/app/admin/catalog/CatalogSearch.tsx` client component: holds search query state, filters `ServiceWithTags[]` by `name` or any `tags[]` entry (case-insensitive substring match) via `useMemo`, and renders `ServiceTable` with the filtered list — purely client-state, no `router.push`/`refresh`/`
` (OFFER-10). - `page.tsx` rewritten: remains an async Server Component calling `getAllServices()`, no longer imports `ServiceForm`, renders `CatalogSearch` (or the existing "Nessun servizio..." message, reworded to point at the table's quick-add row, when the catalog is empty). - `ServiceForm.tsx` deleted entirely — its "+ Aggiungi servizio" full-form creation flow is fully replaced by `ServiceTable`'s `QuickAddRow`. - `npx tsc --noEmit`: zero errors project-wide. `npx eslint` on all 3 touched files: zero issues. `npx next build`: 0 errors, 0 warnings — `/admin/catalog` (auth-gated dynamic route) was not statically pre-rendered, so no live-DB query was attempted (consistent with the plan's DB-state note; not a firewall block, build was simply clean). ## Task Commits Each task was committed atomically: 1. **Task 1: Rewrite ServiceTable as database-view (inline edit, tags, quick-add, active/inactive split)** - `c0bedf3` (feat) 2. **Task 2: Add client-side search bar to page.tsx, remove ServiceForm** - `912a892` (feat) **Plan metadata:** (this commit, following SUMMARY) ## Files Created/Modified - `src/components/admin/catalog/ServiceTable.tsx` - Full rewrite: `ServiceRow` (EditableCell x5 + TagMultiSelect), `QuickAddRow`, `ServiceTable` with active/inactive split and "Servizi disattivati" divider - `src/app/admin/catalog/CatalogSearch.tsx` - New client component: search input (Lucide `Search` icon) + `useMemo` name/tag filter wrapping `ServiceTable` - `src/app/admin/catalog/page.tsx` - Removed `ServiceForm` import/render; now renders `CatalogSearch` with `ServiceWithTags[]` from `getAllServices()` - `src/components/admin/catalog/ServiceForm.tsx` - Deleted (102 lines); quick-add functionality fully replaced by `ServiceTable`'s `QuickAddRow` ## Decisions Made - Left `createService` (and the `serviceSchema` zod validator it shares with `updateService`) in `src/app/admin/catalog/actions.ts` as now-unused dead code, rather than editing `actions.ts` — that file was not in this plan's `files_modified` scope, and `updateService` still depends on `serviceSchema`. Logged to `deferred-items.md` (item #6) for a future cleanup pass. - Followed the plan's prescribed code for `ServiceTable.tsx`/`CatalogSearch.tsx`/`page.tsx` verbatim — no changes needed to satisfy lint/typecheck (unlike Plan 03's `EditableCell`, no `useEffect`/ref patterns were introduced here that would trigger `react-hooks/set-state-in-effect` or `react-hooks/refs`). ## Deviations from Plan None - plan executed exactly as written. All 12 behavior tests for Task 1 and all 7 behavior tests for Task 2 are satisfied by the code as specified in `11-04-PLAN.md`. ## Issues Encountered None. ## Known Stubs None - `ServiceTable` and `CatalogSearch` are both fully wired to live data (`getAllServices()` -> `ServiceWithTags[]`) and Plan 02's server actions (`updateServiceField`, `quickAddService`, `addTagToService`/`removeTagFromService` via `TagMultiSelect`). No hardcoded/empty/placeholder values flow to rendering. ## Threat Flags None - this plan introduces no new endpoints, auth paths, or trust-boundary changes beyond what was already reviewed in `11-04-PLAN.md`'s threat model: - T-11-14 (Information Disclosure, accept): `CatalogSearch` receives the full `ServiceWithTags[]` (including inactive services) regardless of search query — same as pre-Phase-11 `ServiceTable`, and `/admin/catalog` remains behind the Auth.js admin session guard. - T-11-15 (Tampering, mitigate): `QuickAddRow` uses `startTransition` and only clears the input on a successful `quickAddService` resolve; a failed call surfaces an inline error without a partial row (atomic DB insert). - T-11-16 (DoS, accept): catalog size remains bounded by manual admin entry; no bulk import in this phase. ## User Setup Required None - no new environment variables or external service configuration needed. ### DB-state note (carried from Plans 01-03, non-blocking for this plan) The `tags` table (Plan 01) and the `addTagToService`/`removeTagFromService`/`updateServiceField`/`quickAddService` server actions (Plan 02) remain unverified against the **live** Postgres DB — the migration apply step is firewalled and pending manual SSH+docker exec by the user (tracked in `STATE.md` Blockers since Plan 01). This plan's verification was code-only (`npx tsc --noEmit`, `eslint`, `next build`), per the plan's ``. `next build` did not attempt to reach the live DB for `/admin/catalog` (auth-gated dynamic route, not statically rendered) — the build was simply clean, no firewall error encountered. **End-to-end runtime verification of the full database-view UX (inline edit persisting, tag creation persisting, quick-add row creating a row, active/inactive split with real data) requires the Plan 01 `tags` table migration to be applied to the live DB first.** This is the same outstanding checkpoint documented in `11-01-SUMMARY.md`, `11-02-SUMMARY.md`, and `11-03-SUMMARY.md`. ## Next Phase Readiness - **Phase 11 UI deliverable complete:** `/admin/catalog` now satisfies OFFER-07 (inline edit), OFFER-08 (tag multi-select), OFFER-09 (quick-add), OFFER-10 (search/filter), and D-13/D-14 (inactive split, no Azioni column) at the code level. All 4 plans of Phase 11 (01-04) are now executed. - **Pending before milestone close:** Apply the Plan 01 `tags` table migration (`scripts/push-11-tags-migration.ts`) and the Phase 11 legacy-consolidation scripts (`migrate-services.ts`, `validate-services-migration.ts`, `migrate-tags.ts`, `validate-tags-migration.ts`) to the live Postgres DB via SSH+docker exec, per `STATE.md` Blockers. This is required for OFFER-13 and for end-to-end runtime verification of this plan's UI. - **Recommendation for next session:** Resolve the DB-migration checkpoint (apply Plan 01's migration + Phase 11 consolidation scripts), then do a manual smoke test of `/admin/catalog`: inline-edit each column type, add/remove a tag (including creating a brand-new tag), use the quick-add row, toggle a service's active state, and verify the search bar filters by name/tag. - **Phase 12 readiness:** `ServiceWithTags`/`getAllServices()`/`updateServiceField` etc. are the now-proven query/action/UI stack for `services` — Phase 12 (Offer Composition Drag&Drop & CSV Import) can build on this same foundation when rewiring `/admin/offers` to read from `services`. - **Cleanup candidate (non-blocking):** `createService`/`serviceSchema` dead code in `src/app/admin/catalog/actions.ts` (deferred-items.md #6). --- *Phase: 11-catalog-database-view-ux-legacy-consolidation* *Completed: 2026-06-13* ## Self-Check: PASSED - FOUND: src/components/admin/catalog/ServiceTable.tsx - FOUND: src/app/admin/catalog/page.tsx - FOUND: src/app/admin/catalog/CatalogSearch.tsx - CONFIRMED DELETED: src/components/admin/catalog/ServiceForm.tsx - FOUND: .planning/phases/11-catalog-database-view-ux-legacy-consolidation/11-04-SUMMARY.md - FOUND commit: c0bedf3 - FOUND commit: 912a892 - FOUND commit: 9bc1e43