From a918f9608cf423f7c250783fb2f0d170df3fd14e Mon Sep 17 00:00:00 2001 From: nourshoreibah Date: Sun, 30 Aug 2026 15:47:01 -0400 Subject: [PATCH 1/2] fix(frontend): allow creating a project without an end date Empty end date was treated as invalid ("must be after start date") unless "in progress" was checked. The field is optional; only a filled date that precedes the start date should error. Co-authored-by: Cursor --- .../src/app/components/ProjectFormModal.tsx | 17 ++-- .../test/components/ProjectFormModal.test.tsx | 78 +++++++++++++++++++ 2 files changed, 86 insertions(+), 9 deletions(-) diff --git a/apps/frontend/src/app/components/ProjectFormModal.tsx b/apps/frontend/src/app/components/ProjectFormModal.tsx index b12ffe84..50ad006a 100644 --- a/apps/frontend/src/app/components/ProjectFormModal.tsx +++ b/apps/frontend/src/app/components/ProjectFormModal.tsx @@ -74,15 +74,14 @@ function validate(values: ProjectFormValues): FieldErrors { if (!values.startDate) errors.startDate = 'Please select a valid date'; - // The "in progress" checkbox is what makes an end date optional, so the two - // are validated together rather than independently. - if (!values.inProgress) { - if ( - !values.endDate || - (values.startDate && values.endDate < values.startDate) - ) { - errors.endDate = 'Please select a date AFTER the start date'; - } + // End date is optional (open-ended / still in progress). When one is given, + // it must not precede the start date. + if ( + values.endDate && + values.startDate && + values.endDate < values.startDate + ) { + errors.endDate = 'Please select a date AFTER the start date'; } if (values.members.length === 0) { diff --git a/apps/frontend/test/components/ProjectFormModal.test.tsx b/apps/frontend/test/components/ProjectFormModal.test.tsx index a5599b33..697f80a7 100644 --- a/apps/frontend/test/components/ProjectFormModal.test.tsx +++ b/apps/frontend/test/components/ProjectFormModal.test.tsx @@ -87,6 +87,53 @@ function renderEdit() { ); } +function renderCreate() { + return render( + , + ); +} + +async function pickDate(label: string, day: number) { + const heading = screen.getByText(new RegExp(`^${label}`)); + fireEvent.click( + within(heading.parentElement as HTMLElement).getByRole('button'), + ); + const dialog = await screen.findByRole('dialog', { + name: `Choose ${label}`, + hidden: true, + }); + const buttons = within(dialog).getAllByRole('button', { + name: String(day), + hidden: true, + }); + const inMonth = + buttons.find((button) => !button.className.includes('opacity-50')) ?? + buttons[0]; + fireEvent.click(inMonth); +} + +async function fillCreateForm(opts?: { startDay?: number; endDay?: number }) { + fireEvent.change(screen.getByPlaceholderText('Enter project name'), { + target: { value: 'Wells' }, + }); + fireEvent.change(screen.getByPlaceholderText('Enter total funding'), { + target: { value: '1000' }, + }); + fireEvent.change( + screen.getByPlaceholderText('Enter a short project description here'), + { target: { value: 'Dig wells' } }, + ); + + await pickDate('Start Date', opts?.startDay ?? 15); + if (opts?.endDay != null) { + await pickDate('End Date', opts.endDay); + } + + const search = await screen.findByLabelText('Assigned Staff'); + fireEvent.change(search, { target: { value: 'Ada' } }); + fireEvent.click(await screen.findByRole('option', { name: 'Ada Lovelace' })); +} + beforeEach(() => { mockFetch.mockReset(); mockFetch.mockImplementation((path: string) => { @@ -181,3 +228,34 @@ describe('ProjectFormModal staff roles', () => { ).toBe(false); }); }); + +describe('ProjectFormModal end date', () => { + it('lets you create a project without an end date', async () => { + renderCreate(); + await fillCreateForm(); + fireEvent.click(screen.getByRole('button', { name: 'Save' })); + + await waitFor(() => { + expect( + mockFetch.mock.calls.some(([, init]) => init?.method === 'POST'), + ).toBe(true); + }); + expect(savedBody().end_date).toBeNull(); + expect( + screen.queryByText('Please select a date AFTER the start date'), + ).not.toBeInTheDocument(); + }); + + it('still rejects an end date before the start date', async () => { + renderCreate(); + await fillCreateForm({ startDay: 20, endDay: 10 }); + fireEvent.click(screen.getByRole('button', { name: 'Save' })); + + expect( + await screen.findByText('Please select a date AFTER the start date'), + ).toBeInTheDocument(); + expect( + mockFetch.mock.calls.some(([, init]) => init?.method === 'POST'), + ).toBe(false); + }); +}); From 5de3d2136df3dcd3d42438194d2e747866b10d13 Mon Sep 17 00:00:00 2001 From: nourshoreibah Date: Sun, 30 Aug 2026 15:48:01 -0400 Subject: [PATCH 2/2] chore: trim comments Co-authored-by: Cursor --- apps/frontend/src/app/components/ProjectFormModal.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/frontend/src/app/components/ProjectFormModal.tsx b/apps/frontend/src/app/components/ProjectFormModal.tsx index 50ad006a..dc6510ca 100644 --- a/apps/frontend/src/app/components/ProjectFormModal.tsx +++ b/apps/frontend/src/app/components/ProjectFormModal.tsx @@ -74,13 +74,7 @@ function validate(values: ProjectFormValues): FieldErrors { if (!values.startDate) errors.startDate = 'Please select a valid date'; - // End date is optional (open-ended / still in progress). When one is given, - // it must not precede the start date. - if ( - values.endDate && - values.startDate && - values.endDate < values.startDate - ) { + if (values.endDate && values.startDate && values.endDate < values.startDate) { errors.endDate = 'Please select a date AFTER the start date'; }