0
1
Fork 0

Config prettier and eslint for src folder

This commit is contained in:
William Buezas 2024-07-16 21:17:22 -03:00
parent 6458d8db5e
commit 47c6fe02ec
42 changed files with 2564 additions and 2249 deletions

View File

@ -1,22 +1,21 @@
module.exports = { module.exports = {
// https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy // https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
// This option interrupts the configuration hierarchy at this file // This option interrupts the configuration hierarchy at this file
// Remove this if you have an higher level ESLint config file (it usually happens into a monorepos) // Remove this if you have an higher level ESLint config file (it usually happens into a monorepos)
root: true, root: true,
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',
@ -31,49 +30,57 @@ module.exports = {
], ],
plugins: [
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
// required to lint *.vue files
'vue',
],
globals: { plugins: ['vue', 'prettier'],
ga: 'readonly', // Google Analytics
cordova: 'readonly',
__statics: 'readonly',
__QUASAR_SSR__: 'readonly',
__QUASAR_SSR_SERVER__: 'readonly',
__QUASAR_SSR_CLIENT__: 'readonly',
__QUASAR_SSR_PWA__: 'readonly',
process: 'readonly',
Capacitor: 'readonly',
chrome: 'readonly'
},
// add your custom rules here globals: {
rules: { ga: 'readonly', // Google Analytics
cordova: 'readonly',
__statics: 'readonly',
__QUASAR_SSR__: 'readonly',
__QUASAR_SSR_SERVER__: 'readonly',
__QUASAR_SSR_CLIENT__: 'readonly',
__QUASAR_SSR_PWA__: 'readonly',
process: 'readonly',
Capacitor: 'readonly',
chrome: 'readonly',
},
// allow async-await // add your custom rules here
'generator-star-spacing': 'off', rules: {
// allow paren-less arrow functions // allow async-await
'arrow-parens': 'off', 'generator-star-spacing': 'off',
'one-var': 'off', // allow paren-less arrow functions
'no-void': 'off', 'arrow-parens': 'off',
'multiline-ternary': 'off', 'one-var': 'off',
'no-void': 'off',
'multiline-ternary': 'off',
'import/first': 'off', 'import/first': 'off',
'import/named': 'error', 'import/named': 'error',
'import/namespace': 'error', 'import/namespace': 'error',
'import/default': 'error', 'import/default': 'error',
'import/export': 'error', 'import/export': 'error',
'import/extensions': 'off', 'import/extensions': 'off',
'import/no-unresolved': 'off', 'import/no-unresolved': 'off',
'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 y .vue dentro de src
rules: {
semi: 'off',
indent: ['error', 4, { SwitchCase: 1 }],
},
},
],
};

9
.prettierrc.js Normal file
View File

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

589
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -17,12 +17,14 @@
"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", "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",

View File

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

View File

@ -3,8 +3,8 @@ import { appStore } from 'stores/app'
import { userStore } from 'stores/user' import { userStore } from 'stores/user'
export default boot(({ app }) => { export default boot(({ app }) => {
const props = app.config.globalProperties const props = app.config.globalProperties
props.$app = appStore() props.$app = appStore()
props.$user = userStore() props.$user = userStore()
props.$actions = document.createElement('div') props.$actions = document.createElement('div')
}) })

View File

@ -10,33 +10,33 @@ import axios from 'axios'
// "export default () => {}" function below (which runs individually // "export default () => {}" function below (which runs individually
// for each client) // for each client)
const api = axios.create({ const api = axios.create({
baseURL: `//${location.hostname}:${location.port}/api/` baseURL: `//${location.hostname}:${location.port}/api/`
}) })
const jApi = new Connection() const jApi = new Connection()
export default boot(({ app }) => { export default boot(({ app }) => {
const user = userStore() const user = userStore()
function addToken (config) { function addToken (config) {
if (user.token) { if (user.token) {
config.headers.Authorization = user.token config.headers.Authorization = user.token
}
return config
} }
return config api.interceptors.request.use(addToken)
} jApi.use(addToken)
api.interceptors.request.use(addToken)
jApi.use(addToken)
// for use inside Vue files (Options API) through this.$axios and this.$api // for use inside Vue files (Options API) through this.$axios and this.$api
app.config.globalProperties.$axios = axios app.config.globalProperties.$axios = axios
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form) // ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
// so you won't necessarily have to import axios in each vue file // so you won't necessarily have to import axios in each vue file
app.config.globalProperties.$api = api app.config.globalProperties.$api = api
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form) // ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
// so you can easily perform requests against your app's API // so you can easily perform requests against your app's API
app.config.globalProperties.$jApi = jApi app.config.globalProperties.$jApi = jApi
}) })
export { api, jApi } export { api, jApi }

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',
@ -13,55 +12,55 @@ export default async ({ app }) => {
errorHandler(event.reason); errorHandler(event.reason);
} }
*/ */
app.config.errorHandler = (err, vm, info) => { app.config.errorHandler = (err, vm, info) => {
errorHandler(err, vm) errorHandler(err, vm)
}
function errorHandler (err, vm) {
let message
let tMessage
let res = err.response
// XXX: Compatibility with old JSON service
if (err.name === 'JsonException') {
res = {
status: err.statusCode,
data: { error: { message: err.message } }
}
} }
if (res) { function errorHandler (err, vm) {
const status = res.status let message
let tMessage
let res = err.response
if (status >= 400 && status < 500) { // XXX: Compatibility with old JSON service
switch (status) { if (err.name === 'JsonException') {
case 401: res = {
tMessage = 'loginFailed' status: err.statusCode,
break data: { error: { message: err.message } }
case 403: }
tMessage = 'authenticationRequired'
vm.$router.push('/login')
break
case 404:
tMessage = 'notFound'
break
default:
message = res.data.error.message
} }
} else if (status >= 500) {
tMessage = 'internalServerError'
}
} else {
tMessage = 'somethingWentWrong'
console.error(err)
}
if (tMessage) { if (res) {
message = vm.$t(tMessage) const status = res.status
if (status >= 400 && status < 500) {
switch (status) {
case 401:
tMessage = 'loginFailed'
break
case 403:
tMessage = 'authenticationRequired'
vm.$router.push('/login')
break
case 404:
tMessage = 'notFound'
break
default:
message = res.data.error.message
}
} else if (status >= 500) {
tMessage = 'internalServerError'
}
} else {
tMessage = 'somethingWentWrong'
console.error(err)
}
if (tMessage) {
message = vm.$t(tMessage)
}
vm.$q.notify({
message,
type: 'negative'
})
} }
vm.$q.notify({
message,
type: 'negative'
})
}
} }

View File

@ -3,16 +3,16 @@ import { createI18n } from 'vue-i18n'
import messages from 'src/i18n' import messages from 'src/i18n'
export default boot(({ app }) => { export default boot(({ app }) => {
const i18n = createI18n({ const i18n = createI18n({
locale: 'es-ES', locale: 'es-ES',
globalInjection: true, globalInjection: true,
silentTranslationWarn: true, silentTranslationWarn: true,
silentFallbackWarn: true, silentFallbackWarn: true,
messages messages
}) })
// Set i18n instance on app // Set i18n instance on app
app.use(i18n) app.use(i18n)
window.i18n = i18n.global window.i18n = i18n.global
}) })

View File

