99 lines
3.1 KiB
TypeScript
99 lines
3.1 KiB
TypeScript
import { useState, useCallback, useEffect } from "react";
|
|
import type { TaskDetail, TaskCreateInput, Task } from "@hai/core";
|
|
import { fetchConfig, fetchSettings, updateSettings } from "./api";
|
|
import { Header } from "./components/Header";
|
|
import { Board } from "./components/Board";
|
|
import { TaskDetailModal } from "./components/TaskDetailModal";
|
|
import { SettingsModal } from "./components/SettingsModal";
|
|
import { ToastContainer } from "./components/ToastContainer";
|
|
import { useTasks } from "./hooks/useTasks";
|
|
import { ToastProvider, useToast } from "./hooks/useToast";
|
|
|
|
function AppInner() {
|
|
const [isCreating, setIsCreating] = useState(false);
|
|
const [detailTask, setDetailTask] = useState<TaskDetail | null>(null);
|
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
const [maxConcurrent, setMaxConcurrent] = useState(2);
|
|
const [autoMerge, setAutoMerge] = useState(false);
|
|
const { tasks, createTask, moveTask, deleteTask, mergeTask } = useTasks();
|
|
|
|
useEffect(() => {
|
|
fetchConfig()
|
|
.then((cfg) => setMaxConcurrent(cfg.maxConcurrent))
|
|
.catch(() => {/* keep default */});
|
|
fetchSettings()
|
|
.then((s) => setAutoMerge(!!s.autoMerge))
|
|
.catch(() => {/* keep default */});
|
|
}, []);
|
|
const { toasts, addToast, removeToast } = useToast();
|
|
|
|
const handleCreateOpen = useCallback(() => setIsCreating(true), []);
|
|
const handleCancelCreate = useCallback(() => setIsCreating(false), []);
|
|
|
|
const handleCreateTask = useCallback(
|
|
async (input: TaskCreateInput): Promise<Task> => {
|
|
const task = await createTask({ ...input, column: "triage" });
|
|
setIsCreating(false);
|
|
return task;
|
|
},
|
|
[createTask],
|
|
);
|
|
|
|
const handleToggleAutoMerge = useCallback(async () => {
|
|
const next = !autoMerge;
|
|
setAutoMerge(next);
|
|
try {
|
|
await updateSettings({ autoMerge: next });
|
|
} catch {
|
|
setAutoMerge(!next); // revert on failure
|
|
}
|
|
}, [autoMerge]);
|
|
|
|
const handleDetailOpen = useCallback((task: TaskDetail) => {
|
|
setDetailTask(task);
|
|
}, []);
|
|
|
|
const handleDetailClose = useCallback(() => setDetailTask(null), []);
|
|
|
|
return (
|
|
<>
|
|
<Header onOpenSettings={() => setSettingsOpen(true)} />
|
|
<Board
|
|
tasks={tasks}
|
|
maxConcurrent={maxConcurrent}
|
|
onMoveTask={moveTask}
|
|
onOpenDetail={handleDetailOpen}
|
|
addToast={addToast}
|
|
isCreating={isCreating}
|
|
onCancelCreate={handleCancelCreate}
|
|
onCreateTask={handleCreateTask}
|
|
onNewTask={handleCreateOpen}
|
|
autoMerge={autoMerge}
|
|
onToggleAutoMerge={handleToggleAutoMerge}
|
|
/>
|
|
{detailTask && (
|
|
<TaskDetailModal
|
|
task={detailTask}
|
|
onClose={handleDetailClose}
|
|
onMoveTask={moveTask}
|
|
onDeleteTask={deleteTask}
|
|
onMergeTask={mergeTask}
|
|
addToast={addToast}
|
|
/>
|
|
)}
|
|
{settingsOpen && (
|
|
<SettingsModal onClose={() => setSettingsOpen(false)} addToast={addToast} />
|
|
)}
|
|
<ToastContainer toasts={toasts} onRemove={removeToast} />
|
|
</>
|
|
);
|
|
}
|
|
|
|
export function App() {
|
|
return (
|
|
<ToastProvider>
|
|
<AppInner />
|
|
</ToastProvider>
|
|
);
|
|
}
|