-
Notifications
You must be signed in to change notification settings - Fork 432
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GSoC - Doctor Notes Enhancement #8016
base: develop
Are you sure you want to change the base?
GSoC - Doctor Notes Enhancement #8016
Conversation
@UdaySagar-Git is attempting to deploy a commit to the Open Healthcare Network Team on Vercel. A member of the Team first needs to authorize it. |
✅ Deploy Preview for care-egov-staging ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
Hi, This pr has been automatically marked as stale because it has not had any recent activity. It will be automatically closed if no further activity occurs for 7 more days. Thank you for your contributions. |
@rithviknishad can you please remove stale label |
👋 Hi, @UdaySagar-Git, This message is automatically generated by prince-chrismc/label-merge-conflicts-action so don't hesitate to report issues/improvements there. |
👋 Hi, @UdaySagar-Git, This message is automatically generated by prince-chrismc/label-merge-conflicts-action so don't hesitate to report issues/improvements there. |
[update] fixed, looks like it was issue with my local git config @rithviknishad @khavinshankar git isn't tracking file paths correctly , it is showing differntly in my local and in git even changing the location of files not changing path in git |
90a82d7
to
618a407
Compare
👋 Hi, @UdaySagar-Git, This message is automatically generated by prince-chrismc/label-merge-conflicts-action so don't hesitate to report issues/improvements there. |
👋 Hi, @UdaySagar-Git, This message is automatically generated by prince-chrismc/label-merge-conflicts-action so don't hesitate to report issues/improvements there. |
WalkthroughThe changes in this pull request introduce various enhancements across multiple components and files in the application. Key modifications include the addition of new API endpoints for patient notes and consultation file uploads, updates to localization files, and the introduction of new components for managing discussion notes and markdown previews. Additionally, several existing components have been refactored to improve functionality, such as implementing new props for handling different viewing modes and updating state management practices. Overall, these changes aim to enhance user experience and expand the application's capabilities. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant API
participant NotesService
participant NotificationService
User->>API: Request to add a patient note
API->>NotesService: Process note addition
NotesService-->>API: Return note ID
API-->>User: Return success response with note ID
User->>NotificationService: Notify user of new note
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 24
🧹 Outside diff range and nitpick comments (38)
src/common/hooks/useAuthUser.ts (1)
10-10
: Consider adding JSDoc documentation for refetchUser.Since this method is critical for user profile updates and file upload features, adding documentation would help other developers understand its purpose and usage patterns.
+ /** Refreshes the current user data from the backend. + * Used after profile updates, file uploads, or when user data needs synchronization. + * @returns A promise containing the updated user data or error details + */ refetchUser: () => Promise<RequestResult<UserModel>>;src/components/Facility/DoctorNoteReplyPreviewCard.tsx (2)
23-51
: Consider accessibility and structural improvements.While the layout works, there are opportunities for enhancement:
- Add ARIA labels for better screen reader support
- Consider extracting the user avatar into a separate component
- Enhance the tooltip implementation with proper ARIA attributes
Consider applying these improvements:
- <div className="mt-3 flex flex-col rounded-lg border border-gray-300 bg-gray-200/50 pt-2 text-gray-800"> + <div + className="mt-3 flex flex-col rounded-lg border border-gray-300 bg-gray-200/50 pt-2 text-gray-800" + role="article" + aria-label="Note reply preview" + > <div className="flex flex-col"> <div className="flex justify-between pl-3"> <div className="flex gap-2"> - <div className="mt-1 flex h-9 w-9 items-center justify-center rounded-full bg-primary text-lg font-semibold text-white"> + <div + className="mt-1 flex h-9 w-9 items-center justify-center rounded-full bg-primary text-lg font-semibold text-white" + role="img" + aria-label={`${parentNote.created_by_object?.first_name || "Unknown"}'s avatar`} + > {parentNote.created_by_object?.first_name[0]} </div>
64-65
: Improve content truncation UX.The current implementation truncates content at a fixed height without indicating if content is hidden or providing a way to view more.
Consider adding an expand/collapse mechanism:
- <div className="max-h-14 overflow-hidden pb-2 pl-14 text-sm text-gray-700"> + <div className="pb-2 pl-14 text-sm text-gray-700"> + <div className={`${isExpanded ? '' : 'max-h-14 overflow-hidden'}`}> <MarkdownPreview markdown={parentNote.note} /> + </div> + {!isExpanded && contentHeight > 56 && ( + <button + onClick={() => setIsExpanded(true)} + className="mt-1 text-primary hover:text-primary-dark" + > + Show more + </button> + )} </div>Note: This would require adding state management for
isExpanded
and measuring content height.src/components/Facility/PatientConsultationNotesList.tsx (2)
18-19
: LGTM! Consider adding JSDoc comments.The new interface properties are well-typed. Consider adding JSDoc comments to document the purpose of these props and their expected usage.
+ /** View mode for the notes list. Defaults to "default-view" */ mode?: "thread-view" | "default-view"; + /** Callback to update the currently viewed thread note */ setThreadViewNote?: (noteId: string) => void;
83-86
: Consider memoizing setThreadViewNote to prevent unnecessary re-renders.While the effect logic is correct, if
setThreadViewNote
is recreated frequently in the parent component, it could lead to unnecessary re-renders. Consider memoizing the callback in the parent component usinguseCallback
.// In parent component: const setThreadViewNote = useCallback((noteId: string) => { // implementation }, [/* dependencies */]);src/components/Facility/DoctorNote.tsx (2)
13-14
: LGTM! Consider adding JSDoc comments.The new props are well-typed and maintain backward compatibility. Consider adding JSDoc comments to document the purpose and usage of these props.
interface DoctorNoteProps { + /** Determines the display mode of notes. "thread-view" shows threaded discussions, "default-view" shows flat list */ mode?: "thread-view" | "default-view"; + /** Callback function to set the currently viewed thread note */ setThreadViewNote?: (noteId: string) => void;
30-30
: Consider using dynamic height for better responsiveness.The fixed height of
500px
might not work well across different screen sizes and devices. Consider using viewport-relative units or dynamic sizing.- className="mt-4 flex h-[500px] grow flex-col-reverse overflow-y-scroll bg-white sm:ml-2" + className="mt-4 flex h-[calc(100vh-200px)] min-h-[500px] grow flex-col-reverse overflow-y-scroll bg-white sm:ml-2"src/components/Common/MentionDropdown.tsx (2)
26-37
: Consider scroll position in dropdown positioning.The current position calculation doesn't account for scroll position, which could lead to misalignment. Also, the selected index should be reset when the filter changes.
const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 }); const [selectedIndex, setSelectedIndex] = useState<number | null>(null); useEffect(() => { if (editorRef.current) { + const scrollTop = window.pageYOffset || document.documentElement.scrollTop; + const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; setDropdownPosition({ - top: position.top, - left: position.left, + top: position.top + scrollTop, + left: position.left + scrollLeft, }); } }, [position, editorRef]); +useEffect(() => { + setSelectedIndex(null); +}, [filter]);
38-42
: Enhance user filtering flexibility.The current filtering only matches username starts-with. Consider implementing a more flexible search that includes partial matches and possibly searching across other user fields (e.g., first_name, last_name).
const filteredUsers = useMemo(() => { - return users.filter((user) => - user.username.toLowerCase().startsWith(filter.toLowerCase()), - ); + const searchTerm = filter.toLowerCase(); + return users.filter((user) => { + const username = user.username.toLowerCase(); + const firstName = user.first_name.toLowerCase(); + const lastName = user.last_name?.toLowerCase() || ''; + return username.includes(searchTerm) || + firstName.includes(searchTerm) || + lastName.includes(searchTerm); + }); }, [users, filter]);src/components/Patient/DiscussionNotesFiles.tsx (4)
29-38
: Review and clean up unused interface properties.The
StateInterface
contains properties likezoom
,rotation
, and zoom-related flags that don't appear to be used in this component. Consider removing unused properties or documenting their purpose if they're needed for future implementation.
40-51
: Enhance code maintainability with constants and utility functions.Consider the following improvements:
- Extract "UNARCHIVED" as a constant since it's used in multiple places.
- Move the offset calculation to a reusable utility function.
+const DEFAULT_TAB = "UNARCHIVED" as const; +const calculateOffset = (page: number, limit: number) => (page - 1) * limit; + export const DiscussionNotesFiles = (props: DiscussionNotesProps) => { const { t } = useTranslation(); const [currentPage, setCurrentPage] = useState(1); const [offset, setOffset] = useState(0); - const [tab, setTab] = useState("UNARCHIVED"); + const [tab, setTab] = useState(DEFAULT_TAB); const handlePagination = (page: number, limit: number) => { - const offset = (page - 1) * limit; + const offset = calculateOffset(page, limit); setCurrentPage(page); setOffset(offset); };
83-86
: Use translations for tab labels.The tab labels are hardcoded in English. Use the translation function for internationalization support.
const tabs = [ - { text: "Active Files", value: "UNARCHIVED" }, - { text: "Archived Files", value: "ARCHIVED" }, + { text: t("active_files"), value: "UNARCHIVED" }, + { text: t("archived_files"), value: "ARCHIVED" }, ];
117-121
: Simplify editable permission logic.The editable permission check could be simplified and moved to a separate function for better maintainability.
+const canEditFile = (file: FileUploadModel, user: AuthUser) => + file?.uploaded_by?.username === user.username || + ["DistrictAdmin", "StateAdmin"].includes(user.user_type); + <FileBlock file={item} key={item.id} fileManager={fileManager} associating_id={item.associating_id} - editable={ - item?.uploaded_by?.username === authUser.username || - authUser.user_type === "DistrictAdmin" || - authUser.user_type === "StateAdmin" - } + editable={canEditFile(item, authUser)} archivable={true} />src/Utils/useFileUpload.tsx (3)
Line range hint
142-186
: Enhance error handling and state management for better UX.Based on reported issues in the PR:
- The error messages could be more user-friendly
- The state should be properly restored after failed uploads
- Consider adding retry logic for failed uploads
Consider this improvement:
const uploadfile = async (data: CreateFileResponse, file: File) => { const url = data.signed_url; const internal_name = data.internal_name; const newFile = new File([file], `${internal_name}`); - return new Promise<void>((resolve, reject) => { + return new Promise<void>(async (resolve, reject) => { + const maxRetries = 3; + let attempts = 0; + + while (attempts < maxRetries) { + try { uploadFile( url, newFile, "PUT", { "Content-Type": file.type }, (xhr: XMLHttpRequest) => { if (xhr.status >= 200 && xhr.status < 300) { setProgress(null); Notification.Success({ - msg: t("file_uploaded"), + msg: t("file_uploaded_success", { fileName: file.name }), }); setError(null); onUpload && onUpload(data); resolve(); } else { + attempts++; Notification.Error({ - msg: t("file_error__dynamic", { statusText: xhr.statusText }), + msg: t("file_error__dynamic", { + fileName: file.name, + statusText: xhr.statusText, + attempt: attempts, + maxRetries + }), }); setProgress(null); - reject(); + if (attempts === maxRetries) reject(); } }, setProgress as any, () => { + attempts++; Notification.Error({ - msg: t("file_error__network"), + msg: t("file_error__network", { + fileName: file.name, + attempt: attempts, + maxRetries + }), }); setProgress(null); - reject(); + if (attempts === maxRetries) reject(); }, ); + break; // Exit loop if upload succeeds + } catch (error) { + if (attempts === maxRetries) throw error; + } + // Add exponential backoff + await new Promise(r => setTimeout(r, Math.pow(2, attempts) * 1000)); + } }); };
Line range hint
108-141
: Enhance security measures for medical file uploads.Given this handles medical data:
- The 100MB (
10e7
) file size limit might be too permissive- Consider adding virus scanning integration
- Implement strict content-type verification
Consider these security enhancements:
const validateFileUpload = () => { if (files.length === 0) { setError(t("file_error__choose_file")); return false; } for (const file of files) { const filenameLength = file.name.trim().length; if (filenameLength === 0) { setError(t("file_error__file_name")); return false; } - if (file.size > 10e7) { + // Define size limits per file type + const maxSizes = { + 'image': 5 * 1024 * 1024, // 5MB for images + 'audio': 25 * 1024 * 1024, // 25MB for audio + 'document': 10 * 1024 * 1024 // 10MB for documents + }; + const maxSize = maxSizes[type] || 10 * 1024 * 1024; + if (file.size > maxSize) { setError(t("file_error__file_size")); return false; } + // Verify content type matches extension + const declaredType = file.type; + const extension = file.name.split(".").pop()?.toLowerCase(); + const validTypes = { + 'image': /^image\/(jpeg|png|gif|bmp|webp)$/, + 'audio': /^audio\/(mpeg|wav|ogg)$/, + 'document': /^application\/(pdf|msword|vnd.openxmlformats-officedocument.*)$/ + }; + if (!declaredType.match(validTypes[type])) { + setError(t("file_error__invalid_content_type")); + return false; + }
Line range hint
187-229
: Improve user experience with better feedback and preview handling.Based on PR feedback:
- Add file preview capabilities
- Improve progress feedback
- Handle network interruptions gracefully
Consider adding a preview component:
type PreviewProps = { file: File; onRemove: () => void; progress: number | null; }; const FilePreview: React.FC<PreviewProps> = ({ file, onRemove, progress }) => { const isImage = ExtImage.includes(file.name.split('.').pop()?.toLowerCase() || ''); return ( <div className="file-preview"> {isImage && ( <img src={URL.createObjectURL(file)} alt={file.name} className="preview-thumbnail" /> )} <div className="preview-info"> <span>{file.name}</span> {progress !== null && ( <progress value={progress} max={100} /> )} </div> <button onClick={onRemove}>Remove</button> </div> ); };Then expose it through the hook:
return { progress, error, validateFiles: validateFileUpload, handleCameraCapture: () => setCameraModalOpen(true), handleAudioCapture: () => setAudioModalOpen(true), handleFileUpload: handleUpload, Dialogues, Input, fileNames: uploadFileNames, files: files, + FilePreview, // Add the preview component setFileNames: setUploadFileNames, setFileName: (name: string, index = 0) => {
src/components/Common/FilePreviewDialog.tsx (2)
Line range hint
153-160
: Enhance iframe security configuration.The current iframe implementation has security concerns:
- Empty sandbox attribute is too restrictive and might break legitimate previews.
- Direct URL usage without validation could pose security risks.
Consider these security enhancements:
<iframe - sandbox="" + sandbox="allow-scripts allow-same-origin" title="Source Files" - src={fileUrl} + src={validateFileUrl(fileUrl)} className="h-[75vh] w-full" />Add a URL validation helper:
const validateFileUrl = (url: string): string => { try { const validatedUrl = new URL(url); // Ensure URL is from trusted domains if (!validatedUrl.origin.endsWith('your-domain.com')) { throw new Error('Invalid file URL'); } return url; } catch { console.error('Invalid file URL'); return ''; } };
Line range hint
161-167
: Enhance accessibility and user experience.Consider the following improvements for better accessibility and user experience:
- Add more descriptive error messages for unsupported file types.
- Improve screen reader support with ARIA labels.
- Add keyboard shortcuts for common actions.
Here's a suggested implementation:
<div className="flex h-full w-full flex-col items-center justify-center"> <CareIcon icon="l-file" - className="mb-4 text-5xl text-secondary-600" + className="mb-4 text-5xl text-secondary-600" + aria-hidden="true" /> - {t("file_preview_not_supported")} + <p role="alert"> + {t("file_preview_not_supported_description", { + fileType: file_state.extension.toUpperCase(), + supportedTypes: previewExtensions.join(', ').toUpperCase() + })} + </p> </div>Also consider adding keyboard shortcuts:
useEffect(() => { const handleKeyPress = (e: KeyboardEvent) => { if (e.key === '+' && e.ctrlKey) handleZoomIn(); if (e.key === '-' && e.ctrlKey) handleZoomOut(); if (e.key === 'r' && e.ctrlKey) handleRotate(90); }; window.addEventListener('keydown', handleKeyPress); return () => window.removeEventListener('keydown', handleKeyPress); }, []);src/components/Facility/PatientNotesSlideover.tsx (2)
12-16
: Consider removing .js extensions from importsTypeScript/webpack typically handles module resolution without requiring explicit .js extensions. These extensions might cause issues with some bundlers and are generally unnecessary in TypeScript projects.
-import { PATIENT_NOTES_THREADS } from "@/common/constants.js"; -import DoctorNoteReplyPreviewCard from "./DoctorNoteReplyPreviewCard.js"; -import useNotificationSubscriptionState from "@/common/hooks/useNotificationSubscriptionState.js"; -import AuthorizedChild from "../../CAREUI/misc/AuthorizedChild.js"; +import { PATIENT_NOTES_THREADS } from "@/common/constants"; +import DoctorNoteReplyPreviewCard from "./DoctorNoteReplyPreviewCard"; +import useNotificationSubscriptionState from "@/common/hooks/useNotificationSubscriptionState"; +import AuthorizedChild from "../../CAREUI/misc/AuthorizedChild";
Line range hint
79-95
: Consider enhancing error handlingThe function implementation looks good overall, but consider adding explicit error handling for failed requests.
const onAddNote = async () => { if (!/\S+/.test(noteField)) { Notification.Error({ msg: "Note Should Contain At Least 1 Character", }); return; } const { res, data } = await request(routes.addPatientNote, { pathParams: { patientId: patientId }, body: { note: noteField, consultation: consultationId, thread, reply_to: reply_to?.id, }, }); + if (!res?.ok) { + Notification.Error({ + msg: "Failed to add note. Please try again.", + }); + return; + } if (res?.status === 201) { Notification.Success({ msg: "Note added successfully" }); setNoteField(""); setState({ ...state, cPage: 1 }); setReload(true); setReplyTo(undefined); } return data?.id; };src/components/Notifications/NotificationsList.tsx (1)
76-78
: Consider adding data validation for required properties.The URL construction looks good, but consider adding validation for required properties (facility, patient, consultation) to prevent undefined values in the URL.
case "PATIENT_NOTE_ADDED": + if (!data.facility || !data.patient || !data.consultation) { + console.warn('Missing required properties for PATIENT_NOTE_ADDED URL'); + return '#'; + } return `/facility/${data.facility}/patient/${data.patient}/consultation/${data.consultation}/notes`; case "PATIENT_NOTE_MENTIONED": + if (!data.facility || !data.patient || !data.consultation) { + console.warn('Missing required properties for PATIENT_NOTE_MENTIONED URL'); + return '#'; + } return `/facility/${data.facility}/patient/${data.patient}/consultation/${data.consultation}/notes`;src/components/Users/UserProfile.tsx (5)
Line range hint
13-14
: Improve date validation logic and error message.The current date validation has two issues:
- The age requirement (17 years) should be configurable rather than hardcoded
- The error message doesn't clearly communicate the age requirement to users
Consider this improvement:
-if (!dayjs(states.form[field]).isValid() || - dayjs(states.form[field]).isAfter(dayjs().subtract(17, "year"))) { - errors[field] = "Enter a valid date of birth"; +const MIN_AGE_REQUIREMENT = 17; // Move to constants +if (!dayjs(states.form[field]).isValid() || + dayjs(states.form[field]).isAfter(dayjs().subtract(MIN_AGE_REQUIREMENT, "year"))) { + errors[field] = `You must be at least ${MIN_AGE_REQUIREMENT} years old`;
Line range hint
201-211
: Extract password validation logic into a reusable function.The password validation logic is duplicated between the UI validation rules and the
validateNewPassword
function. This could lead to inconsistencies if validation rules change.Consider extracting the validation into a shared utility:
// passwordUtils.ts export const PASSWORD_RULES = { minLength: 8, requireNumbers: true, requireMixedCase: true }; export function validatePassword(password: string): { isValid: boolean; errors: string[] } { const errors: string[] = []; if (password.length < PASSWORD_RULES.minLength) { errors.push(`Password must be at least ${PASSWORD_RULES.minLength} characters`); } if (!/\d/.test(password)) { errors.push("Password must contain at least one number"); } if (password === password.toLowerCase() || password === password.toUpperCase()) { errors.push("Password must contain both uppercase and lowercase letters"); } return { isValid: errors.length === 0, errors }; }
Line range hint
321-368
: Add loading state and prevent concurrent submissions.The form submission handler doesn't prevent multiple concurrent submissions or provide visual feedback during submission.
Consider these improvements:
+const [isSubmitting, setIsSubmitting] = useState(false); const handleSubmit = async (e: FormEvent) => { e.preventDefault(); + if (isSubmitting) return; + const validForm = validateForm(); if (validForm) { + setIsSubmitting(true); try { const data = { // ... existing data preparation }; const { res } = await request(routes.partialUpdateUser, { pathParams: { username: authUser.username }, body: data, }); if (res?.ok) { Notification.Success({ msg: "Details updated successfully", }); await refetchUserData(); setShowEdit(false); } + } catch (error) { + Notification.Error({ + msg: "Failed to update profile. Please try again.", + }); + } finally { + setIsSubmitting(false); + } } };Then update the submit button:
-<Submit onClick={handleSubmit} label={t("update")} disabled={!dirty} /> +<Submit + onClick={handleSubmit} + label={isSubmitting ? t("updating...") : t("update")} + disabled={!dirty || isSubmitting} +/>
Line range hint
481-507
: Modernize file upload implementation.The current implementation uses XMLHttpRequest directly and could benefit from modern approaches and better error handling.
Consider using the Fetch API with proper error handling:
const handleAvatarUpload = async (file: File, onError: () => void) => { const formData = new FormData(); formData.append("profile_picture", file); try { const response = await fetch( `${careConfig.apiUrl}/api/v1/users/${authUser.username}/profile_picture/`, { method: 'POST', headers: { 'Authorization': `Bearer ${localStorage.getItem(LocalStorageKeys.accessToken)}` }, body: formData } ); if (!response.ok) { throw new Error(`Upload failed: ${response.statusText}`); } await sleep(1000); await refetchUser(); Notification.Success({ msg: "Profile picture updated." }); setEditAvatar(false); } catch (error) { console.error('Upload failed:', error); onError(); Notification.Error({ msg: "Failed to upload profile picture. Please try again." }); } };
Line range hint
590-650
: Enhance form accessibility.The form lacks proper ARIA attributes and keyboard navigation support.
Consider these accessibility improvements:
<form + role="form" + aria-label="User Profile Form" action="#" method="POST" > <div className="shadow sm:rounded-md"> <div className="bg-white px-4 pt-5"> <div + role="group" + aria-labelledby="personal-info-heading" className="grid grid-cols-6 gap-4" > + <h2 + id="personal-info-heading" + className="sr-only" + > + Personal Information + </h2> {/* Existing form fields */} </div> </div> <div className="bg-secondary-50 px-4 py-3 text-right sm:px-6"> <Submit onClick={handleSubmit} label={t("update")} disabled={!dirty} + aria-label="Update profile information" /> </div> </div> </form>src/Redux/api.tsx (1)
1167-1173
: Consider adding a create endpoint for consultation file uploads.While the list endpoint is well-implemented, consider adding a corresponding create endpoint to handle file uploads specifically for consultations. This would provide a more complete API surface for the consultation files feature.
Example addition:
// Consultation FileUploads listConsultationFileUploads: { path: "/api/v1/consultation/{consultation_external_id}/files/", method: "GET", TRes: Type<PaginatedResponse<FileUploadModel>>(), }, + createConsultationFileUpload: { + path: "/api/v1/consultation/{consultation_external_id}/files/", + method: "POST", + TBody: Type<CreateFileRequest>(), + TRes: Type<CreateFileResponse>(), + },src/components/Common/MarkdownPreview.tsx (2)
6-9
: ExtendCustomLinkProps
to Include Standard Anchor AttributesCurrently,
CustomLinkProps
only includesclassName
anddata-username
. To ensure that all standard anchor tag properties are available in theCustomLink
component, consider extendingCustomLinkProps
fromReact.ComponentProps<"a">
.Apply this diff to update the interface:
-interface CustomLinkProps { - className?: string; - "data-username"?: string; -} +interface CustomLinkProps extends React.ComponentProps<"a"> { + "data-username"?: string; +}
38-45
: Improve Username Matching in RegexThe current regex
/@(\w+)/g
may not match all valid usernames if they include characters like dots, dashes, or underscores. If usernames can contain such characters, consider updating the regex to accommodate them.For example:
-.replace(/@(\w+)/g, (_, username) => { +.replace(/@([a-zA-Z0-9._-]+)/g, (_, username) => {src/components/Facility/PatientNotesDetailedView.tsx (1)
112-119
: Enhance accessibility by adding aria-labels to buttons.To improve accessibility, consider adding
aria-label
attributes to the close button for screen readers.Apply this diff:
<button onClick={() => setThreadViewNote?.("")} className="btn btn-default" + aria-label="Close detailed view" > <CareIcon icon="l-times" className="mr-1 text-lg" /> Close </button>src/components/Facility/ConsultationDoctorNotes/index.tsx (3)
115-132
: Consider extracting theTabs
component into a separate file.The
Tabs
component is a reusable UI component that could be extracted into a separate file for better modularity and maintainability. This would also make it easier to reuse the component in other parts of the application.
Line range hint
133-193
: Improve the layout and responsiveness of the component.The changes introduce a new layout structure using a combination of flexbox and conditional rendering based on the
threadViewNote
state. While this enhances the user interface, there are a few areas that could be improved:
- Consider using CSS grid instead of flexbox for a more responsive layout that adapts to different screen sizes.
- Ensure that the component is fully responsive and provides a good user experience on smaller screens, such as mobile devices.
- Use semantic HTML elements, such as
<section>
or<article>
, to improve the structure and accessibility of the component.
Based on the search results, I can now provide a final response regarding the error handling in the
onAddNote
function.Error handling needs improvement in the
onAddNote
function.The current implementation has basic validation for empty notes but lacks proper error handling for the API request. The function should:
- Handle network errors and failed requests
- Show appropriate error notifications to users
- Maintain consistent state in case of failures
Here's what's missing:
- No try-catch block around the
request
call- No error handling for non-201 status codes
- No state cleanup in case of failures
🔗 Analysis chain
Line range hint
66-86
: Verify error handling for theonAddNote
function.The
onAddNote
function now returns theid
of the added note, which is a useful enhancement for further processing. However, it's important to ensure proper error handling in case the request fails or returns an unexpected status code.To verify the error handling, run the following script:
Let me try a different search pattern to locate the
onAddNote
function implementation.
Let me check how the
request
function handles errors to complete the verification.
Let me try a different search pattern to find error handling patterns.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify error handling in the `onAddNote` function. # Test: Search for the `onAddNote` function. # Expect: The function should have a catch block or error handling logic. ast-grep --pattern $'onAddNote = async () => { $$$ }'Length of output: 484
Script:
#!/bin/bash # Search for the onAddNote function implementation ast-grep --pattern $'const onAddNote = async $_$ => { $$$ }' # Also search with ripgrep to ensure we don't miss any implementation rg "onAddNote\s*=\s*async" -A 20Length of output: 6492
Script:
#!/bin/bash # Search for the request function implementation and error handling rg "request\s*=.*=>" -A 10 # Also search for any try-catch blocks around request calls rg "try\s*{.*request" -A 10Length of output: 164
Script:
#!/bin/bash # Search for try-catch blocks with request rg "try.*\{" -A 15 --multiline # Search for the request function definition rg "export.*request" -A 10Length of output: 85309
src/components/Facility/PatientNoteCard.tsx (2)
205-205
: Simplify function call with optional chainingYou can simplify the function call by using optional chaining.
Apply this diff to simplify the function call:
-setReplyTo && setReplyTo(note); +setReplyTo?.(note);🧰 Tools
🪛 Biome
[error] 205-205: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
173-173
: Safely access the first character of the first nameTo prevent potential errors if
first_name
is undefined or an empty string, consider safely accessing the first character using optional chaining and a fallback value.Apply this diff to enhance safety:
-{note.created_by_object?.first_name[0]} +{note.created_by_object?.first_name?.[0] || ''}src/components/Common/RichTextEditor.tsx (3)
29-35
: Consider renaming the 'onChange' prop for clarityThe prop
onChange
is being aliased tosetMarkdown
, which may cause confusion sinceonChange
is commonly associated with event handlers. To improve clarity, consider either:
- Renaming the prop to
setMarkdown
in the parent component, reflecting its purpose as a state setter.- Keeping the original prop name
onChange
and using it consistently throughout the component.This will enhance readability and maintain consistency in naming conventions.
402-467
: Enhance accessibility by adding 'aria-label' attributes to toolbar buttonsTo improve accessibility for users utilizing assistive technologies:
- Add descriptive
aria-label
attributes to each toolbar button to convey their function.- Ensure the labels are clear and concise (e.g.,
aria-label="Bold"
for the bold button).This will make the editor more inclusive and user-friendly.
205-293
: Refactor list handling functions to reduce code duplicationThe functions
handleOrderedList
andhandleUnorderedList
contain similar logic for manipulating list formatting. To improve maintainability:
- Abstract the common functionality into a helper function that accepts parameters to distinguish between ordered and unordered lists.
- Reduce repeated code, which simplifies future updates and decreases the potential for bugs.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (22)
src/Locale/en.json
(1 hunks)src/Redux/api.tsx
(3 hunks)src/Utils/useFileUpload.tsx
(1 hunks)src/common/constants.tsx
(1 hunks)src/common/hooks/useAuthUser.ts
(1 hunks)src/components/Common/FilePreviewDialog.tsx
(1 hunks)src/components/Common/MarkdownPreview.tsx
(1 hunks)src/components/Common/MentionDropdown.tsx
(1 hunks)src/components/Common/RichTextEditor.tsx
(1 hunks)src/components/Facility/ConsultationDetails/index.tsx
(2 hunks)src/components/Facility/ConsultationDoctorNotes/index.tsx
(7 hunks)src/components/Facility/DoctorNote.tsx
(2 hunks)src/components/Facility/DoctorNoteReplyPreviewCard.tsx
(2 hunks)src/components/Facility/PatientConsultationNotesList.tsx
(4 hunks)src/components/Facility/PatientNoteCard.tsx
(5 hunks)src/components/Facility/PatientNotesDetailedView.tsx
(1 hunks)src/components/Facility/PatientNotesSlideover.tsx
(6 hunks)src/components/Facility/models.tsx
(3 hunks)src/components/Notifications/NotificationsList.tsx
(1 hunks)src/components/Patient/DiscussionNotesFiles.tsx
(1 hunks)src/components/Patient/PatientNotes.tsx
(5 hunks)src/components/Users/UserProfile.tsx
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/Locale/en.json
🧰 Additional context used
🪛 Biome
src/components/Facility/PatientNoteCard.tsx
[error] 205-205: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (36)
src/common/hooks/useAuthUser.ts (1)
10-10
: LGTM! The refetchUser signature is well-typed.
The Promise<RequestResult> return type properly captures the asynchronous nature of user data refresh and aligns well with TypeScript best practices.
src/components/Facility/DoctorNoteReplyPreviewCard.tsx (2)
Line range hint 1-17
: LGTM! Well-structured imports and interface definition.
The component's interface is clearly defined with proper TypeScript types, and the imports are well-organized.
19-21
: LGTM! Good use of early return pattern.
The early return pattern improves code readability and provides a clean fallback to children when parentNote is undefined.
src/components/Facility/PatientConsultationNotesList.tsx (2)
33-34
: LGTM!
The props destructuring is correct, with appropriate default values.
116-117
: Verify DoctorNote component interface.
Please ensure that the DoctorNote component's interface has been updated to accept these new props.
✅ Verification successful
Props are properly typed and used in DoctorNote component
The verification shows that:
DoctorNoteProps
interface correctly includes both props:mode?: "thread-view" | "default-view"
setThreadViewNote?: (noteId: string) => void
- The props are properly used in the component's logic for conditional rendering and event handling
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check if DoctorNote component properly types these props
# Test: Search for DoctorNote interface/props type definition
ast-grep --pattern 'interface DoctorNoteProps {
$$$
}'
# Test: Search for mode and setThreadViewNote usage in DoctorNote
rg -A 5 'mode.*thread-view|setThreadViewNote' src/components/Facility/DoctorNote.tsx
Length of output: 1551
src/components/Facility/DoctorNote.tsx (3)
18-26
: LGTM!
Clean and consistent props destructuring.
42-42
: LGTM!
Appropriate styling for a chat-like interface with inverse scrolling.
Line range hint 1-91
: Verify integration with all PR features.
While the component handles the reply feature well, please ensure it properly integrates with other features mentioned in the PR objectives:
- User profile images
- Markdown support
- File upload options
- User tagging mechanism
✅ Verification successful
All PR features are properly integrated through PatientNoteCard component
Based on the verification results, the DoctorNote component correctly integrates with all required features through its child component PatientNoteCard:
- Markdown support: Implemented via
<MarkdownPreview>
component - File uploads: Handled through
files
prop and FilePreviewDialog - User tagging: Supported via
mentioned_users
prop in MarkdownPreview - Profile images: Basic avatar support shown through user initials in a circular div
- Reply feature: Implemented through
allowReply
andsetReplyTo
props
The component architecture properly delegates these features to PatientNoteCard, which contains the necessary implementations for all required functionality.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify integration with other features
# Test 1: Check if PatientNoteCard component handles markdown and file previews
echo "Checking PatientNoteCard implementation..."
ast-grep --pattern 'interface PatientNoteCardProps {
$$$
}'
# Test 2: Check for user tagging related props
echo "Checking user tagging integration..."
rg -l "userMentions|taggedUsers|onMention" --type typescript
# Test 3: Check for file upload related components
echo "Checking file upload integration..."
rg -l "FileUpload|uploadFile|handleFileUpload" --type typescript
Length of output: 522
Script:
#!/bin/bash
# Let's try a different approach with correct file extensions and more specific patterns
# Check PatientNoteCard implementation
echo "Checking PatientNoteCard.tsx for feature support..."
rg -A 10 "interface PatientNoteCardProps" --type ts --type tsx
# Check for markdown-related imports and components
echo "Checking markdown support..."
rg -l "markdown|Markdown|ReactMarkdown" --type ts --type tsx
# Check for file upload and attachments
echo "Checking file attachments..."
rg -l "attachment|Attachment|upload" --type ts --type tsx
# Check for user mentions/tagging
echo "Checking user mentions..."
rg -l "@mention|mention|tagged" --type ts --type tsx
# Check for avatar/profile image
echo "Checking profile images..."
rg -l "avatar|Avatar|profile.*image" --type ts --type tsx
Length of output: 813
Script:
#!/bin/bash
# Let's try without specifying file types since ripgrep seems to have issues with tsx/ts types
# Check PatientNoteCard implementation
echo "Checking PatientNoteCard for feature support..."
rg -A 10 "interface PatientNoteCardProps"
# Check for markdown-related imports and components
echo "Checking markdown support..."
rg -l "markdown|Markdown|ReactMarkdown"
# Check for file upload and attachments
echo "Checking file attachments..."
rg -l "attachment|Attachment|upload"
# Check for user mentions/tagging
echo "Checking user mentions..."
rg -l "@mention|mention|tagged"
# Check for avatar/profile image
echo "Checking profile images..."
rg -l "avatar|Avatar|profile.*image"
# Also check the specific PatientNoteCard file content
echo "Checking PatientNoteCard content..."
fd -g "*PatientNoteCard.tsx" -x cat {}
Length of output: 16716
src/components/Common/MentionDropdown.tsx (2)
1-11
: LGTM! Well-structured interface and imports.
The props interface is well-defined with clear types, and all imports are necessary for the component's functionality.
1-113
: Verify integration with markdown editor.
Let's ensure this component is properly integrated with the markdown editor component.
✅ Verification successful
Integration with markdown editor is properly implemented
The MentionsDropdown component is correctly integrated with RichTextEditor:
- All required props are properly passed:
onSelect
,position
,editorRef
, andfilter
- Mentions functionality is properly handled with cursor positioning and text insertion
- Component is rendered conditionally based on
showMentions
state - Keyboard navigation and event handling are implemented correctly
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify integration between MentionsDropdown and markdown editor
# Find markdown editor files that might use MentionsDropdown
echo "Searching for markdown editor integration..."
rg -l "MentionsDropdown" --type ts --type tsx
# Check for proper props passing
echo -e "\nChecking proper props usage..."
ast-grep --pattern 'import { $_ } from "$_/MentionsDropdown"
$$$
<MentionsDropdown
$$$
/>'
Length of output: 344
Script:
#!/bin/bash
# Let's try a different approach to find the integration
# Search for files containing MentionsDropdown with proper file extensions
echo "Searching for MentionsDropdown usage..."
rg -l "MentionsDropdown" --type-add 'tsx:*.tsx' --type-add 'ts:*.ts' --type tsx --type ts
# Look for markdown editor related files
echo -e "\nSearching for markdown editor files..."
rg -l "markdown|editor" --type-add 'tsx:*.tsx' --type-add 'ts:*.ts' --type tsx --type ts
# Get content of files that might show the integration
echo -e "\nChecking content of potential integration files..."
rg -A 5 -B 5 "MentionsDropdown" --type-add 'tsx:*.tsx' --type-add 'ts:*.ts' --type tsx --type ts
Length of output: 3903
Script:
#!/bin/bash
# Let's examine the RichTextEditor integration more closely
# Get the full content of RichTextEditor to verify proper integration
echo "Checking RichTextEditor implementation..."
cat src/components/Common/RichTextEditor.tsx
# Look for any related test files
echo -e "\nSearching for related test files..."
fd -e test.tsx -e test.ts -e spec.tsx -e spec.ts RichTextEditor
Length of output: 23959
src/components/Patient/DiscussionNotesFiles.tsx (1)
20-27
: Consider making critical ModalDetails properties required.
The ModalDetails
interface has all optional properties. If certain properties are always needed when using this interface (e.g., id
, name
), consider making them required by removing the ?
optional modifier.
src/components/Patient/PatientNotes.tsx (4)
16-16
: LGTM! Type safety and i18n improvements.
Good improvements:
- Proper typing for props instead of using
any
- Integration of i18n support for better internationalization
Also applies to: 20-21
26-26
: LGTM! Proper translation hook setup.
The translation hook is correctly initialized and integrated with the component.
Line range hint 1-166
: Verify integration with other PR features.
This component should integrate with several features mentioned in the PR objectives:
- File upload options
- User tagging mechanism
- Profile image support
#!/bin/bash
# Check for file upload integration
echo "Checking for file upload components..."
ast-grep --pattern 'import $_ from $file' | rg 'upload|file'
# Check for user tagging integration
echo "Checking for user tagging components..."
ast-grep --pattern 'import $_ from $file' | rg 'tag|mention|user'
# Check for profile image integration
echo "Checking for profile image components..."
ast-grep --pattern 'import $_ from $file' | rg 'profile|avatar|image'
112-113
: Verify mobile responsiveness of the notes container.
The styling changes look good, but given the PR feedback about mobile view issues:
✅ Verification successful
Mobile responsiveness implementation looks appropriate
The component implements responsive design patterns correctly:
- Uses Tailwind's responsive prefixes (
sm:
) to adjust margins and padding for different screen sizes - Has proper flex layout with
h-screen
for full viewport height - Container adapts with smaller margins and padding on mobile (
mx-3 my-2 p-2
) and larger ones on desktop (sm:mx-10 sm:my-5 sm:p-5
)
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for responsive design patterns in the codebase
echo "Checking for responsive design patterns..."
rg -l 'sm:|md:|lg:|xl:' src/components/Patient/
# Search for mobile-specific styles or components
echo "Checking for mobile-specific implementations..."
rg -l 'mobile|responsive|screen' src/components/Patient/
Length of output: 469
Script:
#!/bin/bash
# Let's try a more specific search for responsive patterns in PatientNotes.tsx
echo "Checking PatientNotes.tsx for responsive patterns..."
rg 'sm:|md:|lg:|xl:' src/components/Patient/PatientNotes.tsx -A 2
# Check for mobile-specific styles in the component
echo "Checking mobile-specific styles in PatientNotes.tsx..."
rg 'mobile|screen' src/components/Patient/PatientNotes.tsx
# Look for viewport-related utilities
echo "Checking for viewport utilities..."
rg 'useWindowSize|useMediaQuery|window\.innerWidth' src/components/Patient/PatientNotes.tsx
Length of output: 860
src/Utils/useFileUpload.tsx (1)
Line range hint 64-74
: Consider enhancing file type security measures.
While exporting the image extensions is useful for reusability, consider adding additional security measures for medical-related file uploads:
- Add MIME type validation in addition to extension checks
- Consider using a content-type detection library
- Document the security implications of allowing certain formats like SVG
Let's verify if MIME type validation is consistently applied:
src/components/Facility/PatientNotesSlideover.tsx (3)
29-29
: LGTM! Good internationalization practice
The addition of useTranslation hook and its usage throughout the component ensures proper internationalization support.
180-182
: LGTM! Appropriate width adjustment
The width increase to 430px provides more space for the enhanced features while maintaining a reasonable size for the slideover panel.
224-241
: Verify accessibility of the rich text editor
The RichTextEditor integration looks good and properly handles authorization. However, we should verify its accessibility features.
✅ Verification successful
Rich text editor has adequate keyboard accessibility implementation
The RichTextEditor component implements sufficient keyboard accessibility features:
- Proper keyboard navigation with Enter key handling for lists and quotes
- Escape key support for closing mentions dropdown
- Full keyboard support for text formatting and link insertion
- ARIA-compatible toolbar buttons with tooltips
- Custom text area with maintained focus management
The implementation follows accessibility best practices by:
- Preserving focus states after operations
- Providing clear visual feedback through tooltips
- Maintaining proper keyboard navigation flow
- Including descriptive labels and IDs for form controls
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for aria attributes and role definitions in the RichTextEditor component
rg -A 5 'aria-|role=' src/components/Common/RichTextEditor.tsx
# Search for keyboard event handlers that might affect accessibility
ast-grep --pattern 'onKeyDown|onKeyPress|onKeyUp' src/components/Common/RichTextEditor.tsx
Length of output: 157
Script:
#!/bin/bash
# First, let's verify if the RichTextEditor file exists and check its contents
fd RichTextEditor.tsx
# If it exists in a different location, let's search the entire codebase for rich text editor accessibility patterns
rg -i "aria-|role=|keyboard|a11y" -g "**/*RichTextEditor*"
# Let's also check if we're using any known rich text editor libraries that might handle accessibility
rg -l "@tiptap|slate-react|draft-js|react-quill" package.json
# Search for any accessibility-related tests
rg -l "accessibility|a11y" -g "*test*" -g "*spec*"
Length of output: 444
Script:
#!/bin/bash
# Let's examine the keyboard event handlers in RichTextEditor.tsx
rg -A 10 "handleEscape|onKeyDown" src/components/Common/RichTextEditor.tsx
# Check if we're using any markdown editor component that might handle accessibility
cat src/components/Common/RichTextEditor.tsx
# Look for any editor-related components that might be imported
rg -l "import.*Editor" src/components/Common/RichTextEditor.tsx
Length of output: 25341
src/components/Facility/ConsultationDetails/index.tsx (3)
39-39
: LGTM!
The import statement follows the project's import organization pattern and uses appropriate relative path notation.
60-60
: LGTM! Clean integration with existing tab system.
The new tab is well-integrated into the existing system, leveraging the flexible tab architecture without requiring changes to the core navigation logic.
60-60
: Verify translation key for the new tab.
Ensure that the translation key CONSULTATION_TAB__DISCUSSION_NOTES_FILES
is defined in the localization files.
✅ Verification successful
Translation key is properly defined
The translation key CONSULTATION_TAB__DISCUSSION_NOTES_FILES
is correctly defined in the localization files, supporting the new tab implementation.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check if translation key exists in localization files
# Search for the translation key in localization files
rg "CONSULTATION_TAB__DISCUSSION_NOTES_FILES" -g "*.json"
Length of output: 408
Script:
#!/bin/bash
# Check for translation keys in all locale files
fd -e json -d 2 . src/Locale --exec cat {} \; | jq -r 'select(.CONSULTATION_TAB__DISCUSSION_NOTES_FILES != null) | keys[] | select(. == "CONSULTATION_TAB__DISCUSSION_NOTES_FILES")'
Length of output: 226
src/components/Notifications/NotificationsList.tsx (2)
76-78
: LGTM: Seamless integration with existing notification system.
The new notification types are well-integrated with the existing notification system. They will automatically work with all features including:
- Read/unread status management
- Notification filtering
- Push notifications
77-78
: Verify if PATIENT_NOTE_MENTIONED requires specific note targeting.
Both PATIENT_NOTE_ADDED
and PATIENT_NOTE_MENTIONED
use identical URLs. Verify if the mention notification should navigate to the specific note where the user was mentioned.
✅ Verification successful
Based on the search results, I can now provide a final response:
PATIENT_NOTE_MENTIONED should navigate to the general notes section
The current implementation is correct. Here's why:
- The consultation notes are displayed in a single view at
/facility/${data.facility}/patient/${data.patient}/consultation/${data.consultation}/notes
- There is no specific note targeting in the URL structure for individual notes
- The notification is meant to direct users to the consultation notes section where they can view all notes, including mentions
- The same URL pattern is used for both PATIENT_NOTE_ADDED and PATIENT_NOTE_MENTIONED, which is appropriate since they both lead to the same notes view
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check if there's any specific handling for note mentions in the codebase
# Look for any specific handling of note mentions in components or routes
rg -A 5 "PATIENT_NOTE_MENTIONED" --type ts --type tsx
# Check for any note-specific URL parameters or routes
rg -A 5 "consultation/.*/notes/.*" --type ts --type tsx
Length of output: 167
Script:
#!/bin/bash
# Search for PATIENT_NOTE_MENTIONED without file type restrictions
rg -A 5 "PATIENT_NOTE_MENTIONED"
# Search for note-specific URL patterns without file type restrictions
rg -A 5 "consultation.*notes"
# Search for any note-specific routing or navigation logic
rg -A 5 "note.*id|noteId"
Length of output: 31678
src/components/Facility/models.tsx (3)
570-570
: LGTM! Property addition aligns with profile image feature.
The new read_profile_picture_url
property follows existing naming conventions and correctly supports the user profile images feature.
586-586
: LGTM! Property addition supports reply feature.
The optional reply_to
property correctly supports the hierarchical reply structure for doctor notes.
583-583
:
Fix typo in interface name: "PaitentNotesReplyModel".
Rename the interface from "PaitentNotesReplyModel" to "PatientNotesReplyModel" to maintain consistency and correctness.
-export interface PaitentNotesReplyModel {
+export interface PatientNotesReplyModel {
Likely invalid or redundant comment.
src/Redux/api.tsx (2)
824-828
: LGTM: Well-structured endpoint for retrieving patient notes.
The endpoint follows RESTful conventions and is properly typed with PatientNotesModel
as the response type.
Line range hint 1143-1166
: LGTM: Comprehensive file upload API endpoints.
The endpoints provide complete CRUD operations with proper typing and follow RESTful conventions. This implementation supports the PR objectives for file upload functionality.
src/common/constants.tsx (1)
672-676
: LGTM! The new notification event is well-structured.
The addition of the PATIENT_NOTE_MENTIONED
notification event follows the existing pattern and uses appropriate values for the notification type.
src/components/Facility/ConsultationDoctorNotes/index.tsx (4)
44-47
: Ensure type safety for the mode
state variable.
The mode
state variable is correctly typed as a union of string literals, ensuring type safety. This is a good practice to prevent unexpected values and catch potential bugs at compile time.
173-180
: Verify the integration of the RichTextEditor
component.
The RichTextEditor
component has replaced the previous text input field, providing a more robust editing experience for notes. However, it's important to verify that the component is properly integrated and functions as expected.
To verify the integration of the RichTextEditor
component, run the following script:
#!/bin/bash
# Description: Verify the integration of the `RichTextEditor` component.
# Test: Search for the usage of the `RichTextEditor` component.
# Expect: The component should be used consistently across the codebase.
rg --type tsx? --type jsx? $'RichTextEditor'
184-193
: Ensure proper error handling and loading states for the PatientNotesDetailedView
component.
The PatientNotesDetailedView
component is conditionally rendered based on the threadViewNote
state. While this allows for detailed viewing of specific notes, it's important to ensure that the component handles error states and loading states gracefully.
To verify the error handling and loading states, run the following script:
#!/bin/bash
# Description: Verify error handling and loading states in the `PatientNotesDetailedView` component.
# Test: Search for the `PatientNotesDetailedView` component.
# Expect: The component should have error handling and loading states.
ast-grep --pattern $'PatientNotesDetailedView'
2-18
: Ensure consistent import paths and extensions.
The import statements have been updated to include .js
extensions for various modules. While this improves clarity regarding file types, it's important to maintain consistency across the codebase. Verify that the same convention is followed in other files and consider updating the project's coding guidelines to reflect this change.
To verify the consistency of import paths and extensions across the codebase, run the following script:
src/components/Common/RichTextEditor.tsx (2)
159-203
: Verify 'Enter' key handling allows normal line breaks
The onKeyDown
handler prevents the default action of the Enter key to manage list and quote formatting. This might unintentionally prevent users from inserting line breaks in normal text. Ensure that:
- Users can insert new lines when not typing within a list or quote.
- The handler only prevents the default action when necessary for formatting purposes.
611-611
:
Fix incorrect method for clearing textarea content
Using editorRef.current.innerHTML = ""
will not clear the content of a <textarea>
element since it does not have an innerHTML
property that affects user input. Instead, you should set the value
property to an empty string to clear the content:
Apply this diff to correct the issue:
- editorRef.current.innerHTML = "";
+ editorRef.current.value = "";
Likely invalid or redundant comment.
{cancelReply && ( | ||
<div | ||
className="mr-2 cursor-pointer p-1 text-2xl" | ||
onClick={cancelReply} | ||
> | ||
<CareIcon | ||
icon="l-times" | ||
className="m-1 rounded-full bg-gray-400 p-1 text-white hover:bg-gray-500" | ||
/> | ||
</div> | ||
)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance button accessibility.
The cancel button should be more accessible with proper button semantics and keyboard support.
Apply these improvements:
- <div
- className="mr-2 cursor-pointer p-1 text-2xl"
- onClick={cancelReply}
+ <button
+ type="button"
+ className="mr-2 cursor-pointer p-1 text-2xl"
+ onClick={cancelReply}
+ aria-label="Cancel reply"
+ title="Cancel reply"
>
<CareIcon
icon="l-times"
className="m-1 rounded-full bg-gray-400 p-1 text-white hover:bg-gray-500"
/>
- </div>
+ </button>
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
{cancelReply && ( | |
<div | |
className="mr-2 cursor-pointer p-1 text-2xl" | |
onClick={cancelReply} | |
> | |
<CareIcon | |
icon="l-times" | |
className="m-1 rounded-full bg-gray-400 p-1 text-white hover:bg-gray-500" | |
/> | |
</div> | |
)} | |
{cancelReply && ( | |
<button | |
type="button" | |
className="mr-2 cursor-pointer p-1 text-2xl" | |
onClick={cancelReply} | |
aria-label="Cancel reply" | |
title="Cancel reply" | |
> | |
<CareIcon | |
icon="l-times" | |
className="m-1 rounded-full bg-gray-400 p-1 text-white hover:bg-gray-500" | |
/> | |
</button> | |
)} |
{state.notes.map((note) => { | ||
if (mode === "thread-view" && !note.root_note_object) { | ||
return ( | ||
<div className="mt-3"> | ||
<PatientNoteCard | ||
key={note.id} | ||
note={note} | ||
setReload={setReload} | ||
disableEdit={disableEdit} | ||
setReplyTo={setReplyTo} | ||
mode={mode} | ||
allowThreadView | ||
allowReply={false} | ||
setThreadViewNote={setThreadViewNote} | ||
/> | ||
</div> | ||
); | ||
} else if (mode === "default-view") { | ||
return ( | ||
<DoctorNoteReplyPreviewCard | ||
key={note.id} | ||
parentNote={note.reply_to_object} | ||
> | ||
<div className="mt-3"> | ||
<PatientNoteCard | ||
note={note} | ||
setReload={setReload} | ||
disableEdit={disableEdit} | ||
setReplyTo={setReplyTo} | ||
/> | ||
</div> | ||
</DoctorNoteReplyPreviewCard> | ||
); | ||
} | ||
})} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix key prop placement and handle all cases in map function.
- The
key
prop should be on the outermost element in the map function - The map function should handle all possible cases explicitly
{state.notes.map((note) => {
if (mode === "thread-view" && !note.root_note_object) {
return (
- <div className="mt-3">
+ <div key={note.id} className="mt-3">
<PatientNoteCard
- key={note.id}
note={note}
setReload={setReload}
disableEdit={disableEdit}
setReplyTo={setReplyTo}
mode={mode}
allowThreadView
allowReply={false}
setThreadViewNote={setThreadViewNote}
/>
</div>
);
} else if (mode === "default-view") {
return (
<DoctorNoteReplyPreviewCard
key={note.id}
parentNote={note.reply_to_object}
>
<div className="mt-3">
<PatientNoteCard
note={note}
setReload={setReload}
disableEdit={disableEdit}
setReplyTo={setReplyTo}
/>
</div>
</DoctorNoteReplyPreviewCard>
);
- }
+ } else {
+ return null; // Explicit handling of other cases
+ }
})}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
{state.notes.map((note) => { | |
if (mode === "thread-view" && !note.root_note_object) { | |
return ( | |
<div className="mt-3"> | |
<PatientNoteCard | |
key={note.id} | |
note={note} | |
setReload={setReload} | |
disableEdit={disableEdit} | |
setReplyTo={setReplyTo} | |
mode={mode} | |
allowThreadView | |
allowReply={false} | |
setThreadViewNote={setThreadViewNote} | |
/> | |
</div> | |
); | |
} else if (mode === "default-view") { | |
return ( | |
<DoctorNoteReplyPreviewCard | |
key={note.id} | |
parentNote={note.reply_to_object} | |
> | |
<div className="mt-3"> | |
<PatientNoteCard | |
note={note} | |
setReload={setReload} | |
disableEdit={disableEdit} | |
setReplyTo={setReplyTo} | |
/> | |
</div> | |
</DoctorNoteReplyPreviewCard> | |
); | |
} | |
})} | |
{state.notes.map((note) => { | |
if (mode === "thread-view" && !note.root_note_object) { | |
return ( | |
<div key={note.id} className="mt-3"> | |
<PatientNoteCard | |
note={note} | |
setReload={setReload} | |
disableEdit={disableEdit} | |
setReplyTo={setReplyTo} | |
mode={mode} | |
allowThreadView | |
allowReply={false} | |
setThreadViewNote={setThreadViewNote} | |
/> | |
</div> | |
); | |
} else if (mode === "default-view") { | |
return ( | |
<DoctorNoteReplyPreviewCard | |
key={note.id} | |
parentNote={note.reply_to_object} | |
> | |
<div className="mt-3"> | |
<PatientNoteCard | |
note={note} | |
setReload={setReload} | |
disableEdit={disableEdit} | |
setReplyTo={setReplyTo} | |
/> | |
</div> | |
</DoctorNoteReplyPreviewCard> | |
); | |
} else { | |
return null; // Explicit handling of other cases | |
} | |
})} |
🛠️ Refactor suggestion
Refactor to reduce code duplication and improve maintainability.
The current implementation has duplicated props and complex conditional logic. Consider extracting common props and simplifying the structure.
+ const commonNoteProps = {
+ setReload,
+ disableEdit,
+ setReplyTo,
+ };
+
{state.notes.map((note) => {
+ const noteCard = (
+ <PatientNoteCard
+ note={note}
+ {...commonNoteProps}
+ mode={mode}
+ allowThreadView={mode === "thread-view"}
+ allowReply={mode !== "thread-view"}
+ setThreadViewNote={setThreadViewNote}
+ />
+ );
+
if (mode === "thread-view" && !note.root_note_object) {
return (
- <div className="mt-3">
- <PatientNoteCard
- key={note.id}
- note={note}
- setReload={setReload}
- disableEdit={disableEdit}
- setReplyTo={setReplyTo}
- mode={mode}
- allowThreadView
- allowReply={false}
- setThreadViewNote={setThreadViewNote}
- />
+ <div key={note.id} className="mt-3">
+ {noteCard}
</div>
);
} else if (mode === "default-view") {
return (
<DoctorNoteReplyPreviewCard
key={note.id}
parentNote={note.reply_to_object}
>
<div className="mt-3">
- <PatientNoteCard
- note={note}
- setReload={setReload}
- disableEdit={disableEdit}
- setReplyTo={setReplyTo}
- />
+ {noteCard}
</div>
</DoctorNoteReplyPreviewCard>
);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
{state.notes.map((note) => { | |
if (mode === "thread-view" && !note.root_note_object) { | |
return ( | |
<div className="mt-3"> | |
<PatientNoteCard | |
key={note.id} | |
note={note} | |
setReload={setReload} | |
disableEdit={disableEdit} | |
setReplyTo={setReplyTo} | |
mode={mode} | |
allowThreadView | |
allowReply={false} | |
setThreadViewNote={setThreadViewNote} | |
/> | |
</div> | |
); | |
} else if (mode === "default-view") { | |
return ( | |
<DoctorNoteReplyPreviewCard | |
key={note.id} | |
parentNote={note.reply_to_object} | |
> | |
<div className="mt-3"> | |
<PatientNoteCard | |
note={note} | |
setReload={setReload} | |
disableEdit={disableEdit} | |
setReplyTo={setReplyTo} | |
/> | |
</div> | |
</DoctorNoteReplyPreviewCard> | |
); | |
} | |
})} | |
const commonNoteProps = { | |
setReload, | |
disableEdit, | |
setReplyTo, | |
}; | |
{state.notes.map((note) => { | |
const noteCard = ( | |
<PatientNoteCard | |
note={note} | |
{...commonNoteProps} | |
mode={mode} | |
allowThreadView={mode === "thread-view"} | |
allowReply={mode !== "thread-view"} | |
setThreadViewNote={setThreadViewNote} | |
/> | |
); | |
if (mode === "thread-view" && !note.root_note_object) { | |
return ( | |
<div key={note.id} className="mt-3"> | |
{noteCard} | |
</div> | |
); | |
} else if (mode === "default-view") { | |
return ( | |
<DoctorNoteReplyPreviewCard | |
key={note.id} | |
parentNote={note.reply_to_object} | |
> | |
<div className="mt-3"> | |
{noteCard} | |
</div> | |
</DoctorNoteReplyPreviewCard> | |
); | |
} | |
})} |
const MentionsDropdown: React.FC<MentionsDropdownProps> = ({ | ||
onSelect, | ||
position, | ||
editorRef, | ||
filter, | ||
}) => { | ||
const facilityId = useSlug("facility"); | ||
const { data, loading } = useQuery(routes.getFacilityUsers, { | ||
pathParams: { facility_id: facilityId }, | ||
}); | ||
|
||
const users = data?.results || []; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling for API failures.
The component currently lacks error handling for API failures, which could lead to a poor user experience. Consider exposing the loading and error states to the parent component.
const MentionsDropdown: React.FC<MentionsDropdownProps> = ({
onSelect,
position,
editorRef,
filter,
+ onError?: (error: Error) => void,
}) => {
const facilityId = useSlug("facility");
- const { data, loading } = useQuery(routes.getFacilityUsers, {
+ const { data, loading, error } = useQuery(routes.getFacilityUsers, {
pathParams: { facility_id: facilityId },
});
+ useEffect(() => {
+ if (error && onError) {
+ onError(error);
+ }
+ }, [error, onError]);
const users = data?.results || [];
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const MentionsDropdown: React.FC<MentionsDropdownProps> = ({ | |
onSelect, | |
position, | |
editorRef, | |
filter, | |
}) => { | |
const facilityId = useSlug("facility"); | |
const { data, loading } = useQuery(routes.getFacilityUsers, { | |
pathParams: { facility_id: facilityId }, | |
}); | |
const users = data?.results || []; | |
const MentionsDropdown: React.FC<MentionsDropdownProps> = ({ | |
onSelect, | |
position, | |
editorRef, | |
filter, | |
onError?: (error: Error) => void, | |
}) => { | |
const facilityId = useSlug("facility"); | |
const { data, loading, error } = useQuery(routes.getFacilityUsers, { | |
pathParams: { facility_id: facilityId }, | |
}); | |
useEffect(() => { | |
if (error && onError) { | |
onError(error); | |
} | |
}, [error, onError]); | |
const users = data?.results || []; |
const handleKeyDown = useCallback( | ||
(event: KeyboardEvent) => { | ||
if (event.key === "Enter" && filteredUsers.length > 0) { | ||
event.preventDefault(); | ||
if (selectedIndex !== null) { | ||
onSelect({ | ||
id: filteredUsers[selectedIndex].id.toString(), | ||
username: filteredUsers[selectedIndex].username, | ||
}); | ||
} else { | ||
onSelect({ | ||
id: filteredUsers[0].id.toString(), | ||
username: filteredUsers[0].username, | ||
}); | ||
} | ||
} else if (event.key === "ArrowDown") { | ||
event.preventDefault(); | ||
setSelectedIndex((prevIndex) => { | ||
if (prevIndex === null) return 0; | ||
return Math.min(filteredUsers.length - 1, prevIndex + 1); | ||
}); | ||
} else if (event.key === "ArrowUp") { | ||
event.preventDefault(); | ||
setSelectedIndex((prevIndex) => { | ||
if (prevIndex === null) return filteredUsers.length - 1; | ||
return Math.max(0, prevIndex - 1); | ||
}); | ||
} | ||
}, | ||
[filteredUsers, selectedIndex, onSelect], | ||
); | ||
|
||
useEffect(() => { | ||
document.addEventListener("keydown", handleKeyDown); | ||
return () => { | ||
document.removeEventListener("keydown", handleKeyDown); | ||
}; | ||
}, [handleKeyDown]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improve keyboard event handling.
The current implementation has several potential issues:
- Global event listener could interfere with other components
- Missing Escape key handling
- Potential race conditions with async operations
+const KEYS = {
+ ENTER: 'Enter',
+ ARROW_UP: 'ArrowUp',
+ ARROW_DOWN: 'ArrowDown',
+ ESCAPE: 'Escape',
+} as const;
const handleKeyDown = useCallback(
(event: KeyboardEvent) => {
+ // Ignore events if editor is not focused
+ if (document.activeElement !== editorRef.current) {
+ return;
+ }
- if (event.key === "Enter" && filteredUsers.length > 0) {
+ if (event.key === KEYS.ENTER && filteredUsers.length > 0) {
event.preventDefault();
if (selectedIndex !== null) {
onSelect({
id: filteredUsers[selectedIndex].id.toString(),
username: filteredUsers[selectedIndex].username,
});
} else {
onSelect({
id: filteredUsers[0].id.toString(),
username: filteredUsers[0].username,
});
}
- } else if (event.key === "ArrowDown") {
+ } else if (event.key === KEYS.ESCAPE) {
+ event.preventDefault();
+ onSelect({ id: '', username: '' }); // Signal to close dropdown
+ } else if (event.key === KEYS.ARROW_DOWN) {
event.preventDefault();
setSelectedIndex((prevIndex) => {
if (prevIndex === null) return 0;
return Math.min(filteredUsers.length - 1, prevIndex + 1);
});
- } else if (event.key === "ArrowUp") {
+ } else if (event.key === KEYS.ARROW_UP) {
event.preventDefault();
setSelectedIndex((prevIndex) => {
if (prevIndex === null) return filteredUsers.length - 1;
return Math.max(0, prevIndex - 1);
});
}
},
- [filteredUsers, selectedIndex, onSelect],
+ [filteredUsers, selectedIndex, onSelect, editorRef],
);
Committable suggestion skipped: line range outside the PR's diff.
return ( | ||
<div | ||
className="absolute z-10 max-h-36 w-64 overflow-y-auto rounded-md bg-white text-sm shadow-lg" | ||
style={{ top: dropdownPosition.top, left: dropdownPosition.left }} | ||
> | ||
{loading && <div className="p-2 text-gray-500">Loading...</div>} | ||
{filteredUsers.length > 0 && !loading ? ( | ||
filteredUsers.map((user, index) => ( | ||
<div | ||
key={user.id} | ||
className={`flex cursor-pointer items-center gap-2 p-2 ${ | ||
index === selectedIndex ? "bg-gray-100" : "hover:bg-gray-100" | ||
}`} | ||
onClick={() => | ||
onSelect({ id: user.id.toString(), username: user.username }) | ||
} | ||
> | ||
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-primary text-xs font-semibold text-white"> | ||
{user.first_name[0]} | ||
</span> | ||
{user.username} | ||
</div> | ||
)) | ||
) : ( | ||
<div className="p-2 text-gray-500">No users found</div> | ||
)} | ||
</div> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance accessibility and UI robustness.
The current implementation needs improvements in accessibility and UI handling:
- Missing ARIA attributes for screen readers
- No handling of long usernames
- Basic loading state
return (
<div
className="absolute z-10 max-h-36 w-64 overflow-y-auto rounded-md bg-white text-sm shadow-lg"
style={{ top: dropdownPosition.top, left: dropdownPosition.left }}
+ role="listbox"
+ aria-label="User mentions"
>
- {loading && <div className="p-2 text-gray-500">Loading...</div>}
+ {loading && (
+ <div className="p-2 text-gray-500" role="status" aria-live="polite">
+ <span className="inline-block animate-spin">⌛</span> Loading users...
+ </div>
+ )}
{filteredUsers.length > 0 && !loading ? (
filteredUsers.map((user, index) => (
<div
key={user.id}
className={`flex cursor-pointer items-center gap-2 p-2 ${
index === selectedIndex ? "bg-gray-100" : "hover:bg-gray-100"
}`}
+ role="option"
+ aria-selected={index === selectedIndex}
onClick={() =>
onSelect({ id: user.id.toString(), username: user.username })
}
>
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-primary text-xs font-semibold text-white">
{user.first_name[0]}
</span>
- {user.username}
+ <span className="truncate" title={user.username}>
+ {user.username}
+ </span>
</div>
))
) : (
- <div className="p-2 text-gray-500">No users found</div>
+ <div className="p-2 text-gray-500" role="status" aria-live="polite">
+ No users found
+ </div>
)}
</div>
);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
return ( | |
<div | |
className="absolute z-10 max-h-36 w-64 overflow-y-auto rounded-md bg-white text-sm shadow-lg" | |
style={{ top: dropdownPosition.top, left: dropdownPosition.left }} | |
> | |
{loading && <div className="p-2 text-gray-500">Loading...</div>} | |
{filteredUsers.length > 0 && !loading ? ( | |
filteredUsers.map((user, index) => ( | |
<div | |
key={user.id} | |
className={`flex cursor-pointer items-center gap-2 p-2 ${ | |
index === selectedIndex ? "bg-gray-100" : "hover:bg-gray-100" | |
}`} | |
onClick={() => | |
onSelect({ id: user.id.toString(), username: user.username }) | |
} | |
> | |
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-primary text-xs font-semibold text-white"> | |
{user.first_name[0]} | |
</span> | |
{user.username} | |
</div> | |
)) | |
) : ( | |
<div className="p-2 text-gray-500">No users found</div> | |
)} | |
</div> | |
); | |
}; | |
return ( | |
<div | |
className="absolute z-10 max-h-36 w-64 overflow-y-auto rounded-md bg-white text-sm shadow-lg" | |
style={{ top: dropdownPosition.top, left: dropdownPosition.left }} | |
role="listbox" | |
aria-label="User mentions" | |
> | |
{loading && ( | |
<div className="p-2 text-gray-500" role="status" aria-live="polite"> | |
<span className="inline-block animate-spin">⌛</span> Loading users... | |
</div> | |
)} | |
{filteredUsers.length > 0 && !loading ? ( | |
filteredUsers.map((user, index) => ( | |
<div | |
key={user.id} | |
className={`flex cursor-pointer items-center gap-2 p-2 ${ | |
index === selectedIndex ? "bg-gray-100" : "hover:bg-gray-100" | |
}`} | |
role="option" | |
aria-selected={index === selectedIndex} | |
onClick={() => | |
onSelect({ id: user.id.toString(), username: user.username }) | |
} | |
> | |
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-primary text-xs font-semibold text-white"> | |
{user.first_name[0]} | |
</span> | |
<span className="truncate" title={user.username}> | |
{user.username} | |
</span> | |
</div> | |
)) | |
) : ( | |
<div className="p-2 text-gray-500" role="status" aria-live="polite"> | |
No users found | |
</div> | |
)} | |
</div> | |
); | |
}; |
setIsLoading(true); | ||
|
||
const { data } = await request(routes.getPatientNote, { | ||
pathParams: { | ||
patientId: patientId, | ||
noteId, | ||
}, | ||
query: { | ||
consultation: consultationId, | ||
thread, | ||
}, | ||
}); | ||
|
||
if (data) { | ||
setState(data); | ||
} | ||
setIsLoading(false); | ||
setReload?.(false); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Implement error handling in 'fetchNotes' function.
Currently, if the API request fails, the component does not handle the error or provide feedback to the user. Adding error handling will improve user experience and make debugging easier.
Apply this diff to handle errors:
const fetchNotes = async () => {
setIsLoading(true);
- const { data } = await request(routes.getPatientNote, {
+ const { res, data } = await request(routes.getPatientNote, {
pathParams: {
patientId: patientId,
noteId,
},
query: {
consultation: consultationId,
thread,
},
});
+ if (res?.ok && data) {
setState(data);
- }
+ } else {
+ Notification.Error({ msg: "Failed to fetch notes." });
+ }
setIsLoading(false);
- setReload?.(false);
+ setReload(false);
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
setIsLoading(true); | |
const { data } = await request(routes.getPatientNote, { | |
pathParams: { | |
patientId: patientId, | |
noteId, | |
}, | |
query: { | |
consultation: consultationId, | |
thread, | |
}, | |
}); | |
if (data) { | |
setState(data); | |
} | |
setIsLoading(false); | |
setReload?.(false); | |
}; | |
setIsLoading(true); | |
const { res, data } = await request(routes.getPatientNote, { | |
pathParams: { | |
patientId: patientId, | |
noteId, | |
}, | |
query: { | |
consultation: consultationId, | |
thread, | |
}, | |
}); | |
if (res?.ok && data) { | |
setState(data); | |
} else { | |
Notification.Error({ msg: "Failed to fetch notes." }); | |
} | |
setIsLoading(false); | |
setReload(false); |
setDownloadURL(URL.createObjectURL(blob)); | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revoke object URLs to prevent memory leaks
When using URL.createObjectURL
, it's important to revoke the object URL with URL.revokeObjectURL
when it's no longer needed to prevent memory leaks.
Apply these diffs to revoke the object URL:
In the downloadFileUrl
function:
-fetch(url)
.then((res) => res.blob())
.then((blob) => {
- setDownloadURL(URL.createObjectURL(blob));
+ const objectURL = URL.createObjectURL(blob);
+ setDownloadURL(objectURL);
});
In the handleClose
function:
const handleClose = () => {
+ if (downloadURL) {
+ URL.revokeObjectURL(downloadURL);
+ }
setDownloadURL("");
setFileState({
...file_state,
open: false,
zoom: 4,
isZoomInDisabled: false,
isZoomOutDisabled: false,
});
};
Also applies to: 107-115
if (allowThreadView && setThreadViewNote) | ||
setThreadViewNote(note.id); | ||
}} | ||
className={`pl-11 text-sm text-gray-700 ${allowThreadView && "cursor-pointer"}`} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid including 'false' in className
when condition is false
Using logical &&
in template literals may include the string 'false'
in the className
when the condition is false, leading to unintended class names. It's better to use a ternary operator to ensure only the desired class is included.
Apply this diff to correct the className
:
-className={`pl-11 text-sm text-gray-700 ${allowThreadView && "cursor-pointer"}`}
+className={`pl-11 text-sm text-gray-700 ${allowThreadView ? "cursor-pointer" : ""}`}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
className={`pl-11 text-sm text-gray-700 ${allowThreadView && "cursor-pointer"}`} | |
className={`pl-11 text-sm text-gray-700 ${allowThreadView ? "cursor-pointer" : ""}`} |
onChange={(e) => { | ||
setMarkdown(e.target.value); | ||
}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid redundant state updates in the 'onChange' handler
The onChange
event updates setMarkdown
directly, and handleInput
(attached to onInput
) also updates setMarkdown
. This can lead to redundant state updates. To optimize performance and prevent potential side effects, consider:
- Removing the
onChange
handler ifhandleInput
sufficiently handles all input changes. - Ensuring that
setMarkdown
is called only once per input event.
{isPreviewMode ? ( | ||
<div className="max-h-[400px] min-h-[70px] overflow-y-auto p-4"> | ||
<MarkdownPreview markdown={markdown} /> | ||
</div> | ||
) : ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure Markdown rendering is secure against XSS attacks
When rendering user-generated Markdown content in MarkdownPreview
, it's crucial to prevent Cross-Site Scripting (XSS) vulnerabilities. Please verify that:
- The Markdown rendering library sanitizes input effectively.
- Any HTML content within the Markdown is properly escaped or filtered.
- Additional security measures are in place if the library does not handle sanitization adequately.
This PR is created to track my progress on the GSoC project (#7159) and to merge all PRs related to Doctor notes.
Backend RR: ohcnetwork/care#2295
walk_through_discussion_notes_enhancement.mp4
File Upload Option
User Tagging Mechanism
Summary by CodeRabbit
Release Notes
New Features
MarkdownPreview
component for rendering markdown content with user mentions.RichTextEditor
for enhanced note editing capabilities.MentionsDropdown
for user mention suggestions while typing.Enhancements
Bug Fixes
Style