Improve the sales import

This commit is contained in:
Urtzi Alfaro
2025-10-15 21:09:42 +02:00
parent 8f9e9a7edc
commit dbb48d8e2c
21 changed files with 992 additions and 409 deletions

View File

@@ -20,6 +20,7 @@ interface TrainingProgress {
message: string;
currentStep?: string;
estimatedTimeRemaining?: number;
estimatedCompletionTime?: string;
}
export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
@@ -59,7 +60,8 @@ export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
progress: data.data?.progress || 0,
message: data.data?.message || 'Entrenando modelo...',
currentStep: data.data?.current_step,
estimatedTimeRemaining: data.data?.estimated_time_remaining_seconds || data.data?.estimated_time_remaining
estimatedTimeRemaining: data.data?.estimated_time_remaining_seconds || data.data?.estimated_time_remaining,
estimatedCompletionTime: data.data?.estimated_completion_time
});
}, []);
@@ -221,7 +223,7 @@ export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
const formatTime = (seconds?: number) => {
if (!seconds) return '';
if (seconds < 60) {
return `${Math.round(seconds)}s`;
} else if (seconds < 3600) {
@@ -231,6 +233,33 @@ export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
}
};
const formatEstimatedCompletionTime = (isoString?: string) => {
if (!isoString) return '';
try {
const completionDate = new Date(isoString);
const now = new Date();
// If completion is today, show time only
if (completionDate.toDateString() === now.toDateString()) {
return completionDate.toLocaleTimeString('es-ES', {
hour: '2-digit',
minute: '2-digit'
});
}
// If completion is another day, show date and time
return completionDate.toLocaleString('es-ES', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
} catch (error) {
return '';
}
};
return (
<div className="space-y-6">
<div className="text-center">
@@ -293,24 +322,61 @@ export const MLTrainingStep: React.FC<MLTrainingStepProps> = ({
</p>
{trainingProgress.stage !== 'completed' && (
<div className="space-y-2">
<div className="w-full bg-[var(--bg-tertiary)] rounded-full h-2">
<div
className="bg-[var(--color-primary)] h-2 rounded-full transition-all duration-300"
style={{ width: `${trainingProgress.progress}%` }}
/>
<div className="space-y-3">
{/* Enhanced Progress Bar */}
<div className="relative">
<div className="w-full bg-[var(--bg-tertiary)] rounded-full h-3 overflow-hidden">
<div
className="bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-primary)]/80 h-3 rounded-full transition-all duration-500 ease-out relative"
style={{ width: `${trainingProgress.progress}%` }}
>
{/* Animated shimmer effect */}
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent animate-shimmer"></div>
</div>
</div>
{/* Progress percentage badge */}
<div className="absolute -top-1 left-1/2 transform -translate-x-1/2 -translate-y-full mb-1">
<span className="text-xs font-semibold text-[var(--color-primary)] bg-[var(--bg-primary)] px-2 py-1 rounded-full shadow-sm border border-[var(--color-primary)]/20">
{trainingProgress.progress}%
</span>
</div>
</div>
<div className="flex justify-between text-xs text-[var(--text-tertiary)]">
<span>{trainingProgress.currentStep || t('onboarding:steps.ml_training.progress.data_preparation', 'Procesando...')}</span>
<div className="flex items-center gap-2">
{/* Training Information */}
<div className="flex flex-col gap-2 text-xs text-[var(--text-tertiary)]">
{/* Current Step */}
<div className="flex justify-between items-center">
<span className="font-medium">{trainingProgress.currentStep || t('onboarding:steps.ml_training.progress.data_preparation', 'Procesando...')}</span>
{jobId && (
<span className={`text-xs ${isConnected ? 'text-green-500' : 'text-red-500'}`}>
{isConnected ? '🟢 Conectado' : '🔴 Desconectado'}
<span className={`text-xs px-2 py-0.5 rounded-full ${isConnected ? 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400' : 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400'}`}>
{isConnected ? '● En vivo' : '● Reconectando...'}
</span>
)}
</div>
{/* Time Information */}
<div className="flex flex-wrap gap-x-4 gap-y-1 text-xs">
{trainingProgress.estimatedTimeRemaining && (
<span>{t('onboarding:steps.ml_training.estimated_time_remaining', 'Tiempo restante estimado: {{time}}', { time: formatTime(trainingProgress.estimatedTimeRemaining) })}</span>
<div className="flex items-center gap-1">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>
{t('onboarding:steps.ml_training.estimated_time_remaining', 'Tiempo restante: {{time}}', {
time: formatTime(trainingProgress.estimatedTimeRemaining)
})}
</span>
</div>
)}
{trainingProgress.estimatedCompletionTime && (
<div className="flex items-center gap-1">
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<span>
Finalizará: {formatEstimatedCompletionTime(trainingProgress.estimatedCompletionTime)}
</span>
</div>
)}
</div>
</div>