feat: Display patient height as ft and inch remainder across vitals UI and PDFs#7970
Conversation
…I and PDFs - Add shared height helpers in utils: round total inches to 0.5, decompose to feet + inch remainder, format observation strings; unit tests included. - Update vitals height form: total inches + ft + inch remainder fields; persist cm unchanged. - Replace decimal-feet display with formatHeightObservationValue in history, progress note, tooltips, and mapVitalsToDisplay (visit note / discharge PDFs). - Remove unused decimal-feet conversion helpers; clarify naming (inchRemainder vs total inches).
There was a problem hiding this comment.
Pull request overview
This PR standardizes patient height display across the EHR vitals UI and generated visit-note/PDF outputs by introducing shared cm↔in/ft formatting helpers and updating height entry to support feet + inch remainder.
Changes:
- Added utils helpers to round total inches to 0.5, decompose cm into ft + inch remainder, and format height observation display strings (with unit tests).
- Updated vitals height form state + UI to include an “inch remainder” field alongside cm, total inches, and feet inputs.
- Replaced prior decimal-feet height rendering with
formatHeightObservationValuein vitals history and visit-note display mapping.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/utils/lib/helpers/vitals/vitals-height.helper.ts | Adds shared height rounding/decomposition/formatting helpers used across UI + PDFs. |
| packages/utils/lib/helpers/vitals/vitals-height.helper.test.ts | Adds unit tests for rounding, decomposition, formatting, and round-trip conversion. |
| packages/utils/lib/helpers/visit-note/map-vitals-to-display.helper.ts | Uses shared height formatting for visit-note/PDF vitals display strings. |
| apps/ehr/src/features/visits/shared/components/vitals/types.ts | Extends height local state to include inch remainder + change handler. |
| apps/ehr/src/features/visits/shared/components/vitals/heights/VitalsHeightCard.tsx | Updates the height input UI to include ft + inch remainder fields and relabels inputs. |
| apps/ehr/src/features/visits/shared/components/vitals/heights/useHeightLocalState.ts | Updates height form synchronization logic across cm / inches / ft+inch remainder. |
| apps/ehr/src/features/visits/shared/components/vitals/heights/helpers.ts | Updates parsing/conversion helpers to support ft + inch remainder input. |
| apps/ehr/src/features/visits/shared/components/vitals/components/VitalsHistoryEntry.tsx | Uses shared height formatting for vitals history display. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export const heightCmToFeetInches = (heightCm: number): HeightFeetInchesDisplay => { | ||
| const totalInches = cmToInches(heightCm); | ||
| const half = roundToNearestHalf(totalInches); | ||
|
|
||
| return { | ||
| heightCm, | ||
| totalInches, | ||
| feet: Math.floor(half / 12), | ||
| inchRemainder: half % 12, | ||
| }; | ||
| }; |
There was a problem hiding this comment.
As per the design, the current implementation is correct - snap is only applied to ft′in″. @alexwillingham @VladMstv what do you think, should we keep it, or implement it the way Copilot suggested and round the middle column as well?
The issue is that, in the end, we might also have to round the value that the user has just entered, which would feel a bit strange and confusing.
| const applyFeetAndInchRemainderToCm = useCallback((feetAsText: string, inchRemainderAsText: string): void => { | ||
| const heightCm = textToHeightNumberFromFeetAndInchRemainder(feetAsText, inchRemainderAsText); | ||
| if (heightCm) { | ||
| setHeightValueTextCm(heightCm.toString()); | ||
|
|
||
| const inchesText = heightCmToInchesText(heightCm); | ||
| setHeightValueTextInches(inchesText); | ||
| setHeightValueTextInches(`${cmToInches(heightCm)}`); | ||
| } else { |
There was a problem hiding this comment.
heightCmToFeetInches(heightCm).totalInches is equal to cmToInches(heightCm), so this change doesn't seem to make sense. It looks like the current rounding behavior is intentional.
| const feet = hasFeet ? textToNumericValue(feetText) : 0; | ||
| const inchRemainder = hasInchRemainder ? textToNumericValue(inchRemainderText) : 0; | ||
|
|
||
| if (feet === undefined || inchRemainder === undefined) return; | ||
|
|
||
| export const heightCmToFeetText = (heightCm: number): string => { | ||
| return cmToFeetText(heightCm); | ||
| return roundHeightValue(feetInchesToCm(feet, inchRemainder)); | ||
| }; |
There was a problem hiding this comment.
Remainder is just a name used in the code to distinguish the inches within the feet value from the total number of inches.
The conversion itself is mathematically correct: 1.24" → 3'0".
We could limit the number of inches at this point, but if we allow users to enter 12, it will naturally be converted into the next full foot. On the other hand, not allowing users to enter 12 would also feel a bit odd.
Add shared height helpers in utils: round total inches to 0.5, decompose to feet + inch remainder, format observation strings; unit tests included.
Update vitals height form: total inches + ft + inch remainder fields; persist cm unchanged.
Replace decimal-feet display with formatHeightObservationValue in history, progress note, tooltips, and mapVitalsToDisplay (visit note / discharge PDFs).
Remove unused decimal-feet conversion helpers; clarify naming (inchRemainder vs total inches).