feat(12.1-02): replace TextFillForm with QuickFillPanel in PreparePanel; delete TextFillForm.tsx
- Add textFillData, selectedFieldId, onQuickFill props to PreparePanelProps - Remove textFillData local state and handleTextFillChange (parent now owns state) - Remove TextFillForm import and JSX block - Add QuickFillPanel: shows Client Name, Property Address, Client Email quick-fill buttons when selectedFieldId is non-null; idle state message when no field selected - handlePreview and handlePrepare use textFillData from props - Delete TextFillForm.tsx (label-keyed generic form no longer used)
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
'use client';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { TextFillForm } from './TextFillForm';
|
||||
import { PreviewModal } from './PreviewModal';
|
||||
|
||||
interface PreparePanelProps {
|
||||
@@ -14,6 +13,9 @@ interface PreparePanelProps {
|
||||
clientPropertyAddress?: string | null;
|
||||
previewToken: string | null;
|
||||
onPreviewTokenChange: (token: string | null) => void;
|
||||
textFillData: Record<string, string>;
|
||||
selectedFieldId: string | null;
|
||||
onQuickFill: (fieldId: string, value: string) => void;
|
||||
}
|
||||
|
||||
function parseEmails(raw: string | undefined): string[] {
|
||||
@@ -24,16 +26,18 @@ function isValidEmail(email: string): boolean {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
||||
}
|
||||
|
||||
export function PreparePanel({ docId, defaultEmail, clientName, currentStatus, agentDownloadUrl, signedAt, clientPropertyAddress, previewToken, onPreviewTokenChange }: PreparePanelProps) {
|
||||
export function PreparePanel({
|
||||
docId, defaultEmail, clientName, currentStatus,
|
||||
agentDownloadUrl, signedAt, clientPropertyAddress,
|
||||
previewToken, onPreviewTokenChange,
|
||||
textFillData, selectedFieldId, onQuickFill,
|
||||
}: PreparePanelProps) {
|
||||
const router = useRouter();
|
||||
const [recipients, setRecipients] = useState(defaultEmail ?? '');
|
||||
// Sync if defaultEmail arrives after initial render (streaming / hydration timing)
|
||||
useEffect(() => {
|
||||
if (defaultEmail) setRecipients(defaultEmail);
|
||||
}, [defaultEmail]);
|
||||
const [textFillData, setTextFillData] = useState<Record<string, string>>(
|
||||
() => (clientPropertyAddress ? { propertyAddress: clientPropertyAddress } : {} as Record<string, string>)
|
||||
);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [result, setResult] = useState<{ ok: boolean; message: string } | null>(null);
|
||||
const [previewBytes, setPreviewBytes] = useState<ArrayBuffer | null>(null);
|
||||
@@ -89,11 +93,6 @@ export function PreparePanel({ docId, defaultEmail, clientName, currentStatus, a
|
||||
);
|
||||
}
|
||||
|
||||
function handleTextFillChange(data: Record<string, string>) {
|
||||
setTextFillData(data);
|
||||
onPreviewTokenChange(null);
|
||||
}
|
||||
|
||||
async function handlePreview() {
|
||||
setLoading(true);
|
||||
setResult(null);
|
||||
@@ -184,12 +183,48 @@ export function PreparePanel({ docId, defaultEmail, clientName, currentStatus, a
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Quick-fill panel — only shown when a text field is selected */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Text fill fields</label>
|
||||
<TextFillForm
|
||||
onChange={handleTextFillChange}
|
||||
initialData={clientPropertyAddress ? { propertyAddress: clientPropertyAddress } : undefined}
|
||||
/>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Text field fill</label>
|
||||
{selectedFieldId ? (
|
||||
<div className="space-y-1.5">
|
||||
<p className="text-xs text-gray-400">
|
||||
Click a suggestion to fill the selected field.
|
||||
</p>
|
||||
{clientName && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onQuickFill(selectedFieldId, clientName)}
|
||||
className="w-full text-left px-3 py-2 text-sm border rounded bg-white hover:bg-blue-50 hover:border-blue-300 transition-colors"
|
||||
>
|
||||
<span className="text-xs text-gray-400 block">Client Name</span>
|
||||
<span className="truncate block">{clientName}</span>
|
||||
</button>
|
||||
)}
|
||||
{clientPropertyAddress && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onQuickFill(selectedFieldId, clientPropertyAddress)}
|
||||
className="w-full text-left px-3 py-2 text-sm border rounded bg-white hover:bg-blue-50 hover:border-blue-300 transition-colors"
|
||||
>
|
||||
<span className="text-xs text-gray-400 block">Property Address</span>
|
||||
<span className="truncate block">{clientPropertyAddress}</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onQuickFill(selectedFieldId, defaultEmail)}
|
||||
className="w-full text-left px-3 py-2 text-sm border rounded bg-white hover:bg-blue-50 hover:border-blue-300 transition-colors"
|
||||
>
|
||||
<span className="text-xs text-gray-400 block">Client Email</span>
|
||||
<span className="truncate block">{defaultEmail}</span>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-gray-400 italic">
|
||||
Click a text field on the document to edit or quick-fill it.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
'use client';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface TextRow { label: string; value: string; }
|
||||
|
||||
interface TextFillFormProps {
|
||||
onChange: (data: Record<string, string>) => void;
|
||||
initialData?: Record<string, string>;
|
||||
}
|
||||
|
||||
function buildInitialRows(initialData?: Record<string, string>): TextRow[] {
|
||||
if (initialData && Object.keys(initialData).length > 0) {
|
||||
const seeded = Object.entries(initialData).map(([label, value]) => ({ label, value }));
|
||||
return [...seeded, { label: '', value: '' }];
|
||||
}
|
||||
return [{ label: '', value: '' }];
|
||||
}
|
||||
|
||||
export function TextFillForm({ onChange, initialData }: TextFillFormProps) {
|
||||
const [rows, setRows] = useState<TextRow[]>([{ label: '', value: '' }]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialData && Object.keys(initialData).length > 0) {
|
||||
setRows(buildInitialRows(initialData));
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
function updateRow(index: number, field: 'label' | 'value', val: string) {
|
||||
const next = rows.map((r, i) => i === index ? { ...r, [field]: val } : r);
|
||||
setRows(next);
|
||||
onChange(Object.fromEntries(next.filter(r => r.label).map(r => [r.label, r.value])));
|
||||
}
|
||||
|
||||
function addRow() {
|
||||
if (rows.length >= 10) return;
|
||||
setRows([...rows, { label: '', value: '' }]);
|
||||
}
|
||||
|
||||
function removeRow(index: number) {
|
||||
const next = rows.filter((_, i) => i !== index);
|
||||
setRows(next.length ? next : [{ label: '', value: '' }]);
|
||||
onChange(Object.fromEntries(next.filter(r => r.label).map(r => [r.label, r.value])));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs text-gray-400 mt-0.5">
|
||||
Pre-fill text fields in the PDF before sending. Leave blank to skip a field.
|
||||
</p>
|
||||
{rows.map((row, i) => (
|
||||
<div key={i} className="rounded border border-gray-200 bg-white p-2 space-y-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-xs font-medium text-gray-400 uppercase tracking-wide flex-1">Field name</span>
|
||||
<button
|
||||
onClick={() => removeRow(i)}
|
||||
className="text-gray-300 hover:text-red-500 text-base leading-none"
|
||||
aria-label="Remove row"
|
||||
type="button"
|
||||
>×</button>
|
||||
</div>
|
||||
<input
|
||||
placeholder="e.g. PropertyAddress"
|
||||
value={row.label}
|
||||
onChange={e => updateRow(i, 'label', e.target.value)}
|
||||
className="w-full border rounded px-2 py-1.5 text-sm focus:border-[var(--gold)] focus:ring-[var(--gold)]"
|
||||
/>
|
||||
<span className="text-xs font-medium text-gray-400 uppercase tracking-wide block pt-0.5">Value</span>
|
||||
<input
|
||||
placeholder="Value to fill in"
|
||||
value={row.value}
|
||||
onChange={e => updateRow(i, 'value', e.target.value)}
|
||||
className="w-full border rounded px-2 py-1.5 text-sm focus:border-[var(--gold)] focus:ring-[var(--gold)]"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{rows.length < 10 && (
|
||||
<button
|
||||
onClick={addRow}
|
||||
className="text-sm text-gray-500 hover:text-gray-700"
|
||||
type="button"
|
||||
>
|
||||
+ Add field
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user