const { useState, useEffect, useCallback } = React; const API = "api"; const TYPES = [ { key:"surgical", label:"Surgical" }, { key:"icd10", label:"ICD-10" }, { key:"theatre_time", label:"Theatre time" }, { key:"anaesthetic", label:"Anaesthetic" }, { key:"asa", label:"ASA" }, { key:"other", label:"Other" }, ]; const STAGES = [ { key:"st_detail", short:"Detail" }, { key:"st_ms", short:"MS" }, { key:"st_cv", short:"CV" }, { key:"st_rp", short:"RP" }, ]; // ---- code reference for the readable summary + quick-picks ---- const CODE_REF = { "0039":{t:"anaesthetic",d:"General anaesthetic"},"0023":{t:"anaesthetic",d:"Sedation"}, "1218":{t:"surgical",d:"Appendicectomy"},"2814":{t:"surgical",d:"Lap cholecystectomy"}, "3092":{t:"surgical",d:"Knee arthroscopy"},"0147":{t:"theatre_time",d:"Theatre time"}, "543":{t:"asa",d:"ASA modifier"},"K35.8":{t:"icd10",d:"Acute appendicitis"}, "K80.2":{t:"icd10",d:"Gallstones"},"M23.2":{t:"icd10",d:"Meniscus tear"}, }; const lookup = raw => { const code = raw.trim().toUpperCase(); const h = CODE_REF[code] || CODE_REF[raw.trim()]; return h ? { code, code_type:h.t, description:h.d } : { code, code_type:"surgical", description:null }; }; // =========================================================================== // Case-number generator (runs entirely in the browser — no data leaves here) // Structure: first 3 letters of SURNAME + 2-digit MONTH + first 3 of HOSPITAL // e.g. Mabitsela, July, Milpark -> MAB07MIL // =========================================================================== function makeCaseNumber(surname, hospital, dateStr) { const s = (surname||"").replace(/[^a-z]/gi,"").toUpperCase().slice(0,3).padEnd(3,"X"); const h = (hospital||"").replace(/[^a-z]/gi,"").toUpperCase().slice(0,3).padEnd(3,"X"); let mm = "00"; if (dateStr) { const d = new Date(dateStr); if(!isNaN(d)) mm = String(d.getMonth()+1).padStart(2,"0"); } return s + mm + h; } async function api(path, opts={}) { const res = await fetch(`${API}/${path}`, { credentials:"same-origin", headers: opts.body && !(opts.body instanceof FormData) ? {"Content-Type":"application/json"} : undefined, ...opts }); let d; try { d = await res.json(); } catch { d = {ok:false,error:"Bad response"}; } if (!res.ok || d.ok===false) throw new Error(d.error||`Error ${res.status}`); return d; } // =========================================================================== function Login({ onIn }) { const [pw,setPw]=useState(""); const [err,setErr]=useState(""); const [busy,setBusy]=useState(false); const go=async()=>{ setBusy(true); setErr(""); try{ await api("auth.php",{method:"POST",body:JSON.stringify({action:"login",password:pw})}); onIn(); } catch(e){ setErr(e.message); setBusy(false); } }; return (
B

Black Book

Anaesthetic case workflow

