Ship leftover Yimaru login/personas, sidebar sections, filter modal, chart/stat clip fixes, and matching feature docs so the polish work is fully committed. Co-authored-by: Cursor <cursoragent@cursor.com>
6.4 KiB
Portal layout conventions (Yimaru / Fortune)
Shared shell patterns for list, detail, and create pages.
Workspace: /Users/kirukib/Desktop/Yaltopia Project/Gishen-B2B
Components
| Piece | Path | Role |
|---|---|---|
PageShell |
src/components/layout/page-shell.tsx |
Full-width page header + content; optional breadcrumb |
DetailPageLayout |
src/components/layout/detail-layout.tsx |
Detail wrapper; breadcrumbs on by default |
Breadcrumbs / Bc |
src/components/layout/breadcrumbs.tsx |
Fortune-style trail; locale links via @/i18n/routing |
StatCard / StatCardGrid |
src/components/layout/stat-card.tsx |
Flat MakerSys metrics (thin border, brand mint icon well) |
TablePanel |
src/components/table/table-panel.tsx |
Flat white panel (rounded-xl, thin border, no shadow) wrapping toolbar + table |
TableToolbar |
src/components/table/table-toolbar.tsx |
In-panel search / filter strip (hairline bottom) |
DataTable |
src/components/table/data-table.tsx |
Column-driven table inside TablePanel |
| Table primitives | src/components/ui/table.tsx |
Yimaru / Fortune density: muted uppercase headers, px-4 cells, mint hover |
| Page loaders | src/components/layout/page-loading.tsx |
Flat MakerSys skeletons: PageStatSkeleton, PageTableSkeleton, PageListSkeleton, PageDetailSkeleton, PageFormSkeleton, PageDashboardSkeleton |
| Sidebar | src/components/layout/app-sidebar.tsx + src/lib/auth/nav.ts |
Role-filtered nav grouped into sections (nav.sections.*) |
Sidebar sections
Nav items carry a section key. navSectionsForRole(role) returns only non-empty groups for that role.
| Section | Routes |
|---|---|
| Overview | Dashboard |
| Organisation | Organisation, Departments, Verification |
| People | Members, Migration |
| Benefits | Packages |
| Finance | Finance, Approvals, Statements |
| Account | Profile (all roles), Prescriptions, My benefits, My orders |
Expanded sidebar shows muted uppercase micro labels; collapsed uses thin dividers between groups (labels hidden). Role filtering via roles / navForRole is unchanged.
Page loading states
Prefer box skeletons over “Loading…” text so list/detail footprints stay flash-free:
- List pages:
PageStatSkeletonwhile stats query loads;TablePanel loadingrenders row skeletons (toolbar stays). - Detail pages:
DetailLoading→PageDetailSkeleton(metrics + profile/content panels). - Dashboard:
PageDashboardSkeleton. - Forms / settings:
PageFormSkeleton.
import { PageStatSkeleton, PageDetailSkeleton } from "@/components/layout/page-loading";
{isLoading ? <PageStatSkeleton cols={4} /> : <StatCardGrid>…</StatCardGrid>}
<TablePanel loading={isLoading}>…</TablePanel>
Breadcrumbs API
import { Breadcrumbs, Bc } from "@/components/layout/breadcrumbs";
import { PageShell } from "@/components/layout/page-shell";
import { DetailPageLayout } from "@/components/layout/detail-layout";
// Detail — breadcrumbs default true
<DetailPageLayout title="…">…</DetailPageLayout>
// Create / nested — opt in
<PageShell title="…" breadcrumb>…</PageShell>
// Custom trail (tests / one-offs)
<Breadcrumbs items={[{ href: "/", label: "Dashboard" }, { label: "Custom" }]} />
Labels
- Static paths and dynamic parents map to
bc.*keys insrc/messages/en.json+am.json. - Path map lives in
PATH_LABEL_KEYS/DYNAMIC_PARENT_KEYSinsidebreadcrumbs.tsx. - To extend: add a path key →
bcmessage key, then Amharic/English strings.
When to use what
| Surface | Stats | Tabs | Breadcrumbs | Width |
|---|---|---|---|---|
| List dashboards | ✓ metric strip | — | off | full |
| Detail | metrics + sections | shadcn Tabs (line) |
on | full |
| Create / import / nested settings | — | optional | on | full-width form card (no max-w-* unless interaction needs it) |
List tables
Use the shared panel pattern so every list matches MakerSys / Yimaru:
import { TablePanel } from "@/components/table/table-panel";
import { TableToolbar } from "@/components/table/table-toolbar";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
<TablePanel
loading={isLoading}
loadingLabel={t("common.loading")}
toolbar={
<TableToolbar
search={search}
onSearchChange={setSearch}
resultCount={filtered.length}
resultLabel={t("common.results")}
/>
}
>
<Table>…</Table>
</TablePanel>
Conventions:
- Panel:
rounded-xl,border-border, whitebg-card, no shadow — do not wrap tables in paddedCard/CardContent. - Header row: muted background, compact uppercase labels (
text-[11px], tracking),px-4. - Body: comfortable
py-4rows, mint-tinted hover, last row border removed. - Empty / loading: empty state cell when idle;
TablePanel loadingshows row skeletons (not text). - Mobile: horizontal scroll via the table container (
overflow-x-auto). - Detail embeds: prefer
DetailTableSection(same panel chrome) with flush table primitives. - Filters: prefer
filters: TableFilterDef[]. When count > 3 (inlineFilterLimit), toolbar shows a Filters button + modal (draft values, Apply / Reset). ≤3 stay inline. Strings underfilters.*in i18n. Members, finance spend, and migration use the modal path today.
Charts & metrics
- Stat cards:
overflow-visible+break-words/tabular-numsso large ETB figures are not clipped. - Charts: Y-axis uses compact tick formatting (
800K) with wider left margin;ChartCardallows overflow so axis labels are not cut off.
Tabs
Use @/components/ui/tabs. Default TabsList variant is line (Yimaru/Fortune underline). Keep 2–4 sections max.
const tTabs = useTranslations("tabs");
<Tabs defaultValue="profile" className="w-full">
<TabsList>
<TabsTrigger value="profile">{tTabs("profile")}</TabsTrigger>
</TabsList>
<TabsContent value="profile" className="space-y-4 pt-1">…</TabsContent>
</Tabs>
Labels live under root tabs.* in en.json / am.json (profile, entitlement, dependants, clinical, rules, perks, summary, errors, breakdown, contract, people, spend, info, basics). Prefer useTranslations("tabs") so missing keys never render as nested tabs.tabs.*.