mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-15 19:52:15 +01:00
refactor timeentries queries and mutations, improve activitygraph, add dashboard reporting table
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
import { useCurrentTimeEntryStore } from '@/utils/useCurrentTimeEntry';
|
||||
import { useTimeEntriesStore } from '@/utils/useTimeEntries';
|
||||
|
||||
export function initializeStores() {
|
||||
// TanStack Query now handles projects, tasks, tags, clients, and members fetching automatically
|
||||
// Only initialize stores that aren't migrated to TanStack Query yet
|
||||
useCurrentTimeEntryStore().fetchCurrentTimeEntry();
|
||||
useTimeEntriesStore().patchTimeEntries();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { QueryClient } from '@tanstack/vue-query';
|
||||
import { api } from '@/packages/api/src';
|
||||
import { getCurrentOrganizationId } from '@/utils/useUser';
|
||||
import { getCurrentOrganizationId, getCurrentMembershipId } from '@/utils/useUser';
|
||||
import { canViewClients, canViewMembers } from '@/utils/permissions';
|
||||
|
||||
/**
|
||||
@@ -21,6 +21,7 @@ const routePrefetchers: Record<string, (queryClient: QueryClient) => void> = {
|
||||
prefetchTasks(queryClient);
|
||||
prefetchTags(queryClient);
|
||||
prefetchClients(queryClient);
|
||||
prefetchTimeEntries(queryClient);
|
||||
},
|
||||
|
||||
'/calendar': (queryClient) => {
|
||||
@@ -221,6 +222,28 @@ function prefetchReports(queryClient: QueryClient) {
|
||||
});
|
||||
}
|
||||
|
||||
function prefetchTimeEntries(queryClient: QueryClient) {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
const memberId = getCurrentMembershipId();
|
||||
if (!organizationId) return;
|
||||
|
||||
queryClient.prefetchInfiniteQuery({
|
||||
queryKey: ['timeEntries', 'infinite', { organizationId, memberId }],
|
||||
queryFn: async () => {
|
||||
const response = await api.getTimeEntries({
|
||||
params: { organization: organizationId },
|
||||
queries: {
|
||||
only_full_dates: 'true',
|
||||
member_id: memberId,
|
||||
},
|
||||
});
|
||||
return response;
|
||||
},
|
||||
initialPageParam: undefined,
|
||||
staleTime: 30000,
|
||||
});
|
||||
}
|
||||
|
||||
function prefetchProjectMembers(queryClient: QueryClient, projectId: string) {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
if (!organizationId || !canViewMembers()) return;
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
getCurrentUserId,
|
||||
} from '@/utils/useUser';
|
||||
import { useLocalStorage } from '@vueuse/core';
|
||||
import { useTimeEntriesStore } from '@/utils/useTimeEntries';
|
||||
import { useNotificationsStore } from '@/utils/notification';
|
||||
import { useQueryClient } from '@tanstack/vue-query';
|
||||
|
||||
dayjs.extend(utc);
|
||||
|
||||
@@ -32,6 +32,7 @@ const emptyTimeEntry = {
|
||||
export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
|
||||
const currentTimeEntry = ref<TimeEntry>(reactive(emptyTimeEntry));
|
||||
const { handleApiRequestNotifications } = useNotificationsStore();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useLocalStorage('solidtime/current-time-entry', currentTimeEntry, {
|
||||
deep: true,
|
||||
@@ -208,7 +209,7 @@ export const useCurrentTimeEntryStore = defineStore('currentTimeEntry', () => {
|
||||
stopLiveTimer();
|
||||
await stopTimer();
|
||||
}
|
||||
useTimeEntriesStore().fetchTimeEntries();
|
||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { getCurrentMembershipId, getCurrentOrganizationId } from '@/utils/useUser';
|
||||
|
||||
import { reactive, ref, type Ref } from 'vue';
|
||||
import { api } from '@/packages/api/src';
|
||||
import type {
|
||||
CreateTimeEntryBody,
|
||||
TimeEntriesQueryParams,
|
||||
TimeEntry,
|
||||
UpdateMultipleTimeEntriesChangeset,
|
||||
} from '@/packages/api/src';
|
||||
import dayjs from 'dayjs';
|
||||
import { useNotificationsStore } from '@/utils/notification';
|
||||
import type {} from '@/packages/api/src';
|
||||
import { useQueryClient } from '@tanstack/vue-query';
|
||||
|
||||
export const useTimeEntriesStore = defineStore(
|
||||
'timeEntries',
|
||||
(): {
|
||||
timeEntries: Ref<TimeEntry[]>;
|
||||
fetchTimeEntries: (queryParams?: TimeEntriesQueryParams) => Promise<void>;
|
||||
updateTimeEntry: (timeEntry: TimeEntry) => Promise<void>;
|
||||
createTimeEntry: (timeEntry: Omit<CreateTimeEntryBody, 'member_id'>) => Promise<void>;
|
||||
deleteTimeEntry: (timeEntryId: string) => Promise<void>;
|
||||
fetchMoreTimeEntries: () => Promise<void>;
|
||||
allTimeEntriesLoaded: Ref<boolean>;
|
||||
updateTimeEntries: (
|
||||
ids: string[],
|
||||
changes: UpdateMultipleTimeEntriesChangeset
|
||||
) => Promise<void>;
|
||||
deleteTimeEntries: (timeEntries: TimeEntry[]) => Promise<void>;
|
||||
patchTimeEntries: (queryParams?: TimeEntriesQueryParams) => Promise<void>;
|
||||
} => {
|
||||
const timeEntries = ref<TimeEntry[]>(reactive([]));
|
||||
|
||||
const allTimeEntriesLoaded = ref(false);
|
||||
const { handleApiRequestNotifications } = useNotificationsStore();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
async function patchTimeEntries(
|
||||
queryParams: TimeEntriesQueryParams = {
|
||||
only_full_dates: 'true',
|
||||
member_id: getCurrentMembershipId(),
|
||||
}
|
||||
) {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
|
||||
if (organizationId) {
|
||||
const timeEntriesResponse = await handleApiRequestNotifications(
|
||||
() =>
|
||||
api.getTimeEntries({
|
||||
params: {
|
||||
organization: organizationId,
|
||||
},
|
||||
queries: queryParams,
|
||||
}),
|
||||
undefined,
|
||||
'Failed to fetch time entries'
|
||||
);
|
||||
if (timeEntriesResponse?.data) {
|
||||
// insert missing time entries
|
||||
const missingTimeEntries = timeEntriesResponse.data.filter(
|
||||
(entry) => !timeEntries.value.find((e) => e.id === entry.id)
|
||||
);
|
||||
timeEntries.value = [...missingTimeEntries, ...timeEntries.value];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchTimeEntries(
|
||||
queryParams: TimeEntriesQueryParams = {
|
||||
only_full_dates: 'true',
|
||||
member_id: getCurrentMembershipId(),
|
||||
}
|
||||
) {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
|
||||
if (organizationId) {
|
||||
const timeEntriesResponse = await handleApiRequestNotifications(
|
||||
() =>
|
||||
api.getTimeEntries({
|
||||
params: {
|
||||
organization: organizationId,
|
||||
},
|
||||
queries: queryParams,
|
||||
}),
|
||||
undefined,
|
||||
'Failed to fetch time entries'
|
||||
);
|
||||
if (timeEntriesResponse?.data) {
|
||||
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 handleApiRequestNotifications(
|
||||
() =>
|
||||
api.getTimeEntries({
|
||||
params: {
|
||||
organization: organizationId,
|
||||
},
|
||||
queries: {
|
||||
only_full_dates: 'true',
|
||||
member_id: getCurrentMembershipId(),
|
||||
end: dayjs(latestTimeEntry.start).utc().format(),
|
||||
},
|
||||
}),
|
||||
undefined,
|
||||
'Failed to fetch time entries'
|
||||
);
|
||||
if (timeEntriesResponse?.data && timeEntriesResponse.data.length > 0) {
|
||||
timeEntries.value = timeEntries.value.concat(timeEntriesResponse.data);
|
||||
} else {
|
||||
allTimeEntriesLoaded.value = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function updateTimeEntries(
|
||||
ids: string[],
|
||||
changes: UpdateMultipleTimeEntriesChangeset
|
||||
) {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
if (organizationId) {
|
||||
await handleApiRequestNotifications(
|
||||
() =>
|
||||
api.updateMultipleTimeEntries(
|
||||
{
|
||||
ids: ids,
|
||||
changes: changes,
|
||||
},
|
||||
{
|
||||
params: {
|
||||
organization: organizationId,
|
||||
},
|
||||
}
|
||||
),
|
||||
'Time entries updated successfully',
|
||||
'Failed to update time entries'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function updateTimeEntry(timeEntry: TimeEntry) {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
if (organizationId) {
|
||||
const response = await handleApiRequestNotifications(
|
||||
() =>
|
||||
api.updateTimeEntry(timeEntry, {
|
||||
params: {
|
||||
organization: organizationId,
|
||||
timeEntry: timeEntry.id,
|
||||
},
|
||||
}),
|
||||
'Time entry updated successfully',
|
||||
'Failed to update time entry'
|
||||
);
|
||||
timeEntries.value = timeEntries.value.map((entry) =>
|
||||
entry.id === timeEntry.id ? response.data : entry
|
||||
);
|
||||
queryClient.invalidateQueries({ queryKey: ['timeEntry'] });
|
||||
}
|
||||
}
|
||||
|
||||
async function createTimeEntry(timeEntry: Omit<CreateTimeEntryBody, 'member_id'>) {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
const memberId = getCurrentMembershipId();
|
||||
if (organizationId && memberId !== undefined) {
|
||||
const newTimeEntry = {
|
||||
...timeEntry,
|
||||
member_id: memberId,
|
||||
} as CreateTimeEntryBody;
|
||||
await handleApiRequestNotifications(
|
||||
() =>
|
||||
api.createTimeEntry(newTimeEntry, {
|
||||
params: {
|
||||
organization: organizationId,
|
||||
},
|
||||
}),
|
||||
'Time entry created successfully',
|
||||
'Failed to create time entry'
|
||||
);
|
||||
await fetchTimeEntries();
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteTimeEntry(timeEntryId: string) {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
if (organizationId) {
|
||||
await handleApiRequestNotifications(
|
||||
() =>
|
||||
api.deleteTimeEntry(undefined, {
|
||||
params: {
|
||||
organization: organizationId,
|
||||
timeEntry: timeEntryId,
|
||||
},
|
||||
}),
|
||||
'Time entry deleted successfully',
|
||||
'Failed to delete time entry'
|
||||
);
|
||||
await fetchTimeEntries();
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteTimeEntries(timeEntries: TimeEntry[]) {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
const timeEntryIds = timeEntries.map((entry) => entry.id);
|
||||
if (organizationId) {
|
||||
await handleApiRequestNotifications(
|
||||
() =>
|
||||
api.deleteTimeEntries(undefined, {
|
||||
queries: {
|
||||
ids: timeEntryIds,
|
||||
},
|
||||
params: {
|
||||
organization: organizationId,
|
||||
},
|
||||
}),
|
||||
'Time entries deleted successfully',
|
||||
'Failed to delete time entries'
|
||||
);
|
||||
await fetchTimeEntries();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
timeEntries,
|
||||
fetchTimeEntries,
|
||||
updateTimeEntry,
|
||||
createTimeEntry,
|
||||
deleteTimeEntry,
|
||||
fetchMoreTimeEntries,
|
||||
allTimeEntriesLoaded,
|
||||
updateTimeEntries,
|
||||
deleteTimeEntries,
|
||||
patchTimeEntries,
|
||||
};
|
||||
}
|
||||
);
|
||||
65
resources/js/utils/useTimeEntriesCalendarQuery.ts
Normal file
65
resources/js/utils/useTimeEntriesCalendarQuery.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { useQuery } from '@tanstack/vue-query';
|
||||
import { api, type TimeEntryResponse } from '@/packages/api/src';
|
||||
import { getCurrentMembershipId, getCurrentOrganizationId } from '@/utils/useUser';
|
||||
import { computed, type Ref } from 'vue';
|
||||
import { getDayJsInstance } from '@/packages/ui/src/utils/time';
|
||||
import { getUserTimezone } from '@/packages/ui/src/utils/settings';
|
||||
|
||||
export function useTimeEntriesCalendarQuery(
|
||||
calendarStart: Ref<Date | undefined>,
|
||||
calendarEnd: Ref<Date | undefined>
|
||||
) {
|
||||
const enableCalendarQuery = computed(() => {
|
||||
return !!getCurrentOrganizationId() && !!calendarStart.value && !!calendarEnd.value;
|
||||
});
|
||||
|
||||
// Calculate expanded date range to include previous and next periods with timezone transformations
|
||||
const expandedDateRange = computed(() => {
|
||||
if (!calendarStart.value || !calendarEnd.value) {
|
||||
return { start: null, end: null };
|
||||
}
|
||||
|
||||
const dayjs = getDayJsInstance();
|
||||
const duration = dayjs(calendarEnd.value).diff(dayjs(calendarStart.value), 'milliseconds');
|
||||
|
||||
// Calculate previous period
|
||||
const previousStart = dayjs(calendarStart.value).subtract(duration, 'milliseconds');
|
||||
// Calculate next period
|
||||
const nextEnd = dayjs(calendarEnd.value).add(duration, 'milliseconds');
|
||||
|
||||
// Apply timezone transformations
|
||||
const formattedStart = previousStart.utc().tz(getUserTimezone(), true).utc().format();
|
||||
const formattedEnd = nextEnd.utc().tz(getUserTimezone(), true).utc().format();
|
||||
|
||||
return {
|
||||
start: formattedStart,
|
||||
end: formattedEnd,
|
||||
};
|
||||
});
|
||||
|
||||
return useQuery<TimeEntryResponse>({
|
||||
queryKey: computed(() => [
|
||||
'timeEntries',
|
||||
'calendar',
|
||||
{
|
||||
start: expandedDateRange.value.start,
|
||||
end: expandedDateRange.value.end,
|
||||
organization: getCurrentOrganizationId(),
|
||||
},
|
||||
]),
|
||||
enabled: enableCalendarQuery,
|
||||
placeholderData: (previousData) => previousData,
|
||||
queryFn: () =>
|
||||
api.getTimeEntries({
|
||||
params: {
|
||||
organization: getCurrentOrganizationId() || '',
|
||||
},
|
||||
queries: {
|
||||
start: expandedDateRange.value.start!,
|
||||
end: expandedDateRange.value.end!,
|
||||
member_id: getCurrentMembershipId(),
|
||||
},
|
||||
}),
|
||||
staleTime: 1000 * 30, // 30 seconds
|
||||
});
|
||||
}
|
||||
51
resources/js/utils/useTimeEntriesInfiniteQuery.ts
Normal file
51
resources/js/utils/useTimeEntriesInfiniteQuery.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { useInfiniteQuery } from '@tanstack/vue-query';
|
||||
import { api } from '@/packages/api/src';
|
||||
import { getCurrentMembershipId, getCurrentOrganizationId } from '@/utils/useUser';
|
||||
import dayjs from 'dayjs';
|
||||
import { computed } from 'vue';
|
||||
|
||||
export function useTimeEntriesInfiniteQuery() {
|
||||
const organizationId = computed(() => getCurrentOrganizationId());
|
||||
const memberId = computed(() => getCurrentMembershipId());
|
||||
|
||||
return useInfiniteQuery({
|
||||
queryKey: computed(() => [
|
||||
'timeEntries',
|
||||
'infinite',
|
||||
{ organizationId: organizationId.value, memberId: memberId.value },
|
||||
]),
|
||||
queryFn: async ({ pageParam }) => {
|
||||
const orgId = organizationId.value;
|
||||
if (!orgId) return { data: [] };
|
||||
|
||||
const queries: Record<string, string | undefined> = {
|
||||
only_full_dates: 'true',
|
||||
member_id: memberId.value,
|
||||
};
|
||||
|
||||
if (pageParam) {
|
||||
queries.end = pageParam;
|
||||
}
|
||||
|
||||
const response = await api.getTimeEntries({
|
||||
params: {
|
||||
organization: orgId,
|
||||
},
|
||||
queries: queries,
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
initialPageParam: undefined as string | undefined,
|
||||
getNextPageParam: (lastPage) => {
|
||||
if (!lastPage?.data || lastPage.data.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const latestTimeEntry = lastPage.data[lastPage.data.length - 1];
|
||||
return dayjs(latestTimeEntry.start).utc().format();
|
||||
},
|
||||
enabled: computed(() => !!organizationId.value),
|
||||
staleTime: 1000 * 30, // 30 seconds
|
||||
gcTime: 1000 * 60 * 10, // 10 minutes
|
||||
});
|
||||
}
|
||||
151
resources/js/utils/useTimeEntriesMutations.ts
Normal file
151
resources/js/utils/useTimeEntriesMutations.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/vue-query';
|
||||
import {
|
||||
api,
|
||||
type CreateTimeEntryBody,
|
||||
type TimeEntry,
|
||||
type UpdateMultipleTimeEntriesChangeset,
|
||||
} from '@/packages/api/src';
|
||||
import { getCurrentMembershipId, getCurrentOrganizationId } from '@/utils/useUser';
|
||||
import { useNotificationsStore } from '@/utils/notification';
|
||||
|
||||
export function useTimeEntriesMutations() {
|
||||
const queryClient = useQueryClient();
|
||||
const { handleApiRequestNotifications } = useNotificationsStore();
|
||||
|
||||
const { mutateAsync: createTimeEntry } = useMutation({
|
||||
mutationFn: async (timeEntry: Omit<CreateTimeEntryBody, 'member_id'>) => {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
const memberId = getCurrentMembershipId();
|
||||
if (organizationId && memberId !== undefined) {
|
||||
const newTimeEntry = {
|
||||
...timeEntry,
|
||||
member_id: memberId,
|
||||
} as CreateTimeEntryBody;
|
||||
|
||||
return await handleApiRequestNotifications(
|
||||
() =>
|
||||
api.createTimeEntry(newTimeEntry, {
|
||||
params: {
|
||||
organization: organizationId,
|
||||
},
|
||||
}),
|
||||
'Time entry created successfully',
|
||||
'Failed to create time entry'
|
||||
);
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: updateTimeEntry } = useMutation({
|
||||
mutationFn: async (timeEntry: TimeEntry) => {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
if (organizationId) {
|
||||
return await handleApiRequestNotifications(
|
||||
() =>
|
||||
api.updateTimeEntry(timeEntry, {
|
||||
params: {
|
||||
organization: organizationId,
|
||||
timeEntry: timeEntry.id,
|
||||
},
|
||||
}),
|
||||
'Time entry updated successfully',
|
||||
'Failed to update time entry'
|
||||
);
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: updateTimeEntries } = useMutation({
|
||||
mutationFn: async ({
|
||||
ids,
|
||||
changes,
|
||||
}: {
|
||||
ids: string[];
|
||||
changes: UpdateMultipleTimeEntriesChangeset;
|
||||
}) => {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
if (organizationId) {
|
||||
return await handleApiRequestNotifications(
|
||||
() =>
|
||||
api.updateMultipleTimeEntries(
|
||||
{
|
||||
ids: ids,
|
||||
changes: changes,
|
||||
},
|
||||
{
|
||||
params: {
|
||||
organization: organizationId,
|
||||
},
|
||||
}
|
||||
),
|
||||
'Time entries updated successfully',
|
||||
'Failed to update time entries'
|
||||
);
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: deleteTimeEntry } = useMutation({
|
||||
mutationFn: async (timeEntryId: string) => {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
if (organizationId) {
|
||||
return await handleApiRequestNotifications(
|
||||
() =>
|
||||
api.deleteTimeEntry(undefined, {
|
||||
params: {
|
||||
organization: organizationId,
|
||||
timeEntry: timeEntryId,
|
||||
},
|
||||
}),
|
||||
'Time entry deleted successfully',
|
||||
'Failed to delete time entry'
|
||||
);
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||
},
|
||||
});
|
||||
|
||||
const { mutateAsync: deleteTimeEntries } = useMutation({
|
||||
mutationFn: async (timeEntries: TimeEntry[]) => {
|
||||
const organizationId = getCurrentOrganizationId();
|
||||
const timeEntryIds = timeEntries.map((entry) => entry.id);
|
||||
if (organizationId) {
|
||||
return await handleApiRequestNotifications(
|
||||
() =>
|
||||
api.deleteTimeEntries(undefined, {
|
||||
queries: {
|
||||
ids: timeEntryIds,
|
||||
},
|
||||
params: {
|
||||
organization: organizationId,
|
||||
},
|
||||
}),
|
||||
'Time entries deleted successfully',
|
||||
'Failed to delete time entries'
|
||||
);
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
createTimeEntry,
|
||||
updateTimeEntry,
|
||||
updateTimeEntries,
|
||||
deleteTimeEntry,
|
||||
deleteTimeEntries,
|
||||
};
|
||||
}
|
||||
21
resources/js/utils/useTimeEntriesReportQuery.ts
Normal file
21
resources/js/utils/useTimeEntriesReportQuery.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { useQuery } from '@tanstack/vue-query';
|
||||
import { api, type TimeEntryResponse } from '@/packages/api/src';
|
||||
import { getCurrentOrganizationId } from '@/utils/useUser';
|
||||
import { computed, type Ref, type ComputedRef, unref } from 'vue';
|
||||
|
||||
export function useTimeEntriesReportQuery(
|
||||
filterParams: Ref<Record<string, unknown>> | ComputedRef<Record<string, unknown>>
|
||||
) {
|
||||
return useQuery<TimeEntryResponse>({
|
||||
queryKey: computed(() => ['timeEntries', 'detailed-report', unref(filterParams)]),
|
||||
enabled: computed(() => !!getCurrentOrganizationId()),
|
||||
queryFn: () =>
|
||||
api.getTimeEntries({
|
||||
params: {
|
||||
organization: getCurrentOrganizationId() || '',
|
||||
},
|
||||
queries: { ...unref(filterParams) },
|
||||
}),
|
||||
staleTime: 1000 * 30, // 30 seconds
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user