leonardo 1 mēnesi atpakaļ
vecāks
revīzija
560fb7c6e4
2 mainītis faili ar 79 papildinājumiem un 17 dzēšanām
  1. 4 0
      src/api/atendimentos.js
  2. 75 17
      src/views/atendimentos/AtendimentoView.vue

+ 4 - 0
src/api/atendimentos.js

@@ -19,6 +19,10 @@ export function obterAtendimento(id) {
   return apiFetch(`/api/atendimentos/${id}`);
 }
 
+export function sincronizarAtendimento(id) {
+  return apiFetch(`/api/atendimentos/${id}/sync`, { method: "POST", body: {} });
+}
+
 export function avaliarAtendimentosPendentes(params = {}) {
   return apiFetch("/api/atendimentos/avaliar", { method: "POST", body: params });
 }

+ 75 - 17
src/views/atendimentos/AtendimentoView.vue

@@ -5,7 +5,8 @@ import LayoutSistema from "../../layout/LayoutSistema.vue";
 import BaseBadge from "../../components/base/BaseBadge.vue";
 import BaseButton from "../../components/base/BaseButton.vue";
 import ModalMidia from "../../components/ModalMidia.vue";
-import { obterAtendimento, salvarGoldenLabel } from "../../api/atendimentos.js";
+import { obterAtendimento, salvarGoldenLabel, sincronizarAtendimento, avaliarAtendimento } from "../../api/atendimentos.js";
+import { useAuth } from "../../composables/useAuth.js";
 import {
   cancelamentoLabel,
   cancelamentoVariant,
@@ -23,6 +24,8 @@ import { useToast } from "../../composables/useToast.js";
 
 const HORARIO_VISITA_INFORMADO_LABEL = { sim: "Sim", nao: "Não", nao_se_aplica: "Não se aplica" };
 const toast = useToast();
+const { user } = useAuth();
+const isAdmin = computed(() => String(user.value?.Nivel) === "3");
 
 const route = useRoute();
 const router = useRouter();
@@ -31,6 +34,8 @@ const carregando = ref(true);
 const erro = ref("");
 const atendimento = ref(null);
 const mediaViewer = ref(null);
+const sincronizandoIfbot = ref(false);
+const reavaliando = ref(false);
 
 const avaliacao = computed(() => atendimento.value?.avaliacao ?? null);
 const goldenLabel = computed(() => atendimento.value?.goldenLabel ?? null);
@@ -177,6 +182,39 @@ async function carregar(id) {
   }
 }
 
+// re-sincroniza este atendimento direto pelo Id, sem passar pelo filtro de "protocolos
+// abertos" — funciona mesmo se o atendimento já estiver fechado no ifbot, cobrindo o
+// caso de alguém ter transcrito um áudio manualmente depois do encerramento
+async function atualizarDoIfbot() {
+  if (sincronizandoIfbot.value || !atendimento.value) return;
+  sincronizandoIfbot.value = true;
+  try {
+    await sincronizarAtendimento(atendimento.value.Id);
+    await carregar(atendimento.value.Id);
+    toast.sucesso("Atendimento atualizado com os dados mais recentes do ifbot.");
+  } catch (err) {
+    toast.erro(err?.message ?? "Falha ao atualizar o atendimento.");
+  } finally {
+    sincronizandoIfbot.value = false;
+  }
+}
+
+// roda a avaliação da IA de novo pra este atendimento — útil depois de "Atualizar do
+// ifbot" trazer uma transcrição nova, já que a avaliação anterior não se atualiza sozinha
+async function reavaliar() {
+  if (reavaliando.value || !atendimento.value) return;
+  reavaliando.value = true;
+  try {
+    await avaliarAtendimento(atendimento.value.Id);
+    await carregar(atendimento.value.Id);
+    toast.sucesso("Atendimento reavaliado.");
+  } catch (err) {
+    toast.erro(err?.message ?? "Falha ao reavaliar o atendimento.");
+  } finally {
+    reavaliando.value = false;
+  }
+}
+
 // watch (em vez de onMounted) porque o Vue Router reaproveita esta instância quando se
 // navega entre duas URLs /atendimentos/:id sem desmontar o componente
 watch(() => route.params.id, carregar, { immediate: true });
