Commit inicial
This commit is contained in:
@@ -1,14 +1,21 @@
|
||||
// components/DataInputRedesigned.tsx
|
||||
// Interfaz de entrada de datos simplificada
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import {
|
||||
AlertCircle, FileText, Database,
|
||||
UploadCloud, File, Loader2, Info, X
|
||||
UploadCloud, File, Loader2, Info, X,
|
||||
HardDrive, Trash2, RefreshCw, Server
|
||||
} from 'lucide-react';
|
||||
import clsx from 'clsx';
|
||||
import toast from 'react-hot-toast';
|
||||
import { checkServerCache, clearServerCache, ServerCacheMetadata } from '../utils/serverCache';
|
||||
import { useAuth } from '../utils/AuthContext';
|
||||
|
||||
interface CacheInfo extends ServerCacheMetadata {
|
||||
// Using server cache metadata structure
|
||||
}
|
||||
|
||||
interface DataInputRedesignedProps {
|
||||
onAnalyze: (config: {
|
||||
@@ -22,6 +29,7 @@ interface DataInputRedesignedProps {
|
||||
file?: File;
|
||||
sheetUrl?: string;
|
||||
useSynthetic?: boolean;
|
||||
useCache?: boolean;
|
||||
}) => void;
|
||||
isAnalyzing: boolean;
|
||||
}
|
||||
@@ -30,6 +38,8 @@ const DataInputRedesigned: React.FC<DataInputRedesignedProps> = ({
|
||||
onAnalyze,
|
||||
isAnalyzing
|
||||
}) => {
|
||||
const { authHeader } = useAuth();
|
||||
|
||||
// Estados para datos manuales - valores vacíos por defecto
|
||||
const [costPerHour, setCostPerHour] = useState<string>('');
|
||||
const [avgCsat, setAvgCsat] = useState<string>('');
|
||||
@@ -43,6 +53,77 @@ const DataInputRedesigned: React.FC<DataInputRedesignedProps> = ({
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
||||
// Estado para caché del servidor
|
||||
const [cacheInfo, setCacheInfo] = useState<CacheInfo | null>(null);
|
||||
const [checkingCache, setCheckingCache] = useState(true);
|
||||
|
||||
// Verificar caché del servidor al cargar
|
||||
useEffect(() => {
|
||||
const checkCache = async () => {
|
||||
console.log('[DataInput] Checking server cache, authHeader:', authHeader ? 'present' : 'null');
|
||||
if (!authHeader) {
|
||||
console.log('[DataInput] No authHeader, skipping cache check');
|
||||
setCheckingCache(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setCheckingCache(true);
|
||||
console.log('[DataInput] Calling checkServerCache...');
|
||||
const { exists, metadata } = await checkServerCache(authHeader);
|
||||
console.log('[DataInput] Cache check result:', { exists, metadata });
|
||||
if (exists && metadata) {
|
||||
setCacheInfo(metadata);
|
||||
console.log('[DataInput] Cache info set:', metadata);
|
||||
// Auto-rellenar coste si hay en caché
|
||||
if (metadata.costPerHour > 0 && !costPerHour) {
|
||||
setCostPerHour(metadata.costPerHour.toString());
|
||||
}
|
||||
} else {
|
||||
console.log('[DataInput] No cache found on server');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[DataInput] Error checking server cache:', error);
|
||||
} finally {
|
||||
setCheckingCache(false);
|
||||
}
|
||||
};
|
||||
checkCache();
|
||||
}, [authHeader]);
|
||||
|
||||
const handleClearCache = async () => {
|
||||
if (!authHeader) return;
|
||||
|
||||
try {
|
||||
const success = await clearServerCache(authHeader);
|
||||
if (success) {
|
||||
setCacheInfo(null);
|
||||
toast.success('Caché del servidor limpiada', { icon: '🗑️' });
|
||||
} else {
|
||||
toast.error('Error limpiando caché del servidor');
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Error limpiando caché');
|
||||
}
|
||||
};
|
||||
|
||||
const handleUseCache = () => {
|
||||
if (!cacheInfo) return;
|
||||
|
||||
const segmentMapping = (highValueQueues || mediumValueQueues || lowValueQueues) ? {
|
||||
high_value_queues: (highValueQueues || '').split(',').map(q => q.trim()).filter(q => q),
|
||||
medium_value_queues: (mediumValueQueues || '').split(',').map(q => q.trim()).filter(q => q),
|
||||
low_value_queues: (lowValueQueues || '').split(',').map(q => q.trim()).filter(q => q)
|
||||
} : undefined;
|
||||
|
||||
onAnalyze({
|
||||
costPerHour: parseFloat(costPerHour) || cacheInfo.costPerHour,
|
||||
avgCsat: parseFloat(avgCsat) || 0,
|
||||
segmentMapping,
|
||||
useCache: true
|
||||
});
|
||||
};
|
||||
|
||||
const handleFileChange = (selectedFile: File | null) => {
|
||||
if (selectedFile) {
|
||||
const allowedTypes = [
|
||||
@@ -111,7 +192,7 @@ const DataInputRedesigned: React.FC<DataInputRedesignedProps> = ({
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="bg-white rounded-lg shadow-sm p-6 border border-slate-200"
|
||||
className="bg-white rounded-lg shadow-sm p-4 sm:p-6 border border-slate-200"
|
||||
>
|
||||
<div className="mb-6">
|
||||
<h2 className="text-lg font-semibold text-slate-800 mb-1 flex items-center gap-2">
|
||||
@@ -123,7 +204,7 @@ const DataInputRedesigned: React.FC<DataInputRedesignedProps> = ({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6">
|
||||
{/* Coste por Hora */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2 flex items-center gap-2">
|
||||
@@ -176,7 +257,7 @@ const DataInputRedesigned: React.FC<DataInputRedesignedProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Segmentación por Cola/Skill */}
|
||||
<div className="col-span-2">
|
||||
<div className="col-span-1 md:col-span-2">
|
||||
<div className="mb-3">
|
||||
<h4 className="font-medium text-slate-700 mb-1 flex items-center gap-2">
|
||||
Segmentación de Clientes por Cola/Skill
|
||||
@@ -187,7 +268,7 @@ const DataInputRedesigned: React.FC<DataInputRedesignedProps> = ({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 sm:gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1">
|
||||
Alto Valor
|
||||
@@ -236,20 +317,102 @@ const DataInputRedesigned: React.FC<DataInputRedesignedProps> = ({
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Sección 2: Subir Archivo */}
|
||||
{/* Sección 2: Datos en Caché del Servidor (si hay) */}
|
||||
{cacheInfo && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.15 }}
|
||||
className="bg-emerald-50 rounded-lg shadow-sm p-4 sm:p-6 border-2 border-emerald-300"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-emerald-800 flex items-center gap-2">
|
||||
<Server size={20} className="text-emerald-600" />
|
||||
Datos en Caché
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleClearCache}
|
||||
className="p-2 text-emerald-600 hover:text-red-600 hover:bg-red-50 rounded-lg transition"
|
||||
title="Limpiar caché"
|
||||
>
|
||||
<Trash2 size={18} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2 sm:gap-4 mb-4">
|
||||
<div className="bg-white rounded-lg p-3 border border-emerald-200">
|
||||
<p className="text-xs text-emerald-600 font-medium">Archivo</p>
|
||||
<p className="text-sm font-semibold text-slate-800 truncate" title={cacheInfo.fileName}>
|
||||
{cacheInfo.fileName}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-3 border border-emerald-200">
|
||||
<p className="text-xs text-emerald-600 font-medium">Registros</p>
|
||||
<p className="text-sm font-semibold text-slate-800">
|
||||
{cacheInfo.recordCount.toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-3 border border-emerald-200">
|
||||
<p className="text-xs text-emerald-600 font-medium">Tamaño Original</p>
|
||||
<p className="text-sm font-semibold text-slate-800">
|
||||
{(cacheInfo.fileSize / (1024 * 1024)).toFixed(1)} MB
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-3 border border-emerald-200">
|
||||
<p className="text-xs text-emerald-600 font-medium">Guardado</p>
|
||||
<p className="text-sm font-semibold text-slate-800">
|
||||
{new Date(cacheInfo.cachedAt).toLocaleDateString('es-ES', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleUseCache}
|
||||
disabled={isAnalyzing || !costPerHour || parseFloat(costPerHour) <= 0}
|
||||
className={clsx(
|
||||
'w-full py-3 rounded-lg font-semibold flex items-center justify-center gap-2 transition-all',
|
||||
(!isAnalyzing && costPerHour && parseFloat(costPerHour) > 0)
|
||||
? 'bg-emerald-600 text-white hover:bg-emerald-700'
|
||||
: 'bg-slate-200 text-slate-400 cursor-not-allowed'
|
||||
)}
|
||||
>
|
||||
{isAnalyzing ? (
|
||||
<>
|
||||
<Loader2 size={20} className="animate-spin" />
|
||||
Analizando...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<RefreshCw size={20} />
|
||||
Usar Datos en Caché
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{(!costPerHour || parseFloat(costPerHour) <= 0) && (
|
||||
<p className="text-xs text-amber-600 mt-2 text-center">
|
||||
Introduce el coste por hora arriba para continuar
|
||||
</p>
|
||||
)}
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Sección 3: Subir Archivo */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="bg-white rounded-lg shadow-sm p-6 border border-slate-200"
|
||||
transition={{ delay: cacheInfo ? 0.25 : 0.2 }}
|
||||
className="bg-white rounded-lg shadow-sm p-4 sm:p-6 border border-slate-200"
|
||||
>
|
||||
<div className="mb-4">
|
||||
<h2 className="text-lg font-semibold text-slate-800 mb-1 flex items-center gap-2">
|
||||
<UploadCloud size={20} className="text-[#6D84E3]" />
|
||||
Datos CSV
|
||||
{cacheInfo ? 'Subir Nuevo Archivo' : 'Datos CSV'}
|
||||
</h2>
|
||||
<p className="text-slate-500 text-sm">
|
||||
Sube el archivo exportado desde tu sistema ACD/CTI
|
||||
{cacheInfo ? 'O sube un archivo diferente para analizar' : 'Sube el archivo exportado desde tu sistema ACD/CTI'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user