Added pagination to dialog search

This commit is contained in:
Alexis
2024-04-08 18:39:29 +02:00
parent e3d689edf9
commit e4587e9304
8 changed files with 308 additions and 82 deletions

View File

@@ -1,15 +1,4 @@
<script lang="ts" setup>
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogTitle,
DialogTrigger
} from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import { isCharacter, type Character } from '@/models/Characters'
import type { LeimDateOrder } from '@/models/Date'
import { isCalendarEvent, type CalendarEvent } from '@/models/Events'
@@ -20,6 +9,28 @@ import { useMagicKeys, useStorage, useTimeoutFn, whenever } from '@vueuse/core'
import { VisuallyHidden } from 'radix-vue'
import { computed, ref } from 'vue'
import { searchUnifier, type SearchMode } from '../Search'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogTitle,
DialogTrigger
} from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import {
Pagination,
PaginationEllipsis,
PaginationFirst,
PaginationLast,
PaginationList,
PaginationListItem,
PaginationNext,
PaginationPrev
} from '@/components/ui/pagination'
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import SearchList from './lists/SearchList.vue'
const { characters } = useCharacters()
@@ -31,14 +42,27 @@ const searchQuery = ref('')
const searchEnough = computed(() => searchQuery.value.length >= 2)
const selectedEntity = useStorage('se', undefined as SearchMode)
const selectedOrder = ref<LeimDateOrder>('asc')
// Order
const selectedOrder = ref<LeimDateOrder>('asc')
function setOrderAsc() {
selectedOrder.value = 'asc'
resetPage()
}
function setOrderDesc() {
selectedOrder.value = 'desc'
resetPage()
}
// Limit
const currentPage = ref<number>(1)
const itemsPerPage = 20
const startOfList = computed(() => (currentPage.value - 1) * itemsPerPage)
const endOfList = computed(() => startOfList.value + itemsPerPage)
function resetPage() {
currentPage.value = 1
}
const searchResults = computed(() => {
@@ -98,7 +122,7 @@ function resetSearch() {
const resetSearchLazy = useTimeoutFn(() => {
resetSearch()
}, 300)
}, 100)
function openDialog() {
modalOpen.value = true
@@ -139,78 +163,126 @@ whenever(keys.control_period, () => {
Rechercher les données disponibles sur le calendrier
</DialogDescription>
</VisuallyHidden>
<div class="relative w-full h-fit">
<Input
id="search"
type="text"
placeholder="Rechercher le calendrier"
class="pl-10 py-6 text-lg"
v-model:model-value="searchQuery"
autocomplete="off"
/>
<span class="absolute start-1 inset-y-0 flex items-center justify-center px-2 opacity-50">
<PhMagnifyingGlass size="20" />
</span>
<!-- Dialog header -->
<div class="grid gap-2">
<div class="relative w-full h-fit">
<Input
id="search"
type="text"
placeholder="Rechercher le calendrier"
class="pl-10 py-6 text-lg"
v-model:model-value="searchQuery"
autocomplete="off"
/>
<span class="absolute start-1 inset-y-0 flex items-center justify-center px-2 opacity-50">
<PhMagnifyingGlass size="20" />
</span>
</div>
<div class="flex items-center justify-between">
<div>
<ToggleGroup
type="single"
class="justify-start"
v-model="selectedEntity"
@update:model-value="resetPage()"
>
<ToggleGroupItem value="events" aria-label="Uniquement les évènements">
Évènements
</ToggleGroupItem>
<ToggleGroupItem value="characters" aria-label="Uniquement les personnages">
Personnages
</ToggleGroupItem>
</ToggleGroup>
</div>
<div class="flex items-center gap-1">
<TooltipProvider :delayDuration="250">
<Tooltip>
<TooltipTrigger>
<Button
:variant="selectedOrder === 'desc' ? 'secondary' : 'outline'"
size="icon"
@click="setOrderDesc()"
>
<PhClockCounterClockwise size="18" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Plus ancien</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider :delayDuration="250">
<Tooltip>
<TooltipTrigger>
<Button
:variant="selectedOrder === 'asc' ? 'secondary' : 'outline'"
size="icon"
@click="setOrderAsc()"
>
<PhClockClockwise size="18" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Plus récent</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</div>
<hr />
</div>
<div class="flex items-center justify-between">
<div>
<ToggleGroup type="single" class="justify-start" v-model="selectedEntity">
<ToggleGroupItem value="events" aria-label="Uniquement les évènements">
Évènements
</ToggleGroupItem>
<ToggleGroupItem value="characters" aria-label="Uniquement les personnages">
Personnages
</ToggleGroupItem>
</ToggleGroup>
</div>
<div class="flex items-center gap-1">
<TooltipProvider :delayDuration="250">
<Tooltip>
<TooltipTrigger>
<Button
:variant="selectedOrder === 'desc' ? 'secondary' : 'outline'"
size="icon"
@click="setOrderDesc()"
>
<PhClockCounterClockwise size="18" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Plus ancien</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipProvider :delayDuration="250">
<Tooltip>
<TooltipTrigger>
<Button
:variant="selectedOrder === 'asc' ? 'secondary' : 'outline'"
size="icon"
@click="setOrderAsc()"
>
<PhClockClockwise size="18" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Plus récent</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</div>
<hr />
<div v-if="searchResults.length > 0" class="overflow-y-auto">
<div v-if="searchResults.length > 0" class="grow overflow-y-auto">
<SearchList
:results="searchResults"
:current-entity="selectedEntity"
:order="selectedOrder"
:start-at="startOfList"
:end-at="endOfList"
@jumped-to-date="closeDialog()"
/>
</div>
<div class="flex justify-end">
<Pagination
v-model:page="currentPage"
:total="searchResults.length"
:items-per-page="itemsPerPage"
:sibling-count="2"
show-edges
:default-page="1"
>
<PaginationList v-slot="{ items }" class="flex items-center gap-1">
<PaginationFirst />
<PaginationPrev />
<template v-for="(item, index) in items">
<PaginationListItem
v-if="item.type === 'page'"
:key="index"
:value="item.value"
as-child
>
<Button
class="w-10 h-10 p-0"
:variant="item.value === currentPage ? 'default' : 'outline'"
>
{{ item.value }}
</Button>
</PaginationListItem>
<PaginationEllipsis v-else :key="item.type" :index="index" />
</template>
<PaginationNext />
<PaginationLast />
</PaginationList>
</Pagination>
</div>
</DialogContent>
</Dialog>
</template>

View File

@@ -12,6 +12,9 @@ const props = defineProps<{
results: (Character | CalendarEvent)[]
currentEntity: SearchMode
order: LeimDateOrder
startAt: number
endAt: number
limit?: number
}>()
const emit = defineEmits(['jumpedToDate'])
@@ -25,12 +28,9 @@ function handleJumpToDate(date?: LeimDate) {
emit('jumpedToDate')
}
const searchLimit = 10
const activeSearchLimit = computed<number>(() => (!props.currentEntity ? searchLimit : 9999))
const resultsToDisplay = computed(() => props.results)
// Initial sorting of the results
const sortedResults = computed(() => {
return [...resultsToDisplay.value].sort((a, b) => {
return [...props.results].sort((a, b) => {
let firstDate: LeimDate
let secondDate: LeimDate
@@ -53,11 +53,14 @@ const sortedResults = computed(() => {
return compareDates(firstDate, secondDate, props.order)
})
})
// Page the sorted results
const pagedResults = computed(() => sortedResults.value.slice(props.startAt, props.endAt))
</script>
<template>
<ul class="grid gap-3">
<li v-for="r in sortedResults" :key="isCalendarEvent(r) ? r.title : r.name">
<li v-for="r in pagedResults" :key="isCalendarEvent(r) ? r.title : r.name">
<button
v-if="isCalendarEvent(r)"
class="block w-full text-left p-2 rounded-sm"

View File

@@ -0,0 +1,25 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { PaginationEllipsis, type PaginationEllipsisProps } from 'radix-vue'
import { MoreHorizontal } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
const props = defineProps<PaginationEllipsisProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<PaginationEllipsis
v-bind="delegatedProps"
:class="cn('w-9 h-9 flex items-center justify-center', props.class)"
>
<slot>
<MoreHorizontal />
</slot>
</PaginationEllipsis>
</template>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { PaginationFirst, type PaginationFirstProps } from 'radix-vue'
import { ChevronsLeft } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
const props = withDefaults(
defineProps<PaginationFirstProps & { class?: HTMLAttributes['class'] }>(),
{
asChild: true
}
)
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<PaginationFirst v-bind="delegatedProps">
<Button :class="cn('w-10 h-10 p-0', props.class)" variant="outline">
<slot>
<ChevronsLeft class="h-4 w-4" />
</slot>
</Button>
</PaginationFirst>
</template>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { PaginationLast, type PaginationLastProps } from 'radix-vue'
import { ChevronsRight } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
const props = withDefaults(
defineProps<PaginationLastProps & { class?: HTMLAttributes['class'] }>(),
{
asChild: true
}
)
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<PaginationLast v-bind="delegatedProps">
<Button :class="cn('w-10 h-10 p-0', props.class)" variant="outline">
<slot>
<ChevronsRight class="h-4 w-4" />
</slot>
</Button>
</PaginationLast>
</template>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { PaginationNext, type PaginationNextProps } from 'radix-vue'
import { ChevronRight } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
const props = withDefaults(
defineProps<PaginationNextProps & { class?: HTMLAttributes['class'] }>(),
{
asChild: true
}
)
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<PaginationNext v-bind="delegatedProps">
<Button :class="cn('w-10 h-10 p-0', props.class)" variant="outline">
<slot>
<ChevronRight class="h-4 w-4" />
</slot>
</Button>
</PaginationNext>
</template>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
import { type HTMLAttributes, computed } from 'vue'
import { PaginationPrev, type PaginationPrevProps } from 'radix-vue'
import { ChevronLeft } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
const props = withDefaults(
defineProps<PaginationPrevProps & { class?: HTMLAttributes['class'] }>(),
{
asChild: true
}
)
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
</script>
<template>
<PaginationPrev v-bind="delegatedProps">
<Button :class="cn('w-10 h-10 p-0', props.class)" variant="outline">
<slot>
<ChevronLeft class="h-4 w-4" />
</slot>
</Button>
</PaginationPrev>
</template>

View File

@@ -0,0 +1,6 @@
export { PaginationRoot as Pagination, PaginationList, PaginationListItem } from 'radix-vue'
export { default as PaginationEllipsis } from './PaginationEllipsis.vue'
export { default as PaginationFirst } from './PaginationFirst.vue'
export { default as PaginationLast } from './PaginationLast.vue'
export { default as PaginationNext } from './PaginationNext.vue'
export { default as PaginationPrev } from './PaginationPrev.vue'