--- task_id: sprint_4_story_1_task_3 story_id: sprint_4_story_1 sprint_id: sprint_4 status: not_started type: frontend assignee: Frontend Developer 1 created_date: 2025-11-05 estimated_hours: 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 1. Create `components/projects/story-metadata-sidebar.tsx` 2. Display Status dropdown (quick status change) 3. Display Priority dropdown (quick priority change) 4. Display Assignee with avatar and change button 5. Display Time Tracking (Estimated vs Actual hours, progress bar) 6. Display Dates (Created, Updated with relative time) 7. Display Parent Epic card (title, status, progress, link) 8. Add responsive design (sidebar moves to top on mobile) 9. 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 ```typescript // 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 (
{/* Status Section */}

Status

{/* Priority Section */}

Priority

{story.priority}
{/* Assignee Section */}

Assignee

{story.assigneeId ? (

Assignee Name

) : ( )}
{/* Time Tracking Section */}

Time Tracking

Estimated {story.estimatedHours || 0}h
Actual {story.actualHours || 0}h

{Math.round(completionPercentage)}% complete

{/* Dates Section */}

Dates

Created {formatDistanceToNow(new Date(story.createdAt), { addSuffix: true })}
Updated {formatDistanceToNow(new Date(story.updatedAt), { addSuffix: true })}
{/* Parent Epic Section */} {parentEpic && (

Parent Epic

{parentEpic.title}

{parentEpic.status}

View Epic Details →

)}
); } function getPriorityVariant(priority: string) { const variants = { Low: 'default', Medium: 'warning', High: 'destructive', Critical: 'destructive', }; return variants[priority] || 'default'; } ``` **Integration in page.tsx**: ```typescript // 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); ``` ## 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**: 1. View Story detail page 2. Verify all metadata sections display correctly 3. Change status via dropdown → Verify updates immediately 4. Verify priority badge color matches priority 5. Verify assignee displays correctly (if assigned) 6. Verify time tracking shows correct hours and percentage 7. Verify dates are in relative format 8. Click parent Epic card → Navigates to Epic detail page 9. Test on mobile → Sidebar moves above main content **Unit Test**: ```typescript 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(); 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(); 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).