/* ============================================================ GOVERNANCE CORE — roles, permissions, status state-machine, seed data, reducer + audit trail. (window.GOV) ============================================================ */ (function(){ const now = Date.now(); const H = (h)=> now - h*3600*1000; // h hours ago const D = (d)=> now - d*24*3600*1000; // d days ago /* ---------- roles ---------- */ const ROLES = { partner: { key:'partner', label:'Partner User', cls:'role-partner', land:'dashboard' }, reviewer: { key:'reviewer', label:'Reviewer', cls:'role-reviewer', land:'queue' }, admin: { key:'admin', label:'Admin', cls:'role-admin', land:'dashboard' }, founder: { key:'founder', label:'Founder', cls:'role-founder', land:'dashboard' }, }; /* ---------- status state-machine ---------- */ const STATUS = { submitted: { key:'submitted', label:'Submitted', cls:'st-submitted', who:'BHF team' }, under_review: { key:'under_review', label:'Under Review', cls:'st-review', who:'Reviewer / Admin' }, needs_info: { key:'needs_info', label:'Needs Information', cls:'st-needs', who:'Partner' }, approved: { key:'approved', label:'Approved', cls:'st-approved', who:'—' }, rejected: { key:'rejected', label:'Rejected', cls:'st-rejected', who:'—' }, closed: { key:'closed', label:'Closed', cls:'st-closed', who:'—' }, }; const STATUS_ORDER = ['submitted','under_review','needs_info','approved','rejected','closed']; const TRANSITIONS = { submitted: ['under_review'], under_review: ['needs_info','approved','rejected'], needs_info: ['under_review'], approved: ['closed'], rejected: ['closed'], closed: ['under_review'], // reopen — admin/founder only }; const REASON_REQUIRED = ['needs_info','rejected']; /* which transitions a given user may perform from a status */ function transitionsFor(status, user, settings){ const r = user.role; let allowed = TRANSITIONS[status] || []; if(r==='partner') return []; // partners respond, never transition if(r==='reviewer'){ if(!settings.reviewersCanDecide) return []; // governed: reviewer recommends only // even when allowed to decide, reviewers cannot reopen a closed case return status==='closed' ? [] : allowed; } // admin & founder: full machine; only founder/admin may reopen closed return allowed; } /* ---------- permission matrix ---------- */ const MATRIX = { view_all_requests: ['admin','founder'], view_reports: ['admin','founder'], view_audit: ['admin','founder'], manage_partners: ['admin','founder'], manage_users: ['admin','founder'], manage_settings: ['founder'], assign_reviewer: ['admin','founder'], submit_request: ['partner'], add_internal_note: ['reviewer','admin','founder'], view_internal: ['reviewer','admin','founder'], recommend: ['reviewer'], use_queue: ['reviewer','admin','founder'], }; function can(user, action, settings){ if(!user) return false; if(action==='change_status'){ if(user.role==='admin'||user.role==='founder') return true; if(user.role==='reviewer') return !!(settings && settings.reviewersCanDecide); return false; } return (MATRIX[action]||[]).includes(user.role); } /* can this user OPEN this request at all (row-level scope, RLS-style) */ function canViewRequest(user, req){ if(!user||!req) return false; if(user.role==='partner') return req.hospitalId === user.hospitalId; if(user.role==='reviewer') return req.reviewerId === user.id; return true; // admin, founder } /* notes a given user is permitted to see */ function visibleNotes(user, req){ const internalOk = can(user,'view_internal'); return (req.notes||[]).filter(n => internalOk || n.scope==='partner'); } function visibleFiles(user, req){ const internalOk = can(user,'view_internal'); return (req.files||[]).filter(f => internalOk || f.scope==='partner'); } /* ---------- nav per role ---------- */ const NAV = { partner: [ {sec:'Workspace'}, {key:'dashboard', label:'Dashboard', icon:'home'}, {key:'submit', label:'Submit Patient', icon:'plus'}, {key:'requests', label:'My Requests', icon:'list'}, ], reviewer: [ {sec:'Review'}, {key:'queue', label:'My Queue', icon:'inbox'}, ], admin: [ {sec:'Overview'}, {key:'dashboard', label:'Dashboard', icon:'home'}, {key:'requests', label:'All Requests', icon:'list'}, {key:'queue', label:'My Queue', icon:'inbox'}, {sec:'Manage'}, {key:'partners', label:'Partners', icon:'hospital'}, {key:'users', label:'Users', icon:'users'}, {sec:'Governance'}, {key:'reports', label:'Reports', icon:'chart'}, {key:'audit', label:'Audit Log', icon:'shield'}, ], founder: [ {sec:'Overview'}, {key:'dashboard', label:'Dashboard', icon:'home'}, {key:'requests', label:'All Requests', icon:'list'}, {sec:'Manage'}, {key:'partners', label:'Partners', icon:'hospital'}, {key:'users', label:'Users', icon:'users'}, {sec:'Governance'}, {key:'reports', label:'Reports', icon:'chart'}, {key:'audit', label:'Audit Log', icon:'shield'}, {key:'settings', label:'Settings', icon:'cog'}, ], }; /* ============================================================ SEED DATA ============================================================ */ function seed(){ const hospitals = [ {id:'h1', name:'City Care Hospital', contact:'Dr. Meena Rao', email:'rao@citycare.in', phone:'98100 11223', city:'New Delhi', status:'active', notes:'MOU signed Aug 2023. High volume.'}, {id:'h2', name:'Apollo East', contact:'Dr. Sanjay Sen', email:'sen@apolloeast.in', phone:'98200 44556', city:'Kolkata', status:'active', notes:''}, {id:'h3', name:'Sunrise Clinic', contact:'Dr. Priya Iyer', email:'iyer@sunrise.in', phone:'90000 77889', city:'Pune', status:'active', notes:''}, {id:'h4', name:'Grace Burns Centre', contact:'Dr. Ravi Kumar', email:'ravi@grace.in', phone:'93000 22110', city:'Chennai', status:'inactive', notes:'MOU under renewal — deactivated pending paperwork.'}, ]; const users = [ {id:'u1', name:'Dr. Meena Rao', email:'rao@citycare.in', role:'partner', hospitalId:'h1', status:'active', lastLogin:H(2)}, {id:'u2', name:'Nurse Anil Gupta', email:'anil@citycare.in', role:'partner', hospitalId:'h1', status:'active', lastLogin:D(1)}, {id:'u3', name:'Dr. Sanjay Sen', email:'sen@apolloeast.in', role:'partner', hospitalId:'h2', status:'active', lastLogin:D(3)}, {id:'u4', name:'Dr. Ravi Kumar', email:'ravi@grace.in', role:'partner', hospitalId:'h4', status:'disabled', lastLogin:D(40)}, {id:'r1', name:'Dr. Nabakishor Haobijam', email:'naba@bhf.org', role:'reviewer', hospitalId:null, status:'active', lastLogin:H(5)}, {id:'r2', name:'Dr. Asha Mehta', email:'asha@bhf.org', role:'reviewer', hospitalId:null, status:'active', lastLogin:H(20)}, {id:'a1', name:'Animesh Chaudhary', email:'animesh@bhf.org', role:'admin', hospitalId:null, status:'active', lastLogin:H(1)}, {id:'f1', name:'Dr. Rajeev B. Ahuja', email:'chairman@bhf.org', role:'founder', hospitalId:null, status:'active', lastLogin:H(3)}, ]; const mkHist = (arr)=>arr; const requests = [ { id:'B-117', hospitalId:'h1', submittedById:'u1', reviewerId:'r1', status:'needs_info', urgency:'urgent', patient:{ref:'PT-2231', initials:'S.K.', age:'27', gender:'Female', diagnosis:'Post-burn contracture, right hand & forearm', condition:'Stable, limited grip'}, request:{type:'Reconstructive surgery', reason:'Release of contracture to restore hand function; patient is sole earner.', contact:'Dr. Meena Rao', partnerNote:'Family below poverty line; referral attached.'}, files:[{id:'f1',name:'medical-report.pdf',type:'Medical report',by:'u1',at:D(5),scope:'partner'}, {id:'f2',name:'hand-xray.jpg',type:'Imaging',by:'u1',at:D(5),scope:'partner'}], notes:[{id:'n1',authorId:'r1',scope:'partner',body:'Thank you for the submission. Could you upload a recent lab report (CBC) and confirm the patient’s weight?',at:H(4)}, {id:'n2',authorId:'r1',scope:'internal',body:'Likely a good candidate. Flagging for Schedule-1 rate confirmation before approval.',at:H(4)}], history:mkHist([{from:null,to:'submitted',byId:'u1',at:D(5),reason:''}, {from:'submitted',to:'under_review',byId:'a1',at:D(4),reason:''}, {from:'under_review',to:'needs_info',byId:'r1',at:H(4),reason:'Missing recent lab report.'}]), createdAt:D(5), updatedAt:H(4) }, { id:'B-116', hospitalId:'h2', submittedById:'u3', reviewerId:null, status:'submitted', urgency:'normal', patient:{ref:'PT-2229', initials:'M.D.', age:'34', gender:'Male', diagnosis:'Facial scarring post kerosene burn', condition:'Healed, scar revision needed'}, request:{type:'Scar revision', reason:'Improve mobility of lower lip and cosmetic outcome.', contact:'Dr. Sanjay Sen', partnerNote:''}, files:[{id:'f3',name:'referral-letter.pdf',type:'Referral letter',by:'u3',at:D(2),scope:'partner'}], notes:[], history:mkHist([{from:null,to:'submitted',byId:'u3',at:D(2),reason:''}]), createdAt:D(2), updatedAt:D(2) }, { id:'B-114', hospitalId:'h1', submittedById:'u2', reviewerId:'r2', status:'under_review', urgency:'normal', patient:{ref:'PT-2219', initials:'R.B.', age:'19', gender:'Female', diagnosis:'Neck contracture limiting extension', condition:'Stable'}, request:{type:'Reconstructive surgery', reason:'Restore neck movement after thermal burn.', contact:'Nurse Anil Gupta', partnerNote:''}, files:[{id:'f4',name:'clinical-photos.zip',type:'Imaging',by:'u2',at:D(6),scope:'partner'}], notes:[{id:'n3',authorId:'r2',scope:'internal',body:'Reviewing surgical plan with Dr. Haobijam tomorrow.',at:D(1)}], history:mkHist([{from:null,to:'submitted',byId:'u2',at:D(6),reason:''},{from:'submitted',to:'under_review',byId:'a1',at:D(5),reason:''}]), createdAt:D(6), updatedAt:D(1) }, { id:'B-109', hospitalId:'h3', submittedById:'u3', reviewerId:'r2', status:'approved', urgency:'low', patient:{ref:'PT-2204', initials:'T.N.', age:'42', gender:'Male', diagnosis:'Axillary contracture, left', condition:'Stable'}, request:{type:'Reconstructive surgery', reason:'Release contracture to restore arm abduction.', contact:'Dr. Priya Iyer', partnerNote:''}, files:[{id:'f5',name:'medical-report.pdf',type:'Medical report',by:'u3',at:D(12),scope:'partner'}], notes:[{id:'n4',authorId:'a1',scope:'partner',body:'Approved. Our team will coordinate scheduling with your hospital.',at:D(3)}], history:mkHist([{from:null,to:'submitted',byId:'u3',at:D(12),reason:''},{from:'submitted',to:'under_review',byId:'a1',at:D(10),reason:''},{from:'under_review',to:'approved',byId:'a1',at:D(3),reason:''}]), createdAt:D(12), updatedAt:D(3) }, { id:'B-103', hospitalId:'h2', submittedById:'u3', reviewerId:'r1', status:'rejected', urgency:'normal', patient:{ref:'PT-2190', initials:'K.P.', age:'55', gender:'Female', diagnosis:'Chronic wound, non-burn origin', condition:'Stable'}, request:{type:'Reconstructive surgery', reason:'Wound coverage requested.', contact:'Dr. Sanjay Sen', partnerNote:''}, files:[], notes:[{id:'n5',authorId:'r1',scope:'partner',body:'After review, this case falls outside BHF’s burn-survivor mandate. We’re unable to proceed but can suggest other resources.',at:D(8)}], history:mkHist([{from:null,to:'submitted',byId:'u3',at:D(14),reason:''},{from:'submitted',to:'under_review',byId:'a1',at:D(11),reason:''},{from:'under_review',to:'rejected',byId:'r1',at:D(8),reason:'Outside burn-survivor mandate.'}]), createdAt:D(14), updatedAt:D(8) }, { id:'B-098', hospitalId:'h1', submittedById:'u1', reviewerId:'r2', status:'closed', urgency:'low', patient:{ref:'PT-2175', initials:'L.S.', age:'31', gender:'Female', diagnosis:'Hand contracture, completed', condition:'Recovered'}, request:{type:'Reconstructive surgery', reason:'Completed case — archived.', contact:'Dr. Meena Rao', partnerNote:''}, files:[], notes:[], history:mkHist([{from:null,to:'submitted',byId:'u1',at:D(40),reason:''},{from:'submitted',to:'under_review',byId:'a1',at:D(38),reason:''},{from:'under_review',to:'approved',byId:'a1',at:D(30),reason:''},{from:'approved',to:'closed',byId:'a1',at:D(20),reason:''}]), createdAt:D(40), updatedAt:D(20) }, ]; const audit = [ {id:'au1', action:'status_change', actorId:'r1', target:'B-117', detail:'Under Review → Needs Information · “Missing recent lab report.”', at:H(4)}, {id:'au2', action:'login', actorId:'a1', target:'—', detail:'Signed in from 49.36.x.x', at:H(1)}, {id:'au3', action:'assignment', actorId:'a1', target:'B-114', detail:'Assigned reviewer: Dr. Asha Mehta', at:D(5)}, {id:'au4', action:'file_download', actorId:'r2', target:'B-114', detail:'Downloaded clinical-photos.zip', at:D(1)}, {id:'au5', action:'status_change', actorId:'a1', target:'B-109', detail:'Under Review → Approved', at:D(3)}, {id:'au6', action:'user_change', actorId:'f1', target:'u4', detail:'Disabled user · Dr. Ravi Kumar (Grace Burns Centre)', at:D(38)}, ]; return { hospitals, users, requests, audit, settings:{ reviewersCanDecide:false }, seq:118, sessionUserId:null, }; } window.GOV = { ROLES, STATUS, STATUS_ORDER, TRANSITIONS, REASON_REQUIRED, NAV, MATRIX, transitionsFor, can, canViewRequest, visibleNotes, visibleFiles, seed, }; window.StoreCtx = React.createContext(null); window.useStore = ()=> React.useContext(window.StoreCtx); })();