fix: enhance data handling in vacation dashboard by adding array checks and improving chart data structure for better stability and performance

This commit is contained in:
2025-11-29 23:25:14 -03:00
parent 545e119367
commit 298326e264

View File

@@ -2,7 +2,7 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { resolve } from '$app/paths'; import { resolve } from '$app/paths';
import { onMount, tick } from 'svelte'; import { onMount, tick } from 'svelte';
import { SvelteDate, SvelteMap } from 'svelte/reactivity'; import { SvelteDate, SvelteMap, SvelteSet } from 'svelte/reactivity';
import { useQuery, useConvexClient } from 'convex-svelte'; import { useQuery, useConvexClient } from 'convex-svelte';
import { api } from '@sgse-app/backend/convex/_generated/api'; import { api } from '@sgse-app/backend/convex/_generated/api';
import type { FunctionReturnType } from 'convex/server'; import type { FunctionReturnType } from 'convex/server';
@@ -110,7 +110,7 @@
// Usar último valor válido ou array vazio // Usar último valor válido ou array vazio
const solicitacoes = $derived<TodasSolicitacoes>( const solicitacoes = $derived<TodasSolicitacoes>(
todasSolicitacoesQuery?.data ?? ultimasSolicitacoesValidas (todasSolicitacoesQuery?.data ?? ultimasSolicitacoesValidas) || []
); );
// Filtros Dashboard // Filtros Dashboard
@@ -153,6 +153,9 @@
periodoFim: string; periodoFim: string;
} }
): TodasSolicitacoes { ): TodasSolicitacoes {
if (!Array.isArray(lista)) {
return [];
}
return lista.filter((periodo) => { return lista.filter((periodo) => {
if (filtros.status !== 'todos' && periodo.status !== filtros.status) { if (filtros.status !== 'todos' && periodo.status !== filtros.status) {
return false; return false;
@@ -271,14 +274,14 @@
}); });
const solicitacoesAprovadas = $derived( const solicitacoesAprovadas = $derived(
solicitacoesFiltradas.filter( (Array.isArray(solicitacoesFiltradas) ? solicitacoesFiltradas : []).filter(
(p) => (p) =>
p.status === 'aprovado' || p.status === 'data_ajustada_aprovada' || p.status === 'EmFérias' p.status === 'aprovado' || p.status === 'data_ajustada_aprovada' || p.status === 'EmFérias'
) )
); );
const periodosDetalhados = $derived<Array<PeriodoDetalhado>>( const periodosDetalhados = $derived<Array<PeriodoDetalhado>>(
solicitacoesAprovadas (Array.isArray(solicitacoesAprovadas) ? solicitacoesAprovadas : [])
.map((periodo) => ({ .map((periodo) => ({
feriasId: periodo._id, feriasId: periodo._id,
funcionarioId: periodo.funcionarioId, funcionarioId: periodo.funcionarioId,
@@ -303,6 +306,10 @@
(() => { (() => {
const agregados = new SvelteMap<string, PeriodoPorMes>(); const agregados = new SvelteMap<string, PeriodoPorMes>();
if (!Array.isArray(periodosDetalhados)) {
return [];
}
for (const periodo of periodosDetalhados) { for (const periodo of periodosDetalhados) {
const inicio = new SvelteDate(`${periodo.dataInicio}T00:00:00`); const inicio = new SvelteDate(`${periodo.dataInicio}T00:00:00`);
const chave = `${inicio.getFullYear()}-${String(inicio.getMonth() + 1).padStart(2, '0')}`; const chave = `${inicio.getFullYear()}-${String(inicio.getMonth() + 1).padStart(2, '0')}`;
@@ -531,19 +538,41 @@
} }
// Dados para gráfico de área - Funcionários de férias nos próximos 12 meses // Dados para gráfico de área - Funcionários de férias nos próximos 12 meses
const chartDataFuncionariosFerias = $derived(() => { type ChartData = {
const hoje = new Date(); labels: string[];
datasets: Array<{
label: string;
data: number[];
backgroundColor: string[];
borderColor: string;
borderWidth: number;
pointBackgroundColor: string[];
pointBorderColor: string;
pointBorderWidth: number;
pointRadius: number;
pointHoverRadius: number;
pointHoverBackgroundColor: string;
pointHoverBorderColor: string;
pointHoverBorderWidth: number;
fill: boolean;
tension: number;
spanGaps: boolean;
}>;
};
const chartDataFuncionariosFerias: ChartData = $derived.by(() => {
// Sempre criar os 12 meses, mesmo sem dados
const hoje = new SvelteDate();
hoje.setHours(0, 0, 0, 0); hoje.setHours(0, 0, 0, 0);
// Criar array com os próximos 12 meses // Criar array com os próximos 12 meses
const meses: Array<{ mes: string; dataInicio: Date; dataFim: Date; quantidade: number }> = []; const meses: Array<{ mes: string; dataInicio: SvelteDate; dataFim: SvelteDate; quantidade: number }> = [];
for (let i = 0; i < 12; i++) { for (let i = 0; i < 12; i++) {
const dataInicioMes = new Date(hoje.getFullYear(), hoje.getMonth() + i, 1); const dataInicioMes = new SvelteDate(hoje.getFullYear(), hoje.getMonth() + i, 1);
const dataFimMes = new Date(hoje.getFullYear(), hoje.getMonth() + i + 1, 0); const dataFimMes = new SvelteDate(hoje.getFullYear(), hoje.getMonth() + i + 1, 0);
dataFimMes.setHours(23, 59, 59, 999); dataFimMes.setHours(23, 59, 59, 999);
const mesLabel = format(dataInicioMes, 'MMM/yyyy', { locale: ptBR }); const mesLabel = format(new Date(dataInicioMes.getTime()), 'MMM/yyyy', { locale: ptBR });
meses.push({ meses.push({
mes: mesLabel, mes: mesLabel,
dataInicio: dataInicioMes, dataInicio: dataInicioMes,
@@ -552,36 +581,38 @@
}); });
} }
// Filtrar apenas solicitações aprovadas que estão ou estarão em férias // Verificação de segurança e filtrar apenas solicitações aprovadas
const solicitacoesAprovadas = ultimasSolicitacoesValidas.filter( if (ultimasSolicitacoesValidas && Array.isArray(ultimasSolicitacoesValidas)) {
(s) => const solicitacoesAprovadas = ultimasSolicitacoesValidas.filter(
s.status === 'aprovado' || (s) =>
s.status === 'data_ajustada_aprovada' || s.status === 'aprovado' ||
s.status === 'EmFérias' s.status === 'data_ajustada_aprovada' ||
); s.status === 'EmFérias'
);
// Calcular quantos funcionários estarão de férias em cada mês // Calcular quantos funcionários estarão de férias em cada mês
meses.forEach((mesInfo) => { meses.forEach((mesInfo) => {
const funcionariosEmFerias = new Set<string>(); const funcionariosEmFerias = new SvelteSet<string>();
solicitacoesAprovadas.forEach((solicitacao) => { solicitacoesAprovadas.forEach((solicitacao) => {
if (!solicitacao.funcionarioId) return; if (!solicitacao.funcionarioId) return;
const dataInicio = new Date(solicitacao.dataInicio); const dataInicio = new SvelteDate(solicitacao.dataInicio);
const dataFim = new Date(solicitacao.dataFim); const dataFim = new SvelteDate(solicitacao.dataFim);
dataInicio.setHours(0, 0, 0, 0); dataInicio.setHours(0, 0, 0, 0);
dataFim.setHours(23, 59, 59, 999); dataFim.setHours(23, 59, 59, 999);
// Verificar se o período de férias se sobrepõe com o mês // Verificar se o período de férias se sobrepõe com o mês
if ( if (
(dataInicio <= mesInfo.dataFim && dataFim >= mesInfo.dataInicio) (dataInicio.getTime() <= mesInfo.dataFim.getTime() && dataFim.getTime() >= mesInfo.dataInicio.getTime())
) { ) {
funcionariosEmFerias.add(String(solicitacao.funcionarioId)); funcionariosEmFerias.add(String(solicitacao.funcionarioId));
} }
});
mesInfo.quantidade = funcionariosEmFerias.size;
}); });
}
mesInfo.quantidade = funcionariosEmFerias.size;
});
// Cores harmoniosas com o tema (gradiente de azul primary para accent) // Cores harmoniosas com o tema (gradiente de azul primary para accent)
const corBase = '#3b82f6'; // Azul primary const corBase = '#3b82f6'; // Azul primary
@@ -2475,12 +2506,12 @@
</p> </p>
</div> </div>
</div> </div>
<div class="bg-base-200/40 w-full overflow-x-auto rounded-xl p-4"> <div class="bg-base-200/40 w-full overflow-x-auto rounded-xl p-4">
{#if chartDataFuncionariosFerias.labels.length === 0}
<div class="flex h-96 items-center justify-center">
<p class="text-base-content/60">Sem dados registrados até o momento.</p>
</div>
{#if chartDataFuncionariosFerias.labels && chartDataFuncionariosFerias.labels.length > 0} {#if chartDataFuncionariosFerias.labels && chartDataFuncionariosFerias.labels.length > 0}
<AreaChart data={chartDataFuncionariosFerias} height={400} />
{:else}
<div class="flex h-96 items-center justify-center">
<p class="text-base-content/60">Carregando dados do gráfico...</p>
</div> </div>
{/if} {/if}
</div> </div>