diff --git a/src/Components/Assets/AssetTypes.tsx b/src/Components/Assets/AssetTypes.tsx index 436c9370dd8..300cfc63758 100644 --- a/src/Components/Assets/AssetTypes.tsx +++ b/src/Components/Assets/AssetTypes.tsx @@ -1,6 +1,7 @@ import { BedModel } from "../Facility/models"; import { PerformedByModel } from "../HCX/misc"; import { PatientModel } from "../Patient/models"; +import { UserAssignedModel } from "../Users/models"; export enum AssetLocationType { OTHER = "OTHER", @@ -22,6 +23,11 @@ export interface AssetLocationObject { }; } +export interface AssetLocationDutyStaffObject { + duty_staff_objects: UserAssignedModel[]; + duty_staff: number; +} + export enum AssetType { NONE = "NONE", INTERNAL = "INTERNAL", diff --git a/src/Components/Facility/AddLocationForm.tsx b/src/Components/Facility/AddLocationForm.tsx index 8f756f91781..1738024fb7f 100644 --- a/src/Components/Facility/AddLocationForm.tsx +++ b/src/Components/Facility/AddLocationForm.tsx @@ -1,19 +1,16 @@ -import { useState, useEffect, lazy, SyntheticEvent } from "react"; -import { useDispatch } from "react-redux"; -import { - createFacilityAssetLocation, - getAnyFacility, - getFacilityAssetLocation, - updateFacilityAssetLocation, -} from "../../Redux/actions"; -import * as Notification from "../../Utils/Notifications.js"; import { navigate } from "raviger"; -import { Submit, Cancel } from "../Common/components/ButtonV2"; -import TextFormField from "../Form/FormFields/TextFormField"; -import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; +import { SyntheticEvent, lazy, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import routes from "../../Redux/api"; +import * as Notification from "../../Utils/Notifications.js"; +import request from "../../Utils/request/request"; +import useQuery from "../../Utils/request/useQuery"; +import { AssetLocationType } from "../Assets/AssetTypes"; +import { Cancel, Submit } from "../Common/components/ButtonV2"; import Page from "../Common/components/Page"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; -import { AssetLocationType } from "../Assets/AssetTypes"; +import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; +import TextFormField from "../Form/FormFields/TextFormField"; const Loading = lazy(() => import("../Common/Loading")); @@ -24,15 +21,19 @@ interface LocationFormProps { export const AddLocationForm = (props: LocationFormProps) => { const { facilityId, locationId } = props; - const dispatchAction: any = useDispatch(); - const [isLoading, setIsLoading] = useState(false); + const { t } = useTranslation(); const [name, setName] = useState(""); const [middlewareAddress, setMiddlewareAddress] = useState(""); const [description, setDescription] = useState(""); - const [facilityName, setFacilityName] = useState(""); - const [locationName, setLocationName] = useState(""); - const [locationType, setLocationType] = useState(""); - const [errors, setErrors] = useState({ + const [locationType, setLocationType] = useState( + AssetLocationType.OTHER + ); + const [errors, setErrors] = useState<{ + name: string; + description: string; + middlewareAddress: string; + locationType: string; + }>({ name: "", description: "", middlewareAddress: "", @@ -41,29 +42,26 @@ export const AddLocationForm = (props: LocationFormProps) => { const headerText = !locationId ? "Add Location" : "Update Location"; const buttonText = !locationId ? "Add Location" : "Update Location"; - useEffect(() => { - async function fetchFacilityName() { - setIsLoading(true); - if (facilityId) { - const res = await dispatchAction(getAnyFacility(facilityId)); + const { data: facility } = useQuery(routes.getAnyFacility, { + pathParams: { id: facilityId }, + }); - setFacilityName(res?.data?.name || ""); - } - if (locationId) { - const res = await dispatchAction( - getFacilityAssetLocation(facilityId, locationId) - ); - - setName(res?.data?.name || ""); - setLocationName(res?.data?.name || ""); - setDescription(res?.data?.description || ""); - setLocationType(res?.data?.location_type || ""); - setMiddlewareAddress(res?.data?.middleware_address || ""); - } - setIsLoading(false); + const { data: location, loading } = useQuery( + routes.getFacilityAssetLocation, + { + pathParams: { + facility_external_id: facilityId, + external_id: locationId || "", + }, } - fetchFacilityName(); - }, [dispatchAction, facilityId, locationId]); + ); + + useEffect(() => { + setName(location?.name || ""); + setDescription(location?.description || ""); + setMiddlewareAddress(location?.middleware_address || ""); + setLocationType(location?.location_type || AssetLocationType.OTHER); + }, [location]); const validateForm = () => { let formValid = true; @@ -72,6 +70,7 @@ export const AddLocationForm = (props: LocationFormProps) => { description: "", middlewareAddress: "", locationType: "", + duty_staff: "", }; if (name.trim().length === 0) { @@ -104,44 +103,49 @@ export const AddLocationForm = (props: LocationFormProps) => { return; } - setIsLoading(true); - const data = { + const bodyData = { name, description, middleware_address: middlewareAddress, location_type: locationType, }; - const res = await dispatchAction( - locationId - ? updateFacilityAssetLocation(data, facilityId, locationId) - : createFacilityAssetLocation(data, facilityId) - ); - setIsLoading(false); - if (res) { - if (res.status === 201 || res.status === 200) { + let response: Response | undefined; + + if (locationId) { + const { res } = await request(routes.updateFacilityAssetLocation, { + pathParams: { + facility_external_id: facilityId, + external_id: locationId, + }, + body: bodyData, + }); + response = res; + } else { + const { res } = await request(routes.createFacilityAssetLocation, { + pathParams: { facility_external_id: facilityId }, + body: bodyData, + }); + response = res; + } + if (response) { + if (response.status === 201 || response.status === 200) { const notificationMessage = locationId ? "Location updated successfully" : "Location created successfully"; - navigate(`/facility/${facilityId}/location`, { - replace: true, - }); Notification.Success({ msg: notificationMessage, }); - } else if (res.status === 400) { - Object.keys(res.data).forEach((key) => { - setErrors((prevState: any) => ({ - ...prevState, - [key]: res.data[key], - })); + + navigate(`/facility/${facilityId}/location`, { + replace: true, }); } } }; - if (isLoading) { + if (loading) { return ; } @@ -150,10 +154,10 @@ export const AddLocationForm = (props: LocationFormProps) => { title={headerText} backUrl={`/facility/${facilityId}/location`} crumbsReplacements={{ - [facilityId]: { name: facilityName }, + [facilityId]: { name: facility?.name }, ...(locationId && { [locationId]: { - name: locationName, + name: location?.name, uri: `/facility/${facilityId}/location`, }, }), @@ -167,7 +171,7 @@ export const AddLocationForm = (props: LocationFormProps) => { setName(e.value)} @@ -178,7 +182,7 @@ export const AddLocationForm = (props: LocationFormProps) => { setDescription(e.value)} error={errors.description} @@ -188,7 +192,7 @@ export const AddLocationForm = (props: LocationFormProps) => { { id="location-middleware-address" name="Location Middleware Address" type="text" - label="Location Middleware Address" + label={t("location_middleware_address")} value={middlewareAddress} onChange={(e) => setMiddlewareAddress(e.value)} error={errors.middlewareAddress} diff --git a/src/Components/Facility/LocationManagement.tsx b/src/Components/Facility/LocationManagement.tsx index 93a08794c82..813484c82eb 100644 --- a/src/Components/Facility/LocationManagement.tsx +++ b/src/Components/Facility/LocationManagement.tsx @@ -1,12 +1,19 @@ -import { lazy } from "react"; -import ButtonV2 from "../Common/components/ButtonV2"; -import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; +import { lazy, useState } from "react"; +import { useTranslation } from "react-i18next"; +import RecordMeta from "../../CAREUI/display/RecordMeta"; import CareIcon from "../../CAREUI/icons/CareIcon"; -import Page from "../Common/components/Page"; -import routes from "../../Redux/api"; import PaginatedList from "../../CAREUI/misc/PaginatedList"; +import routes from "../../Redux/api"; +import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; +import * as Notification from "../../Utils/Notifications.js"; +import request from "../../Utils/request/request"; +import useQuery from "../../Utils/request/useQuery"; +import DialogModal from "../Common/Dialog"; +import ButtonV2 from "../Common/components/ButtonV2"; +import Page from "../Common/components/Page"; +import AutocompleteFormField from "../Form/FormFields/Autocomplete"; +import { UserModel } from "../Users/models"; import { LocationModel } from "./models"; -import RecordMeta from "../../CAREUI/display/RecordMeta"; const Loading = lazy(() => import("../Common/Loading")); @@ -15,6 +22,7 @@ interface Props { } export default function LocationManagement({ facilityId }: Props) { + const { t } = useTranslation(); return ( {() => ( - Add New Location + {t("add_new_location")} +
- No locations available + {t("no_locations_available")} className="my-8 grid gap-3 @4xl:grid-cols-2 @6xl:grid-cols-3 @[100rem]:grid-cols-4 lg:mx-8"> - {(item) => } + {(item) => }
@@ -68,76 +77,260 @@ export default function LocationManagement({ facilityId }: Props) { ); } -const Location = ({ - name, - description, - middleware_address, - location_type, - created_date, - modified_date, - id, -}: LocationModel) => ( -
-
-
-
-

- {name} -

+const DutyStaff = ({ + facilityId, + locationId, + toggle, + setToggle, +}: { + facilityId: string; + locationId: string; + toggle: boolean; + setToggle: React.Dispatch>; +}) => { + const { t } = useTranslation(); + const [disabled, setDisabled] = useState(false); + const { data, loading } = useQuery(routes.getFacilityUsers, { + pathParams: { facility_id: facilityId }, + }); + const [selected, setSelected] = useState(); + const { + data: dutyStaffList, + loading: _dutyStaffLoading, + refetch, + } = useQuery(routes.getFacilityAssetLocationDutyStaff, { + pathParams: { + facility_external_id: facilityId, + external_id: locationId ?? "", + }, + }); + + const dutyStaffIds = dutyStaffList?.map((u) => u.id) || []; + const userList = + data?.results + .filter((u) => u.user_type === "Doctor" || u.user_type === "Staff") + .filter((u) => !dutyStaffIds.includes(u.id)) || []; + + const handleAssign = async () => { + if (!selected) return; + setDisabled(true); + + const { res } = await request(routes.createFacilityAssetLocationDutyStaff, { + pathParams: { + facility_external_id: facilityId, + external_id: locationId ?? "", + }, + body: { duty_staff: selected.id }, + }); + + if (res) { + if (res?.ok && res.status === 201) { + Notification.Success({ + msg: "Duty Staff Assigned Successfully", + }); + refetch(); + } + } else { + Notification.Error({ + msg: "Something went wrong", + }); + } + + setDisabled(false); + setSelected(undefined); + }; + + const handleDelete = async (userId: number) => { + if (!userId) return; + setDisabled(true); + + const { res } = await request(routes.removeFacilityAssetLocationDutyStaff, { + pathParams: { + facility_external_id: facilityId, + external_id: locationId ?? "", + }, + body: { duty_staff: userId }, + }); + + if (res) { + if (res?.ok && res.status === 204) { + Notification.Success({ + msg: "Duty Staff removed Successfully", + }); + refetch(); + } + } else { + Notification.Error({ + msg: "Something went wrong", + }); + } + setDisabled(false); + }; + + return ( + setToggle((prev) => !prev)} + > +
+ setSelected(e.value)} + options={userList} + optionLabel={(option) => + `${option.first_name} ${option.last_name} (${option.user_type})` + } + optionValue={(option) => option} + isLoading={loading} + /> + handleAssign()} + disabled={!selected} + > + {t("assign")} + +
+
+ {dutyStaffList?.map((user) => (
-

- {location_type} +

+ +
{`${user.first_name} ${user.last_name} (${user.user_type})`}
+
+ handleDelete(user.id)} + > + + +
+ ))} +
+
+ ); +}; + +interface LocationProps extends LocationModel { + facilityId: string; +} + +const Location = (props: LocationProps) => { + const { t } = useTranslation(); + const { + id, + name, + description, + middleware_address, + location_type, + created_date, + modified_date, + facilityId, + } = props; + const [toggle, setToggle] = useState(false); + + return ( +
+
+
+
+

+ {name}

+
+

+ {location_type} +

+
+ + + {t("edit")} +
+ +

+ {description || "-"} +

+

+ Middleware Address: +

+

+ {middleware_address || "-"} +

+
+ +
+ {toggle && ( + + )} +
+
setToggle((prev) => !prev)} > - - Edit + + {t("assign_duty_staff")} -
-

- {description || "-"} -

-

- Middleware Address: -

-

- {middleware_address || "-"} -

-
- - - Manage Beds - + + + {t("manage_beds")} + +
-
- - +
+ + +
-
-); + ); +}; diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index c3ac910970c..2e180cca7c2 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -1,11 +1,11 @@ -import { AssignedToObjectModel, DailyRoundsModel } from "../Patient/models"; -import { ProcedureType } from "../Common/prescription-builder/ProcedureBuilder"; -import { NormalPrescription, PRNPrescription } from "../Medicine/models"; +import { ConsultationSuggestionValue } from "../../Common/constants"; import { AssetData, AssetLocationType } from "../Assets/AssetTypes"; -import { UserBareMinimum } from "../Users/models"; import { RouteToFacility } from "../Common/RouteToFacilitySelect"; +import { ProcedureType } from "../Common/prescription-builder/ProcedureBuilder"; import { ConsultationDiagnosis, CreateDiagnosis } from "../Diagnosis/types"; -import { ConsultationSuggestionValue } from "../../Common/constants"; +import { NormalPrescription, PRNPrescription } from "../Medicine/models"; +import { AssignedToObjectModel, DailyRoundsModel } from "../Patient/models"; +import { UserAssignedModel, UserBareMinimum } from "../Users/models"; export interface LocalBodyModel { id: number; @@ -213,6 +213,8 @@ export interface LocationModel { }; created_date?: string; modified_date?: string; + + users?: UserAssignedModel[]; } export interface BedModel { diff --git a/src/Locale/en/Asset.json b/src/Locale/en/Asset.json index f24549ee0b6..7b9979b7d8b 100644 --- a/src/Locale/en/Asset.json +++ b/src/Locale/en/Asset.json @@ -11,5 +11,8 @@ "update_asset_service_record": "Update Asset Service Record", "eg_details_on_functionality_service_etc": "Eg. Details on functionality, service, etc.", "updating": "Updating", - "update": "Update" -} + "update": "Update", + "search_user_placeholder": "Search User", + "assign_duty_staff": "Assign Duty Staff", + "assign": "Assign" +} \ No newline at end of file diff --git a/src/Locale/en/Location.json b/src/Locale/en/Location.json new file mode 100644 index 00000000000..c25681e39ae --- /dev/null +++ b/src/Locale/en/Location.json @@ -0,0 +1,8 @@ +{ + "location_management": "Location Management", + "manage_beds": "Manage Beds", + "add_new_location": "Add New Location", + "no_locations_available": "No Locations Available", + "location_type": "Location Type", + "location_middleware_address": "Location Middleware Address" +} \ No newline at end of file diff --git a/src/Locale/en/index.js b/src/Locale/en/index.js index 781ce97b009..d214226e0f7 100644 --- a/src/Locale/en/index.js +++ b/src/Locale/en/index.js @@ -1,20 +1,21 @@ -import Auth from "./Auth.json"; import Asset from "./Asset.json"; +import Auth from "./Auth.json"; +import Bed from "./Bed.json"; import Common from "./Common.json"; import Consultation from "./Consultation.json"; +import CoverImageEdit from "./CoverImageEdit.json"; +import Diagnosis from "./Diagnosis.json"; import Entities from "./Entities.json"; +import ErrorPages from "./ErrorPages.json"; +import ExternalResult from "./ExternalResult.json"; import Facility from "./Facility.json"; import Hub from "./Hub.json"; -import ErrorPages from "./ErrorPages.json"; -import Shifting from "./Shifting.json"; +import Location from "./Location.json"; +import Medicine from "./Medicine.json"; import Notifications from "./Notifications.json"; -import ExternalResult from "./ExternalResult.json"; -import CoverImageEdit from "./CoverImageEdit.json"; import Resource from "./Resource.json"; +import Shifting from "./Shifting.json"; import SortOptions from "./SortOptions.json"; -import Bed from "./Bed.json"; -import Medicine from "./Medicine.json"; -import Diagnosis from "./Diagnosis.json"; export default { ...Auth, @@ -33,5 +34,6 @@ export default { ...Resource, ...Shifting, ...Bed, + ...Location, SortOptions, }; diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 2b8d4f8f51b..ec097fb7d74 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -29,52 +29,53 @@ import { AssetUpdate, } from "../Components/Assets/AssetTypes"; import { + IDeleteExternalResult, + IExternalResult, + IExternalResultCsv, + ILocalBodies, + ILocalBodyByDistrict, + IPartialUpdateExternalResult, +} from "../Components/ExternalResult/models"; +import { + BedModel, CapacityModal, ConsultationModel, CreateBedBody, CurrentBed, - DistrictModel, DailyRoundsBody, DailyRoundsRes, + DistrictModel, DoctorModal, FacilityModel, + FacilityRequest, IFacilityNotificationRequest, IFacilityNotificationResponse, IUserFacilityRequest, LocalBodyModel, + LocationModel, + PatientNotesModel, PatientStatsModel, - FacilityRequest, StateModel, WardModel, - LocationModel, - PatientNotesModel, - BedModel, } from "../Components/Facility/models"; + +import { HCXPolicyModel } from "../Components/HCX/models"; +import { Prescription } from "../Components/Medicine/models"; import { - IDeleteExternalResult, - IExternalResult, - IExternalResultCsv, - ILocalBodies, - ILocalBodyByDistrict, - IPartialUpdateExternalResult, -} from "../Components/ExternalResult/models"; + NotificationData, + PNconfigData, +} from "../Components/Notifications/models"; +import { DailyRoundsModel, PatientModel } from "../Components/Patient/models"; +import { IComment, IResource } from "../Components/Resource/models"; +import { IShift } from "../Components/Shifting/models"; import { SkillModel, SkillObjectModel, UpdatePasswordForm, + UserBareMinimum, UserModel, } from "../Components/Users/models"; -import { Prescription } from "../Components/Medicine/models"; -import { DailyRoundsModel, PatientModel } from "../Components/Patient/models"; import { PaginatedResponse } from "../Utils/request/types"; -import { - NotificationData, - PNconfigData, -} from "../Components/Notifications/models"; - -import { IComment, IResource } from "../Components/Resource/models"; -import { IShift } from "../Components/Shifting/models"; -import { HCXPolicyModel } from "../Components/HCX/models"; /** * A fake function that returns an empty object casted to type T @@ -337,6 +338,8 @@ const routes = { createFacilityAssetLocation: { path: "/api/v1/facility/{facility_external_id}/asset_location/", method: "POST", + TRes: Type>(), + TBody: Type(), }, getFacilityAssetLocation: { path: "/api/v1/facility/{facility_external_id}/asset_location/{external_id}/", @@ -346,11 +349,34 @@ const routes = { updateFacilityAssetLocation: { path: "/api/v1/facility/{facility_external_id}/asset_location/{external_id}/", method: "PUT", + TRes: Type>(), + TBody: Type(), }, partialUpdateFacilityAssetLocation: { path: "/api/v1/facility/{facility_external_id}/asset_location/{external_id}/", method: "PATCH", }, + getFacilityAssetLocationDutyStaff: { + path: "/api/v1/facility/{facility_external_id}/asset_location/{external_id}/duty_staff/", + method: "GET", + TRes: Type(), + }, + createFacilityAssetLocationDutyStaff: { + path: "/api/v1/facility/{facility_external_id}/asset_location/{external_id}/duty_staff/", + method: "POST", + TRes: Type>(), + TBody: Type<{ + duty_staff: number; + }>(), + }, + removeFacilityAssetLocationDutyStaff: { + path: "/api/v1/facility/{facility_external_id}/asset_location/{external_id}/duty_staff/", + method: "DELETE", + TRes: Type>(), + TBody: Type<{ + duty_staff: number; + }>(), + }, // Asset bed listAssetBeds: {