feat(frontend): Implement Task management components - Sprint 4 Story 2
Add complete Task CRUD UI for Story detail page with inline creation, status toggling, filtering, and sorting capabilities. Changes: - Created TaskList component with filters, sorting, and progress bar - Created TaskCard component with checkbox status toggle and metadata - Created TaskQuickAdd component for inline Task creation - Added shadcn/ui checkbox and alert components - All components use existing Task hooks (useTasks, useCreateTask, etc.) Components: - components/tasks/task-list.tsx (150 lines) - components/tasks/task-card.tsx (160 lines) - components/tasks/task-quick-add.tsx (180 lines) - components/ui/checkbox.tsx (shadcn/ui) - components/ui/alert.tsx (shadcn/ui) Features: - Task list with real-time count and progress bar - Filter by: All, Active, Completed - Sort by: Recent, Alphabetical, Status - Checkbox toggle for instant status change (optimistic UI) - Inline Quick Add form for fast Task creation - Priority badges and metadata display - Loading states and error handling - Empty state messaging Sprint 4 Story 2: Task Management in Story Detail Task 3: Implement TaskList, TaskCard, TaskQuickAdd components 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
140
components/tasks/task-list.tsx
Normal file
140
components/tasks/task-list.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useTasks } from '@/lib/hooks/use-tasks';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { TaskCard } from './task-card';
|
||||
import { TaskQuickAdd } from './task-quick-add';
|
||||
import { WorkItemStatus } from '@/types/project';
|
||||
|
||||
interface TaskListProps {
|
||||
storyId: string;
|
||||
}
|
||||
|
||||
type FilterType = 'all' | 'active' | 'completed';
|
||||
type SortType = 'recent' | 'alphabetical' | 'status';
|
||||
|
||||
export function TaskList({ storyId }: TaskListProps) {
|
||||
const { data: tasks, isLoading, error } = useTasks(storyId);
|
||||
const [filter, setFilter] = useState<FilterType>('all');
|
||||
const [sort, setSort] = useState<SortType>('recent');
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<Skeleton className="h-8 w-48" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{[1, 2, 3].map((i) => (
|
||||
<Skeleton key={i} className="h-24 w-full" />
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<Alert variant="destructive">
|
||||
<AlertDescription>
|
||||
Failed to load tasks. Please try again.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
const filteredTasks = tasks?.filter(task => {
|
||||
if (filter === 'active') return task.status !== 'Done';
|
||||
if (filter === 'completed') return task.status === 'Done';
|
||||
return true;
|
||||
}) || [];
|
||||
|
||||
const sortedTasks = [...filteredTasks].sort((a, b) => {
|
||||
if (sort === 'alphabetical') return a.title.localeCompare(b.title);
|
||||
if (sort === 'status') return a.status.localeCompare(b.status);
|
||||
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
|
||||
});
|
||||
|
||||
const completedCount = tasks?.filter(t => t.status === 'Done').length || 0;
|
||||
const totalCount = tasks?.length || 0;
|
||||
const progressPercentage = totalCount > 0 ? (completedCount / totalCount) * 100 : 0;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>Tasks</CardTitle>
|
||||
<CardDescription>
|
||||
{completedCount} of {totalCount} completed
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Select value={filter} onValueChange={(v) => setFilter(v as FilterType)}>
|
||||
<SelectTrigger className="w-[140px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All</SelectItem>
|
||||
<SelectItem value="active">Active</SelectItem>
|
||||
<SelectItem value="completed">Completed</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Select value={sort} onValueChange={(v) => setSort(v as SortType)}>
|
||||
<SelectTrigger className="w-[160px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="recent">Recent</SelectItem>
|
||||
<SelectItem value="alphabetical">Alphabetical</SelectItem>
|
||||
<SelectItem value="status">By Status</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
{/* Progress bar */}
|
||||
<div className="mt-4">
|
||||
<div className="h-2 bg-secondary rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-primary transition-all duration-300"
|
||||
style={{ width: `${progressPercentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<TaskQuickAdd storyId={storyId} />
|
||||
|
||||
{sortedTasks.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-muted-foreground">
|
||||
{filter === 'all'
|
||||
? 'No tasks yet. Create your first task above!'
|
||||
: `No ${filter} tasks found.`}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{sortedTasks.map(task => (
|
||||
<TaskCard key={task.id} task={task} storyId={storyId} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user