216 lines
5.1 KiB
TypeScript
216 lines
5.1 KiB
TypeScript
import { useState, useCallback, useEffect } from 'react'
|
|
import type {
|
|
Task,
|
|
CreateTaskRequest,
|
|
UpdateTaskRequest,
|
|
ApiError,
|
|
} from '~/types/task'
|
|
import { TaskStatus } from '~/types/task'
|
|
import { apiClient } from '~/services/api'
|
|
|
|
interface UseTasksState {
|
|
tasks: Task[]
|
|
loading: boolean
|
|
error: string | null
|
|
lastFetch: Date | null
|
|
}
|
|
|
|
interface UseTasksActions {
|
|
fetchTasks: () => Promise<void>
|
|
createTask: (data: CreateTaskRequest) => Promise<Task | null>
|
|
updateTask: (id: string, data: UpdateTaskRequest) => Promise<Task | null>
|
|
deleteTask: (id: string) => Promise<boolean>
|
|
refreshTasks: () => Promise<void>
|
|
clearError: () => void
|
|
getTaskById: (id: string) => Task | undefined
|
|
filterTasksByStatus: (status: TaskStatus) => Task[]
|
|
}
|
|
|
|
interface UseTasksOptions {
|
|
autoFetch?: boolean
|
|
refreshInterval?: number
|
|
initialData?: Task[]
|
|
}
|
|
|
|
export function useTasks(
|
|
options: UseTasksOptions = {}
|
|
): UseTasksState & UseTasksActions {
|
|
const { autoFetch = true, refreshInterval, initialData } = options
|
|
|
|
const [state, setState] = useState<UseTasksState>({
|
|
tasks: initialData || [],
|
|
loading: false,
|
|
error: null,
|
|
lastFetch: initialData ? new Date() : null,
|
|
})
|
|
|
|
const clearError = useCallback(() => {
|
|
setState(prev => ({ ...prev, error: null }))
|
|
}, [])
|
|
|
|
const fetchTasks = useCallback(async () => {
|
|
setState(prev => ({ ...prev, loading: true, error: null }))
|
|
|
|
try {
|
|
const tasks = await apiClient.listTasks()
|
|
setState(prev => ({
|
|
...prev,
|
|
tasks,
|
|
loading: false,
|
|
lastFetch: new Date(),
|
|
}))
|
|
} catch (error) {
|
|
const apiError = error as ApiError
|
|
setState(prev => ({
|
|
...prev,
|
|
loading: false,
|
|
error: apiError.message,
|
|
}))
|
|
}
|
|
}, [])
|
|
|
|
const createTask = useCallback(
|
|
async (data: CreateTaskRequest): Promise<Task | null> => {
|
|
setState(prev => ({ ...prev, loading: true, error: null }))
|
|
|
|
try {
|
|
const newTask = await apiClient.createTask(data)
|
|
|
|
// Add the new task to the beginning of the list (most recent first)
|
|
setState(prev => ({
|
|
...prev,
|
|
tasks: [newTask, ...prev.tasks],
|
|
loading: false,
|
|
}))
|
|
|
|
return newTask
|
|
} catch (error) {
|
|
const apiError = error as ApiError
|
|
setState(prev => ({
|
|
...prev,
|
|
loading: false,
|
|
error: apiError.message,
|
|
}))
|
|
return null
|
|
}
|
|
},
|
|
[]
|
|
)
|
|
|
|
const updateTask = useCallback(
|
|
async (id: string, data: UpdateTaskRequest): Promise<Task | null> => {
|
|
try {
|
|
const updatedTask = await apiClient.updateTask(id, data)
|
|
|
|
// Update the task in the local state immediately
|
|
setState(prev => ({
|
|
...prev,
|
|
tasks: prev.tasks.map(task => (task.id === id ? updatedTask : task)),
|
|
}))
|
|
|
|
return updatedTask
|
|
} catch (error) {
|
|
const apiError = error as ApiError
|
|
setState(prev => ({
|
|
...prev,
|
|
error: apiError.message,
|
|
}))
|
|
return null
|
|
}
|
|
},
|
|
[]
|
|
)
|
|
|
|
const deleteTask = useCallback(async (id: string): Promise<boolean> => {
|
|
try {
|
|
await apiClient.deleteTask(id)
|
|
|
|
// Remove the task from the local state immediately
|
|
setState(prev => ({
|
|
...prev,
|
|
tasks: prev.tasks.filter(task => task.id !== id),
|
|
}))
|
|
|
|
return true
|
|
} catch (error) {
|
|
const apiError = error as ApiError
|
|
setState(prev => ({
|
|
...prev,
|
|
error: apiError.message,
|
|
}))
|
|
return false
|
|
}
|
|
}, [])
|
|
|
|
const refreshTasks = useCallback(async () => {
|
|
// Force refresh without showing loading state if tasks already exist
|
|
const showLoading = state.tasks.length === 0
|
|
|
|
if (showLoading) {
|
|
setState(prev => ({ ...prev, loading: true, error: null }))
|
|
} else {
|
|
setState(prev => ({ ...prev, error: null }))
|
|
}
|
|
|
|
try {
|
|
const tasks = await apiClient.listTasks()
|
|
setState(prev => ({
|
|
...prev,
|
|
tasks,
|
|
loading: false,
|
|
lastFetch: new Date(),
|
|
}))
|
|
} catch (error) {
|
|
const apiError = error as ApiError
|
|
setState(prev => ({
|
|
...prev,
|
|
loading: false,
|
|
error: apiError.message,
|
|
}))
|
|
}
|
|
}, [state.tasks])
|
|
|
|
const getTaskById = useCallback(
|
|
(id: string): Task | undefined => {
|
|
return state.tasks.find(task => task.id === id)
|
|
},
|
|
[state.tasks]
|
|
)
|
|
|
|
const filterTasksByStatus = useCallback(
|
|
(status: TaskStatus): Task[] => {
|
|
return state.tasks.filter(task => task.status === status)
|
|
},
|
|
[state.tasks]
|
|
)
|
|
|
|
// Auto-fetch tasks on mount
|
|
useEffect(() => {
|
|
if (autoFetch) {
|
|
fetchTasks()
|
|
}
|
|
}, [autoFetch, fetchTasks])
|
|
|
|
// Set up refresh interval if specified
|
|
useEffect(() => {
|
|
if (!refreshInterval) return
|
|
|
|
const interval = setInterval(() => {
|
|
refreshTasks()
|
|
}, refreshInterval)
|
|
|
|
return () => clearInterval(interval)
|
|
}, [refreshInterval, refreshTasks])
|
|
|
|
return {
|
|
...state,
|
|
fetchTasks,
|
|
createTask,
|
|
updateTask,
|
|
deleteTask,
|
|
refreshTasks,
|
|
clearError,
|
|
getTaskById,
|
|
filterTasksByStatus,
|
|
}
|
|
}
|