251 lines
6.7 KiB
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>
|