update deploy ci

This commit is contained in:
2026-01-12 10:47:18 -03:00
parent 8eb6bfee10
commit 4551adf64f
2 changed files with 403 additions and 356 deletions

View File

@@ -1,37 +1,36 @@
name: Build Docker images name: Build and Deploy Docker images
on: on:
push: push:
branches: ["master"] branches: ['master']
pull_request:
branches: ['master']
jobs: jobs:
build-and-push-dockerfile-image: build-and-push-dockerfile-image:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Log in to Docker Hub - name: Log in to Docker Hub
# Only login if we are actually going to push
if: github.event_name == 'push'
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} # Make sure to add the secrets in your repository in -> Settings -> Secrets (Actions) -> New repository secret username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} # Make sure to add the secrets in your repository in -> Settings -> Secrets (Actions) -> New repository secret password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@v4 uses: docker/build-push-action@v4
with: with:
context: . context: .
file: ./apps/web/Dockerfile file: ./apps/web/Dockerfile
push: true # Only push on 'push' event (merge to master), not on 'pull_request'
# Make sure to replace with your own namespace and repository push: ${{ github.event_name == 'push' }}
tags: | tags: killercf/sgc:latest
killercf/sgc:latest
platforms: linux/amd64 platforms: linux/amd64
build-args: | build-args: |
PUBLIC_CONVEX_URL=${{ secrets.PUBLIC_CONVEX_URL }} PUBLIC_CONVEX_URL=${{ secrets.PUBLIC_CONVEX_URL }}
PUBLIC_CONVEX_SITE_URL=${{ secrets.PUBLIC_CONVEX_SITE_URL }} PUBLIC_CONVEX_SITE_URL=${{ secrets.PUBLIC_CONVEX_SITE_URL }}

View File

