feat: enhance input validation and user experience in 'Almoxarifado' forms by adding oninput handlers for numeric fields, ensuring non-negative values and improving data integrity

This commit is contained in:
2025-12-22 00:18:30 -03:00
parent ae4f8fc6b3
commit ec3b5dc7ea
4 changed files with 190 additions and 35 deletions

View File

@@ -282,8 +282,18 @@
type="number"
class="input input-bordered"
min="0"
step="1"
bind:value={estoqueMinimo}
required
oninput={(e) => {
const value = parseInt((e.target as HTMLInputElement).value);
if (isNaN(value) || value < 0) {
(e.target as HTMLInputElement).value = '';
estoqueMinimo = 0;
} else {
estoqueMinimo = value;
}
}}
/>
</div>
@@ -296,7 +306,17 @@
type="number"
class="input input-bordered"
min="0"
step="1"
bind:value={estoqueMaximo}
oninput={(e) => {
const value = parseInt((e.target as HTMLInputElement).value);
if (isNaN(value) || value < 0) {
(e.target as HTMLInputElement).value = '';
estoqueMaximo = 0;
} else {
estoqueMaximo = value;
}
}}
/>
<label class="label">
<span class="label-text-alt">Opcional</span>

View File

@@ -633,8 +633,18 @@
type="number"
class="input input-bordered w-full focus:input-primary transition-colors h-12"
min="0"
step="1"
bind:value={estoqueMinimo}
required
oninput={(e) => {
const value = parseInt((e.target as HTMLInputElement).value);
if (isNaN(value) || value < 0) {
(e.target as HTMLInputElement).value = '';
estoqueMinimo = 0;
} else {
estoqueMinimo = value;
}
}}
/>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Quantidade mínima para alerta</span>
@@ -650,7 +660,17 @@
type="number"
class="input input-bordered w-full focus:input-primary transition-colors h-12"
min="0"
step="1"
bind:value={estoqueMaximo}
oninput={(e) => {
const value = parseInt((e.target as HTMLInputElement).value);
if (isNaN(value) || value < 0) {
(e.target as HTMLInputElement).value = '';
estoqueMaximo = 0;
} else {
estoqueMaximo = value;
}
}}
/>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Opcional - Capacidade máxima</span>
@@ -666,7 +686,17 @@
type="number"
class="input input-bordered w-full focus:input-primary transition-colors h-12"
min="0"
step="1"
bind:value={estoqueAtual}
oninput={(e) => {
const value = parseInt((e.target as HTMLInputElement).value);
if (isNaN(value) || value < 0) {
(e.target as HTMLInputElement).value = '';
estoqueAtual = 0;
} else {
estoqueAtual = value;
}
}}
/>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Quantidade inicial em estoque</span>

View File

@@ -278,10 +278,19 @@
<input
type="number"
class="input input-bordered w-full focus:input-primary transition-colors h-12"
min="0.01"
step="0.01"
min="1"
step="1"
bind:value={entradaQuantidade}
required
oninput={(e) => {
const value = parseInt((e.target as HTMLInputElement).value);
if (isNaN(value) || value < 1) {
(e.target as HTMLInputElement).value = '';
entradaQuantidade = 0;
} else {
entradaQuantidade = value;
}
}}
/>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Quantidade a ser adicionada ao estoque</span>
@@ -391,10 +400,19 @@
<input
type="number"
class="input input-bordered w-full focus:input-primary transition-colors h-12"
min="0.01"
step="0.01"
min="1"
step="1"
bind:value={saidaQuantidade}
required
oninput={(e) => {
const value = parseInt((e.target as HTMLInputElement).value);
if (isNaN(value) || value < 1) {
(e.target as HTMLInputElement).value = '';
saidaQuantidade = 0;
} else {
saidaQuantidade = value;
}
}}
/>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Quantidade a ser retirada do estoque</span>
@@ -532,8 +550,18 @@
type="number"
class="input input-bordered w-full focus:input-primary transition-colors h-12"
min="0"
step="1"
bind:value={ajusteQuantidadeNova}
required
oninput={(e) => {
const value = parseInt((e.target as HTMLInputElement).value);
if (isNaN(value) || value < 0) {
(e.target as HTMLInputElement).value = '';
ajusteQuantidadeNova = 0;
} else {
ajusteQuantidadeNova = value;
}
}}
/>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Quantidade correta após o ajuste</span>

View File

