feat: implement comprehensive chat system with user presence management, notification handling, and avatar integration; enhance UI components for improved user experience

This commit is contained in:
2025-10-28 11:57:54 -03:00
parent 81e6eb4a42
commit ee2c9c3ae0
47 changed files with 8274 additions and 195 deletions

View File

@@ -318,3 +318,254 @@ export const alterarRole = mutation({
},
});
/**
* Atualizar perfil do usuário (foto, avatar, setor, status, preferências)
*/
export const atualizarPerfil = mutation({
args: {
avatar: v.optional(v.string()),
fotoPerfil: v.optional(v.id("_storage")),
setor: v.optional(v.string()),
statusMensagem: v.optional(v.string()),
statusPresenca: v.optional(
v.union(
v.literal("online"),
v.literal("offline"),
v.literal("ausente"),
v.literal("externo"),
v.literal("em_reuniao")
)
),
notificacoesAtivadas: v.optional(v.boolean()),
somNotificacao: v.optional(v.boolean()),
},
returns: v.null(),
handler: async (ctx, args) => {
// TENTAR BETTER AUTH PRIMEIRO
const identity = await ctx.auth.getUserIdentity();
let usuarioAtual = null;
if (identity && identity.email) {
// Buscar por email (Better Auth)
usuarioAtual = await ctx.db
.query("usuarios")
.withIndex("by_email", (q) => q.eq("email", identity.email!))
.first();
}
// SE NÃO ENCONTROU, BUSCAR POR SESSÃO ATIVA (Sistema customizado)
if (!usuarioAtual) {
const sessaoAtiva = await ctx.db
.query("sessoes")
.filter((q) => q.eq(q.field("ativo"), true))
.order("desc")
.first();
if (sessaoAtiva) {
usuarioAtual = await ctx.db.get(sessaoAtiva.usuarioId);
}
}
if (!usuarioAtual) throw new Error("Usuário não encontrado");
// Validar statusMensagem (max 100 chars)
if (args.statusMensagem && args.statusMensagem.length > 100) {
throw new Error("Mensagem de status deve ter no máximo 100 caracteres");
}
// Atualizar apenas os campos fornecidos
const updates: any = { atualizadoEm: Date.now() };
if (args.avatar !== undefined) updates.avatar = args.avatar;
if (args.fotoPerfil !== undefined) updates.fotoPerfil = args.fotoPerfil;
if (args.setor !== undefined) updates.setor = args.setor;
if (args.statusMensagem !== undefined) updates.statusMensagem = args.statusMensagem;
if (args.statusPresenca !== undefined) {
updates.statusPresenca = args.statusPresenca;
updates.ultimaAtividade = Date.now();
}
if (args.notificacoesAtivadas !== undefined) updates.notificacoesAtivadas = args.notificacoesAtivadas;
if (args.somNotificacao !== undefined) updates.somNotificacao = args.somNotificacao;
await ctx.db.patch(usuarioAtual._id, updates);
return null;
},
});
/**
* Obter perfil do usuário atual
*/
export const obterPerfil = query({
args: {},
handler: async (ctx) => {
console.log("=== DEBUG obterPerfil ===");
// TENTAR BETTER AUTH PRIMEIRO
const identity = await ctx.auth.getUserIdentity();
console.log("Identity:", identity ? "encontrado" : "null");
let usuarioAtual = null;
if (identity && identity.email) {
console.log("Tentando buscar por email:", identity.email);
// Buscar por email (Better Auth)
usuarioAtual = await ctx.db
.query("usuarios")
.withIndex("by_email", (q) => q.eq("email", identity.email!))
.first();
console.log("Usuário encontrado por email:", usuarioAtual ? "SIM" : "NÃO");
}
// SE NÃO ENCONTROU, BUSCAR POR SESSÃO ATIVA (Sistema customizado)
if (!usuarioAtual) {
console.log("Buscando por sessão ativa...");
const sessaoAtiva = await ctx.db
.query("sessoes")
.filter((q) => q.eq(q.field("ativo"), true))
.order("desc")
.first();
console.log("Sessão ativa encontrada:", sessaoAtiva ? "SIM" : "NÃO");
if (sessaoAtiva) {
usuarioAtual = await ctx.db.get(sessaoAtiva.usuarioId);
console.log("Usuário da sessão encontrado:", usuarioAtual ? "SIM" : "NÃO");
}
}
if (!usuarioAtual) {
console.log("❌ Nenhum usuário encontrado");
// Listar todos os usuários para debug
const todosUsuarios = await ctx.db.query("usuarios").collect();
console.log("Total de usuários no banco:", todosUsuarios.length);
console.log("Emails cadastrados:", todosUsuarios.map(u => u.email));
return null;
}
console.log("✅ Usuário encontrado:", usuarioAtual.nome);
// Buscar fotoPerfil URL se existir
let fotoPerfilUrl = null;
if (usuarioAtual.fotoPerfil) {
fotoPerfilUrl = await ctx.storage.getUrl(usuarioAtual.fotoPerfil);
}
return {
_id: usuarioAtual._id,
nome: usuarioAtual.nome,
email: usuarioAtual.email,
matricula: usuarioAtual.matricula,
avatar: usuarioAtual.avatar,
fotoPerfil: usuarioAtual.fotoPerfil,
fotoPerfilUrl,
setor: usuarioAtual.setor,
statusMensagem: usuarioAtual.statusMensagem,
statusPresenca: usuarioAtual.statusPresenca,
notificacoesAtivadas: usuarioAtual.notificacoesAtivadas ?? true,
somNotificacao: usuarioAtual.somNotificacao ?? true,
};
},
});
/**
* Listar todos usuários para o chat (com avatar, foto e status)
*/
export const listarParaChat = query({
args: {},
returns: v.array(
v.object({
_id: v.id("usuarios"),
nome: v.string(),
email: v.string(),
matricula: v.string(),
avatar: v.optional(v.string()),
fotoPerfil: v.optional(v.id("_storage")),
fotoPerfilUrl: v.union(v.string(), v.null()),
statusPresenca: v.optional(
v.union(
v.literal("online"),
v.literal("offline"),
v.literal("ausente"),
v.literal("externo"),
v.literal("em_reuniao")
)
),
statusMensagem: v.optional(v.string()),
ultimaAtividade: v.optional(v.number()),
})
),
handler: async (ctx) => {
// Buscar todos os usuários ativos
const usuarios = await ctx.db
.query("usuarios")
.filter((q) => q.eq(q.field("ativo"), true))
.collect();
// Buscar foto de perfil URL para cada usuário
const usuariosComFoto = await Promise.all(
usuarios.map(async (usuario) => {
let fotoPerfilUrl = null;
if (usuario.fotoPerfil) {
fotoPerfilUrl = await ctx.storage.getUrl(usuario.fotoPerfil);
}
return {
_id: usuario._id,
nome: usuario.nome,
email: usuario.email,
matricula: usuario.matricula,
avatar: usuario.avatar,
fotoPerfil: usuario.fotoPerfil,
fotoPerfilUrl,
statusPresenca: usuario.statusPresenca || "offline",
statusMensagem: usuario.statusMensagem,
ultimaAtividade: usuario.ultimaAtividade,
};
})
);
return usuariosComFoto;
},
});
/**
* Gera URL para upload de foto de perfil
*/
export const uploadFotoPerfil = mutation({
args: {},
handler: async (ctx) => {
// TENTAR BETTER AUTH PRIMEIRO
const identity = await ctx.auth.getUserIdentity();
let usuarioAtual = null;
if (identity && identity.email) {
// Buscar por email (Better Auth)
usuarioAtual = await ctx.db
.query("usuarios")
.withIndex("by_email", (q) => q.eq("email", identity.email!))
.first();
}
// SE NÃO ENCONTROU, BUSCAR POR SESSÃO ATIVA (Sistema customizado)
if (!usuarioAtual) {
const sessaoAtiva = await ctx.db
.query("sessoes")
.filter((q) => q.eq(q.field("ativo"), true))
.order("desc")
.first();
if (sessaoAtiva) {
usuarioAtual = await ctx.db.get(sessaoAtiva.usuarioId);
}
}
if (!usuarioAtual) throw new Error("Usuário não autenticado");
return await ctx.storage.generateUploadUrl();
},
});