Files
TeleEms-Dashboard/src/pages/MasterData.tsx

316 lines
19 KiB
TypeScript

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>
);
};