Skip to content

Commit

Permalink
fix(location): added modal to add/remove duty staff
Browse files Browse the repository at this point in the history
  • Loading branch information
aeswibon committed Dec 4, 2023
1 parent b49247c commit 19a7513
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 89 deletions.
6 changes: 6 additions & 0 deletions src/Components/Assets/AssetTypes.tsx
Original file line number Diff line number Diff line change
@@ -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 interface AssetLocationObject {
id: string;
Expand All @@ -15,6 +16,11 @@ export interface AssetLocationObject {
};
}

export interface AssetLocationDutyStaffObject {
duty_staff_objects: UserAssignedModel[];
duty_staff: number;
}

export enum AssetType {
NONE = "NONE",
INTERNAL = "INTERNAL",
Expand Down
39 changes: 26 additions & 13 deletions src/Components/Facility/AddLocationForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState, useEffect, lazy, SyntheticEvent } from "react";
import { navigate } from "raviger";
import { SyntheticEvent, lazy, useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import {
createFacilityAssetLocation,
Expand All @@ -7,11 +8,10 @@ import {
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 { Cancel, Submit } from "../Common/components/ButtonV2";
import Page from "../Common/components/Page";
import TextAreaFormField from "../Form/FormFields/TextAreaFormField";
import TextFormField from "../Form/FormFields/TextFormField";

const Loading = lazy(() => import("../Common/Loading"));

Expand All @@ -29,10 +29,16 @@ export const AddLocationForm = (props: LocationFormProps) => {
const [description, setDescription] = useState("");
const [facilityName, setFacilityName] = useState("");
const [locationName, setLocationName] = useState("");
const [errors, setErrors] = useState<any>({
const [errors, setErrors] = useState<{
name: string;
description: string;
middlewareAddress: string;
duty_staff: string;
}>({
name: "",
description: "",
middlewareAddress: "",
duty_staff: "",
});
const headerText = !locationId ? "Add Location" : "Update Location";
const buttonText = !locationId ? "Add Location" : "Update Location";
Expand All @@ -41,9 +47,8 @@ export const AddLocationForm = (props: LocationFormProps) => {
async function fetchFacilityName() {
setIsLoading(true);
if (facilityId) {
const res = await dispatchAction(getAnyFacility(facilityId));

setFacilityName(res?.data?.name || "");
const facility = await dispatchAction(getAnyFacility(facilityId));
setFacilityName(facility?.data?.name || "");
}
if (locationId) {
const res = await dispatchAction(
Expand All @@ -66,6 +71,7 @@ export const AddLocationForm = (props: LocationFormProps) => {
name: "",
description: "",
middlewareAddress: "",
duty_staff: "",
};

if (name.trim().length === 0) {
Expand Down Expand Up @@ -112,15 +118,16 @@ export const AddLocationForm = (props: LocationFormProps) => {
? "Location updated successfully"
: "Location created successfully";

navigate(`/facility/${facilityId}/location`, {
replace: true,
});
Notification.Success({
msg: notificationMessage,
});

navigate(`/facility/${facilityId}/location`, {
replace: true,
});
} else if (res.status === 400) {
Object.keys(res.data).forEach((key) => {
setErrors((prevState: any) => ({
setErrors((prevState) => ({
...prevState,
[key]: res.data[key],
}));
Expand Down Expand Up @@ -151,6 +158,12 @@ export const AddLocationForm = (props: LocationFormProps) => {
<div className="cui-card">
<form onSubmit={handleSubmit}>
<div className="mt-2 grid grid-cols-1 gap-4">
<div className="flex flex-row items-center">
<label className="text-lg font-bold text-gray-900">
General Details
</label>
<hr className="ml-6 flex-1 border border-gray-400" />
</div>
<div>
<TextFormField
name="name"
Expand Down
232 changes: 182 additions & 50 deletions src/Components/Facility/LocationManagement.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
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 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";

const Loading = lazy(() => import("../Common/Loading"));
Expand All @@ -14,14 +21,15 @@ interface Props {
}

export default function LocationManagement({ facilityId }: Props) {
const { t } = useTranslation();
return (
<PaginatedList
route={routes.listFacilityAssetLocation}
pathParams={{ facility_external_id: facilityId }}
>
{() => (
<Page
title="Location Management"
title={t("location_management")}
backUrl={`/facility/${facilityId}`}
options={
<ButtonV2
Expand All @@ -41,19 +49,19 @@ export default function LocationManagement({ facilityId }: Props) {
className="w-full lg:hidden"
>
<CareIcon icon="l-plus" className="text-lg" />
Add New Location
{t("add_new_location")}
</ButtonV2>
</div>
<PaginatedList.WhenEmpty className="flex w-full justify-center border-b border-gray-200 bg-white p-5 text-center text-2xl font-bold text-gray-500">
<span>No locations available</span>
<span>{t("no_locations_available")}</span>
</PaginatedList.WhenEmpty>

<PaginatedList.WhenLoading>
<Loading />
</PaginatedList.WhenLoading>

<PaginatedList.Items<LocationModel> className="my-8 flex grow flex-col gap-3 lg:mx-8">
{(item) => <Location {...item} />}
{(item) => <Location {...item} facilityId={facilityId} />}
</PaginatedList.Items>

<div className="flex w-full items-center justify-center">
Expand All @@ -64,48 +72,172 @@ export default function LocationManagement({ facilityId }: Props) {
</PaginatedList>
);
}
interface LocationProps extends LocationModel {
facilityId: string;
}

const Location = (props: LocationProps) => {
const { t } = useTranslation();
const { id, name, description, middleware_address, facilityId } = props;
const [toggle, setToggle] = useState(false);
const [disabled, setDisabled] = useState(false);
const { data, loading } = useQuery(routes.getFacilityUsers, {
pathParams: { facility_id: facilityId },
});
const [selected, setSelected] = useState<UserModel>();
const userList =
data?.results.filter(
(u) => u.user_type === "Doctor" || u.user_type === "Staff"
) || [];

const {
data: dutyStaffList,
loading: dutyStaffLoading,
refetch,
} = useQuery(routes.getFacilityAssetLocationDutyStaff, {
pathParams: { facility_external_id: facilityId, external_id: id ?? "" },
});

const handleAssign = async () => {
if (!selected) return;
setDisabled(true);

const Location = ({
name,
description,
middleware_address,
id,
}: LocationModel) => (
<div className="w-full items-center justify-between rounded border border-gray-300 bg-white p-6 shadow-sm transition-all duration-200 ease-in-out hover:border-primary-400 lg:flex">
<div className="lg:w-3/4">
<div className="w-full items-baseline gap-4 lg:flex lg:items-center">
<p className="break-words text-xl lg:mr-4 lg:w-3/4">
{name}
<p className="break-all text-sm text-gray-700">
{description || "-"}
</p>
</p>
<p className="break-all text-sm lg:mr-4 lg:w-3/4">
{middleware_address}
</p>
const { res } = await request(routes.createFacilityAssetLocationDutyStaff, {
pathParams: { facility_external_id: facilityId, external_id: id ?? "" },
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: id ?? "" },
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);
};

if (loading || dutyStaffLoading) {
return <Loading />;
}

return (
<div className="w-full items-center justify-between rounded border border-gray-300 bg-white p-6 shadow-sm transition-all duration-200 ease-in-out hover:border-primary-400 lg:flex">
<div className="lg:w-3/4">
<div className="w-full items-baseline space-y-2 lg:flex lg:space-x-2">
<p className="break-words text-sm lg:mr-4 lg:w-1/6">{name}</p>
<p className="break-all text-sm lg:w-1/2">{description}</p>
<p className="break-all text-sm lg:w-1/2">{middleware_address}</p>
</div>
</div>
</div>

<div className="mt-4 flex flex-col gap-2 lg:mt-0 lg:flex-row">
<ButtonV2
variant="secondary"
border
className="w-full lg:w-auto"
href={`location/${id}/update`}
authorizeFor={NonReadOnlyUsers}
>
<CareIcon className="care-l-pen text-lg" />
Edit
</ButtonV2>
<ButtonV2
variant="secondary"
border
className="w-full lg:w-auto"
href={`location/${id}/beds`}
>
<CareIcon className="care-l-bed text-lg" />
Manage Beds
</ButtonV2>
<div className="mt-4 flex flex-col gap-2 lg:mt-0 lg:flex-row">
{toggle && (
<DialogModal
title={t("assign_duty_staff")}
show={toggle}
onClose={() => setToggle((prev) => !prev)}
>
<div className="grid grid-cols-2 gap-2 py-4">
{dutyStaffList?.map((user) => (
<button onClick={() => handleDelete(user.id)}>
<div className="rounded-lg border border-primary-600 bg-primary-100 text-primary-900">
<div className="flex px-3 py-1">
<CareIcon className="care-l-user-md" />
<div className="ml-3">{`${user.first_name} ${user.last_name}`}</div>
</div>
</div>
</button>
))}
</div>
<div className="flex w-full items-start gap-2">
<AutocompleteFormField
id="user-search"
name="user_search"
className="w-full"
disabled={disabled}
placeholder={t("search_user_placeholder")}
value={selected}
onChange={(e) => setSelected(e.value)}
options={userList}
optionLabel={(option) =>
`${option.first_name} ${option.last_name} (${option.user_type})`
}
optionValue={(option) => option}
isLoading={loading}
/>
<ButtonV2
variant="primary"
border
className="mt-0.5"
onClick={() => handleAssign()}
disabled={!selected}
>
{t("assign")}
</ButtonV2>
</div>
</DialogModal>
)}
<ButtonV2
variant="secondary"
border
className="w-full lg:w-auto"
onClick={() => setToggle((prev) => !prev)}
>
<CareIcon className="care-l-user text-lg" />
{t("assign_duty_staff")}
</ButtonV2>
<ButtonV2
variant="secondary"
border
className="w-full lg:w-auto"
href={`location/${id}/update`}
authorizeFor={NonReadOnlyUsers}
>
<CareIcon className="care-l-pen text-lg" />
{t("edit")}
</ButtonV2>
<ButtonV2
variant="secondary"
border
className="w-full lg:w-auto"
href={`location/${id}/beds`}
>
<CareIcon className="care-l-bed text-lg" />
{t("manage_beds")}
</ButtonV2>
</div>
</div>
</div>
);
);
};
Loading

0 comments on commit 19a7513

Please sign in to comment.