Home dashboard design (Figma batch 2, project J part 1)
No dedicated 首页/Home frame exists in file xjx0pz8JSVeCqP1kYg3elT. Checked both pages
get_metadata gives: “Final Design” (77:4027) — only the “我的报销” (claims) symbol/instances,
no home/dashboard frame or text. “In Progress” (83:5479) — the only match for
Home/Dashboard/首页/仪表 is a bottom-nav-bar label node 94:27807 reading “Dashboard” (part of a
390-wide mobile mock’s tab bar, next to Project/Claims/Activity tabs), confirming the icon exists
in the design system but no dashboard content was ever designed. This dashboard is therefore
composed from existing components in the batch 1 visual language, per the fallback instruction.
Nav item
Section titled “Nav item”Added 首页/Home as the first workspace item in web/src/app/shell/nav.ts, LayoutGrid icon
(lucide’s 4-squares glyph, matching the audit’s “4-squares (首页)” description), route /home,
no permission (open to every signed-in member — hasPermission treats undefined as always
allowed). Because navItems is scanned in order by auth/HomePage.tsx to find the first item a
member can use, Home — needing no permission — is now always that first item, so / (and thus
the post-login redirect, and any deep link’s next that still resolves through it) lands on
/home for every member. No change was needed to the redirect mechanism itself: HomePage.tsx
already picks “first nav item the member can use” and LoginPage’s safeNext already preserves
a next deep link past this redirect.
Composition (existing endpoints only)
Section titled “Composition (existing endpoints only)”Each section is its own permission-gated block; a member missing every permission below sees an
empty state instead (mirrors auth:home.noModulesTitle’s wording/pattern, own home:empty.* key
so an existing screen’s copy isn’t duplicated across features).
| Section | Permission | Data source (existing hook/endpoint) | Links to |
|---|---|---|---|
| My claims | expenses.claim.read |
useClaimSummary({ scope: 'mine' }) — same 4 KPI buckets (pending/approved/paid/submitted) as ClaimsListPage |
/claims |
| Waiting for you | any of expenses.claim.read, advances.advance.read, pettycash.read |
useApprovalQueues(true) — the same four to-act queues the sidebar badge and ApprovalsPage already poll (60s/focus); each of the 4 kinds shown only under its own permission |
/approvals?kind=<kind> per kind, i.e. exactly where each is acted on |
| Petty cash float | pettycash.read, and only if useFloats('mine') returns a non-empty float (member holds one) |
first non-closed float from useFloats('mine') |
/petty-cash |
| Open advances | advances.advance.read |
useAdvanceSummary({ scope: 'mine' })’s paid bucket (server-documented as payout + acknowledged — money currently with the claimant, not yet acquitted; the closest existing meaning of “open”) |
/advances |
| Recent activity | activity.read |
GET /activity directly with limit=5 (a thin useRecentActivityFeed wrapper in features/home/api.ts; deliberately not useActivityFeed, which is hardcoded to page size 50 for the full feed) |
/activity |
No backend change: every card is served by an endpoint the app already calls elsewhere.
Visual language
Section titled “Visual language”Reuses StatCard (radius-20 soft-tint KPI tiles) and Card/PageHeader/EmptyState/Skeleton
exactly as the claims/petty-cash/advances pages already do — no new primitives. Each section is a
Card with a title and a “view all” link (useNavigate({ to }), the same cross-feature
navigation pattern AccountingLayout and LoginPage/session.tsx already use); the “Waiting for
you” tiles are themselves clickable (StatCard’s existing onClick) since each one’s target
differs by kind. Responsive: sections stack in a single column on mobile (390) and lay out 2/4-up
grids at md/lg, same breakpoints ClaimsListPage’s KPI row already uses. Light/dark and i18n
(en/ms/zh) come for free from the shared StatCard/tone tokens and i18n plumbing; new copy lives
in a new home namespace, reusing approvals:chips.* for the four waiting-for-you labels and
pettycash:kpi.cash/kpi.available for the float card’s two figures instead of duplicating them.
web/src/app/shell/nav.test.ts: Home is first in the workspace group and needs no permission.web/src/features/home/HomePage.test.tsx: each section renders only under its permission (presence/absence per section), the float card additionally needs a held float, and the all-permissions-missing case shows the empty state.web/src/features/auth/HomePage.test.tsx:/redirects to/homeregardless of the member’s permissions, since Home needs none and is first in the list. Deep links themselves are untouched:LoginPage’ssafeNext(next)navigates straight tonextand never routes throughHomePage, so a link into a specific claim/approval keeps working exactly as before.