feat: complete Jane App feature set with Telehealth, Retail POS, Memberships, Multi-Discipline Charting, Waitlist & RBAC

This commit is contained in:
2026-09-05 15:15:35 -07:00
parent e681f066bb
commit dd5057d4e3
9 changed files with 1641 additions and 102 deletions
+197 -11
View File
@@ -8,6 +8,10 @@ import {
INITIAL_APPOINTMENTS,
INITIAL_SOAP_NOTES,
INITIAL_SUPERBILLS,
INITIAL_PRODUCTS,
INITIAL_MEMBERSHIPS,
INITIAL_PACKAGES,
INITIAL_WAITLIST,
} from '@/lib/mock-data';
import {
ClinicTenant,
@@ -15,6 +19,11 @@ import {
SoapNote,
Superbill,
Patient,
RetailProduct,
WellnessMembership,
PrePaidPackage,
WaitlistEntry,
StaffRole,
} from '@/types/clinical';
import { CalendarView } from '@/components/calendar/CalendarView';
import { SoapChartEditor } from '@/components/charting/SoapChartEditor';
@@ -22,6 +31,10 @@ import { SuperbillView } from '@/components/billing/SuperbillView';
import { RetentionView } from '@/components/retention/RetentionView';
import { PatientPortalView } from '@/components/intake/PatientPortalView';
import { SuperAdminView } from '@/components/admin/SuperAdminView';
import { RetailInventoryView } from '@/components/pos/RetailInventoryView';
import { MembershipsView } from '@/components/memberships/MembershipsView';
import { TelehealthRoom } from '@/components/telehealth/TelehealthRoom';
import { WaitlistModal } from '@/components/waitlist/WaitlistModal';
import {
Calendar,
FileText,
@@ -32,6 +45,12 @@ import {
ChevronDown,
Stethoscope,
Laptop,
ShoppingBag,
Repeat,
Video,
Shield,
ShieldAlert,
UserCheck,
} from 'lucide-react';
export default function Home() {
@@ -41,13 +60,23 @@ export default function Home() {
// 'clinic' | 'patient' | 'superadmin'
const [portalMode, setPortalMode] = useState<'clinic' | 'patient' | 'superadmin'>('clinic');
// 'calendar' | 'charting' | 'billing' | 'retention'
const [clinicTab, setClinicTab] = useState<'calendar' | 'charting' | 'billing' | 'retention'>('calendar');
// Staff Role: 'doctor' | 'front_desk' | 'billing_admin'
const [staffRole, setStaffRole] = useState<StaffRole>('doctor');
// Clinic Sub-Tabs: 'calendar' | 'charting' | 'telehealth' | 'retail' | 'memberships' | 'billing' | 'retention'
const [clinicTab, setClinicTab] = useState<
'calendar' | 'charting' | 'telehealth' | 'retail' | 'memberships' | 'billing' | 'retention'
>('calendar');
const [appointments, setAppointments] = useState<Appointment[]>(INITIAL_APPOINTMENTS);
const [patients, setPatients] = useState<Patient[]>(INITIAL_PATIENTS);
const [soapNotes, setSoapNotes] = useState<SoapNote[]>(INITIAL_SOAP_NOTES);
const [superbills, setSuperbills] = useState<Superbill[]>(INITIAL_SUPERBILLS);
const [products, setProducts] = useState<RetailProduct[]>(INITIAL_PRODUCTS);
const [memberships, setMemberships] = useState<WellnessMembership[]>(INITIAL_MEMBERSHIPS);
const [packages, setPackages] = useState<PrePaidPackage[]>(INITIAL_PACKAGES);
const [waitlist, setWaitlist] = useState<WaitlistEntry[]>(INITIAL_WAITLIST);
const [isWaitlistOpen, setIsWaitlistOpen] = useState(false);
const [activePatient, setActivePatient] = useState<Patient>(INITIAL_PATIENTS[0]);
const [activeSoapNote, setActiveSoapNote] = useState<SoapNote | undefined>(INITIAL_SOAP_NOTES[0]);
@@ -63,7 +92,12 @@ export default function Home() {
const existingSoap = soapNotes.find((s) => s.patientId === apt.patientId && s.appointmentId === apt.id);
setActiveSoapNote(existingSoap);
setClinicTab('charting');
if (apt.isTelehealth) {
setClinicTab('telehealth');
} else {
setClinicTab('charting');
}
};
const handleUpdateAppointmentStatus = (aptId: string, status: Appointment['status']) => {
@@ -159,6 +193,24 @@ export default function Home() {
setClinicTab('calendar');
};
const handleUpdateProductStock = (productId: string, newStock: number) => {
setProducts((prev) =>
prev.map((p) => (p.id === productId ? { ...p, stockQty: newStock } : p))
);
};
const handleEnrollPatientInMembership = (patientId: string, planName: string) => {
setPatients((prev) =>
prev.map((p) => (p.id === patientId ? { ...p, activeMembership: planName } : p))
);
};
const handleAutoFillWaitlistPatient = (entryId: string) => {
setWaitlist((prev) =>
prev.map((w) => (w.id === entryId ? { ...w, status: 'notified' } : w))
);
};
return (
<div className="min-h-screen bg-slate-50 text-slate-900 flex flex-col">
{/* Top Hospital Command Header */}
@@ -199,6 +251,52 @@ export default function Home() {
</select>
<ChevronDown className="w-3.5 h-3.5 text-slate-500 absolute right-2.5 top-1/2 -translate-y-1/2 pointer-events-none" />
</div>
{/* Staff Role Switcher (RBAC) */}
<div className="flex items-center gap-1 bg-slate-100 p-1 rounded-lg border border-slate-200 text-xs">
<span className="text-[10px] uppercase font-bold text-slate-500 px-1.5">Role:</span>
<button
type="button"
onClick={() => setStaffRole('doctor')}
className={`px-2 py-1 rounded text-xs font-bold transition ${
staffRole === 'doctor'
? 'bg-white text-sky-800 shadow-2xs border border-slate-200'
: 'text-slate-600 hover:text-slate-900'
}`}
title="Full Doctor Clinical Access"
>
Doctor (D.C.)
</button>
<button
type="button"
onClick={() => {
setStaffRole('front_desk');
if (clinicTab === 'charting' || clinicTab === 'telehealth') {
setClinicTab('calendar');
}
}}
className={`px-2 py-1 rounded text-xs font-bold transition ${
staffRole === 'front_desk'
? 'bg-amber-100 text-amber-900 shadow-2xs border border-amber-200'
: 'text-slate-600 hover:text-slate-900'
}`}
title="HIPAA-Safe: Hides Clinical Notes, Shows Schedule, Retail & Billing"
>
Front Desk
</button>
<button
type="button"
onClick={() => setStaffRole('billing_admin')}
className={`px-2 py-1 rounded text-xs font-bold transition ${
staffRole === 'billing_admin'
? 'bg-white text-emerald-800 shadow-2xs border border-slate-200'
: 'text-slate-600 hover:text-slate-900'
}`}
title="Financial & Billing Administrator"
>
Billing Admin
</button>
</div>
</div>
{/* Portal Switcher Tabs */}
@@ -260,17 +358,65 @@ export default function Home() {
Schedule &amp; Encounters
</button>
{/* Clinical Charts - Hidden if in Front Desk HIPAA-safe mode */}
{staffRole !== 'front_desk' ? (
<button
type="button"
onClick={() => setClinicTab('charting')}
className={`px-3 py-1.5 rounded-md font-semibold flex items-center gap-1.5 transition whitespace-nowrap ${
clinicTab === 'charting'
? 'bg-sky-50 text-sky-800 border border-sky-200 shadow-2xs font-bold'
: 'text-slate-600 hover:text-slate-900'
}`}
>
<FileText className="w-3.5 h-3.5 text-sky-700" />
SOAP Chart &amp; Spine ({activePatient.firstName} {activePatient.lastName})
</button>
) : (
<span className="px-2.5 py-1 text-[11px] text-amber-700 bg-amber-50 rounded-md border border-amber-200 font-medium flex items-center gap-1">
<Shield className="w-3 h-3 text-amber-600" /> HIPAA: Notes Protected
</span>
)}
{staffRole !== 'front_desk' && (
<button
type="button"
onClick={() => setClinicTab('telehealth')}
className={`px-3 py-1.5 rounded-md font-semibold flex items-center gap-1.5 transition whitespace-nowrap ${
clinicTab === 'telehealth'
? 'bg-purple-50 text-purple-800 border border-purple-200 shadow-2xs font-bold'
: 'text-slate-600 hover:text-slate-900'
}`}
>
<Video className="w-3.5 h-3.5 text-purple-600" />
Telehealth Virtual Room
</button>
)}
<button
type="button"
onClick={() => setClinicTab('charting')}
onClick={() => setClinicTab('retail')}
className={`px-3 py-1.5 rounded-md font-semibold flex items-center gap-1.5 transition whitespace-nowrap ${
clinicTab === 'charting'
clinicTab === 'retail'
? 'bg-sky-50 text-sky-800 border border-sky-200 shadow-2xs font-bold'
: 'text-slate-600 hover:text-slate-900'
}`}
>
<FileText className="w-3.5 h-3.5 text-sky-700" />
Clinical SOAP &amp; Spine Map ({activePatient.firstName} {activePatient.lastName})
<ShoppingBag className="w-3.5 h-3.5 text-sky-700" />
Retail &amp; Supplement POS
</button>
<button
type="button"
onClick={() => setClinicTab('memberships')}
className={`px-3 py-1.5 rounded-md font-semibold flex items-center gap-1.5 transition whitespace-nowrap ${
clinicTab === 'memberships'
? 'bg-sky-50 text-sky-800 border border-sky-200 shadow-2xs font-bold'
: 'text-slate-600 hover:text-slate-900'
}`}
>
<Repeat className="w-3.5 h-3.5 text-sky-700" />
Memberships &amp; Packages
</button>
<button
@@ -296,7 +442,7 @@ export default function Home() {
}`}
>
<Users className="w-3.5 h-3.5 text-amber-600" />
Care Plan Retention Radar
Retention Radar
</button>
</div>
)}
@@ -311,28 +457,60 @@ export default function Home() {
appointments={appointments}
providers={activeProviders}
activeTenant={activeTenant}
waitlistCount={waitlist.length}
onSelectAppointment={handleSelectAppointment}
onUpdateStatus={handleUpdateAppointmentStatus}
onNewAppointmentClick={() => setPortalMode('patient')}
onOpenWaitlist={() => setIsWaitlistOpen(true)}
/>
)}
{clinicTab === 'charting' && (
{clinicTab === 'charting' && staffRole !== 'front_desk' && (
<SoapChartEditor
patient={activePatient}
provider={activeProvider}
initialSoapNote={activeSoapNote}
onSaveSoapNote={handleSaveSoapNote}
onGenerateSuperbill={handleGenerateSuperbillFromNote}
onLaunchTelehealth={() => setClinicTab('telehealth')}
onBackToCalendar={() => setClinicTab('calendar')}
/>
)}
{clinicTab === 'telehealth' && staffRole !== 'front_desk' && (
<TelehealthRoom
patient={activePatient}
provider={activeProvider}
onEndCall={(noteData) => {
setClinicTab('charting');
}}
onClose={() => setClinicTab('calendar')}
/>
)}
{clinicTab === 'retail' && (
<RetailInventoryView
products={products}
activeTenant={activeTenant}
onUpdateStock={handleUpdateProductStock}
/>
)}
{clinicTab === 'memberships' && (
<MembershipsView
memberships={memberships}
packages={packages}
patients={patients}
activeTenant={activeTenant}
onEnrollPatient={handleEnrollPatientInMembership}
/>
)}
{clinicTab === 'billing' && (
<SuperbillView
superbill={activeSuperbill}
activeTenant={activeTenant}
onBack={() => setClinicTab('charting')}
onBack={() => setClinicTab('calendar')}
onMarkPaid={handleMarkPaid}
/>
)}
@@ -369,11 +547,19 @@ export default function Home() {
)}
</main>
{/* Cancellation Waitlist Modal */}
<WaitlistModal
waitlist={waitlist}
isOpen={isWaitlistOpen}
onClose={() => setIsWaitlistOpen(false)}
onAutoFillPatient={handleAutoFillWaitlistPatient}
/>
{/* Hospital Footer */}
<footer className="bg-white border-t border-slate-200 py-4 px-6 text-center text-xs text-slate-500">
<div className="flex flex-col sm:flex-row items-center justify-between max-w-7xl mx-auto gap-2">
<span className="font-medium text-slate-600">
Mediusa Clinic OS Hospital-Grade Practice Management Platform
Mediusa Clinic OS Complete Hospital-Grade Practice Management Platform
</span>
<span className="font-mono text-slate-500">
hello@aipilots.site HIPAA Certified Sub-30s Clinical Charting Standard