Skip to content

Fix: Location-Switcher text overflow#16228

Open
pranavrutagi-egov wants to merge 37 commits intoohcnetwork:developfrom
pranavrutagi-egov:issues/#15877/location-switcher-text-overflow
Open

Fix: Location-Switcher text overflow#16228
pranavrutagi-egov wants to merge 37 commits intoohcnetwork:developfrom
pranavrutagi-egov:issues/#15877/location-switcher-text-overflow

Conversation

@pranavrutagi-egov
Copy link
Copy Markdown
Contributor

@pranavrutagi-egov pranavrutagi-egov commented Mar 30, 2026

Proposed Changes

Fixes #15877

  • CSS Changes for text truncation (location name)
  • Tooltip component for full name display on hover

Relevant Screenshots:

  1. Truncated name:
    image

  2. Tootip:
    image

Tagging: @ohcnetwork/care-fe-code-reviewers

Merge Checklist

  • Add specs that demonstrate the bug or test the new feature.
  • Update product documentation.
  • Ensure that UI text is placed in I18n files.
  • Prepare a screenshot or demo video for the changelog entry and attach it to the issue.
  • Request peer reviews.
  • Complete QA on mobile devices.
  • Complete QA on desktop devices.
  • Add or update Playwright tests for related changes

Summary by CodeRabbit

  • New Features

    • Dosage instructions now show all steps across medication views and forms (detailed per-step lists and selectors).
    • Print previews switched to facility-aware templates for many document types (prescriptions, invoices, appointments, orders, reports, etc.).
  • Bug Fixes

    • Improved location switcher and dialogs to prevent overflow, add truncation, and show full names via desktop tooltips.
    • Various UI truncation/overflow fixes (service points, beds, lists).
  • Documentation

    • Added comprehensive Playwright E2E guide and DB snapshot tooling.
  • Chores

    • Added DB snapshot scripts and new npm Playwright helper scripts; updated .gitignore.

@pranavrutagi-egov pranavrutagi-egov requested review from a team March 30, 2026 11:28
Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@github-actions
Copy link
Copy Markdown

⚠️ Merge Checklist Incomplete

Thank you for your contribution! To help us review your PR efficiently, please complete the merge checklist in your PR description.

Your PR will be reviewed once you have marked the appropriate checklist items.

To update the checklist:

  • Change - [ ] to - [x] for completed items
  • Only check items that are relevant to your PR
  • Leave items unchecked if they don't apply

The checklist helps ensure code quality, testing coverage, and documentation are properly addressed.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 30, 2026

Walkthrough

LocationSwitcher UI updated to fix text overflow in constrained widths: container uses min-w-0/flex-1, location name span uses w-full truncate text-left, and the label is wrapped in a desktop-only TooltipComponent. Dialog breadcrumb/path rendering now uses buildLocationPath(location) for consistent truncation and navigation.

Changes

Cohort / File(s) Summary
Location Switcher UI
src/components/ui/sidebar/facility/location/location-switcher.tsx
Added min-w-0/flex-1 to button label container, changed name span to w-full truncate text-left, wrapped label in TooltipComponent (desktop-only). Dialog breadcrumb rendering simplified and keying adjusted; selection navigation now uses buildLocationPath(location).
Location path utilities
src/types/location/utils.ts
Replaced buildLocationHierarchy with buildLocationPath that returns LocationRead[]; added excludeCurrent option to getLocationPath. This supports the switcher/dialog breadcrumb changes.

Possibly related PRs

Suggested labels

needs review, needs testing, Type Changes

Suggested reviewers

  • nihal467
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 30, 2026

🚀 Preview Deployment Ready!

🔗 Preview URL: https://pr-16228.care-preview-a7w.pages.dev

📱 Mobile Access:
Scan the QR code below to open the preview on your mobile device.

QR Code


This preview will be automatically updated when you push new commits to this PR.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/ui/sidebar/facility/location/location-switcher.tsx`:
- Around line 91-100: The TooltipComponent is being rendered with
content={location?.name} which can be undefined and causes an empty tooltip;
update the JSX in the LocationSwitcher component to only render TooltipComponent
when location?.name is truthy (e.g., wrap the existing TooltipComponent block in
a conditional like location?.name ? <TooltipComponent ...> : <div ...>),
otherwise render the same inner div without the TooltipComponent (or use a
native title attribute on the inner div) so no empty tooltip appears while
useCurrentLocation is resolving.
- Line 32: The import for TooltipComponent is out of order; move the line
importing TooltipComponent so it sits with the other "@/components/ui/" imports
(the UI group) instead of after "@/common/constants". Locate the import
statement for TooltipComponent and place it alongside the other UI imports in
this file (e.g., near other imports from "@/components/ui/") to follow the
project's import ordering convention.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fdf93067-0e8f-48ec-a4cb-08faa6fb3ba7

📥 Commits

Reviewing files that changed from the base of the PR and between f05efdb and 0864aac.

📒 Files selected for processing (1)
  • src/components/ui/sidebar/facility/location/location-switcher.tsx


import { RESULTS_PER_PAGE_LIMIT } from "@/common/constants";

import { TooltipComponent } from "@/components/ui/tooltip";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Import order: move this with other @/components/ui/ imports.

The TooltipComponent import should be grouped with the other @/components/ui/ imports (lines 10-26), not placed after @/common/constants.

🧹 Suggested reordering

Move the import to be with the other UI imports:

 import { Separator } from "@/components/ui/separator";
 import { useSidebar } from "@/components/ui/sidebar";
+import { TooltipComponent } from "@/components/ui/tooltip";

 import PaginationComponent from "@/components/Common/Pagination";

 import { RESULTS_PER_PAGE_LIMIT } from "@/common/constants";

-import { TooltipComponent } from "@/components/ui/tooltip";
 import useCurrentLocation from "@/pages/Facility/locations/utils/useCurrentLocation";

As per coding guidelines: "Order imports: 3rd-party → library → CAREUI → UI → components → hooks → utils → relative."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/sidebar/facility/location/location-switcher.tsx` at line
32, The import for TooltipComponent is out of order; move the line importing
TooltipComponent so it sits with the other "@/components/ui/" imports (the UI
group) instead of after "@/common/constants". Locate the import statement for
TooltipComponent and place it alongside the other UI imports in this file (e.g.,
near other imports from "@/components/ui/") to follow the project's import
ordering convention.

Comment on lines +91 to +100
<TooltipComponent content={location?.name}>
<div className="flex min-w-0 flex-col items-start">
<span className="text-xs text-gray-500">
{t("current_location")}
</span>
<span className="w-full truncate text-left text-sm font-medium text-gray-900">
{location?.name}
</span>
</div>
</TooltipComponent>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle undefined location to avoid rendering an empty tooltip.

When location is undefined (initial state before useCurrentLocation resolves), location?.name will be undefined. Based on the TooltipComponent implementation in src/components/ui/tooltip.tsx, this will still render the tooltip wrapper with empty content, potentially showing an empty tooltip on hover.

Consider conditionally rendering the tooltip only when the location name is available:

🛠️ Suggested fix
              <div className="min-w-0 flex-1">
-               <TooltipComponent content={location?.name}>
-                 <div className="flex min-w-0 flex-col items-start">
-                   <span className="text-xs text-gray-500">
-                     {t("current_location")}
-                   </span>
-                   <span className="w-full truncate text-left text-sm font-medium text-gray-900">
-                     {location?.name}
-                   </span>
-                 </div>
-               </TooltipComponent>
+               <div className="flex min-w-0 flex-col items-start">
+                 <span className="text-xs text-gray-500">
+                   {t("current_location")}
+                 </span>
+                 <span
+                   className="w-full truncate text-left text-sm font-medium text-gray-900"
+                   title={location?.name}
+                 >
+                   {location?.name}
+                 </span>
+               </div>
              </div>

Alternatively, keep TooltipComponent but guard rendering:

{location?.name ? (
  <TooltipComponent content={location.name}>
    ...
  </TooltipComponent>
) : (
  <div className="flex min-w-0 flex-col items-start">...</div>
)}

Based on learnings: "In the care_fe repository, prefer the native HTML title attribute for simple tooltips over using shadcn/ui Tooltip components."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/sidebar/facility/location/location-switcher.tsx` around
lines 91 - 100, The TooltipComponent is being rendered with
content={location?.name} which can be undefined and causes an empty tooltip;
update the JSX in the LocationSwitcher component to only render TooltipComponent
when location?.name is truthy (e.g., wrap the existing TooltipComponent block in
a conditional like location?.name ? <TooltipComponent ...> : <div ...>),
otherwise render the same inner div without the TooltipComponent (or use a
native title attribute on the inner div) so no empty tooltip appears while
useCurrentLocation is resolving.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 30, 2026

🎭 Playwright Test Results

Status: ❌ Failed
Test Shards: 3

Metric Count
Total Tests 246
✅ Passed 245
❌ Failed 1
⏭️ Skipped 0

📊 Detailed results are available in the playwright-final-report artifact.

Run: #8141

@pranavrutagi-egov
Copy link
Copy Markdown
Contributor Author

Hi @gigincg, kindly review this PR

Copy link
Copy Markdown
Contributor

@Jacobjeevan Jacobjeevan left a comment

Choose a reason for hiding this comment

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

Please do ensure you are checking relevant/completed items from the merge checklist.

@Jacobjeevan Jacobjeevan added reviewed reviewed by a core member and removed needs review labels Mar 31, 2026
Copilot AI and others added 13 commits March 31, 2026 17:29
…ft empty (ohcnetwork#16244)

Co-authored-by: nihal467 <nihalcct@gmail.com>\
…rk#16045)

Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com>
…on (ohcnetwork#16136)

Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com>
StephZam and others added 5 commits April 1, 2026 22:06
Co-authored-by: Yaswanth Kumar Bethu <yash.topaz@gmail.com>
…ork#16110)

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com>
…network#16032)

Co-authored-by: Ishu Agrawal <ishuagrawal@Ishus-MacBook-Air.local>
@parvathyns-creator
Copy link
Copy Markdown

@nihal467 this looks fine

Copy link
Copy Markdown
Member

@nihal467 nihal467 left a comment

Choose a reason for hiding this comment

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

Image
  • dont show tooltip in the mobile view
  • fix the responsive view in the location selector popup as well

@nihal467 nihal467 added Changes Required and removed needs testing reviewed reviewed by a core member labels Apr 7, 2026
@pranavrutagi-egov pranavrutagi-egov requested a review from a team as a code owner April 7, 2026 10:41
@github-actions github-actions bot added the Type Changes Contains changes in typescript types label Apr 7, 2026
@pranavrutagi-egov
Copy link
Copy Markdown
Contributor Author

Hi @nihal467, as mentioned in your comment, I have made the following changes:

  1. Popup made responsive, not hiding the path but truncating enabling user to see the location even when nested
  2. Showing tooltip only on large screens (screens having hover effect)
  3. Limited width of tootip text thus not overflowing out of screen in case of long names

Here are supporting screenshots:
image
image
image

@github-actions github-actions bot added needs-triage question Further information is requested labels Apr 7, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 45

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (14)
src/components/Common/ProductionWarningBanner.tsx (1)

6-7: 🧹 Nitpick | 🔵 Trivial

Import ordering: raviger should be grouped with external packages.

The raviger import on line 7 should be placed with other 3rd-party imports (lines 1-4), before the internal @/atoms/ import. As per coding guidelines: "Order imports: 3rd-party → library → CAREUI → UI → components → hooks → utils → relative."

Proposed fix
 import { useAtom } from "jotai";
 import { AlertTriangleIcon } from "lucide-react";
 import { useEffect } from "react";
 import { useTranslation } from "react-i18next";
+import { useQueryParams } from "raviger";

 import { developerModeAtom } from "@/atoms/developerMode";
-import { useQueryParams } from "raviger";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Common/ProductionWarningBanner.tsx` around lines 6 - 7, Move
the third-party import for useQueryParams from "raviger" to the top group of
external/3rd-party imports so it sits with other external packages and before
the internal import developerModeAtom from "@/atoms/developerMode"; update the
import order in ProductionWarningBanner.tsx to follow the project's convention
(3rd-party → library → CAREUI → UI → components → hooks → utils → relative) so
useQueryParams is grouped with other external imports.
src/components/Auth/Login.tsx (1)

