Files

257 lines
10 KiB
Markdown
Raw Permalink Normal View History

---
phase: 16-multi-signer-ui
plan: 04
type: execute
wave: 3
depends_on: ["16-02", "16-03"]
files_modified:
- teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx
- teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx
autonomous: true
requirements: [MSIGN-09]
must_haves:
truths:
- "Multi-signer Sent documents show N/M signed badge in Status column"
- "Single-signer documents show no N/M badge (unchanged)"
- "Fully signed documents show existing Signed badge only (no N/M)"
- "N/M is computed from signingTokens with usedAt IS NOT NULL"
artifacts:
- path: "teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx"
provides: "Server-side query joining signingTokens for per-signer completion count"
contains: "signingTokens"
- path: "teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx"
provides: "N/M signed badge rendering in Status column"
contains: "signed"
key_links:
- from: "dashboard/page.tsx query"
to: "DocumentsTable rows"
via: "signedCount and totalSigners fields on row data"
pattern: "signedCount|totalSigners"
- from: "DocumentsTable"
to: "N/M badge"
via: "conditional render when totalSigners > 0 and status === Sent"
pattern: "totalSigners.*>.*0"
---
<objective>
Add the N/M signed badge to the dashboard documents table for multi-signer documents per D-11, D-12, D-13.
Purpose: MSIGN-09 (dashboard shows per-signer completion status). Agent sees at a glance how many signers have completed for documents that are partially signed.
Output: Dashboard query includes signing token counts; DocumentsTable renders "N/M signed" badge next to Status for multi-signer Sent documents.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/16-multi-signer-ui/16-CONTEXT.md
@.planning/phases/16-multi-signer-ui/16-UI-SPEC.md
<interfaces>
From schema.ts:
```typescript
export const documents = pgTable("documents", {
id: text("id").primaryKey(),
// ...
signers: jsonb("signers").$type<DocumentSigner[]>(),
status: text("status").$type<"Draft" | "Sent" | "Viewed" | "Signed">().default("Draft").notNull(),
// ...
});
export const signingTokens = pgTable('signing_tokens', {
id: text('id').primaryKey(),
documentId: text('document_id').notNull().references(() => documents.id, { onDelete: 'cascade' }),
signerEmail: text('signer_email'),
createdAt: timestamp('created_at').defaultNow().notNull(),
expiresAt: timestamp('expires_at').notNull(),
usedAt: timestamp('used_at'),
});
```
DocumentsTable current row type:
```typescript
type DocumentRow = {
id: string;
name: string;
clientName: string | null;
status: "Draft" | "Sent" | "Viewed" | "Signed";
sentAt: Date | null;
signedAt: Date | null;
clientId: string;
};
```
StatusBadge:
```typescript
export function StatusBadge({ status }: { status: "Draft" | "Sent" | "Viewed" | "Signed" });
```
UI-SPEC badge spec:
- Style: `inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-700 ml-1.5`
- Text: "N/M signed"
- Only for multi-signer (signers non-empty) AND status "Sent"
- Not for status "Signed" (existing badge sufficient)
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Add signing token counts to dashboard query</name>
<read_first>
- teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx
- teressa-copeland-homes/src/lib/db/schema.ts (signingTokens table, documents.signers)
- .planning/phases/16-multi-signer-ui/16-CONTEXT.md (D-11, D-12, D-13)
- .planning/phases/16-multi-signer-ui/16-UI-SPEC.md (Component Inventory section 5)
</read_first>
<files>teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx</files>
<action>
Import `signingTokens` from `@/lib/db/schema` and `sql, isNotNull, count` from `drizzle-orm`.
The dashboard currently uses a simple select+leftJoin query. To add signer counts, use a subquery approach to avoid N+1:
After the main `allRows` query, compute signing progress for multi-signer documents. Use a separate query for signing token counts to keep it simple:
```typescript
import { signingTokens } from "@/lib/db/schema";
import { sql } from "drizzle-orm";
// After allRows query, fetch signing token progress for all documents in one query
const tokenCounts = await db
.select({
documentId: signingTokens.documentId,
total: sql<number>`count(*)`.as('total'),
signed: sql<number>`count(${signingTokens.usedAt})`.as('signed'),
})
.from(signingTokens)
.groupBy(signingTokens.documentId);
const tokenMap = new Map(tokenCounts.map(t => [t.documentId, { total: Number(t.total), signed: Number(t.signed) }]));
```
Then augment the rows passed to DocumentsTable:
```typescript
const enrichedRows = filteredRows.map(row => {
const tc = tokenMap.get(row.id);
return {
...row,
signedCount: tc?.signed ?? null,
totalSigners: tc?.total ?? null,
};
});
```
Pass `enrichedRows` to `<DocumentsTable rows={enrichedRows} ...>` instead of `filteredRows`.
Also need to add `signers` to the allRows select to know which documents are multi-signer:
```typescript
signers: documents.signers,
```
And include it in enrichedRows:
```typescript
hasMultipleSigners: Array.isArray(row.signers) && row.signers.length > 0,
```
</action>
<verify>
<automated>cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -30</automated>
</verify>
<acceptance_criteria>
- `grep -n "signingTokens" teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx` shows import and query
- `grep -n "signedCount" teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx` shows field enrichment
- `grep -n "totalSigners" teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx` shows field enrichment
- `grep -n "hasMultipleSigners" teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx` shows multi-signer detection
- `npx tsc --noEmit` passes
</acceptance_criteria>
<done>Dashboard query fetches signing token counts per document and passes enriched rows to DocumentsTable</done>
</task>
<task type="auto">
<name>Task 2: Render N/M signed badge in DocumentsTable Status column</name>
<read_first>
- teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx
- teressa-copeland-homes/src/app/portal/_components/StatusBadge.tsx
- .planning/phases/16-multi-signer-ui/16-CONTEXT.md (D-11, D-12, D-13)
- .planning/phases/16-multi-signer-ui/16-UI-SPEC.md (Component Inventory section 5, badge spec)
</read_first>
<files>teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx</files>
<action>
Update the `DocumentRow` type to include optional new fields:
```typescript
type DocumentRow = {
id: string;
name: string;
clientName: string | null;
status: "Draft" | "Sent" | "Viewed" | "Signed";
sentAt: Date | null;
signedAt: Date | null;
clientId: string;
signedCount?: number | null;
totalSigners?: number | null;
hasMultipleSigners?: boolean;
};
```
In the Status `<td>` cell (currently just `<StatusBadge status={row.status} />`), add the N/M badge after the StatusBadge:
```tsx
<td style={{ padding: "0.875rem 1.5rem" }}>
<StatusBadge status={row.status} />
{row.hasMultipleSigners && row.status === 'Sent' && row.totalSigners != null && row.totalSigners > 0 && (
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-700 ml-1.5">
{row.signedCount ?? 0}/{row.totalSigners} signed
</span>
)}
</td>
```
Per D-12: Only show for multi-signer documents (`hasMultipleSigners`).
Per D-13: Only show for status "Sent" — not for "Signed" (existing Signed badge sufficient).
The UI-SPEC specifies `bg-blue-50 text-blue-700` for the badge (matching the blue theme of Sent status).
</action>
<verify>
<automated>cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -30</automated>
</verify>
<acceptance_criteria>
- `grep -n "signedCount" teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx` shows field in type and render
- `grep -n "totalSigners" teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx` shows field in type and render
- `grep -n "hasMultipleSigners" teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx` shows conditional
- `grep -n "bg-blue-50 text-blue-700" teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx` shows badge styles
- `grep -n "ml-1.5" teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx` shows margin from StatusBadge
- `grep -n "signed" teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx` shows "signed" text in badge
- `npx tsc --noEmit` passes
</acceptance_criteria>
<done>
- Multi-signer Sent documents show "N/M signed" badge next to Sent status badge per D-11
- Single-signer documents show no N/M badge per D-12
- Signed documents show only existing "Signed" badge per D-13
- Badge uses exact UI-SPEC classes: `inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-700 ml-1.5`
- TypeScript compiles cleanly
</done>
</task>
</tasks>
<verification>
- `npx tsc --noEmit` passes
- Dashboard query includes token count subquery
- N/M badge renders only for multi-signer Sent documents
- Badge styling matches UI-SPEC exactly
</verification>
<success_criteria>
- Dashboard shows "1/2 signed" badge for partially-signed multi-signer documents
- No badge for single-signer or fully-signed documents
- Badge computed server-side from signingTokens count
- TypeScript compiles with no errors
</success_criteria>
<output>
After completion, create `.planning/phases/16-multi-signer-ui/16-04-SUMMARY.md`
</output>