ADD new frontend

This commit is contained in:
Urtzi Alfaro
2025-08-28 10:41:04 +02:00
parent 9c247a5f99
commit 0fd273cfce
492 changed files with 114979 additions and 1632 deletions

View File

@@ -0,0 +1,621 @@
/**
* Alerts hook for managing bakery alerts and notifications
*/
import { useState, useEffect, useCallback } from 'react';
import { InventoryService } from '../../services/api/inventory.service';
import { ProductionService } from '../../services/api/production.service';
import { NotificationService } from '../../services/api/notification.service';
export type AlertType = 'inventory' | 'production' | 'quality' | 'maintenance' | 'safety' | 'system' | 'custom';
export type AlertPriority = 'low' | 'medium' | 'high' | 'critical';
export type AlertStatus = 'active' | 'acknowledged' | 'resolved' | 'dismissed';
export interface Alert {
id: string;
type: AlertType;
priority: AlertPriority;
status: AlertStatus;
title: string;
message: string;
details?: Record<string, any>;
source: string;
sourceId?: string;
createdAt: Date;
updatedAt?: Date;
acknowledgedAt?: Date;
acknowledgedBy?: string;
resolvedAt?: Date;
resolvedBy?: string;
dismissedAt?: Date;
dismissedBy?: string;
expiresAt?: Date;
actions?: {
id: string;
label: string;
action: string;
parameters?: Record<string, any>;
}[];
metadata?: Record<string, any>;
}
export interface AlertRule {
id: string;
name: string;
type: AlertType;
priority: AlertPriority;
condition: {
field: string;
operator: 'equals' | 'not_equals' | 'greater_than' | 'less_than' | 'greater_or_equal' | 'less_or_equal' | 'contains' | 'not_contains';
value: any;
}[];
actions: string[];
enabled: boolean;
cooldownPeriod?: number; // in minutes
lastTriggered?: Date;
}
interface AlertsState {
alerts: Alert[];
rules: AlertRule[];
unreadCount: number;
criticalCount: number;
isLoading: boolean;
error: string | null;
filters: {
types: AlertType[];
priorities: AlertPriority[];
statuses: AlertStatus[];
sources: string[];
};
}
interface AlertsActions {
// Alert Management
fetchAlerts: (filters?: Partial<AlertsState['filters']>) => Promise<void>;
acknowledgeAlert: (id: string) => Promise<boolean>;
resolveAlert: (id: string, resolution?: string) => Promise<boolean>;
dismissAlert: (id: string, reason?: string) => Promise<boolean>;
bulkAcknowledge: (ids: string[]) => Promise<boolean>;
bulkResolve: (ids: string[], resolution?: string) => Promise<boolean>;
bulkDismiss: (ids: string[], reason?: string) => Promise<boolean>;
// Alert Creation
createAlert: (alert: Omit<Alert, 'id' | 'createdAt' | 'status'>) => Promise<boolean>;
createCustomAlert: (title: string, message: string, priority?: AlertPriority, details?: Record<string, any>) => Promise<boolean>;
// Alert Rules
fetchAlertRules: () => Promise<void>;
createAlertRule: (rule: Omit<AlertRule, 'id'>) => Promise<boolean>;
updateAlertRule: (id: string, rule: Partial<AlertRule>) => Promise<boolean>;
deleteAlertRule: (id: string) => Promise<boolean>;
testAlertRule: (rule: AlertRule) => Promise<boolean>;
// Monitoring and Checks
checkInventoryAlerts: () => Promise<void>;
checkProductionAlerts: () => Promise<void>;
checkQualityAlerts: () => Promise<void>;
checkMaintenanceAlerts: () => Promise<void>;
checkSystemAlerts: () => Promise<void>;
// Analytics
getAlertAnalytics: (period: string) => Promise<any>;
getAlertTrends: (days: number) => Promise<any>;
getMostFrequentAlerts: (period: string) => Promise<any>;
// Filters and Search
setFilters: (filters: Partial<AlertsState['filters']>) => void;
searchAlerts: (query: string) => Promise<Alert[]>;
// Real-time Updates
subscribeToAlerts: (callback: (alert: Alert) => void) => () => void;
// Utilities
clearError: () => void;
refresh: () => Promise<void>;
}
export const useAlerts = (): AlertsState & AlertsActions => {
const [state, setState] = useState<AlertsState>({
alerts: [],
rules: [],
unreadCount: 0,
criticalCount: 0,
isLoading: false,
error: null,
filters: {
types: [],
priorities: [],
statuses: [],
sources: [],
},
});
const inventoryService = new InventoryService();
const productionService = new ProductionService();
const notificationService = new NotificationService();
// Fetch alerts
const fetchAlerts = useCallback(async (filters?: Partial<AlertsState['filters']>) => {
setState(prev => ({ ...prev, isLoading: true, error: null }));
try {
// Combine filters
const activeFilters = { ...state.filters, ...filters };
// Fetch alerts from different sources
const [inventoryAlerts, productionAlerts, systemAlerts] = await Promise.all([
getInventoryAlerts(activeFilters),
getProductionAlerts(activeFilters),
getSystemAlerts(activeFilters),
]);
const allAlerts = [...inventoryAlerts, ...productionAlerts, ...systemAlerts]
.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
// Calculate counts
const unreadCount = allAlerts.filter(alert => alert.status === 'active').length;
const criticalCount = allAlerts.filter(alert => alert.priority === 'critical' && alert.status === 'active').length;
setState(prev => ({
...prev,
alerts: allAlerts,
unreadCount,
criticalCount,
isLoading: false,
filters: activeFilters,
}));
} catch (error) {
setState(prev => ({
...prev,
isLoading: false,
error: 'Error al cargar alertas',
}));
}
}, [state.filters]);
// Get inventory alerts
const getInventoryAlerts = async (filters: Partial<AlertsState['filters']>): Promise<Alert[]> => {
try {
const response = await inventoryService.getAlerts();
if (response.success && response.data) {
return response.data.map((alert: any) => convertToAlert(alert, 'inventory'));
}
} catch (error) {
console.error('Error fetching inventory alerts:', error);
}
return [];
};
// Get production alerts
const getProductionAlerts = async (filters: Partial<AlertsState['filters']>): Promise<Alert[]> => {
try {
const response = await productionService.getAlerts?.();
if (response?.success && response.data) {
return response.data.map((alert: any) => convertToAlert(alert, 'production'));
}
} catch (error) {
console.error('Error fetching production alerts:', error);
}
return [];
};
// Get system alerts
const getSystemAlerts = async (filters: Partial<AlertsState['filters']>): Promise<Alert[]> => {
try {
const response = await notificationService.getNotifications();
if (response.success && response.data) {
return response.data
.filter((notif: any) => notif.type === 'alert')
.map((alert: any) => convertToAlert(alert, 'system'));
}
} catch (error) {
console.error('Error fetching system alerts:', error);
}
return [];
};
// Convert API response to Alert format
const convertToAlert = (apiAlert: any, source: string): Alert => {
return {
id: apiAlert.id,
type: apiAlert.type || (source as AlertType),
priority: mapPriority(apiAlert.priority || apiAlert.severity),
status: mapStatus(apiAlert.status),
title: apiAlert.title || apiAlert.message?.substring(0, 50) || 'Alert',
message: apiAlert.message || apiAlert.description || '',
details: apiAlert.details || apiAlert.data,
source,
sourceId: apiAlert.source_id,
createdAt: new Date(apiAlert.created_at || Date.now()),
updatedAt: apiAlert.updated_at ? new Date(apiAlert.updated_at) : undefined,
acknowledgedAt: apiAlert.acknowledged_at ? new Date(apiAlert.acknowledged_at) : undefined,
acknowledgedBy: apiAlert.acknowledged_by,
resolvedAt: apiAlert.resolved_at ? new Date(apiAlert.resolved_at) : undefined,
resolvedBy: apiAlert.resolved_by,
dismissedAt: apiAlert.dismissed_at ? new Date(apiAlert.dismissed_at) : undefined,
dismissedBy: apiAlert.dismissed_by,
expiresAt: apiAlert.expires_at ? new Date(apiAlert.expires_at) : undefined,
actions: apiAlert.actions || [],
metadata: apiAlert.metadata || {},
};
};
// Map priority from different sources
const mapPriority = (priority: string): AlertPriority => {
const priorityMap: Record<string, AlertPriority> = {
'low': 'low',
'medium': 'medium',
'high': 'high',
'critical': 'critical',
'urgent': 'critical',
'warning': 'medium',
'error': 'high',
'info': 'low',
};
return priorityMap[priority?.toLowerCase()] || 'medium';
};
// Map status from different sources
const mapStatus = (status: string): AlertStatus => {
const statusMap: Record<string, AlertStatus> = {
'active': 'active',
'new': 'active',
'open': 'active',
'acknowledged': 'acknowledged',
'ack': 'acknowledged',
'resolved': 'resolved',
'closed': 'resolved',
'dismissed': 'dismissed',
'ignored': 'dismissed',
};
return statusMap[status?.toLowerCase()] || 'active';
};
// Acknowledge alert
const acknowledgeAlert = useCallback(async (id: string): Promise<boolean> => {
setState(prev => ({ ...prev, error: null }));
try {
const alert = state.alerts.find(a => a.id === id);
if (!alert) return false;
// Call appropriate service based on source
let success = false;
if (alert.source === 'inventory') {
const response = await inventoryService.markAlertAsRead(id);
success = response.success;
}
if (success) {
setState(prev => ({
...prev,
alerts: prev.alerts.map(a =>
a.id === id
? { ...a, status: 'acknowledged', acknowledgedAt: new Date() }
: a
),
unreadCount: Math.max(0, prev.unreadCount - 1),
}));
}
return success;
} catch (error) {
setState(prev => ({ ...prev, error: 'Error al confirmar alerta' }));
return false;
}
}, [state.alerts, inventoryService]);
// Resolve alert
const resolveAlert = useCallback(async (id: string, resolution?: string): Promise<boolean> => {
setState(prev => ({ ...prev, error: null }));
try {
const alert = state.alerts.find(a => a.id === id);
if (!alert) return false;
// Call appropriate service based on source
let success = false;
if (alert.source === 'inventory') {
const response = await inventoryService.dismissAlert(id);
success = response.success;
}
if (success) {
setState(prev => ({
...prev,
alerts: prev.alerts.map(a =>
a.id === id
? { ...a, status: 'resolved', resolvedAt: new Date(), metadata: { ...a.metadata, resolution } }
: a
),
unreadCount: a.status === 'active' ? Math.max(0, prev.unreadCount - 1) : prev.unreadCount,
criticalCount: a.priority === 'critical' && a.status === 'active' ? Math.max(0, prev.criticalCount - 1) : prev.criticalCount,
}));
}
return success;
} catch (error) {
setState(prev => ({ ...prev, error: 'Error al resolver alerta' }));
return false;
}
}, [state.alerts, inventoryService]);
// Dismiss alert
const dismissAlert = useCallback(async (id: string, reason?: string): Promise<boolean> => {
setState(prev => ({ ...prev, error: null }));
try {
const alert = state.alerts.find(a => a.id === id);
if (!alert) return false;
// Call appropriate service based on source
let success = false;
if (alert.source === 'inventory') {
const response = await inventoryService.dismissAlert(id);
success = response.success;
}
if (success) {
setState(prev => ({
...prev,
alerts: prev.alerts.map(a =>
a.id === id
? { ...a, status: 'dismissed', dismissedAt: new Date(), metadata: { ...a.metadata, dismissReason: reason } }
: a
),
}));
}
return success;
} catch (error) {
setState(prev => ({ ...prev, error: 'Error al descartar alerta' }));
return false;
}
}, [state.alerts, inventoryService]);
// Bulk acknowledge
const bulkAcknowledge = useCallback(async (ids: string[]): Promise<boolean> => {
const results = await Promise.all(ids.map(id => acknowledgeAlert(id)));
return results.every(result => result);
}, [acknowledgeAlert]);
// Bulk resolve
const bulkResolve = useCallback(async (ids: string[], resolution?: string): Promise<boolean> => {
const results = await Promise.all(ids.map(id => resolveAlert(id, resolution)));
return results.every(result => result);
}, [resolveAlert]);
// Bulk dismiss
const bulkDismiss = useCallback(async (ids: string[], reason?: string): Promise<boolean> => {
const results = await Promise.all(ids.map(id => dismissAlert(id, reason)));
return results.every(result => result);
}, [dismissAlert]);
// Create alert
const createAlert = useCallback(async (alert: Omit<Alert, 'id' | 'createdAt' | 'status'>): Promise<boolean> => {
setState(prev => ({ ...prev, error: null }));
try {
const newAlert: Alert = {
...alert,
id: generateAlertId(),
status: 'active',
createdAt: new Date(),
};
setState(prev => ({
...prev,
alerts: [newAlert, ...prev.alerts],
unreadCount: prev.unreadCount + 1,
criticalCount: newAlert.priority === 'critical' ? prev.criticalCount + 1 : prev.criticalCount,
}));
return true;
} catch (error) {
setState(prev => ({ ...prev, error: 'Error al crear alerta' }));
return false;
}
}, []);
// Create custom alert
const createCustomAlert = useCallback(async (
title: string,
message: string,
priority: AlertPriority = 'medium',
details?: Record<string, any>
): Promise<boolean> => {
return createAlert({
type: 'custom',
priority,
title,
message,
details,
source: 'user',
});
}, [createAlert]);
// Check inventory alerts
const checkInventoryAlerts = useCallback(async () => {
try {
// Check for low stock
const stockResponse = await inventoryService.getStockLevels();
if (stockResponse.success && stockResponse.data) {
const lowStockItems = stockResponse.data.filter((item: any) =>
item.current_quantity <= item.reorder_point
);
for (const item of lowStockItems) {
await createAlert({
type: 'inventory',
priority: item.current_quantity === 0 ? 'critical' : 'high',
title: 'Stock bajo',
message: `Stock bajo para ${item.ingredient?.name}: ${item.current_quantity} ${item.unit}`,
source: 'inventory',
sourceId: item.id,
details: { item },
});
}
}
// Check for expiring items
const expirationResponse = await inventoryService.getExpirationReport();
if (expirationResponse?.success && expirationResponse.data) {
const expiringItems = expirationResponse.data.expiring_soon || [];
for (const item of expiringItems) {
await createAlert({
type: 'inventory',
priority: 'medium',
title: 'Producto próximo a expirar',
message: `${item.name} expira el ${item.expiration_date}`,
source: 'inventory',
sourceId: item.id,
details: { item },
});
}
}
} catch (error) {
console.error('Error checking inventory alerts:', error);
}
}, [inventoryService, createAlert]);
// Generate unique ID
const generateAlertId = (): string => {
return `alert_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
};
// Set filters
const setFilters = useCallback((filters: Partial<AlertsState['filters']>) => {
setState(prev => ({
...prev,
filters: { ...prev.filters, ...filters },
}));
}, []);
// Clear error
const clearError = useCallback(() => {
setState(prev => ({ ...prev, error: null }));
}, []);
// Refresh alerts
const refresh = useCallback(async () => {
await fetchAlerts(state.filters);
}, [fetchAlerts, state.filters]);
// Placeholder implementations for remaining functions
const fetchAlertRules = useCallback(async () => {
setState(prev => ({ ...prev, rules: [] }));
}, []);
const createAlertRule = useCallback(async (rule: Omit<AlertRule, 'id'>): Promise<boolean> => {
return true;
}, []);
const updateAlertRule = useCallback(async (id: string, rule: Partial<AlertRule>): Promise<boolean> => {
return true;
}, []);
const deleteAlertRule = useCallback(async (id: string): Promise<boolean> => {
return true;
}, []);
const testAlertRule = useCallback(async (rule: AlertRule): Promise<boolean> => {
return true;
}, []);
const checkProductionAlerts = useCallback(async () => {
// Implementation for production alerts
}, []);
const checkQualityAlerts = useCallback(async () => {
// Implementation for quality alerts
}, []);
const checkMaintenanceAlerts = useCallback(async () => {
// Implementation for maintenance alerts
}, []);
const checkSystemAlerts = useCallback(async () => {
// Implementation for system alerts
}, []);
const getAlertAnalytics = useCallback(async (period: string): Promise<any> => {
return {};
}, []);
const getAlertTrends = useCallback(async (days: number): Promise<any> => {
return {};
}, []);
const getMostFrequentAlerts = useCallback(async (period: string): Promise<any> => {
return {};
}, []);
const searchAlerts = useCallback(async (query: string): Promise<Alert[]> => {
return state.alerts.filter(alert =>
alert.title.toLowerCase().includes(query.toLowerCase()) ||
alert.message.toLowerCase().includes(query.toLowerCase())
);
}, [state.alerts]);
const subscribeToAlerts = useCallback((callback: (alert: Alert) => void): (() => void) => {
// Implementation would set up real-time subscription
return () => {
// Cleanup subscription
};
}, []);
// Initialize alerts on mount
useEffect(() => {
fetchAlerts();
}, []);
// Set up periodic checks
useEffect(() => {
const interval = setInterval(() => {
checkInventoryAlerts();
}, 5 * 60 * 1000); // Check every 5 minutes
return () => clearInterval(interval);
}, [checkInventoryAlerts]);
return {
...state,
fetchAlerts,
acknowledgeAlert,
resolveAlert,
dismissAlert,
bulkAcknowledge,
bulkResolve,
bulkDismiss,
createAlert,
createCustomAlert,
fetchAlertRules,
createAlertRule,
updateAlertRule,
deleteAlertRule,
testAlertRule,
checkInventoryAlerts,
checkProductionAlerts,
checkQualityAlerts,
checkMaintenanceAlerts,
checkSystemAlerts,
getAlertAnalytics,
getAlertTrends,
getMostFrequentAlerts,
setFilters,
searchAlerts,
subscribeToAlerts,
clearError,
refresh,
};
};

View File

@@ -0,0 +1,520 @@
/**
* Bakery workflow hook for managing daily bakery operations and processes
*/
import { useState, useEffect, useCallback } from 'react';
import { ProductionService } from '../../services/api/production.service';
import { InventoryService } from '../../services/api/inventory.service';
import { SalesService } from '../../services/api/sales.service';
import { OrderService } from '../../services/api/order.service';
export interface WorkflowStep {
id: string;
name: string;
description: string;
status: 'pending' | 'in_progress' | 'completed' | 'failed';
priority: 'low' | 'medium' | 'high' | 'urgent';
estimatedDuration: number; // in minutes
actualDuration?: number;
dependencies?: string[];
assignedTo?: string;
startTime?: Date;
endTime?: Date;
notes?: string;
}
export interface DailyWorkflow {
date: string;
steps: WorkflowStep[];
totalEstimatedTime: number;
totalActualTime: number;
completionRate: number;
status: 'not_started' | 'in_progress' | 'completed' | 'behind_schedule';
}
interface BakeryWorkflowState {
currentWorkflow: DailyWorkflow | null;
workflowHistory: DailyWorkflow[];
activeStep: WorkflowStep | null;
isLoading: boolean;
error: string | null;
}
interface BakeryWorkflowActions {
// Workflow Management
initializeDailyWorkflow: (date: string) => Promise<void>;
generateWorkflowFromForecast: (date: string) => Promise<void>;
updateWorkflow: (workflow: Partial<DailyWorkflow>) => Promise<boolean>;
// Step Management
startStep: (stepId: string) => Promise<boolean>;
completeStep: (stepId: string, notes?: string) => Promise<boolean>;
failStep: (stepId: string, reason: string) => Promise<boolean>;
skipStep: (stepId: string, reason: string) => Promise<boolean>;
updateStepProgress: (stepId: string, progress: Partial<WorkflowStep>) => Promise<boolean>;
// Workflow Templates
createWorkflowTemplate: (name: string, steps: Omit<WorkflowStep, 'id' | 'status'>[]) => Promise<boolean>;
loadWorkflowTemplate: (templateId: string, date: string) => Promise<boolean>;
getWorkflowTemplates: () => Promise<any[]>;
// Analytics and Optimization
getWorkflowAnalytics: (startDate: string, endDate: string) => Promise<any>;
getBottleneckAnalysis: (period: string) => Promise<any>;
getEfficiencyReport: (date: string) => Promise<any>;
// Real-time Updates
subscribeToWorkflowUpdates: (callback: (workflow: DailyWorkflow) => void) => () => void;
// Utilities
clearError: () => void;
refresh: () => Promise<void>;
}
export const useBakeryWorkflow = (): BakeryWorkflowState & BakeryWorkflowActions => {
const [state, setState] = useState<BakeryWorkflowState>({
currentWorkflow: null,
workflowHistory: [],
activeStep: null,
isLoading: false,
error: null,
});
const productionService = new ProductionService();
const inventoryService = new InventoryService();
const salesService = new SalesService();
const orderService = new OrderService();
// Initialize daily workflow
const initializeDailyWorkflow = useCallback(async (date: string) => {
setState(prev => ({ ...prev, isLoading: true, error: null }));
try {
// Get existing workflow or create new one
const existingWorkflow = await getWorkflowForDate(date);
if (existingWorkflow) {
setState(prev => ({
...prev,
currentWorkflow: existingWorkflow,
activeStep: findActiveStep(existingWorkflow.steps),
isLoading: false,
}));
} else {
await generateWorkflowFromForecast(date);
}
} catch (error) {
setState(prev => ({
...prev,
isLoading: false,
error: 'Error al inicializar flujo de trabajo diario',
}));
}
}, []);
// Generate workflow from forecast and orders
const generateWorkflowFromForecast = useCallback(async (date: string) => {
setState(prev => ({ ...prev, isLoading: true, error: null }));
try {
// Get daily forecast and orders
const [forecastData, ordersData, inventoryData] = await Promise.all([
productionService.getDailyForecast?.(date),
orderService.getOrdersByDate?.(date),
inventoryService.getStockLevels(),
]);
const workflow = generateWorkflowSteps(forecastData, ordersData, inventoryData);
setState(prev => ({
...prev,
currentWorkflow: workflow,
activeStep: workflow.steps.find(step => step.status === 'pending') || null,
isLoading: false,
}));
} catch (error) {
setState(prev => ({
...prev,
isLoading: false,
error: 'Error al generar flujo de trabajo desde predicción',
}));
}
}, [productionService, orderService, inventoryService]);
// Generate workflow steps based on data
const generateWorkflowSteps = (forecastData: any, ordersData: any, inventoryData: any): DailyWorkflow => {
const steps: WorkflowStep[] = [
// Morning prep
{
id: 'morning_prep',
name: 'Preparación matutina',
description: 'Verificar equipos, ingredientes y planificación del día',
status: 'pending',
priority: 'high',
estimatedDuration: 30,
},
// Inventory check
{
id: 'inventory_check',
name: 'Control de inventario',
description: 'Verificar niveles de stock y calidad de ingredientes',
status: 'pending',
priority: 'high',
estimatedDuration: 20,
dependencies: ['morning_prep'],
},
// Production preparation
{
id: 'production_prep',
name: 'Preparación de producción',
description: 'Preparar ingredientes y configurar equipos',
status: 'pending',
priority: 'high',
estimatedDuration: 45,
dependencies: ['inventory_check'],
},
// Production batches (generated from forecast)
...generateProductionSteps(forecastData),
// Quality control
{
id: 'quality_control',
name: 'Control de calidad',
description: 'Verificar calidad de productos terminados',
status: 'pending',
priority: 'medium',
estimatedDuration: 30,
dependencies: ['production_prep'],
},
// Order fulfillment
...generateOrderSteps(ordersData),
// End of day cleanup
{
id: 'cleanup',
name: 'Limpieza final',
description: 'Limpieza de equipos y área de trabajo',
status: 'pending',
priority: 'medium',
estimatedDuration: 45,
},
// Daily reporting
{
id: 'daily_report',
name: 'Reporte diario',
description: 'Completar reportes de producción y ventas',
status: 'pending',
priority: 'low',
estimatedDuration: 15,
dependencies: ['cleanup'],
},
];
const totalEstimatedTime = steps.reduce((total, step) => total + step.estimatedDuration, 0);
return {
date: new Date().toISOString().split('T')[0],
steps,
totalEstimatedTime,
totalActualTime: 0,
completionRate: 0,
status: 'not_started',
};
};
// Generate production steps from forecast
const generateProductionSteps = (forecastData: any): WorkflowStep[] => {
if (!forecastData || !forecastData.products) return [];
return forecastData.products.map((product: any, index: number) => ({
id: `production_${product.id}`,
name: `Producir ${product.name}`,
description: `Producir ${product.estimated_quantity} unidades de ${product.name}`,
status: 'pending' as const,
priority: product.priority || 'medium' as const,
estimatedDuration: product.production_time || 60,
dependencies: ['production_prep'],
}));
};
// Generate order fulfillment steps
const generateOrderSteps = (ordersData: any): WorkflowStep[] => {
if (!ordersData || !ordersData.length) return [];
const specialOrders = ordersData.filter((order: any) => order.type === 'special' || order.priority === 'high');
return specialOrders.map((order: any) => ({
id: `order_${order.id}`,
name: `Preparar pedido especial`,
description: `Preparar pedido #${order.id} - ${order.items?.length || 0} items`,
status: 'pending' as const,
priority: order.priority || 'medium' as const,
estimatedDuration: order.preparation_time || 30,
dependencies: ['production_prep'],
}));
};
// Find active step
const findActiveStep = (steps: WorkflowStep[]): WorkflowStep | null => {
return steps.find(step => step.status === 'in_progress') ||
steps.find(step => step.status === 'pending') ||
null;
};
// Get workflow for specific date
const getWorkflowForDate = async (date: string): Promise<DailyWorkflow | null> => {
// This would typically fetch from an API
// For now, return null to force generation
return null;
};
// Start a workflow step
const startStep = useCallback(async (stepId: string): Promise<boolean> => {
if (!state.currentWorkflow) return false;
try {
const updatedWorkflow = {
...state.currentWorkflow,
steps: state.currentWorkflow.steps.map(step =>
step.id === stepId
? { ...step, status: 'in_progress' as const, startTime: new Date() }
: step
),
};
setState(prev => ({
...prev,
currentWorkflow: updatedWorkflow,
activeStep: updatedWorkflow.steps.find(step => step.id === stepId) || null,
}));
return true;
} catch (error) {
setState(prev => ({ ...prev, error: 'Error al iniciar paso del flujo de trabajo' }));
return false;
}
}, [state.currentWorkflow]);
// Complete a workflow step
const completeStep = useCallback(async (stepId: string, notes?: string): Promise<boolean> => {
if (!state.currentWorkflow) return false;
try {
const step = state.currentWorkflow.steps.find(s => s.id === stepId);
if (!step || !step.startTime) return false;
const endTime = new Date();
const actualDuration = Math.round((endTime.getTime() - step.startTime.getTime()) / 60000);
const updatedWorkflow = {
...state.currentWorkflow,
steps: state.currentWorkflow.steps.map(s =>
s.id === stepId
? {
...s,
status: 'completed' as const,
endTime,
actualDuration,
notes: notes || s.notes,
}
: s
),
};
// Update completion rate
const completedSteps = updatedWorkflow.steps.filter(s => s.status === 'completed');
updatedWorkflow.completionRate = (completedSteps.length / updatedWorkflow.steps.length) * 100;
updatedWorkflow.totalActualTime = updatedWorkflow.steps
.filter(s => s.actualDuration)
.reduce((total, s) => total + (s.actualDuration || 0), 0);
setState(prev => ({
...prev,
currentWorkflow: updatedWorkflow,
activeStep: findActiveStep(updatedWorkflow.steps),
}));
return true;
} catch (error) {
setState(prev => ({ ...prev, error: 'Error al completar paso del flujo de trabajo' }));
return false;
}
}, [state.currentWorkflow]);
// Fail a workflow step
const failStep = useCallback(async (stepId: string, reason: string): Promise<boolean> => {
if (!state.currentWorkflow) return false;
try {
const updatedWorkflow = {
...state.currentWorkflow,
steps: state.currentWorkflow.steps.map(step =>
step.id === stepId
? { ...step, status: 'failed' as const, notes: reason }
: step
),
};
setState(prev => ({
...prev,
currentWorkflow: updatedWorkflow,
activeStep: findActiveStep(updatedWorkflow.steps),
}));
return true;
} catch (error) {
setState(prev => ({ ...prev, error: 'Error al marcar paso como fallido' }));
return false;
}
}, [state.currentWorkflow]);
// Skip a workflow step
const skipStep = useCallback(async (stepId: string, reason: string): Promise<boolean> => {
if (!state.currentWorkflow) return false;
try {
const updatedWorkflow = {
...state.currentWorkflow,
steps: state.currentWorkflow.steps.map(step =>
step.id === stepId
? { ...step, status: 'completed' as const, notes: `Omitido: ${reason}` }
: step
),
};
setState(prev => ({
...prev,
currentWorkflow: updatedWorkflow,
activeStep: findActiveStep(updatedWorkflow.steps),
}));
return true;
} catch (error) {
setState(prev => ({ ...prev, error: 'Error al omitir paso del flujo de trabajo' }));
return false;
}
}, [state.currentWorkflow]);
// Update workflow
const updateWorkflow = useCallback(async (workflow: Partial<DailyWorkflow>): Promise<boolean> => {
if (!state.currentWorkflow) return false;
try {
const updatedWorkflow = { ...state.currentWorkflow, ...workflow };
setState(prev => ({
...prev,
currentWorkflow: updatedWorkflow,
}));
return true;
} catch (error) {
setState(prev => ({ ...prev, error: 'Error al actualizar flujo de trabajo' }));
return false;
}
}, [state.currentWorkflow]);
// Update step progress
const updateStepProgress = useCallback(async (stepId: string, progress: Partial<WorkflowStep>): Promise<boolean> => {
if (!state.currentWorkflow) return false;
try {
const updatedWorkflow = {
...state.currentWorkflow,
steps: state.currentWorkflow.steps.map(step =>
step.id === stepId ? { ...step, ...progress } : step
),
};
setState(prev => ({
...prev,
currentWorkflow: updatedWorkflow,
}));
return true;
} catch (error) {
setState(prev => ({ ...prev, error: 'Error al actualizar progreso del paso' }));
return false;
}
}, [state.currentWorkflow]);
// Placeholder implementations for template and analytics functions
const createWorkflowTemplate = useCallback(async (name: string, steps: Omit<WorkflowStep, 'id' | 'status'>[]): Promise<boolean> => {
// Implementation would save template to backend
return true;
}, []);
const loadWorkflowTemplate = useCallback(async (templateId: string, date: string): Promise<boolean> => {
// Implementation would load template from backend
return true;
}, []);
const getWorkflowTemplates = useCallback(async (): Promise<any[]> => {
// Implementation would fetch templates from backend
return [];
}, []);
const getWorkflowAnalytics = useCallback(async (startDate: string, endDate: string): Promise<any> => {
// Implementation would fetch analytics from backend
return {};
}, []);
const getBottleneckAnalysis = useCallback(async (period: string): Promise<any> => {
// Implementation would analyze bottlenecks
return {};
}, []);
const getEfficiencyReport = useCallback(async (date: string): Promise<any> => {
// Implementation would generate efficiency report
return {};
}, []);
const subscribeToWorkflowUpdates = useCallback((callback: (workflow: DailyWorkflow) => void): (() => void) => {
// Implementation would set up real-time subscription
return () => {
// Cleanup subscription
};
}, []);
const clearError = useCallback(() => {
setState(prev => ({ ...prev, error: null }));
}, []);
const refresh = useCallback(async () => {
if (state.currentWorkflow) {
await initializeDailyWorkflow(state.currentWorkflow.date);
}
}, [state.currentWorkflow, initializeDailyWorkflow]);
// Initialize workflow for today on mount
useEffect(() => {
const today = new Date().toISOString().split('T')[0];
initializeDailyWorkflow(today);
}, [initializeDailyWorkflow]);
return {
...state,
initializeDailyWorkflow,
generateWorkflowFromForecast,
updateWorkflow,
startStep,
completeStep,
failStep,
skipStep,
updateStepProgress,
createWorkflowTemplate,
loadWorkflowTemplate,
getWorkflowTemplates,
getWorkflowAnalytics,
getBottleneckAnalysis,
getEfficiencyReport,
subscribeToWorkflowUpdates,
clearError,
refresh,
};
};

