diff --git a/src/Components/Medicine/MedicineAdministrationEventsTimeline.tsx b/src/Components/Medicine/MedicineAdministrationEventsTimeline.tsx new file mode 100644 index 00000000000..f8cd3dd910f --- /dev/null +++ b/src/Components/Medicine/MedicineAdministrationEventsTimeline.tsx @@ -0,0 +1,265 @@ +import { useEffect, useState } from "react"; +import { PerformedByModel } from "../HCX/misc"; +import { MedicineAdministrationRecord, Prescription } from "./models"; +import { classNames } from "../../Utils/utils"; +import CareIcon, { IconName } from "../../CAREUI/icons/CareIcon"; +import RecordMeta from "../../CAREUI/display/RecordMeta"; +import { PrescriptionActions } from "../../Redux/actions"; +import { useDispatch } from "react-redux"; +import Spinner from "../Common/Spinner"; +import ConfirmDialog from "../Common/ConfirmDialog"; + +interface Props { + prescription: Prescription | undefined; + actions: ReturnType["prescription"]>; + onArchiveAdministration?: (mar: MedicineAdministrationRecord) => void; +} + +interface EventBase { + timestamp: string; + by: PerformedByModel | undefined; +} + +interface PrescriptionCreatedEvent extends EventBase { + type: "created"; + prescription: Prescription; +} + +interface CommentEvent extends EventBase { + type: "commented"; + comment: string; +} + +interface PrescriptionDiscontinuedEvent extends EventBase { + type: "discontinued"; + reason: string; +} + +interface MedicineAdministrationEvent extends EventBase { + type: "administered"; + administration: MedicineAdministrationRecord; +} + +type Event = + | PrescriptionCreatedEvent + | CommentEvent + | PrescriptionDiscontinuedEvent + | MedicineAdministrationEvent; + +const compileEvents = ( + prescription: Prescription | undefined, + administrations: MedicineAdministrationRecord[] +): Event[] => { + const events: Event[] = []; + + if (prescription) { + events.push({ + type: "created", + timestamp: prescription.created_date, + prescription, + by: prescription.prescribed_by, + }); + } + + if (prescription?.notes) { + events.push({ + type: "commented", + timestamp: prescription.created_date, + comment: prescription.notes, + by: prescription.prescribed_by, + }); + } + + administrations.forEach((administration) => { + events.push({ + type: "administered", + timestamp: administration.created_date, + administration, + by: administration.administered_by, + }); + + if (administration.notes) { + events.push({ + type: "commented", + timestamp: administration.created_date, + comment: administration.notes, + by: administration.administered_by, + }); + } + }); + + if (prescription?.discontinued) { + events.push({ + type: "discontinued", + timestamp: prescription.discontinued_date, + reason: prescription.discontinued_reason || "", + by: undefined, + }); + } + + return events; +}; + +export default function MedicineAdministrationEventsTimeline(props: Props) { + const [events, setEvents] = useState(); + const dispatch = useDispatch(); + const [selectedForArchive, setSelectedForArchive] = + useState(); + const [archiving, setArchiving] = useState(false); + + useEffect(() => { + if (events) { + return; + } + + const fetchAdministrations = async () => { + const res = await dispatch(props.actions.listAdministrations()); + if (res.status === 200) { + const administrations = res.data.results.sort( + (a: MedicineAdministrationRecord, b: MedicineAdministrationRecord) => + new Date(a.created_date).getTime() - + new Date(b.created_date).getTime() + ); + + setEvents(compileEvents(props.prescription, administrations)); + } + }; + + fetchAdministrations(); + }, [events]); + + return ( +
+ {selectedForArchive && ( + { + setArchiving(true); + const res = await dispatch( + props.actions.archive(selectedForArchive) + ); + if (res.status === 200) { + setEvents(undefined); + setArchiving(false); + setSelectedForArchive(undefined); + props.onArchiveAdministration?.(selectedForArchive); + } + }} + onClose={() => setSelectedForArchive(undefined)} + /> + )} +

+ Activity + {events === undefined && } +

+
+
    + {events?.map((activityItem, activityItemIdx) => ( +
  1. +
    +
    +
    + {activityItem.type === "commented" ? ( + <> + +
    +
    +
    + + {activityItem.by?.first_name}{" "} + {activityItem.by?.last_name} + {" "} + commented +
    + +
    +

    + {activityItem.comment} +

    +
    + + ) : ( +
    +
    +
    +

    + + {activityItem.by?.first_name} {activityItem.by?.last_name} + {" "} + {activityItem.type} the prescription. + {activityItem.type === "administered" && ( + <> + {" "} + {activityItem.administration.archived_on ? ( + + Archived{" "} + + + ) : ( + props.onArchiveAdministration && ( + + setSelectedForArchive( + activityItem.administration + ) + } + > + Archive + + ) + )} + + )} +

    + +
    + )} +
  2. + ))} +
