Files
ColaFlow-Web/components/projects/work-item-breadcrumb.tsx
Yaojia Wang 2a0394b5ab feat(frontend): Implement Epic detail page with Story management
Add comprehensive Epic detail page at /epics/[id] with full CRUD operations.

Changes:
- Created Epic detail page with breadcrumb navigation
- Display Epic details: name, description, status, priority, time estimates
- Show list of Stories belonging to the Epic with card view
- Add Edit Epic functionality (opens dialog with form)
- Add Create/Edit/Delete Story functionality under Epic
- Fix Epic type inconsistency (name vs title) across components
- Update Kanban page to map Epic.name to title for unified interface
- Update epic-form to use 'name' field and add createdBy support
- Update work-item-breadcrumb to use Epic.name instead of title

Technical improvements:
- Use Shadcn UI components for consistent design
- Implement optimistic updates with React Query
- Add loading and error states with skeletons
- Follow Next.js App Router patterns with async params
- Add delete confirmation dialogs for Epic and Stories

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 14:56:29 +01:00

111 lines
3.3 KiB
TypeScript

'use client';
import { ChevronRight, Folder, FileText, CheckSquare, Home } from 'lucide-react';
import { useEpic } from '@/lib/hooks/use-epics';
import { useStory } from '@/lib/hooks/use-stories';
import { Skeleton } from '@/components/ui/skeleton';
import Link from 'next/link';
import type { Epic, Story, Task } from '@/types/project';
interface WorkItemBreadcrumbProps {
projectId: string;
projectName?: string;
epic?: Epic;
story?: Story;
task?: Task;
epicId?: string;
storyId?: string;
}
export function WorkItemBreadcrumb({
projectId,
projectName,
epic,
story,
task,
epicId,
storyId,
}: WorkItemBreadcrumbProps) {
// Fetch epic if only epicId provided
const { data: fetchedEpic, isLoading: epicLoading } = useEpic(
epicId && !epic ? epicId : ''
);
const effectiveEpic = epic || fetchedEpic;
// Fetch story if only storyId provided
const { data: fetchedStory, isLoading: storyLoading } = useStory(
storyId && !story ? storyId : ''
);
const effectiveStory = story || fetchedStory;
// If we need to fetch parent epic from story
const { data: parentEpic, isLoading: parentEpicLoading } = useEpic(
effectiveStory && !effectiveEpic ? effectiveStory.epicId : ''
);
const finalEpic = effectiveEpic || parentEpic;
const isLoading = epicLoading || storyLoading || parentEpicLoading;
if (isLoading) {
return (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Skeleton className="h-4 w-24" />
<ChevronRight className="h-4 w-4" />
<Skeleton className="h-4 w-32" />
</div>
);
}
return (
<nav className="flex items-center gap-2 text-sm" aria-label="Breadcrumb">
{/* Project */}
<Link
href={`/projects/${projectId}`}
className="flex items-center gap-1 text-muted-foreground hover:text-foreground transition-colors"
>
<Home className="h-4 w-4" />
{projectName && <span>{projectName}</span>}
</Link>
{/* Epic */}
{finalEpic && (
<>
<ChevronRight className="h-4 w-4 text-muted-foreground" />
<Link
href={`/projects/${projectId}/epics/${finalEpic.id}`}
className="flex items-center gap-1 text-muted-foreground hover:text-foreground transition-colors"
>
<Folder className="h-4 w-4 text-blue-500" />
<span className="max-w-[200px] truncate">{finalEpic.name}</span>
</Link>
</>
)}
{/* Story */}
{effectiveStory && (
<>
<ChevronRight className="h-4 w-4 text-muted-foreground" />
<Link
href={`/projects/${projectId}/stories/${effectiveStory.id}`}
className="flex items-center gap-1 text-muted-foreground hover:text-foreground transition-colors"
>
<FileText className="h-4 w-4 text-green-500" />
<span className="max-w-[200px] truncate">{effectiveStory.title}</span>
</Link>
</>
)}
{/* Task */}
{task && (
<>
<ChevronRight className="h-4 w-4 text-muted-foreground" />
<div className="flex items-center gap-1 font-medium">
<CheckSquare className="h-4 w-4 text-purple-500" />
<span className="max-w-[200px] truncate">{task.title}</span>
</div>
</>
)}
</nav>
);
}