# Data Table 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 = {
  search: {
    type: undefined //Undefined para evitar error al setear como array [String, Object] ya que rompe la función propError
  },
  row_classes: {
    type: Object
  },
  items: {
    type: Array,
    default: () => []
  },
  comportamientos: {
    type: Array,
    default: () => []
  },
  cache_items: {
    type: Boolean,
    default: true
  },
  items_fcn: {
    type: String,
    required: false
  },
  args_items_fcn: {
    type: Array,
    default: () => []
  },
  filtros: {
    type: Array,
    default: () => []
  },
  filtros_fcn: {
    type: String,
    required: false
  },
  args_filtros_fcn: {
    type: Array,
    default: () => []
  },
  headers: {
    type: Array,
    required: false
  },
  rowsPerPage: {
    type: Number,
    default: 5
  },
  groupColor: {
    type: String,
    required: false,
    default: '#FF0000'
  },
  downloadable: {
    type: Boolean,
    default: false
  },
  show_select: {
    type: Boolean,
    default: false
  },
  value: {
    type: Array,
    default: () => []
  },
  headersCSV: {
    // tiene un array con los value de los headers para poner en el CSV
    type: Array
  },
  noExpandedData: {
    type: String,
    default: ''
  },
  itemKey: {
    type: String
  },
  expandedDataProp: {
    type: String,
    default: undefined
  },
  name_csv: {
    type: String,
    default: `table_${new Date().getTime()}`
  },
  id_resolver: {
    type: String
  },
  'column-filters': {
    type: Object,
    required: false,
    default: () => ({
      tooltip: 'Column filter',
      label: '',
      'is-filter-visible-on-load': false
    })
  },
  columnFiltersValues: {
    type: Object,
    required: false,
    default: () => ({})
  },
  refreshConfig: {
    type: Object,
    default: () => ({
      repeatTime: 0,
      timeout: 0,
      refreshingToolTip: 'Refreshing table'
    })
  },
  'must-sort': {
    type: Boolean,
    default: false
  },
  'multi-sort': {
    type: Boolean,
    default: false
  }
}
import Vue from 'vue'
import {
  ref,
  onMounted,
  computed,
  watch,
  onBeforeUnmount,
  h,
  getCurrentInstance,
  nextTick
} from 'vue'
import {
  normalizeProps,
  assignCamelToSnake
} from '../../helpers/propsGenerator'
const mergedProps = normalizeProps(propsLocal)
import resolveMixin from '../../mixins/resolveIdResolver'
import stringToFunction from '../../mixins/stringToFunction'
import {
  fcn_executer,
  iconComponent,
  imageComponent,
  tableComponentName,
  navigator,
  modal,
  fcn_executer_row,
  navigator_row,
  modal_row,
  image_row,
  avatarComponent,
  icon_row,
  availableTableTypes,
  availableRowTypes,
  availableRowGroupsTypes,
  availableFunctionlessRowTypes,
  externalDBEndpoint,
  getCamposEndpoint,
  fcnExecuterEndpoint
} from '../../constants'
import fcnResponseHandler from '../../mixins/fcnResponseHandler'
export default {
  name: tableComponentName + 'Bone',
  mixins: [fcnResponseHandler, resolveMixin, stringToFunction],
  props: mergedProps,
  setup(props, { slots, emit, expose }) {
    const vueInstance = getCurrentInstance()
    const vuetify = vueInstance.proxy.$vuetify
    const { handleError, handleSuccess, handleSuccessResponse } =
      fcnResponseHandler.methods
    const { resolve } = resolveMixin.methods
    const { buildBooleanFcn } = stringToFunction.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 headersLocal = ref([])
    const rowsClassesFunction = ref(false)
    const itemsLocal = ref([])
    const rowsPerPageLocal = ref(0)
    const loading = ref(false)
    const filtrosLocal = ref([])
    const comportamientosTable = ref([])
    const comportamientosRow = ref([])
    const comportamientosRowGroups = ref([])
    const isThereImageTypes = ref(false)
    const showSelectLocal = ref(false)
    const dialogData = ref({
      id_resolver_dlg: '',
      titulo: '',
      show: false,
      dialog_args: {}
    })
    const actionToConfirm = ref({})
    const itemToConfirm = ref({})
    const structureError = ref(false)
    const intervalId = ref(null)
    const repeatTime = ref(null)
    const headerOver = ref(-1)
    const sortSelected = ref([])
    const currentPage = ref(1)
    const isLoadingComportamiento = ref(false)

    watch(
      () => props.items,
      (newItems) => {
        setHeadersItems(newItems)
      }
    )

    watch(
      () => props.headers,
      (newHeaders) => {
        headersLocal.value = Object.assign([], newHeaders)
        setHeadersItems(itemsLocal.value)
      }
    )

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

    const manageGroupalHeader = computed(() => {
      return comportamientosRowGroups.value.length > 0
        ? [
            ...headersLocal.value,
            { name: 'comportamientos-grouped', sortable: false }
          ]
        : headersLocal.value
    })

    const sortDesc = computed(() => {
      return sortSelected.value.map((selected) => {
        return selected.sortDesc
      })
    })

    const selectedHeadersValues = computed(() => {
      return sortSelected.value.map((selected) => {
        return selected.value
      })
    })

    const comportamientosTableOutside = computed(() => {
      // Se hace en bone aunque sea parte de FE para recorrer una única vez el arreglo, en el FE es necesario recorrerlo dos veces
      let comportamientosTableOutside = []
      for (let i = 0; i < comportamientosTable.value.length; i++) {
        let compAux = comportamientosTable.value[i]
        if (compAux.outside && !vuetify.breakpoint.smAndDown)
          comportamientosTableOutside.push(compAux)
      }
      return comportamientosTableOutside
    })

    const comportamientosTableInside = computed(() => {
      // Se hace en bone aunque sea parte de FE para recorrer una única vez el arreglo, en el FE es necesario recorrerlo dos veces
      let comportamientosTableInside = []
      for (let i = 0; i < comportamientosTable.value.length; i++) {
        let compAux = comportamientosTable.value[i]
        if (compAux.inside || !compAux.outside || vuetify.breakpoint.smAndDown)
          comportamientosTableInside.push(compAux)
      }
      return comportamientosTableInside
    })

    const filteredItems = computed(() => {
      //Filtra los items según los filtros de columnas.
      let filteredItems = itemsLocal.value
      Object.keys(props.columnFiltersValues).forEach((key) => {
        let findValue = props.columnFiltersValues[key]
        if (findValue && findValue !== '') {
          filteredItems = filteredItems.filter((item) => {
            let value = item[key]
            if (Array.isArray(value) || typeof value === 'object')
              // Si el componente de filtro tiene como valor por ejemplo 'auto' y el valor de la celda es un objeto, se fija si los valores de las propiedades contienen ese valor
              value = JSON.stringify(Object.values(value))
            else value = value.toString().toLowerCase()
            // range type validation
            const isFindValueArray = Array.isArray(findValue)
            const hasTwoValues = findValue.length === 2
            const hasNumbers =
              typeof findValue[0] === 'number' &&
              typeof findValue[1] === 'number'
            // range type validation
            if (isFindValueArray && hasTwoValues && hasNumbers) {
              // case range
              return value >= findValue[0] && value <= findValue[1]
            } else if (isFindValueArray) {
              let findValueLocal = findValue.find((v) => {
                const valueLower = v.toString().toLowerCase()
                return value.includes(valueLower)
              })
              return typeof findValueLocal !== 'undefined'
            } else {
              let findValueLocal = findValue.toString().toLowerCase()
              return value.includes(findValueLocal)
            }
          })
        }
      })
      return filteredItems
    })

    const refreshingToolTip = computed(() => {
      return props.refreshConfig && props.refreshConfig.refreshingToolTip
        ? props.refreshConfig.refreshingToolTip
        : ''
    })
    /**
     * Toggles the selection of an item in the given array and emits the updated array.
     *
     * @param {boolean} value - The selection status. If true, the item will be selected; if false, it will be deselected.
     * @param {function} select - The function to invoke when selecting an item.
     * @param {object} item - The item to toggle selection for.
     */
    const toggleItemSelection = (value, select, item) => {
      let foundedItemIndex = -1
      props.value.forEach((row, index) => {
        const hasId =
          typeof row.id !== 'undefined' && typeof item.id !== 'undefined'
        if (
          (hasId && row.id === item.id) ||
          (!hasId && JSON.stringify(row) === JSON.stringify(item))
        ) {
          foundedItemIndex = index
        }
      })
      const valueAux = [...props.value]
      if (value) {
        select(item)
        if (foundedItemIndex === -1) {
          valueAux.push(item)
          emit('update:value', valueAux)
        }
      } else if (foundedItemIndex !== -1) {
        valueAux.splice(foundedItemIndex, 1)
        emit('update:value', valueAux)
      }
    }
    /**
     * Gets the checkbox value for the specified item.
     *
     * @param {object} item - The item for which to get the checkbox value.
     * @returns {boolean} - The checkbox value. True if the item is selected, false otherwise.
     */
    const getCheckboxValue = (item) => {
      const itemFounded = props.value.find((row) => {
        const hasId =
          typeof row.id !== 'undefined' && typeof item.id !== 'undefined'
        return (
          (hasId && row.id === item.id) ||
          (!hasId && JSON.stringify(row) === JSON.stringify(item))
        )
      })
      return typeof itemFounded !== 'undefined'
    }
    /**
     * Selects or deselects all items based on the specified isSelected flag.
     *
     * @param {boolean} isSelected - The flag indicating whether to select (true) or deselect (false) all items.
     * @param {Array} items - The array of items to be selected or deselected.
     */
    const selectAllItems = (isSelected, items) => {
      if (isSelected) {
        emit('update:value', [...items]) // Pueden haber items invisibles???? No
      } else {
        emit('update:value', [])
      }
    }
    /**
     * Generates an array representing the pages based on the specified number of rows per page
     * and the total number of items.
     *
     * @param {number} rowsPerPage - The number of rows to display per page.
     * @param {Array} items - The array of items to be paginated.
     * @returns {Array} - An array representing the pages for pagination navigation.
     */
    const getPages = (rowsPerPage, items) => {
      const totalPages = Math.ceil(items.length / rowsPerPage)
      if (totalPages <= 6) {
        return Array.from({ length: totalPages }, (_, index) => index + 1)
      }
      const range = (start, end) =>
        Array.from({ length: end - start + 1 }, (_, index) => start + index)
      const getDisplayedPages = () => {
        if (currentPage.value <= 4) {
          return [...range(1, 5), '...', totalPages]
        } else if (currentPage.value >= totalPages - 3) {
          return [1, '...', ...range(totalPages - 4, totalPages)]
        } else {
          return [
            1,
            '...',
            ...range(currentPage.value - 2, currentPage.value + 2),
            '...',
            totalPages
          ]
        }
      }
      return getDisplayedPages()
    }
    /**
     * Sets a new page number if the provided value is a valid number.
     *
     * @param {number} page - The new page number to be set.
     */
    const setNewPage = (page) => {
      if (!isNaN(page)) {
        currentPage.value = page
      }
    }
    /**
     * Finds the index of the selected item in the sortSelected array based on the specified header index.
     *
     * @param {number} headerIndex - The index of the header for which to find the selected item in sortSelected.
     * @returns {number} - The index of the selected item in the sortSelected array. Returns -1 if not found.
     */
    const indexSelected = (headerIndex) => {
      return sortSelected.value.findIndex(
        (selected) => selected.index === headerIndex
      )
    }
    /**
     * Toggles the sorting selection for a header based on the specified header index and header information.
     *
     * @param {number} headerIndex - The index of the header for which to toggle sorting selection.
     * @param {object} header - The header information containing sorting details.
     */
    const toggleSelectHeaderSort = (headerIndex, header) => {
      const index = indexSelected(headerIndex)
      if (index !== -1) {
        if (!sortSelected.value[index].sortDesc) {
          sortSelected.value[index].sortDesc = true
        } else if (props.mustSort) {
          sortSelected.value[index].sortDesc =
            !sortSelected.value[index].sortDesc
        } else {
          sortSelected.value.splice(index, 1)
        }
      } else if (props.multiSort) {
        sortSelected.value.push({
          sortDesc: false,
          index: headerIndex,
          value: header.value
        })
      } else {
        sortSelected.value = [
          { sortDesc: false, index: headerIndex, value: header.value }
        ]
      }
    }
    /**
     * Handles mouseover and mouseleave events on a header element.
     *
     * @param {Event} e - The mouse event object.
     * @param {number} headerIndex - The index of the header being interacted with.
     */
    const mouseOver = (e, headerIndex) => {
      if (e.type === 'mouseenter') {
        headerOver.value = headerIndex
      } else {
        headerOver.value = -1
      }
    }

    const setHeadersItems = (itemsAux) => {
      setHeaders(itemsAux)
      setItems(itemsAux)
      nextTick(() => {
        checkShowSelectConsistency()
      })
    }

    const checkShowSelectConsistency = () => {
      if (props.show_select) {
        let validIdItems = []
        let invalidItemKey = true
        itemsLocal.value.forEach((item) => {
          if (item.id) {
            validIdItems.push(item)
          }
          if (typeof item[props.itemKey] !== 'undefined' && invalidItemKey) {
            invalidItemKey = false
          }
        })
        let validItemsWithId = itemsLocal.value.length === validIdItems.length
        if (comportamientosTable.value.length === 0) {
          logErrorComponent(
            tableComponentName,
            'Se quiso mostrar la selección de filas pero no se enviaron comportamientos de tabla, o se enviaron desactivados.',
            iclRouter
          )
        } else if (
          typeof comportamientosTable.value.find(
            (comp) => !!comp.id_columna
          ) === 'undefined'
        ) {
          logErrorComponent(
            tableComponentName,
            'Se quiso mostrar la selección de filas pero no se envió la propiedad "id_columna" en ningún comportamiento de tabla.',
            iclRouter
          )
        } else if (
          (!validItemsWithId &&
            (typeof props.itemKey === 'undefined' || invalidItemKey)) ||
          (validItemsWithId &&
            typeof props.itemKey !== 'undefined' &&
            invalidItemKey)
        ) {
          logErrorComponent(
            tableComponentName,
            'Se quiso mostrar la selección de filas pero no se encontraron identificadores válidos para los items.',
            iclRouter
          )
        } else {
          showSelectLocal.value = props.show_select
        }
      }
    }
    const manageFunctionlessRowTypes = (comp) => {
      // controla los tipos de comportamientos pertenecientes a "availableFunctionlessRowTypes" (no tienen funcionalidad)
      comp = { ...comp, disabled: true } // disabled determina si cumplirá alguna función al ser clickeado o no
      if (comp.type === icon_row) {
        return { ...comp, component: iconComponent }
      }
      return comp
    }
    const checkExistence = (arr, element, id = undefined) => {
      // chequea que 'element' se encuentre dentro de 'arr'
      return arr.some((el) => {
        return id === undefined ? el === element : el[id] === element[id]
      })
    }
    const makeQuery = async (fcn, args) => {
      // hace la petición desde un slot de la tabla
      const localArgs = [
        JSON.stringify({
          ...args,
          id_usuario: iclstore.state.id_usuario,
          id_resolver: props.id_resolver
            ? props.id_resolver
            : iclRouter.currentRoute.path
        })
      ]
      try {
        const rsp = await iclAxios.post(fcnExecuterEndpoint + fcn, localArgs)
        handleSuccess(rsp, createdFunctions, {
          iclRouter,
          iclstore,
          emit, // En caso de composition api pasar emit
          handleSuccessResponse
        })
      } catch (error) {
        handleError(error, iclstore)
        console.log(error)
      }
    }
    const setItems = (itemsAux) => {
      if (itemsAux.length > 0) {
        //si hay items los asigna en itemsLocal y crea los headers y los asigna en headersLocal
        if (headersLocal.value.length > 0) {
          itemsLocal.value = []
          nextTick(() => {
            itemsLocal.value = itemsAux
          })
        }
      } else {
        itemsLocal.value = []
      }
    }
    const assignComportamientos = () => {
      headersLocal.value.map((header) => {
        const comp = comportamientosRow.value.find(
          (slot) => slot.columna === header.value
        )
        if (comp) {
          header = Object.assign(header, { ...comp, modified: true })
        } else {
          header = Object.assign(header, { modified: false })
        }
      })
    }
    const setHeaders = (itemsAux) => {
      if (itemsAux.length > 0) {
        //si hay items los asigna en itemsLocal y crea los headers y los asigna en headersLocal
        if (
          typeof props.headers === 'undefined' ||
          props.headers.length === 0
        ) {
          // si no recibe headers se arman con los keys del item que más propiedades tenga
          //const maxLength = Math.max(...itemsAux.map(item => Object.keys(item).length))
          //const firstLongerRow = itemsAux.find(item => Object.keys(item).length === maxLength)
          let max = 0
          let firstLongerRow
          itemsAux.forEach((item) => {
            // se busca el item con más propiedades haciendo un solo for
            let len = Object.keys(item).length // (mejor que método anteriormente comentado, en el cual se hace un map y un find)
            if (len > max) {
              // (aunque más rudimentario)
              max = len
              firstLongerRow = item
            }
          })
          headersLocal.value = Object.keys(firstLongerRow).map((r) => ({
            text: r.toUpperCase().replace(/_/g, ' '),
            value: r
          }))
        } else {
          let localHeaders = JSON.parse(JSON.stringify(props.headers)) // this.headersLocal = structuredClone(this.headers)
          headersLocal.value = filterHeaders(localHeaders, itemsAux)
        }
        if (headersLocal.value.length > 0) {
          assignComportamientos()
        } else {
          logErrorComponent(tableComponentName, 'No se encuentran headers.', iclRouter)
        }
      }
    }
    /**
     * Filtra un array de objetos de headers basado en las propiedades de items.
     *
     * @param {Array<{ value: string }>} headers - Un array de objetos de headers, cada uno con una propiedad 'value'.
     * @param {Array<Object>} itemsAux - Un array de items que se utilizarán para filtrar los headers.
     * @returns {Array<{ value: string }>} - Un nuevo array de objetos de headers que incluye solo aquellos cuyas propiedades 'value' coinciden con las propiedades de los items.
     */
    const filterHeaders = (headers, itemsAux) => {
      const allItemsProperties = []
      itemsAux.forEach((item) => {
        const localItemKeys = Object.keys(item)
        localItemKeys.forEach((key) => {
          if (!allItemsProperties.includes(key)) {
            allItemsProperties.push(key)
          }
        })
      })
      return headers.filter(({ value }) => allItemsProperties.includes(value))
    }
    const getItems = (filters, args) => {
      loading.value = true
      const params = {
        filtros: filters,
        args,
        id_usuario: iclstore.state.id_usuario,
        id_resolver: props.id_resolver
          ? props.id_resolver
          : iclRouter.currentRoute.path
      }
      return iclAxios
        .post(externalDBEndpoint + props.items_fcn, [JSON.stringify(params)])
        .then((rsp) => {
          handleSuccess(rsp, () => {}, {
            iclRouter,
            iclstore,
            emit, // En caso de composition api pasar emit
            handleSuccessResponse
          })
          detectResponseStructErrors(
            rsp,
            props.items_fcn,
            [],
            tableComponentName,
            '{ <propName>: Any }',
            'https://icl.iridiumrobotics.com.ar/components/ir-data-table.html#uso'
          )
          if (
            rsp === null ||
            rsp.length === 0 ||
            (typeof rsp[0][props.items_fcn] !== 'undefined' &&
              rsp[0][props.items_fcn] === null)
          )
            rsp = Object.assign([])
          setHeadersItems(rsp)
        })
        .catch((error) => {
          emit('upload-refreshing-time', null)
          clearInterval(intervalId.value)
          handleError(error, iclstore)
          console.log(error)
        })
        .finally(() => {
          loading.value = false
        })
    }
    const handleTableClick = (action) => {
      // se encarga de la ejecución de los comportamientos de tabla
      let {
        type,
        fcn_click,
        id_columna,
        id_resolver,
        args_fcn_click,
        confirmation,
        titulo,
        position,
        dialogAttrs
      } = action
      if (typeof confirmation === 'undefined' || confirmation === '') {
        let found = []
        let argsLocal = {}
        let isColumnaValid =
          typeof id_columna !== 'undefined' && id_columna !== ''
        if (isColumnaValid && props.value.length > 0) {
          for (let i = 0; i < props.value.length; i++) {
            let sel = props.value[i]
            if (sel[id_columna] !== null && sel[id_columna] !== undefined)
              found.push(sel[id_columna])
            // se pushea a found en caso de que sólo se necesiten estos datos encontrados y no "args_dialog"
            else {
              logErrorComponent(
                tableComponentName,
                'La propiedad "id_columna" con valor "' +
                  id_columna +
                  '" no coincide con algún header de la tabla.',
                iclRouter
              )
              return
            }
          }
        }
        if (type === fcn_executer) {
          argsLocal = Object.assign({}, args_fcn_click)
          if (typeof found !== 'undefined' && found.length > 0)
            argsLocal = { ...argsLocal, selected_rows: found } //Las filas seleccionadas se agregan a argsLocal.selected_rows
          makeQuery(fcn_click, argsLocal)
        } else if (type === modal) {
          id_resolver = checkIndexedFields(
            id_resolver,
            itemsLocal.value,
            id_columna
          )
          let id_resolver_aux = resolve(id_resolver, found)
          assignDialogData({ id_resolver: id_resolver_aux, titulo, position, dialogAttrs })
        } else if (type === navigator) {
          id_resolver = checkIndexedFields(
            id_resolver,
            itemsLocal.value,
            id_columna
          )
          redirect(id_resolver, found)
        }
      } else {
        actionToConfirm.value = Object.assign({}, action)
        assignDialogData({ message: confirmation })
      }
    }
    /*
     * id_resolver (String): might contain #n# values to replace
     * items (Array): items from table
     * id_columna (String): column to search for the values to replace
     *
     * Checks if id_resolver has #n# subtrings to replace with values from a certain column
     * Returns: replaced id_resolver.
     */
    const checkIndexedFields = (idResolver, items, idColumna) => {
      let intervals = idResolver.match(/#\d+#/g)
      if (intervals !== null) {
        intervals.forEach((interval) => {
          let index = interval.slice(1, interval.length - 1)
          idResolver = idResolver.replace(
            interval,
            typeof items[index] !== 'undefined'
              ? items[index][idColumna]
              : 'undefined'
          )
        })
      }
      return idResolver
    }
    const handleRowClick = (action, item) => {
      // se encarga de la ejecución de los comportamientos de celda
      let {
        type,
        fcn_click,
        id_columna,
        id_resolver,
        args_fcn_click,
        confirmation,
        titulo,
        position,
        dialogAttrs
      } = action // action es un objeto de tipo comportamiento

      if (typeof confirmation === 'undefined' || confirmation === '') {
        //si no existe confirmation en el comportamiento, se realiza la acción del mismo
        let idSelected = item[id_columna]
        let argsLocal, idResolverAux

        if (type === fcn_executer_row) {
          argsLocal = { ...args_fcn_click }
          if (typeof idSelected !== 'undefined') {
            argsLocal = {
              ...argsLocal,
              selected_rows: Array.isArray(idSelected)
                ? idSelected
                : [idSelected]
            } //Las filas seleccionadas se agregan a argsLocal.selected_rows
          }
          isLoadingComportamiento.value = true
          makeQuery(fcn_click, argsLocal).finally(() => {
            isLoadingComportamiento.value = false
          })
        } else if (type === modal_row) {
          id_resolver = checkIndexedFields(
            id_resolver,
            itemsLocal.value,
            id_columna
          )
          if (typeof idSelected !== 'undefined') {
            idResolverAux = resolve(
              id_resolver,
              Array.isArray(idSelected) ? idSelected : [idSelected]
            )
          }
          assignDialogData({ id_resolver: idResolverAux, titulo, position, dialogAttrs })
        } else if (type === navigator_row) {
          id_resolver = checkIndexedFields(
            id_resolver,
            itemsLocal.value,
            id_columna
          )
          redirect(
            id_resolver,
            Array.isArray(idSelected) ? idSelected : [idSelected]
          )
        }
      } else {
        //Si existe confirmation en comportamiento, se asigna a la variable actionToConfirm y el item a itemToConfirm, se asigna a dialog un mensaje de confirmación
        actionToConfirm.value = { ...action }
        itemToConfirm.value = { ...item }
        assignDialogData({ message: confirmation }) //Si se acepta la confirmación irDataTable ejecuta  continueAction
      }
    }
    const assignDialogData = (dialogDataLocal) => {
      dialogData.value = Object.assign({})
      // asigna los valores correspondientes al dialog, puede abrirlo o cerrarlo (cerrarlo desde este componente todavía no es utilizado)
      const { id_resolver, titulo, message, position, dialogAttrs } = dialogDataLocal
      dialogData.value.id_resolver_dlg = id_resolver
      dialogData.value.titulo = titulo
      dialogData.value.message = message
      dialogData.value.position = position
      dialogData.value = { ... dialogData.value, ...dialogAttrs }
    }
    const continueAction = (confirmation) => {
      //Confirmation = true si se acepta confirmación, false si cancela (evento emitido por IrDialog)
      if (confirmation) {
        //al aceptar se elimina confirmation ya que con este objeto se llamara nuevamente a los handle...Click, evita bucle de confirmación
        delete actionToConfirm.value.confirmation
        if (checkExistence(availableTableTypes, actionToConfirm.value.type)) {
          handleTableClick(actionToConfirm.value)
        } else {
          handleRowClick(actionToConfirm.value, itemToConfirm.value)
        }
      } else {
        //si se cancela la confirmación se limpian las variables de ToConfirm y se cierra el dialog
        actionToConfirm.value = {}
        itemToConfirm.value = {}
      }
      emit('cerrar-dialog')
    }
    const redirect = (path, query) => {
      // redirecciona
      let pathAux = resolve(path, query)
      if (pathAux.startsWith('https://') || pathAux.startsWith('http://'))
        window.open(pathAux, '_blank')
      else iclRouter.push({ path: pathAux })
    }
    const manageFunctionalRowTypes = (comp) => {
      // controla los tipos de comportamientos pertenecientes a "availableRowTypes" (tienen funcionalidad)
      comp = {
        ...comp,
        disabled: typeof comp.disabled !== 'undefined' ? comp.disabled : false
      }
      if (comp.type === image_row) {
        isThereImageTypes.value = true
        return {
          ...comp,
          component: imageComponent,
          attrs: typeof comp.attrs !== 'undefined' ? comp.attrs : { size: 45 }
        }
      }
      if (comp.component === avatarComponent) {
        return {
          ...comp,
          attrs: typeof comp.attrs !== 'undefined' ? comp.attrs : { size: 45 }
        }
      }
      return comp
    }
    const divideComportamientos = () => {
      // divide los comportamientos entre "availableTableTypes" y "comportamientosRow", y loggea errores
      props.comportamientos.map((comportamiento) => {
        let tipo = comportamiento.type
        if (tipo === '') {
          logErrorComponent(
            tableComponentName,
            'La propiedad "type" del comportamiento se envió vacía.',
            iclRouter
          )
        } else if (typeof tipo === 'undefined') {
          logErrorComponent(
            tableComponentName,
            'La propiedad "type" del comportamiento no se envió.',
            iclRouter
          )
        } else if (
          typeof comportamiento.active === 'boolean' &&
          !comportamiento.active
        ) {
          return
        } else if (checkExistence(availableTableTypes, tipo)) {
          comportamientosTable.value.push(comportamiento)
        } else if (
          !comportamiento['is-grouped'] &&
          checkExistence(availableRowTypes, tipo)
        ) {
          comportamientosRow.value.push(
            manageFunctionalRowTypes(comportamiento)
          )
        } else if (checkExistence(availableFunctionlessRowTypes, tipo)) {
          comportamientosRow.value.push(
            manageFunctionlessRowTypes(comportamiento)
          )
        } else if (
          comportamiento['is-grouped'] &&
          checkExistence(availableRowGroupsTypes, tipo)
        ) {
          const isCompVisible =
            typeof comportamiento.visible === 'undefined' ||
            comportamiento.visible !== false
          if (isCompVisible) {
            comportamientosRowGroups.value.push(comportamiento)
          }
        } else {
          logErrorComponent(
            tableComponentName,
            'La propiedad "type" con valor: "' +
              tipo +
              '" del comportamiento no coincide con un tipo de comportamiento válido.',
            iclRouter
          )
        }
      })
    }
    const getFiltros = async (fcn, args) => {
      let argsAux = {
        id_usuario: iclstore.state.id_usuario,
        id_resolver: props.id_resolver
          ? props.id_resolver
          : iclRouter.currentRoute.path,
        ...args
      }
      return iclAxios
        .post(getCamposEndpoint + fcn, [JSON.stringify(argsAux)])
        .then((rsp) => {
          return rsp
        })
        .catch((error) => {
          console.log(error)
          logErrorComponent(
            tableComponentName,
            'Ocurrió un error en la consulta de los campos.',
            iclRouter
          )
          return props.filtros
        })
    }
    const createdFunctions = async () => {
      // controla que se haya recibido una función para obtener los items
      if (!!props.items_fcn && iclAxios) {
        if (typeof props.filtros_fcn !== 'undefined') {
          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
          )
        } else {
          filtrosLocal.value = props.filtros
        }
        let params = { filtros: filtrosLocal.value }
        params = { ...params, args: props.args_items_fcn }
        let datosSitio = iclstore.getters.getDatosSitios({
          sitio: iclRouter.app._route.path,
          query: props.items_fcn,
          method: 'get',
          params: [JSON.stringify(params)]
        })
        if (
          datosSitio !== null &&
          props.cache_items &&
          datosSitio.response !== null
        ) {
          // los busca en el store, si los encuentra los guarda en itemsLocal hasta que se termine de hacer la consulta por los items
          setHeadersItems([...datosSitio.response])
        }
        await getItems(filtrosLocal.value, props.args_items_fcn)
        manageGetItemsRepeater()
      } else {
        // si no se envía items_fcn se asigna a itemsLocal la propiedad items
        let isFiltrosValid =
          typeof props.filtros_fcn !== 'undefined' || props.filtros.length > 0
        let isItemsFcnValid =
          typeof props.items_fcn !== 'undefined' ||
          props.items_fcn !== null ||
          props.items_fcn !== ''
        if (isFiltrosValid && !isItemsFcnValid) {
          logErrorComponent(
            tableComponentName,
            'Se quiso renderizar un formulario de filtros pero no se detalló alguna función en la propiedad "items_fcn".',
            iclRouter
          )
        }
        itemsLocal.value = props.items
        setHeadersItems(itemsLocal.value)
      }
    }

    const manageGetItemsRepeater = async () => {
      const { repeatTime: newRepeatTime, timeout } = props.refreshConfig
      let timeRemaining = timeout
      let isInfiniteLoop = timeout === -1
      let repeatTimeLocalAux = Math.floor(newRepeatTime / 1000)
      let countingDown = repeatTimeLocalAux
      while (
        typeof newRepeatTime !== 'undefined' &&
        typeof timeout !== 'undefined' &&
        timeRemaining !== 0 &&
        (timeRemaining >= newRepeatTime || isInfiniteLoop)
      ) {
        repeatTime.value = newRepeatTime
        intervalId.value = setInterval(() => {
          if (countingDown === 0) {
            countingDown = repeatTimeLocalAux
          }
          countingDown = countingDown - 1
          emit('upload-refreshing-time', countingDown)
        }, 1000)
        await new Promise((r) => setTimeout(r, newRepeatTime))
        if (!isInfiniteLoop) {
          timeRemaining = timeRemaining - newRepeatTime
        }
        if (timeRemaining < newRepeatTime && !isInfiniteLoop) {
          emit('upload-refreshing-time', null)
        }
        clearInterval(intervalId.value)
        await getItems(filtrosLocal.value, props.args_items_fcn)
      }
    }
    /**
     * Valida que el valor de la celda no coincida con el valor de la propiedad falsy-value o que no sea un falsy value.
     * @param {Object} item - Item de la tabla.
     * @returns {Array} - Arreglo de comportamientos válidos.
     */
    const checkComportamientosGrouped = (item) => {
      let comps = []
      comportamientosRowGroups.value.forEach((comp) => {
        const hasFalsyValue = typeof comp['falsy-value'] !== 'undefined'
        if (typeof comp.attached === 'undefined') {
          comps.push(comp)
        } else if (
          hasFalsyValue &&
          typeof item[comp.attached] !== 'undefined' &&
          item[comp.attached] !== comp['falsy-value']
        ) {
          comps.push(comp)
        } else if (!hasFalsyValue && item[comp.attached]) {
          comps.push(comp)
        }
      })
      return comps
    }
    /*
     * assignCSVItems
     * Genera los items que se escribirán en el csv.
     * @returns {void}
     */
    const assignCSVItems = () => {
      // genera la data para el CSV
      let itemsForCsv = [] // se vuelve a generar por si se actualiza la data de la tabla
      let headersCSVLocal
      if (typeof props.headersCSV === 'undefined') {
        headersCSVLocal = Object.assign(
          [],
          headersLocal.value.map((header) => header.value)
        )
      } else {
        headersCSVLocal = Object.assign([], props.headersCSV)
      }
      for (let i = 0; i < filteredItems.value.length; i++) {
        // Se hace con for para evitar errores de referencia,
        const item = { ...filteredItems.value[i] } // antes se hacía con reduce y fallaba
        for (const [key] of Object.entries(item)) {
          if (!checkExistence(headersCSVLocal, key)) {
            delete item[key]
          }
        }
        itemsForCsv.push({ ...item })
      }
      return itemsForCsv
    }
    onBeforeUnmount(() => {
      clearInterval(intervalId.value)
    })
    assignCamelToSnake(mergedProps, props)
    rowsPerPageLocal.value = props.rowsPerPage
    divideComportamientos()
    createdFunctions()
    onMounted(() => {
      structureError.value = propError(propsLocal, props, tableComponentName)
      if (structureError.value) {
        iclstore.commit('updateSnack', {
          text: 'Ha ocurrido un error al cargar la tabla',
          active: true,
          color: 'warning'
        })
      }
      try {
        if (props.row_classes) {
          rowsClassesFunction.value = eval(buildBooleanFcn(props.row_classes))
        }
      } catch (e) {
        logErrorComponent(
          tableComponentName,
          'Ha ocurrido un error evaluando la funcion definida row_classes -> conditional, revise la estructura de la propiedad. ' +
            e,
          iclRouter
        )
        console.error('Error en función de estilos de filas - ', e)
      }
      emit(
        'set-show-column-filters',
        props.columnFilters ? props.columnFilters['is-visible-on-load'] : false
      )
    })
    expose({
      filteredItems,
      assignCSVItems,
      getItems,
      itemsLocal,
      headersLocal,
      dialogData,
      handleRowClick,
      comportamientosRowGroups,
      filterHeaders
    })
    return () => {
      if (structureError.value) {
        return
      }
      return h(
        'div', // Contenedor como nodo raíz
        [
          slots.default({
            headerOver: headerOver.value,
            sortSelected: sortSelected.value,
            sortDesc: sortDesc.value,
            selectedHeadersValues: selectedHeadersValues.value,
            indexSelected: indexSelected,
            toggleSelectHeaderSort: toggleSelectHeaderSort,
            mouseOver: mouseOver,
            setNewPage: setNewPage,
            multiSort: props.multiSort,
            currentPage: currentPage.value,
            getPages: getPages,
            selectAllItems: selectAllItems,
            getCheckboxValue: getCheckboxValue,
            toggleItemSelection: toggleItemSelection,
            headers: headersLocal.value,
            items: filteredItems.value,
            id_resolver_dlg: dialogData.value.id_resolver_dlg,
            dialogAttrs: dialogData.value,
            loading: loading.value,
            showSelect: showSelectLocal.value,
            buttonsTableOutside: comportamientosTableOutside.value,
            buttonsTableInside: comportamientosTableInside.value,
            comportamientosRowGroups: comportamientosRowGroups.value,
            checkComportamientosGrouped: checkComportamientosGrouped,
            filtros: filtrosLocal.value,
            nameCSV: props.name_csv,
            generateCSV: assignCSVItems,
            downloadable: props.downloadable,
            rowsPerPage: rowsPerPageLocal.value,
            availableTableTypes: availableTableTypes,
            availableRowTypes: availableRowTypes,
            isThereImageTypes: isThereImageTypes.value,
            handleRowClick: handleRowClick,
            handleTableClick: handleTableClick,
            checkType: checkExistence,
            manageGroupalHeader: manageGroupalHeader.value,
            image_row: image_row,
            repeatTime: repeatTime.value,
            noExpandedData: props.noExpandedData,
            refreshingToolTip: refreshingToolTip.value,
            expandedDataProp: props.expandedDataProp,
            submitForm: {
              click: () => {
                getItems(filtrosLocal.value, props.args_items_fcn)
              }
            },
            continueAction: continueAction,
            id_resolver: props.id_resolver
              ? props.id_resolver
              : iclRouter.currentRoute.path,
            row_classes: rowsClassesFunction.value
              ? rowsClassesFunction.value
              : undefined,
            groupColor: props.groupColor,
            search: props.search,
            refreshParent: createdFunctions,
            columnFiltersTooltip: props.columnFilters
              ? props.columnFilters.tooltip
              : undefined,
            columnFiltersLabel: props.columnFilters
              ? props.columnFilters.label
              : '',
            columnFiltersIcon:
              props.columnFilters && props.columnFilters.icon
                ? props.columnFilters.icon
                : 'mdi-table-search',
            columnFiltersColor:
              props.columnFilters && props.columnFilters.color
                ? props.columnFilters.color
                : 'fourth',
            columnFiltersAttrs: props.columnFilters
              ? props.columnFilters['text-field-attrs']
              : undefined,
            columnFilters: props.columnFiltersValues,
            isLoadingComportamiento: isLoadingComportamiento.value
          })
        ]
      )
    }
  }
}
</script>
Last Updated: 4/5/2024, 4:52:19 PM