refactor: enhance 'Almoxarifado' UI with improved layout, updated styling, and efficient data handling for better user experience and performance
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
import { api } from '@sgse-app/backend/convex/_generated/api';
|
import { api } from '@sgse-app/backend/convex/_generated/api';
|
||||||
import { useConvexClient, useQuery } from 'convex-svelte';
|
import { useConvexClient, useQuery } from 'convex-svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
import {
|
import {
|
||||||
Package,
|
Package,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
@@ -14,69 +15,114 @@
|
|||||||
const statsQuery = useQuery(api.almoxarifado.obterEstatisticas, {});
|
const statsQuery = useQuery(api.almoxarifado.obterEstatisticas, {});
|
||||||
const alertasQuery = useQuery(api.almoxarifado.listarAlertas, { status: 'ativo' });
|
const alertasQuery = useQuery(api.almoxarifado.listarAlertas, { status: 'ativo' });
|
||||||
const materiaisQuery = useQuery(api.almoxarifado.listarMateriais, {});
|
const materiaisQuery = useQuery(api.almoxarifado.listarMateriais, {});
|
||||||
|
|
||||||
|
// Criar mapa de materiais para lookup eficiente
|
||||||
|
const materiaisMap = $derived.by(() => {
|
||||||
|
if (!materiaisQuery.data) return new Map();
|
||||||
|
const map = new Map();
|
||||||
|
for (const material of materiaisQuery.data) {
|
||||||
|
map.set(material._id, material);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="container mx-auto px-4 py-4">
|
<main class="container mx-auto px-4 py-6">
|
||||||
|
<!-- Breadcrumb -->
|
||||||
|
<div class="breadcrumbs mb-6 text-sm">
|
||||||
|
<ul>
|
||||||
|
<li><a href={resolve('/')} class="text-primary hover:underline">Dashboard</a></li>
|
||||||
|
<li>Almoxarifado</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Cabeçalho -->
|
<!-- Cabeçalho -->
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h1 class="text-primary mb-2 text-4xl font-bold">Almoxarifado</h1>
|
<div class="mb-4 flex items-center gap-4">
|
||||||
<p class="text-base-content/70 text-lg">
|
<div class="rounded-2xl bg-gradient-to-br from-primary/20 to-primary/30 p-4 shadow-lg">
|
||||||
Controle de estoque e gestão de materiais
|
<Package class="h-10 w-10 text-primary" strokeWidth={2.5} />
|
||||||
</p>
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 class="text-primary mb-2 text-4xl font-bold tracking-tight">Almoxarifado</h1>
|
||||||
|
<p class="text-base-content/70 text-lg">
|
||||||
|
Controle de estoque e gestão de materiais
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Estatísticas -->
|
<!-- Estatísticas -->
|
||||||
{#if statsQuery.data}
|
{#if statsQuery === undefined}
|
||||||
<div class="mb-8 grid grid-cols-1 gap-4 md:grid-cols-4">
|
<div class="mb-8 flex items-center justify-center py-12">
|
||||||
<div class="stats from-primary/10 to-primary/20 bg-linear-to-br shadow-lg">
|
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||||
<div class="stat">
|
</div>
|
||||||
<div class="stat-figure text-primary">
|
{:else if statsQuery.data}
|
||||||
<Package class="h-8 w-8" strokeWidth={2} />
|
<div class="mb-8 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||||
|
<div class="card bg-gradient-to-br from-primary/10 via-primary/5 to-base-100 border border-primary/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="text-sm font-medium text-base-content/60 mb-1">Total de Materiais</div>
|
||||||
|
<div class="text-3xl font-bold text-primary mb-1">
|
||||||
|
{statsQuery.data.totalMateriais}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-base-content/50">Materiais cadastrados</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-xl bg-primary/20 p-3">
|
||||||
|
<Package class="h-8 w-8 text-primary" strokeWidth={2.5} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-title">Total de Materiais</div>
|
|
||||||
<div class="stat-value text-primary">
|
|
||||||
{statsQuery.data.totalMateriais}
|
|
||||||
</div>
|
|
||||||
<div class="stat-desc">Materiais cadastrados</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stats from-success/10 to-success/20 bg-linear-to-br shadow-lg">
|
<div class="card bg-gradient-to-br from-success/10 via-success/5 to-base-100 border border-success/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
|
||||||
<div class="stat">
|
<div class="card-body">
|
||||||
<div class="stat-figure text-success">
|
<div class="flex items-center justify-between">
|
||||||
<CheckCircle2 class="h-8 w-8" strokeWidth={2} />
|
<div class="flex-1">
|
||||||
|
<div class="text-sm font-medium text-base-content/60 mb-1">Materiais Ativos</div>
|
||||||
|
<div class="text-3xl font-bold text-success mb-1">
|
||||||
|
{statsQuery.data.totalMateriaisAtivos}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-base-content/50">Em estoque</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-xl bg-success/20 p-3">
|
||||||
|
<CheckCircle2 class="h-8 w-8 text-success" strokeWidth={2.5} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-title">Materiais Ativos</div>
|
|
||||||
<div class="stat-value text-success">
|
|
||||||
{statsQuery.data.totalMateriaisAtivos}
|
|
||||||
</div>
|
|
||||||
<div class="stat-desc">Em estoque</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stats from-warning/10 to-warning/20 bg-linear-to-br shadow-lg">
|
<div class="card bg-gradient-to-br from-warning/10 via-warning/5 to-base-100 border border-warning/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
|
||||||
<div class="stat">
|
<div class="card-body">
|
||||||
<div class="stat-figure text-warning">
|
<div class="flex items-center justify-between">
|
||||||
<AlertTriangle class="h-8 w-8" strokeWidth={2} />
|
<div class="flex-1">
|
||||||
|
<div class="text-sm font-medium text-base-content/60 mb-1">Alertas Ativos</div>
|
||||||
|
<div class="text-3xl font-bold text-warning mb-1">
|
||||||
|
{statsQuery.data.totalAlertasAtivos}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-base-content/50">Estoque baixo</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-xl bg-warning/20 p-3">
|
||||||
|
<AlertTriangle class="h-8 w-8 text-warning" strokeWidth={2.5} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-title">Alertas Ativos</div>
|
|
||||||
<div class="stat-value text-warning">
|
|
||||||
{statsQuery.data.totalAlertasAtivos}
|
|
||||||
</div>
|
|
||||||
<div class="stat-desc">Estoque baixo</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stats from-info/10 to-info/20 bg-linear-to-br shadow-lg">
|
<div class="card bg-gradient-to-br from-info/10 via-info/5 to-base-100 border border-info/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
|
||||||
<div class="stat">
|
<div class="card-body">
|
||||||
<div class="stat-figure text-info">
|
<div class="flex items-center justify-between">
|
||||||
<ArrowLeftRight class="h-8 w-8" strokeWidth={2} />
|
<div class="flex-1">
|
||||||
|
<div class="text-sm font-medium text-base-content/60 mb-1">Movimentações</div>
|
||||||
|
<div class="text-3xl font-bold text-info mb-1">
|
||||||
|
{statsQuery.data.movimentacoesMes}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-base-content/50">Este mês</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-xl bg-info/20 p-3">
|
||||||
|
<ArrowLeftRight class="h-8 w-8 text-info" strokeWidth={2.5} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-title">Movimentações</div>
|
|
||||||
<div class="stat-value text-info">
|
|
||||||
{statsQuery.data.movimentacoesMes}
|
|
||||||
</div>
|
|
||||||
<div class="stat-desc">Este mês</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,45 +130,58 @@
|
|||||||
|
|
||||||
<!-- Alertas Recentes -->
|
<!-- Alertas Recentes -->
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<div class="card bg-base-100 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title text-2xl">
|
<h2 class="card-title text-2xl mb-1">
|
||||||
<AlertTriangle class="h-6 w-6 text-warning" />
|
<div class="rounded-lg bg-warning/20 p-2">
|
||||||
Alertas de Estoque
|
<AlertTriangle class="h-6 w-6 text-warning" />
|
||||||
|
</div>
|
||||||
|
<span>Alertas de Estoque</span>
|
||||||
</h2>
|
</h2>
|
||||||
{#if alertasQuery.data && alertasQuery.data.length > 0}
|
{#if alertasQuery === undefined}
|
||||||
|
<div class="flex items-center justify-center py-8">
|
||||||
|
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||||
|
</div>
|
||||||
|
{:else if alertasQuery.data && alertasQuery.data.length > 0}
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="table">
|
<table class="table table-zebra">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr class="bg-base-200">
|
||||||
<th>Material</th>
|
<th class="font-semibold">Material</th>
|
||||||
<th>Tipo</th>
|
<th class="font-semibold">Tipo</th>
|
||||||
<th>Quantidade Atual</th>
|
<th class="font-semibold">Quantidade Atual</th>
|
||||||
<th>Quantidade Mínima</th>
|
<th class="font-semibold">Quantidade Mínima</th>
|
||||||
<th>Ações</th>
|
<th class="font-semibold">Ações</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each alertasQuery.data.slice(0, 5) as alerta}
|
{#each alertasQuery.data.slice(0, 5) as alerta}
|
||||||
{@const material = materiaisQuery.data?.find(m => m._id === alerta.materialId)}
|
{@const material = materiaisMap.get(alerta.materialId)}
|
||||||
<tr>
|
<tr class="hover:bg-base-200/50 transition-colors">
|
||||||
<td>
|
<td>
|
||||||
{material?.nome || 'Carregando...'}
|
<div class="font-medium">{material?.nome || 'Carregando...'}</div>
|
||||||
|
{#if material?.codigo}
|
||||||
|
<div class="text-xs text-base-content/50 font-mono">{material.codigo}</div>
|
||||||
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{#if alerta.tipo === 'estoque_zerado'}
|
{#if alerta.tipo === 'estoque_zerado'}
|
||||||
<span class="badge badge-error">Zerado</span>
|
<span class="badge badge-error badge-lg">Zerado</span>
|
||||||
{:else if alerta.tipo === 'estoque_minimo'}
|
{:else if alerta.tipo === 'estoque_minimo'}
|
||||||
<span class="badge badge-warning">Mínimo</span>
|
<span class="badge badge-warning badge-lg">Mínimo</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="badge badge-info">Reposição</span>
|
<span class="badge badge-info badge-lg">Reposição</span>
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<td>{alerta.quantidadeAtual}</td>
|
<td>
|
||||||
<td>{alerta.quantidadeMinima}</td>
|
<span class="font-bold text-error">{alerta.quantidadeAtual}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="font-medium">{alerta.quantidadeMinima}</span>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-primary"
|
class="btn btn-sm btn-primary btn-outline"
|
||||||
onclick={() => goto('/almoxarifado/alertas')}
|
onclick={() => goto('/almoxarifado/alertas')}
|
||||||
>
|
>
|
||||||
Ver Detalhes
|
Ver Detalhes
|
||||||
@@ -133,7 +192,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions justify-end">
|
<div class="card-actions justify-end mt-4">
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
onclick={() => goto('/almoxarifado/alertas')}
|
onclick={() => goto('/almoxarifado/alertas')}
|
||||||
@@ -142,9 +201,9 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="alert alert-success">
|
<div class="alert alert-success shadow-lg">
|
||||||
<CheckCircle2 class="h-6 w-6" />
|
<CheckCircle2 class="h-6 w-6" />
|
||||||
<span>Nenhum alerta ativo no momento!</span>
|
<span class="font-medium">Nenhum alerta ativo no momento!</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -152,43 +211,49 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Ações Rápidas -->
|
<!-- Ações Rápidas -->
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
|
<div class="grid grid-cols-1 gap-6 md:grid-cols-3">
|
||||||
<button
|
<button
|
||||||
class="card bg-base-100 shadow-xl hover:shadow-2xl transition-shadow"
|
class="card bg-base-100 border border-base-300 shadow-xl hover:shadow-2xl hover:border-primary/50 transition-all duration-300 hover:scale-[1.02] group"
|
||||||
onclick={() => goto('/almoxarifado/materiais/cadastro')}
|
onclick={() => goto('/almoxarifado/materiais/cadastro')}
|
||||||
>
|
>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h3 class="card-title">
|
<div class="flex items-center gap-4 mb-2">
|
||||||
<Package class="h-6 w-6 text-primary" />
|
<div class="rounded-xl bg-primary/20 p-3 group-hover:bg-primary/30 transition-colors">
|
||||||
Cadastrar Material
|
<Package class="h-7 w-7 text-primary" strokeWidth={2.5} />
|
||||||
</h3>
|
</div>
|
||||||
<p class="text-base-content/70">Adicionar novo material ao estoque</p>
|
<h3 class="card-title text-lg mb-0">Cadastrar Material</h3>
|
||||||
|
</div>
|
||||||
|
<p class="text-base-content/70 text-sm">Adicionar novo material ao estoque</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="card bg-base-100 shadow-xl hover:shadow-2xl transition-shadow"
|
class="card bg-base-100 border border-base-300 shadow-xl hover:shadow-2xl hover:border-info/50 transition-all duration-300 hover:scale-[1.02] group"
|
||||||
onclick={() => goto('/almoxarifado/movimentacoes')}
|
onclick={() => goto('/almoxarifado/movimentacoes')}
|
||||||
>
|
>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h3 class="card-title">
|
<div class="flex items-center gap-4 mb-2">
|
||||||
<ArrowLeftRight class="h-6 w-6 text-info" />
|
<div class="rounded-xl bg-info/20 p-3 group-hover:bg-info/30 transition-colors">
|
||||||
Registrar Movimentação
|
<ArrowLeftRight class="h-7 w-7 text-info" strokeWidth={2.5} />
|
||||||
</h3>
|
</div>
|
||||||
<p class="text-base-content/70">Registrar entrada ou saída de material</p>
|
<h3 class="card-title text-lg mb-0">Registrar Movimentação</h3>
|
||||||
|
</div>
|
||||||
|
<p class="text-base-content/70 text-sm">Registrar entrada ou saída de material</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="card bg-base-100 shadow-xl hover:shadow-2xl transition-shadow"
|
class="card bg-base-100 border border-base-300 shadow-xl hover:shadow-2xl hover:border-success/50 transition-all duration-300 hover:scale-[1.02] group"
|
||||||
onclick={() => goto('/almoxarifado/relatorios')}
|
onclick={() => goto('/almoxarifado/relatorios')}
|
||||||
>
|
>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h3 class="card-title">
|
<div class="flex items-center gap-4 mb-2">
|
||||||
<BarChart3 class="h-6 w-6 text-success" />
|
<div class="rounded-xl bg-success/20 p-3 group-hover:bg-success/30 transition-colors">
|
||||||
Relatórios
|
<BarChart3 class="h-7 w-7 text-success" strokeWidth={2.5} />
|
||||||
</h3>
|
</div>
|
||||||
<p class="text-base-content/70">Visualizar relatórios e estatísticas</p>
|
<h3 class="card-title text-lg mb-0">Relatórios</h3>
|
||||||
|
</div>
|
||||||
|
<p class="text-base-content/70 text-sm">Visualizar relatórios e estatísticas</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -77,15 +77,10 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="container mx-auto px-4 py-4">
|
<main class="container mx-auto px-4 py-6">
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
<div class="breadcrumbs mb-4 text-sm">
|
<div class="breadcrumbs mb-6 text-sm">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
|
||||||
<a href={resolve('/recursos-humanos')} class="text-primary hover:underline"
|
|
||||||
>Recursos Humanos</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<a href={resolve('/almoxarifado')} class="text-primary hover:underline"
|
<a href={resolve('/almoxarifado')} class="text-primary hover:underline"
|
||||||
>Almoxarifado</a
|
>Almoxarifado</a
|
||||||
@@ -96,14 +91,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Cabeçalho -->
|
<!-- Cabeçalho -->
|
||||||
<div class="mb-6">
|
<div class="mb-8">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<div class="rounded-xl bg-warning/20 p-3">
|
<div class="rounded-2xl bg-gradient-to-br from-warning/20 to-warning/30 p-4 shadow-lg">
|
||||||
<AlertTriangle class="h-8 w-8 text-warning" strokeWidth={2} />
|
<AlertTriangle class="h-10 w-10 text-warning" strokeWidth={2.5} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold">Alertas de Estoque</h1>
|
<h1 class="text-3xl font-bold tracking-tight">Alertas de Estoque</h1>
|
||||||
<p class="text-base-content/70">Visualize e gerencie alertas de estoque baixo</p>
|
<p class="text-base-content/70 text-lg">Visualize e gerencie alertas de estoque baixo</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,8 +111,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Filtros -->
|
<!-- Filtros -->
|
||||||
<div class="card bg-base-100 mb-6 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 mb-6 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
<h3 class="text-lg font-semibold mb-4">Filtros</h3>
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
@@ -147,39 +143,39 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Lista de Alertas -->
|
<!-- Lista de Alertas -->
|
||||||
<div class="card bg-base-100 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{#if alertasQuery.data && alertasQuery.data.length > 0}
|
{#if alertasQuery.data && alertasQuery.data.length > 0}
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="table">
|
<table class="table table-zebra">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr class="bg-base-200">
|
||||||
<th>Material</th>
|
<th class="font-semibold">Material</th>
|
||||||
<th>Tipo</th>
|
<th class="font-semibold">Tipo</th>
|
||||||
<th>Quantidade Atual</th>
|
<th class="font-semibold">Quantidade Atual</th>
|
||||||
<th>Quantidade Mínima</th>
|
<th class="font-semibold">Quantidade Mínima</th>
|
||||||
<th>Diferença</th>
|
<th class="font-semibold">Diferença</th>
|
||||||
<th>Status</th>
|
<th class="font-semibold">Status</th>
|
||||||
<th>Data</th>
|
<th class="font-semibold">Data</th>
|
||||||
<th>Ações</th>
|
<th class="font-semibold">Ações</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each alertasQuery.data as alerta}
|
{#each alertasQuery.data as alerta}
|
||||||
{@const material = materiaisQuery.data?.find(m => m._id === alerta.materialId)}
|
{@const material = materiaisQuery.data?.find(m => m._id === alerta.materialId)}
|
||||||
{@const diferenca = alerta.quantidadeMinima - alerta.quantidadeAtual}
|
{@const diferenca = alerta.quantidadeMinima - alerta.quantidadeAtual}
|
||||||
<tr>
|
<tr class="hover:bg-base-200/50 transition-colors">
|
||||||
<td>
|
<td>
|
||||||
<div class="font-medium">{material?.nome || 'Carregando...'}</div>
|
<div class="font-medium">{material?.nome || 'Carregando...'}</div>
|
||||||
<div class="text-sm text-base-content/60">{material?.codigo || ''}</div>
|
<div class="text-sm text-base-content/60 font-mono">{material?.codigo || ''}</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge {getTipoBadge(alerta.tipo)}">
|
<span class="badge {getTipoBadge(alerta.tipo)} badge-lg">
|
||||||
{getTipoLabel(alerta.tipo)}
|
{getTipoLabel(alerta.tipo)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="font-bold text-error">{alerta.quantidadeAtual}</div>
|
<div class="font-bold text-error text-lg">{alerta.quantidadeAtual}</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="font-medium">{alerta.quantidadeMinima}</div>
|
<div class="font-medium">{alerta.quantidadeMinima}</div>
|
||||||
@@ -189,14 +185,16 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{#if alerta.status === 'ativo'}
|
{#if alerta.status === 'ativo'}
|
||||||
<span class="badge badge-warning">Ativo</span>
|
<span class="badge badge-warning badge-lg">Ativo</span>
|
||||||
{:else if alerta.status === 'resolvido'}
|
{:else if alerta.status === 'resolvido'}
|
||||||
<span class="badge badge-success">Resolvido</span>
|
<span class="badge badge-success badge-lg">Resolvido</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="badge badge-ghost">Ignorado</span>
|
<span class="badge badge-ghost badge-lg">Ignorado</span>
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<td>{new Date(alerta.criadoEm).toLocaleDateString('pt-BR')}</td>
|
<td>
|
||||||
|
<span class="text-sm">{new Date(alerta.criadoEm).toLocaleDateString('pt-BR')}</span>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{#if alerta.status === 'ativo'}
|
{#if alerta.status === 'ativo'}
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
@@ -208,7 +206,7 @@
|
|||||||
Resolver
|
Resolver
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-ghost"
|
class="btn btn-sm btn-ghost hover:btn-error"
|
||||||
onclick={() => ignorarAlerta(alerta._id)}
|
onclick={() => ignorarAlerta(alerta._id)}
|
||||||
>
|
>
|
||||||
<XCircle class="h-4 w-4" />
|
<XCircle class="h-4 w-4" />
|
||||||
@@ -224,9 +222,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="text-center py-12">
|
<div class="text-center py-12">
|
||||||
<CheckCircle class="mx-auto mb-4 h-16 w-16 text-success" />
|
<CheckCircle class="mx-auto mb-4 h-20 w-20 text-success" />
|
||||||
<h3 class="text-xl font-bold mb-2">Nenhum alerta encontrado</h3>
|
<h3 class="text-2xl font-bold mb-2">Nenhum alerta encontrado</h3>
|
||||||
<p class="text-base-content/70">
|
<p class="text-base-content/70 text-lg">
|
||||||
{#if filtroStatus === 'ativo'}
|
{#if filtroStatus === 'ativo'}
|
||||||
Não há alertas ativos no momento. Todos os materiais estão com estoque adequado!
|
Não há alertas ativos no momento. Todos os materiais estão com estoque adequado!
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -52,9 +52,9 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="container mx-auto px-4 py-4">
|
<main class="container mx-auto px-4 py-6">
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
<div class="breadcrumbs mb-4 text-sm">
|
<div class="breadcrumbs mb-6 text-sm">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href={resolve('/almoxarifado')} class="text-primary hover:underline">Almoxarifado</a>
|
<a href={resolve('/almoxarifado')} class="text-primary hover:underline">Almoxarifado</a>
|
||||||
@@ -64,18 +64,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Cabeçalho -->
|
<!-- Cabeçalho -->
|
||||||
<div class="mb-6">
|
<div class="mb-8">
|
||||||
<div class="flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
|
<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="flex items-center gap-4">
|
||||||
<div class="rounded-xl bg-amber-500/20 p-3">
|
<div class="rounded-2xl bg-gradient-to-br from-amber-500/20 to-amber-600/30 p-4 shadow-lg">
|
||||||
<Package class="h-8 w-8 text-amber-600" strokeWidth={2} />
|
<Package class="h-10 w-10 text-amber-600" strokeWidth={2.5} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold">Materiais</h1>
|
<h1 class="text-3xl font-bold tracking-tight">Materiais</h1>
|
||||||
<p class="text-base-content/70">Gerencie o cadastro de materiais do almoxarifado</p>
|
<p class="text-base-content/70 text-lg">Gerencie o cadastro de materiais do almoxarifado</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary" onclick={navCadastro}>
|
<button class="btn btn-primary shadow-lg hover:shadow-xl transition-all" onclick={navCadastro}>
|
||||||
<Plus class="h-5 w-5" />
|
<Plus class="h-5 w-5" />
|
||||||
Cadastrar Material
|
Cadastrar Material
|
||||||
</button>
|
</button>
|
||||||
@@ -83,8 +83,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filtros -->
|
<!-- Filtros -->
|
||||||
<div class="card bg-base-100 mb-6 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 mb-6 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
<h3 class="text-lg font-semibold mb-4">Filtros de Busca</h3>
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-4">
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-4">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
@@ -135,68 +136,74 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tabela -->
|
<!-- Tabela -->
|
||||||
<div class="card bg-base-100 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="table">
|
<table class="table table-zebra">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr class="bg-base-200">
|
||||||
<th>Código</th>
|
<th class="font-semibold">Código</th>
|
||||||
<th>Nome</th>
|
<th class="font-semibold">Nome</th>
|
||||||
<th>Categoria</th>
|
<th class="font-semibold">Categoria</th>
|
||||||
<th>Estoque Atual</th>
|
<th class="font-semibold">Estoque Atual</th>
|
||||||
<th>Estoque Mínimo</th>
|
<th class="font-semibold">Estoque Mínimo</th>
|
||||||
<th>Unidade</th>
|
<th class="font-semibold">Unidade</th>
|
||||||
<th>Status</th>
|
<th class="font-semibold">Status</th>
|
||||||
<th>Ações</th>
|
<th class="font-semibold">Ações</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#if filtered.length === 0}
|
{#if filtered.length === 0}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="8" class="text-center">
|
<td colspan="8" class="text-center">
|
||||||
<div class="py-8">
|
<div class="py-12">
|
||||||
<Package class="mx-auto mb-4 h-12 w-12 text-base-content/30" />
|
<Package class="mx-auto mb-4 h-16 w-16 text-base-content/30" />
|
||||||
<p class="text-base-content/70">Nenhum material encontrado</p>
|
<p class="text-base-content/70 text-lg font-medium">Nenhum material encontrado</p>
|
||||||
|
<p class="text-base-content/50 text-sm mt-2">Tente ajustar os filtros de busca</p>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{:else}
|
{:else}
|
||||||
{#each filtered as material}
|
{#each filtered as material}
|
||||||
<tr>
|
<tr class="hover:bg-base-200/50 transition-colors">
|
||||||
<td>
|
<td>
|
||||||
<div class="font-mono font-bold">{material.codigo}</div>
|
<div class="font-mono font-bold text-primary">{material.codigo}</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="font-medium">{material.nome}</div>
|
<div class="font-medium">{material.nome}</div>
|
||||||
{#if material.descricao}
|
{#if material.descricao}
|
||||||
<div class="text-sm text-base-content/60">{material.descricao}</div>
|
<div class="text-sm text-base-content/60 line-clamp-1">{material.descricao}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge badge-outline">{material.categoria}</span>
|
<span class="badge badge-outline badge-lg">{material.categoria}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="font-bold">{material.estoqueAtual}</span>
|
<span class="font-bold {material.estoqueAtual <= material.estoqueMinimo ? 'text-error' : 'text-success'}">{material.estoqueAtual}</span>
|
||||||
{#if material.estoqueAtual <= material.estoqueMinimo}
|
{#if material.estoqueAtual <= material.estoqueMinimo}
|
||||||
<AlertTriangle class="h-4 w-4 text-warning" />
|
<AlertTriangle class="h-4 w-4 text-warning" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>{material.estoqueMinimo}</td>
|
<td>
|
||||||
<td>{material.unidadeMedida}</td>
|
<span class="font-medium">{material.estoqueMinimo}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge badge-ghost">{material.unidadeMedida}</span>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{#if material.ativo}
|
{#if material.ativo}
|
||||||
<span class="badge badge-success">Ativo</span>
|
<span class="badge badge-success badge-lg">Ativo</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="badge badge-error">Inativo</span>
|
<span class="badge badge-error badge-lg">Inativo</span>
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-ghost"
|
class="btn btn-sm btn-ghost hover:btn-primary"
|
||||||
|
title="Visualizar"
|
||||||
onclick={() =>
|
onclick={() =>
|
||||||
goto(
|
goto(
|
||||||
resolve(
|
resolve(
|
||||||
@@ -208,7 +215,8 @@
|
|||||||
<Eye class="h-4 w-4" />
|
<Eye class="h-4 w-4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-ghost"
|
class="btn btn-sm btn-ghost hover:btn-info"
|
||||||
|
title="Editar"
|
||||||
onclick={() =>
|
onclick={() =>
|
||||||
goto(
|
goto(
|
||||||
resolve(
|
resolve(
|
||||||
@@ -230,8 +238,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if filtered.length > 0}
|
{#if filtered.length > 0}
|
||||||
<div class="mt-4 text-sm text-base-content/70">
|
<div class="mt-6 flex items-center justify-between border-t border-base-300 pt-4">
|
||||||
Mostrando {filtered.length} de {materiais.length} materiais
|
<div class="text-sm text-base-content/70">
|
||||||
|
Mostrando <span class="font-semibold text-base-content">{filtered.length}</span> de <span class="font-semibold text-base-content">{materiais.length}</span> materiais
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -61,9 +61,9 @@
|
|||||||
<span class="loading loading-spinner loading-lg"></span>
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
</div>
|
</div>
|
||||||
{:else if material}
|
{:else if material}
|
||||||
<main class="container mx-auto px-4 py-4">
|
<main class="container mx-auto px-4 py-6">
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
<div class="breadcrumbs mb-4 text-sm">
|
<div class="breadcrumbs mb-6 text-sm">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href={resolve('/almoxarifado')} class="text-primary hover:underline">Almoxarifado</a>
|
<a href={resolve('/almoxarifado')} class="text-primary hover:underline">Almoxarifado</a>
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Cabeçalho -->
|
<!-- Cabeçalho -->
|
||||||
<div class="mb-6">
|
<div class="mb-8">
|
||||||
<div class="flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
|
<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="flex items-center gap-4">
|
||||||
<button
|
<button
|
||||||
@@ -87,15 +87,15 @@
|
|||||||
>
|
>
|
||||||
<ArrowLeft class="h-5 w-5" />
|
<ArrowLeft class="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
<div class="rounded-xl bg-amber-500/20 p-3">
|
<div class="rounded-2xl bg-gradient-to-br from-amber-500/20 to-amber-600/30 p-4 shadow-lg">
|
||||||
<Package class="h-8 w-8 text-amber-600" strokeWidth={2} />
|
<Package class="h-10 w-10 text-amber-600" strokeWidth={2.5} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold">{material.nome}</h1>
|
<h1 class="text-3xl font-bold tracking-tight">{material.nome}</h1>
|
||||||
<p class="text-base-content/70">Detalhes do material</p>
|
<p class="text-base-content/70 text-lg">Detalhes do material</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary" onclick={navEditar}>
|
<button class="btn btn-primary shadow-lg hover:shadow-xl transition-all" onclick={navEditar}>
|
||||||
<Edit class="h-5 w-5" />
|
<Edit class="h-5 w-5" />
|
||||||
Editar Material
|
Editar Material
|
||||||
</button>
|
</button>
|
||||||
@@ -105,10 +105,12 @@
|
|||||||
<!-- Informações Principais -->
|
<!-- Informações Principais -->
|
||||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||||
<!-- Card: Informações Básicas -->
|
<!-- Card: Informações Básicas -->
|
||||||
<div class="card bg-base-100 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title mb-4">
|
<h2 class="card-title mb-6 text-xl">
|
||||||
<Package class="h-5 w-5" />
|
<div class="rounded-lg bg-primary/20 p-2">
|
||||||
|
<Package class="h-6 w-6 text-primary" />
|
||||||
|
</div>
|
||||||
Informações Básicas
|
Informações Básicas
|
||||||
</h2>
|
</h2>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
@@ -157,10 +159,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Card: Estoque -->
|
<!-- Card: Estoque -->
|
||||||
<div class="card bg-base-100 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title mb-4">
|
<h2 class="card-title mb-6 text-xl">
|
||||||
<Boxes class="h-5 w-5" />
|
<div class="rounded-lg bg-info/20 p-2">
|
||||||
|
<Boxes class="h-6 w-6 text-info" />
|
||||||
|
</div>
|
||||||
Estoque
|
Estoque
|
||||||
</h2>
|
</h2>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
@@ -196,10 +200,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Informações Adicionais -->
|
<!-- Informações Adicionais -->
|
||||||
<div class="card bg-base-100 mt-6 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 mt-6 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title mb-4">
|
<h2 class="card-title mb-6 text-xl">
|
||||||
<Tag class="h-5 w-5" />
|
<div class="rounded-lg bg-success/20 p-2">
|
||||||
|
<Tag class="h-6 w-6 text-success" />
|
||||||
|
</div>
|
||||||
Informações Adicionais
|
Informações Adicionais
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
|
|||||||
@@ -138,9 +138,9 @@
|
|||||||
<span class="loading loading-spinner loading-lg"></span>
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<main class="container mx-auto px-4 py-4">
|
<main class="container mx-auto px-4 py-6">
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
<div class="breadcrumbs mb-4 text-sm">
|
<div class="breadcrumbs mb-6 text-sm">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href={resolve('/almoxarifado')} class="text-primary hover:underline">Almoxarifado</a>
|
<a href={resolve('/almoxarifado')} class="text-primary hover:underline">Almoxarifado</a>
|
||||||
@@ -160,7 +160,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Cabeçalho -->
|
<!-- Cabeçalho -->
|
||||||
<div class="mb-6">
|
<div class="mb-8">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<button
|
<button
|
||||||
class="btn btn-ghost btn-sm"
|
class="btn btn-ghost btn-sm"
|
||||||
@@ -168,12 +168,12 @@
|
|||||||
>
|
>
|
||||||
<ArrowLeft class="h-5 w-5" />
|
<ArrowLeft class="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
<div class="rounded-xl bg-amber-500/20 p-3">
|
<div class="rounded-2xl bg-gradient-to-br from-amber-500/20 to-amber-600/30 p-4 shadow-lg">
|
||||||
<Package class="h-8 w-8 text-amber-600" strokeWidth={2} />
|
<Package class="h-10 w-10 text-amber-600" strokeWidth={2.5} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold">Editar Material</h1>
|
<h1 class="text-3xl font-bold tracking-tight">Editar Material</h1>
|
||||||
<p class="text-base-content/70">Atualize as informações do material</p>
|
<p class="text-base-content/70 text-lg">Atualize as informações do material</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -186,7 +186,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Formulário -->
|
<!-- Formulário -->
|
||||||
<div class="card bg-base-100 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
|
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
|
||||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||||
|
|||||||
@@ -89,9 +89,9 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="container mx-auto px-4 py-4">
|
<main class="container mx-auto px-4 py-6">
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
<div class="breadcrumbs mb-4 text-sm">
|
<div class="breadcrumbs mb-6 text-sm">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<a href={resolve('/almoxarifado')} class="text-primary hover:underline">Almoxarifado</a>
|
<a href={resolve('/almoxarifado')} class="text-primary hover:underline">Almoxarifado</a>
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Cabeçalho -->
|
<!-- Cabeçalho -->
|
||||||
<div class="mb-6">
|
<div class="mb-8">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<button
|
<button
|
||||||
class="btn btn-ghost btn-sm"
|
class="btn btn-ghost btn-sm"
|
||||||
@@ -114,12 +114,12 @@
|
|||||||
>
|
>
|
||||||
<ArrowLeft class="h-5 w-5" />
|
<ArrowLeft class="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
<div class="rounded-xl bg-amber-500/20 p-3">
|
<div class="rounded-2xl bg-gradient-to-br from-amber-500/20 to-amber-600/30 p-4 shadow-lg">
|
||||||
<Package class="h-8 w-8 text-amber-600" strokeWidth={2} />
|
<Package class="h-10 w-10 text-amber-600" strokeWidth={2.5} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold">Cadastrar Material</h1>
|
<h1 class="text-3xl font-bold tracking-tight">Cadastrar Material</h1>
|
||||||
<p class="text-base-content/70">Adicione um novo material ao almoxarifado</p>
|
<p class="text-base-content/70 text-lg">Adicione um novo material ao almoxarifado</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Formulário -->
|
<!-- Formulário -->
|
||||||
<div class="card bg-base-100 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
|
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
|
||||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||||
|
|||||||
@@ -39,6 +39,26 @@
|
|||||||
const setoresQuery = useQuery(api.setores.list, {});
|
const setoresQuery = useQuery(api.setores.list, {});
|
||||||
const movimentacoesQuery = useQuery(api.almoxarifado.listarMovimentacoes, {});
|
const movimentacoesQuery = useQuery(api.almoxarifado.listarMovimentacoes, {});
|
||||||
|
|
||||||
|
// Criar mapa de funcionários para lookup eficiente
|
||||||
|
const funcionariosMap = $derived.by(() => {
|
||||||
|
if (!funcionariosQuery.data) return new Map();
|
||||||
|
const map = new Map();
|
||||||
|
for (const funcionario of funcionariosQuery.data) {
|
||||||
|
map.set(funcionario._id, funcionario);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Criar mapa de materiais para lookup eficiente
|
||||||
|
const materiaisMap = $derived.by(() => {
|
||||||
|
if (!materiaisQuery.data) return new Map();
|
||||||
|
const map = new Map();
|
||||||
|
for (const material of materiaisQuery.data) {
|
||||||
|
map.set(material._id, material);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
});
|
||||||
|
|
||||||
let notice = $state<{ kind: 'success' | 'error'; text: string } | null>(null);
|
let notice = $state<{ kind: 'success' | 'error'; text: string } | null>(null);
|
||||||
|
|
||||||
function mostrarMensagem(kind: 'success' | 'error', text: string) {
|
function mostrarMensagem(kind: 'success' | 'error', text: string) {
|
||||||
@@ -151,15 +171,10 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="container mx-auto px-4 py-4">
|
<main class="container mx-auto px-4 py-6">
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
<div class="breadcrumbs mb-4 text-sm">
|
<div class="breadcrumbs mb-6 text-sm">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
|
||||||
<a href={resolve('/recursos-humanos')} class="text-primary hover:underline"
|
|
||||||
>Recursos Humanos</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<a href={resolve('/almoxarifado')} class="text-primary hover:underline"
|
<a href={resolve('/almoxarifado')} class="text-primary hover:underline"
|
||||||
>Almoxarifado</a
|
>Almoxarifado</a
|
||||||
@@ -170,14 +185,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Cabeçalho -->
|
<!-- Cabeçalho -->
|
||||||
<div class="mb-6">
|
<div class="mb-8">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<div class="rounded-xl bg-info/20 p-3">
|
<div class="rounded-2xl bg-gradient-to-br from-info/20 to-info/30 p-4 shadow-lg">
|
||||||
<ArrowLeftRight class="h-8 w-8 text-info" strokeWidth={2} />
|
<ArrowLeftRight class="h-10 w-10 text-info" strokeWidth={2.5} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold">Movimentações de Estoque</h1>
|
<h1 class="text-3xl font-bold tracking-tight">Movimentações de Estoque</h1>
|
||||||
<p class="text-base-content/70">Registre entradas, saídas e ajustes de estoque</p>
|
<p class="text-base-content/70 text-lg">Registre entradas, saídas e ajustes de estoque</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -190,30 +205,30 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Abas -->
|
<!-- Abas -->
|
||||||
<div class="tabs tabs-boxed mb-6">
|
<div class="tabs tabs-boxed mb-6 bg-base-200 shadow-md">
|
||||||
<button
|
<button
|
||||||
class="tab {abaAtiva === 'entrada' ? 'tab-active' : ''}"
|
class="tab {abaAtiva === 'entrada' ? 'tab-active' : ''} transition-all"
|
||||||
onclick={() => (abaAtiva = 'entrada')}
|
onclick={() => (abaAtiva = 'entrada')}
|
||||||
>
|
>
|
||||||
<ArrowDown class="h-5 w-5 mr-2" />
|
<ArrowDown class="h-5 w-5 mr-2" />
|
||||||
Entrada
|
Entrada
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="tab {abaAtiva === 'saida' ? 'tab-active' : ''}"
|
class="tab {abaAtiva === 'saida' ? 'tab-active' : ''} transition-all"
|
||||||
onclick={() => (abaAtiva = 'saida')}
|
onclick={() => (abaAtiva = 'saida')}
|
||||||
>
|
>
|
||||||
<ArrowUp class="h-5 w-5 mr-2" />
|
<ArrowUp class="h-5 w-5 mr-2" />
|
||||||
Saída
|
Saída
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="tab {abaAtiva === 'ajuste' ? 'tab-active' : ''}"
|
class="tab {abaAtiva === 'ajuste' ? 'tab-active' : ''} transition-all"
|
||||||
onclick={() => (abaAtiva = 'ajuste')}
|
onclick={() => (abaAtiva = 'ajuste')}
|
||||||
>
|
>
|
||||||
<Settings class="h-5 w-5 mr-2" />
|
<Settings class="h-5 w-5 mr-2" />
|
||||||
Ajuste
|
Ajuste
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="tab {abaAtiva === 'historico' ? 'tab-active' : ''}"
|
class="tab {abaAtiva === 'historico' ? 'tab-active' : ''} transition-all"
|
||||||
onclick={() => (abaAtiva = 'historico')}
|
onclick={() => (abaAtiva = 'historico')}
|
||||||
>
|
>
|
||||||
<History class="h-5 w-5 mr-2" />
|
<History class="h-5 w-5 mr-2" />
|
||||||
@@ -223,9 +238,14 @@
|
|||||||
|
|
||||||
<!-- Conteúdo das Abas -->
|
<!-- Conteúdo das Abas -->
|
||||||
{#if abaAtiva === 'entrada'}
|
{#if abaAtiva === 'entrada'}
|
||||||
<div class="card bg-base-100 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title mb-4">Registrar Entrada de Material</h2>
|
<h2 class="card-title mb-6 text-xl">
|
||||||
|
<div class="rounded-lg bg-success/20 p-2">
|
||||||
|
<ArrowDown class="h-6 w-6 text-success" />
|
||||||
|
</div>
|
||||||
|
Registrar Entrada de Material
|
||||||
|
</h2>
|
||||||
<form onsubmit={(e) => { e.preventDefault(); registrarEntrada(); }}>
|
<form onsubmit={(e) => { e.preventDefault(); registrarEntrada(); }}>
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<div class="form-control md:col-span-2">
|
<div class="form-control md:col-span-2">
|
||||||
@@ -310,9 +330,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else if abaAtiva === 'saida'}
|
{:else if abaAtiva === 'saida'}
|
||||||
<div class="card bg-base-100 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title mb-4">Registrar Saída de Material</h2>
|
<h2 class="card-title mb-6 text-xl">
|
||||||
|
<div class="rounded-lg bg-error/20 p-2">
|
||||||
|
<ArrowUp class="h-6 w-6 text-error" />
|
||||||
|
</div>
|
||||||
|
Registrar Saída de Material
|
||||||
|
</h2>
|
||||||
<form onsubmit={(e) => { e.preventDefault(); registrarSaida(); }}>
|
<form onsubmit={(e) => { e.preventDefault(); registrarSaida(); }}>
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<div class="form-control md:col-span-2">
|
<div class="form-control md:col-span-2">
|
||||||
@@ -413,12 +438,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else if abaAtiva === 'ajuste'}
|
{:else if abaAtiva === 'ajuste'}
|
||||||
<div class="card bg-base-100 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title mb-4">Ajustar Estoque</h2>
|
<h2 class="card-title mb-6 text-xl">
|
||||||
<div class="alert alert-warning mb-4">
|
<div class="rounded-lg bg-warning/20 p-2">
|
||||||
|
<Settings class="h-6 w-6 text-warning" />
|
||||||
|
</div>
|
||||||
|
Ajustar Estoque
|
||||||
|
</h2>
|
||||||
|
<div class="alert alert-warning mb-6 shadow-lg">
|
||||||
<Settings class="h-6 w-6" />
|
<Settings class="h-6 w-6" />
|
||||||
<span>Ajustes de estoque devem ser justificados e são registrados no histórico.</span>
|
<span class="font-medium">Ajustes de estoque devem ser justificados e são registrados no histórico.</span>
|
||||||
</div>
|
</div>
|
||||||
<form onsubmit={(e) => { e.preventDefault(); ajustarEstoque(); }}>
|
<form onsubmit={(e) => { e.preventDefault(); ajustarEstoque(); }}>
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
@@ -491,50 +521,82 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else if abaAtiva === 'historico'}
|
{:else if abaAtiva === 'historico'}
|
||||||
<div class="card bg-base-100 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title mb-4">Histórico de Movimentações</h2>
|
<h2 class="card-title mb-6 text-xl">
|
||||||
|
<div class="rounded-lg bg-info/20 p-2">
|
||||||
|
<History class="h-6 w-6 text-info" />
|
||||||
|
</div>
|
||||||
|
Histórico de Movimentações
|
||||||
|
</h2>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="table">
|
<table class="table table-zebra">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr class="bg-base-200">
|
||||||
<th>Data</th>
|
<th class="font-semibold">Data</th>
|
||||||
<th>Material</th>
|
<th class="font-semibold">Material</th>
|
||||||
<th>Tipo</th>
|
<th class="font-semibold">Tipo</th>
|
||||||
<th>Quantidade</th>
|
<th class="font-semibold">Quantidade</th>
|
||||||
<th>Anterior</th>
|
<th class="font-semibold">Anterior</th>
|
||||||
<th>Nova</th>
|
<th class="font-semibold">Nova</th>
|
||||||
<th>Motivo</th>
|
<th class="font-semibold">Funcionário</th>
|
||||||
|
<th class="font-semibold">Motivo</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#if movimentacoesQuery.data && movimentacoesQuery.data.length > 0}
|
{#if movimentacoesQuery.data && movimentacoesQuery.data.length > 0}
|
||||||
{#each movimentacoesQuery.data.slice(0, 50) as mov}
|
{#each movimentacoesQuery.data.slice(0, 50) as mov}
|
||||||
{@const material = materiaisQuery.data?.find(m => m._id === mov.materialId)}
|
{@const material = materiaisMap.get(mov.materialId)}
|
||||||
<tr>
|
{@const funcionario = mov.funcionarioId ? funcionariosMap.get(mov.funcionarioId) : null}
|
||||||
<td>{new Date(mov.data).toLocaleString('pt-BR')}</td>
|
<tr class="hover:bg-base-200/50 transition-colors">
|
||||||
<td>{material?.nome || 'Carregando...'}</td>
|
|
||||||
<td>
|
<td>
|
||||||
{#if mov.tipo === 'entrada'}
|
<span class="text-sm">{new Date(mov.data).toLocaleString('pt-BR')}</span>
|
||||||
<span class="badge badge-success">Entrada</span>
|
</td>
|
||||||
{:else if mov.tipo === 'saida'}
|
<td>
|
||||||
<span class="badge badge-error">Saída</span>
|
<div class="font-medium">{material?.nome || 'Carregando...'}</div>
|
||||||
{:else}
|
{#if material?.codigo}
|
||||||
<span class="badge badge-warning">Ajuste</span>
|
<div class="text-xs text-base-content/50 font-mono">{material.codigo}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
<td>{mov.quantidade}</td>
|
<td>
|
||||||
<td>{mov.quantidadeAnterior}</td>
|
{#if mov.tipo === 'entrada'}
|
||||||
<td>{mov.quantidadeNova}</td>
|
<span class="badge badge-success badge-lg">Entrada</span>
|
||||||
<td>{mov.motivo}</td>
|
{:else if mov.tipo === 'saida'}
|
||||||
|
<span class="badge badge-error badge-lg">Saída</span>
|
||||||
|
{:else}
|
||||||
|
<span class="badge badge-warning badge-lg">Ajuste</span>
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="font-bold">{mov.quantidade}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="text-base-content/70">{mov.quantidadeAnterior}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="font-semibold">{mov.quantidadeNova}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{#if funcionario}
|
||||||
|
<div class="font-medium">{funcionario.nome}</div>
|
||||||
|
{#if funcionario.matricula}
|
||||||
|
<div class="text-xs text-base-content/50">Mat: {funcionario.matricula}</div>
|
||||||
|
{/if}
|
||||||
|
{:else}
|
||||||
|
<span class="text-base-content/50 text-sm italic">—</span>
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="text-sm">{mov.motivo}</span>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
{:else}
|
{:else}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="7" class="text-center">
|
<td colspan="8" class="text-center">
|
||||||
<div class="py-8">
|
<div class="py-12">
|
||||||
<History class="mx-auto mb-4 h-12 w-12 text-base-content/30" />
|
<History class="mx-auto mb-4 h-16 w-16 text-base-content/30" />
|
||||||
<p class="text-base-content/70">Nenhuma movimentação registrada</p>
|
<p class="text-base-content/70 text-lg font-medium">Nenhuma movimentação registrada</p>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -51,15 +51,10 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="container mx-auto px-4 py-4">
|
<main class="container mx-auto px-4 py-6">
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
<div class="breadcrumbs mb-4 text-sm">
|
<div class="breadcrumbs mb-6 text-sm">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
|
||||||
<a href={resolve('/recursos-humanos')} class="text-primary hover:underline"
|
|
||||||
>Recursos Humanos</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<a href={resolve('/almoxarifado')} class="text-primary hover:underline"
|
<a href={resolve('/almoxarifado')} class="text-primary hover:underline"
|
||||||
>Almoxarifado</a
|
>Almoxarifado</a
|
||||||
@@ -70,62 +65,78 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Cabeçalho -->
|
<!-- Cabeçalho -->
|
||||||
<div class="mb-6">
|
<div class="mb-8">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<div class="rounded-xl bg-success/20 p-3">
|
<div class="rounded-2xl bg-gradient-to-br from-success/20 to-success/30 p-4 shadow-lg">
|
||||||
<BarChart3 class="h-8 w-8 text-success" strokeWidth={2} />
|
<BarChart3 class="h-10 w-10 text-success" strokeWidth={2.5} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold">Relatórios</h1>
|
<h1 class="text-3xl font-bold tracking-tight">Relatórios</h1>
|
||||||
<p class="text-base-content/70">Estatísticas e relatórios do almoxarifado</p>
|
<p class="text-base-content/70 text-lg">Estatísticas e relatórios do almoxarifado</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Estatísticas Gerais -->
|
<!-- Estatísticas Gerais -->
|
||||||
{#if statsQuery.data}
|
{#if statsQuery.data}
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-4 mb-8">
|
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4 mb-8">
|
||||||
<div class="stats bg-base-100 shadow-lg">
|
<div class="card bg-gradient-to-br from-primary/10 via-primary/5 to-base-100 border border-primary/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
|
||||||
<div class="stat">
|
<div class="card-body">
|
||||||
<div class="stat-figure text-primary">
|
<div class="flex items-center justify-between">
|
||||||
<Package class="h-8 w-8" />
|
<div class="flex-1">
|
||||||
|
<div class="text-sm font-medium text-base-content/60 mb-1">Total de Materiais</div>
|
||||||
|
<div class="text-3xl font-bold text-primary mb-1">{statsQuery.data.totalMateriais}</div>
|
||||||
|
<div class="text-xs text-base-content/50">Cadastrados no sistema</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-xl bg-primary/20 p-3">
|
||||||
|
<Package class="h-8 w-8 text-primary" strokeWidth={2.5} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-title">Total de Materiais</div>
|
|
||||||
<div class="stat-value text-primary">{statsQuery.data.totalMateriais}</div>
|
|
||||||
<div class="stat-desc">Cadastrados no sistema</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stats bg-base-100 shadow-lg">
|
<div class="card bg-gradient-to-br from-success/10 via-success/5 to-base-100 border border-success/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
|
||||||
<div class="stat">
|
<div class="card-body">
|
||||||
<div class="stat-figure text-success">
|
<div class="flex items-center justify-between">
|
||||||
<CheckCircle class="h-8 w-8" />
|
<div class="flex-1">
|
||||||
|
<div class="text-sm font-medium text-base-content/60 mb-1">Materiais Ativos</div>
|
||||||
|
<div class="text-3xl font-bold text-success mb-1">{statsQuery.data.totalMateriaisAtivos}</div>
|
||||||
|
<div class="text-xs text-base-content/50">Em estoque</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-xl bg-success/20 p-3">
|
||||||
|
<CheckCircle class="h-8 w-8 text-success" strokeWidth={2.5} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-title">Materiais Ativos</div>
|
|
||||||
<div class="stat-value text-success">{statsQuery.data.totalMateriaisAtivos}</div>
|
|
||||||
<div class="stat-desc">Em estoque</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stats bg-base-100 shadow-lg">
|
<div class="card bg-gradient-to-br from-warning/10 via-warning/5 to-base-100 border border-warning/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
|
||||||
<div class="stat">
|
<div class="card-body">
|
||||||
<div class="stat-figure text-warning">
|
<div class="flex items-center justify-between">
|
||||||
<AlertTriangle class="h-8 w-8" />
|
<div class="flex-1">
|
||||||
|
<div class="text-sm font-medium text-base-content/60 mb-1">Alertas Ativos</div>
|
||||||
|
<div class="text-3xl font-bold text-warning mb-1">{statsQuery.data.totalAlertasAtivos}</div>
|
||||||
|
<div class="text-xs text-base-content/50">Estoque baixo</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-xl bg-warning/20 p-3">
|
||||||
|
<AlertTriangle class="h-8 w-8 text-warning" strokeWidth={2.5} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-title">Alertas Ativos</div>
|
|
||||||
<div class="stat-value text-warning">{statsQuery.data.totalAlertasAtivos}</div>
|
|
||||||
<div class="stat-desc">Estoque baixo</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stats bg-base-100 shadow-lg">
|
<div class="card bg-gradient-to-br from-info/10 via-info/5 to-base-100 border border-info/20 shadow-xl hover:shadow-2xl transition-all duration-300 hover:scale-105">
|
||||||
<div class="stat">
|
<div class="card-body">
|
||||||
<div class="stat-figure text-info">
|
<div class="flex items-center justify-between">
|
||||||
<ArrowLeftRight class="h-8 w-8" />
|
<div class="flex-1">
|
||||||
|
<div class="text-sm font-medium text-base-content/60 mb-1">Movimentações</div>
|
||||||
|
<div class="text-3xl font-bold text-info mb-1">{statsQuery.data.movimentacoesMes}</div>
|
||||||
|
<div class="text-xs text-base-content/50">Este mês</div>
|
||||||
|
</div>
|
||||||
|
<div class="rounded-xl bg-info/20 p-3">
|
||||||
|
<ArrowLeftRight class="h-8 w-8 text-info" strokeWidth={2.5} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-title">Movimentações</div>
|
|
||||||
<div class="stat-value text-info">{statsQuery.data.movimentacoesMes}</div>
|
|
||||||
<div class="stat-desc">Este mês</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -134,7 +145,7 @@
|
|||||||
<!-- Relatórios Disponíveis -->
|
<!-- Relatórios Disponíveis -->
|
||||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||||
<!-- Relatório de Materiais por Categoria -->
|
<!-- Relatório de Materiais por Categoria -->
|
||||||
<div class="card bg-base-100 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h2 class="card-title">Materiais por Categoria</h2>
|
<h2 class="card-title">Materiais por Categoria</h2>
|
||||||
@@ -171,7 +182,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Movimentações do Mês -->
|
<!-- Movimentações do Mês -->
|
||||||
<div class="card bg-base-100 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h2 class="card-title">Movimentações do Mês</h2>
|
<h2 class="card-title">Movimentações do Mês</h2>
|
||||||
@@ -209,7 +220,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Materiais com Estoque Baixo -->
|
<!-- Materiais com Estoque Baixo -->
|
||||||
<div class="card bg-base-100 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h2 class="card-title">Materiais com Estoque Baixo</h2>
|
<h2 class="card-title">Materiais com Estoque Baixo</h2>
|
||||||
@@ -264,7 +275,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Alertas Recentes -->
|
<!-- Alertas Recentes -->
|
||||||
<div class="card bg-base-100 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h2 class="card-title">Alertas Recentes</h2>
|
<h2 class="card-title">Alertas Recentes</h2>
|
||||||
|
|||||||
@@ -184,15 +184,10 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="container mx-auto px-4 py-4">
|
<main class="container mx-auto px-4 py-6">
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
<div class="breadcrumbs mb-4 text-sm">
|
<div class="breadcrumbs mb-6 text-sm">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
|
||||||
<a href={resolve('/recursos-humanos')} class="text-primary hover:underline"
|
|
||||||
>Recursos Humanos</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<a href={resolve('/almoxarifado')} class="text-primary hover:underline"
|
<a href={resolve('/almoxarifado')} class="text-primary hover:underline"
|
||||||
>Almoxarifado</a
|
>Almoxarifado</a
|
||||||
@@ -203,18 +198,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Cabeçalho -->
|
<!-- Cabeçalho -->
|
||||||
<div class="mb-6">
|
<div class="mb-8">
|
||||||
<div class="flex flex-col items-start justify-between gap-4 sm:flex-row sm:items-center">
|
<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="flex items-center gap-4">
|
||||||
<div class="rounded-xl bg-purple-500/20 p-3">
|
<div class="rounded-2xl bg-gradient-to-br from-purple-500/20 to-purple-600/30 p-4 shadow-lg">
|
||||||
<ClipboardList class="h-8 w-8 text-purple-600" strokeWidth={2} />
|
<ClipboardList class="h-10 w-10 text-purple-600" strokeWidth={2.5} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold">Requisições de Material</h1>
|
<h1 class="text-3xl font-bold tracking-tight">Requisições de Material</h1>
|
||||||
<p class="text-base-content/70">Gerencie requisições de material dos funcionários</p>
|
<p class="text-base-content/70 text-lg">Gerencie requisições de material dos funcionários</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary" onclick={abrirModalNova}>
|
<button class="btn btn-primary shadow-lg hover:shadow-xl transition-all" onclick={abrirModalNova}>
|
||||||
<Plus class="h-5 w-5" />
|
<Plus class="h-5 w-5" />
|
||||||
Nova Requisição
|
Nova Requisição
|
||||||
</button>
|
</button>
|
||||||
@@ -229,11 +224,12 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Filtros -->
|
<!-- Filtros -->
|
||||||
<div class="card bg-base-100 mb-6 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 mb-6 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
<h3 class="text-lg font-semibold mb-4">Filtros</h3>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text">Filtrar por Status</span>
|
<span class="label-text font-medium">Filtrar por Status</span>
|
||||||
</label>
|
</label>
|
||||||
<select class="select select-bordered" bind:value={filtroStatus}>
|
<select class="select select-bordered" bind:value={filtroStatus}>
|
||||||
<option value="">Todos</option>
|
<option value="">Todos</option>
|
||||||
@@ -248,27 +244,28 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Lista de Requisições -->
|
<!-- Lista de Requisições -->
|
||||||
<div class="card bg-base-100 shadow-xl">
|
<div class="card bg-base-100 border border-base-300 shadow-xl">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="table">
|
<table class="table table-zebra">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr class="bg-base-200">
|
||||||
<th>Número</th>
|
<th class="font-semibold">Número</th>
|
||||||
<th>Solicitante</th>
|
<th class="font-semibold">Solicitante</th>
|
||||||
<th>Setor</th>
|
<th class="font-semibold">Setor</th>
|
||||||
<th>Status</th>
|
<th class="font-semibold">Status</th>
|
||||||
<th>Data</th>
|
<th class="font-semibold">Data</th>
|
||||||
<th>Ações</th>
|
<th class="font-semibold">Ações</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#if requisicoes.length === 0}
|
{#if requisicoes.length === 0}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="6" class="text-center">
|
<td colspan="6" class="text-center">
|
||||||
<div class="py-8">
|
<div class="py-12">
|
||||||
<ClipboardList class="mx-auto mb-4 h-12 w-12 text-base-content/30" />
|
<ClipboardList class="mx-auto mb-4 h-16 w-16 text-base-content/30" />
|
||||||
<p class="text-base-content/70">Nenhuma requisição encontrada</p>
|
<p class="text-base-content/70 text-lg font-medium">Nenhuma requisição encontrada</p>
|
||||||
|
<p class="text-base-content/50 text-sm mt-2">Tente ajustar os filtros ou criar uma nova requisição</p>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -276,18 +273,24 @@
|
|||||||
{#each requisicoes as requisicao}
|
{#each requisicoes as requisicao}
|
||||||
{@const solicitante = funcionariosQuery.data?.find(f => f._id === requisicao.solicitanteId)}
|
{@const solicitante = funcionariosQuery.data?.find(f => f._id === requisicao.solicitanteId)}
|
||||||
{@const setor = setoresQuery.data?.find(s => s._id === requisicao.setorId)}
|
{@const setor = setoresQuery.data?.find(s => s._id === requisicao.setorId)}
|
||||||
<tr>
|
<tr class="hover:bg-base-200/50 transition-colors">
|
||||||
<td>
|
<td>
|
||||||
<div class="font-mono font-bold">{requisicao.numero}</div>
|
<div class="font-mono font-bold text-primary">{requisicao.numero}</div>
|
||||||
</td>
|
</td>
|
||||||
<td>{solicitante?.nome || 'Carregando...'}</td>
|
|
||||||
<td>{setor?.nome || 'Carregando...'}</td>
|
|
||||||
<td>
|
<td>
|
||||||
<span class="badge {getStatusBadge(requisicao.status)}">
|
<div class="font-medium">{solicitante?.nome || 'Carregando...'}</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge badge-ghost">{setor?.nome || 'Carregando...'}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge {getStatusBadge(requisicao.status)} badge-lg">
|
||||||
{getStatusLabel(requisicao.status)}
|
{getStatusLabel(requisicao.status)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>{new Date(requisicao.criadoEm).toLocaleDateString('pt-BR')}</td>
|
<td>
|
||||||
|
<span class="text-sm">{new Date(requisicao.criadoEm).toLocaleDateString('pt-BR')}</span>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
{#if requisicao.status === 'pendente'}
|
{#if requisicao.status === 'pendente'}
|
||||||
@@ -321,8 +324,11 @@
|
|||||||
<!-- Modal Nova Requisição -->
|
<!-- Modal Nova Requisição -->
|
||||||
{#if showModalNova}
|
{#if showModalNova}
|
||||||
<div class="modal modal-open">
|
<div class="modal modal-open">
|
||||||
<div class="modal-box max-w-4xl">
|
<div class="modal-box max-w-4xl border border-base-300 shadow-2xl">
|
||||||
<h3 class="text-lg font-bold">Nova Requisição de Material</h3>
|
<h3 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
||||||
|
<ClipboardList class="h-6 w-6 text-primary" />
|
||||||
|
Nova Requisição de Material
|
||||||
|
</h3>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 mt-4">
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 mt-4">
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
|
|||||||
Reference in New Issue
Block a user