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-03-06 15:23:19 +00:00
import { onBeforeRouteLeave , useRouter } 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' ;
2024-05-22 09:22:46 +00:00
import { useArrayData } from 'src/composables/useArrayData' ;
2024-06-11 12:09:42 +00:00
import { useRoute } from 'vue-router' ;
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 ( ) ;
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 ( ) ;
2024-06-11 12:09:42 +00:00
const route = useRoute ( ) ;
2022-10-27 12:59:19 +00:00
const $props = defineProps ( {
url : {
type : String ,
default : '' ,
} ,
model : {
type : String ,
2024-06-11 12:09:42 +00:00
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 ,
} ,
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
} ,
2024-03-06 15:23:19 +00:00
goTo : {
type : String ,
default : '' ,
description : 'It is used for redirect on click "save and continue"' ,
} ,
2024-06-20 08:26:22 +00:00
reload : {
type : Boolean ,
default : false ,
} ,
2022-10-27 12:59:19 +00:00
} ) ;
2023-12-18 15:06:07 +00:00
const emit = defineEmits ( [ 'onFetch' , 'onDataSaved' ] ) ;
2024-06-11 12:09:42 +00:00
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 ) ;
2024-06-11 12:09:42 +00:00
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 ( { } ) ;
2024-06-11 12:09:42 +00:00
const formData = computed ( ( ) => state . get ( modelValue ) ) ;
2024-05-22 09:22:46 +00:00
const formUrl = computed ( ( ) => $props . url ) ;
const defaultButtons = computed ( ( ) => ( {
save : {
color : 'primary' ,
icon : 'save' ,
label : 'globals.save' ,
} ,
reset : {
color : 'primary' ,
icon : 'restart_alt' ,
label : 'globals.reset' ,
} ,
... $props . defaultButtons ,
} ) ) ;
2024-02-02 15:45:29 +00:00
2023-11-30 11:47:40 +00:00
onMounted ( async ( ) => {
2024-05-23 07:29:50 +00:00
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
2024-06-11 12:09:42 +00:00
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 ( ) ;
2024-06-18 07:43:44 +00:00
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 ,
2024-05-22 11:57:12 +00:00
( newVal , oldVal ) => {
if ( ! oldVal ) return ;
2024-05-23 07:29:50 +00:00
hasChanges . value =
! isResetting . value &&
JSON . stringify ( newVal ) !== JSON . stringify ( originalData . value ) ;
2024-05-22 09:22:46 +00:00
isResetting . value = false ;
} ,
{ deep : true }
) ;
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 ,
2024-06-06 07:53:48 +00:00
( val ) => updateAndEmit ( 'onFetch' , val )
2024-05-22 09:22:46 +00:00
) ;
watch ( formUrl , async ( ) => {
originalData . value = null ;
reset ( ) ;
2024-05-22 11:57:12 +00:00
await fetch ( ) ;
2024-05-22 09:22:46 +00:00
} ) ;
2024-02-27 14:41:09 +00:00
onBeforeRouteLeave ( ( to , from , next ) => {
2024-04-19 07:39:17 +00:00
if ( hasChanges . value && $props . observeFormChanges )
2024-04-09 13:12:54 +00:00
quasar . dialog ( {
component : VnConfirm ,
componentProps : {
title : t ( 'Unsaved changes will be lost' ) ,
message : t ( 'Are you sure exit without saving?' ) ,
promise : ( ) => next ( ) ,
} ,
} ) ;
else next ( ) ;
2024-02-27 14:41:09 +00:00
} ) ;
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.
2024-06-11 12:09:42 +00:00
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 ( ) {
2024-02-05 20:51:09 +00:00
try {
2024-05-08 09:02:40 +00:00
let { data } = await axios . get ( $props . url , {
2024-02-05 20:51:09 +00:00
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 ) {
2024-06-11 12:09:42 +00:00
state . set ( modelValue , { } ) ;
2024-02-05 20:51:09 +00:00
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 {
2023-12-26 19:15:46 +00:00
const body = $props . mapper ? $props . mapper ( formData . 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 ) ;
2024-06-20 08:26:22 +00:00
if ( $props . reload ) await arrayData . fetch ( { } ) ;
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' ) ;
2024-06-06 07:33:01 +00:00
} finally {
hasChanges . value = false ;
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 } ) ;
}
2023-08-24 13:05:31 +00:00
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 ) ;
}
) ;
}
2023-01-26 13:29:01 +00:00
2024-06-06 07:53:48 +00:00
function updateAndEmit ( evt , val , res ) {
2024-06-11 12:09:42 +00:00
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 ;
2024-03-08 18:05:11 +00:00
2024-06-11 12:09:42 +00:00
emit ( evt , state . get ( modelValue ) , res ) ;
2024-05-22 09:22:46 +00:00
}
2024-03-08 18:05:11 +00:00
defineExpose ( {
save ,
isLoading ,
2024-03-28 16:45:35 +00:00
hasChanges ,
2024-06-03 11:47:32 +00:00
reset ,
2024-06-04 12:25:03 +00:00
fetch ,
2024-03-08 18:05:11 +00:00
} ) ;
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
/ >
2024-03-06 15:23:19 +00:00
< QBtnDropdown
v - if = "$props.goTo"
2024-03-07 13:54:20 +00:00
@ click = "saveAndGo"
: label = "tMobile('globals.saveAndContinue')"
: title = "t('globals.saveAndContinue')"
2024-03-06 15:23:19 +00:00
: disable = "!hasChanges"
color = "primary"
icon = "save"
split
>
< QList >
2024-03-07 13:54:20 +00:00
< QItem
clickable
v - close - popup
@ click = "save"
: title = "t('globals.save')"
>
2024-03-06 15:23:19 +00:00
< QItemSection >
2024-03-07 13:54:20 +00:00
< QItemLabel >
< QIcon
name = "save"
color = "white"
class = "q-mr-sm"
size = "sm"
/ >
{ { t ( 'globals.save' ) . toUpperCase ( ) } }
< / QItemLabel >
2024-03-06 15:23:19 +00:00
< / QItemSection >
< / QItem >
< / QList >
< / QBtnDropdown >
2023-04-11 11:31:03 +00:00
< QBtn
2024-03-06 15:23:19 +00:00
v - else
2023-08-16 13:04:16 +00:00
: label = "tMobile('globals.save')"
2022-10-31 08:34:01 +00:00
color = "primary"
2023-08-16 13:04:16 +00:00
icon = "save"
@ 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 >
2024-04-02 08:46:27 +00:00
. q - notifications {
color : black ;
}
2023-10-25 16:45:29 +00:00
# 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 >