<script setup> import { onBeforeMount, watch, computed, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue'; import { useArrayData } from 'composables/useArrayData'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useState } from 'src/composables/useState'; import { useRoute } from 'vue-router'; import VnMoreOptions from './VnMoreOptions.vue'; const $props = defineProps({ url: { type: String, default: '', }, filter: { type: Object, default: null, }, title: { type: String, default: '', }, subtitle: { type: Number, default: null, }, dataKey: { type: String, default: null, }, module: { type: String, default: null, }, summary: { type: Object, default: null, }, }); const state = useState(); const route = useRoute(); const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); let arrayData; let store; let entity; const isLoading = ref(false); const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName); defineExpose({ getData }); onBeforeMount(async () => { arrayData = useArrayData($props.dataKey, { url: $props.url, filter: $props.filter, skip: 0, }); store = arrayData.store; entity = computed(() => { const data = (Array.isArray(store.data) ? store.data[0] : store.data) ?? {}; if (data) emit('onFetch', data); return data; }); // It enables to load data only once if the module is the same as the dataKey if (!isSameDataKey.value || !route.params.id) await getData(); watch( () => [$props.url, $props.filter], async () => { if (!isSameDataKey.value) await getData(); } ); }); async function getData() { store.url = $props.url; store.filter = $props.filter ?? {}; isLoading.value = true; try { const { data } = await arrayData.fetch({ append: false, updateRouter: false }); state.set($props.dataKey, data); emit('onFetch', Array.isArray(data) ? data[0] : data); } finally { isLoading.value = false; } } function getValueFromPath(path) { if (!path) return; const keys = path.toString().split('.'); let current = entity.value; for (const key of keys) { if (current[key] === undefined) return undefined; else current = current[key]; } return current; } const emit = defineEmits(['onFetch']); const iconModule = computed(() => route.matched[1].meta.icon); const toModule = computed(() => route.matched[1].path.split('/').length > 2 ? route.matched[1].redirect : route.matched[1].children[0].redirect ); </script> <template> <div class="descriptor"> <template v-if="entity && !isLoading"> <div class="header bg-primary q-pa-sm justify-between"> <slot name="header-extra-action" ><QBtn round flat dense size="md" :icon="iconModule" color="white" class="link" :to="$attrs['to-module'] ?? toModule" > <QTooltip> {{ t('globals.goToModuleIndex') }} </QTooltip> </QBtn></slot > <QBtn @click.stop="viewSummary(entity.id, $props.summary)" round flat dense size="md" icon="preview" color="white" class="link" v-if="summary" > <QTooltip> {{ t('components.smartCard.openSummary') }} </QTooltip> </QBtn> <RouterLink :to="{ name: `${module}Summary`, params: { id: entity.id } }"> <QBtn class="link" color="white" dense flat icon="launch" round size="md" > <QTooltip> {{ t('components.cardDescriptor.summary') }} </QTooltip> </QBtn> </RouterLink> <VnMoreOptions v-if="$slots.menu"> <template #menu="{ menuRef }"> <slot name="menu" :entity="entity" :menu-ref="menuRef" /> </template> </VnMoreOptions> </div> <slot name="before" /> <div class="body q-py-sm"> <QList dense> <QItemLabel header class="ellipsis text-h5" :lines="1"> <div class="title"> <span v-if="$props.title" :title="getValueFromPath(title)"> {{ getValueFromPath(title) ?? $props.title }} </span> <slot v-else name="description" :entity="entity"> <span :title="entity.name"> {{ entity.name }} </span> </slot> </div> </QItemLabel> <QItem dense> <QItemLabel class="subtitle" caption> #{{ getValueFromPath(subtitle) ?? entity.id }} </QItemLabel> </QItem> </QList> <div class="list-box q-mt-xs"> <slot name="body" :entity="entity" /> </div> </div> <div class="icons"> <slot name="icons" :entity="entity" /> </div> <div class="actions justify-center"> <slot name="actions" :entity="entity" /> </div> <slot name="after" /> </template> <!-- Skeleton --> <SkeletonDescriptor v-if="!entity || isLoading" /> </div> <QInnerLoading :label="t('globals.pleaseWait')" :showing="isLoading" color="primary" /> </template> <style lang="scss"> .body { background-color: var(--vn-section-color); .text-h5 { font-size: 20px; padding-top: 5px; padding-bottom: 0px; } .q-item { min-height: 20px; .link { margin-left: 10px; } } .vn-label-value { display: flex; padding: 0px 16px; .label { color: var(--vn-label-color); font-size: 14px; &:not(:has(a))::after { content: ':'; } } .value { color: var(--vn-text-color); font-size: 14px; margin-left: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; } .info { margin-left: 5px; } } } </style> <style lang="scss" scoped> .title { overflow: hidden; text-overflow: ellipsis; span { color: var(--vn-text-color); font-weight: bold; } } .subtitle { color: var(--vn-text-color); font-size: 16px; margin-bottom: 2px; } .list-box { .q-item__label { color: var(--vn-label-color); padding-bottom: 0%; } } .descriptor { width: 256px; .header { display: flex; align-items: center; } .icons { margin: 0 10px; display: flex; justify-content: center; .q-icon { margin-right: 5px; } } .actions { margin: 0 5px; justify-content: center !important; } } </style>