Files
ColaFlow-Web/components/tasks/task-card.tsx
Yaojia Wang 79f210d0ee fix(frontend): Implement Task Edit functionality - Sprint 4 Story 2
Completed the missing Task Edit feature identified as high-priority
issue in Sprint 4 testing.

Changes:
- Created TaskEditDialog component (285 lines)
  - Full form with title, description, priority, hours fields
  - React Hook Form + Zod validation
  - Modal dialog with proper UX (loading states, error handling)
  - Support for all Task fields (estimated/actual hours)
- Integrated TaskEditDialog into TaskCard component
  - Added isEditDialogOpen state management
  - Connected Edit menu item to open dialog
  - Proper event propagation handling

Features:
- Complete CRUD: Users can now edit existing tasks
- Form validation with clear error messages
- Optimistic updates via React Query
- Toast notifications for success/error
- Responsive design matches existing UI

Testing:
- Frontend compiles successfully with no errors
- Component follows existing patterns (Story Form, Task Quick Add)
- Consistent with shadcn/ui design system

Fixes: Task Edit TODO at task-card.tsx:147
Related: Sprint 4 Story 2 - Task Management
Test Report: SPRINT_4_STORY_1-3_FRONTEND_TEST_REPORT.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 23:01:04 +01:00

175 lines
5.2 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';
import { TaskEditDialog } from './task-edit-dialog';
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 [isEditDialogOpen, setIsEditDialogOpen] = 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={() => setIsEditDialogOpen(true)}>
<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>
{/* Edit Dialog */}
<TaskEditDialog
task={task}
open={isEditDialogOpen}
onOpenChange={setIsEditDialogOpen}
/>
</Card>
);
}