219 lines
5.1 KiB
Svelte
219 lines
5.1 KiB
Svelte
<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>
|
|
|