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:
2026-05-11 15:17:02 +02:00
parent 63f96254e5
commit 880c5cbafc
5 changed files with 337 additions and 23 deletions

View File

@@ -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(