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); // Buscar foto do perfil do funcionário através do usuário associado let fotoPerfilUrl: string | null = null; if (funcionario) { 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); } } return { ...rel, funcionario: funcionario ? { ...funcionario, fotoPerfilUrl } : null }; }) ); 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; } });