@@ -214,22 +252,42 @@ watch(() => route.params.id, carregar, { immediate: true });
                 </p>
               </div>
             </div>
-            <div v-if="avaliacao" class="flex flex-wrap items-center gap-2">
-              <BaseBadge :variant="scoreVariant(avaliacao.ScoreAtendente)">
-                {{ avaliacao.ScoreAtendente !== null && avaliacao.ScoreAtendente !== undefined ? `Nota ${avaliacao.ScoreAtendente}` : "Sem nota" }}
-              </BaseBadge>
-              <BaseBadge :variant="resolvidoVariant[avaliacao.Resolvido] ?? 'default'">
-                {{ resolvidoLabel[avaliacao.Resolvido] ?? avaliacao.Resolvido }}
-              </BaseBadge>
-              <BaseBadge :variant="sentimentoVariant[avaliacao.Sentimento] ?? 'default'">
-                {{ sentimentoLabel[avaliacao.Sentimento] ?? avaliacao.Sentimento }}
-              </BaseBadge>
-              <BaseBadge v-if="cancelamentoLabel[avaliacao.StatusCancelamento]" :variant="cancelamentoVariant[avaliacao.StatusCancelamento]">
-                {{ cancelamentoLabel[avaliacao.StatusCancelamento] }}
-              </BaseBadge>
-              <BaseBadge v-if="contextoIxcFalhou(avaliacao.ContextoIxcStatus)" variant="warning" :title="CONTEXTO_IXC_ERRO_LABEL">
-                Contexto ERP indisponível
-              </BaseBadge>
+            <div class="flex flex-wrap items-center gap-2">
+              <BaseButton
+                v-if="isAdmin && atendimento"
+                variant="secondary"
+                :loading="sincronizandoIfbot"
+                :disabled="sincronizandoIfbot"
+                @click="atualizarDoIfbot"
+              >
+                {{ sincronizandoIfbot ? "Atualizando..." : "Atualizar do ifbot" }}
+              </BaseButton>
+              <BaseButton
+                v-if="isAdmin && atendimento"
+                variant="secondary"
+                :loading="reavaliando"
+                :disabled="reavaliando"
+                @click="reavaliar"
+              >
+                {{ reavaliando ? "Reavaliando..." : "Reavaliar" }}
+              </BaseButton>
+              <template v-if="avaliacao">
+                <BaseBadge :variant="scoreVariant(avaliacao.ScoreAtendente)">
+                  {{ avaliacao.ScoreAtendente !== null && avaliacao.ScoreAtendente !== undefined ? `Nota ${avaliacao.ScoreAtendente}` : "Sem nota" }}
+                </BaseBadge>
+                <BaseBadge :variant="resolvidoVariant[avaliacao.Resolvido] ?? 'default'">
+                  {{ resolvidoLabel[avaliacao.Resolvido] ?? avaliacao.Resolvido }}
+                </BaseBadge>
+                <BaseBadge :variant="sentimentoVariant[avaliacao.Sentimento] ?? 'default'">
+                  {{ sentimentoLabel[avaliacao.Sentimento] ?? avaliacao.Sentimento }}
+                </BaseBadge>
+                <BaseBadge v-if="cancelamentoLabel[avaliacao.StatusCancelamento]" :variant="cancelamentoVariant[avaliacao.StatusCancelamento]">
+                  {{ cancelamentoLabel[avaliacao.StatusCancelamento] }}
+                </BaseBadge>
+                <BaseBadge v-if="contextoIxcFalhou(avaliacao.ContextoIxcStatus)" variant="warning" :title="CONTEXTO_IXC_ERRO_LABEL">
+                  Contexto ERP indisponível
+                </BaseBadge>
+              </template>
             </div>
           </div>