forked from verdnatura/salix-front
Create order catalog section
This commit is contained in:
parent
42c8e8c80f
commit
a65db2f4cf
|
@ -555,6 +555,7 @@ export default {
|
|||
create: 'Create',
|
||||
summary: 'Summary',
|
||||
basicData: 'Basic Data',
|
||||
catalog: 'Catalog',
|
||||
},
|
||||
field: {
|
||||
salesPersonFk: 'Sales Person',
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -6,9 +6,12 @@ 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>
|
||||
|
|
|
@ -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>
|
|
@ -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'),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue