upgrade inertia v2; add prefetching; migrate queries to tanstack query

vue
This commit is contained in:
Gregor Vostrak
2026-01-09 03:15:32 +01:00
parent 51af3db305
commit 0a6bde8bc6
59 changed files with 712 additions and 392 deletions

View File

@@ -1,26 +1,9 @@
import { useProjectsStore } from '@/utils/useProjects';
import { useTasksStore } from '@/utils/useTasks';
import { useTagsStore } from '@/utils/useTags';
import { useCurrentTimeEntryStore } from '@/utils/useCurrentTimeEntry';
import { useClientsStore } from '@/utils/useClients';
import { useMembersStore } from '@/utils/useMembers';
import { useTimeEntriesStore } from '@/utils/useTimeEntries';
import { canViewClients, canViewMembers } from '@/utils/permissions';
export function initializeStores() {
refreshStores();
}
export function refreshStores() {
useProjectsStore().fetchProjects();
useTasksStore().fetchTasks();
useTagsStore().fetchTags();
// 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();
if (canViewMembers()) {
useMembersStore().fetchMembers();
}
if (canViewClients()) {
useClientsStore().fetchClients();
}
}

View File

@@ -0,0 +1,283 @@
import type { QueryClient } from '@tanstack/vue-query';
import { api } from '@/packages/api/src';
import { getCurrentOrganizationId } from '@/utils/useUser';
import { canViewClients, canViewMembers } from '@/utils/permissions';
/**
* Route patterns mapped to their prefetch functions.
* Each function receives the QueryClient and prefetches relevant data.
*/
const routePrefetchers: Record<string, (queryClient: QueryClient) => void> = {
'/': (queryClient) => {
prefetchDashboard(queryClient);
},
'/dashboard': (queryClient) => {
prefetchDashboard(queryClient);
},
'/time': (queryClient) => {
prefetchProjects(queryClient);
prefetchTasks(queryClient);
prefetchTags(queryClient);
prefetchClients(queryClient);
},
'/calendar': (queryClient) => {
prefetchProjects(queryClient);
prefetchTasks(queryClient);
prefetchTags(queryClient);
prefetchClients(queryClient);
},
'/projects': (queryClient) => {
prefetchProjects(queryClient);
prefetchClients(queryClient);
},
'/clients': (queryClient) => {
prefetchClients(queryClient);
},
'/tags': (queryClient) => {
prefetchTags(queryClient);
},
'/members': (queryClient) => {
prefetchMembers(queryClient);
},
'/reporting': (queryClient) => {
prefetchProjects(queryClient);
prefetchTags(queryClient);
prefetchClients(queryClient);
prefetchMembers(queryClient);
},
'/reporting/detailed': (queryClient) => {
prefetchProjects(queryClient);
prefetchTasks(queryClient);
prefetchTags(queryClient);
prefetchClients(queryClient);
prefetchMembers(queryClient);
},
'/reporting/shared': (queryClient) => {
prefetchReports(queryClient);
},
};
function prefetchDashboard(queryClient: QueryClient) {
const organizationId = getCurrentOrganizationId();
if (!organizationId) return;
// Prefetch all dashboard card data
queryClient.prefetchQuery({
queryKey: ['timeEntries', organizationId],
queryFn: () =>
api.getTimeEntries({
params: { organization: organizationId },
queries: { limit: 10, offset: 0, only_full_dates: 'true' },
}),
staleTime: 30000,
});
queryClient.prefetchQuery({
queryKey: ['lastSevenDays', organizationId],
queryFn: () => api.lastSevenDays({ params: { organization: organizationId } }),
staleTime: 30000,
});
queryClient.prefetchQuery({
queryKey: ['dailyTrackedHours', organizationId],
queryFn: () => api.dailyTrackedHours({ params: { organization: organizationId } }),
staleTime: 30000,
});
queryClient.prefetchQuery({
queryKey: ['weeklyProjectOverview', organizationId],
queryFn: () => api.weeklyProjectOverview({ params: { organization: organizationId } }),
staleTime: 30000,
});
queryClient.prefetchQuery({
queryKey: ['totalWeeklyTime', organizationId],
queryFn: () => api.totalWeeklyTime({ params: { organization: organizationId } }),
staleTime: 30000,
});
queryClient.prefetchQuery({
queryKey: ['totalWeeklyBillableTime', organizationId],
queryFn: () => api.totalWeeklyBillableTime({ params: { organization: organizationId } }),
staleTime: 30000,
});
queryClient.prefetchQuery({
queryKey: ['totalWeeklyBillableAmount', organizationId],
queryFn: () => api.totalWeeklyBillableAmount({ params: { organization: organizationId } }),
staleTime: 30000,
});
queryClient.prefetchQuery({
queryKey: ['weeklyHistory', organizationId],
queryFn: () => api.weeklyHistory({ params: { organization: organizationId } }),
staleTime: 30000,
});
// Prefetch team activity only if user has permission
if (canViewMembers()) {
queryClient.prefetchQuery({
queryKey: ['latestTeamActivity', organizationId],
queryFn: () => api.latestTeamActivity({ params: { organization: organizationId } }),
staleTime: 30000,
});
}
}
function prefetchProjects(queryClient: QueryClient) {
const organizationId = getCurrentOrganizationId();
if (!organizationId) return;
queryClient.prefetchQuery({
queryKey: ['projects'],
queryFn: () =>
api.getProjects({
params: { organization: organizationId },
queries: { archived: 'all' },
}),
staleTime: 30000, // Consider fresh for 30 seconds
});
}
function prefetchTasks(queryClient: QueryClient) {
const organizationId = getCurrentOrganizationId();
if (!organizationId) return;
queryClient.prefetchQuery({
queryKey: ['tasks'],
queryFn: () =>
api.getTasks({
params: { organization: organizationId },
queries: { done: 'all' },
}),
staleTime: 30000,
});
}
function prefetchTags(queryClient: QueryClient) {
const organizationId = getCurrentOrganizationId();
if (!organizationId) return;
queryClient.prefetchQuery({
queryKey: ['tags'],
queryFn: () =>
api.getTags({
params: { organization: organizationId },
}),
staleTime: 30000,
});
}
function prefetchClients(queryClient: QueryClient) {
const organizationId = getCurrentOrganizationId();
if (!organizationId || !canViewClients()) return;
queryClient.prefetchQuery({
queryKey: ['clients'],
queryFn: () =>
api.getClients({
params: { organization: organizationId },
queries: { archived: 'all' },
}),
staleTime: 30000,
});
}
function prefetchMembers(queryClient: QueryClient) {
const organizationId = getCurrentOrganizationId();
if (!organizationId || !canViewMembers()) return;
queryClient.prefetchQuery({
queryKey: ['members'],
queryFn: () =>
api.getMembers({
params: { organization: organizationId },
}),
staleTime: 30000,
});
}
function prefetchReports(queryClient: QueryClient) {
const organizationId = getCurrentOrganizationId();
if (!organizationId) return;
queryClient.prefetchQuery({
queryKey: ['reports', 1],
queryFn: () =>
api.getReports({
params: { organization: organizationId },
}),
staleTime: 30000,
});
}
function prefetchProjectMembers(queryClient: QueryClient, projectId: string) {
const organizationId = getCurrentOrganizationId();
if (!organizationId || !canViewMembers()) return;
queryClient.prefetchQuery({
queryKey: ['projectMembers', projectId],
queryFn: () =>
api.getProjectMembers({
params: { organization: organizationId, project: projectId },
}),
staleTime: 30000,
});
}
/**
* Matches a URL to find the appropriate prefetcher.
* Handles both exact matches and pattern matching for dynamic routes.
*/
function findPrefetcher(url: string): ((queryClient: QueryClient) => void) | undefined {
// Extract pathname from URL
const pathname = url.startsWith('http') ? new URL(url).pathname : url.split('?')[0];
// Try exact match first
if (routePrefetchers[pathname]) {
return routePrefetchers[pathname];
}
// Try pattern matching for dynamic routes like /projects/{id}
const projectMatch = pathname.match(/^\/projects\/([^/]+)$/);
if (projectMatch) {
const projectId = projectMatch[1];
return (queryClient) => {
prefetchProjects(queryClient);
prefetchTasks(queryClient);
prefetchProjectMembers(queryClient, projectId);
};
}
return undefined;
}
/**
* Sets up Inertia prefetch event listener to warm TanStack Query cache.
* Call this once during app initialization.
*/
export function setupPrefetching(queryClient: QueryClient) {
// Listen for the 'prefetching' event which fires when Inertia starts prefetching a page
// The event detail contains the visit object with the URL being prefetched
document.addEventListener('inertia:prefetching', ((event: CustomEvent) => {
const visit = event.detail?.visit;
if (!visit?.url) return;
const url = visit.url.href || visit.url.toString();
const prefetcher = findPrefetcher(url);
if (prefetcher) {
prefetcher(queryClient);
}
}) as EventListener);
}

