mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
Add ACMCC app source, Supabase backend, and project config
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user