{err}
setPw(e.target.value)} onKeyDown={e=>e.key==="Enter"&&go()} autoFocus />
); } function Dot({ s }) { return ; } // Live client-side mirror of the server's recompute(), so tab dots react // immediately as the user edits (server remains the source of truth on save). function liveStages(c) { const req=["hospital","medical_aid","procedure_type"]; const missing=req.some(r=>!c[r]); const hasCodes=(c.codes||[]).length>0; let st_detail="grey"; if(hasCodes||c.hospital||c.medical_aid){ st_detail=(missing||!hasCodes)?"yellow":"green"; if(c.codes_more)st_detail="yellow"; } const ms=c.ms_filed&&c.ms_scanned&&c.ms_uploaded&&c.ms_submitted; const msAny=c.ms_filed||c.ms_scanned||c.ms_uploaded||c.ms_submitted; const st_ms=ms?"green":(msAny?"yellow":"grey"); const surg=(c.codes||[]).filter(x=>x.code_type==="surgical"), anaes=(c.codes||[]).filter(x=>x.code_type==="anaesthetic"); const anyQ=(c.codes||[]).some(x=>x.code_status==="query"), anyV=(c.codes||[]).some(x=>x.verified); let st_cv="grey"; if(anyV||anyQ){ const surgOk=surg.length?surg.some(x=>x.verified):true; const anOk=anaes.every(x=>x.verified); st_cv=(surgOk&&anOk&&!anyQ)?"green":"yellow"; } return { st_detail, st_ms, st_cv, st_rp:c.st_rp||"grey" }; } function CaseCard({ c, onOpen }) { const codes = c.codes||[]; const summary = [c.hospital, c.procedure_type].filter(Boolean).join(" · "); return (
onOpen(c.id)}>
{c.case_number} {c.theatre_date||"—"}
{summary||"No detail yet"}
{codes.length===0 && No codes} {codes.map((cd,i)=>{cd.code})}
{STAGES.map(s=>(
{s.short}
))}
); } // =========================================================================== // New-case private capture -> generates case number // =========================================================================== function PrivateCapture({ onDone, onClose }) { const [firsts,setFirsts]=useState(""); const [surname,setSurname]=useState(""); const [hospital,setHospital]=useState(""); const [date,setDate]=useState(new Date().toISOString().slice(0,10)); const cn = (surname||hospital) ? makeCaseNumber(surname,hospital,date) : ""; return (
e.stopPropagation()}>
🔒 Private — stays on this device

New case

Used only to build a case number. The name is not saved or sent anywhere.

setFirsts(e.target.value)} placeholder="e.g. Thabo" />
setSurname(e.target.value)} placeholder="e.g. Mabitsela" />
setHospital(e.target.value)} placeholder="e.g. Milpark" />
setDate(e.target.value)} />
Case number
{cn ?
{cn}
:
— — —
}

Structure: first 3 of surname · month · first 3 of hospital. You'll write this number next to the sticker in your book. Only this number is stored.

); } // =========================================================================== // Case detail sheet with staged tabs // =========================================================================== function CaseSheet({ id, prefill, onClose, onSaved, toast }) { const [c,setC]=useState(null); const [saving,setSaving]=useState(false); // amend: which section is being edited (null = read-only view). New cases start on Detail. const [amend,setAmend]=useState(id ? null : "st_detail"); useEffect(()=>{ if (id) api(`cases.php?action=get&id=${id}`).then(r=>setC(normalise(r.case))).catch(e=>toast(e.message)); else setC(blank(prefill)); },[id]); function normalise(x){ return { ...x, codes:(x.codes||[]).map(cd=>({ ...cd, verified:!!+cd.verified, code_status:cd.code_status||"none", comment:cd.comment||"" })), ms_filed:!!+x.ms_filed, ms_scanned:!!+x.ms_scanned, ms_uploaded:!!+x.ms_uploaded, ms_submitted:!!+x.ms_submitted, codes_more:!!+x.codes_more }; } function blank(p){ return { id:null, case_number:p.case_number, theatre_date:p.theatre_date||"", hospital:p.hospital||"", procedure_date:"", theatre_start:"", theatre_end:"", surgeon:"", medical_aid:"", plan:"", procedure_type:"", codes_more:false, notes:"", codes:[], ms_filed:false, ms_scanned:false, ms_uploaded:false, ms_submitted:false, ms_submitted_date:"", st_detail:"grey", st_ms:"grey", st_cv:"grey", st_rp:"grey" }; } if (!c) return
; const set=(k,v)=>setC(o=>({...o,[k]:v})); const setCode=(i,patch)=>setC(o=>({...o,codes:o.codes.map((x,j)=>j===i?{...x,...patch}:x)})); const addCode=(cd={code:"",code_type:"surgical",description:""})=>setC(o=>({...o,codes:[...o.codes,cd]})); const delCode=i=>setC(o=>({...o,codes:o.codes.filter((_,j)=>j!==i)})); const merge=list=>setC(o=>{ const seen=new Set(o.codes.map(x=>x.code+"|"+x.code_type)); return {...o,codes:[...o.codes,...list.filter(x=>x.code&&!seen.has(x.code+"|"+x.code_type))]}; }); const REQUIRED=["hospital","medical_aid","procedure_type"]; const missing=k=>REQUIRED.includes(k)&&!c[k]; const save=async()=>{ setSaving(true); const payload={ action:id?"update":"create", id:id||undefined, case_number:c.case_number, theatre_date:c.theatre_date, procedure_date:c.procedure_date, theatre_start:c.theatre_start, theatre_end:c.theatre_end, hospital:c.hospital, surgeon:c.surgeon, medical_aid:c.medical_aid, plan:c.plan, procedure_type:c.procedure_type, codes_more:c.codes_more, notes:c.notes, codes:c.codes, ms_filed:c.ms_filed, ms_scanned:c.ms_scanned, ms_uploaded:c.ms_uploaded, ms_submitted:c.ms_submitted, ms_submitted_date:c.ms_submitted_date, st_rp:c.st_rp }; try{ await api("cases.php",{method:"POST",body:JSON.stringify(payload)}); toast(id?"Saved":"Case created"); onSaved(); } catch(e){ toast(e.message); setSaving(false); } }; const del=async()=>{ if(!confirm(`Delete ${c.case_number}?`))return; try{ await api("cases.php",{method:"POST",body:JSON.stringify({action:"delete",id})}); toast("Deleted"); onSaved(); } catch(e){ toast(e.message); } }; // amend: which section is being edited (null = read-only view). New cases start editing Detail. const saveSection=async()=>{ await save(); setAmend(null); }; const ls=liveStages(c); const fv=(v)=> (v===null||v===undefined||v==="") ? : {v}; return (
e.stopPropagation()}>

