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 } from 'lucide-react'; import * as XLSX from 'xlsx'; import { AssetCreateRequest, ASSET_TYPES, ASSET_CONDITIONS, BulkImportResponse } from '@/types/asset-management'; import ImportResults from './ImportResults'; interface ImportResult { success: boolean; data?: AssetCreateRequest[]; errors?: string[]; warnings?: string[]; totalRows?: number; validRows?: number; } interface ExcelImportProps { onImport: (assets: AssetCreateRequest[]) => Promise; isImporting?: boolean; } const ExcelImport: 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 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 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 Type': 'Wireless Mouse', 'Brand/Model': 'Logitech MX Master 3', 'Serial Number': 'LG-MX3-003', 'Asset Condition': 'Excellent', 'Purchase Date': '2024-03-01', 'Warranty Expiry': '2026-03-01', 'Purchase Cost': '99.99', 'Current Value': '80.00', 'Location': 'Office Floor 1', 'Notes': 'Ergonomic wireless mouse' }, { 'Asset Type': 'Laptop Stand', 'Brand/Model': 'Rain Design mStand', 'Serial Number': 'RD-MS-004', 'Asset Condition': 'Good', 'Purchase Date': '2024-03-15', 'Warranty Expiry': '2026-03-15', 'Purchase Cost': '59.99', 'Current Value': '50.00', 'Location': 'Office Floor 1', 'Notes': 'Aluminum laptop stand for ergonomics' } ]; const ws = XLSX.utils.json_to_sheet(templateData); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, 'Assets Template'); // Set column widths const colWidths = [ { 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, 'assets_import_template.xlsx'); }; const validateAssetData = (data: any[]): ImportResult => { const errors: string[] = []; const warnings: string[] = []; const validAssets: AssetCreateRequest[] = []; data.forEach((row, index) => { const rowNum = index + 2; // +2 because Excel is 1-indexed and we skip header const asset: Partial = {}; // Required fields validation if (!row['Asset Type']) { errors.push(`Row ${rowNum}: Asset Type is required`); return; } if (!ASSET_TYPES.includes(row['Asset Type'])) { errors.push(`Row ${rowNum}: Invalid Asset Type "${row['Asset Type']}". Must be one of: ${ASSET_TYPES.join(', ')}`); return; } asset.assetType = String(row['Asset Type']).trim(); if (!row['Brand/Model']) { errors.push(`Row ${rowNum}: Brand/Model is required`); return; } asset.brandModel = String(row['Brand/Model']).trim(); if (!row['Serial Number']) { errors.push(`Row ${rowNum}: Serial Number is required`); return; } // Convert serialNumber to string (handles numbers, strings, etc.) asset.serialNumber = String(row['Serial Number']).trim(); if (!row['Asset Condition']) { errors.push(`Row ${rowNum}: Asset Condition is required`); return; } if (!ASSET_CONDITIONS.includes(row['Asset Condition'])) { errors.push(`Row ${rowNum}: Invalid Asset Condition "${row['Asset Condition']}". Must be one of: ${ASSET_CONDITIONS.join(', ')}`); return; } asset.assetCondition = String(row['Asset Condition']).trim(); // Optional fields with validation if (row['Purchase Date']) { const purchaseDate = new Date(row['Purchase Date']); if (isNaN(purchaseDate.getTime())) { warnings.push(`Row ${rowNum}: Invalid Purchase Date format`); } else { asset.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 { asset.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 { asset.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 { asset.currentValue = value; } } if (row['Location']) { asset.location = String(row['Location']).trim(); } if (row['Notes']) { asset.notes = String(row['Notes']).trim(); } validAssets.push(asset as AssetCreateRequest); }); return { success: errors.length === 0, data: validAssets, errors, warnings, totalRows: data.length, validRows: validAssets.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 = validateAssetData(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.

{/* 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 ExcelImport;