diff --git a/frontend/src/components/domain/dashboard/ConfigurationProgressWidget.tsx b/frontend/src/components/domain/dashboard/ConfigurationProgressWidget.tsx new file mode 100644 index 00000000..bedfd538 --- /dev/null +++ b/frontend/src/components/domain/dashboard/ConfigurationProgressWidget.tsx @@ -0,0 +1,298 @@ +import React, { useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { useCurrentTenant } from '../../../stores/tenant.store'; +import { useIngredients } from '../../../api/hooks/inventory'; +import { useSuppliers } from '../../../api/hooks/suppliers'; +import { useRecipes } from '../../../api/hooks/recipes'; +import { useQualityTemplates } from '../../../api/hooks/qualityTemplates'; +import { CheckCircle2, Circle, AlertCircle, ChevronRight, Package, Users, BookOpen, Shield } from 'lucide-react'; + +interface ConfigurationSection { + id: string; + title: string; + icon: React.ElementType; + path: string; + count: number; + minimum: number; + recommended: number; + isOptional?: boolean; + isComplete: boolean; + nextAction?: string; +} + +export const ConfigurationProgressWidget: React.FC = () => { + const { t } = useTranslation(); + const navigate = useNavigate(); + const currentTenant = useCurrentTenant(); + const tenantId = currentTenant?.id || ''; + + // Fetch configuration data + const { data: ingredients = [], isLoading: loadingIngredients } = useIngredients(tenantId, {}, { enabled: !!tenantId }); + const { data: suppliersData, isLoading: loadingSuppliers } = useSuppliers(tenantId, { enabled: !!tenantId }); + const suppliers = suppliersData?.suppliers || []; + const { data: recipesData, isLoading: loadingRecipes } = useRecipes(tenantId, { enabled: !!tenantId }); + const recipes = recipesData?.recipes || []; + const { data: qualityData, isLoading: loadingQuality } = useQualityTemplates(tenantId, { enabled: !!tenantId }); + const qualityTemplates = qualityData?.templates || []; + + const isLoading = loadingIngredients || loadingSuppliers || loadingRecipes || loadingQuality; + + // Calculate configuration sections + const sections: ConfigurationSection[] = useMemo(() => [ + { + id: 'inventory', + title: t('dashboard:config.inventory', 'Inventory'), + icon: Package, + path: '/app/operations/inventory', + count: ingredients.length, + minimum: 3, + recommended: 10, + isComplete: ingredients.length >= 3, + nextAction: ingredients.length < 3 ? t('dashboard:config.add_ingredients', 'Add at least {{count}} ingredients', { count: 3 - ingredients.length }) : undefined + }, + { + id: 'suppliers', + title: t('dashboard:config.suppliers', 'Suppliers'), + icon: Users, + path: '/app/operations/suppliers', + count: suppliers.length, + minimum: 1, + recommended: 3, + isComplete: suppliers.length >= 1, + nextAction: suppliers.length < 1 ? t('dashboard:config.add_supplier', 'Add your first supplier') : undefined + }, + { + id: 'recipes', + title: t('dashboard:config.recipes', 'Recipes'), + icon: BookOpen, + path: '/app/operations/recipes', + count: recipes.length, + minimum: 1, + recommended: 3, + isComplete: recipes.length >= 1, + nextAction: recipes.length < 1 ? t('dashboard:config.add_recipe', 'Create your first recipe') : undefined + }, + { + id: 'quality', + title: t('dashboard:config.quality', 'Quality Standards'), + icon: Shield, + path: '/app/operations/production/quality', + count: qualityTemplates.length, + minimum: 0, + recommended: 2, + isOptional: true, + isComplete: true, // Optional, so always "complete" + nextAction: qualityTemplates.length < 2 ? t('dashboard:config.add_quality', 'Add quality checks (optional)') : undefined + } + ], [ingredients.length, suppliers.length, recipes.length, qualityTemplates.length, t]); + + // Calculate overall progress + const { completedSections, totalSections, progressPercentage, nextIncompleteSection } = useMemo(() => { + const requiredSections = sections.filter(s => !s.isOptional); + const completed = requiredSections.filter(s => s.isComplete).length; + const total = requiredSections.length; + const percentage = Math.round((completed / total) * 100); + const nextIncomplete = sections.find(s => !s.isComplete && !s.isOptional); + + return { + completedSections: completed, + totalSections: total, + progressPercentage: percentage, + nextIncompleteSection: nextIncomplete + }; + }, [sections]); + + const isFullyConfigured = progressPercentage === 100; + + // Determine unlocked features + const unlockedFeatures = useMemo(() => { + const features: string[] = []; + if (ingredients.length >= 3) features.push(t('dashboard:config.features.inventory_tracking', 'Inventory Tracking')); + if (suppliers.length >= 1 && ingredients.length >= 3) features.push(t('dashboard:config.features.purchase_orders', 'Purchase Orders')); + if (recipes.length >= 1 && ingredients.length >= 3) features.push(t('dashboard:config.features.production_planning', 'Production Planning')); + if (recipes.length >= 1 && ingredients.length >= 3 && suppliers.length >= 1) features.push(t('dashboard:config.features.cost_analysis', 'Cost Analysis')); + return features; + }, [ingredients.length, suppliers.length, recipes.length, t]); + + if (isLoading) { + return ( +
+
+
+ {t('common:loading', 'Loading configuration...')} +
+
+ ); + } + + // Don't show widget if fully configured + if (isFullyConfigured) { + return null; + } + + return ( +
+ {/* Header */} +
+
+
+
+ +
+
+