{c.case_number}

{c.theatre_date||"no date"}
{/* ---------- DETAIL ---------- */} {amend==="st_detail" ? (
Case detail
) : (
Case detail
Hospital{fv(c.hospital)}
Surgeon{fv(c.surgeon)}
Medical aid{fv(c.medical_aid)}
Plan{fv(c.plan)}
Procedure{fv(c.procedure_type)}
Theatre time{(c.theatre_start||c.theatre_end)?{(c.theatre_start||"—")+" – "+(c.theatre_end||"—")}:}
Codes {c.codes_more?"(more to add)":""}
{c.codes.length===0 && none} {c.codes.map((cd,i)=>{cd.code})}
{c.notes &&
{c.notes}
}
)} {/* ---------- MS ---------- */} {amend==="st_ms" ? (
MedCore submission
) : (
MedCore submission
{[["ms_filed","Filed"],["ms_scanned","Scanned"],["ms_uploaded","Uploaded"],["ms_submitted","Submitted"]].map(([k,l])=>(
{l} {c[k]?"✓ done":"—"}
))} {c.ms_submitted_date &&
Submitted date{c.ms_submitted_date}
}
)} {/* ---------- CV ---------- */} {amend==="st_cv" ? (
Code verification
) : (
Code verification
{c.codes.length===0 &&
No codes to verify
} {c.codes.map((cd,i)=>(
{cd.code} {TYPES.find(t=>t.key===cd.code_type)?.label} {cd.code_status==="query" ? ⚑ query : {cd.verified?"✓ verified":"—"}}
))} {c.codes.some(x=>x.code_status==="query"&&x.comment) && c.codes.filter(x=>x.code_status==="query"&&x.comment).map((x,i)=>
⚑ {x.code}: {x.comment}
)}
)} {/* ---------- RP ---------- */} {amend==="st_rp" ? (
Remittance & payment
) : (
Remittance & payment
Status {c.st_rp==="green"?"Done":c.st_rp==="yellow"?"Query":"Pending"}
)} {id && !amend && }
); } function DetailTab({ c,set,setCode,addCode,delCode,merge,missing }) { return ( <>
set("hospital",e.target.value)} />
set("surgeon",e.target.value)} />
set("medical_aid",e.target.value)} />
set("plan",e.target.value)} />
set("procedure_type",e.target.value)} placeholder="e.g. Appendicectomy" />
set("theatre_start",e.target.value)} />
set("theatre_end",e.target.value)} />
{c.codes.map((cd,i)=>(
{ const v=e.target.value; const look=lookup(v); setCode(i,{code:v, code_type:cd.code_type||look.code_type, description:cd.description||look.description}); }} />
))}
{["0039","0147","543","2814"].map(q=>)}