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' ;
2024-02-27 14:41:09 +00:00
import { onBeforeRouteLeave } 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' ;
2023-08-16 13:04:16 +00:00
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' ;
2022-10-27 12:59:19 +00:00
const quasar = useQuasar ( ) ;
const state = useState ( ) ;
2023-08-16 13:04:16 +00:00
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 ( ) ;
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-11-30 20:46:07 +00:00
urlCreate : {
type : String ,
default : null ,
} ,
2023-08-07 10:00:42 +00:00
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
} ,
2024-02-26 16:22:25 +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
} ,
2022-10-27 12:59:19 +00:00
} ) ;
2023-12-18 15:06:07 +00:00
const emit = defineEmits ( [ 'onFetch' , 'onDataSaved' ] ) ;
2022-10-27 12:59:19 +00:00
2024-02-02 15:45:29 +00:00
const componentIsRendered = ref ( false ) ;
2023-11-30 11:47:40 +00:00
onMounted ( async ( ) => {
2024-02-02 15:45:29 +00:00
nextTick ( ( ) => {
componentIsRendered . value = true ;
} ) ;
2023-11-30 20:46:07 +00:00
// Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla
2024-03-01 08:33:59 +00:00
state . set ( $props . model , $props . formInitialData ) ;
2024-02-22 09:20:44 +00:00
if ( $props . autoLoad && ! $props . formInitialData ) {
2023-11-30 11:47:40 +00:00
await fetch ( ) ;
}
2023-11-30 20:46:07 +00:00
2024-01-22 16:15:51 +00:00
// Si así se desea disparamos el watcher del form después de 100ms, asi darle tiempo de que se haya cargado la data inicial
// para evitar que detecte cambios cuando es data inicial default
2023-11-30 20:46:07 +00:00
if ( $props . observeFormChanges ) {
2024-01-22 16:15:51 +00:00
setTimeout ( ( ) => {
startFormWatcher ( ) ;
} , 100 ) ;
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-02-27 14:41:09 +00:00
onBeforeRouteLeave ( ( to , from , next ) => {
if ( ! hasChanges . value ) next ( ) ;
quasar . dialog ( {
component : VnConfirm ,
componentProps : {
title : t ( 'Unsaved changes will be lost' ) ,
message : t ( 'Are you sure exit without saving?' ) ,
promise : ( ) => next ( ) ,
} ,
} ) ;
} ) ;
2022-10-27 12:59:19 +00:00
onUnmounted ( ( ) => {
2024-02-26 16:22:25 +00:00
// 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 ) {
state . set ( $props . model , originalData . value ) ;
return ;
}
if ( $props . clearStoreOnUnmount ) state . unset ( $props . model ) ;
2022-10-27 12:59:19 +00:00
} ) ;
const isLoading = ref ( false ) ;
2023-11-30 20:46:07 +00:00
// Si elegimos observar los cambios del form significa que inicialmente las actions estaran deshabilitadas
2023-12-11 14:47:16 +00:00
const isResetting = ref ( false ) ;
2023-11-30 20:46:07 +00:00
const hasChanges = ref ( ! $props . observeFormChanges ) ;
2023-12-18 15:06:07 +00:00
const originalData = ref ( { ... $props . formInitialData } ) ;
2023-08-16 13:04:16 +00:00
const formData = computed ( ( ) => state . get ( $props . model ) ) ;
2023-01-26 13:29:01 +00:00
const formUrl = computed ( ( ) => $props . url ) ;
2024-03-12 14:31:32 +00:00
const defaultButtons = computed ( ( ) => ( {
save : {
color : 'primary' ,
icon : 'restart_alt' ,
label : 'globals.save' ,
} ,
reset : {
color : 'primary' ,
icon : 'save' ,
label : 'globals.reset' ,
} ,
... $props . defaultButtons ,
} ) ) ;
2023-11-30 20:46:07 +00:00
const startFormWatcher = ( ) => {
watch (
( ) => formData . value ,
( val ) => {
2024-01-02 16:36:09 +00:00
hasChanges . value = ! isResetting . value && val ;
2023-12-11 14:47:16 +00:00
isResetting . value = false ;
2023-11-30 20:46:07 +00:00
} ,
{ deep : true }
) ;
} ;
2022-10-27 12:59:19 +00:00
async function fetch ( ) {
const { data } = await axios . get ( $props . url , {
2023-11-23 23:26:50 +00:00
params : { filter : JSON . stringify ( $props . filter ) } ,
2022-10-27 12:59:19 +00:00
} ) ;
state . set ( $props . model , data ) ;
2023-08-21 13:14:19 +00:00
originalData . value = data && JSON . parse ( JSON . stringify ( data ) ) ;
2022-10-27 12:59:19 +00:00
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 ( ) {
2023-12-18 15:06:07 +00:00
if ( $props . observeFormChanges && ! hasChanges . value ) {
2023-11-30 20:46:07 +00:00
notify ( 'globals.noChanges' , 'negative' ) ;
return ;
2022-10-27 12:59:19 +00:00
}
isLoading . value = true ;
2023-11-30 20:46:07 +00:00
try {
2023-12-26 19:15:46 +00:00
const body = $props . mapper ? $props . mapper ( formData . value ) : formData . value ;
2024-01-22 16:15:51 +00:00
let response ;
2024-02-12 14:06:20 +00:00
if ( $props . saveFn ) response = await $props . saveFn ( body ) ;
else
response = await axios [ $props . urlCreate ? 'post' : 'patch' ] (
$props . urlCreate || $props . urlUpdate || $props . url ,
2024-02-05 14:04:13 +00:00
body
) ;
2024-02-12 14:06:20 +00:00
if ( $props . urlCreate ) notify ( 'globals.dataCreated' , 'positive' ) ;
2024-01-22 16:52:53 +00:00
emit ( 'onDataSaved' , formData . value , response ? . data ) ;
2024-01-08 14:00:04 +00:00
originalData . value = JSON . parse ( JSON . stringify ( formData . value ) ) ;
hasChanges . value = false ;
2023-11-30 20:46:07 +00:00
} catch ( err ) {
2024-02-12 14:06:20 +00:00
console . error ( err ) ;
2024-02-27 07:22:38 +00:00
notify ( 'errors.writeRequest' , 'negative' ) ;
2023-11-30 20:46:07 +00:00
}
2022-10-27 12:59:19 +00:00
isLoading . value = false ;
}
function reset ( ) {
state . set ( $props . model , originalData . value ) ;
2023-08-24 13:05:31 +00:00
originalData . value = JSON . parse ( JSON . stringify ( originalData . value ) ) ;
emit ( 'onFetch' , state . get ( $props . model ) ) ;
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 ) ;
}
) ;
}
2023-01-26 13:29:01 +00:00
watch ( formUrl , async ( ) => {
originalData . value = null ;
reset ( ) ;
fetch ( ) ;
} ) ;
2024-03-08 18:05:11 +00:00
defineExpose ( {
save ,
isLoading ,
} ) ;
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" >
2023-10-25 16:55:20 +00:00
< QForm
v - if = "formData"
@ submit = "save"
@ reset = "reset"
class = "q-pa-md"
id = "formModel"
>
< QCard >
< slot
name = "form"
: data = "formData"
: validate = "validate"
: filter = "filter"
/ >
< / QCard >
< / QForm >
< / div >
2024-02-02 15:45:29 +00:00
< Teleport
to = "#st-actions"
v - if = "stateStore?.isSubToolbarShown() && componentIsRendered"
>
2023-08-16 13:04:16 +00:00
< div v-if ="$props.defaultActions" >
2023-08-24 13:05:31 +00:00
< QBtnGroup push class = "q-gutter-x-sm" >
2023-08-16 13:04:16 +00:00
< slot name = "moreActions" / >
2023-07-31 13:24:34 +00:00
< QBtn
2024-03-12 14:31:32 +00:00
: label = "tMobile(defaultButtons.reset.label)"
: color = "defaultButtons.reset.color"
: icon = "defaultButtons.reset.icon"
2023-08-16 13:04:16 +00:00
flat
@ click = "reset"
2023-07-31 13:24:34 +00:00
: disable = "!hasChanges"
2024-03-12 14:31:32 +00:00
: title = "t(defaultButtons.reset.label)"
2023-07-31 13:24:34 +00:00
/ >
2023-04-11 11:31:03 +00:00
< QBtn
2024-03-12 14:31:32 +00:00
: label = "tMobile(defaultButtons.save.label)"
: color = "defaultButtons.save.color"
: icon = "defaultButtons.save.icon"
2023-08-16 13:04:16 +00:00
@ click = "save"
2022-10-31 08:34:01 +00:00
: disable = "!hasChanges"
2024-03-12 14:31:32 +00:00
: title = "t(defaultButtons.save.label)"
2022-10-31 08:34:01 +00:00
/ >
2023-08-24 13:05:31 +00:00
< / QBtnGroup >
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"
2023-12-19 15:10:58 +00:00
style = "min-width: 100%"
2023-04-11 11:31:03 +00:00
/ >
2022-10-27 12:59:19 +00:00
< / template >
2023-10-25 16:45:29 +00:00
< style lang = "scss" scoped >
# formModel {
2023-09-19 07:42:47 +00:00
max - width : 800 px ;
width : 100 % ;
}
2023-11-30 11:47:40 +00:00
2023-10-25 16:45:29 +00:00
. q - card {
padding : 32 px ;
}
2023-09-19 07:42:47 +00:00
< / style >
2024-02-27 14:41:09 +00:00
< i18n >
es :
Unsaved changes will be lost : Los cambios que no haya guardado se perderán
Are you sure exit without saving ? : ¿ Seguro que quiere salir sin guardar ?
< / i18n >