Skip to content

feat: Lab Result Matrix comparison view#639

Open
syseitz wants to merge 5 commits intoafairgiant:mainfrom
syseitz:feature/lab-matrix-comparison-view
Open

feat: Lab Result Matrix comparison view#639
syseitz wants to merge 5 commits intoafairgiant:mainfrom
syseitz:feature/lab-matrix-comparison-view

Conversation

@syseitz
Copy link
Copy Markdown

@syseitz syseitz commented Mar 11, 2026

Summary

  • Adds a Lab Result Matrix view mode to the existing Lab Results page, alongside Cards and Table views
  • Displays all lab test components across multiple dates in a side-by-side comparison table
  • Features category grouping (Hematology, Chemistry, etc.), color-coded status indicators (normal/high/low/critical/abnormal), sticky first column & header, category and abnormal-only filtering, and reference range tooltips

Details

  • New component: LabResultMatrix.jsx — embeddable matrix that takes labResults as prop and fetches all test components
  • ViewToggle.jsx — added matrix mode to MODE_CONFIG
  • LabResults.jsx — integrated matrix as a view mode option, hidden page filters in matrix mode (it has its own)
  • Components view mode button temporarily hidden (backend catalog endpoint not yet available)
  • Cleaned up standalone /lab-matrix route and navigation entry in favor of the integrated view mode approach

Screenshots

The matrix shows parameters as rows and lab result dates as columns, with values color-coded by status.

Test plan

  • Navigate to Lab Results page
  • Click the "Matrix" view mode button
  • Verify matrix loads with all test components across dates
  • Verify horizontal scrolling works for many date columns
  • Verify vertical page scrolling works for many parameters
  • Test category filter and abnormal-only filter
  • Verify sticky first column and header row behavior
  • Test with no lab results (should show empty state)

syseitz added 3 commits March 11, 2026 09:12
Add a new /lab-matrix page that displays all lab test components
across all lab results in a cross-date comparison matrix table.

Features:
- Matrix view with test parameters as rows, dates as columns
- Color-coded status indicators (normal/high/low/critical/abnormal)
- Grouped by test category (hematology, chemistry, etc.)
- Filter by category and abnormal-only mode
- Sticky first column (parameter names) and header row (dates)
- Tooltip with reference ranges on hover
- Responsive scrolling for large datasets
- i18n translations for en, de, es, fr, it, pt

Navigation: accessible via sidebar under "Lab Results"
Move the lab comparison matrix from a standalone page into the existing
Lab Results page as a view mode option alongside Cards, Table, and
Components. Remove the separate /lab-matrix route and navigation entry.

- Add LabResultMatrix component with category grouping, color-coded
  status indicators, sticky headers, and filtering
- Add 'matrix' option to ViewToggle MODE_CONFIG
- Integrate matrix view into LabResults.jsx renderViewContent()
- Hide page filters in matrix mode (has its own)
- Fix duplicate size prop on Badge in LabResultMatrix
Allow the matrix table to render at full height with natural page
scrolling. Horizontal scroll is preserved for wide tables.
Also hide components view mode button (backend endpoint not yet available).
Copy link
Copy Markdown
Owner

@afairgiant afairgiant left a comment

Choose a reason for hiding this comment

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

Code Review — PR #639: Lab Result Matrix Comparison View

Thanks for putting this together, Yannick! The matrix comparison concept is genuinely useful — being able to see lab values side-by-side across dates is a great feature. However, the implementation has a number of issues that need to be addressed before it can be merged. I've grouped them below with concrete examples so they're easy to work through.


1. Unreachable Code

frontend/src/pages/medical/LabMatrix.jsx (540 lines) is not imported by any file, has no route, and is never rendered. It appears to be the original standalone page from before the refactor into an embedded view mode. This should be removed — along with the "labMatrix" keys added to all 6 navigation.json locale files, which are also unused.

Additionally, the 'components' view mode code is now unreachable in LabResults.jsx since the view modes changed to ['cards', 'table', 'matrix']. The TestComponentCatalog import (line 37), the if (viewMode === 'components') block (line 553), and the viewMode !== 'components' filter condition (line 683) are all dead code paths. If Components is being intentionally hidden, that's worth a separate discussion — but either way the dead code should be cleaned up.


2. View Mode Persistence Bug

usePersistedViewMode.js has a VALID_MODES allowlist:

const VALID_MODES = new Set(['cards', 'table', 'components']);

'matrix' isn't in this set. The matrix view works in-session, but on page reload the selection is silently discarded and falls back to Cards. 'matrix' needs to be added to VALID_MODES.


3. Localization

The component imports useTranslation('medical') but never calls t(). All user-facing text is hardcoded English — filter labels ("All", "Abnormal Only"), column headers ("Parameter", "Unit"), the legend, loading/empty states, and all category names. This breaks the experience for users in any of the other 5 supported languages.

The viewToggle.matrix translation key is also missing from all 6 common.json locale files, so the toggle button falls back to the English-only hardcoded string.


4. Hardcoded Austrian Locale

formatDate and formatValue both hardcode 'de-AT':

d.toLocaleDateString('de-AT', { day: '2-digit', month: '2-digit', year: '2-digit' });
val.toLocaleString('de-AT', { maximumFractionDigits: 2 });

This means all users see Austrian-format dates (11.03.26) and commas as decimal separators (4,5 instead of 4.5). The rest of the app uses the useDateFormat() hook to respect user preferences — this component should do the same.


5. Font & Readability

The table forces monospace font at 0.78rem (~12.5px), with the unit column even smaller at 0.7rem (~11.2px). The rest of the app uses Mantine's default proportional font at size="sm" (14px). The matrix is noticeably harder to read than everything else on the page. Using Mantine's standard sizing and font would keep it consistent.


6. Duplicated & Inconsistent Constants

CATEGORY_COLORS, CATEGORY_ORDER, CATEGORY_LABELS, and STATUS_COLORS are all redefined locally instead of importing from the shared labCategories.ts module. The local colors also conflict with the shared ones in several places:

Category This PR Shared labCategories.ts
endocrinology grape purple
lipids orange indigo
hepatology teal lime
immunology cyan green
microbiology lime yellow
cardiology pink grape

Users would see different colors for the same category depending on which page they're on. Importing getCategoryColor() and getCategoryDisplayName() from the shared module would fix this and reduce duplication.


7. Accessibility (WCAG 2.1 AA)

A few gaps that need attention:

  • Color-only status indication (1.4.1): High/low/critical/abnormal status is communicated only through background color and bold weight. Colorblind users can't distinguish red (high) from orange (abnormal). Adding a small directional indicator (e.g., an up/down arrow) or a visually-hidden status label would solve this.

  • Table semantics (1.3.1): The <table> has no <caption> or aria-label, <th> elements lack scope="col", and the first column in data rows uses <td> instead of <th scope="row">. Screen readers won't be able to navigate the table meaningfully.

  • Filter control labels (4.1.2): The SegmentedControl and MultiSelect have no aria-label. Screen reader users won't know what these controls do.

  • Legend incomplete: borderline is defined in STATUS_COLORS but missing from the legend, so users who encounter a yellow cell have no key to interpret it.


8. Missing Error Handling & Request Cleanup

The component has no error state — if the API calls fail, the user sees the "no data" empty state instead of an error message. Every other data-fetching component in the lab results area (TestComponentCatalog.tsx, etc.) maintains an error state and displays an Alert.

Related: the fetchComponents function fires N parallel API requests via Promise.all but has no AbortController. If the user switches view modes while requests are in flight, state updates fire on an unmounted component. The sibling TestComponentCatalog.tsx handles this correctly with an AbortController and cleanup — worth following the same pattern.


9. File Type & Prop Validation

Every other recently-created file in the labresults/ directory is TypeScript (.tsx). This file should follow suit for consistency. If kept as .jsx, it needs PropTypes for the labResults prop at minimum.


10. Tests

No test file was included. At minimum, tests should cover rendering with empty data, rendering with valid data, the category filter, the abnormal-only filter, and the loading state.


Summary

The feature concept is solid and I'd like to see it land. The main areas to address are: removing the dead code, fixing the persistence bug, wiring up translations, using the existing date format hook, aligning with the shared constants, and addressing the WCAG gaps. Happy to help work through any of these if you have questions!

syseitz added 2 commits March 13, 2026 12:03
- Remove dead code: standalone LabMatrix.jsx, unused navigation keys, components view mode
- Fix view mode persistence: add 'matrix' to VALID_MODES
- Localize all hardcoded strings with t() across 6 languages
- Replace hardcoded 'de-AT' locale with useDateFormat hook
- Use Mantine default font/sizing instead of monospace 0.78rem
- Import shared constants from labCategories.ts
- Improve accessibility: status arrows, table semantics, aria-labels, borderline in legend
- Add error state, AbortController pattern, and unmount cleanup
- Convert LabResultMatrix.jsx to TypeScript (.tsx) with proper interfaces
- Add 12 unit tests covering all key behaviors
The hook is shared across pages — removing 'components' would break
other pages that still use that view mode.
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.

2 participants