+
+
+ ); +} + +const eventIcons: Record = { + administered: "l-syringe", + commented: "l-comment-alt-lines", + created: "l-plus-circle", + discontinued: "l-times-circle", +}; diff --git a/src/Components/Medicine/PrescriptionAdministrationsTable.tsx b/src/Components/Medicine/PrescriptionAdministrationsTable.tsx index 81282126d7c..dc2dc3115e8 100644 --- a/src/Components/Medicine/PrescriptionAdministrationsTable.tsx +++ b/src/Components/Medicine/PrescriptionAdministrationsTable.tsx @@ -20,6 +20,7 @@ import { formatTime, } from "../../Utils/utils"; import useRangePagination from "../../Common/hooks/useRangePagination"; +import MedicineAdministrationEventsTimeline from "./MedicineAdministrationEventsTimeline"; interface DateRange { start: Date; @@ -273,6 +274,7 @@ const PrescriptionRow = ({ prescription, ...props }: PrescriptionRowProps) => { props.intervals[props.intervals.length - 1].end, "YYYY-MM-DD" ), + archived: false, }) ); @@ -283,12 +285,7 @@ const PrescriptionRow = ({ prescription, ...props }: PrescriptionRowProps) => { }, [prescription.id, dispatch, props.intervals]); return ( - + <> {showDiscontinue && ( { setShowDetails(false)} - className="w-full md:max-w-4xl" + className="w-full md:max-w-3xl" show > -
+
+ + {administrations && ( + { + setShowDetails(false); + props.refetch(); + }} + /> + )} +
setShowDetails(false)} @@ -356,77 +365,84 @@ const PrescriptionRow = ({ prescription, ...props }: PrescriptionRowProps) => {
)} - setShowDetails(true)} + -
- setShowDetails(true)} + > +
+ + {prescription.medicine_object?.name ?? prescription.medicine_old} + + + {prescription.discontinued && ( + + {t("discontinued")} + )} - > - {prescription.medicine_object?.name ?? prescription.medicine_old} - - {prescription.discontinued && ( - - {t("discontinued")} - - )} + {prescription.route && ( + + {t(prescription.route)} + + )} +
+ - {prescription.route && ( - - {t(prescription.route)} - - )} -
- - - -

{prescription.dosage}

-

- {!prescription.is_prn - ? t("PRESCRIPTION_FREQUENCY_" + prescription.frequency) - : prescription.indicator} -

