feat: enhance ErrorModal and chat components with new features and improvements

- Refactored ErrorModal to utilize a dialog element for better accessibility and user experience, including a close button with an icon.
- Updated chat components to improve participant display and message read status, enhancing user engagement and clarity.
- Introduced loading indicators for user and conversation data in SalaReuniaoManager to improve responsiveness during data fetching.
- Enhanced message handling in MessageList to indicate whether messages have been read, providing users with better feedback on message status.
- Improved overall structure and styling across various components for consistency and maintainability.
This commit is contained in:
2025-11-05 14:05:52 -03:00
parent c459297968
commit 6166043735
9 changed files with 394 additions and 104 deletions

View File

@@ -346,6 +346,7 @@ export const enviarMensagem = mutation({
mencoes: args.mencoes,
respostaPara: args.respostaPara,
enviadaEm: Date.now(),
lidaPor: [], // Inicializar como array vazio
});
// Detectar URLs no conteúdo e extrair preview (apenas para mensagens de texto, assíncrono)
@@ -495,14 +496,19 @@ export const agendarMensagem = mutation({
throw new Error("Você não pertence a esta conversa");
}
// Normalizar conteúdo para busca
const conteudoBusca = normalizarTextoParaBusca(args.conteudo);
// Criar mensagem agendada
const mensagemId = await ctx.db.insert("mensagens", {
conversaId: args.conversaId,
remetenteId: usuarioAtual._id,
tipo: "texto",
conteudo: args.conteudo,
conteudoBusca,
agendadaPara: args.agendadaPara,
enviadaEm: args.agendadaPara, // Será usada quando a mensagem for enviada
enviadaEm: args.agendadaPara, // Será atualizado quando a mensagem for enviada
lidaPor: [], // Inicializar como array vazio
});
return mensagemId;
@@ -662,6 +668,29 @@ export const marcarComoLida = mutation({
});
}
// Atualizar status de leitura nas mensagens
// Buscar todas as mensagens até a mensagem atual (incluindo ela) na conversa
const todasMensagens = await ctx.db
.query("mensagens")
.withIndex("by_conversa", (q) => q.eq("conversaId", args.conversaId))
.filter((q) =>
q.and(
q.lte(q.field("enviadaEm"), mensagem.enviadaEm),
q.neq(q.field("remetenteId"), usuarioAtual._id) // Apenas mensagens de outros usuários
)
)
.collect();
// Atualizar cada mensagem para incluir o usuário atual no array lidaPor (se ainda não estiver)
for (const msg of todasMensagens) {
const lidaPor = msg.lidaPor || [];
if (!lidaPor.includes(usuarioAtual._id)) {
await ctx.db.patch(msg._id, {
lidaPor: [...lidaPor, usuarioAtual._id],
});
}
}
// Marcar notificações desta conversa como lidas
const notificacoes = await ctx.db
.query("notificacoes")
@@ -1455,16 +1484,33 @@ export const enviarNotificacaoReuniao = mutation({
// Criar notificação para todos os participantes
for (const participanteId of conversa.participantes) {
const tituloNotificacao = args.titulo || "Notificação da sala de reunião";
const descricaoNotificacao = args.mensagem.substring(0, 100); // Limitar descrição para push
// Criar notificação no banco
await ctx.db.insert("notificacoes", {
usuarioId: participanteId,
tipo: "nova_mensagem",
conversaId: args.conversaId,
remetenteId: usuarioAtual._id,
titulo: args.titulo || "Notificação da sala de reunião",
titulo: tituloNotificacao,
descricao: args.mensagem,
lida: false,
criadaEm: Date.now(),
});
// Enviar push notification (assíncrono, não bloqueia)
ctx.scheduler.runAfter(0, internal.pushNotifications.enviarPushNotification, {
usuarioId: participanteId,
titulo: tituloNotificacao,
corpo: descricaoNotificacao,
data: {
conversaId: args.conversaId,
tipo: "notificacao_reuniao",
},
}).catch((error) => {
console.error(`Erro ao agendar push para usuário ${participanteId}:`, error);
});
}
return { sucesso: true };
@@ -1952,6 +1998,13 @@ export const listarTodosUsuarios = query({
const funcionario = await ctx.db.get(u.funcionarioId);
matricula = funcionario?.matricula;
}
// Buscar URL da foto de perfil se existir
let fotoPerfilUrl: string | null = null;
if (u.fotoPerfil) {
fotoPerfilUrl = await ctx.storage.getUrl(u.fotoPerfil);
}
return {
_id: u._id,
nome: u.nome,
@@ -1959,6 +2012,7 @@ export const listarTodosUsuarios = query({
matricula,
avatar: u.avatar,
fotoPerfil: u.fotoPerfil,
fotoPerfilUrl,
statusPresenca: u.statusPresenca,
statusMensagem: u.statusMensagem,
setor: u.setor,
@@ -2246,22 +2300,23 @@ export const enviarMensagensAgendadas = internalMutation({
const agora = Date.now();
// Buscar mensagens que deveriam ser enviadas
// Como o índice by_agendamento indexa por agendadaPara, podemos usar range query
// Buscar mensagens com agendadaPara entre 0 e agora (mensagens agendadas que já devem ser enviadas)
// Valores undefined não aparecem no índice, então só buscamos mensagens realmente agendadas
const mensagensAgendadas = await ctx.db
.query("mensagens")
.withIndex("by_agendamento")
.filter((q) =>
q.and(
q.neq(q.field("agendadaPara"), undefined),
q.lte(q.field("agendadaPara"), agora)
)
)
.withIndex("by_agendamento", (q) => q.gte("agendadaPara", 0).lte("agendadaPara", agora))
.collect();
for (const mensagem of mensagensAgendadas) {
// Normalizar conteúdo para busca (se ainda não foi feito)
const conteudoBusca = mensagem.conteudoBusca || normalizarTextoParaBusca(mensagem.conteudo);
// Atualizar mensagem para "enviada"
await ctx.db.patch(mensagem._id, {
agendadaPara: undefined,
enviadaEm: agora,
conteudoBusca: conteudoBusca, // Garantir que tem conteúdo de busca
});
// Atualizar última mensagem da conversa
@@ -2275,19 +2330,43 @@ export const enviarMensagensAgendadas = internalMutation({
// Criar notificações para outros participantes
const remetente = await ctx.db.get(mensagem.remetenteId);
for (const participanteId of conversa.participantes) {
if (participanteId !== mensagem.remetenteId) {
await ctx.db.insert("notificacoes", {
usuarioId: participanteId,
tipo: "nova_mensagem",
conversaId: mensagem.conversaId,
mensagemId: mensagem._id,
remetenteId: mensagem.remetenteId,
titulo: `Nova mensagem de ${remetente?.nome || "Usuário"}`,
descricao: mensagem.conteudo.substring(0, 100),
lida: false,
criadaEm: agora,
});
if (remetente) {
// Determinar tipo de notificação (se há menções)
const tipoNotificacao = mensagem.mencoes && mensagem.mencoes.length > 0 ? "mencao" : "nova_mensagem";
const titulo = tipoNotificacao === "mencao"
? `${remetente.nome} mencionou você`
: `Nova mensagem de ${remetente.nome}`;
const descricao = mensagem.conteudo.substring(0, 100);
for (const participanteId of conversa.participantes) {
if (participanteId !== mensagem.remetenteId) {
// Criar notificação no banco
await ctx.db.insert("notificacoes", {
usuarioId: participanteId,
tipo: tipoNotificacao,
conversaId: mensagem.conversaId,
mensagemId: mensagem._id,
remetenteId: mensagem.remetenteId,
titulo,
descricao,
lida: false,
criadaEm: agora,
});
// Enviar push notification (assíncrono, não bloqueia)
ctx.scheduler.runAfter(0, internal.pushNotifications.enviarPushNotification, {
usuarioId: participanteId,
titulo,
corpo: descricao,
data: {
conversaId: mensagem.conversaId,
mensagemId: mensagem._id,
tipo: tipoNotificacao,
},
}).catch((error) => {
console.error(`Erro ao agendar push para usuário ${participanteId}:`, error);
});
}
}
}
}

View File

@@ -671,6 +671,7 @@ export default defineSchema({
enviadaEm: v.number(),
editadaEm: v.optional(v.number()),
deletada: v.optional(v.boolean()),
lidaPor: v.optional(v.array(v.id("usuarios"))), // IDs dos usuários que leram a mensagem
})
.index("by_conversa", ["conversaId", "enviadaEm"])
.index("by_remetente", ["remetenteId"])