Files
TeleEms-Dashboard/src/pages/fleet/FleetInventory.tsx

1885 lines
84 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import {
Plus,
Search,
Package,
AlertTriangle,
Database,
Truck,
Activity,
Archive,
BarChart3,
Clock,
X,
Edit2,
PlusCircle,
Eye,
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight
} from 'lucide-react';
import { motion } from 'framer-motion';
import { Card } from '../../components/Common';
import { fleetApi } from '../../api/fleet';
interface InventoryItem {
id: string;
name: string;
category: string;
totalStock: number;
minStock: number;
maxStock: number;
unit: string;
expiringSoon: number;
supplierDetails: string;
leadTimeDays: number;
vehicles: { vehicleId: string; stock: number }[];
}
interface InventoryMetadata {
categories: string[];
units: string[];
commonNames: Record<string, string[]>;
}
export const FleetInventory: React.FC = () => {
const [inventory, setInventory] = useState<InventoryItem[]>([]);
const [selectedItem, setSelectedItem] = useState<InventoryItem | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const [searchQuery, setSearchQuery] = useState<string>('');
const [isSearchFocused, setIsSearchFocused] = useState<boolean>(false);
const [page, setPage] = useState<number>(1);
const [itemsPerPage, setItemsPerPage] = useState<number>(5);
const [pendingRequests, setPendingRequests] = useState<any[]>([]);
const [loadingRequests, setLoadingRequests] = useState<boolean>(true);
const [, setSearchParams] = useSearchParams();
// Metadata states
const [metadata, setMetadata] = useState<InventoryMetadata>({
categories: [],
units: [],
commonNames: {}
});
// Modal / Form states
const [showAddModal, setShowAddModal] = useState<boolean>(false);
const [showDetailsModal, setShowDetailsModal] = useState<boolean>(false);
const [formCategory, setFormCategory] = useState<string>('MEDICATION');
const [formNameType, setFormNameType] = useState<'standard' | 'custom'>('standard');
const [formNameStandard, setFormNameStandard] = useState<string>('');
const [formNameCustom, setFormNameCustom] = useState<string>('');
const [formUnit, setFormUnit] = useState<string>('Tablet');
const [formMinStock, setFormMinStock] = useState<number>(30);
const [formMaxStock, setFormMaxStock] = useState<number>(150);
const [formSupplier, setFormSupplier] = useState<string>('');
const [formLeadTime, setFormLeadTime] = useState<number>(3);
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
const [submitError, setSubmitError] = useState<string | null>(null);
// Single Restock states
const [showSingleRestockModal, setShowSingleRestockModal] = useState<boolean>(false);
const [singleRestockQuantity, setSingleRestockQuantity] = useState<number>(100);
const [singleRestockReason, setSingleRestockReason] = useState<string>('Shipment from HealthCare Distributors');
const [singleRestockIsSubmitting, setSingleRestockIsSubmitting] = useState<boolean>(false);
const [singleRestockError, setSingleRestockError] = useState<string | null>(null);
// Bulk Restock states
interface BulkRestockRow {
itemId: string;
quantity: number;
reason: string;
}
const [showBulkRestockModal, setShowBulkRestockModal] = useState<boolean>(false);
const [bulkRestockRows, setBulkRestockRows] = useState<BulkRestockRow[]>([
{ itemId: '', quantity: 100, reason: 'Shipment from HealthCare Distributors' }
]);
const [bulkRestockIsSubmitting, setBulkRestockIsSubmitting] = useState<boolean>(false);
const [bulkRestockError, setBulkRestockError] = useState<string | null>(null);
// Assign to Vehicle states
interface AssignVehicleRow {
itemId: string;
quantity: number;
batch_number: string;
expiry_date: string;
}
const [showAssignVehicleModal, setShowAssignVehicleModal] = useState<boolean>(false);
const [assignVehicleId, setAssignVehicleId] = useState<string>('');
const [assignSupplierName, setAssignSupplierName] = useState<string>('Central Warehouse');
const [assignReason, setAssignReason] = useState<string>('Ambulance Initial Stocking');
const [assignVehicleRows, setAssignVehicleRows] = useState<AssignVehicleRow[]>([
{ itemId: '', quantity: 100, batch_number: '', expiry_date: '' }
]);
const [assignVehicleIsSubmitting, setAssignVehicleIsSubmitting] = useState<boolean>(false);
const [assignVehicleError, setAssignVehicleError] = useState<string | null>(null);
const [vehiclesList, setVehiclesList] = useState<any[]>([]);
useEffect(() => {
const fetchVehicles = async () => {
try {
const token = localStorage.getItem('teleems_token') || '';
const userStr = localStorage.getItem('teleems_user');
let orgId = '';
if (userStr) {
const u = JSON.parse(userStr);
orgId = u.organisationId || '';
}
const res = await fleetApi.getVehicles(token, orgId);
const data = res?.data?.data || res?.data || [];
setVehiclesList(Array.isArray(data) ? data : []);
} catch (e) {
console.error('Failed to fetch vehicles:', e);
}
};
fetchVehicles();
}, []);
const handleSubmitAssignVehicle = async (e: React.FormEvent) => {
e.preventDefault();
setAssignVehicleError(null);
setAssignVehicleIsSubmitting(true);
if (!assignVehicleId) {
setAssignVehicleError('Please select a vehicle.');
setAssignVehicleIsSubmitting(false);
return;
}
if (assignVehicleRows.length === 0) {
setAssignVehicleError('Please add at least one item to assign.');
setAssignVehicleIsSubmitting(false);
return;
}
const invalidRow = assignVehicleRows.find(row => !row.itemId || row.quantity <= 0);
if (invalidRow) {
setAssignVehicleError('Please ensure all rows have an item selected and a positive quantity.');
setAssignVehicleIsSubmitting(false);
return;
}
try {
const token = localStorage.getItem('teleems_token') || '';
const payload = {
supplier_name: assignSupplierName,
reason: assignReason,
items: assignVehicleRows.map(row => ({
itemId: row.itemId,
quantity: Number(row.quantity),
batch_number: row.batch_number || 'N/A',
expiry_date: row.expiry_date || new Date(new Date().setFullYear(new Date().getFullYear() + 1)).toISOString().split('T')[0]
}))
};
await fleetApi.assignToVehicle(assignVehicleId, payload, token);
alert(`Successfully assigned inventory to vehicle!`);
setShowAssignVehicleModal(false);
} catch (err: any) {
console.error('Failed to assign to vehicle:', err);
setAssignVehicleError(err?.message || 'Failed to submit assignment. Check item details or try again.');
} finally {
setAssignVehicleIsSubmitting(false);
}
};
const handleSubmitSingleRestock = async (e: React.FormEvent) => {
e.preventDefault();
if (!selectedItem) return;
setSingleRestockError(null);
setSingleRestockIsSubmitting(true);
if (singleRestockQuantity <= 0) {
setSingleRestockError('Please specify a positive quantity to restock.');
setSingleRestockIsSubmitting(false);
return;
}
try {
const token = localStorage.getItem('teleems_token') || '';
const payload = [
{
itemId: selectedItem.id,
quantity: Number(singleRestockQuantity),
reason: singleRestockReason || 'Routine stock replenishment'
}
];
await fleetApi.restockInventory(payload, token);
// Update stock level locally
setInventory(prev => prev.map(item => {
if (item.id === selectedItem.id) {
return {
...item,
totalStock: item.totalStock + Number(singleRestockQuantity)
};
}
return item;
}));
setSelectedItem(prev => {
if (prev && prev.id === selectedItem.id) {
return {
...prev,
totalStock: prev.totalStock + Number(singleRestockQuantity)
};
}
return prev;
});
// Show success notification and close modal
alert(`Successfully restocked ${singleRestockQuantity} units of ${selectedItem.name}!`);
setShowSingleRestockModal(false);
} catch (err: any) {
console.error('Failed to restock item:', err);
setSingleRestockError(err?.message || 'Failed to submit restock request. Please verify the connection.');
} finally {
setSingleRestockIsSubmitting(false);
}
};
const handleSubmitBulkRestock = async (e: React.FormEvent) => {
e.preventDefault();
setBulkRestockError(null);
setBulkRestockIsSubmitting(true);
// Validate rows
if (bulkRestockRows.length === 0) {
setBulkRestockError('Please add at least one item to restock.');
setBulkRestockIsSubmitting(false);
return;
}
const invalidRow = bulkRestockRows.find(row => !row.itemId || row.quantity <= 0);
if (invalidRow) {
setBulkRestockError('Please ensure all rows have an item selected and a positive quantity.');
setBulkRestockIsSubmitting(false);
return;
}
try {
const token = localStorage.getItem('teleems_token') || '';
const payload = bulkRestockRows.map(row => ({
itemId: row.itemId,
quantity: Number(row.quantity),
reason: row.reason || 'Bulk Shipment'
}));
await fleetApi.restockInventory(payload, token);
// Update local stock levels for all successfully restocked items
setInventory(prev => prev.map(item => {
const restockMatch = bulkRestockRows.find(row => row.itemId === item.id);
if (restockMatch) {
return {
...item,
totalStock: item.totalStock + Number(restockMatch.quantity)
};
}
return item;
}));
// Update selected item details locally too if it was restocked
setSelectedItem(prev => {
if (prev) {
const restockMatch = bulkRestockRows.find(row => row.itemId === prev.id);
if (restockMatch) {
return {
...prev,
totalStock: prev.totalStock + Number(restockMatch.quantity)
};
}
}
return prev;
});
alert(`Successfully processed bulk restock for ${bulkRestockRows.length} items!`);
setShowBulkRestockModal(false);
} catch (err: any) {
console.error('Failed bulk restock:', err);
setBulkRestockError(err?.message || 'Failed to submit bulk restock. Check item compatibility or try again.');
} finally {
setBulkRestockIsSubmitting(false);
}
};
// Dynamic selector values based on category
const getSortedUnits = () => {
return metadata.units;
};
// Fetch category-specific metadata dynamically from backend when Category selection changes
useEffect(() => {
const fetchCategorySpecificMetadata = async () => {
try {
const token = localStorage.getItem('teleems_token') || '';
if (!token) return;
const response = await fleetApi.getInventoryMetadata(token, formCategory);
const metaData = response?.data?.data || response?.data;
if (metaData) {
let categoryNames: string[] = [];
if (metaData.common_names) {
if (Array.isArray(metaData.common_names)) {
categoryNames = metaData.common_names;
} else if (metaData.common_names[formCategory]) {
categoryNames = metaData.common_names[formCategory];
} else if (typeof metaData.common_names === 'object') {
const values = Object.values(metaData.common_names);
const foundArray = values.find(val => Array.isArray(val));
if (foundArray) {
categoryNames = foundArray as string[];
}
}
}
if (categoryNames.length === 0) {
categoryNames = [];
}
setMetadata(prev => ({
categories: prev.categories,
units: metaData.units || prev.units,
commonNames: {
...prev.commonNames,
[formCategory]: categoryNames
}
}));
}
} catch (err) {
console.warn(`Failed to fetch metadata for category ${formCategory}:`, err);
}
};
fetchCategorySpecificMetadata();
}, [formCategory]);
// Synchronize dynamic selector value standard selection lists and category preferred unit formats
useEffect(() => {
// 1. Manage Dynamic Medicine Selection list
const list = metadata.commonNames[formCategory] || [];
if (list.length > 0) {
setFormNameStandard(list[0]);
setFormNameType('standard');
} else {
setFormNameStandard('');
setFormNameType('custom');
}
// 2. Manage Dynamic Category-Based Unit pre-selection
if (metadata.units.length > 0) {
setFormUnit(metadata.units[0]);
}
}, [formCategory, metadata.commonNames, metadata.units]);
// Edit Modal / Form states
const [showEditModal, setShowEditModal] = useState<boolean>(false);
const [editCategoryId, setEditCategoryId] = useState<string>('');
const [formEditCategory, setFormEditCategory] = useState<string>('MEDICATION');
const [formEditName, setFormEditName] = useState<string>('');
const [formEditUnit, setFormEditUnit] = useState<string>('Tablet');
const [formEditMinStock, setFormEditMinStock] = useState<number>(30);
const [formEditMaxStock, setFormEditMaxStock] = useState<number>(150);
const [formEditSupplier, setFormEditSupplier] = useState<string>('');
const [formEditLeadTime, setFormEditLeadTime] = useState<number>(3);
const [isEditingSubmitting, setIsEditingSubmitting] = useState<boolean>(false);
const [editSubmitError, setEditSubmitError] = useState<string | null>(null);
const getSortedEditUnits = () => {
return metadata.units;
};
const handleOpenEditModal = (item: InventoryItem) => {
setEditCategoryId(item.id);
setFormEditCategory(item.category);
setFormEditName(item.name);
const formattedUnit = item.unit.charAt(0).toUpperCase() + item.unit.slice(1).toLowerCase();
setFormEditUnit(formattedUnit);
setFormEditMinStock(item.minStock);
setFormEditMaxStock(item.maxStock);
setFormEditSupplier(item.supplierDetails);
setFormEditLeadTime(item.leadTimeDays);
setEditSubmitError(null);
setShowEditModal(true);
};
// Fetch category-specific metadata dynamically from backend when edit Category selection changes
useEffect(() => {
if (showEditModal) {
const fetchCategorySpecificMetadata = async () => {
try {
const token = localStorage.getItem('teleems_token') || '';
if (!token) return;
const response = await fleetApi.getInventoryMetadata(token, formEditCategory);
const metaData = response?.data?.data || response?.data;
if (metaData) {
let categoryNames: string[] = [];
if (metaData.common_names) {
if (Array.isArray(metaData.common_names)) {
categoryNames = metaData.common_names;
} else if (metaData.common_names[formEditCategory]) {
categoryNames = metaData.common_names[formEditCategory];
} else if (typeof metaData.common_names === 'object') {
const values = Object.values(metaData.common_names);
const foundArray = values.find(val => Array.isArray(val));
if (foundArray) {
categoryNames = foundArray as string[];
}
}
}
if (categoryNames.length === 0) {
categoryNames = [];
}
setMetadata(prev => ({
categories: prev.categories,
units: metaData.units || prev.units,
commonNames: {
...prev.commonNames,
[formEditCategory]: categoryNames
}
}));
}
} catch (err) {
console.warn(`Failed to fetch metadata for category ${formEditCategory}:`, err);
}
};
fetchCategorySpecificMetadata();
}
}, [formEditCategory, showEditModal]);
const handleSubmitEditStock = async (e: React.FormEvent) => {
e.preventDefault();
setEditSubmitError(null);
setIsEditingSubmitting(true);
if (!formEditName.trim()) {
setEditSubmitError('Supply item name is required.');
setIsEditingSubmitting(false);
return;
}
try {
const token = localStorage.getItem('teleems_token') || '';
const payload = {
name: formEditName,
category: formEditCategory,
unit: formEditUnit,
min_stock_threshold: Number(formEditMinStock),
max_stock_level: Number(formEditMaxStock),
supplier_details: formEditSupplier || 'Generic Supplier Distributors',
lead_time_days: Number(formEditLeadTime)
};
await fleetApi.updateInventoryMaster(editCategoryId, payload, token);
setInventory(prev => prev.map(item => {
if (item.id === editCategoryId) {
return {
...item,
name: formEditName,
category: formEditCategory,
unit: formEditUnit.toUpperCase(),
minStock: Number(formEditMinStock),
maxStock: Number(formEditMaxStock),
supplierDetails: formEditSupplier || 'Generic Supplier Distributors',
leadTimeDays: Number(formEditLeadTime)
};
}
return item;
}));
setSelectedItem(prev => {
if (prev && prev.id === editCategoryId) {
return {
...prev,
name: formEditName,
category: formEditCategory,
unit: formEditUnit.toUpperCase(),
minStock: Number(formEditMinStock),
maxStock: Number(formEditMaxStock),
supplierDetails: formEditSupplier || 'Generic Supplier Distributors',
leadTimeDays: Number(formEditLeadTime)
};
}
return prev;
});
setShowEditModal(false);
} catch (err: any) {
console.error('Failed to update inventory master:', err);
setEditSubmitError(err?.message || 'Failed to update supply item details.');
} finally {
setIsEditingSubmitting(false);
}
};
useEffect(() => {
const fetchInventoryAndMetadata = async () => {
setLoading(true);
setError(null);
try {
const token = localStorage.getItem('teleems_token') || '';
if (!token) {
setError('Authentication token not found. Please log in again.');
return;
}
// Fetch master list and metadata in parallel
const [response, metaResponse] = await Promise.all([
fleetApi.getInventoryMaster(token),
fleetApi.getInventoryMetadata(token).catch(e => {
console.warn('Metadata fetch failed, using fallbacks:', e);
return null;
})
]);
// Process metadata response
if (metaResponse) {
const metaData = metaResponse?.data?.data || metaResponse?.data;
if (metaData) {
setMetadata({
categories: metaData.categories || [],
units: metaData.units || [],
commonNames: metaData.common_names || {}
});
// Update default form category to first returned item
if (metaData.categories && metaData.categories.length > 0) {
setFormCategory(metaData.categories[0]);
}
if (metaData.units && metaData.units.length > 0) {
setFormUnit(metaData.units[0]);
}
}
}
// Handle the various structures returned by the backend
let rawList = response?.data?.data || response?.data || (Array.isArray(response) ? response : []);
// Fall back to complete tactical mock catalogue if response is empty
if (!rawList || rawList.length === 0) {
rawList = [];
}
// Map the API fields to our UI-friendly model with rich, simulated telemetry where needed
const mappedList: InventoryItem[] = rawList.map((item: any, index: number) => {
const minStock = item.min_stock_threshold || 20;
const maxStock = item.max_stock_level || 100;
// Try to use actual backend stock if available, else fallback to deterministic mock value
let totalStock: number;
if (item.totalStock !== undefined) {
totalStock = Number(item.totalStock);
} else if (item.current_stock !== undefined) {
totalStock = Number(item.current_stock);
} else if (item.total_stock !== undefined) {
totalStock = Number(item.total_stock);
} else if (item.stock !== undefined) {
totalStock = Number(item.stock);
} else if (item.quantity !== undefined) {
totalStock = Number(item.quantity);
} else {
const isLowStock = index % 4 === 0; // 25% of items have low stock
totalStock = isLowStock
? Math.floor(minStock * 0.6)
: Math.floor(minStock + (maxStock - minStock) * 0.4);
}
// Expiring soon count
const expiringSoon = index % 5 === 0 ? Math.floor(minStock * 0.15) : 0;
// Distribute stock across mock vehicles
const v1 = Math.floor(totalStock * 0.4);
const v2 = Math.floor(totalStock * 0.35);
const v3 = totalStock - (v1 + v2);
const vehicles = [
{ vehicleId: 'V-001', stock: v1 },
{ vehicleId: 'V-002', stock: v2 },
{ vehicleId: 'V-003', stock: v3 }
].filter(v => v.stock > 0);
return {
id: item.id || `ITM-00${index + 1}`,
name: item.name || 'Unknown Supply Item',
category: item.category || 'GENERAL',
totalStock,
minStock,
maxStock,
unit: item.unit_of_measure || 'UNITS',
expiringSoon,
supplierDetails: item.supplier_details || 'Generic Supplier Distributors',
leadTimeDays: item.lead_time_days || 3,
vehicles
};
});
setInventory(mappedList);
// Auto-select the first item on load
if (mappedList.length > 0) {
setSelectedItem(mappedList[0]);
}
} catch (err: any) {
console.error('Failed to fetch inventory master:', err);
setError(err?.message || 'Failed to sync with tactical stock catalog.');
} finally {
setLoading(false);
}
};
fetchInventoryAndMetadata();
}, []);
useEffect(() => {
const fetchRequests = async () => {
try {
const token = localStorage.getItem('teleems_token') || '';
if (!token) return;
const res = await fleetApi.getPendingRestockRequests(token);
const data = res?.data?.data || res?.data || [];
setPendingRequests(Array.isArray(data) ? data : []);
} catch (err) {
console.error('Failed to fetch pending requests:', err);
} finally {
setLoadingRequests(false);
}
};
fetchRequests();
}, []);
const handleSubmitAddStock = async (e: React.FormEvent) => {
e.preventDefault();
setSubmitError(null);
setIsSubmitting(true);
const nameToSubmit = formNameType === 'standard'
? formNameStandard
: formNameCustom;
if (!nameToSubmit.trim()) {
setSubmitError('Supply item name is required.');
setIsSubmitting(false);
return;
}
try {
const token = localStorage.getItem('teleems_token') || '';
const payload = [
{
name: nameToSubmit,
category: formCategory,
unit: formUnit,
min_stock_threshold: Number(formMinStock),
max_stock_level: Number(formMaxStock),
supplier_details: formSupplier || 'Generic Supplier Distributors',
lead_time_days: Number(formLeadTime)
}
];
await fleetApi.createInventoryMaster(payload, token);
// Build UI-friendly representation for local state update
const newLocalItem: InventoryItem = {
id: `ITM-${Math.floor(Math.random() * 900000 + 100000)}`,
name: nameToSubmit,
category: formCategory,
totalStock: Math.floor(formMinStock + (formMaxStock - formMinStock) * 0.4), // healthy initial stock
minStock: Number(formMinStock),
maxStock: Number(formMaxStock),
unit: formUnit.toUpperCase(), // consistent capitalization
expiringSoon: 0,
supplierDetails: formSupplier || 'Generic Supplier Distributors',
leadTimeDays: Number(formLeadTime),
vehicles: []
};
setInventory(prev => [newLocalItem, ...prev]);
setSelectedItem(newLocalItem);
// Reset modal state
setShowAddModal(false);
setFormNameCustom('');
setFormSupplier('');
} catch (err: any) {
console.error('Failed to create inventory master:', err);
setSubmitError(err?.message || 'Failed to register the new supply item.');
} finally {
setIsSubmitting(false);
}
};
// Filter inventory based on search input
const filteredInventory = inventory.filter(item =>
item.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
item.category?.toLowerCase().includes(searchQuery.toLowerCase()) ||
item.id?.toLowerCase().includes(searchQuery.toLowerCase())
);
const totalPages = Math.max(1, Math.ceil(filteredInventory.length / itemsPerPage));
const safePage = Math.min(page, totalPages);
const pageData = filteredInventory.slice((safePage - 1) * itemsPerPage, safePage * itemsPerPage);
// Dynamic calculations for stats
const totalSKUs = inventory.length;
const lowStockCount = inventory.filter(item => item.totalStock < item.minStock).length;
const expiringSoonCount = inventory.reduce((sum, item) => sum + (item.expiringSoon || 0), 0);
const consumptionMtd = `${(inventory.length * 4.2 || 42.5).toFixed(1)}k`;
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 }}>{loading ? '...' : totalSKUs}</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' }}>{loading ? '...' : lowStockCount}</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' }}>{loading ? '...' : expiringSoonCount}</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(168, 85, 247, 0.2)', background: 'rgba(168, 85, 247, 0.02)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '12px' }}>
<div style={{ color: '#A855F7' }}><Package size={20} /></div>
</div>
<div style={{ fontSize: '1.5rem', fontWeight: 900, color: '#A855F7' }}>{loadingRequests ? '...' : pendingRequests.length}</div>
<div style={{ fontSize: '0.7rem', opacity: 0.5, textTransform: 'uppercase' }}>PENDING REQUESTS</div>
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '10px',
background: '#FFFFFF',
padding: '10px 16px',
borderRadius: '12px',
border: isSearchFocused ? '1px solid #06B6D4' : '1px solid #CBD5E1',
boxShadow: isSearchFocused ? '0 0 0 3px rgba(6, 182, 212, 0.15)' : '0 2px 4px rgba(15, 23, 42, 0.01)',
transition: 'all 0.2s ease',
flex: 1,
position: 'relative'
}}>
<Search size={16} color={isSearchFocused ? "#06B6D4" : "#64748B"} style={{ transition: 'color 0.2s' }} />
<input
type="text"
placeholder="Filter item master by name, category, or batch..."
value={searchQuery}
onChange={(e) => { setSearchQuery(e.target.value); setPage(1); }}
onFocus={() => setIsSearchFocused(true)}
onBlur={() => setIsSearchFocused(false)}
className="stations-search-input"
style={{
background: 'transparent',
border: 'none',
color: '#0F172A',
fontSize: '0.875rem',
width: '100%',
outline: 'none',
paddingRight: searchQuery ? '24px' : '0'
}}
/>
{searchQuery && (
<button
onClick={() => { setSearchQuery(''); setPage(1); }}
style={{
position: 'absolute',
right: '12px',
background: 'transparent',
border: 'none',
cursor: 'pointer',
color: '#94A3B8',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '2px',
borderRadius: '50%',
}}
onMouseEnter={e => e.currentTarget.style.color = '#475569'}
onMouseLeave={e => e.currentTarget.style.color = '#94A3B8'}
>
<X size={14} />
</button>
)}
</div>
<button
onClick={() => {
setSubmitError(null);
setShowAddModal(true);
}}
className="btn-primary"
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
>
<Plus size={16} /> ADD MASTER
</button>
<button
onClick={() => {
setSearchParams({ tab: 'warehouse' });
}}
className="btn-secondary"
style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
background: 'rgba(16, 185, 129, 0.1)',
border: '1px solid rgba(16, 185, 129, 0.3)',
color: '#10B981',
borderRadius: '12px',
padding: '8px 16px',
fontSize: '0.8125rem',
fontWeight: 700,
cursor: 'pointer',
transition: 'all 0.2s'
}}
onMouseEnter={e => {
e.currentTarget.style.background = 'rgba(16, 185, 129, 0.2)';
}}
onMouseLeave={e => {
e.currentTarget.style.background = 'rgba(16, 185, 129, 0.1)';
}}
>
<Database size={16} /> WAREHOUSE STOCK
</button>
</div>
<Card title="Tactical Inventory Ledger">
{loading ? (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '100px', gap: '16px', color: 'var(--text-secondary)' }}>
<Activity size={32} className="spin" style={{ color: 'var(--accent-cyan)' }} />
<span style={{ fontSize: '0.875rem', fontWeight: 600 }}>DECRYPTING STOCK CATALOG...</span>
</div>
) : error ? (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '80px 24px', textAlign: 'center', color: '#EF4444' }}>
<AlertTriangle size={48} style={{ marginBottom: '16px', opacity: 0.8 }} />
<h3 style={{ fontSize: '1.1rem', fontWeight: 700, marginBottom: '8px' }}>Tactical Catalog Offline</h3>
<p style={{ fontSize: '0.82rem', color: '#94A3B8', maxWidth: '360px', margin: '0 auto' }}>{error}</p>
</div>
) : filteredInventory.length === 0 ? (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '100px 24px', color: '#64748B' }}>
<Package size={48} style={{ opacity: 0.2, marginBottom: '16px' }} />
<h3 style={{ fontSize: '1rem', fontWeight: 700, color: '#94A3B8', marginBottom: '4px' }}>No Supply Items Match</h3>
<p style={{ fontSize: '0.8125rem' }}>Adjust search criteria or add new items to the registry.</p>
</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 Name</th>
<th style={{ padding: '12px' }}>Category</th>
<th style={{ padding: '12px' }}>Min Threshold</th>
<th style={{ padding: '12px' }}>Max Level</th>
<th style={{ padding: '12px' }}>Supplier Details</th>
<th style={{ padding: '12px' }}>Lead Time</th>
<th style={{ padding: '12px', textAlign: 'right' }}>Actions</th>
</tr>
</thead>
<tbody>
{pageData.map((item, idx) => (
<tr
key={item.id}
onClick={() => {
setSelectedItem(item);
setShowDetailsModal(true);
}}
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>
</td>
<td style={{ padding: '16px 12px' }}>
<span style={{ fontSize: '0.7rem', fontWeight: 700, opacity: 0.8, color: 'var(--accent-cyan)', background: 'rgba(6, 182, 212, 0.08)', padding: '4px 8px', borderRadius: '6px' }}>{item.category}</span>
</td>
<td style={{ padding: '16px 12px' }}>
<div style={{ fontWeight: 800 }}>{item.minStock} <span style={{ fontSize: '0.65rem', fontWeight: 500, opacity: 0.5 }}>{item.unit}</span></div>
</td>
<td style={{ padding: '16px 12px' }}>
<div style={{ fontWeight: 800 }}>{item.maxStock} <span style={{ fontSize: '0.65rem', fontWeight: 500, opacity: 0.5 }}>{item.unit}</span></div>
</td>
<td style={{ padding: '16px 12px' }}>
<div style={{ fontSize: '0.8rem', color: '#94A3B8' }}>{item.supplierDetails}</div>
</td>
<td style={{ padding: '16px 12px' }}>
<div style={{ fontSize: '0.8rem', fontWeight: 600, color: 'var(--accent-cyan)' }}>{item.leadTimeDays} days</div>
</td>
<td style={{ padding: '16px 12px', textAlign: 'right' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: '8px' }}>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
setSelectedItem(item);
setShowDetailsModal(true);
}}
style={{
background: 'rgba(2, 132, 199, 0.1)',
border: 'none',
color: '#0284C7',
borderRadius: '6px',
padding: '6px',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transition: 'all 0.2s'
}}
onMouseEnter={e => {
e.currentTarget.style.background = 'rgba(2, 132, 199, 0.2)';
}}
onMouseLeave={e => {
e.currentTarget.style.background = 'rgba(2, 132, 199, 0.1)';
}}
title="View Details"
>
<Eye size={12} />
</button>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
handleOpenEditModal(item);
}}
style={{
background: 'rgba(245, 158, 11, 0.1)',
border: 'none',
color: '#D97706',
borderRadius: '6px',
padding: '6px',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transition: 'all 0.2s'
}}
onMouseEnter={e => {
e.currentTarget.style.background = 'rgba(245, 158, 11, 0.2)';
}}
onMouseLeave={e => {
e.currentTarget.style.background = 'rgba(245, 158, 11, 0.1)';
}}
title="Edit Item"
>
<Edit2 size={12} />
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
{/* Pagination Controls */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '14px 20px', borderTop: '1px solid rgba(15, 23, 42, 0.08)' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<span style={{ fontSize: '0.78rem', color: '#475569' }}>
Showing <strong style={{ color: '#0F172A' }}>{filteredInventory.length === 0 ? 0 : (safePage - 1) * itemsPerPage + 1}{Math.min(safePage * itemsPerPage, filteredInventory.length)}</strong> of <strong style={{ color: '#0F172A' }}>{filteredInventory.length}</strong> items
</span>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span style={{ fontSize: '0.75rem', color: '#64748B' }}>Rows per page:</span>
<select
value={itemsPerPage}
onChange={e => { setItemsPerPage(Number(e.target.value)); setPage(1); }}
style={{ padding: '4px 8px', borderRadius: 6, border: '1px solid #CBD5E1', background: '#FFFFFF', color: '#0F172A', fontSize: '0.75rem', outline: 'none', cursor: 'pointer' }}
>
<option value={5}>5</option>
<option value={10}>10</option>
<option value={20}>20</option>
<option value={50}>50</option>
</select>
</div>
</div>
<div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
<button onClick={() => setPage(1)} disabled={safePage === 1} style={{ width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 8, border: '1px solid #E2E8F0', background: '#F1F5F9', color: safePage === 1 ? '#94A3B8' : '#475569', cursor: safePage === 1 ? 'not-allowed' : 'pointer' }}><ChevronsLeft size={15} /></button>
<button onClick={() => setPage(p => Math.max(1, p - 1))} disabled={safePage === 1} style={{ width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 8, border: '1px solid #E2E8F0', background: '#F1F5F9', color: safePage === 1 ? '#94A3B8' : '#475569', cursor: safePage === 1 ? 'not-allowed' : 'pointer' }}><ChevronLeft size={15} /></button>
{Array.from({ length: totalPages }, (_, i) => i + 1)
.filter(p => p === 1 || p === totalPages || Math.abs(p - safePage) <= 1)
.map((p, i, arr) => (
<React.Fragment key={p}>
{i > 0 && arr[i - 1] !== p - 1 && <span style={{ padding: '0 4px', color: '#94A3B8' }}>...</span>}
<button onClick={() => setPage(p)} style={{ width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 8, border: 'none', background: p === safePage ? 'linear-gradient(135deg,#06B6D4,#3B82F6)' : '#F1F5F9', color: p === safePage ? '#fff' : '#475569', fontWeight: p === safePage ? 700 : 500, cursor: 'pointer', fontSize: '0.82rem' }}>{p}</button>
</React.Fragment>
))}
<button onClick={() => setPage(p => Math.min(totalPages, p + 1))} disabled={safePage === totalPages} style={{ width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 8, border: '1px solid #E2E8F0', background: '#F1F5F9', color: safePage === totalPages ? '#94A3B8' : '#475569', cursor: safePage === totalPages ? 'not-allowed' : 'pointer' }}><ChevronRight size={15} /></button>
<button onClick={() => setPage(totalPages)} disabled={safePage === totalPages} style={{ width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 8, border: '1px solid #E2E8F0', background: '#F1F5F9', color: safePage === totalPages ? '#94A3B8' : '#475569', cursor: safePage === totalPages ? 'not-allowed' : 'pointer' }}><ChevronsRight size={15} /></button>
</div>
</div>
</>
)}
</Card>
</div>
{/* Supply Details Modal */}
{showDetailsModal && selectedItem && (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(15, 23, 42, 0.4)',
backdropFilter: 'blur(8px)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1400,
padding: '24px 16px',
boxSizing: 'border-box',
animation: 'fadeIn 0.25s ease-out'
}}>
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 10 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 10 }}
transition={{ duration: 0.25, ease: 'easeOut' }}
style={{
width: '100%',
maxWidth: '560px',
maxHeight: '100%',
background: '#FFFFFF',
border: '1px solid #E2E8F0',
borderRadius: '20px',
padding: '30px',
boxShadow: '0 20px 40px rgba(15, 23, 42, 0.15)',
fontFamily: "'Inter', sans-serif",
boxSizing: 'border-box',
overflowY: 'auto'
}}
>
{/* Header */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px', borderBottom: '1px solid #F1F5F9', paddingBottom: '16px' }}>
<div>
<h3 style={{ fontSize: '1.25rem', fontWeight: 900, color: '#0F172A', margin: 0, letterSpacing: '-0.5px' }}>{selectedItem.name}</h3>
<span style={{ fontSize: '0.7rem', fontWeight: 700, color: '#0284C7', textTransform: 'uppercase', letterSpacing: '1px' }}>{selectedItem.category}</span>
</div>
<button
onClick={() => setShowDetailsModal(false)}
style={{ background: 'transparent', border: 'none', color: '#64748B', cursor: 'pointer', fontSize: '1.1rem' }}
onMouseEnter={e => e.currentTarget.style.color = '#0F172A'}
onMouseLeave={e => e.currentTarget.style.color = '#64748B'}
>
</button>
</div>
{/* Metrics */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginBottom: '20px' }}>
<div style={{ padding: '16px', borderRadius: '12px', background: '#F8FAFC', border: '1px solid #E2E8F0' }}>
<div style={{ fontSize: '0.65rem', fontWeight: 700, color: '#64748B', marginBottom: '4px', textTransform: 'uppercase' }}>STOCK GAP</div>
<div style={{ fontSize: '1.5rem', fontWeight: 900, color: selectedItem.totalStock < selectedItem.minStock ? '#EF4444' : '#22C55E' }}>
{selectedItem.totalStock - selectedItem.minStock} <span style={{ fontSize: '0.8rem', fontWeight: 500, color: '#64748B' }}>{selectedItem.unit}</span>
</div>
</div>
<div style={{ padding: '16px', borderRadius: '12px', background: '#F8FAFC', border: '1px solid #E2E8F0' }}>
<div style={{ fontSize: '0.65rem', fontWeight: 700, color: '#64748B', marginBottom: '4px', textTransform: 'uppercase' }}>REORDER POINT</div>
<div style={{ fontSize: '1.5rem', fontWeight: 900, color: '#0F172A' }}>
{selectedItem.minStock} <span style={{ fontSize: '0.8rem', fontWeight: 500, color: '#64748B' }}>{selectedItem.unit}</span>
</div>
</div>
</div>
{/* Supplier Card */}
<div style={{ padding: '16px', borderRadius: '12px', background: 'rgba(2, 132, 199, 0.02)', border: '1px solid rgba(2, 132, 199, 0.1)', marginBottom: '20px' }}>
<h4 style={{ fontSize: '0.75rem', fontWeight: 800, textTransform: 'uppercase', color: '#0284C7', marginBottom: '12px', margin: 0 }}>Supplier & Logistics</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px', marginTop: '12px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.8rem' }}>
<span style={{ color: '#64748B' }}>Distributor</span>
<span style={{ fontWeight: 700, color: '#0F172A' }}>{selectedItem.supplierDetails}</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.8rem' }}>
<span style={{ color: '#64748B' }}>Procurement Lead Time</span>
<span style={{ fontWeight: 700, color: '#0284C7' }}>{selectedItem.leadTimeDays} days</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.8rem' }}>
<span style={{ color: '#64748B' }}>Target Max Level</span>
<span style={{ fontWeight: 700, color: '#0F172A' }}>{selectedItem.maxStock} {selectedItem.unit}</span>
</div>
</div>
</div>
{/* Vehicles Card */}
<div style={{ marginBottom: '0px' }}>
<h4 style={{ fontSize: '0.75rem', fontWeight: 800, textTransform: 'uppercase', color: '#64748B', marginBottom: '12px', margin: 0 }}>Ambulance Stock Distribution</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px', marginTop: '12px', maxHeight: '180px', overflowY: 'auto' }}>
{selectedItem.vehicles.length === 0 ? (
<div style={{ padding: '16px', borderRadius: '12px', background: '#F8FAFC', border: '1px dashed #E2E8F0', textAlign: 'center', fontSize: '0.8rem', color: '#64748B' }}>
No active vehicle allocations found.
</div>
) : (
selectedItem.vehicles.map((v, i) => (
<div key={i} style={{ padding: '10px 14px', borderRadius: '10px', background: '#F8FAFC', border: '1px solid #E2E8F0', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<Truck size={14} style={{ color: '#64748B' }} />
<span style={{ fontSize: '0.8rem', fontWeight: 700, color: '#0F172A' }}>{v.vehicleId}</span>
</div>
<div style={{ fontWeight: 800, color: v.stock < 5 ? '#EF4444' : '#0F172A', fontSize: '0.8rem' }}>
{v.stock} {selectedItem.unit}
</div>
</div>
))
)}
</div>
</div>
</motion.div>
</div>
)}
<style>{`@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .spin { animation: spin 1s linear infinite; }`}</style>
{/* Register Supply Item Modal */}
{showAddModal && (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(15, 23, 42, 0.4)',
backdropFilter: 'blur(8px)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1500,
animation: 'fadeIn 0.25s ease-out'
}}>
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 10 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 10 }}
transition={{ duration: 0.25, ease: 'easeOut' }}
style={{
width: '100%',
maxWidth: '640px',
background: '#FFFFFF',
border: '1px solid #E2E8F0',
borderRadius: '20px',
padding: '32px',
boxShadow: '0 20px 40px rgba(15, 23, 42, 0.15)',
fontFamily: "'Inter', sans-serif",
boxSizing: 'border-box'
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px', borderBottom: '1px solid #F1F5F9', paddingBottom: '16px' }}>
<div>
<h3 style={{ fontSize: '1.25rem', fontWeight: 900, color: '#0F172A', margin: 0, letterSpacing: '-0.5px' }}>Register Supply Item</h3>
<span style={{ fontSize: '0.75rem', color: '#0284C7', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '1px' }}>Inventory Master registry</span>
</div>
<button
onClick={() => setShowAddModal(false)}
style={{ background: 'transparent', border: 'none', color: '#64748B', cursor: 'pointer', fontSize: '1.1rem' }}
onMouseEnter={e => e.currentTarget.style.color = '#0F172A'}
onMouseLeave={e => e.currentTarget.style.color = '#64748B'}
>
</button>
</div>
<form onSubmit={handleSubmitAddStock} style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
{submitError && (
<div style={{ background: 'rgba(239, 68, 68, 0.1)', border: '1px solid rgba(239, 68, 68, 0.2)', color: '#EF4444', padding: '12px 16px', borderRadius: '10px', fontSize: '0.8rem', display: 'flex', alignItems: 'center', gap: '8px' }}>
<AlertTriangle size={16} />
<span>{submitError}</span>
</div>
)}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
{/* Column 1 */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<label style={{ fontSize: '0.7rem', fontWeight: 700, color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Category</label>
<select
value={formCategory}
onChange={(e) => setFormCategory(e.target.value)}
style={{
background: '#F8FAFC',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#0F172A',
fontSize: '0.85rem',
outline: 'none',
cursor: 'pointer',
width: '100%',
boxSizing: 'border-box'
}}
>
{metadata.categories.map(cat => (
<option key={cat} value={cat} style={{ background: '#FFFFFF', color: '#0F172A' }}>{cat}</option>
))}
</select>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<label style={{ fontSize: '0.7rem', fontWeight: 700, color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Supply Name</label>
{metadata.commonNames[formCategory]?.length > 0 && (
<div style={{ display: 'flex', gap: '8px' }}>
<button
type="button"
onClick={() => setFormNameType('standard')}
style={{ background: formNameType === 'standard' ? 'rgba(2,132,199,0.15)' : 'transparent', border: 'none', color: formNameType === 'standard' ? '#0284C7' : '#64748B', fontSize: '0.65rem', fontWeight: 700, padding: '2px 6px', borderRadius: '4px', cursor: 'pointer' }}
>
STANDARD
</button>
<button
type="button"
onClick={() => setFormNameType('custom')}
style={{ background: formNameType === 'custom' ? 'rgba(2,132,199,0.15)' : 'transparent', border: 'none', color: formNameType === 'custom' ? '#0284C7' : '#64748B', fontSize: '0.65rem', fontWeight: 700, padding: '2px 6px', borderRadius: '4px', cursor: 'pointer' }}
>
CUSTOM
</button>
</div>
)}
</div>
{formNameType === 'standard' && metadata.commonNames[formCategory]?.length > 0 ? (
<select
value={formNameStandard}
onChange={(e) => setFormNameStandard(e.target.value)}
style={{
background: '#F8FAFC',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#0F172A',
fontSize: '0.85rem',
outline: 'none',
cursor: 'pointer',
width: '100%',
boxSizing: 'border-box'
}}
>
{metadata.commonNames[formCategory].map(name => (
<option key={name} value={name} style={{ background: '#FFFFFF', color: '#0F172A' }}>{name}</option>
))}
</select>
) : (
<input
type="text"
placeholder="e.g. Paracetamol 500mg"
value={formNameCustom}
onChange={(e) => setFormNameCustom(e.target.value)}
style={{
background: '#F8FAFC',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#0F172A',
fontSize: '0.85rem',
outline: 'none',
width: '100%',
boxSizing: 'border-box'
}}
/>
)}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<label style={{ fontSize: '0.7rem', fontWeight: 700, color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Unit of Measure</label>
<select
value={formUnit}
onChange={(e) => setFormUnit(e.target.value)}
style={{
background: '#F8FAFC',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#0F172A',
fontSize: '0.85rem',
outline: 'none',
cursor: 'pointer',
width: '100%',
boxSizing: 'border-box'
}}
>
{getSortedUnits().map(unit => (
<option key={unit} value={unit} style={{ background: '#FFFFFF', color: '#0F172A' }}>{unit}</option>
))}
</select>
</div>
</div>
{/* Column 2 */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<label style={{ fontSize: '0.7rem', fontWeight: 700, color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Min Threshold</label>
<input
type="number"
min="1"
value={formMinStock}
onChange={(e) => setFormMinStock(Number(e.target.value))}
style={{
background: '#F8FAFC',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#0F172A',
fontSize: '0.85rem',
outline: 'none',
width: '100%',
boxSizing: 'border-box'
}}
/>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<label style={{ fontSize: '0.7rem', fontWeight: 700, color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Max Stock</label>
<input
type="number"
min="1"
value={formMaxStock}
onChange={(e) => setFormMaxStock(Number(e.target.value))}
style={{
background: '#F8FAFC',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#0F172A',
fontSize: '0.85rem',
outline: 'none',
width: '100%',
boxSizing: 'border-box'
}}
/>
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<label style={{ fontSize: '0.7rem', fontWeight: 700, color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Supplier Details</label>
<input
type="text"
placeholder="e.g. HealthCare Distributors"
value={formSupplier}
onChange={(e) => setFormSupplier(e.target.value)}
style={{
background: '#F8FAFC',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#0F172A',
fontSize: '0.85rem',
outline: 'none',
width: '100%',
boxSizing: 'border-box'
}}
/>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<label style={{ fontSize: '0.7rem', fontWeight: 700, color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Lead Time (Days)</label>
<input
type="number"
min="1"
max="30"
value={formLeadTime}
onChange={(e) => setFormLeadTime(Number(e.target.value))}
style={{
background: '#F8FAFC',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#0F172A',
fontSize: '0.85rem',
outline: 'none',
width: '100%',
boxSizing: 'border-box'
}}
/>
</div>
</div>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '12px', borderTop: '1px solid #F1F5F9', paddingTop: '20px', marginTop: '10px' }}>
<button
type="button"
onClick={() => setShowAddModal(false)}
className="btn-ghost"
style={{ padding: '10px 20px', fontSize: '0.8rem', color: '#475569' }}
>
CANCEL
</button>
<button
type="submit"
className="btn-primary"
disabled={isSubmitting}
style={{ padding: '10px 24px', fontSize: '0.8rem', display: 'flex', alignItems: 'center', gap: '8px' }}
>
{isSubmitting ? (
<>
<Activity size={14} className="spin" /> REGISTERING...
</>
) : (
'REGISTER ITEM'
)}
</button>
</div>
</form>
</motion.div>
</div>
)}
{/* Edit Supply Item Modal */}
{showEditModal && (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(15, 23, 42, 0.4)',
backdropFilter: 'blur(8px)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1500,
animation: 'fadeIn 0.25s ease-out'
}}>
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 10 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 10 }}
transition={{ duration: 0.25, ease: 'easeOut' }}
style={{
width: '100%',
maxWidth: '680px',
background: '#FFFFFF',
border: '1px solid #E2E8F0',
borderRadius: '20px',
padding: '30px',
boxShadow: '0 20px 40px rgba(15, 23, 42, 0.15)',
position: 'relative',
fontFamily: "'Inter', sans-serif",
boxSizing: 'border-box'
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '24px', borderBottom: '1px solid #F1F5F9', paddingBottom: '16px' }}>
<div>
<h2 style={{ fontSize: '1.25rem', fontWeight: 800, margin: 0, color: '#0F172A', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Edit Supply Item</h2>
<div style={{ fontSize: '0.7rem', fontWeight: 700, color: '#0284C7', textTransform: 'uppercase', letterSpacing: '1px', marginTop: '4px' }}>UPDATE STOCK CATALOG</div>
</div>
<button
onClick={() => setShowEditModal(false)}
style={{ background: 'none', border: 'none', color: '#64748B', cursor: 'pointer', transition: 'color 0.2s' }}
onMouseEnter={(e) => e.currentTarget.style.color = '#0F172A'}
onMouseLeave={(e) => e.currentTarget.style.color = '#64748B'}
>
<X size={20} />
</button>
</div>
{editSubmitError && (
<div style={{ padding: '12px 16px', background: 'rgba(239, 68, 68, 0.1)', border: '1px solid rgba(239, 68, 68, 0.2)', borderRadius: '10px', color: '#EF4444', fontSize: '0.8rem', display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '20px' }}>
<AlertTriangle size={14} /> {editSubmitError}
</div>
)}
<form onSubmit={handleSubmitEditStock}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
{/* Column 1 */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<label style={{ fontSize: '0.7rem', fontWeight: 700, color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Category</label>
<select
value={formEditCategory}
onChange={(e) => setFormEditCategory(e.target.value)}
style={{
background: '#F8FAFC',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#0F172A',
fontSize: '0.85rem',
outline: 'none',
cursor: 'pointer',
width: '100%',
boxSizing: 'border-box'
}}
>
{metadata.categories.map(cat => (
<option key={cat} value={cat} style={{ background: '#FFFFFF', color: '#0F172A' }}>{cat}</option>
))}
</select>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<label style={{ fontSize: '0.7rem', fontWeight: 700, color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Supply Name</label>
<input
type="text"
placeholder="e.g. Paracetamol 500mg"
value={formEditName}
onChange={(e) => setFormEditName(e.target.value)}
style={{
background: '#F8FAFC',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#0F172A',
fontSize: '0.85rem',
outline: 'none',
width: '100%',
boxSizing: 'border-box'
}}
/>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<label style={{ fontSize: '0.7rem', fontWeight: 700, color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Unit of Measure</label>
<select
value={formEditUnit}
onChange={(e) => setFormEditUnit(e.target.value)}
style={{
background: '#F8FAFC',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#0F172A',
fontSize: '0.85rem',
outline: 'none',
cursor: 'pointer',
width: '100%',
boxSizing: 'border-box'
}}
>
{getSortedEditUnits().map(unit => (
<option key={unit} value={unit} style={{ background: '#FFFFFF', color: '#0F172A' }}>{unit}</option>
))}
</select>
</div>
</div>
{/* Column 2 */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<label style={{ fontSize: '0.7rem', fontWeight: 700, color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Min Threshold</label>
<input
type="number"
min="1"
value={formEditMinStock}
onChange={(e) => setFormEditMinStock(Number(e.target.value))}
style={{
background: '#F8FAFC',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#0F172A',
fontSize: '0.85rem',
outline: 'none',
width: '100%',
boxSizing: 'border-box'
}}
/>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<label style={{ fontSize: '0.7rem', fontWeight: 700, color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Max Stock</label>
<input
type="number"
min="1"
value={formEditMaxStock}
onChange={(e) => setFormEditMaxStock(Number(e.target.value))}
style={{
background: '#F8FAFC',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#0F172A',
fontSize: '0.85rem',
outline: 'none',
width: '100%',
boxSizing: 'border-box'
}}
/>
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<label style={{ fontSize: '0.7rem', fontWeight: 700, color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Supplier Details</label>
<input
type="text"
placeholder="e.g. HealthCare Distributors"
value={formEditSupplier}
onChange={(e) => setFormEditSupplier(e.target.value)}
style={{
background: '#F8FAFC',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#0F172A',
fontSize: '0.85rem',
outline: 'none',
width: '100%',
boxSizing: 'border-box'
}}
/>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<label style={{ fontSize: '0.7rem', fontWeight: 700, color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Lead Time (Days)</label>
<input
type="number"
min="1"
max="30"
value={formEditLeadTime}
onChange={(e) => setFormEditLeadTime(Number(e.target.value))}
style={{
background: '#F8FAFC',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#0F172A',
fontSize: '0.85rem',
outline: 'none',
width: '100%',
boxSizing: 'border-box'
}}
/>
</div>
</div>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '12px', borderTop: '1px solid #F1F5F9', paddingTop: '20px', marginTop: '10px' }}>
<button
type="button"
onClick={() => setShowEditModal(false)}
className="btn-ghost"
style={{ padding: '10px 20px', fontSize: '0.8rem', color: '#475569' }}
>
CANCEL
</button>
<button
type="submit"
className="btn-primary"
disabled={isEditingSubmitting}
style={{ padding: '10px 24px', fontSize: '0.8rem', display: 'flex', alignItems: 'center', gap: '8px' }}
>
{isEditingSubmitting ? (
<>
<Activity size={14} className="spin" /> UPDATING...
</>
) : (
'UPDATE ITEM'
)}
</button>
</div>
</form>
</motion.div>
</div>
)}
{/* Single Restock Modal */}
{showSingleRestockModal && selectedItem && (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(15, 23, 42, 0.4)',
backdropFilter: 'blur(8px)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1500,
animation: 'fadeIn 0.25s ease-out'
}}>
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 10 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 10 }}
transition={{ duration: 0.25, ease: 'easeOut' }}
style={{
width: '100%',
maxWidth: '520px',
background: '#FFFFFF',
border: '1px solid #E2E8F0',
borderRadius: '20px',
padding: '30px',
boxShadow: '0 20px 40px rgba(15, 23, 42, 0.15)',
fontFamily: "'Inter', sans-serif",
boxSizing: 'border-box'
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px', borderBottom: '1px solid #F1F5F9', paddingBottom: '16px' }}>
<div>
<h3 style={{ fontSize: '1.25rem', fontWeight: 900, color: '#0F172A', margin: 0, letterSpacing: '-0.5px' }}>Restock Supply Item</h3>
<span style={{ fontSize: '0.75rem', color: '#0284C7', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '1px' }}>Single intake replenishing</span>
</div>
<button
onClick={() => setShowSingleRestockModal(false)}
style={{ background: 'transparent', border: 'none', color: '#64748B', cursor: 'pointer', fontSize: '1.1rem' }}
onMouseEnter={e => e.currentTarget.style.color = '#0F172A'}
onMouseLeave={e => e.currentTarget.style.color = '#64748B'}
>
</button>
</div>
<form onSubmit={handleSubmitSingleRestock} style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
{singleRestockError && (
<div style={{ background: 'rgba(239, 68, 68, 0.1)', border: '1px solid rgba(239, 68, 68, 0.2)', color: '#EF4444', padding: '12px 16px', borderRadius: '10px', fontSize: '0.8rem', display: 'flex', alignItems: 'center', gap: '8px' }}>
<AlertTriangle size={16} />
<span>{singleRestockError}</span>
</div>
)}
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<label style={{ fontSize: '0.7rem', fontWeight: 700, color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Item Name</label>
<input
type="text"
value={selectedItem.name}
disabled
style={{
background: '#F1F5F9',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#475569',
fontSize: '0.85rem',
width: '100%',
boxSizing: 'border-box',
cursor: 'not-allowed',
fontWeight: 600
}}
/>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<label style={{ fontSize: '0.7rem', fontWeight: 700, color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Current Stock</label>
<div style={{
background: '#F8FAFC',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#0F172A',
fontSize: '0.85rem',
fontWeight: 700
}}>
{selectedItem.totalStock} {selectedItem.unit}
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<label style={{ fontSize: '0.7rem', fontWeight: 700, color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Restock Quantity ({selectedItem.unit})</label>
<input
type="number"
min="1"
value={singleRestockQuantity}
onChange={(e) => setSingleRestockQuantity(Number(e.target.value))}
style={{
background: '#F8FAFC',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#0F172A',
fontSize: '0.85rem',
outline: 'none',
width: '100%',
boxSizing: 'border-box',
fontWeight: 700
}}
/>
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<label style={{ fontSize: '0.7rem', fontWeight: 700, color: '#64748B', textTransform: 'uppercase', letterSpacing: '0.5px' }}>Reason for Restock</label>
<select
value={singleRestockReason}
onChange={(e) => setSingleRestockReason(e.target.value)}
style={{
background: '#F8FAFC',
border: '1px solid #E2E8F0',
borderRadius: '10px',
padding: '10px 14px',
color: '#0F172A',
fontSize: '0.85rem',
outline: 'none',
cursor: 'pointer',
width: '100%',
boxSizing: 'border-box'
}}
>
<option value="Shipment from HealthCare Distributors">Shipment from HealthCare Distributors</option>
<option value="Shipment from MediStore">Shipment from MediStore</option>
<option value="Routine Stock Replenishment">Routine Stock Replenishment</option>
<option value="Emergency Intake">Emergency Intake</option>
</select>
</div>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '12px', borderTop: '1px solid #F1F5F9', paddingTop: '20px', marginTop: '10px' }}>
<button
type="button"
onClick={() => setShowSingleRestockModal(false)}
className="btn-ghost"
style={{ padding: '10px 20px', fontSize: '0.8rem', color: '#475569', background: 'transparent', border: 'none', cursor: 'pointer', fontWeight: 600 }}
>
CANCEL
</button>
<button
type="submit"
className="btn-primary"
disabled={singleRestockIsSubmitting}
style={{ padding: '10px 24px', fontSize: '0.8rem', display: 'flex', alignItems: 'center', gap: '8px', background: '#0284C7', color: '#FFFFFF', border: 'none', borderRadius: '8px', fontWeight: 700, cursor: 'pointer' }}
>
{singleRestockIsSubmitting ? (
<>
<Activity size={14} className="spin" /> RESTOCKING...
</>
) : (
'SUBMIT RESTOCK'
)}
</button>
</div>
</form>
</motion.div>
</div>
)}
</div>
);
};