Skip to content
Snippets Groups Projects
MyShows.vue 6.94 KiB
Newer Older
  • Learn to ignore specific revisions
  • <template>
      <b-container fluid>
        <b-container>
          <PageHeader :title="t('myShows.title')">
            <AddShowButton />
    
            <RadioGroup v-model="displayMode" :choices="['table', 'grid']" name="display-mode">
              <template #icon="{ value }">
                <icon-system-uicons-table-header v-if="value === 'table'" />
                <icon-system-uicons-grid-small v-else-if="value === 'grid'" />
              </template>
            </RadioGroup>
          </PageHeader>
        </b-container>
    
        <b-container>
          <div class="tw-flex tw-gap-6 tw-justify-end tw-items-center tw-mb-6">
            <Tag v-if="displayMode === 'grid'" class="tw-self-stretch tw-justify-self-start tw-px-3">
              {{
                t('myShows.showCount', {
                  count: gridShowResultList.reduce((acc, cur) => acc + cur.items.length, 0),
                  total: gridShowResultList[0]?.count ?? 0,
                })
              }}
            </Tag>
    
            <Tag v-if="isLoading" role="alert" aria-live="polite" class="tw-px-3 tw-self-stretch">
              <span class="tw-flex tw-gap-2 tw-items-center">
                <Loading class="tw-h-1" />
                {{ t('loadingData', { items: t('show.plural') }) }}
              </span>
            </Tag>
    
            <span class="tw-mr-auto" />
    
            <FormGroup class="tw-m-0">
              <template #iconLeft="attrs">
                <icon-system-uicons-search v-bind="attrs" />
              </template>
              <template #default="attrs">
                <input
                  v-model="searchTerm"
                  v-bind="attrs"
                  :aria-label="t('myShows.searchLabel')"
                  :placeholder="t('myShows.searchPlaceholder')"
                />
              </template>
            </FormGroup>
    
            <button
              class="btn btn-default tw-flex tw-items-center tw-gap-2"
              @click="orderFilterDialog.open()"
            >
              <icon-system-uicons-sort />
              {{ t('myShows.sortShows') }}
            </button>
    
            <AEditDialog ref="orderFilterDialog" :title="t('myShows.showOrder')" class="tw-w-min">
              <OrderFilter
                v-model="order"
                translate-base="showFilter.order.choices"
                :choices="[
                  'id',
                  'slug',
                  { name: 'is_active', directions: ['desc', 'asc'] },
                  { name: 'updated_at', directions: ['desc', 'asc'] },
                  'updated_by',
                  { name: 'is_owner', directions: ['desc', 'asc'] },
                ]"
              />
              <template #footer="{ close }">
                <button type="button" class="btn btn-default tw-min-w-[100px]" @click="close">
                  {{ t('ok') }}
                </button>
              </template>
            </AEditDialog>
          </div>
    
          <ShowListTable v-if="displayMode === 'table'" :shows="tableShowResult">
            <template #footer>
              <div
                v-if="tableShowResult.count > 0 || !isLoading"
                class="tw-flex tw-items-center tw-px-6 tw-mt-3 tw-py-3 tw-border-0 tw-border-t tw-border-solid tw-border-gray-200 empty:tw-hidden"
              >
                <PaginationRange v-if="tableShowResult.count > 0" :pagination-data="tableShowResult" />
                <FormGroup v-slot="attrs" class="tw-ml-auto tw-m-0 tw-mr-9 last:tw-mr-0">
                  <label class="tw-flex tw-items-center tw-gap-3 tw-m-0">
                    <span>{{ t('myShows.itemsPerPage') }}</span>
                    <input
                      v-model.lazy="tableShowsPerPage"
                      type="number"
                      min="1"
                      step="1"
                      v-bind="attrs"
                      class="tw-w-[80px] tw-self-center"
                    />
                  </label>
                </FormGroup>
                <Pagination
                  v-model="tableShowsPage"
                  :items-per-page="tableShowsPerPage"
                  :count="tableShowResult.count"
                />
              </div>
            </template>
          </ShowListTable>
    
          <ShowListGrid
            v-else
            :shows="gridShowResultList"
            :has-more="gridShowResult.hasNext"
            :split="order[0] === '-is_owner'"
            @load-more="loadMore"
          />
        </b-container>
      </b-container>
    </template>
    
    <script lang="ts" setup>
    import { useStorage } from '@vueuse/core'
    import { computed, Ref, ref, watch } from 'vue'
    import { useI18n } from '@/i18n'
    import { computedDebounced } from '@/util'
    import { Show } from '@/types'
    import { PaginatedListResult, usePaginatedList } from '@/api'
    import { useShowStore } from '@/stores/shows'
    import PageHeader from '@/components/PageHeader.vue'
    import ShowListTable from '@/components/shows/ShowListTable.vue'
    import FormGroup from '@/components/generic/FormGroup.vue'
    import ShowListGrid from '@/components/shows/ShowListGrid.vue'
    import RadioGroup from '@/components/generic/RadioGroup.vue'
    import Loading from '@/components/generic/Loading.vue'
    import Tag from '@/components/generic/Tag.vue'
    import AEditDialog from '@/components/generic/AEditDialog.vue'
    import OrderFilter from '@/components/OrderFilter.vue'
    import PaginationRange from '@/components/generic/PaginationRange.vue'
    import Pagination from '@/components/generic/Pagination.vue'
    import AddShowButton from '@/components/shows/AddShowButton.vue'
    
    const TABLE_SHOWS_PER_PAGE = 12
    const GRID_SHOWS_PER_PAGE = 10
    
    type DisplayMode = 'grid' | 'table'
    type OrderField = 'is_owner' | 'is_active' | 'slug' | 'updatedAt' | 'updatedBy' | 'id'
    type ShowOrder = OrderField | `-${OrderField}`
    
    const { t } = useI18n()
    const { listIsolated } = useShowStore()
    
    const orderFilterDialog = ref()
    const searchTerm = ref('')
    const debouncedSearchTerm = computedDebounced(searchTerm, (q: string) => (q.trim() ? 0.3 : 0))
    const order = useStorage<ShowOrder[]>('aura:myShows:order', ['-is_owner', '-is_active', 'slug'])
    const query = computed(
      () =>
        new URLSearchParams({
          order: order.value.join(','),
          search: debouncedSearchTerm.value.trim(),
        }),
    )
    
    const displayMode: Ref<DisplayMode> = useStorage<DisplayMode>('aura:myShows:displayMode', 'grid')
    
    const tableShowsPage = ref(1)
    const tableShowsPerPage = useStorage('aura:myShows:tableShowsPerPage', TABLE_SHOWS_PER_PAGE)
    const { result: tableShowResult, isLoading: isLoadingTableShows } = usePaginatedList(
      listIsolated,
      tableShowsPage,
      { query, limit: tableShowsPerPage },
    )
    
    const gridShowsPage = ref(1)
    const gridShowsPerPage = useStorage('aura:myShows:gridShowsPerPage', GRID_SHOWS_PER_PAGE)
    const gridShowResultMap = ref(new Map<number, PaginatedListResult<Show>>())
    const gridShowResultList = computed(() => Array.from(gridShowResultMap.value.values()))
    const { result: gridShowResult, isLoading: isLoadingGridShows } = usePaginatedList(
      listIsolated,
      gridShowsPage,
      { query, limit: gridShowsPerPage },
    )
    watch(query, () => {
      gridShowsPage.value = 1
      gridShowResultMap.value.clear()
    })
    watch(gridShowResult, (newShows: PaginatedListResult<Show>) => {
      gridShowResultMap.value.set(newShows.page, newShows)
    })
    function loadMore() {
      if (gridShowResult.value.hasNext) {
        gridShowsPage.value += 1
      }
    }
    
    const isLoading = computed(() => isLoadingTableShows.value || isLoadingGridShows.value)
    </script>