View File

@@ -1,37 +1,13 @@
import { defineStore } from 'pinia';
import { api } from '@/packages/api/src';
import { computed, ref } from 'vue';
import type {
CreateClientBody,
ClientIndexResponse,
Client,
UpdateClientBody,
} from '@/packages/api/src';
import type { CreateClientBody, Client, UpdateClientBody } from '@/packages/api/src';
import { getCurrentOrganizationId } from '@/utils/useUser';
import { useNotificationsStore } from '@/utils/notification';
import { useQueryClient } from '@tanstack/vue-query';
export const useClientsStore = defineStore('clients', () => {
const clientResponse = ref<ClientIndexResponse | null>(null);
const { handleApiRequestNotifications } = useNotificationsStore();
async function fetchClients() {
const organization = getCurrentOrganizationId();
if (organization) {
clientResponse.value = await handleApiRequestNotifications(
() =>
api.getClients({
queries: {
archived: 'all',
},
params: {
organization: organization,
},
}),
undefined,
'Failed to fetch clients'
);
}
}
const queryClient = useQueryClient();
async function createClient(clientBody: CreateClientBody): Promise<Client | undefined> {
const organization = getCurrentOrganizationId();
@@ -46,7 +22,7 @@ export const useClientsStore = defineStore('clients', () => {
'Client created successfully',
'Failed to create client'
);
await fetchClients();
queryClient.invalidateQueries({ queryKey: ['clients'] });
return response?.data;
}
}
@@ -65,7 +41,7 @@ export const useClientsStore = defineStore('clients', () => {
'Client updated successfully',
'Failed to update client'
);
await fetchClients();
queryClient.invalidateQueries({ queryKey: ['clients'] });
}
}
@@ -83,13 +59,9 @@ export const useClientsStore = defineStore('clients', () => {
'Client deleted successfully',
'Failed to delete client'
);
await fetchClients();
queryClient.invalidateQueries({ queryKey: ['clients'] });
}
}
const clients = computed<Client[]>(() => {
return clientResponse.value?.data || [];
});
return { clients, fetchClients, createClient, deleteClient, updateClient };
return { createClient, deleteClient, updateClient };
});

