Skip to content
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

Open
wants to merge 59 commits into
base: develop
Choose a base branch
from

Conversation

Jacobjeevan
Copy link
Contributor

@Jacobjeevan Jacobjeevan commented Nov 11, 2024

Proposed Changes

To Do:

  • Open an issue for interns: I've already moved most of the elements from existing profile page to to user details page (when it's details of logged in user), but there's a few issues in the works modifying certain elements in the profile page, so may need to tweak user details page.
  • Open an issue on BE: While testing, I noticed that user deletion doesn't work for facility users page (delete route sets 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 for deleted=False flag.

Potential things to check:

  • Conditional rendering of elements
    • Qualifications (under Professional Information) only for Doctors/Nurses
    • YOE and Medical registration number (under Professional Information) only for Doctors
    • Logged in user can edit their own profile as well as change password
    • Edit (other users) only available for District/StateAdmin and superusers only
    • Change password (other users) only available for District/StateAdmin and superusers only
    • Deletion only (other users) for District/StateAdmins and superusers only
      • Only can delete users below in hierarchy
    • Avatar edit option (for all users) only for super user
    • Logged in user can edit their own avatar
  • Users without edit permissions can view profiles of other users
image image image image image image image image image image

If user doesn't have edit permissions:

image image

@ohcnetwork/care-fe-code-reviewers

Merge Checklist

  • Add specs that demonstrate bug / test a new feature.
  • Update product documentation.
  • Ensure that UI text is kept in I18n files.
  • Prep screenshot or demo video for changelog entry, and attach it to issue.
  • Request for Peer Reviews
  • Completion of QA

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced user management functionalities, including linked facilities, skills, and user information.
    • Introduced a confirmation modal for facility management actions.
    • Added components for editing user details, managing user avatars, and displaying user summaries.
  • Improvements

    • Updated localization for better user interaction and error handling.
    • Improved form validation for user inputs, including password resets and user details.
    • Streamlined user management interface with new views and tabs.
  • Bug Fixes

    • Resolved issues with error messages and validation prompts across various components.

- 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
- Refactor FacilityUsers to use similar format as ManageUsers
- Extracted out the list/card logic to UserListAndCard
- Changed FacilityUsers to use userList route (with facilityId as home_facility parameter)
- 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
@Jacobjeevan Jacobjeevan requested a review from a team as a code owner November 11, 2024 10:54
Copy link
Contributor

coderabbitai bot commented Nov 11, 2024

Walkthrough

The 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

File Path Change Summary
public/locale/en.json Added new localization entries for user management, account actions, and error messages.
src/Routers/routes/UserRoutes.tsx Updated import for UserAdd, added UserHome component, and new routes for user details.
src/components/Auth/ResetPassword.tsx Changed import for validateRule, updated validation messages to use translation keys.
src/components/Common/Page.tsx Added hideTitleOnPage property to PageProps interface.
src/components/Common/PageTitle.tsx Introduced hideTitleOnPage prop for conditional title rendering.
src/components/Common/SkillSelect.tsx Updated selected and setSelected property types in SkillSelectProps.
src/components/Common/UserColumns.tsx Added new UserColumns component for displaying user-related data.
src/components/Common/UserDetails.tsx Modified styling of the title rendering.
src/components/Facility/FacilityUsers.tsx Refactored to use useFilters for managing user data and pagination.
src/components/Form/Form.tsx Added new props for form reset behavior and updated handleCancel logic.
src/components/Users/ConfirmFacilityModal.tsx Introduced modal for confirming facility management actions.
src/components/Users/LinkedFacilities.tsx Implemented component for managing linked facilities.
src/components/Users/LinkedSkills.tsx Added component for managing linked skills.
src/components/Users/UserAddEditForm.tsx Comprehensive form for user creation and editing with validation.
src/Utils/utils.ts Updated isUserOnline function to handle undefined last_login.
src/common/validation.tsx Added validateNumber function for numeric validation.
cypress/e2e/users_spec/UsersCreation.cy.ts Modified test cases for user creation to reflect updated error messages and verification steps.
cypress/pageobject/Users/ManageUserPage.ts Extensive updates to methods for user management interactions.

Assessment against linked issues

