|
|
@@ -1,100 +1,95 @@
|
|
|
<template>
|
|
|
<div class="q-pa-md">
|
|
|
<div class="row q-col-gutter-md">
|
|
|
+ <!-- Coluna esquerda: lista -->
|
|
|
<div class="col-12 col-md-5">
|
|
|
- <q-table
|
|
|
- :rows="medias"
|
|
|
- :columns="columns"
|
|
|
- row-key="title"
|
|
|
- flat
|
|
|
- hide-bottom
|
|
|
- class="bg-transparent"
|
|
|
- :row-class="
|
|
|
- (_, index) =>
|
|
|
- index === selectedIndex
|
|
|
- ? 'media-item-active cursor-pointer'
|
|
|
- : 'cursor-pointer'
|
|
|
- "
|
|
|
- @row-click="(_, __, index) => (selectedIndex = index)"
|
|
|
- >
|
|
|
- <template #body-cell-actions>
|
|
|
- <q-td align="center">
|
|
|
- <div class="flex items-center justify-center q-gutter-x-xs">
|
|
|
- <q-icon
|
|
|
- name="mdi-file-edit-outline"
|
|
|
- size="sm"
|
|
|
- color="grey-8"
|
|
|
- class="cursor-pointer"
|
|
|
- style="
|
|
|
- border: 1px solid #c9c9c9;
|
|
|
- border-radius: 8px;
|
|
|
- padding: 4px;
|
|
|
- "
|
|
|
- @click.stop="console.log('edit')"
|
|
|
- />
|
|
|
- <q-icon
|
|
|
- name="mdi-trash-can-outline"
|
|
|
- size="sm"
|
|
|
- color="grey-8"
|
|
|
- class="cursor-pointer"
|
|
|
- style="
|
|
|
- border: 1px solid #c9c9c9;
|
|
|
- border-radius: 8px;
|
|
|
- padding: 4px;
|
|
|
- "
|
|
|
- @click.stop="console.log('trash')"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </q-td>
|
|
|
- </template>
|
|
|
-
|
|
|
- <template #body-cell-franchisee>
|
|
|
- <q-td align="center">
|
|
|
- <div class="flex items-center justify-center q-gutter-x-xs">
|
|
|
- <q-icon
|
|
|
- name="mdi-check"
|
|
|
- size="sm"
|
|
|
- color="suface"
|
|
|
- class="cursor-pointer bg-approved"
|
|
|
- style="border: 1px solid #c9c9c9; border-radius: 8px"
|
|
|
- @click.stop="console.log('edit')"
|
|
|
- />
|
|
|
- <q-icon
|
|
|
- name="mdi-close-circle-outline"
|
|
|
- size="sm"
|
|
|
- color="declined"
|
|
|
- class="cursor-pointer"
|
|
|
- style="border: 1px solid red; border-radius: 8px"
|
|
|
- @click.stop="console.log('trash')"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </q-td>
|
|
|
- </template>
|
|
|
- </q-table>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="col-12 col-md-7">
|
|
|
- <div class="row justify-end q-mb-sm">
|
|
|
+ <div class="row justify-between items-center q-mb-md">
|
|
|
+ <span class="text-subtitle1 text-weight-medium">Mídias</span>
|
|
|
<q-btn
|
|
|
icon="add"
|
|
|
color="primary-2"
|
|
|
style="height: 40px; width: 40px; border-radius: 8px"
|
|
|
+ :disable="!unitId"
|
|
|
+ @click="openAddDialog"
|
|
|
/>
|
|
|
</div>
|
|
|
- <div class="preview-box q-pa-md">
|
|
|
- <span v-if="selectedIndex === null" class="text-grey-5"
|
|
|
- >Pré - Visualização</span
|
|
|
+
|
|
|
+ <div v-if="loading" class="row justify-center q-pa-xl">
|
|
|
+ <q-spinner color="primary" size="40px" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template v-else>
|
|
|
+ <div v-if="medias.length === 0" class="text-center text-grey-6 q-pa-xl">
|
|
|
+ <q-icon name="mdi-image-multiple-outline" size="48px" color="grey-4" />
|
|
|
+ <div class="q-mt-sm">Nenhuma mídia adicionada.</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <q-list v-else separator>
|
|
|
+ <q-item
|
|
|
+ v-for="(item, index) in medias"
|
|
|
+ :key="item.id"
|
|
|
+ clickable
|
|
|
+ :active="selectedIndex === index"
|
|
|
+ active-class="media-item-active"
|
|
|
+ @click="selectedIndex = index"
|
|
|
+ >
|
|
|
+ <q-item-section avatar>
|
|
|
+ <q-icon :name="getFileIcon(item.mime_type)" :color="getFileColor(item.mime_type)" size="md" />
|
|
|
+ </q-item-section>
|
|
|
+
|
|
|
+ <q-item-section>
|
|
|
+ <q-item-label class="ellipsis" style="max-width: 180px">
|
|
|
+ {{ item.title }}
|
|
|
+ </q-item-label>
|
|
|
+ <q-item-label caption>
|
|
|
+ {{ formatDate(item.created_at) }}
|
|
|
+ </q-item-label>
|
|
|
+ </q-item-section>
|
|
|
+
|
|
|
+ <q-item-section side>
|
|
|
+ <q-btn
|
|
|
+ flat round dense icon="delete"
|
|
|
+ color="negative" size="sm"
|
|
|
+ @click.stop="onRemove(item, index)"
|
|
|
+ />
|
|
|
+ </q-item-section>
|
|
|
+ </q-item>
|
|
|
+ </q-list>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Coluna direita: pré-visualização -->
|
|
|
+ <div class="col-12 col-md-7">
|
|
|
+ <div class="preview-box">
|
|
|
+ <div
|
|
|
+ v-if="selectedIndex === null || !medias[selectedIndex]"
|
|
|
+ class="flex flex-center full-height text-grey-5"
|
|
|
+ style="min-height: 500px"
|
|
|
>
|
|
|
- <div v-else>
|
|
|
- <p class="text-weight-medium q-mb-sm">
|
|
|
- {{ medias[selectedIndex].title }}
|
|
|
- </p>
|
|
|
+ <div class="column items-center q-gutter-sm">
|
|
|
+ <q-icon name="mdi-image-multiple-outline" size="64px" color="grey-3" />
|
|
|
+ <span>Selecione uma mídia para visualizar</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template v-else>
|
|
|
<img
|
|
|
- v-if="medias[selectedIndex].url"
|
|
|
- :src="medias[selectedIndex].url"
|
|
|
- style="max-width: 100%; border-radius: 4px"
|
|
|
+ v-if="isImage(medias[selectedIndex].mime_type)"
|
|
|
+ :src="medias[selectedIndex].file_url"
|
|
|
+ style="width: 100%; border-radius: 8px; display: block"
|
|
|
/>
|
|
|
- </div>
|
|
|
+ <video
|
|
|
+ v-else-if="isVideo(medias[selectedIndex].mime_type)"
|
|
|
+ :src="medias[selectedIndex].file_url"
|
|
|
+ controls
|
|
|
+ style="width: 100%; border-radius: 8px; display: block"
|
|
|
+ />
|
|
|
+ <iframe
|
|
|
+ v-else
|
|
|
+ :src="medias[selectedIndex].file_url"
|
|
|
+ style="width: 100%; min-height: 500px; border: none; border-radius: 8px"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -102,56 +97,103 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref } from "vue";
|
|
|
+import { ref, onMounted } from "vue";
|
|
|
+import { useQuasar } from "quasar";
|
|
|
+import { getMediasByUnit, deleteMedia } from "src/api/unit_media";
|
|
|
+import AddMediaDialog from "src/pages/unit/components/AddMediaDialog.vue";
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ unitId: { type: Number, default: null },
|
|
|
+});
|
|
|
|
|
|
+const $q = useQuasar();
|
|
|
+const medias = ref([]);
|
|
|
const selectedIndex = ref(null);
|
|
|
+const loading = ref(false);
|
|
|
+
|
|
|
+async function fetchMedias() {
|
|
|
+ if (!props.unitId) return;
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ medias.value = await getMediasByUnit(props.unitId);
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e);
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function openAddDialog() {
|
|
|
+ $q.dialog({
|
|
|
+ component: AddMediaDialog,
|
|
|
+ componentProps: { unitId: props.unitId },
|
|
|
+ }).onOk((result) => {
|
|
|
+ medias.value.unshift(result);
|
|
|
+ selectedIndex.value = 0;
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function onRemove(item, index) {
|
|
|
+ $q.dialog({
|
|
|
+ title: "Remover mídia",
|
|
|
+ message: `Deseja remover a mídia "${item.title}"?`,
|
|
|
+ ok: { color: "negative", label: "Remover" },
|
|
|
+ cancel: { color: "primary", outline: true, label: "Cancelar" },
|
|
|
+ }).onOk(async () => {
|
|
|
+ try {
|
|
|
+ await deleteMedia(item.id);
|
|
|
+ medias.value.splice(index, 1);
|
|
|
+ if (selectedIndex.value === index) selectedIndex.value = null;
|
|
|
+ else if (selectedIndex.value > index) selectedIndex.value--;
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e);
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function isImage(mimeType) {
|
|
|
+ return mimeType?.startsWith("image/");
|
|
|
+}
|
|
|
+
|
|
|
+function isVideo(mimeType) {
|
|
|
+ return mimeType?.startsWith("video/");
|
|
|
+}
|
|
|
+
|
|
|
+function getFileIcon(mimeType) {
|
|
|
+ if (isImage(mimeType)) return "mdi-image-outline";
|
|
|
+ if (isVideo(mimeType)) return "mdi-video-outline";
|
|
|
+ return "mdi-file-pdf-box";
|
|
|
+}
|
|
|
+
|
|
|
+function getFileColor(mimeType) {
|
|
|
+ if (isImage(mimeType)) return "teal";
|
|
|
+ if (isVideo(mimeType)) return "blue";
|
|
|
+ return "negative";
|
|
|
+}
|
|
|
+
|
|
|
+function formatDate(dateStr) {
|
|
|
+ if (!dateStr) return "";
|
|
|
+ return new Date(dateStr).toLocaleDateString("pt-BR");
|
|
|
+}
|
|
|
|
|
|
-const columns = [
|
|
|
- {
|
|
|
- name: "date",
|
|
|
- label: "Data",
|
|
|
- field: "date",
|
|
|
- align: "left",
|
|
|
- style: "width: 90px",
|
|
|
- },
|
|
|
- { name: "title", label: "Título", field: "title", align: "left" },
|
|
|
- {
|
|
|
- name: "actions",
|
|
|
- label: "Ações",
|
|
|
- field: "actions",
|
|
|
- align: "center",
|
|
|
- style: "width: 90px",
|
|
|
- },
|
|
|
- {
|
|
|
- name: "franchisee",
|
|
|
- label: "Franqueado",
|
|
|
- field: "franchisee",
|
|
|
- align: "center",
|
|
|
- style: "width: 100px",
|
|
|
- },
|
|
|
-];
|
|
|
-
|
|
|
-const medias = ref([
|
|
|
- {
|
|
|
- date: "15/01/2026",
|
|
|
- title: "Imagens Sede",
|
|
|
- url: "",
|
|
|
- },
|
|
|
-]);
|
|
|
+onMounted(fetchMedias);
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
.preview-box {
|
|
|
border: 1px solid #e0e0e0;
|
|
|
border-radius: 8px;
|
|
|
- min-height: 400px;
|
|
|
+ overflow: hidden;
|
|
|
+ min-height: 500px;
|
|
|
}
|
|
|
|
|
|
.media-item-active {
|
|
|
background-color: rgba(255, 131, 64, 0.08);
|
|
|
}
|
|
|
|
|
|
-.transparent-header :deep(thead tr th) {
|
|
|
- background-color: transparent;
|
|
|
+.ellipsis {
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
}
|
|
|
</style>
|