- Install missing UI component dependencies - Fix unused imports in React components - Remove version numbers from all import statements - Fix TypeScript errors in calendar and chart components - Build now succeeds with production bundle generated
224 lines
6.8 KiB
TypeScript
224 lines
6.8 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { useApp } from '../contexts/AppContext';
|
|
import { Task } from '../types';
|
|
import { TaskCard } from './TaskCard';
|
|
import { CreateTaskDialog } from './CreateTaskDialog';
|
|
import { EditTaskDialog } from './EditTaskDialog';
|
|
import { CommentDialog } from './CommentDialog';
|
|
import { Card } from './ui/card';
|
|
import { Button } from './ui/button';
|
|
import { Badge } from './ui/badge';
|
|
import { Plus } from 'lucide-react';
|
|
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
|
|
|
interface DraggableTaskProps {
|
|
task: Task;
|
|
onEdit: (task: Task) => void;
|
|
onComment: (task: Task) => void;
|
|
}
|
|
|
|
const DraggableTask: React.FC<DraggableTaskProps> = ({ task, onEdit, onComment }) => {
|
|
const [{ isDragging }, drag] = useDrag(() => ({
|
|
type: 'TASK',
|
|
item: { id: task.id },
|
|
collect: (monitor) => ({
|
|
isDragging: monitor.isDragging(),
|
|
}),
|
|
}));
|
|
|
|
return (
|
|
<div
|
|
ref={drag}
|
|
style={{ opacity: isDragging ? 0.5 : 1 }}
|
|
className="cursor-move"
|
|
>
|
|
<TaskCard task={task} onEdit={onEdit} onComment={onComment} />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
interface ColumnProps {
|
|
title: string;
|
|
status: Task['status'];
|
|
tasks: Task[];
|
|
onDrop: (taskId: string, newStatus: Task['status']) => void;
|
|
onEdit: (task: Task) => void;
|
|
onComment: (task: Task) => void;
|
|
}
|
|
|
|
const Column: React.FC<ColumnProps> = ({ title, status, tasks, onDrop, onEdit, onComment }) => {
|
|
const [{ isOver }, drop] = useDrop(() => ({
|
|
accept: 'TASK',
|
|
drop: (item: { id: string }) => onDrop(item.id, status),
|
|
collect: (monitor) => ({
|
|
isOver: monitor.isOver(),
|
|
}),
|
|
}));
|
|
|
|
return (
|
|
<div
|
|
ref={drop}
|
|
className={`flex-1 min-w-[280px] ${isOver ? 'opacity-50' : ''}`}
|
|
>
|
|
<Card className="p-4 h-full">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h3 className="flex items-center gap-2">
|
|
{title}
|
|
<Badge variant="secondary">{tasks.length}</Badge>
|
|
</h3>
|
|
</div>
|
|
<div className="space-y-3">
|
|
{tasks.map(task => (
|
|
<DraggableTask
|
|
key={task.id}
|
|
task={task}
|
|
onEdit={onEdit}
|
|
onComment={onComment}
|
|
/>
|
|
))}
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export const KanbanBoard: React.FC = () => {
|
|
const { tasks, userStories, activeSprint, updateTask } = useApp();
|
|
const [createDialogOpen, setCreateDialogOpen] = useState(false);
|
|
const [selectedUserStoryId, setSelectedUserStoryId] = useState<string | null>(null);
|
|
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
|
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
|
|
const [commentDialogOpen, setCommentDialogOpen] = useState(false);
|
|
|
|
const sprintUserStories = userStories.filter(
|
|
story => story.sprintId === activeSprint?.id
|
|
);
|
|
|
|
const handleCreateTask = (userStoryId: string) => {
|
|
setSelectedUserStoryId(userStoryId);
|
|
setCreateDialogOpen(true);
|
|
};
|
|
|
|
const handleEditTask = (task: Task) => {
|
|
setSelectedTask(task);
|
|
setEditDialogOpen(true);
|
|
};
|
|
|
|
const handleCommentTask = (task: Task) => {
|
|
setSelectedTask(task);
|
|
setCommentDialogOpen(true);
|
|
};
|
|
|
|
const handleDrop = (taskId: string, newStatus: Task['status']) => {
|
|
updateTask(taskId, { status: newStatus });
|
|
};
|
|
|
|
if (!activeSprint) {
|
|
return (
|
|
<div className="p-8 text-center">
|
|
<h2 className="mb-2">No Active Sprint</h2>
|
|
<p className="text-muted-foreground">
|
|
Please activate a sprint from the Sprint Management page.
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<DndProvider backend={HTML5Backend}>
|
|
<div className="p-6 space-y-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1>{activeSprint.name}</h1>
|
|
<p className="text-muted-foreground">{activeSprint.goal}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-8">
|
|
{sprintUserStories.map(story => {
|
|
const storyTasks = tasks.filter(task => task.userStoryId === story.id);
|
|
|
|
return (
|
|
<div key={story.id} className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<h2>{story.title}</h2>
|
|
<Badge>{story.storyPoints} pts</Badge>
|
|
</div>
|
|
<Button
|
|
size="sm"
|
|
onClick={() => handleCreateTask(story.id)}
|
|
>
|
|
<Plus className="h-4 w-4 mr-1" />
|
|
Add Task
|
|
</Button>
|
|
</div>
|
|
|
|
<div className="flex gap-4 overflow-x-auto pb-2">
|
|
<Column
|
|
title="To Do"
|
|
status="todo"
|
|
tasks={storyTasks.filter(t => t.status === 'todo')}
|
|
onDrop={handleDrop}
|
|
onEdit={handleEditTask}
|
|
onComment={handleCommentTask}
|
|
/>
|
|
<Column
|
|
title="In Progress"
|
|
status="in-progress"
|
|
tasks={storyTasks.filter(t => t.status === 'in-progress')}
|
|
onDrop={handleDrop}
|
|
onEdit={handleEditTask}
|
|
onComment={handleCommentTask}
|
|
/>
|
|
<Column
|
|
title="Blocked"
|
|
status="blocked"
|
|
tasks={storyTasks.filter(t => t.status === 'blocked')}
|
|
onDrop={handleDrop}
|
|
onEdit={handleEditTask}
|
|
onComment={handleCommentTask}
|
|
/>
|
|
<Column
|
|
title="Done"
|
|
status="done"
|
|
tasks={storyTasks.filter(t => t.status === 'done')}
|
|
onDrop={handleDrop}
|
|
onEdit={handleEditTask}
|
|
onComment={handleCommentTask}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
|
|
{sprintUserStories.length === 0 && (
|
|
<div className="text-center py-12 text-muted-foreground">
|
|
No user stories in this sprint. Add user stories from the backlog.
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<CreateTaskDialog
|
|
open={createDialogOpen}
|
|
onOpenChange={setCreateDialogOpen}
|
|
userStoryId={selectedUserStoryId || ''}
|
|
/>
|
|
|
|
<EditTaskDialog
|
|
open={editDialogOpen}
|
|
onOpenChange={setEditDialogOpen}
|
|
task={selectedTask}
|
|
/>
|
|
|
|
<CommentDialog
|
|
open={commentDialogOpen}
|
|
onOpenChange={setCommentDialogOpen}
|
|
task={selectedTask}
|
|
/>
|
|
</div>
|
|
</DndProvider>
|
|
);
|
|
};
|