diff --git a/src/Components/Common/components/SwitchTabs.tsx b/src/Components/Common/components/SwitchTabs.tsx
index 047dba53b4b..a7872d5a400 100644
--- a/src/Components/Common/components/SwitchTabs.tsx
+++ b/src/Components/Common/components/SwitchTabs.tsx
@@ -1,12 +1,21 @@
+import type { ReactNode } from "react";
+import { classNames } from "../../../Utils/utils";
+
export default function SwitchTabs(props: {
+ className?: string;
isTab2Active: boolean;
onClickTab1: () => void;
onClickTab2: () => void;
- tab1: string;
- tab2: string;
+ tab1: ReactNode;
+ tab2: ReactNode;
}) {
return (
-
+
import("../../Common/PageTitle"));
@@ -23,6 +25,7 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => {
const [ventilatorSocketUrl, setVentilatorSocketUrl] = useState
();
const [monitorBedData, setMonitorBedData] = useState();
const [ventilatorBedData, setVentilatorBedData] = useState();
+ const [showEvents, setShowEvents] = useState(false);
const vitals = useVitalsAspectRatioConfig({
default: undefined,
@@ -665,7 +668,26 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => {
-
+
+ Events
+
+ beta
+
+
+ }
+ tab1="Daily Rounds"
+ onClickTab1={() => setShowEvents(false)}
+ onClickTab2={() => setShowEvents(true)}
+ isTab2Active={showEvents}
+ />
+ {showEvents ? (
+
+ ) : (
+
+ )}
diff --git a/src/Components/Facility/ConsultationDetails/Events/EventsList.tsx b/src/Components/Facility/ConsultationDetails/Events/EventsList.tsx
new file mode 100644
index 00000000000..82a806dfc6b
--- /dev/null
+++ b/src/Components/Facility/ConsultationDetails/Events/EventsList.tsx
@@ -0,0 +1,78 @@
+import { useTranslation } from "react-i18next";
+import { useSlugs } from "../../../../Common/hooks/useSlug";
+import { ConsultationModel } from "../../models";
+import PaginatedList from "../../../../CAREUI/misc/PaginatedList";
+import routes from "../../../../Redux/api";
+import { TimelineNode } from "../../../../CAREUI/display/Timeline";
+import LoadingLogUpdateCard from "../../Consultations/DailyRounds/LoadingCard";
+import GenericEvent from "./GenericEvent";
+import { EventGeneric } from "./types";
+import { getEventIcon } from "./iconMap";
+
+interface Props {
+ consultation: ConsultationModel;
+}
+
+export default function EventsList({ consultation }: Props) {
+ const [consultationId] = useSlugs("consultation");
+ const { t } = useTranslation();
+
+
+ return (
+
+ {() => (
+ <>
+
+
+
+
+ {t("no_consultation_updates")}
+
+
+
+
+
+
className="flex grow flex-col gap-3">
+ {(item, items) => (
+
+ text[0].toUpperCase() + text.toLowerCase().slice(1)
+ )
+ .join(" ") + " Event"
+ }
+ event={{
+ type: item.change_type.replace(/_/g, " ").toLowerCase(),
+ timestamp: item.created_date?.toString() ?? "",
+ by: item.caused_by,
+ icon: getEventIcon(item.event_type.name),
+ }}
+ isLast={items.indexOf(item) == items.length - 1}
+ >
+ {(() => {
+ switch (item.event_type.name) {
+ case "INTERNAL_TRANSFER":
+ case "CLINICAL":
+ case "DIAGNOSIS":
+ case "ENCOUNTER_SUMMARY":
+ case "HEALTH":
+ default:
+ return ;
+ }
+ })()}
+
+ )}
+
+
+
+
+ >
+ )}
+
+ );
+}
diff --git a/src/Components/Facility/ConsultationDetails/Events/GenericEvent.tsx b/src/Components/Facility/ConsultationDetails/Events/GenericEvent.tsx
new file mode 100644
index 00000000000..4296bd4c34b
--- /dev/null
+++ b/src/Components/Facility/ConsultationDetails/Events/GenericEvent.tsx
@@ -0,0 +1,90 @@
+import type { ReactNode } from "react";
+import { EventGeneric } from "./types";
+
+interface IProps {
+ event: EventGeneric;
+}
+
+/**
+ * object - array, date
+ */
+
+const formatValue = (value: unknown, key?: string): ReactNode => {
+ if (value === undefined || value === null) {
+ return "N/A";
+ }
+
+ if (typeof value === "boolean") {
+ return value ? "Yes" : "No";
+ }
+
+ if (typeof value === "number") {
+ return value;
+ }
+
+ if (typeof value === "string") {
+ const trimmed = value.trim();
+
+ if (trimmed === "") {
+ return "Empty";
+ }
+
+ if (!isNaN(Number(trimmed))) {
+ return trimmed;
+ }
+
+ if (new Date(trimmed).toString() !== "Invalid Date") {
+ return new Date(trimmed).toLocaleString();
+ }
+
+ return trimmed;
+ }
+
+ if (typeof value === "object") {
+ if (Array.isArray(value)) {
+ if (value.length === 0) {
+ return `No ${key?.replace(/_/g, " ")}`;
+ }
+
+ return value.map((v) => formatValue(v, key)).join(", ");
+ }
+
+ if (value instanceof Date) {
+ return value.toLocaleString();
+ }
+
+ if (Object.entries(value).length === 0) {
+ return `No ${key?.replace(/_/g, " ")}`;
+ }
+
+ return Object.entries(value).map(([key, value]) => (
+
+
+ {key.replace(/_/g, " ")}
+
+
+ {formatValue(value, key)}
+
+
+ ));
+ }
+
+ return JSON.stringify(value);
+};
+
+export default function GenericEvent({ event }: IProps) {
+ return (
+
+ {Object.entries(event.value).map(([key, value]) => (
+
+
+ {key.replace(/_/g, " ")}
+
+
+ {formatValue(value, key)}
+
+
+ ))}
+
+ );
+}
diff --git a/src/Components/Facility/ConsultationDetails/Events/iconMap.ts b/src/Components/Facility/ConsultationDetails/Events/iconMap.ts
new file mode 100644
index 00000000000..edf097def22
--- /dev/null
+++ b/src/Components/Facility/ConsultationDetails/Events/iconMap.ts
@@ -0,0 +1,13 @@
+import { IconName } from "../../../../CAREUI/icons/CareIcon";
+
+const eventIconMap: Record = {
+ INTERNAL_TRANSFER: "l-exchange-alt",
+ CLINICAL: "l-stethoscope",
+ DIAGNOSIS: "l-tablets",
+ ENCOUNTER_SUMMARY: "l-file-medical-alt",
+ HEALTH: "l-heartbeat",
+};
+
+export const getEventIcon = (eventType: string): IconName => {
+ return eventIconMap[eventType] || "l-robot";
+};
diff --git a/src/Components/Facility/ConsultationDetails/Events/types.ts b/src/Components/Facility/ConsultationDetails/Events/types.ts
new file mode 100644
index 00000000000..f5cf3c9abec
--- /dev/null
+++ b/src/Components/Facility/ConsultationDetails/Events/types.ts
@@ -0,0 +1,30 @@
+import { UserBareMinimum } from "../../../Users/models";
+
+export type Type = {
+ id: number;
+ parent: number | null;
+ name: string;
+ description: string | null;
+ model: string;
+ fields: string[];
+};
+
+export type CausedBy = UserBareMinimum;
+
+export type EventGeneric = {
+ id: string;
+ event_type: Type;
+ created_date: string;
+ object_model: string;
+ object_id: number;
+ is_latest: boolean;
+ meta: {
+ external_id: string;
+ };
+ value: Record;
+ change_type: "CREATED" | "UPDATED" | "DELETED";
+ consultation: number;
+ caused_by: UserBareMinimum;
+};
+
+// TODO: Once event types are finalized, define specific types for each event
diff --git a/src/Components/Facility/Consultations/DailyRoundsList.tsx b/src/Components/Facility/Consultations/DailyRoundsList.tsx
index 61a97f1688b..e23a38e8f59 100644
--- a/src/Components/Facility/Consultations/DailyRoundsList.tsx
+++ b/src/Components/Facility/Consultations/DailyRoundsList.tsx
@@ -6,7 +6,6 @@ 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";
@@ -34,8 +33,7 @@ export default function DailyRoundsList({ consultation }: Props) {
>
{() => (
<>
-
-
+
{
setQuery(query);
diff --git a/src/Components/HCX/misc.ts b/src/Components/HCX/misc.ts
index 9476f19c081..dba0a290b85 100644
--- a/src/Components/HCX/misc.ts
+++ b/src/Components/HCX/misc.ts
@@ -1,9 +1,3 @@
-export interface PerformedByModel {
- id: string;
- first_name: string;
- last_name: string;
- username: string;
- email: string;
- user_type: string;
- last_login: string;
-}
+import { UserBareMinimum } from "../Users/models";
+
+export type PerformedByModel = UserBareMinimum;
diff --git a/src/Components/Patient/PatientInfoCard.tsx b/src/Components/Patient/PatientInfoCard.tsx
index 25683e1a461..101d11113d1 100644
--- a/src/Components/Patient/PatientInfoCard.tsx
+++ b/src/Components/Patient/PatientInfoCard.tsx
@@ -531,7 +531,7 @@ export default function PatientInfoCard(props: {
title={"Manage Patient"}
icon={}
className="xl:justify-center"
- containerClassName="w-full lg:w-auto mt-2 2xl:mt-0 flex justify-center"
+ containerClassName="w-full lg:w-auto mt-2 2xl:mt-0 flex justify-center z-20"
>
{[
diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx
index 8b2a3ae08c2..eb75f23c77c 100644
--- a/src/Redux/api.tsx
+++ b/src/Redux/api.tsx
@@ -95,6 +95,7 @@ import {
} from "../Components/Facility/Investigations";
import { Investigation } from "../Components/Facility/Investigations/Reports/types";
import { ICD11DiagnosisModel } from "../Components/Diagnosis/types";
+import { EventGeneric } from "../Components/Facility/ConsultationDetails/Events/types";
/**
* A fake function that returns an empty object casted to type T
@@ -554,6 +555,12 @@ const routes = {
TRes: Type
>(),
},
+ getEvents: {
+ path: "/api/v1/consultation/{consultationId}/events/",
+ method: "GET",
+ TRes: Type>(),
+ },
+
getDailyReport: {
path: "/api/v1/consultation/{consultationId}/daily_rounds/{id}/",
method: "GET",