Fix usuarios page #6

Merged
killer-cf merged 28 commits from fix-usuarios-page into master 2025-11-04 17:42:21 +00:00
28 changed files with 1972 additions and 771 deletions
Showing only changes of commit 5cb63f9437 - Show all commits

View File

@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { useConvexClient } from "convex-svelte"; import { useConvexClient } from "convex-svelte";
import { api } from "@sgse-app/backend/convex/_generated/api"; import { api } from "@sgse-app/backend/convex/_generated/api";
import type { Id, Doc } from "@sgse-app/backend/convex/_generated/dataModel";
interface Periodo { interface Periodo {
dataInicio: string; dataInicio: string;
@@ -8,9 +9,14 @@
diasCorridos: number; diasCorridos: number;
} }
type SolicitacaoFerias = Doc<"solicitacoesFerias"> & {
funcionario?: Doc<"funcionarios"> | null;
gestor?: Doc<"usuarios"> | null;
};
interface Props { interface Props {
solicitacao: any; solicitacao: SolicitacaoFerias;
gestorId: string; gestorId: Id<"usuarios">;
onSucesso?: () => void; onSucesso?: () => void;
onCancelar?: () => void; onCancelar?: () => void;
} }
@@ -27,7 +33,7 @@
$effect(() => { $effect(() => {
if (modoAjuste && periodos.length === 0) { if (modoAjuste && periodos.length === 0) {
periodos = solicitacao.periodos.map((p: any) => ({...p})); periodos = solicitacao.periodos.map((p) => ({...p}));
} }
}); });
@@ -59,12 +65,12 @@
await client.mutation(api.ferias.aprovar, { await client.mutation(api.ferias.aprovar, {
solicitacaoId: solicitacao._id, solicitacaoId: solicitacao._id,
gestorId: gestorId as any, gestorId: gestorId,
}); });
if (onSucesso) onSucesso(); if (onSucesso) onSucesso();
} catch (e: any) { } catch (e) {
erro = e.message || "Erro ao aprovar solicitação"; erro = e instanceof Error ? e.message : String(e);
} finally { } finally {
processando = false; processando = false;
} }
@@ -82,13 +88,13 @@
await client.mutation(api.ferias.reprovar, { await client.mutation(api.ferias.reprovar, {
solicitacaoId: solicitacao._id, solicitacaoId: solicitacao._id,
gestorId: gestorId as any, gestorId: gestorId,
motivoReprovacao, motivoReprovacao,
}); });
if (onSucesso) onSucesso(); if (onSucesso) onSucesso();
} catch (e: any) { } catch (e) {
erro = e.message || "Erro ao reprovar solicitação"; erro = e instanceof Error ? e.message : String(e);
} finally { } finally {
processando = false; processando = false;
} }
@@ -101,13 +107,13 @@
await client.mutation(api.ferias.ajustarEAprovar, { await client.mutation(api.ferias.ajustarEAprovar, {
solicitacaoId: solicitacao._id, solicitacaoId: solicitacao._id,
gestorId: gestorId as any, gestorId: gestorId,
novosPeriodos: periodos, novosPeriodos: periodos,
}); });
if (onSucesso) onSucesso(); if (onSucesso) onSucesso();
} catch (e: any) { } catch (e) {
erro = e.message || "Erro ao ajustar e aprovar solicitação"; erro = e instanceof Error ? e.message : String(e);
} finally { } finally {
processando = false; processando = false;
} }

View File

