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

191 lines
5.5 KiB
TypeScript

'use client';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useCreateTask } from '@/lib/hooks/use-tasks';
import { CreateTaskDto, WorkItemPriority } from '@/types/project';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Card, CardContent } from '@/components/ui/card';
import { Plus, X } from 'lucide-react';
interface TaskQuickAddProps {
storyId: string;
}
const taskSchema = z.object({
title: z.string().min(1, 'Title is required').max(200, 'Title too long'),
priority: z.enum(['Critical', 'High', 'Medium', 'Low']),
estimatedHours: z.coerce.number().min(0).optional(),
});
type TaskFormData = z.infer<typeof taskSchema>;
export function TaskQuickAdd({ storyId }: TaskQuickAddProps) {
const [isOpen, setIsOpen] = useState(false);
const createTask = useCreateTask();
const form = useForm<TaskFormData>({
resolver: zodResolver(taskSchema),
defaultValues: {
title: '',
priority: 'Medium',
estimatedHours: undefined,
},
});
const onSubmit = async (data: TaskFormData) => {
const taskData: CreateTaskDto = {
storyId,
title: data.title,
priority: data.priority as WorkItemPriority,
estimatedHours: data.estimatedHours,
};
createTask.mutate(taskData, {
onSuccess: () => {
form.reset();
// Keep form open for batch creation
},
});
};
const handleCancel = () => {
form.reset();
setIsOpen(false);
};
if (!isOpen) {
return (
<Button
onClick={() => setIsOpen(true)}
variant="outline"
className="w-full"
size="sm"
>
<Plus className="w-4 h-4 mr-2" />
Add Task
</Button>
);
}
return (
<Card className="border-dashed">
<CardContent className="pt-4">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<div className="flex items-center justify-between mb-2">
<h4 className="text-sm font-medium">Quick Add Task</h4>
<Button
type="button"
variant="ghost"
size="sm"
onClick={handleCancel}
className="h-6 w-6 p-0"
>
<X className="w-4 h-4" />
</Button>
</div>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Title *</FormLabel>
<FormControl>
<Input
placeholder="e.g., Implement login API"
{...field}
autoFocus
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="priority"
render={({ field }) => (
<FormItem>
<FormLabel>Priority</FormLabel>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select priority" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="Critical">Critical</SelectItem>
<SelectItem value="High">High</SelectItem>
<SelectItem value="Medium">Medium</SelectItem>
<SelectItem value="Low">Low</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="estimatedHours"
render={({ field }) => (
<FormItem>
<FormLabel>Est. Hours</FormLabel>
<FormControl>
<Input
type="number"
placeholder="8"
{...field}
value={field.value ?? ''}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex gap-2">
<Button
type="submit"
size="sm"
disabled={createTask.isPending}
className="flex-1"
>
{createTask.isPending ? 'Creating...' : 'Add Task'}
</Button>
<Button
type="button"
variant="outline"
size="sm"
onClick={handleCancel}
>
Cancel
</Button>
</div>
</form>
</Form>
</CardContent>
</Card>
);
}