39768-vm/src/App.jsx
2026-04-21 19:01:50 +00:00

410 lines
14 KiB
JavaScript

import { useState } from 'react';
import {
Navigate,
NavLink,
Outlet,
Route,
Routes,
useNavigate,
} from 'react-router-dom';
import './App.css';
import Welcome from './pages/common/Welcome';
import SignIn from './pages/common/SignIn';
import SignUp from './pages/common/SignUp';
import AdminPage from './pages/admin/AdminPage';
import DoctorLayout from './pages/doctor/DoctorLayout';
import DoctorDashboard from './pages/doctor/Dashboard';
import DoctorPatients from './pages/doctor/Patients';
import DoctorAppointments from './pages/doctor/Appointments';
import DoctorMedicalRecords from './pages/doctor/MedicalRecords';
import DoctorSettings from './pages/doctor/Settings';
import DoctorProfile from './pages/doctor/Profile';
import DoctorSchedule from './pages/doctor/Schedule';
import DoctorPatientHistory from './pages/doctor/PatientHistory';
import AdminDashboard from './pages/admin/Dashboard';
import AdminPatients from './pages/admin/Patients';
import AdminAppointments from './pages/admin/Appointments';
import AdminDoctors from './pages/admin/Doctors';
import AdminReceptionists from './pages/admin/Receptionists';
import AdminMedicalRecords from './pages/admin/MedicalRecords';
import AdminReports from './pages/admin/Reports';
import AdminSettings from './pages/admin/Settings';
import ReceptionistDashboard from './pages/receptionist/Dashboard';
import ReceptionistPatients from './pages/receptionist/Patients';
import ReceptionistAppointments from './pages/receptionist/Appointments';
import ReceptionistDoctors from './pages/receptionist/Doctors';
import ReceptionistMedicalRecords from './pages/receptionist/MedicalRecords';
import ReceptionistReports from './pages/receptionist/Reports';
import ReceptionistSettings from './pages/receptionist/Settings';
import { saveUser } from './utils/userStorage';
const ADMIN_NAV_ITEMS = [
{ id: 'dashboard', path: 'dashboard', icon: 'dashboard', label: 'Dashboard' },
{ id: 'patients', path: 'patients', icon: 'patients', label: 'Patients' },
{ id: 'appointments', path: 'appointments', icon: 'appointments', label: 'Appointments' },
{ id: 'doctors', path: 'doctors', icon: 'doctors', label: 'Doctors' },
{ id: 'receptionists', path: 'receptionists', icon: 'receptionists', label: 'Receptionist' },
{ id: 'records', path: 'records', icon: 'records', label: 'Records' },
{ id: 'reports', path: 'reports', icon: 'reports', label: 'Reports' },
{ id: 'settings', path: 'settings', icon: 'settings', label: 'Settings' },
];
function NavIcon({ name }) {
const common = {
fill: 'none',
stroke: 'currentColor',
strokeWidth: '1.9',
strokeLinecap: 'round',
strokeLinejoin: 'round',
viewBox: '0 0 24 24',
'aria-hidden': 'true',
focusable: 'false',
};
switch (name) {
case 'dashboard':
return (
<svg {...common}>
<rect x="3" y="3" width="7" height="7" rx="1.5" />
<rect x="14" y="3" width="7" height="4" rx="1.5" />
<rect x="14" y="10" width="7" height="11" rx="1.5" />
<rect x="3" y="13" width="7" height="8" rx="1.5" />
</svg>
);
case 'patients':
return (
<svg {...common}>
<circle cx="12" cy="8.5" r="3.5" />
<path d="M4.5 19c1.4-3.2 4.2-5 7.5-5s6.1 1.8 7.5 5" />
</svg>
);
case 'appointments':
return (
<svg {...common}>
<rect x="3" y="5" width="18" height="16" rx="2" />
<path d="M8 3v4M16 3v4M3 10h18" />
</svg>
);
case 'doctors':
return (
<svg {...common}>
<path d="M12 4v16M4 12h16" />
<circle cx="12" cy="12" r="9" />
</svg>
);
case 'receptionists':
return (
<svg {...common}>
<circle cx="9" cy="8" r="2.5" />
<circle cx="16.5" cy="9.5" r="2" />
<path d="M4.5 18c.8-2.4 2.8-4 5.5-4s4.7 1.6 5.5 4" />
<path d="M14 18c.5-1.4 1.7-2.4 3.2-2.4 1.3 0 2.4.7 3 1.8" />
</svg>
);
case 'search':
return (
<svg {...common}>
<circle cx="11" cy="11" r="7" />
<path d="M20 20l-3.5-3.5" />
</svg>
);
case 'records':
return (
<svg {...common}>
<rect x="5" y="3" width="14" height="18" rx="2" />
<path d="M9 8h6M9 12h6M9 16h4" />
</svg>
);
case 'reports':
return (
<svg {...common}>
<path d="M4 20h16" />
<rect x="6" y="10" width="3" height="6" rx="1" />
<rect x="11" y="7" width="3" height="9" rx="1" />
<rect x="16" y="5" width="3" height="11" rx="1" />
</svg>
);
case 'settings':
return (
<svg {...common}>
<circle cx="12" cy="12" r="3" />
<path d="M19.4 15a1 1 0 0 0 .2 1.1l.1.1a1 1 0 0 1 0 1.4l-1.4 1.4a1 1 0 0 1-1.4 0l-.1-.1a1 1 0 0 0-1.1-.2 1 1 0 0 0-.6.9V20a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-.2a1 1 0 0 0-.6-.9 1 1 0 0 0-1.1.2l-.1.1a1 1 0 0 1-1.4 0l-1.4-1.4a1 1 0 0 1 0-1.4l.1-.1a1 1 0 0 0 .2-1.1 1 1 0 0 0-.9-.6H4a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1h.2a1 1 0 0 0 .9-.6 1 1 0 0 0-.2-1.1l-.1-.1a1 1 0 0 1 0-1.4l1.4-1.4a1 1 0 0 1 1.4 0l.1.1a1 1 0 0 0 1.1.2h0a1 1 0 0 0 .6-.9V4a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v.2a1 1 0 0 0 .6.9h0a1 1 0 0 0 1.1-.2l.1-.1a1 1 0 0 1 1.4 0l1.4 1.4a1 1 0 0 1 0 1.4l-.1.1a1 1 0 0 0-.2 1.1v0a1 1 0 0 0 .9.6H20a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1h-.2a1 1 0 0 0-.9.6z" />
</svg>
);
default:
return null;
}
}
function getHomePathForRole(role) {
if (role === 'admin') return '/admin';
if (role === 'doctor') return '/doctor';
if (role === 'receptionist') return '/receptionist';
return '/';
}
function getRoleLabel(role) {
if (role === 'admin') return 'Admin';
if (role === 'doctor') return 'Doctor';
if (role === 'receptionist') return 'Receptionist';
return 'User';
}
function getRoleAvatar(role) {
if (role === 'admin') return '\u{1F469}\u200D\u{1F4BC}';
if (role === 'doctor') return '\u{1F468}\u200D\u2695\uFE0F';
if (role === 'receptionist') return '\u{1F9FE}';
return '\u{1F464}';
}
function AdminLayout({ onLogout, basePath = 'admin', user }) {
const navigate = useNavigate();
const handleLogout = () => {
onLogout();
navigate('/');
};
const navItems =
basePath === 'admin'
? ADMIN_NAV_ITEMS
: ADMIN_NAV_ITEMS.filter((item) => item.id !== 'receptionists');
return (
<div className="app-shell">
<aside className="sidebar">
<div className="sidebar-logo-wrap">
<div className="sidebar-logo">+</div>
<div className="sidebar-brand">
<span className="sidebar-brand-title">ClinicOne</span>
<span className="sidebar-brand-subtitle">Care Hub</span>
</div>
</div>
<nav className="sidebar-nav">
{navItems.map((item) => (
<NavLink
key={item.id}
to={`/${basePath}/${item.path}`}
className={({ isActive }) => `nav-btn ${isActive ? 'active' : ''}`}
>
<span className="nav-btn-icon">
<NavIcon name={item.icon} />
</span>
<span className="nav-btn-label">{item.label}</span>
</NavLink>
))}
</nav>
<button className="sidebar-logout" onClick={handleLogout}>
{'\u{1F6AA}'} Logout
</button>
</aside>
<div className="main-area">
<header className="topbar">
<span className="topbar-brand-name">ClinicOne</span>
<div className="topbar-right">
<div className="topbar-user">
<div className="user-avatar">{getRoleAvatar(user?.role)}</div>
<div className="user-meta">
<div className="user-name">{user?.fullName || user?.username || user?.email || 'User'}</div>
<div className="user-role">{getRoleLabel(user?.role)}</div>
</div>
</div>
</div>
</header>
<main className="page-content">
<Outlet />
</main>
</div>
</div>
);
}
function RequireAuth({ user, children }) {
if (!user) return <Navigate to="/" replace />;
return children;
}
function RequireRole({ user, role, children }) {
if (!user) return <Navigate to="/" replace />;
if (user.role !== role) {
if (user.role === 'admin') return <Navigate to="/admin" replace />;
if (user.role === 'doctor') return <Navigate to="/doctor" replace />;
if (user.role === 'receptionist') return <Navigate to="/receptionist" replace />;
return <Navigate to="/" replace />;
}
return children;
}
const SESSION_KEY = 'clinicone_session';
function getPersistedUser() {
try {
const data = localStorage.getItem(SESSION_KEY);
return data ? JSON.parse(data) : null;
} catch {
return null;
}
}
export default function App() {
const navigate = useNavigate();
const [user, setUser] = useState(() => getPersistedUser());
const goToRoleHome = (role) => {
navigate(getHomePathForRole(role));
};
const persistUser = (userData) => {
localStorage.setItem(SESSION_KEY, JSON.stringify(userData));
setUser(userData);
};
const handleLogin = (userData) => {
persistUser(userData);
goToRoleHome(userData?.role);
};
const handleSignIn = (userData) => {
persistUser(userData);
goToRoleHome(userData?.role);
};
const handleSignUp = (userData) => {
saveUser(userData);
navigate('/');
};
const handleLogout = () => {
localStorage.removeItem(SESSION_KEY);
setUser(null);
};
return (
<Routes>
<Route
path="/"
element={
user ? (
<Navigate to={getHomePathForRole(user.role)} replace />
) : (
<Welcome onLogin={handleLogin} onSwitchToSignUp={() => navigate('/signup')} />
)
}
/>
<Route
path="/signin"
element={
user ? (
<Navigate to={getHomePathForRole(user.role)} replace />
) : (
<SignIn onSignIn={handleSignIn} onSwitchToSignUp={() => navigate('/signup')} />
)
}
/>
<Route
path="/signup"
element={
user ? (
<Navigate to={getHomePathForRole(user.role)} replace />
) : (
<SignUp onSignUp={handleSignUp} onSwitchToSignIn={() => navigate('/')} />
)
}
/>
<Route
path="/admin"
element={
<RequireRole user={user} role="admin">
<AdminLayout onLogout={handleLogout} basePath="admin" user={user} />
</RequireRole>
}
>
<Route index element={<Navigate to="dashboard" replace />} />
<Route path="dashboard" element={<AdminDashboard userRole="admin" user={user} onNavigate={(page) => navigate(`/admin/${page}`)} />} />
<Route path="patients" element={<AdminPatients />} />
<Route path="appointments" element={<AdminAppointments />} />
<Route path="doctors" element={<AdminDoctors userRole="admin" />} />
<Route path="receptionists" element={<AdminReceptionists />} />
<Route path="search" element={<AdminPatients />} />
<Route path="records" element={<AdminMedicalRecords />} />
<Route path="reports" element={<AdminReports />} />
<Route path="settings" element={<AdminSettings />} />
</Route>
<Route
path="/receptionist"
element={
<RequireRole user={user} role="receptionist">
<AdminLayout onLogout={handleLogout} basePath="receptionist" user={user} />
</RequireRole>
}
>
<Route index element={<Navigate to="dashboard" replace />} />
<Route path="dashboard" element={<ReceptionistDashboard userRole="receptionist" onNavigate={(page) => navigate(`/receptionist/${page}`)} />} />
<Route path="patients" element={<ReceptionistPatients />} />
<Route path="appointments" element={<ReceptionistAppointments />} />
<Route path="doctors" element={<ReceptionistDoctors userRole="receptionist" />} />
<Route path="search" element={<ReceptionistPatients />} />
<Route path="records" element={<ReceptionistMedicalRecords />} />
<Route path="reports" element={<ReceptionistReports />} />
<Route
path="settings"
element={
<ReceptionistSettings
user={user}
onPasswordChanged={() => {
handleLogout();
navigate('/');
}}
/>
}
/>
</Route>
<Route
path="/admin-legacy"
element={
<RequireRole user={user} role="admin">
<AdminPage user={user} onLogout={() => { handleLogout(); navigate('/'); }} />
</RequireRole>
}
/>
<Route
path="/doctor"
element={
<RequireRole user={user} role="doctor">
<DoctorLayout user={user} onLogout={handleLogout} />
</RequireRole>
}
>
<Route index element={<Navigate to="dashboard" replace />} />
<Route
path="dashboard"
element={<DoctorDashboard user={user} onNavigate={(page) => navigate(`/doctor/${page}`)} />}
/>
<Route path="patients" element={<DoctorPatients />} />
<Route path="patients/:id/history" element={<DoctorPatientHistory />} />
<Route path="appointments" element={<DoctorAppointments />} />
<Route path="schedule" element={<DoctorSchedule />} />
<Route path="records" element={<DoctorMedicalRecords />} />
<Route path="settings" element={<DoctorSettings />} />
<Route path="profile" element={<DoctorProfile />} />
</Route>
<Route
path="*"
element={
<RequireAuth user={user}>
<Navigate to={getHomePathForRole(user?.role)} replace />
</RequireAuth>
}
/>
</Routes>
);
}