After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 134 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 8.4 KiB |
@ -0,0 +1,13 @@ |
|||||
|
import { boot } from 'quasar/wrappers' |
||||
|
import PocketBase from 'pocketbase' |
||||
|
const pb = new PocketBase('http://127.0.0.1:8090') |
||||
|
|
||||
|
export default boot(({ app }) => { |
||||
|
pb.collection('_superusers').authWithPassword( |
||||
|
'gyoza@hfsplay.fr', |
||||
|
'gyozagyoza' |
||||
|
) |
||||
|
|
||||
|
app.config.globalProperties.$pb = pb |
||||
|
}) |
||||
|
export { pb } |
@ -0,0 +1,75 @@ |
|||||
|
<template> |
||||
|
<div> |
||||
|
<q-list highlight inset-separator sparse no-border> |
||||
|
<q-list-header>Quoi de neuf</q-list-header> |
||||
|
<q-item mulitline |
||||
|
link |
||||
|
v-for="history in lastHistory" |
||||
|
:key="history.timestamp" |
||||
|
:to="`item/${history.ref}`" |
||||
|
> |
||||
|
<q-item-side :icon="history.image ? 'image' : 'chat'" /> |
||||
|
<q-item-main> |
||||
|
<q-item-tile lines="3" label>{{ history.text }}</q-item-tile> |
||||
|
<q-item-tile sublabel> |
||||
|
<span class="text-secondary">{{history.user}}</span> |
||||
|
, au sujet de <b>{{ history.item.name}}</b> #{{history.ref}} |
||||
|
</q-item-tile> |
||||
|
</q-item-main> |
||||
|
<q-item-side right :stamp="timeAgo(history.timestamp)"/> |
||||
|
</q-item> |
||||
|
</q-list> |
||||
|
</div> |
||||
|
|
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { date } from 'quasar' |
||||
|
|
||||
|
export default { |
||||
|
name: 'ActivityFeed', |
||||
|
data () { |
||||
|
return { } |
||||
|
}, |
||||
|
computed: { |
||||
|
stock () { |
||||
|
return this.$store.state.core.firebase.stock || [] |
||||
|
}, |
||||
|
history () { |
||||
|
return this.$store.state.core.firebase.history || [] |
||||
|
}, |
||||
|
username () { return this.$store.state.core.username }, |
||||
|
lastHistory () { |
||||
|
let newHistory = JSON.parse(JSON.stringify(this.history)).slice(-15) |
||||
|
for (let history of newHistory) { |
||||
|
history.item = this.getStock(history.ref) |
||||
|
} |
||||
|
return newHistory.reverse() |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
dateFormat (timestamp, format = 'DD/MM/YY - HH:ss') { |
||||
|
return date.formatDate(timestamp, format) |
||||
|
}, |
||||
|
timeAgo (timestamp) { |
||||
|
let date1 = new Date(timestamp) |
||||
|
let date2 = Date.now() |
||||
|
let hoursDiff = date.getDateDiff(date2, date1, 'hours') |
||||
|
if (hoursDiff < 24) { |
||||
|
return `il y a ${hoursDiff} heure(s)` |
||||
|
} else { |
||||
|
let daysDiff = date.getDateDiff(date2, date1) |
||||
|
return `il y a ${daysDiff} jour(s)` |
||||
|
} |
||||
|
}, |
||||
|
getStock (ref) { |
||||
|
return this.stock.find(item => { |
||||
|
return (item) ? item.ref === ref : false |
||||
|
}) || [] |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style> |
||||
|
</style> |
@ -0,0 +1,79 @@ |
|||||
|
<template> |
||||
|
<div> |
||||
|
<q-input v-model="srcTxt" |
||||
|
@input="updateSearchUrl" |
||||
|
placeholder="Rechercher une référence ou un nom" |
||||
|
autofocus |
||||
|
clearable |
||||
|
/> |
||||
|
<q-list highlight |
||||
|
no-border |
||||
|
v-if="searchResults.length > 0" |
||||
|
class="q-mb-lg" |
||||
|
> |
||||
|
<q-item-label>{{searchResults.length}} résultats de recherche pour "{{srcTxt}}"</q-item-label> |
||||
|
<q-item multiline |
||||
|
link |
||||
|
:class="{'text-red': result.deleted}" |
||||
|
v-for="result in searchResults" |
||||
|
:disabled="result.deleted" |
||||
|
:key="result.ref" |
||||
|
:to="`item/${result.ref}`" |
||||
|
> |
||||
|
<q-item-section> |
||||
|
<q-item-section label>{{ result.name }}</q-item-section> |
||||
|
<q-item-section sublabel> |
||||
|
#{{result.ref}}, dans <span class="text-secondary">{{result.type}}</span> |
||||
|
</q-item-section> |
||||
|
</q-item-section> |
||||
|
<q-item-section right :stamp="`Etat: ${result.state}`"/> |
||||
|
</q-item> |
||||
|
<!-- <q-item-separator />--> |
||||
|
</q-list> |
||||
|
|
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { date } from 'quasar' |
||||
|
|
||||
|
export default { |
||||
|
name: 'GlobalSearch', |
||||
|
data () { |
||||
|
return { srcTxt: null } |
||||
|
}, |
||||
|
computed: { |
||||
|
stock () { |
||||
|
return this.$store.state.core.firebase.stock || [] |
||||
|
}, |
||||
|
searchResults () { |
||||
|
if (!this.srcTxt || this.srcTxt.length < 3) return [] |
||||
|
return this.stock.filter(item => { |
||||
|
return (item.ref + (item.name + '').toLowerCase()).search(this.srcTxt.toLowerCase()) !== -1 |
||||
|
}) || [] |
||||
|
} |
||||
|
}, |
||||
|
created () { |
||||
|
if (!this.srcTxt) this.srcTxt = this.$route.query.search || null |
||||
|
}, |
||||
|
methods: { |
||||
|
timeAgo (timestamp) { |
||||
|
let date1 = new Date(timestamp) |
||||
|
let date2 = Date.now() |
||||
|
let hoursDiff = date.getDateDiff(date2, date1, 'hours') |
||||
|
if (hoursDiff < 24) { |
||||
|
return `il y a ${hoursDiff} heure(s)` |
||||
|
} else { |
||||
|
let daysDiff = date.getDateDiff(date2, date1) |
||||
|
return `il y a ${daysDiff} jour(s)` |
||||
|
} |
||||
|
}, |
||||
|
updateSearchUrl () { |
||||
|
this.$router.replace({ path: this.$router.path, query: { search: this.srcTxt } }) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style> |
||||
|
</style> |
@ -0,0 +1,224 @@ |
|||||
|
<template> |
||||
|
<q-layout> <!-- Be sure to play with the Layout demo on docs --> |
||||
|
<q-header class="no-shadow text-center"> |
||||
|
<q-toolbar color="secondary" inverted style="border-bottom:1px solid rgba(0,0,0,0.1)"> |
||||
|
<q-btn |
||||
|
flat |
||||
|
size="18px" |
||||
|
icon="menu" |
||||
|
@click="leftDrawer = !leftDrawer" |
||||
|
/> |
||||
|
<q-toolbar-title class="text-center"> |
||||
|
<div class="q-px-md" @click="$router.push('/dashboard')" style="display:inline-block;cursor:pointer;max-width:250px"> |
||||
|
<img src="~assets/gyoza_logo_inline.jpg" style="width:100%"> |
||||
|
</div> |
||||
|
</q-toolbar-title> |
||||
|
<q-btn flat class="no-pointer-events">{{username}}</q-btn> |
||||
|
<q-btn |
||||
|
flat |
||||
|
size="18px" |
||||
|
icon="logout" |
||||
|
@click="unsetUser()" |
||||
|
/> |
||||
|
</q-toolbar> |
||||
|
</q-header> |
||||
|
<q-drawer |
||||
|
side="left" |
||||
|
v-model="leftDrawer" |
||||
|
> |
||||
|
<q-list highlight |
||||
|
no-border |
||||
|
inset-separator |
||||
|
> |
||||
|
<q-item-label>Catégories</q-item-label> |
||||
|
<q-item v-for="category in categories" |
||||
|
:key="category.code" |
||||
|
:to="'/category/' + category.code" |
||||
|
> |
||||
|
<q-item-section> |
||||
|
{{ category.name }} |
||||
|
<q-chip square |
||||
|
dense |
||||
|
color="secondary" |
||||
|
class="q-ml-md" |
||||
|
>{{ counters[category.name] }}</q-chip> |
||||
|
</q-item-section> |
||||
|
<q-item-section> |
||||
|
<q-btn round |
||||
|
flat |
||||
|
size="md" |
||||
|
icon="add" |
||||
|
:title="`Ajouter un(e) ${category.name}`" |
||||
|
@click.stop.prevent="addItem(category)" |
||||
|
> |
||||
|
</q-btn> |
||||
|
</q-item-section> |
||||
|
</q-item> |
||||
|
<!--<q-item-separator />--> |
||||
|
<!--<q-list-header>Previous chats</q-list-header>--> |
||||
|
<!--<q-item>--> |
||||
|
<!--<q-item-side avatar="statics/guy-avatar.png" />--> |
||||
|
<!--<q-item-main label="Jack Doe" />--> |
||||
|
<!--</q-item>--> |
||||
|
</q-list> |
||||
|
</q-drawer> |
||||
|
|
||||
|
<q-page-container> |
||||
|
<!-- This is where pages get injected --> |
||||
|
<router-view /> |
||||
|
</q-page-container> |
||||
|
|
||||
|
</q-layout> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import { QSpinnerBars, date } from 'quasar' |
||||
|
|
||||
|
export default { |
||||
|
name: 'DefaultLayout', |
||||
|
data () { |
||||
|
return { |
||||
|
leftDrawer: true, |
||||
|
loading: false, |
||||
|
dataRef: null |
||||
|
} |
||||
|
}, |
||||
|
computed: { |
||||
|
categories () { |
||||
|
return this.$store.state.core.firebase.categories |
||||
|
}, |
||||
|
counters () { |
||||
|
let stock = this.$store.state.core.firebase.stock |
||||
|
let result = {} |
||||
|
if (!this.categories || !stock) return result |
||||
|
for (let cat of this.categories) { |
||||
|
let catStock = stock.filter(item => { return item.type === cat.name }) |
||||
|
let lastRef = (catStock[catStock.length - 1] || {}).ref |
||||
|
let ref = lastRef ? parseInt(lastRef.split('-')[1]) : 0 |
||||
|
result[cat.name] = ref |
||||
|
} |
||||
|
return result |
||||
|
}, |
||||
|
username () { |
||||
|
return this.$store.state.core.username |
||||
|
} |
||||
|
}, |
||||
|
created () { |
||||
|
this.$q.loading.setDefaults({ |
||||
|
spinner: QSpinnerBars, |
||||
|
message: 'OK, laisse moi juste le temps de charger...', |
||||
|
messageColor: 'white', |
||||
|
spinnerSize: 150, // in pixels |
||||
|
spinnerColor: 'white', |
||||
|
customClass: 'bg-secondary', |
||||
|
delay: 0 |
||||
|
}) |
||||
|
this.$q.loading.show() |
||||
|
this.loadAppData() |
||||
|
// Realtime reactivity to DB changes |
||||
|
// TODO: Fix this |
||||
|
// this.dataRef = Firebase.database().ref() |
||||
|
// this.dataRef.on('value', (snapshot) => { |
||||
|
// this.$store.dispatch('core/loadAppData') |
||||
|
// }) |
||||
|
}, |
||||
|
mounted () { |
||||
|
let username = this.$q.localStorage.getItem('username') |
||||
|
console.log('user', username) |
||||
|
if (!username) { |
||||
|
this.$q.dialog({ |
||||
|
title: 'Hey salut !', |
||||
|
message: 'Moi c\'est Gyōza. C\'est quoi ton pseudo ?', |
||||
|
prompt: { |
||||
|
model: '', |
||||
|
type: 'text' // optional |
||||
|
}, |
||||
|
cancel: false, |
||||
|
preventClose: true, |
||||
|
color: 'secondary' |
||||
|
}).onOk(username => { |
||||
|
this.$q.localStorage.set('username', username) |
||||
|
this.$store.commit('core/setUsername', username) |
||||
|
this.$q.notify({ |
||||
|
message: `Enchanté ${username} !`, |
||||
|
color: 'green', |
||||
|
avatar: '/assets/gyoza_logo_nobg.png', |
||||
|
timeout: 2000 |
||||
|
}) |
||||
|
}) |
||||
|
} else { |
||||
|
this.$store.commit('core/setUsername', username) |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
loadAppData () { |
||||
|
this.$q.loading.show() |
||||
|
this.$store.dispatch('core/loadAppData').then(data => { |
||||
|
console.log(data) |
||||
|
this.$q.loading.hide() |
||||
|
}) |
||||
|
}, |
||||
|
unsetUser () { |
||||
|
this.$q.dialog({ |
||||
|
title: 'Une petite seconde...', |
||||
|
message: 'Tu vas être déconnecté(e) là. C\'est vraiment ça que tu veux ?', |
||||
|
color: 'primary', |
||||
|
ok: 'Bah oui, FDP', |
||||
|
cancel: 'Ah non en fait' |
||||
|
}) |
||||
|
.then(() => { |
||||
|
this.$q.localStorage.remove('username') |
||||
|
this.$router.push('/') |
||||
|
}) |
||||
|
}, |
||||
|
addItem (category) { |
||||
|
this.$q.loading.show() |
||||
|
let newItem = { |
||||
|
available: true, |
||||
|
comment: '', |
||||
|
name: '', |
||||
|
owner: 'HFS', |
||||
|
ref: null, |
||||
|
state: null, |
||||
|
type: category.name, |
||||
|
system: null |
||||
|
} |
||||
|
newItem.ref = `${category.code}-${String(this.counters[category.name] + 1).padStart(4, '0')}` |
||||
|
this.$store.dispatch('core/addToCollection', { |
||||
|
collection: 'stock', |
||||
|
data: newItem |
||||
|
}).then(response => { |
||||
|
// Insert history record |
||||
|
this.$store.dispatch('core/addToCollection', { |
||||
|
collection: 'history', |
||||
|
data: { |
||||
|
ref: newItem.ref, |
||||
|
text: 'Entrée en stock', |
||||
|
user: this.$store.state.core.username, |
||||
|
timestamp: Date.now() |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
// Process to new item |
||||
|
this.$q.loading.hide() |
||||
|
this.$q.dialog({ |
||||
|
title: `Item "${category.name}" créé`, |
||||
|
message: 'Tu veux aller éditer les détails de cet item ?', |
||||
|
color: 'primary', |
||||
|
ok: 'Oui, STP', |
||||
|
cancel: 'Osef' |
||||
|
}) |
||||
|
.then(() => { |
||||
|
this.$router.push(`/item/${newItem.ref}`) |
||||
|
}) |
||||
|
.catch(() => { |
||||
|
// Picked "Cancel" or dismissed |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style> |
||||
|
</style> |
@ -0,0 +1,27 @@ |
|||||
|
<template> |
||||
|
<q-layout> <!-- Be sure to play with the Layout demo on docs --> |
||||
|
<q-page-container> |
||||
|
<router-view /> |
||||
|
</q-page-container> |
||||
|
</q-layout> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
// name: 'LayoutName', |
||||
|
data () { |
||||
|
return { |
||||
|
leftDrawer: true |
||||
|
} |
||||
|
}, |
||||
|
created () { |
||||
|
let username = this.$q.localStorage.getItem('username') |
||||
|
if (username) { |
||||
|
this.$router.push('/dashboard') |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style> |
||||
|
</style> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -0,0 +1,49 @@ |
|||||
|
import { pb } from 'src/boot/pocketbase' |
||||
|
//
|
||||
|
// export function loadAppData (store) {
|
||||
|
// return Firebase.database().ref().once('value').then((snapshot) => {
|
||||
|
// store.commit('setFirebaseState', snapshot.val())
|
||||
|
// })
|
||||
|
// }
|
||||
|
//
|
||||
|
export async function loadAppData (store) { |
||||
|
return Promise.all([ |
||||
|
pb.collection('stock').getFullList(), |
||||
|
pb.collection('history').getFullList(), |
||||
|
pb.collection('categories').getFullList() |
||||
|
]).then(([stock, history, categories]) => { |
||||
|
return store.commit('setFirebaseState', { |
||||
|
stock, |
||||
|
history, |
||||
|
categories |
||||
|
}) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// export function addToCollection (store, data) {
|
||||
|
// return Firebase.database().ref('/' + data.collection).once('value').then((snapshot) => {
|
||||
|
// let index = parseInt(Object.keys(snapshot.val()).reverse()[0]) + 1
|
||||
|
// return Firebase.database().ref('/' + data.collection + '/' + index).set(data.data)
|
||||
|
// })
|
||||
|
// }
|
||||
|
//
|
||||
|
// export function removeFromCollection (store, data) {
|
||||
|
// return Firebase.database().ref('/' + data.collection).once('value').then((snapshot) => {
|
||||
|
// let index = snapshot.val().findIndex(item =>
|
||||
|
// item.timestamp === data.data.timestamp && item.ref === data.data.ref
|
||||
|
// )
|
||||
|
// return Firebase.database().ref('/' + data.collection + '/' + index).remove()
|
||||
|
// })
|
||||
|
// }
|
||||
|
//
|
||||
|
// export function updateItem (store, data) {
|
||||
|
// return Firebase.database().ref(`/${data.collection}/${data.itemId}`).set(data.data)
|
||||
|
// }
|
||||
|
//
|
||||
|
// export function deleteItem (store, data) {
|
||||
|
// return Firebase.database().ref(`/${data.collection}/${data.itemId}`).set(null)
|
||||
|
// }
|
||||
|
//
|
||||
|
// export function uploadFile (store, file) {
|
||||
|
// return Firebase.storage().ref().child(Date.now() + file.name).put(file)
|
||||
|
// }
|
@ -0,0 +1,4 @@ |
|||||
|
/* |
||||
|
export function someGetter (state) { |
||||
|
} |
||||
|
*/ |
@ -0,0 +1,12 @@ |
|||||
|
import state from './state' |
||||
|
import * as getters from './getters' |
||||
|
import * as mutations from './mutations' |
||||
|
import * as actions from './actions' |
||||
|
|
||||
|
export default { |
||||
|
namespaced: true, |
||||
|
state, |
||||
|
getters, |
||||
|
mutations, |
||||
|
actions |
||||
|
} |
@ -0,0 +1,8 @@ |
|||||
|
export function setFirebaseState (state, data) { |
||||
|
state.firebase = data |
||||
|
return data |
||||
|
} |
||||
|
|
||||
|
export function setUsername (state, data) { |
||||
|
state.username = data |
||||
|
} |
@ -0,0 +1,8 @@ |
|||||
|
export default { |
||||
|
username: null, |
||||
|
firebase: { |
||||
|
categories: [], |
||||
|
history: [], |
||||
|
stock: [] |
||||
|
} |
||||
|
} |