@@ -110,10 +110,16 @@ class AuthStore {
{} {}
); );
if (usuarioAtualizado && this.state.usuario) { if (usuarioAtualizado) {
// Preservar role e primeiroAcesso do estado atual
this.state.usuario = { this.state.usuario = {
...this.state.usuario,
...usuarioAtualizado, ...usuarioAtualizado,
role: this.state.usuario?.role || {
_id: "",
nome: "Usuário",
nivel: 999,
},
primeiroAcesso: this.state.usuario?.primeiroAcesso ?? false,
}; };
localStorage.setItem( localStorage.setItem(

View File

@@ -39,7 +39,11 @@
} }
}); });
$inspect(authStore.usuario?.funcionarioId); // Debug: Verificar funcionarioId
$effect(() => {
console.log("🔍 [Perfil] funcionarioId:", authStore.usuario?.funcionarioId);
console.log("🔍 [Perfil] Usuário completo:", authStore.usuario);
});
// Queries // Queries
const funcionarioQuery = $derived( const funcionarioQuery = $derived(

View File

@@ -9,15 +9,16 @@ import {
} from "./auth/utils"; } from "./auth/utils";
import { registrarLogin } from "./logsLogin"; import { registrarLogin } from "./logsLogin";
import { Id } from "./_generated/dataModel"; import { Id } from "./_generated/dataModel";
import type { QueryCtx } from "./_generated/server";
/** /**
* Helper para verificar se usuário está bloqueado * Helper para verificar se usuário está bloqueado
*/ */
async function verificarBloqueioUsuario(ctx: any, usuarioId: Id<"usuarios">) { async function verificarBloqueioUsuario(ctx: QueryCtx, usuarioId: Id<"usuarios">) {
const bloqueio = await ctx.db const bloqueio = await ctx.db
.query("bloqueiosUsuarios") .query("bloqueiosUsuarios")
.withIndex("by_usuario", (q: any) => q.eq("usuarioId", usuarioId)) .withIndex("by_usuario", (q) => q.eq("usuarioId", usuarioId))
.filter((q: any) => q.eq(q.field("ativo"), true)) .filter((q) => q.eq(q.field("ativo"), true))
.first(); .first();
return bloqueio !== null; return bloqueio !== null;
@@ -26,17 +27,17 @@ async function verificarBloqueioUsuario(ctx: any, usuarioId: Id<"usuarios">) {
/** /**
* Helper para verificar rate limiting por IP * Helper para verificar rate limiting por IP
*/ */
async function verificarRateLimitIP(ctx: any, ipAddress: string) { async function verificarRateLimitIP(ctx: QueryCtx, ipAddress: string) {
// Últimas 15 minutos // Últimas 15 minutos
const dataLimite = Date.now() - 15 * 60 * 1000; const dataLimite = Date.now() - 15 * 60 * 1000;
const tentativas = await ctx.db const tentativas = await ctx.db
.query("logsLogin") .query("logsLogin")
.withIndex("by_ip", (q: any) => q.eq("ipAddress", ipAddress)) .withIndex("by_ip", (q) => q.eq("ipAddress", ipAddress))
.filter((q: any) => q.gte(q.field("timestamp"), dataLimite)) .filter((q) => q.gte(q.field("timestamp"), dataLimite))
.collect(); .collect();
const falhas = tentativas.filter((t: any) => !t.sucesso).length; const falhas = tentativas.filter((t) => !t.sucesso).length;
// Bloquear se 5 ou mais tentativas falhas em 15 minutos // Bloquear se 5 ou mais tentativas falhas em 15 minutos
return falhas >= 5; return falhas >= 5;
@@ -299,6 +300,7 @@ export const login = mutation({
matricula: usuario.matricula, matricula: usuario.matricula,
nome: usuario.nome, nome: usuario.nome,
email: usuario.email, email: usuario.email,
funcionarioId: usuario.funcionarioId,
role: { role: {
_id: role._id, _id: role._id,
nome: role.nome, nome: role.nome,

View File

@@ -20,11 +20,12 @@ async function getUsuarioAutenticado(ctx: QueryCtx | MutationCtx) {
.first(); .first();
} }
// Se não encontrou via Better Auth, tentar via sessão // Se não encontrou via Better Auth, tentar via sessão mais recente
if (!usuarioAtual) { if (!usuarioAtual) {
const sessaoAtiva = await ctx.db const sessaoAtiva = await ctx.db
.query("sessoes") .query("sessoes")
.filter((q) => q.eq(q.field("ativo"), true)) .filter((q) => q.eq(q.field("ativo"), true))
.order("desc")
.first(); .first();
if (sessaoAtiva) { if (sessaoAtiva) {
@@ -875,7 +876,7 @@ export const buscarMensagens = query({
c.participantes.includes(usuarioAtual._id) c.participantes.includes(usuarioAtual._id)
); );
let mensagens: any[] = []; let mensagens: Doc<"mensagens">[] = [];
if (args.conversaId !== undefined) { if (args.conversaId !== undefined) {
// Buscar em conversa específica // Buscar em conversa específica

View File

@@ -106,7 +106,7 @@ export const salvarConfigEmail = mutation({
/** /**
* Testar conexão SMTP (action - precisa de Node.js) * Testar conexão SMTP (action - precisa de Node.js)
* *
* NOTA: Esta action será implementada quando instalarmos nodemailer. * NOTA: Esta action será implementada quando instalarmos nodemailer.
* Por enquanto, retorna sucesso simulado para não bloquear o desenvolvimento. * Por enquanto, retorna sucesso simulado para não bloquear o desenvolvimento.
*/ */
@@ -126,11 +126,14 @@ export const testarConexaoSMTP = action({
handler: async (ctx, args) => { handler: async (ctx, args) => {
// TODO: Implementar teste real com nodemailer // TODO: Implementar teste real com nodemailer
// Por enquanto, simula sucesso // Por enquanto, simula sucesso
try { try {
// Validações básicas // Validações básicas
if (!args.servidor || args.servidor.trim() === "") { if (!args.servidor || args.servidor.trim() === "") {
return { sucesso: false as const, erro: "Servidor SMTP não pode estar vazio" }; return {
sucesso: false as const,
erro: "Servidor SMTP não pode estar vazio",
};
} }
if (args.porta < 1 || args.porta > 65535) { if (args.porta < 1 || args.porta > 65535) {
@@ -141,10 +144,17 @@ export const testarConexaoSMTP = action({
await new Promise((resolve) => setTimeout(resolve, 1000)); await new Promise((resolve) => setTimeout(resolve, 1000));
// Retornar sucesso simulado // Retornar sucesso simulado
console.log("⚠️ AVISO: Teste de conexão SMTP simulado (nodemailer não instalado ainda)"); console.log(
"⚠️ AVISO: Teste de conexão SMTP simulado (nodemailer não instalado ainda)"
);
return { sucesso: true as const }; return { sucesso: true as const };
} catch (error: any) { } catch (error) {
return { sucesso: false as const, erro: error.message || "Erro ao testar conexão" }; const errorMessage =
error instanceof Error ? error.message : String(error);
return {
sucesso: false as const,
erro: errorMessage || "Erro ao testar conexão",
};
} }
}, },
}); });
@@ -162,5 +172,3 @@ export const marcarConfigTestada = mutation({
}); });
}, },
}); });

View File

@@ -27,7 +27,7 @@ export const updateDocumento = mutation({
// Atualizar o campo específico do documento // Atualizar o campo específico do documento
await ctx.db.patch(args.funcionarioId, { await ctx.db.patch(args.funcionarioId, {
[args.campo]: args.storageId, [args.campo]: args.storageId,
} as any); });
return null; return null;
}, },
@@ -106,7 +106,7 @@ export const getDocumentosUrls = query({
]; ];
for (const campo of campos) { for (const campo of campos) {
const storageId = (funcionario as any)[campo]; const storageId = funcionario[campo as keyof typeof funcionario] as Id<"_storage"> | undefined;
if (storageId) { if (storageId) {
urls[campo] = await ctx.storage.getUrl(storageId); urls[campo] = await ctx.storage.getUrl(storageId);
} else { } else {
@@ -114,7 +114,7 @@ export const getDocumentosUrls = query({
} }
} }
return urls as any; return urls;
}, },
}); });

View File

@@ -8,7 +8,7 @@ import {
} from "./_generated/server"; } from "./_generated/server";
import { Id } from "./_generated/dataModel"; import { Id } from "./_generated/dataModel";
import { renderizarTemplate } from "./templatesMensagens"; import { renderizarTemplate } from "./templatesMensagens";
import { internal } from "./_generated/api"; import { internal, api } from "./_generated/api";
/** /**
* Enfileirar email para envio * Enfileirar email para envio
@@ -58,7 +58,7 @@ export const enviarEmailComTemplate = mutation({
destinatario: v.string(), destinatario: v.string(),
destinatarioId: v.optional(v.id("usuarios")), destinatarioId: v.optional(v.id("usuarios")),
templateCodigo: v.string(), templateCodigo: v.string(),
variaveis: v.any(), // Record<string, string> variaveis: v.record(v.string(), v.string()),
enviadoPorId: v.id("usuarios"), enviadoPorId: v.id("usuarios"),
}, },
returns: v.object({ returns: v.object({
@@ -113,7 +113,7 @@ export const listarFilaEmails = query({
), ),
limite: v.optional(v.number()), limite: v.optional(v.number()),
}, },
returns: v.array(v.any()), // Tipo inferido automaticamente pelo Convex
handler: async (ctx, args) => { handler: async (ctx, args) => {
if (args.status) { if (args.status) {
const emails = await ctx.db const emails = await ctx.db
@@ -166,7 +166,7 @@ export const reenviarEmail = mutation({
*/ */
export const getEmailById = internalQuery({ export const getEmailById = internalQuery({
args: { emailId: v.id("notificacoesEmail") }, args: { emailId: v.id("notificacoesEmail") },
returns: v.union(v.any(), v.null()), // Tipo inferido automaticamente pelo Convex
handler: async (ctx, args) => { handler: async (ctx, args) => {
return await ctx.db.get(args.emailId); return await ctx.db.get(args.emailId);
}, },
@@ -174,7 +174,7 @@ export const getEmailById = internalQuery({
export const getActiveEmailConfig = internalQuery({ export const getActiveEmailConfig = internalQuery({
args: {}, args: {},
returns: v.union(v.any(), v.null()), // Tipo inferido automaticamente pelo Convex
handler: async (ctx) => { handler: async (ctx) => {
return await ctx.db return await ctx.db
.query("configuracaoEmail") .query("configuracaoEmail")
@@ -188,9 +188,10 @@ export const markEmailEnviando = internalMutation({
returns: v.null(), returns: v.null(),
handler: async (ctx, args) => { handler: async (ctx, args) => {
const email = await ctx.db.get(args.emailId); const email = await ctx.db.get(args.emailId);
if (!email) return null;
await ctx.db.patch(args.emailId, { await ctx.db.patch(args.emailId, {
status: "enviando", status: "enviando",
tentativas: ((email as any)?.tentativas || 0) + 1, tentativas: (email.tentativas || 0) + 1,
ultimaTentativa: Date.now(), ultimaTentativa: Date.now(),
}); });
return null; return null;
@@ -214,10 +215,11 @@ export const markEmailFalha = internalMutation({
returns: v.null(), returns: v.null(),
handler: async (ctx, args) => { handler: async (ctx, args) => {
const email = await ctx.db.get(args.emailId); const email = await ctx.db.get(args.emailId);
if (!email) return null;
await ctx.db.patch(args.emailId, { await ctx.db.patch(args.emailId, {
status: "falha", status: "falha",
erroDetalhes: args.erro, erroDetalhes: args.erro,
tentativas: ((email as any)?.tentativas || 0) + 1, tentativas: (email.tentativas || 0) + 1,
}); });
return null; return null;
}, },
@@ -270,12 +272,12 @@ export const enviarEmailAction = action({
// Criar transporter do nodemailer // Criar transporter do nodemailer
const transporter = nodemailer.createTransport({ const transporter = nodemailer.createTransport({
host: (config as any).smtpHost, host: config.servidor,
port: (config as any).smtpPort, port: config.porta,
secure: (config as any).smtpSecure, secure: config.usarSSL,
auth: { auth: {
user: (config as any).smtpUser, user: config.usuario,
pass: (config as any).smtpPassword, pass: config.senhaHash, // Note: em produção deve ser descriptografado
}, },
tls: { tls: {
rejectUnauthorized: false, rejectUnauthorized: false,
@@ -284,15 +286,15 @@ export const enviarEmailAction = action({
// Enviar email REAL // Enviar email REAL
const info = await transporter.sendMail({ const info = await transporter.sendMail({
from: `"${(config as any).remetenteNome}" <${(config as any).remetenteEmail}>`, from: `"${config.nomeRemetente}" <${config.emailRemetente}>`,
to: (email as any).destinatario, to: email.destinatario,
subject: (email as any).assunto, subject: email.assunto,
html: (email as any).corpo, html: email.corpo,
}); });
console.log("✅ Email enviado com sucesso!"); console.log("✅ Email enviado com sucesso!");
console.log(" Para:", (email as any).destinatario); console.log(" Para:", email.destinatario);
console.log(" Assunto:", (email as any).assunto); console.log(" Assunto:", email.assunto);
console.log(" Message ID:", info.messageId); console.log(" Message ID:", info.messageId);
// Marcar como enviado // Marcar como enviado
@@ -301,16 +303,18 @@ export const enviarEmailAction = action({
}); });
return { sucesso: true }; return { sucesso: true };
} catch (error: any) { } catch (error) {
console.error("❌ Erro ao enviar email:", error.message); const errorMessage =
error instanceof Error ? error.message : String(error);
console.error("❌ Erro ao enviar email:", errorMessage);
// Marcar como falha // Marcar como falha
await ctx.runMutation(internal.email.markEmailFalha, { await ctx.runMutation(internal.email.markEmailFalha, {
emailId: args.emailId, emailId: args.emailId,
erro: error.message || "Erro ao enviar email", erro: errorMessage,
}); });
return { sucesso: false, erro: error.message || "Erro ao enviar email" }; return { sucesso: false, erro: errorMessage };
} }
}, },
}); });
@@ -342,7 +346,7 @@ export const processarFilaEmails = internalMutation({
// Agendar envio via action // Agendar envio via action
// IMPORTANTE: Não podemos chamar action diretamente de mutation // IMPORTANTE: Não podemos chamar action diretamente de mutation
// Por isso, usaremos o scheduler // Por isso, usaremos o scheduler com string path
await ctx.scheduler.runAfter(0, "email:enviarEmailAction" as any, { await ctx.scheduler.runAfter(0, "email:enviarEmailAction" as any, {
emailId: email._id, emailId: email._id,
}); });

View File

@@ -1,7 +1,7 @@
import { v } from "convex/values"; import { v } from "convex/values";
import { mutation, query, internalMutation } from "./_generated/server"; import { mutation, query, internalMutation } from "./_generated/server";
import { internal } from "./_generated/api"; import { internal } from "./_generated/api";
import { Id } from "./_generated/dataModel"; import { Id, Doc } from "./_generated/dataModel";
// Validador para períodos // Validador para períodos
const periodoValidator = v.object({ const periodoValidator = v.object({
@@ -11,9 +11,9 @@ const periodoValidator = v.object({
}); });
// Query: Listar TODAS as solicitações (para RH) // Query: Listar TODAS as solicitações (para RH)
// Retorna tipo inferido automaticamente pelo Convex
export const listarTodas = query({ export const listarTodas = query({
args: {}, args: {},
returns: v.array(v.any()),
handler: async (ctx) => { handler: async (ctx) => {
const solicitacoes = await ctx.db.query("solicitacoesFerias").collect(); const solicitacoes = await ctx.db.query("solicitacoesFerias").collect();
@@ -52,7 +52,7 @@ export const listarTodas = query({
// Query: Listar solicitações do funcionário // Query: Listar solicitações do funcionário
export const listarMinhasSolicitacoes = query({ export const listarMinhasSolicitacoes = query({
args: { funcionarioId: v.id("funcionarios") }, args: { funcionarioId: v.id("funcionarios") },
returns: v.array(v.any()), // returns não especificado - TypeScript inferirá automaticamente o tipo correto
handler: async (ctx, args) => { handler: async (ctx, args) => {
return await ctx.db return await ctx.db
.query("solicitacoesFerias") .query("solicitacoesFerias")
@@ -65,9 +65,9 @@ export const listarMinhasSolicitacoes = query({
}); });
// Query: Listar solicitações dos subordinados (para gestores) // Query: Listar solicitações dos subordinados (para gestores)
// Retorna tipo inferido automaticamente pelo Convex
export const listarSolicitacoesSubordinados = query({ export const listarSolicitacoesSubordinados = query({
args: { gestorId: v.id("usuarios") }, args: { gestorId: v.id("usuarios") },
returns: v.array(v.any()),
handler: async (ctx, args) => { handler: async (ctx, args) => {
// Buscar times onde o usuário é gestor // Buscar times onde o usuário é gestor
const timesGestor = await ctx.db const timesGestor = await ctx.db
@@ -76,7 +76,10 @@ export const listarSolicitacoesSubordinados = query({
.filter((q) => q.eq(q.field("ativo"), true)) .filter((q) => q.eq(q.field("ativo"), true))
.collect(); .collect();
const solicitacoes: Array<any> = []; const solicitacoes: Array<Doc<"solicitacoesFerias"> & {
funcionario: Doc<"funcionarios"> | null;
time: Doc<"times"> | null;
}> = [];
for (const time of timesGestor) { for (const time of timesGestor) {
// Buscar membros do time // Buscar membros do time
@@ -113,9 +116,9 @@ export const listarSolicitacoesSubordinados = query({
}); });
// Query: Obter detalhes completos de uma solicitação // Query: Obter detalhes completos de uma solicitação
// Retorna tipo inferido automaticamente pelo Convex
export const obterDetalhes = query({ export const obterDetalhes = query({
args: { solicitacaoId: v.id("solicitacoesFerias") }, args: { solicitacaoId: v.id("solicitacoesFerias") },
returns: v.union(v.any(), v.null()),
handler: async (ctx, args) => { handler: async (ctx, args) => {
const solicitacao = await ctx.db.get(args.solicitacaoId); const solicitacao = await ctx.db.get(args.solicitacaoId);
if (!solicitacao) return null; if (!solicitacao) return null;

View File

@@ -71,7 +71,7 @@ export const getAll = query({
export const getById = query({ export const getById = query({
args: { id: v.id("funcionarios") }, args: { id: v.id("funcionarios") },
returns: v.union(v.any(), v.null()), // Tipo inferido automaticamente pelo Convex
handler: async (ctx, args) => { handler: async (ctx, args) => {
// Autorização: ver funcionário // Autorização: ver funcionário
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
@@ -205,7 +205,7 @@ export const create = mutation({
} }
} }
const novoFuncionarioId = await ctx.db.insert("funcionarios", args as any); const novoFuncionarioId = await ctx.db.insert("funcionarios", args);
return novoFuncionarioId; return novoFuncionarioId;
}, },
}); });
@@ -335,7 +335,7 @@ export const update = mutation({
} }
const { id, ...updateData } = args; const { id, ...updateData } = args;
await ctx.db.patch(id, updateData as any); await ctx.db.patch(id, updateData);
return null; return null;
}, },
}); });
@@ -358,7 +358,7 @@ export const remove = mutation({
// Query para obter ficha completa para impressão // Query para obter ficha completa para impressão
export const getFichaCompleta = query({ export const getFichaCompleta = query({
args: { id: v.id("funcionarios") }, args: { id: v.id("funcionarios") },
returns: v.union(v.any(), v.null()), // Tipo inferido automaticamente pelo Convex
handler: async (ctx, args) => { handler: async (ctx, args) => {
await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, { await ctx.runQuery(internal.permissoesAcoes.assertPermissaoAcaoAtual, {
recurso: "funcionarios", recurso: "funcionarios",
@@ -398,11 +398,10 @@ export const getFichaCompleta = query({
? { ? {
nome: simbolo.nome, nome: simbolo.nome,
descricao: simbolo.descricao, descricao: simbolo.descricao,
// campos adicionais, se existirem no símbolo tipo: simbolo.tipo,
tipo: (simbolo as any).tipo, vencValor: simbolo.vencValor,
vencValor: (simbolo as any).vencValor, repValor: simbolo.repValor,
repValor: (simbolo as any).repValor, valor: simbolo.valor,
valor: (simbolo as any).valor,
} }
: null, : null,
cursos: cursosComUrls, cursos: cursosComUrls,

View File

@@ -38,7 +38,7 @@ export const executar = internalMutation({
} }
// 2. Agrupar funcionários por gestor // 2. Agrupar funcionários por gestor
const gestoresMap = new Map<string, any[]>(); const gestoresMap = new Map<string, Doc<"funcionarios">[]>();
for (const funcionario of funcionariosComGestor) { for (const funcionario of funcionariosComGestor) {
if (!funcionario.gestorId) continue; if (!funcionario.gestorId) continue;
@@ -53,7 +53,7 @@ export const executar = internalMutation({
// 3. Para cada gestor, criar um time // 3. Para cada gestor, criar um time
for (const [gestorId, subordinados] of gestoresMap.entries()) { for (const [gestorId, subordinados] of gestoresMap.entries()) {
try { try {
const gestor = await ctx.db.get(gestorId as any); const gestor = await ctx.db.get(gestorId);
if (!gestor) { if (!gestor) {
erros.push(`Gestor ${gestorId} não encontrado`); erros.push(`Gestor ${gestorId} não encontrado`);
@@ -63,7 +63,7 @@ export const executar = internalMutation({
// Verificar se já existe time para este gestor // Verificar se já existe time para este gestor
const timeExistente = await ctx.db const timeExistente = await ctx.db
.query("times") .query("times")
.withIndex("by_gestor", (q) => q.eq("gestorId", gestorId as any)) .withIndex("by_gestor", (q) => q.eq("gestorId", gestorId))
.filter((q) => q.eq(q.field("ativo"), true)) .filter((q) => q.eq(q.field("ativo"), true))
.first(); .first();
@@ -76,7 +76,7 @@ export const executar = internalMutation({
timeId = await ctx.db.insert("times", { timeId = await ctx.db.insert("times", {
nome: `Equipe ${gestor.nome}`, nome: `Equipe ${gestor.nome}`,
descricao: `Time gerenciado por ${gestor.nome} (migração automática)`, descricao: `Time gerenciado por ${gestor.nome} (migração automática)`,
gestorId: gestorId as any, gestorId: gestorId,
ativo: true, ativo: true,
cor: "#3B82F6", cor: "#3B82F6",
}); });
@@ -102,7 +102,7 @@ export const executar = internalMutation({
}); });
funcionariosAtribuidos++; funcionariosAtribuidos++;
} }
} catch (e: any) { } catch (e) {
erros.push(`Erro ao adicionar ${funcionario.nome} ao time: ${e.message}`); erros.push(`Erro ao adicionar ${funcionario.nome} ao time: ${e.message}`);
} }
} }

View File

@@ -1,15 +1,16 @@
import { v } from "convex/values"; import { v } from "convex/values";
import { mutation, query, internalMutation } from "./_generated/server"; import { mutation, query, internalMutation } from "./_generated/server";
import { internal } from "./_generated/api"; import { internal } from "./_generated/api";
import { Id } from "./_generated/dataModel"; import { Id, Doc } from "./_generated/dataModel";
import type { QueryCtx } from "./_generated/server";
/** /**
* Helper para obter usuário autenticado * Helper para obter usuário autenticado
*/ */
async function getUsuarioAutenticado(ctx: any) { async function getUsuarioAutenticado(ctx: QueryCtx) {
const usuariosOnline = await ctx.db.query("usuarios").collect(); const usuariosOnline = await ctx.db.query("usuarios").collect();
const usuarioOnline = usuariosOnline.find( const usuarioOnline = usuariosOnline.find(
(u: any) => u.statusPresenca === "online" (u) => u.statusPresenca === "online"
); );
return usuarioOnline || null; return usuarioOnline || null;
} }
@@ -298,7 +299,7 @@ export const verificarAlertasInternal = internalMutation({
for (const alerta of alertasAtivos) { for (const alerta of alertasAtivos) {
// Obter valor da métrica correspondente // Obter valor da métrica correspondente
const metricValue = (metrica as any)[alerta.metricName]; const metricValue = (metrica as Record<string, number>)[alerta.metricName];
if (metricValue === undefined) continue; if (metricValue === undefined) continue;
@@ -355,7 +356,7 @@ export const verificarAlertasInternal = internalMutation({
// Buscar usuários TI para notificar // Buscar usuários TI para notificar
const usuarios = await ctx.db.query("usuarios").collect(); const usuarios = await ctx.db.query("usuarios").collect();
const usuariosTI = usuarios.filter( const usuariosTI = usuarios.filter(
(u: any) => u.role?.nome === "ti" || u.role?.nivel === 0 (u) => u.role?.nome === "ti" || u.role?.nivel === 0
); );
for (const usuario of usuariosTI) { for (const usuario of usuariosTI) {

View File

@@ -9,7 +9,7 @@ import { Id } from "./_generated/dataModel";
*/ */
export const listarPerfisCustomizados = query({ export const listarPerfisCustomizados = query({
args: {}, args: {},
returns: v.array(v.any()), // Tipo inferido automaticamente pelo Convex
handler: async (ctx) => { handler: async (ctx) => {
const perfis = await ctx.db.query("perfisCustomizados").collect(); const perfis = await ctx.db.query("perfisCustomizados").collect();
@@ -47,11 +47,11 @@ export const obterPerfilComPermissoes = query({
}, },
returns: v.union( returns: v.union(
v.object({ v.object({
perfil: v.any(), perfil: v.any(), // Doc<"perfisCustomizados"> não pode ser validado diretamente
role: v.any(), role: v.any(), // Doc<"roles"> não pode ser validado diretamente
permissoes: v.array(v.any()), permissoes: v.array(v.any()), // Doc<"permissoes">[] não pode ser validado diretamente
menuPermissoes: v.array(v.any()), menuPermissoes: v.array(v.any()), // Doc<"menuPermissoes">[] não pode ser validado diretamente
usuarios: v.array(v.any()), usuarios: v.array(v.any()), // Doc<"usuarios">[] não pode ser validado diretamente
}), }),
v.null() v.null()
), ),
@@ -227,7 +227,7 @@ export const editarPerfilCustomizado = mutation({
} }
// Atualizar perfil // Atualizar perfil
const updates: any = { const updates: Partial<Doc<"perfisCustomizados">> & { atualizadoEm: number } = {
atualizadoEm: Date.now(), atualizadoEm: Date.now(),
}; };
@@ -419,7 +419,7 @@ export const clonarPerfil = mutation({
// Log de atividade // Log de atividade
await registrarAtividade( await registrarAtividade(
ctx as any, ctx,
args.criadoPorId, args.criadoPorId,
"criar", "criar",
"perfis", "perfis",

View File

@@ -165,7 +165,7 @@ export const assertPermissaoAcaoAtual = internalQuery({
returns: v.null(), returns: v.null(),
handler: async (ctx, args) => { handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity(); const identity = await ctx.auth.getUserIdentity();
let usuarioAtual: any = null; let usuarioAtual: Doc<"usuarios"> | null = null;
if (identity && identity.email) { if (identity && identity.email) {
usuarioAtual = await ctx.db usuarioAtual = await ctx.db
@@ -187,9 +187,9 @@ export const assertPermissaoAcaoAtual = internalQuery({
if (!usuarioAtual) throw new Error("acesso_negado"); if (!usuarioAtual) throw new Error("acesso_negado");
const role: any = await ctx.db.get(usuarioAtual.roleId as any); const role = await ctx.db.get(usuarioAtual.roleId);
if (!role) throw new Error("acesso_negado"); if (!role) throw new Error("acesso_negado");
if ((role as any).nivel <= 1) return null; if (role.nivel <= 1) return null;
const permissao = await ctx.db const permissao = await ctx.db
.query("permissoes") .query("permissoes")
@@ -201,7 +201,7 @@ export const assertPermissaoAcaoAtual = internalQuery({
const links = await ctx.db const links = await ctx.db
.query("rolePermissoes") .query("rolePermissoes")
.withIndex("by_role", (q) => q.eq("roleId", (role as any)._id as any)) .withIndex("by_role", (q) => q.eq("roleId", role._id))
.collect(); .collect();
const ok = links.some((rp) => rp.permissaoId === permissao!._id); const ok = links.some((rp) => rp.permissaoId === permissao!._id);
if (!ok) throw new Error("acesso_negado"); if (!ok) throw new Error("acesso_negado");

View File

@@ -87,7 +87,7 @@ function calcularDataFimPeriodo(dataAdmissao: string, anosPassados: number): str
} }
// Helper: Obter regime de trabalho do funcionário // Helper: Obter regime de trabalho do funcionário
async function obterRegimeTrabalho(ctx: any, funcionarioId: Id<"funcionarios">): Promise<RegimeTrabalho> { async function obterRegimeTrabalho(ctx: QueryCtx, funcionarioId: Id<"funcionarios">): Promise<RegimeTrabalho> {
const funcionario = await ctx.db.get(funcionarioId); const funcionario = await ctx.db.get(funcionarioId);
return funcionario?.regimeTrabalho || "clt"; // Default CLT return funcionario?.regimeTrabalho || "clt"; // Default CLT
} }

View File

@@ -1,6 +1,7 @@
import { v } from "convex/values"; import { v } from "convex/values";
import { mutation, query } from "./_generated/server"; import { mutation, query } from "./_generated/server";
import { registrarAtividade } from "./logsAtividades"; import { registrarAtividade } from "./logsAtividades";
import { Doc } from "./_generated/dataModel";
/** /**
* Listar todos os templates * Listar todos os templates
@@ -111,7 +112,7 @@ export const editarTemplate = mutation({
} }
// Atualizar template // Atualizar template
const updates: any = {}; const updates: Partial<Doc<"templatesMensagens">> = {};
if (args.nome !== undefined) updates.nome = args.nome; if (args.nome !== undefined) updates.nome = args.nome;
if (args.titulo !== undefined) updates.titulo = args.titulo; if (args.titulo !== undefined) updates.titulo = args.titulo;
if (args.corpo !== undefined) updates.corpo = args.corpo; if (args.corpo !== undefined) updates.corpo = args.corpo;

View File

@@ -1,11 +1,11 @@
import { v } from "convex/values"; import { v } from "convex/values";
import { mutation, query } from "./_generated/server"; import { mutation, query } from "./_generated/server";
import { Id } from "./_generated/dataModel"; import { Id, Doc } from "./_generated/dataModel";
// Query: Listar todos os times // Query: Listar todos os times
// Tipo inferido automaticamente pelo Convex
export const listar = query({ export const listar = query({
args: {}, args: {},
returns: v.array(v.any()),
handler: async (ctx) => { handler: async (ctx) => {
const times = await ctx.db.query("times").collect(); const times = await ctx.db.query("times").collect();
@@ -31,9 +31,9 @@ export const listar = query({
}); });
// Query: Obter time por ID com membros // Query: Obter time por ID com membros
// Tipo inferido automaticamente pelo Convex
export const obterPorId = query({ export const obterPorId = query({
args: { id: v.id("times") }, args: { id: v.id("times") },
returns: v.union(v.any(), v.null()),
handler: async (ctx, args) => { handler: async (ctx, args) => {
const time = await ctx.db.get(args.id); const time = await ctx.db.get(args.id);
if (!time) return null; if (!time) return null;
@@ -64,9 +64,9 @@ export const obterPorId = query({
}); });
// Query: Obter time do funcionário // Query: Obter time do funcionário
// Tipo inferido automaticamente pelo Convex
export const obterTimeFuncionario = query({ export const obterTimeFuncionario = query({
args: { funcionarioId: v.id("funcionarios") }, args: { funcionarioId: v.id("funcionarios") },
returns: v.union(v.any(), v.null()),
handler: async (ctx, args) => { handler: async (ctx, args) => {
const relacao = await ctx.db const relacao = await ctx.db
.query("timesMembros") .query("timesMembros")
@@ -89,9 +89,9 @@ export const obterTimeFuncionario = query({
}); });
// Query: Obter times do gestor // Query: Obter times do gestor
// Tipo inferido automaticamente pelo Convex
export const listarPorGestor = query({ export const listarPorGestor = query({
args: { gestorId: v.id("usuarios") }, args: { gestorId: v.id("usuarios") },
returns: v.array(v.any()),
handler: async (ctx, args) => { handler: async (ctx, args) => {
const times = await ctx.db const times = await ctx.db
.query("times") .query("times")

View File

@@ -11,15 +11,22 @@
"jsx": "react-jsx", "jsx": "react-jsx",
"skipLibCheck": true, "skipLibCheck": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"types": [],
/* These compiler options are required by Convex */ /* These compiler options are required by Convex */
"target": "ESNext", "target": "ESNext",
"lib": ["ES2021", "dom"], "lib": [
"ES2021",
"dom"
],
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"module": "ESNext", "module": "ESNext",
"isolatedModules": true, "isolatedModules": true,
"noEmit": true "noEmit": true
}, },
"include": ["./**/*"], "include": [
"exclude": ["./_generated"] "./**/*"
} ],
"exclude": [
"./_generated"
]
}

View File

@@ -2,7 +2,7 @@ import { v } from "convex/values";
import { mutation, query } from "./_generated/server"; import { mutation, query } from "./_generated/server";
import { hashPassword, generateToken } from "./auth/utils"; import { hashPassword, generateToken } from "./auth/utils";
import { registrarAtividade } from "./logsAtividades"; import { registrarAtividade } from "./logsAtividades";
import { Id } from "./_generated/dataModel"; import { Id, Doc } from "./_generated/dataModel";
import { api } from "./_generated/api"; import { api } from "./_generated/api";
/** /**
@@ -436,7 +436,7 @@ export const atualizarPerfil = mutation({
} }
// Atualizar apenas os campos fornecidos // Atualizar apenas os campos fornecidos
const updates: any = { atualizadoEm: Date.now() }; const updates: Partial<Doc<"usuarios">> & { atualizadoEm: number } = { atualizadoEm: Date.now() };
if (args.avatar !== undefined) updates.avatar = args.avatar; if (args.avatar !== undefined) updates.avatar = args.avatar;
if (args.fotoPerfil !== undefined) updates.fotoPerfil = args.fotoPerfil; if (args.fotoPerfil !== undefined) updates.fotoPerfil = args.fotoPerfil;
@@ -882,7 +882,7 @@ export const editarUsuario = mutation({
} }
// Atualizar campos fornecidos // Atualizar campos fornecidos
const updates: any = { const updates: Partial<Doc<"usuarios">> & { atualizadoEm: number } = {
atualizadoEm: Date.now(), atualizadoEm: Date.now(),
}; };

View File

@@ -1,5 +1,6 @@
import { internalMutation, query } from "./_generated/server"; import { internalMutation, query } from "./_generated/server";
import { v } from "convex/values"; import { v } from "convex/values";
import { Id, Doc } from "./_generated/dataModel";
/** /**
* Verificar duplicatas de matrícula * Verificar duplicatas de matrícula
@@ -33,7 +34,7 @@ export const verificarDuplicatas = query({
email: usuario.email || "", email: usuario.email || "",
}); });
return acc; return acc;
}, {} as Record<string, any[]>); }, {} as Record<string, Array<{ _id: Id<"usuarios">; nome: string; email: string }>>);
// Filtrar apenas duplicatas // Filtrar apenas duplicatas
const duplicatas = Object.entries(gruposPorMatricula) const duplicatas = Object.entries(gruposPorMatricula)
@@ -67,7 +68,7 @@ export const removerDuplicatas = internalMutation({
} }
acc[usuario.matricula].push(usuario); acc[usuario.matricula].push(usuario);
return acc; return acc;
}, {} as Record<string, any[]>); }, {} as Record<string, Doc<"usuarios">[]>);
let removidos = 0; let removidos = 0;
const matriculasDuplicadas: string[] = []; const matriculasDuplicadas: string[] = [];