demo seed change
This commit is contained in:
@@ -15,6 +15,7 @@ import * as orchestratorService from '../services/orchestrator';
|
||||
import { suppliersService } from '../services/suppliers';
|
||||
import { useBatchNotifications, useDeliveryNotifications, useOrchestrationNotifications } from '../../hooks/useEventNotifications';
|
||||
import { useSSEEvents } from '../../hooks/useSSE';
|
||||
import { parseISO } from 'date-fns';
|
||||
|
||||
// ============================================================
|
||||
// Types
|
||||
@@ -27,6 +28,7 @@ export interface DashboardData {
|
||||
productionBatches: any[];
|
||||
deliveries: any[];
|
||||
orchestrationSummary: OrchestrationSummary | null;
|
||||
aiInsights: any[]; // AI-generated insights for professional/enterprise tiers
|
||||
|
||||
// Computed/derived data
|
||||
preventedIssues: any[];
|
||||
@@ -70,7 +72,8 @@ export function useDashboardData(tenantId: string) {
|
||||
queryKey: ['dashboard-data', tenantId],
|
||||
queryFn: async () => {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const now = new Date();
|
||||
const now = new Date(); // Keep for local time display
|
||||
const nowUTC = new Date(); // UTC time for accurate comparison with API dates
|
||||
|
||||
// Parallel fetch ALL data needed by all 4 blocks (including suppliers for PO enrichment)
|
||||
const [alertsResponse, pendingPOs, productionResponse, deliveriesResponse, orchestration, suppliers] = await Promise.all([
|
||||
@@ -158,20 +161,20 @@ export function useDashboardData(tenantId: string) {
|
||||
|
||||
const overdueDeliveries = deliveries.filter((d: any) => {
|
||||
if (!isPending(d.status)) return false;
|
||||
const expectedDate = new Date(d.expected_delivery_date);
|
||||
return expectedDate < now;
|
||||
const expectedDate = parseISO(d.expected_delivery_date); // Proper UTC parsing
|
||||
return expectedDate < nowUTC;
|
||||
}).map((d: any) => ({
|
||||
...d,
|
||||
hoursOverdue: Math.ceil((now.getTime() - new Date(d.expected_delivery_date).getTime()) / (1000 * 60 * 60)),
|
||||
hoursOverdue: Math.ceil((nowUTC.getTime() - parseISO(d.expected_delivery_date).getTime()) / (1000 * 60 * 60)),
|
||||
}));
|
||||
|
||||
const pendingDeliveriesFiltered = deliveries.filter((d: any) => {
|
||||
if (!isPending(d.status)) return false;
|
||||
const expectedDate = new Date(d.expected_delivery_date);
|
||||
return expectedDate >= now;
|
||||
const expectedDate = parseISO(d.expected_delivery_date); // Proper UTC parsing
|
||||
return expectedDate >= nowUTC;
|
||||
}).map((d: any) => ({
|
||||
...d,
|
||||
hoursUntil: Math.ceil((new Date(d.expected_delivery_date).getTime() - now.getTime()) / (1000 * 60 * 60)),
|
||||
hoursUntil: Math.ceil((parseISO(d.expected_delivery_date).getTime() - nowUTC.getTime()) / (1000 * 60 * 60)),
|
||||
}));
|
||||
|
||||
// Filter production batches by status
|
||||
@@ -180,10 +183,10 @@ export function useDashboardData(tenantId: string) {
|
||||
if (status !== 'PENDING' && status !== 'SCHEDULED') return false;
|
||||
const plannedStart = b.planned_start_time;
|
||||
if (!plannedStart) return false;
|
||||
return new Date(plannedStart) < now;
|
||||
return parseISO(plannedStart) < nowUTC;
|
||||
}).map((b: any) => ({
|
||||
...b,
|
||||
hoursLate: Math.ceil((now.getTime() - new Date(b.planned_start_time).getTime()) / (1000 * 60 * 60)),
|
||||
hoursLate: Math.ceil((nowUTC.getTime() - parseISO(b.planned_start_time).getTime()) / (1000 * 60 * 60)),
|
||||
}));
|
||||
|
||||
const runningBatches = productionBatches.filter((b: any) =>
|
||||
@@ -195,7 +198,32 @@ export function useDashboardData(tenantId: string) {
|
||||
if (status !== 'PENDING' && status !== 'SCHEDULED') return false;
|
||||
const plannedStart = b.planned_start_time;
|
||||
if (!plannedStart) return true; // No planned start, count as pending
|
||||
return new Date(plannedStart) >= now;
|
||||
return parseISO(plannedStart) >= nowUTC;
|
||||
});
|
||||
|
||||
// Create set of batch IDs that we already show in the UI (late or running)
|
||||
const lateBatchIds = new Set(lateToStartBatches.map((b: any) => b.id));
|
||||
const runningBatchIds = new Set(runningBatches.map((b: any) => b.id));
|
||||
|
||||
// Filter alerts to exclude those for batches already shown in the UI
|
||||
// This prevents duplicate display: batch card + separate alert for the same batch
|
||||
const deduplicatedAlerts = alerts.filter((a: any) => {
|
||||
const eventType = a.event_type || '';
|
||||
const batchId = a.event_metadata?.batch_id || a.entity_links?.production_batch;
|
||||
|
||||
if (!batchId) return true; // Keep alerts not related to batches
|
||||
|
||||
// Filter out batch_start_delayed alerts for batches shown in "late to start" section
|
||||
if (eventType.includes('batch_start_delayed') && lateBatchIds.has(batchId)) {
|
||||
return false; // Already shown as late batch
|
||||
}
|
||||
|
||||
// Filter out production_delay alerts for batches shown in "running" section
|
||||
if (eventType.includes('production_delay') && runningBatchIds.has(batchId)) {
|
||||
return false; // Already shown as running batch (with progress bar showing delay)
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// Build orchestration summary
|
||||
@@ -218,11 +246,12 @@ export function useDashboardData(tenantId: string) {
|
||||
|
||||
return {
|
||||
// Raw data
|
||||
alerts,
|
||||
alerts: deduplicatedAlerts,
|
||||
pendingPOs: enrichedPendingPOs,
|
||||
productionBatches,
|
||||
deliveries,
|
||||
orchestrationSummary,
|
||||
aiInsights: [], // AI-generated insights for professional/enterprise tiers
|
||||
|
||||
// Computed
|
||||
preventedIssues,
|
||||
@@ -283,7 +312,7 @@ export function useDashboardRealtimeSync(tenantId: string) {
|
||||
if (deliveryNotifications.length === 0 || !tenantId) return;
|
||||
|
||||
const latest = deliveryNotifications[0];
|
||||
if (['delivery_received', 'delivery_overdue'].includes(latest.event_type)) {
|
||||
if (['delivery_received', 'delivery_overdue', 'delivery_arriving_soon', 'stock_receipt_incomplete'].includes(latest.event_type)) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['dashboard-data', tenantId],
|
||||
refetchType: 'active',
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ProcurementService } from '../services/procurement-service';
|
||||
import * as orchestratorService from '../services/orchestrator'; // Only for orchestration run info
|
||||
import { ProductionStatus } from '../types/production';
|
||||
import { apiClient } from '../client';
|
||||
import { parseISO } from 'date-fns';
|
||||
|
||||
// ============================================================
|
||||
// Types
|
||||
@@ -327,7 +328,8 @@ export function useSharedDashboardData(tenantId: string) {
|
||||
]);
|
||||
|
||||
// Calculate late-to-start batches (batches that should have started but haven't)
|
||||
const now = new Date();
|
||||
const now = new Date(); // Local time for display
|
||||
const nowUTC = new Date(); // UTC time for accurate comparison with API dates
|
||||
const allBatches = prodBatches?.batches || [];
|
||||
const lateToStart = allBatches.filter((b: any) => {
|
||||
// Only check PENDING or SCHEDULED batches (not started yet)
|
||||
@@ -338,16 +340,18 @@ export function useSharedDashboardData(tenantId: string) {
|
||||
if (!plannedStart) return false;
|
||||
|
||||
// Check if planned start time is in the past (late to start)
|
||||
return new Date(plannedStart) < now;
|
||||
return parseISO(plannedStart) < nowUTC;
|
||||
});
|
||||
|
||||
// Calculate overdue deliveries (pending deliveries with past due date)
|
||||
const allDelivs = deliveries?.deliveries || [];
|
||||
const isPending = (s: string) =>
|
||||
s === 'PENDING' || s === 'sent_to_supplier' || s === 'confirmed';
|
||||
const overdueDelivs = allDelivs.filter((d: any) =>
|
||||
isPending(d.status) && new Date(d.expected_delivery_date) < now
|
||||
);
|
||||
// FIX: Use UTC timestamps for consistent time zone handling
|
||||
const overdueDelivs = allDelivs.filter((d: any) => {
|
||||
const expectedDate = parseISO(d.expected_delivery_date); // Proper UTC parsing
|
||||
return isPending(d.status) && expectedDate.getTime() < nowUTC.getTime();
|
||||
});
|
||||
|
||||
return {
|
||||
overdueDeliveries: overdueDelivs.length,
|
||||
@@ -1019,7 +1023,7 @@ export function useExecutionProgress(tenantId: string) {
|
||||
|
||||
if (!aTime || !bTime) return 0;
|
||||
|
||||
return new Date(aTime).getTime() - new Date(bTime).getTime();
|
||||
return parseISO(aTime).getTime() - parseISO(bTime).getTime();
|
||||
});
|
||||
|
||||
const nextBatchDetail = sortedPendingBatches.length > 0 ? {
|
||||
@@ -1065,10 +1069,12 @@ export function useExecutionProgress(tenantId: string) {
|
||||
const pendingDeliveriesData = allDeliveries.filter((d: any) => isPending(d.status));
|
||||
|
||||
// Identify overdue deliveries (pending deliveries with past due date)
|
||||
// FIX: Use UTC timestamps to avoid time zone issues
|
||||
const overdueDeliveriesData = pendingDeliveriesData.filter((d: any) => {
|
||||
const expectedDate = new Date(d.expected_delivery_date);
|
||||
const now = new Date();
|
||||
return expectedDate < now;
|
||||
const expectedDate = parseISO(d.expected_delivery_date); // Proper UTC parsing
|
||||
const nowUTC = new Date(); // UTC time for accurate comparison
|
||||
// Compare UTC timestamps instead of local time
|
||||
return expectedDate.getTime() < nowUTC.getTime();
|
||||
});
|
||||
|
||||
// Calculate counts
|
||||
@@ -1080,17 +1086,17 @@ export function useExecutionProgress(tenantId: string) {
|
||||
// Convert raw delivery data to the expected format for the UI
|
||||
const processedDeliveries = allDeliveries.map((d: any) => {
|
||||
const itemCount = d.line_items?.length || 0;
|
||||
const expectedDate = new Date(d.expected_delivery_date);
|
||||
const now = new Date();
|
||||
const expectedDate = parseISO(d.expected_delivery_date); // Proper UTC parsing
|
||||
const nowUTC = new Date(); // UTC time for accurate comparison
|
||||
let hoursUntil = 0;
|
||||
let hoursOverdue = 0;
|
||||
|
||||
if (expectedDate < now) {
|
||||
if (expectedDate < nowUTC) {
|
||||
// Calculate hours overdue
|
||||
hoursOverdue = Math.ceil((now.getTime() - expectedDate.getTime()) / (1000 * 60 * 60));
|
||||
hoursOverdue = Math.ceil((nowUTC.getTime() - expectedDate.getTime()) / (1000 * 60 * 60));
|
||||
} else {
|
||||
// Calculate hours until delivery
|
||||
hoursUntil = Math.ceil((expectedDate.getTime() - now.getTime()) / (1000 * 60 * 60));
|
||||
hoursUntil = Math.ceil((expectedDate.getTime() - nowUTC.getTime()) / (1000 * 60 * 60));
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -1110,9 +1116,18 @@ export function useExecutionProgress(tenantId: string) {
|
||||
});
|
||||
|
||||
// Separate into specific lists for the UI
|
||||
// FIX: Use UTC timestamps for consistent time zone handling
|
||||
const receivedDeliveriesList = processedDeliveries.filter((d: any) => isDelivered(d.status));
|
||||
const pendingDeliveriesList = processedDeliveries.filter((d: any) => isPending(d.status) && new Date(d.expectedDeliveryDate) >= new Date());
|
||||
const overdueDeliveriesList = processedDeliveries.filter((d: any) => isPending(d.status) && new Date(d.expectedDeliveryDate) < new Date());
|
||||
const pendingDeliveriesList = processedDeliveries.filter((d: any) => {
|
||||
const expectedDate = new Date(d.expectedDeliveryDate);
|
||||
const now = new Date();
|
||||
return isPending(d.status) && expectedDate.getTime() >= now.getTime();
|
||||
});
|
||||
const overdueDeliveriesList = processedDeliveries.filter((d: any) => {
|
||||
const expectedDate = new Date(d.expectedDeliveryDate);
|
||||
const now = new Date();
|
||||
return isPending(d.status) && expectedDate.getTime() < now.getTime();
|
||||
});
|
||||
|
||||
// Determine delivery status
|
||||
let deliveryStatus: 'no_deliveries' | 'completed' | 'on_track' | 'at_risk' = 'no_deliveries';
|
||||
|
||||
@@ -142,6 +142,7 @@ export interface ProductionBatchResponse {
|
||||
quality_notes: string | null;
|
||||
delay_reason: string | null;
|
||||
cancellation_reason: string | null;
|
||||
reasoning_data?: Record<string, any> | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
completed_at: string | null;
|
||||
|
||||
Reference in New Issue
Block a user