From 01138b3e1c3d815194a36a12cf39f6fae5b493b5 Mon Sep 17 00:00:00 2001 From: killer-cf Date: Sat, 8 Nov 2025 10:11:40 -0300 Subject: [PATCH] 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. --- .../lib/components/AprovarAusencias.svelte | 64 +- .../components/CalendarioAfastamentos.svelte | 57 +- .../ausencias/CalendarioAusencias.svelte | 306 +++-- .../WizardSolicitacaoAusencia.svelte | 76 +- .../src/lib/components/chat/ChatList.svelte | 364 ++++-- .../components/chat/NotificationBell.svelte | 246 ++-- .../components/ferias/DashboardFerias.svelte | 107 +- .../ferias/WizardSolicitacaoFerias.svelte | 107 +- .../lib/components/ti/AlertConfigModal.svelte | 207 +++- .../components/ti/ReportGeneratorModal.svelte | 180 ++- .../components/ti/SystemMonitorCard.svelte | 354 ++++-- .../ti/SystemMonitorCardLocal.svelte | 1094 +++++++++++++---- apps/web/src/routes/(dashboard)/+page.svelte | 26 +- .../(dashboard)/gestao-pessoas/+page.svelte | 35 +- .../routes/(dashboard)/perfil/+page.svelte | 711 +++++++---- .../(dashboard)/recursos-humanos/+page.svelte | 80 +- .../funcionarios/[funcionarioId]/+page.svelte | 680 +++++++--- .../secretaria-executiva/+page.svelte | 31 +- .../(dashboard)/solicitar-acesso/+page.svelte | 134 +- .../src/routes/(dashboard)/ti/+page.svelte | 117 +- .../(dashboard)/ti/monitoramento/+page.svelte | 39 +- .../ti/painel-permissoes/+page.svelte | 285 ++--- .../routes/(dashboard)/ti/perfis/+page.svelte | 326 +++-- .../routes/(dashboard)/ti/times/+page.svelte | 956 ++++++++------ 24 files changed, 4655 insertions(+), 1927 deletions(-) diff --git a/apps/web/src/lib/components/AprovarAusencias.svelte b/apps/web/src/lib/components/AprovarAusencias.svelte index 68f5b53..49b6072 100644 --- a/apps/web/src/lib/components/AprovarAusencias.svelte +++ b/apps/web/src/lib/components/AprovarAusencias.svelte @@ -35,7 +35,7 @@ } const totalDias = $derived( - calcularDias(solicitacao.dataInicio, solicitacao.dataFim) + calcularDias(solicitacao.dataInicio, solicitacao.dataFim), ); async function aprovar() { @@ -52,10 +52,15 @@ if (onSucesso) onSucesso(); } catch (e) { const mensagemErro = e instanceof Error ? e.message : String(e); - + // Verificar se é erro de permissão - if (mensagemErro.includes("permissão") || mensagemErro.includes("permission") || mensagemErro.includes("Você não tem permissão")) { - mensagemErroModal = "Você não tem permissão para aprovar esta solicitação de ausência. Apenas o gestor responsável pelo time do funcionário pode realizar esta ação."; + if ( + mensagemErro.includes("permissão") || + mensagemErro.includes("permission") || + mensagemErro.includes("Você não tem permissão") + ) { + mensagemErroModal = + "Você não tem permissão para aprovar esta solicitação de ausência. Apenas o gestor responsável pelo time do funcionário pode realizar esta ação."; mostrarModalErro = true; } else { erro = mensagemErro; @@ -85,10 +90,15 @@ if (onSucesso) onSucesso(); } catch (e) { const mensagemErro = e instanceof Error ? e.message : String(e); - + // Verificar se é erro de permissão - if (mensagemErro.includes("permissão") || mensagemErro.includes("permission") || mensagemErro.includes("Você não tem permissão")) { - mensagemErroModal = "Você não tem permissão para reprovar esta solicitação de ausência. Apenas o gestor responsável pelo time do funcionário pode realizar esta ação."; + if ( + mensagemErro.includes("permissão") || + mensagemErro.includes("permission") || + mensagemErro.includes("Você não tem permissão") + ) { + mensagemErroModal = + "Você não tem permissão para reprovar esta solicitação de ausência. Apenas o gestor responsável pelo time do funcionário pode realizar esta ação."; mostrarModalErro = true; } else { erro = mensagemErro; @@ -125,7 +135,9 @@
-

Aprovar/Reprovar Ausência

+

+ Aprovar/Reprovar Ausência +

Analise a solicitação e tome uma decisão

@@ -154,14 +166,18 @@

Nome

-

{solicitacao.funcionario?.nome || "N/A"}

+

+ {solicitacao.funcionario?.nome || "N/A"} +

{#if solicitacao.time}

Time

{solicitacao.time.nome}
@@ -192,21 +208,33 @@ Período da Ausência
-
+
Data Início
-
+
{new Date(solicitacao.dataInicio).toLocaleDateString("pt-BR")}
-
+
Data Fim
-
+
{new Date(solicitacao.dataFim).toLocaleDateString("pt-BR")}
-
+
Total de Dias
-
+
{totalDias}
dias corridos
@@ -385,7 +413,8 @@ @@ -395,4 +424,3 @@ margin: 0 auto; } - diff --git a/apps/web/src/lib/components/CalendarioAfastamentos.svelte b/apps/web/src/lib/components/CalendarioAfastamentos.svelte index fc72de1..b419a67 100644 --- a/apps/web/src/lib/components/CalendarioAfastamentos.svelte +++ b/apps/web/src/lib/components/CalendarioAfastamentos.svelte @@ -155,45 +155,60 @@
-
+

Calendário de Afastamentos

- +
Filtrar:
diff --git a/apps/web/src/lib/components/ausencias/CalendarioAusencias.svelte b/apps/web/src/lib/components/ausencias/CalendarioAusencias.svelte index 355f50c..986e577 100644 --- a/apps/web/src/lib/components/ausencias/CalendarioAusencias.svelte +++ b/apps/web/src/lib/components/ausencias/CalendarioAusencias.svelte @@ -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 = { + 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}
- - - -
-

Como usar:

-
    -
  • Clique e arraste no calendário para selecionar o período de ausência
  • -
  • Você pode visualizar suas ausências já solicitadas no calendário
  • -
  • A data de início não pode ser no passado
  • -
-
+ + + +
+

Como usar:

+
    +
  • + Clique e arraste no calendário para selecionar o período de + ausência +
  • +
  • + Você pode visualizar suas ausências já solicitadas no calendário +
  • +
  • A data de início não pode ser no passado
  • +
+
- {#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", + )}

Atenção: Períodos Indisponíveis

-

Os dias marcados em vermelho estão bloqueados porque você já possui solicitações aprovadas ou aguardando aprovação para esses períodos.

-

Você não pode criar novas solicitações que sobreponham esses períodos. Escolha um período diferente.

+

+ Os dias marcados em vermelho + estão bloqueados porque você já possui solicitações + aprovadas + ou aguardando aprovação para esses períodos. +

+

+ Você não pode criar novas solicitações que sobreponham esses + períodos. Escolha um período diferente. +

@@ -647,30 +695,46 @@ {#if ausenciasExistentes.length > 0 || readonly}
-
-
- Aguardando Aprovação -
-
-
- Aprovado -
-
-
- Reprovado -
- {#if !readonly && ausenciasExistentes && ausenciasExistentes.filter(a => a.status === "aprovado" || a.status === "aguardando_aprovacao").length > 0} -
-
+
+
+ Aguardando Aprovação +
+
+
+ Aprovado +
+
+
+ Reprovado +
+ {#if !readonly && ausenciasExistentes && ausenciasExistentes.filter((a) => a.status === "aprovado" || a.status === "aguardando_aprovacao").length > 0} +
+
Dias Bloqueados (Indisponíveis)
{/if}
- - {#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}

- Dias bloqueados não podem ser selecionados para novas solicitações + Dias bloqueados não podem + ser selecionados para novas solicitações

{/if} @@ -679,7 +743,9 @@ {#if dataInicio && dataFim && !readonly} -
+

Data Início

-

{new Date(dataInicio).toLocaleDateString("pt-BR")}

+

+ {new Date(dataInicio).toLocaleDateString("pt-BR")} +

Data Fim

-

{new Date(dataFim).toLocaleDateString("pt-BR")}

+

+ {new Date(dataFim).toLocaleDateString("pt-BR")} +

Total de Dias

-

{calcularDias(dataInicio, dataFim)} dias

+

+ {calcularDias(dataInicio, dataFim)} dias +

@@ -720,7 +792,12 @@ - diff --git a/apps/web/src/lib/components/ausencias/WizardSolicitacaoAusencia.svelte b/apps/web/src/lib/components/ausencias/WizardSolicitacaoAusencia.svelte index 319c97f..5ce13b6 100644 --- a/apps/web/src/lib/components/ausencias/WizardSolicitacaoAusencia.svelte +++ b/apps/web/src/lib/components/ausencias/WizardSolicitacaoAusencia.svelte @@ -26,26 +26,31 @@ let dataFim = $state(""); let motivo = $state(""); let processando = $state(false); - + // Estados para modal de erro let mostrarModalErro = $state(false); let mensagemErroModal = $state(""); let detalhesErroModal = $state(""); // Buscar ausências existentes para exibir no calendário - const ausenciasExistentesQuery = useQuery(api.ausencias.listarMinhasSolicitacoes, { - funcionarioId, - }); + const ausenciasExistentesQuery = useQuery( + api.ausencias.listarMinhasSolicitacoes, + { + funcionarioId, + }, + ); // Filtrar apenas ausências aprovadas ou aguardando aprovação (que bloqueiam novas solicitações) const ausenciasExistentes = $derived( (ausenciasExistentesQuery?.data || []) - .filter((a) => a.status === "aprovado" || a.status === "aguardando_aprovacao") + .filter( + (a) => a.status === "aprovado" || a.status === "aguardando_aprovacao", + ) .map((a) => ({ - dataInicio: a.dataInicio, - dataFim: a.dataFim, + dataInicio: a.dataInicio, + dataFim: a.dataFim, status: a.status as "aguardando_aprovacao" | "aprovado", - })) + })), ); // Calcular dias selecionados @@ -117,14 +122,15 @@ }); toast.success("Solicitação de ausência criada com sucesso!"); - + if (onSucesso) { onSucesso(); } } catch (error) { console.error("Erro ao criar solicitação:", error); - const mensagemErro = error instanceof Error ? error.message : String(error); - + const mensagemErro = + error instanceof Error ? error.message : String(error); + // Verificar se é erro de sobreposição de período if ( mensagemErro.includes("Já existe uma solicitação") || @@ -149,7 +155,10 @@ detalhesErroModal = ""; } - function handlePeriodoSelecionado(periodo: { dataInicio: string; dataFim: string }) { + function handlePeriodoSelecionado(periodo: { + dataInicio: string; + dataFim: string; + }) { dataInicio = periodo.dataInicio; dataFim = periodo.dataFim; } @@ -158,7 +167,9 @@
-

Solicite uma ausência para assuntos particulares

+

+ Solicite uma ausência para assuntos particulares +

@@ -230,22 +241,25 @@

Selecione o Período

- Clique e arraste no calendário para selecionar o período de ausência + Clique e arraste no calendário para selecionar o período de + ausência

{#if ausenciasExistentesQuery === undefined}
- Carregando ausências existentes... + Carregando ausências existentes...
{:else} - + {/if} {#if dataInicio && dataFim} @@ -279,13 +293,16 @@

Informe o Motivo

- Descreva o motivo da sua solicitação de ausência (mínimo 10 caracteres) + Descreva o motivo da sua solicitação de ausência (mínimo 10 + caracteres)

{#if dataInicio && dataFim} -
+

Data Início

-

{new Date(dataInicio).toLocaleDateString("pt-BR")}

+

+ {new Date(dataInicio).toLocaleDateString("pt-BR")} +

Data Fim

-

{new Date(dataFim).toLocaleDateString("pt-BR")}

+

+ {new Date(dataFim).toLocaleDateString("pt-BR")} +

Total de Dias

-

+

{totalDias} dias

@@ -478,4 +501,3 @@ margin: 0 auto; } - diff --git a/apps/web/src/lib/components/chat/ChatList.svelte b/apps/web/src/lib/components/chat/ChatList.svelte index 003ecdb..2ee80b8 100644 --- a/apps/web/src/lib/components/chat/ChatList.svelte +++ b/apps/web/src/lib/components/chat/ChatList.svelte @@ -9,10 +9,10 @@ import NewConversationModal from "./NewConversationModal.svelte"; const client = useConvexClient(); - + // Buscar todos os usuários para o chat const usuarios = useQuery(api.usuarios.listarParaChat, {}); - + // Buscar o perfil do usuário logado const meuPerfil = useQuery(api.usuarios.obterPerfil, {}); @@ -24,54 +24,77 @@ // Debug: monitorar carregamento de dados $effect(() => { - console.log("📊 [ChatList] Usuários carregados:", usuarios?.data?.length || 0); - console.log("👤 [ChatList] Meu perfil:", meuPerfil?.data?.nome || "Carregando..."); - console.log("🆔 [ChatList] Meu ID:", meuPerfil?.data?._id || "Não encontrado"); + console.log( + "📊 [ChatList] Usuários carregados:", + usuarios?.data?.length || 0, + ); + console.log( + "👤 [ChatList] Meu perfil:", + meuPerfil?.data?.nome || "Carregando...", + ); + console.log( + "🆔 [ChatList] Meu ID:", + meuPerfil?.data?._id || "Não encontrado", + ); if (usuarios?.data) { const meuId = meuPerfil?.data?._id; const meusDadosNaLista = usuarios.data.find((u: any) => u._id === meuId); if (meusDadosNaLista) { - console.warn("⚠️ [ChatList] ATENÇÃO: Meu usuário está na lista do backend!", meusDadosNaLista.nome); + console.warn( + "⚠️ [ChatList] ATENÇÃO: Meu usuário está na lista do backend!", + meusDadosNaLista.nome, + ); } } }); const usuariosFiltrados = $derived.by(() => { if (!usuarios?.data || !Array.isArray(usuarios.data)) return []; - + // Se não temos o perfil ainda, retornar lista vazia para evitar mostrar usuários incorretos if (!meuPerfil?.data) { console.log("⏳ [ChatList] Aguardando perfil do usuário..."); return []; } - + const meuId = meuPerfil.data._id; - + // Filtrar o próprio usuário da lista (filtro de segurança no frontend) let listaFiltrada = usuarios.data.filter((u: any) => u._id !== meuId); - + // Log se ainda estiver na lista após filtro (não deveria acontecer) const aindaNaLista = listaFiltrada.find((u: any) => u._id === meuId); if (aindaNaLista) { - console.error("❌ [ChatList] ERRO: Meu usuário ainda está na lista após filtro!"); + console.error( + "❌ [ChatList] ERRO: Meu usuário ainda está na lista após filtro!", + ); } - + // Aplicar busca por nome/email/matrícula if (searchQuery.trim()) { const query = searchQuery.toLowerCase(); - listaFiltrada = listaFiltrada.filter((u: any) => - u.nome?.toLowerCase().includes(query) || - u.email?.toLowerCase().includes(query) || - u.matricula?.toLowerCase().includes(query) + listaFiltrada = listaFiltrada.filter( + (u: any) => + u.nome?.toLowerCase().includes(query) || + u.email?.toLowerCase().includes(query) || + u.matricula?.toLowerCase().includes(query), ); } - + // Ordenar: Online primeiro, depois por nome return listaFiltrada.sort((a: any, b: any) => { - const statusOrder = { online: 0, ausente: 1, externo: 2, em_reuniao: 3, offline: 4 }; - const statusA = statusOrder[a.statusPresenca as keyof typeof statusOrder] ?? 4; - const statusB = statusOrder[b.statusPresenca as keyof typeof statusOrder] ?? 4; - + const statusOrder = { + online: 0, + ausente: 1, + externo: 2, + em_reuniao: 3, + offline: 4, + }; + const statusA = + statusOrder[a.statusPresenca as keyof typeof statusOrder] ?? 4; + const statusB = + statusOrder[b.statusPresenca as keyof typeof statusOrder] ?? 4; + if (statusA !== statusB) return statusA - statusB; return a.nome.localeCompare(b.nome); }); @@ -101,19 +124,22 @@ try { processando = true; console.log("🔄 Clicou no usuário:", usuario.nome, "ID:", usuario._id); - + // Criar ou buscar conversa individual com este usuário console.log("📞 Chamando mutation criarOuBuscarConversaIndividual..."); - const conversaId = await client.mutation(api.chat.criarOuBuscarConversaIndividual, { - outroUsuarioId: usuario._id, - }); - + const conversaId = await client.mutation( + api.chat.criarOuBuscarConversaIndividual, + { + outroUsuarioId: usuario._id, + }, + ); + console.log("✅ Conversa criada/encontrada. ID:", conversaId); - + // Abrir a conversa console.log("📂 Abrindo conversa..."); abrirConversa(conversaId as any); - + console.log("✅ Conversa aberta com sucesso!"); } catch (error) { console.error("❌ Erro ao abrir conversa:", error); @@ -122,7 +148,9 @@ stack: error instanceof Error ? error.stack : undefined, usuario: usuario, }); - alert(`Erro ao abrir conversa: ${error instanceof Error ? error.message : String(error)}`); + alert( + `Erro ao abrir conversa: ${error instanceof Error ? error.message : String(error)}`, + ); } finally { processando = false; } @@ -142,19 +170,17 @@ // Filtrar conversas por tipo e busca const conversasFiltradas = $derived(() => { if (!conversas?.data) return []; - - let lista = conversas.data.filter((c: any) => - c.tipo === "grupo" || c.tipo === "sala_reuniao" + + let lista = conversas.data.filter( + (c: any) => c.tipo === "grupo" || c.tipo === "sala_reuniao", ); - + // Aplicar busca if (searchQuery.trim()) { const query = searchQuery.toLowerCase(); - lista = lista.filter((c: any) => - c.nome?.toLowerCase().includes(query) - ); + lista = lista.filter((c: any) => c.nome?.toLowerCase().includes(query)); } - + return lista; }); @@ -165,7 +191,9 @@ abrirConversa(conversa._id); } catch (error) { console.error("Erro ao abrir conversa:", error); - alert(`Erro ao abrir conversa: ${error instanceof Error ? error.message : String(error)}`); + alert( + `Erro ao abrir conversa: ${error instanceof Error ? error.message : String(error)}`, + ); } finally { processando = false; } @@ -218,7 +246,7 @@ 💬 Conversas ({conversasFiltradas().length})

- +
@@ -247,17 +279,21 @@
{#if activeTab === "usuarios"} - {#if usuarios?.data && usuariosFiltrados.length > 0} - {#each usuariosFiltrados as usuario (usuario._id)} - - {/each} - {:else if !usuarios?.data} - -
- -
- {:else} - -
- + {/each} + {:else if !usuarios?.data} + +
+ +
+ {:else} + +
- - -

Nenhum usuário encontrado

-
+ + + +

Nenhum usuário encontrado

+
{/if} {:else} @@ -341,23 +388,48 @@ {#each conversasFiltradas() as conversa (conversa._id)} - -{#if modalOpen} -
- -
-

Notificações

-
- {#if notificacoesNaoLidas.length > 0} + + {#if modalOpen} +
+ +
+

Notificações

+
+ {#if notificacoesNaoLidas.length > 0} + + {/if} + {#if todasNotificacoes.length > 0} + + {/if} - {/if} - {#if todasNotificacoes.length > 0} - - {/if} - +
-
- -
+ +
{#if todasNotificacoes.length > 0 || notificacoesFerias.length > 0 || notificacoesAusencias.length > 0} {#if notificacoesNaoLidas.length > 0}
-

Não lidas

+

+ Não lidas +

{#each notificacoesNaoLidas as notificacao (notificacao._id)}
+ {/if}
- diff --git a/apps/web/src/lib/components/ferias/DashboardFerias.svelte b/apps/web/src/lib/components/ferias/DashboardFerias.svelte index 3b8fe56..b9f05c1 100644 --- a/apps/web/src/lib/components/ferias/DashboardFerias.svelte +++ b/apps/web/src/lib/components/ferias/DashboardFerias.svelte @@ -12,17 +12,29 @@ // Queries const saldosQuery = useQuery(api.saldoFerias.listarSaldos, { funcionarioId }); - const solicitacoesQuery = useQuery(api.ferias.listarMinhasSolicitacoes, { funcionarioId }); + const solicitacoesQuery = useQuery(api.ferias.listarMinhasSolicitacoes, { + funcionarioId, + }); const saldos = $derived(saldosQuery.data || []); const solicitacoes = $derived(solicitacoesQuery.data || []); // Estatísticas derivadas - const saldoAtual = $derived(saldos.find((s) => s.anoReferencia === new Date().getFullYear())); + const saldoAtual = $derived( + saldos.find((s) => s.anoReferencia === new Date().getFullYear()), + ); const totalSolicitacoes = $derived(solicitacoes.length); - const aprovadas = $derived(solicitacoes.filter((s) => s.status === "aprovado" || s.status === "data_ajustada_aprovada").length); - const pendentes = $derived(solicitacoes.filter((s) => s.status === "aguardando_aprovacao").length); - const reprovadas = $derived(solicitacoes.filter((s) => s.status === "reprovado").length); + const aprovadas = $derived( + solicitacoes.filter( + (s) => s.status === "aprovado" || s.status === "data_ajustada_aprovada", + ).length, + ); + const pendentes = $derived( + solicitacoes.filter((s) => s.status === "aguardando_aprovacao").length, + ); + const reprovadas = $derived( + solicitacoes.filter((s) => s.status === "reprovado").length, + ); // Canvas para gráfico de pizza let canvasSaldo = $state(); @@ -31,7 +43,7 @@ // Função para desenhar gráfico de pizza moderno function desenharGraficoPizza( canvas: HTMLCanvasElement, - dados: { label: string; valor: number; cor: string }[] + dados: { label: string; valor: number; cor: string }[], ) { const ctx = canvas.getContext("2d"); if (!ctx) return; @@ -90,7 +102,11 @@ desenharGraficoPizza(canvasSaldo, [ { label: "Usado", valor: saldoAtual.diasUsados, cor: "#ff6b6b" }, { label: "Pendente", valor: saldoAtual.diasPendentes, cor: "#ffa94d" }, - { label: "Disponível", valor: saldoAtual.diasDisponiveis, cor: "#51cf66" }, + { + label: "Disponível", + valor: saldoAtual.diasDisponiveis, + cor: "#51cf66", + }, ]); } @@ -107,10 +123,14 @@
-

+

📊 Dashboard de Férias

-

Visualize seus saldos e histórico de solicitações

+

+ Visualize seus saldos e histórico de solicitações +

{#if saldosQuery.isLoading || solicitacoesQuery.isLoading} @@ -125,7 +145,7 @@
Disponível
-
{saldoAtual?.diasDisponiveis || 0}
+
+ {saldoAtual?.diasDisponiveis || 0} +
dias para usar
Usado
-
{saldoAtual?.diasUsados || 0}
+
+ {saldoAtual?.diasUsados || 0} +
dias já gozados
Pendentes
-
{saldoAtual?.diasPendentes || 0}
+
+ {saldoAtual?.diasPendentes || 0} +
aguardando aprovação
Total Direito
-
{saldoAtual?.diasDireito || 0}
+
+ {saldoAtual?.diasDireito || 0} +
dias no ano
@@ -246,15 +274,21 @@
- Disponível: {saldoAtual.diasDisponiveis} dias + Disponível: {saldoAtual.diasDisponiveis} dias
- Pendente: {saldoAtual.diasPendentes} dias + Pendente: {saldoAtual.diasPendentes} dias
- Usado: {saldoAtual.diasUsados} dias + Usado: {saldoAtual.diasUsados} dias
{:else} @@ -283,7 +317,9 @@

📋 Status de Solicitações -
Total: {totalSolicitacoes}
+
+ Total: {totalSolicitacoes} +

{#if totalSolicitacoes > 0} @@ -300,15 +336,19 @@
- Aprovadas: {aprovadas} + Aprovadas: {aprovadas}
- Pendentes: {pendentes} + Pendentes: {pendentes}
- Reprovadas: {reprovadas} + Reprovadas: {reprovadas}
{:else} @@ -356,9 +396,20 @@ {saldo.anoReferencia} {saldo.diasDireito} dias - {saldo.diasUsados} - {saldo.diasPendentes} - {saldo.diasDisponiveis} + {saldo.diasUsados} + {saldo.diasPendentes} + {saldo.diasDisponiveis} {#if saldo.status === "ativo"} Ativo @@ -390,5 +441,3 @@ image-rendering: crisp-edges; } - - diff --git a/apps/web/src/lib/components/ferias/WizardSolicitacaoFerias.svelte b/apps/web/src/lib/components/ferias/WizardSolicitacaoFerias.svelte index 35ab325..7fb1a4f 100644 --- a/apps/web/src/lib/components/ferias/WizardSolicitacaoFerias.svelte +++ b/apps/web/src/lib/components/ferias/WizardSolicitacaoFerias.svelte @@ -22,7 +22,11 @@ // Dados da solicitação let anoSelecionado = $state(new Date().getFullYear()); - let periodosFerias: Array<{ dataInicio: string; dataFim: string; dias: number }> = $state([]); + let periodosFerias: Array<{ + dataInicio: string; + dataFim: string; + dias: number; + }> = $state([]); let observacao = $state(""); let processando = $state(false); @@ -31,7 +35,7 @@ useQuery(api.saldoFerias.obterSaldo, { funcionarioId, anoReferencia: anoSelecionado, - }) + }), ); const validacaoQuery = $derived( @@ -44,14 +48,14 @@ dataFim: p.dataFim, })), }) - : { data: null } + : { data: null }, ); // Derivados const saldo = $derived(saldoQuery.data); const validacao = $derived(validacaoQuery.data); const totalDiasSelecionados = $derived( - periodosFerias.reduce((acc, p) => acc + p.dias, 0) + periodosFerias.reduce((acc, p) => acc + p.dias, 0), ); // Anos disponíveis (últimos 3 anos + próximo ano) @@ -61,9 +65,11 @@ }); // Configurações do calendário (baseado no saldo/regime) - const maxPeriodos = $derived(saldo?.regimeTrabalho?.includes("Servidor") ? 2 : 3); + const maxPeriodos = $derived( + saldo?.regimeTrabalho?.includes("Servidor") ? 2 : 3, + ); const minDiasPorPeriodo = $derived( - saldo?.regimeTrabalho?.includes("Servidor") ? 10 : 5 + saldo?.regimeTrabalho?.includes("Servidor") ? 10 : 5, ); // Funções @@ -154,7 +160,9 @@ class:border-primary={passoAtual === i + 1} class:bg-base-200={passoAtual < i + 1} class:text-base-content={passoAtual < i + 1} - style:box-shadow={passoAtual === i + 1 ? "0 0 20px rgba(102, 126, 234, 0.5)" : "none"} + style:box-shadow={passoAtual === i + 1 + ? "0 0 20px rgba(102, 126, 234, 0.5)" + : "none"} > {#if passoAtual > i + 1}
-

Ano & Saldo

+

+ Ano & Saldo +

-

Períodos

+

+ Períodos +

-

Confirmação

+

+ Confirmação +

@@ -207,7 +221,9 @@ {#if passoAtual === 1}
-

+

Escolha o Ano de Referência

@@ -231,14 +247,16 @@
{:else if saldo}

📊 Saldo de Férias {anoSelecionado}

-
+
Disponível
-
{saldo.diasDisponiveis}
+
+ {saldo.diasDisponiveis} +
para usar
@@ -321,7 +341,9 @@

{saldo.regimeTrabalho}

- Período aquisitivo: {new Date(saldo.dataInicio).toLocaleDateString("pt-BR")} + Período aquisitivo: {new Date( + saldo.dataInicio, + ).toLocaleDateString("pt-BR")} a {new Date(saldo.dataFim).toLocaleDateString("pt-BR")}

@@ -371,7 +393,9 @@ {#if passoAtual === 2}
-

+

Selecione os Períodos de Férias

@@ -393,7 +417,8 @@

Saldo disponível: - {saldo?.diasDisponiveis || 0} dias | Selecionados: + {saldo?.diasDisponiveis || 0} dias | + Selecionados: {totalDiasSelecionados} dias | Restante: {(saldo?.diasDisponiveis || 0) - totalDiasSelecionados} dias

@@ -405,10 +430,10 @@ periodosExistentes={periodosFerias} onPeriodoAdicionado={handlePeriodoAdicionado} onPeriodoRemovido={handlePeriodoRemovido} - maxPeriodos={maxPeriodos} - minDiasPorPeriodo={minDiasPorPeriodo} - modoVisualizacao="month"> - + {maxPeriodos} + {minDiasPorPeriodo} + modoVisualizacao="month" + > {#if validacao && periodosFerias.length > 0} @@ -428,7 +453,9 @@ d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> - ✅ Períodos válidos! Total: {validacao.totalDias} dias + ✅ Períodos válidos! Total: {validacao.totalDias} dias
{:else}
@@ -489,7 +516,9 @@ {#if passoAtual === 3}
-

+

Confirme sua Solicitação

@@ -506,7 +535,9 @@
Total de Dias
-
{totalDiasSelecionados}
+
+ {totalDiasSelecionados} +
@@ -521,11 +552,14 @@

- {new Date(periodo.dataInicio).toLocaleDateString("pt-BR", { - day: "2-digit", - month: "long", - year: "numeric", - })} + {new Date(periodo.dataInicio).toLocaleDateString( + "pt-BR", + { + day: "2-digit", + month: "long", + year: "numeric", + }, + )} até {new Date(periodo.dataFim).toLocaleDateString("pt-BR", { day: "2-digit", @@ -533,7 +567,9 @@ year: "numeric", })}

-

{periodo.dias} dias corridos

+

+ {periodo.dias} dias corridos +

{/each} @@ -542,7 +578,9 @@
-
- -
- - -
-
- -
-

- Campos marcados com * são obrigatórios. + + +

+ Gerenciar Perfis de Acesso +

+

+ {perfilSendoEditado + ? "Atualize as informações do perfil selecionado para manter a governança de acesso alinhada com as diretrizes do sistema." + : "Crie um novo perfil de acesso definindo nome, descrição e nível hierárquico conforme os padrões adotados pela Secretaria."}

-
- - +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+

+ Campos marcados com * são obrigatórios. +

+
+ + +
-
{/if} diff --git a/apps/web/src/routes/(dashboard)/ti/perfis/+page.svelte b/apps/web/src/routes/(dashboard)/ti/perfis/+page.svelte index 0da0cea..6232f81 100644 --- a/apps/web/src/routes/(dashboard)/ti/perfis/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/perfis/+page.svelte @@ -39,12 +39,12 @@ // Estatísticas const stats = $derived.by(() => { if (carregando) return null; - + const porNivel = { - 0: roles.filter(r => r.nivel === 0).length, - 1: roles.filter(r => r.nivel === 1).length, - 2: roles.filter(r => r.nivel === 2).length, - 3: roles.filter(r => r.nivel >= 3).length, + 0: roles.filter((r) => r.nivel === 0).length, + 1: roles.filter((r) => r.nivel === 1).length, + 2: roles.filter((r) => r.nivel === 2).length, + 3: roles.filter((r) => r.nivel >= 3).length, }; return { @@ -53,7 +53,7 @@ nivelAlto: porNivel[1], nivelMedio: porNivel[2], nivelBaixo: porNivel[3], - comSetor: roles.filter(r => r.setor).length, + comSetor: roles.filter((r) => r.setor).length, }; }); @@ -66,7 +66,7 @@ resultado = resultado.filter( (r) => r.nome.toLowerCase().includes(buscaLower) || - r.descricao.toLowerCase().includes(buscaLower) + r.descricao.toLowerCase().includes(buscaLower), ); } @@ -138,10 +138,15 @@ filtroNivel = ""; } - const temFiltrosAtivos = $derived(busca.trim() !== "" || filtroSetor !== "" || filtroNivel !== ""); + const temFiltrosAtivos = $derived( + busca.trim() !== "" || filtroSetor !== "" || filtroNivel !== "", + ); - +
@@ -164,7 +169,9 @@

Gestão de Perfis

-

Visualize e gerencie os perfis de acesso do sistema

+

+ Visualize e gerencie os perfis de acesso do sistema +

@@ -172,37 +179,39 @@ {#if stats}
- - - - - 0 + ? ((stats.comSetor / stats.total) * 100).toFixed(0) + "% do total" + : "0%"} Icon={Building2} color="secondary" /> @@ -232,7 +241,11 @@

Filtros de Busca

{#if temFiltrosAtivos} - {/if} @@ -378,12 +413,19 @@ {:else}
{#each rolesFiltradas as role} -
abrirDetalhes(role)}> +
abrirDetalhes(role)} + >

{role.descricao}

-
{obterTextoNivel(role.nivel)}
+
+ {obterTextoNivel(role.nivel)} +
- +
- Nome técnico: - {role.nome} + Nome técnico: + {role.nome}
- + {#if role.setor} -
+
{role.setor}
{/if} - +
-
-
-
+
-

{roleSelecionada.descricao}

+

+ {roleSelecionada.descricao} +

-
{obterTextoNivel(roleSelecionada.nivel)}
- Nível {roleSelecionada.nivel} +
+ {obterTextoNivel(roleSelecionada.nivel)} +
+ Nível {roleSelecionada.nivel}
@@ -546,23 +629,52 @@
- {roleSelecionada.nome} + {roleSelecionada.nome}
- +
@@ -583,26 +697,58 @@
- {roleSelecionada.nivel} -
{obterTextoNivel(roleSelecionada.nivel)}
+ {roleSelecionada.nivel} +
+ {obterTextoNivel(roleSelecionada.nivel)} +
- - + + - {roleSelecionada.nivel === 0 && "Acesso total irrestrito ao sistema. Pode realizar todas as operações sem restrições."} - {roleSelecionada.nivel === 1 && "Acesso alto com algumas restrições. Pode realizar a maioria das operações administrativas."} - {roleSelecionada.nivel === 2 && "Acesso médio com permissões configuráveis. Pode realizar operações padrão do sistema."} - {roleSelecionada.nivel >= 3 && "Acesso limitado com permissões específicas. Operações restritas conforme configuração."} + {roleSelecionada.nivel === 0 && + "Acesso total irrestrito ao sistema. Pode realizar todas as operações sem restrições."} + {roleSelecionada.nivel === 1 && + "Acesso alto com algumas restrições. Pode realizar a maioria das operações administrativas."} + {roleSelecionada.nivel === 2 && + "Acesso médio com permissões configuráveis. Pode realizar operações padrão do sistema."} + {roleSelecionada.nivel >= 3 && + "Acesso limitado com permissões específicas. Operações restritas conforme configuração."}
@@ -614,13 +760,26 @@
-

{formatarData(roleSelecionada._creationTime)}

+

+ {formatarData(roleSelecionada._creationTime)} +

@@ -642,7 +801,11 @@

Configuração de Permissões

- Para configurar permissões específicas deste perfil, acesse o Painel de Permissões. + Para configurar permissões específicas deste perfil, acesse o Painel de Permissões.

@@ -653,9 +816,25 @@ Fechar - - - + + + Configurar Permissões @@ -665,4 +844,3 @@
{/if} - diff --git a/apps/web/src/routes/(dashboard)/ti/times/+page.svelte b/apps/web/src/routes/(dashboard)/ti/times/+page.svelte index 4b87a11..5badc2a 100644 --- a/apps/web/src/routes/(dashboard)/ti/times/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/times/+page.svelte @@ -5,7 +5,7 @@ import ProtectedRoute from "$lib/components/ProtectedRoute.svelte"; import { goto } from "$app/navigation"; import type { Id, Doc } from "@sgse-app/backend/convex/_generated/dataModel"; - + // Tipos baseados nos retornos das queries do backend type Usuario = { _id: Id<"usuarios">; @@ -78,24 +78,26 @@ gestor: Gestor; membros: MembroTime[]; }; - + const client = useConvexClient(); - + // Queries const timesQuery = useQuery(api.times.listar, {}); const usuariosQuery = useQuery(api.usuarios.listar, {}); const funcionariosQuery = useQuery(api.funcionarios.getAll, {}); - + const times = $derived((timesQuery?.data || []) as TimeComDetalhes[]); const usuarios = $derived((usuariosQuery?.data || []) as Usuario[]); - const funcionarios = $derived((funcionariosQuery?.data || []) as Funcionario[]); - - const carregando = $derived( - timesQuery === undefined || - usuariosQuery === undefined || - funcionariosQuery === undefined + const funcionarios = $derived( + (funcionariosQuery?.data || []) as Funcionario[], ); - + + const carregando = $derived( + timesQuery === undefined || + usuariosQuery === undefined || + funcionariosQuery === undefined, + ); + // Estados let modoEdicao = $state(false); let timeEmEdicao = $state(null); @@ -104,22 +106,24 @@ let mostrarConfirmacaoExclusao = $state(false); let timeParaExcluir = $state(null); let processando = $state(false); - + // Form let formNome = $state(""); let formDescricao = $state(""); let formGestorId = $state(""); let formCor = $state("#3B82F6"); - + // Membros let membrosDisponiveis = $derived( funcionarios.filter((f: Funcionario) => { // Verificar se o funcionário já está em algum time ativo - const jaNaEquipe = timeParaMembros?.membros?.some((m: MembroTime) => m.funcionario?._id === f._id); + const jaNaEquipe = timeParaMembros?.membros?.some( + (m: MembroTime) => m.funcionario?._id === f._id, + ); return !jaNaEquipe; - }) + }), ); - + // Cores predefinidas const coresDisponiveis = [ "#3B82F6", // Blue @@ -131,16 +135,17 @@ "#14B8A6", // Teal "#F97316", // Orange ]; - + function novoTime() { modoEdicao = true; timeEmEdicao = null; formNome = ""; formDescricao = ""; formGestorId = ""; - formCor = coresDisponiveis[Math.floor(Math.random() * coresDisponiveis.length)]; + formCor = + coresDisponiveis[Math.floor(Math.random() * coresDisponiveis.length)]; } - + function editarTime(time: TimeComDetalhes) { modoEdicao = true; timeEmEdicao = time; @@ -149,7 +154,7 @@ formGestorId = time.gestorId; formCor = time.cor || "#3B82F6"; } - + function cancelarEdicao() { modoEdicao = false; timeEmEdicao = null; @@ -158,13 +163,13 @@ formGestorId = ""; formCor = "#3B82F6"; } - + async function salvarTime() { if (!formNome.trim() || !formGestorId) { alert("Preencha todos os campos obrigatórios!"); return; } - + processando = true; try { if (timeEmEdicao) { @@ -191,15 +196,15 @@ processando = false; } } - + function confirmarExclusao(time: TimeComDetalhes) { timeParaExcluir = time; mostrarConfirmacaoExclusao = true; } - + async function excluirTime() { if (!timeParaExcluir) return; - + processando = true; try { await client.mutation(api.times.desativar, { id: timeParaExcluir._id }); @@ -212,7 +217,7 @@ processando = false; } } - + async function abrirGerenciarMembros(time: TimeComDetalhes) { const detalhes = await client.query(api.times.obterPorId, { id: time._id }); if (detalhes) { @@ -220,19 +225,21 @@ mostrarModalMembros = true; } } - + async function adicionarMembro(funcionarioId: string) { if (!timeParaMembros) return; - + processando = true; try { await client.mutation(api.times.adicionarMembro, { timeId: timeParaMembros._id, funcionarioId: funcionarioId as Id<"funcionarios">, }); - + // Recarregar detalhes do time - const detalhes = await client.query(api.times.obterPorId, { id: timeParaMembros._id }); + const detalhes = await client.query(api.times.obterPorId, { + id: timeParaMembros._id, + }); if (detalhes) { timeParaMembros = detalhes as TimeComMembros; } @@ -243,17 +250,21 @@ processando = false; } } - + async function removerMembro(membroId: string) { if (!confirm("Deseja realmente remover este membro do time?")) return; - + processando = true; try { - await client.mutation(api.times.removerMembro, { membroId: membroId as Id<"timesMembros"> }); - + await client.mutation(api.times.removerMembro, { + membroId: membroId as Id<"timesMembros">, + }); + // Recarregar detalhes do time if (timeParaMembros) { - const detalhes = await client.query(api.times.obterPorId, { id: timeParaMembros._id }); + const detalhes = await client.query(api.times.obterPorId, { + id: timeParaMembros._id, + }); if (detalhes) { timeParaMembros = detalhes as TimeComMembros; } @@ -265,350 +276,599 @@ processando = false; } } - + function fecharModalMembros() { mostrarModalMembros = false; timeParaMembros = null; } - +
- - - - -
-
-
-
- - - -
-
-

Gestão de Times

-

Organize funcionários em equipes e defina gestores

-
-
-
- - -
+ + -
- - {#if modoEdicao} -
-
-

- {timeEmEdicao ? "Editar Time" : "Novo Time"} -

- -
-
- - -
- -
- - -
- -
- - -
- -
- -
- {#each coresDisponiveis as cor} - - {/each} -
-
-
- -
- - -
-
-
- {/if} - - - {#if carregando} -
- -
- {:else} -
- {#each times.filter((t: TimeComDetalhes) => t.ativo) as time} -
-
-
-
-
-

{time.nome}

-
- -
- -

{time.descricao || "Sem descrição"}

- -
- -
-
- - - - Gestor: {time.gestor?.nome || "Não definido"} -
-
- - - - Membros: {time.totalMembros || 0} -
-
- -
- -
-
-
- {/each} - - {#if times.filter((t: TimeComDetalhes) => t.ativo).length === 0} -
-
- - + +
+
+
+
+ + -

Nenhum time cadastrado

-

Clique em "Novo Time" para criar seu primeiro time

+
+
+

+ Gestão de Times +

+

+ Organize funcionários em equipes e defina gestores +

- {/if} +
+ + +
+
- {/if} - - {#if mostrarModalMembros && timeParaMembros} - - + {/if} - - {#if mostrarConfirmacaoExclusao && timeParaExcluir} - - - {/if} -
+ + + {/if} +
-