238 lines
11 KiB
TypeScript
238 lines
11 KiB
TypeScript
|
|
// ================================================================
|
|||
|
|
// frontend/src/components/dashboard/SetupWizardBlocker.tsx
|
|||
|
|
// ================================================================
|
|||
|
|
/**
|
|||
|
|
* Setup Wizard Blocker - Critical Path Onboarding
|
|||
|
|
*
|
|||
|
|
* JTBD: "I cannot operate my bakery without basic configuration"
|
|||
|
|
*
|
|||
|
|
* This component blocks the entire dashboard when critical setup is incomplete.
|
|||
|
|
* Shows a full-page wizard to guide users through essential configuration.
|
|||
|
|
*
|
|||
|
|
* Triggers when:
|
|||
|
|
* - 0-2 critical sections complete (<50% progress)
|
|||
|
|
* - Missing: Ingredients (<3) OR Suppliers (<1) OR Recipes (<1)
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import React, { useMemo } from 'react';
|
|||
|
|
import { useNavigate } from 'react-router-dom';
|
|||
|
|
import { useTranslation } from 'react-i18next';
|
|||
|
|
import { AlertCircle, Package, Users, BookOpen, ChevronRight, CheckCircle2, Circle } from 'lucide-react';
|
|||
|
|
|
|||
|
|
interface SetupSection {
|
|||
|
|
id: string;
|
|||
|
|
title: string;
|
|||
|
|
description: string;
|
|||
|
|
icon: React.ElementType;
|
|||
|
|
path: string;
|
|||
|
|
isComplete: boolean;
|
|||
|
|
count: number;
|
|||
|
|
minimum: number;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface SetupWizardBlockerProps {
|
|||
|
|
criticalSections: SetupSection[];
|
|||
|
|
onComplete?: () => void;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function SetupWizardBlocker({ criticalSections = [], onComplete }: SetupWizardBlockerProps) {
|
|||
|
|
const { t } = useTranslation(['dashboard', 'common']);
|
|||
|
|
const navigate = useNavigate();
|
|||
|
|
|
|||
|
|
// Calculate progress
|
|||
|
|
const { completedCount, totalCount, progressPercentage, nextSection } = useMemo(() => {
|
|||
|
|
// Guard against undefined or invalid criticalSections
|
|||
|
|
if (!criticalSections || !Array.isArray(criticalSections) || criticalSections.length === 0) {
|
|||
|
|
return {
|
|||
|
|
completedCount: 0,
|
|||
|
|
totalCount: 0,
|
|||
|
|
progressPercentage: 0,
|
|||
|
|
nextSection: undefined,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const completed = criticalSections.filter(s => s.isComplete).length;
|
|||
|
|
const total = criticalSections.length;
|
|||
|
|
const percentage = Math.round((completed / total) * 100);
|
|||
|
|
const next = criticalSections.find(s => !s.isComplete);
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
completedCount: completed,
|
|||
|
|
totalCount: total,
|
|||
|
|
progressPercentage: percentage,
|
|||
|
|
nextSection: next,
|
|||
|
|
};
|
|||
|
|
}, [criticalSections]);
|
|||
|
|
|
|||
|
|
const handleSectionClick = (path: string) => {
|
|||
|
|
navigate(path);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="min-h-screen flex items-center justify-center p-4" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
|||
|
|
<div className="w-full max-w-3xl">
|
|||
|
|
{/* Warning Header */}
|
|||
|
|
<div className="text-center mb-8">
|
|||
|
|
<div className="inline-flex items-center justify-center w-20 h-20 rounded-full mb-4" style={{ backgroundColor: 'var(--color-warning-100)' }}>
|
|||
|
|
<AlertCircle className="w-10 h-10" style={{ color: 'var(--color-warning-600)' }} />
|
|||
|
|
</div>
|
|||
|
|
<h1 className="text-3xl md:text-4xl font-bold mb-3" style={{ color: 'var(--text-primary)' }}>
|
|||
|
|
⚠️ {t('dashboard:setup_blocker.title', 'Configuración Requerida')}
|
|||
|
|
</h1>
|
|||
|
|
<p className="text-lg" style={{ color: 'var(--text-secondary)' }}>
|
|||
|
|
{t('dashboard:setup_blocker.subtitle', 'Necesitas completar la configuración básica antes de usar el panel de control')}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Progress Card */}
|
|||
|
|
<div className="bg-[var(--bg-primary)] border-2 border-[var(--border-primary)] rounded-xl shadow-xl p-8">
|
|||
|
|
{/* Progress Bar */}
|
|||
|
|
<div className="mb-8">
|
|||
|
|
<div className="flex items-center justify-between text-sm mb-3">
|
|||
|
|
<span className="font-semibold" style={{ color: 'var(--text-primary)' }}>
|
|||
|
|
{t('dashboard:setup_blocker.progress', 'Progreso de Configuración')}
|
|||
|
|
</span>
|
|||
|
|
<span className="font-bold" style={{ color: 'var(--color-primary)' }}>
|
|||
|
|
{completedCount}/{totalCount} ({progressPercentage}%)
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
<div className="w-full h-3 bg-[var(--bg-tertiary)] rounded-full overflow-hidden">
|
|||
|
|
<div
|
|||
|
|
className="h-full transition-all duration-500 ease-out"
|
|||
|
|
style={{
|
|||
|
|
width: `${progressPercentage}%`,
|
|||
|
|
background: 'linear-gradient(90deg, var(--color-primary), var(--color-success))',
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Critical Sections List */}
|
|||
|
|
<div className="space-y-4 mb-8">
|
|||
|
|
<h3 className="text-lg font-bold mb-4" style={{ color: 'var(--text-primary)' }}>
|
|||
|
|
{t('dashboard:setup_blocker.required_steps', 'Pasos Requeridos')}
|
|||
|
|
</h3>
|
|||
|
|
|
|||
|
|
{criticalSections.map((section, index) => {
|
|||
|
|
const Icon = section.icon || (() => <div>⚙️</div>);
|
|||
|
|
const isNext = section === nextSection;
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<button
|
|||
|
|
key={section.id}
|
|||
|
|
onClick={() => handleSectionClick(section.path)}
|
|||
|
|
className={`w-full p-5 rounded-lg border-2 transition-all duration-200 text-left group ${
|
|||
|
|
section.isComplete
|
|||
|
|
? 'border-[var(--color-success)]/30 bg-[var(--color-success)]/5'
|
|||
|
|
: isNext
|
|||
|
|
? 'border-[var(--color-primary)] bg-[var(--color-primary)]/5 hover:bg-[var(--color-primary)]/10 shadow-md'
|
|||
|
|
: 'border-[var(--border-secondary)] bg-[var(--bg-secondary)] hover:border-[var(--color-primary)] hover:bg-[var(--bg-primary)]'
|
|||
|
|
}`}
|
|||
|
|
>
|
|||
|
|
<div className="flex items-start gap-4">
|
|||
|
|
{/* Step Number / Status */}
|
|||
|
|
<div className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center font-bold text-sm ${
|
|||
|
|
section.isComplete
|
|||
|
|
? 'bg-[var(--color-success)] text-white'
|
|||
|
|
: isNext
|
|||
|
|
? 'bg-[var(--color-primary)] text-white'
|
|||
|
|
: 'bg-[var(--bg-tertiary)] text-[var(--text-secondary)]'
|
|||
|
|
}`}>
|
|||
|
|
{section.isComplete ? (
|
|||
|
|
<CheckCircle2 className="w-5 h-5" />
|
|||
|
|
) : (
|
|||
|
|
<span>{index + 1}</span>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Section Icon */}
|
|||
|
|
<div className={`w-12 h-12 rounded-full flex items-center justify-center ${
|
|||
|
|
section.isComplete
|
|||
|
|
? 'bg-[var(--color-success)]/20 text-[var(--color-success)]'
|
|||
|
|
: isNext
|
|||
|
|
? 'bg-[var(--color-primary)]/20 text-[var(--color-primary)]'
|
|||
|
|
: 'bg-[var(--bg-tertiary)] text-[var(--text-secondary)]'
|
|||
|
|
}`}>
|
|||
|
|
<Icon className="w-6 h-6" />
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Content */}
|
|||
|
|
<div className="flex-1 min-w-0">
|
|||
|
|
<div className="flex items-center gap-2 mb-1">
|
|||
|
|
<h4 className="font-bold text-lg" style={{ color: 'var(--text-primary)' }}>
|
|||
|
|
{section.title}
|
|||
|
|
</h4>
|
|||
|
|
{isNext && !section.isComplete && (
|
|||
|
|
<span className="px-2 py-0.5 bg-[var(--color-primary)] text-white rounded-full text-xs font-semibold">
|
|||
|
|
{t('dashboard:setup_blocker.next', 'Siguiente')}
|
|||
|
|
</span>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<p className="text-sm mb-2" style={{ color: 'var(--text-secondary)' }}>
|
|||
|
|
{section.description}
|
|||
|
|
</p>
|
|||
|
|
|
|||
|
|
<div className="flex items-center gap-2 text-sm">
|
|||
|
|
{section.isComplete ? (
|
|||
|
|
<span className="font-semibold" style={{ color: 'var(--color-success)' }}>
|
|||
|
|
✅ {section.count} {t('dashboard:setup_blocker.added', 'agregado(s)')}
|
|||
|
|
</span>
|
|||
|
|
) : (
|
|||
|
|
<span className="font-semibold" style={{ color: 'var(--color-warning-700)' }}>
|
|||
|
|
⚠️ {t('dashboard:setup_blocker.minimum_required', 'Mínimo requerido')}: {section.minimum}
|
|||
|
|
</span>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Chevron */}
|
|||
|
|
<ChevronRight className={`w-6 h-6 flex-shrink-0 transition-transform group-hover:translate-x-1 ${
|
|||
|
|
isNext ? 'text-[var(--color-primary)]' : 'text-[var(--text-tertiary)]'
|
|||
|
|
}`} />
|
|||
|
|
</div>
|
|||
|
|
</button>
|
|||
|
|
);
|
|||
|
|
})}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
{/* Next Step CTA */}
|
|||
|
|
{nextSection && (
|
|||
|
|
<div className="p-5 rounded-xl border-2 border-[var(--color-primary)]/30" style={{ backgroundColor: 'var(--color-primary)]/5' }}>
|
|||
|
|
<div className="flex items-start gap-4">
|
|||
|
|
<AlertCircle className="w-6 h-6 flex-shrink-0" style={{ color: 'var(--color-primary)' }} />
|
|||
|
|
<div className="flex-1">
|
|||
|
|
<h4 className="font-bold mb-2" style={{ color: 'var(--text-primary)' }}>
|
|||
|
|
👉 {t('dashboard:setup_blocker.start_with', 'Empieza por')}: {nextSection.title}
|
|||
|
|
</h4>
|
|||
|
|
<p className="text-sm mb-4" style={{ color: 'var(--text-secondary)' }}>
|
|||
|
|
{nextSection.description}
|
|||
|
|
</p>
|
|||
|
|
<button
|
|||
|
|
onClick={() => handleSectionClick(nextSection.path)}
|
|||
|
|
className="px-6 py-3 rounded-lg font-semibold transition-all duration-200 hover:scale-105 active:scale-95 shadow-lg hover:shadow-xl inline-flex items-center gap-2"
|
|||
|
|
style={{
|
|||
|
|
background: 'linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%)',
|
|||
|
|
color: 'white',
|
|||
|
|
}}
|
|||
|
|
>
|
|||
|
|
{t('dashboard:setup_blocker.configure_now', 'Configurar Ahora')} {nextSection.title}
|
|||
|
|
<ChevronRight className="w-5 h-5" />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{/* Help Text */}
|
|||
|
|
<div className="mt-6 pt-6 border-t" style={{ borderColor: 'var(--border-secondary)' }}>
|
|||
|
|
<p className="text-sm text-center" style={{ color: 'var(--text-tertiary)' }}>
|
|||
|
|
💡 {t('dashboard:setup_blocker.help_text', 'Una vez completes estos pasos, podrás acceder al panel de control completo y comenzar a usar todas las funciones de IA')}
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
);
|
|||
|
|
}
|