feat: implement vacation management system with request approval, notification handling, and employee training tracking; enhance UI components for improved user experience

This commit is contained in:
2025-10-29 22:05:29 -03:00
parent f219340cd8
commit 16bcd2ac25
21 changed files with 3910 additions and 617 deletions

View File

@@ -0,0 +1,171 @@
import { internalMutation } from "./_generated/server";
import { v } from "convex/values";
/**
* Migração: Converte estrutura antiga de gestores individuais para times
*
* Esta função cria automaticamente times baseados nos gestores existentes
* e adiciona os funcionários subordinados aos respectivos times.
*
* Execute uma vez via dashboard do Convex:
* Settings > Functions > Internal > migrarParaTimes > executar
*/
export const executar = internalMutation({
args: {},
returns: v.object({
timesCreated: v.number(),
funcionariosAtribuidos: v.number(),
erros: v.array(v.string()),
}),
handler: async (ctx) => {
const erros: string[] = [];
let timesCreated = 0;
let funcionariosAtribuidos = 0;
try {
// 1. Buscar todos os funcionários que têm gestor definido
const funcionariosComGestor = await ctx.db
.query("funcionarios")
.filter((q) => q.neq(q.field("gestorId"), undefined))
.collect();
if (funcionariosComGestor.length === 0) {
return {
timesCreated: 0,
funcionariosAtribuidos: 0,
erros: ["Nenhum funcionário com gestor configurado encontrado"],
};
}
// 2. Agrupar funcionários por gestor
const gestoresMap = new Map<string, any[]>();
for (const funcionario of funcionariosComGestor) {
if (!funcionario.gestorId) continue;
const gestorId = funcionario.gestorId;
if (!gestoresMap.has(gestorId)) {
gestoresMap.set(gestorId, []);
}
gestoresMap.get(gestorId)!.push(funcionario);
}
// 3. Para cada gestor, criar um time
for (const [gestorId, subordinados] of gestoresMap.entries()) {
try {
const gestor = await ctx.db.get(gestorId as any);
if (!gestor) {
erros.push(`Gestor ${gestorId} não encontrado`);
continue;
}
// Verificar se já existe time para este gestor
const timeExistente = await ctx.db
.query("times")
.withIndex("by_gestor", (q) => q.eq("gestorId", gestorId as any))
.filter((q) => q.eq(q.field("ativo"), true))
.first();
let timeId;
if (timeExistente) {
timeId = timeExistente._id;
} else {
// Criar novo time
timeId = await ctx.db.insert("times", {
nome: `Equipe ${gestor.nome}`,
descricao: `Time gerenciado por ${gestor.nome} (migração automática)`,
gestorId: gestorId as any,
ativo: true,
cor: "#3B82F6",
});
timesCreated++;
}
// Adicionar membros ao time
for (const funcionario of subordinados) {
try {
// Verificar se já está em algum time
const membroExistente = await ctx.db
.query("timesMembros")
.withIndex("by_funcionario", (q) => q.eq("funcionarioId", funcionario._id))
.filter((q) => q.eq(q.field("ativo"), true))
.first();
if (!membroExistente) {
await ctx.db.insert("timesMembros", {
timeId: timeId,
funcionarioId: funcionario._id,
dataEntrada: Date.now(),
ativo: true,
});
funcionariosAtribuidos++;
}
} catch (e: any) {
erros.push(`Erro ao adicionar ${funcionario.nome} ao time: ${e.message}`);
}
}
} catch (e: any) {
erros.push(`Erro ao processar gestor ${gestorId}: ${e.message}`);
}
}
return {
timesCreated,
funcionariosAtribuidos,
erros,
};
} catch (e: any) {
erros.push(`Erro geral na migração: ${e.message}`);
return {
timesCreated,
funcionariosAtribuidos,
erros,
};
}
},
});
/**
* Função auxiliar para limpar times inativos antigos
*/
export const limparTimesInativos = internalMutation({
args: {
diasInativos: v.optional(v.number()),
},
returns: v.number(),
handler: async (ctx, args) => {
const diasLimite = args.diasInativos || 30;
const dataLimite = Date.now() - (diasLimite * 24 * 60 * 60 * 1000);
const timesInativos = await ctx.db
.query("times")
.filter((q) => q.eq(q.field("ativo"), false))
.collect();
let removidos = 0;
for (const time of timesInativos) {
if (time._creationTime < dataLimite) {
// Remover membros inativos do time
const membrosInativos = await ctx.db
.query("timesMembros")
.withIndex("by_time", (q) => q.eq("timeId", time._id))
.filter((q) => q.eq(q.field("ativo"), false))
.collect();
for (const membro of membrosInativos) {
await ctx.db.delete(membro._id);
}
// Remover o time
await ctx.db.delete(time._id);
removidos++;
}
}
return removidos;
},
});