Merge pull request 'Order Catalog Filter' (#59) from feature/order-catalog-filter into branch-PR-2

Reviewed-on: hyervoni/salix-front-mindshore#59
This commit is contained in:
Kevin Martinez 2024-01-08 15:05:15 +00:00
commit 1f12609a05
12 changed files with 579 additions and 283 deletions

View File

@ -135,12 +135,11 @@ async function save() {
await axios.patch($props.urlUpdate || $props.url, body); await axios.patch($props.urlUpdate || $props.url, body);
} }
emit('onDataSaved', formData.value); emit('onDataSaved', formData.value);
originalData.value = JSON.parse(JSON.stringify(formData.value));
hasChanges.value = false;
} catch (err) { } catch (err) {
notify('errors.create', 'negative'); notify('errors.create', 'negative');
} }
originalData.value = JSON.parse(JSON.stringify(formData.value));
hasChanges.value = false;
isLoading.value = false; isLoading.value = false;
} }

View File

@ -4,6 +4,8 @@ import { useI18n } from 'vue-i18n';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import toDate from 'filters/toDate'; import toDate from 'filters/toDate';
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
dataKey: { dataKey: {
@ -39,6 +41,10 @@ const props = defineProps({
type: Array, type: Array,
default: () => [], default: () => [],
}, },
customTags: {
type: Array,
default: () => [],
},
}); });
const emit = defineEmits(['refresh', 'clear', 'search', 'init', 'remove']); const emit = defineEmits(['refresh', 'clear', 'search', 'init', 'remove']);
@ -104,19 +110,26 @@ async function clearFilters() {
emit('clear'); emit('clear');
} }
const tags = computed(() => { const tagsList = computed(() =>
return Object.entries(userParams.value) Object.entries(userParams.value)
.filter(([key, value]) => value && !(props.hiddenTags || []).includes(key)) .filter(([key, value]) => value && !(props.hiddenTags || []).includes(key))
.map(([key, value]) => ({ .map(([key, value]) => ({
label: key, label: key,
value: value, value: value,
})); }))
}); );
const tags = computed(() =>
tagsList.value.filter((tag) => !(props.customTags || []).includes(tag.label))
);
const customTags = computed(() =>
tagsList.value.filter((tag) => (props.customTags || []).includes(tag.label))
);
async function remove(key) { async function remove(key) {
userParams.value[key] = null; userParams.value[key] = null;
await search(); await search();
emit('remove', key) emit('remove', key);
} }
function formatValue(value) { function formatValue(value) {
@ -172,21 +185,17 @@ function formatValue(value) {
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<div <div
v-if="tags.length === 0" v-if="tagsList.length === 0"
class="text-grey font-xs text-center full-width" class="text-grey font-xs text-center full-width"
> >
{{ t(`No filters applied`) }} {{ t(`No filters applied`) }}
</div> </div>
<div> <div>
<QChip <VnFilterPanelChip
:key="chip.label"
@remove="remove(chip.label)"
class="text-dark"
color="primary"
icon="label"
:removable="!unremovableParams.includes(chip.label)"
size="sm"
v-for="chip of tags" v-for="chip of tags"
:key="chip.label"
:removable="!unremovableParams.includes(chip.label)"
@remove="remove(chip.label)"
> >
<slot name="tags" :tag="chip" :format-fn="formatValue"> <slot name="tags" :tag="chip" :format-fn="formatValue">
<div class="q-gutter-x-xs"> <div class="q-gutter-x-xs">
@ -194,7 +203,15 @@ function formatValue(value) {
<span>"{{ chip.value }}"</span> <span>"{{ chip.value }}"</span>
</div> </div>
</slot> </slot>
</QChip> </VnFilterPanelChip>
<slot
v-if="$slots.customTags"
name="customTags"
:params="userParams"
:tags="customTags"
:format-fn="formatValue"
:search-fn="search"
/>
</div> </div>
</QItem> </QItem>
<QSeparator /> <QSeparator />

View File

@ -0,0 +1,7 @@
<script setup></script>
<template>
<QChip class="text-dark" color="primary" icon="label" size="sm" v-bind="$attrs">
<slot />
</QChip>
</template>

View File

@ -1,12 +0,0 @@
<script setup>
import OrderForm from 'pages/Order/Card/OrderForm.vue';
</script>
<template>
<QToolbar>
<div id="st-data"></div>
<QSpace />
<div id="st-actions"></div>
</QToolbar>
<OrderForm />
</template>
<style lang="scss" scoped></style>

View File

@ -15,9 +15,7 @@ const stateStore = useStateStore();
</QDrawer> </QDrawer>
<QPageContainer> <QPageContainer>
<QPage> <QPage>
<div class="q-pa-md"> <RouterView></RouterView>
<RouterView></RouterView>
</div>
</QPage> </QPage>
</QPageContainer> </QPageContainer>
</template> </template>

View File

@ -1,11 +1,14 @@
<script setup> <script setup>
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import axios from 'axios';
import VnInput from 'components/common/VnInput.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue'; import VnSelectFilter from 'components/common/VnSelectFilter.vue';
import axios from 'axios'; import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
import { useRoute } from 'vue-router';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -14,6 +17,10 @@ const props = defineProps({
type: String, type: String,
required: true, required: true,
}, },
tags: {
type: Array,
required: true,
},
}); });
const categoryList = ref(null); const categoryList = ref(null);
@ -26,6 +33,20 @@ const resetCategory = () => {
typeList.value = null; typeList.value = null;
}; };
const selectedOrder = ref(null);
const orderList = [
{ way: 'ASC', name: 'Ascendant' },
{ way: 'DESC', name: 'Descendant' },
];
const selectedOrderField = ref(null);
const OrderFields = [
{ field: 'relevancy DESC, name', name: 'Relevancy', priority: 999 },
{ field: 'showOrder, price', name: 'Color and price', priority: 999 },
{ field: 'name', name: 'Name', priority: 999 },
{ field: 'price', name: 'Price', priority: 999 },
];
const clearFilter = (key) => { const clearFilter = (key) => {
if (key === 'categoryFk') { if (key === 'categoryFk') {
resetCategory(); resetCategory();
@ -59,6 +80,11 @@ const onFilterInit = async ({ params }) => {
await loadTypes(params.categoryFk); await loadTypes(params.categoryFk);
selectedCategoryFk.value = params.categoryFk; selectedCategoryFk.value = params.categoryFk;
} }
if (params.orderBy) {
orderByParam.value = JSON.parse(params.orderBy);
selectedOrder.value = orderByParam.value?.way;
selectedOrderField.value = orderByParam.value?.field;
}
}; };
const selectedCategory = computed(() => const selectedCategory = computed(() =>
@ -81,6 +107,60 @@ function exprBuilder(param, value) {
} }
} }
const selectedTag = ref(null);
const tagValues = ref([{}]);
const tagOptions = ref(null);
const isButtonDisabled = computed(
() => !selectedTag.value || tagValues.value.some((item) => !item.value)
);
const applyTagFilter = (params, search) => {
if (!tagValues.value?.length) {
params.tagGroups = null;
search();
return;
}
if (!params.tagGroups) {
params.tagGroups = [];
}
params.tagGroups.push(
JSON.stringify({
values: tagValues.value,
tagSelection: {
...selectedTag.value,
orgShowField: selectedTag.value.name,
},
tagFk: selectedTag.value.tagFk,
})
);
search();
selectedTag.value = null;
tagValues.value = [{}];
};
const removeTagChip = (selection, params, search) => {
if (params.tagGroups) {
params.tagGroups = (params.tagGroups || []).filter(
(value) => value !== selection
);
}
search();
};
const orderByParam = ref(null);
const onOrderFieldChange = (value, params, search) => {
const orderBy = Object.assign({}, orderByParam.value, { field: value.field });
params.orderBy = JSON.stringify(orderBy);
search();
};
const onOrderChange = (value, params, search) => {
const orderBy = Object.assign({}, orderByParam.value, { way: value.way });
params.orderBy = JSON.stringify(orderBy);
search();
};
const setCategoryList = (data) => { const setCategoryList = (data) => {
categoryList.value = (data || []) categoryList.value = (data || [])
.filter((category) => category.display) .filter((category) => category.display)
@ -103,6 +183,7 @@ const getCategoryClass = (category, params) => {
:data-key="props.dataKey" :data-key="props.dataKey"
:hidden-tags="['orderFk', 'orderBy']" :hidden-tags="['orderFk', 'orderBy']"
:expr-builder="exprBuilder" :expr-builder="exprBuilder"
:custom-tags="['tagGroups']"
@init="onFilterInit" @init="onFilterInit"
@remove="clearFilter" @remove="clearFilter"
> >
@ -118,8 +199,27 @@ const getCategoryClass = (category, params) => {
<span>{{ formatFn(tag.value) }}</span> <span>{{ formatFn(tag.value) }}</span>
</div> </div>
</template> </template>
<template #customTags="{ tags: customTags, params, searchFn }">
<template v-for="tag in customTags" :key="tag.label">
<template v-if="tag.label === 'tagGroups'">
<VnFilterPanelChip
v-for="chip in tag.value"
:key="chip"
removable
@remove="removeTagChip(chip, params, searchFn)"
>
<strong> {{ JSON.parse(chip).tagSelection?.name }}: </strong>
<span>{{
(JSON.parse(chip).values || [])
.map((item) => item.value)
.join(' | ')
}}</span>
</VnFilterPanelChip>
</template>
</template>
</template>
<template #body="{ params, searchFn }"> <template #body="{ params, searchFn }">
<QList dense> <QList dense style="max-width: 256px">
<QItem class="category-filter q-mt-md"> <QItem class="category-filter q-mt-md">
<div <div
v-for="category in categoryList" v-for="category in categoryList"
@ -137,7 +237,7 @@ const getCategoryClass = (category, params) => {
</QIcon> </QIcon>
</div> </div>
</QItem> </QItem>
<QItem class="q-mt-md"> <QItem class="q-my-md">
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelectFilter
:label="t('params.type')" :label="t('params.type')"
@ -171,6 +271,130 @@ const getCategoryClass = (category, params) => {
</VnSelectFilter> </VnSelectFilter>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QSeparator />
<QItem class="q-my-md">
<QItemSection>
<VnSelectFilter
:label="t('params.order')"
v-model="selectedOrder"
:options="orderList || []"
option-value="way"
option-label="name"
dense
outlined
rounded
:emit-value="false"
use-input
:is-clearable="false"
@update:model-value="
(value) => onOrderChange(value, params, searchFn)
"
/>
</QItemSection>
</QItem>
<QItem class="q-mb-md">
<QItemSection>
<VnSelectFilter
:label="t('params.order')"
v-model="selectedOrderField"
:options="OrderFields || []"
option-value="field"
option-label="name"
dense
outlined
rounded
:emit-value="false"
use-input
:is-clearable="false"
@update:model-value="
(value) => onOrderFieldChange(value, params, searchFn)
"
/>
</QItemSection>
</QItem>
<QSeparator />
<QItem class="q-mt-md">
<QItemSection>
<VnSelectFilter
:label="t('params.tag')"
v-model="selectedTag"
:options="props.tags || []"
option-value="id"
option-label="name"
dense
outlined
rounded
:emit-value="false"
use-input
/>
</QItemSection>
</QItem>
<QItem
v-for="(value, index) in tagValues"
:key="value"
class="q-mt-md filter-value"
>
<VnInput
v-if="selectedTag?.isFree"
v-model="value.value"
:label="t('params.value')"
is-outlined
class="filter-input"
/>
<VnSelectFilter
v-else
:label="t('params.value')"
v-model="value.value"
:options="tagOptions || []"
option-value="value"
option-label="value"
dense
outlined
rounded
emit-value
use-input
:disable="!selectedTag"
class="filter-input"
/>
<FetchData
v-if="selectedTag && !selectedTag.isFree"
:url="`Tags/${selectedTag?.id}/filterValue`"
limit="30"
auto-load
@on-fetch="(data) => (tagOptions = data)"
/>
<QIcon
name="delete"
class="filter-icon"
@click="(tagValues || []).splice(index, 1)"
/>
</QItem>
<QItem class="q-mt-lg">
<QIcon
name="add_circle"
class="filter-icon"
@click="tagValues.push({})"
/>
</QItem>
<QItem>
<QItemSection class="q-py-sm">
<QBtn
:label="t('Search')"
class="full-width"
color="primary"
dense
icon="search"
rounded
type="button"
unelevated
:disable="isButtonDisabled"
@click.stop="applyTagFilter(params, searchFn)"
/>
</QItemSection>
</QItem>
<QSeparator />
</QList> </QList>
</template> </template>
</VnFilterPanel> </VnFilterPanel>
@ -198,12 +422,29 @@ const getCategoryClass = (category, params) => {
.category-icon { .category-icon {
border-radius: 50%; border-radius: 50%;
background-color: var(--vn-label); background-color: var(--vn-light-gray);
font-size: 2.6rem; font-size: 2.6rem;
padding: 8px; padding: 8px;
cursor: pointer; cursor: pointer;
} }
} }
.filter-icon {
font-size: 24px;
color: $primary;
padding: 0 4px;
cursor: pointer;
}
.filter-input {
flex-shrink: 1;
min-width: 0;
}
.filter-value {
display: flex;
align-items: center;
}
</style> </style>
<i18n> <i18n>
@ -211,10 +452,16 @@ en:
params: params:
type: Type type: Type
orderBy: Order By orderBy: Order By
tag: Tag
value: Value
order: Order
es: es:
params: params:
type: Tipo type: Tipo
orderBy: Ordenar por orderBy: Ordenar por
tag: Etiqueta
value: Valor
order: Orden
Plant: Planta Plant: Planta
Flower: Flor Flower: Flor
Handmade: Confección Handmade: Confección

View File

@ -24,7 +24,7 @@ const dialog = ref(null);
<template> <template>
<div class="container order-catalog-item overflow-hidden"> <div class="container order-catalog-item overflow-hidden">
<div class="card shadow-6 bg-dark"> <QCard class="card shadow-6">
<div class="img-wrapper"> <div class="img-wrapper">
<QImg <QImg
:src="`/api/Images/catalog/200x200/${item.id}/download?access_token=${token}`" :src="`/api/Images/catalog/200x200/${item.id}/download?access_token=${token}`"
@ -76,7 +76,7 @@ const dialog = ref(null);
</p> </p>
</div> </div>
</div> </div>
</div> </QCard>
</div> </div>
</template> </template>

View File

@ -51,13 +51,13 @@ async function remove() {
<i18n> <i18n>
en: en:
deleteOrder: Delete order, deleteOrder: Delete order
confirmDeletion: Confirm deletion, confirmDeletion: Confirm deletion
confirmDeletionMessage: Are you sure you want to delete this order? confirmDeletionMessage: Are you sure you want to delete this order?
es: es:
deleteOrder: Eliminar pedido, deleteOrder: Eliminar pedido
confirmDeletion: Confirmar eliminación, confirmDeletion: Confirmar eliminación
confirmDeletionMessage: Seguro que quieres eliminar este pedido? confirmDeletionMessage: Seguro que quieres eliminar este pedido?
</i18n> </i18n>

View File

@ -65,8 +65,8 @@ const fetchAgencyList = async (landed, addressFk) => {
}; };
const fetchOrderDetails = (order) => { const fetchOrderDetails = (order) => {
fetchAddressList(order?.addressFk) fetchAddressList(order?.addressFk);
fetchAgencyList(order?.landed, order?.addressFk) fetchAgencyList(order?.landed, order?.addressFk);
}; };
const orderMapper = (order) => { const orderMapper = (order) => {
@ -105,7 +105,7 @@ const orderFilter = {
</script> </script>
<template> <template>
<QToolbar> <QToolbar class="bg-vn-dark justify-end">
<div id="st-data"></div> <div id="st-data"></div>
<QSpace /> <QSpace />
<div id="st-actions"></div> <div id="st-actions"></div>
@ -116,94 +116,97 @@ const orderFilter = {
:filter="{ fields: ['id', 'name', 'defaultAddressFk'] }" :filter="{ fields: ['id', 'name', 'defaultAddressFk'] }"
auto-load auto-load
/> />
<FormModel
:url="!isNew ? `Orders/${route.params.id}` : null" <div class="q-pa-md">
:url-create="isNew ? 'Orders/new' : null" <FormModel
:model="ORDER_MODEL" :url="!isNew ? `Orders/${route.params.id}` : null"
:form-initial-data="isNew ? initialFormState : null" :url-create="isNew ? 'Orders/new' : null"
:observe-form-changes="!isNew" :model="ORDER_MODEL"
:mapper="isNew ? orderMapper : null" :form-initial-data="isNew ? initialFormState : null"
:filter="orderFilter" :observe-form-changes="!isNew"
@on-fetch="fetchOrderDetails" :mapper="isNew ? orderMapper : null"
> :filter="orderFilter"
<template #form="{ data }"> @on-fetch="fetchOrderDetails"
<VnRow class="row q-gutter-md q-mb-md"> >
<div class="col"> <template #form="{ data }">
<VnSelectFilter <VnRow class="row q-gutter-md q-mb-md">
:label="t('order.form.clientFk')" <div class="col">
v-model="data.clientFk" <VnSelectFilter
:options="clientList" :label="t('order.form.clientFk')"
option-value="id" v-model="data.clientFk"
option-label="name" :options="clientList"
hide-selected option-value="id"
@update:model-value=" option-label="name"
(client) => fetchAddressList(client.defaultAddressFk) hide-selected
" @update:model-value="
> (client) => fetchAddressList(client.defaultAddressFk)
<template #option="scope"> "
<QItem v-bind="scope.itemProps"> >
<QItemSection> <template #option="scope">
<QItemLabel> <QItem v-bind="scope.itemProps">
{{ `${scope.opt.id}: ${scope.opt.name}` }} <QItemSection>
</QItemLabel> <QItemLabel>
</QItemSection> {{ `${scope.opt.id}: ${scope.opt.name}` }}
</QItem> </QItemLabel>
</template> </QItemSection>
</VnSelectFilter> </QItem>
</div> </template>
<div class="col"> </VnSelectFilter>
<VnSelectFilter </div>
:label="t('order.form.addressFk')" <div class="col">
v-model="data.addressFk" <VnSelectFilter
:options="addressList" :label="t('order.form.addressFk')"
option-value="id" v-model="data.addressFk"
option-label="nickname" :options="addressList"
hide-selected option-value="id"
:disable="!addressList?.length" option-label="nickname"
@update:model-value=" hide-selected
() => fetchAgencyList(data.landed, data.addressFk) :disable="!addressList?.length"
" @update:model-value="
> () => fetchAgencyList(data.landed, data.addressFk)
<template #option="scope"> "
<QItem v-bind="scope.itemProps"> >
<QItemSection> <template #option="scope">
<QItemLabel> <QItem v-bind="scope.itemProps">
{{ <QItemSection>
`${scope.opt.nickname}: ${scope.opt.street},${scope.opt.city}` <QItemLabel>
}} {{
</QItemLabel> `${scope.opt.nickname}: ${scope.opt.street},${scope.opt.city}`
</QItemSection> }}
</QItem> </QItemLabel>
</template> </QItemSection>
</VnSelectFilter> </QItem>
</div> </template>
</VnRow> </VnSelectFilter>
<VnRow class="row q-gutter-md q-mb-md"> </div>
<div class="col"> </VnRow>
<VnInputDate <VnRow class="row q-gutter-md q-mb-md">
placeholder="dd-mm-aaa" <div class="col">
:label="t('order.form.landed')" <VnInputDate
v-model="data.landed" placeholder="dd-mm-aaa"
@update:model-value=" :label="t('order.form.landed')"
() => fetchAgencyList(data.landed, data.addressFk) v-model="data.landed"
" @update:model-value="
/> () => fetchAgencyList(data.landed, data.addressFk)
</div> "
</VnRow> />
<VnRow class="row q-gutter-md q-mb-md"> </div>
<div class="col"> </VnRow>
<VnSelectFilter <VnRow class="row q-gutter-md q-mb-md">
:label="t('order.form.agencyModeFk')" <div class="col">
v-model="data.agencyModeFk" <VnSelectFilter
:options="agencyList" :label="t('order.form.agencyModeFk')"
option-value="agencyModeFk" v-model="data.agencyModeFk"
option-label="agencyMode" :options="agencyList"
hide-selected option-value="agencyModeFk"
:disable="!agencyList?.length" option-label="agencyMode"
> hide-selected
</VnSelectFilter> :disable="!agencyList?.length"
</div> >
</VnRow> </VnSelectFilter>
</template> </div>
</FormModel> </VnRow>
</template>
</FormModel>
</div>
</template> </template>

View File

@ -56,148 +56,161 @@ const detailsColumns = ref([
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()"> <Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
<OrderSearchbar /> <OrderSearchbar />
</Teleport> </Teleport>
<CardSummary ref="summary" :url="`Orders/${entityId}/summary`">
<template #header="{ entity }"> <div class="q-pa-md">
{{ t('order.summary.basket') }} #{{ entity?.id }} - <CardSummary ref="summary" :url="`Orders/${entityId}/summary`">
{{ entity?.client?.name }} ({{ entity?.clientFk }}) <template #header="{ entity }">
</template> {{ t('order.summary.basket') }} #{{ entity?.id }} -
<template #body="{ entity }"> {{ entity?.client?.name }} ({{ entity?.clientFk }})
<QCard class="vn-one"> </template>
<VnLv label="ID" :value="entity.id" /> <template #body="{ entity }">
<VnLv :label="t('order.summary.nickname')" dash> <QCard class="vn-one">
<template #value> <VnLv label="ID" :value="entity.id" />
<span class="link"> <VnLv :label="t('order.summary.nickname')" dash>
{{ dashIfEmpty(entity?.address?.nickname) }} <template #value>
<CustomerDescriptorProxy :id="entity?.clientFk" /> <span class="link">
</span> {{ dashIfEmpty(entity?.address?.nickname) }}
</template> <CustomerDescriptorProxy :id="entity?.clientFk" />
</VnLv> </span>
<VnLv </template>
:label="t('order.summary.company')" </VnLv>
:value="entity?.address?.companyFk" <VnLv
/> :label="t('order.summary.company')"
<VnLv :value="entity?.address?.companyFk"
:label="t('order.summary.confirmed')" />
:value="Boolean(entity?.isConfirmed)" <VnLv
/> :label="t('order.summary.confirmed')"
</QCard> :value="Boolean(entity?.isConfirmed)"
<QCard class="vn-one"> />
<VnLv </QCard>
:label="t('order.summary.created')" <QCard class="vn-one">
:value="toDateHour(entity?.created)" <VnLv
/> :label="t('order.summary.created')"
<VnLv :value="toDateHour(entity?.created)"
:label="t('order.summary.confirmed')" />
:value="toDateHour(entity?.confirmed)" <VnLv
/> :label="t('order.summary.confirmed')"
<VnLv :value="toDateHour(entity?.confirmed)"
:label="t('order.summary.landed')" />
:value="toDateHour(entity?.landed)" <VnLv
/> :label="t('order.summary.landed')"
<VnLv :label="t('order.summary.phone')"> :value="toDateHour(entity?.landed)"
<template #value> />
{{ dashIfEmpty(entity?.address?.phone) }} <VnLv :label="t('order.summary.phone')">
<a <template #value>
v-if="entity?.address?.phone" {{ dashIfEmpty(entity?.address?.phone) }}
:href="`tel:${entity?.address?.phone}`" <a
class="text-primary" v-if="entity?.address?.phone"
> :href="`tel:${entity?.address?.phone}`"
<QIcon name="phone" /> class="text-primary"
</a> >
</template> <QIcon name="phone" />
</VnLv> </a>
<VnLv </template>
:label="t('order.summary.createdFrom')" </VnLv>
:value="entity?.sourceApp" <VnLv
/> :label="t('order.summary.createdFrom')"
<VnLv :value="entity?.sourceApp"
:label="t('order.summary.address')" />
:value="`${entity?.address?.street} - ${entity?.address?.city} (${entity?.address?.province?.name})`" <VnLv
class="order-summary-address" :label="t('order.summary.address')"
/> :value="`${entity?.address?.street} - ${entity?.address?.city} (${entity?.address?.province?.name})`"
</QCard> class="order-summary-address"
<QCard class="vn-one"> />
<p class="header"> </QCard>
{{ t('order.summary.notes') }} <QCard class="vn-one">
</p> <p class="header">
<p v-if="entity?.note" class="no-margin"> {{ t('order.summary.notes') }}
{{ entity?.note }} </p>
</p> <p v-if="entity?.note" class="no-margin">
</QCard> {{ entity?.note }}
<QCard class="vn-one"> </p>
<VnLv> </QCard>
<template #label> <QCard class="vn-one">
<span class="text-h6">{{ t('order.summary.subtotal') }}</span> <VnLv>
</template> <template #label>
<template #value> <span class="text-h6">{{ t('order.summary.subtotal') }}</span>
<span class="text-h6">{{ toCurrency(entity?.subTotal) }}</span> </template>
</template> <template #value>
</VnLv> <span class="text-h6">{{
<VnLv> toCurrency(entity?.subTotal)
<template #label> }}</span>
<span class="text-h6">{{ t('order.summary.vat') }}</span> </template>
</template> </VnLv>
<template #value> <VnLv>
<span class="text-h6">{{ toCurrency(entity?.VAT) }}</span> <template #label>
</template> <span class="text-h6">{{ t('order.summary.vat') }}</span>
</VnLv> </template>
<VnLv> <template #value>
<template #label> <span class="text-h6">{{ toCurrency(entity?.VAT) }}</span>
<span class="text-h6">{{ t('order.summary.total') }}</span> </template>
</template> </VnLv>
<template #value> <VnLv>
<span class="text-h6">{{ toCurrency(entity?.total) }}</span> <template #label>
</template> <span class="text-h6">{{ t('order.summary.total') }}</span>
</VnLv> </template>
</QCard> <template #value>
<QCard> <span class="text-h6">{{ toCurrency(entity?.total) }}</span>
<p class="header"> </template>
{{ t('order.summary.details') }} </VnLv>
</p> </QCard>
<QTable <QCard>
:columns="detailsColumns" <p class="header">
:rows="entity?.rows" {{ t('order.summary.details') }}
flat </p>
hide-pagination <QTable
> :columns="detailsColumns"
<template #header="props"> :rows="entity?.rows"
<QTr :props="props"> flat
<QTh auto-width>{{ t('order.summary.item') }}</QTh> hide-pagination
<QTh>{{ t('order.summary.description') }}</QTh> >
<QTh auto-width>{{ t('order.summary.quantity') }}</QTh> <template #header="props">
<QTh auto-width>{{ t('order.summary.price') }}</QTh> <QTr :props="props">
<QTh auto-width>{{ t('order.summary.amount') }}</QTh> <QTh auto-width>{{ t('order.summary.item') }}</QTh>
</QTr> <QTh>{{ t('order.summary.description') }}</QTh>
</template> <QTh auto-width>{{ t('order.summary.quantity') }}</QTh>
<template #body="props"> <QTh auto-width>{{ t('order.summary.price') }}</QTh>
<QTr :props="props"> <QTh auto-width>{{ t('order.summary.amount') }}</QTh>
<QTd key="item" :props="props" class="item"> </QTr>
{{ props.row.item?.id }} </template>
</QTd> <template #body="props">
<QTd key="description" :props="props" class="description"> <QTr :props="props">
<div class="name"> <QTd key="item" :props="props" class="item">
<span>{{ props.row.item.name }}</span> {{ props.row.item?.id }}
<span v-if="props.row.item.subName" class="subName"> </QTd>
{{ props.row.item.subName }} <QTd key="description" :props="props" class="description">
</span> <div class="name">
</div> <span>{{ props.row.item.name }}</span>
<fetched-tags :item="props.row.item" :max-length="5" /> <span
</QTd> v-if="props.row.item.subName"
<QTd key="quantity" :props="props"> class="subName"
{{ props.row.quantity }} >
</QTd> {{ props.row.item.subName }}
<QTd key="price" :props="props"> </span>
{{ props.row.price }} </div>
</QTd> <fetched-tags
<QTd key="amount" :props="props"> :item="props.row.item"
{{ toCurrency(props.row?.quantity * props.row?.price) }} :max-length="5"
</QTd> />
</QTr> </QTd>
</template> <QTd key="quantity" :props="props">
</QTable> {{ props.row.quantity }}
</QCard> </QTd>
</template> <QTd key="price" :props="props">
</CardSummary> {{ props.row.price }}
</QTd>
<QTd key="amount" :props="props">
{{
toCurrency(props.row?.quantity * props.row?.price)
}}
</QTd>
</QTr>
</template>
</QTable>
</QCard>
</template>
</CardSummary>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
.cardSummary .summaryBody .vn-label-value.order-summary-address { .cardSummary .summaryBody .vn-label-value.order-summary-address {

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { onMounted, onUnmounted } from 'vue'; import {onMounted, onUnmounted, ref} from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnPaginate from 'components/ui/VnPaginate.vue'; import VnPaginate from 'components/ui/VnPaginate.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue';
@ -20,6 +20,22 @@ const catalogParams = {
orderBy: JSON.stringify({ field: 'relevancy DESC, name', way: 'ASC', isTag: false }), orderBy: JSON.stringify({ field: 'relevancy DESC, name', way: 'ASC', isTag: false }),
}; };
const tags = ref([])
function extractTags(items) {
const resultTags = [];
(items || []).forEach((item) => {
(item.tags || []).forEach((tag) => {
const index = resultTags.findIndex((item) => item.tagFk === tag.tagFk);
if (index === -1) {
resultTags.push({ ...tag, priority: 1 });
} else {
resultTags[index].priority += 1;
}
});
});
tags.value = resultTags
}
</script> </script>
<template> <template>
@ -50,7 +66,7 @@ const catalogParams = {
</Teleport> </Teleport>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8"> <QScrollArea class="fit text-grey-8">
<OrderCatalogFilter data-key="OrderCatalogList" /> <OrderCatalogFilter data-key="OrderCatalogList" :tags="tags" />
</QScrollArea> </QScrollArea>
</QDrawer> </QDrawer>
<QPage class="column items-center q-pa-md"> <QPage class="column items-center q-pa-md">
@ -61,6 +77,7 @@ const catalogParams = {
:limit="50" :limit="50"
:user-params="catalogParams" :user-params="catalogParams"
auto-load auto-load
@on-fetch="extractTags"
> >
<template #body="{ rows }"> <template #body="{ rows }">
<div class="catalog-list"> <div class="catalog-list">

View File

@ -25,6 +25,7 @@ const orderSummary = ref({
vat: null, vat: null,
}); });
const componentKey = ref(0); const componentKey = ref(0);
const order = ref(0);
const refresh = () => { const refresh = () => {
componentKey.value += 1; componentKey.value += 1;
@ -63,6 +64,12 @@ async function confirmOrder() {
</script> </script>
<template> <template>
<FetchData
:key="componentKey"
:url="`Orders/${route.params.id}`"
@on-fetch="(data) => (order = data)"
auto-load
/>
<FetchData <FetchData
:key="componentKey" :key="componentKey"
:url="`Orders/${route.params.id}/getTotal`" :url="`Orders/${route.params.id}/getTotal`"
@ -177,7 +184,7 @@ async function confirmOrder() {
:value="toCurrency(row.price * row.quantity)" :value="toCurrency(row.price * row.quantity)"
/> />
</template> </template>
<template #actions> <template #actions v-if="!order?.isConfirmed">
<QBtn <QBtn
:label="t('remove')" :label="t('remove')"
@click.stop="confirmRemove(row)" @click.stop="confirmRemove(row)"
@ -190,7 +197,7 @@ async function confirmOrder() {
</template> </template>
</VnPaginate> </VnPaginate>
</div> </div>
<QPageSticky :offset="[20, 20]"> <QPageSticky :offset="[20, 20]" v-if="!order?.isConfirmed">
<QBtn fab icon="check" color="primary" @click="confirmOrder()" /> <QBtn fab icon="check" color="primary" @click="confirmOrder()" />
<QTooltip> <QTooltip>
{{ t('confirm') }} {{ t('confirm') }}