Merge branch 'dev' into 7220_cypressUnitTest
This commit is contained in:
commit
da39100865
|
@ -5,12 +5,17 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2420.01]
|
||||
|
||||
## [2418.01]
|
||||
|
||||
## [2416.01] - 2024-04-18
|
||||
|
||||
### Added
|
||||
|
||||
- (Worker) => Se crea la sección Taquilla
|
||||
- (General) => Se mantiene el filtro lateral en cualquier parte de la seccíon.
|
||||
|
||||
### Fixed
|
||||
|
||||
- (General) => Se vuelven a mostrar los parámetros en la url al aplicar un filtro
|
||||
|
@ -22,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- (Tickets) => Se añade la opción de clonar ticket. #6951
|
||||
- (Parking) => Se añade la sección Parking. #5186
|
||||
|
||||
- (Rutas) => Se añade el campo "servida" a la tabla y se añade también a los filtros. #7130
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
|
|
|
@ -54,7 +54,6 @@ pipeline {
|
|||
}
|
||||
environment {
|
||||
PROJECT_NAME = 'lilium'
|
||||
STACK_NAME = "${env.PROJECT_NAME}-${env.BRANCH_NAME}"
|
||||
}
|
||||
stages {
|
||||
stage('Install') {
|
||||
|
@ -104,15 +103,18 @@ pipeline {
|
|||
when {
|
||||
expression { PROTECTED_BRANCH }
|
||||
}
|
||||
environment {
|
||||
DOCKER_HOST = "${env.SWARM_HOST}"
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
def packageJson = readJSON file: 'package.json'
|
||||
env.VERSION = packageJson.version
|
||||
}
|
||||
sh "docker stack deploy --with-registry-auth --compose-file docker-compose.yml ${env.STACK_NAME}"
|
||||
withKubeConfig([
|
||||
serverUrl: "$KUBERNETES_API",
|
||||
credentialsId: 'kubernetes',
|
||||
namespace: 'lilium'
|
||||
]) {
|
||||
sh 'kubectl set image deployment/lilium-$BRANCH_NAME lilium-$BRANCH_NAME=$REGISTRY/salix-frontend:$VERSION'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,7 @@
|
|||
version: '3.7'
|
||||
services:
|
||||
main:
|
||||
image: registry.verdnatura.es/salix-frontend:${BRANCH_NAME:?}
|
||||
image: registry.verdnatura.es/salix-frontend:${VERSION:?}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile
|
||||
ports:
|
||||
- 4000
|
||||
deploy:
|
||||
replicas: ${FRONT_REPLICAS:?}
|
||||
placement:
|
||||
constraints:
|
||||
- node.role == worker
|
||||
resources:
|
||||
limits:
|
||||
memory: 1G
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "salix-front",
|
||||
"version": "24.18.0",
|
||||
"version": "24.24.1",
|
||||
"description": "Salix frontend",
|
||||
"productName": "Salix",
|
||||
"author": "Verdnatura",
|
||||
|
@ -32,6 +32,7 @@
|
|||
"@intlify/unplugin-vue-i18n": "^0.8.1",
|
||||
"@pinia/testing": "^0.1.2",
|
||||
"@quasar/app-vite": "^1.7.3",
|
||||
"@quasar/quasar-app-extension-qcalendar": "4.0.0-beta.15",
|
||||
"@quasar/quasar-app-extension-testing-unit-vitest": "^0.4.0",
|
||||
"@vue/test-utils": "^2.4.4",
|
||||
"autoprefixer": "^10.4.14",
|
||||
|
|
14476
pnpm-lock.yaml
14476
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -29,7 +29,7 @@ module.exports = configure(function (/* ctx */) {
|
|||
// app boot file (/src/boot)
|
||||
// --> boot files are part of "main.js"
|
||||
// https://v2.quasar.dev/quasar-cli/boot-files
|
||||
boot: ['i18n', 'axios', 'vnDate', 'validations', 'quasar.defaults'],
|
||||
boot: ['i18n', 'axios', 'vnDate', 'validations', 'quasar', 'quasar.defaults'],
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
||||
css: ['app.scss'],
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"@quasar/testing-unit-vitest": {
|
||||
"options": [
|
||||
"scripts"
|
||||
]
|
||||
}
|
||||
}
|
||||
"@quasar/testing-unit-vitest": {
|
||||
"options": ["scripts"]
|
||||
},
|
||||
"@quasar/qcalendar": {}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { boot } from 'quasar/wrappers';
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import messages from 'src/i18n';
|
||||
import { locales } from 'src/i18n/handle';
|
||||
|
||||
const i18n = createI18n({
|
||||
locale: navigator.language || navigator.userLanguage,
|
||||
|
@ -13,9 +12,8 @@ const i18n = createI18n({
|
|||
legacy: false,
|
||||
});
|
||||
|
||||
export default boot(async ({ app }) => {
|
||||
export default boot(({ app }) => {
|
||||
// Set i18n instance on app
|
||||
await locales();
|
||||
app.use(i18n);
|
||||
});
|
||||
|
||||
|
|
|
@ -3,17 +3,12 @@ import { reactive, ref, onMounted, nextTick } from 'vue';
|
|||
import { useI18n } from 'vue-i18n';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
|
||||
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import FormModelPopup from './FormModelPopup.vue';
|
||||
|
||||
const props = defineProps({
|
||||
showEntityField: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
defineProps({ showEntityField: { type: Boolean, default: true } });
|
||||
|
||||
const emit = defineEmits(['onDataSaved']);
|
||||
const { t } = useI18n();
|
||||
|
@ -26,7 +21,7 @@ const bankEntityFormData = reactive({
|
|||
});
|
||||
|
||||
const countriesFilter = {
|
||||
fields: ['id', 'country', 'code'],
|
||||
fields: ['id', 'name', 'code'],
|
||||
};
|
||||
|
||||
const countriesOptions = ref([]);
|
||||
|
@ -58,32 +53,28 @@ onMounted(async () => {
|
|||
>
|
||||
<template #form-inputs="{ data, validate }">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnInput
|
||||
:label="t('name')"
|
||||
v-model="data.name"
|
||||
:required="true"
|
||||
:rules="validate('bankEntity.name')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnInput
|
||||
ref="bicInputRef"
|
||||
:label="t('swift')"
|
||||
v-model="data.bic"
|
||||
:required="true"
|
||||
:rules="validate('bankEntity.bic')"
|
||||
/>
|
||||
</div>
|
||||
<VnInput
|
||||
:label="t('name')"
|
||||
v-model="data.name"
|
||||
:required="true"
|
||||
:rules="validate('bankEntity.name')"
|
||||
/>
|
||||
<VnInput
|
||||
ref="bicInputRef"
|
||||
:label="t('swift')"
|
||||
v-model="data.bic"
|
||||
:required="true"
|
||||
:rules="validate('bankEntity.bic')"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
:label="t('country')"
|
||||
v-model="data.countryFk"
|
||||
:options="countriesOptions"
|
||||
option-value="id"
|
||||
option-label="country"
|
||||
option-label="name"
|
||||
hide-selected
|
||||
:required="true"
|
||||
:rules="validate('bankEntity.countryFk')"
|
||||
|
|
|
@ -52,9 +52,7 @@ onMounted(() => {
|
|||
</span>
|
||||
<h1 class="title">{{ t('New department') }}</h1>
|
||||
<VnRow class="row q-gutter-md q-mb-md" style="min-width: 250px">
|
||||
<div class="col">
|
||||
<VnInput :label="t('Name')" v-model="data.name" />
|
||||
</div>
|
||||
<VnInput :label="t('Name')" v-model="data.name" />
|
||||
</VnRow>
|
||||
<div class="q-mt-lg row justify-end">
|
||||
<QBtn
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useRouter } from 'vue-router';
|
|||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import FormModelPopup from './FormModelPopup.vue';
|
||||
import VnInputDate from './common/VnInputDate.vue';
|
||||
|
@ -48,7 +48,11 @@ const onDataSaved = async (formData, requestResponse) => {
|
|||
/>
|
||||
<FetchData
|
||||
url="Tickets"
|
||||
:filter="{ fields: ['id', 'nickname'], order: 'shipped DESC', limit: 30 }"
|
||||
:filter="{
|
||||
fields: ['id', 'nickname'],
|
||||
where: { refFk: null },
|
||||
order: 'shipped DESC',
|
||||
}"
|
||||
@on-fetch="(data) => (ticketsOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
|
@ -72,69 +76,57 @@ const onDataSaved = async (formData, requestResponse) => {
|
|||
{{ t('Invoicing in progress...') }}
|
||||
</span>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Ticket')"
|
||||
:options="ticketsOptions"
|
||||
hide-selected
|
||||
option-label="id"
|
||||
option-value="id"
|
||||
v-model="data.ticketFk"
|
||||
@update:model-value="data.clientFk = null"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel> #{{ scope.opt?.id }} </QItemLabel>
|
||||
<QItemLabel caption>{{
|
||||
scope.opt?.nickname
|
||||
}}</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
</div>
|
||||
<VnSelect
|
||||
:label="t('Ticket')"
|
||||
:options="ticketsOptions"
|
||||
hide-selected
|
||||
option-label="id"
|
||||
option-value="id"
|
||||
v-model="data.ticketFk"
|
||||
@update:model-value="data.clientFk = null"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel> #{{ scope.opt?.id }} </QItemLabel>
|
||||
<QItemLabel caption>{{ scope.opt?.nickname }}</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
<span class="row items-center" style="max-width: max-content">{{
|
||||
t('Or')
|
||||
}}</span>
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Client')"
|
||||
:options="clientsOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="data.clientFk"
|
||||
@update:model-value="data.ticketFk = null"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnInputDate :label="t('Max date')" v-model="data.maxShipped" />
|
||||
</div>
|
||||
<VnSelect
|
||||
:label="t('Client')"
|
||||
:options="clientsOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="data.clientFk"
|
||||
@update:model-value="data.ticketFk = null"
|
||||
/>
|
||||
<VnInputDate :label="t('Max date')" v-model="data.maxShipped" />
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Serial')"
|
||||
:options="invoiceOutSerialsOptions"
|
||||
hide-selected
|
||||
option-label="description"
|
||||
option-value="code"
|
||||
v-model="data.serial"
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Area')"
|
||||
:options="taxAreasOptions"
|
||||
hide-selected
|
||||
option-label="code"
|
||||
option-value="code"
|
||||
v-model="data.taxArea"
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
<VnSelect
|
||||
:label="t('Serial')"
|
||||
:options="invoiceOutSerialsOptions"
|
||||
hide-selected
|
||||
option-label="description"
|
||||
option-value="code"
|
||||
v-model="data.serial"
|
||||
:required="true"
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('Area')"
|
||||
:options="taxAreasOptions"
|
||||
hide-selected
|
||||
option-label="code"
|
||||
option-value="code"
|
||||
v-model="data.taxArea"
|
||||
:required="true"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<VnInput
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
|
|||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import FormModelPopup from './FormModelPopup.vue';
|
||||
|
||||
|
@ -40,24 +40,20 @@ const onDataSaved = (dataSaved) => {
|
|||
>
|
||||
<template #form-inputs="{ data, validate }">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnInput
|
||||
:label="t('Name')"
|
||||
v-model="data.name"
|
||||
:rules="validate('city.name')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Province')"
|
||||
:options="provincesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="data.provinceFk"
|
||||
:rules="validate('city.provinceFk')"
|
||||
/>
|
||||
</div>
|
||||
<VnInput
|
||||
:label="t('Name')"
|
||||
v-model="data.name"
|
||||
:rules="validate('city.name')"
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('Province')"
|
||||
:options="provincesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="data.provinceFk"
|
||||
:rules="validate('city.provinceFk')"
|
||||
/>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormModelPopup>
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
|
|||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import CreateNewCityForm from './CreateNewCityForm.vue';
|
||||
import CreateNewProvinceForm from './CreateNewProvinceForm.vue';
|
||||
|
@ -30,21 +30,21 @@ const townsLocationOptions = ref([]);
|
|||
|
||||
const onDataSaved = (formData) => {
|
||||
const newPostcode = {
|
||||
...formData
|
||||
...formData,
|
||||
};
|
||||
const townObject = townsLocationOptions.value.find(
|
||||
({id}) => id === formData.townFk
|
||||
({ id }) => id === formData.townFk
|
||||
);
|
||||
newPostcode.town = townObject?.name;
|
||||
const provinceObject = provincesOptions.value.find(
|
||||
({id}) => id === formData.provinceFk
|
||||
({ id }) => id === formData.provinceFk
|
||||
);
|
||||
newPostcode.province = provinceObject?.name;
|
||||
const countryObject = countriesOptions.value.find(
|
||||
({id}) => id === formData.countryFk
|
||||
({ id }) => id === formData.countryFk
|
||||
);
|
||||
newPostcode.country = countryObject?.country;
|
||||
emit('onDataSaved', newPostcode);
|
||||
emit('onDataSaved', newPostcode);
|
||||
};
|
||||
|
||||
const onCityCreated = async ({ name, provinceFk }, formData) => {
|
||||
|
@ -92,63 +92,55 @@ const onProvinceCreated = async ({ name }, formData) => {
|
|||
>
|
||||
<template #form-inputs="{ data, validate }">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnInput
|
||||
:label="t('Postcode')"
|
||||
v-model="data.code"
|
||||
:rules="validate('postcode.code')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectDialog
|
||||
:label="t('City')"
|
||||
:options="townsLocationOptions"
|
||||
v-model="data.townFk"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
:rules="validate('postcode.city')"
|
||||
:roles-allowed-to-create="['deliveryAssistant']"
|
||||
>
|
||||
<template #form>
|
||||
<CreateNewCityForm
|
||||
@on-data-saved="onCityCreated($event, data)"
|
||||
/>
|
||||
</template>
|
||||
</VnSelectDialog>
|
||||
</div>
|
||||
<VnInput
|
||||
:label="t('Postcode')"
|
||||
v-model="data.code"
|
||||
:rules="validate('postcode.code')"
|
||||
/>
|
||||
<VnSelectDialog
|
||||
:label="t('City')"
|
||||
:options="townsLocationOptions"
|
||||
v-model="data.townFk"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
:rules="validate('postcode.city')"
|
||||
:roles-allowed-to-create="['deliveryAssistant']"
|
||||
>
|
||||
<template #form>
|
||||
<CreateNewCityForm @on-data-saved="onCityCreated($event, data)" />
|
||||
</template>
|
||||
</VnSelectDialog>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-xl">
|
||||
<div class="col">
|
||||
<VnSelectDialog
|
||||
:label="t('Province')"
|
||||
:options="provincesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="data.provinceFk"
|
||||
:rules="validate('postcode.provinceFk')"
|
||||
:roles-allowed-to-create="['deliveryAssistant']"
|
||||
>
|
||||
<template #form>
|
||||
<CreateNewProvinceForm
|
||||
@on-data-saved="onProvinceCreated($event, data)"
|
||||
/>
|
||||
</template>
|
||||
</VnSelectDialog>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Country')"
|
||||
:options="countriesOptions"
|
||||
hide-selected
|
||||
option-label="country"
|
||||
option-value="id"
|
||||
v-model="data.countryFk"
|
||||
:rules="validate('postcode.countryFk')"
|
||||
/>
|
||||
</div> </VnRow
|
||||
></template>
|
||||
<VnSelectDialog
|
||||
:label="t('Province')"
|
||||
:options="provincesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="data.provinceFk"
|
||||
:rules="validate('postcode.provinceFk')"
|
||||
:roles-allowed-to-create="['deliveryAssistant']"
|
||||
>
|
||||
<template #form>
|
||||
<CreateNewProvinceForm
|
||||
@on-data-saved="onProvinceCreated($event, data)"
|
||||
/>
|
||||
</template> </VnSelectDialog
|
||||
></VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-xl"
|
||||
><VnSelect
|
||||
:label="t('Country')"
|
||||
:options="countriesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="data.countryFk"
|
||||
:rules="validate('postcode.countryFk')"
|
||||
/>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormModelPopup>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
|
|||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import FormModelPopup from './FormModelPopup.vue';
|
||||
|
||||
|
@ -40,24 +40,20 @@ const onDataSaved = (dataSaved) => {
|
|||
>
|
||||
<template #form-inputs="{ data, validate }">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnInput
|
||||
:label="t('Name')"
|
||||
v-model="data.name"
|
||||
:rules="validate('province.name')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Autonomy')"
|
||||
:options="autonomiesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="data.autonomyFk"
|
||||
:rules="validate('province.autonomyFk')"
|
||||
/>
|
||||
</div>
|
||||
<VnInput
|
||||
:label="t('Name')"
|
||||
v-model="data.name"
|
||||
:rules="validate('province.name')"
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('Autonomy')"
|
||||
:options="autonomiesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="data.autonomyFk"
|
||||
:rules="validate('province.autonomyFk')"
|
||||
/>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormModelPopup>
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
|
|||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import FormModelPopup from './FormModelPopup.vue';
|
||||
|
||||
|
@ -54,51 +54,42 @@ const onDataSaved = (dataSaved) => {
|
|||
>
|
||||
<template #form-inputs="{ data, validate }">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnInput
|
||||
:label="t('Identifier')"
|
||||
v-model="data.thermographId"
|
||||
:required="true"
|
||||
:rules="validate('thermograph.id')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Model')"
|
||||
:options="thermographsModels"
|
||||
hide-selected
|
||||
option-label="value"
|
||||
option-value="value"
|
||||
v-model="data.model"
|
||||
:required="true"
|
||||
:rules="validate('thermograph.model')"
|
||||
/>
|
||||
</div>
|
||||
<VnInput
|
||||
:label="t('Identifier')"
|
||||
v-model="data.thermographId"
|
||||
:required="true"
|
||||
:rules="validate('thermograph.id')"
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('Model')"
|
||||
:options="thermographsModels"
|
||||
hide-selected
|
||||
option-label="value"
|
||||
option-value="value"
|
||||
v-model="data.model"
|
||||
:required="true"
|
||||
:rules="validate('thermograph.model')"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-xl">
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Warehouse')"
|
||||
:options="warehousesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="data.warehouseId"
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Temperature')"
|
||||
:options="temperaturesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="code"
|
||||
v-model="data.temperatureFk"
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
<VnSelect
|
||||
:label="t('Warehouse')"
|
||||
:options="warehousesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="data.warehouseId"
|
||||
:required="true"
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('Temperature')"
|
||||
:options="temperaturesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="code"
|
||||
v-model="data.temperatureFk"
|
||||
:required="true"
|
||||
/>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormModelPopup>
|
||||
|
|
|
@ -24,6 +24,10 @@ const $props = defineProps({
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
saveUrl: {
|
||||
type: String,
|
||||
default: null,
|
||||
|
@ -76,6 +80,8 @@ defineExpose({
|
|||
reset,
|
||||
hasChanges,
|
||||
saveChanges,
|
||||
getChanges,
|
||||
formData,
|
||||
});
|
||||
|
||||
async function fetch(data) {
|
||||
|
@ -119,11 +125,16 @@ async function onSubmit() {
|
|||
});
|
||||
}
|
||||
isLoading.value = true;
|
||||
await saveChanges();
|
||||
await saveChanges($props.saveFn ? formData.value : null);
|
||||
}
|
||||
|
||||
async function saveChanges(data) {
|
||||
if ($props.saveFn) return $props.saveFn(data, getChanges);
|
||||
if ($props.saveFn) {
|
||||
$props.saveFn(data, getChanges);
|
||||
isLoading.value = false;
|
||||
hasChanges.value = false;
|
||||
return;
|
||||
}
|
||||
const changes = data || getChanges();
|
||||
try {
|
||||
await axios.post($props.saveUrl || $props.url + '/crud', changes);
|
||||
|
@ -260,6 +271,7 @@ watch(formUrl, async () => {
|
|||
<template>
|
||||
<VnPaginate
|
||||
:url="url"
|
||||
:limit="limit"
|
||||
v-bind="$attrs"
|
||||
@on-fetch="fetch"
|
||||
:skeleton="false"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { reactive, computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
|
@ -246,61 +246,55 @@ const makeRequest = async () => {
|
|||
|
||||
<div class="column">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QOptionGroup
|
||||
:options="uploadMethodsOptions"
|
||||
type="radio"
|
||||
v-model="uploadMethodSelected"
|
||||
/>
|
||||
</div>
|
||||
<QOptionGroup
|
||||
:options="uploadMethodsOptions"
|
||||
type="radio"
|
||||
v-model="uploadMethodSelected"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QFile
|
||||
v-if="uploadMethodSelected === 'computer'"
|
||||
ref="inputFileRef"
|
||||
:label="t('File')"
|
||||
:multiple="false"
|
||||
v-model="newPhoto.files"
|
||||
@update:model-value="updatePhotoPreview($event)"
|
||||
:accept="allowedContentTypes"
|
||||
class="required cursor-pointer"
|
||||
>
|
||||
<template #append>
|
||||
<QIcon
|
||||
name="vn:attach"
|
||||
class="cursor-pointer q-mr-sm"
|
||||
@click="openInputFile()"
|
||||
>
|
||||
<!-- <QTooltip>{{ t('globals.selectFile') }}</QTooltip> -->
|
||||
</QIcon>
|
||||
<QIcon name="info" class="cursor-pointer">
|
||||
<QTooltip>{{
|
||||
t('globals.allowedFilesText', {
|
||||
allowedContentTypes: allowedContentTypes,
|
||||
})
|
||||
}}</QTooltip>
|
||||
</QIcon>
|
||||
</template>
|
||||
</QFile>
|
||||
<VnInput
|
||||
v-if="uploadMethodSelected === 'URL'"
|
||||
v-model="newPhoto.url"
|
||||
@update:model-value="updatePhotoPreview($event)"
|
||||
placeholder="https://"
|
||||
/>
|
||||
</div>
|
||||
<QFile
|
||||
v-if="uploadMethodSelected === 'computer'"
|
||||
ref="inputFileRef"
|
||||
:label="t('File')"
|
||||
:multiple="false"
|
||||
v-model="newPhoto.files"
|
||||
@update:model-value="updatePhotoPreview($event)"
|
||||
:accept="allowedContentTypes"
|
||||
class="required cursor-pointer"
|
||||
>
|
||||
<template #append>
|
||||
<QIcon
|
||||
name="vn:attach"
|
||||
class="cursor-pointer q-mr-sm"
|
||||
@click="openInputFile()"
|
||||
>
|
||||
<!-- <QTooltip>{{ t('globals.selectFile') }}</QTooltip> -->
|
||||
</QIcon>
|
||||
<QIcon name="info" class="cursor-pointer">
|
||||
<QTooltip>{{
|
||||
t('globals.allowedFilesText', {
|
||||
allowedContentTypes: allowedContentTypes,
|
||||
})
|
||||
}}</QTooltip>
|
||||
</QIcon>
|
||||
</template>
|
||||
</QFile>
|
||||
<VnInput
|
||||
v-if="uploadMethodSelected === 'URL'"
|
||||
v-model="newPhoto.url"
|
||||
@update:model-value="updatePhotoPreview($event)"
|
||||
placeholder="https://"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Orientation')"
|
||||
:options="viewportTypes"
|
||||
hide-selected
|
||||
option-label="description"
|
||||
v-model="viewportSelection"
|
||||
/>
|
||||
</div>
|
||||
<VnSelect
|
||||
:label="t('Orientation')"
|
||||
:options="viewportTypes"
|
||||
hide-selected
|
||||
option-label="description"
|
||||
v-model="viewportSelection"
|
||||
/>
|
||||
</VnRow>
|
||||
<div class="q-mt-lg row justify-end">
|
||||
<QBtn
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
<script setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { ref, markRaw } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import { QCheckbox } from 'quasar';
|
||||
|
||||
import axios from 'axios';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
|
@ -28,11 +31,16 @@ const $props = defineProps({
|
|||
const { t } = useI18n();
|
||||
const { notify } = useNotify();
|
||||
|
||||
const formData = reactive({
|
||||
field: null,
|
||||
newValue: null,
|
||||
});
|
||||
const inputs = {
|
||||
input: markRaw(VnInput),
|
||||
number: markRaw(VnInput),
|
||||
date: markRaw(VnInputDate),
|
||||
checkbox: markRaw(QCheckbox),
|
||||
select: markRaw(VnSelect),
|
||||
};
|
||||
|
||||
const newValue = ref(null);
|
||||
const selectedField = ref(null);
|
||||
const closeButton = ref(null);
|
||||
const isLoading = ref(false);
|
||||
|
||||
|
@ -47,8 +55,8 @@ const submitData = async () => {
|
|||
isLoading.value = true;
|
||||
const rowsToEdit = $props.rows.map((row) => ({ id: row.id, itemFk: row.itemFk }));
|
||||
const payload = {
|
||||
field: formData.field,
|
||||
newValue: formData.newValue,
|
||||
field: selectedField.value.field,
|
||||
newValue: newValue.value,
|
||||
lines: rowsToEdit,
|
||||
};
|
||||
|
||||
|
@ -75,19 +83,20 @@ const closeForm = () => {
|
|||
<span class="countLines">{{ ` ${rows.length} ` }}</span>
|
||||
<span class="title">{{ t('buy(s)') }}</span>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Field to edit')"
|
||||
:options="fieldsOptions"
|
||||
hide-selected
|
||||
option-label="label"
|
||||
option-value="field"
|
||||
v-model="formData.field"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnInput :label="t('Value')" v-model="formData.newValue" />
|
||||
</div>
|
||||
<VnSelect
|
||||
:label="t('Field to edit')"
|
||||
:options="fieldsOptions"
|
||||
hide-selected
|
||||
option-label="label"
|
||||
v-model="selectedField"
|
||||
/>
|
||||
<component
|
||||
:is="inputs[selectedField?.component || 'input']"
|
||||
v-bind="selectedField?.attrs || {}"
|
||||
v-model="newValue"
|
||||
:label="t('Value')"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</VnRow>
|
||||
<div class="q-mt-lg row justify-end">
|
||||
<QBtn
|
||||
|
|
|
@ -1,21 +1,26 @@
|
|||
<script setup>
|
||||
import { ref, reactive, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
|
||||
|
||||
import axios from 'axios';
|
||||
import { dashIfEmpty } from 'src/filters';
|
||||
|
||||
const props = defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['itemSelected']);
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
|
||||
const itemFilter = {
|
||||
include: [
|
||||
|
@ -73,7 +78,7 @@ const tableColumns = computed(() => [
|
|||
{
|
||||
label: t('entry.buys.color'),
|
||||
name: 'ink',
|
||||
field: 'inkName',
|
||||
field: (row) => row?.ink?.name,
|
||||
align: 'left',
|
||||
},
|
||||
]);
|
||||
|
@ -100,7 +105,7 @@ const fetchResults = async () => {
|
|||
}
|
||||
filter.where = where;
|
||||
|
||||
const { data } = await axios.get(`Entries/${route.params.id}/lastItemBuys`, {
|
||||
const { data } = await axios.get(props.url, {
|
||||
params: { filter: JSON.stringify(filter) },
|
||||
});
|
||||
tableRows.value = data;
|
||||
|
@ -147,48 +152,32 @@ const selectItem = ({ id }) => {
|
|||
</span>
|
||||
<h1 class="title">{{ t('Filter item') }}</h1>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnInput
|
||||
:label="t('entry.buys.name')"
|
||||
v-model="itemFilterParams.name"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnInput
|
||||
:label="t('entry.buys.size')"
|
||||
v-model="itemFilterParams.size"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('entry.buys.producer')"
|
||||
:options="producersOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="itemFilterParams.producerFk"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('entry.buys.type')"
|
||||
:options="ItemTypesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="itemFilterParams.typeFk"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('entry.buys.color')"
|
||||
:options="InksOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="itemFilterParams.inkFk"
|
||||
/>
|
||||
</div>
|
||||
<VnInput :label="t('entry.buys.name')" v-model="itemFilterParams.name" />
|
||||
<VnInput :label="t('entry.buys.size')" v-model="itemFilterParams.size" />
|
||||
<VnSelect
|
||||
:label="t('entry.buys.producer')"
|
||||
:options="producersOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="itemFilterParams.producerFk"
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('entry.buys.type')"
|
||||
:options="ItemTypesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="itemFilterParams.typeFk"
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('entry.buys.color')"
|
||||
:options="InksOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="itemFilterParams.inkFk"
|
||||
/>
|
||||
</VnRow>
|
||||
<div class="q-mt-lg row justify-end">
|
||||
<QBtn
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useI18n } from 'vue-i18n';
|
|||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue';
|
||||
|
||||
import axios from 'axios';
|
||||
|
@ -145,48 +145,38 @@ const selectTravel = ({ id }) => {
|
|||
</span>
|
||||
<h1 class="title">{{ t('Filter travels') }}</h1>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('entry.basicData.agency')"
|
||||
:options="agenciesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="travelFilterParams.agencyModeFk"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('entry.basicData.warehouseOut')"
|
||||
:options="warehousesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="travelFilterParams.warehouseOutFk"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('entry.basicData.warehouseIn')"
|
||||
:options="warehousesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="travelFilterParams.warehouseInFk"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnInputDate
|
||||
:label="t('entry.basicData.shipped')"
|
||||
v-model="travelFilterParams.shipped"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnInputDate
|
||||
:label="t('entry.basicData.landed')"
|
||||
v-model="travelFilterParams.landed"
|
||||
/>
|
||||
</div>
|
||||
<VnSelect
|
||||
:label="t('entry.basicData.agency')"
|
||||
:options="agenciesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="travelFilterParams.agencyModeFk"
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('entry.basicData.warehouseOut')"
|
||||
:options="warehousesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="travelFilterParams.warehouseOutFk"
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('entry.basicData.warehouseIn')"
|
||||
:options="warehousesOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="travelFilterParams.warehouseInFk"
|
||||
/>
|
||||
<VnInputDate
|
||||
:label="t('entry.basicData.shipped')"
|
||||
v-model="travelFilterParams.shipped"
|
||||
/>
|
||||
<VnInputDate
|
||||
:label="t('entry.basicData.landed')"
|
||||
v-model="travelFilterParams.landed"
|
||||
/>
|
||||
</VnRow>
|
||||
<div class="q-mt-lg row justify-end">
|
||||
<QBtn
|
||||
|
|
|
@ -81,6 +81,7 @@ const emit = defineEmits(['onFetch', 'onDataSaved']);
|
|||
const componentIsRendered = ref(false);
|
||||
|
||||
onMounted(async () => {
|
||||
originalData.value = $props.formInitialData;
|
||||
nextTick(() => {
|
||||
componentIsRendered.value = true;
|
||||
});
|
||||
|
@ -101,7 +102,7 @@ onMounted(async () => {
|
|||
});
|
||||
|
||||
onBeforeRouteLeave((to, from, next) => {
|
||||
if (hasChanges.value)
|
||||
if (hasChanges.value && $props.observeFormChanges)
|
||||
quasar.dialog({
|
||||
component: VnConfirm,
|
||||
componentProps: {
|
||||
|
@ -126,7 +127,7 @@ const isLoading = ref(false);
|
|||
// Si elegimos observar los cambios del form significa que inicialmente las actions estaran deshabilitadas
|
||||
const isResetting = ref(false);
|
||||
const hasChanges = ref(!$props.observeFormChanges);
|
||||
const originalData = ref({ ...$props.formInitialData });
|
||||
const originalData = ref({});
|
||||
const formData = computed(() => state.get($props.model));
|
||||
const formUrl = computed(() => $props.url);
|
||||
const defaultButtons = computed(() => ({
|
||||
|
@ -154,14 +155,21 @@ const startFormWatcher = () => {
|
|||
};
|
||||
|
||||
async function fetch() {
|
||||
const { data } = await axios.get($props.url, {
|
||||
params: { filter: JSON.stringify($props.filter) },
|
||||
});
|
||||
try {
|
||||
let { data } = await axios.get($props.url, {
|
||||
params: { filter: JSON.stringify($props.filter) },
|
||||
});
|
||||
|
||||
state.set($props.model, data);
|
||||
originalData.value = data && JSON.parse(JSON.stringify(data));
|
||||
if (Array.isArray(data)) data = data[0] ?? {};
|
||||
|
||||
emit('onFetch', state.get($props.model));
|
||||
state.set($props.model, data);
|
||||
originalData.value = data && JSON.parse(JSON.stringify(data));
|
||||
|
||||
emit('onFetch', state.get($props.model));
|
||||
} catch (error) {
|
||||
state.set($props.model, {});
|
||||
originalData.value = {};
|
||||
}
|
||||
}
|
||||
|
||||
async function save() {
|
||||
|
|
|
@ -6,7 +6,7 @@ import FormModel from 'components/FormModel.vue';
|
|||
|
||||
const emit = defineEmits(['onDataSaved']);
|
||||
|
||||
const $props = defineProps({
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
|
@ -76,25 +76,25 @@ defineExpose({
|
|||
<p>{{ subtitle }}</p>
|
||||
<slot name="form-inputs" :data="data" :validate="validate" />
|
||||
<div class="q-mt-lg row justify-end">
|
||||
<QBtn
|
||||
:label="t('globals.save')"
|
||||
:title="t('globals.save')"
|
||||
type="submit"
|
||||
color="primary"
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
<QBtn
|
||||
:label="t('globals.cancel')"
|
||||
:title="t('globals.cancel')"
|
||||
type="reset"
|
||||
color="primary"
|
||||
flat
|
||||
class="q-ml-sm"
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
v-close-popup
|
||||
/>
|
||||
<QBtn
|
||||
:label="t('globals.save')"
|
||||
:title="t('globals.save')"
|
||||
type="submit"
|
||||
color="primary"
|
||||
class="q-ml-sm"
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</FormModel>
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
|
|||
|
||||
const emit = defineEmits(['onSubmit']);
|
||||
|
||||
const $props = defineProps({
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
|
@ -56,14 +56,6 @@ const closeForm = () => {
|
|||
<p>{{ subtitle }}</p>
|
||||
<slot name="form-inputs" />
|
||||
<div class="q-mt-lg row justify-end">
|
||||
<QBtn
|
||||
v-if="defaultSubmitButton"
|
||||
:label="customSubmitButtonLabel || t('globals.save')"
|
||||
type="submit"
|
||||
color="primary"
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
<QBtn
|
||||
v-if="defaultCancelButton"
|
||||
:label="t('globals.cancel')"
|
||||
|
@ -74,6 +66,14 @@ const closeForm = () => {
|
|||
:loading="isLoading"
|
||||
v-close-popup
|
||||
/>
|
||||
<QBtn
|
||||
v-if="defaultSubmitButton"
|
||||
:label="customSubmitButtonLabel || t('globals.save')"
|
||||
type="submit"
|
||||
color="primary"
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
/>
|
||||
<slot name="customButtons" />
|
||||
</div>
|
||||
</QCard>
|
||||
|
|
|
@ -0,0 +1,359 @@
|
|||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
dataKey: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
customTags: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
exprBuilder: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const itemCategories = ref([]);
|
||||
const selectedCategoryFk = ref(null);
|
||||
const selectedTypeFk = ref(null);
|
||||
const itemTypesOptions = ref([]);
|
||||
const suppliersOptions = ref([]);
|
||||
const tagOptions = ref([]);
|
||||
const tagValues = ref([]);
|
||||
|
||||
const categoryList = computed(() => {
|
||||
return (itemCategories.value || [])
|
||||
.filter((category) => category.display)
|
||||
.map((category) => ({
|
||||
...category,
|
||||
icon: `vn:${(category.icon || '').split('-')[1]}`,
|
||||
}));
|
||||
});
|
||||
|
||||
const selectedCategory = computed(() =>
|
||||
(itemCategories.value || []).find(
|
||||
(category) => category?.id === selectedCategoryFk.value
|
||||
)
|
||||
);
|
||||
|
||||
const selectedType = computed(() => {
|
||||
return (itemTypesOptions.value || []).find(
|
||||
(type) => type?.id === selectedTypeFk.value
|
||||
);
|
||||
});
|
||||
|
||||
const selectCategory = async (params, categoryId, search) => {
|
||||
if (params.categoryFk === categoryId) {
|
||||
resetCategory(params);
|
||||
search();
|
||||
return;
|
||||
}
|
||||
selectedCategoryFk.value = categoryId;
|
||||
params.categoryFk = categoryId;
|
||||
await fetchItemTypes(categoryId);
|
||||
search();
|
||||
};
|
||||
|
||||
const resetCategory = (params) => {
|
||||
selectedCategoryFk.value = null;
|
||||
itemTypesOptions.value = null;
|
||||
if (params) {
|
||||
params.categoryFk = null;
|
||||
params.typeFk = null;
|
||||
}
|
||||
};
|
||||
|
||||
const applyTags = (params, search) => {
|
||||
params.tags = tagValues.value
|
||||
.filter((tag) => tag.selectedTag && tag.value)
|
||||
.map((tag) => ({
|
||||
tagFk: tag.selectedTag.id,
|
||||
tagName: tag.selectedTag.name,
|
||||
value: tag.value,
|
||||
}));
|
||||
search();
|
||||
};
|
||||
|
||||
const fetchItemTypes = async (id) => {
|
||||
try {
|
||||
const filter = {
|
||||
fields: ['id', 'name', 'categoryFk'],
|
||||
where: { categoryFk: id },
|
||||
include: 'category',
|
||||
order: 'name ASC',
|
||||
};
|
||||
const { data } = await axios.get('ItemTypes', {
|
||||
params: { filter: JSON.stringify(filter) },
|
||||
});
|
||||
itemTypesOptions.value = data;
|
||||
} catch (err) {
|
||||
console.error('Error fetching item types', err);
|
||||
}
|
||||
};
|
||||
|
||||
const getCategoryClass = (category, params) => {
|
||||
if (category.id === params?.categoryFk) {
|
||||
return 'active';
|
||||
}
|
||||
};
|
||||
|
||||
const getSelectedTagValues = async (tag) => {
|
||||
try {
|
||||
tag.value = null;
|
||||
const filter = {
|
||||
fields: ['value'],
|
||||
order: 'value ASC',
|
||||
limit: 30,
|
||||
};
|
||||
|
||||
const params = { filter: JSON.stringify(filter) };
|
||||
const { data } = await axios.get(`Tags/${tag.selectedTag.id}/filterValue`, {
|
||||
params,
|
||||
});
|
||||
tag.valueOptions = data;
|
||||
} catch (err) {
|
||||
console.error('Error getting selected tag values');
|
||||
}
|
||||
};
|
||||
|
||||
const removeTag = (index, params, search) => {
|
||||
(tagValues.value || []).splice(index, 1);
|
||||
applyTags(params, search);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
url="ItemCategories"
|
||||
limit="30"
|
||||
auto-load
|
||||
@on-fetch="(data) => (itemCategories = data)"
|
||||
/>
|
||||
<FetchData
|
||||
url="Suppliers"
|
||||
limit="30"
|
||||
auto-load
|
||||
:filter="{ fields: ['id', 'name', 'nickname'], order: 'name ASC', limit: 30 }"
|
||||
@on-fetch="(data) => (suppliersOptions = data)"
|
||||
/>
|
||||
<FetchData
|
||||
url="Tags"
|
||||
:filter="{ fields: ['id', 'name', 'isFree'] }"
|
||||
auto-load
|
||||
limit="30"
|
||||
@on-fetch="(data) => (tagOptions = data)"
|
||||
/>
|
||||
<VnFilterPanel
|
||||
:data-key="props.dataKey"
|
||||
:expr-builder="exprBuilder"
|
||||
:custom-tags="customTags"
|
||||
>
|
||||
<template #tags="{ tag, formatFn }">
|
||||
<strong v-if="tag.label === 'categoryFk'">
|
||||
{{ t(selectedCategory?.name || '') }}
|
||||
</strong>
|
||||
<strong v-else-if="tag.label === 'typeFk'">
|
||||
{{ t(selectedType?.name || '') }}
|
||||
</strong>
|
||||
<div v-else class="q-gutter-x-xs">
|
||||
<strong>{{ t(`components.itemsFilterPanel.${tag.label}`) }}: </strong>
|
||||
<span>{{ formatFn(tag.value) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #customTags="{ tags, params }">
|
||||
<template v-for="tag in tags" :key="tag.label">
|
||||
<VnFilterPanelChip
|
||||
v-for="chip in tag.value"
|
||||
:key="chip"
|
||||
removable
|
||||
@remove="removeTagChip(chip, params, searchFn)"
|
||||
>
|
||||
<div class="q-gutter-x-xs">
|
||||
<strong>{{ chip.tagName }}: </strong>
|
||||
<span>"{{ chip.value }}"</span>
|
||||
</div>
|
||||
</VnFilterPanelChip>
|
||||
</template>
|
||||
</template>
|
||||
<template #body="{ params, searchFn }">
|
||||
<QItem class="category-filter q-mt-md">
|
||||
<QBtn
|
||||
dense
|
||||
flat
|
||||
round
|
||||
v-for="category in categoryList"
|
||||
:key="category.name"
|
||||
:class="['category', getCategoryClass(category, params)]"
|
||||
:icon="category.icon"
|
||||
@click="selectCategory(params, category.id, searchFn)"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t(category.name) }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
</QItem>
|
||||
<QItem class="q-my-md">
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
:label="t('components.itemsFilterPanel.typeFk')"
|
||||
v-model="params.typeFk"
|
||||
:options="itemTypesOptions"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
use-input
|
||||
:disable="!selectedCategoryFk"
|
||||
@update:model-value="
|
||||
(value) => {
|
||||
selectedTypeFk = value;
|
||||
searchFn();
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #option="{ itemProps, opt }">
|
||||
<QItem v-bind="itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ opt.name }}</QItemLabel>
|
||||
<QItemLabel caption>
|
||||
{{ opt.categoryName }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QSeparator />
|
||||
<slot name="body" :params="params" :search-fn="searchFn" />
|
||||
<QItem
|
||||
v-for="(value, index) in tagValues"
|
||||
:key="value"
|
||||
class="q-mt-md filter-value"
|
||||
>
|
||||
<QItemSection class="col">
|
||||
<VnSelect
|
||||
:label="t('components.itemsFilterPanel.tag')"
|
||||
v-model="value.selectedTag"
|
||||
:options="tagOptions"
|
||||
option-label="name"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
:emit-value="false"
|
||||
use-input
|
||||
:is-clearable="false"
|
||||
@update:model-value="getSelectedTagValues(value)"
|
||||
/>
|
||||
</QItemSection>
|
||||
<QItemSection class="col">
|
||||
<VnSelect
|
||||
v-if="!value?.selectedTag?.isFree && value.valueOptions"
|
||||
:label="t('components.itemsFilterPanel.value')"
|
||||
v-model="value.value"
|
||||
:options="value.valueOptions || []"
|
||||
option-value="value"
|
||||
option-label="value"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
emit-value
|
||||
use-input
|
||||
:disable="!value"
|
||||
:is-clearable="false"
|
||||
@update:model-value="applyTags(params, searchFn)"
|
||||
/>
|
||||
<VnInput
|
||||
v-else
|
||||
v-model="value.value"
|
||||
:label="t('components.itemsFilterPanel.value')"
|
||||
:disable="!value"
|
||||
is-outlined
|
||||
:is-clearable="false"
|
||||
@keyup.enter="applyTags(params, searchFn)"
|
||||
/>
|
||||
</QItemSection>
|
||||
<QIcon
|
||||
name="delete"
|
||||
class="fill-icon-on-hover q-px-xs"
|
||||
color="primary"
|
||||
size="sm"
|
||||
@click="removeTag(index, params, searchFn)"
|
||||
/>
|
||||
</QItem>
|
||||
<QItem class="q-mt-lg">
|
||||
<QIcon
|
||||
name="add_circle"
|
||||
class="fill-icon-on-hover q-px-xs"
|
||||
color="primary"
|
||||
size="sm"
|
||||
@click="tagValues.push({})"
|
||||
/>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnFilterPanel>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.category-filter {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
|
||||
.category {
|
||||
padding: 8px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
font-size: 1.4rem;
|
||||
background-color: var(--vn-accent-color);
|
||||
|
||||
&.active {
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
en:
|
||||
params:
|
||||
supplier: Supplier
|
||||
from: From
|
||||
to: To
|
||||
active: Is active
|
||||
visible: Is visible
|
||||
floramondo: Is floramondo
|
||||
salesPersonFk: Buyer
|
||||
categoryFk: Category
|
||||
|
||||
es:
|
||||
params:
|
||||
supplier: Proveedor
|
||||
from: Desde
|
||||
to: Hasta
|
||||
active: Activo
|
||||
visible: Visible
|
||||
floramondo: Floramondo
|
||||
salesPersonFk: Comprador
|
||||
categoryFk: Categoría
|
||||
</i18n>
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { onMounted, ref, reactive } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { QSeparator, useQuasar } from 'quasar';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
@ -22,6 +22,8 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const expansionItemElements = reactive({});
|
||||
|
||||
onMounted(async () => {
|
||||
await navigation.fetchPinned();
|
||||
getRoutes();
|
||||
|
@ -108,6 +110,10 @@ async function togglePinned(item, event) {
|
|||
type: 'positive',
|
||||
});
|
||||
}
|
||||
|
||||
const handleItemExpansion = (itemName) => {
|
||||
expansionItemElements[itemName].scrollToLastElement();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -213,8 +219,12 @@ async function togglePinned(item, event) {
|
|||
:icon="item.icon"
|
||||
:label="t(item.title)"
|
||||
:content-inset-level="0.5"
|
||||
@after-show="handleItemExpansion(item.name)"
|
||||
>
|
||||
<LeftMenuItemGroup :item="item" />
|
||||
<LeftMenuItemGroup
|
||||
:ref="(el) => (expansionItemElements[item.name] = el)"
|
||||
:item="item"
|
||||
/>
|
||||
</QExpansionItem>
|
||||
</QList>
|
||||
</template>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import LeftMenuItem from './LeftMenuItem.vue';
|
||||
import { elementIsVisibleInViewport } from 'src/composables/elementIsVisibleInViewport';
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
|
@ -13,10 +14,27 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const groupEnd = ref(null);
|
||||
|
||||
const scrollToLastElement = () => {
|
||||
if (groupEnd.value && !elementIsVisibleInViewport(groupEnd.value)) {
|
||||
groupEnd.value.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'end',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const item = computed(() => props.item); // eslint-disable-line vue/no-dupe-keys
|
||||
|
||||
defineExpose({
|
||||
scrollToLastElement,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-for="section in item.children" :key="section.name">
|
||||
<LeftMenuItem :item="section" />
|
||||
</template>
|
||||
<div ref="groupEnd" />
|
||||
</template>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { reactive, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import FormModelPopup from './FormModelPopup.vue';
|
||||
|
@ -50,16 +50,15 @@ const onDataSaved = (data) => {
|
|||
>
|
||||
<template #form-inputs="{ data }">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QInput
|
||||
:label="t('Type the visible quantity')"
|
||||
v-model.number="data.quantity"
|
||||
/>
|
||||
</div>
|
||||
<QInput
|
||||
:label="t('Type the visible quantity')"
|
||||
v-model.number="data.quantity"
|
||||
autofocus
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
:label="t('Warehouse')"
|
||||
v-model="data.warehouseFk"
|
||||
:options="warehousesOptions"
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useRouter } from 'vue-router';
|
|||
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import FormPopup from './FormPopup.vue';
|
||||
|
||||
import axios from 'axios';
|
||||
|
@ -83,74 +83,66 @@ const transferInvoice = async () => {
|
|||
>
|
||||
<template #form-inputs>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Client')"
|
||||
:options="clientsOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="transferInvoiceParams.newClientFk"
|
||||
:required="true"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>
|
||||
#{{ scope.opt?.id }} -
|
||||
{{ scope.opt?.name }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Rectificative type')"
|
||||
:options="rectificativeTypeOptions"
|
||||
hide-selected
|
||||
option-label="description"
|
||||
option-value="id"
|
||||
v-model="transferInvoiceParams.cplusRectificationTypeFk"
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
<VnSelect
|
||||
:label="t('Client')"
|
||||
:options="clientsOptions"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="transferInvoiceParams.newClientFk"
|
||||
:required="true"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>
|
||||
#{{ scope.opt?.id }} -
|
||||
{{ scope.opt?.name }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
<VnSelect
|
||||
:label="t('Rectificative type')"
|
||||
:options="rectificativeTypeOptions"
|
||||
hide-selected
|
||||
option-label="description"
|
||||
option-value="id"
|
||||
v-model="transferInvoiceParams.cplusRectificationTypeFk"
|
||||
:required="true"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Class')"
|
||||
:options="siiTypeInvoiceOutsOptions"
|
||||
hide-selected
|
||||
option-label="description"
|
||||
option-value="id"
|
||||
v-model="transferInvoiceParams.siiTypeInvoiceOutFk"
|
||||
:required="true"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>
|
||||
{{ scope.opt?.code }} -
|
||||
{{ scope.opt?.description }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnSelectFilter
|
||||
:label="t('Type')"
|
||||
:options="invoiceCorrectionTypesOptions"
|
||||
hide-selected
|
||||
option-label="description"
|
||||
option-value="id"
|
||||
v-model="transferInvoiceParams.invoiceCorrectionTypeFk"
|
||||
:required="true"
|
||||
/>
|
||||
</div>
|
||||
<VnSelect
|
||||
:label="t('Class')"
|
||||
:options="siiTypeInvoiceOutsOptions"
|
||||
hide-selected
|
||||
option-label="description"
|
||||
option-value="id"
|
||||
v-model="transferInvoiceParams.siiTypeInvoiceOutFk"
|
||||
:required="true"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>
|
||||
{{ scope.opt?.code }} -
|
||||
{{ scope.opt?.description }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
<VnSelect
|
||||
:label="t('Type')"
|
||||
:options="invoiceCorrectionTypesOptions"
|
||||
hide-selected
|
||||
option-label="description"
|
||||
option-value="id"
|
||||
v-model="transferInvoiceParams.invoiceCorrectionTypeFk"
|
||||
:required="true"
|
||||
/>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormPopup>
|
||||
|
|
|
@ -7,7 +7,7 @@ import axios from 'axios';
|
|||
import { useState } from 'src/composables/useState';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
import { localeEquivalence } from 'src/i18n/index';
|
||||
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
|
||||
|
@ -172,42 +172,57 @@ function copyUserToken() {
|
|||
<QSeparator inset class="q-mx-lg" />
|
||||
<div class="col q-gutter-xs q-pa-md">
|
||||
<VnRow>
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
:label="t('components.userPanel.localWarehouse')"
|
||||
v-model="user.localWarehouseFk"
|
||||
:options="warehousesData"
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
/>
|
||||
<VnSelectFilter
|
||||
:label="t('components.userPanel.localBank')"
|
||||
input-debounce="0"
|
||||
hide-selected
|
||||
/>
|
||||
<VnSelect
|
||||
:label="t('components.userPanel.localBank')"
|
||||
v-model="user.localBankFk"
|
||||
:options="accountBankData"
|
||||
option-label="bank"
|
||||
option-value="id"
|
||||
></VnSelectFilter>
|
||||
input-debounce="0"
|
||||
hide-selected
|
||||
>
|
||||
<template #option="{ itemProps, opt }">
|
||||
<QItem v-bind="itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>
|
||||
{{ `${opt.id}: ${opt.bank}` }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
:label="t('components.userPanel.localCompany')"
|
||||
hide-selected
|
||||
v-model="user.companyFk"
|
||||
v-model="user.localCompanyFk"
|
||||
:options="companiesData"
|
||||
option-label="code"
|
||||
option-value="id"
|
||||
input-debounce="0"
|
||||
/>
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
:label="t('components.userPanel.userWarehouse')"
|
||||
hide-selected
|
||||
v-model="user.warehouseFk"
|
||||
:options="warehousesData"
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
input-debounce="0"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
:label="t('components.userPanel.userCompany')"
|
||||
hide-selected
|
||||
v-model="user.companyFk"
|
||||
|
@ -215,6 +230,8 @@ function copyUserToken() {
|
|||
option-label="code"
|
||||
option-value="id"
|
||||
style="flex: 0"
|
||||
dense
|
||||
input-debounce="0"
|
||||
/>
|
||||
</VnRow>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<script setup>
|
||||
import { ref, onMounted, useSlots } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
|
||||
const slots = useSlots();
|
||||
const hasContent = ref(false);
|
||||
const rightPanel = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
rightPanel.value = document.querySelector('#right-panel');
|
||||
if (rightPanel.value.childNodes.length) hasContent.value = true;
|
||||
|
||||
// Check if there's content to display
|
||||
const observer = new MutationObserver(() => {
|
||||
hasContent.value = rightPanel.value.childNodes.length;
|
||||
});
|
||||
|
||||
if (rightPanel.value)
|
||||
observer.observe(rightPanel.value, {
|
||||
subtree: true,
|
||||
childList: true,
|
||||
attributes: true,
|
||||
});
|
||||
|
||||
if (!slots['right-panel'] && !hasContent.value) stateStore.rightDrawer = false;
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const stateStore = useStateStore();
|
||||
</script>
|
||||
<template>
|
||||
<Teleport to="#actions-append">
|
||||
<div class="row q-gutter-x-sm">
|
||||
<QBtn
|
||||
v-if="hasContent || $slots['right-panel']"
|
||||
flat
|
||||
@click="stateStore.toggleRightDrawer()"
|
||||
round
|
||||
dense
|
||||
icon="menu"
|
||||
>
|
||||
<QTooltip bottom anchor="bottom right">
|
||||
{{ t('globals.collapseMenu') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
</div>
|
||||
</Teleport>
|
||||
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
|
||||
<QScrollArea class="fit text-grey-8">
|
||||
<div id="right-panel"></div>
|
||||
<slot v-if="!hasContent" name="right-panel" />
|
||||
</QScrollArea>
|
||||
</QDrawer>
|
||||
</template>
|
|
@ -29,10 +29,12 @@ async function confirm() {
|
|||
const response = { address: address.value };
|
||||
if (props.promise) {
|
||||
isLoading.value = true;
|
||||
const { address: _address, ...restData } = props.data;
|
||||
|
||||
try {
|
||||
Object.assign(response, restData);
|
||||
const dataCopy = JSON.parse(JSON.stringify({ ...props.data }));
|
||||
delete dataCopy.address;
|
||||
|
||||
Object.assign(response, dataCopy);
|
||||
await props.promise(response);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
<script setup>
|
||||
import { onBeforeMount, computed, watchEffect } from 'vue';
|
||||
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import useCardSize from 'src/composables/useCardSize';
|
||||
import VnSubToolbar from '../ui/VnSubToolbar.vue';
|
||||
import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
||||
import LeftMenu from 'components/LeftMenu.vue';
|
||||
import RightMenu from 'components/common/RightMenu.vue';
|
||||
|
||||
const props = defineProps({
|
||||
dataKey: { type: String, required: true },
|
||||
baseUrl: { type: String, default: undefined },
|
||||
customUrl: { type: String, default: undefined },
|
||||
filter: { type: Object, default: () => {} },
|
||||
descriptor: { type: Object, required: true },
|
||||
filterPanel: { type: Object, default: undefined },
|
||||
searchDataKey: { type: String, default: undefined },
|
||||
searchUrl: { type: String, default: undefined },
|
||||
searchbarLabel: { type: String, default: '' },
|
||||
searchbarInfo: { type: String, default: '' },
|
||||
});
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const route = useRoute();
|
||||
const url = computed(() => {
|
||||
if (props.baseUrl) return `${props.baseUrl}/${route.params.id}`;
|
||||
return props.customUrl;
|
||||
});
|
||||
|
||||
const arrayData = useArrayData(props.dataKey, {
|
||||
url: url.value,
|
||||
filter: props.filter,
|
||||
});
|
||||
|
||||
onBeforeMount(async () => {
|
||||
if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id };
|
||||
await arrayData.fetch({ append: false });
|
||||
});
|
||||
|
||||
if (props.baseUrl) {
|
||||
onBeforeRouteUpdate(async (to, from) => {
|
||||
if (to.params.id !== from.params.id) {
|
||||
arrayData.store.url = `${props.baseUrl}/${route.params.id}`;
|
||||
await arrayData.fetch({ append: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (Array.isArray(arrayData.store.data))
|
||||
arrayData.store.data = arrayData.store.data[0];
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<template v-if="stateStore.isHeaderMounted()">
|
||||
<Teleport to="#searchbar" v-if="props.searchDataKey">
|
||||
<slot name="searchbar">
|
||||
<VnSearchbar
|
||||
:data-key="props.searchDataKey"
|
||||
:url="props.searchUrl"
|
||||
:label="props.searchbarLabel"
|
||||
:info="props.searchbarInfo"
|
||||
/>
|
||||
</slot>
|
||||
</Teleport>
|
||||
<slot v-else name="searchbar" />
|
||||
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
|
||||
<QScrollArea class="fit">
|
||||
<component :is="descriptor" />
|
||||
<QSeparator />
|
||||
<LeftMenu source="card" />
|
||||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<RightMenu>
|
||||
<template #right-panel v-if="props.filterPanel">
|
||||
<component :is="props.filterPanel" :data-key="props.searchDataKey" />
|
||||
</template>
|
||||
</RightMenu>
|
||||
</template>
|
||||
<QPageContainer>
|
||||
<QPage>
|
||||
<VnSubToolbar />
|
||||
<div :class="[useCardSize(), $attrs.class]">
|
||||
<RouterView />
|
||||
</div>
|
||||
</QPage>
|
||||
</QPageContainer>
|
||||
</template>
|
|
@ -6,7 +6,7 @@ import axios from 'axios';
|
|||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import FormModelPopup from 'components/FormModelPopup.vue';
|
||||
|
||||
|
@ -123,7 +123,7 @@ function addDefaultData(data) {
|
|||
<div class="q-gutter-y-ms">
|
||||
<VnRow>
|
||||
<VnInput :label="t('globals.reference')" v-model="dms.reference" />
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
:label="t('globals.company')"
|
||||
v-model="dms.companyFk"
|
||||
:options="companies"
|
||||
|
@ -133,7 +133,7 @@ function addDefaultData(data) {
|
|||
/>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
:label="t('globals.warehouse')"
|
||||
v-model="dms.warehouseFk"
|
||||
:options="warehouses"
|
||||
|
@ -141,7 +141,7 @@ function addDefaultData(data) {
|
|||
option-label="name"
|
||||
input-debounce="0"
|
||||
/>
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
:label="t('globals.type')"
|
||||
v-model="dms.dmsTypeFk"
|
||||
:options="dmsTypes"
|
||||
|
|
|
@ -35,7 +35,7 @@ const $props = defineProps({
|
|||
downloadModel: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
default: undefined,
|
||||
},
|
||||
defaultDmsCode: {
|
||||
type: String,
|
||||
|
@ -187,7 +187,7 @@ const columns = computed(() => [
|
|||
downloadFile(
|
||||
prop.row.id,
|
||||
$props.downloadModel,
|
||||
null,
|
||||
undefined,
|
||||
prop.row.download
|
||||
),
|
||||
},
|
||||
|
|
|
@ -26,7 +26,7 @@ const value = computed({
|
|||
emit('update:modelValue', value);
|
||||
},
|
||||
});
|
||||
|
||||
const hover = ref(false);
|
||||
const styleAttrs = computed(() => {
|
||||
return $props.isOutlined
|
||||
? {
|
||||
|
@ -37,10 +37,6 @@ const styleAttrs = computed(() => {
|
|||
: {};
|
||||
});
|
||||
|
||||
const onEnterPress = () => {
|
||||
emit('keyup.enter');
|
||||
};
|
||||
|
||||
const focus = () => {
|
||||
vnInputRef.value.focus();
|
||||
};
|
||||
|
@ -48,23 +44,52 @@ const focus = () => {
|
|||
defineExpose({
|
||||
focus,
|
||||
});
|
||||
|
||||
const inputRules = [
|
||||
(val) => {
|
||||
const { min } = vnInputRef.value.$attrs;
|
||||
if (min >= 0) if (Math.floor(val) < min) return t('inputMin', { value: min });
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QInput
|
||||
ref="vnInputRef"
|
||||
v-model="value"
|
||||
v-bind="{ ...$attrs, ...styleAttrs }"
|
||||
type="text"
|
||||
:class="{ required: $attrs.required }"
|
||||
@keyup.enter="onEnterPress()"
|
||||
<div
|
||||
@mouseover="hover = true"
|
||||
@mouseleave="hover = false"
|
||||
:rules="$attrs.required ? [requiredFieldRule] : null"
|
||||
>
|
||||
<template v-if="$slots.prepend" #prepend>
|
||||
<slot name="prepend" />
|
||||
</template>
|
||||
<template v-if="$slots.append" #append>
|
||||
<slot name="append" />
|
||||
</template>
|
||||
</QInput>
|
||||
<QInput
|
||||
ref="vnInputRef"
|
||||
v-model="value"
|
||||
v-bind="{ ...$attrs, ...styleAttrs }"
|
||||
:type="$attrs.type"
|
||||
:class="{ required: $attrs.required }"
|
||||
@keyup.enter="emit('keyup.enter')"
|
||||
:clearable="false"
|
||||
:rules="inputRules"
|
||||
:lazy-rules="true"
|
||||
hide-bottom-space
|
||||
>
|
||||
<template v-if="$slots.prepend" #prepend>
|
||||
<slot name="prepend" />
|
||||
</template>
|
||||
|
||||
<template #append>
|
||||
<slot name="append" v-if="$slots.append && !$attrs.disabled" />
|
||||
<QIcon
|
||||
name="close"
|
||||
size="xs"
|
||||
v-if="$slots.append && hover && value && !$attrs.disabled"
|
||||
@click="value = null"
|
||||
></QIcon>
|
||||
</template>
|
||||
</QInput>
|
||||
</div>
|
||||
</template>
|
||||
<i18n>
|
||||
en:
|
||||
inputMin: Must be more than {value}
|
||||
es:
|
||||
inputMin: Debe ser mayor a {value}
|
||||
</i18n>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
import isValidDate from "filters/isValidDate";
|
||||
import isValidDate from 'filters/isValidDate';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
|
@ -16,7 +15,13 @@ const props = defineProps({
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
emitDateFormat: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
const hover = ref(false);
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const joinDateAndTime = (date, time) => {
|
||||
|
@ -36,7 +41,10 @@ const value = computed({
|
|||
return props.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
emit('update:modelValue', joinDateAndTime(value, time.value));
|
||||
emit(
|
||||
'update:modelValue',
|
||||
props.emitDateFormat ? new Date(value) : joinDateAndTime(value, time.value)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -77,30 +85,39 @@ const styleAttrs = computed(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<VnInput
|
||||
class="vn-input-date"
|
||||
:model-value="displayDate(value)"
|
||||
v-bind="{ ...$attrs, ...styleAttrs }"
|
||||
readonly
|
||||
@click="isPopupOpen = true"
|
||||
>
|
||||
<template #append>
|
||||
<QIcon name="event" class="cursor-pointer">
|
||||
<QPopupProxy
|
||||
v-model="isPopupOpen"
|
||||
cover
|
||||
transition-show="scale"
|
||||
transition-hide="scale"
|
||||
:no-parent-event="props.readonly"
|
||||
>
|
||||
<QDate
|
||||
:model-value="formatDate(value)"
|
||||
@update:model-value="onDateUpdate"
|
||||
/>
|
||||
</QPopupProxy>
|
||||
</QIcon>
|
||||
</template>
|
||||
</VnInput>
|
||||
<div @mouseover="hover = true" @mouseleave="hover = false">
|
||||
<QInput
|
||||
class="vn-input-date"
|
||||
readonly
|
||||
:model-value="displayDate(value)"
|
||||
v-bind="{ ...$attrs, ...styleAttrs }"
|
||||
@click="isPopupOpen = true"
|
||||
>
|
||||
<template #append>
|
||||
<QIcon
|
||||
name="close"
|
||||
size="xs"
|
||||
v-if="hover && value"
|
||||
@click="onDateUpdate(null)"
|
||||
></QIcon>
|
||||
<QIcon name="event" class="cursor-pointer">
|
||||
<QPopupProxy
|
||||
v-model="isPopupOpen"
|
||||
cover
|
||||
transition-show="scale"
|
||||
transition-hide="scale"
|
||||
:no-parent-event="props.readonly"
|
||||
>
|
||||
<QDate
|
||||
:today-btn="true"
|
||||
:model-value="formatDate(value)"
|
||||
@update:model-value="onDateUpdate"
|
||||
/>
|
||||
</QPopupProxy>
|
||||
</QIcon>
|
||||
</template>
|
||||
</QInput>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import isValidDate from 'filters/isValidDate';
|
||||
import VnInput from "components/common/VnInput.vue";
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
|
@ -20,6 +20,7 @@ const props = defineProps({
|
|||
});
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const value = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
|
@ -27,12 +28,7 @@ const value = computed({
|
|||
set(value) {
|
||||
const [hours, minutes] = value.split(':');
|
||||
const date = new Date(props.modelValue);
|
||||
date.setHours(
|
||||
Number.parseInt(hours) || 0,
|
||||
Number.parseInt(minutes) || 0,
|
||||
0,
|
||||
0
|
||||
);
|
||||
date.setHours(Number.parseInt(hours) || 0, Number.parseInt(minutes) || 0, 0, 0);
|
||||
emit('update:modelValue', value ? date.toISOString() : null);
|
||||
},
|
||||
});
|
||||
|
@ -71,7 +67,7 @@ const styleAttrs = computed(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<VnInput
|
||||
<QInput
|
||||
class="vn-input-time"
|
||||
readonly
|
||||
:model-value="formatTime(value)"
|
||||
|
@ -79,7 +75,7 @@ const styleAttrs = computed(() => {
|
|||
@click="isPopupOpen = true"
|
||||
>
|
||||
<template #append>
|
||||
<QIcon name="event" class="cursor-pointer">
|
||||
<QIcon name="Schedule" class="cursor-pointer">
|
||||
<QPopupProxy
|
||||
v-model="isPopupOpen"
|
||||
cover
|
||||
|
@ -111,7 +107,7 @@ const styleAttrs = computed(() => {
|
|||
</QPopupProxy>
|
||||
</QIcon>
|
||||
</template>
|
||||
</VnInput>
|
||||
</QInput>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -12,7 +12,7 @@ import { useValidator } from 'src/composables/useValidator';
|
|||
import VnAvatar from '../ui/VnAvatar.vue';
|
||||
import VnJsonValue from '../common/VnJsonValue.vue';
|
||||
import FetchData from '../FetchData.vue';
|
||||
import VnSelectFilter from './VnSelectFilter.vue';
|
||||
import VnSelect from './VnSelect.vue';
|
||||
import VnUserLink from '../ui/VnUserLink.vue';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
|
@ -421,12 +421,13 @@ setLogTree();
|
|||
>
|
||||
<div class="timeline">
|
||||
<div class="user-avatar">
|
||||
<VnUserLink :worker-id="userLog.user.id">
|
||||
<VnUserLink :worker-id="userLog?.user?.id">
|
||||
<template #link>
|
||||
<VnAvatar
|
||||
:class="{ 'cursor-pointer': userLog.user.id }"
|
||||
:worker-id="userLog.user.id"
|
||||
:title="userLog.user.nickname"
|
||||
:class="{ 'cursor-pointer': userLog?.user?.id }"
|
||||
:worker-id="userLog?.user?.id"
|
||||
:title="userLog?.user?.nickname"
|
||||
:show-letter="!userLog?.user"
|
||||
size="lg"
|
||||
/>
|
||||
</template>
|
||||
|
@ -659,7 +660,7 @@ setLogTree();
|
|||
</QInput>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
class="full-width"
|
||||
:label="t('globals.entity')"
|
||||
v-model="selectedFilters.changedModel"
|
||||
|
@ -689,7 +690,7 @@ setLogTree();
|
|||
<QSkeleton type="QInput" class="full-width" />
|
||||
</QItemSection>
|
||||
<QItemSection v-if="workers && userRadio !== null">
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
class="full-width"
|
||||
:label="t('globals.user')"
|
||||
v-model="userSelect"
|
||||
|
@ -713,7 +714,7 @@ setLogTree();
|
|||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
</VnSelect>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem class="q-mt-sm">
|
||||
|
|
|
@ -22,6 +22,10 @@ const $props = defineProps({
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
optionFilter: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
default: '',
|
||||
|
@ -57,9 +61,9 @@ const $props = defineProps({
|
|||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const requiredFieldRule = (val) => !!val || t('globals.fieldRequired');
|
||||
const requiredFieldRule = (val) => val ?? t('globals.fieldRequired');
|
||||
|
||||
const { optionLabel, optionValue, options, modelValue } = toRefs($props);
|
||||
const { optionLabel, optionValue, optionFilter, options, modelValue } = toRefs($props);
|
||||
const myOptions = ref([]);
|
||||
const myOptionsOriginal = ref([]);
|
||||
const vnSelectRef = ref();
|
||||
|
@ -109,9 +113,9 @@ async function fetchFilter(val) {
|
|||
const { fields, sortBy, limit } = $props;
|
||||
let key = optionLabel.value;
|
||||
|
||||
if (new RegExp(/\d/g).test(val)) key = optionValue.value;
|
||||
if (new RegExp(/\d/g).test(val)) key = optionFilter.value ?? optionValue.value;
|
||||
|
||||
const where = { [key]: { like: `%${val}%` } };
|
||||
const where = { ...{ [key]: { like: `%${val}%` } }, ...$props.where };
|
||||
return dataRef.value.fetch({ fields, where, order: sortBy, limit });
|
||||
}
|
||||
|
||||
|
@ -167,8 +171,10 @@ watch(modelValue, (newValue) => {
|
|||
hide-selected
|
||||
fill-input
|
||||
ref="vnSelectRef"
|
||||
lazy-rules
|
||||
:class="{ required: $attrs.required }"
|
||||
:rules="$attrs.required ? [requiredFieldRule] : null"
|
||||
virtual-scroll-slice-size="options.length"
|
||||
>
|
||||
<template v-if="isClearable" #append>
|
||||
<QIcon
|
|
@ -1,7 +1,7 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
|
||||
import { useRole } from 'src/composables/useRole';
|
||||
|
||||
|
@ -52,7 +52,7 @@ const toggleForm = () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<VnSelectFilter v-model="value" :options="options" v-bind="$attrs">
|
||||
<VnSelect v-model="value" :options="options" v-bind="$attrs">
|
||||
<template v-if="isAllowedToCreate" #append>
|
||||
<QIcon
|
||||
@click.stop.prevent="toggleForm()"
|
||||
|
@ -72,7 +72,7 @@ const toggleForm = () => {
|
|||
<template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName">
|
||||
<slot :name="slotName" v-bind="slotData" :key="slotName" />
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
</VnSelect>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -21,7 +21,8 @@ const props = defineProps({
|
|||
},
|
||||
template: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
locale: {
|
||||
type: String,
|
||||
|
@ -49,7 +50,7 @@ updateMessage();
|
|||
|
||||
function updateMessage() {
|
||||
const params = props.data;
|
||||
const key = `templates['${props.template}']`;
|
||||
const key = props.template ? `templates['${props.template}']` : '';
|
||||
|
||||
message.value = t(key, params, { locale: locale.value });
|
||||
}
|
||||
|
@ -104,15 +105,14 @@ async function send() {
|
|||
map-options
|
||||
:input-debounce="0"
|
||||
rounded
|
||||
outlined
|
||||
dense
|
||||
/>
|
||||
</QCardSection>
|
||||
<QCardSection class="q-pb-xs">
|
||||
<VnInput :label="t('Phone')" v-model="phone" is-outlined />
|
||||
<VnInput :label="t('Phone')" v-model="phone" />
|
||||
</QCardSection>
|
||||
<QCardSection class="q-pb-xs">
|
||||
<VnInput v-model="subject" :label="t('Subject')" is-outlined />
|
||||
<VnInput v-model="subject" :label="t('Subject')" />
|
||||
</QCardSection>
|
||||
<QCardSection class="q-mb-md" q-input>
|
||||
<QInput
|
||||
|
@ -125,7 +125,6 @@ async function send() {
|
|||
:bottom-slots="true"
|
||||
:rules="[(value) => value.length < maxLength || 'Error!']"
|
||||
stack-label
|
||||
outlined
|
||||
autofocus
|
||||
>
|
||||
<template #append>
|
||||
|
@ -135,6 +134,11 @@ async function send() {
|
|||
@click="message = ''"
|
||||
class="cursor-pointer"
|
||||
/>
|
||||
<QIcon name="info" class="cursor-pointer">
|
||||
<QTooltip>
|
||||
{{ t('messageTooltip') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</template>
|
||||
<template #counter>
|
||||
<QChip :color="color" dense>
|
||||
|
@ -184,18 +188,21 @@ en:
|
|||
es: Spanish
|
||||
fr: French
|
||||
pt: Portuguese
|
||||
messageTooltip: Special characters like accents counts as multiple
|
||||
es:
|
||||
Send SMS: Enviar SMS
|
||||
Language: Idioma
|
||||
Phone: Móvil
|
||||
Subject: Asunto
|
||||
Message: Mensaje
|
||||
messageTooltip: Carácteres especiales como acentos cuentan como varios
|
||||
templates:
|
||||
pendingPayment: 'Su pedido está pendiente de pago.
|
||||
Por favor, entre en la página web y efectue el pago con tarjeta. Muchas gracias.'
|
||||
minAmount: 'Es necesario un importe mínimo de 50€ (Sin IVA) en su pedido
|
||||
{ orderId } del día { shipped } para recibirlo sin portes adicionales.'
|
||||
orderChanges: 'Pedido {orderId} día { shipped }: { changes }'
|
||||
minAmount: 'Te recordamos que tu pedido {orderId} es inferior a 50€.
|
||||
Te recomendamos amplíes para no generar costes extra, provocarán un incremento de tu tarifa.
|
||||
¡Un saludo!'
|
||||
orderChanges: 'Pedido {orderId} con llegada estimada día { landing }: { changes }'
|
||||
en: Inglés
|
||||
es: Español
|
||||
fr: Francés
|
||||
|
@ -207,12 +214,14 @@ fr:
|
|||
Phone: Mobile
|
||||
Subject: Affaire
|
||||
Message: Message
|
||||
messageTooltip: Les caractères spéciaux comme les accents comptent comme plusieurs
|
||||
templates:
|
||||
pendingPayment: 'Votre commande est en attente de paiement.
|
||||
Veuillez vous connecter sur le site web et effectuer le paiement par carte. Merci beaucoup.'
|
||||
minAmount: 'Un montant minimum de 50€ (TVA non incluse) est requis pour votre commande
|
||||
{ orderId } du { shipped } afin de la recevoir sans frais de port supplémentaires.'
|
||||
orderChanges: 'Commande { orderId } du { shipped }: { changes }'
|
||||
pendingPayment: 'Verdnatura : Commande en attente de règlement. Veuillez régler votre commande avant 9h.
|
||||
Sinon elle sera décalée en fonction de vos jours de livraison . Merci'
|
||||
minAmount: 'Verdnatura vous rappelle :
|
||||
Montant minimum nécessaire de 50 euros pour recevoir la commande { orderId } livraison { landing }.
|
||||
Merci.'
|
||||
orderChanges: 'Commande {orderId} livraison {landing} indisponible/s. Désolés pour le dérangement.'
|
||||
en: Anglais
|
||||
es: Espagnol
|
||||
fr: Français
|
||||
|
@ -224,12 +233,13 @@ pt:
|
|||
Phone: Móvel
|
||||
Subject: Assunto
|
||||
Message: Mensagem
|
||||
messageTooltip: Caracteres especiais como acentos contam como vários
|
||||
templates:
|
||||
pendingPayment: 'Seu pedido está pendente de pagamento.
|
||||
Por favor, acesse o site e faça o pagamento com cartão. Muito obrigado.'
|
||||
minAmount: 'É necessário um valor mínimo de 50€ (sem IVA) em seu pedido
|
||||
{ orderId } do dia { shipped } para recebê-lo sem custos de envio adicionais.'
|
||||
orderChanges: 'Pedido { orderId } dia { shipped }: { changes }'
|
||||
{ orderId } do dia { landing } para recebê-lo sem custos de envio adicionais.'
|
||||
orderChanges: 'Pedido { orderId } com chegada dia { landing }: { changes }'
|
||||
en: Inglês
|
||||
es: Espanhol
|
||||
fr: Francês
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { onBeforeMount, useSlots, watch, computed, ref } from 'vue';
|
||||
import { onBeforeMount, watch, computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
|
@ -38,7 +38,6 @@ const $props = defineProps({
|
|||
});
|
||||
|
||||
const state = useState();
|
||||
const slots = useSlots();
|
||||
const { t } = useI18n();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
const arrayData = useArrayData($props.dataKey || $props.module, {
|
||||
|
@ -47,7 +46,7 @@ const arrayData = useArrayData($props.dataKey || $props.module, {
|
|||
skip: 0,
|
||||
});
|
||||
const { store } = arrayData;
|
||||
const entity = computed(() =>Array.isArray( store.data) ? store.data[0] : store.data);
|
||||
const entity = computed(() => (Array.isArray(store.data) ? store.data[0] : store.data));
|
||||
const isLoading = ref(false);
|
||||
|
||||
defineExpose({
|
||||
|
@ -55,14 +54,12 @@ defineExpose({
|
|||
});
|
||||
onBeforeMount(async () => {
|
||||
await getData();
|
||||
watch(
|
||||
() => $props.url,
|
||||
async () => await getData()
|
||||
);
|
||||
watch($props, async () => await getData());
|
||||
});
|
||||
|
||||
async function getData() {
|
||||
store.url = $props.url;
|
||||
store.filter = $props.filter ?? {};
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const { data } = await arrayData.fetch({ append: false, updateRouter: false });
|
||||
|
@ -117,7 +114,7 @@ const emit = defineEmits(['onFetch']);
|
|||
icon="more_vert"
|
||||
round
|
||||
size="md"
|
||||
:class="{ invisible: !slots.menu }"
|
||||
:class="{ invisible: !$slots.menu }"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('components.cardDescriptor.moreOptions') }}
|
||||
|
|
|
@ -15,7 +15,7 @@ const props = defineProps({
|
|||
default: null,
|
||||
},
|
||||
entityId: {
|
||||
type: Number,
|
||||
type: [Number, String],
|
||||
default: null,
|
||||
},
|
||||
dataKey: {
|
||||
|
@ -32,7 +32,7 @@ const arrayData = useArrayData(props.dataKey || route.meta.moduleName, {
|
|||
skip: 0,
|
||||
});
|
||||
const { store } = arrayData;
|
||||
const entity = computed(() => Array.isArray(store.data) ? store.data[0] : store.data);
|
||||
const entity = computed(() => (Array.isArray(store.data) ? store.data[0] : store.data));
|
||||
const isLoading = ref(false);
|
||||
|
||||
defineExpose({
|
||||
|
@ -48,11 +48,28 @@ onBeforeMount(async () => {
|
|||
|
||||
async function fetch() {
|
||||
store.url = props.url;
|
||||
store.filter = props.filter ?? {};
|
||||
isLoading.value = true;
|
||||
const { data } = await arrayData.fetch({ append: false, updateRouter: false });
|
||||
emit('onFetch', data);
|
||||
emit('onFetch', Array.isArray(data) ? data[0] : data);
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
const showRedirectToSummaryIcon = computed(() => {
|
||||
const exist = existSummary(route.matched);
|
||||
return !isSummary.value && route.meta.moduleName && exist;
|
||||
});
|
||||
|
||||
function existSummary(routes) {
|
||||
const hasSummary = routes.some((r) => r.name === `${route.meta.moduleName}Summary`);
|
||||
if (hasSummary) return hasSummary;
|
||||
for (const current of routes) {
|
||||
if (current.path != '/' && current.children) {
|
||||
const exist = existSummary(current.children);
|
||||
if (exist) return exist;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -63,7 +80,7 @@ async function fetch() {
|
|||
<div class="summaryHeader bg-primary q-pa-sm text-weight-bolder">
|
||||
<slot name="header-left">
|
||||
<router-link
|
||||
v-if="!isSummary && route.meta.moduleName"
|
||||
v-if="showRedirectToSummaryIcon"
|
||||
class="header link"
|
||||
:to="{
|
||||
name: `${route.meta.moduleName}Summary`,
|
||||
|
@ -134,6 +151,9 @@ async function fetch() {
|
|||
box-shadow: none;
|
||||
|
||||
.vn-label-value {
|
||||
&.negative > .value span {
|
||||
color: $alert;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 2px;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<script setup>
|
||||
defineProps({
|
||||
import { computed } from 'vue';
|
||||
|
||||
const $props = defineProps({
|
||||
maxLength: {
|
||||
type: Number,
|
||||
required: true,
|
||||
|
@ -8,53 +10,40 @@ defineProps({
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
tag: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'tag',
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'value',
|
||||
},
|
||||
});
|
||||
const tags = computed(() => {
|
||||
return Object.keys($props.item)
|
||||
.filter((i) => i.startsWith(`${$props.tag}`))
|
||||
.reduce((acc, tag) => {
|
||||
const n = tag.split(`${$props.tag}`)[1];
|
||||
const key = `${$props.tag}${n}`;
|
||||
const value = `${$props.value}${n}`;
|
||||
acc[$props.item[key] ?? key] = $props.item[value] ?? '';
|
||||
return acc;
|
||||
}, {});
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="fetchedTags">
|
||||
<div class="wrap">
|
||||
<div
|
||||
v-for="(val, key) in tags"
|
||||
:key="key"
|
||||
class="inline-tag"
|
||||
:class="{ empty: !$props.item.value5 }"
|
||||
:title="$props.item.tag5 + ': ' + $props.item.value5"
|
||||
:title="`${key}: ${val}`"
|
||||
:class="{ empty: !val }"
|
||||
>
|
||||
{{ $props.item.value5 }}
|
||||
</div>
|
||||
<div
|
||||
class="inline-tag"
|
||||
:class="{ empty: !$props.item.tag6 }"
|
||||
:title="$props.item.tag6 + ': ' + $props.item.value6"
|
||||
>
|
||||
{{ $props.item.value6 }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="inline-tag"
|
||||
:class="{ empty: !$props.item.value7 }"
|
||||
:title="$props.item.tag7 + ': ' + $props.item.value7"
|
||||
>
|
||||
{{ $props.item.value7 }}
|
||||
</div>
|
||||
<div
|
||||
class="inline-tag"
|
||||
:class="{ empty: !$props.item.value8 }"
|
||||
:title="$props.item.tag8 + ': ' + $props.item.value8"
|
||||
>
|
||||
{{ $props.item.value8 }}
|
||||
</div>
|
||||
<div
|
||||
class="inline-tag"
|
||||
:class="{ empty: !$props.item.value9 }"
|
||||
:title="$props.item.tag9 + ': ' + $props.item.value9"
|
||||
>
|
||||
{{ $props.item.value9 }}
|
||||
</div>
|
||||
<div
|
||||
class="inline-tag"
|
||||
:class="{ empty: !$props.item.value10 }"
|
||||
:title="$props.item.tag10 + ': ' + $props.item.value10"
|
||||
>
|
||||
{{ $props.item.value10 }}
|
||||
{{ val }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -72,7 +61,7 @@ defineProps({
|
|||
.inline-tag {
|
||||
height: 1rem;
|
||||
margin: 0.05rem;
|
||||
color: $secondary;
|
||||
color: $color-font-secondary;
|
||||
text-align: center;
|
||||
font-size: smaller;
|
||||
padding: 1px;
|
||||
|
@ -83,9 +72,8 @@ defineProps({
|
|||
min-width: 4rem;
|
||||
max-width: 4rem;
|
||||
}
|
||||
|
||||
.empty {
|
||||
border: 1px solid $color-spacer-light;
|
||||
border: 1px solid #2b2b2b;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useQuasar } from 'quasar';
|
||||
import '@quasar/quasar-ui-qcalendar/src/QCalendarVariables.sass';
|
||||
|
||||
const $props = defineProps({
|
||||
bordered: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
transparentBackground: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
viewCustomization: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const $q = useQuasar();
|
||||
|
||||
// El objetivo de asignar las clases de personalización desde el wrapper es no tener conflictos entre vistas que usen el mismo componente
|
||||
const viewCustomizationClasses = {
|
||||
workerCalendar: 'worker-calendar-customizations',
|
||||
};
|
||||
|
||||
const containerClasses = computed(() => {
|
||||
const classes = ['main-container-background'];
|
||||
if (viewCustomizationClasses[$props.viewCustomization])
|
||||
classes.push(viewCustomizationClasses[$props.viewCustomization]);
|
||||
if ($props.bordered) classes.push('--bordered');
|
||||
if ($props.transparentBackground) classes.push('transparent-background');
|
||||
else classes.push($q.dark.isActive ? '--dark' : '--light');
|
||||
return classes;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="containerClasses">
|
||||
<div class="nav-container row"><slot name="header" /></div>
|
||||
<slot name="calendar" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../css/quasar.variables.scss';
|
||||
|
||||
:root {
|
||||
// Cambia los colores del día actual del calendario por los de salix
|
||||
--calendar-border-current-dark: #84d0e2 2px solid;
|
||||
--calendar-border-current: #84d0e2 2px solid;
|
||||
--calendar-current-color-dark: #84d0e2;
|
||||
// Colores de fondo del calendario en dark mode
|
||||
--calendar-outside-background-dark: #222;
|
||||
--calendar-background-dark: #222;
|
||||
}
|
||||
|
||||
// Clases para modificar el color de fecha seleccionada en componente QCalendarMonth
|
||||
.q-dark div .q-calendar-mini .q-calendar-month__day.q-selected .q-calendar__button {
|
||||
background-color: $primary !important;
|
||||
}
|
||||
|
||||
.q-calendar-mini .q-calendar-month__day.q-selected .q-calendar__button {
|
||||
background-color: $primary !important;
|
||||
}
|
||||
|
||||
.q-calendar-month__head--weekday {
|
||||
// Transforma los nombres de los días de la semana a mayúsculas
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.transparent-background {
|
||||
--calendar-background-dark: transparent;
|
||||
--calendar-background: transparent;
|
||||
--calendar-outside-background-dark: transparent;
|
||||
}
|
||||
|
||||
.q-calendar__button {
|
||||
&:hover {
|
||||
background-color: var(--vn-accent-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.main-container-background {
|
||||
--calendar-current-background-dark: transparent;
|
||||
|
||||
&.--dark {
|
||||
background-color: var(--calendar-background-dark);
|
||||
}
|
||||
|
||||
&.--light {
|
||||
background-color: var(--calendar-background);
|
||||
}
|
||||
|
||||
&.--bordered {
|
||||
border: 1px solid #222;
|
||||
}
|
||||
}
|
||||
|
||||
.worker-calendar-customizations {
|
||||
.q-calendar__button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 13px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--vn-label-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.q-calendar-month__week--days > div:nth-child(6),
|
||||
.q-calendar-month__week--days > div:nth-child(7) {
|
||||
// Cambia el color de los días sábado y domingo
|
||||
color: #777777;
|
||||
}
|
||||
|
||||
.q-calendar-month__week--wrapper {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.q-calendar-month__workweek {
|
||||
height: 32px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.q-calendar__button--bordered {
|
||||
color: $info !important;
|
||||
}
|
||||
|
||||
.q-calendar-month__day--content {
|
||||
position: absolute;
|
||||
top: 1;
|
||||
left: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.q-outside .calendar-event {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.q-calendar-month__workweek,
|
||||
.q-calendar-month__head--workweek,
|
||||
.q-calendar-month__head--weekday.q-calendar__center.q-calendar__ellipsis {
|
||||
text-transform: capitalize;
|
||||
color: var(---color-font-secondary);
|
||||
font-weight: bold;
|
||||
font-size: 0.8rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.--bordered {
|
||||
border: 1px solid black;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,28 +1,16 @@
|
|||
<template>
|
||||
<div class="q-pa-md">
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<QSkeleton type="QInput" square />
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<QSkeleton type="QInput" square />
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<QSkeleton type="QInput" square />
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="row q-gutter-md">
|
||||
<QSkeleton type="QBtn" />
|
||||
|
|
|
@ -3,46 +3,36 @@
|
|||
<QSkeleton type="rect" square />
|
||||
</div>
|
||||
<div class="row q-pa-md q-col-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QSkeleton type="rect" class="q-mb-md" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="rect" class="q-mb-md" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="rect" class="q-mb-md" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="rect" class="q-mb-md" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="rect" class="q-mb-md" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
</div>
|
||||
<QSkeleton type="rect" class="q-mb-md" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="rect" class="q-mb-md" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="rect" class="q-mb-md" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="rect" class="q-mb-md" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="rect" class="q-mb-md" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
<QSkeleton type="text" square />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,45 +1,20 @@
|
|||
<template>
|
||||
<div class="q-pa-md w">
|
||||
<div class="row q-gutter-md q-mb-md">
|
||||
<div class="col-1">
|
||||
<QSkeleton type="rect" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="rect" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="rect" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="rect" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="rect" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="rect" square />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row q-gutter-md q-mb-md" v-for="n in 5" :key="n">
|
||||
<div class="col-1">
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
<QSkeleton type="rect" square />
|
||||
<QSkeleton type="rect" square />
|
||||
<QSkeleton type="rect" square />
|
||||
<QSkeleton type="rect" square />
|
||||
<QSkeleton type="rect" square />
|
||||
<QSkeleton type="rect" square />
|
||||
</div>
|
||||
<div class="row q-gutter-md q-mb-md" v-for="n in 5" :key="n">
|
||||
<QSkeleton type="QInput" square />
|
||||
<QSkeleton type="QInput" square />
|
||||
<QSkeleton type="QInput" square />
|
||||
<QSkeleton type="QInput" square />
|
||||
<QSkeleton type="QInput" square />
|
||||
<QSkeleton type="QInput" square />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
|
|||
import { useArrayData } from 'composables/useArrayData';
|
||||
import { useRoute } from 'vue-router';
|
||||
import toDate from 'filters/toDate';
|
||||
|
||||
import useRedirect from 'src/composables/useRedirect';
|
||||
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
@ -56,6 +56,7 @@ const arrayData = useArrayData(props.dataKey, {
|
|||
const route = useRoute();
|
||||
const store = arrayData.store;
|
||||
const userParams = ref({});
|
||||
const { navigate } = useRedirect();
|
||||
|
||||
onMounted(() => {
|
||||
if (props.params) userParams.value = JSON.parse(JSON.stringify(props.params));
|
||||
|
@ -79,6 +80,7 @@ watch(
|
|||
|
||||
const isLoading = ref(false);
|
||||
async function search() {
|
||||
store.filter.where = {};
|
||||
isLoading.value = true;
|
||||
const params = { ...userParams.value };
|
||||
store.userParamsChanged = true;
|
||||
|
@ -91,6 +93,7 @@ async function search() {
|
|||
|
||||
isLoading.value = false;
|
||||
emit('search');
|
||||
navigate(store.data, {});
|
||||
}
|
||||
|
||||
async function reload() {
|
||||
|
@ -101,6 +104,7 @@ async function reload() {
|
|||
if (!props.showAll && !params.length) store.data = [];
|
||||
isLoading.value = false;
|
||||
emit('refresh');
|
||||
navigate(store.data, {});
|
||||
}
|
||||
|
||||
async function clearFilters() {
|
||||
|
@ -146,7 +150,7 @@ const customTags = computed(() =>
|
|||
|
||||
async function remove(key) {
|
||||
userParams.value[key] = null;
|
||||
await search();
|
||||
await arrayData.applyFilter({ params: userParams.value });
|
||||
emit('remove', key);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const props = defineProps({
|
||||
phoneNumber: { type: [String, Number], default: null },
|
||||
});
|
||||
const { t } = useI18n();
|
||||
defineProps({ phoneNumber: { type: [String, Number], default: null } });
|
||||
</script>
|
||||
<template>
|
||||
<QBtn
|
||||
v-if="props.phoneNumber"
|
||||
v-if="phoneNumber"
|
||||
flat
|
||||
round
|
||||
icon="phone"
|
||||
size="sm"
|
||||
color="primary"
|
||||
padding="none"
|
||||
:href="`sip:${props.phoneNumber}`"
|
||||
:href="`sip:${phoneNumber}`"
|
||||
@click.stop
|
||||
/>
|
||||
</template>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<script setup>
|
||||
import VnAvatar from 'src/components/ui/VnAvatar.vue';
|
||||
import { toDateHour } from 'src/filters';
|
||||
import { toDateHourMin } from 'src/filters';
|
||||
import { ref } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import VnPaginate from './VnPaginate.vue';
|
||||
import VnUserLink from '../ui/VnUserLink.vue';
|
||||
import { useState } from 'src/composables/useState';
|
||||
|
||||
const $props = defineProps({
|
||||
url: { type: String, default: null },
|
||||
|
@ -13,8 +14,10 @@ const $props = defineProps({
|
|||
body: { type: Object, default: () => {} },
|
||||
addNote: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const noteModal = ref(false);
|
||||
const state = useState();
|
||||
const currentUser = ref(state.getUser());
|
||||
const newNote = ref('');
|
||||
const vnPaginateRef = ref();
|
||||
|
||||
|
@ -22,98 +25,83 @@ async function insert() {
|
|||
const body = $props.body;
|
||||
Object.assign(body, { text: newNote.value });
|
||||
await axios.post($props.url, body);
|
||||
vnPaginateRef.value.fetch();
|
||||
await vnPaginateRef.value.fetch();
|
||||
newNote.value = '';
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="column items-center full-height full-width">
|
||||
<VnPaginate
|
||||
:data-key="$props.url"
|
||||
:url="$props.url"
|
||||
order="created DESC"
|
||||
:limit="20"
|
||||
:filter="$props.filter"
|
||||
auto-load
|
||||
ref="vnPaginateRef"
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<div class="column items-center full-width">
|
||||
<QCard
|
||||
class="q-pa-xs q-mb-sm full-width"
|
||||
v-for="(note, index) in rows"
|
||||
:key="index"
|
||||
>
|
||||
<QCardSection horizontal>
|
||||
<slot name="picture">
|
||||
<VnAvatar
|
||||
:descriptor="false"
|
||||
:worker-id="note.workerFk"
|
||||
size="md"
|
||||
/>
|
||||
</slot>
|
||||
<div class="full-width row justify-between q-pa-xs">
|
||||
<VnUserLink
|
||||
:name="`${note.worker.user.nickname}`"
|
||||
:worker-id="note.worker.id"
|
||||
/>
|
||||
|
||||
<slot name="actions">
|
||||
{{ toDateHour(note.created) }}
|
||||
</slot>
|
||||
</div>
|
||||
</QCardSection>
|
||||
<QCardSection class="q-pa-xs q-my-none q-py-none">
|
||||
<slot name="text">
|
||||
{{ note.text }}
|
||||
</slot>
|
||||
</QCardSection>
|
||||
</QCard>
|
||||
</div>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
<QPageSticky position="bottom-right" :offset="[25, 25]" v-if="addNote">
|
||||
<QBtn color="primary" icon="add" size="lg" round @click="noteModal = true" />
|
||||
</QPageSticky>
|
||||
<QDialog v-model="noteModal" @hide="newNote = ''">
|
||||
<QCard>
|
||||
<QCardSection>
|
||||
<QItem class="q-px-none">
|
||||
<span class="text-primary text-h6 full-width">
|
||||
<QIcon name="draft" class="q-mr-xs" />
|
||||
{{ t('Add note') }}
|
||||
</span>
|
||||
<QBtn icon="close" flat round dense v-close-popup />
|
||||
</QItem>
|
||||
</QCardSection>
|
||||
<QCardSection>
|
||||
<QInput
|
||||
autofocus
|
||||
type="textarea"
|
||||
:label="t('Add note here...')"
|
||||
filled
|
||||
size="lg"
|
||||
autogrow
|
||||
v-model="newNote"
|
||||
></QInput>
|
||||
</QCardSection>
|
||||
<QCardActions class="justify-end q-mr-sm">
|
||||
<QBtn
|
||||
<QCard class="q-pa-xs q-mb-xl full-width" v-if="$props.addNote">
|
||||
<QCardSection horizontal>
|
||||
<VnAvatar :worker-id="currentUser.id" size="md" />
|
||||
<div class="full-width row justify-between q-pa-xs">
|
||||
<VnUserLink :name="t('New note')" :worker-id="currentUser.id" />
|
||||
{{ t('globals.now') }}
|
||||
</div>
|
||||
</QCardSection>
|
||||
<QCardSection class="q-pa-xs q-my-none q-py-none" horizontal>
|
||||
<QInput
|
||||
v-model="newNote"
|
||||
class="full-width"
|
||||
type="textarea"
|
||||
:label="t('Add note here...')"
|
||||
filled
|
||||
size="lg"
|
||||
autogrow
|
||||
autofocus
|
||||
@keyup.ctrl.enter.stop="insert"
|
||||
clearable
|
||||
>
|
||||
<template #append
|
||||
><QBtn
|
||||
:title="t('Save (ctrl + Enter)')"
|
||||
icon="save"
|
||||
color="primary"
|
||||
flat
|
||||
:label="t('globals.close')"
|
||||
color="primary"
|
||||
v-close-popup
|
||||
/>
|
||||
<QBtn
|
||||
:label="t('globals.save')"
|
||||
color="primary"
|
||||
v-close-popup
|
||||
@click="insert"
|
||||
/>
|
||||
</QCardActions>
|
||||
</QCard>
|
||||
</QDialog>
|
||||
</div>
|
||||
</template>
|
||||
</QInput>
|
||||
</QCardSection>
|
||||
</QCard>
|
||||
<VnPaginate
|
||||
:data-key="$props.url"
|
||||
:url="$props.url"
|
||||
order="created DESC"
|
||||
:limit="0"
|
||||
:filter="$props.filter"
|
||||
auto-load
|
||||
ref="vnPaginateRef"
|
||||
class="show"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<TransitionGroup name="list" tag="div" class="column items-center full-width">
|
||||
<QCard
|
||||
class="q-pa-xs q-mb-sm full-width"
|
||||
v-for="(note, index) in rows"
|
||||
:key="note.id ?? index"
|
||||
>
|
||||
<QCardSection horizontal>
|
||||
<VnAvatar
|
||||
:descriptor="false"
|
||||
:worker-id="note.workerFk"
|
||||
size="md"
|
||||
/>
|
||||
<div class="full-width row justify-between q-pa-xs">
|
||||
<VnUserLink
|
||||
:name="`${note.worker.user.nickname}`"
|
||||
:worker-id="note.worker.id"
|
||||
/>
|
||||
{{ toDateHourMin(note.created) }}
|
||||
</div>
|
||||
</QCardSection>
|
||||
<QCardSection class="q-pa-xs q-my-none q-py-none">
|
||||
{{ note.text }}
|
||||
</QCardSection>
|
||||
</QCard>
|
||||
</TransitionGroup>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.q-card {
|
||||
|
@ -128,9 +116,20 @@ async function insert() {
|
|||
.q-dialog .q-card {
|
||||
width: 400px;
|
||||
}
|
||||
.list-enter-active,
|
||||
.list-leave-active {
|
||||
transition: all 1s ease;
|
||||
}
|
||||
.list-enter-from,
|
||||
.list-leave-to {
|
||||
opacity: 0;
|
||||
background-color: $primary;
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
es:
|
||||
Add note here...: Añadir nota aquí...
|
||||
Add note: Añadir nota
|
||||
New note: Nueva nota
|
||||
Save (ctrl + Enter): Guardar (Ctrl + Intro)
|
||||
|
||||
</i18n>
|
||||
|
|
|
@ -42,6 +42,10 @@ const props = defineProps({
|
|||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
keepOpts: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
offset: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
|
@ -76,8 +80,8 @@ const arrayData = useArrayData(props.dataKey, {
|
|||
order: props.order,
|
||||
userParams: props.userParams,
|
||||
exprBuilder: props.exprBuilder,
|
||||
keepOpts: props.keepOpts,
|
||||
});
|
||||
const hasMoreData = ref();
|
||||
const store = arrayData.store;
|
||||
|
||||
onMounted(() => {
|
||||
|
@ -96,8 +100,10 @@ const addFilter = async (filter, params) => {
|
|||
};
|
||||
|
||||
async function fetch() {
|
||||
store.filter.skip = 0;
|
||||
store.skip = 0;
|
||||
await arrayData.fetch({ append: false });
|
||||
if (!arrayData.hasMoreData.value) {
|
||||
if (!store.hasMoreData) {
|
||||
isLoading.value = false;
|
||||
}
|
||||
emit('onFetch', store.data);
|
||||
|
@ -110,8 +116,8 @@ async function paginate() {
|
|||
|
||||
isLoading.value = true;
|
||||
await arrayData.loadMore();
|
||||
if (!arrayData.hasMoreData.value) {
|
||||
if (store.userParamsChanged) arrayData.hasMoreData.value = true;
|
||||
if (!store.hasMoreData) {
|
||||
if (store.userParamsChanged) store.hasMoreData = true;
|
||||
store.userParamsChanged = false;
|
||||
endPagination();
|
||||
return;
|
||||
|
@ -127,15 +133,12 @@ async function paginate() {
|
|||
}
|
||||
|
||||
function endPagination() {
|
||||
hasMoreData.value = arrayData.hasMoreData.value;
|
||||
isLoading.value = false;
|
||||
emit('onFetch', store.data);
|
||||
emit('onPaginate');
|
||||
}
|
||||
async function onLoad(index, done) {
|
||||
if (!store.data) {
|
||||
return done();
|
||||
}
|
||||
if (!store.data) return done();
|
||||
|
||||
if (store.data.length === 0 || !props.url) return done(false);
|
||||
|
||||
|
@ -143,7 +146,7 @@ async function onLoad(index, done) {
|
|||
|
||||
await paginate();
|
||||
let isDone = false;
|
||||
if (store.userParamsChanged) isDone = !arrayData.hasMoreData.value;
|
||||
if (store.userParamsChanged) isDone = !store.hasMoreData;
|
||||
done(isDone);
|
||||
}
|
||||
|
||||
|
@ -187,8 +190,8 @@ defineExpose({ fetch, addFilter });
|
|||
v-if="store.data"
|
||||
@load="onLoad"
|
||||
:offset="offset"
|
||||
:disable="disableInfiniteScroll || !arrayData.hasMoreData.value"
|
||||
class="full-width"
|
||||
:disable="disableInfiniteScroll || !store.hasMoreData"
|
||||
v-bind="$attrs"
|
||||
>
|
||||
<slot name="body" :rows="store.data"></slot>
|
||||
|
@ -196,7 +199,10 @@ defineExpose({ fetch, addFilter });
|
|||
<QSpinner color="orange" size="md" />
|
||||
</div>
|
||||
</QInfiniteScroll>
|
||||
<div v-if="!isLoading && hasMoreData" class="w-full flex justify-center q-mt-md">
|
||||
<div
|
||||
v-if="!isLoading && store.hasMoreData"
|
||||
class="w-full flex justify-center q-mt-md"
|
||||
>
|
||||
<QBtn color="primary" :label="t('Load more data')" @click="paginate()" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
<script setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import useRedirect from 'src/composables/useRedirect';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const quasar = useQuasar();
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps({
|
||||
dataKey: {
|
||||
|
@ -65,10 +67,10 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const arrayData = useArrayData(props.dataKey, { ...props });
|
||||
const { store } = arrayData;
|
||||
const searchText = ref('');
|
||||
const { navigate } = useRedirect();
|
||||
|
||||
onMounted(() => {
|
||||
const params = store.userParams;
|
||||
|
@ -81,34 +83,20 @@ async function search() {
|
|||
const staticParams = Object.entries(store.userParams).filter(
|
||||
([key, value]) => value && (props.staticParams || []).includes(key)
|
||||
);
|
||||
store.skip = 0;
|
||||
await arrayData.applyFilter({
|
||||
params: {
|
||||
...Object.fromEntries(staticParams),
|
||||
search: searchText.value,
|
||||
},
|
||||
});
|
||||
|
||||
if (!props.redirect) return;
|
||||
|
||||
if (props.customRouteRedirectName)
|
||||
return router.push({
|
||||
name: props.customRouteRedirectName,
|
||||
params: { id: searchText.value },
|
||||
});
|
||||
|
||||
const { matched: matches } = router.currentRoute.value;
|
||||
const { path } = matches.at(-1);
|
||||
const [, moduleName] = path.split('/');
|
||||
|
||||
if (!store.data.length || store.data.length > 1)
|
||||
return router.push({ path: `/${moduleName}/list` });
|
||||
|
||||
const targetId = store.data[0].id;
|
||||
let targetUrl;
|
||||
|
||||
if (path.endsWith('/list')) targetUrl = path.replace('/list', `/${targetId}/summary`);
|
||||
else if (path.includes(':id')) targetUrl = path.replace(':id', targetId);
|
||||
|
||||
await router.push({ path: targetUrl });
|
||||
navigate(store.data, {
|
||||
customRouteRedirectName: props.customRouteRedirectName,
|
||||
searchText: searchText.value,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -117,7 +105,7 @@ async function search() {
|
|||
<VnInput
|
||||
id="searchbar"
|
||||
v-model="searchText"
|
||||
:placeholder="props.label"
|
||||
:placeholder="t(props.label)"
|
||||
dense
|
||||
standout
|
||||
autofocus
|
||||
|
@ -131,19 +119,12 @@ async function search() {
|
|||
/>
|
||||
</template>
|
||||
<template #append>
|
||||
<QIcon
|
||||
v-if="searchText !== ''"
|
||||
name="close"
|
||||
@click="searchText = ''"
|
||||
class="cursor-pointer"
|
||||
/>
|
||||
|
||||
<QIcon
|
||||
v-if="props.info && $q.screen.gt.xs"
|
||||
name="info"
|
||||
class="cursor-info"
|
||||
>
|
||||
<QTooltip>{{ props.info }}</QTooltip>
|
||||
<QTooltip>{{ t(props.info) }}</QTooltip>
|
||||
</QIcon>
|
||||
</template>
|
||||
</VnInput>
|
||||
|
|
|
@ -1,11 +1,27 @@
|
|||
<script setup>
|
||||
import { onMounted, onUnmounted } from 'vue';
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const actions = ref(null);
|
||||
const data = ref(null);
|
||||
const opts = { subtree: true, childList: true, attributes: true };
|
||||
const hasContent = ref(false);
|
||||
|
||||
onMounted(() => {
|
||||
stateStore.toggleSubToolbar();
|
||||
actions.value = document.querySelector('#st-actions');
|
||||
data.value = document.querySelector('#st-data');
|
||||
|
||||
if (!actions.value && !data.value) return;
|
||||
|
||||
// Check if there's content to display
|
||||
const observer = new MutationObserver(
|
||||
() =>
|
||||
(hasContent.value =
|
||||
actions.value.childNodes.length + data.value.childNodes.length)
|
||||
);
|
||||
if (actions.value) observer.observe(actions.value, opts);
|
||||
if (data.value) observer.observe(data.value, opts);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
|
@ -14,7 +30,10 @@ onUnmounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<QToolbar class="bg-vn-section-color justify-end sticky">
|
||||
<QToolbar
|
||||
class="justify-end sticky"
|
||||
v-show="hasContent || $slots['st-actions'] || $slots['st-data']"
|
||||
>
|
||||
<slot name="st-data">
|
||||
<div id="st-data"></div>
|
||||
</slot>
|
||||
|
|
|
@ -90,7 +90,7 @@ const onNodeCreated = async () => {
|
|||
await fetchNodeLeaves(creationNodeSelectedId.value);
|
||||
};
|
||||
|
||||
onMounted(async (n) => {
|
||||
onMounted(async () => {
|
||||
const tree = [...state.get('Tree'), 1];
|
||||
const lastStateTree = state.get('TreeState');
|
||||
if (tree) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script setup>
|
||||
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const $props = defineProps({
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
export const elementIsVisibleInViewport = (el) => {
|
||||
const { top, left, bottom, right } = el.getBoundingClientRect();
|
||||
const { innerHeight, innerWidth } = window;
|
||||
|
||||
return top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth;
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
export function getDateQBadgeColor(date) {
|
||||
let today = Date.vnNew();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
let timeTicket = new Date(date);
|
||||
timeTicket.setHours(0, 0, 0, 0);
|
||||
|
||||
let comparation = today - timeTicket;
|
||||
|
||||
if (comparation == 0) return 'warning';
|
||||
if (comparation < 0) return 'negative';
|
||||
}
|
|
@ -9,12 +9,9 @@ const arrayDataStore = useArrayDataStore();
|
|||
export function useArrayData(key, userOptions) {
|
||||
if (!key) throw new Error('ArrayData: A key is required to use this composable');
|
||||
|
||||
if (!arrayDataStore.get(key)) {
|
||||
arrayDataStore.set(key);
|
||||
}
|
||||
if (!arrayDataStore.get(key)) arrayDataStore.set(key);
|
||||
|
||||
const store = arrayDataStore.get(key);
|
||||
const hasMoreData = ref(false);
|
||||
const route = useRoute();
|
||||
let canceller = null;
|
||||
|
||||
|
@ -22,6 +19,7 @@ export function useArrayData(key, userOptions) {
|
|||
|
||||
onMounted(() => {
|
||||
setOptions();
|
||||
store.skip = 0;
|
||||
|
||||
const query = route.query;
|
||||
if (query.params) {
|
||||
|
@ -29,9 +27,7 @@ export function useArrayData(key, userOptions) {
|
|||
}
|
||||
});
|
||||
|
||||
if (key && userOptions) {
|
||||
setOptions();
|
||||
}
|
||||
if (key && userOptions) setOptions();
|
||||
|
||||
function setOptions() {
|
||||
const allowedOptions = [
|
||||
|
@ -51,7 +47,10 @@ export function useArrayData(key, userOptions) {
|
|||
if (isEmpty || !allowedOptions.includes(option)) continue;
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(store, option)) {
|
||||
store[option] = userOptions[option];
|
||||
const defaultOpts = userOptions[option];
|
||||
store[option] = userOptions.keepOpts?.includes(option)
|
||||
? Object.assign(defaultOpts, store[option])
|
||||
: defaultOpts;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -96,8 +95,7 @@ export function useArrayData(key, userOptions) {
|
|||
});
|
||||
|
||||
const { limit } = filter;
|
||||
|
||||
hasMoreData.value = response.data.length >= limit;
|
||||
store.hasMoreData = limit && response.data.length >= limit;
|
||||
|
||||
if (append) {
|
||||
if (!store.data) store.data = [];
|
||||
|
@ -132,7 +130,8 @@ export function useArrayData(key, userOptions) {
|
|||
store.filter = {};
|
||||
if (params) store.userParams = Object.assign({}, params);
|
||||
|
||||
await fetch({ append: false });
|
||||
const response = await fetch({ append: false });
|
||||
return response;
|
||||
}
|
||||
|
||||
async function addFilter({ filter, params }) {
|
||||
|
@ -156,9 +155,10 @@ export function useArrayData(key, userOptions) {
|
|||
delete store.userParams[param];
|
||||
delete params[param];
|
||||
if (store.filter?.where) {
|
||||
delete store.filter.where[
|
||||
Object.keys(exprBuilder ? exprBuilder(param) : param)[0]
|
||||
];
|
||||
const key = Object.keys(
|
||||
exprBuilder && exprBuilder(param) ? exprBuilder(param) : param
|
||||
);
|
||||
if (key[0]) delete store.filter.where[key[0]];
|
||||
if (Object.keys(store.filter.where).length === 0) {
|
||||
delete store.filter.where;
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ export function useArrayData(key, userOptions) {
|
|||
}
|
||||
|
||||
async function loadMore() {
|
||||
if (!hasMoreData.value) return;
|
||||
if (!store.hasMoreData) return;
|
||||
|
||||
store.skip = store.limit * page.value;
|
||||
page.value += 1;
|
||||
|
@ -211,7 +211,6 @@ export function useArrayData(key, userOptions) {
|
|||
destroy,
|
||||
loadMore,
|
||||
store,
|
||||
hasMoreData,
|
||||
totalRows,
|
||||
updateStateParams,
|
||||
isLoading,
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import { useRouter } from 'vue-router';
|
||||
|
||||
export default function useRedirect() {
|
||||
const router = useRouter();
|
||||
|
||||
const navigate = (data, { customRouteRedirectName, searchText }) => {
|
||||
if (customRouteRedirectName)
|
||||
return router.push({
|
||||
name: customRouteRedirectName,
|
||||
params: { id: searchText },
|
||||
});
|
||||
|
||||
const { matched: matches } = router.currentRoute.value;
|
||||
const { path } = matches.at(-1);
|
||||
|
||||
const to =
|
||||
data.length === 1
|
||||
? path.replace(/\/(list|:id)|-list/, `/${data[0].id}`)
|
||||
: path.replace(/:id.*/, '');
|
||||
|
||||
router.push({ path: to });
|
||||
};
|
||||
|
||||
return { navigate };
|
||||
}
|
|
@ -3,31 +3,46 @@ import { useRole } from './useRole';
|
|||
import { useUserConfig } from './useUserConfig';
|
||||
import axios from 'axios';
|
||||
import useNotify from './useNotify';
|
||||
import { useTokenConfig } from './useTokenConfig';
|
||||
const TOKEN_MULTIMEDIA = 'tokenMultimedia';
|
||||
const TOKEN = 'token';
|
||||
|
||||
export function useSession() {
|
||||
const { notify } = useNotify();
|
||||
let isCheckingToken = false;
|
||||
let intervalId = null;
|
||||
|
||||
function getToken() {
|
||||
const localToken = localStorage.getItem('token');
|
||||
const sessionToken = sessionStorage.getItem('token');
|
||||
const localToken = localStorage.getItem(TOKEN);
|
||||
const sessionToken = sessionStorage.getItem(TOKEN);
|
||||
|
||||
return localToken || sessionToken || '';
|
||||
}
|
||||
function getTokenMultimedia() {
|
||||
const localTokenMultimedia = localStorage.getItem('token'); // Temporal
|
||||
const sessionTokenMultimedia = sessionStorage.getItem('token'); // Temporal
|
||||
const localTokenMultimedia = localStorage.getItem(TOKEN_MULTIMEDIA);
|
||||
const sessionTokenMultimedia = sessionStorage.getItem(TOKEN_MULTIMEDIA);
|
||||
|
||||
return localTokenMultimedia || sessionTokenMultimedia || '';
|
||||
}
|
||||
|
||||
function setToken(data) {
|
||||
if (data.keepLogin) {
|
||||
localStorage.setItem('token', data.token);
|
||||
localStorage.setItem('tokenMultimedia', data.tokenMultimedia);
|
||||
} else {
|
||||
sessionStorage.setItem('token', data.token);
|
||||
sessionStorage.setItem('tokenMultimedia', data.tokenMultimedia);
|
||||
}
|
||||
function setSession(data) {
|
||||
let keepLogin = data.keepLogin;
|
||||
const storage = keepLogin ? localStorage : sessionStorage;
|
||||
storage.setItem(TOKEN, data.token);
|
||||
storage.setItem(TOKEN_MULTIMEDIA, data.tokenMultimedia);
|
||||
storage.setItem('created', data.created);
|
||||
storage.setItem('ttl', data.ttl);
|
||||
sessionStorage.setItem('keepLogin', keepLogin);
|
||||
}
|
||||
|
||||
function keepLogin() {
|
||||
return sessionStorage.getItem('keepLogin');
|
||||
}
|
||||
|
||||
function setToken({ token, tokenMultimedia }) {
|
||||
const storage = keepLogin() ? localStorage : sessionStorage;
|
||||
storage.setItem(TOKEN, token);
|
||||
storage.setItem(TOKEN_MULTIMEDIA, tokenMultimedia);
|
||||
}
|
||||
async function destroyToken(url, storage, key) {
|
||||
if (storage.getItem(key)) {
|
||||
|
@ -47,11 +62,15 @@ export function useSession() {
|
|||
tokenMultimedia: 'Accounts/logout',
|
||||
token: 'VnUsers/logout',
|
||||
};
|
||||
const storage = keepLogin() ? localStorage : sessionStorage;
|
||||
|
||||
for (const [key, url] of Object.entries(tokens)) {
|
||||
await destroyToken(url, localStorage, key);
|
||||
await destroyToken(url, sessionStorage, key);
|
||||
await destroyToken(url, storage, key);
|
||||
}
|
||||
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
|
||||
const { setUser } = useState();
|
||||
|
||||
setUser({
|
||||
|
@ -61,22 +80,75 @@ export function useSession() {
|
|||
lang: '',
|
||||
darkMode: null,
|
||||
});
|
||||
|
||||
stopRenewer();
|
||||
}
|
||||
|
||||
async function login(token, tokenMultimedia, keepLogin) {
|
||||
setToken({ token, tokenMultimedia, keepLogin });
|
||||
async function login(data) {
|
||||
setSession(data);
|
||||
|
||||
await useRole().fetch();
|
||||
await useUserConfig().fetch();
|
||||
await useTokenConfig().fetch();
|
||||
|
||||
startInterval();
|
||||
}
|
||||
|
||||
function isLoggedIn() {
|
||||
const localToken = localStorage.getItem('token');
|
||||
const sessionToken = sessionStorage.getItem('token');
|
||||
|
||||
const localToken = localStorage.getItem(TOKEN);
|
||||
const sessionToken = sessionStorage.getItem(TOKEN);
|
||||
startInterval();
|
||||
return !!(localToken || sessionToken);
|
||||
}
|
||||
|
||||
function startInterval() {
|
||||
stopRenewer();
|
||||
const renewPeriod = +sessionStorage.getItem('renewPeriod');
|
||||
if (!renewPeriod) return;
|
||||
intervalId = setInterval(() => checkValidity(), renewPeriod * 1000);
|
||||
}
|
||||
|
||||
function stopRenewer() {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
|
||||
async function renewToken() {
|
||||
const _token = getToken();
|
||||
const token = await axios.post('VnUsers/renewToken', {
|
||||
headers: { Authorization: _token },
|
||||
});
|
||||
const _tokenMultimedia = getTokenMultimedia();
|
||||
const tokenMultimedia = await axios.post('VnUsers/renewToken', {
|
||||
headers: { Authorization: _tokenMultimedia },
|
||||
});
|
||||
setToken({ token: token.data.id, tokenMultimedia: tokenMultimedia.data.id });
|
||||
}
|
||||
|
||||
async function checkValidity() {
|
||||
const { getTokenConfig } = useState();
|
||||
|
||||
const tokenConfig = getTokenConfig() ?? sessionStorage.getItem('tokenConfig');
|
||||
const storage = keepLogin() ? localStorage : sessionStorage;
|
||||
|
||||
const created = +storage.getItem('created');
|
||||
const ttl = +storage.getItem('ttl');
|
||||
|
||||
if (isCheckingToken || !created) return;
|
||||
isCheckingToken = true;
|
||||
|
||||
const renewPeriodInSeconds = Math.min(ttl, tokenConfig.value.renewPeriod) * 1000;
|
||||
|
||||
const maxDate = created + renewPeriodInSeconds;
|
||||
const now = new Date().getTime();
|
||||
|
||||
if (isNaN(renewPeriodInSeconds) || now <= maxDate) {
|
||||
return (isCheckingToken = false);
|
||||
}
|
||||
|
||||
await renewToken();
|
||||
isCheckingToken = false;
|
||||
}
|
||||
|
||||
return {
|
||||
getToken,
|
||||
getTokenMultimedia,
|
||||
|
@ -84,5 +156,8 @@ export function useSession() {
|
|||
destroy,
|
||||
login,
|
||||
isLoggedIn,
|
||||
checkValidity,
|
||||
setSession,
|
||||
renewToken,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -13,34 +13,19 @@ const user = ref({
|
|||
});
|
||||
|
||||
const roles = ref([]);
|
||||
const tokenConfig = ref({});
|
||||
const drawer = ref(true);
|
||||
const headerMounted = ref(false);
|
||||
|
||||
export function useState() {
|
||||
function getUser() {
|
||||
return computed(() => {
|
||||
return {
|
||||
id: user.value.id,
|
||||
name: user.value.name,
|
||||
nickname: user.value.nickname,
|
||||
lang: user.value.lang,
|
||||
darkMode: user.value.darkMode,
|
||||
companyFk: user.value.companyFk,
|
||||
warehouseFk: user.value.warehouseFk,
|
||||
};
|
||||
return user.value;
|
||||
});
|
||||
}
|
||||
|
||||
function setUser(data) {
|
||||
user.value = {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
nickname: data.nickname,
|
||||
lang: data.lang,
|
||||
darkMode: data.darkMode,
|
||||
companyFk: data.companyFk,
|
||||
warehouseFk: data.warehouseFk,
|
||||
};
|
||||
user.value = data;
|
||||
}
|
||||
|
||||
function getRoles() {
|
||||
|
@ -52,6 +37,15 @@ export function useState() {
|
|||
function setRoles(data) {
|
||||
roles.value = data;
|
||||
}
|
||||
function getTokenConfig() {
|
||||
return computed(() => {
|
||||
return tokenConfig.value;
|
||||
});
|
||||
}
|
||||
|
||||
function setTokenConfig(data) {
|
||||
tokenConfig.value = data;
|
||||
}
|
||||
|
||||
function set(name, data) {
|
||||
state.value[name] = ref(data);
|
||||
|
@ -70,6 +64,8 @@ export function useState() {
|
|||
setUser,
|
||||
getRoles,
|
||||
setRoles,
|
||||
getTokenConfig,
|
||||
setTokenConfig,
|
||||
set,
|
||||
get,
|
||||
unset,
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import axios from 'axios';
|
||||
import { useState } from './useState';
|
||||
import useNotify from './useNotify';
|
||||
|
||||
export function useTokenConfig() {
|
||||
const state = useState();
|
||||
const { notify } = useNotify();
|
||||
|
||||
async function fetch() {
|
||||
try {
|
||||
const { data } = await axios.get('AccessTokenConfigs/findOne', {
|
||||
filter: { fields: ['renewInterval', 'renewPeriod'] },
|
||||
});
|
||||
if (!data) return;
|
||||
state.setTokenConfig(data);
|
||||
sessionStorage.setItem('renewPeriod', data.renewPeriod);
|
||||
return data;
|
||||
} catch (error) {
|
||||
notify('errors.tokenConfig', 'negative');
|
||||
console.error('Error fetching token config:', error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
fetch,
|
||||
state,
|
||||
};
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
// app global css in SCSS form
|
||||
@import './icons.scss';
|
||||
@import '@quasar/quasar-ui-qcalendar/src/QCalendarMonth.sass';
|
||||
|
||||
body.body--light {
|
||||
--font-color: black;
|
||||
|
@ -74,6 +75,10 @@ select:-webkit-autofill {
|
|||
background-color: #666666;
|
||||
}
|
||||
|
||||
.color-vn-label {
|
||||
color: var(--vn-label);
|
||||
}
|
||||
|
||||
.color-vn-text {
|
||||
color: var(--vn-text-color);
|
||||
}
|
||||
|
@ -93,6 +98,10 @@ select:-webkit-autofill {
|
|||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.card-width {
|
||||
width: 770px;
|
||||
}
|
||||
|
||||
.vn-card-list {
|
||||
width: 100%;
|
||||
max-width: 60em;
|
||||
|
@ -110,6 +119,11 @@ select:-webkit-autofill {
|
|||
font-variation-settings: 'FILL' 1;
|
||||
}
|
||||
|
||||
.fill-icon-on-hover:hover {
|
||||
font-variation-settings: 'FILL' 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vn-table-separation-row {
|
||||
height: 16px !important;
|
||||
background-color: var(--vn-section-color) !important;
|
||||
|
@ -127,6 +141,10 @@ select:-webkit-autofill {
|
|||
background-color: var(--vn-section-color);
|
||||
}
|
||||
|
||||
.tr-header {
|
||||
color: var(--vn-label-color);
|
||||
}
|
||||
|
||||
.q-chip,
|
||||
.q-notification__message,
|
||||
.q-notification__icon {
|
||||
|
@ -151,6 +169,13 @@ select:-webkit-autofill {
|
|||
|
||||
/* q-notification row items-stretch q-notification--standard bg-negative text-white */
|
||||
|
||||
.q-card,
|
||||
.q-table,
|
||||
.q-table__bottom,
|
||||
.q-drawer {
|
||||
background-color: var(--vn-section-color);
|
||||
}
|
||||
|
||||
input[type='number'] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
@ -159,4 +184,8 @@ input::-webkit-inner-spin-button {
|
|||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
}
|
||||
}
|
||||
|
||||
.q-scrollarea__content {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@
|
|||
<glyph unicode="" glyph-name="consignatarios" d="M409.6-64v349.867h204.8v-349.867h256v563.2h153.6l-512 460.8-512-460.8h153.6v-563.2h256z" />
|
||||
<glyph unicode="" glyph-name="control" d="M418.133 315.733l-128-123.733 256-256 469.333 469.333-128 128-341.333-341.333zM546.133 311.467l34.133 34.133h-68.267zM230.4 128l-59.733 64 153.6 153.6h-68.267v102.4h426.667l204.8 204.8 85.333-85.333v187.733c0 55.467-46.933 102.4-102.4 102.4h-213.333c-21.333 59.733-76.8 102.4-145.067 102.4s-123.733-42.667-145.067-102.4h-213.333c-55.467 0-102.4-46.933-102.4-102.4v-716.8c0-55.467 46.933-102.4 102.4-102.4h273.067l-196.267 192zM512 857.6c29.867 0 51.2-21.333 51.2-51.2s-21.333-51.2-51.2-51.2-51.2 21.333-51.2 51.2c0 29.867 21.333 51.2 51.2 51.2zM256 652.8h512v-102.4h-512v102.4zM665.6-64h204.8c55.467 0 102.4 46.933 102.4 102.4v204.8l-307.2-307.2z" />
|
||||
<glyph unicode="" glyph-name="credit" d="M921.6 849.067h-819.2c-55.467 0-102.4-42.667-102.4-98.133v-601.6c0-55.467 46.933-102.4 102.4-102.4h819.2c55.467 0 102.4 42.667 102.4 102.4v601.6c0 55.467-46.933 98.133-102.4 98.133zM921.6 145.067h-819.2v302.933h819.2v-302.933zM921.6 648.533h-819.2v102.4h819.2v-102.4z" />
|
||||
<glyph unicode="" glyph-name="deaulter" d="M677.973-64c-30.72 35.84-61.867 70.827-91.307 107.52-40.96 51.2-80.64 103.253-121.173 154.88-16.64 21.333-21.76 20.48-30.72-4.693-13.227-36.693-25.6-73.387-40.107-109.653-5.12-12.8-13.227-26.88-24.32-34.56-51.627-34.987-104.107-69.12-157.867-100.693-10.667-6.4-30.72-5.547-41.813 0.853-8.107 4.693-12.373 23.893-11.093 35.84 0.853 8.96 11.093 19.627 19.627 25.6 39.253 26.453 78.933 51.627 119.040 76.8 18.347 11.52 30.293 26.027 35.84 47.787 12.373 48.213 27.307 95.573 39.253 143.36 8.533 33.707 26.88 58.88 56.32 77.227 40.533 25.173 80.64 52.053 120.747 78.507 6.4 4.267 10.24 11.52 15.36 17.493-7.253 2.56-14.933 7.253-22.187 6.827-75.52-6.4-151.467-13.227-226.987-20.48-2.133 0-4.693-0.853-6.827-0.853-22.613-1.707-39.253 10.24-40.96 29.867s12.373 33.707 35.413 35.84c45.227 4.267 90.88 8.96 136.107 12.8 65.707 5.547 131.84 10.667 197.547 15.36 26.027 1.707 53.76-21.76 67.413-55.467 9.813-23.893 5.12-46.080-18.347-65.28-49.92-40.107-100.693-78.933-151.040-118.187-23.040-17.92-23.893-23.467-6.4-46.507 58.453-78.080 116.48-156.587 174.933-234.667 27.307-36.693 25.173-50.773-12.373-75.52-5.12 0-9.813 0-14.080 0zM791.893 649.813c-43.093 1.28-76.373-31.573-77.227-75.52-0.853-44.373 29.44-76.8 72.107-77.653 45.227-1.28 77.653 29.44 78.080 73.813 0.427 45.227-29.44 78.080-72.96 79.36zM671.147 737.707c0-72.107-34.133-136.107-87.467-176.64l-235.52-21.76c-72.107 36.693-122.027 111.787-122.027 198.4 0 122.88 99.84 222.293 222.72 222.293 122.453 0 222.293-99.413 222.293-222.293zM592.213 680.533l-50.347 18.347c-2.133-8.533-5.12-16.213-9.813-22.613-5.12-6.4-10.24-11.947-16.213-17.067-5.973-4.267-12.373-8.107-19.2-11.093s-13.653-4.693-20.053-5.547c-17.92-2.987-33.707-0.427-48.64 6.827s-26.88 18.347-36.693 32.853l76.373 12.373 7.253 32.427-97.28-15.787c-1.28 5.547-2.987 11.093-3.84 16.64l-0.853 4.267 99.413 16.213 7.253 32.427-106.667-17.493c0.853 9.387 2.987 17.493 6.4 26.027 3.84 8.533 8.107 16.213 14.080 23.040 5.547 6.827 12.8 12.373 21.333 17.067s17.92 8.107 28.587 9.813c6.827 1.28 13.227 1.707 20.907 1.28s14.507-1.707 21.333-3.84c6.827-2.133 13.653-5.973 20.053-10.24 5.973-4.693 11.947-11.093 17.493-18.773l38.827 37.973c-13.227 17.92-30.293 31.147-52.053 39.253-21.76 8.533-46.080 10.667-73.387 6.4-19.627-2.987-36.267-9.387-51.2-17.92-14.507-8.533-26.88-19.2-37.547-32-10.667-12.373-18.773-26.027-23.893-40.96-5.547-14.507-8.96-29.867-9.813-45.653l-21.76-3.84-7.253-32.427 29.013 4.693 0.427-2.987c1.28-6.827 2.56-12.8 4.267-18.347l-23.467-3.84-8.107-32.427 43.52 7.253c6.827-13.653 15.787-26.027 26.027-36.693 10.24-11.52 22.187-20.48 35.413-27.733 13.227-7.68 27.307-12.8 42.667-15.787s31.573-3.413 47.36-0.853c12.373 2.133 24.32 5.12 35.84 10.667s22.613 11.52 32.853 19.2c10.24 8.107 18.347 16.64 26.027 26.453 6.827 9.387 12.373 20.48 15.36 32.427z" />
|
||||
<glyph unicode="" glyph-name="defaulter" d="M677.973-64c-30.72 35.84-61.867 70.827-91.307 107.52-40.96 51.2-80.64 103.253-121.173 154.88-16.64 21.333-21.76 20.48-30.72-4.693-13.227-36.693-25.6-73.387-40.107-109.653-5.12-12.8-13.227-26.88-24.32-34.56-51.627-34.987-104.107-69.12-157.867-100.693-10.667-6.4-30.72-5.547-41.813 0.853-8.107 4.693-12.373 23.893-11.093 35.84 0.853 8.96 11.093 19.627 19.627 25.6 39.253 26.453 78.933 51.627 119.040 76.8 18.347 11.52 30.293 26.027 35.84 47.787 12.373 48.213 27.307 95.573 39.253 143.36 8.533 33.707 26.88 58.88 56.32 77.227 40.533 25.173 80.64 52.053 120.747 78.507 6.4 4.267 10.24 11.52 15.36 17.493-7.253 2.56-14.933 7.253-22.187 6.827-75.52-6.4-151.467-13.227-226.987-20.48-2.133 0-4.693-0.853-6.827-0.853-22.613-1.707-39.253 10.24-40.96 29.867s12.373 33.707 35.413 35.84c45.227 4.267 90.88 8.96 136.107 12.8 65.707 5.547 131.84 10.667 197.547 15.36 26.027 1.707 53.76-21.76 67.413-55.467 9.813-23.893 5.12-46.080-18.347-65.28-49.92-40.107-100.693-78.933-151.040-118.187-23.040-17.92-23.893-23.467-6.4-46.507 58.453-78.080 116.48-156.587 174.933-234.667 27.307-36.693 25.173-50.773-12.373-75.52-5.12 0-9.813 0-14.080 0zM791.893 649.813c-43.093 1.28-76.373-31.573-77.227-75.52-0.853-44.373 29.44-76.8 72.107-77.653 45.227-1.28 77.653 29.44 78.080 73.813 0.427 45.227-29.44 78.080-72.96 79.36zM671.147 737.707c0-72.107-34.133-136.107-87.467-176.64l-235.52-21.76c-72.107 36.693-122.027 111.787-122.027 198.4 0 122.88 99.84 222.293 222.72 222.293 122.453 0 222.293-99.413 222.293-222.293zM592.213 680.533l-50.347 18.347c-2.133-8.533-5.12-16.213-9.813-22.613-5.12-6.4-10.24-11.947-16.213-17.067-5.973-4.267-12.373-8.107-19.2-11.093s-13.653-4.693-20.053-5.547c-17.92-2.987-33.707-0.427-48.64 6.827s-26.88 18.347-36.693 32.853l76.373 12.373 7.253 32.427-97.28-15.787c-1.28 5.547-2.987 11.093-3.84 16.64l-0.853 4.267 99.413 16.213 7.253 32.427-106.667-17.493c0.853 9.387 2.987 17.493 6.4 26.027 3.84 8.533 8.107 16.213 14.080 23.040 5.547 6.827 12.8 12.373 21.333 17.067s17.92 8.107 28.587 9.813c6.827 1.28 13.227 1.707 20.907 1.28s14.507-1.707 21.333-3.84c6.827-2.133 13.653-5.973 20.053-10.24 5.973-4.693 11.947-11.093 17.493-18.773l38.827 37.973c-13.227 17.92-30.293 31.147-52.053 39.253-21.76 8.533-46.080 10.667-73.387 6.4-19.627-2.987-36.267-9.387-51.2-17.92-14.507-8.533-26.88-19.2-37.547-32-10.667-12.373-18.773-26.027-23.893-40.96-5.547-14.507-8.96-29.867-9.813-45.653l-21.76-3.84-7.253-32.427 29.013 4.693 0.427-2.987c1.28-6.827 2.56-12.8 4.267-18.347l-23.467-3.84-8.107-32.427 43.52 7.253c6.827-13.653 15.787-26.027 26.027-36.693 10.24-11.52 22.187-20.48 35.413-27.733 13.227-7.68 27.307-12.8 42.667-15.787s31.573-3.413 47.36-0.853c12.373 2.133 24.32 5.12 35.84 10.667s22.613 11.52 32.853 19.2c10.24 8.107 18.347 16.64 26.027 26.453 6.827 9.387 12.373 20.48 15.36 32.427z" />
|
||||
<glyph unicode="" glyph-name="deletedTicket" d="M160.672 85.696h693.248v639.776c0 0-2.016 234.528-349.696 234.528s-343.552-234.528-343.552-234.528v-639.776zM291.328 652.704h170.976v152.256h102.336v-152.256h171.008v-102.336h-171.008v-356.96h-102.336v356.96h-170.976v102.336zM64 61.056v-123.456h899.008v123.456h-899.008z" />
|
||||
<glyph unicode="" glyph-name="deleteline" d="M354.133 192l-98.133 98.133 157.867 153.6-157.867 157.867 98.133 102.4 157.867-157.867 157.867 153.6 98.133-98.133-157.867-157.867 157.867-153.6-98.133-98.133-157.867 157.867-157.867-157.867zM780.8 507.733l-64-64 59.733-55.467h247.467v119.467h-243.2zM307.2 443.733l-64 64h-243.2v-119.467h251.733l55.467 55.467z" />
|
||||
<glyph unicode="" glyph-name="delivery" d="M789.333 264.533c-55.467 0-102.4-46.933-102.4-102.4s46.933-102.4 102.4-102.4 102.4 46.933 102.4 102.4c0 59.733-46.933 102.4-102.4 102.4zM789.333 110.933c-29.867 0-51.2 21.333-51.2 51.2s21.333 51.2 51.2 51.2 51.2-21.333 51.2-51.2c0-25.6-25.6-51.2-51.2-51.2zM251.733 264.533c-55.467 0-102.4-46.933-102.4-102.4s46.933-102.4 102.4-102.4c55.467 0 102.4 46.933 102.4 102.4 0 59.733-46.933 102.4-102.4 102.4zM251.733 110.933c-29.867 0-51.2 21.333-51.2 51.2s21.333 51.2 51.2 51.2c29.867 0 51.2-21.333 51.2-51.2 0-25.6-25.6-51.2-51.2-51.2zM1006.933 537.6l-196.267 192c-12.8 12.8-29.867 17.067-46.933 17.067h-98.133v38.4c0 25.6-21.333 51.2-51.2 51.2h-563.2c-29.867 0-51.2-21.333-51.2-51.2v-554.667c0-29.867 25.6-51.2 51.2-51.2h68.267c8.533 64 64 115.2 132.267 115.2 64 0 123.733-51.2 132.267-115.2h268.8c8.533 64 64 115.2 132.267 115.2s128-51.2 136.533-115.2h51.2c29.867 0 51.2 25.6 51.2 51.2v260.267c0 17.067-8.533 34.133-17.067 46.933zM725.333 682.667c0 4.267 4.267 8.533 8.533 8.533h34.133c0 0 4.267 0 4.267-4.267l153.6-145.067c4.267 0 0-12.8-4.267-12.8h-187.733c-8.533 0-8.533 4.267-8.533 8.533v145.067zM311.467 597.333c0 46.933 29.867 85.333 59.733 93.867 4.267 0 4.267 0 8.533 0l98.133 12.8v-51.2c0-46.933-29.867-85.333-59.733-93.867-4.267 0-4.267 0-8.533 0l-98.133-17.067v55.467zM311.467 516.267l46.933 8.533c17.067 4.267 29.867-17.067 29.867-38.4l4.267-29.867-51.2-4.267c-17.067-4.267-29.867 12.8-29.867 38.4v25.6zM149.333 597.333v51.2l85.333 12.8c34.133 4.267 55.467-25.6 55.467-72.533v-51.2l-85.333-12.8c-34.133 0-59.733 29.867-55.467 72.533zM285.867 512v-38.4c0-34.133-21.333-64-42.667-68.267h-4.267l-72.533-8.533v38.4c0 34.133 21.333 64 42.667 68.267h4.267l72.533 8.533z" />
|
||||
|
@ -132,4 +132,4 @@
|
|||
<glyph unicode="" glyph-name="polizon" d="M1011.2 456.533l-264.533 106.667 29.867 29.867c4.267 4.267 4.267 12.8 4.267 17.067-4.267 4.267-8.533 8.533-12.8 8.533h-157.867c0 93.867 76.8 157.867 174.933 157.867 4.267 0 8.533 4.267 12.8 8.533s4.267 8.533 0 17.067l-81.067 153.6c-4.267 0-12.8 4.267-17.067 4.267-46.933 0-93.867-17.067-132.267-42.667-25.6-17.067-42.667-38.4-55.467-59.733-12.8 25.6-29.867 42.667-55.467 59.733-38.4 25.6-85.333 42.667-132.267 42.667-4.267 0-12.8-4.267-12.8-8.533l-81.067-153.6c-4.267-4.267-4.267-8.533 0-17.067 4.267-4.267 8.533-8.533 12.8-8.533 98.133 0 174.933-59.733 174.933-153.6v0h-140.8c-4.267 0-12.8-4.267-12.8-8.533-4.267-4.267 0-12.8 4.267-17.067l21.333-21.333-277.333-110.933c-8.533-8.533-12.8-12.8-8.533-21.333 0-8.533 8.533-12.8 17.067-12.8 0 0 0 0 0 0l98.133 4.267-76.8-98.133c0-4.267-4.267-8.533 0-12.8 0-4.267 4.267-8.533 8.533-8.533l85.333-34.133v-179.2c0-8.533 4.267-12.8 8.533-12.8l362.667-145.067c0 0 4.267 0 4.267 0s4.267 0 4.267 0l362.667 145.067c4.267 4.267 8.533 8.533 8.533 12.8v179.2l85.333 34.133c4.267 0 8.533 4.267 8.533 8.533s0 8.533-4.267 12.8l-72.533 98.133 102.4-4.267c8.533 0 12.8 4.267 17.067 12.8 0 8.533-4.267 12.8-12.8 17.067zM110.933 460.8l200.533 81.067 8.533-8.533-170.667-68.267-38.4-4.267zM153.6 294.4v4.267l-72.533 29.867 72.533 98.133 328.533-132.267-72.533-98.133-256 102.4v-4.267zM494.933-25.6l-328.533 132.267v153.6l243.2-98.133c0 0 4.267 0 4.267 0h4.267c0 0 4.267 0 4.267 0v0c0 0 0 0 4.267 0v0c0 0 4.267 0 4.267 4.267l64 85.333v-277.333zM494.933 328.533l-302.933 119.467 149.333 59.733 153.6-162.133v-17.067zM529.067 345.6l162.133 157.867 140.8-55.467-302.933-119.467v17.067zM857.6 106.667l-328.533-132.267v281.6l64-85.333c0 0 0-4.267 4.267-4.267v0c0 0 4.267 0 4.267 0v0c0 0 4.267 0 4.267 0v0 0c0 0 4.267 0 4.267 0l243.2 98.133v-157.867zM942.933 328.533l-328.533-132.267-72.533 98.133 328.533 132.267 72.533-98.133zM874.667 465.067l-162.133 64 12.8 8.533 187.733-76.8-38.4 4.267z" />
|
||||
<glyph unicode="" glyph-name="preserved" d="M512-64c16.213 0 28.587 13.227 28.587 28.587v215.040h2.987c73.387 2.133 130.987 25.173 170.667 67.84 68.267 72.107 62.72 177.493 60.16 197.973v0c-1.28 15.36-14.080 28.16-29.44 29.44-7.253 0.853-33.707 2.987-67.84-2.133l-12.373-2.133 11.093 6.827c14.080 8.96 26.453 19.2 37.547 30.72 68.267 72.107 62.72 177.493 60.16 197.973-1.28 15.36-14.080 28.16-29.44 29.44-14.080 1.28-64 4.267-117.76-14.507l-4.267-1.28 0.853 5.12c2.987 15.36 4.267 31.573 4.267 46.933-2.56 98.56-78.933 168.107-93.867 180.48-12.373 10.24-29.44 10.24-41.813 0-15.36-11.947-92.16-81.067-94.72-180.48-0.853-16.213 0.853-31.573 4.267-47.787l0.853-5.12-4.267 1.28c-53.76 19.2-104.533 16.213-118.613 14.507-15.36-1.28-28.16-14.080-29.44-29.44-2.133-20.48-8.107-125.867 60.16-197.973 11.093-11.093 22.613-21.333 36.693-30.293l10.24-6.827-12.373 2.133c-16.213 2.133-30.293 2.987-41.387 2.987s-19.2-0.853-23.467-0.853c-15.36-1.28-28.16-14.080-29.44-29.44-2.133-20.48-8.107-125.013 60.16-197.973 39.68-41.813 97.28-64.853 169.813-67.84h2.987v-215.040c0.427-14.933 12.8-28.16 29.013-28.16zM715.093 418.133h2.987v-2.987c-0.853-30.72-7.253-87.467-45.653-128-28.587-30.72-71.253-46.933-127.147-49.92h-2.987v2.987c2.133 58.88 20.053 103.68 51.627 133.12 38.827 37.973 92.587 44.8 121.173 44.8zM713.813 679.253h2.987v-2.987c-0.853-30.72-7.253-87.467-45.653-128-28.587-30.293-71.253-46.933-127.147-49.493h-2.987v2.987c2.133 58.88 20.053 103.68 51.627 133.12 39.253 37.12 93.013 43.52 121.173 44.373zM454.4 770.56c1.28 55.040 35.413 99.413 55.893 120.747l2.133 2.133 2.133-2.133c20.053-21.333 53.76-65.707 55.893-120.747 1.28-43.52-17.493-87.040-55.040-128.853l-2.133-2.133-2.133 2.133c-39.68 41.813-58.027 85.333-56.747 128.853zM351.573 548.267c-37.547 39.68-44.8 97.28-45.653 128v2.987h2.987c34.56-0.853 84.053-8.96 121.6-43.947v0c31.573-29.44 48.64-74.24 51.627-133.12v-2.987h-2.987c-56.32 2.133-98.987 18.773-127.573 49.067zM479.573 237.653c-55.893 2.133-98.56 19.2-127.147 49.493-37.547 39.68-44.8 97.28-45.653 128v2.987h2.987c28.587-0.853 82.347-7.253 121.6-43.947 31.573-29.44 48.64-74.24 51.627-133.12v-2.987h-3.413zM471.467 416.427c-8.96 8.107-18.347 15.36-29.44 22.613l-11.093 6.827 12.373-1.28c21.333-2.987 41.813-3.84 64-2.987 0.853 0 1.28 0 2.133 0h2.56c0.853 0 1.28 0 2.133 0 5.12 0 9.387 0 14.507 0 17.493 0 35.413 1.28 52.053 3.84l13.227 2.133-11.093-7.253c-11.093-6.827-20.48-14.507-29.44-22.613-14.507-14.080-28.16-31.573-38.827-51.627l-2.133-4.267-2.133 4.267c-10.667 18.773-23.893 36.267-38.827 50.347z" />
|
||||
<glyph unicode="" glyph-name="recovery" d="M746.667 477.867c68.267 0 140.8-21.333 196.267-72.533 110.933-102.4 115.2-277.333 8.533-384s-277.333-115.2-384-8.533c-93.867 85.333-110.933 221.867-51.2 328.533l51.2-46.933c-34.133-76.8-17.067-170.667 46.933-230.4 81.067-76.8 209.067-72.533 290.133 8.533 76.8 81.067 72.533 209.067-8.533 290.133-42.667 38.4-93.867 55.467-145.067 55.467l4.267-153.6-170.667 162.133 162.133 170.667v-119.467zM337.067 209.067c0 0 0 0 0 0-17.067 8.533-38.4 17.067-55.467 25.6-21.333 8.533-29.867 25.6-29.867 51.2s0 51.2 0 81.067c29.867-17.067 64-29.867 102.4-38.4 4.267 17.067 12.8 34.133 21.333 51.2-17.067 4.267-34.133 8.533-51.2 17.067-21.333 8.533-38.4 21.333-55.467 29.867-12.8 8.533-17.067 17.067-12.8 34.133 0 29.867 0 55.467 0 85.333 68.267-29.867 132.267-46.933 200.533-51.2 17.067 21.333 38.4 38.4 64 55.467 0 0-4.267 0-4.267 0-51.2 0-106.667 4.267-157.867 21.333-34.133 0-59.733 12.8-81.067 29.867-29.867 21.333-29.867 42.667 4.267 64 38.4 25.6 81.067 34.133 128 42.667 55.467 8.533 110.933 8.533 166.4 4.267 51.2-4.267 102.4-17.067 149.333-38.4 12.8-4.267 42.667-29.867 38.4-55.467 55.467 0 106.667-12.8 153.6-34.133 17.067 4.267 29.867 8.533 46.933 12.8 0-12.8 0-25.6 0-38.4 21.333-12.8 38.4-25.6 55.467-42.667 0 98.133 0 200.533 0 298.667 0 42.667-17.067 72.533-51.2 93.867-46.933 29.867-98.133 38.4-149.333 42.667-34.133 8.533-72.533 8.533-110.933 8.533-72.533-4.267-140.8-21.333-200.533-64-4.267 0-8.533 4.267-8.533 4.267-51.2 34.133-110.933 46.933-170.667 51.2-51.2 0-102.4 0-153.6-8.533-46.933-8.533-93.867-17.067-136.533-46.933-25.6-21.333-38.4-42.667-38.4-76.8 0-145.067 0-294.4 0-439.467 0-38.4 17.067-64 46.933-81.067 21.333-12.8 46.933-25.6 72.533-29.867 25.6-8.533 51.2-12.8 81.067-17.067 17.067-46.933 55.467-68.267 102.4-85.333 12.8-4.267 29.867-8.533 42.667-12.8-4.267 17.067-8.533 34.133-8.533 55.467zM964.267 686.933c0-8.533-8.533-21.333-17.067-25.6-34.133-21.333-76.8-34.133-119.467-38.4-4.267 38.4-17.067 68.267-55.467 89.6 68.267 0 128 12.8 192 42.667 4.267-25.6 4.267-46.933 0-68.267zM610.133 883.2c68.267 17.067 136.533 25.6 204.8 17.067 42.667-4.267 85.333-12.8 128-34.133 12.8-8.533 21.333-12.8 25.6-34.133-8.533-8.533-17.067-17.067-25.6-21.333-29.867-17.067-64-29.867-98.133-34.133-38.4-4.267-76.8-8.533-115.2-8.533-46.933 0-89.6 8.533-132.267 25.6-21.333 8.533-42.667 17.067-55.467 42.667 12.8 29.867 38.4 42.667 68.267 46.933zM68.267 844.8c25.6 25.6 64 34.133 98.133 38.4 29.867 4.267 59.733 12.8 85.333 12.8 68.267 0 132.267-4.267 196.267-34.133 8.533-4.267 17.067-8.533 25.6-17.067 12.8-8.533 12.8-21.333 0-34.133-8.533-8.533-17.067-12.8-29.867-21.333-42.667-21.333-85.333-29.867-132.267-34.133-17.067 0-29.867 0-42.667 0-55.467 4.267-110.933 12.8-162.133 29.867-12.8 4.267-29.867 12.8-42.667 21.333-12.8 17.067-8.533 25.6 4.267 38.4zM196.267 307.2c-25.6 8.533-51.2 12.8-72.533 21.333-8.533 4.267-21.333 8.533-29.867 12.8-25.6 12.8-38.4 29.867-34.133 59.733 0 17.067 0 29.867 0 51.2 46.933-25.6 89.6-34.133 136.533-38.4 0-42.667 0-72.533 0-106.667zM55.467 529.067c0 21.333 0 42.667 0 59.733 46.933-12.8 93.867-21.333 140.8-34.133 0-29.867 0-59.733 0-93.867-34.133 4.267-64 12.8-93.867 21.333-8.533 4.267-21.333 12.8-29.867 17.067-12.8 8.533-17.067 17.067-17.067 29.867zM196.267 618.667c-4.267 0-12.8-4.267-17.067 0-34.133 0-68.267 12.8-102.4 29.867-12.8 8.533-21.333 21.333-21.333 38.4s0 38.4 0 59.733c59.733-25.6 115.2-38.4 179.2-42.667-21.333-25.6-42.667-51.2-38.4-85.333z" />
|
||||
</font></defs></svg>
|
||||
</font></defs></svg>
|
||||
|
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 173 KiB |
File diff suppressed because one or more lines are too long
|
@ -1,402 +1,418 @@
|
|||
@font-face {
|
||||
font-family: 'icon';
|
||||
src: url('fonts/icon.eot?2omjsr');
|
||||
src: url('fonts/icon.eot?2omjsr#iefix') format('embedded-opentype'),
|
||||
url('fonts/icon.ttf?2omjsr') format('truetype'),
|
||||
url('fonts/icon.woff?2omjsr') format('woff'),
|
||||
url('fonts/icon.svg?2omjsr#icon') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
font-family: 'icon';
|
||||
src: url('fonts/icon.eot?2omjsr');
|
||||
src: url('fonts/icon.eot?2omjsr#iefix') format('embedded-opentype'),
|
||||
url('fonts/icon.ttf?2omjsr') format('truetype'),
|
||||
url('fonts/icon.woff?2omjsr') format('woff'),
|
||||
url('fonts/icon.svg?2omjsr#icon') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
|
||||
[class^="icon-"], [class*=" icon-"] {
|
||||
/* use !important to prevent issues with browser extensions that change fonts */
|
||||
font-family: 'icon' !important;
|
||||
speak: never;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
[class^='icon-'],
|
||||
[class*=' icon-'] {
|
||||
/* use !important to prevent issues with browser extensions that change fonts */
|
||||
font-family: 'icon' !important;
|
||||
speak: never;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
|
||||
/* Better Font Rendering =========== */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
/* Better Font Rendering =========== */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-100:before {
|
||||
content: "\e926";
|
||||
content: '\e926';
|
||||
}
|
||||
.icon-Client_unpaid:before {
|
||||
content: "\e925";
|
||||
content: '\e925';
|
||||
}
|
||||
.icon-Client_unpaid:before {
|
||||
content: '\e925';
|
||||
}
|
||||
.icon-History:before {
|
||||
content: "\e964";
|
||||
content: '\e964';
|
||||
}
|
||||
.icon-Person:before {
|
||||
content: "\e984";
|
||||
content: '\e984';
|
||||
}
|
||||
.icon-accessory:before {
|
||||
content: "\e948";
|
||||
content: '\e948';
|
||||
}
|
||||
.icon-account:before {
|
||||
content: "\e927";
|
||||
content: '\e927';
|
||||
}
|
||||
.icon-actions:before {
|
||||
content: "\e928";
|
||||
content: '\e928';
|
||||
}
|
||||
.icon-addperson:before {
|
||||
content: "\e929";
|
||||
content: '\e929';
|
||||
}
|
||||
.icon-agency:before {
|
||||
content: "\e92a";
|
||||
content: '\e92a';
|
||||
}
|
||||
.icon-agency:before {
|
||||
content: '\e92a';
|
||||
}
|
||||
.icon-agency-term:before {
|
||||
content: "\e92b";
|
||||
content: '\e92b';
|
||||
}
|
||||
.icon-albaran:before {
|
||||
content: "\e92c";
|
||||
content: '\e92c';
|
||||
}
|
||||
.icon-albaran:before {
|
||||
content: '\e92c';
|
||||
}
|
||||
.icon-anonymous:before {
|
||||
content: "\e92d";
|
||||
content: '\e92d';
|
||||
}
|
||||
.icon-apps:before {
|
||||
content: "\e92e";
|
||||
content: '\e92e';
|
||||
}
|
||||
.icon-artificial:before {
|
||||
content: "\e92f";
|
||||
content: '\e92f';
|
||||
}
|
||||
.icon-attach:before {
|
||||
content: "\e930";
|
||||
content: '\e930';
|
||||
}
|
||||
.icon-barcode:before {
|
||||
content: "\e932";
|
||||
content: '\e932';
|
||||
}
|
||||
.icon-basket:before {
|
||||
content: "\e933";
|
||||
content: '\e933';
|
||||
}
|
||||
.icon-basketadd:before {
|
||||
content: "\e934";
|
||||
content: '\e934';
|
||||
}
|
||||
.icon-bin:before {
|
||||
content: "\e935";
|
||||
content: '\e935';
|
||||
}
|
||||
.icon-botanical:before {
|
||||
content: "\e936";
|
||||
content: '\e936';
|
||||
}
|
||||
.icon-bucket:before {
|
||||
content: "\e937";
|
||||
content: '\e937';
|
||||
}
|
||||
.icon-buscaman:before {
|
||||
content: "\e938";
|
||||
content: '\e938';
|
||||
}
|
||||
.icon-buyrequest:before {
|
||||
content: "\e939";
|
||||
content: '\e939';
|
||||
}
|
||||
.icon-calc_volum:before {
|
||||
content: "\e93a";
|
||||
content: '\e93a';
|
||||
}
|
||||
.icon-calendar:before {
|
||||
content: "\e940";
|
||||
content: '\e940';
|
||||
}
|
||||
.icon-catalog:before {
|
||||
content: "\e941";
|
||||
content: '\e941';
|
||||
}
|
||||
.icon-claims:before {
|
||||
content: "\e942";
|
||||
content: '\e942';
|
||||
}
|
||||
.icon-client:before {
|
||||
content: "\e943";
|
||||
content: '\e943';
|
||||
}
|
||||
.icon-clone:before {
|
||||
content: "\e945";
|
||||
content: '\e945';
|
||||
}
|
||||
.icon-columnadd:before {
|
||||
content: "\e946";
|
||||
content: '\e946';
|
||||
}
|
||||
.icon-columndelete:before {
|
||||
content: "\e947";
|
||||
content: '\e947';
|
||||
}
|
||||
.icon-components:before {
|
||||
content: "\e949";
|
||||
content: '\e949';
|
||||
}
|
||||
.icon-consignatarios:before {
|
||||
content: "\e94b";
|
||||
content: '\e94b';
|
||||
}
|
||||
.icon-control:before {
|
||||
content: "\e94c";
|
||||
content: '\e94c';
|
||||
}
|
||||
.icon-credit:before {
|
||||
content: "\e94d";
|
||||
content: '\e94d';
|
||||
}
|
||||
.icon-deaulter:before {
|
||||
content: "\e94e";
|
||||
.icon-defaulter:before {
|
||||
content: '\e94e';
|
||||
}
|
||||
.icon-deletedTicket:before {
|
||||
content: "\e94f";
|
||||
content: '\e94f';
|
||||
}
|
||||
.icon-deleteline:before {
|
||||
content: "\e950";
|
||||
content: '\e950';
|
||||
}
|
||||
.icon-delivery:before {
|
||||
content: "\e951";
|
||||
content: '\e951';
|
||||
}
|
||||
.icon-deliveryprices:before {
|
||||
content: "\e952";
|
||||
content: '\e952';
|
||||
}
|
||||
.icon-details:before {
|
||||
content: "\e954";
|
||||
content: '\e954';
|
||||
}
|
||||
.icon-dfiscales:before {
|
||||
content: "\e955";
|
||||
content: '\e955';
|
||||
}
|
||||
.icon-disabled:before {
|
||||
content: "\e965";
|
||||
content: '\e965';
|
||||
}
|
||||
.icon-doc:before {
|
||||
content: "\e956";
|
||||
content: '\e956';
|
||||
}
|
||||
.icon-entry:before {
|
||||
content: "\e958";
|
||||
content: '\e958';
|
||||
}
|
||||
.icon-exit:before {
|
||||
content: "\e959";
|
||||
content: '\e959';
|
||||
}
|
||||
.icon-eye:before {
|
||||
content: "\e95a";
|
||||
content: '\e95a';
|
||||
}
|
||||
.icon-fixedPrice:before {
|
||||
content: "\e95b";
|
||||
content: '\e95b';
|
||||
}
|
||||
.icon-flower:before {
|
||||
content: "\e95c";
|
||||
content: '\e95c';
|
||||
}
|
||||
.icon-frozen:before {
|
||||
content: "\e95d";
|
||||
content: '\e95d';
|
||||
}
|
||||
.icon-fruit:before {
|
||||
content: "\e95e";
|
||||
content: '\e95e';
|
||||
}
|
||||
.icon-funeral:before {
|
||||
content: "\e95f";
|
||||
content: '\e95f';
|
||||
}
|
||||
.icon-grafana:before {
|
||||
content: "\e931";
|
||||
content: '\e931';
|
||||
}
|
||||
.icon-grafana:before {
|
||||
content: '\e931';
|
||||
}
|
||||
.icon-greenery:before {
|
||||
content: "\e91e";
|
||||
content: '\e91e';
|
||||
}
|
||||
.icon-greuge:before {
|
||||
content: "\e960";
|
||||
content: '\e960';
|
||||
}
|
||||
.icon-grid:before {
|
||||
content: "\e961";
|
||||
content: '\e961';
|
||||
}
|
||||
.icon-handmade:before {
|
||||
content: "\e94a";
|
||||
content: '\e94a';
|
||||
}
|
||||
.icon-handmadeArtificial:before {
|
||||
content: "\e962";
|
||||
content: '\e962';
|
||||
}
|
||||
.icon-headercol:before {
|
||||
content: "\e963";
|
||||
content: '\e963';
|
||||
}
|
||||
.icon-info:before {
|
||||
content: "\e966";
|
||||
content: '\e966';
|
||||
}
|
||||
.icon-inventory:before {
|
||||
content: "\e967";
|
||||
content: '\e967';
|
||||
}
|
||||
.icon-invoice:before {
|
||||
content: "\e969";
|
||||
content: '\e969';
|
||||
}
|
||||
.icon-invoice-in:before {
|
||||
content: "\e96a";
|
||||
content: '\e96a';
|
||||
}
|
||||
.icon-invoice-in-create:before {
|
||||
content: "\e96b";
|
||||
content: '\e96b';
|
||||
}
|
||||
.icon-invoice-out:before {
|
||||
content: "\e96c";
|
||||
content: '\e96c';
|
||||
}
|
||||
.icon-isTooLittle:before {
|
||||
content: "\e96e";
|
||||
content: '\e96e';
|
||||
}
|
||||
.icon-item:before {
|
||||
content: "\e96f";
|
||||
content: '\e96f';
|
||||
}
|
||||
.icon-languaje:before {
|
||||
content: "\e912";
|
||||
content: '\e912';
|
||||
}
|
||||
.icon-lines:before {
|
||||
content: "\e971";
|
||||
content: '\e971';
|
||||
}
|
||||
.icon-linesprepaired:before {
|
||||
content: "\e972";
|
||||
content: '\e972';
|
||||
}
|
||||
.icon-link-to-corrected:before {
|
||||
content: "\e900";
|
||||
content: '\e900';
|
||||
}
|
||||
.icon-link-to-correcting:before {
|
||||
content: "\e906";
|
||||
content: '\e906';
|
||||
}
|
||||
.icon-logout:before {
|
||||
content: "\e90a";
|
||||
content: '\e90a';
|
||||
}
|
||||
.icon-mana:before {
|
||||
content: "\e974";
|
||||
content: '\e974';
|
||||
}
|
||||
.icon-mandatory:before {
|
||||
content: "\e975";
|
||||
content: '\e975';
|
||||
}
|
||||
.icon-net:before {
|
||||
content: "\e976";
|
||||
content: '\e976';
|
||||
}
|
||||
.icon-newalbaran:before {
|
||||
content: "\e977";
|
||||
content: '\e977';
|
||||
}
|
||||
.icon-niche:before {
|
||||
content: "\e979";
|
||||
content: '\e979';
|
||||
}
|
||||
.icon-no036:before {
|
||||
content: "\e97a";
|
||||
content: '\e97a';
|
||||
}
|
||||
.icon-noPayMethod:before {
|
||||
content: "\e97b";
|
||||
content: '\e97b';
|
||||
}
|
||||
.icon-notes:before {
|
||||
content: "\e97c";
|
||||
content: '\e97c';
|
||||
}
|
||||
.icon-noweb:before {
|
||||
content: "\e97e";
|
||||
content: '\e97e';
|
||||
}
|
||||
.icon-onlinepayment:before {
|
||||
content: "\e97f";
|
||||
content: '\e97f';
|
||||
}
|
||||
.icon-package:before {
|
||||
content: "\e980";
|
||||
content: '\e980';
|
||||
}
|
||||
.icon-payment:before {
|
||||
content: "\e982";
|
||||
content: '\e982';
|
||||
}
|
||||
.icon-pbx:before {
|
||||
content: "\e983";
|
||||
content: '\e983';
|
||||
}
|
||||
.icon-pets:before {
|
||||
content: "\e985";
|
||||
content: '\e985';
|
||||
}
|
||||
.icon-photo:before {
|
||||
content: "\e986";
|
||||
content: '\e986';
|
||||
}
|
||||
.icon-plant:before {
|
||||
content: "\e987";
|
||||
content: '\e987';
|
||||
}
|
||||
.icon-polizon:before {
|
||||
content: "\e989";
|
||||
content: '\e989';
|
||||
}
|
||||
.icon-preserved:before {
|
||||
content: "\e98a";
|
||||
content: '\e98a';
|
||||
}
|
||||
.icon-recovery:before {
|
||||
content: "\e98b";
|
||||
content: '\e98b';
|
||||
}
|
||||
.icon-regentry:before {
|
||||
content: "\e901";
|
||||
content: '\e901';
|
||||
}
|
||||
.icon-reserva:before {
|
||||
content: "\e902";
|
||||
content: '\e902';
|
||||
}
|
||||
.icon-revision:before {
|
||||
content: "\e903";
|
||||
content: '\e903';
|
||||
}
|
||||
.icon-risk:before {
|
||||
content: "\e904";
|
||||
content: '\e904';
|
||||
}
|
||||
.icon-services:before {
|
||||
content: "\e905";
|
||||
content: '\e905';
|
||||
}
|
||||
.icon-settings:before {
|
||||
content: "\e907";
|
||||
content: '\e907';
|
||||
}
|
||||
.icon-shipment:before {
|
||||
content: "\e908";
|
||||
content: '\e908';
|
||||
}
|
||||
.icon-sign:before {
|
||||
content: "\e909";
|
||||
content: '\e909';
|
||||
}
|
||||
.icon-sms:before {
|
||||
content: "\e90b";
|
||||
content: '\e90b';
|
||||
}
|
||||
.icon-solclaim:before {
|
||||
content: "\e90c";
|
||||
content: '\e90c';
|
||||
}
|
||||
.icon-solunion:before {
|
||||
content: "\e90d";
|
||||
content: '\e90d';
|
||||
}
|
||||
.icon-splitline:before {
|
||||
content: "\e90e";
|
||||
content: '\e90e';
|
||||
}
|
||||
.icon-splur:before {
|
||||
content: "\e90f";
|
||||
content: '\e90f';
|
||||
}
|
||||
.icon-stowaway:before {
|
||||
content: "\e910";
|
||||
content: '\e910';
|
||||
}
|
||||
.icon-supplier:before {
|
||||
content: "\e911";
|
||||
content: '\e911';
|
||||
}
|
||||
.icon-supplierfalse:before {
|
||||
content: "\e913";
|
||||
content: '\e913';
|
||||
}
|
||||
.icon-tags:before {
|
||||
content: "\e914";
|
||||
content: '\e914';
|
||||
}
|
||||
.icon-tax:before {
|
||||
content: "\e915";
|
||||
content: '\e915';
|
||||
}
|
||||
.icon-thermometer:before {
|
||||
content: "\e916";
|
||||
content: '\e916';
|
||||
}
|
||||
.icon-ticket:before {
|
||||
content: "\e917";
|
||||
content: '\e917';
|
||||
}
|
||||
.icon-ticketAdd:before {
|
||||
content: "\e918";
|
||||
content: '\e918';
|
||||
}
|
||||
.icon-traceability:before {
|
||||
content: "\e919";
|
||||
content: '\e919';
|
||||
}
|
||||
.icon-transaction:before {
|
||||
content: "\e93b";
|
||||
content: '\e93b';
|
||||
}
|
||||
.icon-transaction:before {
|
||||
content: '\e93b';
|
||||
}
|
||||
.icon-treatments:before {
|
||||
content: "\e91c";
|
||||
content: '\e91c';
|
||||
}
|
||||
.icon-trolley:before {
|
||||
content: "\e91a";
|
||||
content: '\e91a';
|
||||
}
|
||||
.icon-troncales:before {
|
||||
content: "\e91b";
|
||||
content: '\e91b';
|
||||
}
|
||||
.icon-unavailable:before {
|
||||
content: "\e91d";
|
||||
content: '\e91d';
|
||||
}
|
||||
.icon-volume:before {
|
||||
content: "\e91f";
|
||||
content: '\e91f';
|
||||
}
|
||||
.icon-wand:before {
|
||||
content: "\e920";
|
||||
content: '\e920';
|
||||
}
|
||||
.icon-web:before {
|
||||
content: "\e921";
|
||||
content: '\e921';
|
||||
}
|
||||
.icon-wiki:before {
|
||||
content: "\e922";
|
||||
content: '\e922';
|
||||
}
|
||||
.icon-worker:before {
|
||||
content: "\e923";
|
||||
content: '\e923';
|
||||
}
|
||||
.icon-zone:before {
|
||||
content: "\e924";
|
||||
content: '\e924';
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ $primary-light: lighten($primary, 35%);
|
|||
$dark-shadow-color: black;
|
||||
$layout-shadow-dark: 0 0 10px 2px #00000033, 0 0px 10px #0000003d;
|
||||
$spacing-md: 16px;
|
||||
|
||||
$color-font-secondary: #777;
|
||||
.bg-success {
|
||||
background-color: $positive;
|
||||
}
|
||||
|
|
|
@ -26,11 +26,11 @@ export function isValidDate(date) {
|
|||
* // returns "02/12/2022"
|
||||
* toDateFormat(new Date(2022, 11, 2));
|
||||
*/
|
||||
export function toDateFormat(date) {
|
||||
export function toDateFormat(date, locale = 'es-ES') {
|
||||
if (!isValidDate(date)) {
|
||||
return '';
|
||||
}
|
||||
return new Date(date).toLocaleDateString('es-ES', {
|
||||
return new Date(date).toLocaleDateString(locale, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
|
@ -56,7 +56,7 @@ export function toTimeFormat(date, showSeconds = false) {
|
|||
if (!isValidDate(date)) {
|
||||
return '';
|
||||
}
|
||||
return new Date(date).toLocaleDateString('es-ES', {
|
||||
return new Date(date).toLocaleTimeString('es-ES', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: showSeconds ? '2-digit' : undefined,
|
||||
|
@ -91,3 +91,42 @@ export function toDateTimeFormat(date, showSeconds = false) {
|
|||
second: showSeconds ? '2-digit' : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts seconds to a formatted string representing hours and minutes (hh:mm).
|
||||
* @param {number} seconds - The number of seconds to convert.
|
||||
* @param {boolean} includeHSuffix - Optional parameter indicating whether to include "h." after the hour.
|
||||
* @returns {string} A string representing the time in the format "hh:mm" with optional "h." suffix.
|
||||
*/
|
||||
export function secondsToHoursMinutes(seconds, includeHSuffix = true) {
|
||||
if (!seconds) return includeHSuffix ? '00:00 h.' : '00:00';
|
||||
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const remainingMinutes = seconds % 3600;
|
||||
const minutes = Math.floor(remainingMinutes / 60);
|
||||
const formattedHours = hours < 10 ? '0' + hours : hours;
|
||||
const formattedMinutes = minutes < 10 ? '0' + minutes : minutes;
|
||||
|
||||
// Append "h." if includeHSuffix is true
|
||||
const suffix = includeHSuffix ? ' h.' : '';
|
||||
// Return formatted string
|
||||
return formattedHours + ':' + formattedMinutes + suffix;
|
||||
}
|
||||
|
||||
export function getTimeDifferenceWithToday(date) {
|
||||
let today = Date.vnNew();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
date = new Date(date);
|
||||
date.setHours(0, 0, 0, 0);
|
||||
|
||||
return today - date;
|
||||
}
|
||||
|
||||
export function isLower(date) {
|
||||
return getTimeDifferenceWithToday(date) > 0;
|
||||
}
|
||||
|
||||
export function isBigger(date) {
|
||||
return getTimeDifferenceWithToday(date) < 0;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export default function dateRange(value) {
|
||||
const minHour = new Date(value);
|
||||
minHour.setHours(0, 0, 0, 0);
|
||||
const maxHour = new Date(value);
|
||||
const maxHour = new Date();
|
||||
maxHour.setHours(23, 59, 59, 59);
|
||||
|
||||
return [minHour, maxHour];
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import toLowerCase from './toLowerCase';
|
||||
import toDate from './toDate';
|
||||
import toDateString from './toDateString';
|
||||
import toDateHour from './toDateHour';
|
||||
import toDateHourMin from './toDateHourMin';
|
||||
import toDateHourMinSec from './toDateHourMinSec';
|
||||
import toRelativeDate from './toRelativeDate';
|
||||
import toCurrency from './toCurrency';
|
||||
import toPercentage from './toPercentage';
|
||||
|
@ -16,7 +17,8 @@ export {
|
|||
toDate,
|
||||
toHour,
|
||||
toDateString,
|
||||
toDateHour,
|
||||
toDateHourMin,
|
||||
toDateHourMinSec,
|
||||
toRelativeDate,
|
||||
toCurrency,
|
||||
toPercentage,
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
export default function toDateHourMin(date) {
|
||||
const dateHour = new Date(date).toLocaleDateString('es-ES', {
|
||||
timeZone: 'Europe/Madrid',
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
return dateHour;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
export default function toDateHour(date) {
|
||||
export default function toDateHourMinSec(date) {
|
||||
const dateHour = new Date(date).toLocaleDateString('es-ES', {
|
||||
timeZone: 'Europe/Madrid',
|
||||
year: 'numeric',
|
|
@ -1,17 +0,0 @@
|
|||
const modules = import.meta.glob(`../pages/**/locale/**.yml`);
|
||||
import translations from './index';
|
||||
const LOCALE_EXTENSION = '.yml';
|
||||
|
||||
export async function locales() {
|
||||
for await (const module of Object.keys(modules)) {
|
||||
const splittedFile = module.split('/');
|
||||
const lang = splittedFile.pop().split(LOCALE_EXTENSION)[0];
|
||||
const moduleFiles = splittedFile.join('/') + '/' + lang + LOCALE_EXTENSION;
|
||||
import(moduleFiles).then((t) => {
|
||||
Object.assign(translations[lang], t.default);
|
||||
});
|
||||
}
|
||||
return translations;
|
||||
}
|
||||
|
||||
export default translations;
|
|
@ -1,10 +1,23 @@
|
|||
const files = import.meta.glob(`./locale/*.yml`);
|
||||
const modules = import.meta.glob(`../pages/**/locale/*.yml`);
|
||||
|
||||
const translations = {};
|
||||
|
||||
for (const file in files) {
|
||||
const lang = file.split('/').at(2).split('.')[0];
|
||||
import(file).then((t) => {
|
||||
translations[lang] = t.default;
|
||||
|
||||
files[file]()
|
||||
.then((g) => {
|
||||
translations[lang] = g.default;
|
||||
})
|
||||
.finally(() => {
|
||||
const actualLang = lang + '.yml';
|
||||
for (const module in modules) {
|
||||
if (!module.endsWith(actualLang)) continue;
|
||||
modules[module]().then((t) => {
|
||||
Object.assign(translations[lang], t.default);
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -28,9 +28,11 @@ globals:
|
|||
reset: Reset
|
||||
close: Close
|
||||
cancel: Cancel
|
||||
clone: Clone
|
||||
confirm: Confirm
|
||||
assign: Assign
|
||||
back: Back
|
||||
downloadPdf: Download PDF
|
||||
yes: 'Yes'
|
||||
no: 'No'
|
||||
noChanges: No changes to save
|
||||
|
@ -67,6 +69,7 @@ globals:
|
|||
reason: reason
|
||||
noResults: No results
|
||||
system: System
|
||||
notificationSent: Notification sent
|
||||
warehouse: Warehouse
|
||||
company: Company
|
||||
fieldRequired: Field required
|
||||
|
@ -82,20 +85,33 @@ globals:
|
|||
selectFile: Select a file
|
||||
copyClipboard: Copy on clipboard
|
||||
salesPerson: SalesPerson
|
||||
send: Send
|
||||
code: Code
|
||||
pageTitles:
|
||||
summary: Summary
|
||||
basicData: Basic data
|
||||
log: Logs
|
||||
parkingList: Parkings list
|
||||
agencyList: Agencies list
|
||||
agency: Agency
|
||||
workCenters: Work centers
|
||||
modes: Modes
|
||||
zones: Zones
|
||||
zonesList: Zones
|
||||
deliveryList: Delivery days
|
||||
upcomingList: Upcoming deliveries
|
||||
created: Created
|
||||
worker: Worker
|
||||
now: Now
|
||||
name: Name
|
||||
new: New
|
||||
errors:
|
||||
statusUnauthorized: Access denied
|
||||
statusInternalServerError: An internal server error has ocurred
|
||||
statusBadGateway: It seems that the server has fall down
|
||||
statusGatewayTimeout: Could not contact the server
|
||||
userConfig: Error fetching user config
|
||||
tokenConfig: Error fetching token config
|
||||
writeRequest: The requested operation could not be completed
|
||||
login:
|
||||
title: Login
|
||||
|
@ -150,6 +166,13 @@ customer:
|
|||
creditContracts: Credit contracts
|
||||
creditOpinion: Credit opinion
|
||||
others: Others
|
||||
samples: Samples
|
||||
consumption: Consumption
|
||||
mandates: Mandates
|
||||
contacts: Contacts
|
||||
webPayment: Web payment
|
||||
fileManagement: File management
|
||||
unpaid: Unpaid
|
||||
list:
|
||||
phone: Phone
|
||||
email: Email
|
||||
|
@ -160,14 +183,18 @@ customer:
|
|||
customerId: Claim ID
|
||||
salesPerson: Sales person
|
||||
credit: Credit
|
||||
risk: Risk
|
||||
securedCredit: Secured credit
|
||||
payMethod: Pay method
|
||||
debt: Debt
|
||||
isDisabled: Customer is disabled
|
||||
isFrozen: Customer is frozen
|
||||
isFrozen: Customer frozen
|
||||
hasDebt: Customer has debt
|
||||
notChecked: Customer not checked
|
||||
isDisabled: Customer inactive
|
||||
notChecked: Customer no checked
|
||||
webAccountInactive: Web account inactive
|
||||
noWebAccess: Web access is disabled
|
||||
businessType: Business type
|
||||
passwordRequirements: 'The password must have at least { length } length characters, {nAlpha} alphabetic characters, {nUpper} capital letters, {nDigits} digits and {nPunct} symbols (Ex: $%&.)\n'
|
||||
businessTypeFk: Business type
|
||||
summary:
|
||||
basicData: Basic data
|
||||
|
@ -227,21 +254,25 @@ customer:
|
|||
recoverySince: Recovery since
|
||||
businessType: Business Type
|
||||
city: City
|
||||
descriptorInfo: Invoices minus payments plus orders not yet
|
||||
rating: Rating
|
||||
recommendCredit: Recommended credit
|
||||
basicData:
|
||||
socialName: Fiscal name
|
||||
businessType: Business type
|
||||
contact: Contact
|
||||
youCanSaveMultipleEmails: You can save multiple emails
|
||||
email: Email
|
||||
phone: Phone
|
||||
mobile: Mobile
|
||||
salesPerson: Sales person
|
||||
contactChannel: Contact channel
|
||||
previousClient: Previous client
|
||||
extendedList:
|
||||
tableVisibleColumns:
|
||||
id: Identifier
|
||||
name: Name
|
||||
socialName: Social name
|
||||
fi: Tax number
|
||||
salesPersonFk: Salesperson
|
||||
credit: Credit
|
||||
|
@ -401,6 +432,7 @@ ticket:
|
|||
boxing: Boxing
|
||||
sms: Sms
|
||||
notes: Notes
|
||||
sale: Sale
|
||||
list:
|
||||
nickname: Nickname
|
||||
state: State
|
||||
|
@ -467,6 +499,12 @@ ticket:
|
|||
weight: Weight
|
||||
goTo: Go to
|
||||
summaryAmount: Summary
|
||||
create:
|
||||
client: Client
|
||||
address: Address
|
||||
landed: Landed
|
||||
warehouse: Warehouse
|
||||
agency: Agency
|
||||
claim:
|
||||
pageTitles:
|
||||
claims: Claims
|
||||
|
@ -490,7 +528,7 @@ claim:
|
|||
records: records
|
||||
card:
|
||||
claimId: Claim ID
|
||||
assignedTo: Assigned
|
||||
attendedBy: Attended by
|
||||
created: Created
|
||||
state: State
|
||||
ticketId: Ticket ID
|
||||
|
@ -528,6 +566,7 @@ claim:
|
|||
responsible: Responsible
|
||||
worker: Worker
|
||||
redelivery: Redelivery
|
||||
changeState: Change state
|
||||
basicData:
|
||||
customer: Customer
|
||||
assignedTo: Assigned
|
||||
|
@ -788,6 +827,10 @@ worker:
|
|||
dms: My documentation
|
||||
pbx: Private Branch Exchange
|
||||
log: Log
|
||||
calendar: Calendar
|
||||
timeControl: Time control
|
||||
locker: Locker
|
||||
|
||||
list:
|
||||
name: Name
|
||||
email: Email
|
||||
|
@ -819,6 +862,15 @@ worker:
|
|||
role: Role
|
||||
sipExtension: Extension
|
||||
locker: Locker
|
||||
fiDueDate: Fecha de caducidad del DNI
|
||||
sex: Sexo
|
||||
seniority: Antigüedad
|
||||
fi: DNI/NIE/NIF
|
||||
birth: Fecha de nacimiento
|
||||
isFreelance: Autónomo
|
||||
isSsDiscounted: Bonificación SS
|
||||
hasMachineryAuthorized: Autorizado para llevar maquinaria
|
||||
isDisable: Trabajador desactivado
|
||||
notificationsManager:
|
||||
activeNotifications: Active notifications
|
||||
availableNotifications: Available notifications
|
||||
|
@ -957,7 +1009,7 @@ supplier:
|
|||
billingData: Billing data
|
||||
payMethod: Pay method
|
||||
payDeadline: Pay deadline
|
||||
payDay: Día de pago
|
||||
payDay: Pay day
|
||||
account: Account
|
||||
fiscalData: Fiscal data
|
||||
sageTaxType: Sage tax type
|
||||
|
@ -1094,7 +1146,19 @@ item:
|
|||
list: List
|
||||
diary: Diary
|
||||
tags: Tags
|
||||
create: Create
|
||||
buyRequest: Buy requests
|
||||
fixedPrice: Fixed prices
|
||||
wasteBreakdown: Waste breakdown
|
||||
itemCreate: New item
|
||||
barcode: Barcodes
|
||||
tax: Tax
|
||||
log: Log
|
||||
botanical: Botanical
|
||||
shelving: Shelving
|
||||
itemTypeCreate: New item type
|
||||
family: Item Type
|
||||
lastEntries: Last entries
|
||||
descriptor:
|
||||
item: Item
|
||||
buyer: Buyer
|
||||
|
@ -1122,8 +1186,93 @@ item:
|
|||
stemMultiplier: Multiplier
|
||||
producer: Producer
|
||||
landed: Landed
|
||||
fixedPrice:
|
||||
itemId: Item ID
|
||||
groupingPrice: Grouping price
|
||||
packingPrice: Packing price
|
||||
hasMinPrice: Has min price
|
||||
minPrice: Min price
|
||||
started: Started
|
||||
ended: Ended
|
||||
warehouse: Warehouse
|
||||
create:
|
||||
name: Name
|
||||
tag: Tag
|
||||
priority: Priority
|
||||
type: Type
|
||||
intrastat: Intrastat
|
||||
origin: Origin
|
||||
buyRequest:
|
||||
ticketId: 'Ticket ID'
|
||||
shipped: 'Shipped'
|
||||
requester: 'Requester'
|
||||
requested: 'Requested'
|
||||
price: 'Price'
|
||||
attender: 'Atender'
|
||||
item: 'Item'
|
||||
achieved: 'Achieved'
|
||||
concept: 'Concept'
|
||||
state: 'State'
|
||||
summary:
|
||||
basicData: 'Basic data'
|
||||
otherData: 'Other data'
|
||||
description: 'Description'
|
||||
tax: 'Tax'
|
||||
tags: 'Tags'
|
||||
botanical: 'Botanical'
|
||||
barcode: 'Barcode'
|
||||
name: 'Nombre'
|
||||
completeName: 'Nombre completo'
|
||||
family: 'Familia'
|
||||
size: 'Medida'
|
||||
origin: 'Origen'
|
||||
stems: 'Tallos'
|
||||
multiplier: 'Multiplicador'
|
||||
buyer: 'Comprador'
|
||||
doPhoto: 'Do photo'
|
||||
intrastatCode: 'Código intrastat'
|
||||
intrastat: 'Intrastat'
|
||||
ref: 'Referencia'
|
||||
relevance: 'Relevancia'
|
||||
weight: 'Peso (gramos)/tallo'
|
||||
units: 'Unidades/caja'
|
||||
expense: 'Gasto'
|
||||
generic: 'Genérico'
|
||||
recycledPlastic: 'Plástico reciclado'
|
||||
nonRecycledPlastic: 'Plástico no reciclado'
|
||||
minSalesQuantity: 'Cantidad mínima de venta'
|
||||
genus: 'Genus'
|
||||
specie: 'Specie'
|
||||
item/itemType:
|
||||
pageTitles:
|
||||
itemType: Item type
|
||||
basicData: Basic data
|
||||
summary: Summary
|
||||
monitor:
|
||||
pageTitles:
|
||||
monitors: Monitors
|
||||
list: List
|
||||
components:
|
||||
topbar: {}
|
||||
itemsFilterPanel:
|
||||
typeFk: Type
|
||||
tag: Tag
|
||||
value: Value
|
||||
# ItemFixedPriceFilter
|
||||
buyerFk: Buyer
|
||||
warehouseFk: Warehouse
|
||||
started: From
|
||||
ended: To
|
||||
mine: For me
|
||||
hasMinPrice: Minimum price
|
||||
# LatestBuysFilter
|
||||
salesPersonFk: Buyer
|
||||
supplierFk: Supplier
|
||||
from: From
|
||||
to: To
|
||||
active: Is active
|
||||
visible: Is visible
|
||||
floramondo: Is floramondo
|
||||
userPanel:
|
||||
copyToken: Token copied to clipboard
|
||||
settings: Settings
|
||||
|
@ -1148,3 +1297,24 @@ components:
|
|||
VnLv:
|
||||
copyText: '{copyValue} has been copied to the clipboard'
|
||||
iban_tooltip: 'IBAN: ES21 1234 5678 90 0123456789'
|
||||
weekdays:
|
||||
sun: Sunday
|
||||
mon: Monday
|
||||
tue: Tuesday
|
||||
wed: Wednesday
|
||||
thu: Thursday
|
||||
fri: Friday
|
||||
sat: Saturday
|
||||
months:
|
||||
jan: January
|
||||
feb: February
|
||||
mar: March
|
||||
apr: April
|
||||
may: May
|
||||
jun: June
|
||||
jul: July
|
||||
aug: August
|
||||
sep: September
|
||||
oct: October
|
||||
nov: November
|
||||
dec: December
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,79 @@
|
|||
<script setup>
|
||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||
import CardList from 'src/components/ui/CardList.vue';
|
||||
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const stateStore = useStateStore();
|
||||
function navigate(id) {
|
||||
router.push({ path: `/agency/${id}` });
|
||||
}
|
||||
function exprBuilder(param, value) {
|
||||
if (!value) return;
|
||||
if (param !== 'search') return;
|
||||
|
||||
if (!isNaN(value)) return { id: value };
|
||||
|
||||
return { name: { like: `%${value}%` } };
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<template v-if="stateStore.isHeaderMounted()">
|
||||
<Teleport to="#searchbar">
|
||||
<VnSearchbar
|
||||
:info="t('You can search by name')"
|
||||
:label="t('Search agency')"
|
||||
data-key="AgencyList"
|
||||
url="Agencies"
|
||||
/>
|
||||
</Teleport>
|
||||
</template>
|
||||
<QPage class="column items-center q-pa-md">
|
||||
<div class="vn-card-list">
|
||||
<VnPaginate
|
||||
data-key="AgencyList"
|
||||
url="Agencies"
|
||||
order="name"
|
||||
:expr-builder="exprBuilder"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<CardList
|
||||
:id="row.id"
|
||||
:key="row.id"
|
||||
:title="row.name"
|
||||
@click="navigate(row.id)"
|
||||
v-for="row of rows"
|
||||
>
|
||||
<template #list-items>
|
||||
<QCheckbox
|
||||
:label="t('isOwn')"
|
||||
v-model="row.isOwn"
|
||||
:disable="true"
|
||||
/>
|
||||
<QCheckbox
|
||||
:label="t('isAnyVolumeAllowed')"
|
||||
v-model="row.isAnyVolumeAllowed"
|
||||
:disable="true"
|
||||
/>
|
||||
</template>
|
||||
</CardList>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</div>
|
||||
</QPage>
|
||||
</template>
|
||||
<i18n>
|
||||
es:
|
||||
isOwn: Tiene propietario
|
||||
isAnyVolumeAllowed: Permite cualquier volumen
|
||||
Search agency: Buscar agencia
|
||||
You can search by name: Puedes buscar por nombre
|
||||
en:
|
||||
isOwn: Has owner
|
||||
isAnyVolumeAllowed: Allows any volume
|
||||
</i18n>
|
|
@ -0,0 +1,52 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import FormModel from 'components/FormModel.vue';
|
||||
import FetchData from 'src/components/FetchData.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const routeId = route.params?.id || null;
|
||||
const warehouses = ref([]);
|
||||
</script>
|
||||
<template>
|
||||
<FetchData
|
||||
url="warehouses"
|
||||
sort-by="name"
|
||||
@on-fetch="(data) => (warehouses = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FormModel :url="`Agencies/${routeId}`" model="agency" auto-load>
|
||||
<template #form="{ data }">
|
||||
<VnRow>
|
||||
<VnInput v-model="data.name" :label="t('globals.name')" />
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnSelect
|
||||
v-model="data.warehouseFk"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
:label="t('globals.warehouse')"
|
||||
:options="warehouses"
|
||||
use-input
|
||||
input-debounce="0"
|
||||
:is-clearable="false"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<QCheckbox :label="t('agency.isOwn')" v-model="data.isOwn" />
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<QCheckbox
|
||||
:label="t('agency.isAnyVolumeAllowed')"
|
||||
v-model="data.isAnyVolumeAllowed"
|
||||
/>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormModel>
|
||||
</template>
|
|
@ -0,0 +1,15 @@
|
|||
<script setup>
|
||||
import AgencyDescriptor from 'pages/Agency/Card/AgencyDescriptor.vue';
|
||||
import VnCard from 'components/common/VnCard.vue';
|
||||
</script>
|
||||
<template>
|
||||
<VnCard
|
||||
data-key="Agency"
|
||||
base-url="Agencies"
|
||||
:descriptor="AgencyDescriptor"
|
||||
search-data-key="AgencyList"
|
||||
search-url="Agencies"
|
||||
searchbar-label="agency.searchBar.label"
|
||||
searchbar-info="agency.searchBar.info"
|
||||
/>
|
||||
</template>
|
|
@ -0,0 +1,35 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
||||
import VnLv from 'components/ui/VnLv.vue';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const entityId = computed(() => props.id || route.params.id);
|
||||
const { store } = useArrayData('Parking');
|
||||
const card = computed(() => store.data);
|
||||
</script>
|
||||
<template>
|
||||
<CardDescriptor
|
||||
module="Agency"
|
||||
data-key="Agency"
|
||||
:url="`Agencies/${entityId}`"
|
||||
:title="card?.name"
|
||||
:subtitle="props.id"
|
||||
>
|
||||
<template #body="{ entity: agency }">
|
||||
<VnLv :label="t('globals.name')" :value="agency.name" />
|
||||
</template>
|
||||
</CardDescriptor>
|
||||
</template>
|
|
@ -0,0 +1,6 @@
|
|||
<script setup>
|
||||
import VnLog from 'src/components/common/VnLog.vue';
|
||||
</script>
|
||||
<template>
|
||||
<VnLog model="Agency" url="/AgencyLogs" />
|
||||
</template>
|
|
@ -0,0 +1,60 @@
|
|||
<script setup>
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import CardList from 'src/components/ui/CardList.vue';
|
||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||
import VnLv from 'src/components/ui/VnLv.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const routeId = route.params.id;
|
||||
</script>
|
||||
<template>
|
||||
<QPage class="column items-center q-pa-md">
|
||||
<div class="vn-card-list">
|
||||
<VnPaginate
|
||||
data-key="AgencyModes"
|
||||
:url="`AgencyModes`"
|
||||
:filter="{ where: { agencyFk: routeId } }"
|
||||
order="name"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<CardList
|
||||
:id="row.id"
|
||||
:key="row.id"
|
||||
:title="row.name"
|
||||
v-for="row of rows"
|
||||
>
|
||||
<template #list-items>
|
||||
<VnLv
|
||||
:label="t('globals.description')"
|
||||
:value="row?.description"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('deliveryMethod')"
|
||||
:value="row?.deliveryMethodFk"
|
||||
/>
|
||||
<VnLv label="m3" :value="row?.m3" />
|
||||
<VnLv :label="t('inflation')" :value="row?.inflation" />
|
||||
<VnLv :label="t('globals.code')" :value="row?.code" />
|
||||
</template>
|
||||
</CardList>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</div>
|
||||
</QPage>
|
||||
</template>
|
||||
<i18n>
|
||||
es:
|
||||
isOwn: Tiene propietario
|
||||
isAnyVolumeAllowed: Permite cualquier volumen
|
||||
Search agency: Buscar agencia
|
||||
You can search by name: Puedes buscar por nombre
|
||||
deliveryMethod: Método de entrega
|
||||
inflation: Inflación
|
||||
en:
|
||||
isOwn: Has owner
|
||||
isAnyVolumeAllowed: Allows any volume
|
||||
</i18n>
|
|
@ -0,0 +1,42 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import CardSummary from 'components/ui/CardSummary.vue';
|
||||
import VnLv from 'components/ui/VnLv.vue';
|
||||
import VnTitle from 'src/components/common/VnTitle.vue';
|
||||
|
||||
const $props = defineProps({ id: { type: Number, default: 0 } });
|
||||
const { t } = useI18n();
|
||||
const entityId = computed(() => $props.id || useRoute().params.id);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<CardSummary :url="`Agencies/${entityId}`">
|
||||
<template #header="{ entity: agency }">{{ agency.name }}</template>
|
||||
<template #body="{ entity: agency }">
|
||||
<QCard class="vn-one">
|
||||
<QCardSection class="q-pa-none">
|
||||
<VnTitle
|
||||
:url="`#/agency/${entityId}/basic-data`"
|
||||
:text="t('globals.pageTitles.basicData')"
|
||||
/>
|
||||
</QCardSection>
|
||||
<VnLv :label="t('globals.name')" :value="agency.name" />
|
||||
<QCheckbox
|
||||
:label="t('agency.isOwn')"
|
||||
v-model="agency.isOwn"
|
||||
:disable="true"
|
||||
/>
|
||||
<QCheckbox
|
||||
:label="t('agency.isAnyVolumeAllowed')"
|
||||
v-model="agency.isAnyVolumeAllowed"
|
||||
:disable="true"
|
||||
/>
|
||||
</QCard>
|
||||
</template>
|
||||
</CardSummary>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,136 @@
|
|||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { ref, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import FetchData from 'src/components/FetchData.vue';
|
||||
import FormModelPopup from 'src/components/FormModelPopup.vue';
|
||||
|
||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||
import VnLv from 'src/components/ui/VnLv.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const paginate = ref();
|
||||
const dialog = ref();
|
||||
const routeId = computed(() => route.params.id);
|
||||
const quasar = useQuasar();
|
||||
|
||||
const initialData = computed(() => {
|
||||
return {
|
||||
agencyFk: routeId.value,
|
||||
workCenterFk: null,
|
||||
};
|
||||
});
|
||||
|
||||
async function deleteWorCenter(id) {
|
||||
try {
|
||||
await axios.delete(`AgencyWorkCenters/${id}`).then(async () => {
|
||||
quasar.notify({
|
||||
message: t('agency.notification.removeItem'),
|
||||
type: 'positive',
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
quasar.notify({
|
||||
message: t('agency.notification.removeItemError'),
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
paginate.value.fetch();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="centerCard">
|
||||
<FetchData
|
||||
url="workCenters"
|
||||
sort-by="name"
|
||||
@on-fetch="(data) => (warehouses = data)"
|
||||
auto-load
|
||||
/>
|
||||
<VnPaginate
|
||||
ref="paginate"
|
||||
data-key="AgencyWorkCenters"
|
||||
url="AgencyWorkCenters"
|
||||
:filter="{ where: { agencyFk: routeId } }"
|
||||
order="id"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<QCard
|
||||
flat
|
||||
bordered
|
||||
:key="row.id"
|
||||
v-for="row of rows"
|
||||
class="card q-pa-md q-mb-sm"
|
||||
>
|
||||
<QItem>
|
||||
<QItemSection side-left>
|
||||
<VnLv :value="row?.workCenter?.name" icon="delete" />
|
||||
</QItemSection>
|
||||
<QItemSection side>
|
||||
<QBtn
|
||||
@click.stop="deleteWorCenter(row.id)"
|
||||
icon="delete"
|
||||
color="primary"
|
||||
round
|
||||
flat
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</QCard>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</div>
|
||||
<QPageSticky :offset="[18, 18]">
|
||||
<QBtn @click.stop="dialog.show()" color="primary" fab icon="add">
|
||||
<QDialog ref="dialog">
|
||||
<FormModelPopup
|
||||
:title="t('Add work center')"
|
||||
url-create="AgencyWorkCenters"
|
||||
model="AgencyWorkCenter"
|
||||
:form-initial-data="initialData"
|
||||
@on-data-saved="paginate.fetch()"
|
||||
>
|
||||
<template #form-inputs="{ data }">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<VnSelect
|
||||
v-model="data.workCenterFk"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
:label="t('workCenter')"
|
||||
:options="warehouses"
|
||||
use-input
|
||||
input-debounce="0"
|
||||
:is-clearable="false"
|
||||
/>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormModelPopup>
|
||||
</QDialog>
|
||||
</QBtn>
|
||||
<QTooltip>
|
||||
{{ t('globals.new') }}
|
||||
</QTooltip>
|
||||
</QPageSticky>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.centerCard {
|
||||
padding: 5%;
|
||||
width: 100%;
|
||||
max-width: 50%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
<i18n>
|
||||
es:
|
||||
workCenter removed successfully: Centro de trabajo eliminado correctamente
|
||||
This workCenter is already assigned to this agency: Este workCenter ya está asignado a esta agencia
|
||||
Add work center: Añadir centro de trabajo
|
||||
workCenter: Centro de trabajo
|
||||
</i18n>
|
|
@ -0,0 +1,11 @@
|
|||
agency:
|
||||
isOwn: Own
|
||||
isAnyVolumeAllowed: Any volume allowed
|
||||
notification:
|
||||
removeItemError: Error removing agency
|
||||
removeItem: WorkCenter removed successfully
|
||||
pageTitles:
|
||||
agency: Agency
|
||||
searchBar:
|
||||
info: You can search by agency code
|
||||
label: Search agency...
|
|
@ -0,0 +1,12 @@
|
|||
agency:
|
||||
isOwn: Propio
|
||||
isAnyVolumeAllowed: Cualquier volumen
|
||||
removeItem: Agencia eliminada correctamente
|
||||
notification:
|
||||
removeItemError: Error al eliminar la agencia
|
||||
removeItem: Centro de trabajo eliminado correctamente
|
||||
pageTitles:
|
||||
agency: Agencia
|
||||
searchBar:
|
||||
info: Puedes buscar por nombre o id
|
||||
label: Buscar agencia...
|
|
@ -2,22 +2,21 @@
|
|||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useQuasar } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useRoute } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
import { useStateStore } from 'src/stores/useStateStore';
|
||||
import { toDate, toPercentage, toCurrency } from 'filters/index';
|
||||
import { tMobile } from 'src/composables/tMobile';
|
||||
import CrudModel from 'src/components/CrudModel.vue';
|
||||
import FetchData from 'src/components/FetchData.vue';
|
||||
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
|
||||
import VnConfirm from 'src/components/ui/VnConfirm.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
|
||||
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
|
||||
const { t } = useI18n();
|
||||
const quasar = useQuasar();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const stateStore = computed(() => useStateStore());
|
||||
const claim = ref(null);
|
||||
const claimRef = ref();
|
||||
|
@ -38,10 +37,11 @@ const marker_labels = [
|
|||
{ value: DEFAULT_MAX_RESPONSABILITY, label: t('claim.summary.person') },
|
||||
];
|
||||
const multiplicatorValue = ref();
|
||||
const loading = ref(false);
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
name: 'Id',
|
||||
name: 'id',
|
||||
label: t('Id item'),
|
||||
field: (row) => row.itemFk,
|
||||
},
|
||||
|
@ -119,7 +119,7 @@ async function updateDestinations(claimDestinationFk) {
|
|||
|
||||
async function updateDestination(claimDestinationFk, row, options = {}) {
|
||||
if (claimDestinationFk) {
|
||||
await axios.post('Claims/updateClaimDestination', {
|
||||
await post('Claims/updateClaimDestination', {
|
||||
claimDestinationFk,
|
||||
rows: Array.isArray(row) ? row : [row],
|
||||
});
|
||||
|
@ -128,7 +128,7 @@ async function updateDestination(claimDestinationFk, row, options = {}) {
|
|||
}
|
||||
|
||||
async function regularizeClaim() {
|
||||
await axios.post(`Claims/${claimId}/regularizeClaim`);
|
||||
await post(`Claims/${claimId}/regularizeClaim`);
|
||||
await claimRef.value.fetch();
|
||||
await arrayData.fetch({ append: false });
|
||||
quasar.notify({
|
||||
|
@ -147,7 +147,7 @@ async function onUpdateGreugeAccept() {
|
|||
const freightPickUpPrice =
|
||||
(await axios.get(`GreugeConfigs/findOne`)).data.freightPickUpPrice *
|
||||
multiplicatorValue.value;
|
||||
await axios.post(`Greuges`, {
|
||||
await post(`Greuges`, {
|
||||
clientFk: claim.value.clientFk,
|
||||
description: `${t('ClaimGreugeDescription')} ${claimId}`.toUpperCase(),
|
||||
amount: freightPickUpPrice,
|
||||
|
@ -166,14 +166,22 @@ async function save(data) {
|
|||
}
|
||||
|
||||
async function importToNewRefundTicket() {
|
||||
const query = `ClaimBeginnings/${claimId}/importToNewRefundTicket`;
|
||||
await axios.post(query);
|
||||
claimActionsForm.value.reload();
|
||||
await post(`ClaimBeginnings/${claimId}/importToNewRefundTicket`);
|
||||
await claimActionsForm.value.reload();
|
||||
quasar.notify({
|
||||
message: t('globals.dataSaved'),
|
||||
type: 'positive',
|
||||
});
|
||||
}
|
||||
|
||||
async function post(query, params) {
|
||||
loading.value = true;
|
||||
try {
|
||||
await axios.post(query, params);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<FetchData
|
||||
|
@ -193,30 +201,7 @@ async function importToNewRefundTicket() {
|
|||
auto-load
|
||||
@on-fetch="(data) => (destinationTypes = data)"
|
||||
/>
|
||||
<template v-if="stateStore.isHeaderMounted()">
|
||||
<Teleport to="#actions-append">
|
||||
<div class="row q-gutter-x-sm">
|
||||
<QBtn
|
||||
flat
|
||||
@click="stateStore.toggleRightDrawer()"
|
||||
round
|
||||
dense
|
||||
icon="menu"
|
||||
>
|
||||
<QTooltip bottom anchor="bottom right">
|
||||
{{ t('globals.collapseMenu') }}
|
||||
</QTooltip>
|
||||
</QBtn>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
<QDrawer
|
||||
v-model="stateStore.rightDrawer"
|
||||
side="right"
|
||||
:width="300"
|
||||
show-if-above
|
||||
v-if="claim"
|
||||
>
|
||||
<Teleport to="#right-panel" v-if="stateStore.isHeaderMounted() && claim">
|
||||
<QCard class="totalClaim q-my-md q-pa-sm no-box-shadow">
|
||||
{{ `${t('Total claimed')}: ${toCurrency(totalClaimed)}` }}
|
||||
</QCard>
|
||||
|
@ -266,7 +251,7 @@ async function importToNewRefundTicket() {
|
|||
v-model="multiplicatorValue"
|
||||
/>
|
||||
</QCard>
|
||||
</QDrawer>
|
||||
</Teleport>
|
||||
<Teleport to="#st-data" v-if="stateStore.isSubToolbarShown()"> </Teleport>
|
||||
<CrudModel
|
||||
v-if="claim"
|
||||
|
@ -280,6 +265,7 @@ async function importToNewRefundTicket() {
|
|||
:default-save="false"
|
||||
:default-reset="false"
|
||||
@on-fetch="setData"
|
||||
:limit="0"
|
||||
auto-load
|
||||
>
|
||||
<template #body>
|
||||
|
@ -292,6 +278,14 @@ async function importToNewRefundTicket() {
|
|||
v-model:selected="selectedRows"
|
||||
:grid="$q.screen.lt.md"
|
||||
>
|
||||
<template #body-cell-id="{ value }">
|
||||
<QTd align="center">
|
||||
<span class="link">
|
||||
{{ value }}
|
||||
<ItemDescriptorProxy :id="value" />
|
||||
</span>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-ticket="{ value }">
|
||||
<QTd align="center">
|
||||
<span class="link">
|
||||
|
@ -302,7 +296,7 @@ async function importToNewRefundTicket() {
|
|||
</template>
|
||||
<template #body-cell-destination="{ row }">
|
||||
<QTd>
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
v-model="row.claimDestinationFk"
|
||||
:options="destinationTypes"
|
||||
option-label="description"
|
||||
|
@ -344,7 +338,7 @@ async function importToNewRefundTicket() {
|
|||
</QItemSection>
|
||||
<QItemSection side>
|
||||
<QItemLabel v-if="column.name === 'destination'">
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
v-model="props.row.claimDestinationFk"
|
||||
:options="destinationTypes"
|
||||
option-label="description"
|
||||
|
@ -383,6 +377,7 @@ async function importToNewRefundTicket() {
|
|||
icon="check"
|
||||
@click="regularizeClaim"
|
||||
:disable="claim.claimStateFk == resolvedStateId"
|
||||
:loading="loading"
|
||||
/>
|
||||
|
||||
<QBtn
|
||||
|
@ -394,6 +389,7 @@ async function importToNewRefundTicket() {
|
|||
:title="t('Change destination')"
|
||||
icon="swap_horiz"
|
||||
@click="dialogDestination = !dialogDestination"
|
||||
:loading="loading"
|
||||
/>
|
||||
<QBtn
|
||||
color="primary"
|
||||
|
@ -404,6 +400,7 @@ async function importToNewRefundTicket() {
|
|||
icon="Upload"
|
||||
@click="importToNewRefundTicket"
|
||||
:disable="claim.claimStateFk == resolvedStateId"
|
||||
:loading="loading"
|
||||
/>
|
||||
</template>
|
||||
</CrudModel>
|
||||
|
@ -418,7 +415,7 @@ async function importToNewRefundTicket() {
|
|||
</QItem>
|
||||
</QCardSection>
|
||||
<QItemSection>
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
class="q-pa-sm"
|
||||
v-model="claimDestinationFk"
|
||||
:options="destinationTypes"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import FormModel from 'components/FormModel.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
|
@ -37,23 +37,19 @@ const claimFilter = {
|
|||
],
|
||||
};
|
||||
|
||||
const workers = ref([]);
|
||||
const workersCopy = ref([]);
|
||||
const claimStates = ref([]);
|
||||
const claimStatesCopy = ref([]);
|
||||
const optionsList = ref([]);
|
||||
|
||||
function setWorkers(data) {
|
||||
workers.value = data;
|
||||
workersCopy.value = data;
|
||||
}
|
||||
const workersOptions = ref([]);
|
||||
|
||||
function setClaimStates(data) {
|
||||
claimStates.value = data;
|
||||
claimStatesCopy.value = data;
|
||||
}
|
||||
let optionsList;
|
||||
|
||||
async function getEnumValues() {
|
||||
optionsList = [{ id: null, description: t('claim.basicData.null') }];
|
||||
optionsList.value = [{ id: null, description: t('claim.basicData.null') }];
|
||||
const { data } = await axios.get(`Applications/get-enum-values`, {
|
||||
params: {
|
||||
schema: 'vn',
|
||||
|
@ -62,29 +58,11 @@ async function getEnumValues() {
|
|||
},
|
||||
});
|
||||
for (let value of data)
|
||||
optionsList.push({ id: value, description: t(`claim.basicData.${value}`) });
|
||||
optionsList.value.push({ id: value, description: t(`claim.basicData.${value}`) });
|
||||
}
|
||||
|
||||
getEnumValues();
|
||||
|
||||
const workerFilter = {
|
||||
options: workers,
|
||||
filterFn: (options, value) => {
|
||||
const search = value.toLowerCase();
|
||||
|
||||
if (value === '') return workersCopy.value;
|
||||
|
||||
return options.value.filter((row) => {
|
||||
const id = row.id;
|
||||
const name = row.name.toLowerCase();
|
||||
|
||||
const idMatches = id == search;
|
||||
const nameMatches = name.indexOf(search) > -1;
|
||||
|
||||
return idMatches || nameMatches;
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const statesFilter = {
|
||||
options: claimStates,
|
||||
filterFn: (options, value) => {
|
||||
|
@ -104,7 +82,7 @@ const statesFilter = {
|
|||
<FetchData
|
||||
url="Workers/activeWithInheritedRole"
|
||||
:filter="{ where: { role: 'salesPerson' } }"
|
||||
@on-fetch="setWorkers"
|
||||
@on-fetch="(data) => (workersOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData url="ClaimStates" @on-fetch="setClaimStates" auto-load />
|
||||
|
@ -117,86 +95,71 @@ const statesFilter = {
|
|||
>
|
||||
<template #form="{ data, validate, filter }">
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<VnInput
|
||||
v-model="data.client.name"
|
||||
:label="t('claim.basicData.customer')"
|
||||
disable
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<VnInputDate
|
||||
v-model="data.created"
|
||||
:label="t('claim.basicData.created')"
|
||||
/>
|
||||
</div>
|
||||
<VnInput
|
||||
v-model="data.client.name"
|
||||
:label="t('claim.basicData.customer')"
|
||||
disable
|
||||
/>
|
||||
<VnInputDate
|
||||
v-model="data.created"
|
||||
:label="t('claim.basicData.created')"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QSelect
|
||||
v-model="data.workerFk"
|
||||
:options="workers"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
emit-value
|
||||
:label="t('claim.basicData.assignedTo')"
|
||||
map-options
|
||||
use-input
|
||||
@filter="(value, update) => filter(value, update, workerFilter)"
|
||||
:rules="validate('claim.claimStateFk')"
|
||||
:input-debounce="0"
|
||||
>
|
||||
<template #before>
|
||||
<QAvatar color="orange">
|
||||
<QImg
|
||||
v-if="data.workerFk"
|
||||
:src="`/api/Images/user/160x160/${data.workerFk}/download?access_token=${token}`"
|
||||
spinner-color="white"
|
||||
/>
|
||||
</QAvatar>
|
||||
</template>
|
||||
</QSelect>
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSelect
|
||||
v-model="data.claimStateFk"
|
||||
:options="claimStates"
|
||||
option-value="id"
|
||||
option-label="description"
|
||||
emit-value
|
||||
:label="t('claim.basicData.state')"
|
||||
map-options
|
||||
use-input
|
||||
@filter="(value, update) => filter(value, update, statesFilter)"
|
||||
:rules="validate('claim.claimStateFk')"
|
||||
:input-debounce="0"
|
||||
>
|
||||
</QSelect>
|
||||
</div>
|
||||
<VnSelect
|
||||
:label="t('claim.basicData.assignedTo')"
|
||||
v-model="data.workerFk"
|
||||
:options="workersOptions"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
emit-value
|
||||
auto-load
|
||||
:rules="validate('claim.claimStateFk')"
|
||||
>
|
||||
<template #before>
|
||||
<QAvatar color="orange">
|
||||
<QImg
|
||||
v-if="data.workerFk"
|
||||
:src="`/api/Images/user/160x160/${data.workerFk}/download?access_token=${token}`"
|
||||
spinner-color="white"
|
||||
/>
|
||||
</QAvatar>
|
||||
</template>
|
||||
</VnSelect>
|
||||
<QSelect
|
||||
v-model="data.claimStateFk"
|
||||
:options="claimStates"
|
||||
option-value="id"
|
||||
option-label="description"
|
||||
emit-value
|
||||
:label="t('claim.basicData.state')"
|
||||
map-options
|
||||
use-input
|
||||
@filter="(value, update) => filter(value, update, statesFilter)"
|
||||
:rules="validate('claim.claimStateFk')"
|
||||
:input-debounce="0"
|
||||
>
|
||||
</QSelect>
|
||||
</VnRow>
|
||||
<VnRow class="row q-gutter-md q-mb-md">
|
||||
<div class="col">
|
||||
<QInput
|
||||
v-model.number="data.packages"
|
||||
:label="t('globals.packages')"
|
||||
:rules="validate('claim.packages')"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<QSelect
|
||||
v-model="data.pickup"
|
||||
:options="optionsList"
|
||||
option-value="id"
|
||||
option-label="description"
|
||||
emit-value
|
||||
:label="t('claim.basicData.pickup')"
|
||||
map-options
|
||||
use-input
|
||||
:input-debounce="0"
|
||||
>
|
||||
</QSelect>
|
||||
</div>
|
||||
<QInput
|
||||
v-model.number="data.packages"
|
||||
:label="t('globals.packages')"
|
||||
:rules="validate('claim.packages')"
|
||||
type="number"
|
||||
/>
|
||||
<QSelect
|
||||
v-model="data.pickup"
|
||||
:options="optionsList"
|
||||
option-value="id"
|
||||
option-label="description"
|
||||
emit-value
|
||||
:label="t('claim.basicData.pickup')"
|
||||
map-options
|
||||
use-input
|
||||
:input-debounce="0"
|
||||
>
|
||||
</QSelect>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormModel>
|
||||
|
|
|
@ -1,46 +1,17 @@
|
|||
<script setup>
|
||||
import LeftMenu from 'components/LeftMenu.vue';
|
||||
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import VnCard from 'components/common/VnCard.vue';
|
||||
import ClaimDescriptor from './ClaimDescriptor.vue';
|
||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
import useCardSize from 'src/composables/useCardSize';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const { t } = useI18n();
|
||||
import ClaimFilter from '../ClaimFilter.vue';
|
||||
</script>
|
||||
<template>
|
||||
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
|
||||
<VnSearchbar
|
||||
data-key="ClaimList"
|
||||
url="Claims/filter"
|
||||
:label="t('Search claim')"
|
||||
:info="t('You can search by claim id or customer name')"
|
||||
/>
|
||||
</Teleport>
|
||||
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
|
||||
<QScrollArea class="fit">
|
||||
<ClaimDescriptor />
|
||||
<QSeparator />
|
||||
<LeftMenu source="card" />
|
||||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<QPageContainer>
|
||||
<QPage>
|
||||
<VnSubToolbar />
|
||||
<div :class="useCardSize()">
|
||||
<RouterView></RouterView>
|
||||
</div>
|
||||
</QPage>
|
||||
</QPageContainer>
|
||||
<VnCard
|
||||
data-key="Claim"
|
||||
base-url="Claims"
|
||||
:descriptor="ClaimDescriptor"
|
||||
:filter-panel="ClaimFilter"
|
||||
search-data-key="ClaimList"
|
||||
search-url="Claims/filter"
|
||||
searchbar-label="Search claim"
|
||||
searchbar-info="You can search by claim id or customer name"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Search claim: Buscar reclamación
|
||||
You can search by claim id or customer name: Puedes buscar por id de la reclamación o nombre del cliente
|
||||
Details: Detalles
|
||||
Notes: Notas
|
||||
Action: Acción
|
||||
</i18n>
|
||||
|
|
|
@ -11,6 +11,7 @@ import VnLv from 'src/components/ui/VnLv.vue';
|
|||
import useCardDescription from 'src/composables/useCardDescription';
|
||||
import VnUserLink from 'src/components/ui/VnUserLink.vue';
|
||||
import { getUrl } from 'src/composables/getUrl';
|
||||
import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue';
|
||||
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
|
@ -73,8 +74,9 @@ const filter = {
|
|||
|
||||
const STATE_COLOR = {
|
||||
pending: 'warning',
|
||||
managed: 'info',
|
||||
incomplete: 'info',
|
||||
resolved: 'positive',
|
||||
canceled: 'negative',
|
||||
};
|
||||
function stateColor(code) {
|
||||
return STATE_COLOR[code];
|
||||
|
@ -127,17 +129,24 @@ onMounted(async () => {
|
|||
</VnLv>
|
||||
<VnLv
|
||||
v-if="entity.worker"
|
||||
:label="t('claim.card.assignedTo')"
|
||||
:label="t('claim.card.attendedBy')"
|
||||
:value="entity.worker.user.name"
|
||||
>
|
||||
<template #value>
|
||||
<VnUserLink
|
||||
:name="entity.worker.user.name"
|
||||
:name="entity.worker.user.nickname"
|
||||
:worker-id="entity.worker.id"
|
||||
/>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv :label="t('claim.card.zone')" :value="entity.ticket?.zone?.name" />
|
||||
<VnLv :label="t('claim.card.zone')">
|
||||
<template #value>
|
||||
<span class="link">
|
||||
{{ entity.ticket?.zone?.name }}
|
||||
<ZoneDescriptorProxy :id="entity.ticket?.zone?.id" />
|
||||
</span>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv
|
||||
:label="t('claim.card.province')"
|
||||
:value="entity.ticket?.address?.province?.name"
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
|
|||
import { useRoute } from 'vue-router';
|
||||
import CrudModel from 'components/CrudModel.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import { tMobile } from 'composables/tMobile';
|
||||
|
||||
const route = useRoute();
|
||||
|
@ -161,7 +161,7 @@ const columns = computed(() => [
|
|||
auto-width
|
||||
@keyup.ctrl.enter.stop="claimDevelopmentForm.saveChanges()"
|
||||
>
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
v-model="row[col.model]"
|
||||
:options="col.options"
|
||||
:option-value="col.optionValue"
|
||||
|
@ -181,7 +181,7 @@ const columns = computed(() => [
|
|||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnSelectFilter>
|
||||
</VnSelect>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #item="props">
|
||||
|
@ -198,7 +198,7 @@ const columns = computed(() => [
|
|||
<QList dense>
|
||||
<QItem v-for="col in props.cols" :key="col.name">
|
||||
<QItemSection>
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
:label="col.label"
|
||||
v-model="props.row[col.model]"
|
||||
:options="col.options"
|
||||
|
|
|
@ -45,20 +45,25 @@ async function onFetchClaim(data) {
|
|||
|
||||
const amount = ref();
|
||||
const amountClaimed = ref();
|
||||
async function onFetch(rows) {
|
||||
async function onFetch(rows, newRows) {
|
||||
if (newRows) rows = newRows;
|
||||
amount.value = 0;
|
||||
amountClaimed.value = 0;
|
||||
if (!rows || !rows.length) return;
|
||||
|
||||
amount.value = rows.reduce(
|
||||
(accumulator, { sale }) => accumulator + sale.price * sale.quantity,
|
||||
0
|
||||
);
|
||||
for (const row of rows) {
|
||||
const { sale } = row;
|
||||
amount.value = amount.value + totalRow(sale);
|
||||
const price = row.quantity * sale.price;
|
||||
const discount = (sale.discount * price) / 100;
|
||||
amountClaimed.value = amountClaimed.value + (price - discount);
|
||||
}
|
||||
}
|
||||
|
||||
amountClaimed.value = rows.reduce(
|
||||
(accumulator, line) => accumulator + line.sale.price * line.quantity,
|
||||
0
|
||||
);
|
||||
function totalRow({ price, quantity, discount }) {
|
||||
const amount = price * quantity;
|
||||
const appliedDiscount = (discount * amount) / 100;
|
||||
return amount - appliedDiscount;
|
||||
}
|
||||
|
||||
const columns = computed(() => [
|
||||
|
@ -102,12 +107,7 @@ const columns = computed(() => [
|
|||
{
|
||||
name: 'total',
|
||||
label: t('Total'),
|
||||
field: ({ sale }) => {
|
||||
const amount = sale.price * sale.quantity;
|
||||
const appliedDiscount = (sale.discount * amount) / 100;
|
||||
|
||||
return amount - appliedDiscount;
|
||||
},
|
||||
field: ({ sale }) => totalRow(sale),
|
||||
format: (value) => toCurrency(value),
|
||||
sortable: true,
|
||||
},
|
||||
|
@ -121,11 +121,6 @@ async function fetchMana() {
|
|||
mana.value = response.data;
|
||||
}
|
||||
|
||||
async function updateQuantity({ id, quantity }) {
|
||||
if (!id) return;
|
||||
await axios.patch(`ClaimBeginnings/${id}`, { quantity });
|
||||
}
|
||||
|
||||
async function updateDiscount({ saleFk, discount, canceller }) {
|
||||
const body = { salesIds: [saleFk], newDiscount: discount };
|
||||
const claimId = claim.value.ticketFk;
|
||||
|
@ -134,6 +129,7 @@ async function updateDiscount({ saleFk, discount, canceller }) {
|
|||
await axios.post(query, body, {
|
||||
signal: canceller.signal,
|
||||
});
|
||||
await claimLinesForm.value.reload();
|
||||
}
|
||||
|
||||
function onUpdateDiscount(response) {
|
||||
|
@ -155,6 +151,13 @@ function showImportDialog() {
|
|||
})
|
||||
.onOk(() => claimLinesForm.value.reload());
|
||||
}
|
||||
|
||||
async function saveWhenHasChanges() {
|
||||
if (claimLinesForm.value.getChanges().updates) {
|
||||
await claimLinesForm.value.onSubmit();
|
||||
await claimLinesForm.value.reload();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<Teleport to="#st-data" v-if="stateStore.isSubToolbarShown()">
|
||||
|
@ -181,161 +184,134 @@ function showImportDialog() {
|
|||
@on-fetch="onFetchClaim"
|
||||
auto-load
|
||||
/>
|
||||
<div class="column items-center">
|
||||
<div class="list">
|
||||
<CrudModel
|
||||
data-key="ClaimLines"
|
||||
ref="claimLinesForm"
|
||||
:url="`Claims/${route.params.id}/lines`"
|
||||
save-url="ClaimBeginnings/crud"
|
||||
:filter="linesFilter"
|
||||
@on-fetch="onFetch"
|
||||
@save-changes="onFetch"
|
||||
v-model:selected="selected"
|
||||
:default-save="false"
|
||||
:default-reset="false"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<QTable
|
||||
:columns="columns"
|
||||
:rows="rows"
|
||||
:dense="$q.screen.lt.md"
|
||||
row-key="id"
|
||||
selection="multiple"
|
||||
v-model:selected="selected"
|
||||
:grid="$q.screen.lt.md"
|
||||
>
|
||||
<template #body-cell-claimed="{ row, value }">
|
||||
<QTd auto-width align="right" class="text-primary">
|
||||
<span>{{ value }}</span>
|
||||
|
||||
<QPopupEdit
|
||||
v-model="row.quantity"
|
||||
v-slot="scope"
|
||||
:title="t('Claimed quantity')"
|
||||
@update:model-value="updateQuantity(row)"
|
||||
buttons
|
||||
>
|
||||
<QInput
|
||||
v-model="scope.value"
|
||||
type="number"
|
||||
dense
|
||||
autofocus
|
||||
@keyup.enter="scope.set"
|
||||
@focus="($event) => $event.target.select()"
|
||||
/>
|
||||
</QPopupEdit>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-description="{ row, value }">
|
||||
<QTd auto-width align="right" class="text-primary">
|
||||
{{ value }}
|
||||
<ItemDescriptorProxy
|
||||
:id="row.sale.itemFk"
|
||||
></ItemDescriptorProxy>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-discount="{ row, value, rowIndex }">
|
||||
<QTd auto-width align="right" class="text-primary">
|
||||
{{ value }}
|
||||
<VnDiscount
|
||||
:quantity="row.quantity"
|
||||
:price="row.sale.price"
|
||||
:discount="row.sale.discount"
|
||||
:mana="mana"
|
||||
:promise="updateDiscount"
|
||||
:data="{ saleFk: row.sale.id, rowIndex: rowIndex }"
|
||||
@on-update="onUpdateDiscount"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
<!-- View for grid mode -->
|
||||
<template #item="props">
|
||||
<div
|
||||
class="q-mb-md col-12 grid-style-transition"
|
||||
:style="props.selected ? 'transform: scale(0.95);' : ''"
|
||||
>
|
||||
<QCard>
|
||||
<QCardSection>
|
||||
<QCheckbox v-model="props.selected" />
|
||||
</QCardSection>
|
||||
<QSeparator inset />
|
||||
<QList dense>
|
||||
<QItem
|
||||
v-for="column of props.cols"
|
||||
:key="column.name"
|
||||
>
|
||||
<QItemSection>
|
||||
<QItemLabel caption>
|
||||
{{ column.label }}
|
||||
<div class="q-pa-md">
|
||||
<CrudModel
|
||||
data-key="ClaimLines"
|
||||
ref="claimLinesForm"
|
||||
:url="`Claims/${route.params.id}/lines`"
|
||||
save-url="ClaimBeginnings/crud"
|
||||
:filter="linesFilter"
|
||||
@on-fetch="onFetch"
|
||||
v-model:selected="selected"
|
||||
:default-save="false"
|
||||
:default-reset="false"
|
||||
auto-load
|
||||
:limit="0"
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<QTable
|
||||
:columns="columns"
|
||||
:rows="rows"
|
||||
:dense="$q.screen.lt.md"
|
||||
row-key="id"
|
||||
selection="multiple"
|
||||
v-model:selected="selected"
|
||||
:grid="$q.screen.lt.md"
|
||||
>
|
||||
<template #body-cell-claimed="{ row }">
|
||||
<QTd auto-width align="right" class="text-primary">
|
||||
<QInput
|
||||
v-model="row.quantity"
|
||||
type="number"
|
||||
dense
|
||||
@keyup.enter="saveWhenHasChanges()"
|
||||
@blur="saveWhenHasChanges()"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-description="{ row, value }">
|
||||
<QTd auto-width align="right" class="text-primary">
|
||||
{{ value }}
|
||||
<ItemDescriptorProxy
|
||||
:id="row.sale.itemFk"
|
||||
></ItemDescriptorProxy>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-discount="{ row, value, rowIndex }">
|
||||
<QTd auto-width align="right" class="text-primary">
|
||||
{{ value }}
|
||||
<VnDiscount
|
||||
:quantity="row.quantity"
|
||||
:price="row.sale.price"
|
||||
:discount="row.sale.discount"
|
||||
:mana="mana"
|
||||
:promise="updateDiscount"
|
||||
:data="{ saleFk: row.sale.id, rowIndex: rowIndex }"
|
||||
@on-update="onUpdateDiscount"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
<!-- View for grid mode -->
|
||||
<template #item="props">
|
||||
<div
|
||||
class="q-mb-md col-12 grid-style-transition"
|
||||
:style="props.selected ? 'transform: scale(0.95);' : ''"
|
||||
>
|
||||
<QCard>
|
||||
<QCardSection>
|
||||
<QCheckbox v-model="props.selected" />
|
||||
</QCardSection>
|
||||
<QSeparator inset />
|
||||
<QList dense>
|
||||
<QItem
|
||||
v-for="column of props.cols"
|
||||
:key="column.name"
|
||||
>
|
||||
<QItemSection>
|
||||
<QItemLabel caption>
|
||||
{{ column.label }}
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
<QItemSection side>
|
||||
<template v-if="column.name === 'claimed'">
|
||||
<QItemLabel class="text-primary">
|
||||
<QInput
|
||||
v-model="props.row.quantity"
|
||||
type="number"
|
||||
dense
|
||||
autofocus
|
||||
@keyup.enter="
|
||||
saveWhenHasChanges()
|
||||
"
|
||||
@blur="saveWhenHasChanges()"
|
||||
/>
|
||||
</QItemLabel>
|
||||
</QItemSection>
|
||||
<QItemSection side>
|
||||
<template
|
||||
v-if="column.name === 'claimed'"
|
||||
>
|
||||
<QItemLabel class="text-primary">
|
||||
{{ column.value }}
|
||||
<QPopupEdit
|
||||
v-model="props.row.quantity"
|
||||
v-slot="scope"
|
||||
:title="t('Claimed quantity')"
|
||||
@update:model-value="
|
||||
updateQuantity(props.row)
|
||||
"
|
||||
buttons
|
||||
>
|
||||
<QInput
|
||||
v-model="scope.value"
|
||||
type="number"
|
||||
dense
|
||||
autofocus
|
||||
@keyup.enter="scope.set"
|
||||
@focus="
|
||||
($event) =>
|
||||
$event.target.select()
|
||||
"
|
||||
/>
|
||||
</QPopupEdit>
|
||||
</QItemLabel>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="column.name === 'discount'"
|
||||
>
|
||||
<QItemLabel class="text-primary">
|
||||
{{ column.value }}
|
||||
<VnDiscount
|
||||
:quantity="props.row.quantity"
|
||||
:price="props.row.sale.price"
|
||||
:discount="
|
||||
props.row.sale.discount
|
||||
"
|
||||
:mana="mana"
|
||||
:promise="updateDiscount"
|
||||
:data="{
|
||||
saleFk: props.row.sale.id,
|
||||
rowIndex: props.rowIndex,
|
||||
}"
|
||||
@on-update="onUpdateDiscount"
|
||||
/>
|
||||
</QItemLabel>
|
||||
</template>
|
||||
<template v-else>
|
||||
<QItemLabel>
|
||||
{{ column.value }}
|
||||
</QItemLabel>
|
||||
</template>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</QList>
|
||||
</QCard>
|
||||
</div>
|
||||
</template>
|
||||
</QTable>
|
||||
</template>
|
||||
</CrudModel>
|
||||
</div>
|
||||
</template>
|
||||
<template
|
||||
v-else-if="column.name === 'discount'"
|
||||
>
|
||||
<QItemLabel class="text-primary">
|
||||
{{ column.value }}
|
||||
<VnDiscount
|
||||
:quantity="props.row.quantity"
|
||||
:price="props.row.sale.price"
|
||||
:discount="
|
||||
props.row.sale.discount
|
||||
"
|
||||
:mana="mana"
|
||||
:promise="updateDiscount"
|
||||
:data="{
|
||||
saleFk: props.row.sale.id,
|
||||
rowIndex: props.rowIndex,
|
||||
}"
|
||||
@on-update="onUpdateDiscount"
|
||||
/>
|
||||
</QItemLabel>
|
||||
</template>
|
||||
<template v-else>
|
||||
<QItemLabel>
|
||||
{{ column.value }}
|
||||
</QItemLabel>
|
||||
</template>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</QList>
|
||||
</QCard>
|
||||
</div>
|
||||
</template>
|
||||
</QTable>
|
||||
</template>
|
||||
</CrudModel>
|
||||
</div>
|
||||
|
||||
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
||||
|
|
|
@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n';
|
|||
import { useRoute } from 'vue-router';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import { toDate, toCurrency, toPercentage } from 'filters/index';
|
||||
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
|
||||
import axios from 'axios';
|
||||
|
||||
defineEmits([...useDialogPluginComponent.emits]);
|
||||
|
@ -34,6 +35,7 @@ const columns = computed(() => [
|
|||
label: t('Quantity'),
|
||||
field: (row) => row.quantity,
|
||||
sortable: true,
|
||||
default: 0,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
|
@ -74,7 +76,6 @@ async function importLines() {
|
|||
const body = sales.map((row) => ({
|
||||
claimFk: route.params.id,
|
||||
saleFk: row.saleFk,
|
||||
quantity: row.quantity,
|
||||
}));
|
||||
|
||||
canceller = new AbortController();
|
||||
|
@ -118,7 +119,6 @@ function cancel() {
|
|||
<QBtn icon="close" flat round dense v-close-popup />
|
||||
</QCardSection>
|
||||
<QTable
|
||||
class="my-sticky-header-table"
|
||||
:columns="columns"
|
||||
:rows="claimableSales"
|
||||
row-key="saleFk"
|
||||
|
@ -126,7 +126,14 @@ function cancel() {
|
|||
v-model:selected="selected"
|
||||
square
|
||||
flat
|
||||
/>
|
||||
>
|
||||
<template #body-cell-description="{ row, value }">
|
||||
<QTd auto-width align="right" class="link">
|
||||
{{ value }}
|
||||
<ItemDescriptorProxy :id="row.itemFk"></ItemDescriptorProxy>
|
||||
</QTd>
|
||||
</template>
|
||||
</QTable>
|
||||
<QSeparator />
|
||||
<QCardActions align="right">
|
||||
<QBtn :label="t('globals.cancel')" color="primary" flat @click="cancel" />
|
||||
|
@ -148,33 +155,6 @@ function cancel() {
|
|||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.my-sticky-header-table {
|
||||
height: 400px;
|
||||
|
||||
thead tr th {
|
||||
position: sticky;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
thead tr:first-child th {
|
||||
/* this is when the loading indicator appears */
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&.q-table--loading thead tr:last-child th {
|
||||
/* height of all previous header rows */
|
||||
top: 48px;
|
||||
}
|
||||
|
||||
// /* prevent scrolling behind sticky top row on focus */
|
||||
tbody {
|
||||
/* height of all previous header rows */
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Available sales lines: Líneas de venta disponibles
|
||||
|
|
|
@ -16,7 +16,7 @@ const claimId = computed(() => $props.id || route.params.id);
|
|||
|
||||
const claimFilter = {
|
||||
where: { claimFk: claimId.value },
|
||||
fields: ['created', 'workerFk', 'text'],
|
||||
fields: ['id', 'created', 'workerFk', 'text'],
|
||||
include: {
|
||||
relation: 'worker',
|
||||
scope: {
|
||||
|
@ -38,10 +38,11 @@ const body = {
|
|||
</script>
|
||||
<template>
|
||||
<VnNotes
|
||||
style="overflow-y: auto"
|
||||
:add-note="$props.addNote"
|
||||
url="claimObservations"
|
||||
:add-note="$props.addNote"
|
||||
:filter="claimFilter"
|
||||
:body="body"
|
||||
v-bind="$attrs"
|
||||
style="overflow-y: auto"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
import { onMounted, ref, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { toDate, toCurrency } from 'src/filters';
|
||||
import CardSummary from 'components/ui/CardSummary.vue';
|
||||
|
@ -13,8 +13,11 @@ import VnUserLink from 'src/components/ui/VnUserLink.vue';
|
|||
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
|
||||
import VnTitle from 'src/components/common/VnTitle.vue';
|
||||
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
|
||||
import axios from 'axios';
|
||||
import dashIfEmpty from 'src/filters/dashIfEmpty';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const { getTokenMultimedia } = useSession();
|
||||
const token = getTokenMultimedia();
|
||||
|
@ -27,7 +30,7 @@ const $props = defineProps({
|
|||
});
|
||||
|
||||
const entityId = computed(() => $props.id || route.params.id);
|
||||
|
||||
const ClaimStates = ref([]);
|
||||
const claimUrl = ref();
|
||||
const salixUrl = ref();
|
||||
const claimDmsRef = ref();
|
||||
|
@ -99,8 +102,9 @@ const detailsColumns = ref([
|
|||
|
||||
const STATE_COLOR = {
|
||||
pending: 'warning',
|
||||
managed: 'info',
|
||||
incomplete: 'info',
|
||||
resolved: 'positive',
|
||||
canceled: 'negative',
|
||||
};
|
||||
function stateColor(code) {
|
||||
return STATE_COLOR[code];
|
||||
|
@ -162,6 +166,10 @@ function openDialog(dmsId) {
|
|||
multimediaSlide.value = dmsId;
|
||||
multimediaDialog.value = true;
|
||||
}
|
||||
async function changeState(value) {
|
||||
await axios.patch(`Claims/updateClaim/${entityId.value}`, { claimStateFk: value });
|
||||
router.go(route.fullPath);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -171,6 +179,7 @@ function openDialog(dmsId) {
|
|||
@on-fetch="(data) => setClaimDms(data)"
|
||||
ref="claimDmsRef"
|
||||
/>
|
||||
<FetchData url="ClaimStates" @on-fetch="(data) => (ClaimStates = data)" auto-load />
|
||||
<CardSummary
|
||||
ref="summary"
|
||||
:url="`Claims/${entityId}/getSummary`"
|
||||
|
@ -180,6 +189,36 @@ function openDialog(dmsId) {
|
|||
<template #header="{ entity: { claim } }">
|
||||
{{ claim.id }} - {{ claim.client.name }} ({{ claim.client.id }})
|
||||
</template>
|
||||
<template #header-right>
|
||||
<QBtnDropdown
|
||||
side
|
||||
top
|
||||
color="black"
|
||||
text-color="white"
|
||||
:label="t('ticket.summary.changeState')"
|
||||
>
|
||||
<QList>
|
||||
<QVirtualScroll
|
||||
style="max-height: 300px"
|
||||
:items="ClaimStates"
|
||||
separator
|
||||
v-slot="{ item, index }"
|
||||
>
|
||||
<QItem
|
||||
:key="index"
|
||||
dense
|
||||
clickable
|
||||
v-close-popup
|
||||
@click="changeState(item.id)"
|
||||
>
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ item.description }}</QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</QVirtualScroll>
|
||||
</QList>
|
||||
</QBtnDropdown>
|
||||
</template>
|
||||
<template #body="{ entity: { claim, salesClaimed, developments } }">
|
||||
<QCard class="vn-one">
|
||||
<VnTitle
|
||||
|
@ -222,8 +261,8 @@ function openDialog(dmsId) {
|
|||
</template>
|
||||
</VnLv>
|
||||
<VnLv
|
||||
:label="t('claim.summary.pickup')"
|
||||
:value="t(`claim.summary.${claim.pickup}`)"
|
||||
:label="t('claim.basicData.pickup')"
|
||||
:value="`${dashIfEmpty(claim.pickup)}`"
|
||||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-three">
|
||||
|
@ -280,6 +319,48 @@ function openDialog(dmsId) {
|
|||
</template>
|
||||
</QTable>
|
||||
</QCard>
|
||||
<QCard class="vn-two" v-if="claimDms.length > 0">
|
||||
<VnTitle
|
||||
:url="`#/claim/${entityId}/photos`"
|
||||
:text="t('claim.summary.photos')"
|
||||
/>
|
||||
<div class="container">
|
||||
<div
|
||||
class="multimedia-container"
|
||||
v-for="(media, index) of claimDms"
|
||||
:key="index"
|
||||
>
|
||||
<div class="relative-position">
|
||||
<QIcon
|
||||
name="play_circle"
|
||||
color="primary"
|
||||
size="xl"
|
||||
class="absolute-center zindex"
|
||||
v-if="media.isVideo"
|
||||
@click.stop="openDialog(media.dmsFk)"
|
||||
>
|
||||
<QTooltip>Video</QTooltip>
|
||||
</QIcon>
|
||||
<QCard class="multimedia relative-position">
|
||||
<QImg
|
||||
:src="media.url"
|
||||
class="rounded-borders cursor-pointer fit"
|
||||
@click="openDialog(media.dmsFk)"
|
||||
v-if="!media.isVideo"
|
||||
>
|
||||
</QImg>
|
||||
<video
|
||||
:src="media.url"
|
||||
class="rounded-borders cursor-pointer fit"
|
||||
muted="muted"
|
||||
v-if="media.isVideo"
|
||||
@click="openDialog(media.dmsFk)"
|
||||
/>
|
||||
</QCard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</QCard>
|
||||
<QCard class="vn-two" v-if="developments.length > 0">
|
||||
<VnTitle
|
||||
:url="claimUrl + 'development'"
|
||||
|
@ -302,49 +383,6 @@ function openDialog(dmsId) {
|
|||
</template>
|
||||
</QTable>
|
||||
</QCard>
|
||||
<QCard class="vn-max" v-if="claimDms.length > 0">
|
||||
<VnTitle
|
||||
:url="`#/claim/${entityId}/photos`"
|
||||
:text="t('claim.summary.photos')"
|
||||
/>
|
||||
<div class="container">
|
||||
<div
|
||||
class="multimedia-container"
|
||||
v-for="(media, index) of claimDms"
|
||||
:key="index"
|
||||
>
|
||||
<div class="relative-position">
|
||||
<QIcon
|
||||
name="play_circle"
|
||||
color="primary"
|
||||
size="xl"
|
||||
class="absolute-center zindex"
|
||||
v-if="media.isVideo"
|
||||
@click.stop="openDialog(media.dmsFk)"
|
||||
>
|
||||
<QTooltip>Video</QTooltip>header
|
||||
</QIcon>
|
||||
<QCard class="multimedia relative-position">
|
||||
<QImg
|
||||
:src="media.url"
|
||||
class="rounded-borders cursor-pointer fit"
|
||||
@click="openDialog(media.dmsFk)"
|
||||
v-if="!media.isVideo"
|
||||
>
|
||||
</QImg>
|
||||
<video
|
||||
:src="media.url"
|
||||
class="rounded-borders cursor-pointer fit"
|
||||
muted="muted"
|
||||
v-if="media.isVideo"
|
||||
@click="openDialog(media.dmsFk)"
|
||||
/>
|
||||
</QCard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</QCard>
|
||||
|
||||
<QCard class="vn-max">
|
||||
<VnTitle :url="claimUrl + 'action'" :text="t('claim.summary.actions')" />
|
||||
<div id="slider-container" class="q-px-xl q-py-md">
|
||||
|
@ -448,4 +486,8 @@ function openDialog(dmsId) {
|
|||
.zindex {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.change-state {
|
||||
width: 10%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
|
|||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
||||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
|
||||
|
@ -64,7 +64,7 @@ const states = ref();
|
|||
<QSkeleton type="QInput" class="full-width" />
|
||||
</QItemSection>
|
||||
<QItemSection v-if="workers">
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
:label="t('Salesperson')"
|
||||
v-model="params.salesPersonFk"
|
||||
@update:model-value="searchFn()"
|
||||
|
@ -87,7 +87,7 @@ const states = ref();
|
|||
<QSkeleton type="QInput" class="full-width" />
|
||||
</QItemSection>
|
||||
<QItemSection v-if="workers">
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
:label="t('Attender')"
|
||||
v-model="params.attenderFk"
|
||||
@update:model-value="searchFn()"
|
||||
|
@ -110,7 +110,7 @@ const states = ref();
|
|||
<QSkeleton type="QInput" class="full-width" />
|
||||
</QItemSection>
|
||||
<QItemSection v-if="workers">
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
:label="t('Responsible')"
|
||||
v-model="params.claimResponsibleFk"
|
||||
@update:model-value="searchFn()"
|
||||
|
@ -133,7 +133,7 @@ const states = ref();
|
|||
<QSkeleton type="QInput" class="full-width" />
|
||||
</QItemSection>
|
||||
<QItemSection v-if="states">
|
||||
<VnSelectFilter
|
||||
<VnSelect
|
||||
:label="t('State')"
|
||||
v-model="params.claimStateFk"
|
||||
@update:model-value="searchFn()"
|
||||
|
@ -149,6 +149,15 @@ const states = ref();
|
|||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<QCheckbox
|
||||
v-model="params.myTeam"
|
||||
:label="t('myTeam')"
|
||||
toggle-indeterminate
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QSeparator />
|
||||
<QExpansionItem :label="t('More options')" expand-separator>
|
||||
<!-- <QItem>
|
||||
|
@ -192,6 +201,7 @@ en:
|
|||
claimResponsibleFk: Responsible
|
||||
claimStateFk: State
|
||||
created: Created
|
||||
myTeam: My team
|
||||
es:
|
||||
params:
|
||||
search: Contiene
|
||||
|
@ -211,4 +221,5 @@ es:
|
|||
Item: Artículo
|
||||
Created: Creada
|
||||
More options: Más opciones
|
||||
myTeam: Mi equipo
|
||||
</i18n>
|
||||
|
|
|
@ -12,7 +12,6 @@ import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorP
|
|||
import VnUserLink from 'src/components/ui/VnUserLink.vue';
|
||||
import ClaimSummary from './Card/ClaimSummary.vue';
|
||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||
import { getUrl } from 'src/composables/getUrl';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const router = useRouter();
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Search claim: Buscar reclamación
|
||||
You can search by claim id or customer name: Puedes buscar por id de la reclamación o nombre del cliente
|
|
@ -1,18 +1,21 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { onBeforeMount, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const addresses = ref([]);
|
||||
const client = ref(null);
|
||||
const provincesLocation = ref([]);
|
||||
|
||||
const consigneeFilter = {
|
||||
const addressFilter = {
|
||||
fields: [
|
||||
'id',
|
||||
'isDefaultAddress',
|
||||
|
@ -44,6 +47,42 @@ const consigneeFilter = {
|
|||
],
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
const { id } = route.params;
|
||||
getAddressesData(id);
|
||||
getClientData(id);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
(newValue) => {
|
||||
if (!newValue) return;
|
||||
getAddressesData(newValue);
|
||||
getClientData(newValue);
|
||||
}
|
||||
);
|
||||
|
||||
const getAddressesData = async (id) => {
|
||||
try {
|
||||
const { data } = await axios.get(`Clients/${id}/addresses`, {
|
||||
params: { filter: JSON.stringify(addressFilter) },
|
||||
});
|
||||
addresses.value = data;
|
||||
sortAddresses();
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
};
|
||||
|
||||
const getClientData = async (id) => {
|
||||
try {
|
||||
const { data } = await axios.get(`Clients/${id}`);
|
||||
client.value = data;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
};
|
||||
|
||||
const setProvince = (provinceFk) => {
|
||||
const result = provincesLocation.value.filter(
|
||||
(province) => province.id === provinceFk
|
||||
|
@ -51,16 +90,38 @@ const setProvince = (provinceFk) => {
|
|||
return result[0]?.name || '';
|
||||
};
|
||||
|
||||
const toCustomerConsigneeCreate = () => {
|
||||
router.push({ name: 'CustomerConsigneeCreate' });
|
||||
const isDefaultAddress = (address) => {
|
||||
return client?.value?.defaultAddressFk === address.id ? 1 : 0;
|
||||
};
|
||||
|
||||
const toCustomerConsigneeEdit = (consigneeId) => {
|
||||
const setDefault = (address) => {
|
||||
const url = `Clients/${route.params.id}`;
|
||||
const payload = { defaultAddressFk: address.id };
|
||||
axios.patch(url, payload).then((res) => {
|
||||
if (res.data) {
|
||||
client.value.defaultAddressFk = res.data.defaultAddressFk;
|
||||
sortAddresses();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const sortAddresses = () => {
|
||||
if (!client.value || !addresses.value) return;
|
||||
addresses.value = addresses.value.sort((a, b) => {
|
||||
return isDefaultAddress(b) - isDefaultAddress(a);
|
||||
});
|
||||
};
|
||||
|
||||
const toCustomerAddressCreate = () => {
|
||||
router.push({ name: 'CustomerAddressCreate' });
|
||||
};
|
||||
|
||||
const toCustomerAddressEdit = (addressId) => {
|
||||
router.push({
|
||||
name: 'CustomerConsigneeEdit',
|
||||
name: 'CustomerAddressEdit',
|
||||
params: {
|
||||
id: route.params.id,
|
||||
consigneeId,
|
||||
addressId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -73,26 +134,41 @@ const toCustomerConsigneeEdit = (consigneeId) => {
|
|||
url="Provinces/location"
|
||||
/>
|
||||
|
||||
<QCard class="q-pa-lg">
|
||||
<VnPaginate
|
||||
data-key="CustomerConsignees"
|
||||
:url="`Clients/${route.params.id}/addresses`"
|
||||
order="id"
|
||||
auto-load
|
||||
:filter="consigneeFilter"
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<QCard
|
||||
v-for="(item, index) in rows"
|
||||
<div class="full-width flex justify-center">
|
||||
<QCard class="card-width q-pa-lg" v-if="addresses.length">
|
||||
<QCardSection>
|
||||
<div
|
||||
v-for="(item, index) in addresses"
|
||||
:key="index"
|
||||
class="address-card"
|
||||
:class="{
|
||||
'consignees-card': true,
|
||||
'q-mb-md': index < rows.length - 1,
|
||||
'q-mb-md': index < addresses.length - 1,
|
||||
'item-disabled': !item.isActive,
|
||||
}"
|
||||
@click="toCustomerConsigneeEdit(item.id)"
|
||||
@click="toCustomerAddressEdit(item.id)"
|
||||
>
|
||||
<div class="q-ml-xs q-mr-md flex items-center">
|
||||
<QIcon name="star" size="md" color="primary" />
|
||||
<QIcon
|
||||
:style="{
|
||||
'font-variation-settings': `'FILL' ${isDefaultAddress(
|
||||
item
|
||||
)}`,
|
||||
}"
|
||||
color="primary"
|
||||
name="star"
|
||||
size="md"
|
||||
@click.stop="!isDefaultAddress(item) && setDefault(item)"
|
||||
>
|
||||
<QTooltip>
|
||||
{{
|
||||
t(
|
||||
isDefaultAddress(item)
|
||||
? 'Default address'
|
||||
: 'Set as default'
|
||||
)
|
||||
}}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-weight-bold q-mb-sm">
|
||||
|
@ -126,9 +202,9 @@ const toCustomerConsigneeEdit = (consigneeId) => {
|
|||
|
||||
<div v-if="item.observations.length">
|
||||
<div
|
||||
:key="index"
|
||||
:key="obIndex"
|
||||
class="flex q-mb-sm"
|
||||
v-for="(observation, index) in item.observations"
|
||||
v-for="(observation, obIndex) in item.observations"
|
||||
>
|
||||
<div class="text-weight-bold q-mr-sm">
|
||||
{{ observation.observationType.description }}:
|
||||
|
@ -136,12 +212,13 @@ const toCustomerConsigneeEdit = (consigneeId) => {
|
|||
<div>{{ observation.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</QCard>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</QCard>
|
||||
</div>
|
||||
</QCardSection>
|
||||
</QCard>
|
||||
</div>
|
||||
|
||||
<QPageSticky :offset="[18, 18]">
|
||||
<QBtn @click.stop="toCustomerConsigneeCreate()" color="primary" fab icon="add" />
|
||||
<QBtn @click.stop="toCustomerAddressCreate()" color="primary" fab icon="add" />
|
||||
<QTooltip>
|
||||
{{ t('New consignee') }}
|
||||
</QTooltip>
|
||||
|
@ -149,8 +226,8 @@ const toCustomerConsigneeEdit = (consigneeId) => {
|
|||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.consignees-card {
|
||||
border: 2px solid var(--vn-accent-color);
|
||||
.address-card {
|
||||
border: 2px solid var(--vn-light-gray);
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
|
@ -160,6 +237,10 @@ const toCustomerConsigneeEdit = (consigneeId) => {
|
|||
background-color: var(--vn-accent-color);
|
||||
}
|
||||
}
|
||||
|
||||
.item-disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
|
@ -167,4 +248,6 @@ es:
|
|||
Is equalizated: Recargo de equivalencia
|
||||
Is logiflora allowed: Compra directa en Holanda
|
||||
New consignee: Nuevo consignatario
|
||||
Default address: Consignatario predeterminado
|
||||
Set as default: Establecer como predeterminado
|
||||
</i18n>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue