import React, { useState, useEffect } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import * as z from "zod"; import { format } from "date-fns"; import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Task, TaskCreateRequest } from "@/services/tasksApi"; import { issuesApi, employeeService, projectService } from "@/lib/api"; // Import the project service import { IssueApi, Employee, ProjectApi } from "@/types"; // Import ProjectApi import { Dropdown, DropdownOption } from "@/components/ui/dropdown"; // Import the Dropdown component import { useAuth } from "@/lib/auth-context"; // Correct import for useAuth import SlateRichTextEditor from "@/components/custom/SlateRichTextEditor"; // Import SlateRichTextEditor // Helper function to ensure content is HTML formatted const ensureHtmlFormat = (content: string): string => { if (!content) return ''; // Check if the content already looks like HTML (contains HTML tags) const hasHtmlTags = /<[a-z][\s\S]*>/i.test(content); // If it's already HTML, return as is if (hasHtmlTags) { return content; } // Otherwise, wrap it in a paragraph tag return `

${content}

`; }; // Extend TaskCreateRequest to include projectId interface ExtendedTaskCreateRequest extends TaskCreateRequest { projectId?: number | null; } const taskSchema = z.object({ id: z.number().optional(), type: z.string().min(1, "Type is required"), description: z.string().min(1, "Description is required"), sprintName: z.string().nullable().optional(), estimates: z.string().min(1, "Estimates is required"), esUnit: z.number().nullable().optional(), stateDate: z.string().min(1, "Start date is required"), endDate: z.string().nullable().optional(), assignedTo: z.number().nullable().optional(), remainingHr: z.number().nullable().optional(), status: z.string().min(1, "Status is required"), issuesId: z.number().min(1, "Feature is required"), title: z.string().min(1, "Title is required"), projectId: z.number().nullable().optional(), // Add projectId field }); interface TaskFormProps { task?: Task & { projectId?: number | null }; onSubmit: (data: ExtendedTaskCreateRequest) => Promise; onCancel: () => void; isLoading?: boolean; } const statusOptions = ["New", "In Progress", "In Review", "Completed", "Closed"]; const typeOptions = ["Task"]; const estimateUnitOptions: DropdownOption[] = [ { label: "Days", value: "1" }, { label: "Hours", value: "2" } ]; export function TaskForm({ task, onSubmit, onCancel, isLoading }: TaskFormProps) { const [issues, setIssues] = useState([]); const [filteredIssues, setFilteredIssues] = useState([]); const [isLoadingIssues, setIsLoadingIssues] = useState(false); const [employees, setEmployees] = useState([]); const [isLoadingEmployees, setIsLoadingEmployees] = useState(false); const [projects, setProjects] = useState([]); const [isLoadingProjects, setIsLoadingProjects] = useState(false); const isNewTask = !task?.id; const { userData } = useAuth(); // Add useAuth hook to get current user // Helper function to format date strings for input fields const formatDateStringForInput = (dateString: string): string => { // If date includes time part (T), parse and format it if (dateString.includes('T')) { const date = new Date(dateString); return format(date, "yyyy-MM-dd"); } // Return as is if it's already in yyyy-MM-dd format return dateString; }; const defaultValues: ExtendedTaskCreateRequest = { id: task?.id || 0, type: task?.type || "Task", description: task?.description ? ensureHtmlFormat(task.description) : "", sprintName: task?.sprintName || null, estimates: task?.estimates || (isNewTask ? "8" : ""), esUnit: task?.esUnit || 1, // Default to Days (1) stateDate: task?.stateDate ? formatDateStringForInput(task.stateDate) : format(new Date(), "yyyy-MM-dd"), endDate: task?.endDate ? formatDateStringForInput(task.endDate) : null, assignedTo: task?.assignedTo || null, remainingHr: task?.remainingHr || null, status: task?.status || "New", issuesId: task?.issuesId || null, title: task?.title || "", projectId: task?.projectId || null, // Add projectId field }; const form = useForm({ resolver: zodResolver(taskSchema), defaultValues, }); // Get the current projectId value from the form const selectedProjectId = form.watch("projectId"); useEffect(() => { // Reset form when task prop changes if (task) { console.log("Initializing form with task:", task); form.reset({ ...defaultValues, description: task.description || "" // Ensure description is properly set }); } }, [task]); // Additional effect to specifically handle description field initialization useEffect(() => { if (task?.description) { console.log("Setting description value:", task.description); form.setValue("description", ensureHtmlFormat(task.description)); } }, [task?.description, form]); useEffect(() => { // Fetch issues when the component mounts const fetchIssues = async () => { setIsLoadingIssues(true); try { const issuesData = await issuesApi.getAll(); setIssues(issuesData); setFilteredIssues(issuesData); // Initially, all issues are available } catch (error) { console.error("Error fetching issues:", error); } finally { setIsLoadingIssues(false); } }; // Fetch employees when the component mounts const fetchEmployees = async () => { setIsLoadingEmployees(true); try { const employeesData = await employeeService.getAll(); setEmployees(employeesData); console.log("Fetched employees:", employeesData); } catch (error) { console.error("Error fetching employees:", error); } finally { setIsLoadingEmployees(false); } }; // Fetch projects when the component mounts const fetchProjects = async () => { setIsLoadingProjects(true); try { let projectsData; if (userData?.employeeId) { // If user has an employee ID, get only their projects projectsData = await projectService.getByEmployeeId(userData.employeeId); } else { // Otherwise, get all projects projectsData = await projectService.getAll(); } setProjects(projectsData); console.log("Fetched projects:", projectsData); } catch (error) { console.error("Error fetching projects:", error); } finally { setIsLoadingProjects(false); } }; fetchIssues(); fetchEmployees(); fetchProjects(); }, [userData?.employeeId]); // Filter issues when project selection changes useEffect(() => { if (selectedProjectId) { const projectIssues = issues.filter(issue => issue.projectId === selectedProjectId); setFilteredIssues(projectIssues); // If we have a current issue selected that doesn't belong to this project, clear it const currentIssueId = form.getValues("issuesId"); if (currentIssueId) { const issueExists = projectIssues.some(issue => issue.id === currentIssueId); if (!issueExists) { form.setValue("issuesId", null); } } } else { // If no project selected, show all issues setFilteredIssues(issues); } }, [selectedProjectId, issues, form]); // Create dropdown options from filtered issues const issueOptions: DropdownOption[] = filteredIssues.map(issue => ({ label: `${issue.title}`, value: issue.id.toString() })); // Create dropdown options from projects const projectOptions: DropdownOption[] = [ { label: "Select Project", value: "" }, // Add an empty option ...projects.map(project => ({ label: project.projectName, value: project.id.toString() })) ]; // Create dropdown options for employees const employeeOptions: DropdownOption[] = employees.map(employee => ({ label: `${employee.firstName} ${employee.lastName}`, value: employee.id.toString() })); // Create dropdown options for status and type const statusDropdownOptions: DropdownOption[] = statusOptions.map(status => ({ label: status, value: status })); const typeDropdownOptions: DropdownOption[] = typeOptions.map(type => ({ label: type, value: type })); const handleSubmit = async (data: ExtendedTaskCreateRequest) => { try { console.log("Raw form data:", data); // Create a new object with null values for empty fields const formattedData = Object.entries(data).reduce((acc, [key, value]) => { // Check if the value is empty string or 0 (except for certain fields like ID) if (key !== 'id' && (value === '' || value === 0)) { acc[key] = null; } else { acc[key] = value; } return acc; }, {} as any); console.log("Submitting task with formatted data:", formattedData); await onSubmit(formattedData); } catch (error) { console.error("Error submitting form:", error); } }; return (
( Title )} /> ( Status )} /> ( Description { console.log("Description changed:", value); field.onChange(value); }} placeholder="Enter task description" minHeight="150px" /> )} /> {/* Project Selection Field */} ( Project { field.onChange(value ? parseInt(value) : null); }} placeholder="Select project" /> )} /> ( Feature { field.onChange(value ? parseInt(value) : null); }} placeholder={isLoadingIssues ? "Loading Features..." : "Select Feature"} disabled={isLoadingIssues || issueOptions.length === 0} /> {issueOptions.length === 0 && !isLoadingIssues && selectedProjectId && (

No Features available for this project

)}
)} /> ( Estimates )} /> ( Start Date )} /> ( End Date )} /> ( Assigned To field.onChange(value ? Number(value) : null)} placeholder="Select employee" className="w-full" disabled={isLoadingEmployees} emptyMessage={isLoadingEmployees ? "Loading employees..." : "No employees found"} /> )} /> {!isNewTask && ( ( Remaining Hours field.onChange(e.target.value ? Number(e.target.value) : null)} /> )} /> )} {/* Hidden field for esUnit to always be 1 (Days) */} {/* Hidden field for type to always be Task */}
); }