4.9 KiB
KROW Workforce — redesign demo
A complete redesign prototype of the KROW Workforce product with Owliver, the workforce-intelligence panel, built in.
Everything is local. There is no backend, API, database, authentication or outbound network call — all figures come from a single typed mock-data module.
Running it
npm install
npm run dev # http://localhost:5173
npm run build # production build
npm run typecheck # tsc --noEmit
npm run lint # oxlint
Stack
Vite · React 19 · TypeScript · Tailwind CSS v4 · shadcn/ui-style components on Radix · Lucide · Recharts · TanStack Table v9 · React Router.
Layout
One CSS grid (.app-shell in src/index.css):
┌──────────────────────────────────────────────┐
│ sidebar │ GLOBAL HEADER (64px) │
│ (full ├────────────────────┬─────────────┤
│ height) │ MAIN CONTENT │ OWLIVER │
└───────────┴────────────────────┴─────────────┘
The header spans main + Owliver only; the sidebar owns the full viewport height and all navigation. Owliver sits in its own rail on row 2, so it can never overlap or be clipped by the header.
Breakpoints — desktop ≥1280: all three columns. Tablet 768–1279: icon rail plus main, Owliver becomes an overlay. Mobile <768: sidebar becomes a drawer (with the complete navigation, nothing removed), Owliver a bottom sheet.
Structure
src/
config/navigation.ts information architecture (mirrors the vendor workspace)
data/ types, mockData, selectors, store
owliver/ block types, response engine, block renderers
components/shell/ AppShell, Sidebar, TopHeader
components/owliver/ panel + composer
components/shared/ MetricCard, DataTable, Page, Toolbar, StatusBadge
components/ui/ Radix-based primitives
pages/ the fourteen product pages
src/data/mockData.ts is the single source of truth and selectors.ts derives
every computed figure, so a number on a KPI card and the same number quoted by
Owliver cannot disagree.
Home
Home is an AI workspace rather than a dashboard. The hierarchy is greeting → central Owliver card → quick actions → compact metrics → Today.
Performance content (utilisation, trends, attendance, performance health) deliberately does not appear on Home — it lives on the Performance page, which remains complete.
Owliver
One Owliver per page, never two. Home shows the central card and no right rail; every other page shows the right rail and no central card. On Home the grid drops to two columns, so main reflows into the space rather than leaving an empty rail behind — and the rail stays hidden on Home even if it was left open elsewhere.
That means the central card owns the whole interaction on Home: it holds the conversation, the composer and the suggestions, and an answer appears inside that thread. Replies are typed block lists rendered as real components — text, metric cards, alerts, tables, charts, a staff-allocation flow diagram, recommendations, expandable details and working action buttons.
Home's thread starts empty (an invitation to ask). Every other page opens with a greeting, its own brief and one seeded exchange, so the You / Owliver distinction is legible straight away.
State lives in OwliverProvider, above the panel, which gives two properties
the panel could not provide on its own:
- History survives closing. Collapsing the rail, or the tablet/mobile sheet unmounting, no longer discards the conversation.
- Each page keeps its own thread. Navigating switches context instead of leaving Home's answers on the Payroll page; returning to a page restores what was asked there.
Responses are deterministic and delayed 500–900 ms behind a typing indicator. Resolve Understaffed Shifts mutates the shared data, so the greeting, the metrics and the sidebar badges all update behind the panel.
Shell controls
The sidebar collapses to a 72px icon rail (labels become tooltips; active state
and badges are preserved) and Owliver closes so main reclaims its column. Both
are driven by data-sidebar / data-owliver attributes on the grid container,
so the layout reflows rather than overlaying.
Notes for future work
Two bugs worth remembering, both fixed here:
- Passing a fresh
state/initialStateobject to TanStack Table v9'suseTableon every render makes its store notify → re-render → notify. Both are memoised inDataTable.tsx. - Loading the whole app in one chunk blocked first paint long enough to look
like a hang on heavier routes. Pages are lazy-loaded in
App.tsx.