demo seed change

This commit is contained in:
Urtzi Alfaro
2025-12-13 23:57:54 +01:00
parent f3688dfb04
commit ff830a3415
299 changed files with 20328 additions and 19485 deletions

View File

@@ -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',

View File

@@ -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';

View File

@@ -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;