From 4f5e866eb0046688cf70d80bf9494d9ff767f7ef Mon Sep 17 00:00:00 2001 From: Raj kumar <150310085+rajku-dev@users.noreply.github.com> Date: Tue, 4 Mar 2025 17:57:14 +0530 Subject: [PATCH] Add Support for Location Association History of Devices (#10950) --- public/locale/en.json | 1 + .../settings/devices/DeviceDetail.tsx | 8 ++ .../devices/DeviceLocationHistory.tsx | 100 ++++++++++++++++++ .../components/AssociateLocationSheet.tsx | 3 + .../devices/components/DeviceLocationCard.tsx | 81 ++++++++++++++ src/pages/Facility/settings/layout.tsx | 4 + src/types/device/device.ts | 8 ++ src/types/device/deviceApi.ts | 12 ++- 8 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 src/pages/Facility/settings/devices/DeviceLocationHistory.tsx create mode 100644 src/pages/Facility/settings/devices/components/DeviceLocationCard.tsx diff --git a/public/locale/en.json b/public/locale/en.json index 9c51648b7b9..4adcc067ccc 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -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", diff --git a/src/pages/Facility/settings/devices/DeviceDetail.tsx b/src/pages/Facility/settings/devices/DeviceDetail.tsx index d252f111c08..407005ed07f 100644 --- a/src/pages/Facility/settings/devices/DeviceDetail.tsx +++ b/src/pages/Facility/settings/devices/DeviceDetail.tsx @@ -7,6 +7,8 @@ import { useTranslation } from "react-i18next"; import { cn } from "@/lib/utils"; +import CareIcon from "@/CAREUI/icons/CareIcon"; + import { AlertDialog, AlertDialogAction, @@ -144,6 +146,12 @@ export default function DeviceDetail({ facilityId, deviceId }: Props) {
+ + + diff --git a/src/pages/Facility/settings/devices/DeviceLocationHistory.tsx b/src/pages/Facility/settings/devices/DeviceLocationHistory.tsx new file mode 100644 index 00000000000..60367aa2dff --- /dev/null +++ b/src/pages/Facility/settings/devices/DeviceLocationHistory.tsx @@ -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 ( + +
+ {isLoading ? ( +
+
+ +
+
+ ) : ( +
+ {locationsData?.results?.length === 0 ? ( +
+
+
+
+ + {t("no_locations_found")} + +
+
+
+
+ ) : ( +
    + {locationsData?.results?.map((locationData) => ( +
  • + +
  • + ))} +
    +
    RESULTS_PER_PAGE_LIMIT + ? "visible" + : "invisible", + )} + > + setQueryParams({ page })} + /> +
    +
    +
+ )} +
+ )} +
+
+ ); +}; + +export default DeviceLocationHistory; diff --git a/src/pages/Facility/settings/devices/components/AssociateLocationSheet.tsx b/src/pages/Facility/settings/devices/components/AssociateLocationSheet.tsx index 86b1c8c0761..046f4a47015 100644 --- a/src/pages/Facility/settings/devices/components/AssociateLocationSheet.tsx +++ b/src/pages/Facility/settings/devices/components/AssociateLocationSheet.tsx @@ -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); diff --git a/src/pages/Facility/settings/devices/components/DeviceLocationCard.tsx b/src/pages/Facility/settings/devices/components/DeviceLocationCard.tsx new file mode 100644 index 00000000000..b7b1abecbd6 --- /dev/null +++ b/src/pages/Facility/settings/devices/components/DeviceLocationCard.tsx @@ -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 ( + + +
+
+ + {location.name} + + +
+
+ +
+

{t("locations")}

+ + {t(`location_form__${location?.form}`)} + + + {location?.status} + +
+ +
+
+
{t("associated_by")}
+
+ {`${created_by.first_name} ${created_by.last_name}`} +
+
+ +
+
+ {t("association_start_date")} +
+
+ {start ? formatDateTime(start) : t("not_started")} +
+
+ + { +
+
+ {t("association_end_date")} +
+
+ {end ? formatDateTime(end) : "-"} +
+
+ } +
+
+
+ ); +}; diff --git a/src/pages/Facility/settings/layout.tsx b/src/pages/Facility/settings/layout.tsx index 6d256ec4cff..c92dec3537d 100644 --- a/src/pages/Facility/settings/layout.tsx +++ b/src/pages/Facility/settings/layout.tsx @@ -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"; @@ -43,6 +44,9 @@ const getRoutes = (facilityId: string) => ({ "/devices/:id/edit": ({ id }: { id: string }) => ( ), + "/devices/:id/locationHistory": ({ id }: { id: string }) => ( + + ), "*": () => , }); diff --git a/src/types/device/device.ts b/src/types/device/device.ts index 60ec9aa9ff8..6c24e9a7e39 100644 --- a/src/types/device/device.ts +++ b/src/types/device/device.ts @@ -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; diff --git a/src/types/device/deviceApi.ts b/src/types/device/deviceApi.ts index 929c13a25a6..330460bf923 100644 --- a/src/types/device/deviceApi.ts +++ b/src/types/device/deviceApi.ts @@ -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: { @@ -44,4 +49,9 @@ export default { TRes: Type(), TBody: Type<{ location: string }>(), }, + locationHistory: { + path: "/api/v1/facility/{facilityId}/device/{id}/location_history/", + method: HttpMethod.GET, + TRes: Type>(), + }, };