Add POI feature and imporve the overall backend implementation
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
* - Trust-building (explain system reasoning)
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { RefreshCw, ExternalLink, Plus, Sparkles } from 'lucide-react';
|
||||
import { useTenant } from '../../stores/tenant.store';
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
useStartProductionBatch,
|
||||
usePauseProductionBatch,
|
||||
} from '../../api/hooks/newDashboard';
|
||||
import { useRejectPurchaseOrder } from '../../api/hooks/purchase-orders';
|
||||
import { HealthStatusCard } from '../../components/dashboard/HealthStatusCard';
|
||||
import { ActionQueueCard } from '../../components/dashboard/ActionQueueCard';
|
||||
import { OrchestrationSummaryCard } from '../../components/dashboard/OrchestrationSummaryCard';
|
||||
@@ -36,11 +37,15 @@ import { ProductionTimelineCard } from '../../components/dashboard/ProductionTim
|
||||
import { InsightsGrid } from '../../components/dashboard/InsightsGrid';
|
||||
import { UnifiedAddWizard } from '../../components/domain/unified-wizard';
|
||||
import type { ItemType } from '../../components/domain/unified-wizard';
|
||||
import { useDemoTour, shouldStartTour, clearTourStartPending } from '../../features/demo-onboarding';
|
||||
import { DemoBanner } from '../../components/layout/DemoBanner/DemoBanner';
|
||||
|
||||
export function NewDashboardPage() {
|
||||
const navigate = useNavigate();
|
||||
const { currentTenant } = useTenant();
|
||||
const tenantId = currentTenant?.id || '';
|
||||
const { startTour } = useDemoTour();
|
||||
const isDemoMode = localStorage.getItem('demo_mode') === 'true';
|
||||
|
||||
// Unified Add Wizard state
|
||||
const [isAddWizardOpen, setIsAddWizardOpen] = useState(false);
|
||||
@@ -79,6 +84,7 @@ export function NewDashboardPage() {
|
||||
|
||||
// Mutations
|
||||
const approvePO = useApprovePurchaseOrder();
|
||||
const rejectPO = useRejectPurchaseOrder();
|
||||
const startBatch = useStartProductionBatch();
|
||||
const pauseBatch = usePauseProductionBatch();
|
||||
|
||||
@@ -94,6 +100,17 @@ export function NewDashboardPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleReject = async (actionId: string, reason: string) => {
|
||||
try {
|
||||
await rejectPO.mutateAsync({ tenantId, poId: actionId, reason });
|
||||
// Refetch to update UI
|
||||
refetchActionQueue();
|
||||
refetchHealth();
|
||||
} catch (error) {
|
||||
console.error('Error rejecting PO:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleViewDetails = (actionId: string) => {
|
||||
// Navigate to appropriate detail page based on action type
|
||||
navigate(`/app/operations/procurement`);
|
||||
@@ -137,8 +154,43 @@ export function NewDashboardPage() {
|
||||
handleRefreshAll();
|
||||
};
|
||||
|
||||
// Demo tour auto-start logic
|
||||
useEffect(() => {
|
||||
console.log('[Dashboard] Demo mode:', isDemoMode);
|
||||
console.log('[Dashboard] Should start tour:', shouldStartTour());
|
||||
console.log('[Dashboard] SessionStorage demo_tour_should_start:', sessionStorage.getItem('demo_tour_should_start'));
|
||||
console.log('[Dashboard] SessionStorage demo_tour_start_step:', sessionStorage.getItem('demo_tour_start_step'));
|
||||
|
||||
// Check if there's a tour intent from redirection (higher priority)
|
||||
const shouldStartFromRedirect = sessionStorage.getItem('demo_tour_should_start') === 'true';
|
||||
const redirectStartStep = parseInt(sessionStorage.getItem('demo_tour_start_step') || '0', 10);
|
||||
|
||||
if (isDemoMode && (shouldStartTour() || shouldStartFromRedirect)) {
|
||||
console.log('[Dashboard] Starting tour in 1.5s...');
|
||||
const timer = setTimeout(() => {
|
||||
console.log('[Dashboard] Executing startTour()');
|
||||
if (shouldStartFromRedirect) {
|
||||
// Start tour from the specific step that was intended
|
||||
startTour(redirectStartStep);
|
||||
// Clear the redirect intent
|
||||
sessionStorage.removeItem('demo_tour_should_start');
|
||||
sessionStorage.removeItem('demo_tour_start_step');
|
||||
} else {
|
||||
// Start tour normally (from beginning or resume)
|
||||
startTour();
|
||||
clearTourStartPending();
|
||||
}
|
||||
}, 1500);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [isDemoMode, startTour]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen pb-20 md:pb-8" style={{ backgroundColor: 'var(--bg-secondary)' }}>
|
||||
{/* Demo Banner */}
|
||||
{isDemoMode && <DemoBanner />}
|
||||
|
||||
{/* Mobile-optimized container */}
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
{/* Header */}
|
||||
@@ -183,30 +235,40 @@ export function NewDashboardPage() {
|
||||
{/* Main Dashboard Layout */}
|
||||
<div className="space-y-6">
|
||||
{/* SECTION 1: Bakery Health Status */}
|
||||
<HealthStatusCard healthStatus={healthStatus} loading={healthLoading} />
|
||||
<div data-tour="dashboard-stats">
|
||||
<HealthStatusCard healthStatus={healthStatus} loading={healthLoading} />
|
||||
</div>
|
||||
|
||||
{/* SECTION 2: What Needs Your Attention (Action Queue) */}
|
||||
<ActionQueueCard
|
||||
actionQueue={actionQueue}
|
||||
loading={actionQueueLoading}
|
||||
onApprove={handleApprove}
|
||||
onViewDetails={handleViewDetails}
|
||||
onModify={handleModify}
|
||||
/>
|
||||
<div data-tour="pending-po-approvals">
|
||||
<ActionQueueCard
|
||||
actionQueue={actionQueue}
|
||||
loading={actionQueueLoading}
|
||||
onApprove={handleApprove}
|
||||
onReject={handleReject}
|
||||
onViewDetails={handleViewDetails}
|
||||
onModify={handleModify}
|
||||
tenantId={tenantId}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* SECTION 3: What the System Did for You (Orchestration Summary) */}
|
||||
<OrchestrationSummaryCard
|
||||
summary={orchestrationSummary}
|
||||
loading={orchestrationLoading}
|
||||
/>
|
||||
<div data-tour="real-time-alerts">
|
||||
<OrchestrationSummaryCard
|
||||
summary={orchestrationSummary}
|
||||
loading={orchestrationLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* SECTION 4: Today's Production Timeline */}
|
||||
<ProductionTimelineCard
|
||||
timeline={productionTimeline}
|
||||
loading={timelineLoading}
|
||||
onStart={handleStartBatch}
|
||||
onPause={handlePauseBatch}
|
||||
/>
|
||||
<div data-tour="today-production">
|
||||
<ProductionTimelineCard
|
||||
timeline={productionTimeline}
|
||||
loading={timelineLoading}
|
||||
onStart={handleStartBatch}
|
||||
onPause={handlePauseBatch}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* SECTION 5: Quick Insights Grid */}
|
||||
<div>
|
||||
|
||||
@@ -28,6 +28,75 @@ const SustainabilityPage: React.FC = () => {
|
||||
// Date range state (default to last 30 days)
|
||||
const [dateRange, setDateRange] = useState<{ start?: string; end?: string }>({});
|
||||
|
||||
// CSV Export function
|
||||
const exportToCSV = () => {
|
||||
if (!metrics) return;
|
||||
|
||||
const timestamp = new Date().toISOString().split('T')[0];
|
||||
const filename = `sustainability_report_${currentTenant?.name || 'bakery'}_${timestamp}.csv`;
|
||||
|
||||
// Build CSV content
|
||||
const csvRows = [
|
||||
['Sustainability Report'],
|
||||
['Generated:', new Date().toLocaleString()],
|
||||
['Bakery:', currentTenant?.name || 'N/A'],
|
||||
['Period:', `${metrics.period.start_date} to ${metrics.period.end_date}`],
|
||||
[],
|
||||
['WASTE METRICS'],
|
||||
['Total Waste (kg)', metrics.waste_metrics.total_waste_kg.toFixed(2)],
|
||||
['Production Waste (kg)', metrics.waste_metrics.production_waste_kg.toFixed(2)],
|
||||
['Inventory Waste (kg)', metrics.waste_metrics.inventory_waste_kg.toFixed(2)],
|
||||
['Waste Percentage (%)', metrics.waste_metrics.waste_percentage.toFixed(2)],
|
||||
[],
|
||||
['SDG 12.3 COMPLIANCE'],
|
||||
['Status', metrics.sdg_compliance.sdg_12_3.status_label],
|
||||
['Reduction Achieved (%)', metrics.sdg_compliance.sdg_12_3.reduction_achieved.toFixed(2)],
|
||||
['Progress to Target (%)', metrics.sdg_compliance.sdg_12_3.progress_to_target.toFixed(2)],
|
||||
['Target (%)', metrics.sdg_compliance.sdg_12_3.target_percentage],
|
||||
[],
|
||||
['ENVIRONMENTAL IMPACT'],
|
||||
['CO2 Emissions (kg)', metrics.environmental_impact.co2_emissions.kg.toFixed(2)],
|
||||
['CO2 Emissions (tons)', metrics.environmental_impact.co2_emissions.tons.toFixed(4)],
|
||||
['Trees to Offset', metrics.environmental_impact.co2_emissions.trees_to_offset.toFixed(1)],
|
||||
['Equivalent Car KM', metrics.environmental_impact.co2_emissions.car_km_equivalent.toFixed(0)],
|
||||
['Water Footprint (liters)', metrics.environmental_impact.water_footprint.liters.toFixed(0)],
|
||||
['Water Footprint (m³)', metrics.environmental_impact.water_footprint.cubic_meters.toFixed(2)],
|
||||
['Equivalent Showers', metrics.environmental_impact.water_footprint.shower_equivalent.toFixed(0)],
|
||||
['Land Use (m²)', metrics.environmental_impact.land_use.square_meters.toFixed(2)],
|
||||
['Land Use (hectares)', metrics.environmental_impact.land_use.hectares.toFixed(4)],
|
||||
[],
|
||||
['FINANCIAL IMPACT'],
|
||||
['Waste Cost (EUR)', metrics.financial_impact.waste_cost_eur.toFixed(2)],
|
||||
['Potential Monthly Savings (EUR)', metrics.financial_impact.potential_monthly_savings.toFixed(2)],
|
||||
['ROI on Prevention (%)', metrics.financial_impact.roi_on_waste_prevention.toFixed(2)],
|
||||
[],
|
||||
['AVOIDED WASTE (AI PREDICTIONS)'],
|
||||
['Waste Avoided (kg)', metrics.avoided_waste.total_waste_avoided_kg.toFixed(2)],
|
||||
['Cost Savings (EUR)', metrics.avoided_waste.cost_savings_eur.toFixed(2)],
|
||||
['CO2 Avoided (kg)', metrics.avoided_waste.environmental_impact_avoided.co2_kg.toFixed(2)],
|
||||
['Water Saved (liters)', metrics.avoided_waste.environmental_impact_avoided.water_liters.toFixed(0)],
|
||||
[],
|
||||
['GRANT READINESS'],
|
||||
['Certification Ready', metrics.sdg_compliance.certification_ready ? 'Yes' : 'No'],
|
||||
['Eligible Programs', Object.values(metrics.grant_readiness.grant_programs).filter(p => p.eligible).length],
|
||||
['Recommended Applications', metrics.grant_readiness.recommended_applications.join(', ')]
|
||||
];
|
||||
|
||||
// Convert to CSV string
|
||||
const csvContent = csvRows.map(row => row.join(',')).join('\n');
|
||||
|
||||
// Create download
|
||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const link = document.createElement('a');
|
||||
const url = URL.createObjectURL(blob);
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', filename);
|
||||
link.style.visibility = 'hidden';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
};
|
||||
|
||||
// Fetch sustainability metrics
|
||||
const {
|
||||
data: metrics,
|
||||
@@ -226,12 +295,10 @@ const SustainabilityPage: React.FC = () => {
|
||||
id: "export-report",
|
||||
label: t('sustainability:actions.export_report', 'Exportar Informe'),
|
||||
icon: Download,
|
||||
onClick: () => {
|
||||
// TODO: Implement export
|
||||
console.log('Export sustainability report');
|
||||
},
|
||||
onClick: exportToCSV,
|
||||
variant: "outline",
|
||||
size: "sm"
|
||||
size: "sm",
|
||||
disabled: !metrics
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -17,9 +17,9 @@ import type { ItemType } from '../../../../components/domain/unified-wizard';
|
||||
|
||||
// Import AddStockModal separately since we need it for adding batches
|
||||
import AddStockModal from '../../../../components/domain/inventory/AddStockModal';
|
||||
import { useIngredients, useStockAnalytics, useStockMovements, useStockByIngredient, useCreateIngredient, useSoftDeleteIngredient, useHardDeleteIngredient, useAddStock, useConsumeStock, useUpdateIngredient, useUpdateStock, useTransformationsByIngredient } from '../../../../api/hooks/inventory';
|
||||
import { useIngredients, useStockAnalytics, useStockMovements, useStockByIngredient, useCreateIngredient, useSoftDeleteIngredient, useHardDeleteIngredient, useAddStock, useConsumeStock, useUpdateIngredient, useUpdateStock, useCreateStockMovement, useTransformationsByIngredient } from '../../../../api/hooks/inventory';
|
||||
import { useTenantId } from '../../../../hooks/useTenantId';
|
||||
import { IngredientResponse, StockCreate, StockMovementCreate, IngredientCreate } from '../../../../api/types/inventory';
|
||||
import { IngredientResponse, StockCreate, StockMovementCreate, StockMovementType, IngredientCreate } from '../../../../api/types/inventory';
|
||||
import { subscriptionService } from '../../../../api/services/subscription';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
@@ -55,6 +55,7 @@ const InventoryPage: React.FC = () => {
|
||||
const consumeStockMutation = useConsumeStock();
|
||||
const updateIngredientMutation = useUpdateIngredient();
|
||||
const updateStockMutation = useUpdateStock();
|
||||
const createStockMovementMutation = useCreateStockMovement();
|
||||
|
||||
// API Data
|
||||
const {
|
||||
@@ -795,8 +796,36 @@ const InventoryPage: React.FC = () => {
|
||||
});
|
||||
}}
|
||||
onMarkAsWaste={async (batchId) => {
|
||||
// TODO: Implement mark as waste functionality
|
||||
console.log('Mark as waste:', batchId);
|
||||
if (!tenantId || !batches) return;
|
||||
|
||||
try {
|
||||
// Find the batch to get its details
|
||||
const batch = batches.find(b => b.id === batchId);
|
||||
if (!batch) {
|
||||
console.error('Batch not found:', batchId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a waste movement for the entire current quantity
|
||||
const movementData: StockMovementCreate = {
|
||||
ingredient_id: selectedItem!.id,
|
||||
stock_id: batchId,
|
||||
movement_type: StockMovementType.WASTE,
|
||||
quantity: batch.current_quantity,
|
||||
unit_cost: batch.unit_cost || undefined,
|
||||
notes: `Batch marked as waste. Reason: ${batch.is_expired ? 'Expired' : 'Damaged/Spoiled'}`
|
||||
};
|
||||
|
||||
await createStockMovementMutation.mutateAsync({
|
||||
tenantId,
|
||||
movementData
|
||||
});
|
||||
|
||||
// Refresh the batches list
|
||||
queryClient.invalidateQueries({ queryKey: ['stock', 'byIngredient', tenantId, selectedItem!.id] });
|
||||
} catch (error) {
|
||||
console.error('Failed to mark batch as waste:', error);
|
||||
}
|
||||
}}
|
||||
waitForRefetch={true}
|
||||
isRefetching={isRefetchingBatches}
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
CustomerType,
|
||||
CustomerSegment
|
||||
} from '../../../../api/types/orders';
|
||||
import { useOrders, useCustomers, useOrdersDashboard, useCreateOrder, useCreateCustomer, useUpdateCustomer } from '../../../../api/hooks/orders';
|
||||
import { useOrders, useCustomers, useOrdersDashboard, useCreateOrder, useUpdateOrder, useCreateCustomer, useUpdateCustomer } from '../../../../api/hooks/orders';
|
||||
import { useCurrentTenant } from '../../../../stores/tenant.store';
|
||||
import { useAuthUser } from '../../../../stores/auth.store';
|
||||
import { OrderFormModal } from '../../../../components/domain/orders';
|
||||
@@ -76,6 +76,7 @@ const OrdersPage: React.FC = () => {
|
||||
|
||||
// Mutations
|
||||
const createOrderMutation = useCreateOrder();
|
||||
const updateOrderMutation = useUpdateOrder();
|
||||
const createCustomerMutation = useCreateCustomer();
|
||||
const updateCustomerMutation = useUpdateCustomer();
|
||||
|
||||
@@ -634,11 +635,36 @@ const OrdersPage: React.FC = () => {
|
||||
sections={sections}
|
||||
showDefaultActions={true}
|
||||
onSave={async () => {
|
||||
// TODO: Implement order update functionality
|
||||
// Note: The backend only has updateOrderStatus, not a general update endpoint
|
||||
// For now, orders can be updated via status changes using useUpdateOrderStatus
|
||||
console.log('Saving order:', selectedOrder);
|
||||
console.warn('Order update not yet implemented - only status updates are supported via useUpdateOrderStatus');
|
||||
if (!selectedOrder || !tenantId) return;
|
||||
|
||||
try {
|
||||
// Build the update payload from the selectedOrder state
|
||||
const updateData = {
|
||||
status: selectedOrder.status,
|
||||
priority: selectedOrder.priority,
|
||||
requested_delivery_date: selectedOrder.requested_delivery_date,
|
||||
delivery_method: selectedOrder.delivery_method,
|
||||
delivery_address: selectedOrder.delivery_address,
|
||||
delivery_instructions: selectedOrder.delivery_instructions,
|
||||
delivery_window_start: selectedOrder.delivery_window_start,
|
||||
delivery_window_end: selectedOrder.delivery_window_end,
|
||||
payment_method: selectedOrder.payment_method,
|
||||
payment_status: selectedOrder.payment_status,
|
||||
special_instructions: selectedOrder.special_instructions,
|
||||
custom_requirements: selectedOrder.custom_requirements,
|
||||
allergen_warnings: selectedOrder.allergen_warnings,
|
||||
};
|
||||
|
||||
await updateOrderMutation.mutateAsync({
|
||||
tenantId: tenantId,
|
||||
orderId: selectedOrder.id,
|
||||
data: updateData
|
||||
});
|
||||
|
||||
setShowForm(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to update order:', error);
|
||||
}
|
||||
}}
|
||||
onFieldChange={(sectionIndex, fieldIndex, value) => {
|
||||
const newOrder = { ...selectedOrder };
|
||||
|
||||
@@ -11,6 +11,7 @@ import { showToast } from '../../../../utils/toast';
|
||||
import { usePOSConfigurationData, usePOSConfigurationManager, usePOSTransactions, usePOSTransactionsDashboard, usePOSTransaction } from '../../../../api/hooks/pos';
|
||||
import { POSConfiguration } from '../../../../api/types/pos';
|
||||
import { posService } from '../../../../api/services/pos';
|
||||
import { salesService } from '../../../../api/services/sales';
|
||||
import { bakeryColors } from '../../../../styles/colors';
|
||||
|
||||
// Import new POS components
|
||||
@@ -18,6 +19,7 @@ import { POSProductCard } from '../../../../components/domain/pos/POSProductCard
|
||||
import { POSCart } from '../../../../components/domain/pos/POSCart';
|
||||
import { POSPayment } from '../../../../components/domain/pos/POSPayment';
|
||||
import { CreatePOSConfigModal } from '../../../../components/domain/pos/CreatePOSConfigModal';
|
||||
import { POSSyncStatus } from '../../../../components/domain/pos/POSSyncStatus';
|
||||
|
||||
interface CartItem {
|
||||
id: string;
|
||||
@@ -752,17 +754,37 @@ const POSPage: React.FC = () => {
|
||||
const tax = subtotal * taxRate;
|
||||
const total = subtotal + tax;
|
||||
|
||||
const processPayment = (paymentData: any) => {
|
||||
const processPayment = async (paymentData: any) => {
|
||||
if (cart.length === 0) return;
|
||||
|
||||
console.log('Processing payment:', {
|
||||
cart,
|
||||
...paymentData,
|
||||
total,
|
||||
});
|
||||
try {
|
||||
// Create sales records for each item in the cart
|
||||
const saleDate = new Date().toISOString().split('T')[0];
|
||||
|
||||
setCart([]);
|
||||
showToast.success('Venta procesada exitosamente');
|
||||
for (const item of cart) {
|
||||
const salesData = {
|
||||
inventory_product_id: item.id, // Product ID for inventory tracking
|
||||
product_name: item.name,
|
||||
product_category: 'finished_product',
|
||||
quantity_sold: item.quantity,
|
||||
unit_price: item.price,
|
||||
total_amount: item.price * item.quantity,
|
||||
sale_date: saleDate,
|
||||
sales_channel: 'pos',
|
||||
source: 'manual_pos',
|
||||
payment_method: paymentData.method || 'cash',
|
||||
notes: paymentData.notes || 'Venta desde POS manual',
|
||||
};
|
||||
|
||||
await salesService.createSalesRecord(tenantId, salesData);
|
||||
}
|
||||
|
||||
setCart([]);
|
||||
showToast.success(`Venta procesada exitosamente: €${total.toFixed(2)}`);
|
||||
} catch (error: any) {
|
||||
console.error('Error processing payment:', error);
|
||||
showToast.error(error.response?.data?.detail || 'Error al procesar la venta');
|
||||
}
|
||||
};
|
||||
|
||||
// Loading and error states
|
||||
@@ -1030,6 +1052,11 @@ const POSPage: React.FC = () => {
|
||||
{posData.configurations.length > 0 && (
|
||||
<TransactionsSection tenantId={tenantId} />
|
||||
)}
|
||||
|
||||
{/* POS to Sales Sync Status - Only show if there are configurations */}
|
||||
{posData.configurations.length > 0 && (
|
||||
<POSSyncStatus tenantId={tenantId} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -135,78 +135,19 @@ const ProductionPage: React.FC = () => {
|
||||
// The QualityCheckModal should be enhanced to handle stage-specific checks
|
||||
};
|
||||
|
||||
// Helper function to generate mock process stage data for the selected batch
|
||||
const generateMockProcessStageData = (batch: ProductionBatchResponse) => {
|
||||
// Mock data based on batch status - this would come from the API in real implementation
|
||||
const mockProcessStage = {
|
||||
current: batch.status === ProductionStatusEnum.PENDING ? 'mixing' as const :
|
||||
batch.status === ProductionStatusEnum.IN_PROGRESS ? 'baking' as const :
|
||||
batch.status === ProductionStatusEnum.QUALITY_CHECK ? 'cooling' as const :
|
||||
'finishing' as const,
|
||||
history: batch.status !== ProductionStatusEnum.PENDING ? [
|
||||
{ stage: 'mixing' as const, timestamp: batch.actual_start_time || batch.planned_start_time, duration: 30 },
|
||||
...(batch.status === ProductionStatusEnum.IN_PROGRESS || batch.status === ProductionStatusEnum.QUALITY_CHECK || batch.status === ProductionStatusEnum.COMPLETED ? [
|
||||
{ stage: 'proofing' as const, timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(), duration: 90 },
|
||||
{ stage: 'shaping' as const, timestamp: new Date(Date.now() - 1 * 60 * 60 * 1000).toISOString(), duration: 15 }
|
||||
] : []),
|
||||
...(batch.status === ProductionStatusEnum.QUALITY_CHECK || batch.status === ProductionStatusEnum.COMPLETED ? [
|
||||
{ stage: 'baking' as const, timestamp: new Date(Date.now() - 30 * 60 * 1000).toISOString(), duration: 45 }
|
||||
] : [])
|
||||
] : [],
|
||||
pendingQualityChecks: batch.status === ProductionStatusEnum.IN_PROGRESS ? [
|
||||
{
|
||||
id: 'qc1',
|
||||
name: 'Control de temperatura interna',
|
||||
stage: 'baking' as const,
|
||||
isRequired: true,
|
||||
isCritical: true,
|
||||
status: 'pending' as const,
|
||||
checkType: 'temperature' as const
|
||||
}
|
||||
] : batch.status === ProductionStatusEnum.QUALITY_CHECK ? [
|
||||
{
|
||||
id: 'qc2',
|
||||
name: 'Inspección visual final',
|
||||
stage: 'cooling' as const,
|
||||
isRequired: true,
|
||||
isCritical: false,
|
||||
status: 'pending' as const,
|
||||
checkType: 'visual' as const
|
||||
}
|
||||
] : [],
|
||||
completedQualityChecks: batch.status === ProductionStatusEnum.COMPLETED ? [
|
||||
{
|
||||
id: 'qc1',
|
||||
name: 'Control de temperatura interna',
|
||||
stage: 'baking' as const,
|
||||
isRequired: true,
|
||||
isCritical: true,
|
||||
status: 'completed' as const,
|
||||
checkType: 'temperature' as const
|
||||
},
|
||||
{
|
||||
id: 'qc2',
|
||||
name: 'Inspección visual final',
|
||||
stage: 'cooling' as const,
|
||||
isRequired: true,
|
||||
isCritical: false,
|
||||
status: 'completed' as const,
|
||||
checkType: 'visual' as const
|
||||
}
|
||||
] : batch.status === ProductionStatusEnum.IN_PROGRESS ? [
|
||||
{
|
||||
id: 'qc3',
|
||||
name: 'Verificación de masa',
|
||||
stage: 'mixing' as const,
|
||||
isRequired: true,
|
||||
isCritical: false,
|
||||
status: 'completed' as const,
|
||||
checkType: 'visual' as const
|
||||
}
|
||||
] : []
|
||||
// Helper function to get process stage data from the batch (now from real backend data)
|
||||
const getProcessStageData = (batch: ProductionBatchResponse) => {
|
||||
// Backend now provides these fields in the API response:
|
||||
// - current_process_stage
|
||||
// - process_stage_history
|
||||
// - pending_quality_checks
|
||||
// - completed_quality_checks
|
||||
return {
|
||||
current: batch.current_process_stage || 'mixing',
|
||||
history: batch.process_stage_history || [],
|
||||
pendingQualityChecks: batch.pending_quality_checks || [],
|
||||
completedQualityChecks: batch.completed_quality_checks || []
|
||||
};
|
||||
|
||||
return mockProcessStage;
|
||||
};
|
||||
|
||||
|
||||
@@ -576,7 +517,7 @@ const ProductionPage: React.FC = () => {
|
||||
label: '',
|
||||
value: (
|
||||
<CompactProcessStageTracker
|
||||
processStage={generateMockProcessStageData(selectedBatch)}
|
||||
processStage={getProcessStageData(selectedBatch)}
|
||||
onAdvanceStage={(currentStage) => handleStageAdvance(selectedBatch.id, currentStage)}
|
||||
onQualityCheck={(checkId) => {
|
||||
setShowQualityModal(true);
|
||||
|
||||
Reference in New Issue
Block a user