- TypeScript 87.7%
- JavaScript 10.7%
- CSS 1.3%
- HTML 0.2%
- Dockerfile 0.1%
| .forgejo/workflows | ||
| public | ||
| scripts | ||
| src | ||
| .dockerignore | ||
| .gitignore | ||
| CLAUDE.md | ||
| Dockerfile | ||
| eslint.config.js | ||
| index.html | ||
| nginx.conf | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.app.json | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| vite.config.ts | ||
EduOne by Hasky — NUH Medical Affairs Panel Demo
Clickable prototype submitted alongside Hasky Technologies' proposal for the IMDA Open Innovation Platform Call 28: Enabling Unified Clinical Education Operations Through an Integrated Digital Platform (NUH Medical Affairs).
This is a frontend-only demo with all data hardcoded as TypeScript fixtures. Every screen is anchored to a specific provision in the NUH challenge brief — see the BriefAnchor callouts in each route for the verbatim quotes.
What's in the demo
Four end-to-end user journeys, all reading from a single shared cohort fixture (Cardiology AY2026 Q3, 12 students, 5 tutors):
| Route | Persona | Brief alignment |
|---|---|---|
/admin |
Sarah — Allied Health Education admin | Pain points 1, 2, 5 + Solution Reqs 1, 2, 6, 10 |
/tutor |
Dr Aisha — Cardiology consultant (mobile-first) | Pain points 3, 4 + Solution Req 2 |
/student |
Alice Lim — NUS year-3 medical student | Pain point 6 + Metric: 30% survey lift |
/head |
Bryan — Head of Medical Affairs | Pain points 4, 5 + Solution Req 8 |
The role switcher in the top-right of every page lets the panel jump between personas without logging in.
Run locally
npm install
npm run dev
Open whichever port Vite picks (defaults to 5173, may bump if in use).
Capture screenshots
EDUONE_URL=http://localhost:5176 node scripts/capture-screenshots.mjs
Walks all 4 journeys, asserts no console/runtime errors, and writes 13 PNGs to /tmp/eduone-screens/.
Stack
- Vite + React 19 + TypeScript (per Hasky's supported stacks in
SDLC-04-build) - Tailwind v4 with the
enterprise-neutraltemplate (hasky-projects/styling/templates/enterprise-neutral) — the data-first neutral primary, status-only color, ring-borders signature - shadcn-style primitives (Button, Card, Dialog, Badge, Dropdown, Tooltip) hand-authored using Radix + cva
- Recharts for dashboards
@react-pdf/rendererfor Sarah's tutor billing PDF (real client-side download)- Zustand for the shared cohort store
Storytelling principles
Every demo screen follows these (from the validation pass in the execution plan):
- Echo the brief's exact words —
BriefAnchorcallouts surface a verbatim quote next to every screen, with a "Brief: Pain Point #X" or "Brief: Solution Req #N" label. - Quantify against the brief's own targets —
MetricCalloutchips show "Brief target: 5 min → 1 min · We're at 1.2s" wherever a published target is hit. - Anchor every claim to a fragment — every "AI" output (Bryan's chat-to-report, the student extraction) shows the source quote it was derived from. We never claim something the data doesn't support.
- Show the join, don't just say it — every screen reads from one shared cohort fixture. That's the proof for "disconnected and manual data management" being solved.
Repository layout
src/
├── components/
│ ├── BriefAnchor.tsx ← the storytelling primitive
│ ├── Shell.tsx ← topbar + role switcher + sidebar
│ └── ui/ ← shadcn-style primitives
├── fixtures/
│ ├── cardiology-q3.ts ← THE source of truth: cohort, tutors, sessions, anomalies, reflections
│ ├── anti-fatigue-output.ts ← pre-baked LLM extractions from the reflections
│ └── chat-to-report-responses.ts ← pre-baked Bryan chat answers + fuzzy matcher
├── lib/
│ ├── billing-pdf.tsx ← @react-pdf/renderer document for Sarah's billing report
│ └── utils.ts
├── routes/
│ ├── admin/
│ │ ├── CohortListPage.tsx
│ │ ├── CohortDetailPage.tsx
│ │ └── ImportDialog.tsx
│ ├── tutor/
│ │ ├── TutorDashboardPage.tsx
│ │ └── LogSessionDialog.tsx
│ ├── student/
│ │ └── ReflectionPage.tsx
│ └── head/
│ ├── HeadDashboardPage.tsx
│ └── DrillDownSheet.tsx
└── store/
└── cohort-store.ts ← Zustand
Branch structure
main is the early baseline (the Vite scaffold). All active work lives on the dev branch — push commits directly to origin/dev, never to origin/main.
Related artefacts (not in this repo)
- Proposal PDF and supporting docs:
hasky-projects/NUH-challenge/ - Execution plan for this build:
~/.claude/plans/hidden-churning-meadow.md - Challenge brief:
~/Downloads/Challenge Brief_NUH (Medical Affairs)*.pdf