Merge branch 'dev' of https: refs #8224//gitea.verdnatura.es/verdnatura/salix-front into 8224-contextMenu

This commit is contained in:
Alex Moreno 2025-04-24 09:20:54 +02:00
commit de5c62e481
11 changed files with 307 additions and 165 deletions

View File

@ -35,6 +35,7 @@ import { getColAlign } from 'src/composables/getColAlign';
import RightMenu from '../common/RightMenu.vue'; import RightMenu from '../common/RightMenu.vue';
import VnContextMenu from './VnContextMenu.vue'; import VnContextMenu from './VnContextMenu.vue';
import VnScroll from '../common/VnScroll.vue'; import VnScroll from '../common/VnScroll.vue';
import VnMultiCheck from '../common/VnMultiCheck.vue';
const arrayData = useArrayData(useAttrs()['data-key']); const arrayData = useArrayData(useAttrs()['data-key']);
const $props = defineProps({ const $props = defineProps({
@ -114,6 +115,10 @@ const $props = defineProps({
type: Object, type: Object,
default: () => ({}), default: () => ({}),
}, },
multiCheck: {
type: Object,
default: () => ({}),
},
crudModel: { crudModel: {
type: Object, type: Object,
default: () => ({}), default: () => ({}),
@ -158,6 +163,7 @@ const CARD_MODE = 'card';
const TABLE_MODE = 'table'; const TABLE_MODE = 'table';
const mode = ref(CARD_MODE); const mode = ref(CARD_MODE);
const selected = ref([]); const selected = ref([]);
const selectAll = ref(false);
const hasParams = ref(false); const hasParams = ref(false);
const CrudModelRef = ref({}); const CrudModelRef = ref({});
const showForm = ref(false); const showForm = ref(false);
@ -645,6 +651,23 @@ const rowCtrlClickFunction = computed(() => {
}; };
return () => {}; return () => {};
}); });
const handleMultiCheck = (value) => {
if (value) {
selected.value = tableRef.value.rows;
} else {
selected.value = [];
}
emit('update:selected', selected.value);
};
const handleSelectedAll = (data) => {
if (data) {
selected.value = data;
} else {
selected.value = [];
}
emit('update:selected', selected.value);
};
</script> </script>
<template> <template>
<RightMenu v-if="$props.rightSearch" :overlay="overlay"> <RightMenu v-if="$props.rightSearch" :overlay="overlay">
@ -707,6 +730,17 @@ const rowCtrlClickFunction = computed(() => {
:hide-selected-banner="true" :hide-selected-banner="true"
:data-cy :data-cy
> >
<template #header-selection>
<VnMultiCheck
:searchUrl="searchUrl"
:expand="$props.multiCheck.expand"
v-model="selectAll"
:url="$attrs['url']"
@update:selected="handleMultiCheck"
@select:all="handleSelectedAll"
></VnMultiCheck>
</template>
<template #top-left v-if="!$props.withoutHeader"> <template #top-left v-if="!$props.withoutHeader">
<slot name="top-left"> </slot> <slot name="top-left"> </slot>
</template> </template>
@ -1106,7 +1140,9 @@ const rowCtrlClickFunction = computed(() => {
</template> </template>
</FormModelPopup> </FormModelPopup>
</QDialog> </QDialog>
<<<<<<< HEAD
<VnContextMenu ref="contextMenuRef" v-model="arrayData" /> <VnContextMenu ref="contextMenuRef" v-model="arrayData" />
======= >>>>>>> 172b2d157479ccc10c9163961b89d89a6a675e42
<VnScroll <VnScroll
ref="vnScrollRef" ref="vnScrollRef"
v-if="isTableMode" v-if="isTableMode"

View File

@ -0,0 +1,80 @@
<script setup>
import { ref } from 'vue';
import VnCheckbox from './VnCheckbox.vue';
import axios from 'axios';
import { toRaw } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
const route = useRoute();
const { t } = useI18n();
const model = defineModel({ type: [Boolean] });
const props = defineProps({
expand: {
type: Boolean,
default: false,
},
url: {
type: String,
default: null,
required: true,
},
searchUrl: {
type: [String, Boolean],
default: 'table',
},
});
const value = ref(false);
const rows = ref(0);
const onClick = () => {
if (value.value) {
const { filter } = JSON.parse(route.query[props.searchUrl]);
filter.limit = 0;
const params = {
params: { filter: JSON.stringify(filter) },
};
axios
.get(props.url, params)
.then(({ data }) => {
rows.value = data;
})
.catch(console.error);
}
};
defineEmits(['update:selected', 'select:all']);
</script>
<template>
<div style="display: flex">
<VnCheckbox v-model="value" @click="$emit('update:selected', value)" />
<QBtn
v-if="value && $props.expand"
flat
dense
icon="expand_more"
@click="onClick"
>
<QMenu anchor="bottom right" self="top right">
<QList>
<QItem v-ripple clickable @click="$emit('select:all', toRaw(rows))">
{{ t('Select all', { rows: rows.length }) }}
</QItem>
<slot name="more-options"></slot>
</QList>
</QMenu>
</QBtn>
</div>
</template>
<i18n lang="yml">
en:
Select all: 'Select all ({rows})'
fr:
Select all: 'Sélectionner tout ({rows})'
es:
Select all: 'Seleccionar todo ({rows})'
de:
Select all: 'Alle auswählen ({rows})'
it:
Select all: 'Seleziona tutto ({rows})'
pt:
Select all: 'Selecionar tudo ({rows})'
</i18n>

View File

@ -222,7 +222,7 @@ defineExpose({
</script> </script>
<template> <template>
<div class="full-width" v-bind="attrs"> <div class="full-width">
<div <div
v-if="!store.data && !store.data?.length && !isLoading" v-if="!store.data && !store.data?.length && !isLoading"
class="info-row q-pa-md text-center" class="info-row q-pa-md text-center"

View File

@ -100,6 +100,9 @@ const columns = computed(() => [
'row-key': 'id', 'row-key': 'id',
selection: 'multiple', selection: 'multiple',
}" }"
:multi-check="{
expand: true,
}"
v-model:selected="selected" v-model:selected="selected"
:right-search="true" :right-search="true"
:columns="columns" :columns="columns"

View File

@ -98,7 +98,9 @@ onMounted(async () => {
<QBtn color="primary" icon="show_chart" :disable="!selectedRows"> <QBtn color="primary" icon="show_chart" :disable="!selectedRows">
<QPopupProxy ref="popupProxyRef"> <QPopupProxy ref="popupProxyRef">
<QCard class="column q-pa-md"> <QCard class="column q-pa-md">
<span class="text-body1 q-mb-sm">{{ t('Campaign consumption') }}</span> <span class="text-body1 q-mb-sm">{{
t('Campaign consumption', { rows: $props.clients.length })
}}</span>
<VnRow> <VnRow>
<VnSelect <VnSelect
:options="moreFields" :options="moreFields"
@ -140,12 +142,13 @@ onMounted(async () => {
valentinesDay: Valentine's Day valentinesDay: Valentine's Day
mothersDay: Mother's Day mothersDay: Mother's Day
allSaints: All Saints' Day allSaints: All Saints' Day
Campaign consumption: Campaign consumption ({rows})
es: es:
params: params:
valentinesDay: Día de San Valentín valentinesDay: Día de San Valentín
mothersDay: Día de la Madre mothersDay: Día de la Madre
allSaints: Día de Todos los Santos allSaints: Día de Todos los Santos
Campaign consumption: Consumo campaña Campaign consumption: Consumo campaña ({rows})
Campaign: Campaña Campaign: Campaña
From: Desde From: Desde
To: Hasta To: Hasta

View File

@ -25,7 +25,9 @@ const { validate } = useValidator();
const { notify } = useNotify(); const { notify } = useNotify();
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const canEditZone = useAcl().hasAcl('Ticket', 'editZone', 'WRITE'); const canEditZone = useAcl().hasAny([
{ model: 'Ticket', props: 'editZone', accessType: 'WRITE' },
]);
const agencyFetchRef = ref(); const agencyFetchRef = ref();
const warehousesOptions = ref([]); const warehousesOptions = ref([]);
@ -75,8 +77,15 @@ async function getDate(query, params) {
if (!data) return notify(t('basicData.noDeliveryZoneAvailable'), 'negative'); if (!data) return notify(t('basicData.noDeliveryZoneAvailable'), 'negative');
formData.value.zoneFk = data.zoneFk; formData.value.zoneFk = data.zoneFk;
if (data.landed) formData.value.landed = data.landed; formData.value.landed = data.landed;
if (data.shipped) formData.value.shipped = data.shipped; const shippedDate = new Date(params.shipped);
const landedDate = new Date(data.landed);
shippedDate.setHours(
landedDate.getHours(),
landedDate.getMinutes(),
landedDate.getSeconds(),
);
formData.value.shipped = shippedDate.toISOString();
} }
const onChangeZone = async (zoneId) => { const onChangeZone = async (zoneId) => {
@ -125,6 +134,7 @@ const addressId = computed({
formData.value.addressFk = val; formData.value.addressFk = val;
onChangeAddress(val); onChangeAddress(val);
getShipped({ getShipped({
shipped: formData.value?.shipped,
landed: formData.value?.landed, landed: formData.value?.landed,
addressFk: val, addressFk: val,
agencyModeFk: formData.value?.agencyModeFk, agencyModeFk: formData.value?.agencyModeFk,

View File

@ -55,73 +55,75 @@ async function handleSave(e) {
auto-load auto-load
url="ObservationTypes" url="ObservationTypes"
/> />
<div class="flex justify-center"> <div class="full-width flex justify-center">
<CrudModel <QPage class="card-width q-pa-lg">
ref="ticketNotesCrudRef" <CrudModel
data-key="TicketNotes" class="fit"
url="TicketObservations" ref="ticketNotesCrudRef"
model="TicketNotes" data-key="TicketNotes"
:filter="crudModelFilter" url="TicketObservations"
:data-required="crudModelRequiredData" model="TicketNotes"
:default-remove="false" :filter="crudModelFilter"
auto-load :data-required="crudModelRequiredData"
style="max-width: 800px" :default-remove="false"
> auto-load
<template #body="{ rows }"> >
<QCard class="q-px-lg q-py-md"> <template #body="{ rows }">
<div <QCard class="q-px-lg q-py-md">
v-for="(row, index) in rows" <div
:key="index" v-for="(row, index) in rows"
class="q-mb-md row q-gutter-x-md" :key="index"
> class="q-mb-md row items-center q-gutter-x-md"
<VnSelect
:label="t('ticketNotes.observationType')"
:options="observationTypes"
hide-selected
option-label="description"
option-value="id"
v-model="row.observationTypeFk"
:disable="!!row.id"
data-cy="ticketNotesObservationType"
/>
<VnInput
:label="t('basicData.description')"
v-model="row.description"
class="col"
@keydown.enter.stop="handleSave"
autogrow
data-cy="ticketNotesDescription"
/>
<QIcon
name="delete"
size="sm"
class="cursor-pointer"
color="primary"
@click="handleDelete(row)"
data-cy="ticketNotesRemoveNoteBtn"
> >
<QTooltip> <VnSelect
{{ t('ticketNotes.removeNote') }} :label="t('ticketNotes.observationType')"
</QTooltip> :options="observationTypes"
</QIcon> hide-selected
</div> option-label="description"
<VnRow v-if="observationTypes.length > rows.length"> option-value="id"
<QBtn v-model="row.observationTypeFk"
icon="add_circle" :disable="!!row.id"
v-shortcut="'+'" data-cy="ticketNotesObservationType"
flat />
class="fill-icon-on-hover q-ml-md" <VnInput
color="primary" :label="t('basicData.description')"
@click="ticketNotesCrudRef.insert()" v-model="row.description"
data-cy="ticketNotesAddNoteBtn" class="col"
> @keydown.enter.stop="handleSave"
<QTooltip> autogrow
{{ t('ticketNotes.addNote') }} data-cy="ticketNotesDescription"
</QTooltip> />
</QBtn> <QIcon
</VnRow> name="delete"
</QCard> size="sm"
</template> class="cursor-pointer"
</CrudModel> color="primary"
@click="handleDelete(row)"
data-cy="ticketNotesRemoveNoteBtn"
>
<QTooltip>
{{ t('ticketNotes.removeNote') }}
</QTooltip>
</QIcon>
</div>
<VnRow v-if="observationTypes.length > rows.length">
<QBtn
icon="add_circle"
v-shortcut="'+'"
flat
class="fill-icon-on-hover q-ml-md"
color="primary"
@click="ticketNotesCrudRef.insert()"
data-cy="ticketNotesAddNoteBtn"
>
<QTooltip>
{{ t('ticketNotes.addNote') }}
</QTooltip>
</QBtn>
</VnRow>
</QCard>
</template>
</CrudModel>
</QPage>
</div> </div>
</template> </template>

View File

@ -49,88 +49,95 @@ watch(
<FetchData <FetchData
@on-fetch="(data) => (listPackagingsOptions = data)" @on-fetch="(data) => (listPackagingsOptions = data)"
auto-load auto-load
:filter="{ fields: ['packagingFk', 'name'], order: 'name ASC' }"
url="Packagings/listPackaging" url="Packagings/listPackaging"
:filter="{
fields: ['packagingFk', 'name'],
order: ['name ASC'],
}"
/> />
<div class="flex justify-center"> <div class="full-width flex justify-center">
<CrudModel <QPage class="card-width q-pa-lg">
ref="ticketPackagingsCrudRef" <CrudModel
data-key="TicketPackagings" ref="ticketPackagingsCrudRef"
url="TicketPackagings" data-key="TicketPackagings"
model="TicketPackagings" url="TicketPackagings"
:filter="crudModelFilter" model="TicketPackagings"
:data-required="crudModelRequiredData" :filter="crudModelFilter"
:default-remove="false" :data-required="crudModelRequiredData"
auto-load :default-remove="false"
style="max-width: 800px" auto-load
> >
<template #body="{ rows, validate }"> <template #body="{ rows, validate }">
<QCard class="q-px-lg q-py-md"> <QCard class="q-px-lg q-py-md">
<div <div
v-for="(row, index) in rows" v-for="(row, index) in rows"
:key="index" :key="index"
class="q-mb-md row items-center q-gutter-x-md" class="q-mb-md row items-center q-gutter-x-md"
>
<VnSelect
:label="t('package.package')"
:options="listPackagingsOptions"
hide-selected
option-label="name"
option-value="packagingFk"
v-model="row.packagingFk"
> >
<template #option="scope"> <VnSelect
<QItem v-bind="scope.itemProps"> :label="t('package.package')"
<QItemSection> :options="listPackagingsOptions"
<QItemLabel> hide-selected
{{ scope.opt?.name }} option-label="name"
</QItemLabel> option-value="packagingFk"
<QItemLabel caption> v-model="row.packagingFk"
#{{ scope.opt?.itemFk }} >
</QItemLabel> <template #option="scope">
</QItemSection> <QItem v-bind="scope.itemProps">
</QItem> <QItemSection>
</template> <QItemLabel>
</VnSelect> {{ scope.opt?.name }}
<VnInput </QItemLabel>
:label="t('basicData.quantity')" <QItemLabel caption>
v-model.number="row.quantity" #{{ scope.opt?.itemFk }}
class="col" </QItemLabel>
type="number" </QItemSection>
min="1" </QItem>
:required="true" </template>
@update:model-value="handleInputQuantityClear(row)" </VnSelect>
:rules="validate('TicketPackaging.quantity')" <VnInput
/> :label="t('basicData.quantity')"
<VnInputDate :label="t('package.added')" v-model="row.created" /> v-model.number="row.quantity"
<QIcon class="col"
name="delete" type="number"
size="sm" min="1"
class="cursor-pointer" :required="true"
color="primary" @update:model-value="handleInputQuantityClear(row)"
@click="ticketPackagingsCrudRef.remove([row])" :rules="validate('TicketPackaging.quantity')"
> />
<QTooltip> <VnInputDate
{{ t('package.removePackage') }} :label="t('package.added')"
</QTooltip> v-model="row.created"
</QIcon> />
</div> <QIcon
<VnRow> name="delete"
<QBtn size="sm"
icon="add_circle" class="cursor-pointer"
v-shortcut="'+'" color="primary"
flat @click="ticketPackagingsCrudRef.remove([row])"
class="fill-icon-on-hover q-ml-md" >
color="primary" <QTooltip>
@click="ticketPackagingsCrudRef.insert()" {{ t('package.removePackage') }}
> </QTooltip>
<QTooltip> </QIcon>
{{ t('package.addPackage') }} </div>
</QTooltip> <VnRow>
</QBtn> <QBtn
</VnRow> icon="add_circle"
</QCard> v-shortcut="'+'"
</template> flat
</CrudModel> class="fill-icon-on-hover q-ml-md"
color="primary"
@click="ticketPackagingsCrudRef.insert()"
>
<QTooltip>
{{ t('package.addPackage') }}
</QTooltip>
</QBtn>
</VnRow>
</QCard>
</template>
</CrudModel>
</QPage>
</div> </div>
</template> </template>

View File

@ -385,7 +385,12 @@ watch(
if (!$el) return; if (!$el) return;
const head = $el.querySelector('thead'); const head = $el.querySelector('thead');
const firstRow = $el.querySelector('thead > tr'); const firstRow = $el.querySelector('thead > tr');
const headSelectionCol = $el.querySelector(
'thead tr.bg-header th.q-table--col-auto-width',
);
if (headSelectionCol) {
headSelectionCol.classList.add('horizontal-separator');
}
const newRow = document.createElement('tr'); const newRow = document.createElement('tr');
destinationElRef.value = document.createElement('th'); destinationElRef.value = document.createElement('th');
originElRef.value = document.createElement('th'); originElRef.value = document.createElement('th');
@ -394,8 +399,8 @@ watch(
destinationElRef.value.classList.add('text-uppercase', 'color-vn-label'); destinationElRef.value.classList.add('text-uppercase', 'color-vn-label');
originElRef.value.classList.add('text-uppercase', 'color-vn-label'); originElRef.value.classList.add('text-uppercase', 'color-vn-label');
destinationElRef.value.setAttribute('colspan', '7'); destinationElRef.value.setAttribute('colspan', '10');
originElRef.value.setAttribute('colspan', '9'); originElRef.value.setAttribute('colspan', '10');
destinationElRef.value.textContent = `${t( destinationElRef.value.textContent = `${t(
'advanceTickets.destination', 'advanceTickets.destination',
@ -490,8 +495,6 @@ watch(
selection: 'multiple', selection: 'multiple',
}" }"
v-model:selected="selectedTickets" v-model:selected="selectedTickets"
:pagination="{ rowsPerPage: 0 }"
:no-data-label="$t('globals.noResults')"
:right-search="false" :right-search="false"
:order="['futureTotalWithVat ASC']" :order="['futureTotalWithVat ASC']"
auto-load auto-load

View File

@ -189,16 +189,14 @@ const exprBuilder = (param, value) => {
return { return {
code: { like: `%${value}%` }, code: { like: `%${value}%` },
}; };
case 'id':
case 'price':
case 'agencyModeFk': case 'agencyModeFk':
return { return {
agencyModeFk: value, [param]: value,
}; };
case 'search': case 'search':
return /^\d+$/.test(value) ? { id: value } : { name: { like: `%${value}%` } }; return /^\d+$/.test(value) ? { id: value } : { name: { like: `%${value}%` } };
case 'price':
return {
price: value,
};
} }
}; };

View File

@ -54,7 +54,7 @@ describe('Handle Items FixedPrice', () => {
}); });
it('should edit all items', () => { it('should edit all items', () => {
cy.get('.bg-header > :nth-child(1) > .q-checkbox > .q-checkbox__inner').click(); cy.get('.bg-header > :nth-child(1) [data-cy="vnCheckbox"]').click();
cy.dataCy('FixedPriceToolbarEditBtn').should('not.be.disabled'); cy.dataCy('FixedPriceToolbarEditBtn').should('not.be.disabled');
cy.dataCy('FixedPriceToolbarEditBtn').click(); cy.dataCy('FixedPriceToolbarEditBtn').click();
cy.dataCy('EditFixedPriceSelectOption').type(grouping); cy.dataCy('EditFixedPriceSelectOption').type(grouping);
@ -65,7 +65,7 @@ describe('Handle Items FixedPrice', () => {
}); });
it('should remove all items', () => { it('should remove all items', () => {
cy.get('.bg-header > :nth-child(1) > .q-checkbox > .q-checkbox__inner').click(); cy.get('.bg-header > :nth-child(1) [data-cy="vnCheckbox"]').click();
cy.dataCy('crudModelDefaultRemoveBtn').should('not.be.disabled'); cy.dataCy('crudModelDefaultRemoveBtn').should('not.be.disabled');
cy.dataCy('crudModelDefaultRemoveBtn').click(); cy.dataCy('crudModelDefaultRemoveBtn').click();
cy.dataCy('VnConfirm_confirm').click(); cy.dataCy('VnConfirm_confirm').click();