Files
mock/components/SprintManagement.tsx
Your Name e69c6ae5be Fix build errors and TypeScript issues
- Install missing UI component dependencies
- Fix unused imports in React components
- Remove version numbers from all import statements
- Fix TypeScript errors in calendar and chart components
- Build now succeeds with production bundle generated
2025-10-24 18:52:34 -07:00

332 lines
12 KiB
TypeScript

import React, { useState } from 'react';
import { useApp } from '../contexts/AppContext';
import { Sprint } 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 { format } from 'date-fns';
import { Calendar, Plus, Edit, XCircle, PlayCircle } from 'lucide-react';
export const SprintManagement: React.FC = () => {
const { sprints, userStories, updateSprint, closeSprint, addSprint, setActiveSprint } = useApp();
const [editDialogOpen, setEditDialogOpen] = useState(false);
const [createDialogOpen, setCreateDialogOpen] = useState(false);
const [selectedSprint, setSelectedSprint] = useState<Sprint | null>(null);
const [formData, setFormData] = useState({
name: '',
goal: '',
startDate: '',
endDate: '',
capacity: 0
});
const handleEdit = (sprint: Sprint) => {
setSelectedSprint(sprint);
setFormData({
name: sprint.name,
goal: sprint.goal,
startDate: format(new Date(sprint.startDate), 'yyyy-MM-dd'),
endDate: format(new Date(sprint.endDate), 'yyyy-MM-dd'),
capacity: sprint.capacity
});
setEditDialogOpen(true);
};
const handleSave = () => {
if (selectedSprint) {
updateSprint(selectedSprint.id, {
name: formData.name,
goal: formData.goal,
startDate: new Date(formData.startDate),
endDate: new Date(formData.endDate),
capacity: formData.capacity
});
setEditDialogOpen(false);
setSelectedSprint(null);
}
};
const handleCreate = () => {
addSprint({
name: formData.name,
goal: formData.goal,
startDate: new Date(formData.startDate),
endDate: new Date(formData.endDate),
capacity: formData.capacity,
status: 'planning',
userStories: []
});
setCreateDialogOpen(false);
setFormData({
name: '',
goal: '',
startDate: '',
endDate: '',
capacity: 0
});
};
const handleCloseSprint = (sprint: Sprint) => {
if (confirm(`Are you sure you want to close ${sprint.name}? Unfinished items will be moved back to the backlog.`)) {
closeSprint(sprint.id);
}
};
const handleActivate = (sprint: Sprint) => {
updateSprint(sprint.id, { status: 'active' });
setActiveSprint(sprint);
};
const getSprintStories = (sprintId: string) => {
return userStories.filter(story => story.sprintId === sprintId);
};
const getTotalPoints = (sprintId: string) => {
return getSprintStories(sprintId).reduce((sum, story) => sum + story.storyPoints, 0);
};
const getStatusBadge = (status: Sprint['status']) => {
const variants: Record<Sprint['status'], { variant: 'default' | 'secondary' | 'outline', label: string }> = {
planning: { variant: 'outline', label: 'Planning' },
active: { variant: 'default', label: 'Active' },
closed: { variant: 'secondary', label: 'Closed' }
};
const { variant, label } = variants[status];
return <Badge variant={variant}>{label}</Badge>;
};
return (
<div className="p-6 space-y-6">
<div className="flex items-center justify-between">
<div>
<h1>Sprint Management</h1>
<p className="text-muted-foreground">
Create, edit, and manage sprints
</p>
</div>
<Button onClick={() => setCreateDialogOpen(true)}>
<Plus className="h-4 w-4 mr-2" />
New Sprint
</Button>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{sprints.map(sprint => {
const sprintStories = getSprintStories(sprint.id);
const totalPoints = getTotalPoints(sprint.id);
return (
<Card key={sprint.id} className="p-6">
<div className="space-y-4">
<div className="flex items-start justify-between">
<div className="space-y-1">
<h3>{sprint.name}</h3>
{getStatusBadge(sprint.status)}
</div>
</div>
<p className="text-sm text-muted-foreground">{sprint.goal}</p>
<div className="space-y-2 text-sm">
<div className="flex items-center gap-2">
<Calendar className="h-4 w-4 text-muted-foreground" />
<span>
{format(new Date(sprint.startDate), 'MMM dd')} - {format(new Date(sprint.endDate), 'MMM dd, yyyy')}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-muted-foreground">Capacity:</span>
<span>{sprint.capacity} pts</span>
</div>
<div className="flex items-center justify-between">
<span className="text-muted-foreground">Committed:</span>
<span>{totalPoints} pts</span>
</div>
<div className="flex items-center justify-between">
<span className="text-muted-foreground">Stories:</span>
<span>{sprintStories.length}</span>
</div>
</div>
<div className="flex gap-2 pt-2">
{sprint.status !== 'closed' && (
<>
<Button
variant="outline"
size="sm"
onClick={() => handleEdit(sprint)}
>
<Edit className="h-4 w-4 mr-1" />
Edit
</Button>
{sprint.status === 'planning' && (
<Button
variant="default"
size="sm"
onClick={() => handleActivate(sprint)}
>
<PlayCircle className="h-4 w-4 mr-1" />
Activate
</Button>
)}
{sprint.status === 'active' && (
<Button
variant="destructive"
size="sm"
onClick={() => handleCloseSprint(sprint)}
>
<XCircle className="h-4 w-4 mr-1" />
Close
</Button>
)}
</>
)}
</div>
</div>
</Card>
);
})}
</div>
{sprints.length === 0 && (
<div className="text-center py-12 text-muted-foreground">
No sprints yet. Create your first sprint to get started.
</div>
)}
{/* Edit Dialog */}
<Dialog open={editDialogOpen} onOpenChange={setEditDialogOpen}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>Edit Sprint</DialogTitle>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="edit-name">Sprint Name</Label>
<Input
id="edit-name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="edit-goal">Sprint Goal</Label>
<Textarea
id="edit-goal"
value={formData.goal}
onChange={(e) => setFormData({ ...formData, goal: e.target.value })}
rows={3}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="edit-start">Start Date</Label>
<Input
id="edit-start"
type="date"
value={formData.startDate}
onChange={(e) => setFormData({ ...formData, startDate: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="edit-end">End Date</Label>
<Input
id="edit-end"
type="date"
value={formData.endDate}
onChange={(e) => setFormData({ ...formData, endDate: e.target.value })}
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="edit-capacity">Capacity (Story Points)</Label>
<Input
id="edit-capacity"
type="number"
value={formData.capacity}
onChange={(e) => setFormData({ ...formData, capacity: parseInt(e.target.value) || 0 })}
/>
</div>
</div>
<div className="flex justify-end gap-2">
<Button variant="outline" onClick={() => setEditDialogOpen(false)}>
Cancel
</Button>
<Button onClick={handleSave}>Save</Button>
</div>
</DialogContent>
</Dialog>
{/* Create Dialog */}
<Dialog open={createDialogOpen} onOpenChange={setCreateDialogOpen}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>Create New Sprint</DialogTitle>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="create-name">Sprint Name</Label>
<Input
id="create-name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
placeholder="Sprint 1"
/>
</div>
<div className="space-y-2">
<Label htmlFor="create-goal">Sprint Goal</Label>
<Textarea
id="create-goal"
value={formData.goal}
onChange={(e) => setFormData({ ...formData, goal: e.target.value })}
rows={3}
placeholder="What do you want to achieve in this sprint?"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="create-start">Start Date</Label>
<Input
id="create-start"
type="date"
value={formData.startDate}
onChange={(e) => setFormData({ ...formData, startDate: e.target.value })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="create-end">End Date</Label>
<Input
id="create-end"
type="date"
value={formData.endDate}
onChange={(e) => setFormData({ ...formData, endDate: e.target.value })}
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="create-capacity">Capacity (Story Points)</Label>
<Input
id="create-capacity"
type="number"
value={formData.capacity || ''}
onChange={(e) => setFormData({ ...formData, capacity: parseInt(e.target.value) || 0 })}
placeholder="40"
/>
</div>
</div>
<div className="flex justify-end gap-2">
<Button variant="outline" onClick={() => setCreateDialogOpen(false)}>
Cancel
</Button>
<Button onClick={handleCreate}>Create</Button>
</div>
</DialogContent>
</Dialog>
</div>
);
};