Fix new Frontend 15
This commit is contained in:
@@ -2,42 +2,61 @@ import React, { useState, useEffect } from 'react';
|
||||
import { TrendingUp, TrendingDown, Package, AlertTriangle, Cloud, Users } from 'lucide-react';
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, BarChart, Bar } from 'recharts';
|
||||
|
||||
interface DashboardPageProps {
|
||||
user: any;
|
||||
}
|
||||
import { useDashboard } from '../../hooks/useDashboard';
|
||||
|
||||
interface WeatherData {
|
||||
temperature: number;
|
||||
description: string;
|
||||
precipitation: number;
|
||||
}
|
||||
|
||||
interface ForecastData {
|
||||
product: string;
|
||||
predicted: number;
|
||||
confidence: 'high' | 'medium' | 'low';
|
||||
change: number;
|
||||
}
|
||||
// Helper functions
|
||||
const getConfidenceColor = (confidence: 'high' | 'medium' | 'low') => {
|
||||
switch (confidence) {
|
||||
case 'high':
|
||||
return 'bg-success-100 text-success-800';
|
||||
case 'medium':
|
||||
return 'bg-warning-100 text-warning-800';
|
||||
case 'low':
|
||||
return 'bg-danger-100 text-danger-800';
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-800';
|
||||
}
|
||||
};
|
||||
|
||||
interface MetricsData {
|
||||
totalSales: number;
|
||||
wasteReduction: number;
|
||||
accuracy: number;
|
||||
stockouts: number;
|
||||
}
|
||||
const getConfidenceLabel = (confidence: 'high' | 'medium' | 'low') => {
|
||||
switch (confidence) {
|
||||
case 'high':
|
||||
return 'Alta';
|
||||
case 'medium':
|
||||
return 'Media';
|
||||
case 'low':
|
||||
return 'Baja';
|
||||
default:
|
||||
return 'Media';
|
||||
}
|
||||
};
|
||||
|
||||
const DashboardPage: React.FC<DashboardPageProps> = ({ user }) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [weather, setWeather] = useState<WeatherData | null>(null);
|
||||
const [todayForecasts, setTodayForecasts] = useState<ForecastData[]>([]);
|
||||
const [metrics, setMetrics] = useState<MetricsData>({
|
||||
totalSales: 0,
|
||||
wasteReduction: 0,
|
||||
accuracy: 0,
|
||||
stockouts: 0
|
||||
});
|
||||
const DashboardPage = () => {
|
||||
const {
|
||||
weather,
|
||||
todayForecasts,
|
||||
metrics,
|
||||
products,
|
||||
isLoading,
|
||||
error,
|
||||
reload
|
||||
} = useDashboard();
|
||||
|
||||
// Sample historical data for charts
|
||||
if (isLoading) {
|
||||
return <div>Loading dashboard...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
<p>Error: {error}</p>
|
||||
<button onClick={reload}>Retry</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Sample historical data for charts (you can move this to the hook later)
|
||||
const salesHistory = [
|
||||
{ date: '2024-10-28', ventas: 145, prediccion: 140 },
|
||||
{ date: '2024-10-29', ventas: 128, prediccion: 135 },
|
||||
@@ -56,100 +75,14 @@ const DashboardPage: React.FC<DashboardPageProps> = ({ user }) => {
|
||||
{ name: 'Café', quantity: 67, trend: 'up' },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const loadDashboardData = async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
// Simulate API calls - in real implementation, these would be actual API calls
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Mock weather data
|
||||
setWeather({
|
||||
temperature: 18,
|
||||
description: 'Parcialmente nublado',
|
||||
precipitation: 0
|
||||
});
|
||||
|
||||
// Mock today's forecasts
|
||||
setTodayForecasts([
|
||||
{ product: 'Croissants', predicted: 48, confidence: 'high', change: 8 },
|
||||
{ product: 'Pan de molde', predicted: 35, confidence: 'high', change: 3 },
|
||||
{ product: 'Baguettes', predicted: 25, confidence: 'medium', change: -3 },
|
||||
{ product: 'Café', predicted: 72, confidence: 'high', change: 5 },
|
||||
{ product: 'Napolitanas', predicted: 26, confidence: 'medium', change: 3 }
|
||||
]);
|
||||
|
||||
// Mock metrics
|
||||
setMetrics({
|
||||
totalSales: 1247,
|
||||
wasteReduction: 15.3,
|
||||
accuracy: 87.2,
|
||||
stockouts: 2
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading dashboard data:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
loadDashboardData();
|
||||
}, []);
|
||||
|
||||
const getConfidenceColor = (confidence: string) => {
|
||||
switch (confidence) {
|
||||
case 'high':
|
||||
return 'text-success-600 bg-success-100';
|
||||
case 'medium':
|
||||
return 'text-warning-600 bg-warning-100';
|
||||
case 'low':
|
||||
return 'text-danger-600 bg-danger-100';
|
||||
default:
|
||||
return 'text-gray-600 bg-gray-100';
|
||||
}
|
||||
};
|
||||
|
||||
const getConfidenceLabel = (confidence: string) => {
|
||||
switch (confidence) {
|
||||
case 'high':
|
||||
return 'Alta';
|
||||
case 'medium':
|
||||
return 'Media';
|
||||
case 'low':
|
||||
return 'Baja';
|
||||
default:
|
||||
return 'N/A';
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="animate-pulse space-y-6">
|
||||
<div className="h-8 bg-gray-200 rounded w-1/4"></div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{[...Array(4)].map((_, i) => (
|
||||
<div key={i} className="h-32 bg-gray-200 rounded-xl"></div>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div className="h-64 bg-gray-200 rounded-xl"></div>
|
||||
<div className="h-64 bg-gray-200 rounded-xl"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
¡Hola, {user.fullName?.split(' ')[0] || 'Usuario'}! 👋
|
||||
{/* ¡Hola, {user.fullName?.split(' ')[0] || 'Usuario'}! 👋 */}
|
||||
Hola
|
||||
</h1>
|
||||
<p className="text-gray-600 mt-1">
|
||||
Aquí tienes un resumen de tu panadería para hoy
|
||||
@@ -173,7 +106,7 @@ const DashboardPage: React.FC<DashboardPageProps> = ({ user }) => {
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<p className="text-sm font-medium text-gray-600">Ventas de Hoy</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{metrics.totalSales}</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{metrics?.totalSales ?? 0}</p>
|
||||
<p className="text-xs text-success-600 flex items-center mt-1">
|
||||
<TrendingUp className="h-3 w-3 mr-1" />
|
||||
+12% vs ayer
|
||||
@@ -189,7 +122,7 @@ const DashboardPage: React.FC<DashboardPageProps> = ({ user }) => {
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<p className="text-sm font-medium text-gray-600">Reducción Desperdicio</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{metrics.wasteReduction}%</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{metrics?.wasteReduction ?? 0}%</p>
|
||||
<p className="text-xs text-success-600 flex items-center mt-1">
|
||||
<TrendingUp className="h-3 w-3 mr-1" />
|
||||
Mejorando
|
||||
@@ -205,7 +138,7 @@ const DashboardPage: React.FC<DashboardPageProps> = ({ user }) => {
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<p className="text-sm font-medium text-gray-600">Precisión IA</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{metrics.accuracy}%</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{metrics?.accuracy ?? 0}%</p>
|
||||
<p className="text-xs text-success-600 flex items-center mt-1">
|
||||
<TrendingUp className="h-3 w-3 mr-1" />
|
||||
Excelente
|
||||
@@ -221,7 +154,7 @@ const DashboardPage: React.FC<DashboardPageProps> = ({ user }) => {
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<p className="text-sm font-medium text-gray-600">Roturas Stock</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{metrics.stockouts}</p>
|
||||
<p className="text-2xl font-bold text-gray-900">{metrics?.stockouts ?? 0}</p>
|
||||
<p className="text-xs text-success-600 flex items-center mt-1">
|
||||
<TrendingDown className="h-3 w-3 mr-1" />
|
||||
Reduciendo
|
||||
|
||||
@@ -51,7 +51,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
name: '',
|
||||
address: '',
|
||||
businessType: 'individual',
|
||||
products: [],
|
||||
products: MADRID_PRODUCTS, // Automatically assign all products
|
||||
hasHistoricalData: false
|
||||
});
|
||||
|
||||
@@ -64,13 +64,11 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
|
||||
const steps = [
|
||||
{ id: 1, title: 'Datos de Panadería', icon: Store },
|
||||
{ id: 2, title: 'Productos y Servicios', icon: Factory },
|
||||
{ id: 3, title: 'Datos Históricos', icon: Upload },
|
||||
{ id: 4, title: 'Entrenamiento IA', icon: Brain },
|
||||
{ id: 5, title: 'Configuración Final', icon: Check }
|
||||
{ id: 2, title: 'Datos Históricos', icon: Upload },
|
||||
{ id: 3, title: 'Entrenamiento IA', icon: Brain },
|
||||
{ id: 4, title: 'Configuración Final', icon: Check }
|
||||
];
|
||||
|
||||
|
||||
const [trainingProgress, setTrainingProgress] = useState<TrainingProgress>({
|
||||
progress: 0,
|
||||
status: 'pending',
|
||||
@@ -123,7 +121,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
|
||||
// Auto-advance to final step after 2 seconds
|
||||
setTimeout(() => {
|
||||
setCurrentStep(5);
|
||||
setCurrentStep(4);
|
||||
}, 2000);
|
||||
|
||||
} else if (messageType === 'failed' || messageType === 'training_failed' || messageType === 'training_error') {
|
||||
@@ -161,7 +159,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
|
||||
// Connect to WebSocket when training starts
|
||||
useEffect(() => {
|
||||
if (tenantId && trainingJobId && currentStep === 4) {
|
||||
if (tenantId && trainingJobId && currentStep === 3) {
|
||||
connect();
|
||||
}
|
||||
|
||||
@@ -172,9 +170,42 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
};
|
||||
}, [tenantId, trainingJobId, currentStep, connect, disconnect, isConnected]);
|
||||
|
||||
|
||||
const storeTenantId = (tenantId: string) => {
|
||||
try {
|
||||
// Method 1: Store tenant ID directly
|
||||
localStorage.setItem('current_tenant_id', tenantId);
|
||||
|
||||
// Method 2: Update user_data to include tenant_id
|
||||
const existingUserData = localStorage.getItem('user_data');
|
||||
if (existingUserData) {
|
||||
const userData = JSON.parse(existingUserData);
|
||||
userData.current_tenant_id = tenantId;
|
||||
userData.tenant_id = tenantId; // Backup key
|
||||
localStorage.setItem('user_data', JSON.stringify(userData));
|
||||
} else {
|
||||
// Create user_data with tenant info if it doesn't exist
|
||||
localStorage.setItem('user_data', JSON.stringify({
|
||||
current_tenant_id: tenantId,
|
||||
tenant_id: tenantId
|
||||
}));
|
||||
}
|
||||
|
||||
// Method 3: Store in a dedicated tenant context
|
||||
localStorage.setItem('tenant_context', JSON.stringify({
|
||||
current_tenant_id: tenantId,
|
||||
last_updated: new Date().toISOString()
|
||||
}));
|
||||
|
||||
console.log('✅ Tenant ID stored successfully:', tenantId);
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to store tenant ID:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNext = () => {
|
||||
if (validateCurrentStep()) {
|
||||
if (currentStep === 3) {
|
||||
if (currentStep === 2) {
|
||||
// Always proceed to training step after CSV upload
|
||||
startTraining();
|
||||
} else {
|
||||
@@ -200,12 +231,6 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
}
|
||||
return true;
|
||||
case 2:
|
||||
if (bakeryData.products.length === 0) {
|
||||
toast.error('Selecciona al menos un producto');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
case 3:
|
||||
if (!bakeryData.csvFile) {
|
||||
toast.error('Por favor, selecciona un archivo con tus datos históricos');
|
||||
return false;
|
||||
@@ -235,7 +260,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
};
|
||||
|
||||
const startTraining = async () => {
|
||||
setCurrentStep(4);
|
||||
setCurrentStep(3);
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
@@ -259,6 +284,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
|
||||
const tenant = await createTenant(tenantData);
|
||||
setTenantId(tenant.id);
|
||||
storeTenantId(tenant.id);
|
||||
|
||||
// Step 2: Validate and Upload CSV file if provided
|
||||
if (bakeryData.csvFile) {
|
||||
@@ -327,7 +353,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
const handleComplete = async () => {
|
||||
if (!validateCurrentStep()) return;
|
||||
|
||||
if (currentStep < 4) {
|
||||
if (currentStep < 3) {
|
||||
// Start training process
|
||||
await startTraining();
|
||||
} else {
|
||||
@@ -354,7 +380,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
icon: 'ℹ️',
|
||||
duration: 4000
|
||||
});
|
||||
setCurrentStep(5);
|
||||
setCurrentStep(4);
|
||||
};
|
||||
|
||||
const handleTrainingTimeout = () => {
|
||||
@@ -461,52 +487,6 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
);
|
||||
|
||||
case 2:
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
Productos y Servicios
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-6">
|
||||
Selecciona los productos que vendes regularmente. Esto nos ayudará a crear predicciones más precisas.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3">
|
||||
{MADRID_PRODUCTS.map((product) => (
|
||||
<button
|
||||
key={product}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setBakeryData(prev => ({
|
||||
...prev,
|
||||
products: prev.products.includes(product)
|
||||
? prev.products.filter(p => p !== product)
|
||||
: [...prev.products, product]
|
||||
}));
|
||||
}}
|
||||
className={`p-3 text-sm rounded-xl border transition-all ${
|
||||
bakeryData.products.includes(product)
|
||||
? 'border-primary-500 bg-primary-50 text-primary-700'
|
||||
: 'border-gray-300 hover:border-gray-400'
|
||||
}`}
|
||||
>
|
||||
{product}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{bakeryData.products.length > 0 && (
|
||||
<div className="mt-4 p-4 bg-green-50 rounded-xl">
|
||||
<p className="text-sm text-green-700">
|
||||
✅ {bakeryData.products.length} productos seleccionados
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 3:
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
@@ -695,7 +675,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
</div>
|
||||
);
|
||||
|
||||
case 4:
|
||||
case 3:
|
||||
return (
|
||||
<EnhancedTrainingProgress
|
||||
progress={{
|
||||
@@ -715,7 +695,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
/>
|
||||
);
|
||||
|
||||
case 5:
|
||||
case 4:
|
||||
return (
|
||||
<div className="text-center space-y-6">
|
||||
<div className="mx-auto w-16 h-16 bg-green-100 rounded-full flex items-center justify-center">
|
||||
@@ -832,7 +812,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
</button>
|
||||
|
||||
{/* Dynamic Next/Complete Button */}
|
||||
{currentStep < 4 ? (
|
||||
{currentStep < 3 ? (
|
||||
<button
|
||||
onClick={handleNext}
|
||||
disabled={isLoading}
|
||||
@@ -850,7 +830,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
) : currentStep === 4 ? (
|
||||
) : currentStep === 3 ? (
|
||||
// Training step - show different buttons based on status
|
||||
<div className="flex space-x-3">
|
||||
{trainingProgress.status === 'failed' ? (
|
||||
@@ -877,7 +857,7 @@ const OnboardingPage: React.FC<OnboardingPageProps> = ({ user, onComplete }) =>
|
||||
</>
|
||||
) : trainingProgress.status === 'completed' ? (
|
||||
<button
|
||||
onClick={() => setCurrentStep(5)}
|
||||
onClick={() => setCurrentStep(4)}
|
||||
className="flex items-center px-6 py-2 bg-green-500 text-white rounded-xl hover:bg-green-600 transition-all hover:shadow-lg"
|
||||
>
|
||||
Continuar
|
||||
|
||||
Reference in New Issue
Block a user