Files
ColaFlow-Web/components/tasks/task-card.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

166 lines
4.9 KiB
TypeScript

'use client';
import { useState } from 'react';
import { Task, WorkItemStatus } from '@/types/project';
import { useChangeTaskStatus, useUpdateTask, useDeleteTask } from '@/lib/hooks/use-tasks';
import { Card, CardContent, CardHeader } from '@/components/ui/card';
import { Checkbox } from '@/components/ui/checkbox';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
MoreHorizontal,
Pencil,
Trash2,
Clock,
User,
CheckCircle2,
Circle
} from 'lucide-react';
import { cn } from '@/lib/utils';
interface TaskCardProps {
task: Task;
storyId: string;
}
const priorityColors = {
Critical: 'bg-red-500 text-white',
High: 'bg-orange-500 text-white',
Medium: 'bg-yellow-500 text-white',
Low: 'bg-blue-500 text-white',
};
const statusColors = {
Todo: 'text-gray-500',
InProgress: 'text-blue-500',
Done: 'text-green-500',
Blocked: 'text-red-500',
};
export function TaskCard({ task, storyId }: TaskCardProps) {
const [isExpanded, setIsExpanded] = useState(false);
const changeStatus = useChangeTaskStatus();
const updateTask = useUpdateTask();
const deleteTask = useDeleteTask();
const isDone = task.status === 'Done';
const handleCheckboxChange = (checked: boolean) => {
const newStatus: WorkItemStatus = checked ? 'Done' : 'Todo';
changeStatus.mutate({ id: task.id, status: newStatus });
};
const handleDelete = () => {
if (confirm('Are you sure you want to delete this task?')) {
deleteTask.mutate(task.id);
}
};
return (
<Card
className={cn(
"transition-all duration-200 hover:shadow-md cursor-pointer",
isDone && "opacity-60"
)}
onClick={() => setIsExpanded(!isExpanded)}
>
<CardHeader className="p-4">
<div className="flex items-start gap-3">
{/* Checkbox */}
<div onClick={(e) => e.stopPropagation()}>
<Checkbox
checked={isDone}
onCheckedChange={handleCheckboxChange}
disabled={changeStatus.isPending}
className="mt-1"
/>
</div>
{/* Task Content */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2">
<h4 className={cn(
"font-medium text-sm",
isDone && "line-through text-muted-foreground"
)}>
{task.title}
</h4>
<Badge
variant="secondary"
className={cn("text-xs", priorityColors[task.priority])}
>
{task.priority}
</Badge>
</div>
{/* Metadata */}
<div className="flex items-center gap-4 text-xs text-muted-foreground">
{task.estimatedHours && (
<div className="flex items-center gap-1">
<Clock className="w-3 h-3" />
<span>{task.estimatedHours}h</span>
</div>
)}
{task.assigneeId && (
<div className="flex items-center gap-1">
<User className="w-3 h-3" />
<span>Assigned</span>
</div>
)}
<div className={cn("flex items-center gap-1", statusColors[task.status])}>
{isDone ? (
<CheckCircle2 className="w-3 h-3" />
) : (
<Circle className="w-3 h-3" />
)}
<span>{task.status}</span>
</div>
</div>
{/* Description (expanded) */}
{isExpanded && task.description && (
<div className="mt-3 text-sm text-muted-foreground">
{task.description}
</div>
)}
</div>
{/* Actions Menu */}
<div onClick={(e) => e.stopPropagation()}>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0"
>
<MoreHorizontal className="w-4 h-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => {/* TODO: Open edit dialog */}}>
<Pencil className="w-4 h-4 mr-2" />
Edit
</DropdownMenuItem>
<DropdownMenuItem
onClick={handleDelete}
className="text-destructive"
>
<Trash2 className="w-4 h-4 mr-2" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</CardHeader>
</Card>
);
}