diff --git a/src/pages/smart/components/SmartMontagemFlow.tsx b/src/pages/smart/components/SmartMontagemFlow.tsx index 196998b..6250428 100644 --- a/src/pages/smart/components/SmartMontagemFlow.tsx +++ b/src/pages/smart/components/SmartMontagemFlow.tsx @@ -6,7 +6,8 @@ import { RotateCcw, Layers, ChevronRight, - AlertCircle + AlertCircle, + Pencil } from 'lucide-react'; import { OFAtiva } from '@/hooks/useOFsAtivas'; import { useAuth } from '@/hooks/useAuth'; @@ -45,6 +46,7 @@ export const SmartMontagemFlow: React.FC = ({ const [itemSelecionado, setItemSelecionado] = useState(null); const [qtdApontar, setQtdApontar] = useState(1); const [salvando, setSalvando] = useState(false); + const [modoEdicao, setModoEdicao] = useState(false); // Carregar saldo de peças expedidas vs já montadas const carregarDadosMontagem = React.useCallback(async () => { @@ -191,24 +193,42 @@ export const SmartMontagemFlow: React.FC = ({ setSalvando(true); 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({ rdo_id: rdoId, marca_peca: itemSelecionado.marca, - quantidade: qtdApontar, + quantidade: delta, periodo: 'Normal', status: 'Montado', }); if (error) throw error; - smartAudio.playSuccess(); - toast.success(`🏗️ Montagem registrada: ${qtdApontar}x ${itemSelecionado.marca}!`); + if (modoEdicao) { + toast.success(`✏️ Ajuste registrado: ${itemSelecionado.marca} ajustado para ${qtdApontar}!`); + } else { + toast.success(`🏗️ Montagem registrada: ${qtdApontar}x ${itemSelecionado.marca}!`); + } if (onApontamentoMontagem) { onApontamentoMontagem(); } setItemSelecionado(null); + setModoEdicao(false); carregarDadosMontagem(); } catch (e: any) { smartAudio.playAlert(); @@ -224,6 +244,7 @@ export const SmartMontagemFlow: React.FC = ({ // ───────────────────────────────────────────────────────────── if (itemSelecionado) { const saldo = itemSelecionado.saldo_disponivel; + const maxPermitido = modoEdicao ? itemSelecionado.quantidade_ja_apontada + saldo : saldo; return (
@@ -258,7 +279,7 @@ export const SmartMontagemFlow: React.FC = ({ {/* Display da Quantidade */}
- Quantidade Montada Agora + {modoEdicao ? 'Ajustar a Quantidade Total Montada' : 'Quantidade Montada Agora'}
@@ -286,7 +307,7 @@ export const SmartMontagemFlow: React.FC = ({ type="button" onClick={() => { 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" > @@ -296,7 +317,7 @@ export const SmartMontagemFlow: React.FC = ({ type="button" onClick={() => { 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" > @@ -306,7 +327,7 @@ export const SmartMontagemFlow: React.FC = ({ type="button" onClick={() => { 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" > @@ -316,12 +337,12 @@ export const SmartMontagemFlow: React.FC = ({ type="button" onClick={() => { 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" > TODAS - ({saldo}) + ({maxPermitido})
@@ -338,6 +359,11 @@ export const SmartMontagemFlow: React.FC = ({
Registrando... + ) : modoEdicao ? ( + <> + + SALVAR EDIÇÃO ({qtdApontar}) + ) : ( <> @@ -352,6 +378,7 @@ export const SmartMontagemFlow: React.FC = ({ onClick={() => { smartAudio.playClick(); 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" > @@ -423,15 +450,36 @@ export const SmartMontagemFlow: React.FC = ({
-
- Entregues: {item.quantidade_expedida} +
+ Entregues: {item.quantidade_expedida} + {item.quantidade_ja_apontada > 0 && ( +
+ + Montada: {item.quantidade_ja_apontada} + + +
+ )}
{concluida ? ( - + 100% Montada ) : ( - + Saldo: {item.saldo_disponivel} )} @@ -446,6 +494,7 @@ export const SmartMontagemFlow: React.FC = ({ smartAudio.playClick(); setItemSelecionado(item); 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] ${ concluida diff --git a/src/pages/smart/components/SmartProducaoFlow.tsx b/src/pages/smart/components/SmartProducaoFlow.tsx index 52b9835..5d54a01 100644 --- a/src/pages/smart/components/SmartProducaoFlow.tsx +++ b/src/pages/smart/components/SmartProducaoFlow.tsx @@ -14,7 +14,8 @@ import { AlertTriangle, ChevronRight, Filter, - Delete + Delete, + Pencil } from 'lucide-react'; import { OFAtiva } from '@/hooks/useOFsAtivas'; import { useProcessosFabricacao, ProcessoFabricacao } from '@/hooks/useProcessosFabricacao'; @@ -47,6 +48,8 @@ interface CalculoSaldoPeca { qtdDisponivelParaEntrar: number; ehSemMontagem: boolean; motivoBloqueio: string | null; + minPermitido: number; + procSeguinteNome: string | null; } // 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); return { @@ -110,6 +136,8 @@ export const calcularSaldoProcessoPeca = ( qtdDisponivelParaEntrar, ehSemMontagem, motivoBloqueio, + minPermitido, + procSeguinteNome }; }; @@ -136,6 +164,7 @@ export const SmartProducaoFlow: React.FC = ({ // Quantidade selecionada no teclado rápido const [qtdApontar, setQtdApontar] = useState(1); const [salvando, setSalvando] = useState(false); + const [modoEdicao, setModoEdicao] = useState(false); // Dados carregados da OF const [pecas, setPecas] = useState([]); @@ -252,6 +281,7 @@ export const SmartProducaoFlow: React.FC = ({ smartAudio.playClick(); setPecaSelecionada(peca); setQtdApontar(saldo > 0 ? (saldo >= 1 ? 1 : saldo) : 1); + setModoEdicao(false); setEtapaAtual('quantidade'); }; @@ -259,9 +289,22 @@ export const SmartProducaoFlow: React.FC = ({ const handleConfirmarApontamento = async () => { if (!pecaSelecionada || !processoSelecionado || !user) return; - if (qtdApontar <= 0) { + if (qtdApontar < 0) { 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; } @@ -269,15 +312,30 @@ export const SmartProducaoFlow: React.FC = ({ setSalvando(true); 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 = { of_number: obra.of_number, peca_id: pecaSelecionada.id, tipo_apontamento: 'peca' as const, processo_id: processoSelecionado.id, - quantidade_produzida: qtdApontar, + quantidade_produzida: delta, data_apontamento: hoje, 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); @@ -288,10 +346,14 @@ export const SmartProducaoFlow: React.FC = ({ // BIP sonoro de sucesso e vibração háptica! 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 - const pesoTotalKg = qtdApontar * (pecaSelecionada.peso_unitario || 0); + const pesoTotalKg = delta * (pecaSelecionada.peso_unitario || 0); onApontamentoRealizado(qtdApontar, pesoTotalKg); // FAST LOOP / STICKY CONTEXT: @@ -301,13 +363,14 @@ export const SmartProducaoFlow: React.FC = ({ { peca_id: pecaSelecionada.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! setEtapaAtual('pecas'); setPecaSelecionada(null); + setModoEdicao(false); } catch (e: any) { smartAudio.playAlert(); console.error('Erro ao salvar apontamento:', e); @@ -584,17 +647,41 @@ export const SmartProducaoFlow: React.FC = ({
-
Total: {item.quantidade}
+
+ Total: {item.quantidade} + {item.jaProduzido > 0 && ( +
+ + Apontada: {item.jaProduzido} + + +
+ )} +
{concluida ? ( - + 100% Pronto ) : item.saldo > 0 ? ( - + Disponível: {item.saldo} ) : ( - + Pendente ant. )} @@ -641,6 +728,7 @@ export const SmartProducaoFlow: React.FC = ({ smartAudio.playClick(); setPecaSelecionada(item); setQtdApontar(item.saldo); + setModoEdicao(false); 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" @@ -682,6 +770,7 @@ export const SmartProducaoFlow: React.FC = ({ mapaProducao ); const saldoPendente = infoSaldoAtual.saldo; + const maxPermitido = modoEdicao ? infoSaldoAtual.jaProduzido + infoSaldoAtual.saldo : infoSaldoAtual.saldo; // Ajuste por incremento const somarQtd = (valor: number) => { @@ -700,10 +789,10 @@ export const SmartProducaoFlow: React.FC = ({ const strAtual = prev <= 0 ? '' : prev.toString(); const novaStr = strAtual + digito.toString(); const novoValor = parseInt(novaStr, 10) || 0; - if (saldoPendente > 0 && novoValor > saldoPendente) { + if (maxPermitido > 0 && novoValor > maxPermitido) { smartAudio.playAlert(); - toast.warning(`Limite de saldo: máximo de ${saldoPendente} unidades`); - return saldoPendente; + toast.warning(`Limite de saldo: máximo de ${maxPermitido} unidades`); + return maxPermitido; } return novoValor; }); @@ -728,17 +817,17 @@ export const SmartProducaoFlow: React.FC = ({ // Definir Todas const definirTodas = () => { smartAudio.playClick(); - setQtdApontar(saldoPendente > 0 ? saldoPendente : 1); + setQtdApontar(maxPermitido > 0 ? maxPermitido : 1); }; // Digitação direta no input pelo teclado físico ou celular const handleInputChange = (e: React.ChangeEvent) => { const val = e.target.value.replace(/\D/g, ''); const num = parseInt(val, 10) || 0; - if (saldoPendente > 0 && num > saldoPendente) { - setQtdApontar(saldoPendente); + if (maxPermitido > 0 && num > maxPermitido) { + setQtdApontar(maxPermitido); smartAudio.playAlert(); - toast.warning(`Máximo permitido: ${saldoPendente} unidades`); + toast.warning(`Máximo permitido: ${maxPermitido} unidades`); } else { setQtdApontar(num); } @@ -772,7 +861,7 @@ export const SmartProducaoFlow: React.FC = ({
Total da Peça: {pecaSelecionada.quantidade} un - Saldo Pendente: {saldoPendente} un + {modoEdicao ? `Máximo: ${maxPermitido} un` : `Saldo Pendente: ${maxPermitido} un`}
@@ -780,14 +869,14 @@ export const SmartProducaoFlow: React.FC = ({ {/* Visor / Input Central da Quantidade com botões finos -1 e +1 */}
- Digite ou Escolha a Quantidade a Apontar + {modoEdicao ? 'Ajustar a Quantidade Total Apontada' : 'Digite ou Escolha a Quantidade a Apontar'}
@@ -901,6 +990,11 @@ export const SmartProducaoFlow: React.FC = ({
Gravando... + ) : modoEdicao ? ( + <> + + SALVAR EDIÇÃO ({qtdApontar} UN) + ) : ( <> @@ -916,6 +1010,7 @@ export const SmartProducaoFlow: React.FC = ({ onClick={() => { smartAudio.playClick(); 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" >