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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user