From 2947fa558c1a6129e49278e02c99d74d0fbea1d5 Mon Sep 17 00:00:00 2001 From: Chandler Copeland Date: Mon, 6 Apr 2026 14:51:23 -0600 Subject: [PATCH] feat(20-01): add My Templates tab to AddDocumentModal - Add DocumentTemplateRow type and activeTab/docTemplates/selectedDocTemplate state - Lazy-fetch /api/templates on first My Templates tab click - handleSwitchToTemplates lazy-loads on first click only - handleSelectDocTemplate clears selectedTemplate and customFile (mutual exclusivity) - handleSelectTemplate now also clears selectedDocTemplate - handleSubmit: new branch at top sends documentTemplateId to POST /api/documents - Guard and disabled condition updated to include selectedDocTemplate - Tab bar renders with underline-style active indicator matching project Tailwind patterns - Existing Forms Library content and custom upload section wrapped in activeTab === 'forms' conditional --- .../portal/_components/AddDocumentModal.tsx | 169 ++++++++++++++---- 1 file changed, 135 insertions(+), 34 deletions(-) diff --git a/teressa-copeland-homes/src/app/portal/_components/AddDocumentModal.tsx b/teressa-copeland-homes/src/app/portal/_components/AddDocumentModal.tsx index 9755af8..71e7ae2 100644 --- a/teressa-copeland-homes/src/app/portal/_components/AddDocumentModal.tsx +++ b/teressa-copeland-homes/src/app/portal/_components/AddDocumentModal.tsx @@ -4,6 +4,14 @@ import { useRouter } from 'next/navigation'; type FormTemplate = { id: string; name: string; filename: string }; +type DocumentTemplateRow = { + id: string; + name: string; + formName: string | null; + fieldCount: number; + updatedAt: string; +}; + export function AddDocumentModal({ clientId, onClose }: { clientId: string; onClose: () => void }) { const [templates, setTemplates] = useState([]); const [query, setQuery] = useState(''); @@ -14,6 +22,12 @@ export function AddDocumentModal({ clientId, onClose }: { clientId: string; onCl const [saving, setSaving] = useState(false); const router = useRouter(); + // Template tab state + const [activeTab, setActiveTab] = useState<'forms' | 'templates'>('forms'); + const [docTemplates, setDocTemplates] = useState([]); + const [docTemplatesLoaded, setDocTemplatesLoaded] = useState(false); + const [selectedDocTemplate, setSelectedDocTemplate] = useState(null); + useEffect(() => { fetch('/api/forms-library') .then(r => r.json()) @@ -26,6 +40,7 @@ export function AddDocumentModal({ clientId, onClose }: { clientId: string; onCl ); const handleSelectTemplate = (t: FormTemplate) => { + setSelectedDocTemplate(null); setSelectedTemplate(t); setCustomFile(null); setDocName(t.name); @@ -35,16 +50,44 @@ export function AddDocumentModal({ clientId, onClose }: { clientId: string; onCl const file = e.target.files?.[0] ?? null; setCustomFile(file); setSelectedTemplate(null); + setSelectedDocTemplate(null); if (file) setDocName(file.name.replace(/\.pdf$/i, '')); }; + function handleSwitchToTemplates() { + setActiveTab('templates'); + if (!docTemplatesLoaded) { + fetch('/api/templates') + .then(r => r.json()) + .then((data: DocumentTemplateRow[]) => { setDocTemplates(data); setDocTemplatesLoaded(true); }) + .catch(console.error); + } + } + + const handleSelectDocTemplate = (t: DocumentTemplateRow) => { + setSelectedDocTemplate(t); + setSelectedTemplate(null); + setCustomFile(null); + setDocName(t.name); + }; + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - if (!docName.trim() || (!selectedTemplate && !customFile)) return; + if (!docName.trim() || (!selectedTemplate && !customFile && !selectedDocTemplate)) return; setSaving(true); try { - if (customFile) { + if (selectedDocTemplate) { + await fetch('/api/documents', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + clientId, + name: docName.trim(), + documentTemplateId: selectedDocTemplate.id, + }), + }); + } else if (customFile) { const fd = new FormData(); fd.append('clientId', clientId); fd.append('name', docName.trim()); @@ -71,41 +114,99 @@ export function AddDocumentModal({ clientId, onClose }: { clientId: string; onCl

Add Document

- setQuery(e.target.value)} - className="w-full border rounded px-3 py-2 mb-3 text-sm" - /> +
+ + +
-
    - {filtered.length === 0 && ( -
  • No forms found
  • - )} - {filtered.map(t => ( -
  • handleSelectTemplate(t)} - className={`px-3 py-2 text-sm cursor-pointer hover:bg-gray-100 ${selectedTemplate?.id === t.id ? 'bg-blue-50 font-medium' : ''}`} - > - {t.name} -
  • - ))} -
+ {activeTab === 'forms' && ( + <> + setQuery(e.target.value)} + className="w-full border rounded px-3 py-2 mb-3 text-sm" + /> -
- -
- - {customFile && ( - {customFile.name} +
    + {filtered.length === 0 && ( +
  • No forms found
  • + )} + {filtered.map(t => ( +
  • handleSelectTemplate(t)} + className={`px-3 py-2 text-sm cursor-pointer hover:bg-gray-100 ${selectedTemplate?.id === t.id ? 'bg-blue-50 font-medium' : ''}`} + > + {t.name} +
  • + ))} +
+ +
+ +
+ + {customFile && ( + {customFile.name} + )} +
+
+ + )} + + {activeTab === 'templates' && ( +
+ {!docTemplatesLoaded ? ( +

Loading templates...

+ ) : docTemplates.length === 0 ? ( +

+ No templates saved yet. Create one from the Templates page. +

+ ) : ( +
    + {docTemplates.map(t => ( +
  • handleSelectDocTemplate(t)} + className={`px-3 py-2 text-sm cursor-pointer hover:bg-gray-100 ${ + selectedDocTemplate?.id === t.id ? 'bg-blue-50 font-medium' : '' + }`} + > + {t.name} + + {t.formName ?? 'Unknown form'} · {t.fieldCount} field{t.fieldCount !== 1 ? 's' : ''} + +
  • + ))} +
)}
-
+ )}
@@ -124,7 +225,7 @@ export function AddDocumentModal({ clientId, onClose }: { clientId: string; onCl