From 3cbe02fd1ec7ecc6be477c69f41672639ac4aa47 Mon Sep 17 00:00:00 2001 From: killer-cf Date: Wed, 19 Nov 2025 11:47:17 -0300 Subject: [PATCH] refactor: replace Date with SvelteDate for improved date handling in absence components - Updated date handling in CalendarioAusencias and WizardSolicitacaoAusencia components to use SvelteDate for better reactivity and consistency. - Refactored various date-related functions to ensure compatibility with the new SvelteDate type. - Enhanced UI elements to maintain functionality while improving code clarity and maintainability. --- .../ausencias/CalendarioAusencias.svelte | 127 ++++++++---------- .../WizardSolicitacaoAusencia.svelte | 9 +- packages/backend/convex/_generated/api.d.ts | 4 +- 3 files changed, 67 insertions(+), 73 deletions(-) diff --git a/apps/web/src/lib/components/ausencias/CalendarioAusencias.svelte b/apps/web/src/lib/components/ausencias/CalendarioAusencias.svelte index 986e577..4cb971a 100644 --- a/apps/web/src/lib/components/ausencias/CalendarioAusencias.svelte +++ b/apps/web/src/lib/components/ausencias/CalendarioAusencias.svelte @@ -5,6 +5,7 @@ import interactionPlugin from "@fullcalendar/interaction"; import multiMonthPlugin from "@fullcalendar/multimonth"; import ptBrLocale from "@fullcalendar/core/locales/pt-br"; + import { SvelteDate } from "svelte/reactivity"; interface Props { dataInicio?: string; @@ -34,18 +35,6 @@ let calendarEl: HTMLDivElement; let calendar: Calendar | null = null; let selecionando = $state(false); // Flag para evitar atualizações durante seleção - let eventos: Array<{ - id: string; - title: string; - start: string; - end: string; - backgroundColor: string; - borderColor: string; - textColor: string; - extendedProps: { - status: string; - }; - }> = $state([]); // Cores por status const coresStatus: Record< @@ -58,7 +47,7 @@ }; // Converter ausências existentes em eventos - function atualizarEventos() { + let eventos = $derived.by(() => { const novosEventos: Array<{ id: string; title: string; @@ -103,8 +92,8 @@ }); } - eventos = novosEventos; - } + return novosEventos; + }); function getStatusTexto(status: string): string { const textos: Record = { @@ -117,15 +106,15 @@ // Helper: Adicionar 1 dia à data fim (FullCalendar usa exclusive end) function calcularDataFim(dataFim: string): string { - const data = new Date(dataFim); + const data = new SvelteDate(dataFim); data.setDate(data.getDate() + 1); return data.toISOString().split("T")[0]; } // Helper: Calcular dias entre datas (inclusivo) function calcularDias(inicio: string, fim: string): number { - const dInicio = new Date(inicio); - const dFim = new Date(fim); + const dInicio = new SvelteDate(inicio); + const dFim = new SvelteDate(fim); const diffTime = Math.abs(dFim.getTime() - dInicio.getTime()); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1; return diffDays; @@ -133,20 +122,23 @@ // Helper: Verificar se há sobreposição de datas function verificarSobreposicao( - inicio1: Date, - fim1: Date, + inicio1: SvelteDate, + fim1: SvelteDate, inicio2: string, fim2: string, ): boolean { - const d2Inicio = new Date(inicio2); - const d2Fim = new Date(fim2); + const d2Inicio = new SvelteDate(inicio2); + const d2Fim = new SvelteDate(fim2); // Verificar sobreposição: início1 <= fim2 && início2 <= fim1 return inicio1 <= d2Fim && d2Inicio <= fim1; } // Helper: Verificar se período selecionado sobrepõe com ausências existentes - function verificarSobreposicaoComAusencias(inicio: Date, fim: Date): boolean { + function verificarSobreposicaoComAusencias( + inicio: SvelteDate, + fim: SvelteDate, + ): boolean { if (!ausenciasExistentes || ausenciasExistentes.length === 0) return false; // Verificar apenas ausências aprovadas ou aguardando aprovação @@ -159,12 +151,17 @@ ); } + interface FullCalendarDayCellInfo { + el: HTMLElement; + date: Date; + } + // Helper: Atualizar classe de seleção em uma célula - function atualizarClasseSelecionado(info: any) { + function atualizarClasseSelecionado(info: FullCalendarDayCellInfo) { if (dataInicio && dataFim && !readonly) { - const cellDate = new Date(info.date); - const inicio = new Date(dataInicio); - const fim = new Date(dataFim); + const cellDate = new SvelteDate(info.date); + const inicio = new SvelteDate(dataInicio); + const fim = new SvelteDate(dataFim); cellDate.setHours(0, 0, 0, 0); inicio.setHours(0, 0, 0, 0); @@ -181,13 +178,13 @@ } // Helper: Atualizar classe de bloqueio para dias com ausências existentes - function atualizarClasseBloqueado(info: any) { + function atualizarClasseBloqueado(info: FullCalendarDayCellInfo) { if (readonly || !ausenciasExistentes || ausenciasExistentes.length === 0) { info.el.classList.remove("fc-day-blocked"); return; } - const cellDate = new Date(info.date); + const cellDate = new SvelteDate(info.date); cellDate.setHours(0, 0, 0, 0); // Verificar se a data está dentro de alguma ausência aprovada ou aguardando aprovação @@ -196,8 +193,8 @@ (a) => a.status === "aprovado" || a.status === "aguardando_aprovacao", ) .some((ausencia) => { - const inicio = new Date(ausencia.dataInicio); - const fim = new Date(ausencia.dataFim); + const inicio = new SvelteDate(ausencia.dataInicio); + const fim = new SvelteDate(ausencia.dataFim); inicio.setHours(0, 0, 0, 0); fim.setHours(0, 0, 0, 0); return cellDate >= inicio && cellDate <= fim; @@ -218,8 +215,8 @@ const view = calendar.view; if (!view) return; - const inicio = new Date(dataInicio); - const fim = new Date(dataFim); + const inicio = new SvelteDate(dataInicio); + const fim = new SvelteDate(dataFim); inicio.setHours(0, 0, 0, 0); fim.setHours(0, 0, 0, 0); @@ -235,14 +232,14 @@ if (ariaLabel) { // Formato: "dia mês ano" ou similar try { - const cellDate = new Date(ariaLabel); + const cellDate = new SvelteDate(ariaLabel); if (!isNaN(cellDate.getTime())) { cellDate.setHours(0, 0, 0, 0); if (cellDate >= inicio && cellDate <= fim) { cell.classList.add("fc-day-selected"); } } - } catch (e) { + } catch { // Ignorar erros de parsing } } @@ -266,6 +263,7 @@ return; } + const calendarInstance = calendar; const cells = calendarEl.querySelectorAll(".fc-daygrid-day"); const ausenciasBloqueantes = ausenciasExistentes.filter( (a) => a.status === "aprovado" || a.status === "aguardando_aprovacao", @@ -280,17 +278,17 @@ cell.classList.remove("fc-day-blocked"); // Tentar obter a data de diferentes formas - let cellDate: Date | null = null; + let cellDate: SvelteDate | null = null; // Método 1: aria-label const ariaLabel = cell.getAttribute("aria-label"); if (ariaLabel) { try { - const parsed = new Date(ariaLabel); + const parsed = new SvelteDate(ariaLabel); if (!isNaN(parsed.getTime())) { cellDate = parsed; } - } catch (e) { + } catch { // Ignorar } } @@ -300,27 +298,27 @@ const dataDate = cell.getAttribute("data-date"); if (dataDate) { try { - const parsed = new Date(dataDate); + const parsed = new SvelteDate(dataDate); if (!isNaN(parsed.getTime())) { cellDate = parsed; } - } catch (e) { + } catch { // Ignorar } } } // Método 3: Tentar obter do número do dia e contexto do calendário - if (!cellDate && calendar.view) { + if (!cellDate && calendarInstance.view) { const dayNumberEl = cell.querySelector(".fc-daygrid-day-number"); if (dayNumberEl) { const dayNumber = parseInt(dayNumberEl.textContent || "0"); if (dayNumber > 0 && dayNumber <= 31) { // Usar a data da view atual e o número do dia - const viewStart = new Date(calendar.view.activeStart); + const viewStart = new SvelteDate(calendarInstance.view.activeStart); const cellIndex = Array.from(cells).indexOf(cell); if (cellIndex >= 0) { - const possibleDate = new Date(viewStart); + const possibleDate = new SvelteDate(viewStart); possibleDate.setDate(viewStart.getDate() + cellIndex); // Verificar se o número do dia corresponde if (possibleDate.getDate() === dayNumber) { @@ -335,11 +333,11 @@ cellDate.setHours(0, 0, 0, 0); const estaBloqueado = ausenciasBloqueantes.some((ausencia) => { - const inicio = new Date(ausencia.dataInicio); - const fim = new Date(ausencia.dataFim); + const inicio = new SvelteDate(ausencia.dataInicio); + const fim = new SvelteDate(ausencia.dataFim); inicio.setHours(0, 0, 0, 0); fim.setHours(0, 0, 0, 0); - return cellDate >= inicio && cellDate <= fim; + return cellDate! >= inicio && cellDate! <= fim; }); if (estaBloqueado) { @@ -354,9 +352,7 @@ if (!calendar || selecionando) return; // Não atualizar durante seleção // Garantir que temos as ausências antes de atualizar - const ausencias = ausenciasExistentes; - - atualizarEventos(); + void ausenciasExistentes; // Usar requestAnimationFrame para evitar múltiplas atualizações durante seleção requestAnimationFrame(() => { @@ -398,8 +394,6 @@ onMount(() => { if (!calendarEl) return; - atualizarEventos(); - calendar = new Calendar(calendarEl, { plugins: [dayGridPlugin, interactionPlugin, multiMonthPlugin], initialView: @@ -416,9 +410,9 @@ selectMirror: true, unselectAuto: false, selectOverlap: false, - selectConstraint: null, // Permite seleção entre meses diferentes + selectConstraint: undefined, // Permite seleção entre meses diferentes validRange: { - start: new Date().toISOString().split("T")[0], // Não permite selecionar datas passadas + start: new SvelteDate().toISOString().split("T")[0], // Não permite selecionar datas passadas }, events: eventos, @@ -437,12 +431,12 @@ // Usar setTimeout para evitar conflito com atualizações de estado setTimeout(() => { - const inicio = new Date(info.startStr); - const fim = new Date(info.endStr); + const inicio = new SvelteDate(info.startStr); + const fim = new SvelteDate(info.endStr); fim.setDate(fim.getDate() - 1); // FullCalendar usa exclusive end // Validar que não é no passado - const hoje = new Date(); + const hoje = new SvelteDate(); hoje.setHours(0, 0, 0, 0); if (inicio < hoje) { alert("A data de início não pode ser no passado"); @@ -511,11 +505,11 @@ // Desabilitar datas passadas e períodos que sobrepõem com ausências existentes selectAllow: (selectInfo) => { - const hoje = new Date(); + const hoje = new SvelteDate(); hoje.setHours(0, 0, 0, 0); // Bloquear datas passadas - if (new Date(selectInfo.start) < hoje) { + if (new SvelteDate(selectInfo.start) < hoje) { return false; } @@ -525,8 +519,8 @@ ausenciasExistentes && ausenciasExistentes.length > 0 ) { - const inicioSelecao = new Date(selectInfo.start); - const fimSelecao = new Date(selectInfo.end); + const inicioSelecao = new SvelteDate(selectInfo.start); + const fimSelecao = new SvelteDate(selectInfo.end); fimSelecao.setDate(fimSelecao.getDate() - 1); // FullCalendar usa exclusive end inicioSelecao.setHours(0, 0, 0, 0); @@ -578,7 +572,7 @@ ausenciasExistentes && ausenciasExistentes.length > 0 ) { - const cellDate = new Date(arg.date); + const cellDate = new SvelteDate(arg.date); cellDate.setHours(0, 0, 0, 0); const ausenciasBloqueantes = ausenciasExistentes.filter( @@ -587,8 +581,8 @@ ); const estaBloqueado = ausenciasBloqueantes.some((ausencia) => { - const inicio = new Date(ausencia.dataInicio); - const fim = new Date(ausencia.dataFim); + const inicio = new SvelteDate(ausencia.dataInicio); + const fim = new SvelteDate(ausencia.dataFim); inicio.setHours(0, 0, 0, 0); fim.setHours(0, 0, 0, 0); return cellDate >= inicio && cellDate <= fim; @@ -646,9 +640,6 @@ {#if ausenciasExistentes && ausenciasExistentes.filter((a) => a.status === "aprovado" || a.status === "aguardando_aprovacao").length > 0} - {@const ausenciasBloqueantes = ausenciasExistentes.filter( - (a) => a.status === "aprovado" || a.status === "aguardando_aprovacao", - )}
{#if dataInicio && dataFim && !readonly}
-

+

; @@ -67,7 +68,7 @@ return; } - const hoje = new Date(); + const hoje = new SvelteDate(); hoje.setHours(0, 0, 0, 0); const inicio = new Date(dataInicio); @@ -266,7 +267,7 @@

Período selecionado!

- De {new Date(dataInicio).toLocaleDateString('pt-BR')} até{' '} + De {new Date(dataInicio).toLocaleDateString('pt-BR')} até {new Date(dataFim).toLocaleDateString('pt-BR')} ({totalDias} dias)

@@ -286,7 +287,7 @@ {#if dataInicio && dataFim}

@@ -345,7 +346,7 @@ bind:value={motivo} maxlength={500} > -