Created comprehensive Story and Task files for Sprint 4 frontend implementation: Story 1: Story Detail Page Foundation (P0 Critical - 3 days) - 6 tasks: route creation, header, sidebar, data loading, Edit/Delete, responsive design - Fixes critical 404 error when clicking Story cards - Two-column layout consistent with Epic detail page Story 2: Task Management in Story Detail (P0 Critical - 2 days) - 6 tasks: API verification, hooks, TaskList, TaskCard, TaskForm, integration - Complete Task CRUD with checkbox status toggle - Filters, sorting, and optimistic UI updates Story 3: Enhanced Story Form (P1 High - 2 days) - 6 tasks: acceptance criteria, assignee selector, tags, story points, integration - Aligns with UX design specification - Backward compatible with existing Stories Story 4: Quick Add Story Workflow (P1 High - 2 days) - 5 tasks: inline form, keyboard shortcuts, batch creation, navigation - Rapid Story creation with minimal fields - Keyboard shortcut (Cmd/Ctrl + N) Story 5: Story Card Component (P2 Medium - 1 day) - 4 tasks: component variants, visual states, Task count, optimization - Reusable component with list/kanban/compact variants - React.memo optimization Story 6: Kanban Story Creation Enhancement (P2 Optional - 2 days) - 4 tasks: Epic card enhancement, inline form, animation, real-time updates - Contextual Story creation from Kanban - Stretch goal - implement only if ahead of schedule Total: 6 Stories, 31 Tasks, 12 days estimated Priority breakdown: P0 (2), P1 (2), P2 (2 optional) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
10 KiB
10 KiB
task_id, story_id, sprint_id, status, type, assignee, created_date, estimated_hours
| task_id | story_id | sprint_id | status | type | assignee | created_date | estimated_hours |
|---|---|---|---|---|---|---|---|
| sprint_4_story_1_task_3 | sprint_4_story_1 | sprint_4 | not_started | frontend | Frontend Developer 1 | 2025-11-05 | 4 |
Task 3: Implement Story Metadata Sidebar Component
Description
Create the metadata sidebar component that displays Story status, priority, assignee, time tracking, dates, and a parent Epic card. This sidebar provides quick access to Story metadata and context.
What to Do
- Create
components/projects/story-metadata-sidebar.tsx - Display Status dropdown (quick status change)
- Display Priority dropdown (quick priority change)
- Display Assignee with avatar and change button
- Display Time Tracking (Estimated vs Actual hours, progress bar)
- Display Dates (Created, Updated with relative time)
- Display Parent Epic card (title, status, progress, link)
- Add responsive design (sidebar moves to top on mobile)
- Integrate with Story update hooks
Files to Create/Modify
components/projects/story-metadata-sidebar.tsx(new, ~150-200 lines)components/projects/epic-card-compact.tsx(optional, for parent Epic display)app/(dashboard)/stories/[id]/page.tsx(modify, integrate sidebar)
Implementation Details
// components/projects/story-metadata-sidebar.tsx
'use client';
import { useState } from 'react';
import Link from 'next/link';
import { Calendar, Clock, User } from 'lucide-react';
import { Card } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Progress } from '@/components/ui/progress';
import type { Story, Epic } from '@/types/project';
import { useChangeStoryStatus, useAssignStory } from '@/lib/hooks/use-stories';
import { formatDistanceToNow } from 'date-fns';
interface StoryMetadataSidebarProps {
story: Story;
parentEpic?: Epic;
}
export function StoryMetadataSidebar({ story, parentEpic }: StoryMetadataSidebarProps) {
const changeStatus = useChangeStoryStatus();
const assignStory = useAssignStory();
const handleStatusChange = async (newStatus: string) => {
await changeStatus.mutateAsync({ storyId: story.id, status: newStatus });
};
const completionPercentage = story.actualHours && story.estimatedHours
? Math.min(100, (story.actualHours / story.estimatedHours) * 100)
: 0;
return (
<div className="space-y-6">
{/* Status Section */}
<Card className="p-4">
<h3 className="text-sm font-medium mb-3">Status</h3>
<Select
value={story.status}
onValueChange={handleStatusChange}
disabled={changeStatus.isPending}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="Backlog">Backlog</SelectItem>
<SelectItem value="Todo">Todo</SelectItem>
<SelectItem value="InProgress">In Progress</SelectItem>
<SelectItem value="Done">Done</SelectItem>
</SelectContent>
</Select>
</Card>
{/* Priority Section */}
<Card className="p-4">
<h3 className="text-sm font-medium mb-3">Priority</h3>
<Badge variant={getPriorityVariant(story.priority)} className="w-full justify-center">
{story.priority}
</Badge>
</Card>
{/* Assignee Section */}
<Card className="p-4">
<h3 className="text-sm font-medium mb-3">Assignee</h3>
{story.assigneeId ? (
<div className="flex items-center gap-3">
<Avatar className="h-8 w-8">
<AvatarImage src={`/avatars/${story.assigneeId}.png`} />
<AvatarFallback>
<User className="h-4 w-4" />
</AvatarFallback>
</Avatar>
<div className="flex-1">
<p className="text-sm font-medium">Assignee Name</p>
<Button variant="link" size="sm" className="h-auto p-0 text-xs">
Change
</Button>
</div>
</div>
) : (
<Button variant="outline" size="sm" className="w-full">
<User className="h-4 w-4 mr-2" />
Assign
</Button>
)}
</Card>
{/* Time Tracking Section */}
<Card className="p-4">
<h3 className="text-sm font-medium mb-3">Time Tracking</h3>
<div className="space-y-3">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Estimated</span>
<span className="font-medium">{story.estimatedHours || 0}h</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Actual</span>
<span className="font-medium">{story.actualHours || 0}h</span>
</div>
<Progress value={completionPercentage} className="h-2" />
<p className="text-xs text-muted-foreground text-center">
{Math.round(completionPercentage)}% complete
</p>
</div>
</Card>
{/* Dates Section */}
<Card className="p-4">
<h3 className="text-sm font-medium mb-3">Dates</h3>
<div className="space-y-2 text-sm">
<div className="flex items-center gap-2 text-muted-foreground">
<Calendar className="h-4 w-4" />
<span>Created {formatDistanceToNow(new Date(story.createdAt), { addSuffix: true })}</span>
</div>
<div className="flex items-center gap-2 text-muted-foreground">
<Clock className="h-4 w-4" />
<span>Updated {formatDistanceToNow(new Date(story.updatedAt), { addSuffix: true })}</span>
</div>
</div>
</Card>
{/* Parent Epic Section */}
{parentEpic && (
<Card className="p-4">
<h3 className="text-sm font-medium mb-3">Parent Epic</h3>
<Link
href={`/epics/${parentEpic.id}`}
className="block p-3 rounded-lg border hover:bg-accent transition-colors"
>
<div className="flex items-start justify-between mb-2">
<h4 className="font-medium line-clamp-1">{parentEpic.title}</h4>
<Badge variant="outline" className="text-xs">
{parentEpic.status}
</Badge>
</div>
<p className="text-xs text-muted-foreground">
View Epic Details →
</p>
</Link>
</Card>
)}
</div>
);
}
function getPriorityVariant(priority: string) {
const variants = {
Low: 'default',
Medium: 'warning',
High: 'destructive',
Critical: 'destructive',
};
return variants[priority] || 'default';
}
Integration in page.tsx:
// app/(dashboard)/stories/[id]/page.tsx
import { StoryMetadataSidebar } from '@/components/projects/story-metadata-sidebar';
import { useEpic } from '@/lib/hooks/use-epics';
// Inside component
const { data: story } = useStory(params.id);
const { data: parentEpic } = useEpic(story?.epicId);
<StoryMetadataSidebar story={story} parentEpic={parentEpic} />
Acceptance Criteria
- Sidebar displays all metadata sections (Status, Priority, Assignee, Time, Dates, Parent Epic)
- Status dropdown allows quick status changes
- Status changes update immediately (optimistic UI)
- Priority badge shows correct color
- Assignee section shows avatar and name (if assigned)
- Time tracking shows estimated/actual hours and progress bar
- Progress bar correctly calculates percentage
- Dates display in relative format ("2 hours ago", "3 days ago")
- Parent Epic card is clickable and navigates to Epic detail
- Parent Epic card shows Epic status and title
- Responsive: Sidebar moves above content on mobile
- Loading states show skeleton loaders
Testing
Manual Testing:
- View Story detail page
- Verify all metadata sections display correctly
- Change status via dropdown → Verify updates immediately
- Verify priority badge color matches priority
- Verify assignee displays correctly (if assigned)
- Verify time tracking shows correct hours and percentage
- Verify dates are in relative format
- Click parent Epic card → Navigates to Epic detail page
- Test on mobile → Sidebar moves above main content
Unit Test:
import { render, screen } from '@testing-library/react';
import { StoryMetadataSidebar } from './story-metadata-sidebar';
describe('StoryMetadataSidebar', () => {
const mockStory = {
id: '123',
status: 'InProgress',
priority: 'High',
assigneeId: 'user-1',
estimatedHours: 16,
actualHours: 8,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
const mockEpic = {
id: 'epic-1',
title: 'Parent Epic',
status: 'InProgress',
};
it('renders all metadata sections', () => {
render(<StoryMetadataSidebar story={mockStory} parentEpic={mockEpic} />);
expect(screen.getByText('Status')).toBeInTheDocument();
expect(screen.getByText('Priority')).toBeInTheDocument();
expect(screen.getByText('Assignee')).toBeInTheDocument();
expect(screen.getByText('Time Tracking')).toBeInTheDocument();
expect(screen.getByText('Dates')).toBeInTheDocument();
expect(screen.getByText('Parent Epic')).toBeInTheDocument();
});
it('calculates time tracking percentage correctly', () => {
render(<StoryMetadataSidebar story={mockStory} parentEpic={mockEpic} />);
expect(screen.getByText('50% complete')).toBeInTheDocument();
});
});
Dependencies
Prerequisites:
- Task 1 (page structure must exist)
- ✅
useChangeStoryStatus()hook (already exists) - ✅
useAssignStory()hook (already exists) - ✅ shadcn/ui Card, Badge, Select, Avatar, Progress components
- ✅ date-fns for relative time formatting
Blocks:
- None (independent component)
Estimated Time
4 hours
Notes
Code Reuse: Copy sidebar structure from Epic detail page if similar metadata sidebar exists. The parent Epic card is similar to showing a parent Project card in Epic sidebar - reuse that pattern.
Future Enhancement: Assignee selector will be enhanced in Story 3 (Enhanced Story Form).