feat: complete Jane App feature set with Telehealth, Retail POS, Memberships, Multi-Discipline Charting, Waitlist & RBAC
This commit is contained in:
+197
-11
@@ -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 & 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 & 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 & Spine Map ({activePatient.firstName} {activePatient.lastName})
|
||||
<ShoppingBag className="w-3.5 h-3.5 text-sky-700" />
|
||||
Retail & 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 & 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
|
||||
|
||||
Reference in New Issue
Block a user