feat(documents-wizard): replace UUID-paste fields with searchable pickers + inline upload
Reps no longer have to copy/paste UUIDs into the New-document wizard.
Three UUID inputs replaced:
- Template id Input → DocumentTemplatePicker (queries /api/v1/document-templates
with name search; filters to isActive=true)
- Uploaded file id Input → inline FileUploadZone (drop or browse PDF; surfaces
the uploaded file id directly to the wizard via the new onUploadComplete
signature)
- Subject id Input → conditional picker: ClientPicker / CompanyPicker /
YachtPicker / InterestPicker depending on the subject-type dropdown.
Reservation falls back to Input for now (no ReservationPicker yet).
Other polish in the wizard:
- SIGNER_ROLES labels capitalized in the role select (client → Client, etc.)
via a formatSignerRole() helper. Internal values stay lowercase.
- Pinned h-9 on Select triggers so the type/subject row + signer-role select
vertically align with their adjacent inputs.
- Subject-type change now resets subjectId — picker options are type-specific
and a stale id from a different entity table would be invalid.
Infrastructure for hub uploads (will be consumed in a follow-up dropdown +
drag-drop pass):
- /api/v1/files/upload route now parses folderId from FormData (schema
already supported it).
- FileUploadZone accepts a folderId prop and forwards it, plus a new
onUploadComplete(file) callback shape that surfaces { id, filename } on
each successful upload. Existing per-entity callers (Files tab on clients,
companies, yachts, interests) ignore the arg, no behaviour change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,7 +18,17 @@ interface FileUploadZoneProps {
|
||||
clientId?: string;
|
||||
yachtId?: string;
|
||||
companyId?: string;
|
||||
onUploadComplete?: () => void;
|
||||
/**
|
||||
* Optional folder to deposit the file into. Hub uploads pass the
|
||||
* currently-selected folderId so files land where the user expects.
|
||||
*/
|
||||
folderId?: string | null;
|
||||
/**
|
||||
* Fires per successful upload with the file metadata. The wizard /
|
||||
* inline-upload flows use the returned id to wire follow-up actions
|
||||
* (e.g. set as the source PDF for a Documenso signing flow).
|
||||
*/
|
||||
onUploadComplete?: (file?: { id: string; filename?: string }) => void;
|
||||
}
|
||||
|
||||
export function FileUploadZone({
|
||||
@@ -27,6 +37,7 @@ export function FileUploadZone({
|
||||
clientId,
|
||||
yachtId,
|
||||
companyId,
|
||||
folderId,
|
||||
onUploadComplete,
|
||||
}: FileUploadZoneProps) {
|
||||
const [isDragOver, setIsDragOver] = useState(false);
|
||||
@@ -54,6 +65,7 @@ export function FileUploadZone({
|
||||
if (companyId) formData.append('companyId', companyId);
|
||||
if (entityType) formData.append('entityType', entityType);
|
||||
if (entityId) formData.append('entityId', entityId);
|
||||
if (folderId) formData.append('folderId', folderId);
|
||||
|
||||
setUploading((prev) =>
|
||||
prev.map((u) => (u.id === uploadId ? { ...u, progress: 50 } : u)),
|
||||
@@ -73,6 +85,16 @@ export function FileUploadZone({
|
||||
throw new Error('Upload failed');
|
||||
}
|
||||
|
||||
const uploadJson = (await uploadRes
|
||||
.json()
|
||||
.catch(() => null)) as { data?: { id?: string; filename?: string } } | null;
|
||||
if (uploadJson?.data?.id) {
|
||||
onUploadComplete?.({
|
||||
id: uploadJson.data.id,
|
||||
filename: uploadJson.data.filename,
|
||||
});
|
||||
}
|
||||
|
||||
setUploading((prev) =>
|
||||
prev.map((u) => (u.id === uploadId ? { ...u, progress: 100 } : u)),
|
||||
);
|
||||
@@ -90,7 +112,7 @@ export function FileUploadZone({
|
||||
onUploadComplete?.();
|
||||
}, 1500);
|
||||
},
|
||||
[clientId, yachtId, companyId, entityType, entityId, onUploadComplete],
|
||||
[clientId, yachtId, companyId, entityType, entityId, folderId, onUploadComplete],
|
||||
);
|
||||
|
||||
const handleDrop = useCallback(
|
||||
|
||||
Reference in New Issue
Block a user