diff --git a/src/workspaces/officerdev/src/apps/TodoBoard/index.ts b/src/workspaces/officerdev/src/apps/TodoBoard/index.ts new file mode 100644 index 00000000..ce91692d --- /dev/null +++ b/src/workspaces/officerdev/src/apps/TodoBoard/index.ts @@ -0,0 +1,10 @@ +import type { AppRegistryMeta } from '../../AppRegistry'; +import { SquareKanban } from 'lucide-react'; +import { TodoBoardPanel } from './TodoBoardPanel'; + +export { TodoBoardPanel } from './TodoBoardPanel'; +export { useTodoTree, useTodoItem, useTodoPatch } from './useTodos'; + +export const appRegistryMetas: AppRegistryMeta[] = [ + { key: 'todo-board', name: 'Todo board', icon: SquareKanban, component: TodoBoardPanel }, +]; diff --git a/src/workspaces/officerdev/src/apps/TodoBoard/useTodos.ts b/src/workspaces/officerdev/src/apps/TodoBoard/useTodos.ts new file mode 100644 index 00000000..a3f62239 --- /dev/null +++ b/src/workspaces/officerdev/src/apps/TodoBoard/useTodos.ts @@ -0,0 +1,52 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useClient } from 'hooks/useClient'; + +export type TodoItem = { + id: string; + title: string; + theme: string; + status: string; + created: string; + priority: string; + due: string; + order: number; + file: string; +}; + +export type TodoTheme = { theme: string; heading: string; note: string; items: TodoItem[] }; + +export type TodoPatch = { id: string; status?: string; priority?: string; due?: string; order?: number }; + +export const useTodoTree = () => { + const client = useClient(); + return useQuery({ + queryKey: ['todos'], + queryFn: () => client.get<{ themes: TodoTheme[] }>('/todos'), + // The tree is files on disk that other agents edit while the board is open; refetch keeps the board + // honest without a watcher, which the sync contract allows (todo/README.md — poll is the cheapest + // thing that works). + refetchInterval: 30_000, + }); +}; + +export const useTodoItem = (id: string | null) => { + const client = useClient(); + return useQuery({ + queryKey: ['todos', 'item', id], + queryFn: () => + client.get<{ meta: Record; body: string }>(`/todos/item?id=${encodeURIComponent(id!)}`), + enabled: id !== null, + }); +}; + +export const useTodoPatch = () => { + const client = useClient(); + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (patch: TodoPatch) => client.patch<{ ok: boolean }>('/todos/item', patch), + onSuccess: (_data, patch) => { + void queryClient.invalidateQueries({ queryKey: ['todos'] }); + void queryClient.invalidateQueries({ queryKey: ['todos', 'item', patch.id] }); + }, + }); +};