feat: adicionar teclado touch e input direto para quantidade parcial no Modo Smart
This commit is contained in:
@@ -9,10 +9,12 @@ import {
|
|||||||
Check,
|
Check,
|
||||||
Layers,
|
Layers,
|
||||||
Plus,
|
Plus,
|
||||||
|
Minus,
|
||||||
RotateCcw,
|
RotateCcw,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Filter
|
Filter,
|
||||||
|
Delete
|
||||||
} 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';
|
||||||
@@ -523,20 +525,36 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Botão Gigante de Ação de Apontar */}
|
{/* Botões de Ação no Card */}
|
||||||
<div className="mt-3">
|
<div className="mt-3 grid grid-cols-1 sm:grid-cols-3 gap-2">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={concluida}
|
disabled={concluida}
|
||||||
onClick={() => handleIniciarApontamentoPeca(item, item.saldo)}
|
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
|
concluida
|
||||||
? 'bg-slate-800 text-slate-500 border border-slate-700'
|
? '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'
|
: '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>
|
</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>
|
||||||
</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) {
|
if (etapaAtual === 'quantidade' && pecaSelecionada && processoSelecionado) {
|
||||||
const key = `${pecaSelecionada.id}_${processoSelecionado.id}`;
|
const key = `${pecaSelecionada.id}_${processoSelecionado.id}`;
|
||||||
const jaProduzido = mapaProducao.get(key) || 0;
|
const jaProduzido = mapaProducao.get(key) || 0;
|
||||||
const saldoPendente = Math.max(0, pecaSelecionada.quantidade - jaProduzido);
|
const saldoPendente = Math.max(0, pecaSelecionada.quantidade - jaProduzido);
|
||||||
|
|
||||||
|
// Ajuste por incremento
|
||||||
const somarQtd = (valor: number) => {
|
const somarQtd = (valor: number) => {
|
||||||
smartAudio.playClick();
|
smartAudio.playClick();
|
||||||
setQtdApontar((prev) => {
|
setQtdApontar((prev) => {
|
||||||
const nova = prev + valor;
|
const nova = Math.max(1, prev + valor);
|
||||||
return saldoPendente > 0 ? Math.min(saldoPendente, nova) : nova;
|
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 = () => {
|
const definirTodas = () => {
|
||||||
smartAudio.playClick();
|
smartAudio.playClick();
|
||||||
setQtdApontar(saldoPendente > 0 ? saldoPendente : 1);
|
setQtdApontar(saldoPendente > 0 ? saldoPendente : 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const zerar = () => {
|
// Digitação direta no input pelo teclado físico ou celular
|
||||||
smartAudio.playClick();
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setQtdApontar(1);
|
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 (
|
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 */}
|
{/* 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">
|
<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}
|
{pecaSelecionada.marca}
|
||||||
</span>
|
</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}
|
{processoSelecionado.nome}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm font-semibold text-slate-200 mt-1">
|
<div className="text-xs font-semibold text-slate-200 mt-0.5 truncate">
|
||||||
{pecaSelecionada.descricao}
|
{pecaSelecionada.descricao || 'Peça Estrutural'}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between text-xs text-slate-400 mt-2 pt-2 border-t border-slate-700/80">
|
<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>
|
<span>Total da Peça: {pecaSelecionada.quantidade} un</span>
|
||||||
@@ -608,78 +669,134 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Display da Quantidade Selecionada */}
|
{/* Visor / Input Central da Quantidade com botões finos -1 e +1 */}
|
||||||
<div className="my-auto py-3 text-center">
|
<div className="mb-2 text-center">
|
||||||
<span className="text-xs font-black uppercase text-slate-400 tracking-wider">
|
<span className="text-[11px] font-black uppercase text-slate-400 tracking-wider">
|
||||||
Quantidade a Apontar Agora
|
Digite ou Escolha a Quantidade a Apontar
|
||||||
</span>
|
</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">
|
<div className="flex items-center justify-center gap-2 mt-1.5">
|
||||||
<span className="text-4xl sm:text-5xl font-black text-amber-400 tracking-tight">
|
|
||||||
{qtdApontar}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={zerar}
|
onClick={() => somarQtd(-1)}
|
||||||
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"
|
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" />
|
<Minus className="h-6 w-6" />
|
||||||
<span>Zerar</span>
|
</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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Teclado de Ações Rápidas (Botões Gigantes) */}
|
{/* Pílulas de Atalhos Rápidos (+5, +10, +25, TODAS) */}
|
||||||
<div className="grid grid-cols-4 gap-2.5 mb-4">
|
<div className="grid grid-cols-4 gap-1.5 mb-3">
|
||||||
<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>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => somarQtd(5)}
|
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
|
+5
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={definirTodas}
|
onClick={() => somarQtd(10)}
|
||||||
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"
|
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>
|
+10
|
||||||
<span className="text-[11px] opacity-80">({saldoPendente})</span>
|
</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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Botão Gigante de Confirmação com Feedback Imediato */}
|
{/* Botão Gigante de Confirmação com Feedback Imediato */}
|
||||||
<div className="space-y-2.5">
|
<div className="space-y-2">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={salvando || qtdApontar <= 0}
|
disabled={salvando || qtdApontar <= 0}
|
||||||
onClick={handleConfirmarApontamento}
|
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 ? (
|
{salvando ? (
|
||||||
<>
|
<>
|
||||||
<div className="h-6 w-6 animate-spin rounded-full border-3 border-white border-t-transparent" />
|
<div className="h-5 w-5 animate-spin rounded-full border-2 border-white border-t-transparent" />
|
||||||
<span>Registrando...</span>
|
<span>Gravando...</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Check className="h-6 w-6" />
|
<Check className="h-5 w-5" />
|
||||||
<span>CONFIRMAR APONTAMENTO</span>
|
<span>CONFIRMAR {qtdApontar > 0 ? `(${qtdApontar} UNIDADES)` : ''}</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -692,7 +809,7 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
|
|||||||
smartAudio.playClick();
|
smartAudio.playClick();
|
||||||
setEtapaAtual('pecas');
|
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
|
Cancelar e Voltar à Lista
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user