Init config #68

Merged
jsegarra merged 6 commits from wbuezas/hedera-web-mindshore:feature/InitConfig into 4922-vueMigration 2024-07-19 11:13:56 +00:00
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,6 +1,5 @@
export default async ({ app }) => { export default async ({ app }) => {
/* /*
window.addEventListener('error', window.addEventListener('error',
e => onWindowError(e)); e => onWindowError(e));
window.addEventListener('unhandledrejection', window.addEventListener('unhandledrejection',

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: navigator.language || navigator.userLanguage,
locale: 'es-ES', 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> />
{{$app.title}} <QToolbarTitle>
<div {{ $app.title }}
v-if="$app.subtitle" <div v-if="$app.subtitle" class="subtitle text-caption">
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,13 +130,13 @@
</style> </style>
<style lang="scss"> <style lang="scss">
@import "src/css/responsive"; @import 'src/css/responsive';
.q-drawer { .q-drawer {
.q-item { .q-item {
padding-left: 38px; padding-left: 38px;
} }
.q-list .q-list .q-item{ .q-list .q-list .q-item {
padding-left: 50px; padding-left: 50px;
} }
} }
@ -171,71 +167,73 @@
</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(),
supplantedUser: ref(''), supplantedUser: ref(''),
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">
en-US: en-US:
visitor: Visitor visitor: Visitor
es-ES: es-ES:
visitor: Visitante visitor: Visitante
</i18n> </i18n>

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>
@ -51,30 +48,30 @@
<script> <script>
export default { export default {
name: 'PageIndex', name: 'PageIndex',
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">
en-US: en-US:
startOrder: Start order startOrder: Start order
es-ES: es-ES:
startOrder: Empezar pedido startOrder: Empezar pedido
ca-ES: ca-ES:
startOrder: Començar comanda startOrder: Començar comanda
fr-FR: fr-FR:
startOrder: Lancer commande startOrder: Lancer commande
pt-PT: pt-PT:
startOrder: Comece uma encomenda startOrder: Comece uma encomenda
</i18n> </i18n>

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,78 +8,73 @@
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>
<p> <p>
{{$t('warehouse')}} {{ $t('warehouse') }}
{{'Algemesi'}} {{ 'Algemesi' }}
</p> </p>
<q-btn <QBtn flat rounded no-caps>
flat {{ $t('modify') }}
rounded </QBtn>
no-caps>
{{$t('modify')}}
</q-btn>
</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"
v-for="cat in categories" v-for="cat in categories"
: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" /> >
{{$t('viewMore')}} <QIcon name="keyboard_arrow_down" />
{{ $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" /> >
{{$t('viewLess')}} <QIcon name="keyboard_arrow_up" />
{{ $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,121 +158,127 @@
</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
{{item.subName}} class="sub-name text-uppercase text-subtitle1 text-grey-7 ellipsize q-pt-xs"
>
{{ 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">{{
{{$t('from')}} item.available
<span class="price">{{currency(item.buy?.price3)}}</span> }}</span>
{{ $t('from') }}
<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
{{item.subName}} class="text-uppercase text-subtitle1 text-grey-7 ellipsize"
>
{{ 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" {{ $t('cancel') }}
flat> </QBtn>
{{$t('cancel')}} <QBtn @click="showItemDialog = false" flat>
</q-btn> {{ $t('accept') }}
<q-btn </QBtn>
@click="showItemDialog = false" </QCardActions>
flat> </QCard>
{{$t('accept')}} </QDialog>
</q-btn> <QPageSticky>
</q-card-actions> <QBtn
</q-card>
</q-dialog>
<q-page-sticky>
<q-btn
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>
<style lang="scss" scoped> <style lang="scss" scoped>
.search { .search {
max-width: 250px; max-width: 250px;
} }
.basket-info { .basket-info {
background-color: #8cc63f; background-color: #8cc63f;
color: white; color: white;
padding: 17px 28px; padding: 17px 28px;
@ -284,13 +287,13 @@
& > 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;
} }
} }
.categories { .categories {
margin: 0 auto; margin: 0 auto;
width: 220px; width: 220px;
@ -298,32 +301,33 @@
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;
width: 40px; width: 40px;
} }
} }
} }
.tags { .tags {
max-height: 4.6em; max-height: 4.6em;
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 {
font-size: 1.3em; font-size: 1.3em;
} }
.my-card { .my-card {
width: 100%; width: 100%;
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 {
@ -335,19 +339,19 @@
height: 40px; height: 40px;
overflow: hidden; overflow: hidden;
} }
} }
</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',
data () { data() {
return { return {
uid: 0, uid: 0,
search: '', search: '',
@ -376,64 +380,70 @@ 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(
`SELECT c.id, l.name, c.color, c.code `SELECT c.id, l.name, c.color, c.code
FROM vn.itemCategory c FROM vn.itemCategory c
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,105 +564,111 @@ 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">
es-ES: es-ES:
gridView: Vista de rejilla gridView: Vista de rejilla
listView: Vista de lista listView: Vista de lista
shoppingCart: Cesta de la compra shoppingCart: Cesta de la compra

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();
} }
}, },
@ -100,11 +115,11 @@ export default {
date, date,
currency, currency,
async loadData () { async loadData() {
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,50 +127,53 @@ 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">
en-US: en-US:
noInvoicesFound: No invoices found noInvoicesFound: No invoices found
serial: Serial serial: Serial
issued: Date issued: Date
amount: Import amount: Import
downloadInvoicePdf: Download invoice PDF downloadInvoicePdf: Download invoice PDF
notDownloadable: Not available for download, request the invoice to your salesperson notDownloadable: Not available for download, request the invoice to your salesperson
es-ES: es-ES:
noInvoicesFound: No se han encontrado facturas noInvoicesFound: No se han encontrado facturas
serial: Serie serial: Serie
issued: Fecha issued: Fecha
amount: Importe amount: Importe
downloadInvoicePdf: Descargar factura en PDF downloadInvoicePdf: Descargar factura en PDF
notDownloadable: No disponible para descarga, solicita la factura a tu comercial notDownloadable: No disponible para descarga, solicita la factura a tu comercial
ca-ES: ca-ES:
noInvoicesFound: No s'han trobat factures noInvoicesFound: No s'han trobat factures
serial: Sèrie serial: Sèrie
issued: Data issued: Data
amount: Import amount: Import
downloadInvoicePdf: Descarregar PDF downloadInvoicePdf: Descarregar PDF
notDownloadable: No disponible per cescarrega, sol·licita la factura al teu comercial notDownloadable: No disponible per cescarrega, sol·licita la factura al teu comercial
fr-FR: fr-FR:
noInvoicesFound: Aucune facture trouvée noInvoicesFound: Aucune facture trouvée
serial: Série serial: Série
issued: Date issued: Date
amount: Montant amount: Montant
downloadInvoicePdf: Télécharger le PDF downloadInvoicePdf: Télécharger le PDF
notDownloadable: Non disponible en téléchargement, demander la facture à votre commercial notDownloadable: Non disponible en téléchargement, demander la facture à votre commercial
pt-PT: pt-PT:
noInvoicesFound: Nenhuma fatura encontrada noInvoicesFound: Nenhuma fatura encontrada
serial: Serie serial: Serie
issued: Data issued: Data

View File

@ -1,67 +1,69 @@
<template> <template>
<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" {{ currency(debt || 0) }}
:class="{negative: 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>
{{date(order.landed, 'ddd, MMMM Do')}} <QItemLabel>
</q-item-label> {{ date(order.landed, 'ddd, MMMM Do') }}
<q-item-label caption>#{{order.id}}</q-item-label> </QItemLabel>
<q-item-label caption>{{order.nickname}}</q-item-label> <QItemLabel caption>#{{ order.id }}</QItemLabel>
<q-item-label caption>{{order.agency}}</q-item-label> <QItemLabel caption>{{ order.nickname }}</QItemLabel>
</q-item-section> <QItemLabel caption>{{ order.agency }}</QItemLabel>
<q-item-section side top> </QItemSection>
{{order.total}} <QItemSection side top> {{ order.total }} </QItemSection>
</q-item-section> </QItem>
</q-item> </QList>
</q-list> </QCard>
</q-card> <QPageSticky>
<q-page-sticky> <QBtn
<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,55 +93,51 @@
</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',
data () { data() {
return { return {
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: {
date, date,
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">
en-US: en-US:
startOrder: Start order startOrder: Start order
noOrdersFound: No orders found noOrdersFound: No orders found
makePayment: Make payment makePayment: Make payment
@ -150,7 +148,7 @@ export default {
disregards future orders. For get your order shipped, this amount must be disregards future orders. For get your order shipped, this amount must be
equal to or greater than 0. If you want to make a down payment, click the equal to or greater than 0. If you want to make a down payment, click the
payment button, delete the suggested amount and enter the amount you want. payment button, delete the suggested amount and enter the amount you want.
es-ES: es-ES:
startOrder: Empezar pedido startOrder: Empezar pedido
noOrdersFound: No se encontrado pedidos noOrdersFound: No se encontrado pedidos
makePayment: Realizar pago makePayment: Realizar pago
@ -162,7 +160,7 @@ export default {
esta cantidad debe ser igual o mayor que 0. Si quieres realizar una entrega a esta cantidad debe ser igual o mayor que 0. Si quieres realizar una entrega a
cuenta, pulsa el botón de pago, borra la cantidad sugerida e introduce la cuenta, pulsa el botón de pago, borra la cantidad sugerida e introduce la
cantidad que desees. cantidad que desees.
ca-ES: ca-ES:
startOrder: Començar encàrrec startOrder: Començar encàrrec
noOrdersFound: No s'han trobat comandes noOrdersFound: No s'han trobat comandes
makePayment: Realitzar pagament makePayment: Realitzar pagament
@ -174,7 +172,7 @@ export default {
enviat, aquesta quantitat ha de ser igual o més gran que 0. Si vols fer un enviat, aquesta quantitat ha de ser igual o més gran que 0. Si vols fer un
lliurament a compte, prem el botó de pagament, esborra la quantitat suggerida lliurament a compte, prem el botó de pagament, esborra la quantitat suggerida
e introdueix la quantitat que vulguis. e introdueix la quantitat que vulguis.
fr-FR: fr-FR:
startOrder: Acheter startOrder: Acheter
noOrdersFound: Aucune commande trouvée noOrdersFound: Aucune commande trouvée
makePayment: Effectuer un paiement makePayment: Effectuer un paiement
@ -186,7 +184,7 @@ export default {
commande est expédiée, ce montant doit être égal ou supérieur à 0. Si vous commande est expédiée, ce montant doit être égal ou supérieur à 0. Si vous
voulez faire un versement, le montant suggéré effacé et entrez le montant que voulez faire un versement, le montant suggéré effacé et entrez le montant que
vous souhaitez. vous souhaitez.
pt-PT: pt-PT:
startOrder: Iniciar encomenda startOrder: Iniciar encomenda
noOrdersFound: Nenhum pedido encontrado noOrdersFound: Nenhum pedido encontrado
makePayment: Realizar pagamento makePayment: Realizar pagamento

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>
<div class="text-h6">{{$t('deliveryAddress')}}</div> {{ $t('delivery') }}
<div>{{ticket.nickname}}</div> {{ date(ticket.shipped, 'ddd, MMMM Do') }}
<div>{{ticket.street}}</div> </div>
<div>{{ticket.postalCode}} {{ticket.city}} ({{ticket.province}})</div> <div>
</q-card-section> {{ $t(ticket.method != 'PICKUP' ? 'agency' : 'warehouse') }}
<q-separator inset /> {{ ticket.agency }}
<q-list v-for="row in rows" :key="row.itemFk"> </div>
<q-item> </QCardSection>
<q-item-section avatar> <QCardSection>
<q-avatar size="68px"> <div class="text-h6">{{ $t('deliveryAddress') }}</div>
<img :src="`${$app.imageUrl}/catalog/200x200/${row.image}`"> <div>{{ ticket.nickname }}</div>
</q-avatar> <div>{{ ticket.street }}</div>
</q-item-section> <div>
<q-item-section> {{ ticket.postalCode }} {{ ticket.city }} ({{
<q-item-label lines="1"> ticket.province
{{row.concept}} }})
</q-item-label> </div>
<q-item-label lines="1" caption> </QCardSection>
{{row.value5}} {{row.value6}} {{row.value7}} <QSeparator inset />
</q-item-label> <QList v-for="row in rows" :key="row.itemFk">
<q-item-label lines="1"> <QItem>
{{row.quantity}} x {{currency(row.price)}} <QItemSection avatar>
</q-item-label> <QAvatar size="68px">
</q-item-section> <img
<q-item-section side class="total"> :src="`${$app.imageUrl}/catalog/200x200/${row.image}`"
<q-item-label> />
</QAvatar>
</QItemSection>
<QItemSection>
<QItemLabel lines="1">
{{ row.concept }}
</QItemLabel>
<QItemLabel lines="1" caption>
{{ row.value5 }} {{ row.value6 }} {{ row.value7 }}
</QItemLabel>
<QItemLabel lines="1">
{{ row.quantity }} x {{ currency(row.price) }}
</QItemLabel>
</QItemSection>
<QItemSection side class="total">
<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,63 +81,65 @@
</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',
data () { data() {
return { return {
ticket: {}, ticket: {},
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: {
date, date,
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() {
const params = new URLSearchParams({ const params = new URLSearchParams({
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"
@ -60,19 +53,23 @@
</div> </div>
<p class="password-forgotten text-center q-mt-lg"> <p class="password-forgotten text-center q-mt-lg">
<router-link to="/remember-password" class="link"> <router-link to="/remember-password" class="link">
{{$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
{{$t('signUp')}} href="//verdnatura.es/register/"
target="_blank"
class="link"
>
{{ $t('signUp') }}
</a> </a>
</p> </p>
<p class="contact"> <p class="contact">
{{$t('loginPhone')}} · {{$t('loginMail')}} {{ $t('loginPhone') }} · {{ $t('loginMail') }}
</p> </p>
</div> </div>
</div> </div>
@ -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,41 +122,41 @@ a {
</style> </style>
<script> <script>
import { userStore } from 'stores/user' import { userStore } from 'stores/user';
export default { export default {
name: 'VnLogin', name: 'VnLogin',
data () { data() {
return { return {
user: userStore(), user: userStore(),
email: '', email: '',
password: '', password: '',
remember: false, remember: false,
showPwd: true showPwd: true
} };
}, },
mounted () { mounted() {
if (this.$route.query.emailConfirmed !== undefined) { if (this.$route.query.emailConfirmed !== undefined) {
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,33 +1,33 @@
<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') }}
</div> </div>
<div> <div>
{{$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')]"
autofocus autofocus
/> />
<div class="q-mt-lg"> <div class="q-mt-lg">
{{$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"
@ -38,11 +38,11 @@
/> />
<div class="text-center q-mt-md"> <div class="text-center q-mt-md">
<router-link to="/login" class="link"> <router-link to="/login" class="link">
{{$t('return')}} {{ $t('return') }}
</router-link> </router-link>
</div> </div>
</div> </div>
</q-form> </QForm>
</div> </div>
</div> </div>
</template> </template>
@ -56,36 +56,36 @@
} }
a { a {
color: inherit; color: inherit;
font-size: .8rem; font-size: 0.8rem;
} }
</style> </style>
<script> <script>
export default { export default {
name: 'VnRememberPasword', name: 'VnRememberPasword',
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">
en-US: en-US:
user: User user: User
inputEmail: Input email inputEmail: Input email
rememberPassword: Rememeber password rememberPassword: Rememeber password
@ -95,7 +95,7 @@ export default {
weHaveSentEmailToRecover: We've sent you an email where you can recover your password weHaveSentEmailToRecover: We've sent you an email where you can recover your password
send: Send send: Send
return: Return return: Return
es-ES: es-ES:
user: Usuario user: Usuario
inputEmail: Introduce el correo electrónico inputEmail: Introduce el correo electrónico
rememberPassword: Recordar contraseña rememberPassword: Recordar contraseña

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"
@ -53,41 +62,45 @@
/> />
<div class="text-center q-mt-xs"> <div class="text-center q-mt-xs">
<router-link to="/login" class="link"> <router-link to="/login" class="link">
{{$t('return')}} {{ $t('return') }}
</router-link> </router-link>
</div> </div>
</div> </div>
</q-form> </QForm>
</q-card-section> </QCard-section>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'VnRegister', name: 'VnRegister',
data () { data() {
return { return {
password: '', password: '',
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: {
@ -20,42 +21,42 @@ 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,14 +67,26 @@ const baseConfig = {
} }
} }
] ]
},
{
test: /\.(woff|woff2)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/'
}
}
]
} }
] ]
}, },
resolve: { resolve: {
modules: [ modules: [
__dirname +'/js', __dirname + '/js',
__dirname, __dirname,
__dirname +'/forms', __dirname + '/forms',
'node_modules', 'node_modules',
'/usr/lib/node_modules' '/usr/lib/node_modules'
] ]
@ -89,7 +108,7 @@ const baseConfig = {
optimization: { optimization: {
runtimeChunk: true, runtimeChunk: true,
splitChunks: { splitChunks: {
chunks: 'all', chunks: 'all'
} }
}, },
watchOptions: { watchOptions: {
@ -121,13 +140,13 @@ const devConfig = {
host: '0.0.0.0', host: '0.0.0.0',
static: __dirname, static: __dirname,
port: wpConfig.devServerPort, port: wpConfig.devServerPort,
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/projects/hedera-web',
bypass: (req) => req.path !== '/' ? req.path : null bypass: req => (req.path !== '/' ? req.path : null)
} }
} }
} }