diff --git a/app/src/components/floating-timer.tsx b/app/src/components/floating-timer.tsx index 01d40c60..a1b90f9d 100644 --- a/app/src/components/floating-timer.tsx +++ b/app/src/components/floating-timer.tsx @@ -55,6 +55,10 @@ export const FloatingTimer = () => { retry: 1, }); const timer = timerResponse?.timer; + const timerForEditDialog = React.useMemo( + () => (timer ? { ...timer, note: userNote } : null), + [timer, userNote], + ); const { mutate: startTimer } = timeTrackingMutations.useStartTimer(); const { mutate: stopTimer, isPending: isStoppingTimer } = @@ -380,12 +384,12 @@ export const FloatingTimer = () => { )} - {timer && ( + {timerForEditDialog && ( )} diff --git a/app/src/lib/api/mutations/time-tracking.ts b/app/src/lib/api/mutations/time-tracking.ts index 7b358b93..1476f022 100644 --- a/app/src/lib/api/mutations/time-tracking.ts +++ b/app/src/lib/api/mutations/time-tracking.ts @@ -2,7 +2,11 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { api } from "../api"; import { DefaultMutationOptions, MutationFnAsync } from "./mutations"; import { z } from "zod"; -import { timeTrackingQueries } from "../queries/time-tracking"; +import { + GetTimerResponse, + TimerResponse, + timeTrackingQueries, +} from "../queries/time-tracking"; import { useTimeTrackingActions } from "@/hooks/useTimeTrackingStore"; export const timeTrackingMutations = { @@ -127,8 +131,24 @@ function useSaveTimer(options?: DefaultMutationOptions) { }); } +function mergeOptimisticTimerEdit( + timer: TimerResponse, + body: EditTimerPayload, +): TimerResponse { + return { + ...timer, + note: body.userNote ?? timer.note, + projectId: body.projectId ?? timer.projectId, + projectName: body.projectName ?? timer.projectName, + activityId: body.activityId ?? timer.activityId, + activityName: body.activityName ?? timer.activityName, + startTime: body.startTime ?? timer.startTime, + }; +} + function useEditTimer(options?: DefaultMutationOptions) { const queryClient = useQueryClient(); + const timerQueryKey = timeTrackingQueries.getTimer().queryKey; return useMutation({ mutationKey: ["time-tracking", "editTimer"], @@ -137,11 +157,48 @@ function useEditTimer(options?: DefaultMutationOptions) { json: body, }), ...options, + onMutate: async (vars) => { + await queryClient.cancelQueries({ + queryKey: timerQueryKey, + }); + + const previousTimer = + queryClient.getQueryData(timerQueryKey); + + queryClient.setQueryData( + timerQueryKey, + (current) => + current?.timer + ? { + ...current, + timer: mergeOptimisticTimerEdit(current.timer, vars), + } + : current, + ); + + const optionsContext = await options?.onMutate?.(vars); + return { previousTimer, optionsContext } satisfies { + previousTimer: GetTimerResponse | undefined; + optionsContext: unknown; + }; + }, onSuccess: (data, v, c) => { + options?.onSuccess?.(data, v, c?.optionsContext); + }, + onError: (error, v, c) => { + if (c?.previousTimer !== undefined) { + queryClient.setQueryData( + timerQueryKey, + c.previousTimer, + ); + } + options?.onError?.(error, v, c?.optionsContext); + }, + onSettled: (data, error, v, c) => { queryClient.invalidateQueries({ - queryKey: timeTrackingQueries.getTimer().queryKey, + queryKey: timerQueryKey, }); - options?.onSuccess?.(data, v, c); + options?.onSettled?.(data, error, v, c?.optionsContext); }, }); }