/* * Enterprise Dashboard Page * Main dashboard for enterprise parent tenants showing network-wide metrics */ import React, { useState, useEffect } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useNetworkSummary, useChildrenPerformance, useDistributionOverview, useForecastSummary } from '../../api/hooks/useEnterpriseDashboard'; import { Card, CardContent, CardHeader, CardTitle } from '../../components/ui/Card'; import { Button } from '../../components/ui/Button'; import { TrendingUp, MapPin, Truck, Package, BarChart3, Network, Activity, Calendar, Clock, AlertTriangle, PackageCheck, Building2, ArrowLeft, ChevronRight } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { LoadingSpinner } from '../../components/ui/LoadingSpinner'; import { ErrorBoundary } from 'react-error-boundary'; import { apiClient } from '../../api/client/apiClient'; import { useEnterprise } from '../../contexts/EnterpriseContext'; import { useTenant } from '../../stores/tenant.store'; // Components for enterprise dashboard const NetworkSummaryCards = React.lazy(() => import('../../components/dashboard/NetworkSummaryCards')); const DistributionMap = React.lazy(() => import('../../components/maps/DistributionMap')); const PerformanceChart = React.lazy(() => import('../../components/charts/PerformanceChart')); interface EnterpriseDashboardPageProps { tenantId?: string; } const EnterpriseDashboardPage: React.FC = ({ tenantId: propTenantId }) => { const { tenantId: urlTenantId } = useParams<{ tenantId: string }>(); const tenantId = propTenantId || urlTenantId; const navigate = useNavigate(); const { t } = useTranslation('dashboard'); const { state: enterpriseState, drillDownToOutlet, returnToNetworkView, enterNetworkView } = useEnterprise(); const { switchTenant } = useTenant(); const [selectedMetric, setSelectedMetric] = useState('sales'); const [selectedPeriod, setSelectedPeriod] = useState(30); const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]); // Check if tenantId is available at the start useEffect(() => { if (!tenantId) { console.error('No tenant ID available for enterprise dashboard'); navigate('/unauthorized'); } }, [tenantId, navigate]); // Initialize enterprise mode on mount useEffect(() => { if (tenantId && !enterpriseState.parentTenantId) { enterNetworkView(tenantId); } }, [tenantId, enterpriseState.parentTenantId, enterNetworkView]); // Check if user has enterprise tier access useEffect(() => { const checkAccess = async () => { if (!tenantId) { console.error('No tenant ID available for enterprise dashboard'); navigate('/unauthorized'); return; } try { const response = await apiClient.get<{ tenant_type: string }>(`/tenants/${tenantId}`); if (response.tenant_type !== 'parent') { navigate('/unauthorized'); } } catch (error) { console.error('Access check failed:', error); navigate('/unauthorized'); } }; checkAccess(); }, [tenantId, navigate]); // Fetch network summary data const { data: networkSummary, isLoading: isNetworkSummaryLoading, error: networkSummaryError } = useNetworkSummary(tenantId!, { refetchInterval: 60000, // Refetch every minute enabled: !!tenantId, // Only fetch if tenantId is available }); // Fetch children performance data const { data: childrenPerformance, isLoading: isChildrenPerformanceLoading, error: childrenPerformanceError } = useChildrenPerformance(tenantId!, selectedMetric, selectedPeriod, { enabled: !!tenantId, // Only fetch if tenantId is available }); // Fetch distribution overview data const { data: distributionOverview, isLoading: isDistributionLoading, error: distributionError } = useDistributionOverview(tenantId!, selectedDate, { refetchInterval: 60000, // Refetch every minute enabled: !!tenantId, // Only fetch if tenantId is available }); // Fetch enterprise forecast summary const { data: forecastSummary, isLoading: isForecastLoading, error: forecastError } = useForecastSummary(tenantId!, 7, { enabled: !!tenantId, // Only fetch if tenantId is available }); // Handle outlet drill-down const handleOutletClick = async (outletId: string, outletName: string) => { // Calculate network metrics if available const networkMetrics = childrenPerformance?.rankings ? { totalSales: childrenPerformance.rankings.reduce((sum, r) => sum + (selectedMetric === 'sales' ? r.metric_value : 0), 0), totalProduction: 0, totalInventoryValue: childrenPerformance.rankings.reduce((sum, r) => sum + (selectedMetric === 'inventory_value' ? r.metric_value : 0), 0), averageSales: childrenPerformance.rankings.reduce((sum, r) => sum + (selectedMetric === 'sales' ? r.metric_value : 0), 0) / childrenPerformance.rankings.length, averageProduction: 0, averageInventoryValue: childrenPerformance.rankings.reduce((sum, r) => sum + (selectedMetric === 'inventory_value' ? r.metric_value : 0), 0) / childrenPerformance.rankings.length, childCount: childrenPerformance.rankings.length } : undefined; drillDownToOutlet(outletId, outletName, networkMetrics); await switchTenant(outletId); navigate('/app/dashboard'); }; // Handle return to network view const handleReturnToNetwork = async () => { if (enterpriseState.parentTenantId) { returnToNetworkView(); await switchTenant(enterpriseState.parentTenantId); navigate(`/app/enterprise/${enterpriseState.parentTenantId}`); } }; // Error boundary fallback const ErrorFallback = ({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }) => (

