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: true,
|
||||
provira marked this conversation as resolved
|
||||
},
|
||||
defaultSave: {
|
||||
type: Boolean,
|
||||
|
@ -86,7 +94,11 @@ const vnPaginateRef = ref();
|
|||
const formData = ref([]);
|
||||
const saveButtonRef = ref(null);
|
||||
const watchChanges = ref();
|
||||
let isNotEqual = ref(false);
|
||||
let isLastRowEmpty = ref(false);
|
||||
const isFirstFetch = ref(true);
|
||||
const formUrl = computed(() => $props.url);
|
||||
const rowsContainer = ref(null);
|
||||
|
||||
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
|
||||
|
||||
|
@ -122,9 +134,14 @@ async function fetch(data) {
|
|||
const rows = keyData ? data[keyData] : data;
|
||||
resetData(rows);
|
||||
emit('onFetch', rows);
|
||||
if (isFirstFetch.value && $props.insertOnLoad) {
|
||||
await insert();
|
||||
}
|
||||
isFirstFetch.value = false;
|
||||
return rows;
|
||||
}
|
||||
|
||||
|
||||
function resetData(data) {
|
||||
if (!data) return;
|
||||
if (data && Array.isArray(data)) {
|
||||
|
@ -135,9 +152,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) => {
|
||||
hasChanges.value = false;
|
||||
const filteredNewData = nVal.filter(row => !isRowEmpty(row) || row[$props.primaryKey]);
|
||||
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.
|
||||
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 +189,9 @@ async function onSubmit() {
|
|||
});
|
||||
}
|
||||
isLoading.value = true;
|
||||
|
||||
await saveChanges($props.saveFn ? formData.value : null);
|
||||
|
||||
}
|
||||
|
||||
async function onSubmitAndGo() {
|
||||
|
@ -203,14 +229,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;
|
||||
const $index = formData.value.length ? formData.value.at(-1).$index + 1 : 0;
|
||||
|
||||
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 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 +271,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));
|
||||
}
|
||||
if (ids.length) {
|
||||
quasar
|
||||
provira marked this conversation as resolved
Outdated
alexm
commented
Creo que hace lo mismo Creo que hace lo mismo
|
||||
|
@ -248,9 +290,8 @@ async function remove(data) {
|
|||
newData = newData.filter((form) => !ids.some((id) => id == form[pk]));
|
||||
fetch(newData);
|
||||
});
|
||||
} else {
|
||||
reset();
|
||||
}
|
||||
|
||||
emit('update:selected', []);
|
||||
}
|
||||
|
||||
|
@ -261,7 +302,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 }));
|
||||
} else if (originalData.value[i]) {
|
||||
const data = getDifferences(originalData.value[i], row);
|
||||
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
|
||||
if (!isEmpty(data)) {
|
||||
|
@ -287,6 +328,33 @@ 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) {
|
||||
const focusableElements = rowsContainer.value?.querySelectorAll(
|
||||
'input, select, textarea, [tabindex]:not([tabindex="-1"]), .q-field__native, .q-checkbox__input'
|
||||
);
|
||||
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, select, textarea, [tabindex]:not([tabindex="-1"])');
|
||||
if (newElements.length > focusableElements.length) {
|
||||
newElements[newElements.length - 1].focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function reload(params) {
|
||||
const data = await vnPaginateRef.value.fetch(params);
|
||||
fetch(data);
|
||||
|
@ -308,12 +376,14 @@ watch(formUrl, async () => {
|
|||
v-bind="$attrs"
|
||||
>
|
||||
<template #body v-if="formData">
|
||||
<slot
|
||||
name="body"
|
||||
:rows="formData"
|
||||
:validate="validate"
|
||||
:filter="filter"
|
||||
></slot>
|
||||
<div ref="rowsContainer" @keydown.tab.prevent="handleTab">
|
||||
<slot
|
||||
name="body"
|
||||
:rows="formData"
|
||||
:validate="validate"
|
||||
:filter="filter"
|
||||
></slot>
|
||||
</div>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
<SkeletonTable
|
||||
|
|
|
@ -653,6 +653,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"
|
||||
|
|
|
@ -199,11 +199,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);
|
||||
|
||||
|
|
|
@ -89,6 +89,7 @@ onBeforeMount(async () => await setTaxableBase());
|
|||
data-key="InvoiceInDueDays"
|
||||
url="InvoiceInDueDays"
|
||||
:filter="filter"
|
||||
:insert-on-load="false"
|
||||
auto-load
|
||||
:data-required="{ invoiceInFk: invoiceId }"
|
||||
v-model:selected="rowsSelected"
|
||||
|
|
|
@ -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="{
|
||||
|
|
|
@ -58,6 +58,7 @@ const submitTaxes = async (data) => {
|
|||
:save-fn="submitTaxes"
|
||||
:filter="taxesFilter"
|
||||
:default-remove="false"
|
||||
:insert-on-load="false"
|
||||
data-key="ItemTax"
|
||||
model="ItemTax"
|
||||
ref="ItemTaxRef"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -174,6 +174,7 @@ const setUserParams = (params) => {
|
|||
:create="false"
|
||||
:crud-model="{
|
||||
disableInfiniteScroll: true,
|
||||
insertOnLoad: false,
|
||||
}"
|
||||
:table="{
|
||||
'row-key': 'itemFk',
|
||||
|
|
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