112-122: 🧹 Nitpick | 🔵 Trivial

Consider using setTimeout instead of setInterval for cleaner timer logic.

With [resendOtpCountdown] as a dependency, the effect re-runs on every countdown tick, clearing and re-creating the interval each second. While functionally correct, using setTimeout is a cleaner pattern for this use case:

♻️ Proposed refactor using setTimeout
   useEffect(() => {
     if (resendOtpCountdown <= 0) {
       return;
     }
 
-    const timer = setInterval(() => {
+    const timer = setTimeout(() => {
       setResendOtpCountdown((prevTime) => prevTime - 1);
     }, 1000);
 
-    return () => clearInterval(timer);
+    return () => clearTimeout(timer);
   }, [resendOtpCountdown]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Auth/Login.tsx` around lines 112 - 122, The effect in
Login.tsx re-creates an interval every tick because it depends on
resendOtpCountdown; replace the setInterval pattern with a single setTimeout
that runs when resendOtpCountdown > 0 to decrement once (call
setResendOtpCountdown(prev => prev - 1)), and return a cleanup that clears the
timeout; keep resendOtpCountdown in the dependency array so the timeout
schedules the next tick until it reaches 0 and ensure you reference the same
state updater (setResendOtpCountdown) and the resendOtpCountdown variable in the
effect.
src/pages/Facility/billing/PaymentReconciliationSheet.tsx (1)

240-253: 🧹 Nitpick | 🔵 Trivial

Consider clearing reference_number only when switching to cash.

The form.setValue("reference_number", "") call runs on every dependency change when isCashPayment is true, including amount/tenderedAmount changes. While React Hook Form handles this efficiently, you could optimize by only clearing when the payment method actually changes to cash.

♻️ Optional refactor to clear only on method switch
+  // Clear reference number when switching to cash payment
+  useEffect(() => {
+    if (isCashPayment) {
+      form.setValue("reference_number", "");
+    }
+  }, [isCashPayment, form]);
+
   // Calculate returned amount when tender amount, amount or payment method changes
   useEffect(() => {
     if (isCashPayment) {
       // For cash payments, calculate change to return
       form.setValue(
         "returned_amount",
         round(Decimal.max(0, tenderedAmount || "0").minus(amount || "0")),
       );
-      form.setValue("reference_number", "");
     } else {
       // For non-cash payments, tendered amount equals payment amount and returned is 0
       form.setValue("tendered_amount", amount || "0");
       form.setValue("returned_amount", "0");
     }
   }, [tenderedAmount, amount, isCashPayment, form]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Facility/billing/PaymentReconciliationSheet.tsx` around lines 240 -
253, The effect currently clears reference_number on every change when
isCashPayment is true; instead track the previous isCashPayment state (e.g.,
with a useRef like prevIsCashPayment) and only call
form.setValue("reference_number", "") when isCashPayment transitions from false
to true. Keep the existing calculations for tendered_amount and returned_amount
in useEffect (or split into two effects) but only perform the reference_number
clear on the method switch by checking prevIsCashPayment.current before clearing
and then updating prevIsCashPayment.current = isCashPayment. Reference symbols:
useEffect, isCashPayment, form.setValue("reference_number", ""), and
prevIsCashPayment (or equivalent ref).
src/pages/Appointments/components/PractitionerSelector.tsx (3)

197-199: ⚠️ Potential issue | 🟡 Minor

handleSelectAll can introduce duplicate selections.

The function appends all users without checking for duplicates. If some users in the organization are already selected, they'll be added again.

🐛 Filter out already-selected users
  const handleSelectAll = (users: UserReadMinimal[]) => {
-   onSelect([...selected, ...users]);
+   const newUsers = users.filter((u) => !selected.some((s) => s.id === u.id));
+   onSelect([...selected, ...newUsers]);
  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Appointments/components/PractitionerSelector.tsx` around lines 197
- 199, handleSelectAll currently appends the incoming users to selected without
deduplication, which can create duplicate entries; update handleSelectAll to
merge selected and users by unique user identifier (e.g., user.id) and pass the
deduplicated array to onSelect (use selected, users, onSelect and the user id
key to locate and remove duplicates before calling onSelect).

529-555: ⚠️ Potential issue | 🟡 Minor

Clickable element lacks keyboard accessibility.

The clickable div for child organization navigation has onClick but lacks:

  • role="button" for screen reader identification
  • tabIndex={0} for keyboard focus
  • onKeyDown handler for Enter/Space activation

This prevents keyboard-only users from navigating child organizations.

🛠️ Add accessibility attributes
                  <div
                    key={organization.id}
                    className="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 cursor-pointer"
+                   role="button"
+                   tabIndex={0}
                    onClick={() => handleChildOrganizationClick(organization)}
+                   onKeyDown={(e) => {
+                     if (e.key === "Enter" || e.key === " ") {
+                       e.preventDefault();
+                       handleChildOrganizationClick(organization);
+                     }
+                   }}
                  >

As per coding guidelines: "Ensure keyboard navigation works properly in page components".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Appointments/components/PractitionerSelector.tsx` around lines 529
- 555, The clickable organization card using the div with key={organization.id}
should be made keyboard-accessible: add role="button" and tabIndex={0} to the
div and implement an onKeyDown handler that listens for Enter and Space keys and
calls handleChildOrganizationClick(organization) (same action as the existing
onClick); keep existing onClick as-is and ensure the handler uses the
organization variable passed to the component so keyboard activation mirrors
mouse activation.

149-167: 🧹 Nitpick | 🔵 Trivial

Inefficient caching pattern: searchQuery in queryKey causes redundant API calls.

The searchQuery is included in queryKey but not used in queryFn. This creates separate cache entries for each unique search term, even though the API returns identical data. Every keystroke with a new character triggers a fresh API request.

A more efficient approach would be to cache the full dataset once and filter client-side using useMemo:

♻️ Suggested refactor
  // Fetch all practitioners for search functionality
  const { data: allPractitionersData } = useQuery({
-   queryKey: ["allPractitioners", facilityId, searchQuery, showAllOrgs],
+   queryKey: ["allPractitioners", facilityId, showAllOrgs],
    queryFn: query(scheduleApi.appointments.availableUsers, {
      pathParams: { facilityId },
      queryParams: {
        ...(showAllOrgs
          ? {}
          : {
              organization_ids: organizations.map((org) => org.id).join(","),
            }),
      },
    }),
-   select: (data: { users: UserReadMinimal[] }) =>
-     data.users.filter((user) =>
-       formatName(user).toLowerCase().includes(searchQuery.toLowerCase()),
-     ),
-   enabled: open && !!searchQuery && searchQuery.length > 0,
+   enabled: open && searchQuery.trim().length > 0,
  });
+
+ const allPractitioners = useMemo(
+   () =>
+     allPractitionersData?.users?.filter((user) =>
+       formatName(user).toLowerCase().includes(searchQuery.toLowerCase()),
+     ) ?? [],
+   [allPractitionersData, searchQuery],
+ );

Note: Also added .trim() to the enabled check to avoid triggering queries for whitespace-only searches.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Appointments/components/PractitionerSelector.tsx` around lines 149
- 167, The query includes searchQuery in the queryKey causing redundant cached
entries and API calls; remove searchQuery from the queryKey in the useQuery call
(keep facilityId and showAllOrgs), change the select to return the raw
data.users (no filtering) or omit select, and perform client-side filtering in a
useMemo that derives the filtered practitioners from allPractitioners using
formatName(...).toLowerCase().includes(searchQuery.toLowerCase()). Also tighten
the enabled flag to use open && !!searchQuery.trim() so whitespace-only input
doesn't trigger requests; update references to allPractitioners, the useQuery
call, queryKey array, queryFn (scheduleApi.appointments.availableUsers), select,
and enabled accordingly.
src/pages/Facility/services/inventory/externalSupply/deliveryOrder/PrintDeliveryOrder.tsx (1)

126-133: ⚠️ Potential issue | 🟠 Major

Guard the template preview on facility readiness.

useCurrentFacility() can still be loading here, but PrintPreview is mounted immediately with the new facility prop. That means this document can render or auto-print without facility branding on a cold load. Please add the same isFacilityLoading || !facility guard used in PrintInvoice.

As per coding guidelines, "Implement proper loading states and error boundaries in page components".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/pages/Facility/services/inventory/externalSupply/deliveryOrder/PrintDeliveryOrder.tsx`
around lines 126 - 133, The PrintPreview is being mounted before facility data
is ready; update PrintDeliveryOrder to mirror PrintInvoice by reading
isFacilityLoading from useCurrentFacility and only render PrintPreview when
facility is ready (i.e., guard with !isFacilityLoading && facility), otherwise
render a loading/placeholder (or keep disabled) to prevent rendering/auto-print
without facility branding; locate the useCurrentFacility call and the
PrintPreview JSX in PrintDeliveryOrder.tsx and apply this conditional guard.
src/pages/Facility/services/pharmacy/AllMedicationBillForm.tsx (1)

254-259: ⚠️ Potential issue | 🟠 Major

Editing still collapses multi-instruction regimens to the first entry.

Line 257 only seeds AddMedicationSheet with the first dosage instruction, but the save handler writes its returned array back to the full items.${editingItemIndex}.dosageInstructions field. If a medication already has multiple instructions, opening edit mode here drops every instruction except the first and recalculates lot quantities from that truncated regimen. Either edit a specific instruction in place or block this sheet for multi-instruction rows.

As per coding guidelines, "Implement proper data validation for medical information in page components".

Also applies to: 263-296

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Facility/services/pharmacy/AllMedicationBillForm.tsx` around lines
254 - 259, The code currently passes only the first instruction to
AddMedicationSheet via existingDosageInstructions when editing (using
editingItemIndex and
form.watch(`items.${editingItemIndex}.dosageInstructions`)?.[0]), which
truncates multi-instruction regimens on save; update the logic so
AddMedicationSheet receives the full dosageInstructions array and the specific
instruction index (pass
form.watch(`items.${editingItemIndex}.dosageInstructions`) and
editingInstructionIndex) and modify the save handler to replace only the edited
index in items.${editingItemIndex}.dosageInstructions instead of overwriting the
whole array, or alternatively prevent opening the sheet when the array length >
1 and surface a validation message—apply the same change for the related block
at lines 263-296.
src/pages/Facility/services/pharmacy/PrintMedicationReturn.tsx (1)

124-131: ⚠️ Potential issue | 🟠 Major

Wait for facility before mounting the template preview.

This preview now depends on facility, but it never waits for useCurrentFacility() to resolve. On a cold load the document can render or auto-print without facility branding.

Suggested guard
-  const { facility } = useCurrentFacility();
+  const { facility, isFacilityLoading } = useCurrentFacility();
+
+  if (isFacilityLoading || !facility) {
+    return <Loading />;
+  }
As per coding guidelines, "Implement proper loading states and error boundaries in page components".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Facility/services/pharmacy/PrintMedicationReturn.tsx` around lines
124 - 131, The PrintPreview is mounted before useCurrentFacility() resolves, so
guard rendering on facility: in the component wrapping the PrintPreview (where
useCurrentFacility() is called) check for facility being defined and render a
loading state or null until facility is available; specifically, delay returning
the PrintPreview (with props title, disabled, facility, templateSlug) until
facility is truthy so branding is present, and ensure any auto-print behavior is
suppressed while facility is undefined.
src/components/Medicine/MedicationAdministration/PrintMedicationAdministration.tsx (1)

128-165: ⚠️ Potential issue | 🟠 Major

Do not merge PRN and scheduled requests for the same product.

isPRN is only captured when the group is first created. If the same medication has both scheduled and PRN requests, later requests are merged into whichever section was seen first, so the chart can render scheduled orders under the PRN table or vice versa.

One way to split the groups
-      const productId =
-        med.requested_product?.id || med.medication?.code || med.id;
       const productName =
         med.requested_product?.name ||
         med.medication?.display ||
         "Unknown Medication";
       const isPRN = med.dosage_instruction.some((di) => di.as_needed_boolean);
+      const productId =
+        med.requested_product?.id || med.medication?.code || med.id;
+      const groupKey = `${productId}:${isPRN ? "prn" : "regular"}`;

-      if (!groups.has(productId)) {
-        groups.set(productId, {
+      if (!groups.has(groupKey)) {
+        groups.set(groupKey, {
           productId,
           productName,
           requests: [],
           isPRN,
           hasActiveRequests: false,
         });
       }

-      const group = groups.get(productId)!;
+      const group = groups.get(groupKey)!;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/components/Medicine/MedicationAdministration/PrintMedicationAdministration.tsx`
around lines 128 - 165, The grouping currently uses productId alone (in
medicationRequests.results.forEach and the groups Map) and captures isPRN only
when the group is created, causing PRN and scheduled requests for the same
product to be merged incorrectly; update the grouping key to include PRN state
(e.g., use `${productId}:${isPRN ? 'prn' : 'reg'}`) or maintain separate groups
per productId+isPRN when creating entries and when retrieving group with
groups.get(...), ensure productName still derives from the first item but
requests are pushed into the correct PRN-specific group so later filtering
(regular/prn) uses the group's isPRN flag reliably.
src/pages/Facility/billing/account/components/PrintChargeItems.tsx (1)

136-141: ⚠️ Potential issue | 🟠 Major

Wait for facility data before rendering the template header.

Line 420 now makes the print header depend on facility, but this component still never waits for useCurrentFacility() to resolve. On a cold load the preview can render with missing facility branding before the context is ready. PrintInvoices already guards the same template-backed path.

🛠️ Suggested fix
-  const { facility } = useCurrentFacility();
+  const { facility, isFacilityLoading } = useCurrentFacility();
...
+  if (isFacilityLoading || !facility) {
+    return <TableSkeleton count={3} />;
+  }
...
-        disabled={isLoading || isLoadingPayments || isLoadingAccount}
+        disabled={
+          isLoading ||
+          isLoadingPayments ||
+          isLoadingAccount ||
+          isFacilityLoading
+        }

Also applies to: 416-426

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Facility/billing/account/components/PrintChargeItems.tsx` around
lines 136 - 141, The component PrintChargeItems currently renders the template
header before useCurrentFacility() resolves, causing blank/missing branding;
update PrintChargeItems to wait for the facility data (from
useCurrentFacility()) before rendering the template-backed header—use the
facility value or an isLoadingFacility flag to short-circuit rendering (or
return a loader) until facility is available, similar to how PrintInvoices
guards the template path; locate the facility usage in PrintChargeItems and wrap
the header/template render with a guard that checks facility (or isLoading) to
ensure branding is present.
.github/workflows/playwright.yaml (1)

257-297: ⚠️ Potential issue | 🟠 Major

Don't report ✅ Passed when a shard failed before writing JSON.

This block derives status only from parsed test-results.json. If a shard fails earlier (backend startup, setup project, browser install, etc.), it contributes zero counts and the PR comment can still show success. Fold needs.test.result or missing-shard detection into the status calculation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/playwright.yaml around lines 257 - 297, The merge step
(id: merge) currently computes status only from existing test-results.json files
(TOTAL_TESTS, PASSED_TESTS, FAILED_TESTS, SKIPPED_TESTS) so if a shard failed
before writing JSON the overall status can be erroneously marked Passed; update
the merge logic to detect missing or incomplete shards by checking for missing
all-results/playwright-results-shard-*/test-results.json files or a failed-shard
marker (e.g., a needs.test.result flag or sentinel file produced on early
failure) and treat any missing shard or failure marker as a test failure (set
FAILED_TESTS>0 or directly set status to Failed) before emitting status to
GITHUB_OUTPUT so the final status reflects shards that never wrote
test-results.json.
src/components/ui/multi-filter/tagFilter.tsx (2)

62-84: ⚠️ Potential issue | 🟠 Major

Nested tag groups are pruned before users can open them.

isRootLevel now means “has children”, not “level 0”. For any second-level group, the query is disabled until expansion, loadingChildren is false, hasActiveChildren is false, and this returns null immediately. That makes grouped tags below the first level unreachable.

🛠️ Suggested fix
-  const isRootLevel = tag.has_children;
+  const isGroupTag = tag.has_children;
+  const isRootLevel = level === 0 && isGroupTag;
 ...
-        disabled={isRootLevel && allChildrenSelected}
+        disabled={isGroupTag && allChildrenSelected}
 ...
-          if (isRootLevel) {
+          if (isGroupTag) {
             setExpanded(!expanded);
           } else {
             onTagToggle(tag);
           }
 ...
-          {isRootLevel ? (
+          {isGroupTag ? (
             <Component className="h-4 w-4 text-black/80" strokeWidth={1.25} />
           ) : (
             <Checkbox checked={isSelected} className="h-4 w-4" />
           )}
 ...
-          {isRootLevel && !allChildrenSelected && (
+          {isGroupTag && !allChildrenSelected && (
             <ChevronRight
               className={cn(
                 "h-3 w-3 transition-transform",
                 expanded && "rotate-90",
               )}
             />
           )}
 ...
-      {expanded && isRootLevel && (
+      {expanded && isGroupTag && (
         <div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/multi-filter/tagFilter.tsx` around lines 62 - 84, The
component incorrectly uses isRootLevel = tag.has_children to decide pruning,
causing non-root groups to be returned null; change the pruning condition to
only skip rendering for actual root level groups by replacing the if check with
one that uses level (e.g., if (level === 0 && !loadingChildren &&
!hasActiveChildren) return null) and/or rename isRootLevel to hasChildren to
avoid confusion; update references to isRootLevel in this file (tagFilter.tsx)
so only level === 0 is used for the early-return logic while leaving the
query.enabled logic (tag.has_children && (level === 0 || expanded)) unchanged.

63-74: ⚠️ Potential issue | 🟠 Major

Add facilityId to the child-tag query key.

The root-level query includes facilityId in its key (["tags", resource, search, facilityId]), but this child query does not. Since both queries pass facility: facilityId in their params, the cache key should be consistent. Without it, switching facilities could cause React Query to serve cached results from the previous facility while the root and sibling queries refetch under different keys.

Suggested fix
-    queryKey: ["tags", resource, "parent", tag.id],
+    queryKey: ["tags", resource, "parent", tag.id, facilityId],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/multi-filter/tagFilter.tsx` around lines 63 - 74, The
child-tag useQuery currently builds queryKey as ["tags", resource, "parent",
tag.id] but passes facility: facilityId in query params; update the queryKey in
the useQuery call (the one invoking tagConfigApi.list) to include facilityId
(e.g. append facilityId to the array) so the child query cache is namespaced per
facility and stays consistent with the root query; keep the same enabled
condition (tag.has_children && (level === 0 || expanded)).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c5e73a20-e795-41e5-80ed-ad7c67826c00

📥 Commits

Reviewing files that changed from the base of the PR and between 0864aac and a0bf00d.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (107)
  • .github/workflows/playwright.yaml
  • .gitignore
  • CLAUDE.md
  • package.json
  • playwright.config.ts
  • public/locale/en.json
  • scripts/playwright-db.sh
  • src/CAREUI/misc/PrintPreview.tsx
  • src/Utils/inventory.ts
  • src/Utils/schema/computedField.ts
  • src/Utils/schema/types.ts
  • src/components/Auth/Login.tsx
  • src/components/Billing/ChargeItems/ChargeItemsSection.tsx
  • src/components/Common/PrintTable.tsx
  • src/components/Common/ProductionWarningBanner.tsx
  • src/components/Common/RequirementsSelector.tsx
  • src/components/Encounter/EncounterInfoCard.tsx
  • src/components/Facility/ConsultationDetails/PrintAllQuestionnaireResponses.tsx
  • src/components/Facility/ConsultationDetails/PrintQuestionnaireResponse.tsx
  • src/components/Location/BedListing.tsx
  • src/components/Location/LocationNavigation.tsx
  • src/components/Location/hooks/useLocationNavigation.ts
  • src/components/Medicine/DosageInstructionList.tsx
  • src/components/Medicine/MedicationAdministration/GroupedMedicationRow.tsx
  • src/components/Medicine/MedicationAdministration/MedicineAdminForm.tsx
  • src/components/Medicine/MedicationAdministration/MedicineAdminSheet.tsx
  • src/components/Medicine/MedicationAdministration/PrintMedicationAdministration.tsx
  • src/components/Medicine/MedicationAdministration/utils.ts
  • src/components/Medicine/MedicationRequestTable/DispenseHistory.tsx
  • src/components/Medicine/MedicationsTable.tsx
  • src/components/Medicine/utils.ts
  • src/components/Patient/TreatmentSummary.tsx
  • src/components/Prescription/PrescriptionPreview.tsx
  • src/components/Questionnaire/ManageResponseTemplatesSheet.tsx
  • src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx
  • src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx
  • src/components/Questionnaire/QuestionnaireEditor.tsx
  • src/components/Resource/PrintResourceLetter.tsx
  • src/components/Users/UserForm.tsx
  • src/components/ui/multi-filter/dateFilter.tsx
  • src/components/ui/multi-filter/departmentFilter.tsx
  • src/components/ui/multi-filter/filterConfigs.tsx
  • src/components/ui/multi-filter/tagFilter.tsx
  • src/components/ui/sidebar/facility/location/location-switcher.tsx
  • src/hooks/useExtensions.tsx
  • src/pages/Admin/Roles/RoleForm.tsx
  • src/pages/Admin/organizations/components/AdminOrganizationFormSheet.tsx
  • src/pages/Appointments/AppointmentPrint.tsx
  • src/pages/Appointments/components/PractitionerSelector.tsx
  • src/pages/Appointments/components/PrintAppointments.tsx
  • src/pages/Facility/billing/PaymentReconciliationSheet.tsx
  • src/pages/Facility/billing/account/AccountShow.tsx
  • src/pages/Facility/billing/account/components/BedChargeItemsTable.tsx
  • src/pages/Facility/billing/account/components/ChargeItemsTable.tsx
  • src/pages/Facility/billing/account/components/PrintChargeItems.tsx
  • src/pages/Facility/billing/account/utils.ts
  • src/pages/Facility/billing/invoice/InvoiceShow.tsx
  • src/pages/Facility/billing/invoice/PrintInvoice.tsx
  • src/pages/Facility/billing/invoice/PrintInvoices.tsx
  • src/pages/Facility/billing/paymentReconciliation/PrintPaymentReconciliation.tsx
  • src/pages/Facility/locations/LocationContent.tsx
  • src/pages/Facility/queues/ManageQueue.tsx
  • src/pages/Facility/queues/ManageQueueOngoingTab.tsx
  • src/pages/Facility/queues/ServicePointsDropDown.tsx
  • src/pages/Facility/services/diagnosticReports/DiagnosticReportPrint.tsx
  • src/pages/Facility/services/inventory/InventoryList.tsx
  • src/pages/Facility/services/inventory/StockLotSelector.tsx
  • src/pages/Facility/services/inventory/SupplyDeliveryTable.tsx
  • src/pages/Facility/services/inventory/ToDispatch.tsx
  • src/pages/Facility/services/inventory/ToReceive.tsx
  • src/pages/Facility/services/inventory/externalSupply/deliveryOrder/DeliveryOrderShow.tsx
  • src/pages/Facility/services/inventory/externalSupply/deliveryOrder/PrintDeliveryOrder.tsx
  • src/pages/Facility/services/inventory/externalSupply/requestOrder/PrintRequestOrder.tsx
  • src/pages/Facility/services/inventory/externalSupply/requestOrder/RequestOrderShow.tsx
  • src/pages/Facility/services/inventory/externalSupply/utils/inventoryUtils.ts
  • src/pages/Facility/services/pharmacy/AllMedicationBillForm.tsx
  • src/pages/Facility/services/pharmacy/DispensedMedicationList.tsx
  • src/pages/Facility/services/pharmacy/MedicationBillForm.tsx
  • src/pages/Facility/services/pharmacy/MedicationDispenseList.tsx
  • src/pages/Facility/services/pharmacy/PrintDispenseOrder.tsx
  • src/pages/Facility/services/pharmacy/PrintMedicationReturn.tsx
  • src/pages/Facility/services/pharmacy/components/DispensedItemsSheet.tsx
  • src/pages/Facility/services/pharmacy/components/MedicationBillRow.tsx
  • src/pages/Facility/services/serviceRequests/components/AddMultipleChargeItemsSheet.tsx
  • src/pages/Facility/settings/activityDefinition/ActivityDefinitionForm.tsx
  • src/pages/Facility/settings/observationDefinition/ObservationDefinitionForm.tsx
  • src/pages/Facility/settings/organizations/FacilityOrganizationList.tsx
  • src/style/index.css
  • src/types/emr/medicationRequest/medicationRequest.ts
  • src/types/facility/printTemplate.ts
  • src/types/location/utils.ts
  • src/types/scheduling/schedule.ts
  • tests/PLAYWRIGHT_GUIDE.md
  • tests/admin/roles/role.spec.ts
  • tests/facility/patient/encounter/careTeam/linkCareTeam.spec.ts
  • tests/facility/patient/encounter/files/drawings/encounterDrawing.spec.ts
  • tests/facility/patient/encounter/medicine/prescriptionEdit.spec.ts
  • tests/facility/patient/encounter/structuredQuestions/medicationRequest.spec.ts
  • tests/facility/settings/activityDefinition/categoryList.spec.ts
  • tests/facility/settings/billing/discount/discountCodeCreate.spec.ts
  • tests/facility/settings/chargeItemDefinition/chargeItemDefinitionsCreate.spec.ts
  • tests/facility/settings/chargeItemDefinition/chargeItemDefinitionsDelete.spec.ts
  • tests/facility/settings/chargeItemDefinition/chargeItemDefinitionsEdit.spec.ts
  • tests/facility/settings/departments/departmentUserCreate.spec.ts
  • tests/facility/settings/departments/departmentUserManage.spec.ts
  • tests/globalSetup.ts
  • tests/organization/user/userCreation.spec.ts
💤 Files with no reviewable changes (1)
  • src/pages/Facility/locations/LocationContent.tsx

Comment on lines +33 to +35
/* CI workers are controlled per-phase in the workflow (setup=1, chromium=4).
* Locally, use all available cores. */
workers: undefined,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Setup specs:"
rg --files tests -g '*.setup.ts'

echo
echo "Generic Playwright entrypoints:"
rg -nP '(npx\s+)?playwright test(?!.*--project=)' package.json .github/workflows

echo
echo "Relevant config block:"
sed -n '28,70p' playwright.config.ts

Repository: ohcnetwork/care_fe

Length of output: 2334


Config removes setup→chromium ordering on CI, breaking ad-hoc test runs.

fullyParallel: false ensures setup specs run serially among themselves, but does not prevent chromium tests from starting in parallel. The dependencies: process.env.CI ? [] : ["setup"] removes the cross-project dependency entirely when CI=true, making npm run playwright:test unsafe on CI. While the workflow mitigates this by running --project=setup separately before chromium, the config itself provides no guarantees for generic entrypoints like "playwright:test": "playwright test" or "playwright:test:headed".

Setup specs have documented ordering dependencies (e.g., patient.setup.ts depends on facility.setup.ts for facilityId), yet CI environments can bypass this if setup ordering is not enforced by the config. Consider restoring the setup dependency for CI environments or documenting that playwright test must never be invoked directly on CI without explicit project sequencing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@playwright.config.ts` around lines 33 - 35, The config removes cross-project
ordering on CI by setting dependencies: process.env.CI ? [] : ["setup"], which
allows chromium projects to start before setup finishes; change the project
dependency so the chromium projects always depend on the "setup" project (i.e.,
set dependencies: ["setup"] for the chromium project(s) instead of an empty
array when process.env.CI is true), keep fullyParallel: false for intra-project
serial setup, and retain workers behavior as intended; update the dependencies
property in playwright.config.ts (and preserve the "setup" project name) so CI
runs cannot bypass setup ordering.

"add_diagnosis_one": "Add Diagnosis",
"add_diagnosis_other": "Add another Diagnosis",
"add_discount": "Add Discount",
"add_dosage_step": "Add Dosage Step",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Append new en.json keys at the end of the file

These new translation keys are inserted throughout the file instead of being appended at the bottom of public/locale/en.json. Please move newly introduced keys to the end to match repository i18n workflow.

As per coding guidelines: "Append missing i18n keys directly to the end of the public/locale/en.json file without reading i18n files".

Also applies to: 512-513, 731-731, 1418-1418, 1804-1804, 1970-1970, 2431-2431, 3321-3321, 3460-3460, 4758-4758, 4886-4886, 5317-5317, 6425-6425, 6457-6457

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@public/locale/en.json` at line 432, New translation keys (e.g.,
"add_dosage_step" and the other newly introduced keys mentioned in the review)
were inserted mid-file instead of being appended; move each newly added key
string to the end of public/locale/en.json so all new i18n keys are appended
only, ensure the JSON remains valid (commas, no duplicate keys), remove the
mid-file entries after appending, and run any i18n/lint checks to verify no
duplicates or formatting errors remain.

Comment on lines +54 to +81
const [imagesReady, setImagesReady] = useState(false);
const printSectionRef = useRef<HTMLDivElement>(null);

useEffect(() => {
setImagesReady(false);
const node = printSectionRef.current;
if (!node || props.disabled) return;

let cancelled = false;

const waitForImages = async () => {
const images = Array.from(node.querySelectorAll("img"));
await Promise.all(
images.map((img) =>
img.complete
? Promise.resolve()
: img.decode().catch(() => undefined),
),
);
if (!cancelled) setImagesReady(true);
};

waitForImages();

return () => {
cancelled = true;
};
}, [props.disabled, props.facility]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding content dependency to image loading effect.

The useEffect that waits for images to load depends on [props.disabled, props.facility]. If the children content changes dynamically (e.g., data loads asynchronously), new images may be added that aren't detected. Consider adding a content-related dependency or using a MutationObserver for more robust detection.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CAREUI/misc/PrintPreview.tsx` around lines 54 - 81, The effect that waits
for images in PrintPreview (useEffect using printSectionRef, imagesReady and
waitForImages) currently only reruns on props.disabled and props.facility so
dynamically added/changed children/images are missed; update the effect to
either include the content-related dependency (e.g., props.children or whatever
prop supplies the preview HTML) in the dependency array so waitForImages reruns
when content changes, or replace/augment the logic with a MutationObserver
attached to printSectionRef.current that calls waitForImages on
childList/subtree mutations and is properly disconnected in the cleanup; ensure
the observer and cancelled flag respect props.disabled and that you still
setImagesReady(true) only when not cancelled.

Comment on lines +27 to +30
<div className="pointer-events-auto group relative animate-pulse">
<div className="absolute -inset-1 rounded-full bg-red-500 opacity-50 blur-sm" />
<div className="relative flex items-center gap-2 rounded-full bg-red-600 px-4 py-2 text-white sm:shadow-lg ring-2 ring-red-400 transition-all hover:scale-105 hover:shadow-xl">
<AlertTriangleIcon className="h-4 w-4 shrink-0 animate-pulse" />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Redundant animate-pulse and missing motion preference handling.

  1. animate-pulse is applied to both the parent (line 27) and the icon (line 30), creating overlapping animations. Consider removing one.

  2. For WCAG compliance, respect user motion preferences using Tailwind's motion-reduce: variant, especially important in healthcare contexts where users may have vestibular disorders.

Proposed fix
-      <div className="pointer-events-auto group relative animate-pulse">
+      <div className="pointer-events-auto group relative motion-safe:animate-pulse">
         <div className="absolute -inset-1 rounded-full bg-red-500 opacity-50 blur-sm" />
         <div className="relative flex items-center gap-2 rounded-full bg-red-600 px-4 py-2 text-white sm:shadow-lg ring-2 ring-red-400 transition-all hover:scale-105 hover:shadow-xl">
-          <AlertTriangleIcon className="h-4 w-4 shrink-0 animate-pulse" />
+          <AlertTriangleIcon className="h-4 w-4 shrink-0" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Common/ProductionWarningBanner.tsx` around lines 27 - 30, The
ProductionWarningBanner applies animate-pulse twice (on the parent div and on
AlertTriangleIcon), causing redundant overlapping animation; remove one of the
animate-pulse classes (keep it either on the parent container or the
AlertTriangleIcon) and add Tailwind's motion-reduce handling (e.g., use
motion-reduce:animate-none on whichever element keeps the animation) so the
component respects user prefers-reduced-motion; update the className strings on
the parent div and/or AlertTriangleIcon accordingly.

Comment on lines +184 to +195
<Button variant="link" aria-label={t("patient_home")} asChild>
<Link
basePath="/"
href={`/facility/${facilityId}/patients/home?${new URLSearchParams({
phone_number: encounter.patient.phone_number,
year_of_birth: encounter.patient.year_of_birth?.toString() || "",
partial_id: encounter.patient.id.slice(0, 5),
}).toString()}`}
className="text-gray-700 underline hover:text-gray-900 text-sm font-medium"
>
{t("patient_home")}
</Link>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't put patient identifiers into this link.

Serializing phone_number, year_of_birth, and partial_id into the query string leaks PHI into browser history, referrer headers, proxy logs, and screenshots. Use an internal identifier or navigation state instead of demographics in the URL.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Encounter/EncounterInfoCard.tsx` around lines 184 - 195, The
link currently serializes PHI (encounter.patient.phone_number, year_of_birth,
partial_id) into the query string; replace those query params with a non‑PHI
identifier (e.g., encounter.patient.id or a server-issued
internalPatientId/token) or move the data into navigation state/session so no
demographics appear in the URL. Update the Button/Link usage that constructs
href (the Link component and its URLSearchParams call) to only include the safe
internal identifier (or remove href and perform a programmatic navigation that
passes state) and ensure any backend routes expect the internal id instead of
PHI.

Comment on lines +96 to +100
const prescriptionDate = page
.getByText(/^\d{2}\/\d{2}\/\d{4} \d{2}:\d{2} (AM|PM)$/)
.first()
.click();
.first();
await expect(prescriptionDate).toBeVisible({ timeout: 10000 });
await prescriptionDate.click();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Scope the date click to the medication row to avoid flaky mis-clicks.

Line 96 and Line 139 use a global date regex with .first(). If multiple prescriptions are listed, this can open the wrong record and intermittently fail assertions downstream. Anchor the date locator to the row containing medicineName.

🔧 Suggested fix
-      const prescriptionDate = page
-        .getByText(/^\d{2}\/\d{2}\/\d{4} \d{2}:\d{2} (AM|PM)$/)
-        .first();
+      const medicationRow = page.getByRole("row").filter({ hasText: medicineName }).first();
+      const prescriptionDate = medicationRow.getByText(
+        /^\d{2}\/\d{2}\/\d{4} \d{2}:\d{2} (AM|PM)$/,
+      );
       await expect(prescriptionDate).toBeVisible({ timeout: 10000 });
       await prescriptionDate.click();

Also applies to: 139-143

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/facility/patient/encounter/medicine/prescriptionEdit.spec.ts` around
lines 96 - 100, The date locator is too global and uses .first(), causing flaky
clicks; update the logic that defines prescriptionDate to first locate the
medication row containing the specific medicineName text (anchor by the
medicineName locator), then within that row find the date element matching the
/^\d{2}\/\d{2}\/\d{4 \d{2}:\d{2} (AM|PM)$/ regex and click it; replace both
occurrences that set prescriptionDate (the one at the top and the one around
lines 139-143) so they scope the date lookup to the row containing medicineName
rather than using .first().

durationData = faker.helpers.arrayElement(DURATION_UNITS);
durationLabel = durationData.label;
durationUnit = durationData.value;
durationUnit = faker.helpers.arrayElement(DURATION_UNITS);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Plural-only duration unit handling can make this test flaky.

At Line 115, unit values are plural-only (e.g., "days"), while duration can be 1. This leads to assertions like "1 days" at Lines 160/163 and 277/280, which can fail if the UI renders singular labels ("1 day").

💡 Suggested fix
 const DURATION_UNITS = ["days", "hours", "weeks", "months", "years"];
@@
   let durationUnit: string;
+  let durationDisplayUnit: string;
   let duration: number;
@@
     durationUnit = faker.helpers.arrayElement(DURATION_UNITS);
     duration = faker.number.int({ min: 1, max: INT_MAX });
+    durationDisplayUnit = duration === 1 ? durationUnit.replace(/s$/, "") : durationUnit;
@@
     await page
       .getByPlaceholder("Type eg. 5 days, 2 weeks")
-      .fill(`${duration} ${durationUnit}`);
+      .fill(`${duration} ${durationDisplayUnit}`);
@@
     await page
-      .getByRole("option", { name: `${duration} ${durationUnit}` })
+      .getByRole("option", { name: `${duration} ${durationDisplayUnit}` })
       .click();
@@
     await page
       .getByPlaceholder("Type eg. 5 days, 2 weeks")
-      .fill(`${duration} ${durationUnit}`);
+      .fill(`${duration} ${durationDisplayUnit}`);
@@
     await page
-      .getByRole("option", { name: `${duration} ${durationUnit}` })
+      .getByRole("option", { name: `${duration} ${durationDisplayUnit}` })
       .click();

Also applies to: 160-163, 277-280

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tests/facility/patient/encounter/structuredQuestions/medicationRequest.spec.ts`
at line 115, The test picks durationUnit from DURATION_UNITS and can produce
plural-only units like "days" while duration may be 1, causing assertions like
"1 days" to fail; fix by making durationUnit agree with duration — update the
test's setup where durationUnit is assigned (variable durationUnit and array
DURATION_UNITS) to choose a singular form when duration === 1 (or normalize the
final displayed string by pluralizing/singularizing based on duration) so
assertions that compose `${duration} ${durationUnit}` match the UI rendering.

Comment on lines 85 to 87
await expect(page).toHaveURL(
`/facility/${facilityId}/settings/activity_definitions/categories/f-${facilityId}-${testData.slug}`,
`/facility/${facilityId}/settings/activity_definitions/categories/f-${facilityId}-${testData.slug}?status=active`,
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

fd -t f "categoryList.spec.ts" tests/

Repository: ohcnetwork/care_fe

Length of output: 125


🏁 Script executed:

cat -n tests/facility/settings/activityDefinition/categoryList.spec.ts | head -100

Repository: ohcnetwork/care_fe

Length of output: 3864


🏁 Script executed:

cat -n tests/facility/settings/activityDefinition/categoryList.spec.ts | sed -n '80,200p'

Repository: ohcnetwork/care_fe

Length of output: 4920


🏁 Script executed:

fd -t f "categories" src/pages/facility/settings/ | head -20

Repository: ohcnetwork/care_fe

Length of output: 177


🏁 Script executed:

rg "activity_definitions/categories" src/ -t ts -t tsx -A 5 | head -50

Repository: ohcnetwork/care_fe

Length of output: 89


🏁 Script executed:

fd -t f "*.spec.ts" tests/facility/overview/ | head -5

Repository: ohcnetwork/care_fe

Length of output: 173


🏁 Script executed:

cat -n tests/facility/overview/overview.spec.ts | sed -n '1,50p'

Repository: ohcnetwork/care_fe

Length of output: 134


🏁 Script executed:

git ls-files --others --exclude-standard | head -5

Repository: ohcnetwork/care_fe

Length of output: 139


🏁 Script executed:

find tests -name "*.spec.ts" -type f | head -20

Repository: ohcnetwork/care_fe

Length of output: 1079


🏁 Script executed:

find src -type d -name "*activity*" | head -10

Repository: ohcnetwork/care_fe

Length of output: 141


🏁 Script executed:

rg "activity_definitions/categories" src/ -A 3 -B 3 | head -60

Repository: ohcnetwork/care_fe

Length of output: 3851


🏁 Script executed:

cat -n src/pages/Facility/settings/activityDefinition/CategoryList.tsx | head -150

Repository: ohcnetwork/care_fe

Length of output: 157


🏁 Script executed:

rg "status=active" src/ --no-heading | head -20

Repository: ohcnetwork/care_fe

Length of output: 452


🏁 Script executed:

find tests -name "*overview*" -type f

Repository: ohcnetwork/care_fe

Length of output: 44


🏁 Script executed:

ls -la src/pages/Facility/settings/activityDefinition/

Repository: ohcnetwork/care_fe

Length of output: 498


🏁 Script executed:

rg "status.*active" src/pages/Facility/settings/ --no-heading

Repository: ohcnetwork/care_fe

Length of output: 3702


🏁 Script executed:

cat -n src/pages/Facility/settings/activityDefinition/ResourceCategoryList.tsx 2>/dev/null | head -200

Repository: ohcnetwork/care_fe

Length of output: 44


🏁 Script executed:

fd "ResourceCategory" src/pages/Facility/settings/

Repository: ohcnetwork/care_fe

Length of output: 44


🏁 Script executed:

cat -n src/pages/Facility/settings/activityDefinition/ActivityDefinitionListComponent.tsx | sed -n '1,100p'

Repository: ohcnetwork/care_fe

Length of output: 4120


🏁 Script executed:

rg "navigate.*categories" src/pages/Facility/settings/activityDefinition/ -B 3 -A 3

Repository: ohcnetwork/care_fe

Length of output: 44


🏁 Script executed:

cat -n src/pages/Facility/settings/activityDefinition/ActivityDefinitionList.tsx

Repository: ohcnetwork/care_fe

Length of output: 2946


🏁 Script executed:

rg "ResourceCategoryList" src/components/Common/ -l

Repository: ohcnetwork/care_fe

Length of output: 108


🏁 Script executed:

cat -n src/components/Common/ResourceCategoryList.tsx | head -200

Repository: ohcnetwork/care_fe

Length of output: 7838


🏁 Script executed:

cat -n src/components/Common/ResourceCategoryList.tsx | sed -n '200,400p'

Repository: ohcnetwork/care_fe

Length of output: 7426


🏁 Script executed:

rg "status=active" src/components/Common/ResourceCategoryList.tsx -B 5 -A 5

Repository: ohcnetwork/care_fe

Length of output: 44


🏁 Script executed:

cat -n src/pages/Facility/settings/layout.tsx | grep -A 20 "activity_definitions/categories"

Repository: ohcnetwork/care_fe

Length of output: 1068


🏁 Script executed:

rg "useQuery|useFilters" src/pages/Facility/settings/activityDefinition/ActivityDefinitionListComponent.tsx -B 2 -A 8 | head -100

Repository: ohcnetwork/care_fe

Length of output: 1551


🏁 Script executed:

cat -n src/pages/Facility/settings/activityDefinition/ActivityDefinitionListComponent.tsx | sed -n '160,250p'

Repository: ohcnetwork/care_fe

Length of output: 3345


🏁 Script executed:

rg "navigate.*status" src/pages/Facility/settings/activityDefinition/ -B 3 -A 3

Repository: ohcnetwork/care_fe

Length of output: 44


🏁 Script executed:

rg "defaultQueryParams" src/pages/Facility/settings/activityDefinition/ -B 5 -A 5

Repository: ohcnetwork/care_fe

Length of output: 1274


🏁 Script executed:

cat -n src/hooks/useFilters.ts | head -100

Repository: ohcnetwork/care_fe

Length of output: 117


🏁 Script executed:

rg "useFilters" src/hooks/useFilters.ts -A 30 | head -60

Repository: ohcnetwork/care_fe

Length of output: 125


🏁 Script executed:

fd "useFilters" src/ -type f

Repository: ohcnetwork/care_fe

Length of output: 232


🏁 Script executed:

rg "export.*useFilters" src/ -B 2 -A 20 | head -80

Repository: ohcnetwork/care_fe

Length of output: 1261


🏁 Script executed:

cat -n src/hooks/useFilters.tsx | sed -n '1,100p'

Repository: ohcnetwork/care_fe

Length of output: 3823


🏁 Script executed:

cat -n src/pages/Facility/settings/activityDefinition/ActivityDefinitionList.tsx | sed -n '15,35p'

Repository: ohcnetwork/care_fe

Length of output: 734


🏁 Script executed:

cat -n src/hooks/useFilters.tsx | sed -n '78,120p'

Repository: ohcnetwork/care_fe

Length of output: 1575


🏁 Script executed:

rg "qParams\." src/pages/Facility/settings/activityDefinition/ActivityDefinitionListComponent.tsx | head -20

Repository: ohcnetwork/care_fe

Length of output: 452


🏁 Script executed:

cat -n src/pages/Facility/settings/activityDefinition/ActivityDefinitionListComponent.tsx | sed -n '180,205p'

Repository: ohcnetwork/care_fe

Length of output: 1049


🏁 Script executed:

rg "updateQuery|qParams" src/pages/Facility/settings/activityDefinition/ActivityDefinitionListComponent.tsx | head -30

Repository: ohcnetwork/care_fe

Length of output: 951


🏁 Script executed:

rg "toContain" tests/ --type ts | head -10

Repository: ohcnetwork/care_fe

Length of output: 1111


🏁 Script executed:

rg "\.toHaveURL" tests/ --type ts | head -10

Repository: ohcnetwork/care_fe

Length of output: 1048


Use contains-based URL assertions for dynamic query parameters.

The activity definition category pages dynamically add query parameters via the useFilters hook (e.g., status=active, page=1). Exact toHaveURL() assertions will become brittle when filter state changes. Use expect(page.url()).toContain(...) to verify the path and essential params without requiring an exact match.

Applies to lines: 85-87, 123-125, 192-194

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/facility/settings/activityDefinition/categoryList.spec.ts` around lines
85 - 87, The tests use exact toHaveURL assertions that break when dynamic query
params change; update each assertion that checks the category URL (the ones
referencing page, facilityId and testData.slug) to use a contains-based check
against page.url() instead of toHaveURL(), e.g. assert page.url() contains the
path
`/facility/${facilityId}/settings/activity_definitions/categories/f-${facilityId}-${testData.slug}`
and separately assert it contains the essential query param like `status=active`
if needed; replace the three occurrences that currently call toHaveURL(...) with
these contains-based assertions to avoid brittleness.

Comment on lines +69 to +74
const codeField = page.getByRole("textbox", { name: /code/i });
const codeFieldContainer = page.locator("div").filter({ has: codeField });

await expect(codeFieldContainer.getByText(/^code$/i)).toBeVisible();
await expect(codeField).toHaveAttribute("aria-invalid", "true");
await expect(codeFieldContainer.getByText(/required/i)).toBeVisible();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider using the shared field error helper for consistency.

The test helpers tests/helper/error provide getFieldErrorMessage for validating form field errors. Using it would align with the patterns documented in tests/PLAYWRIGHT_GUIDE.md.

♻️ Alternative using helper
+import { getFieldErrorMessage } from "tests/helper/error";
+
 // In test:
-    const codeField = page.getByRole("textbox", { name: /code/i });
-    const codeFieldContainer = page.locator("div").filter({ has: codeField });
-
-    await expect(codeFieldContainer.getByText(/^code$/i)).toBeVisible();
-    await expect(codeField).toHaveAttribute("aria-invalid", "true");
-    await expect(codeFieldContainer.getByText(/required/i)).toBeVisible();
+    const codeField = page.getByRole("textbox", { name: /code/i });
+    await expect(codeField).toHaveAttribute("aria-invalid", "true");
+    await expect(getFieldErrorMessage(codeField)).toContainText("required");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/facility/settings/billing/discount/discountCodeCreate.spec.ts` around
lines 69 - 74, Replace the ad-hoc assertions using codeField and
codeFieldContainer with the shared helper: instead of manually checking
visibility and aria-invalid on codeField and its container, call the test helper
getFieldErrorMessage(...) from tests/helper/error to assert the field label and
the "required" error message and verify the aria-invalid state; update the test
around the codeField/codeFieldContainer locators and use getFieldErrorMessage
with the same field identifier to keep consistency with PLAYWRIGHT_GUIDE.md
patterns.

Comment on lines +56 to +67
await page
.getByRole("combobox")
.filter({ hasText: "Select organization" })
.click();
await page.getByPlaceholder("Search organization").fill(userType);
await page.getByRole("option", { name: userType }).click();
await page
.getByRole("combobox")
.filter({ hasText: "Select designation" })
.click();
await page.getByPlaceholder("Search Roles").fill("Member");
await page.getByRole("option", { name: "Member" }).click();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't feed the new responsibilities picker with the old USER_TYPES set.

This flow no longer targets the old user-type field, but USER_TYPES still includes values like Volunteer and Administrator while the downstream org-link step only uses the seeded ROLES set. That makes the new dropdowns depend on options/designations that may not exist and turns this spec flaky. Reuse the same known-valid role for both steps.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/facility/settings/departments/departmentUserCreate.spec.ts` around
lines 56 - 67, The test uses the old USER_TYPES value when filling the
organization/designation pickers which makes the new responsibilities picker
flaky; update the spec so the same known-valid seeded role (the ROLES value used
later) is used for both steps instead of USER_TYPES — locate the combobox
interactions that use getByPlaceholder("Search organization") and
getByPlaceholder("Search Roles") and replace the userType input with the seeded
role variable (the same value passed to the downstream org-link step) so both
dropdowns target an option that definitely exists.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review continued from previous batch...

Comment on lines +75 to +76
DJANGO_SETTINGS_MODULE=config.settings.local DJANGO_READ_DOT_ENV_FILE=true .venv/bin/python manage.py migrate --verbosity=0
DJANGO_SETTINGS_MODULE=config.settings.local DJANGO_READ_DOT_ENV_FILE=true .venv/bin/python manage.py load_fixtures
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Hardcoded venv path assumes standard setup.

The script assumes the backend uses .venv/bin/python for the virtual environment. If developers use different venv paths (e.g., venv/, env/, or system Python), this will fail.

♻️ Consider making venv path configurable
+PYTHON="${CARE_PYTHON:-$CARE_DIR/.venv/bin/python}"
+
 # Run migrations and load fixtures
 cd "$CARE_DIR"
-DJANGO_SETTINGS_MODULE=config.settings.local DJANGO_READ_DOT_ENV_FILE=true .venv/bin/python manage.py migrate --verbosity=0
-DJANGO_SETTINGS_MODULE=config.settings.local DJANGO_READ_DOT_ENV_FILE=true .venv/bin/python manage.py load_fixtures
+DJANGO_SETTINGS_MODULE=config.settings.local DJANGO_READ_DOT_ENV_FILE=true "$PYTHON" manage.py migrate --verbosity=0
+DJANGO_SETTINGS_MODULE=config.settings.local DJANGO_READ_DOT_ENV_FILE=true "$PYTHON" manage.py load_fixtures
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/playwright-db.sh` around lines 75 - 76, The script currently
hardcodes the Python interpreter to ".venv/bin/python" for the
DJANGO_SETTINGS_MODULE commands (the manage.py migrate and load_fixtures calls);
make the venv path configurable by introducing a VENV_PYTHON (or similar)
variable that first checks $VENV_PYTHON, then $VIRTUAL_ENV/bin/python, then
common fallbacks like .venv/bin/python and venv/bin/python, and finally falls
back to "python3" from PATH, and use that variable in place of the hardcoded
".venv/bin/python" in the DJANGO_SETTINGS_MODULE ... manage.py migrate and ...
manage.py load_fixtures invocations so developers with different venv layouts or
system Python will work.

Comment on lines 67 to +97
<DropdownMenuItem
onSelect={(e) => {
e.preventDefault();
if (hasChildren) {
setExpanded(!expanded);
}
onOrgToggle(org);
}}
className="flex items-center gap-2 px-2 py-1 cursor-pointer"
style={{ paddingLeft: `${level * 16 + 8}px` }}
>
<div className="flex items-center gap-2 flex-1">
<Checkbox checked={isSelected} className="h-4 w-4" />
<div className="flex items-center gap-2 flex-1 min-w-0">
<Checkbox
checked={isSelected}
className="data-[state=checked]:border-primary-700 text-white shrink-0"
/>
<div
className={cn(
"h-3 w-3 rounded-full shrink-0 border",
getColorForOrg(org.id, 0),
)}
className={cn("size-3 rounded-full border", getColorForOrg(org.id))}
/>
<span className="text-sm truncate flex-1">{org.name}</span>
{hasChildren && (
<ChevronRight
className={cn(
"h-3 w-3 transition-transform",
expanded && "rotate-90",
)}
/>
<Button
variant="ghost"
size="xs"
onClick={(e) => {
e.stopPropagation();
setExpanded(!expanded);
}}
className="w-12 justify-center -mr-2"
>
<ChevronRight
className={cn("transition-transform", expanded && "rotate-90")}
/>
</Button>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -t f departmentFilter.tsx

Repository: ohcnetwork/care_fe

Length of output: 113


🏁 Script executed:

cat -n src/components/ui/multi-filter/departmentFilter.tsx | head -150

Repository: ohcnetwork/care_fe

Length of output: 5695


🏁 Script executed:

rg -A 5 -B 5 "onOrgToggle\|setExpanded" src/components/ui/multi-filter/departmentFilter.tsx

Repository: ohcnetwork/care_fe

Length of output: 44


Expansion requires keyboard handler on the menu item.

The nested chevron Button handles expand/collapse only via mouse click. Keyboard users can select departments but cannot expand child departments from the focused row. Add a keyboard handler (e.g., ArrowRight key) to DropdownMenuItem to toggle expansion, or switch to a submenu pattern that exposes expansion to keyboard focus.

Current implementation (lines 67-97)
      <DropdownMenuItem
        onSelect={(e) => {
          e.preventDefault();
          onOrgToggle(org);
        }}
        className="flex items-center gap-2 px-2 py-1 cursor-pointer"
        style={{ paddingLeft: `${level * 16 + 8}px` }}
      >
        <div className="flex items-center gap-2 flex-1 min-w-0">
          <Checkbox
            checked={isSelected}
            className="data-[state=checked]:border-primary-700 text-white shrink-0"
          />
          <div
            className={cn("size-3 rounded-full border", getColorForOrg(org.id))}
          />
          <span className="text-sm truncate flex-1">{org.name}</span>
          {hasChildren && (
            <Button
              variant="ghost"
              size="xs"
              onClick={(e) => {
                e.stopPropagation();
                setExpanded(!expanded);
              }}
              className="w-12 justify-center -mr-2"
            >
              <ChevronRight
                className={cn("transition-transform", expanded && "rotate-90")}
              />
            </Button>
          )}
        </div>
      </DropdownMenuItem>

Per coding guidelines, ensure full keyboard navigation in components for clinical environments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/multi-filter/departmentFilter.tsx` around lines 67 - 97,
The DropdownMenuItem currently only toggles expansion via the child Button
click; add an onKeyDown handler on DropdownMenuItem (next to the existing
onSelect) to listen for ArrowRight (to setExpanded(true)) and ArrowLeft (to
setExpanded(false)) and ensure the handler calls
e.preventDefault()/e.stopPropagation() as needed so it doesn't trigger the
onSelect toggle; keep the existing Button click behavior but remove reliance on
it for keyboard users, and ensure Enter/Space still trigger onOrgToggle as
before while Arrow keys only control expansion state via setExpanded and update
the ChevronRight rotation accordingly.

Comment on lines +175 to +184
} = useMultiFilterState(filterConfigs, onFilterUpdate, {
...qParams,
date:
qParams.date_after || qParams.date_before
? {
from: qParams.date_after ? new Date(qParams.date_after) : undefined,
to: qParams.date_before ? new Date(qParams.date_before) : undefined,
}
: undefined,
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

head -n 200 src/pages/Facility/services/inventory/ToDispatch.tsx | tail -n 40

Repository: ohcnetwork/care_fe

Length of output: 1157


🏁 Script executed:

sed -n '203,210p' src/pages/Facility/services/inventory/ToDispatch.tsx

Repository: ohcnetwork/care_fe

Length of output: 335


🏁 Script executed:

sed -n '315,355p' src/pages/Facility/services/inventory/ToDispatch.tsx

Repository: ohcnetwork/care_fe

Length of output: 1449


🏁 Script executed:

rg "dateTimeQueryString|parseISO" src/pages/Facility/services/inventory/ToDispatch.tsx -B 2 -A 2

Repository: ohcnetwork/care_fe

Length of output: 843


🏁 Script executed:

rg "dateTimeQueryString" src/ -l --type ts --type tsx

Repository: ohcnetwork/care_fe

Length of output: 89


🏁 Script executed:

rg "dateTimeQueryString" src/Utils/ -l

Repository: ohcnetwork/care_fe

Length of output: 80


🏁 Script executed:

sed -n '1,50p' src/Utils/utils.ts | head -n 50

Repository: ohcnetwork/care_fe

Length of output: 1380


🏁 Script executed:

rg "export.*dateTimeQueryString" src/Utils/ -A 10

Repository: ohcnetwork/care_fe

Length of output: 581


🏁 Script executed:

grep -n "function dateTimeQueryString\|const dateTimeQueryString\|export.*dateTimeQueryString" src/Utils/utils.ts

Repository: ohcnetwork/care_fe

Length of output: 140


🏁 Script executed:

rg "from \"date-fns\"" src/pages/Facility/services/inventory/ToDispatch.tsx

Repository: ohcnetwork/care_fe

Length of output: 44


🏁 Script executed:

rg "parseISO" src/ --type ts --type tsx

Repository: ohcnetwork/care_fe

Length of output: 89


🏁 Script executed:

sed -n '86,100p' src/Utils/utils.ts

Repository: ohcnetwork/care_fe

Length of output: 524


🏁 Script executed:

cat src/Utils/dayjs.ts

Repository: ohcnetwork/care_fe

Length of output: 434


🏁 Script executed:

rg "new Date\(" src/pages/Facility/services/inventory/ToDispatch.tsx -B 2 -A 2

Repository: ohcnetwork/care_fe

Length of output: 1251


🏁 Script executed:

rg "dayjs\(" src/Utils/utils.ts -A 1 | head -n 30

Repository: ohcnetwork/care_fe

Length of output: 614


🏁 Script executed:

rg "parseISO\|startOfDay\|endOfDay" src/Utils/ --type ts

Repository: ohcnetwork/care_fe

Length of output: 44


🏁 Script executed:

grep -n "import.*dayjs\|import.*date-fns" src/pages/Facility/services/inventory/ToDispatch.tsx

Repository: ohcnetwork/care_fe

Length of output: 44


🏁 Script executed:

rg "dayjs\(.*\).toDate\(\)" src/Utils/utils.ts -B 2 -A 2

Repository: ohcnetwork/care_fe

Length of output: 272


🏁 Script executed:

python3 << 'EOF'
# Test to understand the timezone parsing behavior
# In the dateTimeQueryString function, it does: dayjs(date).toDate()
# The issue is what happens when you pass new Date("2026-04-07") to dayjs

print("JavaScript new Date parsing behavior:")
print("new Date('2026-04-07') parses date string as UTC")
print("In negative UTC offset timezones (e.g., EST UTC-5), this becomes previous day")
print()
print("dayjs parsing behavior:")
print("dayjs('2026-04-07') should parse as local date by default")
print()
print("The fix suggestion uses parseISO from date-fns")
print("But parseISO is stricter and may have similar UTC semantics")
print()
print("Better approach: Use dayjs() which is already in the codebase")
print("Instead of: new Date(qParams.date_after)")
print("Use: dayjs(qParams.date_after).toDate()")
EOF

Repository: ohcnetwork/care_fe

Length of output: 561


🏁 Script executed:

rg "dateQueryString\|dateTimeQueryString" src/Utils/utils.ts -B 5 -A 15 | head -n 80

Repository: ohcnetwork/care_fe

Length of output: 44


🏁 Script executed:

head -n 1 src/pages/Facility/services/inventory/ToDispatch.tsx && grep -n "^import" src/pages/Facility/services/inventory/ToDispatch.tsx | head -n 20

Repository: ohcnetwork/care_fe

Length of output: 1386


🏁 Script executed:

rg "type DateLike\|interface DateLike" src/Utils/

Repository: ohcnetwork/care_fe

Length of output: 44


🏁 Script executed:

sed -n '70,95p' src/Utils/utils.ts

Repository: ohcnetwork/care_fe

Length of output: 717


🏁 Script executed:

python3 << 'EOF'
# Verify parseISO behavior with ISO strings
# parseISO("2026-04-07") will parse as UTC midnight and return a Date object
# This is the SAME behavior as new Date("2026-04-07")

# The correct solution is to use dayjs which is already imported in utils.ts
# dayjs("2026-04-07") intelligently handles date-only strings in local time

print("Issue Analysis:")
print("=" * 60)
print()
print("Current code: new Date(qParams.date_after)")
print("Problem: Parses '2026-04-07' as UTC → shifts date in negative offset zones")
print()
print("Review suggestion: parseISO(qParams.date_after)")
print("Issue: parseISO also parses as UTC for ISO strings")
print("       This will NOT fix the timezone problem!")
print()
print("Correct approach: dayjs(qParams.date_after).toDate()")
print("Why: dayjs intelligently parses date-only strings in local timezone")
print()
print("Note: dateTimeQueryString already uses dayjs internally!")
print("So just passing the string directly is cleaner than wrapping with new Date()")
EOF

Repository: ohcnetwork/care_fe

Length of output: 661


🏁 Script executed:

rg "export const dateQueryString" src/Utils/utils.ts -A 5

Repository: ohcnetwork/care_fe

Length of output: 287


🏁 Script executed:

python3 << 'EOF'
# Verify parseISO behavior
# According to date-fns documentation, parseISO parses ISO 8601 strings
# For a date-only string "2026-04-07", parseISO will:
# - Treat it as an ISO date string
# - Parse it as UTC (same as new Date!)

import subprocess
result = subprocess.run([
    'rg', 'import.*parseISO', 'src/', 
], capture_output=True, text=True)

print("Usage of parseISO in codebase:")
print(result.stdout if result.stdout else "No imports of parseISO found")
print()
print("Conclusion: parseISO is NOT used in the codebase")
print("date-fns is imported in utils.ts but used minimally")
print("The project primarily uses dayjs for date handling")
EOF

Repository: ohcnetwork/care_fe

Length of output: 434


🏁 Script executed:

rg "import.*from.*date-fns" src/ --max-count=10

Repository: ohcnetwork/care_fe

Length of output: 9253


🏁 Script executed:

rg "DateLike.*=" src/Utils/utils.ts -B 2

Repository: ohcnetwork/care_fe

Length of output: 628


🏁 Script executed:

python3 << 'EOF'
import json

# Document the timezone issue and proposed solutions
analysis = {
    "issue": "new Date('2026-04-07') in negative UTC zones shifts date backward",
    "current_code": "new Date(qParams.date_after)",
    "review_suggestion": "parseISO(qParams.date_after) from date-fns",
    "review_suggestion_flaw": "parseISO also parses as UTC - does NOT fix the issue",
    "available_options": {
        "option_1": {
            "code": "dayjs(qParams.date_after).toDate()",
            "description": "Uses existing dayjs which intelligently handles local dates",
            "already_in_codebase": True
        },
        "option_2": {
            "code": "dateTimeQueryString(qParams.date_after)",
            "description": "Pass string directly to dateTimeQueryString which uses dayjs",
            "already_in_codebase": True,
            "most_elegant": True
        },
        "option_3": {
            "code": "parseISO(qParams.date_after)",
            "description": "From date-fns, but has same UTC parsing issue",
            "fixes_issue": False
        }
    }
}

print(json.dumps(analysis, indent=2))
EOF

Repository: ohcnetwork/care_fe

Length of output: 968


🏁 Script executed:

sed -n '165,190p' src/pages/Facility/services/inventory/ToDispatch.tsx

Repository: ohcnetwork/care_fe

Length of output: 706


🏁 Script executed:

sed -n '1,20p' src/pages/Facility/services/inventory/ToDispatch.tsx

Repository: ohcnetwork/care_fe

Length of output: 797


🏁 Script executed:

rg "import dayjs\|from.*dayjs" src/pages/Facility/services/inventory/ToDispatch.tsx

Repository: ohcnetwork/care_fe

Length of output: 44


Replace new Date() with dayjs() for date-only query parameters.

new Date("2026-04-07") parses date strings as UTC, shifting the date backward by one day in negative-offset time zones. Both filter rehydration and API query bounds are affected. Use dayjs() instead, which intelligently handles local dates:

For filter rehydration (lines 175-184, 315-328):

- from: qParams.date_after ? new Date(qParams.date_after) : undefined,
+ from: qParams.date_after ? dayjs(qParams.date_after).toDate() : undefined,
- to: qParams.date_before ? new Date(qParams.date_before) : undefined,
+ to: qParams.date_before ? dayjs(qParams.date_before).toDate() : undefined,

For API query construction (lines 203-208, 347-352), pass the string directly to dateTimeQueryString which uses dayjs internally:

- date_after: qParams.date_after ? dateTimeQueryString(new Date(qParams.date_after)) : undefined,
+ date_after: qParams.date_after ? dateTimeQueryString(qParams.date_after) : undefined,
- date_before: qParams.date_before ? dateTimeQueryString(new Date(qParams.date_before), true) : undefined,
+ date_before: qParams.date_before ? dateTimeQueryString(qParams.date_before, true) : undefined,

Apply the same changes to created_date_* blocks.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Facility/services/inventory/ToDispatch.tsx` around lines 175 - 184,
The filter rehydration currently constructs Date objects via new Date(...)
(inside the useMultiFilterState call and the created_date_* blocks) which causes
UTC shifts; replace those with dayjs(...) to create local-date-aware values
(e.g., use dayjs(qParams.date_after) / dayjs(qParams.date_before) instead of new
Date(...)) and update any other rehydration sites for created_date_*
accordingly; for API query construction (where dateTimeQueryString is invoked in
ToDispatch), stop converting to Date and instead pass the original date string
(or dayjs instance string) into dateTimeQueryString so it can parse with dayjs
internally (update calls around dateTimeQueryString and any created_date_* query
builders to accept the string/dayjs input).

await page.goto(
`/facility/${facilityId}/settings/billing/discount_configuration`,
);
await page.waitForLoadState("networkidle");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider avoiding waitForLoadState("networkidle").

Using networkidle can be flaky because it waits for no network activity for 500ms, which may not reliably indicate the page is ready. Consider waiting for specific elements or using waitForLoadState("domcontentloaded") instead, or better yet, rely on Playwright's auto-waiting when interacting with elements.

♻️ Suggested alternative
-    await page.waitForLoadState("networkidle");
+    // Remove networkidle waits - Playwright's auto-wait on element interactions is preferred
+    // The subsequent expect/click calls will auto-wait for elements

Or if you need to ensure page stability:

await page.waitForLoadState("domcontentloaded");

Also applies to: 41-41, 55-55

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/facility/settings/billing/discount/discountCodeCreate.spec.ts` at line
16, Replace flaky uses of page.waitForLoadState("networkidle") in the tests with
a more reliable wait: either use page.waitForLoadState("domcontentloaded") or,
preferably, wait for specific elements/locators to be ready (e.g.,
page.waitForSelector or locator.waitFor for the button, form, or input you
interact with) or rely on Playwright's built-in auto-waiting when performing
actions; update each occurrence of page.waitForLoadState("networkidle") in
discountCodeCreate.spec.ts (and the other occurrences noted) to one of these
approaches so the test waits for a concrete element/state rather than network
idle.

Comment on lines +52 to +55

## Authentication

Use one of these storage states depending on the role needed:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Add language specifier to code block.

The code block at line 54 is missing a language specifier, which affects syntax highlighting and triggers a markdownlint warning.

📝 Fix markdown formatting
 ```typescript
 // Most tests use admin
 test.use({ storageState: "tests/.auth/user.json" });
-```
+```

+
 // Nurse-specific tests

Also add blank lines before/after code blocks at lines 16, 27, and 102 to satisfy markdownlint MD031.

🧰 Tools
🪛 markdownlint-cli2 (0.22.0)

[warning] 54-54: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 54-54: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/PLAYWRIGHT_GUIDE.md` around lines 52 - 55, Add a language specifier and
fix surrounding blank lines for the Playwright code block containing the line
test.use({ storageState: "tests/.auth/user.json" });: replace the plain code
fence with ```typescript before that block (so syntax highlighting applies) and
ensure there is a blank line before the opening fence and a blank line after the
closing fence; also add blank lines before/after the other code blocks
referenced near the comments "Most tests use admin", the "Nurse-specific tests"
block, and the block around line 102 to satisfy markdownlint MD031.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs review needs testing needs-triage question Further information is requested Type Changes Contains changes in typescript types

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Text overflow in location switcher