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.
This commit is contained in:
2025-11-19 11:47:17 -03:00
parent 263d561301
commit 3cbe02fd1e
3 changed files with 67 additions and 73 deletions

View File

@@ -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<string, string> = {
@@ -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 @@
<!-- Alerta sobre dias bloqueados -->
{#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",
)}
<div class="alert alert-warning shadow-lg border-2 border-warning/50">
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -744,10 +735,10 @@
<!-- Informação do período selecionado -->
{#if dataInicio && dataFim && !readonly}
<div
class="mt-6 card bg-linear-to-br from-orange-50 to-amber-50 dark:from-orange-950 dark:to-amber-950 shadow-lg border-2 border-orange-500/30"
class="mt-6 card shadow-lg border border-orange-400"
>
<div class="card-body">
<h3 class="card-title text-orange-700 dark:text-orange-400">
<h3 class="card-title">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"