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

New List view for locations in the facility settings #10398

Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions public/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,7 @@
"filter_by": "Filter By",
"filter_by_category": "Filter by category",
"filter_by_date": "Filter by Date",
"filter_location": "Filter by Locations",
"filters": "Filters",
"first_name": "First Name",
"food": "Food",
Expand Down
194 changes: 137 additions & 57 deletions src/pages/Facility/settings/locations/LocationList.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,56 @@
/* eslint-disable i18next/no-literal-string */
import { useQuery } from "@tanstack/react-query";
import { useNavigate } from "raviger";
import { useState } from "react";
import React from "react";
import { useTranslation } from "react-i18next";

import CareIcon from "@/CAREUI/icons/CareIcon";

import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";

import Pagination from "@/components/Common/Pagination";
import { CardGridSkeleton } from "@/components/Common/SkeletonLoading";

import query from "@/Utils/request/query";
import { LocationList as LocationListType } from "@/types/location/location";
import { useView } from "@/Utils/useView";
import {
LocationList as LocationListType,
getLocationFormLabel,
} from "@/types/location/location";
import locationApi from "@/types/location/locationApi";

import LocationSheet from "./LocationSheet";
import { LocationCard } from "./components/LocationCard";
import { LocationChildren } from "./components/LocationChildren";

interface Props {
facilityId: string;
}

