From e85e9d87044c25f087676051cc2a06c241dc0d91 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Mon, 27 May 2024 22:14:30 +0530 Subject: [PATCH 001/106] Improve camera preset selection UI in camera feed (#7915) * Improve camera preset selection in camera feed * switch to gray shade --- src/Components/CameraFeed/AssetBedSelect.tsx | 40 +++++++++++++++++-- src/Components/CameraFeed/CameraFeed.tsx | 9 ++++- .../ConsultationFeedTab.tsx | 21 +++++----- 3 files changed, 56 insertions(+), 14 deletions(-) diff --git a/src/Components/CameraFeed/AssetBedSelect.tsx b/src/Components/CameraFeed/AssetBedSelect.tsx index c8a2d5451bc..0bb40dffb98 100644 --- a/src/Components/CameraFeed/AssetBedSelect.tsx +++ b/src/Components/CameraFeed/AssetBedSelect.tsx @@ -2,6 +2,7 @@ import { Fragment } from "react"; import { AssetBedModel } from "../Assets/AssetTypes"; import { Listbox, Transition } from "@headlessui/react"; import CareIcon from "../../CAREUI/icons/CareIcon"; +import { classNames } from "../../Utils/utils"; interface Props { options: AssetBedModel[]; @@ -10,7 +11,40 @@ interface Props { onChange?: (value: AssetBedModel) => void; } -export default function AssetBedSelect(props: Props) { +export default function CameraPresetSelect(props: Props) { + const label = props.label ?? defaultLabel; + return ( + <> +
+ {props.options + .slice(0, props.options.length > 5 ? 4 : 5) + .map((option) => ( + + ))} + {/* Desktop View */} + {props.options.length > 5 && ( + + )} +
+
+ {/* Mobile View */} + +
+ + ); +} + +const ShowMoreDropdown = (props: Props) => { const selected = props.value; const options = props.options.filter(({ meta }) => meta.type !== "boundary"); @@ -20,7 +54,7 @@ export default function AssetBedSelect(props: Props) { return (
- + {selected ? label(selected) : "No Preset"} @@ -63,7 +97,7 @@ export default function AssetBedSelect(props: Props) {
); -} +}; const defaultLabel = ({ bed_object, meta }: AssetBedModel) => { return `${bed_object.name}: ${meta.preset_name}`; diff --git a/src/Components/CameraFeed/CameraFeed.tsx b/src/Components/CameraFeed/CameraFeed.tsx index 8f7659cf730..f40aed384c8 100644 --- a/src/Components/CameraFeed/CameraFeed.tsx +++ b/src/Components/CameraFeed/CameraFeed.tsx @@ -10,6 +10,7 @@ import FeedNetworkSignal from "./FeedNetworkSignal"; import NoFeedAvailable from "./NoFeedAvailable"; import FeedControls from "./FeedControls"; import Fullscreen from "../../CAREUI/misc/Fullscreen"; +import CareIcon from "../../CAREUI/icons/CareIcon"; interface Props { children?: React.ReactNode; @@ -96,8 +97,13 @@ export default function CameraFeed(props: Props) { )} >
-
+ {props.children} +
+ {props.asset.name}
@@ -109,7 +115,6 @@ export default function CameraFeed(props: Props) { />
- {props.children}
diff --git a/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx index 64ba39ae5aa..40e5ffdc610 100644 --- a/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx +++ b/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx @@ -120,7 +120,7 @@ export const ConsultationFeedTab = (props: ConsultationTabProps) => { }); }} > -
+
{presets ? ( <> { {isUpdatingPreset ? ( ) : ( - + )} From 828cd503875e64a8328065f7a3c6aa5bf9f843b9 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Mon, 27 May 2024 22:36:18 +0530 Subject: [PATCH 002/106] added skeleton loader for consent records (#7905) * added skeleton loader for consent records * fixed consents * minor fix * fixed * treating deleted and archived files differently * fixed responsiveness --- .../Patient/PatientConsentRecordBlock.tsx | 136 +++++++++++------- .../Patient/PatientConsentRecords.tsx | 51 ++++--- src/Utils/useFileManager.tsx | 30 +++- src/style/index.css | 6 + 4 files changed, 141 insertions(+), 82 deletions(-) diff --git a/src/Components/Patient/PatientConsentRecordBlock.tsx b/src/Components/Patient/PatientConsentRecordBlock.tsx index 8f1d3715e8e..9c1969ae5a3 100644 --- a/src/Components/Patient/PatientConsentRecordBlock.tsx +++ b/src/Components/Patient/PatientConsentRecordBlock.tsx @@ -15,7 +15,11 @@ import useAuthUser from "../../Common/hooks/useAuthUser"; export default function PatientConsentRecordBlockGroup(props: { consentRecord: ConsentRecord; previewFile: (file: FileUploadModel, file_associating_id: string) => void; - archiveFile: (file: FileUploadModel, file_associating_id: string) => void; + archiveFile: ( + file: FileUploadModel, + file_associating_id: string, + skipPrompt?: { reason: string }, + ) => void; onDelete: (consentRecord: ConsentRecord) => void; refreshTrigger: any; showArchive: boolean; @@ -39,6 +43,24 @@ export default function PatientConsentRecordBlockGroup(props: { limit: 100, offset: 0, }, + onResponse: (response) => { + /* + if (consentRecord.deleted === true && response.data?.results) { + const unarchivedFiles = response.data.results; + console.log("checking for unarchived files on this deleted consent record") + for (const file of unarchivedFiles) { + console.log("archiving file", file) + archiveFile(file, consentRecord.id, { + reason: "Consent Record Archived", + }); + } + } + */ + + if ((response.data?.results?.length || 0) > 0) { + props.onFilesFound(); + } + }, }); const archivedFilesQuery = useQuery(routes.viewUpload, { @@ -49,6 +71,12 @@ export default function PatientConsentRecordBlockGroup(props: { limit: 100, offset: 0, }, + prefetch: showArchive, + onResponse: (response) => { + if ((response.data?.results?.length || 0) > 0) { + props.onFilesFound(); + } + }, }); const consent = CONSENT_TYPE_CHOICES.find((c) => c.id === consentRecord.type); @@ -59,20 +87,19 @@ export default function PatientConsentRecordBlockGroup(props: { const data = showArchive ? [ ...(archivedFilesQuery.data?.results || []), - ...(filesQuery.data?.results || []), + ...(consentRecord.deleted ? filesQuery.data?.results || [] : []), ] : filesQuery.data?.results; - useEffect(() => { - filesQuery.refetch(); - archivedFilesQuery.refetch(); - }, [refreshTrigger]); + const loading = archivedFilesQuery.loading || filesQuery.loading; useEffect(() => { - if ((data?.length || 0) > 1) { - props.onFilesFound(); + if (!showArchive) { + filesQuery.refetch(); + } else { + archivedFilesQuery.refetch(); } - }, [data]); + }, [showArchive, refreshTrigger]); return (
- - {data?.map((file: FileUploadModel, i: number) => ( -
-
-
- -
-
-
- {file.name} - {file.extension} {file.is_archived && "(Archived)"} + {loading ? ( +
+ ) : ( + data?.map((file: FileUploadModel, i: number) => ( +
+
+
+
-
- {dayjs(file.created_date).format("DD MMM YYYY, hh:mm A")} +
+
+ {file.name} + {file.extension} {file.is_archived && "(Archived)"} +
+
+ {dayjs(file.created_date).format("DD MMM YYYY, hh:mm A")} +
+
+ {!file.is_archived && ( + previewFile(file, consentRecord.id)} + className="" + > + + View + + )} + {(file.is_archived || + file?.uploaded_by?.username === authUser.username || + authUser.user_type === "DistrictAdmin" || + authUser.user_type === "StateAdmin") && ( + archiveFile(file, consentRecord.id)} + className="" + > + + {file.is_archived ? "More Info" : "Archive"} + + )} +
-
- {!file.is_archived && ( - previewFile(file, consentRecord.id)} - className="" - > - - View - - )} - {(file.is_archived || - file?.uploaded_by?.username === authUser.username || - authUser.user_type === "DistrictAdmin" || - authUser.user_type === "StateAdmin") && ( - archiveFile(file, consentRecord.id)} - className="" - > - - {file.is_archived ? "More Info" : "Archive"} - - )} -
-
- ))} + )) + )}
); } diff --git a/src/Components/Patient/PatientConsentRecords.tsx b/src/Components/Patient/PatientConsentRecords.tsx index 16178ef5c68..ca26b270d80 100644 --- a/src/Components/Patient/PatientConsentRecords.tsx +++ b/src/Components/Patient/PatientConsentRecords.tsx @@ -51,7 +51,11 @@ export default function PatientConsentRecords(props: { id: patientId, }, }); - const { data: consultation, refetch } = useQuery(routes.getConsultation, { + const { + data: consultation, + refetch, + loading, + } = useQuery(routes.getConsultation, { pathParams: { id: consultationId! }, onResponse: (data) => { if (data.data && data.data.consent_records) { @@ -130,9 +134,7 @@ export default function PatientConsentRecords(props: { return () => clearTimeout(timeout); }, [consentRecords]); - const tabConsents = consentRecords?.filter( - (record) => showArchived || record.deleted !== true, - ); + const tabConsents = consentRecords?.filter((c) => showArchived || !c.deleted); useEffect(() => { setFilesFound(false); @@ -192,15 +194,15 @@ export default function PatientConsentRecords(props: { className="w-auto" /> setShowArchived(false)} onClickTab2={() => setShowArchived(true)} isTab2Active={showArchived} /> -
-
+
+

Add New Record

record.type === 2 && + newConsent.type === 2 && record.patient_code_status !== newConsent.patient_code_status && record.deleted !== true, @@ -280,25 +283,27 @@ export default function PatientConsentRecords(props: {
- {tabConsents?.length === 0 || - (!filesFound && ( +
+ {loading ? ( +
+ ) : tabConsents?.length === 0 || !filesFound ? (
No records found
- ))} -
- {tabConsents?.map((record, index) => ( - setShowDeleteConsent(record.id)} - refreshTrigger={consultation} - showArchive={showArchived} - onFilesFound={() => setFilesFound(true)} - /> - ))} + ) : null} + {!loading && + tabConsents?.map((record, index) => ( + setShowDeleteConsent(record.id)} + refreshTrigger={consultation} + showArchive={showArchived} + onFilesFound={() => setFilesFound(true)} + /> + ))}
diff --git a/src/Utils/useFileManager.tsx b/src/Utils/useFileManager.tsx index aa57e1918a8..133b4d2533d 100644 --- a/src/Utils/useFileManager.tsx +++ b/src/Utils/useFileManager.tsx @@ -18,7 +18,11 @@ export interface FileManagerOptions { export interface FileManagerResult { viewFile: (file: FileUploadModel, associating_id: string) => void; - archiveFile: (file: FileUploadModel, associating_id: string) => void; + archiveFile: ( + file: FileUploadModel, + associating_id: string, + skipPrompt?: { reason: string }, + ) => void; Dialogues: React.ReactNode; } @@ -97,7 +101,7 @@ export default function useFileManager( } }; - const handleFileArchive = async () => { + const handleFileArchive = async (archiveFile: typeof archiveDialogueOpen) => { if (!validateArchiveReason(archiveReason)) { setArchiving(false); return; @@ -106,9 +110,9 @@ export default function useFileManager( const { res } = await request(routes.editUpload, { body: { is_archived: true, archive_reason: archiveReason }, pathParams: { - id: archiveDialogueOpen?.id || "", + id: archiveFile?.id || "", fileType, - associatingId: archiveDialogueOpen?.associating_id || "", + associatingId: archiveFile?.associating_id || "", }, }); @@ -118,11 +122,25 @@ export default function useFileManager( setArchiveDialogueOpen(null); setArchiving(false); + setArchiveReason(""); onArchive && onArchive(); return res; }; - const archiveFile = (file: FileUploadModel, associating_id: string) => { + const archiveFile = ( + file: FileUploadModel, + associating_id: string, + skipPrompt?: { reason: string }, + ) => { + if (skipPrompt) { + setArchiving(true); + setArchiveReason(skipPrompt.reason); + handleFileArchive({ + ...file, + associating_id, + }); + return; + } setArchiveDialogueOpen({ ...file, associating_id }); }; @@ -174,7 +192,7 @@ export default function useFileManager(
{ event.preventDefault(); - handleFileArchive(); + handleFileArchive(archiveDialogueOpen); }} className="mx-2 my-4 flex w-full flex-col" > diff --git a/src/style/index.css b/src/style/index.css index fe2c9c8edb1..b05417bf912 100644 --- a/src/style/index.css +++ b/src/style/index.css @@ -429,6 +429,12 @@ button:disabled, } } +.skeleton-animate-alpha { + animation: skeletonShimmer 3s infinite linear; + background: linear-gradient(to right, rgba(0, 0, 0, 0.1) 10%, rgba(0, 0, 0, 0.05) 40%, rgba(0, 0, 0, 0.1) 70%); + background-size: 1000px 100%; +} + @media print { body * { From 3e12e19b1b10d529164dca809c2324a28c0c8780 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Tue, 28 May 2024 11:24:04 +0530 Subject: [PATCH 003/106] Adds watermark to Camera Feed and disables access to context menu. (#7885) * Adds watermark and disable context menu access in camera feed * Update src/Components/CameraFeed/CameraFeed.tsx --- src/Components/CameraFeed/CameraFeed.tsx | 6 ++- src/Components/CameraFeed/FeedWatermark.tsx | 55 +++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/Components/CameraFeed/FeedWatermark.tsx diff --git a/src/Components/CameraFeed/CameraFeed.tsx b/src/Components/CameraFeed/CameraFeed.tsx index f40aed384c8..81b526363b9 100644 --- a/src/Components/CameraFeed/CameraFeed.tsx +++ b/src/Components/CameraFeed/CameraFeed.tsx @@ -10,6 +10,7 @@ import FeedNetworkSignal from "./FeedNetworkSignal"; import NoFeedAvailable from "./NoFeedAvailable"; import FeedControls from "./FeedControls"; import Fullscreen from "../../CAREUI/misc/Fullscreen"; +import FeedWatermark from "./FeedWatermark"; import CareIcon from "../../CAREUI/icons/CareIcon"; interface Props { @@ -87,7 +88,6 @@ export default function CameraFeed(props: Props) { setState("loading"); initializeStream(); }; - return ( setFullscreen(false)}>
{/* Notifications */} + {player.status === "playing" && } {/* No Feed informations */} {state === "host_unreachable" && ( @@ -150,6 +151,7 @@ export default function CameraFeed(props: Props) { url={streamUrl} ref={playerRef.current as LegacyRef} controls={false} + pip={false} playsinline playing muted @@ -167,10 +169,12 @@ export default function CameraFeed(props: Props) {
) : (
From ce573b0e201400d09d5dd103bf80f496540df3b3 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Tue, 28 May 2024 11:27:00 +0530 Subject: [PATCH 008/106] fixes badges overlapping with IP days in patient consultation page (#7924) --- src/Components/Patient/PatientInfoCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Patient/PatientInfoCard.tsx b/src/Components/Patient/PatientInfoCard.tsx index a2e6dbaf01c..c378a5cc697 100644 --- a/src/Components/Patient/PatientInfoCard.tsx +++ b/src/Components/Patient/PatientInfoCard.tsx @@ -299,7 +299,7 @@ export default function PatientInfoCard(props: {
{consultation?.patient_no && ( From e42982eb875e3c4ecc3b4600c2017515a6dbac4d Mon Sep 17 00:00:00 2001 From: Sulochan Khadka <122200551+Sulochan-khadka@users.noreply.github.com> Date: Tue, 28 May 2024 11:29:59 +0530 Subject: [PATCH 009/106] Prescriptions: Default Discontinued Prescriptions Collapsed (#7833) * fixes: Prescriptions: Default Discontinued Prescriptions Collapsed #7767 * make necessary changes and fix issues * show medicine name when collapsed * fix key used for list item --------- Co-authored-by: rithviknishad --- .../Medicine/PrescriptionBuilder.tsx | 5 +- .../Medicine/PrescriptionDetailCard.tsx | 252 ++++++++++-------- 2 files changed, 144 insertions(+), 113 deletions(-) diff --git a/src/Components/Medicine/PrescriptionBuilder.tsx b/src/Components/Medicine/PrescriptionBuilder.tsx index 972a74159d2..4fe5bed9e03 100644 --- a/src/Components/Medicine/PrescriptionBuilder.tsx +++ b/src/Components/Medicine/PrescriptionBuilder.tsx @@ -66,10 +66,11 @@ export default function PrescriptionBuilder({ /> )}
- {data?.results.map((obj, index) => ( + {data?.results.map((obj) => ( setShowDiscontinueFor(obj)} onAdministerClick={() => setShowAdministerFor(obj)} readonly={disabled} diff --git a/src/Components/Medicine/PrescriptionDetailCard.tsx b/src/Components/Medicine/PrescriptionDetailCard.tsx index 630ab324662..e70acc9b87a 100644 --- a/src/Components/Medicine/PrescriptionDetailCard.tsx +++ b/src/Components/Medicine/PrescriptionDetailCard.tsx @@ -5,20 +5,29 @@ import ReadMore from "../Common/components/Readmore"; import ButtonV2 from "../Common/components/ButtonV2"; import { useTranslation } from "react-i18next"; import RecordMeta from "../../CAREUI/display/RecordMeta"; +import { useState } from "react"; import { AuthorizedForConsultationRelatedActions } from "../../CAREUI/misc/AuthorizedChild"; -export default function PrescriptionDetailCard({ - prescription, - ...props -}: { +interface Props { prescription: Prescription; readonly?: boolean; children?: React.ReactNode; onDiscontinueClick?: () => void; onAdministerClick?: () => void; selected?: boolean; -}) { + collapsible?: boolean; +} + +export default function PrescriptionDetailCard({ + prescription, + collapsible = false, + ...props +}: Props) { const { t } = useTranslation(); + const [isCollapsed, setIsCollapsed] = useState( + collapsible && prescription.discontinued, + ); + return (
-
+
{ + if (collapsible) { + setIsCollapsed(!isCollapsed); + } + }} + >
@@ -39,14 +56,21 @@ export default function PrescriptionDetailCard({ props.selected ? "text-black" : "text-gray-700", )} > - {prescription.prescription_type === "DISCHARGE" && - `${t("discharge")} `} - {t( - prescription.dosage_type === "PRN" - ? "prn_prescription" - : "prescription", + {isCollapsed ? ( + prescription.medicine_object?.name ?? + prescription.medicine_old + ) : ( + <> + {prescription.prescription_type === "DISCHARGE" && + `${t("discharge")} `} + {t( + prescription.dosage_type === "PRN" + ? "prn_prescription" + : "prescription", + )} + {` #${prescription.id?.slice(-5)}`} + )} - {` #${prescription.id?.slice(-5)}`} {prescription.discontinued && ( @@ -62,7 +86,10 @@ export default function PrescriptionDetailCard({ { + e.stopPropagation(); + props.onAdministerClick?.(); + }} type="button" size="small" variant="secondary" @@ -79,7 +106,10 @@ export default function PrescriptionDetailCard({ variant="danger" ghost border - onClick={props.onDiscontinueClick} + onClick={(e) => { + e.stopPropagation(); + props.onDiscontinueClick?.(); + }} > {t("discontinue")} @@ -89,114 +119,114 @@ export default function PrescriptionDetailCard({ )}
- -
- - {prescription.medicine_object?.name ?? prescription.medicine_old} - - - {prescription.route && - t("PRESCRIPTION_ROUTE_" + prescription.route)} - - {prescription.dosage_type === "TITRATED" ? ( - <> - - {prescription.base_dosage} - - - {prescription.target_dosage} - - - ) : ( + {!isCollapsed && ( +
- {prescription.base_dosage} + {prescription.medicine_object?.name ?? prescription.medicine_old} - )} - - {prescription.dosage_type === "PRN" ? ( - <> + + {prescription.route && + t("PRESCRIPTION_ROUTE_" + prescription.route)} + + {prescription.dosage_type === "TITRATED" ? ( + <> + + {prescription.base_dosage} + + + {prescription.target_dosage} + + + ) : ( - {prescription.indicator} + {prescription.base_dosage} + )} + + {prescription.dosage_type === "PRN" ? ( + <> + + {prescription.indicator} + + + {prescription.max_dosage} + + + {prescription.min_hours_between_doses && + prescription.min_hours_between_doses + " hrs."} + + + ) : ( + <> + + {prescription.frequency && + t( + "PRESCRIPTION_FREQUENCY_" + + prescription.frequency.toUpperCase(), + )} + + + {prescription.days} + + + )} + + {prescription.instruction_on_titration && ( - {prescription.max_dosage} + + + )} + + {prescription.notes && ( + + + )} + + {prescription.discontinued && ( - {prescription.min_hours_between_doses && - prescription.min_hours_between_doses + " hrs."} + {prescription.discontinued_reason} - - ) : ( - <> - - {prescription.frequency && - t( - "PRESCRIPTION_FREQUENCY_" + - prescription.frequency.toUpperCase(), - )} - - - {prescription.days} - - - )} - - {prescription.instruction_on_titration && ( - - - - )} - - {prescription.notes && ( - - - - )} - - {prescription.discontinued && ( - - {prescription.discontinued_reason} - - )} -
- + )} +
+ )}
Prescribed From 71fb32ed3b15243adde307d7cbc9153cd3c88550 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Tue, 28 May 2024 11:31:03 +0530 Subject: [PATCH 010/106] Removes "Bed Status" for Domiciliary Care encounters (#7889) --- src/Components/Facility/ConsultationForm.tsx | 9 +++++++-- src/Components/Patient/PatientInfoCard.tsx | 16 +++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Components/Facility/ConsultationForm.tsx b/src/Components/Facility/ConsultationForm.tsx index 487326e5bdd..b7b99d85448 100644 --- a/src/Components/Facility/ConsultationForm.tsx +++ b/src/Components/Facility/ConsultationForm.tsx @@ -915,6 +915,11 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { if (!isUpdate && ["Bed Status"].includes(sectionTitle)) { return null; } + + if (isUpdate && sectionTitle === "Bed Status") { + return null; + } + const isCurrent = currentSection === sectionTitle; const section = sections[sectionTitle as ConsultationFormSection]; return ( @@ -1265,7 +1270,7 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => {
)} - {["A", "DC"].includes(state.form.suggestion) && !isUpdate && ( + {state.form.suggestion === "A" && !isUpdate && (
Bed {
- {isUpdate && ( + {state.form.suggestion === "A" && isUpdate && ( <>
{sectionTitle("Bed Status")} diff --git a/src/Components/Patient/PatientInfoCard.tsx b/src/Components/Patient/PatientInfoCard.tsx index c378a5cc697..ba9280e903d 100644 --- a/src/Components/Patient/PatientInfoCard.tsx +++ b/src/Components/Patient/PatientInfoCard.tsx @@ -234,13 +234,15 @@ export default function PatientInfoCard(props: { {category.toUpperCase()}
)} - setOpen(true)} - className="mt-1 px-[10px] py-1" - > - {bedDialogTitle} - + {consultation?.admitted && ( + setOpen(true)} + className="mt-1 px-[10px] py-1" + > + {bedDialogTitle} + + )}
Date: Tue, 28 May 2024 16:11:15 +0530 Subject: [PATCH 011/106] fixes overflow issue in live monitoring (#7928) --- src/Components/CameraFeed/AssetBedSelect.tsx | 19 ++++++++++++------- .../CameraFeed/CameraFeedWithBedPresets.tsx | 4 ++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/Components/CameraFeed/AssetBedSelect.tsx b/src/Components/CameraFeed/AssetBedSelect.tsx index 0bb40dffb98..715c326c35d 100644 --- a/src/Components/CameraFeed/AssetBedSelect.tsx +++ b/src/Components/CameraFeed/AssetBedSelect.tsx @@ -16,6 +16,7 @@ export default function CameraPresetSelect(props: Props) { return ( <>
+ {/* Desktop View */} {props.options .slice(0, props.options.length > 5 ? 4 : 5) .map((option) => ( @@ -31,20 +32,19 @@ export default function CameraPresetSelect(props: Props) { {label(option)} ))} - {/* Desktop View */} {props.options.length > 5 && ( - + )}
{/* Mobile View */} - +
); } -const ShowMoreDropdown = (props: Props) => { +export const CameraPresetDropdown = (props: Props) => { const selected = props.value; const options = props.options.filter(({ meta }) => meta.type !== "boundary"); @@ -54,9 +54,14 @@ const ShowMoreDropdown = (props: Props) => { return (
- - - {selected ? label(selected) : "No Preset"} + + + {selected ? label(selected) : "Select preset"} diff --git a/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx b/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx index 4c205c0e9c6..e3fc2ab2129 100644 --- a/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx +++ b/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx @@ -1,10 +1,10 @@ import { useState } from "react"; import { AssetBedModel, AssetData } from "../Assets/AssetTypes"; import CameraFeed from "./CameraFeed"; -import AssetBedSelect from "./AssetBedSelect"; import useQuery from "../../Utils/request/useQuery"; import routes from "../../Redux/api"; import useSlug from "../../Common/hooks/useSlug"; +import { CameraPresetDropdown } from "./AssetBedSelect"; interface Props { asset: AssetData; @@ -29,7 +29,7 @@ export default function LocationFeedTile(props: Props) { {loading ? ( loading presets... ) : ( - Date: Tue, 28 May 2024 16:36:39 +0530 Subject: [PATCH 012/106] Adds support specifying and filtering by ration card category for patients (#7926) * Adds support specifying and filtering by ration card category for patients * update ui --- src/Common/constants.tsx | 2 ++ src/Components/Patient/ManagePatients.tsx | 8 +++++++ src/Components/Patient/PatientFilter.tsx | 22 ++++++++++++++++++ src/Components/Patient/PatientHome.tsx | 27 +++++++++++----------- src/Components/Patient/PatientRegister.tsx | 13 +++++++++++ src/Components/Patient/models.tsx | 2 ++ src/Locale/en/Common.json | 5 +++- 7 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index 5fbff1b48cb..99cb3a7988f 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -1404,3 +1404,5 @@ export const PATIENT_NOTES_THREADS = { Doctors: 10, Nurses: 20, } as const; + +export const RATION_CARD_CATEGORY = ["BPL", "APL", "NO_CARD"] as const; diff --git a/src/Components/Patient/ManagePatients.tsx b/src/Components/Patient/ManagePatients.tsx index ac716f7bf45..a99d12c09fc 100644 --- a/src/Components/Patient/ManagePatients.tsx +++ b/src/Components/Patient/ManagePatients.tsx @@ -181,6 +181,7 @@ export const PatientManager = () => { qParams.date_declared_positive_before || undefined, date_declared_positive_after: qParams.date_declared_positive_after || undefined, + ration_card_category: qParams.ration_card_category || undefined, last_consultation_medico_legal_case: qParams.last_consultation_medico_legal_case || undefined, last_consultation_encounter_date_before: @@ -960,6 +961,13 @@ export const PatientManager = () => { "Is Medico-Legal Case", "last_consultation_medico_legal_case", ), + value( + "Ration Card Category", + "ration_card_category", + qParams.ration_card_category + ? t(`ration_card__${qParams.ration_card_category}`) + : "", + ), value( "Facility", "facility", diff --git a/src/Components/Patient/PatientFilter.tsx b/src/Components/Patient/PatientFilter.tsx index 0d8ebaae2b0..f153f9aa93d 100644 --- a/src/Components/Patient/PatientFilter.tsx +++ b/src/Components/Patient/PatientFilter.tsx @@ -7,6 +7,7 @@ import { FACILITY_TYPES, GENDER_TYPES, PATIENT_FILTER_CATEGORIES, + RATION_CARD_CATEGORY, } from "../../Common/constants"; import useConfig from "../../Common/hooks/useConfig"; import useMergeState from "../../Common/hooks/useMergeState"; @@ -31,11 +32,14 @@ import useQuery from "../../Utils/request/useQuery"; import routes from "../../Redux/api"; import request from "../../Utils/request/request"; import useAuthUser from "../../Common/hooks/useAuthUser"; +import { SelectFormField } from "../Form/FormFields/SelectFormField"; +import { useTranslation } from "react-i18next"; const getDate = (value: any) => value && dayjs(value).isValid() && dayjs(value).toDate(); export default function PatientFilter(props: any) { + const { t } = useTranslation(); const authUser = useAuthUser(); const { kasp_enabled, kasp_string } = useConfig(); const { filter, onChange, closeFilter, removeFilters } = props; @@ -59,6 +63,7 @@ export default function PatientFilter(props: any) { age_min: filter.age_min || null, age_max: filter.age_max || null, date_declared_positive: filter.date_declared_positive || null, + ration_card_category: filter.ration_card_category || null, last_consultation_medico_legal_case: filter.last_consultation_medico_legal_case || null, last_consultation_encounter_date_before: @@ -171,6 +176,7 @@ export default function PatientFilter(props: any) { gender, age_min, age_max, + ration_card_category, last_consultation_medico_legal_case, last_consultation_encounter_date_before, last_consultation_encounter_date_after, @@ -214,6 +220,7 @@ export default function PatientFilter(props: any) { created_date_after: dateQueryString(created_date_after), modified_date_before: dateQueryString(modified_date_before), modified_date_after: dateQueryString(modified_date_after), + ration_card_category, last_consultation_medico_legal_case: last_consultation_medico_legal_case || "", last_consultation_encounter_date_before: dateQueryString( @@ -467,6 +474,21 @@ export default function PatientFilter(props: any) { } />
+ t(`ration_card__${o}`)} + optionValue={(o) => o} + value={filterState.ration_card_category} + onChange={(e) => + setFilterState({ + ...filterState, + [e.name]: e.value, + }) + } + />
{ {patientData.facility_object?.name || "-"}

- {patientGender} | {patientData.blood_group || "-"} + {patientGender} | {patientData.blood_group || "-"} | Born on{" "} + {patientData.date_of_birth + ? formatDate(patientData.date_of_birth) + : patientData.year_of_birth}

-
-
- {patientData.date_of_birth - ? "Date of Birth" - : "Year of Birth"} -
-
- {patientData.date_of_birth - ? formatDate(patientData.date_of_birth) - : patientData.year_of_birth} -
-
Phone @@ -537,6 +528,16 @@ export const PatientHome = (props: any) => { {parseOccupation(patientData.meta_info?.occupation) || "-"}
+
+
+ Ration Card Category +
+
+ {patientData.ration_card_category + ? t(`ration_card__${patientData.ration_card_category}`) + : "-"} +
+
diff --git a/src/Components/Patient/PatientRegister.tsx b/src/Components/Patient/PatientRegister.tsx index 07e682c137f..34d2ac66387 100644 --- a/src/Components/Patient/PatientRegister.tsx +++ b/src/Components/Patient/PatientRegister.tsx @@ -5,6 +5,7 @@ import { GENDER_TYPES, MEDICAL_HISTORY_CHOICES, OCCUPATION_TYPES, + RATION_CARD_CATEGORY, VACCINES, } from "../../Common/constants"; import { @@ -65,6 +66,7 @@ import SelectMenuV2 from "../Form/SelectMenuV2.js"; import Checkbox from "../Common/components/CheckBox.js"; import _ from "lodash"; import { ILocalBodies } from "../ExternalResult/models.js"; +import { useTranslation } from "react-i18next"; const Loading = lazy(() => import("../Common/Loading")); const PageTitle = lazy(() => import("../Common/PageTitle")); @@ -130,6 +132,7 @@ const initForm: any = { last_vaccinated_date: null, abha_number: null, ...medicalHistoryChoices, + ration_card_category: null, }; const initError = Object.assign( @@ -169,6 +172,7 @@ export const parseOccupationFromExt = (occupation: Occupation) => { export const PatientRegister = (props: PatientRegisterProps) => { const authUser = useAuthUser(); + const { t } = useTranslation(); const { goBack } = useAppHistory(); const { gov_data_api_key, enable_hcx, enable_abdm } = useConfig(); const { facilityId, id } = props; @@ -750,6 +754,7 @@ export const PatientRegister = (props: PatientRegisterProps) => { blood_group: formData.blood_group ? formData.blood_group : undefined, medical_history, is_active: true, + ration_card_category: formData.ration_card_category, }; const { res, data: requestData } = id ? await request(routes.updatePatient, { @@ -1702,6 +1707,14 @@ export const PatientRegister = (props: PatientRegisterProps) => { optionLabel={(o) => o.text} optionValue={(o) => o.id} /> + t(`ration_card__${o}`)} + optionValue={(o) => o} + /> ) : (
diff --git a/src/Components/Patient/models.tsx b/src/Components/Patient/models.tsx index 6d6e0b3c979..1a79411ebd5 100644 --- a/src/Components/Patient/models.tsx +++ b/src/Components/Patient/models.tsx @@ -3,6 +3,7 @@ import { PerformedByModel } from "../HCX/misc"; import { CONSCIOUSNESS_LEVEL, OCCUPATION_TYPES, + RATION_CARD_CATEGORY, RHYTHM_CHOICES, } from "../../Common/constants"; @@ -101,6 +102,7 @@ export interface PatientModel { state?: number; nationality?: string; passport_no?: string; + ration_card_category?: (typeof RATION_CARD_CATEGORY)[number] | null; date_of_test?: string; date_of_result?: string; // keeping this to avoid errors in Death report covin_id?: string; diff --git a/src/Locale/en/Common.json b/src/Locale/en/Common.json index e2556587e96..019a51232ba 100644 --- a/src/Locale/en/Common.json +++ b/src/Locale/en/Common.json @@ -163,5 +163,8 @@ "clear_all_filters": "Clear All Filters", "summary": "Summary", "report": "Report", - "treating_doctor":"Treating Doctor" + "treating_doctor": "Treating Doctor", + "ration_card__NO_CARD": "Non-card holder", + "ration_card__BPL": "BPL", + "ration_card__APL": "APL" } \ No newline at end of file From 53164025619ecc7fcbbac6b928f8344900b6d199 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Tue, 28 May 2024 16:36:57 +0530 Subject: [PATCH 013/106] Sort discontinued medicines to the last (#7925) * Sort discontinued medicines to the last * Update src/Components/Medicine/PrescriptionBuilder.tsx * Update src/Components/Medicine/PrescriptionBuilder.tsx --- .../Medicine/PrescriptionBuilder.tsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Components/Medicine/PrescriptionBuilder.tsx b/src/Components/Medicine/PrescriptionBuilder.tsx index 4fe5bed9e03..7833245135c 100644 --- a/src/Components/Medicine/PrescriptionBuilder.tsx +++ b/src/Components/Medicine/PrescriptionBuilder.tsx @@ -12,6 +12,7 @@ import useQuery from "../../Utils/request/useQuery"; import MedicineRoutes from "./routes"; import useSlug from "../../Common/hooks/useSlug"; import { AuthorizedForConsultationRelatedActions } from "../../CAREUI/misc/AuthorizedChild"; +import { compareBy } from "../../Utils/utils"; interface Props { prescription_type?: Prescription["prescription_type"]; @@ -66,16 +67,18 @@ export default function PrescriptionBuilder({ /> )}
- {data?.results.map((obj) => ( - setShowDiscontinueFor(obj)} - onAdministerClick={() => setShowAdministerFor(obj)} - readonly={disabled} - /> - ))} + {data?.results + .sort(compareBy("discontinued")) + ?.map((obj) => ( + setShowDiscontinueFor(obj)} + onAdministerClick={() => setShowAdministerFor(obj)} + readonly={disabled} + /> + ))}
Date: Tue, 28 May 2024 16:40:37 +0530 Subject: [PATCH 014/106] Add Medicine Prescription Log Summary Section (#7584) * Add Medicine Summary Section * add timeline view and show only changes * switch to modal and use timeline view * fix width of modal * show clean message * refactor files * dont show mg twice and show discontinued * show prescription type * add disconitnue and no medicine card * show discontinued in the summary * update changes --- .../ConsultationMedicinesTab.tsx | 2 + .../Medicine/MedicinePrescriptionSummary.tsx | 372 ++++++++++++++++++ src/Locale/en/Medicine.json | 1 + 3 files changed, 375 insertions(+) create mode 100644 src/Components/Medicine/MedicinePrescriptionSummary.tsx diff --git a/src/Components/Facility/ConsultationDetails/ConsultationMedicinesTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationMedicinesTab.tsx index e1e72c2f936..27af4bb6480 100644 --- a/src/Components/Facility/ConsultationDetails/ConsultationMedicinesTab.tsx +++ b/src/Components/Facility/ConsultationDetails/ConsultationMedicinesTab.tsx @@ -1,6 +1,7 @@ import { ConsultationTabProps } from "./index"; import PageTitle from "../../Common/PageHeadTitle"; import MedicineAdministrationSheet from "../../Medicine/MedicineAdministrationSheet"; +import { MedicinePrescriptionSummary } from "../../Medicine/MedicinePrescriptionSummary"; export const ConsultationMedicinesTab = (props: ConsultationTabProps) => { return ( @@ -15,6 +16,7 @@ export const ConsultationMedicinesTab = (props: ConsultationTabProps) => { is_prn={true} readonly={!!props.consultationData.discharge_date} /> +
); }; diff --git a/src/Components/Medicine/MedicinePrescriptionSummary.tsx b/src/Components/Medicine/MedicinePrescriptionSummary.tsx new file mode 100644 index 00000000000..b77acc090c4 --- /dev/null +++ b/src/Components/Medicine/MedicinePrescriptionSummary.tsx @@ -0,0 +1,372 @@ +import MedicineRoutes from "../Medicine/routes"; +import useQuery from "../../Utils/request/useQuery"; +import DialogModal from "../Common/Dialog"; +import { useState } from "react"; +import { lazy } from "react"; +import Timeline, { TimelineNode } from "../../CAREUI/display/Timeline"; +import { MedibaseMedicine, Prescription } from "../Medicine/models"; +import { useTranslation } from "react-i18next"; + +const Loading = lazy(() => import("../Common/Loading")); + +interface MedicinePrescriptionSummaryProps { + consultation: string; +} + +export const MedicinePrescriptionSummary = ({ + consultation, +}: MedicinePrescriptionSummaryProps) => { + const { t } = useTranslation(); + const [showMedicineModal, setShowMedicineModal] = useState({ + open: false, + name: "", + medicineId: "", + }); + const { data } = useQuery(MedicineRoutes.listPrescriptions, { + pathParams: { consultation }, + query: { limit: 100 }, + }); + + const closeMedicineModal = () => { + setShowMedicineModal({ ...showMedicineModal, open: false }); + }; + + function extractUniqueMedicineObjects( + prescriptions: Prescription[], + ): MedibaseMedicine[] { + const uniqueMedicineObjects: Set = new Set(); + const uniqueMedicines: MedibaseMedicine[] = []; + + prescriptions.forEach((prescription: Prescription) => { + if (prescription?.medicine_object) { + const medicineId = prescription?.medicine_object.id; + + if (!uniqueMedicineObjects.has(medicineId)) { + uniqueMedicineObjects.add(medicineId); + uniqueMedicines.push(prescription?.medicine_object); + } + } + }); + + return uniqueMedicines; + } + + const medicinesList: MedibaseMedicine[] = extractUniqueMedicineObjects( + data?.results ?? [], + ); + + return ( +
+

{t("summary")}

+
+ {medicinesList && medicinesList.length > 0 ? ( + medicinesList?.map((med: MedibaseMedicine) => ( +
+
{med.name}
+ +
+ )) + ) : ( +
+
+

{"No Medicine Summary"}

+
+
+ )} +
+ + + {showMedicineModal.name}: {t("prescription_logs")} +

+ } + show={showMedicineModal.open} + onClose={closeMedicineModal} + fixedWidth={false} + className="md:w-3/4" + > + +
+
+ ); +}; + +interface ConsultationMedicineLogsProps { + consultationId: string; + medicineId: string; +} + +export default function ConsultationMedicineLogs({ + consultationId, + medicineId, +}: ConsultationMedicineLogsProps) { + const { data, loading } = useQuery(MedicineRoutes.listPrescriptions, { + pathParams: { consultation: consultationId }, + query: { + medicine: medicineId, + }, + }); + + if (loading) { + return ; + } + + const getDetailsMessage = (prescription: Prescription) => { + const message = `Details: ${ + prescription.base_dosage != null + ? `${prescription.dosage_type === "TITRATED" ? "Start Dosage" : "Dosage"}: ${prescription.base_dosage}, ` + : "" + }${prescription.route != null ? `Route: ${prescription.route}, ` : ""}${ + prescription.target_dosage != null + ? `Target Dosage: ${prescription.target_dosage}, ` + : "" + }${ + prescription.instruction_on_titration != null + ? `Instruction on Titration: ${prescription.instruction_on_titration}, ` + : "" + }${ + prescription.frequency != null + ? `Frequency: ${prescription.frequency}, ` + : "" + }${prescription.days != null ? `Days: ${prescription.days}, ` : ""}${ + prescription.indicator != null + ? `Indicator: ${prescription.indicator}, ` + : "" + }${ + prescription.max_dosage != null + ? `Max Dosage: ${prescription.max_dosage}, ` + : "" + }${ + prescription.min_hours_between_doses != null + ? `Min Hours Between Doses: ${prescription.min_hours_between_doses}, ` + : "" + }${prescription.discontinued ? "Discontinued: Yes, " : ""}${ + prescription.dosage_type + ? `Prescription Type: ${prescription.dosage_type}, ` + : "" + }`.replace(/, $/, ""); + + return message; + }; + + const calculateChanges = (prescriptions: Prescription[]) => { + prescriptions = prescriptions.reverse(); + const changes = []; + + const message = getDetailsMessage(prescriptions[0]); + + changes.push({ + prescriptionId: prescriptions[0].id, + changeMessage: message, + prescribed_by: prescriptions[0].prescribed_by, + created_date: prescriptions[0].created_date, + }); + + if (prescriptions[0].discontinued) { + changes.push({ + prescriptionId: prescriptions[0].id, + changeMessage: "This prescription has been discontinued", + prescribed_by: prescriptions[0].prescribed_by, + created_date: prescriptions[0].discontinued_date, + }); + } + + for (let i = 1; i < prescriptions.length; i++) { + const prevPrescription = prescriptions[i - 1]; + const currentPrescription = prescriptions[i]; + + const changesForPrescription: string[] = []; + + // Check for changes in base dosage + if (prevPrescription.base_dosage !== currentPrescription.base_dosage) { + changesForPrescription.push( + `Base dosage changed to ${currentPrescription.base_dosage} from ${prevPrescription.base_dosage}`, + ); + } + + // Check for changes in route + if (prevPrescription.route !== currentPrescription.route) { + changesForPrescription.push( + `Route changed to ${ + currentPrescription.route ?? "Not specified" + } from ${prevPrescription.route ?? "Not specified"}`, + ); + } + + // Check for changes in dosage type + if (prevPrescription.dosage_type !== currentPrescription.dosage_type) { + changesForPrescription.push( + `Dosage type changed to ${ + currentPrescription.dosage_type ?? "Not specified" + } from ${prevPrescription.dosage_type ?? "Not specified"}`, + ); + } + + // Check for changes in target dosage + if ( + prevPrescription.target_dosage !== currentPrescription.target_dosage + ) { + changesForPrescription.push( + `Target dosage changed to ${ + currentPrescription.target_dosage ?? "Not specified" + } from ${prevPrescription.target_dosage ?? "Not specified"}`, + ); + } + + // Check for changes in instruction on titration + if ( + prevPrescription.instruction_on_titration !== + currentPrescription.instruction_on_titration + ) { + changesForPrescription.push( + `Instruction on titration changed to ${ + currentPrescription.instruction_on_titration ?? "Not specified" + } from ${ + prevPrescription.instruction_on_titration ?? "Not specified" + }`, + ); + } + + // Check for changes in frequency + if (prevPrescription.frequency !== currentPrescription.frequency) { + changesForPrescription.push( + `Frequency changed to ${ + currentPrescription.frequency ?? "Not specified" + } from ${prevPrescription.frequency ?? "Not specified"}`, + ); + } + + // Check for changes in days + if (prevPrescription.days !== currentPrescription.days) { + changesForPrescription.push( + `Days changed to ${ + currentPrescription.days ?? "Not specified" + } from ${prevPrescription.days ?? "Not specified"}`, + ); + } + + // Check for changes in indicator + if (prevPrescription.indicator !== currentPrescription.indicator) { + changesForPrescription.push( + `Indicator changed to ${ + currentPrescription.indicator ?? "Not specified" + } from ${prevPrescription.indicator ?? "Not specified"}`, + ); + } + + // Check for changes in max dosage + if (prevPrescription.max_dosage !== currentPrescription.max_dosage) { + changesForPrescription.push( + `Max dosage changed to ${ + currentPrescription.max_dosage ?? "Not specified" + } from ${prevPrescription.max_dosage ?? "Not specified"}`, + ); + } + + // Check for changes in min hours between doses + if ( + prevPrescription.min_hours_between_doses !== + currentPrescription.min_hours_between_doses + ) { + changesForPrescription.push( + `Min hours between doses changed to ${ + currentPrescription.min_hours_between_doses ?? "Not specified" + } from ${prevPrescription.min_hours_between_doses ?? "Not specified"}`, + ); + } + + // Check if discontinued + if (currentPrescription.discontinued && !prevPrescription.discontinued) { + changesForPrescription.push("Prescription was discontinued"); + } + + // Check if prescription type is changed + if ( + prevPrescription.prescription_type !== + currentPrescription.prescription_type + ) { + changesForPrescription.push( + `Prescription Type changed from ${prevPrescription.prescription_type} to ${currentPrescription.prescription_type}`, + ); + } + + // If there are changes, add them to the changes array + if (changesForPrescription.length > 0 && !prevPrescription.discontinued) { + const message = `Changes: ${changesForPrescription.join(", ")}`; + changes.push({ + prescriptionId: currentPrescription.id, + changeMessage: message, + prescribed_by: currentPrescription.prescribed_by, + created_date: currentPrescription.created_date, + }); + } else { + // If no changes, just list out the details of the prescription + const message = getDetailsMessage(currentPrescription); + + changes.push({ + prescriptionId: currentPrescription.id, + changeMessage: message, + prescribed_by: currentPrescription.prescribed_by, + created_date: currentPrescription.created_date, + }); + } + + if (currentPrescription.discontinued) { + changes.push({ + prescriptionId: currentPrescription.id, + changeMessage: "This prescription has been discontinued", + prescribed_by: currentPrescription.prescribed_by, + created_date: currentPrescription.discontinued_date, + }); + } + } + + return changes.reverse(); + }; + + return ( +
+ + {data?.results && + (() => { + const changesArray = calculateChanges(data?.results); + return changesArray.map((changes, index) => ( + +

{changes?.changeMessage}

+
+ )); + })()} +
+
+ ); +} diff --git a/src/Locale/en/Medicine.json b/src/Locale/en/Medicine.json index 36adc259514..4a5a5785047 100644 --- a/src/Locale/en/Medicine.json +++ b/src/Locale/en/Medicine.json @@ -36,6 +36,7 @@ "prescription_discontinued": "Prescription discontinued", "administration_notes": "Administration Notes", "last_administered": "Last administered", + "prescription_logs": "Prescription Logs", "modification_caution_note": "No modifications possible once added", "discontinue_caution_note": "Are you sure you want to discontinue this prescription?", "edit_caution_note": "A new prescription will be added to the consultation with the edited details and the current prescription will be discontinued.", From f2384750451e2f3fc8195cfa4d3204ce86621ceb Mon Sep 17 00:00:00 2001 From: Manasvi Gaur <120303997+manasvi-gaur@users.noreply.github.com> Date: Tue, 28 May 2024 16:43:03 +0530 Subject: [PATCH 015/106] [Enhancement] Ventilator Parameters Input - Validate Consultation Bed for Linked Ventilator Asset is resolved (#7522) * Ventilator Parameters Input - Validate Consultation Bed for Linked Ventilator Asset is getting resolved * done * requested changes done * lint error solved * clarifying solution * solved that one condition that failed --- src/Components/Common/DialogModal.res | 20 +++++ .../CriticalCare__API.tsx | 17 +++++ .../Recording/CriticalCare__Recording.res | 2 + ...iticalCare__VentilatorParametersEditor.res | 76 +++++++++++++++---- .../Facility/Consultations/Beds.res | 18 +++++ 5 files changed, 119 insertions(+), 14 deletions(-) create mode 100644 src/Components/Common/DialogModal.res create mode 100644 src/Components/Facility/Consultations/Beds.res diff --git a/src/Components/Common/DialogModal.res b/src/Components/Common/DialogModal.res new file mode 100644 index 00000000000..ae03ad8139c --- /dev/null +++ b/src/Components/Common/DialogModal.res @@ -0,0 +1,20 @@ +type reactClass +module DialogModal = { + @module("./Dialog.tsx") @react.component + external make: ( + ~title: React.element, + ~show: bool, + ~onClose: unit => unit, + ~className: string, + ~children: React.element, + ) => React.element = "default" +} + +@react.component +let make = ( + ~title: React.element, + ~show: bool, + ~onClose: unit => unit, + ~className: string, + ~children: React.element, +) => {children} diff --git a/src/Components/CriticalCareRecording/CriticalCare__API.tsx b/src/Components/CriticalCareRecording/CriticalCare__API.tsx index 108acfda05e..7bddbf9eaee 100644 --- a/src/Components/CriticalCareRecording/CriticalCare__API.tsx +++ b/src/Components/CriticalCareRecording/CriticalCare__API.tsx @@ -1,4 +1,6 @@ import { fireRequestV2 } from "../../Redux/fireRequest"; +import routes from "../../Redux/api"; +import request from "../../Utils/request/request"; export const loadDailyRound = ( consultationId: string, @@ -24,3 +26,18 @@ export const updateDailyRound = ( id, }); }; + +export const getAsset = ( + consultationId: string, + setAsset: React.Dispatch>, +) => { + request(routes.listConsultationBeds, { + query: { consultation: consultationId, limit: 1 }, + }).then(({ data }) => { + // here its fetching the ventilator type assets + const assets = data?.results[0].assets_objects?.filter( + (asset) => asset.asset_class == "VENTILATOR", + ); + setAsset(assets?.length || 0); + }); +}; diff --git a/src/Components/CriticalCareRecording/Recording/CriticalCare__Recording.res b/src/Components/CriticalCareRecording/Recording/CriticalCare__Recording.res index 2903ab3e406..e8bfb91e21c 100644 --- a/src/Components/CriticalCareRecording/Recording/CriticalCare__Recording.res +++ b/src/Components/CriticalCareRecording/Recording/CriticalCare__Recording.res @@ -164,6 +164,8 @@ let make = (~id, ~facilityId, ~patientId, ~consultationId, ~dailyRound) => { updateCB={updateDailyRound(send, VentilatorParametersEditor)} id consultationId + patientId + facilityId /> | ArterialBloodGasAnalysisEditor => unit, _ => unit) => unit = "updateDailyRound" +@module("../CriticalCare__API") +external getAsset: (string, (int => int) => unit) => option unit> = "getAsset" + open VentilatorParameters let string_of_int = data => Belt.Option.mapWithDefault(data, "", Js.Int.toString) @@ -14,19 +17,19 @@ let reducer = (state: VentilatorParameters.state, action: VentilatorParameters.a switch action { | SetBilateralAirEntry(bilateral_air_entry) => { ...state, - bilateral_air_entry: bilateral_air_entry, + bilateral_air_entry, } | SetETCO2(etco2) => { ...state, - etco2: etco2, + etco2, } | SetVentilatorInterface(ventilator_interface) => { ...state, - ventilator_interface: ventilator_interface, + ventilator_interface, } | SetVentilatorMode(ventilator_mode) => { ...state, - ventilator_mode: ventilator_mode, + ventilator_mode, } | SetOxygenModality(oxygen_modality) => { @@ -59,7 +62,7 @@ let reducer = (state: VentilatorParameters.state, action: VentilatorParameters.a } | SetOxygenModalityOxygenRate(ventilator_oxygen_modality_oxygen_rate) => { ...state, - ventilator_oxygen_modality_oxygen_rate: ventilator_oxygen_modality_oxygen_rate, + ventilator_oxygen_modality_oxygen_rate, } | SetOxygenModalityFlowRate(oxygen_modality_flow_rate) => { ...state, @@ -204,8 +207,22 @@ let initialState: VentilatorParameters.t => VentilatorParameters.state = ventila } @react.component -let make = (~ventilatorParameters: VentilatorParameters.t, ~id, ~consultationId, ~updateCB) => { +let make = ( + ~ventilatorParameters: VentilatorParameters.t, + ~id, + ~consultationId, + ~updateCB, + ~facilityId, + ~patientId, +) => { let (state, send) = React.useReducer(reducer, initialState(ventilatorParameters)) + let (isOpen, setIsOpen) = React.useState(() => false) + let toggleOpen = () => setIsOpen(prevState => !prevState) + let (asset, setAsset) = React.useState(() => 0) + + React.useEffect1(() => { + getAsset(consultationId, setAsset) + }, [isOpen]) let editor = switch state.ventilator_interface { | INVASIVE => @@ -216,7 +233,7 @@ let make = (~ventilatorParameters: VentilatorParameters.t, ~id, ~consultationId,
-
+
{str("Bilateral Air Entry")}
@@ -225,19 +242,18 @@ let make = (~ventilatorParameters: VentilatorParameters.t, ~id, ~consultationId, id="bilateral-air-entry-yes" label="Yes" checked={switch state.bilateral_air_entry { - | Some(bae) => bae - | None => false + | Some(bae) => bae + | None => false }} onChange={_ => send(SetBilateralAirEntry(Some(true)))} /> - !bae - | None => false + | Some(bae) => !bae + | None => false }} onChange={_ => send(SetBilateralAirEntry(Some(false)))} /> @@ -255,7 +271,6 @@ let make = (~ventilatorParameters: VentilatorParameters.t, ~id, ~consultationId, hasError={ValidationUtils.isInputInRangeInt(0, 200, state.etco2)} />
-

{str("Respiratory Support")}

@@ -282,10 +297,43 @@ let make = (~ventilatorParameters: VentilatorParameters.t, ~id, ~consultationId,
+ toggleOpen()} + className="md:max-w-3xl"> + toggleOpen()} + /> +
} diff --git a/src/Components/Facility/Consultations/Beds.res b/src/Components/Facility/Consultations/Beds.res new file mode 100644 index 00000000000..6e356172147 --- /dev/null +++ b/src/Components/Facility/Consultations/Beds.res @@ -0,0 +1,18 @@ +type reactClass +module Beds = { + @module("./Beds.tsx") @react.component + external make: ( + ~facilityId: string, + ~patientId: string, + ~consultationId: string, + ~setState: unit => unit, + ) => React.element = "default" +} + +@react.component +let make = ( + ~facilityId: string, + ~patientId: string, + ~consultationId: string, + ~setState: unit => unit, +) => From 54a32440907269b18e30d346804d10dd9b56d082 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Tue, 28 May 2024 16:53:00 +0530 Subject: [PATCH 016/106] Adds support for specifying onset and cure date in Encounter Symptoms (#7874) * update types and required utilities * Implements necessary reusable components for composing the Symptoms Builder * Migrate in Consultation Form * Migrate in daily rounds form * improve reusability * patch types, remove unused references, switch to new choices * rename consultation symptom to encounter symptom * Show symptoms in consultation dashboard * add loading state * update sorting * add loading state * fixes during QA * fix responsiveness of symptoms card * update button design * Make symptoms builder entries responsive * make add symptom block responsive * correct separator for for last element * remove unused imports --- src/Common/constants.tsx | 36 -- src/Components/Common/SymptomsSelect.tsx | 85 ---- src/Components/Diagnosis/utils.ts | 2 - .../ConsultationUpdatesTab.tsx | 92 +---- .../Facility/ConsultationDetails/index.tsx | 25 +- src/Components/Facility/ConsultationForm.tsx | 148 +++---- src/Components/Facility/models.tsx | 9 +- .../FormFields/AutocompleteMultiselect.tsx | 2 +- .../Patient/DailyRoundListDetails.tsx | 23 +- src/Components/Patient/DailyRounds.tsx | 39 +- src/Components/Patient/models.tsx | 3 - src/Components/Scribe/formDetails.ts | 2 +- src/Components/Symptoms/SymptomsBuilder.tsx | 379 ++++++++++++++++++ src/Components/Symptoms/SymptomsCard.tsx | 84 ++++ src/Components/Symptoms/api.ts | 47 +++ src/Components/Symptoms/types.ts | 52 +++ src/Components/Symptoms/utils.ts | 37 ++ src/Utils/types.ts | 36 ++ src/Utils/utils.ts | 8 + 19 files changed, 746 insertions(+), 363 deletions(-) delete mode 100644 src/Components/Common/SymptomsSelect.tsx create mode 100644 src/Components/Symptoms/SymptomsBuilder.tsx create mode 100644 src/Components/Symptoms/SymptomsCard.tsx create mode 100644 src/Components/Symptoms/api.ts create mode 100644 src/Components/Symptoms/types.ts create mode 100644 src/Components/Symptoms/utils.ts create mode 100644 src/Utils/types.ts diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index 99cb3a7988f..4eb3b51d012 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -318,42 +318,6 @@ export const REVIEW_AT_CHOICES: Array = [ { id: 30 * 24 * 60, text: "1 month" }, ]; -export const SYMPTOM_CHOICES = [ - { id: 1, text: "ASYMPTOMATIC", isSingleSelect: true }, - { id: 2, text: "FEVER" }, - { id: 3, text: "SORE THROAT" }, - { id: 4, text: "COUGH" }, - { id: 5, text: "BREATHLESSNESS" }, - { id: 6, text: "MYALGIA" }, - { id: 7, text: "ABDOMINAL DISCOMFORT" }, - { id: 8, text: "VOMITING" }, - { id: 11, text: "SPUTUM" }, - { id: 12, text: "NAUSEA" }, - { id: 13, text: "CHEST PAIN" }, - { id: 14, text: "HEMOPTYSIS" }, - { id: 15, text: "NASAL DISCHARGE" }, - { id: 16, text: "BODY ACHE" }, - { id: 17, text: "DIARRHOEA" }, - { id: 18, text: "PAIN" }, - { id: 19, text: "PEDAL EDEMA" }, - { id: 20, text: "WOUND" }, - { id: 21, text: "CONSTIPATION" }, - { id: 22, text: "HEAD ACHE" }, - { id: 23, text: "BLEEDING" }, - { id: 24, text: "DIZZINESS" }, - { id: 25, text: "CHILLS" }, - { id: 26, text: "GENERAL WEAKNESS" }, - { id: 27, text: "IRRITABILITY" }, - { id: 28, text: "CONFUSION" }, - { id: 29, text: "ABDOMINAL PAIN" }, - { id: 30, text: "JOINT PAIN" }, - { id: 31, text: "REDNESS OF EYES" }, - { id: 32, text: "ANOREXIA" }, - { id: 33, text: "NEW LOSS OF TASTE" }, - { id: 34, text: "NEW LOSS OF SMELL" }, - { id: 9, text: "OTHERS" }, -]; - export const DISCHARGE_REASONS = [ { id: 1, text: "Recovered" }, { id: 2, text: "Referred" }, diff --git a/src/Components/Common/SymptomsSelect.tsx b/src/Components/Common/SymptomsSelect.tsx deleted file mode 100644 index cdca3fe60dc..00000000000 --- a/src/Components/Common/SymptomsSelect.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import CareIcon from "../../CAREUI/icons/CareIcon"; -import { SYMPTOM_CHOICES } from "../../Common/constants"; -import { AutocompleteMutliSelect } from "../Form/FormFields/AutocompleteMultiselect"; -import FormField from "../Form/FormFields/FormField"; -import { - FormFieldBaseProps, - useFormFieldPropsResolver, -} from "../Form/FormFields/Utils"; - -const ASYMPTOMATIC_ID = 1; - -/** - * A `FormField` component to select symptoms. - * - * - If "Asymptomatic" is selected, every other selections are unselected. - * - If any non "Asymptomatic" value is selected, ensures "Asymptomatic" is - * unselected. - * - For other scenarios, this simply works like a `MultiSelect`. - */ -export const SymptomsSelect = (props: FormFieldBaseProps) => { - const field = useFormFieldPropsResolver(props); - - const updateSelection = (value: number[]) => { - // Skip the complexities if no initial value was present - if (!props.value?.length) return field.handleChange(value); - - const initialValue = props.value || []; - - if (initialValue.includes(ASYMPTOMATIC_ID) && value.length > 1) { - // If asym. already selected, and new selections have more than one value - const asymptomaticIndex = value.indexOf(1); - if (asymptomaticIndex > -1) { - // unselect asym. - value.splice(asymptomaticIndex, 1); - return field.handleChange(value); - } - } - - if (!initialValue.includes(ASYMPTOMATIC_ID) && value.includes(1)) { - // If new selections have asym., unselect everything else - return field.handleChange([ASYMPTOMATIC_ID]); - } - - field.handleChange(value); - }; - - const getDescription = ({ id }: { id: number }) => { - const value = props.value || []; - if (!value.length) return; - - if (value.includes(ASYMPTOMATIC_ID) && id !== ASYMPTOMATIC_ID) - return ( -
- - - also unselects Asymptomatic - -
- ); - - if (!value.includes(ASYMPTOMATIC_ID) && id === ASYMPTOMATIC_ID) - return ( - - - {`also unselects the other ${value.length} option(s)`} - - ); - }; - - return ( - - option.text} - optionValue={(option) => option.id} - value={props.value || []} - optionDescription={getDescription} - onChange={updateSelection} - /> - - ); -}; diff --git a/src/Components/Diagnosis/utils.ts b/src/Components/Diagnosis/utils.ts index c53f9b81bc1..1cac3cecbca 100644 --- a/src/Components/Diagnosis/utils.ts +++ b/src/Components/Diagnosis/utils.ts @@ -2,8 +2,6 @@ import routes from "../../Redux/api"; import request from "../../Utils/request/request"; import { ICD11DiagnosisModel } from "./types"; -// TODO: cache ICD11 responses and hit the cache if present instead of making an API call. - export const getDiagnosisById = async (id: ICD11DiagnosisModel["id"]) => { return (await request(routes.getICD11Diagnosis, { pathParams: { id } })).data; }; diff --git a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx index 2ba4f7929ae..be2751f0033 100644 --- a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx +++ b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx @@ -5,7 +5,7 @@ import { BedModel } from "../models"; import HL7PatientVitalsMonitor from "../../VitalsMonitor/HL7PatientVitalsMonitor"; import VentilatorPatientVitalsMonitor from "../../VitalsMonitor/VentilatorPatientVitalsMonitor"; import useVitalsAspectRatioConfig from "../../VitalsMonitor/useVitalsAspectRatioConfig"; -import { DISCHARGE_REASONS, SYMPTOM_CHOICES } from "../../../Common/constants"; +import { DISCHARGE_REASONS } from "../../../Common/constants"; import PrescriptionsTable from "../../Medicine/PrescriptionsTable"; import Chip from "../../../CAREUI/display/Chip"; import { @@ -23,6 +23,7 @@ import { getVitalsMonitorSocketUrl } from "../../VitalsMonitor/utils"; import useQuery from "../../../Utils/request/useQuery"; import routes from "../../../Redux/api"; import CareIcon from "../../../CAREUI/icons/CareIcon"; +import EncounterSymptomsCard from "../../Symptoms/SymptomsCard"; const PageTitle = lazy(() => import("../../Common/PageTitle")); @@ -363,91 +364,10 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => { )}
)} - {props.consultationData.symptoms_text && ( -
-
-

- Symptoms -

-
-
- Last Daily Update -
- {props.consultationData.last_daily_round - ?.additional_symptoms && ( - <> -
- {props.consultationData.last_daily_round?.additional_symptoms.map( - (symptom: any, index: number) => ( - choice.id === symptom, - )?.text ?? "Err. Unknown" - } - size="small" - /> - ), - )} -
- {props.consultationData.last_daily_round - ?.other_symptoms && ( -
-
- Other Symptoms: -
- { - props.consultationData.last_daily_round - ?.other_symptoms - } -
- )} - - from{" "} - {formatDate( - props.consultationData.last_daily_round.taken_at, - )} - - - )} -
-
- Consultation Update -
-
- {props.consultationData.symptoms?.map( - (symptom, index) => ( - choice.id === symptom, - )?.text ?? "Err. Unknown" - } - size="small" - /> - ), - )} -
- {props.consultationData.other_symptoms && ( -
-
- Other Symptoms: -
- {props.consultationData.other_symptoms} -
- )} - - from{" "} - {props.consultationData.symptoms_onset_date - ? formatDate(props.consultationData.symptoms_onset_date) - : "--/--/----"} - -
-
-
- )} + +
+ +
{props.consultationData.history_of_present_illness && (
diff --git a/src/Components/Facility/ConsultationDetails/index.tsx b/src/Components/Facility/ConsultationDetails/index.tsx index 38882c66f33..ee1f7bda7ff 100644 --- a/src/Components/Facility/ConsultationDetails/index.tsx +++ b/src/Components/Facility/ConsultationDetails/index.tsx @@ -1,8 +1,4 @@ -import { - CONSULTATION_TABS, - GENDER_TYPES, - SYMPTOM_CHOICES, -} from "../../../Common/constants"; +import { CONSULTATION_TABS, GENDER_TYPES } from "../../../Common/constants"; import { ConsultationModel } from "../models"; import { getConsultation, @@ -44,7 +40,6 @@ import { CameraFeedPermittedUserTypes } from "../../../Utils/permissions"; const Loading = lazy(() => import("../../Common/Loading")); const PageTitle = lazy(() => import("../../Common/PageTitle")); -const symptomChoices = [...SYMPTOM_CHOICES]; export interface ConsultationTabProps { consultationId: string; @@ -114,15 +109,15 @@ export const ConsultationDetails = (props: any) => { ...res.data, symptoms_text: "", }; - if (res.data.symptoms?.length) { - const symptoms = res.data.symptoms - .filter((symptom: number) => symptom !== 9) - .map((symptom: number) => { - const option = symptomChoices.find((i) => i.id === symptom); - return option ? option.text.toLowerCase() : symptom; - }); - data.symptoms_text = symptoms.join(", "); - } + // if (res.data.symptoms?.length) { + // const symptoms = res.data.symptoms + // .filter((symptom: number) => symptom !== 9) + // .map((symptom: number) => { + // const option = symptomChoices.find((i) => i.id === symptom); + // return option ? option.text.toLowerCase() : symptom; + // }); + // data.symptoms_text = symptoms.join(", "); + // } if (facilityId != data.facility || patientId != data.patient) { navigate( `/facility/${data.facility}/patient/${data.patient}/consultation/${data?.id}`, diff --git a/src/Components/Facility/ConsultationForm.tsx b/src/Components/Facility/ConsultationForm.tsx index b7b99d85448..594769ec1c4 100644 --- a/src/Components/Facility/ConsultationForm.tsx +++ b/src/Components/Facility/ConsultationForm.tsx @@ -23,7 +23,6 @@ import { BedSelect } from "../Common/BedSelect"; import Beds from "./Consultations/Beds"; import CareIcon from "../../CAREUI/icons/CareIcon"; import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField"; -import DateFormField from "../Form/FormFields/DateFormField"; import { FacilitySelect } from "../Common/FacilitySelect"; import { FieldChangeEvent, @@ -32,7 +31,6 @@ import { import { FormAction } from "../Form/Utils"; import PatientCategorySelect from "../Patient/PatientCategorySelect"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; -import { SymptomsSelect } from "../Common/SymptomsSelect"; import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; import TextFormField from "../Form/FormFields/TextFormField"; import UserAutocompleteFormField from "../Common/UserAutocompleteFormField"; @@ -61,6 +59,12 @@ import request from "../../Utils/request/request.js"; import routes from "../../Redux/api.js"; import useQuery from "../../Utils/request/useQuery.js"; import { t } from "i18next"; +import { Writable } from "../../Utils/types.js"; +import { EncounterSymptom } from "../Symptoms/types.js"; +import { + EncounterSymptomsBuilder, + CreateSymptomsBuilder, +} from "../Symptoms/SymptomsBuilder.js"; const Loading = lazy(() => import("../Common/Loading")); const PageTitle = lazy(() => import("../Common/PageTitle")); @@ -68,9 +72,7 @@ const PageTitle = lazy(() => import("../Common/PageTitle")); type BooleanStrings = "true" | "false"; type FormDetails = { - symptoms: number[]; - other_symptoms: string; - symptoms_onset_date?: Date; + is_asymptomatic: boolean; suggestion: ConsultationSuggestionValue; route_to_facility?: RouteToFacility; patient: string; @@ -91,6 +93,8 @@ type FormDetails = { treating_physician_object: UserModel | null; create_diagnoses: CreateDiagnosis[]; diagnoses: ConsultationDiagnosis[]; + symptoms: EncounterSymptom[]; + create_symptoms: Writable[]; is_kasp: BooleanStrings; kasp_enabled_date: null; examination_details: string; @@ -119,9 +123,9 @@ type FormDetails = { }; const initForm: FormDetails = { + is_asymptomatic: false, + create_symptoms: [], symptoms: [], - other_symptoms: "", - symptoms_onset_date: undefined, suggestion: "A", route_to_facility: undefined, patient: "", @@ -315,10 +319,6 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { }); }, []); - const hasSymptoms = - !!state.form.symptoms.length && !state.form.symptoms.includes(1); - const isOtherSymptomsSelected = state.form.symptoms.includes(9); - const handleFormFieldChange: FieldChangeEventHandler = (event) => { if (event.name === "suggestion" && event.value === "DD") { dispatch({ @@ -329,12 +329,21 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { consultation_notes: "Patient declared dead", }, }); - } else { + return; + } + + if (event.name === "is_asymptomatic" && event.value === true) { dispatch({ type: "set_form", - form: { ...state.form, [event.name]: event.value }, + form: { ...state.form, [event.name]: event.value, create_symptoms: [] }, }); + return; } + + dispatch({ + type: "set_form", + form: { ...state.form, [event.name]: event.value }, + }); }; const { loading: consultationLoading, refetch } = useQuery( @@ -372,9 +381,6 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { if (data) { const formData = { ...data, - symptoms_onset_date: - data.symptoms_onset_date && - isoStringToDate(data.symptoms_onset_date), encounter_date: isoStringToDate(data.encounter_date), icu_admission_date: data.icu_admission_date && @@ -435,12 +441,6 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { Object.keys(state.form).forEach((field) => { switch (field) { - case "symptoms": - if (!state.form[field] || !state.form[field].length) { - errors[field] = "Please select the symptoms"; - invalidForm = true; - } - return; case "category": if (!state.form[field]) { errors[field] = "Please select a category"; @@ -469,18 +469,6 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { invalidForm = true; } return; - case "other_symptoms": - if (isOtherSymptomsSelected && !state.form[field]) { - errors[field] = "Please enter the other symptom details"; - invalidForm = true; - } - return; - case "symptoms_onset_date": - if (hasSymptoms && !state.form[field]) { - errors[field] = "Please enter date of onset of the above symptoms"; - invalidForm = true; - } - return; case "encounter_date": if (!state.form[field]) { errors[field] = "Field is required"; @@ -501,6 +489,17 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { invalidForm = true; } return; + case "create_symptoms": + if ( + !isUpdate && + !state.form.is_asymptomatic && + state.form[field].length === 0 + ) { + errors[field] = + "Symptoms needs to be added as the patient is symptomatic"; + invalidForm = true; + } + return; case "death_datetime": if (state.form.suggestion === "DD" && !state.form[field]) { errors[field] = "Please enter the date & time of death"; @@ -679,13 +678,6 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { if (validated) { setIsLoading(true); const data: any = { - symptoms: state.form.symptoms, - other_symptoms: isOtherSymptomsSelected - ? state.form.other_symptoms - : undefined, - symptoms_onset_date: hasSymptoms - ? state.form.symptoms_onset_date - : undefined, suggestion: state.form.suggestion, route_to_facility: state.form.route_to_facility, admitted: state.form.suggestion === "A", @@ -698,6 +690,7 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { treatment_plan: state.form.treatment_plan, discharge_date: state.form.discharge_date, create_diagnoses: isUpdate ? undefined : state.form.create_diagnoses, + create_symptoms: isUpdate ? undefined : state.form.create_symptoms, treating_physician: state.form.treating_physician, investigation: state.form.InvestigationAdvice, procedure: state.form.procedure, @@ -1025,41 +1018,48 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => {
)} -
- -
- {isOtherSymptomsSelected && ( -
- -
- )} +
+
+ Symptoms - {hasSymptoms && ( -
- + {!isUpdate && ( + + )} + +
+ {isUpdate ? ( + + ) : ( + { + handleFormFieldChange({ + name: "create_symptoms", + value: symptoms, + }); + }} + /> + )} + +
- )} +
+
; - symptoms_text?: string; - symptoms_onset_date?: string; consultation_notes?: string; is_telemedicine?: boolean; procedure?: ProcedureType[]; diff --git a/src/Components/Form/FormFields/AutocompleteMultiselect.tsx b/src/Components/Form/FormFields/AutocompleteMultiselect.tsx index bed6a41f3c0..3bdbffdc6cb 100644 --- a/src/Components/Form/FormFields/AutocompleteMultiselect.tsx +++ b/src/Components/Form/FormFields/AutocompleteMultiselect.tsx @@ -84,7 +84,7 @@ export const AutocompleteMutliSelect = ( return { option, label, - description: props.optionDescription && props.optionDescription(option), + description: props.optionDescription?.(option), search: label.toLowerCase(), value: (props.optionValue ? props.optionValue(option) : option) as V, }; diff --git a/src/Components/Patient/DailyRoundListDetails.tsx b/src/Components/Patient/DailyRoundListDetails.tsx index 3c5e70044ed..66536c986ca 100644 --- a/src/Components/Patient/DailyRoundListDetails.tsx +++ b/src/Components/Patient/DailyRoundListDetails.tsx @@ -1,5 +1,5 @@ import { lazy, useState } from "react"; -import { CONSCIOUSNESS_LEVEL, SYMPTOM_CHOICES } from "../../Common/constants"; +import { CONSCIOUSNESS_LEVEL } from "../../Common/constants"; import { DailyRoundsModel } from "./models"; import Page from "../Common/components/Page"; import ButtonV2 from "../Common/components/ButtonV2"; @@ -7,7 +7,6 @@ import { formatDateTime } from "../../Utils/utils"; import useQuery from "../../Utils/request/useQuery"; import routes from "../../Redux/api"; const Loading = lazy(() => import("../Common/Loading")); -const symptomChoices = [...SYMPTOM_CHOICES]; export const DailyRoundListDetails = (props: any) => { const { facilityId, patientId, consultationId, id } = props; @@ -21,16 +20,8 @@ export const DailyRoundListDetails = (props: any) => { const tdata: DailyRoundsModel = { ...data, temperature: Number(data.temperature) ? data.temperature : "", - additional_symptoms_text: "", medication_given: data.medication_given ?? [], }; - if (data.additional_symptoms?.length) { - const symptoms = data.additional_symptoms.map((symptom: number) => { - const option = symptomChoices.find((i) => i.id === symptom); - return option ? option.text.toLowerCase() : symptom; - }); - tdata.additional_symptoms_text = symptoms.join(", "); - } setDailyRoundListDetails(tdata); } }, @@ -85,12 +76,6 @@ export const DailyRoundListDetails = (props: any) => { SpO2: {dailyRoundListDetailsData.ventilator_spo2 ?? "-"}
-
- - Additional Symptoms:{" "} - - {dailyRoundListDetailsData.additional_symptoms_text ?? "-"} -
Admitted To *:{" "} @@ -103,12 +88,6 @@ export const DailyRoundListDetails = (props: any) => { {dailyRoundListDetailsData.physical_examination_info ?? "-"}
-
- - Other Symptoms:{" "} - - {dailyRoundListDetailsData.other_symptoms ?? "-"} -
Other Details:{" "} diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx index dd15d44facc..66ebdca4fd2 100644 --- a/src/Components/Patient/DailyRounds.tsx +++ b/src/Components/Patient/DailyRounds.tsx @@ -17,7 +17,6 @@ import { capitalize } from "lodash-es"; import BloodPressureFormField, { BloodPressureValidator, } from "../Common/BloodPressureFormField"; -import { SymptomsSelect } from "../Common/SymptomsSelect"; import TemperatureFormField from "../Common/TemperatureFormField"; import { Cancel, Submit } from "../Common/components/ButtonV2"; import Page from "../Common/components/Page"; @@ -33,11 +32,11 @@ import routes from "../../Redux/api"; import { Scribe } from "../Scribe/Scribe"; import { DAILY_ROUND_FORM_SCRIBE_DATA } from "../Scribe/formDetails"; import { DailyRoundsModel } from "./models"; +import { EncounterSymptomsBuilder } from "../Symptoms/SymptomsBuilder"; +import { FieldLabel } from "../Form/FormFields/FormField"; const Loading = lazy(() => import("../Common/Loading")); const initForm: any = { - additional_symptoms: [], - other_symptoms: "", physical_examination_info: "", other_details: "", patient_category: "", @@ -120,7 +119,6 @@ export const DailyRounds = (props: any) => { const formFields = [ "physical_examination_info", "other_details", - "additional_symptoms", "action", "review_interval", "bp", @@ -201,15 +199,6 @@ export const DailyRounds = (props: any) => { invalidForm = true; } return; - case "other_symptoms": - if ( - state.form.additional_symptoms?.includes(9) && - !state.form[field] - ) { - errors[field] = "Please enter the other symptom details"; - invalidForm = true; - } - return; case "bp": { const error = BloodPressureValidator(state.form.bp); if (error) { @@ -237,11 +226,6 @@ export const DailyRounds = (props: any) => { taken_at: state.form.taken_at ? state.form.taken_at : new Date().toISOString(), - - additional_symptoms: state.form.additional_symptoms, - other_symptoms: state.form.additional_symptoms?.includes(9) - ? state.form.other_symptoms - : undefined, admitted_to: (state.form.admitted === "Select" ? undefined @@ -436,22 +420,11 @@ export const DailyRounds = (props: any) => { label="Other Details" rows={5} /> - - {state.form.additional_symptoms?.includes(9) && ( -
- -
- )} +
+ Symptoms + +
; medication_given?: Array; - additional_symptoms_text?: string; action?: string; review_interval?: number; id?: string; - other_symptoms?: string; admitted_to?: string; patient_category?: PatientCategory; output?: DailyRoundsOutput[]; diff --git a/src/Components/Scribe/formDetails.ts b/src/Components/Scribe/formDetails.ts index 2b56ce90df5..74673adea70 100644 --- a/src/Components/Scribe/formDetails.ts +++ b/src/Components/Scribe/formDetails.ts @@ -3,9 +3,9 @@ import { PATIENT_CATEGORIES, REVIEW_AT_CHOICES, RHYTHM_CHOICES, - SYMPTOM_CHOICES, TELEMEDICINE_ACTIONS, } from "../../Common/constants"; +import { SYMPTOM_CHOICES } from "../Symptoms/types"; import { Field } from "./Scribe"; export const DAILY_ROUND_FORM_SCRIBE_DATA: Field[] = [ diff --git a/src/Components/Symptoms/SymptomsBuilder.tsx b/src/Components/Symptoms/SymptomsBuilder.tsx new file mode 100644 index 00000000000..4d142a67841 --- /dev/null +++ b/src/Components/Symptoms/SymptomsBuilder.tsx @@ -0,0 +1,379 @@ +import { useState } from "react"; +import { Writable } from "../../Utils/types"; +import { + EncounterSymptom, + OTHER_SYMPTOM_CHOICE, + SYMPTOM_CHOICES, +} from "./types"; +import AutocompleteMultiSelectFormField from "../Form/FormFields/AutocompleteMultiselect"; +import DateFormField from "../Form/FormFields/DateFormField"; +import ButtonV2 from "../Common/components/ButtonV2"; +import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; +import { classNames, dateQueryString } from "../../Utils/utils"; +import { FieldChangeEvent } from "../Form/FormFields/Utils"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import useSlug from "../../Common/hooks/useSlug"; +import useQuery from "../../Utils/request/useQuery"; +import SymptomsApi from "./api"; +import request from "../../Utils/request/request"; +import { Success } from "../../Utils/Notifications"; +import { sortByOnsetDate } from "./utils"; + +export const CreateSymptomsBuilder = (props: { + value: Writable[]; + onChange: (value: Writable[]) => void; +}) => { + return ( +
+
    + {props.value.map((obj, index, arr) => { + const handleUpdate = (event: FieldChangeEvent) => { + const updated = { ...obj, [event.name]: event.value }; + props.onChange(arr.map((old, i) => (i === index ? updated : old))); + }; + + const handleRemove = () => { + props.onChange(arr.filter((_, i) => i !== index)); + }; + + return ( +
  • + +
  • + ); + })} +
+ + {props.value.length === 0 && ( +
+ No symptoms added +
+ )} + +
+ props.onChange([...props.value, ...objects])} + /> +
+
+ ); +}; + +export const EncounterSymptomsBuilder = (props: { showAll?: boolean }) => { + const consultationId = useSlug("consultation"); + + const [isProcessing, setIsProcessing] = useState(false); + const { data, loading, refetch } = useQuery(SymptomsApi.list, { + pathParams: { consultationId }, + query: { limit: 100 }, + }); + + if (!data) { + return ( +
+ + Fetching symptom records... +
+ ); + } + + let items = sortByOnsetDate(data.results); + if (!props.showAll) { + items = items.filter( + (i) => i.clinical_impression_status !== "entered-in-error", + ); + } + + return ( +
+
    + {items.map((symptom) => { + const handleUpdate = async (event: FieldChangeEvent) => { + setIsProcessing(true); + await request(SymptomsApi.partialUpdate, { + pathParams: { consultationId, external_id: symptom.id }, + body: { [event.name]: event.value }, + }); + await refetch(); + setIsProcessing(false); + }; + + const handleMarkAsEnteredInError = async () => { + setIsProcessing(true); + await request(SymptomsApi.markAsEnteredInError, { + pathParams: { consultationId, external_id: symptom.id }, + }); + await refetch(); + setIsProcessing(false); + }; + + return ( +
  • + +
  • + ); + })} +
+ + {items.length === 0 && ( +
+ Patient is Asymptomatic +
+ )} + +
+ refetch()} + /> +
+
+ ); +}; + +const SymptomEntry = (props: { + disabled?: boolean; + value: Writable | EncounterSymptom; + onChange: (event: FieldChangeEvent) => void; + onRemove: () => void; +}) => { + const symptom = props.value; + const disabled = + props.disabled || symptom.clinical_impression_status === "entered-in-error"; + return ( +
+ + +
+
+ + + + {symptom.clinical_impression_status === "entered-in-error" && ( + + Entered in Error + + )} +
+ + + +
+
+ ); +}; + +const AddSymptom = (props: { + disabled?: boolean; + existing: (Writable | EncounterSymptom)[]; + onAdd?: (value: Writable[]) => void; + consultationId?: string; +}) => { + const [processing, setProcessing] = useState(false); + const [selected, setSelected] = useState([]); + const [otherSymptom, setOtherSymptom] = useState(""); + const [onsetDate, setOnsetDate] = useState(); + + const activeSymptomIds = props.existing + .filter((o) => o.symptom !== OTHER_SYMPTOM_CHOICE.id && !o.cure_date) + .map((o) => o.symptom); + + const handleAdd = async () => { + const objects = selected.map((symptom) => { + return { + symptom, + onset_date: dateQueryString(onsetDate), + other_symptom: + symptom === OTHER_SYMPTOM_CHOICE.id ? otherSymptom : undefined, + }; + }); + + if (props.consultationId) { + const responses = await Promise.all( + objects.map((body) => + request(SymptomsApi.add, { + body, + pathParams: { consultationId: props.consultationId! }, + }), + ), + ); + + if (responses.every(({ res }) => !!res?.ok)) { + Success({ msg: "Symptoms records updated successfully" }); + } + } + props.onAdd?.(objects); + + setSelected([]); + setOtherSymptom(""); + }; + + const hasSymptoms = !!selected.length; + const otherSymptomValid = selected.includes(OTHER_SYMPTOM_CHOICE.id) + ? !!otherSymptom.trim() + : true; + + return ( +
+ setOnsetDate(value)} + errorClassName="hidden" + /> +
+ setSelected(e.value)} + options={SYMPTOM_CHOICES.filter( + ({ id }) => !activeSymptomIds.includes(id), + )} + optionLabel={(option) => option.text} + optionValue={(option) => option.id} + errorClassName="hidden" + /> + {selected.includes(OTHER_SYMPTOM_CHOICE.id) && ( + setOtherSymptom(value)} + errorClassName="hidden" + /> + )} +
+ { + setProcessing(true); + await handleAdd(); + setProcessing(false); + }} + > + {processing ? ( + <> + + Adding... + + ) : ( + Add Symptom(s) + )} + +
+ ); +}; + +export const SymptomText = (props: { + value: Writable | EncounterSymptom; +}) => { + const symptom = + SYMPTOM_CHOICES.find(({ id }) => props.value.symptom === id) || + OTHER_SYMPTOM_CHOICE; + + const isOtherSymptom = symptom.id === OTHER_SYMPTOM_CHOICE.id; + + return isOtherSymptom ? ( + <> + Other: + + {props.value.other_symptom || "Not specified"} + + + ) : ( + symptom.text + ); +}; diff --git a/src/Components/Symptoms/SymptomsCard.tsx b/src/Components/Symptoms/SymptomsCard.tsx new file mode 100644 index 00000000000..3ea124c17b2 --- /dev/null +++ b/src/Components/Symptoms/SymptomsCard.tsx @@ -0,0 +1,84 @@ +import RecordMeta from "../../CAREUI/display/RecordMeta"; +import useSlug from "../../Common/hooks/useSlug"; +import useQuery from "../../Utils/request/useQuery"; +import { SymptomText } from "./SymptomsBuilder"; +import SymptomsApi from "./api"; +import { type EncounterSymptom } from "./types"; +import { groupAndSortSymptoms } from "./utils"; +import CareIcon from "../../CAREUI/icons/CareIcon"; + +// TODO: switch to list from events as timeline view instead once filter event by event type name is done +const EncounterSymptomsCard = () => { + const consultationId = useSlug("consultation"); + + const { data } = useQuery(SymptomsApi.list, { + pathParams: { consultationId }, + query: { limit: 100 }, + }); + + if (!data) { + return ( +
+ + Fetching symptom records... +
+ ); + } + + const records = groupAndSortSymptoms(data.results); + + return ( +
+

+ Symptoms +

+ +
+ +
+ +
+
+
+ ); +}; + +const SymptomsSection = (props: { + title: string; + symptoms: EncounterSymptom[]; +}) => { + return ( +
+

+ {props.title} +

+
    + {props.symptoms.map((record) => ( +
  • +
    + +
    + + + Onset + + {record.cure_date && ( + + {"; Cured"} + + )} + +
    +
  • + ))} +
+ {!props.symptoms.length && ( +
+ No symptoms +
+ )} +
+ ); +}; + +export default EncounterSymptomsCard; diff --git a/src/Components/Symptoms/api.ts b/src/Components/Symptoms/api.ts new file mode 100644 index 00000000000..1cce062cb1e --- /dev/null +++ b/src/Components/Symptoms/api.ts @@ -0,0 +1,47 @@ +import { Type } from "../../Redux/api"; +import { PaginatedResponse } from "../../Utils/request/types"; +import { WritableOnly } from "../../Utils/types"; +import { EncounterSymptom } from "./types"; + +const SymptomsApi = { + list: { + method: "GET", + path: "/api/v1/consultation/{consultationId}/symptoms/", + TRes: Type>(), + }, + + add: { + path: "/api/v1/consultation/{consultationId}/symptoms/", + method: "POST", + TRes: Type(), + TBody: Type>(), + }, + + retrieve: { + method: "GET", + path: "/api/v1/consultation/{consultationId}/symptoms/{external_id}/", + TRes: Type(), + }, + + update: { + method: "PUT", + path: "/api/v1/consultation/{consultationId}/symptoms/{external_id}/", + TBody: Type>(), + TRes: Type(), + }, + + partialUpdate: { + method: "PATCH", + path: "/api/v1/consultation/{consultationId}/symptoms/{external_id}/", + TBody: Type>>(), + TRes: Type(), + }, + + markAsEnteredInError: { + method: "DELETE", + path: "/api/v1/consultation/{consultationId}/symptoms/{external_id}/", + TRes: Type(), + }, +} as const; + +export default SymptomsApi; diff --git a/src/Components/Symptoms/types.ts b/src/Components/Symptoms/types.ts new file mode 100644 index 00000000000..78e769ce9a9 --- /dev/null +++ b/src/Components/Symptoms/types.ts @@ -0,0 +1,52 @@ +import { BaseModel } from "../../Utils/types"; + +export const OTHER_SYMPTOM_CHOICE = { id: 9, text: "Other Symptom" } as const; + +export const SYMPTOM_CHOICES = [ + { id: 2, text: "Fever" }, + { id: 3, text: "Sore throat" }, + { id: 4, text: "Cough" }, + { id: 5, text: "Breathlessness" }, + { id: 6, text: "Myalgia" }, + { id: 7, text: "Abdominal discomfort" }, + { id: 8, text: "Vomiting" }, + { id: 11, text: "Sputum" }, + { id: 12, text: "Nausea" }, + { id: 13, text: "Chest pain" }, + { id: 14, text: "Hemoptysis" }, + { id: 15, text: "Nasal discharge" }, + { id: 16, text: "Body ache" }, + { id: 17, text: "Diarrhoea" }, + { id: 18, text: "Pain" }, + { id: 19, text: "Pedal Edema" }, + { id: 20, text: "Wound" }, + { id: 21, text: "Constipation" }, + { id: 22, text: "Head ache" }, + { id: 23, text: "Bleeding" }, + { id: 24, text: "Dizziness" }, + { id: 25, text: "Chills" }, + { id: 26, text: "General weakness" }, + { id: 27, text: "Irritability" }, + { id: 28, text: "Confusion" }, + { id: 29, text: "Abdominal pain" }, + { id: 30, text: "Join pain" }, + { id: 31, text: "Redness of eyes" }, + { id: 32, text: "Anorexia" }, + { id: 33, text: "New loss of taste" }, + { id: 34, text: "New loss of smell" }, + OTHER_SYMPTOM_CHOICE, +] as const; + +type ClinicalImpressionStatus = + | "in-progress" + | "completed" + | "entered-in-error"; + +export interface EncounterSymptom extends BaseModel { + symptom: (typeof SYMPTOM_CHOICES)[number]["id"]; + other_symptom?: string | null; + onset_date: string; + cure_date?: string | null; + readonly clinical_impression_status: ClinicalImpressionStatus; + readonly is_migrated: boolean; +} diff --git a/src/Components/Symptoms/utils.ts b/src/Components/Symptoms/utils.ts new file mode 100644 index 00000000000..997acfc914b --- /dev/null +++ b/src/Components/Symptoms/utils.ts @@ -0,0 +1,37 @@ +import { Writable } from "../../Utils/types"; +import { compareByDateString } from "../../Utils/utils"; +import { EncounterSymptom } from "./types"; + +// TODO: switch to using Object.groupBy(...) instead once upgraded to node v22 +export const groupAndSortSymptoms = < + T extends Writable | EncounterSymptom, +>( + records: T[], +) => { + const result: Record = { + "entered-in-error": [], + "in-progress": [], + completed: [], + }; + + for (const record of records) { + const status = + record.clinical_impression_status || + (record.cure_date ? "completed" : "in-progress"); + result[status].push(record); + } + + result["completed"] = sortByOnsetDate(result["completed"]); + result["in-progress"] = sortByOnsetDate(result["in-progress"]); + result["entered-in-error"] = sortByOnsetDate(result["entered-in-error"]); + + return result; +}; + +export const sortByOnsetDate = < + T extends Writable | EncounterSymptom, +>( + records: T[], +) => { + return records.sort(compareByDateString("onset_date")); +}; diff --git a/src/Utils/types.ts b/src/Utils/types.ts new file mode 100644 index 00000000000..519ede36ffc --- /dev/null +++ b/src/Utils/types.ts @@ -0,0 +1,36 @@ +import { PerformedByModel } from "../Components/HCX/misc"; + +export interface BaseModel { + readonly id: string; + readonly modified_date: string; + readonly created_date: string; + readonly created_by: PerformedByModel; + readonly updated_by: PerformedByModel; +} + +export type Writable = { + [P in keyof T as IfEquals< + { [Q in P]: T[P] }, + { -readonly [Q in P]: T[P] }, + never, + P + >]?: undefined; +} & { + [P in keyof T as IfEquals< + { [Q in P]: T[P] }, + { -readonly [Q in P]: T[P] }, + P, + never + >]: T[P]; +}; + +export type WritableOnly = { + [P in keyof T as IfEquals< + { [Q in P]: T[P] }, + { -readonly [Q in P]: T[P] }, + P + >]: T[P]; +}; + +type IfEquals = + (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? A : B; diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index 38ffc2e1c66..d599a494b1c 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -409,6 +409,14 @@ export const compareBy = (key: keyof T) => { }; }; +export const compareByDateString = (key: keyof T) => { + return (a: T, b: T) => { + const aV = new Date(a[key] as string); + const bV = new Date(b[key] as string); + return aV < bV ? -1 : aV > bV ? 1 : 0; + }; +}; + export const isValidUrl = (url?: string) => { try { new URL(url ?? ""); From fb9ba0e3949d2bd6c29c03b8aebfb5bc45f5df37 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Tue, 28 May 2024 17:01:54 +0530 Subject: [PATCH 017/106] Adds support for Doctors Log Update round type (v1) (#7839) * Adds support for Doctors Log Update round type (except syptoms) * Adds Prescription and Diagnosis * fix investigations not working * fix investigations not working * fixes during QA * disable disable save for doctors log --------- Co-authored-by: Khavin Shankar --- .../ConsultationDetails/Events/types.ts | 18 ++ src/Components/Patient/DailyRounds.tsx | 219 +++++++++++++++--- src/Components/Patient/models.tsx | 1 + src/Redux/api.tsx | 13 +- 4 files changed, 214 insertions(+), 37 deletions(-) diff --git a/src/Components/Facility/ConsultationDetails/Events/types.ts b/src/Components/Facility/ConsultationDetails/Events/types.ts index f5cf3c9abec..053450ea346 100644 --- a/src/Components/Facility/ConsultationDetails/Events/types.ts +++ b/src/Components/Facility/ConsultationDetails/Events/types.ts @@ -1,3 +1,5 @@ +import routes from "../../../../Redux/api"; +import request from "../../../../Utils/request/request"; import { UserBareMinimum } from "../../../Users/models"; export type Type = { @@ -28,3 +30,19 @@ export type EventGeneric = { }; // TODO: Once event types are finalized, define specific types for each event + +let cachedEventTypes: Type[] | null = null; + +export const fetchEventTypeByName = async (name: Type["name"]) => { + if (!cachedEventTypes) { + const { data } = await request(routes.listEventTypes, { + query: { limit: 100 }, + }); + + if (data?.results) { + cachedEventTypes = data.results; + } + } + + return cachedEventTypes?.find((t) => t.name === name); +}; diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx index 66ebdca4fd2..5675b56da6e 100644 --- a/src/Components/Patient/DailyRounds.tsx +++ b/src/Components/Patient/DailyRounds.tsx @@ -32,6 +32,17 @@ import routes from "../../Redux/api"; import { Scribe } from "../Scribe/Scribe"; import { DAILY_ROUND_FORM_SCRIBE_DATA } from "../Scribe/formDetails"; import { DailyRoundsModel } from "./models"; +import { fetchEventTypeByName } from "../Facility/ConsultationDetails/Events/types"; +import InvestigationBuilder from "../Common/prescription-builder/InvestigationBuilder"; +import { FieldErrorText } from "../Form/FormFields/FormField"; +import { error } from "@pnotify/core"; +import { useTranslation } from "react-i18next"; +import PrescriptionBuilder from "../Medicine/PrescriptionBuilder"; +import { EditDiagnosesBuilder } from "../Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisBuilder"; +import { + ConditionVerificationStatuses, + ConsultationDiagnosis, +} from "../Diagnosis/types"; import { EncounterSymptomsBuilder } from "../Symptoms/SymptomsBuilder"; import { FieldLabel } from "../Form/FormFields/FormField"; const Loading = lazy(() => import("../Common/Loading")); @@ -47,6 +58,8 @@ const initForm: any = { taken_at: null, rounds_type: "NORMAL", systolic: null, + investigations: [], + investigations_dirty: false, diastolic: null, pulse: null, resp: null, @@ -97,6 +110,7 @@ const DailyRoundsFormReducer = (state = initialState, action: any) => { }; export const DailyRounds = (props: any) => { + const { t } = useTranslation(); const { goBack } = useAppHistory(); const { facilityId, patientId, consultationId, id } = props; const [state, dispatch] = useAutoSaveReducer( @@ -113,6 +127,7 @@ export const DailyRounds = (props: any) => { ...initForm, action: "", }); + const [diagnoses, setDiagnoses] = useState(); const headerText = !id ? "Add Consultation Update" : "Info"; const buttonText = !id ? "Save" : "Continue"; @@ -124,6 +139,7 @@ export const DailyRounds = (props: any) => { "bp", "pulse", "resp", + "investigations", "ventilator_spo2", "rhythm", "rhythm_detail", @@ -132,6 +148,7 @@ export const DailyRounds = (props: any) => { const fetchRoundDetails = useCallback(async () => { setIsLoading(true); + fetchEventTypeByName(""); let formData: any = initialData; if (id) { const { data } = await request(routes.getDailyReport, { @@ -163,6 +180,13 @@ export const DailyRounds = (props: any) => { setPatientName(data.name!); setFacilityName(data.facility_object!.name); setConsultationSuggestion(data.last_consultation?.suggestion); + setDiagnoses( + data.last_consultation?.diagnoses?.sort( + (a: ConsultationDiagnosis, b: ConsultationDiagnosis) => + ConditionVerificationStatuses.indexOf(a.verification_status) - + ConditionVerificationStatuses.indexOf(b.verification_status), + ), + ); setPreviousReviewInterval( Number(data.last_consultation?.review_interval), ); @@ -174,7 +198,11 @@ export const DailyRounds = (props: any) => { ...initialData, action: getAction, }); - formData = { ...formData, ...{ action: getAction } }; + formData = { + ...formData, + action: getAction, + investigations: data.last_consultation?.investigation ?? [], + }; } } else { setPatientName(""); @@ -207,6 +235,33 @@ export const DailyRounds = (props: any) => { } return; } + + case "investigations": { + for (const investigation of state.form.investigations) { + if (!investigation.type?.length) { + errors[field] = "Investigation field can not be empty"; + invalidForm = true; + break; + } + if ( + investigation.repetitive && + !investigation.frequency?.replace(/\s/g, "").length + ) { + errors[field] = "Frequency field cannot be empty"; + invalidForm = true; + break; + } + if ( + !investigation.repetitive && + !investigation.time?.replace(/\s/g, "").length + ) { + errors[field] = "Time field cannot be empty"; + invalidForm = true; + break; + } + } + return; + } default: return; } @@ -220,6 +275,25 @@ export const DailyRounds = (props: any) => { const validForm = validateForm(); if (validForm) { setIsLoading(true); + + if ( + state.form.rounds_type === "DOCTORS_LOG" && + state.form.investigations_dirty + ) { + const { error: investigationError } = await request( + routes.partialUpdateConsultation, + { + body: { investigation: state.form.investigations }, + pathParams: { id: consultationId }, + }, + ); + + if (investigationError) { + Notification.Error({ msg: error }); + return; + } + } + let data: DailyRoundsModel = { rounds_type: state.form.rounds_type, patient_category: state.form.patient_category, @@ -282,14 +356,24 @@ export const DailyRounds = (props: any) => { setIsLoading(false); if (obj) { dispatch({ type: "set_form", form: initForm }); - Notification.Success({ - msg: `${obj.rounds_type === "VENTILATOR" ? "Critical Care" : capitalize(obj.rounds_type)} Log Updates details created successfully`, - }); if (["NORMAL", "TELEMEDICINE"].includes(state.form.rounds_type)) { + Notification.Success({ + msg: `${state.form.rounds_type === "NORMAL" ? "Normal" : "Tele-medicine"} log update created successfully`, + }); + navigate( + `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}`, + ); + } else if (state.form.rounds_type === "DOCTORS_LOG") { + Notification.Success({ + msg: "Doctors log update created successfully", + }); navigate( `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}`, ); } else { + Notification.Success({ + msg: "Critical Care log update created successfully", + }); navigate( `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/daily_rounds/${obj.id}/update`, ); @@ -300,10 +384,16 @@ export const DailyRounds = (props: any) => { }; const handleFormFieldChange = (event: FieldChangeEvent) => { - dispatch({ - type: "set_form", - form: { ...state.form, [event.name]: event.value }, - }); + const form = { + ...state.form, + [event.name]: event.value, + }; + + if (event.name === "investigations") { + form["investigations_dirty"] = true; + } + + dispatch({ type: "set_form", form }); }; const field = (name: string) => { @@ -390,6 +480,7 @@ export const DailyRounds = (props: any) => { options={[ ...[ { id: "NORMAL", text: "Normal" }, + { id: "DOCTORS_LOG", text: "Doctor's Log Update" }, { id: "VENTILATOR", text: "Critical Care" }, ], ...(consultationSuggestion == "DC" @@ -426,34 +517,40 @@ export const DailyRounds = (props: any) => {
- option.desc} - optionValue={(option) => option.text} - value={prevAction} - onChange={(event) => { - handleFormFieldChange(event); - setPreviousAction(event.value); - }} - /> + {state.form.rounds_type !== "DOCTORS_LOG" && ( + <> + option.desc} + optionValue={(option) => option.text} + value={prevAction} + onChange={(event) => { + handleFormFieldChange(event); + setPreviousAction(event.value); + }} + /> - option.text} - optionValue={(option) => option.id} - value={prevReviewInterval} - onChange={(event) => { - handleFormFieldChange(event); - setPreviousReviewInterval(Number(event.value)); - }} - /> + option.text} + optionValue={(option) => option.id} + value={prevReviewInterval} + onChange={(event) => { + handleFormFieldChange(event); + setPreviousReviewInterval(Number(event.value)); + }} + /> + + )} - {["NORMAL", "TELEMEDICINE"].includes(state.form.rounds_type) && ( + {["NORMAL", "TELEMEDICINE", "DOCTORS_LOG"].includes( + state.form.rounds_type, + ) && ( <>

Vitals

@@ -572,6 +669,53 @@ export const DailyRounds = (props: any) => { /> )} + + {state.form.rounds_type === "DOCTORS_LOG" && ( + <> +
+
+

+ {t("investigations")} +

+ { + handleFormFieldChange({ + name: "investigations", + value: investigations, + }); + }} + /> + +
+
+

+ {t("prescription_medications")} +

+ +
+
+

+ {t("prn_prescriptions")} +

+ +
+
+

+ {t("diagnosis")} +

+ {/* */} + {diagnoses ? ( + + ) : ( +
+ Fetching existing diagnosis of patient... +
+ )} +
+
+ + )}
@@ -580,11 +724,14 @@ export const DailyRounds = (props: any) => { disabled={ buttonText === "Save" && formFields.every( - (field: string) => state.form[field] == initialData[field], + (field: string) => + JSON.stringify(state.form[field]) === + JSON.stringify(initialData[field]), ) && (state.form.temperature == initialData.temperature || isNaN(state.form.temperature)) && - state.form.rounds_type !== "VENTILATOR" + state.form.rounds_type !== "VENTILATOR" && + state.form.rounds_type !== "DOCTORS_LOG" } onClick={(e) => handleSubmit(e)} label={buttonText} diff --git a/src/Components/Patient/models.tsx b/src/Components/Patient/models.tsx index 9ca01fcc63f..f64d71146c4 100644 --- a/src/Components/Patient/models.tsx +++ b/src/Components/Patient/models.tsx @@ -276,6 +276,7 @@ export interface DailyRoundsOutput { export const DailyRoundTypes = [ "NORMAL", + "DOCTORS_LOG", "VENTILATOR", "AUTOMATED", "TELEMEDICINE", diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 58147f68943..f4a9732a40c 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -97,7 +97,10 @@ import { import { PaginatedResponse } from "../Utils/request/types"; import { ICD11DiagnosisModel } from "../Components/Diagnosis/types"; -import { EventGeneric } from "../Components/Facility/ConsultationDetails/Events/types"; +import { + EventGeneric, + type Type, +} from "../Components/Facility/ConsultationDetails/Events/types"; import { InvestigationGroup, InvestigationType, @@ -628,6 +631,14 @@ const routes = { TRes: Type(), }, + // Event Types + + listEventTypes: { + path: "/api/v1/event_types/", + method: "GET", + TRes: Type>(), + }, + // Hospital Beds createCapacity: { path: "/api/v1/facility/{facilityId}/capacity/", From 6b75578dc750ef779c754edd842d35a5b19012df Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Tue, 28 May 2024 19:12:48 +0530 Subject: [PATCH 018/106] Adds cypress tests (#7930) --- .../patient_spec/patient_consultation.cy.ts | 30 ++++++++----------- .../e2e/patient_spec/patient_logupdate.cy.ts | 22 +++++++------- .../pageobject/Patient/PatientConsultation.ts | 12 ++++---- .../pageobject/Patient/PatientLogupdate.ts | 10 +++++++ src/Components/Patient/DailyRounds.tsx | 3 +- src/Components/Symptoms/SymptomsBuilder.tsx | 2 ++ src/Components/Symptoms/SymptomsCard.tsx | 2 +- 7 files changed, 44 insertions(+), 37 deletions(-) diff --git a/cypress/e2e/patient_spec/patient_consultation.cy.ts b/cypress/e2e/patient_spec/patient_consultation.cy.ts index d5732a6619d..eef98a5e3ce 100644 --- a/cypress/e2e/patient_spec/patient_consultation.cy.ts +++ b/cypress/e2e/patient_spec/patient_consultation.cy.ts @@ -59,7 +59,7 @@ describe("Patient Consultation in multiple combination", () => { patientConsultationPage.selectConsultationStatus( "Outpatient/Emergency Room", ); - cy.searchAndSelectOption("#symptoms", "ASYMPTOMATIC"); + cy.get("#is_asymptomatic").click(); patientConsultationPage.typePatientIllnessHistory(patientIllnessHistory); patientConsultationPage.typePatientExaminationHistory( patientExaminationHistory, @@ -175,7 +175,7 @@ describe("Patient Consultation in multiple combination", () => { "Outpatient/Emergency Room", ); // Asymptomatic - cy.searchAndSelectOption("#symptoms", "ASYMPTOMATIC"); + cy.get("#is_asymptomatic").click(); // CRITICAL category patientConsultationPage.selectPatientCategory("Critical"); patientConsultationPage.selectPatientSuggestion("Declare Death"); @@ -234,7 +234,7 @@ describe("Patient Consultation in multiple combination", () => { ); patientConsultationPage.selectPatientWard("Dummy Location 1"); // Asymptomatic - cy.searchAndSelectOption("#symptoms", "ASYMPTOMATIC"); + cy.get("#is_asymptomatic").click(); // Abnormal category patientConsultationPage.selectPatientCategory("Moderate"); patientConsultationPage.selectPatientSuggestion("Domiciliary Care"); @@ -293,18 +293,14 @@ describe("Patient Consultation in multiple combination", () => { ); // verify the free text in referring facility name patientConsultationPage.typeReferringFacility("Life Care Hospital"); - // Vomiting and Nausea symptoms + patientConsultationPage.selectSymptomsDate("01012024"); patientConsultationPage.typeAndMultiSelectSymptoms("s", [ - "SPUTUM", - "SORE THROAT", + "Sore throat", + "Sputum", ]); + patientConsultationPage.clickAddSymptom(); // Stable category patientConsultationPage.selectPatientCategory("Mild"); - // Date of symptoms - patientConsultationPage.selectSymptomsDate( - "#symptoms_onset_date", - "01012024", - ); // OP Consultation patientConsultationPage.selectPatientSuggestion("OP Consultation"); // one ICD-11 and no principal @@ -341,18 +337,16 @@ describe("Patient Consultation in multiple combination", () => { patientConsultationPage.selectConsultationStatus( "Outpatient/Emergency Room", ); - // Select the Symptoms - Sore throat and fever symptoms + // Select the Symptoms - Breathlessness and Bleeding symptoms + patientConsultationPage.selectSymptomsDate("01012024"); patientConsultationPage.typeAndMultiSelectSymptoms("b", [ - "BREATHLESSNESS", - "BLEEDING", + "Breathlessness", + "Bleeding", ]); + patientConsultationPage.clickAddSymptom(); // Comfort Care category patientConsultationPage.selectPatientCategory("Comfort Care"); // Date of symptoms - patientConsultationPage.selectSymptomsDate( - "#symptoms_onset_date", - "01012024", - ); // Decision after consultation - Referred to Facility patientConsultationPage.selectPatientSuggestion( "Refer to another Hospital", diff --git a/cypress/e2e/patient_spec/patient_logupdate.cy.ts b/cypress/e2e/patient_spec/patient_logupdate.cy.ts index a55b86e464b..562e430a9ad 100644 --- a/cypress/e2e/patient_spec/patient_logupdate.cy.ts +++ b/cypress/e2e/patient_spec/patient_logupdate.cy.ts @@ -11,7 +11,7 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { const patientLogupdate = new PatientLogupdate(); const domicilaryPatient = "Dummy Patient 11"; const patientCategory = "Moderate"; - const additionalSymptoms = "ASYMPTOMATIC"; + const additionalSymptoms = "Fever"; const physicalExamination = "physical examination details"; const otherExamination = "Other"; const patientSystolic = "119"; @@ -59,9 +59,7 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { patientLogupdate.typeRhythm(patientRhythm); cy.get("#consciousness_level-2").click(); cy.submitButton("Save"); - cy.verifyNotification( - "Telemedicine Log Updates details created successfully", - ); + cy.verifyNotification("Tele-medicine log update created successfully"); }); it("Create a new log normal update for a domicilary care patient and edit it", () => { @@ -86,7 +84,7 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { patientLogupdate.typeRhythm(patientRhythm); cy.get("#consciousness_level-2").click(); cy.submitButton("Save"); - cy.verifyNotification("Normal Log Updates details created successfully"); + cy.verifyNotification("Normal log update created successfully"); cy.closeNotification(); // edit the card and verify the data. cy.contains("Daily Rounds").click(); @@ -109,7 +107,7 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { patientLogupdate.clickClearButtonInElement("#diastolic"); patientLogupdate.typeDiastolic(patientModifiedDiastolic); cy.submitButton("Continue"); - cy.verifyNotification("Normal Log Updates details updated successfully"); + cy.verifyNotification("Normal log update details updated successfully"); cy.contains("Daily Rounds").click(); patientLogupdate.clickLogupdateCard("#dailyround-entry", patientCategory); cy.verifyContentPresence("#consultation-preview", [ @@ -127,7 +125,9 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { patientLogupdate.clickLogupdate(); patientLogupdate.typePhysicalExamination(physicalExamination); patientLogupdate.typeOtherDetails(otherExamination); - patientLogupdate.typeAdditionalSymptoms(additionalSymptoms); + patientLogupdate.selectSymptomsDate("01012024"); + patientLogupdate.typeAndMultiSelectSymptoms("fe", ["Fever"]); + patientLogupdate.clickAddSymptom(); patientLogupdate.selectPatientCategory(patientCategory); patientLogupdate.typeSystolic(patientSystolic); patientLogupdate.typeDiastolic(patientDiastolic); @@ -140,10 +140,10 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { cy.get("#consciousness_level-2").click(); cy.submitButton("Save"); cy.wait(2000); - cy.verifyNotification("Normal Log Updates details created successfully"); + cy.verifyNotification("Normal log update created successfully"); // Verify the card content cy.get("#basic-information").scrollIntoView(); - cy.verifyContentPresence("#basic-information", [additionalSymptoms]); + cy.verifyContentPresence("#encounter-symptoms", [additionalSymptoms]); }); it("Create a normal log update to verify MEWS Score Functionality", () => { @@ -163,7 +163,7 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { patientLogupdate.typeRespiratory(patientRespiratory); cy.get("#consciousness_level-2").click(); cy.submitButton("Save"); - cy.verifyNotification("Normal Log Updates details created successfully"); + cy.verifyNotification("Normal log update created successfully"); cy.closeNotification(); cy.verifyContentPresence("#consultation-buttons", ["9"]); // Verify the Incomplete data will give blank info @@ -173,7 +173,7 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { patientLogupdate.typeDiastolic(patientDiastolic); patientLogupdate.typePulse(patientPulse); cy.submitButton("Save"); - cy.verifyNotification("Normal Log Updates details created successfully"); + cy.verifyNotification("Normal log update created successfully"); cy.closeNotification(); cy.verifyContentPresence("#consultation-buttons", ["-"]); }); diff --git a/cypress/pageobject/Patient/PatientConsultation.ts b/cypress/pageobject/Patient/PatientConsultation.ts index 4400d9a524c..31b1fd6cb68 100644 --- a/cypress/pageobject/Patient/PatientConsultation.ts +++ b/cypress/pageobject/Patient/PatientConsultation.ts @@ -6,14 +6,14 @@ export class PatientConsultationPage { cy.clickAndSelectOption("#route_to_facility", status); } - selectSymptoms(symptoms) { - cy.clickAndMultiSelectOption("#symptoms", symptoms); - } typeAndMultiSelectSymptoms(input, symptoms) { - cy.typeAndMultiSelectOption("#symptoms", input, symptoms); + cy.typeAndMultiSelectOption("#additional_symptoms", input, symptoms); + } + selectSymptomsDate(date: string) { + cy.clickAndTypeDate("#symptoms_onset_date", date); } - selectSymptomsDate(selector: string, date: string) { - cy.clickAndTypeDate(selector, date); + clickAddSymptom() { + cy.get("#add-symptom").click(); } verifyConsultationPatientName(patientName: string) { diff --git a/cypress/pageobject/Patient/PatientLogupdate.ts b/cypress/pageobject/Patient/PatientLogupdate.ts index 3511f0241bb..92ea02a1417 100644 --- a/cypress/pageobject/Patient/PatientLogupdate.ts +++ b/cypress/pageobject/Patient/PatientLogupdate.ts @@ -32,6 +32,16 @@ class PatientLogupdate { cy.searchAndSelectOption("#additional_symptoms", symptoms); } + typeAndMultiSelectSymptoms(input, symptoms) { + cy.typeAndMultiSelectOption("#additional_symptoms", input, symptoms); + } + selectSymptomsDate(date: string) { + cy.clickAndTypeDate("#symptoms_onset_date", date); + } + clickAddSymptom() { + cy.get("#add-symptom").click(); + } + typeSystolic(systolic: string) { cy.searchAndSelectOption("#systolic", systolic); } diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx index 5675b56da6e..d0b2e321898 100644 --- a/src/Components/Patient/DailyRounds.tsx +++ b/src/Components/Patient/DailyRounds.tsx @@ -45,6 +45,7 @@ import { } from "../Diagnosis/types"; import { EncounterSymptomsBuilder } from "../Symptoms/SymptomsBuilder"; import { FieldLabel } from "../Form/FormFields/FormField"; + const Loading = lazy(() => import("../Common/Loading")); const initForm: any = { @@ -336,7 +337,7 @@ export const DailyRounds = (props: any) => { if (obj) { dispatch({ type: "set_form", form: initForm }); Notification.Success({ - msg: `${obj.rounds_type === "VENTILATOR" ? "Critical Care" : capitalize(obj.rounds_type)} Log Updates details updated successfully`, + msg: `${obj.rounds_type === "VENTILATOR" ? "Critical Care" : capitalize(obj.rounds_type)} log update details updated successfully`, }); if (["NORMAL", "TELEMEDICINE"].includes(state.form.rounds_type)) { navigate( diff --git a/src/Components/Symptoms/SymptomsBuilder.tsx b/src/Components/Symptoms/SymptomsBuilder.tsx index 4d142a67841..de6901d8384 100644 --- a/src/Components/Symptoms/SymptomsBuilder.tsx +++ b/src/Components/Symptoms/SymptomsBuilder.tsx @@ -283,6 +283,7 @@ const AddSymptom = (props: { { const records = groupAndSortSymptoms(data.results); return ( -
+

Symptoms

From cf5f1c3edfeefe7f40f000030f73f2ad286d0ef4 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Wed, 29 May 2024 11:43:58 +0530 Subject: [PATCH 019/106] fixes up/down arrow of numeric inputs not clickable in facility create and other places (#7931) * fixes up/down arrow of numeric inputs not clickable in facility create and other places * fix wight and height --- src/Components/Facility/ConsultationForm.tsx | 4 ++-- src/Components/Facility/FacilityCreate.tsx | 2 +- src/Components/Patient/PatientRegister.tsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Components/Facility/ConsultationForm.tsx b/src/Components/Facility/ConsultationForm.tsx index 594769ec1c4..d362abe1ecc 100644 --- a/src/Components/Facility/ConsultationForm.tsx +++ b/src/Components/Facility/ConsultationForm.tsx @@ -1103,7 +1103,7 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { placeholder="Weight" trailingPadding=" " trailing={ -

+

Weight (kg)

} @@ -1116,7 +1116,7 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { placeholder="Height" trailingPadding=" " trailing={ -

+

Height (cm)

} diff --git a/src/Components/Facility/FacilityCreate.tsx b/src/Components/Facility/FacilityCreate.tsx index 1fd51940f8f..383a0646958 100644 --- a/src/Components/Facility/FacilityCreate.tsx +++ b/src/Components/Facility/FacilityCreate.tsx @@ -1001,5 +1001,5 @@ export const FacilityCreate = (props: FacilityProps) => { }; const FieldUnit = ({ unit }: { unit: string }) => { - return

{unit}

; + return

{unit}

; }; diff --git a/src/Components/Patient/PatientRegister.tsx b/src/Components/Patient/PatientRegister.tsx index 34d2ac66387..7d219514267 100644 --- a/src/Components/Patient/PatientRegister.tsx +++ b/src/Components/Patient/PatientRegister.tsx @@ -1345,8 +1345,9 @@ export const PatientRegister = (props: PatientRegisterProps) => { +

{field("age").value !== "" && ( <> @@ -1364,7 +1365,6 @@ export const PatientRegister = (props: PatientRegisterProps) => {

} placeholder="Enter the age" - className="col-span-6 sm:col-span-3" type="number" min={0} /> From 8ae85d6089920b815dc81fc787925d879ffe1755 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Wed, 29 May 2024 11:45:27 +0530 Subject: [PATCH 020/106] fixes overflow issues in consultation dashboard (#7932) * fixes overflow issues in consultation dashboard * Update src/Components/Common/components/Menu.tsx --- .../AdministrationTable.tsx | 4 ++-- src/Components/Patient/PatientInfoCard.tsx | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Components/Medicine/MedicineAdministrationSheet/AdministrationTable.tsx b/src/Components/Medicine/MedicineAdministrationSheet/AdministrationTable.tsx index 98fa1021f9f..49a071b744b 100644 --- a/src/Components/Medicine/MedicineAdministrationSheet/AdministrationTable.tsx +++ b/src/Components/Medicine/MedicineAdministrationSheet/AdministrationTable.tsx @@ -24,7 +24,7 @@ export default function MedicineAdministrationTable({ return (
- +
@@ -51,7 +51,7 @@ export default function MedicineAdministrationTable({ disabled={!pagination.hasPrevious} onClick={pagination.previous} tooltip="Previous 24 hours" - tooltipClassName="tooltip-bottom -translate-x-1/2 text-xs" + tooltipClassName="tooltip-bottom text-xs" > diff --git a/src/Components/Patient/PatientInfoCard.tsx b/src/Components/Patient/PatientInfoCard.tsx index ba9280e903d..247d9312d6e 100644 --- a/src/Components/Patient/PatientInfoCard.tsx +++ b/src/Components/Patient/PatientInfoCard.tsx @@ -185,9 +185,9 @@ export default function PatientInfoCard(props: { )} -
+
{/* Can support for patient picture in the future */} @@ -269,7 +269,7 @@ export default function PatientInfoCard(props: {
-
+
{medicoLegalCase && ( - + MLC )} @@ -301,7 +301,7 @@ export default function PatientInfoCard(props: {
{consultation?.patient_no && ( @@ -520,12 +520,12 @@ export default function PatientInfoCard(props: {
{consultation?.suggestion === "A" && (
-
+
)} {consultation?.last_daily_round && ( -
+
)} @@ -633,7 +633,7 @@ export default function PatientInfoCard(props: { } className="xl:justify-center" containerClassName="w-full lg:w-auto mt-2 2xl:mt-0 flex justify-center z-20" From 778351dee911909631c3283be75d5d3c60dbc204 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Thu, 30 May 2024 15:18:46 +0530 Subject: [PATCH 021/106] Disable switching to "Refer to another facility"/"Declare Death" in Decision after consultation in edit consultation form (#7940) * Adds support for disabling individual options in dropdown menu * Disable Refer to another hospital and declare death in edit consultation form * correct colors disabled option description * add to i18n --- src/Common/constants.tsx | 4 +-- src/Components/Facility/ConsultationForm.tsx | 8 ++++++ .../Form/FormFields/Autocomplete.tsx | 14 ++++++++-- .../FormFields/AutocompleteMultiselect.tsx | 17 +++++++++--- .../Form/FormFields/SelectFormField.tsx | 4 +++ src/Components/Form/MultiSelectMenuV2.tsx | 27 +++++++++++++------ src/Components/Form/SelectMenuV2.tsx | 19 +++++++++---- src/Locale/en/Consultation.json | 3 ++- 8 files changed, 74 insertions(+), 22 deletions(-) diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index 4eb3b51d012..f0d0ab08f04 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -363,10 +363,10 @@ export const SAMPLE_TEST_RESULT = [ export const CONSULTATION_SUGGESTION = [ { id: "HI", text: "Home Isolation", deprecated: true }, // # Deprecated. Preserving option for backward compatibility (use only for readonly operations) { id: "A", text: "Admission" }, - { id: "R", text: "Refer to another Hospital" }, + { id: "R", text: "Refer to another Hospital", editDisabled: true }, { id: "OP", text: "OP Consultation" }, { id: "DC", text: "Domiciliary Care" }, - { id: "DD", text: "Declare Death" }, + { id: "DD", text: "Declare Death", editDisabled: true }, ] as const; export type ConsultationSuggestionValue = diff --git a/src/Components/Facility/ConsultationForm.tsx b/src/Components/Facility/ConsultationForm.tsx index d362abe1ecc..a5e333e43c6 100644 --- a/src/Components/Facility/ConsultationForm.tsx +++ b/src/Components/Facility/ConsultationForm.tsx @@ -1148,6 +1148,14 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { options={CONSULTATION_SUGGESTION.filter( (option) => !("deprecated" in option), )} + optionDisabled={(option) => + isUpdate && "editDisabled" in option + } + optionDescription={(option) => + isUpdate && "editDisabled" in option + ? t("encounter_suggestion_edit_disallowed") + : undefined + } />
diff --git a/src/Components/Form/FormFields/Autocomplete.tsx b/src/Components/Form/FormFields/Autocomplete.tsx index e414fda233a..67b1224cddf 100644 --- a/src/Components/Form/FormFields/Autocomplete.tsx +++ b/src/Components/Form/FormFields/Autocomplete.tsx @@ -17,6 +17,7 @@ type AutocompleteFormFieldProps = FormFieldBaseProps & { optionValue?: OptionCallback; optionDescription?: OptionCallback; optionIcon?: OptionCallback; + optionDisabled?: OptionCallback; onQuery?: (query: string) => void; dropdownIcon?: React.ReactNode | undefined; isLoading?: boolean; @@ -43,6 +44,7 @@ const AutocompleteFormField = ( optionIcon={props.optionIcon} optionValue={props.optionValue} optionDescription={props.optionDescription} + optionDisabled={props.optionDisabled} onQuery={props.onQuery} isLoading={props.isLoading} allowRawInput={props.allowRawInput} @@ -65,6 +67,7 @@ type AutocompleteProps = { optionIcon?: OptionCallback; optionValue?: OptionCallback; optionDescription?: OptionCallback; + optionDisabled?: OptionCallback; className?: string; onQuery?: (query: string) => void; requiredError?: boolean; @@ -105,6 +108,7 @@ export const Autocomplete = (props: AutocompleteProps) => { search: label.toLowerCase(), icon: props.optionIcon?.(option), value: props.optionValue ? props.optionValue(option) : option, + disabled: props.optionDisabled?.(option), }; }); @@ -123,6 +127,7 @@ export const Autocomplete = (props: AutocompleteProps) => { search: query.toLowerCase(), icon: , value: query, + disabled: undefined, }, ...mappedOptions, ]; @@ -204,6 +209,7 @@ export const Autocomplete = (props: AutocompleteProps) => { key={`${props.id}-option-${option.label}-value-${index}`} className={dropdownOptionClassNames} value={option} + disabled={option.disabled} > {({ active }) => (
@@ -214,8 +220,12 @@ export const Autocomplete = (props: AutocompleteProps) => { {option.description && (
{option.description} diff --git a/src/Components/Form/FormFields/AutocompleteMultiselect.tsx b/src/Components/Form/FormFields/AutocompleteMultiselect.tsx index 3bdbffdc6cb..7857e96889d 100644 --- a/src/Components/Form/FormFields/AutocompleteMultiselect.tsx +++ b/src/Components/Form/FormFields/AutocompleteMultiselect.tsx @@ -17,6 +17,7 @@ type AutocompleteMultiSelectFormFieldProps = FormFieldBaseProps & { options: T[]; optionLabel: OptionCallback; optionValue?: OptionCallback; + optionDisabled?: OptionCallback; onQuery?: (query: string) => void; dropdownIcon?: React.ReactNode | undefined; isLoading?: boolean; @@ -50,6 +51,7 @@ type AutocompleteMutliSelectProps = { optionDescription?: OptionCallback; optionLabel: OptionCallback; optionValue?: OptionCallback; + optionDisabled?: OptionCallback; className?: string; onChange: OptionCallback; onQuery?: (query: string) => void; @@ -87,6 +89,7 @@ export const AutocompleteMutliSelect = ( description: props.optionDescription?.(option), search: label.toLowerCase(), value: (props.optionValue ? props.optionValue(option) : option) as V, + disabled: props.optionDisabled?.(option), }; }); @@ -187,8 +190,9 @@ export const AutocompleteMutliSelect = ( onClick={() => { handleSingleSelect(option); }} + disabled={option.disabled} > - {({ selected }) => ( + {({ active, selected }) => ( <>
{option.label} @@ -198,9 +202,14 @@ export const AutocompleteMutliSelect = (
{option.description && (

{option.description}

diff --git a/src/Components/Form/FormFields/SelectFormField.tsx b/src/Components/Form/FormFields/SelectFormField.tsx index 42ba82fa5de..47c28777368 100644 --- a/src/Components/Form/FormFields/SelectFormField.tsx +++ b/src/Components/Form/FormFields/SelectFormField.tsx @@ -14,6 +14,7 @@ type SelectFormFieldProps = FormFieldBaseProps & { optionDescription?: OptionCallback; optionIcon?: OptionCallback; optionValue?: OptionCallback; + optionDisabled?: OptionCallback; }; export const SelectFormField = (props: SelectFormFieldProps) => { @@ -34,6 +35,7 @@ export const SelectFormField = (props: SelectFormFieldProps) => { optionDescription={props.optionDescription} optionIcon={props.optionIcon} optionValue={props.optionValue} + optionDisabled={props.optionDisabled} requiredError={field.error ? props.required : false} /> @@ -48,6 +50,7 @@ type MultiSelectFormFieldProps = FormFieldBaseProps & { optionDescription?: OptionCallback; optionIcon?: OptionCallback; optionValue?: OptionCallback; + optionDisabled?: OptionCallback; }; export const MultiSelectFormField = ( @@ -67,6 +70,7 @@ export const MultiSelectFormField = ( optionSelectedLabel={props.optionSelectedLabel} optionDescription={props.optionDescription} optionIcon={props.optionIcon} + optionDisabled={props.optionDisabled} optionValue={props.optionValue} /> diff --git a/src/Components/Form/MultiSelectMenuV2.tsx b/src/Components/Form/MultiSelectMenuV2.tsx index 1568c3f6e84..419664f98e9 100644 --- a/src/Components/Form/MultiSelectMenuV2.tsx +++ b/src/Components/Form/MultiSelectMenuV2.tsx @@ -16,6 +16,7 @@ type Props = { optionDescription?: OptionCallback; optionIcon?: OptionCallback; optionValue?: OptionCallback; + optionDisabled?: OptionCallback; className?: string; disabled?: boolean; renderSelectedOptions?: OptionCallback; @@ -42,9 +43,10 @@ const MultiSelectMenuV2 = (props: Props) => { option, label, selectedLabel, - description: props.optionDescription && props.optionDescription(option), - icon: props.optionIcon && props.optionIcon(option), + description: props.optionDescription?.(option), + icon: props.optionIcon?.(option), value, + disabled: props.optionDisabled?.(option), isSelected: props.value?.includes(value as any) ?? false, displayChip: (
@@ -138,6 +140,7 @@ const MultiSelectMenuV2 = (props: Props) => { className={dropdownOptionClassNames} value={option} onClick={() => handleSingleSelect(option)} + disabled={option.disabled} > {({ active }) => (
@@ -152,9 +155,14 @@ const MultiSelectMenuV2 = (props: Props) => {
{option.description && (

{option.description}

@@ -205,17 +213,20 @@ export const MultiSelectOptionChip = ({ interface OptionRenderPropArg { active: boolean; selected: boolean; + disabled: boolean; } export const dropdownOptionClassNames = ({ active, selected, + disabled, }: OptionRenderPropArg) => { return classNames( "group/option relative w-full cursor-default select-none p-4 text-sm transition-colors duration-75 ease-in-out", - active && "bg-primary-500 text-white", - !active && selected && "text-primary-500", - !active && !selected && "text-gray-900", + !disabled && active && "bg-primary-500 text-white", + !disabled && !active && selected && "text-primary-500", + !disabled && !active && !selected && "text-gray-900", + disabled && "cursor-not-allowed text-gray-800", selected ? "font-semibold" : "font-normal", ); }; diff --git a/src/Components/Form/SelectMenuV2.tsx b/src/Components/Form/SelectMenuV2.tsx index 03e0312c602..4483c92fe4c 100644 --- a/src/Components/Form/SelectMenuV2.tsx +++ b/src/Components/Form/SelectMenuV2.tsx @@ -19,6 +19,7 @@ type SelectMenuProps = { optionDescription?: OptionCallback; optionIcon?: OptionCallback; optionValue?: OptionCallback; + optionDisabled?: OptionCallback; showIconWhenSelected?: boolean; showChevronIcon?: boolean; className?: string; @@ -51,9 +52,10 @@ const SelectMenuV2 = (props: SelectMenuProps) => { selectedLabel: props.optionSelectedLabel ? props.optionSelectedLabel(option) : label, - description: props.optionDescription && props.optionDescription(option), - icon: props.optionIcon && props.optionIcon(option), + description: props.optionDescription?.(option), + icon: props.optionIcon?.(option), value: props.optionValue ? props.optionValue(option) : option, + disabled: props.optionDisabled?.(option), }; }); @@ -67,6 +69,7 @@ const SelectMenuV2 = (props: SelectMenuProps) => { description: undefined, icon: undefined, value: undefined, + disabled: undefined, }; const options = props.required @@ -128,6 +131,7 @@ const SelectMenuV2 = (props: SelectMenuProps) => { key={index} className={dropdownOptionClassNames} value={option} + disabled={option.disabled} > {({ active, selected }) => (
@@ -144,9 +148,14 @@ const SelectMenuV2 = (props: SelectMenuProps) => {
{option.description && (

{option.description}

diff --git a/src/Locale/en/Consultation.json b/src/Locale/en/Consultation.json index 42f31433495..fc1b5ae5f7d 100644 --- a/src/Locale/en/Consultation.json +++ b/src/Locale/en/Consultation.json @@ -34,5 +34,6 @@ "generate_report": "Generate Report", "prev_sessions": "Prev Sessions", "next_sessions": "Next Sessions", - "no_changes": "No changes" + "no_changes": "No changes", + "encounter_suggestion_edit_disallowed": "Not allowed to switch to this option in edit consultation" } \ No newline at end of file From da03963c6a1fc1217a1209119950eadfd465666e Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Thu, 30 May 2024 15:19:35 +0530 Subject: [PATCH 022/106] Camera Feed: Improve watermark and preset sizes in mobile view (#7935) * Camera Feed: Improve watermark and preset sizes in mobile view * adds notification and correct sizes * decrease watermark opacity to 20 * fixed width for preset sizes * add warning for char. limit of preset name * add banding for mobile landscape view to maintain aspect ratio and fit feed content * min width for preset buttons * improve responsiveness --- src/CAREUI/display/NetworkSignal.tsx | 8 +++- .../Assets/configure/CameraConfigure.tsx | 9 ++++- src/Components/CameraFeed/AssetBedSelect.tsx | 38 +++++++++++-------- src/Components/CameraFeed/CameraFeed.tsx | 8 ++-- src/Components/CameraFeed/FeedWatermark.tsx | 6 +-- .../ConsultationFeedTab.tsx | 6 ++- 6 files changed, 49 insertions(+), 26 deletions(-) diff --git a/src/CAREUI/display/NetworkSignal.tsx b/src/CAREUI/display/NetworkSignal.tsx index 2bcd2744acb..1d5f2d49623 100644 --- a/src/CAREUI/display/NetworkSignal.tsx +++ b/src/CAREUI/display/NetworkSignal.tsx @@ -19,7 +19,7 @@ export default function NetworkSignal({ strength, children }: Props) { return (
)) )} + {!!strength && strength < 2 && ( + + )}
{children}
diff --git a/src/Components/Assets/configure/CameraConfigure.tsx b/src/Components/Assets/configure/CameraConfigure.tsx index c3ba434ef3f..5a8ccd5c184 100644 --- a/src/Components/Assets/configure/CameraConfigure.tsx +++ b/src/Components/Assets/configure/CameraConfigure.tsx @@ -7,6 +7,7 @@ import { getCameraConfig } from "../../../Utils/transformUtils"; import { Submit } from "../../Common/components/ButtonV2"; import TextFormField from "../../Form/FormFields/TextFormField"; import Card from "../../../CAREUI/display/Card"; +import { FieldErrorText } from "../../Form/FormFields/FormField"; interface CameraConfigureProps { asset: AssetData; @@ -59,8 +60,14 @@ export default function CameraConfigure(props: CameraConfigureProps) { value={newPreset} className="mt-1" onChange={(e) => setNewPreset(e.value)} - error="" + errorClassName="hidden" /> + {newPreset.length > 12 && ( + + )}
diff --git a/src/Components/CameraFeed/AssetBedSelect.tsx b/src/Components/CameraFeed/AssetBedSelect.tsx index 715c326c35d..56def3d41c9 100644 --- a/src/Components/CameraFeed/AssetBedSelect.tsx +++ b/src/Components/CameraFeed/AssetBedSelect.tsx @@ -15,17 +15,17 @@ export default function CameraPresetSelect(props: Props) { const label = props.label ?? defaultLabel; return ( <> -
+
{/* Desktop View */} {props.options .slice(0, props.options.length > 5 ? 4 : 5) .map((option) => ( ))} {props.options.length > 5 && ( - + o.id === props.value?.id)} + /> )}
-
+
{/* Mobile View */}
@@ -54,17 +58,19 @@ export const CameraPresetDropdown = (props: Props) => { return (
- - + + {selected ? label(selected) : "Select preset"} - - + + { leaveFrom="opacity-100" leaveTo="opacity-0" > - + {options?.map((obj) => ( { {({ selected }) => ( <> diff --git a/src/Components/CameraFeed/CameraFeed.tsx b/src/Components/CameraFeed/CameraFeed.tsx index 81b526363b9..a5f0a3d80e0 100644 --- a/src/Components/CameraFeed/CameraFeed.tsx +++ b/src/Components/CameraFeed/CameraFeed.tsx @@ -96,13 +96,13 @@ export default function CameraFeed(props: Props) { props.className, )} > -
+
{props.children}
- + {props.asset.name} @@ -170,7 +170,7 @@ export default function CameraFeed(props: Props) { ) : (
+ {((props.patientData.is_antenatal && + isAntenatal(props.patientData.last_menstruation_start_date)) || + isPostPartum(props.patientData.date_of_delivery)) && ( +
+

+ Perinatal Status +

+ +
+ {props.patientData.is_antenatal && + isAntenatal( + props.patientData.last_menstruation_start_date, + ) && ( + + )} + {isPostPartum(props.patientData.date_of_delivery) && ( + + )} +
+ + {props.patientData.last_menstruation_start_date && ( +

+ + Last Menstruation: + + {formatDate( + props.patientData.last_menstruation_start_date, + )} + +

+ )} + + {props.patientData.date_of_delivery && ( +

+ + Date of Delivery: + + {formatDate(props.patientData.date_of_delivery)} + +

+ )} +
+ )}
From fa5e3e6903c008174a213366ccb0ab50eafe3854 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Fri, 31 May 2024 18:09:44 +0530 Subject: [PATCH 025/106] Reorder fields in Daily Rounds (#7954) * move symptoms up * move diagnosis up --- src/Components/Patient/DailyRounds.tsx | 36 +++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx index d0b2e321898..35e9828f9d1 100644 --- a/src/Components/Patient/DailyRounds.tsx +++ b/src/Components/Patient/DailyRounds.tsx @@ -502,6 +502,11 @@ export const DailyRounds = (props: any) => {
+
+ Symptoms + +
+ { rows={5} /> -
- Symptoms - -
- {state.form.rounds_type !== "DOCTORS_LOG" && ( <> { {state.form.rounds_type === "DOCTORS_LOG" && ( <>
+
+

+ {t("diagnosis")} +

+ {/* */} + {diagnoses ? ( + + ) : ( +
+ Fetching existing diagnosis of patient... +
+ )} +

{t("investigations")} @@ -701,19 +714,6 @@ export const DailyRounds = (props: any) => {

-
-

- {t("diagnosis")} -

- {/* */} - {diagnoses ? ( - - ) : ( -
- Fetching existing diagnosis of patient... -
- )} -
)} From cef2795ed3d1f1771e052d0343820e5036bd3315 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Mon, 3 Jun 2024 03:58:34 +0530 Subject: [PATCH 026/106] fix button widths --- src/CAREUI/interactive/FiltersSlideover.tsx | 2 +- src/Components/Common/SortDropdown.tsx | 3 ++- src/Components/Patient/ManagePatients.tsx | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/CAREUI/interactive/FiltersSlideover.tsx b/src/CAREUI/interactive/FiltersSlideover.tsx index 83f92e2bd90..496f1b3e516 100644 --- a/src/CAREUI/interactive/FiltersSlideover.tsx +++ b/src/CAREUI/interactive/FiltersSlideover.tsx @@ -58,7 +58,7 @@ export const AdvancedFilterButton = ({ onClick }: { onClick: () => void }) => { diff --git a/src/Components/Common/SortDropdown.tsx b/src/Components/Common/SortDropdown.tsx index b29662de0a7..8ffd09ba269 100644 --- a/src/Components/Common/SortDropdown.tsx +++ b/src/Components/Common/SortDropdown.tsx @@ -23,8 +23,9 @@ export default function SortDropdownMenu(props: Props) { } + containerClassName="w-full md:w-auto" > {props.options.map(({ isAscending, value }) => ( { selected={qParams.ordering} onSelect={updateQuery} /> -
+
{!isExportAllowed ? ( { From 6e4114bf376bda1821e2a18bf4ee3804110f1351 Mon Sep 17 00:00:00 2001 From: Khavin Shankar Date: Mon, 3 Jun 2024 18:29:05 +0530 Subject: [PATCH 027/106] clean special characters in phone number in whatsapp link (#7964) --- src/Components/Facility/DoctorVideoSlideover.tsx | 4 ++-- src/Components/Patient/PatientHome.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Components/Facility/DoctorVideoSlideover.tsx b/src/Components/Facility/DoctorVideoSlideover.tsx index a77a1017b6b..140de6985c1 100644 --- a/src/Components/Facility/DoctorVideoSlideover.tsx +++ b/src/Components/Facility/DoctorVideoSlideover.tsx @@ -172,11 +172,11 @@ function UserListItem({ user }: { user: UserAnnotatedWithGroup }) { function connectOnWhatsApp(e: React.MouseEvent) { e.stopPropagation(); if (!user.alt_phone_number) return; - const phoneNumber = user.alt_phone_number; + const phoneNumber = user.alt_phone_number?.replace(/\D+/g, ""); const message = `${courtesyTitle(user)} ${user.first_name} ${user.last_name}, I have a query regarding a patient.\n\nPatient Link: ${window.location.href}`; const encodedMessage = encodeURIComponent(message); const whatsappAppURL = `whatsapp://send?phone=${phoneNumber}&text=${encodedMessage}`; - const whatsappWebURL = `https://web.whatsapp.com/send?phone=${phoneNumber}&text=${encodedMessage}`; + const whatsappWebURL = `https://wa.me/${phoneNumber}?text=${encodedMessage}`; const userAgent = navigator.userAgent; const isEdge = /edge\/\d+/i.test(userAgent); diff --git a/src/Components/Patient/PatientHome.tsx b/src/Components/Patient/PatientHome.tsx index e92d043dff4..53a6a8215aa 100644 --- a/src/Components/Patient/PatientHome.tsx +++ b/src/Components/Patient/PatientHome.tsx @@ -287,7 +287,7 @@ export const PatientHome = (props: any) => { {patientData?.last_consultation?.assigned_to_object .alt_phone_number && ( @@ -433,7 +433,7 @@ export const PatientHome = (props: any) => {
Date: Mon, 3 Jun 2024 21:27:08 +0530 Subject: [PATCH 028/106] Camera Feed Fixes: Gracefully handle full-screen errors for unsupported devices; fixes clipping of content in certain sizes in landscape mode (#7965) * fix screen height of camera feed * change label to more preset * fix fullscreen issues with iOS devices * fix presets placeholder and disable if no options * fixes feed alert z index * corrections for feed alert * hacks for iOS * fix missing placeholder for preset dropdown in live feed --- src/CAREUI/misc/Fullscreen.tsx | 15 +++++-- src/Components/CameraFeed/AssetBedSelect.tsx | 19 +++++++-- src/Components/CameraFeed/CameraFeed.tsx | 41 ++++++++++++++----- .../CameraFeed/CameraFeedWithBedPresets.tsx | 1 + src/Components/CameraFeed/FeedAlert.tsx | 10 ++--- .../ConsultationFeedTab.tsx | 9 +++- 6 files changed, 71 insertions(+), 24 deletions(-) diff --git a/src/CAREUI/misc/Fullscreen.tsx b/src/CAREUI/misc/Fullscreen.tsx index 5cfa7865128..82c6d9e91ed 100644 --- a/src/CAREUI/misc/Fullscreen.tsx +++ b/src/CAREUI/misc/Fullscreen.tsx @@ -5,17 +5,25 @@ interface Props { fullscreenClassName?: string; children: React.ReactNode; fullscreen: boolean; - onExit: () => void; + onExit: (reason?: "DEVICE_UNSUPPORTED") => void; } export default function Fullscreen(props: Props) { const ref = useRef(null); useEffect(() => { + if (!ref.current) { + return; + } + if (props.fullscreen) { - ref.current?.requestFullscreen(); + if (ref.current.requestFullscreen) { + ref.current.requestFullscreen(); + } else { + props.onExit("DEVICE_UNSUPPORTED"); + } } else { - document.exitFullscreen(); + document.exitFullscreen?.(); } }, [props.fullscreen]); @@ -27,6 +35,7 @@ export default function Fullscreen(props: Props) { }; document.addEventListener("fullscreenchange", listener); + return () => { document.removeEventListener("fullscreenchange", listener); }; diff --git a/src/Components/CameraFeed/AssetBedSelect.tsx b/src/Components/CameraFeed/AssetBedSelect.tsx index 56def3d41c9..f970a920abc 100644 --- a/src/Components/CameraFeed/AssetBedSelect.tsx +++ b/src/Components/CameraFeed/AssetBedSelect.tsx @@ -35,6 +35,7 @@ export default function CameraPresetSelect(props: Props) { {props.options.length > 5 && ( o.id === props.value?.id)} /> @@ -42,13 +43,15 @@ export default function CameraPresetSelect(props: Props) {
{/* Mobile View */} - +
); } -export const CameraPresetDropdown = (props: Props) => { +export const CameraPresetDropdown = ( + props: Props & { placeholder: string }, +) => { const selected = props.value; const options = props.options.filter(({ meta }) => meta.type !== "boundary"); @@ -56,7 +59,11 @@ export const CameraPresetDropdown = (props: Props) => { const label = props.label ?? defaultLabel; return ( - +
{ )} > - {selected ? label(selected) : "Select preset"} + {options.length === 0 + ? "No presets" + : selected + ? label(selected) + : props.placeholder} diff --git a/src/Components/CameraFeed/CameraFeed.tsx b/src/Components/CameraFeed/CameraFeed.tsx index a5f0a3d80e0..1c6781ee51b 100644 --- a/src/Components/CameraFeed/CameraFeed.tsx +++ b/src/Components/CameraFeed/CameraFeed.tsx @@ -4,7 +4,7 @@ import useOperateCamera, { PTZPayload } from "./useOperateCamera"; import usePlayer from "./usePlayer"; import { getStreamUrl } from "./utils"; import ReactPlayer from "react-player"; -import { classNames, isIOS } from "../../Utils/utils"; +import { classNames, isAppleDevice, isIOS } from "../../Utils/utils"; import FeedAlert, { FeedAlertState } from "./FeedAlert"; import FeedNetworkSignal from "./FeedNetworkSignal"; import NoFeedAvailable from "./NoFeedAvailable"; @@ -12,6 +12,7 @@ import FeedControls from "./FeedControls"; import Fullscreen from "../../CAREUI/misc/Fullscreen"; import FeedWatermark from "./FeedWatermark"; import CareIcon from "../../CAREUI/icons/CareIcon"; +import { Error } from "../../Utils/Notifications"; interface Props { children?: React.ReactNode; @@ -27,6 +28,7 @@ interface Props { constrolsDisabled?: boolean; shortcutsDisabled?: boolean; onMove?: () => void; + onReset?: () => void; } export default function CameraFeed(props: Props) { @@ -86,14 +88,29 @@ export default function CameraFeed(props: Props) { const resetStream = () => { setState("loading"); + props.onReset?.(); initializeStream(); }; return ( - setFullscreen(false)}> + { + setFullscreen(false); + + if (reason === "DEVICE_UNSUPPORTED") { + // iOS webkit allows only video/iframe elements to call full-screen + // APIs. But we need to show controls too, not just the video element. + Error({ + msg: "This device does not support viewing this content in full-screen.", + }); + } + }} + >
@@ -106,14 +123,16 @@ export default function CameraFeed(props: Props) { /> {props.asset.name} -
- -
+ {!isIOS && ( +
+ +
+ )}
diff --git a/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx b/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx index e3fc2ab2129..8ce9c9ef67f 100644 --- a/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx +++ b/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx @@ -33,6 +33,7 @@ export default function LocationFeedTile(props: Props) { options={data?.results ?? []} value={preset} onChange={setPreset} + placeholder="Select preset" /> )}
diff --git a/src/Components/CameraFeed/FeedAlert.tsx b/src/Components/CameraFeed/FeedAlert.tsx index a4f8a3beb18..138af509c8c 100644 --- a/src/Components/CameraFeed/FeedAlert.tsx +++ b/src/Components/CameraFeed/FeedAlert.tsx @@ -15,12 +15,12 @@ interface Props { state?: FeedAlertState; } -const ALERT_ICON_MAP: Record = { +const ALERT_ICON_MAP: Partial> = { playing: "l-play-circle", stop: "l-stop-circle", offline: "l-exclamation-triangle", loading: "l-spinner", - moving: "l-expand-from-corner", + // moving: "l-expand-from-corner", zooming: "l-search", saving_preset: "l-save", host_unreachable: "l-exclamation-triangle", @@ -53,14 +53,14 @@ export default function FeedAlert({ state }: Props) { leaveFrom="opacity-100 translate-y-0" leaveTo="opacity-0 -translate-y-5" > -
- {state && ( +
+ {state && ALERT_ICON_MAP[state] && ( )} diff --git a/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx index 3d12441eb10..91cd86c606e 100644 --- a/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx +++ b/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx @@ -16,7 +16,7 @@ import useOperateCamera, { PTZPayload, } from "../../CameraFeed/useOperateCamera"; import request from "../../../Utils/request/request"; -import { classNames } from "../../../Utils/utils"; +import { classNames, isIOS } from "../../../Utils/utils"; export const ConsultationFeedTab = (props: ConsultationTabProps) => { const authUser = useAuthUser(); @@ -27,6 +27,7 @@ export const ConsultationFeedTab = (props: ConsultationTabProps) => { const [preset, setPreset] = useState(); const [isUpdatingPreset, setIsUpdatingPreset] = useState(false); const [hasMoved, setHasMoved] = useState(false); + const [key, setKey] = useState(0); const divRef = useRef(); const operate = useOperateCamera(asset?.id ?? "", true); @@ -106,9 +107,15 @@ export const ConsultationFeedTab = (props: ConsultationTabProps) => {
setHasMoved(true)} + onReset={() => { + if (isIOS) { + setKey(key + 1); + } + }} onStreamError={() => { triggerGoal("Camera Feed Viewed", { consultationId: props.consultationId, From 339e2a63c68f7fd4546457c49f925ee9fcc47f73 Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Tue, 4 Jun 2024 17:33:55 +0000 Subject: [PATCH 029/106] Support retries in Scribe --- src/Components/Scribe/Scribe.tsx | 115 ++++++++++++++++++++++--------- 1 file changed, 84 insertions(+), 31 deletions(-) diff --git a/src/Components/Scribe/Scribe.tsx b/src/Components/Scribe/Scribe.tsx index 6e876a4762c..b5149267e24 100644 --- a/src/Components/Scribe/Scribe.tsx +++ b/src/Components/Scribe/Scribe.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { Popover } from "@headlessui/react"; import ButtonV2 from "../Common/components/ButtonV2"; import CareIcon from "../../CAREUI/icons/CareIcon"; @@ -59,7 +59,7 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => { const [open, setOpen] = useState(false); const [_progress, setProgress] = useState(0); const [stage, setStage] = useState("start"); - const [editableTranscript, setEditableTranscript] = useState(""); + const [_editableTranscript, setEditableTranscript] = useState(""); const [errors, setErrors] = useState([]); const [isTranscribing, setIsTranscribing] = useState(false); const [formFields, setFormFields] = useState<{ @@ -70,6 +70,13 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => { const [isAudioUploading, setIsAudioUploading] = useState(false); const [updatedTranscript, setUpdatedTranscript] = useState(""); const [scribeID, setScribeID] = useState(""); + const stageRef = useRef(stage); + + useEffect(() => { + if (stageRef.current === "cancelled") { + setStage("start"); + } + }, [stage]); const { isRecording, @@ -81,6 +88,7 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => { const uploadAudioData = (response: any, audioBlob: Blob) => { return new Promise((resolve, reject) => { + if (stageRef.current === "cancelled") resolve(); const url = response.data.signed_url; const internal_name = response.data.internal_name; const f = audioBlob; @@ -109,6 +117,7 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => { const uploadAudio = async (audioBlob: Blob, associatingId: string) => { return new Promise((resolve, reject) => { + if (stageRef.current === "cancelled") resolve({}); const category = "AUDIO"; const name = "audio.mp3"; const filename = Date.now().toString(); @@ -127,6 +136,8 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => { uploadAudioData(response, audioBlob) .then(() => { if (!response?.data?.id) throw Error("Error uploading audio"); + + if (stageRef.current === "cancelled") resolve({}); markUploadComplete(response?.data.id, associatingId).then(() => { resolve(response.data); }); @@ -145,12 +156,14 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => { startRecording(); setProgress(25); setStage("recording"); + stageRef.current = "recording"; }; const handleStopRecordingClick = () => { stopRecording(); setProgress(50); setStage("recording-review"); + stageRef.current = "recording-end"; }; const handleEditChange = (e: { @@ -161,6 +174,7 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => { const waitForTranscript = async (externalId: string): Promise => { return new Promise((resolve, reject) => { + if (stageRef.current === "cancelled") resolve(""); const interval = setInterval(async () => { try { const res = await request(routes.getScribe, { @@ -191,9 +205,29 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => { }); }; + const reProcessTranscript = async () => { + setErrors([]); + setEditableTranscript(updatedTranscript); + setStage("review"); + const res = await request(routes.updateScribe, { + body: { + status: "READY", + transcript: updatedTranscript, + ai_response: null, + }, + pathParams: { + external_id: scribeID, + }, + }); + if (res.error || !res.data) throw Error("Error updating scribe instance"); + setStage("review"); + await handleSubmitTranscript(res.data.external_id); + }; + const waitForFormData = async (externalId: string): Promise => { return new Promise((resolve, reject) => { const interval = setInterval(async () => { + if (stageRef.current === "cancelled") resolve(""); try { const res = await request(routes.getScribe, { pathParams: { @@ -270,12 +304,14 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => { }; const handleSubmitTranscript = async (external_id: string) => { + if (stageRef.current === "cancelled") return; setProgress(75); setIsGPTProcessing(true); try { const updatedFieldsResponse = await waitForFormData(external_id); setProgress(100); const parsedFormData = JSON.parse(updatedFieldsResponse ?? "{}"); + if (stageRef.current === "cancelled") return; setFormFields(parsedFormData); onFormUpdate(parsedFormData); setStage("final-review"); @@ -286,6 +322,7 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => { }; const startTranscription = async (scribeID: string) => { + if (stageRef.current === "cancelled") return; setIsTranscribing(true); const errors = []; try { @@ -302,6 +339,7 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => { //poll for transcript const transcriptResponse = await waitForTranscript(res.data.external_id); + if (stageRef.current === "cancelled") return; setEditableTranscript(transcriptResponse); setUpdatedTranscript(transcriptResponse); setStage("review"); @@ -329,6 +367,10 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => { setFormFields({}); setEditableTranscript(""); setUpdatedTranscript(""); + setIsAudioUploading(false); + setScribeID(""); + setIsHoveringCancelRecord(false); + stageRef.current = "cancelled"; }; const processFormField = ( @@ -485,44 +527,47 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => { src={URL.createObjectURL(blob)} /> ))} + { + handleRerecordClick(); + }} + className="w-full" + > + Restart Recording +
)} {(stage === "review" || stage === "final-review") && (
-

- Transcript -

+
+

+ Transcript +

+

+ (Edit if needed) +

+
@@ -173,7 +173,7 @@ const PatientNoteCard = ({
) : ( -
{noteField}
+
{noteField}
)}
} @@ -186,7 +186,7 @@ const PatientNoteCard = ({ >
-

+

Edit History for note {note.id}

@@ -202,21 +202,23 @@ const PatientNoteCard = ({ return (
-

+

{isLast ? "Created" : "Edited"} On

-

+

{formatDateTime(edit.edited_date)}

-

Note

-

{edit.note}

+

+ Note +

+

{edit.note}

); diff --git a/src/Components/Facility/PatientNotesSlideover.tsx b/src/Components/Facility/PatientNotesSlideover.tsx index e2da270e532..6c8931ce249 100644 --- a/src/Components/Facility/PatientNotesSlideover.tsx +++ b/src/Components/Facility/PatientNotesSlideover.tsx @@ -130,7 +130,7 @@ export default function PatientNotesSlideover(props: PatientNotesProps) {
{show && ( setShow(!show)} @@ -153,7 +153,7 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { />
setShowPatientNotesPopup(false)} > {
{formatDate(date)}
-
-
-
+
+
+
Name : {patientData?.name ?? ""}
@@ -66,13 +66,13 @@ const TreatmentSummary = (props: any) => {
-
+
-
+
Age :{" "} {patientData ? formatPatientAge(patientData, true) : ""}
-
+
OP : {consultationData?.patient_no ?? ""}
@@ -91,8 +91,8 @@ const TreatmentSummary = (props: any) => {
-
-
+
+
Gender : {GENDER_TYPES.find((i) => i.id === patientData?.gender)?.text}
@@ -108,14 +108,14 @@ const TreatmentSummary = (props: any) => {
-
+
Comorbidities :
- +
- - + + @@ -125,10 +125,10 @@ const TreatmentSummary = (props: any) => { (obj: any, index: number) => { return ( - - @@ -137,10 +137,10 @@ const TreatmentSummary = (props: any) => { ) ) : ( - - @@ -150,7 +150,7 @@ const TreatmentSummary = (props: any) => { -
+
Diagnosis :
@@ -177,7 +177,7 @@ const TreatmentSummary = (props: any) => {
-
+
General Instructions : {patientData?.last_consultation?.consultation_notes ? (
@@ -188,29 +188,29 @@ const TreatmentSummary = (props: any) => { )}
-
+
Relevant investigations :
-
DiseaseDetailsDiseaseDetails
+ {obj["disease"]} + {obj["details"] ? obj["details"] : "---"}
+ --- + ---
+
- - - - - - @@ -222,28 +222,28 @@ const TreatmentSummary = (props: any) => { (value: any, index: number) => { return ( - - - - - - @@ -252,22 +252,22 @@ const TreatmentSummary = (props: any) => { ) ) : ( - - - - - - @@ -277,7 +277,7 @@ const TreatmentSummary = (props: any) => { -
+
Treatment : {consultationData?.treatment_plan ? (

{consultationData.treatment_plan}

@@ -287,40 +287,42 @@ const TreatmentSummary = (props: any) => { Treatment summary/Treament Plan :
-
+ Date + Name + Result + Ideal value + values range + unit
+ {formatDate( value["session_object"][ "session_created_date" ], )} + {value["investigation_object"]["name"]} + {value["notes"] || value["value"]} + {value["investigation_object"]["ideal_value"] || "-"} + {value["investigation_object"]["min_value"]} -{" "} {value["investigation_object"]["max_value"]} + {value["investigation_object"]["unit"] || "-"}
+ --- + --- + --- + --- + --- + ---
+
- - - + + + {consultationData?.last_daily_round ? ( - - - ) : ( - - - diff --git a/src/Components/Form/AutoCompleteAsync.tsx b/src/Components/Form/AutoCompleteAsync.tsx index b9caa512137..4fd9346065d 100644 --- a/src/Components/Form/AutoCompleteAsync.tsx +++ b/src/Components/Form/AutoCompleteAsync.tsx @@ -118,7 +118,7 @@ const AutoCompleteAsync = (props: Props) => {
{ e.preventDefault(); onChange(null); @@ -144,7 +144,7 @@ const AutoCompleteAsync = (props: Props) => { {data?.length === 0 ? ( -
+
{query !== "" ? "Nothing found." : "Start typing to search..."} @@ -161,7 +161,7 @@ const AutoCompleteAsync = (props: Props) => {
{optionLabel(item)} {optionLabelChip(item) && ( -
+
{optionLabelChip(item)}
)} diff --git a/src/Components/Form/FormFields/Autocomplete.tsx b/src/Components/Form/FormFields/Autocomplete.tsx index 67b1224cddf..2fbfeacfda4 100644 --- a/src/Components/Form/FormFields/Autocomplete.tsx +++ b/src/Components/Form/FormFields/Autocomplete.tsx @@ -167,14 +167,14 @@ export const Autocomplete = (props: AutocompleteProps) => { /> {!props.disabled && ( -
+
{value?.icon} {value && !props.isLoading && !props.required && (
{ e.preventDefault(); props.onChange(undefined); @@ -199,7 +199,7 @@ export const Autocomplete = (props: AutocompleteProps) => { {filteredOptions.length === 0 && ( -
+
No options found
)} @@ -222,10 +222,10 @@ export const Autocomplete = (props: AutocompleteProps) => { className={classNames( "text-sm font-normal", option.disabled - ? "text-gray-700" + ? "text-secondary-700" : active ? "text-primary-200" - : "text-gray-700", + : "text-secondary-700", )} > {option.description} diff --git a/src/Components/Form/FormFields/AutocompleteMultiselect.tsx b/src/Components/Form/FormFields/AutocompleteMultiselect.tsx index 7857e96889d..6341d2eb231 100644 --- a/src/Components/Form/FormFields/AutocompleteMultiselect.tsx +++ b/src/Components/Form/FormFields/AutocompleteMultiselect.tsx @@ -135,7 +135,7 @@ export const AutocompleteMutliSelect = ( ref={comboButtonRef} className="absolute inset-y-0 right-0 flex items-center pr-2" > -
+
{props.isLoading ? ( ) : ( @@ -205,10 +205,10 @@ export const AutocompleteMutliSelect = ( className={classNames( "text-sm font-normal", option.disabled - ? "text-gray-700" + ? "text-secondary-700" : active ? "text-primary-200" - : "text-gray-700", + : "text-secondary-700", )} > {option.description} diff --git a/src/Components/Form/FormFields/FormField.tsx b/src/Components/Form/FormFields/FormField.tsx index d3e77aabf52..a6829af9ae4 100644 --- a/src/Components/Form/FormFields/FormField.tsx +++ b/src/Components/Form/FormFields/FormField.tsx @@ -16,7 +16,7 @@ export const FieldLabel = (props: LabelProps) => {
DateSpo2TemperatureDateSpo2 + Temperature +
+ {formatDateTime( consultationData.last_daily_round.modified_date, )} + {consultationData.last_daily_round.ventilator_spo2 || "-"} + {consultationData.last_daily_round.temperature || "-"}
+ --- + --- + ---
+
@@ -87,7 +87,7 @@ export default function ClaimDetailCard({ claim }: IProps) { @@ -95,14 +95,16 @@ export default function ClaimDetailCard({ claim }: IProps) { {claim.items?.map((item) => ( - + - @@ -113,17 +115,17 @@ export default function ClaimDetailCard({ claim }: IProps) { - @@ -133,17 +135,17 @@ export default function ClaimDetailCard({ claim }: IProps) { -
Items Price
-
{item.name}
-
{item.id}
+
+ {item.name} +
+
{item.id}
+ {formatCurrency(item.price)}
Total Claim Amount Total Claim Amount + {claim.total_claim_amount && formatCurrency(claim.total_claim_amount)} Total Amount Approved Total Amount Approved + {claim.total_amount_approved ? formatCurrency(claim.total_amount_approved) : "NA"} diff --git a/src/Components/HCX/ClaimsItemsBuilder.tsx b/src/Components/HCX/ClaimsItemsBuilder.tsx index c65d7f84c5c..73ca2305030 100644 --- a/src/Components/HCX/ClaimsItemsBuilder.tsx +++ b/src/Components/HCX/ClaimsItemsBuilder.tsx @@ -55,7 +55,7 @@ export default function ClaimsItemsBuilder(props: Props) { return (
@@ -94,7 +94,7 @@ export default function ClaimsItemsBuilder(props: Props) { Select a policy to add items diff --git a/src/Components/HCX/InsuranceDetailsBuilder.tsx b/src/Components/HCX/InsuranceDetailsBuilder.tsx index f75fa387a5f..1e401410ff0 100644 --- a/src/Components/HCX/InsuranceDetailsBuilder.tsx +++ b/src/Components/HCX/InsuranceDetailsBuilder.tsx @@ -57,7 +57,7 @@ export default function InsuranceDetailsBuilder(props: Props) {
    {props.value?.length === 0 && ( - + No insurance details added )} @@ -100,7 +100,7 @@ const InsuranceDetailEditCard = ({ : undefined; return ( -
    +
    Policy diff --git a/src/Components/Medicine/AdministerMedicine.tsx b/src/Components/Medicine/AdministerMedicine.tsx index 51bd2cf2617..0fcf7864a8a 100644 --- a/src/Components/Medicine/AdministerMedicine.tsx +++ b/src/Components/Medicine/AdministerMedicine.tsx @@ -43,7 +43,7 @@ export default function AdministerMedicine({ prescription, ...props }: Props) { } title={t("administer_medicine")} description={ -
    +
    Last administered {" "} diff --git a/src/Components/Medicine/ManagePrescriptions.tsx b/src/Components/Medicine/ManagePrescriptions.tsx index f581f3e2121..fd335ce19fb 100644 --- a/src/Components/Medicine/ManagePrescriptions.tsx +++ b/src/Components/Medicine/ManagePrescriptions.tsx @@ -15,7 +15,7 @@ export default function ManagePrescriptions() { className="mx-auto flex w-full max-w-4xl flex-col gap-10 rounded bg-white p-6 transition-all sm:rounded-xl sm:p-12" id="medicine-preview" > -
    +

    {t("prescription_medications")} diff --git a/src/Components/Medicine/MedibaseAutocompleteFormField.tsx b/src/Components/Medicine/MedibaseAutocompleteFormField.tsx index 71a01b23298..55361d20258 100644 --- a/src/Components/Medicine/MedibaseAutocompleteFormField.tsx +++ b/src/Components/Medicine/MedibaseAutocompleteFormField.tsx @@ -91,10 +91,10 @@ const OptionDescription = ({ medicine }: { medicine: MedibaseMedicine }) => { const OptionChip = (props: { name?: string; value: string }) => { return (
    - + {props.name && props.name + ":"} - {props.value} + {props.value}
    ); }; diff --git a/src/Components/Medicine/MedicineAdministration.tsx b/src/Components/Medicine/MedicineAdministration.tsx index 650f720caa0..7a8fb663704 100644 --- a/src/Components/Medicine/MedicineAdministration.tsx +++ b/src/Components/Medicine/MedicineAdministration.tsx @@ -111,7 +111,7 @@ export default function MedicineAdministration(props: Props) { readonly selected={shouldAdminister[index]} > -
    +
    -
    +
    {" "} {t("last_administered")} diff --git a/src/Components/Medicine/MedicineAdministrationSheet/AdministrationEventCell.tsx b/src/Components/Medicine/MedicineAdministrationSheet/AdministrationEventCell.tsx index 0949189d96d..81e92e32a58 100644 --- a/src/Components/Medicine/MedicineAdministrationSheet/AdministrationEventCell.tsx +++ b/src/Components/Medicine/MedicineAdministrationSheet/AdministrationEventCell.tsx @@ -47,7 +47,7 @@ export default function AdministrationEventCell({ className="w-full md:max-w-4xl" show={showTimeline} > -
    +
    Administrations on{" "} {formatDateTime(start, "DD/MM/YYYY")} @@ -87,7 +87,9 @@ export default function AdministrationEventCell({ // Check if cell belongs to after prescription.created_date if (dayjs(start).isAfter(prescription.created_date)) { - return ; + return ( + + ); } // Check if cell belongs to a discontinued prescription @@ -105,7 +107,7 @@ export default function AdministrationEventCell({ "text-xl", dayjs(prescription.discontinued_date).isBetween(start, end) ? "text-danger-700" - : "text-gray-400", + : "text-secondary-400", )} /> diff --git a/src/Components/Medicine/MedicineAdministrationSheet/AdministrationEventSeperator.tsx b/src/Components/Medicine/MedicineAdministrationSheet/AdministrationEventSeperator.tsx index a83fa38bd9c..fdaa48cfea2 100644 --- a/src/Components/Medicine/MedicineAdministrationSheet/AdministrationEventSeperator.tsx +++ b/src/Components/Medicine/MedicineAdministrationSheet/AdministrationEventSeperator.tsx @@ -4,7 +4,7 @@ export default function AdministrationEventSeperator({ date }: { date: Date }) { // Show date if it's 00:00 if (date.getHours() === 0) { return ( -
    +

    {formatDateTime(date, "DD/MM")}

    @@ -13,7 +13,7 @@ export default function AdministrationEventSeperator({ date }: { date: Date }) { } return ( -
    +
    {/*

    {formatDateTime(date, "HH")}

    */}
    diff --git a/src/Components/Medicine/MedicineAdministrationSheet/AdministrationTable.tsx b/src/Components/Medicine/MedicineAdministrationSheet/AdministrationTable.tsx index 49a071b744b..f7dbacd5780 100644 --- a/src/Components/Medicine/MedicineAdministrationSheet/AdministrationTable.tsx +++ b/src/Components/Medicine/MedicineAdministrationSheet/AdministrationTable.tsx @@ -24,9 +24,9 @@ export default function MedicineAdministrationTable({ return (
    - + - - + {prescriptions.map((obj, index) => ( {props.theads.map((item) => { return ( - ); })} {props.actions && ( - )} @@ -54,7 +54,7 @@ export default function ResponsiveMedicineTable(props: { props.onClick && props.onClick(med)} @@ -65,14 +65,14 @@ export default function ResponsiveMedicineTable(props: { idx === props.maxWidthColumn ) { return ( - ); } return ( - ); @@ -110,11 +110,11 @@ export default function ResponsiveMedicineTable(props: { className={ props.list.length - 1 === index ? "bg-white p-5 " - : "border-b border-b-gray-400 bg-white p-5" + : "border-b border-b-secondary-400 bg-white p-5" } key={index} > -
    +
    {props.objectKeys.map((key, i) => { if (i !== 0 && i !== props.objectKeys.length - 1) diff --git a/src/Components/Notifications/NoticeBoard.tsx b/src/Components/Notifications/NoticeBoard.tsx index 7835f8576b7..db285eb9d6a 100644 --- a/src/Components/Notifications/NoticeBoard.tsx +++ b/src/Components/Notifications/NoticeBoard.tsx @@ -24,13 +24,13 @@ export const NoticeBoard = () => { >
    {item.message}
    -
    +
    {`${item.caused_by.first_name} ${item.caused_by.last_name}`} -{" "} {item.caused_by.user_type}
    -
    +
    {t("on")}: {formatDateTime(item.created_date)}
    @@ -42,8 +42,8 @@ export const NoticeBoard = () => { notices = (
    - -
    + +
    No Notice Available
    diff --git a/src/Components/Notifications/NotificationsList.tsx b/src/Components/Notifications/NotificationsList.tsx index c9e6720e6ad..2de3ae7922c 100644 --- a/src/Components/Notifications/NotificationsList.tsx +++ b/src/Components/Notifications/NotificationsList.tsx @@ -95,8 +95,8 @@ const NotificationTile = ({ setShowNotifications(false); }} className={classNames( - "relative cursor-pointer rounded px-4 py-5 transition duration-200 ease-in-out hover:bg-gray-200 focus:bg-gray-200 md:rounded-lg lg:px-8", - result.read_at && "text-gray-500", + "relative cursor-pointer rounded px-4 py-5 transition duration-200 ease-in-out hover:bg-secondary-200 focus:bg-secondary-200 md:rounded-lg lg:px-8", + result.read_at && "text-secondary-500", )} >
    @@ -416,7 +416,7 @@ export default function NotificationsList({ } else if (data && data.length === 0) { manageResults = (
    -
    +
    {t("no_results_found")}
    diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx index 97ee038362b..89367c6eb37 100644 --- a/src/Components/Patient/DailyRounds.tsx +++ b/src/Components/Patient/DailyRounds.tsx @@ -707,7 +707,7 @@ export const DailyRounds = (props: any) => { {state.form.rounds_type === "DOCTORS_LOG" && ( <> -
    +

    {t("diagnosis")} @@ -716,7 +716,7 @@ export const DailyRounds = (props: any) => { {diagnoses ? ( ) : ( -
    +
    Fetching existing diagnosis of patient...
    )} diff --git a/src/Components/Patient/FileUpload.tsx b/src/Components/Patient/FileUpload.tsx index 6f63a697229..d8f865d3fef 100644 --- a/src/Components/Patient/FileUpload.tsx +++ b/src/Components/Patient/FileUpload.tsx @@ -858,7 +858,7 @@ export const FileUpload = (props: FileUploadProps) => {
    ) : ( @@ -880,7 +880,7 @@ export const FileUpload = (props: FileUploadProps) => {

    )} @@ -1596,7 +1596,7 @@ export const FileUpload = (props: FileUploadProps) => {
    )} {file && ( -
    +
    {file?.name} @@ -272,7 +272,7 @@ export default function SampleViewAdmin() { @@ -300,7 +300,7 @@ export default function SampleViewAdmin() { } else if (sampeleData?.count === 0) { manageSamples = (
    -
    +
    No Sample Tests Found
    diff --git a/src/Components/Patient/UpdateStatusDialog.tsx b/src/Components/Patient/UpdateStatusDialog.tsx index 8fca8815248..98c72073437 100644 --- a/src/Components/Patient/UpdateStatusDialog.tsx +++ b/src/Components/Patient/UpdateStatusDialog.tsx @@ -214,7 +214,7 @@ const UpdateStatusDialog = (props: Props) => { ) : (
    -
    +
    {t("medicine")} @@ -63,8 +63,8 @@ export default function MedicineAdministrationTable({ className={classNames( "leading-none", start.getHours() === 0 - ? "text-base font-bold text-gray-800" - : "text-sm font-semibold text-gray-700", + ? "text-base font-bold text-secondary-800" + : "text-sm font-semibold text-secondary-700", )} > {formatDateTime( @@ -96,7 +96,7 @@ export default function MedicineAdministrationTable({
    setShowDetails(true)} >
    @@ -186,7 +186,9 @@ export default function MedicineAdministrationTableRow({ {prescription.medicine_object?.name ?? @@ -194,7 +196,7 @@ export default function MedicineAdministrationTableRow({ {prescription.discontinued && ( - + {t("discontinued")} )} @@ -206,7 +208,7 @@ export default function MedicineAdministrationTableRow({ )}
    -
    +
    {prescription.dosage_type !== "TITRATED" ? (

    {prescription.base_dosage}

    ) : ( @@ -237,7 +239,7 @@ export default function MedicineAdministrationTableRow({ {!data?.results ? ( ) : ( { setShowDiscontinued(!showDiscontinued)} > @@ -178,7 +178,7 @@ export default MedicineAdministrationSheet; const NoPrescriptions = ({ prn }: { prn: boolean }) => { return ( -
    +

    {prn diff --git a/src/Components/Medicine/MedicinePrescriptionSummary.tsx b/src/Components/Medicine/MedicinePrescriptionSummary.tsx index b77acc090c4..35988e3d2d2 100644 --- a/src/Components/Medicine/MedicinePrescriptionSummary.tsx +++ b/src/Components/Medicine/MedicinePrescriptionSummary.tsx @@ -57,13 +57,13 @@ export const MedicinePrescriptionSummary = ({ return (
    -

    {t("summary")}

    +

    {t("summary")}

    {medicinesList && medicinesList.length > 0 ? ( medicinesList?.map((med: MedibaseMedicine) => (
    {med.name}

    {prescription.discontinued && ( - + {t("discontinued")} )} @@ -232,7 +232,7 @@ export default function PrescriptionDetailCard({ )}
    )} -
    +
    Prescribed - +
    {props.children ? ( {props.children} ) : ( - + {t("not_specified")} )} diff --git a/src/Components/Medicine/PrescriptionsTable.tsx b/src/Components/Medicine/PrescriptionsTable.tsx index f842ccc654b..8feaa0b151f 100644 --- a/src/Components/Medicine/PrescriptionsTable.tsx +++ b/src/Components/Medicine/PrescriptionsTable.tsx @@ -69,11 +69,11 @@ export default function PrescriptionsTable({ )}
    -
    +
    {is_prn ? "PRN Prescriptions" : "Prescriptions"} -
    +
    {lastModified && formatDateTime(lastModified)} @@ -83,7 +83,7 @@ export default function PrescriptionsTable({
    -
    +
    {data?.results.length === 0 && ( -
    +
    {t("no_data_found")}
    )} diff --git a/src/Components/Medicine/ResponsiveMedicineTables.tsx b/src/Components/Medicine/ResponsiveMedicineTables.tsx index 74623365a1c..073511618f1 100644 --- a/src/Components/Medicine/ResponsiveMedicineTables.tsx +++ b/src/Components/Medicine/ResponsiveMedicineTables.tsx @@ -37,13 +37,13 @@ export default function ResponsiveMedicineTable(props: {
    + {item} + {props.actionLabel || ""}
    + {med[key]} + {med[key]}