forked from ohcnetwork/care_fe
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added Timeline UI for Bed Activity (ohcnetwork#6901)
* BedActivityTimeline component added * In Use indicator added to BedActivityTimeLine * fixed deepscan failure due to missing key prop * UI revision | reduced IN USE size | created BedTitleSuffix component * moved BedActivityTimeline one level up * Timeline note accepts react node | Bed timeline ui changed * improved logic for showing asset changes * moved all input fields to one row * changed in use badge color * Introduced i button popover to BedActivityTimeline * Added Asset diffing function * minor css change * added icons for adding and removing asset * no assets linked text added * moved input fields back to different lines * changed in use bed timeline node icon to l-bed * added missing key props to map objects * added icon style customizability for Timeline Node * fixed null value error * icon color changed for first node * added divider between form and timeline | moved button to right * Refactor BedAllocationNode and BedTimelineAsset components * fixed linting * fixed linting * minor fixes based on review --------- Co-authored-by: rithviknishad <[email protected]>
- Loading branch information
1 parent
30a97d1
commit 90dc755
Showing
3 changed files
with
287 additions
and
61 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
260 changes: 260 additions & 0 deletions
260
src/Components/Facility/Consultations/BedActivityTimeline.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,260 @@ | ||
import Chip from "../../../CAREUI/display/Chip"; | ||
import Timeline, { | ||
TimelineEvent, | ||
TimelineNode, | ||
TimelineNodeTitle, | ||
} from "../../../CAREUI/display/Timeline"; | ||
import CareIcon from "../../../CAREUI/icons/CareIcon"; | ||
import { classNames, formatDateTime, relativeTime } from "../../../Utils/utils"; | ||
import { AssetData } from "../../Assets/AssetTypes"; | ||
import { CurrentBed } from "../models"; | ||
import { Popover, Transition } from "@headlessui/react"; | ||
import { Fragment } from "react"; | ||
|
||
interface AssetDiff { | ||
newlyLinkedAssets: AssetData[]; | ||
existingAssets: AssetData[]; | ||
unlinkedAssets: AssetData[]; | ||
} | ||
|
||
const getAssetDiff = (a: AssetData[], b: AssetData[]): AssetDiff => { | ||
const newlyLinkedAssets: AssetData[] = []; | ||
const existingAssets: AssetData[] = []; | ||
const unlinkedAssets: AssetData[] = []; | ||
|
||
const bMap: Map<string, AssetData> = new Map(); | ||
b.forEach((asset) => bMap.set(asset.id, asset)); | ||
a.forEach((asset) => { | ||
if (!bMap.has(asset.id)) { | ||
unlinkedAssets.push(asset); | ||
} else { | ||
existingAssets.push(asset); | ||
} | ||
}); | ||
b.forEach((asset) => { | ||
if (!a.find((aAsset) => aAsset.id === asset.id)) { | ||
newlyLinkedAssets.push(asset); | ||
} | ||
}); | ||
|
||
return { | ||
newlyLinkedAssets, | ||
existingAssets, | ||
unlinkedAssets, | ||
}; | ||
}; | ||
|
||
interface Props { | ||
consultationBeds: CurrentBed[]; | ||
loading?: boolean; | ||
} | ||
|
||
export default function BedActivityTimeline({ | ||
consultationBeds, | ||
loading, | ||
}: Props) { | ||
return ( | ||
<> | ||
<Timeline | ||
className={classNames( | ||
"py-4 md:px-3", | ||
loading && "animate-pulse opacity-70", | ||
)} | ||
name="bed-allocation" | ||
> | ||
{consultationBeds.map((bed, index) => { | ||
return ( | ||
<BedAllocationNode | ||
key={`activity-${bed.id}`} | ||
bed={bed} | ||
prevBed={consultationBeds[index + 1] ?? undefined} | ||
isLastNode={index === consultationBeds.length - 1} | ||
/> | ||
); | ||
})} | ||
</Timeline> | ||
</> | ||
); | ||
} | ||
|
||
const BedAllocationNode = ({ | ||
bed, | ||
prevBed, | ||
isLastNode, | ||
}: { | ||
bed: CurrentBed; | ||
prevBed?: CurrentBed; | ||
isLastNode: boolean; | ||
}) => { | ||
const { newlyLinkedAssets, existingAssets, unlinkedAssets } = getAssetDiff( | ||
prevBed?.assets_objects ?? [], | ||
bed.assets_objects ?? [], | ||
); | ||
const event: TimelineEvent = { | ||
type: "allocated", | ||
timestamp: bed.start_date, | ||
by: undefined, | ||
icon: "l-bed", | ||
iconWrapperStyle: bed.end_date === null ? "bg-green-500" : "", | ||
iconStyle: bed.end_date === null ? "text-white" : "", | ||
notes: | ||
newlyLinkedAssets.length === 0 && | ||
existingAssets.length === 0 && | ||
unlinkedAssets.length === 0 ? ( | ||
"" | ||
) : ( | ||
<BedTimelineAsset | ||
newlyLinkedAssets={newlyLinkedAssets} | ||
existingAssets={existingAssets} | ||
unlinkedAssets={unlinkedAssets} | ||
/> | ||
), | ||
}; | ||
|
||
return ( | ||
<> | ||
<TimelineNode | ||
name="bed" | ||
event={event} | ||
title={ | ||
<BedTimelineNodeTitle | ||
event={event} | ||
titleSuffix={<BedTitleSuffix bed={bed} prevBed={prevBed} />} | ||
bed={bed} | ||
/> | ||
} | ||
isLast={isLastNode} | ||
/> | ||
</> | ||
); | ||
}; | ||
|
||
const BedTimelineAsset = ({ | ||
newlyLinkedAssets, | ||
existingAssets, | ||
unlinkedAssets, | ||
}: { | ||
newlyLinkedAssets: AssetData[]; | ||
existingAssets: AssetData[]; | ||
unlinkedAssets: AssetData[]; | ||
}) => { | ||
return ( | ||
<div className="flex flex-col gap-1"> | ||
<p className="text-md font-semibold">Assets</p> | ||
{newlyLinkedAssets.length === 0 && | ||
existingAssets.length === 0 && | ||
unlinkedAssets.length === 0 && ( | ||
<p className="text-gray-500">No assets linked</p> | ||
)} | ||
{newlyLinkedAssets.length > 0 && | ||
newlyLinkedAssets.map((newAsset) => ( | ||
<div key={newAsset.id} className="flex gap-1 text-primary"> | ||
<CareIcon icon="l-plus-circle" /> | ||
<span>{newAsset.name}</span> | ||
</div> | ||
))} | ||
{existingAssets.length > 0 && | ||
existingAssets.map((existingAsset) => ( | ||
<div key={existingAsset.id} className="flex gap-1"> | ||
<CareIcon icon="l-check-circle" /> | ||
<span>{existingAsset.name}</span> | ||
</div> | ||
))} | ||
{unlinkedAssets.length > 0 && | ||
unlinkedAssets.map((unlinkedAsset) => ( | ||
<div key={unlinkedAsset.id} className="flex gap-1 text-gray-500"> | ||
<CareIcon icon="l-minus-circle" /> | ||
<span className="line-through">{unlinkedAsset.name}</span> | ||
</div> | ||
))} | ||
</div> | ||
); | ||
}; | ||
|
||
const BedTimelineNodeTitle = (props: { | ||
event: TimelineEvent; | ||
titleSuffix: React.ReactNode; | ||
bed: CurrentBed; | ||
}) => { | ||
const { event, titleSuffix, bed } = props; | ||
|
||
return ( | ||
<TimelineNodeTitle event={event}> | ||
<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"> | ||
{titleSuffix} | ||
</p> | ||
<div className="md:w-fit"> | ||
<BedActivityIButtonPopover bed={bed} /> | ||
</div> | ||
</div> | ||
</TimelineNodeTitle> | ||
); | ||
}; | ||
|
||
const BedTitleSuffix = ({ | ||
bed, | ||
prevBed, | ||
}: { | ||
bed: CurrentBed; | ||
isLastNode?: boolean; | ||
prevBed?: CurrentBed; | ||
}) => { | ||
return ( | ||
<div className="flex flex-col"> | ||
<div className="flex gap-x-2"> | ||
<span>{formatDateTime(bed.start_date).split(";")[0]}</span> | ||
<span className="text-gray-500">-</span> | ||
<span>{formatDateTime(bed.start_date).split(";")[1]}</span> | ||
</div> | ||
<p> | ||
{bed.bed_object.id === prevBed?.bed_object.id | ||
? "Asset changed in" + " " | ||
: "Transferred to" + " "} | ||
<span className="font-semibold">{`${bed.bed_object.name} (${bed.bed_object.bed_type}) in ${bed.bed_object.location_object?.name}`}</span> | ||
{bed.end_date === null && ( | ||
<Chip | ||
text="In Use" | ||
startIcon="l-notes" | ||
size="small" | ||
variant="primary" | ||
className="ml-5" | ||
/> | ||
)} | ||
</p> | ||
</div> | ||
); | ||
}; | ||
|
||
const BedActivityIButtonPopover = ({ | ||
bed, | ||
}: { | ||
event?: TimelineEvent; | ||
bed?: CurrentBed; | ||
}) => { | ||
return ( | ||
<Popover className="relative text-sm text-gray-500 md:text-base"> | ||
<Popover.Button> | ||
<CareIcon | ||
icon="l-info-circle" | ||
className="cursor-pointer text-gray-500 hover:text-gray-600" | ||
/> | ||
</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 z-10 -ml-20 mt-2 w-48 -translate-x-1/2 rounded-lg border border-gray-200 bg-gray-100 p-2 shadow"> | ||
<p className="text-xs text-gray-600"> | ||
updated {relativeTime(bed?.start_date)} | ||
</p> | ||
</Popover.Panel> | ||
</Transition> | ||
</Popover> | ||
); | ||
}; |
Oops, something went wrong.