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

Fix: Add a new list view to locations in the facility settings #10672

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

Conversation

abhimanyurajeesh
Copy link
Contributor

@abhimanyurajeesh abhimanyurajeesh commented Feb 18, 2025

Proposed Changes

Desktop view

image

Mobile view

image

Added new search feature

Added client side filtering and auto expand on search as we are already fetching all the data using pagination.

Screen.Recording.2025-02-19.at.9.19.01.PM.mov

@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

  • New Features
    • Enhanced on-screen instructions with updated text for clearer action prompts.
    • Revamped location management with a tabbed interface that lets you switch between list and map views.
    • Improved organization of locations through hierarchical, expandable rows and refined filtering for a more intuitive, streamlined experience.
    • Introduced a loading state and skeleton loader for better user feedback while fetching location data.

@abhimanyurajeesh abhimanyurajeesh requested a review from a team as a code owner February 18, 2025 10:38
Copy link
Contributor

coderabbitai bot commented Feb 18, 2025

Walkthrough

This pull request enhances the application by adding new localization keys in the English locale file, modifying the facilities locations list component, and introducing a new view component for rendering location details. The localization updates improve clarity for UI interactions, while the LocationList component now removes pagination in favor of tabbed navigation and hierarchical management of locations. The new LocationListView component organizes and displays location data with expandable rows, refined state management, and improved filtering based on user input.

Changes

File(s) Change Summary
public/locale/en.json Added multiple key-value pairs (e.g., "click", "click_add_main_location", "map", etc.) to enhance localization and UI textual instructions.
src/pages/Facility/settings/locations/LocationList.tsx Removed pagination logic; introduced constant queries based on facilityId; added new UI components (Tabs, TabsList, TabsTrigger); implemented state management for activeTab and expandedRows; added helper functions (buildLocationHierarchy, toggleRow, getChildren) for hierarchical data handling and filtering.
src/pages/Facility/settings/locations/components/LocationListView.tsx New file that defines the LocationListView and LocationRow components to render a table view of locations, manage loading states, handle empty results, and support row expansion with conditional rendering.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant LL as LocationList Component
    participant LV as LocationListView Component
    participant LR as LocationRow Component

    U->>LL: Navigate to Locations Page
    LL->>LL: Build location hierarchy & update state (activeTab, expandedRows)
    alt List View Selected
        LL->>LV: Render locations in table view
        U->>LV: Search/filter locations
        U->>LR: Click to expand/collapse a row
    else Map View Selected
        LL->>Map: Render map view with locations
    end
Loading

Possibly related PRs

Suggested labels

needs review, tested

Suggested reviewers

  • Jacobjeevan
  • rithviknishad

Poem

I'm a rabbit with a joyful code hop,
Adding keys and tabs, making UI non-stop.
Localization now sings in a clearer tone,
Rows that expand and collapse like a well-tuned drone.
In this garden of code, every change helps us grow!
🥕🐇

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

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 generate docstrings to generate docstrings for this PR. (Beta)
  • @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 Feb 18, 2025

Deploy Preview for care-ohc ready!

Name Link
🔨 Latest commit 9ce4b67
🔍 Latest deploy log https://app.netlify.com/sites/care-ohc/deploys/67b8c9f98fdf59000846e4df
😎 Deploy Preview https://deploy-preview-10672.preview.ohc.network
📱 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: 1

🧹 Nitpick comments (5)
src/pages/Facility/settings/locations/LocationList.tsx (5)

48-48: Consider pagination or infinite scroll for large data sets.
Using a limit of 1000 could cause performance issues if the result set grows.


55-55: Double-check the large limit usage.
Filtering on the front end for up to 1000 locations can work, but dynamic pagination is typically more scalable.


79-81: Toggle row logic is straightforward.
However, for very large lists, you might memoize or optimize rerenders.


93-238: LocationRow is large—consider extracting reusable parts.
This chunk handles row expansion, nested children, and top-level logic. While functional, the code can be split into smaller, more focused components or hooks for clarity and maintainability.


294-409: Main return block is quite lengthy.
Moving the tab-based UI, info alerts, and search form to smaller sub-components can enhance readability. Otherwise, the logic is consistent and user-friendly.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6210472 and 4fbef70.

