salix-front/src/components/ui/CardDescriptor.vue

368 lines
10 KiB
Vue

<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, useRouter } from 'vue-router';
import { useClipboard } from 'src/composables/useClipboard';
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,
},
summary: {
type: Object,
default: null,
},
width: {
type: String,
default: 'md-width',
},
});
const state = useState();
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const { copyText } = useClipboard();
const { viewSummary } = useSummaryDialog();
let arrayData;
let store;
let entity;
const isLoading = ref(false);
const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName);
const DESCRIPTOR_PROXY = 'DescriptorProxy';
const moduleName = ref();
const isSameModuleName = route.matched[1].meta.moduleName !== moduleName.value;
defineExpose({ getData });
onBeforeMount(async () => {
arrayData = useArrayData($props.dataKey, {
url: $props.url,
userFilter: $props.filter,
skip: 0,
oneRecord: true,
});
store = arrayData.store;
entity = computed(() => {
const data = 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();
},
);
});
function getName() {
let name = $props.dataKey;
if ($props.dataKey.includes(DESCRIPTOR_PROXY)) {
name = name.split(DESCRIPTOR_PROXY)[0];
}
return name;
}
const routeName = computed(() => {
let routeName = getName();
return `${routeName}Summary`;
});
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', 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;
}
function copyIdText(id) {
copyText(id, {
component: {
copyValue: id,
},
});
}
const emit = defineEmits(['onFetch']);
const iconModule = computed(() => {
moduleName.value = getName();
if (isSameModuleName) {
return router.options.routes[1].children.find((r) => r.name === moduleName.value)
?.meta?.icon;
} else {
return route.matched[1].meta.icon;
}
});
const toModule = computed(() => {
moduleName.value = getName();
if (isSameModuleName) {
return router.options.routes[1].children.find((r) => r.name === moduleName.value)
?.children[0]?.redirect;
} else {
return route.matched[1].path.split('/').length > 2
? route.matched[1].redirect
: route.matched[1].children[0].redirect;
}
});
</script>
<template>
<div class="descriptor" data-cy="cardDescriptor">
<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="toModule"
>
<QTooltip>
{{ t('globals.goToModuleIndex') }}
</QTooltip>
</QBtn>
</slot>
<QBtn
@click.stop="viewSummary(entity.id, $props.summary, $props.width)"
round
flat
dense
size="md"
icon="preview"
color="white"
class="link"
v-if="summary"
data-cy="openSummaryBtn"
>
<QTooltip>
{{ t('components.smartCard.openSummary') }}
</QTooltip>
</QBtn>
<RouterLink :to="{ name: routeName, params: { id: entity.id } }">
<QBtn
class="link"
color="white"
dense
flat
icon="launch"
round
size="md"
data-cy="goToSummaryBtn"
>
<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)"
:data-cy="`${$attrs['data-cy'] ?? 'cardDescriptor'}_title`"
>
{{ getValueFromPath(title) ?? $props.title }}
</span>
<slot v-else name="description" :entity="entity">
<span
:title="entity.name"
:data-cy="`${$attrs['data-cy'] ?? 'cardDescriptor'}_description`"
v-text="entity.name"
/>
</slot>
</div>
</QItemLabel>
<QItem>
<QItemLabel
class="subtitle"
:data-cy="`${$attrs['data-cy'] ?? 'cardDescriptor'}_subtitle`"
>
#{{ getValueFromPath(subtitle) ?? entity.id }}
</QItemLabel>
<QBtn
round
flat
dense
size="sm"
icon="content_copy"
color="primary"
@click.stop="copyIdText(entity.id)"
>
<QTooltip>
{{ t('globals.copyId') }}
</QTooltip>
</QBtn>
</QItem>
</QList>
<div
class="list-box q-mt-xs"
:data-cy="`${$attrs['data-cy'] ?? 'cardDescriptor'}_listbox`"
>
<slot name="body" :entity="entity" />
</div>
</div>
<div class="icons">
<slot name="icons" :entity="entity" />
</div>
<div class="actions justify-center" data-cy="descriptor_actions">
<slot name="actions" :entity="entity" />
</div>
<slot name="after" />
</template>
<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>
<i18n>
en:
globals:
copyId: Copy ID
es:
globals:
copyId: Copiar ID
</i18n>