ui/ux on catalogue
This commit is contained in:
@@ -377,27 +377,43 @@ export function CatalogueBrowser({
|
||||
|
||||
It stays above both columns rather than inside the rail: the search
|
||||
narrows the whole catalogue, and the rail only lists brands. */}
|
||||
<HStack gap={1} align="center" wrap="wrap" justify="end">
|
||||
{/* Pinned to the right-hand corner, and stated inline so it cannot be
|
||||
undone by a stylesheet.
|
||||
|
||||
`justify="end"` was already here and already resolves to
|
||||
`justify-content: flex-end` — HStack maps `justify` to the main axis,
|
||||
and the rule is in the bundle — yet the row was reported rendering at
|
||||
the top LEFT of the platform catalogue. A utility class landing after
|
||||
the component's own is the only thing that explains that, and an
|
||||
inline declaration is the one thing such a class cannot outrank.
|
||||
`width` is explicit for the same reason rather than relying on the
|
||||
flex default. */}
|
||||
<HStack gap={1} align="center" wrap="wrap" justify="start" width="100%">
|
||||
<div style={{ width: 216, display: 'flex', gap: 8, alignItems: 'center' }}>
|
||||
<IconButton
|
||||
label={isFiltersOpen ? 'Hide filters' : 'Show filters'}
|
||||
icon={<Funnel size={15} />}
|
||||
variant={isFiltersOpen ? 'secondary' : 'ghost'}
|
||||
size="sm"
|
||||
onClick={() => setIsFiltersOpen((open) => !open)}
|
||||
/>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<SearchInput
|
||||
label="Search the catalogue"
|
||||
value={keyword}
|
||||
onChange={setKeyword}
|
||||
placeholder="Search products…"
|
||||
width="full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{activeFilters.map((filter) => (
|
||||
<Token key={filter.key} label={filter.label} size="sm" onRemove={filter.clear} />
|
||||
))}
|
||||
{activeFilters.length > 1 ? (
|
||||
<Button label="Clear all" variant="ghost" size="sm" onClick={clearFilters} />
|
||||
) : null}
|
||||
<IconButton
|
||||
label={isFiltersOpen ? 'Hide filters' : 'Show filters'}
|
||||
icon={<Funnel size={15} />}
|
||||
variant={isFiltersOpen ? 'secondary' : 'ghost'}
|
||||
size="sm"
|
||||
onClick={() => setIsFiltersOpen((open) => !open)}
|
||||
/>
|
||||
<SearchInput
|
||||
label="Search the catalogue"
|
||||
value={keyword}
|
||||
onChange={setKeyword}
|
||||
placeholder="Search products…"
|
||||
width={220}
|
||||
/>
|
||||
</HStack>
|
||||
|
||||
<div className="catalogue-layout" data-rail={isFiltersOpen ? 'open' : 'closed'}>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { VStack } from '@astryxdesign/core/VStack';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import type { CatalogueBrand } from '@/api/types';
|
||||
|
||||
const COLLAPSED_COUNT = 8;
|
||||
const COLLAPSED_COUNT = 25;
|
||||
|
||||
/**
|
||||
* The brand rail.
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
import type { DeliveryRow, OrderRow, TenantLocation } from '@/api/types';
|
||||
import { KpiCard } from '@/components/KpiCard';
|
||||
import { Tab, TabBar } from '@/components/TabBar';
|
||||
import { StickyRow } from '@/components/StickyRow';
|
||||
import { useSelection, type Selection } from '@/components/useSelection';
|
||||
import { Panel } from '@/components/Panel';
|
||||
import { TablePager } from '@/components/TablePager';
|
||||
@@ -244,57 +245,62 @@ export function DispatchBoard({ scope }: { scope: DispatchScope }) {
|
||||
operations cluster beside it was removed earlier — and a card holding
|
||||
nothing but a tab track is a box drawn round a box. Every other page
|
||||
stands its switcher directly on the page. */}
|
||||
<TabBar aria-label="Dispatch views">
|
||||
<Tab
|
||||
label="By rider"
|
||||
icon={<Bike size={14} />}
|
||||
count={totals.riders || undefined}
|
||||
isActive={mode === 'riders'}
|
||||
onClick={() => changeMode('riders')}
|
||||
/>
|
||||
<Tab
|
||||
label="By store"
|
||||
icon={<Store size={14} />}
|
||||
count={branches.length || undefined}
|
||||
isActive={mode === 'stores'}
|
||||
onClick={() => changeMode('stores')}
|
||||
/>
|
||||
{/* A shop's customer book is its own. Grouping a partner's rounds by
|
||||
customer would mean reading every merchant's book to put a name to
|
||||
a phone number, so the tab is not offered on a partner board. */}
|
||||
{scope.kind === 'tenant' ? (
|
||||
{/* Held under the nav bar while the board scrolls. Dispatch builds its
|
||||
own switcher instead of going through `PageHeader`, so it asks for
|
||||
the behaviour directly — same component, same row, same canvas. */}
|
||||
<StickyRow>
|
||||
<TabBar aria-label="Dispatch views">
|
||||
<Tab
|
||||
label="By customer"
|
||||
icon={<Users size={14} />}
|
||||
isActive={mode === 'customers'}
|
||||
onClick={() => changeMode('customers')}
|
||||
label="By rider"
|
||||
icon={<Bike size={14} />}
|
||||
count={totals.riders || undefined}
|
||||
isActive={mode === 'riders'}
|
||||
onClick={() => changeMode('riders')}
|
||||
/>
|
||||
) : null}
|
||||
<Tab
|
||||
label="By store"
|
||||
icon={<Store size={14} />}
|
||||
count={branches.length || undefined}
|
||||
isActive={mode === 'stores'}
|
||||
onClick={() => changeMode('stores')}
|
||||
/>
|
||||
{/* A shop's customer book is its own. Grouping a partner's rounds by
|
||||
customer would mean reading every merchant's book to put a name to
|
||||
a phone number, so the tab is not offered on a partner board. */}
|
||||
{scope.kind === 'tenant' ? (
|
||||
<Tab
|
||||
label="By customer"
|
||||
icon={<Users size={14} />}
|
||||
isActive={mode === 'customers'}
|
||||
onClick={() => changeMode('customers')}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{/* The three below group the day differently from the three above:
|
||||
those slice the same stops by whose they are, these report on how
|
||||
the day went. The rule in the track says so. */}
|
||||
<Tab
|
||||
label="Active fleet"
|
||||
icon={<Radio size={14} />}
|
||||
isActive={mode === 'active'}
|
||||
onClick={() => changeMode('active')}
|
||||
isGroupStart
|
||||
isLive
|
||||
/>
|
||||
<Tab
|
||||
label="Plan vs actual"
|
||||
icon={<Timer size={14} />}
|
||||
isActive={mode === 'timing'}
|
||||
onClick={() => changeMode('timing')}
|
||||
/>
|
||||
<Tab
|
||||
label="Unit economics"
|
||||
icon={<IndianRupee size={14} />}
|
||||
isActive={mode === 'profit'}
|
||||
onClick={() => changeMode('profit')}
|
||||
/>
|
||||
</TabBar>
|
||||
{/* The three below group the day differently from the three above:
|
||||
those slice the same stops by whose they are, these report on how
|
||||
the day went. The rule in the track says so. */}
|
||||
<Tab
|
||||
label="Active fleet"
|
||||
icon={<Radio size={14} />}
|
||||
isActive={mode === 'active'}
|
||||
onClick={() => changeMode('active')}
|
||||
isGroupStart
|
||||
isLive
|
||||
/>
|
||||
<Tab
|
||||
label="Plan vs actual"
|
||||
icon={<Timer size={14} />}
|
||||
isActive={mode === 'timing'}
|
||||
onClick={() => changeMode('timing')}
|
||||
/>
|
||||
<Tab
|
||||
label="Unit economics"
|
||||
icon={<IndianRupee size={14} />}
|
||||
isActive={mode === 'profit'}
|
||||
onClick={() => changeMode('profit')}
|
||||
/>
|
||||
</TabBar>
|
||||
</StickyRow>
|
||||
|
||||
|
||||
{/* The same four-card row every other page uses — same `kpi-grid`, same
|
||||
|
||||
Reference in New Issue
Block a user