0
1
Fork 0

Merge pull request 'Init config' (!68) from wbuezas/hedera-web-mindshore:feature/InitConfig into 4922-vueMigration

Reviewed-on: verdnatura/hedera-web#68
Reviewed-by: Javier Segarra <jsegarra@verdnatura.es>
This commit is contained in:
Javier Segarra 2024-07-19 11:13:55 +00:00
commit ce557dc5b9
45 changed files with 29084 additions and 28301 deletions

View File

@ -7,15 +7,14 @@ module.exports = {
parserOptions: { parserOptions: {
parser: '@babel/eslint-parser', parser: '@babel/eslint-parser',
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
sourceType: 'module' // Allows for the use of imports sourceType: 'module', // Allows for the use of imports
}, },
env: { env: {
browser: true, browser: true,
'vue/setup-compiler-macros': true 'vue/setup-compiler-macros': true,
}, },
// Rules order is important, please avoid shuffling them
extends: [ extends: [
// Base ESLint recommended rules // Base ESLint recommended rules
// 'eslint:recommended', // 'eslint:recommended',
@ -27,16 +26,10 @@ module.exports = {
// 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability) // 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
// 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) // 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
'standard' 'standard',
], ],
plugins: [ plugins: ['vue', 'prettier'],
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
// required to lint *.vue files
'vue',
],
globals: { globals: {
ga: 'readonly', // Google Analytics ga: 'readonly', // Google Analytics
@ -48,12 +41,11 @@ module.exports = {
__QUASAR_SSR_PWA__: 'readonly', __QUASAR_SSR_PWA__: 'readonly',
process: 'readonly', process: 'readonly',
Capacitor: 'readonly', Capacitor: 'readonly',
chrome: 'readonly' chrome: 'readonly',
}, },
// add your custom rules here // add your custom rules here
rules: { rules: {
// allow async-await // allow async-await
'generator-star-spacing': 'off', 'generator-star-spacing': 'off',
// allow paren-less arrow functions // allow paren-less arrow functions
@ -72,8 +64,19 @@ module.exports = {
'import/no-extraneous-dependencies': 'off', 'import/no-extraneous-dependencies': 'off',
'prefer-promise-reject-errors': 'off', 'prefer-promise-reject-errors': 'off',
semi: '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',
} },
} overrides: [
{
extends: ['plugin:vue/vue3-essential'],
files: ['src/**/*.{js,vue,scss}'], // Aplica ESLint solo a archivos .js, .vue y .scss dentro de src (Proyecto de quasar)
rules: {
semi: 'off',
indent: ['error', 4, { SwitchCase: 1 }],
'space-before-function-paren': 'off',
},
},
],
};

9
.prettierrc.js Normal file
View File

@ -0,0 +1,9 @@
module.exports = {
printWidth: 80,
tabWidth: 4,
useTabs: false,
singleQuote: true,
bracketSpacing: true,
arrowParens: 'avoid',
trailingComma: 'none'
};

13
.vscode/settings.json vendored
View File

@ -4,14 +4,7 @@
"editor.bracketPairColorization.enabled": true, "editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true, "editor.guides.bracketPairs": true,
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.defaultFormatter": "dbaeumer.vscode-eslint", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": [ "editor.codeActionsOnSave": ["source.fixAll.eslint"],
"source.fixAll.eslint" "eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"]
],
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"vue"
]
} }

98
Jenkinsfile vendored
View File

