Split production order preview into steps

This commit is contained in:
Cauê Faleiros
2026-08-25 13:20:04 -03:00
parent cdeb3dcc59
commit d14f7e315f

View File

@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { Link, useOutletContext, useSearchParams } from 'react-router-dom'; import { Link, useOutletContext, useSearchParams } from 'react-router-dom';
import { AlertTriangle, ArrowLeft, ClipboardList, Download, Eye, Layers3, Pencil, RotateCcw, Ruler, Save as SaveIcon, Scissors, Search, Settings2, X } from 'lucide-react'; import { AlertTriangle, ArrowLeft, ArrowRight, ClipboardList, Download, Eye, Layers3, Pencil, RotateCcw, Ruler, Save as SaveIcon, Scissors, Search, Settings2, X } from 'lucide-react';
import DateRangePicker from '../components/DateRangePicker'; import DateRangePicker from '../components/DateRangePicker';
import PaginationControls from '../components/PaginationControls'; import PaginationControls from '../components/PaginationControls';
import ProductColorBadge from '../components/ProductColorBadge'; import ProductColorBadge from '../components/ProductColorBadge';
@@ -18,6 +18,7 @@ type CutProductIssue = Exclude<CutIssue, 'missing_yield_rule'>;
type CorrectionIssueFilter = CutProductIssue | 'all'; type CorrectionIssueFilter = CutProductIssue | 'all';
type SettingsSection = 'rules' | 'corrections'; type SettingsSection = 'rules' | 'corrections';
type CuttingView = 'plan' | 'families' | 'issues'; type CuttingView = 'plan' | 'families' | 'issues';
type PreviewStep = 'selection' | 'confirmation';
type MaterialReadiness = { status: 'ready' | 'material_ready' | 'blocked' | 'missing'; blockers: string[] }; type MaterialReadiness = { status: 'ready' | 'material_ready' | 'blocked' | 'missing'; blockers: string[] };
type CutMaterialRequirement = { key: string; label: string; unit: string; quantity: number }; type CutMaterialRequirement = { key: string; label: string; unit: string; quantity: number };
@@ -163,6 +164,7 @@ const Cutting = () => {
const [itemsPerPage, setItemsPerPage] = useState(10); const [itemsPerPage, setItemsPerPage] = useState(10);
const [isGeneratingOrders, setIsGeneratingOrders] = useState(false); const [isGeneratingOrders, setIsGeneratingOrders] = useState(false);
const [isOrderPreviewOpen, setIsOrderPreviewOpen] = useState(false); const [isOrderPreviewOpen, setIsOrderPreviewOpen] = useState(false);
const [previewStep, setPreviewStep] = useState<PreviewStep>('selection');
const [previewPage, setPreviewPage] = useState(1); const [previewPage, setPreviewPage] = useState(1);
const [previewItemsPerPage, setPreviewItemsPerPage] = useState(10); const [previewItemsPerPage, setPreviewItemsPerPage] = useState(10);
const [selectedPreviewRowIds, setSelectedPreviewRowIds] = useState<string[]>([]); const [selectedPreviewRowIds, setSelectedPreviewRowIds] = useState<string[]>([]);
@@ -670,6 +672,7 @@ const Cutting = () => {
setLotSelections({}); setLotSelections({});
setProductionPlanForm({ name: cutPlanReference, contractor: '', expectedDate: '', notes: '' }); setProductionPlanForm({ name: cutPlanReference, contractor: '', expectedDate: '', notes: '' });
setIsPlanDetailsOpen(false); setIsPlanDetailsOpen(false);
setPreviewStep('selection');
setIsOrderPreviewOpen(true); setIsOrderPreviewOpen(true);
}; };
@@ -885,9 +888,11 @@ const Cutting = () => {
> >
<div className="flex flex-col gap-4 border-b border-dark-border p-5 md:flex-row md:items-start md:justify-between"> <div className="flex flex-col gap-4 border-b border-dark-border p-5 md:flex-row md:items-start md:justify-between">
<div> <div>
<h2 className="text-lg font-bold text-dark-text">Prévia das OPs</h2> <h2 className="text-lg font-bold text-dark-text">{previewStep === 'selection' ? 'Selecionar OPs' : 'Confirmar plano'}</h2>
<p className="mt-1 text-sm font-semibold text-dark-muted"> <p className="mt-1 text-sm font-semibold text-dark-muted">
Escolha os SKUs para criar ordens locais das necessidades do filtro atual. {previewStep === 'selection'
? 'Escolha os SKUs que entrarão neste plano de produção.'
: 'Revise o plano e as reservas antes de criar as ordens locais.'}
</p> </p>
</div> </div>
<button <button
@@ -902,7 +907,19 @@ const Cutting = () => {
</button> </button>
</div> </div>
<div className="flex flex-col gap-3 border-b border-dark-border bg-dark-input/25 px-5 py-3 sm:flex-row sm:items-center sm:justify-between"> <div className="flex items-center gap-2 border-b border-dark-border bg-dark-input/30 px-5 py-3 text-xs font-bold">
<span className={`inline-flex items-center gap-2 rounded-full px-3 py-1.5 ${previewStep === 'selection' ? 'bg-brand-primary/15 text-brand-primary' : 'text-dark-muted'}`}>
<span className={`inline-flex h-5 w-5 items-center justify-center rounded-full ${previewStep === 'selection' ? 'bg-brand-primary text-brand-contrast' : 'border border-dark-border'}`}>1</span>
Selecionar OPs
</span>
<ArrowRight className="h-3.5 w-3.5 text-dark-muted" />
<span className={`inline-flex items-center gap-2 rounded-full px-3 py-1.5 ${previewStep === 'confirmation' ? 'bg-brand-primary/15 text-brand-primary' : 'text-dark-muted'}`}>
<span className={`inline-flex h-5 w-5 items-center justify-center rounded-full ${previewStep === 'confirmation' ? 'bg-brand-primary text-brand-contrast' : 'border border-dark-border'}`}>2</span>
Confirmar plano
</span>
</div>
{previewStep === 'selection' && <div className="flex flex-col gap-3 border-b border-dark-border bg-dark-input/25 px-5 py-3 sm:flex-row sm:items-center sm:justify-between">
<div className="flex flex-wrap items-center gap-2 text-sm font-bold text-dark-text"> <div className="flex flex-wrap items-center gap-2 text-sm font-bold text-dark-text">
<span>{formatNumber(selectedPreviewRows.length)} selecionada{selectedPreviewRows.length === 1 ? '' : 's'}</span> <span>{formatNumber(selectedPreviewRows.length)} selecionada{selectedPreviewRows.length === 1 ? '' : 's'}</span>
<span className="text-dark-muted">·</span> <span className="text-dark-muted">·</span>
@@ -910,9 +927,9 @@ const Cutting = () => {
{previewRowsWithIssues > 0 && <><span className="text-dark-muted">·</span><span className="text-amber-300">{formatNumber(previewRowsWithIssues)} para revisar</span></>} {previewRowsWithIssues > 0 && <><span className="text-dark-muted">·</span><span className="text-amber-300">{formatNumber(previewRowsWithIssues)} para revisar</span></>}
</div> </div>
<span className="text-xs font-semibold text-dark-muted">Cobertura de {formatNumber(targetCoverageDays)} dias</span> <span className="text-xs font-semibold text-dark-muted">Cobertura de {formatNumber(targetCoverageDays)} dias</span>
</div> </div>}
<div className="border-b border-dark-border px-5 py-4"> {previewStep === 'confirmation' && <div className="border-b border-dark-border px-5 py-4">
<div className="flex flex-col gap-3 md:flex-row md:items-end"> <div className="flex flex-col gap-3 md:flex-row md:items-end">
<label className="block min-w-0 flex-1 text-xs font-bold text-dark-muted">Plano de produção <label className="block min-w-0 flex-1 text-xs font-bold text-dark-muted">Plano de produção
<input value={productionPlanForm.name} onChange={(event) => setProductionPlanForm(current => ({ ...current, name: event.target.value }))} className="mt-1 h-10 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none focus:border-brand-primary" /> <input value={productionPlanForm.name} onChange={(event) => setProductionPlanForm(current => ({ ...current, name: event.target.value }))} className="mt-1 h-10 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none focus:border-brand-primary" />
@@ -926,13 +943,41 @@ const Cutting = () => {
<label className="text-xs font-bold text-dark-muted">Previsão de conclusão<input type="date" value={productionPlanForm.expectedDate} onChange={(event) => setProductionPlanForm(current => ({ ...current, expectedDate: event.target.value }))} className="mt-1 h-10 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none focus:border-brand-primary" /></label> <label className="text-xs font-bold text-dark-muted">Previsão de conclusão<input type="date" value={productionPlanForm.expectedDate} onChange={(event) => setProductionPlanForm(current => ({ ...current, expectedDate: event.target.value }))} className="mt-1 h-10 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none focus:border-brand-primary" /></label>
<label className="text-xs font-bold text-dark-muted">Observação<input value={productionPlanForm.notes} onChange={(event) => setProductionPlanForm(current => ({ ...current, notes: event.target.value }))} placeholder="Opcional" className="mt-1 h-10 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary" /></label> <label className="text-xs font-bold text-dark-muted">Observação<input value={productionPlanForm.notes} onChange={(event) => setProductionPlanForm(current => ({ ...current, notes: event.target.value }))} placeholder="Opcional" className="mt-1 h-10 w-full rounded-lg border border-dark-border bg-dark-input px-3 text-sm font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary" /></label>
</div>} </div>}
</div> </div>}
{previewStep === 'confirmation' && <div className="min-h-0 flex-1 overflow-y-auto p-5">
<div className="rounded-xl border border-dark-border bg-dark-input/30 p-4">
<div className="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between">
<div>
<h3 className="text-sm font-bold text-dark-text">OPs incluídas</h3>
<p className="mt-1 text-xs font-semibold text-dark-muted">{formatNumber(selectedPreviewRows.length)} SKU{selectedPreviewRows.length === 1 ? '' : 's'} · {formatNumber(orderPreviewTotalUnits)} un. para cortar</p>
</div>
{previewRowsWithIssues > 0 && <span className="text-xs font-bold text-amber-300">{formatNumber(previewRowsWithIssues)} para revisar</span>}
</div>
<div className="mt-3 divide-y divide-dark-border overflow-hidden rounded-lg border border-dark-border bg-dark-card">
{selectedPreviewRows.map(row => (
<div key={row.id} className="flex items-center justify-between gap-4 px-3 py-2.5">
<div className="min-w-0">
<p className="truncate text-sm font-bold text-dark-text" title={row.name}>{row.name}</p>
<div className="mt-1 flex flex-wrap items-center gap-1.5">
<span className="font-mono text-[10px] text-dark-muted">#{row.id}</span>
<span className={`rounded-full border px-2 py-0.5 text-[10px] font-bold ${familyStyles[row.family.key]}`}>{row.family.materialLabel}</span>
<ProductColorBadge label={row.color} className="max-w-[100px] px-2" />
<span className="text-[10px] font-semibold text-dark-muted">{row.size || 'Sem tamanho'}</span>
</div>
</div>
<span className="shrink-0 text-sm font-bold text-brand-primary">{formatNumber(row.suggestedCutQuantity)} un.</span>
</div>
))}
</div>
</div>
</div>}
{previewStep === 'selection' && <>
<div className="flex flex-col gap-3 border-b border-dark-border bg-dark-input/40 px-4 py-3 sm:flex-row sm:items-center sm:justify-between"> <div className="flex flex-col gap-3 border-b border-dark-border bg-dark-input/40 px-4 py-3 sm:flex-row sm:items-center sm:justify-between">
<p className="text-xs font-semibold text-dark-muted">Todos os SKUs abaixo estão aptos para gerar OP. A seleção permanece ao trocar de página.</p> <p className="text-xs font-semibold text-dark-muted">Todos os SKUs abaixo estão aptos para gerar OP. A seleção permanece ao trocar de página.</p>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
<button type="button" onClick={() => setPreviewSelection(rowsAvailableForOrder)} className="rounded-lg border border-dark-border bg-dark-card px-3 py-1.5 text-xs font-bold text-dark-text transition-colors hover:border-brand-primary cursor-pointer">{areAllPreviewRowsSelected ? 'Todos selecionados' : 'Selecionar todos'}</button> <button type="button" onClick={() => setPreviewSelection(rowsAvailableForOrder)} className="rounded-lg border border-dark-border bg-dark-card px-3 py-1.5 text-xs font-bold text-dark-text transition-colors hover:border-brand-primary cursor-pointer">{areAllPreviewRowsSelected ? 'Todos selecionados' : 'Selecionar todos'}</button>
<button type="button" onClick={() => setPreviewSelection(rowsAvailableForOrder.filter(row => !row.issues.length))} className="rounded-lg border border-emerald-400/30 bg-emerald-400/10 px-3 py-1.5 text-xs font-bold text-emerald-300 transition-colors hover:border-emerald-400/60 cursor-pointer">Somente dados completos</button>
<button type="button" onClick={() => setPreviewSelection([])} disabled={!selectedPreviewRows.length} className="rounded-lg border border-dark-border bg-dark-card px-3 py-1.5 text-xs font-bold text-dark-muted transition-colors hover:border-red-400/40 hover:text-red-300 disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer">Limpar</button> <button type="button" onClick={() => setPreviewSelection([])} disabled={!selectedPreviewRows.length} className="rounded-lg border border-dark-border bg-dark-card px-3 py-1.5 text-xs font-bold text-dark-muted transition-colors hover:border-red-400/40 hover:text-red-300 disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer">Limpar</button>
</div> </div>
</div> </div>
@@ -982,8 +1027,9 @@ const Cutting = () => {
</tbody> </tbody>
</table> </table>
</div> </div>
</>}
{selectedMaterialRequirements.length > 0 && <section className="border-t border-dark-border bg-dark-input/30 p-4"> {previewStep === 'confirmation' && selectedMaterialRequirements.length > 0 && <section className="border-t border-dark-border bg-dark-input/30 p-4">
<div className="flex flex-col gap-1 sm:flex-row sm:items-end sm:justify-between"> <div className="flex flex-col gap-1 sm:flex-row sm:items-end sm:justify-between">
<div> <div>
<h3 className="text-sm font-bold text-dark-text">Reservar lotes para este corte</h3> <h3 className="text-sm font-bold text-dark-text">Reservar lotes para este corte</h3>
@@ -994,7 +1040,7 @@ const Cutting = () => {
{selectedMaterialRequirements.length ? <div className="mt-4 overflow-x-auto rounded-xl border border-dark-border bg-dark-card"><table className="w-full min-w-[760px] text-left text-xs"><thead className="bg-dark-header text-dark-muted"><tr><th className="px-3 py-2.5 font-bold">Material necessário</th><th className="px-3 py-2.5 text-right font-bold">Necessário</th><th className="px-3 py-2.5 font-bold">Lote reservado</th><th className="px-3 py-2.5 text-right font-bold">Quantidade reservada</th></tr></thead><tbody className="divide-y divide-dark-border">{selectedMaterialRequirements.map(requirement => { const selection = lotSelections[requirement.key] || { lotId: '', quantity: '' }; const lots = supplyLots.filter(lot => { const lotKey = normalizeMaterialKey(lot.product); const materialMatch = lotKey === requirement.key || lotKey === normalizeMaterialKey(requirement.label); return lot.availableQuantity > 0 && materialMatch && lot.unit.toLowerCase() === requirement.unit.toLowerCase(); }); return <tr key={requirement.key}><td className="px-3 py-3"><p className="font-bold text-dark-text">{requirement.label}</p><p className="mt-0.5 font-mono text-[10px] text-dark-muted">{requirement.key}</p></td><td className="px-3 py-3 text-right font-bold text-dark-text">{formatNumber(requirement.quantity, 4)} {requirement.unit}</td><td className="px-3 py-3"><select value={selection.lotId} onChange={(event) => setLotSelections(current => ({ ...current, [requirement.key]: { lotId: event.target.value, quantity: current[requirement.key]?.quantity || '' } }))} className="h-9 w-full min-w-[230px] rounded-lg border border-dark-border bg-dark-input px-2 text-xs font-semibold text-dark-text outline-none focus:border-brand-primary"><option value="">{lots.length ? 'Não reservar agora' : 'Sem lote compatível'}</option>{lots.map(lot => <option key={lot.id} value={lot.id}>#{lot.id} · {lot.product} · disp. {formatNumber(lot.availableQuantity, 4)} {lot.unit}</option>)}</select></td><td className="px-3 py-3"><input inputMode="decimal" value={selection.quantity} onChange={(event) => setLotSelections(current => ({ ...current, [requirement.key]: { lotId: current[requirement.key]?.lotId || '', quantity: event.target.value } }))} disabled={!selection.lotId} className="h-9 w-32 rounded-lg border border-dark-border bg-dark-input px-2 text-right text-xs font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary disabled:cursor-not-allowed disabled:opacity-50" placeholder={formatNumber(requirement.quantity, 4)} /></td></tr>; })}</tbody></table></div> : <p className="mt-3 rounded-xl border border-dark-border bg-dark-card px-3 py-3 text-xs font-semibold text-dark-muted">Os SKUs selecionados não possuem componentes de material sincronizados. Eles podem seguir para OP, mas não geram reserva.</p>} {selectedMaterialRequirements.length ? <div className="mt-4 overflow-x-auto rounded-xl border border-dark-border bg-dark-card"><table className="w-full min-w-[760px] text-left text-xs"><thead className="bg-dark-header text-dark-muted"><tr><th className="px-3 py-2.5 font-bold">Material necessário</th><th className="px-3 py-2.5 text-right font-bold">Necessário</th><th className="px-3 py-2.5 font-bold">Lote reservado</th><th className="px-3 py-2.5 text-right font-bold">Quantidade reservada</th></tr></thead><tbody className="divide-y divide-dark-border">{selectedMaterialRequirements.map(requirement => { const selection = lotSelections[requirement.key] || { lotId: '', quantity: '' }; const lots = supplyLots.filter(lot => { const lotKey = normalizeMaterialKey(lot.product); const materialMatch = lotKey === requirement.key || lotKey === normalizeMaterialKey(requirement.label); return lot.availableQuantity > 0 && materialMatch && lot.unit.toLowerCase() === requirement.unit.toLowerCase(); }); return <tr key={requirement.key}><td className="px-3 py-3"><p className="font-bold text-dark-text">{requirement.label}</p><p className="mt-0.5 font-mono text-[10px] text-dark-muted">{requirement.key}</p></td><td className="px-3 py-3 text-right font-bold text-dark-text">{formatNumber(requirement.quantity, 4)} {requirement.unit}</td><td className="px-3 py-3"><select value={selection.lotId} onChange={(event) => setLotSelections(current => ({ ...current, [requirement.key]: { lotId: event.target.value, quantity: current[requirement.key]?.quantity || '' } }))} className="h-9 w-full min-w-[230px] rounded-lg border border-dark-border bg-dark-input px-2 text-xs font-semibold text-dark-text outline-none focus:border-brand-primary"><option value="">{lots.length ? 'Não reservar agora' : 'Sem lote compatível'}</option>{lots.map(lot => <option key={lot.id} value={lot.id}>#{lot.id} · {lot.product} · disp. {formatNumber(lot.availableQuantity, 4)} {lot.unit}</option>)}</select></td><td className="px-3 py-3"><input inputMode="decimal" value={selection.quantity} onChange={(event) => setLotSelections(current => ({ ...current, [requirement.key]: { lotId: current[requirement.key]?.lotId || '', quantity: event.target.value } }))} disabled={!selection.lotId} className="h-9 w-32 rounded-lg border border-dark-border bg-dark-input px-2 text-right text-xs font-semibold text-dark-text outline-none placeholder:text-dark-muted focus:border-brand-primary disabled:cursor-not-allowed disabled:opacity-50" placeholder={formatNumber(requirement.quantity, 4)} /></td></tr>; })}</tbody></table></div> : <p className="mt-3 rounded-xl border border-dark-border bg-dark-card px-3 py-3 text-xs font-semibold text-dark-muted">Os SKUs selecionados não possuem componentes de material sincronizados. Eles podem seguir para OP, mas não geram reserva.</p>}
</section>} </section>}
<PaginationControls {previewStep === 'selection' && <PaginationControls
totalItems={rowsAvailableForOrder.length} totalItems={rowsAvailableForOrder.length}
currentPage={safePreviewPage} currentPage={safePreviewPage}
totalPages={previewTotalPages} totalPages={previewTotalPages}
@@ -1007,29 +1053,32 @@ const Cutting = () => {
onPageChange={setPreviewPage} onPageChange={setPreviewPage}
onPageSizeChange={(pageSize) => { setPreviewItemsPerPage(pageSize); setPreviewPage(1); }} onPageSizeChange={(pageSize) => { setPreviewItemsPerPage(pageSize); setPreviewPage(1); }}
className="border-t border-dark-border px-4 py-3" className="border-t border-dark-border px-4 py-3"
/> />}
<div className="flex flex-col gap-3 border-t border-dark-border p-4 sm:flex-row sm:items-center sm:justify-between"> <div className="flex flex-col gap-3 border-t border-dark-border p-4 sm:flex-row sm:items-center sm:justify-between">
<p className="text-xs font-semibold text-dark-muted"> <p className="text-xs font-semibold text-dark-muted">{previewStep === 'selection'
OPs abertas já existentes para o mesmo plano e SKU serão ignoradas. ? 'Você poderá revisar o plano e reservar lotes na próxima etapa.'
: 'OPs abertas já existentes para o mesmo plano e SKU serão ignoradas.'}
</p> </p>
<div className="flex flex-col gap-2 sm:flex-row sm:justify-end"> <div className="flex flex-col gap-2 sm:flex-row sm:justify-end">
<button <button
type="button" type="button"
onClick={() => setIsOrderPreviewOpen(false)} onClick={() => previewStep === 'selection' ? setIsOrderPreviewOpen(false) : setPreviewStep('selection')}
disabled={isGeneratingOrders} disabled={isGeneratingOrders}
className="inline-flex items-center justify-center rounded-xl border border-dark-border bg-dark-input px-4 py-2 text-sm font-bold text-dark-text transition-colors hover:border-brand-primary disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer" className="inline-flex items-center justify-center rounded-xl border border-dark-border bg-dark-input px-4 py-2 text-sm font-bold text-dark-text transition-colors hover:border-brand-primary disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer"
> >
Cancelar {previewStep === 'selection' ? 'Cancelar' : 'Voltar'}
</button> </button>
<button <button
type="button" type="button"
onClick={() => void generateProductionOrders()} onClick={() => previewStep === 'selection' ? setPreviewStep('confirmation') : void generateProductionOrders()}
disabled={isGeneratingOrders || !selectedPreviewRows.length} disabled={isGeneratingOrders || !selectedPreviewRows.length}
className="inline-flex items-center justify-center gap-2 rounded-xl border border-brand-primary/30 bg-brand-primary/15 px-4 py-2 text-sm font-bold text-brand-primary transition-colors hover:border-brand-primary disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer" className="inline-flex items-center justify-center gap-2 rounded-xl border border-brand-primary/30 bg-brand-primary/15 px-4 py-2 text-sm font-bold text-brand-primary transition-colors hover:border-brand-primary disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer"
> >
<ClipboardList className="h-4 w-4" /> {previewStep === 'selection' ? <ArrowRight className="h-4 w-4" /> : <ClipboardList className="h-4 w-4" />}
{isGeneratingOrders ? 'Gerando OPs' : `Confirmar ${formatNumber(selectedPreviewRows.length)} ${selectedPreviewRows.length === 1 ? 'OP' : 'OPs'}`} {previewStep === 'selection'
? `Continuar com ${formatNumber(selectedPreviewRows.length)} ${selectedPreviewRows.length === 1 ? 'OP' : 'OPs'}`
: isGeneratingOrders ? 'Gerando OPs' : `Confirmar ${formatNumber(selectedPreviewRows.length)} ${selectedPreviewRows.length === 1 ? 'OP' : 'OPs'}`}
</button> </button>
</div> </div>
</div> </div>