<template> <div> <PageHeader :title="t('myShows.title')"> <AddShowButton /> </PageHeader> <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> <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> <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" /> </div> </template> <script lang="ts" setup> import { PaginatedListResult, usePaginatedList } from '@rokoli/bnb/drf' 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 { 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, tableShowsPerPage, { query }, ) 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, gridShowsPerPage, { query }, ) 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>