@ -5,8 +5,8 @@
src: url(./poppins.ttf) format('truetype'); src: url(./poppins.ttf) format('truetype');
} }
@font-face { @font-face {
font-family: 'Open Sans'; font-family: 'Open Sans';
src: url(./opensans.ttf) format('truetype'); src: url(./opensans.ttf) format('truetype');
} }
@mixin mobile { @mixin mobile {
@media screen and (max-width: 960px) { @media screen and (max-width: 960px) {
@ -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,25 +1,24 @@
%margin-auto { %margin-auto {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
.vn-w-xs { .vn-w-xs {
@extend %margin-auto; @extend %margin-auto;
max-width: $width-xs; max-width: $width-xs;
} }
.vn-w-sm { .vn-w-sm {
@extend %margin-auto; @extend %margin-auto;
max-width: $width-sm; max-width: $width-sm;
} }
.vn-w-md { .vn-w-md {
@extend %margin-auto; @extend %margin-auto;
max-width: $width-md; max-width: $width-md;
} }
.vn-w-lg { .vn-w-lg {
@extend %margin-auto; @extend %margin-auto;
max-width: $width-lg; max-width: $width-lg;
} }
.vn-w-xl { .vn-w-xl {
@extend %margin-auto; @extend %margin-auto;
max-width: $width-xl; max-width: $width-xl;
} }

View File

@ -2,84 +2,76 @@
// so you can safely delete all default props below // so you can safely delete all default props below
export default { export default {
failed: 'Action failed', failed: 'Action failed',
success: 'Action was successful', success: 'Action was successful',
internalServerError: 'Internal server error', internalServerError: 'Internal server error',
somethingWentWrong: 'Something went wrong', somethingWentWrong: 'Something went wrong',
loginFailed: 'Login failed', loginFailed: 'Login failed',
authenticationRequired: 'Authentication required', authenticationRequired: 'Authentication required',
notFound: 'Not found', notFound: 'Not found',
today: 'Today', today: 'Today',
yesterday: 'Yesterday', yesterday: 'Yesterday',
tomorrow: 'Tomorrow', tomorrow: 'Tomorrow',
date: { date: {
days: [ days: [
'Sunday', 'Sunday',
'Monday', 'Monday',
'Tuesday', 'Tuesday',
'Wednesday', 'Wednesday',
'Thursday', 'Thursday',
'Friday', 'Friday',
'Saturday' 'Saturday'
], ],
daysShort: [ daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
'Sun', months: [
'Mon', 'January',
'Tue', 'February',
'Wed', 'March',
'Thu', 'April',
'Fri', 'May',
'Sat' 'June',
], 'July',
months: [ 'August',
'January', 'September',
'February', 'October',
'March', 'November',
'April', 'December'
'May', ],
'June', shortMonths: [
'July', 'Jan',
'August', 'Feb',
'September', 'Mar',
'October', 'Apr',
'November', 'May',
'December' 'Jun',
], 'Jul',
shortMonths: [ 'Aug',
'Jan', 'Sep',
'Feb', 'Oct',
'Mar', 'Nov',
'Apr', 'Dec'
'May', ]
'Jun', },
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
},
// menu // menu
home: 'Home', home: 'Home',
catalog: 'Catalog', catalog: 'Catalog',
orders: 'Orders', orders: 'Orders',
order: 'Pending order', order: 'Pending order',
ticket: 'Order', ticket: 'Order',
conditions: 'Conditions', conditions: 'Conditions',
about: 'About us', about: 'About us',
admin: 'Administration', admin: 'Administration',
panel: 'Control panel', panel: 'Control panel',
users: 'Users', users: 'Users',
connections: 'Connections', connections: 'Connections',
visits: 'Visits', visits: 'Visits',
news: 'News', news: 'News',
newEdit: 'Edit new', newEdit: 'Edit new',
images: 'Images', images: 'Images',
items: 'Items', items: 'Items',
config: 'Configuration', config: 'Configuration',
user: 'User', user: 'User',
addresses: 'Addresses', addresses: 'Addresses',
addressEdit: 'Edit address' addressEdit: 'Edit address'
} }

View File

@ -2,84 +2,76 @@
// so you can safely delete all default props below // so you can safely delete all default props below
export default { export default {
failed: 'Acción fallida', failed: 'Acción fallida',
success: 'Acción exitosa', success: 'Acción exitosa',
internalServerError: 'Error interno del servidor', internalServerError: 'Error interno del servidor',
somethingWentWrong: 'Algo salió mal', somethingWentWrong: 'Algo salió mal',
loginFailed: 'Usuario o contraseña incorrectos', loginFailed: 'Usuario o contraseña incorrectos',
authenticationRequired: 'Autenticación requerida', authenticationRequired: 'Autenticación requerida',
notFound: 'No encontrado', notFound: 'No encontrado',
today: 'Hoy', today: 'Hoy',
yesterday: 'Ayer', yesterday: 'Ayer',
tomorrow: 'Mañana', tomorrow: 'Mañana',
date: { date: {
days: [ days: [
'Domingo', 'Domingo',
'Lunes', 'Lunes',
'Martes', 'Martes',
'Miércoles', 'Miércoles',
'Jueves', 'Jueves',
'Viernes', 'Viernes',
'Sábado' 'Sábado'
], ],
daysShort: [ daysShort: ['Do', 'Lu', 'Mi', 'Mi', 'Ju', 'Vi', 'Sa'],
'Do', months: [
'Lu', 'Enero',
'Mi', 'Febrero',
'Mi', 'Marzo',
'Ju', 'Abril',
'Vi', 'Mayo',
'Sa' 'Junio',
], 'Julio',
months: [ 'Agosto',
'Enero', 'Septiembre',
'Febrero', 'Octubre',
'Marzo', 'Noviembre',
'Abril', 'Diciembre'
'Mayo', ],
'Junio', shortMonths: [
'Julio', 'Ene',
'Agosto', 'Feb',
'Septiembre', 'Mar',
'Octubre', 'Abr',
'Noviembre', 'May',
'Diciembre' 'Jun',
], 'Jul',
shortMonths: [ 'Ago',
'Ene', 'Sep',
'Feb', 'Oct',
'Mar', 'Nov',
'Abr', 'Dic'
'May', ]
'Jun', },
'Jul',
'Ago',
'Sep',
'Oct',
'Nov',
'Dic'
]
},
// Menu // Menu
home: 'Inicio', home: 'Inicio',
catalog: 'Catálogo', catalog: 'Catálogo',
orders: 'Pedidos', orders: 'Pedidos',
order: 'Pedido pendiente', order: 'Pedido pendiente',
ticket: 'Pedido', ticket: 'Pedido',
conditions: 'Condiciones', conditions: 'Condiciones',
about: 'Sobre nosotros', about: 'Sobre nosotros',
admin: 'Administración', admin: 'Administración',
panel: 'Panel de control', panel: 'Panel de control',
users: 'Usuarios', users: 'Usuarios',
connections: 'Conexiones', connections: 'Conexiones',
visits: 'Visitas', visits: 'Visitas',
news: 'Noticias', news: 'Noticias',
newEdit: 'Editar noticia', newEdit: 'Editar noticia',
images: 'Imágenes', images: 'Imágenes',
items: 'Artículos', items: 'Artículos',
config: 'Configuración', config: 'Configuración',
user: 'Usuario', user: 'Usuario',
addresses: 'Direcciones', addresses: 'Direcciones',
addressEdit: 'Editar dirección' addressEdit: 'Editar dirección'
} }

View File

@ -2,6 +2,6 @@ import enUS from './en-US'
import esES from './es-ES' import esES from './es-ES'
export default { export default {
'en-US': enUS, 'en-US': enUS,
'es-ES': esES 'es-ES': esES
} }

View File

@ -12,163 +12,173 @@ import { ResultSet } from './result-set'
* the user can send any statement to the server. For example: DROP DATABASE * the user can send any statement to the server. For example: DROP DATABASE
*/ */
const Flag = { const Flag = {
NOT_NULL: 1, NOT_NULL: 1,
PRI_KEY: 2, PRI_KEY: 2,
AI: 512 | 2 | 1 AI: 512 | 2 | 1
} }
const Type = { const Type = {
BOOLEAN: 1, BOOLEAN: 1,
INTEGER: 3, INTEGER: 3,
DOUBLE: 4, DOUBLE: 4,
STRING: 5, STRING: 5,
DATE: 8, DATE: 8,
DATE_TIME: 9 DATE_TIME: 9
} }
export class Connection extends JsonConnection { export class Connection extends JsonConnection {
static Flag = Flag static Flag = Flag
static Type = Type static Type = Type
/** /**
* Runs a SQL query on the database. * Runs a SQL query on the database.
* *
* @param {String} sql The SQL statement * @param {String} sql The SQL statement
* @return {ResultSet} The result * @return {ResultSet} The result
*/ */
async execSql (sql) { async execSql (sql) {
const json = await this.send('core/query', { sql }) const json = await this.send('core/query', { sql })
const results = [] const results = []
let err let err
if (json) { if (json) {
try { try {
if (json && json instanceof Array) { if (json && json instanceof Array) {
for (let i = 0; i < json.length; i++) { for (let i = 0; i < json.length; i++) {
if (json[i] !== true) { if (json[i] !== true) {
const rows = json[i].data const rows = json[i].data
const columns = json[i].columns const columns = json[i].columns
const data = new Array(rows.length) const data = new Array(rows.length)
results.push({ results.push({
data, data,
columns, columns,
tables: json[i].tables tables: json[i].tables
}) })
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++) {
let castFunc = null let castFunc = null
const col = columns[j] const col = columns[j]
switch (col.type) { switch (col.type) {
case Type.DATE: case Type.DATE:
case Type.DATE_TIME: case Type.DATE_TIME:
case Type.TIMESTAMP: case Type.TIMESTAMP:
castFunc = this.valueToDate castFunc = this.valueToDate
break break
}
if (castFunc !== null) {
if (col.def != null) {
col.def = castFunc(col.def)
}
for (let k = 0; k < data.length; k++) {
if (data[k][col.name] != null) {
data[k][col.name] = castFunc(data[k][col.name])
}
}
}
}
} else {
results.push(json[i])
}
}
} }
} catch (e) {
if (castFunc !== null) { err = e
if (col.def != null) { col.def = castFunc(col.def) } }
for (let k = 0; k < data.length; k++) {
if (data[k][col.name] != null) { data[k][col.name] = castFunc(data[k][col.name]) }
}
}
}
} else { results.push(json[i]) }
}
} }
} catch (e) {
err = e return new ResultSet(results, err)
}
} }
return new ResultSet(results, err) /**
}
/**
* Runs a query on the database. * Runs a query on the database.
* *
* @param {String} query The SQL statement * @param {String} query The SQL statement
* @param {Object} params The query params * @param {Object} params The query params
* @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
}) })
return await this.execSql(sql) return await this.execSql(sql)
}
async query (query, params) {
const res = await this.execQuery(query, params)
return res.fetchData()
}
async getObject (query, params) {
const res = await this.execQuery(query, params)
return res.fetchObject()
}
async getValue (query, params) {
const res = await this.execQuery(query, params)
return res.fetchValue()
}
renderValue (v) {
switch (typeof v) {
case 'number':
return v
case 'boolean':
return (v) ? 'TRUE' : 'FALSE'
case 'string':
return "'" + v.replace(this.regexp, this.replaceFunc) + "'"
default:
if (v instanceof Date) {
if (!isNaN(v.getTime())) {
const unixTime = parseInt(fixTz(v).getTime() / 1000)
return 'DATE(FROM_UNIXTIME(' + unixTime + '))'
} else { return '0000-00-00' }
} else { return 'NULL' }
} }
}
/* async query (query, params) {
const res = await this.execQuery(query, params)
return res.fetchData()
}
async getObject (query, params) {
const res = await this.execQuery(query, params)
return res.fetchObject()
}
async getValue (query, params) {
const res = await this.execQuery(query, params)
return res.fetchValue()
}
renderValue (v) {
switch (typeof v) {
case 'number':
return v
case 'boolean':
return v ? 'TRUE' : 'FALSE'
case 'string':
return "'" + v.replace(this.regexp, this.replaceFunc) + "'"
default:
if (v instanceof Date) {
if (!isNaN(v.getTime())) {
const unixTime = parseInt(fixTz(v).getTime() / 1000)
return 'DATE(FROM_UNIXTIME(' + unixTime + '))'
} else {
return '0000-00-00'
}
} else {
return 'NULL'
}
}
}
/*
* Parses a value to date. * Parses a value to date.
*/ */
valueToDate (value) { valueToDate (value) {
return fixTz(new Date(value)) return fixTz(new Date(value))
} }
} }
// 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()
if (!hasTime) { if (!hasTime) {
date.setHours(date.getHours() + 12) date.setHours(date.getHours() + 12)
date.setHours(0, 0, 0, 0) date.setHours(0, 0, 0, 0)
} }
return date return date
} }

View File

@ -1,121 +1,130 @@
import { Result } from './result' import { Result } from './result'
/** /**
* This class stores the database results. * This class stores the database results.
*/ */
export class ResultSet { export class ResultSet {
results = null results = null
error = null error = null
/** /**
* Initilizes the resultset object. * Initilizes the resultset object.
*/ */
constructor (results, error) { constructor (results, error) {
this.results = results this.results = results
this.error = error this.error = error
} }
/** /**
* Gets the query error. * Gets the query error.
* *
* @return {Db.Err} the error or null if no errors hapened * @return {Db.Err} the error or null if no errors hapened
*/ */
getError () { getError () {
return this.error return this.error
} }
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
} }
/** /**
* Fetchs the next result from the resultset. * Fetchs the next result from the resultset.
* *
* @return {Db.Result} the result or %null if error or there are no more results * @return {Db.Result} the result or %null if error or there are no more results
*/ */
fetchResult () { fetchResult () {
const result = this.fetch() const result = this.fetch()
if (result !== null) { if (result !== null) {
if (result.data instanceof Array) { if (result.data instanceof Array) {
return new Result(result) return new Result(result)
} else { } else {
return true return true
} }
}
return null
} }
return null /**
}
/**
* Fetchs the first row object from the next resultset. * Fetchs the first row object from the next resultset.
* *
* @return {Array} the row if success, %null otherwise * @return {Array} the row if success, %null otherwise
*/ */
fetchObject () { fetchObject () {
const result = this.fetch() const result = this.fetch()
if (result !== null && if (
result.data instanceof Array && result !== null &&
result.data.length > 0) { return result.data[0] } result.data instanceof Array &&
result.data.length > 0
) {
return result.data[0]
}
return null return null
} }
/** /**
* Fetchs data from the next resultset. * Fetchs data from the next resultset.
* *
* @return {Array} the data * @return {Array} the data
*/ */
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 }
return null
} }
return null /**
}
/**
* Fetchs the first row and column value from the next resultset. * Fetchs the first row and column value from the next resultset.
* *
* @return {Object} the value if success, %null otherwise * @return {Object} the value if success, %null otherwise
*/ */
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
} }
/** /**
* Fetchs the first row from the next resultset. * Fetchs the first row from the next resultset.
* *
* @return {Array} the row if success, %null otherwise * @return {Array} the row if success, %null otherwise
*/ */
fetchRow () { fetchRow () {
const result = this.fetch() const result = this.fetch()
if (result !== null && if (
result.data instanceof Array && result !== null &&
result.data.length > 0) { result.data instanceof Array &&
const object = result.data[0] result.data.length > 0
const row = new Array(result.columns.length) ) {
for (let i = 0; i < row.length; i++) { const object = result.data[0]
row[i] = object[result.columns[i].name] const row = new Array(result.columns.length)
} for (let i = 0; i < row.length; i++) {
return row row[i] = object[result.columns[i].name]
}
return row
}
return null
} }
return null
}
} }

