leonardo 2 minggu lalu
induk
melakukan
59d6123dce

+ 2 - 0
.env.example

@@ -0,0 +1,2 @@
+# URL base do backend (sem barra no final)
+VITE_API_URL=http://localhost:3001

+ 3 - 0
src/composables/useAuth.js

@@ -144,6 +144,9 @@ async function logout() {
   const rt = session.value?.refreshToken;
   try {
     await logoutRequest({ refreshToken: rt });
+  } catch (err) {
+    // falha no servidor (rede, refresh já inválido) não pode impedir a saída local
+    console.warn("[useAuth] logout no servidor falhou:", err?.message ?? err);
   } finally {
     resetChatSingleton();
     resetConversasSingleton();

+ 12 - 2
src/composables/useChat.js

@@ -87,43 +87,53 @@ export function useChat() {
     };
   }
 
+  // recarregar a lista invalida um "carregar mais" (ou outro recarregamento) em voo —
+  // sem isso o append antigo cai em cima da lista nova e duplica itens
+  let conversationsListEpoch = 0;
+
   async function loadConversationsList() {
+    const epoch = ++conversationsListEpoch;
     conversationsLoading.value = true;
     conversationsError.value = "";
     conversationsOffset.value = 0;
     try {
       const data = await listConversations({ limit: CONV_PAGE_SIZE, offset: 0 });
+      if (epoch !== conversationsListEpoch) return;
       const items = data.items ?? [];
       conversations.value = items.map(normalizeConversation);
       conversationsHasMore.value = items.length >= CONV_PAGE_SIZE;
       conversationsOffset.value = items.length;
     } catch (err) {
+      if (epoch !== conversationsListEpoch) return;
       conversations.value = [];
       conversationsHasMore.value = false;
       if (err?.message !== "session_expired") {
         conversationsError.value = friendlyErrorMessage(err, "Erro ao carregar conversas.");
       }
     } finally {
-      conversationsLoading.value = false;
+      if (epoch === conversationsListEpoch) conversationsLoading.value = false;
     }
   }
 
   async function loadMoreConversations() {
     if (!conversationsHasMore.value || conversationsLoading.value) return;
+    const epoch = conversationsListEpoch;
     conversationsLoading.value = true;
     try {
       const data = await listConversations({ limit: CONV_PAGE_SIZE, offset: conversationsOffset.value });
+      if (epoch !== conversationsListEpoch) return;
       const items = data.items ?? [];
       conversations.value = [...conversations.value, ...items.map(normalizeConversation)];
       conversationsHasMore.value = items.length >= CONV_PAGE_SIZE;
       conversationsOffset.value += items.length;
     } catch (err) {
+      if (epoch !== conversationsListEpoch) return;
       if (err?.message !== "session_expired") {
         toast.erro("Não foi possível carregar mais conversas.");
         console.warn("[useChat] loadMoreConversations:", err);
       }
     } finally {
-      conversationsLoading.value = false;
+      if (epoch === conversationsListEpoch) conversationsLoading.value = false;
     }
   }
 

+ 28 - 12
src/composables/useConversas.js

@@ -36,6 +36,9 @@ export function useConversas() {
   let ultimoTimestamp = null;
   let buscandoNovasMensagens = false;
   let conversasEpoch = 0;
+  // duplo clique na mesma conversa passa pelo guard de id; o epoch distingue as aberturas
+  let aberturaEpoch = 0;
+  let sugestaoEpoch = 0;
   const midiaEmResolucao = new Set();
 
   function limparMidiaCache() {
@@ -92,18 +95,19 @@ export function useConversas() {
   async function buscarSugestao() {
     if(!conversaAtivaId.value) return;
     const idAlvo = conversaAtivaId.value;
+    const epoch = ++sugestaoEpoch;
     sugestaoLoading.value = true;
     sugestaoError.value = "";
     try {
       const { sugestao: texto } = await gerarSugestaoResposta(idAlvo);
-      if ( conversaAtivaId.value !== idAlvo) return;
+      if (epoch !== sugestaoEpoch) return;
       sugestao.value = texto;
     } catch (err){
-      if ( conversaAtivaId.value !== idAlvo) return;
+      if (epoch !== sugestaoEpoch) return;
       sugestao.value = null;
       sugestaoError.value = friendlyErrorMessage(err, "Falha ao gerar sugestão");
     } finally {
-      sugestaoLoading.value = false;
+      if (epoch === sugestaoEpoch) sugestaoLoading.value = false;
     }
   }
 
@@ -133,32 +137,40 @@ export function useConversas() {
     }
   }
 
+  function invalidarSugestao() {
+    sugestaoEpoch++;
+    sugestao.value = null;
+    sugestaoError.value = "";
+    sugestaoLoading.value = false;
+  }
+
   async function abrirConversa(id) {
+    const abertura = ++aberturaEpoch;
     pararPolling();
     conversaAtivaId.value = id;
     mensagens.value = [];
     limparMidiaCache();
     ultimoTimestamp = null;
-    sugestao.value = null;
-    sugestaoError.value = "";
+    invalidarSugestao();
     mensagensLoading.value = true;
     mensagensError.value = "";
 
     try {
       const { results } = await listarMensagensConversa(id);
-      if (conversaAtivaId.value !== id) return;
+      if (abertura !== aberturaEpoch) return;
       mensagens.value = results;
       ultimoTimestamp = results.length > 0 ? results[results.length - 1].Timestamp : null;
       if (results.length && results[results.length - 1].Direcao === "entrada") buscarSugestao();
     } catch (err) {
-      if (conversaAtivaId.value !== id) return;
+      if (abertura !== aberturaEpoch) return;
       mensagensError.value = friendlyErrorMessage(err, "Falha ao carregar mensagens");
     } finally {
-      if (conversaAtivaId.value === id) mensagensLoading.value = false;
+      if (abertura === aberturaEpoch) mensagensLoading.value = false;
     }
 
-    if (conversaAtivaId.value !== id) return;
+    if (abertura !== aberturaEpoch) return;
 
+    pararPolling();
     pollTimer = setInterval(() => {
       if (document.visibilityState === "visible") buscarNovasMensagens();
     }, POLL_INTERVAL_MS);
@@ -166,13 +178,14 @@ export function useConversas() {
   }
 
   function fecharConversa() {
+    aberturaEpoch++;
     pararPolling();
     conversaAtivaId.value = null;
     mensagens.value = [];
+    mensagensLoading.value = false;
     limparMidiaCache();
     ultimoTimestamp = null;
-    sugestao.value = null;
-    sugestaoError.value = "";
+    invalidarSugestao();
   }
 
   async function enviarResposta(texto) {
@@ -194,7 +207,10 @@ export function useConversas() {
 
   async function excluirConversa(id) {
     await excluirConversaApi(id);
-    conversasEpoch++; 
+    // invalida um carregarConversas em voo (que traria a conversa excluída de volta) — e por isso
+    // o loading dele precisa ser desligado aqui, já que o finally dele não vai mais fazê-lo
+    conversasEpoch++;
+    conversasLoading.value = false;
     conversas.value = conversas.value.filter((c) => c.Id !== id);
     if (conversaAtivaId.value === id) fecharConversa();
   }

+ 1 - 1
src/layout/HeaderSistema.vue

@@ -45,9 +45,9 @@ async function encerrarSessao() {
   logoutLoading.value = true;
   try {
     await logout();
-    await router.replace({ name: "login" });
   } finally {
     logoutLoading.value = false;
+    await router.replace({ name: "login" });
   }
 }
 </script>