🚀 Auto-deploy: Implementado edição de apontamentos (lápis) no Modo Smart e regras de processo subsequente
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user