Objective Addressed Explanation
Refactor user list and user management pages (#8878) The PR does not fully implement the required UI changes for user cards and detail pages.
Improve Age Validation in User Creation Form (#9001) Dynamic client-side validation for age is implemented.
Enhance Grid Layout in "View Users Associated with Facilities" Section (#8983) Changes to layout are made, but clarity on grid vs. list view is not specified.

Suggested labels

good to merge

Suggested reviewers

  • nihal467
  • rithviknishad

Poem

🐰 In the fields where users roam,
New features sprout, a digital home.
With skills and facilities linked with care,
User management shines, a breath of fresh air!
Validation dances, ensuring all's right,
Hop along, dear friends, the future's so bright! 🌟


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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

netlify bot commented Nov 11, 2024

Deploy Preview for care-ohc ready!

Name Link
🔨 Latest commit 56ae6a0
🔍 Latest deploy log https://app.netlify.com/sites/care-ohc/deploys/67485ddc921c3c0008f01074
😎 Deploy Preview https://deploy-preview-9080--care-ohc.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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:

  1. Adding a deprecation notice for /user/profile
  2. Implementing a redirect from /user/profile to the new user details page

Example 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:

  1. Use PascalCase for component name
  2. Use React.FC for better type safety
  3. 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 of type 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:

  1. Add aria-label for better screen reader support
  2. 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 imports SkillModel 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 uses any 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 a results 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 (like collapseSidebar) 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.

  1. Extract the props interface for better reusability and documentation.
  2. 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.

  1. The switch statement could be simplified by extracting the modal configurations.
  2. 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.

  1. The show prop could be controlled externally for better flexibility.
  2. The danger variant might not be appropriate for all actions (e.g., replace_home_facility).
  3. 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 userData

The 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 implementation

The 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:

  1. Document the reset behavior in the component's README or documentation
  2. Add unit tests covering the reset functionality
  3. 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:

  1. Add ARIA roles and states to the tab navigation
  2. 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:

  1. Tab navigation and rendering
  2. User data loading and error states
  3. Role-based visibility logic
  4. 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 using useMemo to optimize performance, especially if isUserOnline 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:

  1. Adding special character requirement
  2. Making the validation function more configurable
  3. 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.

  1. Add proper ARIA attributes for better screen reader support
  2. Show validation messages consistently, not just on focus
  3. 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:

  1. Extracting the regex pattern to a constant
  2. 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:

  1. Adding specific error messages for different failure scenarios
  2. Handling session expiry gracefully
  3. 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 messages

The 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 implementation

Several improvements can be made to the validation logic:

  1. Extract magic numbers into named constants
  2. Internationalize error messages
  3. 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 internationalization

The form submission handler needs improvements:

  1. Add error handling for date parsing
  2. Internationalize success message
  3. 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 implementation

The password change handler needs several improvements:

  1. Add proper TypeScript typing for the event parameter
  2. Internationalize error messages
  3. Add loading state during submission
  4. 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: Include rel="noopener noreferrer" when using target="_blank to prevent security risks.

When using target="_blank``, adding rel="noopener noreferrer"prevents the new page from accessing thewindow.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 function t(). 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" /> &nbsp;Need
-          Help?
+          <CareIcon icon="l-question-circle" className="text-lg" /> &nbsp;{t("Need Help?")}

Line range hint 15-23: Use an <a> element instead of Link for external URLs.

The Link component from raviger 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" /> &nbsp;{t("Need Help?")}
-        </Link>
+        </a>

1-1: Remove unused import of Link from raviger.

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 parameters

The calculation of offset depends on qParams.page. If qParams.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 in CountBlock for clarity

The 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 to errors prop

The errors prop is passed as an empty string to SkillSelect. If there are no errors, it's better to pass undefined 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 messages

The 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 field

To 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 directly

Calculating 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 cleanliness

The 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 duplication

The functions renderFacilityButtons and renderHomeFacilityButton 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 the Avatar component is set to user.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 the Avatar component is set to user.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 variable

The 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' message

The 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6931845 and 0565d84.

📒 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 the Breadcrumbs component to control the visibility of the back button/navigation
  • hideTitleOnPage 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:

  1. Providing an alternative heading for screen readers when the visible title is hidden
  2. 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:

  1. The component is used consistently as a secondary heading (h2)
  2. Other components maintain their own heading hierarchy independently
  3. 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:

  1. UI masking for verified mobile numbers
  2. Rate limiting for OTP attempts beyond the current count-based limit
  3. 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 of UserRoles and returns a callback function matching the AuthorizedForCB type
  • useIsAuthorized expects a parameter of type AuthorizedForCB, which is exactly what AuthorizeFor(["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

src/Routers/routes/UserRoutes.tsx Show resolved Hide resolved
src/components/Common/UserColumns.tsx Outdated Show resolved Hide resolved
src/components/Users/LinkedFacilitiesTab.tsx Outdated Show resolved Hide resolved
src/components/Users/LinkedFacilitiesTab.tsx Outdated Show resolved Hide resolved
src/components/Users/LinkedFacilitiesTab.tsx Outdated Show resolved Hide resolved
src/components/Users/UserListAndCard.tsx Outdated Show resolved Hide resolved
src/components/Users/UserListAndCard.tsx Show resolved Hide resolved
src/components/Users/ManageUsers.tsx Show resolved Hide resolved
src/components/Users/UserAddEditForm.tsx Outdated Show resolved Hide resolved
src/components/Users/UserAddEditForm.tsx Outdated Show resolved Hide resolved
@Jacobjeevan Jacobjeevan marked this pull request as draft November 11, 2024 16:38
@github-actions github-actions bot added the merge conflict pull requests with merge conflict label Nov 12, 2024
@nihal467
Copy link
Member

@Jacobjeevan check the test report, clear the conflict and attach a screenshot cypress passing in local

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 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 like initialFormValues 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:

  1. Extracting the form reset logic into a custom hook (e.g., useFormReset)
  2. Splitting the draft functionality into a separate component
  3. 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 variables

The 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 limit

The 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 handlers

The 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 operations

The 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 maintainability

The 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 skeleton

Instead 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 management

The 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 components

The 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

📥 Commits

Reviewing files that changed from the base of the PR and between e36ac80 and c983194.

📒 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: ⚠️ Potential issue

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: ⚠️ Potential issue

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: ⚠️ Potential issue

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.

Comment on lines +200 to +208
<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>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

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.

Suggested change
No Users Found
{t("no_users_found")}

@Jacobjeevan
Copy link
Contributor Author

Jacobjeevan commented Nov 27, 2024

Test Report

Functionality Bug

  • When you search for a username, the input field disappears. It only reappears after clearing the badge. Please revert this to the original behavior where the search field remains visible at all times.
  • Similar issue noticeable for advanced filter as well
  • Disable the "Add Facility" button for non-admin users on their own pages.
  • Allow non-admin users to unlink their home facility and any facility in their access.
  • The avatar on the user profile page and the profile icon are different. Please make them consistent.
  • Reset the component's state after the password has been successfully changed.

Question For Clarity
* [ ] Only the account owner should be able to edit their own user information. However, with your current PR, an admin can edit other users' profiles. Is this the intended behavior?

Yep, that's the intention.

* [ ]  The new profile view details page and the personal profile page display nearly identical data. Could we consider combining them into a single page for simplicity and consistency? [Discuss with the core team].

Yep, we are merging those (removing current profile page), I was waiting on it since there was an intern PR open updating elements in Profile page. We can open an issue that interns can take up once this is merged

  • Ensure the first column remains fixed in the mobile view for the user list. This will improve accessibility and ease of navigation.
* [ ]  Add a "Last Modified" column in the user view page to display the date and time when the profile was last updated. Additionally, consider implementing an alert system to notify users to reverify their profile details after a period of 6 months or 1 year. This will help ensure that user information remains up-to-date. [Discussion required with core team before proceeding]

🤔 Yea could do for enhancement.

Here are the tests passing locally:

image

Details on the error (screenshot below). I sometimes encounter this when testing after resetting the db; bypass it by manually logging in the cypress window.

image

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between c983194 and abeefa9.

📒 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.

@nihal467
Copy link
Member

image

  • the position of badges doesn't look good, we need to think of a different position , which can be taken as a separate issue

@nihal467
Copy link
Member

conditonally approving it, @khavinshankar @rithviknishad merge only after relevant backend is merged and the cypress is passing

Copy link
Member

@rithviknishad rithviknishad left a 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.

src/Utils/request/api.tsx Outdated Show resolved Hide resolved
src/common/validation.tsx Show resolved Hide resolved
src/components/Common/UserColumns.tsx Outdated Show resolved Hide resolved
src/components/Common/UserDetails.tsx Outdated Show resolved Hide resolved
src/components/Facility/FacilityUsers.tsx Outdated Show resolved Hide resolved
@Jacobjeevan
Copy link
Contributor Author

image

* [ ]  the position of badges doesn't look good, we need to think of a different position , which can be taken as a separate issue

How's this?

image

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 maintainability

Consider 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 clarity

The early return should explicitly return null for better clarity and type safety.

  if (!userData) {
-   return <></>;
+   return null;
  }

53-71: Improve error message formatting

Use 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

📥 Commits

Reviewing files that changed from the base of the PR and between abeefa9 and 56ae6a0.

📒 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: ⚠️ Potential issue

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.

Comment on lines +83 to +132
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>
);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

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,
  });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants