Files
todo-app-web/src/pages/Project.tsx

153 lines
5.0 KiB
TypeScript

import { useEffect, useState } from 'react';
import { useParams, useNavigate, useLocation } from 'react-router-dom';
import { LayoutList, LayoutGrid, Plus } from 'lucide-react';
import { cn } from '@/lib/utils';
import { useTasksStore } from '@/stores/tasks';
import { TaskItem } from '@/components/TaskItem';
import { AddTask } from '@/components/AddTask';
import { CompletedSection } from '@/components/CompletedSection';
import { BoardView } from '@/pages/Board';
import { api } from '@/lib/api';
import type { Project as ProjectType, Section } from '@/types';
export function ProjectPage() {
const { id } = useParams<{ id: string }>();
const location = useLocation();
const navigate = useNavigate();
const { tasks, completedTasks, isLoading, fetchTasks, fetchCompletedTasks, setSelectedTask } = useTasksStore();
const [project, setProject] = useState<ProjectType | null>(null);
const [sections, setSections] = useState<Section[]>([]);
const isBoardView = location.pathname.endsWith('/board');
useEffect(() => {
if (!id) return;
fetchTasks({ projectId: id, completed: false });
fetchCompletedTasks({ projectId: id });
api.getProject(id).then((p) => {
setProject(p);
setSections(p.sections || []);
}).catch(console.error);
}, [id]);
if (!project) {
return (
<div className="flex items-center justify-center py-12 text-gray-500">
Loading project...
</div>
);
}
const toggleView = () => {
if (isBoardView) {
navigate(`/project/${id}`);
} else {
navigate(`/project/${id}/board`);
}
};
// Group tasks by section
const unsectionedTasks = tasks.filter((t) => !t.sectionId);
const tasksBySection = sections.map((section) => ({
section,
tasks: tasks.filter((t) => t.sectionId === section.id),
}));
return (
<div className="max-w-5xl mx-auto">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<span
className="w-4 h-4 rounded"
style={{ backgroundColor: project.color }}
/>
<h1 className="text-2xl font-bold text-gray-900">{project.name}</h1>
</div>
<div className="flex items-center gap-1 bg-gray-100 rounded-lg p-0.5">
<button
onClick={() => !isBoardView || toggleView()}
className={cn(
'flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-md transition-colors',
!isBoardView
? 'bg-white text-gray-900 shadow-sm'
: 'text-gray-500 hover:text-gray-700'
)}
>
<LayoutList className="w-4 h-4" />
List
</button>
<button
onClick={() => isBoardView || toggleView()}
className={cn(
'flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-md transition-colors',
isBoardView
? 'bg-white text-gray-900 shadow-sm'
: 'text-gray-500 hover:text-gray-700'
)}
>
<LayoutGrid className="w-4 h-4" />
Board
</button>
</div>
</div>
{isBoardView ? (
<BoardView
project={project}
sections={sections}
tasks={tasks}
isLoading={isLoading}
/>
) : (
/* List view */
<div className="space-y-6">
{isLoading ? (
<div className="text-center py-12 text-gray-500">Loading tasks...</div>
) : (
<>
{/* Unsectioned tasks */}
{unsectionedTasks.length > 0 && (
<div className="space-y-1">
{unsectionedTasks.map((task) => (
<TaskItem
key={task.id}
task={task}
onClick={() => setSelectedTask(task)}
/>
))}
</div>
)}
{/* Add task for unsectioned */}
<AddTask projectId={id} />
{/* Sections */}
{tasksBySection.map(({ section, tasks: sectionTasks }) => (
<div key={section.id}>
<h3 className="text-sm font-semibold text-gray-700 mb-2 px-3 py-1 border-b border-gray-200">
{section.name}
</h3>
<div className="space-y-1">
{sectionTasks.map((task) => (
<TaskItem
key={task.id}
task={task}
onClick={() => setSelectedTask(task)}
/>
))}
</div>
<AddTask projectId={id} sectionId={section.id} />
</div>
))}
{/* Completed tasks */}
<CompletedSection tasks={completedTasks} />
</>
)}
</div>
)}
</div>
);
}