Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ const Medications = ({ medications }) => {
<td className="p-1.5 sm:p-3 text-gray-600 text-[10px] sm:text-sm">
{med.instructions || 'As directed'}
</td>
<td>
{med.quickmed === true && (
<a
href={`/quick-med/medicine/${encodeURIComponent(med.name || med.medicineName)}`}
target="_blank"
rel="noopener noreferrer"
className="quickmed-button"
>
See in QuickMed
</a>
)}
</td>
</tr>
))}
</tbody>
Expand Down
79 changes: 73 additions & 6 deletions client/src/pages/doctor/AppointmentDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
VideoCameraIcon,
XMarkIcon,
} from '@heroicons/react/24/outline';
import { useEffect, useState } from 'react';
import { useEffect, useState, useCallback, useRef } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import {
getAppointmentDetails,
Expand All @@ -25,11 +25,13 @@ import {
getAppointmentPrescription,
updatePrescription,
} from '../../service/prescriptionApiSevice';
import { getMedicineSuggestions } from '../../service/medicineApiService';

const AppointmentDetails = () => {
const { appointmentId } = useParams();
const navigate = useNavigate();

const [medicineSuggestions, setMedicineSuggestions] = useState([]);
const [showSuggestions, setShowSuggestions] = useState(false);
Comment on lines +33 to +34
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

Suggestions dropdown displays under all medication inputs simultaneously.

The shared showSuggestions state doesn't track which medication input is active. When typing in one input, the dropdown will appear under every medication entry in the form. Track the active input index to isolate the dropdown.

Add state to track the active medication index:

 const [medicineSuggestions, setMedicineSuggestions] = useState([]);
 const [showSuggestions, setShowSuggestions] = useState(false);
+const [activeMedicationIndex, setActiveMedicationIndex] = useState(null);

Then update handleMedicineNameChange to set the active index and conditionally render the dropdown only when index === activeMedicationIndex.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In client/src/pages/doctor/AppointmentDetails.jsx around lines 33-34 the shared
boolean showSuggestions causes the suggestions dropdown to appear under every
medication input; add a new state like activeMedicationIndex (initialized to
null) to track which medication entry is currently active, update
handleMedicineNameChange to setActiveMedicationIndex(index) when typing and to
setShowSuggestions(true), and change the dropdown render to only show when
showSuggestions && index === activeMedicationIndex; also clear
activeMedicationIndex (set to null) and hide suggestions on blur/selection to
prevent stale dropdowns.

const [appointment, setAppointment] = useState(null);
const [patientInfo, setPatientInfo] = useState(null);
const [prescription, setPrescription] = useState(null);
Expand All @@ -45,13 +47,43 @@ const AppointmentDetails = () => {
followUpDate: '',
});
const [savingPrescription, setSavingPrescription] = useState(false);

const useDebounce = (callback, delay) => {
const timeoutRef = useRef(null);

return useCallback(
(...args) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => callback(...args), delay);
},
[callback, delay]
);
};
Comment on lines +50 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.

🛠️ Refactor suggestion | 🟠 Major

Move useDebounce outside the component or extract to a separate module.

Defining a custom hook inside the component body recreates it on every render, defeating memoization benefits. Additionally, callback and delay in the dependency array can cause stale closure issues since they reference the outer component's scope.

Extract to a separate utility file (e.g., hooks/useDebounce.js):

import { useRef, useCallback } from 'react';

export const useDebounce = (callback, delay) => {
  const timeoutRef = useRef(null);

  const debouncedFn = useCallback(
    (...args) => {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
      timeoutRef.current = setTimeout(() => callback(...args), delay);
    },
    [callback, delay]
  );

  return debouncedFn;
};

Then import and use it in the component. Also consider using useCallback to stabilize the callback reference passed to useDebounce.

🤖 Prompt for AI Agents
In client/src/pages/doctor/AppointmentDetails.jsx around lines 50 to 62, the
custom hook useDebounce is defined inside the component which causes it to be
recreated on every render and can lead to stale closures; move this hook out of
the component into a separate module (e.g., client/src/hooks/useDebounce.js),
export it, and import it into AppointmentDetails.jsx; in the new module import
useRef and useCallback from React and implement the debounced function there,
and in the component ensure you pass a memoized callback (wrap the handler with
useCallback) and a stable delay value so the dependency array doesn’t trigger
unnecessary recreations.

useEffect(() => {
if (appointmentId) {
fetchAppointmentData();
}
}, [appointmentId]);

