EduOne clickable demo — NUH Medical Affairs IMDA Open Innovation Platform challenge (Call 28)
  • TypeScript 87.7%
  • JavaScript 10.7%
  • CSS 1.3%
  • HTML 0.2%
  • Dockerfile 0.1%
Find a file
2026-06-28 14:22:17 +00:00
.forgejo/workflows fix: use correct Coolify deploy API endpoint [skip cd] (#5) 2026-06-28 14:22:17 +00:00
public Initial: EduOne panel demo build 2026-04-14 17:15:47 +08:00
scripts Initial: EduOne panel demo build 2026-04-14 17:15:47 +08:00
src feat: client-side password gate + docker/nginx deploy setup 2026-04-15 23:44:54 +08:00
.dockerignore feat: client-side password gate + docker/nginx deploy setup 2026-04-15 23:44:54 +08:00
.gitignore Initial: EduOne panel demo build 2026-04-14 17:15:47 +08:00
CLAUDE.md docs: add CLAUDE.md pointing at notes project overlay 2026-04-15 23:46:28 +08:00
Dockerfile feat: client-side password gate + docker/nginx deploy setup 2026-04-15 23:44:54 +08:00
eslint.config.js Initial: EduOne panel demo build 2026-04-14 17:15:47 +08:00
index.html Initial: EduOne panel demo build 2026-04-14 17:15:47 +08:00
nginx.conf feat: client-side password gate + docker/nginx deploy setup 2026-04-15 23:44:54 +08:00
package-lock.json Initial: EduOne panel demo build 2026-04-14 17:15:47 +08:00
package.json Initial: EduOne panel demo build 2026-04-14 17:15:47 +08:00
README.md Initial: EduOne panel demo build 2026-04-14 17:15:47 +08:00
tsconfig.app.json Initial: EduOne panel demo build 2026-04-14 17:15:47 +08:00
tsconfig.json Initial: EduOne panel demo build 2026-04-14 17:15:47 +08:00
tsconfig.node.json Initial: EduOne panel demo build 2026-04-14 17:15:47 +08:00
vite.config.ts Initial: EduOne panel demo build 2026-04-14 17:15:47 +08:00

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-neutral template (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/renderer for 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):

  1. Echo the brief's exact wordsBriefAnchor callouts surface a verbatim quote next to every screen, with a "Brief: Pain Point #X" or "Brief: Solution Req #N" label.
  2. Quantify against the brief's own targetsMetricCallout chips show "Brief target: 5 min → 1 min · We're at 1.2s" wherever a published target is hit.
  3. 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.
  4. 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.

  • 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