import React, { useState, useRef } from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Upload, Download, FileSpreadsheet, CheckCircle, XCircle, AlertTriangle, Loader2, Settings } from 'lucide-react'; import * as XLSX from 'xlsx'; import { SystemDetailsCreateRequest } from '@/types/asset-management'; import ImportResults from './ImportResults'; interface SystemDetailsImportData extends Omit { assetID?: number; // Asset creation fields if asset doesn't exist assetType?: string; brandModel?: string; serialNumber?: string; assetCondition?: string; purchaseDate?: string; warrantyExpiry?: string; purchaseCost?: number; currentValue?: number; location?: string; notes?: string; } interface ImportResult { success: boolean; data?: SystemDetailsImportData[]; errors?: string[]; warnings?: string[]; totalRows?: number; validRows?: number; } interface SystemDetailsBulkImportResponse { success: boolean; message: string; results: { successful: number; failed: number; details: Array<{ row: number; success: boolean; message: string; data?: any; }>; }; } interface SystemDetailsExcelImportProps { onImport: (systemDetails: SystemDetailsImportData[]) => Promise; isImporting?: boolean; } const SystemDetailsExcelImport: React.FC = ({ onImport, isImporting = false }) => { const [file, setFile] = useState(null); const [importResult, setImportResult] = useState(null); const [apiResult, setApiResult] = useState(null); const [isProcessing, setIsProcessing] = useState(false); const fileInputRef = useRef(null); // Template data for Excel generation const generateTemplate = () => { const templateData = [ { 'Asset ID': 1, 'System Model': 'Dell Latitude 5430', 'Processor': 'Intel Core i7-1265U', 'Installed RAM': '16GB DDR4', 'Graphics': 'Intel Iris Xe Graphics', 'Screen Resolution': '1920x1080', 'Disk Model': 'Samsung SSD 980 PRO', 'Disk Size': '512GB', 'Manufacturer': 'Dell', // Asset creation fields (used if asset ID doesn't exist) 'Asset Type': 'Laptop', 'Brand/Model': 'Dell Latitude 5430', 'Serial Number': 'DL5430-001', 'Asset Condition': 'Excellent', 'Purchase Date': '2024-01-15', 'Warranty Expiry': '2027-01-15', 'Purchase Cost': '1200.00', 'Current Value': '1000.00', 'Location': 'Office Floor 1', 'Notes': 'Primary development laptop' }, { 'Asset ID': 999, 'System Model': 'Samsung 27" 4K Monitor', 'Processor': 'N/A', 'Installed RAM': 'N/A', 'Graphics': 'Built-in Display Controller', 'Screen Resolution': '3840x2160', 'Disk Model': 'N/A', 'Disk Size': 'N/A', 'Manufacturer': 'Samsung', // Asset creation fields (will be used since Asset ID 999 likely doesn't exist) 'Asset Type': 'Extended Screen', 'Brand/Model': 'Samsung 27" 4K', 'Serial Number': 'SM27-4K-002', 'Asset Condition': 'Good', 'Purchase Date': '2024-02-01', 'Warranty Expiry': '2027-02-01', 'Purchase Cost': '400.00', 'Current Value': '350.00', 'Location': 'Office Floor 2', 'Notes': 'External monitor for workstation' }, { 'Asset ID': '', 'System Model': 'MacBook Pro 16"', 'Processor': 'Apple M2 Pro', 'Installed RAM': '32GB', 'Graphics': 'Apple M2 Pro GPU', 'Screen Resolution': '3456x2234', 'Disk Model': 'Apple SSD', 'Disk Size': '1TB', 'Manufacturer': 'Apple', // Asset creation fields (required since no Asset ID provided) 'Asset Type': 'Laptop', 'Brand/Model': 'MacBook Pro 16"', 'Serial Number': 'MBP16-003', 'Asset Condition': 'Excellent', 'Purchase Date': '2024-03-01', 'Warranty Expiry': '2027-03-01', 'Purchase Cost': '2500.00', 'Current Value': '2200.00', 'Location': 'Office Floor 3', 'Notes': 'Design team laptop' } ]; const ws = XLSX.utils.json_to_sheet(templateData); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, 'System Details Template'); // Set column widths const colWidths = [ { wch: 10 }, // Asset ID { wch: 25 }, // System Model { wch: 20 }, // Processor { wch: 15 }, // Installed RAM { wch: 25 }, // Graphics { wch: 15 }, // Screen Resolution { wch: 20 }, // Disk Model { wch: 12 }, // Disk Size { wch: 15 }, // Manufacturer { wch: 15 }, // Asset Type { wch: 25 }, // Brand/Model { wch: 20 }, // Serial Number { wch: 15 }, // Asset Condition { wch: 15 }, // Purchase Date { wch: 15 }, // Warranty Expiry { wch: 12 }, // Purchase Cost { wch: 12 }, // Current Value { wch: 20 }, // Location { wch: 30 } // Notes ]; ws['!cols'] = colWidths; XLSX.writeFile(wb, 'system_details_import_template.xlsx'); }; const validateSystemDetailsData = (data: any[]): ImportResult => { const errors: string[] = []; const warnings: string[] = []; const validSystemDetails: SystemDetailsImportData[] = []; data.forEach((row, index) => { const rowNum = index + 2; // +2 because Excel is 1-indexed and we skip header const systemDetail: SystemDetailsImportData = {}; // Asset ID validation (optional - if not provided, we'll create a new asset) if (row['Asset ID']) { const assetId = parseInt(row['Asset ID']); if (isNaN(assetId) || assetId <= 0) { warnings.push(`Row ${rowNum}: Invalid Asset ID "${row['Asset ID']}". Will attempt to create new asset.`); } else { systemDetail.assetID = assetId; } } // Required system details fields if (!row['System Model']) { errors.push(`Row ${rowNum}: System Model is required`); return; } systemDetail.systemModel = String(row['System Model']).trim(); if (!row['Manufacturer']) { errors.push(`Row ${rowNum}: Manufacturer is required`); return; } systemDetail.manufacturer = String(row['Manufacturer']).trim(); // Optional system details fields systemDetail.processor = row['Processor'] ? String(row['Processor']).trim() : ''; systemDetail.installedRAM = row['Installed RAM'] ? String(row['Installed RAM']).trim() : ''; systemDetail.graphics = row['Graphics'] ? String(row['Graphics']).trim() : ''; systemDetail.screenResolution = row['Screen Resolution'] ? String(row['Screen Resolution']).trim() : ''; systemDetail.diskModel = row['Disk Model'] ? String(row['Disk Model']).trim() : ''; systemDetail.diskSize = row['Disk Size'] ? String(row['Disk Size']).trim() : ''; // Asset creation fields (always include for validation, will be used if asset doesn't exist) if (row['Asset Type']) { systemDetail.assetType = String(row['Asset Type']).trim(); } if (row['Brand/Model']) { systemDetail.brandModel = String(row['Brand/Model']).trim(); } if (row['Serial Number']) { systemDetail.serialNumber = String(row['Serial Number']).trim(); } if (row['Asset Condition']) { systemDetail.assetCondition = String(row['Asset Condition']).trim(); } // If no asset ID provided, require asset creation fields if (!systemDetail.assetID) { if (!systemDetail.assetType) { errors.push(`Row ${rowNum}: Asset Type is required when Asset ID is not provided`); return; } if (!systemDetail.brandModel) { errors.push(`Row ${rowNum}: Brand/Model is required when Asset ID is not provided`); return; } if (!systemDetail.serialNumber) { errors.push(`Row ${rowNum}: Serial Number is required when Asset ID is not provided`); return; } if (!systemDetail.assetCondition) { errors.push(`Row ${rowNum}: Asset Condition is required when Asset ID is not provided`); return; } } // Optional asset fields (always include for potential asset creation) if (row['Purchase Date']) { const purchaseDate = new Date(row['Purchase Date']); if (isNaN(purchaseDate.getTime())) { warnings.push(`Row ${rowNum}: Invalid Purchase Date format`); } else { systemDetail.purchaseDate = purchaseDate.toISOString().split('T')[0]; } } if (row['Warranty Expiry']) { const warrantyExpiry = new Date(row['Warranty Expiry']); if (isNaN(warrantyExpiry.getTime())) { warnings.push(`Row ${rowNum}: Invalid Warranty Expiry format`); } else { systemDetail.warrantyExpiry = warrantyExpiry.toISOString().split('T')[0]; } } if (row['Purchase Cost']) { const cost = parseFloat(row['Purchase Cost']); if (isNaN(cost) || cost < 0) { warnings.push(`Row ${rowNum}: Invalid Purchase Cost`); } else { systemDetail.purchaseCost = cost; } } if (row['Current Value']) { const value = parseFloat(row['Current Value']); if (isNaN(value) || value < 0) { warnings.push(`Row ${rowNum}: Invalid Current Value`); } else { systemDetail.currentValue = value; } } if (row['Location']) { systemDetail.location = String(row['Location']).trim(); } if (row['Notes']) { systemDetail.notes = String(row['Notes']).trim(); } validSystemDetails.push(systemDetail); }); return { success: errors.length === 0, data: validSystemDetails, errors, warnings, totalRows: data.length, validRows: validSystemDetails.length }; }; const handleFileSelect = (event: React.ChangeEvent) => { const selectedFile = event.target.files?.[0]; if (selectedFile) { setFile(selectedFile); setImportResult(null); } }; const processFile = async () => { if (!file) return; setIsProcessing(true); try { const data = await file.arrayBuffer(); const workbook = XLSX.read(data); const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; const jsonData = XLSX.utils.sheet_to_json(worksheet); if (jsonData.length === 0) { setImportResult({ success: false, errors: ['The Excel file appears to be empty or has no data rows.'] }); return; } const result = validateSystemDetailsData(jsonData); setImportResult(result); } catch (error) { setImportResult({ success: false, errors: ['Failed to process the Excel file. Please ensure it\'s a valid Excel file.'] }); } finally { setIsProcessing(false); } }; const handleImport = async () => { if (!importResult?.data) return; try { const result = await onImport(importResult.data); setApiResult(result); // Only clear the form if the import was completely successful if (result.success && result.results.failed === 0) { setFile(null); setImportResult(null); if (fileInputRef.current) { fileInputRef.current.value = ''; } } } catch (error) { console.error('Import failed:', error); } }; const resetImport = () => { setFile(null); setImportResult(null); setApiResult(null); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; return (
{/* Template Download */} Excel Template

Download the Excel template to ensure your data is formatted correctly for import. If you provide an Asset ID, the system will check if it exists - if not found, a new asset will be created using the asset fields. If no Asset ID is provided, a new asset will be created automatically.

{/* File Upload */} Upload Excel File
{file && (
{file.name} {(file.size / 1024).toFixed(1)} KB
)}
{/* Import Results */} {importResult && ( {importResult.success ? ( ) : ( )} Import Results {/* Summary */}
{importResult.totalRows || 0}
Total Rows
{importResult.validRows || 0}
Valid Rows
{/* Errors */} {importResult.errors && importResult.errors.length > 0 && (
Errors found:
    {importResult.errors.map((error, index) => (
  • {error}
  • ))}
)} {/* Warnings */} {importResult.warnings && importResult.warnings.length > 0 && (
Warnings:
    {importResult.warnings.map((warning, index) => (
  • {warning}
  • ))}
)} {/* Import Button */} {importResult.success && importResult.data && importResult.data.length > 0 && (
)}
)} {/* API Import Results */} {apiResult && ( )}
); }; export default SystemDetailsExcelImport;