implement TeleEMS platform architecture with centralized API client and master data management system
This commit is contained in:
315
src/pages/MasterData.tsx
Normal file
315
src/pages/MasterData.tsx
Normal file
@@ -0,0 +1,315 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Database,
|
||||
Stethoscope,
|
||||
AlertTriangle,
|
||||
Box,
|
||||
Plus,
|
||||
Search,
|
||||
ChevronRight,
|
||||
Edit3,
|
||||
Trash2,
|
||||
FileText,
|
||||
Thermometer,
|
||||
ShieldAlert,
|
||||
Save,
|
||||
CheckCircle2,
|
||||
ShieldCheck,
|
||||
Hospital
|
||||
} from 'lucide-react';
|
||||
import { Card, StatCard } from '../components/Common';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
|
||||
type MasterTab = 'SYMPTOMS' | 'INCIDENT_CATEGORIES' | 'MEDICAL_INVENTORY' | 'HOSPITAL_REFERRALS';
|
||||
|
||||
export const MasterDataManagement: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState<MasterTab>('SYMPTOMS');
|
||||
|
||||
return (
|
||||
<div className="page-container" style={{ display: 'flex', flexDirection: 'column', gap: '32px' }}>
|
||||
<header style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||
<div>
|
||||
<h2 style={{ fontSize: '2.5rem', fontWeight: 900, background: 'linear-gradient(90deg, #3B82F6, #fff)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>
|
||||
Platform Master Data
|
||||
</h2>
|
||||
<p style={{ color: 'var(--text-secondary)', fontSize: '0.9rem', marginTop: '4px' }}>
|
||||
Manage the categorical DNA of TeleEMS: Symptoms, Incident Logic, and Clinical Inventories.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="glass" style={{ padding: '6px', borderRadius: '12px', display: 'flex', gap: '4px', background: 'rgba(255,255,255,0.05)' }}>
|
||||
<button
|
||||
onClick={() => setActiveTab('SYMPTOMS')}
|
||||
style={{
|
||||
padding: '10px 20px', borderRadius: '8px', border: 'none',
|
||||
background: activeTab === 'SYMPTOMS' ? 'var(--accent-cyan)' : 'transparent',
|
||||
color: activeTab === 'SYMPTOMS' ? '#000' : 'var(--text-secondary)',
|
||||
fontWeight: 700, cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px', fontSize: '0.8rem'
|
||||
}}>
|
||||
<Stethoscope size={16} /> SYMPTOMS
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('INCIDENT_CATEGORIES')}
|
||||
style={{
|
||||
padding: '10px 20px', borderRadius: '8px', border: 'none',
|
||||
background: activeTab === 'INCIDENT_CATEGORIES' ? 'var(--accent-cyan)' : 'transparent',
|
||||
color: activeTab === 'INCIDENT_CATEGORIES' ? '#000' : 'var(--text-secondary)',
|
||||
fontWeight: 700, cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px', fontSize: '0.8rem'
|
||||
}}>
|
||||
<ShieldAlert size={16} /> INCIDENT CATEGORIES
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('MEDICAL_INVENTORY')}
|
||||
style={{
|
||||
padding: '10px 20px', borderRadius: '8px', border: 'none',
|
||||
background: activeTab === 'MEDICAL_INVENTORY' ? 'var(--accent-cyan)' : 'transparent',
|
||||
color: activeTab === 'MEDICAL_INVENTORY' ? '#000' : 'var(--text-secondary)',
|
||||
fontWeight: 700, cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px', fontSize: '0.8rem'
|
||||
}}>
|
||||
<Box size={16} /> INVENTORY MASTER
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('HOSPITAL_REFERRALS')}
|
||||
style={{
|
||||
padding: '10px 20px', borderRadius: '8px', border: 'none',
|
||||
background: activeTab === 'HOSPITAL_REFERRALS' ? 'var(--accent-cyan)' : 'transparent',
|
||||
color: activeTab === 'HOSPITAL_REFERRALS' ? '#000' : 'var(--text-secondary)',
|
||||
fontWeight: 700, cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px', fontSize: '0.8rem'
|
||||
}}>
|
||||
<Hospital size={16} /> HOSPITALS
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<AnimatePresence mode="wait">
|
||||
{activeTab === 'SYMPTOMS' && <SymptomMaster key="symptoms" />}
|
||||
{activeTab === 'INCIDENT_CATEGORIES' && <IncidentCategoryMaster key="categories" />}
|
||||
{activeTab === 'MEDICAL_INVENTORY' && <InventoryMaster key="inventory" />}
|
||||
{activeTab === 'HOSPITAL_REFERRALS' && <HospitalReferralMaster key="referrals" />}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// --- SUB-MODULES ---
|
||||
|
||||
const SymptomMaster = () => {
|
||||
const symptoms = [
|
||||
{ id: 'S-001', name: 'Cardiac Arrest', category: 'Immediate (Red)', instructions: 'Begin CPR, Attach Defibrillator', lang: 'EN, TN, HI' },
|
||||
{ id: 'S-002', name: 'Compound Fracture', category: 'Urgent (Orange)', instructions: 'Stabilize limb, Control bleeding', lang: 'EN, TN' },
|
||||
{ id: 'S-003', name: 'Minor Laceration', category: 'Minor (Green)', instructions: 'Clean wound, Apply dressing', lang: 'EN' },
|
||||
{ id: 'S-004', name: 'Respiratory Distress', category: 'Immediate (Red)', instructions: 'Administer Oxygen, Sitting position', lang: 'EN, TN, HI' },
|
||||
];
|
||||
|
||||
return (
|
||||
<motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -20 }} style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div style={{ display: 'flex', gap: '12px' }}>
|
||||
<div className="glass" style={{ padding: '8px 16px', borderRadius: '8px', display: 'flex', alignItems: 'center', gap: '10px' }}>
|
||||
<Search size={16} color="var(--text-secondary)" />
|
||||
<input type="text" placeholder="Search symptoms..." style={{ background: 'transparent', border: 'none', color: '#fff', outline: 'none', fontSize: '0.85rem' }} />
|
||||
</div>
|
||||
<select className="glass" style={{ padding: '8px 16px', borderRadius: '8px', background: 'rgba(0,0,0,0.5)', color: '#fff', border: '1px solid var(--card-border)', fontSize: '0.85rem' }}>
|
||||
<option>All Severities</option>
|
||||
<option>Red (Immediate)</option>
|
||||
<option>Orange (Urgent)</option>
|
||||
<option>Green (Minor)</option>
|
||||
</select>
|
||||
</div>
|
||||
<button className="glass" style={{ padding: '10px 20px', background: 'var(--accent-cyan)', color: '#000', border: 'none', borderRadius: '8px', fontWeight: 700, display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
||||
<Plus size={18} /> ADD NEW SYMPTOM
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.85rem' }}>
|
||||
<thead>
|
||||
<tr style={{ background: 'rgba(255,255,255,0.03)', textAlign: 'left' }}>
|
||||
<th style={{ padding: '16px' }}>Symptom ID</th>
|
||||
<th style={{ padding: '16px' }}>Clinical Name</th>
|
||||
<th style={{ padding: '16px' }}>Severity Cluster</th>
|
||||
<th style={{ padding: '16px' }}>First Aid Protocol</th>
|
||||
<th style={{ padding: '16px' }}>Localization</th>
|
||||
<th style={{ padding: '16px', textAlign: 'right' }}>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{symptoms.map(s => (
|
||||
<tr key={s.id} style={{ borderBottom: '1px solid rgba(255,255,255,0.03)' }}>
|
||||
<td style={{ padding: '16px' }} className="mono">{s.id}</td>
|
||||
<td style={{ padding: '16px', fontWeight: 800 }}>{s.name}</td>
|
||||
<td style={{ padding: '16px' }}>
|
||||
<span style={{
|
||||
fontSize: '0.65rem', padding: '4px 8px', borderRadius: '4px', fontWeight: 800,
|
||||
background: s.category.includes('Red') ? 'rgba(255, 59, 59, 0.1)' : 'rgba(255, 184, 0, 0.1)',
|
||||
color: s.category.includes('Red') ? 'var(--alert-red)' : 'var(--warning-amber)',
|
||||
border: `1px solid ${s.category.includes('Red') ? 'rgba(255, 59, 59, 0.3)' : 'rgba(255, 184, 0, 0.3)'}`
|
||||
}}>{s.category.toUpperCase()}</span>
|
||||
</td>
|
||||
<td style={{ padding: '16px', color: 'var(--text-secondary)' }}>{s.instructions}</td>
|
||||
<td style={{ padding: '16px' }}>
|
||||
<div style={{ display: 'flex', gap: '4px' }}>
|
||||
{s.lang.split(', ').map(l => (
|
||||
<span key={l} style={{ fontSize: '0.6rem', padding: '2px 4px', background: 'rgba(255,255,255,0.05)', borderRadius: '2px' }}>{l}</span>
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
<td style={{ padding: '16px', textAlign: 'right' }}>
|
||||
<div style={{ display: 'flex', gap: '12px', justifyContent: 'flex-end' }}>
|
||||
<button style={{ background: 'transparent', border: 'none', color: 'var(--accent-cyan)', cursor: 'pointer' }}><Edit3 size={16} /></button>
|
||||
<button style={{ background: 'transparent', border: 'none', color: 'var(--alert-red)', cursor: 'pointer' }}><Trash2 size={16} /></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Card>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
const IncidentCategoryMaster = () => {
|
||||
const categories = [
|
||||
{ name: 'Red - Immediate', desc: 'Life Threatening Emergency', color: 'var(--alert-red)', escalation: 'Immediate Pilot/EMT + Supervisor notification' },
|
||||
{ name: 'Orange - Urgent', desc: 'Non-life threatening but critical', color: 'var(--warning-amber)', escalation: 'Pilot/EMT notification within 2 mins' },
|
||||
{ name: 'Green - Minor', desc: 'Walking wounded / Low priority', color: 'var(--accent-green)', escalation: 'Standard dispatch queue' },
|
||||
{ name: 'Blue - IFT', desc: 'Inter-Facility Transfer', color: 'var(--accent-cyan)', escalation: 'Scheduled transport routing' },
|
||||
{ name: 'Black - Deceased', desc: 'Dead on Arrival / Scene', color: '#4A5568', escalation: 'Mortuary / Police notification' },
|
||||
];
|
||||
|
||||
return (
|
||||
<motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -20 }} style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: '24px' }}>
|
||||
{categories.map((c, i) => (
|
||||
<Card key={i} title={c.name} subtitle={c.desc} style={{ borderLeft: `4px solid ${c.color}` }}>
|
||||
<div style={{ marginTop: '16px', display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
||||
<div>
|
||||
<div style={{ fontSize: '0.7rem', color: 'var(--text-secondary)', textTransform: 'uppercase', marginBottom: '4px' }}>Escalation Logic</div>
|
||||
<div style={{ fontSize: '0.85rem', fontWeight: 600 }}>{c.escalation}</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '12px', marginTop: '10px' }}>
|
||||
<button style={{ background: 'transparent', border: '1px solid var(--card-border)', color: 'var(--text-secondary)', padding: '6px 12px', borderRadius: '4px', fontSize: '0.7rem', fontWeight: 700, cursor: 'pointer' }}>CONFIGURE RULES</button>
|
||||
<button style={{ background: 'transparent', border: '1px solid var(--card-border)', color: 'var(--accent-cyan)', padding: '6px 12px', borderRadius: '4px', fontSize: '0.7rem', fontWeight: 700, cursor: 'pointer' }}>EDIT</button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
<div style={{ border: '2px dashed var(--card-border)', borderRadius: '20px', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', padding: '40px' }} className="hover-glow">
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Plus size={32} color="var(--text-secondary)" style={{ margin: '0 auto 10px' }} />
|
||||
<div style={{ fontWeight: 800, color: 'var(--text-secondary)' }}>ADD CUSTOM CATEGORY</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
const InventoryMaster = () => {
|
||||
const items = [
|
||||
{ name: 'Epinephrine (1mg)', category: 'Drug', unit: 'Ampule', minStock: 10, expiry: true },
|
||||
{ name: 'Sterile Gauze (4x4)', category: 'Disposable', unit: 'Pack', minStock: 50, expiry: false },
|
||||
{ name: 'Automatic Defibrillator', category: 'Medical Device', unit: 'Unit', minStock: 1, expiry: false },
|
||||
{ name: 'Oxygen Cylinder (Portable)', category: 'Reusable', unit: 'Cylinder', minStock: 2, expiry: false },
|
||||
];
|
||||
|
||||
return (
|
||||
<motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -20 }} style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<h3 style={{ fontSize: '1.25rem', fontWeight: 800 }}>Medical Inventory Master List</h3>
|
||||
<button className="glass" style={{ padding: '10px 20px', background: 'var(--accent-cyan)', color: '#000', border: 'none', borderRadius: '8px', fontWeight: 700, display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
||||
<Save size={18} /> SAVE MASTER LIST
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '20px' }}>
|
||||
<StatCard label="Total SKU Nodes" value="482" icon={Box} glowColor="cyan" />
|
||||
<StatCard label="Critical Low Alerts" value="12" icon={AlertTriangle} glowColor="red" />
|
||||
<StatCard label="Global Stock Value" value="₹12.4L" icon={Database} glowColor="green" />
|
||||
<StatCard label="Inventory Compliance" value="100%" icon={ShieldCheck} glowColor="cyan" />
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.85rem' }}>
|
||||
<thead>
|
||||
<tr style={{ background: 'rgba(255,255,255,0.03)', textAlign: 'left' }}>
|
||||
<th style={{ padding: '16px' }}>Item Name</th>
|
||||
<th style={{ padding: '16px' }}>Category</th>
|
||||
<th style={{ padding: '16px' }}>Unit</th>
|
||||
<th style={{ padding: '16px' }}>Min Alert Threshold</th>
|
||||
<th style={{ padding: '16px' }}>Expiry Tracking</th>
|
||||
<th style={{ padding: '16px', textAlign: 'right' }}>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item, i) => (
|
||||
<tr key={i} style={{ borderBottom: '1px solid rgba(255,255,255,0.03)' }}>
|
||||
<td style={{ padding: '16px', fontWeight: 800 }}>{item.name}</td>
|
||||
<td style={{ padding: '16px' }}>
|
||||
<span style={{ fontSize: '0.65rem', padding: '4px 8px', background: 'rgba(255,255,255,0.05)', borderRadius: '4px', border: '1px solid var(--card-border)' }}>{item.category.toUpperCase()}</span>
|
||||
</td>
|
||||
<td style={{ padding: '16px' }}>{item.unit}</td>
|
||||
<td style={{ padding: '16px' }} className="mono">{item.minStock}</td>
|
||||
<td style={{ padding: '16px' }}>
|
||||
{item.expiry ? <CheckCircle2 size={16} color="var(--accent-green)" /> : <Trash2 size={16} color="rgba(255,255,255,0.1)" />}
|
||||
</td>
|
||||
<td style={{ padding: '16px', textAlign: 'right' }}>
|
||||
<div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
|
||||
<button style={{ background: 'transparent', border: '1px solid var(--card-border)', color: 'var(--text-secondary)', padding: '4px 8px', borderRadius: '4px', fontSize: '0.7rem', cursor: 'pointer' }}>BATCH INFO</button>
|
||||
<button style={{ background: 'transparent', border: 'none', color: 'var(--accent-cyan)', cursor: 'pointer' }}><Edit3 size={16} /></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Card>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
const HospitalReferralMaster = () => {
|
||||
const networks = [
|
||||
{ name: 'City Govt Hospital Network', type: 'Government', nodes: 12, region: 'Chennai Central' },
|
||||
{ name: 'Apollo Group Synergy', type: 'Private', nodes: 5, region: 'Regional North' },
|
||||
{ name: 'District Trauma Collective', type: 'Trust', nodes: 8, region: 'Salem/Erode' },
|
||||
];
|
||||
|
||||
return (
|
||||
<motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -20 }} style={{ display: 'flex', flexDirection: 'column', gap: '32px' }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '24px' }}>
|
||||
{networks.map((net, i) => (
|
||||
<Card key={i} title={net.name} subtitle={`${net.type} • ${net.region}`}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '20px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
||||
<Hospital size={20} color="var(--accent-cyan)" />
|
||||
<span style={{ fontWeight: 800, fontSize: '1.2rem' }}>{net.nodes} Nodes</span>
|
||||
</div>
|
||||
<button style={{ background: 'rgba(59, 130, 246, 0.1)', border: '1px solid var(--accent-cyan)', color: 'var(--accent-cyan)', padding: '6px 12px', borderRadius: '6px', fontSize: '0.7rem', fontWeight: 800, cursor: 'pointer' }}>MANAGE NETWORK</button>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Card title="Global Referral Mapping">
|
||||
<p style={{ fontSize: '0.75rem', color: 'var(--text-secondary)', marginBottom: '24px' }}>Configure specialty routing rules for Cardiac, Trauma, and Burn centers across all aggregator zones.</p>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
||||
{[
|
||||
{ target: 'Cardiac Emergencies', hospital: 'Government General Hospital (Cath Lab)', protocol: 'Immediate Redirect' },
|
||||
{ target: 'Penetrating Trauma', hospital: 'District Trauma Center (Level 1)', protocol: 'ED Pre-alert Pulse' },
|
||||
{ target: 'Maternal Emergencies', hospital: 'Regional Women & Child Hub', protocol: 'Specialist Standby' },
|
||||
].map((rule, i) => (
|
||||
<div key={i} style={{ padding: '16px', background: 'rgba(255,255,255,0.02)', borderRadius: '12px', border: '1px solid var(--card-border)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<div>
|
||||
<div style={{ fontWeight: 800, fontSize: '0.9rem', color: 'var(--accent-cyan)' }}>{rule.target}</div>
|
||||
<div style={{ fontSize: '0.75rem', color: 'var(--text-secondary)' }}>Destination: {rule.hospital}</div>
|
||||
</div>
|
||||
<div style={{ textAlign: 'right' }}>
|
||||
<div style={{ fontSize: '0.7rem', fontWeight: 800, textTransform: 'uppercase' }}>{rule.protocol}</div>
|
||||
<Edit3 size={14} style={{ marginTop: '4px', cursor: 'pointer', opacity: 0.5 }} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user