0
0
Fork 0
salix-front-mindshore-fork2/src/components/FormModel.vue

391 lines
11 KiB
Vue
Raw Normal View History

2022-10-27 12:59:19 +00:00
<script setup>
2023-01-26 13:29:01 +00:00
import axios from 'axios';
2024-02-02 15:45:29 +00:00
import { onMounted, onUnmounted, computed, ref, watch, nextTick } from 'vue';
import { onBeforeRouteLeave, useRouter, useRoute } from 'vue-router';
2022-10-27 12:59:19 +00:00
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { useState } from 'src/composables/useState';
import { useStateStore } from 'stores/useStateStore';
2022-10-31 08:34:01 +00:00
import { useValidator } from 'src/composables/useValidator';
2023-11-30 20:46:07 +00:00
import useNotify from 'src/composables/useNotify.js';
2022-11-17 07:04:12 +00:00
import SkeletonForm from 'components/ui/SkeletonForm.vue';
2024-02-27 14:41:09 +00:00
import VnConfirm from './ui/VnConfirm.vue';
2024-03-12 14:31:32 +00:00
import { tMobile } from 'src/composables/tMobile';
2024-05-22 09:22:46 +00:00
import { useArrayData } from 'src/composables/useArrayData';
2022-10-27 12:59:19 +00:00
2024-03-06 15:23:19 +00:00
const { push } = useRouter();
2022-10-27 12:59:19 +00:00
const quasar = useQuasar();
const state = useState();
const stateStore = useStateStore();
const { t } = useI18n();
2022-10-31 08:34:01 +00:00
const { validate } = useValidator();
2023-11-30 20:46:07 +00:00
const { notify } = useNotify();
const route = useRoute();
2024-06-21 11:09:34 +00:00
const myForm = ref(null);
2022-10-27 12:59:19 +00:00
const $props = defineProps({
url: {
type: String,
default: '',
},
model: {
type: String,
default: null,
2022-10-27 12:59:19 +00:00
},
filter: {
type: Object,
default: null,
},
2023-07-10 04:04:05 +00:00
urlUpdate: {
type: String,
default: null,
},
2023-11-30 20:46:07 +00:00
urlCreate: {
type: String,
default: null,
},
defaultActions: {
type: Boolean,
default: true,
},
2024-03-12 14:31:32 +00:00
defaultButtons: {
type: Object,
default: () => {},
},
2023-11-30 11:47:40 +00:00
autoLoad: {
type: Boolean,
default: false,
},
2023-11-30 20:46:07 +00:00
formInitialData: {
type: Object,
default: () => {},
},
observeFormChanges: {
type: Boolean,
default: true,
description:
'Esto se usa principalmente para permitir guardar sin hacer cambios (Útil para la feature de clonar ya que en este caso queremos poder guardar de primeras)',
},
2023-12-11 14:47:16 +00:00
mapper: {
type: Function,
default: null,
2023-12-26 19:15:46 +00:00
},
clearStoreOnUnmount: {
type: Boolean,
default: true,
},
2024-02-12 14:06:20 +00:00
saveFn: {
type: Function,
default: null,
2024-02-05 14:04:13 +00:00
},
2024-03-06 15:23:19 +00:00
goTo: {
type: String,
default: '',
description: 'It is used for redirect on click "save and continue"',
},
reload: {
type: Boolean,
default: false,
},
2024-08-16 10:29:35 +00:00
defaultTrim: {
type: Boolean,
default: true,
},
2024-11-07 13:38:54 +00:00
maxWidth: {
type: [String, Boolean],
default: '800px',
},
2022-10-27 12:59:19 +00:00
});
const emit = defineEmits(['onFetch', 'onDataSaved']);
const modelValue = computed(
() => $props.model ?? `formModel_${route?.meta?.title ?? route.name}`
2024-06-18 07:44:50 +00:00
).value;
2024-02-02 15:45:29 +00:00
const componentIsRendered = ref(false);
const arrayData = useArrayData(modelValue);
2024-05-22 09:22:46 +00:00
const isLoading = ref(false);
// Si elegimos observar los cambios del form significa que inicialmente las actions estaran deshabilitadas
const isResetting = ref(false);
const hasChanges = ref(!$props.observeFormChanges);
const originalData = ref({});
const formData = computed(() => state.get(modelValue));
2024-05-22 09:22:46 +00:00
const defaultButtons = computed(() => ({
save: {
2024-11-22 23:51:33 +00:00
dataCy: 'saveDefaultBtn',
2024-05-22 09:22:46 +00:00
color: 'primary',
icon: 'save',
label: 'globals.save',
2024-06-21 11:09:34 +00:00
click: () => myForm.value.submit(),
type: 'submit',
2024-05-22 09:22:46 +00:00
},
reset: {
2024-11-22 23:51:33 +00:00
dataCy: 'resetDefaultBtn',
2024-05-22 09:22:46 +00:00
color: 'primary',
icon: 'restart_alt',
label: 'globals.reset',
2024-06-21 11:09:34 +00:00
click: () => reset(),
2024-05-22 09:22:46 +00:00
},
...$props.defaultButtons,
}));
2024-02-02 15:45:29 +00:00
2023-11-30 11:47:40 +00:00
onMounted(async () => {
originalData.value = JSON.parse(JSON.stringify($props.formInitialData ?? {}));
2024-05-22 09:22:46 +00:00
nextTick(() => (componentIsRendered.value = true));
2024-02-02 15:45:29 +00:00
2023-11-30 20:46:07 +00:00
// Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla
state.set(modelValue, $props.formInitialData);
2023-11-30 20:46:07 +00:00
2024-06-11 07:06:09 +00:00
if (!$props.formInitialData) {
if ($props.autoLoad && $props.url) await fetch();
else if (arrayData.store.data) updateAndEmit('onFetch', arrayData.store.data);
2024-06-11 07:06:09 +00:00
}
2023-11-30 20:46:07 +00:00
if ($props.observeFormChanges) {
2024-05-22 09:22:46 +00:00
watch(
() => formData.value,
(newVal, oldVal) => {
if (!oldVal) return;
hasChanges.value =
!isResetting.value &&
JSON.stringify(newVal) !== JSON.stringify(originalData.value);
2024-05-22 09:22:46 +00:00
isResetting.value = false;
},
{ deep: true }
2024-05-22 09:22:46 +00:00
);
2023-11-30 20:46:07 +00:00
}
2023-11-30 11:47:40 +00:00
});
2022-10-27 12:59:19 +00:00
2024-05-22 09:22:46 +00:00
if (!$props.url)
watch(
() => arrayData.store.data,
2025-01-08 09:52:57 +00:00
(val) => updateAndEmit('onFetch', val)
2024-05-22 09:22:46 +00:00
);
watch(
() => [$props.url, $props.filter],
async () => {
originalData.value = null;
reset();
await fetch();
}
);
2024-05-22 09:22:46 +00:00
2024-02-27 14:41:09 +00:00
onBeforeRouteLeave((to, from, next) => {
if (hasChanges.value && $props.observeFormChanges)
quasar.dialog({
component: VnConfirm,
componentProps: {
2024-08-01 08:51:13 +00:00
title: t('globals.unsavedPopup.title'),
message: t('globals.unsavedPopup.subtitle'),
promise: () => next(),
},
});
else next();
2024-02-27 14:41:09 +00:00
});
2022-10-27 12:59:19 +00:00
onUnmounted(() => {
// Restauramos los datos originales en el store si se realizaron cambios en el formulario pero no se guardaron, evitando modificaciones erróneas.
if (hasChanges.value) return state.set(modelValue, originalData.value);
if ($props.clearStoreOnUnmount) state.unset(modelValue);
2022-10-27 12:59:19 +00:00
});
async function fetch() {
try {
2024-05-08 09:02:40 +00:00
let { data } = await axios.get($props.url, {
params: { filter: JSON.stringify($props.filter) },
});
2024-05-10 11:07:58 +00:00
if (Array.isArray(data)) data = data[0] ?? {};
2024-05-08 09:02:40 +00:00
2024-06-06 07:53:48 +00:00
updateAndEmit('onFetch', data);
} catch (e) {
state.set(modelValue, {});
originalData.value = {};
}
2022-10-27 12:59:19 +00:00
}
async function save() {
2024-05-22 09:22:46 +00:00
if ($props.observeFormChanges && !hasChanges.value)
return notify('globals.noChanges', 'negative');
2023-11-30 20:46:07 +00:00
2024-05-22 09:22:46 +00:00
isLoading.value = true;
2023-11-30 20:46:07 +00:00
try {
2024-08-16 10:29:35 +00:00
formData.value = trimData(formData.value);
2024-11-22 14:49:01 +00:00
const body = $props.mapper
? $props.mapper(formData.value, originalData.value)
: formData.value;
2024-05-22 09:22:46 +00:00
const method = $props.urlCreate ? 'post' : 'patch';
const url =
$props.urlCreate || $props.urlUpdate || $props.url || arrayData.store.url;
2024-01-22 16:15:51 +00:00
let response;
2024-05-22 09:22:46 +00:00
2024-02-12 14:06:20 +00:00
if ($props.saveFn) response = await $props.saveFn(body);
2024-05-22 09:22:46 +00:00
else response = await axios[method](url, body);
2024-02-12 14:06:20 +00:00
if ($props.urlCreate) notify('globals.dataCreated', 'positive');
2024-06-06 07:53:48 +00:00
updateAndEmit('onDataSaved', formData.value, response?.data);
if ($props.reload) await arrayData.fetch({});
hasChanges.value = false;
2024-06-06 07:33:01 +00:00
} finally {
isLoading.value = false;
2023-11-30 20:46:07 +00:00
}
2022-10-27 12:59:19 +00:00
}
2024-03-06 15:23:19 +00:00
async function saveAndGo() {
2024-05-23 14:48:50 +00:00
await save();
2024-03-06 15:23:19 +00:00
push({ path: $props.goTo });
}
2022-10-27 12:59:19 +00:00
function reset() {
2024-06-06 10:14:00 +00:00
updateAndEmit('onFetch', originalData.value);
2023-12-07 17:33:11 +00:00
if ($props.observeFormChanges) {
hasChanges.value = false;
2023-12-11 14:47:16 +00:00
isResetting.value = true;
2023-12-07 17:33:11 +00:00
}
2022-10-27 12:59:19 +00:00
}
2023-11-30 11:47:40 +00:00
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(
() => {
const { options, filterFn } = filterOptions;
options.value = filterFn(options, value);
},
(ref) => {
ref.setOptionIndex(-1);
ref.moveOptionSelection(1, true);
}
2022-10-31 08:34:01 +00:00
);
}
2023-01-26 13:29:01 +00:00
2024-06-06 07:53:48 +00:00
function updateAndEmit(evt, val, res) {
state.set(modelValue, val);
2024-05-22 09:22:46 +00:00
originalData.value = val && JSON.parse(JSON.stringify(val));
if (!$props.url) arrayData.store.data = val;
emit(evt, state.get(modelValue), res);
2024-05-22 09:22:46 +00:00
}
2024-08-16 10:29:35 +00:00
function trimData(data) {
if (!$props.defaultTrim) return data;
for (const key in data) {
if (typeof data[key] == 'string') data[key] = data[key].trim();
}
return data;
}
defineExpose({
save,
isLoading,
2024-03-28 16:45:35 +00:00
hasChanges,
2024-06-03 11:47:32 +00:00
reset,
fetch,
formData,
});
2022-10-27 12:59:19 +00:00
</script>
<template>
2023-12-26 19:15:46 +00:00
<div class="column items-center full-width">
<QForm
2024-06-21 11:09:34 +00:00
ref="myForm"
v-if="formData"
@submit="save"
@reset="reset"
class="q-pa-md"
2024-11-07 13:38:54 +00:00
:style="maxWidth ? 'max-width: ' + maxWidth : ''"
id="formModel"
2024-12-11 10:30:48 +00:00
:prevent-submit="$attrs['prevent-submit']"
>
<QCard>
<slot
2024-06-27 11:16:42 +00:00
v-if="formData"
name="form"
:data="formData"
:validate="validate"
:filter="filter"
/>
<SkeletonForm v-else />
</QCard>
</QForm>
</div>
2024-02-02 15:45:29 +00:00
<Teleport
to="#st-actions"
v-if="
$props.defaultActions &&
stateStore?.isSubToolbarShown() &&
componentIsRendered
"
2024-02-02 15:45:29 +00:00
>
<QBtnGroup push class="q-gutter-x-sm">
<slot name="moreActions" />
<QBtn
:label="tMobile(defaultButtons.reset.label)"
:color="defaultButtons.reset.color"
:icon="defaultButtons.reset.icon"
flat
@click="defaultButtons.reset.click"
:disable="!hasChanges"
:title="t(defaultButtons.reset.label)"
/>
<QBtnDropdown
2024-11-22 23:51:33 +00:00
data-cy="saveAndContinueDefaultBtn"
v-if="$props.goTo"
@click="saveAndGo"
:label="tMobile('globals.saveAndContinue')"
:title="t('globals.saveAndContinue')"
:disable="!hasChanges"
color="primary"
icon="save"
split
>
<QList>
<QItem
clickable
v-close-popup
@click="save"
:title="t('globals.save')"
>
<QItemSection>
<QItemLabel>
<QIcon
name="save"
color="white"
class="q-mr-sm"
size="sm"
/>
{{ t('globals.save').toUpperCase() }}
</QItemLabel>
</QItemSection>
</QItem>
</QList>
</QBtnDropdown>
<QBtn
v-else
:label="tMobile('globals.save')"
color="primary"
icon="save"
@click="defaultButtons.save.click"
:disable="!hasChanges"
:title="t(defaultButtons.save.label)"
/>
</QBtnGroup>
</Teleport>
2023-04-11 11:31:03 +00:00
<QInnerLoading
:showing="isLoading"
:label="t('globals.pleaseWait')"
color="primary"
style="min-width: 100%"
2023-04-11 11:31:03 +00:00
/>
2022-10-27 12:59:19 +00:00
</template>
<style lang="scss" scoped>
2024-04-02 08:46:27 +00:00
.q-notifications {
color: black;
}
#formModel {
2023-09-19 07:42:47 +00:00
width: 100%;
}
2023-11-30 11:47:40 +00:00
.q-card {
padding: 32px;
}
2023-09-19 07:42:47 +00:00
</style>