Skip to content

Commit

Permalink
Add Support for Location Association History of Devices (#10950)
Browse files Browse the repository at this point in the history
  • Loading branch information
rajku-dev authored Mar 4, 2025
1 parent dd17a1d commit 4f5e866
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 1 deletion.
1 change: 1 addition & 0 deletions public/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,7 @@
"device_availability_status_lost": "Lost",
"device_contact_description": "Contact points associated with this device",
"device_information": "Device Information",
"device_location_history": "Device Location History",
"device_not_found": "Device not found",
"device_status_active": "Active",
"device_status_entered_in_error": "Entered in Error",
Expand Down
8 changes: 8 additions & 0 deletions src/pages/Facility/settings/devices/DeviceDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { useTranslation } from "react-i18next";

import { cn } from "@/lib/utils";

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

import {
AlertDialog,
AlertDialogAction,
Expand Down Expand Up @@ -144,6 +146,12 @@ export default function DeviceDetail({ facilityId, deviceId }: Props) {
<div className="flex items-center justify-between">
<PageTitle title={device.registered_name} />
<div className="flex items-center gap-2">
<Link href={`/devices/${deviceId}/locationHistory`}>
<Button variant="outline_primary" className="mr-3">
<CareIcon icon="l-location-point" className="h-4 w-4" />
{t("location_history")}
</Button>
</Link>
<Link href={`/devices/${deviceId}/edit`}>
<Button variant="outline">{t("edit")}</Button>
</Link>
Expand Down
100 changes: 100 additions & 0 deletions src/pages/Facility/settings/devices/DeviceLocationHistory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { useQuery } from "@tanstack/react-query";
import { useQueryParams } from "raviger";
import { useTranslation } from "react-i18next";

import { cn } from "@/lib/utils";

import Page from "@/components/Common/Page";
import PaginationComponent from "@/components/Common/Pagination";
import { CardListSkeleton } from "@/components/Common/SkeletonLoading";

import { RESULTS_PER_PAGE_LIMIT } from "@/common/constants";

import query from "@/Utils/request/query";
import { DeviceLocationCard } from "@/pages/Facility/settings/devices/components/DeviceLocationCard";
import deviceApi from "@/types/device/deviceApi";

interface Props {
facilityId: string;
deviceId: string;
}

const DeviceLocationHistory = ({ facilityId, deviceId }: Props) => {
const { t } = useTranslation();

const [qParams, setQueryParams] = useQueryParams<{ page?: number }>();

const { data: locationsData, isLoading } = useQuery({
queryKey: ["deviceLocationHistory", facilityId, deviceId, qParams],
queryFn: query(deviceApi.locationHistory, {
queryParams: {
limit: RESULTS_PER_PAGE_LIMIT,
offset: ((qParams.page ?? 1) - 1) * RESULTS_PER_PAGE_LIMIT,
},
pathParams: {
facilityId,
id: deviceId,
},
}),
});

return (
<Page title={t("device_location_history")} className="mt-8">
<div>
{isLoading ? (
<div>
<div className="grid gap-5 my-5">
<CardListSkeleton count={RESULTS_PER_PAGE_LIMIT} />
</div>
</div>
) : (
<div>
{locationsData?.results?.length === 0 ? (
<div className="p-2">
<div className="h-full space-y-2 rounded-lg bg-white px-7 py-12 border border-secondary-300">
<div className="flex w-full items-center justify-center text-lg text-secondary-600">
<div className="h-full flex w-full items-center justify-center">
<span className="text-sm text-gray-500">
{t("no_locations_found")}
</span>
</div>
</div>
</div>
</div>
) : (
<ul className="grid gap-4 my-5">
{locationsData?.results?.map((locationData) => (
<li key={locationData.id} className="w-full">
<DeviceLocationCard
key={locationData.id}
locationData={locationData}
/>
</li>
))}
<div className="flex w-full items-center justify-center">
<div
className={cn(
"flex w-full justify-center",
(locationsData?.count ?? 0) > RESULTS_PER_PAGE_LIMIT
? "visible"
: "invisible",
)}
>
<PaginationComponent
cPage={qParams.page ?? 1}
defaultPerPage={RESULTS_PER_PAGE_LIMIT}
data={{ totalCount: locationsData?.count ?? 0 }}
onChange={(page) => setQueryParams({ page })}
/>
</div>
</div>
</ul>
)}
</div>
)}
</div>
</Page>
);
};

export default DeviceLocationHistory;
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ export default function AssociateLocationSheet({
queryClient.invalidateQueries({
queryKey: ["device", facilityId, deviceId],
});
queryClient.invalidateQueries({
queryKey: ["deviceLocationHistory", facilityId, deviceId],
});
toast.success(t("location_associated_successfully"));
onOpenChange(false);
setSelectedLocation(null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { t } from "i18next";
import { Link } from "raviger";

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

import { Badge } from "@/components/ui/badge";
import { Card, CardContent } from "@/components/ui/card";

import { formatDateTime } from "@/Utils/utils";
import { DeviceLocationHistory } from "@/types/device/device";

interface LocationCardProps {
locationData: DeviceLocationHistory;
}

export const DeviceLocationCard = ({ locationData }: LocationCardProps) => {
const { start, end, location, created_by } = locationData;

return (
<Card className="flex-1 p-4">
<CardContent className="p-4 sm:p-3 space-y-4">
<div className="flex flex-wrap gap-6 sm:gap-6">
<div className="flex gap-3 text-xl font-semibold capitalize">
<Link
href={`/location/${location.id}`}
className="text-gray-950 font-semibold flex items-start gap-0.5"
id="patient-details"
>
{location.name}
<CareIcon
icon="l-external-link-alt"
className="w-3 h-3 opacity-50 mt-1"
/>
</Link>
</div>
</div>

<div className="flex gap-3">
<h2 className="text-lg font-semibold">{t("locations")}</h2>
<Badge variant="outline">
{t(`location_form__${location?.form}`)}
</Badge>
<Badge
variant={location?.status === "active" ? "default" : "secondary"}
>
{location?.status}
</Badge>
</div>

<div className="grid sm:flex sm:flex-wrap gap-7">
<div className="w-full mx-2 sm:w-auto">
<div className="text-gray-600 text-sm">{t("associated_by")}</div>
<div className="font-semibold text-base flex items-center gap-2">
{`${created_by.first_name} ${created_by.last_name}`}
</div>
</div>

<div className="w-full mx-3 sm:w-auto">
<div className="text-gray-600 text-sm">
{t("association_start_date")}
</div>
<div className="font-semibold text-base">
{start ? formatDateTime(start) : t("not_started")}
</div>
</div>

{
<div className="w-full mx-3 sm:w-auto">
<div className="text-gray-600 text-sm">
{t("association_end_date")}
</div>
<div className="font-semibold text-base">
{end ? formatDateTime(end) : "-"}
</div>
</div>
}
</div>
</CardContent>
</Card>
);
};
4 changes: 4 additions & 0 deletions src/pages/Facility/settings/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ErrorPage from "@/components/ErrorPages/DefaultErrorPage";

import CreateDevice from "@/pages/Facility/settings/devices/CreateDevice";
import DeviceDetail from "@/pages/Facility/settings/devices/DeviceDetail";
import DeviceLocationHistory from "@/pages/Facility/settings/devices/DeviceLocationHistory";
import DevicesList from "@/pages/Facility/settings/devices/DevicesList";
import UpdateDevice from "@/pages/Facility/settings/devices/UpdateDevice";

Expand Down Expand Up @@ -43,6 +44,9 @@ const getRoutes = (facilityId: string) => ({
"/devices/:id/edit": ({ id }: { id: string }) => (
<UpdateDevice facilityId={facilityId} deviceId={id} />
),
"/devices/:id/locationHistory": ({ id }: { id: string }) => (
<DeviceLocationHistory facilityId={facilityId} deviceId={id} />
),
"*": () => <ErrorPage />,
});

Expand Down
8 changes: 8 additions & 0 deletions src/types/device/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,12 @@ export interface DeviceList extends DeviceBase {
id: string;
}

export interface DeviceLocationHistory {
id: string;
created_by: UserBase;
location: LocationList;
start: string;
end: string;
}

export type DeviceWrite = DeviceBase;
12 changes: 11 additions & 1 deletion src/types/device/deviceApi.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { HttpMethod, Type } from "@/Utils/request/api";
import { PaginatedResponse } from "@/Utils/request/types";

import { DeviceDetail, DeviceList, DeviceWrite } from "./device";
import {
DeviceDetail,
DeviceList,
DeviceLocationHistory,
DeviceWrite,
} from "./device";

export default {
list: {
Expand Down Expand Up @@ -44,4 +49,9 @@ export default {
TRes: Type<DeviceDetail>(),
TBody: Type<{ location: string }>(),
},
locationHistory: {
path: "/api/v1/facility/{facilityId}/device/{id}/location_history/",
method: HttpMethod.GET,
TRes: Type<PaginatedResponse<DeviceLocationHistory>>(),
},
};

0 comments on commit 4f5e866

Please sign in to comment.