import { v } from "convex/values"; import { mutation, query } from "./_generated/server"; import { getCurrentUserFunction } from "./auth"; // Query: Listar todos os times // Tipo inferido automaticamente pelo Convex export const listar = query({ args: {}, handler: async (ctx) => { const times = await ctx.db.query("times").collect(); // Buscar gestor e contar membros de cada time const timesComDetalhes = await Promise.all( times.map(async (time) => { const gestor = await ctx.db.get(time.gestorId); const membrosAtivos = await ctx.db .query("timesMembros") .withIndex("by_time_and_ativo", (q) => q.eq("timeId", time._id).eq("ativo", true)) .collect(); return { ...time, gestor, totalMembros: membrosAtivos.length, }; }) ); return timesComDetalhes; }, }); // Query: Obter time por ID com membros // Tipo inferido automaticamente pelo Convex export const obterPorId = query({ args: { id: v.id("times") }, handler: async (ctx, args) => { const time = await ctx.db.get(args.id); if (!time) return null; const gestor = await ctx.db.get(time.gestorId); const membrosRelacoes = await ctx.db .query("timesMembros") .withIndex("by_time_and_ativo", (q) => q.eq("timeId", args.id).eq("ativo", true)) .collect(); // Buscar dados completos dos membros const membros = await Promise.all( membrosRelacoes.map(async (rel) => { const funcionario = await ctx.db.get(rel.funcionarioId); return { ...rel, funcionario, }; }) ); return { ...time, gestor, membros, }; }, }); // Query: Obter time do funcionário // Tipo inferido automaticamente pelo Convex export const obterTimeFuncionario = query({ args: { funcionarioId: v.id("funcionarios") }, handler: async (ctx, args) => { const relacao = await ctx.db .query("timesMembros") .withIndex("by_funcionario", (q) => q.eq("funcionarioId", args.funcionarioId)) .filter((q) => q.eq(q.field("ativo"), true)) .first(); if (!relacao) return null; const time = await ctx.db.get(relacao.timeId); if (!time) return null; const gestor = await ctx.db.get(time.gestorId); return { ...time, gestor, }; }, }); // Query: Obter times do gestor // Tipo inferido automaticamente pelo Convex export const listarPorGestor = query({ args: { gestorId: v.id("usuarios") }, handler: async (ctx, args) => { const times = await ctx.db .query("times") .withIndex("by_gestor", (q) => q.eq("gestorId", args.gestorId)) .filter((q) => q.eq(q.field("ativo"), true)) .collect(); const timesComMembros = await Promise.all( times.map(async (time) => { const membrosRelacoes = await ctx.db .query("timesMembros") .withIndex("by_time_and_ativo", (q) => q.eq("timeId", time._id).eq("ativo", true)) .collect(); const membros = await Promise.all( membrosRelacoes.map(async (rel) => { const funcionario = await ctx.db.get(rel.funcionarioId); return { ...rel, funcionario, }; }) ); return { ...time, membros, }; }) ); return timesComMembros; }, }); export const listarSubordinadosDoGestorAtual = query({ args: {}, handler: async (ctx) => { const usuario = await getCurrentUserFunction(ctx); if (!usuario) { return []; } const timesGestor = await ctx.db .query("times") .withIndex("by_gestor", (q) => q.eq("gestorId", usuario._id)) .collect(); const timesComoSuperior = await ctx.db .query("times") .withIndex("by_gestor_superior", (q) => q.eq("gestorSuperiorId", usuario._id)) .collect(); const timesMap = new Map( [...timesGestor, ...timesComoSuperior] .filter((time) => time.ativo) .map((time) => [time._id, time]) ); const resultado = []; for (const time of timesMap.values()) { const membrosRelacoes = await ctx.db .query("timesMembros") .withIndex("by_time_and_ativo", (q) => q.eq("timeId", time._id).eq("ativo", true)) .collect(); const membros = []; for (const rel of membrosRelacoes) { const funcionario = await ctx.db.get(rel.funcionarioId); if (funcionario) { // Buscar foto do perfil do funcionário através do usuário associado let fotoPerfilUrl: string | null = null; const usuario = await ctx.db .query("usuarios") .withIndex("by_funcionarioId", (q) => q.eq("funcionarioId", funcionario._id)) .first(); if (usuario?.fotoPerfil) { fotoPerfilUrl = await ctx.storage.getUrl(usuario.fotoPerfil); } membros.push({ relacaoId: rel._id, funcionario: { ...funcionario, fotoPerfilUrl, }, dataEntrada: rel.dataEntrada, }); } } resultado.push({ ...time, membros, }); } return resultado; }, }); // Mutation: Criar time export const criar = mutation({ args: { nome: v.string(), descricao: v.optional(v.string()), gestorId: v.id("usuarios"), gestorSuperiorId: v.optional(v.id("usuarios")), cor: v.optional(v.string()), }, returns: v.id("times"), handler: async (ctx, args) => { const timeId = await ctx.db.insert("times", { nome: args.nome, descricao: args.descricao, gestorId: args.gestorId, gestorSuperiorId: args.gestorSuperiorId ?? args.gestorId, ativo: true, cor: args.cor || "#3B82F6", }); return timeId; }, }); // Mutation: Atualizar time export const atualizar = mutation({ args: { id: v.id("times"), nome: v.string(), descricao: v.optional(v.string()), gestorId: v.id("usuarios"), gestorSuperiorId: v.optional(v.id("usuarios")), cor: v.optional(v.string()), }, returns: v.null(), handler: async (ctx, args) => { const { id, ...dados } = args; await ctx.db.patch(id, { ...dados, gestorSuperiorId: dados.gestorSuperiorId ?? dados.gestorId, }); return null; }, }); // Mutation: Desativar time export const desativar = mutation({ args: { id: v.id("times") }, returns: v.null(), handler: async (ctx, args) => { // Desativar o time await ctx.db.patch(args.id, { ativo: false }); // Desativar todos os membros const membros = await ctx.db .query("timesMembros") .withIndex("by_time_and_ativo", (q) => q.eq("timeId", args.id).eq("ativo", true)) .collect(); for (const membro of membros) { await ctx.db.patch(membro._id, { ativo: false, dataSaida: Date.now(), }); await ctx.db.patch(membro.funcionarioId, { gestorId: undefined }); } return null; }, }); // Mutation: Adicionar membro ao time export const adicionarMembro = mutation({ args: { timeId: v.id("times"), funcionarioId: v.id("funcionarios"), }, returns: v.id("timesMembros"), handler: async (ctx, args) => { // Verificar se já não está em outro time ativo const membroExistente = await ctx.db .query("timesMembros") .withIndex("by_funcionario", (q) => q.eq("funcionarioId", args.funcionarioId)) .filter((q) => q.eq(q.field("ativo"), true)) .first(); if (membroExistente) { throw new Error("Funcionário já está em um time ativo"); } const time = await ctx.db.get(args.timeId); if (!time || !time.ativo) { throw new Error("Time inválido ou inativo"); } const membroId = await ctx.db.insert("timesMembros", { timeId: args.timeId, funcionarioId: args.funcionarioId, dataEntrada: Date.now(), ativo: true, }); await ctx.db.patch(args.funcionarioId, { gestorId: time.gestorId }); return membroId; }, }); // Mutation: Remover membro do time export const removerMembro = mutation({ args: { membroId: v.id("timesMembros") }, returns: v.null(), handler: async (ctx, args) => { const membro = await ctx.db.get(args.membroId); if (!membro) { throw new Error("Membro não encontrado"); } await ctx.db.patch(args.membroId, { ativo: false, dataSaida: Date.now(), }); await ctx.db.patch(membro.funcionarioId, { gestorId: undefined }); return null; }, }); // Mutation: Transferir membro para outro time export const transferirMembro = mutation({ args: { funcionarioId: v.id("funcionarios"), novoTimeId: v.id("times"), }, returns: v.null(), handler: async (ctx, args) => { // Desativar do time atual const relacaoAtual = await ctx.db .query("timesMembros") .withIndex("by_funcionario", (q) => q.eq("funcionarioId", args.funcionarioId)) .filter((q) => q.eq(q.field("ativo"), true)) .first(); if (relacaoAtual) { await ctx.db.patch(relacaoAtual._id, { ativo: false, dataSaida: Date.now(), }); } // Adicionar ao novo time const novoTime = await ctx.db.get(args.novoTimeId); if (!novoTime || !novoTime.ativo) { throw new Error("Novo time inválido ou inativo"); } await ctx.db.insert("timesMembros", { timeId: args.novoTimeId, funcionarioId: args.funcionarioId, dataEntrada: Date.now(), ativo: true, }); await ctx.db.patch(args.funcionarioId, { gestorId: novoTime.gestorId }); return null; }, });