Co-Authored-By: Claude Haiku 4.5 <[email protected]>
93 lines
2.9 KiB
TypeScript
93 lines
2.9 KiB
TypeScript
import { useEffect, useState, type ReactElement } from 'react';
|
|
import { KanbanBoard } from '@/components/tasks/kanban-board';
|
|
import { TaskDetailModal } from '@/components/tasks/task-detail-modal';
|
|
import { TaskListView } from '@/components/tasks/task-list-view';
|
|
import { api } from '@/lib/api';
|
|
import { cn } from '@/lib/cn';
|
|
import type { Task } from '@/lib/types';
|
|
import { getErrorMessage } from './page-errors';
|
|
|
|
type ViewMode = 'list' | 'kanban';
|
|
|
|
export function TasksPage(): ReactElement {
|
|
const [tasks, setTasks] = useState<Task[]>([]);
|
|
const [view, setView] = useState<ViewMode>('kanban');
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
|
|
void api<Task[]>('/api/tasks')
|
|
.then((response) => {
|
|
if (cancelled) return;
|
|
setTasks(response);
|
|
})
|
|
.catch((caught: unknown) => {
|
|
if (cancelled) return;
|
|
setError(getErrorMessage(caught, 'Failed to load tasks.'));
|
|
})
|
|
.finally(() => {
|
|
if (cancelled) return;
|
|
setLoading(false);
|
|
});
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<div className="flex min-h-screen flex-col px-4 py-6 sm:px-6">
|
|
<header className="mb-6 flex items-center justify-between gap-4 border-b px-1 pb-3">
|
|
<h1 className="text-2xl font-semibold">Tasks</h1>
|
|
<div className="flex rounded-lg border border-surface-border">
|
|
<button
|
|
type="button"
|
|
onClick={() => setView('list')}
|
|
className={cn(
|
|
'px-3 py-1.5 text-xs transition-colors',
|
|
view === 'list'
|
|
? 'bg-surface-elevated text-text-primary'
|
|
: 'text-text-muted hover:text-text-secondary',
|
|
)}
|
|
>
|
|
List
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => setView('kanban')}
|
|
className={cn(
|
|
'px-3 py-1.5 text-xs transition-colors',
|
|
view === 'kanban'
|
|
? 'bg-surface-elevated text-text-primary'
|
|
: 'text-text-muted hover:text-text-secondary',
|
|
)}
|
|
>
|
|
Kanban
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
{error ? (
|
|
<div role="alert" className="mb-6 rounded-lg border border-error/40 px-4 py-3 text-sm">
|
|
{error}
|
|
</div>
|
|
) : null}
|
|
|
|
{loading ? (
|
|
<p className="py-8 text-center text-sm text-text-muted">Loading tasks...</p>
|
|
) : view === 'kanban' ? (
|
|
<KanbanBoard tasks={tasks} onTaskClick={setSelectedTask} />
|
|
) : (
|
|
<TaskListView tasks={tasks} onTaskClick={setSelectedTask} />
|
|
)}
|
|
|
|
{selectedTask ? (
|
|
<TaskDetailModal task={selectedTask} onClose={() => setSelectedTask(null)} />
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|