# Componente

# Introducción

Este componente renderizará componentes en una grilla. La misma se compone de dos partes, una de renderizado ir-iterator (nombre utilizado para renderizar el componente, se debe recibir desde BD) y otra de logica ir-iterator-bone, esta ultima será la encargada de el manejo de los datos así sean ingresados por propiedades o por consulta a la capa de datos.

# Uso

El componente espera recibir un arreglo de items con la siguiente estructura y un type para identificar el componente a renderizar, desde la capa de datos.

type: 'IrDataCard',
items: [
  {
    "title": "1",
    "value": "180",
    "subtitle": "Subtítulo 1"
  },
  {
    "title": "3",
    "value": "199%",
    "subtitle": "Subtítulo 2"
  }
]

# Ejemplo

Propiedad Tipo Default Requerido Descripción
items Array [] false

Arreglo de items. En caso de que uno de los items tenga la estructura: { type: 'IrExample', props: { exampleProp: 'example' } } entonces ese item no respetará la propiedad type del componente IrIterator, será el único componente distinto al resto.

items_fcn String undefined false Nombre de la función a ejecutarse en la DB, la respuesta se utilizará para reemplazar el arreglo de items.
args_items_fcn Object [] false Parámetros de la función que se ejecuta con el nombre proveniente de la propiedad "items_fcn" (ésta propiedad queda inutilizada si no se envía "items_fcn"). *IMPORTANTE* la función descripta en la propiedad "items_fcn" va a recibir los prámetros que incluya esta propiedad y además un parámetro extra "filtros" que va a tener como valor un array hecho string con los valores de los campos del formulario de filtro. En caso de no poseer filtros, el parámetro se envía como un string vacío.
filtros Array [] false Arreglo de campos del formulario de filtro (igual formato que la propiedad "campo" del componente "Formulario"). *IMPORTANTE* Los filtros no se mostrarán si la propiedad "items_fcn" no está setteada. Se explica más detalladamente aquí.
filtros_fcn String undefined false Nombre de la función a ejecutarse en la DB, la respuesta se utilizará para reemplazar el arreglo de filtros.
args_filtros_fcn Object {} false Parámetros de la función que se ejecuta con el nombre proveniente de la propiedad "filtros_fcn" (ésta propiedad queda inutilizada si no se envía "filtros_fcn")
rowsPerPage Number 5 false Determina la cantidad de filas por página que tendrá la tabla inicialmente (puede cambiarse desde los botones de navegación de la misma tabla). En caso de valer -1 se muestran todas las filas.
cache_items Boolean true false

Activa un cache de los items de la tabla, el cual permitirá que al navegar por el sitio se almacenen en el equipo del usuario los items consultados por items_fcn, para mostrar temporalmente mientras se consulta la información actualizada a la DB. Esto dará una mejor performance a la tabla, ya que mostrará contenido mas rápido.

Al desactivarse la tabla no mostrará items hasta obtener la información de la DB, solo mostrará un loading.

item-key String

id

false

Nombre de la propiedad que va a utilizarse como identificador de cada item.

type String

undefined

true

Nombre del componente que se va a renderizar.

columns Number

4

false

Cantidad de columnas que tendrá la grilla que contendrá los registros. Se tomará por default en los breakpoints que no estén definidos.

columns-[breakpoint] Number

undefined

false

Cantidad de columnas que tendrá la grilla que contendrá los registros. La variable breakpoint del nombre de la propiedad puede tomar los valores xs, sm, md, lg o xl. Cada uno de estos nombres corresponde a un breakpoint que varía según el tamaño de la pantalla del dispositivo. Por ende si se establece por ejemplo columns-sm, si un dispositivo cumple el tamaño de este breakpoint se tomaran las columnas determinadas en esta propiedad. Pueden definirse todos los breakpoints en distintas propiedades.

En el caso de xs, sm y md se tomará un valor por default de 1, 2 y 3 respectivamente por default. Para automatizar la adaptación responsive.

Los tamaños de los breakpoints son pueden verse aquí.

adaptPerPage Boolean

false

false

Determinará si la cantidad de registros se adaptará al ancho del contenedor (en caso de ser true, estarán todos los registros de una misma página en una misma línea).

hideDefaultFooter Boolean

false

false

Determinará si se muestra el footer con la paginación.

hideDefaultHeader Boolean

true

false

Determinará si se muestra el header con el buscador y el filtrado.

colorHeader String

'#ffffff'

false

Determinará el color del header.

fullHeight Boolean

false

false

Determina si los componentes renderizados tomarán el alto máximo disponible.

value undefined

undefined

false

Valor que actualiza el componente en caso de que sus items tengan una propiedad value.

Código fuente

<template>
  <ir-iterator-bone
    v-bind="$attrs"
    @close-dialog="cerrarDialog"
    @update-value="updateSelectValue"
    @open-dialog="openDialog"
  >
    <div
      slot-scope="{
        items: itemsBone,
        itemsPerPage,
        keys,
        filtros,
        submitForm,
        id_resolver,
        colorHeader,
        type,
        columns,
        adaptPerPage,
        hideDefaultFooter,
        hideDefaultHeader,
        handleItemClick,
        comportamiento,
        continueAction,
        tituloDialog,
        isValidIdResolver,
        id_resolver_dlg,
        message,
        refreshParent,
        fullHeight,
        linked,
        isLoading,
        dialogAttrs
      }"
    >
      <ir-dialog
        v-model="showDialog"
        :titulo="tituloDialog"
        :json_resolver="{
          id_resolver: id_resolver_dlg,
          args_components_fcn: { filtros: filtros }
        }"
        :message="message"
        v-bind="dialogAttrs"
        @confirm="continueAction"
        @refresh-parent="refreshParent"
      />
      <v-data-iterator
        ref="irIteratorRef"
        :items="itemsBone"
        :items-per-page="itemsPerPage"
        :search="search"
        :loading="isLoading"
        :sort-by="sortBy"
        v-bind="getFilteredProps"
        :sort-desc="sortDesc"
        :hide-default-footer="hideDefaultFooter"
        :style="
          !isLoading
            ? getIteratorStyle(adaptPerPage, itemsPerPage, columns)
            : ''
        "
      >
        <template
          v-if="!hideDefaultHeader"
          #header
        >
          <ir-form
            v-if="filtros.length !== 0 && isFiltros"
            :campos="filtros"
            data-cy="form-filtro"
            flat
            :style="getFormStyle(adaptPerPage, itemsPerPage, columns)"
          >
            <template #footer>
              <v-btn
                dark
                data-cy="aplicar-filtro"
                v-on="submitForm"
              >
                Apply filters
              </v-btn>
            </template>
          </ir-form>
          <div
            v-if="filtros.length !== 0 && visibleFields(filtros)"
            :style="getFiltersStyle(adaptPerPage, itemsPerPage, columns)"
          >
            <div class="d-flex flex-row actionPosition">
              <v-tooltip bottom>
                <template #activator="{ on, attrs }">
                  <v-btn
                    data-cy="form-filtro-show"
                    class="d-inline-flex mx-2"
                    fab
                    small
                    dark
                    v-on="on"
                    @click="mostrarFiltros"
                  >
                    <v-icon dark>
                      {{ isFiltros ? 'mdi-filter-off' : 'mdi-filter' }}
                    </v-icon>
                  </v-btn>
                </template>
                <span>
                  {{ isFiltros ? 'Hide filters' : 'Show filters' }}
                </span>
              </v-tooltip>
            </div>
          </div>
          <v-toolbar
            :color="colorHeader"
            class="mb-1"
            :style="getHeaderStyle(adaptPerPage, itemsPerPage, columns)"
          >
            <v-text-field
              v-model="search"
              clearable
              flat
              solo-inverted
              hide-details
              prepend-inner-icon="mdi-magnify"
              label="Search"
            />
            <v-spacer />
            <v-select
              v-model="sortBy"
              clearable
              flat
              solo-inverted
              hide-details
              :items="keys"
              prepend-inner-icon="mdi-magnify"
              label="Sort by"
            />
            <v-spacer />
            <v-btn-toggle
              v-model="sortDesc"
              mandatory
            >
              <v-btn
                large
                depressed
                color="blue"
                :value="false"
              >
                <v-icon>mdi-arrow-up</v-icon>
              </v-btn>
              <v-btn
                large
                depressed
                color="blue"
                :value="true"
              >
                <v-icon>mdi-arrow-down</v-icon>
              </v-btn>
            </v-btn-toggle>
          </v-toolbar>
        </template>
        <template #item="{ item }">
          <!--El estilo se agregó de esta forma porque con scoped en <style> no toma el estilo por ser un componente de Vuetify.-->
          <style>
            .v-data-footer {
              grid-column: {{ getFooterStyle(adaptPerPage, itemsPerPage, columns) }};
            }
          </style>
          <ir-component-resolver
            class="mb-5"
            style="grid-column: span 1 / span 1; min-height: 100%"
            :full-height="fullHeight"
            :class="
              componentClasses(
                item.value,
                comportamiento.type && isValidIdResolver(item, comportamiento)
              )
            "
            :json_resolver="{
              id_usuario: $iclstore.state.id_usuario,
              id_resolver: id_resolver
            }"
            :dev_components="[getItemsProps(type, item)]"
            @click="
              slotsClickManage(
                item,
                handleItemClick,
                comportamiento,
                linked,
                typeof comportamiento.type !== 'undefined' &&
                  isValidIdResolver(item, comportamiento)
              )
            "
          />
        </template>
        <template #loading>
          <v-progress-linear
            style="width: 100%"
            indeterminate
            color="primary"
          />
          <span v-if="$attrs['loading-text']">{{
            $attrs['loading-text']
          }}</span>
        </template>
      </v-data-iterator>
    </div>
  </ir-iterator-bone>
</template>

<script>
import { ref, computed, nextTick, onMounted, getCurrentInstance } from 'vue'
import { iteratorComponentName } from '../../constants'
import IrIteratorBone from './IrIteratorBone.vue'
import { filterProps } from '../../helpers/filterProps'

export default {
  name: iteratorComponentName,
  components: {
    IrIteratorBone
  },
  inheritAttrs: false,
  props: {
    value: {
      type: undefined,
      required: false,
      default: ''
    }
  },
  setup(props, { emit, attrs }) {
    const vueInstance = getCurrentInstance()
    const vuetify = vueInstance.proxy.$vuetify
    const irIteratorRef = ref(null)
    const showDialog = ref(false)
    const selectedRows = ref([])
    const isFiltros = ref(false)
    const search = ref('')
    const sortBy = ref('')
    const sortDesc = ref(false)
    const otherProps = ref({})

    const getFilteredProps = computed(() => {
      return filterProps(irIteratorRef.value, otherProps.value, ['data-cy'])
    })

    const componentClasses = (itemsValue, isValidIdResolver) => {
      let isValueSelected = props.value === itemsValue
      let isSelectable =
        isValidIdResolver ||
        (typeof props.value !== 'undefined' &&
          typeof itemsValue !== 'undefined')
      return (
        (isValueSelected ? 'active' : '') +
        (isSelectable ? ' non-selected-comps' : ' unselectable')
      )
    }

    const getItemsProps = (type, item) => {
      return item.type && item.props ? item : { type, props: item }
    }

    const visibleFields = (filtros) => {
      return filtros.filter((filtro) => filtro.props.visible).length > 0
    }

    const cerrarDialog = () => {
      showDialog.value = false
    }

    const openDialog = () => {
      showDialog.value = true
    }

    const slotsClickManage = (
      item,
      clickHandler,
      comp,
      linked,
      isValidIdResolver
    ) => {
      if (comp.type !== 'navigator') {
        updateValue(item.value, linked)
      }
      if (typeof comp.type !== 'undefined' && isValidIdResolver) {
        clickHandler(item)
      }
    }

    const updateValue = (newValue, linked) => {
      if (typeof newValue !== 'undefined') {
        nextTick(() => emit('input', newValue))
        if (linked) {
          nextTick(() => emit('change-linked-value'))
        }
      }
    }

    const updateSelectValue = (value) => {
      let localValue = value || props.value
      updateValue(localValue, false)
    }

    const mostrarFiltros = () => {
      isFiltros.value = !isFiltros.value
    }

    const calculateColumns = (adapt, perPage, columns) => {
      let breakpoint = vuetify.breakpoint.name
      return adapt ? perPage : columns[breakpoint] || columns.default || '4'
    }

    const getIteratorStyle = (adaptPerPage, itemsPerPage, columns) => ({
      display: 'grid',
      'grid-template-columns': `repeat(${calculateColumns(
        adaptPerPage,
        itemsPerPage,
        columns
      )}, minmax(0, 1fr))`,
      gap: '10px'
    })

    const getFormStyle = (adaptPerPage, itemsPerPage, columns) => ({
      'margin-bottom': '40px',
      'grid-column': `span ${calculateColumns(
        adaptPerPage,
        itemsPerPage,
        columns
      )} / span ${calculateColumns(adaptPerPage, itemsPerPage, columns)}`
    })

    const getFiltersStyle = (adaptPerPage, itemsPerPage, columns) => ({
      position: 'relative',
      'grid-column': `span ${calculateColumns(
        adaptPerPage,
        itemsPerPage,
        columns
      )} / span ${calculateColumns(adaptPerPage, itemsPerPage, columns)}`,
      width: '100%'
    })

    const getHeaderStyle = (adaptPerPage, itemsPerPage, columns) => ({
      'grid-column': `span ${calculateColumns(
        adaptPerPage,
        itemsPerPage,
        columns
      )} / span ${calculateColumns(adaptPerPage, itemsPerPage, columns)}`
    })

    const getFooterStyle = (adaptPerPage, itemsPerPage, columns) =>
      `span ${calculateColumns(
        adaptPerPage,
        itemsPerPage,
        columns
      )} / span ${calculateColumns(adaptPerPage, itemsPerPage, columns)}`

    onMounted(() => {
      let aux = { ...attrs }
      updateSelectValue(props.value)
      delete aux.items
      delete aux.show_select
      otherProps.value = aux
    })

    return {
      irIteratorRef,
      showDialog,
      selectedRows,
      isFiltros,
      search,
      sortBy,
      sortDesc,
      getFilteredProps,
      componentClasses,
      getItemsProps,
      visibleFields,
      openDialog,
      cerrarDialog,
      slotsClickManage,
      updateValue,
      updateSelectValue,
      mostrarFiltros,
      calculateColumns,
      getIteratorStyle,
      getFormStyle,
      getFiltersStyle,
      getHeaderStyle,
      getFooterStyle
    }
  }
}
</script>

<style scoped>
.actionPosition {
  position: absolute;
  right: -27px;
  top: -35px;
  z-index: 10;
}
.active > :first-child {
  box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
  transform: scale(1.02);
}
.unselectable > :first-child {
  pointer-events: none;
}
.non-selected-comps {
  cursor: pointer;
}
.non-selected-comps > :first-child {
  transition-timing-function: linear;
  transition-duration: 120ms;
  transition-property: all;
}
.non-selected-comps:hover > :first-child {
  transform: scale(1.02);
}
</style>
Last Updated: 4/5/2024, 4:52:19 PM