5673-hotFix_improve_crudModel_vnSelect #100

Merged
alexm merged 2 commits from 5673-hotFix_improve_crudModel_vnSelect into master 2023-10-13 07:13:27 +00:00
11 changed files with 138 additions and 41 deletions

View File

@ -9,7 +9,7 @@
"lint": "eslint --ext .js,.vue ./", "lint": "eslint --ext .js,.vue ./",
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore", "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test:e2e": "cypress open", "test:e2e": "cypress open",
"test:e2e:ci": "cypress run --browser chromium", "test:e2e:ci": "cd ../salix && gulp docker && cd ../salix-front && cypress run --browser chromium",
Review

Aixina forcem que se fasa gulp docker quan tirem els e2e

Aixina forcem que se fasa gulp docker quan tirem els e2e
"test": "echo \"See package.json => scripts for available tests.\" && exit 0", "test": "echo \"See package.json => scripts for available tests.\" && exit 0",
"test:unit": "vitest", "test:unit": "vitest",
"test:unit:ci": "vitest run" "test:unit:ci": "vitest run"

View File

@ -46,7 +46,7 @@ const onResponseError = (error) => {
message = responseError.message; message = responseError.message;
} }
switch (response.status) { switch (response?.status) {
case 500: case 500:
message = 'errors.statusInternalServerError'; message = 'errors.statusInternalServerError';
break; break;
@ -58,7 +58,7 @@ const onResponseError = (error) => {
break; break;
} }
if (session.isLoggedIn() && response.status === 401) { if (session.isLoggedIn() && response?.status === 401) {
session.destroy(); session.destroy();
const hash = window.location.hash; const hash = window.location.hash;
const url = hash.slice(1); const url = hash.slice(1);

View File

@ -8,6 +8,7 @@ import { useStateStore } from 'stores/useStateStore';
import VnPaginate from 'components/ui/VnPaginate.vue'; import VnPaginate from 'components/ui/VnPaginate.vue';
import VnConfirm from 'components/ui/VnConfirm.vue'; import VnConfirm from 'components/ui/VnConfirm.vue';
import SkeletonTable from 'components/ui/SkeletonTable.vue'; import SkeletonTable from 'components/ui/SkeletonTable.vue';
import { tMobile } from 'src/composables/tMobile';
const quasar = useQuasar(); const quasar = useQuasar();
const stateStore = useStateStore(); const stateStore = useStateStore();
@ -62,9 +63,10 @@ const hasChanges = ref(false);
const originalData = ref(); const originalData = ref();
const vnPaginateRef = ref(); const vnPaginateRef = ref();
const formData = ref(); const formData = ref();
const saveButtonRef = ref(null);
const formUrl = computed(() => $props.url); const formUrl = computed(() => $props.url);
const emit = defineEmits(['onFetch', 'update:selected']); const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
defineExpose({ defineExpose({
reload, reload,
@ -73,12 +75,9 @@ defineExpose({
onSubmit, onSubmit,
reset, reset,
hasChanges, hasChanges,
saveChanges,
}); });
function tMobile(...args) {
if (!quasar.platform.is.mobile) return t(...args);
}
async function fetch(data) { async function fetch(data) {
if (data && Array.isArray(data)) { if (data && Array.isArray(data)) {
let $index = 0; let $index = 0;
@ -135,6 +134,7 @@ async function saveChanges(data) {
hasChanges.value = false; hasChanges.value = false;
isLoading.value = false; isLoading.value = false;
emit('saveChanges', data);
} }
async function insert() { async function insert() {
@ -269,7 +269,7 @@ watch(formUrl, async () => {
<SkeletonTable v-if="!formData" /> <SkeletonTable v-if="!formData" />
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()"> <Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
<QBtnGroup push class="q-gutter-x-sm"> <QBtnGroup push class="q-gutter-x-sm">
<slot name="moreActions" /> <slot name="moreBeforeActions" />
<QBtn <QBtn
:label="tMobile('globals.remove')" :label="tMobile('globals.remove')"
color="primary" color="primary"
@ -292,6 +292,7 @@ watch(formUrl, async () => {
/> />
<QBtn <QBtn
:label="tMobile('globals.save')" :label="tMobile('globals.save')"
ref="saveButtonRef"
color="primary" color="primary"
icon="save" icon="save"
@click="onSubmit" @click="onSubmit"
@ -299,6 +300,7 @@ watch(formUrl, async () => {
:title="t('globals.save')" :title="t('globals.save')"
v-if="$props.defaultSave" v-if="$props.defaultSave"
/> />
<slot name="moreAfterActions" />
</QBtnGroup> </QBtnGroup>
</Teleport> </Teleport>
<QInnerLoading <QInnerLoading

View File

@ -19,6 +19,8 @@ const $props = defineProps({
const { optionLabel, options } = toRefs($props); const { optionLabel, options } = toRefs($props);
const myOptions = ref([]); const myOptions = ref([]);
const myOptionsOriginal = ref([]); const myOptionsOriginal = ref([]);
const vnSelectRef = ref(null);
function setOptions(data) { function setOptions(data) {
myOptions.value = JSON.parse(JSON.stringify(data)); myOptions.value = JSON.parse(JSON.stringify(data));
myOptionsOriginal.value = JSON.parse(JSON.stringify(data)); myOptionsOriginal.value = JSON.parse(JSON.stringify(data));
@ -29,6 +31,7 @@ const filter = (val, options) => {
const search = val.toLowerCase(); const search = val.toLowerCase();
if (val === '') return options; if (val === '') return options;
return options.filter((row) => { return options.filter((row) => {
const id = row.id; const id = row.id;
const name = row[$props.optionLabel].toLowerCase(); const name = row[$props.optionLabel].toLowerCase();
@ -41,9 +44,17 @@ const filter = (val, options) => {
}; };
const filterHandler = (val, update) => { const filterHandler = (val, update) => {
update(() => { update(
myOptions.value = filter(val, myOptionsOriginal.value); () => {
}); myOptions.value = filter(val, myOptionsOriginal.value);
},
(ref) => {
if (val !== '' && ref.options.length > 0) {
ref.setOptionIndex(-1);
ref.moveOptionSelection(1, true);
}
}
);
}; };
watch(options, (newValue) => { watch(options, (newValue) => {
@ -70,7 +81,15 @@ const value = computed({
map-options map-options
use-input use-input
@filter="filterHandler" @filter="filterHandler"
clearable hide-selected
clear-icon="close" fill-input
/> ref="vnSelectRef"
>
<template #append>
<QIcon name="close" @click.stop="value = null" class="cursor-pointer" />
</template>
<template v-for="(_, slotName) in $slots" #[slotName]="slotData">
<slot :name="slotName" v-bind="slotData" />
</template>
</QSelect>
</template> </template>

View File

@ -140,14 +140,6 @@ async function onLoad(...params) {
{{ t('No data to display') }} {{ t('No data to display') }}
</h5> </h5>
</div> </div>
<div
v-if="store.data && store.data.length === 0 && !isLoading"
class="info-row q-pa-md text-center"
>
<h5>
{{ t('No results found') }}
</h5>
</div>
<div <div
v-if="props.skeleton && props.autoLoad && !store.data" v-if="props.skeleton && props.autoLoad && !store.data"
class="card-list q-gutter-y-md" class="card-list q-gutter-y-md"

View File

@ -0,0 +1,8 @@
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
export function tMobile(...args) {
const quasar = useQuasar();
const { t } = useI18n();
if (!quasar.platform.is.mobile) return t(...args);
}

View File

@ -1,12 +1,15 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n'; 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 VnSelectFilter from 'components/common/VnSelectFilter.vue';
import { getUrl } from 'composables/getUrl';
import { tMobile } from 'composables/tMobile';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const claimDevelopmentForm = ref(); const claimDevelopmentForm = ref();
@ -16,6 +19,12 @@ const claimResponsibles = ref([]);
const claimRedeliveries = ref([]); const claimRedeliveries = ref([]);
const workers = ref([]); const workers = ref([]);
const selected = ref([]); const selected = ref([]);
const insertButtonRef = ref();
let salixUrl;
onMounted(async () => {
salixUrl = await getUrl(`claim/${route.params.id}`);
});
const developmentsFilter = { const developmentsFilter = {
fields: [ fields: [
@ -43,6 +52,7 @@ const columns = computed(() => [
model: 'claimReasonFk', model: 'claimReasonFk',
optionValue: 'id', optionValue: 'id',
optionLabel: 'description', optionLabel: 'description',
tabIndex: 1,
}, },
{ {
name: 'claimResult', name: 'claimResult',
@ -54,6 +64,7 @@ const columns = computed(() => [
model: 'claimResultFk', model: 'claimResultFk',
optionValue: 'id', optionValue: 'id',
optionLabel: 'description', optionLabel: 'description',
tabIndex: 2,
}, },
{ {
name: 'claimResponsible', name: 'claimResponsible',
@ -65,6 +76,7 @@ const columns = computed(() => [
model: 'claimResponsibleFk', model: 'claimResponsibleFk',
optionValue: 'id', optionValue: 'id',
optionLabel: 'description', optionLabel: 'description',
tabIndex: 3,
}, },
{ {
name: 'worker', name: 'worker',
@ -75,6 +87,7 @@ const columns = computed(() => [
model: 'workerFk', model: 'workerFk',
optionValue: 'id', optionValue: 'id',
optionLabel: 'nickname', optionLabel: 'nickname',
tabIndex: 4,
}, },
{ {
name: 'claimRedelivery', name: 'claimRedelivery',
@ -86,8 +99,13 @@ const columns = computed(() => [
model: 'claimRedeliveryFk', model: 'claimRedeliveryFk',
optionValue: 'id', optionValue: 'id',
optionLabel: 'description', optionLabel: 'description',
tabIndex: 5,
}, },
]); ]);
function goToAction() {
location.href = `${salixUrl}/action`;
}
</script> </script>
<template> <template>
<FetchData <FetchData
@ -115,8 +133,9 @@ const columns = computed(() => [
auto-load auto-load
/> />
<FetchData <FetchData
url="Workers/activeWithInheritedRole" url="Workers/search"
:where="{ role: 'employee' }" :where="{ active: 1 }"
order="name ASC"
@on-fetch="(data) => (workers = data)" @on-fetch="(data) => (workers = data)"
auto-load auto-load
/> />
@ -129,6 +148,8 @@ const columns = computed(() => [
:data-required="{ claimFk: route.params.id }" :data-required="{ claimFk: route.params.id }"
v-model:selected="selected" v-model:selected="selected"
auto-load auto-load
@save-changes="goToAction"
:default-save="false"
> >
<template #body="{ rows }"> <template #body="{ rows }">
<QTable <QTable
@ -142,19 +163,40 @@ const columns = computed(() => [
:grid="$q.screen.lt.md" :grid="$q.screen.lt.md"
> >
<template #body-cell="{ row, col }"> <template #body-cell="{ row, col }">
<QTd auto-width> <QTd
auto-width
@keyup.ctrl.enter.stop="claimDevelopmentForm.saveChanges()"
>
<VnSelectFilter <VnSelectFilter
:label="col.label" :label="col.label"
v-model="row[col.model]" v-model="row[col.model]"
:options="col.options" :options="col.options"
:option-value="col.optionValue" :option-value="col.optionValue"
:option-label="col.optionLabel" :option-label="col.optionLabel"
/> :autofocus="col.tabIndex == 1"
input-debounce="0"
>
<template #option="scope" v-if="col.name == 'worker'">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption>
{{ scope.opt?.nickname }}
{{ scope.opt?.code }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelectFilter>
</QTd> </QTd>
</template> </template>
<template #item="props"> <template #item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition"> <div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
<QCard bordered flat> <QCard
bordered
flat
@keyup.ctrl.enter.stop="claimDevelopmentForm?.saveChanges()"
>
<QCardSection> <QCardSection>
<QCheckbox v-model="props.selected" dense /> <QCheckbox v-model="props.selected" dense />
</QCardSection> </QCardSection>
@ -169,6 +211,8 @@ const columns = computed(() => [
:option-value="col.optionValue" :option-value="col.optionValue"
:option-label="col.optionLabel" :option-label="col.optionLabel"
dense dense
input-debounce="0"
:autofocus="col.tabIndex == 1"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -178,9 +222,28 @@ const columns = computed(() => [
</template> </template>
</QTable> </QTable>
</template> </template>
<template #moreAfterActions>
<QBtn
:label="tMobile('globals.save')"
ref="saveButtonRef"
color="primary"
icon="save"
:disable="!claimDevelopmentForm?.hasChanges"
@click="claimDevelopmentForm?.onSubmit"
:title="t('globals.save')"
/>
</template>
</CrudModel> </CrudModel>
<QPageSticky position="bottom-right" :offset="[25, 25]"> <QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn fab color="primary" icon="add" @click="claimDevelopmentForm.insert()" /> <QBtn
ref="insertButtonRef"
fab
color="primary"
icon="add"
@click="claimDevelopmentForm.insert()"
@keydown.ctrl.enter.stop="claimDevelopmentForm.saveChanges()"
@keydown.enter.stop
/>
</QPageSticky> </QPageSticky>
</template> </template>

View File

@ -40,7 +40,6 @@ const claimLinesForm = ref();
const claim = ref(null); const claim = ref(null);
async function onFetchClaim(data) { async function onFetchClaim(data) {
claim.value = data; claim.value = data;
fetchMana(); fetchMana();
} }
@ -147,8 +146,11 @@ function showImportDialog() {
quasar quasar
.dialog({ .dialog({
component: ClaimLinesImport, component: ClaimLinesImport,
componentProps: {
ticketId: claim.value.ticketFk,
},
}) })
.onOk(() => arrayData.refresh()); .onOk(() => claimLinesForm.value.reload());
} }
</script> </script>
<template> <template>

View File

@ -14,6 +14,13 @@ const route = useRoute();
const quasar = useQuasar(); const quasar = useQuasar();
const { t } = useI18n(); const { t } = useI18n();
const $props = defineProps({
ticketId: {
type: Number,
required: true,
},
});
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'delivered', name: 'delivered',
@ -99,7 +106,7 @@ function cancel() {
</script> </script>
<template> <template>
<FetchData <FetchData
url="Sales/getClaimableFromTicket?ticketFk=16" :url="`Sales/getClaimableFromTicket?ticketFk=${$props.ticketId}`"
Review

La seccio de linies anava mal pq se havia ficat un 16 ahi pq si...

La seccio de linies anava mal pq se havia ficat un 16 ahi pq si...
@on-fetch="(data) => (claimableSales = data)" @on-fetch="(data) => (claimableSales = data)"
auto-load auto-load
/> />

View File

@ -13,15 +13,17 @@ describe('ClaimDevelopment', () => {
it('should reset line', () => { it('should reset line', () => {
cy.selectOption(firstLineReason, 'Novato'); cy.selectOption(firstLineReason, 'Novato');
cy.resetCard(); cy.resetCard();
cy.getValue(firstLineReason).should('have.text', 'Prisas'); cy.getValue(firstLineReason).should('have.value', 'Prisas');
}); });
it('should edit line', () => { it('should edit line', () => {
cy.selectOption(firstLineReason, 'Novato'); cy.selectOption(firstLineReason, 'Novato');
cy.saveCard();
cy.reload(); cy.saveCard();
cy.getValue(firstLineReason).should('have.text', 'Novato'); cy.login('developer');
cy.visit(`/#/claim/${claimId}/development`);
cy.getValue(firstLineReason).should('have.value', 'Novato');
//Restart data //Restart data
cy.selectOption(firstLineReason, 'Prisas'); cy.selectOption(firstLineReason, 'Prisas');
@ -29,13 +31,16 @@ describe('ClaimDevelopment', () => {
}); });
it('should add and remove new line', () => { it('should add and remove new line', () => {
//add row
cy.addCard(); cy.addCard();
cy.get(thirdRow).should('exist'); cy.get(thirdRow).should('exist');
const rowData = [false, 'Novato', 'Roces', 'Compradores', 'employeeNick', 'Tour']; const rowData = [false, 'Novato', 'Roces', 'Compradores', 'employeeNick', 'Tour'];
cy.fillRow(thirdRow, rowData); cy.fillRow(thirdRow, rowData);
cy.saveCard(); cy.saveCard();
cy.login('developer');
cy.visit(`/#/claim/${claimId}/development`);
cy.validateRow(thirdRow, rowData); cy.validateRow(thirdRow, rowData);
cy.reload(); cy.reload();

View File

@ -54,7 +54,7 @@ Cypress.Commands.add('getValue', (selector) => {
else if ($el.find('.q-select__dropdown-icon').length) { else if ($el.find('.q-select__dropdown-icon').length) {
return cy.get( return cy.get(
selector + selector +
'> .q-field > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native > span' '> .q-field > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native > input'
); );
} else { } else {
// Puedes añadir un log o lanzar un error si el elemento no es reconocido // Puedes añadir un log o lanzar un error si el elemento no es reconocido
@ -76,7 +76,6 @@ Cypress.Commands.add('checkOption', (selector) => {
// Global buttons // Global buttons
Cypress.Commands.add('saveCard', () => { Cypress.Commands.add('saveCard', () => {
cy.get('[title="Save"]').click(); cy.get('[title="Save"]').click();
cy.get('[title="Save"]').should('have.class', 'disabled');
}); });
Cypress.Commands.add('resetCard', () => { Cypress.Commands.add('resetCard', () => {
cy.get('[title="Reset"]').click(); cy.get('[title="Reset"]').click();
@ -123,7 +122,7 @@ Cypress.Commands.add('validateRow', (rowSelector, expectedValues) => {
cy.getValue(`:nth-child(${index + 1})`).should(`${prefix}be.checked`); cy.getValue(`:nth-child(${index + 1})`).should(`${prefix}be.checked`);
continue; continue;
} }
cy.getValue(`:nth-child(${index + 1})`).should('have.text', value); cy.getValue(`:nth-child(${index + 1})`).should('have.value', value);
} }
}); });
}); });