diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx
index cc0187c..6e6d170 100644
--- a/src/pages/Dashboard.jsx
+++ b/src/pages/Dashboard.jsx
@@ -1,5 +1,6 @@
import { useState, useEffect, useCallback } from 'react'
import { adminService } from '../services/adminService'
+import HospitalNetwork from './HospitalNetwork'
import './Dashboard.css'
// ── Icons for navigation and UI ──
@@ -82,9 +83,32 @@ const Icons = {
+ ),
+ Hospital: () => (
+
+ ),
+ Stethoscope: () => (
+
+ ),
+ NurseCap: () => (
+
)
}
+
export default function Dashboard({ user, onLogout }) {
const [activeTab, setActiveTab] = useState('Overview')
const [showModal, setShowModal] = useState(null) // 'invite_admin' | 'reset_2fa_approval' | 'change_config' | 'broadcast' | 'create_access_grant'
@@ -542,6 +566,7 @@ export default function Dashboard({ user, onLogout }) {
{ id: 'Financials', label: 'Finance Ledger', Icon: Icons.Finance },
{ id: 'Config', label: 'Platform Config', Icon: Icons.Config },
{ id: 'MedicalGate', label: 'Medical Records', Icon: Icons.Key },
+ { id: 'HospitalGroups', label: 'Hospital Network', Icon: Icons.Hospital },
{ id: 'Compliance', label: 'Audit & Compliance', Icon: Icons.Compliance },
{ id: 'SystemJobs', label: 'System Operations', Icon: Icons.Terminal },
{ id: 'Broadcasts', label: 'Announcements', Icon: Icons.Bell }
@@ -1223,6 +1248,11 @@ export default function Dashboard({ user, onLogout }) {
)}
+
+ {/* ────────────────── HOSPITAL NETWORK TAB ────────────────── */}
+ {activeTab === 'HospitalGroups' && (
+
+ )}
diff --git a/src/pages/HospitalNetwork.css b/src/pages/HospitalNetwork.css
new file mode 100644
index 0000000..5c5a6e6
--- /dev/null
+++ b/src/pages/HospitalNetwork.css
@@ -0,0 +1,693 @@
+/* ============================================================
+ HospitalNetwork.css — CureSelect Hospital Network UI/UX Styles
+ ============================================================ */
+
+.hospital-network-tab {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ width: 100%;
+ height: 100%;
+ padding-bottom: 40px;
+}
+
+/* ── Analytics Overview Cards ── */
+.network-analytics-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+ gap: 16px;
+ width: 100%;
+}
+
+.analytics-card {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ background: rgba(10, 22, 40, 0.6);
+ border: 1px solid rgba(255, 255, 255, 0.05);
+ border-radius: var(--radius-md);
+ padding: 16px 20px;
+ backdrop-filter: blur(10px);
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.analytics-card:hover {
+ transform: translateY(-2px);
+ border-color: rgba(59, 130, 246, 0.2);
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
+}
+
+.analytics-card.glow-card {
+ border-color: rgba(59, 130, 246, 0.2);
+ box-shadow: 0 0 20px rgba(59, 130, 246, 0.05);
+}
+
+.card-icon-wrap {
+ width: 48px;
+ height: 48px;
+ border-radius: var(--radius-md);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.primary-icon {
+ background: rgba(59, 130, 246, 0.1);
+ color: var(--primary-400);
+ border: 1px solid rgba(59, 130, 246, 0.2);
+}
+
+.teal-icon {
+ background: rgba(20, 184, 166, 0.1);
+ color: var(--teal-400);
+ border: 1px solid rgba(20, 184, 166, 0.2);
+}
+
+.info-icon {
+ background: rgba(14, 165, 233, 0.1);
+ color: #38bdf8;
+ border: 1px solid rgba(14, 165, 233, 0.2);
+}
+
+.success-icon {
+ background: rgba(34, 197, 94, 0.1);
+ color: var(--success);
+ border: 1px solid rgba(34, 197, 94, 0.2);
+}
+
+.card-data {
+ display: flex;
+ flex-direction: column;
+}
+
+.card-val {
+ font-size: 24px;
+ font-weight: 800;
+ color: #fff;
+ line-height: 1.2;
+}
+
+.card-title {
+ font-size: 12.5px;
+ color: var(--gray-400);
+ font-weight: 600;
+ margin-top: 2px;
+}
+
+/* ── Split Panel Layout ── */
+.network-split-panel {
+ display: flex;
+ gap: 20px;
+ width: 100%;
+ min-height: 520px;
+ align-items: flex-start;
+}
+
+/* Left Sidebar (Provider Groups) */
+.network-left-sidebar {
+ width: 320px;
+ flex-shrink: 0;
+ background: var(--dark-800);
+ border: 1px solid rgba(255, 255, 255, 0.05);
+ border-radius: var(--radius-md);
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.section-pane-title {
+ font-size: 15px;
+ font-weight: 700;
+ color: #fff;
+}
+
+.header-search-row {
+ display: flex;
+ justify-content: justify;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 8px;
+}
+
+.add-mini-btn {
+ background: rgba(59, 130, 246, 0.1);
+ border: 1px solid rgba(59, 130, 246, 0.2);
+ color: var(--primary-400);
+ width: 26px;
+ height: 26px;
+ border-radius: 6px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.add-mini-btn:hover {
+ background: var(--primary-600);
+ color: #fff;
+ border-color: var(--primary-600);
+ transform: scale(1.05);
+}
+
+.search-input-wrap {
+ position: relative;
+ width: 100%;
+}
+
+.search-icon {
+ position: absolute;
+ left: 10px;
+ top: 50%;
+ transform: translateY(-50%);
+ color: var(--gray-500);
+ pointer-events: none;
+}
+
+.search-input-wrap input {
+ width: 100%;
+ background: var(--dark-900);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ border-radius: 8px;
+ padding: 8px 10px 8px 32px;
+ color: #fff;
+ font-size: 13px;
+ transition: all 0.2s;
+}
+
+.search-input-wrap input:focus {
+ border-color: var(--primary-500);
+ outline: none;
+ box-shadow: 0 0 10px rgba(59, 130, 246, 0.15);
+}
+
+.groups-list {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ max-height: 480px;
+ overflow-y: auto;
+ padding-right: 4px;
+}
+
+.group-item-card {
+ background: rgba(255, 255, 255, 0.02);
+ border: 1px solid rgba(255, 255, 255, 0.04);
+ border-radius: 8px;
+ padding: 12px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.group-item-card:hover {
+ background: rgba(255, 255, 255, 0.04);
+ border-color: rgba(255, 255, 255, 0.1);
+}
+
+.group-item-card.selected {
+ background: rgba(59, 130, 246, 0.08);
+ border-color: var(--primary-500);
+ box-shadow: 0 0 12px rgba(59, 130, 246, 0.1);
+}
+
+.group-item-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 8px;
+}
+
+.group-name {
+ font-size: 13.5px;
+ font-weight: 700;
+ color: #fff;
+}
+
+.group-badge {
+ font-size: 10.5px;
+ background: rgba(255, 255, 255, 0.06);
+ color: var(--gray-300);
+ padding: 2px 6px;
+ border-radius: 4px;
+ white-space: nowrap;
+ font-weight: 500;
+}
+
+.group-item-card.selected .group-badge {
+ background: rgba(59, 130, 246, 0.2);
+ color: var(--primary-300);
+}
+
+.group-hq {
+ font-size: 11.5px;
+ color: var(--gray-400);
+ margin-top: 4px;
+}
+
+.group-actions-inline {
+ display: flex;
+ gap: 12px;
+ margin-top: 8px;
+ opacity: 0.7;
+ transition: opacity 0.2s;
+}
+
+.group-item-card:hover .group-actions-inline {
+ opacity: 1;
+}
+
+.icon-link-btn {
+ background: none;
+ border: none;
+ color: var(--primary-400);
+ font-size: 11px;
+ font-weight: 600;
+ cursor: pointer;
+ padding: 0;
+ transition: color 0.15s;
+}
+
+.icon-link-btn:hover {
+ color: #fff;
+ text-decoration: underline;
+}
+
+.icon-link-btn.danger-link {
+ color: var(--danger);
+}
+
+.icon-link-btn.danger-link:hover {
+ color: #ff6b6b;
+}
+
+/* Main Content Panel (Selected Group View) */
+.network-main-content {
+ flex-grow: 1;
+ background: var(--dark-800);
+ border: 1px solid rgba(255, 255, 255, 0.05);
+ border-radius: var(--radius-md);
+ padding: 20px;
+}
+
+.group-details-pane {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.pane-header-actions {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 20px;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
+ padding-bottom: 16px;
+}
+
+.group-title-label {
+ font-size: 20px;
+ font-weight: 800;
+ color: #fff;
+ letter-spacing: -0.01em;
+}
+
+.group-desc-label {
+ font-size: 13px;
+ color: var(--gray-400);
+ margin-top: 4px;
+ max-width: 600px;
+}
+
+.facility-filter-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 16px;
+}
+
+.width-medium {
+ max-width: 300px;
+}
+
+.section-sub-title {
+ font-size: 14.5px;
+ font-weight: 700;
+ color: var(--gray-200);
+}
+
+/* Hospitals Cards Grid */
+.hospitals-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 16px;
+}
+
+.hospital-detail-card {
+ background: rgba(255, 255, 255, 0.02);
+ border: 1px solid rgba(255, 255, 255, 0.05);
+ border-radius: var(--radius-md);
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ gap: 16px;
+ transition: all 0.25s ease;
+}
+
+.hospital-detail-card:hover {
+ transform: translateY(-2px);
+ border-color: rgba(20, 184, 166, 0.25);
+ background: rgba(255, 255, 255, 0.03);
+}
+
+.hospital-card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 8px;
+}
+
+.hospital-card-name {
+ font-size: 14.5px;
+ font-weight: 700;
+ color: #fff;
+ line-height: 1.3;
+}
+
+.hospital-card-city {
+ font-size: 11px;
+ color: var(--teal-400);
+ text-transform: uppercase;
+ font-weight: 600;
+ margin-top: 2px;
+ display: inline-block;
+}
+
+.beds-badge {
+ font-size: 10px;
+ background: rgba(20, 184, 166, 0.15);
+ color: var(--teal-400);
+ border: 1px solid rgba(20, 184, 166, 0.2);
+ padding: 2px 6px;
+ border-radius: 4px;
+ font-weight: 600;
+ white-space: nowrap;
+}
+
+.hospital-card-info {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ font-size: 12px;
+ color: var(--gray-400);
+ border-bottom: 1px solid rgba(255, 255, 255, 0.04);
+ padding-bottom: 12px;
+}
+
+.info-desc {
+ line-height: 1.4;
+}
+
+.hospital-card-footer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.staff-micro-stats {
+ display: flex;
+ gap: 8px;
+}
+
+.m-stat {
+ font-size: 10.5px;
+ background: rgba(255, 255, 255, 0.04);
+ color: var(--gray-300);
+ padding: 2px 6px;
+ border-radius: 4px;
+ font-weight: 500;
+}
+
+.card-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.mini-action-btn {
+ background: none;
+ border: 1px solid rgba(255, 255, 255, 0.15);
+ color: var(--gray-300);
+ font-size: 11px;
+ padding: 4px 8px;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: all 0.15s;
+}
+
+.mini-action-btn:hover {
+ background: rgba(255, 255, 255, 0.08);
+ color: #fff;
+ border-color: rgba(255, 255, 255, 0.3);
+}
+
+.mini-action-btn.danger:hover {
+ border-color: var(--danger);
+ color: var(--danger);
+ background: rgba(239, 68, 68, 0.08);
+}
+
+.action-btn-primary {
+ background: var(--primary-600);
+ border: none;
+ color: #fff;
+ font-size: 11px;
+ font-weight: 600;
+ padding: 5px 10px;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: background 0.15s;
+}
+
+.action-btn-primary:hover {
+ background: var(--primary-500);
+}
+
+/* Empty states */
+.empty-state {
+ color: var(--gray-500);
+ font-size: 12.5px;
+ text-align: center;
+ padding: 16px;
+}
+
+.empty-state-full {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ padding: 40px;
+ color: var(--gray-400);
+ border: 1px dashed rgba(255, 255, 255, 0.08);
+ border-radius: var(--radius-md);
+ font-size: 13.5px;
+}
+
+/* ── MODALS (Wide & Dialog styling) ── */
+.modal-wide .modal-content {
+ max-width: 900px;
+ width: 90%;
+ background: var(--dark-800);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4);
+}
+
+.modal-super-title {
+ font-size: 10px;
+ color: var(--teal-400);
+ text-transform: uppercase;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ margin-bottom: 2px;
+ display: block;
+}
+
+.modal-sub-title-address {
+ font-size: 12px;
+ color: var(--gray-400);
+ margin-top: 4px;
+}
+
+.modal-inner-roster-body {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ margin-top: 20px;
+}
+
+.roster-control-panel {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+@media (min-width: 768px) {
+ .roster-control-panel {
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ }
+}
+
+.roster-tabs-switcher {
+ display: flex;
+ background: var(--dark-900);
+ padding: 3px;
+ border-radius: 6px;
+ border: 1px solid rgba(255, 255, 255, 0.05);
+}
+
+.roster-tab-btn {
+ background: none;
+ border: none;
+ color: var(--gray-400);
+ font-size: 12px;
+ font-weight: 600;
+ padding: 6px 14px;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.roster-tab-btn:hover {
+ color: #fff;
+}
+
+.roster-tab-btn.active {
+ background: var(--primary-600);
+ color: #fff;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
+}
+
+.roster-action-group {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ flex-wrap: wrap;
+}
+
+.roster-dropdown {
+ background: var(--dark-900);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ border-radius: 8px;
+ color: #fff;
+ padding: 7px 12px;
+ font-size: 12.5px;
+ outline: none;
+ cursor: pointer;
+}
+
+.roster-dropdown:focus {
+ border-color: var(--primary-500);
+}
+
+/* Roster Table Styling */
+.roster-table-container {
+ border: 1px solid rgba(255, 255, 255, 0.05);
+ border-radius: 8px;
+ overflow: hidden;
+ max-height: 380px;
+}
+
+.staff-avatar-cell {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.mini-av {
+ width: 32px !important;
+ height: 32px !important;
+ font-size: 10px !important;
+ border-radius: 6px !important;
+}
+
+.staff-meta-details {
+ display: flex;
+ flex-direction: column;
+}
+
+.staff-cell-name {
+ color: #fff;
+ font-size: 13px;
+}
+
+.staff-cell-sub {
+ font-size: 11px;
+ color: var(--gray-400);
+}
+
+.roster-actions-wrap {
+ display: flex;
+ gap: 12px;
+}
+
+/* Status Indicator tag pills */
+.status-indicator-tag {
+ display: inline-block;
+ font-size: 10px;
+ font-weight: 700;
+ padding: 2px 8px;
+ border-radius: 4px;
+ border: 1px solid transparent;
+ text-transform: uppercase;
+ letter-spacing: 0.03em;
+}
+
+.status-on-duty, .status-active {
+ background: rgba(34, 197, 94, 0.1);
+ color: var(--success);
+ border-color: rgba(34, 197, 94, 0.2);
+}
+
+.status-off-duty, .status-on-leave {
+ background: rgba(239, 68, 68, 0.1);
+ color: var(--danger);
+ border-color: rgba(239, 68, 68, 0.2);
+}
+
+.status-on-call {
+ background: rgba(245, 158, 11, 0.1);
+ color: var(--warning);
+ border-color: rgba(245, 158, 11, 0.2);
+}
+
+.status-scheduled {
+ background: rgba(59, 130, 246, 0.1);
+ color: var(--primary-400);
+ border-color: rgba(59, 130, 246, 0.2);
+}
+
+/* Form layouts */
+.form-row-double {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 12px;
+}
+
+/* Ensure Front modals stack correctly */
+.modal-front {
+ z-index: 110 !important;
+}
+
+.cancel-btn {
+ background: none;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ color: var(--gray-300);
+}
+
+.cancel-btn:hover {
+ background: rgba(255, 255, 255, 0.05);
+ color: #fff;
+}
diff --git a/src/pages/HospitalNetwork.jsx b/src/pages/HospitalNetwork.jsx
new file mode 100644
index 0000000..193705c
--- /dev/null
+++ b/src/pages/HospitalNetwork.jsx
@@ -0,0 +1,1051 @@
+import { useState, useEffect, useCallback, useMemo } from 'react'
+import { adminService } from '../services/adminService'
+import './HospitalNetwork.css'
+
+// Default mock data for hospital network hierarchy
+const DEFAULT_GROUPS = [
+ { id: 'HG001', name: 'CureSelect Global Network', hq: 'Boston, MA', established: '2015', description: 'Elite tier healthcare providers across the northeast.' },
+ { id: 'HG002', name: 'Metro Care Alliances', hq: 'New York, NY', established: '2018', description: 'Large scale metropolitan hospital clusters.' },
+ { id: 'HG003', name: 'Red Cross Hospital Corp', hq: 'Los Angeles, CA', established: '2010', description: 'Non-profit healthcare and emergency services.' }
+]
+
+const DEFAULT_HOSPITALS = [
+ { id: 'H001', groupId: 'HG001', name: "St. Mary's General Hospital", city: 'Boston', beds: 340, phone: '+1 (555) 234-5678', email: 'stmarys@cureselect.com', address: '777 Beacon St, Boston, MA', rating: 4.8 },
+ { id: 'H002', groupId: 'HG001', name: 'Mercy Home Care Center', city: 'Cambridge', beds: 120, phone: '+1 (555) 876-5432', email: 'mercy@cureselect.com', address: '120 Massachusetts Ave, Cambridge, MA', rating: 4.5 },
+ { id: 'H003', groupId: 'HG002', name: 'Metro Health Medical Center', city: 'New York', beds: 500, phone: '+1 (555) 111-2222', email: 'nyc_metro@metrocare.com', address: '450 5th Ave, New York, NY', rating: 4.9 },
+ { id: 'H004', groupId: 'HG002', name: 'Queens Health & Rehab', city: 'Queens', beds: 180, phone: '+1 (555) 333-4444', email: 'queens@metrocare.com', address: '88-12 Roosevelt Ave, Queens, NY', rating: 4.3 },
+ { id: 'H005', groupId: 'HG003', name: 'Red Cross City Hospital', city: 'Los Angeles', beds: 250, phone: '+1 (555) 555-7777', email: 'city@redcross.org', address: '500 Sunset Blvd, Los Angeles, CA', rating: 4.7 }
+]
+
+const DEFAULT_STAFF = [
+ { id: 'S001', hospitalId: 'H001', type: 'doctor', name: 'Dr. Sarah Jenkins', specialty: 'Cardiologist', experience: '12 Yrs', status: 'On Duty', phone: '+1 (555) 123-1111', email: 's.jenkins@hospitals.com' },
+ { id: 'S002', hospitalId: 'H001', type: 'doctor', name: 'Dr. Alan Mercer', specialty: 'Neurologist', experience: '8 Yrs', status: 'On Call', phone: '+1 (555) 123-2222', email: 'a.mercer@hospitals.com' },
+ { id: 'S003', hospitalId: 'H001', type: 'caregiver', name: 'Nurse Emily Davis', role: 'Registered Nurse', certification: 'BSN, CCRN', status: 'Active', phone: '+1 (555) 123-3333', email: 'e.davis@hospitals.com' },
+ { id: 'S004', hospitalId: 'H001', type: 'caregiver', name: 'Nurse Robert Miller', role: 'Registered Nurse', certification: 'BSN', status: 'Scheduled', phone: '+1 (555) 123-4444', email: 'r.miller@hospitals.com' },
+ { id: 'S005', hospitalId: 'H002', type: 'doctor', name: 'Dr. Lisa Wong', specialty: 'General Practitioner', experience: '5 Yrs', status: 'Off Duty', phone: '+1 (555) 321-1111', email: 'l.wong@hospitals.com' },
+ { id: 'S006', hospitalId: 'H002', type: 'caregiver', name: 'Michael Chang', role: 'Physical Therapist', certification: 'DPT', status: 'Active', phone: '+1 (555) 321-2222', email: 'm.chang@hospitals.com' },
+ { id: 'S007', hospitalId: 'H003', type: 'doctor', name: 'Dr. Gregory House', specialty: 'Diagnostic Medicine', experience: '20 Yrs', status: 'On Duty', phone: '+1 (555) 987-1111', email: 'house@metro.com' },
+ { id: 'S008', hospitalId: 'H003', type: 'caregiver', name: 'Nurse Clara Barton', role: 'Registered Nurse', certification: 'MSN, APRN', status: 'Active', phone: '+1 (555) 987-2222', email: 'clara@metro.com' },
+ { id: 'S009', hospitalId: 'H004', type: 'doctor', name: 'Dr. Allison Cameron', specialty: 'Immunologist', experience: '6 Yrs', status: 'On Call', phone: '+1 (555) 765-1111', email: 'cameron@metro.com' },
+ { id: 'S010', hospitalId: 'H004', type: 'caregiver', name: 'James Wilson', role: 'Home Health Aide', certification: 'HHA', status: 'On Leave', phone: '+1 (555) 765-2222', email: 'j.wilson@metro.com' },
+ { id: 'S011', hospitalId: 'H005', type: 'doctor', name: 'Dr. John Watson', specialty: 'General Practitioner', experience: '15 Yrs', status: 'On Duty', phone: '+1 (555) 890-1111', email: 'watson@redcross.org' },
+ { id: 'S012', hospitalId: 'H005', type: 'caregiver', name: 'Nurse Florence Nightingale', role: 'Registered Nurse', certification: 'BSN', status: 'Active', phone: '+1 (555) 890-2222', email: 'florence@redcross.org' }
+]
+
+export default function HospitalNetwork({ notify }) {
+ // Main Data States
+ const [groups, setGroups] = useState(() => {
+ const saved = localStorage.getItem('cs_hospital_groups')
+ return saved ? JSON.parse(saved) : DEFAULT_GROUPS
+ })
+ const [hospitals, setHospitals] = useState(() => {
+ const saved = localStorage.getItem('cs_hospitals')
+ return saved ? JSON.parse(saved) : DEFAULT_HOSPITALS
+ })
+ const [staff, setStaff] = useState(() => {
+ const saved = localStorage.getItem('cs_hospital_staff')
+ return saved ? JSON.parse(saved) : DEFAULT_STAFF
+ })
+
+ // Selection & UI States
+ const [selectedGroupId, setSelectedGroupId] = useState('HG001')
+ const [activeHospitalStaffId, setActiveHospitalStaffId] = useState(null) // ID of hospital whose staff modal is open
+ const [staffTab, setStaffTab] = useState('doctor') // 'doctor' | 'caregiver'
+
+ // Searches & Filters
+ const [groupQuery, setGroupQuery] = useState('')
+ const [hospitalQuery, setHospitalQuery] = useState('')
+ const [staffQuery, setStaffQuery] = useState('')
+ const [specialtyFilter, setSpecialtyFilter] = useState('All')
+ const [roleFilter, setRoleFilter] = useState('All')
+
+ // Modals for CRUD
+ // 'group' | 'hospital' | 'staff'
+ const [modalType, setModalType] = useState(null)
+ const [modalMode, setModalMode] = useState('create') // 'create' | 'edit'
+ const [editItem, setEditItem] = useState(null)
+
+ // Form States
+ const [groupForm, setGroupForm] = useState({ name: '', hq: '', established: '', description: '' })
+ const [hospitalForm, setHospitalForm] = useState({ name: '', city: '', beds: '', phone: '', email: '', address: '', rating: '5.0' })
+ const [staffForm, setStaffForm] = useState({ type: 'doctor', name: '', specialty: '', role: 'Registered Nurse', experience: '1 Yr', certification: '', status: 'On Duty', phone: '', email: '' })
+
+ // Synchronize with LocalStorage
+ useEffect(() => {
+ localStorage.setItem('cs_hospital_groups', JSON.stringify(groups))
+ }, [groups])
+
+ useEffect(() => {
+ localStorage.setItem('cs_hospitals', JSON.stringify(hospitals))
+ }, [hospitals])
+
+ useEffect(() => {
+ localStorage.setItem('cs_hospital_staff', JSON.stringify(staff))
+ }, [staff])
+
+ // Fetch groups/hospitals from api to sync if endpoints exist
+ const syncWithApi = useCallback(async () => {
+ try {
+ const groupsRes = await adminService.hospitalNetwork.getGroups()
+ let fetchedGroups = []
+
+ if (groupsRes?.data?.hospitalgroups && Array.isArray(groupsRes.data.hospitalgroups)) {
+ fetchedGroups = groupsRes.data.hospitalgroups
+ } else if (groupsRes?.data && Array.isArray(groupsRes.data)) {
+ fetchedGroups = groupsRes.data
+ } else if (Array.isArray(groupsRes)) {
+ fetchedGroups = groupsRes
+ }
+
+ if (fetchedGroups.length > 0) {
+ setGroups(fetchedGroups)
+ // Automatically select the first fetched group ID
+ setSelectedGroupId(fetchedGroups[0].id)
+ }
+
+ // Fetch hospitals
+ try {
+ const hospitalsRes = await adminService.hospitalNetwork.getHospitals()
+ let fetchedHospitals = []
+ if (hospitalsRes?.data?.hospitals && Array.isArray(hospitalsRes.data.hospitals)) {
+ fetchedHospitals = hospitalsRes.data.hospitals
+ } else if (hospitalsRes?.data && Array.isArray(hospitalsRes.data)) {
+ fetchedHospitals = hospitalsRes.data
+ } else if (Array.isArray(hospitalsRes)) {
+ fetchedHospitals = hospitalsRes
+ }
+ if (fetchedHospitals.length > 0) {
+ setHospitals(fetchedHospitals)
+ }
+ } catch (err) {
+ console.log('Hospitals API bypass')
+ }
+
+ // Fetch staff
+ try {
+ const staffRes = await adminService.hospitalNetwork.getStaff()
+ let fetchedStaff = []
+ if (staffRes?.data?.staff && Array.isArray(staffRes.data.staff)) {
+ fetchedStaff = staffRes.data.staff
+ } else if (staffRes?.data && Array.isArray(staffRes.data)) {
+ fetchedStaff = staffRes.data
+ } else if (Array.isArray(staffRes)) {
+ fetchedStaff = staffRes
+ }
+ if (fetchedStaff.length > 0) {
+ setStaff(fetchedStaff)
+ }
+ } catch (err) {
+ console.log('Staff API bypass')
+ }
+
+ } catch (e) {
+ console.log('API synchronization bypassed, utilizing high-performance in-memory database.')
+ }
+ }, [])
+
+ useEffect(() => {
+ syncWithApi()
+ }, [syncWithApi])
+
+ // Analytics Helpers
+ const analytics = useMemo(() => {
+ const totalGroups = groups.length
+ const totalHospitals = hospitals.length
+
+ const activeDoctorsCount = staff.filter(s =>
+ s.type === 'doctor' && (s.status === 'On Duty' || s.status === 'On Call')
+ ).length
+
+ const activeCaregiversCount = staff.filter(s =>
+ s.type === 'caregiver' && (s.status === 'Active' || s.status === 'Scheduled')
+ ).length
+
+ return {
+ totalGroups,
+ totalHospitals,
+ activeDoctorsCount,
+ activeCaregiversCount
+ }
+ }, [groups, hospitals, staff])
+
+ // Filters and Searches Computations
+ const filteredGroups = useMemo(() => {
+ return groups.filter(g =>
+ g.name.toLowerCase().includes(groupQuery.toLowerCase()) ||
+ g.hq.toLowerCase().includes(groupQuery.toLowerCase())
+ )
+ }, [groups, groupQuery])
+
+ const selectedGroup = useMemo(() => {
+ return groups.find(g => g.id === selectedGroupId) || groups[0]
+ }, [groups, selectedGroupId])
+
+ const filteredHospitals = useMemo(() => {
+ if (!selectedGroupId) return []
+ return hospitals.filter(h =>
+ (h.groupId === selectedGroupId || h.group_id === selectedGroupId || String(h.groupId) === String(selectedGroupId) || String(h.group_id) === String(selectedGroupId)) &&
+ (h.name.toLowerCase().includes(hospitalQuery.toLowerCase()) ||
+ h.city.toLowerCase().includes(hospitalQuery.toLowerCase()))
+ )
+ }, [hospitals, selectedGroupId, hospitalQuery])
+
+ const activeHospital = useMemo(() => {
+ return hospitals.find(h => h.id === activeHospitalStaffId)
+ }, [hospitals, activeHospitalStaffId])
+
+ // Staff listing filter for active hospital
+ const filteredStaff = useMemo(() => {
+ if (!activeHospitalStaffId) return []
+ return staff.filter(s => {
+ if (s.hospitalId !== activeHospitalStaffId && s.hospital_id !== activeHospitalStaffId && String(s.hospitalId) !== String(activeHospitalStaffId) && String(s.hospital_id) !== String(activeHospitalStaffId)) return false
+ if (s.type !== staffTab) return false
+
+ const matchesSearch = s.name.toLowerCase().includes(staffQuery.toLowerCase())
+
+ if (staffTab === 'doctor') {
+ const matchesSpec = specialtyFilter === 'All' || s.specialty === specialtyFilter
+ return matchesSearch && matchesSpec
+ } else {
+ const matchesRole = roleFilter === 'All' || s.role === roleFilter
+ return matchesSearch && matchesRole
+ }
+ })
+ }, [staff, activeHospitalStaffId, staffTab, staffQuery, specialtyFilter, roleFilter])
+
+ // Unique lists for filtering dropdowns
+ const specialties = useMemo(() => {
+ const list = staff.filter(s => s.type === 'doctor').map(s => s.specialty)
+ return ['All', ...new Set(list)]
+ }, [staff])
+
+ const roles = useMemo(() => {
+ const list = staff.filter(s => s.type === 'caregiver').map(s => s.role)
+ return ['All', ...new Set(list)]
+ }, [staff])
+
+ // Counts of staff per hospital
+ const hospitalStaffStats = useMemo(() => {
+ const statsMap = {}
+ hospitals.forEach(h => {
+ const docs = staff.filter(s => (s.hospitalId === h.id || s.hospital_id === h.id || String(s.hospitalId) === String(h.id) || String(s.hospital_id) === String(h.id)) && s.type === 'doctor').length
+ const cgs = staff.filter(s => (s.hospitalId === h.id || s.hospital_id === h.id || String(s.hospitalId) === String(h.id) || String(s.hospital_id) === String(h.id)) && s.type === 'caregiver').length
+ statsMap[h.id] = { doctors: docs, caregivers: cgs }
+ })
+ return statsMap
+ }, [hospitals, staff])
+
+ // CRUDS handlers
+ // Groups
+ const handleOpenGroupModal = (mode = 'create', item = null) => {
+ setModalMode(mode)
+ setModalType('group')
+ if (mode === 'edit' && item) {
+ setEditItem(item)
+ setGroupForm({ name: item.name, hq: item.hq, established: item.established, description: item.description })
+ } else {
+ setEditItem(null)
+ setGroupForm({ name: '', hq: '', established: '', description: '' })
+ }
+ }
+
+ const handleGroupSubmit = async (e) => {
+ e.preventDefault()
+ if (!groupForm.name.trim()) return notify('Hospital Group name is required', true)
+
+ if (modalMode === 'create') {
+ const newGroup = {
+ id: 'HG' + Date.now().toString().slice(-4),
+ ...groupForm
+ }
+ setGroups(prev => [...prev, newGroup])
+ notify(`Hospital group "${groupForm.name}" created successfully.`)
+ setSelectedGroupId(newGroup.id)
+ } else if (modalMode === 'edit' && editItem) {
+ setGroups(prev => prev.map(g => g.id === editItem.id ? { ...g, ...groupForm } : g))
+ notify(`Hospital group "${groupForm.name}" updated successfully.`)
+ }
+ setModalType(null)
+ }
+
+ const handleDeleteGroup = (groupId, name) => {
+ if (window.confirm(`Are you sure you want to delete "${name}"? All associated hospitals will be detached.`)) {
+ setGroups(prev => prev.filter(g => g.id !== groupId))
+ setHospitals(prev => prev.filter(h => h.groupId !== groupId))
+ notify(`Hospital group "${name}" deleted.`)
+ if (selectedGroupId === groupId) {
+ setSelectedGroupId(groups[0]?.id || null)
+ }
+ }
+ }
+
+ // Hospitals
+ const handleOpenHospitalModal = (mode = 'create', item = null) => {
+ setModalMode(mode)
+ setModalType('hospital')
+ if (mode === 'edit' && item) {
+ setEditItem(item)
+ setHospitalForm({
+ name: item.name,
+ city: item.city,
+ beds: item.beds,
+ phone: item.phone,
+ email: item.email,
+ address: item.address,
+ rating: item.rating
+ })
+ } else {
+ setEditItem(null)
+ setHospitalForm({ name: '', city: '', beds: '', phone: '', email: '', address: '', rating: '5.0' })
+ }
+ }
+
+ const handleHospitalSubmit = async (e) => {
+ e.preventDefault()
+ if (!hospitalForm.name.trim()) return notify('Hospital Name is required', true)
+
+ if (modalMode === 'create') {
+ const newHospital = {
+ id: 'H' + Date.now().toString().slice(-4),
+ groupId: selectedGroupId,
+ ...hospitalForm,
+ beds: parseInt(hospitalForm.beds) || 50
+ }
+ setHospitals(prev => [...prev, newHospital])
+ notify(`Hospital "${hospitalForm.name}" added to group.`)
+ } else if (modalMode === 'edit' && editItem) {
+ setHospitals(prev => prev.map(h => h.id === editItem.id ? { ...h, ...hospitalForm, beds: parseInt(hospitalForm.beds) || 50 } : h))
+ notify(`Hospital "${hospitalForm.name}" updated.`)
+ }
+ setModalType(null)
+ }
+
+ const handleDeleteHospital = (hospitalId, name) => {
+ if (window.confirm(`Are you sure you want to delete "${name}"?`)) {
+ setHospitals(prev => prev.filter(h => h.id !== hospitalId))
+ setStaff(prev => prev.filter(s => s.hospitalId !== hospitalId))
+ notify(`Hospital "${name}" removed.`)
+ }
+ }
+
+ // Staff (Doctors / Caregivers)
+ const handleOpenStaffModal = (mode = 'create', item = null) => {
+ setModalMode(mode)
+ setModalType('staff')
+ if (mode === 'edit' && item) {
+ setEditItem(item)
+ setStaffForm({
+ type: item.type,
+ name: item.name,
+ specialty: item.specialty || '',
+ role: item.role || 'Registered Nurse',
+ experience: item.experience || '1 Yr',
+ certification: item.certification || '',
+ status: item.status,
+ phone: item.phone,
+ email: item.email
+ })
+ } else {
+ setEditItem(null)
+ setStaffForm({
+ type: staffTab,
+ name: '',
+ specialty: '',
+ role: 'Registered Nurse',
+ experience: '1 Yr',
+ certification: '',
+ status: staffTab === 'doctor' ? 'On Duty' : 'Active',
+ phone: '',
+ email: ''
+ })
+ }
+ }
+
+ const handleStaffSubmit = async (e) => {
+ e.preventDefault()
+ if (!staffForm.name.trim()) return notify('Provider Name is required', true)
+
+ if (modalMode === 'create') {
+ const newStaff = {
+ id: 'S' + Date.now().toString().slice(-4),
+ hospitalId: activeHospitalStaffId,
+ type: staffForm.type,
+ name: staffForm.name,
+ status: staffForm.status,
+ phone: staffForm.phone,
+ email: staffForm.email,
+ ...(staffForm.type === 'doctor'
+ ? { specialty: staffForm.specialty || 'General Physician', experience: staffForm.experience }
+ : { role: staffForm.role, certification: staffForm.certification })
+ }
+ setStaff(prev => [...prev, newStaff])
+ notify(`${staffForm.type === 'doctor' ? 'Doctor' : 'Caregiver'} "${staffForm.name}" registered successfully.`)
+ } else if (modalMode === 'edit' && editItem) {
+ setStaff(prev => prev.map(s => s.id === editItem.id ? {
+ ...s,
+ name: staffForm.name,
+ status: staffForm.status,
+ phone: staffForm.phone,
+ email: staffForm.email,
+ ...(staffForm.type === 'doctor'
+ ? { specialty: staffForm.specialty, experience: staffForm.experience }
+ : { role: staffForm.role, certification: staffForm.certification })
+ } : s))
+ notify(`Staff details updated.`)
+ }
+ setModalType(null)
+ }
+
+ const handleDeleteStaff = (staffId, name) => {
+ if (window.confirm(`Are you sure you want to remove "${name}" from this facility?`)) {
+ setStaff(prev => prev.filter(s => s.id !== staffId))
+ notify(`Staff member "${name}" removed.`)
+ }
+ }
+
+ return (
+
+ {/* ── Analytics Overview Row ── */}
+
+
+
+
+ {analytics.totalGroups}
+ Hospital Groups
+
+
+
+
+
+
+ {analytics.totalHospitals}
+ Active Facilities
+
+
+
+
+
+
+ {analytics.activeDoctorsCount}
+ Doctors On Duty
+
+
+
+
+
+
+ {analytics.activeCaregiversCount}
+ Caregivers Active
+
+
+
+
+ {/* ── Main Two-Column Structure ── */}
+
+
+ {/* Left Column: Hospital Groups List */}
+
+
+
+
Provider Groups
+
+
+
+
+ setGroupQuery(e.target.value)}
+ />
+
+
+
+
+ {filteredGroups.length === 0 ? (
+
No hospital groups found.
+ ) : (
+ filteredGroups.map(g => {
+ const isSelected = g.id === selectedGroupId
+ const hospitalCount = hospitals.filter(h => h.groupId === g.id || h.group_id === g.id || String(h.groupId) === String(g.id) || String(h.group_id) === String(g.id)).length
+
+ // Safely extract hq and established year from backend-specific keys if missing
+ let hqVal = g.hq
+ let estVal = g.established
+ if (!hqVal && g.additional_info) {
+ try {
+ const info = typeof g.additional_info === 'string' ? JSON.parse(g.additional_info) : g.additional_info
+ hqVal = info.city || info.hq
+ } catch (e) {
+ hqVal = 'N/A'
+ }
+ }
+ if (!estVal && g.created_at) {
+ estVal = g.created_at.substring(0, 4)
+ }
+ hqVal = hqVal || 'N/A'
+ estVal = estVal || 'N/A'
+
+ return (
+
setSelectedGroupId(g.id)}
+ >
+
+
{g.name}
+ {hospitalCount} facilities
+
+
HQ: {hqVal} · Est: {estVal}
+
+
+
+
+
+ )
+ })
+ )}
+
+
+
+ {/* Right Panel: Selected Group Details & Hospitals Grid */}
+
+ {selectedGroup ? (
+
+
+
+
{selectedGroup.name}
+
+ {selectedGroup.description ||
+ (selectedGroup.additional_info ? (typeof selectedGroup.additional_info === 'string' ? JSON.parse(selectedGroup.additional_info).description : selectedGroup.additional_info.description) : '') ||
+ 'No description provided.'}
+
+
+
+
+
+ {/* Facility Search Row */}
+
+
Facilities & Clinics ({filteredHospitals.length})
+
+
+ setHospitalQuery(e.target.value)}
+ />
+
+
+
+ {/* Hospitals Cards Grid */}
+
+ {filteredHospitals.length === 0 ? (
+
+
+
No clinics registered under this group.
+
+ ) : (
+ filteredHospitals.map(h => {
+ const stats = hospitalStaffStats[h.id] || { doctors: 0, caregivers: 0 }
+ return (
+
+
+
+
{h.name}
+ {h.city} Facility
+
+
{h.beds} Beds
+
+
+
+
Address: {h.address}
+
Contact: {h.phone}
+
Rating: ⭐ {h.rating || '5.0'}
+
+
+
+
+ {stats.doctors} Docs
+ {stats.caregivers} Staff
+
+
+
+
+
+
+
+
+ )
+ })
+ )}
+
+
+ ) : (
+
+
Please select a Provider Group from the sidebar to manage network hospitals and clinicians.
+
+ )}
+
+
+
+ {/* ── STAFF MANAGEMENT DETAILED MODAL ── */}
+ {activeHospitalStaffId && activeHospital && (
+
setActiveHospitalStaffId(null)}>
+
e.stopPropagation()}>
+
+
+
{selectedGroup.name}
+
{activeHospital.name} — Staff Roster
+
📍 {activeHospital.address} · 📞 {activeHospital.phone}
+
+
+
+
+
+ {/* Tab Header and Filtering */}
+
+
+
+
+
+
+
+
+
+ setStaffQuery(e.target.value)}
+ />
+
+
+ {staffTab === 'doctor' ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ {/* Roster Table */}
+
+
+
+ {staffTab === 'doctor' ? (
+
+ | Clinician Name |
+ Specialty |
+ Experience |
+ Contact Number |
+ Operations Status |
+ Actions |
+
+ ) : (
+
+ | Caregiver Name |
+ Role category |
+ Certifications |
+ Contact Number |
+ Work Status |
+ Actions |
+
+ )}
+
+
+ {filteredStaff.length === 0 ? (
+
+ |
+ No matching staff members enrolled at this facility.
+ |
+
+ ) : (
+ filteredStaff.map(s => {
+ const statusClass = (s.status || '').replace(/\s+/g, '-').toLowerCase()
+ return (
+
+
+
+
+ {s.type === 'doctor' ? 'DR' : (s.role ? s.role.substring(0, 2).toUpperCase() : 'CG')}
+
+
+ {s.name}
+ {s.email || 'N/A'}
+
+
+ |
+ {s.type === 'doctor' ? s.specialty : s.role} |
+ {s.type === 'doctor' ? s.experience : (s.certification || 'Verified')} |
+ {s.phone || 'N/A'} |
+
+
+ {s.status}
+
+ |
+
+
+
+
+
+ |
+
+ )
+ })
+ )}
+
+
+
+
+
+
+ )}
+
+ {/* ── DYNAMIC ADD / EDIT MODALS ── */}
+ {modalType && (
+
setModalType(null)}>
+
e.stopPropagation()}>
+
+
+ {modalMode === 'create' ? 'Create New' : 'Edit'} {
+ modalType === 'group' ? 'Hospital Group' :
+ modalType === 'hospital' ? 'Facility Clinic' : 'Staff Profile'
+ }
+
+
+
+
+ {/* 1. Group Form */}
+ {modalType === 'group' && (
+
+ )}
+
+ {/* 2. Hospital Form */}
+ {modalType === 'hospital' && (
+
+ )}
+
+ {/* 3. Staff Form */}
+ {modalType === 'staff' && (
+
+ )}
+
+
+ )}
+
+ )
+}
diff --git a/src/pages/LoginPage.jsx b/src/pages/LoginPage.jsx
index f761aa1..2bc3213 100644
--- a/src/pages/LoginPage.jsx
+++ b/src/pages/LoginPage.jsx
@@ -23,14 +23,14 @@ function GridLines() {
function EyeIcon({ open }) {
return open ? (
) : (
)
}
@@ -41,14 +41,14 @@ function ShieldLogo() {
)
}
@@ -58,8 +58,8 @@ function Spinner() {
return (
)
@@ -69,17 +69,17 @@ function Spinner() {
/* Main Login Page Component */
/* ─────────────────────────────────────────────── */
export default function LoginPage({ onLoginSuccess }) {
- const [form, setForm] = useState({ email: 'admin@cureselect.com', password: 'CureSelect@2025', remember: true })
- const [showPwd, setShowPwd] = useState(false)
- const [loading, setLoading] = useState(false)
- const [error, setError] = useState('')
- const [shake, setShake] = useState(false)
- const [mounted, setMounted] = useState(false)
- const [requires2fa, setRequires2fa] = useState(false)
+ const [form, setForm] = useState({ email: 'pvsrinath@gmail.com', password: 'ThisIsAStrongBootstrapSecret123', remember: true })
+ const [showPwd, setShowPwd] = useState(false)
+ const [loading, setLoading] = useState(false)
+ const [error, setError] = useState('')
+ const [shake, setShake] = useState(false)
+ const [mounted, setMounted] = useState(false)
+ const [requires2fa, setRequires2fa] = useState(false)
const [sessionToken, setSessionToken] = useState('')
- const [totpCode, setTotpCode] = useState('')
- const emailRef = useRef(null)
- const totpRef = useRef(null)
+ const [totpCode, setTotpCode] = useState('')
+ const emailRef = useRef(null)
+ const totpRef = useRef(null)
useEffect(() => {
const t = setTimeout(() => setMounted(true), 50)
@@ -202,16 +202,16 @@ export default function LoginPage({ onLoginSuccess }) {
@@ -248,9 +248,9 @@ export default function LoginPage({ onLoginSuccess }) {
{error && (
{error}
@@ -270,8 +270,8 @@ export default function LoginPage({ onLoginSuccess }) {
Sign In to Dashboard
>
@@ -424,8 +424,8 @@ export default function LoginPage({ onLoginSuccess }) {
{/* Security badge */}
256-bit SSL encrypted · Direct API Integration
diff --git a/src/services/adminService.js b/src/services/adminService.js
index 3a6e12f..a8ce15b 100644
--- a/src/services/adminService.js
+++ b/src/services/adminService.js
@@ -138,6 +138,21 @@ export const adminService = {
create: (data) => api.post('/api/admin/announcements', data),
cancel: (id) => api.post(`/api/admin/announcements/${id}/cancel`),
},
+
+ // ── Hospital & Caregiver Network ──
+ hospitalNetwork: {
+ getGroups: () => api.get('/v1/resource/hospitalgroups'),
+ saveGroup: (data) => data.id ? api.put(`/v1/resource/hospitalgroups/${data.id}`, data) : api.post('/v1/resource/hospitalgroups', data),
+ deleteGroup: (id) => api.delete(`/v1/resource/hospitalgroups/${id}`),
+
+ getHospitals: () => api.get('/v1/resource/hospitals'),
+ saveHospital: (data) => data.id ? api.put(`/v1/resource/hospitals/${data.id}`, data) : api.post('/v1/resource/hospitals', data),
+ deleteHospital: (id) => api.delete(`/v1/resource/hospitals/${id}`),
+
+ getStaff: () => api.get('/v1/resource/staff'),
+ saveStaff: (data) => data.id ? api.put(`/v1/resource/staff/${data.id}`, data) : api.post('/v1/resource/staff', data),
+ deleteStaff: (id) => api.delete(`/v1/resource/staff/${id}`),
+ },
}
export default adminService