-
Notifications
You must be signed in to change notification settings - Fork 501
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
61c8112
commit b7e8df7
Showing
7 changed files
with
1,464 additions
and
776 deletions.
There are no files selected for viewing
12 changes: 5 additions & 7 deletions
12
src/Components/Facility/ConsultationDetails/ConsultationMedicinesTab.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
181 changes: 181 additions & 0 deletions
181
src/Components/Medicine/MedicineAdministrationSheet/AdministrationEventCell.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,181 @@ | ||
import { Disclosure, Popover, Transition } from "@headlessui/react"; | ||
import dayjs from "../../../Utils/dayjs"; | ||
import { MedicineAdministrationRecord, Prescription } from "../models"; | ||
import CareIcon from "../../../CAREUI/icons/CareIcon"; | ||
import { Fragment } from "react"; | ||
import { classNames, formatDateTime, formatTime } from "../../../Utils/utils"; | ||
|
||
interface Props { | ||
administrations: MedicineAdministrationRecord[]; | ||
interval: { start: Date; end: Date }; | ||
prescription: Prescription; | ||
} | ||
|
||
export default function AdministrationEventCell({ | ||
administrations, | ||
interval: { start, end }, | ||
prescription, | ||
}: Props) { | ||
// Check if cell belongs to an administered prescription | ||
const administered = administrations | ||
.filter((administration) => | ||
dayjs(administration.administered_date).isBetween(start, end) | ||
) | ||
.sort( | ||
(a, b) => | ||
new Date(a.administered_date!).getTime() - | ||
new Date(b.administered_date!).getTime() | ||
); | ||
|
||
const hasComment = administered.some((obj) => !!obj.notes); | ||
|
||
if (administered.length) { | ||
return ( | ||
<Popover className="relative"> | ||
<Popover.Button className="scale-100 transition-transform duration-200 ease-in-out hover:scale-110"> | ||
<div className="tooltip"> | ||
<div className="relative mx-auto max-w-min"> | ||
<CareIcon | ||
icon="l-check-circle" | ||
className="text-xl text-primary-500" | ||
/> | ||
{administered.length > 1 && ( | ||
<span className="absolute -bottom-1 -right-2 flex h-4 w-4 items-center justify-center rounded-full bg-primary-500 text-xs font-semibold text-white"> | ||
{administered.length} | ||
</span> | ||
)} | ||
</div> | ||
{hasComment && ( | ||
<CareIcon icon="l-notes" className="text-xl text-primary-500" /> | ||
)} | ||
<span className="tooltip-text tooltip-top -translate-x-1/2 text-xs"> | ||
{administered.length === 1 ? ( | ||
<p> | ||
Administered on{" "} | ||
<strong> | ||
{formatTime(administered[0].administered_date)} | ||
</strong> | ||
</p> | ||
) : ( | ||
<p> | ||
<strong>{administered.length}</strong> administrations | ||
</p> | ||
)} | ||
<p>Click to view details</p> | ||
</span> | ||
</div> | ||
</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 left-1/2 z-10 mt-3 -translate-x-1/2 px-4 sm:px-0"> | ||
<div className="overflow-hidden rounded-lg shadow-lg ring-1 ring-black/5"> | ||
<div className="relative flex flex-col gap-2 bg-white p-4"> | ||
{administered.map((administration) => ( | ||
<div> | ||
<Disclosure defaultOpen={administered.length === 1}> | ||
{({ open }) => ( | ||
<> | ||
<Disclosure.Button | ||
className={classNames( | ||
"flex w-full justify-between border-gray-400 px-4 py-2 text-left text-sm focus:outline-none focus-visible:ring focus-visible:ring-primary-500/75", | ||
open | ||
? "rounded-t-lg border-l border-r border-t bg-gray-200" | ||
: "rounded-lg border hover:bg-gray-200" | ||
)} | ||
> | ||
<span className="text-gray-700"> | ||
Administered on{" "} | ||
<strong className="font-semibold text-gray-900"> | ||
{formatTime(administration.administered_date)} | ||
</strong> | ||
</span> | ||
{administration.notes && ( | ||
<CareIcon | ||
icon="l-notes" | ||
className="ml-2 text-lg" | ||
/> | ||
)} | ||
<CareIcon | ||
icon="l-angle-down" | ||
className={classNames( | ||
"ml-8 text-base", | ||
open ? "rotate-180" : "rotate-0" | ||
)} | ||
/> | ||
</Disclosure.Button> | ||
<Disclosure.Panel className="flex flex-col items-start rounded-b-lg border border-gray-400 bg-gray-200 p-2 px-4 text-sm text-gray-700 shadow"> | ||
<div> | ||
Administered by:{" "} | ||
<span className="font-medium text-gray-900"> | ||
{administration.administered_by?.first_name}{" "} | ||
{administration.administered_by?.last_name} | ||
</span> | ||
</div> | ||
<div> | ||
Notes:{" "} | ||
<span className="font-medium text-gray-800"> | ||
{administration.notes || ( | ||
<span className="italic text-gray-700"> | ||
No notes | ||
</span> | ||
)} | ||
</span> | ||
</div> | ||
</Disclosure.Panel> | ||
</> | ||
)} | ||
</Disclosure> | ||
</div> | ||
))} | ||
</div> | ||
</div> | ||
</Popover.Panel> | ||
</Transition> | ||
</Popover> | ||
); | ||
} | ||
|
||
// 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; | ||
|
||
return ( | ||
<div className="tooltip"> | ||
<CareIcon | ||
icon="l-ban" | ||
className={classNames( | ||
"text-xl", | ||
dayjs(prescription.discontinued_date).isBetween(start, end) | ||
? "text-danger-700" | ||
: "text-gray-400" | ||
)} | ||
/> | ||
<span className="tooltip-text tooltip-top -translate-x-1/2 text-xs"> | ||
<p> | ||
Discontinued on{" "} | ||
<strong>{formatDateTime(prescription.discontinued_date)}</strong> | ||
</p> | ||
<p> | ||
Reason:{" "} | ||
{prescription.discontinued_reason ? ( | ||
<strong>{prescription.discontinued_reason}</strong> | ||
) : ( | ||
<span className="italic">Not specified</span> | ||
)} | ||
</p> | ||
</span> | ||
</div> | ||
); | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
src/Components/Medicine/MedicineAdministrationSheet/AdministrationEventSeperator.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,22 @@ | ||
import { formatDateTime } from "../../../Utils/utils"; | ||
|
||
export default function AdministrationEventSeperator({ date }: { date: Date }) { | ||
// Show date if it's 00:00 | ||
if (date.getHours() === 0) { | ||
return ( | ||
<div className="mx-auto flex h-[58px] flex-col items-center justify-center bg-gray-300 text-center text-xs font-bold text-gray-600 transition-all duration-200 ease-in-out group-hover:bg-primary-500 group-hover:text-white"> | ||
<span className="-rotate-90 uppercase duration-500 ease-in-out"> | ||
<p> {formatDateTime(date, "DD/MM")}</p> | ||
</span> | ||
</div> | ||
); | ||
} | ||
|
||
return ( | ||
<div className="mx-auto flex h-[58px] flex-col items-center justify-center text-center text-xs font-bold text-gray-500 transition-all duration-200 ease-in-out"> | ||
<span className="font-bold duration-500 ease-in-out"> | ||
<p>{formatDateTime(date, "HH")}</p> | ||
</span> | ||
</div> | ||
); | ||
} |
104 changes: 104 additions & 0 deletions
104
src/Components/Medicine/MedicineAdministrationSheet/AdministrationTable.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,104 @@ | ||
import { useTranslation } from "react-i18next"; | ||
import CareIcon from "../../../CAREUI/icons/CareIcon"; | ||
import useRangePagination from "../../../Common/hooks/useRangePagination"; | ||
import { classNames, formatDateTime } from "../../../Utils/utils"; | ||
import ButtonV2 from "../../Common/components/ButtonV2"; | ||
import { Prescription } from "../models"; | ||
import MedicineAdministrationTableRow from "./AdministrationTableRow"; | ||
|
||
interface Props { | ||
prescriptions: Prescription[]; | ||
pagination: ReturnType<typeof useRangePagination>; | ||
onRefetch: () => void; | ||
} | ||
|
||
export default function MedicineAdministrationTable({ | ||
pagination, | ||
prescriptions, | ||
onRefetch, | ||
}: Props) { | ||
const { t } = useTranslation(); | ||
|
||
return ( | ||
<table className="w-full whitespace-nowrap rounded"> | ||
<thead className="sticky top-0 z-10 bg-white text-xs font-medium text-black"> | ||
<tr> | ||
<th className="sticky left-0 z-10 bg-white py-3 pl-4 text-left"> | ||
<div className="flex justify-between gap-2"> | ||
<span className="text-sm">{t("medicine")}</span> | ||
<span className="hidden px-2 text-center text-xs leading-none lg:block"> | ||
<p>Dosage &</p> | ||
<p>{!prescriptions[0]?.is_prn ? "Frequency" : "Indicator"}</p> | ||
</span> | ||
</div> | ||
</th> | ||
|
||
<th> | ||
<ButtonV2 | ||
size="small" | ||
circle | ||
ghost | ||
border | ||
className="mx-2 px-1" | ||
variant="secondary" | ||
disabled={!pagination.hasPrevious} | ||
onClick={pagination.previous} | ||
tooltip="Previous 24 hours" | ||
tooltipClassName="tooltip-bottom -translate-x-1/2 text-xs" | ||
> | ||
<CareIcon icon="l-angle-left-b" className="text-base" /> | ||
</ButtonV2> | ||
</th> | ||
{pagination.slots?.map(({ start }, index) => ( | ||
<> | ||
<th | ||
key={`administration-interval-${index}`} | ||
className={classNames( | ||
"leading-none", | ||
start.getHours() === 0 | ||
? "text-base font-bold text-gray-800" | ||
: "text-sm font-semibold text-gray-700" | ||
)} | ||
> | ||
{formatDateTime( | ||
start, | ||
start.getHours() === 0 ? "DD/MM" : "HH:mm" | ||
)} | ||
</th> | ||
<th key={`administration-slot-${index}`} className="flex w-6" /> | ||
</> | ||
))} | ||
<th> | ||
<ButtonV2 | ||
size="small" | ||
circle | ||
ghost | ||
border | ||
className="mx-2 px-1" | ||
variant="secondary" | ||
disabled={!pagination.hasNext} | ||
onClick={pagination.next} | ||
tooltip="Next 24 hours" | ||
tooltipClassName="tooltip-bottom -translate-x-1/2 text-xs" | ||
> | ||
<CareIcon icon="l-angle-right-b" className="text-base" /> | ||
</ButtonV2> | ||
</th> | ||
|
||
<th className="py-3 pr-2 text-right"></th> | ||
</tr> | ||
</thead> | ||
|
||
<tbody className="divide-y divide-gray-200"> | ||
{prescriptions.map((obj) => ( | ||
<MedicineAdministrationTableRow | ||
key={obj.id} | ||
prescription={obj} | ||
intervals={pagination.slots!} | ||
refetch={onRefetch} | ||
/> | ||
))} | ||
</tbody> | ||
</table> | ||
); | ||
} |
Oops, something went wrong.