refactor: clean up Svelte components and improve code readability

- Refactored multiple Svelte components to enhance code clarity and maintainability.
- Standardized formatting and indentation across various files for consistency.
- Improved error handling messages in the AprovarAusencias component for better user feedback.
- Updated class names in the UI components to align with the new design system.
- Removed unnecessary whitespace and comments to streamline the codebase.
This commit is contained in:
2025-11-08 10:11:40 -03:00
parent 28107b4050
commit 01138b3e1c
24 changed files with 4655 additions and 1927 deletions

View File

@@ -14,7 +14,10 @@
dataFim: string;
status: "aguardando_aprovacao" | "aprovado" | "reprovado";
}>;
onPeriodoSelecionado?: (periodo: { dataInicio: string; dataFim: string }) => void;
onPeriodoSelecionado?: (periodo: {
dataInicio: string;
dataFim: string;
}) => void;
modoVisualizacao?: "month" | "multiMonth";
readonly?: boolean;
}
@@ -45,7 +48,10 @@
}> = $state([]);
// Cores por status
const coresStatus: Record<string, { bg: string; border: string; text: string }> = {
const coresStatus: Record<
string,
{ bg: string; border: string; text: string }
> = {
aguardando_aprovacao: { bg: "#f59e0b", border: "#d97706", text: "#ffffff" }, // Laranja
aprovado: { bg: "#10b981", border: "#059669", text: "#ffffff" }, // Verde
reprovado: { bg: "#ef4444", border: "#dc2626", text: "#ffffff" }, // Vermelho
@@ -65,7 +71,8 @@
status: string;
};
}> = ausenciasExistentes.map((ausencia, index) => {
const cor = coresStatus[ausencia.status] || coresStatus.aguardando_aprovacao;
const cor =
coresStatus[ausencia.status] || coresStatus.aguardando_aprovacao;
return {
id: `ausencia-${index}`,
title: `${getStatusTexto(ausencia.status)} - ${calcularDias(ausencia.dataInicio, ausencia.dataFim)} dias`,
@@ -95,7 +102,7 @@
},
});
}
eventos = novosEventos;
}
@@ -129,11 +136,11 @@
inicio1: Date,
fim1: Date,
inicio2: string,
fim2: string
fim2: string,
): boolean {
const d2Inicio = new Date(inicio2);
const d2Fim = new Date(fim2);
// Verificar sobreposição: início1 <= fim2 && início2 <= fim1
return inicio1 <= d2Fim && d2Inicio <= fim1;
}
@@ -141,14 +148,14 @@
// Helper: Verificar se período selecionado sobrepõe com ausências existentes
function verificarSobreposicaoComAusencias(inicio: Date, fim: Date): boolean {
if (!ausenciasExistentes || ausenciasExistentes.length === 0) return false;
// Verificar apenas ausências aprovadas ou aguardando aprovação
const ausenciasBloqueantes = ausenciasExistentes.filter(
(a) => a.status === "aprovado" || a.status === "aguardando_aprovacao"
(a) => a.status === "aprovado" || a.status === "aguardando_aprovacao",
);
return ausenciasBloqueantes.some((ausencia) =>
verificarSobreposicao(inicio, fim, ausencia.dataInicio, ausencia.dataFim)
verificarSobreposicao(inicio, fim, ausencia.dataInicio, ausencia.dataFim),
);
}
@@ -158,11 +165,11 @@
const cellDate = new Date(info.date);
const inicio = new Date(dataInicio);
const fim = new Date(dataFim);
cellDate.setHours(0, 0, 0, 0);
inicio.setHours(0, 0, 0, 0);
fim.setHours(0, 0, 0, 0);
if (cellDate >= inicio && cellDate <= fim) {
info.el.classList.add("fc-day-selected");
} else {
@@ -185,7 +192,9 @@
// Verificar se a data está dentro de alguma ausência aprovada ou aguardando aprovação
const estaBloqueado = ausenciasExistentes
.filter((a) => a.status === "aprovado" || a.status === "aguardando_aprovacao")
.filter(
(a) => a.status === "aprovado" || a.status === "aguardando_aprovacao",
)
.some((ausencia) => {
const inicio = new Date(ausencia.dataInicio);
const fim = new Date(ausencia.dataFim);
@@ -204,23 +213,23 @@
// Helper: Atualizar todos os dias selecionados no calendário
function atualizarDiasSelecionados() {
if (!calendar || !calendarEl || !dataInicio || !dataFim || readonly) return;
// Usar a API do FullCalendar para iterar sobre todas as células visíveis
const view = calendar.view;
if (!view) return;
const inicio = new Date(dataInicio);
const fim = new Date(dataFim);
inicio.setHours(0, 0, 0, 0);
fim.setHours(0, 0, 0, 0);
// O FullCalendar renderiza as células, então podemos usar dayCellDidMount
// Mas também precisamos atualizar células existentes
const cells = calendarEl.querySelectorAll(".fc-daygrid-day");
cells.forEach((cell) => {
// Remover classe primeiro
cell.classList.remove("fc-day-selected");
// Tentar obter a data do aria-label ou do elemento
const ariaLabel = cell.getAttribute("aria-label");
if (ariaLabel) {
@@ -242,7 +251,13 @@
// Helper: Atualizar todos os dias bloqueados no calendário
function atualizarDiasBloqueados() {
if (!calendar || !calendarEl || readonly || !ausenciasExistentes || ausenciasExistentes.length === 0) {
if (
!calendar ||
!calendarEl ||
readonly ||
!ausenciasExistentes ||
ausenciasExistentes.length === 0
) {
// Remover classes de bloqueio se não houver ausências
if (calendarEl) {
const cells = calendarEl.querySelectorAll(".fc-daygrid-day");
@@ -250,23 +265,23 @@
}
return;
}
const cells = calendarEl.querySelectorAll(".fc-daygrid-day");
const ausenciasBloqueantes = ausenciasExistentes.filter(
(a) => a.status === "aprovado" || a.status === "aguardando_aprovacao"
(a) => a.status === "aprovado" || a.status === "aguardando_aprovacao",
);
if (ausenciasBloqueantes.length === 0) {
cells.forEach((cell) => cell.classList.remove("fc-day-blocked"));
return;
}
cells.forEach((cell) => {
cell.classList.remove("fc-day-blocked");
// Tentar obter a data de diferentes formas
let cellDate: Date | null = null;
// Método 1: aria-label
const ariaLabel = cell.getAttribute("aria-label");
if (ariaLabel) {
@@ -279,7 +294,7 @@
// Ignorar
}
}
// Método 2: data-date attribute
if (!cellDate) {
const dataDate = cell.getAttribute("data-date");
@@ -294,7 +309,7 @@
}
}
}
// Método 3: Tentar obter do número do dia e contexto do calendário
if (!cellDate && calendar.view) {
const dayNumberEl = cell.querySelector(".fc-daygrid-day-number");
@@ -315,10 +330,10 @@
}
}
}
if (cellDate) {
cellDate.setHours(0, 0, 0, 0);
const estaBloqueado = ausenciasBloqueantes.some((ausencia) => {
const inicio = new Date(ausencia.dataInicio);
const fim = new Date(ausencia.dataFim);
@@ -326,7 +341,7 @@
fim.setHours(0, 0, 0, 0);
return cellDate >= inicio && cellDate <= fim;
});
if (estaBloqueado) {
cell.classList.add("fc-day-blocked");
}
@@ -337,18 +352,18 @@
// Atualizar eventos quando mudanças ocorrem (evitar loop infinito)
$effect(() => {
if (!calendar || selecionando) return; // Não atualizar durante seleção
// Garantir que temos as ausências antes de atualizar
const ausencias = ausenciasExistentes;
atualizarEventos();
// Usar requestAnimationFrame para evitar múltiplas atualizações durante seleção
requestAnimationFrame(() => {
if (calendar && !selecionando) {
calendar.removeAllEvents();
calendar.addEventSource(eventos);
// Atualizar classes de seleção e bloqueio quando as datas mudarem
setTimeout(() => {
atualizarDiasSelecionados();
@@ -357,16 +372,17 @@
}
});
});
// Efeito separado para atualizar quando ausências mudarem
$effect(() => {
if (!calendar || readonly) return;
const ausencias = ausenciasExistentes;
const ausenciasBloqueantes = ausencias?.filter(
(a) => a.status === "aprovado" || a.status === "aguardando_aprovacao"
) || [];
const ausenciasBloqueantes =
ausencias?.filter(
(a) => a.status === "aprovado" || a.status === "aguardando_aprovacao",
) || [];
// Se houver ausências bloqueantes, forçar atualização
if (ausenciasBloqueantes.length > 0) {
setTimeout(() => {
@@ -386,12 +402,14 @@
calendar = new Calendar(calendarEl, {
plugins: [dayGridPlugin, interactionPlugin, multiMonthPlugin],
initialView: modoVisualizacao === "multiMonth" ? "multiMonthYear" : "dayGridMonth",
initialView:
modoVisualizacao === "multiMonth" ? "multiMonthYear" : "dayGridMonth",
locale: ptBrLocale,
headerToolbar: {
left: "prev,next today",
center: "title",
right: modoVisualizacao === "multiMonth" ? "multiMonthYear" : "dayGridMonth",
right:
modoVisualizacao === "multiMonth" ? "multiMonthYear" : "dayGridMonth",
},
height: "auto",
selectable: !readonly,
@@ -443,7 +461,9 @@
// Validar sobreposição com ausências existentes
if (verificarSobreposicaoComAusencias(inicio, fim)) {
alert("Este período sobrepõe com uma ausência já aprovada ou aguardando aprovação. Por favor, escolha outro período.");
alert(
"Este período sobrepõe com uma ausência já aprovada ou aguardando aprovação. Por favor, escolha outro período.",
);
calendar?.unselect();
selecionando = false;
return;
@@ -459,7 +479,7 @@
// Não remover seleção imediatamente para manter visualização
// calendar?.unselect();
// Liberar flag após um pequeno delay para garantir que o estado foi atualizado
setTimeout(() => {
selecionando = false;
@@ -472,7 +492,9 @@
if (readonly) {
const status = info.event.extendedProps.status;
const texto = getStatusTexto(status);
alert(`Ausência ${texto}\nPeríodo: ${new Date(info.event.startStr).toLocaleDateString("pt-BR")} até ${new Date(calcularDataFim(info.event.endStr)).toLocaleDateString("pt-BR")}`);
alert(
`Ausência ${texto}\nPeríodo: ${new Date(info.event.startStr).toLocaleDateString("pt-BR")} até ${new Date(calcularDataFim(info.event.endStr)).toLocaleDateString("pt-BR")}`,
);
}
},
@@ -491,35 +513,39 @@
selectAllow: (selectInfo) => {
const hoje = new Date();
hoje.setHours(0, 0, 0, 0);
// Bloquear datas passadas
if (new Date(selectInfo.start) < hoje) {
return false;
}
// Verificar sobreposição com ausências existentes
if (!readonly && ausenciasExistentes && ausenciasExistentes.length > 0) {
if (
!readonly &&
ausenciasExistentes &&
ausenciasExistentes.length > 0
) {
const inicioSelecao = new Date(selectInfo.start);
const fimSelecao = new Date(selectInfo.end);
fimSelecao.setDate(fimSelecao.getDate() - 1); // FullCalendar usa exclusive end
inicioSelecao.setHours(0, 0, 0, 0);
fimSelecao.setHours(0, 0, 0, 0);
if (verificarSobreposicaoComAusencias(inicioSelecao, fimSelecao)) {
return false;
}
}
return true;
},
// Adicionar classe CSS aos dias selecionados e bloqueados
dayCellDidMount: (info) => {
atualizarClasseSelecionado(info);
atualizarClasseBloqueado(info);
},
// Atualizar quando as datas mudarem (navegação do calendário)
datesSet: () => {
setTimeout(() => {
@@ -527,7 +553,7 @@
atualizarDiasBloqueados();
}, 100);
},
// Garantir que as classes sejam aplicadas após renderização inicial
viewDidMount: () => {
setTimeout(() => {
@@ -541,20 +567,25 @@
// Highlight de fim de semana e aplicar classe de bloqueio
dayCellClassNames: (arg) => {
const classes: string[] = [];
if (arg.date.getDay() === 0 || arg.date.getDay() === 6) {
classes.push("fc-day-weekend-custom");
}
// Verificar se o dia está bloqueado
if (!readonly && ausenciasExistentes && ausenciasExistentes.length > 0) {
if (
!readonly &&
ausenciasExistentes &&
ausenciasExistentes.length > 0
) {
const cellDate = new Date(arg.date);
cellDate.setHours(0, 0, 0, 0);
const ausenciasBloqueantes = ausenciasExistentes.filter(
(a) => a.status === "aprovado" || a.status === "aguardando_aprovacao"
(a) =>
a.status === "aprovado" || a.status === "aguardando_aprovacao",
);
const estaBloqueado = ausenciasBloqueantes.some((ausencia) => {
const inicio = new Date(ausencia.dataInicio);
const fim = new Date(ausencia.dataFim);
@@ -562,12 +593,12 @@
fim.setHours(0, 0, 0, 0);
return cellDate >= inicio && cellDate <= fim;
});
if (estaBloqueado) {
classes.push("fc-day-blocked");
}
}
return classes;
},
});
@@ -585,32 +616,39 @@
{#if !readonly}
<div class="space-y-4 mb-4">
<div class="alert alert-info shadow-lg">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-current shrink-0 w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<div class="text-sm">
<p class="font-bold">Como usar:</p>
<ul class="list-disc list-inside mt-1">
<li>Clique e arraste no calendário para selecionar o período de ausência</li>
<li>Você pode visualizar suas ausências já solicitadas no calendário</li>
<li>A data de início não pode ser no passado</li>
</ul>
</div>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="stroke-current shrink-0 w-6 h-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
></path>
</svg>
<div class="text-sm">
<p class="font-bold">Como usar:</p>
<ul class="list-disc list-inside mt-1">
<li>
Clique e arraste no calendário para selecionar o período de
ausência
</li>
<li>
Você pode visualizar suas ausências já solicitadas no calendário
</li>
<li>A data de início não pode ser no passado</li>
</ul>
</div>
</div>
<!-- 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")}
{#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"
@@ -628,8 +666,18 @@
<div class="flex-1">
<h3 class="font-bold">Atenção: Períodos Indisponíveis</h3>
<div class="text-sm mt-1">
<p>Os dias marcados em <span class="font-bold text-error">vermelho</span> estão bloqueados porque você já possui solicitações <strong>aprovadas</strong> ou <strong>aguardando aprovação</strong> para esses períodos.</p>
<p class="mt-2">Você não pode criar novas solicitações que sobreponham esses períodos. Escolha um período diferente.</p>
<p>
Os dias marcados em <span class="font-bold text-error"
>vermelho</span
>
estão bloqueados porque você já possui solicitações
<strong>aprovadas</strong>
ou <strong>aguardando aprovação</strong> para esses períodos.
</p>
<p class="mt-2">
Você não pode criar novas solicitações que sobreponham esses
períodos. Escolha um período diferente.
</p>
</div>
</div>
</div>
@@ -647,30 +695,46 @@
{#if ausenciasExistentes.length > 0 || readonly}
<div class="mt-6 space-y-4">
<div class="flex flex-wrap gap-4 justify-center">
<div class="badge badge-lg gap-2" style="background-color: #f59e0b; border-color: #d97706; color: white;">
<div class="w-3 h-3 rounded-full bg-white"></div>
Aguardando Aprovação
</div>
<div class="badge badge-lg gap-2" style="background-color: #10b981; border-color: #059669; color: white;">
<div class="w-3 h-3 rounded-full bg-white"></div>
Aprovado
</div>
<div class="badge badge-lg gap-2" style="background-color: #ef4444; border-color: #dc2626; color: white;">
<div class="w-3 h-3 rounded-full bg-white"></div>
Reprovado
</div>
{#if !readonly && ausenciasExistentes && ausenciasExistentes.filter(a => a.status === "aprovado" || a.status === "aguardando_aprovacao").length > 0}
<div class="badge badge-lg gap-2" style="background-color: rgba(239, 68, 68, 0.2); border-color: #ef4444; color: #dc2626;">
<div class="w-3 h-3 rounded-full" style="background-color: #ef4444;"></div>
<div
class="badge badge-lg gap-2"
style="background-color: #f59e0b; border-color: #d97706; color: white;"
>
<div class="w-3 h-3 rounded-full bg-white"></div>
Aguardando Aprovação
</div>
<div
class="badge badge-lg gap-2"
style="background-color: #10b981; border-color: #059669; color: white;"
>
<div class="w-3 h-3 rounded-full bg-white"></div>
Aprovado
</div>
<div
class="badge badge-lg gap-2"
style="background-color: #ef4444; border-color: #dc2626; color: white;"
>
<div class="w-3 h-3 rounded-full bg-white"></div>
Reprovado
</div>
{#if !readonly && ausenciasExistentes && ausenciasExistentes.filter((a) => a.status === "aprovado" || a.status === "aguardando_aprovacao").length > 0}
<div
class="badge badge-lg gap-2"
style="background-color: rgba(239, 68, 68, 0.2); border-color: #ef4444; color: #dc2626;"
>
<div
class="w-3 h-3 rounded-full"
style="background-color: #ef4444;"
></div>
Dias Bloqueados (Indisponíveis)
</div>
{/if}
</div>
{#if !readonly && ausenciasExistentes && ausenciasExistentes.filter(a => a.status === "aprovado" || a.status === "aguardando_aprovacao").length > 0}
{#if !readonly && ausenciasExistentes && ausenciasExistentes.filter((a) => a.status === "aprovado" || a.status === "aguardando_aprovacao").length > 0}
<div class="text-center">
<p class="text-sm text-base-content/70">
<span class="font-semibold text-error">Dias bloqueados</span> não podem ser selecionados para novas solicitações
<span class="font-semibold text-error">Dias bloqueados</span> não podem
ser selecionados para novas solicitações
</p>
</div>
{/if}
@@ -679,7 +743,9 @@
<!-- Informação do período selecionado -->
{#if dataInicio && dataFim && !readonly}
<div class="mt-6 card bg-gradient-to-br from-orange-50 to-amber-50 dark:from-orange-950 dark:to-amber-950 shadow-lg border-2 border-orange-500/30">
<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"
>
<div class="card-body">
<h3 class="card-title text-orange-700 dark:text-orange-400">
<svg
@@ -701,15 +767,21 @@
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-2">
<div>
<p class="text-sm text-base-content/70">Data Início</p>
<p class="font-bold text-lg">{new Date(dataInicio).toLocaleDateString("pt-BR")}</p>
<p class="font-bold text-lg">
{new Date(dataInicio).toLocaleDateString("pt-BR")}
</p>
</div>
<div>
<p class="text-sm text-base-content/70">Data Fim</p>
<p class="font-bold text-lg">{new Date(dataFim).toLocaleDateString("pt-BR")}</p>
<p class="font-bold text-lg">
{new Date(dataFim).toLocaleDateString("pt-BR")}
</p>
</div>
<div>
<p class="text-sm text-base-content/70">Total de Dias</p>
<p class="font-bold text-2xl text-orange-600 dark:text-orange-400">{calcularDias(dataInicio, dataFim)} dias</p>
<p class="font-bold text-2xl text-orange-600 dark:text-orange-400">
{calcularDias(dataInicio, dataFim)} dias
</p>
</div>
</div>
</div>
@@ -720,7 +792,12 @@
<style>
/* Calendário Premium */
.calendario-ausencias {
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-family:
"Inter",
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
}
/* Toolbar moderna com cores laranja/amarelo */
@@ -847,12 +924,18 @@
position: relative !important;
}
:global(.calendario-ausencias .fc-daygrid-day.fc-day-blocked .fc-daygrid-day-frame) {
:global(
.calendario-ausencias .fc-daygrid-day.fc-day-blocked .fc-daygrid-day-frame
) {
background-color: rgba(239, 68, 68, 0.2) !important;
border-color: rgba(239, 68, 68, 0.4) !important;
}
:global(.calendario-ausencias .fc-daygrid-day.fc-day-blocked .fc-daygrid-day-number) {
:global(
.calendario-ausencias
.fc-daygrid-day.fc-day-blocked
.fc-daygrid-day-number
) {
color: #dc2626 !important;
font-weight: 700 !important;
text-decoration: line-through !important;
@@ -917,4 +1000,3 @@
}
}
</style>