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,200 @@
import React, { useState } from 'react';
import {
Plus,
Search,
Filter,
ShoppingCart,
Package,
AlertTriangle,
ArrowUpRight,
ArrowDownLeft,
ChevronRight,
Database,
Truck,
Activity,
Archive,
BarChart3
} from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { Card } from '../../components/Common';
interface InventoryItem {
id: string;
name: string;
category: 'MEDICINE' | 'CONSUMABLE' | 'EQUIPMENT';
totalStock: number;
minStock: number;
unit: string;
expiringSoon: number;
vehicles: { vehicleId: string; stock: number }[];
}
const MOCK_INVENTORY: InventoryItem[] = [
{ id: 'ITM-001', name: 'Adrenaline Injection 1mg', category: 'MEDICINE', totalStock: 450, minStock: 100, unit: 'AMPULES', expiringSoon: 24, vehicles: [{ vehicleId: 'V-001', stock: 10 }, { vehicleId: 'V-002', stock: 8 }] },
{ id: 'ITM-002', name: 'Oxygen Cylinder (D-Type)', category: 'EQUIPMENT', totalStock: 32, minStock: 10, unit: 'UNITS', expiringSoon: 0, vehicles: [{ vehicleId: 'V-001', stock: 2 }, { vehicleId: 'V-002', stock: 1 }] },
{ id: 'ITM-003', name: 'Surgical Gloves (Size 7)', category: 'CONSUMABLE', totalStock: 1200, minStock: 500, unit: 'PAIRS', expiringSoon: 0, vehicles: [{ vehicleId: 'V-001', stock: 50 }, { vehicleId: 'V-002', stock: 40 }] },
{ id: 'ITM-004', name: 'IV Fluids (NS 500ml)', category: 'MEDICINE', totalStock: 85, minStock: 150, unit: 'BOTTLES', expiringSoon: 12, vehicles: [{ vehicleId: 'V-001', stock: 5 }, { vehicleId: 'V-002', stock: 3 }] },
];
export const FleetInventory: React.FC = () => {
const [selectedItem, setSelectedItem] = useState<InventoryItem | null>(null);
return (
<div className="fleet-inventory animate-in fade-in duration-500">
{/* Top Stats */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px', marginBottom: '24px' }}>
<div className="glass" style={{ padding: '20px', borderRadius: '16px', border: '1px solid rgba(255,255,255,0.05)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '12px' }}>
<div style={{ color: 'var(--accent-cyan)' }}><Archive size={20} /></div>
<span style={{ fontSize: '0.65rem', fontWeight: 900, color: 'var(--accent-green)' }}>+12%</span>
</div>
<div style={{ fontSize: '1.5rem', fontWeight: 900 }}>1,248</div>
<div style={{ fontSize: '0.7rem', opacity: 0.5, textTransform: 'uppercase' }}>TOTAL SKUs</div>
</div>
<div className="glass" style={{ padding: '20px', borderRadius: '16px', border: '1px solid rgba(239, 68, 68, 0.2)', background: 'rgba(239, 68, 68, 0.02)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '12px' }}>
<div style={{ color: '#EF4444' }}><AlertTriangle size={20} /></div>
</div>
<div style={{ fontSize: '1.5rem', fontWeight: 900, color: '#EF4444' }}>14</div>
<div style={{ fontSize: '0.7rem', opacity: 0.5, textTransform: 'uppercase' }}>LOW STOCK ITEMS</div>
</div>
<div className="glass" style={{ padding: '20px', borderRadius: '16px', border: '1px solid rgba(245, 158, 11, 0.2)', background: 'rgba(245, 158, 11, 0.02)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '12px' }}>
<div style={{ color: '#F59E0B' }}><Clock size={20} /></div>
</div>
<div style={{ fontSize: '1.5rem', fontWeight: 900, color: '#F59E0B' }}>8</div>
<div style={{ fontSize: '0.7rem', opacity: 0.5, textTransform: 'uppercase' }}>EXPIRING (30D)</div>
</div>
<div className="glass" style={{ padding: '20px', borderRadius: '16px', border: '1px solid rgba(59, 130, 246, 0.2)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '12px' }}>
<div style={{ color: 'var(--accent-cyan)' }}><BarChart3 size={20} /></div>
</div>
<div style={{ fontSize: '1.5rem', fontWeight: 900 }}>42.5k</div>
<div style={{ fontSize: '0.7rem', opacity: 0.5, textTransform: 'uppercase' }}>CONSUMPTION (MTD)</div>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1.5fr 1fr', gap: '24px' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<Card title="Tactical Inventory Ledger">
<div style={{ marginBottom: '16px', display: 'flex', gap: '12px' }}>
<div className="glass" style={{ flex: 1, padding: '8px 16px', borderRadius: '12px', display: 'flex', alignItems: 'center', gap: '8px', border: '1px solid rgba(255,255,255,0.1)' }}>
<Search size={16} style={{ opacity: 0.5 }} />
<input
type="text"
placeholder="Filter item master by name, category, or batch..."
style={{ background: 'transparent', border: 'none', color: '#fff', fontSize: '0.8125rem', width: '100%', outline: 'none' }}
/>
</div>
<button className="btn-primary" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<Plus size={16} /> ADD STOCK
</button>
</div>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ textAlign: 'left', opacity: 0.5, fontSize: '0.65rem', textTransform: 'uppercase', borderBottom: '1px solid rgba(255,255,255,0.1)' }}>
<th style={{ padding: '12px' }}>Item Details</th>
<th style={{ padding: '12px' }}>Category</th>
<th style={{ padding: '12px' }}>Current Stock</th>
<th style={{ padding: '12px' }}>Status</th>
</tr>
</thead>
<tbody>
{MOCK_INVENTORY.map(item => (
<tr
key={item.id}
onClick={() => setSelectedItem(item)}
style={{
borderBottom: '1px solid rgba(255,255,255,0.05)',
cursor: 'pointer',
background: selectedItem?.id === item.id ? 'rgba(59, 130, 246, 0.05)' : 'transparent'
}}
className="hover-glow"
>
<td style={{ padding: '16px 12px' }}>
<div style={{ fontWeight: 700, fontSize: '0.875rem' }}>{item.name}</div>
<div style={{ fontSize: '0.65rem', opacity: 0.5 }}>SKU: {item.id}</div>
</td>
<td style={{ padding: '16px 12px' }}>
<span style={{ fontSize: '0.7rem', fontWeight: 600, opacity: 0.7 }}>{item.category}</span>
</td>
<td style={{ padding: '16px 12px' }}>
<div style={{ fontWeight: 800 }}>{item.totalStock} <span style={{ fontSize: '0.65rem', fontWeight: 500, opacity: 0.5 }}>{item.unit}</span></div>
</td>
<td style={{ padding: '16px 12px' }}>
{item.totalStock < item.minStock ? (
<div style={{ color: '#EF4444', fontSize: '0.65rem', fontWeight: 900, display: 'flex', alignItems: 'center', gap: '4px' }}>
<AlertTriangle size={12} /> CRITICAL
</div>
) : (
<div style={{ color: '#22C55E', fontSize: '0.65rem', fontWeight: 900 }}>OPTIMAL</div>
)}
</td>
</tr>
))}
</tbody>
</table>
</Card>
</div>
<div>
{selectedItem ? (
<AnimatePresence mode="wait">
<motion.div
key={selectedItem.id}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
>
<Card title="Supply Intelligence" subtitle={`Ambulance-wise distribution for ${selectedItem.name}`}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px' }}>
<div style={{ padding: '16px', borderRadius: '12px', background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)' }}>
<div style={{ fontSize: '0.65rem', opacity: 0.5, marginBottom: '4px' }}>STOCK GAP</div>
<div style={{ fontSize: '1.25rem', fontWeight: 900, color: selectedItem.totalStock < selectedItem.minStock ? '#EF4444' : '#22C55E' }}>
{selectedItem.totalStock - selectedItem.minStock}
</div>
</div>
<div style={{ padding: '16px', borderRadius: '12px', background: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.05)' }}>
<div style={{ fontSize: '0.65rem', opacity: 0.5, marginBottom: '4px' }}>REORDER POINT</div>
<div style={{ fontSize: '1.25rem', fontWeight: 900 }}>{selectedItem.minStock}</div>
</div>
</div>
<div>
<h4 style={{ fontSize: '0.75rem', fontWeight: 800, textTransform: 'uppercase', color: 'var(--accent-cyan)', marginBottom: '12px' }}>Ambulance Stock Distribution</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
{selectedItem.vehicles.map((v, i) => (
<div key={i} style={{ padding: '12px', borderRadius: '12px', background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.05)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<Truck size={14} style={{ opacity: 0.5 }} />
<span style={{ fontSize: '0.8125rem', fontWeight: 700 }}>{v.vehicleId}</span>
</div>
<div style={{ fontWeight: 800, color: v.stock < 5 ? '#EF4444' : 'inherit' }}>
{v.stock} {selectedItem.unit}
</div>
</div>
))}
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
<button className="btn-ghost" style={{ width: '100%', fontSize: '0.75rem' }}>AUDIT LOG</button>
<button className="btn-primary" style={{ width: '100%', fontSize: '0.75rem' }}>RESTOCK REQUEST</button>
</div>
</div>
</Card>
</motion.div>
</AnimatePresence>
) : (
<div className="glass" style={{ padding: '40px', borderRadius: '16px', textAlign: 'center', border: '1px dashed rgba(255,255,255,0.1)' }}>
<Package size={32} style={{ opacity: 0.2, margin: '0 auto 16px' }} />
<h3 style={{ fontSize: '1rem', fontWeight: 700, marginBottom: '8px' }}>Select Supply Item</h3>
<p style={{ fontSize: '0.875rem', color: 'var(--text-secondary)' }}>Inspect real-time stock levels across the fleet, track expiries, and manage replenishment requests.</p>
</div>
)}
</div>
</div>
</div>
);
};