693 lines
22 KiB
Vue
693 lines
22 KiB
Vue
<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 v-slot: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>
|
|
{{ $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">
|
|
</QSpinner>
|
|
<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
|
|
>
|
|
</QBtn>
|
|
</QCardActions>
|
|
</QCard>
|
|
</div>
|
|
<template v-slot: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 } from 'src/lib/filters.js';
|
|
import { date as qdate } from 'quasar';
|
|
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 };
|
|
},
|
|
data() {
|
|
return {
|
|
uid: 0,
|
|
search: '',
|
|
orderDate: qdate.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 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 myBasket_getAvailable;
|
|
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>
|