-
Notifications
You must be signed in to change notification settings - Fork 447
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
User Management Redesign and User Details Page #9080
base: develop
Are you sure you want to change the base?
User Management Redesign and User Details Page #9080
Conversation
- Creating a separate user detail page (for user management) Pt 1
- Remove role editing from role management component - Use passable formData for formValidation - Minor customizations for role management issues - Only show role management options for pages of users with Roles: Doctor and Nurse
- Import the form into UserInformationTab (removing the current form) - Moved out the form components into its own file - Import the form into UserAdd
- Modified to use a new route instead - Added a new route to Backend rather than changing permissions for UserModel - Display view details button based on permissions
- Moving out additional common functionality inyo UserListAndCard - Removed memoization since it was causing hook errors - Switched back to using getFacilityUsers route in FacilityUsers as that should be faster
- password reset form fix - use passed in username than authUser's username - UserAddEditForm - pass in facility Ids than full facilities in handleSubmit
- Added error message notifications for the form submits
WalkthroughThe pull request introduces significant modifications across various components, primarily focusing on user management and account functionalities. Enhancements include new localization entries for user management actions, updates to routing for user-related pages, and improvements in validation logic for user forms. New components for managing linked facilities and skills have been added, while several existing components have been refactored or removed to streamline functionality. The changes aim to improve user interaction and the overall structure of the user management interface. Changes
Assessment against linked issues
Suggested labels
Suggested reviewers
Poem
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 (
|
✅ Deploy Preview for care-ohc ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
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: 37
🧹 Outside diff range and nitpick comments (67)
src/Routers/routes/UserRoutes.tsx (1)
11-17
: Consider deprecating the "/user/profile" route.The PR summary mentions plans to remove/redirect the existing profile page. Since you've introduced a new user details page at
/users/:username
, consider:
- Adding a deprecation notice for
/user/profile
- Implementing a redirect from
/user/profile
to the new user details pageExample implementation:
"/user/profile": () => { console.warn('Deprecated: Use /users/:username instead'); const username = getCurrentUser()?.username; return username ? <Navigate to={`/users/${username}`} replace /> : <UserProfile />; },src/components/Common/UserColumns.tsx (3)
3-6
: Follow TypeScript naming conventions for type definitions.The type name
userChildProps
should be in PascalCase as per TypeScript conventions.-export type userChildProps = { +export type UserChildProps = { userData: UserModel; username: string; };
8-13
: Improve component type safety and structure.Consider these improvements:
- Use PascalCase for component name
- Use React.FC for better type safety
- Group props into a single interface
+interface UserColumnsProps { + heading: string; + note: string; + Child: (childProps: UserChildProps) => JSX.Element | undefined; + childProps: UserChildProps; +} -export default function userColumns( - heading: string, - note: string, - Child: (childProps: userChildProps) => JSX.Element | undefined, - childProps: userChildProps, -) { +const UserColumns: React.FC<UserColumnsProps> = ({ + heading, + note, + Child, + childProps, +}) => {
1-29
: Well-structured component that aligns with PR objectives.The component provides a consistent and reusable layout pattern for user management sections, which aligns well with the PR's goal of improving user management UI. The split-column design effectively organizes content, with clear separation between section headers and content.
Consider documenting usage examples in comments or creating a Storybook story to showcase different use cases of this reusable component.
src/components/Users/LinkedFacilitiesTab.tsx (2)
7-10
: Consider using interface for better documentation.Consider using an
interface
instead oftype
for Props to allow for better documentation and extensibility.-type Props = { +interface Props { userData: UserModel; username: string; -}; +}
12-32
: Consider adding error boundary and loading state.The component could benefit from error handling and loading states to improve user experience.
Consider wrapping the component with an error boundary and adding a loading state:
export default function LinkedFacilitiesTab(props: Props) { const { userData } = props; const { t } = useTranslation(); if (!userData) { return null; } return ( <ErrorBoundary fallback={<div>{t("error_message")}</div>}> <Suspense fallback={<LoadingSpinner />}> <div className="mt-10 flex flex-col gap-y-12"> {userColumns( t("linked_facilities"), t("linked_facilities_note"), LinkedFacilities, props, )} </div> </Suspense> </ErrorBoundary> ); }src/components/Users/UnlinkSkillDialog.tsx (2)
32-38
: Enhance accessibility and error handling.While the translation implementation is good, consider these improvements:
- Add aria-label for better screen reader support
- Add error handling for undefined skillName
- <div className="flex leading-relaxed text-secondary-800"> + <div + className="flex leading-relaxed text-secondary-800" + role="alert" + aria-label={t("unlink_skill_confirmation_message")} + > <span> - {t("unlink_skill_confirm")} <strong>{props.skillName}</strong>{" "} + {t("unlink_skill_confirm")} <strong>{props.skillName || t("unknown_skill")}</strong>{" "} {t("from_user")} <strong>{props.userName}</strong>?{" "} {t("unlink_skill_access")} </span> </div>
24-25
: Consider using template literals for translation keys.The action prop is hardcoded while the title uses translation. Consider using translation for both for consistency.
- action="Unlink" + action={t("unlink_action")} title={t("unlink_skill")}src/components/Users/RoleAndSkillsTab.tsx (2)
13-16
: Add component documentation.Consider adding JSDoc documentation to describe the component's purpose, props, and usage example.
+/** + * RoleAndSkillsTab displays user qualifications and linked skills based on user type. + * Qualifications are only shown for Doctors and Nurses. + * @param {Props} props - Component props + * @param {UserModel} props.userData - User data containing user type and other details + * @param {string} props.username - Username of the user + */ export default function RoleAndSkillsTab(props: Props) {
17-19
: Improve guard clause implementation.The current implementation silently returns undefined. Consider returning null explicitly or implementing an error boundary for better debugging and maintainability.
if (!userData || !username) { - return; + return null; // Explicit return for better readability }src/components/Common/SkillSelect.tsx (3)
4-4
: Consider moving SkillModel to a shared location.Since
SkillSelect
is in the Common components directory but importsSkillModel
from the Users module, this could create tight coupling or potential circular dependencies. Consider moving the model to a shared location (e.g.,@/models
or@/types
).
Line range hint
54-59
: Add type safety to optionLabel callback.The
optionLabel
callback usesany
type which bypasses TypeScript's type checking. This could lead to runtime errors if the option object structure changes.Apply this fix:
- optionLabel={(option: any) => + optionLabel={(option: SkillModel) => option.name + (option.district_object ? `, ${option.district_object.name}` : "") }
Line range hint
35-47
: Add error handling for API response.The
skillSearch
callback assumes the API response will always have aresults
property. Consider adding error handling to gracefully handle API failures or unexpected response formats.Consider this improvement:
const skillSearch = useCallback( async (text: string) => { const query = { limit: 50, offset: 0, search_text: text, all: searchAll, }; - const { data } = await request(routes.getAllSkills, { query }); - return data?.results.filter( - (skill) => - !userSkills?.some( - (userSkill) => userSkill.skill_object.id === skill.id, - ), - ); + try { + const { data } = await request(routes.getAllSkills, { query }); + return data?.results?.filter( + (skill) => + !userSkills?.some( + (userSkill) => userSkill.skill_object.id === skill.id, + ), + ) ?? []; + } catch (error) { + console.error('Failed to fetch skills:', error); + return []; + } }, [searchAll, userSkills], );src/components/Common/Page.tsx (1)
20-20
: Add JSDoc documentation for the new prop.Please add documentation for the
hideTitleOnPage
prop to maintain consistency with other props (likecollapseSidebar
) and improve maintainability.+ /** + * If true, hides the title on the page while preserving other PageTitle functionality. + * @default false + */ hideTitleOnPage?: boolean;src/components/Users/ConfirmFacilityModal.tsx (3)
6-20
: Consider improving type safety and maintainability.
- Extract the props interface for better reusability and documentation.
- Use a union type for the
type
prop instead of string to restrict valid values.+type FacilityActionType = "unlink_facility" | "clear_home_facility" | "replace_home_facility"; + +interface ConfirmFacilityModalProps { + username: string; + currentFacility?: FacilityModel; + homeFacility?: FacilityModel; + handleCancel: () => void; + handleOk: () => void; + type: FacilityActionType; +} + -const ConfirmFacilityModal = ({ - username, - currentFacility, - homeFacility, - handleCancel, - handleOk, - type, -}: { - username: string; - currentFacility?: FacilityModel; - homeFacility?: FacilityModel; - handleCancel: () => void; - handleOk: () => void; - type: string; -}) => { +const ConfirmFacilityModal = ({ + username, + currentFacility, + homeFacility, + handleCancel, + handleOk, + type, +}: ConfirmFacilityModalProps) => {
21-64
: Improve code organization and consistency.
- The switch statement could be simplified by extracting the modal configurations.
- The body content structure is inconsistent (some cases have extra div wrappers).
+interface ModalConfig { + action: string; + body: JSX.Element; +} + +const getModalConfig = ( + type: FacilityActionType, + t: (key: string) => string, + username: string, + currentFacility?: FacilityModel, + homeFacility?: FacilityModel +): ModalConfig => { + const configs: Record<FacilityActionType, ModalConfig> = { + unlink_facility: { + action: "Unlink", + body: ( + <div> + {t("unlink_facility_confirm")}{" "} + <strong>{currentFacility?.name}</strong> {t("from_user")}{" "} + <strong>{username}</strong> + <br /> + {t("unlink_facility_access")} + </div> + ) + }, + clear_home_facility: { + action: "Clear", + body: ( + <div> + {t("clear_home_facility_confirm")}{" "} + <strong>{currentFacility?.name}</strong> {t("from_user")}{" "} + <strong>{username}</strong> + </div> + ) + }, + replace_home_facility: { + action: "Replace", + body: ( + <div> + {t("replace_home_facility_confirm")}{" "} + <strong>{homeFacility?.name}</strong> {t("with")}{" "} + <strong>{currentFacility?.name}</strong>{" "} + {t("replace_home_facility_confirm_as")} <strong>{username}</strong>? + </div> + ) + } + }; + return configs[type]; +};
65-76
: Consider improving modal control and presentation.
- The
show
prop could be controlled externally for better flexibility.- The danger variant might not be appropriate for all actions (e.g., replace_home_facility).
- The extra div wrapper around the body content is redundant.
- show={true} + show={show} - variant="danger" + variant={type === "unlink_facility" ? "danger" : "warning"} - <div className="flex leading-relaxed text-secondary-800">{body}</div> + {body}src/components/Users/UserSummary.tsx (2)
21-28
: Enhance error handling for undefined userDataThe early return for undefined userData could be improved to provide better user feedback.
Consider this enhancement:
if (!userData) { - return; + return ( + <div className="flex justify-center p-4"> + {t("user_data_not_found")} + </div> + ); }
84-93
: Improve button implementationThe button implementation can be enhanced for better performance and accessibility.
Consider these improvements:
<ButtonV2 - authorizeFor={() => deletePermitted} + authorizeFor={deletePermitted} onClick={() => setshowDeleteDialog(true)} variant="danger" data-testid="user-delete-button" - className="my-1 inline-flex" + className="my-1 inline-flex items-center gap-2" + aria-label={t("delete_account_aria_label")} > <CareIcon icon="l-trash" className="h-4" /> - <span className="">{t("delete_account_btn")}</span> + <span>{t("delete_account_btn")}</span> </ButtonV2>src/components/Users/UserInformation.tsx (2)
66-77
: Add confirmation dialog and loading state for avatar deletion.The avatar deletion could benefit from a confirmation dialog and loading state to prevent accidental deletions and provide better user feedback.
const handleAvatarDelete = async (onError: () => void) => { + const confirmed = window.confirm(t("confirm_delete_avatar")); + if (!confirmed) return; + + setIsDeleting(true); try { const { res } = await request(routes.deleteProfilePicture, { pathParams: { username }, }); if (res?.ok) { Notification.Success({ msg: "Profile picture deleted" }); await refetchUserData(); setEditAvatar(false); } else { onError(); } } catch (error) { onError(); } finally { + setIsDeleting(false); } };
79-116
: Enhance accessibility with ARIA labels and roles.While the component structure is good, it could benefit from improved accessibility.
<div className="overflow-visible rounded-lg bg-white px-4 py-5 shadow sm:rounded-lg sm:px-6"> - <div className="my-4 flex justify-between"> + <div className="my-4 flex justify-between" role="region" aria-label={t("profile_section")}> <div className="flex items-center"> <Avatar imageUrl={userData?.read_profile_picture_url} name={formatDisplayName(userData)} className="h-20 w-20" + aria-label={t("profile_picture")} /> <div className="my-4 ml-4 flex flex-col gap-2"> <ButtonV2 onClick={(_) => setEditAvatar(!editAvatar)} type="button" id="edit-cancel-profile-button" className="border border-gray-200 bg-gray-50 text-black hover:bg-gray-100" shadow={false} + aria-label={t("change_avatar_button")} >src/components/Form/Form.tsx (3)
36-36
: LGTM! Consider adding JSDoc documentation.The new optional
resetFormVals
prop is well-typed and appropriately positioned. Consider adding JSDoc documentation to explain its purpose and behavior.+/** Whether to reset form values to initial state on cancel. */ resetFormVals?: boolean;
83-88
: LGTM! Consider adding error handling.The cancel handler implementation is clean and correctly implements the reset functionality. However, consider wrapping the state reset in a try-catch block to handle potential dispatch errors.
const handleCancel = () => { if (props.resetFormVals) { + try { dispatch({ type: "set_form", form: formVals.current }); + } catch (error) { + console.error('Failed to reset form:', error); + Notification.Error({ msg: "Failed to reset form" }); + } } props.onCancel?.(); };
Line range hint
36-88
: Consider form reset feature documentation and testing.The form reset feature is a good addition that aligns with the user management redesign objectives. To ensure proper usage across the application:
- Document the reset behavior in the component's README or documentation
- Add unit tests covering the reset functionality
- Consider adding a FormProvider context property to expose the reset capability programmatically
Would you like me to help with generating the documentation or test cases?
src/components/Users/UserHome.tsx (5)
23-26
: Consider improving the tabChildProp interface naming and documentation.The interface could be more descriptive and include documentation:
-export interface tabChildProp { +export interface TabContentProps { + /** Component to render the tab content */ body: (childProps: userChildProps) => JSX.Element | undefined; + /** Optional custom name for the tab. If not provided, the tab key is used */ name?: string; }
46-49
: Simplify role visibility check and improve naming.The function can be simplified and renamed to better reflect its purpose.
-const roleInfoBeVisible = () => { - if (["Doctor", "Nurse"].includes(userData?.user_type ?? "")) return true; - return false; -}; +const hasQualifications = () => ["Doctor", "Nurse"].includes(userData?.user_type ?? "");
51-62
: Improve tab configuration maintainability and type safety.Consider extracting the tab configuration to a separate constant and improving type safety:
+export const enum TabKeys { + PROFILE = 'PROFILE', + SKILLS = 'SKILLS', + FACILITIES = 'FACILITIES' +} +type TabConfig = Record<TabKeys, TabContentProps>; -const TABS: { - PROFILE: tabChildProp; - SKILLS: tabChildProp; - FACILITIES: tabChildProp; -} = { +const TABS: TabConfig = { [TabKeys.PROFILE]: { body: UserSummaryTab }, [TabKeys.SKILLS]: { body: RoleAndSkillsTab, - name: roleInfoBeVisible() ? "QUALIFICATIONS_SKILLS" : "SKILLS", + name: hasQualifications() ? "QUALIFICATIONS_SKILLS" : "SKILLS", }, [TabKeys.FACILITIES]: { body: LinkedFacilitiesTab }, };
94-117
: Enhance navigation accessibility and loading state.Consider improving the tab navigation accessibility and loading experience:
- Add ARIA roles and states to the tab navigation
- Consider using a skeleton loader instead of a simple loading spinner
<nav className="flex space-x-6 overflow-x-auto" id="usermanagement_tab_nav" + role="tablist" + aria-label={t("user_management_navigation")} > {keysOf(TABS).map((p) => { const tabName = TABS[p]?.name ?? p; return ( <Link key={p} className={classNames(/*...*/)} href={`/users/${username}/${p.toLocaleLowerCase()}`} + role="tab" + aria-selected={currentTab === p} + aria-controls={`${p.toLowerCase()}-panel`} >
28-127
: Add unit tests for the UserHome component.To ensure reliability and meet PR objectives, consider adding tests for:
- Tab navigation and rendering
- User data loading and error states
- Role-based visibility logic
- Accessibility features
Would you like me to help create a test suite for this component?
src/components/Users/UserBanner.tsx (2)
19-25
: Consider memoizing the online status calculation.The
isUserOnline
check runs on every render due to the useEffect. Consider memoizing this value usinguseMemo
to optimize performance, especially ifisUserOnline
is computationally expensive.-const [userOnline, setUserOnline] = useState(false); +const userOnline = useMemo(() => userData ? isUserOnline(userData) : false, [userData]); -useEffect(() => { - if (!userData) return; - setUserOnline(isUserOnline(userData)); -}, [userData]);
116-124
: Enhance experience calculation robustness.The current experience calculation doesn't handle edge cases like future dates. Consider extracting this logic into a utility function with proper validation.
+// Add to utils.ts +export const calculateExperienceYears = (startDate: string): number => { + const start = dayjs(startDate); + if (!start.isValid() || start.isAfter(dayjs())) { + return 0; + } + return dayjs().diff(start, "years", false); +}; // In component -{dayjs().diff( - userData.doctor_experience_commenced_on, - "years", - false, -)}{" "} +{calculateExperienceYears(userData.doctor_experience_commenced_on)}{" "}src/components/Users/UserResetPassword.tsx (2)
40-50
: Enhance password validation security.While the current validation covers basic requirements, consider strengthening it by:
- Adding special character requirement
- Making the validation function more configurable
- Adding maximum length validation
Example implementation:
-const validateNewPassword = (password: string) => { +const validateNewPassword = (password: string, options = { + minLength: 8, + maxLength: 128, + requireSpecialChar: true +}) => { if ( - password.length < 8 || + password.length < options.minLength || + password.length > options.maxLength || !/\d/.test(password) || password === password.toUpperCase() || - password === password.toLowerCase() + password === password.toLowerCase() || + (options.requireSpecialChar && !/[!@#$%^&*(),.?":{}|<>]/.test(password)) ) { return false; } return true; };
98-192
: Enhance form accessibility and validation UX.
- Add proper ARIA attributes for better screen reader support
- Show validation messages consistently, not just on focus
- Consider extracting validation rules to a configuration object
Example improvements:
-<form action="#" method="POST"> +<form + action="#" + method="POST" + aria-label="Password reset form" + noValidate +> <div className="grid grid-cols-6 gap-4"> <TextFormField name="old_password" label={t("current_password")} className="col-span-6 sm:col-span-3" type="password" value={changePasswordForm.old_password} onChange={(e) => setChangePasswordForm({ ...changePasswordForm, old_password: e.value, })} error={changePasswordErrors.old_password} + aria-required="true" + aria-invalid={!!changePasswordErrors.old_password} + aria-describedby="old-password-error" required /> + {changePasswordErrors.old_password && ( + <div id="old-password-error" className="text-error-600"> + {changePasswordErrors.old_password} + </div> + )} </div> - <div className="text-small mb-2 hidden pl-2 text-secondary-500 peer-focus-within:block"> + <div className="text-small mb-2 pl-2 text-secondary-500" + role="alert" + aria-live="polite"> {/* ... validation messages ... */} </div> </form>src/components/ABDM/LinkAbhaNumber/CreateWithAadhaar.tsx (2)
Line range hint
466-484
: Refactor validation logic to improve maintainability.The ABHA address validation regex pattern is duplicated. Consider:
- Extracting the regex pattern to a constant
- Creating a reusable validation function
Apply this refactor:
+const ABHA_ADDRESS_REGEX = /^(?![\d.])[a-zA-Z0-9._]{4,}(?<!\.)$/; + +const validateAbhaAddress = (healthId: string) => ABHA_ADDRESS_REGEX.test(healthId); + function ChooseAbhaAddress({ // ... <ButtonV2 className="w-full" - disabled={ - memory?.isLoading || - !/^(?![\d.])[a-zA-Z0-9._]{4,}(?<!\.)$/.test(healthId) - } + disabled={memory?.isLoading || !validateAbhaAddress(healthId)} onClick={handleSubmit} >Also applies to: 516-519
Line range hint
252-269
: Enhance error handling for better user experience.The error handling for API calls could be more robust. Consider:
- Adding specific error messages for different failure scenarios
- Handling session expiry gracefully
- Managing error states more effectively in the form state
Apply this enhancement:
const handleResendOtp = async () => { setMemory((prev) => ({ ...prev, isLoading: true })); + try { const { res, data } = await request( routes.abdm.healthId.abhaCreateLinkMobileNumber, { body: { mobile: memory?.mobileNumber.replace("+91", "").replace(/ /g, ""), transaction_id: memory?.transactionId, }, }, ); if (res?.status === 200 && data) { setMemory((prev) => ({ ...prev, transactionId: data.transaction_id, resendOtpCount: prev.resendOtpCount + 1, })); Notify.Success({ msg: data.detail ?? t("mobile_otp_send_success"), }); - } else { + } else if (res?.status === 401) { + Notify.Error({ + msg: t("session_expired_please_retry"), + }); + // Handle session expiry + } else if (res?.status === 429) { + Notify.Error({ + msg: t("too_many_attempts_please_wait"), + }); + // Handle rate limiting + } else { setMemory((prev) => ({ ...prev, resendOtpCount: Infinity, })); - Notify.Success({ + Notify.Error({ msg: t("mobile_otp_send_error"), }); } + } catch (error) { + Notify.Error({ + msg: t("network_error_please_retry"), + }); + } finally { setMemory((prev) => ({ ...prev, isLoading: false })); + } };Also applies to: 359-376
src/components/Users/UserProfile.tsx (4)
Line range hint
279-291
: Internationalize password validation messagesThe password validation messages should use the translation function (t) to support multiple languages, as mentioned in the PR objectives for internationalization of UI text.
const rules = [ { test: (p: string) => p.length >= 8, - message: "Password should be at least 8 characters long", + message: t("password_min_length_message"), }, { test: (p: string) => p !== p.toUpperCase(), - message: "Password should contain at least 1 lowercase letter", + message: t("password_lowercase_message"), }, { test: (p: string) => p !== p.toLowerCase(), - message: "Password should contain at least 1 uppercase letter", + message: t("password_uppercase_message"), }, { test: (p: string) => /\d/.test(p), - message: "Password should contain at least 1 number", + message: t("password_number_message"), }, ];
Line range hint
293-397
: Enhance form validation implementationSeveral improvements can be made to the validation logic:
- Extract magic numbers into named constants
- Internationalize error messages
- Consider extracting field-specific validation logic into separate functions for better maintainability
+ const MIN_AGE = 17; + const MAX_EXPERIENCE_YEARS = 100; + const MAX_WEEKLY_HOURS = 168; const validateForm = () => { const errors = { ...initError }; let invalidForm = false; Object.keys(states.form).forEach((field) => { switch (field) { case "date_of_birth": if (!states.form[field]) { - errors[field] = "Enter a valid date of birth"; + errors[field] = t("enter_valid_dob"); invalidForm = true; } else if ( !dayjs(states.form[field]).isValid() || - dayjs(states.form[field]).isAfter(dayjs().subtract(17, "year")) + dayjs(states.form[field]).isAfter(dayjs().subtract(MIN_AGE, "year")) ) { - errors[field] = "Enter a valid date of birth"; + errors[field] = t("enter_valid_dob"); invalidForm = true; } return; // Similar changes for other validation messages
Line range hint
467-503
: Enhance form submission error handling and internationalizationThe form submission handler needs improvements:
- Add error handling for date parsing
- Internationalize success message
- Consider adding loading state during submission
const handleSubmit = async (e: FormEvent) => { e.preventDefault(); const validForm = validateForm(); if (validForm) { + try { + setIsSubmitting(true); const data = { // ... other fields ... doctor_experience_commenced_on: states.form.user_type === "Doctor" ? dayjs() .subtract( parseInt( (states.form.doctor_experience_commenced_on as string) ?? "0", ), "years", ) .format("YYYY-MM-DD") : undefined, }; const { res } = await request(routes.partialUpdateUser, { pathParams: { username: authUser.username }, body: data, }); if (res?.ok) { Notification.Success({ - msg: "Details updated successfully", + msg: t("details_updated_successfully"), }); await refetchUserData(); setShowEdit(false); } + } catch (error) { + Notification.Error({ + msg: t("error_updating_details"), + }); + } finally { + setIsSubmitting(false); + } } };
Line range hint
544-586
: Improve password change handler implementationThe password change handler needs several improvements:
- Add proper TypeScript typing for the event parameter
- Internationalize error messages
- Add loading state during submission
- Consider using a more structured form validation approach
- const changePassword = async (e: any) => { + const changePassword = async (e: FormEvent) => { e.preventDefault(); + try { + setIsChangingPassword(true); if ( changePasswordForm.new_password_1 !== changePasswordForm.new_password_2 ) { Notification.Error({ - msg: "Passwords are different in new password and confirmation password column.", + msg: t("password_mismatch_error"), }); } else if (!validateNewPassword(changePasswordForm.new_password_1)) { Notification.Error({ - msg: "Entered New Password is not valid, please check!", + msg: t("invalid_password_error"), }); } else if ( changePasswordForm.new_password_1 === changePasswordForm.old_password ) { Notification.Error({ - msg: "New password is same as old password, Please enter a different new password.", + msg: t("same_password_error"), }); } else { const form: UpdatePasswordForm = { old_password: changePasswordForm.old_password, username: authUser.username, new_password: changePasswordForm.new_password_1, }; const { res, data, error } = await request(routes.updatePassword, { body: form, }); if (res?.ok) { Notification.Success({ msg: data?.message }); } else if (!error) { Notification.Error({ - msg: "There was some error. Please try again in some time.", + msg: t("generic_error_message"), }); } setChangePasswordForm({ ...changePasswordForm, new_password_1: "", new_password_2: "", old_password: "", }); } + } catch (error) { + Notification.Error({ + msg: t("password_change_error"), + }); + } finally { + setIsChangingPassword(false); + } };public/locale/en.json (1)
658-660
: Consider enhancing the username validation message.While most validation messages are clear, the username validation message could be more specific about allowed special characters.
Consider updating the message to be more explicit:
- "invalid_username": "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", + "invalid_username": "Required. 150 characters or fewer. Only lowercase letters, numbers, and the special characters @ . + - _ are allowed.",Also applies to: 709-709, 818-818, 1322-1322
src/components/Users/UserAdd.tsx (4)
Line range hint
16-19
: Includerel="noopener noreferrer"
when usingtarget="_blank
to prevent security risks.When using
target="_blank``, adding
rel="noopener noreferrer"prevents the new page from accessing the
window.opener` property, which can pose a security risk. This ensures that the external page cannot manipulate the original page.Apply this diff to fix the issue:
href="https://school.ohc.network/targets/12953" className="inline-block rounded border border-secondary-600 bg-secondary-50 px-4 py-2 text-secondary-600 transition hover:bg-secondary-100" - target="_blank" + target="_blank" rel="noopener noreferrer" >
Line range hint
20-21
: Internationalize the 'Need Help?' text.The text
'Need Help?'
is currently hardcoded and not wrapped in the translation functiont()
. To ensure consistent internationalization across the application, please use the translation function so that the text can be translated into other languages.Apply this diff to fix the issue:
- <CareIcon icon="l-question-circle" className="text-lg" /> Need - Help? + <CareIcon icon="l-question-circle" className="text-lg" /> {t("Need Help?")}
Line range hint
15-23
: Use an<a>
element instead ofLink
for external URLs.The
Link
component fromraviger
is intended for internal routing within the application. When linking to external URLs, it's appropriate to use an<a>
element to ensure proper behavior and accessibility.Apply this diff to fix the issue:
- <Link + <a href="https://school.ohc.network/targets/12953" className="inline-block rounded border border-secondary-600 bg-secondary-50 px-4 py-2 text-secondary-600 transition hover:bg-secondary-100" target="_blank" rel="noopener noreferrer" - > + > <CareIcon icon="l-question-circle" className="text-lg" /> {t("Need Help?")} - </Link> + </a>
1-1
: Remove unused import ofLink
fromraviger
.After replacing the
Link
component with an<a>
element, the import statement is no longer needed and can be removed to keep the code clean.Apply this diff to fix the issue:
-import { Link } from "raviger";
src/components/Facility/FacilityUsers.tsx (2)
28-41
: Ensure robust handling of pagination parametersThe calculation of
offset
depends onqParams.page
. IfqParams.page
is undefined or invalid, it could lead to incorrect offsets. Consider providing a default value to enhance robustness.Apply this diff to provide a default value for
qParams.page
:- (qParams.page ? qParams.page - 1 : 0) * resultsPerPage + ((qParams.page ?? 1) - 1) * resultsPerPage
49-55
: Consider updating the icon inCountBlock
for clarityThe icon
"l-user-injured"
may not best represent the total number of users. Using an icon like"l-users"
might provide better clarity to users.Apply this diff to change the icon:
- icon="l-user-injured" + icon="l-users"src/components/Users/LinkedSkills.tsx (1)
130-141
: Avoid passing empty strings toerrors
propThe
errors
prop is passed as an empty string toSkillSelect
. If there are no errors, it's better to passundefined
or omit the prop entirely to prevent unnecessary rendering or checks within the child component.Apply this diff:
<SkillSelect id="select-skill" multiple={false} name="skill" disabled={!authorizeForAddSkill} showNOptions={Infinity} selected={selectedSkill} setSelected={setSelectedSkill} - errors="" + errors={undefined} className="z-10 w-full" userSkills={skills?.results || []} />src/components/Users/UserQualifications.tsx (3)
179-180
: Localize notification messagesThe success message is hardcoded and not localized. To support internationalization, wrap the message with the
t()
function.- msg: "Details updated successfully", + msg: t("details_updated_successfully"),
220-223
: Add maximum value constraint to the years of experience fieldTo prevent users from entering an invalid number of years of experience, consider adding a
max
attribute to the input field.type="number" min={0} + max={99} label={t("years_of_experience")}
88-91
: Consider storing and handling experience dates directlyCalculating dates by subtracting years from the current date might lead to inaccuracies over time. It's more reliable to store and handle
doctor_experience_commenced_on
as a date rather than calculating it from years of experience.Also applies to: 157-167
src/components/Users/LinkedFacilities.tsx (2)
164-164
: Remove commented-out code to improve code cleanlinessThe lines
//setIsLoading(true);
and//setIsLoading(false);
are commented out. If these lines are no longer needed, consider removing them to keep the codebase clean. If they are placeholders for future functionality, add a TODO comment explaining their purpose.Also applies to: 180-180
184-218
: Refactor render functions to eliminate code duplicationThe functions
renderFacilityButtons
andrenderHomeFacilityButton
have similar structures. Refactoring them into a single function with conditional logic can reduce code duplication and enhance maintainability.Consider combining them as follows:
const renderFacilityButton = (facility: FacilityModel, isHomeFacility: boolean) => { if (!facility) return null; return ( <div id={`facility_${facility.id}`} key={`facility_${facility.id}`}> <div className="flex flex-row items-center rounded-sm border bg-secondary-100"> <div className="rounded p-1 text-sm">{facility.name}</div> <div className="border-l-3 rounded-r bg-secondary-300 px-2 py-1"> {isHomeFacility ? ( <button onClick={() => handleOnClick("clear_home_facility", facility)} title={t("clear_home_facility")} aria-label={t("clear_home_facility")} > <CareIcon icon="l-multiply" className="text-sm" /> </button> ) : ( <DropdownMenu> <DropdownMenuTrigger> <CareIcon icon="l-setting" className="text-sm" /> </DropdownMenuTrigger> <DropdownMenuContent> <DropdownMenuItem onClick={() => handleOnClick( homeFacility ? "replace_home_facility" : "set_home_facility", facility, ) } > {t("set_home_facility")} </DropdownMenuItem> <DropdownMenuItem onClick={() => handleOnClick("unlink_facility", facility)} > {t("unlink_this_facility")} </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> )} </div> </div> </div> ); };Also applies to: 220-240
src/components/Users/UserListAndCard.tsx (6)
268-272
: Simplify conditional rendering for district information.The conditional logic for displaying district information in
UserListRow
can be simplified to improve readability.You can simplify it as follows:
- {"district_object" in user && user.district_object - ? user.district_object?.name - : "district" in user && user.district - ? user.district - : ""} + {user.district_object?.name || user.district || ""}
180-193
: Simplify conditional rendering for district information.The conditional logic for displaying district information in
UserCard
can be simplified for better readability.You can simplify it as follows:
- {"district_object" in user && user.district_object && ( - <div className="text-sm"> - <div className="text-gray-500">{t("district")}</div> - <div className="font-medium"> - {user.district_object.name} - </div> - </div> - )} - {"district" in user && user.district && ( - <div className="text-sm"> - <div className="text-gray-500">{t("district")}</div> - <div className="font-medium">{user.district}</div> - </div> - )} + {(user.district_object?.name || user.district) && ( + <div className="text-sm"> + <div className="text-gray-500">{t("district")}</div> + <div className="font-medium"> + {user.district_object?.name || user.district} + </div> + </div> + )}
162-162
: Use full user name in Avatar component for better display.Currently, the
name
prop for theAvatar
component is set touser.username
on line 162. Consider using the user's full name for a better user experience.Apply this diff:
- name={user.username ?? ""} + name={formatName(user)}
249-249
: Use full user name in Avatar component for better display.Currently, the
name
prop for theAvatar
component is set touser.username
on line 249. Consider using the user's full name for a better user experience.Apply this diff:
- name={user.username ?? ""} + name={formatName(user)}
326-326
: Localize static text 'Card' for internationalization.The text 'Card' on line 326 is not wrapped with the translation function
t
. This may lead to missing translations in other languages.Apply this diff:
- <span>Card</span> + <span>{t("card")}</span>
336-336
: Localize static text 'List' for internationalization.The text 'List' on line 336 is not wrapped with the translation function
t
. This may lead to missing translations in other languages.Apply this diff:
- <span>List</span> + <span>{t("list")}</span>src/components/Users/ManageUsers.tsx (2)
Line range hint
109-127
: Simplify component rendering by eliminating unnecessary variableThe use of the
manageUsers
variable adds unnecessary complexity. Consider rendering the user list components directly within the return statement to improve readability and maintainability.You can refactor the code as follows:
- let manageUsers: JSX.Element = <></>; - if (userListLoading || districtDataLoading || !userListData?.results) { - return <Loading />; - } - if (userListData?.results.length) { - manageUsers = ( - <div> - <UserListView - users={userListData?.results ?? []} - onSearch={(username) => updateQuery({ username })} - searchValue={qParams.username} - /> - <Pagination totalCount={userListData.count} /> - </div> - ); - } else if (userListData?.results && userListData?.results.length === 0) { - manageUsers = ( - <div> - <div className="h-full space-y-2 rounded-lg bg-white p-7 shadow"> - <div className="flex w-full items-center justify-center text-xl font-bold text-secondary-500"> - No Users Found - </div> - </div> - </div> - ); - } - return ( - <Page title={t("user_management")} hideBack={true} breadcrumbs={false}> - {/* ...other components... */} - <div>{manageUsers}</div> - </Page> - ); + if (userListLoading || districtDataLoading || !userListData?.results) { + return <Loading />; + } + return ( + <Page title={t("user_management")} hideBack={true} breadcrumbs={false}> + {/* ...other components... */} + {userListData?.results.length ? ( + <div> + <UserListView + users={userListData?.results ?? []} + onSearch={(username) => updateQuery({ username })} + searchValue={qParams.username} + /> + <Pagination totalCount={userListData.count} /> + </div> + ) : ( + <div> + <div className="h-full space-y-2 rounded-lg bg-white p-7 shadow"> + <div className="flex w-full items-center justify-center text-xl font-bold text-secondary-500"> + {t("no_users_found")} + </div> + </div> + </div> + )} + </Page> + );
Line range hint
124-127
: Internationalize the 'No Users Found' messageThe text
'No Users Found'
is hardcoded and not internationalized. Use the translation function to support multiple languages and improve accessibility.Apply this diff to internationalize the text:
- No Users Found + {t("no_users_found")}Also, ensure the translation key
'no_users_found'
is added to the localization files.src/components/Users/UserAddEditForm.tsx (7)
182-182
: Simplify boolean assignment of 'editUser'.Instead of using a ternary operator, you can directly convert
username
to a boolean.Apply this diff to simplify the assignment:
- const editUser = username ? true : false; + const editUser = !!username;🧰 Tools
🪛 Biome
[error] 182-182: Unnecessary use of boolean literals in conditional expression.
Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with(lint/complexity/noUselessTernary)
305-305
: Correct the typo in the error message.The word "availabality" should be corrected to "availability".
Apply this diff to fix the typo:
Notification.Error({ - msg: "Some error checking username availabality. Please try again later.", + msg: "Some error checking username availability. Please try again later.", });
480-480
: Fix the typo in the validation message."Atleast" should be "at least" in the error message.
Apply this diff to correct the message:
- return "Please select atleast one of the facilities you are linked to"; + return "Please select at least one of the facilities you are linked to";
883-952
: Remove unnecessary fragment around a single child element.The fragment wrapping the
<div>
is redundant since it contains only a single child. Removing it simplifies the JSX.Apply this diff to remove the unnecessary fragment:
- <> <div className="text-small pl-2 text-secondary-500"> {/* ... */} </div> - </>🧰 Tools
🪛 Biome
[error] 886-920: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment(lint/complexity/noUselessFragments)
956-1023
: Eliminate redundant fragment wrapping a single element.The fragment here is unnecessary and can be removed to improve code clarity.
Apply this diff:
- <> <div className="flex flex-col justify-between gap-x-3 sm:flex-row"> {/* ... */} </div> - </>🧰 Tools
🪛 Biome
[error] 956-1023: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment(lint/complexity/noUselessFragments)
1082-1105
: Remove unnecessary fragment to simplify JSX structure.Since the fragment contains only one child, it can be omitted for cleaner code.
Apply this diff:
- <> <div className="flex flex-col justify-between gap-x-3 sm:flex-row"> {/* ... */} </div> - </>🧰 Tools
🪛 Biome
[error] 1082-1105: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment(lint/complexity/noUselessFragments)
883-952
: Refactor validation rule displays into a reusable component.The code for displaying username and password validation rules is similar. Extracting this into a separate component reduces duplication and enhances maintainability.
Also applies to: 972-997
🧰 Tools
🪛 Biome
[error] 886-920: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment(lint/complexity/noUselessFragments)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (33)
public/locale/en.json
(25 hunks)src/Routers/routes/UserRoutes.tsx
(1 hunks)src/Utils/request/api.tsx
(1 hunks)src/components/ABDM/LinkAbhaNumber/CreateWithAadhaar.tsx
(1 hunks)src/components/Auth/ResetPassword.tsx
(1 hunks)src/components/Common/Page.tsx
(2 hunks)src/components/Common/PageTitle.tsx
(3 hunks)src/components/Common/SkillSelect.tsx
(2 hunks)src/components/Common/UserColumns.tsx
(1 hunks)src/components/Common/UserDetails.tsx
(1 hunks)src/components/Facility/FacilityUsers.tsx
(2 hunks)src/components/Form/Form.tsx
(5 hunks)src/components/Users/ConfirmFacilityModal.tsx
(1 hunks)src/components/Users/ConfirmHomeFacilityUpdateDialog.tsx
(0 hunks)src/components/Users/LinkFacilityDialog.tsx
(0 hunks)src/components/Users/LinkedFacilities.tsx
(1 hunks)src/components/Users/LinkedFacilitiesTab.tsx
(1 hunks)src/components/Users/LinkedSkills.tsx
(1 hunks)src/components/Users/ManageUsers.tsx
(4 hunks)src/components/Users/RoleAndSkillsTab.tsx
(1 hunks)src/components/Users/SkillsSlideOver.tsx
(0 hunks)src/components/Users/SkillsSlideOverComponents.tsx
(0 hunks)src/components/Users/UnlinkSkillDialog.tsx
(2 hunks)src/components/Users/UserAdd.tsx
(2 hunks)src/components/Users/UserAddEditForm.tsx
(1 hunks)src/components/Users/UserBanner.tsx
(1 hunks)src/components/Users/UserHome.tsx
(1 hunks)src/components/Users/UserInformation.tsx
(1 hunks)src/components/Users/UserListAndCard.tsx
(1 hunks)src/components/Users/UserProfile.tsx
(1 hunks)src/components/Users/UserQualifications.tsx
(1 hunks)src/components/Users/UserResetPassword.tsx
(1 hunks)src/components/Users/UserSummary.tsx
(1 hunks)
💤 Files with no reviewable changes (4)
- src/components/Users/ConfirmHomeFacilityUpdateDialog.tsx
- src/components/Users/LinkFacilityDialog.tsx
- src/components/Users/SkillsSlideOver.tsx
- src/components/Users/SkillsSlideOverComponents.tsx
✅ Files skipped from review due to trivial changes (2)
- src/components/Auth/ResetPassword.tsx
- src/components/Common/UserDetails.tsx
🧰 Additional context used
🪛 Biome
src/components/Users/UserAddEditForm.tsx
[error] 182-182: Unnecessary use of boolean literals in conditional expression.
Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with
(lint/complexity/noUselessTernary)
[error] 619-619: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.
See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.
(lint/suspicious/noGlobalIsNan)
[error] 886-920: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment
(lint/complexity/noUselessFragments)
[error] 956-1023: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment
(lint/complexity/noUselessFragments)
[error] 1082-1105: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment
(lint/complexity/noUselessFragments)
src/components/Users/UserBanner.tsx
[error] 34-133: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment
(lint/complexity/noUselessFragments)
🔇 Additional comments (36)
src/Routers/routes/UserRoutes.tsx (1)
2-3
: LGTM! Import changes are well-structured.
The import statements follow React conventions and align with the user management redesign objectives.
src/components/Users/LinkedFacilitiesTab.tsx (1)
1-32
: Verify integration with user details page.
The component implementation aligns with PR objectives for user management redesign. However, let's verify its integration with the user details page.
✅ Verification successful
Integration with user details page verified successfully
The LinkedFacilitiesTab component is properly integrated in UserHome.tsx as one of the main tabs (FACILITIES) alongside PROFILE and SKILLS tabs, which aligns with the user management redesign objectives. The implementation and integration are correct.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check how LinkedFacilitiesTab is integrated into the user details page
rg -l "LinkedFacilitiesTab" --type tsx
Length of output: 66
Script:
#!/bin/bash
# Check how LinkedFacilitiesTab is integrated using ripgrep without file type filter
rg -l "LinkedFacilitiesTab"
Length of output: 106
Script:
#!/bin/bash
# Check the integration in UserHome.tsx
rg "LinkedFacilitiesTab" src/components/Users/UserHome.tsx -A 5 -B 5
Length of output: 956
src/components/Users/UnlinkSkillDialog.tsx (1)
15-15
: Verify translation key definitions.
Let's ensure all translation keys used in the component are properly defined in the localization files.
Also applies to: 24-38
✅ Verification successful
All translation keys are properly defined in the localization file
All translation keys used in the UnlinkSkillDialog component (unlink_skill
, unlink_skill_confirm
, from_user
, unlink_skill_access
) are correctly defined in the English locale file.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check if all translation keys are defined in the English locale file
# Keys to check
KEYS=("unlink_skill" "unlink_skill_confirm" "from_user" "unlink_skill_access")
# Check each key in the locale file
for key in "${KEYS[@]}"; do
echo "Checking key: $key"
if ! rg "\"$key\":" public/locale/en.json; then
echo "Warning: Translation key '$key' not found!"
fi
done
Length of output: 866
src/components/Users/RoleAndSkillsTab.tsx (2)
1-11
: LGTM! Well-structured imports and type definitions.
The imports are well-organized and the Props type is properly defined with required fields, which aligns well with the component's guard clause.
21-41
: 🛠️ Refactor suggestion
Enhance type safety for user type checks.
The current string literal comparison for user types could be improved using TypeScript enums or constants for better type safety and maintainability.
+// Add to UserModel.ts or a constants file
+export enum UserType {
+ Doctor = "Doctor",
+ Nurse = "Nurse",
+}
return (
<>
<div className="mt-10 flex flex-col gap-y-12">
{userData.user_type &&
- ["Doctor", "Nurse"].includes(userData.user_type) &&
+ Object.values(UserType).includes(userData.user_type as UserType) &&
userColumns(
t("user_qualifications"),
t("user_qualifications_note"),
UserQualifications,
props,
)}
Additionally, verify that these user types are consistently used across the application.
src/components/Common/Page.tsx (1)
55-55
: Verify the relationship between hideTitleOnPage
and hideBack
props.
The component already has a hideBack
prop that affects padding when the back button is hidden. Please clarify how these props interact and whether they serve distinct purposes.
Let's check the PageTitle component implementation to understand the difference:
✅ Verification successful
The hideBack
and hideTitleOnPage
props serve distinct purposes
Based on the implementation in PageTitle.tsx:
hideBack
is passed to theBreadcrumbs
component to control the visibility of the back button/navigationhideTitleOnPage
controls the visibility of the page title (h2
element) in the main content area- These props are independent and do not interact with each other
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Analyze the relationship between hideTitleOnPage and hideBack props
# Expected: Find the implementation showing distinct purposes for these props
# Search for the PageTitle component implementation
ast-grep --pattern 'export default function PageTitle($$$) {
$$$
}'
# Search for usage patterns of both props
rg -A 5 'hideTitleOnPage|hideBack' src/components/Common/PageTitle.tsx
Length of output: 5285
src/components/Common/PageTitle.tsx (3)
22-22
: LGTM! Well-structured prop addition.
The new prop follows TypeScript and React naming conventions, and its optional nature is appropriate for backward compatibility.
39-39
: LGTM! Clean props destructuring.
The prop destructuring is consistent with the interface definition and maintains the logical grouping of display-related props.
75-77
: Consider accessibility implications when hiding the title.
While the conditional rendering works correctly, hiding the h2 element might affect the page's heading hierarchy and accessibility. Consider:
- Providing an alternative heading for screen readers when the visible title is hidden
- Ensuring the page maintains a logical heading structure
Consider this alternative implementation that maintains accessibility:
- {!hideTitleOnPage && (
- <h2 className="ml-0 text-2xl leading-tight">{title}</h2>
- )}
+ <h2
+ className={classNames(
+ "ml-0 text-2xl leading-tight",
+ hideTitleOnPage && "sr-only"
+ )}
+ >
+ {title}
+ </h2>
This approach keeps the heading in the document structure while visually hiding it when needed.
✅ Verification successful
Based on the search results, I can now generate the final response as I have enough context about the heading structure in the codebase.
Hiding the title is safe from an accessibility perspective
The conditional rendering of the title in PageTitle.tsx
is acceptable because:
- The component is used consistently as a secondary heading (h2)
- Other components maintain their own heading hierarchy independently
- The title hiding is likely used in specific UI scenarios where the same information is presented through other means
While the suggested sr-only
approach is a good practice, the current implementation doesn't pose significant accessibility concerns as it's a controlled hide/show of a secondary heading that doesn't break document outline.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check if there are other headings that might be affected by hiding this h2
rg -g '*.tsx' -g '*.jsx' '<h[1-6]' --type-add 'tsx:*.tsx' --type-add 'jsx:*.jsx'
Length of output: 22902
src/components/Users/ConfirmFacilityModal.tsx (2)
79-79
: LGTM!
The export statement follows React component conventions.
1-79
: Overall implementation aligns well with PR objectives.
The component successfully implements the facility management confirmation dialogs needed for the user management redesign. It provides clear user feedback and handles different facility-related actions appropriately.
Some suggestions for improvement have been provided above, but the core functionality is solid and meets the requirements.
Let's verify the translation keys are properly defined:
✅ Verification successful
Translation keys are properly defined and accessible
The verification confirms that all required translation keys used in the ConfirmFacilityModal component are present in the locale file (public/locale/en.json), ensuring proper internationalization support for all facility management actions.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify that all translation keys used in the component exist in the locale file
# Test: Check if all required translation keys exist
rg -q "unlink_facility|unlink_facility_confirm|unlink_facility_access|clear_home_facility_confirm|replace_home_facility_confirm|replace_home_facility_confirm_as|from_user|with" "public/locale/en.json" && echo "All translation keys found" || echo "Missing translation keys"
Length of output: 263
src/components/Users/UserSummary.tsx (2)
1-20
: LGTM! Well-organized imports
The imports are properly organized with clear separation between external dependencies and internal modules.
47-48
: Verify user permission handling
Let's verify that the permission checks align with the PR objectives.
src/components/Users/UserInformation.tsx (2)
1-19
: LGTM! Well-organized imports with clear separation of concerns.
The imports are properly organized and all dependencies are relevant to the component's functionality.
1-117
: Well-implemented component that aligns with PR objectives.
The UserInformation component successfully contributes to the user management redesign by providing a clean and functional interface for managing user profiles and avatars. It aligns well with the objectives outlined in Issue #8878.
src/components/Form/Form.tsx (2)
134-134
: LGTM! Handler update is correct.
The Cancel button's onClick handler is properly updated to use the new handleCancel function.
47-47
: Verify handling of props.defaults changes.
The useRef initialization looks good, but consider how changes to props.defaults
after initial render might affect the reset behavior.
Consider using useEffect to update formVals.current when props.defaults changes:
const formVals = useRef(props.defaults);
+useEffect(() => {
+ formVals.current = props.defaults;
+}, [props.defaults]);
src/components/Users/UserBanner.tsx (1)
103-113
: LGTM: Role-specific information display.
The conditional rendering of qualifications for Doctors and Nurses, along with Doctor-specific experience information, aligns well with the PR objectives for role-specific displays.
Also applies to: 114-129
src/components/Users/UserResetPassword.tsx (2)
1-19
: LGTM! Well-structured component setup.
The imports are properly organized and the component interface is well-defined with appropriate TypeScript types.
13-17
: Verify user permissions for password changes.
According to the PR objectives, password changes should be restricted to logged-in users and District Admins or higher. However, the current implementation doesn't include these permission checks.
Let's verify the permission implementation:
src/components/ABDM/LinkAbhaNumber/CreateWithAadhaar.tsx (2)
13-13
: LGTM! Good refactoring of validation rules.
Moving validateRule
to UserAddEditForm
promotes code reuse and maintains consistency across user-related forms.
Line range hint 32-38
: Consider enhancing security measures for sensitive data handling.
While basic security measures are in place, consider implementing:
- UI masking for verified mobile numbers
- Rate limiting for OTP attempts beyond the current count-based limit
- Session-based validation for transaction IDs
Let's verify the current security measures:
Also applies to: 89-91
src/components/Users/UserProfile.tsx (1)
20-20
: LGTM: Import path update is correct
The import path change from UserAdd to UserAddEditForm aligns with the refactoring objectives.
src/Utils/request/api.tsx (1)
1046-1049
: LGTM! The endpoint change is consistent with the codebase.
The change to a static endpoint for getUserDetails
is appropriate while maintaining other user-specific endpoints that correctly use the username parameter. This change aligns with the PR objectives for user management redesign.
public/locale/en.json (6)
211-214
: LGTM! Well-structured tab labels.
The new user management tab labels follow a consistent naming pattern and provide clear, hierarchical navigation.
393-394
: LGTM! Clear and context-aware profile management labels.
The labels appropriately distinguish between self-update and admin-update scenarios, and provide helpful guidance for avatar uploads.
Also applies to: 1019-1020
460-460
: LGTM! Comprehensive password-related messages.
The messages provide clear validation requirements, user-friendly error messages, and helpful instructions for password management.
Also applies to: 980-981, 1027-1028, 1129-1129
829-829
: LGTM! Clear and cautionary facility management messages.
The messages effectively communicate the implications of facility management actions and maintain consistent terminology.
Also applies to: 1110-1112, 1279-1281
831-831
: LGTM! Consistent skill management messages.
The messages follow the same clear pattern as facility management, effectively communicating actions and their consequences.
Also applies to: 1282-1284
1356-1356
: LGTM! Clear weekly hours validation message.
The message clearly defines the valid range using the logical maximum of 168 hours (24*7).
src/components/Users/UserAdd.tsx (1)
Line range hint 8-29
: Code implementation looks good.
The UserAdd
component is well-structured and correctly renders the UserAddEditForm
within the Page
component. The use of useTranslation
for the title is appropriate, and the component follows good React practices.
src/components/Facility/FacilityUsers.tsx (4)
13-13
: Good use of explicit prop typing
Explicitly typing props
as { facilityId: number }
enhances type safety and improves code readability.
15-18
: Confirm the cacheBlacklist
configuration in useFilters
Using cacheBlacklist: ["username"]
prevents the username
parameter from being cached between navigations. Please verify if this aligns with the intended behavior.
57-61
: Verify that onSearch
correctly updates the user list
Ensure that the onSearch
handler updates the query parameters and triggers a re-fetch of the user list when the search input changes.
63-63
: Confirm the functionality of the Pagination
component
Check that the Pagination
component interacts properly with qParams.page
and updateQuery
to handle page changes and that it correctly updates the user list.
src/components/Users/LinkedSkills.tsx (1)
56-58
: Verify the usage of AuthorizeFor
within useIsAuthorized
Ensure that AuthorizeFor
is correctly used as an argument for useIsAuthorized
. Depending on its implementation, AuthorizeFor
might not be necessary here, or you might need to pass the roles array directly to useIsAuthorized
.
Run the following script to inspect the implementations of useIsAuthorized
and AuthorizeFor
:
This script will help determine if AuthorizeFor
is appropriately used within useIsAuthorized
and whether any adjustments are necessary.
✅ Verification successful
Let me gather more information about the implementation details.
Let me check one more thing to see how AuthorizeFor is implemented since we didn't get its implementation in the previous search.
Let me try one final search pattern to find the AuthorizeFor implementation.
AuthorizeFor
is correctly used with useIsAuthorized
The implementation shows that:
AuthorizeFor
is a function that takes an array ofUserRole
s and returns a callback function matching theAuthorizedForCB
typeuseIsAuthorized
expects a parameter of typeAuthorizedForCB
, which is exactly whatAuthorizeFor(["DistrictAdmin", "StateAdmin"])
returns- This pattern is consistently used across the codebase in multiple components (e.g., LocationManagement.tsx, BedManagement.tsx)
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the correct usage of `useIsAuthorized` and `AuthorizeFor`.
# Test: Find definitions of `useIsAuthorized` and `AuthorizeFor`.
# Expect: Confirm whether `AuthorizeFor` should be used as an argument to `useIsAuthorized`.
# Search for the definition of `useIsAuthorized`
fd --extension ts --type f | xargs rg 'export function useIsAuthorized'
# Search for the definition of `AuthorizeFor`
fd --extension ts --type f | xargs rg 'export default function AuthorizeFor'
# Search for examples where `useIsAuthorized` is used
rg --type ts 'useIsAuthorized'
# Search for examples where `AuthorizeFor` is used
rg --type ts 'AuthorizeFor'
Length of output: 4086
Script:
#!/bin/bash
# Get the implementation of useIsAuthorized hook
rg -A 10 "export const useIsAuthorized" src/hooks/useIsAuthorized.ts
# Get the implementation of AuthorizeFor
rg -A 10 "export default function AuthorizeFor" src/Utils/AuthorizeFor.tsx
# Get the type definition of AuthorizedForCB
rg "type AuthorizedForCB" src/Utils/AuthorizeFor.tsx
Length of output: 413
Script:
#!/bin/bash
# Search for AuthorizeFor function implementation with a broader pattern
rg -A 10 "AuthorizeFor.*=.*\[" src/Utils/AuthorizeFor.tsx
# Alternative search pattern
rg -A 10 "function AuthorizeFor" src/Utils/AuthorizeFor.tsx
Length of output: 121
Script:
#!/bin/bash
# Try to find any content in AuthorizeFor.tsx
cat src/Utils/AuthorizeFor.tsx
# Alternative search for exports from this file
rg "export" src/Utils/AuthorizeFor.tsx
Length of output: 1620
@Jacobjeevan check the test report, clear the conflict and attach a screenshot cypress passing in local |
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: 2
🧹 Outside diff range and nitpick comments (21)
src/components/Form/Form.tsx (2)
44-44
: Consider a more descriptive name for the formVals ref.While the implementation is correct, consider renaming
formVals
to something more descriptive likeinitialFormValues
to better convey its purpose of storing the form's initial state.- const formVals = useRef(props.defaults); + const initialFormValues = useRef(props.defaults);Also applies to: 50-50
Line range hint
1-156
: Consider splitting form functionality into smaller, focused components.The Form component handles multiple responsibilities including form state management, validation, submission, drafts, and now reset behavior. Consider:
- Extracting the form reset logic into a custom hook (e.g.,
useFormReset
)- Splitting the draft functionality into a separate component
- Creating a dedicated form state management hook
This would improve maintainability and make the component more focused on its core responsibility of form rendering and coordination.
src/components/Users/LinkedFacilities.tsx (5)
46-51
: Consider consolidating related state variablesThe component maintains separate state variables for different aspects of facility management. Consider consolidating them into a single state object for better maintainability and state updates.
- const [facility, setFacility] = useState<any>(null); - const [userFacilities, setUserFacilities] = useState<FacilityModel[] | null>(); - const [homeFacility, setHomeFacility] = useState<FacilityModel | undefined>(); - const [modalProps, setModalProps] = useState(initModalProps); + const [facilityState, setFacilityState] = useState({ + selectedFacility: null as any, + userFacilities: null as FacilityModel[] | null, + homeFacility: undefined as FacilityModel | undefined, + modalProps: initModalProps + });
61-79
: Optimize data processing logic and document the facility limitThe facility data processing could be simplified, and the arbitrary limit of 36 facilities should be documented or made configurable.
const { refetch: refetchUserFacilities } = useQuery(routes.userListFacility, { pathParams: { username: userData.username }, - query: { limit: 36 }, + query: { limit: FACILITY_LIST_LIMIT }, onResponse({ res, data }) { if (res?.status === 200 && data) { - let userFacilities = data?.results; - if (userData.home_facility_object) { - const homeFacility = data?.results.find( - (facility) => facility.id === userData.home_facility_object?.id, - ); - userFacilities = userFacilities.filter( - (facility) => facility.id !== homeFacility?.id, - ); - setHomeFacility(homeFacility); - } - setUserFacilities(userFacilities); + const facilities = data.results; + const homeFacility = userData.home_facility_object + ? facilities.find(f => f.id === userData.home_facility_object?.id) + : undefined; + setHomeFacility(homeFacility); + setUserFacilities(facilities.filter(f => f.id !== homeFacility?.id)); } }, });Consider adding this constant at the top of the file:
const FACILITY_LIST_LIMIT = 36; // Maximum number of facilities to display
81-96
: Add type safety to facility action handlersThe facility action types should be defined as an enum or union type for better type safety and maintainability.
+type FacilityActionType = + | "clear_home_facility" + | "unlink_facility" + | "replace_home_facility" + | "set_home_facility"; -const handleOnClick = (type: string, selectedFacility: FacilityModel) => { +const handleOnClick = (type: FacilityActionType, selectedFacility: FacilityModel) => {
117-194
: Add loading states for API operationsThe API operations should indicate their loading state to provide better user feedback.
+ const [isLoading, setIsLoading] = useState({ + link: false, + unlink: false, + homeFacility: false + }); const replaceHomeFacility = async (facility?: FacilityModel) => { + setIsLoading(prev => ({ ...prev, homeFacility: true })); try { const selectedFacility = facility ?? modalProps.selectedFacility; // ... existing code } finally { + setIsLoading(prev => ({ ...prev, homeFacility: false })); } };Then update the UI to show loading indicators:
<ButtonV2 onClick={() => linkFacility(userData.username, facility)} disabled={!authorizeForHomeFacility} + loading={isLoading.link} >
196-266
: Extract facility button components for better maintainabilityThe facility button rendering logic could be extracted into separate components to reduce complexity and improve reusability.
type FacilityButtonProps = { facility: FacilityModel; onAction: (type: FacilityActionType, facility: FacilityModel) => void; isHome?: boolean; canManage?: boolean; }; const FacilityButton: React.FC<FacilityButtonProps> = ({ facility, onAction, isHome, canManage }) => { const { t } = useTranslation(); if (isHome) { return <HomeFacilityButton {...props} />; } return <LinkedFacilityButton {...props} />; };src/components/Users/UserListAndCard.tsx (2)
63-70
: Improve button accessibility with more descriptive text.The button text "more details" is too generic for screen readers. Include the user's name in the accessible text while keeping the visual text concise.
<button id={`more-details-${username}`} onClick={() => navigate(`/users/${username}`)} className="flex flex-grow-0 items-center gap-2 rounded-lg bg-gray-100 px-3 py-2 text-xs hover:bg-gray-200" + aria-label={t("more_details_for_user", { username })} > <CareIcon icon="l-arrow-up-right" className="text-lg" /> <span>{t("more_details")}</span> </button>
369-369
: Consider memoizing the search callback.The search callback function is recreated on every render. Consider using useCallback to optimize performance, especially if the parent component is complex.
+ const handleSearch = useCallback((e: { value: string }) => onSearch(e.value), [onSearch]); + return ( // ... - onChange={(e) => onSearch(e.value)} + onChange={handleSearch}src/components/Users/ManageUsers.tsx (3)
106-108
: Consider implementing a loading skeletonInstead of showing a full-page loading state, consider implementing a skeleton loader to prevent layout shifts and improve perceived performance.
- return <Loading />; + return ( + <div className="animate-pulse"> + <div className="h-8 w-1/4 bg-gray-200 rounded"></div> + <div className="mt-4 grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3"> + {[...Array(6)].map((_, i) => ( + <div key={i} className="h-32 bg-gray-200 rounded"></div> + ))} + </div> + </div> + );
Line range hint
190-199
: Consider using useReducer for complex state managementThe component uses multiple useState hooks that are closely related. Consider using useReducer to manage related state in a more maintainable way.
type State = { isLoading: boolean; currentPage: number; offset: number; totalCount: number; facility: any; unlinkFacilityData: { show: boolean; userName: string; facility?: FacilityModel; isHomeFacility: boolean; }; }; type Action = | { type: 'SET_LOADING'; payload: boolean } | { type: 'SET_PAGE'; payload: { page: number; limit: number } } | { type: 'SET_FACILITY'; payload: any } | { type: 'SET_UNLINK_DATA'; payload: Partial<State['unlinkFacilityData']> };
Line range hint
276-422
: Consider splitting facility management into separate componentsThe UserFacilities component handles both home facility and linked facilities. Consider splitting these into separate components for better maintainability and reusability.
- export function UserFacilities(props: { user: any }) { + export function HomeFacility({ user, onUpdate }: { user: any; onUpdate: () => void }) { + // Home facility specific logic + } + + export function LinkedFacilities({ user, onUpdate }: { user: any; onUpdate: () => void }) { + // Linked facilities specific logic + } + + export function UserFacilities(props: { user: any }) { + return ( + <> + <HomeFacility user={props.user} onUpdate={refetchUserFacilities} /> + <LinkedFacilities user={props.user} onUpdate={refetchUserFacilities} /> + </> + ); + }src/components/Users/UserAddEditForm.tsx (5)
131-158
: Separate validation logic from UI rendering.The
validateRule
function combines validation logic with UI rendering. Consider splitting these concerns for better maintainability and testability.+interface ValidationResult { + isValid: boolean; + isInitial: boolean; + content: string | JSX.Element; +} +const validateRule = ( + condition: boolean, + content: JSX.Element | string, + isInitialState = false +): ValidationResult => ({ + isValid: condition, + isInitial: isInitialState, + content +}); +const ValidationIcon: React.FC<Omit<ValidationResult, 'content'>> = ({ isValid, isInitial }) => { + if (isInitial) return <CareIcon icon="l-circle" className="text-xl text-gray-500" />; + return isValid ? + <CareIcon icon="l-check-circle" className="text-xl text-green-500" /> : + <CareIcon icon="l-times-circle" className="text-xl text-red-500" />; +}; +const ValidationMessage: React.FC<ValidationResult> = ({ isValid, isInitial, content }) => ( + <div> + <ValidationIcon isValid={isValid} isInitial={isInitial} /> + <span className={classNames( + isInitial ? "text-black" : isValid ? "text-primary-500" : "text-red-500" + )}> + {content} + </span> + </div> +);
282-300
: Enhance error handling in form submission.The error handling in
handleEditSubmit
could be more specific and provide better error messages for different failure scenarios.const handleEditSubmit = async (formData: UserForm) => { if (!username) return; const data = prepData(formData); + try { const { res, error } = await request(routes.partialUpdateUser, { pathParams: { username }, body: data as Partial<UserModel>, }); if (res?.ok) { Notification.Success({ msg: t("user_details_update_success"), }); await refetchUserData(); } else { + const errorMessage = error?.response?.status === 409 + ? t("user_details_conflict") + : error?.message ?? t("user_details_update_error"); Notification.Error({ - msg: error?.message ?? t("user_details_update_error"), + msg: errorMessage, }); } + } catch (error) { + Notification.Error({ + msg: t("network_error"), + }); + } props.onSubmitSuccess?.(); };
467-517
: Simplify phone number handling logic using a reducer pattern.The
handlePhoneChange
function is complex with multiple state updates. Consider using a reducer for cleaner state management.+type PhoneAction = + | { type: 'UPDATE_PHONE'; value: string } + | { type: 'UPDATE_ALT_PHONE'; value: string } + | { type: 'TOGGLE_WHATSAPP'; value: boolean }; +const phoneReducer = (state: UserForm, action: PhoneAction): Partial<UserForm> => { + switch (action.type) { + case 'UPDATE_PHONE': + return { + phone_number: action.value, + ...(state.phone_number_is_whatsapp && { alt_phone_number: action.value }) + }; + case 'UPDATE_ALT_PHONE': + return { alt_phone_number: action.value }; + case 'TOGGLE_WHATSAPP': + return { + phone_number_is_whatsapp: action.value, + alt_phone_number: action.value ? state.phone_number : state.alt_phone_number + }; + } +}; const handlePhoneChange = (event: FieldChangeEvent<unknown>, field: FormContextValue<UserForm>) => { - let formData = { ...state.form }; + const updates = phoneReducer(state.form, { + type: event.name === 'phone_number' ? 'UPDATE_PHONE' : + event.name === 'alt_phone_number' ? 'UPDATE_ALT_PHONE' : 'TOGGLE_WHATSAPP', + value: event.value as string | boolean + }); + Object.entries(updates).forEach(([key, value]) => { + changePhoneNumber(field, key as keyof UserForm, value); + }); dispatch({ type: "set_form", - form: formData, + form: { ...state.form, ...updates }, }); };
554-701
: Modularize form validation logic for better maintainability.The
validateForm
function is quite long and handles multiple concerns. Consider breaking it down into smaller, focused validation rules.+const validationRules: Record<keyof UserForm, (form: UserForm) => string | undefined> = { + user_type: (form) => !form.user_type ? t("please_select_user_type") : undefined, + qualification: (form) => ValidateQualification(form, t), + phone_number: (form) => + !form.phone_number || !validatePhoneNumber(form.phone_number) + ? t("invalid_phone") + : undefined, + // ... other rules +}; const validateForm = (formData: UserForm) => { const errors: Partial<Record<keyof UserForm, string>> = {}; const fieldsToValidate = includedFields || Object.keys(formData); + + fieldsToValidate.forEach((field) => { + const rule = validationRules[field as keyof UserForm]; + if (rule) { + const error = rule(formData); + if (error) errors[field as keyof UserForm] = error; + } + }); return errors; };
1133-1196
: Remove unnecessary Fragment wrappers.Several sections of the code use unnecessary Fragment wrappers that can be removed to improve code clarity.
-<> {isStateLoading ? ( <CircularProgress /> ) : ( <SelectFormField {...field("state")} // ... props /> )} -</> -<> {isDistrictLoading ? ( <CircularProgress /> ) : ( <SelectFormField {...field("district")} // ... props /> )} -</> -<> {isLocalbodyLoading ? ( <CircularProgress /> ) : ( <SelectFormField {...field("local_body")} // ... props /> )} -</>🧰 Tools
🪛 Biome (1.9.4)
[error] 1133-1152: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment(lint/complexity/noUselessFragments)
[error] 1155-1174: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment(lint/complexity/noUselessFragments)
[error] 1177-1196: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment(lint/complexity/noUselessFragments)
public/locale/en.json (4)
534-537
: Improve consistency in profile section notes.The notes for different profile sections have slight inconsistencies in wording. Consider standardizing the format:
- "contact_info_note": "View or update user's contact information", - "contact_info_note_self": "View or update your contact information", - "contact_info_note_view": "View user's contact information", - "personal_information_note": "View or update user's personal information", - "personal_information_note_self": "View or update your personal information", - "personal_information_note_view": "View user's personal information", + "contact_info_note": "View and update user's contact information", + "contact_info_note_self": "View and update your contact information", + "contact_info_note_view": "View user's contact information", + "personal_information_note": "View and update user's personal information", + "personal_information_note_self": "View and update your personal information", + "personal_information_note_view": "View user's personal information"Also applies to: 1134-1136
420-421
: Enhance avatar file size limit message.The file size limit message could be more specific to help users understand the exact limit.
- "change_avatar_note": "JPG, GIF or PNG. 1MB max.", + "change_avatar_note": "Accepted formats: JPG, GIF, or PNG. Maximum file size: 1MB",Also applies to: 661-663
1436-1442
: Enhance facility management error messages.The error messages could provide more specific guidance to users.
- "unlink_facility_error": "Error while unlinking facility. Try again later.", + "unlink_facility_error": "Failed to unlink facility. Please check your permissions and try again.", - "unlink_home_facility_error": "Error while unlinking home facility. Try again later.", + "unlink_home_facility_error": "Failed to clear home facility. Please ensure the user has no active assignments and try again."
1443-1447
: Improve skill management messages.The skill management messages could be more descriptive and helpful.
- "unlink_skill": "Unlink Skill", + "unlink_skill": "Remove Skill", - "unlink_skill_access": "The user will not have the skill associated anymore.", + "unlink_skill_access": "This will remove the skill from the user's profile. Any assignments requiring this skill may be affected.", - "unlink_skill_error": "Error while unlinking skill. Try again later.", + "unlink_skill_error": "Failed to remove skill. Please check if the skill is required for any current assignments and try again."
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (10)
public/locale/en.json
(39 hunks)src/components/Common/Page.tsx
(2 hunks)src/components/Form/Form.tsx
(4 hunks)src/components/Users/LinkedFacilities.tsx
(1 hunks)src/components/Users/ManageUsers.tsx
(3 hunks)src/components/Users/UserAddEditForm.tsx
(1 hunks)src/components/Users/UserBanner.tsx
(1 hunks)src/components/Users/UserListAndCard.tsx
(1 hunks)src/components/Users/UserProfile.tsx
(1 hunks)src/components/Users/UserResetPassword.tsx
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- src/components/Common/Page.tsx
- src/components/Users/UserBanner.tsx
- src/components/Users/UserProfile.tsx
- src/components/Users/UserResetPassword.tsx
🧰 Additional context used
📓 Learnings (3)
src/components/Users/LinkedFacilities.tsx (1)
Learnt from: Jacobjeevan
PR: ohcnetwork/care_fe#9080
File: src/components/Users/LinkedFacilities.tsx:42-42
Timestamp: 2024-11-12T12:46:55.920Z
Learning: In the `LinkedFacilities` component (`src/components/Users/LinkedFacilities.tsx`), when using `FacilitySelect` which supports an array of `FacilityModel` items, and restricting selection to just one, it's acceptable to keep the `facility` state typed as `any` to maintain compatibility.
src/components/Users/UserAddEditForm.tsx (2)
Learnt from: Jacobjeevan
PR: ohcnetwork/care_fe#9080
File: src/components/Users/UserAddEditForm.tsx:655-719
Timestamp: 2024-11-13T23:01:50.298Z
Learning: The duplication in form submission logic in `handleSubmit` has already been addressed in a previous comment.
Learnt from: Jacobjeevan
PR: ohcnetwork/care_fe#9080
File: src/components/Users/UserEditDetails.tsx:90-97
Timestamp: 2024-11-20T19:08:38.025Z
Learning: In the React component `UserEditDetails` (src/components/Users/UserEditDetails.tsx), error handling for form submissions is handled within the `UserAddEditForm` component, so additional error handling is unnecessary.
src/components/Users/UserListAndCard.tsx (4)
Learnt from: Jacobjeevan
PR: ohcnetwork/care_fe#9080
File: src/components/Users/UserListAndCard.tsx:306-312
Timestamp: 2024-11-22T10:44:19.282Z
Learning: The `getDistrict` function in `src/components/Users/UserListAndCard.tsx` returns a JSX element styled specifically for the card view and should not be reused in the list view.
Learnt from: Jacobjeevan
PR: ohcnetwork/care_fe#9080
File: src/components/Users/UserListAndCard.tsx:55-58
Timestamp: 2024-11-14T08:22:33.858Z
Learning: In `src/components/Users/UserListAndCard.tsx`, the `user.user_type` property is always defined for all users; therefore, functions like `CanUserAccess` do not need to handle undefined `user_type`.
Learnt from: Jacobjeevan
PR: ohcnetwork/care_fe#9080
File: src/components/Users/UserListAndCard.tsx:84-84
Timestamp: 2024-11-14T08:09:47.010Z
Learning: In this application, usernames are unique and can be safely used as unique IDs in DOM elements without causing duplication issues.
Learnt from: Jacobjeevan
PR: ohcnetwork/care_fe#9080
File: src/components/Users/UserListAndCard.tsx:333-339
Timestamp: 2024-11-13T14:34:36.221Z
Learning: In the `UserListView` component, the search input already handles cases where there are no results by displaying a no results banner, and the search box is reset when the badge is cleared.
🪛 Biome (1.9.4)
src/components/Users/UserAddEditForm.tsx
[error] 164-164: Unnecessary use of boolean literals in conditional expression.
Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with
(lint/complexity/noUselessTernary)
[error] 865-896: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment
(lint/complexity/noUselessFragments)
[error] 1133-1152: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment
(lint/complexity/noUselessFragments)
[error] 1155-1174: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment
(lint/complexity/noUselessFragments)
[error] 1177-1196: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment
(lint/complexity/noUselessFragments)
🔇 Additional comments (15)
src/components/Form/Form.tsx (4)
36-38
: LGTM! Well-structured prop additions.
The new optional boolean props are well-typed and follow TypeScript best practices. Their names clearly indicate their purpose in controlling form reset behavior and cancel button visibility.
88-93
: LGTM! Clean implementation of cancel handler.
The handleCancel
function is well-implemented with clear separation of concerns. It correctly handles both form reset and the optional cancel callback.
138-143
: LGTM! Clean implementation of conditional cancel button.
The cancel button implementation follows React best practices with proper conditional rendering and maintains accessibility through consistent labeling.
83-84
: 🛠️ Refactor suggestion
Consider updating initialFormValues ref when defaults prop changes.
While the reset functionality is correctly implemented, there's a potential issue if the defaults
prop changes during the component's lifecycle. The initialFormValues
ref won't automatically update to reflect the new defaults.
Consider adding an effect to update the ref when defaults change:
+ useEffect(() => {
+ formVals.current = props.defaults;
+ }, [props.defaults]);
src/components/Users/LinkedFacilities.tsx (3)
267-338
: Well-structured component rendering
The main render function is well-organized with proper conditional rendering and accessibility attributes.
102-116
:
Ensure modalProps.selectedFacility
is defined before proceeding
The modal handler should validate the selected facility before proceeding with the action.
const handleModalOk = () => {
+ if (!modalProps.selectedFacility && modalProps.type !== "clear_home_facility") {
+ Notification.Error({ msg: t("no_facility_selected") });
+ return;
+ }
switch (modalProps.type) {
147-148
:
Avoid mutating props directly; use state or data fetching instead
Direct mutation of props violates React's unidirectional data flow.
- userData.home_facility_object = undefined;
+ setHomeFacility(undefined);
+ // Optionally, refetch user data if necessary
src/components/Users/UserListAndCard.tsx (2)
128-168
: LGTM! Well-structured status indicator component.
The component effectively handles different states and provides good visual feedback.
49-50
:
Replace hardcoded array index with explicit user type check.
The comment indicates this is a temporary solution, but using a hardcoded index (USER_TYPE_OPTIONS[6]
) is fragile and could break if the array order changes.
Consider this safer approach:
- // Temporarily allows creation of users with elevated permissions due to introduction of new roles.
- return [...defaultAllowedUserTypes, USER_TYPE_OPTIONS[6]];
+ // Temporarily allows creation of users with elevated permissions due to introduction of new roles.
+ const approverType = USER_TYPE_OPTIONS.find(type => type.id === "Approver");
+ return approverType
+ ? [...defaultAllowedUserTypes, approverType]
+ : defaultAllowedUserTypes;
Likely invalid or redundant comment.
src/components/Users/ManageUsers.tsx (2)
71-71
: Fix the district query parameter key
The query parameter key should be 'district' instead of 'district_id' to match the API's expected parameter.
Line range hint 315-343
: Verify facility management permissions
The facility management permissions rely on user type checks. Ensure these align with backend authorization rules and consider implementing role-based access control (RBAC).
✅ Verification successful
Facility management permissions are consistently implemented with role-based checks
The codebase shows a well-structured permission system for facility management:
- Permissions are centrally defined in constants (
USER_TYPES
,USER_TYPE_OPTIONS
,ADMIN_USER_TYPES
) - Consistent role-based checks are implemented across components using
["DistrictAdmin", "StateAdmin"].includes(authUser.user_type)
- The permission hierarchy is enforced through
USER_TYPES
array indexing, ensuring proper access levels - Facility access controls are properly implemented with user type validation before allowing management operations
- There are dedicated utility functions in
permissions.ts
to handle authorization checks
The implementation aligns well with backend authorization rules through consistent role-based access control.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for consistent permission checks across the codebase
# Search for similar permission checks
rg -A 2 "DistrictAdmin.*StateAdmin.*includes"
# Look for backend permission implementations
rg -A 5 "def has_facility_permission|def can_manage_facility"
Length of output: 3046
Script:
#!/bin/bash
# Let's check for backend permission implementations in Python files
rg -A 5 "class .*Admin\(|def has_permission|def check_permission|@permission_required"
# Let's also check for any RBAC related configurations or middleware
rg -A 3 "permission_classes|@permission_classes|PERMISSION_CLASSES"
# Check for facility-related permission checks in TypeScript/JavaScript files
ast-grep --pattern 'if ($_ && $_.user_type) {
$$$
}'
Length of output: 215
Script:
#!/bin/bash
# Let's check for user type constants and their usage in authorization
rg -A 3 "USER_TYPE|UserType|type\s*=\s*['\"](?:District|State)Admin['\"]"
# Look for facility-related permission checks in a different way
rg -A 5 "canEdit.*Facility|hasFacility.*Permission|facility.*access"
# Check for any auth-related utility functions
rg -A 3 "usePermissions|checkPermission|hasAccess"
Length of output: 19943
src/components/Users/UserAddEditForm.tsx (2)
177-179
: 🛠️ Refactor suggestion
Improve error handling in the userData fetch callback.
The current error handling is too simplistic and doesn't provide user feedback for different failure scenarios.
-if (!editUser || !result || !result.res || !result.data) return;
+if (!editUser) return;
+if (!result?.res?.ok) {
+ Notification.Error({ msg: t("error_fetching_user_data") });
+ return;
+}
+if (!result.data) {
+ Notification.Error({ msg: t("user_data_not_found") });
+ return;
+}
Likely invalid or redundant comment.
111-126
: 🛠️ Refactor suggestion
Enhance type safety in the reducer.
The reducer's default case should leverage TypeScript's exhaustive type checking to ensure all action types are handled explicitly.
const user_create_reducer = (state = initialState, action: UserFormAction) => {
switch (action.type) {
case "set_form": {
return {
...state,
form: action.form,
};
}
case "set_state": {
if (action.state) return action.state;
return state;
}
default: {
- return state;
+ const _exhaustiveCheck: never = action;
+ return _exhaustiveCheck;
}
}
};
Likely invalid or redundant comment.
public/locale/en.json (2)
1077-1087
: Password validation message improvements.
Referring to the previous review comment about improving password validation message consistency and clarity.
Line range hint 1-1558
: LGTM! The localization changes effectively support the user management redesign.
The new strings provide comprehensive coverage for:
- User profile management
- Password management
- Avatar management
- Facility management
- Skill management
The messages are generally clear and user-friendly, with good error handling and success notifications.
<div id="role" className="font-medium"> | ||
{user.user_type} | ||
</div> | ||
</div> | ||
<div className="text-sm"> | ||
<div className="text-gray-500">{t("home_facility")}</div> | ||
<div id="home-facility" className="font-medium"> | ||
{user.home_facility_object?.name || t("no_home_facility")} | ||
</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.
Fix duplicate HTML IDs across components.
The same IDs ('role', 'district', 'home-facility') are used in both UserCard and UserListRow components. HTML IDs must be unique across the entire document.
- <div id="role" className="font-medium">
+ <div id={`role-card-${user.id}`} className="font-medium">
{user.user_type}
</div>
</div>
<div className="text-sm">
<div className="text-gray-500">{t("home_facility")}</div>
- <div id="home-facility" className="font-medium">
+ <div id={`home-facility-card-${user.id}`} className="font-medium">
{user.home_facility_object?.name || t("no_home_facility")}
</div>
// Similarly in UserListRow:
- <td id="role" className="px-4 py-4 text-sm">
+ <td id={`role-list-${user.id}`} className="px-4 py-4 text-sm">
{user.user_type}
</td>
- <td id="home-facility" className="px-4 py-4 text-sm">
+ <td id={`home-facility-list-${user.id}`} className="px-4 py-4 text-sm">
Also applies to: 301-306
) : ( | ||
<div className="h-full space-y-2 rounded-lg bg-white p-7 shadow"> | ||
<div className="flex w-full items-center justify-center text-xl font-bold text-secondary-500"> | ||
No Users Found |
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
Use translation for "No Users Found" message.
The empty state message should use the translation system like other text in the component.
- No Users Found
+ {t("no_users_found")}
📝 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.
No Users Found | |
{t("no_users_found")} |
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: 0
🧹 Outside diff range and nitpick comments (4)
cypress/e2e/users_spec/UsersManage.cy.ts (4)
95-98
: Add descriptive test comments.The comments about qualification fields could be more descriptive to explain the business logic behind these assertions.
- // Should have qualification field - // Should not have years of experience and medical council registration fields + // Nurses should only have qualification field + // Years of experience and medical council registration are doctor-specific fields
133-136
: Consider extracting date calculation logic.The date calculation logic for experience validation could be moved to a helper function for better reusability and maintainability.
+ const calculateExperienceDate = (yearsOfExperience: number) => { + const date = new Date(Date.now()); + date.setFullYear(date.getFullYear() - yearsOfExperience); + return date.toISOString().split("T")[0]; + }; + - const experienceCommencedOn = new Date(Date.now()); - experienceCommencedOn.setFullYear(experienceCommencedOn.getFullYear() - 10); - const formattedDate = experienceCommencedOn.toISOString().split("T")[0]; + const formattedDate = calculateExperienceDate(10);
286-295
: Group related assertions using Cypress custom commands.The professional info verification steps could be grouped into a custom command for better readability and reusability.
// In commands.ts Cypress.Commands.add('verifyNonDoctorProfessionalInfo', () => { cy.get('[data-testid="qualification-field"]').should('not.exist'); cy.get('[data-testid="experience-field"]').should('not.exist'); cy.get('[data-testid="council-registration-field"]').should('not.exist'); }); // In test file manageUserPage.verifyProfileTabPage(); cy.verifyNonDoctorProfessionalInfo();
356-359
: Consider adding assertion for successful unlink.After unlinking a facility, consider adding an assertion to verify the success notification message.
manageUserPage.clickLinkedFacilitySettings(); manageUserPage.clickUnlinkFacility(); manageUserPage.clickSubmit(); + cy.verifyNotification("Facility unlinked successfully"); + cy.closeNotification(); manageUserPage.linkedfacilitylistnotvisible();
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
cypress/e2e/users_spec/UsersManage.cy.ts
(4 hunks)
🧰 Additional context used
📓 Learnings (1)
cypress/e2e/users_spec/UsersManage.cy.ts (1)
Learnt from: Jacobjeevan
PR: ohcnetwork/care_fe#9080
File: cypress/e2e/users_spec/UsersManage.cy.ts:129-131
Timestamp: 2024-11-14T10:29:07.447Z
Learning: In the Cypress test files (e.g., `cypress/e2e/users_spec/UsersManage.cy.ts`), it is acceptable to use hardcoded English error messages, as testing is conducted in English.
🔇 Additional comments (4)
cypress/e2e/users_spec/UsersManage.cy.ts (4)
20-22
: LGTM! Clear and descriptive variable names.
The new test user variables are well-named and follow the existing naming convention.
Also applies to: 27-27
40-48
: Remove or properly track the commented test case.
The commented test case for avatar upload should either be removed or properly tracked using Cypress's .skip()
.
- // To Do: Add avatar upload
- /* it("District Admin can change their own avatar", () => {
- userPage.typeInSearchInput(nurseUsername);
- userPage.checkUsernameText(nurseUsername);
- manageUserPage.clickMoreDetailsButton(nurseUsername);
- manageUserPage.verifyMoreDetailsPage();
- manageUserPage.verifyChangeAvatarButtonVisible();
- manageUserPage.clickChangeAvatarButton();
- }); */
+ it.skip("District Admin can change their own avatar", () => {
+ // TODO: Implement after avatar upload feature is ready
+ });
233-233
: Replace arbitrary wait statements with proper assertions.
Using fixed wait times (cy.wait(500)
) can make tests flaky. Consider using Cypress's built-in retry-ability and assertions instead.
- cy.wait(500);
+ cy.get('[data-testid="linked-skills-list"]').should('be.visible');
Also applies to: 237-237, 239-239
300-302
: LGTM! Appropriate error message validation.
The error message validation for working hours is clear and matches the business requirements.
conditonally approving it, @khavinshankar @rithviknishad merge only after relevant backend is merged and the cypress is passing |
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.
We could've used shadcn's components and shadcn's form with zod validators in re-designs. But let's do that as separate PR since this is tested.
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: 1
🧹 Outside diff range and nitpick comments (6)
src/components/Users/LinkedFacilitiesTab.tsx (1)
33-42
: Consider adding ARIA labels for accessibility.The render logic is clean and well-structured, but could benefit from improved accessibility.
Consider adding ARIA labels to improve screen reader support:
-<div className="mt-10 flex flex-col gap-y-12"> +<div + className="mt-10 flex flex-col gap-y-12" + role="region" + aria-label={t("linked_facilities")} +>src/components/Facility/FacilityUsers.tsx (2)
16-19
: Consider making the page limit configurable.The hardcoded limit of 18 seems arbitrary. Consider making this configurable through environment variables or constants.
- limit: 18, + limit: DEFAULT_PAGE_SIZE,
20-20
: Document the purpose of activeTab.The
activeTab
state's purpose isn't immediately clear. Consider adding a comment explaining its role in the UserListView component.+ // activeTab controls the view mode (e.g., grid/list) in UserListView const [activeTab, setActiveTab] = useState(0);
Also applies to: 63-64
src/components/Users/UserSummary.tsx (3)
37-43
: Extract props interface for better maintainabilityConsider extracting the props type to a separate interface for better maintainability and reusability.
+interface UserSummaryTabProps { + userData?: UserModel; + refetchUserData?: () => void; +} -export default function UserSummaryTab({ - userData, - refetchUserData, -}: { - userData?: UserModel; - refetchUserData?: () => void; -}) { +export default function UserSummaryTab({ userData, refetchUserData }: UserSummaryTabProps) {
49-51
: Return null explicitly for better clarityThe early return should explicitly return null for better clarity and type safety.
if (!userData) { - return <></>; + return null; }
53-71
: Improve error message formattingUse template literals for better error message formatting.
Notification.Error({ - msg: t("user_delete_error") + ": " + (error || ""), + msg: `${t("user_delete_error")}: ${error || t("unknown_error")}`, });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (6)
src/components/Common/UserColumns.tsx
(1 hunks)src/components/Common/UserDetails.tsx
(1 hunks)src/components/Facility/FacilityUsers.tsx
(2 hunks)src/components/Users/LinkedFacilitiesTab.tsx
(1 hunks)src/components/Users/LinkedSkillsTab.tsx
(1 hunks)src/components/Users/UserSummary.tsx
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- src/components/Common/UserColumns.tsx
- src/components/Common/UserDetails.tsx
- src/components/Users/LinkedSkillsTab.tsx
🧰 Additional context used
📓 Learnings (2)
src/components/Users/LinkedFacilitiesTab.tsx (1)
Learnt from: Jacobjeevan
PR: ohcnetwork/care_fe#9080
File: src/components/Users/LinkedFacilitiesTab.tsx:7-11
Timestamp: 2024-11-12T12:06:30.851Z
Learning: In `src/components/Users/LinkedFacilitiesTab.tsx`, the `username` prop is used when passing it to the `userColumns` function, so it should not be considered unused.
src/components/Users/UserSummary.tsx (1)
Learnt from: Jacobjeevan
PR: ohcnetwork/care_fe#9080
File: src/components/Users/UserSummary.tsx:53-71
Timestamp: 2024-11-22T12:04:39.044Z
Learning: In the `UserSummaryTab` component (`src/components/Users/UserSummary.tsx`), when there is an error during user deletion, the delete dialog is intentionally closed to prevent the user from immediately trying to delete the user again.
🔇 Additional comments (11)
src/components/Users/LinkedFacilitiesTab.tsx (5)
1-11
: LGTM! Well-organized imports.
The imports are logically grouped and all are being used within the component.
13-17
: LGTM! Well-defined prop types.
The prop types are properly defined with TypeScript, and the username
prop is correctly included as it's used in the UserColumns component.
19-23
: LGTM! Proper hooks usage and permissions check.
The component follows React best practices with proper hooks usage and early permissions validation.
25-31
: 🛠️ Refactor suggestion
Improve error handling logic and flow.
The current error handling could be enhanced for better reliability and maintainability.
Consider applying these improvements:
if (!userData) {
Notification.Error({ msg: t("username_userdetails_not_found") });
- return <></>;
+ return null;
}
-else if (!editPermissions) {
+
+if (!editPermissions) {
Notification.Error({ msg: t("no_permission_to_view_page") });
navigate("/users");
+ return null; // Prevent potential render after navigation
}
Likely invalid or redundant comment.
1-43
: Verify backend compatibility for linked facilities.
As mentioned in the PR comments, there are pending backend changes that this component depends on.
Let's verify the backend route compatibility:
✅ Verification successful
Based on the comprehensive analysis of the codebase, I can now generate the final response:
Backend routes are properly integrated and functional
The LinkedFacilitiesTab component is correctly integrated with all necessary backend routes:
- GET
/api/v1/users/{username}/get_facilities/
for listing facilities - PUT
/api/v1/users/{username}/add_facility/
for linking facilities - DELETE
/api/v1/users/{username}/delete_facility/
for unlinking facilities - DELETE
/api/v1/users/{username}/clear_home_facility/
for clearing home facility
The component implementation in LinkedFacilities.tsx properly utilizes these endpoints through the request utilities and handles all facility management operations correctly.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check if the backend routes for linked facilities exist and match the expected format
# Search for API route definitions
rg -A 3 "linked.*facilities" --type ts
# Search for any TODOs or FIXMEs related to backend integration
rg -i "todo.*backend|fixme.*backend" --type ts
Length of output: 1285
Script:
#!/bin/bash
# Let's check the LinkedFacilities component implementation and API calls
# Check LinkedFacilities component implementation
ast-grep --pattern 'export default function LinkedFacilities($_) { $$$ }'
# Search for API calls related to facilities
rg -B 2 -A 4 'api.*facilities|facilities.*api' --type ts
# Check for any facility-related types or interfaces
rg -B 1 -A 3 'interface.*Facility|type.*Facility' --type ts
Length of output: 46964
Script:
#!/bin/bash
# Let's check the API routes related to user facilities
# Check the API routes definition
ast-grep --pattern 'routes = {
$$$
userListFacility: $_,
addUserFacility: $_,
deleteUserFacility: $_,
clearHomeFacility: $_,
$$$
}'
# Check for any pending TODOs or FIXMEs in the LinkedFacilities component
rg -B 2 -A 2 "TODO|FIXME" src/components/Users/LinkedFacilities.tsx
# Check the UserModel interface to verify facility-related fields
ast-grep --pattern 'interface UserModel {
$$$
home_facility?: $_,
home_facility_object?: $_,
$$$
}'
Length of output: 325
Script:
#!/bin/bash
# Let's check the routes file directly and the UserModel definition
# Check the routes file for facility-related endpoints
rg -B 1 -A 3 "userListFacility|addUserFacility|deleteUserFacility|clearHomeFacility" src/Utils/request/api.tsx
# Check the UserModel interface definition
rg -B 2 -A 5 "interface UserModel" src/components/Users/models.tsx
# Check if there are any API-related imports in LinkedFacilities
rg -B 1 -A 1 "import.*request|import.*api" src/components/Users/LinkedFacilities.tsx
Length of output: 1042
src/components/Facility/FacilityUsers.tsx (4)
7-7
: LGTM! Good improvement in type safety.
The change from props: any
to props: { facilityId: number }
provides better type safety and code clarity. The new imports align well with the refactoring objectives.
Also applies to: 9-9, 14-14
34-37
: Remove unnecessary toString() conversion.
The query parameters will be automatically converted to strings.
- offset: ((qParams.page ? qParams.page - 1 : 0) * resultsPerPage).toString(),
+ offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage,
51-57
: Well-structured rendering with good responsive design.
The component's rendering logic is well organized with:
- Proper loading state handling
- Responsive design in CountBlock
- Clear separation of concerns between count display, user list, and pagination
Also applies to: 59-65, 67-67
59-65
: Verify the grid layout improvements.
As per issue #8983, please ensure that the UserListView component properly implements the transition from grid to list-style view.
src/components/Users/UserSummary.tsx (2)
73-82
: LGTM! Well-structured permission checks
The permission checks and data preparation are well-organized and follow best practices.
219-222
:
Fix invalid HTML structure
Nested <p>
tags are invalid HTML and should be replaced with appropriate elements.
-<div className="my-1 text-sm leading-5">
- <p className="mb-2 font-semibold">{t("delete_account")}</p>
- <p className="text-secondary-600">{t("delete_account_note")}</p>
-</div>
+<div className="my-1 text-sm leading-5">
+ <h3 className="mb-2 font-semibold">{t("delete_account")}</h3>
+ <p className="text-secondary-600">{t("delete_account_note")}</p>
+</div>
Likely invalid or redundant comment.
const renderBasicInformation = () => { | ||
if (editPermissions) { | ||
return ( | ||
<UserBasicInfoView | ||
username={userData.username} | ||
userData={userData} | ||
onSubmitSuccess={refetchUserData} | ||
/> | ||
); | ||
} | ||
return ( | ||
<div className="overflow-visible px-4 py-5 sm:px-6 rounded-lg shadow sm:rounded-lg bg-white"> | ||
<BasicInfoDetails user={userData} /> | ||
</div> | ||
); | ||
}; | ||
|
||
const renderContactInformation = () => { | ||
if (editPermissions) { | ||
return ( | ||
<UserContactInfoView | ||
username={userData.username} | ||
userData={userData} | ||
onSubmitSuccess={refetchUserData} | ||
/> | ||
); | ||
} | ||
return ( | ||
<div className="overflow-visible px-4 py-5 sm:px-6 rounded-lg shadow sm:rounded-lg bg-white"> | ||
<ContactInfoDetails user={userData} /> | ||
</div> | ||
); | ||
}; | ||
|
||
const renderProfessionalInformation = () => { | ||
if (editPermissions) { | ||
return ( | ||
<UserProfessionalInfoView | ||
username={userData.username} | ||
userData={userData} | ||
onSubmitSuccess={refetchUserData} | ||
/> | ||
); | ||
} | ||
return ( | ||
<div className="overflow-visible px-4 py-5 sm:px-6 rounded-lg shadow sm:rounded-lg bg-white"> | ||
<ProfessionalInfoDetails user={userData} /> | ||
</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
Consolidate duplicate render functions
The three information rendering functions follow the same pattern and could be consolidated into a single reusable function to reduce code duplication.
+type InformationType = 'basic' | 'contact' | 'professional';
+
+interface RenderInformationProps {
+ username: string;
+ userData: UserModel;
+ onSubmitSuccess?: () => void;
+}
+
+const renderInformation = (
+ type: InformationType,
+ editPermissions: boolean,
+ props: RenderInformationProps
+) => {
+ const components = {
+ basic: { View: UserBasicInfoView, Details: BasicInfoDetails },
+ contact: { View: UserContactInfoView, Details: ContactInfoDetails },
+ professional: { View: UserProfessionalInfoView, Details: ProfessionalInfoDetails },
+ };
+
+ const { View, Details } = components[type];
+
+ if (editPermissions) {
+ return <View {...props} />;
+ }
+
+ return (
+ <div className="overflow-visible px-4 py-5 sm:px-6 rounded-lg shadow sm:rounded-lg bg-white">
+ <Details user={props.userData} />
+ </div>
+ );
+};
Usage:
const renderBasicInformation = () =>
renderInformation('basic', editPermissions, {
username: userData.username,
userData,
onSubmitSuccess: refetchUserData,
});
Proposed Changes
To Do:
is_active flag
to false, which works fine for userList (/api/v1/users/
), but not for getFacilityUsers (facility/facilityId/get_users/
) as latter is checking fordeleted=False
flag.Potential things to check:
If user doesn't have edit permissions:
@ohcnetwork/care-fe-code-reviewers
Merge Checklist
Summary by CodeRabbit
Release Notes
New Features
Improvements
Bug Fixes