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

1885 lines
84 KiB
TypeScript
Raw Normal View History

2026-05-13 12:54:38 +05:30
import React, { useState, useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import {
Plus,
Search,
2026-05-13 12:54:38 +05:30
Package,
AlertTriangle,
2026-05-13 12:54:38 +05:30
Database,
Truck,
Activity,
Archive,
2026-05-13 12:54:38 +05:30
BarChart3,
Clock,
X,
Edit2,
PlusCircle,
Eye,
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight
} from 'lucide-react';
2026-05-13 12:54:38 +05:30
import { motion } from 'framer-motion';
import { Card } from '../../components/Common';
2026-05-13 12:54:38 +05:30
import { fleetApi } from '../../api/fleet';
interface InventoryItem {
id: string;
name: string;
2026-05-13 12:54:38 +05:30
category: string;
totalStock: number;
minStock: number;
2026-05-13 12:54:38 +05:30
maxStock: number;
unit: string;
expiringSoon: number;
2026-05-13 12:54:38 +05:30
supplierDetails: string;
leadTimeDays: number;
vehicles: { vehicleId: string; stock: number }[];
}
2026-05-13 12:54:38 +05:30
interface InventoryMetadata {
categories: string[];
units: string[];
commonNames: Record<string, string[]>;
}
export const FleetInventory: React.FC = () => {
2026-05-13 12:54:38 +05:30
const [inventory, setInventory] = useState<InventoryItem[]>([]);
const [selectedItem, setSelectedItem] = useState<InventoryItem | null>(null);
2026-05-13 12:54:38 +05:30
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>
2026-05-13 12:54:38 +05:30
<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>
2026-05-13 12:54:38 +05:30
<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>
2026-05-13 12:54:38 +05:30
<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>
2026-05-13 12:54:38 +05:30
<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' }}>
2026-05-13 12:54:38 +05:30
<div style={{ color: '#A855F7' }}><Package size={20} /></div>
</div>
2026-05-13 12:54:38 +05:30
<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>
2026-05-13 12:54:38 +05:30
<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>
2026-05-13 12:54:38 +05:30
)}
</div>
<button
onClick={() => {
setSubmitError(null);
setShowAddModal(true);
}}
className="btn-primary"
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
>
<Plus size={16} /> ADD MASTER
</button>
2026-05-13 12:54:38 +05:30
<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)' }}>
2026-05-13 12:54:38 +05:30
<th style={{ padding: '12px' }}>Item Name</th>
<th style={{ padding: '12px' }}>Category</th>
2026-05-13 12:54:38 +05:30
<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>
2026-05-13 12:54:38 +05:30
{pageData.map((item, idx) => (
<tr
key={item.id}
2026-05-13 12:54:38 +05:30
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' }}>
2026-05-13 12:54:38 +05:30
<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' }}>
2026-05-13 12:54:38 +05:30
<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' }}>
2026-05-13 12:54:38 +05:30
<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>
2026-05-13 12:54:38 +05:30
{/* 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>
))}
2026-05-13 12:54:38 +05:30
<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'}
>
2026-05-13 12:54:38 +05:30
</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>
2026-05-13 12:54:38 +05:30
)}
</div>
2026-05-13 12:54:38 +05:30
{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>
))}
2026-05-13 12:54:38 +05:30
</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>
2026-05-13 12:54:38 +05:30
<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>
2026-05-13 12:54:38 +05:30
<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>
2026-05-13 12:54:38 +05:30
)}
{/* 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>
);
};