Spaces:
Sleeping
Sleeping
| import React, { useState, useEffect } from "react"; | |
| import { useForm } from "react-hook-form"; | |
| import { zodResolver } from "@hookform/resolvers/zod"; | |
| import * as z from "zod"; | |
| import { CalendarIcon, Loader2 } from "lucide-react"; | |
| import { format } from "date-fns"; | |
| import { | |
| Dialog, | |
| DialogContent, | |
| DialogDescription, | |
| DialogFooter, | |
| DialogHeader, | |
| DialogTitle, | |
| } from "@/components/ui/dialog"; | |
| import { | |
| Form, | |
| FormControl, | |
| FormField, | |
| FormItem, | |
| FormLabel, | |
| FormMessage, | |
| } from "@/components/ui/form"; | |
| import { Input } from "@/components/ui/input"; | |
| import { Button } from "@/components/ui/button"; | |
| import { Textarea } from "@/components/ui/textarea"; | |
| import { | |
| Select, | |
| SelectContent, | |
| SelectItem, | |
| SelectTrigger, | |
| SelectValue, | |
| } from "@/components/ui/select"; | |
| import { | |
| Popover, | |
| PopoverContent, | |
| PopoverTrigger, | |
| } from "@/components/ui/popover"; | |
| import { Calendar } from "@/components/ui/calendar"; | |
| import { toast } from "@/lib/custom-toast"; | |
| import { tasksApi, TaskCreateRequest } from "@/services/tasksApi"; | |
| import { Employee } from "@/types"; | |
| import SlateRichTextEditor from "@/components/custom/SlateRichTextEditor"; | |
| // Form schema | |
| const formSchema = z.object({ | |
| title: z.string().min(1, "Title is required"), | |
| description: z.string().min(1, "Description is required"), | |
| type: z.string().min(1, "Type is required"), | |
| priority: z.string().min(1, "Priority is required"), | |
| status: z.string().min(1, "Status is required"), | |
| assignedTo: z.number().nullable(), | |
| estimates: z.string().optional(), | |
| stateDate: z.date(), | |
| endDate: z.date().nullable().optional(), | |
| }); | |
| interface AddTaskDialogProps { | |
| issueId: number; | |
| onTaskAdded: () => void; | |
| employees: Employee[]; | |
| isOpen: boolean; | |
| onOpenChange: (open: boolean) => void; | |
| } | |
| const AddTaskDialog: React.FC<AddTaskDialogProps> = ({ | |
| issueId, | |
| onTaskAdded, | |
| employees, | |
| isOpen, | |
| onOpenChange, | |
| }) => { | |
| const [isSubmitting, setIsSubmitting] = useState(false); | |
| console.log("Dialog isOpen state:", isOpen); // Debug log | |
| // Initialize the form | |
| const form = useForm<z.infer<typeof formSchema>>({ | |
| resolver: zodResolver(formSchema), | |
| defaultValues: { | |
| title: "", | |
| description: "", | |
| type: "Task", | |
| priority: "Medium", | |
| status: "New", | |
| assignedTo: null, | |
| estimates: "", | |
| stateDate: new Date(), | |
| endDate: null, | |
| }, | |
| }); | |
| // Reset form when dialog opens | |
| useEffect(() => { | |
| if (isOpen) { | |
| console.log("Dialog opened, resetting form"); // Debug log | |
| form.reset(); | |
| } | |
| }, [isOpen, form]); | |
| // Handle form submission | |
| const onSubmit = async (values: z.infer<typeof formSchema>) => { | |
| setIsSubmitting(true); | |
| try { | |
| // Prepare task data | |
| const taskData: TaskCreateRequest = { | |
| title: values.title, | |
| description: values.description, | |
| type: values.type, | |
| priority: values.priority, | |
| status: values.status, | |
| assignedTo: values.assignedTo, | |
| estimates: values.estimates || "0", | |
| esUnit: 1, // Default to hours | |
| stateDate: values.stateDate.toISOString().split('T')[0], | |
| endDate: values.endDate ? values.endDate.toISOString().split('T')[0] : null, | |
| remainingHr: 0, | |
| sprintName: null, | |
| issuesId: issueId, | |
| }; | |
| console.log("Creating task with data:", taskData); // Debug log | |
| // Create the task | |
| await tasksApi.create(taskData); | |
| // Show success message | |
| toast.success("Task created successfully"); | |
| // Close the dialog and refresh the tasks list | |
| onOpenChange(false); | |
| onTaskAdded(); | |
| } catch (error) { | |
| console.error("Error creating task:", error); | |
| toast.error("Failed to create task. Please try again."); | |
| } finally { | |
| setIsSubmitting(false); | |
| } | |
| }; | |
| const handleOpenChange = (newOpen: boolean) => { | |
| console.log("Dialog openChange triggered:", newOpen); // Debug log | |
| onOpenChange(newOpen); | |
| }; | |
| return ( | |
| <Dialog open={isOpen} onOpenChange={handleOpenChange}> | |
| <DialogContent className="sm:max-w-[550px]"> | |
| <DialogHeader> | |
| <DialogTitle>Add New Task</DialogTitle> | |
| <DialogDescription> | |
| Create a new task linked to this issue. | |
| </DialogDescription> | |
| </DialogHeader> | |
| <Form {...form}> | |
| <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-5"> | |
| <FormField | |
| control={form.control} | |
| name="title" | |
| render={({ field }) => ( | |
| <FormItem> | |
| <FormLabel>Title</FormLabel> | |
| <FormControl> | |
| <Input placeholder="Task title" {...field} /> | |
| </FormControl> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| <FormField | |
| control={form.control} | |
| name="description" | |
| render={({ field }) => ( | |
| <FormItem> | |
| <FormLabel>Description</FormLabel> | |
| <FormControl> | |
| <SlateRichTextEditor | |
| initialValue={field.value || ''} | |
| onChange={(value) => field.onChange(value)} | |
| placeholder="Task description" | |
| minHeight="150px" | |
| /> | |
| </FormControl> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| <div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> | |
| <FormField | |
| control={form.control} | |
| name="type" | |
| render={({ field }) => ( | |
| <FormItem> | |
| <FormLabel>Type</FormLabel> | |
| <FormControl> | |
| <Select | |
| value={field.value} | |
| onValueChange={field.onChange} | |
| > | |
| <SelectTrigger> | |
| <SelectValue placeholder="Select type" /> | |
| </SelectTrigger> | |
| <SelectContent> | |
| <SelectItem value="Task">Task</SelectItem> | |
| <SelectItem value="Bug">Bug</SelectItem> | |
| <SelectItem value="Documentation">Documentation</SelectItem> | |
| <SelectItem value="Enhancement">Enhancement</SelectItem> | |
| </SelectContent> | |
| </Select> | |
| </FormControl> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| <FormField | |
| control={form.control} | |
| name="priority" | |
| render={({ field }) => ( | |
| <FormItem> | |
| <FormLabel>Priority</FormLabel> | |
| <FormControl> | |
| <Select | |
| value={field.value} | |
| onValueChange={field.onChange} | |
| > | |
| <SelectTrigger> | |
| <SelectValue placeholder="Select priority" /> | |
| </SelectTrigger> | |
| <SelectContent> | |
| <SelectItem value="Low">Low</SelectItem> | |
| <SelectItem value="Medium">Medium</SelectItem> | |
| <SelectItem value="High">High</SelectItem> | |
| <SelectItem value="Critical">Critical</SelectItem> | |
| </SelectContent> | |
| </Select> | |
| </FormControl> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| </div> | |
| <div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> | |
| <FormField | |
| control={form.control} | |
| name="status" | |
| render={({ field }) => ( | |
| <FormItem> | |
| <FormLabel>Status</FormLabel> | |
| <FormControl> | |
| <Select | |
| value={field.value} | |
| onValueChange={field.onChange} | |
| > | |
| <SelectTrigger> | |
| <SelectValue placeholder="Select status" /> | |
| </SelectTrigger> | |
| <SelectContent> | |
| <SelectItem value="New">New</SelectItem> | |
| <SelectItem value="In Progress">In Progress</SelectItem> | |
| <SelectItem value="In Review">In Review</SelectItem> | |
| <SelectItem value="Completed">Completed</SelectItem> | |
| <SelectItem value="Closed">Closed</SelectItem> | |
| </SelectContent> | |
| </Select> | |
| </FormControl> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| <FormField | |
| control={form.control} | |
| name="assignedTo" | |
| render={({ field }) => ( | |
| <FormItem> | |
| <FormLabel>Assigned To</FormLabel> | |
| <FormControl> | |
| <Select | |
| value={field.value ? field.value.toString() : "null"} | |
| onValueChange={(value) => field.onChange(value === "null" ? null : parseInt(value))} | |
| > | |
| <SelectTrigger> | |
| <SelectValue placeholder="Select assignee" /> | |
| </SelectTrigger> | |
| <SelectContent> | |
| <SelectItem value="null">Unassigned</SelectItem> | |
| {employees.map((employee) => ( | |
| <SelectItem key={employee.id} value={employee.id.toString()}> | |
| {employee.firstName} {employee.lastName} | |
| </SelectItem> | |
| ))} | |
| </SelectContent> | |
| </Select> | |
| </FormControl> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| </div> | |
| <div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> | |
| <FormField | |
| control={form.control} | |
| name="stateDate" | |
| render={({ field }) => ( | |
| <FormItem className="flex flex-col"> | |
| <FormLabel>Start Date</FormLabel> | |
| <Popover> | |
| <PopoverTrigger asChild> | |
| <FormControl> | |
| <Button | |
| type="button" | |
| variant="outline" | |
| className={`w-full pl-3 text-left font-normal ${!field.value ? "text-muted-foreground" : ""}`} | |
| > | |
| {field.value ? ( | |
| format(field.value, "PPP") | |
| ) : ( | |
| <span>Pick a date</span> | |
| )} | |
| <CalendarIcon className="ml-auto h-4 w-4 opacity-50" /> | |
| </Button> | |
| </FormControl> | |
| </PopoverTrigger> | |
| <PopoverContent className="w-auto p-0" align="start"> | |
| <Calendar | |
| mode="single" | |
| selected={field.value} | |
| onSelect={field.onChange} | |
| initialFocus | |
| /> | |
| </PopoverContent> | |
| </Popover> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| <FormField | |
| control={form.control} | |
| name="endDate" | |
| render={({ field }) => ( | |
| <FormItem className="flex flex-col"> | |
| <FormLabel>Due Date</FormLabel> | |
| <Popover> | |
| <PopoverTrigger asChild> | |
| <FormControl> | |
| <Button | |
| type="button" | |
| variant="outline" | |
| className={`w-full pl-3 text-left font-normal ${!field.value ? "text-muted-foreground" : ""}`} | |
| > | |
| {field.value ? ( | |
| format(field.value, "PPP") | |
| ) : ( | |
| <span>Pick a date</span> | |
| )} | |
| <CalendarIcon className="ml-auto h-4 w-4 opacity-50" /> | |
| </Button> | |
| </FormControl> | |
| </PopoverTrigger> | |
| <PopoverContent className="w-auto p-0" align="start"> | |
| <Calendar | |
| mode="single" | |
| selected={field.value || undefined} | |
| onSelect={field.onChange} | |
| initialFocus | |
| /> | |
| </PopoverContent> | |
| </Popover> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| </div> | |
| <FormField | |
| control={form.control} | |
| name="estimates" | |
| render={({ field }) => ( | |
| <FormItem> | |
| <FormLabel>Estimated Hours</FormLabel> | |
| <FormControl> | |
| <Input | |
| type="number" | |
| placeholder="Estimated hours to complete" | |
| {...field} | |
| /> | |
| </FormControl> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| <DialogFooter className="pt-2"> | |
| <Button | |
| type="button" | |
| variant="outline" | |
| onClick={() => onOpenChange(false)} | |
| disabled={isSubmitting} | |
| > | |
| Cancel | |
| </Button> | |
| <Button type="submit" disabled={isSubmitting}> | |
| {isSubmitting ? ( | |
| <> | |
| <Loader2 className="mr-2 h-4 w-4 animate-spin" /> | |
| Creating... | |
| </> | |
| ) : ( | |
| "Create Task" | |
| )} | |
| </Button> | |
| </DialogFooter> | |
| </form> | |
| </Form> | |
| </DialogContent> | |
| </Dialog> | |
| ); | |
| }; | |
| export default AddTaskDialog; |