Skip to content

(fix) O3-5657: Various fixes to the procedures form#3297

Merged
denniskigen merged 13 commits into
openmrs:feat/procedure-historyfrom
dilankavishka:procedure-history-O3-5657
May 12, 2026
Merged

(fix) O3-5657: Various fixes to the procedures form#3297
denniskigen merged 13 commits into
openmrs:feat/procedure-historyfrom
dilankavishka:procedure-history-O3-5657

Conversation

@dilankavishka
Copy link
Copy Markdown

@dilankavishka dilankavishka commented May 11, 2026

Requirements

  • This PR has a title that briefly describes the work done including the ticket number. If there is a ticket, make sure your PR title includes a conventional commit label. See existing PR titles for inspiration.
  • My work is based on designs, which are linked or shown either in the Jira ticket or the description below. (See also: Styleguide)
  • My work includes tests or is validated by existing tests.

Summary

  • Replace the year and month Selects with ComboBoxes and fix horizontal alignment of the duration fields
  • Fix validation bypass when toggling "Is start date known?" from Yes to No while a start date was already entered (stale startDateTime was left in form state and silently passed validation)
  • Fix the unreachable saving state on the submit button so the spinner now appears while the form is submitting
  • Fix a crash when selecting a procedure type by guarding against a null selectedItem
  • Add estimatedStartDate to the RawProcedure type so the payload contract matches what is actually sent (and the backend persists)
  • Build the estimated-date comparison in local time so end-date validation does not reject the first valid day of the estimated period in non-UTC zones
  • Re-trigger startDateTime validation after submit so the "Start date is required" error clears as soon as the user picks a year
  • Add month-name translations and rename the duration legend to "Procedure duration"
  • Add tests for the toggle bug and the estimated-date save path

Screenshots

  1. Start date time error
Screenshot 2026-05-11 at 17 53 42
  1. When selecting "No" for "Is start date known?"

Before:
Screenshot 2026-05-11 at 17 57 09

After:
Screenshot 2026-05-11 at 17 58 19

Other

Related Issue

https://issues.openmrs.org/browse/O3-5657

@dilankavishka dilankavishka changed the title fix (O3-5657): Use combo boxes and fix alignments (fix) O3-5657: Use combo boxes and fix alignments May 11, 2026
@dilankavishka
Copy link
Copy Markdown
Author

@denniskigen @NethmiRodrigo Could you please review this PR?

Comment thread packages/esm-patient-procedures-app/src/procedures/procedures-form.component.tsx Outdated
Comment thread packages/esm-patient-procedures-app/src/procedures/procedures-form.component.tsx Outdated
@denniskigen
Copy link
Copy Markdown
Member

On packages/esm-patient-procedures-app/src/types.ts (RawProcedure):

RawProcedure is missing estimatedStartDate, but handleSave sends it at procedures-form.component.tsx:171 and the backend persists it (Procedure.estimatedStartDate, exposed via ProcedureResource.getCreatableProperties()).

Could we add it here?

estimatedStartDate?: string | null;

@denniskigen
Copy link
Copy Markdown
Member

On packages/esm-patient-procedures-app/src/procedures/procedures-form.workspace.tsx:39-40:

new Date(data.estimatedStartDate) parses YYYY / YYYY-MM as UTC midnight, while the date picker value is normalized to local midnight in DateTimeField via setHours(0, 0, 0, 0). In UTC+ time zones, an end date on the first valid day of the estimated period gets rejected.

Example in Africa/Nairobi: estimated start 2023-05 parses to 2023-05-01T00:00:00.000Z, but selecting May 1 produces local midnight, 2023-04-30T21:00:00.000Z, so endDateTime >= start is false. The tests run under TZ=UTC, so they miss this.

Could we build the comparison date in local time?

const [year, month] = data.estimatedStartDate.split('-').map(Number);
const start = new Date(year, Number.isFinite(month) ? month - 1 : 0, 1);
return data.endDateTime >= start;

@dilankavishka dilankavishka requested a review from denniskigen May 11, 2026 18:08
setValue('estimatedStartDate', '');
}
}, [isStartDateKnown, estimatedYear, estimatedMonth, setValue]);

Copy link
Copy Markdown
Member

@denniskigen denniskigen May 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work on the toggle clearing logic, @dilankavishka. While testing locally I hit a small validation issue that I think originates here. Wanted to share what I found and a fix that worked end to end.

Repro:

  1. Toggle "Is start date known?" to No. "Start date is required" appears immediately, before the user has touched the year combo.
  2. Pick 2023 for Year. The error stays visible.
  3. Click Save & close. Only then does the error clear.

There are two things going on. First, the error doesn't clear after picking a year because setValue('estimatedStartDate', '2023', { shouldValidate: true }) runs validation, but shouldValidate only updates errors on the path it was called with. The refine's error lives at path: ['startDateTime'], so the stale error there is left untouched. We need an explicit trigger('startDateTime') to refresh that path. Second, the error fires immediately on toggle because setValue('startDateTime', null, { shouldValidate: true }) in the toggle handler revalidates on the spot, before the user has had a chance to fill the year.

