Files
bakery-ia/frontend/src/components/dashboard/SetupWizardBlocker.tsx

238 lines
11 KiB
TypeScript
Raw Normal View History

// ================================================================
// 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>
);
}