---
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 ? (
) : (
)}
{/* 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).