Just those two changes still leave the error appearing on toggle, because this useEffect itself runs whenever isStartDateKnown changes and re-triggers validation. The RHF idiom for progressive validation is to gate the manual trigger on isSubmitted, so we only push back on the user after they've actually tried to submit:

const {
  formState: { errors, isSubmitted },
  setValue,
  trigger,
  ...
} = useFormContext<ProceduresFormSchema>();

useEffect(() => {
  const value =
    !isStartDateKnown && estimatedYear
      ? estimatedMonth ? `${estimatedYear}-${estimatedMonth}` : estimatedYear
      : '';
  setValue('estimatedStartDate', value);
  if (isSubmitted) {
    trigger('startDateTime');
  }
}, [isStartDateKnown, estimatedYear, estimatedMonth, isSubmitted, setValue, trigger]);

And in the toggle handler at line 260:

if (!isKnown) {
  setValue('startDateTime', null);
}

I verified locally that this gives:

  • Toggle pre-submit: no premature error.
  • Submit with year empty: error appears.
  • Pick year post-submit: error clears.
  • Clear year post-submit: error reappears.

Could we also add a test for the "clears after picking a year" path? That's the actual regression the fix addresses, and the current test at procedures-form.workspace.test.tsx:464 only checks the error after submit:

// After the existing submit assertion:
await user.click(screen.getByRole('combobox', { name: /year/i }));
await user.click(screen.getByRole('option', { name: '2023' }));
expect(screen.queryByText(/start date is required/i)).not.toBeInTheDocument();

Side note while we're here: the original three-branch form collapses to two since the if (isStartDateKnown) and final else both write ''. The snippet above already incorporates that.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Screen.Recording.2026-05-12.at.10.16.27.mov

@denniskigen
Copy link
Copy Markdown
Member

Tangential, but can we do something about the duplicate Duration label text?

CleanShot 2026-05-11 at 23 10 18@2x

@dilankavishka
Copy link
Copy Markdown
Author

Tiny nit: since the useEffect above keeps estimatedStartDate in form state in sync, handleSave can read it from there:

const estimatedStartDate = getValues('estimatedStartDate') || null;

Then estimatedYear, estimatedMonth, and isStartDateKnown come out of the useCallback deps. Single source of truth.

@denniskigen To keep the consistency and the better understanding shall we rename the title to Length of procedure ?
Screenshot 2026-05-12 at 10 13 28

so as like this, the length of procedure is 2 hours.

@dilankavishka dilankavishka requested a review from denniskigen May 12, 2026 04:52
@denniskigen
Copy link
Copy Markdown
Member

denniskigen commented May 12, 2026

Tiny nit: since the useEffect above keeps estimatedStartDate in form state in sync, handleSave can read it from there:

const estimatedStartDate = getValues('estimatedStartDate') || null;

Then estimatedYear, estimatedMonth, and isStartDateKnown come out of the useCallback deps. Single source of truth.

@denniskigen To keep the consistency and the better understanding shall we rename the title to Length of procedure ? Screenshot 2026-05-12 at 10 13 28

so as like this, the length of procedure is 2 hours.

Would "Procedure duration" be a more accurate legend label here? @VeronicaMuthee

dilankavishka and others added 2 commits May 12, 2026 17:27
All other keys in en.json are camelCase. The key with a space was an
outlier and harder to grep for and reference consistently.
@denniskigen denniskigen changed the title (fix) O3-5657: Use combo boxes and fix alignments (fix) O3-5657: Various fixes to the procedures form May 12, 2026
Copy link
Copy Markdown
Member

@denniskigen denniskigen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the careful iteration on this one, @dilankavishka. Tracking down the stale startDateTime after the toggle and wiring up the validation re-trigger so the error clears as soon as the user picks a year both took some thought, and the new tests lock both behaviours in nicely. Good catch on the procedure-type null guard too, and tightening the RawProcedure type closes a contract gap that had been hiding in plain sight.

Pushed one small follow-up to switch the duration translation key to camelCase for consistency with the rest of en.json. Approving.

@dilankavishka
Copy link
Copy Markdown
Author

Thanks for the careful iteration on this one, @dilankavishka. Tracking down the stale startDateTime after the toggle and wiring up the validation re-trigger so the error clears as soon as the user picks a year both took some thought, and the new tests lock both behaviours in nicely. Good catch on the procedure-type null guard too, and tightening the RawProcedure type closes a contract gap that had been hiding in plain sight.

Pushed one small follow-up to switch the duration translation key to camelCase for consistency with the rest of en.json. Approving.

Thank you Dennis!

@denniskigen denniskigen merged commit bd88d2b into openmrs:feat/procedure-history May 12, 2026
11 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants