Fix and UI imporvements 3
This commit is contained in:
@@ -15,45 +15,33 @@
|
||||
* - Trust-building (explain system reasoning)
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useMemo, useRef } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { RefreshCw, ExternalLink, Plus, Sparkles, Wifi, WifiOff } from 'lucide-react';
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { Plus, Sparkles } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTenant } from '../../stores/tenant.store';
|
||||
import {
|
||||
useBakeryHealthStatus,
|
||||
useOrchestrationSummary,
|
||||
useUnifiedActionQueue,
|
||||
useProductionTimeline,
|
||||
useApprovePurchaseOrder,
|
||||
useStartProductionBatch,
|
||||
usePauseProductionBatch,
|
||||
useExecutionProgress,
|
||||
useDashboardRealtime, // PHASE 3: SSE state sync
|
||||
useProgressiveDashboard, // PHASE 4: Progressive loading
|
||||
} from '../../api/hooks/useProfessionalDashboard';
|
||||
import { useDashboardData, useDashboardRealtimeSync } from '../../api/hooks/useDashboardData';
|
||||
import { useRejectPurchaseOrder } from '../../api/hooks/purchase-orders';
|
||||
import { useIngredients } from '../../api/hooks/inventory';
|
||||
import { useSuppliers } from '../../api/hooks/suppliers';
|
||||
import { useRecipes } from '../../api/hooks/recipes';
|
||||
import { useQualityTemplates } from '../../api/hooks/qualityTemplates';
|
||||
import { GlanceableHealthHero } from '../../components/dashboard/GlanceableHealthHero';
|
||||
import { SetupWizardBlocker } from '../../components/dashboard/SetupWizardBlocker';
|
||||
import { CollapsibleSetupBanner } from '../../components/dashboard/CollapsibleSetupBanner';
|
||||
import { UnifiedActionQueueCard } from '../../components/dashboard/UnifiedActionQueueCard';
|
||||
import { ExecutionProgressTracker } from '../../components/dashboard/ExecutionProgressTracker';
|
||||
import { IntelligentSystemSummaryCard } from '../../components/dashboard/IntelligentSystemSummaryCard';
|
||||
import { useAuthUser } from '../../stores';
|
||||
import {
|
||||
SystemStatusBlock,
|
||||
PendingPurchasesBlock,
|
||||
PendingDeliveriesBlock,
|
||||
ProductionStatusBlock,
|
||||
} from '../../components/dashboard/blocks';
|
||||
import { UnifiedPurchaseOrderModal } from '../../components/domain/procurement/UnifiedPurchaseOrderModal';
|
||||
import { UnifiedAddWizard } from '../../components/domain/unified-wizard';
|
||||
import type { ItemType } from '../../components/domain/unified-wizard';
|
||||
import { useDemoTour, shouldStartTour, clearTourStartPending } from '../../features/demo-onboarding';
|
||||
import { Package, Users, BookOpen, Shield } from 'lucide-react';
|
||||
import {
|
||||
useBatchNotifications,
|
||||
useDeliveryNotifications,
|
||||
useOrchestrationNotifications,
|
||||
} from '../../hooks';
|
||||
|
||||
|
||||
// Import Enterprise Dashboard
|
||||
@@ -63,28 +51,21 @@ import { SUBSCRIPTION_TIERS } from '../../api/types/subscription';
|
||||
|
||||
// Rename the existing component to BakeryDashboard
|
||||
export function BakeryDashboard() {
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation(['dashboard', 'common', 'alerts']);
|
||||
const { currentTenant } = useTenant();
|
||||
const tenantId = currentTenant?.id || '';
|
||||
const { startTour } = useDemoTour();
|
||||
const isDemoMode = localStorage.getItem('demo_mode') === 'true';
|
||||
|
||||
|
||||
|
||||
|
||||
// Unified Add Wizard state
|
||||
const [isAddWizardOpen, setIsAddWizardOpen] = useState(false);
|
||||
const [addWizardError, setAddWizardError] = useState<string | null>(null);
|
||||
|
||||
// PO Details Modal state
|
||||
const [selectedPOId, setSelectedPOId] = useState<string | null>(null);
|
||||
const [isPOModalOpen, setIsPOModalOpen] = useState(false);
|
||||
const [poModalMode, setPOModalMode] = useState<'view' | 'edit'>('view');
|
||||
|
||||
// Setup Progress Data
|
||||
// Always fetch setup data to determine true progress, but use localStorage as fallback during loading
|
||||
// PHASE 1 OPTIMIZATION: Only use cached value if we're still waiting for API to respond
|
||||
// Setup Progress Data - use localStorage as fallback during loading
|
||||
const setupProgressFromStorage = useMemo(() => {
|
||||
try {
|
||||
const cached = localStorage.getItem(`setup_progress_${tenantId}`);
|
||||
@@ -94,7 +75,7 @@ export function BakeryDashboard() {
|
||||
}
|
||||
}, [tenantId]);
|
||||
|
||||
// Always fetch the actual data to determine true progress
|
||||
// Fetch setup data to determine true progress
|
||||
const { data: ingredients = [], isLoading: loadingIngredients } = useIngredients(
|
||||
tenantId,
|
||||
{},
|
||||
@@ -117,296 +98,57 @@ export function BakeryDashboard() {
|
||||
);
|
||||
const qualityTemplates = Array.isArray(qualityData?.templates) ? qualityData.templates : [];
|
||||
|
||||
// PHASE 4: Progressive data loading for perceived performance boost
|
||||
// NEW: Single unified data fetch for all 4 dashboard blocks
|
||||
const {
|
||||
health: {
|
||||
data: healthStatus,
|
||||
isLoading: healthLoading,
|
||||
refetch: refetchHealth,
|
||||
},
|
||||
actionQueue: {
|
||||
data: actionQueue,
|
||||
isLoading: actionQueueLoading,
|
||||
refetch: refetchActionQueue,
|
||||
},
|
||||
progress: {
|
||||
data: executionProgress,
|
||||
isLoading: executionProgressLoading,
|
||||
refetch: refetchExecutionProgress,
|
||||
},
|
||||
overallLoading,
|
||||
isReady,
|
||||
} = useProgressiveDashboard(tenantId);
|
||||
data: dashboardData,
|
||||
isLoading: dashboardLoading,
|
||||
refetch: refetchDashboard,
|
||||
} = useDashboardData(tenantId);
|
||||
|
||||
// Additional hooks not part of progressive loading
|
||||
const {
|
||||
data: orchestrationSummary,
|
||||
isLoading: orchestrationLoading,
|
||||
refetch: refetchOrchestration,
|
||||
} = useOrchestrationSummary(tenantId);
|
||||
|
||||
const {
|
||||
data: productionTimeline,
|
||||
isLoading: timelineLoading,
|
||||
refetch: refetchTimeline,
|
||||
} = useProductionTimeline(tenantId);
|
||||
|
||||
// Insights functionality removed as it's not needed with new architecture
|
||||
const insights = undefined;
|
||||
const insightsLoading = false;
|
||||
const refetchInsights = () => {};
|
||||
|
||||
// PHASE 3: Enable SSE real-time state synchronization
|
||||
useDashboardRealtime(tenantId);
|
||||
|
||||
|
||||
// PHASE 6: Performance monitoring
|
||||
useEffect(() => {
|
||||
const loadTime = performance.now();
|
||||
console.log(`📊 [Performance] Dashboard loaded in ${loadTime.toFixed(0)}ms`);
|
||||
|
||||
// Calculate setup completion status based on stored progress (approximation since actual data may not be loaded yet)
|
||||
const setupComplete = setupProgressFromStorage >= 100;
|
||||
|
||||
if (loadTime > 1000) {
|
||||
console.warn('⚠️ [Performance] Dashboard load time exceeded target (>1000ms):', {
|
||||
loadTime: `${loadTime.toFixed(0)}ms`,
|
||||
target: '1000ms',
|
||||
setupComplete,
|
||||
queriesSkipped: setupComplete ? 4 : 0,
|
||||
});
|
||||
} else {
|
||||
console.log('✅ [Performance] Dashboard load time within target:', {
|
||||
loadTime: `${loadTime.toFixed(0)}ms`,
|
||||
target: '<1000ms',
|
||||
setupComplete,
|
||||
queriesSkipped: setupComplete ? 4 : 0,
|
||||
});
|
||||
}
|
||||
}, [setupProgressFromStorage]); // Include setupProgressFromStorage as dependency
|
||||
|
||||
// Real-time event subscriptions for automatic refetching
|
||||
const { notifications: batchNotifications } = useBatchNotifications();
|
||||
const { notifications: deliveryNotifications } = useDeliveryNotifications();
|
||||
const { recentNotifications: orchestrationNotifications } = useOrchestrationNotifications();
|
||||
|
||||
console.log('🔄 [Dashboard] Component render - notification counts:', {
|
||||
batch: batchNotifications.length,
|
||||
delivery: deliveryNotifications.length,
|
||||
orchestration: orchestrationNotifications.length,
|
||||
batchIds: batchNotifications.map(n => n.id).join(','),
|
||||
deliveryIds: deliveryNotifications.map(n => n.id).join(','),
|
||||
orchestrationIds: orchestrationNotifications.map(n => n.id).join(','),
|
||||
});
|
||||
|
||||
// SSE connection status
|
||||
const sseConnected = true; // Simplified - based on other notification hooks
|
||||
|
||||
// Store refetch callbacks in a ref to prevent infinite loop from dependency changes
|
||||
// React Query refetch functions are recreated on every query state change, which would
|
||||
// trigger useEffect again if they were in the dependency array
|
||||
const refetchCallbacksRef = useRef({
|
||||
refetchActionQueue,
|
||||
refetchHealth,
|
||||
refetchExecutionProgress,
|
||||
refetchOrchestration,
|
||||
});
|
||||
|
||||
// Store previous notification IDs to prevent infinite refetch loops
|
||||
const prevBatchNotificationsRef = useRef('');
|
||||
const prevDeliveryNotificationsRef = useRef('');
|
||||
const prevOrchestrationNotificationsRef = useRef('');
|
||||
|
||||
// Update ref with latest callbacks on every render
|
||||
useEffect(() => {
|
||||
refetchCallbacksRef.current = {
|
||||
refetchActionQueue,
|
||||
refetchHealth,
|
||||
refetchExecutionProgress,
|
||||
refetchOrchestration,
|
||||
};
|
||||
});
|
||||
|
||||
// Track the latest notification ID to prevent re-running on same notification
|
||||
// Use stringified ID array to create stable dependency that only changes when IDs actually change
|
||||
const batchIdsString = JSON.stringify(batchNotifications.map(n => n.id));
|
||||
const deliveryIdsString = JSON.stringify(deliveryNotifications.map(n => n.id));
|
||||
const orchestrationIdsString = JSON.stringify(orchestrationNotifications.map(n => n.id));
|
||||
|
||||
console.log('📝 [Dashboard] Stringified ID arrays:', {
|
||||
batchIdsString,
|
||||
deliveryIdsString,
|
||||
orchestrationIdsString,
|
||||
});
|
||||
|
||||
const latestBatchNotificationId = useMemo(() => {
|
||||
const result = batchNotifications.length === 0 ? '' : (batchNotifications[0]?.id || '');
|
||||
console.log('🧮 [Dashboard] latestBatchNotificationId useMemo recalculated:', {
|
||||
result,
|
||||
dependency: batchIdsString,
|
||||
notificationCount: batchNotifications.length,
|
||||
});
|
||||
return result;
|
||||
}, [batchIdsString]);
|
||||
|
||||
const latestDeliveryNotificationId = useMemo(() => {
|
||||
const result = deliveryNotifications.length === 0 ? '' : (deliveryNotifications[0]?.id || '');
|
||||
console.log('🧮 [Dashboard] latestDeliveryNotificationId useMemo recalculated:', {
|
||||
result,
|
||||
dependency: deliveryIdsString,
|
||||
notificationCount: deliveryNotifications.length,
|
||||
});
|
||||
return result;
|
||||
}, [deliveryIdsString]);
|
||||
|
||||
const latestOrchestrationNotificationId = useMemo(() => {
|
||||
const result = orchestrationNotifications.length === 0 ? '' : (orchestrationNotifications[0]?.id || '');
|
||||
console.log('🧮 [Dashboard] latestOrchestrationNotificationId useMemo recalculated:', {
|
||||
result,
|
||||
dependency: orchestrationIdsString,
|
||||
notificationCount: orchestrationNotifications.length,
|
||||
});
|
||||
return result;
|
||||
}, [orchestrationIdsString]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('⚡ [Dashboard] batchNotifications useEffect triggered', {
|
||||
latestBatchNotificationId,
|
||||
prevValue: prevBatchNotificationsRef.current,
|
||||
hasChanged: latestBatchNotificationId !== prevBatchNotificationsRef.current,
|
||||
notificationCount: batchNotifications.length,
|
||||
firstNotification: batchNotifications[0],
|
||||
});
|
||||
|
||||
if (latestBatchNotificationId &&
|
||||
latestBatchNotificationId !== prevBatchNotificationsRef.current) {
|
||||
console.log('🔥 [Dashboard] NEW batch notification detected, updating ref and refetching');
|
||||
prevBatchNotificationsRef.current = latestBatchNotificationId;
|
||||
const latest = batchNotifications[0];
|
||||
|
||||
if (['batch_completed', 'batch_started'].includes(latest.event_type)) {
|
||||
console.log('🚀 [Dashboard] Triggering refetch for batch event:', latest.event_type);
|
||||
refetchCallbacksRef.current.refetchExecutionProgress();
|
||||
refetchCallbacksRef.current.refetchHealth();
|
||||
} else {
|
||||
console.log('⏭️ [Dashboard] Skipping refetch - event type not relevant:', latest.event_type);
|
||||
}
|
||||
}
|
||||
}, [latestBatchNotificationId]); // Only run when a NEW notification arrives
|
||||
|
||||
useEffect(() => {
|
||||
console.log('⚡ [Dashboard] deliveryNotifications useEffect triggered', {
|
||||
latestDeliveryNotificationId,
|
||||
prevValue: prevDeliveryNotificationsRef.current,
|
||||
hasChanged: latestDeliveryNotificationId !== prevDeliveryNotificationsRef.current,
|
||||
notificationCount: deliveryNotifications.length,
|
||||
firstNotification: deliveryNotifications[0],
|
||||
});
|
||||
|
||||
if (latestDeliveryNotificationId &&
|
||||
latestDeliveryNotificationId !== prevDeliveryNotificationsRef.current) {
|
||||
console.log('🔥 [Dashboard] NEW delivery notification detected, updating ref and refetching');
|
||||
prevDeliveryNotificationsRef.current = latestDeliveryNotificationId;
|
||||
const latest = deliveryNotifications[0];
|
||||
|
||||
if (['delivery_received', 'delivery_overdue'].includes(latest.event_type)) {
|
||||
console.log('🚀 [Dashboard] Triggering refetch for delivery event:', latest.event_type);
|
||||
refetchCallbacksRef.current.refetchExecutionProgress();
|
||||
refetchCallbacksRef.current.refetchHealth();
|
||||
} else {
|
||||
console.log('⏭️ [Dashboard] Skipping refetch - event type not relevant:', latest.event_type);
|
||||
}
|
||||
}
|
||||
}, [latestDeliveryNotificationId]); // Only run when a NEW notification arrives
|
||||
|
||||
useEffect(() => {
|
||||
console.log('⚡ [Dashboard] orchestrationNotifications useEffect triggered', {
|
||||
latestOrchestrationNotificationId,
|
||||
prevValue: prevOrchestrationNotificationsRef.current,
|
||||
hasChanged: latestOrchestrationNotificationId !== prevOrchestrationNotificationsRef.current,
|
||||
notificationCount: orchestrationNotifications.length,
|
||||
firstNotification: orchestrationNotifications[0],
|
||||
});
|
||||
|
||||
if (latestOrchestrationNotificationId &&
|
||||
latestOrchestrationNotificationId !== prevOrchestrationNotificationsRef.current) {
|
||||
console.log('🔥 [Dashboard] NEW orchestration notification detected, updating ref and refetching');
|
||||
prevOrchestrationNotificationsRef.current = latestOrchestrationNotificationId;
|
||||
const latest = orchestrationNotifications[0];
|
||||
|
||||
if (latest.event_type === 'orchestration_run_completed') {
|
||||
console.log('🚀 [Dashboard] Triggering refetch for orchestration event:', latest.event_type);
|
||||
refetchCallbacksRef.current.refetchOrchestration();
|
||||
refetchCallbacksRef.current.refetchActionQueue();
|
||||
} else {
|
||||
console.log('⏭️ [Dashboard] Skipping refetch - event type not relevant:', latest.event_type);
|
||||
}
|
||||
}
|
||||
}, [latestOrchestrationNotificationId]); // Only run when a NEW notification arrives
|
||||
// Enable SSE real-time state synchronization
|
||||
useDashboardRealtimeSync(tenantId);
|
||||
|
||||
// Mutations
|
||||
const approvePO = useApprovePurchaseOrder();
|
||||
const rejectPO = useRejectPurchaseOrder();
|
||||
const startBatch = useStartProductionBatch();
|
||||
const pauseBatch = usePauseProductionBatch();
|
||||
|
||||
// Handlers
|
||||
const handleApprove = async (actionId: string) => {
|
||||
const handleApprove = async (poId: string) => {
|
||||
try {
|
||||
await approvePO.mutateAsync({ tenantId, poId: actionId });
|
||||
// Refetch to update UI
|
||||
refetchActionQueue();
|
||||
refetchHealth();
|
||||
await approvePO.mutateAsync({ tenantId, poId });
|
||||
// SSE will handle refetch, but trigger immediate refetch for responsiveness
|
||||
refetchDashboard();
|
||||
} catch (error) {
|
||||
console.error('Error approving PO:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReject = async (actionId: string, reason: string) => {
|
||||
const handleReject = async (poId: string, reason: string) => {
|
||||
try {
|
||||
await rejectPO.mutateAsync({ tenantId, poId: actionId, reason });
|
||||
// Refetch to update UI
|
||||
refetchActionQueue();
|
||||
refetchHealth();
|
||||
await rejectPO.mutateAsync({ tenantId, poId, reason });
|
||||
refetchDashboard();
|
||||
} catch (error) {
|
||||
console.error('Error rejecting PO:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleViewDetails = (actionId: string) => {
|
||||
const handleViewDetails = (poId: string) => {
|
||||
// Open modal to show PO details in view mode
|
||||
setSelectedPOId(actionId);
|
||||
setSelectedPOId(poId);
|
||||
setPOModalMode('view');
|
||||
setIsPOModalOpen(true);
|
||||
};
|
||||
|
||||
const handleModify = (actionId: string) => {
|
||||
// Open modal to edit PO details
|
||||
setSelectedPOId(actionId);
|
||||
setPOModalMode('edit');
|
||||
setIsPOModalOpen(true);
|
||||
};
|
||||
|
||||
const handleStartBatch = async (batchId: string) => {
|
||||
try {
|
||||
await startBatch.mutateAsync({ tenantId, batchId });
|
||||
refetchTimeline();
|
||||
refetchHealth();
|
||||
refetchDashboard();
|
||||
} catch (error) {
|
||||
console.error('Error starting batch:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePauseBatch = async (batchId: string) => {
|
||||
try {
|
||||
await pauseBatch.mutateAsync({ tenantId, batchId });
|
||||
refetchTimeline();
|
||||
refetchHealth();
|
||||
} catch (error) {
|
||||
console.error('Error pausing batch:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate configuration sections for setup flow
|
||||
const setupSections = useMemo(() => {
|
||||
// Create safe fallbacks for icons to prevent React error #310
|
||||
@@ -514,19 +256,11 @@ export function BakeryDashboard() {
|
||||
};
|
||||
}, [setupSections, tenantId, loadingIngredients, loadingSuppliers, loadingRecipes, loadingQuality, setupProgressFromStorage]);
|
||||
|
||||
const handleRefreshAll = () => {
|
||||
refetchHealth();
|
||||
refetchOrchestration();
|
||||
refetchActionQueue();
|
||||
refetchExecutionProgress();
|
||||
refetchTimeline();
|
||||
refetchInsights();
|
||||
};
|
||||
|
||||
const handleAddWizardComplete = (itemType: ItemType, data?: any) => {
|
||||
console.log('Item created:', itemType, data);
|
||||
// Refetch relevant data based on what was added
|
||||
handleRefreshAll();
|
||||
// SSE events will handle most updates automatically, but we refetch here
|
||||
// to ensure immediate feedback after user actions
|
||||
refetchDashboard();
|
||||
};
|
||||
|
||||
// Keyboard shortcut for Quick Add (Cmd/Ctrl + K)
|
||||
@@ -600,14 +334,6 @@ export function BakeryDashboard() {
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={handleRefreshAll}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-lg font-semibold transition-colors duration-200 border border-[var(--border-primary)] bg-[var(--bg-primary)] text-[var(--text-secondary)]"
|
||||
>
|
||||
<RefreshCw className="w-5 h-5" />
|
||||
<span className="hidden sm:inline">{t('common:actions.refresh')}</span>
|
||||
</button>
|
||||
|
||||
{/* Unified Add Button with Keyboard Shortcut */}
|
||||
<button
|
||||
onClick={() => setIsAddWizardOpen(true)}
|
||||
@@ -632,8 +358,8 @@ export function BakeryDashboard() {
|
||||
</div>
|
||||
|
||||
{/* Setup Flow - Three States */}
|
||||
{loadingIngredients || loadingSuppliers || loadingRecipes || loadingQuality || !isReady ? (
|
||||
/* Loading state - only show spinner until first priority data (health) is ready */
|
||||
{loadingIngredients || loadingSuppliers || loadingRecipes || loadingQuality ? (
|
||||
/* Loading state - only show spinner until setup data is ready */
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2" style={{ borderColor: 'var(--color-primary)' }}></div>
|
||||
</div>
|
||||
@@ -653,103 +379,45 @@ export function BakeryDashboard() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Main Dashboard Layout */}
|
||||
{/* Main Dashboard Layout - 4 New Focused Blocks */}
|
||||
<div className="space-y-6">
|
||||
{/* SECTION 1: Glanceable Health Hero (Traffic Light) - PRIORITY 1 */}
|
||||
{/* BLOCK 1: System Status + AI Summary */}
|
||||
<div data-tour="dashboard-stats">
|
||||
{healthLoading ? (
|
||||
<div className="bg-[var(--bg-primary)] rounded-lg border border-[var(--border-primary)] p-6 animate-pulse">
|
||||
<div className="h-24 bg-[var(--bg-secondary)] rounded"></div>
|
||||
</div>
|
||||
) : (
|
||||
<GlanceableHealthHero
|
||||
healthStatus={healthStatus!}
|
||||
loading={false}
|
||||
urgentActionCount={actionQueue?.urgentCount || 0}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* SECTION 2: What Needs Your Attention (Unified Action Queue) - PRIORITY 2 */}
|
||||
<div data-tour="pending-po-approvals">
|
||||
{actionQueueLoading ? (
|
||||
<div className="bg-[var(--bg-primary)] rounded-lg border border-[var(--border-primary)] p-6 animate-pulse">
|
||||
<div className="space-y-4">
|
||||
<div className="h-8 bg-[var(--bg-secondary)] rounded w-1/3"></div>
|
||||
<div className="h-32 bg-[var(--bg-secondary)] rounded"></div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<UnifiedActionQueueCard
|
||||
actionQueue={actionQueue!}
|
||||
loading={false}
|
||||
tenantId={tenantId}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* SECTION 3: Execution Progress Tracker (Plan vs Actual) - PRIORITY 3 */}
|
||||
<div data-tour="execution-progress">
|
||||
{executionProgressLoading ? (
|
||||
<div className="bg-[var(--bg-primary)] rounded-lg border border-[var(--border-primary)] p-6 animate-pulse">
|
||||
<div className="space-y-4">
|
||||
<div className="h-8 bg-[var(--bg-secondary)] rounded w-1/2"></div>
|
||||
<div className="h-24 bg-[var(--bg-secondary)] rounded"></div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ExecutionProgressTracker
|
||||
progress={executionProgress}
|
||||
loading={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* SECTION 4: Intelligent System Summary - Unified AI Impact & Orchestration */}
|
||||
<div data-tour="intelligent-system-summary">
|
||||
<IntelligentSystemSummaryCard
|
||||
orchestrationSummary={orchestrationSummary!}
|
||||
orchestrationLoading={orchestrationLoading}
|
||||
onWorkflowComplete={handleRefreshAll}
|
||||
<SystemStatusBlock
|
||||
data={dashboardData}
|
||||
loading={dashboardLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* SECTION 6: Quick Action Links */}
|
||||
<div className="rounded-xl shadow-lg p-6 border border-[var(--border-primary)] bg-[var(--bg-primary)]">
|
||||
<h2 className="text-xl font-bold mb-4 text-[var(--text-primary)]">{t('dashboard:sections.quick_actions')}</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<button
|
||||
onClick={() => navigate('/app/operations/procurement')}
|
||||
className="flex items-center justify-between p-4 rounded-lg transition-colors duration-200 group bg-[var(--bg-tertiary)] border-l-4 border-l-[var(--color-info)]"
|
||||
>
|
||||
<span className="font-semibold text-[var(--text-primary)]">{t('dashboard:quick_actions.view_orders')}</span>
|
||||
<ExternalLink className="w-5 h-5 group-hover:translate-x-1 transition-transform duration-200 text-[var(--color-info)]" />
|
||||
</button>
|
||||
{/* BLOCK 2: Pending Purchases (PO Approvals) */}
|
||||
<div data-tour="pending-po-approvals">
|
||||
<PendingPurchasesBlock
|
||||
pendingPOs={dashboardData?.pendingPOs || []}
|
||||
loading={dashboardLoading}
|
||||
onApprove={handleApprove}
|
||||
onReject={handleReject}
|
||||
onViewDetails={handleViewDetails}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => navigate('/app/operations/production')}
|
||||
className="flex items-center justify-between p-4 rounded-lg transition-colors duration-200 group bg-[var(--bg-tertiary)] border-l-4 border-l-[var(--color-success)]"
|
||||
>
|
||||
<span className="font-semibold text-[var(--text-primary)]">{t('dashboard:quick_actions.view_production')}</span>
|
||||
<ExternalLink className="w-5 h-5 group-hover:translate-x-1 transition-transform duration-200 text-[var(--color-success)]" />
|
||||
</button>
|
||||
{/* BLOCK 3: Pending Deliveries (Overdue + Today) */}
|
||||
<div data-tour="pending-deliveries">
|
||||
<PendingDeliveriesBlock
|
||||
overdueDeliveries={dashboardData?.overdueDeliveries || []}
|
||||
pendingDeliveries={dashboardData?.pendingDeliveries || []}
|
||||
loading={dashboardLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => navigate('/app/database/inventory')}
|
||||
className="flex items-center justify-between p-4 rounded-lg transition-colors duration-200 group bg-[var(--bg-tertiary)] border-l-4 border-l-[var(--color-secondary)]"
|
||||
>
|
||||
<span className="font-semibold text-[var(--text-primary)]">{t('dashboard:quick_actions.view_inventory')}</span>
|
||||
<ExternalLink className="w-5 h-5 group-hover:translate-x-1 transition-transform duration-200 text-[var(--color-secondary)]" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => navigate('/app/database/suppliers')}
|
||||
className="flex items-center justify-between p-4 rounded-lg transition-colors duration-200 group bg-[var(--bg-tertiary)] border-l-4 border-l-[var(--color-warning)]"
|
||||
>
|
||||
<span className="font-semibold text-[var(--text-primary)]">{t('dashboard:quick_actions.view_suppliers')}</span>
|
||||
<ExternalLink className="w-5 h-5 group-hover:translate-x-1 transition-transform duration-200 text-[var(--color-warning)]" />
|
||||
</button>
|
||||
</div>
|
||||
{/* BLOCK 4: Production Status (Late/Running/Pending) */}
|
||||
<div data-tour="execution-progress">
|
||||
<ProductionStatusBlock
|
||||
lateToStartBatches={dashboardData?.lateToStartBatches || []}
|
||||
runningBatches={dashboardData?.runningBatches || []}
|
||||
pendingBatches={dashboardData?.pendingBatches || []}
|
||||
loading={dashboardLoading}
|
||||
onStartBatch={handleStartBatch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
@@ -777,7 +445,8 @@ export function BakeryDashboard() {
|
||||
setIsPOModalOpen(false);
|
||||
setSelectedPOId(null);
|
||||
setPOModalMode('view');
|
||||
handleRefreshAll();
|
||||
// SSE events will handle most updates automatically
|
||||
refetchDashboard();
|
||||
}}
|
||||
onApprove={handleApprove}
|
||||
onReject={handleReject}
|
||||
|
||||
Reference in New Issue
Block a user