Refactored the Kanban board from a mixed Epic/Story/Task view to focus exclusively on Stories, which are the right granularity for Kanban management. Changes: - Created StoryCard component with Epic breadcrumb, priority badges, and estimated hours display - Updated KanbanColumn to use Story type and display epic names - Created CreateStoryDialog for story creation with epic selection - Added useProjectStories hook to fetch all stories across epics for a project - Refactored Kanban page to show Stories only with drag-and-drop status updates - Updated SignalR event handlers to focus on Story events only - Changed UI text from 'New Issue' to 'New Story' and 'update issue status' to 'update story status' - Implemented story status change via useChangeStoryStatus hook 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
51 lines
1.7 KiB
TypeScript
51 lines
1.7 KiB
TypeScript
'use client';
|
|
|
|
import { useDroppable } from '@dnd-kit/core';
|
|
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Story } from '@/types/project';
|
|
import { StoryCard } from './StoryCard';
|
|
|
|
interface KanbanColumnProps {
|
|
id: string;
|
|
title: string;
|
|
stories: Story[];
|
|
epicNames?: Record<string, string>; // Map of epicId -> epicName
|
|
taskCounts?: Record<string, number>; // Map of storyId -> taskCount
|
|
}
|
|
|
|
export function KanbanColumn({ id, title, stories, epicNames = {}, taskCounts = {} }: KanbanColumnProps) {
|
|
const { setNodeRef } = useDroppable({ id });
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="text-sm font-medium flex items-center justify-between">
|
|
<span>{title}</span>
|
|
<span className="text-muted-foreground">{stories.length}</span>
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent ref={setNodeRef} className="space-y-2 min-h-[400px]">
|
|
<SortableContext
|
|
items={stories.map((s) => s.id)}
|
|
strategy={verticalListSortingStrategy}
|
|
>
|
|
{stories.map((story) => (
|
|
<StoryCard
|
|
key={story.id}
|
|
story={story}
|
|
epicName={epicNames[story.epicId]}
|
|
taskCount={taskCounts[story.id]}
|
|
/>
|
|
))}
|
|
</SortableContext>
|
|
{stories.length === 0 && (
|
|
<div className="flex h-32 items-center justify-center rounded-lg border-2 border-dashed border-muted-foreground/25">
|
|
<p className="text-sm text-muted-foreground">No stories</p>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|