Files
ColaFlow-Web/components/tasks/task-list.tsx
Yaojia Wang 8fe6d64e2e 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>
2025-11-05 22:35:38 +01:00

141 lines
4.8 KiB
TypeScript

'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>
);
}