2022-10-27 12:59:19 +00:00
|
|
|
<script setup>
|
2023-01-26 13:29:01 +00:00
|
|
|
import axios from 'axios';
|
2022-10-27 12:59:19 +00:00
|
|
|
import { onMounted, onUnmounted, computed, ref, watch } from 'vue';
|
|
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
import { useQuasar } from 'quasar';
|
|
|
|
import { useState } from 'src/composables/useState';
|
2022-10-31 08:34:01 +00:00
|
|
|
import { useValidator } from 'src/composables/useValidator';
|
2022-11-17 07:04:12 +00:00
|
|
|
import SkeletonForm from 'components/ui/SkeletonForm.vue';
|
2022-10-27 12:59:19 +00:00
|
|
|
|
|
|
|
const quasar = useQuasar();
|
|
|
|
const { t } = useI18n();
|
|
|
|
const state = useState();
|
2022-10-31 08:34:01 +00:00
|
|
|
const { validate } = useValidator();
|
2022-10-27 12:59:19 +00:00
|
|
|
|
|
|
|
const $props = defineProps({
|
|
|
|
url: {
|
|
|
|
type: String,
|
|
|
|
default: '',
|
|
|
|
},
|
|
|
|
model: {
|
|
|
|
type: String,
|
|
|
|
default: '',
|
|
|
|
},
|
|
|
|
filter: {
|
|
|
|
type: Object,
|
|
|
|
default: null,
|
|
|
|
},
|
2023-07-10 04:04:05 +00:00
|
|
|
urlUpdate: {
|
|
|
|
type: String,
|
|
|
|
default: null,
|
|
|
|
},
|
2023-07-31 13:24:34 +00:00
|
|
|
crud: {
|
|
|
|
type: Boolean,
|
|
|
|
default: null,
|
|
|
|
},
|
|
|
|
primaryKey: {
|
|
|
|
type: String,
|
|
|
|
default: 'id',
|
|
|
|
},
|
|
|
|
dataRequired: {
|
|
|
|
type: Object,
|
|
|
|
default: null,
|
|
|
|
},
|
2023-08-07 10:00:42 +00:00
|
|
|
defaultActions: {
|
|
|
|
type: Boolean,
|
|
|
|
default: true,
|
|
|
|
},
|
2022-10-27 12:59:19 +00:00
|
|
|
});
|
|
|
|
|
2023-08-07 10:00:42 +00:00
|
|
|
const isLoading = ref(false);
|
|
|
|
const hasChanges = ref(false);
|
|
|
|
const formData = computed(() => state.get($props.model));
|
|
|
|
const originalData = ref();
|
|
|
|
const formUrl = computed(() => $props.url);
|
|
|
|
const stActionsExist = ref(false);
|
|
|
|
|
2022-10-31 08:34:01 +00:00
|
|
|
const emit = defineEmits(['onFetch']);
|
2022-10-27 12:59:19 +00:00
|
|
|
|
|
|
|
defineExpose({
|
|
|
|
save,
|
2023-07-31 13:24:34 +00:00
|
|
|
insert,
|
|
|
|
remove,
|
2023-08-07 10:00:42 +00:00
|
|
|
onSubmit,
|
|
|
|
reset,
|
|
|
|
hasChanges,
|
2022-10-27 12:59:19 +00:00
|
|
|
});
|
|
|
|
|
2023-08-07 10:00:42 +00:00
|
|
|
onMounted(async () => {
|
|
|
|
await fetch();
|
|
|
|
stActionsExist.value = !!document.querySelector('#st-actions');
|
|
|
|
});
|
2022-10-27 12:59:19 +00:00
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
state.unset($props.model);
|
|
|
|
});
|
|
|
|
|
|
|
|
async function fetch() {
|
|
|
|
const { data } = await axios.get($props.url, {
|
|
|
|
params: { filter: $props.filter },
|
|
|
|
});
|
|
|
|
|
2023-08-07 10:00:42 +00:00
|
|
|
if (Array.isArray(data)) {
|
|
|
|
let $index = 0;
|
|
|
|
data.map((d) => (d.$index = $index++));
|
|
|
|
}
|
|
|
|
|
2022-10-27 12:59:19 +00:00
|
|
|
state.set($props.model, data);
|
2023-07-31 13:24:34 +00:00
|
|
|
originalData.value = JSON.parse(JSON.stringify(data));
|
2022-10-27 12:59:19 +00:00
|
|
|
|
|
|
|
watch(formData.value, () => (hasChanges.value = true));
|
|
|
|
|
2022-10-31 08:34:01 +00:00
|
|
|
emit('onFetch', state.get($props.model));
|
2022-10-27 12:59:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function save() {
|
|
|
|
if (!hasChanges.value) {
|
|
|
|
return quasar.notify({
|
|
|
|
type: 'negative',
|
|
|
|
message: t('globals.noChanges'),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
isLoading.value = true;
|
2023-08-07 10:00:42 +00:00
|
|
|
try {
|
|
|
|
await axios.patch($props.urlUpdate || $props.url, formData.value);
|
|
|
|
} catch {
|
|
|
|
return (isLoading.value = false);
|
|
|
|
}
|
2022-10-31 08:34:01 +00:00
|
|
|
|
2023-07-31 13:24:34 +00:00
|
|
|
originalData.value = JSON.parse(JSON.stringify(formData.value));
|
2022-10-31 08:34:01 +00:00
|
|
|
hasChanges.value = false;
|
2022-10-27 12:59:19 +00:00
|
|
|
isLoading.value = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function reset() {
|
|
|
|
state.set($props.model, originalData.value);
|
2023-08-07 10:00:42 +00:00
|
|
|
watch(formData.value, () => (hasChanges.value = true));
|
2022-10-27 12:59:19 +00:00
|
|
|
hasChanges.value = false;
|
|
|
|
}
|
2023-06-01 07:09:54 +00:00
|
|
|
// eslint-disable-next-line vue/no-dupe-keys
|
2022-10-31 08:34:01 +00:00
|
|
|
function filter(value, update, filterOptions) {
|
|
|
|
update(
|
|
|
|
() => {
|
2023-08-01 13:17:48 +00:00
|
|
|
const { options, filterFn, field } = filterOptions;
|
2022-10-31 08:34:01 +00:00
|
|
|
|
2023-08-01 13:17:48 +00:00
|
|
|
options.value = filterFn(options, value, field);
|
2022-10-31 08:34:01 +00:00
|
|
|
},
|
|
|
|
(ref) => {
|
|
|
|
ref.setOptionIndex(-1);
|
|
|
|
ref.moveOptionSelection(1, true);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2023-01-26 13:29:01 +00:00
|
|
|
|
2023-07-31 13:24:34 +00:00
|
|
|
async function crudSave() {
|
|
|
|
if (!hasChanges.value) {
|
|
|
|
return quasar.notify({
|
|
|
|
type: 'negative',
|
|
|
|
message: t('globals.noChanges'),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
isLoading.value = true;
|
2023-08-07 10:00:42 +00:00
|
|
|
await saveChanges();
|
2023-07-31 13:24:34 +00:00
|
|
|
}
|
|
|
|
|
2023-08-07 10:00:42 +00:00
|
|
|
async function saveChanges() {
|
|
|
|
const changes = getChanges();
|
2023-07-31 13:24:34 +00:00
|
|
|
try {
|
|
|
|
await axios.post($props.urlUpdate || $props.url + '/crud', changes);
|
|
|
|
} catch (e) {
|
2023-08-07 10:00:42 +00:00
|
|
|
return (isLoading.value = false);
|
2023-07-31 13:24:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
originalData.value = JSON.parse(JSON.stringify(formData.value));
|
|
|
|
hasChanges.value = false;
|
|
|
|
isLoading.value = false;
|
2023-08-07 10:00:42 +00:00
|
|
|
if (changes.creates?.length) await fetch();
|
2023-07-31 13:24:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function insert() {
|
2023-08-07 10:00:42 +00:00
|
|
|
const $index = formData.value[formData.value.length - 1].$index + 1;
|
2023-07-31 13:24:34 +00:00
|
|
|
formData.value.push(Object.assign({ $index }, $props.dataRequired));
|
|
|
|
hasChanges.value = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// function addRemove(ids) {
|
|
|
|
// for (let id of ids) {
|
|
|
|
// const index = removed.value.indexOf(id);
|
|
|
|
// if (index > -1) {
|
|
|
|
// removed.value = removed.value.slice(index, 1);
|
|
|
|
// continue;
|
|
|
|
// }
|
|
|
|
// removed.value.push(id);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
async function remove(data) {
|
|
|
|
if (!data.length)
|
|
|
|
return quasar.notify({
|
|
|
|
type: 'warning',
|
|
|
|
message: t('globals.noChanges'),
|
|
|
|
});
|
|
|
|
|
|
|
|
const pk = $props.primaryKey;
|
|
|
|
let ids = data.map((d) => d[pk]).filter(Boolean);
|
|
|
|
let preRemove = data.map((d) => d.$index).filter(Boolean);
|
|
|
|
let newData = formData.value;
|
|
|
|
|
|
|
|
// addRemove(ids);
|
|
|
|
if (preRemove.length) {
|
|
|
|
newData = newData.filter(
|
|
|
|
(form) => !preRemove.some((index) => index == form.$index)
|
|
|
|
);
|
|
|
|
state.set($props.model, newData);
|
|
|
|
const changes = getChanges();
|
|
|
|
if (!changes.creates?.length && !changes.updates?.length)
|
|
|
|
hasChanges.value = false;
|
|
|
|
}
|
|
|
|
if (ids.length) {
|
|
|
|
await saveChanges({ deletes: ids });
|
|
|
|
newData = newData.filter((form) => !ids.some((id) => id == form[pk]));
|
|
|
|
state.set($props.model, newData);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function onSubmit() {
|
|
|
|
if ($props.crud) return crudSave();
|
|
|
|
return save();
|
|
|
|
}
|
|
|
|
|
2023-01-26 13:29:01 +00:00
|
|
|
watch(formUrl, async () => {
|
|
|
|
originalData.value = null;
|
|
|
|
reset();
|
|
|
|
fetch();
|
|
|
|
});
|
2023-07-31 13:24:34 +00:00
|
|
|
|
|
|
|
function getChanges() {
|
|
|
|
const updates = [];
|
|
|
|
const creates = [];
|
|
|
|
|
|
|
|
const pk = $props.primaryKey;
|
|
|
|
|
|
|
|
for (const [i, row] of formData.value.entries()) {
|
|
|
|
if (!row[pk]) {
|
|
|
|
creates.push(row);
|
|
|
|
} else if (originalData.value) {
|
|
|
|
const data = getDifferences(originalData.value[i], row);
|
|
|
|
if (!isEmpty(data)) {
|
|
|
|
updates.push({
|
|
|
|
data,
|
|
|
|
where: { [pk]: row[pk] },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const changes = { updates, creates };
|
|
|
|
|
|
|
|
for (let prop in changes) {
|
|
|
|
if (changes[prop].length === 0) changes[prop] = undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
return changes;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getDifferences(obj1, obj2) {
|
|
|
|
let diff = {};
|
|
|
|
for (let key in obj1) {
|
|
|
|
if (obj2[key] && obj1[key] !== obj2[key]) {
|
|
|
|
diff[key] = obj2[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (let key in obj2) {
|
|
|
|
if (obj1[key] === undefined && obj2[key]) {
|
|
|
|
diff[key] = obj2[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return diff;
|
|
|
|
}
|
|
|
|
|
|
|
|
function isEmpty(obj) {
|
|
|
|
if (obj == null) return true;
|
|
|
|
if (obj === undefined) return true;
|
|
|
|
if (Object.keys(obj).length === 0) return true;
|
|
|
|
|
|
|
|
if (obj.length > 0) return false;
|
|
|
|
}
|
2022-10-27 12:59:19 +00:00
|
|
|
</script>
|
|
|
|
<template>
|
2023-04-11 11:31:03 +00:00
|
|
|
<QBanner v-if="hasChanges" class="text-white bg-warning">
|
|
|
|
<QIcon name="warning" size="md" class="q-mr-md" />
|
2022-10-31 08:34:01 +00:00
|
|
|
<span>{{ t('globals.changesToSave') }}</span>
|
2023-04-11 11:31:03 +00:00
|
|
|
</QBanner>
|
2023-07-31 13:24:34 +00:00
|
|
|
<QForm v-if="formData" @submit="onSubmit" @reset="reset" class="q-pa-md">
|
2022-10-31 08:34:01 +00:00
|
|
|
<slot name="form" :data="formData" :validate="validate" :filter="filter"></slot>
|
2023-08-07 10:00:42 +00:00
|
|
|
</QForm>
|
|
|
|
<Teleport to="#st-actions" v-if="stActionsExist">
|
|
|
|
<div class="row q-gutter-x-sm">
|
|
|
|
<slot name="moreActions" />
|
|
|
|
<div v-if="$props.defaultActions">
|
2023-07-31 13:24:34 +00:00
|
|
|
<QBtn
|
|
|
|
:label="t('globals.save')"
|
|
|
|
color="primary"
|
2023-08-07 10:00:42 +00:00
|
|
|
icon="save"
|
|
|
|
@click="onSubmit"
|
2023-07-31 13:24:34 +00:00
|
|
|
:disable="!hasChanges"
|
|
|
|
/>
|
2023-04-11 11:31:03 +00:00
|
|
|
<QBtn
|
2022-10-31 08:34:01 +00:00
|
|
|
:label="t('globals.reset')"
|
|
|
|
color="primary"
|
2023-08-07 10:00:42 +00:00
|
|
|
class="q-ml-sm"
|
2022-10-31 08:34:01 +00:00
|
|
|
flat
|
2023-08-07 10:00:42 +00:00
|
|
|
@click="reset"
|
2022-10-31 08:34:01 +00:00
|
|
|
:disable="!hasChanges"
|
|
|
|
/>
|
2023-08-07 10:00:42 +00:00
|
|
|
</div>
|
2022-10-31 08:34:01 +00:00
|
|
|
</div>
|
2023-08-07 10:00:42 +00:00
|
|
|
</Teleport>
|
2023-04-11 11:31:03 +00:00
|
|
|
<SkeletonForm v-if="!formData" />
|
|
|
|
<QInnerLoading
|
|
|
|
:showing="isLoading"
|
|
|
|
:label="t('globals.pleaseWait')"
|
|
|
|
color="primary"
|
|
|
|
/>
|
2022-10-27 12:59:19 +00:00
|
|
|
</template>
|