|
@@ -23,6 +23,9 @@ import {
|
|
|
import { ehAudio as ehAudioMidia, ehImagem as ehImagemMidia, ehVideo as ehVideoMidia } from "../../utils/midia.js";
|
|
import { ehAudio as ehAudioMidia, ehImagem as ehImagemMidia, ehVideo as ehVideoMidia } from "../../utils/midia.js";
|
|
|
import { ehNovoDia, formatarData, formatarHora } from "../../utils/data.js";
|
|
import { ehNovoDia, formatarData, formatarHora } from "../../utils/data.js";
|
|
|
import { useToast } from "../../composables/useToast.js";
|
|
import { useToast } from "../../composables/useToast.js";
|
|
|
|
|
+import { friendlyErrorMessage } from "../../api/client.js";
|
|
|
|
|
+import PageHeaderCard from "../../components/base/PageHeaderCard.vue";
|
|
|
|
|
+import Spinner from "../../components/base/Spinner.vue";
|
|
|
|
|
|
|
|
const HORARIO_VISITA_INFORMADO_LABEL = { sim: "Sim", nao: "Não", nao_se_aplica: "Não se aplica" };
|
|
const HORARIO_VISITA_INFORMADO_LABEL = { sim: "Sim", nao: "Não", nao_se_aplica: "Não se aplica" };
|
|
|
const toast = useToast();
|
|
const toast = useToast();
|
|
@@ -98,7 +101,7 @@ async function salvarRotulo() {
|
|
|
atendimento.value = { ...atendimento.value, goldenLabel: atualizado };
|
|
atendimento.value = { ...atendimento.value, goldenLabel: atualizado };
|
|
|
toast.sucesso("Rótulo salvo.");
|
|
toast.sucesso("Rótulo salvo.");
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
- toast.erro(err?.message ?? "Falha ao salvar o rótulo.");
|
|
|
|
|
|
|
+ toast.erro(friendlyErrorMessage(err, "Falha ao salvar o rótulo."));
|
|
|
} finally {
|
|
} finally {
|
|
|
salvando.value = false;
|
|
salvando.value = false;
|
|
|
}
|
|
}
|
|
@@ -116,7 +119,7 @@ async function aceitarComoIa() {
|
|
|
form.value = criarFormulario(atualizado);
|
|
form.value = criarFormulario(atualizado);
|
|
|
toast.sucesso("Avaliação da IA aceita como rótulo.");
|
|
toast.sucesso("Avaliação da IA aceita como rótulo.");
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
- toast.erro(err?.message ?? "Falha ao aceitar a avaliação da IA.");
|
|
|
|
|
|
|
+ toast.erro(friendlyErrorMessage(err, "Falha ao aceitar a avaliação da IA."));
|
|
|
} finally {
|
|
} finally {
|
|
|
aceitando.value = false;
|
|
aceitando.value = false;
|
|
|
}
|
|
}
|
|
@@ -180,7 +183,7 @@ async function carregar(id) {
|
|
|
form.value = criarFormulario(resultado.goldenLabel ?? null);
|
|
form.value = criarFormulario(resultado.goldenLabel ?? null);
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
if (String(route.params.id) !== String(id)) return;
|
|
if (String(route.params.id) !== String(id)) return;
|
|
|
- erro.value = err?.message ?? "Falha ao carregar o atendimento";
|
|
|
|
|
|
|
+ erro.value = friendlyErrorMessage(err, "Falha ao carregar o atendimento");
|
|
|
} finally {
|
|
} finally {
|
|
|
if (String(route.params.id) === String(id)) carregando.value = false;
|
|
if (String(route.params.id) === String(id)) carregando.value = false;
|
|
|
}
|
|
}
|
|
@@ -197,7 +200,7 @@ async function atualizarDoIfbot() {
|
|
|
await carregar(atendimento.value.Id);
|
|
await carregar(atendimento.value.Id);
|
|
|
toast.sucesso("Atendimento atualizado com os dados mais recentes do ifbot.");
|
|
toast.sucesso("Atendimento atualizado com os dados mais recentes do ifbot.");
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
- toast.erro(err?.message ?? "Falha ao atualizar o atendimento.");
|
|
|
|
|
|
|
+ toast.erro(friendlyErrorMessage(err, "Falha ao atualizar o atendimento."));
|
|
|
} finally {
|
|
} finally {
|
|
|
sincronizandoIfbot.value = false;
|
|
sincronizandoIfbot.value = false;
|
|
|
}
|
|
}
|
|
@@ -213,7 +216,7 @@ async function reavaliar() {
|
|
|
await carregar(atendimento.value.Id);
|
|
await carregar(atendimento.value.Id);
|
|
|
toast.sucesso("Atendimento reavaliado.");
|
|
toast.sucesso("Atendimento reavaliado.");
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
- toast.erro(err?.message ?? "Falha ao reavaliar o atendimento.");
|
|
|
|
|
|
|
+ toast.erro(friendlyErrorMessage(err, "Falha ao reavaliar o atendimento."));
|
|
|
} finally {
|
|
} finally {
|
|
|
reavaliando.value = false;
|
|
reavaliando.value = false;
|
|
|
}
|
|
}
|
|
@@ -228,32 +231,32 @@ watch(() => route.params.id, carregar, { immediate: true });
|
|
|
<LayoutSistema>
|
|
<LayoutSistema>
|
|
|
<div class="min-h-[calc(100vh-var(--layout-header-height))] px-4 pt-8 pb-12">
|
|
<div class="min-h-[calc(100vh-var(--layout-header-height))] px-4 pt-8 pb-12">
|
|
|
<div class="mx-auto w-full max-w-[960px] space-y-6">
|
|
<div class="mx-auto w-full max-w-[960px] space-y-6">
|
|
|
- <section
|
|
|
|
|
- class="rounded-2xl border border-gray-200 bg-gray-100/50 p-6 shadow-sm backdrop-blur-md dark:border-gray-700 dark:bg-secondary/50"
|
|
|
|
|
- >
|
|
|
|
|
- <div class="flex flex-wrap items-start justify-between gap-4">
|
|
|
|
|
- <div class="flex min-w-0 items-start gap-4">
|
|
|
|
|
- <button
|
|
|
|
|
- type="button"
|
|
|
|
|
- class="mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-xl border border-gray-300 text-gray-600 hover:bg-black/[0.03] dark:border-gray-600 dark:text-gray-300 dark:hover:bg-white/[0.04]"
|
|
|
|
|
- title="Voltar"
|
|
|
|
|
- @click="voltar"
|
|
|
|
|
- >
|
|
|
|
|
- <BaseIcon :icon="ArrowLeft" class="h-4 w-4" />
|
|
|
|
|
- </button>
|
|
|
|
|
- <div class="min-w-0">
|
|
|
|
|
- <h2 class="text-base font-semibold text-foreground">
|
|
|
|
|
- {{ atendimento?.Codigo ?? `Atendimento #${route.params.id}` }}
|
|
|
|
|
- <span v-if="atendimento?.cliente?.Nome" class="font-normal text-muted-foreground">
|
|
|
|
|
- — {{ atendimento.cliente.Nome }}
|
|
|
|
|
- </span>
|
|
|
|
|
- </h2>
|
|
|
|
|
- <p v-if="atendimento" class="mt-0.5 text-sm text-muted-foreground">
|
|
|
|
|
- <span v-if="atendimento.Setor">{{ atendimento.Setor }} · </span>{{ formatarData(atendimento.Abertura) }}
|
|
|
|
|
- · {{ mensagens.length }} mensagens
|
|
|
|
|
- </p>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <PageHeaderCard align-start title="">
|
|
|
|
|
+ <template #leading>
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ class="mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-xl border border-gray-300 text-gray-600 hover:bg-black/[0.03] dark:border-gray-600 dark:text-gray-300 dark:hover:bg-white/[0.04]"
|
|
|
|
|
+ title="Voltar"
|
|
|
|
|
+ @click="voltar"
|
|
|
|
|
+ >
|
|
|
|
|
+ <BaseIcon :icon="ArrowLeft" class="h-4 w-4" />
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <template #title>
|
|
|
|
|
+ <h2 class="text-base font-semibold text-foreground">
|
|
|
|
|
+ {{ atendimento?.Codigo ?? `Atendimento #${route.params.id}` }}
|
|
|
|
|
+ <span v-if="atendimento?.cliente?.Nome" class="font-normal text-muted-foreground">
|
|
|
|
|
+ — {{ atendimento.cliente.Nome }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </h2>
|
|
|
|
|
+ <p v-if="atendimento" class="mt-0.5 text-sm text-muted-foreground">
|
|
|
|
|
+ <span v-if="atendimento.Setor">{{ atendimento.Setor }} · </span>{{ formatarData(atendimento.Abertura) }}
|
|
|
|
|
+ · {{ mensagens.length }} mensagens
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <template #actions>
|
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
|
<BaseButton
|
|
<BaseButton
|
|
|
v-if="isAdmin && atendimento"
|
|
v-if="isAdmin && atendimento"
|
|
@@ -288,7 +291,7 @@ watch(() => route.params.id, carregar, { immediate: true });
|
|
|
</BaseBadge>
|
|
</BaseBadge>
|
|
|
</template>
|
|
</template>
|
|
|
</div>
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
|
+ </template>
|
|
|
|
|
|
|
|
<p v-if="avaliacao?.Resumo" class="mt-4 text-sm text-muted-foreground">
|
|
<p v-if="avaliacao?.Resumo" class="mt-4 text-sm text-muted-foreground">
|
|
|
<span class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">Resumo da IA: </span>
|
|
<span class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">Resumo da IA: </span>
|
|
@@ -298,14 +301,14 @@ watch(() => route.params.id, carregar, { immediate: true });
|
|
|
<span class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">Motivo do cancelamento: </span>
|
|
<span class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">Motivo do cancelamento: </span>
|
|
|
{{ avaliacao.MotivoCancelamento }}
|
|
{{ avaliacao.MotivoCancelamento }}
|
|
|
</p>
|
|
</p>
|
|
|
- </section>
|
|
|
|
|
|
|
+ </PageHeaderCard>
|
|
|
|
|
|
|
|
<section class="rounded-2xl border border-gray-200 bg-background/70 shadow-sm dark:border-gray-700 dark:bg-background/20">
|
|
<section class="rounded-2xl border border-gray-200 bg-background/70 shadow-sm dark:border-gray-700 dark:bg-background/20">
|
|
|
<div class="border-b border-gray-200 px-4 py-3 text-sm font-semibold text-gray-900 dark:border-gray-700 dark:text-gray-100">
|
|
<div class="border-b border-gray-200 px-4 py-3 text-sm font-semibold text-gray-900 dark:border-gray-700 dark:text-gray-100">
|
|
|
Conversa completa
|
|
Conversa completa
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div v-if="carregando" class="p-6 text-sm text-muted-foreground">Carregando conversa...</div>
|
|
|
|
|
|
|
+ <Spinner v-if="carregando" class="p-6" label="Carregando conversa..." />
|
|
|
<div v-else-if="erro" class="m-4 rounded-lg bg-red-50 px-4 py-3 text-xs text-red-800 dark:bg-red-900/30 dark:text-red-300">
|
|
<div v-else-if="erro" class="m-4 rounded-lg bg-red-50 px-4 py-3 text-xs text-red-800 dark:bg-red-900/30 dark:text-red-300">
|
|
|
{{ erro }}
|
|
{{ erro }}
|
|
|
</div>
|
|
</div>
|