This repository has been archived on 2026-08-11. You can view files and clone it, but cannot push or open issues or pull requests.
Gishen-B2B/docs/features/portal-layout.md
kirukib a522916ff1 Close remaining portal polish gaps on create and org pages.
Make prescription create full-width, restore organisation breadcrumbs, and document the idCard tabs key.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 21:59:31 +03:00

145 lines
6.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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:** `PageStatSkeleton` while stats query loads; `TablePanel loading` renders row skeletons (toolbar stays).
- **Detail pages:** `DetailLoading` → `PageDetailSkeleton` (metrics + profile/content panels).
- **Dashboard:** `PageDashboardSkeleton`.
- **Forms / settings:** `PageFormSkeleton`.
```tsx
import { PageStatSkeleton, PageDetailSkeleton } from "@/components/layout/page-loading";
{isLoading ? <PageStatSkeleton cols={4} /> : <StatCardGrid>…</StatCardGrid>}
<TablePanel loading={isLoading}>…</TablePanel>
```
## Breadcrumbs API
```tsx
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 in `src/messages/en.json` + `am.json`.
- Path map lives in `PATH_LABEL_KEYS` / `DYNAMIC_PARENT_KEYS` inside `breadcrumbs.tsx`.
- To extend: add a path key → `bc` message 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:
```tsx
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`, white `bg-card`, **no shadow** — do not wrap tables in padded `Card` / `CardContent`.
- **Header row:** muted background, compact uppercase labels (`text-[11px]`, tracking), `px-4`.
- **Body:** comfortable `py-4` rows, mint-tinted hover, last row border removed.
- **Empty / loading:** empty state cell when idle; `TablePanel loading` shows 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 under `filters.*` in i18n. Members, finance spend, and migration use the modal path today.
## Charts & metrics
- **Stat cards:** `overflow-visible` + `break-words` / `tabular-nums` so large ETB figures are not clipped.
- **Charts:** Y-axis uses compact tick formatting (`800K`) with wider left margin; `ChartCard` allows 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.
```tsx
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`, `idCard`, `rules`, `perks`, `summary`, `errors`, `breakdown`, `contract`, `people`, `spend`, `info`, `basics`). Prefer `useTranslations("tabs")` so missing keys never render as nested `tabs.tabs.*`.
## Related docs
- [i18n.md](i18n.md) — message files
- [INDEX.md](INDEX.md) — feature → route map