Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 42 additions & 11 deletions src/pages/Facility/queues/ManageQueueOngoingTab.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import PatientIdentifierFilter from "@/components/Patient/PatientIdentifierFilter";
import { useScheduleResourceFromPath } from "@/components/Schedule/useScheduleResource";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
Expand Down Expand Up @@ -56,7 +57,8 @@ export function ManageQueueOngoingTab({ facilityId, queueId }: Props) {
const { preferredServicePointCategories } = usePreferredServicePointCategory({
facilityId,
});
const [{ autoRefresh, search }, setQueryParams] = useQueryParams();
const [qParams, setQueryParams] = useQueryParams();
const { autoRefresh, search, patient_filter, patient_name } = qParams;
const { data: summary } = useQuery({
Comment on lines +61 to 62
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid storing patient names in URL query params (PHI exposure risk).

Line 99 persists patient_name in the URL. In a healthcare flow, this can leak PHI via history, logs, analytics, and shared links. Keep only patient_filter in query params; keep display name in local state.

Proposed fix
   const [qParams, setQueryParams] = useQueryParams();
-  const { autoRefresh, search, patient_filter, patient_name } = qParams;
+  const { autoRefresh, search, patient_filter } = qParams;
+  const [selectedPatientName, setSelectedPatientName] = useState("");

             <PatientIdentifierFilter
               onSelect={(patientId, patientName) => {
                 if (patientId && patientName) {
+                  setSelectedPatientName(patientName);
                   setQueryParams(
                     {
                       patient_filter: patientId,
-                      patient_name: patientName,
                     },
                     { overwrite: false, replace: true },
                   );
                 } else {
-                  delete qParams.patient_filter;
-                  delete qParams.patient_name;
-                  setQueryParams(qParams, { replace: true });
+                  setSelectedPatientName("");
+                  const { patient_filter: _removed, ...nextParams } = qParams;
+                  setQueryParams(nextParams, { replace: true });
                 }
               }}
               placeholder={t("filter_by_identifier")}
               className="w-full sm:w-auto rounded-md h-9 text-gray-500 shadow-sm"
               patientId={patient_filter}
-              patientName={patient_name}
+              patientName={selectedPatientName}
             />
As per coding guidelines, "Handle patient data with appropriate privacy considerations in healthcare page components."

Also applies to: 94-107, 112-113

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

In `@src/pages/Facility/queues/ManageQueueOngoingTab.tsx` around lines 61 - 62,
The component is persisting patient_name in qParams which risks exposing PHI;
update ManageQueueOngoingTab to stop reading/writing patient_name to the
URL—only keep patient_filter in qParams and move patient display name into
component/local state (e.g., useState) for rendering. Locate where qParams is
destructured (const { autoRefresh, search, patient_filter, patient_name } =
qParams) and remove patient_name from qParams usage, and change any setQParams
or URL update calls that include patient_name to only set patient_filter. Ensure
any UI that shows the patient's name reads from the new local state and that
local state is populated from the API response (summary or selection) instead of
the query string.

queryKey: ["token-queue-summary", facilityId, queueId],
queryFn: query(tokenQueueApi.summary, {
Expand All @@ -68,22 +70,50 @@ export function ManageQueueOngoingTab({ facilityId, queueId }: Props) {
return (
<div className="flex flex-col gap-4">
<div className="flex flex-col sm:flex-row justify-between items-start mt-2 gap-4">
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-2 w-full">
<Label className="text-gray-950 text-sm font-medium">
{t("search_patients")}
</Label>
<div className="relative w-64">
<SearchIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 size-4 text-gray-400" />
<Input
type="search"
placeholder={t("search_by_patient_name")}
value={search || ""}
onChange={(e) => setQueryParams({ search: e.target.value || "" })}
className="pl-10"
<div className="flex flex-col sm:flex-row gap-2">
<div className="relative">
<SearchIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 size-4 text-gray-400" />
<Input
type="search"
placeholder={t("search_by_patient_name")}
value={search || ""}
onChange={(e) =>
setQueryParams(
{ search: e.target.value || "" },
{ overwrite: false, replace: true },
)
}
className="pl-10 w-full sm:w-64 h-9"
/>
</div>
<PatientIdentifierFilter
onSelect={(patientId, patientName) => {
if (patientId && patientName) {
setQueryParams(
{
patient_filter: patientId,
patient_name: patientName,
},
{ overwrite: false, replace: true },
);
} else {
Comment on lines +93 to +103
delete qParams.patient_filter;
delete qParams.patient_name;
setQueryParams(qParams, { replace: true });
Comment on lines +104 to +106
}
}}
placeholder={t("filter_by_identifier")}
className="w-full sm:w-auto rounded-md h-9 text-gray-500 shadow-sm"
patientId={patient_filter}
patientName={patient_name}
/>
</div>
</div>
<div className="flex flex-col gap-2">
<div className="flex flex-col gap-2 w-full sm:items-end">
<Label className="text-gray-950 text-sm font-medium">
{t("service_points")}
</Label>
Expand All @@ -101,6 +131,7 @@ export function ManageQueueOngoingTab({ facilityId, queueId }: Props) {
sub_queue_is_null: true,
status: TokenStatus.CREATED,
patient_name: search || "",
patient_filter: patient_filter,
}}
emptyState={
<div className="flex flex-col gap-2 items-center justify-center bg-gray-100 rounded-lg py-10 border border-gray-100">
Expand Down
10 changes: 5 additions & 5 deletions src/pages/Facility/queues/ServicePointsDropDown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const ServicePointsDropDown = () => {
const [isOpen, setIsOpen] = useState(false);
const { assignedServicePointIds, allServicePoints, toggleServicePoint } =
useQueueServicePoints();
const defaultServicePoints = useBreakpoints({ default: 2, sm: 6 });
const defaultServicePoints = useBreakpoints({ default: 4, sm: 6 });
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fixed-width chips plus higher default count can overflow mobile layouts.

Line 21 increases visible chips to 4 by default, and Line 54 forces each chip to w-48. On narrow screens this can overflow and hide content/actions.

Proposed fix
-  const defaultServicePoints = useBreakpoints({ default: 4, sm: 6 });
+  const defaultServicePoints = useBreakpoints({ default: 2, sm: 4 });

-      <div className="flex w-full sm:w-auto gap-1 rounded-r-none border border-r-0 border-gray-300 rounded-l-md p-1 bg-white">
+      <div className="flex w-full sm:w-auto gap-1 rounded-r-none border border-r-0 border-gray-300 rounded-l-md p-1 bg-white overflow-x-auto">

-                    className="flex w-48 items-center justify-center gap-1 border border-gray-300 py-0.5 px-1.5 rounded-sm bg-gray-50 whitespace-nowrap"
+                    className="flex min-w-0 max-w-48 items-center justify-center gap-1 border border-gray-300 py-0.5 px-1.5 rounded-sm bg-gray-50"

Also applies to: 38-38, 54-57

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

In `@src/pages/Facility/queues/ServicePointsDropDown.tsx` at line 21, The chips
are fixed-width and the default visible count (defaultServicePoints via
useBreakpoints) is increased causing overflow on narrow screens; change the
defaultServicePoints fallback to a smaller value (e.g., 2) and remove the hard
`w-48` sizing on the chip elements in ServicePointsDropDown, replacing it with
responsive styling (e.g., flexible width, max-w/full, flex-shrink and
text-truncate or ellipsis) so chips wrap or shrink on mobile; update any other
occurrences around the component (the useBreakpoints call and the chip JSX that
currently uses `w-48`) to use the new responsive classes.


if (!allServicePoints) {
return (
Expand All @@ -35,7 +35,7 @@ export const ServicePointsDropDown = () => {

return (
<div className="flex">
<div className="flex gap-1 rounded-r-none border border-r-0 border-gray-300 rounded-l-md p-1.5 bg-white items-center justify-center">
<div className="flex w-full sm:w-auto gap-1 rounded-r-none border border-r-0 border-gray-300 rounded-l-md p-1 bg-white">
{assignedServicePointIds.length === 0 ? (
<span className="text-sm font-medium">
{t("assign_service_points")}
Expand All @@ -51,10 +51,10 @@ export const ServicePointsDropDown = () => {
return (
<div
key={subQueue.id}
className="flex items-center justify-center gap-1 border border-gray-300 py-0.5 px-1.5 rounded-sm bg-gray-50 whitespace-nowrap"
className="flex w-48 items-center justify-center gap-1 border border-gray-300 py-0.5 px-1.5 rounded-sm bg-gray-50 whitespace-nowrap"
>
<div className="bg-primary-200 border border-primary-500 w-2 h-2 rounded-full" />
<span className="text-sm text-gray-950 font-medium">
<span className="text-sm text-gray-950 font-medium truncate">
{subQueue.name}
</span>
</div>
Expand All @@ -75,7 +75,7 @@ export const ServicePointsDropDown = () => {
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="rounded-l-none w-10 h-11 border border-gray-300 bg-white"
className="rounded-l-none w-10 h-9 border border-gray-300 bg-white"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Loading state height no longer matches the final trigger button height.

Line 78 uses h-9, but the loading skeleton in this component still renders with h-11, causing a layout shift when data loads.

Proposed fix
-        <Skeleton className="h-11 w-40 rounded-r-none rounded-l-md" />
-        <Skeleton className="h-11 w-10 rounded-l-none rounded-r-md" />
+        <Skeleton className="h-9 w-40 rounded-r-none rounded-l-md" />
+        <Skeleton className="h-9 w-10 rounded-l-none rounded-r-md" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Facility/queues/ServicePointsDropDown.tsx` at line 78, The loading
skeleton height mismatches the final trigger button (trigger uses className
containing h-9 while the skeleton renders h-11), causing layout shift; open the
ServicePointsDropDown component and change the skeleton element's height class
from h-11 to h-9 so it matches the trigger button (also verify any other
skeleton/placeholder in ServicePointsDropDown uses h-9 and update them to match
the trigger's className).

>
<ChevronDownIcon />
</Button>
Expand Down
Loading