const fetchSuggestions = useDebounce(async (query) => {
if (query.length >= 2) {
try {
const suggestions = await getMedicineSuggestions(query);
setMedicineSuggestions(suggestions.suggestions);
setShowSuggestions(true);
} catch (err) {
console.error('Suggestion fetch failed', err);
}
} else {
setShowSuggestions(false);
setMedicineSuggestions([]);
}
}, 300);
// Update medicine name input onChange
const handleMedicineNameChange = (index, value) => {
updateMedication(index, 'name', value);
fetchSuggestions(value);
};
const fetchAppointmentData = async () => {
try {
setLoading(true);
Expand Down Expand Up @@ -639,10 +671,45 @@ const AppointmentDetails = () => {
type="text"
placeholder="Medicine name"
value={medication.name}
onChange={(e) => updateMedication(index, 'name', e.target.value)}
onChange={(e) => handleMedicineNameChange(index, e.target.value)}
className="w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500"
required
/>
{showSuggestions && medicineSuggestions.length > 0 && (
<div className="absolute z-10 w-full bg-white border border-gray-200 rounded-md shadow-lg mt-1 max-h-60 overflow-auto">
{medicineSuggestions.map((suggestion, sugIndex) => (
<button
key={sugIndex}
type="button"
onClick={() => {
updateMedication(index, 'name', suggestion);
updateMedication(index, 'quickmed', true);
setShowSuggestions(false);
setMedicineSuggestions([]);
}}
className="w-full text-left p-3 hover:bg-gray-50 border-b border-gray-100 last:border-b-0"
>
<div className="font-medium">{suggestion}</div>

<button
type="button"
onClick={(e) => {
e.stopPropagation();
window.open(
`/quick-med/medicine/${encodeURIComponent(suggestion)}`,
'_blank',
'noopener,noreferrer'
);
}}
className="px-2 py-1 text-xs bg-blue-600 text-white rounded hover:bg-blue-700 flex items-center space-x-1 whitespace-nowrap"
title={`View ${suggestion} in QuickMed`}
>
<span>See in QuickMed</span>
</button>
<div className="text-xs text-gray-500">QuickMed ✓</div>
</button>
Comment on lines +680 to +709
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 | 🔴 Critical

Invalid HTML: nested <button> elements.

A <button> (lines 693-707) is nested inside another <button> (lines 680-709). This violates HTML specification and causes unpredictable click behavior across browsers. The outer button's click handler will also fire when clicking the inner button despite stopPropagation().

Restructure to use a <div> as the container and separate clickable areas:

-<button
+<div
   key={sugIndex}
-  type="button"
-  onClick={() => {
-    updateMedication(index, 'name', suggestion);
-    updateMedication(index, 'quickmed', true);
-    setShowSuggestions(false);
-    setMedicineSuggestions([]);
-  }}
   className="w-full text-left p-3 hover:bg-gray-50 border-b border-gray-100 last:border-b-0"
 >
-  <div className="font-medium">{suggestion}</div>
+  <button
+    type="button"
+    onClick={() => {
+      updateMedication(index, 'name', suggestion);
+      updateMedication(index, 'quickmed', true);
+      setShowSuggestions(false);
+      setMedicineSuggestions([]);
+    }}
+    className="font-medium text-left w-full"
+  >
+    {suggestion}
+  </button>

   <button
     type="button"
     onClick={(e) => {
       e.stopPropagation();
       window.open(
         `/quick-med/medicine/${encodeURIComponent(suggestion)}`,
         '_blank',
         'noopener,noreferrer'
       );
     }}
     className="px-2 py-1 text-xs bg-blue-600 text-white rounded hover:bg-blue-700 flex items-center space-x-1 whitespace-nowrap"
     title={`View ${suggestion} in QuickMed`}
   >
     <span>See in QuickMed</span>
   </button>
   <div className="text-xs text-gray-500">QuickMed ✓</div>
-</button>
+</div>
🤖 Prompt for AI Agents
In client/src/pages/doctor/AppointmentDetails.jsx around lines 680 to 709 there
is an invalid nested <button> (outer button wraps an inner “See in QuickMed”
button) which causes unpredictable behavior; replace the outer <button> with a
non-button container (e.g., a <div> with the same classes) and keep the inner
element as a real <button>, or alternatively make the outer element a
role="button" <div> and forward keyboard handlers (onKeyDown for Enter/Space)
and tabIndex=0 to preserve accessibility; ensure the outer container still calls
updateMedication and closes suggestions on click while the inner real button
only opens the QuickMed link and stops propagation.

))}
</div>
)}
</div>
<div>
<input
Expand Down
1 change: 1 addition & 0 deletions server/models/Appointment/Prescription.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const prescriptionSchema = new mongoose.Schema({
frequency: { type: String, required: true },
duration: { type: String, required: true },
instructions: { type: String },
quickmed: { type: Boolean, default: false },
},
],
tests: [
Expand Down