# Iterator Bone

# Introducción

Éste componente no está encargado de renderizar, es de uso interno de la tabla. Guía de uso parte lógica

# Código fuente

<script>
const propsLocal = {
  items: {
    type: Array,
    default: () => []
  },
  cache_items: {
    type: Boolean,
    default: true
  },
  filtros: {
    type: Array,
    default: () => []
  },
  filtros_fcn: {
    type: String,
    required: false
  },
  args_filtros_fcn: {
    type: Array,
    default: () => []
  },
  items_fcn: {
    type: String,
    required: false
  },
  args_items_fcn: {
    type: Array,
    default: () => []
  },
  rowsPerPage: {
    type: Number,
    default: 5
  },
  comportamiento: {
    type: Object,
    default: () => ({})
  },
  itemKey: {
    type: String
  },
  id_resolver: {
    type: String
  },
  type: {
    type: String
  },
  columns: {
    type: Number
  },
  columnsXs: {
    type: Number,
    default: 1
  },
  columnsSm: {
    type: Number,
    default: 2
  },
  columnsMd: {
    type: Number,
    default: 3
  },
  columnsLg: {
    type: Number
  },
  columnsXl: {
    type: Number
  },
  adaptPerPage: {
    type: Boolean
  },
  hideDefaultFooter: {
    type: Boolean,
    default: false
  },
  hideDefaultHeader: {
    type: Boolean,
    default: true
  },
  colorHeader: {
    type: String,
    default: '#ffffff'
  },
  fullHeight: {
    type: Boolean,
    required: false
  },
  linked: {
    type: Boolean,
    default: false
  }
}

import {
  normalizeProps,
  assignCamelToSnake
} from '../../helpers/propsGenerator'
import resolveMixin from '../../mixins/resolveIdResolver'
import {
  fcn_executer,
  iteratorComponentName,
  navigator,
  modal,
  availableTableTypes,
  externalDBEndpoint,
  getCamposEndpoint
} from '../../constants'
import fcnResponseHandler from '../../mixins/fcnResponseHandler'
const mergedProps = normalizeProps(propsLocal)
import Vue, { ref, onMounted, h, watch, computed } from 'vue'