@ -1,34 +1,25 @@
#!/usr/bin/env groovy #!/usr/bin/env groovy
def BRANCH_ENV = [
test: 'test',
master: 'production'
]
def remote = [:]
node {
stage('Setup') {
env.NODE_ENV = BRANCH_ENV[env.BRANCH_NAME] ?: 'dev'
echo "NODE_NAME: ${env.NODE_NAME}"
echo "WORKSPACE: ${env.WORKSPACE}"
}
}
pipeline { pipeline {
agent any agent any
environment { environment {
PROJECT_NAME = 'hedera-web' PROJECT_NAME = 'hedera-web'
STACK_NAME = "${env.PROJECT_NAME}-${env.BRANCH_NAME}"
} }
stages { stages {
stage('Checkout') {
steps {
script {
def packageJson = readJSON file: 'package.json'
env.VERSION = packageJson.version
switch (env.BRANCH_NAME) {
case 'master':
env.NODE_ENV = 'production'
env.MAIN_REPLICAS = 3
env.CRON_REPLICAS = 1
break
case 'test':
env.NODE_ENV = 'test'
env.MAIN_REPLICAS = 1
env.CRON_REPLICAS = 0
break
}
}
setEnv()
}
}
stage('Debuild') { stage('Debuild') {
when { when {
anyOf { anyOf {
@ -38,31 +29,28 @@ pipeline {
} }
agent { agent {
docker { docker {
image 'registry.verdnatura.es/debuild:2.21.3-vn2' image 'registry.verdnatura.es/verdnatura/debuild:2.23.4-vn7'
registryUrl 'https://registry.verdnatura.es/' registryUrl 'https://registry.verdnatura.es/'
registryCredentialsId 'docker-registry' registryCredentialsId 'docker-registry'
args '-v /mnt/appdata/reprepro:/reprepro'
} }
} }
steps { steps {
sh 'debuild -us -uc -b' sh 'debuild -us -uc -b'
sh 'vn-includedeb stretch' sh 'mkdir -p debuild'
sh 'mv ../hedera-web_* debuild'
script {
def files = findFiles(glob: 'debuild/*.changes')
files.each { file -> env.CHANGES_FILE = file.name }
}
configFileProvider([
configFile(fileId: "dput.cf", variable: 'DPUT_CONFIG')
]) {
sshagent(credentials: ['jenkins-agent']) {
sh 'dput --config "$DPUT_CONFIG" verdnatura "debuild/$CHANGES_FILE"'
} }
} }
stage('Container') {
when {
anyOf {
branch 'master'
branch 'test'
}
}
environment {
CREDS = credentials('docker-registry')
}
steps {
sh 'docker login --username $CREDS_USR --password $CREDS_PSW $REGISTRY'
sh 'docker-compose build --build-arg BUILD_ID=$BUILD_ID --parallel'
sh 'docker-compose push'
} }
} }
stage('Deploy') { stage('Deploy') {
@ -73,15 +61,41 @@ pipeline {
} }
} }
environment { environment {
DOCKER_HOST = "${env.SWARM_HOST}" CREDS = credentials('docker-registry')
IMAGE = "$REGISTRY/verdnatura/hedera-web"
} }
steps { steps {
sh "docker stack deploy --with-registry-auth --compose-file docker-compose.yml ${env.STACK_NAME}" script {
def packageJson = readJSON file: 'package.json'
env.VERSION = "${packageJson.version}"
env.TAG = "${packageJson.version}-build${env.BUILD_ID}"
}
sh 'docker-compose build --build-arg BUILD_ID=$BUILD_ID --parallel'
sh 'docker login --username $CREDS_USR --password $CREDS_PSW $REGISTRY'
sh 'docker push $IMAGE:$TAG'
script {
if (env.BRANCH_NAME == 'master') {
sh 'docker tag $IMAGE:$TAG $IMAGE:latest'
sh 'docker push $IMAGE:latest'
}
}
withKubeConfig([
serverUrl: "$KUBERNETES_API",
credentialsId: 'kubernetes',
namespace: 'salix'
]) {
sh 'kubectl set image deployment/hedera-web-$BRANCH_NAME hedera-web-$BRANCH_NAME=$IMAGE:$TAG'
sh 'kubectl set image deployment/hedera-web-cron-$BRANCH_NAME hedera-web-cron-$BRANCH_NAME=$IMAGE:$TAG'
}
} }
} }
} }
post { post {
unsuccessful { unsuccessful {
setEnv()
sendEmail() sendEmail()
} }
} }

1098
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,8 @@
"version": "22.48.2", "version": "22.48.2",
"description": "Verdnatura web page", "description": "Verdnatura web page",
"license": "GPL-3.0", "license": "GPL-3.0",
"author": "Juan Ferrer Toribio <juan@verdnatura.es>", "productName": "Salix",
"author": "Verdnatura",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.verdnatura.es/hedera-web" "url": "https://git.verdnatura.es/hedera-web"
@ -17,12 +18,15 @@
"assets-webpack-plugin": "^7.1.1", "assets-webpack-plugin": "^7.1.1",
"babel-loader": "^9.1.0", "babel-loader": "^9.1.0",
"bundle-loader": "^0.5.6", "bundle-loader": "^0.5.6",
"eslint": "^8.10.0", "css-loader": "^5.2.7",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.0.0", "eslint-config-standard": "^17.0.0",
"eslint-plugin-import": "^2.19.1", "eslint-plugin-import": "^2.19.1",
"eslint-plugin-n": "^15.0.0", "eslint-plugin-n": "^15.0.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-promise": "^6.0.0", "eslint-plugin-promise": "^6.0.0",
"eslint-plugin-vue": "^9.0.0", "eslint-plugin-vue": "^9.27.0",
"eslint-webpack-plugin": "^3.1.1", "eslint-webpack-plugin": "^3.1.1",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
@ -31,6 +35,10 @@
"json-loader": "^0.5.7", "json-loader": "^0.5.7",
"mini-css-extract-plugin": "^2.7.0", "mini-css-extract-plugin": "^2.7.0",
"node-sass": "^7.0.1", "node-sass": "^7.0.1",
"postcss": "^8.4.39",
"postcss-import": "^13.0.0",
"postcss-loader": "^4.3.0",
"postcss-url": "^10.1.3",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",
"sass-loader": "^12.6.0", "sass-loader": "^12.6.0",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",

View File

@ -104,15 +104,14 @@ module.exports = configure(function (ctx) {
type: 'http' type: 'http'
}, },
port: 8080, port: 8080,
open: true, // opens browser window automatically open: false,
// static: __dirname, // static: __dirname,
headers: { 'Access-Control-Allow-Origin': '*' }, headers: { 'Access-Control-Allow-Origin': '*' },
// stats: { chunks: false }, // stats: { chunks: false },
proxy: { proxy: {
'/api': 'http://localhost:3000', '/api': 'http://localhost:3000',
'/': { '/': {
target: 'http://localhost/projects/hedera-web', target: 'http://localhost:3001',
bypass: (req) => req.path !== '/' ? req.path : null bypass: (req) => req.path !== '/' ? req.path : null
} }
} }
@ -121,7 +120,7 @@ module.exports = configure(function (ctx) {
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-framework // https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-framework
framework: { framework: {
config: {}, config: {},
autoImportComponentCase: 'pascal',
// iconSet: 'material-icons', // Quasar icon set // iconSet: 'material-icons', // Quasar icon set
// lang: 'en-US', // Quasar language pack // lang: 'en-US', // Quasar language pack

View File

@ -3,9 +3,9 @@
</template> </template>
<script> <script>
import { defineComponent } from 'vue' import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'App' name: 'App'
}) });
</script> </script>

View File

@ -1,4 +1,3 @@
export default async ({ app }) => { export default async ({ app }) => {
/* /*
window.addEventListener('error', window.addEventListener('error',

View File

@ -1,18 +1,23 @@
import { boot } from 'quasar/wrappers' import { boot } from 'quasar/wrappers';
import { createI18n } from 'vue-i18n' import { createI18n } from 'vue-i18n';
import messages from 'src/i18n' import messages from 'src/i18n';
export default boot(({ app }) => {
const i18n = createI18n({ const i18n = createI18n({
locale: 'es-ES', locale: navigator.language || navigator.userLanguage,
fallbackLocale: 'en',
globalInjection: true, globalInjection: true,
missingWarn: false,
fallbackWarn: false,
legacy: false,
silentTranslationWarn: true, silentTranslationWarn: true,
silentFallbackWarn: true, silentFallbackWarn: true,
messages messages
}) });
export default boot(({ app }) => {
// Set i18n instance on app // Set i18n instance on app
app.use(i18n) app.use(i18n);
window.i18n = i18n.global window.i18n = i18n.global;
}) });
export { i18n };

View File

@ -28,7 +28,7 @@ a.link {
} }
.q-card { .q-card {
border-radius: 7px; border-radius: 7px;
box-shadow: 0 0 3px rgba(0, 0, 0, .1); box-shadow: 0 0 3px rgba(0, 0, 0, 0.1);
} }
.q-page-sticky.fixed-bottom-right { .q-page-sticky.fixed-bottom-right {
margin: 18px; margin: 18px;

View File

@ -12,17 +12,17 @@
// to match your app's branding. // to match your app's branding.
// Tip: Use the "Theme Builder" on Quasar's documentation website. // Tip: Use the "Theme Builder" on Quasar's documentation website.
$primary : #1A1A1A; $primary: #1a1a1a;
$secondary : #26A69A; $secondary: #26a69a;
$accent: #8cc63f; $accent: #8cc63f;
$dark : #1D1D1D; $dark: #1d1d1d;
$dark-page: #121212; $dark-page: #121212;
$positive : #21BA45; $positive: #21ba45;
$negative : #C10015; $negative: #c10015;
$info : #31CCEC; $info: #31ccec;
$warning : #F2C037; $warning: #f2c037;
// Width // Width

View File

@ -1,4 +1,3 @@
%margin-auto { %margin-auto {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;

View File

@ -22,15 +22,7 @@ export default {
'Friday', 'Friday',
'Saturday' 'Saturday'
], ],
daysShort: [ daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
'Sun',
'Mon',
'Tue',
'Wed',
'Thu',
'Fri',
'Sat'
],
months: [ months: [
'January', 'January',
'February', 'February',

View File

@ -22,15 +22,7 @@ export default {
'Viernes', 'Viernes',
'Sábado' 'Sábado'
], ],
daysShort: [ daysShort: ['Do', 'Lu', 'Mi', 'Mi', 'Ju', 'Vi', 'Sa'],
'Do',
'Lu',
'Mi',
'Mi',
'Ju',
'Vi',
'Sa'
],
months: [ months: [
'Enero', 'Enero',
'Febrero', 'Febrero',

View File

@ -57,8 +57,10 @@ export class Connection extends JsonConnection {
}) })
for (let j = 0; j < rows.length; j++) { for (let j = 0; j < rows.length; j++) {
const row = data[j] = {} const row = (data[j] = {})
for (let k = 0; k < columns.length; k++) { row[columns[k].name] = rows[j][k] } for (let k = 0; k < columns.length; k++) {
row[columns[k].name] = rows[j][k]
}
} }
for (let j = 0; j < columns.length; j++) { for (let j = 0; j < columns.length; j++) {
@ -74,14 +76,20 @@ export class Connection extends JsonConnection {
} }
if (castFunc !== null) { if (castFunc !== null) {
if (col.def != null) { col.def = castFunc(col.def) } if (col.def != null) {
col.def = castFunc(col.def)
}
for (let k = 0; k < data.length; k++) { for (let k = 0; k < data.length; k++) {
if (data[k][col.name] != null) { data[k][col.name] = castFunc(data[k][col.name]) } if (data[k][col.name] != null) {
data[k][col.name] = castFunc(data[k][col.name])
} }
} }
} }
} else { results.push(json[i]) } }
} else {
results.push(json[i])
}
} }
} }
} catch (e) { } catch (e) {
@ -100,7 +108,7 @@ export class Connection extends JsonConnection {
* @return {ResultSet} The result * @return {ResultSet} The result
*/ */
async execQuery (query, params) { async execQuery (query, params) {
const sql = query.replace(/#\w+/g, key => { const sql = query.replace(/#\w+/g, (key) => {
const value = params[key.substring(1)] const value = params[key.substring(1)]
return value ? this.renderValue(value) : key return value ? this.renderValue(value) : key
}) })
@ -128,7 +136,7 @@ export class Connection extends JsonConnection {
case 'number': case 'number':
return v return v
case 'boolean': case 'boolean':
return (v) ? 'TRUE' : 'FALSE' return v ? 'TRUE' : 'FALSE'
case 'string': case 'string':
return "'" + v.replace(this.regexp, this.replaceFunc) + "'" return "'" + v.replace(this.regexp, this.replaceFunc) + "'"
default: default:
@ -136,8 +144,12 @@ export class Connection extends JsonConnection {
if (!isNaN(v.getTime())) { if (!isNaN(v.getTime())) {
const unixTime = parseInt(fixTz(v).getTime() / 1000) const unixTime = parseInt(fixTz(v).getTime() / 1000)
return 'DATE(FROM_UNIXTIME(' + unixTime + '))' return 'DATE(FROM_UNIXTIME(' + unixTime + '))'
} else { return '0000-00-00' } } else {
} else { return 'NULL' } return '0000-00-00'
}
} else {
return 'NULL'
}
} }
} }
@ -151,16 +163,14 @@ export class Connection extends JsonConnection {
// TODO: Read time zone from db configuration // TODO: Read time zone from db configuration
const tz = { timeZone: 'Europe/Madrid' } const tz = { timeZone: 'Europe/Madrid' }
const isLocal = Intl const isLocal = Intl.DateTimeFormat().resolvedOptions().timeZone === tz.timeZone
.DateTimeFormat()
.resolvedOptions()
.timeZone === tz.timeZone
function fixTz (date) { function fixTz (date) {
if (isLocal) return date if (isLocal) return date
const localDate = new Date(date.toLocaleString('en-US', tz)) const localDate = new Date(date.toLocaleString('en-US', tz))
const hasTime = localDate.getHours() || const hasTime =
localDate.getHours() ||
localDate.getMinutes() || localDate.getMinutes() ||
localDate.getSeconds() || localDate.getSeconds() ||
localDate.getMilliseconds() localDate.getMilliseconds()

View File

@ -1,4 +1,3 @@
import { Result } from './result' import { Result } from './result'
/** /**
@ -26,10 +25,13 @@ export class ResultSet {
} }
fetch () { fetch () {
if (this.error) { throw this.error } if (this.error) {
throw this.error
}
if (this.results !== null && if (this.results !== null && this.results.length > 0) {
this.results.length > 0) { return this.results.shift() } return this.results.shift()
}
return null return null
} }
@ -61,9 +63,13 @@ export class ResultSet {
fetchObject () { fetchObject () {
const result = this.fetch() const result = this.fetch()
if (result !== null && if (
result !== null &&
result.data instanceof Array && result.data instanceof Array &&
result.data.length > 0) { return result.data[0] } result.data.length > 0
) {
return result.data[0]
}
return null return null
} }
@ -76,8 +82,7 @@ export class ResultSet {
fetchData () { fetchData () {
const result = this.fetch() const result = this.fetch()
if (result !== null && if (result !== null && result.data instanceof Array) {
result.data instanceof Array) {
return result.data return result.data
} }
@ -92,7 +97,9 @@ export class ResultSet {
fetchValue () { fetchValue () {
const row = this.fetchRow() const row = this.fetchRow()
if (row instanceof Array && row.length > 0) { return row[0] } if (row instanceof Array && row.length > 0) {
return row[0]
}
return null return null
} }
@ -105,9 +112,11 @@ export class ResultSet {
fetchRow () { fetchRow () {
const result = this.fetch() const result = this.fetch()
if (result !== null && if (
result !== null &&
result.data instanceof Array && result.data instanceof Array &&
result.data.length > 0) { result.data.length > 0
) {
const object = result.data[0] const object = result.data[0]
const row = new Array(result.columns.length) const row = new Array(result.columns.length)
for (let i = 0; i < row.length; i++) { for (let i = 0; i < row.length; i++) {

View File

@ -19,7 +19,9 @@ export class Result {
col.index = i col.index = i
this.columnMap[col.name] = col this.columnMap[col.name] = col
} }
} else { this.columnMap = null } } else {
this.columnMap = null
}
} }
/** /**
@ -54,7 +56,9 @@ export class Result {
next () { next () {
this.row++ this.row++
if (this.row >= this.data.length) { return false } if (this.row >= this.data.length) {
return false
}
return true return true
} }

View File

@ -1,4 +1,3 @@
import { VnObject } from './object' import { VnObject } from './object'
import { JsonException } from './json-exception' import { JsonException } from './json-exception'
@ -34,7 +33,9 @@ export class JsonConnection extends VnObject {
const elements = form.elements const elements = form.elements
for (let i = 0; i < elements.length; i++) { for (let i = 0; i < elements.length; i++) {
if (elements[i].name) { params[elements[i].name] = elements[i].value } if (elements[i].name) {
params[elements[i].name] = elements[i].value
}
} }
return this.sendWithUrl('POST', form.action, params) return this.sendWithUrl('POST', form.action, params)
@ -93,32 +94,39 @@ export class JsonConnection extends VnObject {
} }
const promise = new Promise((resolve, reject) => { const promise = new Promise((resolve, reject) => {
request.onreadystatechange = request.onreadystatechange = () =>
() => this._onStateChange(request, resolve, reject) this._onStateChange(request, resolve, reject)
}) })
request.send(config.data) request.send(config.data)
this._requestsCount++ this._requestsCount++
if (this._requestsCount === 1) { this.emit('loading-changed', true) } if (this._requestsCount === 1) {
this.emit('loading-changed', true)
}
return promise return promise
} }
_onStateChange (request, resolve, reject) { _onStateChange (request, resolve, reject) {
if (request.readyState !== 4) { return } if (request.readyState !== 4) {
return
}
this._requestsCount-- this._requestsCount--
if (this._requestsCount === 0) { this.emit('loading-changed', false) } if (this._requestsCount === 0) {
this.emit('loading-changed', false)
}
let data = null let data = null
let error = null let error = null
try { try {
if (request.status === 0) { if (request.status === 0) {
const err = new JsonException() const err = new JsonException()
err.message = 'The server does not respond, please check your Internet connection' err.message =
'The server does not respond, please check your Internet connection'
err.statusCode = request.status err.statusCode = request.status
throw err throw err
} }
@ -144,8 +152,12 @@ export class JsonConnection extends VnObject {
let json let json
let jsData let jsData
if (request.responseText) { json = JSON.parse(request.responseText) } if (request.responseText) {
if (json) { jsData = json.data || json } json = JSON.parse(request.responseText)
}
if (json) {
jsData = json.data || json
}
if (request.status >= 200 && request.status < 300) { if (request.status >= 200 && request.status < 300) {
data = jsData data = jsData
@ -181,6 +193,8 @@ export class JsonConnection extends VnObject {
if (error) { if (error) {
this.emit('error', error) this.emit('error', error)
reject(error) reject(error)
} else { resolve(data) } } else {
resolve(data)
}
} }
} }

View File

@ -1,4 +1,3 @@
/** /**
* The main base class. Manages the signal system. Objects based on this class * The main base class. Manages the signal system. Objects based on this class
* can be instantiated declaratively using XML. * can be instantiated declaratively using XML.
@ -45,7 +44,9 @@ export class VnObject {
* @param {Object} props Properties * @param {Object} props Properties
*/ */
setProperties (props) { setProperties (props) {
for (const prop in props) { this[prop] = props[prop] } for (const prop in props) {
this[prop] = props[prop]
}
} }
/** /**
@ -62,7 +63,9 @@ export class VnObject {
unref () { unref () {
this._refCount-- this._refCount--
if (this._refCount === 0) { this._destroy() } if (this._refCount === 0) {
this._destroy()
}
} }
/** /**
@ -91,14 +94,16 @@ export class VnObject {
*/ */
on (id, callback, instance) { on (id, callback, instance) {
if (!(callback instanceof Function)) { if (!(callback instanceof Function)) {
console.warn('Vn.Object: Invalid callback for signal \'%s\'', id) console.warn("Vn.Object: Invalid callback for signal '%s'", id)
return return
} }
this._signalInit() this._signalInit()
let callbacks = this._thisArg.signals[id] let callbacks = this._thisArg.signals[id]
if (!callbacks) { callbacks = this._thisArg.signals[id] = [] } if (!callbacks) {
callbacks = this._thisArg.signals[id] = []
}
callbacks.push({ callbacks.push({
blocked: false, blocked: false,
@ -115,15 +120,23 @@ export class VnObject {
* @param {boolean} block %true for lock the signal, %false for unlock * @param {boolean} block %true for lock the signal, %false for unlock
*/ */
blockSignal (id, callback, block, instance) { blockSignal (id, callback, block, instance) {
if (!this._thisArg) { return } if (!this._thisArg) {
return
}
const callbacks = this._thisArg.signals[id] const callbacks = this._thisArg.signals[id]
if (!callbacks) { return } if (!callbacks) {
return
}
for (let i = 0; i < callbacks.length; i++) { for (let i = 0; i < callbacks.length; i++) {
if (callbacks[i].callback === callback && if (
callbacks[i].instance === instance) { callbacks[i].blocked = block } callbacks[i].callback === callback &&
callbacks[i].instance === instance
) {
callbacks[i].blocked = block
}
} }
} }
@ -133,19 +146,27 @@ export class VnObject {
* @param {string} id The signal identifier * @param {string} id The signal identifier
*/ */
emit (id) { emit (id) {
if (!this._thisArg) { return } if (!this._thisArg) {
return
}
const callbacks = this._thisArg.signals[id] const callbacks = this._thisArg.signals[id]
if (!callbacks) { return } if (!callbacks) {
return
}
const callbackArgs = [] const callbackArgs = []
callbackArgs.push(this) callbackArgs.push(this)
for (let i = 1; i < arguments.length; i++) { callbackArgs.push(arguments[i]) } for (let i = 1; i < arguments.length; i++) {
callbackArgs.push(arguments[i])
}
for (let i = 0; i < callbacks.length; i++) { for (let i = 0; i < callbacks.length; i++) {
if (!callbacks[i].blocked) { callbacks[i].callback.apply(callbacks[i].instance, callbackArgs) } if (!callbacks[i].blocked) {
callbacks[i].callback.apply(callbacks[i].instance, callbackArgs)
}
} }
} }
@ -157,14 +178,20 @@ export class VnObject {
* @param {Object} instance The instance * @param {Object} instance The instance
*/ */
disconnect (id, callback, instance) { disconnect (id, callback, instance) {
if (!this._thisArg) { return } if (!this._thisArg) {
return
}
const callbacks = this._thisArg.signals[id] const callbacks = this._thisArg.signals[id]
if (callbacks) { if (callbacks) {
for (let i = callbacks.length; i--;) { for (let i = callbacks.length; i--;) {
if (callbacks[i].callback === callback && if (
callbacks[i].instance === instance) { callbacks.splice(i, 1) } callbacks[i].callback === callback &&
callbacks[i].instance === instance
) {
callbacks.splice(i, 1)
}
} }
} }
} }
@ -175,7 +202,9 @@ export class VnObject {
* @param {Object} instance The instance * @param {Object} instance The instance
*/ */
disconnectByInstance (instance) { disconnectByInstance (instance) {
if (!this._thisArg) { return } if (!this._thisArg) {
return
}
const signals = this._thisArg.signals const signals = this._thisArg.signals
@ -184,7 +213,9 @@ export class VnObject {
if (callbacks) { if (callbacks) {
for (let i = callbacks.length; i--;) { for (let i = callbacks.length; i--;) {
if (callbacks[i].instance === instance) { callbacks.splice(i, 1) } if (callbacks[i].instance === instance) {
callbacks.splice(i, 1)
}
} }
} }
} }
@ -196,11 +227,15 @@ export class VnObject {
* but should always call the parent method. * but should always call the parent method.
*/ */
_destroy () { _destroy () {
if (!this._thisArg) { return } if (!this._thisArg) {
return
}
const links = this._thisArg.links const links = this._thisArg.links
for (const key in links) { this._unlink(links[key]) } for (const key in links) {
this._unlink(links[key])
}
this._thisArg = null this._thisArg = null
} }
@ -219,15 +254,21 @@ export class VnObject {
const newObject = prop[key] const newObject = prop[key]
const oldObject = this[key] const oldObject = this[key]
if (oldObject) { this._unlink(oldObject) } if (oldObject) {
this._unlink(oldObject)
}
this[key] = newObject this[key] = newObject
if (newObject) { if (newObject) {
links[key] = newObject.ref() links[key] = newObject.ref()
for (const signal in handlers) { newObject.on(signal, handlers[signal], this) } for (const signal in handlers) {
} else if (oldObject) { links[key] = undefined } newObject.on(signal, handlers[signal], this)
}
} else if (oldObject) {
links[key] = undefined
}
} }
} }

View File

@ -1,5 +1,8 @@
<template> <template>
<q-layout id="bg" class="fullscreen row justify-center items-center layout-view scroll"> <QLayout
id="bg"
class="fullscreen row justify-center items-center layout-view scroll"
>
<div class="column q-pa-md row items-center justify-center"> <div class="column q-pa-md row items-center justify-center">
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<transition> <transition>
@ -7,7 +10,7 @@
</transition> </transition>
</router-view> </router-view>
</div> </div>
</q-layout> </QLayout>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -27,5 +30,5 @@
<script> <script>
export default { export default {
name: 'LoginLayout' name: 'LoginLayout'
} };
</script> </script>

View File

@ -1,84 +1,80 @@
<template> <template>
<q-layout view="lHh Lpr lFf"> <QLayout view="lHh Lpr lFf">
<q-header reveal> <QHeader>
<q-toolbar> <QToolbar>
<q-btn <QBtn
flat flat
dense dense
round round
icon="menu" icon="menu"
aria-label="Menu" aria-label="Menu"
@click="toggleLeftDrawer"/> @click="toggleLeftDrawer"
<q-toolbar-title> />
<QToolbarTitle>
{{ $app.title }} {{ $app.title }}
<div <div v-if="$app.subtitle" class="subtitle text-caption">
v-if="$app.subtitle"
class="subtitle text-caption">
{{ $app.subtitle }} {{ $app.subtitle }}
</div> </div>
</q-toolbar-title> </QToolbarTitle>
<div id="actions" ref="actions"> <div id="actions" ref="actions"></div>
</div> <QBtn
<q-btn
v-if="$app.useRightDrawer" v-if="$app.useRightDrawer"
@click="$app.rightDrawerOpen = !$app.rightDrawerOpen" @click="$app.rightDrawerOpen = !$app.rightDrawerOpen"
aria-label="Menu" aria-label="Menu"
flat flat
dense dense
round> round
<q-icon name="menu"/> >
</q-btn> <QIcon name="menu" />
</q-toolbar> </QBtn>
</q-header> </QToolbar>
<q-drawer </QHeader>
v-model="leftDrawerOpen" <QDrawer v-model="leftDrawerOpen" :width="250" show-if-above>
:width="250" <QToolbar class="logo">
show-if-above> <img src="statics/logo-dark.svg" />
<q-toolbar class="logo"> </QToolbar>
<img src="statics/logo-dark.svg">
</q-toolbar>
<div class="user-info"> <div class="user-info">
<div> <div>
<span id="user-name">{{(user.nickname)}}</span> <span id="user-name">{{ user.nickname }}</span>
<q-btn flat icon="logout" alt="_Exit" @click="logout()"/> <QBtn flat icon="logout" alt="_Exit" @click="logout()" />
</div> </div>
<div id="supplant" class="supplant"> <div id="supplant" class="supplant">
<span id="supplanted">{{ supplantedUser }}</span> <span id="supplanted">{{ supplantedUser }}</span>
<q-btn flat icon="logout" alt="_Exit"/> <QBtn flat icon="logout" alt="_Exit" />
</div> </div>
</div> </div>
<q-list <QList v-for="item in essentialLinks" :key="item.id">
v-for="item in essentialLinks" <QItem v-if="!item.childs" :to="`/${item.path}`">
:key="item.id"> <QItemSection>
<q-item <QItemLabel>{{ item.description }}</QItemLabel>
v-if="!item.childs" </QItemSection>
:to="`/${item.path}`"> </QItem>
<q-item-section> <QExpansionItem
<q-item-label>{{item.description}}</q-item-label>
</q-item-section>
</q-item>
<q-expansion-item
v-if="item.childs" v-if="item.childs"
:label="item.description" :label="item.description"
expand-separator> expand-separator
<q-list> >
<q-item <QList>
<QItem
v-for="subitem in item.childs" v-for="subitem in item.childs"
:key="subitem.id" :key="subitem.id"
:to="`/${subitem.path}`" :to="`/${subitem.path}`"
class="q-pl-lg"> class="q-pl-lg"
<q-item-section> >
<q-item-label>{{subitem.description}}</q-item-label> <QItemSection>
</q-item-section> <QItemLabel>
</q-item> {{ subitem.description }}
</q-list> </QItemLabel>
</q-expansion-item> </QItemSection>
</q-list> </QItem>
</q-drawer> </QList>
<q-page-container> </QExpansionItem>
</QList>
</QDrawer>
<QPageContainer>
<router-view /> <router-view />
</q-page-container> </QPageContainer>
</q-layout> </QLayout>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -134,7 +130,7 @@
</style> </style>
<style lang="scss"> <style lang="scss">
@import "src/css/responsive"; @import 'src/css/responsive';
.q-drawer { .q-drawer {
.q-item { .q-item {
@ -171,15 +167,15 @@
</style> </style>
<script> <script>
import { defineComponent, ref } from 'vue' import { defineComponent, ref } from 'vue';
import { userStore } from 'stores/user' import { userStore } from 'stores/user';
export default defineComponent({ export default defineComponent({
name: 'MainLayout', name: 'MainLayout',
props: {}, props: {},
setup() { setup() {
const leftDrawerOpen = ref(false) const leftDrawerOpen = ref(false);
return { return {
user: userStore(), user: userStore(),
@ -187,50 +183,52 @@ export default defineComponent({
essentialLinks: ref(null), essentialLinks: ref(null),
leftDrawerOpen, leftDrawerOpen,
toggleLeftDrawer() { toggleLeftDrawer() {
leftDrawerOpen.value = !leftDrawerOpen.value leftDrawerOpen.value = !leftDrawerOpen.value;
}
} }
};
}, },
async mounted() { async mounted() {
this.$refs.actions.appendChild(this.$actions) this.$refs.actions.appendChild(this.$actions);
await this.user.loadData() await this.user.loadData();
await this.$app.loadConfig() await this.$app.loadConfig();
await this.fetchData() await this.fetchData();
}, },
methods: { methods: {
async fetchData() { async fetchData() {
const sections = await this.$jApi.query('SELECT * FROM myMenu') const sections = await this.$jApi.query('SELECT * FROM myMenu');
const sectionMap = new Map() const sectionMap = new Map();
for (const section of sections) { for (const section of sections) {
sectionMap.set(section.id, section) sectionMap.set(section.id, section);
} }
const sectionTree = [] const sectionTree = [];
for (const section of sections) { for (const section of sections) {
const parent = section.parentFk const parent = section.parentFk;
if (parent) { if (parent) {
const parentSection = sectionMap.get(parent) const parentSection = sectionMap.get(parent);
if (!parentSection) continue if (!parentSection) continue;
let childs = parentSection.childs let childs = parentSection.childs;
if (!childs) { childs = parentSection.childs = [] } if (!childs) {
childs.push(section) childs = parentSection.childs = [];
}
childs.push(section);
} else { } else {
sectionTree.push(section) sectionTree.push(section);
} }
} }
this.essentialLinks = sectionTree this.essentialLinks = sectionTree;
}, },
async logout() { async logout() {
this.user.logout() this.user.logout();
this.$router.push('/login') this.$router.push('/login');
} }
} }
}) });
</script> </script>
<i18n lang="yaml"> <i18n lang="yaml">

View File

@ -1,4 +1,3 @@
import { date as qdate, format } from 'quasar' import { date as qdate, format } from 'quasar'
const { pad } = format const { pad } = format
@ -61,7 +60,7 @@ export function elapsedTime (val) {
if (!(val instanceof Date)) { if (!(val instanceof Date)) {
val = new Date(val) val = new Date(val)
} }
const now = (new Date()).getTime() const now = new Date().getTime()
val = Math.floor((now - val.getTime()) / 1000) val = Math.floor((now - val.getTime()) / 1000)
const hours = Math.floor(val / 3600) const hours = Math.floor(val / 3600)

View File

@ -1,31 +1,28 @@
<template> <template>
<div style="padding: 0;"> <div style="padding: 0">
<div class="q-pa-sm row items-start"> <div class="q-pa-sm row items-start">
<div <div class="new-card q-pa-sm" v-for="myNew in news" :key="myNew.id">
class="new-card q-pa-sm" <QCard>
v-for="myNew in news" <QImg :src="`${$app.imageUrl}/news/full/${myNew.image}`">
:key="myNew.id"> </QImg>
<q-card> <QCardSection>
<q-img :src="`${$app.imageUrl}/news/full/${myNew.image}`">
</q-img>
<q-card-section>
<div class="text-h5">{{ myNew.title }}</div> <div class="text-h5">{{ myNew.title }}</div>
</q-card-section> </QCardSection>
<q-card-section class="new-body"> <QCardSection class="new-body">
<div v-html="myNew.text" /> <div v-html="myNew.text" />
</q-card-section> </QCardSection>
</q-card> </QCard>
</div> </div>
</div> </div>
<q-page-sticky> <QPageSticky>
<q-btn <QBtn
fab fab
icon="add_shopping_cart" icon="add_shopping_cart"
color="accent" color="accent"
to="/ecomerce/catalog" to="/ecomerce/catalog"
:title="$t('startOrder')" :title="$t('startOrder')"
/> />
</q-page-sticky> </QPageSticky>
</div> </div>
</template> </template>
@ -54,16 +51,16 @@ export default {
data() { data() {
return { return {
news: [] news: []
} };
}, },
async mounted() { async mounted() {
this.news = await this.$jApi.query( this.news = await this.$jApi.query(
`SELECT title, text, image, id `SELECT title, text, image, id
FROM news FROM news
ORDER BY priority, created DESC` ORDER BY priority, created DESC`
) );
}
} }
};
</script> </script>
<i18n lang="yaml"> <i18n lang="yaml">

View File

@ -1,6 +1,6 @@
<template> <template>
<Teleport :to="$actions"> <Teleport :to="$actions">
<q-input <QInput
:placeholder="$t('search')" :placeholder="$t('search')"
v-model="search" v-model="search"
debounce="500" debounce="500"
@ -8,32 +8,28 @@
rounded rounded
dark dark
dense dense
standout> standout
>
<template v-slot:prepend> <template v-slot:prepend>
<q-icon <QIcon v-if="search === ''" name="search" />
v-if="search === ''" <QIcon
name="search"
/>
<q-icon
v-else v-else
name="clear" name="clear"
class="cursor-pointer" class="cursor-pointer"
@click="search = ''" @click="search = ''"
/> />
</template> </template>
</q-input> </QInput>
<q-btn <QBtn
:icon="$t(viewMode == 'list' ? 'view_list' : 'grid_on')" :icon="$t(viewMode == 'list' ? 'view_list' : 'grid_on')"
:label="$t(viewMode == 'list' ? 'listView' : 'gridView')" :label="$t(viewMode == 'list' ? 'listView' : 'gridView')"
@click="onViewModeClick()" @click="onViewModeClick()"
rounded rounded
no-caps/> no-caps
/>
</Teleport> </Teleport>
<div style="padding-bottom: 5em;"> <div style="padding-bottom: 5em">
<q-drawer <QDrawer v-model="$app.rightDrawerOpen" side="right" :width="250">
v-model="$app.rightDrawerOpen"
side="right"
:width="250">
<div class="q-pa-md"> <div class="q-pa-md">
<div class="basket-info"> <div class="basket-info">
<p>{{ date(new Date()) }}</p> <p>{{ date(new Date()) }}</p>
@ -41,27 +37,26 @@
{{ $t('warehouse') }} {{ $t('warehouse') }}
{{ 'Algemesi' }} {{ 'Algemesi' }}
</p> </p>
<q-btn <QBtn flat rounded no-caps>
flat
rounded
no-caps>
{{ $t('modify') }} {{ $t('modify') }}
</q-btn> </QBtn>
</div> </div>
<div class="q-mt-md"> <div class="q-mt-md">
<div class="q-mb-xs text-grey-7"> <div class="q-mb-xs text-grey-7">
{{ $t('category') }} {{ $t('category') }}
<q-icon <QIcon
v-if="category" v-if="category"
style="font-size: 1.3em;" style="font-size: 1.3em"
name="cancel" name="cancel"
class="cursor-pointer" class="cursor-pointer"
:title="$t('deleteFilter')" :title="$t('deleteFilter')"
@click="$router.push({params: {category: null}})" @click="
$router.push({ params: { category: null } })
"
/> />
</div> </div>
<div class="categories"> <div class="categories">
<q-btn <QBtn
flat flat
round round
class="category q-pa-sm" class="category q-pa-sm"
@ -69,17 +64,17 @@
:class="{ active: category == cat.id }" :class="{ active: category == cat.id }"
:key="cat.id" :key="cat.id"
:title="cat.name" :title="cat.name"
:to="{params: {category: cat.id, type: null}}"> :to="{ params: { category: cat.id, type: null } }"
<img :src="`statics/category/${cat.code}.svg`"> >
</q-btn> <img :src="`statics/category/${cat.code}.svg`" />
</QBtn>
</div> </div>
</div> </div>
<div class="q-mt-md" <div class="q-mt-md" v-if="category || search">
v-if="category || search">
<div class="q-mb-xs text-grey-7"> <div class="q-mb-xs text-grey-7">
{{ $t('filterBy') }} {{ $t('filterBy') }}
</div> </div>
<q-select <QSelect
v-model="type" v-model="type"
option-value="id" option-value="id"
option-label="name" option-label="name"
@ -88,9 +83,11 @@
clearable clearable
:label="$t('family')" :label="$t('family')"
@filter="filterType" @filter="filterType"
@input="$router.push({params: {type: type && type.id}})" @input="
$router.push({ params: { type: type && type.id } })
"
/> />
<q-select <QSelect
v-model="order" v-model="order"
input-debounce="0" input-debounce="0"
:options="orderOptions" :options="orderOptions"
@ -98,16 +95,13 @@
/> />
</div> </div>
</div> </div>
<div class="q-pa-md" <div class="q-pa-md" v-if="typeId || search">
v-if="typeId || search"> <div class="q-mb-md" v-for="tag in tags" :key="tag.uid">
<div class="q-mb-md"
v-for="tag in tags"
:key="tag.uid">
<div class="q-mb-xs text-caption text-grey-7"> <div class="q-mb-xs text-caption text-grey-7">
{{ tag.name }} {{ tag.name }}
<q-icon <QIcon
v-if="tag.hasFilter" v-if="tag.hasFilter"
style="font-size: 1.3em;" style="font-size: 1.3em"
name="cancel" name="cancel"
:title="$t('deleteFilter')" :title="$t('deleteFilter')"
class="cursor-pointer" class="cursor-pointer"
@ -117,8 +111,9 @@
<div v-if="!tag.useRange"> <div v-if="!tag.useRange">
<div <div
v-for="value in tag.values.slice(0, tag.showCount)" v-for="value in tag.values.slice(0, tag.showCount)"
:key="value"> :key="value"
<q-checkbox >
<QCheckbox
v-model="tag.filter" v-model="tag.filter"
:dense="true" :dense="true"
:val="value" :val="value"
@ -129,22 +124,24 @@
<div v-if="tag.values.length > tag.showCount"> <div v-if="tag.values.length > tag.showCount">
<span <span
class="cursor-pointer text-blue" class="cursor-pointer text-blue"
@click="tag.showCount = Infinity"> @click="tag.showCount = Infinity"
<q-icon name="keyboard_arrow_down" /> >
<QIcon name="keyboard_arrow_down" />
{{ $t('viewMore') }} {{ $t('viewMore') }}
</span> </span>
</div> </div>
<div v-if="tag.showCount == Infinity"> <div v-if="tag.showCount == Infinity">
<span <span
class="cursor-pointer text-blue" class="cursor-pointer text-blue"
@click="tag.showCount = tag.initialCount"> @click="tag.showCount = tag.initialCount"
<q-icon name="keyboard_arrow_up" /> >
<QIcon name="keyboard_arrow_up" />
{{ $t('viewLess') }} {{ $t('viewLess') }}
</span> </span>
</div> </div>
</div> </div>
<div class="q-mx-md"> <div class="q-mx-md">
<q-range <QRange
class="q-mt-lg" class="q-mt-lg"
v-if="tag.useRange" v-if="tag.useRange"
v-model="tag.filter" v-model="tag.filter"
@ -161,113 +158,119 @@
</div> </div>
</div> </div>
</div> </div>
</q-drawer> </QDrawer>
<q-infinite-scroll <QInfiniteScroll
@load="onLoad" @load="onLoad"
scroll-taget="html" scroll-taget="html"
:offset="800" :offset="800"
:disable="disableScroll"> :disable="disableScroll"
>
<div class="q-pa-md row justify-center q-gutter-md"> <div class="q-pa-md row justify-center q-gutter-md">
<q-spinner <QSpinner v-if="isLoading" color="primary" size="50px">
v-if="isLoading" </QSpinner>
color="primary"
size="50px">
</q-spinner>
<div <div
v-if="items && !items.length" v-if="items && !items.length"
class="text-subtitle1 text-grey-7 q-pa-md"> class="text-subtitle1 text-grey-7 q-pa-md"
>
{{ $t('noItemsFound') }} {{ $t('noItemsFound') }}
</div> </div>
<div <div
v-if="!items && !isLoading" v-if="!items && !isLoading"
class="text-subtitle1 text-grey-7 q-pa-md"> class="text-subtitle1 text-grey-7 q-pa-md"
>
{{ $t('pleaseSetFilter') }} {{ $t('pleaseSetFilter') }}
</div> </div>
<q-card <QCard class="my-card" v-for="item in items" :key="item.id">
class="my-card"
v-for="item in items"
:key="item.id">
<img :src="`${$imageBase}/catalog/200x200/${item.image}`" /> <img :src="`${$imageBase}/catalog/200x200/${item.image}`" />
<q-card-section> <QCardSection>
<div class="name text-subtitle1"> <div class="name text-subtitle1">
{{ item.longName }} {{ item.longName }}
</div> </div>
<div class="sub-name text-uppercase text-subtitle1 text-grey-7 ellipsize q-pt-xs"> <div
class="sub-name text-uppercase text-subtitle1 text-grey-7 ellipsize q-pt-xs"
>
{{ item.subName }} {{ item.subName }}
</div> </div>
<div class="tags q-pt-xs"> <div class="tags q-pt-xs">
<div v-for="tag in item.tags" :key="tag.tagFk"> <div v-for="tag in item.tags" :key="tag.tagFk">
<span class="text-grey-7">{{tag.tag.name}}</span> {{tag.value}} <span class="text-grey-7">{{
tag.tag.name
}}</span>
{{ tag.value }}
</div> </div>
</div> </div>
</q-card-section> </QCardSection>
<q-card-actions class="actions justify-between"> <QCardActions class="actions justify-between">
<div class="q-pl-sm"> <div class="q-pl-sm">
<span class="available bg-green text-white">{{item.available}}</span> <span class="available bg-green text-white">{{
item.available
}}</span>
{{ $t('from') }} {{ $t('from') }}
<span class="price">{{currency(item.buy?.price3)}}</span> <span class="price">{{
currency(item.buy?.price3)
}}</span>
</div> </div>
<q-btn <QBtn
icon="add_shopping_cart" icon="add_shopping_cart"
:title="$t('buy')" :title="$t('buy')"
@click="showItem(item)" @click="showItem(item)"
flat> flat
</q-btn> >
</q-card-actions> </QBtn>
</q-card> </QCardActions>
</QCard>
</div> </div>
<template v-slot:loading> <template v-slot:loading>
<div class="row justify-center q-my-md"> <div class="row justify-center q-my-md">
<q-spinner color="primary" name="dots" size="40px" /> <QSpinner color="primary" name="dots" size="40px" />
</div> </div>
</template> </template>
</q-infinite-scroll> </QInfiniteScroll>
<q-dialog v-model="showItemDialog"> <QDialog v-model="showItemDialog">
<q-card style="width: 25em;"> <QCard style="width: 25em">
<q-img <QImg
:src="`${$imageBase}/catalog/200x200/${item.image}`" :src="`${$imageBase}/catalog/200x200/${item.image}`"
:ratio="5/3"> :ratio="5 / 3"
>
<div class="absolute-bottom text-center q-pa-xs"> <div class="absolute-bottom text-center q-pa-xs">
<div class="text-subtitle1"> <div class="text-subtitle1">
{{ item.longName }} {{ item.longName }}
</div> </div>
</div> </div>
</q-img> </QImg>
<q-card-section> <QCardSection>
<div class="text-uppercase text-subtitle1 text-grey-7 ellipsize"> <div
class="text-uppercase text-subtitle1 text-grey-7 ellipsize"
>
{{ item.subName }} {{ item.subName }}
</div> </div>
<div class="text-grey-7"> <div class="text-grey-7">#{{ item.id }}</div>
#{{item.id}} </QCardSection>
</div> <QCardSection>
</q-card-section>
<q-card-section>
<div v-for="tag in item.tags" :key="tag.tagFk"> <div v-for="tag in item.tags" :key="tag.tagFk">
<span class="text-grey-7">{{tag.tag.name}}</span> {{tag.value}} <span class="text-grey-7">{{ tag.tag.name }}</span>
{{ tag.value }}
</div> </div>
</q-card-section> </QCardSection>
<q-card-actions align="right"> <QCardActions align="right">
<q-btn <QBtn @click="showItemDialog = false" flat>
@click="showItemDialog = false"
flat>
{{ $t('cancel') }} {{ $t('cancel') }}
</q-btn> </QBtn>
<q-btn <QBtn @click="showItemDialog = false" flat>
@click="showItemDialog = false"
flat>
{{ $t('accept') }} {{ $t('accept') }}
</q-btn> </QBtn>
</q-card-actions> </QCardActions>
</q-card> </QCard>
</q-dialog> </QDialog>
<q-page-sticky> <QPageSticky>
<q-btn <QBtn
fab fab
to="/ecomerce/basket" to="/ecomerce/basket"
icon="shopping_cart" icon="shopping_cart"
color="accent" color="accent"
:title="$t('shoppingCart')"/> :title="$t('shoppingCart')"
</q-page-sticky> />
</QPageSticky>
</div> </div>
</template> </template>
@ -284,7 +287,7 @@
& > p { & > p {
margin: 0; margin: 0;
padding: .4em 0; padding: 0.4em 0;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
@ -298,7 +301,7 @@
width: 55px; width: 55px;
&.active { &.active {
background: rgba(0, 0, 0, .08); background: rgba(0, 0, 0, 0.08);
} }
& > img { & > img {
height: 40px; height: 40px;
@ -311,8 +314,8 @@
overflow: hidden; overflow: hidden;
} }
.available { .available {
padding: .15em; padding: 0.15em;
border-radius: .2em; border-radius: 0.2em;
font-size: 1.3em; font-size: 1.3em;
} }
.price { .price {
@ -323,7 +326,8 @@
max-width: 17.5em; max-width: 17.5em;
height: 32.5em; height: 32.5em;
overflow: hidden; overflow: hidden;
.name, .sub-name { .name,
.sub-name {
line-height: 1.3em; line-height: 1.3em;
} }
.ellipsize { .ellipsize {
@ -339,11 +343,11 @@
</style> </style>
<script> <script>
import { date, currency } from 'src/lib/filters.js' import { date, currency } from 'src/lib/filters.js';
import { date as qdate } from 'quasar' import { date as qdate } from 'quasar';
import axios from 'axios' import axios from 'axios';
const CancelToken = axios.CancelToken const CancelToken = axios.CancelToken;
export default { export default {
name: 'HederaCatalog', name: 'HederaCatalog',
@ -376,30 +380,36 @@ export default {
{ {
label: this.$t('relevancy'), label: this.$t('relevancy'),
value: 'relevancy DESC, longName' value: 'relevancy DESC, longName'
}, { },
{
label: this.$t('name'), label: this.$t('name'),
value: 'longName' value: 'longName'
}, { },
{
label: this.$t('siceAsc'), label: this.$t('siceAsc'),
value: 'size ASC' value: 'size ASC'
}, { },
{
label: this.$t('sizeDesc'), label: this.$t('sizeDesc'),
value: 'size DESC' value: 'size DESC'
}, { },
{
label: this.$t('priceAsc'), label: this.$t('priceAsc'),
value: 'price ASC' value: 'price ASC'
}, { },
{
label: this.$t('priceDesc'), label: this.$t('priceDesc'),
value: 'price DESC' value: 'price DESC'
}, { },
{
label: this.$t('available'), label: this.$t('available'),
value: 'available' value: 'available'
} }
] ]
} };
}, },
created() { created() {
this.$app.useRightDrawer = true this.$app.useRightDrawer = true;
}, },
async mounted() { async mounted() {
this.categories = await this.$jApi.query( this.categories = await this.$jApi.query(
@ -408,32 +418,32 @@ export default {
JOIN vn.itemCategoryL10n l ON l.id = c.id JOIN vn.itemCategoryL10n l ON l.id = c.id
WHERE c.display WHERE c.display
ORDER BY display` ORDER BY display`
) );
this.onRouteChange(this.$route) this.onRouteChange(this.$route);
}, },
beforeUnmount() { beforeUnmount() {
this.clearTimeoutAndRequest() this.clearTimeoutAndRequest();
}, },
beforeRouteUpdate(to, from, next) { beforeRouteUpdate(to, from, next) {
this.onRouteChange(to) this.onRouteChange(to);
next() next();
}, },
watch: { watch: {
categories() { categories() {
this.refreshTitle() this.refreshTitle();
}, },
orgTypes() { orgTypes() {
this.refreshTitle() this.refreshTitle();
}, },
order() { order() {
this.loadItems() this.loadItems();
}, },
date() { date() {
this.loadItems() this.loadItems();
}, },
async category(value) { async category(value) {
this.orgTypes = [] this.orgTypes = [];
if (!value) return if (!value) return;
const res = await this.$jApi.execQuery( const res = await this.$jApi.execQuery(
`CALL myBasket_getAvailable; `CALL myBasket_getAvailable;
@ -447,103 +457,104 @@ export default {
ORDER BY t.\`order\`, l.name; ORDER BY t.\`order\`, l.name;
DROP TEMPORARY TABLE tmp.itemAvailable;`, DROP TEMPORARY TABLE tmp.itemAvailable;`,
{ category: value } { category: value }
) );
res.fetch() res.fetch();
this.orgTypes = res.fetchData() this.orgTypes = res.fetchData();
}, },
search(value) { search(value) {
const location = { params: this.$route.params } const location = { params: this.$route.params };
if (value) location.query = { search: value } if (value) location.query = { search: value };
this.$router.push(location) this.$router.push(location);
} }
}, },
methods: { methods: {
date, date,
currency, currency,
onViewModeClick() { onViewModeClick() {
this.viewMode = this.viewMode === 'list' ? 'grid' : 'list' this.viewMode = this.viewMode === 'list' ? 'grid' : 'list';
}, },
onRouteChange(route) { onRouteChange(route) {
let { category, type } = route.params let { category, type } = route.params;
category = parseInt(category) || null category = parseInt(category) || null;
type = parseInt(type) || null type = parseInt(type) || null;
this.category = category this.category = category;
this.typeId = category ? type : null this.typeId = category ? type : null;
this.search = route.query.search || '' this.search = route.query.search || '';
this.tags = [] this.tags = [];
this.refreshTitle() this.refreshTitle();
this.loadItems() this.loadItems();
}, },
refreshTitle() { refreshTitle() {
let title = this.$t(this.$router.currentRoute.value.name) let title = this.$t(this.$router.currentRoute.value.name);
let subtitle let subtitle;
if (this.category) { if (this.category) {
const category = this.categories.find(i => i.id === this.category) || {} const category =
title = category.name this.categories.find(i => i.id === this.category) || {};
title = category.name;
} }
if (this.typeId) { if (this.typeId) {
this.type = this.orgTypes.find(i => i.id === this.typeId) this.type = this.orgTypes.find(i => i.id === this.typeId);
subtitle = title subtitle = title;
title = this.type && this.type.name title = this.type && this.type.name;
} else { } else {
this.type = null this.type = null;
} }
this.$app.$patch({ title, subtitle }) this.$app.$patch({ title, subtitle });
}, },
clearTimeoutAndRequest() { clearTimeoutAndRequest() {
if (this.timeout) { if (this.timeout) {
clearTimeout(this.timeout) clearTimeout(this.timeout);
this.timeout = null this.timeout = null;
} }
if (this.source) { if (this.source) {
this.source.cancel() this.source.cancel();
this.source = null this.source = null;
} }
}, },
loadItemsDelayed() { loadItemsDelayed() {
this.clearTimeoutAndRequest() this.clearTimeoutAndRequest();
this.timeout = setTimeout(() => this.loadItems(), 500) this.timeout = setTimeout(() => this.loadItems(), 500);
}, },
loadItems() { loadItems() {
this.items = null this.items = null;
this.isLoading = true this.isLoading = true;
this.limit = this.pageSize this.limit = this.pageSize;
this.disableScroll = false this.disableScroll = false;
this.isLoading = false this.isLoading = false;
// this.loadItemsBase().finally(() => (this.isLoading = false)) // this.loadItemsBase().finally(() => (this.isLoading = false))
}, },
onLoad(index, done) { onLoad(index, done) {
if (this.isLoading) return done() if (this.isLoading) return done();
this.limit += this.pageSize this.limit += this.pageSize;
done() done();
// this.loadItemsBase().finally(done) // this.loadItemsBase().finally(done)
}, },
loadItemsBase() { loadItemsBase() {
this.clearTimeoutAndRequest() this.clearTimeoutAndRequest();
if (!(this.category || this.typeId || this.search)) { if (!(this.category || this.typeId || this.search)) {
this.tags = [] this.tags = [];
return Promise.resolve(true) return Promise.resolve(true);
} }
const tagFilter = [] const tagFilter = [];
for (const tag of this.tags) { for (const tag of this.tags) {
if (tag.hasFilter) { if (tag.hasFilter) {
tagFilter.push({ tagFilter.push({
tagFk: tag.id, tagFk: tag.id,
values: tag.filter values: tag.filter
}) });
} }
} }
this.source = CancelToken.source() this.source = CancelToken.source();
const params = { const params = {
dated: this.orderDate, dated: this.orderDate,
@ -553,101 +564,107 @@ export default {
order: this.order.value, order: this.order.value,
limit: this.limit, limit: this.limit,
tagFilter tagFilter
} };
const config = { const config = {
params, params,
cancelToken: this.source.token cancelToken: this.source.token
} };
return this.$axios.get('Items/catalog', config) return this.$axios
.get('Items/catalog', config)
.then(res => this.onItemsGet(res)) .then(res => this.onItemsGet(res))
.catch(err => this.onItemsError(err)) .catch(err => this.onItemsError(err))
.finally(() => (this.cancel = null)) .finally(() => (this.cancel = null));
}, },
onItemsError(err) { onItemsError(err) {
if (err.__CANCEL__) return if (err.__CANCEL__) return;
this.disableScroll = true this.disableScroll = true;
throw err throw err;
}, },
onItemsGet(res) { onItemsGet(res) {
for (const tag of res.data.tags) { for (const tag of res.data.tags) {
tag.uid = this.uid++ tag.uid = this.uid++;
if (tag.filter) { if (tag.filter) {
tag.hasFilter = true tag.hasFilter = true;
tag.useRange = tag.useRange = tag.filter.max || tag.filter.min;
tag.filter.max ||
tag.filter.min
} else { } else {
tag.useRange = tag.isQuantitative && tag.useRange =
tag.values.length > this.maxTags tag.isQuantitative && tag.values.length > this.maxTags;
this.resetTagFilter(tag) this.resetTagFilter(tag);
} }
if (tag.values) { if (tag.values) {
tag.initialCount = this.maxTags tag.initialCount = this.maxTags;
if (Array.isArray(tag.filter)) { if (Array.isArray(tag.filter)) {
tag.initialCount = Math.max(tag.initialCount, tag.filter.length) tag.initialCount = Math.max(
tag.initialCount,
tag.filter.length
);
} }
tag.showCount = tag.initialCount tag.showCount = tag.initialCount;
} }
} }
this.items = res.data.items this.items = res.data.items;
this.tags = res.data.tags this.tags = res.data.tags;
this.disableScroll = this.items.length < this.limit this.disableScroll = this.items.length < this.limit;
}, },
onRangeChange(tag, delay) { onRangeChange(tag, delay) {
tag.hasFilter = true tag.hasFilter = true;
if (!delay) this.loadItems() if (!delay) this.loadItems();
else this.loadItemsDelayed() else this.loadItemsDelayed();
}, },
onCheck(tag) { onCheck(tag) {
tag.hasFilter = tag.filter.length > 0 tag.hasFilter = tag.filter.length > 0;
this.loadItems() this.loadItems();
}, },
resetTagFilter(tag) { resetTagFilter(tag) {
tag.hasFilter = false tag.hasFilter = false;
if (tag.useRange) { if (tag.useRange) {
tag.filter = { tag.filter = {
min: tag.min, min: tag.min,
max: tag.max max: tag.max
} };
} else { } else {
tag.filter = [] tag.filter = [];
} }
}, },
onResetTagFilterClick(tag) { onResetTagFilterClick(tag) {
this.resetTagFilter(tag) this.resetTagFilter(tag);
this.loadItems() this.loadItems();
}, },
filterType(val, update) { filterType(val, update) {
if (val === '') { if (val === '') {
update(() => { this.types = this.orgTypes }) update(() => {
this.types = this.orgTypes;
});
} else { } else {
update(() => { update(() => {
const needle = val.toLowerCase() const needle = val.toLowerCase();
this.types = this.orgTypes.filter(type => this.types = this.orgTypes.filter(
type.name.toLowerCase().indexOf(needle) > -1) type => type.name.toLowerCase().indexOf(needle) > -1
}) );
});
} }
}, },
showItem(item) { showItem(item) {
this.item = item this.item = item;
this.showItemDialog = true this.showItemDialog = true;
const conf = this.$state.catalogConfig const conf = this.$state.catalogConfig;
const params = { const params = {
dated: this.orderDate, dated: this.orderDate,
addressFk: conf.addressFk, addressFk: conf.addressFk,
agencyModeFk: conf.agencyModeFk agencyModeFk: conf.agencyModeFk
} };
this.$axios.get(`Items/${item.id}/calcCatalog`, { params }) this.$axios
.then(res => (this.lots = res.data)) .get(`Items/${item.id}/calcCatalog`, { params })
} .then(res => (this.lots = res.data));
} }
} }
};
</script> </script>
<i18n lang="yaml"> <i18n lang="yaml">

View File

@ -1,81 +1,96 @@
<template> <template>
<Teleport :to="$actions"> <Teleport :to="$actions">
<q-select <QSelect
v-model="year" v-model="year"
:options="years" :options="years"
color="white" color="white"
dark dark
standout standout
dense dense
rounded /> rounded
/>
</Teleport> </Teleport>
<div class="vn-w-sm"> <div class="vn-w-sm">
<div <div
v-if="!invoices?.length" v-if="!invoices?.length"
class="text-subtitle1 text-center text-grey-7 q-pa-md"> class="text-subtitle1 text-center text-grey-7 q-pa-md"
>
{{ $t('noInvoicesFound') }} {{ $t('noInvoicesFound') }}
</div> </div>
<q-card v-if="invoices?.length"> <QCard v-if="invoices?.length">
<q-table <QTable
:columns="columns" :columns="columns"
:pagination="pagination" :pagination="pagination"
:rows="invoices" :rows="invoices"
row-key="id" row-key="id"
hide-header hide-header
hide-bottom> hide-bottom
>
<template v-slot:body="props"> <template v-slot:body="props">
<q-tr :props="props"> <QTr :props="props">
<q-td key="ref" :props="props"> <QTd key="ref" :props="props">
{{ props.row.ref }} {{ props.row.ref }}
</q-td> </QTd>
<q-td key="issued" :props="props"> <QTd key="issued" :props="props">
{{ date(props.row.issued, 'ddd, MMMM Do') }} {{ date(props.row.issued, 'ddd, MMMM Do') }}
</q-td> </QTd>
<q-td key="amount" :props="props"> <QTd key="amount" :props="props">
{{ currency(props.row.amount) }} {{ currency(props.row.amount) }}
</q-td> </QTd>
<q-td key="hasPdf" :props="props"> <QTd key="hasPdf" :props="props">
<q-btn <QBtn
v-if="props.row.hasPdf" v-if="props.row.hasPdf"
icon="download" icon="download"
:title="$t('downloadInvoicePdf')" :title="$t('downloadInvoicePdf')"
:href="invoiceUrl(props.row.id)" :href="invoiceUrl(props.row.id)"
target="_blank" target="_blank"
flat flat
round/> round
<q-icon />
<QIcon
v-else v-else
name="warning" name="warning"
:title="$t('notDownloadable')" :title="$t('notDownloadable')"
color="warning" color="warning"
size="24px"/> size="24px"
</q-td> />
</q-tr> </QTd>
</QTr>
</template> </template>
</q-table> </QTable>
</q-card> </QCard>
</div> </div>
</template> </template>
<script> <script>
import { date, currency } from 'src/lib/filters.js' import { date, currency } from 'src/lib/filters.js';
export default { export default {
name: 'OrdersPendingIndex', name: 'OrdersPendingIndex',
data() { data() {
const curYear = (new Date()).getFullYear() const curYear = new Date().getFullYear();
const years = [] const years = [];
for (let year = curYear - 5; year <= curYear; year++) { for (let year = curYear - 5; year <= curYear; year++) {
years.push(year) years.push(year);
} }
return { return {
columns: [ columns: [
{ name: 'ref', label: 'serial', field: 'ref', align: 'left' }, { name: 'ref', label: 'serial', field: 'ref', align: 'left' },
{ name: 'issued', label: 'issued', field: 'issued', align: 'left' }, {
name: 'issued',
label: 'issued',
field: 'issued',
align: 'left'
},
{ name: 'amount', label: 'amount', field: 'amount' }, { name: 'amount', label: 'amount', field: 'amount' },
{ name: 'hasPdf', label: 'download', field: 'hasPdf', align: 'center' } {
name: 'hasPdf',
label: 'download',
field: 'hasPdf',
align: 'center'
}
], ],
pagination: { pagination: {
rowsPerPage: 0 rowsPerPage: 0
@ -83,16 +98,16 @@ export default {
year: curYear, year: curYear,
years, years,
invoices: null invoices: null
} };
}, },
async mounted() { async mounted() {
await this.loadData() await this.loadData();
}, },
watch: { watch: {
async year() { async year() {
await this.loadData() await this.loadData();
} }
}, },
@ -104,7 +119,7 @@ export default {
const params = { const params = {
from: new Date(this.year, 0), from: new Date(this.year, 0),
to: new Date(this.year, 11, 31, 23, 59, 59) to: new Date(this.year, 11, 31, 23, 59, 59)
} };
this._invoices = await this.$jApi.query( this._invoices = await this.$jApi.query(
`SELECT id, ref, issued, amount, hasPdf `SELECT id, ref, issued, amount, hasPdf
FROM myInvoice FROM myInvoice
@ -112,18 +127,21 @@ export default {
ORDER BY issued DESC ORDER BY issued DESC
LIMIT 500`, LIMIT 500`,
params params
) );
}, },
invoiceUrl(id) { invoiceUrl(id) {
return '?' + new URLSearchParams({ return (
'?' +
new URLSearchParams({
srv: 'rest:dms/invoice', srv: 'rest:dms/invoice',
invoice: id, invoice: id,
access_token: this.$user.token access_token: this.$user.token
}).toString() }).toString()
);
} }
} }
} };
</script> </script>
<i18n lang="yaml"> <i18n lang="yaml">

View File

@ -2,66 +2,68 @@
<Teleport :to="$actions"> <Teleport :to="$actions">
<div class="balance"> <div class="balance">
<span class="label">{{ $t('balance') }}</span> <span class="label">{{ $t('balance') }}</span>
<span <span class="amount" :class="{ negative: debt < 0 }">
class="amount"
:class="{negative: debt < 0}">
{{ currency(debt || 0) }} {{ currency(debt || 0) }}
</span> </span>
<q-icon <QIcon
name="info" name="info"
:title="$t('paymentInfo')" :title="$t('paymentInfo')"
class="info" class="info"
size="24px"/> size="24px"
/>
</div> </div>
<q-btn <QBtn
icon="payments" icon="payments"
:label="$t('makePayment')" :label="$t('makePayment')"
@click="onPayClick()" @click="onPayClick()"
rounded rounded
no-caps/> no-caps
<q-btn />
<QBtn
to="/ecomerce/basket" to="/ecomerce/basket"
icon="shopping_cart" icon="shopping_cart"
:label="$t('shoppingCart')" :label="$t('shoppingCart')"
rounded rounded
no-caps/> no-caps
/>
</Teleport> </Teleport>
<div class="vn-w-sm"> <div class="vn-w-sm">
<div <div
v-if="!orders?.length" v-if="!orders?.length"
class="text-subtitle1 text-center text-grey-7 q-pa-md"> class="text-subtitle1 text-center text-grey-7 q-pa-md"
>
{{ $t('noOrdersFound') }} {{ $t('noOrdersFound') }}
</div> </div>
<q-card v-if="orders?.length"> <QCard v-if="orders?.length">
<q-list bordered separator padding > <QList bordered separator padding>
<q-item <QItem
v-for="order in orders" v-for="order in orders"
:key="order.id" :key="order.id"
:to="`ticket/${order.id}`" :to="`ticket/${order.id}`"
clickable clickable
v-ripple> v-ripple
<q-item-section> >
<q-item-label> <QItemSection>
<QItemLabel>
{{ date(order.landed, 'ddd, MMMM Do') }} {{ date(order.landed, 'ddd, MMMM Do') }}
</q-item-label> </QItemLabel>
<q-item-label caption>#{{order.id}}</q-item-label> <QItemLabel caption>#{{ order.id }}</QItemLabel>
<q-item-label caption>{{order.nickname}}</q-item-label> <QItemLabel caption>{{ order.nickname }}</QItemLabel>
<q-item-label caption>{{order.agency}}</q-item-label> <QItemLabel caption>{{ order.agency }}</QItemLabel>
</q-item-section> </QItemSection>
<q-item-section side top> <QItemSection side top> {{ order.total }} </QItemSection>
{{order.total}} </QItem>
</q-item-section> </QList>
</q-item> </QCard>
</q-list> <QPageSticky>
</q-card> <QBtn
<q-page-sticky>
<q-btn
fab fab
icon="add_shopping_cart" icon="add_shopping_cart"
color="accent" color="accent"
to="/ecomerce/catalog" to="/ecomerce/catalog"
:title="$t('startOrder')"/> :title="$t('startOrder')"
</q-page-sticky> />
</QPageSticky>
</div> </div>
</template> </template>
@ -91,8 +93,8 @@
</style> </style>
<script> <script>
import { date, currency } from 'src/lib/filters.js' import { date, currency } from 'src/lib/filters.js';
import { tpvStore } from 'stores/tpv' import { tpvStore } from 'stores/tpv';
export default { export default {
name: 'OrdersPendingIndex', name: 'OrdersPendingIndex',
@ -101,18 +103,14 @@ export default {
orders: null, orders: null,
debt: 0, debt: 0,
tpv: tpvStore() tpv: tpvStore()
} };
}, },
async mounted() { async mounted() {
await this.tpv.check(this.$route) await this.tpv.check(this.$route);
this.orders = await this.$jApi.query( this.orders = await this.$jApi.query('CALL myTicket_list(NULL, NULL)');
'CALL myTicket_list(NULL, NULL)' this.debt = await this.$jApi.getValue('SELECT -myClient_getDebt(NULL)');
)
this.debt = await this.$jApi.getValue(
'SELECT -myClient_getDebt(NULL)'
)
}, },
methods: { methods: {
@ -120,22 +118,22 @@ export default {
currency, currency,
async onPayClick() { async onPayClick() {
let amount = -this.debt let amount = -this.debt;
amount = amount <= 0 ? null : amount amount = amount <= 0 ? null : amount;
let defaultAmountStr = '' let defaultAmountStr = '';
if (amount !== null) { if (amount !== null) {
defaultAmountStr = amount defaultAmountStr = amount;
} }
amount = prompt(this.$t('amountToPay'), defaultAmountStr) amount = prompt(this.$t('amountToPay'), defaultAmountStr);
if (amount != null) { if (amount != null) {
amount = parseFloat(amount.replace(',', '.')) amount = parseFloat(amount.replace(',', '.'));
await this.tpv.pay(amount) await this.tpv.pay(amount);
}
} }
} }
} }
};
</script> </script>
<i18n lang="yaml"> <i18n lang="yaml">

View File

@ -1,60 +1,76 @@
<template> <template>
<Teleport :to="$actions"> <Teleport :to="$actions">
<q-btn <QBtn
icon="print" icon="print"
:label="$t('printDeliveryNote')" :label="$t('printDeliveryNote')"
@click="onPrintClick()" @click="onPrintClick()"
rounded rounded
no-caps/> no-caps
/>
</Teleport> </Teleport>
<div> <div>
<q-card class="vn-w-sm"> <QCard class="vn-w-sm">
<q-card-section> <QCardSection>
<div class="text-h6">#{{ ticket.id }}</div> <div class="text-h6">#{{ ticket.id }}</div>
</q-card-section> </QCardSection>
<q-card-section> <QCardSection>
<div class="text-h6">{{ $t('shippingInformation') }}</div> <div class="text-h6">{{ $t('shippingInformation') }}</div>
<div>{{$t('preparation')}} {{date(ticket.shipped, 'ddd, MMMM Do')}}</div> <div>
<div>{{$t('delivery')}} {{date(ticket.shipped, 'ddd, MMMM Do')}}</div> {{ $t('preparation') }}
<div>{{$t(ticket.method != 'PICKUP' ? 'agency' : 'warehouse')}} {{ticket.agency}}</div> {{ date(ticket.shipped, 'ddd, MMMM Do') }}
</q-card-section> </div>
<q-card-section> <div>
{{ $t('delivery') }}
{{ date(ticket.shipped, 'ddd, MMMM Do') }}
</div>
<div>
{{ $t(ticket.method != 'PICKUP' ? 'agency' : 'warehouse') }}
{{ ticket.agency }}
</div>
</QCardSection>
<QCardSection>
<div class="text-h6">{{ $t('deliveryAddress') }}</div> <div class="text-h6">{{ $t('deliveryAddress') }}</div>
<div>{{ ticket.nickname }}</div> <div>{{ ticket.nickname }}</div>
<div>{{ ticket.street }}</div> <div>{{ ticket.street }}</div>
<div>{{ticket.postalCode}} {{ticket.city}} ({{ticket.province}})</div> <div>
</q-card-section> {{ ticket.postalCode }} {{ ticket.city }} ({{
<q-separator inset /> ticket.province
<q-list v-for="row in rows" :key="row.itemFk"> }})
<q-item> </div>
<q-item-section avatar> </QCardSection>
<q-avatar size="68px"> <QSeparator inset />
<img :src="`${$app.imageUrl}/catalog/200x200/${row.image}`"> <QList v-for="row in rows" :key="row.itemFk">
</q-avatar> <QItem>
</q-item-section> <QItemSection avatar>
<q-item-section> <QAvatar size="68px">
<q-item-label lines="1"> <img
:src="`${$app.imageUrl}/catalog/200x200/${row.image}`"
/>
</QAvatar>
</QItemSection>
<QItemSection>
<QItemLabel lines="1">
{{ row.concept }} {{ row.concept }}
</q-item-label> </QItemLabel>
<q-item-label lines="1" caption> <QItemLabel lines="1" caption>
{{ row.value5 }} {{ row.value6 }} {{ row.value7 }} {{ row.value5 }} {{ row.value6 }} {{ row.value7 }}
</q-item-label> </QItemLabel>
<q-item-label lines="1"> <QItemLabel lines="1">
{{ row.quantity }} x {{ currency(row.price) }} {{ row.quantity }} x {{ currency(row.price) }}
</q-item-label> </QItemLabel>
</q-item-section> </QItemSection>
<q-item-section side class="total"> <QItemSection side class="total">
<q-item-label> <QItemLabel>
<span class="discount" v-if="row.discount"> <span class="discount" v-if="row.discount">
{{ currency(discountSubtotal(row)) }} - {{ currency(discountSubtotal(row)) }} -
{{ currency(row.discount) }} = {{ currency(row.discount) }} =
</span> </span>
{{ currency(subtotal(row)) }} {{ currency(subtotal(row)) }}
</q-item-label> </QItemLabel>
</q-item-section> </QItemSection>
</q-item> </QItem>
</q-list> </QList>
</q-card> </QCard>
</div> </div>
</template> </template>
@ -65,7 +81,7 @@
</style> </style>
<script> <script>
import { date, currency } from 'src/lib/filters.js' import { date, currency } from 'src/lib/filters.js';
export default { export default {
name: 'OrdersConfirmedView', name: 'OrdersConfirmedView',
@ -76,29 +92,29 @@ export default {
rows: null, rows: null,
services: null, services: null,
packages: null packages: null
} };
}, },
async mounted() { async mounted() {
const params = { const params = {
ticket: parseInt(this.$route.params.id) ticket: parseInt(this.$route.params.id)
} };
this.ticket = await this.$jApi.getObject( this.ticket = await this.$jApi.getObject(
'CALL myTicket_get(#ticket)', 'CALL myTicket_get(#ticket)',
params params
) );
this.rows = await this.$jApi.query( this.rows = await this.$jApi.query(
'CALL myTicket_getRows(#ticket)', 'CALL myTicket_getRows(#ticket)',
params params
) );
this.services = await this.$jApi.query( this.services = await this.$jApi.query(
'CALL myTicket_getServices(#ticket)', 'CALL myTicket_getServices(#ticket)',
params params
) );
this.packages = await this.$jApi.query( this.packages = await this.$jApi.query(
'CALL myTicket_getPackages(#ticket)', 'CALL myTicket_getPackages(#ticket)',
params params
) );
}, },
methods: { methods: {
@ -106,12 +122,12 @@ export default {
currency, currency,
discountSubtotal(line) { discountSubtotal(line) {
return line.quantity * line.price return line.quantity * line.price;
}, },
subtotal(line) { subtotal(line) {
const discount = line.discount const discount = line.discount;
return this.discountSubtotal(line) * ((100 - discount) / 100) return this.discountSubtotal(line) * ((100 - discount) / 100);
}, },
onPrintClick() { onPrintClick() {
@ -119,9 +135,11 @@ export default {
access_token: this.$user.token, access_token: this.$user.token,
recipientId: this.$user.id, recipientId: this.$user.id,
type: 'deliveryNote' type: 'deliveryNote'
}) });
window.open(`/api/Tickets/${this.ticket.id}/delivery-note-pdf?${params.toString()}`) window.open(
} `/api/Tickets/${this.ticket.id}/delivery-note-pdf?${params.toString()}`
);
} }
} }
};
</script> </script>

View File

@ -1,15 +1,15 @@
<template> <template>
<div class="fullscreen bg-accent text-white text-center q-pa-md flex flex-center"> <div
class="fullscreen bg-accent text-white text-center q-pa-md flex flex-center"
>
<div> <div>
<div style="font-size: 30vh"> <div style="font-size: 30vh">404</div>
404
</div>
<div class="text-h2" style="opacity:.4"> <div class="text-h2" style="opacity: 0.4">
Oops. Nothing here... Oops. Nothing here...
</div> </div>
<q-btn <QBtn
class="q-mt-xl" class="q-mt-xl"
color="white" color="white"
text-color="accent" text-color="accent"
@ -23,9 +23,9 @@
</template> </template>
<script> <script>
import { defineComponent } from 'vue' import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'ErrorNotFound' name: 'ErrorNotFound'
}) });
</script> </script>

View File

@ -1,17 +1,17 @@
<template> <template>
<q-page class="flex flex-center"> <QPage class="flex flex-center">
<img <img
alt="Quasar logo" alt="Quasar logo"
src="~assets/quasar-logo-vertical.svg" src="~assets/quasar-logo-vertical.svg"
style="width: 200px; height: 200px" style="width: 200px; height: 200px"
> />
</q-page> </QPage>
</template> </template>
<script> <script>
import { defineComponent } from 'vue' import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'IndexPage' name: 'IndexPage'
}) });
</script> </script>

View File

@ -2,34 +2,27 @@
<div class="main"> <div class="main">
<div class="header"> <div class="header">
<router-link to="/" class="block"> <router-link to="/" class="block">
<img <img src="statics/logo.svg" alt="Verdnatura" class="block" />
src="statics/logo.svg"
alt="Verdnatura"
class="block"
/>
</router-link> </router-link>
</div> </div>
<q-form @submit="onLogin" class="q-gutter-y-md"> <QForm @submit="onLogin" class="q-gutter-y-md">
<div class="q-gutter-y-sm"> <div class="q-gutter-y-sm">
<q-input <QInput v-model="email" :label="$t('user')" autofocus />
v-model="email" <QInput
:label="$t('user')"
autofocus
/>
<q-input
v-model="password" v-model="password"
ref="password" ref="password"
:label="$t('password')" :label="$t('password')"
:type="showPwd ? 'password' : 'text'"> :type="showPwd ? 'password' : 'text'"
>
<template v-slot:append> <template v-slot:append>
<q-icon <QIcon
:name="showPwd ? 'visibility_off' : 'visibility'" :name="showPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer" class="cursor-pointer"
@click="showPwd = !showPwd" @click="showPwd = !showPwd"
/> />
</template> </template>
</q-input> </QInput>
<q-checkbox <QCheckbox
v-model="remember" v-model="remember"
:label="$t('remindMe')" :label="$t('remindMe')"
class="remember" class="remember"
@ -37,7 +30,7 @@
/> />
</div> </div>
<div class="justify-center"> <div class="justify-center">
<q-btn <QBtn
type="submit" type="submit"
:label="$t('logIn')" :label="$t('logIn')"
class="full-width" class="full-width"
@ -48,7 +41,7 @@
/> />
</div> </div>
<div class="justify-center"> <div class="justify-center">
<q-btn <QBtn
to="/" to="/"
:label="$t('logInAsGuest')" :label="$t('logInAsGuest')"
class="full-width" class="full-width"
@ -63,11 +56,15 @@
{{ $t('haveForgottenPassword') }} {{ $t('haveForgottenPassword') }}
</router-link> </router-link>
</p> </p>
</q-form> </QForm>
<div class="footer text-center"> <div class="footer text-center">
<p> <p>
{{ $t('notACustomerYet') }} {{ $t('notACustomerYet') }}
<a href="//verdnatura.es/register/" target="_blank" class="link"> <a
href="//verdnatura.es/register/"
target="_blank"
class="link"
>
{{ $t('signUp') }} {{ $t('signUp') }}
</a> </a>
</p> </p>
@ -106,13 +103,13 @@ a {
height: 50px; height: 50px;
} }
.password-forgotten { .password-forgotten {
font-size: .8rem; font-size: 0.8rem;
} }
.footer { .footer {
margin-bottom: $login-margin-top; margin-bottom: $login-margin-top;
margin-top: $login-margin-between; margin-top: $login-margin-between;
text-align: center; text-align: center;
font-size: .8rem; font-size: 0.8rem;
.contact { .contact {
margin-top: 15px; margin-top: 15px;
@ -125,7 +122,7 @@ a {
</style> </style>
<script> <script>
import { userStore } from 'stores/user' import { userStore } from 'stores/user';
export default { export default {
name: 'VnLogin', name: 'VnLogin',
@ -137,7 +134,7 @@ export default {
password: '', password: '',
remember: false, remember: false,
showPwd: true showPwd: true
} };
}, },
mounted() { mounted() {
@ -145,21 +142,21 @@ export default {
this.$q.notify({ this.$q.notify({
message: this.$t('emailConfirmedSuccessfully'), message: this.$t('emailConfirmedSuccessfully'),
type: 'positive' type: 'positive'
}) });
} }
if (this.$route.params.email) { if (this.$route.params.email) {
this.email = this.$route.params.email this.email = this.$route.params.email;
this.$refs.password.focus() this.$refs.password.focus();
} }
}, },
methods: { methods: {
async onLogin() { async onLogin() {
await this.user.login(this.email, this.password, this.remember) await this.user.login(this.email, this.password, this.remember);
this.$router.push('/') this.$router.push('/');
}
} }
} }
};
</script> </script>
<i18n lang="yaml"> <i18n lang="yaml">

View File

@ -1,14 +1,14 @@
<template> <template>
<div class="text-center"> <div class="text-center">
<div> <div>
<q-icon <QIcon
name="contact_support" name="contact_support"
class="block q-mx-auto text-accent" class="block q-mx-auto text-accent"
style="font-size: 120px;" style="font-size: 120px"
/> />
</div> </div>
<div> <div>
<q-form @submit="onSend" class="q-gutter-y-md text-grey-8"> <QForm @submit="onSend" class="q-gutter-y-md text-grey-8">
<div class="text-h5"> <div class="text-h5">
<div> <div>
{{ $t('dontWorry') }} {{ $t('dontWorry') }}
@ -17,7 +17,7 @@
{{ $t('fillData') }} {{ $t('fillData') }}
</div> </div>
</div> </div>
<q-input <QInput
v-model="email" v-model="email"
:label="$t('user')" :label="$t('user')"
:rules="[val => !!val || $t('inputEmail')]" :rules="[val => !!val || $t('inputEmail')]"
@ -27,7 +27,7 @@
{{ $t('weSendEmail') }} {{ $t('weSendEmail') }}
</div> </div>
<div> <div>
<q-btn <QBtn
type="submit" type="submit"
:label="$t('send')" :label="$t('send')"
class="full-width q-mt-md" class="full-width q-mt-md"
@ -42,7 +42,7 @@
</router-link> </router-link>
</div> </div>
</div> </div>
</q-form> </QForm>
</div> </div>
</div> </div>
</template> </template>
@ -56,7 +56,7 @@
} }
a { a {
color: inherit; color: inherit;
font-size: .8rem; font-size: 0.8rem;
} }
</style> </style>
@ -66,22 +66,22 @@ export default {
data() { data() {
return { return {
email: '' email: ''
} };
}, },
methods: { methods: {
async onSend() { async onSend() {
const params = { const params = {
email: this.email email: this.email
} };
await this.$axios.post('Users/reset', params) await this.$axios.post('Users/reset', params);
this.$q.notify({ this.$q.notify({
message: this.$t('weHaveSentEmailToRecover'), message: this.$t('weHaveSentEmailToRecover'),
type: 'positive' type: 'positive'
}) });
this.$router.push('/login') this.$router.push('/login');
}
} }
} }
};
</script> </script>
<i18n lang="yaml"> <i18n lang="yaml">

View File

@ -1,51 +1,60 @@
<template> <template>
<div> <div>
<q-card-section> <QCard-section>
<q-icon <QIcon
name="check" name="check"
class="block q-mx-auto text-accent" class="block q-mx-auto text-accent"
style="font-size: 120px;" style="font-size: 120px"
/> />
</q-card-section> </QCard-section>
<q-card-section> <QCard-section>
<q-form @submit="onRegister" ref="form" class="q-gutter-y-md"> <QForm @submit="onRegister" ref="form" class="q-gutter-y-md">
<div class="text-grey-8 text-h5 text-center"> <div class="text-grey-8 text-h5 text-center">
{{ $t('fillData') }} {{ $t('fillData') }}
</div> </div>
<div class="q-gutter-y-sm"> <div class="q-gutter-y-sm">
<q-input <QInput
v-model="password" v-model="password"
:label="$t('password')" :label="$t('password')"
:type="showPwd ? 'password' : 'text'" :type="showPwd ? 'password' : 'text'"
autofocus autofocus
hint="" hint=""
filled> filled
>
<template v-slot:append> <template v-slot:append>
<q-icon <QIcon
:name="showPwd ? 'visibility_off' : 'visibility'" :name="
showPwd ? 'visibility_off' : 'visibility'
"
class="cursor-pointer" class="cursor-pointer"
@click="showPwd = !showPwd" @click="showPwd = !showPwd"
/> />
</template> </template>
</q-input> </QInput>
<q-input <QInput
v-model="repeatPassword" v-model="repeatPassword"
:label="$t('repeatPassword')" :label="$t('repeatPassword')"
:type="showRpPwd ? 'password' : 'text'" :type="showRpPwd ? 'password' : 'text'"
:rules="[value => value == password || $t('repeatPasswordError')]" :rules="[
value =>
value == password || $t('repeatPasswordError')
]"
hint="" hint=""
filled> filled
>
<template v-slot:append> <template v-slot:append>
<q-icon <QIcon
:name="showRpPwd ? 'visibility_off' : 'visibility'" :name="
showRpPwd ? 'visibility_off' : 'visibility'
"
class="cursor-pointer" class="cursor-pointer"
@click="showRpPwd = !showRpPwd" @click="showRpPwd = !showRpPwd"
/> />
</template> </template>
</q-input> </QInput>
</div> </div>
<div> <div>
<q-btn <QBtn
type="submit" type="submit"
:label="$t('resetPassword')" :label="$t('resetPassword')"
class="full-width" class="full-width"
@ -57,8 +66,8 @@
</router-link> </router-link>
</div> </div>
</div> </div>
</q-form> </QForm>
</q-card-section> </QCard-section>
</div> </div>
</template> </template>
@ -71,23 +80,27 @@ export default {
repeatPassword: '', repeatPassword: '',
showPwd: true, showPwd: true,
showRpPwd: true showRpPwd: true
} };
}, },
methods: { methods: {
async onRegister() { async onRegister() {
const headers = { const headers = {
Authorization: this.$route.query.access_token Authorization: this.$route.query.access_token
} };
await this.$axios.post('users/reset-password', { await this.$axios.post(
'users/reset-password',
{
newPassword: this.password newPassword: this.password
}, { headers }) },
{ headers }
);
this.$q.notify({ this.$q.notify({
message: this.$t('passwordResetSuccessfully'), message: this.$t('passwordResetSuccessfully'),
type: 'positive' type: 'positive'
}) });
this.$router.push('/login') this.$router.push('/login');
}
} }
} }
};
</script> </script>

View File

@ -1,6 +1,11 @@
import { route } from 'quasar/wrappers' import { route } from 'quasar/wrappers'
import { appStore } from 'stores/app' import { appStore } from 'stores/app'
import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router' import {
createRouter,
createMemoryHistory,
createWebHistory,
createWebHashHistory
} from 'vue-router'
import routes from './routes' import routes from './routes'
/* /*
@ -15,7 +20,9 @@ import routes from './routes'
export default route(function (/* { store, ssrContext } */) { export default route(function (/* { store, ssrContext } */) {
const createHistory = process.env.SERVER const createHistory = process.env.SERVER
? createMemoryHistory ? createMemoryHistory
: (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory) : process.env.VUE_ROUTER_MODE === 'history'
? createWebHistory
: createWebHashHistory
const Router = createRouter({ const Router = createRouter({
scrollBehavior: () => ({ left: 0, top: 0 }), scrollBehavior: () => ({ left: 0, top: 0 }),
@ -24,7 +31,9 @@ export default route(function (/* { store, ssrContext } */) {
// Leave this as is and make changes in quasar.conf.js instead! // Leave this as is and make changes in quasar.conf.js instead!
// quasar.conf.js -> build -> vueRouterMode // quasar.conf.js -> build -> vueRouterMode
// quasar.conf.js -> build -> publicPath // quasar.conf.js -> build -> publicPath
history: createHistory(process.env.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE) history: createHistory(
process.env.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE
)
}) })
Router.afterEach((to, from) => { Router.afterEach((to, from) => {

View File

@ -1,4 +1,3 @@
const routes = [ const routes = [
{ {
path: '/login', path: '/login',
@ -8,17 +7,20 @@ const routes = [
name: 'login', name: 'login',
path: '/login/:email?', path: '/login/:email?',
component: () => import('pages/Login/Login.vue') component: () => import('pages/Login/Login.vue')
}, { },
{
name: 'rememberPassword', name: 'rememberPassword',
path: '/remember-password', path: '/remember-password',
component: () => import('pages/Login/RememberPassword.vue') component: () => import('pages/Login/RememberPassword.vue')
}, { },
{
name: 'resetPassword', name: 'resetPassword',
path: '/reset-password', path: '/reset-password',
component: () => import('pages/Login/ResetPassword.vue') component: () => import('pages/Login/ResetPassword.vue')
} }
] ]
}, { },
{
path: '/', path: '/',
component: () => import('layouts/MainLayout.vue'), component: () => import('layouts/MainLayout.vue'),
children: [ children: [
@ -26,23 +28,28 @@ const routes = [
name: '', name: '',
path: '', path: '',
component: () => import('src/pages/Cms/Home.vue') component: () => import('src/pages/Cms/Home.vue')
}, { },
{
name: 'home', name: 'home',
path: '/cms/home', path: '/cms/home',
component: () => import('src/pages/Cms/Home.vue') component: () => import('src/pages/Cms/Home.vue')
}, { },
{
name: 'orders', name: 'orders',
path: '/ecomerce/orders', path: '/ecomerce/orders',
component: () => import('pages/Ecomerce/Orders.vue') component: () => import('pages/Ecomerce/Orders.vue')
}, { },
{
name: 'ticket', name: 'ticket',
path: '/ecomerce/ticket/:id', path: '/ecomerce/ticket/:id',
component: () => import('pages/Ecomerce/Ticket.vue') component: () => import('pages/Ecomerce/Ticket.vue')
}, { },
{
name: 'invoices', name: 'invoices',
path: '/ecomerce/invoices', path: '/ecomerce/invoices',
component: () => import('pages/Ecomerce/Invoices.vue') component: () => import('pages/Ecomerce/Invoices.vue')
}, { },
{
name: 'catalog', name: 'catalog',
path: '/ecomerce/catalog/:category?/:type?', path: '/ecomerce/catalog/:category?/:type?',
component: () => import('pages/Ecomerce/Catalog.vue') component: () => import('pages/Ecomerce/Catalog.vue')

View File

@ -12,9 +12,7 @@ export const appStore = defineStore('hedera', {
actions: { actions: {
async loadConfig () { async loadConfig () {
const imageUrl = await jApi.getValue( const imageUrl = await jApi.getValue('SELECT url FROM imageConfig')
'SELECT url FROM imageConfig'
)
this.$patch({ imageUrl }) this.$patch({ imageUrl })
} }
} }

View File

@ -1,10 +1,10 @@
/* eslint-disable */ /* eslint-disable */
// THIS FEATURE-FLAG FILE IS AUTOGENERATED, // THIS FEATURE-FLAG FILE IS AUTOGENERATED,
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING // REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
import "quasar/dist/types/feature-flag"; import 'quasar/dist/types/feature-flag'
declare module "quasar/dist/types/feature-flag" { declare module 'quasar/dist/types/feature-flag' {
interface QuasarFeatureFlags { interface QuasarFeatureFlags {
store: true; store: true
} }
} }

View File

@ -8,14 +8,16 @@ export const tpvStore = defineStore('tpv', {
const status = route.query.tpvStatus const status = route.query.tpvStatus
if (!(order && status)) return null if (!(order && status)) return null
await jApi.execQuery( await jApi.execQuery('CALL myTpvTransaction_end(#order, #status)', {
'CALL myTpvTransaction_end(#order, #status)', order,
{ order, status } status
) })
if (status === 'ko') { if (status === 'ko') {
const retry = confirm('retryPayQuestion') const retry = confirm('retryPayQuestion')
if (retry) { this.retryPay(order) } if (retry) {
this.retryPay(order)
}
} }
return status return status
@ -61,7 +63,9 @@ export const tpvStore = defineStore('tpv', {
input.name = field input.name = field
form.appendChild(input) form.appendChild(input)
if (postValues[field]) { input.value = postValues[field] } if (postValues[field]) {
input.value = postValues[field]
}
} }
form.submit() form.submit()
@ -73,7 +77,9 @@ export const tpvStore = defineStore('tpv', {
path += location.pathname path += location.pathname
path += location.search ? location.search : '' path += location.search ? location.search : ''
path += '#/ecomerce/orders' path += '#/ecomerce/orders'
path += '?' + new URLSearchParams({ path +=
'?' +
new URLSearchParams({
tpvStatus: status, tpvStatus: status,
tpvOrder: '_transactionId_' tpvOrder: '_transactionId_'
}).toString() }).toString()

View File

@ -1,18 +1,19 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia';
import { api, jApi } from 'boot/axios' import { api, jApi } from 'boot/axios';
export const userStore = defineStore('user', { export const userStore = defineStore('user', {
state: () => { state: () => {
const token = const token =
sessionStorage.getItem('vnToken') || sessionStorage.getItem('vnToken') ||
localStorage.getItem('vnToken') localStorage.getItem('vnToken');
return { return {
token, token,
id: null, id: null,
name: null, name: null,
nickname: null nickname: null,
} isGuest: false
};
}, },
getters: { getters: {
@ -21,41 +22,41 @@ export const userStore = defineStore('user', {
actions: { actions: {
async login(user, password, remember) { async login(user, password, remember) {
const params = { user, password } const params = { user, password };
const res = await api.post('Accounts/login', params) const res = await api.post('Accounts/login', params);
if (remember) { if (remember) {
localStorage.setItem('vnToken', res.data.token) localStorage.setItem('vnToken', res.data.token);
} else { } else {
sessionStorage.setItem('vnToken', res.data.token) sessionStorage.setItem('vnToken', res.data.token);
} }
this.$patch({ this.$patch({
token: res.data.token, token: res.data.token,
name: user name: user
}) });
}, },
async logout() { async logout() {
if (this.token != null) { if (this.token != null) {
try { try {
await api.post('Accounts/logout') await api.post('Accounts/logout');
} catch (e) {} } catch (e) {}
localStorage.removeItem('vnToken') localStorage.removeItem('vnToken');
sessionStorage.removeItem('vnToken') sessionStorage.removeItem('vnToken');
} }
this.$reset() this.$reset();
}, },
async loadData() { async loadData() {
const userData = await jApi.getObject( const userData = await jApi.getObject(
'SELECT id, nickname FROM account.myUser' 'SELECT id, nickname FROM account.myUser'
) );
this.$patch({ this.$patch({
id: userData.id, id: userData.id,
nickname: userData.nickname nickname: userData.nickname
}) });
} }
} }
}) });

View File

@ -28,27 +28,34 @@ const baseConfig = {
presets: ['@babel/preset-env'] presets: ['@babel/preset-env']
} }
} }
}, { },
{
test: /tinymce\/.*\/skin\.css$/i, test: /tinymce\/.*\/skin\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader'] use: [MiniCssExtractPlugin.loader, 'css-loader']
}, { },
{
test: /tinymce\/.*\/content\.css$/i, test: /tinymce\/.*\/content\.css$/i,
loader: 'css-loader', loader: 'css-loader',
options: { esModule: false } options: { esModule: false }
}, { },
{
test: /\.css$/, test: /\.css$/,
use: ['style-loader', 'css-loader'], use: ['style-loader', 'css-loader'],
exclude: [/node_modules/] exclude: [/node_modules/]
}, { },
{
test: /\.yml$/, test: /\.yml$/,
use: ['json-loader', 'yaml-loader'] use: ['json-loader', 'yaml-loader']
}, { },
{
test: /\.xml$/, test: /\.xml$/,
use: 'raw-loader' use: 'raw-loader'
}, { },
{
test: /\.ttf$/, test: /\.ttf$/,
type: 'asset/resource' type: 'asset/resource'
}, { },
{
test: /\.scss$/, test: /\.scss$/,
use: [ use: [
'style-loader', 'style-loader',
@ -60,6 +67,18 @@ const baseConfig = {
} }
} }
] ]
},
{
test: /\.(woff|woff2)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/'
}
}
]
} }
] ]
}, },
@ -89,7 +108,7 @@ const baseConfig = {
optimization: { optimization: {
runtimeChunk: true, runtimeChunk: true,
splitChunks: { splitChunks: {
chunks: 'all', chunks: 'all'
} }
}, },
watchOptions: { watchOptions: {
@ -127,7 +146,7 @@ const devConfig = {
'/api': 'http://localhost:3000', '/api': 'http://localhost:3000',
'/': { '/': {
target: 'http://localhost/projects/hedera-web', target: 'http://localhost/projects/hedera-web',
bypass: (req) => req.path !== '/' ? req.path : null bypass: req => (req.path !== '/' ? req.path : null)
} }
} }
} }