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 (
);
}