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

286
src/components/TopBar.tsx Normal file
View File

@@ -0,0 +1,286 @@
import React, { useState, useEffect } from 'react';
import { Search, Bell, Clock, LogOut, Home, ArrowLeft } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { logout } from '../utils/auth';
export const TopBar: React.FC = () => {
const [time, setTime] = useState(new Date());
const navigate = useNavigate();
useEffect(() => {
const timer = setInterval(() => setTime(new Date()), 1000);
return () => clearInterval(timer);
}, []);
const handleLogout = () => {
logout();
};
const user = React.useMemo(() => {
try {
const stored = localStorage.getItem('teleems_user');
if (!stored || stored === 'undefined' || stored === 'null') return {};
const parsed = JSON.parse(stored);
return parsed && typeof parsed === 'object' ? parsed : {};
} catch {
return {};
}
}, []);
const displayName = String(user.username || 'Admin');
const rawRole = Array.isArray(user.roles) ? (user.roles[0] || 'Administrator') : 'Administrator';
// Shorten long role names for the header
const roleLabel = rawRole
.replace(/_/g, ' ')
.replace('CURESELECT ADMIN', 'CS ADMIN')
.replace('HOSPITAL ADMIN', 'H. ADMIN')
.replace('FLEET OPERATOR', 'FLEET OPS')
.replace('STATION INCHARGE', 'STATION IC');
const initials = displayName.substring(0, 2).toUpperCase() || 'AD';
const formattedTime = time.toLocaleTimeString('en-US', {
hour12: false,
timeZone: 'Asia/Kolkata',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
const tzLabel = 'IST';
return (
<header
className="glass"
style={{
height: 'var(--topbar-height)',
margin: '16px 16px 0 16px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 20px',
gap: '16px',
border: '1px solid var(--card-border)',
background: 'var(--glass-bg)',
zIndex: 900,
borderRadius: '12px',
flexShrink: 0,
minWidth: 0,
overflow: 'hidden',
}}
>
{/* ── LEFT: Search + status ────────────────────────────────── */}
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', flex: 1, minWidth: 0, overflow: 'hidden' }}>
{/* Global Navigation Controls */}
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexShrink: 0 }}>
{/* Back Navigation Button */}
<button
onClick={() => navigate(-1)}
className="hover-glow"
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '6px',
background: 'rgba(0, 0, 0, 0.03)',
border: '1px solid rgba(0, 0, 0, 0.08)',
borderRadius: '8px',
padding: '8px 14px',
color: 'var(--text-primary)',
cursor: 'pointer',
transition: 'all 0.2s ease',
fontWeight: 700,
fontSize: '0.8rem'
}}
title="Go to Previous Screen"
>
<ArrowLeft size={16} />
<span>BACK</span>
</button>
{/* Home Navigation Button */}
<button
onClick={() => navigate('/')}
className="hover-glow"
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '6px',
background: 'rgba(0, 209, 255, 0.1)',
border: '1px solid rgba(0, 209, 255, 0.3)',
borderRadius: '8px',
padding: '8px 14px',
color: 'var(--accent-cyan)',
cursor: 'pointer',
transition: 'all 0.2s ease',
fontWeight: 700,
fontSize: '0.8rem'
}}
title="Return to Dashboard"
>
<Home size={16} />
<span>HOME</span>
</button>
</div>
{/* Search bar */}
<div style={{ position: 'relative', flexShrink: 0, width: 'clamp(160px, 22vw, 320px)' }}>
<Search
size={16}
style={{ position: 'absolute', left: '12px', top: '50%', transform: 'translateY(-50%)', color: 'var(--text-secondary)', pointerEvents: 'none' }}
/>
<input
type="text"
placeholder="Search operators, hospitals, incidents..."
style={{
width: '100%',
background: 'rgba(0, 0, 0, 0.03)',
border: '1px solid var(--card-border)',
borderRadius: '8px',
padding: '8px 10px 8px 36px',
color: 'var(--text-primary)',
fontSize: '0.8rem',
outline: 'none',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
/>
</div>
{/* Status pill — flexible, shrinks if needed */}
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '7px',
background: 'rgba(0, 255, 136, 0.08)',
padding: '5px 11px',
borderRadius: '20px',
border: '1px solid rgba(0, 255, 136, 0.18)',
flexShrink: 1,
minWidth: 0,
overflow: 'hidden',
maxWidth: '260px',
}}
>
<div style={{ width: '7px', height: '7px', background: 'var(--accent-green)', borderRadius: '50%', boxShadow: '0 0 6px var(--accent-green)', flexShrink: 0 }} />
<span style={{ fontSize: '0.68rem', fontWeight: 700, color: 'var(--accent-green)', letterSpacing: '0.03em', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
SYSTEM CONTROL PLANE HEALTHY
</span>
</div>
</div>
{/* ── RIGHT: Clock + Bell + Profile ───────────────────────── */}
<div style={{ display: 'flex', alignItems: 'center', gap: '20px', flexShrink: 0 }}>
{/* Clock */}
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', color: 'var(--text-secondary)', flexShrink: 0 }}>
<Clock size={15} />
<span
className="mono"
style={{ fontSize: '0.92rem', color: 'var(--text-primary)', fontWeight: 600, whiteSpace: 'nowrap' }}
>
{formattedTime}{' '}
<span style={{ fontSize: '0.65rem', color: 'var(--text-secondary)' }}>{tzLabel}</span>
</span>
</div>
{/* Bell */}
<div style={{ position: 'relative', cursor: 'pointer', flexShrink: 0 }}>
<Bell size={18} style={{ color: 'var(--text-secondary)' }} />
<div
style={{
position: 'absolute',
top: '-4px',
right: '-5px',
width: '15px',
height: '15px',
background: 'var(--alert-red)',
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '0.6rem',
fontWeight: 700,
color: '#fff',
boxShadow: '0 0 8px var(--alert-red)',
}}
>
3
</div>
</div>
{/* Divider */}
<div style={{ height: '20px', width: '1px', background: 'var(--card-border)', flexShrink: 0 }} />
{/* Profile */}
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '10px',
cursor: 'pointer',
padding: '4px 8px',
borderRadius: '8px',
transition: 'background 0.2s',
border: '1px solid transparent',
flexShrink: 0,
maxWidth: '220px',
}}
className="hover-glow"
onClick={handleLogout}
title="Click to logout"
>
{/* Avatar */}
<div
style={{
width: '30px',
height: '30px',
borderRadius: '50%',
background: 'linear-gradient(135deg, var(--accent-cyan), #0066ff)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
fontWeight: 700,
fontSize: '0.75rem',
flexShrink: 0,
}}
>
{initials}
</div>
{/* Name + Role */}
<div style={{ display: 'flex', flexDirection: 'column', minWidth: 0, overflow: 'hidden' }}>
<span
style={{
fontSize: '0.82rem',
fontWeight: 700,
color: 'var(--text-primary)',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{displayName}
</span>
<span
style={{
fontSize: '0.65rem',
color: 'var(--accent-cyan)',
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
textTransform: 'uppercase',
letterSpacing: '0.03em',
}}
>
{roleLabel} Logout
</span>
</div>
<LogOut size={14} style={{ color: 'var(--text-secondary)', flexShrink: 0 }} />
</div>
</div>
</header>
);
};