feat: adicionar teclado touch e input direto para quantidade parcial no Modo Smart

This commit is contained in:
2026-09-21 22:49:06 +00:00
parent 1ce4455f37
commit e5c1439f19
+175 -58
View File
@@ -9,10 +9,12 @@ import {
Check,
Layers,
Plus,
Minus,
RotateCcw,
AlertTriangle,
ChevronRight,
Filter
Filter,
Delete
} from 'lucide-react';
import { OFAtiva } from '@/hooks/useOFsAtivas';
import { useProcessosFabricacao, ProcessoFabricacao } from '@/hooks/useProcessosFabricacao';
@@ -523,20 +525,36 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
/>
</div>
{/* Botão Gigante de Ação de Apontar */}
<div className="mt-3">
{/* Botões de Ação no Card */}
<div className="mt-3 grid grid-cols-1 sm:grid-cols-3 gap-2">
<Button
type="button"
disabled={concluida}
onClick={() => handleIniciarApontamentoPeca(item, item.saldo)}
className={`w-full h-13 text-sm sm:text-base font-black rounded-xl uppercase tracking-wider transition-all active:scale-[0.98] ${
className={`sm:col-span-2 h-13 text-sm font-black rounded-xl uppercase tracking-wider transition-all active:scale-[0.98] ${
concluida
? 'bg-slate-800 text-slate-500 border border-slate-700'
: 'bg-amber-500 hover:bg-amber-400 active:bg-amber-600 text-slate-950 shadow-md shadow-amber-950/30'
}`}
>
{concluida ? 'Processo Concluído' : `APONTAR PEÇA (${item.saldo} PENDENTES)`}
{concluida ? 'Processo Concluído' : `DIGITAR / ESCOLHER QUANTIDADE`}
</Button>
{!concluida && (
<Button
type="button"
onClick={() => {
smartAudio.playClick();
setPecaSelecionada(item);
setQtdApontar(item.saldo);
setEtapaAtual('quantidade');
}}
className="h-13 bg-slate-700 hover:bg-slate-650 active:bg-slate-600 text-amber-400 border border-slate-600 text-xs font-black rounded-xl uppercase active:scale-95"
title="Apontar todo o saldo restante de uma vez"
>
TODAS ({item.saldo})
</Button>
)}
</div>
</div>
);
@@ -560,45 +578,88 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
}
// ─────────────────────────────────────────────────────────────
// RENDER: ETAPA 1.4 — APONTAMENTO DA QUANTIDADE (TECLADO RÁPIDO)
// RENDER: ETAPA 1.4 — APONTAMENTO DA QUANTIDADE (TECLADO TOUCH + INPUT DIRETO)
// ─────────────────────────────────────────────────────────────
if (etapaAtual === 'quantidade' && pecaSelecionada && processoSelecionado) {
const key = `${pecaSelecionada.id}_${processoSelecionado.id}`;
const jaProduzido = mapaProducao.get(key) || 0;
const saldoPendente = Math.max(0, pecaSelecionada.quantidade - jaProduzido);
// Ajuste por incremento
const somarQtd = (valor: number) => {
smartAudio.playClick();
setQtdApontar((prev) => {
const nova = prev + valor;
const nova = Math.max(1, prev + valor);
return saldoPendente > 0 ? Math.min(saldoPendente, nova) : nova;
});
};
// Pressionar tecla do teclado numérico touch (0-9)
const handleDigitoTeclado = (digito: number) => {
smartAudio.playClick();
setQtdApontar((prev) => {
// Se estiver em 0 ou for primeira digitação
const strAtual = prev <= 0 ? '' : prev.toString();
const novaStr = strAtual + digito.toString();
const novoValor = parseInt(novaStr, 10) || 0;
if (saldoPendente > 0 && novoValor > saldoPendente) {
smartAudio.playAlert();
toast.warning(`Limite de saldo: máximo de ${saldoPendente} unidades`);
return saldoPendente;
}
return novoValor;
});
};
// Apagar último dígito (Backspace)
const handleBackspace = () => {
smartAudio.playClick();
setQtdApontar((prev) => {
const str = prev.toString();
if (str.length <= 1) return 0;
return parseInt(str.slice(0, -1), 10) || 0;
});
};
// Limpar / Zerar
const handleLimpar = () => {
smartAudio.playClick();
setQtdApontar(0);
};
// Definir Todas
const definirTodas = () => {
smartAudio.playClick();
setQtdApontar(saldoPendente > 0 ? saldoPendente : 1);
};
const zerar = () => {
smartAudio.playClick();
setQtdApontar(1);
// Digitação direta no input pelo teclado físico ou celular
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const val = e.target.value.replace(/\D/g, '');
const num = parseInt(val, 10) || 0;
if (saldoPendente > 0 && num > saldoPendente) {
setQtdApontar(saldoPendente);
smartAudio.playAlert();
toast.warning(`Máximo permitido: ${saldoPendente} unidades`);
} else {
setQtdApontar(num);
}
};
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-3.5 max-w-md mx-auto w-full pb-6">
{/* Resumo da Peça no Topo */}
<div className="p-4 rounded-2xl bg-slate-800/90 border border-slate-700 mb-4 shadow-sm">
<div className="p-3.5 rounded-2xl bg-slate-800/90 border border-slate-700 mb-3 shadow-sm">
<div className="flex items-center justify-between">
<span className="text-2xl font-black text-amber-400">
<span className="text-xl font-black text-amber-400">
{pecaSelecionada.marca}
</span>
<span className="text-xs font-black px-2.5 py-1 rounded-lg bg-amber-500/20 text-amber-300 border border-amber-500/30 uppercase">
<span className="text-xs font-black px-2.5 py-0.5 rounded-lg bg-amber-500/20 text-amber-300 border border-amber-500/30 uppercase">
{processoSelecionado.nome}
</span>
</div>
<div className="text-sm font-semibold text-slate-200 mt-1">
{pecaSelecionada.descricao}
<div className="text-xs font-semibold text-slate-200 mt-0.5 truncate">
{pecaSelecionada.descricao || 'Peça Estrutural'}
</div>
<div className="flex items-center justify-between text-xs text-slate-400 mt-2 pt-2 border-t border-slate-700/80">
<span>Total da Peça: {pecaSelecionada.quantidade} un</span>
@@ -608,78 +669,134 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
</div>
</div>
{/* Display da Quantidade Selecionada */}
<div className="my-auto py-3 text-center">
<span className="text-xs font-black uppercase text-slate-400 tracking-wider">
Quantidade a Apontar Agora
{/* Visor / Input Central da Quantidade com botões finos -1 e +1 */}
<div className="mb-2 text-center">
<span className="text-[11px] font-black uppercase text-slate-400 tracking-wider">
Digite ou Escolha a Quantidade a Apontar
</span>
<div className="flex items-center justify-center gap-4 mt-2">
<div className="min-w-[120px] h-20 px-6 rounded-3xl bg-slate-900 border-2 border-amber-500 flex items-center justify-center shadow-inner">
<span className="text-4xl sm:text-5xl font-black text-amber-400 tracking-tight">
{qtdApontar}
</span>
</div>
<div className="flex items-center justify-center gap-2 mt-1.5">
<button
type="button"
onClick={zerar}
className="h-14 px-4 rounded-2xl bg-slate-800 hover:bg-slate-700 active:bg-slate-650 border border-slate-700 text-slate-300 text-xs font-bold flex items-center gap-1.5 active:scale-95"
onClick={() => somarQtd(-1)}
disabled={qtdApontar <= 1}
className="h-16 w-14 rounded-2xl bg-slate-800 hover:bg-slate-750 active:bg-slate-700 border-2 border-slate-700 disabled:opacity-40 text-white flex items-center justify-center font-black active:scale-95 shadow-sm"
title="Diminuir 1"
>
<RotateCcw className="h-4 w-4" />
<span>Zerar</span>
<Minus className="h-6 w-6" />
</button>
{/* Input Numérico Editável Direto (Toque para digitar ou use o teclado da tela) */}
<div className="flex-1 max-w-[180px] h-16 rounded-2xl bg-slate-900 border-2 border-amber-500 flex items-center justify-center shadow-inner relative">
<input
type="text"
inputMode="numeric"
pattern="[0-9]*"
value={qtdApontar === 0 ? '' : qtdApontar}
placeholder="0"
onChange={handleInputChange}
className="w-full text-center bg-transparent border-none outline-none text-3xl sm:text-4xl font-black text-amber-400 tracking-tight placeholder:text-slate-700"
/>
</div>
<button
type="button"
onClick={() => somarQtd(1)}
disabled={saldoPendente > 0 && qtdApontar >= saldoPendente}
className="h-16 w-14 rounded-2xl bg-slate-800 hover:bg-slate-750 active:bg-slate-700 border-2 border-slate-700 disabled:opacity-40 text-white flex items-center justify-center font-black active:scale-95 shadow-sm"
title="Aumentar 1"
>
<Plus className="h-6 w-6" />
</button>
</div>
</div>
{/* Teclado de Ações Rápidas (Botões Gigantes) */}
<div className="grid grid-cols-4 gap-2.5 mb-4">
<button
type="button"
onClick={() => somarQtd(1)}
className="h-16 rounded-2xl bg-slate-800 hover:bg-slate-750 active:bg-slate-700 border-2 border-slate-700 text-white text-xl font-black active:scale-95 shadow-sm"
>
+1
</button>
<button
type="button"
onClick={() => somarQtd(2)}
className="h-16 rounded-2xl bg-slate-800 hover:bg-slate-750 active:bg-slate-700 border-2 border-slate-700 text-white text-xl font-black active:scale-95 shadow-sm"
>
+2
</button>
{/* Pílulas de Atalhos Rápidos (+5, +10, +25, TODAS) */}
<div className="grid grid-cols-4 gap-1.5 mb-3">
<button
type="button"
onClick={() => somarQtd(5)}
className="h-16 rounded-2xl bg-slate-800 hover:bg-slate-750 active:bg-slate-700 border-2 border-slate-700 text-white text-xl font-black active:scale-95 shadow-sm"
className="h-11 rounded-xl bg-slate-800 hover:bg-slate-750 active:bg-slate-700 border border-slate-700 text-slate-200 text-xs font-bold active:scale-95"
>
+5
</button>
<button
type="button"
onClick={definirTodas}
className="h-16 rounded-2xl bg-amber-500/20 hover:bg-amber-500/30 active:bg-amber-500/40 border-2 border-amber-500/60 text-amber-400 text-sm font-black active:scale-95 flex flex-col items-center justify-center leading-tight"
onClick={() => somarQtd(10)}
className="h-11 rounded-xl bg-slate-800 hover:bg-slate-750 active:bg-slate-700 border border-slate-700 text-slate-200 text-xs font-bold active:scale-95"
>
<span>TODAS</span>
<span className="text-[11px] opacity-80">({saldoPendente})</span>
+10
</button>
<button
type="button"
onClick={() => somarQtd(25)}
className="h-11 rounded-xl bg-slate-800 hover:bg-slate-750 active:bg-slate-700 border border-slate-700 text-slate-200 text-xs font-bold active:scale-95"
>
+25
</button>
<button
type="button"
onClick={definirTodas}
className="h-11 rounded-xl bg-amber-500/20 hover:bg-amber-500/30 border border-amber-500/50 text-amber-300 text-xs font-black active:scale-95"
>
TODAS ({saldoPendente})
</button>
</div>
{/* Teclado Numérico Touch de Chão de Fábrica (0 a 9, C, ⌫) */}
<div className="grid grid-cols-3 gap-2 mb-3 bg-slate-900/80 p-2.5 rounded-2xl border border-slate-800 shadow-inner">
{[1, 2, 3, 4, 5, 6, 7, 8, 9].map((num) => (
<button
key={num}
type="button"
onClick={() => handleDigitoTeclado(num)}
className="h-13 rounded-xl bg-slate-800 hover:bg-slate-750 active:bg-slate-700 border border-slate-700/80 text-white text-xl font-black active:scale-95 shadow-sm transition-all"
>
{num}
</button>
))}
<button
type="button"
onClick={handleLimpar}
className="h-13 rounded-xl bg-red-950/40 hover:bg-red-900/60 active:bg-red-900 border border-red-800/50 text-red-300 text-sm font-black active:scale-95 uppercase tracking-wider"
title="Limpar valor"
>
Limpar
</button>
<button
type="button"
onClick={() => handleDigitoTeclado(0)}
className="h-13 rounded-xl bg-slate-800 hover:bg-slate-750 active:bg-slate-700 border border-slate-700/80 text-white text-xl font-black active:scale-95 shadow-sm"
>
0
</button>
<button
type="button"
onClick={handleBackspace}
className="h-13 rounded-xl bg-slate-800 hover:bg-slate-750 active:bg-slate-700 border border-slate-700/80 text-amber-400 flex items-center justify-center active:scale-95"
title="Apagar último dígito"
>
<Delete className="h-5 w-5" />
</button>
</div>
{/* Botão Gigante de Confirmação com Feedback Imediato */}
<div className="space-y-2.5">
<div className="space-y-2">
<Button
type="button"
disabled={salvando || qtdApontar <= 0}
onClick={handleConfirmarApontamento}
className="w-full h-16 bg-emerald-600 hover:bg-emerald-500 active:bg-emerald-700 text-white text-base sm:text-lg font-black rounded-2xl shadow-lg shadow-emerald-950/40 uppercase tracking-wide active:scale-98 gap-2"
className="w-full h-15 bg-emerald-600 hover:bg-emerald-500 active:bg-emerald-700 text-white text-base font-black rounded-2xl shadow-lg shadow-emerald-950/40 uppercase tracking-wide active:scale-98 gap-2"
>
{salvando ? (
<>
<div className="h-6 w-6 animate-spin rounded-full border-3 border-white border-t-transparent" />
<span>Registrando...</span>
<div className="h-5 w-5 animate-spin rounded-full border-2 border-white border-t-transparent" />
<span>Gravando...</span>
</>
) : (
<>
<Check className="h-6 w-6" />
<span>CONFIRMAR APONTAMENTO</span>
<Check className="h-5 w-5" />
<span>CONFIRMAR {qtdApontar > 0 ? `(${qtdApontar} UNIDADES)` : ''}</span>
</>
)}
</Button>
@@ -692,7 +809,7 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
smartAudio.playClick();
setEtapaAtual('pecas');
}}
className="w-full h-13 bg-slate-800 border-slate-700 text-slate-300 text-sm font-bold rounded-2xl active:scale-98"
className="w-full h-11 bg-slate-800 border-slate-700 text-slate-300 text-xs font-bold rounded-xl active:scale-98"
>
Cancelar e Voltar à Lista
</Button>