|
@@ -3,6 +3,7 @@ import { computed, onMounted, ref } from "vue";
|
|
|
import { useRoute, useRouter } from "vue-router";
|
|
import { useRoute, useRouter } from "vue-router";
|
|
|
import LayoutSistema from "../../layout/LayoutSistema.vue";
|
|
import LayoutSistema from "../../layout/LayoutSistema.vue";
|
|
|
import BaseBadge from "../../components/base/BaseBadge.vue";
|
|
import BaseBadge from "../../components/base/BaseBadge.vue";
|
|
|
|
|
+import ModalMidia from "../../components/ModalMidia.vue";
|
|
|
import { obterAtendimento } from "../../api/atendimentos.js";
|
|
import { obterAtendimento } from "../../api/atendimentos.js";
|
|
|
import { resolvidoLabel, resolvidoVariant, scoreVariant, sentimentoLabel, sentimentoVariant } from "../../utils/avaliacoes.js";
|
|
import { resolvidoLabel, resolvidoVariant, scoreVariant, sentimentoLabel, sentimentoVariant } from "../../utils/avaliacoes.js";
|
|
|
|
|
|
|
@@ -12,6 +13,7 @@ const router = useRouter();
|
|
|
const carregando = ref(true);
|
|
const carregando = ref(true);
|
|
|
const erro = ref("");
|
|
const erro = ref("");
|
|
|
const atendimento = ref(null);
|
|
const atendimento = ref(null);
|
|
|
|
|
+const mediaViewer = ref(null);
|
|
|
|
|
|
|
|
const avaliacao = computed(() => atendimento.value?.avaliacao ?? null);
|
|
const avaliacao = computed(() => atendimento.value?.avaliacao ?? null);
|
|
|
const mensagens = computed(() => atendimento.value?.mensagens ?? []);
|
|
const mensagens = computed(() => atendimento.value?.mensagens ?? []);
|
|
@@ -36,6 +38,7 @@ function autorLabel(m) {
|
|
|
|
|
|
|
|
const tiposAudio = new Set(["mpga", "oga", "mp3", "audio", "ptt"]);
|
|
const tiposAudio = new Set(["mpga", "oga", "mp3", "audio", "ptt"]);
|
|
|
const tiposImagem = new Set(["jpg", "jpeg", "png", "webp", "gif", "image", "sticker"]);
|
|
const tiposImagem = new Set(["jpg", "jpeg", "png", "webp", "gif", "image", "sticker"]);
|
|
|
|
|
+const tiposVideo = new Set(["mp4", "webm", "mov", "3gp", "video", "m4v"]);
|
|
|
|
|
|
|
|
function ehAudio(m) {
|
|
function ehAudio(m) {
|
|
|
return Boolean(m.Transcricao) || tiposAudio.has(m.Tipodemidia);
|
|
return Boolean(m.Transcricao) || tiposAudio.has(m.Tipodemidia);
|
|
@@ -49,9 +52,15 @@ function ehImagem(m) {
|
|
|
return /\.(jpe?g|png|webp|gif)$/i.test(m.MidiaUrl ?? "");
|
|
return /\.(jpe?g|png|webp|gif)$/i.test(m.MidiaUrl ?? "");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// link genérico "Abrir mídia" só para tipos sem renderização própria (pdf, vídeo, ...)
|
|
|
|
|
|
|
+function ehVideo(m) {
|
|
|
|
|
+ if (ehAudio(m)) return false;
|
|
|
|
|
+ if (tiposVideo.has(m.Tipodemidia)) return true;
|
|
|
|
|
+ return /\.(mp4|webm|mov|3gp|m4v)$/i.test(m.MidiaUrl ?? "");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// link genérico "Abrir mídia" só para tipos sem renderização própria (pdf, ...)
|
|
|
function midiaLink(m) {
|
|
function midiaLink(m) {
|
|
|
- return m.MidiaUrl && !ehImagem(m) && !ehAudio(m) ? m.MidiaUrl : null;
|
|
|
|
|
|
|
+ return m.MidiaUrl && !ehImagem(m) && !ehVideo(m) && !ehAudio(m) ? m.MidiaUrl : null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// path de armazenamento do arquivo (ex.: /data/aud/xxx.mp3); em áudios antigos sem
|
|
// path de armazenamento do arquivo (ex.: /data/aud/xxx.mp3); em áudios antigos sem
|
|
@@ -209,14 +218,30 @@ onMounted(async () => {
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
<template v-else-if="ehImagem(m) && m.MidiaUrl">
|
|
<template v-else-if="ehImagem(m) && m.MidiaUrl">
|
|
|
- <a :href="m.MidiaUrl" target="_blank" rel="noopener noreferrer" title="Abrir imagem em tamanho original">
|
|
|
|
|
- <img
|
|
|
|
|
- :src="m.MidiaUrl"
|
|
|
|
|
- alt="Imagem enviada na conversa"
|
|
|
|
|
- loading="lazy"
|
|
|
|
|
- class="mt-1.5 max-h-80 max-w-full rounded-lg object-contain"
|
|
|
|
|
- />
|
|
|
|
|
- </a>
|
|
|
|
|
|
|
+ <img
|
|
|
|
|
+ :src="m.MidiaUrl"
|
|
|
|
|
+ alt="Imagem enviada na conversa"
|
|
|
|
|
+ loading="lazy"
|
|
|
|
|
+ title="Ver imagem"
|
|
|
|
|
+ class="mt-1.5 max-h-80 max-w-full cursor-pointer rounded-lg object-contain"
|
|
|
|
|
+ @click="mediaViewer = { url: m.MidiaUrl, tipo: 'imagem' }"
|
|
|
|
|
+ />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <template v-else-if="ehVideo(m) && m.MidiaUrl">
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="relative mt-1.5 inline-block cursor-pointer overflow-hidden rounded-lg"
|
|
|
|
|
+ title="Ver vídeo"
|
|
|
|
|
+ @click="mediaViewer = { url: m.MidiaUrl, tipo: 'video' }"
|
|
|
|
|
+ >
|
|
|
|
|
+ <video :src="m.MidiaUrl" preload="metadata" muted class="max-h-80 max-w-full rounded-lg object-contain"></video>
|
|
|
|
|
+ <div class="absolute inset-0 flex items-center justify-center bg-black/20">
|
|
|
|
|
+ <div class="flex h-11 w-11 items-center justify-center rounded-full bg-white/85 text-gray-900">
|
|
|
|
|
+ <svg class="h-5 w-5 translate-x-0.5" viewBox="0 0 24 24" fill="currentColor">
|
|
|
|
|
+ <path d="M8 5v14l11-7z" />
|
|
|
|
|
+ </svg>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</template>
|
|
</template>
|
|
|
<p v-else class="mt-0.5 whitespace-pre-wrap break-words text-sm text-gray-800 dark:text-gray-200">{{ m.Texto }}</p>
|
|
<p v-else class="mt-0.5 whitespace-pre-wrap break-words text-sm text-gray-800 dark:text-gray-200">{{ m.Texto }}</p>
|
|
|
<a
|
|
<a
|
|
@@ -236,5 +261,12 @@ onMounted(async () => {
|
|
|
</section>
|
|
</section>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+ <ModalMidia
|
|
|
|
|
+ v-if="mediaViewer"
|
|
|
|
|
+ :url="mediaViewer.url"
|
|
|
|
|
+ :tipo="mediaViewer.tipo"
|
|
|
|
|
+ @close="mediaViewer = null"
|
|
|
|
|
+ />
|
|
|
</LayoutSistema>
|
|
</LayoutSistema>
|
|
|
</template>
|
|
</template>
|