Skip to content

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.

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.

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.

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 /home regardless of the member’s permissions, since Home needs none and is first in the list. Deep links themselves are untouched: LoginPage’s safeNext(next) navigates straight to next and never routes through HomePage, so a link into a specific claim/approval keeps working exactly as before.