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

251 lines
6.7 KiB
Vue

<script setup>
import { ref, inject, onMounted, computed, Teleport } from 'vue';
import { useI18n } from 'vue-i18n';
import { useAppStore } from 'stores/app';
import { storeToRefs } from 'pinia';
import useNotify from 'src/composables/useNotify.js';
import {
generateUpdateSqlQuery,
generateInsertSqlQuery
} from 'src/js/db/sqlService.js';
const props = defineProps({
title: {
type: String,
default: ''
},
table: {
type: String,
default: ''
},
schema: {
type: String,
default: ''
},
// Objeto que define las pks de la tabla. Usado para generar las queries sql correspondientes.
// Debe ser definido como un objeto de pares key-value, donde la clave es el nombre de la columna de la pk.
pks: {
type: Object,
default: () => {}
},
createModelDefault: {
type: Object,
default: () => ({
field: '',
value: ''
})
},
// Objeto que contiene la consulta SQL y los parámetros necesarios para obtener los datos iniciales del formulario.
// `query` debe ser una cadena de texto que representa la consulta SQL.
// `params` es un objeto que mapea los parámetros de la consulta a sus valores.
fetchFormDataSql: {
type: Object,
default: () => ({
query: '',
params: {}
})
},
// Objeto con los datos iniciales del form, si este objeto es definido, no se ejecuta la query fetchFormDataSql
formInitialData: {
type: Object,
default: () => {}
},
// Array de columnas que no se deben actualizar
columnsToIgnoreUpdate: {
type: Array,
default: () => []
},
autoLoad: {
type: Boolean,
default: true
},
isEditMode: {
type: Boolean,
default: true
},
defaultActions: {
type: Boolean,
default: true
},
showBottomActions: {
type: Boolean,
default: false
},
saveFn: {
type: Function,
default: null
},
separationBetweenInputs: {
type: String,
default: 'xs'
}
});
const emit = defineEmits(['onDataSaved']);
const { t } = useI18n();
const jApi = inject('jApi');
const { notify } = useNotify();
const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore);
const loading = ref(false);
const formData = ref({});
const addressFormRef = ref(null);
const modelInfo = ref(null);
// Array de nombre de columnas de la tabla
const tableColumns = computed(
() => modelInfo.value?.columns.map(col => col.name) || []
);
// Array de nombre de columnas que fueron actualizadas y no estan en columnsToIgnoreUpdate
const updatedColumns = computed(() => {
return tableColumns.value.filter(
colName =>
modelInfo.value?.data[0][colName] !== formData.value[colName] &&
!props.columnsToIgnoreUpdate.includes(colName)
);
});
const hasChanges = computed(() => !!updatedColumns.value.length);
const separationBetweenInputs = computed(() => {
return `q-gutter-y-${props.separationBetweenInputs}`;
});
const fetchFormData = async () => {
if (!props.fetchFormDataSql.query) return;
loading.value = true;
const { results } = await jApi.execQuery(
props.fetchFormDataSql.query,
props.fetchFormDataSql.params
);
modelInfo.value = results[0];
if (!modelInfo.value.data[0]) {
modelInfo.value.data[0] = {};
// Si no existen datos iniciales, se inicializan con null, en base a las columnas de la tabla
modelInfo.value.columns.forEach(
col => (modelInfo.value.data[0][col.name] = null)
);
}
formData.value = { ...modelInfo.value.data[0] };
loading.value = false;
};
const onSubmitSuccess = () => {
emit('onDataSaved');
notify(t('dataSaved'), 'positive');
};
const submit = async () => {
try {
if (props.saveFn) {
await props.saveFn(formData.value);
} else {
if (!hasChanges.value) {
return;
}
const sqlQuery = generateSqlQuery();
await jApi.execQuery(sqlQuery, props.pks);
modelInfo.value.data[0] = { ...formData.value };
}
onSubmitSuccess();
} catch (error) {
console.error('Error:', error);
}
};
const generateSqlQuery = () => {
if (props.isEditMode) {
return generateUpdateSqlQuery(
props.schema,
props.table,
props.pks,
updatedColumns.value,
formData.value
);
} else {
return generateInsertSqlQuery(
props.schema,
props.table,
formData.value,
updatedColumns.value,
props.createModelDefault
);
}
};
onMounted(async () => {
if (!props.formInitialData && props.autoLoad) {
fetchFormData();
} else {
formData.value = { ...props.formInitialData };
}
});
defineExpose({
formData,
submit
});
</script>
<template>
<QCard class="form-container" v-bind="$attrs">
<QForm
v-if="!loading"
ref="addressFormRef"
class="column full-width"
:class="separationBetweenInputs"
@submit="submit()"
>
<span v-if="title" class="text-h6 text-bold">
{{ title }}
</span>
<slot name="form" :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
/>
<QBtn
v-if="defaultActions"
:label="t('save')"
type="submit"
:icon="showBottomActions ? undefined : 'check'"
rounded
no-caps
flat
:disabled="!showBottomActions && !updatedColumns.length"
/>
<slot name="actions" :data="formData" />
</component>
</QForm>
<QSpinner v-else color="primary" size="3em" :thickness="2" />
</QCard>
</template>
<style lang="scss" scoped>
.form-container {
width: 100%;
height: max-content;
max-width: 544px;
padding: 32px;
display: flex;
justify-content: center;
}
</style>