mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 01:40:01 +00:00
183fe0a93c
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
193 lines
8.5 KiB
React
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>
|
|
);
|
|
} |