From 5469c50d90fd1d480377d301537da6110e9ea693 Mon Sep 17 00:00:00 2001 From: killer-cf Date: Thu, 30 Oct 2025 14:55:51 -0300 Subject: [PATCH] feat: add svelte-sonner dependency and enhance NotificationBell component - Added `svelte-sonner` to dependencies for improved notification handling. - Refactored the `NotificationBell.svelte` component for better readability and maintainability, including code formatting and structure improvements. - Updated `package.json` and `bun.lock` to reflect the new dependency. --- .../components/chat/NotificationBell.svelte | 170 ++- apps/web/src/lib/stores/auth.svelte.ts | 22 +- .../routes/(dashboard)/perfil/+page.svelte | 1200 +++++++++++++---- .../(dashboard)/ti/usuarios/+page.svelte | 354 +++-- bun.lock | 1 + package.json | 3 +- packages/backend/convex/ferias.ts | 190 +-- packages/backend/convex/funcionarios.ts | 12 +- 8 files changed, 1393 insertions(+), 559 deletions(-) diff --git a/apps/web/src/lib/components/chat/NotificationBell.svelte b/apps/web/src/lib/components/chat/NotificationBell.svelte index b86c35c..b7056b4 100644 --- a/apps/web/src/lib/components/chat/NotificationBell.svelte +++ b/apps/web/src/lib/components/chat/NotificationBell.svelte @@ -5,18 +5,27 @@ import { formatDistanceToNow } from "date-fns"; import { ptBR } from "date-fns/locale"; import { onMount } from "svelte"; + import { authStore } from "$lib/stores/auth.svelte"; // Queries e Client const client = useConvexClient(); - const notificacoesQuery = useQuery(api.chat.obterNotificacoes, { apenasPendentes: true }); + const notificacoesQuery = useQuery(api.chat.obterNotificacoes, { + apenasPendentes: true, + }); const countQuery = useQuery(api.chat.contarNotificacoesNaoLidas, {}); let dropdownOpen = $state(false); let notificacoesFerias = $state([]); // Helpers para obter valores das queries - const count = $derived((typeof countQuery === 'number' ? countQuery : countQuery?.data) ?? 0); - const notificacoes = $derived((Array.isArray(notificacoesQuery) ? notificacoesQuery : notificacoesQuery?.data) ?? []); + const count = $derived( + (typeof countQuery === "number" ? countQuery : countQuery?.data) ?? 0 + ); + const notificacoes = $derived( + (Array.isArray(notificacoesQuery) + ? notificacoesQuery + : notificacoesQuery?.data) ?? [] + ); // Atualizar contador no store $effect(() => { @@ -27,11 +36,15 @@ // Buscar notificações de férias async function buscarNotificacoesFerias() { try { - const usuarioStore = await import("$lib/stores/auth.svelte").then(m => m.authStore); + const usuarioStore = authStore; + if (usuarioStore.usuario?._id) { - const notifsFerias = await client.query(api.ferias.obterNotificacoesNaoLidas, { - usuarioId: usuarioStore.usuario._id as any, - }); + const notifsFerias = await client.query( + api.ferias.obterNotificacoesNaoLidas, + { + usuarioId: usuarioStore.usuario._id as any, + } + ); notificacoesFerias = notifsFerias || []; } } catch (e) { @@ -61,19 +74,25 @@ await client.mutation(api.chat.marcarTodasNotificacoesLidas, {}); // Marcar todas as notificações de férias como lidas for (const notif of notificacoesFerias) { - await client.mutation(api.ferias.marcarComoLida, { notificacaoId: notif._id }); + await client.mutation(api.ferias.marcarComoLida, { + notificacaoId: notif._id, + }); } dropdownOpen = false; await buscarNotificacoesFerias(); } async function handleClickNotificacao(notificacaoId: string) { - await client.mutation(api.chat.marcarNotificacaoLida, { notificacaoId: notificacaoId as any }); + await client.mutation(api.chat.marcarNotificacaoLida, { + notificacaoId: notificacaoId as any, + }); dropdownOpen = false; } async function handleClickNotificacaoFerias(notificacaoId: string) { - await client.mutation(api.ferias.marcarComoLida, { notificacaoId: notificacaoId as any }); + await client.mutation(api.ferias.marcarComoLida, { + notificacaoId: notificacaoId as any, + }); await buscarNotificacoesFerias(); dropdownOpen = false; // Redirecionar para a página de férias @@ -98,43 +117,6 @@ }); - - - +

