New alert service

This commit is contained in:
Urtzi Alfaro
2025-12-05 20:07:01 +01:00
parent 1fe3a73549
commit 667e6e0404
393 changed files with 26002 additions and 61033 deletions

View File

@@ -25,12 +25,13 @@ import {
useOrchestrationSummary,
useUnifiedActionQueue,
useProductionTimeline,
useInsights,
useApprovePurchaseOrder,
useStartProductionBatch,
usePauseProductionBatch,
useExecutionProgress,
} from '../../api/hooks/newDashboard';
useDashboardRealtime, // PHASE 3: SSE state sync
useProgressiveDashboard, // PHASE 4: Progressive loading
} from '../../api/hooks/useProfessionalDashboard';
import { useRejectPurchaseOrder } from '../../api/hooks/purchase-orders';
import { useIngredients } from '../../api/hooks/inventory';
import { useSuppliers } from '../../api/hooks/suppliers';
@@ -54,7 +55,14 @@ import {
useOrchestrationNotifications,
} from '../../hooks';
export function NewDashboardPage() {
// Import Enterprise Dashboard
import EnterpriseDashboardPage from './EnterpriseDashboardPage';
import { useSubscription } from '../../api/hooks/subscription';
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();
@@ -75,48 +83,107 @@ export function NewDashboardPage() {
const [poModalMode, setPOModalMode] = useState<'view' | 'edit'>('view');
// Setup Progress Data
const { data: ingredients = [], isLoading: loadingIngredients } = useIngredients(tenantId, {}, { enabled: !!tenantId });
const { data: suppliers = [], isLoading: loadingSuppliers } = useSuppliers(tenantId, { enabled: !!tenantId });
const { data: recipes = [], isLoading: loadingRecipes } = useRecipes(tenantId, { enabled: !!tenantId });
const { data: qualityData, isLoading: loadingQuality } = useQualityTemplates(tenantId, { enabled: !!tenantId });
// 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
const setupProgressFromStorage = useMemo(() => {
try {
const cached = localStorage.getItem(`setup_progress_${tenantId}`);
return cached ? parseInt(cached, 10) : 0;
} catch {
return 0;
}
}, [tenantId]);
// Always fetch the actual data to determine true progress
const { data: ingredients = [], isLoading: loadingIngredients } = useIngredients(
tenantId,
{},
{ enabled: !!tenantId }
);
const { data: suppliers = [], isLoading: loadingSuppliers } = useSuppliers(
tenantId,
{},
{ enabled: !!tenantId }
);
const { data: recipes = [], isLoading: loadingRecipes } = useRecipes(
tenantId,
{},
{ enabled: !!tenantId }
);
const { data: qualityData, isLoading: loadingQuality } = useQualityTemplates(
tenantId,
{},
{ enabled: !!tenantId }
);
const qualityTemplates = Array.isArray(qualityData?.templates) ? qualityData.templates : [];
// Data fetching
// PHASE 4: Progressive data loading for perceived performance boost
const {
data: healthStatus,
isLoading: healthLoading,
refetch: refetchHealth,
} = useBakeryHealthStatus(tenantId);
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);
// Additional hooks not part of progressive loading
const {
data: orchestrationSummary,
isLoading: orchestrationLoading,
refetch: refetchOrchestration,
} = useOrchestrationSummary(tenantId);
const {
data: actionQueue,
isLoading: actionQueueLoading,
refetch: refetchActionQueue,
} = useUnifiedActionQueue(tenantId);
const {
data: executionProgress,
isLoading: executionProgressLoading,
refetch: refetchExecutionProgress,
} = useExecutionProgress(tenantId);
const {
data: productionTimeline,
isLoading: timelineLoading,
refetch: refetchTimeline,
} = useProductionTimeline(tenantId);
const {
data: insights,
isLoading: insightsLoading,
refetch: refetchInsights,
} = useInsights(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();
@@ -212,7 +279,7 @@ export function NewDashboardPage() {
});
if (latestBatchNotificationId &&
latestBatchNotificationId !== prevBatchNotificationsRef.current) {
latestBatchNotificationId !== prevBatchNotificationsRef.current) {
console.log('🔥 [Dashboard] NEW batch notification detected, updating ref and refetching');
prevBatchNotificationsRef.current = latestBatchNotificationId;
const latest = batchNotifications[0];
@@ -237,7 +304,7 @@ export function NewDashboardPage() {
});
if (latestDeliveryNotificationId &&
latestDeliveryNotificationId !== prevDeliveryNotificationsRef.current) {
latestDeliveryNotificationId !== prevDeliveryNotificationsRef.current) {
console.log('🔥 [Dashboard] NEW delivery notification detected, updating ref and refetching');
prevDeliveryNotificationsRef.current = latestDeliveryNotificationId;
const latest = deliveryNotifications[0];
@@ -262,7 +329,7 @@ export function NewDashboardPage() {
});
if (latestOrchestrationNotificationId &&
latestOrchestrationNotificationId !== prevOrchestrationNotificationsRef.current) {
latestOrchestrationNotificationId !== prevOrchestrationNotificationsRef.current) {
console.log('🔥 [Dashboard] NEW orchestration notification detected, updating ref and refetching');
prevOrchestrationNotificationsRef.current = latestOrchestrationNotificationId;
const latest = orchestrationNotifications[0];
@@ -401,6 +468,17 @@ export function NewDashboardPage() {
// Calculate overall progress
const { completedSections, totalSections, progressPercentage, criticalMissing, recommendedMissing } = useMemo(() => {
// If data is still loading, use stored value as fallback to prevent flickering
if (loadingIngredients || loadingSuppliers || loadingRecipes || loadingQuality) {
return {
completedSections: 0,
totalSections: 4, // 4 required sections
progressPercentage: setupProgressFromStorage, // Use stored value during loading
criticalMissing: [],
recommendedMissing: [],
};
}
// Guard against undefined or invalid setupSections
if (!setupSections || !Array.isArray(setupSections) || setupSections.length === 0) {
return {
@@ -420,6 +498,13 @@ export function NewDashboardPage() {
const critical = setupSections.filter(s => !s.isComplete && s.id !== 'quality');
const recommended = setupSections.filter(s => s.count < s.recommended);
// PHASE 1 OPTIMIZATION: Cache progress to localStorage for next page load
try {
localStorage.setItem(`setup_progress_${tenantId}`, percentage.toString());
} catch {
// Ignore storage errors
}
return {
completedSections: completed,
totalSections: total,
@@ -427,7 +512,7 @@ export function NewDashboardPage() {
criticalMissing: critical,
recommendedMissing: recommended,
};
}, [setupSections]);
}, [setupSections, tenantId, loadingIngredients, loadingSuppliers, loadingRecipes, loadingQuality, setupProgressFromStorage]);
const handleRefreshAll = () => {
refetchHealth();
@@ -547,8 +632,8 @@ export function NewDashboardPage() {
</div>
{/* Setup Flow - Three States */}
{loadingIngredients || loadingSuppliers || loadingRecipes || loadingQuality ? (
/* Loading state */
{loadingIngredients || loadingSuppliers || loadingRecipes || loadingQuality || !isReady ? (
/* Loading state - only show spinner until first priority data (health) 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>
@@ -570,36 +655,60 @@ export function NewDashboardPage() {
{/* Main Dashboard Layout */}
<div className="space-y-6">
{/* SECTION 1: Glanceable Health Hero (Traffic Light) */}
{/* SECTION 1: Glanceable Health Hero (Traffic Light) - PRIORITY 1 */}
<div data-tour="dashboard-stats">
<GlanceableHealthHero
healthStatus={healthStatus}
loading={healthLoading}
urgentActionCount={actionQueue?.urgentCount || 0}
/>
{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) */}
{/* SECTION 2: What Needs Your Attention (Unified Action Queue) - PRIORITY 2 */}
<div data-tour="pending-po-approvals">
<UnifiedActionQueueCard
actionQueue={actionQueue}
loading={actionQueueLoading}
tenantId={tenantId}
/>
{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) */}
{/* SECTION 3: Execution Progress Tracker (Plan vs Actual) - PRIORITY 3 */}
<div data-tour="execution-progress">
<ExecutionProgressTracker
progress={executionProgress}
loading={executionProgressLoading}
/>
{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}
orchestrationSummary={orchestrationSummary!}
orchestrationLoading={orchestrationLoading}
onWorkflowComplete={handleRefreshAll}
/>
@@ -679,4 +788,30 @@ export function NewDashboardPage() {
);
}
export default NewDashboardPage;
/**
* Main Dashboard Page
* Conditionally renders either the Enterprise Dashboard or the Bakery Dashboard
* based on the user's subscription tier.
*/
export function DashboardPage() {
const { subscriptionInfo } = useSubscription();
const { currentTenant } = useTenant();
const { plan, loading } = subscriptionInfo;
const tenantId = currentTenant?.id;
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-b-2" style={{ borderColor: 'var(--color-primary)' }}></div>
</div>
);
}
if (plan === SUBSCRIPTION_TIERS.ENTERPRISE) {
return <EnterpriseDashboardPage tenantId={tenantId} />;
}
return <BakeryDashboard />;
}
export default DashboardPage;