WIP: feat: refs #8463 cardDescriptorBeta #1291
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { onBeforeMount, computed } from 'vue';
|
||||
import { onBeforeMount, computed, onMounted, watch, ref } from 'vue';
|
||||
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
|
@ -7,7 +7,10 @@ import useCardSize from 'src/composables/useCardSize';
|
|||
import LeftMenu from 'components/LeftMenu.vue';
|
||||
import VnSubToolbar from '../ui/VnSubToolbar.vue';
|
||||
|
||||
const emit = defineEmits(['onFetch']);
|
||||
|
||||
const props = defineProps({
|
||||
id: { type: Number, required: false, default: null },
|
||||
dataKey: { type: String, required: true },
|
||||
baseUrl: { type: String, default: undefined },
|
||||
customUrl: { type: String, default: undefined },
|
||||
|
@ -18,52 +21,72 @@ const props = defineProps({
|
|||
searchDataKey: { type: String, default: undefined },
|
||||
searchbarProps: { type: Object, default: undefined },
|
||||
redirectOnError: { type: Boolean, default: false },
|
||||
visual: { type: Boolean, default: true },
|
||||
});
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const arrayData = ref({});
|
||||
|
||||
const id = computed(() => props.id || route?.params?.id);
|
||||
const url = computed(() => {
|
||||
if (props.baseUrl) {
|
||||
return `${props.baseUrl}/${route.params.id}`;
|
||||
return `${props.baseUrl}/${id.value}`;
|
||||
}
|
||||
return props.customUrl;
|
||||
});
|
||||
|
||||
const arrayData = useArrayData(props.dataKey, {
|
||||
url: url.value,
|
||||
filter: props.filter,
|
||||
userFilter: props.userFilter,
|
||||
});
|
||||
|
||||
onBeforeMount(async () => {
|
||||
console.log('asd', id.value);
|
||||
arrayData.value = useArrayData(props.dataKey);
|
||||
if (!arrayData.value.store.data && !arrayData.value.isLoading.value) {
|
||||
arrayData.value = useArrayData(props.dataKey, {
|
||||
url: url.value,
|
||||
filter: props.filter,
|
||||
userFilter: props.userFilter,
|
||||
});
|
||||
}
|
||||
|
||||
if (props.baseUrl && props.visual) {
|
||||
onBeforeRouteUpdate(async (to, from) => {
|
||||
if (to.params.id !== from.params.id) {
|
||||
arrayData.value.store.url = `${props.baseUrl}/${to.params.id}`;
|
||||
await fetch('router');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id };
|
||||
await arrayData.fetch({ append: false, updateRouter: false });
|
||||
if (!props.baseUrl) arrayData.value.store.filter.where = { id: id.value };
|
||||
await fetch('montar');
|
||||
} catch {
|
||||
if (!props.visual) return;
|
||||
const { matched: matches } = router.currentRoute.value;
|
||||
const { path } = matches.at(-1);
|
||||
router.push({ path: path.replace(/:id.*/, '') });
|
||||
}
|
||||
});
|
||||
|
||||
if (props.baseUrl) {
|
||||
onBeforeRouteUpdate(async (to, from) => {
|
||||
if (to.params.id !== from.params.id) {
|
||||
arrayData.store.url = `${props.baseUrl}/${to.params.id}`;
|
||||
await arrayData.fetch({ append: false, updateRouter: false });
|
||||
}
|
||||
});
|
||||
watch(
|
||||
() => arrayData?.value?.isLoading,
|
||||
(loading) => !loading && emit('onFetch', arrayData.value.store.data),
|
||||
|
||||
);
|
||||
|
||||
async function fetch() {
|
||||
await arrayData.value.fetch({ append: false, updateRouter: false });
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()">
|
||||
<component :is="descriptor" />
|
||||
<QSeparator />
|
||||
<LeftMenu source="card" />
|
||||
</Teleport>
|
||||
<VnSubToolbar />
|
||||
<div :class="[useCardSize(), $attrs.class]">
|
||||
<RouterView :key="route.path" />
|
||||
</div>
|
||||
<span v-if="visual">
|
||||
alexm
commented
No me acaba este enfoque, pero he visto y probado que con Vue no se puede acceder a funciones de un componente sin montarlo. No me acaba este enfoque, pero he visto y probado que con Vue no se puede acceder a funciones de un componente sin montarlo.
Seria esta opcion o dividir la logica del template...
|
||||
<Teleport to="#left-panel" v-if="stateStore.isHeaderMounted()">
|
||||
<component :is="descriptor" />
|
||||
<QSeparator />
|
||||
<LeftMenu source="card" />
|
||||
</Teleport>
|
||||
<VnSubToolbar />
|
||||
<div :class="[useCardSize(), $attrs.class]">
|
||||
<RouterView :key="route?.path" />
|
||||
</div>
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,262 @@
|
|||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue';
|
||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||
import { useRoute } from 'vue-router';
|
||||
import VnMoreOptions from './VnMoreOptions.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
subtitle: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
module: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
summary: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
card: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: 'md-width',
|
||||
},
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
const entity = ref({});
|
||||
const isLoading = ref(false);
|
||||
const emit = defineEmits(['onFetch']);
|
||||
|
||||
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 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,
|
||||
);
|
||||
|
||||
function setData(data) {
|
||||
const newData = (Array.isArray(data) ? data[0] : data) ?? {};
|
||||
entity.value = newData;
|
||||
isLoading.value = false;
|
||||
if (newData) emit('onFetch', newData);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
{{ id }}
|
||||
<component
|
||||
alexm
commented
Usamos el card, pero pasandole visual a Usamos el card, pero pasandole visual a `false`
|
||||
:is="card"
|
||||
:id
|
||||
:visual="false"
|
||||
@on-fetch="(data) => setData(data)"
|
||||
v-bind="$attrs"
|
||||
/>
|
||||
<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, $props.width)"
|
||||
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>
|
|
@ -1,12 +1,42 @@
|
|||
<script setup>
|
||||
import VnCardBeta from 'components/common/VnCardBeta.vue';
|
||||
import OrderDescriptor from 'pages/Order/Card/OrderDescriptor.vue';
|
||||
|
||||
const userFilter = {
|
||||
include: [
|
||||
{ relation: 'agencyMode', scope: { fields: ['name'] } },
|
||||
{
|
||||
relation: 'address',
|
||||
scope: { fields: ['nickname'] },
|
||||
},
|
||||
{ relation: 'rows', scope: { fields: ['id'] } },
|
||||
{
|
||||
relation: 'client',
|
||||
scope: {
|
||||
fields: [
|
||||
'salesPersonFk',
|
||||
'name',
|
||||
'isActive',
|
||||
'isFreezed',
|
||||
'isTaxDataChecked',
|
||||
],
|
||||
include: {
|
||||
relation: 'salesPersonUser',
|
||||
scope: { fields: ['id', 'name'] },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VnCardBeta
|
||||
alexm
commented
Ahora el card hace la peticion mas reusable para los modulos Ahora el card hace la peticion mas reusable para los modulos
|
||||
data-key="Order"
|
||||
base-url="Orders"
|
||||
:userFilter
|
||||
:descriptor="OrderDescriptor"
|
||||
v-bind="$attrs"
|
||||
v-on="$attrs"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -6,10 +6,11 @@ import { toCurrency, toDate } from 'src/filters';
|
|||
import { useState } from 'src/composables/useState';
|
||||
import useCardDescription from 'src/composables/useCardDescription';
|
||||
|
||||
import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
||||
import VnLv from 'src/components/ui/VnLv.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||
import OrderCard from './OrderCard.vue';
|
||||
import CardDescriptorBeta from 'src/components/ui/CardDescriptorBeta.vue';
|
||||
|
||||
const DEFAULT_ITEMS = 0;
|
||||
|
||||
|
@ -26,37 +27,13 @@ const state = useState();
|
|||
const { t } = useI18n();
|
||||
const data = ref(useCardDescription());
|
||||
const getTotalRef = ref();
|
||||
const total = ref(0);
|
||||
|
||||
const entityId = computed(() => {
|
||||
return $props.id || route.params.id;
|
||||
});
|
||||
|
||||
const filter = {
|
||||
include: [
|
||||
{ relation: 'agencyMode', scope: { fields: ['name'] } },
|
||||
{
|
||||
relation: 'address',
|
||||
scope: { fields: ['nickname'] },
|
||||
},
|
||||
{ relation: 'rows', scope: { fields: ['id'] } },
|
||||
{
|
||||
relation: 'client',
|
||||
scope: {
|
||||
fields: [
|
||||
'salesPersonFk',
|
||||
'name',
|
||||
'isActive',
|
||||
'isFreezed',
|
||||
'isTaxDataChecked',
|
||||
],
|
||||
include: {
|
||||
relation: 'salesPersonUser',
|
||||
scope: { fields: ['id', 'name'] },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const orderTotal = computed(() => state.get('orderTotal') ?? 0);
|
||||
|
||||
const setData = (entity) => {
|
||||
if (!entity) return;
|
||||
|
@ -68,9 +45,6 @@ const setData = (entity) => {
|
|||
const getConfirmationValue = (isConfirmed) => {
|
||||
return t(isConfirmed ? 'globals.confirmed' : 'order.summary.notConfirmed');
|
||||
};
|
||||
|
||||
const orderTotal = computed(() => state.get('orderTotal') ?? 0);
|
||||
const total = ref(0);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -83,15 +57,14 @@ const total = ref(0);
|
|||
}
|
||||
"
|
||||
/>
|
||||
<CardDescriptor
|
||||
ref="descriptor"
|
||||
:url="`Orders/${entityId}`"
|
||||
:filter="filter"
|
||||
<CardDescriptorBeta
|
||||
v-bind="$attrs"
|
||||
:id
|
||||
:card="OrderCard"
|
||||
module="Order"
|
||||
:title="data.title"
|
||||
:subtitle="data.subtitle"
|
||||
@on-fetch="setData"
|
||||
data-key="orderData"
|
||||
>
|
||||
<template #body="{ entity }">
|
||||
<VnLv
|
||||
|
@ -142,5 +115,5 @@ const total = ref(0);
|
|||
</QBtn>
|
||||
</QCardActions>
|
||||
</template>
|
||||
</CardDescriptor>
|
||||
</CardDescriptorBeta>
|
||||
</template>
|
||||
|
|
|
@ -12,6 +12,11 @@ const $props = defineProps({
|
|||
|
||||
<template>
|
||||
<QPopupProxy>
|
||||
<OrderDescriptor v-if="$props.id" :id="$props.id" :summary="OrderSummary" />
|
||||
<OrderDescriptor
|
||||
v-if="$props.id"
|
||||
:id="$props.id"
|
||||
:summary="OrderSummary"
|
||||
data-key="orderDescriptor"
|
||||
alexm
commented
hay que cambiar el dataKey cuando es popup. sino si estas en ticket y abres un ticketDescriptor, te usara el mismo arrayData para los dos hay que cambiar el dataKey cuando es popup. sino si estas en ticket y abres un ticketDescriptor, te usara el mismo arrayData para los dos
|
||||
/>
|
||||
</QPopupProxy>
|
||||
</template>
|
||||
|
|
|
@ -13,6 +13,7 @@ import FetchedTags from 'components/ui/FetchedTags.vue';
|
|||
import VnTitle from 'src/components/common/VnTitle.vue';
|
||||
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
|
||||
import OrderDescriptorMenu from 'pages/Order/Card/OrderDescriptorMenu.vue';
|
||||
import OrderDescriptorProxy from './OrderDescriptorProxy.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
|
@ -106,7 +107,7 @@ async function handleConfirm() {
|
|||
<template #value>
|
||||
<span class="link">
|
||||
{{ dashIfEmpty(entity?.address?.nickname) }}
|
||||
<CustomerDescriptorProxy :id="entity?.clientFk" />
|
||||
<OrderDescriptorProxy :id="1" />
|
||||
alexm
commented
Lo he puesto para probar el funcionamiento en modo popup Lo he puesto para probar el funcionamiento en modo popup
Habria que quitarlo
|
||||
</span>
|
||||
</template>
|
||||
</VnLv>
|
||||
|
|
Loading…
Reference in New Issue
Con este watcher hacemos que cuando el card cambie de estado, emita los datos.