first commit

This commit is contained in:
Urtzi Alfaro
2025-07-17 13:54:51 +02:00
parent 347ff51bd7
commit 5bb3e93da4
41 changed files with 10084 additions and 94 deletions

View File

@@ -0,0 +1,706 @@
// frontend/src/pages/dashboard/index.tsx (Fixed version)
import React, { useState, useEffect, useMemo } from 'react';
import { useAuth } from '../../contexts/AuthContext';
import ForecastChart from '../../components/charts/ForecastChart';
import dashboardApi from '../../api/dashboardApi';
import {
ChartBarIcon,
CalendarIcon,
CloudIcon,
TruckIcon,
ExclamationTriangleIcon,
CheckCircleIcon,
ArrowTrendingUpIcon,
ArrowTrendingDownIcon,
CogIcon,
BellIcon,
UserCircleIcon,
Bars3Icon,
XMarkIcon,
ArrowPathIcon
} from '@heroicons/react/24/outline';
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
BarElement,
} from 'chart.js';
import { Line } from 'react-chartjs-2';
import { format, subDays } from 'date-fns';
import { es } from 'date-fns/locale';
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
Title,
Tooltip,
Legend,
BarElement
);
interface SalesRecord {
id: string;
product_name: string;
quantity_sold: number;
revenue: number;
sale_date: string;
created_at: string;
}
interface ForecastRecord {
date: string;
product_name: string;
predicted_quantity: number;
confidence_lower: number;
confidence_upper: number;
}
const Dashboard: React.FC = () => {
const { user, tenant, logout } = useAuth();
const [currentDate] = useState(new Date());
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [retryCount, setRetryCount] = useState(0);
const [salesHistory, setSalesHistory] = useState<SalesRecord[]>([]);
const [panForecast, setPanForecast] = useState<ForecastRecord[]>([]);
const [croissantForecast, setCroissantForecast] = useState<ForecastRecord[]>([]);
const [cafeForecast, setCafeForecast] = useState<ForecastRecord[]>([]);
const [bocadilloForecast, setBocadilloForecast] = useState<ForecastRecord[]>([]);
const fetchDashboardData = async (skipLoading = false) => {
if (!skipLoading) {
setLoading(true);
}
setError(null);
try {
console.log('Fetching dashboard data...');
const endDate = format(currentDate, 'yyyy-MM-dd');
const startDate = format(subDays(currentDate, 30), 'yyyy-MM-dd'); // Fetch 30 days instead of 7
console.log('Fetching sales history from', startDate, 'to', endDate);
// Fetch sales history
const fetchedSales = await dashboardApi.getSalesHistory(startDate, endDate);
console.log('Fetched sales:', fetchedSales);
setSalesHistory(fetchedSales || []);
// Fetch forecasts for each product
const products = ['Pan', 'Croissant', 'Cafe', 'Bocadillo'];
for (const product of products) {
try {
console.log(`Fetching forecast for ${product}`);
const forecast = await dashboardApi.getProductForecast(product, 14);
console.log(`Forecast for ${product}:`, forecast);
switch (product) {
case 'Pan':
setPanForecast(forecast || []);
break;
case 'Croissant':
setCroissantForecast(forecast || []);
break;
case 'Cafe':
setCafeForecast(forecast || []);
break;
case 'Bocadillo':
setBocadilloForecast(forecast || []);
break;
}
} catch (forecastError) {
console.error(`Error fetching forecast for ${product}:`, forecastError);
// Continue with other products if one fails
}
}
console.log('Dashboard data loaded successfully');
setRetryCount(0);
} catch (err: any) {
console.error("Error fetching dashboard data:", err);
let errorMessage = "No se pudieron cargar los datos del dashboard.";
if (err.response?.status === 401) {
errorMessage = "Sesión expirada. Por favor, inicia sesión nuevamente.";
// Optionally logout the user
// logout();
} else if (err.response?.status === 403) {
errorMessage = "No tienes permisos para acceder a estos datos.";
} else if (err.response?.status === 404) {
errorMessage = "No se encontraron datos. Esto puede ser normal para nuevos usuarios.";
} else if (err.response?.status >= 500) {
errorMessage = "Error del servidor. Por favor, inténtalo más tarde.";
} else if (err.code === 'NETWORK_ERROR' || !err.response) {
errorMessage = "Error de conexión. Verifica tu conexión a internet.";
}
setError(errorMessage);
// Auto-retry logic for temporary network errors
if (retryCount < 3 && (!err.response || err.response.status >= 500)) {
console.log(`Retrying in 2 seconds... (attempt ${retryCount + 1}/3)`);
setTimeout(() => {
setRetryCount(prev => prev + 1);
fetchDashboardData(true);
}, 2000);
}
} finally {
setLoading(false);
}
};
const handleRetry = () => {
setRetryCount(0);
fetchDashboardData();
};
useEffect(() => {
if (user && tenant) {
console.log('User and tenant loaded, fetching dashboard data');
fetchDashboardData();
} else {
console.log('User or tenant not available yet');
}
}, [user, tenant]);
const salesChartData = useMemo(() => {
if (!salesHistory.length) {
return {
labels: [],
datasets: []
};
}
const salesByDateAndProduct: { [date: string]: { [product: string]: number } } = {};
salesHistory.forEach(sale => {
const date = format(new Date(sale.sale_date), 'yyyy-MM-dd');
if (!salesByDateAndProduct[date]) {
salesByDateAndProduct[date] = {};
}
salesByDateAndProduct[date][sale.product_name] = (salesByDateAndProduct[date][sale.product_name] || 0) + sale.quantity_sold;
});
const uniqueDates = Object.keys(salesByDateAndProduct).sort();
const allProductNames = Array.from(new Set(salesHistory.map(s => s.product_name)));
const datasets = allProductNames.map(productName => {
const productData = uniqueDates.map(date => salesByDateAndProduct[date][productName] || 0);
let borderColor = '';
let backgroundColor = '';
switch(productName.toLowerCase()) {
case 'pan':
borderColor = 'rgb(255, 99, 132)';
backgroundColor = 'rgba(255, 99, 132, 0.5)';
break;
case 'croissant':
borderColor = 'rgb(53, 162, 235)';
backgroundColor = 'rgba(53, 162, 235, 0.5)';
break;
case 'cafe':
borderColor = 'rgb(75, 192, 192)';
backgroundColor = 'rgba(75, 192, 192, 0.5)';
break;
case 'bocadillo':
borderColor = 'rgb(153, 102, 255)';
backgroundColor = 'rgba(153, 102, 255, 0.5)';
break;
default:
borderColor = `hsl(${Math.random() * 360}, 70%, 50%)`;
backgroundColor = `hsla(${Math.random() * 360}, 70%, 50%, 0.5)`;
}
return {
label: `Ventas de ${productName}`,
data: productData,
borderColor,
backgroundColor,
tension: 0.1,
};
});
return {
labels: uniqueDates.map(d => format(new Date(d), 'dd MMM', { locale: es })),
datasets: datasets,
};
}, [salesHistory]);
const demoForecastData = useMemo(() => {
if (!panForecast.length) {
return [];
}
return panForecast.map(f => ({
date: format(new Date(f.date), 'yyyy-MM-dd'),
predicted_quantity: f.predicted_quantity,
confidence_lower: f.confidence_lower,
confidence_upper: f.confidence_upper,
}));
}, [panForecast]);
const productPredictions = useMemo(() => {
const today = format(currentDate, 'yyyy-MM-dd');
const todaySales = salesHistory.filter(s => format(new Date(s.sale_date), 'yyyy-MM-dd') === today);
const currentSalesByProduct: { [product: string]: number } = {};
todaySales.forEach(sale => {
currentSalesByProduct[sale.product_name] = (currentSalesByProduct[sale.product_name] || 0) + sale.quantity_sold;
});
const allForecasts = [
...panForecast,
...croissantForecast,
...cafeForecast,
...bocadilloForecast
];
if (!allForecasts.length) {
return [];
}
const uniqueProductsInForecasts = Array.from(new Set(allForecasts.map(f => f.product_name)));
const predictions = uniqueProductsInForecasts.map((productName, index) => {
const productTodayForecast = allForecasts.find(f =>
f.product_name === productName && format(new Date(f.date), 'yyyy-MM-dd') === today
);
const predicted = productTodayForecast?.predicted_quantity || 0;
const current = currentSalesByProduct[productName] || 0;
let status: 'good' | 'warning' | 'bad' = 'good';
if (predicted > 0) {
const percentageAchieved = (current / predicted) * 100;
if (percentageAchieved < 50) {
status = 'bad';
} else if (percentageAchieved < 90) {
status = 'warning';
}
} else if (current > 0) {
status = 'good';
}
return {
id: index + 1,
product: productName,
predicted: Math.round(predicted),
current: current,
status: status,
};
}).filter(p => p.predicted > 0 || p.current > 0);
return predictions;
}, [salesHistory, panForecast, croissantForecast, cafeForecast, bocadilloForecast, currentDate]);
const kpiData = useMemo(() => {
if (!salesHistory.length) {
return {
totalSalesToday: 0,
salesChange: 0,
totalProductsSoldToday: 0,
productsSoldChange: 0,
wasteToday: 0,
wasteChange: 0,
totalPredictedValueToday: 0,
predictedValueChange: 0,
};
}
const today = format(currentDate, 'yyyy-MM-dd');
const yesterday = format(subDays(currentDate, 1), 'yyyy-MM-dd');
const salesToday = salesHistory.filter(s => format(new Date(s.sale_date), 'yyyy-MM-dd') === today);
const salesYesterday = salesHistory.filter(s => format(new Date(s.sale_date), 'yyyy-MM-dd') === yesterday);
const totalSalesToday = salesToday.reduce((sum, s) => sum + s.revenue, 0);
const totalProductsSoldToday = salesToday.reduce((sum, s) => sum + s.quantity_sold, 0);
const totalSalesYesterday = salesYesterday.reduce((sum, s) => sum + s.revenue, 0);
const totalProductsSoldYesterday = salesYesterday.reduce((sum, s) => sum + s.quantity_sold, 0);
const wasteToday = 15; // Mock data
const wasteLastWeek = 15.3; // Mock data
const salesChange = totalSalesYesterday > 0 ? ((totalSalesToday - totalSalesYesterday) / totalSalesYesterday) * 100 : (totalSalesToday > 0 ? 100 : 0);
const productsSoldChange = totalProductsSoldYesterday > 0 ? ((totalProductsSoldToday - totalProductsSoldYesterday) / totalProductsSoldYesterday) * 100 : (totalProductsSoldToday > 0 ? 100 : 0);
const wasteChange = wasteLastWeek > 0 ? ((wasteToday - wasteLastWeek) / wasteLastWeek) * 100 : (wasteToday > 0 ? 100 : 0);
const totalPredictedValueToday = productPredictions.reduce((sum, p) => sum + p.predicted, 0) * 1.5;
const predictedValueChange = totalPredictedValueToday > 0 ? ((totalSalesToday - totalPredictedValueToday) / totalPredictedValueToday) * 100 : (totalSalesToday > 0 ? 100 : 0);
return {
totalSalesToday,
salesChange,
totalProductsSoldToday,
productsSoldChange,
wasteToday,
wasteChange,
totalPredictedValueToday,
predictedValueChange,
};
}, [salesHistory, productPredictions, currentDate]);
const salesChartOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top' as const,
},
title: {
display: true,
text: 'Histórico de Ventas Recientes',
},
},
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Cantidad Vendida (uds)',
},
},
x: {
title: {
display: true,
text: 'Fecha',
},
},
},
};
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-100">
<div className="text-center">
<div className="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-orange-600 mx-auto"></div>
<p className="mt-4 text-lg text-gray-700">Cargando datos del dashboard...</p>
{retryCount > 0 && (
<p className="mt-2 text-sm text-gray-500">Reintentando... ({retryCount}/3)</p>
)}
</div>
</div>
);
}
if (error) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-100">
<div className="text-center bg-white p-8 rounded-lg shadow-md max-w-md mx-4">
<ExclamationTriangleIcon className="h-12 w-12 text-red-500 mx-auto mb-4" />
<h2 className="text-xl font-semibold text-gray-900 mb-2">Error al cargar datos</h2>
<p className="text-gray-600 mb-6">{error}</p>
<div className="space-y-2">
<button
onClick={handleRetry}
className="w-full bg-orange-600 text-white px-4 py-2 rounded-lg hover:bg-orange-700 transition-colors flex items-center justify-center"
>
<ArrowPathIcon className="h-5 w-5 mr-2" />
Reintentar
</button>
<button
onClick={() => window.location.reload()}
className="w-full bg-gray-200 text-gray-800 px-4 py-2 rounded-lg hover:bg-gray-300 transition-colors"
>
Recargar página
</button>
</div>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gray-100 flex">
{/* Overlay for mobile sidebar */}
{isSidebarOpen && (
<div
className="fixed inset-0 bg-black bg-opacity-50 z-30 lg:hidden"
onClick={() => setIsSidebarOpen(false)}
></div>
)}
{/* Sidebar - Made responsive */}
<aside
className={`fixed inset-y-0 left-0 z-40 bg-white shadow-md overflow-y-auto transform transition-transform duration-300 ease-in-out
${isSidebarOpen ? 'translate-x-0' : '-translate-x-full'} lg:translate-x-0 lg:static lg:inset-0 w-64`}
>
<div className="p-6 border-b border-gray-200">
<div className="flex items-center justify-between">
<h2 className="text-xl font-bold text-gray-900">PanIA</h2>
<button
className="lg:hidden p-2"
onClick={() => setIsSidebarOpen(false)}
>
<XMarkIcon className="h-6 w-6 text-gray-500" />
</button>
</div>
<p className="text-sm text-gray-600 mt-1">{tenant?.name}</p>
</div>
<nav className="mt-6">
<div className="px-6 py-3">
<div className="flex items-center text-orange-600">
<ChartBarIcon className="h-5 w-5 mr-3" />
<span className="font-medium">Dashboard</span>
</div>
</div>
<div className="mt-8 px-6">
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wide">
Herramientas
</h3>
<div className="mt-2 space-y-1">
<div className="flex items-center text-gray-700 hover:bg-gray-100 rounded-lg p-2 cursor-pointer">
<CalendarIcon className="h-5 w-5 mr-3" />
<span className="text-sm">Pronósticos</span>
</div>
<div className="flex items-center text-gray-700 hover:bg-gray-100 rounded-lg p-2 cursor-pointer">
<CloudIcon className="h-5 w-5 mr-3" />
<span className="text-sm">Datos Climáticos</span>
</div>
<div className="flex items-center text-gray-700 hover:bg-gray-100 rounded-lg p-2 cursor-pointer">
<TruckIcon className="h-5 w-5 mr-3" />
<span className="text-sm">Pedidos</span>
</div>
</div>
</div>
<div className="mt-8 px-6">
<h3 className="text-xs font-semibold text-gray-500 uppercase tracking-wide">
Configuración
</h3>
<div className="mt-2 space-y-1">
<div className="flex items-center text-gray-700 hover:bg-gray-100 rounded-lg p-2 cursor-pointer">
<CogIcon className="h-5 w-5 mr-3" />
<span className="text-sm">Ajustes</span>
</div>
<div className="flex items-center text-gray-700 hover:bg-gray-100 rounded-lg p-2 cursor-pointer">
<UserCircleIcon className="h-5 w-5 mr-3" />
<span className="text-sm">Perfil</span>
</div>
</div>
</div>
</nav>
<div className="absolute bottom-0 w-full p-6 border-t border-gray-200">
<div className="flex items-center">
<div className="w-8 h-8 bg-orange-600 rounded-full flex items-center justify-center">
<span className="text-white text-sm font-medium">
{user?.full_name?.charAt(0)}
</span>
</div>
<div className="ml-3">
<p className="text-sm font-medium text-gray-900">{user?.full_name}</p>
<p className="text-xs text-gray-500">{user?.email}</p>
</div>
</div>
<button
onClick={logout}
className="mt-3 w-full text-left text-sm text-red-600 hover:text-red-700"
>
Cerrar sesión
</button>
</div>
</aside>
{/* Main content */}
<div className="flex-1 flex flex-col overflow-hidden">
{/* Header */}
<header className="bg-white shadow-sm border-b border-gray-200 flex items-center justify-between px-4 py-3">
<button
className="lg:hidden p-2"
onClick={() => setIsSidebarOpen(true)}
>
<Bars3Icon className="h-6 w-6 text-gray-500" />
</button>
<h1 className="text-xl font-semibold text-gray-900">Dashboard</h1>
<div className="flex items-center space-x-4">
<span className="text-gray-600 text-sm">
{format(currentDate, 'dd MMMM yyyy', { locale: es })}
</span>
<button onClick={handleRetry} className="p-2 text-gray-500 hover:text-gray-700">
<ArrowPathIcon className="h-5 w-5" />
</button>
<BellIcon className="h-6 w-6 text-gray-500 cursor-pointer" />
</div>
</header>
<main className="flex-1 overflow-y-auto p-4 sm:p-6 lg:p-8">
{/* Show a notice if no data is available */}
{!salesHistory.length && !loading && (
<div className="bg-blue-50 border border-blue-200 rounded-lg p-6 mb-6">
<div className="flex items-center">
<ExclamationTriangleIcon className="h-6 w-6 text-blue-600 mr-3" />
<div>
<h3 className="text-lg font-medium text-blue-900">
No hay datos disponibles
</h3>
<p className="text-blue-700 mt-1">
Parece que aún no tienes datos de ventas. Los datos se generarán automáticamente
después de completar el proceso de configuración.
</p>
</div>
</div>
</div>
)}
{/* KPI Cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div className="bg-white rounded-lg shadow-md p-6">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium text-gray-500">Ventas Hoy</h3>
<ChartBarIcon className="h-6 w-6 text-orange-500" />
</div>
<p className="mt-1 text-3xl font-semibold text-gray-900">
{kpiData.totalSalesToday.toFixed(2)}
</p>
<p className={`mt-2 text-sm flex items-center ${kpiData.salesChange >= 0 ? 'text-green-600' : 'text-red-600'}`}>
{kpiData.salesChange >= 0 ? <ArrowTrendingUpIcon className="h-4 w-4 mr-1" /> : <ArrowTrendingDownIcon className="h-4 w-4 mr-1" />}
{kpiData.salesChange.toFixed(1)}% desde ayer
</p>
</div>
<div className="bg-white rounded-lg shadow-md p-6">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium text-gray-500">Productos Vendidos</h3>
<CheckCircleIcon className="h-6 w-6 text-green-500" />
</div>
<p className="mt-1 text-3xl font-semibold text-gray-900">
{kpiData.totalProductsSoldToday} uds.
</p>
<p className={`mt-2 text-sm flex items-center ${kpiData.productsSoldChange >= 0 ? 'text-green-600' : 'text-red-600'}`}>
{kpiData.productsSoldChange >= 0 ? <ArrowTrendingUpIcon className="h-4 w-4 mr-1" /> : <ArrowTrendingDownIcon className="h-4 w-4 mr-1" />}
{kpiData.productsSoldChange.toFixed(1)}% desde ayer
</p>
</div>
<div className="bg-white rounded-lg shadow-md p-6">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium text-gray-500">Desperdicio</h3>
<ExclamationTriangleIcon className="h-6 w-6 text-red-500" />
</div>
<p className="mt-1 text-3xl font-semibold text-gray-900">
{kpiData.wasteToday} kg
</p>
<p className={`mt-2 text-sm flex items-center ${kpiData.wasteChange <= 0 ? 'text-green-600' : 'text-red-600'}`}>
{kpiData.wasteChange <= 0 ? <ArrowTrendingDownIcon className="h-4 w-4 mr-1" /> : <ArrowTrendingUpIcon className="h-4 w-4 mr-1" />}
{kpiData.wasteChange.toFixed(1)}% desde la semana pasada
</p>
</div>
<div className="bg-white rounded-lg shadow-md p-6">
<div className="flex items-center justify-between">
<h3 className="text-lg font-medium text-gray-500">Valor Pronosticado</h3>
<ChartBarIcon className="h-6 w-6 text-blue-500" />
</div>
<p className="mt-1 text-3xl font-semibold text-gray-900">
{kpiData.totalPredictedValueToday.toFixed(2)}
</p>
<p className={`mt-2 text-sm flex items-center ${kpiData.predictedValueChange >= 0 ? 'text-green-600' : 'text-red-600'}`}>
{kpiData.predictedValueChange >= 0 ? <ArrowTrendingUpIcon className="h-4 w-4 mr-1" /> : <ArrowTrendingDownIcon className="h-4 w-4 mr-1" />}
{kpiData.predictedValueChange.toFixed(1)}% sobre predicción
</p>
</div>
</div>
{/* Charts Section */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold text-gray-900 mb-4">Ventas por Día</h2>
{salesHistory.length > 0 ? (
<div style={{ height: '350px' }}>
<Line data={salesChartData} options={salesChartOptions} />
</div>
) : (
<div className="h-80 flex items-center justify-center text-gray-500">
<div className="text-center">
<ChartBarIcon className="h-12 w-12 mx-auto mb-4 text-gray-400" />
<p>No hay datos de ventas disponibles</p>
</div>
</div>
)}
</div>
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold text-gray-900 mb-4">Pronóstico de Demanda (Pan)</h2>
{demoForecastData.length > 0 ? (
<div style={{ height: '350px' }}>
<ForecastChart data={demoForecastData} productName="Pan" />
</div>
) : (
<div className="h-80 flex items-center justify-center text-gray-500">
<div className="text-center">
<CalendarIcon className="h-12 w-12 mx-auto mb-4 text-gray-400" />
<p>No hay pronósticos disponibles</p>
</div>
</div>
)}
</div>
</div>
{/* Product Predictions */}
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold text-gray-900 mb-4">Predicciones de Productos Clave</h2>
{productPredictions.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{productPredictions.map((prediction) => (
<div key={prediction.id} className="border border-gray-200 rounded-lg p-4">
<div className="flex items-center justify-between mb-2">
<h4 className="text-sm font-medium text-gray-900">{prediction.product}</h4>
<div className={`w-3 h-3 rounded-full ${
prediction.status === 'good' ? 'bg-green-400' :
prediction.status === 'warning' ? 'bg-yellow-400' : 'bg-red-400'
}`}></div>
</div>
<div className="space-y-1">
<div className="flex justify-between text-sm">
<span className="text-gray-500">Predicción:</span>
<span className="font-medium">{prediction.predicted} uds</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-500">Actual:</span>
<span className="font-medium">{prediction.current} uds</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className={`h-2 rounded-full ${
prediction.status === 'good' ? 'bg-green-400' :
prediction.status === 'warning' ? 'bg-yellow-400' : 'bg-red-400'
}`}
style={{
width: `${Math.min((prediction.current / prediction.predicted) * 100, 100)}%`
}}
></div>
</div>
</div>
</div>
))}
</div>
) : (
<div className="text-center py-8 text-gray-500">
<div className="text-center">
<TruckIcon className="h-12 w-12 mx-auto mb-4 text-gray-400" />
<p>No hay predicciones disponibles</p>
</div>
</div>
)}
</div>
</main>
</div>
</div>
);
};
export default Dashboard;