feat: adicionar teclado touch e input direto para quantidade parcial no Modo Smart
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user