0
0
Fork 0

Merge branch 'dev' into 6772_reload_sections

This commit is contained in:
Javier Segarra 2024-05-16 12:04:11 +02:00
commit c345493034
308 changed files with 14321 additions and 4998 deletions

View File

@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- (Worker) => Se crea la sección Taquilla
- (General) => Se mantiene el filtro lateral en cualquier parte de la seccíon.
### Fixed ### Fixed
- (General) => Se vuelven a mostrar los parámetros en la url al aplicar un filtro - (General) => Se vuelven a mostrar los parámetros en la url al aplicar un filtro

View File

@ -1,6 +1,6 @@
{ {
"name": "salix-front", "name": "salix-front",
"version": "24.20.0", "version": "24.22.0",
"description": "Salix frontend", "description": "Salix frontend",
"productName": "Salix", "productName": "Salix",
"author": "Verdnatura", "author": "Verdnatura",

View File

@ -29,7 +29,7 @@ module.exports = configure(function (/* ctx */) {
// app boot file (/src/boot) // app boot file (/src/boot)
// --> boot files are part of "main.js" // --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli/boot-files // https://v2.quasar.dev/quasar-cli/boot-files
boot: ['i18n', 'axios', 'vnDate', 'validations', 'quasar.defaults'], boot: ['i18n', 'axios', 'vnDate', 'validations', 'quasar', 'quasar.defaults'],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
css: ['app.scss'], css: ['app.scss'],

View File

@ -3,17 +3,12 @@ import { reactive, ref, onMounted, nextTick } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import FormModelPopup from './FormModelPopup.vue'; import FormModelPopup from './FormModelPopup.vue';
const props = defineProps({ defineProps({ showEntityField: { type: Boolean, default: true } });
showEntityField: {
type: Boolean,
default: true,
},
});
const emit = defineEmits(['onDataSaved']); const emit = defineEmits(['onDataSaved']);
const { t } = useI18n(); const { t } = useI18n();
@ -26,7 +21,7 @@ const bankEntityFormData = reactive({
}); });
const countriesFilter = { const countriesFilter = {
fields: ['id', 'country', 'code'], fields: ['id', 'name', 'code'],
}; };
const countriesOptions = ref([]); const countriesOptions = ref([]);
@ -58,15 +53,12 @@ onMounted(async () => {
> >
<template #form-inputs="{ data, validate }"> <template #form-inputs="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput <VnInput
:label="t('name')" :label="t('name')"
v-model="data.name" v-model="data.name"
:required="true" :required="true"
:rules="validate('bankEntity.name')" :rules="validate('bankEntity.name')"
/> />
</div>
<div class="col">
<VnInput <VnInput
ref="bicInputRef" ref="bicInputRef"
:label="t('swift')" :label="t('swift')"
@ -74,16 +66,15 @@ onMounted(async () => {
:required="true" :required="true"
:rules="validate('bankEntity.bic')" :rules="validate('bankEntity.bic')"
/> />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">
<VnSelectFilter <VnSelect
:label="t('country')" :label="t('country')"
v-model="data.countryFk" v-model="data.countryFk"
:options="countriesOptions" :options="countriesOptions"
option-value="id" option-value="id"
option-label="country" option-label="name"
hide-selected hide-selected
:required="true" :required="true"
:rules="validate('bankEntity.countryFk')" :rules="validate('bankEntity.countryFk')"

View File

@ -52,9 +52,7 @@ onMounted(() => {
</span> </span>
<h1 class="title">{{ t('New department') }}</h1> <h1 class="title">{{ t('New department') }}</h1>
<VnRow class="row q-gutter-md q-mb-md" style="min-width: 250px"> <VnRow class="row q-gutter-md q-mb-md" style="min-width: 250px">
<div class="col">
<VnInput :label="t('Name')" v-model="data.name" /> <VnInput :label="t('Name')" v-model="data.name" />
</div>
</VnRow> </VnRow>
<div class="q-mt-lg row justify-end"> <div class="q-mt-lg row justify-end">
<QBtn <QBtn

View File

@ -5,7 +5,7 @@ import { useRouter } from 'vue-router';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import FormModelPopup from './FormModelPopup.vue'; import FormModelPopup from './FormModelPopup.vue';
import VnInputDate from './common/VnInputDate.vue'; import VnInputDate from './common/VnInputDate.vue';
@ -72,8 +72,7 @@ const onDataSaved = async (formData, requestResponse) => {
{{ t('Invoicing in progress...') }} {{ t('Invoicing in progress...') }}
</span> </span>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelectFilter
:label="t('Ticket')" :label="t('Ticket')"
:options="ticketsOptions" :options="ticketsOptions"
hide-selected hide-selected
@ -86,19 +85,15 @@ const onDataSaved = async (formData, requestResponse) => {
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
<QItemSection> <QItemSection>
<QItemLabel> #{{ scope.opt?.id }} </QItemLabel> <QItemLabel> #{{ scope.opt?.id }} </QItemLabel>
<QItemLabel caption>{{ <QItemLabel caption>{{ scope.opt?.nickname }}</QItemLabel>
scope.opt?.nickname
}}</QItemLabel>
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
</VnSelectFilter> </VnSelect>
</div>
<span class="row items-center" style="max-width: max-content">{{ <span class="row items-center" style="max-width: max-content">{{
t('Or') t('Or')
}}</span> }}</span>
<div class="col"> <VnSelect
<VnSelectFilter
:label="t('Client')" :label="t('Client')"
:options="clientsOptions" :options="clientsOptions"
hide-selected hide-selected
@ -107,14 +102,10 @@ const onDataSaved = async (formData, requestResponse) => {
v-model="data.clientFk" v-model="data.clientFk"
@update:model-value="data.ticketFk = null" @update:model-value="data.ticketFk = null"
/> />
</div>
<div class="col">
<VnInputDate :label="t('Max date')" v-model="data.maxShipped" /> <VnInputDate :label="t('Max date')" v-model="data.maxShipped" />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelectFilter
:label="t('Serial')" :label="t('Serial')"
:options="invoiceOutSerialsOptions" :options="invoiceOutSerialsOptions"
hide-selected hide-selected
@ -123,9 +114,7 @@ const onDataSaved = async (formData, requestResponse) => {
v-model="data.serial" v-model="data.serial"
:required="true" :required="true"
/> />
</div> <VnSelect
<div class="col">
<VnSelectFilter
:label="t('Area')" :label="t('Area')"
:options="taxAreasOptions" :options="taxAreasOptions"
hide-selected hide-selected
@ -134,7 +123,6 @@ const onDataSaved = async (formData, requestResponse) => {
v-model="data.taxArea" v-model="data.taxArea"
:required="true" :required="true"
/> />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<VnInput <VnInput

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import FormModelPopup from './FormModelPopup.vue'; import FormModelPopup from './FormModelPopup.vue';
@ -40,15 +40,12 @@ const onDataSaved = (dataSaved) => {
> >
<template #form-inputs="{ data, validate }"> <template #form-inputs="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput <VnInput
:label="t('Name')" :label="t('Name')"
v-model="data.name" v-model="data.name"
:rules="validate('city.name')" :rules="validate('city.name')"
/> />
</div> <VnSelect
<div class="col">
<VnSelectFilter
:label="t('Province')" :label="t('Province')"
:options="provincesOptions" :options="provincesOptions"
hide-selected hide-selected
@ -57,7 +54,6 @@ const onDataSaved = (dataSaved) => {
v-model="data.provinceFk" v-model="data.provinceFk"
:rules="validate('city.provinceFk')" :rules="validate('city.provinceFk')"
/> />
</div>
</VnRow> </VnRow>
</template> </template>
</FormModelPopup> </FormModelPopup>

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import CreateNewCityForm from './CreateNewCityForm.vue'; import CreateNewCityForm from './CreateNewCityForm.vue';
import CreateNewProvinceForm from './CreateNewProvinceForm.vue'; import CreateNewProvinceForm from './CreateNewProvinceForm.vue';
@ -30,18 +30,18 @@ const townsLocationOptions = ref([]);
const onDataSaved = (formData) => { const onDataSaved = (formData) => {
const newPostcode = { const newPostcode = {
...formData ...formData,
}; };
const townObject = townsLocationOptions.value.find( const townObject = townsLocationOptions.value.find(
({id}) => id === formData.townFk ({ id }) => id === formData.townFk
); );
newPostcode.town = townObject?.name; newPostcode.town = townObject?.name;
const provinceObject = provincesOptions.value.find( const provinceObject = provincesOptions.value.find(
({id}) => id === formData.provinceFk ({ id }) => id === formData.provinceFk
); );
newPostcode.province = provinceObject?.name; newPostcode.province = provinceObject?.name;
const countryObject = countriesOptions.value.find( const countryObject = countriesOptions.value.find(
({id}) => id === formData.countryFk ({ id }) => id === formData.countryFk
); );
newPostcode.country = countryObject?.country; newPostcode.country = countryObject?.country;
emit('onDataSaved', newPostcode); emit('onDataSaved', newPostcode);
@ -92,14 +92,11 @@ const onProvinceCreated = async ({ name }, formData) => {
> >
<template #form-inputs="{ data, validate }"> <template #form-inputs="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput <VnInput
:label="t('Postcode')" :label="t('Postcode')"
v-model="data.code" v-model="data.code"
:rules="validate('postcode.code')" :rules="validate('postcode.code')"
/> />
</div>
<div class="col">
<VnSelectDialog <VnSelectDialog
:label="t('City')" :label="t('City')"
:options="townsLocationOptions" :options="townsLocationOptions"
@ -111,15 +108,11 @@ const onProvinceCreated = async ({ name }, formData) => {
:roles-allowed-to-create="['deliveryAssistant']" :roles-allowed-to-create="['deliveryAssistant']"
> >
<template #form> <template #form>
<CreateNewCityForm <CreateNewCityForm @on-data-saved="onCityCreated($event, data)" />
@on-data-saved="onCityCreated($event, data)"
/>
</template> </template>
</VnSelectDialog> </VnSelectDialog>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-xl"> <VnRow class="row q-gutter-md q-mb-xl">
<div class="col">
<VnSelectDialog <VnSelectDialog
:label="t('Province')" :label="t('Province')"
:options="provincesOptions" :options="provincesOptions"
@ -134,21 +127,20 @@ const onProvinceCreated = async ({ name }, formData) => {
<CreateNewProvinceForm <CreateNewProvinceForm
@on-data-saved="onProvinceCreated($event, data)" @on-data-saved="onProvinceCreated($event, data)"
/> />
</template> </template> </VnSelectDialog
</VnSelectDialog> ></VnRow>
</div> <VnRow class="row q-gutter-md q-mb-xl"
<div class="col"> ><VnSelect
<VnSelectFilter
:label="t('Country')" :label="t('Country')"
:options="countriesOptions" :options="countriesOptions"
hide-selected hide-selected
option-label="country" option-label="name"
option-value="id" option-value="id"
v-model="data.countryFk" v-model="data.countryFk"
:rules="validate('postcode.countryFk')" :rules="validate('postcode.countryFk')"
/> />
</div> </VnRow </VnRow>
></template> </template>
</FormModelPopup> </FormModelPopup>
</template> </template>

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import FormModelPopup from './FormModelPopup.vue'; import FormModelPopup from './FormModelPopup.vue';
@ -40,15 +40,12 @@ const onDataSaved = (dataSaved) => {
> >
<template #form-inputs="{ data, validate }"> <template #form-inputs="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput <VnInput
:label="t('Name')" :label="t('Name')"
v-model="data.name" v-model="data.name"
:rules="validate('province.name')" :rules="validate('province.name')"
/> />
</div> <VnSelect
<div class="col">
<VnSelectFilter
:label="t('Autonomy')" :label="t('Autonomy')"
:options="autonomiesOptions" :options="autonomiesOptions"
hide-selected hide-selected
@ -57,7 +54,6 @@ const onDataSaved = (dataSaved) => {
v-model="data.autonomyFk" v-model="data.autonomyFk"
:rules="validate('province.autonomyFk')" :rules="validate('province.autonomyFk')"
/> />
</div>
</VnRow> </VnRow>
</template> </template>
</FormModelPopup> </FormModelPopup>

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import FormModelPopup from './FormModelPopup.vue'; import FormModelPopup from './FormModelPopup.vue';
@ -54,17 +54,13 @@ const onDataSaved = (dataSaved) => {
> >
<template #form-inputs="{ data, validate }"> <template #form-inputs="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput <VnInput
:label="t('Identifier')" :label="t('Identifier')"
v-model="data.thermographId" v-model="data.thermographId"
:required="true" :required="true"
:rules="validate('thermograph.id')" :rules="validate('thermograph.id')"
/> />
</div> <VnSelect
<div class="col">
<VnSelectFilter
:label="t('Model')" :label="t('Model')"
:options="thermographsModels" :options="thermographsModels"
hide-selected hide-selected
@ -74,11 +70,9 @@ const onDataSaved = (dataSaved) => {
:required="true" :required="true"
:rules="validate('thermograph.model')" :rules="validate('thermograph.model')"
/> />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-xl"> <VnRow class="row q-gutter-md q-mb-xl">
<div class="col"> <VnSelect
<VnSelectFilter
:label="t('Warehouse')" :label="t('Warehouse')"
:options="warehousesOptions" :options="warehousesOptions"
hide-selected hide-selected
@ -87,9 +81,7 @@ const onDataSaved = (dataSaved) => {
v-model="data.warehouseId" v-model="data.warehouseId"
:required="true" :required="true"
/> />
</div> <VnSelect
<div class="col">
<VnSelectFilter
:label="t('Temperature')" :label="t('Temperature')"
:options="temperaturesOptions" :options="temperaturesOptions"
hide-selected hide-selected
@ -98,7 +90,6 @@ const onDataSaved = (dataSaved) => {
v-model="data.temperatureFk" v-model="data.temperatureFk"
:required="true" :required="true"
/> />
</div>
</VnRow> </VnRow>
</template> </template>
</FormModelPopup> </FormModelPopup>

View File

@ -81,6 +81,7 @@ defineExpose({
hasChanges, hasChanges,
saveChanges, saveChanges,
getChanges, getChanges,
formData,
}); });
async function fetch(data) { async function fetch(data) {
@ -124,11 +125,16 @@ async function onSubmit() {
}); });
} }
isLoading.value = true; isLoading.value = true;
await saveChanges(); await saveChanges($props.saveFn ? formData.value : null);
} }
async function saveChanges(data) { async function saveChanges(data) {
if ($props.saveFn) return $props.saveFn(data, getChanges); if ($props.saveFn) {
$props.saveFn(data, getChanges);
isLoading.value = false;
hasChanges.value = false;
return;
}
const changes = data || getChanges(); const changes = data || getChanges();
try { try {
await axios.post($props.saveUrl || $props.url + '/crud', changes); await axios.post($props.saveUrl || $props.url + '/crud', changes);

View File

@ -2,7 +2,7 @@
import { reactive, computed, ref } from 'vue'; import { reactive, computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.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';
@ -246,16 +246,13 @@ const makeRequest = async () => {
<div class="column"> <div class="column">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QOptionGroup <QOptionGroup
:options="uploadMethodsOptions" :options="uploadMethodsOptions"
type="radio" type="radio"
v-model="uploadMethodSelected" v-model="uploadMethodSelected"
/> />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QFile <QFile
v-if="uploadMethodSelected === 'computer'" v-if="uploadMethodSelected === 'computer'"
ref="inputFileRef" ref="inputFileRef"
@ -289,18 +286,15 @@ const makeRequest = async () => {
@update:model-value="updatePhotoPreview($event)" @update:model-value="updatePhotoPreview($event)"
placeholder="https://" placeholder="https://"
/> />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelectFilter
:label="t('Orientation')" :label="t('Orientation')"
:options="viewportTypes" :options="viewportTypes"
hide-selected hide-selected
option-label="description" option-label="description"
v-model="viewportSelection" v-model="viewportSelection"
/> />
</div>
</VnRow> </VnRow>
<div class="q-mt-lg row justify-end"> <div class="q-mt-lg row justify-end">
<QBtn <QBtn

View File

@ -1,9 +1,12 @@
<script setup> <script setup>
import { ref, reactive } from 'vue'; import { ref, markRaw } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnRow from 'components/ui/VnRow.vue';
import { QCheckbox } from 'quasar';
import axios from 'axios'; import axios from 'axios';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
@ -28,11 +31,16 @@ const $props = defineProps({
const { t } = useI18n(); const { t } = useI18n();
const { notify } = useNotify(); const { notify } = useNotify();
const formData = reactive({ const inputs = {
field: null, input: markRaw(VnInput),
newValue: null, number: markRaw(VnInput),
}); date: markRaw(VnInputDate),
checkbox: markRaw(QCheckbox),
select: markRaw(VnSelect),
};
const newValue = ref(null);
const selectedField = ref(null);
const closeButton = ref(null); const closeButton = ref(null);
const isLoading = ref(false); const isLoading = ref(false);
@ -47,8 +55,8 @@ const submitData = async () => {
isLoading.value = true; isLoading.value = true;
const rowsToEdit = $props.rows.map((row) => ({ id: row.id, itemFk: row.itemFk })); const rowsToEdit = $props.rows.map((row) => ({ id: row.id, itemFk: row.itemFk }));
const payload = { const payload = {
field: formData.field, field: selectedField.value.field,
newValue: formData.newValue, newValue: newValue.value,
lines: rowsToEdit, lines: rowsToEdit,
}; };
@ -75,19 +83,20 @@ const closeForm = () => {
<span class="countLines">{{ ` ${rows.length} ` }}</span> <span class="countLines">{{ ` ${rows.length} ` }}</span>
<span class="title">{{ t('buy(s)') }}</span> <span class="title">{{ t('buy(s)') }}</span>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelectFilter
:label="t('Field to edit')" :label="t('Field to edit')"
:options="fieldsOptions" :options="fieldsOptions"
hide-selected hide-selected
option-label="label" option-label="label"
option-value="field" v-model="selectedField"
v-model="formData.field" />
<component
:is="inputs[selectedField?.component || 'input']"
v-bind="selectedField?.attrs || {}"
v-model="newValue"
:label="t('Value')"
style="width: 200px"
/> />
</div>
<div class="col">
<VnInput :label="t('Value')" v-model="formData.newValue" />
</div>
</VnRow> </VnRow>
<div class="q-mt-lg row justify-end"> <div class="q-mt-lg row justify-end">
<QBtn <QBtn

View File

@ -1,21 +1,26 @@
<script setup> <script setup>
import { ref, reactive, computed } from 'vue'; import { ref, reactive, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
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 VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue'; import VnSelect from 'components/common/VnSelect.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import axios from 'axios'; import axios from 'axios';
import { dashIfEmpty } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
const props = defineProps({
url: {
type: String,
required: true,
},
});
const emit = defineEmits(['itemSelected']); const emit = defineEmits(['itemSelected']);
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute();
const itemFilter = { const itemFilter = {
include: [ include: [
@ -73,7 +78,7 @@ const tableColumns = computed(() => [
{ {
label: t('entry.buys.color'), label: t('entry.buys.color'),
name: 'ink', name: 'ink',
field: 'inkName', field: (row) => row?.ink?.name,
align: 'left', align: 'left',
}, },
]); ]);
@ -100,7 +105,7 @@ const fetchResults = async () => {
} }
filter.where = where; filter.where = where;
const { data } = await axios.get(`Entries/${route.params.id}/lastItemBuys`, { const { data } = await axios.get(props.url, {
params: { filter: JSON.stringify(filter) }, params: { filter: JSON.stringify(filter) },
}); });
tableRows.value = data; tableRows.value = data;
@ -147,20 +152,9 @@ const selectItem = ({ id }) => {
</span> </span>
<h1 class="title">{{ t('Filter item') }}</h1> <h1 class="title">{{ t('Filter item') }}</h1>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnInput :label="t('entry.buys.name')" v-model="itemFilterParams.name" />
<VnInput <VnInput :label="t('entry.buys.size')" v-model="itemFilterParams.size" />
:label="t('entry.buys.name')" <VnSelect
v-model="itemFilterParams.name"
/>
</div>
<div class="col">
<VnInput
:label="t('entry.buys.size')"
v-model="itemFilterParams.size"
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('entry.buys.producer')" :label="t('entry.buys.producer')"
:options="producersOptions" :options="producersOptions"
hide-selected hide-selected
@ -168,9 +162,7 @@ const selectItem = ({ id }) => {
option-value="id" option-value="id"
v-model="itemFilterParams.producerFk" v-model="itemFilterParams.producerFk"
/> />
</div> <VnSelect
<div class="col">
<VnSelectFilter
:label="t('entry.buys.type')" :label="t('entry.buys.type')"
:options="ItemTypesOptions" :options="ItemTypesOptions"
hide-selected hide-selected
@ -178,9 +170,7 @@ const selectItem = ({ id }) => {
option-value="id" option-value="id"
v-model="itemFilterParams.typeFk" v-model="itemFilterParams.typeFk"
/> />
</div> <VnSelect
<div class="col">
<VnSelectFilter
:label="t('entry.buys.color')" :label="t('entry.buys.color')"
:options="InksOptions" :options="InksOptions"
hide-selected hide-selected
@ -188,7 +178,6 @@ const selectItem = ({ id }) => {
option-value="id" option-value="id"
v-model="itemFilterParams.inkFk" v-model="itemFilterParams.inkFk"
/> />
</div>
</VnRow> </VnRow>
<div class="q-mt-lg row justify-end"> <div class="q-mt-lg row justify-end">
<QBtn <QBtn

View File

@ -5,7 +5,7 @@ import { useI18n } from 'vue-i18n';
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 VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue'; import VnSelect from 'components/common/VnSelect.vue';
import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue';
import axios from 'axios'; import axios from 'axios';
@ -145,8 +145,7 @@ const selectTravel = ({ id }) => {
</span> </span>
<h1 class="title">{{ t('Filter travels') }}</h1> <h1 class="title">{{ t('Filter travels') }}</h1>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelectFilter
:label="t('entry.basicData.agency')" :label="t('entry.basicData.agency')"
:options="agenciesOptions" :options="agenciesOptions"
hide-selected hide-selected
@ -154,9 +153,7 @@ const selectTravel = ({ id }) => {
option-value="id" option-value="id"
v-model="travelFilterParams.agencyModeFk" v-model="travelFilterParams.agencyModeFk"
/> />
</div> <VnSelect
<div class="col">
<VnSelectFilter
:label="t('entry.basicData.warehouseOut')" :label="t('entry.basicData.warehouseOut')"
:options="warehousesOptions" :options="warehousesOptions"
hide-selected hide-selected
@ -164,9 +161,7 @@ const selectTravel = ({ id }) => {
option-value="id" option-value="id"
v-model="travelFilterParams.warehouseOutFk" v-model="travelFilterParams.warehouseOutFk"
/> />
</div> <VnSelect
<div class="col">
<VnSelectFilter
:label="t('entry.basicData.warehouseIn')" :label="t('entry.basicData.warehouseIn')"
:options="warehousesOptions" :options="warehousesOptions"
hide-selected hide-selected
@ -174,19 +169,14 @@ const selectTravel = ({ id }) => {
option-value="id" option-value="id"
v-model="travelFilterParams.warehouseInFk" v-model="travelFilterParams.warehouseInFk"
/> />
</div>
<div class="col">
<VnInputDate <VnInputDate
:label="t('entry.basicData.shipped')" :label="t('entry.basicData.shipped')"
v-model="travelFilterParams.shipped" v-model="travelFilterParams.shipped"
/> />
</div>
<div class="col">
<VnInputDate <VnInputDate
:label="t('entry.basicData.landed')" :label="t('entry.basicData.landed')"
v-model="travelFilterParams.landed" v-model="travelFilterParams.landed"
/> />
</div>
</VnRow> </VnRow>
<div class="q-mt-lg row justify-end"> <div class="q-mt-lg row justify-end">
<QBtn <QBtn

View File

@ -102,7 +102,7 @@ onMounted(async () => {
}); });
onBeforeRouteLeave((to, from, next) => { onBeforeRouteLeave((to, from, next) => {
if (hasChanges.value) if (hasChanges.value && $props.observeFormChanges)
quasar.dialog({ quasar.dialog({
component: VnConfirm, component: VnConfirm,
componentProps: { componentProps: {
@ -156,9 +156,12 @@ const startFormWatcher = () => {
async function fetch() { async function fetch() {
try { try {
const { data } = await axios.get($props.url, { let { data } = await axios.get($props.url, {
params: { filter: JSON.stringify($props.filter) }, params: { filter: JSON.stringify($props.filter) },
}); });
if (Array.isArray(data)) data = data[0] ?? {};
state.set($props.model, data); state.set($props.model, data);
originalData.value = data && JSON.parse(JSON.stringify(data)); originalData.value = data && JSON.parse(JSON.stringify(data));

View File

@ -6,7 +6,7 @@ import FormModel from 'components/FormModel.vue';
const emit = defineEmits(['onDataSaved']); const emit = defineEmits(['onDataSaved']);
const $props = defineProps({ defineProps({
title: { title: {
type: String, type: String,
default: '', default: '',
@ -76,14 +76,6 @@ defineExpose({
<p>{{ subtitle }}</p> <p>{{ subtitle }}</p>
<slot name="form-inputs" :data="data" :validate="validate" /> <slot name="form-inputs" :data="data" :validate="validate" />
<div class="q-mt-lg row justify-end"> <div class="q-mt-lg row justify-end">
<QBtn
:label="t('globals.save')"
:title="t('globals.save')"
type="submit"
color="primary"
:disabled="isLoading"
:loading="isLoading"
/>
<QBtn <QBtn
:label="t('globals.cancel')" :label="t('globals.cancel')"
:title="t('globals.cancel')" :title="t('globals.cancel')"
@ -96,6 +88,7 @@ defineExpose({
/> />
<QBtn <QBtn
:label="t('globals.save')" :label="t('globals.save')"
:title="t('globals.save')"
type="submit" type="submit"
color="primary" color="primary"
class="q-ml-sm" class="q-ml-sm"

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
const emit = defineEmits(['onSubmit']); const emit = defineEmits(['onSubmit']);
const $props = defineProps({ defineProps({
title: { title: {
type: String, type: String,
default: '', default: '',

View File

@ -0,0 +1,359 @@
<script setup>
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import VnInput from 'components/common/VnInput.vue';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelect from 'components/common/VnSelect.vue';
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
import axios from 'axios';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
customTags: {
type: Array,
default: () => [],
},
exprBuilder: {
type: Function,
default: null,
},
});
const itemCategories = ref([]);
const selectedCategoryFk = ref(null);
const selectedTypeFk = ref(null);
const itemTypesOptions = ref([]);
const suppliersOptions = ref([]);
const tagOptions = ref([]);
const tagValues = ref([]);
const categoryList = computed(() => {
return (itemCategories.value || [])
.filter((category) => category.display)
.map((category) => ({
...category,
icon: `vn:${(category.icon || '').split('-')[1]}`,
}));
});
const selectedCategory = computed(() =>
(itemCategories.value || []).find(
(category) => category?.id === selectedCategoryFk.value
)
);
const selectedType = computed(() => {
return (itemTypesOptions.value || []).find(
(type) => type?.id === selectedTypeFk.value
);
});
const selectCategory = async (params, categoryId, search) => {
if (params.categoryFk === categoryId) {
resetCategory(params);
search();
return;
}
selectedCategoryFk.value = categoryId;
params.categoryFk = categoryId;
await fetchItemTypes(categoryId);
search();
};
const resetCategory = (params) => {
selectedCategoryFk.value = null;
itemTypesOptions.value = null;
if (params) {
params.categoryFk = null;
params.typeFk = null;
}
};
const applyTags = (params, search) => {
params.tags = tagValues.value
.filter((tag) => tag.selectedTag && tag.value)
.map((tag) => ({
tagFk: tag.selectedTag.id,
tagName: tag.selectedTag.name,
value: tag.value,
}));
search();
};
const fetchItemTypes = async (id) => {
try {
const filter = {
fields: ['id', 'name', 'categoryFk'],
where: { categoryFk: id },
include: 'category',
order: 'name ASC',
};
const { data } = await axios.get('ItemTypes', {
params: { filter: JSON.stringify(filter) },
});
itemTypesOptions.value = data;
} catch (err) {
console.error('Error fetching item types', err);
}
};
const getCategoryClass = (category, params) => {
if (category.id === params?.categoryFk) {
return 'active';
}
};
const getSelectedTagValues = async (tag) => {
try {
tag.value = null;
const filter = {
fields: ['value'],
order: 'value ASC',
limit: 30,
};
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get(`Tags/${tag.selectedTag.id}/filterValue`, {
params,
});
tag.valueOptions = data;
} catch (err) {
console.error('Error getting selected tag values');
}
};
const removeTag = (index, params, search) => {
(tagValues.value || []).splice(index, 1);
applyTags(params, search);
};
</script>
<template>
<FetchData
url="ItemCategories"
limit="30"
auto-load
@on-fetch="(data) => (itemCategories = data)"
/>
<FetchData
url="Suppliers"
limit="30"
auto-load
:filter="{ fields: ['id', 'name', 'nickname'], order: 'name ASC', limit: 30 }"
@on-fetch="(data) => (suppliersOptions = data)"
/>
<FetchData
url="Tags"
:filter="{ fields: ['id', 'name', 'isFree'] }"
auto-load
limit="30"
@on-fetch="(data) => (tagOptions = data)"
/>
<VnFilterPanel
:data-key="props.dataKey"
:expr-builder="exprBuilder"
:custom-tags="customTags"
>
<template #tags="{ tag, formatFn }">
<strong v-if="tag.label === 'categoryFk'">
{{ t(selectedCategory?.name || '') }}
</strong>
<strong v-else-if="tag.label === 'typeFk'">
{{ t(selectedType?.name || '') }}
</strong>
<div v-else class="q-gutter-x-xs">
<strong>{{ t(`components.itemsFilterPanel.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #customTags="{ tags, params }">
<template v-for="tag in tags" :key="tag.label">
<VnFilterPanelChip
v-for="chip in tag.value"
:key="chip"
removable
@remove="removeTagChip(chip, params, searchFn)"
>
<div class="q-gutter-x-xs">
<strong>{{ chip.tagName }}: </strong>
<span>"{{ chip.value }}"</span>
</div>
</VnFilterPanelChip>
</template>
</template>
<template #body="{ params, searchFn }">
<QItem class="category-filter q-mt-md">
<QBtn
dense
flat
round
v-for="category in categoryList"
:key="category.name"
:class="['category', getCategoryClass(category, params)]"
:icon="category.icon"
@click="selectCategory(params, category.id, searchFn)"
>
<QTooltip>
{{ t(category.name) }}
</QTooltip>
</QBtn>
</QItem>
<QItem class="q-my-md">
<QItemSection>
<VnSelect
:label="t('components.itemsFilterPanel.typeFk')"
v-model="params.typeFk"
:options="itemTypesOptions"
option-value="id"
option-label="name"
dense
outlined
rounded
use-input
:disable="!selectedCategoryFk"
@update:model-value="
(value) => {
selectedTypeFk = value;
searchFn();
}
"
>
<template #option="{ itemProps, opt }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel>{{ opt.name }}</QItemLabel>
<QItemLabel caption>
{{ opt.categoryName }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QItemSection>
</QItem>
<QSeparator />
<slot name="body" :params="params" :search-fn="searchFn" />
<QItem
v-for="(value, index) in tagValues"
:key="value"
class="q-mt-md filter-value"
>
<QItemSection class="col">
<VnSelect
:label="t('components.itemsFilterPanel.tag')"
v-model="value.selectedTag"
:options="tagOptions"
option-label="name"
dense
outlined
rounded
:emit-value="false"
use-input
:is-clearable="false"
@update:model-value="getSelectedTagValues(value)"
/>
</QItemSection>
<QItemSection class="col">
<VnSelect
v-if="!value?.selectedTag?.isFree && value.valueOptions"
:label="t('components.itemsFilterPanel.value')"
v-model="value.value"
:options="value.valueOptions || []"
option-value="value"
option-label="value"
dense
outlined
rounded
emit-value
use-input
:disable="!value"
:is-clearable="false"
@update:model-value="applyTags(params, searchFn)"
/>
<VnInput
v-else
v-model="value.value"
:label="t('components.itemsFilterPanel.value')"
:disable="!value"
is-outlined
:is-clearable="false"
@keyup.enter="applyTags(params, searchFn)"
/>
</QItemSection>
<QIcon
name="delete"
class="fill-icon-on-hover q-px-xs"
color="primary"
size="sm"
@click="removeTag(index, params, searchFn)"
/>
</QItem>
<QItem class="q-mt-lg">
<QIcon
name="add_circle"
class="fill-icon-on-hover q-px-xs"
color="primary"
size="sm"
@click="tagValues.push({})"
/>
</QItem>
</template>
</VnFilterPanel>
</template>
<style lang="scss" scoped>
.category-filter {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
.category {
padding: 8px;
width: 60px;
height: 60px;
font-size: 1.4rem;
background-color: var(--vn-accent-color);
&.active {
background-color: $primary;
}
}
}
.filter-value {
display: flex;
align-items: center;
}
</style>
<i18n>
en:
params:
supplier: Supplier
from: From
to: To
active: Is active
visible: Is visible
floramondo: Is floramondo
salesPersonFk: Buyer
categoryFk: Category
es:
params:
supplier: Proveedor
from: Desde
to: Hasta
active: Activo
visible: Visible
floramondo: Floramondo
salesPersonFk: Comprador
categoryFk: Categoría
</i18n>

View File

@ -2,7 +2,7 @@
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import FormModelPopup from './FormModelPopup.vue'; import FormModelPopup from './FormModelPopup.vue';
@ -50,16 +50,15 @@ const onDataSaved = (data) => {
> >
<template #form-inputs="{ data }"> <template #form-inputs="{ data }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QInput <QInput
:label="t('Type the visible quantity')" :label="t('Type the visible quantity')"
v-model.number="data.quantity" v-model.number="data.quantity"
autofocus
/> />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">
<VnSelectFilter <VnSelect
:label="t('Warehouse')" :label="t('Warehouse')"
v-model="data.warehouseFk" v-model="data.warehouseFk"
:options="warehousesOptions" :options="warehousesOptions"

View File

@ -5,7 +5,7 @@ import { useRouter } from 'vue-router';
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 VnSelectFilter from 'components/common/VnSelectFilter.vue'; import VnSelect from 'components/common/VnSelect.vue';
import FormPopup from './FormPopup.vue'; import FormPopup from './FormPopup.vue';
import axios from 'axios'; import axios from 'axios';
@ -83,8 +83,7 @@ const transferInvoice = async () => {
> >
<template #form-inputs> <template #form-inputs>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelectFilter
:label="t('Client')" :label="t('Client')"
:options="clientsOptions" :options="clientsOptions"
hide-selected hide-selected
@ -103,10 +102,8 @@ const transferInvoice = async () => {
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
</VnSelectFilter> </VnSelect>
</div> <VnSelect
<div class="col">
<VnSelectFilter
:label="t('Rectificative type')" :label="t('Rectificative type')"
:options="rectificativeTypeOptions" :options="rectificativeTypeOptions"
hide-selected hide-selected
@ -115,11 +112,9 @@ const transferInvoice = async () => {
v-model="transferInvoiceParams.cplusRectificationTypeFk" v-model="transferInvoiceParams.cplusRectificationTypeFk"
:required="true" :required="true"
/> />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelectFilter
:label="t('Class')" :label="t('Class')"
:options="siiTypeInvoiceOutsOptions" :options="siiTypeInvoiceOutsOptions"
hide-selected hide-selected
@ -138,10 +133,8 @@ const transferInvoice = async () => {
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
</VnSelectFilter> </VnSelect>
</div> <VnSelect
<div class="col">
<VnSelectFilter
:label="t('Type')" :label="t('Type')"
:options="invoiceCorrectionTypesOptions" :options="invoiceCorrectionTypesOptions"
hide-selected hide-selected
@ -150,7 +143,6 @@ const transferInvoice = async () => {
v-model="transferInvoiceParams.invoiceCorrectionTypeFk" v-model="transferInvoiceParams.invoiceCorrectionTypeFk"
:required="true" :required="true"
/> />
</div>
</VnRow> </VnRow>
</template> </template>
</FormPopup> </FormPopup>

View File

@ -7,7 +7,7 @@ import axios from 'axios';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
import { useSession } from 'src/composables/useSession'; import { useSession } from 'src/composables/useSession';
import { localeEquivalence } from 'src/i18n/index'; import { localeEquivalence } from 'src/i18n/index';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
@ -172,42 +172,57 @@ function copyUserToken() {
<QSeparator inset class="q-mx-lg" /> <QSeparator inset class="q-mx-lg" />
<div class="col q-gutter-xs q-pa-md"> <div class="col q-gutter-xs q-pa-md">
<VnRow> <VnRow>
<VnSelectFilter <VnSelect
:label="t('components.userPanel.localWarehouse')" :label="t('components.userPanel.localWarehouse')"
v-model="user.localWarehouseFk" v-model="user.localWarehouseFk"
:options="warehousesData" :options="warehousesData"
option-label="name" option-label="name"
option-value="id" option-value="id"
/> input-debounce="0"
<VnSelectFilter
:label="t('components.userPanel.localBank')"
hide-selected hide-selected
/>
<VnSelect
:label="t('components.userPanel.localBank')"
v-model="user.localBankFk" v-model="user.localBankFk"
:options="accountBankData" :options="accountBankData"
option-label="bank" option-label="bank"
option-value="id" option-value="id"
></VnSelectFilter> input-debounce="0"
hide-selected
>
<template #option="{ itemProps, opt }">
<QItem v-bind="itemProps">
<QItemSection>
<QItemLabel>
{{ `${opt.id}: ${opt.bank}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnSelectFilter <VnSelect
:label="t('components.userPanel.localCompany')" :label="t('components.userPanel.localCompany')"
hide-selected hide-selected
v-model="user.companyFk" v-model="user.localCompanyFk"
:options="companiesData" :options="companiesData"
option-label="code" option-label="code"
option-value="id" option-value="id"
input-debounce="0"
/> />
<VnSelectFilter <VnSelect
:label="t('components.userPanel.userWarehouse')" :label="t('components.userPanel.userWarehouse')"
hide-selected hide-selected
v-model="user.warehouseFk" v-model="user.warehouseFk"
:options="warehousesData" :options="warehousesData"
option-label="name" option-label="name"
option-value="id" option-value="id"
input-debounce="0"
/> />
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnSelectFilter <VnSelect
:label="t('components.userPanel.userCompany')" :label="t('components.userPanel.userCompany')"
hide-selected hide-selected
v-model="user.companyFk" v-model="user.companyFk"
@ -215,6 +230,8 @@ function copyUserToken() {
option-label="code" option-label="code"
option-value="id" option-value="id"
style="flex: 0" style="flex: 0"
dense
input-debounce="0"
/> />
</VnRow> </VnRow>
</div> </div>

View File

@ -0,0 +1,55 @@
<script setup>
import { ref, onMounted, useSlots } from 'vue';
import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore';
const slots = useSlots();
const hasContent = ref(false);
const rightPanel = ref(null);
onMounted(() => {
rightPanel.value = document.querySelector('#right-panel');
if (rightPanel.value.childNodes.length) hasContent.value = true;
// Check if there's content to display
const observer = new MutationObserver(() => {
hasContent.value = rightPanel.value.childNodes.length;
});
if (rightPanel.value)
observer.observe(rightPanel.value, {
subtree: true,
childList: true,
attributes: true,
});
if (!slots['right-panel'] && !hasContent.value) stateStore.rightDrawer = false;
});
const { t } = useI18n();
const stateStore = useStateStore();
</script>
<template>
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
v-if="hasContent || $slots['right-panel']"
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<div id="right-panel"></div>
<slot v-if="!hasContent" name="right-panel" />
</QScrollArea>
</QDrawer>
</template>

View File

@ -29,10 +29,12 @@ async function confirm() {
const response = { address: address.value }; const response = { address: address.value };
if (props.promise) { if (props.promise) {
isLoading.value = true; isLoading.value = true;
const { address: _address, ...restData } = props.data;
try { try {
Object.assign(response, restData); const dataCopy = JSON.parse(JSON.stringify({ ...props.data }));
delete dataCopy.address;
Object.assign(response, dataCopy);
await props.promise(response); await props.promise(response);
} finally { } finally {
isLoading.value = false; isLoading.value = false;

View File

@ -0,0 +1,90 @@
<script setup>
import { onBeforeMount, computed, watchEffect } from 'vue';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import { useArrayData } from 'src/composables/useArrayData';
import { useStateStore } from 'stores/useStateStore';
import useCardSize from 'src/composables/useCardSize';
import VnSubToolbar from '../ui/VnSubToolbar.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
import LeftMenu from 'components/LeftMenu.vue';
import RightMenu from 'components/common/RightMenu.vue';
const props = defineProps({
dataKey: { type: String, required: true },
baseUrl: { type: String, default: undefined },
customUrl: { type: String, default: undefined },
filter: { type: Object, default: () => {} },
descriptor: { type: Object, required: true },
filterPanel: { type: Object, default: undefined },
searchDataKey: { type: String, default: undefined },
searchUrl: { type: String, default: undefined },
searchbarLabel: { type: String, default: '' },
searchbarInfo: { type: String, default: '' },
});
const stateStore = useStateStore();
const route = useRoute();
const url = computed(() => {
if (props.baseUrl) return `${props.baseUrl}/${route.params.id}`;
return props.customUrl;
});
const arrayData = useArrayData(props.dataKey, {
url: url.value,
filter: props.filter,
});
onBeforeMount(async () => {
if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id };
await arrayData.fetch({ append: false });
});
if (props.baseUrl) {
onBeforeRouteUpdate(async (to, from) => {
if (to.params.id !== from.params.id) {
arrayData.store.url = `${props.baseUrl}/${route.params.id}`;
await arrayData.fetch({ append: false });
}
});
}
watchEffect(() => {
if (Array.isArray(arrayData.store.data))
arrayData.store.data = arrayData.store.data[0];
});
</script>
<template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar" v-if="props.searchDataKey">
<slot name="searchbar">
<VnSearchbar
:data-key="props.searchDataKey"
:url="props.searchUrl"
:label="props.searchbarLabel"
:info="props.searchbarInfo"
/>
</slot>
</Teleport>
<slot v-else name="searchbar" />
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit">
<component :is="descriptor" />
<QSeparator />
<LeftMenu source="card" />
</QScrollArea>
</QDrawer>
<RightMenu>
<template #right-panel v-if="props.filterPanel">
<component :is="props.filterPanel" :data-key="props.searchDataKey" />
</template>
</RightMenu>
</template>
<QPageContainer>
<QPage>
<VnSubToolbar />
<div :class="[useCardSize(), $attrs.class]">
<RouterView />
</div>
</QPage>
</QPageContainer>
</template>

View File

@ -6,7 +6,7 @@ import axios from 'axios';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import FormModelPopup from 'components/FormModelPopup.vue'; import FormModelPopup from 'components/FormModelPopup.vue';
@ -123,7 +123,7 @@ function addDefaultData(data) {
<div class="q-gutter-y-ms"> <div class="q-gutter-y-ms">
<VnRow> <VnRow>
<VnInput :label="t('globals.reference')" v-model="dms.reference" /> <VnInput :label="t('globals.reference')" v-model="dms.reference" />
<VnSelectFilter <VnSelect
:label="t('globals.company')" :label="t('globals.company')"
v-model="dms.companyFk" v-model="dms.companyFk"
:options="companies" :options="companies"
@ -133,7 +133,7 @@ function addDefaultData(data) {
/> />
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnSelectFilter <VnSelect
:label="t('globals.warehouse')" :label="t('globals.warehouse')"
v-model="dms.warehouseFk" v-model="dms.warehouseFk"
:options="warehouses" :options="warehouses"
@ -141,7 +141,7 @@ function addDefaultData(data) {
option-label="name" option-label="name"
input-debounce="0" input-debounce="0"
/> />
<VnSelectFilter <VnSelect
:label="t('globals.type')" :label="t('globals.type')"
v-model="dms.dmsTypeFk" v-model="dms.dmsTypeFk"
:options="dmsTypes" :options="dmsTypes"

View File

@ -52,6 +52,13 @@ const focus = () => {
defineExpose({ defineExpose({
focus, focus,
}); });
const inputRules = [
(val) => {
const { min } = vnInputRef.value.$attrs;
if (min >= 0) if (Math.floor(val) < min) return t('inputMin', { value: min });
},
];
</script> </script>
<template> <template>
@ -68,12 +75,15 @@ defineExpose({
:class="{ required: $attrs.required }" :class="{ required: $attrs.required }"
@keyup.enter="onEnterPress()" @keyup.enter="onEnterPress()"
:clearable="false" :clearable="false"
:rules="inputRules"
:lazy-rules="true"
hide-bottom-space
> >
<template v-if="$slots.prepend" #prepend> <template v-if="$slots.prepend" #prepend>
<slot name="prepend" /> <slot name="prepend" />
</template> </template>
<template #append> <template #append v-if="!!$attrs.disabled">
<slot name="append" v-if="$slots.append" /> <slot name="append" v-if="$slots.append" />
<QIcon <QIcon
name="close" name="close"
@ -85,3 +95,9 @@ defineExpose({
</QInput> </QInput>
</div> </div>
</template> </template>
<i18n>
en:
inputMin: Must be more than {value}
es:
inputMin: Debe ser mayor a {value}
</i18n>

View File

@ -15,6 +15,10 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
emitDateFormat: {
type: Boolean,
default: false,
},
}); });
const hover = ref(false); const hover = ref(false);
@ -37,7 +41,10 @@ const value = computed({
return props.modelValue; return props.modelValue;
}, },
set(value) { set(value) {
emit('update:modelValue', joinDateAndTime(value, time.value)); emit(
'update:modelValue',
props.emitDateFormat ? new Date(value) : joinDateAndTime(value, time.value)
);
}, },
}); });

View File

@ -12,7 +12,7 @@ import { useValidator } from 'src/composables/useValidator';
import VnAvatar from '../ui/VnAvatar.vue'; import VnAvatar from '../ui/VnAvatar.vue';
import VnJsonValue from '../common/VnJsonValue.vue'; import VnJsonValue from '../common/VnJsonValue.vue';
import FetchData from '../FetchData.vue'; import FetchData from '../FetchData.vue';
import VnSelectFilter from './VnSelectFilter.vue'; import VnSelect from './VnSelect.vue';
import VnUserLink from '../ui/VnUserLink.vue'; import VnUserLink from '../ui/VnUserLink.vue';
const stateStore = useStateStore(); const stateStore = useStateStore();
@ -421,12 +421,13 @@ setLogTree();
> >
<div class="timeline"> <div class="timeline">
<div class="user-avatar"> <div class="user-avatar">
<VnUserLink :worker-id="userLog.user.id"> <VnUserLink :worker-id="userLog?.user?.id">
<template #link> <template #link>
<VnAvatar <VnAvatar
:class="{ 'cursor-pointer': userLog.user.id }" :class="{ 'cursor-pointer': userLog?.user?.id }"
:worker-id="userLog.user.id" :worker-id="userLog?.user?.id"
:title="userLog.user.nickname" :title="userLog?.user?.nickname"
:show-letter="!userLog?.user"
size="lg" size="lg"
/> />
</template> </template>
@ -659,7 +660,7 @@ setLogTree();
</QInput> </QInput>
</QItem> </QItem>
<QItem> <QItem>
<VnSelectFilter <VnSelect
class="full-width" class="full-width"
:label="t('globals.entity')" :label="t('globals.entity')"
v-model="selectedFilters.changedModel" v-model="selectedFilters.changedModel"
@ -689,7 +690,7 @@ setLogTree();
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
<QItemSection v-if="workers && userRadio !== null"> <QItemSection v-if="workers && userRadio !== null">
<VnSelectFilter <VnSelect
class="full-width" class="full-width"
:label="t('globals.user')" :label="t('globals.user')"
v-model="userSelect" v-model="userSelect"
@ -713,7 +714,7 @@ setLogTree();
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
</VnSelectFilter> </VnSelect>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mt-sm"> <QItem class="q-mt-sm">

View File

@ -57,7 +57,7 @@ const $props = defineProps({
}); });
const { t } = useI18n(); const { t } = useI18n();
const requiredFieldRule = (val) => !!val || t('globals.fieldRequired'); const requiredFieldRule = (val) => val ?? t('globals.fieldRequired');
const { optionLabel, optionValue, options, modelValue } = toRefs($props); const { optionLabel, optionValue, options, modelValue } = toRefs($props);
const myOptions = ref([]); const myOptions = ref([]);
@ -167,8 +167,10 @@ watch(modelValue, (newValue) => {
hide-selected hide-selected
fill-input fill-input
ref="vnSelectRef" ref="vnSelectRef"
lazy-rules
:class="{ required: $attrs.required }" :class="{ required: $attrs.required }"
:rules="$attrs.required ? [requiredFieldRule] : null" :rules="$attrs.required ? [requiredFieldRule] : null"
virtual-scroll-slice-size="options.length"
> >
<template v-if="isClearable" #append> <template v-if="isClearable" #append>
<QIcon <QIcon

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import { useRole } from 'src/composables/useRole'; import { useRole } from 'src/composables/useRole';
@ -52,7 +52,7 @@ const toggleForm = () => {
</script> </script>
<template> <template>
<VnSelectFilter v-model="value" :options="options" v-bind="$attrs"> <VnSelect v-model="value" :options="options" v-bind="$attrs">
<template v-if="isAllowedToCreate" #append> <template v-if="isAllowedToCreate" #append>
<QIcon <QIcon
@click.stop.prevent="toggleForm()" @click.stop.prevent="toggleForm()"
@ -72,7 +72,7 @@ const toggleForm = () => {
<template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName"> <template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName">
<slot :name="slotName" v-bind="slotData" :key="slotName" /> <slot :name="slotName" v-bind="slotData" :key="slotName" />
</template> </template>
</VnSelectFilter> </VnSelect>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -199,8 +199,9 @@ es:
templates: templates:
pendingPayment: 'Su pedido está pendiente de pago. pendingPayment: 'Su pedido está pendiente de pago.
Por favor, entre en la página web y efectue el pago con tarjeta. Muchas gracias.' Por favor, entre en la página web y efectue el pago con tarjeta. Muchas gracias.'
minAmount: 'Es necesario un importe mínimo de 50 (Sin IVA) en su pedido minAmount: 'Te recordamos que tu pedido {orderId} es inferior a 50.
{ orderId } con llegada { landing } para recibirlo sin portes adicionales.' Te recomendamos amplíes para no generar costes extra, provocarán un incremento de tu tarifa.
¡Un saludo!'
orderChanges: 'Pedido {orderId} con llegada estimada día { landing }: { changes }' orderChanges: 'Pedido {orderId} con llegada estimada día { landing }: { changes }'
en: Inglés en: Inglés
es: Español es: Español

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { onBeforeMount, useSlots, watch, computed, ref } from 'vue'; import { onBeforeMount, watch, computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue'; import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
@ -38,7 +38,6 @@ const $props = defineProps({
}); });
const state = useState(); const state = useState();
const slots = useSlots();
const { t } = useI18n(); const { t } = useI18n();
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
const arrayData = useArrayData($props.dataKey || $props.module, { const arrayData = useArrayData($props.dataKey || $props.module, {
@ -47,7 +46,7 @@ const arrayData = useArrayData($props.dataKey || $props.module, {
skip: 0, skip: 0,
}); });
const { store } = arrayData; const { store } = arrayData;
const entity = computed(() =>Array.isArray( store.data) ? store.data[0] : store.data); const entity = computed(() => (Array.isArray(store.data) ? store.data[0] : store.data));
const isLoading = ref(false); const isLoading = ref(false);
defineExpose({ defineExpose({
@ -55,14 +54,12 @@ defineExpose({
}); });
onBeforeMount(async () => { onBeforeMount(async () => {
await getData(); await getData();
watch( watch($props, async () => await getData());
() => $props.url,
async () => await getData()
);
}); });
async function getData() { async function getData() {
store.url = $props.url; store.url = $props.url;
store.filter = $props.filter ?? {};
isLoading.value = true; isLoading.value = true;
try { try {
const { data } = await arrayData.fetch({ append: false, updateRouter: false }); const { data } = await arrayData.fetch({ append: false, updateRouter: false });
@ -117,7 +114,7 @@ const emit = defineEmits(['onFetch']);
icon="more_vert" icon="more_vert"
round round
size="md" size="md"
:class="{ invisible: !slots.menu }" :class="{ invisible: !$slots.menu }"
> >
<QTooltip> <QTooltip>
{{ t('components.cardDescriptor.moreOptions') }} {{ t('components.cardDescriptor.moreOptions') }}

View File

@ -15,7 +15,7 @@ const props = defineProps({
default: null, default: null,
}, },
entityId: { entityId: {
type: Number, type: [Number, String],
default: null, default: null,
}, },
dataKey: { dataKey: {
@ -32,7 +32,7 @@ const arrayData = useArrayData(props.dataKey || route.meta.moduleName, {
skip: 0, skip: 0,
}); });
const { store } = arrayData; const { store } = arrayData;
const entity = computed(() => Array.isArray(store.data) ? store.data[0] : store.data); const entity = computed(() => (Array.isArray(store.data) ? store.data[0] : store.data));
const isLoading = ref(false); const isLoading = ref(false);
defineExpose({ defineExpose({
@ -48,11 +48,19 @@ onBeforeMount(async () => {
async function fetch() { async function fetch() {
store.url = props.url; store.url = props.url;
store.filter = props.filter ?? {};
isLoading.value = true; isLoading.value = true;
const { data } = await arrayData.fetch({ append: false, updateRouter: false }); const { data } = await arrayData.fetch({ append: false, updateRouter: false });
emit('onFetch', data); emit('onFetch', Array.isArray(data) ? data[0] : data);
isLoading.value = false; isLoading.value = false;
} }
const showRedirectToSummaryIcon = computed(() => {
const routeExists = route.matched.some(
(route) => route.name === `${route.meta.moduleName}Summary`
);
return !isSummary.value && route.meta.moduleName && routeExists;
});
</script> </script>
<template> <template>
@ -63,7 +71,7 @@ async function fetch() {
<div class="summaryHeader bg-primary q-pa-sm text-weight-bolder"> <div class="summaryHeader bg-primary q-pa-sm text-weight-bolder">
<slot name="header-left"> <slot name="header-left">
<router-link <router-link
v-if="!isSummary && route.meta.moduleName" v-if="showRedirectToSummaryIcon"
class="header link" class="header link"
:to="{ :to="{
name: `${route.meta.moduleName}Summary`, name: `${route.meta.moduleName}Summary`,
@ -134,6 +142,9 @@ async function fetch() {
box-shadow: none; box-shadow: none;
.vn-label-value { .vn-label-value {
&.negative > .value span {
color: $alert;
}
display: flex; display: flex;
flex-direction: row; flex-direction: row;
margin-top: 2px; margin-top: 2px;

View File

@ -1,29 +1,17 @@
<template> <template>
<div class="q-pa-md"> <div class="q-pa-md">
<div class="row q-gutter-md q-mb-md"> <div class="row q-gutter-md q-mb-md">
<div class="col">
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" square />
</div>
<div class="col">
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" square />
</div> </div>
</div>
<div class="row q-gutter-md q-mb-md"> <div class="row q-gutter-md q-mb-md">
<div class="col">
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" square />
</div>
<div class="col">
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" square />
</div> </div>
</div>
<div class="row q-gutter-md q-mb-md"> <div class="row q-gutter-md q-mb-md">
<div class="col">
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" square />
</div>
<div class="col">
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" square />
</div> </div>
</div>
<div class="row q-gutter-md"> <div class="row q-gutter-md">
<QSkeleton type="QBtn" /> <QSkeleton type="QBtn" />
<QSkeleton type="QBtn" /> <QSkeleton type="QBtn" />

View File

@ -3,39 +3,30 @@
<QSkeleton type="rect" square /> <QSkeleton type="rect" square />
</div> </div>
<div class="row q-pa-md q-col-gutter-md q-mb-md"> <div class="row q-pa-md q-col-gutter-md q-mb-md">
<div class="col">
<QSkeleton type="rect" class="q-mb-md" square /> <QSkeleton type="rect" class="q-mb-md" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
</div>
<div class="col">
<QSkeleton type="rect" class="q-mb-md" square /> <QSkeleton type="rect" class="q-mb-md" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
</div>
<div class="col">
<QSkeleton type="rect" class="q-mb-md" square /> <QSkeleton type="rect" class="q-mb-md" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
</div>
<div class="col">
<QSkeleton type="rect" class="q-mb-md" square /> <QSkeleton type="rect" class="q-mb-md" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
</div>
<div class="col">
<QSkeleton type="rect" class="q-mb-md" square /> <QSkeleton type="rect" class="q-mb-md" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
@ -43,7 +34,6 @@
<QSkeleton type="text" square /> <QSkeleton type="text" square />
<QSkeleton type="text" square /> <QSkeleton type="text" square />
</div> </div>
</div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -1,47 +1,22 @@
<template> <template>
<div class="q-pa-md w"> <div class="q-pa-md w">
<div class="row q-gutter-md q-mb-md"> <div class="row q-gutter-md q-mb-md">
<div class="col-1"> <QSkeleton type="rect" square />
<QSkeleton type="rect" square />
<QSkeleton type="rect" square />
<QSkeleton type="rect" square />
<QSkeleton type="rect" square />
<QSkeleton type="rect" square /> <QSkeleton type="rect" square />
</div> </div>
<div class="col">
<QSkeleton type="rect" square />
</div>
<div class="col">
<QSkeleton type="rect" square />
</div>
<div class="col">
<QSkeleton type="rect" square />
</div>
<div class="col">
<QSkeleton type="rect" square />
</div>
<div class="col">
<QSkeleton type="rect" square />
</div>
</div>
<div class="row q-gutter-md q-mb-md" v-for="n in 5" :key="n"> <div class="row q-gutter-md q-mb-md" v-for="n in 5" :key="n">
<div class="col-1">
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" square />
</div>
<div class="col">
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" square />
</div>
<div class="col">
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" square />
</div>
<div class="col">
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" square />
</div>
<div class="col">
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" square />
</div>
<div class="col">
<QSkeleton type="QInput" square /> <QSkeleton type="QInput" square />
</div> </div>
</div> </div>
</div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.w { .w {

View File

@ -4,7 +4,7 @@ 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();
@ -56,6 +56,7 @@ const arrayData = useArrayData(props.dataKey, {
const route = useRoute(); const route = useRoute();
const store = arrayData.store; const store = arrayData.store;
const userParams = ref({}); const userParams = ref({});
const { navigate } = useRedirect();
onMounted(() => { onMounted(() => {
if (props.params) userParams.value = JSON.parse(JSON.stringify(props.params)); if (props.params) userParams.value = JSON.parse(JSON.stringify(props.params));
@ -79,6 +80,7 @@ watch(
const isLoading = ref(false); const isLoading = ref(false);
async function search() { async function search() {
store.filter.where = {};
isLoading.value = true; isLoading.value = true;
const params = { ...userParams.value }; const params = { ...userParams.value };
store.userParamsChanged = true; store.userParamsChanged = true;
@ -91,6 +93,7 @@ async function search() {
isLoading.value = false; isLoading.value = false;
emit('search'); emit('search');
navigate(store.data, {});
} }
async function reload() { async function reload() {
@ -101,6 +104,7 @@ async function reload() {
if (!props.showAll && !params.length) store.data = []; if (!props.showAll && !params.length) store.data = [];
isLoading.value = false; isLoading.value = false;
emit('refresh'); emit('refresh');
navigate(store.data, {});
} }
async function clearFilters() { async function clearFilters() {
@ -146,7 +150,7 @@ const customTags = computed(() =>
async function remove(key) { async function remove(key) {
userParams.value[key] = null; userParams.value[key] = null;
await search(); await arrayData.applyFilter({ params: userParams.value });
emit('remove', key); emit('remove', key);
} }

View File

@ -1,21 +1,16 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n'; defineProps({ phoneNumber: { type: [String, Number], default: null } });
const props = defineProps({
phoneNumber: { type: [String, Number], default: null },
});
const { t } = useI18n();
</script> </script>
<template> <template>
<QBtn <QBtn
v-if="props.phoneNumber" v-if="phoneNumber"
flat flat
round round
icon="phone" icon="phone"
size="sm" size="sm"
color="primary" color="primary"
padding="none" padding="none"
:href="`sip:${props.phoneNumber}`" :href="`sip:${phoneNumber}`"
@click.stop @click.stop
/> />
</template> </template>
<style scoped></style>

View File

@ -44,6 +44,10 @@ const props = defineProps({
type: Object, type: Object,
default: null, default: null,
}, },
keepOpts: {
type: Array,
default: () => [],
},
offset: { offset: {
type: Number, type: Number,
default: 0, default: 0,
@ -78,6 +82,7 @@ const arrayData = useArrayData(props.dataKey, {
order: props.order, order: props.order,
userParams: props.userParams, userParams: props.userParams,
exprBuilder: props.exprBuilder, exprBuilder: props.exprBuilder,
keepOpts: props.keepOpts,
}); });
const store = arrayData.store; const store = arrayData.store;
@ -103,6 +108,8 @@ const addFilter = async (filter, params) => {
}; };
async function fetch() { async function fetch() {
store.filter.skip = 0;
store.skip = 0;
await arrayData.fetch({ append: false }); await arrayData.fetch({ append: false });
if (!store.hasMoreData) { if (!store.hasMoreData) {
isLoading.value = false; isLoading.value = false;

View File

@ -1,11 +1,13 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
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';
const quasar = useQuasar(); const quasar = useQuasar();
const { t } = useI18n();
const props = defineProps({ const props = defineProps({
dataKey: { dataKey: {
@ -65,10 +67,10 @@ const props = defineProps({
}, },
}); });
const router = useRouter();
const arrayData = useArrayData(props.dataKey, { ...props }); const arrayData = useArrayData(props.dataKey, { ...props });
const { store } = arrayData; const { store } = arrayData;
const searchText = ref(''); const searchText = ref('');
const { navigate } = useRedirect();
onMounted(() => { onMounted(() => {
const params = store.userParams; const params = store.userParams;
@ -81,34 +83,20 @@ async function search() {
const staticParams = Object.entries(store.userParams).filter( const staticParams = Object.entries(store.userParams).filter(
([key, value]) => value && (props.staticParams || []).includes(key) ([key, value]) => value && (props.staticParams || []).includes(key)
); );
store.skip = 0;
await arrayData.applyFilter({ await arrayData.applyFilter({
params: { params: {
...Object.fromEntries(staticParams), ...Object.fromEntries(staticParams),
search: searchText.value, search: searchText.value,
}, },
}); });
if (!props.redirect) return; if (!props.redirect) return;
if (props.customRouteRedirectName) navigate(store.data, {
return router.push({ customRouteRedirectName: props.customRouteRedirectName,
name: props.customRouteRedirectName, searchText: searchText.value,
params: { id: searchText.value },
}); });
const { matched: matches } = router.currentRoute.value;
const { path } = matches.at(-1);
const [, moduleName] = path.split('/');
if (!store.data.length || store.data.length > 1)
return router.push({ path: `/${moduleName}/list` });
const targetId = store.data[0].id;
let targetUrl;
if (path.endsWith('/list')) targetUrl = path.replace('/list', `/${targetId}/summary`);
else if (path.includes(':id')) targetUrl = path.replace(':id', targetId);
await router.push({ path: targetUrl });
} }
</script> </script>
@ -117,7 +105,7 @@ async function search() {
<VnInput <VnInput
id="searchbar" id="searchbar"
v-model="searchText" v-model="searchText"
:placeholder="props.label" :placeholder="t(props.label)"
dense dense
standout standout
autofocus autofocus
@ -136,7 +124,7 @@ async function search() {
name="info" name="info"
class="cursor-info" class="cursor-info"
> >
<QTooltip>{{ props.info }}</QTooltip> <QTooltip>{{ t(props.info) }}</QTooltip>
</QIcon> </QIcon>
</template> </template>
</VnInput> </VnInput>

View File

@ -1,11 +1,27 @@
<script setup> <script setup>
import { onMounted, onUnmounted } from 'vue'; import { onMounted, onUnmounted, ref } from 'vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
const stateStore = useStateStore(); const stateStore = useStateStore();
const actions = ref(null);
const data = ref(null);
const opts = { subtree: true, childList: true, attributes: true };
const hasContent = ref(false);
onMounted(() => { onMounted(() => {
stateStore.toggleSubToolbar(); stateStore.toggleSubToolbar();
actions.value = document.querySelector('#st-actions');
data.value = document.querySelector('#st-data');
if (!actions.value && !data.value) return;
// Check if there's content to display
const observer = new MutationObserver(
() =>
(hasContent.value =
actions.value.childNodes.length + data.value.childNodes.length)
);
if (actions.value) observer.observe(actions.value, opts);
if (data.value) observer.observe(data.value, opts);
}); });
onUnmounted(() => { onUnmounted(() => {
@ -14,7 +30,10 @@ onUnmounted(() => {
</script> </script>
<template> <template>
<QToolbar class="bg-vn-section-color justify-end sticky"> <QToolbar
class="justify-end sticky"
v-show="hasContent || $slots['st-actions'] || $slots['st-data']"
>
<slot name="st-data"> <slot name="st-data">
<div id="st-data"></div> <div id="st-data"></div>
</slot> </slot>

View File

@ -90,7 +90,7 @@ const onNodeCreated = async () => {
await fetchNodeLeaves(creationNodeSelectedId.value); await fetchNodeLeaves(creationNodeSelectedId.value);
}; };
onMounted(async (n) => { onMounted(async () => {
const tree = [...state.get('Tree'), 1]; const tree = [...state.get('Tree'), 1];
const lastStateTree = state.get('TreeState'); const lastStateTree = state.get('TreeState');
if (tree) { if (tree) {

View File

@ -1,6 +1,5 @@
<script setup> <script setup>
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
const $props = defineProps({ const $props = defineProps({

View File

@ -0,0 +1,11 @@
export function getDateQBadgeColor(date) {
let today = Date.vnNew();
today.setHours(0, 0, 0, 0);
let timeTicket = new Date(date);
timeTicket.setHours(0, 0, 0, 0);
let comparation = today - timeTicket;
if (comparation == 0) return 'warning';
if (comparation < 0) return 'negative';
}

View File

@ -63,7 +63,10 @@ export function useArrayData(key, userOptions) {
if (isEmpty || !allowedOptions.includes(option)) continue; if (isEmpty || !allowedOptions.includes(option)) continue;
if (Object.prototype.hasOwnProperty.call(store, option)) { if (Object.prototype.hasOwnProperty.call(store, option)) {
store[option] = userOptions[option]; const defaultOpts = userOptions[option];
store[option] = userOptions.keepOpts?.includes(option)
? Object.assign(defaultOpts, store[option])
: defaultOpts;
} }
} }
} }
@ -167,9 +170,10 @@ export function useArrayData(key, userOptions) {
delete store.userParams[param]; delete store.userParams[param];
delete params[param]; delete params[param];
if (store.filter?.where) { if (store.filter?.where) {
delete store.filter.where[ const key = Object.keys(
Object.keys(exprBuilder ? exprBuilder(param) : param)[0] exprBuilder && exprBuilder(param) ? exprBuilder(param) : param
]; );
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;
} }

View File

@ -0,0 +1,25 @@
import { useRouter } from 'vue-router';
export default function useRedirect() {
const router = useRouter();
const navigate = (data, { customRouteRedirectName, searchText }) => {
if (customRouteRedirectName)
return router.push({
name: customRouteRedirectName,
params: { id: searchText },
});
const { matched: matches } = router.currentRoute.value;
const { path } = matches.at(-1);
const to =
data.length === 1
? path.replace(/\/(list|:id)|-list/, `/${data[0].id}`)
: path.replace(/:id.*/, '');
router.push({ path: to });
};
return { navigate };
}

View File

@ -3,11 +3,14 @@ import { useRole } from './useRole';
import { useUserConfig } from './useUserConfig'; import { useUserConfig } from './useUserConfig';
import axios from 'axios'; import axios from 'axios';
import useNotify from './useNotify'; import useNotify from './useNotify';
import { useTokenConfig } from './useTokenConfig';
const TOKEN_MULTIMEDIA = 'tokenMultimedia'; const TOKEN_MULTIMEDIA = 'tokenMultimedia';
const TOKEN = 'token'; const TOKEN = 'token';
export function useSession() { export function useSession() {
const { notify } = useNotify(); const { notify } = useNotify();
let isCheckingToken = false;
let intervalId = null;
function getToken() { function getToken() {
const localToken = localStorage.getItem(TOKEN); const localToken = localStorage.getItem(TOKEN);
@ -22,10 +25,24 @@ export function useSession() {
return localTokenMultimedia || sessionTokenMultimedia || ''; return localTokenMultimedia || sessionTokenMultimedia || '';
} }
function setToken(data) { function setSession(data) {
const storage = data.keepLogin ? localStorage : sessionStorage; let keepLogin = data.keepLogin;
const storage = keepLogin ? localStorage : sessionStorage;
storage.setItem(TOKEN, data.token); storage.setItem(TOKEN, data.token);
storage.setItem(TOKEN_MULTIMEDIA, data.tokenMultimedia); storage.setItem(TOKEN_MULTIMEDIA, data.tokenMultimedia);
storage.setItem('created', data.created);
storage.setItem('ttl', data.ttl);
sessionStorage.setItem('keepLogin', keepLogin);
}
function keepLogin() {
return sessionStorage.getItem('keepLogin');
}
function setToken({ token, tokenMultimedia }) {
const storage = keepLogin() ? localStorage : sessionStorage;
storage.setItem(TOKEN, token);
storage.setItem(TOKEN_MULTIMEDIA, tokenMultimedia);
} }
async function destroyToken(url, storage, key) { async function destroyToken(url, storage, key) {
if (storage.getItem(key)) { if (storage.getItem(key)) {
@ -45,11 +62,15 @@ export function useSession() {
tokenMultimedia: 'Accounts/logout', tokenMultimedia: 'Accounts/logout',
token: 'VnUsers/logout', token: 'VnUsers/logout',
}; };
const storage = keepLogin() ? localStorage : sessionStorage;
for (const [key, url] of Object.entries(tokens)) { for (const [key, url] of Object.entries(tokens)) {
await destroyToken(url, localStorage, key); await destroyToken(url, storage, key);
await destroyToken(url, sessionStorage, key);
} }
localStorage.clear();
sessionStorage.clear();
const { setUser } = useState(); const { setUser } = useState();
setUser({ setUser({
@ -59,22 +80,75 @@ export function useSession() {
lang: '', lang: '',
darkMode: null, darkMode: null,
}); });
stopRenewer();
} }
async function login(token, tokenMultimedia, keepLogin) { async function login(data) {
setToken({ token, tokenMultimedia, keepLogin }); setSession(data);
await useRole().fetch(); await useRole().fetch();
await useUserConfig().fetch(); await useUserConfig().fetch();
await useTokenConfig().fetch();
startInterval();
} }
function isLoggedIn() { function isLoggedIn() {
const localToken = localStorage.getItem(TOKEN); const localToken = localStorage.getItem(TOKEN);
const sessionToken = sessionStorage.getItem(TOKEN); const sessionToken = sessionStorage.getItem(TOKEN);
startInterval();
return !!(localToken || sessionToken); return !!(localToken || sessionToken);
} }
function startInterval() {
stopRenewer();
const renewPeriod = +sessionStorage.getItem('renewPeriod');
if (!renewPeriod) return;
intervalId = setInterval(() => checkValidity(), renewPeriod * 1000);
}
function stopRenewer() {
clearInterval(intervalId);
}
async function renewToken() {
const _token = getToken();
const token = await axios.post('VnUsers/renewToken', {
headers: { Authorization: _token },
});
const _tokenMultimedia = getTokenMultimedia();
const tokenMultimedia = await axios.post('VnUsers/renewToken', {
headers: { Authorization: _tokenMultimedia },
});
setToken({ token: token.data.id, tokenMultimedia: tokenMultimedia.data.id });
}
async function checkValidity() {
const { getTokenConfig } = useState();
const tokenConfig = getTokenConfig() ?? sessionStorage.getItem('tokenConfig');
const storage = keepLogin() ? localStorage : sessionStorage;
const created = +storage.getItem('created');
const ttl = +storage.getItem('ttl');
if (isCheckingToken || !created) return;
isCheckingToken = true;
const renewPeriodInSeconds = Math.min(ttl, tokenConfig.value.renewPeriod) * 1000;
const maxDate = created + renewPeriodInSeconds;
const now = new Date().getTime();
if (isNaN(renewPeriodInSeconds) || now <= maxDate) {
return (isCheckingToken = false);
}
await renewToken();
isCheckingToken = false;
}
return { return {
getToken, getToken,
getTokenMultimedia, getTokenMultimedia,
@ -82,5 +156,8 @@ export function useSession() {
destroy, destroy,
login, login,
isLoggedIn, isLoggedIn,
checkValidity,
setSession,
renewToken,
}; };
} }

View File

@ -13,34 +13,19 @@ const user = ref({
}); });
const roles = ref([]); const roles = ref([]);
const tokenConfig = ref({});
const drawer = ref(true); const drawer = ref(true);
const headerMounted = ref(false); const headerMounted = ref(false);
export function useState() { export function useState() {
function getUser() { function getUser() {
return computed(() => { return computed(() => {
return { return user.value;
id: user.value.id,
name: user.value.name,
nickname: user.value.nickname,
lang: user.value.lang,
darkMode: user.value.darkMode,
companyFk: user.value.companyFk,
warehouseFk: user.value.warehouseFk,
};
}); });
} }
function setUser(data) { function setUser(data) {
user.value = { user.value = data;
id: data.id,
name: data.name,
nickname: data.nickname,
lang: data.lang,
darkMode: data.darkMode,
companyFk: data.companyFk,
warehouseFk: data.warehouseFk,
};
} }
function getRoles() { function getRoles() {
@ -52,6 +37,15 @@ export function useState() {
function setRoles(data) { function setRoles(data) {
roles.value = data; roles.value = data;
} }
function getTokenConfig() {
return computed(() => {
return tokenConfig.value;
});
}
function setTokenConfig(data) {
tokenConfig.value = data;
}
function set(name, data) { function set(name, data) {
state.value[name] = ref(data); state.value[name] = ref(data);
@ -70,6 +64,8 @@ export function useState() {
setUser, setUser,
getRoles, getRoles,
setRoles, setRoles,
getTokenConfig,
setTokenConfig,
set, set,
get, get,
unset, unset,

View File

@ -0,0 +1,28 @@
import axios from 'axios';
import { useState } from './useState';
import useNotify from './useNotify';
export function useTokenConfig() {
const state = useState();
const { notify } = useNotify();
async function fetch() {
try {
const { data } = await axios.get('AccessTokenConfigs/findOne', {
filter: { fields: ['renewInterval', 'renewPeriod'] },
});
if (!data) return;
state.setTokenConfig(data);
sessionStorage.setItem('renewPeriod', data.renewPeriod);
return data;
} catch (error) {
notify('errors.tokenConfig', 'negative');
console.error('Error fetching token config:', error);
}
}
return {
fetch,
state,
};
}

View File

@ -119,6 +119,11 @@ select:-webkit-autofill {
font-variation-settings: 'FILL' 1; font-variation-settings: 'FILL' 1;
} }
.fill-icon-on-hover:hover {
font-variation-settings: 'FILL' 1;
cursor: pointer;
}
.vn-table-separation-row { .vn-table-separation-row {
height: 16px !important; height: 16px !important;
background-color: var(--vn-section-color) !important; background-color: var(--vn-section-color) !important;
@ -136,6 +141,10 @@ select:-webkit-autofill {
background-color: var(--vn-section-color); background-color: var(--vn-section-color);
} }
.tr-header {
color: var(--vn-label-color);
}
.q-chip, .q-chip,
.q-notification__message, .q-notification__message,
.q-notification__icon { .q-notification__icon {
@ -169,3 +178,7 @@ input::-webkit-inner-spin-button {
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none; -moz-appearance: none;
} }
.q-scrollarea__content {
max-width: 100%;
}

View File

@ -112,3 +112,21 @@ export function secondsToHoursMinutes(seconds, includeHSuffix = true) {
// Return formatted string // Return formatted string
return formattedHours + ':' + formattedMinutes + suffix; return formattedHours + ':' + formattedMinutes + suffix;
} }
export function getTimeDifferenceWithToday(date) {
let today = Date.vnNew();
today.setHours(0, 0, 0, 0);
date = new Date(date);
date.setHours(0, 0, 0, 0);
return today - date;
}
export function isLower(date) {
return getTimeDifferenceWithToday(date) > 0;
}
export function isBigger(date) {
return getTimeDifferenceWithToday(date) < 0;
}

View File

@ -28,9 +28,11 @@ globals:
reset: Reset reset: Reset
close: Close close: Close
cancel: Cancel cancel: Cancel
clone: Clone
confirm: Confirm confirm: Confirm
assign: Assign assign: Assign
back: Back back: Back
downloadPdf: Download PDF
yes: 'Yes' yes: 'Yes'
no: 'No' no: 'No'
noChanges: No changes to save noChanges: No changes to save
@ -90,15 +92,26 @@ globals:
basicData: Basic data basicData: Basic data
log: Logs log: Logs
parkingList: Parkings list parkingList: Parkings list
agencyList: Agencies list
agency: Agency
workCenters: Work centers
modes: Modes
zones: Zones
zonesList: Zones
deliveryList: Delivery days
upcomingList: Upcoming deliveries
created: Created created: Created
worker: Worker worker: Worker
now: Now now: Now
name: Name
new: New
errors: errors:
statusUnauthorized: Access denied statusUnauthorized: Access denied
statusInternalServerError: An internal server error has ocurred statusInternalServerError: An internal server error has ocurred
statusBadGateway: It seems that the server has fall down statusBadGateway: It seems that the server has fall down
statusGatewayTimeout: Could not contact the server statusGatewayTimeout: Could not contact the server
userConfig: Error fetching user config userConfig: Error fetching user config
tokenConfig: Error fetching token config
writeRequest: The requested operation could not be completed writeRequest: The requested operation could not be completed
login: login:
title: Login title: Login
@ -259,6 +272,7 @@ customer:
tableVisibleColumns: tableVisibleColumns:
id: Identifier id: Identifier
name: Name name: Name
socialName: Social name
fi: Tax number fi: Tax number
salesPersonFk: Salesperson salesPersonFk: Salesperson
credit: Credit credit: Credit
@ -418,6 +432,7 @@ ticket:
boxing: Boxing boxing: Boxing
sms: Sms sms: Sms
notes: Notes notes: Notes
sale: Sale
list: list:
nickname: Nickname nickname: Nickname
state: State state: State
@ -513,7 +528,7 @@ claim:
records: records records: records
card: card:
claimId: Claim ID claimId: Claim ID
assignedTo: Assigned attendedBy: Attended by
created: Created created: Created
state: State state: State
ticketId: Ticket ID ticketId: Ticket ID
@ -551,6 +566,7 @@ claim:
responsible: Responsible responsible: Responsible
worker: Worker worker: Worker
redelivery: Redelivery redelivery: Redelivery
changeState: Change state
basicData: basicData:
customer: Customer customer: Customer
assignedTo: Assigned assignedTo: Assigned
@ -813,6 +829,7 @@ worker:
log: Log log: Log
calendar: Calendar calendar: Calendar
timeControl: Time control timeControl: Time control
locker: Locker
list: list:
name: Name name: Name
email: Email email: Email
@ -982,7 +999,7 @@ supplier:
billingData: Billing data billingData: Billing data
payMethod: Pay method payMethod: Pay method
payDeadline: Pay deadline payDeadline: Pay deadline
payDay: Día de pago payDay: Pay day
account: Account account: Account
fiscalData: Fiscal data fiscalData: Fiscal data
sageTaxType: Sage tax type sageTaxType: Sage tax type
@ -1119,8 +1136,19 @@ item:
list: List list: List
diary: Diary diary: Diary
tags: Tags tags: Tags
create: Create
buyRequest: Buy requests
fixedPrice: Fixed prices
wasteBreakdown: Waste breakdown wasteBreakdown: Waste breakdown
itemCreate: New item itemCreate: New item
barcode: Barcodes
tax: Tax
log: Log
botanical: Botanical
shelving: Shelving
itemTypeCreate: New item type
family: Item Type
lastEntries: Last entries
descriptor: descriptor:
item: Item item: Item
buyer: Buyer buyer: Buyer
@ -1148,6 +1176,15 @@ item:
stemMultiplier: Multiplier stemMultiplier: Multiplier
producer: Producer producer: Producer
landed: Landed landed: Landed
fixedPrice:
itemId: Item ID
groupingPrice: Grouping price
packingPrice: Packing price
hasMinPrice: Has min price
minPrice: Min price
started: Started
ended: Ended
warehouse: Warehouse
create: create:
name: Name name: Name
tag: Tag tag: Tag
@ -1155,8 +1192,77 @@ item:
type: Type type: Type
intrastat: Intrastat intrastat: Intrastat
origin: Origin origin: Origin
buyRequest:
ticketId: 'Ticket ID'
shipped: 'Shipped'
requester: 'Requester'
requested: 'Requested'
price: 'Price'
attender: 'Atender'
item: 'Item'
achieved: 'Achieved'
concept: 'Concept'
state: 'State'
summary:
basicData: 'Basic data'
otherData: 'Other data'
description: 'Description'
tax: 'Tax'
tags: 'Tags'
botanical: 'Botanical'
barcode: 'Barcode'
name: 'Nombre'
completeName: 'Nombre completo'
family: 'Familia'
size: 'Medida'
origin: 'Origen'
stems: 'Tallos'
multiplier: 'Multiplicador'
buyer: 'Comprador'
doPhoto: 'Do photo'
intrastatCode: 'Código intrastat'
intrastat: 'Intrastat'
ref: 'Referencia'
relevance: 'Relevancia'
weight: 'Peso (gramos)/tallo'
units: 'Unidades/caja'
expense: 'Gasto'
generic: 'Genérico'
recycledPlastic: 'Plástico reciclado'
nonRecycledPlastic: 'Plástico no reciclado'
minSalesQuantity: 'Cantidad mínima de venta'
genus: 'Genus'
specie: 'Specie'
item/itemType:
pageTitles:
itemType: Item type
basicData: Basic data
summary: Summary
monitor:
pageTitles:
monitors: Monitors
list: List
components: components:
topbar: {} topbar: {}
itemsFilterPanel:
typeFk: Type
tag: Tag
value: Value
# ItemFixedPriceFilter
buyerFk: Buyer
warehouseFk: Warehouse
started: From
ended: To
mine: For me
hasMinPrice: Minimum price
# LatestBuysFilter
salesPersonFk: Buyer
supplierFk: Supplier
from: From
to: To
active: Is active
visible: Is visible
floramondo: Is floramondo
userPanel: userPanel:
copyToken: Token copied to clipboard copyToken: Token copied to clipboard
settings: Settings settings: Settings

View File

@ -28,6 +28,7 @@ globals:
reset: Restaurar reset: Restaurar
close: Cerrar close: Cerrar
cancel: Cancelar cancel: Cancelar
clone: Clonar
confirm: Confirmar confirm: Confirmar
assign: Asignar assign: Asignar
back: Volver back: Volver
@ -58,6 +59,7 @@ globals:
amount: Importe amount: Importe
packages: Bultos packages: Bultos
download: Descargar download: Descargar
downloadPdf: Descargar PDF
selectRows: 'Seleccionar las { numberRows } filas(s)' selectRows: 'Seleccionar las { numberRows } filas(s)'
allRows: 'Todo { numberRows } filas(s)' allRows: 'Todo { numberRows } filas(s)'
markAll: Marcar todo markAll: Marcar todo
@ -90,15 +92,26 @@ globals:
basicData: Datos básicos basicData: Datos básicos
log: Historial log: Historial
parkingList: Listado de parkings parkingList: Listado de parkings
agencyList: Listado de agencias
agency: Agencia
workCenters: Centros de trabajo
modes: Modos
zones: Zonas
zonesList: Zonas
deliveryList: Días de entrega
upcomingList: Próximos repartos
created: Fecha creación created: Fecha creación
worker: Trabajador worker: Trabajador
now: Ahora now: Ahora
name: Nombre
new: Nuevo
errors: errors:
statusUnauthorized: Acceso denegado statusUnauthorized: Acceso denegado
statusInternalServerError: Ha ocurrido un error interno del servidor statusInternalServerError: Ha ocurrido un error interno del servidor
statusBadGateway: Parece ser que el servidor ha caído statusBadGateway: Parece ser que el servidor ha caído
statusGatewayTimeout: No se ha podido contactar con el servidor statusGatewayTimeout: No se ha podido contactar con el servidor
userConfig: Error al obtener configuración de usuario userConfig: Error al obtener configuración de usuario
tokenConfig: Error al obtener configuración de token
writeRequest: No se pudo completar la operación solicitada writeRequest: No se pudo completar la operación solicitada
login: login:
title: Inicio de sesión title: Inicio de sesión
@ -257,6 +270,7 @@ customer:
tableVisibleColumns: tableVisibleColumns:
id: Identificador id: Identificador
name: Nombre name: Nombre
socialName: Razón social
fi: NIF / CIF fi: NIF / CIF
salesPersonFk: Comercial salesPersonFk: Comercial
credit: Crédito credit: Crédito
@ -416,6 +430,7 @@ ticket:
boxing: Encajado boxing: Encajado
sms: Sms sms: Sms
notes: Notas notes: Notas
sale: Lineas del pedido
list: list:
nickname: Alias nickname: Alias
state: Estado state: Estado
@ -511,7 +526,7 @@ claim:
records: registros records: registros
card: card:
claimId: ID reclamación claimId: ID reclamación
assignedTo: Asignada a attendedBy: Atendida por
created: Creada created: Creada
state: Estado state: Estado
ticketId: ID ticket ticketId: ID ticket
@ -549,6 +564,7 @@ claim:
responsible: Responsable responsible: Responsable
worker: Trabajador worker: Trabajador
redelivery: Devolución redelivery: Devolución
changeState: Cambiar estado
basicData: basicData:
customer: Cliente customer: Cliente
assignedTo: Asignada a assignedTo: Asignada a
@ -811,6 +827,7 @@ worker:
log: Historial log: Historial
calendar: Calendario calendar: Calendario
timeControl: Control de horario timeControl: Control de horario
locker: Taquilla
list: list:
name: Nombre name: Nombre
email: Email email: Email
@ -1118,8 +1135,19 @@ item:
list: Listado list: Listado
diary: Histórico diary: Histórico
tags: Etiquetas tags: Etiquetas
fixedPrice: Precios fijados
buyRequest: Peticiones de compra
wasteBreakdown: Deglose de mermas wasteBreakdown: Deglose de mermas
itemCreate: Nuevo artículo itemCreate: Nuevo artículo
basicData: 'Datos básicos'
tax: 'IVA'
botanical: 'Botánico'
barcode: 'Código de barras'
log: Historial
shelving: Carros
itemTypeCreate: Nueva familia
family: Familia
lastEntries: Últimas entradas
descriptor: descriptor:
item: Artículo item: Artículo
buyer: Comprador buyer: Comprador
@ -1147,6 +1175,15 @@ item:
stemMultiplier: Multiplicador stemMultiplier: Multiplicador
producer: Productor producer: Productor
landed: F. entrega landed: F. entrega
fixedPrice:
itemId: ID Artículo
groupingPrice: Precio grouping
packingPrice: Precio packing
hasMinPrice: Tiene precio mínimo
minPrice: Precio min
started: Inicio
ended: Fin
warehouse: Almacén
create: create:
name: Nombre name: Nombre
tag: Etiqueta tag: Etiqueta
@ -1154,8 +1191,83 @@ item:
type: Tipo type: Tipo
intrastat: Intrastat intrastat: Intrastat
origin: Origen origin: Origen
summary:
basicData: 'Datos básicos'
otherData: 'Otros datos'
description: 'Descripción'
tax: 'IVA'
tags: 'Etiquetas'
botanical: 'Botánico'
barcode: 'Código de barras'
name: 'Nombre'
completeName: 'Nombre completo'
family: 'Familia'
size: 'Medida'
origin: 'Origen'
stems: 'Tallos'
multiplier: 'Multiplicador'
buyer: 'Comprador'
doPhoto: 'Hacer foto'
intrastatCode: 'Código intrastat'
intrastat: 'Intrastat'
ref: 'Referencia'
relevance: 'Relevancia'
weight: 'Peso (gramos)/tallo'
units: 'Unidades/caja'
expense: 'Gasto'
generic: 'Genérico'
recycledPlastic: 'Plástico reciclado'
nonRecycledPlastic: 'Plástico no reciclado'
minSalesQuantity: 'Cantidad mínima de venta'
genus: 'Genus'
specie: 'Specie'
buyRequest:
ticketId: 'ID Ticket'
shipped: 'F. envío'
requester: 'Solicitante'
requested: 'Solicitado'
price: 'Precio'
attender: 'Comprador'
item: 'Artículo'
achieved: 'Conseguido'
concept: 'Concepto'
state: 'Estado'
item/itemType:
pageTitles:
itemType: Familia
basicData: Datos básicos
summary: Resumen
zone:
pageTitles:
zones: Zona
zonesList: Zonas
deliveryList: Días de entrega
upcomingList: Próximos repartos
monitor:
pageTitles:
monitors: Monitores
list: Listado
components: components:
topbar: {} topbar: {}
itemsFilterPanel:
typeFk: Tipo
tag: Etiqueta
value: Valor
# ItemFixedPriceFilter
buyerFk: Comprador
warehouseFk: Almacén
started: Desde
ended: Hasta
mine: Para mi
hasMinPrice: Precio mínimo
# LatestBuysFilter
salesPersonFk: Comprador
supplierFk: Proveedor
from: Desde
to: Hasta
active: Activo
visible: Visible
floramondo: Floramondo
userPanel: userPanel:
copyToken: Token copiado al portapapeles copyToken: Token copiado al portapapeles
settings: Configuración settings: Configuración

View File

@ -0,0 +1,79 @@
<script setup>
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import CardList from 'src/components/ui/CardList.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useStateStore } from 'stores/useStateStore';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const router = useRouter();
const stateStore = useStateStore();
function navigate(id) {
router.push({ path: `/agency/${id}` });
}
function exprBuilder(param, value) {
if (!value) return;
if (param !== 'search') return;
if (!isNaN(value)) return { id: value };
return { name: { like: `%${value}%` } };
}
</script>
<template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
:info="t('You can search by name')"
:label="t('Search agency')"
data-key="AgencyList"
url="Agencies"
/>
</Teleport>
</template>
<QPage class="column items-center q-pa-md">
<div class="vn-card-list">
<VnPaginate
data-key="AgencyList"
url="Agencies"
order="name"
:expr-builder="exprBuilder"
auto-load
>
<template #body="{ rows }">
<CardList
:id="row.id"
:key="row.id"
:title="row.name"
@click="navigate(row.id)"
v-for="row of rows"
>
<template #list-items>
<QCheckbox
:label="t('isOwn')"
v-model="row.isOwn"
:disable="true"
/>
<QCheckbox
:label="t('isAnyVolumeAllowed')"
v-model="row.isAnyVolumeAllowed"
:disable="true"
/>
</template>
</CardList>
</template>
</VnPaginate>
</div>
</QPage>
</template>
<i18n>
es:
isOwn: Tiene propietario
isAnyVolumeAllowed: Permite cualquier volumen
Search agency: Buscar agencia
You can search by name: Puedes buscar por nombre
en:
isOwn: Has owner
isAnyVolumeAllowed: Allows any volume
</i18n>

View File

@ -0,0 +1,52 @@
<script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import FormModel from 'components/FormModel.vue';
import FetchData from 'src/components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
const { t } = useI18n();
const route = useRoute();
const routeId = route.params?.id || null;
const warehouses = ref([]);
</script>
<template>
<FetchData
url="warehouses"
sort-by="name"
@on-fetch="(data) => (warehouses = data)"
auto-load
/>
<FormModel :url="`Agencies/${routeId}`" model="agency" auto-load>
<template #form="{ data }">
<VnRow>
<VnInput v-model="data.name" :label="t('globals.name')" />
</VnRow>
<VnRow>
<VnSelect
v-model="data.warehouseFk"
option-value="id"
option-label="name"
:label="t('globals.warehouse')"
:options="warehouses"
use-input
input-debounce="0"
:is-clearable="false"
/>
</VnRow>
<VnRow>
<QCheckbox :label="t('agency.isOwn')" v-model="data.isOwn" />
</VnRow>
<VnRow>
<QCheckbox
:label="t('agency.isAnyVolumeAllowed')"
v-model="data.isAnyVolumeAllowed"
/>
</VnRow>
</template>
</FormModel>
</template>

View File

@ -0,0 +1,15 @@
<script setup>
import AgencyDescriptor from 'pages/Agency/Card/AgencyDescriptor.vue';
import VnCard from 'components/common/VnCard.vue';
</script>
<template>
<VnCard
data-key="Agency"
base-url="Agencies"
:descriptor="AgencyDescriptor"
search-data-key="AgencyList"
search-url="Agencies"
searchbar-label="agency.searchBar.label"
searchbar-info="agency.searchBar.info"
/>
</template>

View File

@ -0,0 +1,35 @@
<script setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { useArrayData } from 'src/composables/useArrayData';
import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'components/ui/VnLv.vue';
const props = defineProps({
id: {
type: Number,
required: false,
default: null,
},
});
const { t } = useI18n();
const route = useRoute();
const entityId = computed(() => props.id || route.params.id);
const { store } = useArrayData('Parking');
const card = computed(() => store.data);
</script>
<template>
<CardDescriptor
module="Agency"
data-key="Agency"
:url="`Agencies/${entityId}`"
:title="card?.name"
:subtitle="props.id"
>
<template #body="{ entity: agency }">
<VnLv :label="t('globals.name')" :value="agency.name" />
</template>
</CardDescriptor>
</template>

View File

@ -0,0 +1,6 @@
<script setup>
import VnLog from 'src/components/common/VnLog.vue';
</script>
<template>
<VnLog model="Agency" url="/AgencyLogs" />
</template>

View File

@ -0,0 +1,60 @@
<script setup>
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CardList from 'src/components/ui/CardList.vue';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import VnLv from 'src/components/ui/VnLv.vue';
const { t } = useI18n();
const route = useRoute();
const routeId = route.params.id;
</script>
<template>
<QPage class="column items-center q-pa-md">
<div class="vn-card-list">
<VnPaginate
data-key="AgencyModes"
:url="`AgencyModes`"
:filter="{ where: { agencyFk: routeId } }"
order="name"
auto-load
>
<template #body="{ rows }">
<CardList
:id="row.id"
:key="row.id"
:title="row.name"
v-for="row of rows"
>
<template #list-items>
<VnLv
:label="t('globals.description')"
:value="row?.description"
/>
<VnLv
:label="t('deliveryMethod')"
:value="row?.deliveryMethodFk"
/>
<VnLv label="m3" :value="row?.m3" />
<VnLv :label="t('inflation')" :value="row?.inflation" />
<VnLv :label="t('globals.code')" :value="row?.code" />
</template>
</CardList>
</template>
</VnPaginate>
</div>
</QPage>
</template>
<i18n>
es:
isOwn: Tiene propietario
isAnyVolumeAllowed: Permite cualquier volumen
Search agency: Buscar agencia
You can search by name: Puedes buscar por nombre
deliveryMethod: Método de entrega
inflation: Inflación
en:
isOwn: Has owner
isAnyVolumeAllowed: Allows any volume
</i18n>

View File

@ -0,0 +1,42 @@
<script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'components/ui/VnLv.vue';
import VnTitle from 'src/components/common/VnTitle.vue';
const $props = defineProps({ id: { type: Number, default: 0 } });
const { t } = useI18n();
const entityId = computed(() => $props.id || useRoute().params.id);
</script>
<template>
<div class="q-pa-md">
<CardSummary :url="`Agencies/${entityId}`">
<template #header="{ entity: agency }">{{ agency.name }}</template>
<template #body="{ entity: agency }">
<QCard class="vn-one">
<QCardSection class="q-pa-none">
<VnTitle
:url="`#/agency/${entityId}/basic-data`"
:text="t('globals.pageTitles.basicData')"
/>
</QCardSection>
<VnLv :label="t('globals.name')" :value="agency.name" />
<QCheckbox
:label="t('agency.isOwn')"
v-model="agency.isOwn"
:disable="true"
/>
<QCheckbox
:label="t('agency.isAnyVolumeAllowed')"
v-model="agency.isAnyVolumeAllowed"
:disable="true"
/>
</QCard>
</template>
</CardSummary>
</div>
</template>

View File

@ -0,0 +1,136 @@
<script setup>
import axios from 'axios';
import { useQuasar } from 'quasar';
import { ref, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import FetchData from 'src/components/FetchData.vue';
import FormModelPopup from 'src/components/FormModelPopup.vue';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
const { t } = useI18n();
const route = useRoute();
const paginate = ref();
const dialog = ref();
const routeId = computed(() => route.params.id);
const quasar = useQuasar();
const initialData = computed(() => {
return {
agencyFk: routeId.value,
workCenterFk: null,
};
});
async function deleteWorCenter(id) {
try {
await axios.delete(`AgencyWorkCenters/${id}`).then(async () => {
quasar.notify({
message: t('agency.notification.removeItem'),
type: 'positive',
});
});
} catch (error) {
quasar.notify({
message: t('agency.notification.removeItemError'),
type: 'error',
});
}
paginate.value.fetch();
}
</script>
<template>
<div class="centerCard">
<FetchData
url="workCenters"
sort-by="name"
@on-fetch="(data) => (warehouses = data)"
auto-load
/>
<VnPaginate
ref="paginate"
data-key="AgencyWorkCenters"
url="AgencyWorkCenters"
:filter="{ where: { agencyFk: routeId } }"
order="id"
auto-load
>
<template #body="{ rows }">
<QCard
flat
bordered
:key="row.id"
v-for="row of rows"
class="card q-pa-md q-mb-sm"
>
<QItem>
<QItemSection side-left>
<VnLv :value="row?.workCenter?.name" icon="delete" />
</QItemSection>
<QItemSection side>
<QBtn
@click.stop="deleteWorCenter(row.id)"
icon="delete"
color="primary"
round
flat
/>
</QItemSection>
</QItem>
</QCard>
</template>
</VnPaginate>
</div>
<QPageSticky :offset="[18, 18]">
<QBtn @click.stop="dialog.show()" color="primary" fab icon="add">
<QDialog ref="dialog">
<FormModelPopup
:title="t('Add work center')"
url-create="AgencyWorkCenters"
model="AgencyWorkCenter"
:form-initial-data="initialData"
@on-data-saved="paginate.fetch()"
>
<template #form-inputs="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<VnSelect
v-model="data.workCenterFk"
option-value="id"
option-label="name"
:label="t('workCenter')"
:options="warehouses"
use-input
input-debounce="0"
:is-clearable="false"
/>
</VnRow>
</template>
</FormModelPopup>
</QDialog>
</QBtn>
<QTooltip>
{{ t('globals.new') }}
</QTooltip>
</QPageSticky>
</template>
<style lang="scss" scoped>
.centerCard {
padding: 5%;
width: 100%;
max-width: 50%;
margin: 0 auto;
}
</style>
<i18n>
es:
workCenter removed successfully: Centro de trabajo eliminado correctamente
This workCenter is already assigned to this agency: Este workCenter ya está asignado a esta agencia
Add work center: Añadir centro de trabajo
workCenter: Centro de trabajo
</i18n>

View File

@ -0,0 +1,11 @@
agency:
isOwn: Own
isAnyVolumeAllowed: Any volume allowed
notification:
removeItemError: Error removing agency
removeItem: WorkCenter removed successfully
pageTitles:
agency: Agency
searchBar:
info: You can search by agency code
label: Search agency...

View File

@ -0,0 +1,12 @@
agency:
isOwn: Propio
isAnyVolumeAllowed: Cualquier volumen
removeItem: Agencia eliminada correctamente
notification:
removeItemError: Error al eliminar la agencia
removeItem: Centro de trabajo eliminado correctamente
pageTitles:
agency: Agencia
searchBar:
info: Puedes buscar por nombre o id
label: Buscar agencia...

View File

@ -2,22 +2,21 @@
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute } from 'vue-router';
import axios from 'axios'; import axios from 'axios';
import { useStateStore } from 'src/stores/useStateStore'; import { useStateStore } from 'src/stores/useStateStore';
import { toDate, toPercentage, toCurrency } from 'filters/index'; import { toDate, toPercentage, toCurrency } from 'filters/index';
import { tMobile } from 'src/composables/tMobile'; import { tMobile } from 'src/composables/tMobile';
import CrudModel from 'src/components/CrudModel.vue'; import CrudModel from 'src/components/CrudModel.vue';
import FetchData from 'src/components/FetchData.vue'; import FetchData from 'src/components/FetchData.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue'; import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
const { t } = useI18n(); const { t } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
const route = useRoute(); const route = useRoute();
const router = useRouter();
const stateStore = computed(() => useStateStore()); const stateStore = computed(() => useStateStore());
const claim = ref(null); const claim = ref(null);
const claimRef = ref(); const claimRef = ref();
@ -38,10 +37,11 @@ const marker_labels = [
{ value: DEFAULT_MAX_RESPONSABILITY, label: t('claim.summary.person') }, { value: DEFAULT_MAX_RESPONSABILITY, label: t('claim.summary.person') },
]; ];
const multiplicatorValue = ref(); const multiplicatorValue = ref();
const loading = ref(false);
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'Id', name: 'id',
label: t('Id item'), label: t('Id item'),
field: (row) => row.itemFk, field: (row) => row.itemFk,
}, },
@ -119,7 +119,7 @@ async function updateDestinations(claimDestinationFk) {
async function updateDestination(claimDestinationFk, row, options = {}) { async function updateDestination(claimDestinationFk, row, options = {}) {
if (claimDestinationFk) { if (claimDestinationFk) {
await axios.post('Claims/updateClaimDestination', { await post('Claims/updateClaimDestination', {
claimDestinationFk, claimDestinationFk,
rows: Array.isArray(row) ? row : [row], rows: Array.isArray(row) ? row : [row],
}); });
@ -128,7 +128,7 @@ async function updateDestination(claimDestinationFk, row, options = {}) {
} }
async function regularizeClaim() { async function regularizeClaim() {
await axios.post(`Claims/${claimId}/regularizeClaim`); await post(`Claims/${claimId}/regularizeClaim`);
await claimRef.value.fetch(); await claimRef.value.fetch();
await arrayData.fetch({ append: false }); await arrayData.fetch({ append: false });
quasar.notify({ quasar.notify({
@ -147,7 +147,7 @@ async function onUpdateGreugeAccept() {
const freightPickUpPrice = const freightPickUpPrice =
(await axios.get(`GreugeConfigs/findOne`)).data.freightPickUpPrice * (await axios.get(`GreugeConfigs/findOne`)).data.freightPickUpPrice *
multiplicatorValue.value; multiplicatorValue.value;
await axios.post(`Greuges`, { await post(`Greuges`, {
clientFk: claim.value.clientFk, clientFk: claim.value.clientFk,
description: `${t('ClaimGreugeDescription')} ${claimId}`.toUpperCase(), description: `${t('ClaimGreugeDescription')} ${claimId}`.toUpperCase(),
amount: freightPickUpPrice, amount: freightPickUpPrice,
@ -166,14 +166,22 @@ async function save(data) {
} }
async function importToNewRefundTicket() { async function importToNewRefundTicket() {
const query = `ClaimBeginnings/${claimId}/importToNewRefundTicket`; await post(`ClaimBeginnings/${claimId}/importToNewRefundTicket`);
await axios.post(query); await claimActionsForm.value.reload();
claimActionsForm.value.reload();
quasar.notify({ quasar.notify({
message: t('globals.dataSaved'), message: t('globals.dataSaved'),
type: 'positive', type: 'positive',
}); });
} }
async function post(query, params) {
loading.value = true;
try {
await axios.post(query, params);
} finally {
loading.value = false;
}
}
</script> </script>
<template> <template>
<FetchData <FetchData
@ -193,30 +201,7 @@ async function importToNewRefundTicket() {
auto-load auto-load
@on-fetch="(data) => (destinationTypes = data)" @on-fetch="(data) => (destinationTypes = data)"
/> />
<template v-if="stateStore.isHeaderMounted()"> <Teleport to="#right-panel" v-if="stateStore.isHeaderMounted() && claim">
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<QDrawer
v-model="stateStore.rightDrawer"
side="right"
:width="300"
show-if-above
v-if="claim"
>
<QCard class="totalClaim q-my-md q-pa-sm no-box-shadow"> <QCard class="totalClaim q-my-md q-pa-sm no-box-shadow">
{{ `${t('Total claimed')}: ${toCurrency(totalClaimed)}` }} {{ `${t('Total claimed')}: ${toCurrency(totalClaimed)}` }}
</QCard> </QCard>
@ -266,7 +251,7 @@ async function importToNewRefundTicket() {
v-model="multiplicatorValue" v-model="multiplicatorValue"
/> />
</QCard> </QCard>
</QDrawer> </Teleport>
<Teleport to="#st-data" v-if="stateStore.isSubToolbarShown()"> </Teleport> <Teleport to="#st-data" v-if="stateStore.isSubToolbarShown()"> </Teleport>
<CrudModel <CrudModel
v-if="claim" v-if="claim"
@ -280,6 +265,7 @@ async function importToNewRefundTicket() {
:default-save="false" :default-save="false"
:default-reset="false" :default-reset="false"
@on-fetch="setData" @on-fetch="setData"
:limit="0"
auto-load auto-load
> >
<template #body> <template #body>
@ -292,6 +278,14 @@ async function importToNewRefundTicket() {
v-model:selected="selectedRows" v-model:selected="selectedRows"
:grid="$q.screen.lt.md" :grid="$q.screen.lt.md"
> >
<template #body-cell-id="{ value }">
<QTd align="center">
<span class="link">
{{ value }}
<ItemDescriptorProxy :id="value" />
</span>
</QTd>
</template>
<template #body-cell-ticket="{ value }"> <template #body-cell-ticket="{ value }">
<QTd align="center"> <QTd align="center">
<span class="link"> <span class="link">
@ -302,7 +296,7 @@ async function importToNewRefundTicket() {
</template> </template>
<template #body-cell-destination="{ row }"> <template #body-cell-destination="{ row }">
<QTd> <QTd>
<VnSelectFilter <VnSelect
v-model="row.claimDestinationFk" v-model="row.claimDestinationFk"
:options="destinationTypes" :options="destinationTypes"
option-label="description" option-label="description"
@ -344,7 +338,7 @@ async function importToNewRefundTicket() {
</QItemSection> </QItemSection>
<QItemSection side> <QItemSection side>
<QItemLabel v-if="column.name === 'destination'"> <QItemLabel v-if="column.name === 'destination'">
<VnSelectFilter <VnSelect
v-model="props.row.claimDestinationFk" v-model="props.row.claimDestinationFk"
:options="destinationTypes" :options="destinationTypes"
option-label="description" option-label="description"
@ -383,6 +377,7 @@ async function importToNewRefundTicket() {
icon="check" icon="check"
@click="regularizeClaim" @click="regularizeClaim"
:disable="claim.claimStateFk == resolvedStateId" :disable="claim.claimStateFk == resolvedStateId"
:loading="loading"
/> />
<QBtn <QBtn
@ -394,6 +389,7 @@ async function importToNewRefundTicket() {
:title="t('Change destination')" :title="t('Change destination')"
icon="swap_horiz" icon="swap_horiz"
@click="dialogDestination = !dialogDestination" @click="dialogDestination = !dialogDestination"
:loading="loading"
/> />
<QBtn <QBtn
color="primary" color="primary"
@ -404,6 +400,7 @@ async function importToNewRefundTicket() {
icon="Upload" icon="Upload"
@click="importToNewRefundTicket" @click="importToNewRefundTicket"
:disable="claim.claimStateFk == resolvedStateId" :disable="claim.claimStateFk == resolvedStateId"
:loading="loading"
/> />
</template> </template>
</CrudModel> </CrudModel>
@ -418,7 +415,7 @@ async function importToNewRefundTicket() {
</QItem> </QItem>
</QCardSection> </QCardSection>
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelect
class="q-pa-sm" class="q-pa-sm"
v-model="claimDestinationFk" v-model="claimDestinationFk"
:options="destinationTypes" :options="destinationTypes"

View File

@ -2,7 +2,7 @@
import { ref } from 'vue'; 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 VnSelect from 'src/components/common/VnSelect.vue';
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';
@ -37,23 +37,19 @@ const claimFilter = {
], ],
}; };
const workers = ref([]);
const workersCopy = ref([]);
const claimStates = ref([]); const claimStates = ref([]);
const claimStatesCopy = ref([]); const claimStatesCopy = ref([]);
const optionsList = ref([]);
function setWorkers(data) { const workersOptions = ref([]);
workers.value = data;
workersCopy.value = data;
}
function setClaimStates(data) { function setClaimStates(data) {
claimStates.value = data; claimStates.value = data;
claimStatesCopy.value = data; claimStatesCopy.value = data;
} }
let optionsList;
async function getEnumValues() { async function getEnumValues() {
optionsList = [{ id: null, description: t('claim.basicData.null') }]; optionsList.value = [{ id: null, description: t('claim.basicData.null') }];
const { data } = await axios.get(`Applications/get-enum-values`, { const { data } = await axios.get(`Applications/get-enum-values`, {
params: { params: {
schema: 'vn', schema: 'vn',
@ -62,29 +58,11 @@ async function getEnumValues() {
}, },
}); });
for (let value of data) for (let value of data)
optionsList.push({ id: value, description: t(`claim.basicData.${value}`) }); optionsList.value.push({ id: value, description: t(`claim.basicData.${value}`) });
} }
getEnumValues(); getEnumValues();
const workerFilter = {
options: workers,
filterFn: (options, value) => {
const search = value.toLowerCase();
if (value === '') return workersCopy.value;
return options.value.filter((row) => {
const id = row.id;
const name = row.name.toLowerCase();
const idMatches = id == search;
const nameMatches = name.indexOf(search) > -1;
return idMatches || nameMatches;
});
},
};
const statesFilter = { const statesFilter = {
options: claimStates, options: claimStates,
filterFn: (options, value) => { filterFn: (options, value) => {
@ -104,7 +82,7 @@ const statesFilter = {
<FetchData <FetchData
url="Workers/activeWithInheritedRole" url="Workers/activeWithInheritedRole"
:filter="{ where: { role: 'salesPerson' } }" :filter="{ where: { role: 'salesPerson' } }"
@on-fetch="setWorkers" @on-fetch="(data) => (workersOptions = data)"
auto-load auto-load
/> />
<FetchData url="ClaimStates" @on-fetch="setClaimStates" auto-load /> <FetchData url="ClaimStates" @on-fetch="setClaimStates" auto-load />
@ -117,34 +95,26 @@ const statesFilter = {
> >
<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">
<div class="col">
<VnInput <VnInput
v-model="data.client.name" v-model="data.client.name"
:label="t('claim.basicData.customer')" :label="t('claim.basicData.customer')"
disable disable
/> />
</div>
<div class="col">
<VnInputDate <VnInputDate
v-model="data.created" v-model="data.created"
:label="t('claim.basicData.created')" :label="t('claim.basicData.created')"
/> />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<QSelect :label="t('claim.basicData.assignedTo')"
v-model="data.workerFk" v-model="data.workerFk"
:options="workers" :options="workersOptions"
option-value="id" option-value="id"
option-label="name" option-label="name"
emit-value emit-value
:label="t('claim.basicData.assignedTo')" auto-load
map-options
use-input
@filter="(value, update) => filter(value, update, workerFilter)"
:rules="validate('claim.claimStateFk')" :rules="validate('claim.claimStateFk')"
:input-debounce="0"
> >
<template #before> <template #before>
<QAvatar color="orange"> <QAvatar color="orange">
@ -155,9 +125,7 @@ const statesFilter = {
/> />
</QAvatar> </QAvatar>
</template> </template>
</QSelect> </VnSelect>
</div>
<div class="col">
<QSelect <QSelect
v-model="data.claimStateFk" v-model="data.claimStateFk"
:options="claimStates" :options="claimStates"
@ -172,18 +140,14 @@ const statesFilter = {
:input-debounce="0" :input-debounce="0"
> >
</QSelect> </QSelect>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QInput <QInput
v-model.number="data.packages" v-model.number="data.packages"
:label="t('globals.packages')" :label="t('globals.packages')"
:rules="validate('claim.packages')" :rules="validate('claim.packages')"
type="number" type="number"
/> />
</div>
<div class="col">
<QSelect <QSelect
v-model="data.pickup" v-model="data.pickup"
:options="optionsList" :options="optionsList"
@ -196,7 +160,6 @@ const statesFilter = {
:input-debounce="0" :input-debounce="0"
> >
</QSelect> </QSelect>
</div>
</VnRow> </VnRow>
</template> </template>
</FormModel> </FormModel>

View File

@ -1,46 +1,17 @@
<script setup> <script setup>
import LeftMenu from 'components/LeftMenu.vue'; import VnCard from 'components/common/VnCard.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useStateStore } from 'stores/useStateStore';
import { useI18n } from 'vue-i18n';
import ClaimDescriptor from './ClaimDescriptor.vue'; import ClaimDescriptor from './ClaimDescriptor.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import ClaimFilter from '../ClaimFilter.vue';
import useCardSize from 'src/composables/useCardSize';
const stateStore = useStateStore();
const { t } = useI18n();
</script> </script>
<template> <template>
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()"> <VnCard
<VnSearchbar data-key="Claim"
data-key="ClaimList" base-url="Claims"
url="Claims/filter" :descriptor="ClaimDescriptor"
:label="t('Search claim')" :filter-panel="ClaimFilter"
:info="t('You can search by claim id or customer name')" search-data-key="ClaimList"
search-url="Claims/filter"
searchbar-label="Search claim"
searchbar-info="You can search by claim id or customer name"
/> />
</Teleport>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit">
<ClaimDescriptor />
<QSeparator />
<LeftMenu source="card" />
</QScrollArea>
</QDrawer>
<QPageContainer>
<QPage>
<VnSubToolbar />
<div :class="useCardSize()">
<RouterView></RouterView>
</div>
</QPage>
</QPageContainer>
</template> </template>
<i18n>
es:
Search claim: Buscar reclamación
You can search by claim id or customer name: Puedes buscar por id de la reclamación o nombre del cliente
Details: Detalles
Notes: Notas
Action: Acción
</i18n>

View File

@ -11,6 +11,7 @@ import VnLv from 'src/components/ui/VnLv.vue';
import useCardDescription from 'src/composables/useCardDescription'; 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';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -73,8 +74,9 @@ const filter = {
const STATE_COLOR = { const STATE_COLOR = {
pending: 'warning', pending: 'warning',
managed: 'info', incomplete: 'info',
resolved: 'positive', resolved: 'positive',
canceled: 'negative',
}; };
function stateColor(code) { function stateColor(code) {
return STATE_COLOR[code]; return STATE_COLOR[code];
@ -127,17 +129,24 @@ onMounted(async () => {
</VnLv> </VnLv>
<VnLv <VnLv
v-if="entity.worker" v-if="entity.worker"
:label="t('claim.card.assignedTo')" :label="t('claim.card.attendedBy')"
:value="entity.worker.user.name" :value="entity.worker.user.name"
> >
<template #value> <template #value>
<VnUserLink <VnUserLink
:name="entity.worker.user.name" :name="entity.worker.user.nickname"
:worker-id="entity.worker.id" :worker-id="entity.worker.id"
/> />
</template> </template>
</VnLv> </VnLv>
<VnLv :label="t('claim.card.zone')" :value="entity.ticket?.zone?.name" /> <VnLv :label="t('claim.card.zone')">
<template #value>
<span class="link">
{{ entity.ticket?.zone?.name }}
<ZoneDescriptorProxy :id="entity.ticket?.zone?.id" />
</span>
</template>
</VnLv>
<VnLv <VnLv
:label="t('claim.card.province')" :label="t('claim.card.province')"
:value="entity.ticket?.address?.province?.name" :value="entity.ticket?.address?.province?.name"

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import CrudModel from 'components/CrudModel.vue'; import CrudModel from 'components/CrudModel.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue'; import VnSelect from 'components/common/VnSelect.vue';
import { tMobile } from 'composables/tMobile'; import { tMobile } from 'composables/tMobile';
const route = useRoute(); const route = useRoute();
@ -161,7 +161,7 @@ const columns = computed(() => [
auto-width auto-width
@keyup.ctrl.enter.stop="claimDevelopmentForm.saveChanges()" @keyup.ctrl.enter.stop="claimDevelopmentForm.saveChanges()"
> >
<VnSelectFilter <VnSelect
v-model="row[col.model]" v-model="row[col.model]"
:options="col.options" :options="col.options"
:option-value="col.optionValue" :option-value="col.optionValue"
@ -181,7 +181,7 @@ const columns = computed(() => [
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
</VnSelectFilter> </VnSelect>
</QTd> </QTd>
</template> </template>
<template #item="props"> <template #item="props">
@ -198,7 +198,7 @@ const columns = computed(() => [
<QList dense> <QList dense>
<QItem v-for="col in props.cols" :key="col.name"> <QItem v-for="col in props.cols" :key="col.name">
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelect
:label="col.label" :label="col.label"
v-model="props.row[col.model]" v-model="props.row[col.model]"
:options="col.options" :options="col.options"

View File

@ -45,20 +45,25 @@ async function onFetchClaim(data) {
const amount = ref(); const amount = ref();
const amountClaimed = ref(); const amountClaimed = ref();
async function onFetch(rows) { async function onFetch(rows, newRows) {
if (newRows) rows = newRows;
amount.value = 0; amount.value = 0;
amountClaimed.value = 0; amountClaimed.value = 0;
if (!rows || !rows.length) return; if (!rows || !rows.length) return;
amount.value = rows.reduce( for (const row of rows) {
(accumulator, { sale }) => accumulator + sale.price * sale.quantity, const { sale } = row;
0 amount.value = amount.value + totalRow(sale);
); const price = row.quantity * sale.price;
const discount = (sale.discount * price) / 100;
amountClaimed.value = amountClaimed.value + (price - discount);
}
}
amountClaimed.value = rows.reduce( function totalRow({ price, quantity, discount }) {
(accumulator, line) => accumulator + line.sale.price * line.quantity, const amount = price * quantity;
0 const appliedDiscount = (discount * amount) / 100;
); return amount - appliedDiscount;
} }
const columns = computed(() => [ const columns = computed(() => [
@ -102,12 +107,7 @@ const columns = computed(() => [
{ {
name: 'total', name: 'total',
label: t('Total'), label: t('Total'),
field: ({ sale }) => { field: ({ sale }) => totalRow(sale),
const amount = sale.price * sale.quantity;
const appliedDiscount = (sale.discount * amount) / 100;
return amount - appliedDiscount;
},
format: (value) => toCurrency(value), format: (value) => toCurrency(value),
sortable: true, sortable: true,
}, },
@ -129,6 +129,7 @@ async function updateDiscount({ saleFk, discount, canceller }) {
await axios.post(query, body, { await axios.post(query, body, {
signal: canceller.signal, signal: canceller.signal,
}); });
await claimLinesForm.value.reload();
} }
function onUpdateDiscount(response) { function onUpdateDiscount(response) {
@ -151,8 +152,11 @@ function showImportDialog() {
.onOk(() => claimLinesForm.value.reload()); .onOk(() => claimLinesForm.value.reload());
} }
function saveWhenHasChanges() { async function saveWhenHasChanges() {
claimLinesForm.value.getChanges().updates && claimLinesForm.value.onSubmit(); if (claimLinesForm.value.getChanges().updates) {
await claimLinesForm.value.onSubmit();
await claimLinesForm.value.reload();
}
} }
</script> </script>
<template> <template>
@ -188,7 +192,6 @@ function saveWhenHasChanges() {
save-url="ClaimBeginnings/crud" save-url="ClaimBeginnings/crud"
:filter="linesFilter" :filter="linesFilter"
@on-fetch="onFetch" @on-fetch="onFetch"
@save-changes="onFetch"
v-model:selected="selected" v-model:selected="selected"
:default-save="false" :default-save="false"
:default-reset="false" :default-reset="false"

View File

@ -35,6 +35,7 @@ const columns = computed(() => [
label: t('Quantity'), label: t('Quantity'),
field: (row) => row.quantity, field: (row) => row.quantity,
sortable: true, sortable: true,
default: 0,
}, },
{ {
name: 'description', name: 'description',
@ -75,7 +76,6 @@ async function importLines() {
const body = sales.map((row) => ({ const body = sales.map((row) => ({
claimFk: route.params.id, claimFk: route.params.id,
saleFk: row.saleFk, saleFk: row.saleFk,
quantity: row.quantity,
})); }));
canceller = new AbortController(); canceller = new AbortController();

View File

@ -1,6 +1,6 @@
<script setup> <script setup>
import { onMounted, ref, computed } from 'vue'; import { onMounted, ref, computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { toDate, toCurrency } from 'src/filters'; import { toDate, toCurrency } from 'src/filters';
import CardSummary from 'components/ui/CardSummary.vue'; import CardSummary from 'components/ui/CardSummary.vue';
@ -13,8 +13,11 @@ import VnUserLink from 'src/components/ui/VnUserLink.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import VnTitle from 'src/components/common/VnTitle.vue'; import VnTitle from 'src/components/common/VnTitle.vue';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
import axios from 'axios';
import dashIfEmpty from 'src/filters/dashIfEmpty';
const route = useRoute(); const route = useRoute();
const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const { getTokenMultimedia } = useSession(); const { getTokenMultimedia } = useSession();
const token = getTokenMultimedia(); const token = getTokenMultimedia();
@ -27,7 +30,7 @@ const $props = defineProps({
}); });
const entityId = computed(() => $props.id || route.params.id); const entityId = computed(() => $props.id || route.params.id);
const ClaimStates = ref([]);
const claimUrl = ref(); const claimUrl = ref();
const salixUrl = ref(); const salixUrl = ref();
const claimDmsRef = ref(); const claimDmsRef = ref();
@ -99,8 +102,9 @@ const detailsColumns = ref([
const STATE_COLOR = { const STATE_COLOR = {
pending: 'warning', pending: 'warning',
managed: 'info', incomplete: 'info',
resolved: 'positive', resolved: 'positive',
canceled: 'negative',
}; };
function stateColor(code) { function stateColor(code) {
return STATE_COLOR[code]; return STATE_COLOR[code];
@ -162,6 +166,10 @@ function openDialog(dmsId) {
multimediaSlide.value = dmsId; multimediaSlide.value = dmsId;
multimediaDialog.value = true; multimediaDialog.value = true;
} }
async function changeState(value) {
await axios.patch(`Claims/updateClaim/${entityId.value}`, { claimStateFk: value });
router.go(route.fullPath);
}
</script> </script>
<template> <template>
@ -171,6 +179,7 @@ function openDialog(dmsId) {
@on-fetch="(data) => setClaimDms(data)" @on-fetch="(data) => setClaimDms(data)"
ref="claimDmsRef" ref="claimDmsRef"
/> />
<FetchData url="ClaimStates" @on-fetch="(data) => (ClaimStates = data)" auto-load />
<CardSummary <CardSummary
ref="summary" ref="summary"
:url="`Claims/${entityId}/getSummary`" :url="`Claims/${entityId}/getSummary`"
@ -180,6 +189,36 @@ function openDialog(dmsId) {
<template #header="{ entity: { claim } }"> <template #header="{ entity: { claim } }">
{{ claim.id }} - {{ claim.client.name }} ({{ claim.client.id }}) {{ claim.id }} - {{ claim.client.name }} ({{ claim.client.id }})
</template> </template>
<template #header-right>
<QBtnDropdown
side
top
color="black"
text-color="white"
:label="t('ticket.summary.changeState')"
>
<QList>
<QVirtualScroll
style="max-height: 300px"
:items="ClaimStates"
separator
v-slot="{ item, index }"
>
<QItem
:key="index"
dense
clickable
v-close-popup
@click="changeState(item.id)"
>
<QItemSection>
<QItemLabel>{{ item.description }}</QItemLabel>
</QItemSection>
</QItem>
</QVirtualScroll>
</QList>
</QBtnDropdown>
</template>
<template #body="{ entity: { claim, salesClaimed, developments } }"> <template #body="{ entity: { claim, salesClaimed, developments } }">
<QCard class="vn-one"> <QCard class="vn-one">
<VnTitle <VnTitle
@ -223,7 +262,7 @@ function openDialog(dmsId) {
</VnLv> </VnLv>
<VnLv <VnLv
:label="t('claim.basicData.pickup')" :label="t('claim.basicData.pickup')"
:value="t(`claim.basicData.${claim.pickup}`)" :value="`${dashIfEmpty(claim.pickup)}`"
/> />
</QCard> </QCard>
<QCard class="vn-three"> <QCard class="vn-three">
@ -447,4 +486,8 @@ function openDialog(dmsId) {
.zindex { .zindex {
z-index: 1; z-index: 1;
} }
.change-state {
width: 10%;
}
</style> </style>

View File

@ -4,7 +4,7 @@ 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 VnSelectFilter from 'components/common/VnSelectFilter.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
@ -64,7 +64,7 @@ const states = ref();
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
<QItemSection v-if="workers"> <QItemSection v-if="workers">
<VnSelectFilter <VnSelect
:label="t('Salesperson')" :label="t('Salesperson')"
v-model="params.salesPersonFk" v-model="params.salesPersonFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
@ -87,7 +87,7 @@ const states = ref();
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
<QItemSection v-if="workers"> <QItemSection v-if="workers">
<VnSelectFilter <VnSelect
:label="t('Attender')" :label="t('Attender')"
v-model="params.attenderFk" v-model="params.attenderFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
@ -110,7 +110,7 @@ const states = ref();
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
<QItemSection v-if="workers"> <QItemSection v-if="workers">
<VnSelectFilter <VnSelect
:label="t('Responsible')" :label="t('Responsible')"
v-model="params.claimResponsibleFk" v-model="params.claimResponsibleFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
@ -133,7 +133,7 @@ const states = ref();
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
<QItemSection v-if="states"> <QItemSection v-if="states">
<VnSelectFilter <VnSelect
:label="t('State')" :label="t('State')"
v-model="params.claimStateFk" v-model="params.claimStateFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
@ -149,6 +149,15 @@ const states = ref();
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem>
<QItemSection>
<QCheckbox
v-model="params.myTeam"
:label="t('myTeam')"
toggle-indeterminate
/>
</QItemSection>
</QItem>
<QSeparator /> <QSeparator />
<QExpansionItem :label="t('More options')" expand-separator> <QExpansionItem :label="t('More options')" expand-separator>
<!-- <QItem> <!-- <QItem>
@ -192,6 +201,7 @@ en:
claimResponsibleFk: Responsible claimResponsibleFk: Responsible
claimStateFk: State claimStateFk: State
created: Created created: Created
myTeam: My team
es: es:
params: params:
search: Contiene search: Contiene
@ -211,4 +221,5 @@ es:
Item: Artículo Item: Artículo
Created: Creada Created: Creada
More options: Más opciones More options: Más opciones
myTeam: Mi equipo
</i18n> </i18n>

View File

@ -12,7 +12,6 @@ import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorP
import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue';
import ClaimSummary from './Card/ClaimSummary.vue'; import ClaimSummary from './Card/ClaimSummary.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { getUrl } from 'src/composables/getUrl';
const stateStore = useStateStore(); const stateStore = useStateStore();
const router = useRouter(); const router = useRouter();

View File

@ -0,0 +1,2 @@
Search claim: Buscar reclamación
You can search by claim id or customer name: Puedes buscar por id de la reclamación o nombre del cliente

View File

@ -202,9 +202,9 @@ const toCustomerAddressEdit = (addressId) => {
<div v-if="item.observations.length"> <div v-if="item.observations.length">
<div <div
:key="index" :key="obIndex"
class="flex q-mb-sm" class="flex q-mb-sm"
v-for="(observation, index) in item.observations" v-for="(observation, obIndex) in item.observations"
> >
<div class="text-weight-bold q-mr-sm"> <div class="text-weight-bold q-mr-sm">
{{ observation.observationType.description }}: {{ observation.observationType.description }}:

View File

@ -11,14 +11,18 @@ import { useState } from 'src/composables/useState';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useValidator } from 'src/composables/useValidator'; import { useValidator } from 'src/composables/useValidator';
import { usePrintService } from 'src/composables/usePrintService'; import { usePrintService } from 'src/composables/usePrintService';
import { useSession } from 'src/composables/useSession';
import VnPaginate from 'src/components/ui/VnPaginate.vue'; import VnPaginate from 'src/components/ui/VnPaginate.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import CustomerNewPayment from 'src/pages/Customer/components/CustomerNewPayment.vue'; import CustomerNewPayment from 'src/pages/Customer/components/CustomerNewPayment.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue'; import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue';
const session = useSession();
const tokenMultimedia = session.getTokenMultimedia();
const { sendEmail } = usePrintService(); const { sendEmail } = usePrintService();
const { t } = useI18n(); const { t } = useI18n();
@ -188,6 +192,11 @@ const saveFieldValue = async (row) => {
const sendEmailAction = () => { const sendEmailAction = () => {
sendEmail(`Suppliers/${route.params.id}/campaign-metrics-email`); sendEmail(`Suppliers/${route.params.id}/campaign-metrics-email`);
}; };
const showBalancePdf = (balance) => {
const url = `api/InvoiceOuts/${balance.id}/download?access_token=${tokenMultimedia}`;
window.open(url, '_blank');
};
</script> </script>
<template> <template>
@ -257,17 +266,28 @@ const sendEmailAction = () => {
<QTd align="center"> <QTd align="center">
<QIcon <QIcon
@click.stop="showDialog = true" @click.stop="showDialog = true"
class="q-ml-md" class="q-ml-md fill-icon"
color="primary" color="primary"
name="outgoing_mail" name="outgoing_mail"
size="sm" size="sm"
style="font-variation-settings: 'FILL' 1"
v-if="row.isCompensation" v-if="row.isCompensation"
> >
<QTooltip> <QTooltip>
{{ t('Send compensation') }} {{ t('Send compensation') }}
</QTooltip> </QTooltip>
</QIcon> </QIcon>
<QIcon
@click="showBalancePdf(row)"
class="q-ml-md fill-icon"
color="primary"
name="cloud_download"
size="sm"
v-if="row.hasPdf"
>
<QTooltip>
{{ t('globals.downloadPdf') }}
</QTooltip>
</QIcon>
<QDialog v-model="showDialog"> <QDialog v-model="showDialog">
<QCard class="q-pa-sm"> <QCard class="q-pa-sm">
@ -316,7 +336,7 @@ const sendEmailAction = () => {
<QDrawer :width="256" show-if-above side="right" v-model="stateStore.rightDrawer"> <QDrawer :width="256" show-if-above side="right" v-model="stateStore.rightDrawer">
<div class="q-mt-xl q-px-md"> <div class="q-mt-xl q-px-md">
<VnSelectFilter <VnSelect
:label="t('Company')" :label="t('Company')"
:options="companiesOptions" :options="companiesOptions"
@update:model-value="updateCompanyId($event)" @update:model-value="updateCompanyId($event)"

View File

@ -70,7 +70,6 @@ const filterOptions = {
<FormModel :url="`Clients/${route.params.id}`" auto-load model="customer"> <FormModel :url="`Clients/${route.params.id}`" auto-load model="customer">
<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">
<div class="col">
<VnInput <VnInput
:label="t('Comercial name')" :label="t('Comercial name')"
:rules="validate('client.socialName')" :rules="validate('client.socialName')"
@ -78,8 +77,7 @@ const filterOptions = {
clearable clearable
v-model="data.name" v-model="data.name"
/> />
</div>
<div class="col">
<QSelect <QSelect
:input-debounce="0" :input-debounce="0"
:label="t('customer.basicData.businessType')" :label="t('customer.basicData.businessType')"
@ -91,18 +89,14 @@ const filterOptions = {
option-value="code" option-value="code"
v-model="data.businessTypeFk" v-model="data.businessTypeFk"
/> />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput <VnInput
:label="t('customer.basicData.contact')" :label="t('customer.basicData.contact')"
:rules="validate('client.contact')" :rules="validate('client.contact')"
clearable clearable
v-model="data.contact" v-model="data.contact"
/> />
</div>
<div class="col">
<VnInput <VnInput
:label="t('customer.basicData.email')" :label="t('customer.basicData.email')"
:rules="validate('client.email')" :rules="validate('client.email')"
@ -118,28 +112,22 @@ const filterOptions = {
</QIcon> </QIcon>
</template> </template>
</VnInput> </VnInput>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput <VnInput
:label="t('customer.basicData.phone')" :label="t('customer.basicData.phone')"
:rules="validate('client.phone')" :rules="validate('client.phone')"
clearable clearable
v-model="data.phone" v-model="data.phone"
/> />
</div>
<div class="col">
<VnInput <VnInput
:label="t('customer.basicData.mobile')" :label="t('customer.basicData.mobile')"
:rules="validate('client.mobile')" :rules="validate('client.mobile')"
clearable clearable
v-model="data.mobile" v-model="data.mobile"
/> />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QSelect <QSelect
:input-debounce="0" :input-debounce="0"
:label="t('customer.basicData.salesPerson')" :label="t('customer.basicData.salesPerson')"
@ -163,23 +151,19 @@ const filterOptions = {
</QAvatar> </QAvatar>
</template> </template>
</QSelect> </QSelect>
</div>
<div class="col">
<QSelect <QSelect
:input-debounce="0"
:label="t('customer.basicData.contactChannel')"
:options="contactChannels"
:rules="validate('client.contactChannelFk')"
emit-value
map-options
option-label="name"
option-value="id"
v-model="data.contactChannelFk" v-model="data.contactChannelFk"
:options="contactChannels"
option-value="id"
option-label="name"
emit-value
:label="t('customer.basicData.contactChannel')"
map-options
:rules="validate('client.contactChannelFk')"
:input-debounce="0"
/> />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QSelect <QSelect
:input-debounce="0" :input-debounce="0"
:label="t('customer.basicData.previousClient')" :label="t('customer.basicData.previousClient')"
@ -201,7 +185,6 @@ const filterOptions = {
</QIcon> </QIcon>
</template> </template>
</QSelect> </QSelect>
</div>
</VnRow> </VnRow>
</template> </template>
</FormModel> </FormModel>

View File

@ -7,7 +7,7 @@ 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 VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CreateBankEntityForm from 'src/components/CreateBankEntityForm.vue'; import CreateBankEntityForm from 'src/components/CreateBankEntityForm.vue';
@ -48,8 +48,7 @@ const getBankEntities = (data, formData) => {
> >
<template #form="{ data, validate }"> <template #form="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelectFilter
:label="t('Billing data')" :label="t('Billing data')"
:options="payMethods" :options="payMethods"
hide-selected hide-selected
@ -57,14 +56,10 @@ const getBankEntities = (data, formData) => {
option-value="id" option-value="id"
v-model="data.payMethod" v-model="data.payMethod"
/> />
</div>
<div class="col">
<VnInput :label="t('Due day')" clearable v-model="data.dueDay" /> <VnInput :label="t('Due day')" clearable v-model="data.dueDay" />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput :label="t('IBAN')" clearable v-model="data.iban"> <VnInput :label="t('IBAN')" clearable v-model="data.iban">
<template #append> <template #append>
<QIcon name="info" class="cursor-info"> <QIcon name="info" class="cursor-info">
@ -72,8 +67,6 @@ const getBankEntities = (data, formData) => {
</QIcon> </QIcon>
</template> </template>
</VnInput> </VnInput>
</div>
<div class="col">
<VnSelectDialog <VnSelectDialog
:label="t('Swift / BIC')" :label="t('Swift / BIC')"
:options="bankEntitiesOptions" :options="bankEntitiesOptions"
@ -93,29 +86,18 @@ const getBankEntities = (data, formData) => {
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
<QItemSection v-if="scope.opt"> <QItemSection v-if="scope.opt">
<QItemLabel <QItemLabel
>{{ scope.opt.bic }} >{{ scope.opt.bic }} {{ scope.opt.name }}</QItemLabel
{{ scope.opt.name }}</QItemLabel
> >
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
</VnSelectDialog> </VnSelectDialog>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QCheckbox :label="t('Received LCR')" v-model="data.hasLcr" /> <QCheckbox :label="t('Received LCR')" v-model="data.hasLcr" />
</div> <QCheckbox :label="t('VNL core received')" v-model="data.hasCoreVnl" />
<div class="col">
<QCheckbox
:label="t('VNL core received')"
v-model="data.hasCoreVnl"
/>
</div>
<div class="col">
<QCheckbox :label="t('VNL B2B received')" v-model="data.hasSepaVnl" /> <QCheckbox :label="t('VNL B2B received')" v-model="data.hasSepaVnl" />
</div>
</VnRow> </VnRow>
</template> </template>
</FormModel> </FormModel>

View File

@ -1,43 +1,17 @@
<script setup> <script setup>
import { useI18n } from 'vue-i18n'; import VnCard from 'components/common/VnCard.vue';
import { useStateStore } from 'stores/useStateStore';
import CustomerDescriptor from './CustomerDescriptor.vue'; import CustomerDescriptor from './CustomerDescriptor.vue';
import LeftMenu from 'components/LeftMenu.vue'; import CustomerFilter from '../CustomerFilter.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import useCardSize from 'src/composables/useCardSize';
const stateStore = useStateStore();
const { t } = useI18n();
</script> </script>
<template> <template>
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()"> <VnCard
<VnSearchbar data-key="Client"
data-key="CustomerList" base-url="Clients"
url="Clients/filter" :descriptor="CustomerDescriptor"
:label="t('Search customer')" :filter-panel="CustomerFilter"
:info="t('You can search by customer id or name')" search-data-key="CustomerList"
search-url="Clients/filter"
searchbar-label="Search customer"
searchbar-info="You can search by customer id or name"
/> />
</Teleport>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit">
<CustomerDescriptor />
<QSeparator />
<LeftMenu source="card" />
</QScrollArea>
</QDrawer>
<QPageContainer>
<QPage>
<VnSubToolbar />
<div :class="useCardSize()">
<RouterView></RouterView>
</div>
</QPage>
</QPageContainer>
</template> </template>
<i18n>
es:
Search customer: Buscar cliente
You can search by customer id or name: Puedes buscar por id o nombre del cliente
</i18n>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue'; import { ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@ -11,10 +11,6 @@ const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const customerContactsRef = ref(null); const customerContactsRef = ref(null);
onMounted(() => {
if (customerContactsRef.value) customerContactsRef.value.reload();
});
</script> </script>
<template> <template>
<div class="full-width flex justify-center"> <div class="full-width flex justify-center">
@ -30,6 +26,7 @@ onMounted(() => {
model="CustomerContacts" model="CustomerContacts"
ref="customerContactsRef" ref="customerContactsRef"
url="ClientContacts" url="ClientContacts"
auto-load
> >
<template #body="{ rows }"> <template #body="{ rows }">
<QCard class="q-pl-lg q-py-md"> <QCard class="q-pl-lg q-py-md">

View File

@ -105,7 +105,7 @@ const updateData = () => {
color="primary" color="primary"
name="lock" name="lock"
size="md" size="md"
style="font-variation-settings: 'FILL' 1" class="fill-icon"
> >
<QTooltip>{{ t('Close contract') }}</QTooltip> <QTooltip>{{ t('Close contract') }}</QTooltip>
</QIcon> </QIcon>

View File

@ -7,7 +7,7 @@ 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 VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnLocation from 'src/components/common/VnLocation.vue'; import VnLocation from 'src/components/common/VnLocation.vue';
const { t } = useI18n(); const { t } = useI18n();
@ -41,7 +41,6 @@ function handleLocation(data, location) {
> >
<template #form="{ data, validate }"> <template #form="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput <VnInput
:label="t('Social name')" :label="t('Social name')"
:required="true" :required="true"
@ -55,21 +54,15 @@ function handleLocation(data, location) {
</QIcon> </QIcon>
</template> </template>
</VnInput> </VnInput>
</div>
<div class="col">
<VnInput :label="t('Tax number')" clearable v-model="data.fi" /> <VnInput :label="t('Tax number')" clearable v-model="data.fi" />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput :label="t('Street')" clearable v-model="data.street" /> <VnInput :label="t('Street')" clearable v-model="data.street" />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelectFilter
:label="t('Sage tax type')" :label="t('Sage tax type')"
:options="typesTaxes" :options="typesTaxes"
hide-selected hide-selected
@ -77,9 +70,7 @@ function handleLocation(data, location) {
option-value="id" option-value="id"
v-model="data.sageTaxTypeFk" v-model="data.sageTaxTypeFk"
/> />
</div> <VnSelect
<div class="col">
<VnSelectFilter
:label="t('Sage transaction type')" :label="t('Sage transaction type')"
:options="typesTransactions" :options="typesTransactions"
hide-selected hide-selected
@ -97,12 +88,10 @@ function handleLocation(data, location) {
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
</VnSelectFilter> </VnSelect>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnLocation <VnLocation
:rules="validate('Worker.postcode')" :rules="validate('Worker.postcode')"
:roles-allowed-to-create="['deliveryAssistant']" :roles-allowed-to-create="['deliveryAssistant']"
@ -111,23 +100,16 @@ function handleLocation(data, location) {
@update:model-value="(location) => handleLocation(data, location)" @update:model-value="(location) => handleLocation(data, location)"
> >
</VnLocation> </VnLocation>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow>
<div class="col">
<QCheckbox :label="t('Active')" v-model="data.isActive" /> <QCheckbox :label="t('Active')" v-model="data.isActive" />
</div>
<div class="col">
<QCheckbox :label="t('Frozen')" v-model="data.isFreezed" /> <QCheckbox :label="t('Frozen')" v-model="data.isFreezed" />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow>
<div class="col">
<QCheckbox :label="t('Has to invoice')" v-model="data.hasToInvoice" /> <QCheckbox :label="t('Has to invoice')" v-model="data.hasToInvoice" />
</div> <div>
<div class="col">
<QCheckbox :label="t('Vies')" v-model="data.isVies" /> <QCheckbox :label="t('Vies')" v-model="data.isVies" />
<QIcon name="info" class="cursor-info q-ml-sm" size="sm"> <QIcon name="info" class="cursor-info q-ml-sm" size="sm">
<QTooltip> <QTooltip>
@ -137,23 +119,16 @@ function handleLocation(data, location) {
</div> </div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow>
<div class="col"> <QCheckbox :label="t('Notify by email')" v-model="data.isToBeMailed" />
<QCheckbox
:label="t('Notify by email')"
v-model="data.isToBeMailed"
/>
</div>
<div class="col">
<QCheckbox <QCheckbox
:label="t('Invoice by address')" :label="t('Invoice by address')"
v-model="data.hasToInvoiceByAddress" v-model="data.hasToInvoiceByAddress"
/> />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow>
<div class="col"> <div>
<QCheckbox <QCheckbox
:label="t('Is equalizated')" :label="t('Is equalizated')"
v-model="data.isEqualizated" v-model="data.isEqualizated"
@ -164,27 +139,18 @@ function handleLocation(data, location) {
</QTooltip> </QTooltip>
</QIcon> </QIcon>
</div> </div>
<div class="col"> <QCheckbox :label="t('Verified data')" v-model="data.isTaxDataChecked" />
<QCheckbox
:label="t('Verified data')"
v-model="data.isTaxDataChecked"
/>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow>
<div class="col">
<QCheckbox <QCheckbox
:label="t('Incoterms authorization')" :label="t('Incoterms authorization')"
v-model="data.hasIncoterms" v-model="data.hasIncoterms"
/> />
</div>
<div class="col">
<QCheckbox <QCheckbox
:label="t('Electronic invoice')" :label="t('Electronic invoice')"
v-model="data.hasElectronicInvoice" v-model="data.hasElectronicInvoice"
/> />
</div>
</VnRow> </VnRow>
</template> </template>
</FormModel> </FormModel>

View File

@ -1,20 +1,17 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute } from 'vue-router';
import { QBtn } from 'quasar'; import { QBtn } from 'quasar';
import { useStateStore } from 'src/stores/useStateStore';
import { toCurrency } from 'src/filters'; import { toCurrency } from 'src/filters';
import { toDateTimeFormat } from 'src/filters/date'; import { toDateTimeFormat } from 'src/filters/date';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const stateStore = computed(() => useStateStore());
const rows = ref([]); const rows = ref([]);
const totalAmount = ref(0); const totalAmount = ref(0);
@ -105,28 +102,40 @@ const columns = computed(() => [
const setRows = (data) => { const setRows = (data) => {
rows.value = data; rows.value = data;
totalAmount.value = data.reduce((accumulator, currentValue) => { totalAmount.value = data.reduce((acc, { amount = 0 }) => acc + amount, 0);
return accumulator + currentValue.amount;
}, 0);
};
const toCustomerGreugeCreate = () => {
router.push({ name: 'CustomerGreugeCreate' });
}; };
</script> </script>
<template> <template>
<FetchData :filter="filter" @on-fetch="setRows" auto-load url="greuges" /> <FetchData :filter="filter" @on-fetch="setRows" auto-load url="greuges" />
<template v-if="stateStore.isHeaderMounted()">
<div class="full-width flex justify-center"> <Teleport to="#actions-append">
<QPage class="card-width q-pa-lg"> <div class="row q-gutter-x-sm">
<QCard class="full-width q-pa-sm" v-if="totalAmount"> <QBtn
<h6 class="flex justify-end q-my-lg q-pr-lg"> flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="300" show-if-above>
<QCard class="full-width q-pa-sm">
<h6 class="flex justify-end q-my-lg q-pr-lg" v-if="totalAmount">
<span class="color-vn-label q-mr-md">{{ t('Total') }}:</span> <span class="color-vn-label q-mr-md">{{ t('Total') }}:</span>
{{ toCurrency(totalAmount) }} {{ toCurrency(totalAmount) }}
</h6> </h6>
<QSkeleton v-else type="QInput" square />
</QCard> </QCard>
</QDrawer>
<div class="full-width flex justify-center">
<QPage class="card-width q-pa-lg">
<QCard class="q-pa-sm q-mt-md"> <QCard class="q-pa-sm q-mt-md">
<QTable <QTable
:columns="columns" :columns="columns"
@ -164,7 +173,7 @@ const toCustomerGreugeCreate = () => {
</div> </div>
<QPageSticky :offset="[18, 18]"> <QPageSticky :offset="[18, 18]">
<QBtn @click.stop="toCustomerGreugeCreate()" color="primary" fab icon="add" /> <QBtn color="primary" fab icon="add" :to="{ name: 'CustomerGreugeCreate' }" />
<QTooltip> <QTooltip>
{{ t('New greuge') }} {{ t('New greuge') }}
</QTooltip> </QTooltip>

View File

@ -8,7 +8,7 @@ import { useStateStore } from 'stores/useStateStore';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
@ -139,7 +139,7 @@ const setInq = (value, status) => {
</QIcon> </QIcon>
</template> </template>
</VnInput> </VnInput>
<VnSelectFilter <VnSelect
:label="t('Entity')" :label="t('Entity')"
:options="[]" :options="[]"
class="q-mt-md" class="q-mt-md"
@ -179,7 +179,7 @@ const setInq = (value, status) => {
/> />
</div> </div>
<VnSelectFilter <VnSelect
:label="t('User')" :label="t('User')"
:options="[]" :options="[]"
class="q-mt-sm" class="q-mt-sm"

View File

@ -11,7 +11,6 @@ import useNotify from 'src/composables/useNotify';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.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 CustomerChangePassword from 'src/pages/Customer/components/CustomerChangePassword.vue'; import CustomerChangePassword from 'src/pages/Customer/components/CustomerChangePassword.vue';

View File

@ -7,7 +7,6 @@ import axios from 'axios';
import { toCurrency, toDateHourMinSec } from 'src/filters'; import { toCurrency, toDateHourMinSec } from 'src/filters';
import FetchData from 'components/FetchData.vue';
import CustomerCloseIconTooltip from '../components/CustomerCloseIconTooltip.vue'; import CustomerCloseIconTooltip from '../components/CustomerCloseIconTooltip.vue';
import CustomerCheckIconTooltip from '../components/CustomerCheckIconTooltip.vue'; import CustomerCheckIconTooltip from '../components/CustomerCheckIconTooltip.vue';

View File

@ -5,7 +5,7 @@ import { useI18n } from 'vue-i18n';
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 VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnLocation from 'src/components/common/VnLocation.vue'; import VnLocation from 'src/components/common/VnLocation.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
@ -49,11 +49,8 @@ function handleLocation(data, location) {
> >
<template #form="{ data, validate }"> <template #form="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QInput :label="t('Comercial name')" v-model="data.name" /> <QInput :label="t('Comercial name')" v-model="data.name" />
</div> <VnSelect
<div class="col">
<VnSelectFilter
:label="t('Salesperson')" :label="t('Salesperson')"
:options="workersOptions" :options="workersOptions"
hide-selected hide-selected
@ -61,11 +58,9 @@ function handleLocation(data, location) {
option-value="id" option-value="id"
v-model="data.salesPersonFk" v-model="data.salesPersonFk"
/> />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col"> <VnSelect
<VnSelectFilter
:label="t('Business type')" :label="t('Business type')"
:options="businessTypesOptions" :options="businessTypesOptions"
hide-selected hide-selected
@ -73,49 +68,35 @@ function handleLocation(data, location) {
option-value="code" option-value="code"
v-model="data.businessTypeFk" v-model="data.businessTypeFk"
/> />
</div>
<div class="col">
<QInput v-model="data.fi" :label="t('Tax number')" /> <QInput v-model="data.fi" :label="t('Tax number')" />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QInput <QInput
:label="t('Business name')" :label="t('Business name')"
:rules="validate('client.socialName')" :rules="validate('client.socialName')"
v-model="data.socialName" v-model="data.socialName"
/> />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QInput <QInput
:label="t('Street')" :label="t('Street')"
:rules="validate('client.street')" :rules="validate('client.street')"
v-model="data.street" v-model="data.street"
/> />
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnLocation <VnLocation
:rules="validate('Worker.postcode')" :rules="validate('Worker.postcode')"
:roles-allowed-to-create="['deliveryAssistant']" :roles-allowed-to-create="['deliveryAssistant']"
:options="postcodesOptions" :options="postcodesOptions"
v-model="data.location" v-model="data.location"
@update:model-value=" @update:model-value="(location) => handleLocation(data, location)"
(location) => handleLocation(data, location)
"
> >
</VnLocation> </VnLocation>
</div>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QInput v-model="data.userName" :label="t('Web user')" /> <QInput v-model="data.userName" :label="t('Web user')" />
</div>
<div class="col">
<QInput <QInput
:label="t('Email')" :label="t('Email')"
:rules="validate('client.email')" :rules="validate('client.email')"
@ -131,7 +112,6 @@ function handleLocation(data, location) {
</QIcon> </QIcon>
</template> </template>
</QInput> </QInput>
</div>
</VnRow> </VnRow>
<QCheckbox <QCheckbox
:label="t('Is equalizated')" :label="t('Is equalizated')"

View File

@ -4,7 +4,7 @@ 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 VnSelectFilter from 'components/common/VnSelectFilter.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
const { t } = useI18n(); const { t } = useI18n();
@ -69,7 +69,7 @@ const zones = ref();
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
<QItemSection v-if="workers"> <QItemSection v-if="workers">
<VnSelectFilter <VnSelect
:label="t('Salesperson')" :label="t('Salesperson')"
v-model="params.salesPersonFk" v-model="params.salesPersonFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
@ -92,7 +92,7 @@ const zones = ref();
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
<QItemSection v-if="provinces"> <QItemSection v-if="provinces">
<VnSelectFilter <VnSelect
:label="t('Province')" :label="t('Province')"
v-model="params.provinceFk" v-model="params.provinceFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
@ -139,7 +139,7 @@ const zones = ref();
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
<QItemSection v-if="zones"> <QItemSection v-if="zones">
<VnSelectFilter <VnSelect
:label="t('Zone')" :label="t('Zone')"
v-model="params.zoneFk" v-model="params.zoneFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"

View File

@ -15,10 +15,3 @@ const stateStore = useStateStore();
<RouterView></RouterView> <RouterView></RouterView>
</QPageContainer> </QPageContainer>
</template> </template>
<style lang="scss">
#searchbar,
.search-panel {
width: 400px;
}
</style>

View File

@ -3,10 +3,10 @@ import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { QBtn, QCheckbox, useQuasar } from 'quasar'; import { QBtn, QCheckbox, useQuasar } from 'quasar';
import { useStateStore } from 'stores/useStateStore';
import { toCurrency, toDate } from 'filters/index'; import { toCurrency, toDate, dateRange } from 'filters/index';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import FetchData from 'components/FetchData.vue';
import CustomerNotificationsFilter from './CustomerDefaulterFilter.vue'; import CustomerNotificationsFilter from './CustomerDefaulterFilter.vue';
import CustomerBalanceDueTotal from './CustomerBalanceDueTotal.vue'; import CustomerBalanceDueTotal from './CustomerBalanceDueTotal.vue';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
@ -14,13 +14,14 @@ import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.v
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import CustomerDefaulterAddObservation from './CustomerDefaulterAddObservation.vue'; import CustomerDefaulterAddObservation from './CustomerDefaulterAddObservation.vue';
const stateStore = useStateStore();
const { t } = useI18n(); const { t, locale } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
const dataRef = ref(null);
const balanceDueTotal = ref(0); const balanceDueTotal = ref(0);
const selected = ref([]); const selected = ref([]);
const rows = ref([]);
const tableColumnComponents = { const tableColumnComponents = {
client: { client: {
@ -167,39 +168,61 @@ const columns = computed(() => [
}, },
]); ]);
const setRows = (data) => {
rows.value = data;
balanceDueTotal.value = data.reduce((accumulator, currentValue) => {
return accumulator + (currentValue['amount'] || 0);
}, 0);
};
const viewAddObservation = (rowsSelected) => { const viewAddObservation = (rowsSelected) => {
quasar.dialog({ quasar.dialog({
component: CustomerDefaulterAddObservation, component: CustomerDefaulterAddObservation,
componentProps: { componentProps: {
clients: rowsSelected, clients: rowsSelected,
promise: refreshData, promise: async () => await dataRef.value.fetch(),
}, },
}); });
}; };
const refreshData = () => { const onFetch = (data) => {
setRows(); for (const element of data) element.isWorker = element.businessTypeFk === 'worker';
balanceDueTotal.value = data.reduce((acc, { amount = 0 }) => acc + amount, 0);
}; };
const onFetch = (data) => { function exprBuilder(param, value) {
for (const element of data) { switch (param) {
element.isWorker = element.businessTypeFk === 'worker'; case 'clientFk':
return { [`d.${param}`]: value?.id };
case 'creditInsurance':
case 'amount':
case 'workerFk':
case 'countryFk':
case 'payMethod':
case 'salesPersonFk':
return { [`d.${param}`]: value };
case 'date':
return { 'd.created': { between: dateRange(value) } };
case 'defaulterSinced':
return { 'd.defaulterSinced': { between: dateRange(value) } };
} }
rows.value = data; }
};
</script> </script>
<template> <template>
<FetchData :filter="filter" @on-fetch="onFetch" auto-load url="Defaulters/filter" /> <template v-if="stateStore.isHeaderMounted()">
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<QDrawer side="right" :width="256" show-if-above> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8"> <QScrollArea class="fit text-grey-8">
<CustomerNotificationsFilter data-key="CustomerDefaulter" /> <CustomerNotificationsFilter data-key="CustomerDefaulter" />
</QScrollArea> </QScrollArea>
@ -214,12 +237,25 @@ const onFetch = (data) => {
icon="vn:notes" icon="vn:notes"
:disabled="!selected.length" :disabled="!selected.length"
@click.stop="viewAddObservation(selected)" @click.stop="viewAddObservation(selected)"
/> >
<QTooltip>{{ t('Add observation') }}</QTooltip>
</QBtn>
</div> </div>
</template> </template>
</VnSubToolbar> </VnSubToolbar>
<QPage class="column items-center q-pa-md"> <QPage class="column items-center q-pa-md">
<VnPaginate
ref="dataRef"
@on-fetch="onFetch"
data-key="CustomerDefaulter"
:filter="filter"
:expr-builder="exprBuilder"
auto-load
url="Defaulters/filter"
>
<template #body="{ rows }">
<div class="q-pa-md">
<QTable <QTable
:columns="columns" :columns="columns"
:rows="rows" :rows="rows"
@ -233,9 +269,15 @@ const onFetch = (data) => {
<QTh> <QTh>
<QCheckbox v-model="props.selected" /> <QCheckbox v-model="props.selected" />
</QTh> </QTh>
<QTh v-for="col in props.cols" :key="col.name" :props="props"> <QTh
v-for="col in props.cols"
:key="col.name"
:props="props"
>
{{ t(col.label) }} {{ t(col.label) }}
<QTooltip v-if="col.tooltip">{{ col.tooltip }}</QTooltip> <QTooltip v-if="col.tooltip">{{
col.tooltip
}}</QTooltip>
</QTh> </QTh>
</QTr> </QTr>
</template> </template>
@ -244,17 +286,33 @@ const onFetch = (data) => {
<QTd :props="props"> <QTd :props="props">
<QTr :props="props" class="cursor-pointer"> <QTr :props="props" class="cursor-pointer">
<component <component
:is="tableColumnComponents[props.col.name].component" :is="
tableColumnComponents[props.col.name]
.component
"
class="col-content" class="col-content"
v-bind="tableColumnComponents[props.col.name].props(props)" v-bind="
@click="tableColumnComponents[props.col.name].event(props)" tableColumnComponents[props.col.name].props(
props
)
"
@click="
tableColumnComponents[props.col.name].event(
props
)
"
> >
<template v-if="props.col.name !== 'isWorker'"> <template v-if="props.col.name !== 'isWorker'">
<div v-if="props.col.name === 'lastObservation'"> <div
v-if="
props.col.name === 'lastObservation'
"
>
<VnInput <VnInput
type="textarea" type="textarea"
v-model="props.value" v-model="props.value"
autogrow autogrow
:disable="true"
/> />
</div> </div>
<div v-else>{{ props.value }}</div> <div v-else>{{ props.value }}</div>
@ -277,6 +335,9 @@ const onFetch = (data) => {
</QTd> </QTd>
</template> </template>
</QTable> </QTable>
</div>
</template>
</VnPaginate>
</QPage> </QPage>
</template> </template>
@ -289,6 +350,7 @@ const onFetch = (data) => {
<i18n> <i18n>
es: es:
Add observation: Añadir observación
Client: Cliente Client: Cliente
Is worker: Es trabajador Is worker: Es trabajador
Salesperson: Comercial Salesperson: Comercial

View File

@ -61,13 +61,11 @@ const onSubmit = async () => {
}} }}
</div> </div>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QInput <QInput
:label="t('Message')" :label="t('Message')"
type="textarea" type="textarea"
v-model="newObservation" v-model="newObservation"
/> />
</div>
</VnRow> </VnRow>
<div class="q-mt-lg row justify-end"> <div class="q-mt-lg row justify-end">
<QBtn <QBtn

View File

@ -6,7 +6,7 @@ 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';
import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnSelectFilter from 'components/common/VnSelectFilter.vue'; import VnSelect from 'components/common/VnSelect.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -48,7 +48,7 @@ const authors = ref();
<template #body="{ params }"> <template #body="{ params }">
<QItem class="q-mb-sm q-mt-sm"> <QItem class="q-mb-sm q-mt-sm">
<QItemSection v-if="clients"> <QItemSection v-if="clients">
<VnSelectFilter <VnSelect
:input-debounce="0" :input-debounce="0"
:label="t('Client')" :label="t('Client')"
:options="clients" :options="clients"
@ -57,7 +57,7 @@ const authors = ref();
hide-selected hide-selected
map-options map-options
option-label="name" option-label="name"
option-value="clientTypeFk" option-value="id"
outlined outlined
rounded rounded
use-input use-input
@ -71,7 +71,7 @@ const authors = ref();
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<QItemSection v-if="salespersons"> <QItemSection v-if="salespersons">
<VnSelectFilter <VnSelect
:input-debounce="0" :input-debounce="0"
:label="t('Salesperson')" :label="t('Salesperson')"
:options="salespersons" :options="salespersons"
@ -94,7 +94,7 @@ const authors = ref();
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<QItemSection v-if="countries"> <QItemSection v-if="countries">
<VnSelectFilter <VnSelect
:input-debounce="0" :input-debounce="0"
:label="t('Country')" :label="t('Country')"
:options="countries" :options="countries"
@ -139,7 +139,7 @@ const authors = ref();
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<QItemSection v-if="authors"> <QItemSection v-if="authors">
<VnSelectFilter <VnSelect
:input-debounce="0" :input-debounce="0"
:label="t('Author')" :label="t('Author')"
:options="authors" :options="authors"
@ -162,7 +162,7 @@ const authors = ref();
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<QItemSection> <QItemSection>
<VnInput <VnInputDate
:label="t('L. O. Date')" :label="t('L. O. Date')"
clearable clearable
is-outlined is-outlined

View File

@ -10,7 +10,7 @@ import CustomerExtendedListActions from './CustomerExtendedListActions.vue';
import CustomerExtendedListFilter from './CustomerExtendedListFilter.vue'; import CustomerExtendedListFilter from './CustomerExtendedListFilter.vue';
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue'; import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import { useArrayData } from 'composables/useArrayData'; import { useArrayData } from 'composables/useArrayData';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { toDate } from 'src/filters'; import { toDate } from 'src/filters';
@ -37,8 +37,6 @@ onMounted(() => {
allColumnNames.value = filteredColumns.map((col) => col.name); allColumnNames.value = filteredColumns.map((col) => col.name);
}); });
const rows = computed(() => arrayData.value.store.data);
const selectedCustomerId = ref(0); const selectedCustomerId = ref(0);
const selectedSalesPersonId = ref(0); const selectedSalesPersonId = ref(0);
const allColumnNames = ref([]); const allColumnNames = ref([]);
@ -70,6 +68,11 @@ const tableColumnComponents = {
props: () => {}, props: () => {},
event: () => {}, event: () => {},
}, },
socialName: {
component: 'span',
props: () => {},
event: () => {},
},
fi: { fi: {
component: 'span', component: 'span',
props: () => {}, props: () => {},
@ -283,6 +286,12 @@ const columns = computed(() => [
label: t('customer.extendedList.tableVisibleColumns.name'), label: t('customer.extendedList.tableVisibleColumns.name'),
name: 'name', name: 'name',
}, },
{
align: 'left',
field: 'socialName',
label: t('customer.extendedList.tableVisibleColumns.socialName'),
name: 'socialName',
},
{ {
align: 'left', align: 'left',
field: 'fi', field: 'fi',
@ -485,6 +494,23 @@ const selectSalesPersonId = (id) => (selectedSalesPersonId.value = id);
</script> </script>
<template> <template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport></template
>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8"> <QScrollArea class="fit text-grey-8">
<CustomerExtendedListFilter <CustomerExtendedListFilter
@ -495,7 +521,7 @@ const selectSalesPersonId = (id) => (selectedSalesPersonId.value = id);
</QScrollArea> </QScrollArea>
</QDrawer> </QDrawer>
<VnSubToolbar> <VnSubToolbar>
<template #st-actions> <template #st-data>
<TableVisibleColumns <TableVisibleColumns
:all-columns="allColumnNames" :all-columns="allColumnNames"
table-code="clientsDetail" table-code="clientsDetail"
@ -508,6 +534,13 @@ const selectSalesPersonId = (id) => (selectedSalesPersonId.value = id);
</VnSubToolbar> </VnSubToolbar>
<QPage class="column items-center q-pa-md"> <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 <QTable
:columns="columns" :columns="columns"
:rows="rows" :rows="rows"
@ -521,13 +554,32 @@ const selectSalesPersonId = (id) => (selectedSalesPersonId.value = id);
{{ value }} {{ value }}
</QTd> </QTd>
</template> </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"> <template #body-cell-id="props">
<QTd @click="stopEventPropagation($event, props.col)"> <QTd @click="stopEventPropagation($event, props.col)">
<component <component
:is="tableColumnComponents[props.col.name].component" :is="tableColumnComponents[props.col.name].component"
class="col-content" class="col-content"
v-bind="tableColumnComponents[props.col.name].props(props)" v-bind="
@click="tableColumnComponents[props.col.name].event(props)" tableColumnComponents[props.col.name].props(props)
"
@click="
tableColumnComponents[props.col.name].event(props)
"
> >
<CustomerDescriptorProxy :id="props.row.id" /> <CustomerDescriptorProxy :id="props.row.id" />
{{ props.row.id }} {{ props.row.id }}
@ -540,10 +592,16 @@ const selectSalesPersonId = (id) => (selectedSalesPersonId.value = id);
v-if="props.row.salesPerson" v-if="props.row.salesPerson"
class="col-content" class="col-content"
:is="tableColumnComponents[props.col.name].component" :is="tableColumnComponents[props.col.name].component"
v-bind="tableColumnComponents[props.col.name].props(props)" v-bind="
@click="tableColumnComponents[props.col.name].event(props)" tableColumnComponents[props.col.name].props(props)
"
@click="
tableColumnComponents[props.col.name].event(props)
"
> >
<WorkerDescriptorProxy :id="props.row.salesPersonFk" /> <WorkerDescriptorProxy
:id="props.row.salesPersonFk"
/>
{{ props.row.salesPerson }} {{ props.row.salesPerson }}
</component> </component>
<span class="col-content" v-else>-</span> <span class="col-content" v-else>-</span>
@ -554,12 +612,19 @@ const selectSalesPersonId = (id) => (selectedSalesPersonId.value = id);
<component <component
:is="tableColumnComponents[props.col.name].component" :is="tableColumnComponents[props.col.name].component"
class="col-content" class="col-content"
v-bind="tableColumnComponents[props.col.name].props(props)" v-bind="
@click="tableColumnComponents[props.col.name].event(props)" tableColumnComponents[props.col.name].props(props)
"
@click="
tableColumnComponents[props.col.name].event(props)
"
/> />
</QTd> </QTd>
</template> </template>
</QTable> </QTable>
</div>
</template>
</VnPaginate>
</QPage> </QPage>
</template> </template>

View File

@ -4,7 +4,7 @@ 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 VnSelectFilter from 'components/common/VnSelectFilter.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import { dateRange } from 'src/filters'; import { dateRange } from 'src/filters';
@ -169,7 +169,7 @@ const shouldRenderColumn = (colName) => {
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
<QItemSection v-if="clients"> <QItemSection v-if="clients">
<VnSelectFilter <VnSelect
:label="t('Social name')" :label="t('Social name')"
v-model="params.socialName" v-model="params.socialName"
@update:model-value="searchFn()" @update:model-value="searchFn()"
@ -201,7 +201,7 @@ const shouldRenderColumn = (colName) => {
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
<QItemSection v-if="workers"> <QItemSection v-if="workers">
<VnSelectFilter <VnSelect
:label=" :label="
t('customer.extendedList.tableVisibleColumns.salesPersonFk') t('customer.extendedList.tableVisibleColumns.salesPersonFk')
" "
@ -270,7 +270,7 @@ const shouldRenderColumn = (colName) => {
</QItem> </QItem>
<QItem v-if="shouldRenderColumn('countryFk')"> <QItem v-if="shouldRenderColumn('countryFk')">
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelect
:label="t('customer.extendedList.tableVisibleColumns.countryFk')" :label="t('customer.extendedList.tableVisibleColumns.countryFk')"
v-model="params.countryFk" v-model="params.countryFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
@ -287,7 +287,7 @@ const shouldRenderColumn = (colName) => {
</QItem> </QItem>
<QItem v-if="shouldRenderColumn('provinceFk')"> <QItem v-if="shouldRenderColumn('provinceFk')">
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelect
:label="t('customer.extendedList.tableVisibleColumns.provinceFk')" :label="t('customer.extendedList.tableVisibleColumns.provinceFk')"
v-model="params.provinceFk" v-model="params.provinceFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
@ -342,7 +342,7 @@ const shouldRenderColumn = (colName) => {
</QItem> </QItem>
<QItem v-if="shouldRenderColumn('businessTypeFk')"> <QItem v-if="shouldRenderColumn('businessTypeFk')">
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelect
:label=" :label="
t('customer.extendedList.tableVisibleColumns.businessTypeFk') t('customer.extendedList.tableVisibleColumns.businessTypeFk')
" "
@ -361,7 +361,7 @@ const shouldRenderColumn = (colName) => {
</QItem> </QItem>
<QItem v-if="shouldRenderColumn('payMethodFk')"> <QItem v-if="shouldRenderColumn('payMethodFk')">
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelect
:label=" :label="
t('customer.extendedList.tableVisibleColumns.payMethodFk') t('customer.extendedList.tableVisibleColumns.payMethodFk')
" "
@ -380,7 +380,7 @@ const shouldRenderColumn = (colName) => {
</QItem> </QItem>
<QItem v-if="shouldRenderColumn('sageTaxTypeFk')"> <QItem v-if="shouldRenderColumn('sageTaxTypeFk')">
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelect
:label=" :label="
t('customer.extendedList.tableVisibleColumns.sageTaxTypeFk') t('customer.extendedList.tableVisibleColumns.sageTaxTypeFk')
" "
@ -399,7 +399,7 @@ const shouldRenderColumn = (colName) => {
</QItem> </QItem>
<QItem v-if="shouldRenderColumn('sageTransactionTypeFk')"> <QItem v-if="shouldRenderColumn('sageTransactionTypeFk')">
<QItemSection> <QItemSection>
<VnSelectFilter <VnSelect
:label=" :label="
t( t(
'customer.extendedList.tableVisibleColumns.sageTransactionTypeFk' 'customer.extendedList.tableVisibleColumns.sageTransactionTypeFk'

View File

@ -1,17 +1,16 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { QBtn } from 'quasar'; import { QBtn } from 'quasar';
import FetchData from 'components/FetchData.vue';
import CustomerNotificationsFilter from './CustomerNotificationsFilter.vue'; import CustomerNotificationsFilter from './CustomerNotificationsFilter.vue';
import CustomerDescriptorProxy from '../Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from '../Card/CustomerDescriptorProxy.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { useStateStore } from 'stores/useStateStore';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import CustomerNotificationsCampaignConsumption from './CustomerNotificationsCampaignConsumption.vue';
const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const rows = ref([]);
const selected = ref([]); const selected = ref([]);
const selectedCustomerId = ref(0); const selectedCustomerId = ref(0);
@ -82,22 +81,42 @@ const selectCustomerId = (id) => {
</script> </script>
<template> <template>
<FetchData <template v-if="stateStore.isHeaderMounted()">
:filter="filter" <Teleport to="#actions-append">
@on-fetch="(data) => (rows = data)" <div class="row q-gutter-x-sm">
auto-load <QBtn
url="Clients" flat
/> @click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<QDrawer side="right" :width="256" show-if-above> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8"> <QScrollArea class="fit text-grey-8">
<CustomerNotificationsFilter data-key="CustomerNotifications" /> <CustomerNotificationsFilter data-key="CustomerNotifications" />
</QScrollArea> </QScrollArea>
</QDrawer> </QDrawer>
<VnSubToolbar class="justify-end">
<VnSubToolbar /> <template #st-data>
<CustomerNotificationsCampaignConsumption
:selected-rows="selected.length > 0"
:clients="selected"
:promise="refreshData"
/>
</template>
</VnSubToolbar>
<QPage class="column items-center q-pa-md"> <QPage class="column items-center q-pa-md">
<VnPaginate data-key="CustomerNotifications" url="Clients" auto-load>
<template #body="{ rows }">
<div class="q-pa-md">
<QTable <QTable
:columns="columns" :columns="columns"
:rows="rows" :rows="rows"
@ -110,18 +129,34 @@ const selectCustomerId = (id) => {
<QTd :props="props"> <QTd :props="props">
<QTr :props="props" class="cursor-pointer"> <QTr :props="props" class="cursor-pointer">
<component <component
:is="tableColumnComponents[props.col.name].component" :is="
tableColumnComponents[props.col.name]
.component
"
class="col-content" class="col-content"
v-bind="tableColumnComponents[props.col.name].props(props)" v-bind="
@click="tableColumnComponents[props.col.name].event(props)" tableColumnComponents[props.col.name].props(
props
)
"
@click="
tableColumnComponents[props.col.name].event(
props
)
"
> >
{{ props.value }} {{ props.value }}
<CustomerDescriptorProxy :id="selectedCustomerId" /> <CustomerDescriptorProxy
:id="selectedCustomerId"
/>
</component> </component>
</QTr> </QTr>
</QTd> </QTd>
</template> </template>
</QTable> </QTable>
</div>
</template>
</VnPaginate>
</QPage> </QPage>
</template> </template>
@ -140,4 +175,5 @@ es:
Phone: Teléfono Phone: Teléfono
City: Población City: Población
Email: Email Email: Email
Campaign consumption: Consumo campaña
</i18n> </i18n>

View File

@ -0,0 +1,152 @@
<script setup>
import { ref, toRefs } from 'vue';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import useNotify from 'src/composables/useNotify';
import { useValidator } from 'src/composables/useValidator';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'components/common/VnSelect.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import FetchData from 'src/components/FetchData.vue';
import { watch } from 'vue';
import { onMounted } from 'vue';
const $props = defineProps({
clients: {
type: Array,
required: true,
},
promise: {
type: Function,
required: true,
},
selectedRows: {
type: Boolean,
},
});
const { selectedRows } = toRefs($props);
const { notify } = useNotify();
const { t } = useI18n();
const validationsStore = useValidator();
const campaignParams = ref(null);
const campaignsOptions = ref(null);
const moreFields = ref([]);
const popupProxyRef = ref(null);
const upcomingOptions = ref(null);
const campaignChange = ({ name }) => {
const campaign = campaignsOptions.value.find((c) => c.code === name);
handleDates(campaign);
};
const handleDates = (campaign) => {
const from = new Date(campaign.dated);
from.setDate(from.getDate() - campaign.scopeDays);
campaignParams.value = {
code: campaign.code,
from: from,
to: campaign.dated,
};
};
watch(selectedRows, () => {
handleDates(upcomingOptions.value);
});
const onSubmit = async () => {
try {
const data = {
clients: $props.clients.map((item) => item.id),
from: campaignParams.value.from,
to: campaignParams.value.to,
};
const params = JSON.stringify(data);
await axios.post('ClientConsumptionQueues', { params });
notify('globals.dataSaved', 'positive');
popupProxyRef.value.hide();
} catch (error) {
notify(error.message, 'negative');
}
};
onMounted(async () => {
const { models } = validationsStore;
const properties = models.Item?.properties || {};
const _moreFields = ['valentinesDay', 'mothersDay', 'allSaints'];
_moreFields.forEach((field) => {
let prop = properties[field];
const label = t(`params.${field}`);
moreFields.value.push({
name: field,
label,
type: prop ? prop.type : null,
});
});
});
</script>
<template>
<FetchData
url="Campaigns/latest"
@on-fetch="(data) => (campaignsOptions = data)"
:filter="{ fields: ['id', 'code', 'dated'], order: 'code ASC', limit: 30 }"
auto-load
/>
<FetchData
url="Campaigns/upcoming"
@on-fetch="(data) => (upcomingOptions = data)"
auto-load
/>
<QBtn color="primary" icon="show_chart" :disable="!selectedRows">
<QPopupProxy ref="popupProxyRef">
<QCard class="column q-pa-md">
<span class="text-body1 q-mb-sm">{{ t('Campaign consumption') }}</span>
<VnRow class="row q-gutter-md q-mb-md">
<VnSelect
:options="moreFields"
option-value="code"
option-label="label"
v-model="campaignParams.code"
:label="t('Campaign')"
@update:model-value="campaignChange"
/>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnInputDate v-model="campaignParams.from" :label="t('From')" />
<VnInputDate v-model="campaignParams.to" :label="t('To')" />
</VnRow>
<div class="q-mt-lg row justify-end">
<QBtn
:label="t('globals.cancel')"
color="primary"
flat
class="q-mr-md"
v-close-popup
/>
<QBtn
:label="t('globals.save')"
type="submit"
color="primary"
@click="onSubmit()"
/>
</div>
</QCard>
</QPopupProxy>
<QTooltip>{{ t('Campaign consumption') }}</QTooltip>
</QBtn>
</template>
<i18n>
en:
params:
valentinesDay: Valentine's Day
mothersDay: Mother's Day
allSaints: All Saints' Day
es:
params:
valentinesDay: Día de San Valentín
mothersDay: Día de la Madre
allSaints: Día de Todos los Santos
Campaign consumption: Consumo campaña
Campaign: Campaña
From: Desde
To: Hasta
</i18n>

View File

@ -5,7 +5,7 @@ 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';
import VnSelectFilter from 'components/common/VnSelectFilter.vue'; import VnSelect from 'components/common/VnSelect.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -52,7 +52,7 @@ const clients = ref();
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
<QItemSection v-if="clients"> <QItemSection v-if="clients">
<VnSelectFilter <VnSelect
:input-debounce="0" :input-debounce="0"
:label="t('Social name')" :label="t('Social name')"
:options="clients" :options="clients"
@ -76,7 +76,7 @@ const clients = ref();
<QSkeleton type="QInput" class="full-width" /> <QSkeleton type="QInput" class="full-width" />
</QItemSection> </QItemSection>
<QItemSection v-if="cities"> <QItemSection v-if="cities">
<VnSelectFilter <VnSelect
:input-debounce="0" :input-debounce="0"
:label="t('City')" :label="t('City')"
:options="cities" :options="cities"

View File

@ -110,13 +110,7 @@ function stateColor(row) {
</div> </div>
</Teleport> </Teleport>
</template> </template>
<QDrawer <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
v-model="stateStore.rightDrawer"
side="right"
:width="256"
show-if-above
:breakpoint="1600"
>
<QScrollArea class="fit text-grey-8"> <QScrollArea class="fit text-grey-8">
<CustomerPaymentsFilter data-key="CustomerTransactions" /> <CustomerPaymentsFilter data-key="CustomerTransactions" />
</QScrollArea> </QScrollArea>

View File

@ -9,9 +9,8 @@ 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 VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CustomerCreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue';
import CustomerNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue'; import CustomerNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue';
const { t } = useI18n(); const { t } = useI18n();
@ -101,7 +100,7 @@ function handleLocation(data, location) {
/> />
<div class="row justify-between q-gutter-md q-mb-md"> <div class="row justify-between q-gutter-md q-mb-md">
<VnSelectFilter <VnSelect
:label="t('Agency')" :label="t('Agency')"
:options="agencyModes" :options="agencyModes"
:rules="validate('route.agencyFk')" :rules="validate('route.agencyFk')"
@ -121,7 +120,7 @@ function handleLocation(data, location) {
</div> </div>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
<VnSelectFilter <VnSelect
:label="t('Incoterms')" :label="t('Incoterms')"
:options="incoterms" :options="incoterms"
hide-selected hide-selected

Some files were not shown because too many files have changed in this diff Show More