Something went wrong

{error.message}

); if (isNetworkSummaryLoading || isChildrenPerformanceLoading || isDistributionLoading || isForecastLoading) { return (
); } if (networkSummaryError || childrenPerformanceError || distributionError || forecastError) { return (

Error Loading Dashboard

{networkSummaryError?.message || childrenPerformanceError?.message || distributionError?.message || forecastError?.message}

); } return (
{/* Breadcrumb / Return to Network Banner */} {enterpriseState.selectedOutletId && !enterpriseState.isNetworkView && (
Network Overview {enterpriseState.selectedOutletName}
{enterpriseState.networkMetrics && (
Network Average Sales: €{enterpriseState.networkMetrics.averageSales.toLocaleString()}
Total Outlets: {enterpriseState.networkMetrics.childCount}
Network Total: €{enterpriseState.networkMetrics.totalSales.toLocaleString()}
)}
)} {/* Enhanced Header */}
{/* Title Section with Gradient Icon */}

{t('enterprise.network_dashboard')}

{t('enterprise.network_summary_description')}

{/* Network Summary Cards */}
{/* Distribution Map and Performance Chart Row */}
{/* Distribution Map */}
{t('enterprise.distribution_map')}
setSelectedDate(e.target.value)} className="border rounded px-2 py-1 text-sm" />
{distributionOverview ? ( ) : (
{t('enterprise.no_distribution_data')}
)}
{/* Performance Chart */}
{t('enterprise.outlet_performance')}
{childrenPerformance ? ( ) : (
{t('enterprise.no_performance_data')}
)}
{/* Forecast Summary */}
{t('enterprise.network_forecast')} {forecastSummary && forecastSummary.aggregated_forecasts ? (
{/* Total Demand Card */}

{t('enterprise.total_demand')}

{Object.values(forecastSummary.aggregated_forecasts).reduce((total: number, day: any) => total + Object.values(day).reduce((dayTotal: number, product: any) => dayTotal + (product.predicted_demand || 0), 0), 0 ).toLocaleString()}

{/* Days Forecast Card */}

{t('enterprise.days_forecast')}

{forecastSummary.days_forecast || 7}

{/* Average Daily Demand Card */}

{t('enterprise.avg_daily_demand')}

{forecastSummary.aggregated_forecasts ? Math.round(Object.values(forecastSummary.aggregated_forecasts).reduce((total: number, day: any) => total + Object.values(day).reduce((dayTotal: number, product: any) => dayTotal + (product.predicted_demand || 0), 0), 0) / Object.keys(forecastSummary.aggregated_forecasts).length ).toLocaleString() : 0}

{/* Last Updated Card */}

{t('enterprise.last_updated')}

{forecastSummary.last_updated ? new Date(forecastSummary.last_updated).toLocaleTimeString() : 'N/A'}

) : (
{t('enterprise.no_forecast_data')}
)}
{/* Quick Actions */}

Agregar Punto de Venta

Añadir un nuevo outlet a la red enterprise

Transferencias Internas

Gestionar pedidos entre obrador central y outlets

Rutas de Distribución

Optimizar rutas de entrega entre ubicaciones

); }; export default EnterpriseDashboardPage;