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

Timeline style view for Daily Rounds #6808

Merged
merged 20 commits into from
Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/CAREUI/display/RecordMeta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const RecordMeta = ({
let child = (
<div className="tooltip">
<span className="underline">{relativeTime(time)}</span>
<span className="tooltip-text tooltip-bottom flex -translate-x-1/2 gap-1 text-xs font-medium tracking-wider">
<span className="tooltip-text tooltip-left flex gap-1 text-xs font-medium tracking-wider">
{formatDateTime(time)}
{user && !inlineUser && (
<span className="flex items-center gap-1">
Expand Down
40 changes: 23 additions & 17 deletions src/CAREUI/display/Timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,23 +76,29 @@ export const TimelineNode = (props: TimelineNodeProps) => {
>
{props.title || (
<TimelineNodeTitle event={props.event}>
<p className="flex-auto py-0.5 text-xs leading-5 text-gray-600">
{props.event.by && (
<span className="font-medium text-gray-900">
{formatName(props.event.by)}{" "}
</span>
)}
{props.titleSuffix
? props.titleSuffix
: `${props.event.type} the ${props.name || name}.`}
</p>
{props.actions && (
<TimelineNodeActions>{props.actions}</TimelineNodeActions>
)}
<RecordMeta
className="flex-none py-0.5 text-xs leading-5 text-gray-500"
time={props.event.timestamp}
/>
<div className="flex w-full justify-between gap-2">
<p className="flex-auto py-0.5 text-xs leading-5 text-gray-600 md:w-2/3">
{props.event.by && (
<span className="font-medium text-gray-900">
{formatName(props.event.by)}{" "}
{props.event.by.user_type &&
`(${props.event.by.user_type}) `}
</span>
)}
{props.titleSuffix
? props.titleSuffix
: `${props.event.type} the ${props.name || name}.`}
</p>
<div className="md:w-fit">
{props.actions && (
<TimelineNodeActions>{props.actions}</TimelineNodeActions>
)}
<RecordMeta
className="flex-none py-0.5 text-xs leading-5 text-gray-500"
time={props.event.timestamp}
/>
</div>
</div>
</TimelineNodeTitle>
)}
</div>
Expand Down
43 changes: 29 additions & 14 deletions src/CAREUI/misc/PaginatedList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ButtonV2, {
import CareIcon from "../icons/CareIcon";
import { classNames } from "../../Utils/utils";
import Pagination from "../../Components/Common/Pagination";
import Timeline from "../display/Timeline";

const DEFAULT_PER_PAGE_LIMIT = 14;

Expand Down Expand Up @@ -129,20 +130,26 @@ interface ItemsProps<TItem> {
const Items = <TItem extends object>(props: ItemsProps<TItem>) => {
const { loading, items } = useContextualized<TItem>();

if (loading) {
return null;
}

return (
<ul className={props.className}>
{loading && props.shimmer
? Array.from({ length: props.shimmerCount ?? 8 }).map((_, i) => (
<li key={i} className="w-full">
{props.shimmer}
</li>
))
: items.map((item, index, items) => (
<li key={index} className="w-full">
{props.children(item, items)}
</li>
))}
</ul>
<Timeline className="rounded-lg bg-white p-2 shadow" name="log update">
<ul className={props.className}>
{loading && props.shimmer
? Array.from({ length: props.shimmerCount ?? 8 }).map((_, i) => (
<li key={i} className="w-full">
{props.shimmer}
</li>
))
: items.map((item, index, items) => (
<li key={index} className="w-full">
{props.children(item, items)}
</li>
))}
</ul>
</Timeline>
Comment on lines +138 to +152
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PagiantedList is a generic reusable component. Wrapping timeline here is not appropriate. Because not all Paginated List Items are Timelines, although all Timelines may be Paginated List Items.

);
};

Expand All @@ -153,8 +160,16 @@ interface PaginatorProps {
hideIfSinglePage?: boolean;
}

const Paginator = ({ className, hideIfSinglePage }: PaginatorProps) => {
const Paginator = <TItem extends object>({
className,
hideIfSinglePage,
}: PaginatorProps) => {
const { data, perPage, currentPage, setPage } = useContextualized<object>();
const { loading } = useContextualized<TItem>();

if (loading) {
return null;
}

if (hideIfSinglePage && (data?.count ?? 0) <= perPage) {
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useTranslation } from "react-i18next";
import RecordMeta from "../../../../CAREUI/display/RecordMeta";
import CareIcon from "../../../../CAREUI/icons/CareIcon";
import ButtonV2 from "../../../Common/components/ButtonV2";
import { DailyRoundsModel } from "../../../Patient/models";
Expand All @@ -12,87 +11,48 @@ interface Props {
onViewDetails: () => void;
onUpdateLog?: () => void;
}

const getName = (item: any) => {
return `${item?.first_name} ${item?.last_name} (${item?.user_type})`;
};

const DefaultLogUpdateCard = ({ round, ...props }: Props) => {
const { t } = useTranslation();
const telemedicine_doctor_update =
round.created_by_telemedicine || round.last_updated_by_telemedicine;

const by = props.consultationData.assigned_to_object || round.created_by;

return (
<div
className={`flex w-full flex-col gap-4 rounded-lg p-4 shadow @container ${
telemedicine_doctor_update ? "bg-purple-200" : "bg-white"
}`}
>
<div className="flex flex-col items-start gap-1">
<div className="flex w-min items-center gap-2 rounded-full border bg-gray-50 text-gray-500">
<div className="rounded-full bg-gray-100 px-1.5 py-0.5">
<CareIcon className="care-l-user-nurse text-lg" />
</div>
<span className="flex gap-1 whitespace-nowrap pr-3 text-sm tracking-wider">
<span className="font-semibold">{`${by?.first_name} ${by?.last_name}`}</span>
<span className="hidden font-medium @xs:block">
({by?.user_type})
</span>
</span>
</div>
<span className="flex gap-1 text-xs text-gray-700">
{t("created")} <RecordMeta time={round.created_date} />
</span>
</div>
<div className="flex flex-col gap-2">
{!telemedicine_doctor_update && round?.last_edited_by && (
<LogUpdateCardAttribute
attributeKey={"Updated By" as any}
attributeValue={getName(round.last_edited_by)}
/>
)}
<LogUpdateCardAttribute
attributeKey={"Round Type" as any}
attributeValue={t(round.rounds_type)}
/>
<LogUpdateCardAttribute
attributeKey="patient_category"
attributeValue={round.patient_category}
/>
<LogUpdateCardAttribute
attributeKey="physical_examination_info"
attributeValue={round.physical_examination_info}
/>
<LogUpdateCardAttribute
attributeKey="other_details"
attributeValue={round.other_details}
/>

<div className="mt-2 flex flex-col space-x-0 space-y-2 @xs:flex-row @xs:space-x-2 @xs:space-y-0">
<ButtonV2
variant="secondary"
border
ghost
onClick={props.onViewDetails}
>
<CareIcon className="care-l-eye text-lg" />
<span>{t("view_details")}</span>
</ButtonV2>
{props.onUpdateLog && (
<ButtonV2
variant="secondary"
border
ghost
className="tooltip"
onClick={props.onUpdateLog}
>
<CareIcon className="care-l-pen text-lg" />
<span>{t("update_log")}</span>
</ButtonV2>
)}
</div>
<div className="flex w-full flex-col gap-4 rounded-lg border border-gray-400 p-4 @container">
<LogUpdateCardAttribute
attributeKey={"Round Type" as any}
attributeValue={t(round.rounds_type)}
/>
<LogUpdateCardAttribute
attributeKey="patient_category"
attributeValue={round.patient_category}
/>
<LogUpdateCardAttribute
attributeKey="physical_examination_info"
attributeValue={round.physical_examination_info}
/>
<LogUpdateCardAttribute
attributeKey="other_details"
attributeValue={round.other_details}
/>
<div className="mt-2 flex items-center gap-2">
<ButtonV2
variant="secondary"
border
ghost
size="small"
onClick={props.onViewDetails}
Pranshu1902 marked this conversation as resolved.
Show resolved Hide resolved
>
<CareIcon className="care-l-eye text-lg" />
<span>{t("view_details")}</span>
</ButtonV2>
<ButtonV2
variant="secondary"
border
ghost
size="small"
onClick={props.onUpdateLog}
>
<CareIcon className="care-l-pen text-lg" />
<span>{t("update_log")}</span>
</ButtonV2>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { useTranslation } from "react-i18next";
import RecordMeta from "../../../../CAREUI/display/RecordMeta";
import CareIcon from "../../../../CAREUI/icons/CareIcon";
import { DailyRoundsModel } from "../../../Patient/models";
import LogUpdateCardAttribute from "./LogUpdateCardAttribute";

Expand Down Expand Up @@ -67,20 +65,7 @@ const VirtualNursingAssistantLogUpdateCard = (props: Props) => {
const diffKeys = Object.keys(diff);

return (
<div className="flex w-full flex-col gap-4 rounded-lg border border-green-300 bg-white p-4 shadow shadow-primary-500/20">
<div className="flex flex-col items-start gap-1">
<div className="flex w-min items-center gap-2 rounded-full border bg-green-50 text-primary-400">
<div className="rounded-full bg-green-100 px-1.5 py-0.5">
<CareIcon className="care-l-robot text-lg" />
</div>
<span className="whitespace-nowrap pr-3 text-sm font-semibold tracking-wider">
{t("virtual_nursing_assistant")}
</span>
</div>
<span className="flex gap-1 text-xs text-gray-700">
{t("created")} <RecordMeta time={props.round.created_date} />
</span>
</div>
<div className="flex w-full flex-col gap-4 rounded-lg border border-green-300 bg-white p-4 shadow-primary-500/20">
<div className="flex flex-col gap-1">
{diffKeys.length > 0 ? (
Object.keys(diff).map((key) => (
Expand Down
63 changes: 48 additions & 15 deletions src/Components/Facility/Consultations/DailyRoundsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import PageTitle from "../../Common/PageTitle";
import DailyRoundsFilter from "./DailyRoundsFilter";
import { ConsultationModel } from "../models";
import { useSlugs } from "../../../Common/hooks/useSlug";
import { TimelineNode } from "../../../CAREUI/display/Timeline";

interface Props {
consultation: ConsultationModel;
Expand Down Expand Up @@ -45,20 +46,34 @@ export default function DailyRoundsList({ consultation }: Props) {
</span>
</PaginatedList.WhenEmpty>
<PaginatedList.WhenLoading>
<>
{Array.from({ length: 3 }).map((_, i) => (
<LoadingLogUpdateCard key={i} />
))}
</>
<LoadingLogUpdateCard />
</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]}
/>
<TimelineNode
event={{
type: "created",
timestamp: item.taken_at?.toString() ?? "",
by: {
user_type: "",
first_name: "Virtual",
last_name: "Assistant",
username: "",
id: "",
email: "",
last_login: "",
},
icon: "l-robot",
}}
isLast={items.indexOf(item) == items.length - 1}
>
<VirtualNursingAssistantLogUpdateCard
round={item}
previousRound={items[items.indexOf(item) + 1]}
/>
</TimelineNode>
);
}

Expand All @@ -69,12 +84,30 @@ export default function DailyRoundsList({ consultation }: Props) {
: `${consultationUrl}/daily_rounds/${item.id}`;

return (
<DefaultLogUpdateCard
round={item}
consultationData={consultation}
onViewDetails={() => navigate(itemUrl)}
onUpdateLog={() => navigate(`${itemUrl}/update`)}
/>
<TimelineNode
event={{
type: "created",
timestamp: item.taken_at?.toString() ?? "",
by: {
user_type: item.created_by?.user_type ?? "",
first_name: item.created_by?.first_name ?? "",
last_name: item.created_by?.last_name ?? "",
username: "",
id: "",
email: "",
last_login: "",
},
icon: "l-user-nurse",
}}
isLast={items.indexOf(item) == items.length - 1}
>
<DefaultLogUpdateCard
round={item}
consultationData={consultation}
onViewDetails={() => navigate(itemUrl)}
onUpdateLog={() => navigate(`${itemUrl}/update`)}
/>
</TimelineNode>
);
}}
</PaginatedList.Items>
Expand Down
Loading