View File

@@ -0,0 +1,655 @@
/**
* Production schedule hook for managing bakery production scheduling and capacity planning
*/
import { useState, useEffect, useCallback } from 'react';
import { ProductionService } from '../../services/api/production.service';
import { InventoryService } from '../../services/api/inventory.service';
import { ForecastingService } from '../../services/api/forecasting.service';
export interface ScheduleItem {
id: string;
recipeId: string;
recipeName: string;
quantity: number;
priority: 'low' | 'medium' | 'high' | 'urgent';
estimatedStartTime: Date;
estimatedEndTime: Date;
actualStartTime?: Date;
actualEndTime?: Date;
status: 'scheduled' | 'in_progress' | 'completed' | 'cancelled' | 'delayed';
assignedEquipment?: string[];
assignedStaff?: string[];
requiredIngredients: {
ingredientId: string;
ingredientName: string;
requiredQuantity: number;
availableQuantity: number;
unit: string;
}[];
notes?: string;
dependencies?: string[];
}
export interface ProductionSlot {
startTime: Date;
endTime: Date;
isAvailable: boolean;
assignedItems: ScheduleItem[];
capacity: number;
utilizationRate: number;
}
export interface DailySchedule {
date: string;
items: ScheduleItem[];
slots: ProductionSlot[];
totalCapacity: number;
totalUtilization: number;
efficiency: number;
bottlenecks: string[];
}
interface ProductionScheduleState {
currentSchedule: DailySchedule | null;
scheduleHistory: DailySchedule[];
availableRecipes: any[];
equipmentStatus: any[];
staffAvailability: any[];
isLoading: boolean;
error: string | null;
constraints: {
maxDailyCapacity: number;
workingHours: { start: string; end: string };
equipmentLimitations: Record<string, number>;
staffLimitations: Record<string, number>;
};
}
interface ProductionScheduleActions {
// Schedule Management
loadSchedule: (date: string) => Promise<void>;
createSchedule: (date: string) => Promise<void>;
updateSchedule: (schedule: Partial<DailySchedule>) => Promise<boolean>;
// Schedule Items
addScheduleItem: (item: Omit<ScheduleItem, 'id'>) => Promise<boolean>;
updateScheduleItem: (id: string, item: Partial<ScheduleItem>) => Promise<boolean>;
removeScheduleItem: (id: string) => Promise<boolean>;
moveScheduleItem: (id: string, newStartTime: Date) => Promise<boolean>;
// Automatic Scheduling
autoSchedule: (date: string, items: Omit<ScheduleItem, 'id'>[]) => Promise<boolean>;
optimizeSchedule: (date: string) => Promise<boolean>;
generateFromForecast: (date: string) => Promise<boolean>;
// Capacity Management
checkCapacity: (date: string, newItem: Omit<ScheduleItem, 'id'>) => Promise<{ canSchedule: boolean; suggestedTime?: Date; conflicts?: string[] }>;
getAvailableSlots: (date: string, duration: number) => Promise<ProductionSlot[]>;
calculateUtilization: (date: string) => Promise<number>;
// Resource Management
checkIngredientAvailability: (items: ScheduleItem[]) => Promise<{ available: boolean; shortages: any[] }>;
checkEquipmentAvailability: (date: string, equipment: string[], timeSlot: { start: Date; end: Date }) => Promise<boolean>;
checkStaffAvailability: (date: string, staff: string[], timeSlot: { start: Date; end: Date }) => Promise<boolean>;
// Analytics and Optimization
getScheduleAnalytics: (startDate: string, endDate: string) => Promise<any>;
getBottleneckAnalysis: (date: string) => Promise<any>;
getEfficiencyReport: (period: string) => Promise<any>;
predictDelays: (date: string) => Promise<any>;
// Templates and Presets
saveScheduleTemplate: (name: string, template: Omit<ScheduleItem, 'id'>[]) => Promise<boolean>;
loadScheduleTemplate: (templateId: string, date: string) => Promise<boolean>;
getScheduleTemplates: () => Promise<any[]>;
// Utilities
clearError: () => void;
refresh: () => Promise<void>;
}
export const useProductionSchedule = (): ProductionScheduleState & ProductionScheduleActions => {
const [state, setState] = useState<ProductionScheduleState>({
currentSchedule: null,
scheduleHistory: [],
availableRecipes: [],
equipmentStatus: [],
staffAvailability: [],
isLoading: false,
error: null,
constraints: {
maxDailyCapacity: 8 * 60, // 8 hours in minutes
workingHours: { start: '06:00', end: '20:00' },
equipmentLimitations: {},
staffLimitations: {},
},
});
const productionService = new ProductionService();
const inventoryService = new InventoryService();
const forecastingService = new ForecastingService();
// Load schedule for specific date
const loadSchedule = useCallback(async (date: string) => {
setState(prev => ({ ...prev, isLoading: true, error: null }));
try {
// Get schedule data from API
const scheduleData = await getScheduleFromAPI(date);
if (scheduleData) {
setState(prev => ({
...prev,
currentSchedule: scheduleData,
isLoading: false,
}));
} else {
// Create new schedule if none exists
await createSchedule(date);
}
} catch (error) {
setState(prev => ({
...prev,
isLoading: false,
error: 'Error al cargar programación de producción',
}));
}
}, []);
// Create new schedule
const createSchedule = useCallback(async (date: string) => {
setState(prev => ({ ...prev, isLoading: true, error: null }));
try {
const workingHours = generateWorkingHours(date, state.constraints.workingHours);
const slots = generateTimeSlots(workingHours, 30); // 30-minute slots
const newSchedule: DailySchedule = {
date,
items: [],
slots,
totalCapacity: state.constraints.maxDailyCapacity,
totalUtilization: 0,
efficiency: 0,
bottlenecks: [],
};
setState(prev => ({
...prev,
currentSchedule: newSchedule,
isLoading: false,
}));
} catch (error) {
setState(prev => ({
...prev,
isLoading: false,
error: 'Error al crear nueva programación',
}));
}
}, [state.constraints]);
// Generate working hours for a date
const generateWorkingHours = (date: string, workingHours: { start: string; end: string }) => {
const startTime = new Date(`${date}T${workingHours.start}`);
const endTime = new Date(`${date}T${workingHours.end}`);
return { startTime, endTime };
};
// Generate time slots
const generateTimeSlots = (workingHours: { startTime: Date; endTime: Date }, slotDuration: number): ProductionSlot[] => {
const slots: ProductionSlot[] = [];
const current = new Date(workingHours.startTime);
while (current < workingHours.endTime) {
const slotEnd = new Date(current.getTime() + slotDuration * 60000);
slots.push({
startTime: new Date(current),
endTime: slotEnd,
isAvailable: true,
assignedItems: [],
capacity: 1,
utilizationRate: 0,
});
current.setTime(slotEnd.getTime());
}
return slots;
};
// Add schedule item
const addScheduleItem = useCallback(async (item: Omit<ScheduleItem, 'id'>): Promise<boolean> => {
if (!state.currentSchedule) return false;
setState(prev => ({ ...prev, error: null }));
try {
// Check capacity and resources
const capacityCheck = await checkCapacity(state.currentSchedule.date, item);
if (!capacityCheck.canSchedule) {
setState(prev => ({
...prev,
error: `No se puede programar: ${capacityCheck.conflicts?.join(', ')}`,
}));
return false;
}
const newItem: ScheduleItem = {
...item,
id: generateScheduleItemId(),
};
const updatedSchedule = {
...state.currentSchedule,
items: [...state.currentSchedule.items, newItem],
};
// Recalculate utilization and efficiency
recalculateScheduleMetrics(updatedSchedule);
setState(prev => ({
...prev,
currentSchedule: updatedSchedule,
}));
return true;
} catch (error) {
setState(prev => ({ ...prev, error: 'Error al agregar item a la programación' }));
return false;
}
}, [state.currentSchedule]);
// Update schedule item
const updateScheduleItem = useCallback(async (id: string, item: Partial<ScheduleItem>): Promise<boolean> => {
if (!state.currentSchedule) return false;
try {
const updatedSchedule = {
...state.currentSchedule,
items: state.currentSchedule.items.map(scheduleItem =>
scheduleItem.id === id ? { ...scheduleItem, ...item } : scheduleItem
),
};
recalculateScheduleMetrics(updatedSchedule);
setState(prev => ({
...prev,
currentSchedule: updatedSchedule,
}));
return true;
} catch (error) {
setState(prev => ({ ...prev, error: 'Error al actualizar item de programación' }));
return false;
}
}, [state.currentSchedule]);
// Remove schedule item
const removeScheduleItem = useCallback(async (id: string): Promise<boolean> => {
if (!state.currentSchedule) return false;
try {
const updatedSchedule = {
...state.currentSchedule,
items: state.currentSchedule.items.filter(item => item.id !== id),
};
recalculateScheduleMetrics(updatedSchedule);
setState(prev => ({
...prev,
currentSchedule: updatedSchedule,
}));
return true;
} catch (error) {
setState(prev => ({ ...prev, error: 'Error al eliminar item de programación' }));
return false;
}
}, [state.currentSchedule]);
// Move schedule item to new time
const moveScheduleItem = useCallback(async (id: string, newStartTime: Date): Promise<boolean> => {
if (!state.currentSchedule) return false;
const item = state.currentSchedule.items.find(item => item.id === id);
if (!item) return false;
const duration = item.estimatedEndTime.getTime() - item.estimatedStartTime.getTime();
const newEndTime = new Date(newStartTime.getTime() + duration);
return updateScheduleItem(id, {
estimatedStartTime: newStartTime,
estimatedEndTime: newEndTime,
});
}, [state.currentSchedule, updateScheduleItem]);
// Auto-schedule items
const autoSchedule = useCallback(async (date: string, items: Omit<ScheduleItem, 'id'>[]): Promise<boolean> => {
setState(prev => ({ ...prev, isLoading: true, error: null }));
try {
// Sort items by priority and estimated duration
const sortedItems = [...items].sort((a, b) => {
const priorityOrder = { urgent: 4, high: 3, medium: 2, low: 1 };
return priorityOrder[b.priority] - priorityOrder[a.priority];
});
const schedule = await createOptimalSchedule(date, sortedItems);
setState(prev => ({
...prev,
currentSchedule: schedule,
isLoading: false,
}));
return true;
} catch (error) {
setState(prev => ({
...prev,
isLoading: false,
error: 'Error al programar automáticamente',
}));
return false;
}
}, []);
// Create optimal schedule
const createOptimalSchedule = async (date: string, items: Omit<ScheduleItem, 'id'>[]): Promise<DailySchedule> => {
const workingHours = generateWorkingHours(date, state.constraints.workingHours);
const slots = generateTimeSlots(workingHours, 30);
const scheduledItems: ScheduleItem[] = [];
let currentTime = new Date(workingHours.startTime);
for (const item of items) {
const duration = item.estimatedEndTime.getTime() - item.estimatedStartTime.getTime();
const endTime = new Date(currentTime.getTime() + duration);
// Check if item fits in remaining time
if (endTime <= workingHours.endTime) {
scheduledItems.push({
...item,
id: generateScheduleItemId(),
estimatedStartTime: new Date(currentTime),
estimatedEndTime: endTime,
});
currentTime = endTime;
}
}
const schedule: DailySchedule = {
date,
items: scheduledItems,
slots,
totalCapacity: state.constraints.maxDailyCapacity,
totalUtilization: 0,
efficiency: 0,
bottlenecks: [],
};
recalculateScheduleMetrics(schedule);
return schedule;
};
// Check capacity for new item
const checkCapacity = useCallback(async (date: string, newItem: Omit<ScheduleItem, 'id'>) => {
const conflicts: string[] = [];
let canSchedule = true;
// Check time conflicts
if (state.currentSchedule) {
const hasTimeConflict = state.currentSchedule.items.some(item => {
return (newItem.estimatedStartTime < item.estimatedEndTime &&
newItem.estimatedEndTime > item.estimatedStartTime);
});
if (hasTimeConflict) {
conflicts.push('Conflicto de horario');
canSchedule = false;
}
}
// Check ingredient availability
const ingredientCheck = await checkIngredientAvailability([newItem as ScheduleItem]);
if (!ingredientCheck.available) {
conflicts.push('Ingredientes insuficientes');
canSchedule = false;
}
return { canSchedule, conflicts };
}, [state.currentSchedule]);
// Get available slots
const getAvailableSlots = useCallback(async (date: string, duration: number): Promise<ProductionSlot[]> => {
if (!state.currentSchedule) return [];
return state.currentSchedule.slots.filter(slot => {
const slotDuration = slot.endTime.getTime() - slot.startTime.getTime();
return slot.isAvailable && slotDuration >= duration * 60000;
});
}, [state.currentSchedule]);
// Check ingredient availability
const checkIngredientAvailability = useCallback(async (items: ScheduleItem[]) => {
try {
const stockLevels = await inventoryService.getStockLevels();
const shortages: any[] = [];
for (const item of items) {
for (const ingredient of item.requiredIngredients) {
const stock = stockLevels.data?.find((s: any) => s.ingredient_id === ingredient.ingredientId);
if (!stock || stock.current_quantity < ingredient.requiredQuantity) {
shortages.push({
ingredientName: ingredient.ingredientName,
required: ingredient.requiredQuantity,
available: stock?.current_quantity || 0,
});
}
}
}
return { available: shortages.length === 0, shortages };
} catch (error) {
return { available: false, shortages: [] };
}
}, [inventoryService]);
// Generate from forecast
const generateFromForecast = useCallback(async (date: string): Promise<boolean> => {
setState(prev => ({ ...prev, isLoading: true, error: null }));
try {
// Get forecast data
const forecast = await forecastingService.generateDemandForecast('default', 1);
if (!forecast) {
setState(prev => ({
...prev,
isLoading: false,
error: 'No se pudo obtener predicción de demanda',
}));
return false;
}
// Convert forecast to schedule items
const items = convertForecastToScheduleItems(forecast);
// Auto-schedule the items
return await autoSchedule(date, items);
} catch (error) {
setState(prev => ({
...prev,
isLoading: false,
error: 'Error al generar programación desde predicción',
}));
return false;
}
}, [forecastingService, autoSchedule]);
// Convert forecast to schedule items
const convertForecastToScheduleItems = (forecast: any): Omit<ScheduleItem, 'id'>[] => {
if (!forecast.products) return [];
return forecast.products.map((product: any) => ({
recipeId: product.recipe_id || `recipe_${product.id}`,
recipeName: product.name,
quantity: product.estimated_quantity || 1,
priority: 'medium' as const,
estimatedStartTime: new Date(),
estimatedEndTime: new Date(Date.now() + (product.production_time || 60) * 60000),
status: 'scheduled' as const,
requiredIngredients: product.ingredients || [],
}));
};
// Recalculate schedule metrics
const recalculateScheduleMetrics = (schedule: DailySchedule) => {
const totalScheduledTime = schedule.items.reduce((total, item) => {
return total + (item.estimatedEndTime.getTime() - item.estimatedStartTime.getTime());
}, 0);
schedule.totalUtilization = (totalScheduledTime / (schedule.totalCapacity * 60000)) * 100;
schedule.efficiency = calculateEfficiency(schedule.items);
schedule.bottlenecks = identifyBottlenecks(schedule.items);
};
// Calculate efficiency
const calculateEfficiency = (items: ScheduleItem[]): number => {
if (items.length === 0) return 0;
const completedItems = items.filter(item => item.status === 'completed');
return (completedItems.length / items.length) * 100;
};
// Identify bottlenecks
const identifyBottlenecks = (items: ScheduleItem[]): string[] => {
const bottlenecks: string[] = [];
// Check for equipment conflicts
const equipmentUsage: Record<string, number> = {};
items.forEach(item => {
item.assignedEquipment?.forEach(equipment => {
equipmentUsage[equipment] = (equipmentUsage[equipment] || 0) + 1;
});
});
Object.entries(equipmentUsage).forEach(([equipment, usage]) => {
if (usage > 1) {
bottlenecks.push(`Conflicto de equipamiento: ${equipment}`);
}
});
return bottlenecks;
};
// Generate unique ID
const generateScheduleItemId = (): string => {
return `schedule_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
};
// Get schedule from API (placeholder)
const getScheduleFromAPI = async (date: string): Promise<DailySchedule | null> => {
// This would fetch from actual API
return null;
};
// Placeholder implementations for remaining functions
const updateSchedule = useCallback(async (schedule: Partial<DailySchedule>): Promise<boolean> => {
return true;
}, []);
const optimizeSchedule = useCallback(async (date: string): Promise<boolean> => {
return true;
}, []);
const calculateUtilization = useCallback(async (date: string): Promise<number> => {
return state.currentSchedule?.totalUtilization || 0;
}, [state.currentSchedule]);
const checkEquipmentAvailability = useCallback(async (date: string, equipment: string[], timeSlot: { start: Date; end: Date }): Promise<boolean> => {
return true;
}, []);
const checkStaffAvailability = useCallback(async (date: string, staff: string[], timeSlot: { start: Date; end: Date }): Promise<boolean> => {
return true;
}, []);
const getScheduleAnalytics = useCallback(async (startDate: string, endDate: string): Promise<any> => {
return {};
}, []);
const getBottleneckAnalysis = useCallback(async (date: string): Promise<any> => {
return {};
}, []);
const getEfficiencyReport = useCallback(async (period: string): Promise<any> => {
return {};
}, []);
const predictDelays = useCallback(async (date: string): Promise<any> => {
return {};
}, []);
const saveScheduleTemplate = useCallback(async (name: string, template: Omit<ScheduleItem, 'id'>[]): Promise<boolean> => {
return true;
}, []);
const loadScheduleTemplate = useCallback(async (templateId: string, date: string): Promise<boolean> => {
return true;
}, []);
const getScheduleTemplates = useCallback(async (): Promise<any[]> => {
return [];
}, []);
const clearError = useCallback(() => {
setState(prev => ({ ...prev, error: null }));
}, []);
const refresh = useCallback(async () => {
if (state.currentSchedule) {
await loadSchedule(state.currentSchedule.date);
}
}, [state.currentSchedule, loadSchedule]);
// Load today's schedule on mount
useEffect(() => {
const today = new Date().toISOString().split('T')[0];
loadSchedule(today);
}, [loadSchedule]);
return {
...state,
loadSchedule,
createSchedule,
updateSchedule,
addScheduleItem,
updateScheduleItem,
removeScheduleItem,
moveScheduleItem,
autoSchedule,
optimizeSchedule,
generateFromForecast,
checkCapacity,
getAvailableSlots,
calculateUtilization,
checkIngredientAvailability,
checkEquipmentAvailability,
checkStaffAvailability,
getScheduleAnalytics,
getBottleneckAnalysis,
getEfficiencyReport,
predictDelays,
saveScheduleTemplate,
loadScheduleTemplate,
getScheduleTemplates,
clearError,
refresh,
};
};