hedera-web/src/components/common/FormModel.vue

232 lines
6.1 KiB
Vue

<script setup>
import { ref, inject, onMounted, computed, Teleport, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useAppStore } from 'stores/app';
import { storeToRefs } from 'pinia';
import useNotify from 'src/composables/useNotify.js';
const props = defineProps({
title: {
type: String,
default: ''
},
table: {
type: String,
default: ''
},
schema: {
type: String,
default: ''
},
// Objeto con los datos iniciales del form, si este objeto es definido, no se ejecuta la query fetch
formInitialData: {
type: Object,
default: () => {}
},
autoLoad: {
type: Boolean,
default: true
},
defaultActions: {
type: Boolean,
default: true
},
showBottomActions: {
type: Boolean,
default: false
},
saveFn: {
type: Function,
default: null
},
separationBetweenInputs: {
type: String,
default: 'xs'
},
url: {
type: String,
default: ''
},
urlUpdate: {
type: String,
default: null
},
urlCreate: {
type: String,
default: null
},
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)'
},
mapper: {
type: Function,
default: null
},
filter: {
type: Object,
default: null
}
});
const emit = defineEmits(['onDataSaved', 'onDataFetched']);
const api = inject('api');
const { t } = useI18n();
const { notify } = useNotify();
const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore);
const isLoading = ref(false);
const formData = ref({});
const formModelRef = ref(null);
const hasChanges = ref(!props.observeFormChanges);
const isResetting = ref(false);
const originalData = ref(null);
const separationBetweenInputs = computed(() => {
return `q-gutter-y-${props.separationBetweenInputs}`;
});
const onSubmitSuccess = () => {
emit('onDataSaved');
notify(t('dataSaved'), 'positive');
};
onMounted(async () => {
if (!props.formInitialData) {
if (props.autoLoad && props.url) await fetch();
originalData.value = { ...formData.value };
} else {
formData.value = { ...props.formInitialData };
originalData.value = { ...props.formInitialData };
}
if (props.observeFormChanges) {
watch(
() => formData.value,
(newVal, oldVal) => {
if (!oldVal) return;
hasChanges.value =
!isResetting.value &&
JSON.stringify(newVal) !==
JSON.stringify(originalData.value);
isResetting.value = false;
},
{ deep: true }
);
}
});
async function fetch() {
try {
let { data } = await api.get(props.url, {
params: { filter: JSON.stringify(props.filter) }
});
if (Array.isArray(data)) data = data[0] ?? {};
formData.value = { ...data };
emit('onDataFetched', formData.value);
} catch (e) {
throw e;
}
}
const submitForm = async evt => {
const isFormValid = await formModelRef.value.validate();
if (isFormValid) await save(evt);
};
async function save() {
if (props.observeFormChanges && !hasChanges.value)
return notify('noChanges', 'negative');
isLoading.value = true;
try {
const body = props.mapper
? props.mapper(formData.value, originalData.value)
: formData.value;
const method = props.urlCreate ? 'post' : 'patch';
const url = props.urlCreate || props.urlUpdate || props.url;
await Promise.resolve(
props.saveFn ? props.saveFn(body) : api[method](url, body)
);
onSubmitSuccess();
hasChanges.value = false;
} finally {
isLoading.value = false;
}
}
defineExpose({
formData,
submitForm
});
</script>
<template>
<QCard class="form-container" v-bind="$attrs">
<QForm ref="formModelRef" class="form" :class="separationBetweenInputs">
<span v-if="title" class="text-h6 text-bold">
{{ title }}
</span>
<slot name="form" :data="formData" />
<slot name="extraForm" :data="formData" />
<component
v-if="isHeaderMounted"
:is="showBottomActions ? 'div' : Teleport"
to="#actions"
class="flex row justify-end q-gutter-x-sm"
:class="{ 'q-mt-md': showBottomActions }"
>
<QBtn
v-if="defaultActions && showBottomActions"
:label="t('cancel')"
:icon="showBottomActions ? undefined : 'check'"
rounded
no-caps
flat
v-close-popup
>
<QTooltip>{{ t('cancel') }}</QTooltip>
</QBtn>
<QBtn
v-if="defaultActions"
:label="t('save')"
:icon="showBottomActions ? undefined : 'check'"
rounded
no-caps
flat
:loading="isLoading"
:disabled="!showBottomActions && !hasChanges"
@click="submitForm()"
data-cy="formModelDefaultSaveButton"
>
<QTooltip>{{ t('save') }}</QTooltip>
</QBtn>
<slot name="actions" :data="formData" />
</component>
</QForm>
</QCard>
</template>
<style lang="scss" scoped>
.form-container {
width: 100%;
height: max-content;
padding: 32px;
max-width: 544px;
display: flex;
justify-content: center;
}
.form {
display: flex;
flex-direction: column;
width: 100%;
}
</style>