From d0692c36083f7a74c26fbd4ac50a3f834af679d4 Mon Sep 17 00:00:00 2001 From: killer-cf Date: Tue, 4 Nov 2025 13:41:12 -0300 Subject: [PATCH 1/2] chore: update editorconfig and tool versions - Changed the indent size in .editorconfig from 4 to 2 spaces for consistency. - Updated Node.js version in .tool-versions from 25.0.0 to 22.21.1 to align with project requirements. --- .editorconfig | 2 +- .tool-versions | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index c1322dc..ebe51d3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ root = true [*] indent_style = space -indent_size = 4 +indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = false diff --git a/.tool-versions b/.tool-versions index 18964c8..668b21d 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -nodejs 25.0.0 +nodejs 22.21.1 From fbec5c46c2e7a7fe02af795fde0f2e5131b44e19 Mon Sep 17 00:00:00 2001 From: killer-cf Date: Tue, 4 Nov 2025 14:37:28 -0300 Subject: [PATCH 2/2] feat: enhance user management with matricula retrieval and validation - Updated user-related queries and mutations to retrieve the matricula from associated funcionario records, improving data accuracy. - Refactored user creation and listing functionalities to ensure matricula is correctly handled and displayed. - Enhanced error handling and validation for user operations, ensuring a more robust user management experience. - Improved the overall structure of user-related code for better maintainability and clarity. --- .../(dashboard)/ti/notificacoes/+page.svelte | 1341 ++++++++++++----- .../ti/usuarios/criar/+page.svelte | 103 +- packages/backend/convex/autenticacao.ts | 27 +- packages/backend/convex/chat.ts | 76 +- packages/backend/convex/logsAcesso.ts | 7 +- packages/backend/convex/logsAtividades.ts | 14 +- packages/backend/convex/schema.ts | 52 +- packages/backend/convex/seed.ts | 1 - packages/backend/convex/usuarios.ts | 158 +- .../backend/convex/verificarMatriculas.ts | 58 +- 10 files changed, 1250 insertions(+), 587 deletions(-) diff --git a/apps/web/src/routes/(dashboard)/ti/notificacoes/+page.svelte b/apps/web/src/routes/(dashboard)/ti/notificacoes/+page.svelte index 0c4776e..e2ffd97 100644 --- a/apps/web/src/routes/(dashboard)/ti/notificacoes/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/notificacoes/+page.svelte @@ -5,11 +5,11 @@ import { ptBR } from "date-fns/locale"; import { authStore } from "$lib/stores/auth.svelte"; import type { Id, Doc } from "@sgse-app/backend/convex/_generated/dataModel"; - + // Tipos para agendamentos type TipoAgendamento = "email" | "chat"; type StatusAgendamento = "agendado" | "enviado" | "cancelado"; - + interface AgendamentoEmail { _id: Id<"notificacoesEmail">; _creationTime: number; @@ -26,7 +26,7 @@ destinatarioInfo: Doc<"usuarios"> | null; templateInfo: Doc<"templatesMensagens"> | null; } - + interface AgendamentoChat { _id: Id<"mensagens">; _creationTime: number; @@ -38,35 +38,40 @@ conversaInfo: Doc<"conversas"> | null; destinatarioInfo: Doc<"usuarios"> | null; } - - type Agendamento = + + type Agendamento = | { tipo: "email"; dados: AgendamentoEmail } | { tipo: "chat"; dados: AgendamentoChat }; const client = useConvexClient(); - + // Queries const templatesQuery = useQuery(api.templatesMensagens.listarTemplates, {}); const usuariosQuery = useQuery(api.usuarios.listar, {}); - + // Mapa de emailIds para rastrear status let emailIdsRastreados = $state>(new Set()); - + // Query para buscar status dos emails - const emailIdsArray = $derived(Array.from(emailIdsRastreados).map(id => id as Id<"notificacoesEmail">)); + const emailIdsArray = $derived( + Array.from(emailIdsRastreados).map((id) => id as Id<"notificacoesEmail">), + ); const emailsStatusQuery = useQuery( api.email.buscarEmailsPorIds, - emailIdsArray.length > 0 ? { emailIds: emailIdsArray } : undefined + emailIdsArray.length > 0 ? { emailIds: emailIdsArray } : undefined, ); - + // Queries para agendamentos - const agendamentosEmailQuery = useQuery(api.email.listarAgendamentosEmail, {}); + const agendamentosEmailQuery = useQuery( + api.email.listarAgendamentosEmail, + {}, + ); const agendamentosChatQuery = useQuery(api.chat.listarAgendamentosChat, {}); - + // Filtro de agendamentos type FiltroAgendamento = "todos" | "agendados" | "enviados"; let filtroAgendamento = $state("todos"); - + // Extrair dados das queries de forma robusta const templates = $derived.by(() => { if (templatesQuery === undefined || templatesQuery === null) { @@ -96,25 +101,27 @@ // Estados de carregamento e erro const carregandoTemplates = $derived( - templatesQuery === undefined || templatesQuery === null + templatesQuery === undefined || templatesQuery === null, ); const carregandoUsuarios = $derived( - usuariosQuery === undefined || usuariosQuery === null + usuariosQuery === undefined || usuariosQuery === null, ); - + // Verificar erros de forma mais robusta const erroTemplates = $derived.by(() => { if (templatesQuery === undefined || templatesQuery === null) return false; // Verificar se é um objeto com propriedade error - if (typeof templatesQuery === 'object' && 'error' in templatesQuery) { - return templatesQuery.error !== undefined && templatesQuery.error !== null; + if (typeof templatesQuery === "object" && "error" in templatesQuery) { + return ( + templatesQuery.error !== undefined && templatesQuery.error !== null + ); } return false; }); - + const erroUsuarios = $derived.by(() => { if (usuariosQuery === undefined || usuariosQuery === null) return false; - if (typeof usuariosQuery === 'object' && 'error' in usuariosQuery) { + if (typeof usuariosQuery === "object" && "error" in usuariosQuery) { return usuariosQuery.error !== undefined && usuariosQuery.error !== null; } return false; @@ -137,7 +144,7 @@ let processando = $state(false); let criandoTemplates = $state(false); let progressoEnvio = $state({ total: 0, enviados: 0, falhas: 0 }); - + // Estrutura de dados para logs de envio type StatusLog = "sucesso" | "erro" | "fila" | "info" | "enviando"; type TipoLog = "chat" | "email"; @@ -149,19 +156,19 @@ mensagem: string; emailId?: string; // Para emails, guardar o ID para rastrear status }; - + let logsEnvio = $state([]); let terminalScrollRef: HTMLDivElement | null = $state(null); - + // Estados para agendamento let agendarEnvio = $state(false); let dataAgendamento = $state(""); let horaAgendamento = $state(""); - + // Calcular data/hora mínimas const now = new Date(); const minDate = format(now, "yyyy-MM-dd"); - + // Hora mínima recalculada baseada na data selecionada const horaMinima = $derived.by(() => { if (!dataAgendamento) return format(now, "HH:mm"); @@ -171,7 +178,7 @@ } return undefined; // Sem restrição se for data futura }); - + function getPreviewAgendamento(): string { if (!agendarEnvio || !dataAgendamento || !horaAgendamento) return ""; try { @@ -181,7 +188,7 @@ return ""; } } - + // Estados para modal de novo template let modalNovoTemplateAberto = $state(false); let codigoTemplate = $state(""); @@ -190,14 +197,17 @@ let corpoTemplate = $state(""); let variaveisTemplate = $state(""); let criandoNovoTemplate = $state(false); - + // Estado para mensagens de feedback - let mensagem = $state<{ tipo: "success" | "error" | "info"; texto: string } | null>(null); + let mensagem = $state<{ + tipo: "success" | "error" | "info"; + texto: string; + } | null>(null); const templateSelecionado = $derived( - templates.find(t => t._id === templateId) + templates.find((t) => t._id === templateId), ); - + // Função para mostrar mensagens function mostrarMensagem(tipo: "success" | "error" | "info", texto: string) { mensagem = { tipo, texto }; @@ -205,23 +215,32 @@ mensagem = null; }, 5000); } - + // Função para adicionar log ao terminal - function adicionarLog(tipo: TipoLog, destinatario: string, status: StatusLog, mensagem: string, emailId?: string) { - logsEnvio = [...logsEnvio, { - timestamp: Date.now(), - tipo, - destinatario, - status, - mensagem, - emailId, - }]; - + function adicionarLog( + tipo: TipoLog, + destinatario: string, + status: StatusLog, + mensagem: string, + emailId?: string, + ) { + logsEnvio = [ + ...logsEnvio, + { + timestamp: Date.now(), + tipo, + destinatario, + status, + mensagem, + emailId, + }, + ]; + // Adicionar emailId ao rastreamento se fornecido if (emailId) { emailIdsRastreados = new Set([...emailIdsRastreados, emailId]); } - + // Auto-scroll para o final setTimeout(() => { if (terminalScrollRef) { @@ -229,88 +248,102 @@ } }, 10); } - + // Atualizar logs quando status dos emails mudar $effect(() => { if (!emailsStatusQuery || emailsStatusQuery === undefined) return; - + // Extrair dados da query - const emails = Array.isArray(emailsStatusQuery) - ? emailsStatusQuery + const emails = Array.isArray(emailsStatusQuery) + ? emailsStatusQuery : "data" in emailsStatusQuery && Array.isArray(emailsStatusQuery.data) - ? emailsStatusQuery.data - : []; - + ? emailsStatusQuery.data + : []; + if (emails.length === 0) return; - + for (const email of emails) { if (!email) continue; - + // Encontrar logs relacionados a este email - const logsRelacionados = logsEnvio.filter(log => log.emailId === email._id); - + const logsRelacionados = logsEnvio.filter( + (log) => log.emailId === email._id, + ); + for (const log of logsRelacionados) { // Verificar se o status mudou - const novoStatus: StatusLog = - email.status === "enviado" ? "sucesso" : - email.status === "falha" ? "erro" : - email.status === "enviando" ? "enviando" : - email.status === "pendente" ? "fila" : - "info"; - + const novoStatus: StatusLog = + email.status === "enviado" + ? "sucesso" + : email.status === "falha" + ? "erro" + : email.status === "enviando" + ? "enviando" + : email.status === "pendente" + ? "fila" + : "info"; + // Se o status mudou, atualizar o log if (log.status !== novoStatus) { - const indice = logsEnvio.findIndex(l => l === log); + const indice = logsEnvio.findIndex((l) => l === log); if (indice !== -1) { - const novaMensagem = - email.status === "enviado" ? "Email enviado com sucesso" : - email.status === "falha" ? `Falha ao enviar: ${email.erroDetalhes || "Erro desconhecido"}` : - email.status === "enviando" ? "Enviando email..." : - email.status === "pendente" ? "Email em fila de envio" : - log.mensagem; - - logsEnvio = logsEnvio.map((l, i) => - i === indice + const novaMensagem = + email.status === "enviado" + ? "Email enviado com sucesso" + : email.status === "falha" + ? `Falha ao enviar: ${email.erroDetalhes || "Erro desconhecido"}` + : email.status === "enviando" + ? "Enviando email..." + : email.status === "pendente" + ? "Email em fila de envio" + : log.mensagem; + + logsEnvio = logsEnvio.map((l, i) => + i === indice ? { ...l, status: novoStatus, mensagem: novaMensagem } - : l + : l, ); } } } } }); - + // Função para limpar logs function limparLogs() { logsEnvio = []; emailIdsRastreados = new Set(); } - + // Extrair e processar agendamentos const agendamentosEmail = $derived.by(() => { - if (!agendamentosEmailQuery || agendamentosEmailQuery === undefined) return []; - const dados = Array.isArray(agendamentosEmailQuery) - ? agendamentosEmailQuery - : "data" in agendamentosEmailQuery && Array.isArray(agendamentosEmailQuery.data) - ? agendamentosEmailQuery.data - : []; + if (!agendamentosEmailQuery || agendamentosEmailQuery === undefined) + return []; + const dados = Array.isArray(agendamentosEmailQuery) + ? agendamentosEmailQuery + : "data" in agendamentosEmailQuery && + Array.isArray(agendamentosEmailQuery.data) + ? agendamentosEmailQuery.data + : []; return dados as AgendamentoEmail[]; }); - + const agendamentosChat = $derived.by(() => { - if (!agendamentosChatQuery || agendamentosChatQuery === undefined) return []; - const dados = Array.isArray(agendamentosChatQuery) - ? agendamentosChatQuery - : "data" in agendamentosChatQuery && Array.isArray(agendamentosChatQuery.data) - ? agendamentosChatQuery.data - : []; + if (!agendamentosChatQuery || agendamentosChatQuery === undefined) + return []; + const dados = Array.isArray(agendamentosChatQuery) + ? agendamentosChatQuery + : "data" in agendamentosChatQuery && + Array.isArray(agendamentosChatQuery.data) + ? agendamentosChatQuery.data + : []; return dados as AgendamentoChat[]; }); - + // Combinar e processar agendamentos const todosAgendamentos = $derived.by(() => { const agendamentos: Agendamento[] = []; - + for (const email of agendamentosEmail) { if (email.agendadaPara) { agendamentos.push({ @@ -319,7 +352,7 @@ }); } } - + for (const chat of agendamentosChat) { if (chat.agendadaPara) { agendamentos.push({ @@ -328,20 +361,26 @@ }); } } - + // Ordenar: futuros primeiro (mais próximos primeiro), depois passados (mais recentes primeiro) return agendamentos.sort((a, b) => { - const timestampA = a.tipo === "email" ? (a.dados.agendadaPara ?? 0) : (a.dados.agendadaPara ?? 0); - const timestampB = b.tipo === "email" ? (b.dados.agendadaPara ?? 0) : (b.dados.agendadaPara ?? 0); + const timestampA = + a.tipo === "email" + ? (a.dados.agendadaPara ?? 0) + : (a.dados.agendadaPara ?? 0); + const timestampB = + b.tipo === "email" + ? (b.dados.agendadaPara ?? 0) + : (b.dados.agendadaPara ?? 0); const agora = Date.now(); - + const aFuturo = timestampA > agora; const bFuturo = timestampB > agora; - + // Futuros primeiro if (aFuturo && !bFuturo) return -1; if (!aFuturo && bFuturo) return 1; - + // Dentro do mesmo grupo, ordenar por timestamp if (aFuturo) { // Futuros: mais próximos primeiro @@ -352,57 +391,77 @@ } }); }); - + // Filtrar agendamentos const agendamentosFiltrados = $derived.by(() => { if (filtroAgendamento === "todos") return todosAgendamentos; - - return todosAgendamentos.filter(ag => { + + return todosAgendamentos.filter((ag) => { const status = obterStatusAgendamento(ag); if (filtroAgendamento === "agendados") return status === "agendado"; if (filtroAgendamento === "enviados") return status === "enviado"; return true; }); }); - + // Função para obter status do agendamento function obterStatusAgendamento(agendamento: Agendamento): StatusAgendamento { if (agendamento.tipo === "email") { const email = agendamento.dados; if (email.status === "enviado") return "enviado"; - if (email.agendadaPara && email.agendadaPara <= Date.now()) return "enviado"; + if (email.agendadaPara && email.agendadaPara <= Date.now()) + return "enviado"; return "agendado"; } else { const chat = agendamento.dados; - if (chat.agendadaPara && chat.agendadaPara <= Date.now()) return "enviado"; + if (chat.agendadaPara && chat.agendadaPara <= Date.now()) + return "enviado"; return "agendado"; } } - + // Função para cancelar agendamento async function cancelarAgendamento(agendamento: Agendamento) { if (!confirm("Tem certeza que deseja cancelar este agendamento?")) { return; } - + try { if (agendamento.tipo === "email") { - const resultado = await client.mutation(api.email.cancelarAgendamentoEmail, { - emailId: agendamento.dados._id, - }); + const resultado = await client.mutation( + api.email.cancelarAgendamentoEmail, + { + emailId: agendamento.dados._id, + }, + ); if (resultado.sucesso) { - mostrarMensagem("success", "Agendamento de email cancelado com sucesso!"); + mostrarMensagem( + "success", + "Agendamento de email cancelado com sucesso!", + ); } else { - mostrarMensagem("error", resultado.erro || "Erro ao cancelar agendamento"); + mostrarMensagem( + "error", + resultado.erro || "Erro ao cancelar agendamento", + ); } } else { - const resultado = await client.mutation(api.chat.cancelarMensagemAgendada, { - mensagemId: agendamento.dados._id, - }); + const resultado = await client.mutation( + api.chat.cancelarMensagemAgendada, + { + mensagemId: agendamento.dados._id, + }, + ); if (resultado.sucesso) { - mostrarMensagem("success", "Agendamento de chat cancelado com sucesso!"); + mostrarMensagem( + "success", + "Agendamento de chat cancelado com sucesso!", + ); } else { - mostrarMensagem("error", resultado.erro || "Erro ao cancelar agendamento"); + mostrarMensagem( + "error", + resultado.erro || "Erro ao cancelar agendamento", + ); } } } catch (error) { @@ -410,32 +469,39 @@ mostrarMensagem("error", `Erro ao cancelar agendamento: ${erro}`); } } - + // Função para obter nome do destinatário function obterNomeDestinatario(agendamento: Agendamento): string { if (agendamento.tipo === "email") { - return agendamento.dados.destinatarioInfo?.nome || agendamento.dados.destinatario || "Usuário"; + return ( + agendamento.dados.destinatarioInfo?.nome || + agendamento.dados.destinatario || + "Usuário" + ); } else { return agendamento.dados.destinatarioInfo?.nome || "Usuário"; } } - + // Função para formatar data/hora do agendamento function formatarDataAgendamento(agendamento: Agendamento): string { - const timestamp = agendamento.tipo === "email" - ? agendamento.dados.agendadaPara - : agendamento.dados.agendadaPara; - + const timestamp = + agendamento.tipo === "email" + ? agendamento.dados.agendadaPara + : agendamento.dados.agendadaPara; + if (!timestamp) return "N/A"; - - return format(new Date(timestamp), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR }); + + return format(new Date(timestamp), "dd/MM/yyyy 'às' HH:mm", { + locale: ptBR, + }); } - + // Função para formatar timestamp function formatarTimestamp(timestamp: number): string { return format(new Date(timestamp), "HH:mm:ss", { locale: ptBR }); } - + // Função para obter cor do status function obterCorStatus(status: StatusLog): string { switch (status) { @@ -456,12 +522,18 @@ async function criarTemplatesPadrao() { if (criandoTemplates) return; - + criandoTemplates = true; try { - const resultado = await client.mutation(api.templatesMensagens.criarTemplatesPadrao, {}); + const resultado = await client.mutation( + api.templatesMensagens.criarTemplatesPadrao, + {}, + ); if (resultado.sucesso) { - mostrarMensagem("success", "Templates padrão criados com sucesso! A página será recarregada."); + mostrarMensagem( + "success", + "Templates padrão criados com sucesso! A página será recarregada.", + ); setTimeout(() => { window.location.reload(); }, 2000); @@ -493,7 +565,10 @@ async function salvarNovoTemplate() { if (!authStore.usuario) { - mostrarMensagem("error", "Você precisa estar autenticado para criar templates."); + mostrarMensagem( + "error", + "Você precisa estar autenticado para criar templates.", + ); return; } @@ -518,19 +593,22 @@ // Processar variáveis (separadas por vírgula ou espaço) const variaveis = variaveisTemplate .split(/[,;\s]+/) - .map(v => v.trim()) - .filter(v => v.length > 0); + .map((v) => v.trim()) + .filter((v) => v.length > 0); criandoNovoTemplate = true; try { - const resultado = await client.mutation(api.templatesMensagens.criarTemplate, { - codigo: codigoTemplate.trim().toUpperCase().replace(/\s+/g, "_"), - nome: nomeTemplate.trim(), - titulo: tituloTemplate.trim(), - corpo: corpoTemplate.trim(), - variaveis: variaveis.length > 0 ? variaveis : undefined, - criadoPorId: authStore.usuario._id as Id<"usuarios">, - }); + const resultado = await client.mutation( + api.templatesMensagens.criarTemplate, + { + codigo: codigoTemplate.trim().toUpperCase().replace(/\s+/g, "_"), + nome: nomeTemplate.trim(), + titulo: tituloTemplate.trim(), + corpo: corpoTemplate.trim(), + variaveis: variaveis.length > 0 ? variaveis : undefined, + criadoPorId: authStore.usuario._id as Id<"usuarios">, + }, + ); if (resultado.sucesso) { mostrarMensagem("success", "Template criado com sucesso!"); @@ -540,7 +618,10 @@ window.location.reload(); }, 1500); } else { - mostrarMensagem("error", "Erro ao criar template: " + (resultado.erro || "Erro desconhecido")); + mostrarMensagem( + "error", + "Erro ao criar template: " + (resultado.erro || "Erro desconhecido"), + ); } } catch (error) { const erro = error instanceof Error ? error.message : "Erro desconhecido"; @@ -553,7 +634,10 @@ async function enviarNotificacao() { if (!enviarParaTodos && !destinatarioId) { - mostrarMensagem("error", "Selecione um destinatário ou marque 'Enviar para todos'"); + mostrarMensagem( + "error", + "Selecione um destinatário ou marque 'Enviar para todos'", + ); return; } @@ -574,7 +658,7 @@ mostrarMensagem("error", "Preencha a data e hora para agendamento"); return; } - + try { const dataHora = new Date(`${dataAgendamento}T${horaAgendamento}`); if (dataHora.getTime() <= Date.now()) { @@ -590,31 +674,43 @@ processando = true; progressoEnvio = { total: 0, enviados: 0, falhas: 0 }; - + // Limpar logs anteriores quando iniciar novo envio logsEnvio = []; emailIdsRastreados = new Set(); - + try { // Obter lista de destinatários - const destinatarios: typeof usuarios = enviarParaTodos - ? usuarios - : usuarios.filter(u => u._id === destinatarioId); + const destinatarios: typeof usuarios = enviarParaTodos + ? usuarios + : usuarios.filter((u) => u._id === destinatarioId); if (destinatarios.length === 0) { - adicionarLog("email", "Sistema", "erro", "Nenhum destinatário encontrado"); + adicionarLog( + "email", + "Sistema", + "erro", + "Nenhum destinatário encontrado", + ); mostrarMensagem("error", "Nenhum destinatário encontrado"); return; } progressoEnvio.total = destinatarios.length; - + // Log inicial - const tipoMensagem = usarTemplate ? `Template: ${templateSelecionado?.nome || ""}` : "Mensagem personalizada"; - const destinatariosText = enviarParaTodos - ? `Todos os usuários (${destinatarios.length})` - : destinatarios.map(d => d.nome).join(", "); - adicionarLog("email", "Sistema", "info", `Iniciando envio de notificação via ${canal} para ${destinatariosText} - ${tipoMensagem}`); + const tipoMensagem = usarTemplate + ? `Template: ${templateSelecionado?.nome || ""}` + : "Mensagem personalizada"; + const destinatariosText = enviarParaTodos + ? `Todos os usuários (${destinatarios.length})` + : destinatarios.map((d) => d.nome).join(", "); + adicionarLog( + "email", + "Sistema", + "info", + `Iniciando envio de notificação via ${canal} para ${destinatariosText} - ${tipoMensagem}`, + ); // Se for envio para um único usuário if (destinatarios.length === 1) { @@ -625,43 +721,81 @@ // ENVIAR PARA CHAT if (canal === "chat" || canal === "ambos") { try { - adicionarLog("chat", destinatario.nome, "enviando", "Criando/buscando conversa..."); + adicionarLog( + "chat", + destinatario.nome, + "enviando", + "Criando/buscando conversa...", + ); const conversaResult = await client.mutation( api.chat.criarOuBuscarConversaIndividual, - { outroUsuarioId: destinatario._id as Id<"usuarios"> } + { outroUsuarioId: destinatario._id as Id<"usuarios"> }, ); if (conversaResult.conversaId) { - const mensagem = usarTemplate + const mensagem = usarTemplate ? templateSelecionado?.corpo || "" : mensagemPersonalizada; if (agendadaPara) { // Agendar mensagem - adicionarLog("chat", destinatario.nome, "info", "Agendando mensagem..."); - resultadoChat = await client.mutation(api.chat.agendarMensagem, { - conversaId: conversaResult.conversaId, - conteudo: mensagem, - agendadaPara: agendadaPara, - }); - const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR }); - adicionarLog("chat", destinatario.nome, "sucesso", `Mensagem agendada para ${dataFormatada}`); + adicionarLog( + "chat", + destinatario.nome, + "info", + "Agendando mensagem...", + ); + resultadoChat = await client.mutation( + api.chat.agendarMensagem, + { + conversaId: conversaResult.conversaId, + conteudo: mensagem, + agendadaPara: agendadaPara, + }, + ); + const dataFormatada = format( + new Date(agendadaPara), + "dd/MM/yyyy 'às' HH:mm", + { locale: ptBR }, + ); + adicionarLog( + "chat", + destinatario.nome, + "sucesso", + `Mensagem agendada para ${dataFormatada}`, + ); } else { // Envio imediato - adicionarLog("chat", destinatario.nome, "enviando", "Enviando mensagem..."); + adicionarLog( + "chat", + destinatario.nome, + "enviando", + "Enviando mensagem...", + ); resultadoChat = await client.mutation(api.chat.enviarMensagem, { conversaId: conversaResult.conversaId, conteudo: mensagem, tipo: "texto", permitirNotificacaoParaSiMesmo: true, }); - adicionarLog("chat", destinatario.nome, "sucesso", "Mensagem enviada com sucesso"); + adicionarLog( + "chat", + destinatario.nome, + "sucesso", + "Mensagem enviada com sucesso", + ); } } else { - adicionarLog("chat", destinatario.nome, "erro", "Falha ao criar/buscar conversa"); + adicionarLog( + "chat", + destinatario.nome, + "erro", + "Falha ao criar/buscar conversa", + ); } } catch (error) { - const erro = error instanceof Error ? error.message : "Erro desconhecido"; + const erro = + error instanceof Error ? error.message : "Erro desconhecido"; console.error("Erro ao enviar chat:", error); adicionarLog("chat", destinatario.nome, "erro", `Erro: ${erro}`); } @@ -671,70 +805,138 @@ if (canal === "email" || canal === "ambos") { if (destinatario.email) { try { - adicionarLog("email", destinatario.nome, "enviando", `Enfileirando email para ${destinatario.email}...`); + adicionarLog( + "email", + destinatario.nome, + "enviando", + `Enfileirando email para ${destinatario.email}...`, + ); if (usarTemplate && templateId) { const template = templateSelecionado; if (template) { - resultadoEmail = await client.mutation(api.email.enviarEmailComTemplate, { - destinatario: destinatario.email, - destinatarioId: destinatario._id as Id<"usuarios">, - templateCodigo: template.codigo, - variaveis: { - nome: destinatario.nome, - matricula: destinatario.matricula, + resultadoEmail = await client.mutation( + api.email.enviarEmailComTemplate, + { + destinatario: destinatario.email, + destinatarioId: destinatario._id as Id<"usuarios">, + templateCodigo: template.codigo, + variaveis: { + nome: destinatario.nome, + matricula: destinatario.matricula, + }, + enviadoPorId: authStore.usuario._id as Id<"usuarios">, + agendadaPara: agendadaPara, }, - enviadoPorId: authStore.usuario._id as Id<"usuarios">, - agendadaPara: agendadaPara, - }); + ); if (resultadoEmail?.sucesso && resultadoEmail?.emailId) { if (agendadaPara) { - const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR }); - adicionarLog("email", destinatario.nome, "fila", `Email agendado para ${dataFormatada}`, resultadoEmail.emailId); + const dataFormatada = format( + new Date(agendadaPara), + "dd/MM/yyyy 'às' HH:mm", + { locale: ptBR }, + ); + adicionarLog( + "email", + destinatario.nome, + "fila", + `Email agendado para ${dataFormatada}`, + resultadoEmail.emailId, + ); } else { - adicionarLog("email", destinatario.nome, "fila", "Email enfileirado para envio", resultadoEmail.emailId); + adicionarLog( + "email", + destinatario.nome, + "fila", + "Email enfileirado para envio", + resultadoEmail.emailId, + ); } } else { - adicionarLog("email", destinatario.nome, "erro", "Falha ao enfileirar email"); + adicionarLog( + "email", + destinatario.nome, + "erro", + "Falha ao enfileirar email", + ); } } else { - adicionarLog("email", destinatario.nome, "erro", "Template não encontrado"); + adicionarLog( + "email", + destinatario.nome, + "erro", + "Template não encontrado", + ); } } else { - resultadoEmail = await client.mutation(api.email.enfileirarEmail, { - destinatario: destinatario.email, - destinatarioId: destinatario._id as Id<"usuarios">, - assunto: "Notificação do Sistema", - corpo: mensagemPersonalizada, - enviadoPorId: authStore.usuario._id as Id<"usuarios">, - agendadaPara: agendadaPara, - }); + resultadoEmail = await client.mutation( + api.email.enfileirarEmail, + { + destinatario: destinatario.email, + destinatarioId: destinatario._id as Id<"usuarios">, + assunto: "Notificação do Sistema", + corpo: mensagemPersonalizada, + enviadoPorId: authStore.usuario._id as Id<"usuarios">, + agendadaPara: agendadaPara, + }, + ); if (resultadoEmail?.sucesso && resultadoEmail?.emailId) { if (agendadaPara) { - const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR }); - adicionarLog("email", destinatario.nome, "fila", `Email agendado para ${dataFormatada}`, resultadoEmail.emailId); + const dataFormatada = format( + new Date(agendadaPara), + "dd/MM/yyyy 'às' HH:mm", + { locale: ptBR }, + ); + adicionarLog( + "email", + destinatario.nome, + "fila", + `Email agendado para ${dataFormatada}`, + resultadoEmail.emailId, + ); } else { - adicionarLog("email", destinatario.nome, "fila", "Email enfileirado para envio", resultadoEmail.emailId); + adicionarLog( + "email", + destinatario.nome, + "fila", + "Email enfileirado para envio", + resultadoEmail.emailId, + ); } } else { - adicionarLog("email", destinatario.nome, "erro", "Falha ao enfileirar email"); + adicionarLog( + "email", + destinatario.nome, + "erro", + "Falha ao enfileirar email", + ); } } } catch (error) { - const erro = error instanceof Error ? error.message : "Erro desconhecido"; + const erro = + error instanceof Error ? error.message : "Erro desconhecido"; console.error("Erro ao enviar email:", error); adicionarLog("email", destinatario.nome, "erro", `Erro: ${erro}`); } } else { - adicionarLog("email", destinatario.nome, "erro", "Destinatário não possui email cadastrado"); + adicionarLog( + "email", + destinatario.nome, + "erro", + "Destinatário não possui email cadastrado", + ); } } // Feedback de sucesso - let mensagemSucesso = agendadaPara + let mensagemSucesso = agendadaPara ? `Notificação agendada com sucesso!` : "Notificação enviada com sucesso!"; if (agendadaPara) { - const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR }); + const dataFormatada = format( + new Date(agendadaPara), + "dd/MM/yyyy 'às' HH:mm", + { locale: ptBR }, + ); mensagemSucesso += ` Será enviada em: ${dataFormatada}`; } else { if (canal === "ambos") { @@ -761,23 +963,33 @@ let falhasChat = 0; let falhasEmail = 0; - adicionarLog("email", "Sistema", "info", `Processando ${destinatarios.length} destinatários...`); + adicionarLog( + "email", + "Sistema", + "info", + `Processando ${destinatarios.length} destinatários...`, + ); for (const destinatario of destinatarios) { try { // ENVIAR PARA CHAT if (canal === "chat" || canal === "ambos") { try { - adicionarLog("chat", destinatario.nome, "enviando", "Processando..."); + adicionarLog( + "chat", + destinatario.nome, + "enviando", + "Processando...", + ); const conversaResult = await client.mutation( api.chat.criarOuBuscarConversaIndividual, - { outroUsuarioId: destinatario._id as Id<"usuarios"> } + { outroUsuarioId: destinatario._id as Id<"usuarios"> }, ); if (conversaResult.conversaId) { // Para templates, usar corpo direto (o backend já faz substituição via email) // Para mensagem personalizada, usar diretamente - const mensagem = usarTemplate + const mensagem = usarTemplate ? templateSelecionado?.corpo || "" : mensagemPersonalizada; @@ -787,8 +999,17 @@ conteudo: mensagem, agendadaPara: agendadaPara, }); - const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR }); - adicionarLog("chat", destinatario.nome, "sucesso", `Agendado para ${dataFormatada}`); + const dataFormatada = format( + new Date(agendadaPara), + "dd/MM/yyyy 'às' HH:mm", + { locale: ptBR }, + ); + adicionarLog( + "chat", + destinatario.nome, + "sucesso", + `Agendado para ${dataFormatada}`, + ); } else { await client.mutation(api.chat.enviarMensagem, { conversaId: conversaResult.conversaId, @@ -796,17 +1017,36 @@ tipo: "texto", permitirNotificacaoParaSiMesmo: true, }); - adicionarLog("chat", destinatario.nome, "sucesso", "Enviado com sucesso"); + adicionarLog( + "chat", + destinatario.nome, + "sucesso", + "Enviado com sucesso", + ); } sucessosChat++; } else { - adicionarLog("chat", destinatario.nome, "erro", "Falha ao criar/buscar conversa"); + adicionarLog( + "chat", + destinatario.nome, + "erro", + "Falha ao criar/buscar conversa", + ); falhasChat++; } } catch (error) { - const erro = error instanceof Error ? error.message : "Erro desconhecido"; - console.error(`Erro ao enviar chat para ${destinatario.nome}:`, error); - adicionarLog("chat", destinatario.nome, "erro", `Erro: ${erro}`); + const erro = + error instanceof Error ? error.message : "Erro desconhecido"; + console.error( + `Erro ao enviar chat para ${destinatario.nome}:`, + error, + ); + adicionarLog( + "chat", + destinatario.nome, + "erro", + `Erro: ${erro}`, + ); falhasChat++; } } @@ -815,95 +1055,176 @@ if (canal === "email" || canal === "ambos") { if (destinatario.email) { try { - adicionarLog("email", destinatario.nome, "enviando", `Enfileirando email para ${destinatario.email}...`); + adicionarLog( + "email", + destinatario.nome, + "enviando", + `Enfileirando email para ${destinatario.email}...`, + ); if (usarTemplate && templateId) { const template = templateSelecionado; if (template) { - const resultadoEmail = await client.mutation(api.email.enviarEmailComTemplate, { - destinatario: destinatario.email, - destinatarioId: destinatario._id as Id<"usuarios">, - templateCodigo: template.codigo, - variaveis: { - nome: destinatario.nome, - matricula: destinatario.matricula || "", + const resultadoEmail = await client.mutation( + api.email.enviarEmailComTemplate, + { + destinatario: destinatario.email, + destinatarioId: destinatario._id as Id<"usuarios">, + templateCodigo: template.codigo, + variaveis: { + nome: destinatario.nome, + matricula: destinatario.matricula || "", + }, + enviadoPorId: authStore.usuario._id as Id<"usuarios">, + agendadaPara: agendadaPara, }, - enviadoPorId: authStore.usuario._id as Id<"usuarios">, - agendadaPara: agendadaPara, - }); + ); if (resultadoEmail?.sucesso && resultadoEmail?.emailId) { if (agendadaPara) { - const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR }); - adicionarLog("email", destinatario.nome, "fila", `Agendado para ${dataFormatada}`, resultadoEmail.emailId); + const dataFormatada = format( + new Date(agendadaPara), + "dd/MM/yyyy 'às' HH:mm", + { locale: ptBR }, + ); + adicionarLog( + "email", + destinatario.nome, + "fila", + `Agendado para ${dataFormatada}`, + resultadoEmail.emailId, + ); } else { - adicionarLog("email", destinatario.nome, "fila", "Enfileirado para envio", resultadoEmail.emailId); + adicionarLog( + "email", + destinatario.nome, + "fila", + "Enfileirado para envio", + resultadoEmail.emailId, + ); } sucessosEmail++; } else { - adicionarLog("email", destinatario.nome, "erro", "Falha ao enfileirar email"); + adicionarLog( + "email", + destinatario.nome, + "erro", + "Falha ao enfileirar email", + ); falhasEmail++; } } else { - adicionarLog("email", destinatario.nome, "erro", "Template não encontrado"); + adicionarLog( + "email", + destinatario.nome, + "erro", + "Template não encontrado", + ); falhasEmail++; } } else { - const resultadoEmail = await client.mutation(api.email.enfileirarEmail, { - destinatario: destinatario.email, - destinatarioId: destinatario._id as Id<"usuarios">, - assunto: "Notificação do Sistema", - corpo: mensagemPersonalizada, - enviadoPorId: authStore.usuario._id as Id<"usuarios">, - agendadaPara: agendadaPara, - }); + const resultadoEmail = await client.mutation( + api.email.enfileirarEmail, + { + destinatario: destinatario.email, + destinatarioId: destinatario._id as Id<"usuarios">, + assunto: "Notificação do Sistema", + corpo: mensagemPersonalizada, + enviadoPorId: authStore.usuario._id as Id<"usuarios">, + agendadaPara: agendadaPara, + }, + ); if (resultadoEmail?.sucesso && resultadoEmail?.emailId) { if (agendadaPara) { - const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR }); - adicionarLog("email", destinatario.nome, "fila", `Agendado para ${dataFormatada}`, resultadoEmail.emailId); + const dataFormatada = format( + new Date(agendadaPara), + "dd/MM/yyyy 'às' HH:mm", + { locale: ptBR }, + ); + adicionarLog( + "email", + destinatario.nome, + "fila", + `Agendado para ${dataFormatada}`, + resultadoEmail.emailId, + ); } else { - adicionarLog("email", destinatario.nome, "fila", "Enfileirado para envio", resultadoEmail.emailId); + adicionarLog( + "email", + destinatario.nome, + "fila", + "Enfileirado para envio", + resultadoEmail.emailId, + ); } sucessosEmail++; } else { - adicionarLog("email", destinatario.nome, "erro", "Falha ao enfileirar email"); + adicionarLog( + "email", + destinatario.nome, + "erro", + "Falha ao enfileirar email", + ); falhasEmail++; } } } catch (error) { - const erro = error instanceof Error ? error.message : "Erro desconhecido"; - console.error(`Erro ao enviar email para ${destinatario.nome}:`, error); - adicionarLog("email", destinatario.nome, "erro", `Erro: ${erro}`); + const erro = + error instanceof Error + ? error.message + : "Erro desconhecido"; + console.error( + `Erro ao enviar email para ${destinatario.nome}:`, + error, + ); + adicionarLog( + "email", + destinatario.nome, + "erro", + `Erro: ${erro}`, + ); falhasEmail++; } } else { - adicionarLog("email", destinatario.nome, "erro", "Destinatário não possui email cadastrado"); + adicionarLog( + "email", + destinatario.nome, + "erro", + "Destinatário não possui email cadastrado", + ); falhasEmail++; } } progressoEnvio.enviados++; } catch (error) { - console.error(`Erro geral ao enviar para ${destinatario.nome}:`, error); + console.error( + `Erro geral ao enviar para ${destinatario.nome}:`, + error, + ); progressoEnvio.falhas++; } } // Feedback de envio em massa - let mensagemMassa = agendadaPara + let mensagemMassa = agendadaPara ? `Agendamento em massa concluído! ` : `Envio em massa concluído! `; - + if (agendadaPara) { - const dataFormatada = format(new Date(agendadaPara), "dd/MM/yyyy 'às' HH:mm", { locale: ptBR }); + const dataFormatada = format( + new Date(agendadaPara), + "dd/MM/yyyy 'às' HH:mm", + { locale: ptBR }, + ); mensagemMassa += `Será enviado em: ${dataFormatada}. `; } - + if (canal === "ambos") { - mensagemMassa += `Chat: ${sucessosChat} ${agendadaPara ? 'agendados' : 'enviados'}, ${falhasChat} falhas. `; - mensagemMassa += `Email: ${sucessosEmail} ${agendadaPara ? 'agendados' : 'enviados'}, ${falhasEmail} falhas.`; + mensagemMassa += `Chat: ${sucessosChat} ${agendadaPara ? "agendados" : "enviados"}, ${falhasChat} falhas. `; + mensagemMassa += `Email: ${sucessosEmail} ${agendadaPara ? "agendados" : "enviados"}, ${falhasEmail} falhas.`; } else if (canal === "chat") { - mensagemMassa += `Chat: ${sucessosChat} ${agendadaPara ? 'agendados' : 'enviados'}, ${falhasChat} falhas.`; + mensagemMassa += `Chat: ${sucessosChat} ${agendadaPara ? "agendados" : "enviados"}, ${falhasChat} falhas.`; } else if (canal === "email") { - mensagemMassa += `Email: ${sucessosEmail} ${agendadaPara ? 'agendados' : 'enviados'}, ${falhasEmail} falhas.`; + mensagemMassa += `Email: ${sucessosEmail} ${agendadaPara ? "agendados" : "enviados"}, ${falhasEmail} falhas.`; } // Adicionar log resumo @@ -937,13 +1258,28 @@
- - + +
-

