import React, { useState } from 'react'; import { useApp } from '../contexts/AppContext'; import { UserStory } from '../types'; import { Card } from './ui/card'; import { Badge } from './ui/badge'; import { Button } from './ui/button'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from './ui/dialog'; import { Input } from './ui/input'; import { Textarea } from './ui/textarea'; import { Label } from './ui/label'; import { DndProvider, useDrag, useDrop } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import { GripVertical, CheckCircle2, Plus } from 'lucide-react'; interface DraggableStoryProps { story: UserStory; index: number; moveStory: (fromIndex: number, toIndex: number) => void; onEdit: (story: UserStory) => void; } const DraggableStory: React.FC = ({ story, index, moveStory, onEdit }) => { const [{ isDragging }, drag] = useDrag(() => ({ type: 'STORY', item: { index }, collect: (monitor) => ({ isDragging: monitor.isDragging(), }), })); const [, drop] = useDrop(() => ({ accept: 'STORY', hover: (item: { index: number }) => { if (item.index !== index) { moveStory(item.index, index); item.index = index; } }, })); const getStatusColor = (status: UserStory['status']) => { switch (status) { case 'sprint-ready': return 'bg-green-500'; case 'in-sprint': return 'bg-blue-500'; default: return 'bg-gray-400'; } }; return (
drag(drop(node))} style={{ opacity: isDragging ? 0.5 : 1 }}>

{story.title}

{story.storyPoints} pts Value: {story.businessValue}

{story.description}

{story.acceptanceCriteria && (
Acceptance Criteria: {story.acceptanceCriteria}
)}
{story.status === 'backlog' && ( )}
); }; export const ProductBacklog: React.FC = () => { const { userStories, reorderUserStories, updateUserStory, addUserStory } = useApp(); const [editDialogOpen, setEditDialogOpen] = useState(false); const [createDialogOpen, setCreateDialogOpen] = useState(false); const [selectedStory, setSelectedStory] = useState(null); const [formData, setFormData] = useState({ title: '', description: '', storyPoints: 0, businessValue: 0, acceptanceCriteria: '' }); const backlogStories = userStories .filter(story => story.status !== 'in-sprint') .sort((a, b) => a.priority - b.priority); const moveStory = (fromIndex: number, toIndex: number) => { const stories = [...backlogStories]; const [movedStory] = stories.splice(fromIndex, 1); stories.splice(toIndex, 0, movedStory); const updatedStories = stories.map((story, index) => ({ ...story, priority: index + 1 })); const otherStories = userStories.filter(story => story.status === 'in-sprint'); reorderUserStories([...updatedStories, ...otherStories]); }; const handleEdit = (story: UserStory) => { setSelectedStory(story); setFormData({ title: story.title, description: story.description, storyPoints: story.storyPoints, businessValue: story.businessValue, acceptanceCriteria: story.acceptanceCriteria }); setEditDialogOpen(true); }; const handleSave = () => { if (selectedStory) { updateUserStory(selectedStory.id, formData); setEditDialogOpen(false); setSelectedStory(null); } }; const handleCreate = () => { addUserStory({ ...formData, status: 'backlog', priority: userStories.length + 1 }); setCreateDialogOpen(false); setFormData({ title: '', description: '', storyPoints: 0, businessValue: 0, acceptanceCriteria: '' }); }; const handleMarkSprintReady = () => { if (selectedStory) { updateUserStory(selectedStory.id, { status: 'sprint-ready' }); setEditDialogOpen(false); setSelectedStory(null); } }; return (

Product Backlog

Drag and drop to prioritize backlog items

Backlog
Sprint Ready
In Sprint
{backlogStories.map((story, index) => ( ))}
{backlogStories.length === 0 && (
No items in backlog. Create a new user story to get started.
)} {/* Edit Dialog */} Edit User Story
setFormData({ ...formData, title: e.target.value })} />