feat: implement hospital network management module with CRUD operations and state synchronization
This commit is contained in:
@@ -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 = {
|
||||
<polyline points="1 20 1 14 7 14"/>
|
||||
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>
|
||||
</svg>
|
||||
),
|
||||
Hospital: () => (
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M18 22V8a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v14" />
|
||||
<path d="M4 22h16" />
|
||||
<path d="M10 16h4" />
|
||||
<path d="M12 14v4" />
|
||||
<path d="M10 10h4" />
|
||||
</svg>
|
||||
),
|
||||
Stethoscope: () => (
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M4.8 2.3A2.5 2.5 0 0 0 2.5 4.8v5A7.7 7.7 0 0 0 10 17.5h.3M19.2 2.3a2.5 2.5 0 0 1 2.3 2.5v5a7.7 7.7 0 0 1-7.5 7.7h-.3" />
|
||||
<path d="M12 17v4" />
|
||||
<circle cx="12" cy="21" r="2" />
|
||||
</svg>
|
||||
),
|
||||
NurseCap: () => (
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M2 22h20M6 12V6a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v6" />
|
||||
<path d="M12 3v19M6 12c0 2.5 2 4.5 4.5 4.5h3c2.5 0 4.5-2 4.5-4.5" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
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 }) {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ────────────────── HOSPITAL NETWORK TAB ────────────────── */}
|
||||
{activeTab === 'HospitalGroups' && (
|
||||
<HospitalNetwork notify={notify} />
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+34
-34
@@ -23,14 +23,14 @@ function GridLines() {
|
||||
function EyeIcon({ open }) {
|
||||
return open ? (
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/>
|
||||
<path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/>
|
||||
<line x1="1" y1="23" x2="23" y2="1"/>
|
||||
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94" />
|
||||
<path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19" />
|
||||
<line x1="1" y1="23" x2="23" y2="1" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -41,14 +41,14 @@ function ShieldLogo() {
|
||||
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="shieldGrad" x1="0" y1="0" x2="44" y2="44" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stopColor="#3b82f6"/>
|
||||
<stop offset="100%" stopColor="#14b8a6"/>
|
||||
<stop offset="0%" stopColor="#3b82f6" />
|
||||
<stop offset="100%" stopColor="#14b8a6" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M22 2L4 10v12c0 10.5 7.7 20.3 18 22.6C32.3 42.3 40 32.5 40 22V10L22 2z" fill="url(#shieldGrad)" opacity="0.15"/>
|
||||
<path d="M22 2L4 10v12c0 10.5 7.7 20.3 18 22.6C32.3 42.3 40 32.5 40 22V10L22 2z" stroke="url(#shieldGrad)" strokeWidth="1.5" fill="none"/>
|
||||
<line x1="22" y1="13" x2="22" y2="31" stroke="url(#shieldGrad)" strokeWidth="2.5" strokeLinecap="round"/>
|
||||
<line x1="13" y1="22" x2="31" y2="22" stroke="url(#shieldGrad)" strokeWidth="2.5" strokeLinecap="round"/>
|
||||
<path d="M22 2L4 10v12c0 10.5 7.7 20.3 18 22.6C32.3 42.3 40 32.5 40 22V10L22 2z" fill="url(#shieldGrad)" opacity="0.15" />
|
||||
<path d="M22 2L4 10v12c0 10.5 7.7 20.3 18 22.6C32.3 42.3 40 32.5 40 22V10L22 2z" stroke="url(#shieldGrad)" strokeWidth="1.5" fill="none" />
|
||||
<line x1="22" y1="13" x2="22" y2="31" stroke="url(#shieldGrad)" strokeWidth="2.5" strokeLinecap="round" />
|
||||
<line x1="13" y1="22" x2="31" y2="22" stroke="url(#shieldGrad)" strokeWidth="2.5" strokeLinecap="round" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -58,8 +58,8 @@ function Spinner() {
|
||||
return (
|
||||
<span className="spinner" aria-hidden="true">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<circle cx="10" cy="10" r="8" stroke="rgba(255,255,255,0.3)" strokeWidth="2"/>
|
||||
<path d="M10 2a8 8 0 0 1 8 8" stroke="#fff" strokeWidth="2" strokeLinecap="round"/>
|
||||
<circle cx="10" cy="10" r="8" stroke="rgba(255,255,255,0.3)" strokeWidth="2" />
|
||||
<path d="M10 2a8 8 0 0 1 8 8" stroke="#fff" strokeWidth="2" strokeLinecap="round" />
|
||||
</svg>
|
||||
</span>
|
||||
)
|
||||
@@ -69,7 +69,7 @@ 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 [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('')
|
||||
@@ -202,16 +202,16 @@ export default function LoginPage({ onLoginSuccess }) {
|
||||
<svg width="64" height="64" viewBox="0 0 44 44" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="hg1" x1="0" y1="0" x2="44" y2="44" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stopColor="#3b82f6"/>
|
||||
<stop offset="100%" stopColor="#14b8a6"/>
|
||||
<stop offset="0%" stopColor="#3b82f6" />
|
||||
<stop offset="100%" stopColor="#14b8a6" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M22 2L4 10v12c0 10.5 7.7 20.3 18 22.6C32.3 42.3 40 32.5 40 22V10L22 2z"
|
||||
fill="url(#hg1)" opacity="0.2"/>
|
||||
fill="url(#hg1)" opacity="0.2" />
|
||||
<path d="M22 2L4 10v12c0 10.5 7.7 20.3 18 22.6C32.3 42.3 40 32.5 40 22V10L22 2z"
|
||||
stroke="url(#hg1)" strokeWidth="1.5" fill="none"/>
|
||||
<line x1="22" y1="13" x2="22" y2="31" stroke="url(#hg1)" strokeWidth="2.5" strokeLinecap="round"/>
|
||||
<line x1="13" y1="22" x2="31" y2="22" stroke="url(#hg1)" strokeWidth="2.5" strokeLinecap="round"/>
|
||||
stroke="url(#hg1)" strokeWidth="1.5" fill="none" />
|
||||
<line x1="22" y1="13" x2="22" y2="31" stroke="url(#hg1)" strokeWidth="2.5" strokeLinecap="round" />
|
||||
<line x1="13" y1="22" x2="31" y2="22" stroke="url(#hg1)" strokeWidth="2.5" strokeLinecap="round" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
@@ -248,9 +248,9 @@ export default function LoginPage({ onLoginSuccess }) {
|
||||
{error && (
|
||||
<div className="error-banner" role="alert">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<line x1="12" y1="8" x2="12" y2="12"/>
|
||||
<line x1="12" y1="16" x2="12.01" y2="16"/>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<line x1="12" y1="8" x2="12" y2="12" />
|
||||
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||||
</svg>
|
||||
{error}
|
||||
</div>
|
||||
@@ -270,8 +270,8 @@ export default function LoginPage({ onLoginSuccess }) {
|
||||
<div className="field-wrapper">
|
||||
<span className="field-icon">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||
</svg>
|
||||
</span>
|
||||
<input
|
||||
@@ -319,8 +319,8 @@ export default function LoginPage({ onLoginSuccess }) {
|
||||
<div className="field-wrapper">
|
||||
<span className="field-icon">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/>
|
||||
<polyline points="22,6 12,13 2,6"/>
|
||||
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
|
||||
<polyline points="22,6 12,13 2,6" />
|
||||
</svg>
|
||||
</span>
|
||||
<input
|
||||
@@ -346,8 +346,8 @@ export default function LoginPage({ onLoginSuccess }) {
|
||||
<div className="field-wrapper">
|
||||
<span className="field-icon">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||
</svg>
|
||||
</span>
|
||||
<input
|
||||
@@ -404,9 +404,9 @@ export default function LoginPage({ onLoginSuccess }) {
|
||||
) : (
|
||||
<>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/>
|
||||
<polyline points="10 17 15 12 10 7"/>
|
||||
<line x1="15" y1="12" x2="3" y2="12"/>
|
||||
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4" />
|
||||
<polyline points="10 17 15 12 10 7" />
|
||||
<line x1="15" y1="12" x2="3" y2="12" />
|
||||
</svg>
|
||||
Sign In to Dashboard
|
||||
</>
|
||||
@@ -424,8 +424,8 @@ export default function LoginPage({ onLoginSuccess }) {
|
||||
{/* Security badge */}
|
||||
<div className="security-badge">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2"/>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" />
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
||||
</svg>
|
||||
256-bit SSL encrypted · Direct API Integration
|
||||
</div>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user