Added pagination to dialog search
This commit is contained in:
@@ -1,15 +1,4 @@
|
|||||||
<script lang="ts" setup>
|
<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 { isCharacter, type Character } from '@/models/Characters'
|
||||||
import type { LeimDateOrder } from '@/models/Date'
|
import type { LeimDateOrder } from '@/models/Date'
|
||||||
import { isCalendarEvent, type CalendarEvent } from '@/models/Events'
|
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 { VisuallyHidden } from 'radix-vue'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { searchUnifier, type SearchMode } from '../Search'
|
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'
|
import SearchList from './lists/SearchList.vue'
|
||||||
|
|
||||||
const { characters } = useCharacters()
|
const { characters } = useCharacters()
|
||||||
@@ -31,14 +42,27 @@ const searchQuery = ref('')
|
|||||||
const searchEnough = computed(() => searchQuery.value.length >= 2)
|
const searchEnough = computed(() => searchQuery.value.length >= 2)
|
||||||
|
|
||||||
const selectedEntity = useStorage('se', undefined as SearchMode)
|
const selectedEntity = useStorage('se', undefined as SearchMode)
|
||||||
const selectedOrder = ref<LeimDateOrder>('asc')
|
|
||||||
|
|
||||||
|
// Order
|
||||||
|
const selectedOrder = ref<LeimDateOrder>('asc')
|
||||||
function setOrderAsc() {
|
function setOrderAsc() {
|
||||||
selectedOrder.value = 'asc'
|
selectedOrder.value = 'asc'
|
||||||
|
resetPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
function setOrderDesc() {
|
function setOrderDesc() {
|
||||||
selectedOrder.value = 'desc'
|
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(() => {
|
const searchResults = computed(() => {
|
||||||
@@ -98,7 +122,7 @@ function resetSearch() {
|
|||||||
|
|
||||||
const resetSearchLazy = useTimeoutFn(() => {
|
const resetSearchLazy = useTimeoutFn(() => {
|
||||||
resetSearch()
|
resetSearch()
|
||||||
}, 300)
|
}, 100)
|
||||||
|
|
||||||
function openDialog() {
|
function openDialog() {
|
||||||
modalOpen.value = true
|
modalOpen.value = true
|
||||||
@@ -139,6 +163,9 @@ whenever(keys.control_period, () => {
|
|||||||
Rechercher les données disponibles sur le calendrier
|
Rechercher les données disponibles sur le calendrier
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</VisuallyHidden>
|
</VisuallyHidden>
|
||||||
|
|
||||||
|
<!-- Dialog header -->
|
||||||
|
<div class="grid gap-2">
|
||||||
<div class="relative w-full h-fit">
|
<div class="relative w-full h-fit">
|
||||||
<Input
|
<Input
|
||||||
id="search"
|
id="search"
|
||||||
@@ -155,7 +182,12 @@ whenever(keys.control_period, () => {
|
|||||||
|
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<ToggleGroup type="single" class="justify-start" v-model="selectedEntity">
|
<ToggleGroup
|
||||||
|
type="single"
|
||||||
|
class="justify-start"
|
||||||
|
v-model="selectedEntity"
|
||||||
|
@update:model-value="resetPage()"
|
||||||
|
>
|
||||||
<ToggleGroupItem value="events" aria-label="Uniquement les évènements">
|
<ToggleGroupItem value="events" aria-label="Uniquement les évènements">
|
||||||
Évènements
|
Évènements
|
||||||
</ToggleGroupItem>
|
</ToggleGroupItem>
|
||||||
@@ -164,6 +196,7 @@ whenever(keys.control_period, () => {
|
|||||||
</ToggleGroupItem>
|
</ToggleGroupItem>
|
||||||
</ToggleGroup>
|
</ToggleGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<TooltipProvider :delayDuration="250">
|
<TooltipProvider :delayDuration="250">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -202,15 +235,54 @@ whenever(keys.control_period, () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="searchResults.length > 0" class="overflow-y-auto">
|
<div v-if="searchResults.length > 0" class="grow overflow-y-auto">
|
||||||
<SearchList
|
<SearchList
|
||||||
:results="searchResults"
|
:results="searchResults"
|
||||||
:current-entity="selectedEntity"
|
:current-entity="selectedEntity"
|
||||||
:order="selectedOrder"
|
:order="selectedOrder"
|
||||||
|
:start-at="startOfList"
|
||||||
|
:end-at="endOfList"
|
||||||
@jumped-to-date="closeDialog()"
|
@jumped-to-date="closeDialog()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ const props = defineProps<{
|
|||||||
results: (Character | CalendarEvent)[]
|
results: (Character | CalendarEvent)[]
|
||||||
currentEntity: SearchMode
|
currentEntity: SearchMode
|
||||||
order: LeimDateOrder
|
order: LeimDateOrder
|
||||||
|
startAt: number
|
||||||
|
endAt: number
|
||||||
|
limit?: number
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits(['jumpedToDate'])
|
const emit = defineEmits(['jumpedToDate'])
|
||||||
@@ -25,12 +28,9 @@ function handleJumpToDate(date?: LeimDate) {
|
|||||||
emit('jumpedToDate')
|
emit('jumpedToDate')
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchLimit = 10
|
// Initial sorting of the results
|
||||||
const activeSearchLimit = computed<number>(() => (!props.currentEntity ? searchLimit : 9999))
|
|
||||||
|
|
||||||
const resultsToDisplay = computed(() => props.results)
|
|
||||||
const sortedResults = computed(() => {
|
const sortedResults = computed(() => {
|
||||||
return [...resultsToDisplay.value].sort((a, b) => {
|
return [...props.results].sort((a, b) => {
|
||||||
let firstDate: LeimDate
|
let firstDate: LeimDate
|
||||||
let secondDate: LeimDate
|
let secondDate: LeimDate
|
||||||
|
|
||||||
@@ -53,11 +53,14 @@ const sortedResults = computed(() => {
|
|||||||
return compareDates(firstDate, secondDate, props.order)
|
return compareDates(firstDate, secondDate, props.order)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Page the sorted results
|
||||||
|
const pagedResults = computed(() => sortedResults.value.slice(props.startAt, props.endAt))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ul class="grid gap-3">
|
<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
|
<button
|
||||||
v-if="isCalendarEvent(r)"
|
v-if="isCalendarEvent(r)"
|
||||||
class="block w-full text-left p-2 rounded-sm"
|
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