New alert service
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user