From 6a3895dc4967225d414f28a01f98810528e60e2b Mon Sep 17 00:00:00 2001 From: Griffin Cooper <72273901+gcooper407@users.noreply.github.com> Date: Wed, 19 Nov 2025 20:26:18 -0500 Subject: [PATCH 1/2] #3667: with @Jfresh18 -- added form validation to TaskFormModal and fixed form clearing issue on incomplete submit --- src/frontend/src/components/NERFormModal.tsx | 14 ++++-- .../TaskList/TaskFormModal.tsx | 43 +++++++++++++------ .../TaskList/v2/TaskColumn.tsx | 31 ++++++------- 3 files changed, 54 insertions(+), 34 deletions(-) diff --git a/src/frontend/src/components/NERFormModal.tsx b/src/frontend/src/components/NERFormModal.tsx index 9b07327840..b6deeabdef 100644 --- a/src/frontend/src/components/NERFormModal.tsx +++ b/src/frontend/src/components/NERFormModal.tsx @@ -1,6 +1,7 @@ import { ReactNode } from 'react'; import { FieldValues, UseFormHandleSubmit, UseFormReset } from 'react-hook-form'; import NERModal, { NERModalProps } from './NERModal'; +import { useToast } from '../hooks/toasts.hooks'; interface NERFormModalProps extends NERModalProps { reset: UseFormReset; @@ -27,18 +28,23 @@ const NERFormModal = ({ hideBackDrop = false, paperProps }: NERFormModalProps) => { + const toast = useToast(); /** * Wrapper function for onSubmit so that form data is reset after submit */ const onSubmitWrapper = async (data: any) => { - await onFormSubmit(data); - reset(); + try { + await onFormSubmit(data); + reset(); + } catch (e: unknown) { + if (e instanceof Error) toast.error(e.message, 6000); + } }; - const handleFormSubmit = (e: React.FormEvent) => { + const handleFormSubmit = async (e: React.FormEvent) => { e.preventDefault(); e.stopPropagation(); // Prevent event bubbling - handleUseFormSubmit(onSubmitWrapper)(e); + await handleUseFormSubmit(onSubmitWrapper)(e); }; return ( diff --git a/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/TaskList/TaskFormModal.tsx b/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/TaskList/TaskFormModal.tsx index 2cb26fdb43..62dddcd63d 100644 --- a/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/TaskList/TaskFormModal.tsx +++ b/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/TaskList/TaskFormModal.tsx @@ -2,7 +2,7 @@ import { yupResolver } from '@hookform/resolvers/yup'; import { Autocomplete, FormControl, FormHelperText, FormLabel, Grid, MenuItem, TextField } from '@mui/material'; import { DatePicker } from '@mui/x-date-pickers'; import { Controller, useForm } from 'react-hook-form'; -import { countWords, isGuest, isUnderWordCount, notGuest, Task, TaskPriority, TeamPreview } from 'shared'; +import { countWords, isGuest, isUnderWordCount, notGuest, Task, TaskStatus, TaskPriority, TeamPreview } from 'shared'; import { useAllUsers, useCurrentUser } from '../../../../hooks/users.hooks'; import * as yup from 'yup'; import { taskUserToAutocompleteOption } from '../../../../utils/task.utils'; @@ -10,16 +10,6 @@ import NERFormModal from '../../../../components/NERFormModal'; import LoadingIndicator from '../../../../components/LoadingIndicator'; import ErrorPage from '../../../ErrorPage'; -const schema = yup.object().shape({ - notes: yup.string().optional(), - startDate: yup.date().optional(), - deadline: yup.date().optional(), - priority: yup.mixed().oneOf(Object.values(TaskPriority)).required(), - assignees: yup.array().required(), - title: yup.string().required(), - taskId: yup.string().required() -}); - export interface EditTaskFormInput { taskId: string; title: string; @@ -32,6 +22,7 @@ export interface EditTaskFormInput { interface TaskFormModalProps { task?: Task; + status?: Task['status']; teams: TeamPreview[]; modalShow: boolean; onHide: () => void; @@ -39,7 +30,33 @@ interface TaskFormModalProps { onReset?: () => void; } -const TaskFormModal: React.FC = ({ task, onSubmit, modalShow, onHide, onReset }) => { +const TaskFormModal: React.FC = ({ task, status, onSubmit, modalShow, onHide, onReset }) => { + let schema; + + if (status === TaskStatus.IN_PROGRESS) { + console.log('here'); + schema = yup.object().shape({ + notes: yup.string().optional(), + startDate: yup.date().optional(), + deadline: yup.date().required('Deadline is required for In Progress tasks'), + priority: yup.mixed().oneOf(Object.values(TaskPriority)).required(), + assignees: yup.array().required().min(1, 'At least one assignee is required for In Progress tasks'), + title: yup.string().required(), + taskId: yup.string().required() + }); + } else { + console.log('there'); + schema = yup.object().shape({ + notes: yup.string().optional(), + startDate: yup.date().optional(), + deadline: yup.date().optional(), + priority: yup.mixed().oneOf(Object.values(TaskPriority)).required(), + assignees: yup.array().required(), + title: yup.string().required(), + taskId: yup.string().required() + }); + } + const user = useCurrentUser(); const { data: users, isLoading, isError, error } = useAllUsers(); @@ -161,6 +178,7 @@ const TaskFormModal: React.FC = ({ task, onSubmit, modalShow /> )} /> + {errors.assignees?.message} @@ -199,6 +217,7 @@ const TaskFormModal: React.FC = ({ task, onSubmit, modalShow /> )} /> + {errors.deadline?.message} diff --git a/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/TaskList/v2/TaskColumn.tsx b/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/TaskList/v2/TaskColumn.tsx index 5fcf249231..ff1f42ff05 100644 --- a/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/TaskList/v2/TaskColumn.tsx +++ b/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/TaskList/v2/TaskColumn.tsx @@ -30,30 +30,25 @@ export const TaskColumn = ({ const theme = useTheme(); const handleCreateTask = async ({ notes, title, deadline, assignees, priority, startDate }: EditTaskFormInput) => { - try { - const task = await createTask({ - wbsNum: project.wbsNum, - title, - deadline: deadline ? transformDate(deadline) : undefined, - startDate: startDate ? transformDate(startDate) : undefined, - priority, - status: status as TaskStatus, - assignees, - notes - }); - onAddTask(task); - toast.success('Task Successfully Created!'); - } catch (e: unknown) { - if (e instanceof Error) { - toast.error(e.message, 6000); - } - } + const task = await createTask({ + wbsNum: project.wbsNum, + title, + deadline: deadline ? transformDate(deadline) : undefined, + startDate: startDate ? transformDate(startDate) : undefined, + priority, + status: status as TaskStatus, + assignees, + notes + }); + onAddTask(task); + toast.success('Task Successfully Created!'); setShowCreateTaskModal(false); }; return ( <> setShowCreateTaskModal(false)} modalShow={showCreateTaskModal} From d06a83773c2d434db238846d5439df9631f26619 Mon Sep 17 00:00:00 2001 From: John Cassidy Date: Wed, 3 Dec 2025 18:51:47 -0500 Subject: [PATCH 2/2] add frontend validation for word count of task notes --- .../TaskList/TaskFormModal.tsx | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/TaskList/TaskFormModal.tsx b/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/TaskList/TaskFormModal.tsx index 62dddcd63d..7a5f80760f 100644 --- a/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/TaskList/TaskFormModal.tsx +++ b/src/frontend/src/pages/ProjectDetailPage/ProjectViewContainer/TaskList/TaskFormModal.tsx @@ -34,9 +34,15 @@ const TaskFormModal: React.FC = ({ task, status, onSubmit, m let schema; if (status === TaskStatus.IN_PROGRESS) { - console.log('here'); schema = yup.object().shape({ - notes: yup.string().optional(), + notes: yup + .string() + .optional() + .test((value) => { + if (!value) return true; + const wordCount = countWords(value); + return wordCount < 250; + }), startDate: yup.date().optional(), deadline: yup.date().required('Deadline is required for In Progress tasks'), priority: yup.mixed().oneOf(Object.values(TaskPriority)).required(), @@ -45,9 +51,15 @@ const TaskFormModal: React.FC = ({ task, status, onSubmit, m taskId: yup.string().required() }); } else { - console.log('there'); schema = yup.object().shape({ - notes: yup.string().optional(), + notes: yup + .string() + .optional() + .test((value) => { + if (!value) return true; + const wordCount = countWords(value); + return wordCount < 250; + }), startDate: yup.date().optional(), deadline: yup.date().optional(), priority: yup.mixed().oneOf(Object.values(TaskPriority)).required(),