diff options
| author | soryu <soryu@soryu.co> | 2026-01-06 04:08:11 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-01-11 03:01:13 +0000 |
| commit | 8b17a175c3e7e27b789812eba4e3cd760beadb10 (patch) | |
| tree | 7864dcaa2fa9db47fdfd4e8bfdb0b1dde832aa33 /makima/frontend/src/hooks/useTasks.ts | |
| parent | f79c416c58557d2f946aa5332989afdfa8c021cd (diff) | |
| download | soryu-8b17a175c3e7e27b789812eba4e3cd760beadb10.tar.gz soryu-8b17a175c3e7e27b789812eba4e3cd760beadb10.zip | |
Initial Control system
Diffstat (limited to 'makima/frontend/src/hooks/useTasks.ts')
| -rw-r--r-- | makima/frontend/src/hooks/useTasks.ts | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/makima/frontend/src/hooks/useTasks.ts b/makima/frontend/src/hooks/useTasks.ts new file mode 100644 index 0000000..6e6c992 --- /dev/null +++ b/makima/frontend/src/hooks/useTasks.ts @@ -0,0 +1,130 @@ +import { useState, useCallback, useEffect } from "react"; +import { + listTasks, + getTask, + createTask, + updateTask, + deleteTask, + VersionConflictError, + type TaskSummary, + type TaskWithSubtasks, + type CreateTaskRequest, + type UpdateTaskRequest, +} from "../lib/api"; + +export interface ConflictState { + hasConflict: boolean; + expectedVersion: number; + actualVersion: number; +} + +export function useTasks() { + const [tasks, setTasks] = useState<TaskSummary[]>([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState<string | null>(null); + const [conflict, setConflict] = useState<ConflictState | null>(null); + + const fetchTasks = useCallback(async () => { + setLoading(true); + setError(null); + try { + const response = await listTasks(); + setTasks(response.tasks); + } catch (e) { + setError(e instanceof Error ? e.message : "Failed to fetch tasks"); + } finally { + setLoading(false); + } + }, []); + + const fetchTask = useCallback( + async (id: string): Promise<TaskWithSubtasks | null> => { + setError(null); + try { + return await getTask(id); + } catch (e) { + setError(e instanceof Error ? e.message : "Failed to fetch task"); + return null; + } + }, + [] + ); + + const saveTask = useCallback( + async (data: CreateTaskRequest): Promise<TaskWithSubtasks | null> => { + setError(null); + try { + const task = await createTask(data); + await fetchTasks(); // Refresh list + // Return as TaskWithSubtasks + return { ...task, subtasks: [] }; + } catch (e) { + setError(e instanceof Error ? e.message : "Failed to save task"); + return null; + } + }, + [fetchTasks] + ); + + const editTask = useCallback( + async (id: string, data: UpdateTaskRequest): Promise<TaskWithSubtasks | null> => { + setError(null); + setConflict(null); + try { + await updateTask(id, data); + await fetchTasks(); // Refresh list + // Re-fetch to get subtasks + return await getTask(id); + } catch (e) { + if (e instanceof VersionConflictError) { + setConflict({ + hasConflict: true, + expectedVersion: e.expectedVersion, + actualVersion: e.actualVersion, + }); + return null; + } + setError(e instanceof Error ? e.message : "Failed to update task"); + return null; + } + }, + [fetchTasks] + ); + + const clearConflict = useCallback(() => { + setConflict(null); + }, []); + + const removeTask = useCallback( + async (id: string): Promise<boolean> => { + setError(null); + try { + await deleteTask(id); + await fetchTasks(); // Refresh list + return true; + } catch (e) { + setError(e instanceof Error ? e.message : "Failed to delete task"); + return false; + } + }, + [fetchTasks] + ); + + // Initial fetch + useEffect(() => { + fetchTasks(); + }, [fetchTasks]); + + return { + tasks, + loading, + error, + conflict, + clearConflict, + fetchTasks, + fetchTask, + saveTask, + editTask, + removeTask, + }; +} |