View File

@@ -0,0 +1,35 @@
import { useQuery, useQueryClient } from '@tanstack/vue-query';
import { api } from '@/packages/api/src';
import { getCurrentOrganizationId } from '@/utils/useUser';
import type { Client } from '@/packages/api/src';
import { computed } from 'vue';
export function useClientsQuery() {
const queryClient = useQueryClient();
const query = useQuery({
queryKey: ['clients'],
queryFn: async () => {
const organizationId = getCurrentOrganizationId();
if (!organizationId) throw new Error('No organization');
return api.getClients({
params: { organization: organizationId },
queries: { archived: 'all' },
});
},
enabled: () => !!getCurrentOrganizationId(),
staleTime: 1000 * 30, // 30 seconds
});
const clients = computed<Client[]>(() => query.data.value?.data ?? []);
const invalidateClients = () => {
queryClient.invalidateQueries({ queryKey: ['clients'] });
};
return {
...query,
clients,
invalidateClients,
};
}

View File

@@ -1,31 +1,15 @@
import { defineStore } from 'pinia';
import { api } from '@/packages/api/src';
import { computed, ref } from 'vue';
import type { Member, MemberIndexResponse, UpdateMemberBody } from '@/packages/api/src';
import type { UpdateMemberBody } from '@/packages/api/src';
import { getCurrentOrganizationId } from '@/utils/useUser';
import { useNotificationsStore } from '@/utils/notification';
import { useQueryClient } from '@tanstack/vue-query';
export type MemberBillableKey = 'default-rate' | 'custom-rate';
export const useMembersStore = defineStore('members', () => {
const membersResponse = ref<MemberIndexResponse | null>(null);
const { handleApiRequestNotifications } = useNotificationsStore();
async function fetchMembers() {
const organization = getCurrentOrganizationId();
if (organization) {
membersResponse.value = await handleApiRequestNotifications(
() =>
api.getMembers({
params: {
organization: organization,
},
}),
undefined,
'Failed to fetch members'
);
}
}
const queryClient = useQueryClient();
async function removeMember(membershipId: string) {
const organization = getCurrentOrganizationId();
@@ -41,7 +25,7 @@ export const useMembersStore = defineStore('members', () => {
'Member deleted successfully',
'Failed to delete member'
);
await fetchMembers();
queryClient.invalidateQueries({ queryKey: ['members'] });
}
}
@@ -59,13 +43,9 @@ export const useMembersStore = defineStore('members', () => {
'Member updated successfully',
'Failed to update member'
);
await fetchMembers();
queryClient.invalidateQueries({ queryKey: ['members'] });
}
}
const members = computed<Member[]>(() => {
return membersResponse.value?.data || [];
});
return { members, fetchMembers, removeMember, updateMember };
return { removeMember, updateMember };
});