Notificações e Mensagens

-

Enviar notificações para usuários do sistema

+

+ Notificações e Mensagens +

+

+ Enviar notificações para usuários do sistema +

@@ -989,7 +1325,7 @@ @@ -1001,7 +1337,7 @@

Enviar Notificação

- +
@@ -1009,18 +1345,20 @@ Destinatário *
- Chat @@ -1089,20 +1428,20 @@
@@ -1115,7 +1454,11 @@ - {#if carregandoTemplates} @@ -1133,8 +1476,18 @@ {#if templateSelecionado}
- - + +
{templateSelecionado.titulo}
@@ -1148,10 +1501,10 @@ -
@@ -1161,14 +1514,14 @@
- + {#if agendarEnvio}
@@ -1184,7 +1537,7 @@ disabled={processando} />
- +
- + {#if getPreviewAgendamento()}
- - + + {getPreviewAgendamento()}
@@ -1213,7 +1576,7 @@
- {/if}
-
- [{formatarTimestamp(log.timestamp)}] - + [{formatarTimestamp(log.timestamp)}] + {log.tipo.toUpperCase()} {log.destinatario}: @@ -1298,13 +1689,24 @@

Templates Disponíveis

- @@ -1327,7 +1729,11 @@

{template.titulo}

{template.corpo}

- + {template.tipo} {#if template.variaveis && template.variaveis.length > 0} @@ -1339,15 +1745,38 @@
{#if template.tipo !== "sistema"} {/if} @@ -1359,12 +1788,27 @@ {:else}
- - + + -

Nenhum template disponível

-

Clique no botão abaixo para criar os templates padrão do sistema.

- - - @@ -1426,11 +1898,26 @@ {#if agendamentosFiltrados.length === 0}
- - + + -

Nenhum agendamento encontrado

-

Os agendamentos aparecerão aqui quando você agendar envios.

+

+ Nenhum agendamento encontrado +

+

+ Os agendamentos aparecerão aqui quando você agendar envios. +

{:else} @@ -1452,22 +1939,46 @@ {@const nomeDestinatario = obterNomeDestinatario(agendamento)} {@const dataFormatada = formatarDataAgendamento(agendamento)} {@const podeCancelar = status === "agendado"} - {@const templateNome = agendamento.tipo === "email" && agendamento.dados.templateInfo - ? agendamento.dados.templateInfo.nome - : agendamento.tipo === "email" && agendamento.dados.templateId - ? "Template removido" - : "-"} + {@const templateNome = + agendamento.tipo === "email" && agendamento.dados.templateInfo + ? agendamento.dados.templateInfo.nome + : agendamento.tipo === "email" && + agendamento.dados.templateId + ? "Template removido" + : "-"}
{#if agendamento.tipo === "email"} - - + + Email {:else} - - + + Chat {/if} @@ -1476,21 +1987,32 @@
{nomeDestinatario}
{#if agendamento.tipo === "email"} -
{agendamento.dados.destinatario}
+
+ {agendamento.dados.destinatario} +
{/if}
{dataFormatada}
{#if podeCancelar} - {@const tempoRestante = agendamento.tipo === "email" - ? (agendamento.dados.agendadaPara ?? 0) - Date.now() - : (agendamento.dados.agendadaPara ?? 0) - Date.now()} - {@const horasRestantes = Math.floor(tempoRestante / (1000 * 60 * 60))} - {@const minutosRestantes = Math.floor((tempoRestante % (1000 * 60 * 60)) / (1000 * 60))} + {@const tempoRestante = + agendamento.tipo === "email" + ? (agendamento.dados.agendadaPara ?? 0) - Date.now() + : (agendamento.dados.agendadaPara ?? 0) - Date.now()} + {@const horasRestantes = Math.floor( + tempoRestante / (1000 * 60 * 60), + )} + {@const minutosRestantes = Math.floor( + (tempoRestante % (1000 * 60 * 60)) / (1000 * 60), + )} {#if horasRestantes < 1 && minutosRestantes < 60} -
Em {minutosRestantes} min
+
+ Em {minutosRestantes} min +
{:else if horasRestantes < 24} -
Em {horasRestantes}h {minutosRestantes}min
+
+ Em {horasRestantes}h {minutosRestantes}min +
{/if} {/if} @@ -1506,9 +2028,13 @@ {#if agendamento.tipo === "email"} {#if agendamento.dados.templateInfo} -
{agendamento.dados.templateInfo.nome}
+
+ {agendamento.dados.templateInfo.nome} +
{:else if agendamento.dados.templateId} -
Template removido
+
+ Template removido +
{:else}
-
{/if} @@ -1523,8 +2049,19 @@ class="btn btn-sm btn-error btn-outline" onclick={() => cancelarAgendamento(agendamento)} > - - + + Cancelar @@ -1543,10 +2080,23 @@
- - + + - Para enviar emails, certifique-se de configurar o SMTP em Configurações de Email. + Para enviar emails, certifique-se de configurar o SMTP em Configurações + de Email.
@@ -1555,7 +2105,7 @@ @@ -1630,27 +2190,30 @@ -
{/if} - diff --git a/apps/web/src/routes/(dashboard)/ti/usuarios/criar/+page.svelte b/apps/web/src/routes/(dashboard)/ti/usuarios/criar/+page.svelte index 87b8961..e36a233 100644 --- a/apps/web/src/routes/(dashboard)/ti/usuarios/criar/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/usuarios/criar/+page.svelte @@ -22,7 +22,6 @@ }); // Estados do formulário - let matricula = $state(""); let nome = $state(""); let email = $state(""); let roleId = $state(""); @@ -30,7 +29,9 @@ let senhaInicial = $state(""); let confirmarSenha = $state(""); let processando = $state(false); - let mensagem = $state<{ tipo: "success" | "error"; texto: string } | null>(null); + let mensagem = $state<{ tipo: "success" | "error"; texto: string } | null>( + null, + ); function mostrarMensagem(tipo: "success" | "error", texto: string) { mensagem = { tipo, texto }; @@ -43,8 +44,7 @@ e.preventDefault(); // Validações - const matriculaStr = String(matricula).trim(); - if (!matriculaStr || !nome.trim() || !email.trim() || !roleId || !senhaInicial) { + if (!nome.trim() || !email.trim() || !roleId || !senhaInicial) { mostrarMensagem("error", "Preencha todos os campos obrigatórios"); return; } @@ -63,11 +63,12 @@ try { const resultado = await client.mutation(api.usuarios.criar, { - matricula: matriculaStr, nome: nome.trim(), email: email.trim(), roleId: roleId as Id<"roles">, - funcionarioId: funcionarioId ? (funcionarioId as Id<"funcionarios">) : undefined, + funcionarioId: funcionarioId + ? (funcionarioId as Id<"funcionarios">) + : undefined, senhaInicial: senhaInicial, }); @@ -75,7 +76,7 @@ if (senhaGerada) { mostrarMensagem( "success", - `Usuário criado! SENHA TEMPORÁRIA: ${senhaGerada} - Anote esta senha, ela não será exibida novamente!` + `Usuário criado! SENHA TEMPORÁRIA: ${senhaGerada} - Anote esta senha, ela não será exibida novamente!`, ); setTimeout(() => { goto("/ti/usuarios"); @@ -102,17 +103,19 @@ // Auto-completar ao selecionar funcionário $effect(() => { if (funcionarioId && funcionarios?.data) { - const funcSelecionado = funcionarios.data.find((f: any) => f._id === funcionarioId); + const funcSelecionado = funcionarios.data.find( + (f: any) => f._id === funcionarioId, + ); if (funcSelecionado) { email = funcSelecionado.email || email; nome = funcSelecionado.nome || nome; - matricula = funcSelecionado.matricula || matricula; } } }); function gerarSenhaAleatoria() { - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@#$!"; + const chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@#$!"; let senha = ""; for (let i = 0; i < 12; i++) { senha += chars.charAt(Math.floor(Math.random() * chars.length)); @@ -154,8 +157,12 @@
-

Criar Novo Usuário

-

Cadastre um novo usuário no sistema

+

+ Criar Novo Usuário +

+

+ Cadastre um novo usuário no sistema +

@@ -248,7 +255,9 @@
- Ao selecionar, os campos serão preenchidos automaticamente + Ao selecionar, os campos serão preenchidos automaticamente
- -
- - -
-
-
+
@@ -341,7 +340,9 @@ {#if !roles?.data || !Array.isArray(roles.data)}
- Carregando perfis disponíveis... + Carregando perfis disponíveis...
{/if}
@@ -446,7 +447,9 @@

Senha Gerada:

- + {senhaGerada}

- ⚠️ IMPORTANTE: Anote esta senha! Você precisará repassá-la - manualmente ao usuário até que o SMTP seja configurado. + ⚠️ IMPORTANTE: Anote esta senha! Você precisará + repassá-la manualmente ao usuário até que o SMTP seja configurado.

@@ -500,18 +503,27 @@

Informações Importantes

-
- +
+ Cancelar -
- diff --git a/packages/backend/convex/autenticacao.ts b/packages/backend/convex/autenticacao.ts index 43dc379..44ae1ce 100644 --- a/packages/backend/convex/autenticacao.ts +++ b/packages/backend/convex/autenticacao.ts @@ -294,12 +294,19 @@ export const login = mutation({ timestamp: agora, }); + // Obter matrícula do funcionário se houver + let matricula: string | undefined = undefined; + if (usuario.funcionarioId) { + const funcionario = await ctx.db.get(usuario.funcionarioId); + matricula = funcionario?.matricula; + } + return { sucesso: true as const, token, usuario: { _id: usuario._id, - matricula: usuario.matricula, + matricula: matricula || "", nome: usuario.nome, email: usuario.email, funcionarioId: usuario.funcionarioId, @@ -568,12 +575,19 @@ export const loginComIP = internalMutation({ timestamp: agora, }); + // Obter matrícula do funcionário se houver + let matricula: string | undefined = undefined; + if (usuario.funcionarioId) { + const funcionario = await ctx.db.get(usuario.funcionarioId); + matricula = funcionario?.matricula; + } + return { sucesso: true as const, token, usuario: { _id: usuario._id, - matricula: usuario.matricula, + matricula: matricula || "", nome: usuario.nome, email: usuario.email, funcionarioId: usuario.funcionarioId, @@ -688,11 +702,18 @@ export const verificarSessao = query({ return { valido: false as const, motivo: "Role não encontrada" }; } + // Obter matrícula do funcionário se houver + let matricula: string | undefined = undefined; + if (usuario.funcionarioId) { + const funcionario = await ctx.db.get(usuario.funcionarioId); + matricula = funcionario?.matricula; + } + return { valido: true as const, usuario: { _id: usuario._id, - matricula: usuario.matricula, + matricula: matricula || "", nome: usuario.nome, email: usuario.email, funcionarioId: usuario.funcionarioId, diff --git a/packages/backend/convex/chat.ts b/packages/backend/convex/chat.ts index cac7a8d..56bb63d 100644 --- a/packages/backend/convex/chat.ts +++ b/packages/backend/convex/chat.ts @@ -94,7 +94,9 @@ export const criarConversa = mutation({ conversaId, remetenteId: usuarioAtual._id, titulo: "Adicionado a grupo", - descricao: `Você foi adicionado ao grupo "${args.nome || "Sem nome"}" por ${usuarioAtual.nome}`, + descricao: `Você foi adicionado ao grupo "${ + args.nome || "Sem nome" + }" por ${usuarioAtual.nome}`, lida: false, criadaEm: Date.now(), }); @@ -226,8 +228,9 @@ export const enviarMensagem = mutation({ for (const participanteId of conversa.participantes) { // ✅ MODIFICADO: Permite notificação para si mesmo se flag estiver ativa const ehOMesmoUsuario = participanteId === usuarioAtual._id; - const deveCriarNotificacao = !ehOMesmoUsuario || args.permitirNotificacaoParaSiMesmo; - + const deveCriarNotificacao = + !ehOMesmoUsuario || args.permitirNotificacaoParaSiMesmo; + if (deveCriarNotificacao) { const tipoNotificacao = args.mencoes?.includes(participanteId) ? "mencao" @@ -318,7 +321,10 @@ export const cancelarMensagemAgendada = mutation({ } if (mensagem.remetenteId !== usuarioAtual._id) { - return { sucesso: false, erro: "Você só pode cancelar suas próprias mensagens" }; + return { + sucesso: false, + erro: "Você só pode cancelar suas próprias mensagens", + }; } if (!mensagem.agendadaPara) { @@ -611,16 +617,20 @@ export const listarConversas = query({ // Para conversas individuais, pegar o outro usuário let outroUsuario = null; if (conversa.tipo === "individual") { - const outroUsuarioRaw = participantes.find((p) => p?._id !== usuarioAtual._id); + const outroUsuarioRaw = participantes.find( + (p) => p?._id !== usuarioAtual._id + ); if (outroUsuarioRaw) { // 🔄 BUSCAR DADOS ATUALIZADOS DO USUÁRIO (não usar snapshot) const usuarioAtualizado = await ctx.db.get(outroUsuarioRaw._id); - + if (usuarioAtualizado) { // Adicionar URL da foto de perfil let fotoPerfilUrl = null; if (usuarioAtualizado.fotoPerfil) { - fotoPerfilUrl = await ctx.storage.getUrl(usuarioAtualizado.fotoPerfil); + fotoPerfilUrl = await ctx.storage.getUrl( + usuarioAtualizado.fotoPerfil + ); } outroUsuario = { ...usuarioAtualizado, @@ -643,7 +653,7 @@ export const listarConversas = query({ .query("mensagens") .withIndex("by_conversa", (q) => q.eq("conversaId", conversa._id)) .collect(); - + const mensagens = todasMensagens.filter((m) => !m.agendadaPara); let naoLidas = 0; @@ -756,7 +766,16 @@ export const obterMensagensAgendadas = query({ */ export const listarAgendamentosChat = query({ args: {}, - handler: async (ctx): Promise & { conversaInfo: Doc<"conversas"> | null; destinatarioInfo: Doc<"usuarios"> | null }>> => { + handler: async ( + ctx + ): Promise< + Array< + Doc<"mensagens"> & { + conversaInfo: Doc<"conversas"> | null; + destinatarioInfo: Doc<"usuarios"> | null; + } + > + > => { const usuarioAtual = await getUsuarioAutenticado(ctx); if (!usuarioAtual) { return []; @@ -912,20 +931,31 @@ export const listarTodosUsuarios = query({ .withIndex("by_ativo", (q) => q.eq("ativo", true)) .collect(); - // Excluir o usuário atual - return usuarios - .filter((u) => u._id !== usuarioAtual._id) - .map((u) => ({ - _id: u._id, - nome: u.nome, - email: u.email, - matricula: u.matricula, - avatar: u.avatar, - fotoPerfil: u.fotoPerfil, - statusPresenca: u.statusPresenca, - statusMensagem: u.statusMensagem, - setor: u.setor, - })); + // Excluir o usuário atual e buscar matrículas + const usuariosComMatricula = await Promise.all( + usuarios + .filter((u) => u._id !== usuarioAtual._id) + .map(async (u) => { + let matricula: string | undefined = undefined; + if (u.funcionarioId) { + const funcionario = await ctx.db.get(u.funcionarioId); + matricula = funcionario?.matricula; + } + return { + _id: u._id, + nome: u.nome, + email: u.email, + matricula, + avatar: u.avatar, + fotoPerfil: u.fotoPerfil, + statusPresenca: u.statusPresenca, + statusMensagem: u.statusMensagem, + setor: u.setor, + }; + }) + ); + + return usuariosComMatricula; }, }); diff --git a/packages/backend/convex/logsAcesso.ts b/packages/backend/convex/logsAcesso.ts index 70e6421..bfa1d76 100644 --- a/packages/backend/convex/logsAcesso.ts +++ b/packages/backend/convex/logsAcesso.ts @@ -88,9 +88,14 @@ export const listar = query({ if (log.usuarioId) { const user = await ctx.db.get(log.usuarioId); if (user) { + let matricula: string | undefined = undefined; + if (user.funcionarioId) { + const funcionario = await ctx.db.get(user.funcionarioId); + matricula = funcionario?.matricula; + } usuario = { _id: user._id, - matricula: user.matricula, + matricula: matricula || "", nome: user.nome, }; } diff --git a/packages/backend/convex/logsAtividades.ts b/packages/backend/convex/logsAtividades.ts index 2a87b16..090957a 100644 --- a/packages/backend/convex/logsAtividades.ts +++ b/packages/backend/convex/logsAtividades.ts @@ -78,10 +78,15 @@ export const listarAtividades = query({ const atividadesComUsuarios = await Promise.all( atividades.map(async (atividade) => { const usuario = await ctx.db.get(atividade.usuarioId); + let matricula = "N/A"; + if (usuario?.funcionarioId) { + const funcionario = await ctx.db.get(usuario.funcionarioId); + matricula = funcionario?.matricula || "N/A"; + } return { ...atividade, usuarioNome: usuario?.nome || "Usuário Desconhecido", - usuarioMatricula: usuario?.matricula || "N/A", + usuarioMatricula: matricula, }; }) ); @@ -157,10 +162,15 @@ export const obterHistoricoRecurso = query({ const atividadesComUsuarios = await Promise.all( atividades.map(async (atividade) => { const usuario = await ctx.db.get(atividade.usuarioId); + let matricula = "N/A"; + if (usuario?.funcionarioId) { + const funcionario = await ctx.db.get(usuario.funcionarioId); + matricula = funcionario?.matricula || "N/A"; + } return { ...atividade, usuarioNome: usuario?.nome || "Usuário Desconhecido", - usuarioMatricula: usuario?.matricula || "N/A", + usuarioMatricula: matricula, }; }) ); diff --git a/packages/backend/convex/schema.ts b/packages/backend/convex/schema.ts index 0f07dbc..d561bbd 100644 --- a/packages/backend/convex/schema.ts +++ b/packages/backend/convex/schema.ts @@ -30,18 +30,19 @@ export default defineSchema({ simboloId: v.id("simbolos"), simboloTipo: simboloTipo, gestorId: v.optional(v.id("usuarios")), - statusFerias: v.optional(v.union( - v.literal("ativo"), - v.literal("em_ferias") - )), - + statusFerias: v.optional( + v.union(v.literal("ativo"), v.literal("em_ferias")) + ), + // Regime de trabalho (para cálculo correto de férias) - regimeTrabalho: v.optional(v.union( - v.literal("clt"), // CLT - Consolidação das Leis do Trabalho - v.literal("estatutario_pe"), // Servidor Público Estadual de Pernambuco - v.literal("estatutario_federal"), // Servidor Público Federal - v.literal("estatutario_municipal") // Servidor Público Municipal - )), + regimeTrabalho: v.optional( + v.union( + v.literal("clt"), // CLT - Consolidação das Leis do Trabalho + v.literal("estatutario_pe"), // Servidor Público Estadual de Pernambuco + v.literal("estatutario_federal"), // Servidor Público Federal + v.literal("estatutario_municipal") // Servidor Público Municipal + ) + ), // Dados Pessoais Adicionais (opcionais) nomePai: v.optional(v.string()), @@ -191,10 +192,7 @@ export default defineSchema({ licencas: defineTable({ funcionarioId: v.id("funcionarios"), - tipo: v.union( - v.literal("maternidade"), - v.literal("paternidade") - ), + tipo: v.union(v.literal("maternidade"), v.literal("paternidade")), dataInicio: v.string(), dataFim: v.string(), documentoId: v.optional(v.id("_storage")), @@ -237,11 +235,15 @@ export default defineSchema({ data: v.number(), usuarioId: v.id("usuarios"), acao: v.string(), - periodosAnteriores: v.optional(v.array(v.object({ - dataInicio: v.string(), - dataFim: v.string(), - diasCorridos: v.number(), - }))), + periodosAnteriores: v.optional( + v.array( + v.object({ + dataInicio: v.string(), + dataFim: v.string(), + diasCorridos: v.number(), + }) + ) + ), }) ) ), @@ -343,7 +345,6 @@ export default defineSchema({ // Sistema de Autenticação e Controle de Acesso usuarios: defineTable({ - matricula: v.string(), senhaHash: v.string(), // Senha criptografada com bcrypt nome: v.string(), email: v.string(), @@ -380,7 +381,6 @@ export default defineSchema({ notificacoesAtivadas: v.optional(v.boolean()), somNotificacao: v.optional(v.boolean()), }) - .index("by_matricula", ["matricula"]) .index("by_email", ["email"]) .index("by_role", ["roleId"]) .index("by_ativo", ["ativo"]) @@ -500,7 +500,7 @@ export default defineSchema({ .index("by_data_inicio", ["dataInicio"]), // Perfis Customizados - + // Templates de Mensagens templatesMensagens: defineTable({ codigo: v.string(), // "USUARIO_BLOQUEADO", "SENHA_RESETADA", etc. @@ -663,8 +663,7 @@ export default defineSchema({ mensagensPorMinuto: v.optional(v.number()), tempoRespostaMedio: v.optional(v.number()), errosCount: v.optional(v.number()), - }) - .index("by_timestamp", ["timestamp"]), + }).index("by_timestamp", ["timestamp"]), alertConfigurations: defineTable({ metricName: v.string(), @@ -681,8 +680,7 @@ export default defineSchema({ notifyByChat: v.boolean(), createdBy: v.id("usuarios"), lastModified: v.number(), - }) - .index("by_enabled", ["enabled"]), + }).index("by_enabled", ["enabled"]), alertHistory: defineTable({ configId: v.id("alertConfigurations"), diff --git a/packages/backend/convex/seed.ts b/packages/backend/convex/seed.ts index 7fc21fe..d24f307 100644 --- a/packages/backend/convex/seed.ts +++ b/packages/backend/convex/seed.ts @@ -316,7 +316,6 @@ export const seedDatabase = internalMutation({ const senhaInicial = await hashPassword("Mudar@123"); await ctx.db.insert("usuarios", { - matricula: funcionario.matricula, senhaHash: senhaInicial, nome: funcionario.nome, email: funcionario.email, diff --git a/packages/backend/convex/usuarios.ts b/packages/backend/convex/usuarios.ts index 46c0d28..95946d9 100644 --- a/packages/backend/convex/usuarios.ts +++ b/packages/backend/convex/usuarios.ts @@ -4,6 +4,21 @@ import { hashPassword, generateToken } from "./auth/utils"; import { registrarAtividade } from "./logsAtividades"; import { Id, Doc } from "./_generated/dataModel"; import { api } from "./_generated/api"; +import type { QueryCtx } from "./_generated/server"; + +/** + * Helper para obter a matrícula do usuário (do funcionário se houver) + */ +async function obterMatriculaUsuario( + ctx: QueryCtx, + usuario: Doc<"usuarios"> +): Promise { + if (usuario.funcionarioId) { + const funcionario = await ctx.db.get(usuario.funcionarioId); + return funcionario?.matricula; + } + return undefined; +} /** * Associar funcionário a um usuário @@ -30,8 +45,11 @@ export const associarFuncionario = mutation({ .first(); if (usuarioExistente && usuarioExistente._id !== args.usuarioId) { + const matricula = await obterMatriculaUsuario(ctx, usuarioExistente); throw new Error( - `Este funcionário já está associado ao usuário: ${usuarioExistente.nome} (${usuarioExistente.matricula})` + `Este funcionário já está associado ao usuário: ${ + usuarioExistente.nome + }${matricula ? ` (${matricula})` : ""}` ); } @@ -66,7 +84,6 @@ export const desassociarFuncionario = mutation({ */ export const criar = mutation({ args: { - matricula: v.string(), nome: v.string(), email: v.string(), roleId: v.id("roles"), @@ -78,16 +95,6 @@ export const criar = mutation({ v.object({ sucesso: v.literal(false), erro: v.string() }) ), handler: async (ctx, args) => { - // Verificar se matrícula já existe - const existente = await ctx.db - .query("usuarios") - .withIndex("by_matricula", (q) => q.eq("matricula", args.matricula)) - .first(); - - if (existente) { - return { sucesso: false as const, erro: "Matrícula já cadastrada" }; - } - // Verificar se email já existe const emailExistente = await ctx.db .query("usuarios") @@ -103,7 +110,6 @@ export const criar = mutation({ // Criar usuário const usuarioId = await ctx.db.insert("usuarios", { - matricula: args.matricula, senhaHash, nome: args.nome, email: args.email, @@ -194,9 +200,17 @@ export const listar = query({ handler: async (ctx, args) => { let usuarios = await ctx.db.query("usuarios").collect(); - // Filtrar por matrícula + // Filtrar por matrícula (buscar no funcionário) if (args.matricula) { - usuarios = usuarios.filter((u) => u.matricula.includes(args.matricula!)); + const usuariosComMatricula = await Promise.all( + usuarios.map(async (u) => { + const matricula = await obterMatriculaUsuario(ctx, u); + return { usuario: u, matricula }; + }) + ); + usuarios = usuariosComMatricula + .filter(({ matricula }) => matricula?.includes(args.matricula!)) + .map(({ usuario }) => usuario); } // Filtrar por ativo @@ -206,20 +220,25 @@ export const listar = query({ // Buscar roles e funcionários const resultado = []; - const usuariosSemRole: Array<{ nome: string; matricula: string; roleId: Id<"roles"> }> = []; + const usuariosSemRole: Array<{ + nome: string; + matricula: string; + roleId: Id<"roles">; + }> = []; for (const usuario of usuarios) { try { const role = await ctx.db.get(usuario.roleId); - + // Se a role não existe, criar uma role de erro mas ainda incluir o usuário if (!role) { + const matricula = await obterMatriculaUsuario(ctx, usuario); usuariosSemRole.push({ nome: usuario.nome, - matricula: usuario.matricula, + matricula: matricula || "N/A", roleId: usuario.roleId, }); - + // Filtrar por setor - se filtro está ativo e role não existe, pular if (args.setor) { continue; @@ -240,14 +259,19 @@ export const listar = query({ }; } } catch (error) { - console.error(`Erro ao buscar funcionário ${usuario.funcionarioId} para usuário ${usuario._id}:`, error); + console.error( + `Erro ao buscar funcionário ${usuario.funcionarioId} para usuário ${usuario._id}:`, + error + ); } } + const matriculaUsuario = await obterMatriculaUsuario(ctx, usuario); + // Criar role de erro (sem _creationTime pois a role não existe) resultado.push({ _id: usuario._id, - matricula: usuario.matricula, + matricula: matriculaUsuario, nome: usuario.nome, email: usuario.email, ativo: usuario.ativo, @@ -294,7 +318,10 @@ export const listar = query({ }; } } catch (error) { - console.error(`Erro ao buscar funcionário ${usuario.funcionarioId} para usuário ${usuario._id}:`, error); + console.error( + `Erro ao buscar funcionário ${usuario.funcionarioId} para usuário ${usuario._id}:`, + error + ); } } @@ -305,14 +332,18 @@ export const listar = query({ nome: role.nome, nivel: role.nivel, ...(role.criadoPor !== undefined && { criadoPor: role.criadoPor }), - ...(role.customizado !== undefined && { customizado: role.customizado }), + ...(role.customizado !== undefined && { + customizado: role.customizado, + }), ...(role.editavel !== undefined && { editavel: role.editavel }), ...(role.setor !== undefined && { setor: role.setor }), }; + const matriculaUsuario = await obterMatriculaUsuario(ctx, usuario); + resultado.push({ _id: usuario._id, - matricula: usuario.matricula, + matricula: matriculaUsuario, nome: usuario.nome, email: usuario.email, ativo: usuario.ativo, @@ -334,7 +365,12 @@ export const listar = query({ if (usuariosSemRole.length > 0) { console.warn( `⚠️ Encontrados ${usuariosSemRole.length} usuário(s) com perfil ausente:`, - usuariosSemRole.map((u) => `${u.nome} (${u.matricula}) - RoleID: ${u.roleId}`) + usuariosSemRole.map( + (u) => + `${u.nome}${ + u.matricula !== "N/A" ? ` (${u.matricula})` : "" + } - RoleID: ${u.roleId}` + ) ); } @@ -559,7 +595,9 @@ export const atualizarPerfil = mutation({ } // Atualizar apenas os campos fornecidos - const updates: Partial> & { atualizadoEm: number } = { atualizadoEm: Date.now() }; + const updates: Partial> & { atualizadoEm: number } = { + atualizadoEm: Date.now(), + }; if (args.avatar !== undefined) updates.avatar = args.avatar; if (args.fotoPerfil !== undefined) updates.fotoPerfil = args.fotoPerfil; @@ -591,7 +629,7 @@ export const obterPerfil = query({ _id: v.id("usuarios"), nome: v.string(), email: v.string(), - matricula: v.string(), + matricula: v.optional(v.string()), funcionarioId: v.optional(v.id("funcionarios")), avatar: v.optional(v.string()), fotoPerfil: v.optional(v.id("_storage")), @@ -675,11 +713,13 @@ export const obterPerfil = query({ fotoPerfilUrl = await ctx.storage.getUrl(usuarioAtual.fotoPerfil); } + const matricula = await obterMatriculaUsuario(ctx, usuarioAtual); + return { _id: usuarioAtual._id, nome: usuarioAtual.nome, email: usuarioAtual.email, - matricula: usuarioAtual.matricula, + matricula: matricula || undefined, funcionarioId: usuarioAtual.funcionarioId, avatar: usuarioAtual.avatar, fotoPerfil: usuarioAtual.fotoPerfil, @@ -735,11 +775,13 @@ export const listarParaChat = query({ fotoPerfilUrl = await ctx.storage.getUrl(usuario.fotoPerfil); } + const matricula = await obterMatriculaUsuario(ctx, usuario); + return { _id: usuario._id, nome: usuario.nome, email: usuario.email, - matricula: usuario.matricula || undefined, + matricula: matricula || undefined, avatar: usuario.avatar, fotoPerfil: usuario.fotoPerfil, fotoPerfilUrl, @@ -1035,7 +1077,6 @@ export const editarUsuario = mutation({ */ export const criarAdminMaster = mutation({ args: { - matricula: v.string(), nome: v.string(), email: v.string(), senha: v.optional(v.string()), @@ -1074,32 +1115,9 @@ export const criarAdminMaster = mutation({ }; } - // Se já existir usuário por matrícula, promove/atualiza - const existentePorMatricula = await ctx.db - .query("usuarios") - .withIndex("by_matricula", (q) => q.eq("matricula", args.matricula)) - .first(); - const senhaTemporaria = args.senha || gerarSenhaTemporaria(); const senhaHash = await hashPassword(senhaTemporaria); - if (existentePorMatricula) { - await ctx.db.patch(existentePorMatricula._id, { - nome: args.nome, - email: args.email, - senhaHash, - roleId: roleTIMaster._id, - ativo: true, - primeiroAcesso: true, - atualizadoEm: Date.now(), - }); - return { - sucesso: true as const, - usuarioId: existentePorMatricula._id, - senhaTemporaria, - }; - } - // Verificar se email já existe const existentePorEmail = await ctx.db .query("usuarios") @@ -1108,7 +1126,6 @@ export const criarAdminMaster = mutation({ if (existentePorEmail) { // Promove usuário existente por email await ctx.db.patch(existentePorEmail._id, { - matricula: args.matricula, nome: args.nome, senhaHash, roleId: roleTIMaster._id, @@ -1125,7 +1142,6 @@ export const criarAdminMaster = mutation({ // Criar novo usuário TI Master const usuarioId = await ctx.db.insert("usuarios", { - matricula: args.matricula, senhaHash, nome: args.nome, email: args.email, @@ -1194,7 +1210,6 @@ export const excluirUsuarioLogico = mutation({ */ export const criarUsuarioCompleto = mutation({ args: { - matricula: v.string(), nome: v.string(), email: v.string(), roleId: v.id("roles"), @@ -1212,16 +1227,6 @@ export const criarUsuarioCompleto = mutation({ v.object({ sucesso: v.literal(false), erro: v.string() }) ), handler: async (ctx, args) => { - // Verificar se matrícula já existe - const existente = await ctx.db - .query("usuarios") - .withIndex("by_matricula", (q) => q.eq("matricula", args.matricula)) - .first(); - - if (existente) { - return { sucesso: false as const, erro: "Matrícula já cadastrada" }; - } - // Verificar se email já existe const emailExistente = await ctx.db .query("usuarios") @@ -1238,7 +1243,6 @@ export const criarUsuarioCompleto = mutation({ // Criar usuário const usuarioId = await ctx.db.insert("usuarios", { - matricula: args.matricula, senhaHash, nome: args.nome, email: args.email, @@ -1256,7 +1260,7 @@ export const criarUsuarioCompleto = mutation({ args.criadoPorId, "criar", "usuarios", - JSON.stringify({ usuarioId, matricula: args.matricula, nome: args.nome }), + JSON.stringify({ usuarioId, nome: args.nome }), usuarioId ); @@ -1272,7 +1276,6 @@ export const criarUsuarioCompleto = mutation({ */ export const criarAdminPadrao = mutation({ args: { - matricula: v.optional(v.string()), nome: v.optional(v.string()), email: v.optional(v.string()), senha: v.optional(v.string()), @@ -1282,7 +1285,6 @@ export const criarAdminPadrao = mutation({ usuarioId: v.optional(v.id("usuarios")), }), handler: async (ctx, args) => { - const matricula = args.matricula ?? "0000"; const nome = args.nome ?? "Administrador Geral"; const email = args.email ?? "admin@sgse.pe.gov.br"; const senha = args.senha ?? "Admin@123"; @@ -1306,12 +1308,7 @@ export const criarAdminPadrao = mutation({ if (!roleAdmin) return { sucesso: false }; - // Verificar se já existe por matrícula ou email - const existentePorMatricula = await ctx.db - .query("usuarios") - .withIndex("by_matricula", (q) => q.eq("matricula", matricula)) - .first(); - + // Verificar se já existe por email const existentePorEmail = await ctx.db .query("usuarios") .withIndex("by_email", (q) => q.eq("email", email)) @@ -1319,10 +1316,8 @@ export const criarAdminPadrao = mutation({ const senhaHash = await hashPassword(senha); - if (existentePorMatricula || existentePorEmail) { - const alvo = existentePorMatricula ?? existentePorEmail!; - await ctx.db.patch(alvo._id, { - matricula, + if (existentePorEmail) { + await ctx.db.patch(existentePorEmail._id, { nome, email, senhaHash, @@ -1331,11 +1326,10 @@ export const criarAdminPadrao = mutation({ primeiroAcesso: false, atualizadoEm: Date.now(), }); - return { sucesso: true, usuarioId: alvo._id }; + return { sucesso: true, usuarioId: existentePorEmail._id }; } const usuarioId = await ctx.db.insert("usuarios", { - matricula, senhaHash, nome, email, diff --git a/packages/backend/convex/verificarMatriculas.ts b/packages/backend/convex/verificarMatriculas.ts index 1c5c5ba..0b05979 100644 --- a/packages/backend/convex/verificarMatriculas.ts +++ b/packages/backend/convex/verificarMatriculas.ts @@ -3,7 +3,7 @@ import { v } from "convex/values"; import { Id, Doc } from "./_generated/dataModel"; /** - * Verificar duplicatas de matrícula + * Verificar duplicatas de matrícula (agora busca do funcionário associado) */ export const verificarDuplicatas = query({ args: {}, @@ -23,18 +23,27 @@ export const verificarDuplicatas = query({ handler: async (ctx) => { const usuarios = await ctx.db.query("usuarios").collect(); - // Agrupar por matrícula - const gruposPorMatricula = usuarios.reduce((acc, usuario) => { - if (!acc[usuario.matricula]) { - acc[usuario.matricula] = []; + // Agrupar por matrícula do funcionário associado + const gruposPorMatricula: Record; nome: string; email: string }>> = {}; + + for (const usuario of usuarios) { + let matricula: string | undefined = undefined; + if (usuario.funcionarioId) { + const funcionario = await ctx.db.get(usuario.funcionarioId); + matricula = funcionario?.matricula; } - acc[usuario.matricula].push({ - _id: usuario._id, - nome: usuario.nome, - email: usuario.email || "", - }); - return acc; - }, {} as Record; nome: string; email: string }>>); + + if (matricula) { + if (!gruposPorMatricula[matricula]) { + gruposPorMatricula[matricula] = []; + } + gruposPorMatricula[matricula].push({ + _id: usuario._id, + nome: usuario.nome, + email: usuario.email || "", + }); + } + } // Filtrar apenas duplicatas const duplicatas = Object.entries(gruposPorMatricula) @@ -50,7 +59,7 @@ export const verificarDuplicatas = query({ }); /** - * Remover duplicatas mantendo apenas o mais recente + * Remover duplicatas mantendo apenas o mais recente (agora busca do funcionário associado) */ export const removerDuplicatas = internalMutation({ args: {}, @@ -61,14 +70,23 @@ export const removerDuplicatas = internalMutation({ handler: async (ctx) => { const usuarios = await ctx.db.query("usuarios").collect(); - // Agrupar por matrícula - const gruposPorMatricula = usuarios.reduce((acc, usuario) => { - if (!acc[usuario.matricula]) { - acc[usuario.matricula] = []; + // Agrupar por matrícula do funcionário associado + const gruposPorMatricula: Record[]> = {}; + + for (const usuario of usuarios) { + let matricula: string | undefined = undefined; + if (usuario.funcionarioId) { + const funcionario = await ctx.db.get(usuario.funcionarioId); + matricula = funcionario?.matricula; } - acc[usuario.matricula].push(usuario); - return acc; - }, {} as Record[]>); + + if (matricula) { + if (!gruposPorMatricula[matricula]) { + gruposPorMatricula[matricula] = []; + } + gruposPorMatricula[matricula].push(usuario); + } + } let removidos = 0; const matriculasDuplicadas: string[] = [];