Imporve enterprise
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
} from '../../api/hooks/useEnterpriseDashboard';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '../../components/ui/Card';
|
||||
import { Button } from '../../components/ui/Button';
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../../components/ui/Tabs';
|
||||
import {
|
||||
TrendingUp,
|
||||
MapPin,
|
||||
@@ -27,7 +28,11 @@ import {
|
||||
PackageCheck,
|
||||
Building2,
|
||||
ArrowLeft,
|
||||
ChevronRight
|
||||
ChevronRight,
|
||||
Target,
|
||||
Warehouse,
|
||||
ShoppingCart,
|
||||
ShieldCheck
|
||||
} from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LoadingSpinner } from '../../components/ui/LoadingSpinner';
|
||||
@@ -35,11 +40,18 @@ import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { apiClient } from '../../api/client/apiClient';
|
||||
import { useEnterprise } from '../../contexts/EnterpriseContext';
|
||||
import { useTenant } from '../../stores/tenant.store';
|
||||
import { useSSEEvents } from '../../hooks/useSSE';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
// 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'));
|
||||
const NetworkOverviewTab = React.lazy(() => import('../../components/dashboard/NetworkOverviewTab'));
|
||||
const NetworkPerformanceTab = React.lazy(() => import('../../components/dashboard/NetworkPerformanceTab'));
|
||||
const OutletFulfillmentTab = React.lazy(() => import('../../components/dashboard/OutletFulfillmentTab'));
|
||||
const ProductionTab = React.lazy(() => import('../../components/dashboard/ProductionTab'));
|
||||
const DistributionTab = React.lazy(() => import('../../components/dashboard/DistributionTab'));
|
||||
|
||||
interface EnterpriseDashboardPageProps {
|
||||
tenantId?: string;
|
||||
@@ -56,6 +68,51 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
||||
const [selectedMetric, setSelectedMetric] = useState('sales');
|
||||
const [selectedPeriod, setSelectedPeriod] = useState(30);
|
||||
const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]);
|
||||
const [activeTab, setActiveTab] = useState('overview');
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// SSE Integration for real-time updates
|
||||
const { events: sseEvents } = useSSEEvents({
|
||||
channels: ['*.alerts', '*.notifications', 'recommendations']
|
||||
});
|
||||
|
||||
// Invalidate enterprise data on relevant SSE events
|
||||
useEffect(() => {
|
||||
if (sseEvents.length === 0 || !tenantId) return;
|
||||
|
||||
const latest = sseEvents[0];
|
||||
const relevantEventTypes = [
|
||||
'batch_completed', 'batch_started', 'batch_state_changed',
|
||||
'delivery_received', 'delivery_overdue', 'delivery_arriving_soon',
|
||||
'stock_receipt_incomplete', 'orchestration_run_completed',
|
||||
'production_delay', 'batch_start_delayed', 'equipment_maintenance',
|
||||
'network_alert', 'outlet_performance_update', 'distribution_route_update'
|
||||
];
|
||||
|
||||
if (relevantEventTypes.includes(latest.event_type)) {
|
||||
// Invalidate all enterprise dashboard queries
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['enterprise', 'network-summary', tenantId],
|
||||
refetchType: 'active',
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['enterprise', 'children-performance', tenantId],
|
||||
refetchType: 'active',
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['enterprise', 'distribution-overview', tenantId],
|
||||
refetchType: 'active',
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['enterprise', 'forecast-summary', tenantId],
|
||||
refetchType: 'active',
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['control-panel-data', tenantId],
|
||||
refetchType: 'active',
|
||||
});
|
||||
}
|
||||
}, [sseEvents, tenantId, queryClient]);
|
||||
|
||||
// Check if tenantId is available at the start
|
||||
useEffect(() => {
|
||||
@@ -273,258 +330,187 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Network Summary Cards */}
|
||||
<div className="mb-8">
|
||||
<NetworkSummaryCards
|
||||
data={networkSummary}
|
||||
isLoading={isNetworkSummaryLoading}
|
||||
/>
|
||||
</div>
|
||||
{/* Main Tabs Structure */}
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="mb-6">
|
||||
<TabsList className="grid grid-cols-1 sm:grid-cols-3 lg:grid-cols-8 gap-2 mb-8">
|
||||
<TabsTrigger value="overview">
|
||||
<Network className="w-4 h-4 mr-2" />
|
||||
{t('enterprise.network_status')}
|
||||
</TabsTrigger>
|
||||
|
||||
{/* Distribution Map and Performance Chart Row */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
|
||||
{/* Distribution Map */}
|
||||
<div>
|
||||
<Card className="h-full">
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Truck className="w-5 h-5 text-[var(--color-info)]" />
|
||||
<CardTitle>{t('enterprise.distribution_map')}</CardTitle>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="w-4 h-4 text-[var(--text-secondary)]" />
|
||||
<input
|
||||
type="date"
|
||||
value={selectedDate}
|
||||
onChange={(e) => setSelectedDate(e.target.value)}
|
||||
className="border border-[var(--border-primary)] rounded-md px-2 py-1 text-sm bg-[var(--input-bg)] text-[var(--text-primary)]"
|
||||
/>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{distributionOverview ? (
|
||||
<DistributionMap
|
||||
routes={distributionOverview.route_sequences}
|
||||
shipments={distributionOverview.status_counts}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-96 flex items-center justify-center text-[var(--text-secondary)]">
|
||||
{t('enterprise.no_distribution_data')}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<TabsTrigger value="network-performance">
|
||||
<Target className="w-4 h-4 mr-2" />
|
||||
{t('enterprise.network_performance')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="fulfillment">
|
||||
<Warehouse className="w-4 h-4 mr-2" />
|
||||
{t('enterprise.outlet_fulfillment')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="distribution">
|
||||
<Truck className="w-4 h-4 mr-2" />
|
||||
{t('enterprise.distribution_map')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="forecast">
|
||||
<TrendingUp className="w-4 h-4 mr-2" />
|
||||
{t('enterprise.network_forecast')}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="production">
|
||||
<Package className="w-4 h-4 mr-2" />
|
||||
{t('enterprise.production')}
|
||||
</TabsTrigger>
|
||||
|
||||
{/* Performance Chart */}
|
||||
<div>
|
||||
<Card className="h-full">
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<BarChart3 className="w-5 h-5 text-[var(--color-success)]" />
|
||||
<CardTitle>{t('enterprise.outlet_performance')}</CardTitle>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<select
|
||||
value={selectedMetric}
|
||||
onChange={(e) => setSelectedMetric(e.target.value)}
|
||||
className="border border-[var(--border-primary)] rounded-md px-2 py-1 text-sm bg-[var(--input-bg)] text-[var(--text-primary)]"
|
||||
>
|
||||
<option value="sales">{t('enterprise.metrics.sales')}</option>
|
||||
<option value="inventory_value">{t('enterprise.metrics.inventory_value')}</option>
|
||||
<option value="order_frequency">{t('enterprise.metrics.order_frequency')}</option>
|
||||
</select>
|
||||
<select
|
||||
value={selectedPeriod}
|
||||
onChange={(e) => setSelectedPeriod(Number(e.target.value))}
|
||||
className="border border-[var(--border-primary)] rounded-md px-2 py-1 text-sm bg-[var(--input-bg)] text-[var(--text-primary)]"
|
||||
>
|
||||
<option value={7}>{t('enterprise.last_7_days')}</option>
|
||||
<option value={30}>{t('enterprise.last_30_days')}</option>
|
||||
<option value={90}>{t('enterprise.last_90_days')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{childrenPerformance ? (
|
||||
<PerformanceChart
|
||||
data={childrenPerformance.rankings}
|
||||
metric={selectedMetric}
|
||||
period={selectedPeriod}
|
||||
onOutletClick={handleOutletClick}
|
||||
/>
|
||||
) : (
|
||||
<div className="h-96 flex items-center justify-center text-[var(--text-secondary)]">
|
||||
{t('enterprise.no_performance_data')}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</TabsList>
|
||||
|
||||
{/* Forecast Summary */}
|
||||
<div className="mb-8">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center gap-2">
|
||||
<TrendingUp className="w-5 h-5 text-[var(--color-primary)]" />
|
||||
<CardTitle>{t('enterprise.network_forecast')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{forecastSummary && forecastSummary.aggregated_forecasts ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{/* Total Demand Card */}
|
||||
<Card className="hover:shadow-lg transition-shadow duration-300">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div
|
||||
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
||||
style={{ backgroundColor: 'var(--color-info-100)' }}
|
||||
>
|
||||
<Package className="w-5 h-5" style={{ color: 'var(--color-info-600)' }} />
|
||||
</div>
|
||||
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-info-800)' }}>
|
||||
{t('enterprise.total_demand')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-3xl font-bold" style={{ color: 'var(--color-info-900)' }}>
|
||||
{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()}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* Tab Content */}
|
||||
<TabsContent value="overview">
|
||||
<NetworkOverviewTab
|
||||
tenantId={tenantId!}
|
||||
onOutletClick={handleOutletClick}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
{/* Days Forecast Card */}
|
||||
<Card className="hover:shadow-lg transition-shadow duration-300">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div
|
||||
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
||||
style={{ backgroundColor: 'var(--color-success-100)' }}
|
||||
>
|
||||
<Calendar className="w-5 h-5" style={{ color: 'var(--color-success-600)' }} />
|
||||
</div>
|
||||
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-success-800)' }}>
|
||||
{t('enterprise.days_forecast')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-3xl font-bold" style={{ color: 'var(--color-success-900)' }}>
|
||||
{forecastSummary.days_forecast || 7}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Average Daily Demand Card */}
|
||||
<Card className="hover:shadow-lg transition-shadow duration-300">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div
|
||||
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
||||
style={{ backgroundColor: 'var(--color-secondary-100)' }}
|
||||
>
|
||||
<Activity className="w-5 h-5" style={{ color: 'var(--color-secondary-600)' }} />
|
||||
</div>
|
||||
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-secondary-800)' }}>
|
||||
{t('enterprise.avg_daily_demand')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-3xl font-bold" style={{ color: 'var(--color-secondary-900)' }}>
|
||||
{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}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Last Updated Card */}
|
||||
<Card className="hover:shadow-lg transition-shadow duration-300">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div
|
||||
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
||||
style={{ backgroundColor: 'var(--color-warning-100)' }}
|
||||
>
|
||||
<Clock className="w-5 h-5" style={{ color: 'var(--color-warning-600)' }} />
|
||||
</div>
|
||||
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-warning-800)' }}>
|
||||
{t('enterprise.last_updated')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-lg font-semibold" style={{ color: 'var(--color-warning-900)' }}>
|
||||
{forecastSummary.last_updated ?
|
||||
new Date(forecastSummary.last_updated).toLocaleTimeString() :
|
||||
'N/A'}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-48 text-[var(--text-secondary)]">
|
||||
{t('enterprise.no_forecast_data')}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<TabsContent value="network-performance">
|
||||
<NetworkPerformanceTab
|
||||
tenantId={tenantId!}
|
||||
onOutletClick={handleOutletClick}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Building2 className="w-6 h-6 text-[var(--color-primary)]" />
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)]">Agregar Punto de Venta</h3>
|
||||
</div>
|
||||
<p className="text-[var(--text-secondary)] mb-4">Añadir un nuevo outlet a la red enterprise</p>
|
||||
<Button
|
||||
onClick={() => navigate(`/app/tenants/${tenantId}/settings/organization`)}
|
||||
className="w-full"
|
||||
>
|
||||
Crear Outlet
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<TabsContent value="distribution">
|
||||
<DistributionTab
|
||||
tenantId={tenantId!}
|
||||
selectedDate={selectedDate}
|
||||
onDateChange={setSelectedDate}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<PackageCheck className="w-6 h-6 text-[var(--color-success)]" />
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)]">Transferencias Internas</h3>
|
||||
</div>
|
||||
<p className="text-[var(--text-secondary)] mb-4">Gestionar pedidos entre obrador central y outlets</p>
|
||||
<Button
|
||||
onClick={() => navigate(`/app/tenants/${tenantId}/procurement/internal-transfers`)}
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
>
|
||||
Ver Transferencias
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<TabsContent value="forecast">
|
||||
{/* Forecast Summary */}
|
||||
<div className="mb-8">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center gap-2">
|
||||
<TrendingUp className="w-5 h-5 text-[var(--color-primary)]" />
|
||||
<CardTitle>{t('enterprise.network_forecast')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{forecastSummary && forecastSummary.aggregated_forecasts ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{/* Total Demand Card */}
|
||||
<Card className="hover:shadow-lg transition-shadow duration-300">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div
|
||||
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
||||
style={{ backgroundColor: 'var(--color-info-100)' }}
|
||||
>
|
||||
<Package className="w-5 h-5" style={{ color: 'var(--color-info-600)' }} />
|
||||
</div>
|
||||
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-info-800)' }}>
|
||||
{t('enterprise.total_demand')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-3xl font-bold" style={{ color: 'var(--color-info-900)' }}>
|
||||
{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()}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<MapPin className="w-6 h-6 text-[var(--color-info)]" />
|
||||
<h3 className="text-lg font-semibold text-[var(--text-primary)]">Rutas de Distribución</h3>
|
||||
</div>
|
||||
<p className="text-[var(--text-secondary)] mb-4">Optimizar rutas de entrega entre ubicaciones</p>
|
||||
<Button
|
||||
onClick={() => navigate(`/app/tenants/${tenantId}/distribution/routes`)}
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
>
|
||||
Ver Rutas
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
{/* Days Forecast Card */}
|
||||
<Card className="hover:shadow-lg transition-shadow duration-300">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div
|
||||
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
||||
style={{ backgroundColor: 'var(--color-success-100)' }}
|
||||
>
|
||||
<Calendar className="w-5 h-5" style={{ color: 'var(--color-success-600)' }} />
|
||||
</div>
|
||||
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-success-800)' }}>
|
||||
{t('enterprise.days_forecast')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-3xl font-bold" style={{ color: 'var(--color-success-900)' }}>
|
||||
{forecastSummary.days_forecast || 7}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Average Daily Demand Card */}
|
||||
<Card className="hover:shadow-lg transition-shadow duration-300">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div
|
||||
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
||||
style={{ backgroundColor: 'var(--color-secondary-100)' }}
|
||||
>
|
||||
<Activity className="w-5 h-5" style={{ color: 'var(--color-secondary-600)' }} />
|
||||
</div>
|
||||
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-secondary-800)' }}>
|
||||
{t('enterprise.avg_daily_demand')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-3xl font-bold" style={{ color: 'var(--color-secondary-900)' }}>
|
||||
{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}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Last Updated Card */}
|
||||
<Card className="hover:shadow-lg transition-shadow duration-300">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div
|
||||
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
||||
style={{ backgroundColor: 'var(--color-warning-100)' }}
|
||||
>
|
||||
<Clock className="w-5 h-5" style={{ color: 'var(--color-warning-600)' }} />
|
||||
</div>
|
||||
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-warning-800)' }}>
|
||||
{t('enterprise.last_updated')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-lg font-semibold" style={{ color: 'var(--color-warning-900)' }}>
|
||||
{forecastSummary.last_updated ?
|
||||
new Date(forecastSummary.last_updated).toLocaleTimeString() :
|
||||
'N/A'}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-48 text-[var(--text-secondary)]">
|
||||
{t('enterprise.no_forecast_data')}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="fulfillment">
|
||||
<OutletFulfillmentTab
|
||||
tenantId={tenantId!}
|
||||
onOutletClick={handleOutletClick}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="production">
|
||||
<ProductionTab tenantId={tenantId!} />
|
||||
</TabsContent>
|
||||
|
||||
|
||||
</Tabs>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user