256 lines
6.8 KiB
Vue
256 lines
6.8 KiB
Vue
<script setup>
|
|
import { onBeforeMount, useSlots, 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';
|
|
|
|
const $props = defineProps({
|
|
url: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
filter: {
|
|
type: Object,
|
|
default: null,
|
|
},
|
|
module: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
title: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
subtitle: {
|
|
type: Number,
|
|
default: 0,
|
|
},
|
|
dataKey: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
summary: {
|
|
type: Object,
|
|
default: null,
|
|
},
|
|
});
|
|
|
|
const state = useState();
|
|
const slots = useSlots();
|
|
const { t } = useI18n();
|
|
const { viewSummary } = useSummaryDialog();
|
|
const arrayData = useArrayData($props.dataKey || $props.module, {
|
|
url: $props.url,
|
|
filter: $props.filter,
|
|
skip: 0,
|
|
});
|
|
const { store } = arrayData;
|
|
const entity = computed(() =>Array.isArray( store.data) ? store.data[0] : store.data);
|
|
const isLoading = ref(false);
|
|
|
|
defineExpose({
|
|
getData,
|
|
});
|
|
onBeforeMount(async () => {
|
|
await getData();
|
|
watch(
|
|
() => $props.url,
|
|
async () => await getData()
|
|
);
|
|
});
|
|
|
|
async function getData() {
|
|
store.url = $props.url;
|
|
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;
|
|
}
|
|
}
|
|
const emit = defineEmits(['onFetch']);
|
|
</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
|
|
@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>
|
|
<QBtn
|
|
color="white"
|
|
dense
|
|
flat
|
|
icon="more_vert"
|
|
round
|
|
size="md"
|
|
:class="{ invisible: !slots.menu }"
|
|
>
|
|
<QTooltip>
|
|
{{ t('components.cardDescriptor.moreOptions') }}
|
|
</QTooltip>
|
|
<QMenu>
|
|
<QList>
|
|
<slot name="menu" :entity="entity" />
|
|
</QList>
|
|
</QMenu>
|
|
</QBtn>
|
|
</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="$props.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>
|
|
#{{ $props.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;
|
|
}
|
|
.icons {
|
|
margin: 0 10px;
|
|
display: flex;
|
|
justify-content: center;
|
|
.q-icon {
|
|
margin-right: 5px;
|
|
}
|
|
}
|
|
.actions {
|
|
margin: 0 5px;
|
|
justify-content: center !important;
|
|
}
|
|
}
|
|
</style>
|