Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added facility hubs #7772

Merged
merged 12 commits into from
Sep 24, 2024
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 @@ -1469,6 +1472,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 @@ -851,6 +852,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 @@ -37,6 +37,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 @@ -96,6 +97,13 @@ export const FacilityHome = ({ facilityId }: Props) => {
});
};

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

if (isLoading) {
return <Loading />;
}
Expand Down Expand Up @@ -292,6 +300,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 @@ -15,6 +15,7 @@ import { NormalPrescription, PRNPrescription } from "../Medicine/models";
import {
AssignedToObjectModel,
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 @@ -559,13 +584,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
Loading