Add a ne model and card design across pages
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Plus, Download, Clock, Users, AlertCircle, CheckCircle, Timer, ChefHat, Eye, Edit, Calendar, Zap } from 'lucide-react';
|
||||
import { Button, Input, Card, Badge, StatsGrid } from '../../../../components/ui';
|
||||
import { Plus, Download, Clock, Users, AlertCircle, CheckCircle, Timer, ChefHat, Eye, Edit, Calendar, Zap, Package } from 'lucide-react';
|
||||
import { Button, Input, Card, Badge, StatsGrid, StatusCard, getStatusColor, StatusModal } from '../../../../components/ui';
|
||||
import { pagePresets } from '../../../../components/ui/Stats/StatsPresets';
|
||||
import { PageHeader } from '../../../../components/layout';
|
||||
import { ProductionSchedule, BatchTracker, QualityControl } from '../../../../components/domain/production';
|
||||
@@ -10,6 +10,7 @@ const ProductionPage: React.FC = () => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [selectedOrder, setSelectedOrder] = useState<typeof mockProductionOrders[0] | null>(null);
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [modalMode, setModalMode] = useState<'view' | 'edit'>('view');
|
||||
|
||||
const mockProductionStats = {
|
||||
dailyTarget: 150,
|
||||
@@ -111,42 +112,25 @@ const ProductionPage: React.FC = () => {
|
||||
},
|
||||
];
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
const getProductionStatusConfig = (status: string, priority: string) => {
|
||||
const statusConfig = {
|
||||
pending: { color: 'warning', text: 'Pendiente', icon: Clock },
|
||||
in_progress: { color: 'info', text: 'En Proceso', icon: Timer },
|
||||
completed: { color: 'success', text: 'Completado', icon: CheckCircle },
|
||||
cancelled: { color: 'error', text: 'Cancelado', icon: AlertCircle },
|
||||
pending: { text: 'Pendiente', icon: Clock },
|
||||
in_progress: { text: 'En Proceso', icon: Timer },
|
||||
completed: { text: 'Completado', icon: CheckCircle },
|
||||
cancelled: { text: 'Cancelado', icon: AlertCircle },
|
||||
};
|
||||
|
||||
const config = statusConfig[status as keyof typeof statusConfig];
|
||||
const Icon = config?.icon;
|
||||
return (
|
||||
<Badge
|
||||
variant={config?.color as any}
|
||||
icon={Icon && <Icon size={12} />}
|
||||
text={config?.text || status}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const getPriorityBadge = (priority: string) => {
|
||||
const priorityConfig = {
|
||||
low: { color: 'outline', text: 'Baja' },
|
||||
medium: { color: 'secondary', text: 'Media' },
|
||||
high: { color: 'warning', text: 'Alta' },
|
||||
urgent: { color: 'error', text: 'Urgente', icon: Zap },
|
||||
};
|
||||
const isUrgent = priority === 'urgent';
|
||||
|
||||
const config = priorityConfig[priority as keyof typeof priorityConfig];
|
||||
const Icon = config?.icon;
|
||||
return (
|
||||
<Badge
|
||||
variant={config?.color as any}
|
||||
icon={Icon && <Icon size={12} />}
|
||||
text={config?.text || priority}
|
||||
/>
|
||||
);
|
||||
return {
|
||||
color: getStatusColor(status),
|
||||
text: config?.text || status,
|
||||
icon: Icon,
|
||||
isCritical: isUrgent,
|
||||
isHighlight: false
|
||||
};
|
||||
};
|
||||
|
||||
const filteredOrders = mockProductionOrders.filter(order => {
|
||||
@@ -245,120 +229,52 @@ const ProductionPage: React.FC = () => {
|
||||
|
||||
{/* Production Orders Grid */}
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{filteredOrders.map((order) => (
|
||||
<Card key={order.id} className="p-4">
|
||||
<div className="space-y-4">
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex-shrink-0 bg-[var(--color-primary)]/10 p-2 rounded-lg">
|
||||
<ChefHat className="w-4 h-4 text-[var(--text-tertiary)]" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium text-[var(--text-primary)]">
|
||||
{order.recipeName}
|
||||
</div>
|
||||
<div className="text-sm text-[var(--text-secondary)]">
|
||||
ID: {order.id}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{getStatusBadge(order.status)}
|
||||
</div>
|
||||
|
||||
{/* Priority and Quantity */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
{getPriorityBadge(order.priority)}
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-lg font-bold text-[var(--text-primary)]">
|
||||
{order.quantity}
|
||||
</div>
|
||||
<div className="text-xs text-[var(--text-tertiary)]">
|
||||
unidades
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Assigned Worker */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="w-4 h-4 text-[var(--text-tertiary)]" />
|
||||
<span className="text-sm text-[var(--text-primary)]">
|
||||
{order.assignedTo}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Time Information */}
|
||||
<div className="space-y-2 text-xs">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-[var(--text-secondary)]">Inicio:</span>
|
||||
<span className="text-[var(--text-primary)]">
|
||||
{new Date(order.startTime).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' })}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-[var(--text-secondary)]">Est. finalización:</span>
|
||||
<span className="text-[var(--text-primary)]">
|
||||
{new Date(order.estimatedCompletion).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress Bar */}
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between text-xs">
|
||||
<span className="text-[var(--text-secondary)]">Progreso</span>
|
||||
<span className="text-[var(--text-primary)]">
|
||||
{order.progress}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-[var(--bg-tertiary)] rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full transition-all duration-300 ${
|
||||
order.progress === 100
|
||||
? 'bg-[var(--color-success)]'
|
||||
: order.progress > 50
|
||||
? 'bg-[var(--color-info)]'
|
||||
: order.progress > 0
|
||||
? 'bg-[var(--color-warning)]'
|
||||
: 'bg-[var(--bg-quaternary)]'
|
||||
}`}
|
||||
style={{ width: `${order.progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-2 pt-2 border-t border-[var(--border-primary)]">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
onClick={() => {
|
||||
{filteredOrders.map((order) => {
|
||||
const statusConfig = getProductionStatusConfig(order.status, order.priority);
|
||||
|
||||
return (
|
||||
<StatusCard
|
||||
key={order.id}
|
||||
id={order.id}
|
||||
statusIndicator={statusConfig}
|
||||
title={order.recipeName}
|
||||
subtitle={`Asignado a: ${order.assignedTo}`}
|
||||
primaryValue={order.quantity}
|
||||
primaryValueLabel="unidades"
|
||||
secondaryInfo={{
|
||||
label: 'Horario',
|
||||
value: `${new Date(order.startTime).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' })} → ${new Date(order.estimatedCompletion).toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' })}`
|
||||
}}
|
||||
progress={{
|
||||
label: 'Progreso',
|
||||
percentage: order.progress,
|
||||
color: statusConfig.color
|
||||
}}
|
||||
actions={[
|
||||
{
|
||||
label: 'Ver',
|
||||
icon: Eye,
|
||||
variant: 'outline',
|
||||
onClick: () => {
|
||||
setSelectedOrder(order);
|
||||
setModalMode('view');
|
||||
setShowForm(true);
|
||||
}}
|
||||
>
|
||||
<Eye className="w-4 h-4 mr-1" />
|
||||
Ver
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
onClick={() => {
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Editar',
|
||||
icon: Edit,
|
||||
variant: 'outline',
|
||||
onClick: () => {
|
||||
setSelectedOrder(order);
|
||||
setModalMode('edit');
|
||||
setShowForm(true);
|
||||
}}
|
||||
>
|
||||
<Edit className="w-4 h-4 mr-1" />
|
||||
Editar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Empty State */}
|
||||
@@ -388,52 +304,70 @@ const ProductionPage: React.FC = () => {
|
||||
<QualityControl />
|
||||
)}
|
||||
|
||||
{/* Production Order Form Modal - Placeholder */}
|
||||
{showForm && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<Card className="w-full max-w-2xl max-h-[90vh] overflow-auto m-4 p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-xl font-semibold text-[var(--text-primary)]">
|
||||
{selectedOrder ? 'Ver Orden de Producción' : 'Nueva Orden de Producción'}
|
||||
</h2>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setShowForm(false);
|
||||
setSelectedOrder(null);
|
||||
}}
|
||||
>
|
||||
Cerrar
|
||||
</Button>
|
||||
</div>
|
||||
{selectedOrder && (
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-medium">{selectedOrder.recipeName}</h3>
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="font-medium">Cantidad:</span> {selectedOrder.quantity} unidades
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">Asignado a:</span> {selectedOrder.assignedTo}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">Estado:</span> {selectedOrder.status}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">Progreso:</span> {selectedOrder.progress}%
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">Inicio:</span> {new Date(selectedOrder.startTime).toLocaleString('es-ES')}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium">Finalización:</span> {new Date(selectedOrder.estimatedCompletion).toLocaleString('es-ES')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
{/* Production Order Modal */}
|
||||
{showForm && selectedOrder && (
|
||||
<StatusModal
|
||||
isOpen={showForm}
|
||||
onClose={() => {
|
||||
setShowForm(false);
|
||||
setSelectedOrder(null);
|
||||
setModalMode('view');
|
||||
}}
|
||||
mode={modalMode}
|
||||
onModeChange={setModalMode}
|
||||
title={selectedOrder.recipeName}
|
||||
subtitle={`Orden de Producción #${selectedOrder.id}`}
|
||||
statusIndicator={getProductionStatusConfig(selectedOrder.status, selectedOrder.priority)}
|
||||
size="lg"
|
||||
sections={[
|
||||
{
|
||||
title: 'Información General',
|
||||
icon: Package,
|
||||
fields: [
|
||||
{
|
||||
label: 'Cantidad',
|
||||
value: `${selectedOrder.quantity} unidades`,
|
||||
highlight: true
|
||||
},
|
||||
{
|
||||
label: 'Asignado a',
|
||||
value: selectedOrder.assignedTo,
|
||||
},
|
||||
{
|
||||
label: 'Prioridad',
|
||||
value: selectedOrder.priority,
|
||||
type: 'status'
|
||||
},
|
||||
{
|
||||
label: 'Progreso',
|
||||
value: selectedOrder.progress,
|
||||
type: 'percentage',
|
||||
highlight: true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Cronograma',
|
||||
icon: Clock,
|
||||
fields: [
|
||||
{
|
||||
label: 'Hora de inicio',
|
||||
value: selectedOrder.startTime,
|
||||
type: 'datetime'
|
||||
},
|
||||
{
|
||||
label: 'Finalización estimada',
|
||||
value: selectedOrder.estimatedCompletion,
|
||||
type: 'datetime'
|
||||
}
|
||||
]
|
||||
}
|
||||
]}
|
||||
onEdit={() => {
|
||||
// Handle edit mode
|
||||
console.log('Editing production order:', selectedOrder.id);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user