fix(uat-batch-1): wave-1 blocker bugs — supplemental gate, file FK, downloads, search dedup, notes stale, expense form, vocab
Surgical fixes for the 7 UAT blockers that prevent productive forward
testing. Each item has a corresponding entry in alpha-uat-master.md.
- supplemental-info route relocated out of (portal) so it bypasses the
isPortalDisabledGlobally() kill-switch. URL unchanged.
- file upload service derives client_id/company_id/yacht_id from
(entityType, entityId) when not explicitly passed, so interest-tab
uploads no longer land with client_id=NULL and stay visible in the
Attachments list.
- triggerBlobDownload / triggerUrlDownload helpers in src/lib/utils
attach the anchor to the DOM before click so Chromium honours the
download attribute; 7 sites refactored, file-named downloads stop
arriving as bare UUIDs.
- search-nav-catalog dedupes by href at the result-collection layer so
the same href can no longer surface twice in the command-K dropdown
(kills the React duplicate-key warning); /admin/templates entries
merged into a single richer-keyword variant.
- NotesList gains a parentInvalidateKey prop, wired through all five
callers (interest, client, yacht, company, residential client/
interest) so the Overview "Latest note" teaser refreshes when a note
is added in the Notes tab.
- expense-form-dialog: setValue('receiptFileIds') / setValue(
'noReceiptAcknowledged') on upload/clear/checkbox so the schema-level
refine sees the field and Create stops silently no-op'ing on submit.
- bulk-add-berths-wizard: side-pontoon dropdown now reads through
useVocabulary('berth_side_pontoon_options') instead of a wrong local
enum ('Port', 'Starboard', 'Bow', 'Stern') — wizard data now matches
the rest of the platform + honours admin-editable per-port overrides.
tsc clean. 1419/1419 vitest. lint clean on touched files.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -88,6 +88,7 @@ export function ExpenseFormDialog({ open, onOpenChange, expense }: ExpenseFormDi
|
||||
expenseDate: new Date(expense.expenseDate),
|
||||
paymentStatus: (expense.paymentStatus as CreateExpenseInput['paymentStatus']) ?? 'unpaid',
|
||||
tripLabel: expense.tripLabel ?? undefined,
|
||||
noReceiptAcknowledged: Boolean(expense.noReceiptAcknowledged),
|
||||
});
|
||||
setUploadedReceipt(null);
|
||||
setPreviewUrl(null);
|
||||
@@ -98,6 +99,7 @@ export function ExpenseFormDialog({ open, onOpenChange, expense }: ExpenseFormDi
|
||||
currency: 'USD',
|
||||
paymentStatus: 'unpaid',
|
||||
expenseDate: new Date(),
|
||||
noReceiptAcknowledged: false,
|
||||
});
|
||||
setUploadedReceipt(null);
|
||||
setPreviewUrl(null);
|
||||
@@ -166,9 +168,15 @@ export function ExpenseFormDialog({ open, onOpenChange, expense }: ExpenseFormDi
|
||||
const json = (await res.json()) as { data: { id: string; filename: string } };
|
||||
setUploadedReceipt({ id: json.data.id, filename: json.data.filename });
|
||||
setNoReceipt(false);
|
||||
// Keep form state in sync so the schema-level refine that requires
|
||||
// receiptFileIds.length > 0 || noReceiptAcknowledged === true sees
|
||||
// a populated value at validation time.
|
||||
setValue('receiptFileIds', [json.data.id], { shouldValidate: true });
|
||||
setValue('noReceiptAcknowledged', false, { shouldValidate: true });
|
||||
} catch (err) {
|
||||
setUploadError(err instanceof Error ? err.message : 'Upload failed');
|
||||
setUploadedReceipt(null);
|
||||
setValue('receiptFileIds', undefined, { shouldValidate: true });
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
@@ -180,6 +188,7 @@ export function ExpenseFormDialog({ open, onOpenChange, expense }: ExpenseFormDi
|
||||
setUploadedReceipt(null);
|
||||
setUploadError(null);
|
||||
if (fileInputRef.current) fileInputRef.current.value = '';
|
||||
setValue('receiptFileIds', undefined, { shouldValidate: true });
|
||||
}
|
||||
|
||||
function onSubmit(data: CreateExpenseInput) {
|
||||
@@ -403,6 +412,7 @@ export function ExpenseFormDialog({ open, onOpenChange, expense }: ExpenseFormDi
|
||||
const next = checked === true;
|
||||
setNoReceipt(next);
|
||||
if (next) clearReceipt();
|
||||
setValue('noReceiptAcknowledged', next, { shouldValidate: true });
|
||||
}}
|
||||
/>
|
||||
<Label htmlFor="noReceipt" className="text-sm font-normal leading-tight">
|
||||
|
||||
Reference in New Issue
Block a user