Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 6739-invoiceOut
This commit is contained in:
commit
975c844d65
98
CHANGELOG.md
98
CHANGELOG.md
|
@ -1,3 +1,100 @@
|
||||||
|
# Version 24.24 - 2024-06-11
|
||||||
|
|
||||||
|
### Added 🆕
|
||||||
|
|
||||||
|
- feat: 6942 hashtag in key : value summary by:jgallego
|
||||||
|
- feat: #6957: Rename FetchedTags instance tag by:Javier Segarra
|
||||||
|
- feat: refactor template by:Javier Segarra
|
||||||
|
- feat: refs #6600 Add option to add comment for photo motivation by:jorgep
|
||||||
|
- feat: refs #6942 test e2e tobook & toUnbook by:jorgep
|
||||||
|
- feat: refs #6942 to book summary button & reactive value by:jorgep
|
||||||
|
- feat: refs #6942 to unbook by:jorgep
|
||||||
|
- feat: refs #6942 url update by:jorgep
|
||||||
|
- feat: refs #6942 use correct currency in InvoiceIn components by:jorgep
|
||||||
|
- feat: refs #6942 vat rate total by:jorgep
|
||||||
|
- feat: refs #7494 new icons (7494-icons) by:alexm
|
||||||
|
- feat: refs #7494 new icons by:alexm
|
||||||
|
- feat: refs #7542 drop space by:jorgep
|
||||||
|
- feat: refs #7542 empty by:jorgep
|
||||||
|
- fix: refs #6942 changes and new features by:jorgep
|
||||||
|
- fix: style by:Javier Segarra
|
||||||
|
- style: color transparent when is fetive by:Javier Segarra
|
||||||
|
- style: fix color when is empty by:Javier Segarra
|
||||||
|
- style: reset poc style (6957_refactorFetechedTags) by:Javier Segarra
|
||||||
|
- style: reset poc style by:Javier Segarra
|
||||||
|
- style updates by:Javier Segarra
|
||||||
|
|
||||||
|
### Changed 📦
|
||||||
|
|
||||||
|
- feat: refactor template by:Javier Segarra
|
||||||
|
- perf: 6957 add color as new shared variable by:Javier Segarra
|
||||||
|
- perf: 6957 change fetchedTags color by:Javier Segarra
|
||||||
|
- perf: remove local tree variable by:Javier Segarra
|
||||||
|
- refactor: add flat by:alexm
|
||||||
|
- refactor: refs #6600 replace QInput to VnInput by:jorgep
|
||||||
|
- refactor: refs #6652 improved defaulter section by:Jon
|
||||||
|
- refactor: refs #6942 Fix getTotalAmount function to correctly calculate the total amount in InvoiceInDueDay.vue by:jorgep
|
||||||
|
- refactor: refs #6942 new summary layout by:jorgep
|
||||||
|
- refactor: refs #6942 store key & actions by:jorgep
|
||||||
|
- refactor: refs #6942 summary by:jorgep
|
||||||
|
- refactor: refs #6942 use router hook by:jorgep
|
||||||
|
- refactor: refs #6942 WIP summary layout by:jorgep
|
||||||
|
|
||||||
|
### Fixed 🛠️
|
||||||
|
|
||||||
|
- fix: 9-12 by:Javier Segarra
|
||||||
|
- fix: defaulter icon by:alexm
|
||||||
|
- fix: refs #5186 validation by:jorgep
|
||||||
|
- fix: refs #6095 add reFfk null on search by:pablone
|
||||||
|
- fix: refs #6942 cardDescriptor use store if its popup or different source data by:jorgep
|
||||||
|
- fix: refs #6942 changes and new features by:jorgep
|
||||||
|
- fix: refs #6942 drop comments by:jorgep
|
||||||
|
- fix: refs #6942 drop console by:jorgep
|
||||||
|
- fix: refs #6942 drop console.log by:jorgep
|
||||||
|
- fix: refs #6942 e2e test (origin/6942-warmfix-fixFormModel) by:jorgep
|
||||||
|
- fix: refs #6942 e2e tests by:jorgep
|
||||||
|
- fix: refs #6942 e2e tests by:jorgep
|
||||||
|
- fix: refs #6942 fix emit on data saved by:jorgep
|
||||||
|
- fix: refs #6942 fix emit on reset by:jorgep
|
||||||
|
- fix: refs #6942 fix vncard by:jorgep
|
||||||
|
- fix: refs #6942 formModel & CardDescriptor by:jorgep
|
||||||
|
- fix: refs #6942 formModel watch changes & invoiceInCreate by:jorgep
|
||||||
|
- fix: refs #6942 import by:jorgep
|
||||||
|
- fix: refs #6942 reloading by:jorgep
|
||||||
|
- fix: refs #6942 rollback by:jorgep
|
||||||
|
- fix: refs #6942 selectable expense by:jorgep
|
||||||
|
- fix: refs #6942 skip e2e tests by:jorgep
|
||||||
|
- fix: refs #6942 table bottom highlight & drop isBooked field by:jorgep
|
||||||
|
- fix: refs #6942 tests e2e by:jorgep
|
||||||
|
- fix: refs #6942 tests & summary table spacing by:jorgep
|
||||||
|
- fix: refs #6942 unit tests by:jorgep
|
||||||
|
- fix: refs #6942 vnLocation by:jorgep
|
||||||
|
- fix: refs #6942 wip: formModel by:jorgep
|
||||||
|
- fix: refs #7542 use right panel by:jorgep
|
||||||
|
- fix: searchbar redirect by:alexm
|
||||||
|
- fix: style by:Javier Segarra
|
||||||
|
- fix: WorkerCalendarItem by:Javier Segarra
|
||||||
|
- mini fix by:wbuezas
|
||||||
|
- refs #6111 clean code fix changes by:carlossa
|
||||||
|
- refs #6111 fix merge, fix column by:carlossa
|
||||||
|
- refs #6111 fix qtable, actions, scroll by:carlossa
|
||||||
|
- refs #6111 fix routeList by:carlossa
|
||||||
|
- refs #6111 fix sticky by:carlossa
|
||||||
|
- refs #6111 fix trad remove logs by:carlossa
|
||||||
|
- refs #6111 fix visibleColumns by:carlossa
|
||||||
|
- refs #6111 routeList fix by:carlossa
|
||||||
|
- refs #6332 fix calendar by:carlossa
|
||||||
|
- refs #6332 fix colors by:carlossa
|
||||||
|
- refs #6332 fix festive by:carlossa
|
||||||
|
- refs #6820 fix BasicData Tickets by:carlossa
|
||||||
|
- refs #6820 fix error front by:carlossa
|
||||||
|
- refs #6820 fix traduction by:carlossa
|
||||||
|
- refs #7391 fix textarea by:carlossa
|
||||||
|
- refs #7396 fix summary by:carlossa
|
||||||
|
- Search childs fix by:wbuezas
|
||||||
|
- small fix by:wbuezas
|
||||||
|
- style: fix color when is empty by:Javier Segarra
|
||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
@ -11,6 +108,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
- (Item) => Se añade la opción de añadir un comentario del motivo de hacer una foto
|
- (Item) => Se añade la opción de añadir un comentario del motivo de hacer una foto
|
||||||
- (Worker) => Se añade la opción de crear un trabajador ajeno a la empresa
|
- (Worker) => Se añade la opción de crear un trabajador ajeno a la empresa
|
||||||
|
- (Route) => Ahora se muestran todos los cmrs
|
||||||
|
|
||||||
## [2418.01]
|
## [2418.01]
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,7 @@ pipeline {
|
||||||
sh 'quasar build'
|
sh 'quasar build'
|
||||||
script {
|
script {
|
||||||
def packageJson = readJSON file: 'package.json'
|
def packageJson = readJSON file: 'package.json'
|
||||||
env.VERSION = packageJson.version
|
env.VERSION = "${packageJson.version}-build${env.BUILD_ID}"
|
||||||
}
|
}
|
||||||
dockerBuild()
|
dockerBuild()
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ pipeline {
|
||||||
steps {
|
steps {
|
||||||
script {
|
script {
|
||||||
def packageJson = readJSON file: 'package.json'
|
def packageJson = readJSON file: 'package.json'
|
||||||
env.VERSION = packageJson.version
|
env.VERSION = "${packageJson.version}-build${env.BUILD_ID}"
|
||||||
}
|
}
|
||||||
withKubeConfig([
|
withKubeConfig([
|
||||||
serverUrl: "$KUBERNETES_API",
|
serverUrl: "$KUBERNETES_API",
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
features_types=(chore feat style)
|
||||||
|
changes_types=(refactor perf)
|
||||||
|
fix_types=(fix revert)
|
||||||
|
file="CHANGELOG.md"
|
||||||
|
file_tmp="temp_log.txt"
|
||||||
|
file_current_tmp="temp_current_log.txt"
|
||||||
|
|
||||||
|
setType(){
|
||||||
|
echo "### $1" >> $file_tmp
|
||||||
|
arr=("$@")
|
||||||
|
echo "" > $file_current_tmp
|
||||||
|
for i in "${arr[@]}"
|
||||||
|
do
|
||||||
|
git log --grep="$i" --oneline --no-merges --format="- %s %d by:%an" master..test >> $file_current_tmp
|
||||||
|
done
|
||||||
|
# remove duplicates
|
||||||
|
sort -o $file_current_tmp -u $file_current_tmp
|
||||||
|
cat $file_current_tmp >> $file_tmp
|
||||||
|
echo "" >> $file_tmp
|
||||||
|
# remove tmp current file
|
||||||
|
[ -e $file_current_tmp ] && rm $file_current_tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "# Version XX.XX - XXXX-XX-XX" >> $file_tmp
|
||||||
|
echo "" >> $file_tmp
|
||||||
|
|
||||||
|
setType "Added 🆕" "${features_types[@]}"
|
||||||
|
setType "Changed 📦" "${changes_types[@]}"
|
||||||
|
setType "Fixed 🛠️" "${fix_types[@]}"
|
||||||
|
|
||||||
|
cat $file >> $file_tmp
|
||||||
|
mv $file_tmp $file
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "salix-front",
|
"name": "salix-front",
|
||||||
"version": "24.26.2",
|
"version": "24.28.1",
|
||||||
"description": "Salix frontend",
|
"description": "Salix frontend",
|
||||||
"productName": "Salix",
|
"productName": "Salix",
|
||||||
"author": "Verdnatura",
|
"author": "Verdnatura",
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { Notify } from 'quasar';
|
|
||||||
import { useSession } from 'src/composables/useSession';
|
import { useSession } from 'src/composables/useSession';
|
||||||
import { Router } from 'src/router';
|
import { Router } from 'src/router';
|
||||||
import { i18n } from './i18n';
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const { t } = i18n.global;
|
const { notify } = useNotify();
|
||||||
|
|
||||||
axios.defaults.baseURL = '/api/';
|
axios.defaults.baseURL = '/api/';
|
||||||
|
|
||||||
|
@ -27,10 +26,7 @@ const onResponse = (response) => {
|
||||||
|
|
||||||
const isSaveRequest = method === 'patch';
|
const isSaveRequest = method === 'patch';
|
||||||
if (isSaveRequest) {
|
if (isSaveRequest) {
|
||||||
Notify.create({
|
notify('globals.dataSaved', 'positive');
|
||||||
message: t('globals.dataSaved'),
|
|
||||||
type: 'positive',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
|
@ -67,10 +63,7 @@ const onResponseError = (error) => {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
Notify.create({
|
notify(message, 'negative');
|
||||||
message: t(message),
|
|
||||||
type: 'negative',
|
|
||||||
});
|
|
||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
};
|
};
|
||||||
|
|
|
@ -27,7 +27,6 @@ export default {
|
||||||
this.$el.addEventListener('keyup', function (evt) {
|
this.$el.addEventListener('keyup', function (evt) {
|
||||||
if (evt.key === 'Enter') {
|
if (evt.key === 'Enter') {
|
||||||
const input = evt.target;
|
const input = evt.target;
|
||||||
console.log('input', input);
|
|
||||||
if (input.type == 'textarea' && evt.shiftKey) {
|
if (input.type == 'textarea' && evt.shiftKey) {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
let { selectionStart, selectionEnd } = input;
|
let { selectionStart, selectionEnd } = input;
|
||||||
|
|
|
@ -67,6 +67,10 @@ const $props = defineProps({
|
||||||
default: '',
|
default: '',
|
||||||
description: 'It is used for redirect on click "save and continue"',
|
description: 'It is used for redirect on click "save and continue"',
|
||||||
},
|
},
|
||||||
|
hasSubtoolbar: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
@ -75,6 +79,7 @@ const originalData = ref();
|
||||||
const vnPaginateRef = ref();
|
const vnPaginateRef = ref();
|
||||||
const formData = ref();
|
const formData = ref();
|
||||||
const saveButtonRef = ref(null);
|
const saveButtonRef = ref(null);
|
||||||
|
const watchChanges = ref();
|
||||||
const formUrl = computed(() => $props.url);
|
const formUrl = computed(() => $props.url);
|
||||||
|
|
||||||
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
|
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
|
||||||
|
@ -89,6 +94,7 @@ defineExpose({
|
||||||
saveChanges,
|
saveChanges,
|
||||||
getChanges,
|
getChanges,
|
||||||
formData,
|
formData,
|
||||||
|
vnPaginateRef,
|
||||||
});
|
});
|
||||||
|
|
||||||
async function fetch(data) {
|
async function fetch(data) {
|
||||||
|
@ -97,19 +103,26 @@ async function fetch(data) {
|
||||||
data.map((d) => (d.$index = $index++));
|
data.map((d) => (d.$index = $index++));
|
||||||
}
|
}
|
||||||
|
|
||||||
originalData.value = data && JSON.parse(JSON.stringify(data));
|
resetData(data);
|
||||||
formData.value = data && JSON.parse(JSON.stringify(data));
|
|
||||||
watch(formData, () => (hasChanges.value = true), { deep: true });
|
|
||||||
|
|
||||||
emit('onFetch', data);
|
emit('onFetch', data);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetData(data) {
|
||||||
|
if (!data) return;
|
||||||
|
originalData.value = JSON.parse(JSON.stringify(data));
|
||||||
|
formData.value = JSON.parse(JSON.stringify(data));
|
||||||
|
|
||||||
|
if (watchChanges.value) watchChanges.value(); //destoy watcher
|
||||||
|
watchChanges.value = watch(formData, () => (hasChanges.value = true), { deep: true });
|
||||||
|
}
|
||||||
|
|
||||||
async function reset() {
|
async function reset() {
|
||||||
await fetch(originalData.value);
|
await fetch(originalData.value);
|
||||||
hasChanges.value = false;
|
hasChanges.value = false;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line vue/no-dupe-keys
|
|
||||||
function filter(value, update, filterOptions) {
|
function filter(value, update, filterOptions) {
|
||||||
update(
|
update(
|
||||||
() => {
|
() => {
|
||||||
|
@ -135,7 +148,7 @@ async function onSubmit() {
|
||||||
await saveChanges($props.saveFn ? formData.value : null);
|
await saveChanges($props.saveFn ? formData.value : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onSumbitAndGo() {
|
async function onSubmitAndGo() {
|
||||||
await onSubmit();
|
await onSubmit();
|
||||||
push({ path: $props.goTo });
|
push({ path: $props.goTo });
|
||||||
}
|
}
|
||||||
|
@ -271,8 +284,9 @@ function isEmpty(obj) {
|
||||||
if (obj.length > 0) return false;
|
if (obj.length > 0) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reload() {
|
async function reload(params) {
|
||||||
vnPaginateRef.value.fetch();
|
const data = await vnPaginateRef.value.fetch(params);
|
||||||
|
fetch(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(formUrl, async () => {
|
watch(formUrl, async () => {
|
||||||
|
@ -284,10 +298,11 @@ watch(formUrl, async () => {
|
||||||
<VnPaginate
|
<VnPaginate
|
||||||
:url="url"
|
:url="url"
|
||||||
:limit="limit"
|
:limit="limit"
|
||||||
v-bind="$attrs"
|
|
||||||
@on-fetch="fetch"
|
@on-fetch="fetch"
|
||||||
|
@on-change="resetData"
|
||||||
:skeleton="false"
|
:skeleton="false"
|
||||||
ref="vnPaginateRef"
|
ref="vnPaginateRef"
|
||||||
|
v-bind="$attrs"
|
||||||
>
|
>
|
||||||
<template #body v-if="formData">
|
<template #body v-if="formData">
|
||||||
<slot
|
<slot
|
||||||
|
@ -298,8 +313,8 @@ watch(formUrl, async () => {
|
||||||
></slot>
|
></slot>
|
||||||
</template>
|
</template>
|
||||||
</VnPaginate>
|
</VnPaginate>
|
||||||
<SkeletonTable v-if="!formData" />
|
<SkeletonTable v-if="!formData" :columns="$attrs.columns?.length" />
|
||||||
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
|
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown() && hasSubtoolbar">
|
||||||
<QBtnGroup push style="column-gap: 10px">
|
<QBtnGroup push style="column-gap: 10px">
|
||||||
<slot name="moreBeforeActions" />
|
<slot name="moreBeforeActions" />
|
||||||
<QBtn
|
<QBtn
|
||||||
|
@ -324,7 +339,7 @@ watch(formUrl, async () => {
|
||||||
/>
|
/>
|
||||||
<QBtnDropdown
|
<QBtnDropdown
|
||||||
v-if="$props.goTo && $props.defaultSave"
|
v-if="$props.goTo && $props.defaultSave"
|
||||||
@click="onSumbitAndGo"
|
@click="onSubmitAndGo"
|
||||||
:label="tMobile('globals.saveAndContinue')"
|
:label="tMobile('globals.saveAndContinue')"
|
||||||
:title="t('globals.saveAndContinue')"
|
:title="t('globals.saveAndContinue')"
|
||||||
:disable="!hasChanges"
|
:disable="!hasChanges"
|
||||||
|
|
|
@ -12,6 +12,7 @@ import SkeletonForm from 'components/ui/SkeletonForm.vue';
|
||||||
import VnConfirm from './ui/VnConfirm.vue';
|
import VnConfirm from './ui/VnConfirm.vue';
|
||||||
import { tMobile } from 'src/composables/tMobile';
|
import { tMobile } from 'src/composables/tMobile';
|
||||||
import { useArrayData } from 'src/composables/useArrayData';
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
const { push } = useRouter();
|
const { push } = useRouter();
|
||||||
const quasar = useQuasar();
|
const quasar = useQuasar();
|
||||||
|
@ -20,6 +21,7 @@ const stateStore = useStateStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { validate } = useValidator();
|
const { validate } = useValidator();
|
||||||
const { notify } = useNotify();
|
const { notify } = useNotify();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
url: {
|
url: {
|
||||||
|
@ -28,7 +30,7 @@ const $props = defineProps({
|
||||||
},
|
},
|
||||||
model: {
|
model: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: null,
|
||||||
},
|
},
|
||||||
filter: {
|
filter: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
@ -81,18 +83,23 @@ const $props = defineProps({
|
||||||
default: '',
|
default: '',
|
||||||
description: 'It is used for redirect on click "save and continue"',
|
description: 'It is used for redirect on click "save and continue"',
|
||||||
},
|
},
|
||||||
|
reload: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['onFetch', 'onDataSaved']);
|
const emit = defineEmits(['onFetch', 'onDataSaved']);
|
||||||
|
const modelValue = computed(
|
||||||
|
() => $props.model ?? `formModel_${route?.meta?.title ?? route.name}`
|
||||||
|
).value;
|
||||||
const componentIsRendered = ref(false);
|
const componentIsRendered = ref(false);
|
||||||
const arrayData = useArrayData($props.model);
|
const arrayData = useArrayData(modelValue);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
// Si elegimos observar los cambios del form significa que inicialmente las actions estaran deshabilitadas
|
// Si elegimos observar los cambios del form significa que inicialmente las actions estaran deshabilitadas
|
||||||
const isResetting = ref(false);
|
const isResetting = ref(false);
|
||||||
const hasChanges = ref(!$props.observeFormChanges);
|
const hasChanges = ref(!$props.observeFormChanges);
|
||||||
const originalData = ref({});
|
const originalData = ref({});
|
||||||
const formData = computed(() => state.get($props.model));
|
const formData = computed(() => state.get(modelValue));
|
||||||
const formUrl = computed(() => $props.url);
|
const formUrl = computed(() => $props.url);
|
||||||
const defaultButtons = computed(() => ({
|
const defaultButtons = computed(() => ({
|
||||||
save: {
|
save: {
|
||||||
|
@ -114,11 +121,12 @@ onMounted(async () => {
|
||||||
nextTick(() => (componentIsRendered.value = true));
|
nextTick(() => (componentIsRendered.value = true));
|
||||||
|
|
||||||
// Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla
|
// Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla
|
||||||
state.set($props.model, $props.formInitialData);
|
state.set(modelValue, $props.formInitialData);
|
||||||
|
|
||||||
if ($props.autoLoad && !$props.formInitialData && $props.url) await fetch();
|
if (!$props.formInitialData) {
|
||||||
|
if ($props.autoLoad && $props.url) await fetch();
|
||||||
else if (arrayData.store.data) updateAndEmit('onFetch', arrayData.store.data);
|
else if (arrayData.store.data) updateAndEmit('onFetch', arrayData.store.data);
|
||||||
|
}
|
||||||
if ($props.observeFormChanges) {
|
if ($props.observeFormChanges) {
|
||||||
watch(
|
watch(
|
||||||
() => formData.value,
|
() => formData.value,
|
||||||
|
@ -161,8 +169,8 @@ onBeforeRouteLeave((to, from, next) => {
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
// Restauramos los datos originales en el store si se realizaron cambios en el formulario pero no se guardaron, evitando modificaciones erróneas.
|
// Restauramos los datos originales en el store si se realizaron cambios en el formulario pero no se guardaron, evitando modificaciones erróneas.
|
||||||
if (hasChanges.value) return state.set($props.model, originalData.value);
|
if (hasChanges.value) return state.set(modelValue, originalData.value);
|
||||||
if ($props.clearStoreOnUnmount) state.unset($props.model);
|
if ($props.clearStoreOnUnmount) state.unset(modelValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function fetch() {
|
async function fetch() {
|
||||||
|
@ -174,7 +182,7 @@ async function fetch() {
|
||||||
|
|
||||||
updateAndEmit('onFetch', data);
|
updateAndEmit('onFetch', data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
state.set($props.model, {});
|
state.set(modelValue, {});
|
||||||
originalData.value = {};
|
originalData.value = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,6 +205,7 @@ async function save() {
|
||||||
if ($props.urlCreate) notify('globals.dataCreated', 'positive');
|
if ($props.urlCreate) notify('globals.dataCreated', 'positive');
|
||||||
|
|
||||||
updateAndEmit('onDataSaved', formData.value, response?.data);
|
updateAndEmit('onDataSaved', formData.value, response?.data);
|
||||||
|
if ($props.reload) await arrayData.fetch({});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
notify('errors.writeRequest', 'negative');
|
notify('errors.writeRequest', 'negative');
|
||||||
|
@ -235,14 +244,20 @@ function filter(value, update, filterOptions) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateAndEmit(evt, val, res) {
|
function updateAndEmit(evt, val, res) {
|
||||||
state.set($props.model, val);
|
state.set(modelValue, val);
|
||||||
originalData.value = val && JSON.parse(JSON.stringify(val));
|
originalData.value = val && JSON.parse(JSON.stringify(val));
|
||||||
if (!$props.url) arrayData.store.data = val;
|
if (!$props.url) arrayData.store.data = val;
|
||||||
|
|
||||||
emit(evt, state.get($props.model), res);
|
emit(evt, state.get(modelValue), res);
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({ save, isLoading, hasChanges });
|
defineExpose({
|
||||||
|
save,
|
||||||
|
isLoading,
|
||||||
|
hasChanges,
|
||||||
|
reset,
|
||||||
|
fetch,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="column items-center full-width">
|
<div class="column items-center full-width">
|
||||||
|
|
|
@ -11,6 +11,7 @@ import VnSelect from 'src/components/common/VnSelect.vue';
|
||||||
import VnRow from 'components/ui/VnRow.vue';
|
import VnRow from 'components/ui/VnRow.vue';
|
||||||
import FetchData from 'components/FetchData.vue';
|
import FetchData from 'components/FetchData.vue';
|
||||||
import { useClipboard } from 'src/composables/useClipboard';
|
import { useClipboard } from 'src/composables/useClipboard';
|
||||||
|
import VnImg from 'src/components/ui/VnImg.vue';
|
||||||
|
|
||||||
const state = useState();
|
const state = useState();
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
|
@ -47,7 +48,6 @@ const darkMode = computed({
|
||||||
});
|
});
|
||||||
|
|
||||||
const user = state.getUser();
|
const user = state.getUser();
|
||||||
const token = session.getTokenMultimedia();
|
|
||||||
const warehousesData = ref();
|
const warehousesData = ref();
|
||||||
const companiesData = ref();
|
const companiesData = ref();
|
||||||
const accountBankData = ref();
|
const accountBankData = ref();
|
||||||
|
@ -149,10 +149,7 @@ function saveUserData(param, value) {
|
||||||
|
|
||||||
<div class="col column items-center q-mb-sm">
|
<div class="col column items-center q-mb-sm">
|
||||||
<QAvatar size="80px">
|
<QAvatar size="80px">
|
||||||
<QImg
|
<VnImg :id="user.id" collection="user" size="160x160" />
|
||||||
:src="`/api/Images/user/160x160/${user.id}/download?access_token=${token}`"
|
|
||||||
spinner-color="white"
|
|
||||||
/>
|
|
||||||
</QAvatar>
|
</QAvatar>
|
||||||
|
|
||||||
<div class="text-subtitle1 q-mt-md">
|
<div class="text-subtitle1 q-mt-md">
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
columns: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function stopEventPropagation(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<slot name="beforeChip" :row="row"></slot>
|
||||||
|
<span
|
||||||
|
v-for="col of columns"
|
||||||
|
:key="col.name"
|
||||||
|
@click="stopEventPropagation"
|
||||||
|
class="cursor-text"
|
||||||
|
>
|
||||||
|
<QChip
|
||||||
|
v-if="col.chip.condition(row[col.name], row)"
|
||||||
|
:title="col.label"
|
||||||
|
:class="[
|
||||||
|
col.chip.color
|
||||||
|
? col.chip.color(row)
|
||||||
|
: !col.chip.icon && 'bg-chip-secondary',
|
||||||
|
col.chip.icon && 'q-px-none',
|
||||||
|
]"
|
||||||
|
dense
|
||||||
|
square
|
||||||
|
>
|
||||||
|
<span v-if="!col.chip.icon">{{ row[col.name] }}</span>
|
||||||
|
<QIcon v-else :name="col.chip.icon" color="primary-light" />
|
||||||
|
</QChip>
|
||||||
|
</span>
|
||||||
|
<slot name="afterChip" :row="row"></slot>
|
||||||
|
</template>
|
||||||
|
<style lang="scss">
|
||||||
|
.bg-chip-secondary {
|
||||||
|
background-color: var(--vn-page-color);
|
||||||
|
color: var(--vn-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-text {
|
||||||
|
pointer-events: all;
|
||||||
|
cursor: text;
|
||||||
|
user-select: all;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,161 @@
|
||||||
|
<script setup>
|
||||||
|
import { markRaw, computed, defineModel } from 'vue';
|
||||||
|
import { QIcon, QCheckbox } from 'quasar';
|
||||||
|
import { dashIfEmpty } from 'src/filters';
|
||||||
|
|
||||||
|
/* basic input */
|
||||||
|
import VnSelect from 'components/common/VnSelect.vue';
|
||||||
|
import VnInput from 'components/common/VnInput.vue';
|
||||||
|
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||||
|
import VnComponent from 'components/common/VnComponent.vue';
|
||||||
|
|
||||||
|
const model = defineModel(undefined, { required: true });
|
||||||
|
const $props = defineProps({
|
||||||
|
column: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
row: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
type: [Object, String],
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
componentProp: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
isEditable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
showLabel: {
|
||||||
|
type: Boolean,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultComponents = {
|
||||||
|
input: {
|
||||||
|
component: markRaw(VnInput),
|
||||||
|
attrs: {
|
||||||
|
disable: !$props.isEditable,
|
||||||
|
},
|
||||||
|
forceAttrs: {
|
||||||
|
label: $props.showLabel && $props.column.label,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
number: {
|
||||||
|
component: markRaw(VnInput),
|
||||||
|
attrs: {
|
||||||
|
disable: !$props.isEditable,
|
||||||
|
},
|
||||||
|
forceAttrs: {
|
||||||
|
label: $props.showLabel && $props.column.label,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
component: markRaw(VnInputDate),
|
||||||
|
attrs: {
|
||||||
|
readonly: true,
|
||||||
|
disable: !$props.isEditable,
|
||||||
|
style: 'min-width: 125px',
|
||||||
|
},
|
||||||
|
forceAttrs: {
|
||||||
|
label: $props.showLabel && $props.column.label,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
checkbox: {
|
||||||
|
component: markRaw(QCheckbox),
|
||||||
|
attrs: (prop) => {
|
||||||
|
const defaultAttrs = {
|
||||||
|
disable: !$props.isEditable,
|
||||||
|
'model-value': Boolean(prop),
|
||||||
|
class: 'no-padding',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof prop == 'number') {
|
||||||
|
defaultAttrs['true-value'] = 1;
|
||||||
|
defaultAttrs['false-value'] = 0;
|
||||||
|
}
|
||||||
|
return defaultAttrs;
|
||||||
|
},
|
||||||
|
forceAttrs: {
|
||||||
|
label: $props.showLabel && $props.column.label,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
component: markRaw(VnSelect),
|
||||||
|
attrs: {
|
||||||
|
disable: !$props.isEditable,
|
||||||
|
},
|
||||||
|
forceAttrs: {
|
||||||
|
label: $props.showLabel && $props.column.label,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
component: markRaw(QIcon),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const value = computed(() => {
|
||||||
|
return $props.column.format
|
||||||
|
? $props.column.format($props.row, dashIfEmpty)
|
||||||
|
: dashIfEmpty($props.row[$props.column.name]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const col = computed(() => {
|
||||||
|
let newColumn = { ...$props.column };
|
||||||
|
const specific = newColumn[$props.componentProp];
|
||||||
|
if (specific) {
|
||||||
|
newColumn = {
|
||||||
|
...newColumn,
|
||||||
|
...specific,
|
||||||
|
...specific.attrs,
|
||||||
|
...specific.forceAttrs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(newColumn.name.startsWith('is') || newColumn.name.startsWith('has')) &&
|
||||||
|
!newColumn.component
|
||||||
|
)
|
||||||
|
newColumn.component = 'checkbox';
|
||||||
|
if ($props.default && !newColumn.component) newColumn.component = $props.default;
|
||||||
|
|
||||||
|
return newColumn;
|
||||||
|
});
|
||||||
|
|
||||||
|
const components = computed(() => $props.components ?? defaultComponents);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="row no-wrap fit">
|
||||||
|
<VnComponent
|
||||||
|
v-if="col.before"
|
||||||
|
:prop="col.before"
|
||||||
|
:components="components"
|
||||||
|
:value="model"
|
||||||
|
v-model="model"
|
||||||
|
/>
|
||||||
|
<VnComponent
|
||||||
|
v-if="col.component"
|
||||||
|
:prop="col"
|
||||||
|
:components="components"
|
||||||
|
:value="model"
|
||||||
|
v-model="model"
|
||||||
|
/>
|
||||||
|
<span :title="value" v-else>{{ value }}</span>
|
||||||
|
<VnComponent
|
||||||
|
v-if="col.after"
|
||||||
|
:prop="col.after"
|
||||||
|
:components="components"
|
||||||
|
:value="model"
|
||||||
|
v-model="model"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,146 @@
|
||||||
|
<script setup>
|
||||||
|
import { markRaw, computed, defineModel } from 'vue';
|
||||||
|
import { QCheckbox } from 'quasar';
|
||||||
|
import { useArrayData } from 'composables/useArrayData';
|
||||||
|
|
||||||
|
/* basic input */
|
||||||
|
import VnSelect from 'components/common/VnSelect.vue';
|
||||||
|
import VnInput from 'components/common/VnInput.vue';
|
||||||
|
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||||
|
import VnTableColumn from 'components/VnTable/VnColumn.vue';
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
column: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
showTitle: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
dataKey: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
searchUrl: {
|
||||||
|
type: String,
|
||||||
|
default: 'params',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const model = defineModel(undefined, { required: true });
|
||||||
|
const arrayData = useArrayData($props.dataKey, { searchUrl: $props.searchUrl });
|
||||||
|
const columnFilter = computed(() => $props.column?.columnFilter);
|
||||||
|
|
||||||
|
const updateEvent = { 'update:modelValue': addFilter };
|
||||||
|
const enterEvent = {
|
||||||
|
'keyup.enter': () => addFilter(model.value),
|
||||||
|
remove: () => addFilter(null),
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultAttrs = {
|
||||||
|
filled: !$props.showTitle,
|
||||||
|
class: 'q-px-sm q-pb-xs q-pt-none',
|
||||||
|
dense: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const forceAttrs = {
|
||||||
|
label: $props.showTitle ? '' : $props.column.label,
|
||||||
|
};
|
||||||
|
|
||||||
|
const components = {
|
||||||
|
input: {
|
||||||
|
component: markRaw(VnInput),
|
||||||
|
event: enterEvent,
|
||||||
|
attrs: {
|
||||||
|
...defaultAttrs,
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
forceAttrs,
|
||||||
|
},
|
||||||
|
number: {
|
||||||
|
component: markRaw(VnInput),
|
||||||
|
event: enterEvent,
|
||||||
|
attrs: {
|
||||||
|
...defaultAttrs,
|
||||||
|
clearable: true,
|
||||||
|
},
|
||||||
|
forceAttrs,
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
component: markRaw(VnInputDate),
|
||||||
|
event: updateEvent,
|
||||||
|
attrs: {
|
||||||
|
...defaultAttrs,
|
||||||
|
style: 'min-width: 150px',
|
||||||
|
},
|
||||||
|
forceAttrs,
|
||||||
|
},
|
||||||
|
checkbox: {
|
||||||
|
component: markRaw(QCheckbox),
|
||||||
|
event: updateEvent,
|
||||||
|
attrs: {
|
||||||
|
dense: true,
|
||||||
|
class: $props.showTitle ? 'q-py-sm q-mt-md' : 'q-px-md q-py-xs',
|
||||||
|
'toggle-indeterminate': true,
|
||||||
|
},
|
||||||
|
forceAttrs,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
component: markRaw(VnSelect),
|
||||||
|
event: updateEvent,
|
||||||
|
attrs: {
|
||||||
|
class: 'q-px-md q-pb-xs q-pt-none',
|
||||||
|
dense: true,
|
||||||
|
filled: !$props.showTitle,
|
||||||
|
},
|
||||||
|
forceAttrs,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function addFilter(value) {
|
||||||
|
value ??= undefined;
|
||||||
|
if (value && typeof value === 'object') value = model.value;
|
||||||
|
value = value === '' ? undefined : value;
|
||||||
|
let field = columnFilter.value?.name ?? $props.column.name;
|
||||||
|
|
||||||
|
if (columnFilter.value?.inWhere) {
|
||||||
|
if (columnFilter.value.alias) field = columnFilter.value.alias + '.' + field;
|
||||||
|
return await arrayData.addFilterWhere({ [field]: value });
|
||||||
|
}
|
||||||
|
await arrayData.addFilter({ params: { [field]: value } });
|
||||||
|
}
|
||||||
|
|
||||||
|
function alignRow() {
|
||||||
|
switch ($props.column.align) {
|
||||||
|
case 'left':
|
||||||
|
return 'justify-start items-start';
|
||||||
|
case 'right':
|
||||||
|
return 'justify-end items-end';
|
||||||
|
default:
|
||||||
|
return 'flex-center';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const showFilter = computed(
|
||||||
|
() => $props.column?.columnFilter !== false && $props.column.name != 'tableActions'
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="showTitle"
|
||||||
|
class="q-pt-sm q-px-sm ellipsis"
|
||||||
|
:class="`text-${column?.align ?? 'left'}`"
|
||||||
|
:style="!showFilter ? { 'min-height': 72 + 'px' } : ''"
|
||||||
|
>
|
||||||
|
{{ column?.label }}
|
||||||
|
</div>
|
||||||
|
<div v-if="showFilter" class="full-width" :class="alignRow()">
|
||||||
|
<VnTableColumn
|
||||||
|
:column="$props.column"
|
||||||
|
default="input"
|
||||||
|
v-model="model"
|
||||||
|
:components="components"
|
||||||
|
component-prop="columnFilter"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,628 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, computed, watch } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
|
||||||
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
|
import FormModelPopup from 'components/FormModelPopup.vue';
|
||||||
|
import CrudModel from 'src/components/CrudModel.vue';
|
||||||
|
import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
|
||||||
|
import VnLv from 'components/ui/VnLv.vue';
|
||||||
|
|
||||||
|
import VnTableColumn from 'components/VnTable/VnColumn.vue';
|
||||||
|
import VnTableFilter from 'components/VnTable/VnFilter.vue';
|
||||||
|
import VnTableChip from 'components/VnTable/VnChip.vue';
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
columns: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
defaultMode: {
|
||||||
|
type: String,
|
||||||
|
default: 'card', // 'table', 'card'
|
||||||
|
},
|
||||||
|
columnSearch: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
rightSearch: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
rowClick: {
|
||||||
|
type: Function,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
redirect: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
cardClass: {
|
||||||
|
type: String,
|
||||||
|
default: 'flex-one',
|
||||||
|
},
|
||||||
|
searchUrl: {
|
||||||
|
type: String,
|
||||||
|
default: 'table',
|
||||||
|
},
|
||||||
|
isEditable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
useModel: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { t } = useI18n();
|
||||||
|
const stateStore = useStateStore();
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const quasar = useQuasar();
|
||||||
|
|
||||||
|
const DEFAULT_MODE = 'card';
|
||||||
|
const TABLE_MODE = 'table';
|
||||||
|
const mode = ref(DEFAULT_MODE);
|
||||||
|
const selected = ref([]);
|
||||||
|
const routeQuery = JSON.parse(route?.query[$props.searchUrl] ?? '{}');
|
||||||
|
const params = ref({ ...routeQuery, ...routeQuery.filter?.where });
|
||||||
|
const CrudModelRef = ref({});
|
||||||
|
const showForm = ref(false);
|
||||||
|
const splittedColumns = ref({ columns: [] });
|
||||||
|
const tableModes = [
|
||||||
|
{
|
||||||
|
icon: 'view_column',
|
||||||
|
title: t('table view'),
|
||||||
|
value: TABLE_MODE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'grid_view',
|
||||||
|
title: t('grid view'),
|
||||||
|
value: DEFAULT_MODE,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
mode.value = quasar.platform.is.mobile ? DEFAULT_MODE : $props.defaultMode;
|
||||||
|
stateStore.rightDrawer = true;
|
||||||
|
setUserParams(route.query[$props.searchUrl]);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => $props.columns,
|
||||||
|
(value) => splitColumns(value),
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.query[$props.searchUrl],
|
||||||
|
(val) => setUserParams(val)
|
||||||
|
);
|
||||||
|
|
||||||
|
function setUserParams(watchedParams) {
|
||||||
|
if (!watchedParams) return;
|
||||||
|
|
||||||
|
if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams);
|
||||||
|
const where = JSON.parse(watchedParams?.filter)?.where;
|
||||||
|
watchedParams = { ...watchedParams, ...where };
|
||||||
|
delete watchedParams.filter;
|
||||||
|
params.value = { ...params.value, ...watchedParams };
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitColumns(columns) {
|
||||||
|
splittedColumns.value = {
|
||||||
|
columns: [],
|
||||||
|
chips: [],
|
||||||
|
create: [],
|
||||||
|
visible: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const col of columns) {
|
||||||
|
if (col.name == 'tableActions') splittedColumns.value.actions = col;
|
||||||
|
if (col.chip) splittedColumns.value.chips.push(col);
|
||||||
|
if (col.isTitle) splittedColumns.value.title = col;
|
||||||
|
if (col.create) splittedColumns.value.create.push(col);
|
||||||
|
if (col.cardVisible) splittedColumns.value.visible.push(col);
|
||||||
|
if ($props.isEditable && col.disable == null) col.disable = false;
|
||||||
|
if ($props.useModel) col.columnFilter = { ...col.columnFilter, inWhere: true };
|
||||||
|
splittedColumns.value.columns.push(col);
|
||||||
|
}
|
||||||
|
// Status column
|
||||||
|
if (splittedColumns.value.chips.length) {
|
||||||
|
splittedColumns.value.columnChips = splittedColumns.value.chips.filter(
|
||||||
|
(c) => !c.isId
|
||||||
|
);
|
||||||
|
if (splittedColumns.value.columnChips.length)
|
||||||
|
splittedColumns.value.columns.unshift({
|
||||||
|
align: 'left',
|
||||||
|
label: t('status'),
|
||||||
|
name: 'tableStatus',
|
||||||
|
columnFilter: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowClickFunction = computed(() => {
|
||||||
|
if ($props.rowClick) return $props.rowClick;
|
||||||
|
if ($props.redirect) return ({ id }) => redirectFn(id);
|
||||||
|
return () => {};
|
||||||
|
});
|
||||||
|
|
||||||
|
function redirectFn(id) {
|
||||||
|
router.push({ path: `/${$props.redirect}/${id}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopEventPropagation(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function reload(params) {
|
||||||
|
CrudModelRef.value.reload(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
function columnName(col) {
|
||||||
|
const column = { ...col, ...col.columnFilter };
|
||||||
|
let name = column.name;
|
||||||
|
if (column.alias) name = column.alias + '.' + name;
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColAlign(col) {
|
||||||
|
return 'text-' + (col.align ?? 'left')
|
||||||
|
}
|
||||||
|
defineExpose({
|
||||||
|
reload,
|
||||||
|
redirect: redirectFn,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<QDrawer
|
||||||
|
v-if="$props.rightSearch"
|
||||||
|
v-model="stateStore.rightDrawer"
|
||||||
|
side="right"
|
||||||
|
:width="256"
|
||||||
|
show-if-above
|
||||||
|
>
|
||||||
|
<QScrollArea class="fit">
|
||||||
|
<VnFilterPanel
|
||||||
|
:data-key="$attrs['data-key']"
|
||||||
|
:search-button="true"
|
||||||
|
v-model="params"
|
||||||
|
:disable-submit-event="true"
|
||||||
|
:search-url="searchUrl"
|
||||||
|
>
|
||||||
|
<template #body>
|
||||||
|
<VnTableFilter
|
||||||
|
:column="col"
|
||||||
|
:data-key="$attrs['data-key']"
|
||||||
|
v-for="col of splittedColumns.columns"
|
||||||
|
:key="col.id"
|
||||||
|
v-model="params[columnName(col)]"
|
||||||
|
:search-url="searchUrl"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<slot
|
||||||
|
name="moreFilterPanel"
|
||||||
|
:params="params"
|
||||||
|
:columns="splittedColumns.columns"
|
||||||
|
/>
|
||||||
|
</VnFilterPanel>
|
||||||
|
</QScrollArea>
|
||||||
|
</QDrawer>
|
||||||
|
<!-- class in div to fix warn-->
|
||||||
|
<div class="q-px-md">
|
||||||
|
<CrudModel
|
||||||
|
v-bind="$attrs"
|
||||||
|
:limit="20"
|
||||||
|
ref="CrudModelRef"
|
||||||
|
:search-url="searchUrl"
|
||||||
|
:disable-infinite-scroll="mode == TABLE_MODE"
|
||||||
|
@save-changes="reload"
|
||||||
|
:has-subtoolbar="isEditable"
|
||||||
|
>
|
||||||
|
<template #body="{ rows }">
|
||||||
|
<QTable
|
||||||
|
v-bind="$attrs['QTable']"
|
||||||
|
class="vnTable"
|
||||||
|
:columns="splittedColumns.columns"
|
||||||
|
:rows="rows"
|
||||||
|
v-model:selected="selected"
|
||||||
|
:grid="mode != TABLE_MODE"
|
||||||
|
table-header-class="bg-header"
|
||||||
|
card-container-class="grid-three"
|
||||||
|
flat
|
||||||
|
:style="mode == TABLE_MODE && 'max-height: 90vh'"
|
||||||
|
virtual-scroll
|
||||||
|
@virtual-scroll="
|
||||||
|
(event) =>
|
||||||
|
event.index > rows.length - 2 &&
|
||||||
|
CrudModelRef.vnPaginateRef.paginate()
|
||||||
|
"
|
||||||
|
@row-click="(_, row) => rowClickFunction(row)"
|
||||||
|
>
|
||||||
|
<template #top-left>
|
||||||
|
<slot name="top-left"></slot>
|
||||||
|
</template>
|
||||||
|
<template #top-right>
|
||||||
|
<!-- <QBtn
|
||||||
|
icon="visibility"
|
||||||
|
title="asd"
|
||||||
|
class="bg-vn-section-color q-mr-md"
|
||||||
|
dense
|
||||||
|
v-if="mode == 'table'"
|
||||||
|
/> -->
|
||||||
|
<QBtnToggle
|
||||||
|
v-model="mode"
|
||||||
|
toggle-color="primary"
|
||||||
|
class="bg-vn-section-color"
|
||||||
|
dense
|
||||||
|
:options="tableModes"
|
||||||
|
/>
|
||||||
|
<QBtn
|
||||||
|
icon="filter_alt"
|
||||||
|
title="asd"
|
||||||
|
class="bg-vn-section-color q-ml-md"
|
||||||
|
dense
|
||||||
|
@click="stateStore.toggleRightDrawer()"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #header-cell="{ col }">
|
||||||
|
<QTh
|
||||||
|
auto-width
|
||||||
|
style="min-width: 100px"
|
||||||
|
v-if="$props.columnSearch"
|
||||||
|
>
|
||||||
|
<VnTableFilter
|
||||||
|
:column="col"
|
||||||
|
:show-title="true"
|
||||||
|
:data-key="$attrs['data-key']"
|
||||||
|
v-model="params[columnName(col)]"
|
||||||
|
:search-url="searchUrl"
|
||||||
|
/>
|
||||||
|
</QTh>
|
||||||
|
</template>
|
||||||
|
<template #header-cell-tableActions>
|
||||||
|
<QTh auto-width class="sticky" />
|
||||||
|
</template>
|
||||||
|
<template #body-cell-tableStatus="{ col, row }">
|
||||||
|
<QTd auto-width :class="getColAlign(col)">
|
||||||
|
<VnTableChip
|
||||||
|
:columns="splittedColumns.columnChips"
|
||||||
|
:row="row"
|
||||||
|
>
|
||||||
|
<template #afterChip>
|
||||||
|
<slot name="afterChip" :row="row"></slot>
|
||||||
|
</template>
|
||||||
|
</VnTableChip>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell="{ col, row }">
|
||||||
|
<!-- Columns -->
|
||||||
|
<QTd
|
||||||
|
auto-width
|
||||||
|
class="no-margin q-px-xs"
|
||||||
|
:class="getColAlign(col)"
|
||||||
|
>
|
||||||
|
<VnTableColumn
|
||||||
|
:column="col"
|
||||||
|
:row="row"
|
||||||
|
:is-editable="false"
|
||||||
|
v-model="row[col.name]"
|
||||||
|
component-prop="columnField"
|
||||||
|
/>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-tableActions="{ col, row }">
|
||||||
|
<QTd
|
||||||
|
auto-width
|
||||||
|
:class="getColAlign(col)"
|
||||||
|
class="sticky no-padding"
|
||||||
|
@click="stopEventPropagation($event)"
|
||||||
|
>
|
||||||
|
<QBtn
|
||||||
|
v-for="(btn, index) of col.actions"
|
||||||
|
:key="index"
|
||||||
|
:title="btn.title"
|
||||||
|
:icon="btn.icon"
|
||||||
|
class="q-px-sm"
|
||||||
|
flat
|
||||||
|
:class="
|
||||||
|
btn.isPrimary
|
||||||
|
? 'text-primary-light'
|
||||||
|
: 'color-vn-text '
|
||||||
|
"
|
||||||
|
@click="btn.action(row)"
|
||||||
|
/>
|
||||||
|
</QTd>
|
||||||
|
</template>
|
||||||
|
<template #item="{ row, colsMap }">
|
||||||
|
<component
|
||||||
|
:is="$props.redirect ? 'router-link' : 'span'"
|
||||||
|
:to="`/${$props.redirect}/` + row.id"
|
||||||
|
>
|
||||||
|
<QCard
|
||||||
|
bordered
|
||||||
|
flat
|
||||||
|
class="row no-wrap justify-between cursor-pointer"
|
||||||
|
@click="
|
||||||
|
(_, row) => {
|
||||||
|
$props.rowClick && $props.rowClick(row);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<QCardSection
|
||||||
|
vertical
|
||||||
|
class="no-margin no-padding"
|
||||||
|
:class="colsMap.tableActions ? 'w-80' : 'fit'"
|
||||||
|
>
|
||||||
|
<!-- Chips -->
|
||||||
|
<QCardSection
|
||||||
|
v-if="splittedColumns.chips.length"
|
||||||
|
class="no-margin q-px-xs q-py-none"
|
||||||
|
>
|
||||||
|
<VnTableChip
|
||||||
|
:columns="splittedColumns.chips"
|
||||||
|
:row="row"
|
||||||
|
>
|
||||||
|
<template #afterChip>
|
||||||
|
<slot name="afterChip" :row="row"></slot>
|
||||||
|
</template>
|
||||||
|
</VnTableChip>
|
||||||
|
</QCardSection>
|
||||||
|
<!-- Title -->
|
||||||
|
<QCardSection
|
||||||
|
v-if="splittedColumns.title"
|
||||||
|
class="q-pl-sm q-py-none text-primary-light text-bold text-h6 cardEllipsis"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:title="row[splittedColumns.title.name]"
|
||||||
|
@click="stopEventPropagation($event)"
|
||||||
|
class="cursor-text"
|
||||||
|
>
|
||||||
|
{{ row[splittedColumns.title.name] }}
|
||||||
|
</span>
|
||||||
|
</QCardSection>
|
||||||
|
<!-- Fields -->
|
||||||
|
<QCardSection
|
||||||
|
class="q-pl-sm q-pr-lg q-py-xs"
|
||||||
|
:class="$props.cardClass"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="col of splittedColumns.visible"
|
||||||
|
:key="col.name"
|
||||||
|
class="fields"
|
||||||
|
>
|
||||||
|
<VnLv
|
||||||
|
:label="
|
||||||
|
!col.component &&
|
||||||
|
col.label &&
|
||||||
|
`${col.label}:`
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #value>
|
||||||
|
<span
|
||||||
|
@click="
|
||||||
|
stopEventPropagation($event)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<VnTableColumn
|
||||||
|
:column="col"
|
||||||
|
:row="row"
|
||||||
|
:is-editable="false"
|
||||||
|
v-model="row[col.name]"
|
||||||
|
component-prop="columnField"
|
||||||
|
:show-label="true"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</VnLv>
|
||||||
|
</div>
|
||||||
|
</QCardSection>
|
||||||
|
</QCardSection>
|
||||||
|
<!-- Actions -->
|
||||||
|
<QCardSection
|
||||||
|
v-if="colsMap.tableActions"
|
||||||
|
class="column flex-center w-10 no-margin q-pa-xs q-gutter-y-xs"
|
||||||
|
@click="stopEventPropagation($event)"
|
||||||
|
>
|
||||||
|
<QBtn
|
||||||
|
v-for="(btn, index) of splittedColumns.actions
|
||||||
|
.actions"
|
||||||
|
:key="index"
|
||||||
|
:title="btn.title"
|
||||||
|
:icon="btn.icon"
|
||||||
|
class="q-pa-xs"
|
||||||
|
flat
|
||||||
|
:class="
|
||||||
|
btn.isPrimary
|
||||||
|
? 'text-primary-light'
|
||||||
|
: 'color-vn-text '
|
||||||
|
"
|
||||||
|
@click="btn.action(row)"
|
||||||
|
/>
|
||||||
|
</QCardSection>
|
||||||
|
</QCard>
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
</QTable>
|
||||||
|
</template>
|
||||||
|
</CrudModel>
|
||||||
|
</div>
|
||||||
|
<QPageSticky v-if="create" :offset="[20, 20]" style="z-index: 2">
|
||||||
|
<QBtn @click="showForm = !showForm" color="primary" fab icon="add" />
|
||||||
|
<QTooltip>
|
||||||
|
{{ create.title }}
|
||||||
|
</QTooltip>
|
||||||
|
</QPageSticky>
|
||||||
|
<QDialog v-model="showForm" transition-show="scale" transition-hide="scale">
|
||||||
|
<FormModelPopup
|
||||||
|
v-bind="create"
|
||||||
|
:model="$attrs['data-key'] + 'Create'"
|
||||||
|
@on-data-saved="(_, res) => create.onDataSaved(res)"
|
||||||
|
>
|
||||||
|
<template #form-inputs="{ data }">
|
||||||
|
<div class="grid-create">
|
||||||
|
<VnTableColumn
|
||||||
|
v-for="column of splittedColumns.create"
|
||||||
|
:key="column.name"
|
||||||
|
:column="column"
|
||||||
|
:row="{}"
|
||||||
|
default="input"
|
||||||
|
v-model="data[column.name]"
|
||||||
|
:show-label="true"
|
||||||
|
/>
|
||||||
|
<slot name="more-create-dialog" :data="data" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</FormModelPopup>
|
||||||
|
</QDialog>
|
||||||
|
</template>
|
||||||
|
<i18n>
|
||||||
|
en:
|
||||||
|
status: Status
|
||||||
|
es:
|
||||||
|
status: Estados
|
||||||
|
</i18n>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.bg-chip-secondary {
|
||||||
|
background-color: var(--vn-page-color);
|
||||||
|
color: var(--vn-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-header {
|
||||||
|
background-color: #5d5d5d;
|
||||||
|
color: var(--vn-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-table--dark .q-table__bottom,
|
||||||
|
.q-table--dark thead,
|
||||||
|
.q-table--dark tr,
|
||||||
|
.q-table--dark th,
|
||||||
|
.q-table--dark td {
|
||||||
|
border-color: #222222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-table__container > div:first-child {
|
||||||
|
background-color: var(--vn-page-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-three {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(400px, max-content));
|
||||||
|
max-width: 100%;
|
||||||
|
grid-gap: 20px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-create {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, max-content));
|
||||||
|
max-width: 100%;
|
||||||
|
grid-gap: 20px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-one {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
div.fields {
|
||||||
|
width: 100%;
|
||||||
|
.vn-label-value {
|
||||||
|
display: flex;
|
||||||
|
gap: 2%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-table th {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vnTable {
|
||||||
|
thead tr th {
|
||||||
|
position: sticky;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
thead tr:first-child th {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.q-table__top {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
.q-checkbox {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 9px;
|
||||||
|
& .q-checkbox__label {
|
||||||
|
margin-left: 31px;
|
||||||
|
color: var(--vn-text-color);
|
||||||
|
}
|
||||||
|
& .q-checkbox__inner {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
color: var(--vn-label-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sticky {
|
||||||
|
position: sticky;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
td.sticky {
|
||||||
|
background-color: var(--q-dark);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vn-label-value {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
color: var(--vn-text-color);
|
||||||
|
.value {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
pointer-events: all;
|
||||||
|
cursor: text;
|
||||||
|
user-select: all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardEllipsis {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-two {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, max-content));
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
overflow: scroll;
|
||||||
|
white-space: wrap;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-80 {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-20 {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cursor-text {
|
||||||
|
pointer-events: all;
|
||||||
|
cursor: text;
|
||||||
|
user-select: all;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onBeforeMount, computed, watchEffect } from 'vue';
|
import { onBeforeMount, computed } from 'vue';
|
||||||
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useArrayData } from 'src/composables/useArrayData';
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
import useCardSize from 'src/composables/useCardSize';
|
import useCardSize from 'src/composables/useCardSize';
|
||||||
|
@ -41,20 +41,6 @@ onBeforeMount(async () => {
|
||||||
if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id };
|
if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id };
|
||||||
await arrayData.fetch({ append: false });
|
await arrayData.fetch({ append: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
if (props.baseUrl) {
|
|
||||||
onBeforeRouteUpdate(async (to, from) => {
|
|
||||||
if (to.params.id !== from.params.id) {
|
|
||||||
arrayData.store.url = `${props.baseUrl}/${to.params.id}`;
|
|
||||||
await arrayData.fetch({ append: false });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
if (Array.isArray(arrayData.store.data))
|
|
||||||
arrayData.store.data = arrayData.store.data[0];
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<QDrawer
|
<QDrawer
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
<script setup>
|
||||||
|
import { computed, defineModel } from 'vue';
|
||||||
|
|
||||||
|
const model = defineModel(undefined, { required: true });
|
||||||
|
const $props = defineProps({
|
||||||
|
prop: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: [Object, Number, String],
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const componentArray = computed(() => {
|
||||||
|
if (typeof $props.prop === 'object') return [$props.prop];
|
||||||
|
return $props.prop;
|
||||||
|
});
|
||||||
|
|
||||||
|
function mix(toComponent) {
|
||||||
|
const { component, attrs, event } = toComponent;
|
||||||
|
const customComponent = $props.components[component];
|
||||||
|
return {
|
||||||
|
component: customComponent?.component ?? component,
|
||||||
|
attrs: {
|
||||||
|
...toValueAttrs(attrs),
|
||||||
|
...toValueAttrs(customComponent?.attrs),
|
||||||
|
...toComponent,
|
||||||
|
...toValueAttrs(customComponent?.forceAttrs),
|
||||||
|
},
|
||||||
|
event: event ?? customComponent?.event,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toValueAttrs(attrs) {
|
||||||
|
if (!attrs) return;
|
||||||
|
return typeof attrs == 'function' ? attrs($props.value) : attrs;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<span
|
||||||
|
v-for="toComponent of componentArray"
|
||||||
|
:key="toComponent.name"
|
||||||
|
class="column flex-center fit"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
v-if="toComponent?.component"
|
||||||
|
:is="mix(toComponent).component"
|
||||||
|
v-bind="mix(toComponent).attrs"
|
||||||
|
v-on="mix(toComponent).event ?? {}"
|
||||||
|
v-model="model"
|
||||||
|
class="fit"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</template>
|
|
@ -2,7 +2,12 @@
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'update:options', 'keyup.enter']);
|
const emit = defineEmits([
|
||||||
|
'update:modelValue',
|
||||||
|
'update:options',
|
||||||
|
'keyup.enter',
|
||||||
|
'remove',
|
||||||
|
]);
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
|
@ -13,6 +18,14 @@ const $props = defineProps({
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
info: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
clearable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -74,15 +87,19 @@ const inputRules = [
|
||||||
<template v-if="$slots.prepend" #prepend>
|
<template v-if="$slots.prepend" #prepend>
|
||||||
<slot name="prepend" />
|
<slot name="prepend" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #append>
|
<template #append>
|
||||||
<slot name="append" v-if="$slots.append && !$attrs.disabled" />
|
<slot name="append" v-if="$slots.append && !$attrs.disabled" />
|
||||||
<QIcon
|
<QIcon
|
||||||
name="close"
|
name="close"
|
||||||
size="xs"
|
size="xs"
|
||||||
v-if="hover && value && !$attrs.disabled"
|
v-if="hover && value && !$attrs.disabled && $props.clearable"
|
||||||
@click="value = null"
|
@click="value = null"
|
||||||
></QIcon>
|
></QIcon>
|
||||||
|
<QIcon v-if="info" name="info">
|
||||||
|
<QTooltip max-width="350px">
|
||||||
|
{{ info }}
|
||||||
|
</QTooltip>
|
||||||
|
</QIcon>
|
||||||
</template>
|
</template>
|
||||||
</QInput>
|
</QInput>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -97,7 +97,7 @@ const styleAttrs = computed(() => {
|
||||||
<QIcon
|
<QIcon
|
||||||
name="close"
|
name="close"
|
||||||
size="xs"
|
size="xs"
|
||||||
v-if="hover && value"
|
v-if="hover && value && !readonly"
|
||||||
@click="onDateUpdate(null)"
|
@click="onDateUpdate(null)"
|
||||||
></QIcon>
|
></QIcon>
|
||||||
<QIcon name="event" class="cursor-pointer">
|
<QIcon name="event" class="cursor-pointer">
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { ref, onUnmounted } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
@ -376,6 +376,10 @@ async function clearFilter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
setLogTree();
|
setLogTree();
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
stateStore.rightDrawer = false;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<FetchData
|
<FetchData
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
title: { type: String, default: null },
|
||||||
|
content: { type: [String, Number], default: null },
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<QPopupProxy>
|
||||||
|
<QCard>
|
||||||
|
<slot name="title">
|
||||||
|
<div
|
||||||
|
class="header q-px-sm q-py-xs q-ma-none text-white text-bold bg-primary"
|
||||||
|
v-text="title"
|
||||||
|
/>
|
||||||
|
</slot>
|
||||||
|
<slot name="content">
|
||||||
|
<QCardSection class="change-detail q-pa-sm">
|
||||||
|
{{ content }}
|
||||||
|
</QCardSection>
|
||||||
|
</slot>
|
||||||
|
</QCard>
|
||||||
|
</QPopupProxy>
|
||||||
|
</template>
|
|
@ -0,0 +1,97 @@
|
||||||
|
<script setup>
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
progress: {
|
||||||
|
type: Number, //Progress value (1.0 > x > 0.0)
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
showDialog: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
cancelled: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['cancel', 'close']);
|
||||||
|
|
||||||
|
const dialogRef = ref(null);
|
||||||
|
|
||||||
|
const _showDialog = computed({
|
||||||
|
get: () => $props.showDialog,
|
||||||
|
set: (value) => {
|
||||||
|
if (value) dialogRef.value.show();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const _progress = computed(() => $props.progress);
|
||||||
|
|
||||||
|
const progressLabel = computed(() => `${Math.round($props.progress * 100)}%`);
|
||||||
|
|
||||||
|
const cancel = () => {
|
||||||
|
dialogRef.value.hide();
|
||||||
|
emit('cancel');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<QDialog ref="dialogRef" v-model="_showDialog" @hide="onDialogHide">
|
||||||
|
<QCard class="full-width dialog">
|
||||||
|
<QCardSection class="row">
|
||||||
|
<span class="text-h6">{{ t('Progress') }}</span>
|
||||||
|
<QSpace />
|
||||||
|
<QBtn icon="close" flat round dense @click="emit('close')" />
|
||||||
|
</QCardSection>
|
||||||
|
<QCardSection>
|
||||||
|
<div class="column">
|
||||||
|
<span>{{ t('Total progress') }}:</span>
|
||||||
|
<QLinearProgress
|
||||||
|
size="30px"
|
||||||
|
:value="_progress"
|
||||||
|
color="primary"
|
||||||
|
stripe
|
||||||
|
class="q-mt-sm q-mb-md"
|
||||||
|
>
|
||||||
|
<div class="absolute-full flex flex-center">
|
||||||
|
<QBadge
|
||||||
|
v-if="cancelled"
|
||||||
|
text-color="white"
|
||||||
|
color="negative"
|
||||||
|
:label="t('Cancelled')"
|
||||||
|
/>
|
||||||
|
<span v-else class="text-white text-subtitle1">
|
||||||
|
{{ progressLabel }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</QLinearProgress>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</QCardSection>
|
||||||
|
<QCardActions align="right">
|
||||||
|
<QBtn
|
||||||
|
v-if="!cancelled && progress < 1"
|
||||||
|
type="button"
|
||||||
|
flat
|
||||||
|
class="text-primary"
|
||||||
|
@click="cancel()"
|
||||||
|
>
|
||||||
|
{{ t('globals.cancel') }}
|
||||||
|
</QBtn>
|
||||||
|
</QCardActions>
|
||||||
|
</QCard>
|
||||||
|
</QDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
Progress: Progreso
|
||||||
|
Total progress: Progreso total
|
||||||
|
Cancelled: Cancelado
|
||||||
|
</i18n>
|
|
@ -1,6 +1,5 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, toRefs, computed, watch } from 'vue';
|
import { ref, toRefs, computed, watch, onMounted } from 'vue';
|
||||||
import { onMounted } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import FetchData from 'src/components/FetchData.vue';
|
import FetchData from 'src/components/FetchData.vue';
|
||||||
const emit = defineEmits(['update:modelValue', 'update:options']);
|
const emit = defineEmits(['update:modelValue', 'update:options']);
|
||||||
|
@ -16,11 +15,11 @@ const $props = defineProps({
|
||||||
},
|
},
|
||||||
optionLabel: {
|
optionLabel: {
|
||||||
type: [String],
|
type: [String],
|
||||||
default: '',
|
default: 'name',
|
||||||
},
|
},
|
||||||
optionValue: {
|
optionValue: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: 'id',
|
||||||
},
|
},
|
||||||
optionFilter: {
|
optionFilter: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -58,6 +57,10 @@ const $props = defineProps({
|
||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
default: '30',
|
default: '30',
|
||||||
},
|
},
|
||||||
|
focusOnMount: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -116,7 +119,9 @@ async function fetchFilter(val) {
|
||||||
if (new RegExp(/\d/g).test(val)) key = optionFilter.value ?? optionValue.value;
|
if (new RegExp(/\d/g).test(val)) key = optionFilter.value ?? optionValue.value;
|
||||||
|
|
||||||
const where = { ...{ [key]: { like: `%${val}%` } }, ...$props.where };
|
const where = { ...{ [key]: { like: `%${val}%` } }, ...$props.where };
|
||||||
return dataRef.value.fetch({ fields, where, order: sortBy, limit });
|
const fetchOptions = { where, order: sortBy, limit };
|
||||||
|
if (fields) fetchOptions.fields = fields;
|
||||||
|
return dataRef.value.fetch(fetchOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function filterHandler(val, update) {
|
async function filterHandler(val, update) {
|
||||||
|
@ -146,6 +151,10 @@ watch(modelValue, (newValue) => {
|
||||||
if (!myOptions.value.some((option) => option[optionValue.value] == newValue))
|
if (!myOptions.value.some((option) => option[optionValue.value] == newValue))
|
||||||
fetchFilter(newValue);
|
fetchFilter(newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if ($props.focusOnMount) setTimeout(() => vnSelectRef.value.showPopup(), 300);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -178,6 +187,7 @@ watch(modelValue, (newValue) => {
|
||||||
>
|
>
|
||||||
<template v-if="isClearable" #append>
|
<template v-if="isClearable" #append>
|
||||||
<QIcon
|
<QIcon
|
||||||
|
v-show="value"
|
||||||
name="close"
|
name="close"
|
||||||
@click.stop="value = null"
|
@click.stop="value = null"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
|
|
|
@ -184,6 +184,7 @@ en:
|
||||||
minAmount: 'A minimum amount of 50€ (VAT excluded) is required for your order
|
minAmount: 'A minimum amount of 50€ (VAT excluded) is required for your order
|
||||||
{ orderId } of { shipped } to receive it without additional shipping costs.'
|
{ orderId } of { shipped } to receive it without additional shipping costs.'
|
||||||
orderChanges: 'Order {orderId} of { shipped }: { changes }'
|
orderChanges: 'Order {orderId} of { shipped }: { changes }'
|
||||||
|
productNotAvailable: 'Verdnatura communicates: Your order {ticketFk} with reception date on {landed}. {notAvailables} not available. Sorry for the inconvenience.'
|
||||||
en: English
|
en: English
|
||||||
es: Spanish
|
es: Spanish
|
||||||
fr: French
|
fr: French
|
||||||
|
@ -203,6 +204,7 @@ es:
|
||||||
Te recomendamos amplíes para no generar costes extra, provocarán un incremento de tu tarifa.
|
Te recomendamos amplíes para no generar costes extra, provocarán un incremento de tu tarifa.
|
||||||
¡Un saludo!'
|
¡Un saludo!'
|
||||||
orderChanges: 'Pedido {orderId} con llegada estimada día { landing }: { changes }'
|
orderChanges: 'Pedido {orderId} con llegada estimada día { landing }: { changes }'
|
||||||
|
productNotAvailable: 'Verdnatura le comunica: Pedido {ticketFk} con fecha de recepción {landed}. {notAvailables} no disponible/s. Disculpe las molestias.'
|
||||||
en: Inglés
|
en: Inglés
|
||||||
es: Español
|
es: Español
|
||||||
fr: Francés
|
fr: Francés
|
||||||
|
@ -222,6 +224,7 @@ fr:
|
||||||
Montant minimum nécessaire de 50 euros pour recevoir la commande { orderId } livraison { landing }.
|
Montant minimum nécessaire de 50 euros pour recevoir la commande { orderId } livraison { landing }.
|
||||||
Merci.'
|
Merci.'
|
||||||
orderChanges: 'Commande {orderId} livraison {landing} indisponible/s. Désolés pour le dérangement.'
|
orderChanges: 'Commande {orderId} livraison {landing} indisponible/s. Désolés pour le dérangement.'
|
||||||
|
productNotAvailable: 'Verdnatura communique : Votre commande {ticketFk} avec date de réception le {landed}. {notAvailables} non disponible. Nous sommes désolés pour les inconvénients.'
|
||||||
en: Anglais
|
en: Anglais
|
||||||
es: Espagnol
|
es: Espagnol
|
||||||
fr: Français
|
fr: Français
|
||||||
|
@ -240,6 +243,7 @@ pt:
|
||||||
minAmount: 'É necessário um valor mínimo de 50€ (sem IVA) em seu pedido
|
minAmount: 'É necessário um valor mínimo de 50€ (sem IVA) em seu pedido
|
||||||
{ orderId } do dia { landing } para recebê-lo sem custos de envio adicionais.'
|
{ orderId } do dia { landing } para recebê-lo sem custos de envio adicionais.'
|
||||||
orderChanges: 'Pedido { orderId } com chegada dia { landing }: { changes }'
|
orderChanges: 'Pedido { orderId } com chegada dia { landing }: { changes }'
|
||||||
|
productNotAvailable: 'Verdnatura comunica: Seu pedido {ticketFk} com data de recepção em {landed}. {notAvailables} não disponível/eis. Desculpe pelo transtorno.'
|
||||||
en: Inglês
|
en: Inglês
|
||||||
es: Espanhol
|
es: Espanhol
|
||||||
fr: Francês
|
fr: Francês
|
||||||
|
|
|
@ -39,6 +39,7 @@ const $props = defineProps({
|
||||||
});
|
});
|
||||||
|
|
||||||
const state = useState();
|
const state = useState();
|
||||||
|
const route = useRoute();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { viewSummary } = useSummaryDialog();
|
const { viewSummary } = useSummaryDialog();
|
||||||
let arrayData;
|
let arrayData;
|
||||||
|
@ -57,7 +58,7 @@ onBeforeMount(async () => {
|
||||||
store = arrayData.store;
|
store = arrayData.store;
|
||||||
entity = computed(() => (Array.isArray(store.data) ? store.data[0] : store.data));
|
entity = computed(() => (Array.isArray(store.data) ? store.data[0] : store.data));
|
||||||
// It enables to load data only once if the module is the same as the dataKey
|
// It enables to load data only once if the module is the same as the dataKey
|
||||||
if ($props.dataKey !== useRoute().meta.moduleName) await getData();
|
if ($props.dataKey !== route.meta.moduleName || !route.params.id) await getData();
|
||||||
watch(
|
watch(
|
||||||
() => [$props.url, $props.filter],
|
() => [$props.url, $props.filter],
|
||||||
async () => await getData()
|
async () => await getData()
|
||||||
|
@ -115,13 +116,13 @@ const emit = defineEmits(['onFetch']);
|
||||||
</QBtn>
|
</QBtn>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<QBtn
|
<QBtn
|
||||||
|
v-if="$slots.menu"
|
||||||
color="white"
|
color="white"
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
icon="more_vert"
|
icon="more_vert"
|
||||||
round
|
round
|
||||||
size="md"
|
size="md"
|
||||||
:class="{ invisible: !$slots.menu }"
|
|
||||||
>
|
>
|
||||||
<QTooltip>
|
<QTooltip>
|
||||||
{{ t('components.cardDescriptor.moreOptions') }}
|
{{ t('components.cardDescriptor.moreOptions') }}
|
||||||
|
|
|
@ -28,7 +28,7 @@ const toggleCardCheck = (item) => {
|
||||||
<div class="title text-primary text-weight-bold text-h5">
|
<div class="title text-primary text-weight-bold text-h5">
|
||||||
{{ $props.title }}
|
{{ $props.title }}
|
||||||
</div>
|
</div>
|
||||||
<QChip class="q-chip-color" outline size="sm">
|
<QChip v-if="$props.id" class="q-chip-color" outline size="sm">
|
||||||
{{ t('ID') }}: {{ $props.id }}
|
{{ t('ID') }}: {{ $props.id }}
|
||||||
</QChip>
|
</QChip>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,11 +22,15 @@ const props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
moduleName: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const emit = defineEmits(['onFetch']);
|
const emit = defineEmits(['onFetch']);
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const isSummary = ref();
|
const isSummary = ref();
|
||||||
const arrayData = useArrayData(props.dataKey || route.meta.moduleName, {
|
const arrayData = useArrayData(props.dataKey, {
|
||||||
url: props.url,
|
url: props.url,
|
||||||
filter: props.filter,
|
filter: props.filter,
|
||||||
skip: 0,
|
skip: 0,
|
||||||
|
@ -83,7 +87,7 @@ function existSummary(routes) {
|
||||||
v-if="showRedirectToSummaryIcon"
|
v-if="showRedirectToSummaryIcon"
|
||||||
class="header link"
|
class="header link"
|
||||||
:to="{
|
:to="{
|
||||||
name: `${route.meta.moduleName}Summary`,
|
name: `${moduleName ?? route.meta.moduleName}Summary`,
|
||||||
params: { id: entityId || entity.id },
|
params: { id: entityId || entity.id },
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
|
@ -159,9 +163,9 @@ function existSummary(routes) {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
.label {
|
.label {
|
||||||
color: var(--vn-label-color);
|
color: var(--vn-label-color);
|
||||||
width: 8em;
|
width: 9em;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: wrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
|
|
|
@ -1,25 +1,38 @@
|
||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
columns: {
|
||||||
|
type: Number,
|
||||||
|
default: 6,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="q-pa-md w">
|
<div class="q-pa-md q-mx-md container">
|
||||||
<div class="row q-gutter-md q-mb-md">
|
<div class="row q-gutter-md q-mb-md justify-around no-wrap">
|
||||||
<QSkeleton type="rect" square />
|
<QSkeleton type="rect" square v-for="n in columns" :key="n" class="column" />
|
||||||
<QSkeleton type="rect" square />
|
|
||||||
<QSkeleton type="rect" square />
|
|
||||||
<QSkeleton type="rect" square />
|
|
||||||
<QSkeleton type="rect" square />
|
|
||||||
<QSkeleton type="rect" square />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-gutter-md q-mb-md" v-for="n in 5" :key="n">
|
<div
|
||||||
<QSkeleton type="QInput" square />
|
class="row q-gutter-md q-mb-md justify-around no-wrap"
|
||||||
<QSkeleton type="QInput" square />
|
v-for="n in 5"
|
||||||
<QSkeleton type="QInput" square />
|
:key="n"
|
||||||
<QSkeleton type="QInput" square />
|
>
|
||||||
<QSkeleton type="QInput" square />
|
<QSkeleton
|
||||||
<QSkeleton type="QInput" square />
|
type="QInput"
|
||||||
|
square
|
||||||
|
v-for="m in columns"
|
||||||
|
:key="m"
|
||||||
|
class="column"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.w {
|
.container {
|
||||||
width: 80vw;
|
width: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
.column {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 200px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -67,6 +67,7 @@ async function confirm() {
|
||||||
</QCardSection>
|
</QCardSection>
|
||||||
<QCardSection class="row items-center">
|
<QCardSection class="row items-center">
|
||||||
<span v-html="message"></span>
|
<span v-html="message"></span>
|
||||||
|
<slot name="customHTML"></slot>
|
||||||
</QCardSection>
|
</QCardSection>
|
||||||
<QCardActions align="right">
|
<QCardActions align="right">
|
||||||
<QBtn
|
<QBtn
|
||||||
|
|
|
@ -4,11 +4,11 @@ import { useI18n } from 'vue-i18n';
|
||||||
import { useArrayData } from 'composables/useArrayData';
|
import { useArrayData } from 'composables/useArrayData';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import toDate from 'filters/toDate';
|
import toDate from 'filters/toDate';
|
||||||
import useRedirect from 'src/composables/useRedirect';
|
|
||||||
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
|
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const props = defineProps({
|
const params = defineModel({ default: {}, required: true, type: Object });
|
||||||
|
const $props = defineProps({
|
||||||
dataKey: {
|
dataKey: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -18,11 +18,6 @@ const props = defineProps({
|
||||||
required: false,
|
required: false,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
params: {
|
|
||||||
type: Object,
|
|
||||||
required: false,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
showAll: {
|
showAll: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
|
@ -40,12 +35,20 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
hiddenTags: {
|
hiddenTags: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => ['filter'],
|
||||||
},
|
},
|
||||||
customTags: {
|
customTags: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
disableSubmitEvent: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
searchUrl: {
|
||||||
|
type: String,
|
||||||
|
default: 'params',
|
||||||
|
},
|
||||||
redirect: {
|
redirect: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
|
@ -54,61 +57,64 @@ const props = defineProps({
|
||||||
|
|
||||||
const emit = defineEmits(['refresh', 'clear', 'search', 'init', 'remove']);
|
const emit = defineEmits(['refresh', 'clear', 'search', 'init', 'remove']);
|
||||||
|
|
||||||
const arrayData = useArrayData(props.dataKey, {
|
const arrayData = useArrayData($props.dataKey, {
|
||||||
exprBuilder: props.exprBuilder,
|
exprBuilder: $props.exprBuilder,
|
||||||
|
searchUrl: $props.searchUrl,
|
||||||
|
navigate: {},
|
||||||
});
|
});
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const store = arrayData.store;
|
const store = arrayData.store;
|
||||||
const userParams = ref({});
|
|
||||||
const { navigate } = useRedirect();
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.params) userParams.value = JSON.parse(JSON.stringify(props.params));
|
emit('init', { params: params.value });
|
||||||
if (Object.keys(store.userParams).length > 0) {
|
|
||||||
userParams.value = JSON.parse(JSON.stringify(store.userParams));
|
|
||||||
}
|
|
||||||
emit('init', { params: userParams.value });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function setUserParams(watchedParams) {
|
||||||
|
if (!watchedParams) return;
|
||||||
|
|
||||||
|
if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams);
|
||||||
|
watchedParams = { ...watchedParams, ...watchedParams.filter?.where };
|
||||||
|
delete watchedParams.filter;
|
||||||
|
params.value = { ...params.value, ...watchedParams };
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.query.params,
|
() => route.query[$props.searchUrl],
|
||||||
(val) => {
|
(val) => setUserParams(val)
|
||||||
if (!val) {
|
);
|
||||||
userParams.value = {};
|
|
||||||
} else {
|
watch(
|
||||||
const parsedParams = JSON.parse(val);
|
() => arrayData.store.userParams,
|
||||||
userParams.value = { ...parsedParams };
|
(val) => setUserParams(val)
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
async function search() {
|
async function search(evt) {
|
||||||
|
if (evt && $props.disableSubmitEvent) return;
|
||||||
|
|
||||||
store.filter.where = {};
|
store.filter.where = {};
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
const params = { ...userParams.value };
|
const filter = { ...params.value };
|
||||||
store.userParamsChanged = true;
|
store.userParamsChanged = true;
|
||||||
store.filter.skip = 0;
|
store.filter.skip = 0;
|
||||||
store.skip = 0;
|
store.skip = 0;
|
||||||
const { params: newParams } = await arrayData.addFilter({ params });
|
const { params: newParams } = await arrayData.addFilter({ params: params.value });
|
||||||
userParams.value = newParams;
|
params.value = newParams;
|
||||||
|
|
||||||
if (!props.showAll && !Object.values(params).length) store.data = [];
|
if (!$props.showAll && !Object.values(filter).length) store.data = [];
|
||||||
|
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
emit('search');
|
emit('search');
|
||||||
if (props.redirect) navigate(store.data, {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reload() {
|
async function reload() {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
const params = Object.values(userParams.value).filter((param) => param);
|
const params = Object.values(params.value).filter((param) => param);
|
||||||
|
|
||||||
await arrayData.fetch({ append: false });
|
await arrayData.fetch({ append: false });
|
||||||
if (!props.showAll && !params.length) store.data = [];
|
if (!$props.showAll && !params.length) store.data = [];
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
emit('refresh');
|
emit('refresh');
|
||||||
if (props.redirect) navigate(store.data, {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clearFilters() {
|
async function clearFilters() {
|
||||||
|
@ -117,18 +123,19 @@ async function clearFilters() {
|
||||||
store.filter.skip = 0;
|
store.filter.skip = 0;
|
||||||
store.skip = 0;
|
store.skip = 0;
|
||||||
// Filtrar los params no removibles
|
// Filtrar los params no removibles
|
||||||
const removableFilters = Object.keys(userParams.value).filter((param) =>
|
const removableFilters = Object.keys(params.value).filter((param) =>
|
||||||
props.unremovableParams.includes(param)
|
$props.unremovableParams.includes(param)
|
||||||
);
|
);
|
||||||
const newParams = {};
|
const newParams = {};
|
||||||
// Conservar solo los params que no son removibles
|
// Conservar solo los params que no son removibles
|
||||||
for (const key of removableFilters) {
|
for (const key of removableFilters) {
|
||||||
newParams[key] = userParams.value[key];
|
newParams[key] = params.value[key];
|
||||||
}
|
}
|
||||||
userParams.value = { ...newParams }; // Actualizar los params con los removibles
|
params.value = {};
|
||||||
await arrayData.applyFilter({ params: userParams.value });
|
params.value = { ...newParams }; // Actualizar los params con los removibles
|
||||||
|
await arrayData.applyFilter({ params: params.value });
|
||||||
|
|
||||||
if (!props.showAll) {
|
if (!$props.showAll) {
|
||||||
store.data = [];
|
store.data = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,36 +143,32 @@ async function clearFilters() {
|
||||||
emit('clear');
|
emit('clear');
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagsList = computed(() =>
|
const tagsList = computed(() => {
|
||||||
Object.entries(userParams.value)
|
const tagList = [];
|
||||||
.filter(([key, value]) => value && !(props.hiddenTags || []).includes(key))
|
for (const key of Object.keys(params.value)) {
|
||||||
.map(([key, value]) => ({
|
const value = params.value[key];
|
||||||
label: key,
|
if (value == null || ($props.hiddenTags || []).includes(key)) continue;
|
||||||
value: value,
|
tagList.push({ label: key, value });
|
||||||
}))
|
}
|
||||||
);
|
return tagList;
|
||||||
|
});
|
||||||
|
|
||||||
const tags = computed(() =>
|
const tags = computed(() => {
|
||||||
tagsList.value.filter((tag) => !(props.customTags || []).includes(tag.label))
|
return tagsList.value.filter((tag) => !($props.customTags || []).includes(tag.key));
|
||||||
);
|
});
|
||||||
const customTags = computed(() =>
|
const customTags = computed(() =>
|
||||||
tagsList.value.filter((tag) => (props.customTags || []).includes(tag.label))
|
tagsList.value.filter((tag) => ($props.customTags || []).includes(tag.key))
|
||||||
);
|
);
|
||||||
|
|
||||||
async function remove(key) {
|
async function remove(key) {
|
||||||
userParams.value[key] = null;
|
params.value[key] = undefined;
|
||||||
await arrayData.applyFilter({ params: userParams.value });
|
search();
|
||||||
emit('remove', key);
|
emit('remove', key);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatValue(value) {
|
function formatValue(value) {
|
||||||
if (typeof value === 'boolean') {
|
if (typeof value === 'boolean') return value ? t('Yes') : t('No');
|
||||||
return value ? t('Yes') : t('No');
|
if (isNaN(value) && !isNaN(Date.parse(value))) return toDate(value);
|
||||||
}
|
|
||||||
|
|
||||||
if (isNaN(value) && !isNaN(Date.parse(value))) {
|
|
||||||
return toDate(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return `"${value}"`;
|
return `"${value}"`;
|
||||||
}
|
}
|
||||||
|
@ -226,14 +229,14 @@ function formatValue(value) {
|
||||||
<slot name="tags" :tag="chip" :format-fn="formatValue">
|
<slot name="tags" :tag="chip" :format-fn="formatValue">
|
||||||
<div class="q-gutter-x-xs">
|
<div class="q-gutter-x-xs">
|
||||||
<strong>{{ chip.label }}:</strong>
|
<strong>{{ chip.label }}:</strong>
|
||||||
<span>"{{ chip.value }}"</span>
|
<span>"{{ formatValue(chip.value) }}"</span>
|
||||||
</div>
|
</div>
|
||||||
</slot>
|
</slot>
|
||||||
</VnFilterPanelChip>
|
</VnFilterPanelChip>
|
||||||
<slot
|
<slot
|
||||||
v-if="$slots.customTags"
|
v-if="$slots.customTags"
|
||||||
name="customTags"
|
name="customTags"
|
||||||
:params="userParams"
|
:params="params"
|
||||||
:tags="customTags"
|
:tags="customTags"
|
||||||
:format-fn="formatValue"
|
:format-fn="formatValue"
|
||||||
:search-fn="search"
|
:search-fn="search"
|
||||||
|
@ -243,9 +246,9 @@ function formatValue(value) {
|
||||||
<QSeparator />
|
<QSeparator />
|
||||||
</QList>
|
</QList>
|
||||||
<QList dense class="list q-gutter-y-sm q-mt-sm">
|
<QList dense class="list q-gutter-y-sm q-mt-sm">
|
||||||
<slot name="body" :params="userParams" :search-fn="search"></slot>
|
<slot name="body" :params="params" :search-fn="search"></slot>
|
||||||
</QList>
|
</QList>
|
||||||
<template v-if="props.searchButton">
|
<template v-if="$props.searchButton">
|
||||||
<QItem>
|
<QItem>
|
||||||
<QItemSection class="q-py-sm">
|
<QItemSection class="q-py-sm">
|
||||||
<QBtn
|
<QBtn
|
||||||
|
@ -255,7 +258,7 @@ function formatValue(value) {
|
||||||
dense
|
dense
|
||||||
icon="search"
|
icon="search"
|
||||||
rounded
|
rounded
|
||||||
type="submit"
|
:type="disableSubmitEvent ? 'button' : 'submit'"
|
||||||
unelevated
|
unelevated
|
||||||
/>
|
/>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
|
@ -269,7 +272,6 @@ function formatValue(value) {
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.list {
|
.list {
|
||||||
width: 256px;
|
width: 256px;
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted } from 'vue';
|
||||||
|
import { useSession } from 'src/composables/useSession';
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
storage: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 'Images',
|
||||||
|
},
|
||||||
|
collection: {
|
||||||
|
type: String,
|
||||||
|
default: 'catalog',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: '200x200',
|
||||||
|
},
|
||||||
|
zoomSize: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: 'lg',
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const show = ref(false);
|
||||||
|
const token = useSession().getTokenMultimedia();
|
||||||
|
const timeStamp = ref(`timestamp=${Date.now()}`);
|
||||||
|
const url = computed(
|
||||||
|
() =>
|
||||||
|
`/api/${$props.storage}/${$props.collection}/${$props.size}/${$props.id}/download?access_token=${token}&${timeStamp.value}`
|
||||||
|
);
|
||||||
|
const reload = (emit = false) => {
|
||||||
|
timeStamp.value = `timestamp=${Date.now()}`;
|
||||||
|
};
|
||||||
|
defineExpose({
|
||||||
|
reload,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<QImg :src="url" v-bind="$attrs" @click="show = !show" spinner-color="primary" />
|
||||||
|
<QDialog v-model="show" v-if="$props.zoomSize">
|
||||||
|
<QImg
|
||||||
|
:src="url"
|
||||||
|
size="full"
|
||||||
|
class="img_zoom"
|
||||||
|
v-bind="$attrs"
|
||||||
|
spinner-color="primary"
|
||||||
|
/>
|
||||||
|
</QDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.q-img {
|
||||||
|
cursor: zoom-in;
|
||||||
|
min-width: 50px;
|
||||||
|
}
|
||||||
|
.rounded {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.img_zoom {
|
||||||
|
border-radius: 0%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -58,14 +58,19 @@ const props = defineProps({
|
||||||
type: Function,
|
type: Function,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
searchUrl: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
disableInfiniteScroll: {
|
disableInfiniteScroll: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['onFetch', 'onPaginate']);
|
const emit = defineEmits(['onFetch', 'onPaginate', 'onChange']);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
const mounted = ref(false);
|
||||||
const pagination = ref({
|
const pagination = ref({
|
||||||
sortBy: props.order,
|
sortBy: props.order,
|
||||||
rowsPerPage: props.limit,
|
rowsPerPage: props.limit,
|
||||||
|
@ -81,11 +86,13 @@ const arrayData = useArrayData(props.dataKey, {
|
||||||
userParams: props.userParams,
|
userParams: props.userParams,
|
||||||
exprBuilder: props.exprBuilder,
|
exprBuilder: props.exprBuilder,
|
||||||
keepOpts: props.keepOpts,
|
keepOpts: props.keepOpts,
|
||||||
|
searchUrl: props.searchUrl,
|
||||||
});
|
});
|
||||||
const store = arrayData.store;
|
const store = arrayData.store;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
if (props.autoLoad) fetch();
|
if (props.autoLoad) await fetch();
|
||||||
|
mounted.value = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
@ -95,11 +102,22 @@ watch(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => store.data,
|
||||||
|
(data) => emit('onChange', data)
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.url,
|
||||||
|
(url) => fetch({ url })
|
||||||
|
);
|
||||||
|
|
||||||
const addFilter = async (filter, params) => {
|
const addFilter = async (filter, params) => {
|
||||||
await arrayData.addFilter({ filter, params });
|
await arrayData.addFilter({ filter, params });
|
||||||
};
|
};
|
||||||
|
|
||||||
async function fetch() {
|
async function fetch(params) {
|
||||||
|
useArrayData(props.dataKey, params);
|
||||||
store.filter.skip = 0;
|
store.filter.skip = 0;
|
||||||
store.skip = 0;
|
store.skip = 0;
|
||||||
await arrayData.fetch({ append: false });
|
await arrayData.fetch({ append: false });
|
||||||
|
@ -107,6 +125,7 @@ async function fetch() {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
emit('onFetch', store.data);
|
emit('onFetch', store.data);
|
||||||
|
return store.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function paginate() {
|
async function paginate() {
|
||||||
|
@ -138,7 +157,7 @@ function endPagination() {
|
||||||
emit('onPaginate');
|
emit('onPaginate');
|
||||||
}
|
}
|
||||||
async function onLoad(index, done) {
|
async function onLoad(index, done) {
|
||||||
if (!store.data) return done();
|
if (!store.data || !mounted.value) return done();
|
||||||
|
|
||||||
if (store.data.length === 0 || !props.url) return done(false);
|
if (store.data.length === 0 || !props.url) return done(false);
|
||||||
|
|
||||||
|
@ -150,7 +169,7 @@ async function onLoad(index, done) {
|
||||||
done(isDone);
|
done(isDone);
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({ fetch, addFilter });
|
defineExpose({ fetch, addFilter, paginate });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -199,12 +218,6 @@ defineExpose({ fetch, addFilter });
|
||||||
<QSpinner color="orange" size="md" />
|
<QSpinner color="orange" size="md" />
|
||||||
</div>
|
</div>
|
||||||
</QInfiniteScroll>
|
</QInfiniteScroll>
|
||||||
<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>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { onMounted, ref, watch } from 'vue';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { useArrayData } from 'composables/useArrayData';
|
import { useArrayData } from 'composables/useArrayData';
|
||||||
import VnInput from 'src/components/common/VnInput.vue';
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
import useRedirect from 'src/composables/useRedirect';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useStateStore } from 'src/stores/useStateStore';
|
import { useStateStore } from 'src/stores/useStateStore';
|
||||||
|
|
||||||
|
@ -18,17 +17,14 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
|
||||||
default: 'Search',
|
default: 'Search',
|
||||||
},
|
},
|
||||||
info: {
|
info: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
redirect: {
|
redirect: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
url: {
|
url: {
|
||||||
|
@ -73,10 +69,20 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let arrayData = useArrayData(props.dataKey, { ...props });
|
|
||||||
let store = arrayData.store;
|
|
||||||
const searchText = ref('');
|
const searchText = ref('');
|
||||||
const { navigate } = useRedirect();
|
let arrayDataProps = { ...props };
|
||||||
|
if (props.redirect)
|
||||||
|
arrayDataProps = {
|
||||||
|
...props,
|
||||||
|
...{
|
||||||
|
navigate: {
|
||||||
|
customRouteRedirectName: props.customRouteRedirectName,
|
||||||
|
searchText: searchText.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let arrayData = useArrayData(props.dataKey, arrayDataProps);
|
||||||
|
let store = arrayData.store;
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.dataKey,
|
() => props.dataKey,
|
||||||
|
@ -106,13 +112,6 @@ async function search() {
|
||||||
search: searchText.value,
|
search: searchText.value,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!props.redirect) return;
|
|
||||||
|
|
||||||
navigate(store.data, {
|
|
||||||
customRouteRedirectName: props.customRouteRedirectName,
|
|
||||||
searchText: searchText.value,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useState } from './useState';
|
||||||
|
|
||||||
|
export function useAcl() {
|
||||||
|
const state = useState();
|
||||||
|
|
||||||
|
async function fetch() {
|
||||||
|
const { data } = await axios.get('VnUsers/acls');
|
||||||
|
const acls = {};
|
||||||
|
data.forEach((acl) => {
|
||||||
|
acls[acl.model] = acls[acl.model] || {};
|
||||||
|
acls[acl.model][acl.property] = acls[acl.model][acl.property] || {};
|
||||||
|
acls[acl.model][acl.property][acl.accessType] = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
state.setAcls(acls);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasAny(model, prop, accessType) {
|
||||||
|
const acls = state.getAcls().value[model];
|
||||||
|
if (acls)
|
||||||
|
return ['*', prop].some((key) => {
|
||||||
|
const acl = acls[key];
|
||||||
|
return acl && (acl['*'] || acl[accessType]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
fetch,
|
||||||
|
hasAny,
|
||||||
|
state,
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { onMounted, ref, computed } from 'vue';
|
import { onMounted, ref, computed } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { useArrayDataStore } from 'stores/useArrayDataStore';
|
import { useArrayDataStore } from 'stores/useArrayDataStore';
|
||||||
import { buildFilter } from 'filters/filterPanel';
|
import { buildFilter } from 'filters/filterPanel';
|
||||||
|
@ -13,6 +13,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
||||||
|
|
||||||
const store = arrayDataStore.get(key);
|
const store = arrayDataStore.get(key);
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
let canceller = null;
|
let canceller = null;
|
||||||
|
|
||||||
const page = ref(1);
|
const page = ref(1);
|
||||||
|
@ -22,8 +23,13 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
||||||
store.skip = 0;
|
store.skip = 0;
|
||||||
|
|
||||||
const query = route.query;
|
const query = route.query;
|
||||||
if (query.params) {
|
const searchUrl = store.searchUrl;
|
||||||
store.userParams = JSON.parse(query.params);
|
if (query[searchUrl]) {
|
||||||
|
const params = JSON.parse(query[searchUrl]);
|
||||||
|
const filter = params?.filter;
|
||||||
|
delete params.filter;
|
||||||
|
store.userParams = { ...params, ...store.userParams };
|
||||||
|
store.userFilter = { ...JSON.parse(filter), ...store.userFilter };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -40,13 +46,15 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
||||||
'userParams',
|
'userParams',
|
||||||
'userFilter',
|
'userFilter',
|
||||||
'exprBuilder',
|
'exprBuilder',
|
||||||
|
'searchUrl',
|
||||||
|
'navigate',
|
||||||
];
|
];
|
||||||
if (typeof userOptions === 'object') {
|
if (typeof userOptions === 'object') {
|
||||||
for (const option in userOptions) {
|
for (const option in userOptions) {
|
||||||
const isEmpty = userOptions[option] == null || userOptions[option] === '';
|
const isEmpty = userOptions[option] == null || userOptions[option] === '';
|
||||||
if (isEmpty || !allowedOptions.includes(option)) continue;
|
if (isEmpty || !allowedOptions.includes(option)) continue;
|
||||||
|
|
||||||
if (Object.prototype.hasOwnProperty.call(store, option)) {
|
if (Object.hasOwn(store, option)) {
|
||||||
const defaultOpts = userOptions[option];
|
const defaultOpts = userOptions[option];
|
||||||
store[option] = userOptions.keepOpts?.includes(option)
|
store[option] = userOptions.keepOpts?.includes(option)
|
||||||
? Object.assign(defaultOpts, store[option])
|
? Object.assign(defaultOpts, store[option])
|
||||||
|
@ -87,8 +95,8 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
||||||
|
|
||||||
Object.assign(params, userParams);
|
Object.assign(params, userParams);
|
||||||
|
|
||||||
store.isLoading = true;
|
|
||||||
store.currentFilter = params;
|
store.currentFilter = params;
|
||||||
|
store.isLoading = true;
|
||||||
const response = await axios.get(store.url, {
|
const response = await axios.get(store.url, {
|
||||||
signal: canceller.signal,
|
signal: canceller.signal,
|
||||||
params,
|
params,
|
||||||
|
@ -118,6 +126,10 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteOption(option) {
|
||||||
|
delete store[option];
|
||||||
|
}
|
||||||
|
|
||||||
function cancelRequest() {
|
function cancelRequest() {
|
||||||
if (canceller) {
|
if (canceller) {
|
||||||
canceller.abort();
|
canceller.abort();
|
||||||
|
@ -128,7 +140,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
||||||
async function applyFilter({ filter, params }) {
|
async function applyFilter({ filter, params }) {
|
||||||
if (filter) store.userFilter = filter;
|
if (filter) store.userFilter = filter;
|
||||||
store.filter = {};
|
store.filter = {};
|
||||||
if (params) store.userParams = Object.assign({}, params);
|
if (params) store.userParams = { ...params };
|
||||||
|
|
||||||
const response = await fetch({ append: false });
|
const response = await fetch({ append: false });
|
||||||
return response;
|
return response;
|
||||||
|
@ -137,7 +149,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
||||||
async function addFilter({ filter, params }) {
|
async function addFilter({ filter, params }) {
|
||||||
if (filter) store.userFilter = Object.assign(store.userFilter, filter);
|
if (filter) store.userFilter = Object.assign(store.userFilter, filter);
|
||||||
|
|
||||||
let userParams = Object.assign({}, store.userParams, params);
|
let userParams = { ...store.userParams, ...params };
|
||||||
userParams = sanitizerParams(userParams, store?.exprBuilder);
|
userParams = sanitizerParams(userParams, store?.exprBuilder);
|
||||||
|
|
||||||
store.userParams = userParams;
|
store.userParams = userParams;
|
||||||
|
@ -149,15 +161,20 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
||||||
return { filter, params };
|
return { filter, params };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function addFilterWhere(where) {
|
||||||
|
const storedFilter = { ...store.userFilter };
|
||||||
|
if (!storedFilter?.where) storedFilter.where = {};
|
||||||
|
where = { ...storedFilter.where, ...where };
|
||||||
|
await addFilter({ filter: { where } });
|
||||||
|
}
|
||||||
|
|
||||||
function sanitizerParams(params, exprBuilder) {
|
function sanitizerParams(params, exprBuilder) {
|
||||||
for (const param in params) {
|
for (const param in params) {
|
||||||
if (params[param] === '' || params[param] === null) {
|
if (params[param] === '' || params[param] === null) {
|
||||||
delete store.userParams[param];
|
delete store.userParams[param];
|
||||||
delete params[param];
|
delete params[param];
|
||||||
if (store.filter?.where) {
|
if (store.filter?.where) {
|
||||||
const key = Object.keys(
|
const key = Object.keys(exprBuilder ? exprBuilder(param) : param);
|
||||||
exprBuilder && exprBuilder(param) ? exprBuilder(param) : param
|
|
||||||
);
|
|
||||||
if (key[0]) delete store.filter.where[key[0]];
|
if (key[0]) delete store.filter.where[key[0]];
|
||||||
if (Object.keys(store.filter.where).length === 0) {
|
if (Object.keys(store.filter.where).length === 0) {
|
||||||
delete store.filter.where;
|
delete store.filter.where;
|
||||||
|
@ -182,22 +199,34 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStateParams() {
|
function updateStateParams() {
|
||||||
const query = {};
|
const newUrl = { path: route.path, query: { ...(route.query ?? {}) } };
|
||||||
if (store.order) query.order = store.order;
|
newUrl.query[store.searchUrl] = JSON.stringify(store.currentFilter);
|
||||||
if (store.limit) query.limit = store.limit;
|
|
||||||
if (store.skip) query.skip = store.skip;
|
|
||||||
if (store.userParams && Object.keys(store.userParams).length !== 0)
|
|
||||||
query.params = JSON.stringify(store.userParams);
|
|
||||||
|
|
||||||
const url = new URL(window.location.href);
|
if (store.navigate) {
|
||||||
const { hash: currentHash } = url;
|
const { customRouteRedirectName, searchText } = store.navigate;
|
||||||
const [currentRoute] = currentHash.split('?');
|
if (customRouteRedirectName)
|
||||||
|
return router.push({
|
||||||
|
name: customRouteRedirectName,
|
||||||
|
params: { id: searchText },
|
||||||
|
});
|
||||||
|
const { matched: matches } = router.currentRoute.value;
|
||||||
|
const { path } = matches.at(-1);
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const to =
|
||||||
for (const param in query) params.append(param, query[param]);
|
store?.data?.length === 1
|
||||||
|
? path.replace(/\/(list|:id)|-list/, `/${store.data[0].id}`)
|
||||||
|
: path.replace(/:id.*/, '');
|
||||||
|
|
||||||
url.hash = currentRoute + '?' + params.toString();
|
if (route.path != to) {
|
||||||
window.history.pushState({}, '', url.hash);
|
const pushUrl = { path: to };
|
||||||
|
if (to.endsWith('/list') || to.endsWith('/'))
|
||||||
|
pushUrl.query = newUrl.query;
|
||||||
|
destroy();
|
||||||
|
return router.push(pushUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
router.replace(newUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalRows = computed(() => (store.data && store.data.length) || 0);
|
const totalRows = computed(() => (store.data && store.data.length) || 0);
|
||||||
|
@ -207,6 +236,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
||||||
fetch,
|
fetch,
|
||||||
applyFilter,
|
applyFilter,
|
||||||
addFilter,
|
addFilter,
|
||||||
|
addFilterWhere,
|
||||||
refresh,
|
refresh,
|
||||||
destroy,
|
destroy,
|
||||||
loadMore,
|
loadMore,
|
||||||
|
@ -214,5 +244,6 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
|
||||||
totalRows,
|
totalRows,
|
||||||
updateStateParams,
|
updateStateParams,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
deleteOption,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
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 };
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { useState } from './useState';
|
import { useState } from './useState';
|
||||||
import { useRole } from './useRole';
|
import { useRole } from './useRole';
|
||||||
|
import { useAcl } from './useAcl';
|
||||||
import { useUserConfig } from './useUserConfig';
|
import { useUserConfig } from './useUserConfig';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import useNotify from './useNotify';
|
import useNotify from './useNotify';
|
||||||
|
@ -88,6 +89,7 @@ export function useSession() {
|
||||||
setSession(data);
|
setSession(data);
|
||||||
|
|
||||||
await useRole().fetch();
|
await useRole().fetch();
|
||||||
|
await useAcl().fetch();
|
||||||
await useUserConfig().fetch();
|
await useUserConfig().fetch();
|
||||||
await useTokenConfig().fetch();
|
await useTokenConfig().fetch();
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ if (sessionStorage.getItem('user'))
|
||||||
user.value = JSON.parse(sessionStorage.getItem('user'));
|
user.value = JSON.parse(sessionStorage.getItem('user'));
|
||||||
|
|
||||||
const roles = ref([]);
|
const roles = ref([]);
|
||||||
|
const acls = ref([]);
|
||||||
const tokenConfig = ref({});
|
const tokenConfig = ref({});
|
||||||
const drawer = ref(true);
|
const drawer = ref(true);
|
||||||
const headerMounted = ref(false);
|
const headerMounted = ref(false);
|
||||||
|
@ -42,6 +43,14 @@ export function useState() {
|
||||||
function setRoles(data) {
|
function setRoles(data) {
|
||||||
roles.value = data;
|
roles.value = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAcls() {
|
||||||
|
return computed(() => acls.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAcls(data) {
|
||||||
|
acls.value = data;
|
||||||
|
}
|
||||||
function getTokenConfig() {
|
function getTokenConfig() {
|
||||||
return computed(() => {
|
return computed(() => {
|
||||||
return tokenConfig.value;
|
return tokenConfig.value;
|
||||||
|
@ -69,6 +78,8 @@ export function useState() {
|
||||||
setUser,
|
setUser,
|
||||||
getRoles,
|
getRoles,
|
||||||
setRoles,
|
setRoles,
|
||||||
|
getAcls,
|
||||||
|
setAcls,
|
||||||
getTokenConfig,
|
getTokenConfig,
|
||||||
setTokenConfig,
|
setTokenConfig,
|
||||||
set,
|
set,
|
||||||
|
|
|
@ -115,6 +115,13 @@ select:-webkit-autofill {
|
||||||
background-color: var(--vn-accent-color);
|
background-color: var(--vn-accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-primary-light {
|
||||||
|
color: $primary-light !important;
|
||||||
|
}
|
||||||
|
.bg-primary-light {
|
||||||
|
background: $primary-light !important;
|
||||||
|
}
|
||||||
|
|
||||||
.fill-icon {
|
.fill-icon {
|
||||||
font-variation-settings: 'FILL' 1;
|
font-variation-settings: 'FILL' 1;
|
||||||
}
|
}
|
||||||
|
@ -189,3 +196,26 @@ input::-webkit-inner-spin-button {
|
||||||
.q-scrollarea__content {
|
.q-scrollarea__content {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== Scrollbar CSS ===== /
|
||||||
|
/ Firefox */
|
||||||
|
|
||||||
|
* {
|
||||||
|
scrollbar-width: auto;
|
||||||
|
scrollbar-color: var(--vn-label-color) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chrome, Edge, and Safari */
|
||||||
|
*::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--vn-label-color);
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ $color-link: #66bfff;
|
||||||
$color-spacer-light: #a3a3a31f;
|
$color-spacer-light: #a3a3a31f;
|
||||||
$color-spacer: #7979794d;
|
$color-spacer: #7979794d;
|
||||||
$border-thin-light: 1px solid $color-spacer-light;
|
$border-thin-light: 1px solid $color-spacer-light;
|
||||||
$primary-light: lighten($primary, 35%);
|
$primary-light: #f5b351;
|
||||||
$dark-shadow-color: black;
|
$dark-shadow-color: black;
|
||||||
$layout-shadow-dark: 0 0 10px 2px #00000033, 0 0px 10px #0000003d;
|
$layout-shadow-dark: 0 0 10px 2px #00000033, 0 0px 10px #0000003d;
|
||||||
$spacing-md: 16px;
|
$spacing-md: 16px;
|
||||||
|
|
|
@ -8,11 +8,8 @@ export default function (value, fractionSize = 2) {
|
||||||
const options = {
|
const options = {
|
||||||
style: 'percent',
|
style: 'percent',
|
||||||
minimumFractionDigits: fractionSize,
|
minimumFractionDigits: fractionSize,
|
||||||
maximumFractionDigits: fractionSize
|
maximumFractionDigits: fractionSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
return new Intl.NumberFormat(locale, options)
|
return new Intl.NumberFormat(locale, options).format(parseFloat(value));
|
||||||
.format(parseFloat(value));
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -113,6 +113,7 @@ globals:
|
||||||
name: Name
|
name: Name
|
||||||
new: New
|
new: New
|
||||||
comment: Comment
|
comment: Comment
|
||||||
|
observations: Observations
|
||||||
errors:
|
errors:
|
||||||
statusUnauthorized: Access denied
|
statusUnauthorized: Access denied
|
||||||
statusInternalServerError: An internal server error has ocurred
|
statusInternalServerError: An internal server error has ocurred
|
||||||
|
@ -279,8 +280,8 @@ customer:
|
||||||
extendedList:
|
extendedList:
|
||||||
tableVisibleColumns:
|
tableVisibleColumns:
|
||||||
id: Identifier
|
id: Identifier
|
||||||
name: Name
|
name: Comercial name
|
||||||
socialName: Social name
|
socialName: Business name
|
||||||
fi: Tax number
|
fi: Tax number
|
||||||
salesPersonFk: Salesperson
|
salesPersonFk: Salesperson
|
||||||
credit: Credit
|
credit: Credit
|
||||||
|
@ -396,6 +397,7 @@ entry:
|
||||||
type: Type
|
type: Type
|
||||||
color: Color
|
color: Color
|
||||||
id: ID
|
id: ID
|
||||||
|
printedStickers: Printed stickers
|
||||||
notes:
|
notes:
|
||||||
observationType: Observation type
|
observationType: Observation type
|
||||||
descriptor:
|
descriptor:
|
||||||
|
@ -420,6 +422,7 @@ entry:
|
||||||
buyingValue: Buying value
|
buyingValue: Buying value
|
||||||
freightValue: Freight value
|
freightValue: Freight value
|
||||||
comissionValue: Commission value
|
comissionValue: Commission value
|
||||||
|
description: Description
|
||||||
packageValue: Package value
|
packageValue: Package value
|
||||||
isIgnored: Is ignored
|
isIgnored: Is ignored
|
||||||
price2: Grouping
|
price2: Grouping
|
||||||
|
@ -441,6 +444,8 @@ ticket:
|
||||||
sms: Sms
|
sms: Sms
|
||||||
notes: Notes
|
notes: Notes
|
||||||
sale: Sale
|
sale: Sale
|
||||||
|
ticketAdvance: Advance tickets
|
||||||
|
futureTickets: Future tickets
|
||||||
list:
|
list:
|
||||||
nickname: Nickname
|
nickname: Nickname
|
||||||
state: State
|
state: State
|
||||||
|
@ -472,6 +477,7 @@ ticket:
|
||||||
agency: Agency
|
agency: Agency
|
||||||
zone: Zone
|
zone: Zone
|
||||||
warehouse: Warehouse
|
warehouse: Warehouse
|
||||||
|
collection: Collection
|
||||||
route: Route
|
route: Route
|
||||||
invoice: Invoice
|
invoice: Invoice
|
||||||
shipped: Shipped
|
shipped: Shipped
|
||||||
|
@ -581,6 +587,9 @@ claim:
|
||||||
created: Created
|
created: Created
|
||||||
state: State
|
state: State
|
||||||
pickup: Pick up
|
pickup: Pick up
|
||||||
|
null: No
|
||||||
|
agency: Agency
|
||||||
|
delivery: Delivery
|
||||||
photo:
|
photo:
|
||||||
fileDescription: 'Claim id {claimId} from client {clientName} id {clientId}'
|
fileDescription: 'Claim id {claimId} from client {clientName} id {clientId}'
|
||||||
noData: 'There are no images/videos, click here or drag and drop the file'
|
noData: 'There are no images/videos, click here or drag and drop the file'
|
||||||
|
@ -838,7 +847,7 @@ worker:
|
||||||
calendar: Calendar
|
calendar: Calendar
|
||||||
timeControl: Time control
|
timeControl: Time control
|
||||||
locker: Locker
|
locker: Locker
|
||||||
|
formation: Formation
|
||||||
list:
|
list:
|
||||||
name: Name
|
name: Name
|
||||||
email: Email
|
email: Email
|
||||||
|
@ -908,6 +917,16 @@ worker:
|
||||||
payMethods: Pay method
|
payMethods: Pay method
|
||||||
iban: IBAN
|
iban: IBAN
|
||||||
bankEntity: Swift / BIC
|
bankEntity: Swift / BIC
|
||||||
|
formation:
|
||||||
|
tableVisibleColumns:
|
||||||
|
course: Curso
|
||||||
|
startDate: Fecha Inicio
|
||||||
|
endDate: Fecha Fin
|
||||||
|
center: Centro Formación
|
||||||
|
invoice: Factura
|
||||||
|
amount: Importe
|
||||||
|
remark: Bonficado
|
||||||
|
hasDiploma: Diploma
|
||||||
imageNotFound: Image not found
|
imageNotFound: Image not found
|
||||||
wagon:
|
wagon:
|
||||||
pageTitles:
|
pageTitles:
|
||||||
|
@ -962,7 +981,7 @@ roadmap:
|
||||||
route:
|
route:
|
||||||
pageTitles:
|
pageTitles:
|
||||||
routes: Routes
|
routes: Routes
|
||||||
cmrsList: External CMRs list
|
cmrsList: CMRs list
|
||||||
RouteList: List
|
RouteList: List
|
||||||
routeCreate: New route
|
routeCreate: New route
|
||||||
basicData: Basic Data
|
basicData: Basic Data
|
||||||
|
@ -986,6 +1005,18 @@ route:
|
||||||
shipped: Preparation date
|
shipped: Preparation date
|
||||||
viewCmr: View CMR
|
viewCmr: View CMR
|
||||||
downloadCmrs: Download CMRs
|
downloadCmrs: Download CMRs
|
||||||
|
columnLabels:
|
||||||
|
Id: Id
|
||||||
|
vehicle: Vehicle
|
||||||
|
description: Description
|
||||||
|
isServed: Served
|
||||||
|
worker: Worker
|
||||||
|
date: Date
|
||||||
|
started: Started
|
||||||
|
actions: Actions
|
||||||
|
agency: Agency
|
||||||
|
volume: Volume
|
||||||
|
finished: Finished
|
||||||
supplier:
|
supplier:
|
||||||
pageTitles:
|
pageTitles:
|
||||||
suppliers: Suppliers
|
suppliers: Suppliers
|
||||||
|
@ -1177,6 +1208,7 @@ item:
|
||||||
available: Available
|
available: Available
|
||||||
warehouseText: 'Calculated on the warehouse of { warehouseName }'
|
warehouseText: 'Calculated on the warehouse of { warehouseName }'
|
||||||
itemDiary: Item diary
|
itemDiary: Item diary
|
||||||
|
producer: Producer
|
||||||
list:
|
list:
|
||||||
id: Identifier
|
id: Identifier
|
||||||
grouping: Grouping
|
grouping: Grouping
|
||||||
|
|
|
@ -107,12 +107,14 @@ globals:
|
||||||
aliasUsers: Usuarios
|
aliasUsers: Usuarios
|
||||||
subRoles: Subroles
|
subRoles: Subroles
|
||||||
inheritedRoles: Roles heredados
|
inheritedRoles: Roles heredados
|
||||||
|
workers: Trabajadores
|
||||||
created: Fecha creación
|
created: Fecha creación
|
||||||
worker: Trabajador
|
worker: Trabajador
|
||||||
now: Ahora
|
now: Ahora
|
||||||
name: Nombre
|
name: Nombre
|
||||||
new: Nuevo
|
new: Nuevo
|
||||||
comment: Comentario
|
comment: Comentario
|
||||||
|
observations: Observaciones
|
||||||
errors:
|
errors:
|
||||||
statusUnauthorized: Acceso denegado
|
statusUnauthorized: Acceso denegado
|
||||||
statusInternalServerError: Ha ocurrido un error interno del servidor
|
statusInternalServerError: Ha ocurrido un error interno del servidor
|
||||||
|
@ -277,7 +279,7 @@ customer:
|
||||||
extendedList:
|
extendedList:
|
||||||
tableVisibleColumns:
|
tableVisibleColumns:
|
||||||
id: Identificador
|
id: Identificador
|
||||||
name: Nombre
|
name: Nombre Comercial
|
||||||
socialName: Razón social
|
socialName: Razón social
|
||||||
fi: NIF / CIF
|
fi: NIF / CIF
|
||||||
salesPersonFk: Comercial
|
salesPersonFk: Comercial
|
||||||
|
@ -394,6 +396,7 @@ entry:
|
||||||
type: Tipo
|
type: Tipo
|
||||||
color: Color
|
color: Color
|
||||||
id: ID
|
id: ID
|
||||||
|
printedStickers: Etiquetas impresas
|
||||||
notes:
|
notes:
|
||||||
observationType: Tipo de observación
|
observationType: Tipo de observación
|
||||||
descriptor:
|
descriptor:
|
||||||
|
@ -418,6 +421,7 @@ entry:
|
||||||
buyingValue: Coste
|
buyingValue: Coste
|
||||||
freightValue: Porte
|
freightValue: Porte
|
||||||
comissionValue: Comisión
|
comissionValue: Comisión
|
||||||
|
description: Descripción
|
||||||
packageValue: Embalaje
|
packageValue: Embalaje
|
||||||
isIgnored: Ignorado
|
isIgnored: Ignorado
|
||||||
price2: Grouping
|
price2: Grouping
|
||||||
|
@ -439,6 +443,8 @@ ticket:
|
||||||
sms: Sms
|
sms: Sms
|
||||||
notes: Notas
|
notes: Notas
|
||||||
sale: Lineas del pedido
|
sale: Lineas del pedido
|
||||||
|
ticketAdvance: Adelantar tickets
|
||||||
|
futureTickets: Tickets a futuro
|
||||||
list:
|
list:
|
||||||
nickname: Alias
|
nickname: Alias
|
||||||
state: Estado
|
state: Estado
|
||||||
|
@ -470,6 +476,7 @@ ticket:
|
||||||
agency: Agencia
|
agency: Agencia
|
||||||
zone: Zona
|
zone: Zona
|
||||||
warehouse: Almacén
|
warehouse: Almacén
|
||||||
|
collection: Colección
|
||||||
route: Ruta
|
route: Ruta
|
||||||
invoice: Factura
|
invoice: Factura
|
||||||
shipped: Enviado
|
shipped: Enviado
|
||||||
|
@ -836,6 +843,7 @@ worker:
|
||||||
calendar: Calendario
|
calendar: Calendario
|
||||||
timeControl: Control de horario
|
timeControl: Control de horario
|
||||||
locker: Taquilla
|
locker: Taquilla
|
||||||
|
formation: Formación
|
||||||
list:
|
list:
|
||||||
name: Nombre
|
name: Nombre
|
||||||
email: Email
|
email: Email
|
||||||
|
@ -896,6 +904,16 @@ worker:
|
||||||
payMethods: Método de pago
|
payMethods: Método de pago
|
||||||
iban: IBAN
|
iban: IBAN
|
||||||
bankEntity: Swift / BIC
|
bankEntity: Swift / BIC
|
||||||
|
formation:
|
||||||
|
tableVisibleColumns:
|
||||||
|
course: Curso
|
||||||
|
startDate: Fecha Inicio
|
||||||
|
endDate: Fecha Fin
|
||||||
|
center: Centro Formación
|
||||||
|
invoice: Factura
|
||||||
|
amount: Importe
|
||||||
|
remark: Bonficado
|
||||||
|
hasDiploma: Diploma
|
||||||
imageNotFound: No se ha encontrado la imagen
|
imageNotFound: No se ha encontrado la imagen
|
||||||
wagon:
|
wagon:
|
||||||
pageTitles:
|
pageTitles:
|
||||||
|
@ -950,7 +968,7 @@ roadmap:
|
||||||
route:
|
route:
|
||||||
pageTitles:
|
pageTitles:
|
||||||
routes: Rutas
|
routes: Rutas
|
||||||
cmrsList: Listado de CMRs externos
|
cmrsList: Listado de CMRs
|
||||||
RouteList: Listado
|
RouteList: Listado
|
||||||
routeCreate: Nueva ruta
|
routeCreate: Nueva ruta
|
||||||
basicData: Datos básicos
|
basicData: Datos básicos
|
||||||
|
@ -974,6 +992,18 @@ route:
|
||||||
shipped: Fecha preparación
|
shipped: Fecha preparación
|
||||||
viewCmr: Ver CMR
|
viewCmr: Ver CMR
|
||||||
downloadCmrs: Descargar CMRs
|
downloadCmrs: Descargar CMRs
|
||||||
|
columnLabels:
|
||||||
|
Id: Id
|
||||||
|
vehicle: Vehículo
|
||||||
|
description: Descripción
|
||||||
|
isServed: Servida
|
||||||
|
worker: Trabajador
|
||||||
|
date: Fecha
|
||||||
|
started: Iniciada
|
||||||
|
actions: Acciones
|
||||||
|
agency: Agencia
|
||||||
|
volume: Volumen
|
||||||
|
finished: Finalizada
|
||||||
supplier:
|
supplier:
|
||||||
pageTitles:
|
pageTitles:
|
||||||
suppliers: Proveedores
|
suppliers: Proveedores
|
||||||
|
@ -1166,6 +1196,7 @@ item:
|
||||||
available: Disponible
|
available: Disponible
|
||||||
warehouseText: 'Calculado sobre el almacén de { warehouseName }'
|
warehouseText: 'Calculado sobre el almacén de { warehouseName }'
|
||||||
itemDiary: Registro de compra-venta
|
itemDiary: Registro de compra-venta
|
||||||
|
producer: Productor
|
||||||
list:
|
list:
|
||||||
id: Identificador
|
id: Identificador
|
||||||
grouping: Grouping
|
grouping: Grouping
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
<script setup>
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import FormModel from 'components/FormModel.vue';
|
||||||
|
import VnRow from 'components/ui/VnRow.vue';
|
||||||
|
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||||
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { notify } = useNotify();
|
||||||
|
|
||||||
|
const onSynchronizeAll = async () => {
|
||||||
|
try {
|
||||||
|
notify(t('Synchronizing in the background'), 'positive');
|
||||||
|
await axios.patch(`Accounts/syncAll`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error synchronizing all accounts', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSynchronizeRoles = async () => {
|
||||||
|
try {
|
||||||
|
await axios.patch(`RoleInherits/sync`);
|
||||||
|
notify(t('Roles synchronized!'), 'positive');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error synchronizing roles', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<QPage>
|
||||||
|
<VnSubToolbar />
|
||||||
|
<FormModel
|
||||||
|
:url="`AccountConfigs/${1}`"
|
||||||
|
:url-update="`AccountConfigs/${1}`"
|
||||||
|
model="AccountAccounts"
|
||||||
|
auto-load
|
||||||
|
>
|
||||||
|
<template #moreActions>
|
||||||
|
<QBtn
|
||||||
|
class="q-ml-none"
|
||||||
|
color="primary"
|
||||||
|
:label="t('accounts.syncAll')"
|
||||||
|
@click="onSynchronizeAll()"
|
||||||
|
/>
|
||||||
|
<QBtn
|
||||||
|
color="primary"
|
||||||
|
:label="t('accounts.syncRoles')"
|
||||||
|
@click="onSynchronizeRoles()"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #form="{ data }">
|
||||||
|
<div class="q-gutter-y-sm">
|
||||||
|
<VnInput :label="t('accounts.homedir')" v-model="data.homedir" />
|
||||||
|
<VnInput :label="t('accounts.shell')" v-model="data.shell" />
|
||||||
|
<VnInput
|
||||||
|
:label="t('accounts.idBase')"
|
||||||
|
v-model="data.idBase"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
|
<VnRow>
|
||||||
|
<VnInput
|
||||||
|
:label="t('accounts.min')"
|
||||||
|
v-model="data.min"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
|
<VnInput
|
||||||
|
:label="t('accounts.max')"
|
||||||
|
v-model="data.max"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
|
</VnRow>
|
||||||
|
<VnRow>
|
||||||
|
<VnInput
|
||||||
|
:label="t('accounts.warn')"
|
||||||
|
v-model="data.warn"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
|
<VnInput
|
||||||
|
:label="t('accounts.inact')"
|
||||||
|
v-model="data.inact"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
|
</VnRow>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</FormModel>
|
||||||
|
</QPage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
Roles synchronized!: ¡Roles sincronizados!
|
||||||
|
Synchronizing in the background: Sincronizando en segundo plano
|
||||||
|
</i18n>
|
|
@ -8,12 +8,12 @@ import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
||||||
import CardList from 'src/components/ui/CardList.vue';
|
import CardList from 'src/components/ui/CardList.vue';
|
||||||
import VnLv from 'src/components/ui/VnLv.vue';
|
import VnLv from 'src/components/ui/VnLv.vue';
|
||||||
import AclFilter from './Acls/AclFilter.vue';
|
import AclFilter from './Acls/AclFilter.vue';
|
||||||
|
import AclFormView from './Acls/AclFormView.vue';
|
||||||
|
|
||||||
import { useVnConfirm } from 'composables/useVnConfirm';
|
import { useVnConfirm } from 'composables/useVnConfirm';
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import useNotify from 'src/composables/useNotify.js';
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
import AclFormView from './Acls/AclFormView.vue';
|
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
id: {
|
id: {
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||||
|
import CardList from 'src/components/ui/CardList.vue';
|
||||||
|
import VnLv from 'src/components/ui/VnLv.vue';
|
||||||
|
|
||||||
|
import { toDateTimeFormat } from 'src/filters/date.js';
|
||||||
|
import axios from 'axios';
|
||||||
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
import { useVnConfirm } from 'composables/useVnConfirm';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const router = useRouter();
|
||||||
|
const { notify } = useNotify();
|
||||||
|
const { openConfirmationModal } = useVnConfirm();
|
||||||
|
|
||||||
|
const paginateRef = ref(null);
|
||||||
|
const filter = {
|
||||||
|
fields: ['id', 'created', 'userId'],
|
||||||
|
include: {
|
||||||
|
relation: 'user',
|
||||||
|
scope: {
|
||||||
|
fields: ['username'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
order: 'created DESC',
|
||||||
|
};
|
||||||
|
|
||||||
|
const urlPath = 'AccessTokens';
|
||||||
|
|
||||||
|
const refresh = () => paginateRef.value.fetch();
|
||||||
|
|
||||||
|
const navigate = (id) => router.push({ name: 'AccountSummary', params: { id } });
|
||||||
|
|
||||||
|
const killSession = async (id) => {
|
||||||
|
try {
|
||||||
|
await axios.delete(`${urlPath}/${id}`);
|
||||||
|
paginateRef.value.fetch();
|
||||||
|
notify(t('Session killed'), 'positive');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error killing session', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<QPage class="column items-center q-pa-md">
|
||||||
|
<div class="vn-card-list">
|
||||||
|
<VnPaginate
|
||||||
|
:data-key="urlPath"
|
||||||
|
ref="paginateRef"
|
||||||
|
:filter="filter"
|
||||||
|
:url="urlPath"
|
||||||
|
order="created DESC"
|
||||||
|
auto-load
|
||||||
|
>
|
||||||
|
<template #body="{ rows }">
|
||||||
|
<CardList
|
||||||
|
:key="row.id"
|
||||||
|
:title="row.user?.username"
|
||||||
|
@click="navigate(row.userId)"
|
||||||
|
v-for="row of rows"
|
||||||
|
>
|
||||||
|
<template #list-items>
|
||||||
|
<div style="flex-direction: column; width: 100%">
|
||||||
|
<VnLv
|
||||||
|
:label="t('connections.username')"
|
||||||
|
:value="row.user?.username"
|
||||||
|
>
|
||||||
|
</VnLv>
|
||||||
|
<VnLv
|
||||||
|
:label="t('connections.created')"
|
||||||
|
:value="toDateTimeFormat(row.created)"
|
||||||
|
>
|
||||||
|
</VnLv>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<QBtn
|
||||||
|
class="q-mt-xs"
|
||||||
|
:label="t('connections.killSession')"
|
||||||
|
@click.stop="
|
||||||
|
openConfirmationModal(
|
||||||
|
t('Session will be killed'),
|
||||||
|
t('Are you sure you want to continue?'),
|
||||||
|
() => killSession(row.id)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
outline
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</CardList>
|
||||||
|
</template>
|
||||||
|
</VnPaginate>
|
||||||
|
</div>
|
||||||
|
<QPageSticky position="bottom-right" :offset="[18, 18]">
|
||||||
|
<QBtn fab icon="refresh" color="primary" @click="refresh()">
|
||||||
|
<QTooltip>{{ t('connections.refresh') }}</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
</QPageSticky>
|
||||||
|
</QPage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
Session killed: Sesión matada
|
||||||
|
Session will be killed: Se va a matar la sesión
|
||||||
|
Are you sure you want to continue?: ¿Seguro que quieres continuar?
|
||||||
|
</i18n>
|
|
@ -0,0 +1,81 @@
|
||||||
|
<script setup>
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import FormModelPopup from 'components/FormModelPopup.vue';
|
||||||
|
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||||
|
import FetchData from 'components/FetchData.vue';
|
||||||
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const newAccountForm = reactive({
|
||||||
|
active: true,
|
||||||
|
});
|
||||||
|
const rolesOptions = ref([]);
|
||||||
|
|
||||||
|
const redirectToAccountBasicData = (_, { id }) => {
|
||||||
|
router.push({ name: 'AccountBasicData', params: { id } });
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<FetchData
|
||||||
|
url="VnRoles"
|
||||||
|
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
|
||||||
|
@on-fetch="(data) => (rolesOptions = data)"
|
||||||
|
auto-load
|
||||||
|
/>
|
||||||
|
<FormModelPopup
|
||||||
|
:title="t('account.card.newUser')"
|
||||||
|
url-create="VnUsers"
|
||||||
|
model="users"
|
||||||
|
:form-initial-data="newAccountForm"
|
||||||
|
@on-data-saved="redirectToAccountBasicData"
|
||||||
|
>
|
||||||
|
<template #form-inputs="{ data, validate }">
|
||||||
|
<div class="column q-gutter-sm">
|
||||||
|
<VnInput
|
||||||
|
v-model="data.name"
|
||||||
|
:label="t('account.create.name')"
|
||||||
|
:rules="validate('VnUser.name')"
|
||||||
|
/>
|
||||||
|
<VnInput
|
||||||
|
v-model="data.nickname"
|
||||||
|
:label="t('account.create.nickname')"
|
||||||
|
:rules="validate('VnUser.nickname')"
|
||||||
|
/>
|
||||||
|
<VnInput
|
||||||
|
v-model="data.email"
|
||||||
|
:label="t('account.create.email')"
|
||||||
|
type="email"
|
||||||
|
:rules="validate('VnUser.email')"
|
||||||
|
/>
|
||||||
|
<VnSelect
|
||||||
|
:label="t('account.create.role')"
|
||||||
|
v-model="data.roleFk"
|
||||||
|
:options="rolesOptions"
|
||||||
|
option-value="id"
|
||||||
|
option-label="name"
|
||||||
|
map-options
|
||||||
|
hide-selected
|
||||||
|
:rules="validate('VnUser.roleFk')"
|
||||||
|
/>
|
||||||
|
<VnInput
|
||||||
|
v-model="data.password"
|
||||||
|
:label="t('account.create.password')"
|
||||||
|
type="password"
|
||||||
|
:rules="validate('VnUser.password')"
|
||||||
|
/>
|
||||||
|
<QCheckbox
|
||||||
|
:label="t('account.create.active')"
|
||||||
|
v-model="data.active"
|
||||||
|
:toggle-indeterminate="false"
|
||||||
|
:rules="validate('VnUser.active')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</FormModelPopup>
|
||||||
|
</template>
|
|
@ -0,0 +1,87 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import FetchData from 'components/FetchData.vue';
|
||||||
|
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||||
|
import VnSelect from 'components/common/VnSelect.vue';
|
||||||
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const props = defineProps({
|
||||||
|
dataKey: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
exprBuilder: {
|
||||||
|
type: Function,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const rolesOptions = ref([]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<FetchData
|
||||||
|
url="VnRoles"
|
||||||
|
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
|
||||||
|
@on-fetch="(data) => (rolesOptions = data)"
|
||||||
|
auto-load
|
||||||
|
/>
|
||||||
|
<VnFilterPanel
|
||||||
|
:data-key="props.dataKey"
|
||||||
|
:search-button="true"
|
||||||
|
:hidden-tags="['search']"
|
||||||
|
:redirect="false"
|
||||||
|
>
|
||||||
|
<template #tags="{ tag, formatFn }">
|
||||||
|
<div class="q-gutter-x-xs">
|
||||||
|
<strong>{{ t(`account.card.${tag.label}`) }}: </strong>
|
||||||
|
<span>{{ formatFn(tag.value) }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #body="{ params, searchFn }">
|
||||||
|
<QItem class="q-my-sm">
|
||||||
|
<QItemSection>
|
||||||
|
<VnInput
|
||||||
|
:label="t('account.card.name')"
|
||||||
|
v-model="params.name"
|
||||||
|
lazy-rules
|
||||||
|
is-outlined
|
||||||
|
/>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem class="q-my-sm">
|
||||||
|
<QItemSection>
|
||||||
|
<VnInput
|
||||||
|
:label="t('account.card.alias')"
|
||||||
|
v-model="params.nickname"
|
||||||
|
lazy-rules
|
||||||
|
is-outlined
|
||||||
|
/>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem class="q-mb-sm">
|
||||||
|
<QItemSection>
|
||||||
|
<VnSelect
|
||||||
|
:label="t('account.card.role')"
|
||||||
|
v-model="params.roleFk"
|
||||||
|
@update:model-value="searchFn()"
|
||||||
|
:options="rolesOptions"
|
||||||
|
option-value="id"
|
||||||
|
option-label="name"
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
use-input
|
||||||
|
hide-selected
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
rounded
|
||||||
|
:input-debounce="0"
|
||||||
|
/>
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</template>
|
||||||
|
</VnFilterPanel>
|
||||||
|
</template>
|
|
@ -0,0 +1,171 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, computed } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import FormModel from 'components/FormModel.vue';
|
||||||
|
import VnRow from 'components/ui/VnRow.vue';
|
||||||
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||||
|
|
||||||
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { notify } = useNotify();
|
||||||
|
|
||||||
|
const arrayData = useArrayData('AccountLdap');
|
||||||
|
|
||||||
|
const URL_UPDATE = `LdapConfigs/${1}`;
|
||||||
|
const URL_CREATE = `LdapConfigs`;
|
||||||
|
const DEFAULT_DATA = {
|
||||||
|
id: 1,
|
||||||
|
hasData: false,
|
||||||
|
groupDn: null,
|
||||||
|
password: null,
|
||||||
|
rdn: null,
|
||||||
|
server: null,
|
||||||
|
userDn: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialData = ref({
|
||||||
|
...DEFAULT_DATA,
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasData = computed({
|
||||||
|
get: () => initialData.value.hasData,
|
||||||
|
set: (val) => {
|
||||||
|
initialData.value.hasData = val;
|
||||||
|
if (!val) formCustomFn.value = deleteMailForward;
|
||||||
|
else formCustomFn.value = null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const initialDataLoaded = ref(false);
|
||||||
|
const formUrlCreate = ref(null);
|
||||||
|
const formUrlUpdate = ref(null);
|
||||||
|
const formCustomFn = ref(null);
|
||||||
|
|
||||||
|
const onTestConection = async () => {
|
||||||
|
try {
|
||||||
|
await axios.get(`LdapConfigs/test`);
|
||||||
|
notify(t('LDAP connection established!'), 'positive');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error testing connection', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInitialLdapConfig = async () => {
|
||||||
|
try {
|
||||||
|
initialDataLoaded.value = false;
|
||||||
|
const { data } = await axios.get(URL_UPDATE);
|
||||||
|
initialData.value = data;
|
||||||
|
hasData.value = true;
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
hasData.value = false;
|
||||||
|
arrayData.destroy();
|
||||||
|
console.error('Error fetching initial LDAP config', error);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
// Si asignamos un valor a urlUpdate, debemos asignar null a urlCreate y viceversa, ya puede causar problemas en formModel
|
||||||
|
if (hasData.value) {
|
||||||
|
formUrlUpdate.value = URL_UPDATE;
|
||||||
|
formUrlCreate.value = null;
|
||||||
|
} else {
|
||||||
|
formUrlUpdate.value = null;
|
||||||
|
formUrlCreate.value = URL_CREATE;
|
||||||
|
}
|
||||||
|
initialDataLoaded.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteMailForward = async () => {
|
||||||
|
try {
|
||||||
|
await axios.delete(URL_UPDATE);
|
||||||
|
initialData.value = { ...DEFAULT_DATA };
|
||||||
|
hasData.value = false;
|
||||||
|
notify(t('globals.dataSaved'), 'positive');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error deleting mail forward', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => await getInitialLdapConfig());
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<QPage>
|
||||||
|
<VnSubToolbar />
|
||||||
|
<FormModel
|
||||||
|
:key="initialDataLoaded"
|
||||||
|
model="AccountLdap"
|
||||||
|
:form-initial-data="initialData"
|
||||||
|
:url-create="formUrlCreate"
|
||||||
|
:url-update="formUrlUpdate"
|
||||||
|
:save-fn="formCustomFn"
|
||||||
|
auto-load
|
||||||
|
@on-data-saved="getInitialLdapConfig()"
|
||||||
|
>
|
||||||
|
<template #moreActions>
|
||||||
|
<QBtn
|
||||||
|
class="q-ml-none"
|
||||||
|
color="primary"
|
||||||
|
:label="t('ldap.testConnection')"
|
||||||
|
@click="onTestConection()"
|
||||||
|
>
|
||||||
|
<QTooltip>
|
||||||
|
{{ t('ldap.testConnection') }}
|
||||||
|
</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
</template>
|
||||||
|
<template #form="{ data, validate }">
|
||||||
|
<VnRow class="row q-gutter-md">
|
||||||
|
<div class="col">
|
||||||
|
<QCheckbox
|
||||||
|
:label="t('ldap.enableSync')"
|
||||||
|
v-model="data.hasData"
|
||||||
|
@update:model-value="($event) => (hasData = $event)"
|
||||||
|
:toggle-indeterminate="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</VnRow>
|
||||||
|
<template v-if="hasData">
|
||||||
|
<VnInput
|
||||||
|
:label="t('ldap.server')"
|
||||||
|
clearable
|
||||||
|
v-model="data.server"
|
||||||
|
:required="true"
|
||||||
|
:rules="validate('LdapConfig.server')"
|
||||||
|
/>
|
||||||
|
<VnInput
|
||||||
|
:label="t('ldap.rdn')"
|
||||||
|
clearable
|
||||||
|
v-model="data.rdn"
|
||||||
|
:required="true"
|
||||||
|
:rules="validate('LdapConfig.rdn')"
|
||||||
|
/>
|
||||||
|
<VnInput
|
||||||
|
:label="t('ldap.password')"
|
||||||
|
clearable
|
||||||
|
type="password"
|
||||||
|
v-model="data.password"
|
||||||
|
:required="true"
|
||||||
|
:rules="validate('LdapConfig.password')"
|
||||||
|
/>
|
||||||
|
<VnInput :label="t('ldap.userDN')" clearable v-model="data.userDn" />
|
||||||
|
<VnInput
|
||||||
|
:label="t('ldap.groupDN')"
|
||||||
|
clearable
|
||||||
|
v-model="data.groupDn"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</FormModel>
|
||||||
|
</QPage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
LDAP connection established!: ¡Conexión con LDAP establecida!
|
||||||
|
</i18n>
|
|
@ -1 +1,144 @@
|
||||||
<template>Account list</template>
|
<script setup>
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||||
|
import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
||||||
|
import VnLv from 'src/components/ui/VnLv.vue';
|
||||||
|
import CardList from 'src/components/ui/CardList.vue';
|
||||||
|
import AccountSummary from './Card/AccountSummary.vue';
|
||||||
|
import AccountFilter from './AccountFilter.vue';
|
||||||
|
import AccountCreate from './AccountCreate.vue';
|
||||||
|
|
||||||
|
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||||
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
|
import { useRole } from 'src/composables/useRole';
|
||||||
|
import { QDialog } from 'quasar';
|
||||||
|
|
||||||
|
const stateStore = useStateStore();
|
||||||
|
const router = useRouter();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { viewSummary } = useSummaryDialog();
|
||||||
|
const accountCreateDialogRef = ref(null);
|
||||||
|
const showNewUserBtn = computed(() => useRole().hasAny(['itManagement']));
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
fields: ['id', 'nickname', 'name', 'role'],
|
||||||
|
include: { relation: 'role', scope: { fields: ['id', 'name'] } },
|
||||||
|
};
|
||||||
|
|
||||||
|
const exprBuilder = (param, value) => {
|
||||||
|
switch (param) {
|
||||||
|
case 'search':
|
||||||
|
return /^\d+$/.test(value)
|
||||||
|
? { id: value }
|
||||||
|
: {
|
||||||
|
or: [
|
||||||
|
{ name: { like: `%${value}%` } },
|
||||||
|
{ nickname: { like: `%${value}%` } },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case 'name':
|
||||||
|
case 'nickname':
|
||||||
|
return { [param]: { like: `%${value}%` } };
|
||||||
|
case 'roleFk':
|
||||||
|
return { [param]: value };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getApiUrl = () => new URL(window.location).origin;
|
||||||
|
|
||||||
|
const navigate = (event, id) => {
|
||||||
|
if (event.ctrlKey || event.metaKey)
|
||||||
|
return window.open(`${getApiUrl()}/#/account/${id}/summary`);
|
||||||
|
router.push({ path: `/account/${id}` });
|
||||||
|
};
|
||||||
|
|
||||||
|
const openCreateModal = () => accountCreateDialogRef.value.show();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<template v-if="stateStore.isHeaderMounted()">
|
||||||
|
<Teleport to="#searchbar">
|
||||||
|
<VnSearchbar
|
||||||
|
data-key="AccountList"
|
||||||
|
url="VnUsers/preview"
|
||||||
|
:expr-builder="exprBuilder"
|
||||||
|
:label="t('account.search')"
|
||||||
|
:info="t('account.searchInfo')"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
|
<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="256" show-if-above>
|
||||||
|
<QScrollArea class="fit text-grey-8">
|
||||||
|
<AccountFilter data-key="AccountList" :expr-builder="exprBuilder" />
|
||||||
|
</QScrollArea>
|
||||||
|
</QDrawer>
|
||||||
|
<QPage class="column items-center q-pa-md">
|
||||||
|
<div class="vn-card-list">
|
||||||
|
<VnPaginate
|
||||||
|
:filter="filter"
|
||||||
|
data-key="AccountList"
|
||||||
|
url="VnUsers/preview"
|
||||||
|
auto-load
|
||||||
|
>
|
||||||
|
<template #body="{ rows }">
|
||||||
|
<CardList
|
||||||
|
v-for="row of rows"
|
||||||
|
:id="row.id"
|
||||||
|
:key="row.id"
|
||||||
|
:title="row.nickname"
|
||||||
|
@click="navigate($event, row.id)"
|
||||||
|
>
|
||||||
|
<template #list-items>
|
||||||
|
<VnLv :label="t('account.card.name')" :value="row.nickname">
|
||||||
|
</VnLv>
|
||||||
|
<VnLv
|
||||||
|
:label="t('account.card.nickname')"
|
||||||
|
:value="row.username"
|
||||||
|
>
|
||||||
|
</VnLv>
|
||||||
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<QBtn
|
||||||
|
:label="t('components.smartCard.openSummary')"
|
||||||
|
@click.stop="viewSummary(row.id, AccountSummary)"
|
||||||
|
color="primary"
|
||||||
|
style="margin-top: 15px"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</CardList>
|
||||||
|
</template>
|
||||||
|
</VnPaginate>
|
||||||
|
</div>
|
||||||
|
<QDialog
|
||||||
|
ref="accountCreateDialogRef"
|
||||||
|
transition-hide="scale"
|
||||||
|
transition-show="scale"
|
||||||
|
>
|
||||||
|
<AccountCreate />
|
||||||
|
</QDialog>
|
||||||
|
<QPageSticky :offset="[20, 20]" v-if="showNewUserBtn">
|
||||||
|
<QBtn @click="openCreateModal" color="primary" fab icon="add" />
|
||||||
|
<QTooltip class="text-no-wrap">
|
||||||
|
{{ t('account.card.newUser') }}
|
||||||
|
</QTooltip>
|
||||||
|
</QPageSticky>
|
||||||
|
</QPage>
|
||||||
|
</template>
|
||||||
|
|
|
@ -0,0 +1,187 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, computed } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import FormModel from 'components/FormModel.vue';
|
||||||
|
import VnRow from 'components/ui/VnRow.vue';
|
||||||
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||||
|
|
||||||
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { notify } = useNotify();
|
||||||
|
|
||||||
|
const arrayData = useArrayData('AccountSamba');
|
||||||
|
|
||||||
|
const formModel = ref(null);
|
||||||
|
|
||||||
|
const URL_UPDATE = `SambaConfigs/${1}`;
|
||||||
|
const URL_CREATE = `SambaConfigs`;
|
||||||
|
|
||||||
|
const DEFAULT_DATA = {
|
||||||
|
id: 1,
|
||||||
|
hasData: false,
|
||||||
|
adDomain: null,
|
||||||
|
adController: null,
|
||||||
|
adUser: null,
|
||||||
|
adPassword: null,
|
||||||
|
userDn: null,
|
||||||
|
verifyCert: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialData = ref({
|
||||||
|
...DEFAULT_DATA,
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasData = computed({
|
||||||
|
get: () => initialData.value.hasData,
|
||||||
|
set: (val) => {
|
||||||
|
initialData.value.hasData = val;
|
||||||
|
if (!val) formCustomFn.value = deleteMailForward;
|
||||||
|
else formCustomFn.value = null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const initialDataLoaded = ref(false);
|
||||||
|
const formUrlCreate = ref(null);
|
||||||
|
const formUrlUpdate = ref(null);
|
||||||
|
const formCustomFn = ref(null);
|
||||||
|
|
||||||
|
const onTestConection = async () => {
|
||||||
|
try {
|
||||||
|
await axios.get(`SambaConfigs/test`);
|
||||||
|
notify(t('Samba connection established!'), 'positive');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error testing connection', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInitialSambaConfig = async () => {
|
||||||
|
try {
|
||||||
|
initialDataLoaded.value = false;
|
||||||
|
const { data } = await axios.get(URL_UPDATE);
|
||||||
|
initialData.value = data;
|
||||||
|
hasData.value = true;
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
hasData.value = false;
|
||||||
|
arrayData.destroy();
|
||||||
|
console.error('Error fetching initial Samba config', error);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (hasData.value) {
|
||||||
|
formUrlUpdate.value = URL_UPDATE;
|
||||||
|
formUrlCreate.value = null;
|
||||||
|
} else {
|
||||||
|
formUrlUpdate.value = null;
|
||||||
|
formUrlCreate.value = URL_CREATE;
|
||||||
|
}
|
||||||
|
initialDataLoaded.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteMailForward = async () => {
|
||||||
|
try {
|
||||||
|
await axios.delete(URL_UPDATE);
|
||||||
|
initialData.value = { ...DEFAULT_DATA };
|
||||||
|
hasData.value = false;
|
||||||
|
notify(t('globals.dataSaved'), 'positive');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error deleting mail forward', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => await getInitialSambaConfig());
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<QPage>
|
||||||
|
<VnSubToolbar />
|
||||||
|
<FormModel
|
||||||
|
ref="formModel"
|
||||||
|
:key="initialDataLoaded"
|
||||||
|
model="AccountSamba"
|
||||||
|
:form-initial-data="initialData"
|
||||||
|
:url-create="formUrlCreate"
|
||||||
|
:url-update="formUrlUpdate"
|
||||||
|
:save-fn="formCustomFn"
|
||||||
|
auto-load
|
||||||
|
@on-data-saved="getInitialSambaConfig()"
|
||||||
|
>
|
||||||
|
<template #moreActions>
|
||||||
|
<QBtn
|
||||||
|
class="q-ml-none"
|
||||||
|
color="primary"
|
||||||
|
:label="t('samba.testConnection')"
|
||||||
|
:disable="formModel.hasChanges"
|
||||||
|
@click="onTestConection()"
|
||||||
|
>
|
||||||
|
<QTooltip>
|
||||||
|
{{ t('samba.testConnection') }}
|
||||||
|
</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
</template>
|
||||||
|
<template #form="{ data, validate }">
|
||||||
|
<VnRow class="row q-gutter-md">
|
||||||
|
<div class="col">
|
||||||
|
<QCheckbox
|
||||||
|
:label="t('samba.enableSync')"
|
||||||
|
v-model="data.hasData"
|
||||||
|
@update:model-value="($event) => (hasData = $event)"
|
||||||
|
:toggle-indeterminate="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</VnRow>
|
||||||
|
<template v-if="hasData">
|
||||||
|
<VnInput
|
||||||
|
:label="t('samba.domainAD')"
|
||||||
|
clearable
|
||||||
|
v-model="data.adDomain"
|
||||||
|
:required="true"
|
||||||
|
:rules="validate('SambaConfigs.server')"
|
||||||
|
/>
|
||||||
|
<VnInput
|
||||||
|
:label="t('samba.domainController')"
|
||||||
|
clearable
|
||||||
|
v-model="data.adController"
|
||||||
|
:required="true"
|
||||||
|
:rules="validate('SambaConfigs.adController')"
|
||||||
|
/>
|
||||||
|
<VnInput
|
||||||
|
:label="t('samba.userAD')"
|
||||||
|
clearable
|
||||||
|
v-model="data.adUser"
|
||||||
|
:rules="validate('SambaConfigs.adUser')"
|
||||||
|
/>
|
||||||
|
<VnInput
|
||||||
|
:label="t('samba.passwordAD')"
|
||||||
|
clearable
|
||||||
|
type="password"
|
||||||
|
v-model="data.adPassword"
|
||||||
|
/>
|
||||||
|
<VnInput
|
||||||
|
:label="t('samba.domainPart')"
|
||||||
|
clearable
|
||||||
|
v-model="data.userDn"
|
||||||
|
:required="true"
|
||||||
|
:rules="validate('SambaConfigs.userDn')"
|
||||||
|
/>
|
||||||
|
<QCheckbox
|
||||||
|
:label="t('samba.verifyCertificate')"
|
||||||
|
v-model="data.verifyCert"
|
||||||
|
:rules="validate('SambaConfigs.groupDn')"
|
||||||
|
:toggle-indeterminate="false"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</FormModel>
|
||||||
|
</QPage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
Samba connection established!: ¡Conexión con LDAP establecida!
|
||||||
|
</i18n>
|
|
@ -1,11 +1,9 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import FormModel from 'components/FormModel.vue';
|
import FormModel from 'components/FormModel.vue';
|
||||||
import VnInput from 'src/components/common/VnInput.vue';
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
<script setup>
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||||
|
import FormModel from 'components/FormModel.vue';
|
||||||
|
import VnRow from 'components/ui/VnRow.vue';
|
||||||
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const formModelRef = ref(null);
|
||||||
|
|
||||||
|
const accountFilter = {
|
||||||
|
where: { id: route.params.id },
|
||||||
|
fields: ['id', 'email', 'nickname', 'name', 'accountStateFk', 'packages', 'pickup'],
|
||||||
|
include: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.params.id,
|
||||||
|
() => formModelRef.value.reset()
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<FormModel
|
||||||
|
ref="formModelRef"
|
||||||
|
:url="`VnUsers/preview`"
|
||||||
|
:url-update="`VnUsers/${route.params.id}/update-user`"
|
||||||
|
:filter="accountFilter"
|
||||||
|
model="Accounts"
|
||||||
|
auto-load
|
||||||
|
@on-data-saved="formModelRef.fetch()"
|
||||||
|
>
|
||||||
|
<template #form="{ data }">
|
||||||
|
<div class="q-gutter-y-sm">
|
||||||
|
<VnInput v-model="data.name" :label="t('account.card.nickname')" />
|
||||||
|
<VnInput v-model="data.nickname" :label="t('account.card.alias')" />
|
||||||
|
<VnInput v-model="data.email" :label="t('account.card.email')" />
|
||||||
|
<VnSelect
|
||||||
|
v-model="data.lang"
|
||||||
|
:options="['es', 'en']"
|
||||||
|
:label="t('account.card.lang')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</FormModel>
|
||||||
|
</template>
|
|
@ -0,0 +1,34 @@
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
import VnCard from 'components/common/VnCard.vue';
|
||||||
|
import AccountDescriptor from './AccountDescriptor.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const routeName = computed(() => route.name);
|
||||||
|
const customRouteRedirectName = computed(() => routeName.value);
|
||||||
|
const searchBarDataKeys = {
|
||||||
|
AccountSummary: 'AccountSummary',
|
||||||
|
AccountInheritedRoles: 'AccountInheritedRoles',
|
||||||
|
AccountMailForwarding: 'AccountMailForwarding',
|
||||||
|
AccountMailAlias: 'AccountMailAlias',
|
||||||
|
AccountPrivileges: 'AccountPrivileges',
|
||||||
|
AccountLog: 'AccountLog',
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VnCard
|
||||||
|
data-key="Account"
|
||||||
|
:descriptor="AccountDescriptor"
|
||||||
|
:search-data-key="searchBarDataKeys[routeName]"
|
||||||
|
:search-custom-route-redirect="customRouteRedirectName"
|
||||||
|
:search-redirect="!!customRouteRedirectName"
|
||||||
|
:searchbar-label="t('account.search')"
|
||||||
|
:searchbar-info="t('account.searchInfo')"
|
||||||
|
/>
|
||||||
|
</template>
|
|
@ -0,0 +1,130 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import CardDescriptor from 'components/ui/CardDescriptor.vue';
|
||||||
|
import VnLv from 'src/components/ui/VnLv.vue';
|
||||||
|
import useCardDescription from 'src/composables/useCardDescription';
|
||||||
|
import AccountDescriptorMenu from './AccountDescriptorMenu.vue';
|
||||||
|
import FetchData from 'src/components/FetchData.vue';
|
||||||
|
import VnImg from 'src/components/ui/VnImg.vue';
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const entityId = computed(() => {
|
||||||
|
return $props.id || route.params.id;
|
||||||
|
});
|
||||||
|
const data = ref(useCardDescription());
|
||||||
|
const setData = (entity) => (data.value = useCardDescription(entity.nickname, entity.id));
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
where: { id: entityId },
|
||||||
|
fields: ['id', 'nickname', 'name', 'role'],
|
||||||
|
include: { relation: 'role', scope: { fields: ['id', 'name'] } },
|
||||||
|
};
|
||||||
|
const hasAccount = ref(false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<FetchData
|
||||||
|
:url="`Accounts/${entityId}/exists`"
|
||||||
|
auto-load
|
||||||
|
@on-fetch="(data) => (hasAccount = data.exists)"
|
||||||
|
/>
|
||||||
|
<CardDescriptor
|
||||||
|
ref="descriptor"
|
||||||
|
:url="`VnUsers/preview`"
|
||||||
|
:filter="filter"
|
||||||
|
module="Account"
|
||||||
|
@on-fetch="setData"
|
||||||
|
data-key="AccountId"
|
||||||
|
:title="data.title"
|
||||||
|
:subtitle="data.subtitle"
|
||||||
|
>
|
||||||
|
<template #header-extra-action>
|
||||||
|
<QBtn
|
||||||
|
round
|
||||||
|
flat
|
||||||
|
size="md"
|
||||||
|
color="white"
|
||||||
|
icon="face"
|
||||||
|
:to="{ name: 'AccountList' }"
|
||||||
|
>
|
||||||
|
<QTooltip>
|
||||||
|
{{ t('Go to module index') }}
|
||||||
|
</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
</template>
|
||||||
|
<template #menu>
|
||||||
|
<AccountDescriptorMenu :has-account="hasAccount" />
|
||||||
|
</template>
|
||||||
|
<template #before>
|
||||||
|
<!-- falla id :id="entityId.value" collection="user" size="160x160" -->
|
||||||
|
<VnImg :id="entityId" collection="user" size="160x160" class="photo">
|
||||||
|
<template #error>
|
||||||
|
<div
|
||||||
|
class="absolute-full picture text-center q-pa-md flex flex-center"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div class="text-grey-5" style="opacity: 0.4; font-size: 5vh">
|
||||||
|
<QIcon name="vn:claims" />
|
||||||
|
</div>
|
||||||
|
<div class="text-grey-5" style="opacity: 0.4">
|
||||||
|
{{ t('account.imageNotFound') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</VnImg>
|
||||||
|
</template>
|
||||||
|
<template #body="{ entity }">
|
||||||
|
<VnLv :label="t('account.card.nickname')" :value="entity.nickname" />
|
||||||
|
<VnLv :label="t('account.card.role')" :value="entity.role.name" />
|
||||||
|
</template>
|
||||||
|
<template #actions="{ entity }">
|
||||||
|
<QCardActions class="q-gutter-x-md">
|
||||||
|
<QIcon
|
||||||
|
v-if="!entity.active"
|
||||||
|
color="primary"
|
||||||
|
name="vn:disabled"
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
size="sm"
|
||||||
|
class="fill-icon"
|
||||||
|
>
|
||||||
|
<QTooltip>{{ t('account.card.deactivated') }}</QTooltip>
|
||||||
|
</QIcon>
|
||||||
|
<QIcon
|
||||||
|
color="primary"
|
||||||
|
name="contact_mail"
|
||||||
|
v-if="entity.hasAccount"
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
size="sm"
|
||||||
|
class="fill-icon"
|
||||||
|
>
|
||||||
|
<QTooltip>{{ t('account.card.enabled') }}</QTooltip>
|
||||||
|
</QIcon>
|
||||||
|
</QCardActions>
|
||||||
|
</template>
|
||||||
|
</CardDescriptor>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
.q-item__label {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<i18n>
|
||||||
|
en:
|
||||||
|
accountRate: Claming rate
|
||||||
|
es:
|
||||||
|
accountRate: Ratio de reclamación
|
||||||
|
</i18n>
|
|
@ -0,0 +1,187 @@
|
||||||
|
<script setup>
|
||||||
|
import axios from 'axios';
|
||||||
|
import { computed, ref, toRefs } from 'vue';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useVnConfirm } from 'composables/useVnConfirm';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
|
import CustomerChangePassword from 'src/pages/Customer/components/CustomerChangePassword.vue';
|
||||||
|
import VnConfirm from 'src/components/ui/VnConfirm.vue';
|
||||||
|
|
||||||
|
const quasar = useQuasar();
|
||||||
|
const $props = defineProps({
|
||||||
|
hasAccount: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { hasAccount } = toRefs($props);
|
||||||
|
const { openConfirmationModal } = useVnConfirm();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const account = computed(() => useArrayData('AccountId').store.data[0]);
|
||||||
|
account.value.hasAccount = hasAccount.value;
|
||||||
|
const entityId = computed(() => +route.params.id);
|
||||||
|
|
||||||
|
async function updateStatusAccount(active) {
|
||||||
|
if (active) {
|
||||||
|
await axios.post(`Accounts`, { id: entityId.value });
|
||||||
|
} else {
|
||||||
|
await axios.delete(`Accounts/${entityId.value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
account.value.hasAccount = active;
|
||||||
|
const status = active ? 'enable' : 'disable';
|
||||||
|
quasar.notify({
|
||||||
|
message: t(`account.card.${status}Account.success`),
|
||||||
|
type: 'positive',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function updateStatusUser(active) {
|
||||||
|
await axios.patch(`VnUsers/${entityId.value}`, { active });
|
||||||
|
account.value.active = active;
|
||||||
|
const status = active ? 'activate' : 'deactivate';
|
||||||
|
quasar.notify({
|
||||||
|
message: t(`account.card.actions.${status}User.success`),
|
||||||
|
type: 'positive',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function setPassword() {
|
||||||
|
quasar.dialog({
|
||||||
|
component: CustomerChangePassword,
|
||||||
|
componentProps: {
|
||||||
|
id: entityId.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const showSyncDialog = ref(false);
|
||||||
|
const syncPassword = ref(null);
|
||||||
|
const shouldSyncPassword = ref(false);
|
||||||
|
async function sync() {
|
||||||
|
const params = { force: true };
|
||||||
|
if (shouldSyncPassword.value) params.password = syncPassword.value;
|
||||||
|
await axios.patch(`Accounts/${account.value.name}/sync`, {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
quasar.notify({
|
||||||
|
message: t('account.card.actions.sync.success'),
|
||||||
|
type: 'positive',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<VnConfirm
|
||||||
|
v-model="showSyncDialog"
|
||||||
|
:message="t('account.card.actions.sync.message')"
|
||||||
|
:title="t('account.card.actions.sync.title')"
|
||||||
|
:promise="sync"
|
||||||
|
>
|
||||||
|
<template #customHTML>
|
||||||
|
{{ shouldSyncPassword }}
|
||||||
|
<QCheckbox
|
||||||
|
:label="t('account.card.actions.sync.checkbox')"
|
||||||
|
v-model="shouldSyncPassword"
|
||||||
|
class="full-width"
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
>
|
||||||
|
<QIcon style="padding-left: 10px" color="primary" name="info" size="sm">
|
||||||
|
<QTooltip>{{ t('account.card.actions.sync.tooltip') }}</QTooltip>
|
||||||
|
</QIcon></QCheckbox
|
||||||
|
>
|
||||||
|
<QInput
|
||||||
|
v-if="shouldSyncPassword"
|
||||||
|
:label="t('login.password')"
|
||||||
|
v-model="syncPassword"
|
||||||
|
class="full-width"
|
||||||
|
clearable
|
||||||
|
clear-icon="close"
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</VnConfirm>
|
||||||
|
<QItem v-ripple clickable @click="setPassword">
|
||||||
|
<QItemSection>{{ t('account.card.actions.setPassword') }}</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem
|
||||||
|
v-if="!account.hasAccount"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
@click="
|
||||||
|
openConfirmationModal(
|
||||||
|
t('account.card.actions.enableAccount.title'),
|
||||||
|
t('account.card.actions.enableAccount.subtitle'),
|
||||||
|
() => updateStatusAccount(true)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<QItemSection>{{ t('account.card.actions.enableAccount.name') }}</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem
|
||||||
|
v-if="account.hasAccount"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
@click="
|
||||||
|
openConfirmationModal(
|
||||||
|
t('account.card.actions.disableAccount.title'),
|
||||||
|
t('account.card.actions.disableAccount.subtitle'),
|
||||||
|
() => updateStatusAccount(false)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<QItemSection>{{ t('account.card.actions.disableAccount.name') }}</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
|
||||||
|
<QItem
|
||||||
|
v-if="!account.active"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
@click="
|
||||||
|
openConfirmationModal(
|
||||||
|
t('account.card.actions.activateUser.title'),
|
||||||
|
t('account.card.actions.activateUser.title'),
|
||||||
|
() => updateStatusUser(true)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<QItemSection>{{ t('account.card.actions.activateUser.name') }}</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem
|
||||||
|
v-if="account.active"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
@click="
|
||||||
|
openConfirmationModal(
|
||||||
|
t('account.card.actions.deactivateUser.title'),
|
||||||
|
t('account.card.actions.deactivateUser.title'),
|
||||||
|
() => updateStatusUser(false)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<QItemSection>{{ t('account.card.actions.deactivateUser.name') }}</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem v-ripple clickable @click="showSyncDialog = true">
|
||||||
|
<QItemSection>{{ t('account.card.actions.sync.name') }}</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
|
||||||
|
<QSeparator />
|
||||||
|
<QItem
|
||||||
|
@click="
|
||||||
|
openConfirmationModal(
|
||||||
|
t('account.card.actions.delete.title'),
|
||||||
|
t('account.card.actions.delete.subTitle'),
|
||||||
|
removeAccount
|
||||||
|
)
|
||||||
|
"
|
||||||
|
v-ripple
|
||||||
|
clickable
|
||||||
|
>
|
||||||
|
<QItemSection avatar>
|
||||||
|
<QIcon name="delete" />
|
||||||
|
</QItemSection>
|
||||||
|
<QItemSection>{{ t('account.card.actions.delete.name') }}</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
</template>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<script setup>
|
||||||
|
import InheritedRoles from '../InheritedRoles.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<InheritedRoles data-key="AccountInheritedRoles" />
|
||||||
|
</template>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<script setup>
|
||||||
|
import VnLog from 'src/components/common/VnLog.vue';
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<VnLog model="User" />
|
||||||
|
</template>
|
|
@ -0,0 +1,187 @@
|
||||||
|
<script setup>
|
||||||
|
import { computed, ref, watch, onMounted, nextTick } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||||
|
import AccountMailAliasCreateForm from './AccountMailAliasCreateForm.vue';
|
||||||
|
|
||||||
|
import { useVnConfirm } from 'composables/useVnConfirm';
|
||||||
|
import { useArrayData } from 'composables/useArrayData';
|
||||||
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const route = useRoute();
|
||||||
|
const { openConfirmationModal } = useVnConfirm();
|
||||||
|
const { notify } = useNotify();
|
||||||
|
|
||||||
|
const paginateRef = ref(null);
|
||||||
|
const createMailAliasDialogRef = ref(null);
|
||||||
|
|
||||||
|
const arrayData = useArrayData('AccountMailAliases');
|
||||||
|
const store = arrayData.store;
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const hasAccount = ref(false);
|
||||||
|
const data = computed(() => {
|
||||||
|
const dataCopy = store.data;
|
||||||
|
return dataCopy.sort((a, b) => a.alias?.alias.localeCompare(b.alias?.alias));
|
||||||
|
});
|
||||||
|
|
||||||
|
const filter = computed(() => ({
|
||||||
|
where: { account: route.params.id },
|
||||||
|
include: {
|
||||||
|
relation: 'alias',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'alias', 'description'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const urlPath = 'MailAliasAccounts';
|
||||||
|
|
||||||
|
const columns = computed(() => [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'action',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const fetchAccountExistence = async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await axios.get(`Accounts/${route.params.id}/exists`);
|
||||||
|
return data.exists;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching account existence', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteMailAlias = async (row) => {
|
||||||
|
try {
|
||||||
|
await axios.delete(`${urlPath}/${row.id}`);
|
||||||
|
fetchMailAliases();
|
||||||
|
notify(t('Unsubscribed from alias!'), 'positive');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createMailAlias = async (mailAliasFormData) => {
|
||||||
|
try {
|
||||||
|
await axios.post(urlPath, mailAliasFormData);
|
||||||
|
notify(t('Subscribed to alias!'), 'positive');
|
||||||
|
fetchMailAliases();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchMailAliases = async () => {
|
||||||
|
await nextTick();
|
||||||
|
paginateRef.value.fetch();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAccountData = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
hasAccount.value = await fetchAccountExistence();
|
||||||
|
if (!hasAccount.value) {
|
||||||
|
loading.value = false;
|
||||||
|
store.data = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await fetchMailAliases();
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openCreateMailAliasForm = () => createMailAliasDialogRef.value.show();
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.params.id,
|
||||||
|
() => {
|
||||||
|
store.url = urlPath;
|
||||||
|
store.filter = filter.value;
|
||||||
|
getAccountData();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(async () => await getAccountData());
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<QPage class="column items-center q-pa-md">
|
||||||
|
<div class="full-width" style="max-width: 400px">
|
||||||
|
<QSpinner v-if="loading" color="primary" size="md" />
|
||||||
|
<VnPaginate
|
||||||
|
ref="paginateRef"
|
||||||
|
data-key="AccountMailAliases"
|
||||||
|
:filter="filter"
|
||||||
|
:url="urlPath"
|
||||||
|
auto-load
|
||||||
|
>
|
||||||
|
<template #body="{ rows }">
|
||||||
|
<QTable
|
||||||
|
v-if="hasAccount && !loading"
|
||||||
|
:rows="data"
|
||||||
|
:columns="columns"
|
||||||
|
hide-header
|
||||||
|
>
|
||||||
|
<template #body="{ row, rowIndex }">
|
||||||
|
<QTr>
|
||||||
|
<QTd>
|
||||||
|
<div class="column">
|
||||||
|
<span>{{ row.alias?.alias }}</span>
|
||||||
|
<span class="color-vn-label">{{
|
||||||
|
row.alias?.description
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</QTd>
|
||||||
|
<QTd style="width: 50px !important">
|
||||||
|
<QIcon
|
||||||
|
name="delete"
|
||||||
|
size="sm"
|
||||||
|
class="cursor-pointer"
|
||||||
|
color="primary"
|
||||||
|
@click.stop.prevent="
|
||||||
|
openConfirmationModal(
|
||||||
|
t('User will be removed from alias'),
|
||||||
|
t('¿Seguro que quieres continuar?'),
|
||||||
|
() => deleteMailAlias(row, rows, rowIndex)
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<QTooltip>
|
||||||
|
{{ t('globals.delete') }}
|
||||||
|
</QTooltip>
|
||||||
|
</QIcon>
|
||||||
|
</QTd>
|
||||||
|
</QTr>
|
||||||
|
</template>
|
||||||
|
</QTable>
|
||||||
|
</template>
|
||||||
|
</VnPaginate>
|
||||||
|
<h5 v-if="!hasAccount" class="text-center">
|
||||||
|
{{ t('account.mailForwarding.accountNotEnabled') }}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<QDialog ref="createMailAliasDialogRef">
|
||||||
|
<AccountMailAliasCreateForm @on-submit-create-alias="createMailAlias" />
|
||||||
|
</QDialog>
|
||||||
|
<QPageSticky position="bottom-right" :offset="[18, 18]">
|
||||||
|
<QBtn fab icon="add" color="primary" @click="openCreateMailAliasForm()">
|
||||||
|
<QTooltip>{{ t('warehouses.add') }}</QTooltip>
|
||||||
|
</QBtn>
|
||||||
|
</QPageSticky>
|
||||||
|
</QPage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
Unsubscribed from alias!: ¡Desuscrito del alias!
|
||||||
|
Subscribed to alias!: ¡Suscrito al alias!
|
||||||
|
User will be removed from alias: El usuario será borrado del alias
|
||||||
|
Are you sure you want to continue?: ¿Seguro que quieres continuar?
|
||||||
|
</i18n>
|
|
@ -0,0 +1,51 @@
|
||||||
|
<script setup>
|
||||||
|
import { reactive, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||||
|
import FetchData from 'components/FetchData.vue';
|
||||||
|
import VnRow from 'components/ui/VnRow.vue';
|
||||||
|
import FormPopup from 'components/FormPopup.vue';
|
||||||
|
|
||||||
|
const emit = defineEmits(['onSubmitCreateAlias']);
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const aliasFormData = reactive({
|
||||||
|
mailAlias: null,
|
||||||
|
account: route.params.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const aliasOptions = ref([]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<FetchData
|
||||||
|
url="MailAliases"
|
||||||
|
:filter="{ fields: ['id', 'alias'], order: 'alias ASC' }"
|
||||||
|
auto-load
|
||||||
|
@on-fetch="(data) => (aliasOptions = data)"
|
||||||
|
/>
|
||||||
|
<FormPopup
|
||||||
|
model="ZoneWarehouse"
|
||||||
|
@on-submit="emit('onSubmitCreateAlias', aliasFormData)"
|
||||||
|
>
|
||||||
|
<template #form-inputs>
|
||||||
|
<VnRow class="row q-gutter-md q-mb-md">
|
||||||
|
<div class="col">
|
||||||
|
<VnSelect
|
||||||
|
:label="t('account.card.alias')"
|
||||||
|
v-model="aliasFormData.mailAlias"
|
||||||
|
:options="aliasOptions"
|
||||||
|
option-value="id"
|
||||||
|
option-label="alias"
|
||||||
|
hide-selected
|
||||||
|
:required="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</VnRow>
|
||||||
|
</template>
|
||||||
|
</FormPopup>
|
||||||
|
</template>
|
|
@ -0,0 +1,159 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, watch, computed } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
import VnRow from 'components/ui/VnRow.vue';
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const route = useRoute();
|
||||||
|
const stateStore = useStateStore();
|
||||||
|
const { notify } = useNotify();
|
||||||
|
|
||||||
|
const initialData = ref({});
|
||||||
|
const formData = ref({
|
||||||
|
forwardTo: null,
|
||||||
|
account: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasAccount = ref(false);
|
||||||
|
const hasData = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
|
const hasDataChanged = computed(
|
||||||
|
() =>
|
||||||
|
formData.value.forwardTo !== initialData.value.forwardTo ||
|
||||||
|
initialData.value.hasData !== hasData.value
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchAccountExistence = async () => {
|
||||||
|
try {
|
||||||
|
const { data } = await axios.get(`Accounts/${route.params.id}/exists`);
|
||||||
|
return data.exists;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching account existence', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchMailForwards = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`MailForwards/${route.params.id}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching mail forwards', err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteMailForward = async () => {
|
||||||
|
try {
|
||||||
|
await axios.delete(`MailForwards/${route.params.id}`);
|
||||||
|
formData.value.forwardTo = null;
|
||||||
|
initialData.value.forwardTo = null;
|
||||||
|
initialData.value.hasData = hasData.value;
|
||||||
|
notify(t('globals.dataSaved'), 'positive');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error deleting mail forward', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateMailForward = async () => {
|
||||||
|
try {
|
||||||
|
await axios.patch('MailForwards', formData.value);
|
||||||
|
initialData.value = { ...formData.value };
|
||||||
|
initialData.value.hasData = hasData.value;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error creating mail forward', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
if (hasData.value) await updateMailForward();
|
||||||
|
else await deleteMailForward();
|
||||||
|
};
|
||||||
|
|
||||||
|
const setInitialData = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
initialData.value.account = route.params.id;
|
||||||
|
formData.value.account = route.params.id;
|
||||||
|
hasAccount.value = await fetchAccountExistence(route.params.id);
|
||||||
|
if (!hasAccount.value) {
|
||||||
|
loading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await fetchMailForwards(route.params.id);
|
||||||
|
const forwardTo = result ? result.forwardTo : null;
|
||||||
|
formData.value.forwardTo = forwardTo;
|
||||||
|
initialData.value.forwardTo = forwardTo;
|
||||||
|
|
||||||
|
initialData.value.hasData = hasAccount.value && !!forwardTo;
|
||||||
|
hasData.value = hasAccount.value && !!forwardTo;
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.params.id,
|
||||||
|
() => setInitialData()
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(async () => await setInitialData());
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<QSpinner v-if="loading" color="primary" size="md" />
|
||||||
|
<QForm
|
||||||
|
v-else-if="hasAccount"
|
||||||
|
@submit="onSubmit()"
|
||||||
|
class="full-width"
|
||||||
|
style="max-width: 800px"
|
||||||
|
>
|
||||||
|
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
|
||||||
|
<div>
|
||||||
|
<QBtnGroup push class="q-gutter-x-sm">
|
||||||
|
<slot name="moreActions" />
|
||||||
|
<QBtn
|
||||||
|
color="primary"
|
||||||
|
icon="restart_alt"
|
||||||
|
flat
|
||||||
|
@click="reset()"
|
||||||
|
:label="t('globals.reset')"
|
||||||
|
/>
|
||||||
|
<QBtn
|
||||||
|
color="primary"
|
||||||
|
icon="save"
|
||||||
|
@click="onSubmit()"
|
||||||
|
:disable="!hasDataChanged"
|
||||||
|
:label="t('globals.save')"
|
||||||
|
/>
|
||||||
|
</QBtnGroup>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
<QCard class="q-pa-lg">
|
||||||
|
<VnRow class="row q-mb-md">
|
||||||
|
<QCheckbox
|
||||||
|
v-model="hasData"
|
||||||
|
:label="t('account.mailForwarding.enableMailForwarding')"
|
||||||
|
:toggle-indeterminate="false"
|
||||||
|
/>
|
||||||
|
</VnRow>
|
||||||
|
<VnRow v-if="hasData" class="row q-gutter-md q-mb-md">
|
||||||
|
<VnInput
|
||||||
|
v-model="formData.forwardTo"
|
||||||
|
:label="t('account.mailForwarding.forwardingMail')"
|
||||||
|
:info="t('account.mailForwarding.mailInputInfo')"
|
||||||
|
>
|
||||||
|
</VnInput>
|
||||||
|
</VnRow>
|
||||||
|
</QCard>
|
||||||
|
</QForm>
|
||||||
|
<h5 v-else class="text-center">
|
||||||
|
{{ t('account.mailForwarding.accountNotEnabled') }}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,49 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
import FetchData from 'components/FetchData.vue';
|
||||||
|
import FormModel from 'components/FormModel.vue';
|
||||||
|
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const rolesOptions = ref([]);
|
||||||
|
const formModelRef = ref();
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<FetchData
|
||||||
|
url="VnRoles"
|
||||||
|
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
|
||||||
|
auto-load
|
||||||
|
@on-fetch="(data) => (rolesOptions = data)"
|
||||||
|
/>
|
||||||
|
<FormModel
|
||||||
|
ref="formModelRef"
|
||||||
|
model="AccountPrivileges"
|
||||||
|
:url="`VnUsers/${route.params.id}`"
|
||||||
|
:url-create="`VnUsers/${route.params.id}/privileges`"
|
||||||
|
auto-load
|
||||||
|
@on-data-saved="formModelRef.fetch()"
|
||||||
|
>
|
||||||
|
<template #form="{ data }">
|
||||||
|
<div class="q-gutter-y-sm">
|
||||||
|
<QCheckbox
|
||||||
|
v-model="data.hasGrant"
|
||||||
|
:label="t('account.card.privileges.delegate')"
|
||||||
|
/>
|
||||||
|
<VnSelect
|
||||||
|
:label="t('account.card.role')"
|
||||||
|
v-model="data.roleFk"
|
||||||
|
:options="rolesOptions"
|
||||||
|
option-value="id"
|
||||||
|
option-label="name"
|
||||||
|
hide-selected
|
||||||
|
:required="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</FormModel>
|
||||||
|
</template>
|
|
@ -0,0 +1,102 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import CardSummary from 'components/ui/CardSummary.vue';
|
||||||
|
import VnLv from 'src/components/ui/VnLv.vue';
|
||||||
|
|
||||||
|
import { useArrayData } from 'src/composables/useArrayData';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { store } = useArrayData('Account');
|
||||||
|
const account = ref(store.data);
|
||||||
|
|
||||||
|
const entityId = computed(() => $props.id || route.params.id);
|
||||||
|
const filter = {
|
||||||
|
where: { id: entityId },
|
||||||
|
fields: ['id', 'nickname', 'name', 'role'],
|
||||||
|
include: { relation: 'role', scope: { fields: ['id', 'name'] } },
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CardSummary
|
||||||
|
data-key="AccountSummary"
|
||||||
|
ref="AccountSummary"
|
||||||
|
url="VnUsers/preview"
|
||||||
|
:filter="filter"
|
||||||
|
@on-fetch="(data) => (account = data)"
|
||||||
|
>
|
||||||
|
<template #header>{{ account.id }} - {{ account.nickname }}</template>
|
||||||
|
<template #body>
|
||||||
|
<QCard class="vn-one">
|
||||||
|
<QCardSection class="q-pa-none">
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'AccountBasicData', params: { id: entityId } }"
|
||||||
|
class="header header-link"
|
||||||
|
>
|
||||||
|
{{ t('globals.pageTitles.basicData') }}
|
||||||
|
<QIcon name="open_in_new" />
|
||||||
|
</router-link>
|
||||||
|
</QCardSection>
|
||||||
|
<VnLv :label="t('account.card.nickname')" :value="account.nickname" />
|
||||||
|
<VnLv :label="t('account.card.role')" :value="account.role.name" />
|
||||||
|
</QCard>
|
||||||
|
</template>
|
||||||
|
</CardSummary>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.q-dialog__inner--minimized > div {
|
||||||
|
max-width: 80%;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.multimedia-container {
|
||||||
|
flex: 1 0 21%;
|
||||||
|
}
|
||||||
|
.multimedia {
|
||||||
|
transition: all 0.5s;
|
||||||
|
opacity: 1;
|
||||||
|
height: 250px;
|
||||||
|
|
||||||
|
.q-img {
|
||||||
|
object-fit: cover;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
video {
|
||||||
|
object-fit: cover;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.multimedia:hover {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
top: 1%;
|
||||||
|
right: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zindex {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-state {
|
||||||
|
width: 10%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,104 @@
|
||||||
|
<script setup>
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||||
|
|
||||||
|
import { useArrayData } from 'composables/useArrayData';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
dataKey: { type: String, required: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const paginateRef = ref(null);
|
||||||
|
|
||||||
|
const arrayData = useArrayData(props.dataKey);
|
||||||
|
const store = arrayData.store;
|
||||||
|
|
||||||
|
const data = computed(() => {
|
||||||
|
const dataCopy = store.data;
|
||||||
|
return dataCopy.sort((a, b) => a.role?.name.localeCompare(b.role?.name));
|
||||||
|
});
|
||||||
|
|
||||||
|
const filter = computed(() => ({
|
||||||
|
where: {
|
||||||
|
prindicpalType: 'USER',
|
||||||
|
principalId: route.params.id,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
relation: 'role',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'name', 'description'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const urlPath = 'RoleMappings';
|
||||||
|
|
||||||
|
const columns = computed(() => [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.params.id,
|
||||||
|
() => {
|
||||||
|
store.url = urlPath;
|
||||||
|
store.filter = filter.value;
|
||||||
|
store.limit = 0;
|
||||||
|
fetchSubRoles();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchSubRoles = () => paginateRef.value.fetch();
|
||||||
|
|
||||||
|
const redirectToRoleSummary = (id) =>
|
||||||
|
router.push({ name: 'RoleSummary', params: { id } });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<QPage class="column items-center q-pa-md">
|
||||||
|
<div class="full-width" style="max-width: 400px">
|
||||||
|
<VnPaginate
|
||||||
|
ref="paginateRef"
|
||||||
|
:data-key="dataKey"
|
||||||
|
:filter="filter"
|
||||||
|
:url="urlPath"
|
||||||
|
:limit="0"
|
||||||
|
auto-load
|
||||||
|
>
|
||||||
|
<template #body>
|
||||||
|
<QTable :rows="data" :columns="columns" hide-header>
|
||||||
|
<template #body="{ row }">
|
||||||
|
<QTr
|
||||||
|
@click="redirectToRoleSummary(row.role?.id)"
|
||||||
|
class="cursor-pointer"
|
||||||
|
>
|
||||||
|
<QTd>
|
||||||
|
<div class="column">
|
||||||
|
<span>{{ row.role?.name }}</span>
|
||||||
|
<span class="color-vn-label">{{
|
||||||
|
row.role?.description
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</QTd>
|
||||||
|
</QTr>
|
||||||
|
</template>
|
||||||
|
</QTable>
|
||||||
|
</template>
|
||||||
|
</VnPaginate>
|
||||||
|
</div>
|
||||||
|
</QPage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
Role removed. Changes will take a while to fully propagate.: Rol eliminado. Los cambios tardaran un tiempo en propagarse completamente.
|
||||||
|
Role added! Changes will take a while to fully propagate.: ¡Rol añadido! Los cambios tardaran un tiempo en propagarse completamente.
|
||||||
|
El rol va a ser eliminado: Role will be removed
|
||||||
|
¿Seguro que quieres continuar?: Are you sure you want to continue?
|
||||||
|
</i18n>
|
|
@ -15,24 +15,75 @@ account:
|
||||||
privileges: Privileges
|
privileges: Privileges
|
||||||
mailAlias: Mail Alias
|
mailAlias: Mail Alias
|
||||||
mailForwarding: Mail Forwarding
|
mailForwarding: Mail Forwarding
|
||||||
|
accountCreate: New user
|
||||||
aliasUsers: Users
|
aliasUsers: Users
|
||||||
card:
|
card:
|
||||||
name: Name
|
name: Name
|
||||||
nickname: User
|
nickname: User
|
||||||
role: Rol
|
role: Role
|
||||||
email: Email
|
email: Email
|
||||||
alias: Alias
|
alias: Alias
|
||||||
lang: Language
|
lang: Language
|
||||||
|
roleFk: Role
|
||||||
|
newUser: New user
|
||||||
|
ticketTracking: Ticket tracking
|
||||||
|
privileges:
|
||||||
|
delegate: Can delegate privileges
|
||||||
|
enabled: Account enabled!
|
||||||
|
disabled: Account disabled!
|
||||||
|
willActivated: User will activated
|
||||||
|
willDeactivated: User will be deactivated
|
||||||
|
activated: User activated!
|
||||||
|
deactivated: User deactivated!
|
||||||
actions:
|
actions:
|
||||||
setPassword: Set password
|
setPassword: Set password
|
||||||
disableAccount:
|
disableAccount:
|
||||||
name: Disable account
|
name: Disable account
|
||||||
title: La cuenta será deshabilitada
|
title: The account will be disabled
|
||||||
subtitle: ¿Seguro que quieres continuar?
|
subtitle: Are you sure you want to continue?
|
||||||
disableUser: Disable user
|
success: 'Account disabled!'
|
||||||
sync: Sync
|
enableAccount:
|
||||||
delete: Delete
|
name: Enable account
|
||||||
|
title: The account will be enabled
|
||||||
|
subtitle: Are you sure you want to continue?
|
||||||
|
success: 'Account enabled!'
|
||||||
|
deactivateUser:
|
||||||
|
name: Deactivate user
|
||||||
|
title: The user will be deactivated
|
||||||
|
subtitle: Are you sure you want to continue?
|
||||||
|
success: 'User deactivated!'
|
||||||
|
activateUser:
|
||||||
|
name: Activate user
|
||||||
|
title: The user will be disabled
|
||||||
|
subtitle: Are you sure you want to continue?
|
||||||
|
success: 'User activated!'
|
||||||
|
sync:
|
||||||
|
name: Sync
|
||||||
|
title: The account will be sync
|
||||||
|
subtitle: Are you sure you want to continue?
|
||||||
|
success: 'User synchronized!'
|
||||||
|
checkbox: Synchronize password
|
||||||
|
message: Do you want to synchronize user?
|
||||||
|
tooltip: If password is not specified, just user attributes are synchronized
|
||||||
|
delete:
|
||||||
|
name: Delete
|
||||||
|
title: The account will be deleted
|
||||||
|
subtitle: Are you sure you want to continue?
|
||||||
|
success: ''
|
||||||
search: Search user
|
search: Search user
|
||||||
|
searchInfo: You can search by id, name or nickname
|
||||||
|
create:
|
||||||
|
name: Name
|
||||||
|
nickname: Nickname
|
||||||
|
email: Email
|
||||||
|
role: Role
|
||||||
|
password: Password
|
||||||
|
active: Active
|
||||||
|
mailForwarding:
|
||||||
|
forwardingMail: Forward email
|
||||||
|
accountNotEnabled: Account not enabled
|
||||||
|
enableMailForwarding: Enable mail forwarding
|
||||||
|
mailInputInfo: All emails will be forwarded to the specified address.
|
||||||
role:
|
role:
|
||||||
pageTitles:
|
pageTitles:
|
||||||
inheritedRoles: Inherited Roles
|
inheritedRoles: Inherited Roles
|
||||||
|
@ -67,6 +118,7 @@ ldap:
|
||||||
groupDN: Group DN
|
groupDN: Group DN
|
||||||
testConnection: Test connection
|
testConnection: Test connection
|
||||||
success: LDAP connection established!
|
success: LDAP connection established!
|
||||||
|
password: Password
|
||||||
samba:
|
samba:
|
||||||
enableSync: Enable synchronization
|
enableSync: Enable synchronization
|
||||||
domainController: Domain controller
|
domainController: Domain controller
|
||||||
|
@ -78,6 +130,21 @@ samba:
|
||||||
verifyCertificate: Verify certificate
|
verifyCertificate: Verify certificate
|
||||||
testConnection: Test connection
|
testConnection: Test connection
|
||||||
success: Samba connection established!
|
success: Samba connection established!
|
||||||
|
accounts:
|
||||||
|
homedir: Homedir base
|
||||||
|
shell: Shell
|
||||||
|
idBase: User and role base id
|
||||||
|
min: Min
|
||||||
|
max: Max
|
||||||
|
warn: Warn
|
||||||
|
inact: Inact
|
||||||
|
syncAll: Synchronize all
|
||||||
|
syncRoles: Synchronize roles
|
||||||
|
connections:
|
||||||
|
refresh: Refresh
|
||||||
|
username: Username
|
||||||
|
created: Created
|
||||||
|
killSession: Kill session
|
||||||
acls:
|
acls:
|
||||||
role: Role
|
role: Role
|
||||||
accessType: Access type
|
accessType: Access type
|
||||||
|
|
|
@ -15,6 +15,7 @@ account:
|
||||||
privileges: Privilegios
|
privileges: Privilegios
|
||||||
mailAlias: Alias de correo
|
mailAlias: Alias de correo
|
||||||
mailForwarding: Reenvío de correo
|
mailForwarding: Reenvío de correo
|
||||||
|
accountCreate: Nuevo usuario
|
||||||
aliasUsers: Usuarios
|
aliasUsers: Usuarios
|
||||||
card:
|
card:
|
||||||
nickname: Usuario
|
nickname: Usuario
|
||||||
|
@ -22,27 +23,66 @@ account:
|
||||||
role: Rol
|
role: Rol
|
||||||
email: Mail
|
email: Mail
|
||||||
alias: Alias
|
alias: Alias
|
||||||
lang: dioma
|
lang: Idioma
|
||||||
|
roleFk: Rol
|
||||||
|
enabled: ¡Cuenta habilitada!
|
||||||
|
disabled: ¡Cuenta deshabilitada!
|
||||||
|
willActivated: El usuario será activado
|
||||||
|
willDeactivated: El usuario será desactivado
|
||||||
|
activated: ¡Usuario activado!
|
||||||
|
deactivated: ¡Usuario desactivado!
|
||||||
|
newUser: Nuevo usuario
|
||||||
|
privileges:
|
||||||
|
delegate: Puede delegar privilegios
|
||||||
actions:
|
actions:
|
||||||
setPassword: Establecer contraseña
|
setPassword: Establecer contraseña
|
||||||
disableAccount:
|
disableAccount:
|
||||||
name: Deshabilitar cuenta
|
name: Deshabilitar cuenta
|
||||||
title: La cuenta será deshabilitada
|
title: La cuenta será deshabilitada
|
||||||
subtitle: ¿Seguro que quieres continuar?
|
subtitle: ¿Seguro que quieres continuar?
|
||||||
disableUser:
|
success: '¡Cuenta deshabilitada!'
|
||||||
|
enableAccount:
|
||||||
|
name: Habilitar cuenta
|
||||||
|
title: La cuenta será habilitada
|
||||||
|
subtitle: ¿Seguro que quieres continuar?
|
||||||
|
success: '¡Cuenta habilitada!'
|
||||||
|
deactivateUser:
|
||||||
name: Desactivar usuario
|
name: Desactivar usuario
|
||||||
title: El usuario será deshabilitado
|
title: El usuario será deshabilitado
|
||||||
subtitle: ¿Seguro que quieres continuar?
|
subtitle: ¿Seguro que quieres continuar?
|
||||||
|
success: '¡Usuario desactivado!'
|
||||||
|
activateUser:
|
||||||
|
name: Activar usuario
|
||||||
|
title: El usuario será activado
|
||||||
|
subtitle: ¿Seguro que quieres continuar?
|
||||||
|
success: '¡Usuario activado!'
|
||||||
sync:
|
sync:
|
||||||
name: Sincronizar
|
name: Sincronizar
|
||||||
title: El usuario será sincronizado
|
title: El usuario será sincronizado
|
||||||
subtitle: ¿Seguro que quieres continuar?
|
subtitle: ¿Seguro que quieres continuar?
|
||||||
|
success: '¡Usuario sincronizado!'
|
||||||
|
checkbox: Sincronizar contraseña
|
||||||
|
message: ¿Quieres sincronizar el usuario?
|
||||||
|
tooltip: Si la contraseña no se especifica solo se sincronizarán lo atributos del usuario
|
||||||
delete:
|
delete:
|
||||||
name: Eliminar
|
name: Eliminar
|
||||||
title: El usuario será eliminado
|
title: El usuario será eliminado
|
||||||
subtitle: ¿Seguro que quieres continuar?
|
subtitle: ¿Seguro que quieres continuar?
|
||||||
|
success: ''
|
||||||
search: Buscar usuario
|
search: Buscar usuario
|
||||||
|
searchInfo: Puedes buscar por id, nombre o usuario
|
||||||
|
create:
|
||||||
|
name: Nombre
|
||||||
|
nickname: Nombre mostrado
|
||||||
|
email: Email
|
||||||
|
role: Rol
|
||||||
|
password: Contraseña
|
||||||
|
active: Activo
|
||||||
|
mailForwarding:
|
||||||
|
forwardingMail: Dirección de reenvío
|
||||||
|
accountNotEnabled: Cuenta no habilitada
|
||||||
|
enableMailForwarding: Habilitar redirección de correo
|
||||||
|
mailInputInfo: Todos los correos serán reenviados a la dirección especificada, no se mantendrá copia de los mismos en el buzón del usuario.
|
||||||
role:
|
role:
|
||||||
pageTitles:
|
pageTitles:
|
||||||
inheritedRoles: Roles heredados
|
inheritedRoles: Roles heredados
|
||||||
|
@ -70,6 +110,7 @@ mailAlias:
|
||||||
name: Nombre
|
name: Nombre
|
||||||
isPublic: Público
|
isPublic: Público
|
||||||
ldap:
|
ldap:
|
||||||
|
password: Contraseña
|
||||||
enableSync: Habilitar sincronización
|
enableSync: Habilitar sincronización
|
||||||
server: Servidor
|
server: Servidor
|
||||||
rdn: RDN
|
rdn: RDN
|
||||||
|
@ -86,9 +127,24 @@ samba:
|
||||||
userAD: Usuario AD
|
userAD: Usuario AD
|
||||||
passwordAD: Contraseña AD
|
passwordAD: Contraseña AD
|
||||||
domainPart: DN usuarios (sin la parte del dominio)
|
domainPart: DN usuarios (sin la parte del dominio)
|
||||||
Verify certificate: Verificar certificado
|
verifyCertificate: Verificar certificado
|
||||||
testConnection: Probar conexión
|
testConnection: Probar conexión
|
||||||
success: ¡Conexión con Samba establecida!
|
success: ¡Conexión con Samba establecida!
|
||||||
|
accounts:
|
||||||
|
homedir: Directorio base para carpetas de usuario
|
||||||
|
shell: Intérprete de línea de comandos
|
||||||
|
idBase: Id base usuarios y roles
|
||||||
|
min: Min
|
||||||
|
max: Max
|
||||||
|
warn: Warn
|
||||||
|
inact: Inact
|
||||||
|
syncAll: Sincronizar todo
|
||||||
|
syncRoles: Sincronizar roles
|
||||||
|
connections:
|
||||||
|
refresh: Actualizar
|
||||||
|
username: Nombre de usuario
|
||||||
|
created: Creado
|
||||||
|
killSession: Matar sesión
|
||||||
acls:
|
acls:
|
||||||
role: Rol
|
role: Rol
|
||||||
accessType: Tipo de acceso
|
accessType: Tipo de acceso
|
||||||
|
|
|
@ -33,7 +33,6 @@ function exprBuilder(param, value) {
|
||||||
url="Agencies"
|
url="Agencies"
|
||||||
order="name"
|
order="name"
|
||||||
:expr-builder="exprBuilder"
|
:expr-builder="exprBuilder"
|
||||||
auto-load
|
|
||||||
>
|
>
|
||||||
<template #body="{ rows }">
|
<template #body="{ rows }">
|
||||||
<CardList
|
<CardList
|
||||||
|
|
|
@ -14,7 +14,7 @@ const entityId = computed(() => $props.id || useRoute().params.id);
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="q-pa-md">
|
<div class="q-pa-md">
|
||||||
<CardSummary :url="`Agencies/${entityId}`">
|
<CardSummary :url="`Agencies/${entityId}`" data-key="Agency">
|
||||||
<template #header="{ entity: agency }">{{ agency.name }}</template>
|
<template #header="{ entity: agency }">{{ agency.name }}</template>
|
||||||
<template #body="{ entity: agency }">
|
<template #body="{ entity: agency }">
|
||||||
<QCard class="vn-one">
|
<QCard class="vn-one">
|
||||||
|
|
|
@ -10,32 +10,13 @@ import VnInput from 'src/components/common/VnInput.vue';
|
||||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { useSession } from 'src/composables/useSession';
|
// import { useSession } from 'src/composables/useSession';
|
||||||
|
import VnImg from 'src/components/ui/VnImg.vue';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { getTokenMultimedia } = useSession();
|
// const { getTokenMultimedia } = useSession();
|
||||||
const token = getTokenMultimedia();
|
// const token = getTokenMultimedia();
|
||||||
|
|
||||||
const claimFilter = {
|
|
||||||
fields: [
|
|
||||||
'id',
|
|
||||||
'clientFk',
|
|
||||||
'created',
|
|
||||||
'workerFk',
|
|
||||||
'claimStateFk',
|
|
||||||
'packages',
|
|
||||||
'pickup',
|
|
||||||
],
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
relation: 'client',
|
|
||||||
scope: {
|
|
||||||
fields: ['name'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const claimStates = ref([]);
|
const claimStates = ref([]);
|
||||||
const claimStatesCopy = ref([]);
|
const claimStatesCopy = ref([]);
|
||||||
|
@ -87,11 +68,10 @@ const statesFilter = {
|
||||||
/>
|
/>
|
||||||
<FetchData url="ClaimStates" @on-fetch="setClaimStates" auto-load />
|
<FetchData url="ClaimStates" @on-fetch="setClaimStates" auto-load />
|
||||||
<FormModel
|
<FormModel
|
||||||
:url="`Claims/${route.params.id}`"
|
model="Claim"
|
||||||
:url-update="`Claims/updateClaim/${route.params.id}`"
|
:url-update="`Claims/updateClaim/${route.params.id}`"
|
||||||
:filter="claimFilter"
|
|
||||||
model="claim"
|
|
||||||
auto-load
|
auto-load
|
||||||
|
:reload="true"
|
||||||
>
|
>
|
||||||
<template #form="{ data, validate, filter }">
|
<template #form="{ data, validate, filter }">
|
||||||
<VnRow class="row q-gutter-md q-mb-md">
|
<VnRow class="row q-gutter-md q-mb-md">
|
||||||
|
@ -118,9 +98,11 @@ const statesFilter = {
|
||||||
>
|
>
|
||||||
<template #before>
|
<template #before>
|
||||||
<QAvatar color="orange">
|
<QAvatar color="orange">
|
||||||
<QImg
|
<VnImg
|
||||||
v-if="data.workerFk"
|
v-if="data.workerFk"
|
||||||
:src="`/api/Images/user/160x160/${data.workerFk}/download?access_token=${token}`"
|
:size="'160x160'"
|
||||||
|
:id="data.workerFk"
|
||||||
|
collection="user"
|
||||||
spinner-color="white"
|
spinner-color="white"
|
||||||
/>
|
/>
|
||||||
</QAvatar>
|
</QAvatar>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import VnCard from 'components/common/VnCard.vue';
|
import VnCard from 'components/common/VnCard.vue';
|
||||||
import ClaimDescriptor from './ClaimDescriptor.vue';
|
import ClaimDescriptor from './ClaimDescriptor.vue';
|
||||||
import ClaimFilter from '../ClaimFilter.vue';
|
import ClaimFilter from '../ClaimFilter.vue';
|
||||||
|
import filter from './ClaimFilter.js';
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<VnCard
|
<VnCard
|
||||||
|
@ -13,5 +14,6 @@ import ClaimFilter from '../ClaimFilter.vue';
|
||||||
search-url="Claims/filter"
|
search-url="Claims/filter"
|
||||||
searchbar-label="Search claim"
|
searchbar-label="Search claim"
|
||||||
searchbar-info="You can search by claim id or customer name"
|
searchbar-info="You can search by claim id or customer name"
|
||||||
|
:filter="filter"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -12,6 +12,7 @@ import useCardDescription from 'src/composables/useCardDescription';
|
||||||
import VnUserLink from 'src/components/ui/VnUserLink.vue';
|
import VnUserLink from 'src/components/ui/VnUserLink.vue';
|
||||||
import { getUrl } from 'src/composables/getUrl';
|
import { getUrl } from 'src/composables/getUrl';
|
||||||
import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue';
|
import ZoneDescriptorProxy from 'src/pages/Zone/Card/ZoneDescriptorProxy.vue';
|
||||||
|
import filter from './ClaimFilter.js';
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
id: {
|
id: {
|
||||||
|
@ -29,49 +30,6 @@ const entityId = computed(() => {
|
||||||
return $props.id || route.params.id;
|
return $props.id || route.params.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
const filter = {
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
relation: 'client',
|
|
||||||
scope: {
|
|
||||||
include: [
|
|
||||||
{ relation: 'salesPersonUser' },
|
|
||||||
{
|
|
||||||
relation: 'claimsRatio',
|
|
||||||
scope: {
|
|
||||||
fields: ['claimingRate'],
|
|
||||||
limit: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
relation: 'claimState',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
relation: 'ticket',
|
|
||||||
scope: {
|
|
||||||
include: [
|
|
||||||
{ relation: 'zone' },
|
|
||||||
{
|
|
||||||
relation: 'address',
|
|
||||||
scope: {
|
|
||||||
include: { relation: 'province' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
relation: 'worker',
|
|
||||||
scope: {
|
|
||||||
include: { relation: 'user' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const STATE_COLOR = {
|
const STATE_COLOR = {
|
||||||
pending: 'warning',
|
pending: 'warning',
|
||||||
incomplete: 'info',
|
incomplete: 'info',
|
||||||
|
@ -101,7 +59,7 @@ onMounted(async () => {
|
||||||
:title="data.title"
|
:title="data.title"
|
||||||
:subtitle="data.subtitle"
|
:subtitle="data.subtitle"
|
||||||
@on-fetch="setData"
|
@on-fetch="setData"
|
||||||
data-key="claimData"
|
data-key="Claim"
|
||||||
>
|
>
|
||||||
<template #menu="{ entity }">
|
<template #menu="{ entity }">
|
||||||
<ClaimDescriptorMenu :claim="entity" />
|
<ClaimDescriptorMenu :claim="entity" />
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
export default {
|
||||||
|
fields: [
|
||||||
|
'id',
|
||||||
|
'clientFk',
|
||||||
|
'created',
|
||||||
|
'workerFk',
|
||||||
|
'claimStateFk',
|
||||||
|
'packages',
|
||||||
|
'pickup',
|
||||||
|
'ticketFk',
|
||||||
|
],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
relation: 'client',
|
||||||
|
scope: {
|
||||||
|
include: [
|
||||||
|
{ relation: 'salesPersonUser' },
|
||||||
|
{
|
||||||
|
relation: 'claimsRatio',
|
||||||
|
scope: {
|
||||||
|
fields: ['claimingRate'],
|
||||||
|
limit: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relation: 'claimState',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relation: 'ticket',
|
||||||
|
scope: {
|
||||||
|
include: [
|
||||||
|
{ relation: 'zone' },
|
||||||
|
{
|
||||||
|
relation: 'address',
|
||||||
|
scope: {
|
||||||
|
include: { relation: 'province' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relation: 'worker',
|
||||||
|
scope: {
|
||||||
|
include: { relation: 'user' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
|
@ -185,6 +185,7 @@ async function changeState(value) {
|
||||||
:url="`Claims/${entityId}/getSummary`"
|
:url="`Claims/${entityId}/getSummary`"
|
||||||
:entity-id="entityId"
|
:entity-id="entityId"
|
||||||
@on-fetch="getClaimDms"
|
@on-fetch="getClaimDms"
|
||||||
|
data-key="claimSummary"
|
||||||
>
|
>
|
||||||
<template #header="{ entity: { claim } }">
|
<template #header="{ entity: { claim } }">
|
||||||
{{ claim.id }} - {{ claim.client.name }} ({{ claim.client.id }})
|
{{ claim.id }} - {{ claim.client.name }} ({{ claim.client.id }})
|
||||||
|
|
|
@ -234,7 +234,7 @@ const showBalancePdf = (balance) => {
|
||||||
<template #body-cell-employee="{ row }">
|
<template #body-cell-employee="{ row }">
|
||||||
<QTd auto-width @click.stop>
|
<QTd auto-width @click.stop>
|
||||||
<QBtn color="blue" flat no-caps>{{ row.userName }}</QBtn>
|
<QBtn color="blue" flat no-caps>{{ row.userName }}</QBtn>
|
||||||
<WorkerDescriptorProxy :id="row.clientFk" />
|
<WorkerDescriptorProxy :id="row.workerFk" />
|
||||||
</QTd>
|
</QTd>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -3,16 +3,14 @@ import { ref } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import { useSession } from 'src/composables/useSession';
|
|
||||||
import FetchData from 'components/FetchData.vue';
|
import FetchData from 'components/FetchData.vue';
|
||||||
import FormModel from 'components/FormModel.vue';
|
import FormModel from 'components/FormModel.vue';
|
||||||
import VnRow from 'components/ui/VnRow.vue';
|
import VnRow from 'components/ui/VnRow.vue';
|
||||||
import VnInput from 'src/components/common/VnInput.vue';
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
import VnImg from 'src/components/ui/VnImg.vue';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { getTokenMultimedia } = useSession();
|
|
||||||
const token = getTokenMultimedia();
|
|
||||||
|
|
||||||
const workers = ref([]);
|
const workers = ref([]);
|
||||||
const workersCopy = ref([]);
|
const workersCopy = ref([]);
|
||||||
|
@ -143,10 +141,11 @@ const filterOptions = {
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<QAvatar color="orange">
|
<QAvatar color="orange">
|
||||||
<QImg
|
<VnImg
|
||||||
:src="`/api/Images/user/160x160/${data.salesPersonFk}/download?access_token=${token}`"
|
|
||||||
spinner-color="white"
|
|
||||||
v-if="data.salesPersonFk"
|
v-if="data.salesPersonFk"
|
||||||
|
:id="user.id"
|
||||||
|
collection="user"
|
||||||
|
spinner-color="white"
|
||||||
/>
|
/>
|
||||||
</QAvatar>
|
</QAvatar>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -10,7 +10,7 @@ import CustomerFilter from '../CustomerFilter.vue';
|
||||||
:descriptor="CustomerDescriptor"
|
:descriptor="CustomerDescriptor"
|
||||||
:filter-panel="CustomerFilter"
|
:filter-panel="CustomerFilter"
|
||||||
search-data-key="CustomerList"
|
search-data-key="CustomerList"
|
||||||
search-url="Clients/filter"
|
search-url="Clients/extendedListFilter"
|
||||||
searchbar-label="Search customer"
|
searchbar-label="Search customer"
|
||||||
searchbar-info="You can search by customer id or name"
|
searchbar-info="You can search by customer id or name"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -143,10 +143,6 @@ function handleLocation(data, location) {
|
||||||
</VnRow>
|
</VnRow>
|
||||||
|
|
||||||
<VnRow>
|
<VnRow>
|
||||||
<QCheckbox
|
|
||||||
:label="t('Incoterms authorization')"
|
|
||||||
v-model="data.hasIncoterms"
|
|
||||||
/>
|
|
||||||
<QCheckbox
|
<QCheckbox
|
||||||
:label="t('Electronic invoice')"
|
:label="t('Electronic invoice')"
|
||||||
v-model="data.hasElectronicInvoice"
|
v-model="data.hasElectronicInvoice"
|
||||||
|
|
|
@ -61,7 +61,11 @@ const creditWarning = computed(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CardSummary ref="summary" :url="`Clients/${entityId}/summary`">
|
<CardSummary
|
||||||
|
ref="summary"
|
||||||
|
:url="`Clients/${entityId}/summary`"
|
||||||
|
data-key="CustomerSummary"
|
||||||
|
>
|
||||||
<template #body="{ entity }">
|
<template #body="{ entity }">
|
||||||
<QCard class="vn-one">
|
<QCard class="vn-one">
|
||||||
<VnTitle
|
<VnTitle
|
||||||
|
@ -306,10 +310,8 @@ const creditWarning = computed(() => {
|
||||||
:value="entity.recommendedCredit"
|
:value="entity.recommendedCredit"
|
||||||
/>
|
/>
|
||||||
</QCard>
|
</QCard>
|
||||||
<QCard>
|
<QCard class="vn-one">
|
||||||
<div class="header">
|
<VnTitle :text="t('Latest tickets')" />
|
||||||
{{ t('Latest tickets') }}
|
|
||||||
</div>
|
|
||||||
<CustomerSummaryTable />
|
<CustomerSummaryTable />
|
||||||
</QCard>
|
</QCard>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -28,7 +28,6 @@ const isLoading = ref(false);
|
||||||
const name = ref(null);
|
const name = ref(null);
|
||||||
const usersPreviewRef = ref(null);
|
const usersPreviewRef = ref(null);
|
||||||
const user = ref([]);
|
const user = ref([]);
|
||||||
const userPasswords = ref(0);
|
|
||||||
|
|
||||||
const dataChanges = computed(() => {
|
const dataChanges = computed(() => {
|
||||||
return (
|
return (
|
||||||
|
@ -45,7 +44,6 @@ const showChangePasswordDialog = () => {
|
||||||
component: CustomerChangePassword,
|
component: CustomerChangePassword,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
id: route.params.id,
|
id: route.params.id,
|
||||||
userPasswords: userPasswords.value,
|
|
||||||
promise: usersPreviewRef.value.fetch(),
|
promise: usersPreviewRef.value.fetch(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -97,11 +95,6 @@ const onSubmit = async () => {
|
||||||
@on-fetch="(data) => (canChangePassword = data)"
|
@on-fetch="(data) => (canChangePassword = data)"
|
||||||
auto-load
|
auto-load
|
||||||
/>
|
/>
|
||||||
<FetchData
|
|
||||||
@on-fetch="(data) => (userPasswords = data[0])"
|
|
||||||
auto-load
|
|
||||||
url="UserPasswords"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
|
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
|
||||||
<QBtnGroup push class="q-gutter-x-sm">
|
<QBtnGroup push class="q-gutter-x-sm">
|
||||||
|
|
|
@ -29,7 +29,7 @@ const zones = ref();
|
||||||
@on-fetch="(data) => (workers = data)"
|
@on-fetch="(data) => (workers = data)"
|
||||||
auto-load
|
auto-load
|
||||||
/>
|
/>
|
||||||
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
|
<VnFilterPanel :data-key="props.dataKey" :search-button="true" search-url="table">
|
||||||
<template #tags="{ tag, formatFn }">
|
<template #tags="{ tag, formatFn }">
|
||||||
<div class="q-gutter-x-xs">
|
<div class="q-gutter-x-xs">
|
||||||
<strong>{{ t(`params.${tag.label}`) }}: </strong>
|
<strong>{{ t(`params.${tag.label}`) }}: </strong>
|
||||||
|
|
|
@ -1,94 +1,439 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref, computed, markRaw } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
import VnTable from 'components/VnTable/VnTable.vue';
|
||||||
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
|
import VnLocation from 'src/components/common/VnLocation.vue';
|
||||||
import CustomerFilter from './CustomerFilter.vue';
|
import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
||||||
import VnLv from 'src/components/ui/VnLv.vue';
|
|
||||||
import CardList from 'src/components/ui/CardList.vue';
|
|
||||||
import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
|
|
||||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
|
||||||
import CustomerSummary from './Card/CustomerSummary.vue';
|
import CustomerSummary from './Card/CustomerSummary.vue';
|
||||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||||
|
|
||||||
|
import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
|
||||||
|
|
||||||
|
import { toDate } from 'src/filters';
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { viewSummary } = useSummaryDialog();
|
const router = useRouter();
|
||||||
|
|
||||||
function navigate(id) {
|
const postcodesOptions = ref([]);
|
||||||
router.push({ path: `/customer/${id}` });
|
const tableRef = ref();
|
||||||
}
|
|
||||||
|
|
||||||
const redirectToCreateView = () => {
|
const columns = computed(() => [
|
||||||
router.push({ name: 'CustomerCreate' });
|
{
|
||||||
|
align: 'left',
|
||||||
|
name: 'id',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.id'),
|
||||||
|
chip: {
|
||||||
|
condition: () => true,
|
||||||
|
},
|
||||||
|
isId: true,
|
||||||
|
columnFilter: {
|
||||||
|
component: 'select',
|
||||||
|
name: 'search',
|
||||||
|
attrs: {
|
||||||
|
url: 'Clients',
|
||||||
|
fields: ['id', 'name'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.name'),
|
||||||
|
name: 'name',
|
||||||
|
isTitle: true,
|
||||||
|
create: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
name: 'socialName',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.socialName'),
|
||||||
|
isTitle: true,
|
||||||
|
create: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.fi'),
|
||||||
|
name: 'fi',
|
||||||
|
create: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.salesPersonFk'),
|
||||||
|
name: 'salesPersonFk',
|
||||||
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
|
url: 'Workers/activeWithInheritedRole',
|
||||||
|
fields: ['id', 'name'],
|
||||||
|
where: { role: 'salesPerson' },
|
||||||
|
},
|
||||||
|
create: true,
|
||||||
|
columnField: {
|
||||||
|
component: null,
|
||||||
|
},
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(row.salesPerson),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.credit'),
|
||||||
|
name: 'credit',
|
||||||
|
component: 'number',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.creditInsurance'),
|
||||||
|
name: 'creditInsurance',
|
||||||
|
component: 'number',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.phone'),
|
||||||
|
name: 'phone',
|
||||||
|
cardVisible: true,
|
||||||
|
columnFilter: {
|
||||||
|
component: 'number',
|
||||||
|
},
|
||||||
|
columnField: {
|
||||||
|
component: null,
|
||||||
|
after: {
|
||||||
|
component: markRaw(VnLinkPhone),
|
||||||
|
attrs: (prop) => {
|
||||||
|
return {
|
||||||
|
'phone-number': prop,
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.mobile'),
|
||||||
|
name: 'mobile',
|
||||||
|
cardVisible: true,
|
||||||
|
columnFilter: {
|
||||||
|
component: 'number',
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.street'),
|
||||||
|
name: 'street',
|
||||||
|
create: true,
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.countryFk'),
|
||||||
|
name: 'countryFk',
|
||||||
|
columnFilter: {
|
||||||
|
component: 'select',
|
||||||
|
inWhere: true,
|
||||||
|
alias: 'c',
|
||||||
|
attrs: {
|
||||||
|
url: 'Countries',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(row.country),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.provinceFk'),
|
||||||
|
name: 'provinceFk',
|
||||||
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
|
url: 'Provinces',
|
||||||
|
},
|
||||||
|
columnField: {
|
||||||
|
component: null,
|
||||||
|
},
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(row.province),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.city'),
|
||||||
|
name: 'city',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.postcode'),
|
||||||
|
name: 'postcode',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.email'),
|
||||||
|
name: 'email',
|
||||||
|
cardVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.created'),
|
||||||
|
name: 'created',
|
||||||
|
format: ({ created }) => toDate(created),
|
||||||
|
component: 'date',
|
||||||
|
columnFilter: {
|
||||||
|
alias: 'c',
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.businessTypeFk'),
|
||||||
|
name: 'businessTypeFk',
|
||||||
|
create: true,
|
||||||
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
|
url: 'BusinessTypes',
|
||||||
|
optionLabel: 'description',
|
||||||
|
optionValue: 'code',
|
||||||
|
},
|
||||||
|
columnField: {
|
||||||
|
component: null,
|
||||||
|
},
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(row.businessType),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.payMethodFk'),
|
||||||
|
name: 'payMethodFk',
|
||||||
|
columnFilter: {
|
||||||
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
|
url: 'PayMethods',
|
||||||
|
},
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(row.payMethod),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.sageTaxTypeFk'),
|
||||||
|
name: 'sageTaxTypeFk',
|
||||||
|
columnFilter: {
|
||||||
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
|
optionLabel: 'vat',
|
||||||
|
url: 'SageTaxTypes',
|
||||||
|
},
|
||||||
|
alias: 'sti',
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(row.sageTaxType),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.sageTransactionTypeFk'),
|
||||||
|
name: 'sageTransactionTypeFk',
|
||||||
|
columnFilter: {
|
||||||
|
component: 'select',
|
||||||
|
attrs: {
|
||||||
|
optionLabel: 'transaction',
|
||||||
|
url: 'SageTransactionTypes',
|
||||||
|
},
|
||||||
|
alias: 'stt',
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
format: (row, dashIfEmpty) => dashIfEmpty(row.sageTransactionType),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.isActive'),
|
||||||
|
name: 'isActive',
|
||||||
|
chip: {
|
||||||
|
color: null,
|
||||||
|
condition: (value) => !value,
|
||||||
|
icon: 'vn:disabled',
|
||||||
|
},
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.isVies'),
|
||||||
|
name: 'isVies',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.isTaxDataChecked'),
|
||||||
|
name: 'isTaxDataChecked',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.isEqualizated'),
|
||||||
|
name: 'isEqualizated',
|
||||||
|
create: true,
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.isFreezed'),
|
||||||
|
name: 'isFreezed',
|
||||||
|
chip: {
|
||||||
|
color: null,
|
||||||
|
condition: (value) => value,
|
||||||
|
icon: 'vn:frozen',
|
||||||
|
},
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.hasToInvoice'),
|
||||||
|
name: 'hasToInvoice',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.hasToInvoiceByAddress'),
|
||||||
|
name: 'hasToInvoiceByAddress',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.isToBeMailed'),
|
||||||
|
name: 'isToBeMailed',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.hasLcr'),
|
||||||
|
name: 'hasLcr',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.hasCoreVnl'),
|
||||||
|
name: 'hasCoreVnl',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'left',
|
||||||
|
label: t('customer.extendedList.tableVisibleColumns.hasSepaVnl'),
|
||||||
|
name: 'hasSepaVnl',
|
||||||
|
columnFilter: {
|
||||||
|
inWhere: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
align: 'right',
|
||||||
|
label: '',
|
||||||
|
name: 'tableActions',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
title: t('Client ticket list'),
|
||||||
|
icon: 'vn:ticket',
|
||||||
|
action: redirectToCreateView,
|
||||||
|
isPrimary: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Client ticket list'),
|
||||||
|
icon: 'preview',
|
||||||
|
action: (row) => viewSummary(row.id, CustomerSummary),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { viewSummary } = useSummaryDialog();
|
||||||
|
const redirectToCreateView = (row) => {
|
||||||
|
router.push({
|
||||||
|
name: 'TicketList',
|
||||||
|
query: {
|
||||||
|
params: JSON.stringify({
|
||||||
|
clientFk: row.id,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleLocation(data, location) {
|
||||||
|
const { town, code, provinceFk, countryFk } = location ?? {};
|
||||||
|
data.postcode = code;
|
||||||
|
data.city = town;
|
||||||
|
data.provinceFk = provinceFk;
|
||||||
|
data.countryFk = countryFk;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VnSearchbar
|
<VnSearchbar
|
||||||
:info="t('You can search by customer id or name')"
|
:info="t('You can search by customer id or name')"
|
||||||
:label="t('Search customer')"
|
:label="t('Search customer')"
|
||||||
data-key="CustomerList"
|
data-key="Customer"
|
||||||
/>
|
/>
|
||||||
<RightMenu>
|
<VnTable
|
||||||
<template #right-panel>
|
ref="tableRef"
|
||||||
<CustomerFilter data-key="CustomerList" />
|
data-key="Customer"
|
||||||
</template>
|
url="Clients/extendedListFilter"
|
||||||
</RightMenu>
|
:create="{
|
||||||
<QPage class="column items-center q-pa-md">
|
urlCreate: 'Clients/createWithUser',
|
||||||
<div class="vn-card-list">
|
title: 'Create client',
|
||||||
<VnPaginate
|
onDataSaved: ({ id }) => tableRef.redirect(id),
|
||||||
auto-load
|
formInitialData: {
|
||||||
data-key="CustomerList"
|
active: true,
|
||||||
|
isEqualizated: false,
|
||||||
|
},
|
||||||
|
}"
|
||||||
order="id DESC"
|
order="id DESC"
|
||||||
url="/Clients/filter"
|
:columns="columns"
|
||||||
|
default-mode="table"
|
||||||
|
redirect="customer"
|
||||||
|
auto-load
|
||||||
>
|
>
|
||||||
<template #body="{ rows }">
|
<template #more-create-dialog="{ data }">
|
||||||
<CardList
|
<VnLocation
|
||||||
:id="row.id"
|
:roles-allowed-to-create="['deliveryAssistant']"
|
||||||
:key="row.id"
|
:options="postcodesOptions"
|
||||||
:title="row.name"
|
v-model="data.location"
|
||||||
@click="navigate(row.id)"
|
@update:model-value="(location) => handleLocation(data, location)"
|
||||||
v-for="row of rows"
|
|
||||||
>
|
|
||||||
<template #list-items>
|
|
||||||
<VnLv :label="t('customer.list.email')" :value="row.email" />
|
|
||||||
<VnLv :value="row.phone">
|
|
||||||
<template #label>
|
|
||||||
{{ t('customer.list.phone') }}
|
|
||||||
<VnLinkPhone :phone-number="row.phone" />
|
|
||||||
</template>
|
|
||||||
</VnLv>
|
|
||||||
</template>
|
|
||||||
<template #actions>
|
|
||||||
<QBtn
|
|
||||||
:label="t('components.smartCard.openCard')"
|
|
||||||
@click.stop="navigate(row.id)"
|
|
||||||
outline
|
|
||||||
/>
|
|
||||||
<QBtn
|
|
||||||
:label="t('components.smartCard.openSummary')"
|
|
||||||
@click.stop="viewSummary(row.id, CustomerSummary)"
|
|
||||||
color="primary"
|
|
||||||
style="margin-top: 15px"
|
|
||||||
/>
|
/>
|
||||||
|
<QInput v-model="data.userName" :label="t('Web user')" />
|
||||||
|
<QInput :label="t('Email')" clearable type="email" v-model="data.email">
|
||||||
|
<template #append>
|
||||||
|
<QIcon name="info" class="cursor-info">
|
||||||
|
<QTooltip max-width="400px">{{
|
||||||
|
t('customer.basicData.youCanSaveMultipleEmails')
|
||||||
|
}}</QTooltip>
|
||||||
|
</QIcon>
|
||||||
</template>
|
</template>
|
||||||
</CardList>
|
</QInput>
|
||||||
</template>
|
</template>
|
||||||
</VnPaginate>
|
</VnTable>
|
||||||
</div>
|
|
||||||
<QPageSticky :offset="[20, 20]">
|
|
||||||
<QBtn @click="redirectToCreateView()" color="primary" fab icon="add" />
|
|
||||||
<QTooltip>
|
|
||||||
{{ t('New client') }}
|
|
||||||
</QTooltip>
|
|
||||||
</QPageSticky>
|
|
||||||
</QPage>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<i18n>
|
<i18n>
|
||||||
es:
|
es:
|
||||||
Search customer: Buscar cliente
|
Web user: Usuario Web
|
||||||
You can search by customer id or name: Puedes buscar por id o nombre del cliente
|
|
||||||
New client: Nuevo cliente
|
|
||||||
</i18n>
|
</i18n>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.col-content {
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -22,7 +22,7 @@ const balanceDueTotal = ref(0);
|
||||||
const selected = ref([]);
|
const selected = ref([]);
|
||||||
|
|
||||||
const tableColumnComponents = {
|
const tableColumnComponents = {
|
||||||
client: {
|
clientFk: {
|
||||||
component: QBtn,
|
component: QBtn,
|
||||||
props: () => ({ flat: true, class: 'link', noCaps: true }),
|
props: () => ({ flat: true, class: 'link', noCaps: true }),
|
||||||
event: () => {},
|
event: () => {},
|
||||||
|
@ -40,7 +40,7 @@ const tableColumnComponents = {
|
||||||
props: () => ({ flat: true, class: 'link', noCaps: true }),
|
props: () => ({ flat: true, class: 'link', noCaps: true }),
|
||||||
event: () => {},
|
event: () => {},
|
||||||
},
|
},
|
||||||
department: {
|
departmentName: {
|
||||||
component: 'span',
|
component: 'span',
|
||||||
props: () => {},
|
props: () => {},
|
||||||
event: () => {},
|
event: () => {},
|
||||||
|
@ -102,12 +102,12 @@ const columns = computed(() => [
|
||||||
align: 'left',
|
align: 'left',
|
||||||
field: 'clientName',
|
field: 'clientName',
|
||||||
label: t('Client'),
|
label: t('Client'),
|
||||||
name: 'client',
|
name: 'clientFk',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
align: 'left',
|
align: 'left',
|
||||||
field: 'isWorker',
|
field: ({ isWorker }) => Boolean(isWorker),
|
||||||
label: t('Is worker'),
|
label: t('Is worker'),
|
||||||
name: 'isWorker',
|
name: 'isWorker',
|
||||||
},
|
},
|
||||||
|
@ -122,7 +122,7 @@ const columns = computed(() => [
|
||||||
align: 'left',
|
align: 'left',
|
||||||
field: 'departmentName',
|
field: 'departmentName',
|
||||||
label: t('Department'),
|
label: t('Department'),
|
||||||
name: 'department',
|
name: 'departmentName',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -204,48 +204,24 @@ const viewAddObservation = (rowsSelected) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const departments = ref(new Map());
|
|
||||||
|
|
||||||
const onFetch = async (data) => {
|
const onFetch = async (data) => {
|
||||||
const salesPersonFks = data.map((item) => item.salesPersonFk);
|
|
||||||
const departmentNames = salesPersonFks.map(async (salesPersonFk) => {
|
|
||||||
try {
|
|
||||||
const { data: workerDepartment } = await axios.get(
|
|
||||||
`WorkerDepartments/${salesPersonFk}`
|
|
||||||
);
|
|
||||||
const { data: department } = await axios.get(
|
|
||||||
`Departments/${workerDepartment.departmentFk}`
|
|
||||||
);
|
|
||||||
departments.value.set(salesPersonFk, department.name);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Err: ', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const recoveryData = await axios.get('Recoveries');
|
const recoveryData = await axios.get('Recoveries');
|
||||||
|
|
||||||
const recoveries = recoveryData.data.map(({ clientFk, finished }) => ({
|
const recoveries = recoveryData.data.map(({ clientFk, finished }) => ({
|
||||||
clientFk,
|
clientFk,
|
||||||
finished,
|
finished,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
await Promise.all(departmentNames);
|
|
||||||
|
|
||||||
data.forEach((item) => {
|
data.forEach((item) => {
|
||||||
item.departmentName = departments.value.get(item.salesPersonFk);
|
|
||||||
item.isWorker = item.businessTypeFk === 'worker';
|
|
||||||
const recovery = recoveries.find(({ clientFk }) => clientFk === item.clientFk);
|
const recovery = recoveries.find(({ clientFk }) => clientFk === item.clientFk);
|
||||||
item.finished = recovery?.finished === null;
|
item.finished = recovery?.finished === null;
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const element of data) element.isWorker = element.businessTypeFk === 'worker';
|
|
||||||
|
|
||||||
balanceDueTotal.value = data.reduce((acc, { amount = 0 }) => acc + amount, 0);
|
balanceDueTotal.value = data.reduce((acc, { amount = 0 }) => acc + amount, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
function exprBuilder(param, value) {
|
function exprBuilder(param, value) {
|
||||||
switch (param) {
|
switch (param) {
|
||||||
case 'clientFk':
|
case 'clientFk':
|
||||||
return { [`d.${param}`]: value?.id };
|
return { [`d.${param}`]: value };
|
||||||
case 'creditInsurance':
|
case 'creditInsurance':
|
||||||
case 'amount':
|
case 'amount':
|
||||||
case 'workerFk':
|
case 'workerFk':
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import FetchData from 'components/FetchData.vue';
|
import FetchData from 'components/FetchData.vue';
|
||||||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||||
import VnInput from 'src/components/common/VnInput.vue';
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
@ -16,14 +15,13 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const clients = ref();
|
|
||||||
const salespersons = ref();
|
const salespersons = ref();
|
||||||
const countries = ref();
|
const countries = ref();
|
||||||
const authors = ref();
|
const authors = ref();
|
||||||
|
const departments = ref();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FetchData @on-fetch="(data) => (clients = data)" auto-load url="Clients" />
|
|
||||||
<FetchData
|
<FetchData
|
||||||
:filter="{ where: { role: 'salesPerson' } }"
|
:filter="{ where: { role: 'salesPerson' } }"
|
||||||
@on-fetch="(data) => (salespersons = data)"
|
@on-fetch="(data) => (salespersons = data)"
|
||||||
|
@ -36,6 +34,7 @@ const authors = ref();
|
||||||
auto-load
|
auto-load
|
||||||
url="Workers/activeWithInheritedRole"
|
url="Workers/activeWithInheritedRole"
|
||||||
/>
|
/>
|
||||||
|
<FetchData @on-fetch="(data) => (departments = data)" auto-load url="Departments" />
|
||||||
|
|
||||||
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
|
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
|
||||||
<template #tags="{ tag, formatFn }">
|
<template #tags="{ tag, formatFn }">
|
||||||
|
@ -47,29 +46,22 @@ const authors = ref();
|
||||||
|
|
||||||
<template #body="{ params, searchFn }">
|
<template #body="{ params, searchFn }">
|
||||||
<QItem class="q-mb-sm">
|
<QItem class="q-mb-sm">
|
||||||
<QItemSection v-if="clients">
|
|
||||||
<VnSelect
|
<VnSelect
|
||||||
:label="t('Client')"
|
:label="t('Client')"
|
||||||
:options="clients"
|
url="Clients"
|
||||||
dense
|
dense
|
||||||
emit-value
|
|
||||||
hide-selected
|
|
||||||
map-options
|
|
||||||
option-label="name"
|
option-label="name"
|
||||||
option-value="id"
|
option-value="id"
|
||||||
outlined
|
outlined
|
||||||
rounded
|
rounded
|
||||||
use-input
|
emit-value
|
||||||
|
hide-selected
|
||||||
|
map-options
|
||||||
v-model="params.clientFk"
|
v-model="params.clientFk"
|
||||||
|
use-input
|
||||||
@update:model-value="searchFn()"
|
@update:model-value="searchFn()"
|
||||||
auto-load
|
|
||||||
/>
|
/>
|
||||||
</QItemSection>
|
|
||||||
<QItemSection v-else>
|
|
||||||
<QSkeleton class="full-width" type="QInput" />
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
</QItem>
|
||||||
|
|
||||||
<QItem class="q-mb-sm">
|
<QItem class="q-mb-sm">
|
||||||
<QItemSection v-if="salespersons">
|
<QItemSection v-if="salespersons">
|
||||||
<VnSelect
|
<VnSelect
|
||||||
|
@ -93,6 +85,29 @@ const authors = ref();
|
||||||
<QSkeleton class="full-width" type="QInput" />
|
<QSkeleton class="full-width" type="QInput" />
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
</QItem>
|
</QItem>
|
||||||
|
<QItem class="q-mb-sm">
|
||||||
|
<QItemSection v-if="departments">
|
||||||
|
<VnSelect
|
||||||
|
:input-debounce="0"
|
||||||
|
:label="t('Departments')"
|
||||||
|
:options="departments"
|
||||||
|
dense
|
||||||
|
emit-value
|
||||||
|
hide-selected
|
||||||
|
map-options
|
||||||
|
option-label="name"
|
||||||
|
option-value="id"
|
||||||
|
outlined
|
||||||
|
rounded
|
||||||
|
use-input
|
||||||
|
v-model="params.departmentFk"
|
||||||
|
@update:model-value="searchFn()"
|
||||||
|
/>
|
||||||
|
</QItemSection>
|
||||||
|
<QItemSection v-else>
|
||||||
|
<QSkeleton class="full-width" type="QInput" />
|
||||||
|
</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
|
||||||
<QItem class="q-mb-sm">
|
<QItem class="q-mb-sm">
|
||||||
<QItemSection v-if="countries">
|
<QItemSection v-if="countries">
|
||||||
|
|
|
@ -1,619 +0,0 @@
|
||||||
<script setup>
|
|
||||||
import { ref, computed, onBeforeMount, onMounted } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
import { QBtn, QIcon } from 'quasar';
|
|
||||||
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
|
||||||
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
|
|
||||||
import CustomerExtendedListActions from './CustomerExtendedListActions.vue';
|
|
||||||
import CustomerExtendedListFilter from './CustomerExtendedListFilter.vue';
|
|
||||||
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
|
|
||||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
|
||||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
|
||||||
import { useArrayData } from 'composables/useArrayData';
|
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
|
||||||
import { toDate } from 'src/filters';
|
|
||||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
const router = useRouter();
|
|
||||||
const stateStore = useStateStore();
|
|
||||||
|
|
||||||
const arrayData = ref(null);
|
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
|
||||||
arrayData.value = useArrayData('CustomerExtendedList', {
|
|
||||||
url: 'Clients/extendedListFilter',
|
|
||||||
limit: 0,
|
|
||||||
});
|
|
||||||
await arrayData.value.fetch({ append: false });
|
|
||||||
stateStore.rightDrawer = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const filteredColumns = columns.value.filter(
|
|
||||||
(col) => col.name !== 'actions' && col.name !== 'customerStatus'
|
|
||||||
);
|
|
||||||
allColumnNames.value = filteredColumns.map((col) => col.name);
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedCustomerId = ref(0);
|
|
||||||
const selectedSalesPersonId = ref(0);
|
|
||||||
const allColumnNames = ref([]);
|
|
||||||
const visibleColumns = ref([]);
|
|
||||||
|
|
||||||
const tableColumnComponents = {
|
|
||||||
customerStatus: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: !prop.row.isActive
|
|
||||||
? 'vn:disabled'
|
|
||||||
: prop.row.isActive && prop.row.isFreezed
|
|
||||||
? 'vn:frozen'
|
|
||||||
: '',
|
|
||||||
color: 'primary',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
id: {
|
|
||||||
component: QBtn,
|
|
||||||
props: () => ({ flat: true, color: 'blue' }),
|
|
||||||
event: (prop) => {
|
|
||||||
selectCustomerId(prop.row.id);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
socialName: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
fi: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
salesPersonFk: {
|
|
||||||
component: QBtn,
|
|
||||||
props: () => ({ flat: true, color: 'blue' }),
|
|
||||||
event: (prop) => selectSalesPersonId(prop.row.salesPersonFk),
|
|
||||||
},
|
|
||||||
credit: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
creditInsurance: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
phone: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
mobile: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
street: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
countryFk: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
provinceFk: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
city: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
postcode: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
created: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
businessTypeFk: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
payMethodFk: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
sageTaxTypeFk: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
sageTransactionTypeFk: {
|
|
||||||
component: 'span',
|
|
||||||
props: () => {},
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
isActive: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.isActive ? 'check' : 'close',
|
|
||||||
color: prop.row.isActive ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
isVies: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.isVies ? 'check' : 'close',
|
|
||||||
color: prop.row.isVies ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
isTaxDataChecked: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.isTaxDataChecked ? 'check' : 'close',
|
|
||||||
color: prop.row.isTaxDataChecked ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
isEqualizated: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.isEqualizated ? 'check' : 'close',
|
|
||||||
color: prop.row.isEqualizated ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
isFreezed: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.isFreezed ? 'check' : 'close',
|
|
||||||
color: prop.row.isFreezed ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
hasToInvoice: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.hasToInvoice ? 'check' : 'close',
|
|
||||||
color: prop.row.hasToInvoice ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
hasToInvoiceByAddress: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.hasToInvoiceByAddress ? 'check' : 'close',
|
|
||||||
color: prop.row.hasToInvoiceByAddress ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
isToBeMailed: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.isToBeMailed ? 'check' : 'close',
|
|
||||||
color: prop.row.isToBeMailed ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
hasLcr: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.hasLcr ? 'check' : 'close',
|
|
||||||
color: prop.row.hasLcr ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
hasCoreVnl: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.hasCoreVnl ? 'check' : 'close',
|
|
||||||
color: prop.row.hasCoreVnl ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
hasSepaVnl: {
|
|
||||||
component: QIcon,
|
|
||||||
props: (prop) => ({
|
|
||||||
name: prop.row.hasSepaVnl ? 'check' : 'close',
|
|
||||||
color: prop.row.hasSepaVnl ? 'positive' : 'negative',
|
|
||||||
size: 'sm',
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
component: CustomerExtendedListActions,
|
|
||||||
props: (prop) => ({
|
|
||||||
id: prop.row.id,
|
|
||||||
}),
|
|
||||||
event: () => {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const columns = computed(() => [
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: '',
|
|
||||||
label: '',
|
|
||||||
name: 'customerStatus',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'id',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.id'),
|
|
||||||
name: 'id',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'name',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.name'),
|
|
||||||
name: 'name',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'socialName',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.socialName'),
|
|
||||||
name: 'socialName',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'fi',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.fi'),
|
|
||||||
name: 'fi',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'salesPerson',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.salesPersonFk'),
|
|
||||||
name: 'salesPersonFk',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'credit',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.credit'),
|
|
||||||
name: 'credit',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'creditInsurance',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.creditInsurance'),
|
|
||||||
name: 'creditInsurance',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'phone',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.phone'),
|
|
||||||
name: 'phone',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'mobile',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.mobile'),
|
|
||||||
name: 'mobile',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'street',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.street'),
|
|
||||||
name: 'street',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'country',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.countryFk'),
|
|
||||||
name: 'countryFk',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'province',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.provinceFk'),
|
|
||||||
name: 'provinceFk',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'city',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.city'),
|
|
||||||
name: 'city',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'postcode',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.postcode'),
|
|
||||||
name: 'postcode',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'email',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.email'),
|
|
||||||
name: 'email',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'created',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.created'),
|
|
||||||
name: 'created',
|
|
||||||
format: (value) => toDate(value),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'businessType',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.businessTypeFk'),
|
|
||||||
name: 'businessTypeFk',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'payMethod',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.payMethodFk'),
|
|
||||||
name: 'payMethodFk',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'sageTaxType',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.sageTaxTypeFk'),
|
|
||||||
name: 'sageTaxTypeFk',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'sageTransactionType',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.sageTransactionTypeFk'),
|
|
||||||
name: 'sageTransactionTypeFk',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'isActive',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.isActive'),
|
|
||||||
name: 'isActive',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'isVies',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.isVies'),
|
|
||||||
name: 'isVies',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'isTaxDataChecked',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.isTaxDataChecked'),
|
|
||||||
name: 'isTaxDataChecked',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'isEqualizated',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.isEqualizated'),
|
|
||||||
name: 'isEqualizated',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'isFreezed',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.isFreezed'),
|
|
||||||
name: 'isFreezed',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'hasToInvoice',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.hasToInvoice'),
|
|
||||||
name: 'hasToInvoice',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'hasToInvoiceByAddress',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.hasToInvoiceByAddress'),
|
|
||||||
name: 'hasToInvoiceByAddress',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'isToBeMailed',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.isToBeMailed'),
|
|
||||||
name: 'isToBeMailed',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'hasLcr',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.hasLcr'),
|
|
||||||
name: 'hasLcr',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'hasCoreVnl',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.hasCoreVnl'),
|
|
||||||
name: 'hasCoreVnl',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'left',
|
|
||||||
field: 'hasSepaVnl',
|
|
||||||
label: t('customer.extendedList.tableVisibleColumns.hasSepaVnl'),
|
|
||||||
name: 'hasSepaVnl',
|
|
||||||
format: () => ' ',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
align: 'right',
|
|
||||||
field: 'actions',
|
|
||||||
label: '',
|
|
||||||
name: 'actions',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const stopEventPropagation = (event, col) => {
|
|
||||||
if (!['id', 'salesPersonFk'].includes(col.name)) return;
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
};
|
|
||||||
|
|
||||||
const navigateToTravelId = (id) => router.push({ path: `/customer/${id}` });
|
|
||||||
|
|
||||||
const selectCustomerId = (id) => (selectedCustomerId.value = id);
|
|
||||||
|
|
||||||
const selectSalesPersonId = (id) => (selectedSalesPersonId.value = id);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<RightMenu>
|
|
||||||
<template #right-panel>
|
|
||||||
<CustomerExtendedListFilter
|
|
||||||
v-if="visibleColumns.length !== 0"
|
|
||||||
data-key="CustomerExtendedList"
|
|
||||||
:visible-columns="visibleColumns"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</RightMenu>
|
|
||||||
<VnSubToolbar>
|
|
||||||
<template #st-data>
|
|
||||||
<TableVisibleColumns
|
|
||||||
:all-columns="allColumnNames"
|
|
||||||
table-code="clientsDetail"
|
|
||||||
labels-traductions-path="customer.extendedList.tableVisibleColumns"
|
|
||||||
@on-config-saved="
|
|
||||||
visibleColumns = ['customerStatus', ...$event, 'actions']
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</VnSubToolbar>
|
|
||||||
<QPage class="column items-center q-pa-md">
|
|
||||||
<VnPaginate
|
|
||||||
data-key="CustomerExtendedList"
|
|
||||||
url="Clients/extendedListFilter"
|
|
||||||
auto-load
|
|
||||||
>
|
|
||||||
<template #body="{ rows }">
|
|
||||||
<div class="q-pa-md">
|
|
||||||
<QTable
|
|
||||||
:columns="columns"
|
|
||||||
:rows="rows"
|
|
||||||
class="full-width q-mt-md"
|
|
||||||
row-key="id"
|
|
||||||
:visible-columns="visibleColumns"
|
|
||||||
@row-click="(evt, row, id) => navigateToTravelId(row.id)"
|
|
||||||
>
|
|
||||||
<template #body-cell="{ col, value }">
|
|
||||||
<QTd @click="stopEventPropagation($event, col)">
|
|
||||||
{{ value }}
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #body-cell-customerStatus="props">
|
|
||||||
<QTd @click="stopEventPropagation($event, props.col)">
|
|
||||||
<component
|
|
||||||
:is="tableColumnComponents[props.col.name].component"
|
|
||||||
class="col-content"
|
|
||||||
v-bind="
|
|
||||||
tableColumnComponents[props.col.name].props(props)
|
|
||||||
"
|
|
||||||
@click="
|
|
||||||
tableColumnComponents[props.col.name].event(props)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
</component>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #body-cell-id="props">
|
|
||||||
<QTd @click="stopEventPropagation($event, props.col)">
|
|
||||||
<component
|
|
||||||
:is="tableColumnComponents[props.col.name].component"
|
|
||||||
class="col-content"
|
|
||||||
v-bind="
|
|
||||||
tableColumnComponents[props.col.name].props(props)
|
|
||||||
"
|
|
||||||
@click="
|
|
||||||
tableColumnComponents[props.col.name].event(props)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<CustomerDescriptorProxy :id="props.row.id" />
|
|
||||||
{{ props.row.id }}
|
|
||||||
</component>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #body-cell-salesPersonFk="props">
|
|
||||||
<QTd @click="stopEventPropagation($event, props.col)">
|
|
||||||
<component
|
|
||||||
v-if="props.row.salesPerson"
|
|
||||||
class="col-content"
|
|
||||||
:is="tableColumnComponents[props.col.name].component"
|
|
||||||
v-bind="
|
|
||||||
tableColumnComponents[props.col.name].props(props)
|
|
||||||
"
|
|
||||||
@click="
|
|
||||||
tableColumnComponents[props.col.name].event(props)
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<WorkerDescriptorProxy
|
|
||||||
:id="props.row.salesPersonFk"
|
|
||||||
/>
|
|
||||||
{{ props.row.salesPerson }}
|
|
||||||
</component>
|
|
||||||
<span class="col-content" v-else>-</span>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
<template #body-cell-actions="props">
|
|
||||||
<QTd @click="stopEventPropagation($event, props.col)">
|
|
||||||
<component
|
|
||||||
:is="tableColumnComponents[props.col.name].component"
|
|
||||||
class="col-content"
|
|
||||||
v-bind="
|
|
||||||
tableColumnComponents[props.col.name].props(props)
|
|
||||||
"
|
|
||||||
@click="
|
|
||||||
tableColumnComponents[props.col.name].event(props)
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</QTd>
|
|
||||||
</template>
|
|
||||||
</QTable>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</VnPaginate>
|
|
||||||
</QPage>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.col-content {
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 6px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,60 +0,0 @@
|
||||||
<script setup>
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
import CustomerSummary from '../Card/CustomerSummary.vue';
|
|
||||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
const router = useRouter();
|
|
||||||
const { viewSummary } = useSummaryDialog();
|
|
||||||
|
|
||||||
const $props = defineProps({
|
|
||||||
id: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const redirectToCreateView = () => {
|
|
||||||
router.push({
|
|
||||||
name: 'TicketList',
|
|
||||||
query: {
|
|
||||||
params: JSON.stringify({
|
|
||||||
clientFk: $props.id,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<QIcon
|
|
||||||
@click.stop="redirectToCreateView"
|
|
||||||
color="primary"
|
|
||||||
name="vn:ticket"
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
<QTooltip>
|
|
||||||
{{ t('Client ticket list') }}
|
|
||||||
</QTooltip>
|
|
||||||
</QIcon>
|
|
||||||
<QIcon
|
|
||||||
@click.stop="viewSummary($props.id, CustomerSummary)"
|
|
||||||
class="q-ml-md"
|
|
||||||
color="primary"
|
|
||||||
name="preview"
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
<QTooltip>
|
|
||||||
{{ t('Preview') }}
|
|
||||||
</QTooltip>
|
|
||||||
</QIcon>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<i18n>
|
|
||||||
es:
|
|
||||||
Client ticket list: Listado de tickets del cliente
|
|
||||||
Preview: Vista previa
|
|
||||||
</i18n>
|
|
|
@ -1,571 +0,0 @@
|
||||||
<script setup>
|
|
||||||
import { ref, computed } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
|
|
||||||
import FetchData from 'components/FetchData.vue';
|
|
||||||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
|
||||||
import VnSelect from 'components/common/VnSelect.vue';
|
|
||||||
import VnInput from 'src/components/common/VnInput.vue';
|
|
||||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
|
||||||
import { dateRange } from 'src/filters';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
dataKey: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
visibleColumns: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const clients = ref();
|
|
||||||
const workers = ref();
|
|
||||||
const countriesOptions = ref([]);
|
|
||||||
const provincesOptions = ref([]);
|
|
||||||
const paymethodsOptions = ref([]);
|
|
||||||
const businessTypesOptions = ref([]);
|
|
||||||
const sageTaxTypesOptions = ref([]);
|
|
||||||
const sageTransactionTypesOptions = ref([]);
|
|
||||||
|
|
||||||
const visibleColumnsSet = computed(() => new Set(props.visibleColumns));
|
|
||||||
|
|
||||||
const exprBuilder = (param, value) => {
|
|
||||||
switch (param) {
|
|
||||||
case 'created':
|
|
||||||
return {
|
|
||||||
'c.created': {
|
|
||||||
between: dateRange(value),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
case 'id':
|
|
||||||
case 'name':
|
|
||||||
case 'socialName':
|
|
||||||
case 'fi':
|
|
||||||
case 'credit':
|
|
||||||
case 'creditInsurance':
|
|
||||||
case 'phone':
|
|
||||||
case 'mobile':
|
|
||||||
case 'street':
|
|
||||||
case 'city':
|
|
||||||
case 'postcode':
|
|
||||||
case 'email':
|
|
||||||
case 'isActive':
|
|
||||||
case 'isVies':
|
|
||||||
case 'isTaxDataChecked':
|
|
||||||
case 'isEqualizated':
|
|
||||||
case 'isFreezed':
|
|
||||||
case 'hasToInvoice':
|
|
||||||
case 'hasToInvoiceByAddress':
|
|
||||||
case 'isToBeMailed':
|
|
||||||
case 'hasSepaVnl':
|
|
||||||
case 'hasLcr':
|
|
||||||
case 'hasCoreVnl':
|
|
||||||
case 'countryFk':
|
|
||||||
case 'provinceFk':
|
|
||||||
case 'salesPersonFk':
|
|
||||||
case 'businessTypeFk':
|
|
||||||
case 'payMethodFk':
|
|
||||||
case 'sageTaxTypeFk':
|
|
||||||
case 'sageTransactionTypeFk':
|
|
||||||
return { [`c.${param}`]: value };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const shouldRenderColumn = (colName) => {
|
|
||||||
return visibleColumnsSet.value.has(colName);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<FetchData
|
|
||||||
url="Clients"
|
|
||||||
:filter="{ where: { role: 'socialName' } }"
|
|
||||||
@on-fetch="(data) => (clients = data)"
|
|
||||||
auto-load
|
|
||||||
/>
|
|
||||||
<FetchData
|
|
||||||
url="Workers/activeWithInheritedRole"
|
|
||||||
:filter="{ where: { role: 'salesPerson' } }"
|
|
||||||
@on-fetch="(data) => (workers = data)"
|
|
||||||
auto-load
|
|
||||||
/>
|
|
||||||
<FetchData
|
|
||||||
url="Workers/activeWithInheritedRole"
|
|
||||||
:filter="{ where: { role: 'salesPerson' } }"
|
|
||||||
@on-fetch="(data) => (workers = data)"
|
|
||||||
auto-load
|
|
||||||
/>
|
|
||||||
<FetchData
|
|
||||||
url="Countries"
|
|
||||||
:filter="{ fields: ['id', 'country'], order: 'country ASC' }"
|
|
||||||
@on-fetch="(data) => (countriesOptions = data)"
|
|
||||||
auto-load
|
|
||||||
/>
|
|
||||||
<FetchData
|
|
||||||
ref="provincesFetchDataRef"
|
|
||||||
@on-fetch="(data) => (provincesOptions = data)"
|
|
||||||
auto-load
|
|
||||||
url="Provinces"
|
|
||||||
/>
|
|
||||||
<FetchData
|
|
||||||
url="Paymethods"
|
|
||||||
@on-fetch="(data) => (paymethodsOptions = data)"
|
|
||||||
auto-load
|
|
||||||
/>
|
|
||||||
<FetchData
|
|
||||||
url="BusinessTypes"
|
|
||||||
@on-fetch="(data) => (businessTypesOptions = data)"
|
|
||||||
auto-load
|
|
||||||
/>
|
|
||||||
<FetchData
|
|
||||||
url="SageTaxTypes"
|
|
||||||
auto-load
|
|
||||||
@on-fetch="(data) => (sageTaxTypesOptions = data)"
|
|
||||||
/>
|
|
||||||
<FetchData
|
|
||||||
url="sageTransactionTypes"
|
|
||||||
auto-load
|
|
||||||
@on-fetch="(data) => (sageTransactionTypesOptions = data)"
|
|
||||||
/>
|
|
||||||
<VnFilterPanel
|
|
||||||
:data-key="props.dataKey"
|
|
||||||
:search-button="true"
|
|
||||||
:expr-builder="exprBuilder"
|
|
||||||
>
|
|
||||||
<template #tags="{ tag, formatFn }">
|
|
||||||
<div class="q-gutter-x-xs">
|
|
||||||
<strong
|
|
||||||
>{{ t(`customer.extendedList.tableVisibleColumns.${tag.label}`) }}:
|
|
||||||
</strong>
|
|
||||||
<span>{{ formatFn(tag.value) }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #body="{ params, searchFn }">
|
|
||||||
<QItem v-if="shouldRenderColumn('id')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.id')"
|
|
||||||
v-model="params.id"
|
|
||||||
is-outlined
|
|
||||||
clearable
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('name')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.name')"
|
|
||||||
v-model="params.name"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<!-- <QItem class="q-mb-sm">
|
|
||||||
<QItemSection v-if="!clients">
|
|
||||||
<QSkeleton type="QInput" class="full-width" />
|
|
||||||
</QItemSection>
|
|
||||||
<QItemSection v-if="clients">
|
|
||||||
<VnSelect
|
|
||||||
:label="t('Social name')"
|
|
||||||
v-model="params.socialName"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:options="clients"
|
|
||||||
option-value="socialName"
|
|
||||||
option-label="socialName"
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
use-input
|
|
||||||
hide-selected
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
:input-debounce="0"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem> -->
|
|
||||||
<QItem v-if="shouldRenderColumn('fi')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.fi')"
|
|
||||||
v-model="params.fi"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('salesPersonFk')">
|
|
||||||
<QItemSection v-if="!workers">
|
|
||||||
<QSkeleton type="QInput" class="full-width" />
|
|
||||||
</QItemSection>
|
|
||||||
<QItemSection v-if="workers">
|
|
||||||
<VnSelect
|
|
||||||
:label="
|
|
||||||
t('customer.extendedList.tableVisibleColumns.salesPersonFk')
|
|
||||||
"
|
|
||||||
v-model="params.salesPersonFk"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:options="workers"
|
|
||||||
option-value="id"
|
|
||||||
option-label="name"
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
use-input
|
|
||||||
hide-selected
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
:input-debounce="0"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('credit')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.credit')"
|
|
||||||
v-model="params.credit"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('creditInsurance')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="
|
|
||||||
t('customer.extendedList.tableVisibleColumns.creditInsurance')
|
|
||||||
"
|
|
||||||
v-model="params.creditInsurance"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('phone')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.phone')"
|
|
||||||
v-model="params.phone"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('mobile')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.mobile')"
|
|
||||||
v-model="params.mobile"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('street')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.street')"
|
|
||||||
v-model="params.street"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('countryFk')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnSelect
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.countryFk')"
|
|
||||||
v-model="params.countryFk"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:options="countriesOptions"
|
|
||||||
option-value="id"
|
|
||||||
option-label="country"
|
|
||||||
map-options
|
|
||||||
hide-selected
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('provinceFk')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnSelect
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.provinceFk')"
|
|
||||||
v-model="params.provinceFk"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:options="provincesOptions"
|
|
||||||
option-value="id"
|
|
||||||
option-label="name"
|
|
||||||
map-options
|
|
||||||
hide-selected
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('city')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.city')"
|
|
||||||
v-model="params.city"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('postcode')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.postcode')"
|
|
||||||
v-model="params.postcode"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('email')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInput
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.email')"
|
|
||||||
v-model="params.email"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
|
|
||||||
<QItem v-if="shouldRenderColumn('created')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnInputDate
|
|
||||||
v-model="params.created"
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.created')"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
is-outlined
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('businessTypeFk')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnSelect
|
|
||||||
:label="
|
|
||||||
t('customer.extendedList.tableVisibleColumns.businessTypeFk')
|
|
||||||
"
|
|
||||||
v-model="params.businessTypeFk"
|
|
||||||
:options="businessTypesOptions"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
option-value="code"
|
|
||||||
option-label="description"
|
|
||||||
map-options
|
|
||||||
hide-selected
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('payMethodFk')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnSelect
|
|
||||||
:label="
|
|
||||||
t('customer.extendedList.tableVisibleColumns.payMethodFk')
|
|
||||||
"
|
|
||||||
v-model="params.payMethodFk"
|
|
||||||
:options="paymethodsOptions"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
option-value="id"
|
|
||||||
option-label="name"
|
|
||||||
map-options
|
|
||||||
hide-selected
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('sageTaxTypeFk')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnSelect
|
|
||||||
:label="
|
|
||||||
t('customer.extendedList.tableVisibleColumns.sageTaxTypeFk')
|
|
||||||
"
|
|
||||||
v-model="params.sageTaxTypeFk"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:options="sageTaxTypesOptions"
|
|
||||||
option-value="id"
|
|
||||||
option-label="vat"
|
|
||||||
map-options
|
|
||||||
hide-selected
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('sageTransactionTypeFk')">
|
|
||||||
<QItemSection>
|
|
||||||
<VnSelect
|
|
||||||
:label="
|
|
||||||
t(
|
|
||||||
'customer.extendedList.tableVisibleColumns.sageTransactionTypeFk'
|
|
||||||
)
|
|
||||||
"
|
|
||||||
v-model="params.sageTransactionTypeFk"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:options="sageTransactionTypesOptions"
|
|
||||||
option-value="id"
|
|
||||||
option-label="transaction"
|
|
||||||
map-options
|
|
||||||
hide-selected
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('isActive') || shouldRenderColumn('isVies')">
|
|
||||||
<QItemSection v-if="shouldRenderColumn('isActive')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.isActive"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.isActive')"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
<QItemSection v-if="shouldRenderColumn('isVies')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.isVies"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.isVies')"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem
|
|
||||||
v-if="
|
|
||||||
shouldRenderColumn('isEqualizated') ||
|
|
||||||
shouldRenderColumn('isTaxDataChecked')
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<QItemSection v-if="shouldRenderColumn('isTaxDataChecked')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.isTaxDataChecked"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="
|
|
||||||
t(
|
|
||||||
'customer.extendedList.tableVisibleColumns.isTaxDataChecked'
|
|
||||||
)
|
|
||||||
"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
<QItemSection v-if="shouldRenderColumn('isEqualizated')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.isEqualizated"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="
|
|
||||||
t('customer.extendedList.tableVisibleColumns.isEqualizated')
|
|
||||||
"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem
|
|
||||||
v-if="
|
|
||||||
shouldRenderColumn('hasToInvoice') || shouldRenderColumn('isFreezed')
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<QItemSection v-if="shouldRenderColumn('isFreezed')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.isFreezed"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.isFreezed')"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
<QItemSection v-if="shouldRenderColumn('hasToInvoice')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.hasToInvoice"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="
|
|
||||||
t('customer.extendedList.tableVisibleColumns.hasToInvoice')
|
|
||||||
"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem
|
|
||||||
v-if="
|
|
||||||
shouldRenderColumn('isToBeMailed') ||
|
|
||||||
shouldRenderColumn('hasToInvoiceByAddress')
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<QItemSection v-if="shouldRenderColumn('hasToInvoiceByAddress')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.hasToInvoiceByAddress"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="
|
|
||||||
t(
|
|
||||||
'customer.extendedList.tableVisibleColumns.hasToInvoiceByAddress'
|
|
||||||
)
|
|
||||||
"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
<QItemSection v-if="shouldRenderColumn('isToBeMailed')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.isToBeMailed"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="
|
|
||||||
t('customer.extendedList.tableVisibleColumns.isToBeMailed')
|
|
||||||
"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem
|
|
||||||
v-if="shouldRenderColumn('hasLcr') || shouldRenderColumn('hasCoreVnl')"
|
|
||||||
>
|
|
||||||
<QItemSection v-if="shouldRenderColumn('hasLcr')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.hasLcr"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.hasLcr')"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
<QItemSection v-if="shouldRenderColumn('hasCoreVnl')">
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.hasCoreVnl"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.hasCoreVnl')"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
<QItem v-if="shouldRenderColumn('hasSepaVnl')">
|
|
||||||
<QItemSection>
|
|
||||||
<QCheckbox
|
|
||||||
v-model="params.hasSepaVnl"
|
|
||||||
@update:model-value="searchFn()"
|
|
||||||
:label="t('customer.extendedList.tableVisibleColumns.hasSepaVnl')"
|
|
||||||
toggle-indeterminate
|
|
||||||
:false-value="undefined"
|
|
||||||
/>
|
|
||||||
</QItemSection>
|
|
||||||
</QItem>
|
|
||||||
|
|
||||||
<QSeparator />
|
|
||||||
</template>
|
|
||||||
</VnFilterPanel>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<i18n>
|
|
||||||
es:
|
|
||||||
Social name: Razón social
|
|
||||||
</i18n>
|
|
|
@ -9,6 +9,7 @@ import useNotify from 'src/composables/useNotify';
|
||||||
|
|
||||||
import VnRow from 'components/ui/VnRow.vue';
|
import VnRow from 'components/ui/VnRow.vue';
|
||||||
import VnInput from 'src/components/common/VnInput.vue';
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
import FetchData from 'src/components/FetchData.vue';
|
||||||
|
|
||||||
const { dialogRef } = useDialogPluginComponent();
|
const { dialogRef } = useDialogPluginComponent();
|
||||||
const { notify } = useNotify();
|
const { notify } = useNotify();
|
||||||
|
@ -19,15 +20,12 @@ const $props = defineProps({
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
userPasswords: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
promise: {
|
promise: {
|
||||||
type: Function,
|
type: Function,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const userPasswords = ref({});
|
||||||
|
|
||||||
const closeButton = ref(null);
|
const closeButton = ref(null);
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
@ -60,6 +58,11 @@ const onSubmit = async () => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<QDialog ref="dialogRef">
|
<QDialog ref="dialogRef">
|
||||||
|
<FetchData
|
||||||
|
@on-fetch="(data) => (userPasswords = data[0])"
|
||||||
|
auto-load
|
||||||
|
url="UserPasswords"
|
||||||
|
/>
|
||||||
<QCard class="q-pa-lg">
|
<QCard class="q-pa-lg">
|
||||||
<QCardSection>
|
<QCardSection>
|
||||||
<QForm @submit.prevent="onSubmit">
|
<QForm @submit.prevent="onSubmit">
|
||||||
|
@ -71,7 +74,7 @@ const onSubmit = async () => {
|
||||||
<QIcon name="close" size="sm" />
|
<QIcon name="close" size="sm" />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<VnRow class="row q-gutter-md q-mb-md">
|
<VnRow class="row q-gutter-md q-mb-md" style="flex-direction: column">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<VnInput
|
<VnInput
|
||||||
:label="t('New password')"
|
:label="t('New password')"
|
||||||
|
@ -84,11 +87,7 @@ const onSubmit = async () => {
|
||||||
<QTooltip>
|
<QTooltip>
|
||||||
{{
|
{{
|
||||||
t('customer.card.passwordRequirements', {
|
t('customer.card.passwordRequirements', {
|
||||||
length: $props.userPasswords.length,
|
...userPasswords,
|
||||||
nAlpha: $props.userPasswords.nAlpha,
|
|
||||||
nDigits: $props.userPasswords.nDigits,
|
|
||||||
nPunct: $props.userPasswords.nPunct,
|
|
||||||
nUpper: $props.userPasswords.nUpper,
|
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</QTooltip>
|
</QTooltip>
|
||||||
|
|
|
@ -162,6 +162,7 @@ const navigateToticketSummary = (id) => {
|
||||||
params: { id },
|
params: { id },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
const commonColumns = (col) => ['date', 'state', 'total'].includes(col);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -171,13 +172,13 @@ const navigateToticketSummary = (id) => {
|
||||||
auto-load
|
auto-load
|
||||||
url="Tickets"
|
url="Tickets"
|
||||||
/>
|
/>
|
||||||
|
<QCard class="vn-one q-py-sm flex justify-between">
|
||||||
<QTable
|
<QTable
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:pagination="{ rowsPerPage: 12 }"
|
:pagination="{ rowsPerPage: 12 }"
|
||||||
:rows="rows"
|
:rows="rows"
|
||||||
class="full-width q-mt-md"
|
class="full-width"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
v-if="rows?.length"
|
|
||||||
>
|
>
|
||||||
<template #body-cell="props">
|
<template #body-cell="props">
|
||||||
<QTd :props="props" @click="navigateToticketSummary(props.row.id)">
|
<QTd :props="props" @click="navigateToticketSummary(props.row.id)">
|
||||||
|
@ -185,19 +186,19 @@ const navigateToticketSummary = (id) => {
|
||||||
<component
|
<component
|
||||||
:is="tableColumnComponents[props.col.name].component"
|
:is="tableColumnComponents[props.col.name].component"
|
||||||
@click="tableColumnComponents[props.col.name].event(props)"
|
@click="tableColumnComponents[props.col.name].event(props)"
|
||||||
class="rounded-borders q-pa-sm"
|
class="rounded-borders"
|
||||||
v-bind="tableColumnComponents[props.col.name].props(props)"
|
v-bind="tableColumnComponents[props.col.name].props(props)"
|
||||||
>
|
>
|
||||||
<template
|
<template v-if="!commonColumns(props.col.name)">
|
||||||
v-if="
|
<span
|
||||||
props.col.name === 'id' ||
|
:class="{
|
||||||
props.col.name === 'nickname' ||
|
link:
|
||||||
props.col.name === 'agency' ||
|
|
||||||
props.col.name === 'route' ||
|
props.col.name === 'route' ||
|
||||||
props.col.name === 'packages'
|
props.col.name === 'nickname',
|
||||||
"
|
}"
|
||||||
>
|
>
|
||||||
{{ props.value }}
|
{{ props.value }}
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="props.col.name === 'date'">
|
<template v-if="props.col.name === 'date'">
|
||||||
<QBadge class="q-pa-sm" color="warning">
|
<QBadge class="q-pa-sm" color="warning">
|
||||||
|
@ -232,6 +233,7 @@ const navigateToticketSummary = (id) => {
|
||||||
</QTd>
|
</QTd>
|
||||||
</template>
|
</template>
|
||||||
</QTable>
|
</QTable>
|
||||||
|
</QCard>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<i18n>
|
<i18n>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import DepartmentDescriptor from './DepartmentDescriptor.vue';
|
import DepartmentDescriptor from './DepartmentDescriptor.vue';
|
||||||
import DepartmentSummaryDialog from './DepartmentSummaryDialog.vue';
|
import DepartmentSummary from './DepartmentSummary.vue';
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
id: {
|
id: {
|
||||||
|
@ -15,7 +15,7 @@ const $props = defineProps({
|
||||||
<DepartmentDescriptor
|
<DepartmentDescriptor
|
||||||
v-if="$props.id"
|
v-if="$props.id"
|
||||||
:id="$props.id"
|
:id="$props.id"
|
||||||
:summary="DepartmentSummaryDialog"
|
:summary="DepartmentSummary"
|
||||||
/>
|
/>
|
||||||
</QPopupProxy>
|
</QPopupProxy>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -32,6 +32,7 @@ onMounted(async () => {
|
||||||
:url="`Departments/${entityId}`"
|
:url="`Departments/${entityId}`"
|
||||||
class="full-width"
|
class="full-width"
|
||||||
style="max-width: 900px"
|
style="max-width: 900px"
|
||||||
|
module-name="Department"
|
||||||
>
|
>
|
||||||
<template #header="{ entity }">
|
<template #header="{ entity }">
|
||||||
<div>{{ entity.name }}</div>
|
<div>{{ entity.name }}</div>
|
||||||
|
|
|
@ -11,9 +11,9 @@ import VnInput from 'src/components/common/VnInput.vue';
|
||||||
import FetchedTags from 'components/ui/FetchedTags.vue';
|
import FetchedTags from 'components/ui/FetchedTags.vue';
|
||||||
import VnConfirm from 'components/ui/VnConfirm.vue';
|
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||||
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
|
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
|
||||||
|
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||||
|
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
|
||||||
import { toCurrency } from 'src/filters';
|
import { toCurrency } from 'src/filters';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import useNotify from 'src/composables/useNotify.js';
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
@ -22,7 +22,6 @@ const quasar = useQuasar();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const stateStore = useStateStore();
|
|
||||||
const { notify } = useNotify();
|
const { notify } = useNotify();
|
||||||
|
|
||||||
const rowsSelected = ref([]);
|
const rowsSelected = ref([]);
|
||||||
|
@ -82,6 +81,16 @@ const tableColumnComponents = computed(() => ({
|
||||||
},
|
},
|
||||||
event: getInputEvents,
|
event: getInputEvents,
|
||||||
},
|
},
|
||||||
|
printedStickers: {
|
||||||
|
component: VnInput,
|
||||||
|
props: {
|
||||||
|
type: 'number',
|
||||||
|
min: 0,
|
||||||
|
class: 'input-number',
|
||||||
|
dense: true,
|
||||||
|
},
|
||||||
|
event: getInputEvents,
|
||||||
|
},
|
||||||
weight: {
|
weight: {
|
||||||
component: VnInput,
|
component: VnInput,
|
||||||
props: {
|
props: {
|
||||||
|
@ -147,7 +156,7 @@ const entriesTableColumns = computed(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: t('entry.summary.item'),
|
label: t('entry.summary.item'),
|
||||||
field: 'id',
|
field: 'itemFk',
|
||||||
name: 'item',
|
name: 'item',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
|
@ -169,6 +178,12 @@ const entriesTableColumns = computed(() => {
|
||||||
name: 'stickers',
|
name: 'stickers',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: t('entry.buys.printedStickers'),
|
||||||
|
field: 'printedStickers',
|
||||||
|
name: 'printedStickers',
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: t('entry.summary.weight'),
|
label: t('entry.summary.weight'),
|
||||||
field: 'weight',
|
field: 'weight',
|
||||||
|
@ -216,7 +231,6 @@ const entriesTableColumns = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const copyOriginalRowsData = (rows) => {
|
const copyOriginalRowsData = (rows) => {
|
||||||
// el objetivo de esto es guardar los valores iniciales de todas las rows para evitar guardar cambios si la data no cambió al disparar los eventos
|
|
||||||
originalRowDataCopy.value = JSON.parse(JSON.stringify(rows));
|
originalRowDataCopy.value = JSON.parse(JSON.stringify(rows));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -297,7 +311,8 @@ const lockIconType = (groupingMode, mode) => {
|
||||||
auto-load
|
auto-load
|
||||||
@on-fetch="(data) => (packagingsOptions = data)"
|
@on-fetch="(data) => (packagingsOptions = data)"
|
||||||
/>
|
/>
|
||||||
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
|
<VnSubToolbar>
|
||||||
|
<template #st-actions>
|
||||||
<QBtnGroup push style="column-gap: 10px">
|
<QBtnGroup push style="column-gap: 10px">
|
||||||
<slot name="moreBeforeActions" />
|
<slot name="moreBeforeActions" />
|
||||||
<QBtn
|
<QBtn
|
||||||
|
@ -310,7 +325,8 @@ const lockIconType = (groupingMode, mode) => {
|
||||||
:title="t('globals.remove')"
|
:title="t('globals.remove')"
|
||||||
/>
|
/>
|
||||||
</QBtnGroup>
|
</QBtnGroup>
|
||||||
</Teleport>
|
</template>
|
||||||
|
</VnSubToolbar>
|
||||||
<VnPaginate
|
<VnPaginate
|
||||||
ref="entryBuysPaginateRef"
|
ref="entryBuysPaginateRef"
|
||||||
data-key="EntryBuys"
|
data-key="EntryBuys"
|
||||||
|
@ -386,19 +402,16 @@ const lockIconType = (groupingMode, mode) => {
|
||||||
</template>
|
</template>
|
||||||
<ItemDescriptorProxy
|
<ItemDescriptorProxy
|
||||||
v-if="col.name === 'item'"
|
v-if="col.name === 'item'"
|
||||||
:id="props.row.id"
|
:id="props.row.item.id"
|
||||||
/>
|
/>
|
||||||
</component>
|
</component>
|
||||||
</QTd>
|
</QTd>
|
||||||
</QTr>
|
</QTr>
|
||||||
<QTr no-hover>
|
<QTr no-hover class="full-width infoRow" style="column-span: all">
|
||||||
<QTd />
|
<QTd />
|
||||||
<QTd>
|
<QTd cols>
|
||||||
<span>{{ props.row.item.itemType.code }}</span>
|
<span>{{ props.row.item.itemType.code }}</span>
|
||||||
</QTd>
|
</QTd>
|
||||||
<QTd>
|
|
||||||
<span>{{ props.row.item.id }}</span>
|
|
||||||
</QTd>
|
|
||||||
<QTd>
|
<QTd>
|
||||||
<span>{{ props.row.item.size }}</span>
|
<span>{{ props.row.item.size }}</span>
|
||||||
</QTd>
|
</QTd>
|
||||||
|
@ -413,10 +426,6 @@ const lockIconType = (groupingMode, mode) => {
|
||||||
<FetchedTags :item="props.row.item" :max-length="5" />
|
<FetchedTags :item="props.row.item" :max-length="5" />
|
||||||
</QTd>
|
</QTd>
|
||||||
</QTr>
|
</QTr>
|
||||||
<!-- Esta última row es utilizada para agregar un espaciado y así marcar una diferencia visual entre los diferentes buys -->
|
|
||||||
<QTr v-if="props.rowIndex !== rows.length - 1" class="separation-row">
|
|
||||||
<QTd colspan="12" class="vn-table-separation-row" />
|
|
||||||
</QTr>
|
|
||||||
</template>
|
</template>
|
||||||
<template #item="props">
|
<template #item="props">
|
||||||
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
|
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
|
||||||
|
@ -466,11 +475,13 @@ const lockIconType = (groupingMode, mode) => {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.separation-row {
|
.q-table--horizontal-separator tbody tr:nth-child(odd) > td {
|
||||||
background-color: var(--vn-section-color) !important;
|
border-bottom-width: 0px;
|
||||||
|
border-top-width: 2px;
|
||||||
|
border-color: var(--vn-text-color);
|
||||||
}
|
}
|
||||||
.grid-style-transition {
|
.infoRow > td {
|
||||||
transition: transform 0.28s, background-color 0.28s;
|
color: var(--vn-label-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
@ -135,14 +135,19 @@ watch;
|
||||||
<template #icons>
|
<template #icons>
|
||||||
<QCardActions class="q-gutter-x-md">
|
<QCardActions class="q-gutter-x-md">
|
||||||
<QIcon
|
<QIcon
|
||||||
v-if="currentEntry.isExcludedFromAvailable"
|
v-if="currentEntry?.isExcludedFromAvailable"
|
||||||
name="vn:inventory"
|
name="vn:inventory"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="xs"
|
size="xs"
|
||||||
>
|
>
|
||||||
<QTooltip>{{ t('Inventory entry') }}</QTooltip>
|
<QTooltip>{{ t('Inventory entry') }}</QTooltip>
|
||||||
</QIcon>
|
</QIcon>
|
||||||
<QIcon v-if="currentEntry.isRaid" name="vn:net" color="primary" size="xs">
|
<QIcon
|
||||||
|
v-if="currentEntry?.isRaid"
|
||||||
|
name="vn:net"
|
||||||
|
color="primary"
|
||||||
|
size="xs"
|
||||||
|
>
|
||||||
<QTooltip>{{ t('Virtual entry') }}</QTooltip>
|
<QTooltip>{{ t('Virtual entry') }}</QTooltip>
|
||||||
</QIcon>
|
</QIcon>
|
||||||
</QCardActions>
|
</QCardActions>
|
||||||
|
|
|
@ -161,6 +161,7 @@ const fetchEntryBuys = async () => {
|
||||||
ref="summaryRef"
|
ref="summaryRef"
|
||||||
:url="`Entries/${entityId}/getEntry`"
|
:url="`Entries/${entityId}/getEntry`"
|
||||||
@on-fetch="(data) => setEntryData(data)"
|
@on-fetch="(data) => setEntryData(data)"
|
||||||
|
data-key="EntrySummary"
|
||||||
>
|
>
|
||||||
<template #header-left>
|
<template #header-left>
|
||||||
<router-link
|
<router-link
|
||||||
|
|
|
@ -16,14 +16,15 @@ import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||||
|
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
import { toDate, toCurrency } from 'src/filters';
|
import { toDate, toCurrency } from 'src/filters';
|
||||||
import { useSession } from 'composables/useSession';
|
// import { useSession } from 'composables/useSession';
|
||||||
import { dashIfEmpty } from 'src/filters';
|
import { dashIfEmpty } from 'src/filters';
|
||||||
import { useArrayData } from 'composables/useArrayData';
|
import { useArrayData } from 'composables/useArrayData';
|
||||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||||
|
import VnImg from 'src/components/ui/VnImg.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { getTokenMultimedia } = useSession();
|
// const { getTokenMultimedia } = useSession();
|
||||||
const token = getTokenMultimedia();
|
// const token = getTokenMultimedia();
|
||||||
const stateStore = useStateStore();
|
const stateStore = useStateStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
@ -167,7 +168,7 @@ const columns = computed(() => [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('globals.description'),
|
label: t('entry.latestBuys.description'),
|
||||||
field: 'description',
|
field: 'description',
|
||||||
name: 'description',
|
name: 'description',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
@ -653,6 +654,15 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
||||||
<EntryLatestBuysFilter data-key="EntryLatestBuys" />
|
<EntryLatestBuysFilter data-key="EntryLatestBuys" />
|
||||||
</template>
|
</template>
|
||||||
</RightMenu>
|
</RightMenu>
|
||||||
|
<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>
|
||||||
<QPage class="column items-center q-pa-md">
|
<QPage class="column items-center q-pa-md">
|
||||||
<QTable
|
<QTable
|
||||||
:rows="rows"
|
:rows="rows"
|
||||||
|
@ -686,14 +696,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
||||||
</template>
|
</template>
|
||||||
<template #body-cell-picture="{ row }">
|
<template #body-cell-picture="{ row }">
|
||||||
<QTd>
|
<QTd>
|
||||||
<QImg
|
<VnImg :id="row.itemFk" size="50x50" class="image" />
|
||||||
:src="`/api/Images/catalog/50x50/${row.itemFk}/download?access_token=${token}`"
|
|
||||||
spinner-color="primary"
|
|
||||||
:ratio="1"
|
|
||||||
height="50px"
|
|
||||||
width="50px"
|
|
||||||
class="image"
|
|
||||||
/>
|
|
||||||
</QTd>
|
</QTd>
|
||||||
</template>
|
</template>
|
||||||
<template #body-cell-itemFk="{ row }">
|
<template #body-cell-itemFk="{ row }">
|
||||||
|
|
|
@ -184,13 +184,6 @@ const suppliersOptions = ref([]);
|
||||||
@click="removeTag(index, params, searchFn)"
|
@click="removeTag(index, params, searchFn)"
|
||||||
/>
|
/>
|
||||||
</QItem>
|
</QItem>
|
||||||
<QItem class="q-mt-lg">
|
|
||||||
<QIcon
|
|
||||||
name="add_circle"
|
|
||||||
class="filter-icon"
|
|
||||||
@click="tagValues.push({})"
|
|
||||||
/>
|
|
||||||
</QItem>
|
|
||||||
</template>
|
</template>
|
||||||
</ItemsFilterPanel>
|
</ItemsFilterPanel>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -10,8 +10,10 @@ import FetchData from 'src/components/FetchData.vue';
|
||||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||||
import VnCurrency from 'src/components/common/VnCurrency.vue';
|
import VnCurrency from 'src/components/common/VnCurrency.vue';
|
||||||
import { toCurrency } from 'src/filters';
|
import { toCurrency } from 'src/filters';
|
||||||
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const { notify } = useNotify();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const arrayData = useArrayData();
|
const arrayData = useArrayData();
|
||||||
const invoiceIn = computed(() => arrayData.store.data);
|
const invoiceIn = computed(() => arrayData.store.data);
|
||||||
|
@ -69,6 +71,7 @@ const isNotEuro = (code) => code != 'EUR';
|
||||||
async function insert() {
|
async function insert() {
|
||||||
await axios.post('/InvoiceInDueDays/new', { id: +invoiceId });
|
await axios.post('/InvoiceInDueDays/new', { id: +invoiceId });
|
||||||
await invoiceInFormRef.value.reload();
|
await invoiceInFormRef.value.reload();
|
||||||
|
notify(t('globals.dataSaved'), 'positive');
|
||||||
}
|
}
|
||||||
const getTotalAmount = (rows) => rows.reduce((acc, { amount }) => acc + +amount, 0);
|
const getTotalAmount = (rows) => rows.reduce((acc, { amount }) => acc + +amount, 0);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -106,6 +106,7 @@ const ticketsColumns = ref([
|
||||||
ref="summary"
|
ref="summary"
|
||||||
:url="`InvoiceOuts/${entityId}/summary`"
|
:url="`InvoiceOuts/${entityId}/summary`"
|
||||||
:entity-id="entityId"
|
:entity-id="entityId"
|
||||||
|
data-key="InvoiceOutSummary"
|
||||||
>
|
>
|
||||||
<template #header="{ entity: { invoiceOut } }">
|
<template #header="{ entity: { invoiceOut } }">
|
||||||
<div>{{ invoiceOut.ref }} - {{ invoiceOut.client?.socialName }}</div>
|
<div>{{ invoiceOut.ref }} - {{ invoiceOut.client?.socialName }}</div>
|
||||||
|
|
|
@ -13,9 +13,9 @@ import ItemDescriptorImage from 'src/pages/Item/Card/ItemDescriptorImage.vue';
|
||||||
|
|
||||||
import { useState } from 'src/composables/useState';
|
import { useState } from 'src/composables/useState';
|
||||||
import useCardDescription from 'src/composables/useCardDescription';
|
import useCardDescription from 'src/composables/useCardDescription';
|
||||||
import { useSession } from 'src/composables/useSession';
|
|
||||||
import { getUrl } from 'src/composables/getUrl';
|
import { getUrl } from 'src/composables/getUrl';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { dashIfEmpty } from 'src/filters';
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
id: {
|
id: {
|
||||||
|
@ -41,14 +41,12 @@ const quasar = useQuasar();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { getTokenMultimedia } = useSession();
|
|
||||||
const state = useState();
|
const state = useState();
|
||||||
const user = state.getUser();
|
const user = state.getUser();
|
||||||
|
|
||||||
const entityId = computed(() => {
|
const entityId = computed(() => {
|
||||||
return $props.id || route.params.id;
|
return $props.id || route.params.id;
|
||||||
});
|
});
|
||||||
const image = ref(null);
|
|
||||||
const regularizeStockFormDialog = ref(null);
|
const regularizeStockFormDialog = ref(null);
|
||||||
const item = ref(null);
|
const item = ref(null);
|
||||||
const available = ref(null);
|
const available = ref(null);
|
||||||
|
@ -66,17 +64,10 @@ const warehouseFk = computed({
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await getItemAvatar();
|
|
||||||
warehouseFk.value = user.value.warehouseFk;
|
warehouseFk.value = user.value.warehouseFk;
|
||||||
salixUrl.value = await getUrl('');
|
salixUrl.value = await getUrl('');
|
||||||
});
|
});
|
||||||
|
|
||||||
const getItemAvatar = async () => {
|
|
||||||
const token = getTokenMultimedia();
|
|
||||||
const timeStamp = `timestamp=${Date.now()}`;
|
|
||||||
image.value = `/api/Images/catalog/200x200/${entityId.value}/download?access_token=${token}&${timeStamp}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const data = ref(useCardDescription());
|
const data = ref(useCardDescription());
|
||||||
const setData = (entity) => {
|
const setData = (entity) => {
|
||||||
if (!entity) return;
|
if (!entity) return;
|
||||||
|
@ -182,6 +173,10 @@ const openCloneDialog = async () => {
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</VnLv>
|
</VnLv>
|
||||||
|
<VnLv
|
||||||
|
:label="t('item.descriptor.producer')"
|
||||||
|
:value="dashIfEmpty(entity.subName)"
|
||||||
|
/>
|
||||||
<VnLv
|
<VnLv
|
||||||
v-if="entity.value5"
|
v-if="entity.value5"
|
||||||
:label="t('item.descriptor.color')"
|
:label="t('item.descriptor.color')"
|
||||||
|
|
|
@ -3,8 +3,7 @@ import { ref, onMounted } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import EditPictureForm from 'components/EditPictureForm.vue';
|
import EditPictureForm from 'components/EditPictureForm.vue';
|
||||||
|
import VnImg from 'src/components/ui/VnImg.vue';
|
||||||
import { useSession } from 'src/composables/useSession';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
const $props = defineProps({
|
const $props = defineProps({
|
||||||
|
@ -27,19 +26,12 @@ const $props = defineProps({
|
||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { getTokenMultimedia } = useSession();
|
|
||||||
|
|
||||||
const image = ref(null);
|
const image = ref(null);
|
||||||
const editPhotoFormDialog = ref(null);
|
const editPhotoFormDialog = ref(null);
|
||||||
const showEditPhotoForm = ref(false);
|
const showEditPhotoForm = ref(false);
|
||||||
const warehouseName = ref(null);
|
const warehouseName = ref(null);
|
||||||
|
|
||||||
const getItemAvatar = async () => {
|
|
||||||
const token = getTokenMultimedia();
|
|
||||||
const timeStamp = `timestamp=${Date.now()}`;
|
|
||||||
image.value = `/api/Images/catalog/200x200/${$props.entityId}/download?access_token=${token}&${timeStamp}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleEditPictureForm = () => {
|
const toggleEditPictureForm = () => {
|
||||||
showEditPhotoForm.value = !showEditPhotoForm.value;
|
showEditPhotoForm.value = !showEditPhotoForm.value;
|
||||||
};
|
};
|
||||||
|
@ -62,14 +54,17 @@ const getWarehouseName = async (warehouseFk) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
getItemAvatar();
|
|
||||||
getItemConfigs();
|
getItemConfigs();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handlePhotoUpdated = (evt = false) => {
|
||||||
|
image.value.reload(evt);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="relative-position">
|
<div class="relative-position">
|
||||||
<QImg :src="image" spinner-color="primary" style="min-height: 256px">
|
<VnImg ref="image" :id="$props.entityId" @refresh="handlePhotoUpdated(true)">
|
||||||
<template #error>
|
<template #error>
|
||||||
<div class="absolute-full picture text-center q-pa-md flex flex-center">
|
<div class="absolute-full picture text-center q-pa-md flex flex-center">
|
||||||
<div>
|
<div>
|
||||||
|
@ -82,7 +77,7 @@ onMounted(async () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</QImg>
|
</VnImg>
|
||||||
<QBtn
|
<QBtn
|
||||||
v-if="showEditButton"
|
v-if="showEditButton"
|
||||||
color="primary"
|
color="primary"
|
||||||
|
@ -97,7 +92,7 @@ onMounted(async () => {
|
||||||
collection="catalog"
|
collection="catalog"
|
||||||
:id="entityId"
|
:id="entityId"
|
||||||
@close-form="toggleEditPictureForm()"
|
@close-form="toggleEditPictureForm()"
|
||||||
@on-photo-uploaded="getItemAvatar()"
|
@on-photo-uploaded="handlePhotoUpdated"
|
||||||
/>
|
/>
|
||||||
</QDialog>
|
</QDialog>
|
||||||
</QBtn>
|
</QBtn>
|
||||||
|
|
|
@ -33,7 +33,6 @@ const user = state.getUser();
|
||||||
const fixedPrices = ref([]);
|
const fixedPrices = ref([]);
|
||||||
const fixedPricesOriginalData = ref([]);
|
const fixedPricesOriginalData = ref([]);
|
||||||
const warehousesOptions = ref([]);
|
const warehousesOptions = ref([]);
|
||||||
const itemsWithNameOptions = ref([]);
|
|
||||||
const rowsSelected = ref([]);
|
const rowsSelected = ref([]);
|
||||||
|
|
||||||
const exprBuilder = (param, value) => {
|
const exprBuilder = (param, value) => {
|
||||||
|
@ -371,12 +370,6 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
||||||
auto-load
|
auto-load
|
||||||
@on-fetch="(data) => onWarehousesFetched(data)"
|
@on-fetch="(data) => onWarehousesFetched(data)"
|
||||||
/>
|
/>
|
||||||
<FetchData
|
|
||||||
url="Items/withName"
|
|
||||||
:filter="{ fields: ['id', 'name'], order: 'id DESC' }"
|
|
||||||
auto-load
|
|
||||||
@on-fetch="(data) => (itemsWithNameOptions = data)"
|
|
||||||
/>
|
|
||||||
<RightMenu>
|
<RightMenu>
|
||||||
<template #right-panel>
|
<template #right-panel>
|
||||||
<ItemFixedPriceFilter
|
<ItemFixedPriceFilter
|
||||||
|
@ -419,7 +412,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
||||||
<template #body-cell-itemId="props">
|
<template #body-cell-itemId="props">
|
||||||
<QTd>
|
<QTd>
|
||||||
<VnSelect
|
<VnSelect
|
||||||
:options="itemsWithNameOptions"
|
url="Items/withName"
|
||||||
hide-selected
|
hide-selected
|
||||||
option-label="id"
|
option-label="id"
|
||||||
option-value="id"
|
option-value="id"
|
||||||
|
@ -562,7 +555,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
||||||
</QTd>
|
</QTd>
|
||||||
</template>
|
</template>
|
||||||
</QTable>
|
</QTable>
|
||||||
<QPageSticky v-if="rowsSelected.length > 0" :offset="[20, 20]">
|
<QPageSticky v-if="rowsSelected.length" :offset="[20, 20]">
|
||||||
<QBtn @click="openEditTableCellDialog()" color="primary" fab icon="edit" />
|
<QBtn @click="openEditTableCellDialog()" color="primary" fab icon="edit" />
|
||||||
<QTooltip>
|
<QTooltip>
|
||||||
{{ t('Edit fixed price(s)') }}
|
{{ t('Edit fixed price(s)') }}
|
||||||
|
|
|
@ -16,17 +16,15 @@ import ItemListFilter from './ItemListFilter.vue';
|
||||||
|
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
import { toDateFormat } from 'src/filters/date.js';
|
import { toDateFormat } from 'src/filters/date.js';
|
||||||
import { useSession } from 'composables/useSession';
|
|
||||||
import { dashIfEmpty } from 'src/filters';
|
import { dashIfEmpty } from 'src/filters';
|
||||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||||
import { useVnConfirm } from 'composables/useVnConfirm';
|
import { useVnConfirm } from 'composables/useVnConfirm';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||||
|
import VnImg from 'src/components/ui/VnImg.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { getTokenMultimedia } = useSession();
|
|
||||||
const token = getTokenMultimedia();
|
|
||||||
const stateStore = useStateStore();
|
const stateStore = useStateStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { viewSummary } = useSummaryDialog();
|
const { viewSummary } = useSummaryDialog();
|
||||||
|
@ -491,10 +489,9 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
||||||
</template>
|
</template>
|
||||||
<template #body-cell-picture="{ row }">
|
<template #body-cell-picture="{ row }">
|
||||||
<QTd>
|
<QTd>
|
||||||
<QImg
|
<VnImg
|
||||||
:src="`/api/Images/catalog/50x50/${row.id}/download?access_token=${token}`"
|
size="50x50"
|
||||||
spinner-color="primary"
|
:id="row.id"
|
||||||
:ratio="1"
|
|
||||||
height="50px"
|
height="50px"
|
||||||
width="50px"
|
width="50px"
|
||||||
class="image"
|
class="image"
|
||||||
|
|
|
@ -3,14 +3,15 @@ import { computed, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import VnInput from 'components/common/VnInput.vue';
|
|
||||||
import FetchData from 'components/FetchData.vue';
|
import FetchData from 'components/FetchData.vue';
|
||||||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||||
import VnSelect from 'components/common/VnSelect.vue';
|
import VnSelect from 'components/common/VnSelect.vue';
|
||||||
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
|
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
|
||||||
|
import { useValidator } from 'src/composables/useValidator';
|
||||||
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
dataKey: {
|
dataKey: {
|
||||||
|
@ -21,32 +22,34 @@ const props = defineProps({
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
tagValue: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const categoryList = ref(null);
|
const categoryList = ref(null);
|
||||||
const selectedCategoryFk = ref(null);
|
const selectedCategoryFk = ref(null);
|
||||||
const typeList = ref(null);
|
const typeList = ref(null);
|
||||||
const selectedTypeFk = ref(null);
|
const selectedTypeFk = ref(null);
|
||||||
|
const validationsStore = useValidator();
|
||||||
|
const selectedOrder = ref(null);
|
||||||
|
const selectedOrderField = ref(null);
|
||||||
|
const moreFields = ref([]);
|
||||||
|
const moreFieldsOrder = ref([]);
|
||||||
|
const createValue = (val, done) => {
|
||||||
|
if (val.length > 2) {
|
||||||
|
if (!tagOptions.value.includes(val)) {
|
||||||
|
done(tagOptions.value, 'add-unique');
|
||||||
|
}
|
||||||
|
tagValues.value.push({ value: val });
|
||||||
|
}
|
||||||
|
};
|
||||||
const resetCategory = () => {
|
const resetCategory = () => {
|
||||||
selectedCategoryFk.value = null;
|
selectedCategoryFk.value = null;
|
||||||
typeList.value = null;
|
typeList.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedOrder = ref(null);
|
|
||||||
const orderList = [
|
|
||||||
{ way: 'ASC', name: 'Ascendant' },
|
|
||||||
{ way: 'DESC', name: 'Descendant' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const selectedOrderField = ref(null);
|
|
||||||
const OrderFields = [
|
|
||||||
{ field: 'relevancy DESC, name', name: 'Relevancy', priority: 999 },
|
|
||||||
{ field: 'showOrder, price', name: 'Color and price', priority: 999 },
|
|
||||||
{ field: 'name', name: 'Name', priority: 999 },
|
|
||||||
{ field: 'price', name: 'Price', priority: 999 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const clearFilter = (key) => {
|
const clearFilter = (key) => {
|
||||||
if (key === 'categoryFk') {
|
if (key === 'categoryFk') {
|
||||||
resetCategory();
|
resetCategory();
|
||||||
|
@ -72,21 +75,6 @@ const loadTypes = async (categoryFk) => {
|
||||||
typeList.value = data;
|
typeList.value = data;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFilterInit = async ({ params }) => {
|
|
||||||
if (params.typeFk) {
|
|
||||||
selectedTypeFk.value = params.typeFk;
|
|
||||||
}
|
|
||||||
if (params.categoryFk) {
|
|
||||||
await loadTypes(params.categoryFk);
|
|
||||||
selectedCategoryFk.value = params.categoryFk;
|
|
||||||
}
|
|
||||||
if (params.orderBy) {
|
|
||||||
orderByParam.value = JSON.parse(params.orderBy);
|
|
||||||
selectedOrder.value = orderByParam.value?.way;
|
|
||||||
selectedOrderField.value = orderByParam.value?.field;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectedCategory = computed(() =>
|
const selectedCategory = computed(() =>
|
||||||
(categoryList.value || []).find(
|
(categoryList.value || []).find(
|
||||||
(category) => category?.id === selectedCategoryFk.value
|
(category) => category?.id === selectedCategoryFk.value
|
||||||
|
@ -109,10 +97,7 @@ function exprBuilder(param, value) {
|
||||||
|
|
||||||
const selectedTag = ref(null);
|
const selectedTag = ref(null);
|
||||||
const tagValues = ref([{}]);
|
const tagValues = ref([{}]);
|
||||||
const tagOptions = ref(null);
|
const tagOptions = ref([]);
|
||||||
const isButtonDisabled = computed(
|
|
||||||
() => !selectedTag.value || tagValues.value.some((item) => !item.value)
|
|
||||||
);
|
|
||||||
|
|
||||||
const applyTagFilter = (params, search) => {
|
const applyTagFilter = (params, search) => {
|
||||||
if (!tagValues.value?.length) {
|
if (!tagValues.value?.length) {
|
||||||
|
@ -125,12 +110,12 @@ const applyTagFilter = (params, search) => {
|
||||||
}
|
}
|
||||||
params.tagGroups.push(
|
params.tagGroups.push(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
values: tagValues.value,
|
values: tagValues.value.filter((obj) => Object.keys(obj).length > 0),
|
||||||
tagSelection: {
|
tagSelection: {
|
||||||
...selectedTag.value,
|
...selectedTag.value,
|
||||||
orgShowField: selectedTag.value.name,
|
orgShowField: selectedTag?.value?.name,
|
||||||
},
|
},
|
||||||
tagFk: selectedTag.value.tagFk,
|
tagFk: selectedTag?.value?.tagFk,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
search();
|
search();
|
||||||
|
@ -147,20 +132,52 @@ const removeTagChip = (selection, params, search) => {
|
||||||
search();
|
search();
|
||||||
};
|
};
|
||||||
|
|
||||||
const orderByParam = ref(null);
|
const onOrderChange = (value, params) => {
|
||||||
|
const tagObj = JSON.parse(params.orderBy);
|
||||||
const onOrderFieldChange = (value, params, search) => {
|
tagObj.way = value.name;
|
||||||
const orderBy = Object.assign({}, orderByParam.value, { field: value.field });
|
params.orderBy = JSON.stringify(tagObj);
|
||||||
params.orderBy = JSON.stringify(orderBy);
|
|
||||||
search();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onOrderChange = (value, params, search) => {
|
const onOrderFieldChange = (value, params) => {
|
||||||
const orderBy = Object.assign({}, orderByParam.value, { way: value.way });
|
const tagObj = JSON.parse(params.orderBy); // esto donde va
|
||||||
params.orderBy = JSON.stringify(orderBy);
|
const fields = {
|
||||||
search();
|
Relevancy: (value) => value + ' DESC, name',
|
||||||
|
ColorAndPrice: 'showOrder, price',
|
||||||
|
Name: 'name',
|
||||||
|
Price: 'price',
|
||||||
|
};
|
||||||
|
let tagField = fields[value];
|
||||||
|
if (!tagField) return;
|
||||||
|
|
||||||
|
if (typeof tagField === 'function') tagField = tagField(value);
|
||||||
|
tagObj.field = tagField;
|
||||||
|
params.orderBy = JSON.stringify(tagObj);
|
||||||
|
switch (value) {
|
||||||
|
case 'Relevancy':
|
||||||
|
tagObj.field = value + ' DESC, name';
|
||||||
|
params.orderBy = JSON.stringify(tagObj);
|
||||||
|
console.log('params: ', params);
|
||||||
|
break;
|
||||||
|
case 'ColorAndPrice':
|
||||||
|
tagObj.field = 'showOrder, price';
|
||||||
|
params.orderBy = JSON.stringify(tagObj);
|
||||||
|
console.log('params: ', params);
|
||||||
|
break;
|
||||||
|
case 'Name':
|
||||||
|
tagObj.field = 'name';
|
||||||
|
params.orderBy = JSON.stringify(tagObj);
|
||||||
|
console.log('params: ', params);
|
||||||
|
break;
|
||||||
|
case 'Price':
|
||||||
|
tagObj.field = 'price';
|
||||||
|
params.orderBy = JSON.stringify(tagObj);
|
||||||
|
console.log('params: ', params);
|
||||||
|
break;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const _moreFields = ['ASC', 'DESC'];
|
||||||
|
const _moreFieldsTypes = ['Relevancy', 'ColorAndPrice', 'Name', 'Price'];
|
||||||
const setCategoryList = (data) => {
|
const setCategoryList = (data) => {
|
||||||
categoryList.value = (data || [])
|
categoryList.value = (data || [])
|
||||||
.filter((category) => category.display)
|
.filter((category) => category.display)
|
||||||
|
@ -168,6 +185,8 @@ const setCategoryList = (data) => {
|
||||||
...category,
|
...category,
|
||||||
icon: `vn:${(category.icon || '').split('-')[1]}`,
|
icon: `vn:${(category.icon || '').split('-')[1]}`,
|
||||||
}));
|
}));
|
||||||
|
moreFields.value = useLang(_moreFields);
|
||||||
|
moreFieldsOrder.value = useLang(_moreFieldsTypes);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCategoryClass = (category, params) => {
|
const getCategoryClass = (category, params) => {
|
||||||
|
@ -175,6 +194,20 @@ const getCategoryClass = (category, params) => {
|
||||||
return 'active';
|
return 'active';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const useLang = (values) => {
|
||||||
|
const { models } = validationsStore;
|
||||||
|
const properties = models.Item?.properties || {};
|
||||||
|
return values.map((name) => {
|
||||||
|
let prop = properties[name];
|
||||||
|
const label = t(`params.${name}`);
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
type: prop ? prop.type : null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -182,9 +215,9 @@ const getCategoryClass = (category, params) => {
|
||||||
<VnFilterPanel
|
<VnFilterPanel
|
||||||
:data-key="props.dataKey"
|
:data-key="props.dataKey"
|
||||||
:hidden-tags="['orderFk', 'orderBy']"
|
:hidden-tags="['orderFk', 'orderBy']"
|
||||||
|
:unremovable-params="['orderFk', 'orderBy']"
|
||||||
:expr-builder="exprBuilder"
|
:expr-builder="exprBuilder"
|
||||||
:custom-tags="['tagGroups']"
|
:custom-tags="['tagGroups']"
|
||||||
@init="onFilterInit"
|
|
||||||
@remove="clearFilter"
|
@remove="clearFilter"
|
||||||
>
|
>
|
||||||
<template #tags="{ tag, formatFn }">
|
<template #tags="{ tag, formatFn }">
|
||||||
|
@ -274,40 +307,29 @@ const getCategoryClass = (category, params) => {
|
||||||
<QItem class="q-my-md">
|
<QItem class="q-my-md">
|
||||||
<QItemSection>
|
<QItemSection>
|
||||||
<VnSelect
|
<VnSelect
|
||||||
:label="t('params.order')"
|
:label="t('Order')"
|
||||||
v-model="selectedOrder"
|
v-model="selectedOrder"
|
||||||
:options="orderList || []"
|
:options="moreFields"
|
||||||
option-value="way"
|
option-label="label"
|
||||||
option-label="name"
|
|
||||||
dense
|
dense
|
||||||
outlined
|
outlined
|
||||||
rounded
|
rounded
|
||||||
:emit-value="false"
|
@update:model-value="(value) => onOrderChange(value, params)"
|
||||||
use-input
|
|
||||||
:is-clearable="false"
|
|
||||||
@update:model-value="
|
|
||||||
(value) => onOrderChange(value, params, searchFn)
|
|
||||||
"
|
|
||||||
/>
|
/>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
</QItem>
|
</QItem>
|
||||||
<QItem class="q-mb-md">
|
<QItem class="q-mb-md">
|
||||||
<QItemSection>
|
<QItemSection>
|
||||||
<VnSelect
|
<VnSelect
|
||||||
:label="t('params.order')"
|
:label="t('Order by')"
|
||||||
v-model="selectedOrderField"
|
v-model="selectedOrderField"
|
||||||
:options="OrderFields || []"
|
:options="moreFieldsOrder"
|
||||||
option-value="field"
|
option-label="label"
|
||||||
option-label="name"
|
option-value="name"
|
||||||
dense
|
dense
|
||||||
outlined
|
outlined
|
||||||
rounded
|
rounded
|
||||||
:emit-value="false"
|
@update:model-value="(value) => onOrderFieldChange(value, params)"
|
||||||
use-input
|
|
||||||
:is-clearable="false"
|
|
||||||
@update:model-value="
|
|
||||||
(value) => onOrderFieldChange(value, params, searchFn)
|
|
||||||
"
|
|
||||||
/>
|
/>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
</QItem>
|
</QItem>
|
||||||
|
@ -333,15 +355,30 @@ const getCategoryClass = (category, params) => {
|
||||||
:key="value"
|
:key="value"
|
||||||
class="q-mt-md filter-value"
|
class="q-mt-md filter-value"
|
||||||
>
|
>
|
||||||
<VnInput
|
<FetchData
|
||||||
v-if="selectedTag?.isFree"
|
v-if="selectedTag"
|
||||||
v-model="value.value"
|
:url="`Tags/${selectedTag}/filterValue`"
|
||||||
:label="t('params.value')"
|
limit="30"
|
||||||
is-outlined
|
auto-load
|
||||||
class="filter-input"
|
@on-fetch="(data) => (tagOptions = data)"
|
||||||
/>
|
/>
|
||||||
<VnSelect
|
<VnSelect
|
||||||
v-else
|
v-if="!selectedTag"
|
||||||
|
:label="t('params.value')"
|
||||||
|
v-model="value.value"
|
||||||
|
:options="tagValue || []"
|
||||||
|
option-value="value"
|
||||||
|
option-label="value"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
rounded
|
||||||
|
emit-value
|
||||||
|
use-input
|
||||||
|
class="filter-input"
|
||||||
|
@new-value="createValue"
|
||||||
|
/>
|
||||||
|
<VnSelect
|
||||||
|
v-else-if="selectedTag === 1"
|
||||||
:label="t('params.value')"
|
:label="t('params.value')"
|
||||||
v-model="value.value"
|
v-model="value.value"
|
||||||
:options="tagOptions || []"
|
:options="tagOptions || []"
|
||||||
|
@ -352,18 +389,18 @@ const getCategoryClass = (category, params) => {
|
||||||
rounded
|
rounded
|
||||||
emit-value
|
emit-value
|
||||||
use-input
|
use-input
|
||||||
:disable="!selectedTag"
|
class="filter-input"
|
||||||
|
@new-value="createValue"
|
||||||
|
/>
|
||||||
|
<VnInput
|
||||||
|
v-else
|
||||||
|
:label="t('params.value')"
|
||||||
|
v-model="value.value"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
rounded
|
||||||
class="filter-input"
|
class="filter-input"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FetchData
|
|
||||||
v-if="selectedTag && !selectedTag.isFree"
|
|
||||||
:url="`Tags/${selectedTag?.id}/filterValue`"
|
|
||||||
limit="30"
|
|
||||||
auto-load
|
|
||||||
@on-fetch="(data) => (tagOptions = data)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<QIcon
|
<QIcon
|
||||||
name="delete"
|
name="delete"
|
||||||
class="filter-icon"
|
class="filter-icon"
|
||||||
|
@ -388,7 +425,6 @@ const getCategoryClass = (category, params) => {
|
||||||
rounded
|
rounded
|
||||||
type="button"
|
type="button"
|
||||||
unelevated
|
unelevated
|
||||||
:disable="isButtonDisabled"
|
|
||||||
@click.stop="applyTagFilter(params, searchFn)"
|
@click.stop="applyTagFilter(params, searchFn)"
|
||||||
/>
|
/>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
|
@ -453,6 +489,12 @@ en:
|
||||||
tag: Tag
|
tag: Tag
|
||||||
value: Value
|
value: Value
|
||||||
order: Order
|
order: Order
|
||||||
|
ASC: Ascendant
|
||||||
|
DESC: Descendant
|
||||||
|
Relevancy: Relevancy
|
||||||
|
ColorAndPrice: Color and price
|
||||||
|
Name: Name
|
||||||
|
Price: Price
|
||||||
es:
|
es:
|
||||||
params:
|
params:
|
||||||
type: Tipo
|
type: Tipo
|
||||||
|
@ -460,6 +502,14 @@ es:
|
||||||
tag: Etiqueta
|
tag: Etiqueta
|
||||||
value: Valor
|
value: Valor
|
||||||
order: Orden
|
order: Orden
|
||||||
|
ASC: Ascendiente
|
||||||
|
DESC: Descendiente
|
||||||
|
Relevancy: Relevancia
|
||||||
|
ColorAndPrice: Color y precio
|
||||||
|
Name: Nombre
|
||||||
|
Price: Precio
|
||||||
|
Order: Orden
|
||||||
|
Order by: Ordenar por
|
||||||
Plant: Planta
|
Plant: Planta
|
||||||
Flower: Flor
|
Flower: Flor
|
||||||
Handmade: Confección
|
Handmade: Confección
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue