Shuunen/c-est-donne

View on GitHub
src/components/items-list-header.vue

Summary

Maintainability
Test Coverage
<!-- eslint-disable vue/no-deprecated-slot-attribute -->
<script setup lang="ts">
import SlTabGroup from '@shoelace-style/shoelace/dist/components/tab-group/tab-group'
import { capitalize } from 'shuutils'
import { ref, watch } from 'vue'
import { state } from '../state'
import { ItemStatus, type Item } from '../utils/items.utils'
import { log } from '../utils/logger.utils'
import { Display, Filter } from '../utils/tabs.utils'
import { $t } from '../utils/translate.utils'

const counts = ref({ [Filter.Available]: 0, [Filter.ReservedByMe]: 0, [Filter.All]: 0 })
const filterTabs = ref<SlTabGroup>()
const displayTabs = ref<SlTabGroup>()

function listSort (itemA: Item, itemB: Item): number {
  log('sorting items')
  // sort by availability
  if ([Filter.Available, Filter.ReservedByMe].includes(state.filter)) return Number(itemB.isVisible) - Number(itemA.isVisible)
  // or by name by default
  // eslint-disable-next-line sonar/strings-comparison
  return itemA.name > itemB.name ? 1 : -1 // eslint-disable-line @typescript-eslint/no-magic-numbers
}

function onFilter (): void {
  log('on filter :', state.filter)
  if (state.filter === Filter.Available) for (const item of state.items) item.isVisible = item.status === ItemStatus.Available
  else if (state.filter === Filter.ReservedByMe) for (const item of state.items) item.isVisible = item.status === ItemStatus.ReservedByMe
  else for (const item of state.items) item.isVisible = true
  // eslint-disable-next-line etc/no-assign-mutated-array
  state.items = state.items.sort(listSort)
  filterTabs.value?.show(state.filter)
}

function onDisplay (): void {
  log('on display :', state.display, displayTabs.value)
  displayTabs.value?.show(state.display)
}

function refreshCounts (): void {
  counts.value[Filter.Available] = state.items.filter(item => item.status === ItemStatus.Available).length
  counts.value[Filter.ReservedByMe] = state.items.filter(item => item.status === ItemStatus.ReservedByMe).length
  counts.value[Filter.All] = state.items.length
}

function onItems (): void {
  log('updating items list')
  refreshCounts()
  onFilter()
  onDisplay()
}

function labelFor (tab: Filter): string {
  if (tab === Filter.Available) return $t('tab-available', { count: counts.value[Filter.Available] })
  if (tab === Filter.ReservedByMe) return $t('tab-reserved-by-me', { count: counts.value[Filter.ReservedByMe] })
  return $t('tab-all', { count: counts.value[Filter.All] })
}

function showAll (): void {
  state.filter = Filter.All
}
function showAvailable (): void {
  state.filter = Filter.Available
}
function showReservedByMe (): void {
  state.filter = Filter.ReservedByMe
}
function showList (): void {
  state.display = Display.List
}
function showCards (): void {
  state.display = Display.Cards
}

watch(() => state.items, onItems)
watch(() => state.filter, onFilter)
watch(() => state.display, onDisplay)
</script>

<template>
  <div v-if="state.user.email" class="app-items-list--header flex-row flex-wrap items-end justify-end sm:flex">
    <div class="flex items-center justify-end gap-3 sm:hidden">
      <p>{{ $t('display') }}</p>
      <sl-dropdown>
        <sl-button slot="trigger" caret>
          <template v-if="state.filter === Filter.Available">{{ labelFor(Filter.Available) }}</template>
          <template v-else-if="state.filter === Filter.ReservedByMe">{{ labelFor(Filter.ReservedByMe) }}</template>
          <template v-else-if="state.filter === Filter.All">{{ labelFor(Filter.All) }}</template>
        </sl-button>
        <sl-menu>
          <sl-menu-item v-if="state.filter !== Filter.Available" @click="showAvailable">
            {{ labelFor(Filter.Available) }}
          </sl-menu-item>
          <sl-menu-item v-if="state.filter !== Filter.ReservedByMe" @click="showReservedByMe">
            {{ labelFor(Filter.ReservedByMe) }}
          </sl-menu-item>
          <sl-menu-item v-if="state.filter !== Filter.All" @click="showAll">
            {{ labelFor(Filter.All) }}
          </sl-menu-item>
        </sl-menu>
      </sl-dropdown>
    </div>
    <sl-tab-group ref="filterTabs" class="hidden grow sm:block">
      <sl-tab slot="nav" panel="available" @click="showAvailable">{{ capitalize(labelFor(Filter.Available)) }}</sl-tab>
      <sl-tab slot="nav" panel="reserved-by-me" @click="showReservedByMe">{{ capitalize(labelFor(Filter.ReservedByMe)) }}</sl-tab>
      <sl-tab slot="nav" panel="all" @click="showAll">{{ capitalize(labelFor(Filter.All)) }}</sl-tab>
    </sl-tab-group>
    <sl-tab-group ref="displayTabs" class="hidden sm:block">
      <sl-tab slot="nav" panel="list" @click="showList">
        {{ $t('display-list') }}
        <sl-icon class="ml-3 mt-1" name="list"></sl-icon>
      </sl-tab>
      <sl-tab slot="nav" panel="cards" @click="showCards">
        {{ $t('display-card') }}
        <sl-icon class="ml-3 mt-1" name="card-heading"></sl-icon>
      </sl-tab>
    </sl-tab-group>
  </div>
  <div v-else></div>
</template>