creates application
This commit is contained in:
136
frontend/src/contexts/TimerContext.tsx
Normal file
136
frontend/src/contexts/TimerContext.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
useEffect,
|
||||
useCallback,
|
||||
type ReactNode,
|
||||
} from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { timerApi } from '@/api/timer';
|
||||
import type { OngoingTimer, TimeEntry } from '@/types';
|
||||
|
||||
interface TimerContextType {
|
||||
ongoingTimer: OngoingTimer | null;
|
||||
isLoading: boolean;
|
||||
elapsedSeconds: number;
|
||||
startTimer: (projectId?: string) => Promise<void>;
|
||||
updateTimerProject: (projectId?: string | null) => Promise<void>;
|
||||
stopTimer: (projectId?: string) => Promise<TimeEntry | null>;
|
||||
}
|
||||
|
||||
const TimerContext = createContext<TimerContextType | undefined>(undefined);
|
||||
|
||||
export function TimerProvider({ children }: { children: ReactNode }) {
|
||||
const queryClient = useQueryClient();
|
||||
const [elapsedSeconds, setElapsedSeconds] = useState(0);
|
||||
const [elapsedInterval, setElapsedInterval] = useState<NodeJS.Timeout | null>(null);
|
||||
|
||||
const { data: ongoingTimer, isLoading } = useQuery({
|
||||
queryKey: ['ongoingTimer'],
|
||||
queryFn: timerApi.getOngoing,
|
||||
refetchInterval: 60000, // Refetch every minute to sync with server
|
||||
});
|
||||
|
||||
// Calculate elapsed time
|
||||
useEffect(() => {
|
||||
if (ongoingTimer) {
|
||||
const startTime = new Date(ongoingTimer.startTime).getTime();
|
||||
const now = Date.now();
|
||||
const initialElapsed = Math.floor((now - startTime) / 1000);
|
||||
setElapsedSeconds(initialElapsed);
|
||||
|
||||
// Start interval to update elapsed time
|
||||
const interval = setInterval(() => {
|
||||
setElapsedSeconds(Math.floor((Date.now() - startTime) / 1000));
|
||||
}, 1000);
|
||||
setElapsedInterval(interval);
|
||||
} else {
|
||||
setElapsedSeconds(0);
|
||||
if (elapsedInterval) {
|
||||
clearInterval(elapsedInterval);
|
||||
setElapsedInterval(null);
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (elapsedInterval) {
|
||||
clearInterval(elapsedInterval);
|
||||
}
|
||||
};
|
||||
}, [ongoingTimer]);
|
||||
|
||||
// Start timer mutation
|
||||
const startMutation = useMutation({
|
||||
mutationFn: timerApi.start,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['ongoingTimer'] });
|
||||
},
|
||||
});
|
||||
|
||||
// Update timer mutation
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: timerApi.update,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['ongoingTimer'] });
|
||||
},
|
||||
});
|
||||
|
||||
// Stop timer mutation
|
||||
const stopMutation = useMutation({
|
||||
mutationFn: timerApi.stop,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['ongoingTimer'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['timeEntries'] });
|
||||
},
|
||||
});
|
||||
|
||||
const startTimer = useCallback(
|
||||
async (projectId?: string) => {
|
||||
await startMutation.mutateAsync(projectId);
|
||||
},
|
||||
[startMutation]
|
||||
);
|
||||
|
||||
const updateTimerProject = useCallback(
|
||||
async (projectId?: string | null) => {
|
||||
await updateMutation.mutateAsync(projectId);
|
||||
},
|
||||
[updateMutation]
|
||||
);
|
||||
|
||||
const stopTimer = useCallback(
|
||||
async (projectId?: string): Promise<TimeEntry | null> => {
|
||||
try {
|
||||
const entry = await stopMutation.mutateAsync(projectId);
|
||||
return entry;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[stopMutation]
|
||||
);
|
||||
|
||||
return (
|
||||
<TimerContext.Provider
|
||||
value={{
|
||||
ongoingTimer: ongoingTimer ?? null,
|
||||
isLoading,
|
||||
elapsedSeconds,
|
||||
startTimer,
|
||||
updateTimerProject,
|
||||
stopTimer,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</TimerContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useTimer() {
|
||||
const context = useContext(TimerContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useTimer must be used within a TimerProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
Reference in New Issue
Block a user