# Formulario Bone

# Introducción

Próximamente será desarrollado en otra sección

# Obtención de los campos

# Código fuente

<script>
const v1ExternalDBEndpoint = '/v1/icl/exec/externalDB/'
const propsLocal = {
  campos: { type: Array, default: () => [] },
  sections: { type: Array, default: () => [] },
  readonly: { type: Boolean, default: false },
  flat: { type: Boolean, default: false },
  filter: {
    type: Object,
    default: () => {
      return {}
    }
  },
  id_resolver: {
    type: String
  },
  campos_fcn: {
    type: String
  },
  args_campos_fcn: {
    type: Array,
    default: () => []
  },
  forceSubmitRefresh: {
    type: Boolean,
    default: false
  },
  comportamientos: {
    type: Array,
    default: () => []
  },
  campos_fcn_refresh: {
    type: String,
    required: false
  },
  args_campos_fcn_refresh: {
    type: Array,
    required: false
  },
  refresh_timer: {
    type: Object,
    required: false
  },
  sectionsAttrs: {
    type: Object,
    default: () => ({})
  },
  sectionsFcn: {
    type: String,
    default: ''
  },
  argsSectionsFcn: {
    type: Array,
    default: () => []
  }
}
import Vue from 'vue'
import {
  ref,
  onMounted,
  computed,
  watch,
  onBeforeUnmount,
  h,
  getCurrentInstance
} from 'vue'
import {
  normalizeProps,
  assignCamelToSnake
} from '../../../helpers/propsGenerator'
const mergedProps = normalizeProps(propsLocal)
import resolveMixin from '../../../mixins/resolveIdResolver'
import {
  form_navigator,
  form_modal,
  form_fcn_executer,
  unavailableTypes,
  formComponentName,
  externalDBEndpoint,
  getCamposEndpoint,
  fcnExecuterEndpoint
} from '../../../constants'
import fcnResponseHandler from '../../../mixins/fcnResponseHandler'
export default {
  name: formComponentName + 'Bone',
  mixins: [fcnResponseHandler, resolveMixin],
  props: mergedProps,
  setup(props, { slots, emit, expose }) {
    const vueInstance = getCurrentInstance()
    const vuetify = vueInstance.proxy.$vuetify
    const iclAxios = Vue.prototype.$iclAxios
    const iclstore = Vue.prototype.$iclstore
    const iclRouter = Vue.prototype.$iclRouter
    const logErrorComponent = Vue.prototype.$logErrorComponent
    const detectResponseStructErrors = Vue.prototype.$detectResponseStructErrors
    const propError = Vue.prototype.$propError
    const { handleError, handleSuccess, handleSuccessResponse } =
      fcnResponseHandler.methods
    const { resolve } = resolveMixin.methods
    const camposData = ref([])
    const datosResp = ref({})
    const camposDataAux = ref([])
    const invalids = ref([])
    const loadingRefresh = ref(false)
    const loadingUpdate = ref(false)
    const hasError = ref(false)
    const refreshed = ref(false)
    const dialogData = ref({
      id_resolver_dlg: '',
      titulo: '',
      show: false,
      dialog_args: {}
    })
    const comportamientosLocal = ref([])
    const actionToConfirm = ref({})
    const structureError = ref(false)
    const firstTime = ref(true)
    const loopInterval = ref(undefined)
    const sectionsLocal = ref([])
    const sectionsAttrsDefault = ref({
      flat: true,
      accordion: true,
      hover: true,
      multiple: true
    })

    const sectionsComputed = computed(() => {
      if (props.sectionsFcn) return sectionsLocal.value
      return props.sections
    })

    const sectionsAttrsComputed = computed(() => {
      let openSections = sectionsComputed.value.map((sec, ind) => {
        if (sec['is-default-open']) return ind
      })
      return {
        ...sectionsAttrsDefault.value,
        ...props.sectionsAttrs,
        value: openSections
      }
    })

    const columnasControl = computed(() => {
      let mayor = 1
      for (let i in camposData.value) {
        let props = camposData.value[i].props
        let widthByBreakpoint = {
          xs: props['width-xs'],
          sm: props['width-sm'],
          md: props['width-md'],
          lg: props['width-lg'],
          xl: props['width-xl'],
          default: props.width
        }
        let breakpoint = vuetify.breakpoint.name
        let selectedBreakpoint = widthByBreakpoint[breakpoint]
          ? widthByBreakpoint[breakpoint]
          : widthByBreakpoint.default
        if (!selectedBreakpoint) selectedBreakpoint = 1
        if (selectedBreakpoint > mayor) mayor = selectedBreakpoint
      }
      return mayor
    })

    const generatedIdResolver = computed(() => {
      return props.id_resolver ? props.id_resolver : iclRouter.currentRoute.path
    })

    watch(
      () => props.readonly,
      (isReadonly) => {
        if (camposDataAux.value.length === 0 && camposData.value.length !== 0)
          camposDataAux.value = JSON.parse(JSON.stringify(camposData.value))

        if (isReadonly) {
          camposData.value.forEach((campo) => {
            campo.props.readonly = true
          })
        } else {
          if (camposDataAux.value.length !== 0)
            camposData.value = JSON.parse(JSON.stringify(camposDataAux.value))
        }
      }
    )

    watch(
      () => props.campos,
      (isCampos) => {
        assignCampos(isCampos)
      }
    )

    watch(
      () => camposData.value,
      (newCampos) => {
        emit('changeCamposValues', newCampos)
      },
      { deep: true }
    )

    const changeFieldProps = (isCampo, props) => {
      camposData.value = camposData.value.map((wasCampo) => {
        return wasCampo.props.id === isCampo.props.id
          ? { ...wasCampo, props: { ...wasCampo.props, ...props } }
          : wasCampo
      })
    }

    const changeLinkedValue = (fieldData) => {
      const { action, campo } = fieldData || {}
      refreshed.value = true
      if (action === 'update') {
        changeFieldProps(campo, { disabled: true, loading: true })
        createdFunctions(
          props.campos_fcn_refresh,
          props.args_campos_fcn_refresh,
          { campo }
        )
      } else {
        createdFunctions(
          props.campos_fcn_refresh,
          props.args_campos_fcn_refresh
        )
      }
    }

    const muestraCampo = (campo) => {
      if (
        Object.keys(props.filter).length === 0 &&
        (typeof campo.visible === 'undefined' || campo.visible)
      ) {
        return true
      }
      if (typeof campo.visible === 'undefined' || campo.visible) {
        if (
          props.filter.hasOwnProperty('nombre') &&
          campo[props.filter.nombre] &&
          Object.keys(props.filter).length !== 1
        ) {
          return campo[props.filter.nombre] === props.filter.valor
        }
        return false
      }
      return false
    }

    const isIrGpsMarkerValida = (campo) => {
      if (campo.value.lat === '' || campo.value.long === '') {
        invalids.value.push('latitud y longitud de ' + campo.label)
        return false
      }
      return true
    }

    const typesValidator = (campos) => {
      let aux = true
      for (let campo in campos) {
        let campoProps = campos[campo]
        if (
          typeof campoProps === 'object' &&
          (typeof campoProps.props.visible === 'undefined' ||
            campoProps.props.visible) &&
          (campoProps.props.required || campoProps.type === 'IrRepeater')
        ) {
          if (campoProps.type === 'IrGpsMarker') {
            aux = isIrGpsMarkerValida(campoProps.props)
          } else if (campoProps.type === 'IrRepeater') {
            aux = isIrRepeaterValida(campoProps.props)
          } else if (campoProps.type === 'IrWysiwyg') {
            aux = isIrWysiwygValida(campoProps.props)
          } else {
            if (campoProps.props.value === '') {
              aux = false
              invalids.value.push(campoProps.props.label)
              break
            }
          }
        }
      }
      return aux
    }

    const isIrRepeaterValida = (campo) => {
      let aux = true
      for (let arr of campo.items) {
        let auxrepeater = true
        auxrepeater = typesValidator(arr)
        if (!auxrepeater) {
          invalids.value.push(campo.label)
          aux = false
          break
        }
      }
      return aux
    }

    const isIrWysiwygValida = (campo) => {
      let valid = campo.value !== '<p></p>'
      if (!valid) {
        invalids.value.push(campo.label)
      }
      return valid
    }

    const validaForm = () => {
      let aux = true
      invalids.value = []
      aux = typesValidator(camposData.value)
      if (!aux) {
        let invalidos = ''
        for (let i = 0; i < invalids.value.length; i++) {
          if (i === invalids.value.length - 1) {
            invalidos = invalidos + invalids.value[i]
          } else {
            invalidos = invalidos + invalids.value[i] + ' - '
          }
        }
        iclstore.commit('updateSnack', {
          active: true,
          text: 'Complete correctamente los campos ' + invalidos,
          color: 'error'
        })
      }
      return aux
    }

    const makeQuery = async (fcn, args) => {
      let { pre_api, post_api } = args
      if (pre_api && pre_api.config && pre_api.config.loading_message) {
        iclstore.dispatch('cancelSnack')
        iclstore.commit('updateSnack', {
          text: pre_api.config.loading_message,
          active: true,
          color: 'success',
          time: 999999,
          is_closing_allowed: false
        })
      } else if (
        post_api &&
        post_api.config &&
        post_api.config.loading_message
      ) {
        iclstore.dispatch('cancelSnack')
        iclstore.commit('updateSnack', {
          text: post_api.config.loading_message,
          active: true,
          color: 'success',
          time: 999999,
          is_closing_allowed: false
        })
      }
      emit('toggle-loading-overlay', true)
      let localArgs = [
        JSON.stringify({
          ...args,
          id_usuario: iclstore.state.id_usuario,
          id_resolver: generatedIdResolver.value
        })
      ]
      iclAxios
        .post(fcnExecuterEndpoint + fcn, localArgs, { timeout: 0 })
        .then((rsp) => {
          handleSuccess(rsp, manageCreatedFunctions, {
            iclRouter,
            iclstore,
            emit,
            handleSuccessResponse,
            assignDialogData,
            resolve
          })
        })
        .catch((error) => {
          handleError(error, iclstore)
          console.error(error)
        })
        .finally(() => {
          if (
            (pre_api && pre_api.config && pre_api.config.loading_message) ||
            (post_api && post_api.config && post_api.config.loading_message)
          ) {
            iclstore.dispatch('showNextSnack')
          }
          emit('toggle-loading-overlay', false)
        })
    }

    const preApiModal = async (
      fcn,
      args,
      titulo,
      id_resolver_aux,
      position,
      dialogAttrs
    ) => {
      let { pre_api } = args
      if (pre_api && pre_api.config && pre_api.config.loading_message) {
        iclstore.commit('updateSnack', {
          text: pre_api.config.loading_message,
          active: true,
          color: 'success',
          time: 999999
        })
      }
      emit('toggle-loading-overlay', true)
      let localArgs = [
        JSON.stringify({
          ...args,
          id_usuario: iclstore.state.id_usuario,
          id_resolver: generatedIdResolver.value
        })
      ]
      iclAxios
        .post( v1ExternalDBEndpoint + fcn, localArgs)
        .catch((error) => {
          handleError(error, iclstore)
          console.error(error)
        })
        .finally(() => {
          iclstore.dispatch('cancelSnack')
          assignDialogData({ titulo, id_resolver: id_resolver_aux, position, dialogAttrs })
          emit('toggle-loading-overlay', false)
        })
    }

    const preApiNavigator = async (fcn, args, id_resolver, found) => {
      let { pre_api } = args
      if (pre_api && pre_api.config && pre_api.config.loading_message) {
        iclstore.commit('updateSnack', {
          text: pre_api.config.loading_message,
          active: true,
          color: 'success',
          time: 999999
        })
      }
      emit('toggle-loading-overlay', true)
      let localArgs = [
        JSON.stringify({
          ...args,
          id_usuario: iclstore.state.id_usuario,
          id_resolver: generatedIdResolver.value
        })
      ]
      iclAxios
        .post(v1ExternalDBEndpoint + fcn, localArgs)
        .catch((error) => {
          handleError(error, iclstore)
          console.error(error)
        })
        .finally(() => {
          iclstore.dispatch('cancelSnack')
          emit('toggle-loading-overlay', false)
          redirect(id_resolver, found)
        })
    }

    const manageCreatedFunctions = async () => {
      emit('refresh') // para customizaciones de componente
      if (
        refreshed.value ||
        (props.campos_fcn_refresh && props.forceSubmitRefresh)
      ) {
        await createdFunctions(
          props.campos_fcn_refresh,
          props.args_campos_fcn_refresh
        )
      } else {
        await createdFunctions(props.campos_fcn, props.args_campos_fcn)
      }
    }

    const consultaSections = () => {
      if (props.sectionsFcn) {
        let argsAux = {
          args: props.argsSectionsFcn,
          id_usuario: iclstore.state.id_usuario,
          id_resolver: generatedIdResolver.value
        }
        loadingUpdate.value = true
        iclAxios
          .post(externalDBEndpoint + props.sectionsFcn, [
            JSON.stringify(argsAux)
          ])
          .then((res) => {
            sectionsLocal.value = res
          })
          .catch((err) => {
            console.log(err)
            iclstore.commit('updateSnack', {
              text: 'Ha ocurrido un error al cargar algunos campos del formulario',
              active: true,
              color: 'warning'
            })
          })
          .finally(() => {
            loadingUpdate.value = false
          })
      }
    }

    const checkCompIdField = () => {
      if (camposData.value.length > 0) {
        comportamientosLocal.value = Object.assign([])
        props.comportamientos.forEach((comp) => {
          if (typeof comp.field_id !== 'undefined') {
            let arrFieldId = Array.isArray(comp.field_id)
              ? comp.field_id
              : [comp.field_id]
            let found = findFields(
              comp.field_id,
              'Se envió el "field_id" "#id#" que no cerresponde a un id de campo existente. No se renderizará el comportamiento de tipo "' +
                comp.type +
                '".',
              ' No se renderizará el comportamiento de tipo "' +
                comp.type +
                '".'
            )
            if (typeof found === 'undefined') return
            else if (found.length === arrFieldId.length)
              comportamientosLocal.value.push(comp)
          } else comportamientosLocal.value.push(comp)
        })
      }
    }

    const replaceCamposById = (campos) => {
      let camposAux = Object.assign(camposData.value)
      campos.forEach(({ props: { id } }, index) => {
        if (id) {
          const campoIndex = camposAux.findIndex(({ props: { id: idAux } }) => {
            return idAux === id
          })
          if (campoIndex !== -1) camposAux[campoIndex] = { ...campos[index] }
          else camposAux[camposAux.length] = { ...campos[index] }
        }
      })
      assignCampos(camposAux)
    }

    const handleFormClick = (action) => {
      let {
        type,
        fcn_click,
        field_id,
        id_resolver,
        args_fcn_click,
        titulo,
        confirmation,
        validation,
        pre_api,
        post_api,
        position,
        dialogAttrs
      } = action
      let modalPosition = action['modal-position']
      if (typeof confirmation === 'undefined' || confirmation === '') {
        let found = []
        let id_resolver_aux = ''
        let argsLocal = {}
        let isFieldValid = typeof field_id !== 'undefined'
        if (isFieldValid && camposData.value.length > 0) {
          let foundAux = findFields(
            field_id,
            'Ocurrió un error y no se encontró el campo correspondiente con id: "#id#".',
            ''
          )
          if (typeof foundAux === 'undefined') {
            emit('cerrarDialog')
            return
          }
          found = foundAux
        }
        let validValidation = typeof validation !== 'undefined'
        if (
          type === form_fcn_executer &&
          ((validValidation && validation && validaForm()) ||
            (validValidation && !validation) ||
            (!validValidation && validaForm()))
        ) {
          argsLocal = {
            args: typeof args_fcn_click !== 'undefined' ? args_fcn_click : []
          }
          let foundAux = found.map((field) => field.props.value)
          argsLocal = { ...argsLocal, fields: foundAux } // El valor del campo correspondiente se agrega a argsLocal.field
          makeQuery(fcn_click, {
            ...argsLocal,
            campos: camposData.value,
            pre_api,
            post_api
          })
          emit('cerrarDialog')
        } else if (type === form_modal) {
          id_resolver_aux = resolve(id_resolver, found)
          if (pre_api)
            preApiModal(
              'fcn_modal',
              { campos: camposData.value, pre_api },
              titulo,
              id_resolver_aux,
              modalPosition,
              dialogAttrs
            )
          else
            assignDialogData({
              titulo,
              id_resolver: id_resolver_aux,
              position: modalPosition,
              dialogAttrs
            })
        } else if (type === form_navigator) {
          emit('cerrarDialog')
          if (pre_api)
            preApiNavigator(
              'fcn_navigator',
              { campos: camposData.value, pre_api },
              id_resolver,
              found
            )
          else redirect(id_resolver, found)
        }
      } else {
        actionToConfirm.value = Object.assign({}, action)
        assignDialogData({ message: confirmation })
      }
    }

    const findFields = (ids, msg, unavailableTypesErrorMsg) => {
      let arrFieldId = Array.isArray(ids) ? ids : [ids]
      let found = []
      for (let i = 0; i < arrFieldId.length; i++) {
        let id = arrFieldId[i]
        let foundedField = camposData.value.find(
          (campo) => campo.props.id === id
        )
        if (typeof foundedField === 'undefined') {
          logErrorComponent(
            formComponentName,
            msg.replace('#id#', id),
            iclRouter
          )
          return
        } else if (
          typeof unavailableTypes.find((type) => foundedField.type === type) !==
          'undefined'
        ) {
          logErrorComponent(
            formComponentName,
            'Se envió un "field_id" correspondiente a un campo de tipo "' +
              foundedField.type +
              '" y el mismo no es válido.' +
              unavailableTypesErrorMsg,
            iclRouter
          )
          return
        } else found.push(foundedField)
      }
      return found
    }

    const assignCampos = (campos) => {
      camposData.value = Object.assign(campos)
      emit('updateCampos', camposData.value) // para customizaciones de componente
      checkCompIdField()
    }

    const createdFunctions = async (
      consultaCampos,
      argsConsultaCampos,
      updateFieldData
    ) => {
      let { campo, isFieldUpdate = false } = updateFieldData
        ? updateFieldData
        : {}
      if (
        typeof consultaCampos !== 'undefined' &&
        consultaCampos !== null &&
        iclAxios
      ) {
        let argsAux = {
          ...argsConsultaCampos,
          id_usuario: iclstore.state.id_usuario,
          id_resolver: generatedIdResolver.value
        }
        const isUpdatingField = typeof campo !== 'undefined'
        if (!isUpdatingField && !isFieldUpdate)
          // Cuando se actualiza el formulario por campo por id el loading lo tiene el componente que genera la actualización, no el formulario
          loadingUpdate.value = true
        if (refreshed.value) {
          if (isUpdatingField) argsAux = { ...argsAux, campo }
          else argsAux = { ...argsAux, campos: camposData.value }
        }
        let endpoint
        if (!refreshed.value) endpoint = getCamposEndpoint
        else endpoint = externalDBEndpoint
        let paramsDetectErrors = [
          consultaCampos,
          ['props', 'type'],
          formComponentName,
          '{type: String, props: Object}',
          'https://icl.iridiumrobotics.com.ar/components/formularios.html#uso'
        ]
        return await iclAxios
          .post(endpoint + consultaCampos, [JSON.stringify(argsAux)])
          .then((rsp) => {
            rsp = rsp.data ?? rsp            
            if (isUpdatingField || isFieldUpdate) {
              replaceCamposById(rsp)
              if (isUpdatingField)
                changeFieldProps(campo, { disabled: false, loading: false })
            } else assignCampos(rsp)
            detectResponseStructErrors(rsp, ...paramsDetectErrors)
          })
          .catch((error) => {
            console.log(error)
            logErrorComponent(
              formComponentName,
              'Ocurrió un error en la consulta de los campos.',
              iclRouter
            )
            assignCampos(props.campos)
            hasError.value = true
          })
          .finally(() => {
            loadingUpdate.value = false
            if (firstTime.value) {
              firstTime.value = false
              manageRefreshRepeater()
            }
          })
      } else assignCampos(props.campos)
      if (firstTime.value && props.campos_fcn_refresh) {
        firstTime.value = false
        manageRefreshRepeater()
      }
    }

    const manageRefreshRepeater = () => {
      const { repeat_time, loops, type, freeze_screen } = props.refresh_timer
        ? props.refresh_timer
        : {}
      let isInfiniteLoop = loops === -1
      let countingDown = loops
      let isFieldUpdate = type === 'update'
      const isLoopAvailable =
        typeof repeat_time !== 'undefined' &&
        typeof loops !== 'undefined' &&
        (countingDown > 0 || isInfiniteLoop)
      if (isLoopAvailable) {
        refreshed.value = true
        loopInterval.value = setInterval(async () => {
          console.log('in loop')
          if (!isInfiniteLoop) countingDown = countingDown - 1
          if (isFieldUpdate && freeze_screen)
            emit('toggle-loading-overlay', true)
          await createdFunctions(
            props.campos_fcn_refresh,
            props.args_campos_fcn_refresh,
            { isFieldUpdate }
          ).finally(() => {
            emit('toggle-loading-overlay', false)
          })
          if (!isInfiniteLoop && countingDown <= 0)
            clearInterval(loopInterval.value)
        }, repeat_time)
      }
    }

    const continueAction = (confirmation) => {
      if (confirmation) {
        delete actionToConfirm.value.confirmation
        handleFormClick(actionToConfirm.value)
      } else emit('cerrarDialog')
      actionToConfirm.value = Object.assign({})
    }

    const redirect = (path, query) => {
      let pathAux = resolve(path, query)
      if (pathAux.indexOf('https://') === 0 || pathAux.indexOf('http://') === 0)
        window.location.href = pathAux
      else iclRouter.push({ path: pathAux })
    }

    const assignDialogData = ({ titulo, id_resolver, message, position, dialogAttrs }) => {
      dialogData.value = Object.assign({})
      dialogData.value.id_resolver_dlg = id_resolver
      dialogData.value.titulo = titulo
      dialogData.value.message = message
      dialogData.value.position = position
      dialogData.value = {...dialogData.value, ...dialogAttrs}
      emit('abrirDialog')
    }

    const emitDisparadorUpdate = (key) => {
      let varkey = key
      emit('disparador', varkey)
    }

    assignCamelToSnake(mergedProps, props)
    consultaSections()
    createdFunctions(props.campos_fcn, props.args_campos_fcn)

    onMounted(() => {
      // if (process.client) {
      document.body.id = 'address'
      document.body.classList =
        'lang-es country-ar currency-ars layout-full-width page-address tax-display-enabled page-customer-account'
      // }
      structureError.value = propError(propsLocal, props, formComponentName)
      if (structureError.value) {
        iclstore.commit('updateSnack', {
          text: 'Ha ocurrido un error al cargar algunos campos del formulario',
          active: true,
          color: 'warning'
        })
      }
    })

    onBeforeUnmount(() => {
      if (loopInterval.value) {
        clearInterval(loopInterval.value)
      }
    })
    expose({
      createdFunctions,
      camposData,
      comportamientosLocal,
      checkCompIdField,
      muestraCampo
    })
    return () => {
      if (structureError.value) {
        return
      }
      return h(
        'div', // Contenedor como nodo raíz
        [
          slots.default({
            loadingRefresh: loadingRefresh.value,
            loadingUpdate: loadingUpdate.value,
            toggleLoading: () => {
              loadingRefresh.value = !loadingRefresh.value
            },
            buttonsForm: comportamientosLocal.value,
            columnasControl: columnasControl.value,
            form: camposData.value,
            muestraCampo: muestraCampo,
            conditionalSlot: !props.readonly,
            flat: props.flat,
            id_resolver_dlg: dialogData.value.id_resolver_dlg,
            dialogAttrs: dialogData.value,
            handleFormClick: handleFormClick,
            continueAction: continueAction,
            hasError: hasError.value,
            changeLinkedValue: changeLinkedValue,
            id_resolver: props.id_resolver,
            refreshParent: manageCreatedFunctions,
            saveDraftFcn: (draftData) => {
              let { args, fcn } = draftData
              if (!fcn) {
                logErrorComponent(
                  formComponentName,
                  'No se ha definido correctamente la propiedad save-draft. Es necesario que se envíe la key fcn',
                  iclRouter
                )
                return
              }
              if (args)
                makeQuery(fcn, { ...{ args: args }, campos: camposData.value })
              else makeQuery(fcn, { args: {}, campos: camposData.value })
            },
            sections: sectionsComputed.value,
            sectionsAttrs: sectionsAttrsComputed.value
          })
        ]
      )
    }
  }
}
</script>
Last Updated: 4/5/2024, 4:52:19 PM