From a65db2f4cf49deb0314ee0c7f6d643a4b0b8b775 Mon Sep 17 00:00:00 2001 From: Kevin Martinez <kevin.martinez@mindshore.io> Date: Tue, 19 Dec 2023 09:15:54 -0400 Subject: [PATCH] Create order catalog section --- src/i18n/en/index.js | 1 + src/pages/Order/Card/OrderCard.vue | 6 +- src/pages/Order/Card/OrderCatalogItem.vue | 182 ++++++++++++++++++ .../Order/Card/OrderCatalogItemDialog.vue | 83 ++++++++ src/pages/Order/Card/OrderSummary.vue | 20 +- src/pages/Order/OrderCatalog.vue | 81 ++++++++ src/router/modules/order.js | 11 +- 7 files changed, 373 insertions(+), 11 deletions(-) create mode 100644 src/pages/Order/Card/OrderCatalogItem.vue create mode 100644 src/pages/Order/Card/OrderCatalogItemDialog.vue create mode 100644 src/pages/Order/OrderCatalog.vue diff --git a/src/i18n/en/index.js b/src/i18n/en/index.js index 21cb2ea74..752314813 100644 --- a/src/i18n/en/index.js +++ b/src/i18n/en/index.js @@ -555,6 +555,7 @@ export default { create: 'Create', summary: 'Summary', basicData: 'Basic Data', + catalog: 'Catalog', }, field: { salesPersonFk: 'Sales Person', diff --git a/src/pages/Order/Card/OrderCard.vue b/src/pages/Order/Card/OrderCard.vue index 5a9a6452a..f6288207d 100644 --- a/src/pages/Order/Card/OrderCard.vue +++ b/src/pages/Order/Card/OrderCard.vue @@ -1,15 +1,11 @@ <script setup> import LeftMenu from 'components/LeftMenu.vue'; import { useStateStore } from 'stores/useStateStore'; -import OrderSearchbar from 'pages/Order/Card/OrderSearchbar.vue'; -import OrderDescriptor from "pages/Order/Card/OrderDescriptor.vue"; +import OrderDescriptor from 'pages/Order/Card/OrderDescriptor.vue'; const stateStore = useStateStore(); </script> <template> - <Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()"> - <OrderSearchbar /> - </Teleport> <QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256"> <QScrollArea class="fit"> <OrderDescriptor /> diff --git a/src/pages/Order/Card/OrderCatalogItem.vue b/src/pages/Order/Card/OrderCatalogItem.vue new file mode 100644 index 000000000..d187df8ce --- /dev/null +++ b/src/pages/Order/Card/OrderCatalogItem.vue @@ -0,0 +1,182 @@ +<script setup> +import { useSession } from 'composables/useSession'; +import VnLv from 'components/ui/VnLv.vue'; +import { useI18n } from 'vue-i18n'; +import OrderCatalogItemDialog from 'pages/Order/Card/OrderCatalogItemDialog.vue'; +import toCurrency from '../../../filters/toCurrency'; +import { ref } from 'vue'; + +const session = useSession(); +const token = session.getToken(); +const { t } = useI18n(); + +defineProps({ + item: { + type: Object, + required: true, + }, +}); + +const dialog = ref(null); +</script> + +<template> + <div class="container order-catalog-item overflow-hidden"> + <div class="card shadow-6 bg-dark"> + <div class="img-wrapper"> + <QImg + :src="`/api/Images/catalog/200x200/${item.id}/download?access_token=${token}`" + spinner-color="primary" + :ratio="1" + height="192" + width="192" + class="image" + /> + <div v-if="item.hex" class="item-color-container"> + <div + class="item-color" + :style="{ backgroundColor: `#${item.hex}` }" + ></div> + </div> + </div> + <div class="content"> + <span class="link">{{ item.name }}</span> + <p class="subName">{{ item.subName }}</p> + <template v-for="index in 4" :key="`tag-${index}`"> + <VnLv + v-if="item?.[`tag${index + 4}`]" + :label="item?.[`tag${index + 4}`] + ':'" + :value="item?.[`value${index + 4}`]" + /> + </template> + <QRating + :model-value="item.stars" + icon="star" + icon-selected="star" + color="primary" + readonly + /> + <div class="footer"> + <div class="price"> + <p>{{ item.available }} to {{ item.price }}</p> + <QIcon name="add_circle" class="icon"> + <QTooltip>{{ t('globals.add') }}</QTooltip> + <QPopupProxy ref="dialog"> + <OrderCatalogItemDialog + :prices="item.prices" + @added="() => dialog.hide()" + /> + </QPopupProxy> + </QIcon> + </div> + <p v-if="item.priceKg" class="price-kg"> + {{ t('price-kg') }} {{ toCurrency(item.priceKg) || 1123 }} + </p> + </div> + </div> + </div> + </div> +</template> + +<style lang="scss"> +.order-catalog-item { + .vn-label-value { + display: flex; + gap: 4px; + font-size: 11px; + + .label { + color: var(--vn-label); + } + + .value { + color: var(--vn-text); + } + } +} +</style> +<style lang="scss" scoped> +.container { + max-width: 448px; + width: 100%; +} + +.card { + display: flex; + height: 100%; + max-height: 192px; +} + +.card > * { + flex: 1; +} + +.img-wrapper { + position: relative; + max-width: 192px; +} + +.content { + padding: 12px; + display: flex; + flex-direction: column; + gap: 4px; + + .subName { + color: var(--vn-label); + text-transform: uppercase; + } + + p { + margin-bottom: 0; + } +} + +.footer { + .price { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + display: flex; + align-items: center; + justify-content: space-between; + + p { + font-size: 12px; + } + + .icon { + color: $primary; + font-size: 24px; + cursor: pointer; + } + } + + .price-kg { + font-size: 12px; + } +} + +.item-color-container { + position: absolute; + bottom: 12px; + right: 12px; + background: linear-gradient($dark, $primary); + border-radius: 50%; + width: 40px; + height: 40px; + padding: 4px; + + .item-color { + width: 100%; + height: 100%; + border-radius: 50%; + } +} +</style> +<i18n> +es: + price-kg: Precio por Kg +en: + price-kg: Price per Kg +</i18n> diff --git a/src/pages/Order/Card/OrderCatalogItemDialog.vue b/src/pages/Order/Card/OrderCatalogItemDialog.vue new file mode 100644 index 000000000..8d12824e6 --- /dev/null +++ b/src/pages/Order/Card/OrderCatalogItemDialog.vue @@ -0,0 +1,83 @@ +<script setup> +import toCurrency from '../../../filters/toCurrency'; +import { ref } from 'vue'; +import { useI18n } from 'vue-i18n'; +import axios from 'axios'; +import { useRoute } from 'vue-router'; +import useNotify from 'composables/useNotify'; + +const { t } = useI18n(); +const route = useRoute(); +const { notify } = useNotify(); +const props = defineProps({ + prices: { + type: Array, + required: true, + }, +}); + +const emit = defineEmits(['added']); + +const fields = ref((props.prices || []).map((item) => ({ ...item, quantity: 0 }))); + +const addToOrder = async () => { + const items = (fields.value || []).filter((item) => Number(item.quantity) > 0); + await axios.post('/OrderRows/addToOrder', { + items, + orderFk: Number(route.params.id), + }); + notify(t('globals.dataSaved'), 'positive'); + emit('added'); +}; +</script> + +<template> + <div class="container order-catalog-item q-pb-md"> + <QForm @submit.prevent="addToOrder"> + <QMarkupTable class="shadow-0"> + <tbody> + <tr v-for="item in fields" :key="item.warehouse"> + <td class="text-bold q-py-lg"> + {{ item.warehouse }} + </td> + <td class="text-right"> + <span + class="link" + @click=" + () => { + item.quantity = + Number(item.quantity) + item.grouping; + } + " + > + {{ item.grouping }} + </span> + x {{ toCurrency(item.price) }} + </td> + <td class="text-right"> + <QInput + v-model="item.quantity" + type="number" + :step="item.grouping" + min="0" + dense + /> + </td> + </tr> + </tbody> + </QMarkupTable> + <div class="flex justify-center q-mt-lg"> + <QBtn color="primary" type="submit"> + {{ t('globals.add') }} + </QBtn> + </div> + </QForm> + </div> +</template> + +<style lang="scss" scoped> +.container { + max-width: 448px; + width: 100%; +} +</style> diff --git a/src/pages/Order/Card/OrderSummary.vue b/src/pages/Order/Card/OrderSummary.vue index 7902204ef..d3ecc7984 100644 --- a/src/pages/Order/Card/OrderSummary.vue +++ b/src/pages/Order/Card/OrderSummary.vue @@ -1,14 +1,17 @@ <script setup> -import {computed, ref} from 'vue'; +import { computed, ref } from 'vue'; import { useRoute } from 'vue-router'; import CardSummary from 'components/ui/CardSummary.vue'; import { useI18n } from 'vue-i18n'; import VnLv from 'components/ui/VnLv.vue'; import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; import { toCurrency, toDateHour } from 'src/filters'; +import OrderSearchbar from 'pages/Order/Card/OrderSearchbar.vue'; +import { useStateStore } from 'stores/useStateStore'; const { t } = useI18n(); const route = useRoute(); +const stateStore = useStateStore(); const $props = defineProps({ id: { @@ -44,11 +47,14 @@ const detailsColumns = ref([ name: 'amount', label: t('claim.summary.description'), field: (row) => toCurrency(row?.quantity * row?.price), - } -]) + }, +]); </script> <template> + <Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()"> + <OrderSearchbar /> + </Teleport> <CardSummary ref="summary" :url="`Orders/${entityId}/summary`"> <template #header="{ entity }"> {{ t('order.summary.basket') }} #{{ entity?.id }} - @@ -147,8 +153,12 @@ const detailsColumns = ref([ <p class="header"> {{ t('order.summary.details') }} </p> - <QTable :columns="detailsColumns" :rows="entity?.rows" flat hide-pagination> - + <QTable + :columns="detailsColumns" + :rows="entity?.rows" + flat + hide-pagination + > </QTable> </QCard> </template> diff --git a/src/pages/Order/OrderCatalog.vue b/src/pages/Order/OrderCatalog.vue new file mode 100644 index 000000000..c4fec45a4 --- /dev/null +++ b/src/pages/Order/OrderCatalog.vue @@ -0,0 +1,81 @@ +<script setup> +import { useStateStore } from 'stores/useStateStore'; +import { useRoute } from 'vue-router'; +import { onMounted, onUnmounted } from 'vue'; +import VnPaginate from 'components/ui/VnPaginate.vue'; +import OrderCatalogItem from 'pages/Order/Card/OrderCatalogItem.vue'; +import OrderSearchbar from 'pages/Order/Card/OrderSearchbar.vue'; +import { useI18n } from 'vue-i18n'; + +const route = useRoute(); +const stateStore = useStateStore(); +const { t } = useI18n(); + +onMounted(() => (stateStore.rightDrawer = true)); +onUnmounted(() => (stateStore.rightDrawer = false)); + +const catalogParams = { + orderFk: route.params.id, + orderBy: JSON.stringify({ field: 'relevancy DESC, name', way: 'ASC', isTag: false }), +}; +</script> + +<template> + <Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()"> + <OrderSearchbar + data-key="OrderCatalogList" + url="Orders/CatalogFilter" + :limit="50" + :user-params="catalogParams" + /> + </Teleport> + <Teleport v-if="stateStore.isHeaderMounted()" to="#actions-append"> + <div class="row q-gutter-x-sm"> + <QBtn + flat + @click.stop="stateStore.toggleRightDrawer()" + round + dense + icon="menu" + > + <QTooltip bottom anchor="bottom right"> + {{ t('globals.collapseMenu') }} + </QTooltip> + </QBtn> + </div> + </Teleport> + <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> + <QScrollArea class="fit text-grey-8"></QScrollArea> + </QDrawer> + <QPage class="column items-center q-pa-md"> + <div class="card-list"> + <VnPaginate + data-key="OrderCatalogList" + url="Orders/CatalogFilter" + :limit="50" + :user-params="catalogParams" + auto-load + > + <template #body="{ rows }"> + <div class="catalog-list"> + <OrderCatalogItem v-for="row in rows" :key="row.id" :item="row" /> + </div> + </template> + </VnPaginate> + </div> + </QPage> +</template> + +<style lang="scss"> +.card-list { + width: 100%; +} + +.catalog-list { + display: flex; + align-items: flex-start; + flex-wrap: wrap; + justify-content: center; + gap: 16px; +} +</style> diff --git a/src/router/modules/order.js b/src/router/modules/order.js index ffbc1665e..e91e95493 100644 --- a/src/router/modules/order.js +++ b/src/router/modules/order.js @@ -11,7 +11,7 @@ export default { redirect: { name: 'OrderMain' }, menus: { main: ['OrderList'], - card: ['OrderBasicData'], + card: ['OrderBasicData', 'OrderCatalog'], }, children: [ { @@ -63,6 +63,15 @@ export default { }, component: () => import('src/pages/Order/Card/OrderForm.vue'), }, + { + name: 'OrderCatalog', + path: 'catalog', + meta: { + title: 'catalog', + icon: 'vn:basket', + }, + component: () => import('src/pages/Order/OrderCatalog.vue'), + }, ], }, ],