'use client' import { useState, useCallback, useRef } from 'react' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table' import { Upload, FileJson, FileSpreadsheet, Download, AlertCircle, Check, } from 'lucide-react' import { cn } from '@/lib/utils' // ── Types ───────────────────────────────────────────────────── export interface CsvFieldDef { key: string label: string required?: boolean } interface MockDataImportDialogProps { open: boolean onOpenChange: (open: boolean) => void csvFields: CsvFieldDef[] defaultMappings?: Record parseCsvRows: (rows: Record[]) => T validateReport: (data: unknown) => { valid: boolean; error?: string } templateCsvContent: string templateFileName: string onImport: (report: T, meta: { source: 'csv' | 'json'; fileName: string; rowCount: number }) => Promise } function parseCsv(text: string): { headers: string[]; rows: string[][] } { const lines = text.split(/\r?\n/).filter(line => line.trim()) if (lines.length === 0) return { headers: [], rows: [] } const separator = lines[0].includes(';') ? ';' : ',' const headers = lines[0].split(separator).map(h => h.trim().replace(/^"(.*)"$/, '$1')) const rows = lines.slice(1).map(line => line.split(separator).map(cell => cell.trim().replace(/^"(.*)"$/, '$1')) ) return { headers, rows } } // ── Component ───────────────────────────────────────────────── type Step = 'upload' | 'map-csv' | 'preview-json' | 'importing' export default function MockDataImportDialog({ open, onOpenChange, csvFields, defaultMappings, parseCsvRows, validateReport, templateCsvContent, templateFileName, onImport, }: MockDataImportDialogProps) { const [step, setStep] = useState('upload') const [isDragging, setIsDragging] = useState(false) const [error, setError] = useState(null) const fileInputRef = useRef(null) // CSV state const [csvHeaders, setCsvHeaders] = useState([]) const [csvRows, setCsvRows] = useState([]) const [mappings, setMappings] = useState>({}) const [fileName, setFileName] = useState('') // JSON state const [jsonReport, setJsonReport] = useState(null) const [jsonSummary, setJsonSummary] = useState('') const reset = useCallback(() => { setStep('upload') setError(null) setCsvHeaders([]) setCsvRows([]) setMappings({}) setFileName('') setJsonReport(null) setJsonSummary('') setIsDragging(false) }, []) const handleOpenChange = useCallback((open: boolean) => { if (!open) reset() onOpenChange(open) }, [onOpenChange, reset]) const processFile = useCallback((file: File) => { setError(null) setFileName(file.name) const reader = new FileReader() reader.onload = (ev) => { const text = ev.target?.result as string if (file.name.endsWith('.json')) { // JSON path try { const parsed = JSON.parse(text) const validation = validateReport(parsed) if (!validation.valid) { setError(validation.error || 'Ogiltig JSON-struktur') return } setJsonReport(parsed as T) // Build summary const keys = Object.keys(parsed) const lines = Array.isArray(parsed.lines) ? parsed.lines.length : Array.isArray(parsed.receivables) ? parsed.receivables.length : Array.isArray(parsed.boxes) ? parsed.boxes.length : null setJsonSummary( `${keys.length} fält` + (lines !== null ? `, ${lines} rader` : '') ) setStep('preview-json') } catch { setError('Kunde inte tolka JSON-filen. Kontrollera formatet.') } } else { // CSV path const parsed = parseCsv(text) if (parsed.headers.length === 0 || parsed.rows.length === 0) { setError('Ingen data hittades i CSV-filen.') return } setCsvHeaders(parsed.headers) setCsvRows(parsed.rows) // Auto-map columns const autoMappings: Record = {} for (const field of csvFields) { const defaultCol = defaultMappings?.[field.key] if (defaultCol && parsed.headers.includes(defaultCol)) { autoMappings[field.key] = defaultCol } else { const match = parsed.headers.find( h => h.toLowerCase() === field.key.toLowerCase() || h.toLowerCase() === field.label.toLowerCase() ) if (match) autoMappings[field.key] = match } } setMappings(autoMappings) setStep('map-csv') } } reader.readAsText(file) }, [csvFields, defaultMappings, validateReport]) const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault() setIsDragging(false) const file = e.dataTransfer.files[0] if (file) processFile(file) }, [processFile]) const handleFileInput = useCallback((e: React.ChangeEvent) => { const file = e.target.files?.[0] if (file) processFile(file) }, [processFile]) const handleCsvImport = useCallback(async () => { setStep('importing') setError(null) try { const mappedRows = csvRows.map(row => { const obj: Record = {} for (const [fieldKey, csvCol] of Object.entries(mappings)) { const colIdx = csvHeaders.indexOf(csvCol) if (colIdx >= 0 && row[colIdx]) { obj[fieldKey] = row[colIdx] } } return obj }).filter(row => Object.keys(row).length > 0) const report = parseCsvRows(mappedRows) await onImport(report, { source: 'csv', fileName, rowCount: mappedRows.length }) handleOpenChange(false) } catch (e) { setError(e instanceof Error ? e.message : 'Import misslyckades') setStep('map-csv') } }, [csvRows, csvHeaders, mappings, parseCsvRows, onImport, fileName, handleOpenChange]) const handleJsonImport = useCallback(async () => { if (!jsonReport) return setStep('importing') setError(null) try { await onImport(jsonReport, { source: 'json', fileName, rowCount: 0 }) handleOpenChange(false) } catch (e) { setError(e instanceof Error ? e.message : 'Import misslyckades') setStep('preview-json') } }, [jsonReport, onImport, fileName, handleOpenChange]) const downloadTemplate = useCallback(() => { const blob = new Blob([templateCsvContent], { type: 'text/csv;charset=utf-8;' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = templateFileName document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) }, [templateCsvContent, templateFileName]) const requiredFieldsMapped = csvFields .filter(f => f.required) .every(f => mappings[f.key]) return ( {step === 'upload' && 'Importera testdata'} {step === 'map-csv' && 'Kolumnmappning'} {step === 'preview-json' && 'Förhandsgranska JSON'} {step === 'importing' && 'Importerar...'} {/* ── Error ─────────────────────────────────────── */} {error && (
{error}
)} {/* ── Step: Upload ──────────────────────────────── */} {step === 'upload' && (
{ e.preventDefault(); setIsDragging(true) }} onDragLeave={() => setIsDragging(false)} onDrop={handleDrop} >

Dra och släpp en fil här

CSV (.csv) eller JSON (.json)

)} {/* ── Step: Map CSV ─────────────────────────────── */} {step === 'map-csv' && (
{fileName} — {csvRows.length} rader
{csvFields.map(field => (
))}
{/* Preview first 5 rows */} {csvRows.length > 0 && (
{csvHeaders.map(h => ( {h} ))} {csvRows.slice(0, 5).map((row, i) => ( {row.map((cell, j) => ( {cell} ))} ))}
)}
)} {/* ── Step: Preview JSON ────────────────────────── */} {step === 'preview-json' && (
{fileName}
Giltig JSON — {jsonSummary}
)} {/* ── Step: Importing ───────────────────────────── */} {step === 'importing' && (

Importerar testdata...

)}
) }