View File

@@ -0,0 +1,34 @@
import { useQuery, useQueryClient } from '@tanstack/vue-query';
import { api } from '@/packages/api/src';
import { getCurrentOrganizationId } from '@/utils/useUser';
import type { Member } from '@/packages/api/src';
import { computed } from 'vue';
export function useMembersQuery() {
const queryClient = useQueryClient();
const query = useQuery({
queryKey: ['members'],
queryFn: async () => {
const organizationId = getCurrentOrganizationId();
if (!organizationId) throw new Error('No organization');
return api.getMembers({
params: { organization: organizationId },
});
},
enabled: () => !!getCurrentOrganizationId(),
staleTime: 1000 * 30, // 30 seconds
});
const members = computed<Member[]>(() => query.data.value?.data ?? []);
const invalidateMembers = () => {
queryClient.invalidateQueries({ queryKey: ['members'] });
};
return {
...query,
members,
invalidateMembers,
};
}

View File

@@ -1,35 +1,13 @@
import { defineStore } from 'pinia';
import { api } from '@/packages/api/src';
import { computed, ref } from 'vue';
import type {
CreateProjectMemberBody,
ProjectMember,
ProjectMemberResponse,
UpdateProjectMemberBody,
} from '@/packages/api/src';
import type { CreateProjectMemberBody, UpdateProjectMemberBody } from '@/packages/api/src';
import { getCurrentOrganizationId } from '@/utils/useUser';
import { useNotificationsStore } from '@/utils/notification';
import { useQueryClient } from '@tanstack/vue-query';
export const useProjectMembersStore = defineStore('project-members', () => {
const projectMemberResponse = ref<ProjectMemberResponse | null>(null);
const { handleApiRequestNotifications } = useNotificationsStore();
async function fetchProjectMembers(projectId: string) {
const organization = getCurrentOrganizationId();
if (organization) {
projectMemberResponse.value = await handleApiRequestNotifications(
() =>
api.getProjectMembers({
params: {
organization: organization,
project: projectId,
},
}),
undefined,
'Failed to fetch project members'
);
}
}
const queryClient = useQueryClient();
async function createProjectMember(
projectId: string,
@@ -48,7 +26,7 @@ export const useProjectMembersStore = defineStore('project-members', () => {
'Project member added successfully',
'Failed to add project member'
);
await fetchProjectMembers(projectId);
queryClient.invalidateQueries({ queryKey: ['projectMembers', projectId] });
}
}
@@ -69,7 +47,11 @@ export const useProjectMembersStore = defineStore('project-members', () => {
'Project member updated successfully',
'Failed to update project member'
);
await fetchProjectMembers(response.data.project_id);
if (response?.data?.project_id) {
queryClient.invalidateQueries({
queryKey: ['projectMembers', response.data.project_id],
});
}
}
}
@@ -87,15 +69,11 @@ export const useProjectMembersStore = defineStore('project-members', () => {
'Project member removed successfully',
'Failed to remove project member'
);
await fetchProjectMembers(projectId);
queryClient.invalidateQueries({ queryKey: ['projectMembers', projectId] });
}
}
const projectMembers = computed<ProjectMember[]>(() => projectMemberResponse.value?.data || []);
return {
projectMembers,
fetchProjectMembers,
createProjectMember,
deleteProjectMember,
updateProjectMember,

View File

@@ -0,0 +1,39 @@
import { useQuery, useQueryClient } from '@tanstack/vue-query';
import { api } from '@/packages/api/src';
import { getCurrentOrganizationId } from '@/utils/useUser';
import type { ProjectMember } from '@/packages/api/src';
import { computed, type Ref } from 'vue';
export function useProjectMembersQuery(projectId: Ref<string | null> | string) {
const queryClient = useQueryClient();
const projectIdValue = computed(() => {
return typeof projectId === 'string' ? projectId : projectId.value;
});
const query = useQuery({
queryKey: ['projectMembers', projectIdValue],
queryFn: async () => {
const organizationId = getCurrentOrganizationId();
const pid = projectIdValue.value;
if (!organizationId || !pid) throw new Error('No organization or project');
return api.getProjectMembers({
params: { organization: organizationId, project: pid },
});
},
enabled: () => !!getCurrentOrganizationId() && !!projectIdValue.value,
staleTime: 1000 * 30, // 30 seconds
});
const projectMembers = computed<ProjectMember[]>(() => query.data.value?.data ?? []);
const invalidateProjectMembers = () => {
queryClient.invalidateQueries({ queryKey: ['projectMembers', projectIdValue.value] });
};
return {
...query,
projectMembers,
invalidateProjectMembers,
};
}

View File

@@ -18,6 +18,7 @@ export function useProjectsQuery() {
});
},
enabled: () => !!getCurrentOrganizationId(),
staleTime: 1000 * 30, // 30 seconds
});
const projects = computed<Project[]>(() => query.data.value?.data ?? []);

