10 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 16-multi-signer-ui | 04 | execute | 3 |
|
|
true |
|
|
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.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_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 From schema.ts: ```typescript export const documents = pgTable("documents", { id: text("id").primaryKey(), // ... signers: jsonb("signers").$type(), 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:
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)
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,
```
cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -30
- `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
Dashboard query fetches signing token counts per document and passes enriched rows to DocumentsTable
Task 2: Render N/M signed badge in DocumentsTable Status column
- 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)
teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx
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).
cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -30
- `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
- 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
- `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
<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>