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 all commits
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
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions public/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@
"active_files": "Active Files",
"active_prescriptions": "Active Prescriptions",
"add": "Add",
"addLocation": "Click \"Add Location\" to add a main location.",
"add_another_session": "Add another session",
"add_as": "Add as",
"add_attachments": "Add Attachments",
Expand Down Expand Up @@ -1034,6 +1035,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 Expand Up @@ -1260,6 +1262,7 @@
"manage_tags_description": "Add or remove tags for this questionnaire",
"manage_user": "Manage User",
"manufacturer": "Manufacturer",
"map": "Map",
"map_acronym": "M.A.P.",
"mark_active": "Mark Active",
"mark_all_as_read": "Mark all as Read",
Expand Down Expand Up @@ -1891,6 +1894,7 @@
"search_user": "Search User",
"search_user_description": "Search for a user and assign a role to add them to the patient.",
"searching": "Searching...",
"seeDetails": "Hover or focus to reveal \"See Details\", which opens a page for managing sub-locations.",
"see_attachments": "See Attachments",
"see_note": "See Note",
"select": "Select",
Expand Down
202 changes: 141 additions & 61 deletions src/pages/Facility/settings/locations/LocationList.tsx
Original file line number Diff line number Diff line change
@@ -1,122 +1,202 @@
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 [expandedAll, setExpandedAll] = useState(false);
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,
},
}),
});

const handleAddLocation = () => {
setSelectedLocation(null);
setIsSheetOpen(true);
};

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

const handleSheetClose = () => {
setIsSheetOpen(false);
setSelectedLocation(null);
};
const handleExpandAll = () => {
setExpandedAll((prev) => !prev);
};

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="list" id="user-card-view">
<div className="flex items-center gap-2">
<CareIcon icon="l-list-ul" className="text-lg" />
<span>{t("list")}</span>
</div>
</TabsTrigger>
<TabsTrigger value="map" id="user-list-view">
<div className="flex items-center gap-2">
<CareIcon icon="l-sitemap" className="text-lg" />
<span>{t("map")}</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>
{t("addLocation")}
<br />
{t("seeDetails")}
</p>
</div>
</div>

{isLoading ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<CardGridSkeleton count={6} />
</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 className="w-[80%]">{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 items-center">
<Button
variant="outline"
className="mr-3"
onClick={handleExpandAll}
>
<CareIcon icon="l-plus" className="w-4 h-5" />
{expandedAll ? "Collapse All" : "Expand All"}
</Button>
<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={1}
expandedAll={expandedAll}
/>
) : null}
</React.Fragment>
))}
</TableBody>
</Table>
</div>
)}
<LocationSheet
Expand Down
Loading
Loading