feat(uat-batch-11): picker polish + BulkAddBerthsWizard currency + DocumentsHub root cleanup
- BulkAddBerthsWizard `priceCurrency` row + apply-to-all swapped from
freetext Input to the shared CurrencySelect. Same idiom as
berth-form + expense-form-dialog.
- /api/v1/yachts/autocomplete no longer short-circuits to `[]` when
the search query is empty — the service returns the top 20
most-recently-updated yachts so the picker has a useful default
view the moment it opens. Saves the rep from a dead-end empty
state.
- YachtPicker gains a fallback useQuery against `/api/v1/yachts/{id}`
when the selected yacht isn't present in the current autocomplete
window. Trigger label now shows the real name (was falling back to
"Yacht <uuid-prefix>" when a parent pre-selected a value from a URL
param).
- DocumentsHub: breadcrumb row only renders when a folder is
selected. The "Home / All documents" placeholder was wasted
vertical space above the PageHeader on the root view.
tsc clean. 1419/1419 vitest pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,7 @@ import {
|
||||
import { apiFetch } from '@/lib/api/client';
|
||||
import { toastError } from '@/lib/api/toast-error';
|
||||
import { useVocabulary } from '@/hooks/use-vocabulary';
|
||||
import { CurrencySelect } from '@/components/shared/currency-select';
|
||||
|
||||
const DOCK_LETTERS = ['A', 'B', 'C', 'D', 'E'] as const;
|
||||
type DockLetter = (typeof DOCK_LETTERS)[number];
|
||||
@@ -283,12 +284,10 @@ export function BulkAddBerthsWizard() {
|
||||
/>
|
||||
</td>
|
||||
<td className="py-1 pr-2">
|
||||
<Input
|
||||
<CurrencySelect
|
||||
value={undefined}
|
||||
onValueChange={(v) => applyToAll('priceCurrency', v)}
|
||||
className="h-7 text-xs"
|
||||
onBlur={(e) => {
|
||||
if (e.target.value) applyToAll('priceCurrency', e.target.value.toUpperCase());
|
||||
}}
|
||||
placeholder="all"
|
||||
/>
|
||||
</td>
|
||||
<td />
|
||||
@@ -351,12 +350,10 @@ export function BulkAddBerthsWizard() {
|
||||
/>
|
||||
</td>
|
||||
<td className="py-1 pr-2">
|
||||
<Input
|
||||
className="h-7 w-20 text-xs"
|
||||
value={row.priceCurrency}
|
||||
onChange={(e) =>
|
||||
setRowField(idx, 'priceCurrency', e.target.value.toUpperCase())
|
||||
}
|
||||
<CurrencySelect
|
||||
value={row.priceCurrency || undefined}
|
||||
onValueChange={(v) => setRowField(idx, 'priceCurrency', v)}
|
||||
className="h-7 w-24 text-xs"
|
||||
/>
|
||||
</td>
|
||||
<td className="py-1 pr-2">
|
||||
|
||||
@@ -193,9 +193,9 @@ export function DocumentsHub({ portSlug }: DocumentsHubProps) {
|
||||
|
||||
const contentPane = (
|
||||
<div className="flex-1 min-w-0 p-4 space-y-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<FolderBreadcrumb selectedFolderId={selectedFolderId} onSelect={handleFolderSelect} />
|
||||
{selectedFolderId !== undefined && (
|
||||
{selectedFolderId !== undefined && (
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<FolderBreadcrumb selectedFolderId={selectedFolderId} onSelect={handleFolderSelect} />
|
||||
<NewDocumentMenu
|
||||
portSlug={portSlug}
|
||||
folderId={selectedFolderId}
|
||||
@@ -205,8 +205,8 @@ export function DocumentsHub({ portSlug }: DocumentsHubProps) {
|
||||
entityId={isEntityFolder ? (selectedFolder!.entityId ?? undefined) : undefined}
|
||||
size="sm"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedFolderId === undefined ? (
|
||||
<>
|
||||
|
||||
@@ -72,10 +72,23 @@ export function YachtPicker({
|
||||
)
|
||||
: rawOptions;
|
||||
|
||||
// When `value` is set but the selected yacht isn't in the current
|
||||
// autocomplete window (e.g. parent pre-selected it from a URL param
|
||||
// or the rep typed something that filtered it out), fetch its name
|
||||
// by id so the trigger doesn't fall back to "Yacht <uuid-prefix>".
|
||||
const fallbackQuery = useQuery<{ data: { name: string } }>({
|
||||
queryKey: ['yacht-detail-label', value],
|
||||
queryFn: () => apiFetch(`/api/v1/yachts/${value}`),
|
||||
enabled: !!value && !rawOptions.some((o) => o.id === value),
|
||||
staleTime: 60_000,
|
||||
});
|
||||
|
||||
const selectedLabel = (() => {
|
||||
if (!value) return placeholder;
|
||||
const match = rawOptions.find((o) => o.id === value);
|
||||
return match?.name ?? `Yacht ${value.slice(0, 8)}`;
|
||||
if (match) return match.name;
|
||||
if (fallbackQuery.data?.data.name) return fallbackQuery.data.data.name;
|
||||
return `Yacht ${value.slice(0, 8)}`;
|
||||
})();
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user