Compare commits

...

9 Commits

Author SHA1 Message Date
ebef3ee901 FIx history date display 2025-06-27 17:56:42 +02:00
d656bb15ff Add recap page 2025-06-27 17:50:22 +02:00
9fe860b526 Renumerate script 2025-06-27 15:49:04 +02:00
a7225eb054 Add shortId handling for QR
Add renumerate script
Add Tasks
2025-06-27 15:35:31 +02:00
6459dbd68c Fix feed date
Fix redirect to new item
2025-06-27 14:01:29 +02:00
361bb0c1ed Many fixes n stuff 2025-04-18 14:42:43 +02:00
892158fc30 Fix style 2025-04-18 13:29:15 +02:00
96899a8477 Fix style 2025-04-16 21:38:56 +02:00
08cf066c88 Fix style 2025-04-16 21:24:49 +02:00
29 changed files with 461 additions and 176 deletions

View File

@ -74,6 +74,7 @@ module.exports = {
'no-unused-vars': 'off', 'no-unused-vars': 'off',
'no-trailing-spaces' : 'off', 'no-trailing-spaces' : 'off',
'vue/multi-word-component-names' : 'off', 'vue/multi-word-component-names' : 'off',
'vue/no-v-text-v-html-on-component' : 'off',
// allow debugger during development only // allow debugger during development only
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'

1
.gitignore vendored
View File

@ -33,3 +33,4 @@ yarn-error.log*
.env.local* .env.local*
pocketbase/pb_data/* pocketbase/pb_data/*
pocketbase/pocketbase.exe

View File

@ -8,7 +8,7 @@ HFSPlay stock management
- [ ] editable categories - [ ] editable categories
- [x] qrcode generation - [x] qrcode generation
- [x] fix search to be persistent - [x] fix search to be persistent
- [ ] fix comments to be more legible - [x] fix comments to be more legible
- [ ] fix comments to allow resolution - [ ] fix comments to allow resolution
- [ ] batch print QR codes - [ ] batch print QR codes
- [x] fix tables pagination - [x] fix tables pagination

View File

@ -13,7 +13,8 @@
"import-categories": "node ./scripts/import_categories.js", "import-categories": "node ./scripts/import_categories.js",
"import-stock": "node ./scripts/import_stock.js", "import-stock": "node ./scripts/import_stock.js",
"import-history": "node ./scripts/import_history.js", "import-history": "node ./scripts/import_history.js",
"import-history-files": "node ./scripts/import_history_files.js" "import-history-files": "node ./scripts/import_history_files.js",
"renumerate-stock": "node ./scripts/renumerate.js"
}, },
"dependencies": { "dependencies": {
"@quasar/extras": "^1.16.4", "@quasar/extras": "^1.16.4",

BIN
public/font/Geist-Black.otf Normal file

Binary file not shown.

BIN
public/font/Geist-Bold.otf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/font/Geist-Light.otf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/font/Geist-Thin.otf Normal file

Binary file not shown.

BIN
public/font/Geist-var.ttf Normal file

Binary file not shown.

View File

@ -19,8 +19,6 @@ module.exports = configure(function (/* ctx */) {
// --> boot files are part of "main.js" // --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli-vite/boot-files // https://v2.quasar.dev/quasar-cli-vite/boot-files
boot: [ boot: [
'axios',
'pocketbase' 'pocketbase'
], ],

View File

@ -6,7 +6,7 @@ const pb = new PocketBase('http://127.0.0.1:8090')
async function main () { async function main () {
try { try {
// Authenticate as an admin // Authenticate as an admin
await pb.admins.authWithPassword('karkinge@gmail.com', 'PBk4rk1ng3*') // Replace with your admin credentials await pb.admins.authWithPassword('karkinge@gmail.com', 'XXXXXXX') // Replace with your admin credentials
// Parse JSON file with categories // Parse JSON file with categories
const data = require('../../gyoza/database_backups/20250320.json') const data = require('../../gyoza/database_backups/20250320.json')

View File

@ -6,7 +6,7 @@ const pb = new PocketBase('http://127.0.0.1:8090')
async function main () { async function main () {
try { try {
// Authenticate as an admin // Authenticate as an admin
await pb.admins.authWithPassword('karkinge@gmail.com', 'PBk4rk1ng3*') // Replace with your admin credentials await pb.admins.authWithPassword('karkinge@gmail.com', 'XXXXXXX') // Replace with your admin credentials
// Parse JSON file with categories // Parse JSON file with categories
const data = require('../../gyoza/database_backups/20250320.json') const data = require('../../gyoza/database_backups/20250320.json')

View File

@ -7,7 +7,7 @@ const pb = new PocketBase('http://127.0.0.1:8090')
async function main () { async function main () {
try { try {
// Authenticate as an admin // Authenticate as an admin
await pb.admins.authWithPassword('karkinge@gmail.com', 'PBk4rk1ng3*') // Replace with your admin credentials await pb.admins.authWithPassword('karkinge@gmail.com', 'XXXXXXX') // Replace with your admin credentials
// Parse JSON file with categories // Parse JSON file with categories
const filesPath = '../gyoza/storage/' const filesPath = '../gyoza/storage/'

View File

@ -6,7 +6,7 @@ const pb = new PocketBase('http://127.0.0.1:8090')
async function main () { async function main () {
try { try {
// Authenticate as an admin // Authenticate as an admin
await pb.admins.authWithPassword('karkinge@gmail.com', 'PBk4rk1ng3*') // Replace with your admin credentials await pb.admins.authWithPassword('karkinge@gmail.com', 'XXXXXXX') // Replace with your admin credentials
// Parse JSON file with categories // Parse JSON file with categories
const data = require('../../gyoza/database_backups/20250320.json') const data = require('../../gyoza/database_backups/20250320.json')

31
scripts/renumerate.js Normal file
View File

@ -0,0 +1,31 @@
const PocketBase = require('pocketbase').default
// Initialize the PocketBase client
// const pb = new PocketBase('https://stock.hfsplay.fr')
const pb = new PocketBase('http://127.0.0.1:8090')
async function main () {
try {
// Authenticate as an admin
// await pb.admins.authWithPassword('gyoza@hfsplay.fr', 'gyozagyoza') // Replace with your admin credentials
await pb.collection('_superusers').authWithPassword('karkinge@gmail.com', 'XXXXXXX') // Replace with your admin credentials
console.log('connected !')
let stock = await pb.collection('stock').getFullList({
sort: 'created'
})
console.log('Stock items count:', stock.length)
for (let i = 0; i < stock.length; i++) {
const item = stock[i]
if (!item) continue
// Update the item with a new ID
const updatedItem = await pb.collection('stock').update(item.id, {
short_id: i + 1 // Adjust the ID as needed
})
console.log(`Updated item ${item.id} to new ID ${updatedItem.id}`)
}
} catch (error) {
console.error('Error:', error)
}
}
main()

View File

@ -1,6 +1,6 @@
import { boot } from 'quasar/wrappers' import { boot } from 'quasar/wrappers'
import PocketBase from 'pocketbase' import PocketBase from 'pocketbase'
const pb = new PocketBase('http://127.0.0.1:8090') const pb = new PocketBase(process.env.DEV ? 'http://127.0.0.1:8090' : 'https://stock.hfsplay.fr')
export default boot(({ app }) => { export default boot(({ app }) => {
pb.collection('_superusers').authWithPassword( pb.collection('_superusers').authWithPassword(

View File

@ -20,7 +20,7 @@
</q-item-section> </q-item-section>
<q-item-section side top> <q-item-section side top>
<q-item-label caption>{{timeAgo(history.date)}}</q-item-label> <q-item-label caption>{{timeAgo(history.date || history.created)}}</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
</q-list> </q-list>
@ -63,7 +63,7 @@ export default {
if (hoursDiff < 24) { if (hoursDiff < 24) {
return `il y a ${hoursDiff} heure(s)` return `il y a ${hoursDiff} heure(s)`
} else { } else {
let daysDiff = date.getDateDiff(date2, date1) let daysDiff = date.getDateDiff(date2, date1, 'days')
return `il y a ${daysDiff} jour(s)` return `il y a ${daysDiff} jour(s)`
} }
}, },

View File

@ -1 +1,10 @@
// app global css in SCSS form // app global css in SCSS form
@font-face {
font-family: geist;
src: url(./font/Geist-var.ttf) format('truetype');
}
// declare a class which applies it
html, body {
font-family: 'geist';
}

BIN
src/css/font/Geist-var.ttf Normal file

Binary file not shown.

View File

@ -66,7 +66,15 @@
<!-- PRINT STUFF--> <!-- PRINT STUFF-->
<q-separator/> <q-separator/>
<q-item-label header>Utilitaires</q-item-label> <q-item-label header>Utilitaires</q-item-label>
<q-item :to="'/tasks'"
>
<q-item-section>Tâches</q-item-section>
<q-item-section side>
<q-icon name="task"></q-icon>
</q-item-section>
</q-item>
<q-item :to="'/print-settings'" <q-item :to="'/print-settings'"
disable
> >
<q-item-section>Impression</q-item-section> <q-item-section>Impression</q-item-section>
<q-item-section side> <q-item-section side>
@ -188,6 +196,8 @@ export default {
}, },
async addItem (category) { async addItem (category) {
this.$q.loading.show() this.$q.loading.show()
// Get next short id
let shortId = await this.$store.dispatch('core/getNextShortId')
let newItem = { let newItem = {
available: true, available: true,
comment: '', comment: '',
@ -196,7 +206,8 @@ export default {
ref: null, ref: null,
state: null, state: null,
type: category.name, type: category.name,
system: null system: null,
short_id: shortId
} }
newItem.ref = `${category.code}-${String(this.counters[category.name] + 1).padStart(4, '0')}` newItem.ref = `${category.code}-${String(this.counters[category.name] + 1).padStart(4, '0')}`
await this.$store.dispatch('core/addToCollection', { await this.$store.dispatch('core/addToCollection', {
@ -224,12 +235,9 @@ export default {
ok: 'Oui, STP', ok: 'Oui, STP',
cancel: 'Osef' cancel: 'Osef'
}) })
.then(() => { .onOk(() => {
this.$router.push(`/item/${newItem.ref}`) this.$router.push(`/item/${newItem.ref}`)
}) })
.catch(() => {
// Picked "Cancel" or dismissed
})
} }
} }
} }

View File

@ -1,10 +1,13 @@
<template> <template>
<q-page class="q-pa-md"> <q-page class="q-pa-md">
<q-btn @click="generateQRCode" class="float-right" icon="qr_code"></q-btn> <q-btn @click="generateQRCode" class="float-right" icon="qr_code"></q-btn>
<q-chip class="float-right text-white" color="secondary">{{item.ref}}</q-chip> <div class="q-ma-none text-h3 text-weight-bolder row" :class="{'text-faded': !item.name}">
<q-chip v-if="item.deleted" class="float-right q-mr-md" color="red">Supprimé</q-chip> <span class="q-mr-md">{{ item.name || '---- -- ----' }}</span>
<h2 class="q-ma-none" :class="{'text-faded': !item.name}">{{item.name || '---- -- ----'}}</h2> <q-chip square size="lg" class="text-white q-mr-md" color="secondary">{{item.ref}}</q-chip>
<h4 class="q-ma-none q-pb-md text-grey">{{item.type}}</h4> <q-chip square outline size="lg" class="q-mr-md" color="secondary">{{item.short_id}}</q-chip>
<q-chip square size="lg" v-if="item.deleted" color="red">Supprimé</q-chip>
</div>
<h5 class="q-ma-none q-pb-md text-grey">{{item.type}}</h5>
<q-dialog v-model="showQr"> <q-dialog v-model="showQr">
<q-card style="width: 500px"> <q-card style="width: 500px">
@ -23,26 +26,36 @@
</q-card> </q-card>
</q-dialog> </q-dialog>
<div class="q-py-md"> <div class="row q-col-gutter-xl">
<q-input v-model="item.name" <div class="col-12 col-lg-6">
:disable="item.deleted" <div class="row">
type="text" <div class="col">
outlined color="secondary" <h4 class="q-ma-none q-pb-md text-light">Informations</h4>
stack-label label="Nom" class="q-mb-md" </div>
@update:model-value="changed = true"/> </div>
<q-input v-model="item.owner" <div>
:disable="item.deleted" <q-input v-model="item.name"
outlined color="secondary" stack-label label="Propriétaire" class="q-mb-md" :disable="item.deleted"
@input="changed = true"/> type="text"
<q-field borderless stack-label label="Etat cosmétique" class="q-mb-md"> label="Nom"
<q-btn-toggle outlined color="secondary"
no-caps class="q-mb-md"
:disable="item.deleted" @update:model-value="changed = true">
v-model="item.state" </q-input>
@update:model-value="changed = true" <q-input v-model="item.owner"
toggle-color="secondary" :disable="item.deleted"
text-color="black" outlined color="secondary" stack-label label="Propriétaire" class="q-mb-md"
:options="[ @input="changed = true"/>
<q-field borderless stack-label label="Etat cosmétique" class="q-mb-md">
<q-btn-toggle
class="q-mt-sm"
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: 'Neuf', value: 'Neuf'},
{label: 'Excellent', value: 'Excellent'}, {label: 'Excellent', value: 'Excellent'},
{label: 'Bon', value: 'Bon'}, {label: 'Bon', value: 'Bon'},
@ -50,150 +63,184 @@
{label: 'Abimé', value: 'Abimé'}, {label: 'Abimé', value: 'Abimé'},
{label: 'Inutilisable', value: 'Inutilisable'} {label: 'Inutilisable', value: 'Inutilisable'}
]" ]"
/> />
</q-field> </q-field>
<q-field borderless stack-label label="Fonctionnel" class="q-mb-md"> <q-field borderless stack-label label="Fonctionnel" class="q-mb-md">
<q-btn-toggle <q-btn-toggle
no-caps class="q-mt-sm"
:disable="item.deleted" no-caps
v-model="item.working" :disable="item.deleted"
:toggle-color="item.working ? 'green' : 'red'" v-model="item.working"
text-color="black" :toggle-color="item.working ? 'green' : 'red'"
:options="[ text-color="black"
:options="[
{label: '✔', value: true}, {label: '✔', value: true},
{label: '?', value: null}, {label: '?', value: null},
{label: '✘', value: false} {label: '✘', value: false}
]" ]"
@input="changed = true" @input="changed = true"
/> />
</q-field> </q-field>
<q-field borderless stack-label label="En stock" class="q-mb-md"> <q-field borderless stack-label label="En stock" class="q-mb-md">
<q-btn-toggle <q-btn-toggle
no-caps class="q-mt-sm"
:disable="item.deleted" no-caps
v-model="item.available" :disable="item.deleted"
:toggle-color="item.available ? 'green' : 'red'" v-model="item.available"
text-color="black" :toggle-color="item.available ? 'green' : 'red'"
:options="[ text-color="black"
:options="[
{label: '✔', value: true}, {label: '✔', value: true},
{label: '✘', value: false} {label: '✘', value: false}
]" ]"
@input="changed = true" @input="changed = true"
/> />
</q-field> </q-field>
<q-field borderless stack-label label="Supprimé" class="q-mb-md"> <q-field borderless stack-label label="Supprimé" class="q-mb-md">
<q-btn-toggle <q-btn-toggle
no-caps no-caps
v-model="item.deleted" v-model="item.deleted"
:toggle-color="item.deleted ? 'green' : 'red'" :toggle-color="item.deleted ? 'green' : 'red'"
text-color="black" text-color="black"
:options="[ :options="[
{label: 'Oui', value: true}, {label: 'Oui', value: true},
{label: 'Non', value: false} {label: 'Non', value: false}
]" ]"
@input="changed = true" @input="changed = true"
/> class="q-mt-sm"
</q-field> />
<q-input </q-field>
:disable="item.deleted" <q-input
v-model="item.comment" :disable="item.deleted"
@update:model-value="changed = true" v-model="item.comment"
type="textarea" @update:model-value="changed = true"
:max-height="100" type="textarea"
rows="4" :max-height="100"
outlined color="secondary" stack-label label="Commentaire" class="q-mb-md" rows="5"
inverted outlined color="secondary" stack-label label="Commentaire" class="q-mb-md"
/> inverted
<q-btn :disabled="!changed || originalItem.deleted" />
@click="updateItem()" <q-btn :disabled="!changed || originalItem.deleted"
color="primary" @click="updateItem()"
:flat="!changed" color="primary"
>Enregistrer</q-btn> :flat="!changed"
>Enregistrer</q-btn>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="row">
<div class="col">
<h4 class="q-ma-none q-pb-md text-light">Historique</h4>
</div>
<div class="col text-right">
<q-btn outline icon="add" label="ajouter une entrée" color="primary" @click="openHistoryForm()"></q-btn>
</div>
</div>
<q-list bordered separator class="rounded-borders">
<q-item clickable
v-for="event in history"
:key="event.id"
@click="showHistory = true;selectedHistory = event"
>
<q-item-section top thumbnail class="q-ml-none" style="margin:-8px 0 -8px -16px">
<img
:src="getImageUrl(event, event.image_file, true)"
alt="photo"
v-if="event.image_file"
/>
</q-item-section>
<q-item-section>
<q-item-label lines="3" v-html="event.text.split('\n').join('</br>')"></q-item-label>
<q-item-label caption>
<q-icon name="task" class="q-mr-sm" size="xs"
v-if="event.type === 'issue'"
:color="event.solved_at ? 'green' : 'grey'"
/>
<span class="text-capitalize">{{ event.user }}</span>
<template v-if="event.solved_at">
<q-icon name="arrow_right" size="sm"/>
fait par <span class="text-capitalize">{{ event.solved_by }}</span> le {{getDate(event.solved_at)}}
</template>
</q-item-label>
</q-item-section>
<q-item-section side top>
<q-item-label caption>{{ getDate(event.date || event.created) }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</div>
</div> </div>
<br/> <q-dialog v-model="showHistoryForm">
<br/> <q-card>
<q-card-section>
<div class="text-h6">Ajouter une entrée</div>
</q-card-section>
<q-card-section>
<q-input
v-model="pendingHistory"
autofocus
type="textarea"
label="Description"
:max-height="100"
rows="3"
invertedd
color="secondary"
/>
<br/>
<q-btn-group outline spread>
<q-btn :outline="pendingHistoryType !== 'comment'" color="secondary" label="Info" icon="info" @click="pendingHistoryType = 'comment'"/>
<q-btn :outline="pendingHistoryType !== 'issue'" color="secondary" label="Tâche" icon="task" @click="pendingHistoryType = 'issue'"/>
</q-btn-group>
<br/>
<q-uploader
:accept="'.jpg,.png'"
:factory="uploadFile"
label="Uploader une photo"
@added="hasFile = true"
ref="uploader"
no-thumbnails
hide-upload-btn
flat
bordered
color="secondary"
/>
<br/>
</q-card-section>
<q-separator></q-separator>
<q-card-actions align="right">
<q-btn flat color="grey" @click.stop="showHistoryForm = false">Annuler</q-btn>
<q-btn flat color="primary" @click.stop="addHistory()">Ok</q-btn>
</q-card-actions>
</q-card>
</q-dialog>
<h4 class="q-ma-none q-pb-md text-light">Historique</h4> <q-dialog v-model="showHistory">
<q-timeline color="secondary"> <q-card>
<!--<q-timeline-entry heading>--> <img :src="getImageUrl(selectedHistory, selectedHistory.image_file)"
<!--Historique--> alt="photo"
<!--</q-timeline-entry>--> v-if="selectedHistory.image_file"
<q-timeline-entry />
title="Ajouter une entrée" <q-card-section>
side="left" <div v-html="selectedHistory.text.split('\n').join('</br>')"></div>
icon="add" <div class="text-caption text-grey">
color="primary" <span class="text-capitalize">{{ selectedHistory.user }}</span>
@click="openHistoryForm()" <q-separator vertical />
style="cursor:pointer" <span>{{ getDate(selectedHistory.date || selectedHistory.created) }}</span>
>
<q-slide-transition>
<div v-show="showHistoryForm">
<q-input
v-model="pendingHistory"
type="textarea"
label="Description"
:max-height="100"
rows="3"
invertedd
color="secondary"
/>
<!-- <q-input label="Date"-->
<!-- v-model="pendingHistoryDate"-->
<!-- type="datetime"-->
<!-- format24h />-->
<br/>
<q-uploader
:accept="'.jpg,.png'"
:factory="uploadFile"
label="Uploader une photo"
@added="hasFile = true"
ref="uploader"
no-thumbnails
hide-upload-btn
flat
color="secondary"
/>
<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> </div>
</q-slide-transition> </q-card-section>
</q-timeline-entry> <q-card-section v-if="selectedHistory.type === 'issue' && !selectedHistory.solved_at">
<q-timeline-entry <q-btn outline color="secondary" label="Marquer comme terminé" @click="setTaskDone(selectedHistory)"></q-btn>
v-for="event in history.slice().reverse()" </q-card-section>
:key="event.id" <q-separator/>
:subtitle="getDate(event.date)" <q-card-actions align="right">
:title="event.user" <q-btn flat color="red" @click.stop="removeHistory(selectedHistory)">Supprimer</q-btn>
side="left" <q-btn flat color="grey" @click.stop="showHistory = false;selectedHistory = null">Fermer</q-btn>
> </q-card-actions>
<div> </q-card>
<q-icon name="format_quote" </q-dialog>
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> </q-page>
</template> </template>
@ -204,17 +251,20 @@ import QRCode from 'qrcode'
export default { export default {
name: 'StockItem', name: 'StockItem',
props: ['itemRef'], props: ['itemRef', 'shortId'],
data () { data () {
return { return {
showHistoryForm: false, showHistoryForm: false,
pendingHistory: null, pendingHistory: null,
pendingHistoryType: 'comment',
changed: false, changed: false,
hasFile: false, hasFile: false,
username: null, username: null,
qrCodeDataUrl: null, qrCodeDataUrl: null,
showQr: false, showQr: false,
item: {} item: {},
showHistory: false,
selectedHistory: null
} }
}, },
computed: { computed: {
@ -243,7 +293,20 @@ export default {
} }
}, },
created: function () { created: function () {
if (this.shortId) {
let itemRef = this.stock.find(item => {
return item.short_id === parseInt(this.shortId)
})?.ref
console.log('Redirecting to item with ref:', itemRef)
if (itemRef) this.$router.push(`/item/${itemRef}`)
}
this.loadItem() this.loadItem()
this.$watch(
() => this.itemRef,
(newId, oldId) => {
this.loadItem()
}
)
}, },
mounted () { mounted () {
this.username = this.$q.localStorage.getItem('username') this.username = this.$q.localStorage.getItem('username')
@ -267,6 +330,7 @@ export default {
data: { data: {
ref: this.itemRef, ref: this.itemRef,
text: this.pendingHistory, text: this.pendingHistory,
type: this.pendingHistoryType,
user: this.$store.state.core.username, user: this.$store.state.core.username,
image_file: file, image_file: file,
date: date.toISOString() date: date.toISOString()
@ -322,6 +386,23 @@ export default {
}) })
}) })
}, },
setTaskDone (history) {
this.$store.dispatch('core/updateItem', {
collection: 'history',
itemId: history.id,
data: {
id: history.id,
solved_at: new Date().toISOString(),
solved_by: this.username
}
}).then(_ => {
this.$store.dispatch('core/loadAppData').then(_ => {
this.loadItem()
this.showHistory = false
this.selectedHistory = null
})
})
},
async uploadFile (file) { async uploadFile (file) {
// Resize and compress the image before uploading // Resize and compress the image before uploading
const options = { const options = {
@ -335,8 +416,8 @@ export default {
console.error('Error compressing image:', error) console.error('Error compressing image:', error)
} }
}, },
getImageUrl (history, image) { getImageUrl (history, image, thumb = false) {
return this.$pb.files.getURL(history, image, { thumb: '300x300f' }) return this.$pb.files.getURL(history, image, { thumb: thumb ? '300x300f' : null })
}, },
loadItem () { loadItem () {
let item = this.stock.find(item => { let item = this.stock.find(item => {
@ -367,5 +448,8 @@ export default {
} }
</script> </script>
<style> <style scoped>
.q-field__label{
padding-bottom:16px !important;
}
</style> </style>

142
src/pages/TasksPage.vue Normal file
View File

@ -0,0 +1,142 @@
<template>
<q-page padding>
<h4 class="q-mt-none">Tâches</h4>
<q-list bordered separator class="rounded-borders">
<q-item clickable
v-for="issue in issues"
:key="issue.id"
@click="showHistory = true;selectedHistory = issue"
>
<q-item-section top thumbnail class="q-ml-none" style="margin:-8px 0 -8px -16px">
<img
:src="getImageUrl(issue, issue.image_file, true)"
alt="photo"
v-if="issue.image_file"
/>
</q-item-section>
<q-item-section avatar>
<q-icon name="task"
:color="issue.solved_at ? 'green' : 'grey'"
/>
</q-item-section>
<q-item-section>
<q-item-label lines="3" v-html="issue.text.split('\n').join('</br>')"></q-item-label>
<q-item-label caption>
<span class="text-secondary">{{ issue.item.type }} / </span>
<span class="text-secondary q-mr-sm">{{ issue.item.name }}</span>
<span v-if="issue.item.ref">#{{ issue.item.ref }}</span>
</q-item-label>
<q-item-label caption>
<span class="text-capitalize">{{ issue.user }}</span>
<template v-if="issue.solved_at">
<q-icon name="arrow_right" size="sm"/>
fait par <span class="text-capitalize">{{ issue.solved_by }}</span> le {{ getDate(issue.solved_at) }}
</template>
</q-item-label>
</q-item-section>
<q-item-section side top>
<q-item-label caption>{{ getDate(issue.created) }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
<q-dialog v-model="showHistory">
<q-card>
<img :src="getImageUrl(selectedHistory, selectedHistory.image_file)"
alt="photo"
v-if="selectedHistory.image_file"
/>
<q-card-section>
<div v-html="selectedHistory.text.split('\n').join('</br>')"></div>
<div class="text-caption text-grey">
<span class="text-secondary">{{ selectedHistory.item.type }} / </span>
<span class="text-secondary q-mr-sm">{{ selectedHistory.item.name }}</span>
<span v-if="selectedHistory.item.ref">#{{ selectedHistory.item.ref }}</span>
<q-separator class="q-my-md"/>
<span class="text-capitalize q-mr-sm">{{ selectedHistory.user }}</span>
<template v-if="selectedHistory.solved_at">
<q-icon name="arrow_right" size="sm"/>
fait par <span class="text-capitalize">{{ selectedHistory.solved_by }}</span> le {{ getDate(selectedHistory.solved_at) }}
</template>
<!-- <span>{{ getDate(selectedHistory.created) }}</span>-->
</div>
</q-card-section>
<q-card-section v-if="!selectedHistory.solved_at">
<q-btn outline color="secondary" label="Marquer comme terminé" @click="setTaskDone(selectedHistory)"></q-btn>
</q-card-section>
<q-separator/>
<q-card-actions align="right">
<q-btn flat color="red" @click.stop="removeHistory(selectedHistory)">Supprimer</q-btn>
<q-btn flat color="grey" @click.stop="showHistory = false;selectedHistory = null">Fermer</q-btn>
</q-card-actions>
</q-card>
</q-dialog>
<!-- <pre>{{ issues }}</pre>-->
</q-page>
</template>
<script>
import { date, debounce } from 'quasar'
export default {
name: 'TasksPage',
data () {
return {
showHistory: false,
selectedHistory: null
}
},
created () {
},
computed: {
rawIssues () {
return this.$store.state.core.firebase.history.filter(item => {
return item.type === 'issue'
}) || []
},
items () {
return this.$store.state.core.firebase.stock.filter(item => {
let refs = Array.from(new Set(this.rawIssues.map(issue => issue.ref)))
return refs.includes(item.ref)
}) || []
},
issues () {
return this.rawIssues.map(issue => {
let item = this.items.find(item => item.ref === issue.ref) || {}
return {
...issue,
item
}
})
}
},
methods: {
getDate (timestamp) {
return date.formatDate(timestamp, 'DD/MM/YY - HH:mm')
},
setTaskDone (history) {
this.$store.dispatch('core/updateItem', {
collection: 'history',
itemId: history.id,
data: {
id: history.id,
solved_at: new Date().toISOString(),
solved_by: this.$store.state.core.username
}
}).then(_ => {
this.$q.notify('Terminé !')
this.$store.dispatch('core/loadAppData').then(_ => {
this.showHistory = false
this.selectedHistory = null
})
})
}
}
}
</script>
<style>
</style>

View File

@ -13,7 +13,9 @@ const routes = [
{ path: 'dashboard', component: () => import('pages/Dashboard.vue') }, { path: 'dashboard', component: () => import('pages/Dashboard.vue') },
{ path: 'category/:categoryCode', props: true, component: () => import('pages/StockItems.vue') }, { path: 'category/:categoryCode', props: true, component: () => import('pages/StockItems.vue') },
{ path: 'item/:itemRef', props: true, component: () => import('pages/StockItem.vue') }, { path: 'item/:itemRef', props: true, component: () => import('pages/StockItem.vue') },
{ path: 'print-settings', props: true, component: () => import('pages/PrintSettings.vue') } { path: 'print-settings', props: true, component: () => import('pages/PrintSettings.vue') },
{ path: 'tasks', props: true, component: () => import('pages/TasksPage.vue') },
{ path: ':shortId', props: true, component: () => import('pages/StockItem.vue') }
] ]
} }
] ]

View File

@ -30,6 +30,14 @@ export function deleteItem (store, data) {
return pb.collection(data.collection).delete(data.data.id) return pb.collection(data.collection).delete(data.data.id)
} }
export function getNextShortId (store) {
return pb.collection('stock').getFirstListItem('short_id>0', {
sort: '-short_id'
}).then(item => {
return item.short_id + 1
})
}
// export function uploadFile (store, file) { // export function uploadFile (store, file) {
// return Firebase.storage().ref().child(Date.now() + file.name).put(file) // return Firebase.storage().ref().child(Date.now() + file.name).put(file)
// } // }