implement TeleEMS platform architecture with centralized API client and master data management system

This commit is contained in:
2026-05-04 15:27:35 +05:30
parent e165269e92
commit 8dc773d205
61 changed files with 22829 additions and 0 deletions

View File

@@ -0,0 +1,893 @@
import React, { useState, useEffect, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Hospital,
CheckCircle,
AlertCircle,
Plus,
Phone,
Settings,
CheckCircle2,
MoreVertical,
Shield,
User as UserIcon,
Navigation2,
XCircle,
Stethoscope,
Lock,
Edit2,
Trash2,
Eye,
EyeOff
} from 'lucide-react';
import { Card } from '../components/Common';
import { BarChart, Bar, XAxis, YAxis, ResponsiveContainer, Tooltip } from 'recharts';
import { motion, AnimatePresence } from 'framer-motion';
import { authApi } from '../api/auth';
import { incidentsApi } from '../api/incidents';
type ViewMode = 'NETWORK_OVERVIEW' | 'HOSPITAL_MGMT' | 'APPROVAL_QUEUE' | 'ANALYTICS';
export const HospitalsNetwork: React.FC = () => {
const [viewMode, setViewMode] = useState<ViewMode>('NETWORK_OVERVIEW');
const [isModalOpen, setIsModalOpen] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [realHospitals, setRealHospitals] = useState<any[]>([]);
const [incidents, setIncidents] = useState<any[]>([]);
const [issues, setIssues] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [editingHospital, setEditingHospital] = useState<any | null>(null);
const navigate = useNavigate();
const loadIncidents = async () => {
try {
const token = localStorage.getItem('teleems_token') || '';
if (!token) return;
const res = await incidentsApi.getIncidents({ limit: 10 }, token);
if (res && res.data) {
setIncidents(res.data);
}
} catch (err) {
console.error('Failed to load incidents:', err);
}
};
const loadHospitals = async () => {
try {
const token = localStorage.getItem('teleems_token') || '';
if (!token) return;
const response = await authApi.getUsers(token);
if (response && (response.status === 401 || response.message?.toLowerCase().includes('expired'))) {
localStorage.removeItem('teleems_auth');
localStorage.removeItem('teleems_token');
localStorage.removeItem('teleems_user');
navigate('/login');
return;
}
if (response && response.data) {
// Robust Hospital Node extraction
const filteredAdmins = response.data.filter((u: any) => {
const roles = Array.isArray(u.roles) ? u.roles.map((r: any) => String(r).toUpperCase()) : [];
return roles.includes('HOSPITAL ADMIN') || roles.includes('HOSPITAL_ADMIN');
});
const hospitalNodes = filteredAdmins.map((u: any) => {
const metaHosp = u.metadata?.hospital || {};
const metaOrg = u.metadata?.organization || {};
// Determine activity status
const activeInc = incidents.filter(i => i.hospital_id === u.id && i.status !== 'RESOLVED').length;
const [available] = (metaHosp.beds || '0/0').split('/').map((n: string) => parseInt(n) || 0);
let activityStatus = 'IDLE';
if (activeInc > 5) activityStatus = 'CRITICAL LOAD';
else if (activeInc > 0) activityStatus = `HANDLING ${activeInc} INCIDENTS`;
else if (available < 5) activityStatus = 'NEAR CAPACITY';
return {
id: u.id,
name: metaHosp.name || metaOrg.company_name || u.name || u.username || 'Unknown Hospital',
type: metaHosp.type || metaHosp.specialization || 'Multi-Specialty',
beds: metaHosp.beds || '15/60',
status: u.status || 'ACTIVE',
activity: activityStatus,
accreditation: metaHosp.accreditation || 'NABH',
admin: u.name || u.username,
phone: u.phone || 'Contact Support',
email: u.email,
city: metaHosp.city || metaOrg.city || 'Chennai',
radius: metaHosp.radius || '15km',
zones: [metaHosp.city || metaOrg.city || 'Chennai'],
rawMetadata: u.metadata,
roles: u.roles || []
};
});
setRealHospitals(hospitalNodes);
// Derive current issues
const newIssues = hospitalNodes.map(h => {
const [available] = (h.beds || '0/0').split('/').map((n: string) => parseInt(n) || 0);
if (available === 0) return { type: 'CRITICAL', msg: `${h.name}: Zero bed capacity`, hospital: h.name };
if (available < 5) return { type: 'WARNING', msg: `${h.name}: Low bed availability`, hospital: h.name };
return null;
}).filter(Boolean);
setIssues(newIssues as any[]);
}
} catch (error) {
console.error('Failed to fetch hospitals:', error);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
loadHospitals();
loadIncidents();
const interval = setInterval(() => {
loadHospitals();
loadIncidents();
}, 30000);
return () => clearInterval(interval);
}, []);
const hospitalStats = useMemo(() => {
return realHospitals.map(h => {
const [available, total] = (h.beds || '0/0').split('/').map((n: string) => parseInt(n) || 0);
return {
name: h.name,
total: total || 100,
available: available || 0
};
});
}, [realHospitals]);
const handleHospitalSubmit = async (data: any) => {
setIsSubmitting(true);
try {
const token = localStorage.getItem('teleems_token') || '';
if (!token) {
throw new Error('No authentication token found. Please login again.');
}
const result = await authApi.registerUser(data, token);
if (result.error || result.status === 401) {
throw new Error(result.error?.message || result.message || 'Unauthorized');
}
console.log('Hospital Registration Success:', result);
alert('Hospital registered successfully!');
setIsModalOpen(false);
loadHospitals();
} catch (error: any) {
console.error('Registration failed:', error);
const isExpired = error.message.includes('expired');
alert(`Registration failed: ${error.message}${isExpired ? '. Your session has expired, redirecting to login...' : ''}`);
if (isExpired) {
localStorage.removeItem('teleems_auth');
localStorage.removeItem('teleems_token');
localStorage.removeItem('teleems_user');
navigate('/login');
}
} finally {
setIsSubmitting(false);
}
};
const handleStatusToggle = async (hospital: any) => {
try {
const newStatus = hospital.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE';
const token = localStorage.getItem('teleems_token') || '';
const payload = {
name: hospital.admin,
email: hospital.email,
phone: hospital.phone || '',
status: newStatus,
role: 'HOSPITAL_ADMIN',
metadata: hospital.rawMetadata
};
const res = await authApi.updateUser(hospital.id, payload, token);
if (res.status === 401) {
navigate('/login');
return;
}
loadHospitals();
} catch (error) {
console.error('Failed to toggle status:', error);
}
};
const handleEditHospitalSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!editingHospital) return;
setIsSubmitting(true);
try {
const token = localStorage.getItem('teleems_token') || '';
const payload = {
name: editingHospital.admin,
email: editingHospital.email,
phone: editingHospital.phone || '',
status: editingHospital.status,
role: 'HOSPITAL_ADMIN',
metadata: {
...editingHospital.rawMetadata,
hospital: {
...editingHospital.rawMetadata?.hospital,
name: editingHospital.name,
city: editingHospital.city
}
}
};
const res = await authApi.updateUser(editingHospital.id, payload, token);
if (res.status === 401) {
navigate('/login');
return;
}
setEditingHospital(null);
loadHospitals();
} catch (error) {
console.error('Update failed:', error);
} finally {
setIsSubmitting(false);
}
};
const triggerSubmit = () => {
const form = document.getElementById('hospital-reg-form') as HTMLFormElement;
if (form) form.requestSubmit();
};
return (
<div className="page-container" style={{ padding: '0 40px 40px 40px' }}>
<header className="network-header-premium" style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<div>
<h2 style={{ fontSize: '2.5rem', fontWeight: 900, background: 'linear-gradient(90deg, var(--accent-blue), var(--text-primary))', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', letterSpacing: '-1.5px' }}>
Hospital Governance
</h2>
<p style={{ color: 'var(--text-secondary)', fontSize: '0.9rem', marginTop: '4px', fontWeight: 700, letterSpacing: '0.5px' }}>
NETWORK OPERATIONAL CONTROL {realHospitals.length} ACTIVE NODES
</p>
</div>
<div className="glass" style={{ padding: '6px', borderRadius: '12px', display: 'flex', gap: '4px', background: 'rgba(0,0,0,0.03)' }}>
<button
onClick={() => setViewMode('NETWORK_OVERVIEW')}
style={{
padding: '10px 20px', borderRadius: '8px', border: 'none',
background: viewMode === 'NETWORK_OVERVIEW' ? 'var(--accent-cyan)' : 'transparent',
color: viewMode === 'NETWORK_OVERVIEW' ? '#fff' : 'var(--text-secondary)',
fontWeight: 700, cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px', fontSize: '0.8rem'
}}>
<Shield size={16} /> NETWORK OVERVIEW
</button>
<button
onClick={() => setViewMode('HOSPITAL_MGMT')}
style={{
padding: '10px 20px', borderRadius: '8px', border: 'none',
background: viewMode === 'HOSPITAL_MGMT' ? 'var(--accent-cyan)' : 'transparent',
color: viewMode === 'HOSPITAL_MGMT' ? '#fff' : 'var(--text-secondary)',
fontWeight: 700, cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px', fontSize: '0.8rem'
}}>
<Hospital size={16} /> ACCOUNTS
</button>
<button
onClick={() => setViewMode('APPROVAL_QUEUE')}
style={{
padding: '10px 20px', borderRadius: '8px', border: 'none',
background: viewMode === 'APPROVAL_QUEUE' ? 'var(--accent-cyan)' : 'transparent',
color: viewMode === 'APPROVAL_QUEUE' ? '#fff' : 'var(--text-secondary)',
fontWeight: 700, cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px', fontSize: '0.8rem'
}}>
<CheckCircle2 size={16} /> APPROVALS <span style={{ background: 'var(--alert-red)', color: '#fff', fontSize: '0.6rem', padding: '2px 6px', borderRadius: '10px' }}>2</span>
</button>
<button
onClick={() => setViewMode('ANALYTICS')}
style={{
padding: '10px 20px', borderRadius: '8px', border: 'none',
background: viewMode === 'ANALYTICS' ? 'var(--accent-cyan)' : 'transparent',
color: viewMode === 'ANALYTICS' ? '#fff' : 'var(--text-secondary)',
fontWeight: 700, cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px', fontSize: '0.8rem'
}}>
<Stethoscope size={16} /> REPORTS
</button>
</div>
</header>
<AnimatePresence mode="wait">
{viewMode === 'NETWORK_OVERVIEW' && (
<motion.div key="overview" initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }} style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
{/* NETWORK PULSE STRIP */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '20px' }}>
<Card style={{ padding: '20px', background: 'rgba(59, 130, 246, 0.03)', border: '1px solid rgba(59, 130, 246, 0.2)' }}>
<div style={{ fontSize: '0.7rem', color: 'var(--text-secondary)', textTransform: 'uppercase', fontWeight: 800 }}>Live Incidents</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: '10px', marginTop: '8px' }}>
<div style={{ fontSize: '2.5rem', fontWeight: 900, color: 'var(--accent-cyan)' }}>{incidents.filter(i => i.status !== 'RESOLVED').length}</div>
<div style={{ fontSize: '0.8rem', color: 'var(--accent-green)', fontWeight: 700 }}>ACTIVE</div>
</div>
</Card>
<Card style={{ padding: '20px', background: 'rgba(255, 82, 82, 0.03)', border: '1px solid rgba(255, 82, 82, 0.2)' }}>
<div style={{ fontSize: '0.7rem', color: 'var(--text-secondary)', textTransform: 'uppercase', fontWeight: 800 }}>Critical Issues</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: '10px', marginTop: '8px' }}>
<div style={{ fontSize: '2.5rem', fontWeight: 900, color: 'var(--alert-red)' }}>{issues.filter(i => i.type === 'CRITICAL').length}</div>
<div style={{ fontSize: '0.8rem', color: 'var(--alert-red)', fontWeight: 700 }}>ALERTS</div>
</div>
</Card>
<Card style={{ padding: '20px' }}>
<div style={{ fontSize: '0.7rem', color: 'var(--text-secondary)', textTransform: 'uppercase', fontWeight: 800 }}>Network Sync</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: '10px', marginTop: '8px' }}>
<div style={{ fontSize: '2.5rem', fontWeight: 900, color: 'var(--text-primary)' }}>98%</div>
<div style={{ fontSize: '0.8rem', color: 'var(--accent-cyan)', fontWeight: 700 }}>STABLE</div>
</div>
</Card>
<Card style={{ padding: '20px' }}>
<div style={{ fontSize: '0.7rem', color: 'var(--text-secondary)', textTransform: 'uppercase', fontWeight: 800 }}>System Health</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginTop: '12px' }}>
<div style={{ width: '12px', height: '12px', borderRadius: '50%', background: 'var(--accent-green)', boxShadow: '0 0 10px var(--accent-green)' }}></div>
<div style={{ fontSize: '1.2rem', fontWeight: 800 }}>OPTIMAL</div>
</div>
</Card>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 2fr) 1fr', gap: '24px' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h3 style={{ fontSize: '1.2rem', fontWeight: 800, display: 'flex', alignItems: 'center', gap: '10px' }}>
<Hospital size={20} color="var(--accent-cyan)" /> HOSPITAL NODES
</h3>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: '20px' }}>
{realHospitals.length === 0 && !isLoading && (
<div style={{ gridColumn: '1/-1', textAlign: 'center', padding: '40px', color: 'var(--text-secondary)' }}>
No hospital nodes registered in network.
</div>
)}
{realHospitals.map((h) => (
<Card key={h.id} className="hover-glow" style={{ padding: '20px', border: issues.some(i => i.hospital === h.name && i.type === 'CRITICAL') ? '1px solid var(--alert-red)' : '1px solid var(--card-border)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<div style={{ width: '44px', height: '44px', background: 'rgba(59, 130, 246, 0.1)', borderRadius: '10px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Hospital size={22} color="var(--accent-cyan)" />
</div>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: '4px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', fontSize: '0.65rem', fontWeight: 800, color: h.status === 'ACTIVE' ? 'var(--accent-green)' : 'var(--alert-red)', textTransform: 'uppercase' }}>
<div style={{ width: '6px', height: '6px', borderRadius: '50%', background: h.status === 'ACTIVE' ? 'var(--accent-green)' : 'var(--alert-red)' }} />
{h.status}
</div>
<div style={{ fontSize: '0.6rem', color: 'var(--text-secondary)', fontWeight: 700 }}>{h.activity}</div>
</div>
</div>
<div style={{ marginTop: '20px' }}>
<div style={{ fontSize: '1.2rem', fontWeight: 800 }}>{h.name}</div>
<div style={{ display: 'flex', gap: '8px', marginTop: '6px' }}>
<span style={{ fontSize: '0.65rem', color: 'var(--accent-cyan)', fontWeight: 700 }}>{h.type}</span>
<span style={{ fontSize: '0.65rem', color: 'var(--text-secondary)' }}> {h.city}</span>
</div>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginTop: '24px', borderTop: '1px solid var(--card-border)', paddingTop: '16px' }}>
<div>
<div style={{ fontSize: '0.6rem', color: 'var(--text-secondary)', textTransform: 'uppercase' }}>Available Beds</div>
<div className="mono" style={{ fontSize: '1rem', fontWeight: 800, color: h.beds.startsWith('0') ? 'var(--alert-red)' : 'var(--accent-green)' }}>{h.beds}</div>
</div>
<div style={{ textAlign: 'right' }}>
<div style={{ fontSize: '0.6rem', color: 'var(--text-secondary)', textTransform: 'uppercase' }}>Active Cases</div>
<div className="mono" style={{ fontSize: '1rem', fontWeight: 800, color: 'var(--text-primary)' }}>{incidents.filter(i => i.hospital_id === h.id && i.status !== 'RESOLVED').length}</div>
</div>
</div>
</Card>
))}
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
<Card title="CRITICAL ISSUES" subtitle="Real-time network alerts" style={{ border: '1px solid rgba(255, 82, 82, 0.3)' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px', marginTop: '15px' }}>
{issues.length === 0 ? (
<div style={{ padding: '20px', textAlign: 'center', color: 'var(--text-secondary)', fontSize: '0.8rem' }}>No critical issues detected.</div>
) : (
issues.map((issue, idx) => (
<div key={idx} style={{ padding: '12px', background: issue.type === 'CRITICAL' ? 'rgba(255, 82, 82, 0.1)' : 'rgba(255, 183, 77, 0.1)', borderLeft: `4px solid ${issue.type === 'CRITICAL' ? 'var(--alert-red)' : 'var(--warning-amber)'}`, borderRadius: '4px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '4px' }}>
<AlertCircle size={14} color={issue.type === 'CRITICAL' ? 'var(--alert-red)' : 'var(--warning-amber)'} />
<span style={{ fontSize: '0.75rem', fontWeight: 800, color: issue.type === 'CRITICAL' ? 'var(--alert-red)' : 'var(--warning-amber)' }}>{issue.type}</span>
</div>
<div style={{ fontSize: '0.85rem', fontWeight: 600 }}>{issue.msg}</div>
<div style={{ fontSize: '0.7rem', color: 'var(--text-secondary)', marginTop: '4px' }}>Identified 2m ago</div>
</div>
))
)}
</div>
</Card>
<Card title="LIVE ACTIVITY" subtitle="Latest incidents across network">
<div style={{ display: 'flex', flexDirection: 'column', gap: '15px', marginTop: '15px' }}>
{incidents.length === 0 ? (
<div style={{ padding: '20px', textAlign: 'center', color: 'var(--text-secondary)', fontSize: '0.8rem' }}>Monitoring network traffic...</div>
) : (
incidents.slice(0, 5).map((inc, i) => (
<div key={i} style={{ paddingBottom: '12px', borderBottom: '1px solid var(--card-border)', display: 'flex', gap: '12px' }}>
<div style={{ width: '32px', height: '32px', background: 'rgba(0,0,0,0.03)', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
<Navigation2 size={16} color="var(--accent-cyan)" />
</div>
<div>
<div style={{ fontSize: '0.85rem', fontWeight: 700 }}>{inc.patient_name || 'Emergency Call'}</div>
<div style={{ fontSize: '0.7rem', color: 'var(--text-secondary)' }}>{inc.status.toUpperCase()} {inc.priority || 'P1'}</div>
<div style={{ fontSize: '0.65rem', color: 'var(--accent-cyan)', marginTop: '4px' }}>{new Date(inc.created_at).toLocaleTimeString()}</div>
</div>
</div>
))
)}
</div>
{incidents.length > 5 && (
<button style={{ width: '100%', padding: '10px', background: 'transparent', border: 'none', color: 'var(--accent-cyan)', fontSize: '0.75rem', fontWeight: 700, cursor: 'pointer', marginTop: '10px' }}>VIEW ALL ACTIVITY</button>
)}
</Card>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '24px' }}>
<Card title="Regional Bed Capacity" subtitle="Real-time availability by hospital node">
<div style={{ height: '300px', marginTop: '20px' }}>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={hospitalStats} layout="vertical">
<XAxis type="number" hide />
<YAxis dataKey="name" type="category" width={100} stroke="var(--text-secondary)" fontSize={11} tick={{fontWeight: 600}} />
<Tooltip cursor={{fill: 'rgba(0,0,0,0.02)'}} contentStyle={{ background: 'var(--base-bg)', border: '1px solid var(--card-border)', borderRadius: '8px' }} />
<Bar dataKey="total" fill="rgba(0,0,0,0.03)" radius={[0, 4, 4, 0]} barSize={20} />
<Bar dataKey="available" fill="var(--accent-cyan)" radius={[0, 4, 4, 0]} barSize={20} />
</BarChart>
</ResponsiveContainer>
</div>
</Card>
<Card title="HMIS Data Exchange Health" subtitle="Integration status of hospital information systems">
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
{[
{ name: 'Apollo Main', api: 'REST/FHIR', status: 'Healthy', latency: '45ms', lastSync: '12s ago' },
{ name: 'MGM Healthcare', api: 'REST/FHIR', status: 'Healthy', latency: '38ms', lastSync: '5s ago' },
{ name: 'MIOT Int.', api: 'REST/FHIR', status: 'Healthy', latency: '42ms', lastSync: '8s ago' },
{ name: 'Global Health', api: 'HL7v2', status: 'Syncing', latency: '120ms', lastSync: '1m ago' },
{ name: 'Stanley Medical', api: 'Direct SQL', status: 'Critical', latency: '--', lastSync: '14h ago' },
].map((item, i) => (
<div key={i} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '14px', background: 'rgba(0,0,0,0.02)', borderRadius: '10px', border: '1px solid var(--card-border)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<div style={{ width: '8px', height: '8px', borderRadius: '50%', background: item.status === 'Healthy' ? 'var(--accent-green)' : (item.status === 'Syncing' ? 'var(--warning-amber)' : 'var(--alert-red)') }} />
<div>
<div style={{ fontSize: '0.85rem', fontWeight: 700 }}>{item.name}</div>
<div style={{ fontSize: '0.65rem', color: 'var(--text-secondary)' }}>{item.api} Protocol</div>
</div>
</div>
<div style={{ textAlign: 'right' }}>
<div className="mono" style={{ fontSize: '0.75rem', color: 'var(--accent-cyan)' }}>{item.latency}</div>
<div style={{ fontSize: '0.65rem', color: 'var(--text-secondary)' }}>{item.lastSync}</div>
</div>
</div>
))}
</div>
</Card>
</div>
</motion.div>
)}
{viewMode === 'HOSPITAL_MGMT' && (
<HospitalManagement
key="management"
hospitals={realHospitals}
onRegister={() => setIsModalOpen(true)}
onEdit={(h) => setEditingHospital(h)}
onToggleStatus={handleStatusToggle}
/>
)}
{viewMode === 'APPROVAL_QUEUE' && (
<motion.div key="approvals" initial={{ opacity: 0 }} animate={{ opacity: 1 }} style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
<Card title="Pending Hospital Registrations" subtitle="Review and approve new hospital node requests.">
<table style={{ width: '100%', borderCollapse: 'collapse', marginTop: '20px' }}>
<thead>
<tr style={{ textAlign: 'left', borderBottom: '1px solid var(--card-border)' }}>
<th style={{ padding: '16px' }}>Request Details</th>
<th style={{ padding: '16px' }}>Admin Info</th>
<th style={{ padding: '16px' }}>Submitted</th>
<th style={{ padding: '16px', textAlign: 'right' }}>Actions</th>
</tr>
</thead>
<tbody>
{[
{ id: 'REQ-9901', name: 'Global Health City', city: 'Chennai', admin: 'Dr. S. Karthik', email: 'sk@globalhealth.com', date: '2026-04-16' },
{ id: 'REQ-9905', name: 'Fortis Malar', city: 'Chennai', admin: 'Pavitra M.', email: 'p.malar@fortis.com', date: '2026-04-15' }
].map(req => (
<tr key={req.id} style={{ borderBottom: '1px solid rgba(0,0,0,0.02)' }}>
<td style={{ padding: '16px' }}>
<div style={{ fontWeight: 700 }}>{req.name}</div>
<div style={{ fontSize: '0.75rem', color: 'var(--text-secondary)' }}>{req.city} Node</div>
</td>
<td style={{ padding: '16px' }}>
<div style={{ fontSize: '0.85rem' }}>{req.admin}</div>
<div style={{ fontSize: '0.7rem', color: 'var(--accent-cyan)' }}>{req.email}</div>
</td>
<td style={{ padding: '16px', fontSize: '0.85rem' }}>{req.date}</td>
<td style={{ padding: '16px', textAlign: 'right' }}>
<div style={{ display: 'flex', gap: '10px', justifyContent: 'flex-end' }}>
<button style={{ padding: '8px 16px', background: 'var(--accent-green)', color: '#fff', border: 'none', borderRadius: '4px', fontWeight: 700, cursor: 'pointer' }}>APPROVE</button>
<button style={{ padding: '8px 16px', background: 'rgba(0,0,0,0.02)', color: 'var(--alert-red)', border: '1px solid var(--alert-red)', borderRadius: '4px', fontWeight: 700, cursor: 'pointer' }}>REJECT</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</Card>
</motion.div>
)}
{viewMode === 'ANALYTICS' && (
<motion.div key="analytics" initial={{ opacity: 0 }} animate={{ opacity: 1 }} style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '24px' }}>
<Card title="Total Incidents Handled" subtitle="Last 30 days total volume">
<div style={{ fontSize: '3rem', fontWeight: 900, color: 'var(--accent-cyan)', margin: '20px 0' }}>1,248</div>
<div style={{ color: 'var(--accent-green)', fontWeight: 700 }}> 14% vs last month</div>
</Card>
<Card title="Avg Handover Time" subtitle="Ambulance arrival to ED handoff">
<div style={{ fontSize: '3rem', fontWeight: 900, color: 'var(--text-primary)', margin: '20px 0' }}>12.4m</div>
<div style={{ color: 'var(--accent-green)', fontWeight: 700 }}>-1.2m improvement</div>
</Card>
<Card title="Clinical Escalation Rate" subtitle="TeleLink sessions requiring specialist">
<div style={{ fontSize: '3rem', fontWeight: 900, color: 'var(--alert-red)', margin: '20px 0' }}>8.2%</div>
<div style={{ color: 'var(--text-secondary)', fontWeight: 700 }}>Target: &lt; 10%</div>
</Card>
</div>
<Card title="Network Volume Trends">
<div style={{ height: '300px', display: 'flex', alignItems: 'flex-end', gap: '10px', padding: '40px 0' }}>
{[40, 65, 52, 88, 70, 95, 82, 60, 75, 90, 110, 85].map((h, i) => (
<div key={i} style={{ flex: 1, background: 'var(--accent-cyan)', height: `${h}%`, borderRadius: '4px 4px 0 0', opacity: 0.6 + (h/200), position: 'relative' }}>
<div style={{ position: 'absolute', top: '-25px', left: '50%', transform: 'translateX(-50%)', fontSize: '0.6rem', fontWeight: 900 }}>{h}</div>
</div>
))}
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.7rem', color: 'var(--text-secondary)', marginTop: '10px' }}>
<span>APR 01</span>
<span>APR 07</span>
<span>APR 14</span>
<span>APR 21</span>
<span>APR 28</span>
</div>
</Card>
</motion.div>
)}
</AnimatePresence>
<Modal
isOpen={isModalOpen}
title="Register New Hospital"
onClose={() => setIsModalOpen(false)}
onSubmit={triggerSubmit}
loading={isSubmitting}
>
<HospitalRegistrationForm onSubmit={handleHospitalSubmit} loading={isSubmitting} />
</Modal>
{/* EDIT HOSPITAL MODAL */}
<Modal
isOpen={!!editingHospital}
title={`Edit ${editingHospital?.name}`}
onClose={() => setEditingHospital(null)}
onSubmit={() => {
const form = document.getElementById('hospital-edit-form') as HTMLFormElement;
if (form) form.requestSubmit();
}}
loading={isSubmitting}
>
{editingHospital && (
<form id="hospital-edit-form" onSubmit={handleEditHospitalSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
<div>
<label style={{ display: 'block', fontSize: '0.7rem', color: 'var(--accent-cyan)', marginBottom: '8px', fontWeight: 700 }}>HOSPITAL FULL NAME</label>
<input name="name" type="text" required value={editingHospital.name} onChange={(e) => setEditingHospital({...editingHospital, name: e.target.value})} style={{ width: '100%', padding: '12px', background: 'rgba(0,0,0,0.02)', border: '1px solid var(--card-border)', borderRadius: '8px', color: 'var(--text-primary)' }} />
</div>
<div>
<label style={{ display: 'block', fontSize: '0.7rem', color: 'var(--accent-cyan)', marginBottom: '8px', fontWeight: 700 }}>PRIMARY CITY</label>
<input name="city" type="text" required value={editingHospital.city} onChange={(e) => setEditingHospital({...editingHospital, city: e.target.value})} style={{ width: '100%', padding: '12px', background: 'rgba(0,0,0,0.02)', border: '1px solid var(--card-border)', borderRadius: '8px', color: 'var(--text-primary)' }} />
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1.2fr 1fr', gap: '20px' }}>
<div>
<label style={{ display: 'block', fontSize: '0.7rem', color: 'var(--accent-cyan)', marginBottom: '8px', fontWeight: 700 }}>ADMINISTRATOR NAME</label>
<input name="admin" type="text" required value={editingHospital.admin} onChange={(e) => setEditingHospital({...editingHospital, admin: e.target.value})} style={{ width: '100%', padding: '12px', background: 'rgba(0,0,0,0.02)', border: '1px solid var(--card-border)', borderRadius: '8px', color: 'var(--text-primary)' }} />
</div>
<div>
<label style={{ display: 'block', fontSize: '0.7rem', color: 'var(--accent-cyan)', marginBottom: '8px', fontWeight: 700 }}>CONTACT PHONE</label>
<input name="phone" type="text" required value={editingHospital.phone} onChange={(e) => setEditingHospital({...editingHospital, phone: e.target.value})} style={{ width: '100%', padding: '12px', background: 'rgba(0,0,0,0.02)', border: '1px solid var(--card-border)', borderRadius: '8px', color: 'var(--text-primary)' }} />
</div>
</div>
<div>
<label style={{ display: 'block', fontSize: '0.7rem', color: 'var(--accent-cyan)', marginBottom: '8px', fontWeight: 700 }}>OFFICIAL EMAIL</label>
<input name="email" type="email" required value={editingHospital.email} onChange={(e) => setEditingHospital({...editingHospital, email: e.target.value})} style={{ width: '100%', padding: '12px', background: 'rgba(0,0,0,0.02)', border: '1px solid var(--card-border)', borderRadius: '8px', color: 'var(--text-primary)' }} />
</div>
</form>
)}
</Modal>
</div>
);
};
// 5.3 Hospital Management (CRUD Sub-page)
const HospitalManagement: React.FC<{
hospitals: any[];
onRegister: () => void;
onEdit: (h: any) => void;
onToggleStatus: (h: any) => void;
}> = ({ hospitals, onRegister, onEdit, onToggleStatus }) => {
const filteredHospitals = hospitals; // realHospitals already filtered in loadHospitals for Admine role
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', display: 'flex', alignItems: 'center', gap: '12px', fontWeight: 700 }}>
<Settings size={24} color="var(--accent-cyan)" /> Hospital Account Management
</h3>
<div style={{ display: 'flex', gap: '12px' }}>
<button onClick={onRegister} className="glass" style={{ padding: '10px 20px', background: 'var(--accent-cyan)', color: '#fff', border: 'none', borderRadius: '6px', fontWeight: 700, display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
<Plus size={18} /> REGISTER NEW HOSPITAL
</button>
</div>
</div>
<Card>
<div style={{ overflowX: 'auto' }}>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.85rem' }}>
<thead>
<tr style={{ background: 'rgba(0,0,0,0.02)', textAlign: 'left' }}>
<th style={{ padding: '16px' }}>Hospital Details</th>
<th style={{ padding: '16px' }}>Leadership & Contact</th>
<th style={{ padding: '16px' }}>Service Area</th>
<th style={{ padding: '16px' }}>Accreditation</th>
<th style={{ padding: '16px' }}>Status</th>
<th style={{ padding: '16px', textAlign: 'right' }}>Actions</th>
</tr>
</thead>
<tbody>
{filteredHospitals.map((h, i) => (
<tr key={i} style={{ borderBottom: '1px solid rgba(0,0,0,0.02)' }}>
<td style={{ padding: '16px' }}>
<div style={{ fontWeight: 800 }}>{h.name}</div>
<div style={{ fontSize: '0.7rem', color: 'var(--accent-cyan)', marginTop: '2px', fontWeight: 700 }}>{h.type.toUpperCase()}</div>
</td>
<td style={{ padding: '16px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<UserIcon size={14} color="var(--text-secondary)" />
<span>{h.admin}</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginTop: '4px', fontSize: '0.75rem', color: 'var(--text-secondary)' }}>
<Phone size={14} color="var(--text-secondary)" />
<span>{h.phone}</span>
</div>
</td>
<td style={{ padding: '16px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<Navigation2 size={14} color="var(--accent-cyan)" />
<span className="mono" style={{ fontWeight: 700 }}>{h.radius}</span>
</div>
<div style={{ fontSize: '0.65rem', color: 'var(--text-secondary)', marginTop: '4px' }}>
{h.zones.slice(0, 2).join(', ')}{h.zones.length > 2 ? '...' : ''}
</div>
</td>
<td style={{ padding: '16px' }}>
<span style={{ fontSize: '0.7rem', padding: '3px 8px', background: 'rgba(0,0,0,0.02)', borderRadius: '4px', border: '1px solid var(--card-border)' }}>{h.acc}</span>
</td>
<td style={{ padding: '16px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', color: h.status === 'ACTIVE' ? 'var(--accent-green)' : 'var(--alert-red)', fontWeight: 800 }}>
{h.status === 'ACTIVE' ? <CheckCircle2 size={14} /> : <XCircle size={14} />}
{h.status}
</div>
</td>
<td style={{ padding: '16px', textAlign: 'right' }}>
<div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
<button
onClick={() => onEdit(h)}
style={{ background: 'transparent', border: 'none', color: 'var(--accent-cyan)', cursor: 'pointer' }}
>
<Edit2 size={18} />
</button>
<button
onClick={() => onToggleStatus(h)}
style={{
padding: '6px 12px', background: 'rgba(0,0,0,0.02)', border: '1px solid var(--card-border)',
borderRadius: '4px', fontSize: '0.65rem', color: h.status === 'ACTIVE' ? 'var(--alert-red)' : 'var(--accent-green)',
fontWeight: 800, cursor: 'pointer'
}}>
{h.status === 'ACTIVE' ? 'DEACTIVATE' : 'ACTIVATE'}
</button>
<button style={{ background: 'transparent', border: 'none', color: 'var(--alert-red)', cursor: 'pointer' }}>
<Trash2 size={18} />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</Card>
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(400px, 1fr) 400px', gap: '24px' }}>
<Card title="Teleconsult Routing Rules">
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
{[
{ trigger: 'Major Cardiac (Red)', target: 'Cath Lab / Cardiac Centre', priority: 'P0' },
{ trigger: 'Severe Burns (Red)', target: 'Burns Specialty Unit', priority: 'P0' },
{ trigger: 'Pediatric Emergency', target: 'Pediatric ED Node', priority: 'P1' },
].map((rule, i) => (
<div key={i} style={{ padding: '16px', background: 'rgba(0,0,0,0.01)', border: '1px solid var(--card-border)', borderRadius: '10px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<Stethoscope size={20} color="var(--accent-cyan)" />
<div>
<div style={{ fontWeight: 700 }}>{rule.trigger}</div>
<div style={{ fontSize: '0.75rem', color: 'var(--text-secondary)' }}>Route to: {rule.target}</div>
</div>
</div>
<div className="mono" style={{ padding: '4px 8px', background: 'rgba(59, 130, 246, 0.1)', color: 'var(--accent-cyan)', fontWeight: 800, borderRadius: '4px' }}>{rule.priority}</div>
</div>
))}
<button style={{ padding: '12px', background: 'transparent', border: '1px dashed var(--card-border)', color: 'var(--text-secondary)', borderRadius: '10px', fontSize: '0.8rem', cursor: 'pointer' }}>
+ DEFINE NEW ROUTING RULE
</button>
</div>
</Card>
<Card title="Accreditation Compliance">
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ fontSize: '0.85rem' }}>NABH Verification</span>
<div style={{ color: 'var(--accent-green)', fontWeight: 700, fontSize: '0.75rem' }}>VALID</div>
</div>
<div style={{ width: '100%', height: '4px', background: 'rgba(0,0,0,0.02)', borderRadius: '2px' }}>
<div style={{ width: '92%', height: '100%', background: 'var(--accent-green)', borderRadius: '2px' }}></div>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '8px' }}>
<span style={{ fontSize: '0.85rem' }}>Quality Assurance Audit</span>
<div style={{ color: 'var(--warning-amber)', fontWeight: 700, fontSize: '0.75rem' }}>PENDING</div>
</div>
<p style={{ fontSize: '0.7rem', color: 'var(--text-secondary)', lineHeight: '1.5' }}>
Automatic compliance checks run every 30 days. Last verification was successful for 88% of the hospital network.
</p>
<button style={{ marginTop: '10px', padding: '10px', background: 'rgba(59, 130, 246, 0.1)', border: '1px solid var(--accent-cyan)', color: 'var(--accent-cyan)', borderRadius: '8px', fontSize: '0.8rem', fontWeight: 700, cursor: 'pointer' }}>
RUN FULL COMPLIANCE SYNC
</button>
</div>
</Card>
</div>
</motion.div>
);
};
// --- COMPONENTS ---
const Modal: React.FC<{ isOpen: boolean; title: string; onClose: () => void; children: React.ReactNode; onSubmit?: () => void; loading?: boolean }> = ({ isOpen, title, onClose, children, onSubmit, loading }) => {
if (!isOpen) return null;
return (
<div className="modal-overlay" style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(255,255,255,0.85)', backdropFilter: 'blur(8px)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '40px' }}>
<motion.div initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} className="glass" style={{ width: '100%', maxWidth: '800px', maxHeight: '90vh', overflowY: 'auto', background: 'var(--card-bg)', padding: '32px', borderRadius: '20px', border: '1px solid var(--accent-cyan)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
<h3 style={{ fontSize: '1.5rem', fontWeight: 800 }}>{title}</h3>
<button onClick={onClose} style={{ background: 'transparent', border: 'none', color: 'var(--text-secondary)', cursor: 'pointer' }}><XCircle size={24} /></button>
</div>
{children}
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '16px', marginTop: '32px' }}>
<button onClick={onClose} style={{ padding: '10px 20px', background: 'rgba(0,0,0,0.02)', border: '1px solid var(--card-border)', color: 'var(--text-primary)', borderRadius: '6px', fontWeight: 700, cursor: 'pointer' }} disabled={loading}>CANCEL</button>
<button
onClick={onSubmit}
className="btn-primary"
disabled={loading}
style={{ padding: '10px 20px', background: 'var(--accent-cyan)', border: 'none', color: '#fff', borderRadius: '6px', fontWeight: 700, cursor: 'pointer', opacity: loading ? 0.7 : 1 }}
>
{loading ? 'PROCESSING...' : 'REGISTER HOSPITAL'}
</button>
</div>
</motion.div>
</div>
);
};
const HospitalRegistrationForm: React.FC<{ onSubmit: (data: any) => void }> = ({ onSubmit }) => {
const [formData, setFormData] = useState({
admin_name: '',
email: '',
phone: '',
password: '',
hospital_name: '',
city: '',
lat: '13.0827',
lon: '80.2707'
});
const [showPassword, setShowPassword] = useState(false);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const handleFormSubmit = (e: React.FormEvent) => {
e.preventDefault();
const payload = {
role: "HOSPITAL_ADMIN",
name: formData.admin_name,
phone: formData.phone,
email: formData.email,
password: formData.password,
metadata: {
hospital: {
name: formData.hospital_name,
city: formData.city,
lat: parseFloat(formData.lat),
lon: parseFloat(formData.lon)
}
}
};
onSubmit(payload);
};
return (
<form id="hospital-reg-form" onSubmit={handleFormSubmit} style={{ display: 'flex', flexWrap: 'wrap', gap: '20px' }}>
<h4 style={{ width: '100%', color: 'var(--accent-cyan)', borderBottom: '1px solid rgba(59, 130, 246, 0.1)', paddingBottom: '8px' }}>ADMINISTRATOR DETAILS</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px', flex: '1 1 200px' }}>
<label style={{ fontSize: '0.75rem', fontWeight: 600, color: 'var(--text-secondary)', textTransform: 'uppercase' }}>Full Name</label>
<input name="admin_name" value={formData.admin_name} onChange={handleChange} required placeholder="Dr. Administrator Name" className="glass" style={{ padding: '10px 14px', background: 'rgba(0,0,0,0.02)', border: '1px solid var(--card-border)', borderRadius: '6px', color: 'var(--text-primary)', outline: 'none', fontSize: '0.85rem' }} />
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px', flex: '1 1 200px' }}>
<label style={{ fontSize: '0.75rem', fontWeight: 600, color: 'var(--text-secondary)', textTransform: 'uppercase' }}>Email Address</label>
<input name="email" value={formData.email} onChange={handleChange} required type="email" placeholder="hospital@example.com" autoComplete="new-password" className="glass" style={{ padding: '10px 14px', background: 'rgba(0,0,0,0.02)', border: '1px solid var(--card-border)', borderRadius: '6px', color: 'var(--text-primary)', outline: 'none', fontSize: '0.85rem' }} />
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px', flex: '1 1 200px' }}>
<label style={{ fontSize: '0.75rem', fontWeight: 600, color: 'var(--text-secondary)', textTransform: 'uppercase' }}>Phone Number</label>
<input name="phone" value={formData.phone} onChange={handleChange} required placeholder="9876543210" className="glass" style={{ padding: '10px 14px', background: 'rgba(0,0,0,0.02)', border: '1px solid var(--card-border)', borderRadius: '6px', color: 'var(--text-primary)', outline: 'none', fontSize: '0.85rem' }} />
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px', flex: '1 1 200px' }}>
<label style={{ fontSize: '0.75rem', fontWeight: 600, color: 'var(--text-secondary)', textTransform: 'uppercase' }}>Access Password</label>
<div style={{ position: 'relative', display: 'flex', alignItems: 'center' }}>
<input name="password" value={formData.password} onChange={handleChange} required type={showPassword ? "text" : "password"} autoComplete="new-password" placeholder="••••••••" className="glass" style={{ padding: '10px 14px', paddingLeft: '36px', paddingRight: '40px', background: 'rgba(0,0,0,0.02)', border: '1px solid var(--card-border)', borderRadius: '6px', color: 'var(--text-primary)', outline: 'none', fontSize: '0.85rem', width: '100%' }} />
<Lock size={14} style={{ position: 'absolute', left: '12px', top: '50%', transform: 'translateY(-50%)', color: 'var(--text-secondary)' }} />
<button type="button" onClick={() => setShowPassword(!showPassword)} style={{ position: 'absolute', right: '14px', top: '50%', transform: 'translateY(-50%)', background: 'none', border: 'none', color: 'var(--text-secondary)', cursor: 'pointer', padding: 0, display: 'flex' }}>
{showPassword ? <EyeOff size={16} /> : <Eye size={16} />}
</button>
</div>
</div>
<h4 style={{ width: '100%', color: 'var(--accent-cyan)', borderBottom: '1px solid rgba(59, 130, 246, 0.1)', paddingBottom: '8px', marginTop: '10px' }}>HOSPITAL PROFILE</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px', flex: '1 1 100%' }}>
<label style={{ fontSize: '0.75rem', fontWeight: 600, color: 'var(--text-secondary)', textTransform: 'uppercase' }}>Hospital Name</label>
<input name="hospital_name" value={formData.hospital_name} onChange={handleChange} required placeholder="Apollo Hospital Chennai" className="glass" style={{ padding: '10px 14px', background: 'rgba(0,0,0,0.02)', border: '1px solid var(--card-border)', borderRadius: '6px', color: 'var(--text-primary)', outline: 'none', fontSize: '0.85rem' }} />
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px', flex: '1 1 200px' }}>
<label style={{ fontSize: '0.75rem', fontWeight: 600, color: 'var(--text-secondary)', textTransform: 'uppercase' }}>City</label>
<input name="city" value={formData.city} onChange={handleChange} required placeholder="Chennai" className="glass" style={{ padding: '10px 14px', background: 'rgba(0,0,0,0.02)', border: '1px solid var(--card-border)', borderRadius: '6px', color: 'var(--text-primary)', outline: 'none', fontSize: '0.85rem' }} />
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px', flex: '1 1 200px' }}>
<label style={{ fontSize: '0.75rem', fontWeight: 600, color: 'var(--text-secondary)', textTransform: 'uppercase' }}>Latitude</label>
<input name="lat" value={formData.lat} onChange={handleChange} required type="number" step="0.0001" className="glass" style={{ padding: '10px 14px', background: 'rgba(0,0,0,0.02)', border: '1px solid var(--card-border)', borderRadius: '6px', color: 'var(--text-primary)', outline: 'none', fontSize: '0.85rem' }} />
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px', flex: '1 1 200px' }}>
<label style={{ fontSize: '0.75rem', fontWeight: 600, color: 'var(--text-secondary)', textTransform: 'uppercase' }}>Longitude</label>
<input name="lon" value={formData.lon} onChange={handleChange} required type="number" step="0.0001" className="glass" style={{ padding: '10px 14px', background: 'rgba(0,0,0,0.02)', border: '1px solid var(--card-border)', borderRadius: '6px', color: 'var(--text-primary)', outline: 'none', fontSize: '0.85rem' }} />
</div>
</form>
);
};