Files
mock/components/KanbanBoard.tsx
Your Name e69c6ae5be Fix build errors and TypeScript issues
- 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
2025-10-24 18:52:34 -07:00

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>
);
};