Files
bakery-ia/frontend/src/components/dashboard/SetupWizardBlocker.tsx
2025-11-27 15:52:40 +01:00

238 lines
11 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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