@@ -13,7 +13,6 @@
Package, Package,
AlertTriangle, AlertTriangle,
ArrowLeftRight, ArrowLeftRight,
Download,
CheckCircle, CheckCircle,
ArrowDown, ArrowDown,
ArrowUp, ArrowUp,
@@ -21,6 +20,7 @@
FileText, FileText,
FileSpreadsheet FileSpreadsheet
} from 'lucide-svelte'; } from 'lucide-svelte';
import { SvelteDate } from 'svelte/reactivity';
const statsQuery = useQuery(api.almoxarifado.obterEstatisticas, {}); const statsQuery = useQuery(api.almoxarifado.obterEstatisticas, {});
const materiaisQuery = useQuery(api.almoxarifado.listarMateriais, {}); const materiaisQuery = useQuery(api.almoxarifado.listarMateriais, {});
@@ -31,7 +31,7 @@
let tipoRelatorioGerando = $state<string | null>(null); let tipoRelatorioGerando = $state<string | null>(null);
// Agrupar materiais por categoria // Agrupar materiais por categoria
const materiaisPorCategoria = $derived(() => { const materiaisPorCategoria = $derived.by(() => {
if (!materiaisQuery.data) return {}; if (!materiaisQuery.data) return {};
const agrupado: Record<string, number> = {}; const agrupado: Record<string, number> = {};
materiaisQuery.data.forEach((m) => { materiaisQuery.data.forEach((m) => {
@@ -41,10 +41,10 @@
}); });
// Movimentações do mês // Movimentações do mês
const movimentacoesMes = $derived(() => { const movimentacoesMes = $derived.by(() => {
if (!movimentacoesQuery.data) return { entrada: 0, saida: 0, ajuste: 0 }; if (!movimentacoesQuery.data) return { entrada: 0, saida: 0, ajuste: 0 };
const agora = Date.now(); const agora = Date.now();
const inicioMes = new Date(agora); const inicioMes = new SvelteDate(agora);
inicioMes.setDate(1); inicioMes.setDate(1);
inicioMes.setHours(0, 0, 0, 0); inicioMes.setHours(0, 0, 0, 0);
@@ -124,7 +124,10 @@
if (ctx) { if (ctx) {
ctx.drawImage(logoImg, 0, 0); ctx.drawImage(logoImg, 0, 0);
const blob = await new Promise<Blob>((resolve, reject) => { const blob = await new Promise<Blob>((resolve, reject) => {
canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('Falha ao converter'))), 'image/png'); canvas.toBlob(
(b) => (b ? resolve(b) : reject(new Error('Falha ao converter'))),
'image/png'
);
}); });
return await blob.arrayBuffer(); return await blob.arrayBuffer();
} }
@@ -257,7 +260,10 @@
yPos += 15; yPos += 15;
const dados = materiaisPorCategoria; const dados = materiaisPorCategoria;
const dadosArray = Object.entries(dados).map(([categoria, quantidade]) => [categoria, String(quantidade)]); const dadosArray = Object.entries(dados).map(([categoria, quantidade]) => [
categoria,
String(quantidade)
]);
if (dadosArray.length === 0) { if (dadosArray.length === 0) {
doc.text('Nenhum dado disponível', 14, yPos); doc.text('Nenhum dado disponível', 14, yPos);
@@ -532,7 +538,12 @@
{ header: 'Estoque Mínimo', key: 'estoqueMinimo', width: 18 } { header: 'Estoque Mínimo', key: 'estoqueMinimo', width: 18 }
]; ];
await adicionarTituloExcel(worksheet, 'RELATÓRIO DE MATERIAIS COM ESTOQUE BAIXO', 4, workbook); await adicionarTituloExcel(
worksheet,
'RELATÓRIO DE MATERIAIS COM ESTOQUE BAIXO',
4,
workbook
);
const headerRow = worksheet.getRow(2); const headerRow = worksheet.getRow(2);
headerRow.values = ['Código', 'Material', 'Estoque Atual', 'Estoque Mínimo']; headerRow.values = ['Código', 'Material', 'Estoque Atual', 'Estoque Mínimo'];
@@ -751,9 +762,7 @@
<div class="breadcrumbs mb-6 text-sm"> <div class="breadcrumbs mb-6 text-sm">
<ul> <ul>
<li> <li>
<a href={resolve('/almoxarifado')} class="text-primary hover:underline" <a href={resolve('/almoxarifado')} class="text-primary hover:underline">Almoxarifado</a>
>Almoxarifado</a
>
</li> </li>
<li>Relatórios</li> <li>Relatórios</li>
</ul> </ul>
@@ -762,14 +771,18 @@
<!-- Cabeçalho --> <!-- Cabeçalho -->
<div class="mb-8"> <div class="mb-8">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<div class="rounded-2xl bg-gradient-to-br from-primary/20 via-primary/10 to-primary/5 p-4 shadow-lg border border-primary/20"> <div
<BarChart3 class="h-10 w-10 text-primary" strokeWidth={2.5} /> class="from-primary/20 via-primary/10 to-primary/5 border-primary/20 rounded-2xl border bg-linear-to-br p-4 shadow-lg"
>
<BarChart3 class="text-primary h-10 w-10" strokeWidth={2.5} />
</div> </div>
<div class="flex-1"> <div class="flex-1">
<h1 class="text-4xl font-bold tracking-tight bg-gradient-to-r from-primary to-primary/70 bg-clip-text text-transparent"> <h1
class="from-primary to-primary/70 bg-linear-to-r bg-clip-text text-4xl font-bold tracking-tight text-transparent"
>
Relatórios Relatórios
</h1> </h1>
<p class="text-base-content/70 text-lg mt-1">Estatísticas e relatórios do almoxarifado</p> <p class="text-base-content/70 mt-1 text-lg">Estatísticas e relatórios do almoxarifado</p>
</div> </div>
</div> </div>
</div> </div>
@@ -777,379 +790,414 @@
<!-- Estatísticas Gerais --> <!-- Estatísticas Gerais -->
{#if statsQuery.data} {#if statsQuery.data}
<div class="mb-10"> <div class="mb-10">
<div class="mb-6 flex items-center gap-3 border-b-2 border-primary/20 pb-4"> <div class="border-primary/20 mb-6 flex items-center gap-3 border-b-2 pb-4">
<div class="rounded-lg bg-primary/10 p-2.5"> <div class="bg-primary/10 rounded-lg p-2.5">
<BarChart3 class="h-5 w-5 text-primary" strokeWidth={2.5} /> <BarChart3 class="text-primary h-5 w-5" strokeWidth={2.5} />
</div> </div>
<h2 class="text-xl font-bold text-base-content">Estatísticas Gerais</h2> <h2 class="text-base-content text-xl font-bold">Estatísticas Gerais</h2>
</div> </div>
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4"> <div class="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
<div class="card-body"> class="card from-primary/10 via-primary/5 to-base-100 border-primary/20 border bg-linear-to-br shadow-xl transition-all duration-300 hover:scale-105 hover:shadow-2xl"
<div class="flex items-center justify-between"> >
<div class="flex-1"> <div class="card-body">
<div class="text-sm font-medium text-base-content/60 mb-1">Total de Materiais</div> <div class="flex items-center justify-between">
<div class="text-3xl font-bold text-primary mb-1">{statsQuery.data.totalMateriais}</div> <div class="flex-1">
<div class="text-xs text-base-content/50">Cadastrados no sistema</div> <div class="text-base-content/60 mb-1 text-sm font-medium">Total de Materiais</div>
</div> <div class="text-primary mb-1 text-3xl font-bold">
<div class="rounded-xl bg-primary/20 p-3"> {statsQuery.data.totalMateriais}
<Package class="h-8 w-8 text-primary" strokeWidth={2.5} /> </div>
<div class="text-base-content/50 text-xs">Cadastrados no sistema</div>
</div>
<div class="bg-primary/20 rounded-xl p-3">
<Package class="text-primary h-8 w-8" strokeWidth={2.5} />
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<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
<div class="card-body"> class="card from-success/10 via-success/5 to-base-100 border-success/20 border bg-linear-to-br shadow-xl transition-all duration-300 hover:scale-105 hover:shadow-2xl"
<div class="flex items-center justify-between"> >
<div class="flex-1"> <div class="card-body">
<div class="text-sm font-medium text-base-content/60 mb-1">Materiais Ativos</div> <div class="flex items-center justify-between">
<div class="text-3xl font-bold text-success mb-1">{statsQuery.data.totalMateriaisAtivos}</div> <div class="flex-1">
<div class="text-xs text-base-content/50">Em estoque</div> <div class="text-base-content/60 mb-1 text-sm font-medium">Materiais Ativos</div>
</div> <div class="text-success mb-1 text-3xl font-bold">
<div class="rounded-xl bg-success/20 p-3"> {statsQuery.data.totalMateriaisAtivos}
<CheckCircle class="h-8 w-8 text-success" strokeWidth={2.5} /> </div>
<div class="text-base-content/50 text-xs">Em estoque</div>
</div>
<div class="bg-success/20 rounded-xl p-3">
<CheckCircle class="text-success h-8 w-8" strokeWidth={2.5} />
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<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
<div class="card-body"> class="card from-warning/10 via-warning/5 to-base-100 border-warning/20 border bg-linear-to-br shadow-xl transition-all duration-300 hover:scale-105 hover:shadow-2xl"
<div class="flex items-center justify-between"> >
<div class="flex-1"> <div class="card-body">
<div class="text-sm font-medium text-base-content/60 mb-1">Alertas Ativos</div> <div class="flex items-center justify-between">
<div class="text-3xl font-bold text-warning mb-1">{statsQuery.data.totalAlertasAtivos}</div> <div class="flex-1">
<div class="text-xs text-base-content/50">Estoque baixo</div> <div class="text-base-content/60 mb-1 text-sm font-medium">Alertas Ativos</div>
</div> <div class="text-warning mb-1 text-3xl font-bold">
<div class="rounded-xl bg-warning/20 p-3"> {statsQuery.data.totalAlertasAtivos}
<AlertTriangle class="h-8 w-8 text-warning" strokeWidth={2.5} /> </div>
<div class="text-base-content/50 text-xs">Estoque baixo</div>
</div>
<div class="bg-warning/20 rounded-xl p-3">
<AlertTriangle class="text-warning h-8 w-8" strokeWidth={2.5} />
</div>
</div> </div>
</div> </div>
</div> </div>
</div>
<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
<div class="card-body"> class="card from-info/10 via-info/5 to-base-100 border-info/20 border bg-linear-to-br shadow-xl transition-all duration-300 hover:scale-105 hover:shadow-2xl"
<div class="flex items-center justify-between"> >
<div class="flex-1"> <div class="card-body">
<div class="text-sm font-medium text-base-content/60 mb-1">Movimentações</div> <div class="flex items-center justify-between">
<div class="text-3xl font-bold text-info mb-1">{statsQuery.data.movimentacoesMes}</div> <div class="flex-1">
<div class="text-xs text-base-content/50">Este mês</div> <div class="text-base-content/60 mb-1 text-sm font-medium">Movimentações</div>
</div> <div class="text-info mb-1 text-3xl font-bold">
<div class="rounded-xl bg-info/20 p-3"> {statsQuery.data.movimentacoesMes}
<ArrowLeftRight class="h-8 w-8 text-info" strokeWidth={2.5} /> </div>
<div class="text-base-content/50 text-xs">Este mês</div>
</div>
<div class="bg-info/20 rounded-xl p-3">
<ArrowLeftRight class="text-info h-8 w-8" strokeWidth={2.5} />
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
{/if} {/if}
<!-- Relatórios Disponíveis --> <!-- Relatórios Disponíveis -->
<div class="mb-8"> <div class="mb-8">
<div class="mb-6 flex items-center gap-3 border-b-2 border-info/20 pb-4"> <div class="border-info/20 mb-6 flex items-center gap-3 border-b-2 pb-4">
<div class="rounded-lg bg-info/10 p-2.5"> <div class="bg-info/10 rounded-lg p-2.5">
<FileText class="h-5 w-5 text-info" strokeWidth={2.5} /> <FileText class="text-info h-5 w-5" strokeWidth={2.5} />
</div> </div>
<h2 class="text-xl font-bold text-base-content">Relatórios Disponíveis</h2> <h2 class="text-base-content text-xl font-bold">Relatórios Disponíveis</h2>
</div> </div>
<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 border border-base-300 shadow-2xl"> <div class="card bg-base-100 border-base-300 border shadow-2xl">
<div class="card-body p-6"> <div class="card-body p-6">
<div class="flex items-center justify-between mb-6 border-b-2 border-base-300 pb-4"> <div class="border-base-300 mb-6 flex items-center justify-between border-b-2 pb-4">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="rounded-lg bg-primary/10 p-2"> <div class="bg-primary/10 rounded-lg p-2">
<Package class="h-5 w-5 text-primary" strokeWidth={2.5} /> <Package class="text-primary h-5 w-5" strokeWidth={2.5} />
</div>
<h2 class="text-base-content text-lg font-bold">Materiais por Categoria</h2>
</div>
<div class="flex gap-2">
<button
class="btn btn-sm btn-primary shadow-md transition-all hover:shadow-lg"
onclick={gerarPDFMateriaisCategoria}
disabled={gerandoRelatorio}
title="Gerar PDF"
>
{#if gerandoRelatorio && tipoRelatorioGerando === 'materiais-categoria-pdf'}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<FileText class="h-4 w-4" />
{/if}
PDF
</button>
<button
class="btn btn-sm btn-success shadow-md transition-all hover:shadow-lg"
onclick={gerarExcelMateriaisCategoria}
disabled={gerandoRelatorio}
title="Gerar Excel"
>
{#if gerandoRelatorio && tipoRelatorioGerando === 'materiais-categoria-excel'}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<FileSpreadsheet class="h-4 w-4" />
{/if}
Excel
</button>
</div> </div>
<h2 class="text-lg font-bold text-base-content">Materiais por Categoria</h2>
</div> </div>
<div class="flex gap-2"> {#if materiaisQuery.data && Object.keys(materiaisPorCategoria).length > 0}
<button <div class="space-y-3">
class="btn btn-sm btn-primary shadow-md hover:shadow-lg transition-all" {#each Object.entries(materiaisPorCategoria) as [categoria, quantidade] (categoria)}
onclick={gerarPDFMateriaisCategoria} <div
disabled={gerandoRelatorio} class="hover:bg-base-200/50 flex items-center justify-between rounded-lg p-2 transition-colors"
title="Gerar PDF" >
> <span class="text-base-content font-semibold">{categoria}</span>
{#if gerandoRelatorio && tipoRelatorioGerando === 'materiais-categoria-pdf'} <div class="flex items-center gap-3">
<span class="loading loading-spinner loading-xs"></span> <div class="w-40">
{:else} <div class="bg-base-300 h-3 overflow-hidden rounded-full shadow-inner">
<FileText class="h-4 w-4" /> <div
{/if} class="from-primary to-primary/70 h-full rounded-full bg-linear-to-r transition-all"
PDF style="width: {(quantidade / (materiaisQuery.data?.length || 1)) * 100}%"
</button> ></div>
<button </div>
class="btn btn-sm btn-success shadow-md hover:shadow-lg transition-all"
onclick={gerarExcelMateriaisCategoria}
disabled={gerandoRelatorio}
title="Gerar Excel"
>
{#if gerandoRelatorio && tipoRelatorioGerando === 'materiais-categoria-excel'}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<FileSpreadsheet class="h-4 w-4" />
{/if}
Excel
</button>
</div>
</div>
{#if materiaisQuery.data && Object.keys(materiaisPorCategoria).length > 0}
<div class="space-y-3">
{#each Object.entries(materiaisPorCategoria) as [categoria, quantidade]}
<div class="flex items-center justify-between p-2 rounded-lg hover:bg-base-200/50 transition-colors">
<span class="font-semibold text-base-content">{categoria}</span>
<div class="flex items-center gap-3">
<div class="w-40">
<div class="h-3 bg-base-300 rounded-full overflow-hidden shadow-inner">
<div
class="h-full bg-gradient-to-r from-primary to-primary/70 transition-all rounded-full"
style="width: {(quantidade / (materiaisQuery.data?.length || 1)) * 100}%"
></div>
</div> </div>
<span class="text-primary w-12 text-right text-base font-bold"
>{quantidade}</span
>
</div> </div>
<span class="text-base font-bold text-primary w-12 text-right">{quantidade}</span>
</div> </div>
</div> {/each}
{/each}
</div>
{:else}
<div class="alert alert-info border-info/30 bg-info/10">
<Package class="h-5 w-5 text-info" />
<span class="font-medium">Nenhum dado disponível</span>
</div>
{/if}
</div>
</div>
<!-- Movimentações do Mês -->
<div class="card bg-base-100 border border-base-300 shadow-2xl">
<div class="card-body p-6">
<div class="flex items-center justify-between mb-6 border-b-2 border-base-300 pb-4">
<div class="flex items-center gap-3">
<div class="rounded-lg bg-info/10 p-2">
<ArrowLeftRight class="h-5 w-5 text-info" strokeWidth={2.5} />
</div> </div>
<h2 class="text-lg font-bold text-base-content">Movimentações do Mês</h2>
</div>
<div class="flex gap-2">
<button
class="btn btn-sm btn-primary shadow-md hover:shadow-lg transition-all"
onclick={gerarPDFMovimentacoesMes}
disabled={gerandoRelatorio}
title="Gerar PDF"
>
{#if gerandoRelatorio && tipoRelatorioGerando === 'movimentacoes-mes-pdf'}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<FileText class="h-4 w-4" />
{/if}
PDF
</button>
<button
class="btn btn-sm btn-success shadow-md hover:shadow-lg transition-all"
onclick={gerarExcelMovimentacoesMes}
disabled={gerandoRelatorio}
title="Gerar Excel"
>
{#if gerandoRelatorio && tipoRelatorioGerando === 'movimentacoes-mes-excel'}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<FileSpreadsheet class="h-4 w-4" />
{/if}
Excel
</button>
</div>
</div>
<div class="space-y-4">
<div class="flex items-center justify-between p-3 rounded-lg bg-success/10 border border-success/20 hover:bg-success/15 transition-colors">
<div class="flex items-center gap-3">
<div class="rounded-lg bg-success/20 p-2">
<ArrowDown class="h-5 w-5 text-success" strokeWidth={2.5} />
</div>
<span class="font-semibold text-base-content">Entradas</span>
</div>
<span class="text-2xl font-bold text-success">{movimentacoesMes.entrada}</span>
</div>
<div class="flex items-center justify-between p-3 rounded-lg bg-error/10 border border-error/20 hover:bg-error/15 transition-colors">
<div class="flex items-center gap-3">
<div class="rounded-lg bg-error/20 p-2">
<ArrowUp class="h-5 w-5 text-error" strokeWidth={2.5} />
</div>
<span class="font-semibold text-base-content">Saídas</span>
</div>
<span class="text-2xl font-bold text-error">{movimentacoesMes.saida}</span>
</div>
<div class="flex items-center justify-between p-3 rounded-lg bg-warning/10 border border-warning/20 hover:bg-warning/15 transition-colors">
<div class="flex items-center gap-3">
<div class="rounded-lg bg-warning/20 p-2">
<Settings class="h-5 w-5 text-warning" strokeWidth={2.5} />
</div>
<span class="font-semibold text-base-content">Ajustes</span>
</div>
<span class="text-2xl font-bold text-warning">{movimentacoesMes.ajuste}</span>
</div>
</div>
</div>
</div>
<!-- Materiais com Estoque Baixo -->
<div class="card bg-base-100 border border-base-300 shadow-2xl">
<div class="card-body p-6">
<div class="flex items-center justify-between mb-6 border-b-2 border-base-300 pb-4">
<div class="flex items-center gap-3">
<div class="rounded-lg bg-warning/10 p-2">
<AlertTriangle class="h-5 w-5 text-warning" strokeWidth={2.5} />
</div>
<h2 class="text-lg font-bold text-base-content">Materiais com Estoque Baixo</h2>
</div>
<div class="flex gap-2">
<button
class="btn btn-sm btn-primary shadow-md hover:shadow-lg transition-all"
onclick={gerarPDFEstoqueBaixo}
disabled={gerandoRelatorio}
title="Gerar PDF"
>
{#if gerandoRelatorio && tipoRelatorioGerando === 'estoque-baixo-pdf'}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<FileText class="h-4 w-4" />
{/if}
PDF
</button>
<button
class="btn btn-sm btn-success shadow-md hover:shadow-lg transition-all"
onclick={gerarExcelEstoqueBaixo}
disabled={gerandoRelatorio}
title="Gerar Excel"
>
{#if gerandoRelatorio && tipoRelatorioGerando === 'estoque-baixo-excel'}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<FileSpreadsheet class="h-4 w-4" />
{/if}
Excel
</button>
</div>
</div>
{#if materiaisQuery.data}
{@const estoqueBaixo = materiaisQuery.data.filter(m => m.estoqueAtual <= m.estoqueMinimo)}
{#if estoqueBaixo.length > 0}
<div class="overflow-x-auto rounded-lg border border-base-300">
<table class="table table-zebra table-sm">
<thead>
<tr class="bg-base-200">
<th class="font-bold text-base-content">Material</th>
<th class="font-bold text-base-content">Atual</th>
<th class="font-bold text-base-content">Mínimo</th>
</tr>
</thead>
<tbody>
{#each estoqueBaixo.slice(0, 10) as material}
<tr class="hover:bg-base-200/50 transition-colors">
<td>
<div class="font-medium">{material.nome}</div>
<div class="text-xs text-base-content/60 font-mono">{material.codigo}</div>
</td>
<td>
<span class="font-bold text-error">{material.estoqueAtual}</span>
</td>
<td>
<span class="font-medium">{material.estoqueMinimo}</span>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{#if estoqueBaixo.length > 10}
<p class="text-sm text-base-content/70 mt-4 text-center font-medium">
E mais <span class="text-primary font-bold">{estoqueBaixo.length - 10}</span> materiais...
</p>
{/if}
{:else} {:else}
<div class="alert alert-success border-success/30 bg-success/10"> <div class="alert alert-info border-info/30 bg-info/10">
<CheckCircle class="h-6 w-6 text-success" /> <Package class="text-info h-5 w-5" />
<span class="font-medium">Todos os materiais estão com estoque adequado!</span> <span class="font-medium">Nenhum dado disponível</span>
</div> </div>
{/if} {/if}
{/if} </div>
</div> </div>
</div>
<!-- Alertas Recentes --> <!-- Movimentações do Mês -->
<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-6"> <div class="card-body p-6">
<div class="flex items-center justify-between mb-6 border-b-2 border-base-300 pb-4"> <div class="border-base-300 mb-6 flex items-center justify-between border-b-2 pb-4">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="rounded-lg bg-warning/10 p-2"> <div class="bg-info/10 rounded-lg p-2">
<AlertTriangle class="h-5 w-5 text-warning" strokeWidth={2.5} /> <ArrowLeftRight class="text-info h-5 w-5" strokeWidth={2.5} />
</div>
<h2 class="text-base-content text-lg font-bold">Movimentações do Mês</h2>
</div>
<div class="flex gap-2">
<button
class="btn btn-sm btn-primary shadow-md transition-all hover:shadow-lg"
onclick={gerarPDFMovimentacoesMes}
disabled={gerandoRelatorio}
title="Gerar PDF"
>
{#if gerandoRelatorio && tipoRelatorioGerando === 'movimentacoes-mes-pdf'}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<FileText class="h-4 w-4" />
{/if}
PDF
</button>
<button
class="btn btn-sm btn-success shadow-md transition-all hover:shadow-lg"
onclick={gerarExcelMovimentacoesMes}
disabled={gerandoRelatorio}
title="Gerar Excel"
>
{#if gerandoRelatorio && tipoRelatorioGerando === 'movimentacoes-mes-excel'}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<FileSpreadsheet class="h-4 w-4" />
{/if}
Excel
</button>
</div> </div>
<h2 class="text-lg font-bold text-base-content">Alertas Recentes</h2>
</div> </div>
<div class="flex gap-2"> <div class="space-y-4">
<button <div
class="btn btn-sm btn-primary shadow-md hover:shadow-lg transition-all" class="bg-success/10 border-success/20 hover:bg-success/15 flex items-center justify-between rounded-lg border p-3 transition-colors"
onclick={gerarPDFAlertas}
disabled={gerandoRelatorio}
title="Gerar PDF"
> >
{#if gerandoRelatorio && tipoRelatorioGerando === 'alertas-pdf'} <div class="flex items-center gap-3">
<span class="loading loading-spinner loading-xs"></span> <div class="bg-success/20 rounded-lg p-2">
{:else} <ArrowDown class="text-success h-5 w-5" strokeWidth={2.5} />
<FileText class="h-4 w-4" /> </div>
{/if} <span class="text-base-content font-semibold">Entradas</span>
PDF </div>
</button> <span class="text-success text-2xl font-bold">{movimentacoesMes.entrada}</span>
<button </div>
class="btn btn-sm btn-success shadow-md hover:shadow-lg transition-all" <div
onclick={gerarExcelAlertas} class="bg-error/10 border-error/20 hover:bg-error/15 flex items-center justify-between rounded-lg border p-3 transition-colors"
disabled={gerandoRelatorio}
title="Gerar Excel"
> >
{#if gerandoRelatorio && tipoRelatorioGerando === 'alertas-excel'} <div class="flex items-center gap-3">
<span class="loading loading-spinner loading-xs"></span> <div class="bg-error/20 rounded-lg p-2">
{:else} <ArrowUp class="text-error h-5 w-5" strokeWidth={2.5} />
<FileSpreadsheet class="h-4 w-4" /> </div>
{/if} <span class="text-base-content font-semibold">Saídas</span>
Excel </div>
</button> <span class="text-error text-2xl font-bold">{movimentacoesMes.saida}</span>
</div>
<div
class="bg-warning/10 border-warning/20 hover:bg-warning/15 flex items-center justify-between rounded-lg border p-3 transition-colors"
>
<div class="flex items-center gap-3">
<div class="bg-warning/20 rounded-lg p-2">
<Settings class="text-warning h-5 w-5" strokeWidth={2.5} />
</div>
<span class="text-base-content font-semibold">Ajustes</span>
</div>
<span class="text-warning text-2xl font-bold">{movimentacoesMes.ajuste}</span>
</div>
</div> </div>
</div> </div>
{#if alertasQuery.data && alertasQuery.data.length > 0} </div>
<div class="space-y-3">
{#each alertasQuery.data.slice(0, 5) as alerta} <!-- Materiais com Estoque Baixo -->
<div class="flex items-center justify-between p-3 bg-warning/10 rounded-lg border border-warning/20 hover:bg-warning/15 transition-colors"> <div class="card bg-base-100 border-base-300 border shadow-2xl">
<div class="flex-1"> <div class="card-body p-6">
<div class="font-semibold text-sm text-base-content"> <div class="border-base-300 mb-6 flex items-center justify-between border-b-2 pb-4">
{#if materiaisQuery.data} <div class="flex items-center gap-3">
{@const material = materiaisQuery.data.find(m => m._id === alerta.materialId)} <div class="bg-warning/10 rounded-lg p-2">
{material?.nome || 'Carregando...'} <AlertTriangle class="text-warning h-5 w-5" strokeWidth={2.5} />
{/if}
</div>
<div class="text-xs text-base-content/60 mt-1 font-mono">
Estoque: <span class="font-bold text-error">{alerta.quantidadeAtual}</span> / Mínimo: <span class="font-bold">{alerta.quantidadeMinima}</span>
</div>
</div>
<span class="badge badge-warning badge-lg ml-3">
{#if alerta.tipo === 'estoque_zerado'}
Zerado
{:else}
Mínimo
{/if}
</span>
</div> </div>
{/each} <h2 class="text-base-content text-lg font-bold">Materiais com Estoque Baixo</h2>
</div>
<div class="flex gap-2">
<button
class="btn btn-sm btn-primary shadow-md transition-all hover:shadow-lg"
onclick={gerarPDFEstoqueBaixo}
disabled={gerandoRelatorio}
title="Gerar PDF"
>
{#if gerandoRelatorio && tipoRelatorioGerando === 'estoque-baixo-pdf'}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<FileText class="h-4 w-4" />
{/if}
PDF
</button>
<button
class="btn btn-sm btn-success shadow-md transition-all hover:shadow-lg"
onclick={gerarExcelEstoqueBaixo}
disabled={gerandoRelatorio}
title="Gerar Excel"
>
{#if gerandoRelatorio && tipoRelatorioGerando === 'estoque-baixo-excel'}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<FileSpreadsheet class="h-4 w-4" />
{/if}
Excel
</button>
</div>
</div> </div>
{:else} {#if materiaisQuery.data}
<div class="alert alert-success border-success/30 bg-success/10"> {@const estoqueBaixo = materiaisQuery.data.filter(
<CheckCircle class="h-6 w-6 text-success" /> (m) => m.estoqueAtual <= m.estoqueMinimo
<span class="font-medium">Nenhum alerta ativo</span> )}
{#if estoqueBaixo.length > 0}
<div class="border-base-300 overflow-x-auto rounded-lg border">
<table class="table-zebra table-sm table">
<thead>
<tr class="bg-base-200">
<th class="text-base-content font-bold">Material</th>
<th class="text-base-content font-bold">Atual</th>
<th class="text-base-content font-bold">Mínimo</th>
</tr>
</thead>
<tbody>
{#each estoqueBaixo.slice(0, 10) as material (material._id)}
<tr class="hover:bg-base-200/50 transition-colors">
<td>
<div class="font-medium">{material.nome}</div>
<div class="text-base-content/60 font-mono text-xs">
{material.codigo}
</div>
</td>
<td>
<span class="text-error font-bold">{material.estoqueAtual}</span>
</td>
<td>
<span class="font-medium">{material.estoqueMinimo}</span>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
{#if estoqueBaixo.length > 10}
<p class="text-base-content/70 mt-4 text-center text-sm font-medium">
E mais <span class="text-primary font-bold">{estoqueBaixo.length - 10}</span> materiais...
</p>
{/if}
{:else}
<div class="alert alert-success border-success/30 bg-success/10">
<CheckCircle class="text-success h-6 w-6" />
<span class="font-medium">Todos os materiais estão com estoque adequado!</span>
</div>
{/if}
{/if}
</div>
</div>
<!-- Alertas Recentes -->
<div class="card bg-base-100 border-base-300 border shadow-2xl">
<div class="card-body p-6">
<div class="border-base-300 mb-6 flex items-center justify-between border-b-2 pb-4">
<div class="flex items-center gap-3">
<div class="bg-warning/10 rounded-lg p-2">
<AlertTriangle class="text-warning h-5 w-5" strokeWidth={2.5} />
</div>
<h2 class="text-base-content text-lg font-bold">Alertas Recentes</h2>
</div>
<div class="flex gap-2">
<button
class="btn btn-sm btn-primary shadow-md transition-all hover:shadow-lg"
onclick={gerarPDFAlertas}
disabled={gerandoRelatorio}
title="Gerar PDF"
>
{#if gerandoRelatorio && tipoRelatorioGerando === 'alertas-pdf'}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<FileText class="h-4 w-4" />
{/if}
PDF
</button>
<button
class="btn btn-sm btn-success shadow-md transition-all hover:shadow-lg"
onclick={gerarExcelAlertas}
disabled={gerandoRelatorio}
title="Gerar Excel"
>
{#if gerandoRelatorio && tipoRelatorioGerando === 'alertas-excel'}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<FileSpreadsheet class="h-4 w-4" />
{/if}
Excel
</button>
</div>
</div> </div>
{/if} {#if alertasQuery.data && alertasQuery.data.length > 0}
<div class="space-y-3">
{#each alertasQuery.data.slice(0, 5) as alerta (alerta._id)}
<div
class="bg-warning/10 border-warning/20 hover:bg-warning/15 flex items-center justify-between rounded-lg border p-3 transition-colors"
>
<div class="flex-1">
<div class="text-base-content text-sm font-semibold">
{#if materiaisQuery.data}
{@const material = materiaisQuery.data.find(
(m) => m._id === alerta.materialId
)}
{material?.nome || 'Carregando...'}
{/if}
</div>
<div class="text-base-content/60 mt-1 font-mono text-xs">
Estoque: <span class="text-error font-bold">{alerta.quantidadeAtual}</span> /
Mínimo: <span class="font-bold">{alerta.quantidadeMinima}</span>
</div>
</div>
<span class="badge badge-warning badge-lg ml-3">
{#if alerta.tipo === 'estoque_zerado'}
Zerado
{:else}
Mínimo
{/if}
</span>
</div>
{/each}
</div>
{:else}
<div class="alert alert-success border-success/30 bg-success/10">
<CheckCircle class="text-success h-6 w-6" />
<span class="font-medium">Nenhum alerta ativo</span>
</div>
{/if}
</div>
</div> </div>
</div> </div>
</div> </div>
</main> </main>