fix some errors

This commit is contained in:
2026-01-12 14:52:46 -03:00
parent 417394ddbe
commit ec6ba1d95f
3 changed files with 771 additions and 508 deletions

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { api } from '@sgse-app/backend/convex/_generated/api';
import type { Doc, Id } from '@sgse-app/backend/convex/_generated/dataModel';
import type { Doc } from '@sgse-app/backend/convex/_generated/dataModel';
import { useConvexClient, useQuery } from 'convex-svelte';
import { goto } from '$app/navigation';
import { resolve } from '$app/paths';
@@ -14,8 +14,7 @@
Trash2,
Info,
X,
Filter,
Barcode
Filter
} from 'lucide-svelte';
import BarcodeScanner from '$lib/components/almoxarifado/BarcodeScanner.svelte';
@@ -36,7 +35,20 @@
return [];
}
});
let filtered = $state<Array<Doc<'materiais'>>>([]);
let filtered = $derived.by(() => {
const busca = filtroBusca.toLowerCase();
return materiais.filter((m) => {
const okBusca =
!busca ||
m.codigo.toLowerCase().includes(busca) ||
m.nome.toLowerCase().includes(busca) ||
(m.codigoBarras && m.codigoBarras.toLowerCase().includes(busca));
const okCategoria = !filtroCategoria || m.categoria === filtroCategoria;
const okAtivo = filtroAtivo === '' || m.ativo === filtroAtivo;
const okEstoqueBaixo = !filtroEstoqueBaixo || m.estoqueAtual <= m.estoqueMinimo;
return okBusca && okCategoria && okAtivo && okEstoqueBaixo;
});
});
let filtroBusca = $state('');
let filtroCategoria = $state('');
let filtroAtivo = $state<boolean | ''>('');
@@ -59,21 +71,6 @@
Array.from(new Set(materiais.map((m) => m.categoria).filter(Boolean))).sort()
);
function applyFilters() {
const busca = filtroBusca.toLowerCase();
filtered = materiais.filter((m) => {
const okBusca =
!busca ||
m.codigo.toLowerCase().includes(busca) ||
m.nome.toLowerCase().includes(busca) ||
(m.codigoBarras && m.codigoBarras.toLowerCase().includes(busca));
const okCategoria = !filtroCategoria || m.categoria === filtroCategoria;
const okAtivo = filtroAtivo === '' || m.ativo === filtroAtivo;
const okEstoqueBaixo = !filtroEstoqueBaixo || m.estoqueAtual <= m.estoqueMinimo;
return okBusca && okCategoria && okAtivo && okEstoqueBaixo;
});
}
async function buscarPorCodigoBarras(codigo: string) {
if (!codigo.trim() || codigo.trim().length < 8) {
return;
@@ -102,14 +99,6 @@
}
}
// Aplicar filtros sempre que materiais ou filtros mudarem
$effect(() => {
// Acessar materiais para criar dependência reativa
const _ = materiais;
// Aplicar filtros
applyFilters();
});
function navCadastro() {
goto(resolve('/almoxarifado/materiais/cadastro'));
}
@@ -179,9 +168,6 @@
try {
excluindo = true;
// Remover item da lista localmente imediatamente (otimistic update)
filtered = filtered.filter((m) => m._id !== materialId);
await client.mutation(api.almoxarifado.deletarMaterial, {
id: materialId
});
@@ -197,9 +183,6 @@
notice = null;
}, 5000);
} catch (error: unknown) {
// Se houver erro, recarregar a lista para garantir consistência
applyFilters();
const message = error instanceof Error ? error.message : 'Erro ao excluir material';
// Determinar tipo de erro e criar mensagem mais clara
@@ -287,13 +270,13 @@
<div class="flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
<div class="flex items-center gap-4">
<div
class="from-primary/20 via-primary/10 to-primary/5 border-primary/20 rounded-2xl border bg-gradient-to-br p-4 shadow-lg"
class="from-primary/20 via-primary/10 to-primary/5 border-primary/20 rounded-2xl border bg-linear-to-br p-4 shadow-lg"
>
<Package class="text-primary h-10 w-10" strokeWidth={2.5} />
</div>
<div class="flex-1">
<h1
class="from-primary to-primary/70 bg-gradient-to-r bg-clip-text text-4xl font-bold tracking-tight text-transparent"
class="from-primary to-primary/70 bg-linear-to-r bg-clip-text text-4xl font-bold tracking-tight text-transparent"
>
Materiais
</h1>
@@ -337,7 +320,7 @@
</div>
<div class="grid grid-cols-1 gap-6 md:grid-cols-4">
<div class="form-control">
<label class="label pb-2">
<label class="label pb-2" for="busca-material">
<span class="label-text flex items-center gap-2 font-semibold">
<Search class="h-4 w-4" />
Buscar
@@ -346,6 +329,7 @@
<div class="relative">
<Search class="text-base-content/40 absolute top-1/2 left-3 h-5 w-5 -translate-y-1/2" />
<input
id="busca-material"
type="text"
placeholder="Código, nome ou código de barras..."
class="input input-bordered focus:input-primary h-12 w-full pl-10 transition-colors {buscandoPorCodigoBarras
@@ -367,25 +351,27 @@
</div>
<div class="form-control">
<label class="label pb-2">
<label class="label pb-2" for="filtro-categoria">
<span class="label-text font-semibold">Categoria</span>
</label>
<select
id="filtro-categoria"
class="select select-bordered focus:select-primary h-12 w-full transition-colors"
bind:value={filtroCategoria}
>
<option value="">Todas as categorias</option>
{#each categorias as cat}
{#each categorias as cat (cat)}
<option value={cat}>{cat}</option>
{/each}
</select>
</div>
<div class="form-control">
<label class="label pb-2">
<label class="label pb-2" for="filtro-status">
<span class="label-text font-semibold">Status</span>
</label>
<select
id="filtro-status"
class="select select-bordered focus:select-primary h-12 w-full transition-colors"
bind:value={filtroAtivo}
>
@@ -396,13 +382,14 @@
</div>
<div class="form-control">
<label class="label pb-2">
<label class="label pb-2" for="filtro-estoque-baixo">
<span class="label-text font-semibold">Filtros Adicionais</span>
</label>
<label
class="label border-base-300 hover:bg-base-200 cursor-pointer justify-start gap-3 rounded-lg border p-3 transition-colors"
>
<input
id="filtro-estoque-baixo"
type="checkbox"
class="checkbox checkbox-primary"
bind:checked={filtroEstoqueBaixo}
@@ -453,8 +440,8 @@
</td>
</tr>
{:else}
{#each filtered as material}
<tr class="hover:bg-base-200/50 transition-colors" key={material._id}>
{#each filtered as material (material._id)}
<tr class="hover:bg-base-200/50 transition-colors">
<td>
<div class="text-primary font-mono font-bold">{material.codigo}</div>
</td>
@@ -499,7 +486,8 @@
<button
class="btn btn-sm btn-ghost hover:btn-primary transition-all"
title="Visualizar detalhes"
onclick={() => goto(resolve('/almoxarifado/materiais/' + material._id))}
onclick={() =>
goto(resolve('/almoxarifado/materiais/' + material._id) as any)}
>
<Eye class="h-4 w-4" />
</button>
@@ -507,7 +495,9 @@
class="btn btn-sm btn-ghost hover:btn-info transition-all"
title="Editar material"
onclick={() =>
goto(resolve('/almoxarifado/materiais/' + material._id + '/editar'))}
goto(
resolve('/almoxarifado/materiais/' + material._id + '/editar') as any
)}
>
<Edit class="h-4 w-4" />
</button>

View File

@@ -1,9 +1,22 @@
<script lang="ts">
import { api } from '@sgse-app/backend/convex/_generated/api';
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
import type { FunctionReturnType } from 'convex/server';
import { useConvexClient, useQuery } from 'convex-svelte';
import { resolve } from '$app/paths';
import { ArrowLeftRight, ArrowDown, ArrowUp, Settings, History, Package, FileText, User, Building2, AlertCircle } from 'lucide-svelte';
import {
ArrowLeftRight,
ArrowDown,
ArrowUp,
Settings,
History,
Package,
FileText,
User,
Building2,
AlertCircle
} from 'lucide-svelte';
import { SvelteMap } from 'svelte/reactivity';
import BarcodeScanner from '$lib/components/almoxarifado/BarcodeScanner.svelte';
const client = useConvexClient();
@@ -44,20 +57,26 @@
const setoresQuery = useQuery(api.setores.list, {});
const movimentacoesQuery = useQuery(api.almoxarifado.listarMovimentacoesComHistorico, {});
type FuncionariosQueryResult = FunctionReturnType<typeof api.funcionarios.getAll>;
type Funcionario = FuncionariosQueryResult[number];
// Criar mapa de funcionários para lookup eficiente
const funcionariosMap = $derived.by(() => {
if (!funcionariosQuery.data) return new Map();
const map = new Map();
const map = new SvelteMap<Id<'funcionarios'>, Funcionario>();
if (!funcionariosQuery.data) return map;
for (const funcionario of funcionariosQuery.data) {
map.set(funcionario._id, funcionario);
}
return map;
});
type MateriaisQueryResult = FunctionReturnType<typeof api.almoxarifado.listarMateriais>;
type Material = MateriaisQueryResult[number];
// Criar mapa de materiais para lookup eficiente
const materiaisMap = $derived.by(() => {
if (!materiaisQuery.data) return new Map();
const map = new Map();
const map = new SvelteMap<Id<'materiais'>, Material>();
if (!materiaisQuery.data) return map;
for (const material of materiaisQuery.data) {
map.set(material._id, material);
}
@@ -170,9 +189,7 @@
materialId: saidaMaterialId as Id<'materiais'>,
quantidade: saidaQuantidade,
motivo: saidaMotivo.trim(),
funcionarioId: saidaFuncionarioId
? (saidaFuncionarioId as Id<'funcionarios'>)
: undefined,
funcionarioId: saidaFuncionarioId ? (saidaFuncionarioId as Id<'funcionarios'>) : undefined,
setorId: saidaSetorId ? (saidaSetorId as Id<'setores'>) : undefined,
observacoes: saidaObservacoes.trim() || undefined
});
@@ -235,9 +252,7 @@
<div class="breadcrumbs mb-6 text-sm">
<ul>
<li>
<a href={resolve('/almoxarifado')} class="text-primary hover:underline"
>Almoxarifado</a
>
<a href={resolve('/almoxarifado')} class="text-primary hover:underline">Almoxarifado</a>
</li>
<li>Movimentações</li>
</ul>
@@ -246,14 +261,20 @@
<!-- Cabeçalho -->
<div class="mb-8">
<div class="flex items-center gap-4">
<div class="rounded-2xl bg-gradient-to-br from-info/20 via-info/10 to-info/5 p-4 shadow-lg border border-info/20">
<ArrowLeftRight class="h-10 w-10 text-info" strokeWidth={2.5} />
<div
class="from-info/20 via-info/10 to-info/5 border-info/20 rounded-2xl border bg-linear-to-br p-4 shadow-lg"
>
<ArrowLeftRight class="text-info h-10 w-10" strokeWidth={2.5} />
</div>
<div class="flex-1">
<h1 class="text-4xl font-bold tracking-tight bg-gradient-to-r from-info to-info/70 bg-clip-text text-transparent">
<h1
class="from-info to-info/70 bg-linear-to-r bg-clip-text text-4xl font-bold tracking-tight text-transparent"
>
Movimentações de Estoque
</h1>
<p class="text-base-content/70 text-lg mt-1">Registre entradas, saídas e ajustes de estoque do almoxarifado</p>
<p class="text-base-content/70 mt-1 text-lg">
Registre entradas, saídas e ajustes de estoque do almoxarifado
</p>
</div>
</div>
</div>
@@ -266,50 +287,55 @@
{/if}
<!-- Abas -->
<div class="tabs tabs-boxed mb-8 bg-base-200 shadow-lg rounded-xl p-1">
<div class="tabs tabs-boxed bg-base-200 mb-8 rounded-xl p-1 shadow-lg">
<button
class="tab {abaAtiva === 'entrada' ? 'tab-active' : ''} transition-all font-semibold"
class="tab {abaAtiva === 'entrada' ? 'tab-active' : ''} font-semibold transition-all"
onclick={() => (abaAtiva = 'entrada')}
>
<ArrowDown class="h-5 w-5 mr-2" />
<ArrowDown class="mr-2 h-5 w-5" />
Entrada
</button>
<button
class="tab {abaAtiva === 'saida' ? 'tab-active' : ''} transition-all font-semibold"
class="tab {abaAtiva === 'saida' ? 'tab-active' : ''} font-semibold transition-all"
onclick={() => (abaAtiva = 'saida')}
>
<ArrowUp class="h-5 w-5 mr-2" />
<ArrowUp class="mr-2 h-5 w-5" />
Saída
</button>
<button
class="tab {abaAtiva === 'ajuste' ? 'tab-active' : ''} transition-all font-semibold"
class="tab {abaAtiva === 'ajuste' ? 'tab-active' : ''} font-semibold transition-all"
onclick={() => (abaAtiva = 'ajuste')}
>
<Settings class="h-5 w-5 mr-2" />
<Settings class="mr-2 h-5 w-5" />
Ajuste
</button>
<button
class="tab {abaAtiva === 'historico' ? 'tab-active' : ''} transition-all font-semibold"
class="tab {abaAtiva === 'historico' ? 'tab-active' : ''} font-semibold transition-all"
onclick={() => (abaAtiva = 'historico')}
>
<History class="h-5 w-5 mr-2" />
<History class="mr-2 h-5 w-5" />
Histórico
</button>
</div>
<!-- Conteúdo das Abas -->
{#if abaAtiva === 'entrada'}
<div class="card bg-base-100 border border-base-300 shadow-2xl">
<div class="card bg-base-100 border-base-300 border shadow-2xl">
<div class="card-body p-8">
<div class="mb-8 flex items-center gap-3 border-b-2 border-success/20 pb-4">
<div class="rounded-lg bg-success/10 p-2.5">
<ArrowDown class="h-6 w-6 text-success" strokeWidth={2.5} />
<div class="border-success/20 mb-8 flex items-center gap-3 border-b-2 pb-4">
<div class="bg-success/10 rounded-lg p-2.5">
<ArrowDown class="text-success h-6 w-6" strokeWidth={2.5} />
</div>
<h2 class="text-2xl font-bold text-base-content">Registrar Entrada de Material</h2>
<h2 class="text-base-content text-2xl font-bold">Registrar Entrada de Material</h2>
</div>
<form onsubmit={(e) => { e.preventDefault(); registrarEntrada(); }}>
<form
onsubmit={(e) => {
e.preventDefault();
registrarEntrada();
}}
>
<!-- Leitor de Código de Barras -->
<div class="mb-6 rounded-xl border border-base-300 bg-base-200/50 p-4">
<div class="border-base-300 bg-base-200/50 mb-6 rounded-xl border p-4">
<BarcodeScanner
enabled={entradaScannerEnabled}
onScan={buscarMaterialPorCodigoBarrasEntrada}
@@ -320,8 +346,8 @@
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<!-- Material -->
<div class="form-control md:col-span-2">
<label class="label pb-2">
<span class="label-text font-semibold flex items-center gap-2">
<label for="entradaMaterialId" class="label pb-2">
<span class="label-text flex items-center gap-2 font-semibold">
<Package class="h-4 w-4" />
Material <span class="text-error">*</span>
{#if entradaBuscandoMaterial}
@@ -329,17 +355,24 @@
{/if}
</span>
</label>
<select class="select select-bordered w-full focus:select-primary transition-colors h-12 {entradaBuscandoMaterial ? 'input-info' : ''}" bind:value={entradaMaterialId} required>
<select
id="entradaMaterialId"
class="select select-bordered focus:select-primary h-12 w-full transition-colors {entradaBuscandoMaterial
? 'input-info'
: ''}"
bind:value={entradaMaterialId}
required
>
<option value="">Selecione um material</option>
{#if materiaisQuery.data}
{#each materiaisQuery.data as material}
{#each materiaisQuery.data as material (material._id)}
<option value={material._id}>
{material.codigo} - {material.nome} (Estoque: {material.estoqueAtual}{material.unidadeMedida})
</option>
{/each}
{/if}
</select>
<label class="label pt-1">
<div class="label pt-1">
<span class="label-text-alt text-base-content/60">
{#if entradaBuscandoMaterial}
<span class="text-info">Buscando material...</span>
@@ -347,17 +380,20 @@
Selecione manualmente ou use o leitor de código de barras acima
{/if}
</span>
</label>
</div>
</div>
<!-- Quantidade -->
<div class="form-control">
<label class="label pb-2">
<span class="label-text font-semibold">Quantidade <span class="text-error">*</span></span>
<label for="entradaQuantidade" class="label pb-2">
<span class="label-text font-semibold"
>Quantidade <span class="text-error">*</span></span
>
</label>
<input
id="entradaQuantidade"
type="number"
class="input input-bordered w-full focus:input-primary transition-colors h-12"
class="input input-bordered focus:input-primary h-12 w-full transition-colors"
min="1"
step="1"
bind:value={entradaQuantidade}
@@ -372,54 +408,65 @@
}
}}
/>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Quantidade a ser adicionada ao estoque</span>
</label>
<div class="label pt-1">
<span class="label-text-alt text-base-content/60"
>Quantidade a ser adicionada ao estoque</span
>
</div>
</div>
<!-- Documento -->
<div class="form-control">
<label class="label pb-2">
<span class="label-text font-semibold flex items-center gap-2">
<label for="entradaDocumento" class="label pb-2">
<span class="label-text flex items-center gap-2 font-semibold">
<FileText class="h-4 w-4" />
Documento
</span>
</label>
<input
id="entradaDocumento"
type="text"
class="input input-bordered w-full focus:input-primary transition-colors h-12"
class="input input-bordered focus:input-primary h-12 w-full transition-colors"
placeholder="Número da nota fiscal"
bind:value={entradaDocumento}
/>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">NF, nota fiscal ou documento relacionado</span>
</label>
<div class="label pt-1">
<span class="label-text-alt text-base-content/60"
>NF, nota fiscal ou documento relacionado</span
>
</div>
</div>
<!-- Motivo -->
<div class="form-control md:col-span-2">
<label class="label pb-2">
<span class="label-text font-semibold">Motivo <span class="text-error">*</span></span>
<label for="entradaMotivo" class="label pb-2">
<span class="label-text font-semibold"
>Motivo <span class="text-error">*</span></span
>
</label>
<input
id="entradaMotivo"
type="text"
class="input input-bordered w-full focus:input-primary transition-colors h-12"
class="input input-bordered focus:input-primary h-12 w-full transition-colors"
placeholder="Ex: Compra, Doação, Devolução"
bind:value={entradaMotivo}
required
/>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Razão da entrada do material no estoque</span>
</label>
<div class="label pt-1">
<span class="label-text-alt text-base-content/60"
>Razão da entrada do material no estoque</span
>
</div>
</div>
<!-- Observações -->
<div class="form-control md:col-span-2">
<label class="label pb-2">
<label for="entradaObservacoes" class="label pb-2">
<span class="label-text font-semibold">Observações</span>
</label>
<textarea
class="textarea textarea-bordered w-full focus:textarea-primary transition-colors min-h-[100px]"
id="entradaObservacoes"
class="textarea textarea-bordered focus:textarea-primary min-h-[100px] w-full transition-colors"
placeholder="Observações adicionais (opcional)"
bind:value={entradaObservacoes}
rows="3"
@@ -427,8 +474,12 @@
</div>
</div>
<div class="card-actions mt-8 justify-end gap-4 border-t-2 border-base-300 pt-6">
<button type="submit" class="btn btn-success btn-lg min-w-[180px] shadow-lg hover:shadow-xl transition-all" disabled={entradaLoading}>
<div class="card-actions border-base-300 mt-8 justify-end gap-4 border-t-2 pt-6">
<button
type="submit"
class="btn btn-success btn-lg min-w-[180px] shadow-lg transition-all hover:shadow-xl"
disabled={entradaLoading}
>
{#if entradaLoading}
<span class="loading loading-spinner loading-sm"></span>
Registrando...
@@ -442,17 +493,22 @@
</div>
</div>
{:else if abaAtiva === 'saida'}
<div class="card bg-base-100 border border-base-300 shadow-2xl">
<div class="card bg-base-100 border-base-300 border shadow-2xl">
<div class="card-body p-8">
<div class="mb-8 flex items-center gap-3 border-b-2 border-error/20 pb-4">
<div class="rounded-lg bg-error/10 p-2.5">
<ArrowUp class="h-6 w-6 text-error" strokeWidth={2.5} />
<div class="border-error/20 mb-8 flex items-center gap-3 border-b-2 pb-4">
<div class="bg-error/10 rounded-lg p-2.5">
<ArrowUp class="text-error h-6 w-6" strokeWidth={2.5} />
</div>
<h2 class="text-2xl font-bold text-base-content">Registrar Saída de Material</h2>
<h2 class="text-base-content text-2xl font-bold">Registrar Saída de Material</h2>
</div>
<form onsubmit={(e) => { e.preventDefault(); registrarSaida(); }}>
<form
onsubmit={(e) => {
e.preventDefault();
registrarSaida();
}}
>
<!-- Leitor de Código de Barras -->
<div class="mb-6 rounded-xl border border-base-300 bg-base-200/50 p-4">
<div class="border-base-300 bg-base-200/50 mb-6 rounded-xl border p-4">
<BarcodeScanner
enabled={saidaScannerEnabled}
onScan={buscarMaterialPorCodigoBarrasSaida}
@@ -463,8 +519,8 @@
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<!-- Material -->
<div class="form-control md:col-span-2">
<label class="label pb-2">
<span class="label-text font-semibold flex items-center gap-2">
<label for="saidaMaterialId" class="label pb-2">
<span class="label-text flex items-center gap-2 font-semibold">
<Package class="h-4 w-4" />
Material <span class="text-error">*</span>
{#if saidaBuscandoMaterial}
@@ -472,17 +528,24 @@
{/if}
</span>
</label>
<select class="select select-bordered w-full focus:select-primary transition-colors h-12 {saidaBuscandoMaterial ? 'input-info' : ''}" bind:value={saidaMaterialId} required>
<select
id="saidaMaterialId"
class="select select-bordered focus:select-primary h-12 w-full transition-colors {saidaBuscandoMaterial
? 'input-info'
: ''}"
bind:value={saidaMaterialId}
required
>
<option value="">Selecione um material</option>
{#if materiaisQuery.data}
{#each materiaisQuery.data as material}
{#each materiaisQuery.data as material (material._id)}
<option value={material._id}>
{material.codigo} - {material.nome} (Estoque: {material.estoqueAtual}{material.unidadeMedida})
</option>
{/each}
{/if}
</select>
<label class="label pt-1">
<div class="label pt-1">
<span class="label-text-alt text-base-content/60">
{#if saidaBuscandoMaterial}
<span class="text-info">Buscando material...</span>
@@ -490,17 +553,20 @@
Selecione manualmente ou use o leitor de código de barras acima
{/if}
</span>
</label>
</div>
</div>
<!-- Quantidade -->
<div class="form-control">
<label class="label pb-2">
<span class="label-text font-semibold">Quantidade <span class="text-error">*</span></span>
<label for="saidaQuantidade" class="label pb-2">
<span class="label-text font-semibold"
>Quantidade <span class="text-error">*</span></span
>
</label>
<input
id="saidaQuantidade"
type="number"
class="input input-bordered w-full focus:input-primary transition-colors h-12"
class="input input-bordered focus:input-primary h-12 w-full transition-colors"
min="1"
step="1"
bind:value={saidaQuantidade}
@@ -515,77 +581,97 @@
}
}}
/>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Quantidade a ser retirada do estoque</span>
</label>
<div class="label pt-1">
<span class="label-text-alt text-base-content/60"
>Quantidade a ser retirada do estoque</span
>
</div>
</div>
<!-- Funcionário -->
<div class="form-control">
<label class="label pb-2">
<span class="label-text font-semibold flex items-center gap-2">
<label for="saidaFuncionarioId" class="label pb-2">
<span class="label-text flex items-center gap-2 font-semibold">
<User class="h-4 w-4" />
Funcionário
</span>
</label>
<select class="select select-bordered w-full focus:select-primary transition-colors h-12" bind:value={saidaFuncionarioId}>
<select
id="saidaFuncionarioId"
class="select select-bordered focus:select-primary h-12 w-full transition-colors"
bind:value={saidaFuncionarioId}
>
<option value="">Selecione (opcional)</option>
{#if funcionariosQuery.data}
{#each funcionariosQuery.data as funcionario}
{#each funcionariosQuery.data as funcionario (funcionario._id)}
<option value={funcionario._id}>{funcionario.nome}</option>
{/each}
{/if}
</select>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Funcionário que receberá o material</span>
</label>
<div class="label pt-1">
<span class="label-text-alt text-base-content/60"
>Funcionário que receberá o material</span
>
</div>
</div>
<!-- Setor -->
<div class="form-control">
<label class="label pb-2">
<span class="label-text font-semibold flex items-center gap-2">
<label for="saidaSetorId" class="label pb-2">
<span class="label-text flex items-center gap-2 font-semibold">
<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={saidaSetorId}>
<select
id="saidaSetorId"
class="select select-bordered focus:select-primary h-12 w-full transition-colors"
bind:value={saidaSetorId}
>
<option value="">Selecione (opcional)</option>
{#if setoresQuery.data}
{#each setoresQuery.data as setor}
{#each setoresQuery.data as setor (setor._id)}
<option value={setor._id}>{setor.nome}</option>
{/each}
{/if}
</select>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Setor que receberá o material</span>
</label>
<div class="label pt-1">
<span class="label-text-alt text-base-content/60"
>Setor que receberá o material</span
>
</div>
</div>
<!-- Motivo -->
<div class="form-control md:col-span-2">
<label class="label pb-2">
<span class="label-text font-semibold">Motivo <span class="text-error">*</span></span>
<label for="saidaMotivo" class="label pb-2">
<span class="label-text font-semibold"
>Motivo <span class="text-error">*</span></span
>
</label>
<input
id="saidaMotivo"
type="text"
class="input input-bordered w-full focus:input-primary transition-colors h-12"
class="input input-bordered focus:input-primary h-12 w-full transition-colors"
placeholder="Ex: Uso interno, Empréstimo, Descarte"
bind:value={saidaMotivo}
required
/>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Razão da saída do material do estoque</span>
</label>
<div class="label pt-1">
<span class="label-text-alt text-base-content/60"
>Razão da saída do material do estoque</span
>
</div>
</div>
<!-- Observações -->
<div class="form-control md:col-span-2">
<label class="label pb-2">
<label for="saidaObservacoes" class="label pb-2">
<span class="label-text font-semibold">Observações</span>
</label>
<textarea
class="textarea textarea-bordered w-full focus:textarea-primary transition-colors min-h-[100px]"
id="saidaObservacoes"
class="textarea textarea-bordered focus:textarea-primary min-h-[100px] w-full transition-colors"
placeholder="Observações adicionais (opcional)"
bind:value={saidaObservacoes}
rows="3"
@@ -593,8 +679,12 @@
</div>
</div>
<div class="card-actions mt-8 justify-end gap-4 border-t-2 border-base-300 pt-6">
<button type="submit" class="btn btn-error btn-lg min-w-[180px] shadow-lg hover:shadow-xl transition-all" disabled={saidaLoading}>
<div class="card-actions border-base-300 mt-8 justify-end gap-4 border-t-2 pt-6">
<button
type="submit"
class="btn btn-error btn-lg min-w-[180px] shadow-lg transition-all hover:shadow-xl"
disabled={saidaLoading}
>
{#if saidaLoading}
<span class="loading loading-spinner loading-sm"></span>
Registrando...
@@ -608,32 +698,45 @@
</div>
</div>
{:else if abaAtiva === 'ajuste'}
<div class="card bg-base-100 border border-base-300 shadow-2xl">
<div class="card bg-base-100 border-base-300 border shadow-2xl">
<div class="card-body p-8">
<div class="mb-8 flex items-center gap-3 border-b-2 border-warning/20 pb-4">
<div class="rounded-lg bg-warning/10 p-2.5">
<Settings class="h-6 w-6 text-warning" strokeWidth={2.5} />
<div class="border-warning/20 mb-8 flex items-center gap-3 border-b-2 pb-4">
<div class="bg-warning/10 rounded-lg p-2.5">
<Settings class="text-warning h-6 w-6" strokeWidth={2.5} />
</div>
<h2 class="text-2xl font-bold text-base-content">Ajustar Estoque</h2>
<h2 class="text-base-content text-2xl font-bold">Ajustar Estoque</h2>
</div>
<div class="alert alert-warning mb-8 shadow-lg border-2 border-warning/30">
<div class="alert alert-warning border-warning/30 mb-8 border-2 shadow-lg">
<AlertCircle class="h-6 w-6" />
<span class="font-semibold">Atenção: Ajustes de estoque devem ser justificados e são registrados permanentemente no histórico.</span>
<span class="font-semibold"
>Atenção: Ajustes de estoque devem ser justificados e são registrados permanentemente no
histórico.</span
>
</div>
<form onsubmit={(e) => { e.preventDefault(); ajustarEstoque(); }}>
<form
onsubmit={(e) => {
e.preventDefault();
ajustarEstoque();
}}
>
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
<!-- Material -->
<div class="form-control md:col-span-2">
<label class="label pb-2">
<span class="label-text font-semibold flex items-center gap-2">
<label for="ajusteMaterialId" class="label pb-2">
<span class="label-text flex items-center gap-2 font-semibold">
<Package class="h-4 w-4" />
Material <span class="text-error">*</span>
</span>
</label>
<select class="select select-bordered w-full focus:select-primary transition-colors h-12" bind:value={ajusteMaterialId} required>
<select
id="ajusteMaterialId"
class="select select-bordered focus:select-primary h-12 w-full transition-colors"
bind:value={ajusteMaterialId}
required
>
<option value="">Selecione um material</option>
{#if materiaisQuery.data}
{#each materiaisQuery.data as material}
{#each materiaisQuery.data as material (material._id)}
<option value={material._id}>
{material.codigo} - {material.nome} (Atual: {material.estoqueAtual}{material.unidadeMedida})
</option>
@@ -644,12 +747,15 @@
<!-- Nova Quantidade -->
<div class="form-control md:col-span-2">
<label class="label pb-2">
<span class="label-text font-semibold">Nova Quantidade <span class="text-error">*</span></span>
<label for="ajusteQuantidadeNova" class="label pb-2">
<span class="label-text font-semibold"
>Nova Quantidade <span class="text-error">*</span></span
>
</label>
<input
id="ajusteQuantidadeNova"
type="number"
class="input input-bordered w-full focus:input-primary transition-colors h-12"
class="input input-bordered focus:input-primary h-12 w-full transition-colors"
min="0"
step="1"
bind:value={ajusteQuantidadeNova}
@@ -664,35 +770,43 @@
}
}}
/>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Quantidade correta após o ajuste</span>
</label>
<div class="label pt-1">
<span class="label-text-alt text-base-content/60"
>Quantidade correta após o ajuste</span
>
</div>
</div>
<!-- Justificativa -->
<div class="form-control md:col-span-2">
<label class="label pb-2">
<span class="label-text font-semibold">Justificativa <span class="text-error">*</span></span>
<label for="ajusteMotivo" class="label pb-2">
<span class="label-text font-semibold"
>Justificativa <span class="text-error">*</span></span
>
</label>
<input
id="ajusteMotivo"
type="text"
class="input input-bordered w-full focus:input-primary transition-colors h-12"
class="input input-bordered focus:input-primary h-12 w-full transition-colors"
placeholder="Ex: Inventário físico, Correção de erro, Perda"
bind:value={ajusteMotivo}
required
/>
<label class="label pt-1">
<span class="label-text-alt text-base-content/60">Razão para o ajuste de estoque</span>
</label>
<div class="label pt-1">
<span class="label-text-alt text-base-content/60"
>Razão para o ajuste de estoque</span
>
</div>
</div>
<!-- Observações -->
<div class="form-control md:col-span-2">
<label class="label pb-2">
<label for="ajusteObservacoes" class="label pb-2">
<span class="label-text font-semibold">Observações</span>
</label>
<textarea
class="textarea textarea-bordered w-full focus:textarea-primary transition-colors min-h-[100px]"
id="ajusteObservacoes"
class="textarea textarea-bordered focus:textarea-primary min-h-[100px] w-full transition-colors"
placeholder="Observações adicionais (opcional)"
bind:value={ajusteObservacoes}
rows="3"
@@ -700,8 +814,12 @@
</div>
</div>
<div class="card-actions mt-8 justify-end gap-4 border-t-2 border-base-300 pt-6">
<button type="submit" class="btn btn-warning btn-lg min-w-[180px] shadow-lg hover:shadow-xl transition-all" disabled={ajusteLoading}>
<div class="card-actions border-base-300 mt-8 justify-end gap-4 border-t-2 pt-6">
<button
type="submit"
class="btn btn-warning btn-lg min-w-[180px] shadow-lg transition-all hover:shadow-xl"
disabled={ajusteLoading}
>
{#if ajusteLoading}
<span class="loading loading-spinner loading-sm"></span>
Ajustando...
@@ -715,34 +833,36 @@
</div>
</div>
{:else if abaAtiva === 'historico'}
<div class="card bg-base-100 border border-base-300 shadow-2xl">
<div class="card bg-base-100 border-base-300 border shadow-2xl">
<div class="card-body p-8">
<div class="mb-8 flex items-center gap-3 border-b-2 border-info/20 pb-4">
<div class="rounded-lg bg-info/10 p-2.5">
<History class="h-6 w-6 text-info" strokeWidth={2.5} />
<div class="border-info/20 mb-8 flex items-center gap-3 border-b-2 pb-4">
<div class="bg-info/10 rounded-lg p-2.5">
<History class="text-info h-6 w-6" strokeWidth={2.5} />
</div>
<h2 class="text-2xl font-bold text-base-content">Histórico de Movimentações</h2>
<h2 class="text-base-content text-2xl font-bold">Histórico de Movimentações</h2>
</div>
<div class="overflow-x-auto rounded-lg border border-base-300">
<table class="table table-zebra">
<div class="border-base-300 overflow-x-auto rounded-lg border">
<table class="table-zebra table">
<thead>
<tr class="bg-base-200">
<th class="font-bold text-base-content">Data</th>
<th class="font-bold text-base-content">Material</th>
<th class="font-bold text-base-content">Tipo</th>
<th class="font-bold text-base-content">Quantidade</th>
<th class="font-bold text-base-content">Anterior</th>
<th class="font-bold text-base-content">Nova</th>
<th class="font-bold text-base-content">Funcionário</th>
<th class="font-bold text-base-content">Usuário</th>
<th class="font-bold text-base-content">Motivo</th>
<th class="text-base-content font-bold">Data</th>
<th class="text-base-content font-bold">Material</th>
<th class="text-base-content font-bold">Tipo</th>
<th class="text-base-content font-bold">Quantidade</th>
<th class="text-base-content font-bold">Anterior</th>
<th class="text-base-content font-bold">Nova</th>
<th class="text-base-content font-bold">Funcionário</th>
<th class="text-base-content font-bold">Usuário</th>
<th class="text-base-content font-bold">Motivo</th>
</tr>
</thead>
<tbody>
{#if movimentacoesQuery.data && movimentacoesQuery.data.length > 0}
{#each movimentacoesQuery.data.slice(0, 100) as item}
{#each movimentacoesQuery.data.slice(0, 100) as item (item.id)}
{@const material = materiaisMap.get(item.materialId)}
{@const funcionario = item.funcionarioId ? funcionariosMap.get(item.funcionarioId) : null}
{@const funcionario = item.funcionarioId
? funcionariosMap.get(item.funcionarioId)
: null}
{@const isMovimentacao = item.tipo === 'movimentacao'}
{@const isAlteracao = item.tipo === 'alteracao'}
<tr class="hover:bg-base-200/50 transition-colors">
@@ -751,17 +871,21 @@
</td>
<td>
{#if isAlteracao && item.tipoAlteracao === 'exclusao'}
<div class="font-medium text-base-content/60 italic">
<div class="text-base-content/60 font-medium italic">
Material excluído (ID: {item.materialId})
</div>
{:else if material}
<div class="font-medium">{material.nome}</div>
{#if material.codigo}
<div class="text-xs text-base-content/50 font-mono">{material.codigo}</div>
<div class="text-base-content/50 font-mono text-xs">
{material.codigo}
</div>
{/if}
{:else}
<div class="font-medium text-base-content/60">Material não encontrado</div>
<div class="text-xs text-base-content/50 font-mono">ID: {item.materialId}</div>
<div class="text-base-content/60 font-medium">Material não encontrado</div>
<div class="text-base-content/50 font-mono text-xs">
ID: {item.materialId}
</div>
{/if}
</td>
<td>
@@ -810,7 +934,9 @@
{#if funcionario}
<div class="font-medium">{funcionario.nome}</div>
{#if funcionario.matricula}
<div class="text-xs text-base-content/50">Mat: {funcionario.matricula}</div>
<div class="text-base-content/50 text-xs">
Mat: {funcionario.matricula}
</div>
{/if}
{:else}
<span class="text-base-content/50 text-sm italic"></span>
@@ -820,7 +946,7 @@
{#if item.usuarioNome}
<div class="font-medium">{item.usuarioNome}</div>
{#if isAlteracao}
<div class="text-xs text-base-content/50">
<div class="text-base-content/50 text-xs">
{#if item.tipoAlteracao === 'criacao'}
Criou
{:else if item.tipoAlteracao === 'edicao'}
@@ -843,8 +969,10 @@
<tr>
<td colspan="9" class="text-center">
<div class="py-12">
<History class="mx-auto mb-4 h-16 w-16 text-base-content/30" />
<p class="text-base-content/70 text-lg font-medium">Nenhuma movimentação registrada</p>
<History class="text-base-content/30 mx-auto mb-4 h-16 w-16" />
<p class="text-base-content/70 text-lg font-medium">
Nenhuma movimentação registrada
</p>
</div>
</td>
</tr>
@@ -856,5 +984,3 @@
</div>
{/if}
</main>