export default function LocationList({ facilityId }: Props) {
const { t } = useTranslation();
const [page, setPage] = useState(1);
const [searchQuery, setSearchQuery] = useState("");
const [selectedLocation, setSelectedLocation] =
useState<LocationListType | null>(null);
const [isSheetOpen, setIsSheetOpen] = useState(false);
const limit = 12;
const [activeTab, setActiveTab] = useView("users", "card");
const navigate = useNavigate();

const { data, isLoading } = useQuery({
queryKey: ["locations", facilityId, page, limit, searchQuery],
queryKey: ["locations", facilityId, searchQuery],
queryFn: query.debounced(locationApi.list, {
pathParams: { facility_id: facilityId },
queryParams: {
parent: "",
offset: (page - 1) * limit,
limit,
name: searchQuery || undefined,
},
}),
Expand All @@ -49,36 +61,68 @@ export default function LocationList({ facilityId }: Props) {
setIsSheetOpen(true);
};

const handleEditLocation = (location: LocationListType) => {
setSelectedLocation(location);
setIsSheetOpen(true);
};

const handleSheetClose = () => {
setIsSheetOpen(false);
setSelectedLocation(null);
};

return (
<div className="space-y-6">
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div className="flex items-center gap-4">
<h2 className="text-lg font-semibold">{t("locations")}</h2>
<Button variant="default" onClick={handleAddLocation}>
<div className="">
<h2 className="text-lg font-semibold mb-2">{t("locations")}</h2>
<div className="flex justify-between items-center gap-4 w-full ">
<div className="flex items-center gap-4">
<Tabs
value={activeTab}
onValueChange={(value) => setActiveTab(value as "card" | "list")}
className="ml-auto"
>
<TabsList className="flex">
<TabsTrigger value="card" id="user-card-view">
<div className="flex items-center gap-2">
<CareIcon icon="l-credit-card" className="text-lg" />
<span>{t("card")}</span>
</div>
</TabsTrigger>
<TabsTrigger value="list" id="user-list-view">
<div className="flex items-center gap-2">
<CareIcon icon="l-list-ul" className="text-lg" />
<span>{t("list")}</span>
</div>
</TabsTrigger>
</TabsList>
</Tabs>
<div className="w-72">
<Input
placeholder={t("filter_location")}
value={searchQuery}
onChange={(e) => {
setSearchQuery(e.target.value);
}}
className="w-full"
/>
</div>
AnveshNalimela marked this conversation as resolved.
Show resolved Hide resolved
</div>

<Button variant="primary" onClick={handleAddLocation}>
<CareIcon icon="l-plus" className="h-4 w-4 mr-2" />
{t("add_location")}
</Button>
</div>
<div className="w-72">
<Input
placeholder={t("search_by_name")}
value={searchQuery}
onChange={(e) => {
setSearchQuery(e.target.value);
setPage(1);
}}
className="w-full"
/>
</div>
<div className="flex items-center gap-2 p-4 border border-blue-200 bg-blue-50 rounded-lg">
<div className="text-blue-500">
<CareIcon icon="l-exclamation-octagon" className="w-6 h-6 mr-2" />
</div>
<div className="text-sm text-blue-900">
<p>
Click <span className="font-semibold">"Add Location"</span> to add a
main location.
<br />
Hover or focus to reveal{" "}
<span className="font-semibold">"See Details"</span>, which opens a
page for managing sub-locations.
</p>
</div>
</div>

Expand All @@ -88,35 +132,71 @@ export default function LocationList({ facilityId }: Props) {
</div>
) : (
<div className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{data?.results?.length ? (
data.results.map((location: LocationListType) => (
<LocationCard
key={location.id}
location={location}
onEdit={handleEditLocation}
/>
))
) : (
<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>
)}
</div>
{data && data.count > limit && (
<div className="flex justify-center">
<Pagination
data={{ totalCount: data.count }}
onChange={(page, _) => setPage(page)}
defaultPerPage={limit}
cPage={page}
/>
</div>
)}
<Table>
<TableHeader>
<TableRow className="divide-x bg-gray-100">
<TableHead>{t("location")}</TableHead>
<TableHead>{t("location_form")}</TableHead>
</TableRow>
</TableHeader>

<TableBody>
{data?.results.map((location) => (
<React.Fragment key={location.id}>
<TableRow
onClick={(e) => {
e.stopPropagation();
setSelectedLocation(
selectedLocation?.id === location.id ? null : location,
);
}}
className="divide-x font-medium cursor-pointer bg-white dark:bg-gray-900"
>
AnveshNalimela marked this conversation as resolved.
Show resolved Hide resolved
<TableCell className="pl-4 font-bold flex justify-between items-center cursor-pointer group">
{/* Left Section: Icon & Name */}
<div className="flex items-center">
<CareIcon
icon={
selectedLocation?.id === location.id
? "l-angle-down"
: "l-angle-right-b"
}
className="w-5 h-5"
/>
<span className="ml-2">{location.name}</span>
</div>

{/* Right Section: Button */}
<div className="flex">
<Button
variant="outline"
className="opacity-0 group-hover:opacity-100 transition-opacity duration-300 ml-auto"
onClick={() => navigate(`/location/${location.id}`)}
>
<CareIcon icon="l-eye" className="w-4 h-5 mr-2" />
See Details
</Button>
</div>
</TableCell>

<TableCell>
{getLocationFormLabel(location?.form)}
</TableCell>
</TableRow>

{/* Recursive Child Rendering */}
{selectedLocation?.id === location.id &&
location.has_children ? (
<LocationChildren
facilityId={facilityId}
location={location}
level={0}
/>
) : null}
</React.Fragment>
))}
</TableBody>
</Table>
</div>
)}
<LocationSheet
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useQuery } from "@tanstack/react-query";
import React from "react";

import CareIcon from "@/CAREUI/icons/CareIcon";

import { TableCell, TableRow } from "@/components/ui/table";

import query from "@/Utils/request/query";
import {
LocationList as LocationListType,
getLocationFormLabel,
} from "@/types/location/location";
import locationApi from "@/types/location/locationApi";

interface Props {
facilityId: string;
location: LocationListType;
level: number;
}

export function LocationChildren({ facilityId, location, level }: Props) {
const paddingClasses = ["pl-4", "pl-8", "pl-12", "pl-16", "pl-20"];
AnveshNalimela marked this conversation as resolved.
Show resolved Hide resolved
const { data: children } = useQuery({
queryKey: ["locations", facilityId, location?.id, "children"],
queryFn: query.debounced(locationApi.list, {
pathParams: { facility_id: facilityId },
queryParams: {
parent: location?.id,
},
}),
});
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

Add error handling for the query.

The query implementation lacks error handling which could lead to runtime issues if the API call fails.

Add error state handling:

- const { data: children } = useQuery({
+ const { data: children, error } = useQuery({
   queryKey: ["locations", facilityId, location?.id, "children"],
   queryFn: query.debounced(locationApi.list, {
     pathParams: { facility_id: facilityId },
     queryParams: {
       parent: location?.id,
     },
   }),
 });
+ 
+ if (error) {
+   return <div>Error loading child locations: {error.message}</div>;
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { data: children } = useQuery({
queryKey: ["locations", facilityId, location?.id, "children"],
queryFn: query.debounced(locationApi.list, {
pathParams: { facility_id: facilityId },
queryParams: {
parent: location?.id,
},
}),
});
const { data: children, error } = useQuery({
queryKey: ["locations", facilityId, location?.id, "children"],
queryFn: query.debounced(locationApi.list, {
pathParams: { facility_id: facilityId },
queryParams: {
parent: location?.id,
},
}),
});
if (error) {
return <div>Error loading child locations: {error.message}</div>;
}

return children?.results?.map((childLocation: LocationListType) => (
<React.Fragment key={childLocation.id}>
<TableRow className="divide-x font-medium cursor-pointer bg-gray-50 dark:bg-gray-800">
<TableCell className={paddingClasses[level]}>
{" "}
{/* Indentation for hierarchy */}
<CareIcon icon="l-corner-down-right" /> {childLocation.name}
</TableCell>
<TableCell>{getLocationFormLabel(childLocation?.form)}</TableCell>
</TableRow>
{childLocation.has_children && (
<LocationChildren
facilityId={facilityId}
location={childLocation}
level={level + 1}
/>
)}
</React.Fragment>
));
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

Add null check and loading state for children data.

The component doesn't handle the loading state or null children data, which could cause rendering issues.

Add proper checks:

+ if (!children?.results) {
+   return null;
+ }

  return children?.results?.map((childLocation: LocationListType) => (
    <React.Fragment key={childLocation.id}>
📝 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
return children?.results?.map((childLocation: LocationListType) => (
<React.Fragment key={childLocation.id}>
<TableRow className="divide-x font-medium cursor-pointer bg-gray-50 dark:bg-gray-800">
<TableCell className={paddingClasses[level]}>
{" "}
{/* Indentation for hierarchy */}
<CareIcon icon="l-corner-down-right" /> {childLocation.name}
</TableCell>
<TableCell>{getLocationFormLabel(childLocation?.form)}</TableCell>
</TableRow>
{childLocation.has_children && (
<LocationChildren
facilityId={facilityId}
location={childLocation}
level={level + 1}
/>
)}
</React.Fragment>
));
if (!children?.results) {
return null;
}
return children?.results?.map((childLocation: LocationListType) => (
<React.Fragment key={childLocation.id}>
<TableRow className="divide-x font-medium cursor-pointer bg-gray-50 dark:bg-gray-800">
<TableCell className={paddingClasses[level]}>
{" "}
{/* Indentation for hierarchy */}
<CareIcon icon="l-corner-down-right" /> {childLocation.name}
</TableCell>
<TableCell>{getLocationFormLabel(childLocation?.form)}</TableCell>
</TableRow>
{childLocation.has_children && (
<LocationChildren
facilityId={facilityId}
location={childLocation}
level={level + 1}
/>
)}
</React.Fragment>
));

}
Loading