Added pagination to dialog search
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
25
src/components/ui/pagination/PaginationEllipsis.vue
Normal file
25
src/components/ui/pagination/PaginationEllipsis.vue
Normal 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>
|
||||
30
src/components/ui/pagination/PaginationFirst.vue
Normal file
30
src/components/ui/pagination/PaginationFirst.vue
Normal 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>
|
||||
30
src/components/ui/pagination/PaginationLast.vue
Normal file
30
src/components/ui/pagination/PaginationLast.vue
Normal 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>
|
||||
30
src/components/ui/pagination/PaginationNext.vue
Normal file
30
src/components/ui/pagination/PaginationNext.vue
Normal 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>
|
||||
30
src/components/ui/pagination/PaginationPrev.vue
Normal file
30
src/components/ui/pagination/PaginationPrev.vue
Normal 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>
|
||||
6
src/components/ui/pagination/index.ts
Normal file
6
src/components/ui/pagination/index.ts
Normal 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'
|
||||
Reference in New Issue
Block a user