fix(ui): mobile + dashboard polish + dev CSRF relaxation
- filter-bar: hide select / multi-select fields when the options list is
empty (was rendering bare "Tags" / "Status" labels above empty inputs)
- berth-detail-header: show "Berth A1" title on mobile (was hidden via
`hidden sm:block`)
- dashboard-shell: time-aware greeting (Good morning/afternoon/evening,
firstName) using the existing ['me'] cache; falls back to
"Welcome back" when firstName isn't set yet
- mobile-topbar: hide UUID-segment fallback title flash on detail-page
navigation — when the URL last segment is a UUID, walk up to the
parent collection name ("Clients", "Yachts") until the page sets the
real entity title via useMobileChrome
- mobile-bottom-tabs: subtle bg-primary/10 pill behind icon on active
tab for a clear "you are here" cue
- branded-auth-shell: lock to viewport via fixed/inset-0 so the iOS
Safari rubber-band bounce doesn't scroll the centered login card
- middleware: skip CSRF origin check in development. LAN testing
(real iPhone on 192.168.x.x hitting the Mac dev server while a Mac
browser tab is on localhost) trips the cross-origin defense; prod
keeps it as-is.
- package.json dev script: -H 0.0.0.0 so the dev server is reachable
from devices on the LAN
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -188,6 +188,10 @@ function FilterField({
|
||||
// Radix Select forbids empty-string item values (throws at render
|
||||
// time, crashes the page). Use a sentinel and translate.
|
||||
const ANY = '__any__';
|
||||
// Hide the field entirely when there's nothing to pick — avoids a
|
||||
// bare "Status" / "Stage" label sitting above an empty dropdown
|
||||
// before the parent has loaded options.
|
||||
if (!definition.options || definition.options.length === 0) return null;
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs">{definition.label}</Label>
|
||||
@@ -211,12 +215,17 @@ function FilterField({
|
||||
);
|
||||
}
|
||||
|
||||
case 'multi-select':
|
||||
case 'multi-select': {
|
||||
// Hide the entire field — label + checkbox list — when there's
|
||||
// nothing to filter by. Tags/segments/etc. that aren't configured
|
||||
// yet would otherwise show as a "Tags" header with an empty box
|
||||
// beneath it.
|
||||
if (!definition.options || definition.options.length === 0) return null;
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs">{definition.label}</Label>
|
||||
<div className="max-h-32 overflow-y-auto space-y-1">
|
||||
{definition.options?.map((opt) => {
|
||||
{definition.options.map((opt) => {
|
||||
const selected = Array.isArray(value) ? value.includes(opt.value) : false;
|
||||
return (
|
||||
<div key={opt.value} className="flex items-center gap-2">
|
||||
@@ -243,6 +252,7 @@ function FilterField({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
case 'boolean':
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user