Return open orders to cut plan
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m4s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m4s
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { Fragment, type FormEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
import { Fragment, type FormEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { Link, useOutletContext } from 'react-router-dom';
|
import { Link, useNavigate, useOutletContext } from 'react-router-dom';
|
||||||
import { ArrowLeft, CheckCircle2, ChevronDown, ChevronUp, ClipboardList, Clock3, Download, PackageCheck, PlayCircle, Search, XCircle } from 'lucide-react';
|
import { ArrowLeft, CheckCircle2, ChevronDown, ChevronUp, ClipboardList, Clock3, Download, PackageCheck, PlayCircle, Search, Undo2, XCircle } from 'lucide-react';
|
||||||
import DateRangePicker from '../components/DateRangePicker';
|
import DateRangePicker from '../components/DateRangePicker';
|
||||||
import PaginationControls from '../components/PaginationControls';
|
import PaginationControls from '../components/PaginationControls';
|
||||||
import RefreshStatus from '../components/RefreshStatus';
|
import RefreshStatus from '../components/RefreshStatus';
|
||||||
@@ -121,6 +121,7 @@ const ProductionOrders = () => {
|
|||||||
refreshInterval: number;
|
refreshInterval: number;
|
||||||
setRefreshInterval: (interval: number) => void;
|
setRefreshInterval: (interval: number) => void;
|
||||||
}>();
|
}>();
|
||||||
|
const navigate = useNavigate();
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [statusFilter, setStatusFilter] = useState<ProductionOrderStatusTab>('all');
|
const [statusFilter, setStatusFilter] = useState<ProductionOrderStatusTab>('all');
|
||||||
const [summary, setSummary] = useState<ProductionOrderSummary>(emptySummary);
|
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 refreshSupplyLots = async () => {
|
||||||
const supplySummary = await fetchSupplySummary();
|
const supplySummary = await fetchSupplySummary();
|
||||||
setSupplyLots(supplySummary.lots);
|
setSupplyLots(supplySummary.lots);
|
||||||
@@ -267,11 +287,17 @@ const ProductionOrders = () => {
|
|||||||
exportToCSV(exportData, `ordens_producao_${new Date().toISOString().split('T')[0]}.csv`);
|
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 className="space-y-6 rounded-xl border border-dark-border bg-dark-input/25 p-5">
|
||||||
<div>
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||||
<p className="text-xs font-bold uppercase tracking-widest text-dark-muted">Detalhes da OP</p>
|
<div>
|
||||||
<p className="mt-1 text-sm font-bold text-dark-text">{order.number || `OP #${order.id}`} · {order.productDescription}</p>
|
<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>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3 md:grid-cols-4">
|
<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>}
|
{(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>
|
</div>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|||||||
Reference in New Issue
Block a user