forked from verdnatura/hedera-web
Merge pull request 'Catalog view' (!87) from wbuezas/hedera-web-mindshore:feature/Catalog into 4922-vueMigration
Reviewed-on: verdnatura/hedera-web#87
This commit is contained in:
commit
baa1422025
|
@ -28,13 +28,9 @@ const props = defineProps({
|
|||
type: Number,
|
||||
required: true
|
||||
},
|
||||
rounded: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
fullRounded: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
roundedBorders: {
|
||||
type: String,
|
||||
default: 'none'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
|
@ -70,6 +66,17 @@ const showEditForm = ref(false);
|
|||
const url = computed(() => {
|
||||
return `${props.baseURL ?? app.imageUrl}/${props.storage}/${props.size}/${props.id}`;
|
||||
});
|
||||
|
||||
const rounded = computed(() => {
|
||||
const roundedMap = {
|
||||
none: '',
|
||||
default: 'rounded',
|
||||
full: 'full-rounded',
|
||||
top: 'top-rounded',
|
||||
bottom: 'bottom-rounded'
|
||||
};
|
||||
return roundedMap[props.roundedBorders];
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="relative-position main-image-container">
|
||||
|
@ -85,11 +92,7 @@ const url = computed(() => {
|
|||
<QTooltip>{{ t('addOrEditImage') }}</QTooltip>
|
||||
</QBtn>
|
||||
<QImg
|
||||
:class="{
|
||||
zoomIn: props.zoomSize,
|
||||
rounded: props.rounded,
|
||||
'full-rounded': props.fullRounded
|
||||
}"
|
||||
:class="[rounded, { zoomIn: props.zoomSize }]"
|
||||
class="main-image"
|
||||
:src="url"
|
||||
v-bind="$attrs"
|
||||
|
@ -161,9 +164,19 @@ const url = computed(() => {
|
|||
.rounded {
|
||||
border-radius: 0.6em;
|
||||
}
|
||||
|
||||
.full-rounded {
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
.rounded-bottom {
|
||||
border-radius: 0.6em 0.6em 0 0;
|
||||
}
|
||||
|
||||
.rounded-top {
|
||||
border-radius: 0 0 0.6em 0.6em;
|
||||
}
|
||||
|
||||
.img_zoom {
|
||||
border-radius: 0%;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ const props = defineProps({
|
|||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: 'Search'
|
||||
default: ''
|
||||
},
|
||||
sqlQuery: {
|
||||
type: String,
|
||||
|
|
|
@ -103,6 +103,9 @@ export default {
|
|||
user: 'Usuari',
|
||||
password: 'Contrasenya',
|
||||
modify: 'Modificar',
|
||||
shoppingCart: 'Cistella de la compra',
|
||||
available: 'Disponible',
|
||||
minQuantity: 'Quantitat mínima',
|
||||
// Image related translations
|
||||
'Cant lock cache': 'No es pot bloquejar la memòria cau',
|
||||
'Bad file format': 'Format de fitxer no reconegut',
|
||||
|
|
|
@ -136,6 +136,9 @@ export default {
|
|||
remindMe: 'Remember me',
|
||||
password: 'Password',
|
||||
modify: 'Modify',
|
||||
shoppingCart: 'Shopping cart',
|
||||
available: 'Available',
|
||||
minQuantity: 'Minimum quantity',
|
||||
// Image related translations
|
||||
'Cant lock cache': 'The cache could not be blocked',
|
||||
'Bad file format': 'Unrecognized file format',
|
||||
|
|
|
@ -135,6 +135,9 @@ export default {
|
|||
cancel: 'Cancelar',
|
||||
of: 'de',
|
||||
modify: 'Modificar',
|
||||
shoppingCart: 'Cesta de la compra',
|
||||
available: 'Disponible',
|
||||
minQuantity: 'Cantidad mínima',
|
||||
// Image related translations
|
||||
'Cant lock cache': 'La caché no pudo ser bloqueada',
|
||||
'Bad file format': 'Formato de archivo no reconocido',
|
||||
|
|
|
@ -103,6 +103,9 @@ export default {
|
|||
user: 'Utilisateur',
|
||||
password: 'Mot de passe',
|
||||
modify: 'Modifier',
|
||||
shoppingCart: 'Panier',
|
||||
available: 'Disponible',
|
||||
minQuantity: 'Quantité minimum',
|
||||
// Image related translations
|
||||
'Cant lock cache': "Le cache n'a pas pu être verrouillé",
|
||||
'Bad file format': 'Format de fichier non reconnu',
|
||||
|
|
|
@ -101,6 +101,9 @@ export default {
|
|||
user: 'Utilizador',
|
||||
password: 'Senha',
|
||||
modify: 'Modificar',
|
||||
shoppingCart: 'Cesta da compra',
|
||||
available: 'Disponível',
|
||||
minQuantity: 'Quantidade mínima',
|
||||
// Image related translations
|
||||
'Cant lock cache': 'O cache não pôde ser bloqueado',
|
||||
'Bad file format': 'Formato de arquivo inválido',
|
||||
|
|
|
@ -104,10 +104,7 @@ onMounted(async () => {
|
|||
</QBtn>
|
||||
</Teleport>
|
||||
<QPage class="vn-w-sm">
|
||||
<QList
|
||||
class="rounded-borders shadow-1 shadow-transition"
|
||||
separator
|
||||
>
|
||||
<QList class="rounded-borders shadow-1 shadow-transition" separator>
|
||||
<CardList
|
||||
v-for="(address, index) in addresses"
|
||||
:key="index"
|
||||
|
|
|
@ -33,9 +33,9 @@ const onSearch = data => (items.value = data || []);
|
|||
<template>
|
||||
<Teleport v-if="isHeaderMounted" to="#actions">
|
||||
<VnSearchBar
|
||||
:sqlQuery="query"
|
||||
@onSearch="onSearch"
|
||||
@onSearchError="items = []"
|
||||
:sql-query="query"
|
||||
@on-search="onSearch"
|
||||
@on-search-error="items = []"
|
||||
/>
|
||||
</Teleport>
|
||||
<QPage class="vn-w-xs">
|
||||
|
@ -66,8 +66,8 @@ const onSearch = data => (items.value = data || []);
|
|||
class="q-mr-md"
|
||||
rounded
|
||||
editable
|
||||
editSchema="catalog"
|
||||
:editImageName="item.image"
|
||||
edit-schema="catalog"
|
||||
:edit-image-name="item.image"
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
|
|
|
@ -1,706 +0,0 @@
|
|||
<template>
|
||||
<Teleport v-if="isHeaderMounted" to="#actions">
|
||||
<QInput
|
||||
:placeholder="$t('search')"
|
||||
v-model="search"
|
||||
debounce="500"
|
||||
class="search q-mr-sm"
|
||||
rounded
|
||||
dark
|
||||
dense
|
||||
standout
|
||||
>
|
||||
<template #prepend>
|
||||
<QIcon v-if="search === ''" name="search" />
|
||||
<QIcon
|
||||
v-else
|
||||
name="clear"
|
||||
class="cursor-pointer"
|
||||
@click="search = ''"
|
||||
/>
|
||||
</template>
|
||||
</QInput>
|
||||
<QBtn
|
||||
:icon="$t(viewMode == 'list' ? 'view_list' : 'grid_on')"
|
||||
:label="$t(viewMode == 'list' ? 'listView' : 'gridView')"
|
||||
@click="onViewModeClick()"
|
||||
rounded
|
||||
no-caps
|
||||
/>
|
||||
</Teleport>
|
||||
<div style="padding-bottom: 5em">
|
||||
<QDrawer v-model="$app.rightDrawerOpen" side="right" :width="250">
|
||||
<div class="q-pa-md">
|
||||
<div class="basket-info">
|
||||
<p>{{ date(new Date()) }}</p>
|
||||
<p>
|
||||
{{ $t('warehouse') }}
|
||||
{{ 'Algemesi' }}
|
||||
</p>
|
||||
<QBtn
|
||||
flat
|
||||
rounded
|
||||
no-caps
|
||||
:to="{
|
||||
name: 'checkout',
|
||||
params: { id: appStore.basketOrderId },
|
||||
query: { continue: 'catalog' }
|
||||
}"
|
||||
>
|
||||
{{ $t('modify') }}
|
||||
</QBtn>
|
||||
</div>
|
||||
<div class="q-mt-md">
|
||||
<div class="q-mb-xs text-grey-7">
|
||||
{{ $t('category') }}
|
||||
<QIcon
|
||||
v-if="category"
|
||||
style="font-size: 1.3em"
|
||||
name="cancel"
|
||||
class="cursor-pointer"
|
||||
:title="$t('deleteFilter')"
|
||||
@click="
|
||||
$router.push({ params: { category: null } })
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="categories">
|
||||
<QBtn
|
||||
flat
|
||||
round
|
||||
class="category q-pa-sm"
|
||||
v-for="cat in categories"
|
||||
:class="{ active: category == cat.id }"
|
||||
:key="cat.id"
|
||||
:title="cat.name"
|
||||
:to="{ params: { category: cat.id, type: null } }"
|
||||
>
|
||||
<img :src="`statics/category/${cat.code}.svg`" />
|
||||
</QBtn>
|
||||
</div>
|
||||
</div>
|
||||
<div class="q-mt-md" v-if="category || search">
|
||||
<div class="q-mb-xs text-grey-7">
|
||||
{{ $t('filterBy') }}
|
||||
</div>
|
||||
<QSelect
|
||||
v-model="type"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
:options="types"
|
||||
:disable="!category"
|
||||
clearable
|
||||
:label="$t('family')"
|
||||
@filter="filterType"
|
||||
@input="
|
||||
$router.push({ params: { type: type && type.id } })
|
||||
"
|
||||
/>
|
||||
<QSelect
|
||||
v-model="order"
|
||||
input-debounce="0"
|
||||
:options="orderOptions"
|
||||
:label="$t('orderBy')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="q-pa-md" v-if="typeId || search">
|
||||
<div class="q-mb-md" v-for="tag in tags" :key="tag.uid">
|
||||
<div class="q-mb-xs text-caption text-grey-7">
|
||||
{{ tag.name }}
|
||||
<QIcon
|
||||
v-if="tag.hasFilter"
|
||||
style="font-size: 1.3em"
|
||||
name="cancel"
|
||||
:title="$t('deleteFilter')"
|
||||
class="cursor-pointer"
|
||||
@click="onResetTagFilterClick(tag)"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="!tag.useRange">
|
||||
<div
|
||||
v-for="value in tag.values.slice(0, tag.showCount)"
|
||||
:key="value"
|
||||
>
|
||||
<QCheckbox
|
||||
v-model="tag.filter"
|
||||
:dense="true"
|
||||
:val="value"
|
||||
:label="value"
|
||||
@input="onCheck(tag)"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="tag.values.length > tag.showCount">
|
||||
<span
|
||||
class="cursor-pointer text-blue"
|
||||
@click="tag.showCount = Infinity"
|
||||
>
|
||||
<QIcon name="keyboard_arrow_down" />
|
||||
{{ $t('viewMore') }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="tag.showCount == Infinity">
|
||||
<span
|
||||
class="cursor-pointer text-blue"
|
||||
@click="tag.showCount = tag.initialCount"
|
||||
>
|
||||
<QIcon name="keyboard_arrow_up" />
|
||||
{{ $t('viewLess') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="q-mx-md">
|
||||
<QRange
|
||||
class="q-mt-lg"
|
||||
v-if="tag.useRange"
|
||||
v-model="tag.filter"
|
||||
:min="tag.min"
|
||||
:max="tag.max"
|
||||
:step="tag.step"
|
||||
:color="tag.hasFilter ? 'primary' : 'grey-6'"
|
||||
@input="onRangeChange(tag, true)"
|
||||
@change="onRangeChange(tag)"
|
||||
label-always
|
||||
markers
|
||||
snap
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</QDrawer>
|
||||
<QInfiniteScroll
|
||||
@load="onLoad"
|
||||
scroll-taget="html"
|
||||
:offset="800"
|
||||
:disable="disableScroll"
|
||||
>
|
||||
<div class="q-pa-md row justify-center q-gutter-md">
|
||||
<QSpinner v-if="isLoading" color="primary" size="50px" />
|
||||
<div
|
||||
v-if="items && !items.length"
|
||||
class="text-subtitle1 text-grey-7 q-pa-md"
|
||||
>
|
||||
{{ $t('noItemsFound') }}
|
||||
</div>
|
||||
<div
|
||||
v-if="!items && !isLoading"
|
||||
class="text-subtitle1 text-grey-7 q-pa-md"
|
||||
>
|
||||
{{ $t('pleaseSetFilter') }}
|
||||
</div>
|
||||
<QCard class="my-card" v-for="_item in items" :key="_item.id">
|
||||
<img
|
||||
:src="`${$imageBase}/catalog/200x200/${_item.image}`"
|
||||
/>
|
||||
<QCardSection>
|
||||
<div class="name text-subtitle1">
|
||||
{{ _item.longName }}
|
||||
</div>
|
||||
<div
|
||||
class="sub-name text-uppercase text-subtitle1 text-grey-7 ellipsize q-pt-xs"
|
||||
>
|
||||
{{ _item.subName }}
|
||||
</div>
|
||||
<div class="tags q-pt-xs">
|
||||
<div v-for="tag in _item.tags" :key="tag.tagFk">
|
||||
<span class="text-grey-7">{{
|
||||
tag.tag.name
|
||||
}}</span>
|
||||
{{ tag.value }}
|
||||
</div>
|
||||
</div>
|
||||
</QCardSection>
|
||||
<QCardActions class="actions justify-between">
|
||||
<div class="q-pl-sm">
|
||||
<span class="available bg-green text-white">{{
|
||||
_item.available
|
||||
}}</span>
|
||||
{{ $t('from') }}
|
||||
<span class="price">{{
|
||||
currency(_item.buy?.price3)
|
||||
}}</span>
|
||||
</div>
|
||||
<QBtn
|
||||
icon="add_shopping_cart"
|
||||
:title="$t('buy')"
|
||||
@click="showItem(_item)"
|
||||
flat
|
||||
/>
|
||||
</QCardActions>
|
||||
</QCard>
|
||||
</div>
|
||||
<template #loading>
|
||||
<div class="row justify-center q-my-md">
|
||||
<QSpinner color="primary" name="dots" size="40px" />
|
||||
</div>
|
||||
</template>
|
||||
</QInfiniteScroll>
|
||||
<QDialog v-model="showItemDialog">
|
||||
<QCard style="width: 25em">
|
||||
<QImg
|
||||
:src="`${$imageBase}/catalog/200x200/${item.image}`"
|
||||
:ratio="5 / 3"
|
||||
>
|
||||
<div class="absolute-bottom text-center q-pa-xs">
|
||||
<div class="text-subtitle1">
|
||||
{{ item.longName }}
|
||||
</div>
|
||||
</div>
|
||||
</QImg>
|
||||
<QCardSection>
|
||||
<div
|
||||
class="text-uppercase text-subtitle1 text-grey-7 ellipsize"
|
||||
>
|
||||
{{ item.subName }}
|
||||
</div>
|
||||
<div class="text-grey-7">#{{ item.id }}</div>
|
||||
</QCardSection>
|
||||
<QCardSection>
|
||||
<div v-for="tag in item.tags" :key="tag.tagFk">
|
||||
<span class="text-grey-7">{{ tag.tag.name }}</span>
|
||||
{{ tag.value }}
|
||||
</div>
|
||||
</QCardSection>
|
||||
<QCardActions align="right">
|
||||
<QBtn @click="showItemDialog = false" flat>
|
||||
{{ $t('cancel') }}
|
||||
</QBtn>
|
||||
<QBtn @click="showItemDialog = false" flat>
|
||||
{{ $t('accept') }}
|
||||
</QBtn>
|
||||
</QCardActions>
|
||||
</QCard>
|
||||
</QDialog>
|
||||
<QPageSticky>
|
||||
<QBtn
|
||||
fab
|
||||
to="/ecomerce/basket"
|
||||
icon="shopping_cart"
|
||||
color="accent"
|
||||
:title="$t('shoppingCart')"
|
||||
/>
|
||||
</QPageSticky>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search {
|
||||
max-width: 250px;
|
||||
}
|
||||
.basket-info {
|
||||
background-color: #8cc63f;
|
||||
color: white;
|
||||
padding: 17px 28px;
|
||||
border-radius: 7px;
|
||||
text-align: center;
|
||||
|
||||
& > p {
|
||||
margin: 0;
|
||||
padding: 0.4em 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
.categories {
|
||||
margin: 0 auto;
|
||||
width: 220px;
|
||||
|
||||
.category {
|
||||
width: 55px;
|
||||
|
||||
&.active {
|
||||
background: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
& > img {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tags {
|
||||
max-height: 4.6em;
|
||||
overflow: hidden;
|
||||
}
|
||||
.available {
|
||||
padding: 0.15em;
|
||||
border-radius: 0.2em;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
.price {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
.my-card {
|
||||
width: 100%;
|
||||
max-width: 17.5em;
|
||||
height: 32.5em;
|
||||
overflow: hidden;
|
||||
.name,
|
||||
.sub-name {
|
||||
line-height: 1.3em;
|
||||
}
|
||||
.ellipsize {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.description {
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { date, currency, formatDate } from 'src/lib/filters.js';
|
||||
import axios from 'axios';
|
||||
import { useAppStore } from 'stores/app';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
const CancelToken = axios.CancelToken;
|
||||
|
||||
export default {
|
||||
name: 'HederaCatalog',
|
||||
setup() {
|
||||
const appStore = useAppStore();
|
||||
const { isHeaderMounted } = storeToRefs(appStore);
|
||||
return { isHeaderMounted, appStore };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
uid: 0,
|
||||
search: '',
|
||||
orderDate: formatDate(new Date(), 'YYYY/MM/DD'),
|
||||
category: null,
|
||||
categories: [],
|
||||
type: null,
|
||||
typeId: null,
|
||||
types: [],
|
||||
orgTypes: [],
|
||||
item: {},
|
||||
showItemDialog: false,
|
||||
tags: [],
|
||||
isLoading: false,
|
||||
items: null,
|
||||
limit: null,
|
||||
pageSize: 30,
|
||||
maxTags: 5,
|
||||
disableScroll: true,
|
||||
viewMode: 'list',
|
||||
order: {
|
||||
label: this.$t('relevancy'),
|
||||
value: 'relevancy DESC, longName'
|
||||
},
|
||||
orderOptions: [
|
||||
{
|
||||
label: this.$t('relevancy'),
|
||||
value: 'relevancy DESC, longName'
|
||||
},
|
||||
{
|
||||
label: this.$t('name'),
|
||||
value: 'longName'
|
||||
},
|
||||
{
|
||||
label: this.$t('siceAsc'),
|
||||
value: 'size ASC'
|
||||
},
|
||||
{
|
||||
label: this.$t('sizeDesc'),
|
||||
value: 'size DESC'
|
||||
},
|
||||
{
|
||||
label: this.$t('priceAsc'),
|
||||
value: 'price ASC'
|
||||
},
|
||||
{
|
||||
label: this.$t('priceDesc'),
|
||||
value: 'price DESC'
|
||||
},
|
||||
{
|
||||
label: this.$t('available'),
|
||||
value: 'available'
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.$app.useRightDrawer = true;
|
||||
},
|
||||
async beforeMount() {
|
||||
const isGuest = false; // TODO: Integrate isGuest logic
|
||||
if (!isGuest) {
|
||||
this.appStore.check('catalog');
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.categories = await this.$jApi.query(
|
||||
`SELECT c.id, l.name, c.color, c.code
|
||||
FROM vn.itemCategory c
|
||||
JOIN vn.itemCategoryL10n l ON l.id = c.id
|
||||
WHERE c.display
|
||||
ORDER BY display`
|
||||
);
|
||||
this.onRouteChange(this.$route);
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.clearTimeoutAndRequest();
|
||||
},
|
||||
beforeRouteUpdate(to, from, next) {
|
||||
this.onRouteChange(to);
|
||||
next();
|
||||
},
|
||||
watch: {
|
||||
categories() {
|
||||
this.refreshTitle();
|
||||
},
|
||||
orgTypes() {
|
||||
this.refreshTitle();
|
||||
},
|
||||
order() {
|
||||
this.loadItems();
|
||||
},
|
||||
date() {
|
||||
this.loadItems();
|
||||
},
|
||||
async category(value) {
|
||||
this.orgTypes = [];
|
||||
if (!value) return;
|
||||
|
||||
const res = await this.$jApi.execQuery(
|
||||
`CALL myOrder_getAvailable(${this.appStore.basketOrderId});
|
||||
SELECT DISTINCT t.id, l.name
|
||||
FROM vn.item i
|
||||
JOIN vn.itemType t ON t.id = i.typeFk
|
||||
JOIN tmp.itemAvailable a ON a.id = i.id
|
||||
JOIN vn.itemTypeL10n l ON l.id = t.id
|
||||
WHERE t.\`order\` >= 0
|
||||
AND t.categoryFk = #category
|
||||
ORDER BY t.\`order\`, l.name;
|
||||
DROP TEMPORARY TABLE tmp.itemAvailable;`,
|
||||
{ category: value }
|
||||
);
|
||||
res.fetch();
|
||||
this.orgTypes = res.fetchData();
|
||||
},
|
||||
search(value) {
|
||||
const location = { params: this.$route.params };
|
||||
if (value) location.query = { search: value };
|
||||
this.$router.push(location);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
date,
|
||||
currency,
|
||||
onViewModeClick() {
|
||||
this.viewMode = this.viewMode === 'list' ? 'grid' : 'list';
|
||||
},
|
||||
onRouteChange(route) {
|
||||
let { category, type } = route.params;
|
||||
|
||||
category = parseInt(category) || null;
|
||||
type = parseInt(type) || null;
|
||||
|
||||
this.category = category;
|
||||
this.typeId = category ? type : null;
|
||||
this.search = route.query.search || '';
|
||||
this.tags = [];
|
||||
|
||||
this.refreshTitle();
|
||||
this.loadItems();
|
||||
},
|
||||
refreshTitle() {
|
||||
let title = this.$t(this.$router.currentRoute.value.name);
|
||||
let subtitle;
|
||||
|
||||
if (this.category) {
|
||||
const category =
|
||||
this.categories.find(i => i.id === this.category) || {};
|
||||
title = category.name;
|
||||
}
|
||||
|
||||
if (this.typeId) {
|
||||
this.type = this.orgTypes.find(i => i.id === this.typeId);
|
||||
subtitle = title;
|
||||
title = this.type && this.type.name;
|
||||
} else {
|
||||
this.type = null;
|
||||
}
|
||||
|
||||
this.$app.$patch({ title, subtitle });
|
||||
},
|
||||
clearTimeoutAndRequest() {
|
||||
if (this.timeout) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
if (this.source) {
|
||||
this.source.cancel();
|
||||
this.source = null;
|
||||
}
|
||||
},
|
||||
loadItemsDelayed() {
|
||||
this.clearTimeoutAndRequest();
|
||||
this.timeout = setTimeout(() => this.loadItems(), 500);
|
||||
},
|
||||
loadItems() {
|
||||
this.items = null;
|
||||
this.isLoading = true;
|
||||
this.limit = this.pageSize;
|
||||
this.disableScroll = false;
|
||||
this.isLoading = false;
|
||||
// this.loadItemsBase().finally(() => (this.isLoading = false))
|
||||
},
|
||||
onLoad(index, done) {
|
||||
if (this.isLoading) return done();
|
||||
this.limit += this.pageSize;
|
||||
done();
|
||||
// this.loadItemsBase().finally(done)
|
||||
},
|
||||
loadItemsBase() {
|
||||
this.clearTimeoutAndRequest();
|
||||
|
||||
if (!(this.category || this.typeId || this.search)) {
|
||||
this.tags = [];
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
const tagFilter = [];
|
||||
|
||||
for (const tag of this.tags) {
|
||||
if (tag.hasFilter) {
|
||||
tagFilter.push({
|
||||
tagFk: tag.id,
|
||||
values: tag.filter
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.source = CancelToken.source();
|
||||
|
||||
const params = {
|
||||
dated: this.orderDate,
|
||||
typeFk: this.typeId,
|
||||
categoryFk: this.category,
|
||||
search: this.search,
|
||||
order: this.order.value,
|
||||
limit: this.limit,
|
||||
tagFilter
|
||||
};
|
||||
const config = {
|
||||
params,
|
||||
cancelToken: this.source.token
|
||||
};
|
||||
return this.$axios
|
||||
.get('Items/catalog', config)
|
||||
.then(res => this.onItemsGet(res))
|
||||
.catch(err => this.onItemsError(err))
|
||||
.finally(() => (this.cancel = null));
|
||||
},
|
||||
onItemsError(err) {
|
||||
if (err.__CANCEL__) return;
|
||||
this.disableScroll = true;
|
||||
throw err;
|
||||
},
|
||||
onItemsGet(res) {
|
||||
for (const tag of res.data.tags) {
|
||||
tag.uid = this.uid++;
|
||||
|
||||
if (tag.filter) {
|
||||
tag.hasFilter = true;
|
||||
tag.useRange = tag.filter.max || tag.filter.min;
|
||||
} else {
|
||||
tag.useRange =
|
||||
tag.isQuantitative && tag.values.length > this.maxTags;
|
||||
this.resetTagFilter(tag);
|
||||
}
|
||||
|
||||
if (tag.values) {
|
||||
tag.initialCount = this.maxTags;
|
||||
if (Array.isArray(tag.filter)) {
|
||||
tag.initialCount = Math.max(
|
||||
tag.initialCount,
|
||||
tag.filter.length
|
||||
);
|
||||
}
|
||||
tag.showCount = tag.initialCount;
|
||||
}
|
||||
}
|
||||
|
||||
this.items = res.data.items;
|
||||
this.tags = res.data.tags;
|
||||
this.disableScroll = this.items.length < this.limit;
|
||||
},
|
||||
onRangeChange(tag, delay) {
|
||||
tag.hasFilter = true;
|
||||
|
||||
if (!delay) this.loadItems();
|
||||
else this.loadItemsDelayed();
|
||||
},
|
||||
onCheck(tag) {
|
||||
tag.hasFilter = tag.filter.length > 0;
|
||||
this.loadItems();
|
||||
},
|
||||
resetTagFilter(tag) {
|
||||
tag.hasFilter = false;
|
||||
|
||||
if (tag.useRange) {
|
||||
tag.filter = {
|
||||
min: tag.min,
|
||||
max: tag.max
|
||||
};
|
||||
} else {
|
||||
tag.filter = [];
|
||||
}
|
||||
},
|
||||
onResetTagFilterClick(tag) {
|
||||
this.resetTagFilter(tag);
|
||||
this.loadItems();
|
||||
},
|
||||
filterType(val, update) {
|
||||
if (val === '') {
|
||||
update(() => {
|
||||
this.types = this.orgTypes;
|
||||
});
|
||||
} else {
|
||||
update(() => {
|
||||
const needle = val.toLowerCase();
|
||||
this.types = this.orgTypes.filter(
|
||||
type => type.name.toLowerCase().indexOf(needle) > -1
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
showItem(item) {
|
||||
this.item = item;
|
||||
this.showItemDialog = true;
|
||||
|
||||
const conf = this.$state.catalogConfig;
|
||||
const params = {
|
||||
dated: this.orderDate,
|
||||
addressFk: conf.addressFk,
|
||||
agencyModeFk: conf.agencyModeFk
|
||||
};
|
||||
this.$axios
|
||||
.get(`Items/${item.id}/calcCatalog`, { params })
|
||||
.then(res => (this.lots = res.data));
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
es-ES:
|
||||
gridView: Vista de rejilla
|
||||
listView: Vista de lista
|
||||
shoppingCart: Cesta de la compra
|
||||
warehouse: Almacén
|
||||
agency: Agencia
|
||||
modify: Modificar
|
||||
category: Categoría
|
||||
deleteFilter: Quitar filtro
|
||||
filterBy: Filtrar por
|
||||
family: Familia
|
||||
orderBy: Ordernar por
|
||||
pleaseSetFilter: Elige un filtro en el menú de la derecha
|
||||
search: Buscar
|
||||
</i18n>
|
|
@ -0,0 +1,207 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import VnImg from 'src/components/ui/VnImg.vue';
|
||||
import CardList from 'src/components/ui/CardList.vue';
|
||||
|
||||
import { currency } from 'src/lib/filters.js';
|
||||
|
||||
defineProps({
|
||||
item: { type: Object, default: () => {} },
|
||||
viewMode: { type: String, default: 'grid' }
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QCard v-if="viewMode === 'grid'" v-ripple class="catalog-card">
|
||||
<VnImg
|
||||
storage="catalog"
|
||||
size="200x200"
|
||||
:id="item.image"
|
||||
height="210px"
|
||||
rounded="bottom"
|
||||
/>
|
||||
<div class="column" style="height: 205px; padding: 10px">
|
||||
<div class="column" style="margin-bottom: auto">
|
||||
<div class="text-subtitle2 ellipsis-2-lines">
|
||||
{{ item.item }}
|
||||
</div>
|
||||
<div
|
||||
class="row justify-between text-uppercase text-subtitle1 text-grey-7"
|
||||
>
|
||||
<span class="text-subtitle2">
|
||||
{{ item.subName }}
|
||||
</span>
|
||||
<span> #{{ item.id }}</span>
|
||||
</div>
|
||||
<div class="tags q-pt-xs text-caption">
|
||||
<div
|
||||
v-for="(tag, index) in item.previewTags"
|
||||
:key="index"
|
||||
class="full-width row"
|
||||
>
|
||||
<span class="text-grey-7 col">
|
||||
{{ tag.name }}
|
||||
</span>
|
||||
<span class="col ellipsis">{{ tag.value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="item.minQuantity" class="row justify-end">
|
||||
<QIcon
|
||||
name="production_quantity_limits"
|
||||
size="xs"
|
||||
color="negative"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('minQuantity') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
<span class="text-negative text-caption">{{
|
||||
item.minQuantity
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-between items-cente q-gutter-x-xs">
|
||||
<QBadge
|
||||
:label="`x${item.grouping}`"
|
||||
color="grey"
|
||||
class="col-2 justify-end text-body2"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('minGrouping') }}
|
||||
</QTooltip>
|
||||
</QBadge>
|
||||
<QBadge
|
||||
outline
|
||||
:label="item.available"
|
||||
color="accent"
|
||||
text-color="black"
|
||||
class="col justify-end text-body2"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('available') }}
|
||||
</QTooltip>
|
||||
</QBadge>
|
||||
<QBadge
|
||||
outline
|
||||
:label="currency(item.price)"
|
||||
color="accent"
|
||||
text-color="black"
|
||||
class="col justify-end text-body2"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('groupingPrice') }}
|
||||
</QTooltip>
|
||||
</QBadge>
|
||||
</div>
|
||||
</div>
|
||||
</QCard>
|
||||
<CardList v-else class="vn-w-sm">
|
||||
<template #prepend>
|
||||
<VnImg
|
||||
storage="catalog"
|
||||
size="200x200"
|
||||
:id="item.image"
|
||||
width="105px"
|
||||
height="105px"
|
||||
rounded-borders="full"
|
||||
class="q-mr-md"
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<span class="ellipsis-2-lines">
|
||||
{{ item.item }}
|
||||
</span>
|
||||
<div class="row justify-between text-uppercase text-grey-7">
|
||||
<span>{{ item.subName }}</span>
|
||||
<span>#{{ item.id }}</span>
|
||||
</div>
|
||||
<div class="full-width row">
|
||||
<span
|
||||
v-for="(tag, index) in item.previewTags"
|
||||
:key="index"
|
||||
class="text-grey-7 text-caption q-mr-sm"
|
||||
>
|
||||
{{ tag.value }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="item.minQuantity" class="row justify-end">
|
||||
<QIcon
|
||||
name="production_quantity_limits"
|
||||
size="xs"
|
||||
color="negative"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('minQuantity') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
<span class="text-negative text-caption">{{
|
||||
item.minQuantity
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="row justify-end items-center q-gutter-x-xs q-mt-sm">
|
||||
<QBadge
|
||||
:label="`x${item.grouping}`"
|
||||
color="grey"
|
||||
class="col-2 justify-end text-body2"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('minGrouping') }}
|
||||
</QTooltip>
|
||||
</QBadge>
|
||||
<QBadge
|
||||
outline
|
||||
:label="item.available"
|
||||
color="accent"
|
||||
text-color="black"
|
||||
class="col-3 justify-end text-body2"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('available') }}
|
||||
</QTooltip>
|
||||
</QBadge>
|
||||
<QBadge
|
||||
outline
|
||||
:label="currency(item.price)"
|
||||
color="accent"
|
||||
text-color="black"
|
||||
class="col-3 justify-end text-body2"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('groupingPrice') }}
|
||||
</QTooltip>
|
||||
</QBadge>
|
||||
</div>
|
||||
</template>
|
||||
</CardList>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.catalog-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 210px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n lang="yaml">
|
||||
en-US:
|
||||
groupingPrice: Price per group
|
||||
minGrouping: Minimum packing
|
||||
es-ES:
|
||||
groupingPrice: Precio por grupo
|
||||
minGrouping: Cantidad mínima
|
||||
ca-ES:
|
||||
groupingPrice: Preu per grup
|
||||
minGrouping: Empaquetament mínim
|
||||
fr-FR:
|
||||
groupingPrice: Prix par groupe
|
||||
minGrouping: Emballage minimum
|
||||
pt-PT:
|
||||
groupingPrice: Preço por grupo
|
||||
minGrouping: Embalagem mínima
|
||||
</i18n>
|
File diff suppressed because it is too large
Load Diff
|
@ -174,7 +174,6 @@ en-US:
|
|||
startOrder: Start order
|
||||
noOrdersFound: No orders found
|
||||
makePayment: Make payment
|
||||
shoppingCart: Shopping cart
|
||||
balance: 'Balance:'
|
||||
paymentInfo: >-
|
||||
The amount shown is your slope (negative) or favorable balance today, it
|
||||
|
@ -187,7 +186,6 @@ es-ES:
|
|||
startOrder: Empezar pedido
|
||||
noOrdersFound: No se encontrado pedidos
|
||||
makePayment: Realizar pago
|
||||
shoppingCart: Cesta de la compra
|
||||
balance: 'Saldo:'
|
||||
paymentInfo: >-
|
||||
La cantidad mostrada es tu saldo pendiente (negativa) o favorable a día de
|
||||
|
@ -201,7 +199,6 @@ ca-ES:
|
|||
startOrder: Començar encàrrec
|
||||
noOrdersFound: No s'han trobat comandes
|
||||
makePayment: Realitzar pagament
|
||||
shoppingCart: Cistella de la compra
|
||||
balance: 'Saldo:'
|
||||
paymentInfo: >-
|
||||
La quantitat mostrada és el teu saldo pendent (negatiu) o favorable a dia
|
||||
|
@ -215,7 +212,6 @@ fr-FR:
|
|||
startOrder: Acheter
|
||||
noOrdersFound: Aucune commande trouvée
|
||||
makePayment: Effectuer un paiement
|
||||
shoppingCart: Panier
|
||||
balance: 'Balance:'
|
||||
paymentInfo: >-
|
||||
Le montant indiqué est votre pente (négative) ou balance favorable
|
||||
|
@ -229,7 +225,6 @@ pt-PT:
|
|||
startOrder: Iniciar encomenda
|
||||
noOrdersFound: Nenhum pedido encontrado
|
||||
makePayment: Realizar pagamento
|
||||
shoppingCart: Cesta da compra
|
||||
balance: 'Saldo:'
|
||||
paymentInfo: >-
|
||||
A quantidade mostrada é seu saldo pendente (negativo) ou favorável a dia de
|
||||
|
|
|
@ -66,7 +66,7 @@ const deleteRow = id => {
|
|||
<template>
|
||||
<QCard class="vn-w-sm" style="padding: 32px">
|
||||
<QCardSection class="no-padding q-mb-md">
|
||||
<div class="text-h6">#{{ ticket.id }}</div>
|
||||
<div class="text-h6 text-bold">#{{ ticket.id }}</div>
|
||||
</QCardSection>
|
||||
<QCardSection class="no-padding q-mb-md q-gutter-y-xs">
|
||||
<div class="text-subtitle1 text-bold">
|
||||
|
|
|
@ -41,12 +41,23 @@ onMounted(() => {
|
|||
password.value.focus();
|
||||
}
|
||||
});
|
||||
async function onLogin() {
|
||||
await userStore.login(email.value, password.value, remember.value);
|
||||
|
||||
const onLogin = async () => {
|
||||
await userStore.fetchUser();
|
||||
await userStore.updateUserLang(selectedLocaleValue.value);
|
||||
await router.push('/');
|
||||
}
|
||||
};
|
||||
|
||||
const login = async () => {
|
||||
await userStore.login(email.value, password.value, remember.value);
|
||||
await onLogin();
|
||||
};
|
||||
|
||||
const loginAsGuest = async () => {
|
||||
userStore.isGuest = true;
|
||||
localStorage.setItem('hederaGuest', true);
|
||||
await onLogin();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -56,7 +67,7 @@ async function onLogin() {
|
|||
<img src="statics/logo.svg" alt="Verdnatura" class="block" />
|
||||
</router-link>
|
||||
</div>
|
||||
<QForm @submit="onLogin" class="q-gutter-y-md">
|
||||
<QForm @submit="login()" class="q-gutter-y-md">
|
||||
<div class="q-gutter-y-sm">
|
||||
<QInput v-model="email" :label="$t('user')" autofocus />
|
||||
<QInput
|
||||
|
@ -106,7 +117,7 @@ async function onLogin() {
|
|||
</div>
|
||||
<div class="justify-center">
|
||||
<QBtn
|
||||
to="/"
|
||||
@click="loginAsGuest()"
|
||||
:label="$t('logInAsGuest')"
|
||||
class="full-width"
|
||||
color="primary"
|
||||
|
|
|
@ -75,7 +75,7 @@ const routes = [
|
|||
meta: {
|
||||
title: 'Catalog'
|
||||
},
|
||||
component: () => import('pages/Ecomerce/Catalog.vue')
|
||||
component: () => import('pages/Ecomerce/CatalogView.vue')
|
||||
},
|
||||
{
|
||||
name: 'basket',
|
||||
|
|
|
@ -139,6 +139,11 @@ export const useAppStore = defineStore('hedera', {
|
|||
unloadOrder() {
|
||||
localStorage.removeItem(storageOrderName);
|
||||
this.basketOrderId = null;
|
||||
},
|
||||
|
||||
onLogout() {
|
||||
this.unloadOrder();
|
||||
this.menuEssentialLinks = [];
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
|
|
|
@ -2,6 +2,7 @@ import { defineStore } from 'pinia';
|
|||
import { api, jApi } from 'boot/axios';
|
||||
import { i18n } from 'src/boot/i18n';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import { useAppStore } from 'src/stores/app.js';
|
||||
|
||||
const { t } = i18n.global;
|
||||
const { notify } = useNotify();
|
||||
|
@ -35,6 +36,7 @@ export const useUserStore = defineStore('user', {
|
|||
|
||||
actions: {
|
||||
async init() {
|
||||
this.isGuest = localStorage.getItem('hederaGuest') || false;
|
||||
await this.fetchUser();
|
||||
await this.supplantInit();
|
||||
this.updateSiteLocale();
|
||||
|
@ -71,6 +73,8 @@ export const useUserStore = defineStore('user', {
|
|||
sessionStorage.removeItem('vnToken');
|
||||
}
|
||||
this.$reset();
|
||||
localStorage.removeItem('hederaGuest');
|
||||
useAppStore().onLogout();
|
||||
},
|
||||
|
||||
async fetchUser(userType = 'user') {
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
export default function debounce(callback, delay) {
|
||||
let timeoutId;
|
||||
return (...args) => {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => {
|
||||
// eslint-disable-next-line
|
||||
callback(...args);
|
||||
}, delay);
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue