summaryrefslogtreecommitdiff
path: root/makima/frontend/src/hooks/useTasks.ts
diff options
context:
space:
mode:
Diffstat (limited to 'makima/frontend/src/hooks/useTasks.ts')
-rw-r--r--makima/frontend/src/hooks/useTasks.ts130
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,
+ };
+}