fix(uat): prod UAT batch — reports, sidebar, search, berths, breakpoint
- financial report: drop Expenses KPI, Net Contribution, cash-flow chart, expense donut + ledger (expenses are business-trip costs, not net contribution) - dashboard report PDF: pagination-safe tables (TableSection + per-row wrap) so long doc lists no longer overlap/crush - clients PDF report: rename "Nationality" -> "Country" - sidebar: hide a section header when all its items gate off (FINANCIAL orphan) - topbar: move global search into the 1fr grid track so it can't overlap "New" - clients card: show all linked berths (not just latest interest's primary) - berths list: hide table-only toggles (ft/m, density, columns) in card mode - lists: lower table/card breakpoint lg -> md so narrow desktops get tables - alert-rules: stale floor created_at -> updated_at (survives created_at backfill) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -402,6 +402,20 @@ function SidebarContent({
|
||||
if (section.requiresResidentialModule && !residentialModuleEnabled) return null;
|
||||
if (section.umamiRequired && !umamiConfigured) return null;
|
||||
|
||||
// Resolve the items this section will actually render after
|
||||
// per-item module/permission gating. If they all gate off
|
||||
// (e.g. the Financial section once the Expenses module is
|
||||
// disabled), skip the whole section so its header + separator
|
||||
// don't linger as an orphaned label.
|
||||
const visibleItems = section.items.filter((item) => {
|
||||
const gated = item as NavItemGated;
|
||||
if (gated.requiresTenanciesModule && !tenanciesModuleEnabled) return false;
|
||||
if (gated.requiresExpensesModule && !expensesModuleEnabled) return false;
|
||||
if (gated.umamiRequired && !umamiConfigured) return false;
|
||||
return true;
|
||||
});
|
||||
if (visibleItems.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div key={section.title}>
|
||||
{!collapsed && (
|
||||
@@ -425,24 +439,15 @@ function SidebarContent({
|
||||
)}
|
||||
{(!section.adminRequired || adminExpanded || collapsed) && (
|
||||
<ul className="space-y-0.5">
|
||||
{section.items
|
||||
.filter((item) => {
|
||||
const gated = item as NavItemGated;
|
||||
if (gated.requiresTenanciesModule && !tenanciesModuleEnabled)
|
||||
return false;
|
||||
if (gated.requiresExpensesModule && !expensesModuleEnabled) return false;
|
||||
if (gated.umamiRequired && !umamiConfigured) return false;
|
||||
return true;
|
||||
})
|
||||
.map((item) => (
|
||||
<li key={item.href}>
|
||||
<NavItemLink
|
||||
item={item}
|
||||
collapsed={collapsed}
|
||||
active={isActive(item.href, item.exact)}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
{visibleItems.map((item) => (
|
||||
<li key={item.href}>
|
||||
<NavItemLink
|
||||
item={item}
|
||||
collapsed={collapsed}
|
||||
active={isActive(item.href, item.exact)}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
<Separator className="mt-3 bg-slate-200" aria-hidden />
|
||||
|
||||
@@ -62,33 +62,18 @@ export function Topbar({ ports, user, leadingSlot }: TopbarProps) {
|
||||
<BackButton variant="desktop" />
|
||||
</div>
|
||||
|
||||
{/* CENTER (spacer): the search bar is absolutely positioned below
|
||||
so it anchors to true viewport center regardless of left/right
|
||||
column widths. This empty grid track keeps `auto 1fr auto` so
|
||||
the right column behaves the same as before. */}
|
||||
<div aria-hidden />
|
||||
|
||||
{/* CENTER: global search, anchored to true viewport center.
|
||||
The topbar element starts AFTER the 256px sidebar at lg+, so
|
||||
`left: 50%` of the topbar lands sidebar/2 (=128px) right of the
|
||||
viewport center. We subtract that offset at lg+ so the search
|
||||
bar sits under the browser address bar; below lg the sidebar
|
||||
is hidden behind a Sheet and the topbar spans the full
|
||||
viewport, so plain `left: 50%` is already correct.
|
||||
|
||||
Caps scale by viewport tier so the bar doesn't crowd the side
|
||||
columns. The previous max-w-2xl (672px) at xl ate so much of
|
||||
the topbar that the back-button column on the left got
|
||||
visually clipped by the search bar; tightened to max-w-xl so
|
||||
a "Back to Administration"-class label can render in full:
|
||||
base: max-w-md (28rem)
|
||||
lg: max-w-lg (32rem)
|
||||
xl: max-w-xl (36rem)
|
||||
The wrapper is pointer-events-none so it doesn't capture
|
||||
clicks meant for the left/right columns underneath; only the
|
||||
input itself receives pointer events. */}
|
||||
<div className="pointer-events-none absolute inset-y-0 left-1/2 lg:left-[calc(50%-var(--width-sidebar)/2)] flex w-full max-w-md -translate-x-1/2 items-center px-4 lg:max-w-lg xl:max-w-xl">
|
||||
<div className="pointer-events-auto w-full min-w-0">
|
||||
{/* CENTER: global search lives IN the 1fr grid track so it is
|
||||
bounded by the left (back-button) and right (actions) columns
|
||||
and can never overlap them. The previous approach absolutely
|
||||
positioned the bar at viewport-center, which ignored the side
|
||||
columns and crowded the "New" button at narrower widths
|
||||
(UAT 2026-06-03). `mx-auto` keeps it visually centered within
|
||||
the available middle space; `max-w-xl` stops it sprawling on
|
||||
wide screens; `min-w-0` lets it shrink rather than push the
|
||||
side columns. The grid `gap-3` guarantees breathing room from
|
||||
both neighbours. */}
|
||||
<div className="min-w-0 px-2">
|
||||
<div className="mx-auto w-full min-w-0 max-w-xl">
|
||||
<CommandSearch />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user