ClinicARE
A hospital runs on lists — patients, beds, appointments, invoices. ClinicARE is the admin system that keeps all four in one place, fast enough to use during a shift.
- 11
- screens shipped
- 9
- domain modules
- 920
- seeded records
- 2
- UI languages
Dashboard · light theme · the interface ships in Russian and Uzbek
Built to be judged on the boring parts
Anyone can render a chart. Clinic software lives or dies on the fifteenth minute of use: filtering 150 patients, sharing a filtered view with a colleague, staying logged in through a long shift.
Problem
Small clinics track admissions in spreadsheets. Records get duplicated, bed availability is a phone call, and nobody can say how many patients came in last month without counting rows by hand.
Approach
One register per entity, one table component behind all of them, and every list view addressable by URL — so a filtered screen is a link you can paste into a chat.
Outcome
Nine modules and eleven screens running on a mock API with 920 seeded records, so the demo behaves like production: real pagination, real sorting, real latency.
Shareable state
The URL is the table's memory
Page, page size, sort column, sort direction, search text and every column filter live in the query string — never in component state. Reload the tab and the view survives; send the link and your colleague sees the same rows. One useDataTableQuery hook drives all nine tables.
Patient register · the same table engine renders all nine modules
Modules
Nine registers, one grammar
Every module reads the same way: a header with the primary action, a filterable table, a create/edit modal and a delete confirmation. Learn one screen and you know all nine.
Dashboard
Four live counters, a patient curve switchable between weekly, monthly and yearly, today's appointments and the recent patient register.
Appointments
Book a patient with a doctor, a date and a note. Scheduled, completed and cancelled states are filterable from the column header.
130 recordsRooms
Bed inventory by type — VIP, ICU, Regular — with availability, occupancy and maintenance status.
110 recordsPayments
Invoices tied to a patient and the treating doctor, with amount, note and paid / pending / cancelled state.
200 recordsDoctors
The medical roster: specialty, contact details and availability, each row editable in place.
80 recordsPatients
The master register — name, gender, date of birth, address, phone, emergency contact and blood type.
150 recordsInpatients
Who is admitted, in which room, since when — the bridge between the patient register and the bed inventory.
150 recordsUsers & access
Staff accounts across four roles — Admin, Doctor, Staff, Nurse — feeding the permission checks that build the sidebar.
100 recordsSettings & help
Eight settings tabs — account, general, preferences, language, notifications, linked accounts, billing, plans — plus a searchable FAQ.
Small screens
The same system in a pocket
The sidebar becomes a drawer, tables scroll horizontally and every create/edit form reflows to a single column — the modal keeps its validation, its date picker and its required-field markers down to 360px.
- One layout, no separate mobile build
- Keyboard focus stays visible on every control
- prefers-reduced-motion is respected
Under the hood
Decisions the demo is built on
The parts a client never sees in a screenshot and feels every day.
Expired tokens refresh without a flicker
When a request comes back 401, the axios layer refreshes the access token once and replays the failed request. Requests that fail while a refresh is in flight queue up and resume with the new token instead of firing a refresh each.
- A dashboard with six parallel queries triggers one refresh, not six
- Each request retries at most once — no loops
- Refresh failure is the only path to a forced logout
A mock backend that behaves like a real one
A Service Worker intercepts every request in the browser and answers it from seeded datasets — 920 records generated from a fixed seed, so the demo looks identical on every machine and every reload.
- Lists are paginated, sorted, searched and filtered server-side
- Create, update and delete persist for the session
- Swapping in a real API means changing one base URL
The menu generates the router
Sidebar groups and route definitions are built from the same navigation array, so a screen can never appear in one and be missing from the other. Route strings live in exactly one file.
- A route cannot exist without a permission
- Renaming a route is a one-line change
- Folders follow bulletproof-react: a feature owns its page, columns, modals and route
Two languages, three themes, no drift
Every visible string comes from a translation file — around 500 keys per language, Russian and Uzbek, switched from the header without a reload. Theme follows light, dark or the operating system, applied before first paint.
- Ant Design tokens and Tailwind utilities read the same colour variables
- The app never flashes white on a dark screen
- Adding a third language is one JSON file, not a search across the codebase
Stack
Current versions, no legacy shims
Typechecking runs as part of the production build, so a type error fails the deploy rather than reaching the browser.
- React 19
- TypeScript
- Vite
- Ant Design 6
- Tailwind CSS 4
- React Router 7
- TanStack Query
- Zustand
- Recharts
- MSW
- i18next
- react-hook-form
- axios
- dayjs
- ru · uz
Need an admin panel your team will actually use?
ClinicARE is one example. I build internal tools, dashboards and data-heavy interfaces — from the first wireframe to the deployed build.