mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 00:02:15 +01:00
add endless scrolling to time overview page
This commit is contained in:
@@ -51,7 +51,7 @@ class TimeEntryIndexRequest extends FormRequest
|
||||
'string',
|
||||
'in:true,false',
|
||||
],
|
||||
// Limit the number of returned time entries
|
||||
// Limit the number of returned time entries (default: 150)
|
||||
'limit' => [
|
||||
'integer',
|
||||
'min:1',
|
||||
@@ -59,7 +59,8 @@ class TimeEntryIndexRequest extends FormRequest
|
||||
],
|
||||
// Filter makes sure that only time entries of a whole date are returned
|
||||
'only_full_dates' => [
|
||||
'boolean',
|
||||
'string',
|
||||
'in:true,false',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import AppLayout from '@/Layouts/AppLayout.vue';
|
||||
import TimeTracker from '@/Components/TimeTracker.vue';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { computed, onMounted, ref, watch } from 'vue';
|
||||
import MainContainer from '@/Pages/MainContainer.vue';
|
||||
import { useTimeEntriesStore } from '@/utils/useTimeEntries';
|
||||
import { storeToRefs } from 'pinia';
|
||||
@@ -9,9 +9,25 @@ import dayjs from 'dayjs';
|
||||
import type { TimeEntry } from '@/utils/api';
|
||||
import TimeEntryRowHeading from '@/Components/Common/TimeEntry/TimeEntryRowHeading.vue';
|
||||
import TimeEntryRow from '@/Components/Common/TimeEntry/TimeEntryRow.vue';
|
||||
import { useElementVisibility } from '@vueuse/core';
|
||||
|
||||
const timeEntriesStore = useTimeEntriesStore();
|
||||
const { timeEntries } = storeToRefs(timeEntriesStore);
|
||||
const { timeEntries, allTimeEntriesLoaded } = storeToRefs(timeEntriesStore);
|
||||
|
||||
const loading = ref(false);
|
||||
const loadMoreContainer = ref<HTMLDivElement | null>(null);
|
||||
const isLoadMoreVisible = useElementVisibility(loadMoreContainer);
|
||||
|
||||
watch(isLoadMoreVisible, async (isVisible) => {
|
||||
if (
|
||||
isVisible &&
|
||||
timeEntries.value.length > 0 &&
|
||||
!allTimeEntriesLoaded.value
|
||||
) {
|
||||
loading.value = true;
|
||||
await timeEntriesStore.fetchMoreTimeEntries();
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
await timeEntriesStore.fetchTimeEntries();
|
||||
@@ -43,5 +59,34 @@ const groupedTimeEntries = computed(() => {
|
||||
v-for="entry in value"
|
||||
:time-entry="entry"></TimeEntryRow>
|
||||
</div>
|
||||
<div ref="loadMoreContainer">
|
||||
<div
|
||||
v-if="loading && !allTimeEntriesLoaded"
|
||||
class="flex justify-center items-center py-5 text-white font-medium">
|
||||
<svg
|
||||
class="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24">
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<span> Loading more time entries... </span>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="allTimeEntriesLoaded"
|
||||
class="flex justify-center items-center py-5 text-white font-medium">
|
||||
All time entries are loaded!
|
||||
</div>
|
||||
</div>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
@@ -3,10 +3,13 @@ import { getCurrentOrganizationId } from '@/utils/useUser';
|
||||
import { api } from '../../../openapi.json.client';
|
||||
import { reactive, ref } from 'vue';
|
||||
import type { TimeEntry } from '@/utils/api';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export const useTimeEntriesStore = defineStore('timeEntries', () => {
|
||||
const timeEntries = ref<TimeEntry[]>(reactive([]));
|
||||
|
||||
const allTimeEntriesLoaded = ref(false);
|
||||
|
||||
async function fetchTimeEntries() {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
if (organizationId) {
|
||||
@@ -14,11 +17,40 @@ export const useTimeEntriesStore = defineStore('timeEntries', () => {
|
||||
params: {
|
||||
organization: organizationId,
|
||||
},
|
||||
queries: {
|
||||
only_full_dates: true,
|
||||
},
|
||||
});
|
||||
timeEntries.value = timeEntriesResponse.data;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchMoreTimeEntries() {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
if (organizationId) {
|
||||
const latestTimeEntry =
|
||||
timeEntries.value[timeEntries.value.length - 1];
|
||||
dayjs(latestTimeEntry.start).utc().format('YYYY-MM-DD');
|
||||
|
||||
const timeEntriesResponse = await api.getTimeEntries({
|
||||
params: {
|
||||
organization: organizationId,
|
||||
},
|
||||
queries: {
|
||||
only_full_dates: true,
|
||||
before: dayjs(latestTimeEntry.start).utc().format(),
|
||||
},
|
||||
});
|
||||
if (timeEntriesResponse.data.length > 0) {
|
||||
timeEntries.value = timeEntries.value.concat(
|
||||
timeEntriesResponse.data
|
||||
);
|
||||
} else {
|
||||
allTimeEntriesLoaded.value = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function updateTimeEntry(timeEntry: TimeEntry) {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
if (organizationId) {
|
||||
@@ -65,5 +97,7 @@ export const useTimeEntriesStore = defineStore('timeEntries', () => {
|
||||
updateTimeEntry,
|
||||
createTimeEntry,
|
||||
deleteTimeEntry,
|
||||
fetchMoreTimeEntries,
|
||||
allTimeEntriesLoaded,
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user