diff --git a/src/frontend/src/hooks/useApi.ts b/src/frontend/src/hooks/useApi.ts new file mode 100644 index 0000000..0bc85e6 --- /dev/null +++ b/src/frontend/src/hooks/useApi.ts @@ -0,0 +1,84 @@ +// 自定义 hooks + +import { useState, useEffect, useCallback, useRef } from 'react'; +import * as api from '../api'; +import type { Task, Project, DaemonStatus } from '../types'; + +export function useProjects() { + const [projects, setProjects] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const refresh = useCallback(async () => { + try { + setLoading(true); + const data = await api.listProjects(); + setProjects(data); + setError(null); + } catch (e) { + setError(String(e)); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { refresh(); }, [refresh]); + return { projects, loading, error, refresh }; +} + +export function useTasks(projectId: string | null) { + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const refresh = useCallback(async () => { + if (!projectId) return; + try { + setLoading(true); + const data = await api.listTasks(projectId); + setTasks(data); + setError(null); + } catch (e) { + setError(String(e)); + } finally { + setLoading(false); + } + }, [projectId]); + + useEffect(() => { refresh(); }, [refresh]); + return { tasks, loading, error, refresh }; +} + +export function useDaemonStatus() { + const [status, setStatus] = useState(null); + + const refresh = useCallback(async () => { + try { + const data = await api.getDaemonStatus(); + setStatus(data); + } catch { + // Daemon may not be running + } + }, []); + + useEffect(() => { + refresh(); + const interval = setInterval(refresh, 10000); + return () => clearInterval(interval); + }, [refresh]); + + return { status, refresh }; +} + +export function useSSE(onEvent: (data: unknown) => void) { + const esRef = useRef(null); + + useEffect(() => { + esRef.current = api.createEventSource(onEvent, () => { + // Auto-reconnect is handled by EventSource + }); + return () => { + esRef.current?.close(); + }; + }, [onEvent]); +}