Specialty-Clinic Order System Miniapps
Per-clinic Order System surface for every OPD specialty clinic.
Status: Phase 1–2 in progress (2026-05-29). Goal: a per-clinic “Order System” surface for every OPD
specialty clinic where a user picks one of the clinic’s dayProcedures, opens an order dialog (with
STAT / Urgent / Routine + clinic addon fields), submits, and watches the order land in that clinic’s
procedure day-queue.
Decisions (locked 2026-05-29)
- Surface: sandbox targets first (this work), then Core App Dialogs integration + an upgraded Core App launcher (orthogonal filters, full-screen view, menu search) — Phase 3, next.
- Scope: cover all 13 clinics (15 configs) — table below is the source of truth so nothing is forgotten. The reusable component is config-driven, so each clinic is “free” once the component works.
- Queue behaviour: optimistic + live merge — an optimistic row appears immediately on submit
(so the flow is visible offline in the sandbox) AND
useProcedureDayQueuerealtime rows supersede it when wired to a live Supabase backend (dedupe byprocedure_request_id, live wins).
Plumbing facts (verified, do not re-derive)
- Order dialog:
web/packages/miniapps/specialty-clinics/OpdProcedureRequestDialog.tsx— already has the STAT/Urgent/Routine radio group + phase-grouped addon fields. FiresuseRecordProcedureEvent.record({ eventType:'requested', departmentId: subDeptId, priority, … }). - Write hook:
@/services/procedure-day-queue/useRecordProcedureEvent→supabase.rpc('record_procedure_event'). Readsstate.user.userviauseSelector(needs a Redux Provider). Returns{ ok }; returns{ok:false}when Supabase env is absent (sandbox) — so the dialog’sonSubmitteddoes NOT fire offline unless we opt in via the newdemoOptimisticprop. - Read hook:
@/services/procedure-day-queue/useProcedureDayQueue({ deptId, day })→ realtime onprocedure_day_queuefiltered bydepartment_id. Returns{ rows, loading, error, refetch, connected }. Row typeProcedureDayQueueRow(status:pending|accepted|waiting|operating|completed|cancelled|postponed). - Sandbox providers = React Query + MUI + Router only (no Redux/auth/i18n). A target must wrap its own
minimal Redux store (see
OpdProcedureRequestDialogTarget.tsx). Target key parsed fromwindow.location.search(?target=) insandbox/main.tsx; extra params like&clinic=are readable by the target. - Key collision:
palliative-careis used by BOTH the Clinic and the Center configs;getSpecialtyClinicConfig('palliative-care')returns the Clinic (first match). IterateSPECIALTY_CLINIC_CONFIGSdirectly to reach both.
Per-clinic coverage (all 15 configs)
Add = sandbox order-system reachable (?target=ClinicOrderSystem&clinic=<key>).
Verify = ordered a procedure, optimistic row appeared in the day-queue panel.
| # | Clinic | key |
category | subDeptId |
preset | #proc | #addons | Add | Verify |
|---|---|---|---|---|---|---|---|---|---|
| 1 | GI Endoscopy | gi-endoscopy |
clinic | 0e42fb73-7112-4d09-8e6b-b18802da3bd6 |
full-or | 5 | 9 | [ ] | [ ] |
| 2 | Hemodialysis | hemodialysis |
clinic | 91d1f806-3f36-4e54-ac27-d8a109f73710 |
minor-ops | 4 | 15 | [ ] | [ ] |
| 3 | Psychiatry | psychiatry |
clinic | 605d9544-dc29-4c36-9c88-bbb6253b8f21 |
— | 3 | 0 | [ ] | [ ] |
| 4 | Family Medicine | family-medicine |
clinic | fc1a0001-0001-4000-a000-000000000001 |
— | 3 | 0 | [ ] | [ ] |
| 5 | Palliative Care (Clinic) | palliative-care |
clinic | fc1a0001-0003-4000-a000-000000000003 |
— | 3 | 0 | [ ] | [ ] |
| 6 | Elderly | elderly |
clinic | fc1a0001-0005-4000-a000-000000000005 |
— | 3 | 0 | [ ] | [ ] |
| 7 | Holter Monitoring | holter-monitoring |
diagnostic_device | cadba3b9-40e2-4f9e-8f2d-da3ab1704b28 |
custom | 3 | 11 | [ ] | [ ] |
| 8 | ABP Monitoring | abp |
diagnostic_device | cadba3b9-40e2-4f9e-8f2d-da3ab1704b28 shared |
custom | 2 | 13 | [ ] | [ ] |
| 9 | Respiratory/Thoracic | respiratory-thoracic |
specialized_unit | 88e4977c-72c5-4fcc-b227-f5e136713055 |
— | 5 | 0 | [ ] | [ ] |
| 10 | Antenatal Care | antenatal-care |
specialized_unit | 041c90e4-e750-4d85-a0c1-98d40f997e9e |
— | 4 | 0 | [ ] | [ ] |
| 11 | Diabetes Registry | diabetes-registry |
specialized_unit | 8df92e78-0823-4908-b8ac-b24ccd3f3276 |
— | 4 | 0 | [ ] | [ ] |
| 12 | Vaccination | vaccination |
other_service | fc1a0001-0006-4000-a000-000000000006 |
safety-only | 3 | 13 | [ ] | [ ] |
| 13 | Palliative Care Center | palliative-care collides |
other_service | fc1a0001-0002-4000-a000-000000000002 |
— | 3 | 0 | [ ] | [ ] |
| 14 | Home Isolation | home-isolation |
other_service | c979fb20-8fcd-4a11-a615-4a3a12d47fd5 |
— | 2 | 0 | [ ] | [ ] |
| 15 | Home Visit | home-visit |
other_service | sc-placeholder-home-visit-0001 placeholder |
— | 3 | 0 | [ ] | [ ] |
Clinics with 0 addons still order fine — the dialog submits the canonical ProcedureRequest fields (code, name, priority) and the row lands in that subDept’s queue; there are just no pre/intra/post addon fields to fill.
Phase 1 — Component + dialog (DONE 2026-05-29)
- [x] Dialog
OpdProcedureRequestDialog.tsx— additive optional props (existing callers unaffected):- [x]
procedureChoices?: {code,nameTh,nameEn}[]→ render a Select (auto-fills code+name) vs free-text - [x]
initialProcedureCode?/initialProcedureName?→ seed state (re-seeds on open via useEffect) - [x]
demoOptimistic?: boolean→ onrecord()not-ok, still fireonSubmitted({…, offline:true})+ close - [x]
onSubmittedpayload gains optionaloffline?: boolean
- [x]
- [x] Component
ClinicOrderSystem.tsx(new, reusable, in the specialty-clinics package):- [x] Left:
config.dayProcedurespicker → opens the dialog with that procedure preselected - [x] Right:
useProcedureDayQueue({deptId: config.subDeptId})rows + optimistic rows, deduped by id - [x] Tabs Active / Done / Cancelled; per-row advance (pending→accepted→operating→completed) + cancel for optimistic rows
- [x] connected/offline badge + counts; STAT rows sort to top with red tint
- [x] Left:
- [x] Barrel
index.tsexportsClinicOrderSystem
Phase 2 — Sandbox targets (all 13, DONE 2026-05-29)
- [x]
web/sandbox/targets/ClinicOrderSystemTarget.tsx— minimal Redux store; reads?clinic=<key>; hub grid + search of all 15 configs when absent; full clinic order system when present - [x] Register
ClinicOrderSysteminweb/sandbox/registry.ts - [x] Per-clinic reach: the hub lists all 15 configs (verified via snapshot — counts + presets + placeholder warning all correct). One generic component covers every clinic, so
Add= done for all via?target=ClinicOrderSystem&clinic=<key>.
Verification log (2026-05-29, sandbox port 5188)
- Hub
?target=ClinicOrderSystem— renders all 15 clinics with correct category/preset/proc/addon counts + Home Visit placeholder warning. — verified - GI Endoscopy
?clinic=gi-endoscopy— order system renders; clicking EGD opens the dialog with EGD preselected in the new procedure dropdown, 9 addons phase-grouped, STAT/Urgent/Routine present. Submit correctly disabled until required pre-procedure addons (scope_id/type/sedation) are filled. — verified - Psychiatry
?clinic=psychiatry— ordered ECT (no required addons → submit enabled): queue went 0 → 2 active across two submits, rows show HN/name/PSY-ECT/ROUTINE/Pending/time. — verified - Live-backend finding: this sandbox has
VITE_SUPABASE_URL/ANON_KEYset → the queue badge readslive, therecord_procedure_eventRPC actually succeeded, and realprocedure_day_queuerows arrived via realtime. The optimistic→live “live wins” dedupe was exercised for real (a locally-advanced optimistic row is superseded by the backend row — backend owns lifecycle, which is correct). Note: two demo ECT rows for HN 66000123 (สมชาย ใจดี) were written to the liveprocedure_day_queueunder psychiatry subDept605d9544…— harmless additive demo data; clean up if undesired.
Phase 3 — Core App Dialogs + launcher upgrade (NEXT, not this work)
Per the locked decision: after the sandbox flow is proven, surface these in the real Core App.
- [ ] Register an Order-System module per clinic in
DynamicCoreAppenum +DynamicContentRenderer(mount ``, real backend), with icon/color inweb/src/utils/properties/coreApp.tsx - [ ] Core App launcher upgrade:
- [ ] Orthogonal filters (e.g. by clinic category × procedure tier × dept type) over the module menu
- [ ] Full-screen view of the launcher
- [ ] Search across Core App menu entries
- [ ] Surface the per-clinic order systems in
web/src/containers/specialty-clinic/page.tsxhub
Key files
| File | Role |
|---|---|
web/packages/miniapps/specialty-clinics/ClinicOrderSystem.tsx |
NEW reusable order-system (picker + optimistic-merge queue) |
web/packages/miniapps/specialty-clinics/OpdProcedureRequestDialog.tsx |
order dialog (STAT/urgency/addons) — enhanced with preselect + demoOptimistic |
web/packages/miniapps/specialty-clinics/clinic-configs.ts |
the 15 clinic configs (dayProcedures + addons + subDeptId) |
web/sandbox/targets/ClinicOrderSystemTarget.tsx |
NEW sandbox wrapper (hub + per-clinic deep link) |
web/sandbox/registry.ts |
sandbox target registry (?target=ClinicOrderSystem) |
web/src/services/procedure-day-queue/useProcedureDayQueue.ts |
realtime day-queue read hook |
web/src/services/procedure-day-queue/useRecordProcedureEvent.ts |
RPC write hook (record_procedure_event) |
docs/architecture/specialty-clinic-procedure-entry-paths.md |
canonical entry-path design |
docs/architecture/procedure-day-queue.md |
day-queue lifecycle + Supabase-primary model |