@@ -147,26 +192,56 @@ const ProductionPage: React.FC = () => {
title="Gestión de Producción"
description="Planifica y controla la producción diaria de tu panadería"
actions={[
- {
- id: "export",
- label: "Exportar",
- variant: "outline" as const,
- icon: Download,
- onClick: () => console.log('Export production orders')
- },
{
id: "new",
label: "Nueva Orden de Producción",
variant: "primary" as const,
icon: Plus,
- onClick: () => setShowForm(true)
+ onClick: () => setShowCreateModal(true)
}
]}
/>
{/* Production Stats */}
-
= 80 ? 'success' as const : 'warning' as const,
+ icon: Timer,
+ },
+ {
+ title: 'Completado a Tiempo',
+ value: `${productionStats.onTimeCompletion}%`,
+ variant: productionStats.onTimeCompletion >= 90 ? 'success' as const : 'error' as const,
+ icon: CheckCircle,
+ },
+ {
+ title: 'Puntuación Calidad',
+ value: `${productionStats.qualityScore}%`,
+ variant: productionStats.qualityScore >= 85 ? 'success' as const : 'warning' as const,
+ icon: Package,
+ },
+ {
+ title: 'Producción Hoy',
+ value: formatters.number(productionStats.totalOutput),
+ variant: 'info' as const,
+ icon: ChefHat,
+ },
+ {
+ title: 'Eficiencia',
+ value: `${productionStats.efficiency}%`,
+ variant: productionStats.efficiency >= 75 ? 'success' as const : 'warning' as const,
+ icon: Timer,
+ },
+ ]}
columns={3}
/>
@@ -209,66 +284,64 @@ const ProductionPage: React.FC = () => {
{/* Production Orders Tab */}
{activeTab === 'schedule' && (
<>
- {/* Simplified Controls */}
+ {/* Search Controls */}
- {/* Production Orders Grid */}
+ {/* Production Batches Grid */}
- {filteredOrders.map((order) => {
- const statusConfig = getProductionStatusConfig(order.status, order.priority);
-
+ {filteredBatches.map((batch) => {
+ const statusConfig = getProductionStatusConfig(batch.status, batch.priority);
+ const progress = calculateProgress(batch);
+
return (
{
- setSelectedOrder(order);
+ setSelectedBatch(batch);
setModalMode('view');
- setShowForm(true);
+ setShowBatchModal(true);
}
},
{
label: 'Editar',
icon: Edit,
- variant: 'outline',
+ priority: 'secondary',
onClick: () => {
- setSelectedOrder(order);
+ setSelectedBatch(batch);
setModalMode('edit');
- setShowForm(true);
+ setShowBatchModal(true);
}
}
]}
@@ -278,16 +351,19 @@ const ProductionPage: React.FC = () => {
{/* Empty State */}
- {filteredOrders.length === 0 && (
+ {filteredBatches.length === 0 && (
- No se encontraron órdenes de producción
+ No se encontraron lotes de producción
- Intenta ajustar la búsqueda o crear una nueva orden de producción
+ {batches.length === 0
+ ? 'No hay lotes de producción activos. Crea el primer lote para comenzar.'
+ : 'Intenta ajustar la búsqueda o crear un nuevo lote de producción'
+ }
-
setShowForm(true)}>
+ setShowCreateModal(true)}>
Nueva Orden de Producción
@@ -304,20 +380,20 @@ const ProductionPage: React.FC = () => {
)}
- {/* Production Order Modal */}
- {showForm && selectedOrder && (
+ {/* Production Batch Modal */}
+ {showBatchModal && selectedBatch && (
{
- setShowForm(false);
- setSelectedOrder(null);
+ setShowBatchModal(false);
+ setSelectedBatch(null);
setModalMode('view');
}}
mode={modalMode}
onModeChange={setModalMode}
- title={selectedOrder.recipeName}
- subtitle={`Orden de Producción #${selectedOrder.id}`}
- statusIndicator={getProductionStatusConfig(selectedOrder.status, selectedOrder.priority)}
+ title={selectedBatch.product_name}
+ subtitle={`Lote de Producción #${selectedBatch.batch_number}`}
+ statusIndicator={getProductionStatusConfig(selectedBatch.status, selectedBatch.priority)}
size="lg"
sections={[
{
@@ -325,24 +401,37 @@ const ProductionPage: React.FC = () => {
icon: Package,
fields: [
{
- label: 'Cantidad',
- value: `${selectedOrder.quantity} unidades`,
+ label: 'Cantidad Planificada',
+ value: `${selectedBatch.planned_quantity} unidades`,
highlight: true
},
{
- label: 'Asignado a',
- value: selectedOrder.assignedTo,
+ label: 'Cantidad Real',
+ value: selectedBatch.actual_quantity
+ ? `${selectedBatch.actual_quantity} unidades`
+ : 'Pendiente',
+ editable: modalMode === 'edit',
+ type: 'number'
},
{
label: 'Prioridad',
- value: selectedOrder.priority,
- type: 'status'
+ value: selectedBatch.priority,
+ type: 'select',
+ editable: modalMode === 'edit',
+ options: productionEnums.getProductionPriorityOptions()
},
{
- label: 'Progreso',
- value: selectedOrder.progress,
- type: 'percentage',
- highlight: true
+ label: 'Estado',
+ value: selectedBatch.status,
+ type: 'select',
+ editable: modalMode === 'edit',
+ options: productionEnums.getProductionStatusOptions()
+ },
+ {
+ label: 'Personal Asignado',
+ value: selectedBatch.staff_assigned?.join(', ') || 'No asignado',
+ editable: modalMode === 'edit',
+ type: 'text'
}
]
},
@@ -351,24 +440,128 @@ const ProductionPage: React.FC = () => {
icon: Clock,
fields: [
{
- label: 'Hora de inicio',
- value: selectedOrder.startTime,
+ label: 'Inicio Planificado',
+ value: selectedBatch.planned_start_time,
type: 'datetime'
},
{
- label: 'Finalización estimada',
- value: selectedOrder.estimatedCompletion,
+ label: 'Fin Planificado',
+ value: selectedBatch.planned_end_time,
type: 'datetime'
+ },
+ {
+ label: 'Inicio Real',
+ value: selectedBatch.actual_start_time || 'Pendiente',
+ type: 'datetime'
+ },
+ {
+ label: 'Fin Real',
+ value: selectedBatch.actual_end_time || 'Pendiente',
+ type: 'datetime'
+ }
+ ]
+ },
+ {
+ title: 'Calidad y Costos',
+ icon: CheckCircle,
+ fields: [
+ {
+ label: 'Puntuación de Calidad',
+ value: selectedBatch.quality_score
+ ? `${selectedBatch.quality_score}/10`
+ : 'Pendiente'
+ },
+ {
+ label: 'Rendimiento',
+ value: selectedBatch.yield_percentage
+ ? `${selectedBatch.yield_percentage}%`
+ : 'Calculando...'
+ },
+ {
+ label: 'Costo Estimado',
+ value: selectedBatch.estimated_cost || 0,
+ type: 'currency'
+ },
+ {
+ label: 'Costo Real',
+ value: selectedBatch.actual_cost || 0,
+ type: 'currency'
}
]
}
]}
- onEdit={() => {
- // Handle edit mode
- console.log('Editing production order:', selectedOrder.id);
+ onSave={async () => {
+ try {
+ // Implementation would depend on specific fields changed
+ console.log('Saving batch changes:', selectedBatch.id);
+ // await updateBatchStatusMutation.mutateAsync({
+ // batchId: selectedBatch.id,
+ // updates: selectedBatch
+ // });
+ setShowBatchModal(false);
+ setSelectedBatch(null);
+ setModalMode('view');
+ } catch (error) {
+ console.error('Error saving batch:', error);
+ }
+ }}
+ onFieldChange={(sectionIndex, fieldIndex, value) => {
+ if (!selectedBatch) return;
+
+ const sections = [
+ ['planned_quantity', 'actual_quantity', 'priority', 'status', 'staff_assigned'],
+ ['planned_start_time', 'planned_end_time', 'actual_start_time', 'actual_end_time'],
+ ['quality_score', 'yield_percentage', 'estimated_cost', 'actual_cost']
+ ];
+
+ // Get the field names from modal sections
+ const sectionFields = [
+ { fields: ['planned_quantity', 'actual_quantity', 'priority', 'status', 'staff_assigned'] },
+ { fields: ['planned_start_time', 'planned_end_time', 'actual_start_time', 'actual_end_time'] },
+ { fields: ['quality_score', 'yield_percentage', 'estimated_cost', 'actual_cost'] }
+ ];
+
+ const fieldMapping: Record = {
+ 'Cantidad Real': 'actual_quantity',
+ 'Prioridad': 'priority',
+ 'Estado': 'status',
+ 'Personal Asignado': 'staff_assigned'
+ };
+
+ // Get section labels to map back to field names
+ const sectionLabels = [
+ ['Cantidad Planificada', 'Cantidad Real', 'Prioridad', 'Estado', 'Personal Asignado'],
+ ['Inicio Planificado', 'Fin Planificado', 'Inicio Real', 'Fin Real'],
+ ['Puntuación de Calidad', 'Rendimiento', 'Costo Estimado', 'Costo Real']
+ ];
+
+ const fieldLabel = sectionLabels[sectionIndex]?.[fieldIndex];
+ const propertyName = fieldMapping[fieldLabel] || sectionFields[sectionIndex]?.fields[fieldIndex];
+
+ if (propertyName) {
+ let processedValue: any = value;
+
+ if (propertyName === 'staff_assigned' && typeof value === 'string') {
+ processedValue = value.split(',').map(s => s.trim()).filter(s => s.length > 0);
+ } else if (propertyName === 'actual_quantity') {
+ processedValue = parseFloat(value as string) || 0;
+ }
+
+ setSelectedBatch({
+ ...selectedBatch,
+ [propertyName]: processedValue
+ });
+ }
}}
/>
)}
+
+ {/* Create Production Batch Modal */}
+ setShowCreateModal(false)}
+ onCreateBatch={handleCreateBatch}
+ />
);
};
diff --git a/frontend/src/pages/app/settings/team/TeamPage.tsx b/frontend/src/pages/app/settings/team/TeamPage.tsx
index a1651580..88a059bf 100644
--- a/frontend/src/pages/app/settings/team/TeamPage.tsx
+++ b/frontend/src/pages/app/settings/team/TeamPage.tsx
@@ -1,6 +1,7 @@
import React, { useState, useMemo } from 'react';
import { Users, Plus, Search, Mail, Phone, Shield, Trash2, Crown, X, UserCheck } from 'lucide-react';
-import { Button, Card, Badge, Input, StatusCard, getStatusColor } from '../../../../components/ui';
+import { Button, Card, Badge, Input, StatusCard, getStatusColor, StatsGrid } from '../../../../components/ui';
+import AddTeamMemberModal from '../../../../components/domain/team/AddTeamMemberModal';
import { PageHeader } from '../../../../components/layout';
import { useTeamMembers, useAddTeamMember, useRemoveTeamMember, useUpdateMemberRole } from '../../../../api/hooks/tenant';
import { useAllUsers } from '../../../../api/hooks/user';
@@ -285,55 +286,36 @@ const TeamPage: React.FC = () => {
/>
{/* Team Stats */}
-
-
-
-
-
Total Equipo
-
{teamStats.total}
-
-
-
-
-
-
-
-
-
-
-
Activos
-
{teamStats.active}
-
-
-
-
-
-
-
-
-
-
-
Administradores
-
{teamStats.admins}
-
-
-
-
-
-
-
-
-
-
-
Propietarios
-
{teamStats.owners}
-
-
-
-
-
-
-
+
{/* Filters and Search */}
@@ -368,6 +350,21 @@ const TeamPage: React.FC = () => {
+ {/* Add Member Button */}
+ {canManageTeam && availableUsers.length > 0 && filteredMembers.length > 0 && (
+
{filteredMembers.map((member) => (
@@ -398,124 +395,58 @@ const TeamPage: React.FC = () => {
{filteredMembers.length === 0 && (
-