Files
red/.planning/phases/19-template-editor-ui/19-02-SUMMARY.md
Chandler Copeland c81e1e2187 docs(19-02): complete template editor UI plan
- SUMMARY.md documents list page, editor page, TemplatePageClient, TemplatePanel
- STATE.md: plan advanced to 2, progress 97%, 2 decisions logged
- ROADMAP.md: phase 19 updated (2/3 plans complete)
- REQUIREMENTS.md: TMPL-05 through TMPL-09 marked complete
2026-04-06 13:17:09 -06:00

98 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 19-template-editor-ui
plan: "02"
subsystem: template-editor-ui
tags: [templates, ui, pdf-viewer, field-placer, role-management, ai-auto-place]
dependency_graph:
requires:
- 19-01 # PdfViewerWrapper onPersist/fieldsUrl/fileUrl abstraction
- 18-02 # PATCH /api/templates/[id] and /api/templates/[id]/fields endpoints
provides:
- /portal/templates list page with create modal
- /portal/templates/[id] editor page with FieldPlacer in template mode
- TemplatePageClient state owner
- TemplatePanel right panel with role management, AI auto-place, save
affects:
- 20-01 # Apply template — depends on template editor page existing
tech_stack:
added: []
patterns:
- Server component queries documentTemplates with formTemplates LEFT JOIN (mirrors clients/page.tsx)
- TemplatePageClient mirrors DocumentPageClient: state owner pattern, PdfViewerWrapper reuse
- TemplatePanel mirrors PreparePanel: 280px right panel, inline styles, gold/navy color scheme
- Role labels stored in DocumentSigner.email slot (v1.3 Research decision)
- onPersist merges textFillData hints into field.hint for type='text' fields
key_files:
created:
- teressa-copeland-homes/src/app/portal/(protected)/templates/page.tsx
- teressa-copeland-homes/src/app/portal/(protected)/templates/TemplatesListClient.tsx
- teressa-copeland-homes/src/app/portal/(protected)/templates/[id]/page.tsx
- teressa-copeland-homes/src/app/portal/(protected)/templates/[id]/_components/TemplatePageClient.tsx
- teressa-copeland-homes/src/app/portal/(protected)/templates/[id]/_components/TemplatePanel.tsx
modified: []
decisions:
- "[19-02]: TemplatesListClient placed as sibling file to page.tsx (not inline) — mirrors ClientsPageClient pattern for separation of concerns"
- "[19-02]: TemplatePanel onRenameRole/onRemoveRole are async in props signature — enables TemplatePanel to await role operations without holding fetch logic itself"
- "[19-02]: ConfirmDialog shown for all role removals (not just when fields > 0) — simplifies TemplatePanel by avoiding a fields fetch just for the check; UI-SPEC allows this"
metrics:
duration_minutes: 4
completed_date: "2026-04-06"
tasks_completed: 2
files_created: 5
files_modified: 0
---
# Phase 19 Plan 02: Template Editor UI Summary
## One-liner
Template editor UI with list page, create modal, TemplatePageClient state owner, and TemplatePanel with role management, AI auto-place, and save — all wired to the Phase 18 CRUD API and Phase 19-01 PdfViewerWrapper abstractions.
## What Was Built
### Task 1: Templates list page (`/portal/templates`)
**`page.tsx`** (server component) queries `documentTemplates` LEFT JOINed with `formTemplates`, filtered `WHERE archivedAt IS NULL`, ordered by `updatedAt DESC`. Also fetches all `formTemplates` for the create modal picker. Renders `TemplatesListClient`.
**`TemplatesListClient.tsx`** (client component):
- Page heading "Templates" with subtitle showing count
- "+ New Template" button (gold `#C9A84C`)
- Empty state: "No templates yet" with body copy and CTA
- List rows: name (navy/600), form name (gray-500), field count, last-updated date. Row hover `#F0EDE8`. Click navigates via `useRouter().push()`.
- Create modal: overlay + white card, template name input + form select dropdown, POSTs to `/api/templates`, navigates to `/portal/templates/${id}` on 201 success.
### Task 2: Template editor pages
**`[id]/page.tsx`** (server component): queries with `with: { formTemplate: true }`, calls `notFound()` on missing/archived. Passes `templateId`, `templateName`, `formName`, `initialFields` to `TemplatePageClient`.
**`TemplatePageClient.tsx`** (client state owner):
- `deriveRolesFromFields()` extracts signer roles from existing `field.signerEmail` values; falls back to `[Buyer, Seller]` defaults if none.
- `textFillData` initialized from `field.hint` values on existing fields.
- `handlePersist` merges `textFillData[id]` into `field.hint` for `f.type === 'text'` before PATCHing.
- `handleAiAutoPlace` POSTs to `/api/templates/[id]/ai-prepare` and increments `aiPlacementKey` to reload FieldPlacer.
- `handleRenameRole` / `handleRemoveRole` fetch current fields, update `signerEmail`, and PATCH — then increment `aiPlacementKey` to reload.
- `handleSave` PATCHes the template name (fields already persisted via `onPersist` on each drag/drop/delete/resize).
- PdfViewerWrapper receives `fieldsUrl=/api/templates/[id]/fields` and `fileUrl=/api/templates/[id]/file` — operates in template mode.
**`TemplatePanel.tsx`** (right panel, 280px sticky):
- Template name: inline `<input>` with border-bottom style; `onBlur` PATCHes name immediately.
- "Signers / Roles" section: color-dot pills, click-to-rename inline input (Enter/blur commits, Escape cancels), `×` remove button with `ConfirmDialog` confirmation.
- Add role: text input + "Add" button + preset chips (Buyer, Co-Buyer, Seller, Co-Seller — filtered to exclude already-present roles).
- "AI Auto-place Fields" button (navy `#1B2B4B`): CSS spinner + "Placing..." loading state, red error text below.
- "Save Template" button (gold `#C9A84C`): "Saving..." at 0.7 opacity, "Saved" in green `#059669` for 3s on success.
## Verification
- `npx tsc --noEmit`: exits 0 (no errors)
- `npm run build`: succeeds — `/portal/templates` and `/portal/templates/[id]` listed as dynamic routes
## Deviations from Plan
None — plan executed exactly as written, with one minor UX simplification:
**ConfirmDialog shown for all role removals (not just when fields > 0):** The plan specifies fetching field count to conditionally show the dialog. Simplified to always show ConfirmDialog on role removal — avoids a round-trip fetch purely for conditional UI logic, and the dialog message is safe to show generically ("will unassign its field(s)"). This is strictly better UX (no flicker from async check) and consistent with the ConfirmDialog pattern elsewhere.
## Known Stubs
None — all data flows are wired. List page reads from DB. Editor page reads template + renders PDF via `/api/templates/[id]/file`. Fields load from `/api/templates/[id]/fields`. Persist writes to `/api/templates/[id]` via PATCH.