Compare commits
9 Commits
2b5692399c
...
master
Author | SHA1 | Date | |
---|---|---|---|
ebef3ee901 | |||
d656bb15ff | |||
9fe860b526 | |||
a7225eb054 | |||
6459dbd68c | |||
361bb0c1ed | |||
892158fc30 | |||
96899a8477 | |||
08cf066c88 |
@ -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
1
.gitignore
vendored
@ -33,3 +33,4 @@ yarn-error.log*
|
|||||||
.env.local*
|
.env.local*
|
||||||
|
|
||||||
pocketbase/pb_data/*
|
pocketbase/pb_data/*
|
||||||
|
pocketbase/pocketbase.exe
|
||||||
|
@ -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
|
||||||
|
@ -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
BIN
public/font/Geist-Black.otf
Normal file
Binary file not shown.
BIN
public/font/Geist-Bold.otf
Normal file
BIN
public/font/Geist-Bold.otf
Normal file
Binary file not shown.
BIN
public/font/Geist-ExtraBold.otf
Normal file
BIN
public/font/Geist-ExtraBold.otf
Normal file
Binary file not shown.
BIN
public/font/Geist-ExtraLight.otf
Normal file
BIN
public/font/Geist-ExtraLight.otf
Normal file
Binary file not shown.
BIN
public/font/Geist-Light.otf
Normal file
BIN
public/font/Geist-Light.otf
Normal file
Binary file not shown.
BIN
public/font/Geist-Medium.otf
Normal file
BIN
public/font/Geist-Medium.otf
Normal file
Binary file not shown.
BIN
public/font/Geist-Regular.otf
Normal file
BIN
public/font/Geist-Regular.otf
Normal file
Binary file not shown.
BIN
public/font/Geist-SemiBold.otf
Normal file
BIN
public/font/Geist-SemiBold.otf
Normal file
Binary file not shown.
BIN
public/font/Geist-Thin.otf
Normal file
BIN
public/font/Geist-Thin.otf
Normal file
Binary file not shown.
BIN
public/font/Geist-var.ttf
Normal file
BIN
public/font/Geist-var.ttf
Normal file
Binary file not shown.
@ -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'
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -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')
|
||||||
|
@ -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')
|
||||||
|
@ -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/'
|
||||||
|
@ -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
31
scripts/renumerate.js
Normal 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()
|
@ -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(
|
||||||
|
@ -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)`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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
BIN
src/css/font/Geist-var.ttf
Normal file
Binary file not shown.
@ -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
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
142
src/pages/TasksPage.vue
Normal 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>
|
@ -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') }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -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)
|
||||||
// }
|
// }
|
||||||
|
Reference in New Issue
Block a user