- - - - {/* Administration Cells */} - {props.intervals.map(({ start, end }, index) => ( - - {administrations === undefined ? ( - - ) : ( - - )} + +

{prescription.dosage}

+

+ {!prescription.is_prn + ? t("PRESCRIPTION_FREQUENCY_" + prescription.frequency) + : prescription.indicator} +

- ))} - - - {/* Action Buttons */} - - setShowAdminister(true)} - > - {t("administer")} - - - + + + {/* Administration Cells */} + {props.intervals.map(({ start, end }, index) => ( + + {administrations === undefined ? ( + + ) : ( + + )} + + ))} + + + {/* Action Buttons */} + + setShowAdminister(true)} + > + {t("administer")} + + + + ); }; @@ -446,67 +462,39 @@ const AdministrationCell = ({ dayjs(administration.administered_date).isBetween(start, end) ); - if (administered.length) { + // Check if cell belongs to a discontinued prescription and discontinued_date is not within the cell interval. + if ( + prescription.discontinued && + dayjs(end).isAfter(prescription.discontinued_date) && + !dayjs(prescription.discontinued_date).isBetween(start, end) + ) { + return; + } + + if (administered.length || prescription.discontinued) { return ( -
-
+
+ {!!administered.length && ( - {administered.length > 1 && ( - - {administered.length} - - )} -
- -

- Administered on{" "} - {formatDateTime(administered[0].administered_date)} -

-

- {administered.length > 1 - ? `Administered ${administered.length} times` - : `Administered ${formatTime(administered[0].administered_date)}`} -

-
-
- ); - } + )} - // Check if cell belongs to a discontinued prescription - if ( - prescription.discontinued && - dayjs(end).isAfter(prescription.discontinued_date) - ) { - if (!dayjs(prescription.discontinued_date).isBetween(start, end)) return; + {administered.length > 1 && ( + + )} - return ( -
- )} - /> - -

- Discontinued on{" "} - {formatDateTime(prescription.discontinued_date)} -

-

- Reason:{" "} - {prescription.discontinued_reason ? ( - {prescription.discontinued_reason} - ) : ( - Not specified - )} -

-
); } @@ -515,21 +503,6 @@ const AdministrationCell = ({ if (dayjs(start).isAfter(prescription.created_date)) { return ; } - - // Check if prescription.created_date is between start and end - // if (dayjs(prescription.created_date).isBetween(start, end)) { - // return ( - //
- // - // - //

- // Prescribed on{" "} - // {formatDateTime(prescription.created_date)} - //

- //
- //
- // ); - // } }; function getAdministrationBounds(prescriptions: Prescription[]) { diff --git a/src/Components/Medicine/models.ts b/src/Components/Medicine/models.ts index 62aea46b6d2..c33dfcf3932 100644 --- a/src/Components/Medicine/models.ts +++ b/src/Components/Medicine/models.ts @@ -50,13 +50,15 @@ export interface PRNPrescription extends BasePrescription { export type Prescription = NormalPrescription | PRNPrescription; export type MedicineAdministrationRecord = { - readonly id?: string; - readonly prescription?: Prescription; + readonly id: string; + readonly prescription: Prescription; notes: string; administered_date?: string; - readonly administered_by?: PerformedByModel; - readonly created_date?: string; - readonly modified_date?: string; + readonly administered_by: PerformedByModel; + readonly archived_by: PerformedByModel | undefined; + readonly archived_on: string | undefined; + readonly created_date: string; + readonly modified_date: string; }; export type MedibaseMedicine = { diff --git a/src/Redux/actions.tsx b/src/Redux/actions.tsx index 6e0d91fc59d..03e6ec13350 100644 --- a/src/Redux/actions.tsx +++ b/src/Redux/actions.tsx @@ -1031,7 +1031,7 @@ export const PrescriptionActions = (consultation_external_id: string) => { get: () => fireRequest("getPrescription", [], {}, pathParams), /** Administer a prescription */ - administer: (obj: MedicineAdministrationRecord) => + administer: (obj: Partial) => fireRequest( "administerPrescription", [], @@ -1040,9 +1040,18 @@ export const PrescriptionActions = (consultation_external_id: string) => { `administer-medicine-${external_id}` ), + archive: (mar: MedicineAdministrationRecord) => + fireRequest( + "archiveAdministration", + [], + {}, + { ...pathParams, external_id: mar.id } + ), + listAdministrations: (query?: { administered_date_after?: string; administered_date_before?: string; + archived?: boolean; }) => fireRequest( "listAdministrations", diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index b98a099f439..3c786d4821d 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -971,6 +971,11 @@ const routes = { method: "GET", }, + archiveAdministration: { + path: "/api/v1/consultation/{consultation_external_id}/prescription_administration/{external_id}/archive/", + method: "POST", + }, + getPrescription: { path: "/api/v1/consultation/{consultation_external_id}/prescriptions/{external_id}/", method: "GET",