From e902419b6e8bd0eb8974b482daef1c212df31ca4 Mon Sep 17 00:00:00 2001 From: Urtzi Alfaro Date: Thu, 27 Nov 2025 15:52:40 +0100 Subject: [PATCH] New alert system and panel de control page --- Tiltfile | 43 +- frontend/.dockerignore | 41 + frontend/Dockerfile.kubernetes | 41 +- frontend/Dockerfile.kubernetes.debug | 89 ++ frontend/nginx-main.conf | 12 + frontend/src/api/hooks/newDashboard.ts | 104 +- .../components/dashboard/ActionQueueCard.tsx | 665 ----------- .../dashboard/CollapsibleSetupBanner.tsx | 230 ++++ .../dashboard/GlanceableHealthHero.tsx | 371 ++++++ .../components/dashboard/HealthStatusCard.tsx | 221 ---- .../src/components/dashboard/InsightsGrid.tsx | 129 -- .../IntelligentSystemSummaryCard.tsx | 432 +++++++ .../dashboard/OrchestrationSummaryCard.tsx | 316 ----- .../dashboard/ProductionTimelineCard.tsx | 324 ----- .../dashboard/SetupWizardBlocker.tsx | 237 ++++ .../dashboard/StockReceiptModal.tsx | 677 +++++++++++ .../dashboard/UnifiedActionQueueCard.tsx | 111 +- frontend/src/components/dashboard/index.ts | 17 +- .../domain/dashboard/AlertBulkActions.tsx | 126 -- .../components/domain/dashboard/AlertCard.tsx | 446 ------- .../domain/dashboard/AlertContextActions.tsx | 49 - .../domain/dashboard/AlertFilters.tsx | 306 ----- .../domain/dashboard/AlertGroupHeader.tsx | 84 -- .../domain/dashboard/AlertSnoozeMenu.tsx | 118 -- .../domain/dashboard/AlertTrends.tsx | 179 --- .../AutoActionCountdownComponent.tsx | 286 +++++ .../dashboard/ConfigurationProgressWidget.tsx | 298 ----- .../domain/dashboard/PriorityBadge.tsx | 83 ++ .../dashboard/PriorityScoreExplainerModal.tsx | 358 ++++++ .../domain/dashboard/RealTimeAlerts.tsx | 539 --------- .../SmartActionConsequencePreview.tsx | 284 +++++ .../dashboard/TrendVisualizationComponent.tsx | 189 +++ .../layout/DemoBanner/DemoBanner.tsx | 14 +- .../src/components/layout/Header/Header.tsx | 122 +- .../src/components/layout/Sidebar/Sidebar.tsx | 2 + .../src/components/ui/KeyValueEditor/index.ts | 2 +- .../NotificationPanel/NotificationPanel.tsx | 369 +++--- frontend/src/contexts/SSEContext.tsx | 181 +-- .../demo-onboarding/utils/tour-state.ts | 19 +- frontend/src/hooks/index.ts | 28 +- frontend/src/hooks/useAlertActions.ts | 71 -- frontend/src/hooks/useAlertAnalytics.ts | 181 --- frontend/src/hooks/useAlertFilters.ts | 112 -- frontend/src/hooks/useAlertGrouping.ts | 102 -- frontend/src/hooks/useEventNotifications.ts | 318 +++++ frontend/src/hooks/useNotifications.ts | 171 ++- frontend/src/hooks/useRecommendations.ts | 230 ++++ frontend/src/hooks/useSSE.ts | 238 ++++ frontend/src/locales/en/alerts.json | 9 +- frontend/src/locales/en/common.json | 16 +- frontend/src/locales/en/dashboard.json | 91 +- frontend/src/locales/en/inventory.json | 31 + frontend/src/locales/en/reasoning.json | 4 + frontend/src/locales/es/alerts.json | 109 +- frontend/src/locales/es/common.json | 12 +- frontend/src/locales/es/dashboard.json | 123 +- frontend/src/locales/es/reasoning.json | 4 + frontend/src/locales/eu/alerts.json | 4 +- frontend/src/locales/eu/common.json | 12 +- frontend/src/locales/eu/dashboard.json | 91 +- frontend/src/locales/eu/reasoning.json | 4 + frontend/src/locales/index.ts | 8 +- frontend/src/pages/app/CommunicationsPage.tsx | 14 + .../src/pages/app/DashboardPage.legacy.tsx | 611 ---------- frontend/src/pages/app/DashboardPage.tsx | 177 ++- frontend/src/pages/public/DemoPage.tsx | 42 +- frontend/src/router/AppRouter.tsx | 4 +- frontend/src/router/routes.config.ts | 3 +- frontend/src/types/alerts.ts | 219 ++++ frontend/src/types/events.ts | 369 ++++++ frontend/src/utils/alertHelpers.ts | 109 +- frontend/src/utils/alertI18n.ts | 152 +++ frontend/vite.config.ts | 115 +- gateway/Dockerfile | 9 + gateway/app/main.py | 311 ++++- gateway/app/routes/tenant.py | 7 + .../components/frontend/frontend-service.yaml | 4 +- infrastructure/kubernetes/base/configmap.yaml | 27 + .../alert-priority-recalculation-cronjob.yaml | 120 ++ .../cronjobs/delivery-tracking-cronjob.yaml | 176 +++ .../base/jobs/demo-seed-alerts-job.yaml | 67 ++ .../kubernetes/base/kustomization.yaml | 3 + .../overlays/dev/kustomization.yaml | 8 +- kind-config.yaml | 6 + scripts/seed_all_demo_data.sh | 5 + services/alert_processor/app/api/__init__.py | 3 +- services/alert_processor/app/api/alerts.py | 339 +++++- services/alert_processor/app/api/analytics.py | 160 +++ .../alert_processor/app/api/internal_demo.py | 305 +++++ services/alert_processor/app/api_server.py | 5 +- services/alert_processor/app/config.py | 59 +- services/alert_processor/app/dependencies.py | 56 + services/alert_processor/app/jobs/__init__.py | 12 + services/alert_processor/app/jobs/__main__.py | 44 + .../app/jobs/priority_recalculation.py | 337 ++++++ services/alert_processor/app/main.py | 396 +++--- .../alert_processor/app/models/__init__.py | 23 +- services/alert_processor/app/models/alerts.py | 90 -- services/alert_processor/app/models/events.py | 402 +++++++ .../app/repositories/alerts_repository.py | 61 +- .../app/repositories/analytics_repository.py | 154 ++- .../app/services/enrichment/__init__.py | 21 + .../app/services/enrichment/alert_grouping.py | 163 +++ .../services/enrichment/context_enrichment.py | 1061 +++++++++++++++++ .../app/services/enrichment/email_digest.py | 239 ++++ .../services/enrichment/enrichment_router.py | 391 ++++++ .../enrichment/orchestrator_client.py | 102 ++ .../services/enrichment/priority_scoring.py | 415 +++++++ .../enrichment/timing_intelligence.py | 140 +++ .../services/enrichment/trend_detection.py | 104 ++ .../app/services/redis_publisher.py | 228 ++++ .../app/services/tenant_deletion_service.py | 20 +- ...d7a76c1b10_initial_schema_20251015_1230.py | 100 -- .../20251019_1430_add_alert_interactions.py | 51 - .../20251125_unified_initial_schema.py | 275 +++++ .../scripts/demo/seed_demo_alerts.py | 321 +++++ services/auth/Dockerfile | 8 +- .../demo_session/app/api/demo_operations.py | 62 + .../app/services/clone_orchestrator.py | 6 + services/demo_session/scripts/README.md | 440 +++++++ .../scripts/seed_dashboard_comprehensive.py | 722 +++++++++++ .../scripts/seed_enriched_alert_demo.py | 373 ++++++ services/forecasting/app/api/internal_demo.py | 58 +- .../app/services/forecasting_alert_service.py | 1 - .../forecasting_recommendation_service.py | 359 ++++++ .../scripts/demo/seed_demo_forecasts.py | 26 +- services/inventory/app/api/internal_demo.py | 40 +- services/inventory/app/api/stock_receipts.py | 459 +++++++ services/inventory/app/main.py | 2 +- services/inventory/app/models/__init__.py | 12 + .../inventory/app/models/stock_receipt.py | 233 ++++ .../app/services/inventory_alert_service.py | 72 +- .../inventory_notification_service.py | 246 ++++ ...51029_1400_add_local_production_support.py | 77 -- ...0251108_1200_make_stock_fields_nullable.py | 84 -- ....py => 20251123_unified_initial_schema.py} | 153 ++- .../inventory/scripts/demo/seed_demo_stock.py | 16 +- services/orchestrator/app/api/dashboard.py | 190 ++- services/orchestrator/app/api/internal.py | 181 +++ .../orchestrator/app/api/internal_demo.py | 85 +- services/orchestrator/app/core/config.py | 9 +- services/orchestrator/app/main.py | 2 + .../app/services/delivery_tracking_service.py | 420 +++++++ .../orchestration_notification_service.py | 275 +++++ .../app/services/orchestrator_service.py | 108 +- services/orchestrator/app/utils/cache.py | 66 +- .../demo/seed_demo_orchestration_runs.py | 117 +- .../procurement_notification_service.py | 14 +- .../scripts/demo/seed_demo_customers.py | 7 +- .../orders/scripts/demo/seed_demo_orders.py | 7 +- services/pos/app/api/internal_demo.py | 33 +- .../pos/scripts/demo/seed_demo_pos_configs.py | 24 +- services/procurement/app/api/internal_demo.py | 125 ++ .../procurement/app/models/purchase_order.py | 1 + .../app/services/procurement_event_service.py | 395 ++++++ .../app/services/purchase_order_service.py | 88 ++ .../versions/001_unified_initial_schema.py | 1 + .../demo/seed_demo_procurement_plans.py | 7 +- .../scripts/demo/seed_demo_purchase_orders.py | 62 +- .../scripts/emit_pending_po_alerts.py | 174 +++ .../app/services/production_alert_service.py | 176 ++- .../production_notification_service.py | 307 +++++ services/production/migrate_to_raw_alerts.py | 250 ++++ .../scripts/demo/lotes_produccion_es.json | 99 ++ .../scripts/demo/seed_demo_batches.py | 3 +- .../scripts/demo/seed_demo_equipment.py | 7 +- .../demo/seed_demo_quality_templates.py | 7 +- services/recipes/app/api/internal_demo.py | 86 +- services/sales/app/api/internal_demo.py | 47 +- shared/alerts/base_service.py | 82 +- shared/alerts/context_templates.py | 948 +++++++++++++++ shared/alerts/templates.py | 276 ----- shared/clients/alerts_client.py | 86 ++ shared/config/rabbitmq_config.py | 188 ++- shared/schemas/alert_types.py | 276 +++++ shared/schemas/event_classification.py | 343 ++++++ shared/utils/demo_dates.py | 4 +- test_i18n_parameters.py | 183 +++ 178 files changed, 20982 insertions(+), 6944 deletions(-) create mode 100644 frontend/.dockerignore create mode 100644 frontend/Dockerfile.kubernetes.debug create mode 100644 frontend/nginx-main.conf delete mode 100644 frontend/src/components/dashboard/ActionQueueCard.tsx create mode 100644 frontend/src/components/dashboard/CollapsibleSetupBanner.tsx create mode 100644 frontend/src/components/dashboard/GlanceableHealthHero.tsx delete mode 100644 frontend/src/components/dashboard/HealthStatusCard.tsx delete mode 100644 frontend/src/components/dashboard/InsightsGrid.tsx create mode 100644 frontend/src/components/dashboard/IntelligentSystemSummaryCard.tsx delete mode 100644 frontend/src/components/dashboard/OrchestrationSummaryCard.tsx delete mode 100644 frontend/src/components/dashboard/ProductionTimelineCard.tsx create mode 100644 frontend/src/components/dashboard/SetupWizardBlocker.tsx create mode 100644 frontend/src/components/dashboard/StockReceiptModal.tsx delete mode 100644 frontend/src/components/domain/dashboard/AlertBulkActions.tsx delete mode 100644 frontend/src/components/domain/dashboard/AlertCard.tsx delete mode 100644 frontend/src/components/domain/dashboard/AlertContextActions.tsx delete mode 100644 frontend/src/components/domain/dashboard/AlertFilters.tsx delete mode 100644 frontend/src/components/domain/dashboard/AlertGroupHeader.tsx delete mode 100644 frontend/src/components/domain/dashboard/AlertSnoozeMenu.tsx delete mode 100644 frontend/src/components/domain/dashboard/AlertTrends.tsx create mode 100644 frontend/src/components/domain/dashboard/AutoActionCountdownComponent.tsx delete mode 100644 frontend/src/components/domain/dashboard/ConfigurationProgressWidget.tsx create mode 100644 frontend/src/components/domain/dashboard/PriorityBadge.tsx create mode 100644 frontend/src/components/domain/dashboard/PriorityScoreExplainerModal.tsx delete mode 100644 frontend/src/components/domain/dashboard/RealTimeAlerts.tsx create mode 100644 frontend/src/components/domain/dashboard/SmartActionConsequencePreview.tsx create mode 100644 frontend/src/components/domain/dashboard/TrendVisualizationComponent.tsx delete mode 100644 frontend/src/hooks/useAlertActions.ts delete mode 100644 frontend/src/hooks/useAlertAnalytics.ts delete mode 100644 frontend/src/hooks/useAlertFilters.ts delete mode 100644 frontend/src/hooks/useAlertGrouping.ts create mode 100644 frontend/src/hooks/useEventNotifications.ts create mode 100644 frontend/src/hooks/useRecommendations.ts create mode 100644 frontend/src/hooks/useSSE.ts create mode 100644 frontend/src/pages/app/CommunicationsPage.tsx delete mode 100644 frontend/src/pages/app/DashboardPage.legacy.tsx create mode 100644 frontend/src/types/alerts.ts create mode 100644 frontend/src/types/events.ts create mode 100644 frontend/src/utils/alertI18n.ts create mode 100644 infrastructure/kubernetes/base/cronjobs/alert-priority-recalculation-cronjob.yaml create mode 100644 infrastructure/kubernetes/base/cronjobs/delivery-tracking-cronjob.yaml create mode 100644 infrastructure/kubernetes/base/jobs/demo-seed-alerts-job.yaml create mode 100644 services/alert_processor/app/api/internal_demo.py create mode 100644 services/alert_processor/app/dependencies.py create mode 100644 services/alert_processor/app/jobs/__init__.py create mode 100644 services/alert_processor/app/jobs/__main__.py create mode 100644 services/alert_processor/app/jobs/priority_recalculation.py delete mode 100644 services/alert_processor/app/models/alerts.py create mode 100644 services/alert_processor/app/models/events.py create mode 100644 services/alert_processor/app/services/enrichment/__init__.py create mode 100644 services/alert_processor/app/services/enrichment/alert_grouping.py create mode 100644 services/alert_processor/app/services/enrichment/context_enrichment.py create mode 100644 services/alert_processor/app/services/enrichment/email_digest.py create mode 100644 services/alert_processor/app/services/enrichment/enrichment_router.py create mode 100644 services/alert_processor/app/services/enrichment/orchestrator_client.py create mode 100644 services/alert_processor/app/services/enrichment/priority_scoring.py create mode 100644 services/alert_processor/app/services/enrichment/timing_intelligence.py create mode 100644 services/alert_processor/app/services/enrichment/trend_detection.py create mode 100644 services/alert_processor/app/services/redis_publisher.py delete mode 100644 services/alert_processor/migrations/versions/20251015_1230_5ad7a76c1b10_initial_schema_20251015_1230.py delete mode 100644 services/alert_processor/migrations/versions/20251019_1430_add_alert_interactions.py create mode 100644 services/alert_processor/migrations/versions/20251125_unified_initial_schema.py create mode 100644 services/alert_processor/scripts/demo/seed_demo_alerts.py create mode 100644 services/demo_session/scripts/README.md create mode 100755 services/demo_session/scripts/seed_dashboard_comprehensive.py create mode 100644 services/demo_session/scripts/seed_enriched_alert_demo.py create mode 100644 services/forecasting/app/services/forecasting_recommendation_service.py create mode 100644 services/inventory/app/api/stock_receipts.py create mode 100644 services/inventory/app/models/stock_receipt.py create mode 100644 services/inventory/app/services/inventory_notification_service.py delete mode 100644 services/inventory/migrations/versions/20251029_1400_add_local_production_support.py delete mode 100644 services/inventory/migrations/versions/20251108_1200_make_stock_fields_nullable.py rename services/inventory/migrations/versions/{20251015_1229_e7fcea67bf4e_initial_schema_20251015_1229.py => 20251123_unified_initial_schema.py} (82%) create mode 100644 services/orchestrator/app/api/internal.py create mode 100644 services/orchestrator/app/services/delivery_tracking_service.py create mode 100644 services/orchestrator/app/services/orchestration_notification_service.py create mode 100644 services/procurement/app/services/procurement_event_service.py create mode 100644 services/procurement/scripts/emit_pending_po_alerts.py create mode 100644 services/production/app/services/production_notification_service.py create mode 100644 services/production/migrate_to_raw_alerts.py create mode 100644 shared/alerts/context_templates.py delete mode 100644 shared/alerts/templates.py create mode 100644 shared/schemas/alert_types.py create mode 100644 shared/schemas/event_classification.py create mode 100644 test_i18n_parameters.py diff --git a/Tiltfile b/Tiltfile index 0dcabd37..67b53edd 100644 --- a/Tiltfile +++ b/Tiltfile @@ -48,14 +48,42 @@ def python_live_update(service_name, service_path): # ============================================================================= # FRONTEND (React + Vite) # ============================================================================= +# Multi-stage build with optimized memory settings for large Vite builds +# Set FRONTEND_DEBUG=true environment variable to build with development mode (no minification) +# for easier debugging of React errors + +# Check for FRONTEND_DEBUG environment variable (Starlark uses os.getenv) +frontend_debug_env = os.getenv('FRONTEND_DEBUG', 'false') +frontend_debug = frontend_debug_env.lower() == 'true' + +# Log the build mode +if frontend_debug: + print(""" + 🐛 FRONTEND DEBUG MODE ENABLED + Building frontend with NO minification for easier debugging. + Full React error messages will be displayed. + To disable: unset FRONTEND_DEBUG or set FRONTEND_DEBUG=false + """) +else: + print(""" + 📦 FRONTEND PRODUCTION MODE + Building frontend with minification for optimized performance. + To enable debug mode: export FRONTEND_DEBUG=true + """) + docker_build( 'bakery/dashboard', context='./frontend', - dockerfile='./frontend/Dockerfile.kubernetes', + dockerfile='./frontend/Dockerfile.kubernetes.debug' if frontend_debug else './frontend/Dockerfile.kubernetes', + # Remove target to build the full image, including the build stage live_update=[ sync('./frontend/src', '/app/src'), sync('./frontend/public', '/app/public'), ], + # Increase Node.js memory for build stage + build_args={ + 'NODE_OPTIONS': '--max-old-space-size=8192' + }, # Ignore test artifacts and reports ignore=[ 'playwright-report/**', @@ -157,7 +185,7 @@ build_python_service('production-service', 'production') build_python_service('procurement-service', 'procurement') # NEW: Sprint 3 build_python_service('orchestrator-service', 'orchestrator') # NEW: Sprint 2 build_python_service('ai-insights-service', 'ai_insights') # NEW: AI Insights Platform -build_python_service('alert-processor', 'alert_processor') +build_python_service('alert-processor', 'alert_processor') # Unified Alert Service with enrichment build_python_service('demo-session-service', 'demo_session') # ============================================================================= @@ -181,7 +209,7 @@ k8s_resource('production-db', resource_deps=['security-setup'], labels=['databas k8s_resource('procurement-db', resource_deps=['security-setup'], labels=['databases']) # NEW: Sprint 3 k8s_resource('orchestrator-db', resource_deps=['security-setup'], labels=['databases']) # NEW: Sprint 2 k8s_resource('ai-insights-db', resource_deps=['security-setup'], labels=['databases']) # NEW: AI Insights Platform -k8s_resource('alert-processor-db', resource_deps=['security-setup'], labels=['databases']) +k8s_resource('alert-processor-db', resource_deps=['security-setup'], labels=['databases']) # Unified Alert Service k8s_resource('demo-session-db', resource_deps=['security-setup'], labels=['databases']) k8s_resource('redis', resource_deps=['security-setup'], labels=['infrastructure']) @@ -373,6 +401,11 @@ k8s_resource('demo-seed-orchestration-runs', resource_deps=['orchestrator-migration', 'demo-seed-tenants'], labels=['demo-init']) +# Weight 28: Seed alerts (alert processor service) - after orchestration runs as alerts reference recent data +k8s_resource('demo-seed-alerts', + resource_deps=['alert-processor-migration', 'demo-seed-tenants'], + labels=['demo-init']) + k8s_resource('demo-seed-pos-configs', resource_deps=['demo-seed-tenants'], labels=['demo-init']) @@ -526,8 +559,8 @@ k8s_resource('frontend', # Update check interval - how often Tilt checks for file changes update_settings( - max_parallel_updates=3, - k8s_upsert_timeout_secs=60 + max_parallel_updates=2, # Reduce parallel updates to avoid resource exhaustion on local machines + k8s_upsert_timeout_secs=120 # Increase timeout for slower local builds ) # Watch settings - configure file watching behavior diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 00000000..b4d72adc --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,41 @@ +# Dependencies +node_modules + +# Production build output (should be recreated during Docker build) +dist + +# Testing +coverage +test-results +playwright-report + +# Logs +logs +*.log + +# Environment variables +.env* +!.env.example + +# IDE files +.vscode +.idea +*.swp +*.swo + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Build outputs +build +.nyc_output + +# Local configuration files +.git +.gitignore \ No newline at end of file diff --git a/frontend/Dockerfile.kubernetes b/frontend/Dockerfile.kubernetes index 7fb24905..f82888b2 100644 --- a/frontend/Dockerfile.kubernetes +++ b/frontend/Dockerfile.kubernetes @@ -9,15 +9,31 @@ WORKDIR /app # Copy package files COPY package*.json ./ -# Install dependencies including dev dependencies for building +# Install all dependencies for building RUN npm ci --verbose && \ npm cache clean --force -# Copy source code +# Copy source code (excluding unnecessary files like node_modules, dist, etc.) COPY . . -# Build the application for production -# This will use environment variables available at build time +# Create a default runtime config in the public directory if it doesn't exist to satisfy the reference in index.html +RUN if [ ! -f public/runtime-config.js ]; then \ + mkdir -p public && \ + echo "window.__RUNTIME_CONFIG__ = {};" > public/runtime-config.js; \ + fi + +# Set build-time environment variables to prevent hanging on undefined variables +ENV NODE_ENV=production +ENV CI=true +ENV VITE_API_URL=/api +ENV VITE_APP_TITLE="BakeWise" +ENV VITE_APP_VERSION="1.0.0" +ENV VITE_PILOT_MODE_ENABLED="false" +ENV VITE_PILOT_COUPON_CODE="PILOT2025" +ENV VITE_PILOT_TRIAL_MONTHS="3" +ENV VITE_STRIPE_PUBLISHABLE_KEY="pk_test_" +# Set Node.js memory limit for the build process +ENV NODE_OPTIONS="--max-old-space-size=4096" RUN npm run build # Stage 2: Production server with Nginx @@ -26,6 +42,9 @@ FROM nginx:1.25-alpine AS production # Install curl for health checks RUN apk add --no-cache curl +# Copy main nginx configuration that sets the PID file location +COPY nginx-main.conf /etc/nginx/nginx.conf + # Remove default nginx configuration RUN rm /etc/nginx/conf.d/default.conf @@ -49,18 +68,7 @@ RUN chown -R nginx:nginx /usr/share/nginx/html && \ # Create nginx PID directory and fix permissions RUN mkdir -p /var/run/nginx /var/lib/nginx/tmp && \ - chown -R nginx:nginx /var/run/nginx /var/lib/nginx - -# Custom nginx.conf for running as non-root -RUN echo 'pid /var/run/nginx/nginx.pid;' > /etc/nginx/nginx.conf && \ - echo 'events { worker_connections 1024; }' >> /etc/nginx/nginx.conf && \ - echo 'http {' >> /etc/nginx/nginx.conf && \ - echo ' include /etc/nginx/mime.types;' >> /etc/nginx/nginx.conf && \ - echo ' default_type application/octet-stream;' >> /etc/nginx/nginx.conf && \ - echo ' sendfile on;' >> /etc/nginx/nginx.conf && \ - echo ' keepalive_timeout 65;' >> /etc/nginx/nginx.conf && \ - echo ' include /etc/nginx/conf.d/*.conf;' >> /etc/nginx/nginx.conf && \ - echo '}' >> /etc/nginx/nginx.conf + chown -R nginx:nginx /var/run/nginx /var/lib/nginx /etc/nginx # Switch to non-root user USER nginx @@ -74,3 +82,4 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ # Start nginx CMD ["nginx", "-g", "daemon off;"] + diff --git a/frontend/Dockerfile.kubernetes.debug b/frontend/Dockerfile.kubernetes.debug new file mode 100644 index 00000000..728815d5 --- /dev/null +++ b/frontend/Dockerfile.kubernetes.debug @@ -0,0 +1,89 @@ +# Kubernetes-optimized DEBUG Dockerfile for Frontend +# Multi-stage build for DEVELOPMENT/DEBUG deployment +# This build DISABLES minification and provides full React error messages + +# Stage 1: Build the application in DEVELOPMENT MODE +FROM node:18-alpine AS builder + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install all dependencies for building +RUN npm ci --verbose && \ + npm cache clean --force + +# Copy source code (excluding unnecessary files like node_modules, dist, etc.) +COPY . . + +# Create a default runtime config in the public directory if it doesn't exist to satisfy the reference in index.html +RUN if [ ! -f public/runtime-config.js ]; then \ + mkdir -p public && \ + echo "window.__RUNTIME_CONFIG__ = {};" > public/runtime-config.js; \ + fi + +# DEBUG BUILD SETTINGS - NO MINIFICATION +# This will produce larger bundles but with full error messages +ENV NODE_ENV=development +ENV CI=true +ENV VITE_API_URL=/api +ENV VITE_APP_TITLE="BakeWise (Debug)" +ENV VITE_APP_VERSION="1.0.0-debug" +ENV VITE_PILOT_MODE_ENABLED="false" +ENV VITE_PILOT_COUPON_CODE="PILOT2025" +ENV VITE_PILOT_TRIAL_MONTHS="3" +ENV VITE_STRIPE_PUBLISHABLE_KEY="pk_test_" + +# Set Node.js memory limit for the build process +ENV NODE_OPTIONS="--max-old-space-size=4096" + +# Build in development mode (no minification, full source maps) +RUN npm run build -- --mode development + +# Stage 2: Production server with Nginx (same as production) +FROM nginx:1.25-alpine AS production + +# Install curl for health checks +RUN apk add --no-cache curl + +# Copy main nginx configuration that sets the PID file location +COPY nginx-main.conf /etc/nginx/nginx.conf + +# Remove default nginx configuration +RUN rm /etc/nginx/conf.d/default.conf + +# Copy custom nginx configuration +COPY nginx.conf /etc/nginx/conf.d/ + +# Copy built application from builder stage +COPY --from=builder /app/dist /usr/share/nginx/html + +# Copy and setup environment substitution script +COPY substitute-env.sh /docker-entrypoint.d/30-substitute-env.sh + +# Make the script executable +RUN chmod +x /docker-entrypoint.d/30-substitute-env.sh + +# Set proper permissions +RUN chown -R nginx:nginx /usr/share/nginx/html && \ + chown -R nginx:nginx /var/cache/nginx && \ + chown -R nginx:nginx /var/log/nginx && \ + chown -R nginx:nginx /etc/nginx/conf.d + +# Create nginx PID directory and fix permissions +RUN mkdir -p /var/run/nginx /var/lib/nginx/tmp && \ + chown -R nginx:nginx /var/run/nginx /var/lib/nginx /etc/nginx + +# Switch to non-root user +USER nginx + +# Expose port 3000 (to match current setup) +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ + CMD curl -f http://localhost:3000/health || exit 1 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/nginx-main.conf b/frontend/nginx-main.conf new file mode 100644 index 00000000..0dc97974 --- /dev/null +++ b/frontend/nginx-main.conf @@ -0,0 +1,12 @@ +pid /var/run/nginx/nginx.pid; +worker_processes auto; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + include /etc/nginx/conf.d/*.conf; +} \ No newline at end of file diff --git a/frontend/src/api/hooks/newDashboard.ts b/frontend/src/api/hooks/newDashboard.ts index 0e433860..ae4d3ce2 100644 --- a/frontend/src/api/hooks/newDashboard.ts +++ b/frontend/src/api/hooks/newDashboard.ts @@ -20,11 +20,13 @@ import { apiClient } from '../client'; // ============================================================ export interface HealthChecklistItem { - icon: 'check' | 'warning' | 'alert'; + icon: 'check' | 'warning' | 'alert' | 'ai_handled'; text?: string; // Deprecated: Use textKey instead textKey?: string; // i18n key for translation textParams?: Record; // Parameters for i18n translation actionRequired: boolean; + status: 'good' | 'ai_handled' | 'needs_you'; // Tri-state status + actionPath?: string; // Optional path to navigate for action } export interface HeadlineData { @@ -40,6 +42,7 @@ export interface BakeryHealthStatus { checklistItems: HealthChecklistItem[]; criticalIssues: number; pendingActions: number; + aiPreventedIssues: number; // Count of issues AI prevented } export interface ReasoningInputs { @@ -127,6 +130,53 @@ export interface ActionQueue { importantCount: number; } +// New unified action queue with time-based grouping +export interface EnrichedAlert { + id: string; + alert_type: string; + type_class: string; + priority_level: string; + priority_score: number; + title: string; + message: string; + actions: Array<{ + type: string; + label: string; + variant: 'primary' | 'secondary' | 'ghost'; + metadata?: Record; + disabled?: boolean; + estimated_time_minutes?: number; + }>; + urgency_context?: { + deadline?: string; + time_until_consequence_hours?: number; + }; + alert_metadata?: { + escalation?: { + original_score: number; + boost_applied: number; + escalated_at: string; + reason: string; + }; + }; + business_impact?: { + financial_impact_eur?: number; + affected_orders?: number; + }; + ai_reasoning_summary?: string; + hidden_from_ui?: boolean; +} + +export interface UnifiedActionQueue { + urgent: EnrichedAlert[]; // <6h to deadline or CRITICAL + today: EnrichedAlert[]; // <24h to deadline + week: EnrichedAlert[]; // <7d to deadline or escalated + totalActions: number; + urgentCount: number; + todayCount: number; + weekCount: number; +} + export interface ProductionTimelineItem { id: string; batchNumber: string; @@ -234,7 +284,7 @@ export function useOrchestrationSummary(tenantId: string, runId?: string) { } /** - * Get action queue + * Get action queue (LEGACY - use useUnifiedActionQueue for new implementation) * * Prioritized list of what requires user attention right now. * This is the core JTBD dashboard feature. @@ -254,6 +304,31 @@ export function useActionQueue(tenantId: string) { }); } +/** + * Get unified action queue with time-based grouping + * + * Returns all action-needed alerts grouped by urgency: + * - URGENT: <6h to deadline or CRITICAL priority + * - TODAY: <24h to deadline + * - THIS WEEK: <7d to deadline or escalated (>48h pending) + * + * This is the NEW implementation for the redesigned Action Queue Card. + */ +export function useUnifiedActionQueue(tenantId: string) { + return useQuery({ + queryKey: ['unified-action-queue', tenantId], + queryFn: async () => { + return await apiClient.get( + `/tenants/${tenantId}/dashboard/unified-action-queue` + ); + }, + enabled: !!tenantId, + refetchInterval: 30000, // Refresh every 30 seconds (more frequent than legacy) + staleTime: 15000, + retry: 2, + }); +} + /** * Get production timeline * @@ -294,6 +369,31 @@ export function useInsights(tenantId: string) { }); } +/** + * Get execution progress - plan vs actual for today + * + * Shows how today's execution is progressing: + * - Production: batches completed/in-progress/pending + * - Deliveries: received/pending/overdue + * - Approvals: pending count + * + * This is the NEW implementation for the ExecutionProgressTracker component. + */ +export function useExecutionProgress(tenantId: string) { + return useQuery({ + queryKey: ['execution-progress', tenantId], + queryFn: async () => { + return await apiClient.get( + `/tenants/${tenantId}/dashboard/execution-progress` + ); + }, + enabled: !!tenantId, + refetchInterval: 60000, // Refresh every minute + staleTime: 30000, + retry: 2, + }); +} + // ============================================================ // Action Mutations // ============================================================ diff --git a/frontend/src/components/dashboard/ActionQueueCard.tsx b/frontend/src/components/dashboard/ActionQueueCard.tsx deleted file mode 100644 index 14f132fb..00000000 --- a/frontend/src/components/dashboard/ActionQueueCard.tsx +++ /dev/null @@ -1,665 +0,0 @@ -// ================================================================ -// frontend/src/components/dashboard/ActionQueueCard.tsx -// ================================================================ -/** - * Action Queue Card - What needs your attention right now - * - * Prioritized list of actions the user needs to take, with context - * about why each action is needed and what happens if they don't do it. - */ - -import React, { useState, useMemo } from 'react'; -import { - FileText, - AlertCircle, - CheckCircle2, - Eye, - Edit, - Clock, - Euro, - ChevronDown, - ChevronUp, - X, - Package, - Building2, - Calendar, - Truck, -} from 'lucide-react'; -import { ActionItem, ActionQueue } from '../../api/hooks/newDashboard'; -import { useReasoningFormatter } from '../../hooks/useReasoningTranslation'; -import { useTranslation } from 'react-i18next'; -import { usePurchaseOrder } from '../../api/hooks/purchase-orders'; - -interface ActionQueueCardProps { - actionQueue: ActionQueue; - loading?: boolean; - onApprove?: (actionId: string) => void; - onReject?: (actionId: string, reason: string) => void; - onViewDetails?: (actionId: string) => void; - onModify?: (actionId: string) => void; - tenantId?: string; -} - -const urgencyConfig = { - critical: { - bgColor: 'var(--color-error-50)', - borderColor: 'var(--color-error-300)', - badgeBgColor: 'var(--color-error-100)', - badgeTextColor: 'var(--color-error-800)', - icon: AlertCircle, - iconColor: 'var(--color-error-700)', - }, - important: { - bgColor: 'var(--color-warning-50)', - borderColor: 'var(--color-warning-300)', - badgeBgColor: 'var(--color-warning-100)', - badgeTextColor: 'var(--color-warning-900)', - icon: AlertCircle, - iconColor: 'var(--color-warning-700)', - }, - normal: { - bgColor: 'var(--color-info-50)', - borderColor: 'var(--color-info-300)', - badgeBgColor: 'var(--color-info-100)', - badgeTextColor: 'var(--color-info-800)', - icon: FileText, - iconColor: 'var(--color-info-700)', - }, -}; - -/** - * Helper function to translate keys with proper namespace handling - * Maps backend key formats to correct i18next namespaces - */ -function translateKey( - key: string, - params: Record, - tDashboard: any, - tReasoning: any -): string { - // Preprocess parameters - join arrays into strings - const processedParams = { ...params }; - - // Convert product_names array to product_names_joined string - if ('product_names' in processedParams && Array.isArray(processedParams.product_names)) { - processedParams.product_names_joined = processedParams.product_names.join(', '); - } - - // Convert affected_products array to affected_products_joined string - if ('affected_products' in processedParams && Array.isArray(processedParams.affected_products)) { - processedParams.affected_products_joined = processedParams.affected_products.join(', '); - } - - // Determine namespace based on key prefix - if (key.startsWith('reasoning.')) { - // Remove 'reasoning.' prefix and use reasoning namespace - const translationKey = key.substring('reasoning.'.length); - // Use i18next-icu for interpolation with {variable} syntax - return String(tReasoning(translationKey, processedParams)); - } else if (key.startsWith('action_queue.') || key.startsWith('dashboard.') || key.startsWith('health.')) { - // Use dashboard namespace - return String(tDashboard(key, processedParams)); - } - - // Default to dashboard - return String(tDashboard(key, processedParams)); -} - -function ActionItemCard({ - action, - onApprove, - onReject, - onViewDetails, - onModify, - tenantId, -}: { - action: ActionItem; - onApprove?: (id: string) => void; - onReject?: (id: string, reason: string) => void; - onViewDetails?: (id: string) => void; - onModify?: (id: string) => void; - tenantId?: string; -}) { - const [expanded, setExpanded] = useState(false); - const [showDetails, setShowDetails] = useState(false); - const [showRejectModal, setShowRejectModal] = useState(false); - const [rejectionReason, setRejectionReason] = useState(''); - const config = urgencyConfig[action.urgency as keyof typeof urgencyConfig] || urgencyConfig.normal; - const UrgencyIcon = config.icon; - const { formatPOAction } = useReasoningFormatter(); - const { t: tReasoning } = useTranslation('reasoning'); - const { t: tDashboard } = useTranslation('dashboard'); - - // Fetch PO details if this is a PO action and details are expanded - const { data: poDetail } = usePurchaseOrder( - tenantId || '', - action.id, - { enabled: !!tenantId && showDetails && action.type === 'po_approval' } - ); - - // Translate i18n fields (or fallback to deprecated text fields or reasoning_data for alerts) - const reasoning = useMemo(() => { - if (action.reasoning_i18n) { - return translateKey(action.reasoning_i18n.key, action.reasoning_i18n.params, tDashboard, tReasoning); - } - if (action.reasoning_data) { - const formatted = formatPOAction(action.reasoning_data); - return formatted.reasoning; - } - return action.reasoning || ''; - }, [action.reasoning_i18n, action.reasoning_data, action.reasoning, tDashboard, tReasoning, formatPOAction]); - - const consequence = useMemo(() => { - if (action.consequence_i18n) { - return translateKey(action.consequence_i18n.key, action.consequence_i18n.params, tDashboard, tReasoning); - } - if (action.reasoning_data) { - const formatted = formatPOAction(action.reasoning_data); - return formatted.consequence; - } - return ''; - }, [action.consequence_i18n, action.reasoning_data, tDashboard, tReasoning, formatPOAction]); - - return ( -
- {/* Header */} -
- -
-
-

