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:
@@ -94,13 +94,25 @@ export async function uploadFile(
|
||||
sizeBytes: normalizedSize,
|
||||
});
|
||||
|
||||
// Derive the entity FK from (entityType, entityId) when the caller
|
||||
// didn't pass it explicitly. Without this, an interest-tab upload that
|
||||
// sets `entityType='client'` + `entityId=<UUID>` lands with
|
||||
// `client_id=NULL` — the Attachments list filters on `clientId` and
|
||||
// the file vanishes from the interest's Documents tab.
|
||||
const derivedClientId =
|
||||
data.clientId ?? (data.entityType === 'client' ? (data.entityId ?? null) : null);
|
||||
const derivedCompanyId =
|
||||
data.companyId ?? (data.entityType === 'company' ? (data.entityId ?? null) : null);
|
||||
const derivedYachtId =
|
||||
data.yachtId ?? (data.entityType === 'yacht' ? (data.entityId ?? null) : null);
|
||||
|
||||
// E8: auto-set entity FK from system-managed folder when the rep uploads
|
||||
// directly into a client/company/yacht folder. No-op for non-system folders.
|
||||
const enrichedValues = await applyEntityFkFromFolder(portId, {
|
||||
portId,
|
||||
clientId: data.clientId ?? null,
|
||||
yachtId: data.yachtId ?? null,
|
||||
companyId: data.companyId ?? null,
|
||||
clientId: derivedClientId,
|
||||
yachtId: derivedYachtId,
|
||||
companyId: derivedCompanyId,
|
||||
folderId: data.folderId ?? null,
|
||||
filename: sanitizedFilename,
|
||||
originalName: sanitizedOriginal,
|
||||
|
||||
@@ -89,7 +89,15 @@ export const NAV_CATALOG: NavCatalogEntry[] = [
|
||||
href: '/:portSlug/admin/templates',
|
||||
label: 'Document templates',
|
||||
category: 'settings',
|
||||
keywords: ['eoi', 'documenso', 'pdf templates', 'template merge fields'],
|
||||
keywords: [
|
||||
'eoi',
|
||||
'documenso',
|
||||
'pdf templates',
|
||||
'email templates',
|
||||
'template merge fields',
|
||||
'merge fields',
|
||||
'eoi template',
|
||||
],
|
||||
requires: 'admin.manage_settings',
|
||||
},
|
||||
{
|
||||
@@ -271,13 +279,6 @@ export const NAV_CATALOG: NavCatalogEntry[] = [
|
||||
keywords: ['form templates', 'inquiry', 'intake', 'public form'],
|
||||
requires: 'admin.manage_forms',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/templates',
|
||||
label: 'Document templates',
|
||||
category: 'admin',
|
||||
keywords: ['pdf templates', 'email templates', 'merge fields', 'eoi template'],
|
||||
requires: 'admin.manage_settings',
|
||||
},
|
||||
{
|
||||
href: '/:portSlug/admin/email-templates',
|
||||
label: 'Email templates',
|
||||
@@ -425,7 +426,7 @@ export function searchNavCatalog(
|
||||
if (q.length === 0) return [];
|
||||
|
||||
const limit = opts.limit ?? 5;
|
||||
const out: Array<NavCatalogEntry & { score: number }> = [];
|
||||
const byHref = new Map<string, NavCatalogEntry & { score: number }>();
|
||||
|
||||
for (const entry of NAV_CATALOG) {
|
||||
if (entry.superAdminOnly && !opts.isSuperAdmin) continue;
|
||||
@@ -434,9 +435,20 @@ export function searchNavCatalog(
|
||||
}
|
||||
|
||||
const score = scoreEntry(q, entry);
|
||||
if (score > 0) out.push({ ...entry, score });
|
||||
if (score === 0) continue;
|
||||
|
||||
// Some hrefs intentionally appear in multiple catalog categories
|
||||
// (e.g. /admin/templates lives under both 'settings' and 'admin').
|
||||
// Keep the highest-scoring variant so the dropdown never renders
|
||||
// two rows with the same `id` (href) — React would otherwise warn
|
||||
// about duplicate keys.
|
||||
const existing = byHref.get(entry.href);
|
||||
if (!existing || score > existing.score) {
|
||||
byHref.set(entry.href, { ...entry, score });
|
||||
}
|
||||
}
|
||||
|
||||
const out = Array.from(byHref.values());
|
||||
out.sort((a, b) => b.score - a.score);
|
||||
return out.slice(0, limit);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user