feat: implement error handling and logging in server hooks to capture and notify on 404 and 500 errors, enhancing server reliability and monitoring
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
<script lang="ts">
|
||||
import { BarChart3, CheckCircle2, XCircle, Users } from 'lucide-svelte';
|
||||
|
||||
interface Estatisticas {
|
||||
totalRegistros: number;
|
||||
dentroDoPrazo: number;
|
||||
foraDoPrazo: number;
|
||||
totalFuncionarios: number;
|
||||
funcionariosDentroPrazo: number;
|
||||
funcionariosForaPrazo: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
estatisticas?: Estatisticas;
|
||||
}
|
||||
|
||||
let { estatisticas = undefined }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if estatisticas}
|
||||
<div class="mb-8 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||
<!-- Total de Registros -->
|
||||
<div
|
||||
class="card transform border border-blue-500/20 bg-gradient-to-br from-blue-500/10 to-blue-600/20 shadow-xl transition-all duration-300 hover:-translate-y-1 hover:shadow-2xl"
|
||||
>
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<p class="text-base-content/70 mb-1 text-sm font-semibold">Total de Registros</p>
|
||||
<p class="text-base-content text-3xl font-bold">{estatisticas.totalRegistros}</p>
|
||||
</div>
|
||||
<div class="rounded-xl bg-blue-500/20 p-3">
|
||||
<BarChart3 class="h-8 w-8 text-blue-600" strokeWidth={2.5} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dentro do Prazo -->
|
||||
<div
|
||||
class="card transform border border-green-500/20 bg-gradient-to-br from-green-500/10 to-green-600/20 shadow-xl transition-all duration-300 hover:-translate-y-1 hover:shadow-2xl"
|
||||
>
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<p class="text-base-content/70 mb-1 text-sm font-semibold">Dentro do Prazo</p>
|
||||
<p class="text-3xl font-bold text-green-600">{estatisticas.dentroDoPrazo}</p>
|
||||
<p class="text-base-content/60 mt-1 text-xs">
|
||||
{estatisticas.totalRegistros > 0
|
||||
? ((estatisticas.dentroDoPrazo / estatisticas.totalRegistros) * 100).toFixed(1)
|
||||
: 0}% do total
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-xl bg-green-500/20 p-3">
|
||||
<CheckCircle2 class="h-8 w-8 text-green-600" strokeWidth={2.5} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fora do Prazo -->
|
||||
<div
|
||||
class="card transform border border-red-500/20 bg-gradient-to-br from-red-500/10 to-red-600/20 shadow-xl transition-all duration-300 hover:-translate-y-1 hover:shadow-2xl"
|
||||
>
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<p class="text-base-content/70 mb-1 text-sm font-semibold">Fora do Prazo</p>
|
||||
<p class="text-3xl font-bold text-red-600">{estatisticas.foraDoPrazo}</p>
|
||||
<p class="text-base-content/60 mt-1 text-xs">
|
||||
{estatisticas.totalRegistros > 0
|
||||
? ((estatisticas.foraDoPrazo / estatisticas.totalRegistros) * 100).toFixed(1)
|
||||
: 0}% do total
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-xl bg-red-500/20 p-3">
|
||||
<XCircle class="h-8 w-8 text-red-600" strokeWidth={2.5} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Funcionários -->
|
||||
<div
|
||||
class="card transform border border-purple-500/20 bg-gradient-to-br from-purple-500/10 to-purple-600/20 shadow-xl transition-all duration-300 hover:-translate-y-1 hover:shadow-2xl"
|
||||
>
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<p class="text-base-content/70 mb-1 text-sm font-semibold">Funcionários</p>
|
||||
<p class="text-3xl font-bold text-purple-600">{estatisticas.totalFuncionarios}</p>
|
||||
<p class="text-base-content/60 mt-1 text-xs">
|
||||
{estatisticas.funcionariosDentroPrazo} dentro, {estatisticas.funcionariosForaPrazo} fora
|
||||
</p>
|
||||
</div>
|
||||
<div class="rounded-xl bg-purple-500/20 p-3">
|
||||
<Users class="h-8 w-8 text-purple-600" strokeWidth={2.5} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { Chart, registerables } from 'chart.js';
|
||||
import { BarChart3, XCircle, FileText } from 'lucide-svelte';
|
||||
|
||||
interface Estatisticas {
|
||||
totalRegistros: number;
|
||||
dentroDoPrazo: number;
|
||||
foraDoPrazo: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
estatisticas?: Estatisticas;
|
||||
chartData?: {
|
||||
labels: string[];
|
||||
datasets: Array<{
|
||||
label: string;
|
||||
data: number[];
|
||||
backgroundColor: string;
|
||||
borderColor: string;
|
||||
borderWidth: number;
|
||||
}>;
|
||||
} | null;
|
||||
isLoading?: boolean;
|
||||
error?: Error | null;
|
||||
}
|
||||
|
||||
let { estatisticas = undefined, chartData = null, isLoading = false, error = null }: Props = $props();
|
||||
|
||||
let chartCanvas: HTMLCanvasElement;
|
||||
let chartInstance: Chart | null = null;
|
||||
|
||||
onMount(() => {
|
||||
Chart.register(...registerables);
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (chartCanvas && estatisticas && chartData && !chartInstance) {
|
||||
try {
|
||||
criarGrafico();
|
||||
} catch (err) {
|
||||
console.error('Erro ao criar gráfico no onMount:', err);
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (chartInstance) {
|
||||
chartInstance.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
function criarGrafico() {
|
||||
if (!chartCanvas || !estatisticas || !chartData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ctx = chartCanvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (chartInstance) {
|
||||
chartInstance.destroy();
|
||||
chartInstance = null;
|
||||
}
|
||||
|
||||
try {
|
||||
chartInstance = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: chartData,
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'top',
|
||||
labels: {
|
||||
color: 'hsl(var(--bc))',
|
||||
font: {
|
||||
size: 12,
|
||||
family: "'Inter', sans-serif"
|
||||
},
|
||||
usePointStyle: true,
|
||||
padding: 15
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.85)',
|
||||
titleColor: '#fff',
|
||||
bodyColor: '#fff',
|
||||
borderColor: 'hsl(var(--p))',
|
||||
borderWidth: 1,
|
||||
padding: 12,
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
const label = context.dataset.label || '';
|
||||
const value = context.parsed.y;
|
||||
const total = estatisticas!.totalRegistros;
|
||||
const percentage = total > 0 ? ((value / total) * 100).toFixed(1) : '0.0';
|
||||
return `${label}: ${value} (${percentage}%)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
ticks: {
|
||||
color: 'hsl(var(--bc))',
|
||||
font: {
|
||||
size: 12
|
||||
}
|
||||
}
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
color: 'rgba(0, 0, 0, 0.05)'
|
||||
},
|
||||
ticks: {
|
||||
color: 'hsl(var(--bc))',
|
||||
font: {
|
||||
size: 11
|
||||
},
|
||||
stepSize: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
duration: 1000,
|
||||
easing: 'easeInOutQuart'
|
||||
},
|
||||
interaction: {
|
||||
mode: 'index',
|
||||
intersect: false
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erro ao criar gráfico:', error);
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (chartCanvas && estatisticas && chartData) {
|
||||
if (chartInstance) {
|
||||
chartInstance.destroy();
|
||||
chartInstance = null;
|
||||
}
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
try {
|
||||
criarGrafico();
|
||||
} catch (err) {
|
||||
console.error('Erro ao criar gráfico no effect:', err);
|
||||
}
|
||||
}, 200);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="card bg-base-100/90 border-base-300 mb-8 border shadow-xl backdrop-blur-sm">
|
||||
<div class="card-body">
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<h2 class="card-title text-2xl">
|
||||
<div class="bg-primary/10 rounded-lg p-2">
|
||||
<BarChart3 class="text-primary h-6 w-6" strokeWidth={2.5} />
|
||||
</div>
|
||||
<span>Visão Geral das Estatísticas</span>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="bg-base-200/50 border-base-300 relative h-80 w-full rounded-xl border p-4">
|
||||
{#if isLoading}
|
||||
<div class="bg-base-200/30 absolute inset-0 flex items-center justify-center rounded-xl">
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<span class="loading loading-spinner loading-lg text-primary"></span>
|
||||
<span class="text-base-content/70 font-medium">Carregando estatísticas...</span>
|
||||
</div>
|
||||
</div>
|
||||
{:else if error}
|
||||
<div class="bg-base-200/30 absolute inset-0 flex items-center justify-center rounded-xl">
|
||||
<div class="alert alert-error shadow-lg">
|
||||
<XCircle class="h-6 w-6" />
|
||||
<div>
|
||||
<h3 class="font-bold">Erro ao carregar estatísticas</h3>
|
||||
<div class="mt-1 text-sm">
|
||||
{error?.message || String(error) || 'Erro desconhecido'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if !estatisticas || !chartData}
|
||||
<div class="bg-base-200/30 absolute inset-0 flex items-center justify-center rounded-xl">
|
||||
<div class="text-center">
|
||||
<FileText class="text-base-content/30 mx-auto mb-2 h-12 w-12" />
|
||||
<p class="text-base-content/70">Nenhuma estatística disponível</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<canvas bind:this={chartCanvas} class="h-full w-full"></canvas>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<script lang="ts">
|
||||
import { Clock } from 'lucide-svelte';
|
||||
|
||||
interface Estatisticas {
|
||||
totalRegistros: number;
|
||||
totalFuncionarios: number;
|
||||
dentroDoPrazo: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
estatisticas?: Estatisticas;
|
||||
}
|
||||
|
||||
let { estatisticas = undefined }: Props = $props();
|
||||
</script>
|
||||
|
||||
<section
|
||||
class="border-base-300 from-primary/10 via-base-100 to-secondary/10 relative mb-8 overflow-hidden rounded-2xl border bg-gradient-to-br p-8 shadow-lg"
|
||||
>
|
||||
<div class="bg-primary/20 absolute top-10 -left-10 h-40 w-40 rounded-full blur-3xl"></div>
|
||||
<div class="bg-secondary/20 absolute right-0 -bottom-16 h-56 w-56 rounded-full blur-3xl"></div>
|
||||
<div class="relative z-10 flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<div
|
||||
class="bg-primary/20 border-primary/30 rounded-2xl border p-4 shadow-lg backdrop-blur-sm"
|
||||
>
|
||||
<Clock class="text-primary h-10 w-10" strokeWidth={2.5} />
|
||||
</div>
|
||||
<div class="max-w-3xl space-y-2">
|
||||
<h1 class="text-base-content text-4xl leading-tight font-black sm:text-5xl">
|
||||
Registro de Pontos
|
||||
</h1>
|
||||
<p class="text-base-content/70 text-base leading-relaxed sm:text-lg">
|
||||
Gerencie e visualize os registros de ponto dos funcionários com informações detalhadas e
|
||||
relatórios
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{#if estatisticas}
|
||||
<div
|
||||
class="border-base-200/60 bg-base-100/70 grid grid-cols-2 gap-4 rounded-2xl border p-6 shadow-lg backdrop-blur sm:max-w-sm"
|
||||
>
|
||||
<div>
|
||||
<p class="text-base-content/60 text-sm font-semibold">Total de Registros</p>
|
||||
<p class="text-base-content mt-2 text-2xl font-bold">{estatisticas.totalRegistros}</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<p class="text-base-content/60 text-sm font-semibold">Funcionários</p>
|
||||
<p class="text-base-content mt-2 text-xl font-bold">{estatisticas.totalFuncionarios}</p>
|
||||
</div>
|
||||
<div
|
||||
class="via-base-300 col-span-2 h-px bg-gradient-to-r from-transparent to-transparent"
|
||||
></div>
|
||||
<div class="text-base-content/70 col-span-2 flex items-center justify-between text-sm">
|
||||
<span>
|
||||
{estatisticas.totalRegistros > 0
|
||||
? ((estatisticas.dentroDoPrazo / estatisticas.totalRegistros) * 100).toFixed(1)
|
||||
: 0}% dentro do prazo
|
||||
</span>
|
||||
<span class="badge badge-primary badge-sm">Ativo</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user