From 9d2f6e7c79330fc4b4318a4f48947a57d3f3985c Mon Sep 17 00:00:00 2001 From: deyvisonwanderley Date: Fri, 21 Nov 2025 16:57:21 -0300 Subject: [PATCH] feat: add error handling modal to ChatWindow and improve call initiation logic - Introduced ErrorModal in ChatWindow to display error messages related to call initiation issues. - Enhanced error handling during call initiation to provide user-friendly feedback based on different error scenarios. - Refactored backend queries to streamline the retrieval of active calls, ensuring accurate status checks. - Updated function names for clarity and consistency in the backend call management logic. --- .../src/lib/components/chat/ChatWindow.svelte | 49 +++++++++++++++- packages/backend/convex/_generated/api.d.ts | 2 + packages/backend/convex/chamadas.ts | 57 ++++++++++--------- packages/backend/convex/schema.ts | 1 - 4 files changed, 78 insertions(+), 31 deletions(-) diff --git a/apps/web/src/lib/components/chat/ChatWindow.svelte b/apps/web/src/lib/components/chat/ChatWindow.svelte index a86155c..0308121 100644 --- a/apps/web/src/lib/components/chat/ChatWindow.svelte +++ b/apps/web/src/lib/components/chat/ChatWindow.svelte @@ -10,6 +10,7 @@ import ScheduleMessageModal from './ScheduleMessageModal.svelte'; import SalaReuniaoManager from './SalaReuniaoManager.svelte'; import CallWindow from '../call/CallWindow.svelte'; + import ErrorModal from '../ErrorModal.svelte'; import { getAvatarUrl } from '$lib/utils/avatarGenerator'; import { browser } from '$app/environment'; import { @@ -41,6 +42,12 @@ let showNotificacaoModal = $state(false); let iniciandoChamada = $state(false); let chamadaAtiva = $state | null>(null); + + // Estados para modal de erro + let showErrorModal = $state(false); + let errorTitle = $state('Erro'); + let errorMessage = $state(''); + let errorDetails = $state(undefined); const chamadaAtivaQuery = useQuery(api.chamadas.obterChamadaAtiva, { conversaId: conversaId as Id<'conversas'> @@ -139,7 +146,10 @@ // Funções para chamadas async function iniciarChamada(tipo: 'audio' | 'video'): Promise { if (chamadaAtual) { - alert('Já existe uma chamada ativa nesta conversa'); + errorTitle = 'Chamada Ativa'; + errorMessage = 'Já existe uma chamada ativa nesta conversa'; + errorDetails = undefined; + showErrorModal = true; return; } @@ -155,12 +165,36 @@ chamadaAtiva = chamadaId; } catch (error) { console.error('Erro ao iniciar chamada:', error); - const errorMessage = error instanceof Error ? error.message : 'Erro ao iniciar chamada'; - alert(errorMessage); + + // Tratar diferentes tipos de erro + if (error instanceof Error) { + errorTitle = 'Erro ao Iniciar Chamada'; + + // Verificar se é erro do Convex sobre função não encontrada + if (error.message.includes('Could not find public function')) { + errorMessage = 'O servidor de chamadas não está sincronizado. Por favor, aguarde alguns segundos e tente novamente.'; + errorDetails = `Erro técnico: ${error.message}\n\nSugestão: Verifique se o backend Convex está rodando corretamente.`; + } else { + errorMessage = error.message || 'Erro ao iniciar chamada'; + errorDetails = error.stack; + } + } else { + errorTitle = 'Erro ao Iniciar Chamada'; + errorMessage = 'Ocorreu um erro desconhecido ao iniciar a chamada. Por favor, tente novamente.'; + errorDetails = String(error); + } + + showErrorModal = true; } finally { iniciandoChamada = false; } } + + function fecharErrorModal(): void { + showErrorModal = false; + errorMessage = ''; + errorDetails = undefined; + } function fecharChamada(): void { chamadaAtiva = null; @@ -592,3 +626,12 @@ {/if} + + + diff --git a/packages/backend/convex/_generated/api.d.ts b/packages/backend/convex/_generated/api.d.ts index ac18618..88b3687 100644 --- a/packages/backend/convex/_generated/api.d.ts +++ b/packages/backend/convex/_generated/api.d.ts @@ -18,6 +18,7 @@ import type * as ausencias from "../ausencias.js"; import type * as autenticacao from "../autenticacao.js"; import type * as auth from "../auth.js"; import type * as auth_utils from "../auth/utils.js"; +import type * as chamadas from "../chamadas.js"; import type * as chamados from "../chamados.js"; import type * as chat from "../chat.js"; import type * as configuracaoEmail from "../configuracaoEmail.js"; @@ -71,6 +72,7 @@ declare const fullApi: ApiFromModules<{ autenticacao: typeof autenticacao; auth: typeof auth; "auth/utils": typeof auth_utils; + chamadas: typeof chamadas; chamados: typeof chamados; chat: typeof chat; configuracaoEmail: typeof configuracaoEmail; diff --git a/packages/backend/convex/chamadas.ts b/packages/backend/convex/chamadas.ts index 07504bd..9d29c41 100644 --- a/packages/backend/convex/chamadas.ts +++ b/packages/backend/convex/chamadas.ts @@ -27,9 +27,9 @@ function gerarRoomName(conversaId: Id<'conversas'>, tipo: 'audio' | 'video'): st } /** - * Verificar se usuário é anfitrião da chamada + * Verificar se usuário é anfitrião da chamada (helper interno) */ -async function verificarAnfitriao( +async function verificarSeEhAnfitriao( ctx: QueryCtx | MutationCtx, chamadaId: Id<'chamadas'>, usuarioId: Id<'usuarios'> @@ -77,18 +77,20 @@ export const criarChamada = mutation({ // Verificar se já existe chamada ativa const chamadasAtivas = await ctx.db .query('chamadas') - .withIndex('by_conversa_ativa', (q) => q.eq('conversaId', args.conversaId)) - .filter((q) => - q.or( - q.eq(q.field('status'), 'aguardando'), - q.eq(q.field('status'), 'em_andamento') - ) - ) + .withIndex('by_conversa', (q) => q.eq('conversaId', args.conversaId).eq('status', 'aguardando')) .collect(); + + // Também verificar chamadas em andamento + const chamadasEmAndamento = await ctx.db + .query('chamadas') + .withIndex('by_conversa', (q) => q.eq('conversaId', args.conversaId).eq('status', 'em_andamento')) + .collect(); + + const todasAtivas = [...chamadasAtivas, ...chamadasEmAndamento]; - if (chamadasAtivas.length > 0) { + if (todasAtivas.length > 0) { // Retornar chamada ativa existente - return chamadasAtivas[0]._id; + return todasAtivas[0]._id; } // Obter participantes da conversa @@ -218,7 +220,7 @@ export const cancelarChamada = mutation({ if (!chamada) throw new Error('Chamada não encontrada'); // Apenas anfitrião pode cancelar - const ehAnfitriao = await verificarAnfitriao(ctx, args.chamadaId, usuarioAtual._id); + const ehAnfitriao = await verificarSeEhAnfitriao(ctx, args.chamadaId, usuarioAtual._id); if (!ehAnfitriao) { throw new Error('Apenas o anfitrião pode cancelar a chamada'); } @@ -307,7 +309,7 @@ export const removerParticipante = mutation({ if (!chamada) throw new Error('Chamada não encontrada'); // Apenas anfitrião pode remover participantes - const ehAnfitriao = await verificarAnfitriao(ctx, args.chamadaId, usuarioAtual._id); + const ehAnfitriao = await verificarSeEhAnfitriao(ctx, args.chamadaId, usuarioAtual._id); if (!ehAnfitriao) { throw new Error('Apenas o anfitrião pode remover participantes'); } @@ -357,7 +359,7 @@ export const toggleAudioVideoParticipante = mutation({ if (!chamada) throw new Error('Chamada não encontrada'); // Apenas anfitrião pode controlar outros participantes - const ehAnfitriao = await verificarAnfitriao(ctx, args.chamadaId, usuarioAtual._id); + const ehAnfitriao = await verificarSeEhAnfitriao(ctx, args.chamadaId, usuarioAtual._id); const ehOProprioParticipante = args.participanteId === usuarioAtual._id; if (!ehAnfitriao && !ehOProprioParticipante) { @@ -417,7 +419,7 @@ export const iniciarGravacao = mutation({ if (!chamada) throw new Error('Chamada não encontrada'); // Apenas anfitrião pode iniciar gravação - const ehAnfitriao = await verificarAnfitriao(ctx, args.chamadaId, usuarioAtual._id); + const ehAnfitriao = await verificarSeEhAnfitriao(ctx, args.chamadaId, usuarioAtual._id); if (!ehAnfitriao) { throw new Error('Apenas o anfitrião pode iniciar a gravação'); } @@ -453,7 +455,7 @@ export const finalizarGravacao = mutation({ if (!chamada) throw new Error('Chamada não encontrada'); // Apenas anfitrião pode finalizar gravação - const ehAnfitriao = await verificarAnfitriao(ctx, args.chamadaId, usuarioAtual._id); + const ehAnfitriao = await verificarSeEhAnfitriao(ctx, args.chamadaId, usuarioAtual._id); if (!ehAnfitriao) { throw new Error('Apenas o anfitrião pode finalizar a gravação'); } @@ -491,23 +493,24 @@ export const obterChamadaAtiva = query({ if (!participa) return null; // Buscar chamada ativa - const chamadasAtivas = await ctx.db + const chamadasAguardando = await ctx.db .query('chamadas') - .withIndex('by_conversa_ativa', (q) => q.eq('conversaId', args.conversaId)) - .filter((q) => - q.or( - q.eq(q.field('status'), 'aguardando'), - q.eq(q.field('status'), 'em_andamento') - ) - ) + .withIndex('by_conversa', (q) => q.eq('conversaId', args.conversaId).eq('status', 'aguardando')) + .collect(); + + const chamadasEmAndamento = await ctx.db + .query('chamadas') + .withIndex('by_conversa', (q) => q.eq('conversaId', args.conversaId).eq('status', 'em_andamento')) .collect(); - if (chamadasAtivas.length === 0) { + const todasAtivas = [...chamadasAguardando, ...chamadasEmAndamento]; + + if (todasAtivas.length === 0) { return null; } // Retornar a chamada mais recente - return chamadasAtivas.sort((a, b) => (b.criadoEm || 0) - (a.criadoEm || 0))[0]; + return todasAtivas.sort((a, b) => (b.criadoEm || 0) - (a.criadoEm || 0))[0]; } }); @@ -549,7 +552,7 @@ export const verificarAnfitriao = query({ const usuarioAtual = await getUsuarioAutenticado(ctx); if (!usuarioAtual) return false; - return await verificarAnfitriao(ctx, args.chamadaId, usuarioAtual._id); + return await verificarSeEhAnfitriao(ctx, args.chamadaId, usuarioAtual._id); } }); diff --git a/packages/backend/convex/schema.ts b/packages/backend/convex/schema.ts index 1356306..0e79f29 100644 --- a/packages/backend/convex/schema.ts +++ b/packages/backend/convex/schema.ts @@ -860,7 +860,6 @@ export default defineSchema({ criadoEm: v.number(), }) .index("by_conversa", ["conversaId", "status"]) - .index("by_conversa_ativa", ["conversaId", "status"]) .index("by_criado_por", ["criadoPor"]) .index("by_status", ["status"]) .index("by_room_name", ["roomName"]),