Skip to content

Commit

Permalink
Added facility hubs (#7772)
Browse files Browse the repository at this point in the history
  • Loading branch information
shivankacker committed Sep 24, 2024
1 parent c483e81 commit 7878cd6
Show file tree
Hide file tree
Showing 11 changed files with 463 additions and 11 deletions.
16 changes: 15 additions & 1 deletion src/Common/constants.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { PatientCategory } from "../Components/Facility/models";
import {
PatientCategory,
SpokeRelationship,
} from "../Components/Facility/models";
import { SortOption } from "../Components/Common/SortDropdown";
import { dateQueryString } from "../Utils/utils";
import { IconName } from "../CAREUI/icons/CareIcon";
Expand Down Expand Up @@ -1517,6 +1520,17 @@ export const DEFAULT_ALLOWED_EXTENSIONS = [
"application/vnd.oasis.opendocument.spreadsheet,application/pdf",
];

export const SPOKE_RELATION_TYPES = [
{
text: "Regular",
value: SpokeRelationship.REGULAR,
},
{
text: "Tele ICU",
value: SpokeRelationship.TELE_ICU,
},
];

export const HumanBodyPaths = {
anterior: [
{
Expand Down
6 changes: 6 additions & 0 deletions src/Components/Common/FacilitySelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ interface FacilitySelectProps {
selected?: FacilityModel | FacilityModel[] | null;
setSelected: (selected: FacilityModel | FacilityModel[] | null) => void;
allowNone?: boolean;
placeholder?: string;
filter?: (facilities: FacilityModel) => boolean;
}

export const FacilitySelect = (props: FacilitySelectProps) => {
Expand All @@ -44,6 +46,8 @@ export const FacilitySelect = (props: FacilitySelectProps) => {
allowNone = false,
freeText = false,
errors = "",
placeholder,
filter,
} = props;

const facilitySearch = useCallback(
Expand Down Expand Up @@ -82,6 +86,7 @@ export const FacilitySelect = (props: FacilitySelectProps) => {

return (
<AutoCompleteAsync
placeholder={placeholder}
name={name}
required={required}
multiple={multiple}
Expand All @@ -97,6 +102,7 @@ export const FacilitySelect = (props: FacilitySelectProps) => {
compareBy="id"
className={className}
error={errors}
filter={filter}
/>
);
};
34 changes: 34 additions & 0 deletions src/Components/Facility/FacilityBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Link } from "raviger";
import CareIcon from "../../CAREUI/icons/CareIcon";
import { FacilityModel } from "./models";

export default function FacilityBlock(props: { facility: FacilityModel }) {
const { facility } = props;

return (
<Link
className="flex items-center gap-4 text-inherit"
target="_blank"
href={`/facility/${facility.id}`}
>
<div className="flex aspect-square h-14 shrink-0 items-center justify-center overflow-hidden rounded-lg border border-gray-400 bg-gray-200">
{facility.read_cover_image_url ? (
<img
className="h-full w-full object-cover"
src={facility.read_cover_image_url}
/>
) : (
<>
<CareIcon icon="l-hospital" />
</>
)}
</div>
<div>
<b className="font-semibold">{facility.name}</b>
<p className="text-sm">
{facility.address} {facility.local_body_object?.name}
</p>
</div>
</Link>
);
}
11 changes: 10 additions & 1 deletion src/Components/Facility/FacilityCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import routes from "../../Redux/api.js";
import useQuery from "../../Utils/request/useQuery.js";
import { RequestResult } from "../../Utils/request/types.js";
import useAuthUser from "../../Common/hooks/useAuthUser";
import SpokeFacilityEditor from "./SpokeFacilityEditor.js";
import careConfig from "@careConfig";

const Loading = lazy(() => import("../Common/Loading"));
Expand Down Expand Up @@ -247,7 +248,7 @@ export const FacilityCreate = (props: FacilityProps) => {
},
);

useQuery(routes.getPermittedFacility, {
const facilityQuery = useQuery(routes.getPermittedFacility, {
pathParams: {
id: facilityId!,
},
Expand Down Expand Up @@ -850,6 +851,14 @@ export const FacilityCreate = (props: FacilityProps) => {
required
types={["mobile", "landline"]}
/>
<div className="py-4 md:col-span-2">
<h4 className="mb-4">{t("spokes")}</h4>
{facilityId && (
<SpokeFacilityEditor
facility={{ ...facilityQuery.data, id: facilityId }}
/>
)}
</div>
<div className="grid grid-cols-1 gap-4 py-4 sm:grid-cols-2 md:col-span-2 xl:grid-cols-4">
<TextFormField
{...field("oxygen_capacity")}
Expand Down
22 changes: 22 additions & 0 deletions src/Components/Facility/FacilityHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { FieldLabel } from "../Form/FormFields/FormField.js";
import { LocationSelect } from "../Common/LocationSelect.js";
import { CameraFeedPermittedUserTypes } from "../../Utils/permissions.js";
import { FacilityStaffList } from "./FacilityStaffList.js";
import FacilityBlock from "./FacilityBlock.js";

type Props = {
facilityId: string;
Expand Down Expand Up @@ -93,6 +94,13 @@ export const FacilityHome = ({ facilityId }: Props) => {
});
};

const spokesQuery = useQuery(routes.getFacilitySpokes, {
pathParams: {
id: facilityId,
},
silent: true,
});

if (isLoading) {
return <Loading />;
}
Expand Down Expand Up @@ -289,6 +297,20 @@ export const FacilityHome = ({ facilityId }: Props) => {
/>
</div>
</div>
{!!spokesQuery.data?.results.length && (
<div className="mt-4 flex items-center gap-3">
<div id="spokes-view">
<h1 className="text-base font-semibold text-[#B9B9B9]">
{t("spokes")}
</h1>
<div className="mt-4 grid grid-cols-1 gap-4 xl:grid-cols-2">
{spokesQuery.data?.results.map((spoke) => (
<FacilityBlock facility={spoke.spoke_object} />
))}
</div>
</div>
</div>
)}
</div>
</div>
</div>
Expand Down
154 changes: 154 additions & 0 deletions src/Components/Facility/SpokeFacilityEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import routes from "../../Redux/api";
import request from "../../Utils/request/request";
import useQuery from "../../Utils/request/useQuery";
import { SelectFormField } from "../Form/FormFields/SelectFormField";
import {
FacilityModel,
FacilitySpokeErrors,
FacilitySpokeModel,
FacilitySpokeRequest,
SpokeRelationship,
} from "./models";
import ModelCrudEditor from "../Form/ModelCrudEditor";
import { FacilitySelect } from "../Common/FacilitySelect";
import { useEffect, useState } from "react";
import { SPOKE_RELATION_TYPES } from "../../Common/constants";
import FacilityBlock from "./FacilityBlock";
import { useTranslation } from "react-i18next";

export interface SpokeFacilityEditorProps {
facility: Omit<FacilityModel, "id"> & { id: string };
}

export default function SpokeFacilityEditor(props: SpokeFacilityEditorProps) {
const { facility } = props;

const { t } = useTranslation();

const spokesQuery = useQuery(routes.getFacilitySpokes, {
pathParams: {
id: facility.id,
},
});

const spokes = spokesQuery.data?.results;

const createSpoke = (body: FacilitySpokeRequest) =>
request(routes.createFacilitySpoke, {
body,
pathParams: {
id: facility.id,
},
onResponse: ({ res }) => {
if (res?.ok) {
spokesQuery.refetch();
}
},
});

const deleteSpoke = (spokeFacilityId: string) =>
request(routes.deleteFacilitySpoke, {
pathParams: {
id: facility.id,
spoke_id: spokeFacilityId,
},
onResponse: ({ res }) => {
if (res?.ok) {
spokesQuery.refetch();
}
},
});

const updateSpoke = (spokeFacilityId: string, body: FacilitySpokeRequest) =>
request(routes.updateFacilitySpokes, {
pathParams: {
id: facility.id,
spoke_id: spokeFacilityId,
},
body,
onResponse: ({ res }) => {
if (res?.ok) {
spokesQuery.refetch();
}
},
});

const FormRender = (
item: FacilitySpokeModel | FacilitySpokeRequest,
setItem: (item: FacilitySpokeModel | FacilitySpokeRequest) => void,
processing: boolean,
) => {
const [selectedFacility, setSelectedFacility] = useState<FacilityModel>();

useEffect(() => {
setItem({ ...item, spoke: selectedFacility?.id });
}, [selectedFacility]);

return (
<div className="flex w-full flex-col items-center gap-4 md:flex-row">
{"id" in item ? (
<div className="w-full">
<FacilityBlock facility={item.spoke_object} />
</div>
) : (
<FacilitySelect
multiple={false}
name="facility"
placeholder={t("add_spoke")}
showAll={false}
showNOptions={8}
selected={selectedFacility}
setSelected={(v) =>
v && !Array.isArray(v) && setSelectedFacility(v)
}
errors=""
className="w-full"
disabled={processing}
filter={(f) =>
!!f.id &&
facility.id !== f.id &&
!spokes?.flatMap((s) => s.spoke_object.id).includes(f.id)
}
/>
)}
<SelectFormField
name="relationship_type"
options={SPOKE_RELATION_TYPES}
optionLabel={(v) => v.text}
optionValue={(v) => v.value}
value={item.relationship}
onChange={(v) => setItem({ ...item, relationship: v.value })}
errorClassName="hidden"
className="w-full shrink-0 md:w-auto"
disabled={processing}
/>
</div>
);
};

return (
<>
<ModelCrudEditor<
FacilitySpokeModel,
FacilitySpokeRequest,
FacilitySpokeErrors
>
items={spokes}
onCreate={createSpoke}
onUpdate={updateSpoke}
onDelete={deleteSpoke}
loading={spokesQuery.loading}
errors={{}}
emptyText={"No Spokes"}
empty={{
spoke: "",
relationship: SpokeRelationship.REGULAR,
}}
createText="Add Spoke"
allowCreate={(item) => !item.relationship || !item.spoke}
>
{FormRender}
</ModelCrudEditor>
</>
);
}
33 changes: 26 additions & 7 deletions src/Components/Facility/models.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
AssignedToObjectModel,
BloodPressure,
DailyRoundsModel,
FacilityNameModel,
FileUploadModel,
} from "../Patient/models";
import { EncounterSymptom } from "../Symptoms/types";
Expand Down Expand Up @@ -79,8 +80,32 @@ export interface FacilityModel {
local_body?: number;
ward?: number;
pincode?: string;
latitude?: string;
longitude?: string;
kasp_empanelled?: boolean;
patient_count?: string;
bed_count?: string;
}

export enum SpokeRelationship {
REGULAR = 1,
TELE_ICU = 2,
}

export interface FacilitySpokeModel {
id: string;
hub_object: FacilityNameModel;
spoke_object: FacilityNameModel;
relationship: SpokeRelationship;
}

export interface FacilitySpokeRequest {
spoke?: string;
relationship?: SpokeRelationship;
}

export interface FacilitySpokeErrors {}

export interface CapacityModal {
id?: number;
room_type?: number;
Expand Down Expand Up @@ -588,13 +613,7 @@ export type IUserFacilityRequest = {
facility: string;
};

export type FacilityRequest = Omit<FacilityModel, "location"> & {
latitude?: string;
longitude?: string;
kasp_empanelled?: boolean;
patient_count?: string;
bed_count?: string;
};
export type FacilityRequest = Omit<FacilityModel, "location" | "id">;

export type InventorySummaryResponse = {
id: string;
Expand Down
Loading

0 comments on commit 7878cd6

Please sign in to comment.