Merge pull request 'Mejoras sección pedidos' (!79) from wbuezas/hedera-web-mindshore:feature/MejorasPedidos into 4922-vueMigration

Reviewed-on: #79
This commit is contained in:
Javier Segarra 2024-08-23 12:05:15 +00:00
commit ed29a1939c
29 changed files with 606 additions and 255 deletions

View File

@ -22,8 +22,8 @@ module.exports = {
// Uncomment any of the lines below to choose desired strictness,
// but leave only one uncommented!
// See https://eslint.vuejs.org/rules/#available-rules
'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention)
// 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
// 'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention)
'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
// 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
'standard'
@ -66,7 +66,18 @@ module.exports = {
'prefer-promise-reject-errors': 'off',
semi: 'off',
// allow debugger during development only
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'vue/html-indent': [
'error',
4,
{
attribute: 1,
baseIndent: 1,
closeBracket: 0,
alignAttributesVertically: true,
ignores: []
}
]
},
overrides: [
{

View File

@ -3,8 +3,11 @@
"eslint.autoFixOnSave": true,
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": false,
"editor.defaultFormatter": null,
"editor.codeActionsOnSave": ["source.fixAll.eslint"],
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"]
"eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"],
"[sql]": {
"editor.formatOnSave": true
}
}

View File

@ -15,6 +15,12 @@ const { configure } = require('quasar/wrappers');
module.exports = configure(function (ctx) {
return {
// fix: true,
// include = [],
// exclude = [],
// rawOptions = {},
warnings: true,
errors: true,
// https://v2.quasar.dev/quasar-cli-webpack/supporting-ts
supportTS: false,
@ -102,7 +108,7 @@ module.exports = configure(function (ctx) {
proxy: {
'/api': 'http://localhost:3000',
'/': {
target: 'http://localhost:3002',
target: 'http://localhost:3001',
bypass: req => (req.path !== '/' ? req.path : null)
}
}

View File

@ -21,7 +21,7 @@ const onRequestError = error => {
};
const onResponseError = error => {
let message = '';
let message = error.message;
const response = error.response;
const responseData = response && response.data;
@ -47,6 +47,7 @@ export default boot(({ app }) => {
api.interceptors.response.use(response => response, onResponseError);
jApi.use(addToken);
jApi.useErrorInterceptor(onResponseError);
// for use inside Vue files (Options API) through this.$axios and this.$api

View File

@ -179,7 +179,10 @@ defineExpose({
</script>
<template>
<QCard class="form-container" v-bind="$attrs">
<QCard
class="form-container"
v-bind="$attrs"
>
<QForm
v-if="!loading"
ref="addressFormRef"
@ -188,7 +191,14 @@ defineExpose({
<span class="text-h6 text-bold">
{{ title }}
</span>
<slot name="form" :data="formData" />
<slot
name="form"
:data="formData"
/>
<slot
name="extraForm"
:data="formData"
/>
<component
:is="showBottomActions ? 'div' : Teleport"
:to="$actions"
@ -217,11 +227,20 @@ defineExpose({
<slot name="actions" />
</component>
</QForm>
<QSpinner v-else color="primary" size="3em" :thickness="2" />
<QSpinner
v-else
color="primary"
size="3em"
:thickness="2"
/>
</QCard>
</template>
<style lang="scss" scoped>
.no-form-container {
padding: 0 !important;
box-shadow: none;
border: none;
}
.form-container {
width: 100%;
height: max-content;

View File

@ -56,10 +56,13 @@ defineExpose({
const inputRules = [
val => {
const { min } = vnInputRef.value.$attrs;
const { min, max } = vnInputRef.value.$attrs;
if (min >= 0) {
if (Math.floor(val) < min) return t('inputMin', { value: min });
}
if (max > 0) {
if (Math.floor(val) > max) return t('inputMax', { value: max });
}
}
];
</script>
@ -82,11 +85,17 @@ const inputRules = [
hide-bottom-space
@keyup.enter="emit('keyup.enter')"
>
<template v-if="$slots.prepend" #prepend>
<template
v-if="$slots.prepend"
#prepend
>
<slot name="prepend" />
</template>
<template #append>
<slot v-if="$slots.append && !$attrs.disabled" name="append" />
<slot
v-if="$slots.append && !$attrs.disabled"
name="append"
/>
<QIcon
v-if="hover && value && !$attrs.disabled && props.clearable"
name="close"
@ -98,7 +107,10 @@ const inputRules = [
}
"
/>
<QIcon v-if="info" name="info">
<QIcon
v-if="info"
name="info"
>
<QTooltip max-width="350px">
{{ info }}
</QTooltip>
@ -111,12 +123,17 @@ const inputRules = [
<i18n lang="yaml">
en-US:
inputMin: Must be more than {value}
inputMax: Must be less than {value}
es-ES:
inputMin: Must be more than {value}
inputMin: Debe ser mayor a {value}
inputMax: Debe ser menor a {value}
ca-ES:
inputMin: Ha de ser més gran que {value}
inputMax: Ha de ser menys que {value}
fr-FR:
inputMin: Doit être supérieur à {value}
inputMax: Doit être supérieur à {value}
pt-PT:
inputMin: Deve ser maior que {value}
inputMax: Deve ser maior que {value}
</i18n>

View File

@ -162,7 +162,10 @@ async function filterHandler(val, update) {
:rules="$attrs.required ? [requiredFieldRule] : null"
virtual-scroll-slice-size="options.length"
>
<template v-if="isClearable" #append>
<template
v-if="isClearable"
#append
>
<QIcon
v-show="value"
name="close"
@ -176,7 +179,11 @@ async function filterHandler(val, update) {
#[slotName]="slotData"
:key="slotName"
>
<slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
<slot
:name="slotName"
v-bind="slotData ?? {}"
:key="slotName"
/>
</template>
</QSelect>
</template>

View File

@ -30,7 +30,10 @@ const handleClick = () => {
</div>
</div>
</QItemSection>
<QItemSection class="no-padding" side>
<QItemSection
class="no-padding"
side
>
<slot name="actions" />
</QItemSection>
</QItem>

View File

@ -98,12 +98,12 @@ onMounted(async () => {
<VnForm
ref="vnFormRef"
:title="t('changePassword')"
:formInitialData="formData"
:saveFn="changePassword"
showBottomActions
:defaultActions="false"
:form-initial-data="formData"
:save-fn="changePassword"
show-bottom-actions
:default-actions="false"
style="max-width: 300px"
@onDataSaved="onPasswordChanged()"
@on-data-saved="onPasswordChanged()"
>
<template #form>
<VnInput
@ -112,38 +112,42 @@ onMounted(async () => {
v-model="formData.oldPassword"
:type="!showOldPwd ? 'password' : 'text'"
:label="t('oldPassword')"
>
>
<template #append>
<QIcon
:name="showOldPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="showOldPwd = !showOldPwd"
/>
</template>
</VnInput>
<QIcon
:name="showOldPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="showOldPwd = !showOldPwd"
/>
</template>
</VnInput>
<VnInput
ref="newPasswordRef"
v-model="formData.newPassword"
:type="!showNewPwd ? 'password' : 'text'"
:label="t('newPassword')"
><template #append>
<QIcon
:name="showNewPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="showNewPwd = !showNewPwd"
/>
</template></VnInput>
>
<template #append>
<QIcon
:name="showNewPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="showNewPwd = !showNewPwd"
/>
</template>
</VnInput>
<VnInput
v-model="repeatPassword"
:type="!showCopyPwd ? 'password' : 'text'"
:label="t('repeatPassword')"
><template #append>
<QIcon
:name="showCopyPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="showCopyPwd = !showCopyPwd"
/>
</template></VnInput>
>
<template #append>
<QIcon
:name="showCopyPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="showCopyPwd = !showCopyPwd"
/>
</template>
</VnInput>
</template>
<template #actions>
<QBtn
@ -153,7 +157,13 @@ onMounted(async () => {
flat
@click="passwordRequirementsDialogRef.show()"
/>
<QBtn :label="t('modify')" type="submit" rounded no-caps flat />
<QBtn
:label="t('modify')"
type="submit"
rounded
no-caps
flat
/>
</template>
</VnForm>
<QDialog ref="passwordRequirementsDialogRef">
@ -161,7 +171,10 @@ onMounted(async () => {
<span class="text-h6 text-bold q-mb-md">
{{ t('passwordRequirements') }}
</span>
<div class="column" style="max-width: max-content">
<div
class="column"
style="max-width: max-content"
>
<span>
{{
t('charactersLong', {

View File

@ -73,8 +73,8 @@ async function confirm() {
/>
</QCardSection>
<QCardSection class="row items-center">
<span v-html="message"></span>
<slot name="customHTML"></slot>
<span v-html="message" />
<slot name="customHTML" />
</QCardSection>
<QCardActions align="right">
<QBtn

View File

@ -43,7 +43,10 @@ const url = computed(() => {
@click="show = !show"
spinner-color="primary"
/>
<QDialog v-model="show" v-if="props.zoomSize">
<QDialog
v-model="show"
v-if="props.zoomSize"
>
<QImg
:src="url"
size="full"

View File

@ -27,8 +27,14 @@ const props = defineProps({
:rows-per-page-options="props.rowsPerPageOptions"
table-header-class="vntable-header-default"
>
<template v-for="(_, slotName) in $slots" v-slot:[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps" />
<template
v-for="(_, slotName) in $slots"
#[slotName]="slotProps"
>
<slot
:name="slotName"
v-bind="slotProps"
/>
</template>
</QTable>
</template>

View File

@ -42,3 +42,12 @@ a.link {
.no-padding {
padding: 0 !important;
}
input[type='number'] {
-moz-appearance: textfield;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
}

View File

@ -1,70 +1,75 @@
import { VnObject } from './object'
import { JsonException } from './json-exception'
import { VnObject } from './object';
import { JsonException } from './json-exception';
/**
* Handler for JSON rest connections.
*/
export class JsonConnection extends VnObject {
_connected = false
_requestsCount = 0
token = null
interceptors = []
_connected = false;
_requestsCount = 0;
token = null;
interceptors = [];
errorInterceptor = null;
use (fn) {
this.interceptors.push(fn)
use(fn) {
this.interceptors.push(fn);
}
useErrorInterceptor(fn) {
this.errorInterceptor = fn;
}
/**
* Executes the specified REST service with the given params and calls
* the callback when response is received.
*
* @param {String} url The service path
* @param {Object} params The params to pass to the service
* @return {Object} The parsed JSON response
*/
async send (url, params) {
if (!params) params = {}
params.srv = `json:${url}`
return this.sendWithUrl('POST', '.', params)
* Executes the specified REST service with the given params and calls
* the callback when response is received.
*
* @param {String} url The service path
* @param {Object} params The params to pass to the service
* @return {Object} The parsed JSON response
*/
async send(url, params) {
if (!params) params = {};
params.srv = `json:${url}`;
return this.sendWithUrl('POST', '.', params);
}
async sendForm (form) {
const params = {}
const elements = form.elements
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
params[elements[i].name] = elements[i].value;
}
}
return this.sendWithUrl('POST', form.action, params)
return this.sendWithUrl('POST', form.action, params);
}
async sendFormMultipart (form) {
async sendFormMultipart(form) {
return this.request({
method: 'POST',
url: form.action,
data: new FormData(form)
})
});
}
async sendFormData (formData) {
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()
* 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])
urlParams.set(key, params[key]);
}
}
@ -75,126 +80,129 @@ export class JsonConnection extends VnObject {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
});
}
async request (config) {
const request = new XMLHttpRequest()
request.open(config.method, config.url, true)
async request(config) {
const request = new XMLHttpRequest();
request.open(config.method, config.url, true);
for (const fn of this.interceptors) {
config = fn(config)
config = fn(config);
}
const headers = config.headers
const headers = config.headers;
if (headers) {
for (const header in headers) {
request.setRequestHeader(header, headers[header])
request.setRequestHeader(header, headers[header]);
}
}
const promise = new Promise((resolve, reject) => {
request.onreadystatechange = () =>
this._onStateChange(request, resolve, reject)
})
this._onStateChange(request, resolve, reject);
});
request.send(config.data)
request.send(config.data);
this._requestsCount++
this._requestsCount++;
if (this._requestsCount === 1) {
this.emit('loading-changed', true)
this.emit('loading-changed', true);
}
return promise
return promise;
}
_onStateChange (request, resolve, reject) {
_onStateChange(request, resolve, reject) {
if (request.readyState !== 4) {
return
return;
}
this._requestsCount--
this._requestsCount--;
if (this._requestsCount === 0) {
this.emit('loading-changed', false)
this.emit('loading-changed', false);
}
let data = null
let error = null
let data = null;
let error = null;
try {
if (request.status === 0) {
const err = new JsonException()
const err = new JsonException();
err.message =
'The server does not respond, please check your Internet connection'
err.statusCode = request.status
throw err
'The server does not respond, please check your Internet connection';
err.statusCode = request.status;
throw err;
}
let contentType = null
let contentType = null;
try {
contentType = request
.getResponseHeader('Content-Type')
.split(';')[0]
.trim()
.trim();
} catch (err) {
console.warn(err)
console.warn(err);
}
if (contentType !== 'application/json') {
const err = new JsonException()
err.message = request.statusText
err.statusCode = request.status
throw err
const err = new JsonException();
err.message = request.statusText;
err.statusCode = request.status;
throw err;
}
let json
let jsData
let json;
let jsData;
if (request.responseText) {
json = JSON.parse(request.responseText)
json = JSON.parse(request.responseText);
}
if (json) {
jsData = json.data || json
jsData = json.data || json;
}
if (request.status >= 200 && request.status < 300) {
data = jsData
data = jsData;
} else {
let exception = jsData.exception
let exception = jsData.exception;
const err = new JsonException()
err.statusCode = request.status
const err = new JsonException();
err.statusCode = request.status;
if (exception) {
exception = exception
.replace(/\\/g, '.')
.replace(/Exception$/, '')
.replace(/^Vn\.Web\./, '')
.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
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
err.message = request.statusText;
}
throw err
throw err;
}
} catch (e) {
data = null
error = e
data = null;
error = e;
}
if (error) {
this.emit('error', error)
reject(error)
if (this.errorInterceptor) {
this.errorInterceptor(error);
}
this.emit('error', error);
reject(error);
} else {
resolve(data)
resolve(data);
}
}
}

View File

@ -12,11 +12,17 @@
/>
<QToolbarTitle>
{{ $app.title }}
<div v-if="$app.subtitle" class="subtitle text-caption">
<div
v-if="$app.subtitle"
class="subtitle text-caption"
>
{{ $app.subtitle }}
</div>
</QToolbarTitle>
<div id="actions" ref="actions"></div>
<div
id="actions"
ref="actions"
/>
<QBtn
v-if="$app.useRightDrawer"
@click="$app.rightDrawerOpen = !$app.rightDrawerOpen"
@ -29,22 +35,44 @@
</QBtn>
</QToolbar>
</QHeader>
<QDrawer v-model="leftDrawerOpen" :width="250" show-if-above>
<QDrawer
v-model="leftDrawerOpen"
:width="250"
show-if-above
>
<QToolbar class="logo">
<img src="statics/logo-dark.svg" />
<img src="statics/logo-dark.svg">
</QToolbar>
<div class="user-info">
<div>
<span id="user-name">{{ user.nickname }}</span>
<QBtn flat icon="logout" alt="_Exit" @click="logout()" />
<QBtn
flat
icon="logout"
alt="_Exit"
@click="logout()"
/>
</div>
<div id="supplant" class="supplant">
<div
id="supplant"
class="supplant"
>
<span id="supplanted">{{ supplantedUser }}</span>
<QBtn flat icon="logout" alt="_Exit" />
<QBtn
flat
icon="logout"
alt="_Exit"
/>
</div>
</div>
<QList v-for="item in essentialLinks" :key="item.id">
<QItem v-if="!item.childs" :to="`/${item.path}`">
<QList
v-for="item in essentialLinks"
:key="item.id"
>
<QItem
v-if="!item.childs"
:to="`/${item.path}`"
>
<QItemSection>
<QItemLabel>{{ item.description }}</QItemLabel>
</QItemSection>

View File

@ -13,6 +13,7 @@ const { t } = useI18n();
const jApi = inject('jApi');
const vnFormRef = ref(null);
const vnFormRef2 = ref(null);
const changePasswordFormDialog = ref(null);
const showChangePasswordForm = ref(false);
const langOptions = ref([]);
@ -63,11 +64,11 @@ onMounted(() => fetchLanguagesSql());
<VnForm
ref="vnFormRef"
:title="t('personalInformation')"
:fetchFormDataSql="fetchConfigDataSql"
:fetch-form-data-sql="fetchConfigDataSql"
:pks="pks"
table="myUser"
schema="account"
:defaultActions="false"
:default-actions="false"
>
<template #form="{ data }">
<VnInput
@ -94,9 +95,29 @@ onMounted(() => fetchLanguagesSql());
option-label="name"
option-value="code"
:options="langOptions"
@update:modelValue="vnFormRef.submit()"
@update:model-value="vnFormRef.submit()"
/>
</template>
<template #extraForm>
<VnForm
class="no-form-container"
ref="vnFormRef2"
:pks="pks"
table="myClient"
schema="hedera"
:fetch-form-data-sql="fetchConfigDataSql"
:default-actions="false"
>
<template #form="{ data }">
<QCheckbox
v-model="data.isToBeMailed"
:label="t('isToBeMailed')"
@update:model-value="vnFormRef2.submit()"
dense
/>
</template>
</VnForm>
</template>
</VnForm>
</QPage>
<QDialog
@ -113,6 +134,7 @@ onMounted(() => fetchLanguagesSql());
<i18n lang="yaml">
en-US:
personalInformation: Personal Information
isToBeMailed: Receive invoices by email
name: Name
email: Email
nickname: Display name
@ -122,6 +144,7 @@ en-US:
changePassword: Change password
es-ES:
personalInformation: Datos personales
isToBeMailed: Recibir facturas por correo electrónico
name: Nombre
email: Correo electrónico
nickname: Nombre a mostrar
@ -131,6 +154,7 @@ es-ES:
changePassword: Cambiar contraseña
ca-ES:
personalInformation: Dades personals
isToBeMailed: Rebre factures per correu electrònic
name: Nom
email: Correu electrònic
nickname: Nom a mostrar
@ -140,6 +164,7 @@ ca-ES:
changePassword: Canviar contrasenya
fr-FR:
personalInformation: Informations personnelles
isToBeMailed: Recevoir des factures par e-mail
name: Nom
email: E-mail
nickname: Nom à afficher
@ -149,6 +174,7 @@ fr-FR:
changePassword: Changer le mot de passe
pt-PT:
personalInformation: Dados pessoais
isToBeMailed: Receber facturas por e-mail
name: Nome
email: E-mail
nickname: Nom à afficher

View File

@ -67,29 +67,41 @@ onMounted(() => getCountries());
</Teleport>
<VnForm
ref="vnFormRef"
:fetchFormDataSql="fetchAddressDataSql"
:columnsToIgnoreUpdate="['countryFk']"
:createModelDefault="{
:fetch-form-data-sql="fetchAddressDataSql"
:columns-to-ignore-update="['countryFk']"
:create-model-default="{
field: 'clientFk',
value: 'account.myUser_getId()'
}"
:pks="pks"
:isEditMode="isEditMode"
:title="t('addEditAddress')"
:is-edit-mode="isEditMode"
:title="t(isEditMode ? 'editAddress' : 'addAddress')"
table="myAddress"
schema="hedera"
@onDataSaved="goBack()"
@on-data-saved="goBack()"
>
<template #form="{ data }">
<VnInput v-model="data.nickname" :label="t('name')" />
<VnInput v-model="data.street" :label="t('address')" />
<VnInput v-model="data.city" :label="t('city')" />
<VnInput v-model="data.postalCode" :label="t('postalCode')" />
<VnInput
v-model="data.nickname"
:label="t('name')"
/>
<VnInput
v-model="data.street"
:label="t('address')"
/>
<VnInput
v-model="data.city"
:label="t('city')"
/>
<VnInput
v-model="data.postalCode"
:label="t('postalCode')"
/>
<VnSelect
v-model="data.countryFk"
:label="t('country')"
:options="countriesOptions"
@update:modelValue="data.provinceFk = null"
@update:model-value="data.provinceFk = null"
/>
<VnSelect
v-model="data.provinceFk"
@ -114,7 +126,6 @@ onMounted(() => getCountries());
en-US:
back: Back
accept: Accept
addEditAddress: Add or edit address
name: Consignee
address: Address
city: City
@ -122,21 +133,23 @@ en-US:
country: Country
province: Province
addressChangedSuccessfully: Address changed successfully
addAddress: Add address
editAddress: Edit address
es-ES:
back: Volver
accept: Aceptar
addEditAddress: Añadir o modificar dirección
name: Consignatario
address: Morada
address: Dirección
city: Ciudad
postalCode: Código postal
country: País
province: Distrito
province: Provincia
addressChangedSuccessfully: Dirección modificada correctamente
addAddress: Añadir dirección
editAddress: Modificar dirección
ca-ES:
back: Tornar
accept: Acceptar
addEditAddress: Afegir o modificar adreça
name: Consignatari
address: Direcció
city: Ciutat
@ -144,10 +157,11 @@ ca-ES:
country: País
province: Província
addressChangedSuccessfully: Adreça modificada correctament
addAddress: Afegir adreça
editAddress: Modificar adreça
fr-FR:
back: Retour
accept: Accepter
addEditAddress: Ajouter ou modifier l'adresse
name: Destinataire
address: Numéro Rue
city: Ville
@ -155,10 +169,11 @@ fr-FR:
country: Pays
province: Province
addressChangedSuccessfully: Adresse modifié avec succès
addAddress: Ajouter adresse
editAddress: Modifier adresse
pt-PT:
back: Voltar
accept: Aceitar
addEditAddress: Adicionar ou modificar morada
name: Consignatario
address: Morada
city: Concelho
@ -166,4 +181,6 @@ pt-PT:
country: País
province: Distrito
addressChangedSuccessfully: Morada modificada corretamente
addAddress: Adicionar morada
editAddress: Modificar morada
</i18n>

View File

@ -96,7 +96,10 @@ onMounted(async () => {
/>
</Teleport>
<QPage class="vn-w-sm">
<QList class="rounded-borders shadow-1 shadow-transition" separator>
<QList
class="rounded-borders shadow-1 shadow-transition"
separator
>
<CardList
v-for="(address, index) in addresses"
:key="index"
@ -133,13 +136,21 @@ onMounted(async () => {
() => removeAddress(address.id)
)
"
/>
>
<QTooltip>
{{ t('deleteAddress') }}
</QTooltip>
</QBtn>
<QBtn
icon="edit"
flat
rounded
@click.stop="goToAddressDetails(address.id)"
/>
>
<QTooltip>
{{ t('editAddress') }}
</QTooltip>
</QBtn>
</template>
</CardList>
</QList>
@ -151,20 +162,30 @@ en-US:
addAddress: Add address
defaultAddressModified: Default address modified
confirmDeleteAddress: Are you sure you want to delete the address?
editAddress: Edit address
deleteAddress: Delete address
es-ES:
addAddress: Añadir dirección
defaultAddressModified: Dirección por defecto modificada
confirmDeleteAddress: ¿Estás seguro de que quieres borrar la dirección?
editAddress: Modificar dirección
deleteAddress: Borrar dirección
ca-ES:
addAddress: Afegir adreça
defaultAddressModified: Adreça per defecte modificada
confirmDeleteAddress: Estàs segur que vols esborrar l'adreça?
editAddress: Modificar adreça
deleteAddress: Esborrar adreça
fr-FR:
addAddress: Ajouter une adresse
defaultAddressModified: Adresse par défaut modifiée
confirmDeleteAddress: Êtes-vous sûr de vouloir supprimer l'adresse?
editAddress: Modifier adresse
deleteAddress: Supprimer adresse
pt-PT:
addAddress: Adicionar Morada
defaultAddressModified: Endereço padrão modificado
confirmDeleteAddress: Tem a certeza de que deseja excluir o endereço?
editAddress: Modificar morada
deleteAddress: Eliminar morada
</i18n>

View File

@ -20,15 +20,27 @@ onMounted(async () => await fetchData());
<template>
<div style="padding: 0">
<div class="q-pa-sm row items-start">
<div class="new-card q-pa-sm" v-for="myNew in news" :key="myNew.id">
<div
class="new-card q-pa-sm"
v-for="myNew in news"
:key="myNew.id"
>
<QCard>
<VnImg :id="myNew.image" storage="news" />
<VnImg
:id="myNew.image"
storage="news"
/>
<QCardSection>
<div class="text-h5">{{ myNew.title }}</div>
<div class="text-h5">
{{ myNew.title }}
</div>
</QCardSection>
<QCardSection class="new-body">
<div v-html="myNew.text" class="card-text" />
<div
v-html="myNew.text"
class="card-text"
/>
</QCardSection>
</QCard>
</div>
@ -43,7 +55,10 @@ onMounted(async () => await fetchData());
/>
</QPageSticky>
</div>
<QDialog v-model="showPreview" @hide="selectedImageSrc = ''">
<QDialog
v-model="showPreview"
@hide="selectedImageSrc = ''"
>
<QImg :src="selectedImageSrc" />
</QDialog>
</template>

View File

@ -10,8 +10,11 @@
dense
standout
>
<template v-slot:prepend>
<QIcon v-if="search === ''" name="search" />
<template #prepend>
<QIcon
v-if="search === ''"
name="search"
/>
<QIcon
v-else
name="clear"
@ -29,7 +32,11 @@
/>
</Teleport>
<div style="padding-bottom: 5em">
<QDrawer v-model="$app.rightDrawerOpen" side="right" :width="250">
<QDrawer
v-model="$app.rightDrawerOpen"
side="right"
:width="250"
>
<div class="q-pa-md">
<div class="basket-info">
<p>{{ date(new Date()) }}</p>
@ -37,7 +44,11 @@
{{ $t('warehouse') }}
{{ 'Algemesi' }}
</p>
<QBtn flat rounded no-caps>
<QBtn
flat
rounded
no-caps
>
{{ $t('modify') }}
</QBtn>
</div>
@ -66,11 +77,14 @@
:title="cat.name"
:to="{ params: { category: cat.id, type: null } }"
>
<img :src="`statics/category/${cat.code}.svg`" />
<img :src="`statics/category/${cat.code}.svg`">
</QBtn>
</div>
</div>
<div class="q-mt-md" v-if="category || search">
<div
class="q-mt-md"
v-if="category || search"
>
<div class="q-mb-xs text-grey-7">
{{ $t('filterBy') }}
</div>
@ -95,8 +109,15 @@
/>
</div>
</div>
<div class="q-pa-md" v-if="typeId || search">
<div class="q-mb-md" v-for="tag in tags" :key="tag.uid">
<div
class="q-pa-md"
v-if="typeId || search"
>
<div
class="q-mb-md"
v-for="tag in tags"
:key="tag.uid"
>
<div class="q-mb-xs text-caption text-grey-7">
{{ tag.name }}
<QIcon
@ -166,8 +187,11 @@
:disable="disableScroll"
>
<div class="q-pa-md row justify-center q-gutter-md">
<QSpinner v-if="isLoading" color="primary" size="50px">
</QSpinner>
<QSpinner
v-if="isLoading"
color="primary"
size="50px"
/>
<div
v-if="items && !items.length"
class="text-subtitle1 text-grey-7 q-pa-md"
@ -180,19 +204,26 @@
>
{{ $t('pleaseSetFilter') }}
</div>
<QCard class="my-card" v-for="item in items" :key="item.id">
<img :src="`${$imageBase}/catalog/200x200/${item.image}`" />
<QCard
class="my-card"
v-for="_item in items"
:key="_item.id"
>
<img :src="`${$imageBase}/catalog/200x200/${_item.image}`">
<QCardSection>
<div class="name text-subtitle1">
{{ item.longName }}
{{ _item.longName }}
</div>
<div
class="sub-name text-uppercase text-subtitle1 text-grey-7 ellipsize q-pt-xs"
>
{{ item.subName }}
{{ _item.subName }}
</div>
<div class="tags q-pt-xs">
<div v-for="tag in item.tags" :key="tag.tagFk">
<div
v-for="tag in _item.tags"
:key="tag.tagFk"
>
<span class="text-grey-7">{{
tag.tag.name
}}</span>
@ -203,26 +234,29 @@
<QCardActions class="actions justify-between">
<div class="q-pl-sm">
<span class="available bg-green text-white">{{
item.available
_item.available
}}</span>
{{ $t('from') }}
<span class="price">{{
currency(item.buy?.price3)
currency(_item.buy?.price3)
}}</span>
</div>
<QBtn
icon="add_shopping_cart"
:title="$t('buy')"
@click="showItem(item)"
@click="showItem(_item)"
flat
>
</QBtn>
/>
</QCardActions>
</QCard>
</div>
<template v-slot:loading>
<template #loading>
<div class="row justify-center q-my-md">
<QSpinner color="primary" name="dots" size="40px" />
<QSpinner
color="primary"
name="dots"
size="40px"
/>
</div>
</template>
</QInfiniteScroll>
@ -244,19 +278,30 @@
>
{{ item.subName }}
</div>
<div class="text-grey-7">#{{ item.id }}</div>
<div class="text-grey-7">
#{{ item.id }}
</div>
</QCardSection>
<QCardSection>
<div v-for="tag in item.tags" :key="tag.tagFk">
<div
v-for="tag in item.tags"
:key="tag.tagFk"
>
<span class="text-grey-7">{{ tag.tag.name }}</span>
{{ tag.value }}
</div>
</QCardSection>
<QCardActions align="right">
<QBtn @click="showItemDialog = false" flat>
<QBtn
@click="showItemDialog = false"
flat
>
{{ $t('cancel') }}
</QBtn>
<QBtn @click="showItemDialog = false" flat>
<QBtn
@click="showItemDialog = false"
flat
>
{{ $t('accept') }}
</QBtn>
</QCardActions>

View File

@ -17,18 +17,26 @@ const years = ref([]);
const invoices = ref([]);
const columns = computed(() => [
{ name: 'ref', label: t('invoice'), field: 'ref', align: 'left' },
{
name: 'ref',
label: t('invoice'),
field: 'ref',
align: 'left',
sortable: true
},
{
name: 'issued',
label: t('issued'),
field: 'issued',
align: 'left',
sortable: true,
format: val => formatDate(val, 'D MMM YYYY')
},
{
name: 'amount',
label: t('amount'),
field: 'amount',
sortable: true,
format: val => currency(val)
},
{

View File

@ -7,12 +7,14 @@ import CardList from 'src/components/ui/CardList.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue';
import useNotify from 'src/composables/useNotify.js';
import { currency, formatDateTitle } from 'src/lib/filters.js';
import { tpvStore } from 'stores/tpv';
const { t } = useI18n();
const route = useRoute();
const jApi = inject('jApi');
const { notify } = useNotify();
const showAmountToPayDialog = ref(null);
const amountToPay = ref(null);
@ -36,6 +38,10 @@ const onPayClick = async () => {
};
const onConfirmPay = async () => {
if (amountToPay.value <= 0) {
notify(t('amountError'), 'negative');
return;
}
if (amountToPay.value) {
const amount = amountToPay.value.toString().replace('.', ',');
amountToPay.value = parseFloat(amount);
@ -48,10 +54,17 @@ const onConfirmPay = async () => {
<Teleport :to="$actions">
<div class="balance">
<span class="label">{{ t('balance') }}</span>
<span class="amount" :class="{ negative: debt < 0 }">
<span
class="amount"
:class="{ negative: debt < 0 }"
>
{{ currency(debt || 0) }}
</span>
<QIcon name="info" class="info" size="sm">
<QIcon
name="info"
class="info"
size="sm"
>
<QTooltip max-width="450px">
{{ t('paymentInfo') }}
</QTooltip>
@ -126,6 +139,9 @@ const onConfirmPay = async () => {
v-model="amountToPay"
:clearable="false"
class="full-width"
type="number"
min="0"
:max="debt * -1"
/>
</template>
</VnConfirm>
@ -170,6 +186,7 @@ en-US:
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.
amountToPay: 'Amount to pay (€):'
amountError: The amount must be a positive number less than or equal to the outstanding amount
es-ES:
startOrder: Empezar pedido
noOrdersFound: No se encontrado pedidos
@ -183,6 +200,7 @@ es-ES:
cuenta, pulsa el botón de pago, borra la cantidad sugerida e introduce la
cantidad que desees.
amountToPay: 'Cantidad a pagar (€):'
amountError: La cantidad debe ser un número positivo e inferior o igual al importe pendiente
ca-ES:
startOrder: Començar encàrrec
noOrdersFound: No s'han trobat comandes
@ -196,6 +214,7 @@ ca-ES:
lliurament a compte, prem el botó de pagament, esborra la quantitat suggerida
e introdueix la quantitat que vulguis.
amountToPay: 'Quantitat a pagar (€):'
amountError: La quantitat ha de ser un nombre positiu i inferior o igual a l'import pendent
fr-FR:
startOrder: Acheter
noOrdersFound: Aucune commande trouvée
@ -209,6 +228,7 @@ fr-FR:
voulez faire un versement, le montant suggéré effacé et entrez le montant que
vous souhaitez.
amountToPay: 'Montant à payer (€):'
amountError: La quantité doit être un neméro positif et inférieur ou égal à la somme restant à payer
pt-PT:
startOrder: Iniciar encomenda
noOrdersFound: Nenhum pedido encontrado
@ -222,4 +242,5 @@ pt-PT:
conta, clique no botão de pagamento, apague a quantidade sugerida e introduza
a quantidade que deseje.
amountToPay: 'Valor a pagar (€):'
amountError: A quantidade deve ser um número positivo e inferior ou igual ao importe pendiente
</i18n>

View File

@ -82,9 +82,11 @@ onMounted(async () => {
:to="{ name: 'basket', params: { id: order.id } }"
>
<template #content>
<QItemLabel class="text-bold q-mb-sm">{{
formatDateTitle(order.sent)
}}</QItemLabel>
<QItemLabel class="text-bold q-mb-sm">
{{
formatDateTitle(order.sent)
}}
</QItemLabel>
<QItemLabel> #{{ order.id }} </QItemLabel>
<QItemLabel>{{ order.nickname }}</QItemLabel>
<QItemLabel>{{ order.agency }}</QItemLabel>

View File

@ -25,9 +25,14 @@ const lineSubtotal = line =>
</script>
<template>
<QCard class="vn-w-sm" style="padding: 32px">
<QCard
class="vn-w-sm"
style="padding: 32px"
>
<QCardSection class="no-padding q-mb-md">
<div class="text-h6">#{{ ticket.id }}</div>
<div class="text-h6">
#{{ ticket.id }}
</div>
</QCardSection>
<QCardSection class="no-padding q-mb-md q-gutter-y-xs">
<div class="text-subtitle1 text-bold">
@ -69,7 +74,10 @@ const lineSubtotal = line =>
</span>
</QCardSection>
<QSeparator inset />
<QList v-for="row in rows" :key="row.itemFk">
<QList
v-for="row in rows"
:key="row.itemFk"
>
<QItem>
<QItemSection avatar>
<VnImg
@ -83,16 +91,25 @@ const lineSubtotal = line =>
<QItemLabel lines="1">
{{ row.concept }}
</QItemLabel>
<QItemLabel lines="1" caption>
<QItemLabel
lines="1"
caption
>
{{ row.value5 }} {{ row.value6 }} {{ row.value7 }}
</QItemLabel>
<QItemLabel lines="1">
{{ row.quantity }} x {{ currency(row.price) }}
</QItemLabel>
</QItemSection>
<QItemSection side class="total">
<QItemSection
side
class="total"
>
<QItemLabel>
<span class="discount" v-if="row.discount">
<span
class="discount"
v-if="row.discount"
>
{{ currency(lineDiscountSubtotal(row)) }} -
{{ currency(row.discount) }} =
</span>

View File

@ -3,7 +3,7 @@ import { onMounted, inject, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import TicketDetails from 'src/components/ui/TicketDetails.vue';
import TicketDetails from 'src/pages/Ecomerce/TicketDetails.vue';
import { userStore as useUserStore } from 'stores/user';
@ -56,7 +56,10 @@ const onPrintClick = () => {
/>
</Teleport>
<QPage>
<TicketDetails :rows="rows" :ticket="ticket" />
<TicketDetails
:rows="rows"
:ticket="ticket"
/>
</QPage>
</template>

View File

@ -3,9 +3,14 @@
class="fullscreen bg-accent text-white text-center q-pa-md flex flex-center"
>
<div>
<div style="font-size: 30vh">404</div>
<div style="font-size: 30vh">
404
</div>
<div class="text-h2" style="opacity: 0.4">
<div
class="text-h2"
style="opacity: 0.4"
>
Oops. Nothing here...
</div>

View File

@ -37,19 +37,33 @@ async function onLogin() {
<template>
<div class="main">
<div class="header">
<router-link to="/" class="block">
<img src="statics/logo.svg" alt="Verdnatura" class="block" />
<router-link
to="/"
class="block"
>
<img
src="statics/logo.svg"
alt="Verdnatura"
class="block"
>
</router-link>
</div>
<QForm @submit="onLogin" class="q-gutter-y-md">
<QForm
@submit="onLogin"
class="q-gutter-y-md"
>
<div class="q-gutter-y-sm">
<QInput v-model="email" :label="$t('user')" autofocus />
<QInput
v-model="email"
:label="$t('user')"
autofocus
/>
<QInput
v-model="password"
:label="$t('password')"
:type="!showPwd ? 'password' : 'text'"
>
<template v-slot:append>
<template #append>
<QIcon
:name="showPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@ -57,31 +71,38 @@ async function onLogin() {
/>
</template>
</QInput>
<div class=" text-center"> <QCheckbox
v-model="remember"
:label="$t('remindMe')"
dense
/> <QBtn
id="switchLanguage"
:label="$t('language')"
icon="translate"
color="primary"
size="sm"
flat
rounded
>
<QMenu auto-close>
<QList dense v-for="lang in langs" :key="lang">
<QItem
disabled
v-ripple
clickable
<div class="text-center">
<QCheckbox
v-model="remember"
:label="$t('remindMe')"
dense
/>
<QBtn
id="switchLanguage"
:label="$t('language')"
icon="translate"
color="primary"
size="sm"
flat
rounded
>
<QMenu auto-close>
<QList
dense
v-for="lang in langs"
:key="lang"
>
{{ $t(`langs.${lang}`) }}
</QItem>
</QList>
</QMenu>
</QBtn></div>
<QItem
disabled
v-ripple
clickable
>
{{ $t(`langs.${lang}`) }}
</QItem>
</QList>
</QMenu>
</QBtn>
</div>
</div>
<div class="justify-center">
<QBtn
@ -106,7 +127,10 @@ async function onLogin() {
/>
</div>
<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') }}
</router-link>
</p>

View File

@ -8,7 +8,10 @@
/>
</div>
<div>
<QForm @submit="onSend" class="q-gutter-y-md text-grey-8">
<QForm
@submit="onSend"
class="q-gutter-y-md text-grey-8"
>
<div class="text-h5">
<div>
{{ $t('dontWorry') }}
@ -37,7 +40,10 @@
unelevated
/>
<div class="text-center q-mt-md">
<router-link to="/login" class="link">
<router-link
to="/login"
class="link"
>
{{ $t('return') }}
</router-link>
</div>

View File

@ -8,7 +8,11 @@
/>
</QCard-section>
<QCard-section>
<QForm @submit="onRegister" ref="form" class="q-gutter-y-md">
<QForm
@submit="onRegister"
ref="form"
class="q-gutter-y-md"
>
<div class="text-grey-8 text-h5 text-center">
{{ $t('fillData') }}
</div>
@ -21,7 +25,7 @@
hint=""
filled
>
<template v-slot:append>
<template #append>
<QIcon
:name="
showPwd ? 'visibility_off' : 'visibility'
@ -42,7 +46,7 @@
hint=""
filled
>
<template v-slot:append>
<template #append>
<QIcon
:name="
showRpPwd ? 'visibility_off' : 'visibility'
@ -61,7 +65,10 @@
color="primary"
/>
<div class="text-center q-mt-xs">
<router-link to="/login" class="link">
<router-link
to="/login"
class="link"
>
{{ $t('return') }}
</router-link>
</div>