📒 Files selected for processing (2)
  • public/locale/en.json (7 hunks)
  • src/pages/Facility/settings/locations/LocationList.tsx (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Test
  • GitHub Check: cypress-run (1)
🔇 Additional comments (23)
src/pages/Facility/settings/locations/LocationList.tsx (13)

2-3: No issues with these new imports.
These straightforward imports for PenLine and Link look good.


12-19: Table components import looks fine.
Importing these UI table components is appropriate and helps maintain a clean, structured layout.


20-20: Tabs import confirmed.
Importing Tabs and related sub-components is correct for managing tabbed navigation.


22-22: Page component import is good.
Ensures consistent page layout styling throughout the app.


23-23: Skeleton loading import confirmed.
Allows a helpful loading placeholder while fetching data.


25-25: useBreakpoints import approved.
Great custom hook approach for responsive design.


28-28: useView import looks good.
An effective way to persist tab view or route params.


30-30: Helper function import for location form label.
Neat utility for mapping form codes to friendly labels.


45-46: State for active tab and expanded rows.
Storing these as local states is straightforward. Just be mindful that a large data set could result in a large expandedRows.


51-52: Good use of query key.
Including facilityId and searchQuery ensures relevant caching behavior.


59-59: Conditional query enabling.
Using enabled: !!facilityId is a standard pattern to prevent unnecessary requests.


62-62: Data destructuring looks fine.
Falling back to an empty array is a useful safeguard.


240-292: renderListView handles empty/loading states well.
This is a clear approach that improves UX. Optionally, you can pre-filter data outside of the rendering function to keep rendering purely presentational.

public/locale/en.json (10)

579-580: New translations for “click” and “click_on.”
These keys look appropriate for guiding user interactions.


583-583: “collapse_all” translation.
Concise and user-friendly for any UI expansions.


1000-1000: “expand_all” translation.
Clear labeling for expanding nested items.


1084-1084: “filter_by_locations” translation.
Useful for user filtering in the updated location list.


1146-1146: “hover_focus_reveal” translation.
Great for tooltips or subtle UI prompts.


1317-1317: “map” translation.
Essential for the upcoming map view.


1319-1319: “map_view_coming_soon” translation.
Accurately sets user expectation for future functionality.


1544-1544: “open_manage_sub_locations” translation.
Helps clarify the available action in the interface for nested location management.


2176-2177: Additional location-related strings.
“to_add_location” and “to_add_main_location” are helpful in guiding user steps.


2179-2179: “to_edit” translation.
A concise addition for clarifying edit-related actions.

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

🧹 Nitpick comments (4)
src/pages/Facility/settings/locations/LocationList.tsx (4)

39-56: Consider memoizing the hierarchy building function.

The buildLocationHierarchy function rebuilds the entire hierarchy on every render. While the current implementation is clean, it could benefit from memoization for larger datasets.

-function buildLocationHierarchy(locations: LocationListType[]) {
+const buildLocationHierarchy = (locations: LocationListType[]) => {
   const childrenMap = new Map<string, LocationListType[]>();
   const topLevelLocations: LocationListType[] = [];
   // ... rest of the implementation
   return { childrenMap, topLevelLocations };
-}
+};
+
+const memoizedBuildLocationHierarchy = useMemo(
+  () => buildLocationHierarchy,
+  []
+);

145-161: Consider extracting the recursive toggle logic.

The toggleAllChildren function contains complex recursive logic that could be moved outside the component for better maintainability and reusability.

+const toggleChildrenRecursively = (
+  getChildren: (parentId: string) => LocationListType[],
+  parentId: string,
+  expand: boolean,
+  newExpandedRows: Record<string, boolean>
+) => {
+  getChildren(parentId).forEach((child) => {
+    newExpandedRows[child.id] = expand;
+    toggleChildrenRecursively(getChildren, child.id, expand, newExpandedRows);
+  });
+};

 const toggleAllChildren = () => {
   setExpandedRows((prevExpandedRows) => {
     const newExpandedRows = { ...prevExpandedRows };
-    const toggleChildren = (parentId: string, expand: boolean) => {
-      getChildren(parentId).forEach((child) => {
-        newExpandedRows[child.id] = expand;
-        toggleChildren(child.id, expand);
-      });
-    };
     const shouldExpand = !children.every(
       (child) => prevExpandedRows[child.id],
     );
     newExpandedRows[location.id] = shouldExpand;
-    toggleChildren(location.id, shouldExpand);
+    toggleChildrenRecursively(getChildren, location.id, shouldExpand, newExpandedRows);
     return newExpandedRows;
   });
 };

365-413: Consider extracting the info banner to a separate component.

The info banner implementation is quite large and could be extracted into a reusable component, especially since it contains conditional rendering for desktop/mobile views.

+const LocationInfoBanner = () => {
+  const { t } = useTranslation();
+  return (
+    <div className="rounded-lg border-2 border-blue-200 bg-blue-50 p-4">
+      {/* ... existing banner implementation ... */}
+    </div>
+  );
+};

 {activeTab === "list" && (
-  <div className="rounded-lg border-2 border-blue-200 bg-blue-50 p-4">
-    {/* ... existing banner implementation ... */}
-  </div>
+  <LocationInfoBanner />
 )}

415-421: Consider implementing a loading state for the map view.

The map view placeholder could benefit from a loading state to maintain consistency with the list view.

 {activeTab === "list" ? (
   renderListView()
 ) : (
   <div className="h-[600px] bg-gray-100 rounded-lg flex items-center justify-center">
+    {isLoading ? (
+      <div className="animate-pulse">
+        <CareIcon icon="l-map" className="h-8 w-8 text-gray-400" />
+      </div>
+    ) : (
       <p className="text-gray-500">{t("map_view_coming_soon")}</p>
+    )}
   </div>
 )}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4fbef70 and 68c8489.

📒 Files selected for processing (1)
  • src/pages/Facility/settings/locations/LocationList.tsx (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Test
  • GitHub Check: cypress-run (1)
🔇 Additional comments (3)
src/pages/Facility/settings/locations/LocationList.tsx (3)

87-104: LGTM! Efficient filtering implementation.

The getChildren and filteredTopLevelLocations implementations are well-optimized:

  • Properly memoized to prevent unnecessary recalculations
  • Case-insensitive search
  • Clear separation of concerns

272-315: LGTM! Well-structured list view implementation.

The renderListView function is well-organized with clear handling of:

  • Loading states
  • Empty states
  • Search results
  • Table layout

67-79:

❓ Verification inconclusive

Verify the impact of the high LIMIT value.

Setting a constant LIMIT of 1000 might:

  1. Impact initial load performance
  2. Cause memory issues with large datasets
  3. Affect server response times

Consider implementing virtual scrolling for better performance with large datasets.


🏁 Script executed:

#!/bin/bash
# Check for potential performance impact
# Look for similar patterns in the codebase
rg -A 2 "limit.*=.*1000" .

Length of output: 28


Review update: Evaluate the Performance Impact of Using a High LIMIT Value

  • The constant LIMIT of 1000 in src/pages/Facility/settings/locations/LocationList.tsx may lead to performance issues (e.g., increased memory usage, slower page loads, and extended server response times) if the dataset grows large.
  • Although a repository-wide search for similar patterns did not produce conclusive results, it's important to manually verify this behavior in typical deployment environments.
  • Consider evaluating if the current data volume justifies the high limit. If not, exploring alternatives such as pagination or virtual scrolling could offer better performance and scalability.

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

🧹 Nitpick comments (3)
src/pages/Facility/settings/locations/LocationList.tsx (3)

39-56: Consider memoizing location hierarchy computation.

The buildLocationHierarchy function processes the entire location dataset on each render. While the implementation is correct, consider memoizing the results based on the locations array to prevent unnecessary recomputation.

+ // Add useMemo to cache results
+ const memoizedHierarchy = useMemo(() => {
   const childrenMap = new Map<string, LocationListType[]>();
   const topLevelLocations: LocationListType[] = [];

   locations.forEach((location) => {
     if (!location.parent || Object.keys(location.parent).length === 0) {
       topLevelLocations.push(location);
     } else {
       const parentId = location.parent.id;
       if (!childrenMap.has(parentId)) {
         childrenMap.set(parentId, []);
       }
       childrenMap.get(parentId)?.push(location);
     }
   });

   return { childrenMap, topLevelLocations };
+ }, [locations]);

125-270: Consider decomposing LocationRow for better maintainability.

The LocationRow component handles multiple responsibilities including row rendering, expansion logic, and action buttons. Consider breaking it down into smaller, focused components:

+ const LocationActions = ({ location, isMobile, handleEditLocation }: LocationActionsProps) => (
+   <div className="flex items-center gap-2 ml-auto">
+     <Button
+       variant="white"
+       size={isMobile ? "xs" : "sm"}
+       onClick={() => handleEditLocation(location)}
+       className="opacity-100 lg:opacity-0 group-hover:opacity-100 transition-opacity"
+     >
+       <PenLine className="h-4 w-4" />
+       <span className="hidden lg:inline">{t("edit")}</span>
+     </Button>
+     {/* ... other actions */}
+   </div>
+ );

+ const ExpandCollapseButton = ({ isExpanded, onClick, children }: ExpandCollapseProps) => (
+   <Button
+     variant="white"
+     onClick={onClick}
+     className="opacity-100 lg:opacity-0 group-hover:opacity-100 transition-opacity gap-2"
+   >
+     {children}
+   </Button>
+ );

This would improve:

  • Code reusability
  • Testing isolation
  • Maintenance

272-315: Add error boundary protection for the list view.

The renderListView function handles loading and empty states but lacks error handling. Consider wrapping it in an error boundary to gracefully handle rendering failures.

+ class LocationListErrorBoundary extends React.Component {
+   state = { hasError: false };
+   
+   static getDerivedStateFromError(error) {
+     return { hasError: true };
+   }
+   
+   render() {
+     if (this.state.hasError) {
+       return (
+         <Card className="col-span-full">
+           <CardContent className="p-6 text-center text-gray-500">
+             {t("error_loading_locations")}
+           </CardContent>
+         </Card>
+       );
+     }
+     return this.props.children;
+   }
+ }

  const renderListView = () => {
+   return (
+     <LocationListErrorBoundary>
        {/* existing render logic */}
+     </LocationListErrorBoundary>
+   );
  };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68c8489 and 2b44688.

📒 Files selected for processing (2)
  • public/locale/en.json (7 hunks)
  • src/pages/Facility/settings/locations/LocationList.tsx (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: cypress-run (1)
🔇 Additional comments (3)
src/pages/Facility/settings/locations/LocationList.tsx (2)

1-34: Well-organized imports and component setup!

The imports are logically grouped and include all necessary UI components for the new table-based list view.


317-437: Well-implemented location list with comprehensive features!

The implementation successfully:

  • Replaces grid with a table layout
  • Adds tab navigation between list and map views
  • Provides clear user guidance through tooltips
  • Implements responsive design for mobile/desktop
  • Includes search functionality
public/locale/en.json (1)

579-580: Well-structured localization additions for the new location list view!

The new localization keys:

  • Follow consistent naming patterns
  • Cover all new UI text requirements
  • Support both desktop and mobile interfaces

Also applies to: 583-583, 1000-1000, 1084-1084, 2175-2178

@abhimanyurajeesh
Copy link
Contributor Author

Disable/commented out Map View as @shivankacker is working on that. (#10377)

Copy link
Contributor

@Jacobjeevan Jacobjeevan left a comment

Choose a reason for hiding this comment

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

Check the expand all behavior; once I have manually expanded, expand all button should become collapse all (it doesn't, instead user has to click twice).

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

♻️ Duplicate comments (1)
src/pages/Facility/settings/locations/LocationList.tsx (1)

67-67: 🛠️ Refactor suggestion

Consider using pagination instead of a high limit.

Setting a high constant limit (LIMIT = 1000) could impact performance with large datasets. Consider implementing proper pagination or infinite scroll.

Based on the past review comment from Jacobjeevan suggesting to "switch to using query.paginated instead of high limit."

🧹 Nitpick comments (2)
src/pages/Facility/settings/locations/LocationList.tsx (2)

87-118: Optimize search implementation for better performance.

The current search implementation recursively traverses the location hierarchy for each search operation. Consider:

  1. Implementing debounced search
  2. Memoizing intermediate search results
+import { useMemo, useCallback, useDebounce } from 'react';
+
+const debouncedSearchQuery = useDebounce(searchQuery, 300);
+
+const searchCache = useMemo(() => new Map<string, boolean>(), [debouncedSearchQuery]);
+
 const hasMatchingChildren = useCallback(
   (parentId: string): boolean => {
+    if (searchCache.has(parentId)) {
+      return searchCache.get(parentId)!;
+    }
     const children = childrenMap.get(parentId) || [];
-    return children.some(
+    const result = children.some(
       (child) => matchesSearch(child.name) || hasMatchingChildren(child.id),
     );
+    searchCache.set(parentId, result);
+    return result;
   },
-  [childrenMap, searchQuery],
+  [childrenMap, debouncedSearchQuery, searchCache],
 );

180-263: Improve accessibility of the location row component.

Add ARIA attributes to enhance screen reader support:

 <TableRow
   className="group hover:bg-muted/50"
   style={{ "--indent": `${indent}rem` } as React.CSSProperties}
+  role="row"
+  aria-expanded={isExpanded}
 >
   <TableCell
     className={`${
       isTopLevel
         ? "bg-white font-bold text-gray-900"
         : "bg-white font-medium text-gray-900"
     } flex justify-between lg:flex-row flex-col pl-[var(--indent)] flex-wrap gap-2`}
+    role="cell"
   >
     <div className="flex items-center">
       {isTopLevel || children.length > 0 ? (
         <Button
           size="icon"
           variant="link"
           onClick={() => toggleRow(location.id)}
+          aria-label={`${isExpanded ? 'Collapse' : 'Expand'} ${location.name}`}
         >
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b44688 and 5b3f2ea.

📒 Files selected for processing (1)
  • src/pages/Facility/settings/locations/LocationList.tsx (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: cypress-run (1)
🔇 Additional comments (4)
src/pages/Facility/settings/locations/LocationList.tsx (4)

39-56: LGTM! Efficient hierarchy building implementation.

The buildLocationHierarchy function efficiently organizes locations using a Map for O(n) time complexity.


374-417: LGTM! Clear and informative help text implementation.

The info section provides clear instructions for users on both desktop and mobile views, with appropriate icons and responsive layout.


279-322: LGTM! Well-structured list view implementation.

The renderListView function handles loading states, empty states, and data presentation effectively with clear separation of concerns.


342-349:

❓ Verification inconclusive

Verify Map view implementation status.

The Map view is currently commented out with a note indicating it will be added later. This aligns with the PR objectives mentioning that @shivankacker is working on this feature in issue #10377.


🏁 Script executed:

#!/bin/bash
# Check the status of the Map view implementation
gh issue view 10377

Length of output: 110


Action: Please manually verify the Map view implementation status

The commented-out Map view code appears to be intentionally left in place until @shivankacker’s work for issue #10377 is ready. However, the automated check using gh issue view 10377 failed due to the repository not being recognized as a git repository. As a result, we could not confirm the implementation status automatically. Please manually verify that issue #10377 is still open and that the Map view functionality is pending.

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

🧹 Nitpick comments (2)
src/pages/Facility/settings/locations/LocationList.tsx (2)

84-114: Consider memoizing the search matcher function.

The matchesSearch function is recreated on every render. While it's a simple function, it's used in multiple places and could benefit from memoization.

-const matchesSearch = (name: string) =>
-  name.toLowerCase().includes(searchQuery.toLowerCase());
+const matchesSearch = useCallback(
+  (name: string) => name.toLowerCase().includes(searchQuery.toLowerCase()),
+  [searchQuery]
+);

276-321: Consider extracting the empty state card into a reusable component.

The empty state card could be reused across different list views in the application.

+const EmptyStateCard = ({ searchQuery, t }: { searchQuery: string; t: TFunction }) => (
+  <Card className="col-span-full">
+    <CardContent className="p-6 text-center text-gray-500">
+      {searchQuery ? t("no_locations_found") : t("no_locations_available")}
+    </CardContent>
+  </Card>
+);

 const renderListView = () => {
   if (isLoading) {
     return <TableSkeleton count={6} />;
   }

   if (!tableData?.length) {
-    return (
-      <Card className="col-span-full">
-        <CardContent className="p-6 text-center text-gray-500">
-          {searchQuery ? t("no_locations_found") : t("no_locations_available")}
-        </CardContent>
-      </Card>
-    );
+    return <EmptyStateCard searchQuery={searchQuery} t={t} />;
   }
   // ... rest of the code
 };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5b3f2ea and 0b3b404.

📒 Files selected for processing (1)
  • src/pages/Facility/settings/locations/LocationList.tsx (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Test
  • GitHub Check: cypress-run (1)
  • GitHub Check: OSSAR-Scan
🔇 Additional comments (2)
src/pages/Facility/settings/locations/LocationList.tsx (2)

39-56: LGTM! Well-structured hierarchy builder.

The buildLocationHierarchy function efficiently organizes locations into a parent-child structure using Maps, which provides O(n) time complexity.


435-440:

❓ Verification inconclusive

Consider adding loading state to the LocationSheet.

The LocationSheet component might need to handle loading states when fetching location details.


🏁 Script executed:

#!/bin/bash
# Check if LocationSheet handles loading states
ast-grep --pattern 'function LocationSheet({ $_ }) {
  $$$
  const [isLoading, setIsLoading] = useState($$$);
  $$$
}'

Length of output: 120


🏁 Script executed:

#!/bin/bash
# Check if LocationSheet is implemented as an arrow function with a loading state.
ast-grep --pattern 'const LocationSheet = ({ $_ }) => {
  $$$
  const \[isLoading, setIsLoading\] = useState($$$);
  $$$
}'

Length of output: 125


Attention: Missing Loading Indicator in LocationSheet

Our verification attempts did not find any evidence of a local loading state (e.g., an isLoading flag using useState) within the LocationSheet component. If the component is expected to fetch or update location details asynchronously, it would benefit from having a defined loading state to improve the user experience while data is being loaded.

  • Suggestion: Add a loading state (for example, const [isLoading, setIsLoading] = useState(false)) and adjust the UI accordingly (e.g., display a spinner or placeholder) during asynchronous operations.
  • Next Step: Confirm whether the absence of this state is intentional (perhaps managed externally) or if it should be incorporated directly in LocationSheet.

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

🧹 Nitpick comments (3)
src/pages/Facility/settings/locations/LocationList.tsx (3)

76-96: Consider memoizing search-related functions.

The search-related functions are recreated on every render. Consider extracting and memoizing them to improve performance.

+const matchesSearch = useCallback(
+  (name: string, query: string) =>
+    name.toLowerCase().includes(query.toLowerCase()),
+  []
+);

+const hasMatchingDescendant = useCallback(
+  (locationId: string, allLocations: LocationListType[], query: string): boolean => {
+    const children = allLocations.filter(
+      (loc) => loc.parent?.id === locationId,
+    );
+    return children.some(
+      (child) => matchesSearch(child.name, query) || hasMatchingDescendant(child.id, allLocations, query),
+    );
+  },
+  [matchesSearch]
+);

 const filteredData = useMemo(() => {
   if (!searchQuery) return data?.results || [];

   const allLocations = data?.results || [];
-  const matchesSearch = (name: string) =>
-    name.toLowerCase().includes(searchQuery.toLowerCase());

   return allLocations.filter(
     (location) =>
-      matchesSearch(location.name) || hasMatchingDescendant(location.id),
+      matchesSearch(location.name, searchQuery) || hasMatchingDescendant(location.id, allLocations, searchQuery),
   );
 }, [data?.results, searchQuery, matchesSearch, hasMatchingDescendant]);

155-188: Potential performance issue with search effect.

The search effect rebuilds the entire expanded rows state on every search query change. Consider optimizing this by:

  1. Debouncing the search input
  2. Using a more efficient state update mechanism
+import { useDebouncedCallback } from 'use-debounce';

+const debouncedSetSearchQuery = useDebouncedCallback(
+  (value: string) => setSearchQuery(value),
+  300
+);

// In the Input onChange handler:
-setSearchQuery(e.target.value);
+debouncedSetSearchQuery(e.target.value);

428-476: Consider extracting the info box into a separate component.

The info box markup is quite complex and could be extracted into a reusable component to improve maintainability.

+const LocationInfoBox = () => {
+  const { t } = useTranslation();
+  return (
+    <div className="rounded-lg border-2 border-blue-200 bg-blue-50 p-4">
+      {/* ... existing info box markup ... */}
+    </div>
+  );
+};

// In the main component:
{activeTab === "list" && <LocationInfoBox />}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf0698a and acfdd68.

📒 Files selected for processing (1)
  • src/pages/Facility/settings/locations/LocationList.tsx (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Test
  • GitHub Check: cypress-run (1)
  • GitHub Check: OSSAR-Scan
🔇 Additional comments (4)
src/pages/Facility/settings/locations/LocationList.tsx (4)

39-56: LGTM! Efficient hierarchy building implementation.

The buildLocationHierarchy function efficiently organizes locations into a parent-child structure using a Map, which provides O(1) lookup time.


67-74: Add error handling for the query.

The query could fail due to network issues or server errors, but there's no error handling in place.

 const { data, isLoading } = useQuery({
   queryKey: ["locations", facilityId],
   queryFn: query.paginated(locationApi.list, {
     pathParams: { facility_id: facilityId },
     queryParams: {},
   }),
   enabled: !!facilityId,
+  retry: 2,
+  onError: (error) => {
+    // Handle error appropriately
+    console.error('Failed to fetch locations:', error);
+  },
 });

210-226: Potential performance issue with recursive state updates.

The toggleAllChildren function recursively updates state for all children. For deeply nested structures with many nodes, this could cause performance issues.


479-488: Simplify the conditional rendering of views.

The current implementation has commented-out code and a redundant conditional that always renders the list view.

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

♻️ Duplicate comments (1)
src/pages/Facility/settings/locations/LocationList.tsx (1)

213-229: 🛠️ Refactor suggestion

Optimize recursive state updates in toggleAllChildren.

The current implementation recursively updates state for each child, which could cause performance issues with deeply nested structures.

Consider batching the updates:

 const toggleAllChildren = () => {
+  const getAllChildIds = (parentId: string, expand: boolean): Record<string, boolean> => {
+    const result: Record<string, boolean> = {};
+    getChildren(parentId).forEach((child) => {
+      result[child.id] = expand;
+      Object.assign(result, getAllChildIds(child.id, expand));
+    });
+    return result;
+  };
+
   setExpandedRows((prevExpandedRows) => {
-    const newExpandedRows = { ...prevExpandedRows };
-    const toggleChildren = (parentId: string, expand: boolean) => {
-      getChildren(parentId).forEach((child) => {
-        newExpandedRows[child.id] = expand;
-        toggleChildren(child.id, expand);
-      });
-    };
     const shouldExpand = !children.every(
       (child) => prevExpandedRows[child.id],
     );
-    newExpandedRows[location.id] = shouldExpand;
-    toggleChildren(location.id, shouldExpand);
-    return newExpandedRows;
+    return {
+      ...prevExpandedRows,
+      [location.id]: shouldExpand,
+      ...getAllChildIds(location.id, shouldExpand),
+    };
   });
 };
🧹 Nitpick comments (2)
src/pages/Facility/settings/locations/LocationList.tsx (2)

76-96: Consider optimizing deep recursion in search logic.

The hasMatchingDescendant function recursively traverses the entire location tree for each search. For deep hierarchies, this could impact performance.

Consider memoizing intermediate results or using an iterative approach:

-const hasMatchingDescendant = (locationId: string): boolean => {
+const hasMatchingDescendant = (locationId: string, cache = new Map<string, boolean>()): boolean => {
+  if (cache.has(locationId)) return cache.get(locationId)!;
+
   const children = allLocations.filter(
     (loc) => loc.parent?.id === locationId,
   );
-  return children.some(
+  const result = children.some(
     (child) => matchesSearch(child.name) || hasMatchingDescendant(child.id),
   );
+  cache.set(locationId, result);
+  return result;
 };

485-494: Simplify the conditional rendering of views.

Since the map view is commented out and will be added later, the conditional rendering can be simplified.

-{
-  activeTab === "list"
-    ? renderListView()
-    : /* Map view will be added later
-    <div className="h-[600px] bg-gray-100 rounded-lg flex items-center justify-center">
-      <p className="text-gray-500">{t("map_view_coming_soon")}</p>
-    </div>
-    */
-      renderListView() // Default to list view for now
-}
+{renderListView()}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between acfdd68 and d58e27b.

📒 Files selected for processing (2)
  • public/locale/en.json (6 hunks)
  • src/pages/Facility/settings/locations/LocationList.tsx (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Test
  • GitHub Check: cypress-run (1)
🔇 Additional comments (3)
src/pages/Facility/settings/locations/LocationList.tsx (2)

39-56: LGTM! Well-structured utility function.

The buildLocationHierarchy function efficiently organizes locations into a parent-child structure using a Map, making it easy to traverse the hierarchy.


335-380: LGTM! Well-structured list view implementation.

The renderListView function properly handles all states (loading, empty, populated) and uses semantic table structure for accessibility.

public/locale/en.json (1)

579-584: LGTM! Well-structured localization keys.

The new localization keys follow consistent naming patterns and properly handle dynamic content with placeholders for icons and text formatting.

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

♻️ Duplicate comments (3)
src/pages/Facility/settings/locations/LocationList.tsx (3)

213-229: 🛠️ Refactor suggestion

Optimize recursive state updates for better performance.

The current implementation of toggleAllChildren could cause performance issues with deeply nested structures.

This is a duplicate of a previous review comment. The suggestion to batch updates or use a more efficient state management approach still applies.


485-494: 🛠️ Refactor suggestion

Simplify the view rendering logic.

The current implementation has commented-out code and a redundant conditional that always renders the list view.

This is a duplicate of a previous review comment. The suggestion to simplify the conditional rendering still applies.


67-74: 🛠️ Refactor suggestion

Consider adding error handling and loading states.

The query configuration lacks error handling which could lead to silent failures.

 const { data, isLoading } = useQuery({
   queryKey: ["locations", facilityId],
   queryFn: query.paginated(locationApi.list, {
     pathParams: { facility_id: facilityId },
     queryParams: {},
   }),
   enabled: !!facilityId,
+  retry: 2,
+  onError: (error) => {
+    // Handle error appropriately using your error notification system
+    console.error('Failed to fetch locations:', error);
+  },
 });
🧹 Nitpick comments (1)
src/pages/Facility/settings/locations/LocationList.tsx (1)

193-333: Consider decomposing LocationRow into smaller components.

The LocationRow component is handling multiple responsibilities including rendering, state management, and child management. Consider breaking it down into smaller, more focused components.

+const LocationActions = ({ location, children, allExpanded, toggleAllChildren, handleEditLocation, isMobile }) => (
+  <div className="flex justify-between items-center gap-2">
+    {/* ... action buttons ... */}
+  </div>
+);

+const LocationName = ({ location, isTopLevel, children, isExpanded, toggleRow }) => (
+  <div className="flex items-center">
+    {/* ... name display logic ... */}
+  </div>
+);

 const LocationRow = ({
   location,
   // ... other props
 }) => {
   // ... setup code ...
   return (
     <>
       <TableRow>
         <TableCell>
-          {/* ... current implementation ... */}
+          <LocationName {...nameProps} />
+          {isTopLevel && <LocationActions {...actionProps} />}
         </TableCell>
       </TableRow>
       {/* ... children rendering ... */}
     </>
   );
 };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d58e27b and 66fc186.

📒 Files selected for processing (1)
  • src/pages/Facility/settings/locations/LocationList.tsx (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Test
  • GitHub Check: cypress-run (1)
  • GitHub Check: OSSAR-Scan
🔇 Additional comments (1)
src/pages/Facility/settings/locations/LocationList.tsx (1)

335-380: Well-structured list view implementation!

The list view implementation handles loading and empty states appropriately, and uses a table layout effectively for structured display.

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

🧹 Nitpick comments (1)
src/pages/Facility/settings/locations/components/LocationRow.tsx (1)

76-97: Simplify conditional rendering logic.

The nested ternary expressions make the code harder to read. Consider extracting this into a separate function for better maintainability.

-            {isTopLevel || children.length > 0 ? (
-              <Button
-                size="icon"
-                variant="link"
-                onClick={() => toggleRow(location.id)}
-                disabled={children.length === 0}
-              >
-                {isExpanded ? (
-                  <CareIcon icon="l-angle-down" className="h-5 w-5" />
-                ) : (
-                  <CareIcon icon="l-angle-right" className="h-5 w-5" />
-                )}
-              </Button>
-            ) : location.parent ? (
-              <CareIcon
-                icon="l-corner-down-right-alt"
-                className="h-4 w-4 text-gray-400 ml-4 mr-2"
-              />
-            ) : (
-              <div className="w-8" />
-            )}
+            {renderExpandButton()}

Add this helper function:

const renderExpandButton = () => {
  if (isTopLevel || children.length > 0) {
    return (
      <Button
        size="icon"
        variant="link"
        onClick={() => toggleRow(location.id)}
        disabled={children.length === 0}
      >
        <CareIcon 
          icon={isExpanded ? "l-angle-down" : "l-angle-right"} 
          className="h-5 w-5" 
        />
      </Button>
    );
  }
  
  if (location.parent) {
    return (
      <CareIcon
        icon="l-corner-down-right-alt"
        className="h-4 w-4 text-gray-400 ml-4 mr-2"
      />
    );
  }
  
  return <div className="w-8" />;
};
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66fc186 and a7c2ab6.

📒 Files selected for processing (2)
  • src/pages/Facility/settings/locations/LocationList.tsx (2 hunks)
  • src/pages/Facility/settings/locations/components/LocationRow.tsx (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: cypress-run (1)
🔇 Additional comments (7)
src/pages/Facility/settings/locations/components/LocationRow.tsx (3)

15-25: Well-structured props interface!

The props interface is well-defined with clear types and follows TypeScript best practices.


43-59: Optimize recursive state updates for better performance.

The recursive state updates in toggleAllChildren could impact performance with deeply nested structures.


148-160: LGTM! Recursive rendering is well implemented.

The recursive rendering of child rows with proper prop passing ensures consistent behavior across the hierarchy.

src/pages/Facility/settings/locations/LocationList.tsx (4)

35-52: Excellent implementation of location hierarchy!

The buildLocationHierarchy function efficiently organizes locations using a Map for O(1) lookups, making it performant for large datasets.


72-92: Optimize search and filtering logic to reduce redundancy.

Multiple implementations of similar filtering logic could impact performance with large datasets.

Also applies to: 100-108


189-236: Well-implemented list view with proper state handling!

The renderListView function effectively handles loading states, empty results, and table layout.


341-350: Simplify the conditional rendering of views.

The current implementation has commented-out code and a redundant conditional that always renders the list view.

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

♻️ Duplicate comments (1)
src/pages/Facility/settings/locations/LocationList.tsx (1)

72-125: 🛠️ Refactor suggestion

Optimize search and filtering logic to reduce code duplication.

The code has multiple implementations of the same search logic (matchesSearch function) and similar filtering patterns. This could impact performance and maintainability.

+const createSearchMatcher = (searchQuery: string) => {
+  const query = searchQuery.toLowerCase();
+  return (name: string) => name.toLowerCase().includes(query);
+};

+const createHierarchySearcher = (allLocations: LocationListType[], matchesSearch: (name: string) => boolean) => {
+  const hasMatchingDescendant = (locationId: string): boolean => {
+    const children = allLocations.filter((loc) => loc.parent?.id === locationId);
+    return children.some((child) => 
+      matchesSearch(child.name) || hasMatchingDescendant(child.id)
+    );
+  };
+  return hasMatchingDescendant;
+};

 const filteredData = useMemo(() => {
   if (!searchQuery) return data?.results || [];

   const allLocations = data?.results || [];
-  const matchesSearch = (name: string) =>
-    name.toLowerCase().includes(searchQuery.toLowerCase());
+  const matchesSearch = createSearchMatcher(searchQuery);
+  const hasMatchingDescendant = createHierarchySearcher(allLocations, matchesSearch);

   return allLocations.filter(
     (location) =>
       matchesSearch(location.name) || hasMatchingDescendant(location.id),
   );
 }, [data?.results, searchQuery]);
🧹 Nitpick comments (3)
src/pages/Facility/settings/locations/LocationList.tsx (3)

35-52: Add error handling to buildLocationHierarchy function.

The function should handle potential edge cases such as circular references in the parent-child relationships.

 function buildLocationHierarchy(locations: LocationListType[]) {
   const childrenMap = new Map<string, LocationListType[]>();
   const topLevelLocations: LocationListType[] = [];
+  const seenIds = new Set<string>();
 
   locations.forEach((location) => {
+    // Check for circular references
+    if (seenIds.has(location.id)) {
+      console.warn(`Circular reference detected for location ${location.id}`);
+      return;
+    }
+    seenIds.add(location.id);
+
     if (!location.parent || Object.keys(location.parent).length === 0) {
       topLevelLocations.push(location);
     } else {

142-152: Optimize toggleRow to avoid unnecessary re-renders.

The function modifies state directly and triggers multiple state updates. Consider using a functional update and batching the changes.

 const toggleRow = (id: string) => {
-  const newExpandedRows = { ...expandedRows };
-  newExpandedRows[id] = !newExpandedRows[id];
-  const children = getChildren(id);
-  children.forEach((child) => {
-    if (!child.has_children) {
-      newExpandedRows[child.id] = !newExpandedRows[child.id];
-    }
-  });
-  setExpandedRows(newExpandedRows);
+  setExpandedRows((prev) => {
+    const next = { ...prev };
+    next[id] = !next[id];
+    getChildren(id)
+      .filter(child => !child.has_children)
+      .forEach(child => {
+        next[child.id] = next[id];
+      });
+    return next;
+  });
 };

242-364: Enhance accessibility for the location management interface.

Consider adding ARIA labels and roles to improve screen reader support.

-    <Page title={t("locations")} hideTitleOnPage={true} className="p-0">
+    <Page 
+      title={t("locations")} 
+      hideTitleOnPage={true} 
+      className="p-0"
+      role="region"
+      aria-label={t("locations_management")}
+    >
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a7c2ab6 and b36b844.

📒 Files selected for processing (1)
  • src/pages/Facility/settings/locations/LocationList.tsx (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: Redirect rules - care-ohc
  • GitHub Check: Header rules - care-ohc
  • GitHub Check: Pages changed - care-ohc
  • GitHub Check: Test
  • GitHub Check: lint
  • GitHub Check: cypress-run (1)
  • GitHub Check: CodeQL-Build
  • GitHub Check: OSSAR-Scan
🔇 Additional comments (3)
src/pages/Facility/settings/locations/LocationList.tsx (3)

63-70: Add error handling for the query.

The query could fail due to network issues or server errors, but there's no error handling in place.


177-186: Handle potential null values safely.

The parent lookup logic could lead to runtime errors if the location is not found.


345-354: Simplify the conditional rendering of views.

The current implementation has commented-out code and a redundant conditional that always renders the list view.

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

🧹 Nitpick comments (6)
src/pages/Facility/settings/locations/components/LocationListView.tsx (4)

53-69: Consider optimizing recursive state updates.

The toggleAllChildren function recursively updates state for all children. For deeply nested structures with many nodes, this could cause performance issues.

Consider batching the updates:

 const toggleAllChildren = () => {
-  setExpandedRows((prevExpandedRows) => {
+  const getAllChildIds = (parentId: string, expand: boolean, ids: Record<string, boolean> = {}) => {
     const newExpandedRows = { ...prevExpandedRows };
-    const toggleChildren = (parentId: string, expand: boolean) => {
-      getChildren(parentId).forEach((child) => {
-        newExpandedRows[child.id] = expand;
-        toggleChildren(child.id, expand);
-      });
-    };
+    getChildren(parentId).forEach((child) => {
+      ids[child.id] = expand;
+      getAllChildIds(child.id, expand, ids);
+    });
+    return ids;
+  };
+
+  setExpandedRows((prevExpandedRows) => {
     const shouldExpand = !children.every(
       (child) => prevExpandedRows[child.id],
     );
-    newExpandedRows[location.id] = shouldExpand;
-    toggleChildren(location.id, shouldExpand);
-    return newExpandedRows;
+    return {
+      ...prevExpandedRows,
+      [location.id]: shouldExpand,
+      ...getAllChildIds(location.id, shouldExpand),
+    };
   });
 };

141-149: Improve accessibility for interactive elements.

The link and button elements need proper ARIA labels for better screen reader support.

-<Button variant="white" size={isMobile ? "xs" : "sm"} asChild>
+<Button 
+  variant="white" 
+  size={isMobile ? "xs" : "sm"} 
+  asChild
+  aria-label={t("see_location_details", { name: location.name })}
+>
   <Link
     href={`/location/${location.id}`}
     className="text-gray-900 flex items-center"
+    role="link"
   >

202-204: Enhance loading state feedback.

The current loading state uses a basic skeleton loader. Consider adding more context about what's being loaded.

-return <TableSkeleton count={6} />;
+return (
+  <div role="status" aria-live="polite">
+    <p className="sr-only">{t("loading_locations")}</p>
+    <TableSkeleton count={6} />
+  </div>
+);

206-214: Improve empty state messaging.

The empty state message could be more helpful by providing guidance on next steps.

 return (
   <Card className="col-span-full">
     <CardContent className="p-6 text-center text-gray-500">
-      {searchQuery ? t("no_locations_found") : t("no_locations_available")}
+      <p>
+        {searchQuery
+          ? t("no_locations_found_with_query", { query: searchQuery })
+          : t("no_locations_available_add_one")}
+      </p>
+      {!searchQuery && (
+        <Button
+          variant="link"
+          onClick={onAddLocation}
+          className="mt-2"
+        >
+          {t("add_first_location")}
+        </Button>
+      )}
     </CardContent>
   </Card>
 );
src/pages/Facility/settings/locations/LocationList.tsx (2)

54-61: Consider implementing pagination or virtual scrolling.

Loading all locations at once could impact performance with large datasets. Consider implementing pagination or virtual scrolling for better performance.

 const { data, isLoading } = useQuery({
   queryKey: ["locations", facilityId],
   queryFn: query.paginated(locationApi.list, {
     pathParams: { facility_id: facilityId },
-    queryParams: {},
+    queryParams: {
+      limit: 50,
+      offset: 0,
+    },
   }),
   enabled: !!facilityId,
+  onError: (error) => {
+    console.error('Failed to fetch locations:', error);
+  },
 });

145-182: Simplify search expansion logic.

The search effect is complex and could be simplified. Consider using a more declarative approach.

 useEffect(() => {
   if (!searchQuery) {
     setExpandedRows({});
     return;
   }

-  const allLocations = data?.results || [];
-  const matchesSearch = (name: string) =>
-    name.toLowerCase().includes(searchQuery.toLowerCase());
-
-  const hasMatchingDescendant = (locationId: string): boolean => {
-    const children = allLocations.filter(
-      (loc) => loc.parent?.id === locationId,
-    );
-    return children.some(
-      (child) => matchesSearch(child.name) || hasMatchingDescendant(child.id),
-    );
-  };
+  const matchingLocations = filteredData.filter(
+    location => createSearchMatcher(searchQuery)(location.name)
+  );
 
   const newExpandedRows: Record<string, boolean> = {};
-  allLocations.forEach((location) => {
-    if (matchesSearch(location.name) || hasMatchingDescendant(location.id)) {
-      let currentLoc = location;
-      while (currentLoc.parent?.id) {
-        newExpandedRows[currentLoc.parent.id] = true;
-        const parentLoc = allLocations.find(
-          (loc) => loc.id === currentLoc.parent?.id,
-        );
-        if (!parentLoc) {
-          break;
-        }
-        currentLoc = parentLoc;
-      }
+  matchingLocations.forEach(location => {
+    let currentLoc = location;
+    while (currentLoc.parent?.id) {
+      newExpandedRows[currentLoc.parent.id] = true;
+      currentLoc = data?.results.find(
+        loc => loc.id === currentLoc.parent?.id
+      ) ?? currentLoc;
     }
   });
 
   setExpandedRows(newExpandedRows);
-}, [searchQuery, data?.results]);
+}, [searchQuery, filteredData, data?.results]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b36b844 and 968910b.

📒 Files selected for processing (2)
  • src/pages/Facility/settings/locations/LocationList.tsx (2 hunks)
  • src/pages/Facility/settings/locations/components/LocationListView.tsx (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: cypress-run (1)

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

♻️ Duplicate comments (1)
src/pages/Facility/settings/locations/LocationList.tsx (1)

59-66: 🛠️ Refactor suggestion

Add error handling to the query configuration.

The query lacks error handling and retry configuration, which could lead to poor user experience during network issues.

 const { data, isLoading } = useQuery({
   queryKey: ["locations", facilityId],
   queryFn: query.paginated(locationApi.list, {
     pathParams: { facility_id: facilityId },
     queryParams: {},
   }),
   enabled: !!facilityId,
+  retry: 2,
+  onError: (error) => {
+    // Handle error appropriately, e.g., show toast notification
+    console.error('Failed to fetch locations:', error);
+  },
+  staleTime: 5 * 60 * 1000, // Cache data for 5 minutes
 });
🧹 Nitpick comments (3)
src/pages/Facility/settings/locations/LocationList.tsx (3)

31-48: Optimize the hierarchy building function for better performance.

The buildLocationHierarchy function could be optimized to reduce iterations and Map lookups.

 function buildLocationHierarchy(locations: LocationListType[]) {
-  const childrenMap = new Map<string, LocationListType[]>();
-  const topLevelLocations: LocationListType[] = [];
+  const result = locations.reduce((acc, location) => {
+    if (!location.parent || Object.keys(location.parent).length === 0) {
+      acc.topLevelLocations.push(location);
+    } else {
+      const parentId = location.parent.id;
+      if (!acc.childrenMap.has(parentId)) {
+        acc.childrenMap.set(parentId, []);
+      }
+      acc.childrenMap.get(parentId)!.push(location);
+    }
+    return acc;
+  }, {
+    childrenMap: new Map<string, LocationListType[]>(),
+    topLevelLocations: [] as LocationListType[]
+  });

-  locations.forEach((location) => {
-    if (!location.parent || Object.keys(location.parent).length === 0) {
-      topLevelLocations.push(location);
-    } else {
-      const parentId = location.parent.id;
-      if (!childrenMap.has(parentId)) {
-        childrenMap.set(parentId, []);
-      }
-      childrenMap.get(parentId)?.push(location);
-    }
-  });

-  return { childrenMap, topLevelLocations };
+  return result;
 }

139-149: Improve the row toggle handler for better type safety and immutability.

The toggleRow function mutates state directly and lacks type safety.

 const toggleRow = (id: string) => {
-  const newExpandedRows = { ...expandedRows };
-  newExpandedRows[id] = !newExpandedRows[id];
-  const children = getChildren(id);
-  children.forEach((child) => {
-    if (!child.has_children) {
-      newExpandedRows[child.id] = !newExpandedRows[child.id];
-    }
-  });
-  setExpandedRows(newExpandedRows);
+  setExpandedRows((prev) => {
+    const childUpdates = getChildren(id)
+      .filter((child) => !child.has_children)
+      .reduce((acc, child) => ({
+        ...acc,
+        [child.id]: !prev[child.id]
+      }), {});
+
+    return {
+      ...prev,
+      [id]: !prev[id],
+      ...childUpdates
+    };
+  });
 };

208-215: Remove commented-out map view code.

The commented-out map view code should be removed as it's not currently used and clutters the codebase. If needed, this code can be retrieved from version control when implementing the map view feature.

-                  {/* Map view will be added later
-                  <TabsTrigger value="map" id="location-map-view">
-                    <div className="flex items-center gap-1">
-                      <CareIcon icon="l-map" className="text-lg" />
-                      <span>{t("map")}</span>
-                    </div>
-                  </TabsTrigger>
-                  */}

-          {/* Map view will be added later, for now always show list view */}

Also applies to: 293-293

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 968910b and 9ce4b67.

📒 Files selected for processing (1)
  • src/pages/Facility/settings/locations/LocationList.tsx (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: cypress-run (1)

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

Successfully merging this pull request may close these issues.

Add a new list view to locations in the facility settings
3 participants