157 lines
9.9 KiB
Markdown
157 lines
9.9 KiB
Markdown
|
|
---
|
||
|
|
phase: 03-agent-portal-shell
|
||
|
|
plan: 03
|
||
|
|
subsystem: ui
|
||
|
|
tags: [nextjs, tailwind, drizzle, server-actions, react, portal, dashboard, clients, seed]
|
||
|
|
|
||
|
|
# Dependency graph
|
||
|
|
requires:
|
||
|
|
- phase: 03-agent-portal-shell
|
||
|
|
plan: 02
|
||
|
|
provides: DocumentsTable, StatusBadge, PortalNav, authenticated layout, createClient/updateClient/deleteClient server actions
|
||
|
|
- phase: 03-agent-portal-shell
|
||
|
|
plan: 01
|
||
|
|
provides: clients and documents tables in PostgreSQL, /portal/* route protection
|
||
|
|
|
||
|
|
provides:
|
||
|
|
- portal/(protected)/dashboard/page.tsx — async server component with Drizzle JOIN query, URL-based status filter, DocumentsTable
|
||
|
|
- portal/(protected)/clients/page.tsx — async server component fetching clients with docCount + lastActivity via GROUP BY
|
||
|
|
- _components/DashboardFilters.tsx — client component with useRouter for URL-based filter navigation
|
||
|
|
- _components/ClientCard.tsx — server component card with name, email, doc count, last activity; Link to /portal/clients/[id]
|
||
|
|
- _components/ClientModal.tsx — client component modal with useActionState for create/edit modes; closes on success
|
||
|
|
- _components/ClientsPageClient.tsx — client wrapper holding modal open state; renders card grid or empty state CTA
|
||
|
|
- scripts/seed.ts — extended with 2 clients + 4 placeholder documents (idempotent via onConflictDoNothing)
|
||
|
|
|
||
|
|
affects: [03-04 — client profile page uses ClientCard pattern and ClientModal edit mode; Phase 4+ — document workflows depend on seeded clients]
|
||
|
|
|
||
|
|
# Tech tracking
|
||
|
|
tech-stack:
|
||
|
|
added: []
|
||
|
|
patterns:
|
||
|
|
- "URL-based filter state: server component reads searchParams, passes currentStatus to client DashboardFilters which uses router.push(?status=X)"
|
||
|
|
- "Server + client page split: server page fetches data, passes to ClientsPageClient (use client) for modal state management"
|
||
|
|
- "Seed idempotency: onConflictDoNothing on all inserts + query-back pattern to get IDs of seeded rows before dependent inserts"
|
||
|
|
- "ClientModal bind pattern: updateClient.bind(null, clientId) produces correct (prevState, formData) signature for useActionState"
|
||
|
|
|
||
|
|
key-files:
|
||
|
|
created:
|
||
|
|
- teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx
|
||
|
|
- teressa-copeland-homes/src/app/portal/(protected)/clients/page.tsx
|
||
|
|
- teressa-copeland-homes/src/app/portal/_components/DashboardFilters.tsx
|
||
|
|
- teressa-copeland-homes/src/app/portal/_components/ClientCard.tsx
|
||
|
|
- teressa-copeland-homes/src/app/portal/_components/ClientModal.tsx
|
||
|
|
- teressa-copeland-homes/src/app/portal/_components/ClientsPageClient.tsx
|
||
|
|
modified:
|
||
|
|
- teressa-copeland-homes/scripts/seed.ts
|
||
|
|
|
||
|
|
key-decisions:
|
||
|
|
- "DashboardFilters extracted to separate _components file — 'use client' directive must be at file top; cannot be mixed into server component file even with plan's inline suggestion"
|
||
|
|
- "ClientsPageClient extracted to _components/ClientsPageClient.tsx — cleaner than inline function below default export in clients/page.tsx"
|
||
|
|
- "Seed requires DOTENV_CONFIG_PATH=.env.local — dotenv/config loads .env by default, project uses .env.local; seed ran successfully with env path set"
|
||
|
|
|
||
|
|
patterns-established:
|
||
|
|
- "Dashboard filter: searchParams is Promise in Next.js 16 — must await before use in server component"
|
||
|
|
- "SQL aggregate in Drizzle: sql<number>`count(${documents.id})::int` for docCount, sql<Date | null>`max(${documents.sentAt})` for lastActivity"
|
||
|
|
- "useActionState from 'react' (not react-dom) — React 19 canonical import, as established in Phase 02"
|
||
|
|
|
||
|
|
requirements-completed: [CLIENT-01, CLIENT-02, DASH-01, DASH-02]
|
||
|
|
|
||
|
|
# Metrics
|
||
|
|
duration: 9min
|
||
|
|
completed: 2026-03-19
|
||
|
|
---
|
||
|
|
|
||
|
|
# Phase 3 Plan 03: Agent Portal Shell — Dashboard Page and Clients List Summary
|
||
|
|
|
||
|
|
**Dashboard with URL-filtered documents table and Clients card grid with create modal, backed by Drizzle JOIN queries and seeded with 2 clients + 4 placeholder documents**
|
||
|
|
|
||
|
|
## Performance
|
||
|
|
|
||
|
|
- **Duration:** 9 min
|
||
|
|
- **Started:** 2026-03-19T22:42:39Z
|
||
|
|
- **Completed:** 2026-03-19T22:51:33Z
|
||
|
|
- **Tasks:** 3
|
||
|
|
- **Files modified:** 7
|
||
|
|
|
||
|
|
## Accomplishments
|
||
|
|
|
||
|
|
- Created `portal/(protected)/dashboard/page.tsx` — queries all documents with LEFT JOIN to clients, filters by URL `?status=` param, renders DocumentsTable with gold-bordered status filter dropdown
|
||
|
|
- Created `portal/(protected)/clients/page.tsx` + `ClientsPageClient.tsx` — server/client split: server fetches clients with docCount + lastActivity via Drizzle GROUP BY; client wrapper holds modal open state; card grid with empty state CTA
|
||
|
|
- Created `ClientCard.tsx`, `ClientModal.tsx`, `DashboardFilters.tsx` — reusable portal components for card display, create/edit modal (useActionState + bind pattern), and URL-navigation filter
|
||
|
|
- Extended `scripts/seed.ts` — idempotent seeding of 2 clients (Sarah Johnson, Mike Torres) and 4 documents covering Draft/Sent/Signed statuses
|
||
|
|
|
||
|
|
## Task Commits
|
||
|
|
|
||
|
|
Each task was committed atomically:
|
||
|
|
|
||
|
|
1. **Task 1: Dashboard page with filterable documents table** - `e55d7a1` (feat)
|
||
|
|
2. **Task 2: Clients list page with card grid and create modal** - `df1924a` (feat)
|
||
|
|
3. **Task 3: Extend seed.ts with client and placeholder document rows** - `3fa2e1c` (feat)
|
||
|
|
|
||
|
|
**Plan metadata:** (pending docs commit)
|
||
|
|
|
||
|
|
## Files Created/Modified
|
||
|
|
|
||
|
|
- `teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx` — Async server component: Drizzle JOIN query, URL ?status filter, DocumentsTable, DashboardFilters
|
||
|
|
- `teressa-copeland-homes/src/app/portal/(protected)/clients/page.tsx` — Async server component: fetches clients with docCount + lastActivity, passes to ClientsPageClient
|
||
|
|
- `teressa-copeland-homes/src/app/portal/_components/DashboardFilters.tsx` — "use client" select component; router.push(?status=X) on change
|
||
|
|
- `teressa-copeland-homes/src/app/portal/_components/ClientCard.tsx` — Server component card wrapped in Link; name, email, doc count, last activity display
|
||
|
|
- `teressa-copeland-homes/src/app/portal/_components/ClientModal.tsx` — "use client" modal; useActionState from 'react'; create/edit modes via bind; closes on success via useEffect
|
||
|
|
- `teressa-copeland-homes/src/app/portal/_components/ClientsPageClient.tsx` — "use client" wrapper with isOpen state; card grid + empty state + ClientModal
|
||
|
|
- `teressa-copeland-homes/scripts/seed.ts` — Extended with clients + documents seed (onConflictDoNothing, query-back pattern for IDs)
|
||
|
|
|
||
|
|
## Decisions Made
|
||
|
|
|
||
|
|
- **DashboardFilters in separate file:** The plan suggested inlining a `"use client"` component below the default export. But `"use client"` is a file-level directive — it must be at the top of the file. Extracted to `_components/DashboardFilters.tsx` (clean separation, TypeScript clean).
|
||
|
|
- **ClientsPageClient in separate file:** Similarly extracted to `_components/ClientsPageClient.tsx` rather than inlined, matching project convention.
|
||
|
|
- **Seed env loading:** `scripts/seed.ts` uses `import "dotenv/config"` which reads `.env` by default. The project uses `.env.local`. Seed runs correctly with `DOTENV_CONFIG_PATH=.env.local npm run db:seed`. Database was seeded successfully; data verified in PostgreSQL.
|
||
|
|
|
||
|
|
## Deviations from Plan
|
||
|
|
|
||
|
|
### Auto-fixed Issues
|
||
|
|
|
||
|
|
**1. [Rule 3 - Blocking] Extracted DashboardFilters to separate file**
|
||
|
|
- **Found during:** Task 1 (Dashboard page creation)
|
||
|
|
- **Issue:** Plan specified inline `"use client"` function inside the server component file. Next.js does not support mixed directives — `"use client"` must be the first statement in a file; it cannot appear inside a function body or after imports. Inlining as written would fail at build time.
|
||
|
|
- **Fix:** Created `src/app/portal/_components/DashboardFilters.tsx` as a separate "use client" component; imported it into the dashboard server component
|
||
|
|
- **Files modified:** `_components/DashboardFilters.tsx` (new), `dashboard/page.tsx` (imports from DashboardFilters)
|
||
|
|
- **Verification:** `npx tsc --noEmit` passes cleanly
|
||
|
|
- **Committed in:** `e55d7a1` (Task 1 commit)
|
||
|
|
|
||
|
|
**2. [Rule 3 - Blocking] Extracted ClientsPageClient to separate file**
|
||
|
|
- **Found during:** Task 2 (Clients page creation)
|
||
|
|
- **Issue:** Plan suggested inlining `ClientsPageClient` below the `export default` in `clients/page.tsx`. While technically possible (unlike "use client" directives), extracting to a separate file follows the established project convention and avoids potential issues with mixing server and client component definitions in the same file.
|
||
|
|
- **Fix:** Created `src/app/portal/_components/ClientsPageClient.tsx`; `clients/page.tsx` imports and renders it
|
||
|
|
- **Files modified:** `_components/ClientsPageClient.tsx` (new), `clients/page.tsx`
|
||
|
|
- **Verification:** `npx tsc --noEmit` passes cleanly
|
||
|
|
- **Committed in:** `df1924a` (Task 2 commit)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Total deviations:** 2 auto-fixed (2 blocking — Next.js directive placement constraint + component extraction)
|
||
|
|
**Impact on plan:** Both auto-fixes required for correctness and consistency. No scope creep. All plan artifacts delivered.
|
||
|
|
|
||
|
|
## Issues Encountered
|
||
|
|
|
||
|
|
- Seed script (`dotenv/config`) loads `.env` by default but project uses `.env.local`. Seed runs correctly with `DOTENV_CONFIG_PATH=.env.local npm run db:seed`. Data is seeded. If `db:seed` must run without the env prefix in the future, the npm script should be updated to `tsx --env-file=.env.local scripts/seed.ts`.
|
||
|
|
|
||
|
|
## User Setup Required
|
||
|
|
|
||
|
|
None - no external service configuration required. All portal components are pure UI, server actions, and database operations.
|
||
|
|
|
||
|
|
## Next Phase Readiness
|
||
|
|
|
||
|
|
- Dashboard renders filterable documents table — ready for real document records once upload flow is built
|
||
|
|
- Clients card grid displays live database counts + last activity
|
||
|
|
- `ClientModal` supports both `create` and `edit` modes — ready for Plan 03-04 (client profile page with edit capability)
|
||
|
|
- Seed provides 2 clients and 4 documents — portal views are populated from first load
|
||
|
|
- TypeScript compiles cleanly; no blockers for 03-04
|
||
|
|
|
||
|
|
---
|
||
|
|
*Phase: 03-agent-portal-shell*
|
||
|
|
*Completed: 2026-03-19*
|
||
|
|
|
||
|
|
## Self-Check: PASSED
|
||
|
|
|
||
|
|
All 7 implementation files and SUMMARY.md verified present. All 3 task commits verified in git log (e55d7a1, df1924a, 3fa2e1c). TypeScript compiles cleanly.
|