🚀 Auto-deploy: Implementado edição de apontamentos (lápis) no Modo Smart e regras de processo subsequente

This commit is contained in:
2026-09-23 21:57:03 +00:00
parent d7e920a729
commit 7ad5006ba5
2 changed files with 182 additions and 38 deletions
@@ -6,7 +6,8 @@ import {
RotateCcw, RotateCcw,
Layers, Layers,
ChevronRight, ChevronRight,
AlertCircle AlertCircle,
Pencil
} from 'lucide-react'; } from 'lucide-react';
import { OFAtiva } from '@/hooks/useOFsAtivas'; import { OFAtiva } from '@/hooks/useOFsAtivas';
import { useAuth } from '@/hooks/useAuth'; import { useAuth } from '@/hooks/useAuth';
@@ -45,6 +46,7 @@ export const SmartMontagemFlow: React.FC<SmartMontagemFlowProps> = ({
const [itemSelecionado, setItemSelecionado] = useState<ItemMontagem | null>(null); const [itemSelecionado, setItemSelecionado] = useState<ItemMontagem | null>(null);
const [qtdApontar, setQtdApontar] = useState<number>(1); const [qtdApontar, setQtdApontar] = useState<number>(1);
const [salvando, setSalvando] = useState<boolean>(false); const [salvando, setSalvando] = useState<boolean>(false);
const [modoEdicao, setModoEdicao] = useState<boolean>(false);
// Carregar saldo de peças expedidas vs já montadas // Carregar saldo de peças expedidas vs já montadas
const carregarDadosMontagem = React.useCallback(async () => { const carregarDadosMontagem = React.useCallback(async () => {
@@ -191,24 +193,42 @@ export const SmartMontagemFlow: React.FC<SmartMontagemFlowProps> = ({
setSalvando(true); setSalvando(true);
const rdoId = await obterOuCriarRDO(); const rdoId = await obterOuCriarRDO();
let delta = qtdApontar;
if (modoEdicao) {
delta = qtdApontar - itemSelecionado.quantidade_ja_apontada;
}
if (delta === 0) {
smartAudio.playClick();
toast.info('Nenhuma alteração na quantidade foi feita.');
setItemSelecionado(null);
setModoEdicao(false);
setSalvando(false);
return;
}
const { error } = await supabase.from('apontamentos_peca_obra').insert({ const { error } = await supabase.from('apontamentos_peca_obra').insert({
rdo_id: rdoId, rdo_id: rdoId,
marca_peca: itemSelecionado.marca, marca_peca: itemSelecionado.marca,
quantidade: qtdApontar, quantidade: delta,
periodo: 'Normal', periodo: 'Normal',
status: 'Montado', status: 'Montado',
}); });
if (error) throw error; if (error) throw error;
smartAudio.playSuccess(); if (modoEdicao) {
toast.success(`🏗️ Montagem registrada: ${qtdApontar}x ${itemSelecionado.marca}!`); toast.success(`✏️ Ajuste registrado: ${itemSelecionado.marca} ajustado para ${qtdApontar}!`);
} else {
toast.success(`🏗️ Montagem registrada: ${qtdApontar}x ${itemSelecionado.marca}!`);
}
if (onApontamentoMontagem) { if (onApontamentoMontagem) {
onApontamentoMontagem(); onApontamentoMontagem();
} }
setItemSelecionado(null); setItemSelecionado(null);
setModoEdicao(false);
carregarDadosMontagem(); carregarDadosMontagem();
} catch (e: any) { } catch (e: any) {
smartAudio.playAlert(); smartAudio.playAlert();
@@ -224,6 +244,7 @@ export const SmartMontagemFlow: React.FC<SmartMontagemFlowProps> = ({
// ───────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────
if (itemSelecionado) { if (itemSelecionado) {
const saldo = itemSelecionado.saldo_disponivel; const saldo = itemSelecionado.saldo_disponivel;
const maxPermitido = modoEdicao ? itemSelecionado.quantidade_ja_apontada + saldo : saldo;
return ( return (
<div className="flex flex-col flex-1 p-4 max-w-xl mx-auto w-full"> <div className="flex flex-col flex-1 p-4 max-w-xl mx-auto w-full">
@@ -258,7 +279,7 @@ export const SmartMontagemFlow: React.FC<SmartMontagemFlowProps> = ({
{/* Display da Quantidade */} {/* Display da Quantidade */}
<div className="my-auto py-3 text-center"> <div className="my-auto py-3 text-center">
<span className="text-xs font-black uppercase text-slate-500 dark:text-slate-400 tracking-wider"> <span className="text-xs font-black uppercase text-slate-500 dark:text-slate-400 tracking-wider">
Quantidade Montada Agora {modoEdicao ? 'Ajustar a Quantidade Total Montada' : 'Quantidade Montada Agora'}
</span> </span>
<div className="flex items-center justify-center gap-4 mt-2"> <div className="flex items-center justify-center gap-4 mt-2">
<div className="min-w-[120px] h-20 px-6 rounded-3xl bg-slate-50 dark:bg-slate-900 border-2 border-emerald-500 flex items-center justify-center shadow-inner"> <div className="min-w-[120px] h-20 px-6 rounded-3xl bg-slate-50 dark:bg-slate-900 border-2 border-emerald-500 flex items-center justify-center shadow-inner">
@@ -286,7 +307,7 @@ export const SmartMontagemFlow: React.FC<SmartMontagemFlowProps> = ({
type="button" type="button"
onClick={() => { onClick={() => {
smartAudio.playClick(); smartAudio.playClick();
setQtdApontar((p) => Math.min(saldo, p + 1)); setQtdApontar((p) => Math.min(maxPermitido, p + 1));
}} }}
className="h-16 rounded-2xl bg-slate-100 hover:bg-slate-200 text-slate-800 border-2 border-slate-200 dark:bg-slate-800 dark:hover:bg-slate-750 active:bg-slate-700 dark:border-slate-700 dark:text-white text-xl font-black active:scale-95 shadow-sm" className="h-16 rounded-2xl bg-slate-100 hover:bg-slate-200 text-slate-800 border-2 border-slate-200 dark:bg-slate-800 dark:hover:bg-slate-750 active:bg-slate-700 dark:border-slate-700 dark:text-white text-xl font-black active:scale-95 shadow-sm"
> >
@@ -296,7 +317,7 @@ export const SmartMontagemFlow: React.FC<SmartMontagemFlowProps> = ({
type="button" type="button"
onClick={() => { onClick={() => {
smartAudio.playClick(); smartAudio.playClick();
setQtdApontar((p) => Math.min(saldo, p + 2)); setQtdApontar((p) => Math.min(maxPermitido, p + 2));
}} }}
className="h-16 rounded-2xl bg-slate-100 hover:bg-slate-200 text-slate-800 border-2 border-slate-200 dark:bg-slate-800 dark:hover:bg-slate-750 active:bg-slate-700 dark:border-slate-700 dark:text-white text-xl font-black active:scale-95 shadow-sm" className="h-16 rounded-2xl bg-slate-100 hover:bg-slate-200 text-slate-800 border-2 border-slate-200 dark:bg-slate-800 dark:hover:bg-slate-750 active:bg-slate-700 dark:border-slate-700 dark:text-white text-xl font-black active:scale-95 shadow-sm"
> >
@@ -306,7 +327,7 @@ export const SmartMontagemFlow: React.FC<SmartMontagemFlowProps> = ({
type="button" type="button"
onClick={() => { onClick={() => {
smartAudio.playClick(); smartAudio.playClick();
setQtdApontar((p) => Math.min(saldo, p + 5)); setQtdApontar((p) => Math.min(maxPermitido, p + 5));
}} }}
className="h-16 rounded-2xl bg-slate-100 hover:bg-slate-200 text-slate-800 border-2 border-slate-200 dark:bg-slate-800 dark:hover:bg-slate-750 active:bg-slate-700 dark:border-slate-700 dark:text-white text-xl font-black active:scale-95 shadow-sm" className="h-16 rounded-2xl bg-slate-100 hover:bg-slate-200 text-slate-800 border-2 border-slate-200 dark:bg-slate-800 dark:hover:bg-slate-750 active:bg-slate-700 dark:border-slate-700 dark:text-white text-xl font-black active:scale-95 shadow-sm"
> >
@@ -316,12 +337,12 @@ export const SmartMontagemFlow: React.FC<SmartMontagemFlowProps> = ({
type="button" type="button"
onClick={() => { onClick={() => {
smartAudio.playClick(); smartAudio.playClick();
setQtdApontar(saldo > 0 ? saldo : 1); setQtdApontar(maxPermitido > 0 ? maxPermitido : 1);
}} }}
className="h-16 rounded-2xl bg-emerald-100 hover:bg-emerald-200 border-2 border-emerald-300 text-emerald-800 dark:bg-emerald-500/20 dark:hover:bg-emerald-500/30 dark:border-emerald-500/60 dark:text-emerald-400 text-sm font-black active:scale-95 flex flex-col items-center justify-center leading-tight shadow-sm" className="h-16 rounded-2xl bg-emerald-100 hover:bg-emerald-200 border-2 border-emerald-300 text-emerald-800 dark:bg-emerald-500/20 dark:hover:bg-emerald-500/30 dark:border-emerald-500/60 dark:text-emerald-400 text-sm font-black active:scale-95 flex flex-col items-center justify-center leading-tight shadow-sm"
> >
<span>TODAS</span> <span>TODAS</span>
<span className="text-[11px] opacity-80">({saldo})</span> <span className="text-[11px] opacity-80">({maxPermitido})</span>
</button> </button>
</div> </div>
@@ -338,6 +359,11 @@ export const SmartMontagemFlow: React.FC<SmartMontagemFlowProps> = ({
<div className="h-6 w-6 animate-spin rounded-full border-3 border-white border-t-transparent" /> <div className="h-6 w-6 animate-spin rounded-full border-3 border-white border-t-transparent" />
<span>Registrando...</span> <span>Registrando...</span>
</> </>
) : modoEdicao ? (
<>
<Check className="h-6 w-6" />
<span>SALVAR EDIÇÃO ({qtdApontar})</span>
</>
) : ( ) : (
<> <>
<Check className="h-6 w-6" /> <Check className="h-6 w-6" />
@@ -352,6 +378,7 @@ export const SmartMontagemFlow: React.FC<SmartMontagemFlowProps> = ({
onClick={() => { onClick={() => {
smartAudio.playClick(); smartAudio.playClick();
setItemSelecionado(null); setItemSelecionado(null);
setModoEdicao(false);
}} }}
className="w-full h-13 bg-white hover:bg-slate-100 border border-slate-200 text-slate-700 dark:bg-slate-800 dark:border-slate-700 dark:text-slate-300 text-sm font-bold rounded-2xl active:scale-98 shadow-sm" className="w-full h-13 bg-white hover:bg-slate-100 border border-slate-200 text-slate-700 dark:bg-slate-800 dark:border-slate-700 dark:text-slate-300 text-sm font-bold rounded-2xl active:scale-98 shadow-sm"
> >
@@ -423,15 +450,36 @@ export const SmartMontagemFlow: React.FC<SmartMontagemFlowProps> = ({
</div> </div>
<div className="text-right shrink-0"> <div className="text-right shrink-0">
<div className="text-xs font-semibold text-slate-500 dark:text-slate-400"> <div className="text-xs font-semibold text-slate-500 dark:text-slate-400 flex flex-col items-end gap-1">
Entregues: {item.quantidade_expedida} <span>Entregues: {item.quantidade_expedida}</span>
{item.quantidade_ja_apontada > 0 && (
<div className="flex items-center gap-1.5 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 px-2 py-0.5 rounded-md mt-0.5 shadow-sm">
<span className="text-[11px] font-bold text-slate-700 dark:text-slate-300">
Montada: {item.quantidade_ja_apontada}
</span>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
smartAudio.playClick();
setItemSelecionado(item);
setQtdApontar(item.quantidade_ja_apontada);
setModoEdicao(true);
}}
className="text-emerald-600 hover:text-emerald-800 dark:text-emerald-500 dark:hover:text-emerald-300 transition-colors p-1 rounded-md hover:bg-emerald-50 dark:hover:bg-emerald-500/10"
title="Editar quantidade montada"
>
<Pencil className="h-3.5 w-3.5" />
</button>
</div>
)}
</div> </div>
{concluida ? ( {concluida ? (
<span className="inline-flex items-center gap-1 text-xs font-black text-emerald-800 bg-emerald-100 border border-emerald-300 dark:text-emerald-400 px-2 py-0.5 rounded-full dark:bg-emerald-950/80 dark:border-emerald-700"> <span className="inline-flex items-center gap-1 text-xs font-black text-emerald-800 bg-emerald-100 border border-emerald-300 dark:text-emerald-400 px-2 py-0.5 rounded-full dark:bg-emerald-950/80 dark:border-emerald-700 mt-2">
<Check className="h-3 w-3" /> 100% Montada <Check className="h-3 w-3" /> 100% Montada
</span> </span>
) : ( ) : (
<span className="inline-flex items-center gap-1 text-xs font-black text-emerald-800 bg-emerald-100 border border-emerald-300 dark:text-emerald-300 px-2 py-0.5 rounded-full dark:bg-emerald-950/80 dark:border-emerald-700"> <span className="inline-flex items-center gap-1 text-xs font-black text-emerald-800 bg-emerald-100 border border-emerald-300 dark:text-emerald-300 px-2 py-0.5 rounded-full dark:bg-emerald-950/80 dark:border-emerald-700 mt-2">
Saldo: {item.saldo_disponivel} Saldo: {item.saldo_disponivel}
</span> </span>
)} )}
@@ -446,6 +494,7 @@ export const SmartMontagemFlow: React.FC<SmartMontagemFlowProps> = ({
smartAudio.playClick(); smartAudio.playClick();
setItemSelecionado(item); setItemSelecionado(item);
setQtdApontar(item.saldo_disponivel > 0 ? 1 : 1); setQtdApontar(item.saldo_disponivel > 0 ? 1 : 1);
setModoEdicao(false);
}} }}
className={`w-full h-13 text-sm font-black rounded-xl uppercase tracking-wider transition-all active:scale-[0.98] ${ className={`w-full h-13 text-sm font-black rounded-xl uppercase tracking-wider transition-all active:scale-[0.98] ${
concluida concluida
+119 -24
View File
@@ -14,7 +14,8 @@ import {
AlertTriangle, AlertTriangle,
ChevronRight, ChevronRight,
Filter, Filter,
Delete Delete,
Pencil
} from 'lucide-react'; } from 'lucide-react';
import { OFAtiva } from '@/hooks/useOFsAtivas'; import { OFAtiva } from '@/hooks/useOFsAtivas';
import { useProcessosFabricacao, ProcessoFabricacao } from '@/hooks/useProcessosFabricacao'; import { useProcessosFabricacao, ProcessoFabricacao } from '@/hooks/useProcessosFabricacao';
@@ -47,6 +48,8 @@ interface CalculoSaldoPeca {
qtdDisponivelParaEntrar: number; qtdDisponivelParaEntrar: number;
ehSemMontagem: boolean; ehSemMontagem: boolean;
motivoBloqueio: string | null; motivoBloqueio: string | null;
minPermitido: number;
procSeguinteNome: string | null;
} }
// Função auxiliar pura para cálculo sequencial de saldo por processo e regra S/M // Função auxiliar pura para cálculo sequencial de saldo por processo e regra S/M
@@ -102,6 +105,29 @@ export const calcularSaldoProcessoPeca = (
} }
} }
// REGRA 3: Encontrar processo subsequente válido para limitar a edição (minPermitido)
let minPermitido = 0;
let procSeguinteNome: string | null = null;
if (indexAtual >= 0 && indexAtual < procsOrdenados.length - 1) {
let procSeguinteValido: ProcessoFabricacao | null = null;
for (let i = indexAtual + 1; i < procsOrdenados.length; i++) {
const proc = procsOrdenados[i];
const nomeP = proc.nome.toLowerCase();
// Se a peça for S/M, pula solda na busca do subsequente
if (ehSemMontagem && nomeP.includes('solda')) {
continue;
}
procSeguinteValido = proc;
break;
}
if (procSeguinteValido) {
const keySeg = `${peca.id}_${procSeguinteValido.id}`;
minPermitido = mapaProd.get(keySeg) || 0;
procSeguinteNome = procSeguinteValido.nome;
}
}
const saldo = Math.max(0, qtdDisponivelParaEntrar - jaProduzidoAtual); const saldo = Math.max(0, qtdDisponivelParaEntrar - jaProduzidoAtual);
return { return {
@@ -110,6 +136,8 @@ export const calcularSaldoProcessoPeca = (
qtdDisponivelParaEntrar, qtdDisponivelParaEntrar,
ehSemMontagem, ehSemMontagem,
motivoBloqueio, motivoBloqueio,
minPermitido,
procSeguinteNome
}; };
}; };
@@ -136,6 +164,7 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
// Quantidade selecionada no teclado rápido // Quantidade selecionada no teclado rápido
const [qtdApontar, setQtdApontar] = useState<number>(1); const [qtdApontar, setQtdApontar] = useState<number>(1);
const [salvando, setSalvando] = useState<boolean>(false); const [salvando, setSalvando] = useState<boolean>(false);
const [modoEdicao, setModoEdicao] = useState<boolean>(false);
// Dados carregados da OF // Dados carregados da OF
const [pecas, setPecas] = useState<PecaData[]>([]); const [pecas, setPecas] = useState<PecaData[]>([]);
@@ -252,6 +281,7 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
smartAudio.playClick(); smartAudio.playClick();
setPecaSelecionada(peca); setPecaSelecionada(peca);
setQtdApontar(saldo > 0 ? (saldo >= 1 ? 1 : saldo) : 1); setQtdApontar(saldo > 0 ? (saldo >= 1 ? 1 : saldo) : 1);
setModoEdicao(false);
setEtapaAtual('quantidade'); setEtapaAtual('quantidade');
}; };
@@ -259,9 +289,22 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
const handleConfirmarApontamento = async () => { const handleConfirmarApontamento = async () => {
if (!pecaSelecionada || !processoSelecionado || !user) return; if (!pecaSelecionada || !processoSelecionado || !user) return;
if (qtdApontar <= 0) { if (qtdApontar < 0) {
smartAudio.playAlert(); smartAudio.playAlert();
toast.error('Informe uma quantidade maior que zero'); toast.error('A quantidade não pode ser negativa');
return;
}
if (!modoEdicao && qtdApontar <= 0) {
smartAudio.playAlert();
toast.error('Informe uma quantidade maior que zero para um novo apontamento');
return;
}
const info = calcularSaldoProcessoPeca(pecaSelecionada, processoSelecionado, processos, mapaProducao);
if (modoEdicao && qtdApontar < info.minPermitido) {
smartAudio.playAlert();
toast.error(`Não é possível reduzir para ${qtdApontar}. O processo seguinte (${info.procSeguinteNome}) já consumiu ${info.minPermitido} peças. Ajuste o processo seguinte primeiro.`);
return; return;
} }
@@ -269,15 +312,30 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
setSalvando(true); setSalvando(true);
const hoje = new Date().toISOString().split('T')[0]; const hoje = new Date().toISOString().split('T')[0];
let delta = qtdApontar;
if (modoEdicao) {
delta = qtdApontar - info.jaProduzido;
}
if (delta === 0) {
smartAudio.playClick();
toast.info('Nenhuma alteração na quantidade foi feita.');
setEtapaAtual('pecas');
setPecaSelecionada(null);
setModoEdicao(false);
setSalvando(false);
return;
}
const payload = { const payload = {
of_number: obra.of_number, of_number: obra.of_number,
peca_id: pecaSelecionada.id, peca_id: pecaSelecionada.id,
tipo_apontamento: 'peca' as const, tipo_apontamento: 'peca' as const,
processo_id: processoSelecionado.id, processo_id: processoSelecionado.id,
quantidade_produzida: qtdApontar, quantidade_produzida: delta,
data_apontamento: hoje, data_apontamento: hoje,
created_by: user.id, created_by: user.id,
observacoes: 'Apontado via Modo Smart', observacoes: modoEdicao ? 'Ajuste de quantidade via Modo Smart' : 'Apontado via Modo Smart',
}; };
const { error } = await supabase.from('apontamentos_producao').insert(payload); const { error } = await supabase.from('apontamentos_producao').insert(payload);
@@ -288,10 +346,14 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
// BIP sonoro de sucesso e vibração háptica! // BIP sonoro de sucesso e vibração háptica!
smartAudio.playSuccess(); smartAudio.playSuccess();
toast.success(`✅ Apontado: ${qtdApontar}x ${pecaSelecionada.marca} (${processoSelecionado.nome})`); if (modoEdicao) {
toast.success(`✏️ Ajuste realizado: ${pecaSelecionada.marca} (${processoSelecionado.nome}) para ${qtdApontar} un`);
} else {
toast.success(`✅ Apontado: ${qtdApontar}x ${pecaSelecionada.marca} (${processoSelecionado.nome})`);
}
// Notificar estatísticas do operador // Notificar estatísticas do operador
const pesoTotalKg = qtdApontar * (pecaSelecionada.peso_unitario || 0); const pesoTotalKg = delta * (pecaSelecionada.peso_unitario || 0);
onApontamentoRealizado(qtdApontar, pesoTotalKg); onApontamentoRealizado(qtdApontar, pesoTotalKg);
// FAST LOOP / STICKY CONTEXT: // FAST LOOP / STICKY CONTEXT:
@@ -301,13 +363,14 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
{ {
peca_id: pecaSelecionada.id, peca_id: pecaSelecionada.id,
processo_id: processoSelecionado.id, processo_id: processoSelecionado.id,
quantidade_produzida: qtdApontar, quantidade_produzida: delta,
}, },
]); ]);
// Permanece na mesma fase para apontar a próxima peça sem voltar tudo! // Permanece na mesma fase para apontar a próxima peça sem voltar tudo!
setEtapaAtual('pecas'); setEtapaAtual('pecas');
setPecaSelecionada(null); setPecaSelecionada(null);
setModoEdicao(false);
} catch (e: any) { } catch (e: any) {
smartAudio.playAlert(); smartAudio.playAlert();
console.error('Erro ao salvar apontamento:', e); console.error('Erro ao salvar apontamento:', e);
@@ -584,17 +647,41 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
</div> </div>
<div className="text-right shrink-0"> <div className="text-right shrink-0">
<div className="text-xs font-semibold text-slate-500 dark:text-slate-400">Total: {item.quantidade}</div> <div className="text-xs font-semibold text-slate-500 dark:text-slate-400 flex flex-col items-end gap-1">
<span>Total: {item.quantidade}</span>
{item.jaProduzido > 0 && (
<div className="flex items-center gap-1.5 bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-slate-700 px-2 py-0.5 rounded-md mt-0.5 mb-0.5 shadow-sm">
<span className="text-[11px] font-bold text-slate-700 dark:text-slate-300">
Apontada: {item.jaProduzido}
</span>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
smartAudio.playClick();
setPecaSelecionada(item);
setQtdApontar(item.jaProduzido);
setModoEdicao(true);
setEtapaAtual('quantidade');
}}
className="text-amber-600 hover:text-amber-800 dark:text-amber-500 dark:hover:text-amber-300 transition-colors p-1 rounded-md hover:bg-amber-50 dark:hover:bg-amber-500/10"
title="Editar quantidade apontada"
>
<Pencil className="h-3.5 w-3.5" />
</button>
</div>
)}
</div>
{concluida ? ( {concluida ? (
<span className="inline-flex items-center gap-1 text-xs font-black text-emerald-800 dark:text-emerald-400 px-2 py-0.5 rounded-full bg-emerald-100 dark:bg-emerald-950/80 border border-emerald-300 dark:border-emerald-700"> <span className="inline-flex items-center gap-1 text-xs font-black text-emerald-800 dark:text-emerald-400 px-2 py-0.5 rounded-full bg-emerald-100 dark:bg-emerald-950/80 border border-emerald-300 dark:border-emerald-700 mt-1">
<Check className="h-3 w-3" /> 100% Pronto <Check className="h-3 w-3" /> 100% Pronto
</span> </span>
) : item.saldo > 0 ? ( ) : item.saldo > 0 ? (
<span className="inline-flex items-center gap-1 text-xs font-black text-amber-800 dark:text-amber-300 px-2 py-0.5 rounded-full bg-amber-100 dark:bg-amber-950/80 border border-amber-300 dark:border-amber-700"> <span className="inline-flex items-center gap-1 text-xs font-black text-amber-800 dark:text-amber-300 px-2 py-0.5 rounded-full bg-amber-100 dark:bg-amber-950/80 border border-amber-300 dark:border-amber-700 mt-1">
Disponível: {item.saldo} Disponível: {item.saldo}
</span> </span>
) : ( ) : (
<span className="inline-flex items-center gap-1 text-[11px] font-bold text-slate-600 dark:text-slate-400 px-2 py-0.5 rounded-full bg-slate-100 dark:bg-slate-800 border border-slate-300 dark:border-slate-700"> <span className="inline-flex items-center gap-1 text-[11px] font-bold text-slate-600 dark:text-slate-400 px-2 py-0.5 rounded-full bg-slate-100 dark:bg-slate-800 border border-slate-300 dark:border-slate-700 mt-1">
Pendente ant. Pendente ant.
</span> </span>
)} )}
@@ -641,6 +728,7 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
smartAudio.playClick(); smartAudio.playClick();
setPecaSelecionada(item); setPecaSelecionada(item);
setQtdApontar(item.saldo); setQtdApontar(item.saldo);
setModoEdicao(false);
setEtapaAtual('quantidade'); setEtapaAtual('quantidade');
}} }}
className="h-13 bg-amber-50 hover:bg-amber-100 active:bg-amber-200 text-amber-800 border border-amber-300 dark:bg-slate-700 dark:hover:bg-slate-650 dark:active:bg-slate-600 dark:text-amber-400 dark:border-slate-600 text-xs font-black rounded-xl uppercase active:scale-95 transition-colors" className="h-13 bg-amber-50 hover:bg-amber-100 active:bg-amber-200 text-amber-800 border border-amber-300 dark:bg-slate-700 dark:hover:bg-slate-650 dark:active:bg-slate-600 dark:text-amber-400 dark:border-slate-600 text-xs font-black rounded-xl uppercase active:scale-95 transition-colors"
@@ -682,6 +770,7 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
mapaProducao mapaProducao
); );
const saldoPendente = infoSaldoAtual.saldo; const saldoPendente = infoSaldoAtual.saldo;
const maxPermitido = modoEdicao ? infoSaldoAtual.jaProduzido + infoSaldoAtual.saldo : infoSaldoAtual.saldo;
// Ajuste por incremento // Ajuste por incremento
const somarQtd = (valor: number) => { const somarQtd = (valor: number) => {
@@ -700,10 +789,10 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
const strAtual = prev <= 0 ? '' : prev.toString(); const strAtual = prev <= 0 ? '' : prev.toString();
const novaStr = strAtual + digito.toString(); const novaStr = strAtual + digito.toString();
const novoValor = parseInt(novaStr, 10) || 0; const novoValor = parseInt(novaStr, 10) || 0;
if (saldoPendente > 0 && novoValor > saldoPendente) { if (maxPermitido > 0 && novoValor > maxPermitido) {
smartAudio.playAlert(); smartAudio.playAlert();
toast.warning(`Limite de saldo: máximo de ${saldoPendente} unidades`); toast.warning(`Limite de saldo: máximo de ${maxPermitido} unidades`);
return saldoPendente; return maxPermitido;
} }
return novoValor; return novoValor;
}); });
@@ -728,17 +817,17 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
// Definir Todas // Definir Todas
const definirTodas = () => { const definirTodas = () => {
smartAudio.playClick(); smartAudio.playClick();
setQtdApontar(saldoPendente > 0 ? saldoPendente : 1); setQtdApontar(maxPermitido > 0 ? maxPermitido : 1);
}; };
// Digitação direta no input pelo teclado físico ou celular // Digitação direta no input pelo teclado físico ou celular
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const val = e.target.value.replace(/\D/g, ''); const val = e.target.value.replace(/\D/g, '');
const num = parseInt(val, 10) || 0; const num = parseInt(val, 10) || 0;
if (saldoPendente > 0 && num > saldoPendente) { if (maxPermitido > 0 && num > maxPermitido) {
setQtdApontar(saldoPendente); setQtdApontar(maxPermitido);
smartAudio.playAlert(); smartAudio.playAlert();
toast.warning(`Máximo permitido: ${saldoPendente} unidades`); toast.warning(`Máximo permitido: ${maxPermitido} unidades`);
} else { } else {
setQtdApontar(num); setQtdApontar(num);
} }
@@ -772,7 +861,7 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
<div className="flex items-center justify-between text-xs text-slate-500 dark:text-slate-400 mt-2 pt-2 border-t border-slate-200 dark:border-slate-700/80"> <div className="flex items-center justify-between text-xs text-slate-500 dark:text-slate-400 mt-2 pt-2 border-t border-slate-200 dark:border-slate-700/80">
<span>Total da Peça: {pecaSelecionada.quantidade} un</span> <span>Total da Peça: {pecaSelecionada.quantidade} un</span>
<span className="text-amber-700 dark:text-amber-400 font-bold"> <span className="text-amber-700 dark:text-amber-400 font-bold">
Saldo Pendente: {saldoPendente} un {modoEdicao ? `Máximo: ${maxPermitido} un` : `Saldo Pendente: ${maxPermitido} un`}
</span> </span>
</div> </div>
</div> </div>
@@ -780,14 +869,14 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
{/* Visor / Input Central da Quantidade com botões finos -1 e +1 */} {/* Visor / Input Central da Quantidade com botões finos -1 e +1 */}
<div className="mb-2 text-center"> <div className="mb-2 text-center">
<span className="text-[11px] font-black uppercase text-slate-500 dark:text-slate-400 tracking-wider"> <span className="text-[11px] font-black uppercase text-slate-500 dark:text-slate-400 tracking-wider">
Digite ou Escolha a Quantidade a Apontar {modoEdicao ? 'Ajustar a Quantidade Total Apontada' : 'Digite ou Escolha a Quantidade a Apontar'}
</span> </span>
<div className="flex items-center justify-center gap-2 mt-1.5"> <div className="flex items-center justify-center gap-2 mt-1.5">
<button <button
type="button" type="button"
onClick={() => somarQtd(-1)} onClick={() => somarQtd(-1)}
disabled={qtdApontar <= 1} disabled={qtdApontar <= 0}
className="h-16 w-14 rounded-2xl bg-white hover:bg-slate-100 active:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-750 dark:active:bg-slate-700 border-2 border-slate-200 dark:border-slate-700 disabled:opacity-40 text-slate-800 dark:text-white flex items-center justify-center font-black active:scale-95 shadow-sm transition-colors" className="h-16 w-14 rounded-2xl bg-white hover:bg-slate-100 active:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-750 dark:active:bg-slate-700 border-2 border-slate-200 dark:border-slate-700 disabled:opacity-40 text-slate-800 dark:text-white flex items-center justify-center font-black active:scale-95 shadow-sm transition-colors"
title="Diminuir 1" title="Diminuir 1"
> >
@@ -810,7 +899,7 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
<button <button
type="button" type="button"
onClick={() => somarQtd(1)} onClick={() => somarQtd(1)}
disabled={saldoPendente > 0 && qtdApontar >= saldoPendente} disabled={maxPermitido > 0 && qtdApontar >= maxPermitido}
className="h-16 w-14 rounded-2xl bg-white hover:bg-slate-100 active:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-750 dark:active:bg-slate-700 border-2 border-slate-200 dark:border-slate-700 disabled:opacity-40 text-slate-800 dark:text-white flex items-center justify-center font-black active:scale-95 shadow-sm transition-colors" className="h-16 w-14 rounded-2xl bg-white hover:bg-slate-100 active:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-750 dark:active:bg-slate-700 border-2 border-slate-200 dark:border-slate-700 disabled:opacity-40 text-slate-800 dark:text-white flex items-center justify-center font-black active:scale-95 shadow-sm transition-colors"
title="Aumentar 1" title="Aumentar 1"
> >
@@ -847,7 +936,7 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
onClick={definirTodas} onClick={definirTodas}
className="h-11 rounded-xl bg-amber-100 hover:bg-amber-200 active:bg-amber-300 border border-amber-300 text-amber-800 dark:bg-amber-500/20 dark:hover:bg-amber-500/30 dark:border-amber-500/50 dark:text-amber-300 text-xs font-black active:scale-95 transition-colors" className="h-11 rounded-xl bg-amber-100 hover:bg-amber-200 active:bg-amber-300 border border-amber-300 text-amber-800 dark:bg-amber-500/20 dark:hover:bg-amber-500/30 dark:border-amber-500/50 dark:text-amber-300 text-xs font-black active:scale-95 transition-colors"
> >
TODAS ({saldoPendente}) TODAS ({maxPermitido})
</button> </button>
</div> </div>
@@ -901,6 +990,11 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
<div className="h-5 w-5 animate-spin rounded-full border-2 border-white border-t-transparent" /> <div className="h-5 w-5 animate-spin rounded-full border-2 border-white border-t-transparent" />
<span>Gravando...</span> <span>Gravando...</span>
</> </>
) : modoEdicao ? (
<>
<Check className="h-5 w-5" />
<span>SALVAR EDIÇÃO ({qtdApontar} UN)</span>
</>
) : ( ) : (
<> <>
<Check className="h-5 w-5" /> <Check className="h-5 w-5" />
@@ -916,6 +1010,7 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
onClick={() => { onClick={() => {
smartAudio.playClick(); smartAudio.playClick();
setEtapaAtual('pecas'); setEtapaAtual('pecas');
setModoEdicao(false);
}} }}
className="w-full h-11 bg-white hover:bg-slate-100 text-slate-700 border-slate-300 dark:bg-slate-800 dark:border-slate-700 dark:text-slate-300 text-xs font-bold rounded-xl active:scale-98 transition-colors" className="w-full h-11 bg-white hover:bg-slate-100 text-slate-700 border-slate-300 dark:bg-slate-800 dark:border-slate-700 dark:text-slate-300 text-xs font-bold rounded-xl active:scale-98 transition-colors"
> >