{authStore.usuario?.nome}

- + {#if funcionario?.descricaoCargo} -

- - +

+ + {funcionario.descricaoCargo}

{/if} - -

- - + +

+ + {authStore.usuario?.email}

- -
-
+ +
+
{authStore.usuario?.role?.nome || "Usuário"}
- + {#if meuTime} -
- - +
+ + {meuTime.nome}
{/if} - + {#if funcionario?.statusFerias === "em_ferias"} -
+
🏖️ Em Férias
{:else} -
+
✅ Ativo
{/if} @@ -360,45 +439,83 @@
-
+
- + - + {#if ehGestor} @@ -412,60 +529,122 @@
-
+

Seu Perfil

-

{authStore.usuario?.role?.nome || "Usuário"}

+

+ {authStore.usuario?.role?.nome || "Usuário"} +

- - + +
-
+

Seu Time

-

{meuTime?.nome || "Sem time"}

+

+ {meuTime?.nome || "Sem time"} +

- - + +
-
+

Status

-

{funcionario?.statusFerias === "em_ferias" ? "Em Férias" : "Ativo"}

+

+ {funcionario?.statusFerias === "em_ferias" + ? "Em Férias" + : "Ativo"} +

- - + +
-
+

Matrícula

-

{funcionario?.matricula || "---"}

+

+ {funcionario?.matricula || "---"} +

- - + +
@@ -475,46 +654,115 @@
-
-
+
+

- - + + Informações Pessoais

-
- - +
+ +
- Nome Completo -

{authStore.usuario?.nome}

+ Nome Completo +

+ {authStore.usuario?.nome} +

- +
- -
- - + +
+ +
- E-mail Institucional -

{authStore.usuario?.email}

+ E-mail Institucional +

+ {authStore.usuario?.email} +

- +
- -
- - + +
+ +
- Perfil de Acesso -
{authStore.usuario?.role?.nome || "Usuário"}
+ Perfil de Acesso +
+ {authStore.usuario?.role?.nome || "Usuário"} +
@@ -523,70 +771,168 @@ {#if funcionario} -
+

- - + + Dados Funcionais

-
- - +
+ +
- Matrícula -

{funcionario.matricula || "Não informada"}

+ Matrícula +

+ {funcionario.matricula || "Não informada"} +

- +
- -
- - + +
+ +
- CPF -

{funcionario.cpf}

+ CPF +

+ {funcionario.cpf} +

- +
- -
- - + +
+ +
- Time + Time {#if meuTime}
-
+
{meuTime.nome}
-

Gestor: {meuTime.gestor?.nome}

- {:else} -

Não atribuído a um time

+

+ Gestor: {meuTime.gestor?.nome} +

+ {:else} +

+ Não atribuído a um time +

{/if}
- +
- -
- - + +
+ +
- Status Atual + Status Atual {#if funcionario.statusFerias === "em_ferias"} -
🏖️ Em Férias
+
+ 🏖️ Em Férias +
{:else} -
✅ Ativo
+
+ ✅ Ativo +
{/if}
@@ -598,46 +944,105 @@ {#if ehGestor} -
+
-

- - +

+ + Times que Você Gerencia -
{meusTimesGestor.length}
+
+ {meusTimesGestor.length} +

- + {#if meusTimesGestor.length === 0}
- - + + Você não gerencia nenhum time no momento.
{:else} -
+
{#each meusTimesGestor as time} -
+
-

{time.nome}

-

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

-
-
+

+ {time.nome} +

+

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

+
+
- +
- - - - {time.membros?.length || 0} - membros + + + + {time.membros?.length || 0} + membros
-
+
Gestor
@@ -650,7 +1055,6 @@
{/if}
- {:else if abaAtiva === "minhas-ferias"}
@@ -658,62 +1062,110 @@ {#if funcionario} - +
{:else}
- - + +

Perfil de funcionário não encontrado

-
Seu usuário ainda não está associado a um cadastro de funcionário. Entre em contato com o RH.
-
-
+
+ Seu usuário ainda não está associado a um cadastro de + funcionário. Entre em contato com o RH. +
+
+
{/if} {:else} {#if funcionario} - mostrarFormSolicitar = false} + (mostrarFormSolicitar = false)} /> {/if} {/if}
- {:else if abaAtiva === "aprovar-ferias"}

- - + + Solicitações da Equipe -
{solicitacoesSubordinados.length}
+
+ {solicitacoesSubordinados.length} +

{#if solicitacoesSubordinados.length === 0}
- - + + - Nenhuma solicitação pendente no momento. + Nenhuma solicitação pendente no momento.
{:else}
@@ -733,20 +1185,36 @@ {#each solicitacoesSubordinados as solicitacao} -
{solicitacao.funcionario?.nome}
+
+ {solicitacao.funcionario?.nome} +
{#if solicitacao.time} -
+
{solicitacao.time.nome}
{/if} {solicitacao.anoReferencia} - {solicitacao.periodos.length} - {solicitacao.periodos.reduce((acc: number, p: any) => acc + p.diasCorridos, 0)} + {solicitacao.periodos.length} + {solicitacao.periodos.reduce( + (acc: number, p: any) => acc + p.diasCorridos, + 0 + )} -
+
{getStatusTexto(solicitacao.status)}
@@ -755,10 +1223,22 @@ @@ -766,11 +1246,28 @@ @@ -785,7 +1282,7 @@
{/if} -
+
{#if solicitacaoSelecionada} @@ -796,12 +1293,16 @@ solicitacao={solicitacaoSelecionada} gestorId={authStore.usuario._id} onSucesso={recarregar} - onCancelar={() => solicitacaoSelecionada = null} + onCancelar={() => (solicitacaoSelecionada = null)} /> {/if}
{/if} @@ -810,21 +1311,39 @@ {#if mostrarModalFoto} {/if} + + diff --git a/apps/web/src/routes/(dashboard)/ti/usuarios/+page.svelte b/apps/web/src/routes/(dashboard)/ti/usuarios/+page.svelte index cfde9d7..b10bee7 100644 --- a/apps/web/src/routes/(dashboard)/ti/usuarios/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/usuarios/+page.svelte @@ -9,65 +9,74 @@ const client = useConvexClient(); const usuarios = useQuery(api.usuarios.listar, {}); - + let filtroNome = $state(""); - let filtroStatus = $state<"todos" | "ativo" | "bloqueado" | "inativo">("todos"); + let filtroStatus = $state<"todos" | "ativo" | "bloqueado" | "inativo">( + "todos" + ); let usuarioSelecionado = $state(null); let modalAberto = $state(false); - let modalAcao = $state<"bloquear" | "desbloquear" | "reset" | "associar">("bloquear"); + let modalAcao = $state<"bloquear" | "desbloquear" | "reset" | "associar">( + "bloquear" + ); let motivo = $state(""); let processando = $state(false); - + // Modal de associar funcionário let modalAssociarAberto = $state(false); let usuarioParaAssociar = $state(null); let funcionarioSelecionadoId = $state(""); let buscaFuncionario = $state(""); - + // Query de funcionários - const funcionarios = useQuery(api.funcionarios.list, {}); + const funcionarios = useQuery(api.funcionarios.getAll, {}); // Usuários filtrados const usuariosFiltrados = $derived.by(() => { if (!usuarios?.data || !Array.isArray(usuarios.data)) return []; - - return usuarios.data.filter(u => { - const matchNome = !filtroNome || + + return usuarios.data.filter((u) => { + const matchNome = + !filtroNome || u.nome.toLowerCase().includes(filtroNome.toLowerCase()) || u.matricula.includes(filtroNome) || u.email?.toLowerCase().includes(filtroNome.toLowerCase()); - - const matchStatus = filtroStatus === "todos" || + + const matchStatus = + filtroStatus === "todos" || (filtroStatus === "ativo" && u.ativo && !u.bloqueado) || (filtroStatus === "bloqueado" && u.bloqueado) || (filtroStatus === "inativo" && !u.ativo); - + return matchNome && matchStatus; }); }); - + // Funcionários filtrados (sem associação ou disponíveis) const funcionariosFiltrados = $derived.by(() => { if (!funcionarios?.data || !Array.isArray(funcionarios.data)) return []; - - return funcionarios.data.filter(f => { - // Filtro por busca - const matchBusca = !buscaFuncionario || - f.nome.toLowerCase().includes(buscaFuncionario.toLowerCase()) || - f.cpf?.includes(buscaFuncionario) || - f.matricula?.includes(buscaFuncionario); - - return matchBusca; - }).sort((a, b) => a.nome.localeCompare(b.nome)); + + return funcionarios.data + .filter((f) => { + // Filtro por busca + const matchBusca = + !buscaFuncionario || + f.nome.toLowerCase().includes(buscaFuncionario.toLowerCase()) || + f.cpf?.includes(buscaFuncionario) || + f.matricula?.includes(buscaFuncionario); + + return matchBusca; + }) + .sort((a, b) => a.nome.localeCompare(b.nome)); }); const stats = $derived.by(() => { if (!usuarios?.data || !Array.isArray(usuarios.data)) return null; return { total: usuarios.data.length, - ativos: usuarios.data.filter(u => u.ativo && !u.bloqueado).length, - bloqueados: usuarios.data.filter(u => u.bloqueado).length, - inativos: usuarios.data.filter(u => !u.ativo).length + ativos: usuarios.data.filter((u) => u.ativo && !u.bloqueado).length, + bloqueados: usuarios.data.filter((u) => u.bloqueado).length, + inativos: usuarios.data.filter((u) => !u.ativo).length, }; }); @@ -83,31 +92,31 @@ usuarioSelecionado = null; motivo = ""; } - + function abrirModalAssociar(usuario: any) { usuarioParaAssociar = usuario; funcionarioSelecionadoId = usuario.funcionarioId || ""; buscaFuncionario = ""; modalAssociarAberto = true; } - + function fecharModalAssociar() { modalAssociarAberto = false; usuarioParaAssociar = null; funcionarioSelecionadoId = ""; buscaFuncionario = ""; } - + async function associarFuncionario() { if (!usuarioParaAssociar || !funcionarioSelecionadoId) return; - + processando = true; try { await client.mutation(api.usuarios.associarFuncionario, { usuarioId: usuarioParaAssociar._id as Id<"usuarios">, - funcionarioId: funcionarioSelecionadoId as Id<"funcionarios"> + funcionarioId: funcionarioSelecionadoId as Id<"funcionarios">, }); - + alert("Funcionário associado com sucesso!"); fecharModalAssociar(); } catch (error: any) { @@ -116,18 +125,19 @@ processando = false; } } - + async function desassociarFuncionario() { if (!usuarioParaAssociar) return; - - if (!confirm("Deseja realmente desassociar o funcionário deste usuário?")) return; - + + if (!confirm("Deseja realmente desassociar o funcionário deste usuário?")) + return; + processando = true; try { await client.mutation(api.usuarios.desassociarFuncionario, { - usuarioId: usuarioParaAssociar._id as Id<"usuarios"> + usuarioId: usuarioParaAssociar._id as Id<"usuarios">, }); - + alert("Funcionário desassociado com sucesso!"); fecharModalAssociar(); } catch (error: any) { @@ -139,32 +149,32 @@ async function executarAcao() { if (!usuarioSelecionado) return; - + if (!authStore.usuario) { alert("Usuário não autenticado"); return; } - + processando = true; try { if (modalAcao === "bloquear") { await client.mutation(api.usuarios.bloquearUsuario, { usuarioId: usuarioSelecionado._id as Id<"usuarios">, motivo, - bloqueadoPorId: authStore.usuario._id as Id<"usuarios"> + bloqueadoPorId: authStore.usuario._id as Id<"usuarios">, }); } else if (modalAcao === "desbloquear") { await client.mutation(api.usuarios.desbloquearUsuario, { usuarioId: usuarioSelecionado._id as Id<"usuarios">, - desbloqueadoPorId: authStore.usuario._id as Id<"usuarios"> + desbloqueadoPorId: authStore.usuario._id as Id<"usuarios">, }); } else if (modalAcao === "reset") { await client.mutation(api.usuarios.resetarSenhaUsuario, { usuarioId: usuarioSelecionado._id as Id<"usuarios">, - resetadoPorId: authStore.usuario._id as Id<"usuarios"> + resetadoPorId: authStore.usuario._id as Id<"usuarios">, }); } - + fecharModal(); } catch (error) { console.error("Erro ao executar ação:", error); @@ -183,8 +193,19 @@

Gerenciar usuários do sistema

- - + + Criar Usuário @@ -220,20 +241,24 @@ -
- +
- @@ -250,7 +275,7 @@

Usuários ({usuariosFiltrados.length})

- +
@@ -270,67 +295,138 @@
{usuario.nome} {usuario.email || "-"} - {#if usuario.funcionarioId} + {#if usuario.funcionario?._id}
- - + + Associado
{:else}
- - + + Não associado
{/if}
- +
- - + {#if usuario.bloqueado} - {:else} - {/if} - - @@ -356,14 +452,17 @@