View File

@@ -1,4 +1,4 @@
import { defineStore, storeToRefs } from 'pinia';
import { defineStore } from 'pinia';
import { api } from '@/packages/api/src';
import { type Component, computed, ref } from 'vue';
import type {
@@ -8,11 +8,11 @@ import type {
} from '@/packages/api/src';
import { getCurrentOrganizationId, getCurrentRole, getCurrentUser } from '@/utils/useUser';
import { useNotificationsStore } from '@/utils/notification';
import { useProjectsStore } from '@/utils/useProjects';
import { useMembersStore } from '@/utils/useMembers';
import { useTasksStore } from '@/utils/useTasks';
import { useClientsStore } from '@/utils/useClients';
import { useTagsStore } from '@/utils/useTags';
import { useProjectsQuery } from '@/utils/useProjectsQuery';
import { useMembersQuery } from '@/utils/useMembersQuery';
import { useTasksQuery } from '@/utils/useTasksQuery';
import { useClientsQuery } from '@/utils/useClientsQuery';
import { useTagsQuery } from '@/utils/useTagsQuery';
import { CheckCircleIcon, UserCircleIcon, UserGroupIcon } from '@heroicons/vue/20/solid';
import { DocumentTextIcon, FolderIcon } from '@heroicons/vue/16/solid';
import BillableIcon from '@/packages/ui/src/Icons/BillableIcon.vue';
@@ -32,6 +32,13 @@ export const useReportingStore = defineStore('reporting', () => {
const { handleApiRequestNotifications } = useNotificationsStore();
// Cache query composables to avoid creating new subscriptions on every call
const { projects } = useProjectsQuery();
const { members } = useMembersQuery();
const { tasks } = useTasksQuery();
const { clients } = useClientsQuery();
const { tags } = useTagsQuery();
async function fetchGraphReporting(params: AggregatedTimeEntriesQueryParams) {
const organization = getCurrentOrganizationId();
if (organization) {
@@ -93,31 +100,21 @@ export const useReportingStore = defineStore('reporting', () => {
}
if (type === 'project') {
const projectsStore = useProjectsStore();
const { projects } = storeToRefs(projectsStore);
return projects.value.find((project) => project.id === key)?.name;
}
if (type === 'user') {
if (getCurrentRole() === 'employee') {
return getCurrentUser().name;
}
const memberStore = useMembersStore();
const { members } = storeToRefs(memberStore);
return members.value.find((member) => member.user_id === key)?.name;
}
if (type === 'task') {
const taskStore = useTasksStore();
const { tasks } = storeToRefs(taskStore);
return tasks.value.find((task) => task.id === key)?.name;
}
if (type === 'client') {
const clientsStore = useClientsStore();
const { clients } = storeToRefs(clientsStore);
return clients.value.find((client) => client.id === key)?.name;
}
if (type === 'tag') {
const tagsStore = useTagsStore();
const { tags } = storeToRefs(tagsStore);
return tags.value.find((tag) => tag.id === key)?.name;
}
if (type === 'billable') {

View File

@@ -1,33 +1,13 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
import type { Tag } from '@/packages/api/src';
import { getCurrentOrganizationId } from '@/utils/useUser';
import { api } from '@/packages/api/src';
import { useNotificationsStore } from '@/utils/notification';
import { useQueryClient } from '@tanstack/vue-query';
export const useTagsStore = defineStore('tags', () => {
const tags = ref<Tag[]>([]);
const { handleApiRequestNotifications } = useNotificationsStore();
async function fetchTags() {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
const response = await handleApiRequestNotifications(
() =>
api.getTags({
params: {
organization: organizationId,
},
}),
undefined,
'Failed to fetch tags'
);
if (response?.data) {
tags.value = response.data;
}
} else {
throw new Error('Failed to fetch current tags because organization ID is missing.');
}
}
const queryClient = useQueryClient();
async function deleteTag(tagId: string) {
const organizationId = getCurrentOrganizationId();
@@ -43,11 +23,11 @@ export const useTagsStore = defineStore('tags', () => {
'Tag deleted successfully',
'Failed to delete tag'
);
await fetchTags();
queryClient.invalidateQueries({ queryKey: ['tags'] });
}
}
async function createTag(name: string) {
async function createTag(name: string): Promise<Tag | undefined> {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
const response = await handleApiRequestNotifications(
@@ -66,7 +46,7 @@ export const useTagsStore = defineStore('tags', () => {
'Failed to create tag'
);
if (response?.data) {
tags.value.unshift(response.data);
queryClient.invalidateQueries({ queryKey: ['tags'] });
return response.data;
}
} else {
@@ -74,5 +54,5 @@ export const useTagsStore = defineStore('tags', () => {
}
}
return { tags, fetchTags, createTag, deleteTag };
return { createTag, deleteTag };
});

View File

@@ -0,0 +1,34 @@
import { useQuery, useQueryClient } from '@tanstack/vue-query';
import { api } from '@/packages/api/src';
import { getCurrentOrganizationId } from '@/utils/useUser';
import type { Tag } from '@/packages/api/src';
import { computed } from 'vue';
export function useTagsQuery() {
const queryClient = useQueryClient();
const query = useQuery({
queryKey: ['tags'],
queryFn: async () => {
const organizationId = getCurrentOrganizationId();
if (!organizationId) throw new Error('No organization');
return api.getTags({
params: { organization: organizationId },
});
},
enabled: () => !!getCurrentOrganizationId(),
staleTime: 1000 * 30, // 30 seconds
});
const tags = computed<Tag[]>(() => query.data.value?.data ?? []);
const invalidateTags = () => {
queryClient.invalidateQueries({ queryKey: ['tags'] });
};
return {
...query,
tags,
invalidateTags,
};
}

View File

@@ -1,32 +1,13 @@
import { defineStore } from 'pinia';
import { getCurrentOrganizationId } from '@/utils/useUser';
import { api } from '@/packages/api/src';
import { reactive, ref } from 'vue';
import type { CreateTaskBody, Task, UpdateTaskBody } from '@/packages/api/src';
import type { CreateTaskBody, UpdateTaskBody } from '@/packages/api/src';
import { useNotificationsStore } from '@/utils/notification';
import { useQueryClient } from '@tanstack/vue-query';
export const useTasksStore = defineStore('tasks', () => {
const tasks = ref<Task[]>(reactive([]));
const { handleApiRequestNotifications } = useNotificationsStore();
async function fetchTasks() {
const organizationId = getCurrentOrganizationId();
if (organizationId) {
const tasksResponse = await handleApiRequestNotifications(() =>
api.getTasks({
params: {
organization: organizationId,
},
queries: {
done: 'all',
},
})
);
if (tasksResponse?.data) {
tasks.value = tasksResponse.data;
}
}
}
const queryClient = useQueryClient();
async function updateTask(taskId: string, taskBody: UpdateTaskBody) {
const organizationId = getCurrentOrganizationId();
@@ -42,7 +23,7 @@ export const useTasksStore = defineStore('tasks', () => {
'Task updated successfully',
'Failed to update task'
);
await fetchTasks();
queryClient.invalidateQueries({ queryKey: ['tasks'] });
}
}
@@ -59,7 +40,7 @@ export const useTasksStore = defineStore('tasks', () => {
'Task created successfully',
'Failed to create task'
);
await fetchTasks();
queryClient.invalidateQueries({ queryKey: ['tasks'] });
}
}
@@ -77,13 +58,11 @@ export const useTasksStore = defineStore('tasks', () => {
'Task deleted successfully',
'Failed to delete task'
);
await fetchTasks();
queryClient.invalidateQueries({ queryKey: ['tasks'] });
}
}
return {
tasks,
fetchTasks,
updateTask,
createTask,
deleteTask,

View File

@@ -0,0 +1,35 @@
import { useQuery, useQueryClient } from '@tanstack/vue-query';
import { api } from '@/packages/api/src';
import { getCurrentOrganizationId } from '@/utils/useUser';
import type { Task } from '@/packages/api/src';
import { computed } from 'vue';
export function useTasksQuery() {
const queryClient = useQueryClient();
const query = useQuery({
queryKey: ['tasks'],
queryFn: async () => {
const organizationId = getCurrentOrganizationId();
if (!organizationId) throw new Error('No organization');
return api.getTasks({
params: { organization: organizationId },
queries: { done: 'all' },
});
},
enabled: () => !!getCurrentOrganizationId(),
staleTime: 1000 * 30, // 30 seconds
});
const tasks = computed<Task[]>(() => query.data.value?.data ?? []);
const invalidateTasks = () => {
queryClient.invalidateQueries({ queryKey: ['tasks'] });
};
return {
...query,
tasks,
invalidateTasks,
};
}