From e87de4fbb59d78d8dbff4d13c0e611cd05e1480e Mon Sep 17 00:00:00 2001 From: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> Date: Tue, 3 Oct 2023 08:21:03 +0530 Subject: [PATCH 01/65] Fix Flaky Cypress test in Facility Module (#6384) * fix random facility page failure * fix random facility page failure --- cypress/e2e/facility_spec/locations.cy.ts | 1 + cypress/pageobject/Facility/FacilityCreation.ts | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cypress/e2e/facility_spec/locations.cy.ts b/cypress/e2e/facility_spec/locations.cy.ts index d5b3cc9990f..cac9adbdde1 100644 --- a/cypress/e2e/facility_spec/locations.cy.ts +++ b/cypress/e2e/facility_spec/locations.cy.ts @@ -13,6 +13,7 @@ describe("Location Management Section", () => { cy.intercept("GET", "**/api/v1/facility/**").as("getFacilities"); cy.get("[id='facility-details']").first().click(); cy.wait("@getFacilities").its("response.statusCode").should("eq", 200); + cy.get("h1.text-3xl.font-bold", { timeout: 10000 }).should("be.visible"); cy.get("#manage-facility-dropdown button").should("be.visible"); cy.get("[id='manage-facility-dropdown']").scrollIntoView().click(); cy.get("[id=location-management]").click(); diff --git a/cypress/pageobject/Facility/FacilityCreation.ts b/cypress/pageobject/Facility/FacilityCreation.ts index 42ec6d8fd7c..e0a572ccaec 100644 --- a/cypress/pageobject/Facility/FacilityCreation.ts +++ b/cypress/pageobject/Facility/FacilityCreation.ts @@ -106,6 +106,7 @@ class FacilityPage { } clickManageFacilityDropdown() { + cy.get("h1.text-3xl.font-bold", { timeout: 20000 }).should("be.visible"); cy.get("#manage-facility-dropdown button").scrollIntoView(); cy.get("#manage-facility-dropdown button") .contains("Manage Facility") @@ -198,8 +199,6 @@ class FacilityPage { cy.intercept("GET", "**/api/v1/facility/**").as("getManagePage"); cy.go("back"); cy.wait("@getManagePage").its("response.statusCode").should("eq", 200); - cy.get("#manage-facility-dropdown").scrollIntoView(); - cy.get("#manage-facility-dropdown").should("exist"); } verifyfacilityviewassetredirection() { From b7fc53c670bb85e68631f8ce3b02defcda675ae7 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Tue, 3 Oct 2023 12:15:41 +0530 Subject: [PATCH 02/65] Revert "Prescriptions: Shrink discontinued prescriptions + Flip MAR timeline + Freeze primary columns in horizontal scroll (#6282)" (#6386) This reverts commit 5009a86abfb96e9b21f8371635176e4c3f73c5a7. --- src/Common/hooks/useRangePagination.ts | 32 +-- .../PrescriptionAdministrationsTable.tsx | 266 +++++++----------- src/Redux/actions.tsx | 2 +- 3 files changed, 120 insertions(+), 180 deletions(-) diff --git a/src/Common/hooks/useRangePagination.ts b/src/Common/hooks/useRangePagination.ts index e6bbe9f573e..7652ae546c1 100644 --- a/src/Common/hooks/useRangePagination.ts +++ b/src/Common/hooks/useRangePagination.ts @@ -9,18 +9,17 @@ interface Props { bounds: DateRange; perPage: number; slots?: number; - snapToLatest?: boolean; - reverse?: boolean; + defaultEnd?: boolean; } const useRangePagination = ({ bounds, perPage, ...props }: Props) => { const [currentRange, setCurrentRange] = useState( - getInitialBounds(bounds, perPage, props.snapToLatest) + getInitialBounds(bounds, perPage, props.defaultEnd) ); useEffect(() => { - setCurrentRange(getInitialBounds(bounds, perPage, props.snapToLatest)); - }, [bounds, perPage, props.snapToLatest]); + setCurrentRange(getInitialBounds(bounds, perPage, props.defaultEnd)); + }, [bounds, perPage, props.defaultEnd]); const next = () => { const { end } = currentRange; @@ -63,24 +62,17 @@ const useRangePagination = ({ bounds, perPage, ...props }: Props) => { } const slots: DateRange[] = []; - const { start, end } = currentRange; + const { start } = currentRange; const delta = perPage / props.slots; for (let i = 0; i < props.slots; i++) { - if (props.snapToLatest) { - slots.push({ - start: new Date(end.valueOf() - delta * (i - 1)), - end: new Date(end.valueOf() - delta * i), - }); - } else { - slots.push({ - start: new Date(start.valueOf() + delta * i), - end: new Date(start.valueOf() + delta * (i + 1)), - }); - } + slots.push({ + start: new Date(start.valueOf() + delta * i), + end: new Date(start.valueOf() + delta * (i + 1)), + }); } - return props.reverse ? slots.reverse() : slots; + return slots; }, [currentRange, props.slots, perPage]); return { @@ -98,7 +90,7 @@ export default useRangePagination; const getInitialBounds = ( bounds: DateRange, perPage: number, - snapToLatest?: boolean + defaultEnd?: boolean ) => { const deltaBounds = bounds.end.valueOf() - bounds.start.valueOf(); @@ -106,7 +98,7 @@ const getInitialBounds = ( return bounds; } - if (snapToLatest) { + if (defaultEnd) { return { start: new Date(bounds.end.valueOf() - perPage), end: bounds.end, diff --git a/src/Components/Medicine/PrescriptionAdministrationsTable.tsx b/src/Components/Medicine/PrescriptionAdministrationsTable.tsx index 470caa1042b..81282126d7c 100644 --- a/src/Components/Medicine/PrescriptionAdministrationsTable.tsx +++ b/src/Components/Medicine/PrescriptionAdministrationsTable.tsx @@ -47,10 +47,6 @@ export default function PrescriptionAdministrationsTable({ const { t } = useTranslation(); const [state, setState] = useState(); - - const [showDiscontinued, setShowDiscontinued] = useState(false); - const [discontinuedCount, setDiscontinuedCount] = useState(); - const pagination = useRangePagination({ bounds: state?.administrationsTimeBounds ?? { start: new Date(), @@ -58,8 +54,7 @@ export default function PrescriptionAdministrationsTable({ }, perPage: 24 * 60 * 60 * 1000, slots: 24, - snapToLatest: true, - reverse: true, + defaultEnd: true, }); const [showBulkAdminister, setShowBulkAdminister] = useState(false); @@ -69,13 +64,8 @@ export default function PrescriptionAdministrationsTable({ ); const refetch = useCallback(async () => { - const filters = { - is_prn: prn, - prescription_type: "REGULAR", - }; - const res = await dispatch( - list(showDiscontinued ? filters : { ...filters, discontinued: false }) + list({ is_prn: prn, prescription_type: "REGULAR" }) ); setState({ @@ -84,14 +74,7 @@ export default function PrescriptionAdministrationsTable({ ), administrationsTimeBounds: getAdministrationBounds(res.data.results), }); - - if (showDiscontinued === false) { - const discontinuedRes = await dispatch( - list({ ...filters, discontinued: true, limit: 0 }) - ); - setDiscontinuedCount(discontinuedRes.data.count); - } - }, [consultation_id, showDiscontinued, dispatch]); + }, [consultation_id, dispatch]); useEffect(() => { refetch(); @@ -158,22 +141,17 @@ export default function PrescriptionAdministrationsTable({ } /> -
- +
+
- + + )) - : pagination.slots - ?.map(({ start, end }, index) => ( - - )) - .reverse()} + : pagination.slots?.map(({ start, end }, index) => ( + + ))}
-
- {t("medicine")} - -

Dosage &

-

- {!state?.prescriptions[0]?.is_prn - ? "Frequency" - : "Indicator"} -

-
-
+
{t("medicine")} +

Dosage &

+

+ {!state?.prescriptions[0]?.is_prn ? "Frequency" : "Indicator"} +

@@ -184,10 +162,8 @@ export default function PrescriptionAdministrationsTable({ border className="mx-2 px-1" variant="secondary" - disabled={!pagination.hasNext} - onClick={pagination.next} - tooltip="Next 24 hours" - tooltipClassName="tooltip-bottom -translate-x-1/2 text-xs" + disabled={!pagination.hasPrevious} + onClick={pagination.previous} > @@ -201,26 +177,24 @@ export default function PrescriptionAdministrationsTable({

-

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

-

{formatDateTime(end, "HH:mm")}

- - - Administration(s) between -
- {formatTime(start)} and{" "} - {formatTime(end)} -
- on {formatDate(start)} -
-
+

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

+

{formatDateTime(start, "HH:mm")}

+ + + Administration(s) between +
+ {formatTime(start)} and{" "} + {formatTime(end)} +
+ on {formatDate(start)} +
+
@@ -255,23 +227,6 @@ export default function PrescriptionAdministrationsTable({
- {showDiscontinued === false && !!discontinuedCount && ( - setShowDiscontinued(true)} - > - - - - Show {discontinuedCount} other discontinued - prescription(s) - - - - )} - {state?.prescriptions.length === 0 && (
@@ -328,7 +283,12 @@ const PrescriptionRow = ({ prescription, ...props }: PrescriptionRowProps) => { }, [prescription.id, dispatch, props.intervals]); return ( - <> + {showDiscontinue && ( {
)} - setShowDetails(true)} > - setShowDetails(true)} - > -
-
- - {prescription.medicine_object?.name ?? - prescription.medicine_old} - - - {prescription.discontinued && ( - - {t("discontinued")} - - )} - - {prescription.route && ( - - {t(prescription.route)} - - )} -
+
+ + {prescription.medicine_object?.name ?? prescription.medicine_old} + -
-

{prescription.dosage}

-

- {!prescription.is_prn - ? t("PRESCRIPTION_FREQUENCY_" + prescription.frequency) - : prescription.indicator} -

-
-
- + {prescription.discontinued && ( + + {t("discontinued")} + + )} - - {/* Administration Cells */} - {props.intervals - .map(({ start, end }, index) => ( - - {administrations === undefined ? ( - - ) : ( - - )} - - )) - .reverse()} - - - {/* Action Buttons */} - - setShowAdminister(true)} - > - {t("administer")} - + {prescription.route && ( + + {t(prescription.route)} + + )} +
+ + + +

{prescription.dosage}

+

+ {!prescription.is_prn + ? t("PRESCRIPTION_FREQUENCY_" + prescription.frequency) + : prescription.indicator} +

+ + + + {/* Administration Cells */} + {props.intervals.map(({ start, end }, index) => ( + + {administrations === undefined ? ( + + ) : ( + + )} - - + ))} + + + {/* Action Buttons */} + + setShowAdminister(true)} + > + {t("administer")} + + + ); }; diff --git a/src/Redux/actions.tsx b/src/Redux/actions.tsx index 1a5bd7e4fb1..6e0d91fc59d 100644 --- a/src/Redux/actions.tsx +++ b/src/Redux/actions.tsx @@ -1003,7 +1003,7 @@ export const PrescriptionActions = (consultation_external_id: string) => { const pathParams = { consultation_external_id }; return { - list: (query?: Record) => { + list: (query?: Partial) => { let altKey; if (query?.is_prn !== undefined) { altKey = query?.is_prn From 8589460e826b3d3262dd2d22ee0d623846f0b26d Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Tue, 3 Oct 2023 20:22:47 +0530 Subject: [PATCH 03/65] Refactor Asset Model Import Formatting (#6388) * Refactor Asset Model Import Formatting * fixes to warranty_amc_end_of_validity * Fix asset import file for cypress --- cypress/fixtures/sampleAsset.xlsx | Bin 45398 -> 35942 bytes src/Common/constants.tsx | 45 +++++++++++++++------ src/Components/Assets/AssetImportModal.tsx | 20 +++++---- 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/cypress/fixtures/sampleAsset.xlsx b/cypress/fixtures/sampleAsset.xlsx index f8e234ce4477934a0426a088783ba4988500f7aa..49421f62baca164b5908d5b182ce11f8776aefb3 100644 GIT binary patch literal 35942 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%0DwfYI@bY)VNaG;z9w`%`XRb#^tALRKi~d%t+gfXc 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 OHExE3z-aI91n~g)q&wgM literal 45398 zcmeHw30TbA8-EvBvV=;M43QR;NKrIMDeViW4uqW&`q=Osh-A$8fEXy-iorC5a3knQ+iPnE+0uwzn5H+N>OEfK0OeSD# zy|wGI6%>;1Eva0S8kpqMx<4BgzN&*+W%GbKEFCO#934)5*BW!NnV^`f-8Z!|7of=j z1)NV>A+5d@o78@`9YHvelY`@oAO{D}kHzSPr6paFcHKv`9q_WeREY~c2@~3}JnuG{ z7usWGkk_Mx+@nN{%r!61P!kda*p|#SQ@~??l@-uiY_s-FnBFE_YR9o%7E`b31R88oaq~ zvs|^3K!;NB6uYyIMU zy}Z%{^0x3QK`|6Kzkrj+V!zKXz5}a+-&0>OPw@ z%$0kj*;sw-EHWXz{?P|xktSm-E~~yX;b7m<%LwBa+==l?p!`C=<(jY5+c)N2f4Fj& z@73*i>(&;6YKFYNIRtD9PCzZ;A{2)~jmjZ+W5k!F~HGF2AjuuHMHi z){BliUcT9}OBqKwJzCjwTLtR#3($YMhDjeM$m6m+b>B z(kd*H&%90%@9XniiMJH%TDg6O<-DC&H>b~V(3IYK3dL7{+J)2L=^tG;_;eL6z1;V9 zsjGz}!M<6tExy#JvCv;2IvdD-h(b^^wM#-8u5oVqJe7k1le!^qkl7e)Nut}=X>DRcQy zeA~JUJ3m-TH`c!#FCj{bY;fF#SnYChx#qs&8~)wH+pX4WEchrlmtPs3i%0`a|kNH)3S$%rz+upZ93l*Bp1QUHoAGeHp%OTmLw5uqJ!pY{{1$ zeFjLryr$UmrfW~>Y`%WFO!ayJ@5io%dr=8yMzW>Pm5)ELU#OGg_KG8&x*3vPePnz0>V1+75mJA=_QH|d&MOgnjQd)6qYj*uD=M@uT#>h@bWh$XmNwsn=XC z(y5j*!nsC!#C;1^V#I_OMSfV$X|S6U>u2at{bKgs-6CDH{U1gMJ-J+0_yHLg|7Ssl z0;f=_%nN0>jbLWQN8;%F*}JxsIT_Yb7Cmjw!6jO5;MYno-%Ut9mchT=$~LX#)P3Pa znqvNs;EPWwzIpci1V^<5bZ@^;>I0ON%^AmdBz5TJ4pF=W>~9DWAWd!)Z1g zIasg!dKMq*xP79=VB7VTOU~ZrYcp!ac}E^e6`OJY^c6KHWc9WMb9(#!h)Hig=R|ya z&%knH^j5L0SOW{u!)OJb*BNhRyB5i1Z*@o$H__A?;Y5XVVS;1olybR?_YXR)Jhkxh(hA$ro3bPk@zjv$8*2Xvy zv7yK_VV)1TOF~0Gtq_?fU>3clGc;SWtwTs{?gkiCgLN>nz)F6Mr@xMl`+R}5Av+!G=IhG|6zhr? zJaR_l3eD2vz&?8WlEdv*{vH4JGf&;kRJ{U?3c~Z_+QnCPAa&N(oiVKmp4rfg$tN{; zSh?CfF3T1FGb%Rf*o(CRHTgP+N|j885)ye!NJ3qInqg-8Z0Qh9&R2~2l;G)V@>)&* zO{7MLOnIr*o)zIw!ghvfwD0HSzm{qM6D60r-;m*(81HVK^HrP7d+MUe&Cv6JSFkh0vK>~tWizbtM zbH@k-&rz}ij+{^MbR!lRBE0&`VP3=CNobNUdLm4Unnc}7326&KcvACg(PP84IMN6= zVWK>QI#NE0pw!~Xo#^qAEb1^Bjv6fIMoiqM=8vKWKf}p=`7r925ZnaCkUCV}i=$BZ z36%aY_{5mD*I>5`j!N`}QAe`S6Q4)X6Fp%`XtMYai9jBhs5S(0DV+q$$5J?QEUs7O ztS2dqFfn3CxZr>uuE0@Sx#1Jm#V&-2o=%w8L=&2-jU%^=lCT7-jb$Tfs0U3Rgi%YA za>-;E+N7TwM{aaLk7a19<0hI%am2WZ0QA^MRuc823p|&I(5_8Fr}t8Z#}$A?R6rO@w(GSKAWFD__OErBw~jU#o#a$^aoelna&DeQd%8ju5O zK~P#jqZ6MAlwm0*6megR5IRy@|jdYAzr~q z4sjE_NH{NI*hD901l6BkOYMW>#(_xU$1oU`GSuSI9DtyH48tUn@&mLd%3TN)9AU!M zKM6gY??4@u8}7thBXbidefb$UDZ)gnFDxHuItDjJa_}0>g5kyoLl6^q>O@NbrdIn( z?pQ5`I*{5ss7>fz*u$3_s9ayW7!nrQvDyHFF3!lH*e z(enwO)Hs;bL`w;cep{e;evU6lr0D4>sM;_*; z4z(a6(5G`u>;tNXCnh=&QUG9aXbPn;WC9MS4ocz1d>wMBYbYHa)Y6KY2=q}8N62KD`!Gdo)1tLT5LBMj^Q^98T`+Yrt37I-u)&yUA_oqxtDch@HJ{Rp{0% z(wJLsixf%e=MO)hA)E? zyv8C5<$B?bS{Slt7NxKiFQrvmI0(WUuMxaN#aT9l_yT_Wd zwC7XO`Uup)mQpE2GQK6|G7J|3Ur5ftc)jx??uS!?deQHwV!kX;;4@WPLEgJM#m!st6 z`&p>QMXYu-V7#R8s$H+~7lpLWLd916n?g?y>TQQ!*r39N(V`*6B?L3CfGC%u-sJ3j z;-b$Z6lucP?Z9r7I?DGfsjkoyM!ek%3X{neUcP;+?lg%uE0pyH4lNoNy5(}1aQ{64 z-l_oG`Xt98w)agBjC32O1RHDtg|pFREJspEDxCOWp@JRVG%VE6VgVa;YPaypMVD28 zo(jG2uyn^<>fManU#alP<#3Z3%6Gt%dK=?eRy2|uovY;G)n(ze5LK203J+7HaZ0WM z8ofOILan2!?!FU?N)_UJb&i7GUI|!H-<0vrg0&wNzEJY@g-&;Okd6 z7Wv)8d>-Vcbj{(WNmf85D@431X}LV&8mTD!=ew60`;P0)D=s=8uew8bowlr+HBDlN zH*!Gckf`=hF^|vEc~R?+3vUTJpsKRWQn}=^joTCB1A<`^m0GZ_keHP7ZjojYk8Z23 z-X_fxc*kJxmM5z>=xN<74?^lU)ENYxB_3

3{GwPx#=zj(sy?FWW-|fEUs= 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 diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index f64a9fa27ae..e51494f6bac 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -896,7 +896,7 @@ export const XLSXAssetImportSchema = { Class: { prop: "asset_class", type: String, - oneOf: ["HL7MONITOR", "ONVIF"], + oneOf: ["HL7MONITOR", "ONVIF", "VENTILATOR", ""], }, Description: { prop: "description", type: String }, "Working Status": { @@ -908,7 +908,7 @@ export const XLSXAssetImportSchema = { } else if (status === "NOT WORKING") { return false; } else { - throw new Error("Invalid Working Status"); + throw new Error("Invalid Working Status: " + status); } }, required: true, @@ -917,6 +917,7 @@ export const XLSXAssetImportSchema = { prop: "not_working_reason", type: String, }, + "Serial Number": { prop: "serial_number", type: String }, "QR Code ID": { prop: "qr_code_id", type: String }, Manufacturer: { prop: "manufacturer", type: String }, "Vendor Name": { prop: "vendor_name", type: String }, @@ -925,10 +926,11 @@ export const XLSXAssetImportSchema = { prop: "support_email", type: String, parse: (email: string) => { + if (!email) return null; const isValid = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/.test(email); if (!isValid) { - throw new Error("Invalid Support Email"); + throw new Error("Invalid Support Email: " + email); } return email; @@ -938,23 +940,38 @@ export const XLSXAssetImportSchema = { prop: "support_phone", type: String, parse: (phone: number | string) => { - phone = "+91" + String(phone); - if (!PhoneNumberValidator(["support"])(phone) === undefined) { - throw new Error("Invalid Support Phone Number"); + phone = String(phone); + if (phone.length === 10 && !phone.startsWith("1800")) { + phone = "+91" + phone; + } + if (phone.startsWith("91") && phone.length === 12) { + phone = "+" + phone; + } + if (phone.startsWith("+911800")) { + phone = "1800" + phone.slice(6); + } + if ( + PhoneNumberValidator(["mobile", "landline", "support"])(phone) !== + undefined + ) { + throw new Error("Invalid Support Phone Number: " + phone); } return phone ? phone : undefined; }, required: true, }, - "Warrenty End Date": { + "Warranty End Date": { prop: "warranty_amc_end_of_validity", type: String, parse: (date: string) => { - const parsed = new Date(date); + if (!date) return null; + const parts = date.split("-"); + const reformattedDateStr = `${parts[2]}-${parts[1]}-${parts[0]}`; + const parsed = new Date(reformattedDateStr); if (String(parsed) === "Invalid Date") { - throw new Error("Invalid Warrenty End Date"); + throw new Error("Invalid Warranty End Date:" + date); } return dateQueryString(parsed); @@ -964,10 +981,13 @@ export const XLSXAssetImportSchema = { prop: "last_serviced_on", type: String, parse: (date: string) => { - const parsed = new Date(date); + if (!date) return null; + const parts = date.split("-"); + const reformattedDateStr = `${parts[2]}-${parts[1]}-${parts[0]}`; + const parsed = new Date(reformattedDateStr); if (String(parsed) === "Invalid Date") { - throw new Error("Invalid Last Service Date"); + throw new Error("Invalid Last Service Date:" + date); } return dateQueryString(parsed); @@ -981,13 +1001,14 @@ export const XLSXAssetImportSchema = { prop: "local_ip_address", type: String, parse: (ip: string) => { + if (!ip) return null; const isValid = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test( ip ); if (!isValid) { - throw new Error("Invalid Config IP Address"); + throw new Error("Invalid Config IP Address: " + ip); } return ip; diff --git a/src/Components/Assets/AssetImportModal.tsx b/src/Components/Assets/AssetImportModal.tsx index 548df2901ec..7382c7eff4f 100644 --- a/src/Components/Assets/AssetImportModal.tsx +++ b/src/Components/Assets/AssetImportModal.tsx @@ -85,11 +85,11 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { } } } - } catch (e) { + } catch (e: any) { setPreview(undefined); console.log(e); Notification.Error({ - msg: "Invalid file", + msg: "Invalid file: " + e.message, }); setSelectedFile(undefined); } @@ -113,7 +113,7 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { let error = false; for (const asset of preview || []) { - const asset_data = JSON.stringify({ + const asset_data: any = { name: asset.name, asset_type: asset.asset_type, asset_class: asset.asset_class, @@ -129,11 +129,15 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { qr_code_id: asset.qr_code_id, manufacturer: asset.manufacturer, meta: { ...asset.meta }, - warranty_amc_end_of_validity: asset.warranty_amc_end_of_validity, - last_serviced_on: asset.last_serviced_on, note: asset.notes, - cancelToken: { promise: {} }, - }); + }; + + if (asset.last_serviced_on) + asset_data["last_serviced_on"] = asset.last_serviced_on; + + if (asset.warranty_amc_end_of_validity) + asset_data["warranty_amc_end_of_validity"] = + asset.warranty_amc_end_of_validity; const response = await fetch("/api/v1/asset/", { method: "POST", @@ -142,7 +146,7 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { Authorization: "Bearer " + localStorage.getItem(LocalStorageKeys.accessToken), }, - body: asset_data, + body: JSON.stringify(asset_data), }); const data = await response.json(); if (response.status !== 201) { From d53eed9adb58ff6d4aba183815050b644dcee948 Mon Sep 17 00:00:00 2001 From: Aaron Jevil Nazareth <93522968+jevil25@users.noreply.github.com> Date: Wed, 4 Oct 2023 09:12:04 +0530 Subject: [PATCH 04/65] =?UTF-8?q?Refactor:=20replaced=20Dispatch=20to=20us?= =?UTF-8?q?eQuery/Request=20of=20src/Components/Auth/log=E2=80=A6=20(#6333?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor: replaced useDispatch to useQuery of src/Components/Auth/login.tsx * fix:useQuery changed to Request * feat: replaced dispatch with request * fix: types and added pathparams * fix: request body error * Update Login.tsx * change:Tres to Tbody * fixes: response type change * Update package-lock.json --- src/Components/Auth/Login.tsx | 76 ++++++++++++--------------- src/Components/Auth/ResetPassword.tsx | 46 ++++++++-------- src/Redux/api.tsx | 13 +++++ 3 files changed, 70 insertions(+), 65 deletions(-) diff --git a/src/Components/Auth/Login.tsx b/src/Components/Auth/Login.tsx index cb7ea7f08f8..dd6a42d3983 100644 --- a/src/Components/Auth/Login.tsx +++ b/src/Components/Auth/Login.tsx @@ -1,10 +1,9 @@ import { useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; -import { postForgotPassword, postLogin } from "../../Redux/actions"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; import { useTranslation } from "react-i18next"; import ReCaptcha from "react-google-recaptcha"; import * as Notification from "../../Utils/Notifications.js"; -import { get } from "lodash"; import LegendInput from "../../CAREUI/interactive/LegendInput"; import LanguageSelectorLogin from "../Common/LanguageSelectorLogin"; import CareIcon from "../../CAREUI/icons/CareIcon"; @@ -25,7 +24,6 @@ export const Login = (props: { forgot?: boolean }) => { custom_logo_alt, custom_description, } = useConfig(); - const dispatch: any = useDispatch(); const initForm: any = { username: "", password: "", @@ -90,37 +88,35 @@ export const Login = (props: { forgot?: boolean }) => { }; }, []); - const handleSubmit = (e: any) => { + const handleSubmit = async (e: any) => { e.preventDefault(); const valid = validateData(); if (valid) { // replaces button with spinner setLoading(true); - dispatch(postLogin(valid)).then((resp: any) => { - const res = get(resp, "data", null); - const statusCode = get(resp, "status", ""); - if (res && statusCode === 429) { - setCaptcha(true); - // captcha displayed set back to login button - setLoading(false); - } else if (res && statusCode === 200) { - localStorage.setItem(LocalStorageKeys.accessToken, res.access); - localStorage.setItem(LocalStorageKeys.refreshToken, res.refresh); - - if ( - window.location.pathname === "/" || - window.location.pathname === "/login" - ) { - window.location.href = "/facility"; - } else { - window.location.href = window.location.pathname.toString(); - } + const { res, data } = await request(routes.login, { + body: { ...valid }, + }); + if (res && res.status === 429) { + setCaptcha(true); + // captcha displayed set back to login button + setLoading(false); + } else if (res && res.status === 200 && data) { + localStorage.setItem(LocalStorageKeys.accessToken, data.access); + localStorage.setItem(LocalStorageKeys.refreshToken, data.refresh); + if ( + window.location.pathname === "/" || + window.location.pathname === "/login" + ) { + window.location.href = "/facility"; } else { - // error from server set back to login button - setLoading(false); + window.location.href = window.location.pathname.toString(); } - }); + } else { + // error from server set back to login button + setLoading(false); + } } }; @@ -146,26 +142,22 @@ export const Login = (props: { forgot?: boolean }) => { return form; }; - const handleForgetSubmit = (e: any) => { + const handleForgetSubmit = async (e: any) => { e.preventDefault(); const valid = validateForgetData(); if (valid) { setLoading(true); - dispatch(postForgotPassword(valid)).then((resp: any) => { - setLoading(false); - const res = resp && resp.data; - if (res && res.status === "OK") { - Notification.Success({ - msg: t("password_sent"), - }); - } else if (res && res.data) { - setErrors(res.data); - } else { - Notification.Error({ - msg: t("something_wrong"), - }); - } + const { res, error } = await request(routes.forgotPassword, { + body: { ...valid }, }); + setLoading(false); + if (res && res.statusText === "OK") { + Notification.Success({ + msg: t("password_sent"), + }); + } else if (res && error) { + setErrors(error); + } } }; diff --git a/src/Components/Auth/ResetPassword.tsx b/src/Components/Auth/ResetPassword.tsx index 08dd1da44a0..2f02737f6de 100644 --- a/src/Components/Auth/ResetPassword.tsx +++ b/src/Components/Auth/ResetPassword.tsx @@ -1,7 +1,6 @@ import { useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; +import request from "../../Utils/request/request.js"; import * as Notification from "../../Utils/Notifications.js"; -import { postResetPassword, checkResetToken } from "../../Redux/actions"; import { navigate } from "raviger"; import { useTranslation } from "react-i18next"; import { LocalStorageKeys } from "../../Common/constants"; @@ -9,9 +8,9 @@ import { Cancel, Submit } from "../Common/components/ButtonV2"; import TextFormField from "../Form/FormFields/TextFormField"; import { validateRule } from "../Users/UserAdd"; import { validatePassword } from "../../Common/validation.js"; +import routes from "../../Redux/api.js"; export const ResetPassword = (props: any) => { - const dispatch: any = useDispatch(); const initForm: any = { password: "", confirm: "", @@ -65,36 +64,37 @@ export const ResetPassword = (props: any) => { return form; }; - const handleSubmit = (e: any) => { + const handleSubmit = async (e: any) => { e.preventDefault(); const valid = validateData(); if (valid) { valid.token = props.token; - dispatch(postResetPassword(valid)).then((resp: any) => { - const res = resp && resp.data; - if (res && res.status === "OK") { - localStorage.removeItem(LocalStorageKeys.accessToken); - Notification.Success({ - msg: t("password_reset_success"), - }); - navigate("/login"); - } else if (res && res.data) { - setErrors(res.data); - } else { - Notification.Error({ - msg: t("password_reset_failure"), - }); - } + const { res, error } = await request(routes.resetPassword, { + body: { ...valid }, }); + if (res && res.statusText === "OK") { + localStorage.removeItem(LocalStorageKeys.accessToken); + Notification.Success({ + msg: t("password_reset_success"), + }); + navigate("/login"); + } else if (res && error) { + setErrors(error); + } } }; useEffect(() => { - if (props.token) { - dispatch(checkResetToken({ token: props.token })).then((resp: any) => { - const res = resp && resp.data; - if (!res || res.status !== "OK") navigate("/invalid-reset"); + const checkResetToken = async () => { + const { res } = await request(routes.checkResetToken, { + body: { token: props.token }, }); + if (!res || res.statusText !== "OK") { + navigate("/invalid-reset"); + } + }; + if (props.token) { + checkResetToken(); } else { navigate("/invalid-reset"); } diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 1aa06c7e1bc..f26457183dd 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -17,6 +17,11 @@ interface JwtTokenObtainPair { refresh: string; } +interface LoginInput { + username: string; + password: string; +} + const routes = { config: { path: import.meta.env.REACT_APP_CONFIG ?? "/config.json", @@ -30,6 +35,8 @@ const routes = { path: "/api/v1/auth/login/", method: "POST", noAuth: true, + TRes: Type(), + TBody: Type(), }, token_refresh: { @@ -47,16 +54,22 @@ const routes = { checkResetToken: { path: "/api/v1/password_reset/check/", method: "POST", + TRes: Type>(), + TBody: Type<{ token: string }>(), }, resetPassword: { path: "/api/v1/password_reset/confirm/", method: "POST", + TRes: Type>(), + TBody: Type<{ password: string; confirm: string }>(), }, forgotPassword: { path: "/api/v1/password_reset/", method: "POST", + TRes: Type>(), + TBody: Type<{ username: string }>(), }, updatePassword: { From 66e5d9192f9ce7fadeb81fae58f5bff502480b33 Mon Sep 17 00:00:00 2001 From: Pranshu Aggarwal <70687348+Pranshu1902@users.noreply.github.com> Date: Wed, 4 Oct 2023 09:44:56 +0530 Subject: [PATCH 05/65] fix hover height (#6352) --- src/Components/Facility/FacilityHome.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Facility/FacilityHome.tsx b/src/Components/Facility/FacilityHome.tsx index 15317b1b56c..4fa61514c64 100644 --- a/src/Components/Facility/FacilityHome.tsx +++ b/src/Components/Facility/FacilityHome.tsx @@ -323,7 +323,7 @@ export const FacilityHome = (props: any) => { StaffUserTypeIndex; const editCoverImageTooltip = hasPermissionToEditCoverImage && ( -

+
{`${hasCoverImage ? "Edit" : "Upload"}`}
From 93f20fb77e452a3f3d93982ccfd79518e181d2a9 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Wed, 4 Oct 2023 09:55:30 +0530 Subject: [PATCH 06/65] Split routes of App Router (#6363) --- ...tyMiddleware.tsx => FacilityConfigure.tsx} | 2 +- src/Components/Facility/FacilityHome.tsx | 4 +- .../Notifications/ShowPushNotification.tsx | 6 +- src/Components/Patient/FileUpload.tsx | 2 +- src/Components/Patient/SampleDetails.tsx | 7 +- src/Components/Resource/ListView.tsx | 2 +- src/Components/Resource/ResourceBoardView.tsx | 2 +- src/Components/Resource/ResourceDetails.tsx | 2 +- src/Components/Shifting/BoardView.tsx | 4 +- src/Components/Shifting/ListView.tsx | 4 +- src/Components/Shifting/ShiftDetails.tsx | 2 +- src/Redux/actions.tsx | 2 +- src/Routers/AppRouter.tsx | 429 ++---------------- src/Routers/routes/AssetRoutes.tsx | 21 + src/Routers/routes/ConsultationRoutes.tsx | 141 ++++++ src/Routers/routes/ExternalResultRoutes.tsx | 14 + .../routes/FacilityInventoryRoutes.tsx | 24 + src/Routers/routes/FacilityLocationRoutes.tsx | 38 ++ src/Routers/routes/FacilityRoutes.tsx | 45 ++ src/Routers/routes/HCXRoutes.tsx | 6 + src/Routers/routes/PatientRoutes.tsx | 46 ++ src/Routers/routes/ResourceRoutes.tsx | 25 + src/Routers/routes/SampleRoutes.tsx | 25 + src/Routers/routes/ShiftingRoutes.tsx | 27 ++ src/Routers/routes/UserRoutes.tsx | 9 + src/Routers/types.ts | 5 + 26 files changed, 481 insertions(+), 413 deletions(-) rename src/Components/Facility/{UpdateFacilityMiddleware.tsx => FacilityConfigure.tsx} (98%) create mode 100644 src/Routers/routes/AssetRoutes.tsx create mode 100644 src/Routers/routes/ConsultationRoutes.tsx create mode 100644 src/Routers/routes/ExternalResultRoutes.tsx create mode 100644 src/Routers/routes/FacilityInventoryRoutes.tsx create mode 100644 src/Routers/routes/FacilityLocationRoutes.tsx create mode 100644 src/Routers/routes/FacilityRoutes.tsx create mode 100644 src/Routers/routes/HCXRoutes.tsx create mode 100644 src/Routers/routes/PatientRoutes.tsx create mode 100644 src/Routers/routes/ResourceRoutes.tsx create mode 100644 src/Routers/routes/SampleRoutes.tsx create mode 100644 src/Routers/routes/ShiftingRoutes.tsx create mode 100644 src/Routers/routes/UserRoutes.tsx create mode 100644 src/Routers/types.ts diff --git a/src/Components/Facility/UpdateFacilityMiddleware.tsx b/src/Components/Facility/FacilityConfigure.tsx similarity index 98% rename from src/Components/Facility/UpdateFacilityMiddleware.tsx rename to src/Components/Facility/FacilityConfigure.tsx index 211d8cf458a..6f0a8a9869c 100644 --- a/src/Components/Facility/UpdateFacilityMiddleware.tsx +++ b/src/Components/Facility/FacilityConfigure.tsx @@ -46,7 +46,7 @@ const FormReducer = (state = initialState, action: any) => { } }; -export const UpdateFacilityMiddleware = (props: any) => { +export const FacilityConfigure = (props: any) => { const [state, dispatch] = useReducer(FormReducer, initialState); const { facilityId } = props; const dispatchAction: any = useDispatch(); diff --git a/src/Components/Facility/FacilityHome.tsx b/src/Components/Facility/FacilityHome.tsx index 4fa61514c64..68990e64416 100644 --- a/src/Components/Facility/FacilityHome.tsx +++ b/src/Components/Facility/FacilityHome.tsx @@ -541,9 +541,7 @@ export const FacilityHome = (props: any) => { - navigate(`/facility/${facilityId}/middleware/update`) - } + onClick={() => navigate(`/facility/${facilityId}/configure`)} authorizeFor={NonReadOnlyUsers} icon={} > diff --git a/src/Components/Notifications/ShowPushNotification.tsx b/src/Components/Notifications/ShowPushNotification.tsx index fe88c087d5e..09b62bb9333 100644 --- a/src/Components/Notifications/ShowPushNotification.tsx +++ b/src/Components/Notifications/ShowPushNotification.tsx @@ -1,13 +1,13 @@ import { useDispatch } from "react-redux"; import { getNotificationData } from "../../Redux/actions"; import { useEffect } from "react"; +import { DetailRoute } from "../../Routers/types"; -export default function ShowPushNotification({ external_id }: any) { +export default function ShowPushNotification({ id }: DetailRoute) { const dispatch: any = useDispatch(); const resultUrl = async () => { - console.log("ID:", external_id.id); - const res = await dispatch(getNotificationData({ id: external_id.id })); + const res = await dispatch(getNotificationData({ id })); const data = res.data.caused_objects; switch (res.data.event) { case "PATIENT_CREATED": diff --git a/src/Components/Patient/FileUpload.tsx b/src/Components/Patient/FileUpload.tsx index fe0881e6994..2b0b068b0ef 100644 --- a/src/Components/Patient/FileUpload.tsx +++ b/src/Components/Patient/FileUpload.tsx @@ -100,7 +100,7 @@ interface FileUploadProps { hideBack: boolean; audio?: boolean; unspecified: boolean; - sampleId?: number; + sampleId?: string; claimId?: string; } diff --git a/src/Components/Patient/SampleDetails.tsx b/src/Components/Patient/SampleDetails.tsx index 7814109978b..e29df92f47f 100644 --- a/src/Components/Patient/SampleDetails.tsx +++ b/src/Components/Patient/SampleDetails.tsx @@ -13,14 +13,11 @@ import { getTestSample } from "../../Redux/actions"; import { navigate } from "raviger"; import { useDispatch } from "react-redux"; +import { DetailRoute } from "../../Routers/types"; const Loading = lazy(() => import("../Common/Loading")); -interface SampleDetailsProps { - id: number; -} - -export const SampleDetails = ({ id }: SampleDetailsProps) => { +export const SampleDetails = ({ id }: DetailRoute) => { const dispatch: any = useDispatch(); const [isLoading, setIsLoading] = useState(false); const [sampleDetails, setSampleDetails] = useState({}); diff --git a/src/Components/Resource/ListView.tsx b/src/Components/Resource/ListView.tsx index b368c9bf3ea..24b4e263039 100644 --- a/src/Components/Resource/ListView.tsx +++ b/src/Components/Resource/ListView.tsx @@ -31,7 +31,7 @@ export default function ListView() { const { t } = useTranslation(); const onBoardViewBtnClick = () => - navigate("/resource/board-view", { query: qParams }); + navigate("/resource/board", { query: qParams }); const appliedFilters = formatFilter(qParams); const refreshList = () => { diff --git a/src/Components/Resource/ResourceBoardView.tsx b/src/Components/Resource/ResourceBoardView.tsx index de3058f9406..d3b2b202649 100644 --- a/src/Components/Resource/ResourceBoardView.tsx +++ b/src/Components/Resource/ResourceBoardView.tsx @@ -32,7 +32,7 @@ export default function BoardView() { const { t } = useTranslation(); const onListViewBtnClick = () => { - navigate("/resource/list-view", { query: qParams }); + navigate("/resource/list", { query: qParams }); localStorage.setItem("defaultResourceView", "list"); }; diff --git a/src/Components/Resource/ResourceDetails.tsx b/src/Components/Resource/ResourceDetails.tsx index 591f6b48bdb..67adc7edd82 100644 --- a/src/Components/Resource/ResourceDetails.tsx +++ b/src/Components/Resource/ResourceDetails.tsx @@ -231,7 +231,7 @@ export default function ResourceDetails(props: { id: string }) { {isPrintMode ? (
diff --git a/src/Components/Shifting/BoardView.tsx b/src/Components/Shifting/BoardView.tsx index 0eddfddf745..85815770acf 100644 --- a/src/Components/Shifting/BoardView.tsx +++ b/src/Components/Shifting/BoardView.tsx @@ -95,9 +95,7 @@ export default function BoardView() {
- navigate("/shifting/list-view", { query: qParams }) - } + onClick={() => navigate("/shifting/list", { query: qParams })} > {t("list_view")} diff --git a/src/Components/Shifting/ListView.tsx b/src/Components/Shifting/ListView.tsx index 6d55122ea11..f3fb14a4c0a 100644 --- a/src/Components/Shifting/ListView.tsx +++ b/src/Components/Shifting/ListView.tsx @@ -312,9 +312,7 @@ export default function ListView() {
- navigate("/shifting/board-view", { query: qParams }) - } + onClick={() => navigate("/shifting/board", { query: qParams })} > {t("board_view")} diff --git a/src/Components/Shifting/ShiftDetails.tsx b/src/Components/Shifting/ShiftDetails.tsx index 03e09702aac..7bffe429960 100644 --- a/src/Components/Shifting/ShiftDetails.tsx +++ b/src/Components/Shifting/ShiftDetails.tsx @@ -557,7 +557,7 @@ export default function ShiftDetails(props: { id: string }) { ) : ( { export const getTestList = (params: object) => { return fireRequest("getTestSampleList", [], params); }; -export const getTestSample = (id: number) => { +export const getTestSample = (id: string) => { return fireRequest("getTestSample", [id], {}); }; export const patchSample = (params: object, pathParam: object) => { diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx index f1449f13bc2..0f6108b00e3 100644 --- a/src/Routers/AppRouter.tsx +++ b/src/Routers/AppRouter.tsx @@ -1,64 +1,9 @@ import { useRedirect, useRoutes, usePath, Redirect } from "raviger"; import { useState, useEffect } from "react"; -import { ConsultationDetails } from "../Components/Facility/ConsultationDetails"; -import TreatmentSummary from "../Components/Facility/TreatmentSummary"; -import { ConsultationForm } from "../Components/Facility/ConsultationForm"; -import { FacilityCreate } from "../Components/Facility/FacilityCreate"; -import { FacilityHome } from "../Components/Facility/FacilityHome"; -import { HospitalList } from "../Components/Facility/HospitalList"; -import { TriageForm } from "../Components/Facility/TriageForm"; -import { DailyRounds } from "../Components/Patient/DailyRounds"; -import { PatientManager } from "../Components/Patient/ManagePatients"; -import PatientNotes from "../Components/Patient/PatientNotes"; -import { PatientHome } from "../Components/Patient/PatientHome"; -import { PatientRegister } from "../Components/Patient/PatientRegister"; -import { SampleDetails } from "../Components/Patient/SampleDetails"; -import SampleReport from "../Components/Patient/SamplePreview"; -import { SampleTest } from "../Components/Patient/SampleTest"; -import SampleViewAdmin from "../Components/Patient/SampleViewAdmin"; -import ManageUsers from "../Components/Users/ManageUsers"; -import { UserAdd } from "../Components/Users/UserAdd"; -import InventoryList from "../Components/Facility/InventoryList"; -import InventoryLog from "../Components/Facility/InventoryLog"; -import { AddInventoryForm } from "../Components/Facility/AddInventoryForm"; -import { SetInventoryForm } from "../Components/Facility/SetInventoryForm"; -import MinQuantityList from "../Components/Facility/MinQuantityList"; -import { ShiftCreate } from "../Components/Patient/ShiftCreate"; -import UserProfile from "../Components/Users/UserProfile"; -import ShiftBoardView from "../Components/Shifting/BoardView"; -import ShiftListView from "../Components/Shifting/ListView"; -import ShiftDetails from "../Components/Shifting/ShiftDetails"; -import { ShiftDetailsUpdate } from "../Components/Shifting/ShiftDetailsUpdate"; -import ResourceCreate from "../Components/Resource/ResourceCreate"; -import ResourceBoardView from "../Components/Resource/ResourceBoardView"; -import ResourceListView from "../Components/Resource/ListView"; -import ResourceDetails from "../Components/Resource/ResourceDetails"; -import { ResourceDetailsUpdate } from "../Components/Resource/ResourceDetailsUpdate"; -import ResultList from "../Components/ExternalResult/ResultList"; -import ResultItem from "../Components/ExternalResult/ResultItem"; -import ExternalResultUpload from "../Components/ExternalResult/ExternalResultUpload"; -import ResultUpdate from "../Components/ExternalResult/ResultUpdate"; -import { FileUpload } from "../Components/Patient/FileUpload"; -import Investigation from "../Components/Facility/Investigations"; -import ShowInvestigation from "../Components/Facility/Investigations/ShowInvestigation"; -import InvestigationReports from "../Components/Facility/Investigations/Reports"; -import AssetCreate from "../Components/Facility/AssetCreate"; -import DeathReport from "../Components/DeathReport/DeathReport"; -import { make as CriticalCareRecording } from "../Components/CriticalCareRecording/CriticalCareRecording.bs"; + import ShowPushNotification from "../Components/Notifications/ShowPushNotification"; import { NoticeBoard } from "../Components/Notifications/NoticeBoard"; -import { AddLocationForm } from "../Components/Facility/AddLocationForm"; -import { AddBedForm } from "../Components/Facility/AddBedForm"; -import LocationManagement from "../Components/Facility/LocationManagement"; -import { BedManagement } from "../Components/Facility/BedManagement"; -import AssetsList from "../Components/Assets/AssetsList"; -import AssetManage from "../Components/Assets/AssetManage"; -import AssetConfigure from "../Components/Assets/AssetConfigure"; -import { DailyRoundListDetails } from "../Components/Patient/DailyRoundListDetails"; import Error404 from "../Components/ErrorPages/404"; -import { DndProvider } from "react-dnd"; -import { HTML5Backend } from "react-dnd-html5-backend"; -import FacilityUsers from "../Components/Facility/FacilityUsers"; import { DesktopSidebar, MobileSidebar, @@ -66,349 +11,55 @@ import { SidebarShrinkContext, } from "../Components/Common/Sidebar/Sidebar"; import { BLACKLISTED_PATHS, LocalStorageKeys } from "../Common/constants"; -import { UpdateFacilityMiddleware } from "../Components/Facility/UpdateFacilityMiddleware"; import useConfig from "../Common/hooks/useConfig"; -import ConsultationClaims from "../Components/Facility/ConsultationClaims"; import { handleSignOut } from "../Utils/utils"; import SessionExpired from "../Components/ErrorPages/SessionExpired"; -import ManagePrescriptions from "../Components/Medicine/ManagePrescriptions"; -import CentralNursingStation from "../Components/Facility/CentralNursingStation"; -export default function AppRouter() { - const { main_logo, enable_hcx } = useConfig(); +import UserRoutes from "./routes/UserRoutes"; +import PatientRoutes from "./routes/PatientRoutes"; +import SampleRoutes from "./routes/SampleRoutes"; +import FacilityRoutes from "./routes/FacilityRoutes"; +import ConsultationRoutes from "./routes/ConsultationRoutes"; +import HCXRoutes from "./routes/HCXRoutes"; +import ShiftingRoutes from "./routes/ShiftingRoutes"; +import AssetRoutes from "./routes/AssetRoutes"; +import ResourceRoutes from "./routes/ResourceRoutes"; +import ExternalResultRoutes from "./routes/ExternalResultRoutes"; +import { DetailRoute } from "./types"; + +const Routes = { + "/": () => , - const routes = { - "/": () => , - "/users": () => , - "/users/add": () => , - "/user/profile": () => , - "/patients": () => , - "/patient/:id": ({ id }: any) => , - "/patient/:id/investigation_reports": ({ id }: any) => ( - - ), - "/sample": () => , - "/sample/:id": ({ id }: any) => , - "/patient/:patientId/test_sample/:sampleId/icmr_sample": ({ - patientId, - sampleId, - }: any) => , - "/facility": () => , - "/facility/create": () => , - "/facility/:facilityId/update": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/middleware/update": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/users": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/resource/new": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/triage": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/patient": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/patient/:id": ({ facilityId, id }: any) => ( - - ), - "/facility/:facilityId/patient/:id/update": ({ facilityId, id }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/sample-test": ({ - facilityId, - patientId, - }: any) => , - "/facility/:facilityId/patient/:patientId/sample/:id": ({ id }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/notes": ({ - facilityId, - patientId, - }: any) => , - "/facility/:facilityId/patient/:patientId/files": ({ - facilityId, - patientId, - }: any) => ( - - ), - "/facility/:facilityId/triage/:id": ({ facilityId, id }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation": ({ - facilityId, - patientId, - }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation/:id/update": ({ - facilityId, - patientId, - id, - }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation/:id/files/": ({ - facilityId, - patientId, - id, - }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation/:consultationId/prescriptions": - (path: any) => , - "/facility/:facilityId/patient/:patientId/consultation/:id/investigation": - ({ facilityId, patientId, id }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation/:id/investigation/:sessionId": - ({ facilityId, patientId, id, sessionId }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation/:id/daily-rounds": ({ - facilityId, - patientId, - id, - }: any) => ( - - ), - ...(enable_hcx - ? { - "/facility/:facilityId/patient/:patientId/consultation/:consultationId/claims": - (pathParams: any) => , - } - : {}), - "/facility/:facilityId/patient/:patientId/consultation/:consultationId/daily-rounds/:id/update": - ({ facilityId, patientId, consultationId, id }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation/:consultationId/daily-rounds/:id": - ({ facilityId, patientId, consultationId, id }: any) => ( - - ), + ...AssetRoutes, + ...ConsultationRoutes, + ...ExternalResultRoutes, + ...FacilityRoutes, + ...PatientRoutes, + ...ResourceRoutes, + ...SampleRoutes, + ...ShiftingRoutes, + ...UserRoutes, - "/facility/:facilityId/patient/:patientId/consultation/:consultationId/daily_rounds/:id": - ({ facilityId, patientId, consultationId, id }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation/:consultationId/daily_rounds/:id/update": - ({ facilityId, patientId, consultationId, id }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/shift/new": ({ - facilityId, - patientId, - }: any) => , - "/facility/:facilityId/inventory": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/location": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/location/:locationId/beds": ({ - facilityId, - locationId, - }: any) => ( - - ), - "/facility/:facilityId/inventory/add": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/location/add": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/location/:locationId/update": ({ - facilityId, - locationId, - }: any) => ( - - ), - "/facility/:facilityId/location/:locationId/beds/add": ({ - facilityId, - locationId, - }: any) => , - "/facility/:facilityId/location/:locationId/beds/:bedId/update": ({ - facilityId, - locationId, - bedId, - }: any) => ( - - ), - "/facility/:facilityId/inventory/min_quantity/set": ({ - facilityId, - }: any) => , - "/facility/:facilityId/inventory/min_quantity/list": ({ - facilityId, - }: any) => , - "/facility/:facilityId/inventory/min_quantity": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/inventory/:inventoryId": ({ - facilityId, - inventoryId, - }: any) => ( - - ), - "/facility/:facilityId/assets/new": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/assets/:assetId/update": ({ - facilityId, - assetId, - }: any) => , - "/assets": () => , - "/facility/:facilityId/assets/:assetId": ({ assetId, facilityId }: any) => ( - - ), - "/facility/:facilityId/assets/:assetId/configure": ({ - assetId, - facilityId, - }: any) => , - "/facility/:facilityId/cns": ({ facilityId }: any) => ( - - ), + "/notifications/:id": ({ id }: DetailRoute) => ( + + ), + "/notice_board": () => , + + "/session-expired": () => , + "/not-found": () => , +}; + +export default function AppRouter() { + const { main_logo, enable_hcx } = useConfig(); - "/shifting": () => - localStorage.getItem("defaultShiftView") === "list" ? ( - - ) : ( - - - - ), - "/shifting/board-view": () => ( - - - - ), - "/shifting/list-view": () => , - "/shifting/:id": ({ id }: any) => , - "/shifting/:id/update": ({ id }: any) => , - "/resource": () => - localStorage.getItem("defaultResourceView") === "list" ? ( - - ) : ( - - - - ), + let routes = Routes; - "/resource/board-view": () => ( - - - - ), - "/resource/list-view": () => , - "/resource/:id": ({ id }: any) => , - "/resource/:id/update": ({ id }: any) => , - "/external_results": () => , - "/external_results/upload": () => , - "/external_results/:id": ({ id }: any) => , - "/external_results/:id/update": ({ id }: any) => , - "/death_report/:id": ({ id }: any) => , - "/notifications/:id": (id: any) => ( - - ), - "/notice_board": () => , - "/facility/:facilityId/patient/:patientId/consultation/:consultationId": ({ - facilityId, - patientId, - consultationId, - }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation/:consultationId/treatment-summary": - ({ facilityId, patientId, consultationId }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation/:consultationId/:tab": - ({ facilityId, patientId, consultationId, tab }: any) => ( - - ), - "/session-expired": () => , - "/not-found": () => , - }; + if (enable_hcx) { + routes = { ...routes, ...HCXRoutes }; + } - useRedirect("/", "/facility"); useRedirect("/user", "/users"); - const pages = useRoutes(routes as any) || ; + const pages = useRoutes(routes) || ; const path = usePath(); const [sidebarOpen, setSidebarOpen] = useState(false); diff --git a/src/Routers/routes/AssetRoutes.tsx b/src/Routers/routes/AssetRoutes.tsx new file mode 100644 index 00000000000..d3bd96ca437 --- /dev/null +++ b/src/Routers/routes/AssetRoutes.tsx @@ -0,0 +1,21 @@ +import AssetConfigure from "../../Components/Assets/AssetConfigure"; +import AssetManage from "../../Components/Assets/AssetManage"; +import AssetsList from "../../Components/Assets/AssetsList"; +import AssetCreate from "../../Components/Facility/AssetCreate"; + +export default { + "/assets": () => , + + "/facility/:facilityId/assets/new": (params: any) => ( + + ), + "/facility/:facilityId/assets/:assetId/update": (params: any) => ( + + ), + "/facility/:facilityId/assets/:assetId": (params: any) => ( + + ), + "/facility/:facilityId/assets/:assetId/configure": (params: any) => ( + + ), +}; diff --git a/src/Routers/routes/ConsultationRoutes.tsx b/src/Routers/routes/ConsultationRoutes.tsx new file mode 100644 index 00000000000..4f1d6e7d75d --- /dev/null +++ b/src/Routers/routes/ConsultationRoutes.tsx @@ -0,0 +1,141 @@ +import { ConsultationForm } from "../../Components/Facility/ConsultationForm"; +import Investigation from "../../Components/Facility/Investigations"; +import ShowInvestigation from "../../Components/Facility/Investigations/ShowInvestigation"; +import ManagePrescriptions from "../../Components/Medicine/ManagePrescriptions"; +import { DailyRoundListDetails } from "../../Components/Patient/DailyRoundListDetails"; +import { DailyRounds } from "../../Components/Patient/DailyRounds"; +import { FileUpload } from "../../Components/Patient/FileUpload"; +import { make as CriticalCareRecording } from "../../Components/CriticalCareRecording/CriticalCareRecording.bs"; +import { ConsultationDetails } from "../../Components/Facility/ConsultationDetails"; +import TreatmentSummary from "../../Components/Facility/TreatmentSummary"; + +export default { + "/facility/:facilityId/patient/:patientId/consultation": ({ + facilityId, + patientId, + }: any) => , + "/facility/:facilityId/patient/:patientId/consultation/:id/update": ({ + facilityId, + patientId, + id, + }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:id/files/": ({ + facilityId, + patientId, + id, + }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:consultationId/prescriptions": + (path: any) => , + "/facility/:facilityId/patient/:patientId/consultation/:id/investigation": ({ + facilityId, + patientId, + id, + }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:id/investigation/:sessionId": + ({ facilityId, patientId, id, sessionId }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:id/daily-rounds": ({ + facilityId, + patientId, + id, + }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:consultationId/daily-rounds/:id/update": + ({ facilityId, patientId, consultationId, id }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:consultationId/daily-rounds/:id": + ({ facilityId, patientId, consultationId, id }: any) => ( + + ), + + "/facility/:facilityId/patient/:patientId/consultation/:consultationId/daily_rounds/:id": + ({ facilityId, patientId, consultationId, id }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:consultationId/daily_rounds/:id/update": + ({ facilityId, patientId, consultationId, id }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:consultationId": ({ + facilityId, + patientId, + consultationId, + }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:consultationId/treatment-summary": + ({ facilityId, patientId, consultationId }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:consultationId/:tab": + ({ facilityId, patientId, consultationId, tab }: any) => ( + + ), +}; diff --git a/src/Routers/routes/ExternalResultRoutes.tsx b/src/Routers/routes/ExternalResultRoutes.tsx new file mode 100644 index 00000000000..af4bf090d78 --- /dev/null +++ b/src/Routers/routes/ExternalResultRoutes.tsx @@ -0,0 +1,14 @@ +import ExternalResultUpload from "../../Components/ExternalResult/ExternalResultUpload"; +import ResultItem from "../../Components/ExternalResult/ResultItem"; +import ResultList from "../../Components/ExternalResult/ResultList"; +import ResultUpdate from "../../Components/ExternalResult/ResultUpdate"; +import { DetailRoute } from "../types"; + +export default { + "/external_results": () => , + "/external_results/upload": () => , + "/external_results/:id": ({ id }: DetailRoute) => , + "/external_results/:id/update": ({ id }: DetailRoute) => ( + + ), +}; diff --git a/src/Routers/routes/FacilityInventoryRoutes.tsx b/src/Routers/routes/FacilityInventoryRoutes.tsx new file mode 100644 index 00000000000..17e93b2bc60 --- /dev/null +++ b/src/Routers/routes/FacilityInventoryRoutes.tsx @@ -0,0 +1,24 @@ +import { Redirect } from "raviger"; +import InventoryList from "../../Components/Facility/InventoryList"; +import InventoryLog from "../../Components/Facility/InventoryLog"; +import MinQuantityList from "../../Components/Facility/MinQuantityList"; +import { SetInventoryForm } from "../../Components/Facility/SetInventoryForm"; + +export default { + "/facility/:facilityId/inventory": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/inventory/min_quantity/set": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/inventory/min_quantity/list": ({ + facilityId, + }: any) => , + "/facility/:facilityId/inventory/min_quantity": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/inventory/:inventoryId": ({ + facilityId, + inventoryId, + }: any) => , +}; diff --git a/src/Routers/routes/FacilityLocationRoutes.tsx b/src/Routers/routes/FacilityLocationRoutes.tsx new file mode 100644 index 00000000000..c43673b60f5 --- /dev/null +++ b/src/Routers/routes/FacilityLocationRoutes.tsx @@ -0,0 +1,38 @@ +import { AddBedForm } from "../../Components/Facility/AddBedForm"; +import { AddInventoryForm } from "../../Components/Facility/AddInventoryForm"; +import { AddLocationForm } from "../../Components/Facility/AddLocationForm"; +import { BedManagement } from "../../Components/Facility/BedManagement"; +import LocationManagement from "../../Components/Facility/LocationManagement"; + +export default { + "/facility/:facilityId/location": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/location/:locationId/beds": ({ + facilityId, + locationId, + }: any) => , + "/facility/:facilityId/inventory/add": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/location/add": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/location/:locationId/update": ({ + facilityId, + locationId, + }: any) => ( + + ), + "/facility/:facilityId/location/:locationId/beds/add": ({ + facilityId, + locationId, + }: any) => , + "/facility/:facilityId/location/:locationId/beds/:bedId/update": ({ + facilityId, + locationId, + bedId, + }: any) => ( + + ), +}; diff --git a/src/Routers/routes/FacilityRoutes.tsx b/src/Routers/routes/FacilityRoutes.tsx new file mode 100644 index 00000000000..77247df9189 --- /dev/null +++ b/src/Routers/routes/FacilityRoutes.tsx @@ -0,0 +1,45 @@ +import { FacilityConfigure } from "../../Components/Facility/FacilityConfigure"; +import { FacilityCreate } from "../../Components/Facility/FacilityCreate"; +import { FacilityHome } from "../../Components/Facility/FacilityHome"; +import FacilityUsers from "../../Components/Facility/FacilityUsers"; +import { HospitalList } from "../../Components/Facility/HospitalList"; +import { TriageForm } from "../../Components/Facility/TriageForm"; +import ResourceCreate from "../../Components/Resource/ResourceCreate"; +import CentralNursingStation from "../../Components/Facility/CentralNursingStation"; +import FacilityLocationRoutes from "./FacilityLocationRoutes"; +import FacilityInventoryRoutes from "./FacilityInventoryRoutes"; + +export default { + "/facility": () => , + "/facility/create": () => , + "/facility/:facilityId/update": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/configure": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/cns": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId": ({ facilityId }: any) => ( + + ), + + "/facility/:facilityId/users": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/resource/new": ({ facilityId }: any) => ( + + ), + + // Triage related routes + "/facility/:facilityId/triage": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/triage/:id": ({ facilityId, id }: any) => ( + + ), + + ...FacilityLocationRoutes, + ...FacilityInventoryRoutes, +}; diff --git a/src/Routers/routes/HCXRoutes.tsx b/src/Routers/routes/HCXRoutes.tsx new file mode 100644 index 00000000000..8a36e033c15 --- /dev/null +++ b/src/Routers/routes/HCXRoutes.tsx @@ -0,0 +1,6 @@ +import ConsultationClaims from "../../Components/Facility/ConsultationClaims"; + +export default { + "/facility/:facilityId/patient/:patientId/consultation/:consultationId/claims": + (pathParams: any) => , +}; diff --git a/src/Routers/routes/PatientRoutes.tsx b/src/Routers/routes/PatientRoutes.tsx new file mode 100644 index 00000000000..ae594d767ec --- /dev/null +++ b/src/Routers/routes/PatientRoutes.tsx @@ -0,0 +1,46 @@ +import InvestigationReports from "../../Components/Facility/Investigations/Reports"; +import { FileUpload } from "../../Components/Patient/FileUpload"; +import { PatientManager } from "../../Components/Patient/ManagePatients"; +import { PatientHome } from "../../Components/Patient/PatientHome"; +import PatientNotes from "../../Components/Patient/PatientNotes"; +import { PatientRegister } from "../../Components/Patient/PatientRegister"; +import { DetailRoute } from "../types"; +import DeathReport from "../../Components/DeathReport/DeathReport"; + +export default { + "/patients": () => , + "/patient/:id": ({ id }: DetailRoute) => , + "/patient/:id/investigation_reports": ({ id }: DetailRoute) => ( + + ), + + // Facility Scoped Routes + "/facility/:facilityId/patient": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/patient/:id": ({ facilityId, id }: any) => ( + + ), + "/facility/:facilityId/patient/:id/update": ({ facilityId, id }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/notes": ({ + facilityId, + patientId, + }: any) => , + "/facility/:facilityId/patient/:patientId/files": ({ + facilityId, + patientId, + }: any) => ( + + ), + "/death_report/:id": ({ id }: any) => , +}; diff --git a/src/Routers/routes/ResourceRoutes.tsx b/src/Routers/routes/ResourceRoutes.tsx new file mode 100644 index 00000000000..8408ab4d79d --- /dev/null +++ b/src/Routers/routes/ResourceRoutes.tsx @@ -0,0 +1,25 @@ +import { DndProvider } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; +import ResourceDetails from "../../Components/Resource/ResourceDetails"; +import { ResourceDetailsUpdate } from "../../Components/Resource/ResourceDetailsUpdate"; +import ListView from "../../Components/Resource/ListView"; +import BoardView from "../../Components/Resource/ResourceBoardView"; +import { Redirect } from "raviger"; +import { DetailRoute } from "../types"; + +const getDefaultView = () => + localStorage.getItem("defaultResourceView") === "list" ? "list" : "board"; + +export default { + "/resource": () => , + "/resource/board": () => ( + + + + ), + "/resource/list": () => , + "/resource/:id": ({ id }: DetailRoute) => , + "/resource/:id/update": ({ id }: DetailRoute) => ( + + ), +}; diff --git a/src/Routers/routes/SampleRoutes.tsx b/src/Routers/routes/SampleRoutes.tsx new file mode 100644 index 00000000000..290a34fd4eb --- /dev/null +++ b/src/Routers/routes/SampleRoutes.tsx @@ -0,0 +1,25 @@ +import { SampleDetails } from "../../Components/Patient/SampleDetails"; +import SampleReport from "../../Components/Patient/SamplePreview"; +import { SampleTest } from "../../Components/Patient/SampleTest"; +import SampleViewAdmin from "../../Components/Patient/SampleViewAdmin"; +import { DetailRoute, RouteParams } from "../types"; + +export default { + "/sample": () => , + "/sample/:id": ({ id }: DetailRoute) => , + "/patient/:patientId/test_sample/:sampleId/icmr_sample": ({ + patientId, + sampleId, + }: RouteParams<"patientId" | "sampleId">) => ( + + ), + "/facility/:facilityId/patient/:patientId/sample-test": ({ + facilityId, + patientId, + }: RouteParams<"facilityId" | "patientId">) => ( + + ), + "/facility/:facilityId/patient/:patientId/sample/:id": ({ + id, + }: DetailRoute) => , +}; diff --git a/src/Routers/routes/ShiftingRoutes.tsx b/src/Routers/routes/ShiftingRoutes.tsx new file mode 100644 index 00000000000..9b20b4a1a0b --- /dev/null +++ b/src/Routers/routes/ShiftingRoutes.tsx @@ -0,0 +1,27 @@ +import { DndProvider } from "react-dnd"; +import { HTML5Backend } from "react-dnd-html5-backend"; +import { ShiftCreate } from "../../Components/Patient/ShiftCreate"; +import ShiftDetails from "../../Components/Shifting/ShiftDetails"; +import { ShiftDetailsUpdate } from "../../Components/Shifting/ShiftDetailsUpdate"; +import ListView from "../../Components/Shifting/ListView"; +import BoardView from "../../Components/Shifting/BoardView"; +import { Redirect } from "raviger"; + +const getDefaultView = () => + localStorage.getItem("defaultShiftView") === "list" ? "list" : "board"; + +export default { + "/shifting": () => , + "/shifting/board": () => ( + + + + ), + "/shifting/list": () => , + "/shifting/:id": ({ id }: any) => , + "/shifting/:id/update": ({ id }: any) => , + "/facility/:facilityId/patient/:patientId/shift/new": ({ + facilityId, + patientId, + }: any) => , +}; diff --git a/src/Routers/routes/UserRoutes.tsx b/src/Routers/routes/UserRoutes.tsx new file mode 100644 index 00000000000..56877ca4c78 --- /dev/null +++ b/src/Routers/routes/UserRoutes.tsx @@ -0,0 +1,9 @@ +import ManageUsers from "../../Components/Users/ManageUsers"; +import { UserAdd } from "../../Components/Users/UserAdd"; +import UserProfile from "../../Components/Users/UserProfile"; + +export default { + "/users": () => , + "/users/add": () => , + "/user/profile": () => , +}; diff --git a/src/Routers/types.ts b/src/Routers/types.ts new file mode 100644 index 00000000000..dc7138b8df7 --- /dev/null +++ b/src/Routers/types.ts @@ -0,0 +1,5 @@ +export type RouteParams = Record; + +export interface DetailRoute { + id: string; +} From e4d2a0f2fc405e611393f34544758ff2c166a1dc Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Wed, 4 Oct 2023 09:56:13 +0530 Subject: [PATCH 07/65] Prescription: show prescribed on & by and discontinued date in detail card (#6365) --- src/CAREUI/display/RecordMeta.tsx | 18 ++++++++++----- .../Medicine/PrescriptionDetailCard.tsx | 22 +++++++++++++++++-- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/CAREUI/display/RecordMeta.tsx b/src/CAREUI/display/RecordMeta.tsx index 83928f1f23a..48cc8d370ad 100644 --- a/src/CAREUI/display/RecordMeta.tsx +++ b/src/CAREUI/display/RecordMeta.tsx @@ -11,13 +11,14 @@ interface Props { last_name: string; last_login: string | undefined; }; + inlineUser?: boolean; } /** * A generic component to display relative time along with a tooltip and a user * if provided. */ -const RecordMeta = ({ time, user, prefix, className }: Props) => { +const RecordMeta = ({ time, user, prefix, className, inlineUser }: Props) => { const isOnline = user && isUserOnline(user); let child = ( @@ -25,14 +26,15 @@ const RecordMeta = ({ time, user, prefix, className }: Props) => { {relativeTime(time)} {formatDateTime(time)} - {user && ( - <> + {user && !inlineUser && ( + + by {user.first_name} {user.last_name} {isOnline && ( -
+
)} - + )}
@@ -43,7 +45,13 @@ const RecordMeta = ({ time, user, prefix, className }: Props) => {
{prefix} {child} + {user && inlineUser && by} {user && } + {user && inlineUser && ( + + {user.first_name} {user.last_name} + + )}
); } diff --git a/src/Components/Medicine/PrescriptionDetailCard.tsx b/src/Components/Medicine/PrescriptionDetailCard.tsx index 6da4fa7ae8d..bf27aa34068 100644 --- a/src/Components/Medicine/PrescriptionDetailCard.tsx +++ b/src/Components/Medicine/PrescriptionDetailCard.tsx @@ -5,6 +5,7 @@ import ReadMore from "../Common/components/Readmore"; import ButtonV2 from "../Common/components/ButtonV2"; import { PrescriptionActions } from "../../Redux/actions"; import { useTranslation } from "react-i18next"; +import RecordMeta from "../../CAREUI/display/RecordMeta"; export default function PrescriptionDetailCard({ prescription, @@ -29,7 +30,7 @@ export default function PrescriptionDetailCard({ prescription.discontinued && "bg-gray-200 opacity-80" )} > -
+
@@ -83,7 +84,7 @@ export default function PrescriptionDetailCard({
-
+
{prescription.medicine_object?.name ?? prescription.medicine_old} @@ -146,6 +147,23 @@ export default function PrescriptionDetailCard({ )}
+ +
+ + Prescribed + + + {prescription.discontinued && ( + + and was discontinued + + + )} +
{props.children} From f8e9647ac60b86b0c42beac7fd37fd60625ca322 Mon Sep 17 00:00:00 2001 From: Gampa Sri Harsh <114745442+sriharsh05@users.noreply.github.com> Date: Wed, 4 Oct 2023 09:58:28 +0530 Subject: [PATCH 08/65] Fixed bug in location picker in update facility page (#6377) * add all cases for map rendering * implement location pointer on searched location --- package-lock.json | 16 ------- src/Components/Common/GLocationPicker.tsx | 55 +++++++++++++---------- 2 files changed, 32 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4871fbe374b..2a9fe3f520f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14496,8 +14496,6 @@ }, "node_modules/npm/node_modules/cross-spawn/node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "inBundle": true, "license": "ISC", @@ -15254,8 +15252,6 @@ }, "node_modules/npm/node_modules/minipass": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -16398,8 +16394,6 @@ }, "node_modules/npm/node_modules/tar": { "version": "6.1.15", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", - "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", "dev": true, "inBundle": true, "license": "ISC", @@ -16500,8 +16494,6 @@ }, "node_modules/npm/node_modules/util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true, "inBundle": true, "license": "MIT" @@ -16569,8 +16561,6 @@ }, "node_modules/npm/node_modules/wrap-ansi": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -16589,8 +16579,6 @@ "node_modules/npm/node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "inBundle": true, "license": "MIT", @@ -16670,8 +16658,6 @@ }, "node_modules/npm/node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, "inBundle": true, "license": "ISC" @@ -16691,8 +16677,6 @@ }, "node_modules/npm/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "inBundle": true, "license": "ISC" diff --git a/src/Components/Common/GLocationPicker.tsx b/src/Components/Common/GLocationPicker.tsx index 5356d28fa2a..fc121b8519f 100644 --- a/src/Components/Common/GLocationPicker.tsx +++ b/src/Components/Common/GLocationPicker.tsx @@ -7,14 +7,6 @@ import CareIcon from "../../CAREUI/icons/CareIcon"; import useConfig from "../../Common/hooks/useConfig"; import { Popover } from "@headlessui/react"; -const render = (status: Status) => { - if (status === "LOADING") { - return ; - } - - return

{status}

; -}; - interface GLocationPickerProps { lat: number; lng: number; @@ -67,22 +59,37 @@ const GLocationPicker = ({ setCenter(m?.getCenter()?.toJSON()); }; + const render = (status: Status) => { + switch (status) { + case Status.LOADING: + return ; + case Status.SUCCESS: + return ( + + {location && } + + ); + default: + return

{status}

; + } + }; + return (
- - - {location && } - - +
); }; @@ -149,7 +156,9 @@ const Map: React.FC = ({ places.length > 0 && places[0].geometry?.location ) { - handleOnChange(places[0].geometry.location); + const selectedLocation = places[0].geometry.location; + handleOnChange(selectedLocation); + map?.setCenter(selectedLocation); } }); } From a4a46716d367b8f0554b4ed6237028327120f3b2 Mon Sep 17 00:00:00 2001 From: "Tasnimul H. Tauhid" Date: Wed, 4 Oct 2023 10:02:50 +0530 Subject: [PATCH 09/65] Added cam auto reset in asset config page (#6375) --- .../Facility/Consultations/LiveFeed.tsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/Components/Facility/Consultations/LiveFeed.tsx b/src/Components/Facility/Consultations/LiveFeed.tsx index c6ba749b471..aba473e958d 100644 --- a/src/Components/Facility/Consultations/LiveFeed.tsx +++ b/src/Components/Facility/Consultations/LiveFeed.tsx @@ -36,6 +36,7 @@ const LiveFeed = (props: any) => { const [streamStatus, setStreamStatus] = useState( StreamStatus.Offline ); + const [videoStartTime, setVideoStartTime] = useState(null); const [bed, setBed] = useState({}); const [preset, setNewPreset] = useState(""); const [loading, setLoading] = useState(); @@ -100,6 +101,16 @@ const LiveFeed = (props: any) => { }, }); + const calculateVideoLiveDelay = () => { + const video = liveFeedPlayerRef.current as HTMLVideoElement; + if (!video || !videoStartTime) return 0; + + const timeDifference = + (new Date().getTime() - videoStartTime.getTime()) / 1000; + + return timeDifference - video.currentTime; + }; + const getBedPresets = async (id: any) => { const bedAssets = await dispatch( listAssetBeds({ @@ -223,6 +234,7 @@ const LiveFeed = (props: any) => { }, reset: () => { setStreamStatus(StreamStatus.Loading); + setVideoStartTime(null); startStream({ onSuccess: () => setStreamStatus(StreamStatus.Playing), onError: () => setStreamStatus(StreamStatus.Offline), @@ -344,8 +356,25 @@ const LiveFeed = (props: any) => { playsInline className="z-10 h-full w-full" ref={liveFeedPlayerRef} + onPlay={() => { + setVideoStartTime(() => new Date()); + }} + onWaiting={() => { + const delay = calculateVideoLiveDelay(); + if (delay > 5) { + setStreamStatus(StreamStatus.Loading); + } + }} > + {streamStatus === StreamStatus.Playing && + calculateVideoLiveDelay() > 3 && ( +
+ + Slow Network Detected +
+ )} + {loading && (
From c7c37c6e15d988dab5a09580a9eb653fdc7af2c3 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Wed, 4 Oct 2023 10:03:29 +0530 Subject: [PATCH 10/65] =?UTF-8?q?=F0=9F=92=8A=20Adds=20support=20for=20edi?= =?UTF-8?q?ting=20prescriptions=20+=20=20Adds=20`useSlug`=20hook=20(#6369)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adds hook: `useSlug` * bug fix: NumericWithUnits field not showing intial value * Form: support for showing global errors * Adds support for editing prescriptions (fixes #6340) --- src/Common/hooks/useSlug.ts | 45 +++++ src/Components/Form/Form.tsx | 5 + .../FormFields/NumericWithUnitsFormField.tsx | 1 + src/Components/Form/Utils.ts | 4 +- .../Medicine/CreatePrescriptionForm.tsx | 14 +- .../Medicine/EditPrescriptionForm.tsx | 158 ++++++++++++++++++ .../PrescriptionAdministrationsTable.tsx | 45 +++++ src/Components/Medicine/models.ts | 2 +- src/Components/Medicine/validators.ts | 49 ++++++ src/Locale/en/Medicine.json | 4 +- src/Redux/api.tsx | 5 + 11 files changed, 318 insertions(+), 14 deletions(-) create mode 100644 src/Common/hooks/useSlug.ts create mode 100644 src/Components/Medicine/EditPrescriptionForm.tsx create mode 100644 src/Components/Medicine/validators.ts diff --git a/src/Common/hooks/useSlug.ts b/src/Common/hooks/useSlug.ts new file mode 100644 index 00000000000..69d8f591c84 --- /dev/null +++ b/src/Common/hooks/useSlug.ts @@ -0,0 +1,45 @@ +import { usePath } from "raviger"; + +/** + * Returns the slug from the current path. + * @param prefix The prefix of the slug. + * @returns The slug. + * @example + * // Current path: /consultation/94b9a + * const consultation = useSlug("consultation"); // consultation = "94b9a" + */ +export default function useSlug(prefix: string) { + const path = usePath() ?? ""; + return findSlug(path.split("/"), prefix); +} + +/** + * Returns the slugs from the current path. + * @param prefix The prefixes of the slug. + * @returns The slugs + * @example + * // Current path: /facility/5b0a/consultation/94b9a + * const [facility, consultation] = useSlug("facility", "consultation"); + * // facility = "5b0a" + * // consultation = "94b9a" + */ +export const useSlugs = (...prefix: string[]) => { + const path = usePath() ?? ""; + return prefix.map((p) => findSlug(path.split("/"), p)); +}; + +const findSlug = (segments: string[], prefix: string) => { + const index = segments.findIndex((segment) => segment === prefix); + if (index === -1) { + throw new Error( + `Prefix "${prefix}" not found in path "${segments.join("/")}"` + ); + } + + const slug = segments[index + 1]; + if (!slug) { + throw new Error(`Slug not found in path "${segments.join("/")}"`); + } + + return slug; +}; diff --git a/src/Components/Form/Form.tsx b/src/Components/Form/Form.tsx index 5b1cf018965..e934c4ffe0e 100644 --- a/src/Components/Form/Form.tsx +++ b/src/Components/Form/Form.tsx @@ -7,6 +7,7 @@ import { FormContextValue, createFormContext } from "./FormContext"; import { FieldChangeEvent } from "./FormFields/Utils"; import { FormDetails, FormErrors, FormState, formReducer } from "./Utils"; import { DraftSection, useAutoSaveReducer } from "../../Utils/AutoSave"; +import * as Notification from "../../Utils/Notifications"; type Props = { className?: string; @@ -51,6 +52,10 @@ const Form = ({ if (Object.keys(errors).length) { dispatch({ type: "set_errors", errors }); + + if (errors.$all) { + Notification.Error({ msg: errors.$all }); + } return; } } diff --git a/src/Components/Form/FormFields/NumericWithUnitsFormField.tsx b/src/Components/Form/FormFields/NumericWithUnitsFormField.tsx index 31ac781e018..02aa03fdf71 100644 --- a/src/Components/Form/FormFields/NumericWithUnitsFormField.tsx +++ b/src/Components/Form/FormFields/NumericWithUnitsFormField.tsx @@ -34,6 +34,7 @@ export default function NumericWithUnitsFormField(props: Props) { max={props.max} autoComplete={props.autoComplete} required={field.required} + value={numValue} onChange={(e) => field.handleChange(e.target.value + " " + unitValue)} />
diff --git a/src/Components/Form/Utils.ts b/src/Components/Form/Utils.ts index 2ec5d4b60e5..0592e81a06c 100644 --- a/src/Components/Form/Utils.ts +++ b/src/Components/Form/Utils.ts @@ -1,7 +1,9 @@ import { FieldError } from "./FieldValidators"; export type FormDetails = { [key: string]: any }; -export type FormErrors = Partial>; +export type FormErrors = Partial< + Record +>; export type FormState = { form: T; errors: FormErrors }; export type FormAction = | { type: "set_form"; form: T } diff --git a/src/Components/Medicine/CreatePrescriptionForm.tsx b/src/Components/Medicine/CreatePrescriptionForm.tsx index fb7fec5f431..2a58c632d20 100644 --- a/src/Components/Medicine/CreatePrescriptionForm.tsx +++ b/src/Components/Medicine/CreatePrescriptionForm.tsx @@ -1,4 +1,4 @@ -import { FieldError, RequiredFieldValidator } from "../Form/FieldValidators"; +import { RequiredFieldValidator } from "../Form/FieldValidators"; import Form from "../Form/Form"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; @@ -11,6 +11,7 @@ import NumericWithUnitsFormField from "../Form/FormFields/NumericWithUnitsFormFi import { useTranslation } from "react-i18next"; import MedibaseAutocompleteFormField from "./MedibaseAutocompleteFormField"; import dayjs from "../../Utils/dayjs"; +import { PrescriptionFormValidator } from "./validators"; export default function CreatePrescriptionForm(props: { prescription: Prescription; @@ -40,16 +41,7 @@ export default function CreatePrescriptionForm(props: { } }} noPadding - validate={(form) => { - const errors: Partial> = {}; - errors.medicine_object = RequiredFieldValidator()(form.medicine_object); - errors.dosage = RequiredFieldValidator()(form.dosage); - if (form.is_prn) - errors.indicator = RequiredFieldValidator()(form.indicator); - if (!form.is_prn) - errors.frequency = RequiredFieldValidator()(form.frequency); - return errors; - }} + validate={PrescriptionFormValidator()} className="max-w-3xl" > {(field) => ( diff --git a/src/Components/Medicine/EditPrescriptionForm.tsx b/src/Components/Medicine/EditPrescriptionForm.tsx new file mode 100644 index 00000000000..d5261f70c7e --- /dev/null +++ b/src/Components/Medicine/EditPrescriptionForm.tsx @@ -0,0 +1,158 @@ +import { useState } from "react"; +import Form from "../Form/Form"; +import { Prescription } from "./models"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import * as Notification from "../../Utils/Notifications"; +import useSlug from "../../Common/hooks/useSlug"; +import { RequiredFieldValidator } from "../Form/FieldValidators"; +import { useTranslation } from "react-i18next"; +import { SelectFormField } from "../Form/FormFields/SelectFormField"; +import NumericWithUnitsFormField from "../Form/FormFields/NumericWithUnitsFormField"; +import { + PRESCRIPTION_FREQUENCIES, + PRESCRIPTION_ROUTES, +} from "./CreatePrescriptionForm"; +import TextFormField from "../Form/FormFields/TextFormField"; +import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; +import { EditPrescriptionFormValidator } from "./validators"; + +interface Props { + initial: Prescription; + onDone: (created: boolean) => void; +} + +const handleSubmit = async ( + consultation_external_id: string, + oldObj: Prescription, + { discontinued_reason, ...newObj }: Prescription +) => { + const discontinue = await request(routes.discontinuePrescription, { + pathParams: { consultation_external_id, external_id: oldObj.id }, + body: { + discontinued_reason: discontinued_reason + ? `Edit: ${discontinued_reason}` + : "Edited", + }, + }); + + if (discontinue.res?.status !== 200) { + Notification.Error({ + msg: "Failed to discontinue previous prescription", + }); + return; + } + + const { res } = await request(routes.createPrescription, { + pathParams: { consultation_external_id }, + body: { + ...newObj, + // Forcing the medicine to be the same as the old one + medicine: oldObj.medicine_object?.id, + medicine_old: oldObj.medicine_old, + }, + }); + + return res?.status === 201; +}; + +export default function EditPrescriptionForm(props: Props) { + const consultation = useSlug("consultation"); + const [isLoading, setIsLoading] = useState(false); + const { t } = useTranslation(); + + return ( + + disabled={isLoading} + defaults={props.initial} + onCancel={() => props.onDone(false)} + onSubmit={async (obj) => { + setIsLoading(true); + const success = await handleSubmit(consultation, props.initial, obj); + setIsLoading(false); + + if (success) { + props.onDone(true); + } + }} + noPadding + validate={EditPrescriptionFormValidator(props.initial)} + > + {(field) => ( + <> + + +
+ t("PRESCRIPTION_ROUTE_" + key)} + optionValue={(key) => key} + /> + +
+ + {props.initial.is_prn ? ( + <> + + + `${hours} hrs.`} + optionValue={(hours) => hours} + position="above" + /> + + ) : ( +
+ + t("PRESCRIPTION_FREQUENCY_" + key.toUpperCase()) + } + optionValue={([key]) => key} + /> + +
+ )} + + + + )} + + ); +} diff --git a/src/Components/Medicine/PrescriptionAdministrationsTable.tsx b/src/Components/Medicine/PrescriptionAdministrationsTable.tsx index 81282126d7c..6a018f8a4f3 100644 --- a/src/Components/Medicine/PrescriptionAdministrationsTable.tsx +++ b/src/Components/Medicine/PrescriptionAdministrationsTable.tsx @@ -20,6 +20,7 @@ import { formatTime, } from "../../Utils/utils"; import useRangePagination from "../../Common/hooks/useRangePagination"; +import EditPrescriptionForm from "./EditPrescriptionForm"; interface DateRange { start: Date; @@ -254,6 +255,7 @@ const PrescriptionRow = ({ prescription, ...props }: PrescriptionRowProps) => { const { t } = useTranslation(); // const [showActions, setShowActions] = useState(false); const [showDetails, setShowDetails] = useState(false); + const [showEdit, setShowEdit] = useState(false); const [showAdminister, setShowAdminister] = useState(false); const [showDiscontinue, setShowDiscontinue] = useState(false); const [administrations, setAdministrations] = @@ -342,6 +344,21 @@ const PrescriptionRow = ({ prescription, ...props }: PrescriptionRowProps) => { {t("discontinue")} + { + setShowDetails(false); + setShowEdit(true); + }} + > + + {t("edit")} + {
)} + {showEdit && ( + setShowEdit(false)} + show={showEdit} + title={`${t("edit")} ${t( + prescription.is_prn ? "prn_prescription" : "prescription_medication" + )}: ${ + prescription.medicine_object?.name ?? prescription.medicine_old + }`} + description={ +
+ + {t("edit_caution_note")} +
+ } + className="w-full max-w-3xl lg:min-w-[600px]" + > + { + setShowEdit(false); + if (success) { + props.refetch(); + } + }} + /> +
+ )} setShowDetails(true)} diff --git a/src/Components/Medicine/models.ts b/src/Components/Medicine/models.ts index 62aea46b6d2..0c49d199b21 100644 --- a/src/Components/Medicine/models.ts +++ b/src/Components/Medicine/models.ts @@ -1,7 +1,7 @@ import { PerformedByModel } from "../HCX/misc"; interface BasePrescription { - readonly id?: string; + readonly id: string; medicine?: string; medicine_object?: MedibaseMedicine; medicine_old?: string; diff --git a/src/Components/Medicine/validators.ts b/src/Components/Medicine/validators.ts new file mode 100644 index 00000000000..40261646d05 --- /dev/null +++ b/src/Components/Medicine/validators.ts @@ -0,0 +1,49 @@ +import { FieldError, RequiredFieldValidator } from "../Form/FieldValidators"; +import { FormErrors } from "../Form/Utils"; +import { Prescription } from "./models"; + +export const PrescriptionFormValidator = () => { + return (form: Prescription): FormErrors => { + const errors: Partial> = {}; + errors.medicine_object = RequiredFieldValidator()(form.medicine_object); + errors.dosage = RequiredFieldValidator()(form.dosage); + if (form.is_prn) + errors.indicator = RequiredFieldValidator()(form.indicator); + if (!form.is_prn) + errors.frequency = RequiredFieldValidator()(form.frequency); + return errors; + }; +}; + +export const EditPrescriptionFormValidator = (old: Prescription) => { + return (form: Prescription): FormErrors => { + const errors = PrescriptionFormValidator()(form); + + if (comparePrescriptions(old, form)) { + errors.$all = "No changes made"; + } + + return errors; + }; +}; + +const PRESCRIPTION_COMPARE_FIELDS: (keyof Prescription)[] = [ + "medicine", + "days", + "discontinued", + "dosage", + "frequency", + "indicator", + "is_prn", + "max_dosage", + "min_hours_between_doses", + "prescription_type", + "route", +]; + +export const comparePrescriptions = (a: Prescription, b: Prescription) => { + return ( + PRESCRIPTION_COMPARE_FIELDS.every((field) => a[field] === b[field]) && + a.medicine_object?.id === b.medicine_object?.id + ); +}; diff --git a/src/Locale/en/Medicine.json b/src/Locale/en/Medicine.json index 8cc80234b74..d32015e7618 100644 --- a/src/Locale/en/Medicine.json +++ b/src/Locale/en/Medicine.json @@ -33,7 +33,9 @@ "last_administered": "Last administered", "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.", "reason_for_discontinuation": "Reason for discontinuation", + "reason_for_edit": "Reason for edit", "PRESCRIPTION_ROUTE_ORAL": "Oral", "PRESCRIPTION_ROUTE_IV": "IV", "PRESCRIPTION_ROUTE_IM": "IM", @@ -47,4 +49,4 @@ "PRESCRIPTION_FREQUENCY_Q4H": "4th hourly", "PRESCRIPTION_FREQUENCY_QOD": "Alternate day", "PRESCRIPTION_FREQUENCY_QWK": "Once a week" -} +} \ No newline at end of file diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index f26457183dd..9cf9a0b6643 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -1,6 +1,7 @@ import { IConfig } from "../Common/hooks/useConfig"; import { AssetData } from "../Components/Assets/AssetTypes"; import { LocationModel } from "../Components/Facility/models"; +import { Prescription } from "../Components/Medicine/models"; import { UserModel } from "../Components/Users/models"; import { PaginatedResponse } from "../Utils/request/types"; @@ -985,6 +986,8 @@ const routes = { createPrescription: { path: "/api/v1/consultation/{consultation_external_id}/prescriptions/", method: "POST", + TBody: Type(), + TRes: Type(), }, listAdministrations: { @@ -1010,6 +1013,8 @@ const routes = { discontinuePrescription: { path: "/api/v1/consultation/{consultation_external_id}/prescriptions/{external_id}/discontinue/", method: "POST", + TBody: Type<{ discontinued_reason: string }>(), + TRes: Type>(), }, // HCX Endpoints From 4f0246915be891ba3c598d19317c8d1dc55e92e8 Mon Sep 17 00:00:00 2001 From: Suprabath <34211797+suprabathk@users.noreply.github.com> Date: Wed, 4 Oct 2023 18:50:07 +0530 Subject: [PATCH 11/65] Assets date picker (#6338) * Using DateFormField instead of DateInputV2 * Using Label from DateFormField * Prevent Datepicker focus * Removed unused Ref * Using formatDate instead of .format * Added translations * Added translations for AssetServiceEditModal * Added translations for AssetCreate --------- Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> Co-authored-by: Khavin Shankar --- .../Assets/AssetServiceEditModal.tsx | 60 +++++++++----- src/Components/Common/DateInputV2.tsx | 8 +- src/Components/Facility/AssetCreate.tsx | 81 ++++++++++--------- src/Locale/en/Asset.json | 14 +++- src/Locale/en/Facility.json | 34 +++++++- 5 files changed, 130 insertions(+), 67 deletions(-) diff --git a/src/Components/Assets/AssetServiceEditModal.tsx b/src/Components/Assets/AssetServiceEditModal.tsx index 66d44d11907..690524e471b 100644 --- a/src/Components/Assets/AssetServiceEditModal.tsx +++ b/src/Components/Assets/AssetServiceEditModal.tsx @@ -7,10 +7,10 @@ import DialogModal from "../Common/Dialog"; import { AssetData, AssetService, AssetServiceEdit } from "./AssetTypes"; import dayjs from "dayjs"; import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; -import DateInputV2 from "../Common/DateInputV2"; -import { FieldLabel } from "../Form/FormFields/FormField"; import { formatDate, formatDateTime } from "../../Utils/utils"; import CareIcon from "../../CAREUI/icons/CareIcon"; +import DateFormField from "../Form/FormFields/DateFormField"; +import { t } from "i18next"; export const AssetServiceEditModal = (props: { asset?: AssetData; @@ -61,12 +61,12 @@ export const AssetServiceEditModal = (props: {

- Update record for asset + {t("update_record_for_asset")} {props.asset?.name}

@@ -110,13 +110,17 @@ export const AssetServiceEditModal = (props: {
-

Edited On

+

+ {t("edited_on")} +

{formatDateTime(editRecord.edited_on)}

-

Edited By

+

+ {t("edited_by")} +

{editRecord.edited_by.username}

@@ -125,7 +129,7 @@ export const AssetServiceEditModal = (props: {

- Serviced On + {t("serviced_on")}

-

Notes

+

+ {t("notes")} +

{editRecord.note || "-"}

@@ -151,7 +157,7 @@ export const AssetServiceEditModal = (props: { editRecord ? setEditRecord(undefined) : props.handleClose(); }} > - {editRecord ? "Back" : "Close"} + {editRecord ? t("back") : t("close")}
@@ -163,12 +169,12 @@ export const AssetServiceEditModal = (props: {

- Update record for asset + {t("update_record_for_asset")} {props.asset?.name}

@@ -178,19 +184,31 @@ export const AssetServiceEditModal = (props: { className="col-span-6 sm:col-span-3" data-testid="asset-last-serviced-on-input" > - Serviced On - { - setForm({ - ...form, - serviced_on: dayjs(date).format("YYYY-MM-DD"), - }); + if ( + dayjs(date.value).format("YYYY-MM-DD") > + new Date( + props.service_record.created_date + ).toLocaleDateString("en-ca") + ) { + Notification.Error({ + msg: `Service date can't be after ${formatDate( + props.service_record.created_date + )} (Creation date)`, + }); + } else { + setForm({ + ...form, + serviced_on: dayjs(date.value).format("YYYY-MM-DD"), + }); + } }} - max={new Date(props.service_record.created_date)} />
@@ -198,8 +216,8 @@ export const AssetServiceEditModal = (props: { { setForm({ ...form, note: e.value }); @@ -210,7 +228,7 @@ export const AssetServiceEditModal = (props: {
diff --git a/src/Components/Common/DateInputV2.tsx b/src/Components/Common/DateInputV2.tsx index bcebd4e0055..7036d5c8bfb 100644 --- a/src/Components/Common/DateInputV2.tsx +++ b/src/Components/Common/DateInputV2.tsx @@ -1,4 +1,4 @@ -import { MutableRefObject, useEffect, useRef, useState } from "react"; +import { MutableRefObject, useEffect, useState } from "react"; import { addMonths, addYears, @@ -60,7 +60,6 @@ const DateInputV2: React.FC = ({ const [displayValue, setDisplayValue] = useState( value ? dayjs(value).format("DDMMYYYY") : "" ); - const popover = useRef(null); const decrement = () => { switch (type) { @@ -241,7 +240,6 @@ const DateInputV2: React.FC = ({ onBlur={() => { setIsOpen?.(false); }} - ref={popover} static className={classNames( "cui-dropdown-base absolute mt-0.5 w-72 divide-y-0 p-4", @@ -252,10 +250,6 @@ const DateInputV2: React.FC = ({ { - popover.current?.focus(); - e.preventDefault(); - }} className="cui-input-base bg-gray-50" value={ displayValue.replace( diff --git a/src/Components/Facility/AssetCreate.tsx b/src/Components/Facility/AssetCreate.tsx index 156d738857a..beafe8d33c8 100644 --- a/src/Components/Facility/AssetCreate.tsx +++ b/src/Components/Facility/AssetCreate.tsx @@ -37,7 +37,8 @@ import useVisibility from "../../Utils/useVisibility"; import { validateEmailAddress } from "../../Common/validation"; import { dateQueryString, parsePhoneNumber } from "../../Utils/utils.js"; import dayjs from "../../Utils/dayjs"; -import DateInputV2 from "../Common/DateInputV2.js"; +import DateFormField from "../Form/FormFields/DateFormField.js"; +import { t } from "i18next"; const Loading = lazy(() => import("../Common/Loading")); @@ -404,7 +405,7 @@ const AssetCreate = (props: AssetProps) => { if (locations.length === 0) { return ( {

- You need at least a location to create an assest. + {t("you_need_at_least_a_location_to_create_an_assest")}

@@ -440,7 +441,8 @@ const AssetCreate = (props: AssetProps) => { onClick={() => setIsScannerActive(false)} className="btn btn-default mb-2" > - Close Scanner + + {t("close_scanner")} { } style={{ width: "100%" }} /> -

Scan Asset QR!

+

+ {t("scan_asset_qr")} +

); @@ -479,7 +483,7 @@ const AssetCreate = (props: AssetProps) => { return (
{ > setName(value)} @@ -544,7 +548,7 @@ const AssetCreate = (props: AssetProps) => { {/* Location */} - Asset Location + {t("asset_location")}
{ data-testid="asset-type-input" > { { > setDescription(value)} error={state.errors.description} @@ -664,7 +668,7 @@ const AssetCreate = (props: AssetProps) => { className="col-span-6" required name="is_working" - label="Working Status" + label={t("working_status")} options={["true", "false"]} optionLabel={(option) => { return ( @@ -692,8 +696,8 @@ const AssetCreate = (props: AssetProps) => { > setNotWorkingReason(e.value)} error={state.errors.not_working_reason} @@ -717,7 +721,7 @@ const AssetCreate = (props: AssetProps) => { id="qr_code_id" name="qr_code_id" placeholder="" - label="Asset QR ID" + label={t("asset_qr_id")} value={qrCodeId} onChange={(e) => setQrCodeId(e.value)} error={state.errors.qr_code_id} @@ -743,9 +747,9 @@ const AssetCreate = (props: AssetProps) => { setManufacturer(e.value)} error={state.errors.manufacturer} /> @@ -760,7 +764,7 @@ const AssetCreate = (props: AssetProps) => { { const value = dayjs(event.value); @@ -788,8 +792,8 @@ const AssetCreate = (props: AssetProps) => { setSupportName(e.value)} error={state.errors.support_name} @@ -804,7 +808,7 @@ const AssetCreate = (props: AssetProps) => { > setSupportPhone(e.value)} @@ -822,8 +826,8 @@ const AssetCreate = (props: AssetProps) => { setSupportEmail(e.value)} error={state.errors.support_email} @@ -841,9 +845,9 @@ const AssetCreate = (props: AssetProps) => { setVendorName(e.value)} error={state.errors.vendor_name} /> @@ -858,7 +862,7 @@ const AssetCreate = (props: AssetProps) => { setSerialNumber(e.value)} error={state.errors.serial_number} @@ -874,25 +878,26 @@ const AssetCreate = (props: AssetProps) => { ref={fieldRef["last_serviced_on"]} data-testid="asset-last-serviced-on-input" > - Last Serviced On - { if ( - dayjs(date).format("YYYY-MM-DD") > + dayjs(date.value).format("YYYY-MM-DD") > new Date().toLocaleDateString("en-ca") ) { Notification.Error({ msg: "Last Serviced date can't be in future", }); } else { - setLastServicedOn(dayjs(date).format("YYYY-MM-DD")); + setLastServicedOn( + dayjs(date.value).format("YYYY-MM-DD") + ); } }} - max={new Date()} /> { > setNotes(e.value)} error={state.errors.notes} @@ -928,13 +935,13 @@ const AssetCreate = (props: AssetProps) => { /> handleSubmit(e, false)} - label={assetId ? "Update" : "Create Asset"} + label={assetId ? t("update") : t("create_asset")} /> {!assetId && ( handleSubmit(e, true)} - label="Create & Add More" + label={t("create_add_more")} /> )}
diff --git a/src/Locale/en/Asset.json b/src/Locale/en/Asset.json index cf13de5cd47..f24549ee0b6 100644 --- a/src/Locale/en/Asset.json +++ b/src/Locale/en/Asset.json @@ -1,3 +1,15 @@ { - "create_asset": "Create Asset" + "create_asset": "Create Asset", + "edit_history": "Edit History", + "update_record_for_asset": "Update record for asset", + "edited_on": "Edited On", + "edited_by": "Edited By", + "serviced_on": "Serviced On", + "notes": "Notes", + "back": "Back", + "close": "Close", + "update_asset_service_record": "Update Asset Service Record", + "eg_details_on_functionality_service_etc": "Eg. Details on functionality, service, etc.", + "updating": "Updating", + "update": "Update" } diff --git a/src/Locale/en/Facility.json b/src/Locale/en/Facility.json index aa44d86dda5..5e69f8108af 100644 --- a/src/Locale/en/Facility.json +++ b/src/Locale/en/Facility.json @@ -20,5 +20,37 @@ "type_b_cylinders": "B Type Cylinders", "type_c_cylinders": "C Type Cylinders", "type_d_cylinders": "D Type Cylinders", - "select_local_body": "Select Local Body" + "select_local_body": "Select Local Body", + "update_asset": "Update Asset", + "create_new_asset": "Create New Asset", + "you_need_at_least_a_location_to_create_an_assest": "You need at least a location to create an assest.", + "add_location": "Add Location", + "close_scanner": "Close Scanner", + "scan_asset_qr": "Scan Asset QR!", + "update": "Update", + "create": "Create", + "asset_name": "Asset Name", + "asset_location": "Asset Location", + "asset_type": "Asset Type", + "asset_class": "Asset Class", + "description": "Description", + "details_about_the_equipment": "Details about the equipment", + "working_status": "Working Status", + "why_the_asset_is_not_working": "Why the asset is not working?", + "describe_why_the_asset_is_not_working": "Describe why the asset is not working", + "asset_qr_id": "Asset QR ID", + "manufacturer": "Manufacturer", + "eg_xyz": "Eg. XYZ", + "eg_abc": "Eg. ABC", + "warranty_amc_expiry": "Warranty / AMC Expiry", + "customer_support_name": "Customer Support Name", + "customer_support_number": "Customer support number", + "customer_support_email": "Customer Support Email", + "eg_mail_example_com": "Eg. mail@example.com", + "vendor_name": "Vendor Name", + "serial_number": "Serial Number", + "last_serviced_on": "Last Serviced On", + "notes": "Notes", + "create_asset": "Create Asset", + "create_add_more": "Create & Add More" } From 7df0f13ea5f3a22dc8c9c4570f675e09dae1450a Mon Sep 17 00:00:00 2001 From: Pranshu Aggarwal <70687348+Pranshu1902@users.noreply.github.com> Date: Wed, 4 Oct 2023 19:58:46 +0530 Subject: [PATCH 12/65] Add show unread notifications filter button (#6356) * add show unread filter button * refactor Co-authored-by: Khavin Shankar --------- Co-authored-by: Khavin Shankar --- .../Notifications/NotificationsList.tsx | 64 ++++++++++++------- src/Locale/en/Notifications.json | 2 + 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/src/Components/Notifications/NotificationsList.tsx b/src/Components/Notifications/NotificationsList.tsx index f6afa6cccd8..5e3aa65b3ec 100644 --- a/src/Components/Notifications/NotificationsList.tsx +++ b/src/Components/Notifications/NotificationsList.tsx @@ -165,6 +165,7 @@ export default function NotificationsList({ const [isMarkingAllAsRead, setIsMarkingAllAsRead] = useState(false); const [isSubscribed, setIsSubscribed] = useState(""); const [isSubscribing, setIsSubscribing] = useState(false); + const [showUnread, setShowUnread] = useState(false); const { t } = useTranslation(); useEffect(() => { @@ -351,33 +352,37 @@ export default function NotificationsList({ } else if (data?.length) { manageResults = ( <> - {data.map((result: any) => ( - - ))} + {data + .filter((notification: any) => showUnread ? notification.read_at === null : true) + .map((result: any) => ( + + ))} {isLoading && (
)} - {totalCount > RESULT_LIMIT && offset < totalCount - RESULT_LIMIT && ( -
- setOffset((prev) => prev + RESULT_LIMIT)} - > - {isLoading ? t("loading") : t("load_more")} - -
- )} + {!showUnread && + totalCount > RESULT_LIMIT && + offset < totalCount - RESULT_LIMIT && ( +
+ setOffset((prev) => prev + RESULT_LIMIT)} + > + {isLoading ? t("loading") : t("load_more")} + +
+ )} ); } else if (data && data.length === 0) { @@ -448,6 +453,21 @@ export default function NotificationsList({ /> {t("mark_all_as_read")} + setShowUnread(!showUnread)} + > + + + + {showUnread + ? t("show_all_notifications") + : t("show_unread_notifications")} + +
Date: Thu, 5 Oct 2023 12:48:31 +0530 Subject: [PATCH 13/65] fixes #6397; reset is working state on asset create more (#6398) Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> --- src/Components/Facility/AssetCreate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Facility/AssetCreate.tsx b/src/Components/Facility/AssetCreate.tsx index beafe8d33c8..84fc09188d0 100644 --- a/src/Components/Facility/AssetCreate.tsx +++ b/src/Components/Facility/AssetCreate.tsx @@ -306,7 +306,7 @@ const AssetCreate = (props: AssetProps) => { setLocation(""); setAssetType(assetTypeInitial); setAssetClass(assetClassInitial); - setIsWorking(""); + setIsWorking(undefined); setNotWorkingReason(""); setSerialNumber(""); setVendorName(""); From 8473f350b5a03a8c2eeef365d4b1a670ba0ec7f4 Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Thu, 5 Oct 2023 12:49:48 +0530 Subject: [PATCH 14/65] Show asset import progress (#6400) * Show asset import progress * Fix cypress --- cypress/support/commands.ts | 2 +- src/Components/Assets/AssetImportModal.tsx | 68 ++++++++++++++-------- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 7f5484564fc..2dd8c477233 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -81,7 +81,7 @@ Cypress.Commands.add( Cypress.Commands.add("verifyNotification", (text) => { cy.get(".pnotify-container").should("exist").contains(text); - return cy.get(".pnotify-container").click({ force: true }); + return cy.get(".pnotify-container").contains(text).click({ force: true }); }); Cypress.on("uncaught:exception", () => { diff --git a/src/Components/Assets/AssetImportModal.tsx b/src/Components/Assets/AssetImportModal.tsx index 7382c7eff4f..54ac25e2ac8 100644 --- a/src/Components/Assets/AssetImportModal.tsx +++ b/src/Components/Assets/AssetImportModal.tsx @@ -8,7 +8,6 @@ import { Cancel, Submit } from "../Common/components/ButtonV2"; import { listFacilityAssetLocation } from "../../Redux/actions"; import { useDispatch } from "react-redux"; import { Link } from "raviger"; -import SelectMenuV2 from "../Form/SelectMenuV2"; import readXlsxFile from "read-excel-file"; import { LocalStorageKeys, @@ -17,6 +16,7 @@ import { import { parseCsvFile } from "../../Utils/utils"; import useConfig from "../../Common/hooks/useConfig"; import DialogModal from "../Common/Dialog"; +import { SelectFormField } from "../Form/FormFields/SelectFormField"; interface Props { open: boolean; @@ -25,14 +25,18 @@ interface Props { } const AssetImportModal = ({ open, onClose, facility }: Props) => { - const [isImporting, setIsUploading] = useState(false); + const [isImporting, setIsImporting] = useState(false); const [selectedFile, setSelectedFile] = useState(); const [preview, setPreview] = useState<(AssetData & { notes?: string; last_serviced_on?: string })[]>(); const [location, setLocation] = useState(""); + const [errors, setErrors] = useState({ + location: "", + }); const [locations, setLocations] = useState([]); const dispatchAction: any = useDispatch(); const { sample_format_asset_import } = useConfig(); + const [locationsLoading, setLocationsLoading] = useState(false); const closeModal = () => { setPreview(undefined); @@ -41,9 +45,11 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { }; useEffect(() => { + setLocationsLoading(true); dispatchAction( listFacilityAssetLocation({}, { facility_external_id: facility.id }) ).then(({ data }: any) => { + setLocationsLoading(false); if (data.count > 0) { setLocations(data.results); } @@ -110,7 +116,16 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { closeModal(); return; } + if (!location) { + setErrors({ + ...errors, + location: "Please select a location", + }); + return; + } + setIsImporting(true); let error = false; + Notification.Success({ msg: "Importing assets..." }); for (const asset of preview || []) { const asset_data: any = { @@ -156,16 +171,22 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { }); error = true; } else { - if (preview) setPreview(preview.filter((a) => a.id !== asset.id)); + setPreview((preview) => { + return preview?.slice(1); + }); } } if (!error) { Notification.Success({ msg: "Assets imported successfully" }); await sleep(1000); - setIsUploading(false); - closeModal(); + setIsImporting(false); window.location.reload(); - } else Notification.Error({ msg: "Error importing some assets" }); + } else { + Notification.Error({ msg: "Error importing some assets" }); + await sleep(1000); + setIsImporting(false); + closeModal(); + } }; const dragProps = useDragAndDrop(); @@ -193,7 +214,7 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { fixedWidth={false} > {facility.name} - {locations.length === 0 ? ( + {!locationsLoading && locations.length === 0 ? ( <>

@@ -218,31 +239,28 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { {preview && preview?.length > 0 ? (

- {preview.length} assets will be imported + {preview.length} assets {isImporting ? "are being" : "will be"}{" "} + imported

-
From 64c0b248499382fcd01bc4f5c225b69fb4ce0841 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Thu, 5 Oct 2023 12:52:06 +0530 Subject: [PATCH 15/65] prescription, freeze columns and shrink discontinued (#6389) --- .../PrescriptionAdministrationsTable.tsx | 123 ++++++++++++------ src/Redux/actions.tsx | 2 +- 2 files changed, 83 insertions(+), 42 deletions(-) diff --git a/src/Components/Medicine/PrescriptionAdministrationsTable.tsx b/src/Components/Medicine/PrescriptionAdministrationsTable.tsx index 6a018f8a4f3..c80b66f44c6 100644 --- a/src/Components/Medicine/PrescriptionAdministrationsTable.tsx +++ b/src/Components/Medicine/PrescriptionAdministrationsTable.tsx @@ -48,6 +48,8 @@ export default function PrescriptionAdministrationsTable({ const { t } = useTranslation(); const [state, setState] = useState(); + const [showDiscontinued, setShowDiscontinued] = useState(false); + const [discontinuedCount, setDiscontinuedCount] = useState(); const pagination = useRangePagination({ bounds: state?.administrationsTimeBounds ?? { start: new Date(), @@ -65,8 +67,13 @@ export default function PrescriptionAdministrationsTable({ ); const refetch = useCallback(async () => { + const filters = { + is_prn: prn, + prescription_type: "REGULAR", + }; + const res = await dispatch( - list({ is_prn: prn, prescription_type: "REGULAR" }) + list(showDiscontinued ? filters : { ...filters, discontinued: false }) ); setState({ @@ -75,7 +82,14 @@ export default function PrescriptionAdministrationsTable({ ), administrationsTimeBounds: getAdministrationBounds(res.data.results), }); - }, [consultation_id, dispatch]); + + if (showDiscontinued === false) { + const discontinuedRes = await dispatch( + list({ ...filters, discontinued: true, limit: 0 }) + ); + setDiscontinuedCount(discontinuedRes.data.count); + } + }, [consultation_id, showDiscontinued, dispatch]); useEffect(() => { refetch(); @@ -142,17 +156,22 @@ export default function PrescriptionAdministrationsTable({ } /> -
- +
+
- - -
{t("medicine")} -

Dosage &

-

- {!state?.prescriptions[0]?.is_prn ? "Frequency" : "Indicator"} -

+
+
+ {t("medicine")} + +

Dosage &

+

+ {!state?.prescriptions[0]?.is_prn + ? "Frequency" + : "Indicator"} +

+
+
@@ -165,6 +184,8 @@ export default function PrescriptionAdministrationsTable({ variant="secondary" disabled={!pagination.hasPrevious} onClick={pagination.previous} + tooltip="Previous 24 hours" + tooltipClassName="tooltip-bottom -translate-x-1/2 text-xs" > @@ -206,6 +227,8 @@ export default function PrescriptionAdministrationsTable({ variant="secondary" disabled={!pagination.hasNext} onClick={pagination.next} + tooltip="Next 24 hours" + tooltipClassName="tooltip-bottom -translate-x-1/2 text-xs" > @@ -228,6 +251,23 @@ export default function PrescriptionAdministrationsTable({
+ {showDiscontinued === false && !!discontinuedCount && ( + setShowDiscontinued(true)} + > + + + + Show {discontinuedCount} other discontinued + prescription(s) + + + + )} + {state?.prescriptions.length === 0 && (
@@ -287,8 +327,7 @@ const PrescriptionRow = ({ prescription, ...props }: PrescriptionRowProps) => { return ( {showDiscontinue && ( @@ -402,42 +441,44 @@ const PrescriptionRow = ({ prescription, ...props }: PrescriptionRowProps) => { )} setShowDetails(true)} > -
- +
+ + {prescription.medicine_object?.name ?? prescription.medicine_old} + + + {prescription.discontinued && ( + + {t("discontinued")} + )} - > - {prescription.medicine_object?.name ?? prescription.medicine_old} - - {prescription.discontinued && ( - - {t("discontinued")} - - )} + {prescription.route && ( + + {t(prescription.route)} + + )} +
- {prescription.route && ( - - {t(prescription.route)} - - )} +
+

{prescription.dosage}

+

+ {!prescription.is_prn + ? t("PRESCRIPTION_FREQUENCY_" + prescription.frequency) + : prescription.indicator} +

+
- -

{prescription.dosage}

-

- {!prescription.is_prn - ? t("PRESCRIPTION_FREQUENCY_" + prescription.frequency) - : prescription.indicator} -

- - {/* Administration Cells */} {props.intervals.map(({ start, end }, index) => ( diff --git a/src/Redux/actions.tsx b/src/Redux/actions.tsx index 7d9779e7cd6..26e006f4cea 100644 --- a/src/Redux/actions.tsx +++ b/src/Redux/actions.tsx @@ -1003,7 +1003,7 @@ export const PrescriptionActions = (consultation_external_id: string) => { const pathParams = { consultation_external_id }; return { - list: (query?: Partial) => { + list: (query?: Record) => { let altKey; if (query?.is_prn !== undefined) { altKey = query?.is_prn From 91abee0b8ba7f0922a68999bd39bd0a6a1888879 Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Fri, 6 Oct 2023 16:20:11 +0530 Subject: [PATCH 16/65] Fix password reset (#6404) * Fix password reset * fix response status check * Update ResetPassword.tsx --- src/Components/Auth/Login.tsx | 2 +- src/Components/Auth/ResetPassword.tsx | 4 ++-- src/Redux/api.tsx | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Components/Auth/Login.tsx b/src/Components/Auth/Login.tsx index dd6a42d3983..140a0013fd9 100644 --- a/src/Components/Auth/Login.tsx +++ b/src/Components/Auth/Login.tsx @@ -151,7 +151,7 @@ export const Login = (props: { forgot?: boolean }) => { body: { ...valid }, }); setLoading(false); - if (res && res.statusText === "OK") { + if (res?.ok) { Notification.Success({ msg: t("password_sent"), }); diff --git a/src/Components/Auth/ResetPassword.tsx b/src/Components/Auth/ResetPassword.tsx index 2f02737f6de..47d120e1a97 100644 --- a/src/Components/Auth/ResetPassword.tsx +++ b/src/Components/Auth/ResetPassword.tsx @@ -72,7 +72,7 @@ export const ResetPassword = (props: any) => { const { res, error } = await request(routes.resetPassword, { body: { ...valid }, }); - if (res && res.statusText === "OK") { + if (res?.ok) { localStorage.removeItem(LocalStorageKeys.accessToken); Notification.Success({ msg: t("password_reset_success"), @@ -89,7 +89,7 @@ export const ResetPassword = (props: any) => { const { res } = await request(routes.checkResetToken, { body: { token: props.token }, }); - if (!res || res.statusText !== "OK") { + if (!res || !res.ok) { navigate("/invalid-reset"); } }; diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 9cf9a0b6643..8a4ca5cf1df 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -55,6 +55,7 @@ const routes = { checkResetToken: { path: "/api/v1/password_reset/check/", method: "POST", + noAuth: true, TRes: Type>(), TBody: Type<{ token: string }>(), }, @@ -62,6 +63,7 @@ const routes = { resetPassword: { path: "/api/v1/password_reset/confirm/", method: "POST", + noAuth: true, TRes: Type>(), TBody: Type<{ password: string; confirm: string }>(), }, @@ -69,6 +71,7 @@ const routes = { forgotPassword: { path: "/api/v1/password_reset/", method: "POST", + noAuth: true, TRes: Type>(), TBody: Type<{ username: string }>(), }, From 9d26e02aee3ae0b012fefb2aad9f796aa8e04e64 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Fri, 6 Oct 2023 17:40:51 +0530 Subject: [PATCH 17/65] Vitals Monitor: Shows relative time for blood pressure and hide if stale (30 mins) (#6407) * shows relative time for bp (part of #6393) * add comments --- .../VitalsMonitor/HL7PatientVitalsMonitor.tsx | 36 ++++++++++++++++--- src/Components/VitalsMonitor/types.ts | 2 +- .../VitalsMonitor/useHL7VitalsMonitor.ts | 3 +- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/Components/VitalsMonitor/HL7PatientVitalsMonitor.tsx b/src/Components/VitalsMonitor/HL7PatientVitalsMonitor.tsx index 1b45fd80ddc..44cadd8263c 100644 --- a/src/Components/VitalsMonitor/HL7PatientVitalsMonitor.tsx +++ b/src/Components/VitalsMonitor/HL7PatientVitalsMonitor.tsx @@ -8,6 +8,15 @@ import { classNames } from "../../Utils/utils"; import { IVitalsComponentProps, VitalsValueBase } from "./types"; import { triggerGoal } from "../../Integrations/Plausible"; import useAuthUser from "../../Common/hooks/useAuthUser"; +import dayjs from "dayjs"; + +const minutesAgo = (timestamp: string) => { + return `${dayjs().diff(dayjs(timestamp), "minute")}m ago`; +}; + +const isWithinMinutes = (timestamp: string, minutes: number) => { + return dayjs().diff(dayjs(timestamp), "minute") < minutes; +}; export default function HL7PatientVitalsMonitor(props: IVitalsComponentProps) { const { connect, waveformCanvas, data, isOnline } = useHL7VitalsMonitor( @@ -30,6 +39,10 @@ export default function HL7PatientVitalsMonitor(props: IVitalsComponentProps) { connect(props.socketUrl); }, [props.socketUrl]); + const bpWithinMaxPersistence = !!( + (data.bp?.["date-time"] && isWithinMinutes(data.bp?.["date-time"], 30)) // Max blood pressure persistence is 30 minutes + ); + return (
{props.patientAssetBed && ( @@ -97,24 +110,37 @@ export default function HL7PatientVitalsMonitor(props: IVitalsComponentProps) { {/* Blood Pressure */}
-
+
NIBP - {data.bp?.systolic.unit ?? "--"} + + {bpWithinMaxPersistence ? data.bp?.systolic.unit ?? "--" : "--"} + + + {data.bp?.["date-time"] && minutesAgo(data.bp?.["date-time"])} +
Sys / Dia
- {data.bp?.systolic.value ?? "--"} + + {bpWithinMaxPersistence + ? data.bp?.systolic.value ?? "--" + : "--"} + / - {data.bp?.diastolic.value ?? "--"} + + {bpWithinMaxPersistence + ? data.bp?.diastolic.value ?? "--" + : "--"} +
Mean - {data.bp?.map.value ?? "--"} + {bpWithinMaxPersistence ? data.bp?.map.value ?? "--" : "--"}
diff --git a/src/Components/VitalsMonitor/types.ts b/src/Components/VitalsMonitor/types.ts index 066b7a7cc78..60979a6f9b0 100644 --- a/src/Components/VitalsMonitor/types.ts +++ b/src/Components/VitalsMonitor/types.ts @@ -8,7 +8,7 @@ export interface VitalsDataBase { "patient-name": string; } -export interface VitalsValueBase { +export interface VitalsValueBase extends VitalsDataBase { value: number; unit: string; interpretation: string; diff --git a/src/Components/VitalsMonitor/useHL7VitalsMonitor.ts b/src/Components/VitalsMonitor/useHL7VitalsMonitor.ts index 8b74a2d05d2..ed16cc2edfd 100644 --- a/src/Components/VitalsMonitor/useHL7VitalsMonitor.ts +++ b/src/Components/VitalsMonitor/useHL7VitalsMonitor.ts @@ -8,11 +8,12 @@ import useCanvas from "../../Common/hooks/useCanvas"; import { ChannelOptions, IVitalsComponentProps, + VitalsDataBase, VitalsValueBase as VitalsValue, } from "./types"; import { getChannel, getVitalsCanvasSizeAndDuration } from "./utils"; -interface VitalsBPValue { +interface VitalsBPValue extends VitalsDataBase { systolic: VitalsValue; diastolic: VitalsValue; map: VitalsValue; From 6558eb732ff211bd65392c7066085abae760f5c2 Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Fri, 6 Oct 2023 21:18:00 +0530 Subject: [PATCH 18/65] Fix date range end picker (#6413) --- src/Components/Common/DateInputV2.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Components/Common/DateInputV2.tsx b/src/Components/Common/DateInputV2.tsx index 7036d5c8bfb..12009cb495f 100644 --- a/src/Components/Common/DateInputV2.tsx +++ b/src/Components/Common/DateInputV2.tsx @@ -113,6 +113,7 @@ const DateInputV2: React.FC = ({ ) ); close(); + setIsOpen?.(false); }; const getDayCount = (date: Date) => { @@ -212,13 +213,7 @@ const DateInputV2: React.FC = ({ {({ open, close }) => (
- { - setIsOpen?.(!isOpen); - }} - > + = ({ {(open || isOpen) && ( { - setIsOpen?.(false); - }} static className={classNames( "cui-dropdown-base absolute mt-0.5 w-72 divide-y-0 p-4", From 83597f1ad315dd9b2f5b2557fd2b16e51f3555fb Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Fri, 6 Oct 2023 21:18:35 +0530 Subject: [PATCH 19/65] cypress fix: select non group ICD11 entity; fixes #6411 (#6412) --- cypress/pageobject/Patient/PatientConsultation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cypress/pageobject/Patient/PatientConsultation.ts b/cypress/pageobject/Patient/PatientConsultation.ts index 6937559d548..2eb6550cb8b 100644 --- a/cypress/pageobject/Patient/PatientConsultation.ts +++ b/cypress/pageobject/Patient/PatientConsultation.ts @@ -49,7 +49,7 @@ export class PatientConsultationPage { .click() .type("1A"); cy.get("#icd11_diagnoses_object [role='option']") - .contains("1A03 Intestinal infections due to Escherichia coli") + .contains("1A00 Cholera") .scrollIntoView() .click(); cy.get("label[for='icd11_diagnoses_object']").click(); @@ -57,7 +57,7 @@ export class PatientConsultationPage { cy.get("#icd11_principal_diagnosis [role='combobox']").click().type("1A"); cy.get("#icd11_principal_diagnosis [role='option']") - .contains("1A03 Intestinal infections due to Escherichia coli") + .contains("1A00 Cholera") .click(); cy.get("#consultation_notes").click().type(consulationNotes); From 73f49f969f0f6daea77e907f9e774b13dc58a254 Mon Sep 17 00:00:00 2001 From: Onkar Jadhav Date: Sat, 7 Oct 2023 12:38:38 +0530 Subject: [PATCH 20/65] Optimize lodash. Fixes #6006 --- package-lock.json | 18 ++++++++++++++++-- package.json | 4 ++-- src/Common/hooks/useAsyncOptions.ts | 2 +- src/Components/Common/Uptime.tsx | 2 +- .../ExternalResult/ExternalResultUpload.tsx | 4 ++-- .../Investigations/InvestigationTable.tsx | 1 - .../Facility/Investigations/Reports/index.tsx | 4 ++-- .../Facility/Investigations/Reports/utils.tsx | 12 ++++++------ .../Investigations/ShowInvestigation.tsx | 6 +++--- .../Facility/Investigations/Table.tsx | 4 ++-- src/Components/Facility/LegacyFacilityCNS.tsx | 2 +- src/Components/Form/AutoCompleteAsync.tsx | 2 +- src/Components/Form/Form.tsx | 2 +- src/Components/Patient/PatientRegister.tsx | 2 +- src/Components/Patient/SampleDetails.tsx | 10 +++++----- src/Components/Patient/SampleTestCard.tsx | 6 +++--- src/Redux/fireRequest.tsx | 2 +- src/Utils/Notifications.js | 4 ++-- 18 files changed, 50 insertions(+), 37 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2a9fe3f520f..3a92ede81e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "events": "^3.3.0", "i18next": "^23.2.7", "i18next-browser-languagedetector": "^7.1.0", - "lodash": "^4.17.21", + "lodash-es": "^4.17.21", "postcss-loader": "^7.3.3", "qrcode.react": "^3.1.0", "raviger": "^4.1.2", @@ -73,7 +73,7 @@ "@types/cypress": "^1.1.3", "@types/echarts": "^4.9.18", "@types/google.maps": "^3.53.4", - "@types/lodash": "^4.14.195", + "@types/lodash-es": "^4.17.9", "@types/lodash.get": "^4.4.7", "@types/node": "^20.4.0", "@types/prop-types": "*", @@ -5483,6 +5483,15 @@ "integrity": "sha512-22y3o88f4a94mKljsZcanlNWPzO0uBsBdzLAngf2tp533LzZcQzb6+eZPJ+vCTt+bqF2XnvT9gejTLsAcJAJyQ==", "dev": true }, + "node_modules/@types/lodash-es": { + "version": "4.17.9", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.9.tgz", + "integrity": "sha512-ZTcmhiI3NNU7dEvWLZJkzG6ao49zOIjEgIE0RgV7wbPxU0f2xT3VSAHw2gmst8swH6V0YkLRGp4qPlX/6I90MQ==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/lodash.get": { "version": "4.4.7", "resolved": "https://registry.npmjs.org/@types/lodash.get/-/lodash.get-4.4.7.tgz", @@ -12454,6 +12463,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "node_modules/lodash.castarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", diff --git a/package.json b/package.json index 246734dc3d4..08400b51f55 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "events": "^3.3.0", "i18next": "^23.2.7", "i18next-browser-languagedetector": "^7.1.0", - "lodash": "^4.17.21", + "lodash-es": "^4.17.21", "postcss-loader": "^7.3.3", "qrcode.react": "^3.1.0", "raviger": "^4.1.2", @@ -113,7 +113,7 @@ "@types/cypress": "^1.1.3", "@types/echarts": "^4.9.18", "@types/google.maps": "^3.53.4", - "@types/lodash": "^4.14.195", + "@types/lodash-es": "^4.17.9", "@types/lodash.get": "^4.4.7", "@types/node": "^20.4.0", "@types/prop-types": "*", diff --git a/src/Common/hooks/useAsyncOptions.ts b/src/Common/hooks/useAsyncOptions.ts index f0a1c895430..2f3f68d5c3a 100644 --- a/src/Common/hooks/useAsyncOptions.ts +++ b/src/Common/hooks/useAsyncOptions.ts @@ -1,4 +1,4 @@ -import { debounce } from "lodash"; +import { debounce } from "lodash-es"; import { useMemo, useState } from "react"; import { useDispatch } from "react-redux"; diff --git a/src/Components/Common/Uptime.tsx b/src/Components/Common/Uptime.tsx index ce60e7b7f7e..6f6966b9053 100644 --- a/src/Components/Common/Uptime.tsx +++ b/src/Components/Common/Uptime.tsx @@ -4,7 +4,7 @@ import { listAssetAvailability } from "../../Redux/actions"; import { useDispatch } from "react-redux"; import * as Notification from "../../Utils/Notifications.js"; import { AssetStatus, AssetUptimeRecord } from "../Assets/AssetTypes"; -import { reverse } from "lodash"; +import { reverse } from "lodash-es"; import { classNames } from "../../Utils/utils"; import dayjs from "../../Utils/dayjs"; diff --git a/src/Components/ExternalResult/ExternalResultUpload.tsx b/src/Components/ExternalResult/ExternalResultUpload.tsx index 5f39cb213a4..48c5bb0433c 100644 --- a/src/Components/ExternalResult/ExternalResultUpload.tsx +++ b/src/Components/ExternalResult/ExternalResultUpload.tsx @@ -1,4 +1,4 @@ -import _ from "lodash"; +import { startCase, camelCase } from "lodash-es"; import { navigate } from "raviger"; import { lazy, useState } from "react"; import CSVReader from "react-csv-reader"; @@ -122,7 +122,7 @@ export default function ExternalResultUpload() { ? errors.map((error: any) => { return (
- {_.startCase(_.camelCase(error[0][0]))} -{" "} + {startCase(camelCase(error[0][0]))} -{" "} {error[0][1]}
); diff --git a/src/Components/Facility/Investigations/InvestigationTable.tsx b/src/Components/Facility/Investigations/InvestigationTable.tsx index 8149e18496b..54e7e50bdc7 100644 --- a/src/Components/Facility/Investigations/InvestigationTable.tsx +++ b/src/Components/Facility/Investigations/InvestigationTable.tsx @@ -2,7 +2,6 @@ import ButtonV2 from "../../Common/components/ButtonV2"; import CareIcon from "../../../CAREUI/icons/CareIcon"; import { SelectFormField } from "../../Form/FormFields/SelectFormField"; import TextFormField from "../../Form/FormFields/TextFormField"; -import _ from "lodash"; import { classNames } from "../../../Utils/utils"; import { useState } from "react"; diff --git a/src/Components/Facility/Investigations/Reports/index.tsx b/src/Components/Facility/Investigations/Reports/index.tsx index ab5380c62ca..2db928386f1 100644 --- a/src/Components/Facility/Investigations/Reports/index.tsx +++ b/src/Components/Facility/Investigations/Reports/index.tsx @@ -17,7 +17,7 @@ import { InvestigationResponse } from "./types"; import Loading from "../../../Common/Loading"; import Page from "../../../Common/components/Page"; import ReportTable from "./ReportTable"; -import _ from "lodash"; +import { chain } from "lodash-es"; import { useDispatch } from "react-redux"; import { useRef } from "react"; @@ -175,7 +175,7 @@ const InvestigationReports = ({ id }: any) => { }) ); - const investigationList = _.chain(data) + const investigationList = chain(data) .compact() .flatten() .map((i) => ({ diff --git a/src/Components/Facility/Investigations/Reports/utils.tsx b/src/Components/Facility/Investigations/Reports/utils.tsx index 485a3e1d5f4..46b95800339 100644 --- a/src/Components/Facility/Investigations/Reports/utils.tsx +++ b/src/Components/Facility/Investigations/Reports/utils.tsx @@ -1,20 +1,20 @@ -import _ from "lodash"; +import { memoize, chain, findIndex } from "lodash-es"; import { InvestigationResponse } from "./types"; -export const transformData = _.memoize((data: InvestigationResponse) => { - const sessions = _.chain(data) +export const transformData = memoize((data: InvestigationResponse) => { + const sessions = chain(data) .map((value) => value.session_object) .uniqBy("session_external_id") .orderBy("session_created_date", "desc") .value(); - const groupByInvestigation = _.chain(data) + const groupByInvestigation = chain(data) .groupBy("investigation_object.external_id") .values() .value(); const reqData = groupByInvestigation.map((value) => { const sessionValues = Array.from({ length: sessions.length }); value.forEach((val) => { - const sessionIndex = _.findIndex(sessions, [ + const sessionIndex = findIndex(sessions, [ "session_external_id", val.session_object.session_external_id, ]); @@ -55,7 +55,7 @@ export const transformData = _.memoize((data: InvestigationResponse) => { return { sessions, data: reqData }; }); -export const getColorIndex = _.memoize( +export const getColorIndex = memoize( ({ max, min, value }: { min?: number; max?: number; value?: number }) => { if (!max && min && value) { // 1 => yellow color diff --git a/src/Components/Facility/Investigations/ShowInvestigation.tsx b/src/Components/Facility/Investigations/ShowInvestigation.tsx index 4c2826f6eea..4b8d1d065a7 100644 --- a/src/Components/Facility/Investigations/ShowInvestigation.tsx +++ b/src/Components/Facility/Investigations/ShowInvestigation.tsx @@ -9,7 +9,7 @@ import { import PageTitle from "../../Common/PageTitle"; import InvestigationTable from "./InvestigationTable"; -import _ from "lodash"; +import { set, chain } from "lodash-es"; import { navigate } from "raviger"; import * as Notification from "../../../Utils/Notifications.js"; @@ -110,7 +110,7 @@ export default function ShowInvestigation(props: any) { const handleValueChange = (value: any, name: string) => { const changedFields = { ...state.changedFields }; - _.set(changedFields, name, value); + set(changedFields, name, value); dispatch({ type: "set_changed_fields", changedFields }); }; @@ -147,7 +147,7 @@ export default function ShowInvestigation(props: any) { }; const handleUpdateCancel = useCallback(() => { - const changedValues = _.chain(state.initialValues) + const changedValues = chain(state.initialValues) .map((val: any, _key: string) => ({ id: val?.id, initialValue: val?.notes || val?.value || null, diff --git a/src/Components/Facility/Investigations/Table.tsx b/src/Components/Facility/Investigations/Table.tsx index 4d6ce4c2340..4bd7e841543 100644 --- a/src/Components/Facility/Investigations/Table.tsx +++ b/src/Components/Facility/Investigations/Table.tsx @@ -1,7 +1,7 @@ import { FieldChangeEvent } from "../../Form/FormFields/Utils"; import { SelectFormField } from "../../Form/FormFields/SelectFormField"; import TextFormField from "../../Form/FormFields/TextFormField"; -import _ from "lodash"; +import { set } from "lodash-es"; import { useState } from "react"; const TestRow = ({ data, value, onChange, i }: any) => { @@ -59,7 +59,7 @@ export const TestTable = ({ title, data, state, dispatch }: any) => { const handleValueChange = (value: any, name: string) => { const form = { ...state }; - _.set(form, name, value); + set(form, name, value); dispatch({ type: "set_form", form }); }; diff --git a/src/Components/Facility/LegacyFacilityCNS.tsx b/src/Components/Facility/LegacyFacilityCNS.tsx index b3a933b9a9f..5b0005c9daf 100644 --- a/src/Components/Facility/LegacyFacilityCNS.tsx +++ b/src/Components/Facility/LegacyFacilityCNS.tsx @@ -16,7 +16,7 @@ import Pagination from "../Common/Pagination"; import { PatientModel } from "../Patient/models"; import { FacilityModel } from "./models"; import AutocompleteFormField from "../Form/FormFields/Autocomplete"; -import { uniqBy } from "lodash"; +import { uniqBy } from "lodash-es"; import DialogModal from "../Common/Dialog"; import { LegacyMonitorCard } from "./LegacyMonitorCard"; diff --git a/src/Components/Form/AutoCompleteAsync.tsx b/src/Components/Form/AutoCompleteAsync.tsx index 5f33c6388c5..db2d1e888df 100644 --- a/src/Components/Form/AutoCompleteAsync.tsx +++ b/src/Components/Form/AutoCompleteAsync.tsx @@ -1,6 +1,6 @@ import { useEffect, useState, useMemo } from "react"; import { Combobox } from "@headlessui/react"; -import { debounce } from "lodash"; +import { debounce } from "lodash-es"; import { DropdownTransition } from "../Common/components/HelperComponents"; import CareIcon from "../../CAREUI/icons/CareIcon"; import { diff --git a/src/Components/Form/Form.tsx b/src/Components/Form/Form.tsx index e934c4ffe0e..66383616034 100644 --- a/src/Components/Form/Form.tsx +++ b/src/Components/Form/Form.tsx @@ -1,4 +1,4 @@ -import { isEmpty, omitBy } from "lodash"; +import { isEmpty, omitBy } from "lodash-es"; import { useEffect, useMemo, useState } from "react"; import { classNames } from "../../Utils/utils"; import { Cancel, Submit } from "../Common/components/ButtonV2"; diff --git a/src/Components/Patient/PatientRegister.tsx b/src/Components/Patient/PatientRegister.tsx index c515e7584c1..4fc6fef7fbe 100644 --- a/src/Components/Patient/PatientRegister.tsx +++ b/src/Components/Patient/PatientRegister.tsx @@ -61,7 +61,7 @@ import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; import TextFormField from "../Form/FormFields/TextFormField"; import TransferPatientDialog from "../Facility/TransferPatientDialog"; import countryList from "../../Common/static/countries.json"; -import { debounce } from "lodash"; +import { debounce } from "lodash-es"; import useAppHistory from "../../Common/hooks/useAppHistory"; import useConfig from "../../Common/hooks/useConfig"; diff --git a/src/Components/Patient/SampleDetails.tsx b/src/Components/Patient/SampleDetails.tsx index e29df92f47f..518fae2e728 100644 --- a/src/Components/Patient/SampleDetails.tsx +++ b/src/Components/Patient/SampleDetails.tsx @@ -7,7 +7,7 @@ import ButtonV2 from "../Common/components/ButtonV2"; import Card from "../../CAREUI/display/Card"; import { FileUpload } from "./FileUpload"; import Page from "../Common/components/Page"; -import _ from "lodash"; +import { startCase, camelCase } from "lodash-es"; import { formatAge, formatDateTime } from "../../Utils/utils"; import { getTestSample } from "../../Redux/actions"; @@ -259,11 +259,11 @@ export const SampleDetails = ({ id }: DetailRoute) => {
Status: {" "} - {_.startCase(_.camelCase(flow.status))} + {startCase(camelCase(flow.status))}
Label:{" "} - {_.capitalize(flow.notes)} + {capitalize(flow.notes)}
Created On :{" "} @@ -343,7 +343,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { Doctor's Name:{" "} - {_.startCase(_.camelCase(sampleDetails.doctor_name))} + {startCase(camelCase(sampleDetails.doctor_name))}
)} {sampleDetails.diagnosis && ( @@ -426,7 +426,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { Sample Type:{" "} - {_.startCase(_.camelCase(sampleDetails.sample_type))} + {startCase(camelCase(sampleDetails.sample_type))}
)}
diff --git a/src/Components/Patient/SampleTestCard.tsx b/src/Components/Patient/SampleTestCard.tsx index 847be365181..5387154dd35 100644 --- a/src/Components/Patient/SampleTestCard.tsx +++ b/src/Components/Patient/SampleTestCard.tsx @@ -6,7 +6,7 @@ import { SAMPLE_TEST_STATUS } from "../../Common/constants"; import { patchSample } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications"; import UpdateStatusDialog from "./UpdateStatusDialog"; -import _ from "lodash"; +import { startCase, camelCase } from "lodash-es"; import { formatDateTime } from "../../Utils/utils"; import ButtonV2 from "../Common/components/ButtonV2"; import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; @@ -92,7 +92,7 @@ export const SampleTestCard = (props: SampleDetailsProps) => { Status{" "}
- {_.startCase(_.camelCase(itemData.status))} + {startCase(camelCase(itemData.status))}
@@ -126,7 +126,7 @@ export const SampleTestCard = (props: SampleDetailsProps) => { Result{" "}
- {_.startCase(_.camelCase(itemData.result))} + {startCase(camelCase(itemData.result))}

diff --git a/src/Redux/fireRequest.tsx b/src/Redux/fireRequest.tsx index 9859ea540b6..bf083e23a92 100644 --- a/src/Redux/fireRequest.tsx +++ b/src/Redux/fireRequest.tsx @@ -1,6 +1,6 @@ import * as Notification from "../Utils/Notifications.js"; -import { isEmpty, omitBy } from "lodash"; +import { isEmpty, omitBy } from "lodash-es"; import { LocalStorageKeys } from "../Common/constants"; import api from "./api"; diff --git a/src/Utils/Notifications.js b/src/Utils/Notifications.js index b4393049531..298622c35ca 100644 --- a/src/Utils/Notifications.js +++ b/src/Utils/Notifications.js @@ -1,6 +1,6 @@ import { alert, Stack, defaultModules } from "@pnotify/core"; import * as PNotifyMobile from "@pnotify/mobile"; -import _ from "lodash"; +import { startCase, camelCase } from "lodash-es"; defaultModules.set(PNotifyMobile, {}); @@ -43,7 +43,7 @@ const notifyError = (error) => { errorMsg = error.detail; } else { for (let [key, value] of Object.entries(error)) { - let keyName = _.startCase(_.camelCase(key)); + let keyName = startCase(camelCase(key)); if (Array.isArray(value)) { const uniques = [...new Set(value)]; errorMsg += `${keyName} - ${uniques.splice(0, 5).join(", ")}`; From e64138c58d2ff19f2e57ff73cbcf7e6248b8929c Mon Sep 17 00:00:00 2001 From: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> Date: Mon, 9 Oct 2023 13:53:38 +0530 Subject: [PATCH 21/65] fix cypress (#6421) --- cypress/e2e/assets_spec/asset_homepage.cy.ts | 5 +- package-lock.json | 196 +++++++++++++++++++ 2 files changed, 198 insertions(+), 3 deletions(-) diff --git a/cypress/e2e/assets_spec/asset_homepage.cy.ts b/cypress/e2e/assets_spec/asset_homepage.cy.ts index 8bc24c7370f..e1fcf278442 100644 --- a/cypress/e2e/assets_spec/asset_homepage.cy.ts +++ b/cypress/e2e/assets_spec/asset_homepage.cy.ts @@ -73,7 +73,7 @@ describe("Asset Tab", () => { assetFilters.assertAssetTypeText("INTERNAL"); assetFilters.assertAssetClassText("ONVIF"); assetFilters.assertStatusText("ACTIVE"); - assetFilters.assertLocationText("Camera Locations"); + assetFilters.assertLocationText("Camera Loc"); assetFilters.clickadvancefilter(); assetFilters.clearFilters(); }); @@ -90,9 +90,8 @@ describe("Asset Tab", () => { assetPage.selectImportOption(); assetPage.selectImportFacility("Dummy Facility 1"); assetPage.importAssetFile(); - assetPage.selectImportLocation("Camera Locations"); + assetPage.selectImportLocation("Camera Loc"); assetPage.clickImportAsset(); - assetPage.verifySuccessNotification("Assets imported successfully"); }); it("verify imported asset", () => { diff --git a/package-lock.json b/package-lock.json index 2a9fe3f520f..54ef3890c77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13720,6 +13720,8 @@ }, "node_modules/npm/node_modules/@colors/colors": { "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -13730,6 +13732,8 @@ }, "node_modules/npm/node_modules/@isaacs/cliui": { "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, "inBundle": true, "license": "ISC", @@ -13747,6 +13751,8 @@ }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, "inBundle": true, "license": "MIT", @@ -13759,12 +13765,16 @@ }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "inBundle": true, "license": "MIT", @@ -13782,6 +13792,8 @@ }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -14034,6 +14046,8 @@ }, "node_modules/npm/node_modules/@pkgjs/parseargs": { "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "inBundle": true, "license": "MIT", @@ -14118,6 +14132,8 @@ }, "node_modules/npm/node_modules/agent-base": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -14144,6 +14160,8 @@ }, "node_modules/npm/node_modules/aggregate-error": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, "inBundle": true, "license": "MIT", @@ -14157,6 +14175,8 @@ }, "node_modules/npm/node_modules/ansi-regex": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -14166,6 +14186,8 @@ }, "node_modules/npm/node_modules/ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "inBundle": true, "license": "MIT", @@ -14181,6 +14203,8 @@ }, "node_modules/npm/node_modules/aproba": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "dev": true, "inBundle": true, "license": "ISC" @@ -14206,12 +14230,16 @@ }, "node_modules/npm/node_modules/balanced-match": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/base64-js": { "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true, "funding": [ { @@ -14256,6 +14284,8 @@ }, "node_modules/npm/node_modules/brace-expansion": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "inBundle": true, "license": "MIT", @@ -14333,6 +14363,8 @@ }, "node_modules/npm/node_modules/chownr": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -14369,6 +14401,8 @@ }, "node_modules/npm/node_modules/clean-stack": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true, "inBundle": true, "license": "MIT", @@ -14406,6 +14440,8 @@ }, "node_modules/npm/node_modules/clone": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true, "inBundle": true, "license": "MIT", @@ -14424,6 +14460,8 @@ }, "node_modules/npm/node_modules/color-convert": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -14436,6 +14474,8 @@ }, "node_modules/npm/node_modules/color-name": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "inBundle": true, "license": "MIT" @@ -14470,18 +14510,24 @@ }, "node_modules/npm/node_modules/concat-map": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/console-control-strings": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/cross-spawn": { "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "inBundle": true, "license": "MIT", @@ -14496,6 +14542,8 @@ }, "node_modules/npm/node_modules/cross-spawn/node_modules/which": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "inBundle": true, "license": "ISC", @@ -14511,6 +14559,8 @@ }, "node_modules/npm/node_modules/cssesc": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, "inBundle": true, "license": "MIT", @@ -14523,6 +14573,8 @@ }, "node_modules/npm/node_modules/debug": { "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -14540,12 +14592,16 @@ }, "node_modules/npm/node_modules/debug/node_modules/ms": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/defaults": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, "inBundle": true, "license": "MIT", @@ -14558,6 +14614,8 @@ }, "node_modules/npm/node_modules/delegates": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "dev": true, "inBundle": true, "license": "MIT" @@ -14582,12 +14640,16 @@ }, "node_modules/npm/node_modules/eastasianwidth": { "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/emoji-regex": { "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "inBundle": true, "license": "MIT" @@ -14628,6 +14690,8 @@ }, "node_modules/npm/node_modules/events": { "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, "inBundle": true, "license": "MIT", @@ -14652,6 +14716,8 @@ }, "node_modules/npm/node_modules/foreground-child": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", "dev": true, "inBundle": true, "license": "ISC", @@ -14680,12 +14746,16 @@ }, "node_modules/npm/node_modules/fs.realpath": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/function-bind": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true, "inBundle": true, "license": "MIT" @@ -14739,6 +14809,8 @@ }, "node_modules/npm/node_modules/has": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "inBundle": true, "license": "MIT", @@ -14751,6 +14823,8 @@ }, "node_modules/npm/node_modules/has-unicode": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "dev": true, "inBundle": true, "license": "ISC" @@ -14789,6 +14863,8 @@ }, "node_modules/npm/node_modules/https-proxy-agent": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "inBundle": true, "license": "MIT", @@ -14856,6 +14932,8 @@ }, "node_modules/npm/node_modules/imurmurhash": { "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "inBundle": true, "license": "MIT", @@ -14865,6 +14943,8 @@ }, "node_modules/npm/node_modules/indent-string": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, "inBundle": true, "license": "MIT", @@ -14874,6 +14954,8 @@ }, "node_modules/npm/node_modules/inflight": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "inBundle": true, "license": "ISC", @@ -14884,6 +14966,8 @@ }, "node_modules/npm/node_modules/inherits": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, "inBundle": true, "license": "ISC" @@ -14917,6 +15001,8 @@ }, "node_modules/npm/node_modules/ip": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", "dev": true, "inBundle": true, "license": "MIT" @@ -14956,6 +15042,8 @@ }, "node_modules/npm/node_modules/is-fullwidth-code-point": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "inBundle": true, "license": "MIT", @@ -14971,6 +15059,8 @@ }, "node_modules/npm/node_modules/isexe": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "inBundle": true, "license": "ISC" @@ -15237,6 +15327,8 @@ }, "node_modules/npm/node_modules/minimatch": { "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "inBundle": true, "license": "ISC", @@ -15252,6 +15344,8 @@ }, "node_modules/npm/node_modules/minipass": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -15273,6 +15367,8 @@ }, "node_modules/npm/node_modules/minipass-collect/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15314,6 +15410,8 @@ }, "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15336,6 +15434,8 @@ }, "node_modules/npm/node_modules/minipass-json-stream/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15360,6 +15460,8 @@ }, "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15384,6 +15486,8 @@ }, "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15396,6 +15500,8 @@ }, "node_modules/npm/node_modules/minizlib": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, "inBundle": true, "license": "MIT", @@ -15409,6 +15515,8 @@ }, "node_modules/npm/node_modules/minizlib/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15421,6 +15529,8 @@ }, "node_modules/npm/node_modules/mkdirp": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, "inBundle": true, "license": "MIT", @@ -15501,6 +15611,8 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "inBundle": true, "license": "MIT", @@ -15530,6 +15642,8 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/glob": { "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "inBundle": true, "license": "ISC", @@ -15550,6 +15664,8 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15592,6 +15708,8 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/readable-stream": { "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "inBundle": true, "license": "MIT", @@ -15606,6 +15724,8 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/signal-exit": { "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, "inBundle": true, "license": "ISC" @@ -15796,6 +15916,8 @@ }, "node_modules/npm/node_modules/once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "inBundle": true, "license": "ISC", @@ -15805,6 +15927,8 @@ }, "node_modules/npm/node_modules/p-map": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -15866,6 +15990,8 @@ }, "node_modules/npm/node_modules/path-is-absolute": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "inBundle": true, "license": "MIT", @@ -15875,6 +16001,8 @@ }, "node_modules/npm/node_modules/path-key": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "inBundle": true, "license": "MIT", @@ -15931,6 +16059,8 @@ }, "node_modules/npm/node_modules/process": { "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true, "inBundle": true, "license": "MIT", @@ -16070,6 +16200,8 @@ }, "node_modules/npm/node_modules/rimraf": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "inBundle": true, "license": "ISC", @@ -16085,6 +16217,8 @@ }, "node_modules/npm/node_modules/rimraf/node_modules/brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "inBundle": true, "license": "MIT", @@ -16095,6 +16229,8 @@ }, "node_modules/npm/node_modules/rimraf/node_modules/glob": { "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "inBundle": true, "license": "ISC", @@ -16115,6 +16251,8 @@ }, "node_modules/npm/node_modules/rimraf/node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "inBundle": true, "license": "ISC", @@ -16127,6 +16265,8 @@ }, "node_modules/npm/node_modules/safe-buffer": { "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, "funding": [ { @@ -16154,6 +16294,8 @@ }, "node_modules/npm/node_modules/semver": { "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "inBundle": true, "license": "ISC", @@ -16169,6 +16311,8 @@ }, "node_modules/npm/node_modules/semver/node_modules/lru-cache": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "inBundle": true, "license": "ISC", @@ -16181,12 +16325,16 @@ }, "node_modules/npm/node_modules/set-blocking": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/shebang-command": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "inBundle": true, "license": "MIT", @@ -16199,6 +16347,8 @@ }, "node_modules/npm/node_modules/shebang-regex": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "inBundle": true, "license": "MIT", @@ -16208,6 +16358,8 @@ }, "node_modules/npm/node_modules/signal-exit": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", + "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", "dev": true, "inBundle": true, "license": "ISC", @@ -16275,6 +16427,8 @@ }, "node_modules/npm/node_modules/spdx-correct": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, "inBundle": true, "license": "Apache-2.0", @@ -16285,12 +16439,16 @@ }, "node_modules/npm/node_modules/spdx-exceptions": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", "dev": true, "inBundle": true, "license": "CC-BY-3.0" }, "node_modules/npm/node_modules/spdx-expression-parse": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "inBundle": true, "license": "MIT", @@ -16301,6 +16459,8 @@ }, "node_modules/npm/node_modules/spdx-license-ids": { "version": "3.0.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", "dev": true, "inBundle": true, "license": "CC0-1.0" @@ -16319,6 +16479,8 @@ }, "node_modules/npm/node_modules/string_decoder": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "inBundle": true, "license": "MIT", @@ -16328,6 +16490,8 @@ }, "node_modules/npm/node_modules/string-width": { "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "inBundle": true, "license": "MIT", @@ -16343,6 +16507,8 @@ "node_modules/npm/node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "inBundle": true, "license": "MIT", @@ -16357,6 +16523,8 @@ }, "node_modules/npm/node_modules/strip-ansi": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "inBundle": true, "license": "MIT", @@ -16370,6 +16538,8 @@ "node_modules/npm/node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "inBundle": true, "license": "MIT", @@ -16394,6 +16564,8 @@ }, "node_modules/npm/node_modules/tar": { "version": "6.1.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", + "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", "dev": true, "inBundle": true, "license": "ISC", @@ -16411,6 +16583,8 @@ }, "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, "inBundle": true, "license": "ISC", @@ -16423,6 +16597,8 @@ }, "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -16494,6 +16670,8 @@ }, "node_modules/npm/node_modules/util-deprecate": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true, "inBundle": true, "license": "MIT" @@ -16561,6 +16739,8 @@ }, "node_modules/npm/node_modules/wrap-ansi": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -16579,6 +16759,8 @@ "node_modules/npm/node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "inBundle": true, "license": "MIT", @@ -16596,6 +16778,8 @@ }, "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, "inBundle": true, "license": "MIT", @@ -16608,6 +16792,8 @@ }, "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, "inBundle": true, "license": "MIT", @@ -16620,12 +16806,16 @@ }, "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "inBundle": true, "license": "MIT", @@ -16643,6 +16833,8 @@ }, "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -16658,6 +16850,8 @@ }, "node_modules/npm/node_modules/wrappy": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, "inBundle": true, "license": "ISC" @@ -16677,6 +16871,8 @@ }, "node_modules/npm/node_modules/yallist": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "inBundle": true, "license": "ISC" From 0866331fcd6564ad12b0ab091bd8b426fdfe502d Mon Sep 17 00:00:00 2001 From: Devdeep Ghosh <63492939+thedevildude@users.noreply.github.com> Date: Mon, 9 Oct 2023 16:10:34 +0530 Subject: [PATCH 22/65] Replaced date-fns utility functions from DateInputV2 component with day.js (#6405) * resolved conflict * replaced getDay, getDaysInMonth, isEqual with dayjs utils --- src/Components/Common/DateInputV2.tsx | 63 ++++++++++++--------------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/src/Components/Common/DateInputV2.tsx b/src/Components/Common/DateInputV2.tsx index 12009cb495f..95652e68638 100644 --- a/src/Components/Common/DateInputV2.tsx +++ b/src/Components/Common/DateInputV2.tsx @@ -1,14 +1,4 @@ import { MutableRefObject, useEffect, useState } from "react"; -import { - addMonths, - addYears, - format, - getDay, - getDaysInMonth, - isEqual, - subMonths, - subYears, -} from "date-fns"; import CareIcon from "../../CAREUI/icons/CareIcon"; import { Popover } from "@headlessui/react"; @@ -64,14 +54,20 @@ const DateInputV2: React.FC = ({ const decrement = () => { switch (type) { case "date": - setDatePickerHeaderDate((prev) => subMonths(prev, 1)); + setDatePickerHeaderDate((prev) => + dayjs(prev).subtract(1, "month").toDate() + ); break; case "month": - setDatePickerHeaderDate((prev) => subYears(prev, 1)); + setDatePickerHeaderDate((prev) => + dayjs(prev).subtract(1, "year").toDate() + ); break; case "year": - setDatePickerHeaderDate((prev) => subYears(prev, 1)); - setYear((prev) => subYears(prev, 10)); + setDatePickerHeaderDate((prev) => + dayjs(prev).subtract(1, "year").toDate() + ); + setYear((prev) => dayjs(prev).subtract(10, "year").toDate()); break; } }; @@ -79,24 +75,24 @@ const DateInputV2: React.FC = ({ const increment = () => { switch (type) { case "date": - setDatePickerHeaderDate((prev) => addMonths(prev, 1)); + setDatePickerHeaderDate((prev) => dayjs(prev).add(1, "month").toDate()); break; case "month": - setDatePickerHeaderDate((prev) => addYears(prev, 1)); + setDatePickerHeaderDate((prev) => dayjs(prev).add(1, "year").toDate()); break; case "year": - setDatePickerHeaderDate((prev) => addYears(prev, 1)); - setYear((prev) => addYears(prev, 10)); + setDatePickerHeaderDate((prev) => dayjs(prev).add(1, "year").toDate()); + setYear((prev) => dayjs(prev).add(10, "year").toDate()); break; } }; const isSelectedDate = (date: number) => { - if (value) - return isEqual( - new Date(value.getFullYear(), value.getMonth(), date), - value - ); + if (value) { + return dayjs( + new Date(value.getFullYear(), value.getMonth(), date) + ).isSame(dayjs(value)); + } }; type CloseFunction = ( @@ -117,9 +113,11 @@ const DateInputV2: React.FC = ({ }; const getDayCount = (date: Date) => { - const daysInMonth = getDaysInMonth(date); + const daysInMonth = dayjs(date).daysInMonth(); - const dayOfWeek = getDay(new Date(date.getFullYear(), date.getMonth(), 1)); + const dayOfWeek = dayjs( + new Date(date.getFullYear(), date.getMonth(), 1) + ).day(); const blankDaysArray = []; for (let i = 1; i <= dayOfWeek; i++) { blankDaysArray.push(i); @@ -282,7 +280,7 @@ const DateInputV2: React.FC = ({ onClick={showMonthPicker} className="cursor-pointer rounded px-3 py-1 text-center font-medium text-black hover:bg-gray-300" > - {format(datePickerHeaderDate, "MMMM")} + {dayjs(datePickerHeaderDate).format("MMMM")}
)}
= ({

{type == "year" ? year.getFullYear() - : format(datePickerHeaderDate, "yyyy")} + : dayjs(datePickerHeaderDate).format("YYYY")}

@@ -371,14 +369,9 @@ const DateInputV2: React.FC = ({ )} onClick={setMonthValue(i)} > - {format( - new Date( - datePickerHeaderDate.getFullYear(), - i, - 1 - ), - "MMM" - )} + {dayjs( + new Date(datePickerHeaderDate.getFullYear(), i, 1) + ).format("MMM")}
))}
From 17515b86dcec3758b00beacf0edf7e2e9c3dc689 Mon Sep 17 00:00:00 2001 From: Gokulram A Date: Mon, 9 Oct 2023 16:11:02 +0530 Subject: [PATCH 23/65] Aligned Service History & Transaction History in Asset Details (#6382) * Aligned Service History & Transaction History in Asset Details * aliged moved on and edit properly --- src/Components/Assets/AssetManage.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Components/Assets/AssetManage.tsx b/src/Components/Assets/AssetManage.tsx index 84325d704aa..17078583681 100644 --- a/src/Components/Assets/AssetManage.tsx +++ b/src/Components/Assets/AssetManage.tsx @@ -164,18 +164,18 @@ const AssetManage = (props: AssetManageProps) => { {transaction.from_location.name} - + {transaction.to_location.name} - + {transaction.performed_by.first_name}{" "} {transaction.performed_by.last_name} - + {formatDateTime(transaction.modified_date)} @@ -202,7 +202,7 @@ const AssetManage = (props: AssetManageProps) => { setServiceDetails( services.map((service: AssetService) => ( - + {dayjs(service.serviced_on).format("DD MMM YYYY")} @@ -229,7 +229,7 @@ const AssetManage = (props: AssetManageProps) => { )} - + { - - @@ -558,13 +558,13 @@ const AssetManage = (props: AssetManageProps) => { - - - From 10179d3c286ba2288bb1cc934b7e2ecf4d017f70 Mon Sep 17 00:00:00 2001 From: Gokulram A Date: Mon, 9 Oct 2023 16:12:50 +0530 Subject: [PATCH 24/65] Added AMC Warranty filters and expiry labels for assets (#6364) * added warranty filters and labels for assets * fixed warranty label date --- src/Components/Assets/AssetFilter.tsx | 34 ++++++++++++++++ src/Components/Assets/AssetManage.tsx | 4 ++ src/Components/Assets/AssetsList.tsx | 57 +++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/src/Components/Assets/AssetFilter.tsx b/src/Components/Assets/AssetFilter.tsx index 4fc4ff6d7f2..4dca7dfecdb 100644 --- a/src/Components/Assets/AssetFilter.tsx +++ b/src/Components/Assets/AssetFilter.tsx @@ -14,6 +14,11 @@ import { AssetClass, AssetLocationObject } from "./AssetTypes"; import { FieldLabel } from "../Form/FormFields/FormField"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; import FiltersSlideover from "../../CAREUI/interactive/FiltersSlideover"; +import DateRangeFormField from "../Form/FormFields/DateRangeFormField"; +import dayjs from "dayjs"; +import { FieldChangeEvent } from "../Form/FormFields/Utils"; +import { DateRange } from "../Common/DateRangeInputV2"; +import { dateQueryString } from "../../Utils/utils"; const initialLocation = { id: "", @@ -25,6 +30,9 @@ const initialLocation = { }, }; +const getDate = (value: any) => + value && dayjs(value).isValid() && dayjs(value).toDate(); + function AssetFilter(props: any) { const { filter, onChange, closeFilter } = props; const dispatch: any = useDispatch(); @@ -40,6 +48,10 @@ function AssetFilter(props: any) { ); const [facilityId, setFacilityId] = useState(filter.facility); const [locationId, setLocationId] = useState(filter.location); + const [warrantyExpiry, setWarrantyExpiry] = useState({ + before: filter.warranty_amc_end_of_validity_before || null, + after: filter.warranty_amc_end_of_validity_after || null, + }); const [qParams, _] = useQueryParams(); useEffect(() => { @@ -112,6 +124,10 @@ function AssetFilter(props: any) { asset_class: asset_class ?? "", status: asset_status ?? "", location: locationId, + warranty_amc_end_of_validity_before: dateQueryString( + warrantyExpiry.before + ), + warranty_amc_end_of_validity_after: dateQueryString(warrantyExpiry.after), }; onChange(data); }; @@ -124,6 +140,13 @@ function AssetFilter(props: any) { setLocationId(selectedId); }; + const handleDateRangeChange = (event: FieldChangeEvent) => { + const state = { ...warrantyExpiry }; + state.after = event.value.start?.toString(); + state.before = event.value.end?.toString(); + setWarrantyExpiry(state); + }; + return ( setAssetClass(value)} /> + + ); } diff --git a/src/Components/Assets/AssetManage.tsx b/src/Components/Assets/AssetManage.tsx index 17078583681..e6d1836e27b 100644 --- a/src/Components/Assets/AssetManage.tsx +++ b/src/Components/Assets/AssetManage.tsx @@ -34,6 +34,7 @@ import useAuthUser from "../../Common/hooks/useAuthUser"; import dayjs from "dayjs"; import RelativeDateUserMention from "../Common/RelativeDateUserMention"; import { AssetServiceEditModal } from "./AssetServiceEditModal"; +import { warrantyAmcValidityChip } from "./AssetsList"; import Page from "../Common/components/Page"; interface AssetManageProps { @@ -408,6 +409,9 @@ const AssetManage = (props: AssetManageProps) => { startIcon="l-times" /> )} + {warrantyAmcValidityChip( + asset?.warranty_amc_end_of_validity as string + )}
diff --git a/src/Components/Assets/AssetsList.tsx b/src/Components/Assets/AssetsList.tsx index d91468166ac..46b4da5283d 100644 --- a/src/Components/Assets/AssetsList.tsx +++ b/src/Components/Assets/AssetsList.tsx @@ -74,6 +74,10 @@ const AssetsList = () => { asset_class: qParams.asset_class || "", location: qParams.facility ? qParams.location || "" : "", status: qParams.status || "", + warranty_amc_end_of_validity_before: + qParams.warranty_amc_end_of_validity_before || "", + warranty_amc_end_of_validity_after: + qParams.warranty_amc_end_of_validity_after || "", }; const { data } = await dispatch(listAssets(params)); if (!status.aborted) { @@ -103,6 +107,8 @@ const AssetsList = () => { qParams.asset_class, qParams.location, qParams.status, + qParams.warranty_amc_end_of_validity_before, + qParams.warranty_amc_end_of_validity_after, dispatch, ] ); @@ -288,6 +294,7 @@ const AssetsList = () => { ) : ( )} + {warrantyAmcValidityChip(asset.warranty_amc_end_of_validity)}
@@ -428,6 +435,16 @@ const AssetsList = () => { value("Asset Class", "asset_class", asset_class ?? ""), value("Status", "status", status?.replace(/_/g, " ") ?? ""), value("Location", "location", locationName ?? ""), + value( + "Warranty AMC End Of Validity Before", + "warranty_amc_end_of_validity_before", + qParams.warranty_amc_end_of_validity_before ?? "" + ), + value( + "Warranty AMC End Of Validity After", + "warranty_amc_end_of_validity_after", + qParams.warranty_amc_end_of_validity_after ?? "" + ), ]} />
@@ -484,4 +501,44 @@ const AssetsList = () => { ); }; +export const warrantyAmcValidityChip = ( + warranty_amc_end_of_validity: string +) => { + if (warranty_amc_end_of_validity === "" || !warranty_amc_end_of_validity) + return; + const today = new Date(); + const warrantyAmcEndDate = new Date(warranty_amc_end_of_validity); + + const days = Math.ceil( + Math.abs(Number(warrantyAmcEndDate) - Number(today)) / (1000 * 60 * 60 * 24) + ); + + if (warrantyAmcEndDate < today) { + return ( + + ); + } else if (days <= 30) { + return ( + + ); + } else if (days <= 90) { + return ( + + ); + } +}; + export default AssetsList; From a21bc89dacf1d914b44295b62fa9fa078c4d89bb Mon Sep 17 00:00:00 2001 From: Kshitij Verma <101321276+kshitijv256@users.noreply.github.com> Date: Mon, 9 Oct 2023 16:13:32 +0530 Subject: [PATCH 25/65] whitelisted jwt_token_refresh_interval (#6418) * whitelisted jwt_token_refresh_interval * removed todo --- public/config.json | 3 ++- src/Common/hooks/useConfig.ts | 1 + src/Providers/AuthUserProvider.tsx | 9 +++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/public/config.json b/public/config.json index 8bb0dab3b57..5e67902d4fe 100644 --- a/public/config.json +++ b/public/config.json @@ -22,5 +22,6 @@ "kasp_full_string": "Karunya Arogya Suraksha Padhathi", "sample_format_asset_import": "https://spreadsheets.google.com/feeds/download/spreadsheets/Export?key=11JaEhNHdyCHth4YQs_44YaRlP77Rrqe81VSEfg1glko&exportFormat=xlsx", "sample_format_external_result_import": "https://docs.google.com/spreadsheets/d/17VfgryA6OYSYgtQZeXU9mp7kNvLySeEawvnLBO_1nuE/export?format=csv&id=17VfgryA6OYSYgtQZeXU9mp7kNvLySeEawvnLBO_1nuE", - "enable_abdm": true + "enable_abdm": true, + "jwt_token_refresh_interval": 300000 } \ No newline at end of file diff --git a/src/Common/hooks/useConfig.ts b/src/Common/hooks/useConfig.ts index 7b956afec07..15e456ea571 100644 --- a/src/Common/hooks/useConfig.ts +++ b/src/Common/hooks/useConfig.ts @@ -68,6 +68,7 @@ export interface IConfig { * Env to toggle peacetime and wartime shifting */ wartime_shifting: boolean; + jwt_token_refresh_interval?: number; } const useConfig = () => { diff --git a/src/Providers/AuthUserProvider.tsx b/src/Providers/AuthUserProvider.tsx index 64027a17215..a64b38f630c 100644 --- a/src/Providers/AuthUserProvider.tsx +++ b/src/Providers/AuthUserProvider.tsx @@ -5,6 +5,7 @@ import routes from "../Redux/api"; import useQuery from "../Utils/request/useQuery"; import { LocalStorageKeys } from "../Common/constants"; import request from "../Utils/request/request"; +import useConfig from "../Common/hooks/useConfig"; interface Props { children: React.ReactNode; @@ -12,6 +13,7 @@ interface Props { } export default function AuthUserProvider({ children, unauthorized }: Props) { + const { jwt_token_refresh_interval } = useConfig(); const { res, data, loading } = useQuery(routes.currentUser, { refetchOnWindowFocus: false, prefetch: true, @@ -24,8 +26,11 @@ export default function AuthUserProvider({ children, unauthorized }: Props) { } updateRefreshToken(true); - setInterval(() => updateRefreshToken(), 5 * 60 * 1000); // TODO: move this interval to config.json - }, [data]); + setInterval( + () => updateRefreshToken(), + jwt_token_refresh_interval ?? 5 * 60 * 3000 + ); + }, [data, jwt_token_refresh_interval]); if (loading || !res) { return ; From 59daace7ce8e2d2131e13241d906d955f2aad0a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 16:19:36 +0530 Subject: [PATCH 26/65] Bump uuid from 9.0.0 to 9.0.1 (#6409) Bumps [uuid](https://github.com/uuidjs/uuid) from 9.0.0 to 9.0.1. - [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md) - [Commits](https://github.com/uuidjs/uuid/compare/v9.0.0...v9.0.1) --- updated-dependencies: - dependency-name: uuid dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 12 ++++++++---- package.json | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 54ef3890c77..1fcda2c19ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,7 @@ "rehype-raw": "^6.1.1", "rescript-webapi": "^0.8.0", "use-keyboard-shortcut": "^1.1.6", - "uuid": "^9.0.0" + "uuid": "^9.0.1" }, "devDependencies": { "@storybook/addon-essentials": "^7.0.26", @@ -21083,9 +21083,13 @@ } }, "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } diff --git a/package.json b/package.json index 246734dc3d4..c9a9e80258c 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "rehype-raw": "^6.1.1", "rescript-webapi": "^0.8.0", "use-keyboard-shortcut": "^1.1.6", - "uuid": "^9.0.0" + "uuid": "^9.0.1" }, "devDependencies": { "@storybook/addon-essentials": "^7.0.26", From 32c25aa28dd660a5e77dcd20fbcbbbad4bf10933 Mon Sep 17 00:00:00 2001 From: Onkar Jadhav <56870381+Omkar76@users.noreply.github.com> Date: Wed, 11 Oct 2023 08:33:15 +0530 Subject: [PATCH 27/65] Using the treeshakable api for echarts (#6424) --- .../Consultations/components/LinePlot.tsx | 33 +++++++++++++++++-- .../components/StackedLinePlot.tsx | 31 +++++++++++++++-- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/Components/Facility/Consultations/components/LinePlot.tsx b/src/Components/Facility/Consultations/components/LinePlot.tsx index 492f80f0388..92e3afcec61 100644 --- a/src/Components/Facility/Consultations/components/LinePlot.tsx +++ b/src/Components/Facility/Consultations/components/LinePlot.tsx @@ -1,4 +1,32 @@ -import ReactECharts from "echarts-for-react"; +import ReactEchartsCore from "echarts-for-react/lib/core"; +import { BarChart, LineChart } from "echarts/charts"; +import { + DataZoomComponent, + GridComponent, + LegendComponent, + TitleComponent, + ToolboxComponent, + TooltipComponent, + VisualMapComponent, + VisualMapPiecewiseComponent, +} from "echarts/components"; + +import * as echarts from "echarts/core"; +import { CanvasRenderer } from "echarts/renderers"; +echarts.use([ + BarChart, + LineChart, + CanvasRenderer, + DataZoomComponent, + GridComponent, + LegendComponent, + LegendComponent, + TitleComponent, + ToolboxComponent, + TooltipComponent, + VisualMapComponent, + VisualMapPiecewiseComponent, +]); export const LinePlot = (props: any) => { const { @@ -197,7 +225,8 @@ export const LinePlot = (props: any) => { } return ( - { @@ -81,5 +108,5 @@ export const StackedLinePlot = (props: any) => { }, series: series, }; - return ; + return ; }; From 6d1effce5513758c5ddb80a70e0a77eb98357944 Mon Sep 17 00:00:00 2001 From: Ashraf Mohammed <98876115+AshrafMd-1@users.noreply.github.com> Date: Wed, 11 Oct 2023 08:33:50 +0530 Subject: [PATCH 28/65] Set page title to medicine (#6431) --- .../Facility/ConsultationDetails/ConsultationMedicinesTab.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Components/Facility/ConsultationDetails/ConsultationMedicinesTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationMedicinesTab.tsx index 19810102833..0643765339f 100644 --- a/src/Components/Facility/ConsultationDetails/ConsultationMedicinesTab.tsx +++ b/src/Components/Facility/ConsultationDetails/ConsultationMedicinesTab.tsx @@ -1,9 +1,12 @@ import { ConsultationTabProps } from "./index"; import PrescriptionAdministrationsTable from "../../Medicine/PrescriptionAdministrationsTable"; +import PageTitle from "../../Common/PageHeadTitle"; export const ConsultationMedicinesTab = (props: ConsultationTabProps) => { return (
+ {/* eslint-disable-next-line i18next/no-literal-string */} + Date: Wed, 11 Oct 2023 08:34:52 +0530 Subject: [PATCH 29/65] Track camera feed views and display offline status (#6408) --- src/Common/hooks/useMSEplayer.ts | 3 ++ .../Facility/Consultations/Feed.tsx | 30 ++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/Common/hooks/useMSEplayer.ts b/src/Common/hooks/useMSEplayer.ts index b14f94a6a43..fcbf216ed6a 100644 --- a/src/Common/hooks/useMSEplayer.ts +++ b/src/Common/hooks/useMSEplayer.ts @@ -208,6 +208,9 @@ export const useMSEMediaPlayer = ({ readPacket(event.data); } }; + ws.onerror = function (event) { + onError && onError(event); + }; }, false ); diff --git a/src/Components/Facility/Consultations/Feed.tsx b/src/Components/Facility/Consultations/Feed.tsx index 75ab72d168c..31691c736f4 100644 --- a/src/Components/Facility/Consultations/Feed.tsx +++ b/src/Components/Facility/Consultations/Feed.tsx @@ -32,6 +32,7 @@ import useKeyboardShortcut from "use-keyboard-shortcut"; import useFullscreen from "../../../Common/hooks/useFullscreen.js"; import { triggerGoal } from "../../../Integrations/Plausible.js"; import useAuthUser from "../../../Common/hooks/useAuthUser.js"; +import Spinner from "../../Common/Spinner.js"; interface IFeedProps { facilityId: string; @@ -57,6 +58,7 @@ export const Feed: React.FC = ({ consultationId, facilityId }) => { const [cameraState, setCameraState] = useState(null); const [isFullscreen, setFullscreen] = useFullscreen(); const [videoStartTime, setVideoStartTime] = useState(null); + const [statusReported, setStatusReported] = useState(false); const authUser = useAuthUser(); useEffect(() => { @@ -232,13 +234,32 @@ export const Feed: React.FC = ({ consultationId, facilityId }) => { useEffect(() => { let tId: any; if (streamStatus !== StreamStatus.Playing) { - setStreamStatus(StreamStatus.Loading); + if (streamStatus !== StreamStatus.Offline) { + setStreamStatus(StreamStatus.Loading); + } tId = setTimeout(() => { startStream({ onSuccess: () => setStreamStatus(StreamStatus.Playing), - onError: () => setStreamStatus(StreamStatus.Offline), + onError: () => { + setStreamStatus(StreamStatus.Offline); + if (!statusReported) { + triggerGoal("Camera Feed Viewed", { + consultationId, + userId: authUser.id, + result: "error", + }); + setStatusReported(true); + } + }, }); }, 100); + } else if (!statusReported) { + triggerGoal("Camera Feed Viewed", { + consultationId, + userId: authUser.id, + result: "success", + }); + setStatusReported(true); } return () => { @@ -505,8 +526,9 @@ export const Feed: React.FC = ({ consultationId, facilityId }) => { STATUS: OFFLINE

Feed is currently not live.

-

- Click refresh button to try again. +

Trying to connect...

+

+

)} From 31b530a0403cd2b3e4b83a5eb1c9fed50d2eb5d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Oct 2023 08:35:46 +0530 Subject: [PATCH 30/65] Bump react-player from 2.12.0 to 2.13.0 (#6317) Bumps [react-player](https://github.com/CookPete/react-player) from 2.12.0 to 2.13.0. - [Changelog](https://github.com/cookpete/react-player/blob/master/CHANGELOG.md) - [Commits](https://github.com/CookPete/react-player/compare/v2.12.0...v2.13.0) --- updated-dependencies: - dependency-name: react-player dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Rithvik Nishad --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1fcda2c19ed..6697b3e7021 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "react-google-recaptcha": "^3.1.0", "react-i18next": "^13.0.1", "react-markdown": "^8.0.7", - "react-player": "^2.12.0", + "react-player": "^2.13.0", "react-qr-reader": "^2.2.1", "react-redux": "^8.1.1", "react-transition-group": "^4.4.5", @@ -18369,9 +18369,9 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/react-player": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/react-player/-/react-player-2.12.0.tgz", - "integrity": "sha512-rymLRz/2GJJD+Wc01S7S+i9pGMFYnNmQibR2gVE3KmHJCBNN8BhPAlOPTGZtn1uKpJ6p4RPLlzPQ1OLreXd8gw==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/react-player/-/react-player-2.13.0.tgz", + "integrity": "sha512-gkY7ZdbVFztlKFFhCPcnDrFQm+L399b8fhWsKatZ+b2wpKJwfUHBXQFMRxqYQGT0ic1/wQ7D7EZEWy7ZBqk2pw==", "dependencies": { "deepmerge": "^4.0.0", "load-script": "^1.0.0", diff --git a/package.json b/package.json index c9a9e80258c..6114fdf7496 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "react-google-recaptcha": "^3.1.0", "react-i18next": "^13.0.1", "react-markdown": "^8.0.7", - "react-player": "^2.12.0", + "react-player": "^2.13.0", "react-qr-reader": "^2.2.1", "react-redux": "^8.1.1", "react-transition-group": "^4.4.5", From be98542fd1985e7801886f9723211355a84b967b Mon Sep 17 00:00:00 2001 From: Onkar Jadhav Date: Thu, 12 Oct 2023 15:39:18 +0530 Subject: [PATCH 31/65] Replace startCase, camelCase with css capitalize --- .../ExternalResult/ExternalResultUpload.tsx | 7 ++-- src/Components/Patient/SampleDetails.tsx | 33 +++++++++++-------- src/Components/Patient/SampleTestCard.tsx | 16 ++++----- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/Components/ExternalResult/ExternalResultUpload.tsx b/src/Components/ExternalResult/ExternalResultUpload.tsx index 48c5bb0433c..c8bfef1a89c 100644 --- a/src/Components/ExternalResult/ExternalResultUpload.tsx +++ b/src/Components/ExternalResult/ExternalResultUpload.tsx @@ -1,4 +1,3 @@ -import { startCase, camelCase } from "lodash-es"; import { navigate } from "raviger"; import { lazy, useState } from "react"; import CSVReader from "react-csv-reader"; @@ -117,13 +116,13 @@ export default function ExternalResultUpload() {
{index + 1}
{data.name}
-
+
{errors && errors.length !== 0 ? errors.map((error: any) => { return (
- {startCase(camelCase(error[0][0]))} -{" "} - {error[0][1]} + {error[0][0].toLowerCase()} -{" "} + {error[0][1].toLowerCase()}
); }) diff --git a/src/Components/Patient/SampleDetails.tsx b/src/Components/Patient/SampleDetails.tsx index 518fae2e728..672f3905792 100644 --- a/src/Components/Patient/SampleDetails.tsx +++ b/src/Components/Patient/SampleDetails.tsx @@ -7,7 +7,6 @@ import ButtonV2 from "../Common/components/ButtonV2"; import Card from "../../CAREUI/display/Card"; import { FileUpload } from "./FileUpload"; import Page from "../Common/components/Page"; -import { startCase, camelCase } from "lodash-es"; import { formatAge, formatDateTime } from "../../Utils/utils"; import { getTestSample } from "../../Redux/actions"; @@ -258,12 +257,16 @@ export const SampleDetails = ({ id }: DetailRoute) => {
- Status: {" "} - {startCase(camelCase(flow.status))} + + Status:{" "} + {" "} + {flow.status}
- Label:{" "} - {capitalize(flow.notes)} + + Label: + {" "} + {flow.notes?.toLowerCase()}
Created On :{" "} @@ -301,12 +304,16 @@ export const SampleDetails = ({ id }: DetailRoute) => {
- Status: - {_.startCase(_.camelCase(sampleDetails.status))} + + Status:{" "} + + {sampleDetails.status}
- Result: - {_.startCase(_.camelCase(sampleDetails.result))} + + Result:{" "} + + {sampleDetails.result}
Patient: @@ -339,11 +346,11 @@ export const SampleDetails = ({ id }: DetailRoute) => {
)} {sampleDetails.doctor_name && ( -
+
Doctor's Name:{" "} - {startCase(camelCase(sampleDetails.doctor_name))} + {sampleDetails.doctor_name}
)} {sampleDetails.diagnosis && ( @@ -423,10 +430,10 @@ export const SampleDetails = ({ id }: DetailRoute) => { )} {sampleDetails.sample_type && (
- + Sample Type:{" "} - {startCase(camelCase(sampleDetails.sample_type))} + {sampleDetails.sample_type}
)}
diff --git a/src/Components/Patient/SampleTestCard.tsx b/src/Components/Patient/SampleTestCard.tsx index 5387154dd35..0c299f0a1b5 100644 --- a/src/Components/Patient/SampleTestCard.tsx +++ b/src/Components/Patient/SampleTestCard.tsx @@ -6,7 +6,6 @@ import { SAMPLE_TEST_STATUS } from "../../Common/constants"; import { patchSample } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications"; import UpdateStatusDialog from "./UpdateStatusDialog"; -import { startCase, camelCase } from "lodash-es"; import { formatDateTime } from "../../Utils/utils"; import ButtonV2 from "../Common/components/ButtonV2"; import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; @@ -91,8 +90,8 @@ export const SampleTestCard = (props: SampleDetailsProps) => {
Status{" "}
-
- {startCase(camelCase(itemData.status))} +
+ {itemData.status?.toLowerCase()}
@@ -101,10 +100,11 @@ export const SampleTestCard = (props: SampleDetailsProps) => {
Sample Type{" "}
-
- {itemData.sample_type !== "OTHER TYPE" +
+ {(itemData.sample_type !== "OTHER TYPE" ? itemData.sample_type - : itemData.sample_type_other} + : itemData.sample_type_other + )?.toLowerCase()}
@@ -125,8 +125,8 @@ export const SampleTestCard = (props: SampleDetailsProps) => {
Result{" "}
-
- {startCase(camelCase(itemData.result))} +
+ {itemData.result?.toLowerCase()}
From 5e3012a2d4532aaddd806a8fc231b3bcd1467a5e Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Thu, 12 Oct 2023 15:52:26 +0530 Subject: [PATCH 32/65] fixes nutrition tab showing nursing analysis (#6437) --- ...sultationNeutritionTab.tsx => ConsultationNutritionTab.tsx} | 2 +- src/Components/Facility/ConsultationDetails/index.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) rename src/Components/Facility/ConsultationDetails/{ConsultationNeutritionTab.tsx => ConsultationNutritionTab.tsx} (86%) diff --git a/src/Components/Facility/ConsultationDetails/ConsultationNeutritionTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationNutritionTab.tsx similarity index 86% rename from src/Components/Facility/ConsultationDetails/ConsultationNeutritionTab.tsx rename to src/Components/Facility/ConsultationDetails/ConsultationNutritionTab.tsx index 69f130aca0d..74e250f0577 100644 --- a/src/Components/Facility/ConsultationDetails/ConsultationNeutritionTab.tsx +++ b/src/Components/Facility/ConsultationDetails/ConsultationNutritionTab.tsx @@ -4,7 +4,7 @@ import { NutritionPlots } from "../Consultations/NutritionPlots"; const PageTitle = lazy(() => import("../../Common/PageTitle")); -export const ConsultationNeutritionTab = (props: ConsultationTabProps) => { +export const ConsultationNutritionTab = (props: ConsultationTabProps) => { return (
diff --git a/src/Components/Facility/ConsultationDetails/index.tsx b/src/Components/Facility/ConsultationDetails/index.tsx index 403f1752704..3d3271b16d5 100644 --- a/src/Components/Facility/ConsultationDetails/index.tsx +++ b/src/Components/Facility/ConsultationDetails/index.tsx @@ -41,6 +41,7 @@ import { ConsultationVentilatorTab } from "./ConsultationVentilatorTab"; import { ConsultationPressureSoreTab } from "./ConsultationPressureSoreTab"; import { ConsultationDialysisTab } from "./ConsultationDialysisTab"; import { ConsultationNeurologicalMonitoringTab } from "./ConsultationNeurologicalMonitoringTab"; +import { ConsultationNutritionTab } from "./ConsultationNutritionTab"; const Loading = lazy(() => import("../../Common/Loading")); const PageTitle = lazy(() => import("../../Common/PageTitle")); @@ -65,7 +66,7 @@ const TABS = { NURSING: ConsultationNursingTab, NEUROLOGICAL_MONITORING: ConsultationNeurologicalMonitoringTab, VENTILATOR: ConsultationVentilatorTab, - NUTRITION: ConsultationNursingTab, + NUTRITION: ConsultationNutritionTab, PRESSURE_SORE: ConsultationPressureSoreTab, DIALYSIS: ConsultationDialysisTab, }; From 362003cb361d6e199fb7e47e03946091ce3ef32d Mon Sep 17 00:00:00 2001 From: Abhiuday Gupta <77210185+cp-Coder@users.noreply.github.com> Date: Tue, 17 Oct 2023 06:29:10 +0530 Subject: [PATCH 33/65] fix: add action and recommend discharge data on patient consultation page (#5347) * fix(daily_round): added recommend_discharge as action * fix: rebased the branch --- src/Common/constants.tsx | 1 + src/Components/Patient/DailyRounds.tsx | 44 +++++------- src/Components/Patient/PatientInfoCard.tsx | 82 +++++++++++++--------- src/Components/Patient/models.tsx | 1 + 4 files changed, 69 insertions(+), 59 deletions(-) diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index e51494f6bac..0b08b4b9a42 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -499,6 +499,7 @@ export const TELEMEDICINE_ACTIONS = [ { id: 60, text: "COMPLETE", desc: "Complete" }, { id: 70, text: "REVIEW", desc: "Review" }, { id: 80, text: "NOT_REACHABLE", desc: "Not Reachable" }, + { id: 90, text: "DISCHARGE_RECOMMENDED", desc: "Discharge Recommended" }, ]; export const FRONTLINE_WORKER = [ diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx index a84ad4a3e8b..ff749aed29e 100644 --- a/src/Components/Patient/DailyRounds.tsx +++ b/src/Components/Patient/DailyRounds.tsx @@ -1,40 +1,40 @@ import { navigate } from "raviger"; -import { useCallback, useState, useEffect, lazy } from "react"; +import dayjs from "dayjs"; +import { lazy, useCallback, useEffect, useState } from "react"; import { useDispatch } from "react-redux"; import { - TELEMEDICINE_ACTIONS, + PATIENT_CATEGORIES, REVIEW_AT_CHOICES, RHYTHM_CHOICES, - PATIENT_CATEGORIES, + TELEMEDICINE_ACTIONS, } from "../../Common/constants"; +import useAppHistory from "../../Common/hooks/useAppHistory"; import { statusType, useAbortableEffect } from "../../Common/utils"; import { createDailyReport, getConsultationDailyRoundsDetails, getDailyReport, - updateDailyReport, getPatient, + updateDailyReport, } from "../../Redux/actions"; +import { DraftSection, useAutoSaveReducer } from "../../Utils/AutoSave"; import * as Notification from "../../Utils/Notifications"; import { formatDateTime } from "../../Utils/utils"; -import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; -import { Cancel, Submit } from "../Common/components/ButtonV2"; -import useAppHistory from "../../Common/hooks/useAppHistory"; -import { DraftSection, useAutoSaveReducer } from "../../Utils/AutoSave"; -import Page from "../Common/components/Page"; -import { FieldChangeEvent } from "../Form/FormFields/Utils"; -import TextFormField from "../Form/FormFields/TextFormField"; -import { SelectFormField } from "../Form/FormFields/SelectFormField"; -import PatientCategorySelect from "./PatientCategorySelect"; -import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField"; -import { SymptomsSelect } from "../Common/SymptomsSelect"; -import RangeAutocompleteFormField from "../Form/FormFields/RangeAutocompleteFormField"; import BloodPressureFormField, { meanArterialPressure, } from "../Common/BloodPressureFormField"; +import { SymptomsSelect } from "../Common/SymptomsSelect"; import TemperatureFormField from "../Common/TemperatureFormField"; -import dayjs from "dayjs"; +import { Cancel, Submit } from "../Common/components/ButtonV2"; +import Page from "../Common/components/Page"; +import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField"; +import RangeAutocompleteFormField from "../Form/FormFields/RangeAutocompleteFormField"; +import { SelectFormField } from "../Form/FormFields/SelectFormField"; +import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; +import TextFormField from "../Form/FormFields/TextFormField"; +import { FieldChangeEvent } from "../Form/FormFields/Utils"; +import PatientCategorySelect from "./PatientCategorySelect"; const Loading = lazy(() => import("../Common/Loading")); const initForm: any = { @@ -44,8 +44,7 @@ const initForm: any = { other_details: "", patient_category: "", current_health: 0, - recommend_discharge: false, - action: null, + actions: null, review_interval: 0, admitted_to: "", taken_at: null, @@ -274,7 +273,6 @@ export const DailyRounds = (props: any) => { physical_examination_info: state.form.physical_examination_info, other_details: state.form.other_details, consultation: consultationId, - recommend_discharge: JSON.parse(state.form.recommend_discharge), action: prevAction, review_interval: Number(prevReviewInterval), }; @@ -516,12 +514,6 @@ export const DailyRounds = (props: any) => { }} /> - - {state.form.rounds_type === "NORMAL" && ( <>

Vitals

diff --git a/src/Components/Patient/PatientInfoCard.tsx b/src/Components/Patient/PatientInfoCard.tsx index 701b76b5e3a..2d297642abb 100644 --- a/src/Components/Patient/PatientInfoCard.tsx +++ b/src/Components/Patient/PatientInfoCard.tsx @@ -5,23 +5,24 @@ import { DISCHARGE_REASONS, PATIENT_CATEGORIES, RESPIRATORY_SUPPORT, + TELEMEDICINE_ACTIONS, } from "../../Common/constants"; import { ConsultationModel, PatientCategory } from "../Facility/models"; -import ABHAProfileModal from "../ABDM/ABHAProfileModal"; -import Beds from "../Facility/Consultations/Beds"; -import ButtonV2 from "../Common/components/ButtonV2"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import DialogModal from "../Common/Dialog"; import { Link } from "raviger"; +import { useState } from "react"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import useConfig from "../../Common/hooks/useConfig"; +import { getDimensionOrDash } from "../../Common/utils"; +import dayjs from "../../Utils/dayjs"; +import { formatDate, formatDateTime } from "../../Utils/utils.js"; +import ABHAProfileModal from "../ABDM/ABHAProfileModal"; import LinkABHANumberModal from "../ABDM/LinkABHANumberModal"; import LinkCareContextModal from "../ABDM/LinkCareContextModal"; +import DialogModal from "../Common/Dialog"; +import ButtonV2 from "../Common/components/ButtonV2"; +import Beds from "../Facility/Consultations/Beds"; import { PatientModel } from "./models"; -import { getDimensionOrDash } from "../../Common/utils"; -import useConfig from "../../Common/hooks/useConfig"; -import { useState } from "react"; -import { formatAge, formatDate, formatDateTime } from "../../Utils/utils.js"; -import dayjs from "../../Utils/dayjs"; export default function PatientInfoCard(props: { patient: PatientModel; @@ -170,29 +171,44 @@ export default function PatientInfoCard(props: { Discharged from CARE

)} -

- {formatAge(patient.age, patient.date_of_birth, true)} - • - {patient.gender} - {consultation?.suggestion === "DC" && ( - <> - • - - - Domiciliary Care - - - )} - {consultation?.is_readmission && ( - <> - • - - - Readmitted - - - )} -

+
+
+ {patient.action && patient.action != 10 && ( +
+
+ + {" "} + { + TELEMEDICINE_ACTIONS.find( + (i) => i.id === patient.action + )?.desc + } + +
+
+ )} +
+
+ Age: {patient.age} years +
+
+
+
+ Gender: {patient.gender} +
+
+ {consultation?.suggestion === "DC" && ( +
+
+
+ Domiciliary Care + +
+
+
+ )} +
+
{[ ["Blood Group", patient.blood_group, patient.blood_group], diff --git a/src/Components/Patient/models.tsx b/src/Components/Patient/models.tsx index 5c9e24d548b..341e13e3c80 100644 --- a/src/Components/Patient/models.tsx +++ b/src/Components/Patient/models.tsx @@ -46,6 +46,7 @@ export interface AbhaObject { export interface PatientModel { test_id?: string; id?: string; + action?: number; name?: string; age?: number; allow_transfer?: boolean; From dd16bfad7e913fbad5421667587a934bc883e92e Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Tue, 17 Oct 2023 06:30:37 +0530 Subject: [PATCH 34/65] Middleware override feature in asset location (#6368) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Middleware override feature in asset location * Fix tooltip clipping * Fix Flaky Cypress test in Facility Module (#6384) * fix random facility page failure * fix random facility page failure * Revert "Prescriptions: Shrink discontinued prescriptions + Flip MAR timeline + Freeze primary columns in horizontal scroll (#6282)" (#6386) This reverts commit 5009a86abfb96e9b21f8371635176e4c3f73c5a7. * Refactor Asset Model Import Formatting (#6388) * Refactor Asset Model Import Formatting * fixes to warranty_amc_end_of_validity * Fix asset import file for cypress * Refactor: replaced Dispatch to useQuery/Request of src/Components/Auth/log… (#6333) * Refactor: replaced useDispatch to useQuery of src/Components/Auth/login.tsx * fix:useQuery changed to Request * feat: replaced dispatch with request * fix: types and added pathparams * fix: request body error * Update Login.tsx * change:Tres to Tbody * fixes: response type change * Update package-lock.json * fix hover height (#6352) * Split routes of App Router (#6363) * Prescription: show prescribed on & by and discontinued date in detail card (#6365) * Fixed bug in location picker in update facility page (#6377) * add all cases for map rendering * implement location pointer on searched location * Added cam auto reset in asset config page (#6375) * 💊 Adds support for editing prescriptions + Adds `useSlug` hook (#6369) * Adds hook: `useSlug` * bug fix: NumericWithUnits field not showing intial value * Form: support for showing global errors * Adds support for editing prescriptions (fixes #6340) * Fix cypress * fix cypress * Improve design --------- Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> Co-authored-by: Rithvik Nishad Co-authored-by: Aaron Jevil Nazareth <93522968+jevil25@users.noreply.github.com> Co-authored-by: Pranshu Aggarwal <70687348+Pranshu1902@users.noreply.github.com> Co-authored-by: Gampa Sri Harsh <114745442+sriharsh05@users.noreply.github.com> Co-authored-by: Tasnimul H. Tauhid --- cypress/pageobject/Asset/AssetCreation.ts | 2 +- src/CAREUI/misc/PaginatedList.tsx | 2 +- .../Assets/AssetType/HL7Monitor.tsx | 41 +++++++++++++++---- .../Assets/AssetType/ONVIFCamera.tsx | 30 ++++++++++++-- src/Components/Facility/AddLocationForm.tsx | 27 +++++++++++- src/Components/Facility/FacilityConfigure.tsx | 1 + .../Facility/LocationManagement.tsx | 20 +++++++-- src/Components/Facility/models.tsx | 1 + 8 files changed, 104 insertions(+), 20 deletions(-) diff --git a/cypress/pageobject/Asset/AssetCreation.ts b/cypress/pageobject/Asset/AssetCreation.ts index 45356a1a4e7..2c727820292 100644 --- a/cypress/pageobject/Asset/AssetCreation.ts +++ b/cypress/pageobject/Asset/AssetCreation.ts @@ -150,7 +150,7 @@ export class AssetPage { configureVitalAsset(hostName: string, localIp: string) { cy.get("[data-testid=asset-configure-button]").click(); - cy.get("#middlewareHostname").type(hostName); + cy.get("#middleware_hostname").type(hostName); cy.get("#localipAddress").type(localIp); } diff --git a/src/CAREUI/misc/PaginatedList.tsx b/src/CAREUI/misc/PaginatedList.tsx index 28ee17650ad..3b699177d9e 100644 --- a/src/CAREUI/misc/PaginatedList.tsx +++ b/src/CAREUI/misc/PaginatedList.tsx @@ -30,7 +30,7 @@ function useContextualized() { return ctx as PaginatedListContext; } -interface Props extends QueryOptions { +interface Props extends QueryOptions> { route: QueryRoute>; perPage?: number; children: (ctx: PaginatedListContext) => JSX.Element | JSX.Element[]; diff --git a/src/Components/Assets/AssetType/HL7Monitor.tsx b/src/Components/Assets/AssetType/HL7Monitor.tsx index 55f4d0c258e..6f896ac343f 100644 --- a/src/Components/Assets/AssetType/HL7Monitor.tsx +++ b/src/Components/Assets/AssetType/HL7Monitor.tsx @@ -74,15 +74,16 @@ const HL7Monitor = (props: HL7MonitorProps) => { }); } else { Notification.Error({ - msg: "Something went wrong..!", + msg: "Something went wrong!", }); } } else { - setIpAddress_error("Please Enter a Valid IP address !!"); + setIpAddress_error("IP address is invalid"); } }; - const middleware = middlewareHostname || facilityMiddlewareHostname; + const fallbackMiddleware = + asset?.location_object?.middleware_address || facilityMiddlewareHostname; if (isLoading) return ; return ( @@ -93,11 +94,29 @@ const HL7Monitor = (props: HL7MonitorProps) => {

Connection

-
+
+

Middleware Hostname

+ {!middlewareHostname && ( +
+ + + Middleware hostname sourced from{" "} + {asset?.location_object?.middleware_address + ? "asset location" + : "asset facility"} + +
+ )} +
+ } + placeholder={fallbackMiddleware} value={middlewareHostname} onChange={(e) => setMiddlewareHostname(e.value)} errorClassName="hidden" @@ -127,12 +146,16 @@ const HL7Monitor = (props: HL7MonitorProps) => { {assetType === "HL7MONITOR" && ( )} {assetType === "VENTILATOR" && ( )}
diff --git a/src/Components/Assets/AssetType/ONVIFCamera.tsx b/src/Components/Assets/AssetType/ONVIFCamera.tsx index 4720c876010..cb9cc1cc497 100644 --- a/src/Components/Assets/AssetType/ONVIFCamera.tsx +++ b/src/Components/Assets/AssetType/ONVIFCamera.tsx @@ -17,6 +17,7 @@ import TextFormField from "../../Form/FormFields/TextFormField"; import { Submit } from "../../Common/components/ButtonV2"; import { SyntheticEvent } from "react"; import useAuthUser from "../../../Common/hooks/useAuthUser"; +import CareIcon from "../../../CAREUI/icons/CareIcon"; interface Props { assetId: string; @@ -90,11 +91,11 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => { Notification.Success({ msg: "Asset Configured Successfully" }); onUpdated?.(); } else { - Notification.Error({ msg: "Something went wrong..!" }); + Notification.Error({ msg: "Something went wrong!" }); } setLoadingSetConfiguration(false); } else { - setIpAddress_error("Please Enter a Valid Camera address !!"); + setIpAddress_error("IP address is invalid"); } }; @@ -139,6 +140,9 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => { setLoadingAddPreset(false); }; + const fallbackMiddleware = + asset?.location_object?.middleware_address || facilityMiddlewareHostname; + if (isLoading) return ; return ( @@ -148,8 +152,26 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => {
+

Middleware Hostname

+ {!middlewareHostname && ( +
+ + + Middleware hostname sourced from{" "} + {asset?.location_object?.middleware_address + ? "asset location" + : "asset facility"} + +
+ )} +
+ } + placeholder={fallbackMiddleware} value={middlewareHostname} onChange={({ value }) => setMiddlewareHostname(value)} /> diff --git a/src/Components/Facility/AddLocationForm.tsx b/src/Components/Facility/AddLocationForm.tsx index f0dd7893aca..e71b68cc95c 100644 --- a/src/Components/Facility/AddLocationForm.tsx +++ b/src/Components/Facility/AddLocationForm.tsx @@ -25,12 +25,14 @@ export const AddLocationForm = (props: LocationFormProps) => { const dispatchAction: any = useDispatch(); const [isLoading, setIsLoading] = useState(false); const [name, setName] = useState(""); + const [middlewareAddress, setMiddlewareAddress] = useState(""); const [description, setDescription] = useState(""); const [facilityName, setFacilityName] = useState(""); const [locationName, setLocationName] = useState(""); const [errors, setErrors] = useState({ name: "", description: "", + middlewareAddress: "", }); const headerText = !locationId ? "Add Location" : "Update Location"; const buttonText = !locationId ? "Add Location" : "Update Location"; @@ -51,6 +53,7 @@ export const AddLocationForm = (props: LocationFormProps) => { setName(res?.data?.name || ""); setLocationName(res?.data?.name || ""); setDescription(res?.data?.description || ""); + setMiddlewareAddress(res?.data?.middleware_address || ""); } setIsLoading(false); } @@ -62,6 +65,7 @@ export const AddLocationForm = (props: LocationFormProps) => { const error = { name: "", description: "", + middlewareAddress: "", }; if (name.trim().length === 0) { @@ -69,6 +73,16 @@ export const AddLocationForm = (props: LocationFormProps) => { formValid = false; } + if ( + middlewareAddress && + middlewareAddress.match( + /^(?!https?:\/\/)[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*\.[a-zA-Z]{2,}$/ + ) === null + ) { + error.middlewareAddress = "Invalid Middleware Address"; + formValid = false; + } + setErrors(error); return formValid; }; @@ -83,6 +97,7 @@ export const AddLocationForm = (props: LocationFormProps) => { const data = { name, description, + middleware_address: middlewareAddress, }; const res = await dispatchAction( @@ -157,8 +172,18 @@ export const AddLocationForm = (props: LocationFormProps) => { error={errors.description} />
+
+ setMiddlewareAddress(e.value)} + error={errors.middlewareAddress} + /> +
-
+
navigate(`/facility/${facilityId}/location`, { diff --git a/src/Components/Facility/FacilityConfigure.tsx b/src/Components/Facility/FacilityConfigure.tsx index 6f0a8a9869c..fd5754b441d 100644 --- a/src/Components/Facility/FacilityConfigure.tsx +++ b/src/Components/Facility/FacilityConfigure.tsx @@ -155,6 +155,7 @@ export const FacilityConfigure = (props: any) => { handleChange(e)} error={state.errors?.middleware_address} diff --git a/src/Components/Facility/LocationManagement.tsx b/src/Components/Facility/LocationManagement.tsx index 38dcfc1f389..c018a7c222b 100644 --- a/src/Components/Facility/LocationManagement.tsx +++ b/src/Components/Facility/LocationManagement.tsx @@ -65,12 +65,24 @@ export default function LocationManagement({ facilityId }: Props) { ); } -const Location = ({ name, description, id }: LocationModel) => ( +const Location = ({ + name, + description, + middleware_address, + id, +}: LocationModel) => (
-
-

{name}

-

{description}

+
+

+ {name} +

+ {description || "-"} +

+

+

+ {middleware_address} +

diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index de2c6af698a..439194e85de 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -186,6 +186,7 @@ export interface LocationModel { id?: string; name?: string; description?: string; + middleware_address?: string; facility?: { name: string; }; From c7765246999d6ad593ace5da165f64b2a611fd4e Mon Sep 17 00:00:00 2001 From: konavivekramakrishna <101407963+konavivekramakrishna@users.noreply.github.com> Date: Tue, 17 Oct 2023 06:33:24 +0530 Subject: [PATCH 35/65] Replaced useDispatch with useQuery and request in ExternalResult (src/Components/ExternalResult/** ) (#6402) * replaced dispatch with useQuery in ResultList * replaced useDispatch with useQuery in listfilter * replaced useDispatch with reqeust in ExternalResultUpload * fixed filtering * fix ResultList * rm clg * make code readable * fix build * removed useEffect and useState from ResultList.tsx * Apply changes from code review --------- Co-authored-by: rithviknishad --- .../ExternalResult/ExternalResultUpload.tsx | 35 +++-- src/Components/ExternalResult/ListFilter.tsx | 115 +++++++++-------- src/Components/ExternalResult/ResultItem.tsx | 117 ++++++++--------- src/Components/ExternalResult/ResultList.tsx | 106 ++++++--------- .../ExternalResult/ResultUpdate.tsx | 121 ++++++++---------- src/Components/ExternalResult/models.ts | 72 +++++++++++ src/Redux/actions.tsx | 7 - src/Redux/api.tsx | 27 +++- src/Utils/request/request.ts | 20 ++- 9 files changed, 340 insertions(+), 280 deletions(-) create mode 100644 src/Components/ExternalResult/models.ts diff --git a/src/Components/ExternalResult/ExternalResultUpload.tsx b/src/Components/ExternalResult/ExternalResultUpload.tsx index 5f39cb213a4..a9d5c29bfe8 100644 --- a/src/Components/ExternalResult/ExternalResultUpload.tsx +++ b/src/Components/ExternalResult/ExternalResultUpload.tsx @@ -2,21 +2,21 @@ import _ from "lodash"; import { navigate } from "raviger"; import { lazy, useState } from "react"; import CSVReader from "react-csv-reader"; -import { useDispatch } from "react-redux"; import useConfig from "../../Common/hooks/useConfig"; -import { externalResultUploadCsv } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications.js"; const PageTitle = lazy(() => import("../Common/PageTitle")); import { useTranslation } from "react-i18next"; import { Cancel, Submit } from "../Common/components/ButtonV2"; import useAppHistory from "../../Common/hooks/useAppHistory"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import { IExternalResult } from "./models"; export default function ExternalResultUpload() { const { sample_format_external_result_import } = useConfig(); - const dispatch: any = useDispatch(); // for disabling save button once clicked const [loading, setLoading] = useState(false); - const [csvData, setCsvData] = useState(new Array()); + const [csvData, setCsvData] = useState(new Array()); const [errors, setErrors] = useState([]); const handleForce = (data: any) => { setCsvData(data); @@ -32,26 +32,35 @@ export default function ExternalResultUpload() { header.toLowerCase().replace(/\W/g, "_"), }; - const handleSubmit = (e: any) => { + const handleSubmit = async (e: any) => { e.preventDefault(); setLoading(true); const valid = true; - if (csvData.length !== 0) { - const data = { - sample_tests: csvData, - }; + if (csvData.length !== 0) { if (valid) { setErrors([]); - dispatch(externalResultUploadCsv(data)).then((resp: any) => { - if (resp && resp.status === 202) { + + try { + const { res, data } = await request(routes.externalResultUploadCsv, { + body: { + sample_tests: csvData, + }, + }); + + if (res && res.status === 202) { setLoading(false); navigate("/external_results"); } else { - setErrors(resp.data.map((err: any) => Object.entries(err))); + if (data) { + setErrors(data.map((err: any) => Object.entries(err))); + } setLoading(false); } - }); + } catch (error) { + console.error("An error occurred:", error); + setLoading(false); + } } else { setLoading(false); } diff --git a/src/Components/ExternalResult/ListFilter.tsx b/src/Components/ExternalResult/ListFilter.tsx index 91b75e535d2..75358196ffd 100644 --- a/src/Components/ExternalResult/ListFilter.tsx +++ b/src/Components/ExternalResult/ListFilter.tsx @@ -1,6 +1,4 @@ -import { useEffect, useState } from "react"; -import { getAllLocalBodyByDistrict } from "../../Redux/actions"; -import { useDispatch } from "react-redux"; +import { useState } from "react"; import useMergeState from "../../Common/hooks/useMergeState"; import { navigate } from "raviger"; import { useTranslation } from "react-i18next"; @@ -11,6 +9,9 @@ import DateRangeFormField from "../Form/FormFields/DateRangeFormField"; import dayjs from "dayjs"; import { dateQueryString } from "../../Utils/utils"; import useAuthUser from "../../Common/hooks/useAuthUser"; +import useQuery from "../../Utils/request/useQuery"; +import routes from "../../Redux/api"; +import Loading from "../Common/Loading"; const clearFilterState = { created_date_before: "", @@ -29,10 +30,8 @@ export default function ListFilter(props: any) { const { filter, onChange, closeFilter, dataList } = props; const [wardList, setWardList] = useState([]); const [lsgList, setLsgList] = useState([]); - const [wards, setWards] = useState([]); const [selectedLsgs, setSelectedLsgs] = useState([]); - const dispatch: any = useDispatch(); const authUser = useAuthUser(); const [filterState, setFilterState] = useMergeState({ created_date_before: filter.created_date_before || null, @@ -45,6 +44,57 @@ export default function ListFilter(props: any) { }); const { t } = useTranslation(); + const { loading } = useQuery(routes.getAllLocalBodyByDistrict, { + pathParams: { id: String(authUser.district) }, + onResponse: ({ res, data }) => { + if (res && data) { + let allWards: any[] = []; + let allLsgs: any[] = []; + if (res && data) { + data.forEach((local: any) => { + allLsgs = [...allLsgs, { id: local.id, name: local.name }]; + if (local.wards) { + local.wards.forEach((ward: any) => { + allWards = [ + ...allWards, + { + id: ward.id, + name: ward.number + ": " + ward.name, + panchayath: local.name, + number: ward.number, + local_body_id: local.id, + }, + ]; + }); + } + }); + } + + sortByName(allWards); + sortByName(allLsgs); + setWardList(allWards || []); + setLsgList(allLsgs || []); + const filteredWard = filter?.wards?.split(",").map(Number); + const selectedWards: any = + filteredWard && allWards + ? allWards.filter(({ id }: { id: number }) => { + return filteredWard.includes(id); + }) + : []; + setWards(selectedWards); + + const filteredLsgs = filter?.local_bodies?.split(",").map(Number); + const selectedLsgs: any = + filteredLsgs && allLsgs + ? allLsgs.filter(({ id }: { id: number }) => { + return filteredLsgs.includes(id); + }) + : []; + setSelectedLsgs(selectedLsgs); + } + }, + }); + const handleDateRangeChange = ( startDateId: string, endDateId: string, @@ -118,59 +168,10 @@ export default function ListFilter(props: any) { }); }; - useEffect(() => { - async function getWardList() { - const id = authUser.district; - const res = await dispatch(getAllLocalBodyByDistrict({ id })); - let allWards: any[] = []; - let allLsgs: any[] = []; - res?.data?.forEach((local: any) => { - allLsgs = [...allLsgs, { id: local.id, name: local.name }]; - if (local.wards) { - local.wards.forEach((ward: any) => { - allWards = [ - ...allWards, - { - id: ward.id, - name: ward.number + ": " + ward.name, - panchayath: local.name, - number: ward.number, - local_body_id: local.id, - }, - ]; - }); - } - }); - sortByName(allWards); - sortByName(allLsgs); - setWardList(allWards || []); - setLsgList(allLsgs || []); - const filteredWard = filter?.wards?.split(",").map(Number); - const selectedWards: any = - filteredWard && allWards - ? allWards.filter(({ id }: { id: number }) => { - return filteredWard.includes(id); - }) - : []; - setWards(selectedWards); - - const filteredLsgs = filter?.local_bodies?.split(",").map(Number); - const selectedLsgs: any = - filteredLsgs && allLsgs - ? allLsgs.filter(({ id }: { id: number }) => { - return filteredLsgs.includes(id); - }) - : []; - setSelectedLsgs(selectedLsgs); - } - getWardList(); - }, []); - const filterWards = () => { const selectedLsgIds: any = selectedLsgs.map((e) => { return e.id; }); - const selectedwards: any = selectedLsgIds.length === 0 ? wardList @@ -183,13 +184,15 @@ export default function ListFilter(props: any) { const handleChange = (event: any) => { const { name, value } = event.target; - const filterData: any = { ...filterState }; filterData[name] = value; - setFilterState(filterData); }; + if (loading) { + ; + } + return ( import("../Common/Loading")); export default function ResultItem(props: any) { - const dispatch: any = useDispatch(); - const initialData: any = {}; - const [data, setData] = useState(initialData); - const [isLoading, setIsLoading] = useState(true); const [showDeleteAlert, setShowDeleteAlert] = useState(false); const { t } = useTranslation(); - const fetchData = useCallback( - async (status: statusType) => { - setIsLoading(true); - const res = await dispatch(externalResult({ id: props.id })); - if (!status.aborted) { - if (res && res.data) { - setData(res.data); - } - setIsLoading(false); - } - }, - [props.id, dispatch] - ); + const { data: resultItemData, loading } = useQuery(routes.externalResult, { + pathParams: { id: props.id }, + }); const handleDelete = async () => { - const res = await dispatch(deleteExternalResult(props.id)); - if (res?.status === 204) { - Notification.Success({ - msg: t("record_has_been_deleted_successfully"), + if (showDeleteAlert) { + const { res, data } = await request(routes.deleteExternalResult, { + pathParams: { id: props.id }, }); - } else { - Notification.Error({ - msg: - t("error_while_deleting_record") + ": " + (res?.data?.detail || ""), - }); - } - setShowDeleteAlert(false); - navigate("/external_results"); + if (res?.status === 204) { + Notification.Success({ + msg: t("record_has_been_deleted_successfully"), + }); + } else { + Notification.Error({ + msg: t("error_while_deleting_record") + ": " + (data?.detail || ""), + }); + } + setShowDeleteAlert(false); + navigate("/external_results"); + } }; - useAbortableEffect( - (status: statusType) => { - fetchData(status); - }, - [fetchData] - ); - - if (isLoading) { + if (loading || !resultItemData) { return ; } @@ -69,14 +50,18 @@ export default function ResultItem(props: any) { variant="danger" action={t("delete")} show={showDeleteAlert} - onConfirm={() => handleDelete()} + onConfirm={() => { + handleDelete(); + }} onClose={() => setShowDeleteAlert(false)} />
diff --git a/src/Components/ExternalResult/ResultList.tsx b/src/Components/ExternalResult/ResultList.tsx index 74fbf8430b0..d148b26e3f5 100644 --- a/src/Components/ExternalResult/ResultList.tsx +++ b/src/Components/ExternalResult/ResultList.tsx @@ -1,12 +1,10 @@ import ButtonV2 from "../Common/components/ButtonV2"; import { navigate } from "raviger"; -import { lazy, useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; +import { lazy, useState } from "react"; import { externalResultList } from "../../Redux/actions"; import ListFilter from "./ListFilter"; import FacilitiesSelectDialogue from "./FacilitiesSelectDialogue"; import { FacilityModel } from "../Facility/models"; -import { parsePhoneNumber } from "../../Utils/utils"; import SearchInput from "../Form/SearchInput"; import useFilters from "../../Common/hooks/useFilters"; import CareIcon from "../../CAREUI/icons/CareIcon"; @@ -15,14 +13,12 @@ import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField"; import CountBlock from "../../CAREUI/display/Count"; import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; import Page from "../Common/components/Page"; - +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; +import { parsePhoneNumber } from "../../Utils/utils"; const Loading = lazy(() => import("../Common/Loading")); export default function ResultList() { - const dispatch: any = useDispatch(); - const [data, setData] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [totalCount, setTotalCount] = useState(0); const { qParams, updateQuery, @@ -57,61 +53,31 @@ export default function ResultList() { setPhoneNumberError("Enter a valid number"); }; + const params = { + page: qParams.page || 1, + name: qParams.name || "", + mobile_number: qParams.mobile_number + ? parsePhoneNumber(qParams.mobile_number) ?? "" + : "", + wards: qParams.wards || undefined, + local_bodies: qParams.local_bodies || undefined, + created_date_before: qParams.created_date_before || undefined, + created_date_after: qParams.created_date_after || undefined, + result_date_before: qParams.result_date_before || undefined, + result_date_after: qParams.result_date_after || undefined, + sample_collection_date_after: + qParams.sample_collection_date_after || undefined, + sample_collection_date_before: + qParams.sample_collection_date_before || undefined, + offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, + srf_id: qParams.srf_id || undefined, + }; - let manageResults: any = null; - useEffect(() => { - setIsLoading(true); - const params = { - page: qParams.page || 1, - name: qParams.name || "", - mobile_number: qParams.mobile_number - ? parsePhoneNumber(qParams.mobile_number) ?? "" - : "", - wards: qParams.wards || undefined, - local_bodies: qParams.local_bodies || undefined, - created_date_before: qParams.created_date_before || undefined, - created_date_after: qParams.created_date_after || undefined, - result_date_before: qParams.result_date_before || undefined, - result_date_after: qParams.result_date_after || undefined, - sample_collection_date_after: - qParams.sample_collection_date_after || undefined, - sample_collection_date_before: - qParams.sample_collection_date_before || undefined, - offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, - srf_id: qParams.srf_id || undefined, - }; - - dispatch(externalResultList(params, "externalResultList")) - .then((res: any) => { - if (res && res.data) { - setData(res.data.results); - setTotalCount(res.data.count); - setIsLoading(false); - } - }) - .catch(() => { - setIsLoading(false); - }); + const { data, loading } = useQuery(routes.externalResultList, { + query: params, + }); - if (!params.mobile_number) { - setPhoneNum("+91"); - } - }, [ - dispatch, - qParams.name, - qParams.page, - qParams.mobile_number, - qParams.wards, - qParams.created_date_before, - qParams.created_date_after, - qParams.result_date_before, - qParams.result_date_after, - qParams.sample_collection_date_after, - qParams.sample_collection_date_before, - qParams.local_bodies, - qParams.srf_id, - dataList, - ]); + let manageResults: any = null; const removeLSGFilter = (paramKey: any, id: any) => { const updatedLsgList = dataList.lsgList.filter((x: any) => x.id !== id); @@ -158,8 +124,8 @@ export default function ResultList() { }; let resultList: any[] = []; - if (data && data.length) { - resultList = data.map((result: any) => { + if (data?.results.length) { + resultList = data.results.map((result: any) => { const resultUrl = `/external_results/${result.id}`; return (
@@ -173,7 +139,7 @@ export default function ResultList() { className="group inline-flex space-x-2 text-sm leading-5" >

- {result.name} - {result.age} {result.age_in} + {`${result.name}`} - {result.age} {result.age_in}

@@ -214,7 +180,7 @@ export default function ResultList() { }); } - if (isLoading || !data) { + if (loading) { manageResults = ( ); - } else if (data && data.length) { + } else if (data?.results.length) { manageResults = <>{resultList}; - } else if (data && data.length === 0) { + } else if (data?.results.length === 0) { manageResults = (
+ Serviced on @@ -537,7 +537,7 @@ const AssetManage = (props: AssetManageProps) => { Last Updated + Edit
Moved from + Moved to + Moved By + Moved On
@@ -222,9 +188,9 @@ export default function ResultList() {
@@ -286,8 +252,8 @@ export default function ResultList() {
@@ -358,7 +324,7 @@ export default function ResultList() {
- + import("../Common/Loading")); @@ -63,79 +57,70 @@ export default function UpdateResult(props: any) { const { id } = props; const { goBack } = useAppHistory(); - const dispatchAction: any = useDispatch(); const [state, dispatch] = useReducer(FormReducer, initialState); - const [isLoading, setIsLoading] = useState(false); + const [isLoading, setIsLoading] = useState(true); const [isLocalbodyLoading, setIsLocalbodyLoading] = useState(false); const [isWardLoading, setIsWardLoading] = useState(false); const [localBody, setLocalBody] = useState(initialLocalbodies); const [ward, setWard] = useState(initialLocalbodies); - const fetchData = useCallback( - async (status: statusType) => { - setIsLoading(true); - const res = await dispatchAction(externalResult({ id: id })); - if (!status.aborted) { - if (res && res.data) { - const form = { ...state.form }; - form["name"] = res.data.name; - form["age"] = res.data.age; - form["age_in"] = res.data.age_in; - form["srf_id"] = res.data.srf_id; - form["address"] = res.data.address; - form["district"] = res.data.district_object.name; - form["local_body"] = String(res.data.local_body); - form["ward"] = String(res.data.ward); - form["patient_created"] = String(res.data.patient_created); + const { loading } = useQuery(routes.externalResult, { + pathParams: { id }, + onResponse: async ({ res, data }) => { + if (res && data) { + const form = { ...state.form }; + form["name"] = data.name; + form["age"] = data.age; + form["age_in"] = data.age_in; + form["srf_id"] = data.srf_id; + form["address"] = data.address; + form["district"] = data.district_object.name; + form["local_body"] = String(data.local_body); + form["ward"] = String(data.ward); + form["patient_created"] = String(data.patient_created); - dispatch({ type: "set_form", form }); + dispatch({ type: "set_form", form }); - Promise.all([ - fetchLocalBody(res.data.district), - fetchWards(res.data.local_body), - ]); - } + Promise.all([ + fetchLocalBody(data.district), + fetchWards(data.local_body), + ]); setIsLoading(false); } }, - [props.id, dispatchAction] - ); + }); - const fetchLocalBody = useCallback( - async (id: string) => { - if (Number(id) > 0) { - setIsLocalbodyLoading(true); - const localBodyList = await dispatchAction( - getLocalbodyByDistrict({ id }) - ); + const fetchLocalBody = async (id: number) => { + if (Number(id) > 0) { + setIsLocalbodyLoading(true); + const { res, data } = await request(routes.getLocalbodyByDistrict, { + pathParams: { id: String(id) }, + }); + if (res && data) { setIsLocalbodyLoading(false); - setLocalBody([...initialLocalbodies, ...localBodyList.data]); - } else { - setLocalBody(initialLocalbodies); + setLocalBody([...initialLocalbodies, ...data]); } - }, - [dispatchAction] - ); + } else { + setLocalBody(initialLocalbodies); + } + }; const fetchWards = useCallback( - async (id: string) => { + async (id: number) => { if (Number(id) > 0) { setIsWardLoading(true); - const wardList = await dispatchAction(getWardByLocalBody({ id })); + const { res, data } = await request(routes.getWardByLocalBody, { + pathParams: { id: String(id) }, + }); + if (res && data) { + setWard([...initialWard, ...data.results]); + } setIsWardLoading(false); - setWard([...initialWard, ...wardList.data.results]); } else { setWard(initialLocalbodies); } }, - [dispatchAction] - ); - - useAbortableEffect( - (status: statusType) => { - fetchData(status); - }, - [fetchData] + [props.id] ); const validateForm = () => { @@ -195,15 +180,20 @@ export default function UpdateResult(props: any) { const validForm = validateForm(); if (validForm) { setIsLoading(true); - const data = { + const rdata = { address: state.form.address ? state.form.address : undefined, local_body: state.form.local_body ? state.form.local_body : undefined, ward: state.form.ward, patient_created: state.form.patient_created === "true", }; - const res = await dispatchAction(partialUpdateExternalResult(id, data)); + + const { res, data } = await request(routes.partialUpdateExternalResult, { + pathParams: { id }, + body: rdata, + }); + setIsLoading(false); - if (res && res.data) { + if (res && data) { dispatch({ type: "set_form", form: initForm }); Notification.Success({ msg: "External Result updated successfully", @@ -213,7 +203,7 @@ export default function UpdateResult(props: any) { } }; - if (isLoading) { + if (isLoading || loading) { return ; } @@ -262,10 +252,7 @@ export default function UpdateResult(props: any) { options={localBody} optionLabel={(localBody) => localBody.name} optionValue={(localBody) => localBody.id} - onChange={(e) => [ - handleChange(e), - fetchWards(String(e.value)), - ]} + onChange={(e) => [handleChange(e), fetchWards(e.value)]} error={state.errors.local_body} /> )} diff --git a/src/Components/ExternalResult/models.ts b/src/Components/ExternalResult/models.ts new file mode 100644 index 00000000000..8ccaba04d05 --- /dev/null +++ b/src/Components/ExternalResult/models.ts @@ -0,0 +1,72 @@ +export interface IExternalResultUploadCsv { + sample_tests: any[]; +} + +export interface IExternalResult { + id: number; + name: string; + age: number; + age_in: string; + test_type: string; + result: string; + result_date: string; + patient_created: boolean; + gender: string; + source: string; + is_repeat: boolean; + mobile_number: string; + patient_status: string; + sample_type: string; + sample_collection_date: string; + patient_category: string; + srf_id: string; + district_object: { + id: number; + name: string; + state: number; + }; + district: number; + ward: number; + local_body: number; + address: string; + ward_object: { + id: number; + number: number; + name: string; + }; + local_body_object: { + id: number; + name: string; + }; +} + +export interface ILocalBodies { + id: number; + name: string; + state: number; + number: number; + body_type: number; + localbody_code: string; + district: number; +} + +export interface IDeleteExternalResult { + detail: string; +} + +export interface IPartialUpdateExternalResult { + address: string; + ward: number; + local_body: number; + patient_created: boolean; +} + +export interface ILocalBodyByDistrict { + id: number; + name: string; + state: number; +} + +export interface IExternalResultCsv { + sample_tests: Partial[]; +} diff --git a/src/Redux/actions.tsx b/src/Redux/actions.tsx index 26e006f4cea..06a12796737 100644 --- a/src/Redux/actions.tsx +++ b/src/Redux/actions.tsx @@ -668,13 +668,6 @@ export const externalResultList = (params: object, altKey: string) => { export const externalResult = (pathParam: object) => { return fireRequest("externalResult", [], {}, pathParam); }; -export const externalResultUploadCsv = (params: object) => { - return fireRequest("externalResultUploadCsv", [], params); -}; - -export const deleteExternalResult = (id: string) => { - return fireRequest("deleteExternalResult", [id], {}); -}; export const updateExternalResult = (id: number, params: object) => { return fireRequest("updateExternalResult", [], params, { id }); diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 8a4ca5cf1df..02c38afcc5a 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -1,6 +1,14 @@ import { IConfig } from "../Common/hooks/useConfig"; import { AssetData } from "../Components/Assets/AssetTypes"; -import { LocationModel } from "../Components/Facility/models"; +import { + IDeleteExternalResult, + IExternalResult, + IExternalResultCsv, + ILocalBodies, + ILocalBodyByDistrict, + IPartialUpdateExternalResult, +} from "../Components/ExternalResult/models"; +import { LocationModel, WardModel } from "../Components/Facility/models"; import { Prescription } from "../Components/Medicine/models"; import { UserModel } from "../Components/Users/models"; import { PaginatedResponse } from "../Utils/request/types"; @@ -501,18 +509,25 @@ const routes = { // External Results externalResultList: { path: "/api/v1/external_result/", + method: "GET", + TRes: Type>(), }, externalResult: { path: "/api/v1/external_result/{id}/", + method: "GET", + TRes: Type(), }, externalResultUploadCsv: { path: "/api/v1/external_result/bulk_upsert/", method: "POST", + TBody: Type(), + TRes: Type(), }, deleteExternalResult: { - path: "/api/v1/external_result", + path: "/api/v1/external_result/{id}/", method: "DELETE", + TRes: Type(), }, updateExternalResult: { @@ -523,6 +538,8 @@ const routes = { partialUpdateExternalResult: { path: "/api/v1/external_result/{id}/", method: "PATCH", + TRes: Type(), + TBody: Type(), }, // States @@ -547,9 +564,13 @@ const routes = { }, getAllLocalBodyByDistrict: { path: "/api/v1/district/{id}/get_all_local_body/", + method: "GET", + TRes: Type(), }, getLocalbodyByDistrict: { path: "/api/v1/district/{id}/local_bodies/", + method: "GET", + TRes: Type(), }, // Local Body @@ -572,6 +593,8 @@ const routes = { }, getWardByLocalBody: { path: "/api/v1/ward/?local_body={id}", + method: "GET", + TRes: Type>(), }, // Sample Test diff --git a/src/Utils/request/request.ts b/src/Utils/request/request.ts index 2d735734aae..d428107b64c 100644 --- a/src/Utils/request/request.ts +++ b/src/Utils/request/request.ts @@ -38,7 +38,7 @@ export default async function request( try { const res = await fetch(url, options); - const data: TData = await res.json(); + const data = await getResponseBody(res); result = { res, @@ -61,3 +61,21 @@ export default async function request( ); return result; } + +async function getResponseBody(res: Response): Promise { + if (!(res.headers.get("content-length") !== "0")) { + return null as TData; + } + + const isJson = res.headers.get("content-type")?.includes("application/json"); + + if (!isJson) { + return (await res.text()) as TData; + } + + try { + return await res.json(); + } catch { + return (await res.text()) as TData; + } +} From 7e29f250190ad65ff7726842a133694fd6bf5b86 Mon Sep 17 00:00:00 2001 From: Gokulram A Date: Tue, 17 Oct 2023 06:33:58 +0530 Subject: [PATCH 36/65] Added cypress test to verify warranty expiry label on an asset (#6428) * added cypress test to verify warranty expiry label * updated spec to check if the warranty expiry label goes off once updated * verify the last change --------- Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> --- cypress/e2e/assets_spec/assets_manage.cy.ts | 33 +++++++++++++++++++++ cypress/pageobject/Asset/AssetCreation.ts | 27 +++++++++++++++-- src/CAREUI/display/Chip.tsx | 2 ++ src/Components/Assets/AssetsList.tsx | 3 ++ 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/cypress/e2e/assets_spec/assets_manage.cy.ts b/cypress/e2e/assets_spec/assets_manage.cy.ts index 34c554b374e..8b440f68761 100644 --- a/cypress/e2e/assets_spec/assets_manage.cy.ts +++ b/cypress/e2e/assets_spec/assets_manage.cy.ts @@ -5,6 +5,12 @@ import { AssetSearchPage } from "../../pageobject/Asset/AssetSearch"; import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import { AssetFilters } from "../../pageobject/Asset/AssetFilters"; +function addDaysToDate(numberOfDays: number) { + const inputDate = new Date(); + inputDate.setDate(inputDate.getDate() + numberOfDays); + return inputDate.toISOString().split("T")[0]; +} + describe("Asset", () => { const assetPage = new AssetPage(); const loginPage = new LoginPage(); @@ -26,6 +32,33 @@ describe("Asset", () => { cy.awaitUrl("/assets"); }); + it("Verify Asset Warranty Expiry Label", () => { + assetSearchPage.typeSearchKeyword(assetname); + assetSearchPage.pressEnter(); + assetSearchPage.verifyBadgeContent(assetname); + assetSearchPage.clickAssetByName(assetname); + assetPage.clickupdatedetailbutton(); + assetPage.scrollintoWarrantyDetails(); + assetPage.enterWarrantyExpiryDate(addDaysToDate(100)); // greater than 3 months + assetPage.clickassetupdatebutton(); + assetPage.verifyWarrantyExpiryLabel(""); + assetPage.clickupdatedetailbutton(); + assetPage.scrollintoWarrantyDetails(); + assetPage.enterWarrantyExpiryDate(addDaysToDate(80)); // less than 3 months + assetPage.clickassetupdatebutton(); + assetPage.verifyWarrantyExpiryLabel("3 months"); + assetPage.clickupdatedetailbutton(); + assetPage.scrollintoWarrantyDetails(); + assetPage.enterWarrantyExpiryDate(addDaysToDate(20)); // less than 1 month + assetPage.clickassetupdatebutton(); + assetPage.verifyWarrantyExpiryLabel("1 month"); + assetPage.clickupdatedetailbutton(); + assetPage.scrollintoWarrantyDetails(); + assetPage.enterWarrantyExpiryDate(addDaysToDate(100)); // check for greater than 3 months again to verify the label is removed + assetPage.clickassetupdatebutton(); + assetPage.verifyWarrantyExpiryLabel(""); + }); + it("Create & Edit a service history and verify reflection", () => { assetSearchPage.typeSearchKeyword(assetname); assetSearchPage.pressEnter(); diff --git a/cypress/pageobject/Asset/AssetCreation.ts b/cypress/pageobject/Asset/AssetCreation.ts index 2c727820292..93bbc87c9ab 100644 --- a/cypress/pageobject/Asset/AssetCreation.ts +++ b/cypress/pageobject/Asset/AssetCreation.ts @@ -285,16 +285,39 @@ export class AssetPage { cy.get("#notes").scrollIntoView(); } - enterAssetNotes(text) { + enterAssetNotes(text: string) { cy.get("#notes").click().clear(); cy.get("#notes").click().type(text); } - enterAssetservicedate(text) { + enterAssetservicedate(text: string) { cy.get("input[name='last_serviced_on']").click(); cy.get("#date-input").click().type(text); } + scrollintoWarrantyDetails() { + cy.get("#warranty-details").scrollIntoView(); + } + + enterWarrantyExpiryDate(text: string) { + cy.get("#WarrantyAMCExpiry").click(); + cy.get("#WarrantyAMCExpiry").click().type(text); + } + + verifyWarrantyExpiryLabel(duration: string) { + if (duration === "") { + cy.get("#warranty-amc-expired-red").should("not.exist"); + cy.get("#warranty-amc-expiring-soon-orange").should("not.exist"); + cy.get("#warranty-amc-expiring-soon-yellow").should("not.exist"); + } else if (duration === "expired") { + cy.get("#warranty-amc-expired-red").should("be.visible"); + } else if (duration === "1 month") { + cy.get("#warranty-amc-expiring-soon-orange").should("be.visible"); + } else if (duration === "3 months") { + cy.get("#warranty-amc-expiring-soon-yellow").should("be.visible"); + } + } + clickassetupdatebutton() { cy.get("#submit").click(); } diff --git a/src/CAREUI/display/Chip.tsx b/src/CAREUI/display/Chip.tsx index abc21711551..7bcbd6078e4 100644 --- a/src/CAREUI/display/Chip.tsx +++ b/src/CAREUI/display/Chip.tsx @@ -11,6 +11,7 @@ interface Props { text: string; tooltip?: string; className?: string; + id?: string; } export default function Chip({ @@ -21,6 +22,7 @@ export default function Chip({ }: Props) { return ( Date: Tue, 17 Oct 2023 06:34:39 +0530 Subject: [PATCH 37/65] Replaced export button Icon with relevant Export Icon (#6451) --- src/Components/Common/Export.tsx | 2 +- src/Components/Patient/ManagePatients.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Components/Common/Export.tsx b/src/Components/Common/Export.tsx index 3595b3c84a0..f991c476aac 100644 --- a/src/Components/Common/Export.tsx +++ b/src/Components/Common/Export.tsx @@ -44,7 +44,7 @@ export const ExportMenu = ({ } + icon={} className="tooltip border-primary-500 bg-white text-primary-500 hover:bg-primary-100 enabled:border" > {exportItems.map((item) => ( diff --git a/src/Components/Patient/ManagePatients.tsx b/src/Components/Patient/ManagePatients.tsx index 6e83eb9bc91..aa3a837647d 100644 --- a/src/Components/Patient/ManagePatients.tsx +++ b/src/Components/Patient/ManagePatients.tsx @@ -815,7 +815,7 @@ export const PatientManager = () => { }} className="mr-5 w-full lg:w-fit" > - + Export ) : ( From 9b362c104390e5a60bf8059b8ef38a217987b007 Mon Sep 17 00:00:00 2001 From: Kshitij Verma <101321276+kshitijv256@users.noreply.github.com> Date: Wed, 18 Oct 2023 06:46:31 +0530 Subject: [PATCH 38/65] Replaced dispatch with useQuery and request in Asset module (#6374) * changed to request in HL7monitor.tsx * replaced dispatch in assetTypes * changed dispatch in filteer, import files * changed all dispatch requests with useQuery and request in asset module * added TBody to various endpoints * fixed assetfilter page * fixed asset import modal page * made requested changes * made requested changes * reverted to assetBedModel * fixed delete asset function * fixed after delete redirection * removed extra setstates * updated failing cypress tests for asset module * removed useEffects * removed changes from cypress files * fixed merge issues * fixed transactions issue * remove console logs ---------- Co-authored-by: Rithvik Nishad --- cypress/e2e/assets_spec/asset_homepage.cy.ts | 2 +- src/Components/Assets/AssetFilter.tsx | 79 +++------ src/Components/Assets/AssetImportModal.tsx | 22 +-- src/Components/Assets/AssetManage.tsx | 129 ++++++-------- .../Assets/AssetServiceEditModal.tsx | 20 ++- .../Assets/AssetType/HL7Monitor.tsx | 36 ++-- .../Assets/AssetType/ONVIFCamera.tsx | 58 +++--- src/Components/Assets/AssetTypes.tsx | 12 ++ src/Components/Assets/AssetsList.tsx | 166 ++++++------------ .../Assets/configure/MonitorConfigure.tsx | 78 ++++---- src/Redux/actions.tsx | 32 ---- src/Redux/api.tsx | 33 +++- 12 files changed, 270 insertions(+), 397 deletions(-) diff --git a/cypress/e2e/assets_spec/asset_homepage.cy.ts b/cypress/e2e/assets_spec/asset_homepage.cy.ts index e1fcf278442..61f7d8a52c0 100644 --- a/cypress/e2e/assets_spec/asset_homepage.cy.ts +++ b/cypress/e2e/assets_spec/asset_homepage.cy.ts @@ -90,7 +90,7 @@ describe("Asset Tab", () => { assetPage.selectImportOption(); assetPage.selectImportFacility("Dummy Facility 1"); assetPage.importAssetFile(); - assetPage.selectImportLocation("Camera Loc"); + assetPage.selectImportLocation("Camera Locations"); assetPage.clickImportAsset(); }); diff --git a/src/Components/Assets/AssetFilter.tsx b/src/Components/Assets/AssetFilter.tsx index 4dca7dfecdb..8b250088d1a 100644 --- a/src/Components/Assets/AssetFilter.tsx +++ b/src/Components/Assets/AssetFilter.tsx @@ -1,19 +1,14 @@ import { useState, useEffect, useCallback } from "react"; -import { useAbortableEffect, statusType } from "../../Common/utils"; import { navigate, useQueryParams } from "raviger"; import { FacilitySelect } from "../Common/FacilitySelect"; import { FacilityModel } from "../Facility/models"; -import { useDispatch } from "react-redux"; -import { - getFacilityAssetLocation, - getPermittedFacility, -} from "../../Redux/actions"; -import * as Notification from "../../Utils/Notifications.js"; import { LocationSelect } from "../Common/LocationSelect"; import { AssetClass, AssetLocationObject } from "./AssetTypes"; import { FieldLabel } from "../Form/FormFields/FormField"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; import FiltersSlideover from "../../CAREUI/interactive/FiltersSlideover"; +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; import DateRangeFormField from "../Form/FormFields/DateRangeFormField"; import dayjs from "dayjs"; import { FieldChangeEvent } from "../Form/FormFields/Utils"; @@ -35,7 +30,6 @@ const getDate = (value: any) => function AssetFilter(props: any) { const { filter, onChange, closeFilter } = props; - const dispatch: any = useDispatch(); const [facility, setFacility] = useState({ name: "" }); const [location, setLocation] = useState(initialLocation); @@ -46,7 +40,7 @@ function AssetFilter(props: any) { const [asset_class, setAssetClass] = useState( filter.asset_class || "" ); - const [facilityId, setFacilityId] = useState(filter.facility); + const [facilityId, setFacilityId] = useState(filter.facility); const [locationId, setLocationId] = useState(filter.location); const [warrantyExpiry, setWarrantyExpiry] = useState({ before: filter.warranty_amc_end_of_validity_before || null, @@ -54,8 +48,31 @@ function AssetFilter(props: any) { }); const [qParams, _] = useQueryParams(); + useQuery(routes.getPermittedFacility, { + pathParams: { id: facilityId }, + onResponse: ({ res, data }) => { + if (res?.status === 200 && data) { + setFacility(data); + } + }, + prefetch: !!facilityId, + }); + + useQuery(routes.getFacilityAssetLocation, { + pathParams: { + facilityId: String(facilityId), + locationId: String(locationId), + }, + onResponse: ({ res, data }) => { + if (res?.status === 200 && data) { + setLocation(data); + } + }, + prefetch: !!(facilityId && locationId), + }); + useEffect(() => { - setFacilityId(facility?.id ? facility?.id : ""); + setFacilityId(facility?.id ? `${facility?.id}` : ""); setLocationId( facility?.id === qParams.facility ? qParams.location ?? "" : "" ); @@ -75,48 +92,6 @@ function AssetFilter(props: any) { else navigate("/assets"); }, [qParams]); - const fetchFacility = useCallback( - async (status: statusType) => { - if (facilityId) { - const facilityData: any = await dispatch( - getPermittedFacility(facilityId) - ); - if (!status.aborted) { - setFacility(facilityData?.data); - } - } - }, - [filter.facility] - ); - - const fetchLocation = useCallback( - async (status: statusType) => { - if (locationId && facilityId) { - const [locationData]: any = await Promise.all([ - dispatch( - getFacilityAssetLocation(String(facilityId), String(locationId)) - ), - ]); - if (!status.aborted && locationData !== undefined) { - if (!locationData.data) - Notification.Error({ - msg: "Something went wrong..!", - }); - else { - setLocation(locationData.data); - } - } - } else { - setLocation(initialLocation); - } - }, - [filter.location] - ); - - useAbortableEffect((status: statusType) => { - filter.facility && fetchFacility(status); - filter.location && fetchLocation(status); - }, []); const applyFilter = () => { const data = { facility: facilityId, diff --git a/src/Components/Assets/AssetImportModal.tsx b/src/Components/Assets/AssetImportModal.tsx index 54ac25e2ac8..f8ab7cbc65f 100644 --- a/src/Components/Assets/AssetImportModal.tsx +++ b/src/Components/Assets/AssetImportModal.tsx @@ -5,8 +5,6 @@ import { FacilityModel } from "../Facility/models"; import { AssetData } from "./AssetTypes"; import * as Notification from "../../Utils/Notifications.js"; import { Cancel, Submit } from "../Common/components/ButtonV2"; -import { listFacilityAssetLocation } from "../../Redux/actions"; -import { useDispatch } from "react-redux"; import { Link } from "raviger"; import readXlsxFile from "read-excel-file"; import { @@ -16,6 +14,8 @@ import { import { parseCsvFile } from "../../Utils/utils"; import useConfig from "../../Common/hooks/useConfig"; import DialogModal from "../Common/Dialog"; +import useQuery from "../../Utils/request/useQuery"; +import routes from "../../Redux/api"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; interface Props { @@ -34,7 +34,6 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { location: "", }); const [locations, setLocations] = useState([]); - const dispatchAction: any = useDispatch(); const { sample_format_asset_import } = useConfig(); const [locationsLoading, setLocationsLoading] = useState(false); @@ -43,18 +42,14 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { setSelectedFile(undefined); onClose && onClose(); }; - - useEffect(() => { - setLocationsLoading(true); - dispatchAction( - listFacilityAssetLocation({}, { facility_external_id: facility.id }) - ).then(({ data }: any) => { - setLocationsLoading(false); - if (data.count > 0) { + useQuery(routes.listFacilityAssetLocation, { + pathParams: { facility_external_id: `${facility.id}` }, + onResponse: ({ res, data }) => { + if (res?.status === 200 && data) { setLocations(data.results); } - }); - }, []); + }, + }); useEffect(() => { const readFile = async () => { @@ -362,6 +357,7 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { {isImporting ? ( diff --git a/src/Components/Assets/AssetManage.tsx b/src/Components/Assets/AssetManage.tsx index e6d1836e27b..23098bff22f 100644 --- a/src/Components/Assets/AssetManage.tsx +++ b/src/Components/Assets/AssetManage.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback, useEffect, ReactElement, lazy } from "react"; +import { useState, useEffect, ReactElement, lazy } from "react"; import { AssetClass, assetClassProps, @@ -6,14 +6,6 @@ import { AssetService, AssetTransaction, } from "./AssetTypes"; -import { statusType, useAbortableEffect } from "../../Common/utils"; -import { useDispatch } from "react-redux"; -import { - deleteAsset, - getAsset, - listAssetService, - listAssetTransaction, -} from "../../Redux/actions"; import Pagination from "../Common/Pagination"; import { navigate } from "raviger"; import QRCode from "qrcode.react"; @@ -36,6 +28,9 @@ import RelativeDateUserMention from "../Common/RelativeDateUserMention"; import { AssetServiceEditModal } from "./AssetServiceEditModal"; import { warrantyAmcValidityChip } from "./AssetsList"; import Page from "../Common/components/Page"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; interface AssetManageProps { assetId: string; @@ -51,80 +46,60 @@ const checkAuthority = (type: string, cutoff: string) => { const AssetManage = (props: AssetManageProps) => { const { t } = useTranslation(); const { assetId, facilityId } = props; - const [asset, setAsset] = useState(); const [isPrintMode, setIsPrintMode] = useState(false); const [currentPage, setCurrentPage] = useState(1); const [offset, setOffset] = useState(0); const [totalCount, setTotalCount] = useState(0); - const [transactions, setTransactions] = useState([]); const [transactionDetails, setTransactionDetails] = useState< ReactElement | ReactElement[] >(); - const [services, setServices] = useState([]); const [servicesDetails, setServiceDetails] = useState< ReactElement | ReactElement[] >(); - const [isLoading, setIsLoading] = useState(false); - const dispatch = useDispatch(); const limit = 14; const authUser = useAuthUser(); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [serviceEditData, setServiceEditData] = useState< AssetService & { open: boolean; viewOnly?: boolean } >(); + const [transactionFilter, setTransactionFilter] = useState({}); - const fetchData = useCallback( - async (status: statusType) => { - setIsLoading(true); - const assetData = await dispatch(getAsset(assetId)); - if (!status.aborted) { - setIsLoading(false); - if (assetData?.data) { - setAsset(assetData.data); - - const transactionFilter = assetData.qr_code_id - ? { qr_code_id: assetData.qr_code_id } - : { external_id: assetId }; - - const [transactionsData, servicesData] = await Promise.all([ - dispatch( - listAssetTransaction({ - ...transactionFilter, - limit, - offset, - }) - ), - dispatch(listAssetService({}, assetId)), - ]); - - if (transactionsData?.data) { - setTransactions(transactionsData.data.results); - setTotalCount(transactionsData.data.count); - } else { - Notification.Error({ - msg: "Error fetching transactions", - }); - } - if (servicesData?.data) { - setServices(servicesData.data.results); - } else { - Notification.Error({ - msg: "Error fetching service logs", - }); - } - } else { - navigate("/not-found"); - } + const { data: asset, loading } = useQuery(routes.getAsset, { + pathParams: { + external_id: assetId, + }, + onResponse: ({ res, data }) => { + if (res?.status === 200 && data) { + setTransactionFilter( + data.qr_code_id + ? { qr_code_id: data.qr_code_id } + : { external_id: assetId } + ); } }, - [dispatch, assetId, offset] - ); + }); - useAbortableEffect( - (status: statusType) => { - fetchData(status); + const { data: transactions } = useQuery(routes.listAssetTransaction, { + prefetch: !!asset, + query: { + ...transactionFilter, + limit, + offset, }, - [dispatch, fetchData] + onResponse: ({ res, data }) => { + if (res?.status === 200 && data) { + setTotalCount(data.count); + } + }, + }); + + const { data: services, refetch: serviceRefetch } = useQuery( + routes.listAssetService, + { + pathParams: { + asset_external_id: assetId, + }, + } ); const handlePagination = (page: number, limit: number) => { @@ -158,7 +133,7 @@ const AssetManage = (props: AssetManageProps) => { const populateTableRows = (txns: AssetTransaction[]) => { if (txns.length > 0) { setTransactionDetails( - transactions.map((transaction: AssetTransaction) => ( + transactions?.results.map((transaction: AssetTransaction) => ( @@ -201,7 +176,7 @@ const AssetManage = (props: AssetManageProps) => { const populateServiceTableRows = (txns: AssetService[]) => { if (txns.length > 0) { setServiceDetails( - services.map((service: AssetService) => ( + services?.results.map((service: AssetService) => ( @@ -276,14 +251,14 @@ const AssetManage = (props: AssetManageProps) => { }; useEffect(() => { - populateTableRows(transactions); + if (transactions) populateTableRows(transactions.results); }, [transactions]); useEffect(() => { - populateServiceTableRows(services); + if (services) populateServiceTableRows(services?.results); }, [services]); - if (isLoading) return ; + if (loading) return ; if (isPrintMode) return ; const assetClassProp = @@ -324,13 +299,17 @@ const AssetManage = (props: AssetManageProps) => { const handleDelete = async () => { if (asset) { - const response = await dispatch(deleteAsset(asset.id)); - if (response && response.status === 204) { - Notification.Success({ - msg: "Asset deleted successfully", - }); - navigate("/assets"); - } + await request(routes.deleteAsset, { + pathParams: { + external_id: asset.id, + }, + onResponse: () => { + Notification.Success({ + msg: "Asset deleted successfully", + }); + navigate("/assets"); + }, + }); } }; @@ -595,7 +574,7 @@ const AssetManage = (props: AssetManageProps) => { handleClose={() => setServiceEditData({ ...serviceEditData, open: false }) } - handleUpdate={() => fetchData({ aborted: false })} + handleUpdate={() => serviceRefetch()} show={serviceEditData.open} viewOnly={serviceEditData.viewOnly} /> diff --git a/src/Components/Assets/AssetServiceEditModal.tsx b/src/Components/Assets/AssetServiceEditModal.tsx index 690524e471b..3964dfe0770 100644 --- a/src/Components/Assets/AssetServiceEditModal.tsx +++ b/src/Components/Assets/AssetServiceEditModal.tsx @@ -1,6 +1,4 @@ import { useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; -import { updateAssetService } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications.js"; import ButtonV2, { Cancel, Submit } from "../Common/components/ButtonV2"; import DialogModal from "../Common/Dialog"; @@ -9,6 +7,8 @@ import dayjs from "dayjs"; import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; import { formatDate, formatDateTime } from "../../Utils/utils"; import CareIcon from "../../CAREUI/icons/CareIcon"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; import DateFormField from "../Form/FormFields/DateFormField"; import { t } from "i18next"; @@ -24,23 +24,25 @@ export const AssetServiceEditModal = (props: { serviced_on: props.service_record?.serviced_on, note: props.service_record?.note, }); - const dispatchAction: any = useDispatch(); const [isLoading, setIsLoading] = useState(false); const [editRecord, setEditRecord] = useState(); const handleSubmit = async (e: any) => { e.preventDefault(); setIsLoading(true); - const data = { + const body = { serviced_on: form.serviced_on, note: form.note, }; - - const res = await dispatchAction( - updateAssetService(props.asset?.id ?? "", props.service_record.id, data) - ); + const { data } = await request(routes.updateAssetService, { + pathParams: { + asset_external_id: props.asset?.id ?? "", + external_id: props.service_record.id, + }, + body: body, + }); setIsLoading(false); - if (res?.data) { + if (data) { Notification.Success({ msg: "Asset service record updated successfully", }); diff --git a/src/Components/Assets/AssetType/HL7Monitor.tsx b/src/Components/Assets/AssetType/HL7Monitor.tsx index 6f896ac343f..b4fefbc90b2 100644 --- a/src/Components/Assets/AssetType/HL7Monitor.tsx +++ b/src/Components/Assets/AssetType/HL7Monitor.tsx @@ -1,10 +1,5 @@ import { SyntheticEvent, useEffect, useState } from "react"; import { AssetData } from "../AssetTypes"; -import { useDispatch } from "react-redux"; -import { - partialUpdateAsset, - getPermittedFacility, -} from "../../../Redux/actions"; import * as Notification from "../../../Utils/Notifications.js"; import MonitorConfigure from "../configure/MonitorConfigure"; import Loading from "../../Common/Loading"; @@ -16,6 +11,9 @@ import TextFormField from "../../Form/FormFields/TextFormField"; import HL7PatientVitalsMonitor from "../../VitalsMonitor/HL7PatientVitalsMonitor"; import VentilatorPatientVitalsMonitor from "../../VitalsMonitor/VentilatorPatientVitalsMonitor"; import useAuthUser from "../../../Common/hooks/useAuthUser"; +import request from "../../../Utils/request/request"; +import routes from "../../../Redux/api"; +import useQuery from "../../../Utils/request/useQuery"; interface HL7MonitorProps { assetId: string; @@ -33,19 +31,14 @@ const HL7Monitor = (props: HL7MonitorProps) => { const [localipAddress, setLocalIPAddress] = useState(""); const [ipadrdress_error, setIpAddress_error] = useState(""); const authUser = useAuthUser(); - const dispatch = useDispatch(); - - useEffect(() => { - const fetchFacility = async () => { - const res = await dispatch(getPermittedFacility(facilityId)); - - if (res.status === 200 && res.data) { - setFacilityMiddlewareHostname(res.data.middleware_address); + const { data: facility, loading } = useQuery(routes.getPermittedFacility, { + pathParams: { id: facilityId }, + onResponse: ({ res, data }) => { + if (res?.status === 200 && data && data.middleware_address) { + setFacilityMiddlewareHostname(data.middleware_address); } - }; - - if (facilityId) fetchFacility(); - }, [dispatch, facilityId]); + }, + }); useEffect(() => { setAssetType(asset?.asset_class); @@ -65,9 +58,10 @@ const HL7Monitor = (props: HL7MonitorProps) => { local_ip_address: localipAddress, }, }; - const res: any = await Promise.resolve( - dispatch(partialUpdateAsset(assetId, data)) - ); + const { res } = await request(routes.partialUpdateAsset, { + pathParams: { external_id: assetId }, + body: data, + }); if (res?.status === 200) { Notification.Success({ msg: "Asset Configured Successfully", @@ -85,7 +79,7 @@ const HL7Monitor = (props: HL7MonitorProps) => { const fallbackMiddleware = asset?.location_object?.middleware_address || facilityMiddlewareHostname; - if (isLoading) return ; + if (isLoading || loading || !facility) return ; return (
diff --git a/src/Components/Assets/AssetType/ONVIFCamera.tsx b/src/Components/Assets/AssetType/ONVIFCamera.tsx index cb9cc1cc497..eb6ebb9abfd 100644 --- a/src/Components/Assets/AssetType/ONVIFCamera.tsx +++ b/src/Components/Assets/AssetType/ONVIFCamera.tsx @@ -1,11 +1,5 @@ import { useEffect, useState } from "react"; import { AssetData } from "../AssetTypes"; -import { useDispatch } from "react-redux"; -import { - partialUpdateAsset, - createAssetBed, - getPermittedFacility, -} from "../../../Redux/actions"; import * as Notification from "../../../Utils/Notifications.js"; import { BedModel } from "../../Facility/models"; import axios from "axios"; @@ -17,8 +11,14 @@ import TextFormField from "../../Form/FormFields/TextFormField"; import { Submit } from "../../Common/components/ButtonV2"; import { SyntheticEvent } from "react"; import useAuthUser from "../../../Common/hooks/useAuthUser"; + +import request from "../../../Utils/request/request"; +import routes from "../../../Redux/api"; +import useQuery from "../../../Utils/request/useQuery"; + import CareIcon from "../../../CAREUI/icons/CareIcon"; + interface Props { assetId: string; facilityId: string; @@ -44,19 +44,15 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => { const [refreshPresetsHash, setRefreshPresetsHash] = useState( Number(new Date()) ); - const dispatch = useDispatch(); + const { data: facility, loading } = useQuery(routes.getPermittedFacility, { + pathParams: { id: facilityId }, + }); const authUser = useAuthUser(); useEffect(() => { - const fetchFacility = async () => { - const res = await dispatch(getPermittedFacility(facilityId)); - - if (res.status === 200 && res.data) { - setFacilityMiddlewareHostname(res.data.middleware_address); - } - }; - - if (facilityId) fetchFacility(); - }, [dispatch, facilityId]); + if (facility?.middleware_address) { + setFacilityMiddlewareHostname(facility.middleware_address); + } + }, [facility, facilityId]); useEffect(() => { if (asset) { @@ -84,9 +80,10 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => { camera_access_key: `${username}:${password}:${streamUuid}`, }, }; - const res: any = await Promise.resolve( - dispatch(partialUpdateAsset(assetId, data)) - ); + const { res } = await request(routes.partialUpdateAsset, { + pathParams: { external_id: assetId }, + body: data, + }); if (res?.status === 200) { Notification.Success({ msg: "Asset Configured Successfully" }); onUpdated?.(); @@ -111,15 +108,14 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => { const presetData = await axios.get( `https://${facilityMiddlewareHostname}/status?hostname=${config.hostname}&port=${config.port}&username=${config.username}&password=${config.password}` ); - const res: any = await Promise.resolve( - dispatch( - createAssetBed( - { meta: { ...data, ...presetData.data } }, - assetId, - bed?.id as string - ) - ) - ); + + const { res } = await request(routes.createAssetBed, { + body: { + meta: { ...data, ...presetData.data }, + asset: assetId, + bed: bed?.id as string, + }, + }); if (res?.status === 201) { Notification.Success({ msg: "Preset Added Successfully", @@ -139,12 +135,12 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => { } setLoadingAddPreset(false); }; + + if (isLoading || loading || !facility) return ; const fallbackMiddleware = asset?.location_object?.middleware_address || facilityMiddlewareHostname; - if (isLoading) return ; - return (
{["DistrictAdmin", "StateAdmin"].includes(authUser.user_type) && ( diff --git a/src/Components/Assets/AssetTypes.tsx b/src/Components/Assets/AssetTypes.tsx index 52c7e45003c..8b96b6beeb7 100644 --- a/src/Components/Assets/AssetTypes.tsx +++ b/src/Components/Assets/AssetTypes.tsx @@ -91,6 +91,8 @@ export interface AssetData { }; } +export type AssetUpdate = Partial; + export interface AssetsResponse { count: number; next?: string; @@ -137,8 +139,12 @@ export interface AssetBedModel { created_date: string; modified_date: string; meta: Record; + asset?: string; + bed?: string; } +export type AssetBedBody = Partial; + export interface AssetServiceEdit { id: string; asset_service: AssetService; @@ -166,3 +172,9 @@ export interface PatientAssetBed { patient?: PatientModel; meta?: Record; } + +export interface AssetServiceUpdate { + external_id: string; + serviced_on: string; + note: string; +} diff --git a/src/Components/Assets/AssetsList.tsx b/src/Components/Assets/AssetsList.tsx index 1c366a23e7e..4bcb2698a9a 100644 --- a/src/Components/Assets/AssetsList.tsx +++ b/src/Components/Assets/AssetsList.tsx @@ -1,15 +1,8 @@ -import { useDispatch } from "react-redux"; import QrReader from "react-qr-reader"; -import { statusType, useAbortableEffect } from "../../Common/utils"; import * as Notification from "../../Utils/Notifications.js"; -import { - getAnyFacility, - listAssets, - getFacilityAssetLocation, - getAsset, -} from "../../Redux/actions"; +import { listAssets } from "../../Redux/actions"; import { assetClassProps, AssetData } from "./AssetTypes"; -import { useState, useCallback, useEffect, lazy } from "react"; +import { useState, useEffect, lazy } from "react"; import { Link, navigate } from "raviger"; import AssetFilter from "./AssetFilter"; import { parseQueryParams } from "../../Utils/primitives"; @@ -28,6 +21,9 @@ import AssetImportModal from "./AssetImportModal"; import Page from "../Common/components/Page"; import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; import { useTranslation } from "react-i18next"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; const Loading = lazy(() => import("../Common/Loading")); @@ -52,66 +48,45 @@ const AssetsList = () => { const [status, setStatus] = useState(); const [facilityName, setFacilityName] = useState(); const [asset_class, setAssetClass] = useState(); - const [locationName, setLocationName] = useState(); const [importAssetModalOpen, setImportAssetModalOpen] = useState(false); - const dispatch: any = useDispatch(); const assetsExist = assets.length > 0 && Object.keys(assets[0]).length > 0; const [showFacilityDialog, setShowFacilityDialog] = useState(false); const [selectedFacility, setSelectedFacility] = useState({ name: "", }); + const params = { + limit: resultsPerPage, + page: qParams.page, + offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, + search_text: qParams.search || "", + facility: qParams.facility || "", + asset_type: qParams.asset_type || "", + asset_class: qParams.asset_class || "", + location: qParams.facility ? qParams.location || "" : "", + status: qParams.status || "", + }; - const fetchData = useCallback( - async (status: statusType) => { - setIsLoading(true); - const params = { - limit: resultsPerPage, - page: qParams.page, - offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, - search_text: qParams.search || "", - facility: qParams.facility || "", - asset_type: qParams.asset_type || "", - asset_class: qParams.asset_class || "", - location: qParams.facility ? qParams.location || "" : "", - status: qParams.status || "", - warranty_amc_end_of_validity_before: - qParams.warranty_amc_end_of_validity_before || "", - warranty_amc_end_of_validity_after: - qParams.warranty_amc_end_of_validity_after || "", - }; - const { data } = await dispatch(listAssets(params)); - if (!status.aborted) { - setIsLoading(false); - if (!data) - Notification.Error({ - msg: "Something went wrong..!", - }); - else { - setAssets(data.results); - setTotalCount(data.count); - if (qParams.facility) { - const fetchFacility = await dispatch( - getAnyFacility(qParams.facility) - ); - setSelectedFacility(fetchFacility.data as FacilityModel); - } - } + useQuery(routes.listAssets, { + query: params, + onResponse: ({ res, data }) => { + if (res?.status === 200 && data) { + setAssets(data.results); + setTotalCount(data.count); } }, - [ - resultsPerPage, - qParams.page, - qParams.search, - qParams.facility, - qParams.asset_type, - qParams.asset_class, - qParams.location, - qParams.status, - qParams.warranty_amc_end_of_validity_before, - qParams.warranty_amc_end_of_validity_after, - dispatch, - ] - ); + }); + + useQuery(routes.getAnyFacility, { + pathParams: { id: qParams.facility }, + onResponse: ({ res, data }) => { + if (res?.status === 200 && data) { + setFacility(data); + setSelectedFacility(data); + setFacilityName(data.name); + } + }, + prefetch: !!qParams.facility, + }); useEffect(() => { setAssetType(qParams.asset_type); @@ -125,56 +100,13 @@ const AssetsList = () => { setAssetClass(qParams.asset_class); }, [qParams.asset_class]); - useAbortableEffect( - (status: statusType) => { - fetchData(status); + const { data: location } = useQuery(routes.getFacilityAssetLocation, { + pathParams: { + facility_external_id: String(qParams.facility), + external_id: String(qParams.location), }, - [dispatch, fetchData] - ); - useEffect(() => { - async function fetchFacilityName() { - if (!qParams.facility) return setFacilityName(""); - const res = await dispatch(getAnyFacility(qParams.facility, "facility")); - setFacilityName(res?.data?.name); - } - fetchFacilityName(); - }, [dispatch, qParams.facility]); - - const fetchFacility = useCallback( - async (status: statusType) => { - if (!qParams.facility) return setFacility(undefined); - setIsLoading(true); - const res = await dispatch(getAnyFacility(qParams.facility)); - if (!status.aborted) { - setFacility(res?.data); - setIsLoading(false); - } - }, - [dispatch, qParams.facility] - ); - const fetchLocationName = useCallback( - async (status: statusType) => { - if (!qParams.location || !qParams.facility) - return setLocationName(undefined); - setIsLoading(true); - const res = await dispatch( - getFacilityAssetLocation(qParams.facility, qParams.location) - ); - if (!status.aborted) { - setLocationName(res?.data?.name); - setIsLoading(false); - } - }, - [dispatch, qParams.facility, qParams.location] - ); - - useAbortableEffect( - (status: statusType) => { - fetchFacility(status); - fetchLocationName(status); - }, - [fetchFacility, fetchLocationName] - ); + prefetch: !!(qParams.facility && qParams.location), + }); const getAssetIdFromQR = async (assetUrl: string) => { try { @@ -184,8 +116,10 @@ const AssetsList = () => { // QR Maybe searchParams "asset" or "assetQR" const assetId = params.asset || params.assetQR; if (assetId) { - const { data } = await dispatch(listAssets({ qr_code_id: assetId })); - return data.results[0].id; + const { data } = await request(routes.listAssets, { + query: { qr_code_id: assetId }, + }); + return data?.results[0].id; } } catch (err) { console.log(err); @@ -193,11 +127,13 @@ const AssetsList = () => { }; const checkValidAssetId = async (assetId: string) => { - const assetData = await dispatch(getAsset(assetId)); + const { data: assetData } = await request(routes.getAsset, { + pathParams: { id: assetId }, + }); try { - if (assetData.data) { + if (assetData) { navigate( - `/facility/${assetData.data.location_object.facility.id}/assets/${assetId}` + `/facility/${assetData.location_object.facility.id}/assets/${assetId}` ); } } catch (err) { @@ -434,7 +370,7 @@ const AssetsList = () => { value("Asset Type", "asset_type", asset_type ?? ""), value("Asset Class", "asset_class", asset_class ?? ""), value("Status", "status", status?.replace(/_/g, " ") ?? ""), - value("Location", "location", locationName ?? ""), + value("Location", "location", location?.name ?? ""), value( "Warranty AMC End Of Validity Before", "warranty_amc_end_of_validity_before", diff --git a/src/Components/Assets/configure/MonitorConfigure.tsx b/src/Components/Assets/configure/MonitorConfigure.tsx index 1d469335ce2..d1b24b24959 100644 --- a/src/Components/Assets/configure/MonitorConfigure.tsx +++ b/src/Components/Assets/configure/MonitorConfigure.tsx @@ -1,62 +1,50 @@ -import { Dispatch, useEffect, useState } from "react"; +import { useState } from "react"; import { BedSelect } from "../../Common/BedSelect"; import { BedModel } from "../../Facility/models"; import { AssetData } from "../AssetTypes"; -import { - createAssetBed, - listAssetBeds, - partialUpdateAssetBed, -} from "../../../Redux/actions"; import * as Notification from "../../../Utils/Notifications.js"; -import { useDispatch } from "react-redux"; import { Submit } from "../../Common/components/ButtonV2"; import { FieldLabel } from "../../Form/FormFields/FormField"; +import request from "../../../Utils/request/request"; +import routes from "../../../Redux/api"; +import useQuery from "../../../Utils/request/useQuery"; -const saveLink = (assetId: string, bedId: string, dispatch: Dispatch) => { - dispatch(createAssetBed({}, assetId, bedId)); +const saveLink = async (assetId: string, bedId: string) => { + await request(routes.createAssetBed, { + body: { + asset: assetId, + bed: bedId, + }, + }); Notification.Success({ msg: "AssetBed Link created successfully" }); }; -const update_Link = ( +const update_Link = async ( assetbedId: string, assetId: string, - bed: BedModel, - assetBed: any, - dispatch: Dispatch + bed: BedModel ) => { - dispatch( - partialUpdateAssetBed( - { - asset: assetId, - bed: bed.id, - }, - assetbedId - ) - ); + await request(routes.partialUpdateAssetBed, { + pathParams: { external_id: assetbedId }, + body: { + asset: assetId, + bed: bed.id ?? "", + }, + }); Notification.Success({ msg: "AssetBed Link updated successfully" }); }; export default function MonitorConfigure({ asset }: { asset: AssetData }) { const [bed, setBed] = useState({}); const [updateLink, setUpdateLink] = useState(false); - const [assetBed, setAssetBed] = useState(); - const dispatch: any = useDispatch(); - - const getAssetBeds = async (id: string) => { - const assetBeds = await dispatch(listAssetBeds({ asset: id })); - if (assetBeds.data?.results?.length > 0) { - setUpdateLink(true); - setAssetBed(assetBeds.data.results[0]); - setBed(assetBeds.data.results[0].bed_object); - } else { - setUpdateLink(false); - } - }; - - useEffect(() => { - if (asset.id) { - getAssetBeds(asset.id); - } - }, [asset]); + const { data: assetBed } = useQuery(routes.listAssetBeds, { + query: { asset: asset.id }, + onResponse: ({ res, data }) => { + if (res?.status === 200 && data && data.results.length > 0) { + setBed(data.results[0].bed_object); + setUpdateLink(true); + } + }, + }); return ( diff --git a/src/Redux/actions.tsx b/src/Redux/actions.tsx index 06a12796737..7a92b1a7658 100644 --- a/src/Redux/actions.tsx +++ b/src/Redux/actions.tsx @@ -834,47 +834,15 @@ export const listAssets = (params: object) => fireRequest("listAssets", [], params); export const createAsset = (params: object) => fireRequest("createAsset", [], params); -export const getAssetUserLocation = (params: object) => - fireRequest("getAssetUserLocation", [], params); -export const createAssetUserLocation = (params: object) => - fireRequest("createAssetUserLocation", [], params); export const getAsset = (id: string) => fireRequest("getAsset", [], {}, { external_id: id }); export const deleteAsset = (id: string) => fireRequest("deleteAsset", [], {}, { external_id: id }); export const updateAsset = (id: string, params: object) => fireRequest("updateAsset", [], params, { external_id: id }); -export const partialUpdateAsset = (id: string, params: object) => - fireRequest("partialUpdateAsset", [], params, { external_id: id }); export const operateAsset = (id: string, params: object) => fireRequest("operateAsset", [], params, { external_id: id }); -export const listAssetTransaction = (params: object) => - fireRequest("listAssetTransaction", [], params); -export const getAssetTransaction = (id: string) => - fireRequest("getAssetTransaction", [], {}, { id }); - -export const listAssetService = (params: object, asset_external_id: string) => - fireRequest("listAssetService", [], params, { asset_external_id }); -export const getAssetService = ( - params: object, - asset_external_id: string, - external_id: string -) => - fireRequest("getAssetService", [], params, { - asset_external_id, - external_id, - }); -export const updateAssetService = ( - asset_external_id: string, - external_id: string, - params: object -) => - fireRequest("updateAssetService", [], params, { - asset_external_id, - external_id, - }); - // ABDM related export const generateAadhaarOtp = (aadhaar: string) => fireRequest("generateAadhaarOtp", [], { aadhaar }); diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 02c38afcc5a..a05d461c98d 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -1,5 +1,17 @@ import { IConfig } from "../Common/hooks/useConfig"; -import { AssetData } from "../Components/Assets/AssetTypes"; + +import { + AssetBedBody, + AssetBedModel, + AssetData, + AssetLocationObject, + AssetService, + AssetServiceUpdate, + AssetTransaction, + AssetUpdate, +} from "../Components/Assets/AssetTypes"; +import { FacilityModel, LocationModel, WardModel } from "../Components/Facility/models"; + import { IDeleteExternalResult, IExternalResult, @@ -8,7 +20,6 @@ import { ILocalBodyByDistrict, IPartialUpdateExternalResult, } from "../Components/ExternalResult/models"; -import { LocationModel, WardModel } from "../Components/Facility/models"; import { Prescription } from "../Components/Medicine/models"; import { UserModel } from "../Components/Users/models"; import { PaginatedResponse } from "../Utils/request/types"; @@ -197,10 +208,14 @@ const routes = { getPermittedFacility: { path: "/api/v1/facility/{id}/", + method: "GET", + TRes: Type(), }, getAnyFacility: { path: "/api/v1/getallfacilities/{id}/", + method: "GET", + TRes: Type(), }, updateFacility: { @@ -234,6 +249,7 @@ const routes = { getFacilityAssetLocation: { path: "/api/v1/facility/{facility_external_id}/asset_location/{external_id}/", method: "GET", + TRes: Type(), }, updateFacilityAssetLocation: { path: "/api/v1/facility/{facility_external_id}/asset_location/{external_id}/", @@ -248,10 +264,13 @@ const routes = { listAssetBeds: { path: "/api/v1/assetbed/", method: "GET", + TRes: Type>(), }, createAssetBed: { path: "/api/v1/assetbed/", method: "POST", + TRes: Type(), + TBody: Type(), }, getAssetBed: { path: "/api/v1/assetbed/{external_id}/", @@ -264,6 +283,8 @@ const routes = { partialUpdateAssetBed: { path: "/api/v1/assetbed/{external_id}/", method: "PATCH", + TRes: Type(), + TBody: Type(), }, deleteAssetBed: { path: "/api/v1/assetbed/{external_id}/", @@ -830,6 +851,7 @@ const routes = { listAssets: { path: "/api/v1/asset", method: "GET", + TRes: Type>(), }, createAsset: { path: "/api/v1/asset/", @@ -851,6 +873,7 @@ const routes = { deleteAsset: { path: "/api/v1/asset/{external_id}/", method: "DELETE", + TRes: Type(), }, updateAsset: { path: "/api/v1/asset/{external_id}/", @@ -859,6 +882,8 @@ const routes = { partialUpdateAsset: { path: "/api/v1/asset/{external_id}/", method: "PATCH", + TRes: Type(), + TBody: Type(), }, // Asset transaction endpoints @@ -866,6 +891,7 @@ const routes = { listAssetTransaction: { path: "/api/v1/asset_transaction/", method: "GET", + TRes: Type>(), }, getAssetTransaction: { path: "/api/v1/asset_transaction/{id}", @@ -877,6 +903,7 @@ const routes = { listAssetService: { path: "/api/v1/asset/{asset_external_id}/service_records/", method: "GET", + TRes: Type>(), }, getAssetService: { path: "/api/v1/asset/{asset_external_id}/service_records/{external_id}", @@ -885,6 +912,8 @@ const routes = { updateAssetService: { path: "/api/v1/asset/{asset_external_id}/service_records/{external_id}", method: "PUT", + TRes: Type(), + TBody: Type(), }, // ABDM HealthID endpoints From 67f51890d9921e73873584513463f5e72c640ded Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Wed, 18 Oct 2023 15:03:49 +0530 Subject: [PATCH 39/65] Fix CSS unintentended RTL style (#6465) --- .../Facility/Consultations/NeurologicalTables.tsx | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Components/Facility/Consultations/NeurologicalTables.tsx b/src/Components/Facility/Consultations/NeurologicalTables.tsx index 89e9d598604..d33c7d6a311 100644 --- a/src/Components/Facility/Consultations/NeurologicalTables.tsx +++ b/src/Components/Facility/Consultations/NeurologicalTables.tsx @@ -28,10 +28,7 @@ const DataTable = (props: any) => { Right
-
+
{data.map((x: any, i: any) => { return (
{
Level Of Consciousness
-
+
{locData.map((x: any, i: any) => (
{ Total
-
+
{glasgowData.map((x: any, i: any) => { return (
Date: Wed, 18 Oct 2023 15:59:28 +0530 Subject: [PATCH 40/65] Replaced useDispatch with useQuery and request. (#6344) * Replaced useDispatch with useQuery and request. * Removed all relevant no longer used actions from actions.tsx * Replaced Res to Type * Added TBody. * used abha in place of JSON.parse(value). * Fixed mobile otp issue * Replaced Rdata with Rerror * Fixed link care context --------- Co-authored-by: Khavin Shankar --- src/Components/ABDM/ABHAProfileModal.tsx | 19 +- .../ABDM/ConfigureHealthFacility.tsx | 98 ++++--- src/Components/ABDM/LinkABHANumberModal.tsx | 187 ++++++++----- src/Components/ABDM/LinkCareContextModal.tsx | 19 +- src/Components/ABDM/models.ts | 81 ++++++ .../Notifications/NotificationsList.tsx | 20 +- src/Redux/actions.tsx | 80 ------ src/Redux/api.tsx | 264 +++++++++++------- src/Utils/request/request.ts | 1 + 9 files changed, 445 insertions(+), 324 deletions(-) diff --git a/src/Components/ABDM/ABHAProfileModal.tsx b/src/Components/ABDM/ABHAProfileModal.tsx index 0d6b7d9b90b..cc16fd45b8d 100644 --- a/src/Components/ABDM/ABHAProfileModal.tsx +++ b/src/Components/ABDM/ABHAProfileModal.tsx @@ -5,9 +5,9 @@ import CareIcon from "../../CAREUI/icons/CareIcon"; import DialogModal from "../Common/Dialog"; import QRCode from "qrcode.react"; import { formatDateTime } from "../../Utils/utils"; -import { getAbhaCard } from "../../Redux/actions"; -import { useDispatch } from "react-redux"; import { useRef } from "react"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; interface IProps { patientId?: string; @@ -18,21 +18,24 @@ interface IProps { const ABHAProfileModal = ({ patientId, show, onClose, abha }: IProps) => { const printRef = useRef(null); - const dispatch = useDispatch(); const downloadAbhaCard = async (type: "pdf" | "png") => { if (!patientId) return; - const response = await dispatch(getAbhaCard(patientId, type)); + const { res, data } = await request(routes.abha.getAbhaCard, { + body: { + patient: patientId, + type: type, + }, + }); - if (response.status === 200 && response.data) { + if (res?.status === 200 && data) { if (type === "png") { const downloadLink = document.createElement("a"); - downloadLink.href = - "data:application/octet-stream;base64," + response.data; + downloadLink.href = "data:application/octet-stream;base64," + data; downloadLink.download = "abha.png"; downloadLink.click(); } else { - const htmlPopup = ``; + const htmlPopup = ``; const printWindow = window.open("", "PDF"); printWindow?.document.write(htmlPopup); diff --git a/src/Components/ABDM/ConfigureHealthFacility.tsx b/src/Components/ABDM/ConfigureHealthFacility.tsx index 025a3f0e862..a2b8f254bcb 100644 --- a/src/Components/ABDM/ConfigureHealthFacility.tsx +++ b/src/Components/ABDM/ConfigureHealthFacility.tsx @@ -1,12 +1,12 @@ -import { lazy, useCallback, useEffect, useReducer, useState } from "react"; -import { useDispatch } from "react-redux"; - -import { healthFacilityActions } from "../../Redux/actions"; +import { lazy, useEffect, useReducer, useState } from "react"; import * as Notification from "../../Utils/Notifications.js"; import { navigate } from "raviger"; import { Cancel, Submit } from "../Common/components/ButtonV2"; import TextFormField from "../Form/FormFields/TextFormField"; import { classNames } from "../../Utils/utils"; +import useQuery from "../../Utils/request/useQuery"; +import routes from "../../Redux/api"; +import request from "../../Utils/request/request"; const Loading = lazy(() => import("../Common/Loading")); const initForm = { @@ -40,30 +40,28 @@ const FormReducer = (state = initialState, action: any) => { export const ConfigureHealthFacility = (props: any) => { const [state, dispatch] = useReducer(FormReducer, initialState); const { facilityId } = props; - const dispatchAction: any = useDispatch(); const [isLoading, setIsLoading] = useState(false); - const fetchData = useCallback(async () => { - if (facilityId) { - setIsLoading(true); - const res = await dispatchAction(healthFacilityActions.read(facilityId)); - - if (res?.status === 200 && res?.data) { - const formData = { - ...state.form, - hf_id: res.data.hf_id, - health_facility: res.data, - }; - dispatch({ type: "set_form", form: formData }); - } + const { + data: health_facility, + loading, + refetch, + } = useQuery(routes.abha.getHealthFacility, { + pathParams: { facility__external_id: facilityId }, + }); - setIsLoading(false); - } - }, [dispatchAction, facilityId]); + useEffect(() => { + const formData = { + ...state.form, + hf_id: health_facility?.hf_id, + health_facility: health_facility, + }; + dispatch({ type: "set_form", form: formData }); + }, [health_facility]); useEffect(() => { - fetchData(); - }, [dispatch, fetchData]); + refetch(); + }, [dispatch, refetch]); const handleSubmit = async (e: any) => { e.preventDefault(); @@ -78,27 +76,43 @@ export const ConfigureHealthFacility = (props: any) => { return; } - let res = null; + let response = null; + let responseData = null; if (state.form.health_facility) { - res = await dispatchAction( - healthFacilityActions.partialUpdate(facilityId, { - hf_id: state.form.hf_id, - }) + const { res, data } = await request( + routes.abha.partialUpdateHealthFacility, + { + pathParams: { + facility__external_id: facilityId, + }, + body: { + hf_id: state.form.hf_id, + }, + } ); + response = res; + responseData = data; } else if (state.form.hf_id === state.form.health_facility?.hf_id) { - res = await dispatchAction( - healthFacilityActions.registerService(facilityId) + const { res, data } = await request( + routes.abha.registerHealthFacilityAsService, + { + pathParams: { + facility__external_id: facilityId, + }, + } ); + response = res; + responseData = data; - if (res?.status === 200 && res?.data) { - if (res.data?.registered) { + if (response?.status === 200 && responseData) { + if (responseData?.registered) { dispatch({ type: "set_form", form: { ...state.form, health_facility: { ...state.form?.health_facility, - registered: res.data.registered, + registered: responseData.registered, }, }, }); @@ -112,24 +126,26 @@ export const ConfigureHealthFacility = (props: any) => { }); return; } else { - res = await dispatchAction( - healthFacilityActions.create({ + const { res, data } = await request(routes.abha.createHealthFacility, { + body: { facility: facilityId, hf_id: state.form.hf_id, - }) - ); + }, + }); + response = res; + responseData = data; } setIsLoading(false); - if (res && res.data) { + if (response && responseData) { Notification.Success({ msg: "Health Facility config updated successfully", }); navigate(`/facility/${facilityId}`); } else { - if (res?.data) + if (responseData) Notification.Error({ - msg: "Something went wrong: " + (res.data.detail || ""), + msg: "Something went wrong: " + (responseData.detail || ""), }); } setIsLoading(false); @@ -142,7 +158,7 @@ export const ConfigureHealthFacility = (props: any) => { }); }; - if (isLoading) { + if (loading || isLoading) { return ; } diff --git a/src/Components/ABDM/LinkABHANumberModal.tsx b/src/Components/ABDM/LinkABHANumberModal.tsx index 30b1ff4afa3..367c9815db1 100644 --- a/src/Components/ABDM/LinkABHANumberModal.tsx +++ b/src/Components/ABDM/LinkABHANumberModal.tsx @@ -1,19 +1,6 @@ import * as Notify from "../../Utils/Notifications"; import Dropdown, { DropdownItem } from "../Common/components/Menu"; -import { - checkAndGenerateMobileOtp, - confirmWithAadhaarOtp, - confirmWithMobileOtp, - createHealthId, - generateAadhaarOtp, - initiateAbdmAuthentication, - linkViaQR, - resentAadhaarOtp, - searchByHealthId, - verifyAadhaarOtp, - verifyMobileOtp, -} from "../../Redux/actions"; import { useEffect, useState } from "react"; import ButtonV2 from "../Common/components/ButtonV2"; @@ -24,7 +11,8 @@ import OtpFormField from "../Form/FormFields/OtpFormField"; import QRScanner from "../Common/QRScanner"; import TextFormField from "../Form/FormFields/TextFormField"; import { classNames } from "../../Utils/utils"; -import { useDispatch } from "react-redux"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; export const validateRule = ( condition: boolean, @@ -144,8 +132,6 @@ const ScanABHAQRSection = ({ onSuccess, closeModal, }: ScanABHAQRSectionProps) => { - const dispatch = useDispatch(); - const [qrValue, setQrValue] = useState(""); const [authMethods, setAuthMethods] = useState([]); const [selectedAuthMethod, setSelectedAuthMethod] = useState(""); @@ -187,23 +173,23 @@ const ScanABHAQRSection = ({ try { const abha = JSON.parse(value); - const res = await dispatch(linkViaQR(abha, patientId)); + const { res, data } = await request(routes.abha.linkViaQR, { + body: { ...abha, patientId }, + }); if (res?.status === 200 || res?.status === 202) { Notify.Success({ msg: "Request sent successfully" }); onSuccess?.({ - ...res.data, + ...data, abha_profile: { - ...res.data?.abha_profile, - healthIdNumber: res.data?.abha_profile?.abha_number, - healthId: res.data?.abha_profile?.health_id, + ...data?.abha_profile, + healthIdNumber: data?.abha_profile?.abha_number, + healthId: data?.abha_profile?.health_id, mobile: abha?.mobile, monthOfBirth: - res.data?.abha_profile?.date_of_birth?.split("-")[1], - dayOfBirth: - res.data?.abha_profile?.date_of_birth?.split("-")[2], - yearOfBirth: - res.data?.abha_profile?.date_of_birth?.split("-")[0], + data?.abha_profile?.date_of_birth?.split("-")[1], + dayOfBirth: data?.abha_profile?.date_of_birth?.split("-")[2], + yearOfBirth: data?.abha_profile?.date_of_birth?.split("-")[0], }, }); } else { @@ -256,29 +242,55 @@ const ScanABHAQRSection = ({ disabled={otp.length !== 6} onClick={async () => { let response = null; + let Rdata = null; + let Rerror = null; switch (selectedAuthMethod) { case "MOBILE_OTP": - response = await dispatch( - confirmWithMobileOtp(txnId, otp, patientId) - ); + { + const { res, data, error } = await request( + routes.abha.confirmWithMobileOtp, + { + body: { + otp: otp, + txnId: txnId, + patientId: patientId, + }, + } + ); + response = res; + Rdata = data; + Rerror = error; + } break; case "AADHAAR_OTP": - response = await dispatch( - confirmWithAadhaarOtp(txnId, otp, patientId) - ); + { + const { res, data, error } = await request( + routes.abha.confirmWithAadhaarOtp, + { + body: { + otp: otp, + txnId: txnId, + patientId: patientId, + }, + } + ); + response = res; + Rdata = data; + Rerror = error; + } break; } - if (response.status === 200) { - onSuccess?.(response.data); + if (response?.status === 200) { + onSuccess?.(Rdata); Notify.Success({ msg: "ABHA Number linked successfully", }); } else { Notify.Error({ - msg: response?.message ?? "Something went wrong!", + msg: Rerror ?? "Something went wrong!", }); } }} @@ -290,13 +302,14 @@ const ScanABHAQRSection = ({ {authMethods.map((method) => ( { - const response = await dispatch( - initiateAbdmAuthentication(method, qrValue) + const { res, data } = await request( + routes.abha.initiateAbdmAuthentication, + { body: { authMethod: method, healthid: qrValue } } ); - if (response.status === 200 && response?.data?.txnId) { + if (res?.status === 200 && data?.txnId) { setSelectedAuthMethod(method); - setTxnId(response.data.txnId); + setTxnId(data.txnId); } }} > @@ -308,11 +321,18 @@ const ScanABHAQRSection = ({ { - const response = await dispatch(searchByHealthId(qrValue)); - - if (response.status === 200 && response?.data?.authMethods) { + const { res, data } = await request( + routes.abha.searchByHealthId, + { + body: { + healthId: qrValue, + }, + } + ); + + if (res?.status === 200 && data?.authMethods) { setAuthMethods( - response.data.authMethods?.filter?.((method: string) => + data.authMethods?.filter?.((method: string) => supportedAuthMethods.find( (supported) => supported === method ) @@ -339,8 +359,6 @@ const VerifyAadhaarSection = ({ onVerified, onSignin, }: VerifyAadhaarSectionProps) => { - const dispatch = useDispatch(); - const [aadhaarNumber, setAadhaarNumber] = useState(""); const [aadhaarNumberError, setAadhaarNumberError] = useState(); @@ -382,17 +400,22 @@ const VerifyAadhaarSection = ({ if (!validateAadhaar()) return; setIsSendingOtp(true); - const res = await dispatch(generateAadhaarOtp(aadhaarNumber)); + + const { res, data } = await request(routes.abha.generateAadhaarOtp, { + body: { + aadhaar: aadhaarNumber, + }, + }); setIsSendingOtp(false); - if (res.status === 200 && res.data) { - const { txnId } = res.data; + if (res?.status === 200 && data) { + const { txnId } = data; setTxnId(txnId); Notify.Success({ msg: "OTP has been sent to the mobile number registered with the Aadhar number.", }); } else { - Notify.Error({ msg: JSON.stringify(res.data) }); + Notify.Error({ msg: JSON.stringify(data) }); } }; @@ -400,16 +423,20 @@ const VerifyAadhaarSection = ({ if (!validateAadhaar() || !txnId) return; setIsSendingOtp(true); - const res = await dispatch(resentAadhaarOtp(txnId)); + const { res, data } = await request(routes.abha.generateAadhaarOtp, { + body: { + txnId: txnId, + }, + }); setIsSendingOtp(false); - if (res.status === 200 && res.data.txnId) { - setTxnId(res.data.txnId); + if (res?.status === 200 && data?.txnId) { + setTxnId(data.txnId); Notify.Success({ msg: "OTP has been resent to the mobile number registered with the Aadhar number.", }); } else { - Notify.Error({ msg: JSON.stringify(res.data) }); + Notify.Error({ msg: JSON.stringify(data) }); } }; @@ -430,11 +457,16 @@ const VerifyAadhaarSection = ({ if (!validateOtp() || !txnId) return; setIsVerifyingOtp(true); - const res = await dispatch(verifyAadhaarOtp(txnId, otp)); + const { res, data } = await request(routes.abha.verifyAadhaarOtp, { + body: { + otp: otp, + txnId: txnId, + }, + }); setIsVerifyingOtp(false); - if (res.status === 200 && res.data.txnId) { - setTxnId(res.data.txnId); + if (res?.status === 200 && data?.txnId) { + setTxnId(data.txnId); Notify.Success({ msg: "OTP verified" }); setIsVerified(true); } else { @@ -563,8 +595,6 @@ const VerifyMobileSection = ({ onVerified, patientMobile, }: VerifyMobileSectionProps) => { - const dispatch = useDispatch(); - const [mobile, setMobile] = useState(() => patientMobile || ""); const [mobileError, setMobileError] = useState(); @@ -602,11 +632,16 @@ const VerifyMobileSection = ({ setOtpDispatched(false); setIsSendingOtp(true); - const res = await dispatch(checkAndGenerateMobileOtp(txnId, mobile)); + const { res, data } = await request(routes.abha.checkAndGenerateMobileOtp, { + body: { + mobile: mobile, + txnId: txnId, + }, + }); setIsSendingOtp(false); - if (res.status === 200 && res.data) { - const { txnId, mobileLinked } = res.data; + if (res?.status === 200 && data) { + const { txnId, mobileLinked } = data; setTxnId(txnId); if (mobileLinked) { @@ -621,7 +656,7 @@ const VerifyMobileSection = ({ }); } } else { - Notify.Error({ msg: JSON.stringify(res.data) }); + Notify.Error({ msg: JSON.stringify(data) }); } }; @@ -642,11 +677,16 @@ const VerifyMobileSection = ({ if (!validateOtp()) return; setIsVerifyingOtp(true); - const res = await dispatch(verifyMobileOtp(txnId, otp)); + const { res, data } = await request(routes.abha.verifyMobileOtp, { + body: { + txnId: txnId, + otp: otp, + }, + }); setIsVerifyingOtp(false); - if (res.status === 200 && res.data.txnId) { - setTxnId(res.data.txnId); + if (res?.status === 200 && data?.txnId) { + setTxnId(data.txnId); Notify.Success({ msg: "OTP verified" }); setIsVerified(true); } else { @@ -718,21 +758,24 @@ const CreateHealthIDSection = ({ onCreateSuccess, patientId, }: CreateHealthIDSectionProps) => { - const dispatch = useDispatch(); const [healthId, setHealthId] = useState(""); const [isCreating, setIsCreating] = useState(false); const [isHealthIdInputInFocus, setIsHealthIdInputInFocus] = useState(false); const handleCreateHealthId = async () => { setIsCreating(true); - const res = await dispatch( - createHealthId({ txnId: transactionId, patientId, healthId }) - ); - if (res.status === 200) { + const { res, data } = await request(routes.abha.createHealthId, { + body: { + healthId: healthId, + txnId: transactionId, + patientId: patientId, + }, + }); + if (res?.status === 200) { Notify.Success({ msg: "Abha Address created" }); - onCreateSuccess(res.data); + onCreateSuccess(data); } else { - Notify.Error({ msg: JSON.stringify(res.data) }); + Notify.Error({ msg: JSON.stringify(data) }); } setIsCreating(false); }; diff --git a/src/Components/ABDM/LinkCareContextModal.tsx b/src/Components/ABDM/LinkCareContextModal.tsx index fc0b488954d..36749ff3d23 100644 --- a/src/Components/ABDM/LinkCareContextModal.tsx +++ b/src/Components/ABDM/LinkCareContextModal.tsx @@ -5,9 +5,9 @@ import DateFormField from "../Form/FormFields/DateFormField"; import DialogModal from "../Common/Dialog"; import { PatientModel } from "../Patient/models"; import TextFormField from "../Form/FormFields/TextFormField"; -import { linkCareContext } from "../../Redux/actions"; -import { useDispatch } from "react-redux"; import { useState } from "react"; +import routes from "../../Redux/api.js"; +import request from "../../Utils/request/request.js"; interface IProps { consultationId: string; @@ -25,8 +25,6 @@ const LinkCareContextModal = ({ const [acceptedDisclaimer, setAcceptedDisclaimer] = useState(false); const [isLinkingCareContext, setIsLinkingCareContext] = useState(false); - const dispatch = useDispatch(); - return ( { setIsLinkingCareContext(true); - const res = await dispatch( - linkCareContext(consultationId, { + const { res } = await request(routes.abha.linkCareContext, { + body: { + consultation: consultationId, name: patient?.abha_number_object?.name, gender: patient?.abha_number_object?.gender, dob: patient?.abha_number_object?.date_of_birth, - }) - ); - - if (res.status === 202) { + }, + reattempts: 0, + }); + if (res?.status === 202) { Notification.Success({ msg: "Care Context sucessfully linked!", }); diff --git a/src/Components/ABDM/models.ts b/src/Components/ABDM/models.ts index b01579fba88..f992cc9df4f 100644 --- a/src/Components/ABDM/models.ts +++ b/src/Components/ABDM/models.ts @@ -17,3 +17,84 @@ export interface ICreateHealthIdResponse { healthId?: string; healthIdNumber: string; } + +export interface IHealthFacility { + id: string; + registered: boolean; + external_id: string; + created_date: string; + modified_date: string; + hf_id: string; + facility: string; + detail?: string; +} + +export interface ILinkABHANumber { + abha_profile: { + abha_number: string; + health_id: string; + date_of_birth: string; + }; +} + +export interface IConfirmMobileOtp { + otp: string; + txnId: string; + patientId?: string; + message?: string; +} + +export interface IHealthId { + authMethods?: string[]; +} + +export interface IAadhaarOtp { + txnId: string; +} + +export interface ICheckAndGenerateMobileOtp { + mobileLinked: boolean; + txnId: string; +} + +export interface IAadhaarOtpTBody { + aadhaar?: string; + txnId?: string; +} + +export interface IVerifyAadhaarOtpTBody { + consultation?: string; + name?: string; + gender?: "M" | "F" | "O"; + dob?: string; + otp?: string; + txnId?: string; +} + +export interface IGenerateMobileOtpTBody { + mobile: string; + txnId: string; +} + +export interface ISearchByHealthIdTBody { + healthId: string; +} + +export interface IinitiateAbdmAuthenticationTBody { + authMethod: string; + healthid: string; +} + +export interface IgetAbhaCardTBody { + patient: string; + type: "pdf" | "png"; +} + +export interface IcreateHealthFacilityTBody { + facility: string; + hf_id: string; +} + +export interface IpartialUpdateHealthFacilityTBody { + hf_id: string; +} diff --git a/src/Components/Notifications/NotificationsList.tsx b/src/Components/Notifications/NotificationsList.tsx index 5e3aa65b3ec..5f124516a14 100644 --- a/src/Components/Notifications/NotificationsList.tsx +++ b/src/Components/Notifications/NotificationsList.tsx @@ -353,15 +353,17 @@ export default function NotificationsList({ manageResults = ( <> {data - .filter((notification: any) => showUnread ? notification.read_at === null : true) - .map((result: any) => ( - - ))} + .filter((notification: any) => + showUnread ? notification.read_at === null : true + ) + .map((result: any) => ( + + ))} {isLoading && (
diff --git a/src/Redux/actions.tsx b/src/Redux/actions.tsx index 7a92b1a7658..1d2d6f4f7b6 100644 --- a/src/Redux/actions.tsx +++ b/src/Redux/actions.tsx @@ -6,8 +6,6 @@ import { } from "../Components/Medicine/models"; import { fireRequest, fireRequestForFiles } from "./fireRequest"; -import { ICreateHealthIdRequest } from "../Components/ABDM/models"; - export const getConfig = () => { return fireRequestForFiles("config"); }; @@ -844,54 +842,13 @@ export const operateAsset = (id: string, params: object) => fireRequest("operateAsset", [], params, { external_id: id }); // ABDM related -export const generateAadhaarOtp = (aadhaar: string) => - fireRequest("generateAadhaarOtp", [], { aadhaar }); export const resentAadhaarOtp = (txnId: string) => fireRequest("resendAadhaarOtp", [], { txnId }); -export const verifyAadhaarOtp = (txnId: string, otp: string) => - fireRequest("verifyAadhaarOtp", [], { txnId, otp }); - export const generateMobileOtp = (txnId: string, mobile: string) => fireRequest("generateMobileOtp", [], { txnId, mobile }); -export const checkAndGenerateMobileOtp = (txnId: string, mobile: string) => - fireRequest("checkAndGenerateMobileOtp", [], { txnId, mobile }); - -export const verifyMobileOtp = (txnId: string, otp: string) => - fireRequest("verifyMobileOtp", [], { txnId, otp }); - -export const createHealthId = (data: ICreateHealthIdRequest) => - fireRequest("createHealthId", [], data); - -export const searchByHealthId = (healthId: string) => - fireRequest("searchByHealthId", [], { healthId }); - -export const initiateAbdmAuthentication = ( - authMethod: string, - healthid: string -) => fireRequest("initiateAbdmAuthentication", [], { authMethod, healthid }); - -export const confirmWithAadhaarOtp = ( - txnId: string, - otp: string, - patientId?: string -) => fireRequest("confirmWithAadhaarOtp", [], { txnId, otp, patientId }); - -export const confirmWithMobileOtp = ( - txnId: string, - otp: string, - patientId?: string -) => fireRequest("confirmWithMobileOtp", [], { txnId, otp, patientId }); - -export const linkViaQR = (abha_details: any, patientId?: string) => { - return fireRequest("linkViaQR", [], { - ...abha_details, - patientId, - }); -}; - export const linkCareContext = ( consultationId: string, data: { name?: string; gender?: "M" | "F" | "O"; dob?: string } @@ -902,53 +859,16 @@ export const linkCareContext = ( }); }; -export const getAbhaCard = (patient: string, type: "pdf" | "png") => { - return fireRequest("getAbhaCard", [], { - patient, - type, - }); -}; - export const healthFacilityActions = { list: (params: object) => { return fireRequest("listHealthFacilities", [], params); }, - create: (data: object) => { - return fireRequest("createHealthFacility", [], data); - }, - - read: (id: string) => { - return fireRequest( - "getHealthFacility", - [], - {}, - { facility_id: id }, - undefined, - true - ); - }, - update: (id: string, data: object) => { return fireRequest("updateHealthFacility", [], data, { facility_id: id, }); }, - - partialUpdate: (id: string, data: object) => { - return fireRequest("partialUpdateHealthFacility", [], data, { - facility_id: id, - }); - }, - - registerService: (id: string) => { - return fireRequest( - "registerHealthFacilityAsService", - [], - {}, - { facility_id: id } - ); - }, }; export const listAssetAvailability = (params: object) => diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index a05d461c98d..7e1ef24c058 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -1,5 +1,23 @@ import { IConfig } from "../Common/hooks/useConfig"; - +import { + IAadhaarOtp, + IAadhaarOtpTBody, + ICheckAndGenerateMobileOtp, + IConfirmMobileOtp, + ICreateHealthIdRequest, + ICreateHealthIdResponse, + IGenerateMobileOtpTBody, + IHealthFacility, + IHealthId, + ILinkABHANumber, + ISearchByHealthIdTBody, + IVerifyAadhaarOtpTBody, + IcreateHealthFacilityTBody, + IgetAbhaCardTBody, + IinitiateAbdmAuthenticationTBody, + IpartialUpdateHealthFacilityTBody, +} from "../Components/ABDM/models"; +import { AssetData } from "../Components/Assets/AssetTypes"; import { AssetBedBody, AssetBedModel, @@ -11,7 +29,6 @@ import { AssetUpdate, } from "../Components/Assets/AssetTypes"; import { FacilityModel, LocationModel, WardModel } from "../Components/Facility/models"; - import { IDeleteExternalResult, IExternalResult, @@ -916,108 +933,147 @@ const routes = { TBody: Type(), }, - // ABDM HealthID endpoints - generateAadhaarOtp: { - path: "/api/v1/abdm/healthid/generate_aadhaar_otp/", - method: "POST", - }, - - resendAadhaarOtp: { - path: "/api/v1/abdm/healthid/resend_aadhaar_otp/", - method: "POST", - }, - - verifyAadhaarOtp: { - path: "/api/v1/abdm/healthid/verify_aadhaar_otp/", - method: "POST", - }, - - generateMobileOtp: { - path: "/api/v1/abdm/healthid/generate_mobile_otp/", - method: "POST", - }, - - checkAndGenerateMobileOtp: { - path: "/api/v1/abdm/healthid/check_and_generate_mobile_otp/", - method: "POST", - }, - - // TODO: resend mobile otp - verifyMobileOtp: { - path: "/api/v1/abdm/healthid/verify_mobile_otp/", - method: "POST", - }, - - createHealthId: { - path: "/api/v1/abdm/healthid/create_health_id/", - method: "POST", - }, - - searchByHealthId: { - path: "/api/v1/abdm/healthid/search_by_health_id/", - method: "POST", - }, - - initiateAbdmAuthentication: { - path: "/api/v1/abdm/healthid/auth_init/", - method: "POST", - }, - - confirmWithAadhaarOtp: { - path: "/api/v1/abdm/healthid/confirm_with_aadhaar_otp/", - method: "POST", - }, - - confirmWithMobileOtp: { - path: "/api/v1/abdm/healthid/confirm_with_mobile_otp/", - method: "POST", - }, - - linkViaQR: { - path: "/api/v1/abdm/healthid/link_via_qr/", - method: "POST", - }, - - linkCareContext: { - path: "/api/v1/abdm/healthid/add_care_context/", - method: "POST", - }, - - getAbhaCard: { - path: "/api/v1/abdm/healthid/get_abha_card/", - method: "POST", - }, - - // ABDM Health Facility - - listHealthFacility: { - path: "/api/v1/abdm/health_facility/", - method: "GET", - }, - - createHealthFacility: { - path: "/api/v1/abdm/health_facility/", - method: "POST", - }, - - getHealthFacility: { - path: "/api/v1/abdm/health_facility/{facility_id}/", - method: "GET", - }, - - updateHealthFacility: { - path: "/api/v1/abdm/health_facility/{facility_id}/", - method: "PUT", - }, - - partialUpdateHealthFacility: { - path: "/api/v1/abdm/health_facility/{facility_id}/", - method: "PATCH", - }, - - registerHealthFacilityAsService: { - path: "/api/v1/abdm/health_facility/{facility_id}/register_service/", - method: "POST", + abha: { + // ABDM HealthID endpoints + generateAadhaarOtp: { + path: "/api/v1/abdm/healthid/generate_aadhaar_otp/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + resendAadhaarOtp: { + path: "/api/v1/abdm/healthid/resend_aadhaar_otp/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + verifyAadhaarOtp: { + path: "/api/v1/abdm/healthid/verify_aadhaar_otp/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + generateMobileOtp: { + path: "/api/v1/abdm/healthid/generate_mobile_otp/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + checkAndGenerateMobileOtp: { + path: "/api/v1/abdm/healthid/check_and_generate_mobile_otp/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + // TODO: resend mobile otp + verifyMobileOtp: { + path: "/api/v1/abdm/healthid/verify_mobile_otp/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + createHealthId: { + path: "/api/v1/abdm/healthid/create_health_id/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + searchByHealthId: { + path: "/api/v1/abdm/healthid/search_by_health_id/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + initiateAbdmAuthentication: { + path: "/api/v1/abdm/healthid/auth_init/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + confirmWithAadhaarOtp: { + path: "/api/v1/abdm/healthid/confirm_with_aadhaar_otp/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + confirmWithMobileOtp: { + path: "/api/v1/abdm/healthid/confirm_with_mobile_otp/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + linkViaQR: { + path: "/api/v1/abdm/healthid/link_via_qr/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + linkCareContext: { + path: "/api/v1/abdm/healthid/add_care_context/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + getAbhaCard: { + path: "/api/v1/abdm/healthid/get_abha_card/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + // ABDM Health Facility + + listHealthFacility: { + path: "/api/v1/abdm/health_facility/", + method: "GET", + }, + + createHealthFacility: { + path: "/api/v1/abdm/health_facility/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + getHealthFacility: { + path: "/api/v1/abdm/health_facility/{facility_id}/", + method: "GET", + TRes: Type(), + }, + + updateHealthFacility: { + path: "/api/v1/abdm/health_facility/{facility_id}/", + method: "PUT", + TRes: Type(), + TBody: Type(), + }, + + partialUpdateHealthFacility: { + path: "/api/v1/abdm/health_facility/{facility_id}/", + method: "PATCH", + TRes: Type(), + TBody: Type(), + }, + + registerHealthFacilityAsService: { + path: "/api/v1/abdm/health_facility/{facility_id}/register_service/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, }, // Asset Availability endpoints diff --git a/src/Utils/request/request.ts b/src/Utils/request/request.ts index d428107b64c..7cfb647d4b7 100644 --- a/src/Utils/request/request.ts +++ b/src/Utils/request/request.ts @@ -38,6 +38,7 @@ export default async function request( try { const res = await fetch(url, options); + const data = await getResponseBody(res); result = { From db528390424f6d7a839c857a6a061449fb916584 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Wed, 18 Oct 2023 17:09:35 +0530 Subject: [PATCH 41/65] Fixes JWT token refresh interval default fallback to 5 mins (instead of 15 mins) (#6466) * fix JWT token refresh fallback interval * remove jwt_token_refresh_interval from `config.json` --- public/config.json | 11 +++++------ src/Providers/AuthUserProvider.tsx | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/public/config.json b/public/config.json index 5e67902d4fe..444362cfb20 100644 --- a/public/config.json +++ b/public/config.json @@ -5,12 +5,12 @@ "site_url": "care.coronasafe.in", "analytics_server_url": "https://plausible.10bedicu.in", "header_logo": { - "light":"https://cdn.coronasafe.network/header_logo.png", - "dark":"https://cdn.coronasafe.network/header_logo.png" + "light": "https://cdn.coronasafe.network/header_logo.png", + "dark": "https://cdn.coronasafe.network/header_logo.png" }, "main_logo": { - "light":"https://cdn.coronasafe.network/light-logo.svg", - "dark":"https://cdn.coronasafe.network/black-logo.svg" + "light": "https://cdn.coronasafe.network/light-logo.svg", + "dark": "https://cdn.coronasafe.network/black-logo.svg" }, "gmaps_api_key": "AIzaSyDsBAc3y7deI5ZO3NtK5GuzKwtUzQNJNUk", "gov_data_api_key": "579b464db66ec23bdd000001cdd3946e44ce4aad7209ff7b23ac571b", @@ -22,6 +22,5 @@ "kasp_full_string": "Karunya Arogya Suraksha Padhathi", "sample_format_asset_import": "https://spreadsheets.google.com/feeds/download/spreadsheets/Export?key=11JaEhNHdyCHth4YQs_44YaRlP77Rrqe81VSEfg1glko&exportFormat=xlsx", "sample_format_external_result_import": "https://docs.google.com/spreadsheets/d/17VfgryA6OYSYgtQZeXU9mp7kNvLySeEawvnLBO_1nuE/export?format=csv&id=17VfgryA6OYSYgtQZeXU9mp7kNvLySeEawvnLBO_1nuE", - "enable_abdm": true, - "jwt_token_refresh_interval": 300000 + "enable_abdm": true } \ No newline at end of file diff --git a/src/Providers/AuthUserProvider.tsx b/src/Providers/AuthUserProvider.tsx index a64b38f630c..d515149341e 100644 --- a/src/Providers/AuthUserProvider.tsx +++ b/src/Providers/AuthUserProvider.tsx @@ -28,7 +28,7 @@ export default function AuthUserProvider({ children, unauthorized }: Props) { updateRefreshToken(true); setInterval( () => updateRefreshToken(), - jwt_token_refresh_interval ?? 5 * 60 * 3000 + jwt_token_refresh_interval ?? 5 * 60 * 1000 ); }, [data, jwt_token_refresh_interval]); From 6ee84227c47851865df19856317f4ecdb6729841 Mon Sep 17 00:00:00 2001 From: Abhiuday Gupta <77210185+cp-Coder@users.noreply.github.com> Date: Wed, 18 Oct 2023 18:41:28 +0530 Subject: [PATCH 42/65] Daily Rounds: Updated consciousness levels choices to MEWS (#6435) * fix(daily_round): aligned consciousness scale to mews * fix(daily_round): removed optional description * fix NeurologicalTables * fix line break --------- Co-authored-by: rithviknishad --- ...icalCare__NeurologicalMonitoringEditor.res | 44 +++++++++---------- .../DailyRound__NeurologicalMonitoring.res | 4 -- .../CriticalCare__NeurologicalMonitoring.res | 36 +++++++++------ .../Consultations/NeurologicalTables.tsx | 13 +++--- 4 files changed, 51 insertions(+), 46 deletions(-) diff --git a/src/Components/CriticalCareRecording/NeurologicalMonitoring/CriticalCare__NeurologicalMonitoringEditor.res b/src/Components/CriticalCareRecording/NeurologicalMonitoring/CriticalCare__NeurologicalMonitoringEditor.res index 35d137e0414..dfe0e5cc3dc 100644 --- a/src/Components/CriticalCareRecording/NeurologicalMonitoring/CriticalCare__NeurologicalMonitoringEditor.res +++ b/src/Components/CriticalCareRecording/NeurologicalMonitoring/CriticalCare__NeurologicalMonitoringEditor.res @@ -68,12 +68,12 @@ let reducer = (state, action) => { | SetConsciousnessLevel(consciousnessLevel) => { ...state, dirty: true, - consciousnessLevel: consciousnessLevel, + consciousnessLevel, } | SetConsciousnessLevelDetails(consciousnessLevelDetails) => { ...state, dirty: true, - consciousnessLevelDetails: consciousnessLevelDetails, + consciousnessLevelDetails, } | SetLeftPupilSize(leftPupilSize) => { ...state, @@ -83,17 +83,17 @@ let reducer = (state, action) => { | SetLeftPupilSizeDetails(leftPupilSizeDetails) => { ...state, dirty: true, - leftPupilSizeDetails: leftPupilSizeDetails, + leftPupilSizeDetails, } | SetLeftPupilLightReaction(leftPupilLightReaction) => { ...state, dirty: true, - leftPupilLightReaction: leftPupilLightReaction, + leftPupilLightReaction, } | SetLeftPupilLightReactionDetails(leftPupilLightReactionDetails) => { ...state, dirty: true, - leftPupilLightReactionDetails: leftPupilLightReactionDetails, + leftPupilLightReactionDetails, } | SetRightPupilSize(rightPupilSize) => { ...state, @@ -103,17 +103,17 @@ let reducer = (state, action) => { | SetRightPupilSizeDetails(rightPupilSizeDetails) => { ...state, dirty: true, - rightPupilSizeDetails: rightPupilSizeDetails, + rightPupilSizeDetails, } | SetRightPupilLightReaction(rightPupilLightReaction) => { ...state, dirty: true, - rightPupilLightReaction: rightPupilLightReaction, + rightPupilLightReaction, } | SetRightPupilLightReactionDetails(rightPupilLightReactionDetails) => { ...state, dirty: true, - rightPupilLightReactionDetails: rightPupilLightReactionDetails, + rightPupilLightReactionDetails, } | SetGlasgowEyeOpen(glasgowEyeOpen) => {...state, glasgowEyeOpen: Some(glasgowEyeOpen)} | SetGlasgowVerbalResponse(glasgowVerbalResponse) => { @@ -129,22 +129,22 @@ let reducer = (state, action) => { | SetLimbResponseUpperExtremityRight(limbResponseUpperExtremityRight) => { ...state, dirty: true, - limbResponseUpperExtremityRight: limbResponseUpperExtremityRight, + limbResponseUpperExtremityRight, } | SetLimbResponseUpperExtremityLeft(limbResponseUpperExtremityLeft) => { ...state, dirty: true, - limbResponseUpperExtremityLeft: limbResponseUpperExtremityLeft, + limbResponseUpperExtremityLeft, } | SetLimbResponseLowerExtremityRight(limbResponseLowerExtremityRight) => { ...state, dirty: true, - limbResponseLowerExtremityRight: limbResponseLowerExtremityRight, + limbResponseLowerExtremityRight, } | SetLimbResponseLowerExtremityLeft(limbResponseLowerExtremityLeft) => { ...state, dirty: true, - limbResponseLowerExtremityLeft: limbResponseLowerExtremityLeft, + limbResponseLowerExtremityLeft, } | SetSaving => {...state, saving: true} @@ -265,7 +265,7 @@ let renderConsciousnessLevel = ( ) => {
{str("Level Of Consciousness")}
-
+
{Js.Array.map(consciousnessLevel => { onInputChange(consciousnessLevel)} /> - }, [Alert, Drowsy, Stuporous, Comatose, CannotBeAssessed])->React.array} + }, [ + Unresponsive, + Alert, + RespondsToPain, + RespondsToVoice, + AgitatedOrConfused, + OnsetOfAgitationAndConfusion, + ])->React.array}
- {ReactUtils.nullUnless( - onInputDetailsChange(event)} - />, - input === NeurologicalMonitoring.CannotBeAssessed, - )}
} diff --git a/src/Components/CriticalCareRecording/NeurologicalMonitoring/DailyRound__NeurologicalMonitoring.res b/src/Components/CriticalCareRecording/NeurologicalMonitoring/DailyRound__NeurologicalMonitoring.res index f476fd36c46..7dd295ecb51 100644 --- a/src/Components/CriticalCareRecording/NeurologicalMonitoring/DailyRound__NeurologicalMonitoring.res +++ b/src/Components/CriticalCareRecording/NeurologicalMonitoring/DailyRound__NeurologicalMonitoring.res @@ -16,10 +16,6 @@ let make = ( NeurologicalMonitoring.consciousnessLevel(neurologicalMonitoring), ), )} - {renderOptionalDescription( - "Consciousness Level Reaction Description", - NeurologicalMonitoring.consciousnessLevelDetails(neurologicalMonitoring), - )}
{title("Left Pupil")} diff --git a/src/Components/CriticalCareRecording/types/CriticalCare__NeurologicalMonitoring.res b/src/Components/CriticalCareRecording/types/CriticalCare__NeurologicalMonitoring.res index f42ca5c140d..0fae710ce36 100644 --- a/src/Components/CriticalCareRecording/types/CriticalCare__NeurologicalMonitoring.res +++ b/src/Components/CriticalCareRecording/types/CriticalCare__NeurologicalMonitoring.res @@ -1,4 +1,11 @@ -type consciousnessLevel = Alert | Drowsy | Stuporous | Comatose | CannotBeAssessed | Unknown +type consciousnessLevel = + | Unresponsive + | RespondsToPain + | RespondsToVoice + | Alert + | AgitatedOrConfused + | OnsetOfAgitationAndConfusion + | Unknown type lightReaction = Brisk | Sluggish | Fixed | CannotBeAssessed | Unknown type limpResponse = Strong | Moderate | Weak | Flexion | Extension | NONE_ | Unknown @@ -68,11 +75,12 @@ let make = ( let makeConsciousnessLevel = consciousnessLevel => { switch consciousnessLevel { + | "UNRESPONSIVE" => Unresponsive + | "RESPONDS_TO_PAIN" => RespondsToPain + | "RESPONDS_TO_VOICE" => RespondsToVoice | "ALERT" => Alert - | "DROWSY" => Drowsy - | "STUPOROUS" => Stuporous - | "COMATOSE" => Comatose - | "CANNOT_BE_ASSESSED" => CannotBeAssessed + | "AGITATED_OR_CONFUSED" => AgitatedOrConfused + | "ONSET_OF_AGITATION_AND_CONFUSION" => OnsetOfAgitationAndConfusion | "UNKNOWN" | _ => Unknown @@ -81,11 +89,12 @@ let makeConsciousnessLevel = consciousnessLevel => { let encodeConConsciousnessLevel = consciousnessLevel => { switch consciousnessLevel { + | Unresponsive => "UNRESPONSIVE" + | RespondsToPain => "RESPONDS_TO_PAIN" + | RespondsToVoice => "RESPONDS_TO_VOICE" | Alert => "ALERT" - | Drowsy => "DROWSY" - | Stuporous => "STUPOROUS" - | Comatose => "COMATOSE" - | CannotBeAssessed => "CANNOT_BE_ASSESSED" + | AgitatedOrConfused => "AGITATED_OR_CONFUSED" + | OnsetOfAgitationAndConfusion => "ONSET_OF_AGITATION_AND_CONFUSION" | Unknown => "UNKNOWN" } } @@ -150,11 +159,12 @@ let lightReactionToString = lightReaction => { let consciousnessLevelToString = consciousnessLevel => { switch consciousnessLevel { + | Unresponsive => "Unresponsive" + | RespondsToPain => "Responds to Pain" + | RespondsToVoice => "Responds to Voice" | Alert => "Alert" - | Drowsy => "Drowsy" - | Stuporous => "Stuporous" - | Comatose => "Comatose" - | CannotBeAssessed => "Cannot be assessed" + | AgitatedOrConfused => "Agitated or Confused" + | OnsetOfAgitationAndConfusion => "Onset of Agitation and Confusion" | Unknown => "Unknown" } } diff --git a/src/Components/Facility/Consultations/NeurologicalTables.tsx b/src/Components/Facility/Consultations/NeurologicalTables.tsx index d33c7d6a311..36fae175480 100644 --- a/src/Components/Facility/Consultations/NeurologicalTables.tsx +++ b/src/Components/Facility/Consultations/NeurologicalTables.tsx @@ -95,10 +95,11 @@ export const NeurologicalTable = (props: any) => { const LOC_OPTIONS = [ { id: 0, value: "Unknown" }, { id: 5, value: "Alert" }, - { id: 10, value: "Drowsy" }, - { id: 15, value: "Stuporous" }, - { id: 20, value: "Comatose" }, - { id: 25, value: "Cannot Be Assessed" }, + { id: 10, value: "Responds to Voice" }, + { id: 15, value: "Responds to Pain" }, + { id: 20, value: "Unresponsive" }, + { id: 25, value: "Agitated or Confused" }, + { id: 30, value: "Onset of Agitation and Confusion" }, ]; const REACTION_OPTIONS = [ @@ -298,9 +299,9 @@ export const NeurologicalTable = (props: any) => { {locData.map((x: any, i: any) => (
-
+
{x.date}
From 80116533220baa98336600d5af7e7d79f3e91a56 Mon Sep 17 00:00:00 2001 From: Khavin Shankar Date: Thu, 19 Oct 2023 14:59:07 +0530 Subject: [PATCH 43/65] Fixed typo in pathParams of ConfigureHealthFacility and replaced useDispatch in ConfigureFacility (#6470) --- .../ABDM/ConfigureHealthFacility.tsx | 51 +++++----- src/Components/Facility/FacilityConfigure.tsx | 95 +++++++++---------- src/Components/Facility/models.tsx | 5 +- src/Redux/api.tsx | 11 ++- 4 files changed, 79 insertions(+), 83 deletions(-) diff --git a/src/Components/ABDM/ConfigureHealthFacility.tsx b/src/Components/ABDM/ConfigureHealthFacility.tsx index a2b8f254bcb..16e6bf90ea4 100644 --- a/src/Components/ABDM/ConfigureHealthFacility.tsx +++ b/src/Components/ABDM/ConfigureHealthFacility.tsx @@ -1,4 +1,4 @@ -import { lazy, useEffect, useReducer, useState } from "react"; +import { lazy, useReducer, useState } from "react"; import * as Notification from "../../Utils/Notifications.js"; import { navigate } from "raviger"; import { Cancel, Submit } from "../Common/components/ButtonV2"; @@ -7,6 +7,7 @@ import { classNames } from "../../Utils/utils"; import useQuery from "../../Utils/request/useQuery"; import routes from "../../Redux/api"; import request from "../../Utils/request/request"; +import { FieldChangeEvent } from "../Form/FormFields/Utils.js"; const Loading = lazy(() => import("../Common/Loading")); const initForm = { @@ -42,28 +43,24 @@ export const ConfigureHealthFacility = (props: any) => { const { facilityId } = props; const [isLoading, setIsLoading] = useState(false); - const { - data: health_facility, - loading, - refetch, - } = useQuery(routes.abha.getHealthFacility, { - pathParams: { facility__external_id: facilityId }, + const { loading } = useQuery(routes.abha.getHealthFacility, { + pathParams: { facility_id: facilityId }, + silent: true, + onResponse(res) { + if (res.data) { + dispatch({ + type: "set_form", + form: { + ...state.form, + health_facility: res.data, + hf_id: res.data.hf_id, + }, + }); + } + }, }); - useEffect(() => { - const formData = { - ...state.form, - hf_id: health_facility?.hf_id, - health_facility: health_facility, - }; - dispatch({ type: "set_form", form: formData }); - }, [health_facility]); - - useEffect(() => { - refetch(); - }, [dispatch, refetch]); - - const handleSubmit = async (e: any) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsLoading(true); @@ -83,7 +80,7 @@ export const ConfigureHealthFacility = (props: any) => { routes.abha.partialUpdateHealthFacility, { pathParams: { - facility__external_id: facilityId, + facility_id: facilityId, }, body: { hf_id: state.form.hf_id, @@ -97,7 +94,7 @@ export const ConfigureHealthFacility = (props: any) => { routes.abha.registerHealthFacilityAsService, { pathParams: { - facility__external_id: facilityId, + facility_id: facilityId, }, } ); @@ -151,7 +148,7 @@ export const ConfigureHealthFacility = (props: any) => { setIsLoading(false); }; - const handleChange = (e: any) => { + const handleChange = (e: FieldChangeEvent) => { dispatch({ type: "set_form", form: { ...state.form, [e.name]: e.value }, @@ -164,7 +161,7 @@ export const ConfigureHealthFacility = (props: any) => { return (
- handleSubmit(e)}> +
{ } required value={state.form.hf_id} - onChange={(e) => handleChange(e)} + onChange={handleChange} error={state.errors?.hf_id} />
-
+
navigate(`/facility/${facilityId}`)} /> import("../Common/Loading")); const initForm = { @@ -22,6 +19,7 @@ const initForm = { ward: 0, middleware_address: "", }; + const initialState = { form: { ...initForm }, errors: {}, @@ -46,44 +44,33 @@ const FormReducer = (state = initialState, action: any) => { } }; -export const FacilityConfigure = (props: any) => { +interface IProps { + facilityId: string; +} + +export const FacilityConfigure = (props: IProps) => { const [state, dispatch] = useReducer(FormReducer, initialState); const { facilityId } = props; - const dispatchAction: any = useDispatch(); const [isLoading, setIsLoading] = useState(false); - const fetchData = useCallback( - async (status: statusType) => { - if (facilityId) { - setIsLoading(true); - const res = await dispatchAction(getPermittedFacility(facilityId)); - if (!status.aborted && res.data) { - const formData = { - name: res.data.name, - state: res.data.state, - district: res.data.district, - local_body: res.data.local_body, - ward: res.data.ward, - middleware_address: res.data.middleware_address, - }; - dispatch({ type: "set_form", form: formData }); - } else { - navigate(`/facility/${facilityId}`); - } - setIsLoading(false); + const { loading } = useQuery(routes.getPermittedFacility, { + pathParams: { id: facilityId }, + onResponse: (res) => { + if (res.data) { + const formData = { + name: res.data.name, + state: res.data.state, + district: res.data.district, + local_body: res.data.local_body, + ward: res.data.ward, + middleware_address: res.data.middleware_address, + }; + dispatch({ type: "set_form", form: formData }); } }, - [dispatchAction, facilityId] - ); + }); - useAbortableEffect( - (status: statusType) => { - fetchData(status); - }, - [dispatch, fetchData] - ); - - const handleSubmit = async (e: any) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsLoading(true); if (!state.form.middleware_address) { @@ -108,35 +95,39 @@ export const FacilityConfigure = (props: any) => { setIsLoading(false); return; } - const data: any = { + + const data = { ...state.form, middleware_address: state.form.middleware_address, }; - const res = await dispatchAction(partialUpdateFacility(facilityId, data)); + const { res, error } = await request(routes.partialUpdateFacility, { + pathParams: { id: facilityId }, + body: data, + }); + setIsLoading(false); - if (res && res.data) { + if (res?.ok) { Notification.Success({ msg: "Facility updated successfully", }); navigate(`/facility/${facilityId}`); } else { - if (res?.data) - Notification.Error({ - msg: "Something went wrong: " + (res.data.detail || ""), - }); + Notification.Error({ + msg: error?.detail ?? "Something went wrong", + }); } setIsLoading(false); }; - const handleChange = (e: any) => { + const handleChange = (e: FieldChangeEvent) => { dispatch({ type: "set_form", form: { ...state.form, [e.name]: e.value }, }); }; - if (isLoading) { + if (isLoading || loading) { return ; } @@ -146,10 +137,10 @@ export const FacilityConfigure = (props: any) => { crumbsReplacements={{ [facilityId]: { name: state.form.name }, }} - className="mx-auto max-w-3xl" + className="w-full overflow-x-hidden" >
- handleSubmit(e)}> +
{ label="Facility Middleware Address" required value={state.form.middleware_address} - onChange={(e) => handleChange(e)} + onChange={handleChange} error={state.errors?.middleware_address} />
-
+
navigate(`/facility/${facilityId}`)} />
diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index 439194e85de..a9861a4869c 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -28,7 +28,6 @@ export interface WardModel { export interface FacilityModel { id?: number; name?: string; - district?: number; read_cover_image_url?: string; facility_type?: string; address?: string; @@ -53,6 +52,10 @@ export interface FacilityModel { ward_object?: WardModel; modified_date?: string; created_date?: string; + state: number; + district: number; + local_body: number; + ward: number; } export interface CapacityModal { diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 7e1ef24c058..d775c970a2d 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -17,7 +17,6 @@ import { IinitiateAbdmAuthenticationTBody, IpartialUpdateHealthFacilityTBody, } from "../Components/ABDM/models"; -import { AssetData } from "../Components/Assets/AssetTypes"; import { AssetBedBody, AssetBedModel, @@ -28,7 +27,11 @@ import { AssetTransaction, AssetUpdate, } from "../Components/Assets/AssetTypes"; -import { FacilityModel, LocationModel, WardModel } from "../Components/Facility/models"; +import { + FacilityModel, + LocationModel, + WardModel, +} from "../Components/Facility/models"; import { IDeleteExternalResult, IExternalResult, @@ -241,8 +244,10 @@ const routes = { }, partialUpdateFacility: { - path: "/api/v1/facility", + path: "/api/v1/facility/{id}/", method: "PATCH", + TRes: Type(), + TBody: Type>(), }, deleteFacilityCoverImage: { From 9eac85c010d6bfae20d065603fdaaf7d149c3879 Mon Sep 17 00:00:00 2001 From: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> Date: Fri, 20 Oct 2023 09:03:19 +0800 Subject: [PATCH 44/65] New Cypress Test | Search using username | User Tab (#6478) * Username search cypress test * import error --- cypress/e2e/users_spec/user_homepage.cy.ts | 39 ++++++++++++++++++++++ cypress/pageobject/Users/UserSearch.ts | 37 ++++++++++++++++++++ src/CAREUI/display/FilterBadge.tsx | 1 + src/Components/Users/ManageUsers.tsx | 1 + 4 files changed, 78 insertions(+) create mode 100644 cypress/e2e/users_spec/user_homepage.cy.ts create mode 100644 cypress/pageobject/Users/UserSearch.ts diff --git a/cypress/e2e/users_spec/user_homepage.cy.ts b/cypress/e2e/users_spec/user_homepage.cy.ts new file mode 100644 index 00000000000..40a55bad1e8 --- /dev/null +++ b/cypress/e2e/users_spec/user_homepage.cy.ts @@ -0,0 +1,39 @@ +/// + +import { cy, describe, before, beforeEach, it, afterEach } from "local-cypress"; +import LoginPage from "../../pageobject/Login/LoginPage"; +import { UserPage } from "../../pageobject/Users/UserSearch"; + +describe("Asset Tab", () => { + const userPage = new UserPage(); + const usernameToTest = "devdoctor"; + const currentuser = "devdistrictadmin"; + const loginPage = new LoginPage(); + before(() => { + loginPage.loginAsDisctrictAdmin(); + cy.saveLocalStorage(); + }); + + beforeEach(() => { + cy.restoreLocalStorage(); + cy.awaitUrl("/users"); + }); + + it("Search by username", () => { + userPage.checkSearchInputVisibility(); + userPage.typeInSearchInput(usernameToTest); + userPage.checkUrlForUsername(usernameToTest); + userPage.checkUsernameText(usernameToTest); + userPage.checkUsernameBadgeVisibility(true); + userPage.clearSearchInput(); + userPage.checkUsernameBadgeVisibility(false); + userPage.typeInSearchInput(usernameToTest); + userPage.checkUsernameText(usernameToTest); + userPage.clickRemoveIcon(); + userPage.checkUsernameBadgeVisibility(false); + userPage.checkUsernameText(currentuser); + }); + afterEach(() => { + cy.saveLocalStorage(); + }); +}); diff --git a/cypress/pageobject/Users/UserSearch.ts b/cypress/pageobject/Users/UserSearch.ts new file mode 100644 index 00000000000..311a4b87c3a --- /dev/null +++ b/cypress/pageobject/Users/UserSearch.ts @@ -0,0 +1,37 @@ +// UserPage.ts +export class UserPage { + // Element selectors + searchByUsernameInput = "#search-by-username"; + usernameText = "#username"; + usernameBadge = "[data-testid='Username']"; + removeIcon = "#removeicon"; + + checkSearchInputVisibility() { + cy.get(this.searchByUsernameInput).should("be.visible"); + } + + typeInSearchInput(text: string) { + cy.get(this.searchByUsernameInput).click().type(text); + } + + clearSearchInput() { + cy.get(this.searchByUsernameInput).click().clear(); + } + + checkUrlForUsername(username: string) { + cy.url().should("include", `username=${username}`); + } + + checkUsernameText(username: string) { + cy.get(this.usernameText).should("have.text", username); + } + + checkUsernameBadgeVisibility(shouldBeVisible: boolean) { + const assertion = shouldBeVisible ? "be.visible" : "not.be.visible"; + cy.get(this.usernameBadge).should(assertion); + } + + clickRemoveIcon() { + cy.get(this.removeIcon).click(); + } +} diff --git a/src/CAREUI/display/FilterBadge.tsx b/src/CAREUI/display/FilterBadge.tsx index c0f691f48b9..2443130981a 100644 --- a/src/CAREUI/display/FilterBadge.tsx +++ b/src/CAREUI/display/FilterBadge.tsx @@ -18,6 +18,7 @@ const FilterBadge = ({ name, value, onRemove }: FilterBadgeProps) => { > {`${name}: ${value}`} diff --git a/src/Components/Users/ManageUsers.tsx b/src/Components/Users/ManageUsers.tsx index 8625d442f52..150c102aff1 100644 --- a/src/Components/Users/ManageUsers.tsx +++ b/src/Components/Users/ManageUsers.tsx @@ -543,6 +543,7 @@ export default function ManageUsers() {
updateQuery({ [e.name]: e.value })} value={qParams.username} From 08193ba86a89b916cfa3d93c160b0c7cf6116423 Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Fri, 20 Oct 2023 06:34:29 +0530 Subject: [PATCH 45/65] Fix loadash-es import (#6477) --- .../ExternalResult/ExternalResultUpload.tsx | 5 +++-- .../Facility/Investigations/Reports/index.tsx | 5 ++--- .../Facility/Investigations/Reports/utils.tsx | 12 ++++++------ .../Investigations/ShowInvestigation.tsx | 6 +++--- src/Components/Patient/SampleDetails.tsx | 19 ++++++++----------- src/Components/Patient/SampleTestCard.tsx | 9 +++++---- src/Utils/Notifications.js | 4 ++-- 7 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/Components/ExternalResult/ExternalResultUpload.tsx b/src/Components/ExternalResult/ExternalResultUpload.tsx index a3b6dd2fbf9..20a2cec3341 100644 --- a/src/Components/ExternalResult/ExternalResultUpload.tsx +++ b/src/Components/ExternalResult/ExternalResultUpload.tsx @@ -1,3 +1,4 @@ +import _ from "lodash-es"; import { navigate } from "raviger"; import { lazy, useState } from "react"; import CSVReader from "react-csv-reader"; @@ -130,8 +131,8 @@ export default function ExternalResultUpload() { ? errors.map((error: any) => { return (
- {error[0][0].toLowerCase()} -{" "} - {error[0][1].toLowerCase()} + {_.startCase(_.camelCase(error[0][0]))} -{" "} + {error[0][1]}
); }) diff --git a/src/Components/Facility/Investigations/Reports/index.tsx b/src/Components/Facility/Investigations/Reports/index.tsx index 2db928386f1..9b3c29d40f8 100644 --- a/src/Components/Facility/Investigations/Reports/index.tsx +++ b/src/Components/Facility/Investigations/Reports/index.tsx @@ -1,5 +1,5 @@ import * as Notification from "../../../../Utils/Notifications"; - +import _ from "lodash-es"; import { Group, InvestigationType } from ".."; import { getPatient, @@ -17,7 +17,6 @@ import { InvestigationResponse } from "./types"; import Loading from "../../../Common/Loading"; import Page from "../../../Common/components/Page"; import ReportTable from "./ReportTable"; -import { chain } from "lodash-es"; import { useDispatch } from "react-redux"; import { useRef } from "react"; @@ -175,7 +174,7 @@ const InvestigationReports = ({ id }: any) => { }) ); - const investigationList = chain(data) + const investigationList = _.chain(data) .compact() .flatten() .map((i) => ({ diff --git a/src/Components/Facility/Investigations/Reports/utils.tsx b/src/Components/Facility/Investigations/Reports/utils.tsx index 46b95800339..e57f3c42c53 100644 --- a/src/Components/Facility/Investigations/Reports/utils.tsx +++ b/src/Components/Facility/Investigations/Reports/utils.tsx @@ -1,20 +1,20 @@ -import { memoize, chain, findIndex } from "lodash-es"; +import _ from "lodash-es"; import { InvestigationResponse } from "./types"; -export const transformData = memoize((data: InvestigationResponse) => { - const sessions = chain(data) +export const transformData = _.memoize((data: InvestigationResponse) => { + const sessions = _.chain(data) .map((value) => value.session_object) .uniqBy("session_external_id") .orderBy("session_created_date", "desc") .value(); - const groupByInvestigation = chain(data) + const groupByInvestigation = _.chain(data) .groupBy("investigation_object.external_id") .values() .value(); const reqData = groupByInvestigation.map((value) => { const sessionValues = Array.from({ length: sessions.length }); value.forEach((val) => { - const sessionIndex = findIndex(sessions, [ + const sessionIndex = _.findIndex(sessions, [ "session_external_id", val.session_object.session_external_id, ]); @@ -55,7 +55,7 @@ export const transformData = memoize((data: InvestigationResponse) => { return { sessions, data: reqData }; }); -export const getColorIndex = memoize( +export const getColorIndex = _.memoize( ({ max, min, value }: { min?: number; max?: number; value?: number }) => { if (!max && min && value) { // 1 => yellow color diff --git a/src/Components/Facility/Investigations/ShowInvestigation.tsx b/src/Components/Facility/Investigations/ShowInvestigation.tsx index 4b8d1d065a7..00196bb6678 100644 --- a/src/Components/Facility/Investigations/ShowInvestigation.tsx +++ b/src/Components/Facility/Investigations/ShowInvestigation.tsx @@ -8,8 +8,8 @@ import { } from "../../../Redux/actions"; import PageTitle from "../../Common/PageTitle"; import InvestigationTable from "./InvestigationTable"; - -import { set, chain } from "lodash-es"; +import _ from "lodash-es"; +import { set } from "lodash-es"; import { navigate } from "raviger"; import * as Notification from "../../../Utils/Notifications.js"; @@ -147,7 +147,7 @@ export default function ShowInvestigation(props: any) { }; const handleUpdateCancel = useCallback(() => { - const changedValues = chain(state.initialValues) + const changedValues = _.chain(state.initialValues) .map((val: any, _key: string) => ({ id: val?.id, initialValue: val?.notes || val?.value || null, diff --git a/src/Components/Patient/SampleDetails.tsx b/src/Components/Patient/SampleDetails.tsx index 672f3905792..9b08395a233 100644 --- a/src/Components/Patient/SampleDetails.tsx +++ b/src/Components/Patient/SampleDetails.tsx @@ -7,6 +7,7 @@ import ButtonV2 from "../Common/components/ButtonV2"; import Card from "../../CAREUI/display/Card"; import { FileUpload } from "./FileUpload"; import Page from "../Common/components/Page"; +import _ from "lodash-es"; import { formatAge, formatDateTime } from "../../Utils/utils"; import { getTestSample } from "../../Redux/actions"; @@ -114,7 +115,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { ) : (
Age: - {formatAge(patientData.age, patientData.date_of_birth)} + {formatAge(patientData?.age, patientData?.date_of_birth)}
)}
@@ -257,16 +258,12 @@ export const SampleDetails = ({ id }: DetailRoute) => {
- - Status:{" "} - {" "} - {flow.status} + Status: {" "} + {_.startCase(_.camelCase(flow.status))}
- - Label: - {" "} - {flow.notes?.toLowerCase()} + Label:{" "} + {_.capitalize(flow.notes)}
Created On :{" "} @@ -350,7 +347,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { Doctor's Name:{" "} - {sampleDetails.doctor_name} + {_.startCase(_.camelCase(sampleDetails.doctor_name))}
)} {sampleDetails.diagnosis && ( @@ -433,7 +430,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { Sample Type:{" "} - {sampleDetails.sample_type} + {_.startCase(_.camelCase(sampleDetails.sample_type))}
)}
diff --git a/src/Components/Patient/SampleTestCard.tsx b/src/Components/Patient/SampleTestCard.tsx index 0c299f0a1b5..1ae1ff8671b 100644 --- a/src/Components/Patient/SampleTestCard.tsx +++ b/src/Components/Patient/SampleTestCard.tsx @@ -6,6 +6,7 @@ import { SAMPLE_TEST_STATUS } from "../../Common/constants"; import { patchSample } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications"; import UpdateStatusDialog from "./UpdateStatusDialog"; +import _ from "lodash-es"; import { formatDateTime } from "../../Utils/utils"; import ButtonV2 from "../Common/components/ButtonV2"; import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; @@ -90,8 +91,8 @@ export const SampleTestCard = (props: SampleDetailsProps) => {
Status{" "}
-
- {itemData.status?.toLowerCase()} +
+ {_.startCase(_.camelCase(itemData.status))}
@@ -125,8 +126,8 @@ export const SampleTestCard = (props: SampleDetailsProps) => {
Result{" "}
-
- {itemData.result?.toLowerCase()} +
+ {_.startCase(_.camelCase(itemData.result))}
diff --git a/src/Utils/Notifications.js b/src/Utils/Notifications.js index 298622c35ca..188a2f7a61b 100644 --- a/src/Utils/Notifications.js +++ b/src/Utils/Notifications.js @@ -1,6 +1,6 @@ import { alert, Stack, defaultModules } from "@pnotify/core"; import * as PNotifyMobile from "@pnotify/mobile"; -import { startCase, camelCase } from "lodash-es"; +import _ from "lodash-es"; defaultModules.set(PNotifyMobile, {}); @@ -43,7 +43,7 @@ const notifyError = (error) => { errorMsg = error.detail; } else { for (let [key, value] of Object.entries(error)) { - let keyName = startCase(camelCase(key)); + let keyName = _.startCase(_.camelCase(key)); if (Array.isArray(value)) { const uniques = [...new Set(value)]; errorMsg += `${keyName} - ${uniques.splice(0, 5).join(", ")}`; From 33f103d18501564b2cc840eedd30dc146c092e8f Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Fri, 20 Oct 2023 06:36:01 +0530 Subject: [PATCH 46/65] Fix session expiry detection (#6472) * fix session expiry detection * Fix logout bug --- src/Redux/fireRequest.tsx | 14 +++++++++----- src/Routers/AppRouter.tsx | 7 ++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Redux/fireRequest.tsx b/src/Redux/fireRequest.tsx index bf083e23a92..3d8c677d47d 100644 --- a/src/Redux/fireRequest.tsx +++ b/src/Redux/fireRequest.tsx @@ -93,11 +93,15 @@ export const fireRequest = ( const config: any = { headers: {}, }; - if (!request.noAuth && localStorage.getItem(LocalStorageKeys.accessToken)) { - config.headers["Authorization"] = - "Bearer " + localStorage.getItem(LocalStorageKeys.accessToken); - } else { - // TODO: get access token + if (!request.noAuth) { + const access_token = localStorage.getItem(LocalStorageKeys.accessToken); + if (access_token) { + config.headers["Authorization"] = "Bearer " + access_token; + } else { + // The access token is missing from the local storage. Redirect to login page. + window.location.href = "/"; + return; + } } const axiosApiCall: any = axios.create(config); diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx index 0f6108b00e3..d098a480149 100644 --- a/src/Routers/AppRouter.tsx +++ b/src/Routers/AppRouter.tsx @@ -65,7 +65,12 @@ export default function AppRouter() { useEffect(() => { addEventListener("storage", (event: any) => { - if (event.key === LocalStorageKeys.accessToken && !event.newValue) { + if ( + [LocalStorageKeys.accessToken, LocalStorageKeys.refreshToken].includes( + event.key + ) && + !event.newValue + ) { handleSignOut(true); } }); From e452bb5e075ec1e4670a848dca0846c94158d458 Mon Sep 17 00:00:00 2001 From: Shivam Jha <86483059+ShivamJhaa@users.noreply.github.com> Date: Fri, 20 Oct 2023 06:38:06 +0530 Subject: [PATCH 47/65] Added test of patient_crud.cy.ts (#6135) * Added test for importing and configuring an asset * Revert few changes * nits * Migrated to POM approach * Migrated patient_crud to POM approach * reverted some chnages * FIx * Merge conflicts * Fixed test * Added test for patient_test * nits * add responsiveness to virtual nursing assistant card (#6130) * allow use stock as well (#6115) * fixed responsive issue of 'Update Log' button in patient consultation page (#6113) * fix multiple bed bug (#6111) * Fixed typo in Date format in Asset management (#6105) * Added padding to count block on patients page * fixed date format in asset manage page * show only items with no min value (#6103) * check for id in response (#6100) * Added Responsiveness to File upload (#6096) * add responsiveness * refactor * remove overlap (#6094) * fixed failing test * Fix * Fixed comments * fix comments * Fixed all comments * Migrated test to new test suite * Added upload file test * fixed failing test * Fix failing test * Revert unwanted changes * fixed conflict * random failure fixed * temporary fixed medicine selection --------- Co-authored-by: Pranshu Aggarwal <70687348+Pranshu1902@users.noreply.github.com> Co-authored-by: Gokulram A Co-authored-by: Kshitij Verma <101321276+kshitijv256@users.noreply.github.com> Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> --- cypress/e2e/patient_spec/patient_crud.cy.ts | 17 +- cypress/e2e/patient_spec/patient_manage.cy.ts | 78 ++++++++++ .../pageobject/Patient/PatientConsultation.ts | 146 +++++++++++++++++- cypress/pageobject/Patient/PatientCreation.ts | 6 + src/Components/Facility/ConsultationCard.tsx | 1 + .../Facility/ConsultationDetails/index.tsx | 8 +- src/Components/Patient/FileUpload.tsx | 2 + src/Components/Patient/PatientNotes.tsx | 2 + 8 files changed, 256 insertions(+), 4 deletions(-) create mode 100644 cypress/e2e/patient_spec/patient_manage.cy.ts diff --git a/cypress/e2e/patient_spec/patient_crud.cy.ts b/cypress/e2e/patient_spec/patient_crud.cy.ts index fcfd97039f7..4fef861aeab 100644 --- a/cypress/e2e/patient_spec/patient_crud.cy.ts +++ b/cypress/e2e/patient_spec/patient_crud.cy.ts @@ -130,12 +130,27 @@ describe("Patient Creation with consultation", () => { patientConsultationPage.interceptMediaBase(); patientConsultationPage.selectMedicinebox(); patientConsultationPage.waitForMediabaseStatusCode(); - patientConsultationPage.prescribeMedicine(); + patientConsultationPage.prescribefirstMedicine(); patientConsultationPage.enterDosage("3"); patientConsultationPage.selectDosageFrequency("Twice daily"); patientConsultationPage.submitPrescriptionAndReturn(); }); + it("Edit created consultation to existing patient", () => { + updatePatientPage.visitUpdatedPatient(); + patientConsultationPage.visitEditConsultationPage(); + patientConsultationPage.fillIllnessHistory("editted"); + patientConsultationPage.selectConsultationStatus( + "Referred from other hospital" + ); + patientConsultationPage.updateSymptoms("FEVER"); + patientConsultationPage.setSymptomsDate("01082023"); + patientConsultationPage.updateConsultation(); + patientConsultationPage.verifySuccessNotification( + "Consultation updated successfully" + ); + }); + afterEach(() => { cy.saveLocalStorage(); }); diff --git a/cypress/e2e/patient_spec/patient_manage.cy.ts b/cypress/e2e/patient_spec/patient_manage.cy.ts new file mode 100644 index 00000000000..e8d381286aa --- /dev/null +++ b/cypress/e2e/patient_spec/patient_manage.cy.ts @@ -0,0 +1,78 @@ +import { afterEach, before, beforeEach, cy, describe, it } from "local-cypress"; +import LoginPage from "../../pageobject/Login/LoginPage"; +import { PatientConsultationPage } from "../../pageobject/Patient/PatientConsultation"; +import { PatientPage } from "../../pageobject/Patient/PatientCreation"; + +describe("Patient", () => { + const loginPage = new LoginPage(); + const patientPage = new PatientPage(); + const patientConsultationPage = new PatientConsultationPage(); + + before(() => { + loginPage.loginAsDisctrictAdmin(); + cy.saveLocalStorage(); + }); + + beforeEach(() => { + cy.restoreLocalStorage(); + cy.awaitUrl("/patients"); + }); + + // it("Create Patient shift requests.", () => { + // patientPage.visitPatient(); + // patientConsultationPage.visitShiftRequestPage(); + // patientConsultationPage.enterPatientShiftDetails( + // "Test User", + // phone_number, + // "Dummy Shifting", + // "Reason" + // ); + // patientConsultationPage.createShiftRequest(); + // patientConsultationPage.verifySuccessNotification( + // "Shift request created successfully" + // ); + // }); + // commented out the shifting request, as logic need to be re-visited + + it("Post doctor notes for an already created patient", () => { + patientPage.visitPatient(); + patientConsultationPage.visitDoctorNotesPage(); + patientConsultationPage.addDoctorsNotes("Test Doctor Notes"); + patientConsultationPage.postDoctorNotes(); + patientConsultationPage.verifySuccessNotification( + "Note added successfully" + ); + }); + + it("Edit prescription for an already created patient", () => { + patientPage.visitPatient(); + patientConsultationPage.visitEditPrescriptionPage(); + patientConsultationPage.clickAddPrescription(); + patientConsultationPage.interceptMediaBase(); + patientConsultationPage.selectMedicinebox(); + patientConsultationPage.waitForMediabaseStatusCode(); + patientConsultationPage.prescribesecondMedicine(); + patientConsultationPage.enterDosage("4"); + patientConsultationPage.selectDosageFrequency("Twice daily"); + patientConsultationPage.submitPrescription(); + }); + + it("Upload consultations file ", () => { + patientPage.visitPatient(); + patientConsultationPage.visitFilesPage(); + patientConsultationPage.uploadFile(); + patientConsultationPage.clickUploadFile(); + }); + + it("Discharge a patient", () => { + patientPage.visitPatient(); + patientConsultationPage.clickDischargePatient(); + patientConsultationPage.selectDischargeReason("Recovered"); + patientConsultationPage.addDischargeNotes("Discharge notes"); + patientConsultationPage.confirmDischarge(); + }); + + afterEach(() => { + cy.saveLocalStorage(); + }); +}); diff --git a/cypress/pageobject/Patient/PatientConsultation.ts b/cypress/pageobject/Patient/PatientConsultation.ts index 2eb6550cb8b..bf301641898 100644 --- a/cypress/pageobject/Patient/PatientConsultation.ts +++ b/cypress/pageobject/Patient/PatientConsultation.ts @@ -1,5 +1,7 @@ export class PatientConsultationPage { selectConsultationStatus(status: string) { + cy.get("#consultation_status").scrollIntoView(); + cy.get("#consultation_status").should("be.visible"); cy.get("#consultation_status") .click() .then(() => { @@ -83,10 +85,18 @@ export class PatientConsultationPage { cy.intercept("GET", "**/api/v1/medibase/**").as("getMediaBase"); } - prescribeMedicine() { + prescribefirstMedicine() { cy.get("div#medicine_object input[placeholder='Select'][role='combobox']") .click() - .type("dolo{enter}"); + .type("dolo") + .type("{downarrow}{enter}"); + } + + prescribesecondMedicine() { + cy.get("div#medicine_object input[placeholder='Select'][role='combobox']") + .click() + .type("dolo") + .type("{downarrow}{downarrow}{enter}"); } selectMedicinebox() { @@ -95,6 +105,23 @@ export class PatientConsultationPage { ).click(); } + visitFilesPage() { + cy.get("a").contains("Files").click(); + } + + uploadFile() { + cy.get("#file_upload_patient").selectFile( + "cypress/fixtures/sampleAsset.xlsx", + { force: true } + ); + } + + clickUploadFile() { + cy.intercept("POST", "**/api/v1/files/").as("uploadFile"); + cy.get("#upload_file_button").click(); + cy.wait("@uploadFile").its("response.statusCode").should("eq", 201); + } + waitForMediabaseStatusCode() { cy.wait("@getMediaBase").its("response.statusCode").should("eq", 200); } @@ -119,4 +146,119 @@ export class PatientConsultationPage { cy.get("[data-testid='return-to-patient-dashboard']").click(); cy.wait("@submitPrescription").its("response.statusCode").should("eq", 201); } + + visitEditConsultationPage() { + cy.get("#view_consulation_updates").click(); + cy.get("button").contains("Edit Consultation Details").click(); + } + + setSymptomsDate(date: string) { + cy.get("#symptoms_onset_date") + .click() + .then(() => { + cy.get("[placeholder='DD/MM/YYYY']").type(date); + }); + } + + updateConsultation() { + cy.intercept("PUT", "**/api/v1/consultation/**").as("updateConsultation"); + cy.get("#submit").contains("Update Consultation").click(); + cy.wait("@updateConsultation").its("response.statusCode").should("eq", 200); + } + + verifySuccessNotification(message: string) { + cy.verifyNotification(message); + } + + updateSymptoms(symptoms: string) { + this.selectSymptoms(symptoms); + cy.get("#symptoms").click(); + } + + visitShiftRequestPage() { + cy.get("#create_shift_request").click(); + } + + enterPatientShiftDetails( + name: string, + phone_number: string, + facilityName: string, + reason: string + ) { + cy.get("#refering_facility_contact_name").type(name); + cy.get("#refering_facility_contact_number").type(phone_number); + cy.get("input[name='assigned_facility']") + .type(facilityName) + .then(() => { + cy.get("[role='option']").first().click(); + }); + cy.get("#reason").type(reason); + } + + createShiftRequest() { + cy.intercept("POST", "**/api/v1/shift/").as("createShiftRequest"); + cy.get("#submit").click(); + cy.wait("@createShiftRequest").its("response.statusCode").should("eq", 201); + } + + visitDoctorNotesPage() { + cy.get("#patient_doctor_notes").click(); + } + + addDoctorsNotes(notes: string) { + cy.get("#doctor_notes_textarea").type(notes); + } + + postDoctorNotes() { + cy.intercept("POST", "**/api/v1/patient/*/notes").as("postDoctorNotes"); + cy.get("#submit").contains("Post Your Note").click(); + cy.wait("@postDoctorNotes").its("response.statusCode").should("eq", 201); + } + + clickDischargePatient() { + cy.get("#discharge_patient_from_care").click(); + } + + selectDischargeReason(reason: string) { + cy.get("#discharge_reason") + .click() + .then(() => { + cy.get("[role='option']").contains(reason).click(); + }); + } + + addDischargeNotes(notes: string) { + cy.get("#discharge_notes").type(notes); + } + + confirmDischarge() { + cy.intercept("POST", "**/api/v1/consultation/*/discharge_patient/").as( + "dischargePatient" + ); + cy.get("#submit").contains("Confirm Discharge").click(); + cy.wait("@dischargePatient").its("response.statusCode").should("eq", 200); + } + + discontinuePreviousPrescription() { + cy.intercept( + "POST", + "**/api/v1/consultation/*/prescriptions/*/discontinue/" + ).as("deletePrescription"); + cy.get("button").contains("Discontinue").click(); + cy.get("#submit").contains("Discontinue").click(); + cy.wait("@deletePrescription").its("response.statusCode").should("eq", 200); + } + + visitEditPrescriptionPage() { + cy.get("#consultation_tab_nav").contains("Medicines").click(); + cy.get("a[href='prescriptions']").first().click(); + } + + submitPrescription() { + cy.intercept("POST", "**/api/v1/consultation/*/prescriptions/").as( + "submitPrescription" + ); + cy.get("#submit").contains("Submit").click(); + cy.wait("@submitPrescription").its("response.statusCode").should("eq", 201); + } } diff --git a/cypress/pageobject/Patient/PatientCreation.ts b/cypress/pageobject/Patient/PatientCreation.ts index 4fcd43dc490..47bf9c913ad 100644 --- a/cypress/pageobject/Patient/PatientCreation.ts +++ b/cypress/pageobject/Patient/PatientCreation.ts @@ -10,6 +10,12 @@ export class PatientPage { cy.wait("@getFacilities").its("response.statusCode").should("eq", 200); } + visitPatient() { + cy.intercept("GET", "**/api/v1/consultation/**").as("getPatient"); + cy.get("[data-cy='patient']").first().click(); + cy.wait("@getPatient").its("response.statusCode").should("eq", 200); + } + selectFacility(facilityName: string) { cy.get("input[name='facilities']") .type(facilityName) diff --git a/src/Components/Facility/ConsultationCard.tsx b/src/Components/Facility/ConsultationCard.tsx index f6b4484b477..4859b981e8f 100644 --- a/src/Components/Facility/ConsultationCard.tsx +++ b/src/Components/Facility/ConsultationCard.tsx @@ -131,6 +131,7 @@ export const ConsultationCard = (props: ConsultationProps) => {
navigate( diff --git a/src/Components/Facility/ConsultationDetails/index.tsx b/src/Components/Facility/ConsultationDetails/index.tsx index 3d3271b16d5..d21200543c3 100644 --- a/src/Components/Facility/ConsultationDetails/index.tsx +++ b/src/Components/Facility/ConsultationDetails/index.tsx @@ -309,6 +309,7 @@ export const ConsultationDetails = (props: any) => { ) : ( navigate( `/facility/${patientData.facility}/patient/${patientData.id}/shift/new` @@ -352,6 +353,7 @@ export const ConsultationDetails = (props: any) => { Patient Details @@ -450,6 +452,7 @@ export const ConsultationDetails = (props: any) => { setOpenDischargeDialog(true)} disabled={!!consultationData.discharge_date} > @@ -496,7 +499,10 @@ export const ConsultationDetails = (props: any) => {
-