- {action.title_i18n ? translateKey(action.title_i18n.key, action.title_i18n.params, tDashboard, tReasoning) : (action.title || 'Action Required')} -

- - {action.urgency || 'normal'} - -
-

- {action.subtitle_i18n ? translateKey(action.subtitle_i18n.key, action.subtitle_i18n.params, tDashboard, tReasoning) : (action.subtitle || '')} -

-
-
- - {/* Amount (for POs) */} - {action.amount && ( -
- - - {action.amount.toFixed(2)} {action.currency} - -
- )} - - {/* Reasoning (always visible) */} -
-

- {tReasoning('jtbd.action_queue.why_needed')} -

-

{reasoning}

-
- - {/* Consequence (expandable) */} - {consequence && ( - <> - - - {expanded && ( -
-

{consequence}

-
- )} - - )} - - {/* Inline PO Details (expandable) */} - {action.type === 'po_approval' && ( - <> - - - {showDetails && poDetail && ( -
- {/* Supplier Info */} -
- -
-

- {poDetail.supplier?.name || 'Supplier'} -

- {poDetail.supplier?.contact_person && ( -

- Contact: {poDetail.supplier.contact_person} -

- )} - {poDetail.supplier?.email && ( -

- {poDetail.supplier.email} -

- )} -
-
- - {/* Delivery Date & Tracking */} - {poDetail.required_delivery_date && ( -
-
- -
-

- Required Delivery -

-

- {new Date(poDetail.required_delivery_date).toLocaleDateString()} -

-
-
- - {/* Estimated Delivery Date (shown after approval) */} - {poDetail.estimated_delivery_date && ( -
- -
-

- Expected Arrival -

-

- {new Date(poDetail.estimated_delivery_date).toLocaleDateString()} -

-
- {(() => { - const now = new Date(); - const estimatedDate = new Date(poDetail.estimated_delivery_date); - const daysUntil = Math.ceil((estimatedDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)); - - let statusColor = 'var(--color-success-600)'; - let statusText = 'On Track'; - - if (daysUntil < 0) { - statusColor = 'var(--color-error-600)'; - statusText = `${Math.abs(daysUntil)}d Overdue`; - } else if (daysUntil === 0) { - statusColor = 'var(--color-warning-600)'; - statusText = 'Due Today'; - } else if (daysUntil <= 2) { - statusColor = 'var(--color-warning-600)'; - statusText = `${daysUntil}d Left`; - } else { - statusText = `${daysUntil}d Left`; - } - - return ( - - {statusText} - - ); - })()} -
- )} -
- )} - - {/* Line Items */} - {poDetail.items && poDetail.items.length > 0 && ( -
-

- Order Items ({poDetail.items.length}) -

-
- {poDetail.items.map((item, idx) => ( -
-
-

- {item.product_name || item.product_code || 'Product'} -

-

- {item.ordered_quantity} {item.unit_of_measure} × €{parseFloat(item.unit_price).toFixed(2)} -

-
-

- €{parseFloat(item.line_total).toFixed(2)} -

-
- ))} -
-
- )} - - {/* Total Amount */} -
-

Total Amount

-

- €{parseFloat(poDetail.total_amount).toFixed(2)} -

-
-
- )} - - )} - - {/* Time Estimate */} -
- - - {tReasoning('jtbd.action_queue.estimated_time')}: {action.estimatedTimeMinutes || 5} min - -
- - {/* Rejection Modal */} - {showRejectModal && ( -
setShowRejectModal(false)} - > -
e.stopPropagation()} - > -
-

- Reject Purchase Order -

- -
- -

- Please provide a reason for rejecting this purchase order: -

- -