Return open orders to cut plan
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m4s

This commit is contained in:
Cauê Faleiros
2026-08-12 14:59:24 -03:00
parent 52d3137ef5
commit 743dc55687

View File

@@ -1,6 +1,6 @@
import { Fragment, type FormEvent, useCallback, useEffect, useMemo, useState } from 'react';
import { Link, useOutletContext } from 'react-router-dom';
import { ArrowLeft, CheckCircle2, ChevronDown, ChevronUp, ClipboardList, Clock3, Download, PackageCheck, PlayCircle, Search, XCircle } from 'lucide-react';
import { Link, useNavigate, useOutletContext } from 'react-router-dom';
import { ArrowLeft, CheckCircle2, ChevronDown, ChevronUp, ClipboardList, Clock3, Download, PackageCheck, PlayCircle, Search, Undo2, XCircle } from 'lucide-react';
import DateRangePicker from '../components/DateRangePicker';
import PaginationControls from '../components/PaginationControls';
import RefreshStatus from '../components/RefreshStatus';
@@ -121,6 +121,7 @@ const ProductionOrders = () => {
refreshInterval: number;
setRefreshInterval: (interval: number) => void;
}>();
const navigate = useNavigate();
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState<ProductionOrderStatusTab>('all');
const [summary, setSummary] = useState<ProductionOrderSummary>(emptySummary);
@@ -219,6 +220,25 @@ const ProductionOrders = () => {
}
};
const returnOrderToCutPlan = async (order: ProductionOrderItem) => {
if (order.status !== 'open' || (order.integrationStatus || '').toLowerCase() !== 'local') return;
const orderNumber = order.number || `#${order.id}`;
if (!window.confirm(`Devolver a OP ${orderNumber} ao Plano de Corte?\n\nA OP será cancelada, preservando o histórico. A necessidade voltará para o Plano de Corte e poderá ser gerada novamente quando você decidir.`)) return;
setBusyStatusOrderId(order.id);
setStatusMessage('');
try {
await updateProductionOrderStatus(order.id, 'canceled');
setSelectedOrderId(null);
setStatusMessage(`OP ${orderNumber} devolvida ao Plano de Corte.`);
navigate('/supplies/cutting');
} catch (error) {
setStatusMessage(error instanceof Error ? error.message : 'Não foi possível devolver a OP ao Plano de Corte.');
} finally {
setBusyStatusOrderId(null);
}
};
const refreshSupplyLots = async () => {
const supplySummary = await fetchSupplySummary();
setSupplyLots(supplySummary.lots);
@@ -267,11 +287,17 @@ const ProductionOrders = () => {
exportToCSV(exportData, `ordens_producao_${new Date().toISOString().split('T')[0]}.csv`);
};
const renderOrderDetails = (order: ProductionOrderItem) => (
const renderOrderDetails = (order: ProductionOrderItem) => {
const canReturnToCutPlan = order.status === 'open' && (order.integrationStatus || '').toLowerCase() === 'local';
return (
<div className="space-y-6 rounded-xl border border-dark-border bg-dark-input/25 p-5">
<div>
<p className="text-xs font-bold uppercase tracking-widest text-dark-muted">Detalhes da OP</p>
<p className="mt-1 text-sm font-bold text-dark-text">{order.number || `OP #${order.id}`} · {order.productDescription}</p>
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div>
<p className="text-xs font-bold uppercase tracking-widest text-dark-muted">Detalhes da OP</p>
<p className="mt-1 text-sm font-bold text-dark-text">{order.number || `OP #${order.id}`} · {order.productDescription}</p>
</div>
{canReturnToCutPlan && <button type="button" onClick={() => void returnOrderToCutPlan(order)} disabled={busyStatusOrderId === order.id} className="inline-flex h-9 shrink-0 items-center justify-center gap-2 rounded-lg border border-amber-400/30 bg-amber-400/10 px-3 text-xs font-bold text-amber-200 transition-colors hover:border-amber-400/60 hover:bg-amber-400/15 disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer"><Undo2 className="h-4 w-4" />Devolver ao Plano de Corte</button>}
</div>
<div className="grid grid-cols-2 gap-3 md:grid-cols-4">
@@ -302,7 +328,8 @@ const ProductionOrders = () => {
{(order.steps.length > 0 || order.notes || order.supplier || order.lotCode || order.rollQuantity || order.fabricKg || order.ribKg || order.yieldPiecesPerKg) && <section><h3 className="text-xs font-bold uppercase tracking-widest text-dark-muted">Informações adicionais</h3><div className="mt-3 grid grid-cols-1 gap-3 sm:grid-cols-2">{(order.supplier || order.lotCode || order.rollQuantity || order.fabricKg || order.ribKg || order.yieldPiecesPerKg) && <dl className="grid grid-cols-2 gap-3 rounded-xl border border-dark-border bg-dark-card p-4 text-xs sm:col-span-2">{order.supplier && <div><dt className="font-bold text-dark-muted">Fornecedor</dt><dd className="mt-1 font-bold text-dark-text">{order.supplier}</dd></div>}{order.lotCode && <div><dt className="font-bold text-dark-muted">Lote</dt><dd className="mt-1 font-bold text-dark-text">{order.lotCode}</dd></div>}{order.rollQuantity !== null && <div><dt className="font-bold text-dark-muted">Rolos</dt><dd className="mt-1 font-bold text-dark-text">{formatQuantity(order.rollQuantity)}</dd></div>}{order.fabricKg !== null && <div><dt className="font-bold text-dark-muted">Malha</dt><dd className="mt-1 font-bold text-dark-text">{formatQuantity(order.fabricKg)} kg</dd></div>}{order.ribKg !== null && <div><dt className="font-bold text-dark-muted">Ribana</dt><dd className="mt-1 font-bold text-dark-text">{formatQuantity(order.ribKg)} kg</dd></div>}{order.yieldPiecesPerKg !== null && <div><dt className="font-bold text-dark-muted">Rendimento</dt><dd className="mt-1 font-bold text-dark-text">{formatQuantity(order.yieldPiecesPerKg)} un./kg</dd></div>}</dl>}{order.steps.length > 0 && <div className="rounded-xl border border-dark-border bg-dark-card p-4 sm:col-span-2"><p className="text-xs font-bold text-dark-text">Etapas</p><div className="mt-3 divide-y divide-dark-border">{order.steps.map(step => <div key={step.id} className="flex items-center gap-3 py-2 text-xs"><span className="h-2.5 w-2.5 rounded-full" style={{ backgroundColor: step.color || 'var(--color-brand-primary)' }} /><span className="font-bold text-dark-text">{step.name}</span><span className="ml-auto font-semibold text-dark-muted">{formatDate(step.startDate)} - {formatDate(step.endDate)}</span></div>)}</div></div>}{order.notes && <p className="whitespace-pre-wrap rounded-xl border border-dark-border bg-dark-card p-4 text-sm font-semibold text-dark-muted sm:col-span-2">{order.notes}</p>}</div></section>}
</div>
);
);
};
return (
<div className="space-y-6">