View File

@ -2,60 +2,64 @@
* This class stores a database result. * This class stores a database result.
*/ */
export class Result { export class Result {
/** /**
* Initilizes the result object. * Initilizes the result object.
*/ */
constructor (result) { constructor (result) {
this.data = result.data this.data = result.data
this.tables = result.tables this.tables = result.tables
this.columns = result.columns this.columns = result.columns
this.row = -1 this.row = -1
if (this.columns) { if (this.columns) {
this.columnMap = {} this.columnMap = {}
for (let i = 0; i < this.columns.length; i++) { for (let i = 0; i < this.columns.length; i++) {
const col = this.columns[i] const col = this.columns[i]
col.index = i col.index = i
this.columnMap[col.name] = col this.columnMap[col.name] = col
} }
} else { this.columnMap = null } } else {
} this.columnMap = null
}
}
/** /**
* Gets a value from de result. * Gets a value from de result.
* *
* @param {String} columnName The column name * @param {String} columnName The column name
* @return {Object} The cell value * @return {Object} The cell value
*/ */
get (columnName) { get (columnName) {
return this.data[this.row][columnName] return this.data[this.row][columnName]
} }
/** /**
* Gets a row. * Gets a row.
* *
* @return {Object} The cell value * @return {Object} The cell value
*/ */
getObject () { getObject () {
return this.data[this.row] return this.data[this.row]
} }
/** /**
* Resets the result iterator. * Resets the result iterator.
*/ */
reset () { reset () {
this.row = -1 this.row = -1
} }
/** /**
* Moves the internal iterator to the next row. * Moves the internal iterator to the next row.
*/ */
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'
@ -6,16 +5,16 @@ import { JsonException } from './json-exception'
* Handler for JSON rest connections. * Handler for JSON rest connections.
*/ */
export class JsonConnection extends VnObject { export class JsonConnection extends VnObject {
_connected = false _connected = false
_requestsCount = 0 _requestsCount = 0
token = null token = null
interceptors = [] interceptors = []
use (fn) { use (fn) {
this.interceptors.push(fn) this.interceptors.push(fn)
} }
/** /**
* Executes the specified REST service with the given params and calls * Executes the specified REST service with the given params and calls
* the callback when response is received. * the callback when response is received.
* *
@ -23,164 +22,179 @@ export class JsonConnection extends VnObject {
* @param {Object} params The params to pass to the service * @param {Object} params The params to pass to the service
* @return {Object} The parsed JSON response * @return {Object} The parsed JSON response
*/ */
async send (url, params) { async send (url, params) {
if (!params) params = {} if (!params) params = {}
params.srv = `json:${url}` params.srv = `json:${url}`
return this.sendWithUrl('POST', '.', params) return this.sendWithUrl('POST', '.', params)
}
async sendForm (form) {
const params = {}
const elements = form.elements
for (let i = 0; i < elements.length; i++) {
if (elements[i].name) { params[elements[i].name] = elements[i].value }
} }
return this.sendWithUrl('POST', form.action, params) async sendForm (form) {
} const params = {}
const elements = form.elements
async sendFormMultipart (form) { for (let i = 0; i < elements.length; i++) {
return this.request({ if (elements[i].name) {
method: 'POST', params[elements[i].name] = elements[i].value
url: form.action, }
data: new FormData(form)
})
}
async sendFormData (formData) {
return this.request({
method: 'POST',
url: '',
data: formData
})
}
/*
* Called when REST response is received.
*/
async sendWithUrl (method, url, params) {
const urlParams = new URLSearchParams()
for (const key in params) {
if (params[key] != null) {
urlParams.set(key, params[key])
}
}
return this.request({
method,
url,
data: urlParams.toString(),
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
}
async request (config) {
const request = new XMLHttpRequest()
request.open(config.method, config.url, true)
for (const fn of this.interceptors) {
config = fn(config)
}
const headers = config.headers
if (headers) {
for (const header in headers) {
request.setRequestHeader(header, headers[header])
}
}
const promise = new Promise((resolve, reject) => {
request.onreadystatechange =
() => this._onStateChange(request, resolve, reject)
})
request.send(config.data)
this._requestsCount++
if (this._requestsCount === 1) { this.emit('loading-changed', true) }
return promise
}
_onStateChange (request, resolve, reject) {
if (request.readyState !== 4) { return }
this._requestsCount--
if (this._requestsCount === 0) { this.emit('loading-changed', false) }
let data = null
let error = null
try {
if (request.status === 0) {
const err = new JsonException()
err.message = 'The server does not respond, please check your Internet connection'
err.statusCode = request.status
throw err
}
let contentType = null
try {
contentType = request
.getResponseHeader('Content-Type')
.split(';')[0]
.trim()
} catch (err) {
console.warn(err)
}
if (contentType !== 'application/json') {
const err = new JsonException()
err.message = request.statusText
err.statusCode = request.status
throw err
}
let json
let jsData
if (request.responseText) { json = JSON.parse(request.responseText) }
if (json) { jsData = json.data || json }
if (request.status >= 200 && request.status < 300) {
data = jsData
} else {
let exception = jsData.exception
const err = new JsonException()
err.statusCode = request.status
if (exception) {
exception = exception
.replace(/\\/g, '.')
.replace(/Exception$/, '')
.replace(/^Vn\.Web\./, '')
err.exception = exception
err.message = jsData.message
err.code = jsData.code
err.file = jsData.file
err.line = jsData.line
err.trace = jsData.trace
} else {
err.message = request.statusText
} }
throw err return this.sendWithUrl('POST', form.action, params)
}
} catch (e) {
data = null
error = e
} }
if (error) { async sendFormMultipart (form) {
this.emit('error', error) return this.request({
reject(error) method: 'POST',
} else { resolve(data) } url: form.action,
} data: new FormData(form)
})
}
async sendFormData (formData) {
return this.request({
method: 'POST',
url: '',
data: formData
})
}
/*
* Called when REST response is received.
*/
async sendWithUrl (method, url, params) {
const urlParams = new URLSearchParams()
for (const key in params) {
if (params[key] != null) {
urlParams.set(key, params[key])
}
}
return this.request({
method,
url,
data: urlParams.toString(),
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
}
async request (config) {
const request = new XMLHttpRequest()
request.open(config.method, config.url, true)
for (const fn of this.interceptors) {
config = fn(config)
}
const headers = config.headers
if (headers) {
for (const header in headers) {
request.setRequestHeader(header, headers[header])
}
}
const promise = new Promise((resolve, reject) => {
request.onreadystatechange = () =>
this._onStateChange(request, resolve, reject)
})
request.send(config.data)
this._requestsCount++
if (this._requestsCount === 1) {
this.emit('loading-changed', true)
}
return promise
}
_onStateChange (request, resolve, reject) {
if (request.readyState !== 4) {
return
}
this._requestsCount--
if (this._requestsCount === 0) {
this.emit('loading-changed', false)
}
let data = null
let error = null
try {
if (request.status === 0) {
const err = new JsonException()
err.message =
'The server does not respond, please check your Internet connection'
err.statusCode = request.status
throw err
}
let contentType = null
try {
contentType = request
.getResponseHeader('Content-Type')
.split(';')[0]
.trim()
} catch (err) {
console.warn(err)
}
if (contentType !== 'application/json') {
const err = new JsonException()
err.message = request.statusText
err.statusCode = request.status
throw err
}
let json
let jsData
if (request.responseText) {
json = JSON.parse(request.responseText)
}
if (json) {
jsData = json.data || json
}
if (request.status >= 200 && request.status < 300) {
data = jsData
} else {
let exception = jsData.exception
const err = new JsonException()
err.statusCode = request.status
if (exception) {
exception = exception
.replace(/\\/g, '.')
.replace(/Exception$/, '')
.replace(/^Vn\.Web\./, '')
err.exception = exception
err.message = jsData.message
err.code = jsData.code
err.file = jsData.file
err.line = jsData.line
err.trace = jsData.trace
} else {
err.message = request.statusText
}
throw err
}
} catch (e) {
data = null
error = e
}
if (error) {
this.emit('error', error)
reject(error)
} else {
resolve(data)
}
}
} }

View File

@ -2,14 +2,14 @@
* This class stores the database errors. * This class stores the database errors.
*/ */
export class JsonException { export class JsonException {
constructor (exception, message, code, file, line, trace, statucCode) { constructor (exception, message, code, file, line, trace, statucCode) {
this.name = 'JsonException' this.name = 'JsonException'
this.exception = exception this.exception = exception
this.message = message this.message = message
this.code = code this.code = code
this.file = file this.file = file
this.line = line this.line = line
this.trace = trace this.trace = trace
this.statusCode = statucCode this.statusCode = statucCode
} }
} }

View File

@ -1,248 +1,289 @@
/** /**
* 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.
*/ */
export class VnObject { export class VnObject {
/** /**
* Tag to be used when the class instance is defined via XML. All classes * Tag to be used when the class instance is defined via XML. All classes
* must define this attribute, even if it is not used. * must define this attribute, even if it is not used.
*/ */
static Tag = 'vn-object' static Tag = 'vn-object'
/** /**
* Class public properties. * Class public properties.
*/ */
static Properties = {} static Properties = {}
/* /*
* Reference count. * Reference count.
*/ */
_refCount = 1 _refCount = 1
/* /*
* Signal handlers data. * Signal handlers data.
*/ */
_thisArg = null _thisArg = null
/** /**
* Initializes the object and sets all properties passed to the class * Initializes the object and sets all properties passed to the class
* constructor. * constructor.
* *
* @param {Object} props The properties passed to the contructor * @param {Object} props The properties passed to the contructor
*/ */
constructor (props) { constructor (props) {
this.setProperties(props) this.setProperties(props)
} }
initialize (props) { initialize (props) {
this.setProperties(props) this.setProperties(props)
} }
/** /**
* Sets a group of object properties. * Sets a group of object properties.
* *
* @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]
}
}
/** /**
* Increases the object reference count. * Increases the object reference count.
*/ */
ref () { ref () {
this._refCount++ this._refCount++
return this return this
} }
/** /**
* Decreases the object reference count. * Decreases the object reference count.
*/ */
unref () { unref () {
this._refCount-- this._refCount--
if (this._refCount === 0) { this._destroy() } if (this._refCount === 0) {
} this._destroy()
}
}
/** /**
* Called from @Vn.Builder when it finds a custom tag as a child of the * Called from @Vn.Builder when it finds a custom tag as a child of the
* element. * element.
* *
* @param {Vn.Scope} scope The scope instance * @param {Vn.Scope} scope The scope instance
* @param {Node} node The custom tag child nodes * @param {Node} node The custom tag child nodes
*/ */
loadXml () {} loadXml () {}
/** /**
* Called from @Vn.Builder when it finds a a child tag that isn't * Called from @Vn.Builder when it finds a a child tag that isn't
* associated to any property. * associated to any property.
* *
* @param {Object} child The child object instance * @param {Object} child The child object instance
*/ */
appendChild () {} appendChild () {}
/** /**
* Conects a signal with a function. * Conects a signal with a function.
* *
* @param {string} id The signal identifier * @param {string} id The signal identifier
* @param {function} callback The callback * @param {function} callback The callback
* @param {Object} instance The instance * @param {Object} instance The instance
*/ */
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()
let callbacks = this._thisArg.signals[id]
if (!callbacks) {
callbacks = this._thisArg.signals[id] = []
}
callbacks.push({
blocked: false,
callback,
instance
})
} }
this._signalInit() /**
let callbacks = this._thisArg.signals[id]
if (!callbacks) { callbacks = this._thisArg.signals[id] = [] }
callbacks.push({
blocked: false,
callback,
instance
})
}
/**
* Locks/Unlocks a signal emission to the specified object. * Locks/Unlocks a signal emission to the specified object.
* *
* @param {string} id The signal identifier * @param {string} id The signal identifier
* @param {function} callback The callback * @param {function} callback The callback
* @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
}
}
} }
}
/** /**
* Emits a signal in the object. * Emits a signal in the object.
* *
* @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)
}
}
} }
}
/** /**
* Disconnects a signal from current object. * Disconnects a signal from current object.
* *
* @param {string} id The signal identifier * @param {string} id The signal identifier
* @param {function} callback The connected callback * @param {function} callback The connected callback
* @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)
}
}
}
} }
}
/** /**
* Disconnects all signals for the given instance. * Disconnects all signals for the given instance.
* *
* @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
for (const signalId in signals) {
const callbacks = signals[signalId]
if (callbacks) {
for (let i = callbacks.length; i--;) {
if (callbacks[i].instance === instance) { callbacks.splice(i, 1) }
} }
}
}
}
/** const signals = this._thisArg.signals
for (const signalId in signals) {
const callbacks = signals[signalId]
if (callbacks) {
for (let i = callbacks.length; i--;) {
if (callbacks[i].instance === instance) {
callbacks.splice(i, 1)
}
}
}
}
}
/**
* Destroys the object, this method should only be called before losing * Destroys the object, this method should only be called before losing
* the last reference to the object. It can be overwritten by child classes * the last reference to the object. It can be overwritten by child classes
* 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
} }
/** /**
* Links the object with another object. * Links the object with another object.
* *
* @param {Object} prop The linked property * @param {Object} prop The linked property
* @param {Object} handlers The object events to listen with * @param {Object} handlers The object events to listen with
*/ */
link (prop, handlers) { link (prop, handlers) {
this._signalInit() this._signalInit()
const links = this._thisArg.links const links = this._thisArg.links
for (const key in prop) { for (const key in prop) {
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
}
}
} }
}
_unlink (object) { _unlink (object) {
if (!object) return if (!object) return
object.disconnectByInstance(this) object.disconnectByInstance(this)
object.unref() object.unref()
} }
_signalInit () { _signalInit () {
if (!this._thisArg) { if (!this._thisArg) {
this._thisArg = { this._thisArg = {
signals: {}, signals: {},
links: {} links: {}
} }
}
} }
}
} }

View File

@ -1,5 +1,8 @@
<template> <template>
<q-layout id="bg" class="fullscreen row justify-center items-center layout-view scroll"> <q-layout
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>
@ -26,6 +29,6 @@
<script> <script>
export default { export default {
name: 'LoginLayout' name: 'LoginLayout'
} }
</script> </script>

View File

@ -8,67 +8,61 @@
round round
icon="menu" icon="menu"
aria-label="Menu" aria-label="Menu"
@click="toggleLeftDrawer"/> @click="toggleLeftDrawer"
/>
<q-toolbar-title> <q-toolbar-title>
{{$app.title}} {{ $app.title }}
<div <div v-if="$app.subtitle" class="subtitle text-caption">
v-if="$app.subtitle" {{ $app.subtitle }}
class="subtitle text-caption">
{{$app.subtitle}}
</div> </div>
</q-toolbar-title> </q-toolbar-title>
<div id="actions" ref="actions"> <div id="actions" ref="actions"></div>
</div>
<q-btn <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-icon name="menu" />
</q-btn> </q-btn>
</q-toolbar> </q-toolbar>
</q-header> </q-header>
<q-drawer <q-drawer v-model="leftDrawerOpen" :width="250" show-if-above>
v-model="leftDrawerOpen"
:width="250"
show-if-above>
<q-toolbar class="logo"> <q-toolbar class="logo">
<img src="statics/logo-dark.svg"> <img src="statics/logo-dark.svg" />
</q-toolbar> </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()"/> <q-btn 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"/> <q-btn flat icon="logout" alt="_Exit" />
</div> </div>
</div> </div>
<q-list <q-list v-for="item in essentialLinks" :key="item.id">
v-for="item in essentialLinks" <q-item v-if="!item.childs" :to="`/${item.path}`">
:key="item.id">
<q-item
v-if="!item.childs"
:to="`/${item.path}`">
<q-item-section> <q-item-section>
<q-item-label>{{item.description}}</q-item-label> <q-item-label>{{ item.description }}</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-expansion-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-list>
<q-item <q-item
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-section>
<q-item-label>{{subitem.description}}</q-item-label> <q-item-label>{{ subitem.description }}</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
</q-list> </q-list>
@ -134,13 +128,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;
} }
} }
@ -175,67 +169,69 @@ 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 () {
this.$refs.actions.appendChild(this.$actions)
await this.user.loadData()
await this.$app.loadConfig()
await this.fetchData()
},
methods: {
async fetchData () {
const sections = await this.$jApi.query('SELECT * FROM myMenu')
const sectionMap = new Map()
for (const section of sections) {
sectionMap.set(section.id, section)
}
const sectionTree = []
for (const section of sections) {
const parent = section.parentFk
if (parent) {
const parentSection = sectionMap.get(parent)
if (!parentSection) continue
let childs = parentSection.childs
if (!childs) { childs = parentSection.childs = [] }
childs.push(section)
} else {
sectionTree.push(section)
} }
}
this.essentialLinks = sectionTree
}, },
async logout () { async mounted () {
this.user.logout() this.$refs.actions.appendChild(this.$actions)
this.$router.push('/login') await this.user.loadData()
await this.$app.loadConfig()
await this.fetchData()
},
methods: {
async fetchData () {
const sections = await this.$jApi.query('SELECT * FROM myMenu')
const sectionMap = new Map()
for (const section of sections) {
sectionMap.set(section.id, section)
}
const sectionTree = []
for (const section of sections) {
const parent = section.parentFk
if (parent) {
const parentSection = sectionMap.get(parent)
if (!parentSection) continue
let childs = parentSection.childs
if (!childs) {
childs = parentSection.childs = []
}
childs.push(section)
} else {
sectionTree.push(section)
}
}
this.essentialLinks = sectionTree
},
async logout () {
this.user.logout()
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,74 +1,73 @@
import { date as qdate, format } from 'quasar' import { date as qdate, format } from 'quasar'
const { pad } = format const { pad } = format
export function currency (val) { export function currency (val) {
return typeof val === 'number' ? val.toFixed(2) + '€' : val return typeof val === 'number' ? val.toFixed(2) + '€' : val
} }
export function date (val, format) { export function date (val, format) {
if (val == null) return val if (val == null) return val
if (!(val instanceof Date)) { if (!(val instanceof Date)) {
val = new Date(val) val = new Date(val)
} }
return qdate.formatDate(val, format, window.i18n.tm('date')) return qdate.formatDate(val, format, window.i18n.tm('date'))
} }
export function relDate (val) { export function relDate (val) {
if (val == null) return val if (val == null) return val
if (!(val instanceof Date)) { if (!(val instanceof Date)) {
val = new Date(val) val = new Date(val)
}
const dif = qdate.getDateDiff(new Date(), val, 'days')
let day
switch (dif) {
case 0:
day = 'today'
break
case 1:
day = 'yesterday'
break
case -1:
day = 'tomorrow'
break
}
if (day) {
day = window.i18n.t(day)
} else {
if (dif > 0 && dif <= 7) {
day = qdate.formatDate(val, 'ddd', window.i18n.tm('date'))
} else {
day = qdate.formatDate(val, 'ddd, MMMM Do', window.i18n.tm('date'))
} }
}
return day const dif = qdate.getDateDiff(new Date(), val, 'days')
let day
switch (dif) {
case 0:
day = 'today'
break
case 1:
day = 'yesterday'
break
case -1:
day = 'tomorrow'
break
}
if (day) {
day = window.i18n.t(day)
} else {
if (dif > 0 && dif <= 7) {
day = qdate.formatDate(val, 'ddd', window.i18n.tm('date'))
} else {
day = qdate.formatDate(val, 'ddd, MMMM Do', window.i18n.tm('date'))
}
}
return day
} }
export function relTime (val) { export function relTime (val) {
if (val == null) return val if (val == null) return val
if (!(val instanceof Date)) { if (!(val instanceof Date)) {
val = new Date(val) val = new Date(val)
} }
return relDate(val) + ' ' + qdate.formatDate(val, 'H:mm:ss') return relDate(val) + ' ' + qdate.formatDate(val, 'H:mm:ss')
} }
export function elapsedTime (val) { export function elapsedTime (val) {
if (val == null) return val if (val == null) return 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)
val -= hours * 3600 val -= hours * 3600
const minutes = Math.floor(val / 60) const minutes = Math.floor(val / 60)
val -= minutes * 60 val -= minutes * 60
const seconds = val const seconds = val
return `${pad(hours, 2)}:${pad(minutes, 2)}:${pad(seconds, 2)}` return `${pad(hours, 2)}:${pad(minutes, 2)}:${pad(seconds, 2)}`
} }

View File

@ -1,18 +1,14 @@
<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"
v-for="myNew in news"
:key="myNew.id">
<q-card> <q-card>
<q-img :src="`${$app.imageUrl}/news/full/${myNew.image}`"> <q-img :src="`${$app.imageUrl}/news/full/${myNew.image}`"> </q-img>
</q-img>
<q-card-section> <q-card-section>
<div class="text-h5">{{ myNew.title }}</div> <div class="text-h5">{{ myNew.title }}</div>
</q-card-section> </q-card-section>
<q-card-section class="new-body"> <q-card-section class="new-body">
<div v-html="myNew.text"/> <div v-html="myNew.text" />
</q-card-section> </q-card-section>
</q-card> </q-card>
</div> </div>
@ -50,31 +46,31 @@
<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>

File diff suppressed because it is too large Load Diff

View File

@ -7,13 +7,15 @@
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"> <q-card v-if="invoices?.length">
<q-table <q-table
@ -22,7 +24,8 @@
: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"> <q-tr :props="props">
<q-td key="ref" :props="props"> <q-td key="ref" :props="props">
@ -42,13 +45,15 @@
:href="invoiceUrl(props.row.id)" :href="invoiceUrl(props.row.id)"
target="_blank" target="_blank"
flat flat
round/> round
/>
<q-icon <q-icon
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-td>
</q-tr> </q-tr>
</template> </template>
@ -61,105 +66,108 @@
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
}, },
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()
} }
}, },
methods: { methods: {
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
WHERE issued BETWEEN #from AND #to WHERE issued BETWEEN #from AND #to
ORDER BY issued DESC ORDER BY issued DESC
LIMIT 500`, LIMIT 500`,
params params
) )
}, },
invoiceUrl (id) { invoiceUrl (id) {
return '?' + new URLSearchParams({ return (
srv: 'rest:dms/invoice', '?' +
invoice: id, new URLSearchParams({
access_token: this.$user.token srv: 'rest:dms/invoice',
}).toString() invoice: id,
access_token: this.$user.token
}).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
amount: Importe amount: Importe
downloadInvoicePdf: Baixar PDF downloadInvoicePdf: Baixar PDF
notDownloadable: Não disponível para download, solicite a fatura ao seu comercial notDownloadable: Não disponível para download, solicite a fatura ao seu comercial
</i18n> </i18n>

View File

@ -1,56 +1,52 @@
<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 <q-icon name="info" :title="$t('paymentInfo')" class="info" size="24px" />
name="info"
:title="$t('paymentInfo')"
class="info"
size="24px"/>
</div> </div>
<q-btn <q-btn
icon="payments" icon="payments"
:label="$t('makePayment')" :label="$t('makePayment')"
@click="onPayClick()" @click="onPayClick()"
rounded rounded
no-caps/> no-caps
/>
<q-btn <q-btn
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"> <q-card v-if="orders?.length">
<q-list bordered separator padding > <q-list bordered separator padding>
<q-item <q-item
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-section>
<q-item-label> <q-item-label>
{{date(order.landed, 'ddd, MMMM Do')}} {{ date(order.landed, 'ddd, MMMM Do') }}
</q-item-label> </q-item-label>
<q-item-label caption>#{{order.id}}</q-item-label> <q-item-label caption>#{{ order.id }}</q-item-label>
<q-item-label caption>{{order.nickname}}</q-item-label> <q-item-label caption>{{ order.nickname }}</q-item-label>
<q-item-label caption>{{order.agency}}</q-item-label> <q-item-label caption>{{ order.agency }}</q-item-label>
</q-item-section>
<q-item-section side top>
{{order.total}}
</q-item-section> </q-item-section>
<q-item-section side top> {{ order.total }} </q-item-section>
</q-item> </q-item>
</q-list> </q-list>
</q-card> </q-card>
@ -60,7 +56,8 @@
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> </q-page-sticky>
</div> </div>
</template> </template>
@ -95,107 +92,103 @@ 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 () {
await this.tpv.check(this.$route)
this.orders = await this.$jApi.query('CALL myTicket_list(NULL, NULL)')
this.debt = await this.$jApi.getValue('SELECT -myClient_getDebt(NULL)')
},
methods: {
date,
currency,
async onPayClick () {
let amount = -this.debt
amount = amount <= 0 ? null : amount
let defaultAmountStr = ''
if (amount !== null) {
defaultAmountStr = amount
}
amount = prompt(this.$t('amountToPay'), defaultAmountStr)
if (amount != null) {
amount = parseFloat(amount.replace(',', '.'))
await this.tpv.pay(amount)
}
}
} }
},
async mounted () {
await this.tpv.check(this.$route)
this.orders = await this.$jApi.query(
'CALL myTicket_list(NULL, NULL)'
)
this.debt = await this.$jApi.getValue(
'SELECT -myClient_getDebt(NULL)'
)
},
methods: {
date,
currency,
async onPayClick () {
let amount = -this.debt
amount = amount <= 0 ? null : amount
let defaultAmountStr = ''
if (amount !== null) {
defaultAmountStr = amount
}
amount = prompt(this.$t('amountToPay'), defaultAmountStr)
if (amount != null) {
amount = parseFloat(amount.replace(',', '.'))
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
shoppingCart: Shopping cart shoppingCart: Shopping cart
balance: 'Balance:' balance: 'Balance:'
paymentInfo: >- paymentInfo: >-
The amount shown is your slope (negative) or favorable balance today, it The amount shown is your slope (negative) or favorable balance today, it
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
shoppingCart: Cesta de la compra shoppingCart: Cesta de la compra
balance: 'Saldo:' balance: 'Saldo:'
paymentInfo: >- paymentInfo: >-
La cantidad mostrada es tu saldo pendiente (negativa) o favorable a día de La cantidad mostrada es tu saldo pendiente (negativa) o favorable a día de
hoy, no tiene en cuenta pedidos del futuro. Para que tu pedido sea enviado, hoy, no tiene en cuenta pedidos del futuro. Para que tu pedido sea enviado,
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
shoppingCart: Cistella de la compra shoppingCart: Cistella de la compra
balance: 'Saldo:' balance: 'Saldo:'
paymentInfo: >- paymentInfo: >-
La quantitat mostrada és el teu saldo pendent (negatiu) o favorable a dia La quantitat mostrada és el teu saldo pendent (negatiu) o favorable a dia
d'avui, no en compte comandes del futur. Perquè la teva comanda sigui d'avui, no en compte comandes del futur. Perquè la teva comanda sigui
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
shoppingCart: Panier shoppingCart: Panier
balance: 'Balance:' balance: 'Balance:'
paymentInfo: >- paymentInfo: >-
Le montant indiqué est votre pente (négative) ou balance favorable Le montant indiqué est votre pente (négative) ou balance favorable
aujourd'hui, ne tient pas compte pour les commandes futures. Obtenir votre aujourd'hui, ne tient pas compte pour les commandes futures. Obtenir votre
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
shoppingCart: Cesta da compra shoppingCart: Cesta da compra
balance: 'Saldo:' balance: 'Saldo:'
paymentInfo: >- paymentInfo: >-
A quantidade mostrada é seu saldo pendente (negativo) ou favorável a dia de A quantidade mostrada é seu saldo pendente (negativo) ou favorável a dia de
hoje, não se vincula a pedidos futuros. Para que seu pedido seja enviado, esta hoje, não se vincula a pedidos futuros. Para que seu pedido seja enviado, esta
quantidade deve ser igual ou superior a 0. Se queres realizar um depósito à quantidade deve ser igual ou superior a 0. Se queres realizar um depósito à
conta, clique no botão de pagamento, apague a quantidade sugerida e introduza conta, clique no botão de pagamento, apague a quantidade sugerida e introduza
a quantidade que deseje. a quantidade que deseje.
</i18n> </i18n>

View File

@ -5,51 +5,61 @@
: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"> <q-card class="vn-w-sm">
<q-card-section> <q-card-section>
<div class="text-h6">#{{ticket.id}}</div> <div class="text-h6">#{{ ticket.id }}</div>
</q-card-section> </q-card-section>
<q-card-section> <q-card-section>
<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') }} {{ date(ticket.shipped, 'ddd, MMMM Do') }}
<div>{{$t(ticket.method != 'PICKUP' ? 'agency' : 'warehouse')}} {{ticket.agency}}</div> </div>
<div>
{{ $t('delivery') }} {{ date(ticket.shipped, 'ddd, MMMM Do') }}
</div>
<div>
{{ $t(ticket.method != 'PICKUP' ? 'agency' : 'warehouse') }}
{{ ticket.agency }}
</div>
</q-card-section> </q-card-section>
<q-card-section> <q-card-section>
<div class="text-h6">{{$t('deliveryAddress')}}</div> <div class="text-h6">{{ $t('deliveryAddress') }}</div>
<div>{{ticket.nickname}}</div> <div>{{ ticket.nickname }}</div>
<div>{{ticket.street}}</div> <div>{{ ticket.street }}</div>
<div>{{ticket.postalCode}} {{ticket.city}} ({{ticket.province}})</div> <div>
{{ ticket.postalCode }} {{ ticket.city }} ({{ ticket.province }})
</div>
</q-card-section> </q-card-section>
<q-separator inset /> <q-separator inset />
<q-list v-for="row in rows" :key="row.itemFk"> <q-list v-for="row in rows" :key="row.itemFk">
<q-item> <q-item>
<q-item-section avatar> <q-item-section avatar>
<q-avatar size="68px"> <q-avatar size="68px">
<img :src="`${$app.imageUrl}/catalog/200x200/${row.image}`"> <img :src="`${$app.imageUrl}/catalog/200x200/${row.image}`" />
</q-avatar> </q-avatar>
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label lines="1"> <q-item-label lines="1">
{{row.concept}} {{ row.concept }}
</q-item-label> </q-item-label>
<q-item-label lines="1" caption> <q-item-label lines="1" caption>
{{row.value5}} {{row.value6}} {{row.value7}} {{ row.value5 }} {{ row.value6 }} {{ row.value7 }}
</q-item-label> </q-item-label>
<q-item-label lines="1"> <q-item-label lines="1">
{{row.quantity}} x {{currency(row.price)}} {{ row.quantity }} x {{ currency(row.price) }}
</q-item-label> </q-item-label>
</q-item-section> </q-item-section>
<q-item-section side class="total"> <q-item-section side class="total">
<q-item-label> <q-item-label>
<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> </q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
@ -68,60 +78,59 @@
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 () {
const params = {
ticket: parseInt(this.$route.params.id)
}
this.ticket = await this.$jApi.getObject(
'CALL myTicket_get(#ticket)',
params
)
this.rows = await this.$jApi.query(
'CALL myTicket_getRows(#ticket)',
params
)
this.services = await this.$jApi.query(
'CALL myTicket_getServices(#ticket)',
params
)
this.packages = await this.$jApi.query(
'CALL myTicket_getPackages(#ticket)',
params
)
},
methods: {
date,
currency,
discountSubtotal (line) {
return line.quantity * line.price
}, },
subtotal (line) { async mounted () {
const discount = line.discount const params = {
return this.discountSubtotal(line) * ((100 - discount) / 100) ticket: parseInt(this.$route.params.id)
}
this.ticket = await this.$jApi.getObject(
'CALL myTicket_get(#ticket)',
params
)
this.rows = await this.$jApi.query('CALL myTicket_getRows(#ticket)', params)
this.services = await this.$jApi.query(
'CALL myTicket_getServices(#ticket)',
params
)
this.packages = await this.$jApi.query(
'CALL myTicket_getPackages(#ticket)',
params
)
}, },
onPrintClick () { methods: {
const params = new URLSearchParams({ date,
access_token: this.$user.token, currency,
recipientId: this.$user.id,
type: 'deliveryNote' discountSubtotal (line) {
}) return line.quantity * line.price
window.open(`/api/Tickets/${this.ticket.id}/delivery-note-pdf?${params.toString()}`) },
subtotal (line) {
const discount = line.discount
return this.discountSubtotal(line) * ((100 - discount) / 100)
},
onPrintClick () {
const params = new URLSearchParams({
access_token: this.$user.token,
recipientId: this.$user.id,
type: 'deliveryNote'
})
window.open(
`/api/Tickets/${this.ticket.id}/delivery-note-pdf?${params.toString()}`
)
}
} }
}
} }
</script> </script>

View File

@ -1,13 +1,11 @@
<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...</div>
Oops. Nothing here...
</div>
<q-btn <q-btn
class="q-mt-xl" class="q-mt-xl"
@ -26,6 +24,6 @@
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
export default defineComponent({ export default defineComponent({
name: 'ErrorNotFound' name: 'ErrorNotFound'
}) })
</script> </script>

View File

@ -4,7 +4,7 @@
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> </q-page>
</template> </template>
@ -12,6 +12,6 @@
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
export default defineComponent({ export default defineComponent({
name: 'IndexPage' name: 'IndexPage'
}) })
</script> </script>

View File

@ -2,25 +2,18 @@
<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"> <q-form @submit="onLogin" class="q-gutter-y-md">
<div class="q-gutter-y-sm"> <div class="q-gutter-y-sm">
<q-input <q-input v-model="email" :label="$t('user')" autofocus />
v-model="email"
:label="$t('user')"
autofocus
/>
<q-input <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 <q-icon
:name="showPwd ? 'visibility_off' : 'visibility'" :name="showPwd ? 'visibility_off' : 'visibility'"
@ -60,20 +53,18 @@
</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> </q-form>
<div class="footer text-center"> <div class="footer text-center">
<p> <p>
{{$t('notACustomerYet')}} {{ $t('notACustomerYet') }}
<a href="//verdnatura.es/register/" target="_blank" class="link"> <a href="//verdnatura.es/register/" target="_blank" class="link">
{{$t('signUp')}} {{ $t('signUp') }}
</a> </a>
</p> </p>
<p class="contact"> <p class="contact">{{ $t('loginPhone') }} · {{ $t('loginMail') }}</p>
{{$t('loginPhone')}} · {{$t('loginMail')}}
</p>
</div> </div>
</div> </div>
</template> </template>
@ -106,13 +97,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;
@ -128,37 +119,37 @@ a {
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>

View File

@ -4,27 +4,27 @@
<q-icon <q-icon
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"> <q-form @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 <q-input
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 <q-btn
@ -38,7 +38,7 @@
/> />
<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>
@ -56,53 +56,53 @@
} }
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: {
async onSend () {
const params = {
email: this.email
}
await this.$axios.post('Users/reset', params)
this.$q.notify({
message: this.$t('weHaveSentEmailToRecover'),
type: 'positive'
})
this.$router.push('/login')
}
} }
},
methods: {
async onSend () {
const params = {
email: this.email
}
await this.$axios.post('Users/reset', params)
this.$q.notify({
message: this.$t('weHaveSentEmailToRecover'),
type: 'positive'
})
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
dontWorry: Don't worry! dontWorry: Don't worry!
fillData: Fill the data fillData: Fill the data
weSendEmail: We will sent you an email to recover your password weSendEmail: We will sent you an email to recover your password
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
dontWorry: ¡No te preocupes! dontWorry: ¡No te preocupes!
fillData: Rellena los datos fillData: Rellena los datos
weSendEmail: Te enviaremos un correo para restablecer tu contraseña weSendEmail: Te enviaremos un correo para restablecer tu contraseña
weHaveSentEmailToRecover: Te hemos enviado un correo donde podrás recuperar tu contraseña weHaveSentEmailToRecover: Te hemos enviado un correo donde podrás recuperar tu contraseña
send: Enviar send: Enviar
return: Volver return: Volver
</i18n> </i18n>

View File

@ -4,13 +4,13 @@
<q-icon <q-icon
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> </q-card-section>
<q-card-section> <q-card-section>
<q-form @submit="onRegister" ref="form" class="q-gutter-y-md"> <q-form @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 <q-input
@ -19,7 +19,8 @@
:type="showPwd ? 'password' : 'text'" :type="showPwd ? 'password' : 'text'"
autofocus autofocus
hint="" hint=""
filled> filled
>
<template v-slot:append> <template v-slot:append>
<q-icon <q-icon
:name="showPwd ? 'visibility_off' : 'visibility'" :name="showPwd ? 'visibility_off' : 'visibility'"
@ -32,9 +33,10 @@
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 <q-icon
:name="showRpPwd ? 'visibility_off' : 'visibility'" :name="showRpPwd ? 'visibility_off' : 'visibility'"
@ -53,7 +55,7 @@
/> />
<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>
@ -64,30 +66,34 @@
<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(
newPassword: this.password 'users/reset-password',
}, { headers }) {
newPassword: this.password
},
{ 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'
/* /*
@ -13,30 +18,34 @@ 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 }),
routes, routes,
// 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) => {
if (from.name === to.name) return
const app = appStore()
app.$patch({
title: window.i18n.t(to.name || 'home'),
subtitle: null,
useRightDrawer: false,
rightDrawerOpen: true
}) })
})
return Router Router.afterEach((to, from) => {
if (from.name === to.name) return
const app = appStore()
app.$patch({
title: window.i18n.t(to.name || 'home'),
subtitle: null,
useRightDrawer: false,
rightDrawerOpen: true
})
})
return Router
}) })

View File

@ -1,61 +1,68 @@
const routes = [ const routes = [
{ {
path: '/login', path: '/login',
component: () => import('layouts/LoginLayout.vue'), component: () => import('layouts/LoginLayout.vue'),
children: [ children: [
{ {
name: 'login', name: 'login',
path: '/login/:email?', path: '/login/:email?',
component: () => import('pages/Login/Login.vue') component: () => import('pages/Login/Login.vue')
}, { },
name: 'rememberPassword', {
path: '/remember-password', name: 'rememberPassword',
component: () => import('pages/Login/RememberPassword.vue') path: '/remember-password',
}, { component: () => import('pages/Login/RememberPassword.vue')
name: 'resetPassword', },
path: '/reset-password', {
component: () => import('pages/Login/ResetPassword.vue') name: 'resetPassword',
} path: '/reset-password',
] component: () => import('pages/Login/ResetPassword.vue')
}, { }
path: '/', ]
component: () => import('layouts/MainLayout.vue'), },
children: [ {
{ path: '/',
name: '', component: () => import('layouts/MainLayout.vue'),
path: '', children: [
component: () => import('src/pages/Cms/Home.vue') {
}, { name: '',
name: 'home', path: '',
path: '/cms/home', component: () => import('src/pages/Cms/Home.vue')
component: () => import('src/pages/Cms/Home.vue') },
}, { {
name: 'orders', name: 'home',
path: '/ecomerce/orders', path: '/cms/home',
component: () => import('pages/Ecomerce/Orders.vue') component: () => import('src/pages/Cms/Home.vue')
}, { },
name: 'ticket', {
path: '/ecomerce/ticket/:id', name: 'orders',
component: () => import('pages/Ecomerce/Ticket.vue') path: '/ecomerce/orders',
}, { component: () => import('pages/Ecomerce/Orders.vue')
name: 'invoices', },
path: '/ecomerce/invoices', {
component: () => import('pages/Ecomerce/Invoices.vue') name: 'ticket',
}, { path: '/ecomerce/ticket/:id',
name: 'catalog', component: () => import('pages/Ecomerce/Ticket.vue')
path: '/ecomerce/catalog/:category?/:type?', },
component: () => import('pages/Ecomerce/Catalog.vue') {
} name: 'invoices',
] path: '/ecomerce/invoices',
}, component: () => import('pages/Ecomerce/Invoices.vue')
},
{
name: 'catalog',
path: '/ecomerce/catalog/:category?/:type?',
component: () => import('pages/Ecomerce/Catalog.vue')
}
]
},
// Always leave this as last one, // Always leave this as last one,
// but you can also remove it // but you can also remove it
{ {
path: '/:catchAll(.*)*', path: '/:catchAll(.*)*',
component: () => import('pages/ErrorNotFound.vue') component: () => import('pages/ErrorNotFound.vue')
} }
] ]
export default routes export default routes

View File

@ -2,20 +2,18 @@ import { defineStore } from 'pinia'
import { jApi } from 'boot/axios' import { jApi } from 'boot/axios'
export const appStore = defineStore('hedera', { export const appStore = defineStore('hedera', {
state: () => ({ state: () => ({
title: null, title: null,
subtitle: null, subtitle: null,
imageUrl: '', imageUrl: '',
useRightDrawer: false, useRightDrawer: false,
rightDrawerOpen: false rightDrawerOpen: false
}), }),
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

@ -11,10 +11,10 @@ import { createPinia } from 'pinia'
*/ */
export default store((/* { ssrContext } */) => { export default store((/* { ssrContext } */) => {
const pinia = createPinia() const pinia = createPinia()
// You can add Pinia plugins here // You can add Pinia plugins here
// pinia.use(SomePiniaPlugin) // pinia.use(SomePiniaPlugin)
return pinia return pinia
}) })

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

@ -2,86 +2,92 @@ import { defineStore } from 'pinia'
import { jApi } from 'boot/axios' import { jApi } from 'boot/axios'
export const tpvStore = defineStore('tpv', { export const tpvStore = defineStore('tpv', {
actions: { actions: {
async check (route) { async check (route) {
const order = route.query.tpvOrder const order = route.query.tpvOrder
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
}, },
async pay (amount, company) { async pay (amount, company) {
await this.realPay(amount * 100, company) await this.realPay(amount * 100, company)
}, },
async retryPay (order) { async retryPay (order) {
const payment = await jApi.getObject( const payment = await jApi.getObject(
`SELECT t.amount, m.companyFk `SELECT t.amount, m.companyFk
FROM myTpvTransaction t FROM myTpvTransaction t
JOIN tpvMerchant m ON m.id = t.merchantFk JOIN tpvMerchant m ON m.id = t.merchantFk
WHERE t.id = #order`, WHERE t.id = #order`,
{ order } { order }
) )
await this.realPay(payment.amount, payment.companyFk) await this.realPay(payment.amount, payment.companyFk)
}, },
async realPay (amount, company) { async realPay (amount, company) {
if (!isNumeric(amount) || amount <= 0) { if (!isNumeric(amount) || amount <= 0) {
throw new Error('payAmountError') throw new Error('payAmountError')
} }
const json = await jApi.send('tpv/transaction', { const json = await jApi.send('tpv/transaction', {
amount: parseInt(amount), amount: parseInt(amount),
urlOk: this.makeUrl('ok'), urlOk: this.makeUrl('ok'),
urlKo: this.makeUrl('ko'), urlKo: this.makeUrl('ko'),
company company
}) })
const postValues = json.postValues const postValues = json.postValues
const form = document.createElement('form') const form = document.createElement('form')
form.method = 'POST' form.method = 'POST'
form.action = json.url form.action = json.url
document.body.appendChild(form) document.body.appendChild(form)
for (const field in postValues) { for (const field in postValues) {
const input = document.createElement('input') const input = document.createElement('input')
input.type = 'hidden' input.type = 'hidden'
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()
}, },
makeUrl (status) { makeUrl (status) {
let path = location.protocol + '//' + location.hostname let path = location.protocol + '//' + location.hostname
path += location.port ? ':' + location.port : '' path += location.port ? ':' + location.port : ''
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 +=
tpvStatus: status, '?' +
tpvOrder: '_transactionId_' new URLSearchParams({
}).toString() tpvStatus: status,
return path tpvOrder: '_transactionId_'
}).toString()
return path
}
} }
}
}) })
function isNumeric (n) { function isNumeric (n) {
return !isNaN(parseFloat(n)) && isFinite(n) return !isNaN(parseFloat(n)) && isFinite(n)
} }

View File

@ -2,60 +2,59 @@ 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
} }
},
getters: {
loggedIn: state => state.token != null
},
actions: {
async login (user, password, remember) {
const params = { user, password }
const res = await api.post('Accounts/login', params)
if (remember) {
localStorage.setItem('vnToken', res.data.token)
} else {
sessionStorage.setItem('vnToken', res.data.token)
}
this.$patch({
token: res.data.token,
name: user
})
}, },
async logout () { getters: {
if (this.token != null) { loggedIn: (state) => state.token != null
try {
await api.post('Accounts/logout')
} catch (e) {}
localStorage.removeItem('vnToken')
sessionStorage.removeItem('vnToken')
}
this.$reset()
}, },
async loadData () { actions: {
const userData = await jApi.getObject( async login (user, password, remember) {
'SELECT id, nickname FROM account.myUser' const params = { user, password }
) const res = await api.post('Accounts/login', params)
this.$patch({ if (remember) {
id: userData.id, localStorage.setItem('vnToken', res.data.token)
nickname: userData.nickname } else {
}) sessionStorage.setItem('vnToken', res.data.token)
}
this.$patch({
token: res.data.token,
name: user
})
},
async logout () {
if (this.token != null) {
try {
await api.post('Accounts/logout')
} catch (e) {}
localStorage.removeItem('vnToken')
sessionStorage.removeItem('vnToken')
}
this.$reset()
},
async loadData () {
const userData = await jApi.getObject(
'SELECT id, nickname FROM account.myUser'
)
this.$patch({
id: userData.id,
nickname: userData.nickname
})
}
} }
}
}) })