WIP: feat: #8406 upgraded CrudModel #1627
|
@ -1,6 +1,6 @@
|
|||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { computed, ref, useAttrs, watch } from 'vue';
|
||||
import { computed, ref, useAttrs, watch, nextTick } from 'vue';
|
||||
import { useRouter, onBeforeRouteLeave } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
@ -42,7 +42,15 @@ const $props = defineProps({
|
|||
},
|
||||
dataRequired: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
default: () => ({}),
|
||||
},
|
||||
dataDefault: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
insertOnLoad: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
provira marked this conversation as resolved
Outdated
|
||||
},
|
||||
defaultSave: {
|
||||
type: Boolean,
|
||||
|
@ -56,6 +64,10 @@ const $props = defineProps({
|
|||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
defaultAdd: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
selected: {
|
||||
type: Object,
|
||||
default: null,
|
||||
|
@ -86,7 +98,10 @@ const vnPaginateRef = ref();
|
|||
const formData = ref();
|
||||
const saveButtonRef = ref(null);
|
||||
const watchChanges = ref();
|
||||
let isNotEqual = ref(false);
|
||||
let isLastRowEmpty = ref(false);
|
||||
const formUrl = computed(() => $props.url);
|
||||
const rowsContainer = ref(null);
|
||||
|
||||
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
|
||||
|
||||
|
@ -122,9 +137,13 @@ async function fetch(data) {
|
|||
const rows = keyData ? data[keyData] : data;
|
||||
resetData(rows);
|
||||
emit('onFetch', rows);
|
||||
if ($props.insertOnLoad) {
|
||||
await insert();
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
|
||||
function resetData(data) {
|
||||
if (!data) return;
|
||||
if (data && Array.isArray(data)) {
|
||||
|
@ -135,9 +154,16 @@ function resetData(data) {
|
|||
formData.value = JSON.parse(JSON.stringify(data));
|
||||
|
||||
if (watchChanges.value) watchChanges.value(); //destroy watcher
|
||||
watchChanges.value = watch(formData, () => (hasChanges.value = true), { deep: true });
|
||||
}
|
||||
watchChanges.value = watch(formData, (nVal) => {
|
||||
provira marked this conversation as resolved
Outdated
alexm
commented
Hay unas funciones para sacar los cambios Las usaria para esto tambien Hay unas funciones para sacar los cambios
`import { getDifferences, getUpdatedValues } from 'src/filters';`
Las usaria para esto tambien
jorgep
commented
Me suena que esto lo miramos juntos... Prueba a ver si se puede simplificar con las funciones que dice Alex. Me suena que esto lo miramos juntos... Prueba a ver si se puede simplificar con las funciones que dice Alex.
|
||||
hasChanges.value = false;
|
||||
const filteredNewData = nVal.filter(row => !isRowEmpty(row) || row[$props.primaryKey]);
|
||||
const filteredOriginal = originalData.value.filter(row => row[$props.primaryKey]);
|
||||
|
||||
const changes = getDifferences(filteredOriginal, filteredNewData);
|
||||
hasChanges.value = !isEmpty(changes);
|
||||
|
||||
}, { deep: true });
|
||||
}
|
||||
async function reset() {
|
||||
await fetch(originalData.value);
|
||||
hasChanges.value = false;
|
||||
|
@ -165,7 +191,9 @@ async function onSubmit() {
|
|||
});
|
||||
}
|
||||
isLoading.value = true;
|
||||
|
||||
await saveChanges($props.saveFn ? formData.value : null);
|
||||
|
||||
}
|
||||
|
||||
async function onSubmitAndGo() {
|
||||
|
@ -203,14 +231,32 @@ async function saveChanges(data) {
|
|||
});
|
||||
}
|
||||
|
||||
async function insert(pushData = $props.dataRequired) {
|
||||
const $index = formData.value.length
|
||||
? formData.value[formData.value.length - 1].$index + 1
|
||||
: 0;
|
||||
formData.value.push(Object.assign({ $index }, pushData));
|
||||
hasChanges.value = true;
|
||||
async function insert(pushData = { ...$props.dataRequired, ...$props.dataDefault }) {
|
||||
formData.value = formData.value.filter(row => !isRowEmpty(row));
|
||||
|
||||
const lastRow = formData.value.at(-1);
|
||||
const isLastRowEmpty = lastRow ? isRowEmpty(lastRow) : false;
|
||||
|
||||
if (formData.value.length > 0 && isLastRowEmpty) return;
|
||||
provira marked this conversation as resolved
Outdated
alexm
commented
Para ignorar las líneas vacías, usaría un filter Para ignorar las líneas vacías, usaría un filter
Para quitar las líneas vacías, usaría un map
|
||||
const $index = formData.value.length ? formData.value.at(-1).$index + 1 : 0;
|
||||
|
||||
const nRow = Object.assign({ $index }, pushData);
|
||||
formData.value.push(nRow);
|
||||
|
||||
const hasChange = Object.keys(nRow).some(key => !isChange(nRow, key));
|
||||
if (hasChange) hasChanges.value = true;
|
||||
}
|
||||
|
||||
function isRowEmpty(row) {
|
||||
return Object.keys(row).every(key => isChange(row, key));
|
||||
}
|
||||
|
||||
|
||||
function isChange(row,key){
|
||||
return !row[key] || key == '$index' || Object.hasOwn($props.dataRequired || {}, key);
|
||||
}
|
||||
|
||||
|
||||
async function remove(data) {
|
||||
if (!data.length)
|
||||
return quasar.notify({
|
||||
|
@ -227,10 +273,8 @@ async function remove(data) {
|
|||
newData = newData.filter(
|
||||
(form) => !preRemove.some((index) => index == form.$index),
|
||||
);
|
||||
const changes = getChanges();
|
||||
if (!changes.creates?.length && !changes.updates?.length)
|
||||
hasChanges.value = false;
|
||||
fetch(newData);
|
||||
formData.value = newData;
|
||||
hasChanges.value = JSON.stringify(removeIndexField(formData.value)) !== JSON.stringify(removeIndexField(originalData.value));
|
||||
}
|
||||
provira marked this conversation as resolved
Outdated
alexm
commented
Creo que hace lo mismo Creo que hace lo mismo
|
||||
if (ids.length) {
|
||||
quasar
|
||||
|
@ -248,9 +292,8 @@ async function remove(data) {
|
|||
newData = newData.filter((form) => !ids.some((id) => id == form[pk]));
|
||||
fetch(newData);
|
||||
});
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
|
||||
emit('update:selected', []);
|
||||
}
|
||||
|
||||
|
@ -261,7 +304,7 @@ function getChanges() {
|
|||
const pk = $props.primaryKey;
|
||||
for (const [i, row] of formData.value.entries()) {
|
||||
if (!row[pk]) {
|
||||
creates.push(row);
|
||||
creates.push(Object.assign(row, { ...$props.dataRequired }));
|
||||
provira marked this conversation as resolved
Outdated
alexm
commented
si no hay filas, fetch? Se tiene original data si no hay filas, fetch? Se tiene original data
|
||||
} else if (originalData.value[i]) {
|
||||
const data = getDifferences(originalData.value[i], row);
|
||||
if (!isEmpty(data)) {
|
||||
|
@ -287,6 +330,40 @@ function isEmpty(obj) {
|
|||
return !Object.keys(obj).length;
|
||||
}
|
||||
|
||||
function removeIndexField(data) {
|
||||
if (Array.isArray(data)) {
|
||||
return data.map(({ $index, ...rest }) => rest);
|
||||
} else if (typeof data === 'object' && data !== null) {
|
||||
const { $index, ...rest } = data;
|
||||
return rest;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleTab(event) {
|
||||
if (event.target.classList.contains('q-checkbox__input') ||
|
||||
event.target.type === 'checkbox' ||
|
||||
event.target.closest('.q-checkbox')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focusableElements = rowsContainer.value?.querySelectorAll(
|
||||
'input:not([type="checkbox"]), select, textarea, [tabindex]:not([tabindex="-1"]):not(.q-checkbox__input), .q-field__native'
|
||||
);
|
||||
|
||||
if (!focusableElements || focusableElements.length === 0) return;
|
||||
|
||||
const lastElement = focusableElements[focusableElements.length - 1];
|
||||
if (event.target === lastElement) {
|
||||
event.preventDefault();
|
||||
await insert();
|
||||
await nextTick();
|
||||
const newElements = rowsContainer.value.querySelectorAll('input:not([type="checkbox"]), select, textarea, [tabindex]:not([tabindex="-1"]):not(.q-checkbox__input)');
|
||||
if (newElements.length > focusableElements.length) {
|
||||
newElements[newElements.length - 1].focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function reload(params) {
|
||||
const data = await vnPaginateRef.value.fetch(params);
|
||||
fetch(data);
|
||||
|
@ -312,12 +389,14 @@ watch(formUrl, async () => {
|
|||
v-bind="$attrs"
|
||||
>
|
||||
<template #body v-if="formData">
|
||||
<div ref="rowsContainer" @keydown.tab="handleTab">
|
||||
<slot
|
||||
name="body"
|
||||
:rows="formData"
|
||||
:validate="validate"
|
||||
:filter="filter"
|
||||
></slot>
|
||||
</div>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown() && hasSubToolbar">
|
||||
|
@ -397,4 +476,16 @@ watch(formUrl, async () => {
|
|||
:label="t && t('globals.pleaseWait')"
|
||||
color="primary"
|
||||
/>
|
||||
<div class=" bottomButton" v-if="defaultAdd">
|
||||
<QBtn
|
||||
@click="insert()"
|
||||
class="cursor-pointer fill-icon"
|
||||
color="primary"
|
||||
icon="add_circle"
|
||||
size="md"
|
||||
round
|
||||
flat
|
||||
v-shortcut="'+'"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -663,6 +663,7 @@ const rowCtrlClickFunction = computed(() => {
|
|||
:class="$attrs['class'] ?? 'q-px-md'"
|
||||
:limit="$attrs['limit'] ?? 100"
|
||||
ref="CrudModelRef"
|
||||
:insert-on-load="crudModel.insertOnLoad"
|
||||
@on-fetch="(...args) => emit('onFetch', ...args)"
|
||||
:search-url="searchUrl"
|
||||
:disable-infinite-scroll="isTableMode"
|
||||
|
|
|
@ -193,11 +193,11 @@ describe('CrudModel', () => {
|
|||
});
|
||||
|
||||
it('should set originalData and formatData with data and generate watchChanges', async () => {
|
||||
data = {
|
||||
data = [{
|
||||
name: 'Tony',
|
||||
lastName: 'Stark',
|
||||
age: 42,
|
||||
};
|
||||
}];
|
||||
|
||||
vm.resetData(data);
|
||||
|
||||
|
|
|
@ -138,6 +138,8 @@ const columns = computed(() => [
|
|||
:filter="developmentsFilter"
|
||||
ref="claimDevelopmentForm"
|
||||
:data-required="{ claimFk: route.params.id }"
|
||||
:defaultAdd="true"
|
||||
:insert-on-load="true"
|
||||
v-model:selected="selected"
|
||||
@save-changes="$router.push(`/claim/${route.params.id}/action`)"
|
||||
:default-save="false"
|
||||
|
|
|
@ -89,6 +89,8 @@ onBeforeMount(async () => await setTaxableBase());
|
|||
data-key="InvoiceInDueDays"
|
||||
url="InvoiceInDueDays"
|
||||
:filter="filter"
|
||||
:insert-on-load="true"
|
||||
:default-add="true"
|
||||
auto-load
|
||||
:data-required="{ invoiceInFk: invoiceId }"
|
||||
v-model:selected="rowsSelected"
|
||||
|
|
|
@ -89,7 +89,9 @@ const columns = computed(() => [
|
|||
url="InvoiceInIntrastats"
|
||||
auto-load
|
||||
:data-required="{ invoiceInFk: invoiceInId }"
|
||||
:default-add="true"
|
||||
:filter="filter"
|
||||
:insert-on-load="true"
|
||||
v-model:selected="rowsSelected"
|
||||
@on-fetch="(data) => (invoceInIntrastat = data)"
|
||||
>
|
||||
|
|
|
@ -187,6 +187,8 @@ function setCursor(ref) {
|
|||
url="InvoiceInTaxes"
|
||||
:filter="filter"
|
||||
:data-required="{ invoiceInFk: $route.params.id }"
|
||||
:default-add="true"
|
||||
:insert-on-load="true"
|
||||
auto-load
|
||||
v-model:selected="rowsSelected"
|
||||
:go-to="`/invoice-in/${$route.params.id}/due-day`"
|
||||
|
|
|
@ -51,6 +51,7 @@ const submit = async (rows) => {
|
|||
<CrudModel
|
||||
:data-required="{ itemFk: route.params.id }"
|
||||
:default-remove="false"
|
||||
:insert-on-load="true"
|
||||
:filter="{
|
||||
fields: ['id', 'itemFk', 'code'],
|
||||
where: { itemFk: route.params.id },
|
||||
|
|
|
@ -76,15 +76,22 @@ const insertTag = (rows) => {
|
|||
model="ItemTags"
|
||||
url="ItemTags"
|
||||
:data-required="{
|
||||
$index: undefined,
|
||||
itemFk: route.params.id,
|
||||
priority: undefined,
|
||||
tag: {
|
||||
isFree: undefined,
|
||||
isFree: true,
|
||||
value: undefined,
|
||||
name: undefined,
|
||||
},
|
||||
|
||||
}"
|
||||
:data-default="{
|
||||
tag: {
|
||||
isFree: true,
|
||||
value: undefined,
|
||||
name: undefined,
|
||||
},
|
||||
tagFk: undefined,
|
||||
priority: undefined,
|
||||
}"
|
||||
:default-remove="false"
|
||||
:user-filter="{
|
||||
|
|
|
@ -24,10 +24,10 @@ const crudModelFilter = reactive({
|
|||
where: { ticketFk: route.params.id },
|
||||
});
|
||||
|
||||
const crudModelRequiredData = computed(() => ({
|
||||
const crudModelDefaultData = computed(() => ({
|
||||
created: Date.vnNew(),
|
||||
packagingFk: null,
|
||||
quantity: 0,
|
||||
created: Date.vnNew(),
|
||||
ticketFk: route.params.id,
|
||||
}));
|
||||
|
||||
|
@ -59,7 +59,7 @@ watch(
|
|||
url="TicketPackagings"
|
||||
model="TicketPackagings"
|
||||
:filter="crudModelFilter"
|
||||
:data-required="crudModelRequiredData"
|
||||
:data-default="crudModelDefaultData"
|
||||
:default-remove="false"
|
||||
auto-load
|
||||
style="max-width: 800px"
|
||||
|
|
|
@ -719,6 +719,7 @@ watch(
|
|||
:create-as-dialog="false"
|
||||
:crud-model="{
|
||||
disableInfiniteScroll: true,
|
||||
insertOnLoad: false,
|
||||
}"
|
||||
:default-remove="false"
|
||||
:default-reset="false"
|
||||
|
|
|
@ -181,6 +181,7 @@ function beforeSave(data) {
|
|||
model="TicketService"
|
||||
:filter="crudModelFilter"
|
||||
:data-required="crudModelRequiredData"
|
||||
:default-add="true"
|
||||
auto-load
|
||||
v-model:selected="selected"
|
||||
:order="['description ASC']"
|
||||
|
|
|
@ -181,6 +181,7 @@ const setUserParams = (params) => {
|
|||
:create="false"
|
||||
:crud-model="{
|
||||
disableInfiniteScroll: true,
|
||||
insertOnLoad: false,
|
||||
}"
|
||||
:table="{
|
||||
'row-key': 'itemFk',
|
||||
|
|
|
@ -99,6 +99,10 @@ const columns = computed(() => [
|
|||
workerFk: entityId,
|
||||
},
|
||||
}"
|
||||
:crud-model="{
|
||||
insertOnLoad: false,
|
||||
}"
|
||||
|
||||
order="paymentDate DESC"
|
||||
:columns="columns"
|
||||
auto-load
|
||||
|
|
|
@ -128,6 +128,9 @@ const columns = computed(() => [
|
|||
workerFk: entityId,
|
||||
},
|
||||
}"
|
||||
:crud-model="{
|
||||
insertOnLoad: false,
|
||||
}"
|
||||
order="id DESC"
|
||||
:columns="columns"
|
||||
auto-load
|
||||
|
|
|
@ -109,6 +109,9 @@ const columns = [
|
|||
workerFk: entityId,
|
||||
},
|
||||
}"
|
||||
:crud-model="{
|
||||
insertOnLoad: false,
|
||||
}"
|
||||
order="date DESC"
|
||||
:columns="columns"
|
||||
auto-load
|
||||
|
|
|
@ -170,6 +170,9 @@ function isSigned(row) {
|
|||
'row-key': 'deviceProductionFk',
|
||||
selection: 'multiple',
|
||||
}"
|
||||
:crud-model="{
|
||||
insertOnLoad: false,
|
||||
}"
|
||||
:table-filter="{ hiddenTags: ['userFk'] }"
|
||||
>
|
||||
<template #moreBeforeActions>
|
||||
|
|
Loading…
Reference in New Issue
default false, y donde se quiera, poner true
@jgallego que opinas?
la mayoria de los campos no se usan para crear nuevos registros, por tanto, por defecto false creo que es mejor, además el hecho de quererlo en el formulario de creación para mi tiene que ser explicito. Por tanto, default: false