From a00d52216ef87e2a331c67dd0035372fa1698fa0 Mon Sep 17 00:00:00 2001 From: Chandler Copeland Date: Fri, 3 Apr 2026 17:41:51 -0600 Subject: [PATCH] feat(clients): show contacts on client cards, auto-persist seeded signers to DB --- .../app/portal/(protected)/clients/page.tsx | 3 +- .../_components/DocumentPageClient.tsx | 14 ++++- .../src/app/portal/_components/ClientCard.tsx | 60 +++++++++++-------- .../portal/_components/ClientsPageClient.tsx | 32 +++++----- 4 files changed, 67 insertions(+), 42 deletions(-) diff --git a/teressa-copeland-homes/src/app/portal/(protected)/clients/page.tsx b/teressa-copeland-homes/src/app/portal/(protected)/clients/page.tsx index b738470..e070c59 100644 --- a/teressa-copeland-homes/src/app/portal/(protected)/clients/page.tsx +++ b/teressa-copeland-homes/src/app/portal/(protected)/clients/page.tsx @@ -9,13 +9,14 @@ export default async function ClientsPage() { id: clients.id, name: clients.name, email: clients.email, + contacts: clients.contacts, createdAt: clients.createdAt, docCount: sql`count(${documents.id})::int`, lastActivity: sql`max(${documents.sentAt})`, }) .from(clients) .leftJoin(documents, eq(documents.clientId, clients.id)) - .groupBy(clients.id, clients.name, clients.email, clients.createdAt) + .groupBy(clients.id, clients.name, clients.email, clients.contacts, clients.createdAt) .orderBy(desc(clients.createdAt)); return ; diff --git a/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx b/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx index b1c44cd..6966aa8 100644 --- a/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx +++ b/teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/DocumentPageClient.tsx @@ -1,5 +1,5 @@ 'use client'; -import { useState, useCallback } from 'react'; +import { useState, useCallback, useEffect } from 'react'; import { PdfViewerWrapper } from './PdfViewerWrapper'; import { PreparePanel } from './PreparePanel'; import type { DocumentSigner } from '@/lib/db/schema'; @@ -45,6 +45,18 @@ export function DocumentPageClient({ const [signers, setSigners] = useState(defaultSigners); const [unassignedFieldIds, setUnassignedFieldIds] = useState>(new Set()); + // Persist auto-seeded signers to DB so they survive refresh + useEffect(() => { + if (initialSigners.length === 0 && defaultSigners.length > 0) { + fetch(`/api/documents/${docId}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ signers: defaultSigners }), + }).catch(() => {}); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const handleFieldsChanged = useCallback(() => { setPreviewToken(null); }, []); diff --git a/teressa-copeland-homes/src/app/portal/_components/ClientCard.tsx b/teressa-copeland-homes/src/app/portal/_components/ClientCard.tsx index a8cac93..02b3394 100644 --- a/teressa-copeland-homes/src/app/portal/_components/ClientCard.tsx +++ b/teressa-copeland-homes/src/app/portal/_components/ClientCard.tsx @@ -1,38 +1,50 @@ import Link from "next/link"; +import type { ClientContact } from "@/lib/db/schema"; type ClientCardProps = { id: string; name: string; email: string; + contacts?: ClientContact[]; docCount: number; lastActivity: Date | null; }; -export function ClientCard({ - id, - name, - email, - docCount, - lastActivity, -}: ClientCardProps) { +export function ClientCard({ id, name, email, contacts = [], docCount, lastActivity }: ClientCardProps) { return ( - -
-

{name}

-

{email}

-

- Documents: {docCount} -

-

- Last activity:{" "} - {lastActivity - ? lastActivity.toLocaleDateString("en-US", { - month: "short", - day: "numeric", - year: "numeric", - }) - : "No activity yet"} -

+ +
(e.currentTarget.style.boxShadow = "0 4px 12px rgba(0,0,0,0.12)")} + onMouseLeave={e => (e.currentTarget.style.boxShadow = "0 1px 4px rgba(0,0,0,0.07)")} + > + {/* Primary contact */} +

{name}

+

0 ? "0.5rem" : "0.75rem" }}>{email}

+ + {/* Additional contacts */} + {contacts.length > 0 && ( +
+ {contacts.map(c => ( +
+ + +
+ {c.name} + {c.email} +
+
+ ))} +
+ )} + +
+ {docCount} document{docCount !== 1 ? "s" : ""} + + {lastActivity + ? new Date(lastActivity).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }) + : "No activity yet"} + +
); diff --git a/teressa-copeland-homes/src/app/portal/_components/ClientsPageClient.tsx b/teressa-copeland-homes/src/app/portal/_components/ClientsPageClient.tsx index b7b278d..6626467 100644 --- a/teressa-copeland-homes/src/app/portal/_components/ClientsPageClient.tsx +++ b/teressa-copeland-homes/src/app/portal/_components/ClientsPageClient.tsx @@ -3,41 +3,44 @@ import { useState } from "react"; import { ClientCard } from "./ClientCard"; import { ClientModal } from "./ClientModal"; +import type { ClientContact } from "@/lib/db/schema"; type ClientRow = { id: string; name: string; email: string; + contacts?: ClientContact[] | null; docCount: number; lastActivity: Date | null; createdAt: Date; }; -type ClientsPageClientProps = { - clients: ClientRow[]; -}; - -export function ClientsPageClient({ clients }: ClientsPageClientProps) { +export function ClientsPageClient({ clients }: { clients: ClientRow[] }) { const [isOpen, setIsOpen] = useState(false); return (
-
-

Clients

+
+
+

Clients

+

{clients.length} client{clients.length !== 1 ? "s" : ""}

+
{clients.length === 0 ? ( -
-

No clients yet.

+
+

No clients yet.

@@ -50,6 +53,7 @@ export function ClientsPageClient({ clients }: ClientsPageClientProps) { id={client.id} name={client.name} email={client.email} + contacts={client.contacts ?? []} docCount={client.docCount} lastActivity={client.lastActivity} /> @@ -57,11 +61,7 @@ export function ClientsPageClient({ clients }: ClientsPageClientProps) {
)} - setIsOpen(false)} - mode="create" - /> + setIsOpen(false)} mode="create" />
); }