Skip to content

Commit

Permalink
Added a Date/time and round type filter for log updates (#6713)
Browse files Browse the repository at this point in the history
* Add DailyRoundsFilterModel interface and
DailyRoundsFilter component

* refactored dailyRoundList

* useQuery: Fix GET/HEAD cannot contain body issue

* Support for filtering by `taken_at` dt range

* Refactor DailyRoundsFilter and DailyRoundsList
components

* Refactor roundTypeOptions in DailyRoundsFilter
component

* Improve translations coverage and minor refactors

* update slugs for request

---------

Co-authored-by: rithviknishad <[email protected]>
  • Loading branch information
konavivekramakrishna and rithviknishad authored Dec 6, 2023
1 parent 5e13944 commit f746576
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@ import PrescriptionsTable from "../../Medicine/PrescriptionsTable";
import Chip from "../../../CAREUI/display/Chip";
import { formatAge, formatDate, formatDateTime } from "../../../Utils/utils";
import ReadMore from "../../Common/components/Readmore";
import { DailyRoundsList } from "../Consultations/DailyRoundsList";
import DailyRoundsList from "../Consultations/DailyRoundsList";

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

export const ConsultationUpdatesTab = (props: ConsultationTabProps) => {
const dispatch: any = useDispatch();
const [showAutomatedRounds, setShowAutomatedRounds] = useState(true);
const [hl7SocketUrl, setHL7SocketUrl] = useState<string>();
const [ventilatorSocketUrl, setVentilatorSocketUrl] = useState<string>();
const [monitorBedData, setMonitorBedData] = useState<AssetBedModel>();
Expand Down Expand Up @@ -674,31 +673,7 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => {
</div>
</div>
<div className="w-full pl-4 xl:w-1/3">
<div className="flex flex-col md:flex-row md:items-center md:justify-between">
<PageTitle title="Update Log" hideBack breadcrumbs={false} />
<div className="mb-[2rem] pl-[1.5rem] md:mb-[0.125rem]">
<input
className="relative float-left ml-[-1.5rem] mr-[6px] mt-[0.15rem] h-[1.125rem] w-[1.125rem] appearance-none rounded-[0.25rem] border-[0.125rem] border-solid border-[rgba(0,0,0,0.25)] bg-white outline-none before:pointer-events-none before:absolute before:h-[0.875rem] before:w-[0.875rem] before:scale-0 before:rounded-full before:bg-transparent before:opacity-0 before:shadow-[0px_0px_0px_13px_transparent] before:content-[''] checked:border-primary checked:bg-primary checked:before:opacity-[0.16] checked:after:absolute checked:after:-mt-px checked:after:ml-[0.25rem] checked:after:block checked:after:h-[0.8125rem] checked:after:w-[0.375rem] checked:after:rotate-45 checked:after:border-[0.125rem] checked:after:border-l-0 checked:after:border-t-0 checked:after:border-solid checked:after:border-white checked:after:bg-transparent checked:after:content-[''] hover:cursor-pointer hover:before:opacity-[0.04] hover:before:shadow-[0px_0px_0px_13px_rgba(0,0,0,0.6)] focus:shadow-none focus:transition-[border-color_0.2s] focus:before:scale-100 focus:before:opacity-[0.12] focus:before:shadow-[0px_0px_0px_13px_rgba(0,0,0,0.6)] focus:before:transition-[box-shadow_0.2s,transform_0.2s] focus:after:absolute focus:after:z-[1] focus:after:block focus:after:h-[0.875rem] focus:after:w-[0.875rem] focus:after:rounded-[0.125rem] focus:after:bg-white focus:after:content-[''] checked:focus:border-primary checked:focus:bg-primary checked:focus:before:scale-100 checked:focus:before:shadow-[0px_0px_0px_13px_#3b71ca] checked:focus:before:transition-[box-shadow_0.2s,transform_0.2s] checked:focus:after:-mt-px checked:focus:after:ml-[0.25rem] checked:focus:after:h-[0.8125rem] checked:focus:after:w-[0.375rem] checked:focus:after:rotate-45 checked:focus:after:rounded-none checked:focus:after:border-[0.125rem] checked:focus:after:border-l-0 checked:focus:after:border-t-0 checked:focus:after:border-solid checked:focus:after:border-white checked:focus:after:bg-transparent"
type="checkbox"
id="automated-rounds-visible-checkbox"
checked={showAutomatedRounds}
onChange={() => setShowAutomatedRounds((s) => !s)}
/>
<label
className="inline-block pl-[0.15rem] hover:cursor-pointer"
htmlFor="automated-rounds-visible-checkbox"
>
Show Automated Rounds
</label>
</div>
</div>
<DailyRoundsList
facilityId={props.facilityId}
patientId={props.patientId}
consultationId={props.consultationId}
consultationData={props.consultationData}
showAutomatedRounds={showAutomatedRounds}
/>
<DailyRoundsList consultation={props.consultationData} />
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import CareIcon from "../../../../CAREUI/icons/CareIcon";
import ButtonV2 from "../../../Common/components/ButtonV2";
import { DailyRoundsModel } from "../../../Patient/models";
import LogUpdateCardAttribute from "./LogUpdateCardAttribute";
import { ConsultationModel } from "../../models";

interface Props {
round: DailyRoundsModel;
consultationData: any;
consultationData: ConsultationModel;
onViewDetails: () => void;
onUpdateLog?: () => void;
}
Expand Down
115 changes: 115 additions & 0 deletions src/Components/Facility/Consultations/DailyRoundsFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { Popover, Transition } from "@headlessui/react";
import ButtonV2 from "../../Common/components/ButtonV2";
import { Fragment } from "react";
import { SelectFormField } from "../../Form/FormFields/SelectFormField";
import TextFormField from "../../Form/FormFields/TextFormField";
import CareIcon from "../../../CAREUI/icons/CareIcon";
import dayjs from "dayjs";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { DailyRoundTypes, DailyRoundsModel } from "../../Patient/models";
import { FieldChangeEvent } from "../../Form/FormFields/Utils";

type FilterState = {
rounds_type?: DailyRoundsModel["rounds_type"];
taken_at_after?: string;
taken_at_before?: string;
};

interface Props {
onApply: (filter: FilterState) => void;
}

export default function DailyRoundsFilter(props: Props) {
const { t } = useTranslation();
const [filter, setFilter] = useState<FilterState>({});

const field = (name: keyof FilterState) => ({
name,
value: filter[name],
onChange: (e: FieldChangeEvent<unknown>) =>
setFilter({ ...filter, [e.name]: e.value }),
labelClassName: "text-sm",
errorClassName: "hidden",
});

return (
<div className="flex flex-row-reverse items-center gap-4 md:flex-row">
<Popover className="relative ">
<Popover.Button>
<ButtonV2 variant="secondary" className="mr-5 border">
<CareIcon className="care-l-filter" />
{t("filter")}
</ButtonV2>
</Popover.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute right-0 z-30 mt-1 w-80 px-4 sm:px-0 md:w-96 lg:max-w-3xl">
<div className="rounded-lg shadow-lg ring-1 ring-gray-400">
<div className="rounded-t-lg bg-gray-100 px-6 py-4">
<div className="flow-root rounded-md">
<span className="block text-sm text-gray-800">
{t("filter_by")}
</span>
</div>
</div>
<div className="relative flex flex-col gap-4 rounded-b-lg bg-white p-6">
<SelectFormField
{...field("rounds_type")}
label={t("Round Type")}
options={DailyRoundTypes}
placeholder={t("show_all")}
optionLabel={(o) => t(o)}
optionValue={(o) => o}
/>
<TextFormField
{...field("taken_at_after")}
label="Measured after"
type="datetime-local"
max={dayjs().format("YYYY-MM-DDTHH:mm")}
/>
<TextFormField
{...field("taken_at_before")}
label="Measured before"
type="datetime-local"
max={dayjs().format("YYYY-MM-DDTHH:mm")}
/>

<Popover.Button>
<ButtonV2
variant="secondary"
onClick={() => {
setFilter({});
props.onApply({});
}}
border
className="w-full"
>
{t("clear")}
</ButtonV2>
</Popover.Button>
<Popover.Button>
<ButtonV2
variant="primary"
onClick={() => props.onApply(filter)}
border
className="w-full"
>
{t("apply")}
</ButtonV2>
</Popover.Button>
</div>
</div>
</Popover.Panel>
</Transition>
</Popover>
</div>
);
}
134 changes: 65 additions & 69 deletions src/Components/Facility/Consultations/DailyRoundsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,88 +6,84 @@ import { useTranslation } from "react-i18next";
import LoadingLogUpdateCard from "./DailyRounds/LoadingCard";
import routes from "../../../Redux/api";
import PaginatedList from "../../../CAREUI/misc/PaginatedList";
import PageTitle from "../../Common/PageTitle";
import DailyRoundsFilter from "./DailyRoundsFilter";
import { ConsultationModel } from "../models";
import { useSlugs } from "../../../Common/hooks/useSlug";

export const DailyRoundsList = (props: any) => {
interface Props {
consultation: ConsultationModel;
}

export default function DailyRoundsList({ consultation }: Props) {
const [facilityId, patientId, consultationId] = useSlugs(
"facility",
"patient",
"consultation"
);
const { t } = useTranslation();
const {
facilityId,
patientId,
consultationId,
consultationData,
showAutomatedRounds,
} = props;

const consultationUrl = `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}`;

return (
<PaginatedList
route={routes.getDailyReports}
pathParams={{
consultationId,
}}
query={{
rounds_type: showAutomatedRounds ? "" : "NORMAL,VENTILATOR,ICU",
}}
pathParams={{ consultationId }}
>
{(_) => (
<div className="-mt-2 flex w-full flex-col gap-4">
<div className="flex max-h-[85vh] flex-col gap-4 overflow-y-auto overflow-x-hidden px-3">
<PaginatedList.WhenEmpty className="flex w-full justify-center rounded-md border-b border-gray-200 bg-white px-5 py-1 text-center text-2xl font-bold text-gray-500">
<span className="flex justify-center rounded-lg bg-white p-3 text-gray-700">
{t("no_consultation_updates")}
</span>
</PaginatedList.WhenEmpty>
<PaginatedList.WhenLoading>
<>
{Array.from({ length: 3 }).map((_, i) => (
<LoadingLogUpdateCard key={i} />
))}
</>
</PaginatedList.WhenLoading>
<PaginatedList.Items<DailyRoundsModel> className="flex grow flex-col gap-3">
{(item, items) => {
if (item.rounds_type === "AUTOMATED") {
{({ refetch }) => (
<>
<div className="flex flex-1 justify-between">
<PageTitle title="Update Log" hideBack breadcrumbs={false} />
<DailyRoundsFilter onApply={(query) => refetch({ query })} />
</div>

<div className="-mt-2 flex w-full flex-col gap-4">
<div className="flex max-h-[85vh] flex-col gap-4 overflow-y-auto overflow-x-hidden px-3">
<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 className="flex justify-center rounded-lg bg-white p-3 text-gray-700 ">
{t("no_consultation_updates")}
</span>
</PaginatedList.WhenEmpty>
<PaginatedList.WhenLoading>
<>
{Array.from({ length: 3 }).map((_, i) => (
<LoadingLogUpdateCard key={i} />
))}
</>
</PaginatedList.WhenLoading>
<PaginatedList.Items<DailyRoundsModel> className="flex grow flex-col gap-3">
{(item, items) => {
if (item.rounds_type === "AUTOMATED") {
return (
<VirtualNursingAssistantLogUpdateCard
round={item}
previousRound={items[items.indexOf(item) + 1]}
/>
);
}

const itemUrl =
item.rounds_type === "NORMAL"
? `${consultationUrl}/daily-rounds/${item.id}`
: `${consultationUrl}/daily_rounds/${item.id}`;

return (
<VirtualNursingAssistantLogUpdateCard
<DefaultLogUpdateCard
round={item}
previousRound={items[items.indexOf(item) + 1]}
consultationData={consultation}
onViewDetails={() => navigate(itemUrl)}
onUpdateLog={() => navigate(`${itemUrl}/update`)}
/>
);
}
return (
<DefaultLogUpdateCard
round={item}
consultationData={consultationData}
onViewDetails={() => {
if (item.rounds_type === "NORMAL") {
navigate(
`/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/daily-rounds/${item.id}`
);
} else {
navigate(
`/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/daily_rounds/${item.id}`
);
}
}}
onUpdateLog={() => {
if (item.rounds_type === "NORMAL") {
navigate(
`/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/daily-rounds/${item.id}/update`
);
} else {
navigate(
`/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/daily_rounds/${item.id}/update`
);
}
}}
/>
);
}}
</PaginatedList.Items>
<div className="flex w-full items-center justify-center">
<PaginatedList.Paginator hideIfSinglePage />
}}
</PaginatedList.Items>
<div className="flex w-full items-center justify-center">
<PaginatedList.Paginator hideIfSinglePage />
</div>
</div>
</div>
</div>
</>
)}
</PaginatedList>
);
};
}
2 changes: 1 addition & 1 deletion src/Components/Facility/models.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export interface ConsultationModel {
history_of_present_illness?: string;
facility?: number;
facility_name?: string;
id?: string;
id: string;
modified_date?: string;
other_symptoms?: string;
patient?: string;
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Form/FormFields/SelectFormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type OptionCallback<T, R> = (option: T) => R;

type SelectFormFieldProps<T, V = T> = FormFieldBaseProps<V> & {
placeholder?: React.ReactNode;
options: T[];
options: readonly T[];
position?: "above" | "below";
optionLabel: OptionCallback<T, React.ReactNode>;
optionSelectedLabel?: OptionCallback<T, React.ReactNode>;
Expand Down
7 changes: 5 additions & 2 deletions src/Components/Patient/models.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ export interface DailyRoundsOutput {
quantity: number;
}

export const DailyRoundTypes = ["NORMAL", "VENTILATOR", "AUTOMATED"] as const;

export interface DailyRoundsModel {
ventilator_spo2?: number;
spo2?: string;
Expand All @@ -290,7 +292,7 @@ export interface DailyRoundsModel {
medication_given?: Array<any>;
additional_symptoms_text?: string;
current_health?: string;
id?: any;
id: string;
other_symptoms?: string;
admitted_to?: string;
patient_category?: PatientCategory;
Expand All @@ -299,7 +301,7 @@ export interface DailyRoundsModel {
created_date?: string;
modified_date?: string;
taken_at?: string;
rounds_type?: "NORMAL" | "VENTILATOR" | "ICU" | "AUTOMATED";
rounds_type: (typeof DailyRoundTypes)[number];
last_updated_by_telemedicine?: boolean;
created_by_telemedicine?: boolean;
created_by?: {
Expand All @@ -314,6 +316,7 @@ export interface DailyRoundsModel {
};
bed?: string;
}

export interface FacilityNameModel {
id?: string;
name?: string;
Expand Down
3 changes: 2 additions & 1 deletion src/Locale/en/Common.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"clear": "Clear",
"apply": "Apply",
"filter_by": "Filter By",
"filter": "Filter",
"ordering": "Ordering",
"phone_number": "Phone Number",
"emergency_contact_number": "Emergency Contact Number",
Expand Down Expand Up @@ -158,4 +159,4 @@
"clear_selection": "Clear selection",
"select_date": "Select date",
"DD/MM/YYYY": "DD/MM/YYYY"
}
}
Loading

0 comments on commit f746576

Please sign in to comment.