import { useCallback, useEffect, useMemo, useState } from 'react'; import { Link } from 'react-router-dom'; import { ArrowLeft, CheckCircle2, ClipboardList, Factory, Loader2, PlayCircle, type LucideIcon } from 'lucide-react'; import { fetchProductionPlans, updateProductionPlanStatus } from '../dataService'; import type { ProductionOrderStatus, ProductionPlan } from '../types'; const statusOptions: Array<{ value: ProductionOrderStatus; label: string; className: string }> = [ { value: 'open', label: 'Em aberto', className: 'border-amber-400/35 bg-amber-400/10 text-amber-300' }, { value: 'cutting', label: 'Em corte', className: 'border-amber-300/35 bg-amber-300/10 text-amber-200' }, { value: 'sewing', label: 'Em costura', className: 'border-sky-400/35 bg-sky-400/10 text-sky-300' }, { value: 'review', label: 'Em revisão', className: 'border-violet-400/35 bg-violet-400/10 text-violet-300' }, { value: 'finished', label: 'Finalizada', className: 'border-emerald-400/35 bg-emerald-400/10 text-emerald-300' }, { value: 'canceled', label: 'Cancelada', className: 'border-zinc-500/35 bg-zinc-500/10 text-zinc-400' } ]; const statusStyle = (status: string) => statusOptions.find(option => option.value === status)?.className || 'border-dark-border bg-dark-input text-dark-muted'; const formatNumber = (value: number) => new Intl.NumberFormat('pt-BR', { maximumFractionDigits: 2 }).format(value); const formatDate = (value: string | null) => value ? new Intl.DateTimeFormat('pt-BR').format(new Date(`${value.slice(0, 10)}T00:00:00`)) : '-'; const planMetrics: Array<{ label: string; value: string | number; icon: LucideIcon; tone: string }> = [ { label: 'Planos ativos', value: 0, icon: ClipboardList, tone: 'text-brand-primary' }, { label: 'OPs planejadas', value: 0, icon: Factory, tone: 'text-sky-300' }, { label: 'Unidades em fluxo', value: 0, icon: PlayCircle, tone: 'text-amber-300' } ]; const ProductionPlans = () => { const [plans, setPlans] = useState([]); const [isLoading, setIsLoading] = useState(true); const [busyPlanId, setBusyPlanId] = useState(null); const [message, setMessage] = useState(''); const loadPlans = useCallback(async () => { setIsLoading(true); try { setPlans(await fetchProductionPlans()); } catch (error) { setMessage(error instanceof Error ? error.message : 'Não foi possível carregar os planos.'); } finally { setIsLoading(false); } }, []); useEffect(() => { const timer = window.setTimeout(() => { void loadPlans(); }, 0); return () => window.clearTimeout(timer); }, [loadPlans]); const activePlans = useMemo(() => plans.filter(plan => !['finished', 'canceled'].includes(plan.status)), [plans]); const totalOpenQuantity = useMemo(() => activePlans.reduce((total, plan) => total + plan.totalQuantity, 0), [activePlans]); const movePlan = async (plan: ProductionPlan, status: ProductionOrderStatus) => { if (status === plan.status) return; setBusyPlanId(plan.id); setMessage(''); try { const updated = await updateProductionPlanStatus(plan.id, status); setPlans(current => current.map(item => item.id === updated.id ? updated : item)); setMessage(`${updated.number} movido para ${updated.statusLabel}. As OPs ainda ativas acompanharam o plano.`); } catch (error) { setMessage(error instanceof Error ? error.message : 'Não foi possível mover o plano.'); } finally { setBusyPlanId(null); } }; return (
Ordens de Produção

Planos de Produção

Controle PCP acima das fichas: cada plano agrupa OPs, materiais reservados e a movimentação da operação.

{planMetrics.map(metric => { const value = metric.label === 'Planos ativos' ? activePlans.length : metric.label === 'OPs planejadas' ? activePlans.reduce((total, plan) => total + plan.orderCount, 0) : formatNumber(totalOpenQuantity); const MetricIcon = metric.icon; return

{metric.label}

{value}

; })}
{message &&
{message}
}

Fila de planos

Mover um plano atualiza todas as OPs locais que continuam ativas; OPs concluídas e canceladas preservam seu histórico.

{isLoading ?
: plans.length ? (
{plans.map(plan => (
{plan.number}{plan.statusLabel}

{plan.name}

{plan.contractor ? `Facção: ${plan.contractor}` : 'Sem facção definida'} · previsão {formatDate(plan.expectedDate)}

{plan.notes &&

{plan.notes}

}

Escopo

{plan.orderCount} {plan.orderCount === 1 ? 'OP' : 'OPs'} · {formatNumber(plan.totalQuantity)} un.

Cortado {formatNumber(plan.actualCutQuantity)} · final {formatNumber(plan.finishedQuantity)}

{statusOptions.filter(option => Number(plan.statusCounts[option.value] || 0) > 0).map(option => {plan.statusCounts[option.value]} {option.label.toLowerCase()})}
))}
) :

Nenhum plano confirmado

Gere OPs a partir do Plano de Corte para criar o primeiro plano PCP.

}
); }; export default ProductionPlans;