export default {
  name: iteratorComponentName + 'Bone',
  mixins: [fcnResponseHandler, resolveMixin],
  props: mergedProps,
  setup(props, { slots, emit }) {
    const { handleError, handleSuccess, handleSuccessResponse } =
      fcnResponseHandler.methods
    const { resolve } = resolveMixin.methods
    const iclAxios = Vue.prototype.$iclAxios
    const iclstore = Vue.prototype.$iclstore
    const iclRouter = Vue.prototype.$iclRouter
    const detectResponseStructErrors = Vue.prototype.$detectResponseStructErrors
    const logErrorComponent = Vue.prototype.$logErrorComponent
    const propError = Vue.prototype.$propError
    const itemsLocal = ref([])
    const filtrosLocal = ref([])
    const loading = ref(false)
    const keys = ref([])
    const dialogData = ref({ id_resolver_dlg: '', titulo: '', dialog_args: {} })
    const comportamientoLocal = ref({})
    const actionToConfirm = ref({})
    const itemToConfirm = ref({})
    const structureError = ref(false)
    const computedIdResolver = computed(() => {
      return props.idResolver
        ? props.idResolver
        : props.id_resolver
    })

    // Watchers
    watch(
      () => props.filtros,
      (newFiltros) => {
        filtrosLocal.value = newFiltros
      }
    )

    const getFiltros = async (fcn, args) => {
      let argsAux = {
        id_usuario: iclstore.state.id_usuario,
        id_resolver: computedIdResolver.value
          ? computedIdResolver.value
          : iclRouter.currentRoute.path,
        ...args
      }

      try {
        const rsp = await iclAxios.post(getCamposEndpoint + fcn, [
          JSON.stringify(argsAux)
        ])
        return rsp
      } catch (error) {
        console.log(error)
        logErrorComponent(
          iteratorComponentName,
          'Ocurrió un error en la consulta de los campos.',
          iclRouter
        )
        return props.filtros
      }
    }

    const getItems = async (fcn, filters, args) => {
      loading.value = true
      let params = {
        filtros: filters,
        args,
        id_usuario: iclstore.state.id_usuario,
        id_resolver: computedIdResolver.value
          ? computedIdResolver.value
          : iclRouter.currentRoute.path
      }

      try {
        let rsp = await iclAxios.post(externalDBEndpoint + fcn, [
          JSON.stringify(params)
        ])
        handleSuccess(rsp, () => {}, {
          iclRouter,
          iclstore,
          emit,
          handleSuccessResponse
        })

        if (Array.isArray(rsp)) {
          detectResponseStructErrors(
            rsp,
            fcn,
            [],
            'IrDataCardBone',
            '{ <propName>: Any }',
            'https://icl.iridiumrobotics.com.ar/components/ir-data-table.html#uso'
          )
        } else {
          structureError.value = propError(
            propsLocal,
            { ...props, ...rsp },
            iteratorComponentName
          )
        }

        if (
          rsp === null ||
          rsp.length === 0 ||
          (typeof rsp[0][fcn] !== 'undefined' && rsp[0][fcn] === null)
        )
          rsp = Object.assign([])
        manageResponse(rsp)
        setKeys()
      } catch (error) {
        handleError(error, iclstore)
        console.log(error)
      } finally {
        loading.value = false
      }
    }

    const manageResponse = (response) => {
      if (Array.isArray(response)) {
        itemsLocal.value = response
      } else {
        itemsLocal.value = response.items ? [...response.items] : []
        if (response.value || response.items) {
          emit('update-value', response.value)
        }
      }
    }

    const setKeys = () => {
      let max = 0
      let firstLongerRow = {}
      itemsLocal.value.forEach((item) => {
        let len = Object.keys(item).length
        if (len > max) {
          max = len
          firstLongerRow = item
        }
      })
      keys.value = Object.keys(firstLongerRow)
    }

    const checkIndexedFields = (id_resolver, items, id_columna) => {
      let intervals = id_resolver.match(/#\d+#/g)
      if (intervals !== null) {
        intervals.forEach((interval) => {
          let index = interval.slice(1, interval.length - 1)
          id_resolver = id_resolver.replace(
            interval,
            typeof items[index] !== 'undefined'
              ? items[index][id_columna]
              : 'undefined'
          )
        })
      }
      return id_resolver
    }

    const makeQuery = async (fcn, args) => {
      let localArgs = [
        JSON.stringify({
          ...args,
          id_usuario: iclstore.state.id_usuario,
          id_resolver: computedIdResolver.value
            ? computedIdResolver.value
            : iclRouter.currentRoute.path
        })
      ]
      try {
        const rsp = await iclAxios.post(externalDBEndpoint + fcn, localArgs)
        handleSuccess(rsp, createdFunctions, {
          iclRouter,
          iclstore,
          emit,
          handleSuccessResponse
        })
      } catch (error) {
        handleError(error, iclstore)
        console.log(error)
      }
    }

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

    const redirect = (path, query) => {
      let pathAux = resolve(path, query)
      if (pathAux.startsWith('https://') || pathAux.startsWith('http://')) {
        window.open(pathAux, '_blank')
      } else {
        iclRouter.push({ path: pathAux })
      }
    }

    const continueAction = (confirmation) => {
      if (confirmation) {
        delete actionToConfirm.value.confirmation
        handleItemClick(itemToConfirm.value)
      } else {
        emit('close-dialog')
      }
      actionToConfirm.value = Object.assign({})
      itemToConfirm.value = Object.assign({})
    }

    const isValidIdResolver = (item, comportamiento) => {
      let { id_columna, id_resolver, type } = comportamiento
      let idSelected = item[id_columna]
      let id_resolver_aux
      if (type !== fcn_executer) {
        id_resolver_aux = resolve(
          id_resolver,
          Array.isArray(idSelected) ? idSelected : [idSelected]
        )
        if (!id_resolver_aux || id_resolver_aux === '')
          console.error(
            `No se ha enviado un id_resolver para el comportamiento ${type} de IrIterator`
          )
        return id_resolver_aux && id_resolver_aux !== ''
      }
      return true
    }

    const createdFunctions = async () => {
      if (!(!!props.items_fcn && iclAxios)) {
        handleMissingItemsFcn()
        return
      }
      if (typeof props.filtros_fcn === 'undefined') {
        filtrosLocal.value = props.filtros
      } else {
        let datosFiltros = iclstore.getters.getDatosSitios({
          sitio: iclRouter.app._route.path,
          query: props.filtros_fcn,
          method: 'post',
          params: props.args_filtros_fcn
        })
        if (datosFiltros !== null) {
          // los busca en el store, si los encuentra los guarda en filtrosLocal hasta que se termine de hacer la consulta por los filtros
          datosFiltros = Object.assign({}, JSON.parse(datosFiltros.response))
          filtrosLocal.value = datosFiltros[0].props.campos
        }
        filtrosLocal.value = await getFiltros(
          props.filtros_fcn,
          props.args_filtros_fcn
        )
      }
      const params = { filtros: filtrosLocal.value, args: props.args_items_fcn }
      const datosSitio = iclstore.getters.getDatosSitios({
        sitio: iclRouter.app._route.path,
        query: props.items_fcn,
        method: 'get',
        params: [JSON.stringify(params)]
      })

      // los busca en el store, si los encuentra los guarda en itemsLocal hasta que se termine de hacer la consulta por los items
      if (
        datosSitio !== null &&
        props.cache_items &&
        datosSitio.response !== null
      )
        itemsLocal.value = [...datosSitio.response]
      await getItems(props.items_fcn, filtrosLocal.value, props.args_items_fcn)
    }

    // Manejo de items_fcn faltante
    const handleMissingItemsFcn = () => {
      const isFiltrosValid =
        typeof props.filtros_fcn !== 'undefined' || props.filtros.length > 0
      const isItemsFcnValid =
        typeof props.items_fcn !== 'undefined' ||
        props.items_fcn !== null ||
        props.items_fcn !== ''
      if (isFiltrosValid && !isItemsFcnValid) {
        logErrorComponent(
          iteratorComponentName,
          'Se quiso renderizar un formulario de filtros pero no se detalló alguna función en la propiedad "items_fcn".',
          iclRouter
        )
      }
      itemsLocal.value = props.items
      setKeys()
    }

    const checkExistence = (arr, element, id = undefined) => {
      // chequea que 'element' se encuentre dentro de 'arr'
      return (
        typeof arr.find((el) => {
          return typeof id === 'undefined'
            ? el === element
            : el[id] === element[id]
        }) !== 'undefined'
      )
    }

    const validateComportamiento = () => {
      if (Object.keys(props.comportamiento).length !== 0) {
        // valida que el objeto no esté vacío
        let tipo = props.comportamiento.type
        if (tipo === '') {
          logErrorComponent(
            iteratorComponentName,
            'La propiedad "type" del comportamiento se envió vacía.',
            iclRouter
          )
          return
        } else if (typeof tipo === 'undefined') {
          logErrorComponent(
            iteratorComponentName,
            'La propiedad "type" del comportamiento no se envió.',
            iclRouter
          )
          return
        }
        let active = !(
          typeof props.comportamiento.active === 'boolean' &&
          !props.comportamiento.active
        )
        if (active && checkExistence(availableTableTypes, tipo))
          comportamientoLocal.value = props.comportamiento
        else if (!active)
          logErrorComponent(
            iteratorComponentName,
            'La propiedad "type" del comportamiento no coincide con un tipo de comportamiento valido.',
            iclRouter
          )
      }
    }

    const handleItemClick = (item) => {
      let {
        type,
        fcn_click,
        id_columna,
        id_resolver,
        args_fcn_click,
        confirmation,
        titulo,
        dialogAttrs
      } = actionToConfirm.value.type
        ? actionToConfirm.value
        : comportamientoLocal.value
      if (confirmation === undefined || confirmation === '') {
        let idSelected = item[id_columna]
        let argsLocal, id_resolver_aux
        if (type === fcn_executer) {
          argsLocal = { ...args_fcn_click }
          if (idSelected !== undefined)
            argsLocal = { ...argsLocal, selected_rows: [idSelected] }
          makeQuery(fcn_click, argsLocal)
        } else if (type === modal) {
          id_resolver = checkIndexedFields(
            id_resolver,
            itemsLocal.value,
            id_columna
          )
          if (idSelected !== undefined)
            id_resolver_aux = resolve(
              id_resolver,
              Array.isArray(idSelected) ? idSelected : [idSelected]
            )
          // Si el id_resolver_aux es una url externa, se abre en nueva pestaña
          if (
            id_resolver_aux.startsWith('https://') ||
            id_resolver_aux.startsWith('http://')
          ) {
            window.open(id_resolver_aux, '_blank')
            emit('close-dialog')
          } else {
            emit('open-dialog')
            assignDialogData(id_resolver_aux, titulo, undefined, dialogAttrs)
          }
        } else if (type === navigator) {
          id_resolver = checkIndexedFields(
            id_resolver,
            itemsLocal.value,
            id_columna
          )
          redirect(
            id_resolver,
            Array.isArray(idSelected) ? idSelected : [idSelected]
          )
        }
      } else {
        actionToConfirm.value = Object.assign({}, comportamientoLocal.value)
        itemToConfirm.value = Object.assign({}, item)
        assignDialogData(undefined, undefined, confirmation)
        emit('open-dialog')
      }
    }

    onMounted(() => {
      structureError.value = propError(propsLocal, props, iteratorComponentName)
      if (structureError.value) {
        iclstore.commit('updateSnack', {
          text: 'Ha ocurrido un error al cargar el componente.',
          active: true,
          color: 'warning'
        })
      }
    })

    const itemsPerPage = ref(-1)
    assignCamelToSnake(mergedProps, props)
    itemsPerPage.value = props.rowsPerPage
    validateComportamiento()
    createdFunctions()

    return () => {
      if (structureError.value) return
      return h(
        'div', // Contenedor como nodo raíz
        [
          slots.default({
            items: itemsLocal.value,
            itemsPerPage: itemsPerPage.value,
            keys: keys.value,
            id_resolver_dlg: dialogData.value.id_resolver_dlg,
            tituloDialog: dialogData.value.titulo,
            dialogAttrs: dialogData.value,
            message: dialogData.value.message,
            filtros: filtrosLocal.value,
            submitForm: {
              click: () => {
                getItems(
                  props.items_fcn,
                  filtrosLocal.value,
                  props.args_items_fcn
                )
              }
            },
            comportamiento: comportamientoLocal.value,
            id_resolver: computedIdResolver.value,
            handleItemClick: handleItemClick,
            type: props.type,
            fullWidth: props.fullWidth,
            columns: {
              xs: props.columnsXs,
              sm: props.columnsSm,
              md: props.columnsMd,
              lg: props.columnsLg,
              xl: props.columnsXl,
              default: props.columns
            },
            isValidIdResolver: isValidIdResolver,
            continueAction: continueAction,
            adaptPerPage: props.adaptPerPage,
            hideDefaultFooter: props.hideDefaultFooter,
            hideDefaultHeader: props.hideDefaultHeader,
            colorHeader: props.colorHeader,
            refreshParent: createdFunctions,
            fullHeight: props.fullHeight,
            isLoading: loading.value,
            linked: props.linked
          })
        ]
      )
    }
  }
}
</script>
Last Updated: 4/5/2024, 4:52:19 PM