11<script setup lang="ts">
2- import { useQuery } from ' @tanstack/vue-query'
2+ import { useQuery , useInfiniteQuery } from ' @tanstack/vue-query'
33import { type Component , computed , nextTick , onMounted , ref , watch , watchEffect } from ' vue'
44
55import {
@@ -14,6 +14,7 @@ import {
1414 emptyTimeEntry ,
1515 getAllTimeEntries ,
1616 getCurrentTimeEntry ,
17+ getTimeEntriesPage ,
1718 useTimeEntryCreateMutation ,
1819 useTimeEntryDeleteMutation ,
1920 useTimeEntryStopMutation ,
@@ -39,7 +40,7 @@ import { LoadingSpinner } from '@solidtime/ui'
3940import { useLiveTimer } from ' ../utils/liveTimer.ts'
4041import { ClockIcon } from ' @heroicons/vue/20/solid'
4142import { CardTitle } from ' @solidtime/ui'
42- import { useStorage } from ' @vueuse/core'
43+ import { useStorage , useElementVisibility } from ' @vueuse/core'
4344import { currentMembershipId , useMyMemberships } from ' ../utils/myMemberships.ts'
4445import { getAllClients , useClientCreateMutation } from ' ../utils/clients.ts'
4546import { dayjs } from ' ../utils/dayjs.ts'
@@ -58,12 +59,30 @@ watch(currentOrganizationId, () => {
5859 selectedTimeEntries .value = []
5960})
6061
61- const { data : timeEntriesResponse } = useQuery ({
62+ const {
63+ data : timeEntriesInfiniteData,
64+ fetchNextPage,
65+ hasNextPage,
66+ isFetchingNextPage,
67+ } = useInfiniteQuery ({
6268 queryKey: [' timeEntries' , currentOrganizationId ],
63- queryFn : () => getAllTimeEntries (currentOrganizationId .value , currentMembershipId .value ),
69+ queryFn : ({ pageParam }) =>
70+ getTimeEntriesPage (currentOrganizationId .value , currentMembershipId .value , pageParam ),
71+ getNextPageParam : (lastPage ) => {
72+ if (lastPage ?.data && lastPage .data .length > 0 ) {
73+ const lastTimeEntry = lastPage .data [lastPage .data .length - 1 ]
74+ return lastTimeEntry .start
75+ }
76+ return undefined
77+ },
6478 enabled: currentOrganizationLoaded ,
79+ initialPageParam: undefined as string | undefined ,
80+ })
81+
82+ const timeEntries = computed (() => {
83+ if (! timeEntriesInfiniteData .value ) return undefined
84+ return timeEntriesInfiniteData .value .pages .flatMap ((page ) => page .data )
6585})
66- const timeEntries = computed (() => timeEntriesResponse .value ?.data )
6786
6887const { data : currentTimeEntryResponse, isError : currentTimeEntryResponseIsError } = useQuery ({
6988 queryKey: [' currentTimeEntry' ],
@@ -321,6 +340,16 @@ const canCreateProjects = computed(() => {
321340})
322341
323342const showManualTimeEntryModal = ref (false )
343+
344+ // Infinite scroll
345+ const loadMoreContainer = ref <HTMLDivElement | null >(null )
346+ const isLoadMoreVisible = useElementVisibility (loadMoreContainer )
347+
348+ watch (isLoadMoreVisible , async (isVisible ) => {
349+ if (isVisible && hasNextPage .value && ! isFetchingNextPage .value ) {
350+ await fetchNextPage ()
351+ }
352+ })
324353 </script >
325354
326355<template >
@@ -440,6 +469,19 @@ const showManualTimeEntryModal = ref(false)
440469 <h3 class =" text-white font-semibold" >No time entries found</h3 >
441470 <p class =" pb-5 text-muted" >Create your first time entry now!</p >
442471 </div >
472+ <div ref =" loadMoreContainer" >
473+ <div
474+ v-if =" isFetchingNextPage"
475+ class =" flex justify-center items-center py-5 text-white font-medium" >
476+ <LoadingSpinner class =" ml-0 mr-0" ></LoadingSpinner >
477+ <span class =" ml-2" > Loading more time entries... </span >
478+ </div >
479+ <div
480+ v-else-if =" !hasNextPage && timeEntries && timeEntries.length > 0"
481+ class =" flex justify-center items-center py-5 text-muted font-medium" >
482+ All time entries are loaded!
483+ </div >
484+ </div >
443485 </div >
444486 </div >
445487 <div v-else class =" flex items-center justify-center h-full" >
0 commit comments