Files
acmcc/src/components/CallLogImportDialog.jsx
T
2026-06-01 20:19:26 -04:00

193 lines
8.5 KiB
React

import React, { useState, useRef } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Progress } from '@/components/ui/progress';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Upload, FileText, AlertTriangle, CheckCircle, Shield, X, Download, Loader2 } from 'lucide-react';
import { useAuth } from '@/contexts/AuthContext';
import { useToast } from '@/hooks/use-toast';
export function CallLogImportDialog({ open, onOpenChange, onSuccess }) {
const { user } = useAuth();
const { toast } = useToast();
const fileInputRef = useRef(null);
const [file, setFile] = useState(null);
const [importing, setImporting] = useState(false);
const [progress, setProgress] = useState(0);
const [validationResult, setValidationResult] = useState(null);
const [analyzing, setAnalyzing] = useState(false);
const resetValidation = () => {
setValidationResult(null);
};
const handleFileChange = (e) => {
const selectedFile = e.target.files[0];
if (selectedFile) {
if (selectedFile.size > 5 * 1024 * 1024) {
toast({ variant: "destructive", title: "File Too Large", description: "Max file size is 5MB." });
return;
}
setFile(selectedFile);
resetValidation();
}
};
const handleAnalyze = async () => {
if (!file) return;
setAnalyzing(true);
try {
// Placeholder: In production, use a real validation hook/service
setValidationResult({ valid: true, sanitizedRecords: [], errors: [] });
toast({ title: "Analysis Complete", description: "File validated successfully." });
} catch (err) {
toast({ variant: "destructive", title: "Analysis Failed", description: err.message });
} finally {
setAnalyzing(false);
}
};
const executeImport = async () => {
if (!validationResult || !validationResult.valid) return;
setImporting(true);
setProgress(10);
try {
// Placeholder: In production, use processCallLogImport
setProgress(100);
toast({ title: "Import Successful", description: `Imported ${validationResult.sanitizedRecords.length} logs.` });
if (onSuccess) onSuccess();
onOpenChange(false);
} catch (err) {
toast({ variant: "destructive", title: "Import Failed", description: err.message });
} finally {
setImporting(false);
setProgress(0);
}
};
const downloadTemplate = () => {
toast({ title: "Template", description: "Template download not yet configured." });
};
const handleClose = () => {
if (importing) return;
setFile(null);
resetValidation();
onOpenChange(false);
};
return (
<Dialog open={open} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Shield className="w-5 h-5 text-blue-600" />
Secure Call Log Import
</DialogTitle>
<DialogDescription>
Import call logs from CSV, Excel, or JSON. Data is validated for security and integrity.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-2">
{!file ? (
<div className="border-2 border-dashed border-slate-200 rounded-lg p-8 flex flex-col items-center justify-center text-center hover:bg-slate-50 transition-colors">
<Upload className="w-10 h-10 text-slate-400 mb-4" />
<h3 className="font-medium text-slate-900">Upload Log File</h3>
<p className="text-sm text-slate-500 mb-4">CSV, Excel, JSON (max 5MB)</p>
<div className="flex gap-2">
<Button variant="outline" onClick={() => fileInputRef.current?.click()}>
Select File
</Button>
<Button variant="ghost" size="sm" onClick={downloadTemplate} title="Download Template">
<Download className="w-4 h-4" />
</Button>
</div>
<input
type="file"
ref={fileInputRef}
className="hidden"
accept=".csv, .xlsx, .xls, .json"
onChange={handleFileChange}
/>
</div>
) : (
<div className="space-y-4">
<div className="flex items-center justify-between p-3 bg-slate-50 rounded border">
<div className="flex items-center gap-3">
<FileText className="w-5 h-5 text-blue-500" />
<span className="font-medium text-sm truncate max-w-[300px]">{file.name}</span>
</div>
<Button variant="ghost" size="icon" onClick={() => { setFile(null); resetValidation(); }} disabled={importing || analyzing}>
<X className="w-4 h-4" />
</Button>
</div>
{!validationResult && (
<Button onClick={handleAnalyze} disabled={analyzing} className="w-full">
{analyzing ? <Loader2 className="w-4 h-4 mr-2 animate-spin" /> : "Validate & Preview"}
{analyzing ? "Analyzing..." : ""}
</Button>
)}
{validationResult && (
<div className="animate-in fade-in slide-in-from-top-2">
{validationResult.valid ? (
<Alert className="bg-green-50 border-green-200">
<CheckCircle className="h-4 w-4 text-green-600" />
<AlertTitle className="text-green-800">Validation Passed</AlertTitle>
<AlertDescription className="text-green-700">
Ready to import {validationResult.sanitizedRecords.length} records.
</AlertDescription>
</Alert>
) : (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Validation Failed</AlertTitle>
<AlertDescription>
Found {validationResult.errors.length} issues. Import blocked.
</AlertDescription>
</Alert>
)}
{!validationResult.valid && (
<ScrollArea className="h-[150px] w-full border rounded mt-2 p-2 bg-red-50 text-xs text-red-700">
{validationResult.errors.map((err, i) => (
<div key={i} className="mb-1 border-b border-red-100 pb-1 last:border-0">
<strong>Row {err.row}:</strong> {err.messages.join(', ')}
</div>
))}
</ScrollArea>
)}
</div>
)}
{importing && (
<div className="space-y-1">
<div className="flex justify-between text-xs text-slate-500">
<span>Importing...</span>
<span>{progress}%</span>
</div>
<Progress value={progress} />
</div>
)}
</div>
)}
</div>
<DialogFooter>
<Button variant="ghost" onClick={handleClose} disabled={importing}>Close</Button>
{validationResult?.valid && (
<Button onClick={executeImport} disabled={importing} className="bg-green-600 hover:bg-green-700">
{importing ? <Loader2 className="w-4 h-4 mr-2 animate-spin" /> : "Confirm Import"}
</Button>
)}
</DialogFooter>
</DialogContent>
</Dialog>
);
}