Add kanban board view, project creation, and task assignment
This commit is contained in:
135
src/pages/Board.tsx
Normal file
135
src/pages/Board.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import { Calendar, Flag, Plus } from 'lucide-react';
|
||||
import type { Task, Project, Section } from '@/types';
|
||||
import { cn, formatDate, isOverdue, getPriorityColor } from '@/lib/utils';
|
||||
import { useTasksStore } from '@/stores/tasks';
|
||||
import { AddTask } from '@/components/AddTask';
|
||||
|
||||
interface BoardViewProps {
|
||||
project: Project;
|
||||
sections: Section[];
|
||||
tasks: Task[];
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
function TaskCard({ task }: { task: Task }) {
|
||||
const { toggleComplete, setSelectedTask } = useTasksStore();
|
||||
const overdue = !task.isCompleted && isOverdue(task.dueDate);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="bg-white border border-gray-200 rounded-lg p-3 shadow-sm hover:shadow-md transition-shadow cursor-pointer"
|
||||
onClick={() => setSelectedTask(task)}
|
||||
>
|
||||
<div className="flex items-start gap-2">
|
||||
{/* Priority indicator */}
|
||||
<span
|
||||
className="mt-1 w-2 h-2 rounded-full flex-shrink-0"
|
||||
style={{ backgroundColor: getPriorityColor(task.priority) }}
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className={cn(
|
||||
'text-sm font-medium text-gray-900',
|
||||
task.isCompleted && 'line-through text-gray-500'
|
||||
)}>
|
||||
{task.title}
|
||||
</p>
|
||||
|
||||
{task.description && (
|
||||
<p className="text-xs text-gray-500 mt-1 line-clamp-2">
|
||||
{task.description}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2 mt-2 flex-wrap">
|
||||
{task.dueDate && (
|
||||
<span className={cn(
|
||||
'inline-flex items-center gap-1 text-xs',
|
||||
overdue ? 'text-red-500' : 'text-gray-500'
|
||||
)}>
|
||||
<Calendar className="w-3 h-3" />
|
||||
{formatDate(task.dueDate)}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{task.assignee && (
|
||||
<span
|
||||
className="w-5 h-5 rounded-full bg-blue-100 text-blue-700 flex items-center justify-center text-[10px] font-medium flex-shrink-0"
|
||||
title={task.assignee.name}
|
||||
>
|
||||
{task.assignee.name.charAt(0).toUpperCase()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BoardColumn({
|
||||
title,
|
||||
tasks,
|
||||
projectId,
|
||||
sectionId,
|
||||
}: {
|
||||
title: string;
|
||||
tasks: Task[];
|
||||
projectId: string;
|
||||
sectionId?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex-shrink-0 w-72 flex flex-col bg-gray-100 rounded-xl max-h-full">
|
||||
{/* Column header */}
|
||||
<div className="px-3 py-3 flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold text-gray-700">
|
||||
{title}
|
||||
<span className="ml-2 text-xs font-normal text-gray-400">{tasks.length}</span>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Tasks */}
|
||||
<div className="flex-1 overflow-y-auto px-2 pb-2 space-y-2">
|
||||
{tasks.map((task) => (
|
||||
<TaskCard key={task.id} task={task} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Add task */}
|
||||
<div className="px-2 pb-2">
|
||||
<AddTask projectId={projectId} sectionId={sectionId} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function BoardView({ project, sections, tasks, isLoading }: BoardViewProps) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="text-center py-12 text-gray-500">Loading tasks...</div>
|
||||
);
|
||||
}
|
||||
|
||||
const unsectionedTasks = tasks.filter((t) => !t.sectionId);
|
||||
const columns = [
|
||||
{ title: 'No Section', tasks: unsectionedTasks, sectionId: undefined },
|
||||
...sections.map((section) => ({
|
||||
title: section.name,
|
||||
tasks: tasks.filter((t) => t.sectionId === section.id),
|
||||
sectionId: section.id,
|
||||
})),
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex gap-4 overflow-x-auto pb-4" style={{ minHeight: '60vh' }}>
|
||||
{columns.map((col) => (
|
||||
<BoardColumn
|
||||
key={col.sectionId || 'unsectioned'}
|
||||
title={col.title}
|
||||
tasks={col.tasks}
|
||||
projectId={project.id}
|
||||
sectionId={col.sectionId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user