diff --git a/components/tasks/task-card.tsx b/components/tasks/task-card.tsx index 469785c..87d18fb 100644 --- a/components/tasks/task-card.tsx +++ b/components/tasks/task-card.tsx @@ -23,6 +23,7 @@ import { Circle } from 'lucide-react'; import { cn } from '@/lib/utils'; +import { TaskEditDialog } from './task-edit-dialog'; interface TaskCardProps { task: Task; @@ -45,6 +46,7 @@ const statusColors = { 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(); @@ -144,7 +146,7 @@ export function TaskCard({ task, storyId }: TaskCardProps) { - {/* TODO: Open edit dialog */}}> + setIsEditDialogOpen(true)}> Edit @@ -160,6 +162,13 @@ export function TaskCard({ task, storyId }: TaskCardProps) { + + {/* Edit Dialog */} + ); } diff --git a/components/tasks/task-edit-dialog.tsx b/components/tasks/task-edit-dialog.tsx new file mode 100644 index 0000000..26c6b06 --- /dev/null +++ b/components/tasks/task-edit-dialog.tsx @@ -0,0 +1,273 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import * as z from 'zod'; +import { Task, UpdateTaskDto, WorkItemPriority } from '@/types/project'; +import { useUpdateTask } from '@/lib/hooks/use-tasks'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from '@/components/ui/dialog'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + FormDescription, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { Textarea } from '@/components/ui/textarea'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Button } from '@/components/ui/button'; +import { Loader2 } from 'lucide-react'; + +interface TaskEditDialogProps { + task: Task; + open: boolean; + onOpenChange: (open: boolean) => void; +} + +const taskSchema = z.object({ + title: z.string().min(1, 'Title is required').max(200, 'Title must be less than 200 characters'), + description: z.string().max(2000, 'Description must be less than 2000 characters').optional(), + priority: z.enum(['Low', 'Medium', 'High', 'Critical']), + estimatedHours: z + .number() + .min(0, 'Estimated hours must be positive') + .max(1000, 'Estimated hours must be less than 1000') + .optional() + .or(z.literal('')), + actualHours: z + .number() + .min(0, 'Actual hours must be positive') + .max(1000, 'Actual hours must be less than 1000') + .optional() + .or(z.literal('')), +}); + +type TaskFormValues = z.infer; + +export function TaskEditDialog({ task, open, onOpenChange }: TaskEditDialogProps) { + const updateTask = useUpdateTask(); + const [isSubmitting, setIsSubmitting] = useState(false); + + const form = useForm({ + resolver: zodResolver(taskSchema), + defaultValues: { + title: task.title, + description: task.description || '', + priority: task.priority, + estimatedHours: task.estimatedHours || ('' as any), + actualHours: task.actualHours || ('' as any), + }, + }); + + // Reset form when task changes + useEffect(() => { + form.reset({ + title: task.title, + description: task.description || '', + priority: task.priority, + estimatedHours: task.estimatedHours || ('' as any), + actualHours: task.actualHours || ('' as any), + }); + }, [task, form]); + + async function onSubmit(data: TaskFormValues) { + setIsSubmitting(true); + try { + const updateData: UpdateTaskDto = { + title: data.title, + description: data.description || undefined, + priority: data.priority, + estimatedHours: typeof data.estimatedHours === 'number' ? data.estimatedHours : undefined, + actualHours: typeof data.actualHours === 'number' ? data.actualHours : undefined, + }; + + await updateTask.mutateAsync({ + id: task.id, + data: updateData, + }); + + onOpenChange(false); + form.reset(); + } catch (error) { + // Error handling is done in the mutation hook + } finally { + setIsSubmitting(false); + } + } + + return ( + + + + Edit Task + + +
+ + {/* Title */} + ( + + Title + + + + + + )} + /> + + {/* Description */} + ( + + Description + +