Basic UI rework
This commit is contained in:
27
src/pages/Dashboard.vue
Normal file
27
src/pages/Dashboard.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<q-page class="flex flex-center flex-">
|
||||
<div v-if="username" class="q-py-lg">
|
||||
<GlobalSearch />
|
||||
<Feed />
|
||||
</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Feed from '../components/Feed.vue'
|
||||
import GlobalSearch from '../components/GlobalSearch.vue'
|
||||
export default {
|
||||
name: 'DashboardPage',
|
||||
components: { Feed, GlobalSearch },
|
||||
computed: {
|
||||
username () {
|
||||
return this.$store.state.core.username
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
22
src/pages/Error404.vue
Normal file
22
src/pages/Error404.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div class="fixed-center text-center">
|
||||
<p>
|
||||
<img
|
||||
src="~assets/methmoji.png"
|
||||
style="width:30vw;max-width:150px;"
|
||||
>
|
||||
</p>
|
||||
<p class="text-faded">Y'a rien ici, frêre...<strong>(404)</strong></p>
|
||||
<q-btn
|
||||
color="secondary"
|
||||
style="width:200px;"
|
||||
@click="$router.push('/')"
|
||||
>Retour a la home</q-btn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Error404'
|
||||
}
|
||||
</script>
|
||||
18
src/pages/Index.vue
Normal file
18
src/pages/Index.vue
Normal file
@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<q-page class="flex flex-center flex-">
|
||||
<div class="text-center">
|
||||
<img alt="Quasar logo" src="~assets/gyoza.jpg">
|
||||
<br/>
|
||||
<q-btn label="Go" color="primary" to="/dashboard"></q-btn>
|
||||
</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PageIndex'
|
||||
}
|
||||
</script>
|
||||
308
src/pages/StockItem.vue
Normal file
308
src/pages/StockItem.vue
Normal file
@ -0,0 +1,308 @@
|
||||
<template>
|
||||
<q-page class="q-pa-md">
|
||||
<q-chip class="float-right text-white" color="secondary">{{item.ref}}</q-chip>
|
||||
<q-chip v-if="item.deleted" class="float-right q-mr-md" color="red">Supprimé</q-chip>
|
||||
<h2 class="q-ma-none" :class="{'text-faded': !item.name}">{{item.name || '---- -- ----'}}</h2>
|
||||
<h4 class="q-ma-none q-pb-md text-light">{{item.type}}</h4>
|
||||
|
||||
<div class="q-py-md">
|
||||
<q-input v-model="item.name"
|
||||
:disable="item.deleted"
|
||||
type="text"
|
||||
standout
|
||||
stack-label label="Nom" class="q-mb-md"
|
||||
@input="changed = true"/>
|
||||
<q-input v-model="item.owner"
|
||||
:disable="item.deleted"
|
||||
color="secondary"
|
||||
standout stack-label label="Propriétaire" class="q-mb-md"
|
||||
@input="changed = true"/>
|
||||
<q-field borderless stack-label label="Etat cosmétique" class="q-mb-md">
|
||||
<q-btn-toggle
|
||||
no-caps
|
||||
:disable="item.deleted"
|
||||
v-model="item.state"
|
||||
@update:model-value="changed = true"
|
||||
toggle-color="secondary"
|
||||
text-color="black"
|
||||
:options="[
|
||||
{label: 'Neuf', value: 'Neuf'},
|
||||
{label: 'Excellent', value: 'Excellent'},
|
||||
{label: 'Bon', value: 'Bon'},
|
||||
{label: 'Moyen', value: 'Moyen'},
|
||||
{label: 'Abimé', value: 'Abimé'},
|
||||
{label: 'Inutilisable', value: 'Inutilisable'}
|
||||
]"
|
||||
/>
|
||||
</q-field>
|
||||
<q-field borderless stack-label label="Fonctionnel" class="q-mb-md">
|
||||
<q-btn-toggle
|
||||
no-caps
|
||||
:disable="item.deleted"
|
||||
v-model="item.working"
|
||||
:toggle-color="item.working ? 'green' : 'red'"
|
||||
:options="[
|
||||
{label: '✔', value: true},
|
||||
{label: '?', value: null},
|
||||
{label: '✘', value: false}
|
||||
]"
|
||||
@input="changed = true"
|
||||
/>
|
||||
</q-field>
|
||||
<q-field borderless stack-label label="En stock" class="q-mb-md">
|
||||
<q-btn-toggle
|
||||
no-caps
|
||||
:disable="item.deleted"
|
||||
v-model="item.available"
|
||||
:toggle-color="item.available ? 'green' : 'red'"
|
||||
:options="[
|
||||
{label: '✔', value: true},
|
||||
{label: '✘', value: false}
|
||||
]"
|
||||
@input="changed = true"
|
||||
/>
|
||||
</q-field>
|
||||
<q-field borderless stack-label label="Supprimé" class="q-mb-md">
|
||||
<q-btn-toggle
|
||||
no-caps
|
||||
v-model="item.deleted"
|
||||
:toggle-color="item.deleted ? 'green' : 'red'"
|
||||
:options="[
|
||||
{label: 'Oui', value: true},
|
||||
{label: 'Non', value: false}
|
||||
]"
|
||||
@input="changed = true"
|
||||
/>
|
||||
</q-field>
|
||||
<q-input
|
||||
:disable="item.deleted"
|
||||
v-model="item.comment"
|
||||
@update:model-value="changed = true"
|
||||
type="textarea"
|
||||
:max-height="100"
|
||||
rows="4"
|
||||
standout stack-label label="Commentaire" class="q-mb-md"
|
||||
inverted
|
||||
/>
|
||||
<q-btn :disabled="!changed || originalItem.deleted"
|
||||
@click="updateItem()"
|
||||
color="primary"
|
||||
:flat="!changed"
|
||||
>Enregistrer</q-btn>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<h4 class="q-ma-none q-pb-md text-light">Historique</h4>
|
||||
<q-timeline color="secondary">
|
||||
<!--<q-timeline-entry heading>-->
|
||||
<!--Historique-->
|
||||
<!--</q-timeline-entry>-->
|
||||
<q-timeline-entry
|
||||
title="Ajouter une entrée"
|
||||
side="left"
|
||||
icon="add"
|
||||
color="primary"
|
||||
@click="openHistoryForm()"
|
||||
style="cursor:pointer"
|
||||
>
|
||||
<q-slide-transition>
|
||||
<div v-show="showHistoryForm">
|
||||
<q-input
|
||||
v-model="pendingHistory"
|
||||
type="textarea"
|
||||
label="Description"
|
||||
:max-height="100"
|
||||
rows="3"
|
||||
invertedd
|
||||
/>
|
||||
<q-input label="Date"
|
||||
v-model="pendingHistoryDate"
|
||||
type="datetime"
|
||||
format24h />
|
||||
<br/>
|
||||
<q-uploader url=""
|
||||
:upload-factory="uploadFile"
|
||||
@add="hasFile = true"
|
||||
ref="uploader"
|
||||
:extensions="'.jpg,.png'"
|
||||
auto-expand
|
||||
hide-upload-button
|
||||
stack-label="Uploader une photo"
|
||||
/>
|
||||
<br/>
|
||||
<q-btn flat color="faded" @click.stop="showHistoryForm = false">Annuler</q-btn>
|
||||
<q-btn flat color="primary" @click.stop="addHistory()">Ok</q-btn>
|
||||
</div>
|
||||
</q-slide-transition>
|
||||
</q-timeline-entry>
|
||||
<q-timeline-entry
|
||||
v-for="event in history.slice().reverse()"
|
||||
:key="event.timestamp"
|
||||
:subtitle="getDate(event.timestamp)"
|
||||
:title="event.user"
|
||||
side="left"
|
||||
>
|
||||
<div>
|
||||
<q-icon name="format_quote"
|
||||
color="light"
|
||||
size="2rem"
|
||||
/>
|
||||
{{ event.text }}
|
||||
<q-icon name="format_quote"
|
||||
color="light"
|
||||
size="2rem"
|
||||
/>
|
||||
</div>
|
||||
<q-img :src="getImageUrl(event, event.image_file)"
|
||||
alt="photo"
|
||||
v-if="event.image_file"
|
||||
class="q-py-md"
|
||||
style="max-height:300px;max-width:300px"
|
||||
:fit="'contain'"
|
||||
/>
|
||||
<br/>
|
||||
<q-btn flat
|
||||
:disable="item.deleted"
|
||||
icon="delete"
|
||||
label="Supprimer cette entrée"
|
||||
@click="removeHistory(event)"
|
||||
/>
|
||||
</q-timeline-entry>
|
||||
</q-timeline>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { date } from 'quasar'
|
||||
export default {
|
||||
name: 'StockItem',
|
||||
props: ['itemRef'],
|
||||
data () {
|
||||
return {
|
||||
showHistoryForm: false,
|
||||
pendingHistory: null,
|
||||
pendingHistoryDate: null,
|
||||
changed: false,
|
||||
hasFile: false,
|
||||
username: null,
|
||||
item: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
category () {
|
||||
return this.item.type || ''
|
||||
},
|
||||
stock () {
|
||||
return this.$store.state.core.firebase.stock || []
|
||||
},
|
||||
originalItem () {
|
||||
return this.stock.find(item => {
|
||||
return (item) ? item.ref === this.itemRef : false
|
||||
}) || []
|
||||
},
|
||||
itemId () {
|
||||
return this.$store.state.core.firebase.stock.findIndex(item => {
|
||||
return (item) ? item.ref === this.itemRef : false
|
||||
})
|
||||
},
|
||||
history () {
|
||||
return this.$store.state.core.firebase.history.filter(item => {
|
||||
return item.ref === this.itemRef
|
||||
}).sort((a, b) => {
|
||||
return (a.timestamp > b.timestamp) ? 1 : -1
|
||||
}) || []
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
let item = this.stock.find(item => {
|
||||
return (item) ? item.ref === this.itemRef : false
|
||||
}) || []
|
||||
this.item = { ...item }
|
||||
},
|
||||
mounted () {
|
||||
this.username = this.$q.localStorage.getItem('username')
|
||||
},
|
||||
methods: {
|
||||
getDate (timestamp) {
|
||||
return date.formatDate(timestamp, 'DD/MM/YY - HH:mm')
|
||||
},
|
||||
openHistoryForm () {
|
||||
this.showHistoryForm = true
|
||||
this.pendingHistoryDate = Date.now()
|
||||
},
|
||||
async addHistory () {
|
||||
this.$q.loading.show()
|
||||
let url = null
|
||||
if (this.hasFile) {
|
||||
let resp = await this.uploadFile(this.$refs.uploader.files[0])
|
||||
url = resp
|
||||
console.log(resp)
|
||||
}
|
||||
this.$store.dispatch('core/addToCollection', {
|
||||
collection: 'history',
|
||||
data: {
|
||||
ref: this.itemRef,
|
||||
text: this.pendingHistory,
|
||||
user: this.$store.state.core.username,
|
||||
image: url,
|
||||
timestamp: this.pendingHistoryDate
|
||||
}
|
||||
}).then(response => {
|
||||
// this.$speech(`ooo history desu, arigato !`)
|
||||
this.showHistoryForm = false
|
||||
this.pendingHistory = null
|
||||
this.pendingHistoryDate = null
|
||||
this.hasFile = false
|
||||
this.$refs.uploader.reset()
|
||||
this.$q.loading.hide()
|
||||
})
|
||||
},
|
||||
removeHistory (data) {
|
||||
this.$q.dialog({
|
||||
title: 'Supprimer ?',
|
||||
message: "Il est possible que tu sois Meth, nous sommes donc légalement obligés de te demander : es-tu absolument sur d'être certain de vouloir effectuer cette action irréversible ?",
|
||||
ok: 'Supprimer',
|
||||
cancel: 'Annuler'
|
||||
}).then(() => {
|
||||
this.$store.dispatch('core/removeFromCollection', {
|
||||
collection: 'history',
|
||||
data
|
||||
}).then(_ => this.$q.notify('Supprimé !'))
|
||||
})
|
||||
},
|
||||
updateItem () {
|
||||
this.$q.loading.show()
|
||||
this.$store.dispatch('core/updateItem', {
|
||||
collection: 'stock',
|
||||
itemId: this.itemId,
|
||||
data: this.item
|
||||
}).then(response => {
|
||||
// this.$speech(`sugoï, domo arigato senpai !`)
|
||||
this.$q.notify({
|
||||
message: 'Enregistré !',
|
||||
color: 'grey',
|
||||
avatar: '~assets/gyoza_logo_nobg.png',
|
||||
timeout: 2000
|
||||
})
|
||||
this.changed = false
|
||||
this.$store.dispatch('core/loadAppData').then(_ => {
|
||||
this.$q.loading.hide()
|
||||
})
|
||||
})
|
||||
},
|
||||
uploadFile (file) {
|
||||
return this.$store.dispatch('core/uploadFile', file).then(response => {
|
||||
return response.ref.getDownloadURL()
|
||||
})
|
||||
},
|
||||
getImageUrl (history, image) {
|
||||
return this.$pb.files.getURL(history, image, { thumb: '300x300f' })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
146
src/pages/StockItems.vue
Normal file
146
src/pages/StockItems.vue
Normal file
@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<q-page padding>
|
||||
<h4 class="q-mt-none">{{category.name}}</h4>
|
||||
<q-table
|
||||
:title="category.name"
|
||||
:rows="items"
|
||||
:columns="dynamicColumns"
|
||||
:pagination="pagination"
|
||||
row-key="ref"
|
||||
@update:pagination="updatePaginationUrl"
|
||||
>
|
||||
|
||||
<template v-slot:top>
|
||||
<q-toggle v-model="showDeleted"
|
||||
label="Voir items supprimés"
|
||||
class="q-mr-md"
|
||||
/>
|
||||
<q-input
|
||||
hide-underline
|
||||
color="secondary"
|
||||
v-model="search"
|
||||
class="col-6"
|
||||
@input="updateSearchUrl"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-slot:body="props">
|
||||
<q-tr
|
||||
:props="props"
|
||||
@click="goToDetail(props)"
|
||||
:class="{'cursor-pointer': true, 'text-red': props.row.deleted}">
|
||||
<q-td v-for="col in props.cols" :key="col.ref">
|
||||
<div v-if="col.name === 'available'" class="text-center">
|
||||
<span v-if="col.value" class="text-green">✔</span>
|
||||
<span v-else class="text-red">✘</span>
|
||||
</div>
|
||||
<div v-if="col.name === 'deleted'" class="text-center">
|
||||
<span v-if="col.value" class="text-red">✘</span>
|
||||
</div>
|
||||
<div v-if="col.name === 'state'" class="text-center">
|
||||
<q-chip small :color="stateColor(col.value)" :title="col.value"></q-chip>
|
||||
</div>
|
||||
<span v-if="col.name !== 'deleted' && col.name !== 'available'&& col.name !== 'state'">
|
||||
{{ col.value }}
|
||||
</span>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
|
||||
</q-table>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { debounce } from 'quasar'
|
||||
|
||||
export default {
|
||||
name: 'StockItems',
|
||||
props: ['categoryCode'],
|
||||
data () {
|
||||
return {
|
||||
search: null,
|
||||
showDeleted: false,
|
||||
pagination: {
|
||||
sortBy: null,
|
||||
descending: false,
|
||||
page: 1,
|
||||
rowsPerPage: 15
|
||||
},
|
||||
columns: [
|
||||
{ label: 'Référence', align: 'left', field: 'ref', sortable: true, name: 'ref' },
|
||||
{ label: 'Nom', align: 'left', field: 'name', sortable: true, name: 'name' },
|
||||
{ label: 'Etat', align: 'left', field: 'state', sortable: true, name: 'state' },
|
||||
{ label: 'Propriétaire', align: 'left', field: 'owner', sortable: true, name: 'owner' },
|
||||
{ label: 'En stock', align: 'left', field: 'available', sortable: true, name: 'available' },
|
||||
{ label: 'Supprimé', align: 'left', field: 'deleted', sortable: false, name: 'deleted', format: val => val ? '✔' : '' }
|
||||
]
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (!this.search) this.search = this.$route.query.search || null
|
||||
let paginationQuery = { ...this.$route.query }
|
||||
delete paginationQuery.search
|
||||
this.pagination = { ...this.pagination, ...paginationQuery }
|
||||
console.log('__Pagination:', this.pagination)
|
||||
},
|
||||
computed: {
|
||||
dynamicColumns () {
|
||||
let columns = JSON.parse(JSON.stringify(this.columns))
|
||||
if (!this.showDeleted) columns.pop()
|
||||
return columns
|
||||
},
|
||||
category () {
|
||||
return this.$store.state.core.firebase.categories.find(item => {
|
||||
return parseInt(item.code) === parseInt(this.categoryCode)
|
||||
}) || {}
|
||||
},
|
||||
items () {
|
||||
let items = this.$store.state.core.firebase.stock.filter(item => {
|
||||
return item.type === this.category.name &&
|
||||
(this.search ? (item.ref + (item.name + '').toLowerCase()).search(this.search.toLowerCase()) !== -1 : true) &&
|
||||
(this.showDeleted ? true : !item.deleted)
|
||||
}).slice().reverse() || []
|
||||
if (this.pagination.sortBy) {
|
||||
console.log('__Sorting:', this.pagination)
|
||||
return [...items].sort((a, b) => {
|
||||
const x = this.pagination.descending ? b : a
|
||||
const y = this.pagination.descending ? a : b
|
||||
return x[this.pagination.sortBy] > y[this.pagination.sortBy]
|
||||
})
|
||||
}
|
||||
return items
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goToDetail (data) {
|
||||
this.$router.push(`/item/${data.key}`)
|
||||
},
|
||||
stateColor (state) {
|
||||
let colors = {
|
||||
Neuf: 'green-14',
|
||||
Excellent: 'green-12',
|
||||
Bon: 'lime-13',
|
||||
Moyen: 'orange',
|
||||
Abimé: 'red',
|
||||
Inutilisable: 'black'
|
||||
}
|
||||
return colors[state]
|
||||
},
|
||||
updateSearchUrl: debounce(function () {
|
||||
this.$router.replace({ path: this.$router.path, query: { search: this.search } })
|
||||
}, 300),
|
||||
updatePaginationUrl: debounce(function () {
|
||||
this.$router.replace({
|
||||
path: this.$router.path,
|
||||
query: {
|
||||
...this.pagination
|
||||
}
|
||||
})
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
Reference in New Issue
Block a user