import React, { useState, useMemo, useEffect } from 'react'; import { format } from 'date-fns'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Package, User, Calendar, CheckCircle, Loader2, AlertCircle, Search, Plus, Minus, } from 'lucide-react'; import { AssetRequest, AssetRequestAssignmentRequest, AssignedAssetDetail, } from '@/types/asset-request'; import { Asset, AssetAssignment } from '@/types/asset-management'; import { validateAssignmentRequest, calculateInventoryImpact, logInventoryChange, createInventoryAuditLog, } from '@/utils/inventoryManagement'; interface AssetRequestAssignmentProps { approvedRequests: AssetRequest[]; availableAssets: Asset[]; allAssets: Asset[]; activeAssignments: AssetAssignment[]; isLoading?: boolean; onAssign: (id: number, data: AssetRequestAssignmentRequest) => Promise; currentUserId: number; } interface AssetAllocation { assetType: string; requestedQuantity: number; allocatedAssets: Asset[]; } const AssetRequestAssignment: React.FC = ({ approvedRequests, availableAssets, allAssets, activeAssignments, isLoading = false, onAssign, currentUserId, }) => { const [selectedRequest, setSelectedRequest] = useState(null); const [showAssignmentDialog, setShowAssignmentDialog] = useState(false); const [assetAllocations, setAssetAllocations] = useState([]); const [searchTerm, setSearchTerm] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const [filterAssetType, setFilterAssetType] = useState('all'); // Get unique asset types from available assets const assetTypes = useMemo(() => { const types = new Set(availableAssets.map((asset) => asset.assetType)); return Array.from(types).sort(); }, [availableAssets]); // Filter available assets based on search and type const filteredAvailableAssets = useMemo(() => { let filtered = [...availableAssets]; if (filterAssetType !== 'all') { filtered = filtered.filter((asset) => asset.assetType === filterAssetType); } if (searchTerm) { const term = searchTerm.toLowerCase(); filtered = filtered.filter( (asset) => asset.assetCode.toLowerCase().includes(term) || asset.assetType.toLowerCase().includes(term) || asset.brandModel.toLowerCase().includes(term) || asset.serialNumber.toLowerCase().includes(term) ); } return filtered; }, [availableAssets, searchTerm, filterAssetType]); // Initialize allocations when a request is selected useEffect(() => { if (selectedRequest) { const allocations: AssetAllocation[] = selectedRequest.requestedAssets.map((item) => ({ assetType: item.assetType, requestedQuantity: item.quantity, allocatedAssets: [], })); setAssetAllocations(allocations); } }, [selectedRequest]); const handleSelectRequest = (request: AssetRequest) => { setSelectedRequest(request); setShowAssignmentDialog(true); setSearchTerm(''); setFilterAssetType('all'); }; const handleAddAsset = (asset: Asset, assetType: string) => { setAssetAllocations((prev) => prev.map((allocation) => { if (allocation.assetType === assetType) { // Check if asset is already allocated if (allocation.allocatedAssets.some((a) => a.assetID === asset.assetID)) { return allocation; } // Check if we've reached the requested quantity if (allocation.allocatedAssets.length >= allocation.requestedQuantity) { return allocation; } return { ...allocation, allocatedAssets: [...allocation.allocatedAssets, asset], }; } return allocation; }) ); }; const handleRemoveAsset = (assetId: number, assetType: string) => { setAssetAllocations((prev) => prev.map((allocation) => { if (allocation.assetType === assetType) { return { ...allocation, allocatedAssets: allocation.allocatedAssets.filter( (a) => a.assetID !== assetId ), }; } return allocation; }) ); }; const handleAssignSubmit = async () => { if (!selectedRequest) return; // Validate that all requested assets have been allocated const allAllocated = assetAllocations.every( (allocation) => allocation.allocatedAssets.length === allocation.requestedQuantity ); if (!allAllocated) { return; } // Build assigned assets list const assignedAssets: AssignedAssetDetail[] = []; assetAllocations.forEach((allocation) => { allocation.allocatedAssets.forEach((asset) => { assignedAssets.push({ assetID: asset.assetID, assetCode: asset.assetCode, assetType: asset.assetType, quantity: 1, // Each asset is assigned individually }); }); }); // Validate assignment request const validation = validateAssignmentRequest(assignedAssets, allAssets, activeAssignments); if (!validation.valid) { console.error('Assignment validation failed:', validation.errors); alert(`Cannot assign assets:\n${validation.errors.join('\n')}`); return; } // Show warnings if any if (validation.warnings.length > 0) { const proceed = window.confirm( `Warning:\n${validation.warnings.join('\n')}\n\nDo you want to proceed with the assignment?` ); if (!proceed) { return; } } // Calculate inventory impact const impact = calculateInventoryImpact(assignedAssets, allAssets, activeAssignments); console.log('Inventory impact:', impact); // Log each assignment for audit trail assignedAssets.forEach((asset) => { const auditLog = createInventoryAuditLog({ action: 'assignment', assetType: asset.assetType, assetId: asset.assetID, assetCode: asset.assetCode, employeeId: selectedRequest.employeeID, employeeName: selectedRequest.employeeName, performedBy: currentUserId, previousState: 'available', newState: 'assigned', requestId: selectedRequest.requestID, remarks: `Assigned via asset request #${selectedRequest.requestID}` }); logInventoryChange(auditLog); }); setIsSubmitting(true); try { await onAssign(selectedRequest.requestID, { assignedAssets, assignedBy: currentUserId, }); console.log('Assignment completed successfully'); console.log('Updated inventory status:', impact); setShowAssignmentDialog(false); setSelectedRequest(null); setAssetAllocations([]); } catch (error) { console.error('Error assigning assets:', error); alert(`Failed to assign assets: ${error instanceof Error ? error.message : 'Unknown error'}`); } finally { setIsSubmitting(false); } }; const isAssetAllocated = (assetId: number): boolean => { return assetAllocations.some((allocation) => allocation.allocatedAssets.some((a) => a.assetID === assetId) ); }; const canAddMoreAssets = (assetType: string): boolean => { const allocation = assetAllocations.find((a) => a.assetType === assetType); if (!allocation) return false; return allocation.allocatedAssets.length < allocation.requestedQuantity; }; const getAllocationProgress = (assetType: string): { current: number; total: number } => { const allocation = assetAllocations.find((a) => a.assetType === assetType); if (!allocation) return { current: 0, total: 0 }; return { current: allocation.allocatedAssets.length, total: allocation.requestedQuantity, }; }; const isAllocationComplete = (): boolean => { return assetAllocations.every( (allocation) => allocation.allocatedAssets.length === allocation.requestedQuantity ); }; if (isLoading) { return ( ); } return (
Approved Requests - Assignment Queue {approvedRequests.length === 0 ? (

No approved requests waiting for asset assignment

) : (
Request ID Employee Requested Assets Approved Date Actions {approvedRequests.map((request) => ( #{request.requestID}
{request.employeeName}
{request.requestedAssets.map((asset, idx) => (
{asset.assetType} (x{asset.quantity})
))}
{request.reviewedDate && (
{format(new Date(request.reviewedDate), 'MMM dd, yyyy')}
)}
))}
)}
{/* Assignment Dialog */} Assign Assets to Request #{selectedRequest?.requestID} Select specific assets to assign to {selectedRequest?.employeeName} {selectedRequest && (
{/* Request Summary */} Request Summary
Employee:{' '} {selectedRequest.employeeName}
Submitted:{' '} {format(new Date(selectedRequest.submittedDate), 'MMM dd, yyyy')}
Justification:{' '} {selectedRequest.justification}
{/* Allocation Progress */}

Allocation Progress

{assetAllocations.map((allocation, idx) => { const progress = getAllocationProgress(allocation.assetType); const isComplete = progress.current === progress.total; return (
{allocation.assetType}
{progress.current}/{progress.total} Assigned
{/* Allocated Assets */} {allocation.allocatedAssets.length > 0 && (
{allocation.allocatedAssets.map((asset) => (
{asset.assetCode} {asset.brandModel}
))}
)}
); })}
{/* Asset Selection */}

Available Assets

{/* Filters */}
setSearchTerm(e.target.value)} className="pl-10" />
{/* Assets List */}
{filteredAvailableAssets.length === 0 ? (

No available assets match your search

) : ( Asset Code Type Brand/Model Condition Action {filteredAvailableAssets.map((asset) => { const allocated = isAssetAllocated(asset.assetID); const canAdd = canAddMoreAssets(asset.assetType); return ( {asset.assetCode} {asset.assetType} {asset.brandModel} {asset.assetCondition} {allocated ? ( Allocated ) : ( )} ); })}
)}
)}
); }; export default AssetRequestAssignment;