From 4873b360fc6e96fa36c19e9f5ad286184019ff67 Mon Sep 17 00:00:00 2001 From: mohamedshahil-csh Date: Sat, 29 Aug 2026 15:22:36 +0530 Subject: [PATCH] initialize foundational styles, dashboard layout, and hospital network module --- src/index.css | 79 ++-- src/pages/Dashboard.css | 133 +++---- src/pages/HospitalNetwork.css | 136 ++++--- src/pages/HospitalNetwork.jsx | 658 ++++++++++++++++++++++++++-------- src/pages/LoginPage.css | 38 +- 5 files changed, 729 insertions(+), 315 deletions(-) diff --git a/src/index.css b/src/index.css index b00c770..baa7420 100644 --- a/src/index.css +++ b/src/index.css @@ -9,45 +9,46 @@ } :root { - /* Brand Colors */ - --primary-50: #eef6ff; - --primary-100: #d9ecff; - --primary-200: #bcdcff; - --primary-300: #8ec5fe; - --primary-400: #5aa5fb; - --primary-500: #3b82f6; - --primary-600: #2563eb; - --primary-700: #1d4ed8; - --primary-800: #1e40af; - --primary-900: #1e3a8a; + /* Brand Colors — Cream-compatible blue palette */ + --primary-50: #fcfbfa; + --primary-100: #f7f5f0; + --primary-200: #f1ebd8; + --primary-300: #e2d3af; + --primary-400: #d0c098; + --primary-500: #2563eb; + --primary-600: #1d4ed8; + --primary-700: #1e3a8a; + --primary-800: #172554; + --primary-900: #0f172a; - --teal-400: #2dd4bf; - --teal-500: #14b8a6; - --teal-600: #0d9488; + /* Cream-compatible teal palette */ + --teal-400: #0d9488; + --teal-500: #0f766e; + --teal-600: #115e59; - /* Dark palette */ - --dark-900: #050d1a; - --dark-800: #0a1628; - --dark-700: #0f2040; - --dark-600: #162a52; - --dark-500: #1e3a6e; + /* Cream & White surface palette (was Dark palette) */ + --dark-900: #faf7f0; /* Page Background (soft creamy ivory) */ + --dark-800: #ffffff; /* Primary Card/Sidebar surface (crisp white) */ + --dark-700: #f5eedc; /* Darker cream for borders / card hovers */ + --dark-600: #eae1cc; /* Soft warm beige for borders/inputs */ + --dark-500: #d5c8ab; /* Deep beige for inactive borders/dividers */ - /* Neutrals */ - --gray-50: #f8fafc; - --gray-100: #f1f5f9; - --gray-200: #e2e8f0; - --gray-300: #cbd5e1; - --gray-400: #94a3b8; - --gray-500: #64748b; - --gray-600: #475569; - --gray-700: #334155; - --gray-800: #1e293b; - --gray-900: #0f172a; + /* Neutrals — soft dark brown/charcoal for text (was light neutrals) */ + --gray-50: #1e1b18; /* Body/Primary Text (soft espresso) */ + --gray-100: #29241f; /* Primary titles */ + --gray-200: #3b342c; /* Secondary text */ + --gray-300: #52493e; /* Medium description text */ + --gray-400: #6f6354; /* Muted text */ + --gray-500: #887b6a; /* Placeholders */ + --gray-600: #a29584; /* Soft dividers */ + --gray-700: #c1b5a5; + --gray-800: #e2dbcd; + --gray-900: #f3efe3; /* Semantic */ --danger: #ef4444; - --success: #22c55e; - --warning: #f59e0b; + --success: #16a34a; + --warning: #d97706; /* Typography */ --font-primary: 'Plus Jakarta Sans', 'Inter', system-ui, sans-serif; @@ -60,12 +61,12 @@ --radius-xl: 24px; --radius-full: 9999px; - /* Shadows */ - --shadow-sm: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); - --shadow-md: 0 4px 16px rgba(0,0,0,0.25); - --shadow-lg: 0 10px 40px rgba(0,0,0,0.35); - --shadow-glow: 0 0 40px rgba(59, 130, 246, 0.4); - --shadow-teal: 0 0 30px rgba(20, 184, 166, 0.3); + /* Shadows — soft warm ambient shadows */ + --shadow-sm: 0 2px 4px rgba(47, 43, 36, 0.04), 0 1px 2px rgba(47, 43, 36, 0.03); + --shadow-md: 0 6px 16px rgba(47, 43, 36, 0.06); + --shadow-lg: 0 12px 32px rgba(47, 43, 36, 0.08); + --shadow-glow: 0 0 30px rgba(37, 99, 235, 0.1); + --shadow-teal: 0 0 30px rgba(13, 148, 136, 0.08); } html { diff --git a/src/pages/Dashboard.css b/src/pages/Dashboard.css index 3826b1f..b27c673 100644 --- a/src/pages/Dashboard.css +++ b/src/pages/Dashboard.css @@ -15,7 +15,7 @@ .sidebar { width: 280px; background: var(--dark-800); - border-right: 1px solid rgba(255, 255, 255, 0.05); + border-right: 1px solid var(--dark-600); display: flex; flex-direction: column; justify-content: space-between; @@ -29,7 +29,7 @@ align-items: center; gap: 12px; padding: 8px 12px 24px 12px; - border-bottom: 1px solid rgba(255, 255, 255, 0.05); + border-bottom: 1px solid var(--dark-600); } .logo-icon-wrapper { @@ -51,7 +51,7 @@ .logo-brand { font-size: 18px; font-weight: 800; - color: #fff; + color: var(--gray-50); letter-spacing: -0.02em; } @@ -97,8 +97,8 @@ } .nav-item:hover { - color: #fff; - background: rgba(255, 255, 255, 0.03); + color: var(--gray-50); + background: rgba(0, 0, 0, 0.02); } .nav-item:hover svg { @@ -106,10 +106,10 @@ } .nav-item.active { - color: #fff; - background: linear-gradient(135deg, rgba(37, 99, 235, 0.15) 0%, rgba(20, 184, 166, 0.05) 100%); - border: 1px solid rgba(37, 99, 235, 0.25); - box-shadow: 0 4px 12px rgba(37, 99, 235, 0.1); + color: var(--primary-600); + background: linear-gradient(135deg, rgba(37, 99, 235, 0.08) 0%, rgba(20, 184, 166, 0.02) 100%); + border: 1px solid rgba(37, 99, 235, 0.15); + box-shadow: 0 4px 12px rgba(37, 99, 235, 0.05); } .nav-item.active svg { @@ -118,7 +118,7 @@ } .sidebar-footer { - border-top: 1px solid rgba(255, 255, 255, 0.05); + border-top: 1px solid var(--dark-600); padding-top: 20px; display: flex; flex-direction: column; @@ -153,7 +153,7 @@ .user-name { font-size: 14px; font-weight: 700; - color: #fff; + color: var(--gray-50); } .user-role { @@ -198,9 +198,9 @@ /* ── Topbar ── */ .topbar { height: 70px; - background: rgba(10, 22, 40, 0.5); + background: var(--dark-800); backdrop-filter: blur(8px); - border-bottom: 1px solid rgba(255, 255, 255, 0.05); + border-bottom: 1px solid var(--dark-700); display: flex; align-items: center; justify-content: space-between; @@ -220,8 +220,8 @@ gap: 8px; font-size: 12.5px; color: var(--gray-400); - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.06); + background: var(--dark-900); + border: 1px solid var(--dark-600); padding: 6px 12px; border-radius: var(--radius-full); } @@ -256,8 +256,8 @@ gap: 8px; font-size: 12px; color: var(--gray-400); - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.05); + background: var(--dark-900); + border: 1px solid var(--dark-600); padding: 6px 12px; border-radius: var(--radius-full); } @@ -286,7 +286,7 @@ .pane-title { font-size: 26px; font-weight: 800; - color: #fff; + color: var(--gray-50); letter-spacing: -0.02em; } @@ -326,8 +326,8 @@ } .metric-card { - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.06); + background: var(--dark-800); + border: 1px solid var(--dark-600); border-radius: var(--radius-lg); padding: 24px; display: flex; @@ -355,8 +355,8 @@ .glow-yellow .card-glass-glow { background: var(--warning); } .metric-card:hover { - background: rgba(255, 255, 255, 0.05); - border-color: rgba(255, 255, 255, 0.12); + background: var(--dark-700); + border-color: var(--dark-500); transform: translateY(-2px); } @@ -374,7 +374,7 @@ .metric-value { font-size: 36px; font-weight: 800; - color: #fff; + color: var(--gray-50); margin: 12px 0 6px 0; letter-spacing: -0.03em; } @@ -392,8 +392,8 @@ } .detail-panel { - background: rgba(10, 22, 40, 0.4); - border: 1px solid rgba(255, 255, 255, 0.05); + background: var(--dark-800); + border: 1px solid var(--dark-600); border-radius: var(--radius-lg); padding: 24px; } @@ -401,7 +401,7 @@ .panel-title { font-size: 16px; font-weight: 700; - color: #fff; + color: var(--gray-100); margin-bottom: 20px; } @@ -416,8 +416,8 @@ display: flex; align-items: center; justify-content: space-between; - background: rgba(255, 255, 255, 0.02); - border: 1px solid rgba(255, 255, 255, 0.06); + background: var(--dark-900); + border: 1px solid var(--dark-600); padding: 14px 18px; border-radius: var(--radius-md); } @@ -431,7 +431,7 @@ .pinger-name { font-size: 13.5px; font-weight: 600; - color: #fff; + color: var(--gray-50); } .pinger-value { @@ -474,7 +474,7 @@ .kpi-num { font-size: 22px; font-weight: 800; - color: #fff; + color: var(--gray-50); } .kpi-label { @@ -497,7 +497,10 @@ padding: 14px 16px; border-radius: var(--radius-md); border-left: 4px solid transparent; - background: rgba(255, 255, 255, 0.02); + background: var(--dark-900); + border-top: 1px solid var(--dark-600); + border-right: 1px solid var(--dark-600); + border-bottom: 1px solid var(--dark-600); } .alert-critical { @@ -533,9 +536,9 @@ } .resolve-btn { - background: rgba(255, 255, 255, 0.06); - border: 1px solid rgba(255, 255, 255, 0.1); - color: var(--gray-300); + background: var(--dark-800); + border: 1px solid var(--dark-600); + color: var(--gray-200); padding: 6px 12px; border-radius: var(--radius-sm); font-size: 12px; @@ -575,13 +578,13 @@ font-weight: 700; color: var(--gray-500); text-transform: uppercase; - border-bottom: 1px solid rgba(255, 255, 255, 0.05); + border-bottom: 1px solid var(--dark-600); } .telemetry-table td { padding: 16px; font-size: 13.5px; - border-bottom: 1px solid rgba(255, 255, 255, 0.03); + border-bottom: 1px solid var(--dark-700); } .hr-badge { @@ -609,12 +612,12 @@ } .status-indicator-tag { - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.08); + background: var(--dark-700); + border: 1px solid var(--dark-600); padding: 4px 10px; border-radius: var(--radius-full); font-size: 11px; - color: var(--gray-400); + color: var(--gray-300); } /* ── Admin Provisioning & Roster Cards ── */ @@ -625,20 +628,22 @@ } .cg-card { - background: rgba(255, 255, 255, 0.02); - border: 1px solid rgba(255, 255, 255, 0.05); + background: var(--dark-800); + border: 1px solid var(--dark-600); border-radius: var(--radius-lg); padding: 20px; display: flex; flex-direction: column; gap: 16px; transition: all 0.25s ease; + box-shadow: var(--shadow-sm); } .cg-card:hover { - background: rgba(255, 255, 255, 0.04); - border-color: rgba(59, 130, 246, 0.2); + background: var(--dark-700); + border-color: var(--primary-400); transform: translateY(-2px); + box-shadow: var(--shadow-md); } .cg-header { @@ -651,8 +656,8 @@ width: 48px; height: 48px; border-radius: 50%; - background: rgba(59, 130, 246, 0.15); - color: var(--primary-300); + background: rgba(59, 130, 246, 0.1); + color: var(--primary-600); display: flex; align-items: center; justify-content: center; @@ -663,7 +668,7 @@ .cg-name { font-size: 15px; font-weight: 700; - color: #fff; + color: var(--gray-50); } .cg-role { @@ -673,8 +678,8 @@ } .cg-body { - border-top: 1px solid rgba(255, 255, 255, 0.05); - border-bottom: 1px solid rgba(255, 255, 255, 0.05); + border-top: 1px solid var(--dark-600); + border-bottom: 1px solid var(--dark-600); padding: 12px 0; display: flex; flex-direction: column; @@ -753,8 +758,8 @@ /* ── Settings Options List ── */ .settings-options-list { - background: rgba(255, 255, 255, 0.02); - border: 1px solid rgba(255, 255, 255, 0.05); + background: var(--dark-800); + border: 1px solid var(--dark-600); border-radius: var(--radius-lg); padding: 8px 0; max-width: 100%; @@ -765,7 +770,7 @@ align-items: center; justify-content: space-between; padding: 20px 24px; - border-bottom: 1px solid rgba(255, 255, 255, 0.03); + border-bottom: 1px solid var(--dark-700); } .setting-row:last-child { @@ -782,7 +787,7 @@ .setting-label { font-size: 14px; font-weight: 700; - color: #fff; + color: var(--gray-100); } .setting-description { @@ -809,7 +814,7 @@ position: absolute; cursor: pointer; inset: 0; - background-color: rgba(255, 255, 255, 0.1); + background-color: var(--dark-700); transition: .3s; border-radius: 24px; } @@ -854,10 +859,12 @@ input:checked + .slider:before { .modal-content { background: var(--dark-800); - border: 1px solid rgba(255, 255, 255, 0.08); + border: 1px solid var(--dark-600); border-radius: var(--radius-lg); width: 100%; max-width: 480px; + max-height: 90vh; + overflow-y: auto; padding: 28px; box-shadow: var(--shadow-lg); animation: modalSlideUp 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); @@ -878,7 +885,7 @@ input:checked + .slider:before { .modal-header h3 { font-size: 18px; font-weight: 800; - color: #fff; + color: var(--gray-50); } .close-btn { @@ -891,7 +898,7 @@ input:checked + .slider:before { } .close-btn:hover { - color: #fff; + color: var(--gray-100); } .modal-form { @@ -914,11 +921,11 @@ input:checked + .slider:before { .form-group input, .form-group select { - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.08); + background: var(--dark-900); + border: 1px solid var(--dark-600); border-radius: var(--radius-md); padding: 12px; - color: #fff; + color: var(--gray-50); font-size: 13.5px; font-family: var(--font-primary); outline: none; @@ -940,7 +947,7 @@ input:checked + .slider:before { .cancel-btn { background: none; - border: 1px solid rgba(255, 255, 255, 0.08); + border: 1px solid var(--dark-600); color: var(--gray-300); padding: 10px 18px; border-radius: var(--radius-md); @@ -949,8 +956,8 @@ input:checked + .slider:before { } .cancel-btn:hover { - background: rgba(255, 255, 255, 0.03); - color: #fff; + background: var(--dark-700); + color: var(--gray-100); } .submit-btn { @@ -1000,7 +1007,7 @@ input:checked + .slider:before { width: 100%; height: auto; border-right: none; - border-bottom: 1px solid rgba(255, 255, 255, 0.05); + border-bottom: 1px solid var(--dark-600); } .nav-menu { flex-direction: row; diff --git a/src/pages/HospitalNetwork.css b/src/pages/HospitalNetwork.css index 5c5a6e6..94298ce 100644 --- a/src/pages/HospitalNetwork.css +++ b/src/pages/HospitalNetwork.css @@ -23,18 +23,19 @@ display: flex; align-items: center; gap: 16px; - background: rgba(10, 22, 40, 0.6); - border: 1px solid rgba(255, 255, 255, 0.05); + background: var(--dark-800); + border: 1px solid var(--dark-600); border-radius: var(--radius-md); padding: 16px 20px; backdrop-filter: blur(10px); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: var(--shadow-sm); } .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); + box-shadow: var(--shadow-md); } .analytics-card.glow-card { @@ -83,7 +84,7 @@ .card-val { font-size: 24px; font-weight: 800; - color: #fff; + color: var(--gray-50); line-height: 1.2; } @@ -108,7 +109,7 @@ width: 320px; flex-shrink: 0; background: var(--dark-800); - border: 1px solid rgba(255, 255, 255, 0.05); + border: 1px solid var(--dark-600); border-radius: var(--radius-md); padding: 16px; display: flex; @@ -119,7 +120,7 @@ .section-pane-title { font-size: 15px; font-weight: 700; - color: #fff; + color: var(--gray-50); } .header-search-row { @@ -168,10 +169,10 @@ .search-input-wrap input { width: 100%; background: var(--dark-900); - border: 1px solid rgba(255, 255, 255, 0.08); + border: 1px solid var(--dark-600); border-radius: 8px; padding: 8px 10px 8px 32px; - color: #fff; + color: var(--gray-50); font-size: 13px; transition: all 0.2s; } @@ -192,8 +193,8 @@ } .group-item-card { - background: rgba(255, 255, 255, 0.02); - border: 1px solid rgba(255, 255, 255, 0.04); + background: var(--dark-900); + border: 1px solid var(--dark-600); border-radius: 8px; padding: 12px; cursor: pointer; @@ -201,14 +202,14 @@ } .group-item-card:hover { - background: rgba(255, 255, 255, 0.04); - border-color: rgba(255, 255, 255, 0.1); + background: var(--dark-700); + border-color: var(--dark-500); } .group-item-card.selected { - background: rgba(59, 130, 246, 0.08); + background: rgba(59, 130, 246, 0.06); border-color: var(--primary-500); - box-shadow: 0 0 12px rgba(59, 130, 246, 0.1); + box-shadow: 0 0 12px rgba(59, 130, 246, 0.05); } .group-item-header { @@ -221,12 +222,12 @@ .group-name { font-size: 13.5px; font-weight: 700; - color: #fff; + color: var(--gray-50); } .group-badge { font-size: 10.5px; - background: rgba(255, 255, 255, 0.06); + background: var(--dark-600); color: var(--gray-300); padding: 2px 6px; border-radius: 4px; @@ -269,7 +270,7 @@ } .icon-link-btn:hover { - color: #fff; + color: var(--primary-600); text-decoration: underline; } @@ -285,9 +286,10 @@ .network-main-content { flex-grow: 1; background: var(--dark-800); - border: 1px solid rgba(255, 255, 255, 0.05); + border: 1px solid var(--dark-600); border-radius: var(--radius-md); padding: 20px; + box-shadow: var(--shadow-sm); } .group-details-pane { @@ -301,14 +303,14 @@ justify-content: space-between; align-items: flex-start; gap: 20px; - border-bottom: 1px solid rgba(255, 255, 255, 0.05); + border-bottom: 1px solid var(--dark-600); padding-bottom: 16px; } .group-title-label { font-size: 20px; font-weight: 800; - color: #fff; + color: var(--gray-50); letter-spacing: -0.01em; } @@ -344,8 +346,8 @@ } .hospital-detail-card { - background: rgba(255, 255, 255, 0.02); - border: 1px solid rgba(255, 255, 255, 0.05); + background: var(--dark-900); + border: 1px solid var(--dark-600); border-radius: var(--radius-md); padding: 16px; display: flex; @@ -357,8 +359,8 @@ .hospital-detail-card:hover { transform: translateY(-2px); - border-color: rgba(20, 184, 166, 0.25); - background: rgba(255, 255, 255, 0.03); + border-color: var(--teal-500); + background: var(--dark-700); } .hospital-card-header { @@ -371,7 +373,7 @@ .hospital-card-name { font-size: 14.5px; font-weight: 700; - color: #fff; + color: var(--gray-50); line-height: 1.3; } @@ -401,7 +403,7 @@ gap: 4px; font-size: 12px; color: var(--gray-400); - border-bottom: 1px solid rgba(255, 255, 255, 0.04); + border-bottom: 1px solid var(--dark-600); padding-bottom: 12px; } @@ -422,8 +424,8 @@ .m-stat { font-size: 10.5px; - background: rgba(255, 255, 255, 0.04); - color: var(--gray-300); + background: var(--dark-600); + color: var(--gray-100); padding: 2px 6px; border-radius: 4px; font-weight: 500; @@ -436,8 +438,8 @@ .mini-action-btn { background: none; - border: 1px solid rgba(255, 255, 255, 0.15); - color: var(--gray-300); + border: 1px solid var(--dark-500); + color: var(--gray-200); font-size: 11px; padding: 4px 8px; border-radius: 4px; @@ -446,9 +448,9 @@ } .mini-action-btn:hover { - background: rgba(255, 255, 255, 0.08); - color: #fff; - border-color: rgba(255, 255, 255, 0.3); + background: var(--dark-700); + color: var(--gray-100); + border-color: var(--dark-500); } .mini-action-btn.danger:hover { @@ -489,7 +491,7 @@ text-align: center; padding: 40px; color: var(--gray-400); - border: 1px dashed rgba(255, 255, 255, 0.08); + border: 1px dashed var(--dark-500); border-radius: var(--radius-md); font-size: 13.5px; } @@ -498,9 +500,11 @@ .modal-wide .modal-content { max-width: 900px; width: 90%; + max-height: 90vh; + overflow-y: auto; background: var(--dark-800); - border: 1px solid rgba(255, 255, 255, 0.08); - box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4); + border: 1px solid var(--dark-600); + box-shadow: var(--shadow-lg); } .modal-super-title { @@ -545,7 +549,7 @@ background: var(--dark-900); padding: 3px; border-radius: 6px; - border: 1px solid rgba(255, 255, 255, 0.05); + border: 1px solid var(--dark-600); } .roster-tab-btn { @@ -561,13 +565,13 @@ } .roster-tab-btn:hover { - color: #fff; + color: var(--gray-100); } .roster-tab-btn.active { background: var(--primary-600); color: #fff; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); + box-shadow: var(--shadow-sm); } .roster-action-group { @@ -579,9 +583,9 @@ .roster-dropdown { background: var(--dark-900); - border: 1px solid rgba(255, 255, 255, 0.08); + border: 1px solid var(--dark-600); border-radius: 8px; - color: #fff; + color: var(--gray-50); padding: 7px 12px; font-size: 12.5px; outline: none; @@ -594,7 +598,7 @@ /* Roster Table Styling */ .roster-table-container { - border: 1px solid rgba(255, 255, 255, 0.05); + border: 1px solid var(--dark-600); border-radius: 8px; overflow: hidden; max-height: 380px; @@ -619,7 +623,7 @@ } .staff-cell-name { - color: #fff; + color: var(--gray-50); font-size: 13px; } @@ -683,11 +687,51 @@ .cancel-btn { background: none; - border: 1px solid rgba(255, 255, 255, 0.1); + border: 1px solid var(--dark-600); color: var(--gray-300); } .cancel-btn:hover { - background: rgba(255, 255, 255, 0.05); - color: #fff; + background: var(--dark-700); + color: var(--gray-100); +} + +/* ── TWO-COLUMN FORM LAYOUT ── */ +.form-grid-two-col { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 24px; + align-items: start; +} + +.form-column { + display: flex; + flex-direction: column; + gap: 14px; +} + +.form-section-title { + font-size: 13.5px; + font-weight: 600; + color: var(--primary-600); + border-bottom: 1px solid var(--dark-600); + padding-bottom: 6px; + margin-top: 0; + margin-bottom: 8px; +} + +.form-info-box { + background: var(--dark-900); + border-left: 3px solid var(--teal-500); + padding: 10px; + border-radius: var(--radius-sm); + font-size: 11.5px; + color: var(--gray-300); + line-height: 1.5; + margin-top: 6px; +} + +.form-actions-grid-span { + grid-column: span 2; + margin-top: 10px; } diff --git a/src/pages/HospitalNetwork.jsx b/src/pages/HospitalNetwork.jsx index 193705c..4f37d12 100644 --- a/src/pages/HospitalNetwork.jsx +++ b/src/pages/HospitalNetwork.jsx @@ -51,7 +51,7 @@ export default function HospitalNetwork({ notify }) { 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('') @@ -67,7 +67,29 @@ export default function HospitalNetwork({ notify }) { // Form States const [groupForm, setGroupForm] = useState({ name: '', hq: '', established: '', description: '' }) - const [hospitalForm, setHospitalForm] = useState({ name: '', city: '', beds: '', phone: '', email: '', address: '', rating: '5.0' }) + const [hospitalForm, setHospitalForm] = useState({ + name: '', + reg_no: '', + beds: '50', + phone: '', + email: '', + address: '', + city: '', + state: '', + zip: '', + country: 'India', + website: '', + logoUrl: 'https://example.com/primecare-logo.png', + logoPath: '/uploads/logos/primecare-medical-center.png', + specialities: 'Cardiology, Neurology, Urology, Orthopedics, Emergency Medicine', + adminFirstName: '', + adminLastName: '', + adminEmail: '', + adminMobile: '', + adminCountryCode: '+91', + adminPassword: '', + adminUsername: '', + }) const [staffForm, setStaffForm] = useState({ type: 'doctor', name: '', specialty: '', role: 'Registered Nurse', experience: '1 Yr', certification: '', status: 'On Duty', phone: '', email: '' }) // Synchronize with LocalStorage @@ -88,7 +110,7 @@ export default function HospitalNetwork({ notify }) { 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)) { @@ -96,7 +118,21 @@ export default function HospitalNetwork({ notify }) { } else if (Array.isArray(groupsRes)) { fetchedGroups = groupsRes } - + + // Normalize backend shape → frontend shape + // Backend uses additional_info.city for HQ, reg_no as identifier, no established/description fields + fetchedGroups = fetchedGroups.map(g => { + const info = g.additional_info + ? (typeof g.additional_info === 'string' ? JSON.parse(g.additional_info) : g.additional_info) + : {} + return { + ...g, + hq: g.hq || (info.city && info.state ? `${info.city}, ${info.state}` : info.city || info.hq || 'N/A'), + established: g.established || (g.created_at ? g.created_at.substring(0, 4) : 'N/A'), + description: g.description || info.website || `Reg No: ${g.reg_no || 'N/A'}`, + } + }) + if (fetchedGroups.length > 0) { setGroups(fetchedGroups) // Automatically select the first fetched group ID @@ -114,6 +150,22 @@ export default function HospitalNetwork({ notify }) { } else if (Array.isArray(hospitalsRes)) { fetchedHospitals = hospitalsRes } + + fetchedHospitals = fetchedHospitals.map(h => { + const info = h.additional_info + ? (typeof h.additional_info === 'string' ? JSON.parse(h.additional_info) : h.additional_info) + : {} + return { + ...h, + city: h.city || info.city || 'N/A', + beds: h.beds || info.beds || 50, + phone: h.phone || info.phone || 'N/A', + email: h.email || info.contact_email || info.email || 'N/A', + address: h.address || info.address || 'N/A', + rating: h.rating || info.rating || '5.0' + } + }) + if (fetchedHospitals.length > 0) { setHospitals(fetchedHospitals) } @@ -152,12 +204,12 @@ export default function HospitalNetwork({ notify }) { const analytics = useMemo(() => { const totalGroups = groups.length const totalHospitals = hospitals.length - - const activeDoctorsCount = staff.filter(s => + + const activeDoctorsCount = staff.filter(s => s.type === 'doctor' && (s.status === 'On Duty' || s.status === 'On Call') ).length - const activeCaregiversCount = staff.filter(s => + const activeCaregiversCount = staff.filter(s => s.type === 'caregiver' && (s.status === 'Active' || s.status === 'Scheduled') ).length @@ -171,9 +223,9 @@ export default function HospitalNetwork({ notify }) { // Filters and Searches Computations const filteredGroups = useMemo(() => { - return groups.filter(g => + return groups.filter(g => g.name.toLowerCase().includes(groupQuery.toLowerCase()) || - g.hq.toLowerCase().includes(groupQuery.toLowerCase()) + (g.hq || '').toLowerCase().includes(groupQuery.toLowerCase()) ) }, [groups, groupQuery]) @@ -183,10 +235,10 @@ export default function HospitalNetwork({ notify }) { const filteredHospitals = useMemo(() => { if (!selectedGroupId) return [] - return hospitals.filter(h => + 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())) + h.city.toLowerCase().includes(hospitalQuery.toLowerCase())) ) }, [hospitals, selectedGroupId, hospitalQuery]) @@ -200,9 +252,9 @@ export default function HospitalNetwork({ notify }) { 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 @@ -271,7 +323,7 @@ export default function HospitalNetwork({ notify }) { 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)) + setHospitals(prev => prev.filter(h => h.groupId !== groupId)) notify(`Hospital group "${name}" deleted.`) if (selectedGroupId === groupId) { setSelectedGroupId(groups[0]?.id || null) @@ -285,39 +337,167 @@ export default function HospitalNetwork({ notify }) { setModalType('hospital') if (mode === 'edit' && item) { setEditItem(item) + const info = item.additional_info + ? (typeof item.additional_info === 'string' ? JSON.parse(item.additional_info) : item.additional_info) + : {} + const logo = item.logo + ? (typeof item.logo === 'string' ? JSON.parse(item.logo) : item.logo) + : {} + const specialities = item.hospital_speciality && Array.isArray(item.hospital_speciality) + ? item.hospital_speciality.map(s => s.speciality || s).join(', ') + : (item.hospitalSpeciality && Array.isArray(item.hospitalSpeciality) + ? item.hospitalSpeciality.map(s => s.speciality || s).join(', ') + : 'Cardiology, Neurology, Urology, Orthopedics, Emergency Medicine') + setHospitalForm({ - name: item.name, - city: item.city, - beds: item.beds, - phone: item.phone, - email: item.email, - address: item.address, - rating: item.rating + name: item.name || '', + reg_no: item.reg_no || '', + beds: item.beds || info.beds || '50', + phone: item.phone || info.phone || '', + email: item.email || info.contact_email || info.email || '', + address: item.address || info.address || '', + city: item.city || info.city || '', + state: info.state || '', + zip: info.zip || '', + country: info.country || 'India', + website: info.website || '', + logoUrl: logo.url || 'https://example.com/primecare-logo.png', + logoPath: logo.path || '/uploads/logos/primecare-medical-center.png', + specialities: specialities, + adminFirstName: item.primaryStaff?.user?.first_name || '', + adminLastName: item.primaryStaff?.user?.last_name || '', + adminEmail: item.primaryStaff?.user?.email || '', + adminMobile: item.primaryStaff?.user?.mobile || '', + adminCountryCode: item.primaryStaff?.user?.country_code || '+91', + adminPassword: '', + adminUsername: item.primaryStaff?.user?.username || '', }) } else { setEditItem(null) - setHospitalForm({ name: '', city: '', beds: '', phone: '', email: '', address: '', rating: '5.0' }) + setHospitalForm({ + name: '', + reg_no: '', + beds: '50', + phone: '', + email: '', + address: '', + city: '', + state: '', + zip: '', + country: 'India', + website: '', + logoUrl: 'https://example.com/primecare-logo.png', + logoPath: '/uploads/logos/primecare-medical-center.png', + specialities: 'Cardiology, Neurology, Urology, Orthopedics, Emergency Medicine', + adminFirstName: '', + adminLastName: '', + adminEmail: '', + adminMobile: '', + adminCountryCode: '+91', + adminPassword: '', + adminUsername: '', + }) } } 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 + + try { + if (modalMode === 'create') { + const payload = { + name: hospitalForm.name, + group_id: parseInt(selectedGroupId) || null, + reg_no: hospitalForm.reg_no, + additional_info: { + contact_email: hospitalForm.email, + phone: hospitalForm.phone, + address: hospitalForm.address, + city: hospitalForm.city, + state: hospitalForm.state, + zip: hospitalForm.zip, + country: hospitalForm.country, + website: hospitalForm.website, + beds: parseInt(hospitalForm.beds) || 50, + rating: '5.0' + }, + logo: { + url: hospitalForm.logoUrl, + path: hospitalForm.logoPath + }, + hospital_speciality: hospitalForm.specialities.split(',').map(s => s.trim()).filter(Boolean), + user: { + role: 'hospital', + first_name: hospitalForm.adminFirstName, + last_name: hospitalForm.adminLastName, + email: hospitalForm.adminEmail, + mobile: hospitalForm.adminMobile, + country_code: hospitalForm.adminCountryCode, + password: hospitalForm.adminPassword, + gender: 'male', + dob: '1980-01-01', + username: hospitalForm.adminUsername || (hospitalForm.adminFirstName.toLowerCase() + '_' + hospitalForm.adminLastName.toLowerCase()), + timezone_id: 1, + is_2fa: 1 + } + } + + const res = await adminService.hospitalNetwork.saveHospital(payload) + if (res?.success || res?.status === 201) { + notify(`Hospital "${hospitalForm.name}" created successfully.`) + await syncWithApi() + } else { + notify(res?.message || 'Failed to create hospital', true) + } + } else if (modalMode === 'edit' && editItem) { + const payload = { + id: editItem.id, + name: hospitalForm.name, + group_id: parseInt(selectedGroupId) || null, + reg_no: hospitalForm.reg_no, + additional_info: { + contact_email: hospitalForm.email, + phone: hospitalForm.phone, + address: hospitalForm.address, + city: hospitalForm.city, + state: hospitalForm.state, + zip: hospitalForm.zip, + country: hospitalForm.country, + website: hospitalForm.website, + beds: parseInt(hospitalForm.beds) || 50, + rating: editItem.rating || '5.0' + }, + logo: { + url: hospitalForm.logoUrl, + path: hospitalForm.logoPath + }, + hospital_speciality: hospitalForm.specialities.split(',').map(s => s.trim()).filter(Boolean), + user: { + id: editItem.primaryStaff?.user?.id || editItem.user_id, + role: 'hospital', + first_name: hospitalForm.adminFirstName, + last_name: hospitalForm.adminLastName, + email: hospitalForm.adminEmail, + mobile: hospitalForm.adminMobile, + country_code: hospitalForm.adminCountryCode, + username: hospitalForm.adminUsername || (hospitalForm.adminFirstName.toLowerCase() + '_' + hospitalForm.adminLastName.toLowerCase()), + ...(hospitalForm.adminPassword ? { password: hospitalForm.adminPassword } : {}) + } + } + + const res = await adminService.hospitalNetwork.saveHospital(payload) + if (res?.success || res?.status === 200) { + notify(`Hospital "${hospitalForm.name}" updated successfully.`) + await syncWithApi() + } else { + notify(res?.message || 'Failed to update hospital', true) + } } - 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) + } catch (err) { + notify(err.message || 'An error occurred during submission', true) } - setModalType(null) } const handleDeleteHospital = (hospitalId, name) => { @@ -374,20 +554,20 @@ export default function HospitalNetwork({ notify }) { status: staffForm.status, phone: staffForm.phone, email: staffForm.email, - ...(staffForm.type === 'doctor' + ...(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, + 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' + ...(staffForm.type === 'doctor' ? { specialty: staffForm.specialty, experience: staffForm.experience } : { role: staffForm.role, certification: staffForm.certification }) } : s)) @@ -462,12 +642,12 @@ export default function HospitalNetwork({ notify }) { {/* ── Main Two-Column Structure ── */}
- + {/* Left Column: Hospital Groups List */}
-

Provider Groups

+

Hospital Groups

-
{staffTab === 'doctor' ? ( - ) : ( - setGroupForm(prev => ({ ...prev, name: e.target.value }))} /> @@ -816,19 +996,19 @@ export default function HospitalNetwork({ notify }) {
- setGroupForm(prev => ({ ...prev, hq: e.target.value }))} />
- setGroupForm(prev => ({ ...prev, established: e.target.value }))} /> @@ -836,8 +1016,8 @@ export default function HospitalNetwork({ notify }) {
-