+ 🏗️ {t('dashboard:config.title', 'Complete Your Bakery Setup')} +

+

+ {t('dashboard:config.subtitle', 'Configure essential features to get started')} +

+
+
+
+ + {/* Progress Bar */} +
+
+ + {completedSections}/{totalSections} {t('dashboard:config.sections_complete', 'sections complete')} + + {progressPercentage}% +
+
+
+
+
+
+ + {/* Sections List */} +
+ {sections.map((section) => { + const Icon = section.icon; + const meetsRecommended = section.count >= section.recommended; + + return ( + + ); + })} +
+ + {/* Next Action / Unlocked Features */} +
+ {nextIncompleteSection ? ( +
+
+ +
+

+ 👉 {t('dashboard:config.next_step', 'Next Step')} +

+

+ {nextIncompleteSection.nextAction} +

+ +
+
+
+ ) : unlockedFeatures.length > 0 && ( +
+
+ +
+

+ 🎉 {t('dashboard:config.features_unlocked', 'Features Unlocked!')} +

+
    + {unlockedFeatures.map((feature, idx) => ( +
  • + + {feature} +
  • + ))} +
+
+
+
+ )} +
+
+ ); +}; diff --git a/frontend/src/components/domain/setup-wizard/components/StepNavigation.tsx b/frontend/src/components/domain/setup-wizard/components/StepNavigation.tsx index adc8c931..75d6bc0c 100644 --- a/frontend/src/components/domain/setup-wizard/components/StepNavigation.tsx +++ b/frontend/src/components/domain/setup-wizard/components/StepNavigation.tsx @@ -95,7 +95,7 @@ export const StepNavigation: React.FC = ({
) : ( -
- {t('setup_wizard:quality.need_more', 'Need {{count}} more', { count: 2 - templates.length })} +
+ {t('setup_wizard:quality.recommended', '2+ recommended (optional)')}
)}
diff --git a/frontend/src/pages/app/DashboardPage.tsx b/frontend/src/pages/app/DashboardPage.tsx index 1017fb78..196a6ad7 100644 --- a/frontend/src/pages/app/DashboardPage.tsx +++ b/frontend/src/pages/app/DashboardPage.tsx @@ -5,6 +5,7 @@ import { PageHeader } from '../../components/layout'; import StatsGrid from '../../components/ui/Stats/StatsGrid'; import RealTimeAlerts from '../../components/domain/dashboard/RealTimeAlerts'; import { IncompleteIngredientsAlert } from '../../components/domain/dashboard/IncompleteIngredientsAlert'; +import { ConfigurationProgressWidget } from '../../components/domain/dashboard/ConfigurationProgressWidget'; import PendingPOApprovals from '../../components/domain/dashboard/PendingPOApprovals'; import TodayProduction from '../../components/domain/dashboard/TodayProduction'; // Sustainability widget removed - now using stats in StatsGrid @@ -426,6 +427,9 @@ const DashboardPage: React.FC = () => { {/* Dashboard Content - Main Sections */}
+ {/* 0. Configuration Progress Widget */} + + {/* 1. Real-time Alerts */}