---
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 (
    <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**:
```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);

<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**:
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(<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).