@@ -3,13 +3,16 @@
import type { Doc, Id } from '@sgse-app/backend/convex/_generated/dataModel';
import { useConvexClient, useQuery } from 'convex-svelte';
import { resolve } from '$app/paths';
import { ClipboardList, Plus, CheckCircle, XCircle, Package, Filter, User, Building2, Calendar, FileText } from 'lucide-svelte';
import { ClipboardList, Plus, CheckCircle, XCircle, Package, Filter, User, Building2, Calendar, FileText, Search, Hash } from 'lucide-svelte';
import ErrorModal from '$lib/components/ErrorModal.svelte';
const client = useConvexClient();
let requisicoes = $state<Array<Doc<'requisicoesMaterial'>>>([]);
let filtroStatus = $state<string>('');
let filtroSolicitante = $state<Id<'funcionarios'> | ''>('');
let filtroNumero = $state<string>('');
let filtroSetor = $state<Id<'setores'> | ''>('');
let showModalNova = $state(false);
// Estados do formulário de nova requisição
@@ -93,18 +96,13 @@
$effect(() => {
if (requisicoesQuery.data) {
requisicoes = requisicoesQuery.data;
if (filtroStatus) {
requisicoes = requisicoes.filter((r) => r.status === filtroStatus);
}
}
});
$effect(() => {
if (requisicoesQuery.data) {
requisicoes = filtroStatus
? requisicoesQuery.data.filter((r) => r.status === filtroStatus)
: requisicoesQuery.data;
requisicoes = requisicoesQuery.data.filter((r) => {
const okStatus = !filtroStatus || r.status === filtroStatus;
const okSolicitante = !filtroSolicitante || r.solicitanteId === filtroSolicitante;
const okSetor = !filtroSetor || r.setorId === filtroSetor;
const okNumero = !filtroNumero || r.numero.toLowerCase().includes(filtroNumero.toLowerCase());
return okStatus && okSolicitante && okSetor && okNumero;
});
}
});
@@ -292,21 +290,91 @@
</div>
<h3 class="text-xl font-bold text-base-content">Filtros de Busca</h3>
</div>
<div class="form-control max-w-md">
<label class="label pb-2">
<span class="label-text font-semibold">Filtrar por Status</span>
</label>
<select class="select select-bordered w-full focus:select-primary transition-colors h-12" bind:value={filtroStatus}>
<option value="">Todos os status</option>
<option value="pendente">Pendente</option>
<option value="aprovada">Aprovada</option>
<option value="atendida">Atendida</option>
<option value="rejeitada">Rejeitada</option>
<option value="cancelada">Cancelada</option>
</select>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Filtre as requisições por status</span>
</label>
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
<!-- Filtro: Número do Requerimento -->
<div class="form-control">
<label class="label pb-2">
<span class="label-text font-semibold flex items-center gap-2">
<Hash class="h-4 w-4" />
Número do Requerimento
</span>
</label>
<div class="relative">
<Search class="absolute left-3 top-1/2 h-5 w-5 -translate-y-1/2 text-base-content/40" />
<input
type="text"
placeholder="Ex: REQ-001"
class="input input-bordered w-full pl-10 h-12 focus:input-primary transition-colors"
bind:value={filtroNumero}
/>
</div>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Busque pelo número da requisição</span>
</label>
</div>
<!-- Filtro: Solicitante -->
<div class="form-control">
<label class="label pb-2">
<span class="label-text font-semibold flex items-center gap-2">
<User class="h-4 w-4" />
Solicitante
</span>
</label>
<select class="select select-bordered w-full focus:select-primary transition-colors h-12" bind:value={filtroSolicitante}>
<option value="">Todos os solicitantes</option>
{#if funcionariosQuery.data}
{#each funcionariosQuery.data as funcionario}
<option value={funcionario._id}>{funcionario.nome}</option>
{/each}
{/if}
</select>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Filtre por funcionário solicitante</span>
</label>
</div>
<!-- Filtro: Setor -->
<div class="form-control">
<label class="label pb-2">
<span class="label-text font-semibold flex items-center gap-2">
<Building2 class="h-4 w-4" />
Setor
</span>
</label>
<select class="select select-bordered w-full focus:select-primary transition-colors h-12" bind:value={filtroSetor}>
<option value="">Todos os setores</option>
{#if setoresQuery.data}
{#each setoresQuery.data as setor}
<option value={setor._id}>{setor.nome}</option>
{/each}
{/if}
</select>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Filtre por setor</span>
</label>
</div>
<!-- Filtro: Status -->
<div class="form-control">
<label class="label pb-2">
<span class="label-text font-semibold flex items-center gap-2">
<FileText class="h-4 w-4" />
Status
</span>
</label>
<select class="select select-bordered w-full focus:select-primary transition-colors h-12" bind:value={filtroStatus}>
<option value="">Todos os status</option>
<option value="pendente">Pendente</option>
<option value="aprovada">Aprovada</option>
<option value="atendida">Atendida</option>
<option value="rejeitada">Rejeitada</option>
<option value="cancelada">Cancelada</option>
</select>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Filtre as requisições por status</span>
</label>
</div>
</div>
</div>
</div>
@@ -508,10 +576,19 @@
<input
type="number"
class="input input-bordered w-full focus:input-primary transition-colors h-12"
min="0.01"
step="0.01"
min="1"
step="1"
bind:value={novoItemQuantidade}
placeholder="0.00"
placeholder="0"
oninput={(e) => {
const value = parseInt((e.target as HTMLInputElement).value);
if (isNaN(value) || value < 1) {
(e.target as HTMLInputElement).value = '';
novoItemQuantidade = 0;
} else {
novoItemQuantidade = value;
}
}}
/>
</div>