hedera-web/src/pages/Catalog.vue

450 lines
12 KiB
Vue

<template>
<div>
<q-drawer
v-model="rightDrawerOpen"
content-class="bg-grey-2"
side="right"
elevated>
<div class="q-pa-md">
<div class="q-mb-xs text-caption text-grey-7">
{{$t('category')}}
<q-icon
v-if="category"
style="font-size: 1.3em;"
name="cancel"
class="cursor-pointer"
:title="$t('deleteFilter')"
:to="{params: {category: null}}"
/>
</div>
<div>
<q-btn
flat
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}}">
<img
class="category-img"
:src="`statics/category/${cat.id}.svg`">
</q-btn>
</div>
<q-select
v-model="type"
option-value="id"
option-label="name"
:options="types"
:disable="!category"
clearable
:label="$t('family')"
@filter="filterType"
/>
</div>
<q-separator />
<div class="q-pa-md">
<q-select
v-model="order"
input-debounce="0"
:options="orderOptions"
:label="$t('orderBy')"
/>
<q-input
v-model="date"
mask="date"
:rules="['date']"
:label="$t('deliveryDate')">
<template v-slot:append>
<q-icon name="event" class="cursor-pointer">
<q-popup-proxy ref="qDateProxy" anchor="bottom left" self="top middle">
<q-date v-model="date" @input="() => $refs.qDateProxy.hide()" />
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</div>
<q-separator />
<div class="q-pa-md"
v-if="type || 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}}
<q-icon
v-if="tag.filter.length || tag.filter.min || tag.filter.max"
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">
<q-checkbox
v-model="tag.filter"
:dense="true"
:val="value"
:label="value"
@input="loadItems"
/>
</div>
<div v-if="tag.values.length > tag.showCount">
<span
class="cursor-pointer text-blue"
@click="tag.showCount = Infinity">
<q-icon 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">
<q-icon name="keyboard_arrow_up" />
{{$t('viewLess')}}
</span>
</div>
</div>
<div class="q-mx-md">
<q-range
class="q-mt-lg"
v-if="tag.useRange"
v-model="tag.filter"
:min="tag.min"
:max="tag.max"
:step="tag.step"
@input="loadItemsDelayed"
@change="loadItems"
label-always
markers
snap
/>
</div>
</div>
</div>
</q-drawer>
<q-infinite-scroll
@load="onLoad"
scroll-taget="html"
:offset="800"
:disable="disableScroll">
<div class="q-pa-md row justify-center q-gutter-md">
<q-spinner
v-if="isLoading"
color="primary"
size="50px">
</q-spinner>
<q-card
class="my-card"
v-for="item in items"
:key="item.id">
<img :src="`${$imageBase}/catalog/200x200/${item.image}`" />
<q-card-section>
<div class="name text-subtitle1">{{item.longName}}</div>
<div class="text-uppercase text-subtitle1 text-grey-7">
{{item.subName}}
</div>
</q-card-section>
<q-card-section class="tags">
<div v-for="tag in item.tags" :key="tag.tagFk">
<span class="text-grey-7">{{tag.tag.name}}</span> {{tag.value}}
</div>
</q-card-section>
<q-card-actions class="actions justify-between">
<div class="q-pl-sm">
<span class="available bg-green text-white">{{item.available}}</span>
{{$t('from')}}
<span class="price">{{item.inbounds[0].buy && item.inbounds[0].buy.price3 | currency}}</span>
</div>
<q-btn flat>{{$t('buy')}}</q-btn>
</q-card-actions>
</q-card>
</div>
<template slot="loading">
<div class="row justify-center q-my-md">
<q-spinner color="primary" name="dots" size="40px" />
</div>
</template>
</q-infinite-scroll>
</div>
</template>
<style lang="stylus" scoped>
.my-card
width 100%
max-width 21em
height 38em
overflow hidden
.name
white-space nowrap
overflow hidden
text-overflow ellipsis
.description
height 40px
overflow hidden
.category
width 25%
&.active
background: rgba(0, 0, 0, .08)
.category-img
height 3.5em
.tags
max-height 6.2em
overflow hidden
.available
padding .15em
border-radius .2em
font-size 1.3em
.price
font-size 1.3em
.actions
position absolute
bottom 0
width 100%
</style>
<script>
import { date } from 'quasar'
import axios from 'axios'
const CancelToken = axios.CancelToken
export default {
name: 'Catalog',
data () {
return {
uid: 0,
search: null,
date: date.formatDate(new Date(), 'YYYY/MM/DD'),
category: null,
categories: [],
tags: [],
type: null,
types: [],
orgTypes: [],
isLoading: false,
items: null,
rightDrawerOpen: this.$q.platform.is.desktop,
pageSize: 24,
limit: null,
maxTags: 5,
disableScroll: true,
order: {
label: this.$t('name'),
value: 'name'
},
orderOptions: [
{
label: this.$t('name'),
value: 'longName'
}, /* {
label: this.$t('priceAsc'),
value: 'price ASC'
}, {
label: this.$t('priceDesc'),
value: 'price DESC'
}, {
label: this.$t('available'),
value: 'available'
}, */ {
label: this.$t('siceAsc'),
value: 'size ASC'
}, {
label: this.$t('sizeDesc'),
value: 'size DESC'
}
]
}
},
mounted () {
this.$axios.get('ItemCategories')
.then(res => (this.categories = res.data))
},
beforeDestroy () {
this.clearTimeoutAndRequest()
},
watch: {
order () {
this.loadItems()
},
date () {
this.loadItems()
},
type (type) {
this.$router.push({
params: {
category: this.category,
type: type && type.id
}
})
this.$state.subtitle = type && type.name
this.$state.search = ''
this.loadItems()
},
'$state.search': function (value) {
if (!value) this.loadItems()
else this.loadItemsDelayed()
},
'$route.params.category': function (categoryId) {
let category = this.categories.find(i => i.id === categoryId)
if (category) {
this.$state.title = category.name
this.$state.titleColor = `#${category.color}`
} else {
this.$state.title = this.$t(this.$router.currentRoute.name)
this.$state.titleColor = null
}
this.category = categoryId
this.type = null
let params = { filter: { where: { categoryFk: categoryId } } }
this.$axios.get('ItemTypes', { params })
.then(res => (this.orgTypes = res.data))
},
'$route.params.type': function (type) {
if (!this.type) this.type = { id: type }
}
},
methods: {
loadItemsDelayed () {
this.clearTimeoutAndRequest()
this.timeout = setTimeout(() => this.loadItems(), 500)
},
clearTimeoutAndRequest () {
if (this.timeout) {
clearTimeout(this.timeout)
this.timeout = null
}
if (this.source) {
this.source.cancel()
this.source = null
}
},
loadItems () {
this.items = []
this.isLoading = true
this.limit = this.pageSize
this.disableScroll = false
this.loadItemsBase()
.finally(() => {
this.isLoading = false
})
},
onLoad (index, done) {
if (this.isLoading) return done()
this.limit += this.pageSize
this.loadItemsBase()
.finally(() => done())
},
loadItemsBase () {
this.clearTimeoutAndRequest()
if (!this.type) {
return Promise.resolve(true)
}
let typeFk
let tagFilter = []
for (let tag of this.tags) {
if (this.hasFilters(tag)) {
tagFilter.push({
tagFk: tag.id,
values: tag.filter
})
}
}
if (this.type) {
typeFk = this.type.id
}
this.source = CancelToken.source()
let params = {
dated: this.date,
typeFk: typeFk,
search: this.$state.search,
order: this.order.value,
tagFilter: tagFilter,
limit: this.limit
}
let config = {
params,
cancelToken: this.source.token
}
return this.$axios.get('Items/catalog', config)
.then(res => this.onItemsGet(res))
.catch(err => { if (!err.__CANCEL__) throw err })
.finally(() => (this.cancel = null))
},
onItemsGet (res) {
for (let tag of res.data.tags) {
tag.uid = this.uid++
if (tag.filter) {
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
},
resetTagFilter (tag) {
if (tag.useRange) {
tag.filter = {
min: null,
max: null
}
} else {
tag.filter = []
}
},
onResetTagFilterClick (tag) {
this.resetTagFilter(tag)
this.loadItems()
},
hasFilters (tag) {
let filter = tag.filter
return filter.length || filter.min || filter.max
},
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)
})
}
}
},
filters: {
currency: i => i ? i.toFixed(2) + '€' : i
}
}
</script>