From b1222aac63fa878c59864b2b8441ef83f7758aee Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Sun, 10 Nov 2024 11:14:27 +0530 Subject: [PATCH 001/708] basic scheduling list and create (ui only) --- package-lock.json | 97 ++++++++++++ package.json | 3 + src/CAREUI/display/ColoredIndicator.tsx | 41 +++++ src/CAREUI/interactive/Calendar.tsx | 8 + src/CAREUI/interactive/CheckboxArray.tsx | 23 +++ src/CAREUI/interactive/WeekdayCheckbox.tsx | 37 +++++ src/Locale/en.json | 17 +++ src/Routers/AppRouter.tsx | 2 + src/components/Common/Sidebar/Sidebar.tsx | 1 + .../Schedule/ScheduleExceptionsList.tsx | 3 + .../Schedule/ScheduleTemplateForm.tsx | 136 +++++++++++++++++ .../Schedule/ScheduleTemplatesList.tsx | 114 ++++++++++++++ .../Schedule/SchedulingHomePage.tsx | 49 ++++++ src/components/Schedule/routes.tsx | 10 ++ src/components/ui/button.tsx | 2 + src/components/ui/checkbox.tsx | 28 ++++ src/components/ui/drawer.tsx | 116 ++++++++++++++ src/components/ui/sheet.tsx | 141 ++++++++++++++++++ src/hooks/useActiveLink.ts | 2 + 19 files changed, 830 insertions(+) create mode 100644 src/CAREUI/display/ColoredIndicator.tsx create mode 100644 src/CAREUI/interactive/Calendar.tsx create mode 100644 src/CAREUI/interactive/CheckboxArray.tsx create mode 100644 src/CAREUI/interactive/WeekdayCheckbox.tsx create mode 100644 src/components/Schedule/ScheduleExceptionsList.tsx create mode 100644 src/components/Schedule/ScheduleTemplateForm.tsx create mode 100644 src/components/Schedule/ScheduleTemplatesList.tsx create mode 100644 src/components/Schedule/SchedulingHomePage.tsx create mode 100644 src/components/Schedule/routes.tsx create mode 100644 src/components/ui/checkbox.tsx create mode 100644 src/components/ui/drawer.tsx create mode 100644 src/components/ui/sheet.tsx diff --git a/package-lock.json b/package-lock.json index 46a7ccfbd3b..fa9ab1972d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,8 @@ "@hello-pangea/dnd": "^17.0.0", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", + "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-slot": "^1.1.0", @@ -58,6 +60,7 @@ "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "use-keyboard-shortcut": "^1.1.6", + "vaul": "^1.1.1", "xlsx": "^0.18.5" }, "devDependencies": { @@ -3315,6 +3318,36 @@ } } }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.2.tgz", + "integrity": "sha512-/i0fl686zaJbDQLNKrkCbMyDm6FQMt4jg323k7HuqitoANm9sE23Ql8yOK3Wusk34HSLKDChhMux05FnP6KUkw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", @@ -3386,6 +3419,42 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz", + "integrity": "sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", @@ -3880,6 +3949,21 @@ } } }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz", + "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-rect": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", @@ -19446,6 +19530,19 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/vaul": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.1.tgz", + "integrity": "sha512-+ejzF6ffQKPcfgS7uOrGn017g39F8SO4yLPXbBhpC7a0H+oPqPna8f1BUfXaz8eU4+pxbQcmjxW+jWBSbxjaFg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0" + } + }, "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", diff --git a/package.json b/package.json index fbddcc91cbb..f8d8a8e9b17 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,8 @@ "@hello-pangea/dnd": "^17.0.0", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", + "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-slot": "^1.1.0", @@ -97,6 +99,7 @@ "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "use-keyboard-shortcut": "^1.1.6", + "vaul": "^1.1.1", "xlsx": "^0.18.5" }, "devDependencies": { diff --git a/src/CAREUI/display/ColoredIndicator.tsx b/src/CAREUI/display/ColoredIndicator.tsx new file mode 100644 index 00000000000..4c27dee1926 --- /dev/null +++ b/src/CAREUI/display/ColoredIndicator.tsx @@ -0,0 +1,41 @@ +import { cn } from "@/lib/utils"; + +const colorForID = (uuid: string, pallete: string[]) => { + let hash = 0; + for (let i = 0; i < uuid.length; i++) { + hash = uuid.charCodeAt(i) + ((hash << 5) - hash); + } + return pallete[Math.abs(hash) % pallete.length]; +}; + +interface Props { + id: string; + pallete?: string[]; + className?: string; +} + +/** + * Returns a div with a background color from the pallete based on specified `id`. + * Just to consistently get back the same color for a given id. + */ +export default function ColoredIndicator(props: Props) { + return ( +
+ ); +} diff --git a/src/CAREUI/interactive/Calendar.tsx b/src/CAREUI/interactive/Calendar.tsx new file mode 100644 index 00000000000..3d690b95e11 --- /dev/null +++ b/src/CAREUI/interactive/Calendar.tsx @@ -0,0 +1,8 @@ +interface Props { + className?: string; + initialMonth?: Date; +} + +export default function Calendar(props: Props) { + return
TODO: build a calendar!
; +} diff --git a/src/CAREUI/interactive/CheckboxArray.tsx b/src/CAREUI/interactive/CheckboxArray.tsx new file mode 100644 index 00000000000..7de091665c8 --- /dev/null +++ b/src/CAREUI/interactive/CheckboxArray.tsx @@ -0,0 +1,23 @@ +import { Checkbox } from "@/components/ui/checkbox"; + +interface Props { + value?: string[]; + onChange: (value: string[]) => void; + options: string[]; + optionLabel: (option: string) => string; +} + +export default function CheckboxArray(props: Props) { + return ( + + ); +} diff --git a/src/CAREUI/interactive/WeekdayCheckbox.tsx b/src/CAREUI/interactive/WeekdayCheckbox.tsx new file mode 100644 index 00000000000..b61f5876b40 --- /dev/null +++ b/src/CAREUI/interactive/WeekdayCheckbox.tsx @@ -0,0 +1,37 @@ +import { Checkbox } from "@/components/ui/checkbox"; + +const DAYS_OF_WEEK = { + MONDAY: 1, + TUESDAY: 2, + WEDNESDAY: 3, + THURSDAY: 4, + FRIDAY: 5, + SATURDAY: 6, + SUNDAY: 7, +} as const; + +export type DayOfWeekValue = (typeof DAYS_OF_WEEK)[keyof typeof DAYS_OF_WEEK]; + +interface Props { + value?: DayOfWeekValue[]; + onChange: (value: DayOfWeekValue[]) => void; +} + +export default function WeekdayCheckbox(props: Props) { + const selected = new Set(props.value ?? []); + return ( + + ); +} diff --git a/src/Locale/en.json b/src/Locale/en.json index 9a7f4d750c2..74da8867e7f 100644 --- a/src/Locale/en.json +++ b/src/Locale/en.json @@ -42,6 +42,20 @@ "CONSULTATION_TAB__UPDATES": "Overview", "CONSULTATION_TAB__VENTILATOR": "Ventilation", "Cancel": "Cancel", + "DAYS_OF_WEEK_SHORT__1": "Mon", + "DAYS_OF_WEEK_SHORT__2": "Tue", + "DAYS_OF_WEEK_SHORT__3": "Wed", + "DAYS_OF_WEEK_SHORT__4": "Thu", + "DAYS_OF_WEEK_SHORT__5": "Fri", + "DAYS_OF_WEEK_SHORT__6": "Sat", + "DAYS_OF_WEEK__1": "Monday", + "DAYS_OF_WEEK__2": "Tuesday", + "DAYS_OF_WEEK__3": "Wednesday", + "DAYS_OF_WEEK__4": "Thursday", + "DAYS_OF_WEEK__5": "Friday", + "DAYS_OF_WEEK__6": "Saturday", + "DAYS_OF_WEEK__7": "Sunday", + "DAYS_OF_WEE_SHORTK__7": "Sun", "DD/MM/YYYY": "DD/MM/YYYY", "DOCTORS_LOG": "Progress Note", "DOMESTIC_HEALTHCARE_SUPPORT__FAMILY_MEMBER": "Family member", @@ -628,6 +642,7 @@ "escape": "Escape", "estimated_contact_date": "Estimated contact date", "events": "Events", + "exceptions": "Exceptions", "expand_sidebar": "Expand Sidebar", "expected_burn_rate": "Expected Burn Rate", "expired_on": "Expired On", @@ -1083,6 +1098,8 @@ "save_and_continue": "Save and Continue", "save_investigation": "Save Investigation", "scan_asset_qr": "Scan Asset QR!", + "schedule": "Schedule", + "schedule_calendar": "Schedule Calendar", "search_by_username": "Search by username", "search_for_facility": "Search for Facility", "search_icd11_placeholder": "Search for ICD-11 Diagnoses", diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx index c8f6fbec83e..f80d8fb46f1 100644 --- a/src/Routers/AppRouter.tsx +++ b/src/Routers/AppRouter.tsx @@ -16,6 +16,7 @@ import Error404 from "@/components/ErrorPages/404"; import SessionExpired from "@/components/ErrorPages/SessionExpired"; import { NoticeBoard } from "@/components/Notifications/NoticeBoard"; import ShowPushNotification from "@/components/Notifications/ShowPushNotification"; +import ScheduleRoutes from "@/components/Schedule/routes"; import { usePluginRoutes } from "@/hooks/useCareApps"; @@ -55,6 +56,7 @@ const Routes: AppRoutes = { ...ResourceRoutes, ...SampleRoutes, ...ShiftingRoutes, + ...ScheduleRoutes, ...UserRoutes, "/notifications/:id": ({ id }) => , diff --git a/src/components/Common/Sidebar/Sidebar.tsx b/src/components/Common/Sidebar/Sidebar.tsx index 6612555a908..8b3fa55397b 100644 --- a/src/components/Common/Sidebar/Sidebar.tsx +++ b/src/components/Common/Sidebar/Sidebar.tsx @@ -57,6 +57,7 @@ const StatelessSidebar = ({ const { t } = useTranslation(); const BaseNavItems: INavItem[] = [ { text: t("facilities"), to: "/facility", icon: "l-hospital" }, + { text: t("schedule"), to: "/schedule", icon: "l-schedule" }, { text: t("patients"), to: "/patients", icon: "l-user-injured" }, { text: t("assets"), to: "/assets", icon: "l-shopping-cart-alt" }, { text: t("sample_test"), to: "/sample", icon: "l-medkit" }, diff --git a/src/components/Schedule/ScheduleExceptionsList.tsx b/src/components/Schedule/ScheduleExceptionsList.tsx new file mode 100644 index 00000000000..9e3e6069fb8 --- /dev/null +++ b/src/components/Schedule/ScheduleExceptionsList.tsx @@ -0,0 +1,3 @@ +export default function ScheduleExceptionsList() { + return ""; +} diff --git a/src/components/Schedule/ScheduleTemplateForm.tsx b/src/components/Schedule/ScheduleTemplateForm.tsx new file mode 100644 index 00000000000..d7778c04a67 --- /dev/null +++ b/src/components/Schedule/ScheduleTemplateForm.tsx @@ -0,0 +1,136 @@ +import CareIcon from "@/CAREUI/icons/CareIcon"; +import WeekdayCheckbox from "@/CAREUI/interactive/WeekdayCheckbox"; + +import { Button } from "@/components/ui/button"; + +import DateFormField from "@/components/Form/FormFields/DateFormField"; +import { FieldLabel } from "@/components/Form/FormFields/FormField"; +import { SelectFormField } from "@/components/Form/FormFields/SelectFormField"; +import TextFormField from "@/components/Form/FormFields/TextFormField"; + +export default function ScheduleTemplateForm() { + return ( +
+ {}} + /> +
+ {}} + /> + {}} + /> +
+
+ + Weekly Schedule + + + Select the weekdays for applying the{" "} + Regular OP Day template to + schedule appointments + + {}} /> + +
    +
  • + +
  • +
+ +
+ +
+
+
+ ); +} + +const ScheduleAvailabilityForm = () => { + return ( +
+
+
+ + Morning Consultations +
+ +
+ +
+ {}} + /> + o} + optionValue={(o) => o} + onChange={() => {}} + /> + {}} + /> + {}} + /> +
+
+ {}} + /> +
+
+ Info +
+ + Allocating 10 tokens in this schedule provides approximately 6 + minutes for each patient + +
+
+
+ ); +}; diff --git a/src/components/Schedule/ScheduleTemplatesList.tsx b/src/components/Schedule/ScheduleTemplatesList.tsx new file mode 100644 index 00000000000..f939b55491a --- /dev/null +++ b/src/components/Schedule/ScheduleTemplatesList.tsx @@ -0,0 +1,114 @@ +import ColoredIndicator from "@/CAREUI/display/ColoredIndicator"; + +import { Button } from "@/components/ui/button"; +import { + Sheet, + SheetClose, + SheetContent, + SheetFooter, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet"; + +import ScheduleTemplateForm from "@/components/Schedule/ScheduleTemplateForm"; + +export default function ScheduleTemplatesList() { + return ( +
+
+

Schedule Templates

+ + + + + + + Create Schedule Template + + +
+ +
+ + + + + + + + + + +
+
+
+
    +
  • + +
  • +
  • + +
  • +
+
+ ); +} + +const ScheduleTemplateItem = () => { + return ( +
+
+
+ +
+ Regular OP Day + Scheduled for Monday +
+
+
menu
+
+
+
    +
  • +
    +
    +
    + Morning Consultations +

    + Outpatient Schedule + | + 5 slots (20 mins.) +

    +
    + 09:00 AM - 12:00 PM +
    +
    +
  • +
  • +
    +
    +
    + Morning Consultations +

    + Outpatient Schedule + | + 5 slots (20 mins.) +

    +
    + 09:00 AM - 12:00 PM +
    +
    +
  • +
+ + Valid from 01 Nov 2024 till{" "} + 31 Jan 2025 + +
+
+ ); +}; diff --git a/src/components/Schedule/SchedulingHomePage.tsx b/src/components/Schedule/SchedulingHomePage.tsx new file mode 100644 index 00000000000..cf7450f75ab --- /dev/null +++ b/src/components/Schedule/SchedulingHomePage.tsx @@ -0,0 +1,49 @@ +import { Link } from "raviger"; +import { useTranslation } from "react-i18next"; + +import { cn } from "@/lib/utils"; + +import Calendar from "@/CAREUI/interactive/Calendar"; + +import Page from "@/components/Common/Page"; +import ScheduleExceptionsList from "@/components/Schedule/ScheduleExceptionsList"; +import ScheduleTemplatesList from "@/components/Schedule/ScheduleTemplatesList"; + +interface Props { + view: "schedule" | "exceptions"; +} + +export default function SchedulingHomePage(props: Props) { + const { t } = useTranslation(); + + return ( + + + +
+ + + {props.view === "schedule" && } + {props.view === "exceptions" && } +
+
+ ); +} diff --git a/src/components/Schedule/routes.tsx b/src/components/Schedule/routes.tsx new file mode 100644 index 00000000000..915eb112987 --- /dev/null +++ b/src/components/Schedule/routes.tsx @@ -0,0 +1,10 @@ +import SchedulingHomePage from "@/components/Schedule/SchedulingHomePage"; + +import { AppRoutes } from "@/Routers/AppRouter"; + +const ScheduleRoutes: AppRoutes = { + "/schedule": () => , + "/exceptions": () => , +}; + +export default ScheduleRoutes; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index f49a7052a78..4247426fb8c 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -11,6 +11,8 @@ const buttonVariants = cva( variant: { default: "bg-gray-900 text-gray-50 shadow hover:bg-gray-900/90 dark:bg-gray-50 dark:text-gray-900 dark:hover:bg-gray-50/90", + primary: + "bg-primary-700 text-white shadow hover:bg-primary-700/90 dark:bg-primary-100 dark:text-primary-900 dark:hover:bg-primary-100/90", destructive: "bg-red-500 text-gray-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-gray-50 dark:hover:bg-red-900/90", outline: diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 00000000000..12b7c37f660 --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,28 @@ +import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import { CheckIcon } from "@radix-ui/react-icons"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)); +Checkbox.displayName = CheckboxPrimitive.Root.displayName; + +export { Checkbox }; diff --git a/src/components/ui/drawer.tsx b/src/components/ui/drawer.tsx new file mode 100644 index 00000000000..fed2b82d662 --- /dev/null +++ b/src/components/ui/drawer.tsx @@ -0,0 +1,116 @@ +import * as React from "react"; +import { Drawer as DrawerPrimitive } from "vaul"; + +import { cn } from "@/lib/utils"; + +const Drawer = ({ + shouldScaleBackground = true, + ...props +}: React.ComponentProps) => ( + +); +Drawer.displayName = "Drawer"; + +const DrawerTrigger = DrawerPrimitive.Trigger; + +const DrawerPortal = DrawerPrimitive.Portal; + +const DrawerClose = DrawerPrimitive.Close; + +const DrawerOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName; + +const DrawerContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + +
+ {children} + + +)); +DrawerContent.displayName = "DrawerContent"; + +const DrawerHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DrawerHeader.displayName = "DrawerHeader"; + +const DrawerFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DrawerFooter.displayName = "DrawerFooter"; + +const DrawerTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DrawerTitle.displayName = DrawerPrimitive.Title.displayName; + +const DrawerDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DrawerDescription.displayName = DrawerPrimitive.Description.displayName; + +export { + Drawer, + DrawerPortal, + DrawerOverlay, + DrawerTrigger, + DrawerClose, + DrawerContent, + DrawerHeader, + DrawerFooter, + DrawerTitle, + DrawerDescription, +}; diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx new file mode 100644 index 00000000000..6260a630611 --- /dev/null +++ b/src/components/ui/sheet.tsx @@ -0,0 +1,141 @@ +import * as SheetPrimitive from "@radix-ui/react-dialog"; +import { Cross2Icon } from "@radix-ui/react-icons"; +import { type VariantProps, cva } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Sheet = SheetPrimitive.Root; + +const SheetTrigger = SheetPrimitive.Trigger; + +const SheetClose = SheetPrimitive.Close; + +const SheetPortal = SheetPrimitive.Portal; + +const SheetOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; + +const sheetVariants = cva( + "fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out dark:bg-gray-950", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + right: + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + }, + }, + defaultVariants: { + side: "right", + }, + }, +); + +interface SheetContentProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const SheetContent = React.forwardRef< + React.ElementRef, + SheetContentProps +>(({ side = "right", className, children, ...props }, ref) => ( + + + + + + Close + + {children} + + +)); +SheetContent.displayName = SheetPrimitive.Content.displayName; + +const SheetHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +SheetHeader.displayName = "SheetHeader"; + +const SheetFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +SheetFooter.displayName = "SheetFooter"; + +const SheetTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SheetTitle.displayName = SheetPrimitive.Title.displayName; + +const SheetDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SheetDescription.displayName = SheetPrimitive.Description.displayName; + +export { + Sheet, + SheetPortal, + SheetOverlay, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +}; diff --git a/src/hooks/useActiveLink.ts b/src/hooks/useActiveLink.ts index b52b15600dd..188db8769c7 100644 --- a/src/hooks/useActiveLink.ts +++ b/src/hooks/useActiveLink.ts @@ -18,6 +18,8 @@ const activeLinkPriority = { "/users": "/users", "/notice_board": "/notice_board", "/facility": "/facility", + "/schedule": "/schedule", + "/exceptions": "/schedule", }; /** From 1637e005acb587f8d14ed8161beb6ebac2d082fa Mon Sep 17 00:00:00 2001 From: Gigin George Date: Sun, 10 Nov 2024 10:50:40 +0000 Subject: [PATCH 002/708] WIP Microfrontends; Add few shadcn components --- .cursorrules | 40 ++++- package-lock.json | 157 ++++++++++++++--- package.json | 5 +- src/Locale/en.json | 3 + src/Redux/api.tsx | 30 ++++ src/Routers/AppRouter.tsx | 18 +- src/Utils/AuthorizeFor.tsx | 4 +- src/components/Common/Sidebar/Sidebar.tsx | 1 + .../{404.tsx => DefaultErrorPage.tsx} | 33 +++- .../Facility/ConsultationDetails/index.tsx | 4 +- src/components/Patient/PatientRegister.tsx | 4 +- src/components/ui/alert-dialog.tsx | 139 +++++++++++++++ src/components/ui/card.tsx | 83 +++++++++ src/components/ui/input.tsx | 25 +++ src/components/ui/label.tsx | 24 +++ src/components/ui/table.tsx | 120 +++++++++++++ src/components/ui/textarea.tsx | 24 +++ src/pages/Apps/PlugConfigEdit.tsx | 158 ++++++++++++++++++ src/pages/Apps/PlugConfigList.tsx | 59 +++++++ src/types/plugConfig.ts | 4 + vite.config.mts | 8 + 21 files changed, 898 insertions(+), 45 deletions(-) rename src/components/ErrorPages/{404.tsx => DefaultErrorPage.tsx} (51%) create mode 100644 src/components/ui/alert-dialog.tsx create mode 100644 src/components/ui/card.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/components/ui/table.tsx create mode 100644 src/components/ui/textarea.tsx create mode 100644 src/pages/Apps/PlugConfigEdit.tsx create mode 100644 src/pages/Apps/PlugConfigList.tsx create mode 100644 src/types/plugConfig.ts diff --git a/.cursorrules b/.cursorrules index 2eed3afe459..909a90e941d 100644 --- a/.cursorrules +++ b/.cursorrules @@ -1,18 +1,48 @@ Care is a React Typescript Project, built with Vite and styled with TailwindCSS. -Care uses a Plugin Architecture. Apps are installed in /apps. +Pages are defined in /src/pages. -Care uses a custom useQuery hook to fetch data from the API. APIs are defined in the api.tsx file +The React Components for the pages are defined in /src/components. Care primarily uses shadcn/ui components. -Here's an example of how to use the useQuery hook to fetch data from the API: +Routing for the React Pages are defined in /src/Routers/AppRouter.tsx using `raviger`. The AppRouter has a Routes object that maps paths to React Components. + +For Icons, Care uses an implementation of Unicons which can be used like this: + +``` +import CareIcon from "@/CAREUI/icons/CareIcon"; + + +``` + +The main routes are accessible from the BaseNavItems defined within the StatelessSidebar component in /src/components/Common/Sidebar/Sidebar.tsx. + +Care uses a custom useQuery hook to fetch data from the API. API routes are defined in the api.tsx file like: ``` -useQuery from "@/common/hooks/useQuery"; +routes = { + createScribe: { + path: "/api/care_scribe/scribe/", + method: "POST", + TReq: Type(), + TRes: Type(), + }, + ...otherRoutes +} +``` + +Here's an example of how to use the useQuery hook to fetch data from the API + +``` +import useQuery from "@/Utils/request/useQuery"; const { data, loading, error } = useQuery(routes.getFacilityUsers, { facility_id: "1", }); +``` + +Here's an example of how to make a request to the API -request from "@/common/utils/request"; +``` +import request from "@/Utils/request/request"; const { res } = await request(routes.partialUpdateAsset, { pathParams: { external_id: assetId }, body: data, diff --git a/package-lock.json b/package-lock.json index a4276cb3d35..c76af19ed00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,8 +19,10 @@ "@hello-pangea/dnd": "^17.0.0", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", + "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.3", @@ -61,6 +63,7 @@ "xlsx": "^0.18.5" }, "devDependencies": { + "@originjs/vite-plugin-federation": "^1.3.6", "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "^0.5.13", @@ -480,11 +483,10 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", - "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -1889,8 +1891,7 @@ "node_modules/@bufbuild/protobuf": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz", - "integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==", - "license": "(Apache-2.0 AND BSD-3-Clause)" + "integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==" }, "node_modules/@colors/colors": { "version": "1.5.0", @@ -2796,7 +2797,6 @@ "version": "0.11.10", "resolved": "https://registry.npmjs.org/@livekit/components-core/-/components-core-0.11.10.tgz", "integrity": "sha512-PvFlKq1W64b9GfFjG7L4/o7ulAl5yFFpDTvG+JHQiXkaPaecMPt/qPbs6zdvUlC7om1TGMuW/pIN7o585Xz9Fg==", - "license": "Apache-2.0", "dependencies": { "@floating-ui/dom": "1.6.11", "loglevel": "1.9.1", @@ -2814,7 +2814,6 @@ "version": "2.6.7", "resolved": "https://registry.npmjs.org/@livekit/components-react/-/components-react-2.6.7.tgz", "integrity": "sha512-z8dgrBrRXIe7oagwFyjehdwL/4zpySJyPdAjeMDXZVbTXYNAugb3a88Ws9yQz4PZFECLkIPXJCN3C3YR+bgh5Q==", - "license": "Apache-2.0", "dependencies": { "@livekit/components-core": "0.11.10", "clsx": "2.1.1", @@ -2840,7 +2839,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/@livekit/components-styles/-/components-styles-1.1.4.tgz", "integrity": "sha512-QCupn7tQ/dy/WZclrfsgtDe8peiGYS6Ied1IGkKOysaXo04l90t62SIUTKyxgd0dNDhUDC0p34qCggGZs/44lQ==", - "license": "Apache-2.0", "engines": { "node": ">=18" } @@ -2848,14 +2846,12 @@ "node_modules/@livekit/mutex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@livekit/mutex/-/mutex-1.0.0.tgz", - "integrity": "sha512-aiUhoThBNF9UyGTxEURFzJLhhPLIVTnQiEVMjRhPnfHNKLfo2JY9xovHKIus7B78UD5hsP6DlgpmAsjrz4U0Iw==", - "license": "Apache-2.0" + "integrity": "sha512-aiUhoThBNF9UyGTxEURFzJLhhPLIVTnQiEVMjRhPnfHNKLfo2JY9xovHKIus7B78UD5hsP6DlgpmAsjrz4U0Iw==" }, "node_modules/@livekit/protocol": { "version": "1.24.0", "resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.24.0.tgz", "integrity": "sha512-9dCsqnkMn7lvbI4NGh18zhLDsrXyUcpS++TEFgEk5Xv1WM3R2kT3EzqgL1P/mr3jaabM6rJ8wZA/KJLuQNpF5w==", - "license": "Apache-2.0", "dependencies": { "@bufbuild/protobuf": "^1.10.0" } @@ -3268,6 +3264,41 @@ "node": "^16.13.0 || >=18.0.0" } }, + "node_modules/@originjs/vite-plugin-federation": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@originjs/vite-plugin-federation/-/vite-plugin-federation-1.3.6.tgz", + "integrity": "sha512-tHLMjdMJFPFMSJrUuJJiv8l7OFRvM19E9O1B9dhbk+04i3RnYwE9A6oNtSUM1dnvkalzCLwZIuMpti28/tnh8g==", + "dev": true, + "dependencies": { + "estree-walker": "^3.0.2", + "magic-string": "^0.27.0" + }, + "engines": { + "node": ">=14.0.0", + "pnpm": ">=7.0.1" + } + }, + "node_modules/@originjs/vite-plugin-federation/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@originjs/vite-plugin-federation/node_modules/magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3312,6 +3343,33 @@ "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", "license": "MIT" }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.2.tgz", + "integrity": "sha512-eGSlLzPhKO+TErxkiGcCZGuvbVMnLA1MTnyBksGOeGRGkxHiiJUujsjmNTdWTm4iHVSRaUao9/4Ur671auMghQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dialog": "1.1.2", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", @@ -3406,6 +3464,41 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz", + "integrity": "sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", @@ -3544,6 +3637,28 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz", + "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-menu": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.2.tgz", @@ -3752,7 +3867,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", - "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.0" }, @@ -11704,10 +11818,9 @@ } }, "node_modules/livekit-client": { - "version": "2.5.10", - "resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.5.10.tgz", - "integrity": "sha512-H7EeIb19LAH8ejlvhh0JWtWkvXDan6Yf3bpFGlDMb54uPmyRgBY+McfgQsFgJCB9WJL0X+GYUoV1Cmnn8iAoIQ==", - "license": "Apache-2.0", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.6.0.tgz", + "integrity": "sha512-hpxNBtyWIFCefoHjHoSjqPCw3m7AfSJVcVZw6rMsqds4u+dSpWLfYkglWP8JuPGUIssyOsZm/+bV3gBWfuOGGQ==", "dependencies": { "@livekit/mutex": "1.0.0", "@livekit/protocol": "1.24.0", @@ -11723,8 +11836,7 @@ "node_modules/livekit-client/node_modules/tslib": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "license": "0BSD" + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, "node_modules/load-plugin": { "version": "6.0.3", @@ -11935,7 +12047,6 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", - "license": "MIT", "engines": { "node": ">= 0.6.0" }, @@ -17203,7 +17314,6 @@ "version": "2.14.2", "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.2.tgz", "integrity": "sha512-icY6jVao7MfKCieyo1AyxFYm1baiM+fA00qW/KrNNVlkxHAd34riEKuEkUe4bBb3gJwLJZM+xT60Yj1QL8rHiA==", - "license": "MIT", "bin": { "sdp-verify": "checker.js" } @@ -18536,8 +18646,7 @@ "node_modules/ts-debounce": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/ts-debounce/-/ts-debounce-4.0.0.tgz", - "integrity": "sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==", - "license": "MIT" + "integrity": "sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==" }, "node_modules/ts-interface-checker": { "version": "0.1.13", @@ -18723,7 +18832,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz", "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==", - "license": "MIT", "optionalDependencies": { "rxjs": "*" } @@ -19309,7 +19417,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.0.tgz", "integrity": "sha512-bBIa7yUyPhE1BCc0GmR96VU/15l/9gP1Ch5mYdLcFBaFGQsdmXkvjV0TtOqW1yUd6VjIwDunm+flSciCQXujiw==", - "license": "MIT", "dependencies": { "lodash.debounce": "^4.0.8" }, diff --git a/package.json b/package.json index e779081dc4b..86e87b05f61 100644 --- a/package.json +++ b/package.json @@ -58,8 +58,10 @@ "@hello-pangea/dnd": "^17.0.0", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", + "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.3", @@ -100,6 +102,7 @@ "xlsx": "^0.18.5" }, "devDependencies": { + "@originjs/vite-plugin-federation": "^1.3.6", "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "^0.5.13", @@ -168,4 +171,4 @@ "node": ">=20.12.0" }, "packageManager": "npm@10.5.0" -} \ No newline at end of file +} diff --git a/src/Locale/en.json b/src/Locale/en.json index 784dd9d3905..7a7f17de979 100644 --- a/src/Locale/en.json +++ b/src/Locale/en.json @@ -294,6 +294,7 @@ "any_id": "Enter any ID linked with your ABHA number", "any_id_description": "Currently we support: Aadhaar Number / Mobile Number", "any_other_comments": "Any other comments", + "app_settings": "App Settings", "apply": "Apply", "approved_by_district_covid_control_room": "Approved by District COVID Control Room", "approving_facility": "Name of Approving Facility", @@ -484,6 +485,7 @@ "continue_watching": "Continue watching", "contribute_github": "Contribute on Github", "copied_to_clipboard": "Copied to clipboard", + "could_not_load_page": "We are facing some difficulties showing the Page you were looking for. Our Engineers have been notified and we'll make sure that this is resolved on the fly!", "countries_travelled": "Countries travelled", "covid_19_cat_gov": "Covid_19 Clinical Category as per Govt. of Kerala guideline (A/B/C)", "covid_19_death_reporting_form_1": "Covid-19 Death Reporting : Form 1", @@ -915,6 +917,7 @@ "otp_verification_success": "OTP has been verified successfully.", "out_of_range_error": "Value must be between {{ start }} and {{ end }}.", "oxygen_information": "Oxygen Information", + "page_load_error": "Couldn't Load the Page", "page_not_found": "Page Not Found", "pain": "Pain", "pain_chart_description": "Mark region and intensity of pain", diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 22f0285d22d..1a76af39f9a 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -105,6 +105,7 @@ import { } from "@/components/ABDM/types/health-facility"; import { PMJAYPackageItem } from "@/components/Common/PMJAYProcedurePackageAutocomplete"; import { InsurerOptionModel } from "@/components/HCX/InsurerAutocomplete"; +import { PlugConfig } from "@/types/plugConfig"; /** * A fake function that returns an empty object casted to type T @@ -1807,6 +1808,35 @@ const routes = { }, }, }, + plugConfig: { + listPlugConfigs: { + path: "/api/v1/plug_config/", + method: "GET", + TRes: Type<{ configs: PlugConfig[] }>(), + }, + getPlugConfig: { + path: "/api/v1/plug_config/{slug}/", + method: "GET", + TRes: Type(), + }, + createPlugConfig: { + path: "/api/v1/plug_config/", + method: "POST", + TReq: Type(), + TRes: Type(), + }, + updatePlugConfig: { + path: "/api/v1/plug_config/{slug}/", + method: "PATCH", + TReq: Type(), + TRes: Type(), + }, + deletePlugConfig: { + path: "/api/v1/plug_config/{slug}/", + method: "DELETE", + TRes: Type>(), + }, + }, } as const; export default routes; diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx index 5a7b2cbb312..cd7edcea6a4 100644 --- a/src/Routers/AppRouter.tsx +++ b/src/Routers/AppRouter.tsx @@ -3,7 +3,7 @@ import { useState, useEffect } from "react"; import ShowPushNotification from "@/components/Notifications/ShowPushNotification"; import { NoticeBoard } from "@/components/Notifications/NoticeBoard"; -import Error404 from "@/components/ErrorPages/404"; +import ErrorPage from "@/components/ErrorPages/DefaultErrorPage"; import { DesktopSidebar, MobileSidebar, @@ -27,6 +27,9 @@ import ResourceRoutes from "./routes/ResourceRoutes"; import { usePluginRoutes } from "@/common/hooks/useCareApps"; import careConfig from "@careConfig"; import IconIndex from "../CAREUI/icons/Index"; +import { PlugConfigList } from "@/pages/Apps/PlugConfigList"; +import { PlugConfigEdit } from "@/pages/Apps/PlugConfigEdit"; +import ErrorBoundary from "@/components/Common/ErrorBoundary"; export type RouteParams = T extends `${string}:${infer Param}/${infer Rest}` @@ -66,11 +69,14 @@ const Routes: AppRoutes = { ), "/session-expired": () => , - "/not-found": () => , + "/not-found": () => , "/icons": () => , // Only include the icon route in development environment ...(import.meta.env.PROD ? { "/icons": () => } : {}), + + "/apps": () => , + "/apps/plug-configs/:slug": ({ slug }) => , }; export default function AppRouter() { @@ -90,7 +96,7 @@ export default function AppRouter() { ...pluginRoutes, }; - const pages = useRoutes(routes) || ; + const pages = useRoutes(routes) || ; const path = usePath(); const [sidebarOpen, setSidebarOpen] = useState(false); @@ -170,7 +176,11 @@ export default function AppRouter() { className="flex-1 overflow-y-scroll bg-gray-100 pb-4 focus:outline-none md:py-0" >
- {pages} + } + > + {pages} +
diff --git a/src/Utils/AuthorizeFor.tsx b/src/Utils/AuthorizeFor.tsx index 6e1e048ee4e..b3d63e08bff 100644 --- a/src/Utils/AuthorizeFor.tsx +++ b/src/Utils/AuthorizeFor.tsx @@ -1,7 +1,7 @@ import { UserRole } from "@/common/constants"; import React from "react"; import useAuthUser from "@/common/hooks/useAuthUser"; -import Error404 from "@/components/ErrorPages/404"; +import ErrorPage from "@/components/ErrorPages/DefaultErrorPage"; export type AuthorizedForCB = (userType: UserRole) => boolean; @@ -44,6 +44,6 @@ export const AuthorizeUserRoute: React.FC = ({ if (userTypes.includes(authUser.user_type)) { return <>{children}; } else { - return ; + return ; } }; diff --git a/src/components/Common/Sidebar/Sidebar.tsx b/src/components/Common/Sidebar/Sidebar.tsx index 2b117562fc9..16b6df97585 100644 --- a/src/components/Common/Sidebar/Sidebar.tsx +++ b/src/components/Common/Sidebar/Sidebar.tsx @@ -56,6 +56,7 @@ const StatelessSidebar = ({ { text: t("resource"), to: "/resource", icon: "l-heart-medical" }, { text: t("users"), to: "/users", icon: "l-users-alt" }, { text: t("notice_board"), to: "/notice_board", icon: "l-meeting-board" }, + { text: t("app_settings"), to: "/apps", icon: "l-setting" }, ]; const PluginNavItems = useCareAppNavItems(); diff --git a/src/components/ErrorPages/404.tsx b/src/components/ErrorPages/DefaultErrorPage.tsx similarity index 51% rename from src/components/ErrorPages/404.tsx rename to src/components/ErrorPages/DefaultErrorPage.tsx index ebb6b5cb789..7b9f7861cad 100644 --- a/src/components/ErrorPages/404.tsx +++ b/src/components/ErrorPages/DefaultErrorPage.tsx @@ -3,18 +3,43 @@ import * as Notification from "../../Utils/Notifications"; import { useEffect } from "react"; import { useTranslation } from "react-i18next"; -export default function Error404() { +type ErrorType = "PAGE_NOT_FOUND" | "PAGE_LOAD_ERROR"; + +interface ErrorPageProps { + forError?: ErrorType; +} + +export default function ErrorPage({ + forError = "PAGE_NOT_FOUND", +}: ErrorPageProps) { const { t } = useTranslation(); + useEffect(() => { Notification.closeAllNotifications(); }, []); + + const errorContent = { + PAGE_NOT_FOUND: { + image: "/images/404.svg", + title: t("page_not_found"), + message: t("404_message"), + }, + PAGE_LOAD_ERROR: { + image: "/images/404.svg", + title: t("page_load_error"), + message: t("could_not_load_page"), + }, + }; + + const { image, title, message } = errorContent[forError]; + return (
- {t("error_404")} -

{t("page_not_found")}

+ {title} +

{title}

- {t("404_message")} + {message}

{ }; if (!tab) { - return ; + return ; } const SelectedTab = TABS[tab]; diff --git a/src/components/Patient/PatientRegister.tsx b/src/components/Patient/PatientRegister.tsx index 6aab7799772..96286f19916 100644 --- a/src/components/Patient/PatientRegister.tsx +++ b/src/components/Patient/PatientRegister.tsx @@ -44,7 +44,7 @@ import ConfirmDialog from "@/components/Common/ConfirmDialog"; import DateFormField from "../Form/FormFields/DateFormField"; import DialogModal from "@/components/Common/Dialog"; import DuplicatePatientDialog from "../Facility/DuplicatePatientDialog"; -import Error404 from "../ErrorPages/404"; +import ErrorPage from "../ErrorPages/DefaultErrorPage"; import Form from "../Form/Form"; import { HCXPolicyModel } from "../HCX/models"; import HCXPolicyValidator from "../HCX/validators"; @@ -929,7 +929,7 @@ export const PatientRegister = (props: PatientRegisterProps) => { }; if (!isLoading && facilityId && facilityObject && !PatientRegisterAuth()) { - return ; + return ; } return ( diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx new file mode 100644 index 00000000000..d736932ea06 --- /dev/null +++ b/src/components/ui/alert-dialog.tsx @@ -0,0 +1,139 @@ +import * as React from "react"; +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; + +import { cn } from "@/lib/utils"; +import { buttonVariants } from "@/components/ui/button"; + +const AlertDialog = AlertDialogPrimitive.Root; + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger; + +const AlertDialogPortal = AlertDialogPrimitive.Portal; + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)); +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +

+); +AlertDialogHeader.displayName = "AlertDialogHeader"; + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +AlertDialogFooter.displayName = "AlertDialogFooter"; + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName; + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +}; diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 00000000000..5bd1b4bfdf4 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,83 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +Card.displayName = "Card"; + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardHeader.displayName = "CardHeader"; + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardTitle.displayName = "CardTitle"; + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardDescription.displayName = "CardDescription"; + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); +CardContent.displayName = "CardContent"; + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +CardFooter.displayName = "CardFooter"; + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 00000000000..2de761f037d --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ); + }, +); +Input.displayName = "Input"; + +export { Input }; diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx new file mode 100644 index 00000000000..44912aff543 --- /dev/null +++ b/src/components/ui/label.tsx @@ -0,0 +1,24 @@ +import * as React from "react"; +import * as LabelPrimitive from "@radix-ui/react-label"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", +); + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)); +Label.displayName = LabelPrimitive.Root.displayName; + +export { Label }; diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx new file mode 100644 index 00000000000..db72e0ed963 --- /dev/null +++ b/src/components/ui/table.tsx @@ -0,0 +1,120 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)); +Table.displayName = "Table"; + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableHeader.displayName = "TableHeader"; + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableBody.displayName = "TableBody"; + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className, + )} + {...props} + /> +)); +TableFooter.displayName = "TableFooter"; + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableRow.displayName = "TableRow"; + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( + - - - - - - ); -}; diff --git a/src/components/Medicine/models.ts b/src/components/Medicine/models.ts index bd98853e68b..e727e6957b8 100644 --- a/src/components/Medicine/models.ts +++ b/src/components/Medicine/models.ts @@ -1,4 +1,3 @@ -import { PRESCRIPTION_ROUTES } from "@/components/Medicine/CreatePrescriptionForm"; import { UserBareMinimum } from "@/components/Users/models"; export const DOSAGE_UNITS = [ @@ -19,7 +18,7 @@ interface BasePrescription { medicine?: string; medicine_object?: MedibaseMedicine; medicine_old?: string; - route?: (typeof PRESCRIPTION_ROUTES)[number]; + route?: string; dosage_type?: "REGULAR" | "TITRATED" | "PRN"; base_dosage?: DosageValue; target_dosage?: DosageValue; diff --git a/src/components/Notifications/NoticeBoard.tsx b/src/components/Notifications/NoticeBoard.tsx deleted file mode 100644 index 4097ae030c4..00000000000 --- a/src/components/Notifications/NoticeBoard.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import Loading from "@/components/Common/Loading"; -import Page from "@/components/Common/Page"; - -import routes from "@/Utils/request/api"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { formatDateTime, formatName } from "@/Utils/utils"; - -export const NoticeBoard = () => { - const { t } = useTranslation(); - const { data, loading } = useTanStackQueryInstead(routes.getNotifications, { - query: { offset: 0, event: "MESSAGE", medium_sent: "SYSTEM" }, - }); - - let notices; - - if (data?.results.length) { - notices = ( -
- {data.results.map((item) => ( -
-
-
- {item.message} -
-
- {formatName(item.caused_by)} -{" "} - - {item.caused_by.user_type} - -
-
- {t("on")}: {formatDateTime(item.created_date)} -
-
-
- ))} -
- ); - } else { - notices = ( -
-
- -
- No Notice Available -
-
-
- ); - } - - if (loading) return ; - return ( - -
{notices}
-
- ); -}; diff --git a/src/components/Notifications/ShowPushNotification.tsx b/src/components/Notifications/ShowPushNotification.tsx deleted file mode 100644 index 0b79624a550..00000000000 --- a/src/components/Notifications/ShowPushNotification.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { NotificationData } from "@/components/Notifications/models"; - -import routes from "@/Utils/request/api"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; - -export default function ShowPushNotification({ id }: { id: string }) { - useTanStackQueryInstead(routes.getNotificationData, { - pathParams: { id }, - onResponse(res) { - if (res.data) { - window.location.href = resultUrl(res.data); - } - }, - }); - - const resultUrl = ({ caused_objects, event }: NotificationData) => { - switch (event) { - case "PATIENT_CREATED": - return `/facility/${caused_objects?.facility}/patient/${caused_objects?.patient}`; - case "PATIENT_UPDATED": - return `/facility/${caused_objects?.facility}/patient/${caused_objects?.patient}`; - case "PATIENT_CONSULTATION_CREATED": - return `/facility/${caused_objects?.facility}/patient/${caused_objects?.patient}/consultation/${caused_objects?.consultation}`; - case "PATIENT_CONSULTATION_UPDATED": - return `/facility/${caused_objects?.facility}/patient/${caused_objects?.patient}/consultation/${caused_objects?.consultation}`; - case "PATIENT_CONSULTATION_UPDATE_CREATED": - return `/facility/${caused_objects?.facility}/patient/${caused_objects?.patient}/consultation/${caused_objects?.consultation}/log_updates/${caused_objects?.daily_round}`; - case "PATIENT_CONSULTATION_UPDATE_UPDATED": - return `/facility/${caused_objects?.facility}/patient/${caused_objects?.patient}/consultation/${caused_objects?.consultation}/log_updates/${caused_objects?.daily_round}`; - case "INVESTIGATION_SESSION_CREATED": - return `/facility/${caused_objects?.facility}/patient/${caused_objects?.patient}/consultation/${caused_objects?.consultation}/investigation/${caused_objects?.session}`; - case "PATIENT_NOTE_ADDED": - return `/facility/${caused_objects.facility}/patient/${caused_objects.patient}/notes`; - case "MESSAGE": - return "/notice_board/"; - default: - return "#"; - } - }; - - return <>; -} diff --git a/src/components/Patient/DailyRoundListDetails.tsx b/src/components/Patient/DailyRoundListDetails.tsx deleted file mode 100644 index 66ca76c9a44..00000000000 --- a/src/components/Patient/DailyRoundListDetails.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import { Link } from "raviger"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import { Button } from "@/components/ui/button"; - -import Loading from "@/components/Common/Loading"; -import Page from "@/components/Common/Page"; -import { DailyRoundsModel } from "@/components/Patient/models"; - -import routes from "@/Utils/request/api"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { formatDateTime } from "@/Utils/utils"; - -export const DailyRoundListDetails = (props: any) => { - const { t } = useTranslation(); - const { facilityId, patientId, consultationId, id } = props; - const [dailyRoundListDetailsData, setDailyRoundListDetails] = - useState({}); - - const { loading: isLoading } = useTanStackQueryInstead( - routes.getDailyReport, - { - pathParams: { consultationId, id }, - onResponse: ({ data }) => { - if (data) { - setDailyRoundListDetails(data); - } - }, - }, - ); - - if (isLoading) { - return ; - } - - return ( - -
-
-
-
- - Patient Category:{" "} - - {dailyRoundListDetailsData.patient_category ?? "-"} -
-
- -
-
- -
-
-
- -
-
- Temperature: - {dailyRoundListDetailsData.temperature ?? "-"} -
-
- Taken at: - {dailyRoundListDetailsData.taken_at - ? formatDateTime(dailyRoundListDetailsData.taken_at) - : "-"} -
-
- SpO2: - {dailyRoundListDetailsData.ventilator_spo2 ?? "-"} -
-
- - Admitted To *:{" "} - - {dailyRoundListDetailsData.admitted_to ?? "-"} -
-
- - Physical Examination Info:{" "} - - {dailyRoundListDetailsData.physical_examination_info ?? "-"} -
-
- - Other Details:{" "} - - {dailyRoundListDetailsData.other_details ?? "-"} -
-
- Pulse(bpm): - {dailyRoundListDetailsData.pulse ?? "-"} -
-
- BP -
-
- - Systolic:{" "} - - {dailyRoundListDetailsData.bp?.systolic ?? "-"} -
-
- {" "} - - Diastolic: - - {dailyRoundListDetailsData.bp?.diastolic ?? "-"} -
-
-
- -
- - Respiratory Rate (bpm): - - - {dailyRoundListDetailsData.resp ?? "-"} -
-
- Rhythm: - {dailyRoundListDetailsData.rhythm ?? "-"} -
-
- - Rhythm Description:{" "} - - {dailyRoundListDetailsData.rhythm_detail ?? "-"} -
-
- - Level Of Consciousness:{" "} - - {(dailyRoundListDetailsData.consciousness_level && - t( - `CONSCIOUSNESS_LEVEL__${dailyRoundListDetailsData.consciousness_level}`, - )) || - "-"} -
-
- - Recommend Discharge:{" "} - - {dailyRoundListDetailsData.recommend_discharge ? ( - Yes - ) : ( - No - )} -
-
-
-
- ); -}; diff --git a/src/components/Patient/DiagnosesFilter.tsx b/src/components/Patient/DiagnosesFilter.tsx deleted file mode 100644 index 0258e96c5ba..00000000000 --- a/src/components/Patient/DiagnosesFilter.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { ICD11DiagnosisModel } from "@/components/Diagnosis/types"; -import { getDiagnosesByIds } from "@/components/Diagnosis/utils"; -import AutocompleteMultiSelectFormField from "@/components/Form/FormFields/AutocompleteMultiselect"; - -import useDebounce from "@/hooks/useDebounce"; - -import { Error } from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { mergeQueryOptions } from "@/Utils/utils"; - -export const FILTER_BY_DIAGNOSES_KEYS = [ - "diagnoses", - "diagnoses_confirmed", - "diagnoses_unconfirmed", - "diagnoses_provisional", - "diagnoses_differential", -] as const; - -export const DIAGNOSES_FILTER_LABELS = { - diagnoses: "Diagnoses (of any verification status)", - diagnoses_unconfirmed: "Unconfirmed Diagnoses", - diagnoses_provisional: "Provisional Diagnoses", - diagnoses_differential: "Differential Diagnoses", - diagnoses_confirmed: "Confirmed Diagnoses", -} as const; - -export type DiagnosesFilterKey = (typeof FILTER_BY_DIAGNOSES_KEYS)[number]; - -interface Props { - name: DiagnosesFilterKey; - value?: string; - onChange: (event: { name: DiagnosesFilterKey; value: string }) => void; -} - -export default function DiagnosesFilter(props: Props) { - const { t } = useTranslation(); - const [diagnoses, setDiagnoses] = useState([]); - const { res, data, loading, refetch } = useTanStackQueryInstead( - routes.listICD11Diagnosis, - { - silent: true, - prefetch: false, - }, - ); - - const handleQuery = useDebounce( - (query: string) => refetch({ query: { query } }), - 300, - ); - - useEffect(() => { - if (res?.status === 500) { - Error({ msg: "ICD-11 Diagnosis functionality is facing issues." }); - } - }, [res?.status]); - - useEffect(() => { - if (!props.value) { - setDiagnoses([]); - return; - } - if (diagnoses.map((d) => d.id).join(",") === props.value) { - return; - } - - // Re-use the objects which we already have, fetch the rest. - const ids = props.value.split(","); - const existing = diagnoses.filter(({ id }) => ids.includes(id)); - const objIds = existing.map((o) => o.id); - const diagnosesToBeFetched = ids.filter((id) => !objIds.includes(id)); - getDiagnosesByIds(diagnosesToBeFetched).then((data) => { - const retrieved = data.filter(Boolean) as ICD11DiagnosisModel[]; - setDiagnoses([...existing, ...retrieved]); - }); - }, [props.value]); - - return ( - { - setDiagnoses(e.value); - props.onChange({ - name: props.name, - value: e.value.map((o) => o.id).join(","), - }); - }} - options={mergeQueryOptions(diagnoses, data ?? [], (obj) => obj.id)} - optionLabel={(option) => option.label} - optionValue={(option) => option} - onQuery={handleQuery} - isLoading={loading} - /> - ); -} diff --git a/src/components/Patient/InsuranceDetails.tsx b/src/components/Patient/InsuranceDetails.tsx deleted file mode 100644 index 1796a657b9e..00000000000 --- a/src/components/Patient/InsuranceDetails.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import Loading from "@/components/Common/Loading"; -import Page from "@/components/Common/Page"; -import { HCXPolicyModel } from "@/components/HCX/models"; -import { InsuranceDetailsCard } from "@/components/Patient/InsuranceDetailsCard"; - -import routes from "@/Utils/request/api"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; - -interface IProps { - facilityId: string; - id: string; -} - -export const InsuranceDetails = (props: IProps) => { - const { facilityId, id } = props; - - const { data: insuranceDetials, loading } = useTanStackQueryInstead( - routes.hcx.policies.list, - { - query: { - patient: id, - }, - }, - ); - - if (loading) { - return ; - } - - return ( - - {loading ? ( - - ) : insuranceDetials?.count === 0 ? ( -
- No Insurance Details Available -
- ) : ( -
- {insuranceDetials?.results.map((data: HCXPolicyModel) => ( - - ))} -
- )} -
- ); -}; diff --git a/src/components/Patient/InsuranceDetailsCard.tsx b/src/components/Patient/InsuranceDetailsCard.tsx deleted file mode 100644 index 6685e7a99a2..00000000000 --- a/src/components/Patient/InsuranceDetailsCard.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import { HCXPolicyModel } from "@/components/HCX/models"; - -interface InsuranceDetails { - data: HCXPolicyModel; -} - -export const InsuranceDetailsCard = (props: InsuranceDetails) => { - const { data } = props; - - const { t } = useTranslation(); - - return ( -
-
-
-
-
- {t("insurance__member_id")} -
-
- {data.subscriber_id || ""} -
-
-
-
- {t("insurance__policy_name")} -
-
- {data.policy_id || ""} -
-
-
-
- {t("insurance__insurer_id")} -
-
- {data.insurer_id || ""} -
-
-
-
- {t("insurance__insurer_name")} -
-
- {data.insurer_name || ""} -
-
-
-
-
- ); -}; diff --git a/src/components/Patient/ManagePatients.tsx b/src/components/Patient/ManagePatients.tsx deleted file mode 100644 index 3633963e765..00000000000 --- a/src/components/Patient/ManagePatients.tsx +++ /dev/null @@ -1,1039 +0,0 @@ -import dayjs from "dayjs"; -import { Link, navigate } from "raviger"; -import { ReactNode, useCallback, useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { Avatar } from "@/components/Common/Avatar"; -import { ExportMenu } from "@/components/Common/Export"; -import Loading from "@/components/Common/Loading"; -import SearchByMultipleFields from "@/components/Common/SearchByMultipleFields"; -import SortDropdownMenu from "@/components/Common/SortDropdown"; - -import useAuthUser from "@/hooks/useAuthUser"; -import useFilters from "@/hooks/useFilters"; - -import { - ADMITTED_TO, - CONSENT_TYPE_CHOICES, - DISCHARGE_REASONS, - GENDER_TYPES, - PATIENT_CATEGORIES, - PATIENT_SORT_OPTIONS, - RESPIRATORY_SUPPORT, - TELEMEDICINE_ACTIONS, -} from "@/common/constants"; -import { parseOptionId } from "@/common/utils"; - -import routes from "@/Utils/request/api"; - -import Chip from "../../CAREUI/display/Chip"; -import CountBlock from "../../CAREUI/display/Count"; -import FilterBadge from "../../CAREUI/display/FilterBadge"; -import RecordMeta from "../../CAREUI/display/RecordMeta"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; -import { triggerGoal } from "../../Integrations/Plausible"; -import * as Notification from "../../Utils/Notifications"; -import request from "../../Utils/request/request"; -import useTanStackQueryInstead from "../../Utils/request/useQuery"; -import { - formatPatientAge, - humanizeStrings, - parsePhoneNumber, -} from "../../Utils/utils"; -import { ICD11DiagnosisModel } from "../Diagnosis/types"; -import { getDiagnosesByIds } from "../Diagnosis/utils"; -import FacilitiesSelectDialogue from "../ExternalResult/FacilitiesSelectDialogue"; -import DoctorVideoSlideover from "../Facility/DoctorVideoSlideover"; -import { FacilityModel, PatientCategory } from "../Facility/models"; -import { Button } from "../ui/button"; -import { - DIAGNOSES_FILTER_LABELS, - DiagnosesFilterKey, - FILTER_BY_DIAGNOSES_KEYS, -} from "./DiagnosesFilter"; -import PatientFilter from "./PatientFilter"; -import { isPatientMandatoryDataFilled } from "./Utils"; - -interface TabPanelProps { - children?: ReactNode; - dir?: string; - index: any; - value: any; -} - -function TabPanel(props: TabPanelProps) { - const { children, value, index, ...other } = props; - - return ( - - ); -} - -export const PatientManager = () => { - const { t } = useTranslation(); - const { - qParams, - updateQuery, - advancedFilter, - Pagination, - FilterBadges, - resultsPerPage, - clearSearch, - } = useFilters({ - limit: 12, - cacheBlacklist: [ - "name", - "patient_no", - "phone_number", - "emergency_phone_number", - ], - }); - const [selectedFacility, setSelectedFacility] = useState(); - const authUser = useAuthUser(); - const [diagnoses, setDiagnoses] = useState([]); - const [showDialog, setShowDialog] = useState<"create" | "list-discharged">(); - const [showDoctors, setShowDoctors] = useState(false); - - const tabValue = - qParams.last_consultation__new_discharge_reason || - qParams.is_active === "False" - ? 1 - : 0; - - const params = { - page: qParams.page || 1, - limit: resultsPerPage, - name: qParams.name || undefined, - patient_no: qParams.patient_no || undefined, - is_active: - !qParams.last_consultation__new_discharge_reason && - (qParams.is_active || "True"), - phone_number: qParams.phone_number - ? parsePhoneNumber(qParams.phone_number) - : undefined, - emergency_phone_number: qParams.emergency_phone_number - ? parsePhoneNumber(qParams.emergency_phone_number) - : undefined, - local_body: qParams.lsgBody || undefined, - facility: qParams.facility, - facility_type: qParams.facility_type || undefined, - district: qParams.district || undefined, - offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, - created_date_before: qParams.created_date_before || undefined, - created_date_after: qParams.created_date_after || undefined, - modified_date_before: qParams.modified_date_before || undefined, - modified_date_after: qParams.modified_date_after || undefined, - ordering: qParams.ordering || undefined, - category: qParams.category || undefined, - gender: qParams.gender || undefined, - age_min: qParams.age_min || undefined, - age_max: qParams.age_max || undefined, - date_declared_positive_before: - qParams.date_declared_positive_before || undefined, - date_declared_positive_after: - qParams.date_declared_positive_after || undefined, - ration_card_category: qParams.ration_card_category || undefined, - last_consultation_medico_legal_case: - qParams.last_consultation_medico_legal_case || undefined, - last_consultation_encounter_date_before: - qParams.last_consultation_encounter_date_before || undefined, - last_consultation_encounter_date_after: - qParams.last_consultation_encounter_date_after || undefined, - last_consultation_discharge_date_before: - qParams.last_consultation_discharge_date_before || undefined, - last_consultation_discharge_date_after: - qParams.last_consultation_discharge_date_after || undefined, - last_consultation_admitted_bed_type_list: - qParams.last_consultation_admitted_bed_type_list || undefined, - last_consultation__consent_types: - qParams.last_consultation__consent_types || undefined, - last_consultation__new_discharge_reason: - qParams.last_consultation__new_discharge_reason || undefined, - last_consultation_current_bed__location: - qParams.last_consultation_current_bed__location || undefined, - number_of_doses: qParams.number_of_doses || undefined, - covin_id: qParams.covin_id || undefined, - is_declared_positive: qParams.is_declared_positive || undefined, - last_vaccinated_date_before: - qParams.last_vaccinated_date_before || undefined, - last_vaccinated_date_after: qParams.last_vaccinated_date_after || undefined, - last_consultation_is_telemedicine: - qParams.last_consultation_is_telemedicine || undefined, - is_antenatal: qParams.is_antenatal || undefined, - last_menstruation_start_date_after: - (qParams.is_antenatal === "true" && - dayjs().subtract(9, "month").format("YYYY-MM-DD")) || - undefined, - ventilator_interface: qParams.ventilator_interface || undefined, - diagnoses: qParams.diagnoses || undefined, - diagnoses_confirmed: qParams.diagnoses_confirmed || undefined, - diagnoses_provisional: qParams.diagnoses_provisional || undefined, - diagnoses_unconfirmed: qParams.diagnoses_unconfirmed || undefined, - diagnoses_differential: qParams.diagnoses_differential || undefined, - review_missed: qParams.review_missed || undefined, - }; - - useEffect(() => { - const ids: string[] = []; - FILTER_BY_DIAGNOSES_KEYS.forEach((key) => { - ids.push(...(qParams[key] ?? "").split(",").filter(Boolean)); - }); - const existing = diagnoses.filter(({ id }) => ids.includes(id)); - const objIds = existing.map((o) => o.id); - const diagnosesToBeFetched = ids.filter((id) => !objIds.includes(id)); - getDiagnosesByIds(diagnosesToBeFetched).then((data) => { - const retrieved = data.filter(Boolean) as ICD11DiagnosisModel[]; - setDiagnoses([...existing, ...retrieved]); - }); - }, [ - qParams.diagnoses, - qParams.diagnoses_confirmed, - qParams.diagnoses_provisional, - qParams.diagnoses_unconfirmed, - qParams.diagnoses_differential, - ]); - - const date_range_fields = [ - [params.created_date_before, params.created_date_after], - [params.modified_date_before, params.modified_date_after], - [params.date_declared_positive_before, params.date_declared_positive_after], - [params.last_vaccinated_date_before, params.last_vaccinated_date_after], - [ - params.last_consultation_encounter_date_before, - params.last_consultation_encounter_date_after, - ], - [ - params.last_consultation_discharge_date_before, - params.last_consultation_discharge_date_after, - ], - ]; - - const durations = date_range_fields.map((field: string[]) => { - // XOR (checks if only one of the dates is set) - if (!field[0] !== !field[1]) { - return -1; - } - if (field[0] && field[1]) { - return dayjs(field[0]).diff(dayjs(field[1]), "days"); - } - return 0; - }); - - const isExportAllowed = - durations.every((x) => x >= 0 && x <= 7) && - !durations.every((x) => x === 0); - - let managePatients: any = null; - const preventDuplicatePatientsDuetoPolicyId = (data: any) => { - // Generate a array which contains imforamation of duplicate patient IDs and there respective linenumbers - const lines = data.split("\n"); // Split the data into individual lines - const idsMap = new Map(); // To store indices of lines with the same patient ID - - lines.map((line: any, i: number) => { - const patientId = line.split(",")[0]; // Extract the patient ID from each line - if (idsMap.has(patientId)) { - idsMap.get(patientId).push(i); // Add the index to the existing array - } else { - idsMap.set(patientId, [i]); // Create a new array with the current index - } - }); - - const linesWithSameId = Array.from(idsMap.entries()) - .filter(([_, indices]) => indices.length > 1) - .map(([patientId, indices]) => ({ - patientId, - indexSame: indices, - })); - - // after getting the array of duplicate patient IDs and there respective linenumbers we will merge the policy IDs of the duplicate patients - - linesWithSameId.map((lineInfo) => { - const indexes = lineInfo.indexSame; - //get policyid of all the duplicate patients and merge them by seperating them with a semicolon - const mergedPolicyId = `${indexes.map((currentIndex: number) => { - return `${lines[currentIndex].split(",")[5]};`; - })}`.replace(/,/g, ""); - // replace the policy ID of the first patient with the merged policy ID - const arrayOfCurrentLine = lines[indexes[0]].split(","); - arrayOfCurrentLine[5] = mergedPolicyId; - const lineAfterMerge = arrayOfCurrentLine.join(","); - lines[indexes[0]] = `${lineAfterMerge}`; - }); - - // after merging the policy IDs of the duplicate patients we will remove the duplicate patients from the data - const uniqueLines = []; - const ids = new Set(); // To keep track of unique patient IDs - - for (const line of lines) { - const patientId = line.split(",")[0]; // Extract the patient ID from each line - if (!ids.has(patientId)) { - uniqueLines.push(line); - ids.add(patientId); - } - } - - const cleanedData = uniqueLines.join("\n"); // Join the unique lines back together - return cleanedData; - }; - - const { loading: isLoading, data } = useTanStackQueryInstead( - routes.patientList, - { - query: params, - }, - ); - - const getTheCategoryFromId = () => { - let category_name; - if (qParams.category) { - category_name = PATIENT_CATEGORIES.find( - (item: any) => qParams.category === item.id, - )?.text; - - return String(category_name); - } else { - return ""; - } - }; - - const { data: districtData } = useTanStackQueryInstead(routes.getDistrict, { - pathParams: { - id: qParams.district, - }, - prefetch: !!Number(qParams.district), - }); - - const { data: LocalBodyData } = useTanStackQueryInstead(routes.getLocalBody, { - pathParams: { - id: qParams.lsgBody, - }, - prefetch: !!Number(qParams.lsgBody), - }); - - const { data: facilityData } = useTanStackQueryInstead( - routes.getAnyFacility, - { - pathParams: { - id: qParams.facility, - }, - prefetch: !!qParams.facility, - }, - ); - const { data: facilityAssetLocationData } = useTanStackQueryInstead( - routes.getFacilityAssetLocation, - { - pathParams: { - facility_external_id: qParams.facility, - external_id: qParams.last_consultation_current_bed__location, - }, - prefetch: !!qParams.last_consultation_current_bed__location, - }, - ); - - const LastAdmittedToTypeBadges = () => { - const badge = (key: string, value: string | undefined, id: string) => { - return ( - value && ( - { - const lcat = qParams.last_consultation_admitted_bed_type_list - .split(",") - .filter((x: string) => x != id) - .join(","); - updateQuery({ - ...qParams, - last_consultation_admitted_bed_type_list: lcat, - }); - }} - /> - ) - ); - }; - return qParams.last_consultation_admitted_bed_type_list - .split(",") - .map((id: string) => { - const text = ADMITTED_TO.find((obj) => obj.id == id)?.text; - return badge("Bed Type", text, id); - }); - }; - - const HasConsentTypesBadges = () => { - const badge = (key: string, value: string | undefined, id: string) => { - return ( - value && ( - { - const lcat = qParams.last_consultation__consent_types - .split(",") - .filter((x: string) => x != id) - .join(","); - updateQuery({ - ...qParams, - last_consultation__consent_types: lcat, - }); - }} - /> - ) - ); - }; - - return qParams.last_consultation__consent_types - .split(",") - .map((id: string) => { - const text = [ - ...CONSENT_TYPE_CHOICES, - { id: "None", text: "No Consents" }, - ].find((obj) => obj.id == id)?.text; - return badge("Has Consent", text, id); - }); - }; - - const getDiagnosisFilterValue = (key: DiagnosesFilterKey) => { - const ids: string[] = (qParams[key] ?? "").split(","); - return ids.map((id) => diagnoses.find((obj) => obj.id == id)?.label ?? id); - }; - - let patientList: ReactNode[] = []; - if (data?.count) { - patientList = data.results.map((patient) => { - let patientUrl = ""; - // Open Patient Details Page for Volunteer Users; Encounter is not accessible for Volunteer Users - if (authUser.user_type === "Volunteer") { - patientUrl = `/facility/${patient.facility}/patient/${patient.id}`; - } else if (!isPatientMandatoryDataFilled(patient)) { - patientUrl = `/facility/${patient.facility}/patient/${patient.id}`; - } else if ( - patient.last_consultation && - patient.last_consultation?.facility === patient.facility && - !(patient.last_consultation?.discharge_date && patient.is_active) - ) { - patientUrl = `/facility/${patient.facility}/patient/${patient.id}/consultation/${patient.last_consultation.id}`; - } else if (patient.facility) { - patientUrl = `/facility/${patient.facility}/patient/${patient.id}`; - } else { - patientUrl = `/patient/${patient.id}`; - } - - const category: PatientCategory | undefined = - patient?.last_consultation?.last_daily_round?.patient_category ?? - patient?.last_consultation?.category; - const categoryClass = category - ? PATIENT_CATEGORIES.find((c) => c.text === category)?.twClass - : "patient-unknown"; - - const children = ( -
-
- - {category || "UNKNOWN"} - -
-
-
- {patient?.last_consultation?.current_bed && - patient?.last_consultation?.discharge_date === null ? ( -
- - { - patient?.last_consultation?.current_bed?.bed_object - ?.location_object?.name - } - - - {patient?.last_consultation?.current_bed?.bed_object?.name} - - - { - patient?.last_consultation?.current_bed?.bed_object - ?.location_object?.name - } -
- {patient?.last_consultation?.current_bed?.bed_object?.name} -
-
- ) : patient.last_consultation?.suggestion === "DC" ? ( -
-
- - - Domiciliary Care - -
-
- ) : ( -
- -
- )} -
-
-
-
- {patient.name} - - {formatPatientAge(patient, true)} - -
-
- - {patient.action && patient.action != 10 && ( - - { - TELEMEDICINE_ACTIONS.find((i) => i.id === patient.action) - ?.desc - } - - )} - - {patient.facility_object && ( -
-
-

- {patient.facility_object.name} -

- - {t("updated")} - - } - time={patient.modified_date} - /> -
-
- )} -
-
- {!isPatientMandatoryDataFilled(patient) && ( - - - - - - - - )} - - {isPatientMandatoryDataFilled(patient) && - (!patient.last_consultation || - patient.last_consultation?.facility !== patient.facility || - (patient.last_consultation?.discharge_date && - patient.is_active)) ? ( - - - - - - - - ) : ( - <> - {patient.last_consultation?.patient_no && ( - - )} - - )} - {patient.review_time && - !patient.last_consultation?.discharge_date && - Number(patient.last_consultation?.review_interval) > 0 && - dayjs().isAfter(patient.review_time) && ( - - )} - {patient.last_consultation?.is_readmission && ( - - )} - {patient.last_consultation?.suggestion === "A" && - patient.last_consultation.facility === patient.facility && - !patient.last_consultation.discharge_date && ( - - )} - {patient.is_medical_worker && patient.is_active && ( - - )} - {!( - patient.last_consultation?.facility !== patient.facility - ) && - !( - patient.last_consultation?.discharge_date || - !patient.is_active - ) && - dayjs(patient.last_consultation?.modified_date).isBefore( - new Date().getTime() - 24 * 60 * 60 * 1000, - ) && ( - - - - - - - - )} - {/* {!!patient.last_consultation?.has_consents.length || ( - - - - - - - - )} */} -
-
-
- {patient.last_consultation?.last_daily_round - ?.ventilator_interface && - patient.last_consultation?.last_daily_round - ?.ventilator_interface !== "UNKNOWN" && - !patient.last_consultation?.discharge_date && ( -
- { - RESPIRATORY_SUPPORT.find( - (resp) => - resp.value === - patient.last_consultation?.last_daily_round - ?.ventilator_interface, - )?.id - } -
- )} -
-
- ); - - if ( - authUser.user_type === "Staff" || - authUser.user_type === "StaffReadOnly" - ) { - return children; - } - - return ( - - {children} - - ); - }); - } - - if (isLoading || !data) { - managePatients = ( -
- -
- ); - } else if (data?.count) { - managePatients = ( - <> -
- {patientList} -
- - - ); - } else if (data && data.count === 0) { - managePatients = ( -
-

- {t("no_patients_found")} -

-
- ); - } - - const searchOptions = [ - { - key: "name", - label: "Name", - type: "text" as const, - placeholder: t("search_by_patient_name"), - value: qParams.name || "", - shortcutKey: "n", - }, - { - key: "patient_no", - label: "IP/OP No", - type: "text" as const, - placeholder: t("search_by_patient_no"), - value: qParams.patient_no || "", - shortcutKey: "u", - }, - { - key: "phone_number", - label: "Phone Number", - type: "phone" as const, - placeholder: t("search_by_phone_number"), - value: qParams.phone_number || "", - shortcutKey: "p", - }, - { - key: "emergency_phone_number", - label: "Emergency Contact Phone Number", - type: "phone" as const, - placeholder: t("search_by_emergency_phone_number"), - value: qParams.emergency_phone_number || "", - shortcutKey: "e", - }, - ]; - - const handleSearch = useCallback( - (key: string, value: string) => { - const updatedQuery: Record = {}; - - switch (key) { - case "phone_number": - case "emergency_contact_number": - if (value.length >= 13 || value === "") { - updatedQuery[key] = value; - } else { - updatedQuery[key] = ""; - } - break; - case "name": - case "patient_no": - updatedQuery[key] = value; - break; - default: - break; - } - - updateQuery(updatedQuery); - }, - [updateQuery], - ); - - return ( - <> -
-
- {!!params.facility && ( - - )} - - advancedFilter.setShow(true)} /> - -
- {!isExportAllowed ? ( - - ) : ( - { - const query = { - ...params, - csv: true, - facility: qParams.facility, - is_active: "True", - }; - const { data } = await request(routes.patientList, { - query, - }); - return data ?? null; - }, - parse: preventDuplicatePatientsDuetoPolicyId, - }, - ]} - /> - )} - - {!isExportAllowed && ( - - {t("select_seven_day_period")} - - )} -
-
-
- - setSelectedFacility(e)} - selectedFacility={selectedFacility} - handleOk={() => { - if (selectedFacility) { - switch (showDialog) { - case "create": - navigate(`facility/${selectedFacility.id}/patient`); - break; - case "list-discharged": - navigate( - `/patients/discharged?facility=${selectedFacility.id}`, - ); - break; - } - } else { - Notification.Error({ msg: "No facility selected" }); - } - }} - handleCancel={() => { - setShowDialog(undefined); - setSelectedFacility(undefined); - }} - /> - -
-
- -
- - -
-
- [ - phoneNumber("Primary number", "phone_number"), - phoneNumber("Emergency number", "emergency_phone_number"), - badge("Patient name", "name"), - badge("IP/OP number", "patient_no"), - ...dateRange("Modified", "modified_date"), - ...dateRange("Created", "created_date"), - ...dateRange("Admitted", "last_consultation_encounter_date"), - ...dateRange("Discharged", "last_consultation_discharge_date"), - // Admitted to type badges - badge("No. of vaccination doses", "number_of_doses"), - badge("COWIN ID", "covin_id"), - badge("Is Antenatal", "is_antenatal"), - badge("Review Missed", "review_missed"), - badge( - "Is Medico-Legal Case", - "last_consultation_medico_legal_case", - ), - value( - "Ration Card Category", - "ration_card_category", - qParams.ration_card_category - ? t(`ration_card__${qParams.ration_card_category}`) - : "", - ), - value( - "Facility", - "facility", - qParams.facility ? facilityData?.name || "" : "", - ), - value( - "Location", - "last_consultation_current_bed__location", - qParams.last_consultation_current_bed__location - ? facilityAssetLocationData?.name || - qParams.last_consultation_current_bed__locations - : "", - ), - badge("Facility Type", "facility_type"), - value( - "District", - "district", - qParams.district ? districtData?.name || "" : "", - ), - ordering(), - value("Category", "category", getTheCategoryFromId()), - value( - "Respiratory Support", - "ventilator_interface", - qParams.ventilator_interface && - t(`RESPIRATORY_SUPPORT_SHORT__${qParams.ventilator_interface}`), - ), - value( - "Gender", - "gender", - parseOptionId(GENDER_TYPES, qParams.gender) || "", - ), - { - name: "Admitted to", - value: ADMITTED_TO[qParams.last_consultation_admitted_to], - paramKey: "last_consultation_admitted_to", - }, - ...range("Age", "age"), - { - name: "LSG Body", - value: qParams.lsgBody ? LocalBodyData?.name || "" : "", - paramKey: "lsgBody", - }, - ...FILTER_BY_DIAGNOSES_KEYS.map((key) => - value( - DIAGNOSES_FILTER_LABELS[key], - key, - humanizeStrings(getDiagnosisFilterValue(key)), - ), - ), - badge("Declared Status", "is_declared_positive"), - ...dateRange("Declared positive", "date_declared_positive"), - ...dateRange("Last vaccinated", "last_vaccinated_date"), - { - name: "Telemedicine", - paramKey: "last_consultation_is_telemedicine", - }, - value( - "Discharge Reason", - "last_consultation__new_discharge_reason", - parseOptionId( - DISCHARGE_REASONS, - qParams.last_consultation__new_discharge_reason, - ) || "", - ), - ]} - children={ - (qParams.last_consultation_admitted_bed_type_list || - qParams.last_consultation__consent_types) && ( - <> - {qParams.last_consultation_admitted_bed_type_list && - LastAdmittedToTypeBadges()} - {qParams.last_consultation__consent_types && - HasConsentTypesBadges()} - - ) - } - /> -
-
- - -
{managePatients}
-
- -
{managePatients}
-
- -
- - ); -}; diff --git a/src/components/Patient/PatientCategorySelect.tsx b/src/components/Patient/PatientCategorySelect.tsx deleted file mode 100644 index 0cdb003db77..00000000000 --- a/src/components/Patient/PatientCategorySelect.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { SelectFormField } from "@/components/Form/FormFields/SelectFormField"; -import { FormFieldBaseProps } from "@/components/Form/FormFields/Utils"; - -import { PATIENT_CATEGORIES, PatientCategoryID } from "@/common/constants"; - -import { classNames } from "@/Utils/utils"; - -/** - * A `FormField` component to select patient category and is by default a mandatory - * field. - */ -export default function PatientCategorySelect( - props: FormFieldBaseProps, -) { - return ( - c.id !== "Comfort") - } // Comfort Care is discontinued - optionValue={(option) => option.id} - optionLabel={(option) => option.text} - optionDescription={(option) => option.description} - optionSelectedLabel={(option) => ( - -
-

{option.text}

- - )} - /> - ); -} diff --git a/src/components/Patient/PatientConsentRecords.tsx b/src/components/Patient/PatientConsentRecords.tsx index c2232c91c09..55ced7435ed 100644 --- a/src/components/Patient/PatientConsentRecords.tsx +++ b/src/components/Patient/PatientConsentRecords.tsx @@ -23,7 +23,6 @@ import { import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { formatDateTime } from "@/Utils/utils"; export default function PatientConsentRecords(props: { facilityId: string; @@ -56,12 +55,6 @@ export default function PatientConsentRecords(props: { }, }); - const { data: patient } = useTanStackQueryInstead(routes.getPatient, { - pathParams: { - id: patientId, - }, - }); - const { data: consentRecordsData, refetch } = useTanStackQueryInstead( routes.listConsents, { @@ -112,22 +105,6 @@ export default function PatientConsentRecords(props: { return ( {fileUpload.Dialogues} diff --git a/src/components/Patient/PatientDetailsTab/Notes.tsx b/src/components/Patient/PatientDetailsTab/Notes.tsx deleted file mode 100644 index c3a57f799fa..00000000000 --- a/src/components/Patient/PatientDetailsTab/Notes.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import { t } from "i18next"; -import { useState } from "react"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import { Button } from "@/components/ui/button"; - -import DoctorNoteReplyPreviewCard from "@/components/Facility/DoctorNoteReplyPreviewCard"; -import PatientNotesList from "@/components/Facility/PatientNotesList"; -import { - PatientNoteStateType, - PatientNotesModel, -} from "@/components/Facility/models"; -import AutoExpandingTextInputFormField from "@/components/Form/FormFields/AutoExpandingTextInputFormField"; -import { PatientProps } from "@/components/Patient/PatientDetailsTab"; -import { useAddPatientNote } from "@/components/Patient/Utils"; - -import useAuthUser from "@/hooks/useAuthUser"; -import { useMessageListener } from "@/hooks/useMessageListener"; - -import { PATIENT_NOTES_THREADS } from "@/common/constants"; - -import * as Notification from "@/Utils/Notifications"; -import { classNames, keysOf } from "@/Utils/utils"; - -const PatientNotes = (props: PatientProps) => { - const { patientData, id: patientId, facilityId } = props; - - const authUser = useAuthUser(); - const [thread, setThread] = useState( - authUser.user_type === "Nurse" - ? PATIENT_NOTES_THREADS.Nurses - : PATIENT_NOTES_THREADS.Doctors, - ); - - const [noteField, setNoteField] = useState(""); - const [reload, setReload] = useState(false); - const [reply_to, setReplyTo] = useState( - undefined, - ); - const { mutate: addNote } = useAddPatientNote({ - patientId, - thread, - }); - const initialData: PatientNoteStateType = { - notes: [], - }; - const [state, setState] = useState(initialData); - - const onAddNote = () => { - if (!/\S+/.test(noteField)) { - Notification.Error({ - msg: "Note Should Contain At Least 1 Character", - }); - return; - } - addNote({ - note: noteField, - reply_to: reply_to?.id, - thread, - }); - setReplyTo(undefined); - setNoteField(""); - }; - - useMessageListener((data) => { - const message = data?.message; - if ( - (message?.from == "patient/doctor_notes/create" || - message?.from == "patient/doctor_notes/edit") && - message?.facility_id == facilityId && - message?.patient_id == patientId - ) { - setReload(true); - } - }); - - return ( -
-
-
- {keysOf(PATIENT_NOTES_THREADS).map((current) => ( - - ))} -
- - setReplyTo(undefined)} - > -
- setNoteField(e.value)} - className="w-full grow" - errorClassName="hidden" - innerClassName="pr-10" - placeholder={t("notes_placeholder")} - disabled={!!patientData.death_datetime} - /> - -
-
-
-
- ); -}; - -export default PatientNotes; diff --git a/src/components/Patient/PatientDetailsTab/index.tsx b/src/components/Patient/PatientDetailsTab/index.tsx index e5a875bf605..26dd69460e5 100644 --- a/src/components/Patient/PatientDetailsTab/index.tsx +++ b/src/components/Patient/PatientDetailsTab/index.tsx @@ -1,6 +1,5 @@ import EncounterHistory from "@/components/Patient/PatientDetailsTab//EncounterHistory"; import { HealthProfileSummary } from "@/components/Patient/PatientDetailsTab//HealthProfileSummary"; -import PatientNotes from "@/components/Patient/PatientDetailsTab//Notes"; import { Demography } from "@/components/Patient/PatientDetailsTab/Demography"; import { Updates } from "@/components/Patient/PatientDetailsTab/patientUpdates"; @@ -37,18 +36,10 @@ export const patientTabs = [ route: "updates", component: Updates, }, - // { - // route: "shift", - // component: ShiftingHistory, - // }, { route: "resource_requests", component: ResourceRequests, }, - { - route: "patient-notes", - component: PatientNotes, - }, { route: "users", component: PatientUsers, diff --git a/src/components/Patient/PatientFilter.tsx b/src/components/Patient/PatientFilter.tsx deleted file mode 100644 index 0349efd404f..00000000000 --- a/src/components/Patient/PatientFilter.tsx +++ /dev/null @@ -1,931 +0,0 @@ -import dayjs from "dayjs"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import FilterBadge from "@/CAREUI/display/FilterBadge"; -import CareIcon from "@/CAREUI/icons/CareIcon"; -import FiltersSlideover from "@/CAREUI/interactive/FiltersSlideover"; - -import AccordionV2 from "@/components/Common/AccordionV2"; -import { DateRange } from "@/components/Common/DateRangeInputV2"; -import { FacilitySelect } from "@/components/Common/FacilitySelect"; -import { LocationSelect } from "@/components/Common/LocationSelect"; -import DistrictSelect from "@/components/Facility/FacilityFilter/DistrictSelect"; -import AutoCompleteAsync from "@/components/Form/AutoCompleteAsync"; -import DateRangeFormField from "@/components/Form/FormFields/DateRangeFormField"; -import { FieldLabel } from "@/components/Form/FormFields/FormField"; -import { SelectFormField } from "@/components/Form/FormFields/SelectFormField"; -import TextFormField from "@/components/Form/FormFields/TextFormField"; -import { - FieldChangeEvent, - FieldChangeEventHandler, -} from "@/components/Form/FormFields/Utils"; -import MultiSelectMenuV2 from "@/components/Form/MultiSelectMenuV2"; -import SelectMenuV2 from "@/components/Form/SelectMenuV2"; -import DiagnosesFilter, { - DIAGNOSES_FILTER_LABELS, - DiagnosesFilterKey, - FILTER_BY_DIAGNOSES_KEYS, -} from "@/components/Patient/DiagnosesFilter"; - -import useAuthUser from "@/hooks/useAuthUser"; -import useFilters from "@/hooks/useFilters"; -import useMergeState from "@/hooks/useMergeState"; - -import { - ADMITTED_TO, - CONSENT_TYPE_CHOICES, - DISCHARGE_REASONS, - FACILITY_TYPES, - GENDER_TYPES, - PATIENT_CATEGORIES, - PATIENT_FILTER_CATEGORIES, - RATION_CARD_CATEGORY, -} from "@/common/constants"; -import { parseOptionId } from "@/common/utils"; - -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { dateQueryString, humanizeStrings } from "@/Utils/utils"; - -import { ICD11DiagnosisModel } from "../Diagnosis/types"; -import { getDiagnosesByIds } from "../Diagnosis/utils"; - -const getDate = (value: any) => - value && dayjs(value).isValid() && dayjs(value).toDate(); - -export default function PatientFilter(props: any) { - const { t } = useTranslation(); - const authUser = useAuthUser(); - const { filter, onChange, closeFilter, removeFilters } = props; - - const [filterState, setFilterState] = useMergeState({ - district: filter.district || "", - facility: filter.facility || "", - facility_type: filter.facility_type || "", - lsgBody: filter.lsgBody || "", - facility_ref: null, - lsgBody_ref: null, - district_ref: null, - date_declared_positive_before: filter.date_declared_positive_before || null, - date_declared_positive_after: filter.date_declared_positive_after || null, - created_date_before: filter.created_date_before || null, - created_date_after: filter.created_date_after || null, - modified_date_before: filter.modified_date_before || null, - modified_date_after: filter.modified_date_after || null, - category: filter.category || null, - gender: filter.gender || null, - age_min: filter.age_min || null, - age_max: filter.age_max || null, - date_declared_positive: filter.date_declared_positive || null, - ration_card_category: filter.ration_card_category || null, - last_consultation_medico_legal_case: - filter.last_consultation_medico_legal_case || null, - last_consultation_encounter_date_before: - filter.last_consultation_encounter_date_before || null, - last_consultation_encounter_date_after: - filter.last_consultation_encounter_date_after || null, - last_consultation_discharge_date_before: - filter.last_consultation_discharge_date_before || null, - last_consultation_discharge_date_after: - filter.last_consultation_discharge_date_after || null, - last_consultation_admitted_bed_type_list: - filter.last_consultation_admitted_bed_type_list - ? filter.last_consultation_admitted_bed_type_list.split(",") - : [], - last_consultation__consent_types: filter.last_consultation__consent_types, - last_consultation_current_bed__location: - filter.last_consultation_current_bed__location || "", - last_consultation__new_discharge_reason: - filter.last_consultation__new_discharge_reason || null, - number_of_doses: filter.number_of_doses || null, - covin_id: filter.covin_id || null, - is_declared_positive: filter.is_declared_positive || null, - last_vaccinated_date_before: filter.last_vaccinated_date_before || null, - last_vaccinated_date_after: filter.last_vaccinated_date_after || null, - last_consultation_is_telemedicine: - filter.last_consultation_is_telemedicine || null, - is_antenatal: filter.is_antenatal || null, - ventilator_interface: filter.ventilator_interface || null, - diagnoses: filter.diagnoses || null, - diagnoses_confirmed: filter.diagnoses_confirmed || null, - diagnoses_provisional: filter.diagnoses_provisional || null, - diagnoses_unconfirmed: filter.diagnoses_unconfirmed || null, - diagnoses_differential: filter.diagnoses_differential || null, - review_missed: filter.review_missed || null, - }); - - useTanStackQueryInstead(routes.getAnyFacility, { - pathParams: { id: filter.facility }, - prefetch: !!filter.facility, - onResponse: ({ data }) => setFilterState({ facility_ref: data }), - }); - - useTanStackQueryInstead(routes.getDistrict, { - pathParams: { id: filter.district }, - prefetch: !!filter.district, - onResponse: ({ data }) => setFilterState({ district_ref: data }), - }); - - useTanStackQueryInstead(routes.getLocalBody, { - pathParams: { id: filter.lsgBody }, - prefetch: !!filter.lsgBody, - onResponse: ({ data }) => setFilterState({ lsgBody_ref: data }), - }); - - const RESPIRATORY_SUPPORT_FILTER = [ - { id: "UNKNOWN", text: "None" }, - { id: "OXYGEN_SUPPORT", text: "O2 Support" }, - { id: "NON_INVASIVE", text: "NIV" }, - { id: "INVASIVE", text: "IV" }, - ]; - - const TELEMEDICINE_FILTER = [ - { id: "true", text: "Yes" }, - { id: "false", text: "No" }, - ]; - - const setFilterWithRef = (name: string, selected?: any) => { - setFilterState({ - [`${name}_ref`]: selected, - [name]: selected?.id, - }); - }; - - const lsgSearch = async (search: string) => { - const { data } = await request(routes.getAllLocalBody, { - query: { local_body_name: search }, - }); - return data?.results; - }; - - const applyFilter = () => { - const { - district, - facility, - facility_type, - lsgBody, - date_declared_positive_before, - date_declared_positive_after, - created_date_before, - created_date_after, - modified_date_before, - modified_date_after, - category, - gender, - age_min, - age_max, - ration_card_category, - last_consultation_medico_legal_case, - last_consultation_encounter_date_before, - last_consultation_encounter_date_after, - last_consultation_discharge_date_before, - last_consultation_discharge_date_after, - last_consultation_admitted_bed_type_list, - last_consultation__consent_types, - last_consultation__new_discharge_reason, - last_consultation_current_bed__location, - number_of_doses, - covin_id, - is_declared_positive, - last_vaccinated_date_before, - last_vaccinated_date_after, - last_consultation_is_telemedicine, - is_antenatal, - ventilator_interface, - diagnoses, - diagnoses_confirmed, - diagnoses_provisional, - diagnoses_unconfirmed, - diagnoses_differential, - review_missed, - } = filterState; - const data = { - district: district || "", - lsgBody: lsgBody || "", - facility: facility || "", - last_consultation_current_bed__location: - last_consultation_current_bed__location || "", - facility_type: facility_type || "", - date_declared_positive_before: dateQueryString( - date_declared_positive_before, - ), - date_declared_positive_after: dateQueryString( - date_declared_positive_after, - ), - created_date_before: dateQueryString(created_date_before), - created_date_after: dateQueryString(created_date_after), - modified_date_before: dateQueryString(modified_date_before), - modified_date_after: dateQueryString(modified_date_after), - ration_card_category, - last_consultation_medico_legal_case: - last_consultation_medico_legal_case || "", - last_consultation_encounter_date_before: dateQueryString( - last_consultation_encounter_date_before, - ), - last_consultation_encounter_date_after: dateQueryString( - last_consultation_encounter_date_after, - ), - last_consultation_discharge_date_before: dateQueryString( - last_consultation_discharge_date_before, - ), - last_consultation_discharge_date_after: dateQueryString( - last_consultation_discharge_date_after, - ), - category: category || "", - gender: gender || "", - age_min: age_min || "", - age_max: age_max || "", - last_consultation_admitted_bed_type_list: - last_consultation_admitted_bed_type_list || [], - last_consultation__consent_types: last_consultation__consent_types, - last_consultation__new_discharge_reason: - last_consultation__new_discharge_reason || "", - number_of_doses: number_of_doses || "", - covin_id: covin_id || "", - is_declared_positive: is_declared_positive || "", - last_vaccinated_date_before: dateQueryString(last_vaccinated_date_before), - last_vaccinated_date_after: dateQueryString(last_vaccinated_date_after), - last_consultation_is_telemedicine: - last_consultation_is_telemedicine || "", - is_antenatal: is_antenatal || "", - ventilator_interface: ventilator_interface || "", - diagnoses: diagnoses || "", - diagnoses_confirmed: diagnoses_confirmed || "", - diagnoses_provisional: diagnoses_provisional || "", - diagnoses_unconfirmed: diagnoses_unconfirmed || "", - diagnoses_differential: diagnoses_differential || "", - review_missed: review_missed || "", - }; - onChange(data); - }; - - const handleDateRangeChange = (event: FieldChangeEvent) => { - const filterData: any = { ...filterState }; - filterData[`${event.name}_after`] = event.value.start?.toString(); - filterData[`${event.name}_before`] = event.value.end?.toString(); - setFilterState(filterData); - }; - - const handleFormFieldChange: FieldChangeEventHandler = (event) => - setFilterState({ ...filterState, [event.name]: event.value }); - - return ( - { - removeFilters(); - closeFilter(); - }} - > - - Patient Details based - - } - expanded={true} - className="w-full rounded-md" - > -
-
- Gender - o.text} - optionIcon={(o) => {o.icon}} - optionValue={(o) => o.id} - value={filterState.gender} - onChange={(v) => setFilterState({ ...filterState, gender: v })} - /> -
-
- Category - o.text} - optionValue={(o) => o.id} - value={filterState.category} - onChange={(v) => setFilterState({ ...filterState, category: v })} - /> -
-
- Age -
- 0 ? filterState.age_min : 0) - } - type="number" - min={0} - onChange={handleFormFieldChange} - errorClassName="hidden" - /> - 0 ? filterState.age_max : 0) - } - onChange={handleFormFieldChange} - errorClassName="hidden" - /> -
-
-
- - {props.dischargePage && "Last "}Admitted to (Bed Types) - - o.id} - optionLabel={(o) => o.text} - onChange={(o) => - setFilterState({ - ...filterState, - last_consultation_admitted_bed_type_list: o, - }) - } - /> -
- - {(props.dischargePage || - ["StateAdmin", "StateReadOnlyAdmin"].includes( - authUser.user_type, - )) && ( -
- Discharge Reason - o.id} - optionLabel={(o) => o.text} - onChange={(o) => - setFilterState({ - ...filterState, - last_consultation__new_discharge_reason: o, - }) - } - /> -
- )} -
- Telemedicine - o.text} - optionValue={(o) => o.id} - value={filterState.last_consultation_is_telemedicine} - onChange={(v) => - setFilterState({ - ...filterState, - last_consultation_is_telemedicine: v, - }) - } - /> -
-
- Respiratory Support - o.text} - optionValue={(o) => o.id} - value={filterState.ventilator_interface} - onChange={(v) => - setFilterState({ - ...filterState, - ventilator_interface: v, - }) - } - /> -
- {/*
- Is Antenatal - - o === "true" ? "Antenatal" : "Non-antenatal" - } - value={filterState.is_antenatal} - onChange={(v) => - setFilterState({ ...filterState, is_antenatal: v }) - } - /> -
*/} -
- {props.dischargePage || ( - <> - Review Missed - (o === "true" ? "Yes" : "No")} - value={filterState.review_missed} - onChange={(v) => - setFilterState({ ...filterState, review_missed: v }) - } - /> - - )} -
-
- Is Medico-Legal Case - - o === "true" ? "Medico-Legal" : "Non-Medico-Legal" - } - value={filterState.last_consultation_medico_legal_case} - onChange={(v) => - setFilterState({ - ...filterState, - last_consultation_medico_legal_case: v, - }) - } - /> -
- t(`ration_card__${o}`)} - optionValue={(o) => o} - value={filterState.ration_card_category} - onChange={(e) => - setFilterState({ - ...filterState, - [e.name]: e.value, - }) - } - /> -
-
- - ICD-11 Diagnoses based - - } - expanded - className="w-full" - > - {FILTER_BY_DIAGNOSES_KEYS.map((name) => ( - - ))} - - - Date based - - } - expanded={true} - className="w-full rounded-md" - > -
- - - - -
-
- - Geography based - - } - expanded={true} - className="rounded-md" - > -
- {!props.dischargePage && ( -
- Facility - setFilterWithRef("facility", obj)} - /> -
- )} - {filterState.facility && ( -
- Location - - setFilterState({ - ...filterState, - last_consultation_current_bed__location: selected, - }) - } - /> -
- )} - {!props.dischargePage && ( -
- Facility type - o.text} - optionValue={(o) => o.text} - value={filterState.facility_type} - onChange={(v) => - setFilterState({ ...filterState, facility_type: v }) - } - optionIcon={() => ( - - )} - /> -
- )} -
- LSG Body -
- setFilterWithRef("lsgBody", obj)} - optionLabel={(option) => option.name} - compareBy="id" - /> -
-
- -
- District - setFilterWithRef("district", obj)} - errors={""} - /> -
-
-
-
- ); -} - -export function PatientFilterBadges() { - const { t } = useTranslation(); - - const { qParams, FilterBadges, updateQuery } = useFilters({ - limit: 12, - cacheBlacklist: [ - "name", - "patient_no", - "phone_number", - "emergency_phone_number", - ], - }); - - const [diagnoses, setDiagnoses] = useState([]); - - const { data: districtData } = useTanStackQueryInstead(routes.getDistrict, { - pathParams: { - id: qParams.district, - }, - prefetch: !!Number(qParams.district), - }); - - const { data: LocalBodyData } = useTanStackQueryInstead(routes.getLocalBody, { - pathParams: { - id: qParams.lsgBody, - }, - prefetch: !!Number(qParams.lsgBody), - }); - - const { data: facilityData } = useTanStackQueryInstead( - routes.getAnyFacility, - { - pathParams: { - id: qParams.facility, - }, - prefetch: !!qParams.facility, - }, - ); - const { data: facilityAssetLocationData } = useTanStackQueryInstead( - routes.getFacilityAssetLocation, - { - pathParams: { - facility_external_id: qParams.facility, - external_id: qParams.last_consultation_current_bed__location, - }, - prefetch: !!qParams.last_consultation_current_bed__location, - }, - ); - - const LastAdmittedToTypeBadges = () => { - const badge = (key: string, value: string | undefined, id: string) => { - return ( - value && ( - { - const lcat = qParams.last_consultation_admitted_bed_type_list - .split(",") - .filter((x: string) => x != id) - .join(","); - updateQuery({ - ...qParams, - last_consultation_admitted_bed_type_list: lcat, - }); - }} - /> - ) - ); - }; - return qParams.last_consultation_admitted_bed_type_list - .split(",") - .map((id: string) => { - const text = ADMITTED_TO.find((obj) => obj.id == id)?.text; - return badge("Bed Type", text, id); - }); - }; - - const HasConsentTypesBadges = () => { - const badge = (key: string, value: string | undefined, id: string) => { - return ( - value && ( - { - const lcat = qParams.last_consultation__consent_types - .split(",") - .filter((x: string) => x != id) - .join(","); - updateQuery({ - ...qParams, - last_consultation__consent_types: lcat, - }); - }} - /> - ) - ); - }; - - return qParams.last_consultation__consent_types - .split(",") - .map((id: string) => { - const text = [ - ...CONSENT_TYPE_CHOICES, - { id: "None", text: "No Consents" }, - ].find((obj) => obj.id == id)?.text; - return badge("Has Consent", text, id); - }); - }; - - const getTheCategoryFromId = () => { - let category_name; - if (qParams.category) { - category_name = PATIENT_CATEGORIES.find( - (item: any) => qParams.category === item.id, - )?.text; - - return String(category_name); - } else { - return ""; - } - }; - - const getDiagnosisFilterValue = (key: DiagnosesFilterKey) => { - const ids: string[] = (qParams[key] ?? "").split(","); - return ids.map((id) => diagnoses.find((obj) => obj.id == id)?.label ?? id); - }; - - useEffect(() => { - const ids: string[] = []; - FILTER_BY_DIAGNOSES_KEYS.forEach((key) => { - ids.push(...(qParams[key] ?? "").split(",").filter(Boolean)); - }); - const existing = diagnoses.filter(({ id }) => ids.includes(id)); - const objIds = existing.map((o) => o.id); - const diagnosesToBeFetched = ids.filter((id) => !objIds.includes(id)); - getDiagnosesByIds(diagnosesToBeFetched).then((data) => { - const retrieved = data.filter(Boolean) as ICD11DiagnosisModel[]; - setDiagnoses([...existing, ...retrieved]); - }); - }, [ - qParams.diagnoses, - qParams.diagnoses_confirmed, - qParams.diagnoses_provisional, - qParams.diagnoses_unconfirmed, - qParams.diagnoses_differential, - ]); - - return ( - [ - phoneNumber("Primary number", "phone_number"), - phoneNumber("Emergency number", "emergency_phone_number"), - badge("Patient name", "name"), - badge("IP/OP number", "patient_no"), - ...dateRange("Modified", "modified_date"), - ...dateRange("Created", "created_date"), - ...dateRange("Admitted", "last_consultation_encounter_date"), - ...dateRange("Discharged", "last_consultation_discharge_date"), - // Admitted to type badges - badge("No. of vaccination doses", "number_of_doses"), - badge("COWIN ID", "covin_id"), - badge("Is Antenatal", "is_antenatal"), - badge("Review Missed", "review_missed"), - badge("Is Medico-Legal Case", "last_consultation_medico_legal_case"), - value( - "Ration Card Category", - "ration_card_category", - qParams.ration_card_category - ? t(`ration_card__${qParams.ration_card_category}`) - : "", - ), - value( - "Facility", - "facility", - qParams.facility ? facilityData?.name || "" : "", - ), - value( - "Location", - "last_consultation_current_bed__location", - qParams.last_consultation_current_bed__location - ? facilityAssetLocationData?.name || - qParams.last_consultation_current_bed__locations - : "", - ), - badge("Facility Type", "facility_type"), - value( - "District", - "district", - qParams.district ? districtData?.name || "" : "", - ), - ordering(), - value("Category", "category", getTheCategoryFromId()), - value( - "Respiratory Support", - "ventilator_interface", - qParams.ventilator_interface && - t(`RESPIRATORY_SUPPORT_SHORT__${qParams.ventilator_interface}`), - ), - value( - "Gender", - "gender", - parseOptionId(GENDER_TYPES, qParams.gender) || "", - ), - { - name: "Admitted to", - value: ADMITTED_TO[qParams.last_consultation_admitted_to], - paramKey: "last_consultation_admitted_to", - }, - ...range("Age", "age"), - { - name: "LSG Body", - value: qParams.lsgBody ? LocalBodyData?.name || "" : "", - paramKey: "lsgBody", - }, - ...FILTER_BY_DIAGNOSES_KEYS.map((key) => - value( - DIAGNOSES_FILTER_LABELS[key], - key, - humanizeStrings(getDiagnosisFilterValue(key)), - ), - ), - badge("Declared Status", "is_declared_positive"), - ...dateRange("Declared positive", "date_declared_positive"), - ...dateRange("Last vaccinated", "last_vaccinated_date"), - { - name: "Telemedicine", - paramKey: "last_consultation_is_telemedicine", - }, - value( - "Discharge Reason", - "last_consultation__new_discharge_reason", - parseOptionId( - DISCHARGE_REASONS, - qParams.last_consultation__new_discharge_reason, - ) || "", - ), - ]} - children={ - (qParams.last_consultation_admitted_bed_type_list || - qParams.last_consultation__consent_types) && ( - <> - {qParams.last_consultation_admitted_bed_type_list && - LastAdmittedToTypeBadges()} - {qParams.last_consultation__consent_types && - HasConsentTypesBadges()} - - ) - } - /> - ); -} diff --git a/src/components/Patient/PatientInfoCard.tsx b/src/components/Patient/PatientInfoCard.tsx index 022ebabaf25..8c0c8d29014 100644 --- a/src/components/Patient/PatientInfoCard.tsx +++ b/src/components/Patient/PatientInfoCard.tsx @@ -19,7 +19,6 @@ import { import { Avatar } from "@/components/Common/Avatar"; -import { PLUGIN_Component } from "@/PluginEngine"; import { formatDateTime, formatPatientAge } from "@/Utils/utils"; import { Encounter, completedEncounterStatus } from "@/types/emr/encounter"; import { Patient } from "@/types/emr/newPatient"; @@ -274,8 +273,6 @@ export default function PatientInfoCard(props: PatientInfoCardProps) { )}
- - ); } diff --git a/src/components/Patient/PatientNotes.tsx b/src/components/Patient/PatientNotes.tsx deleted file mode 100644 index b1e0c557837..00000000000 --- a/src/components/Patient/PatientNotes.tsx +++ /dev/null @@ -1,187 +0,0 @@ -import { t } from "i18next"; -import { useEffect, useState } from "react"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import ButtonV2 from "@/components/Common/ButtonV2"; -import Page from "@/components/Common/Page"; -import DoctorNoteReplyPreviewCard from "@/components/Facility/DoctorNoteReplyPreviewCard"; -import PatientNotesList from "@/components/Facility/PatientNotesList"; -import { - PatientNoteStateType, - PatientNotesModel, -} from "@/components/Facility/models"; -import AutoExpandingTextInputFormField from "@/components/Form/FormFields/AutoExpandingTextInputFormField"; -import { useAddPatientNote } from "@/components/Patient/Utils"; - -import useAuthUser from "@/hooks/useAuthUser"; -import { useMessageListener } from "@/hooks/useMessageListener"; - -import { PATIENT_NOTES_THREADS } from "@/common/constants"; - -import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; -import * as Notification from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import { classNames, keysOf } from "@/Utils/utils"; - -interface PatientNotesProps { - patientId: any; - facilityId: any; -} - -const PatientNotes = (props: PatientNotesProps) => { - const { patientId, facilityId } = props; - - const authUser = useAuthUser(); - const [thread, setThread] = useState( - authUser.user_type === "Nurse" - ? PATIENT_NOTES_THREADS.Nurses - : PATIENT_NOTES_THREADS.Doctors, - ); - - const [patientActive, setPatientActive] = useState(true); - const [noteField, setNoteField] = useState(""); - const [reload, setReload] = useState(false); - const [facilityName, setFacilityName] = useState(""); - const [patientName, setPatientName] = useState(""); - const [reply_to, setReplyTo] = useState( - undefined, - ); - - const initialData: PatientNoteStateType = { - notes: [], - }; - const [state, setState] = useState(initialData); - - const { mutate: addNote } = useAddPatientNote({ - patientId, - thread, - }); - - const onAddNote = () => { - if (!/\S+/.test(noteField)) { - Notification.Error({ - msg: "Note Should Contain At Least 1 Character", - }); - return; - } - addNote({ - note: noteField, - reply_to: reply_to?.id, - thread, - }); - setReplyTo(undefined); - setNoteField(""); - }; - - useEffect(() => { - async function fetchPatientName() { - if (patientId) { - const { data } = await request(routes.getPatient, { - pathParams: { id: patientId }, - }); - if (data) { - setPatientActive(data.is_active ?? true); - setPatientName(data.name ?? ""); - setFacilityName(data.facility_object?.name ?? ""); - } - } - } - fetchPatientName(); - }, [patientId]); - - useMessageListener((data) => { - const message = data?.message; - if ( - (message?.from == "patient/doctor_notes/create" || - message?.from == "patient/doctor_notes/edit") && - message?.facility_id == facilityId && - message?.patient_id == patientId - ) { - setReload(true); - } - }); - - return ( - -
-
- {keysOf(PATIENT_NOTES_THREADS).map((current) => ( - - ))} -
- - setReplyTo(undefined)} - > -
- setNoteField(e.value)} - className="w-full grow" - errorClassName="hidden" - innerClassName="pr-10" - placeholder={t("notes_placeholder")} - disabled={!patientActive} - /> - - - -
-
-
-
- ); -}; - -export default PatientNotes; diff --git a/src/components/Patient/ShiftCreate.tsx b/src/components/Patient/ShiftCreate.tsx deleted file mode 100644 index 2190cbefac4..00000000000 --- a/src/components/Patient/ShiftCreate.tsx +++ /dev/null @@ -1,402 +0,0 @@ -import careConfig from "@careConfig"; -import { navigate } from "raviger"; -import { useReducer, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import Card from "@/CAREUI/display/Card"; - -import { Cancel, Submit } from "@/components/Common/ButtonV2"; -import { FacilitySelect } from "@/components/Common/FacilitySelect"; -import Loading from "@/components/Common/Loading"; -import Page from "@/components/Common/Page"; -import { PhoneNumberValidator } from "@/components/Form/FieldValidators"; -import CheckBoxFormField from "@/components/Form/FormFields/CheckBoxFormField"; -import { FieldLabel } from "@/components/Form/FormFields/FormField"; -import PhoneNumberFormField from "@/components/Form/FormFields/PhoneNumberFormField"; -import { SelectFormField } from "@/components/Form/FormFields/SelectFormField"; -import TextAreaFormField from "@/components/Form/FormFields/TextAreaFormField"; -import TextFormField from "@/components/Form/FormFields/TextFormField"; -import { FieldChangeEvent } from "@/components/Form/FormFields/Utils"; -import PatientCategorySelect from "@/components/Patient/PatientCategorySelect"; - -import useAppHistory from "@/hooks/useAppHistory"; - -import { - BREATHLESSNESS_LEVEL, - FACILITY_TYPES, - PATIENT_CATEGORIES, - SHIFTING_VEHICLE_CHOICES, -} from "@/common/constants"; -import { phonePreg } from "@/common/validation"; - -import * as Notification from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { parsePhoneNumber } from "@/Utils/utils"; - -interface patientShiftProps { - facilityId: string; - patientId: string; -} - -export const ShiftCreate = (props: patientShiftProps) => { - const { goBack } = useAppHistory(); - const { facilityId, patientId } = props; - const [isLoading, setIsLoading] = useState(false); - const [patientCategory, setPatientCategory] = useState(); - const { t } = useTranslation(); - - const initForm: any = { - shifting_approving_facility: null, - assigned_facility: null, - emergency: "false", - is_up_shift: "true", - reason: "", - vehicle_preference: "", - comments: "", - refering_facility_contact_name: "", - refering_facility_contact_number: "", - assigned_facility_type: null, - preferred_vehicle_choice: null, - breathlessness_level: null, - patient_category: "", - ambulance_driver_name: "", - ambulance_phone_number: "", - ambulance_number: "", - }; - - let requiredFields: any = { - refering_facility_contact_name: { - errorText: "Name of contact of the current facility", - }, - refering_facility_contact_number: { - errorText: "Phone number of contact of the current facility", - invalidText: "Please enter valid phone number", - }, - reason: { - errorText: "Reason for shifting in mandatory", - invalidText: "Please enter reason for shifting", - }, - }; - - if (careConfig.wartimeShifting) { - requiredFields = { - ...requiredFields, - shifting_approving_facility: { - errorText: "Name of the referring facility", - }, - assigned_facility_type: { - errorText: "Please Select Facility Type", - }, - preferred_vehicle_choice: { - errorText: "Please Preferred Vehicle Type", - }, - breathlessness_level: { - errorText: "Severity of Breathlessness is required", - }, - }; - } - - const initError = Object.assign( - {}, - ...Object.keys(initForm).map((k) => ({ [k]: "" })), - ); - - const initialState = { - form: { ...initForm }, - errors: { ...initError }, - }; - - const { data: patientData } = useTanStackQueryInstead(routes.getPatient, { - pathParams: { - id: patientId, - }, - prefetch: !!patientId, - onResponse: ({ data }) => { - if (data) { - const patient_category = - data.last_consultation?.last_daily_round?.patient_category ?? - data.last_consultation?.category; - setPatientCategory( - PATIENT_CATEGORIES.find((c) => c.text === patient_category)?.id, - ); - } - }, - }); - - const shiftFormReducer = (state = initialState, action: any) => { - switch (action.type) { - case "set_form": { - return { - ...state, - form: action.form, - }; - } - case "set_error": { - return { - ...state, - errors: action.errors, - }; - } - case "set_field": - return { - form: { ...state.form, [action.name]: action.value }, - errors: { ...state.errors, [action.name]: action.error }, - }; - default: - return state; - } - }; - - const [state, dispatch] = useReducer(shiftFormReducer, initialState); - - const validateForm = () => { - const errors = { ...initError }; - - let isInvalidForm = false; - Object.keys(requiredFields).forEach((field) => { - switch (field) { - case "refering_facility_contact_number": { - if (!state.form[field]) { - errors[field] = requiredFields[field].errorText; - isInvalidForm = true; - } else if ( - !PhoneNumberValidator()( - parsePhoneNumber(state.form[field]) ?? "", - ) === undefined || - !phonePreg(String(parsePhoneNumber(state.form[field]))) - ) { - errors[field] = requiredFields[field].invalidText; - isInvalidForm = true; - } - return; - } - default: { - if (!state.form[field]) { - errors[field] = requiredFields[field].errorText; - isInvalidForm = true; - } - } - } - }); - - dispatch({ type: "set_error", errors }); - return !isInvalidForm; - }; - - const handleFormFieldChange = (event: FieldChangeEvent) => { - dispatch({ - type: "set_field", - name: event.name, - value: event.value, - error: "", - }); - }; - - const handleSubmit = async () => { - const validForm = validateForm(); - - if (validForm) { - setIsLoading(true); - - const data = { - status: careConfig.wartimeShifting ? "PENDING" : "APPROVED", - origin_facility: props.facilityId, - shifting_approving_facility: state.form.shifting_approving_facility?.id, - assigned_facility: state.form?.assigned_facility?.id, - assigned_facility_external: !state.form?.assigned_facility?.id - ? state.form?.assigned_facility?.name - : null, - patient: props.patientId, - emergency: state.form.emergency === "true", - is_up_shift: state.form.is_up_shift === "true", - reason: state.form.reason, - vehicle_preference: state.form.vehicle_preference, - comments: state.form.comments, - assigned_facility_type: state.form.assigned_facility_type, - preferred_vehicle_choice: state.form.preferred_vehicle_choice, - refering_facility_contact_name: - state.form.refering_facility_contact_name, - refering_facility_contact_number: parsePhoneNumber( - state.form.refering_facility_contact_number, - ), - breathlessness_level: state.form.breathlessness_level, - patient_category: patientCategory, - ambulance_driver_name: state.form.ambulance_driver_name, - ambulance_phone_number: parsePhoneNumber( - state.form.ambulance_phone_number, - ), - ambulance_number: state.form.ambulance_number, - }; - - await request(routes.createShift, { - body: data, - - onResponse: ({ res, data }) => { - setIsLoading(false); - - if (res?.ok && data) { - dispatch({ type: "set_form", form: initForm }); - Notification.Success({ - msg: "Shift request created successfully", - }); - - navigate(`/shifting/${data.id}`); - } - }, - }); - } - }; - - if (isLoading) { - return ; - } - - const field = (name: string) => ({ - name, - value: state.form[name], - onChange: handleFormFieldChange, - error: state.errors[name], - }); - - return ( - - - - - - - {careConfig.wartimeShifting && ( -
- - Name of shifting approving facility - - - handleFormFieldChange({ - name: "shifting_approving_facility", - value, - }) - } - errors={state.errors.shifting_approving_facility} - /> -
- )} - -
- {t("what_facility_assign_the_patient_to")} - - handleFormFieldChange({ name: "assigned_facility", value }) - } - freeText={true} - errors={state.errors.assigned_facility} - /> -
- - - - - - setPatientCategory(e.value)} - label="Patient Category" - /> - - {careConfig.wartimeShifting && ( - <> - option.text} - optionValue={(option) => option.text} - /> - option.text} - optionValue={(option) => option.text} - /> - option} - optionValue={(option) => option} - /> - - )} - - - - - - - - - - -
- goBack()} /> - -
-
-
- ); -}; diff --git a/src/components/Patient/Utils.ts b/src/components/Patient/Utils.ts deleted file mode 100644 index d6526086276..00000000000 --- a/src/components/Patient/Utils.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -import { PatientNotesModel } from "@/components/Facility/models"; - -import routes from "@/Utils/request/api"; -import mutate from "@/Utils/request/mutate"; -import { PatientModel } from "@/types/emr/patient"; - -export function isPatientMandatoryDataFilled(patient: PatientModel) { - return ( - patient.phone_number && - patient.emergency_phone_number && - patient.name && - patient.gender && - (patient.date_of_birth || patient.year_of_birth) && - patient.address && - patient.permanent_address && - patient.pincode && - patient.state && - patient.district && - patient.local_body && - ("medical_history" in patient ? patient.medical_history : true) && - patient.blood_group - ); -} - -export const getPatientUrl = (patient: PatientModel) => { - let patientUrl = ""; - if (!isPatientMandatoryDataFilled(patient)) { - patientUrl = `/facility/${patient.facility}/patient/${patient.id}`; - } else if ( - patient.last_consultation && - patient.last_consultation?.facility === patient.facility && - !(patient.last_consultation?.discharge_date && patient.is_active) - ) { - patientUrl = `/facility/${patient.facility}/patient/${patient.id}/consultation/${patient.last_consultation.id}`; - } else if (patient.facility) { - patientUrl = `/facility/${patient.facility}/patient/${patient.id}`; - } else { - patientUrl = `/patient/${patient.id}`; - } - return patientUrl; -}; - -export const useAddPatientNote = (options: { - patientId: string; - thread: PatientNotesModel["thread"]; - consultationId?: string; -}) => { - const queryClient = useQueryClient(); - const { patientId, thread, consultationId } = options; - - return useMutation({ - mutationFn: mutate(routes.addPatientNote, { - pathParams: { patientId }, - }), - onSuccess: (data) => { - queryClient.invalidateQueries({ - queryKey: ["notes", patientId, thread, consultationId], - }); - return data; - }, - }); -}; diff --git a/src/components/Resource/ResourceFilter.tsx b/src/components/Resource/ResourceFilter.tsx index a1d21368a4c..aa68136bf8b 100644 --- a/src/components/Resource/ResourceFilter.tsx +++ b/src/components/Resource/ResourceFilter.tsx @@ -71,21 +71,6 @@ export default function ListFilter(props: any) { }, ); - const { loading: assignedFacilityLoading } = useTanStackQueryInstead( - routes.getAnyFacility, - { - pathParams: { id: filter.assigned_facility }, - prefetch: filter.assigned_facility !== undefined, - onResponse: ({ res, data }) => { - if (res && data) { - setFilterState({ - assigned_facility_ref: filter.assigned_facility === "" ? "" : data, - }); - } - }, - }, - ); - const setFacility = (selected: any, name: string) => { setFilterState({ ...filterState, @@ -188,22 +173,6 @@ export default function ListFilter(props: any) { )} -
- Assigned facility - {filter.approving_facility && assignedFacilityLoading ? ( - - ) : ( - setFacility(obj, "assigned_facility")} - className="resource-page-filter-dropdown" - errors={""} - /> - )} -
- { {facility.name}
- - {facility.local_body_object?.name} - {facility.pincode},{" "} - + {facility.pincode}, {`Ph.: ${facility.phone_number}`}
diff --git a/src/components/TeleIcu/Icons/DoctorIcon.tsx b/src/components/TeleIcu/Icons/DoctorIcon.tsx deleted file mode 100644 index b8ea120b915..00000000000 --- a/src/components/TeleIcu/Icons/DoctorIcon.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { LegacyRef, SVGProps } from "react"; - -export const DoctorIcon = ({ ref, ...props }: SVGProps) => { - return ( - } - viewBox="0 0 14 16" - xmlns="http://www.w3.org/2000/svg" - > - - - ); -}; diff --git a/src/components/TeleIcu/Icons/PatientIcon.tsx b/src/components/TeleIcu/Icons/PatientIcon.tsx deleted file mode 100644 index 27577821041..00000000000 --- a/src/components/TeleIcu/Icons/PatientIcon.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { LegacyRef, SVGProps } from "react"; - -export const PatientIcon = ({ ref, ...props }: SVGProps) => { - return ( - } - viewBox="0 0 16 20" - xmlns="http://www.w3.org/2000/svg" - > - - - ); -}; diff --git a/src/components/Users/ManageUsers.tsx b/src/components/Users/ManageUsers.tsx deleted file mode 100644 index e0f07c97565..00000000000 --- a/src/components/Users/ManageUsers.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import { navigate } from "raviger"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import CountBlock from "@/CAREUI/display/Count"; -import CareIcon from "@/CAREUI/icons/CareIcon"; -import { AdvancedFilterButton } from "@/CAREUI/interactive/FiltersSlideover"; - -import ButtonV2 from "@/components/Common/ButtonV2"; -import Loading from "@/components/Common/Loading"; -import Page from "@/components/Common/Page"; -import UserFilter from "@/components/Users/UserFilter"; -import UserListView from "@/components/Users/UserListAndCard"; - -import useAuthUser from "@/hooks/useAuthUser"; -import useFilters from "@/hooks/useFilters"; - -import { USER_TYPES } from "@/common/constants"; - -import routes from "@/Utils/request/api"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; - -export default function ManageUsers() { - const { t } = useTranslation(); - const { - qParams, - updateQuery, - Pagination, - FilterBadges, - advancedFilter, - resultsPerPage, - } = useFilters({ - limit: 18, - cacheBlacklist: ["username"], - }); - let manageUsers: JSX.Element = <>; - const authUser = useAuthUser(); - const userIndex = USER_TYPES.indexOf(authUser.user_type); - const userTypes = authUser.is_superuser - ? [...USER_TYPES] - : USER_TYPES.slice(0, userIndex + 1); - const [activeTab, setActiveTab] = useState(0); - - const { data: homeFacilityData } = useTanStackQueryInstead( - routes.getAnyFacility, - { - pathParams: { id: qParams.home_facility }, - prefetch: !!qParams.home_facility && qParams.home_facility !== "NONE", - }, - ); - - const { data: userListData, loading: userListLoading } = - useTanStackQueryInstead(routes.getUserList, { - query: { - limit: resultsPerPage.toString(), - offset: ( - (qParams.page ? qParams.page - 1 : 0) * resultsPerPage - ).toString(), - username: qParams.username, - first_name: qParams.first_name, - last_name: qParams.last_name, - phone_number: qParams.phone_number, - alt_phone_number: qParams.alt_phone_number, - user_type: qParams.user_type, - district_id: qParams.district, - home_facility: qParams.home_facility, - last_active_days: qParams.last_active_days, - }, - }); - - useEffect(() => { - if (!qParams.state && qParams.district) { - advancedFilter.removeFilters(["district"]); - } - if (!qParams.district && qParams.state) { - advancedFilter.removeFilters(["state"]); - } - }, [advancedFilter, qParams]); - - const { data: districtData, loading: districtDataLoading } = - useTanStackQueryInstead(routes.getDistrict, { - prefetch: !!qParams.district, - pathParams: { id: qParams.district }, - }); - - const addUser = ( - navigate("/users/add")} - > - -

{t("add_new_user")}

-
- ); - - if (userListLoading || districtDataLoading || !userListData?.results) { - return ; - } - - manageUsers = ( -
- updateQuery({ username })} - searchValue={qParams.username} - activeTab={activeTab} - onTabChange={setActiveTab} - /> - -
- ); - - return ( - -
- -
-
- advancedFilter.setShow(true)} - /> - {userTypes.length && addUser} -
- - -
-
- -
- [ - badge("Username", "username"), - badge("First Name", "first_name"), - badge("Last Name", "last_name"), - phoneNumber(), - phoneNumber("WhatsApp no.", "alt_phone_number"), - badge("Role", "user_type"), - value( - "District", - "district", - qParams.district ? districtData?.name || "" : "", - ), - value( - "Home Facility", - "home_facility", - qParams.home_facility - ? qParams.home_facility === "NONE" - ? t("no_home_facility") - : homeFacilityData?.name || "" - : "", - ), - value( - "Last Active", - "last_active_days", - (() => { - if (!qParams.last_active_days) return ""; - if (qParams.last_active_days === "never") return "Never"; - return `in the last ${qParams.last_active_days} day${qParams.last_active_days > 1 ? "s" : ""}`; - })(), - ), - ]} - /> -
- -
-
{manageUsers}
-
-
- ); -} diff --git a/src/components/Users/UserFilter.tsx b/src/components/Users/UserFilter.tsx index 2899f7b69cc..66cac45b44a 100644 --- a/src/components/Users/UserFilter.tsx +++ b/src/components/Users/UserFilter.tsx @@ -1,11 +1,5 @@ -import { useTranslation } from "react-i18next"; - import FiltersSlideover from "@/CAREUI/interactive/FiltersSlideover"; -import DistrictAutocompleteFormField from "@/components/Common/DistrictAutocompleteFormField"; -import { FacilitySelect } from "@/components/Common/FacilitySelect"; -import StateAutocompleteFormField from "@/components/Common/StateAutocompleteFormField"; -import { FacilityModel } from "@/components/Facility/models"; import { FieldLabel } from "@/components/Form/FormFields/FormField"; import PhoneNumberFormField from "@/components/Form/FormFields/PhoneNumberFormField"; import TextFormField from "@/components/Form/FormFields/TextFormField"; @@ -31,7 +25,6 @@ const parsePhoneNumberForFilterParam = (phoneNumber: string) => { }; export default function UserFilter(props: any) { - const { t } = useTranslation(); const { filter, onChange, closeFilter, removeFilters } = props; const [filterState, setFilterState] = useMergeState({ first_name: filter.first_name || "", @@ -90,13 +83,6 @@ export default function UserFilter(props: any) { else setFilterState({ ...filterState, [name]: value }); }; - const field = (name: string) => ({ - name, - label: t(name), - value: filterState[name], - onChange: handleChange, - }); - return ( -
- Home Facility - - setFilterState({ - ...filterState, - home_facility: (selected as FacilityModel)?.id || "", - home_facility_ref: selected, - }) - } - selected={ - filterState.home_facility === "NONE" - ? { name: t("no_home_facility"), id: "NONE" } - : filterState.home_facility_ref - } - errors="" - multiple={false} - /> -
-
Active in last...
- -
= - { - Pleth: "pleth-waveform", - Respiration: "spo2-waveform", - - // Maps each ECG wave name to the event "ecg-waveform" - ...(Object.fromEntries( - ECG_WAVENAME_KEYS.map((key) => [key, "ecg-waveform"]), - ) as Record), - }; - -/** - * Provides the API for connecting to the Vitals Monitor WebSocket and emitting - * events for each observation. - * - * @example - * const device = new HL7DeviceClient("wss://vitals-middleware.local/observations/192.168.1.14"); - * - * device.on("SpO2", (observation) => { - * console.log(observation.value); - * }); - */ -class HL7DeviceClient extends EventEmitter { - constructor(socketUrl: string) { - super(); - this.ws = new WebSocket(socketUrl); - } - - ws: WebSocket; - - connect() { - this.ws.addEventListener("message", (event) => { - const observations = parseObservations(event.data); - - observations.forEach((observation) => { - if (observation.observation_id === "waveform") { - this.emit(WAVEFORM_KEY_MAP[observation["wave-name"]], observation); - } else { - this.emit(observation.observation_id, observation); - } - }); - }); - } - - disconnect() { - this.ws.close(); - } - - on(event: EventName, listener: (data: HL7MonitorData) => void): this { - return super.on(event, listener); - } - - emit(event: EventName, data: HL7MonitorData): boolean { - return super.emit(event, data); - } - - once(event: EventName, listener: (data: HL7MonitorData) => void): this { - return super.once(event, listener); - } - - off(event: EventName, listener: (data: HL7MonitorData) => void): this { - return super.off(event, listener); - } -} - -export default HL7DeviceClient; - -export interface HL7VitalsValueData extends VitalsDataBase, VitalsValueBase { - observation_id: - | "heart-rate" - | "ST" - | "SpO2" - | "pulse-rate" - | "respiratory-rate" - | "body-temperature1" - | "body-temperature2"; -} - -type EcgWaveName = (typeof ECG_WAVENAME_KEYS)[number]; - -export interface HL7VitalsWaveformData extends VitalsWaveformBase { - "wave-name": EcgWaveName | "Pleth" | "Respiration"; -} - -export interface HL7VitalsBloodPressureData extends VitalsDataBase { - observation_id: "blood-pressure"; - status: string; - systolic: VitalsValueBase; - diastolic: VitalsValueBase; - map: VitalsValueBase; -} - -export interface HL7DeviceConnectionData extends VitalsDataBase { - observation_id: "device-connection"; - status: "string"; -} - -export type HL7MonitorData = - | HL7VitalsValueData - | HL7VitalsWaveformData - | HL7VitalsBloodPressureData - | HL7DeviceConnectionData; - -type EventName = - | HL7MonitorData["observation_id"] - | "ecg-waveform" - | "pleth-waveform" - | "spo2-waveform"; - -const parseObservations = (data: string) => { - return JSON.parse(data || "[]") as HL7MonitorData[]; -}; diff --git a/src/components/VitalsMonitor/HL7PatientVitalsMonitor.tsx b/src/components/VitalsMonitor/HL7PatientVitalsMonitor.tsx deleted file mode 100644 index 662d2786a32..00000000000 --- a/src/components/VitalsMonitor/HL7PatientVitalsMonitor.tsx +++ /dev/null @@ -1,238 +0,0 @@ -import dayjs from "dayjs"; -import { useEffect } from "react"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import VitalsMonitorFooter from "@/components/VitalsMonitor/VitalsMonitorFooter"; -import VitalsMonitorHeader from "@/components/VitalsMonitor/VitalsMonitorHeader"; -import WaveformLabels from "@/components/VitalsMonitor/WaveformLabels"; -import { - IVitalsComponentProps, - VitalsValueBase, -} from "@/components/VitalsMonitor/types"; -import useHL7VitalsMonitor from "@/components/VitalsMonitor/useHL7VitalsMonitor"; - -import useAuthUser from "@/hooks/useAuthUser"; - -import { triggerGoal } from "@/Integrations/Plausible"; -import { classNames } from "@/Utils/utils"; - -const minutesAgo = (timestamp: string) => { - return `${dayjs().diff(dayjs(timestamp), "minute")}m ago`; -}; - -export default function HL7PatientVitalsMonitor(props: IVitalsComponentProps) { - const { connect, waveformCanvas, data, isOnline } = useHL7VitalsMonitor( - props.config, - ); - const { bed, asset } = props.patientAssetBed ?? {}; - const authUser = useAuthUser(); - - useEffect(() => { - if (isOnline) { - triggerGoal("Device Viewed", { - bedId: bed?.id, - assetId: asset?.id, - userId: authUser.id, - }); - } - }, [isOnline]); - - useEffect(() => { - connect(props.socketUrl); - }, [props.socketUrl]); - - const bpWithinMaxPersistence = dayjs(data.bp?.["date-time"]).isAfter( - props.patientCurrentBedAssignmentDate, - ); - - return ( -
- {props.hideHeader ? null : ( - - )} -
- - {/* Pulse Rate */} - ❤️ - } - /> - - {/* Blood Pressure */} -
-
- NIBP - - {bpWithinMaxPersistence - ? (data.bp?.systolic.unit ?? "--") - : "--"} - - - {data.bp?.["date-time"] && minutesAgo(data.bp?.["date-time"])} - -
-
- Sys / Dia -
-
- - {bpWithinMaxPersistence - ? (data.bp?.systolic.value ?? "--") - : "--"} - - / - - {bpWithinMaxPersistence - ? (data.bp?.diastolic.value ?? "--") - : "--"} - -
-
- - Mean - - - {bpWithinMaxPersistence ? (data.bp?.map.value ?? "--") : "--"} - -
-
- - {/* SpO2 */} - - - {/* Respiratory Rate */} - - - {/* Temperature */} -
-
- TEMP - - {data.temperature1?.unit?.replace("deg ", "°") ?? "--"} - -
-
-
- T1 - - {data.temperature1?.value ?? "--"} - -
-
- T2 - - {data.temperature2?.value ?? "--"} - -
-
- TD - - {data.temperature1?.value && data.temperature2?.value - ? Math.abs( - data.temperature1?.value - data.temperature2?.value, - ) - : "--"} - -
-
-
-
-
-
- - - No incoming data from HL7 Monitor - -
-
- - - -
-
-
- {props.hideFooter ? null : } -
- ); -} - -export const VitalsNonWaveformContent = ({ - children, -}: { - children: JSX.Element | JSX.Element[]; -}) => ( -
- {children} -
-); - -interface NonWaveformDataProps { - label: string; - attr?: VitalsValueBase; - className?: string; - suffix?: JSX.Element; -} - -const NonWaveformData = ({ - label, - attr, - className, - suffix, -}: NonWaveformDataProps) => { - return ( -
-
- {label} - {attr?.unit ?? "--"} -
- - {attr?.value ?? "--"} - - {attr?.value && suffix} -
- ); -}; diff --git a/src/components/VitalsMonitor/HL7VitalsRenderer.ts b/src/components/VitalsMonitor/HL7VitalsRenderer.ts deleted file mode 100644 index 20e9eb94acf..00000000000 --- a/src/components/VitalsMonitor/HL7VitalsRenderer.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { ChannelOptions } from "@/components/VitalsMonitor/types"; -import { lerp } from "@/components/VitalsMonitor/utils"; - -interface ChannelState { - buffer: number[]; - cursor: Position; - color: string; - deltaX: number; - transform: (value: number) => number; - chunkSize: number; - options: ChannelOptions; - rows: number; -} - -interface Position { - x: number; - y: number; -} - -interface Options { - /** - * The size of the canvas rendering context. - * - * Height should preferably be a multiple of 4. - */ - size: { width: number; height: number }; - /** - * The 2D render context of the canvas to draw the vitals waveforms on. - */ - foregroundRenderContext: CanvasRenderingContext2D; - /** - * The 2D render context of the canvas to draw the - */ - backgroundRenderContext: CanvasRenderingContext2D; - /** - * The interval at which the canvas is rendered in milliseconds. - */ - animationInterval: number; - /** - * Options for ECG channel. - */ - ecg: ChannelOptions; - /** - * Options for Pleth channel. - */ - pleth: ChannelOptions; - /** - * Options for SPO2 channel. - */ - spo2: ChannelOptions; - /** - * Duration of each row on the canvas in seconds. - */ - duration: number; -} - -/** - * Provides the API for rendering vitals waveforms on a canvas. - * - * Strategy: - * - Render frequency is set manually and is independent of the sampling rate. - * - Manages rendering of all the vitals channels. - */ -class HL7VitalsRenderer { - constructor(options: Options) { - const { - ecg, - pleth, - spo2, - size: { height: h, width: w }, - duration, - } = options; - - this.options = options; - this.state = { - ecg: { - color: "#0ffc03", - buffer: [], - cursor: { x: 0, y: 0 }, - deltaX: w / (duration * ecg.samplingRate), - transform: lerp(ecg.lowLimit, ecg.highLimit, h * 0.25, 0), - chunkSize: ecg.samplingRate * options.animationInterval * 1e-3, - options: ecg, - rows: 2, - }, - - pleth: { - color: "#ffff24", - buffer: [], - cursor: { x: 0, y: 0 }, - deltaX: w / (duration * pleth.samplingRate), - transform: lerp(pleth.lowLimit, pleth.highLimit, h * 0.75, h * 0.5), - chunkSize: pleth.samplingRate * options.animationInterval * 1e-3, - options: pleth, - rows: 1, - }, - - spo2: { - color: "#03a9f4", - buffer: [], - cursor: { x: 0, y: 0 }, - deltaX: w / (duration * spo2.samplingRate), - transform: lerp(spo2.lowLimit, spo2.highLimit, h, h * 0.75), - chunkSize: spo2.samplingRate * options.animationInterval * 1e-3, - options: spo2, - rows: 1, - }, - }; - - // Draw baseline for each channel. - this.initialize(this.state.ecg); - this.initialize(this.state.pleth); - this.initialize(this.state.spo2); - - // Start rendering. - setInterval(() => { - this.render(this.state.ecg); - this.render(this.state.pleth); - this.render(this.state.spo2); - }, options.animationInterval); - } - - private options: Options; - private state: { ecg: ChannelState; pleth: ChannelState; spo2: ChannelState }; - - /** - * Appends data to the buffer of the specified channel. - */ - append(channel: "ecg" | "pleth" | "spo2", data: number[]) { - const state = this.state[channel]; - state.buffer.push(...data.map(state.transform)); - } - - private initialize(channel: ChannelState) { - const { foregroundRenderContext: ctx, size } = this.options; - const { transform, rows, options, color } = channel; - - for (let row = 0; row < rows; row++) { - const y = transform(options.baseline) + (row * size.height) / 4; - - ctx.beginPath(); - ctx.moveTo(0, y); - ctx.lineTo(size.width, y); - ctx.strokeStyle = color; - ctx.stroke(); - } - - channel.cursor = { x: 0, y: transform(options.baseline) }; - } - - private render(channel: ChannelState) { - const { foregroundRenderContext: ctx, size } = this.options; - const { cursor, deltaX, transform } = channel; - - const xMax = size.width * channel.rows; - - ctx.beginPath(); - ctx.moveTo(...this.correctForRow(cursor)); - - ctx.strokeStyle = channel.color; - ctx.lineWidth = 1.5; - - channel.buffer.splice(0, channel.chunkSize).forEach((nextY) => { - cursor.x += deltaX; - cursor.y = nextY; - - if (cursor.x > xMax) { - cursor.x = 0; - } - - const [x, y] = this.correctForRow(cursor); - - if (x < deltaX) { - ctx.beginPath(); - ctx.moveTo(x, y); - } else { - ctx.lineTo(x, y); - } - }); - - ctx.stroke(); - - const deltaRows = Math.floor(cursor.x / size.width); - ctx.clearRect( - cursor.x - deltaRows * size.width, - 1 + transform(channel.options.highLimit) + (deltaRows * size.height) / 4, - 10, - size.height / 4, - ); - } - - /** - * Corrects the cursor position for the appropriate row if the cursor is - * outside the bounds of the canvas. - */ - private correctForRow(cursor: Position): [number, number] { - const { size } = this.options; - - const deltaRows = Math.floor(cursor.x / size.width); - - const x = cursor.x % size.width; - const y = cursor.y + (deltaRows * size.height) / 4; - - return [x, y]; - } -} - -export default HL7VitalsRenderer; diff --git a/src/components/VitalsMonitor/VentilatorDeviceClient.ts b/src/components/VitalsMonitor/VentilatorDeviceClient.ts deleted file mode 100644 index 899a015958d..00000000000 --- a/src/components/VitalsMonitor/VentilatorDeviceClient.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { EventEmitter } from "events"; - -import { - VitalsDataBase, - VitalsValueBase, - VitalsWaveformBase, -} from "@/components/VitalsMonitor/types"; - -const WAVEFORM_KEY_MAP: Record = { - P: "pressure-waveform", - F: "flow-waveform", - V: "volume-waveform", -}; - -/** - * Provides the API for connecting to the Vitals Monitor WebSocket and emitting - * events for each observation. - * - * @example - * const device = new VentilatorDeviceClient("wss://vitals-middleware.local/observations/192.168.1.14"); - * - * device.on("FiO2", (observation) => { - * console.log(observation.value); - * }); - */ -class VentilatorDeviceClient extends EventEmitter { - constructor(socketUrl: string) { - super(); - this.ws = new WebSocket(socketUrl); - } - - ws: WebSocket; - - connect() { - this.ws.addEventListener("message", (event) => { - const observations = parseObservations(event.data); - - observations.forEach((observation) => { - if (observation.observation_id === "waveform") { - this.emit(WAVEFORM_KEY_MAP[observation["wave-name"]], observation); - } else { - this.emit(observation.observation_id, observation); - } - }); - }); - } - - disconnect() { - this.ws.close(); - } - - on(event: EventName, listener: (data: VentilatorData) => void): this { - return super.on(event, listener); - } - - emit(event: EventName, data: VentilatorData): boolean { - return super.emit(event, data); - } - - once(event: EventName, listener: (data: VentilatorData) => void): this { - return super.once(event, listener); - } - - off(event: EventName, listener: (data: VentilatorData) => void): this { - return super.off(event, listener); - } -} - -export default VentilatorDeviceClient; - -export interface VentilatorVitalsValueData - extends VitalsDataBase, - VitalsValueBase { - observation_id: "Mode" | "PEEP" | "R.Rate" | "Insp-Time" | "FiO2"; -} - -export interface VentilatorVitalsWaveformData extends VitalsWaveformBase { - "wave-name": "P" | "F" | "V"; -} - -export type VentilatorData = - | VentilatorVitalsValueData - | VentilatorVitalsWaveformData; - -type EventName = - | VentilatorData["observation_id"] - | "pressure-waveform" - | "flow-waveform" - | "volume-waveform"; - -const parseObservations = (data: string) => { - return JSON.parse(data || "[]") as VentilatorData[]; -}; diff --git a/src/components/VitalsMonitor/VentilatorPatientVitalsMonitor.tsx b/src/components/VitalsMonitor/VentilatorPatientVitalsMonitor.tsx deleted file mode 100644 index c75ee5f2510..00000000000 --- a/src/components/VitalsMonitor/VentilatorPatientVitalsMonitor.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { useEffect } from "react"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import { VitalsNonWaveformContent } from "@/components/VitalsMonitor/HL7PatientVitalsMonitor"; -import VitalsMonitorFooter from "@/components/VitalsMonitor/VitalsMonitorFooter"; -import VitalsMonitorHeader from "@/components/VitalsMonitor/VitalsMonitorHeader"; -import WaveformLabels from "@/components/VitalsMonitor/WaveformLabels"; -import { - IVitalsComponentProps, - VitalsValueBase, -} from "@/components/VitalsMonitor/types"; -import useVentilatorVitalsMonitor from "@/components/VitalsMonitor/useVentilatorVitalsMonitor"; - -import { classNames } from "@/Utils/utils"; - -export default function VentilatorPatientVitalsMonitor( - props: IVitalsComponentProps, -) { - const { connect, waveformCanvas, data, isOnline } = - useVentilatorVitalsMonitor(props.config); - - useEffect(() => { - connect(props.socketUrl); - }, [props.socketUrl]); - - return ( -
- {props.hideHeader ? null : ( - - )} -
-
-
- - No incoming data from Ventilator -
-
- - - -
-
- - - - - - -
- {props.hideFooter ? null : ( - - )} -
- ); -} - -interface NonWaveformDataProps { - label: string; - attr?: VitalsValueBase; - className?: string; -} - -const NonWaveformData = ({ label, attr, className }: NonWaveformDataProps) => { - return ( -
-
- {label} - {attr?.unit ?? "--"} -
- - {attr?.value ?? "--"} - -
- ); -}; diff --git a/src/components/VitalsMonitor/VentilatorWaveformsRenderer.ts b/src/components/VitalsMonitor/VentilatorWaveformsRenderer.ts deleted file mode 100644 index 2d1f94a58ad..00000000000 --- a/src/components/VitalsMonitor/VentilatorWaveformsRenderer.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { ChannelOptions } from "@/components/VitalsMonitor/types"; -import { lerp } from "@/components/VitalsMonitor/utils"; - -interface ChannelState { - buffer: number[]; - cursor: Position; - color: string; - deltaX: number; - transform: (value: number) => number; - chunkSize: number; - options: ChannelOptions; - rows: number; -} - -interface Position { - x: number; - y: number; -} - -interface Options { - /** - * The size of the canvas rendering context. - * - * Height should preferably be a multiple of 4. - */ - size: { width: number; height: number }; - /** - * The 2D render context of the canvas to draw the vitals waveforms on. - */ - foregroundRenderContext: CanvasRenderingContext2D; - /** - * The 2D render context of the canvas to draw the - */ - backgroundRenderContext: CanvasRenderingContext2D; - /** - * The interval at which the canvas is rendered in milliseconds. - */ - animationInterval: number; - /** - * Options for Pressure channel. - */ - pressure: ChannelOptions; - /** - * Options for Flow channel. - */ - flow: ChannelOptions; - /** - * Options for Volume channel. - */ - volume: ChannelOptions; - /** - * Duration of each row on the canvas in seconds. - */ - duration: number; -} - -/** - * Provides the API for rendering vitals waveforms on a canvas. - * - * Strategy: - * - Render frequency is set manually and is independent of the sampling rate. - * - Manages rendering of all the vitals channels. - */ -class VentilatorVitalsRenderer { - constructor(options: Options) { - const { - pressure, - flow, - volume, - size: { height: h, width: w }, - duration, - } = options; - - this.options = options; - this.state = { - pressure: { - color: "#0ffc03", - buffer: [], - cursor: { x: 0, y: 0 }, - deltaX: w / (duration * pressure.samplingRate), - transform: lerp(pressure.lowLimit, pressure.highLimit, h * 0.33, 0), - chunkSize: pressure.samplingRate * options.animationInterval * 1e-3, - options: pressure, - rows: 1, - }, - - flow: { - color: "#ffff24", - buffer: [], - cursor: { x: 0, y: 0 }, - deltaX: w / (duration * flow.samplingRate), - transform: lerp(flow.lowLimit, flow.highLimit, h * 0.67, h * 0.33), - chunkSize: flow.samplingRate * options.animationInterval * 1e-3, - options: flow, - rows: 1, - }, - - volume: { - color: "#03a9f4", - buffer: [], - cursor: { x: 0, y: 0 }, - deltaX: w / (duration * volume.samplingRate), - transform: lerp(volume.lowLimit, volume.highLimit, h, h * 0.67), - chunkSize: volume.samplingRate * options.animationInterval * 1e-3, - options: volume, - rows: 1, - }, - }; - - // Draw baseline for each channel. - this.initialize(this.state.pressure); - this.initialize(this.state.flow); - this.initialize(this.state.volume); - - // Start rendering. - setInterval(() => { - this.render(this.state.pressure); - this.render(this.state.flow); - this.render(this.state.volume); - }, options.animationInterval); - } - - private options: Options; - private state: { - pressure: ChannelState; - flow: ChannelState; - volume: ChannelState; - }; - - /** - * Appends data to the buffer of the specified channel. - */ - append(channel: "pressure" | "flow" | "volume", data: number[]) { - const state = this.state[channel]; - state.buffer.push(...data.map(state.transform)); - } - - private initialize(channel: ChannelState) { - const { foregroundRenderContext: ctx, size } = this.options; - const { transform, rows, options, color } = channel; - - for (let row = 0; row < rows; row++) { - const y = transform(options.baseline) + (row * size.height) / 4; - - ctx.beginPath(); - ctx.moveTo(0, y); - ctx.lineTo(size.width, y); - ctx.strokeStyle = color; - ctx.stroke(); - } - - channel.cursor = { x: 0, y: transform(options.baseline) }; - } - - private render(channel: ChannelState) { - const { foregroundRenderContext: ctx, size } = this.options; - const { cursor, deltaX, transform } = channel; - - const xMax = size.width * channel.rows; - - ctx.beginPath(); - ctx.moveTo(...this.correctForRow(cursor)); - - ctx.strokeStyle = channel.color; - ctx.lineWidth = 1.5; - - channel.buffer.splice(0, channel.chunkSize).forEach((nextY) => { - cursor.x += deltaX; - cursor.y = nextY; - - if (cursor.x > xMax) { - cursor.x = 0; - } - - const [x, y] = this.correctForRow(cursor); - - if (x < deltaX) { - ctx.beginPath(); - ctx.moveTo(x, y); - } else { - ctx.lineTo(x, y); - } - }); - - ctx.stroke(); - - const deltaRows = Math.floor(cursor.x / size.width); - ctx.clearRect( - cursor.x - deltaRows * size.width, - 1 + transform(channel.options.highLimit) + (deltaRows * size.height) / 3, - 10, - size.height / 3 + 5, - ); - } - - /** - * Corrects the cursor position for the appropriate row if the cursor is - * outside the bounds of the canvas. - */ - private correctForRow(cursor: Position): [number, number] { - const { size } = this.options; - - const deltaRows = Math.floor(cursor.x / size.width); - - const x = cursor.x % size.width; - const y = cursor.y + (deltaRows * size.height) / 4; - - return [x, y]; - } -} - -export default VentilatorVitalsRenderer; diff --git a/src/components/VitalsMonitor/VitalsMonitorFooter.tsx b/src/components/VitalsMonitor/VitalsMonitorFooter.tsx deleted file mode 100644 index c7077f79372..00000000000 --- a/src/components/VitalsMonitor/VitalsMonitorFooter.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { AssetData } from "@/components/Assets/AssetTypes"; -import AssetInfoPopover from "@/components/Common/AssetInfoPopover"; - -interface IVitalsMonitorFooterProps { - asset?: AssetData; -} - -const VitalsMonitorFooter = ({ asset }: IVitalsMonitorFooterProps) => { - return ( -
-

{asset?.name}

- -
- ); -}; - -export default VitalsMonitorFooter; diff --git a/src/components/VitalsMonitor/VitalsMonitorHeader.tsx b/src/components/VitalsMonitor/VitalsMonitorHeader.tsx deleted file mode 100644 index 6bf1d974bfe..00000000000 --- a/src/components/VitalsMonitor/VitalsMonitorHeader.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { Link } from "raviger"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import { PatientAssetBed } from "@/components/Assets/AssetTypes"; - -import { formatPatientAge } from "@/Utils/utils"; - -interface VitalsMonitorHeaderProps { - patientAssetBed?: PatientAssetBed; -} - -const VitalsMonitorHeader = ({ patientAssetBed }: VitalsMonitorHeaderProps) => { - const { patient, bed } = patientAssetBed ?? {}; - return ( -
-
- {patient ? ( - - {patient?.name} - - ) : ( - - - No Patient - - )} - {patient && ( - - {`${formatPatientAge(patient)}; `} - - )} -
-
- {bed && ( - - - - {bed.name} - - - - {bed.location_object?.name} - - - )} -
-
- ); -}; - -export default VitalsMonitorHeader; diff --git a/src/components/VitalsMonitor/WaveformLabels.tsx b/src/components/VitalsMonitor/WaveformLabels.tsx deleted file mode 100644 index 3fac3d53880..00000000000 --- a/src/components/VitalsMonitor/WaveformLabels.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { classNames } from "@/Utils/utils"; - -interface Props { - labels: Record; -} - -export default function WaveformLabels({ labels }: Props) { - return ( -
- {Object.entries(labels).map(([label, className], i) => ( - - {label} - - ))} -
- ); -} diff --git a/src/components/VitalsMonitor/types.ts b/src/components/VitalsMonitor/types.ts deleted file mode 100644 index 1e6b3427479..00000000000 --- a/src/components/VitalsMonitor/types.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { PatientAssetBed } from "@/components/Assets/AssetTypes"; -import { getVitalsCanvasSizeAndDuration } from "@/components/VitalsMonitor/utils"; - -export interface VitalsDataBase { - device_id: string; - "date-time": string; - "patient-id": string; - "patient-name": string; -} - -export interface VitalsValueBase extends VitalsDataBase { - value: number; - unit: string; - interpretation: string; - "low-limit": number; - "high-limit": number; -} - -export interface VitalsWaveformBase extends VitalsDataBase { - observation_id: "waveform"; - resolution: string; - "sampling rate": string; - "data-baseline": number; - "data-low-limit": number; - "data-high-limit": number; - data: string; -} - -export interface ChannelOptions { - /** - * The baseline value for this channel. - */ - baseline: number; - /** - * The minimum value that can be displayed for this channel. - */ - lowLimit: number; - /** - * The maximum value that can be displayed for this channel. - */ - highLimit: number; - /** - * No. of data points expected to be received per second. - */ - samplingRate: number; -} - -export interface IVitalsComponentProps { - patientCurrentBedAssignmentDate?: string; - patientAssetBed?: PatientAssetBed; - socketUrl: string; - config?: ReturnType; - hideHeader?: boolean; - hideFooter?: boolean; -} diff --git a/src/components/VitalsMonitor/useHL7VitalsMonitor.ts b/src/components/VitalsMonitor/useHL7VitalsMonitor.ts deleted file mode 100644 index 49843c0662f..00000000000 --- a/src/components/VitalsMonitor/useHL7VitalsMonitor.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { useCallback, useRef, useState } from "react"; - -import HL7DeviceClient, { - HL7MonitorData, - HL7VitalsWaveformData, -} from "@/components/VitalsMonitor/HL7DeviceClient"; -import HL7VitalsRenderer from "@/components/VitalsMonitor/HL7VitalsRenderer"; -import { - ChannelOptions, - IVitalsComponentProps, - VitalsDataBase, - VitalsValueBase as VitalsValue, -} from "@/components/VitalsMonitor/types"; -import { - getChannel, - getVitalsCanvasSizeAndDuration, -} from "@/components/VitalsMonitor/utils"; - -import useCanvas from "@/hooks/useCanvas"; - -interface VitalsBPValue extends VitalsDataBase { - systolic: VitalsValue; - diastolic: VitalsValue; - map: VitalsValue; -} - -export default function useHL7VitalsMonitor( - config?: IVitalsComponentProps["config"], -) { - const waveformForegroundCanvas = useCanvas(); - const waveformBackgroundCanvas = useCanvas(); - const rendererConfig = config ?? getVitalsCanvasSizeAndDuration(); - - // Non waveform data states. - const [isOnline, setIsOnline] = useState(false); - const [pulseRate, setPulseRate] = useState(); - const [heartRate, setHeartRate] = useState(); - const [bp, setBp] = useState(); - const [spo2, setSpo2] = useState(); - const [respiratoryRate, setRespiratoryRate] = useState(); - const [temperature1, setTemperature1] = useState(); - const [temperature2, setTemperature2] = useState(); - - // Waveform data states. - const device = useRef(); - const renderer = useRef(null); - - const ecgOptionsRef = useRef(); - const plethOptionsRef = useRef(); - const spo2OptionsRef = useRef(); - - const connect = useCallback( - (socketUrl: string) => { - setIsOnline(false); - device.current?.disconnect(); - - device.current = new HL7DeviceClient(socketUrl); - device.current.connect(); - - function obtainRenderer() { - if ( - !ecgOptionsRef.current || - !plethOptionsRef.current || - !spo2OptionsRef.current || - !waveformForegroundCanvas.contextRef.current || - !waveformBackgroundCanvas.contextRef.current - ) - return; - - setIsOnline(true); - - renderer.current = new HL7VitalsRenderer({ - foregroundRenderContext: waveformForegroundCanvas.contextRef.current, - backgroundRenderContext: waveformBackgroundCanvas.contextRef.current, - animationInterval: 50, - ecg: ecgOptionsRef.current, - pleth: plethOptionsRef.current, - spo2: spo2OptionsRef.current, - ...rendererConfig, - }); - - const _renderer = renderer.current; - device.current?.on("ecg-waveform", ingestTo(_renderer, "ecg")); - device.current?.on("pleth-waveform", ingestTo(_renderer, "pleth")); - device.current?.on("spo2-waveform", ingestTo(_renderer, "spo2")); - - const hook = (set: (data: any) => void) => (d: HL7MonitorData) => - set(d); - device.current?.on("pulse-rate", hook(setPulseRate)); - device.current?.on("heart-rate", hook(setHeartRate)); - device.current?.on("SpO2", hook(setSpo2)); - device.current?.on("respiratory-rate", hook(setRespiratoryRate)); - device.current?.on("body-temperature1", hook(setTemperature1)); - device.current?.on("body-temperature2", hook(setTemperature2)); - device.current?.on("blood-pressure", hook(setBp)); - } - - device.current.once("ecg-waveform", (observation) => { - ecgOptionsRef.current = getChannel( - observation as HL7VitalsWaveformData, - ); - obtainRenderer(); - }); - - device.current.once("pleth-waveform", (observation) => { - plethOptionsRef.current = getChannel( - observation as HL7VitalsWaveformData, - ); - obtainRenderer(); - }); - - device.current.once("spo2-waveform", (observation) => { - spo2OptionsRef.current = getChannel( - observation as HL7VitalsWaveformData, - ); - obtainRenderer(); - }); - }, - [waveformForegroundCanvas.contextRef, waveformBackgroundCanvas], - ); - - return { - connect, - waveformCanvas: { - foreground: waveformForegroundCanvas, - background: waveformBackgroundCanvas, - size: rendererConfig.size, - }, - data: { - pulseRate, - heartRate, - bp, - spo2, - respiratoryRate, - temperature1, - temperature2, - }, - device, - isOnline, - }; -} - -const ingestTo = ( - vitalsRenderer: HL7VitalsRenderer, - channel: "ecg" | "pleth" | "spo2", -) => { - return (observation: HL7MonitorData) => { - vitalsRenderer.append( - channel, - (observation as HL7VitalsWaveformData).data - .split(" ") - .map((x) => parseInt(x)) || [], - ); - }; -}; diff --git a/src/components/VitalsMonitor/useVentilatorVitalsMonitor.ts b/src/components/VitalsMonitor/useVentilatorVitalsMonitor.ts deleted file mode 100644 index e52bced2a4b..00000000000 --- a/src/components/VitalsMonitor/useVentilatorVitalsMonitor.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { useCallback, useRef, useState } from "react"; - -import VentilatorDeviceClient, { - VentilatorData, - VentilatorVitalsWaveformData, -} from "@/components/VitalsMonitor/VentilatorDeviceClient"; -import VentilatorVitalsRenderer from "@/components/VitalsMonitor/VentilatorWaveformsRenderer"; -import { - ChannelOptions, - IVitalsComponentProps, - VitalsValueBase as VitalsValue, -} from "@/components/VitalsMonitor/types"; -import { - getChannel, - getVitalsCanvasSizeAndDuration, -} from "@/components/VitalsMonitor/utils"; - -import useCanvas from "@/hooks/useCanvas"; - -export default function useVentilatorVitalsMonitor( - config?: IVitalsComponentProps["config"], -) { - const waveformForegroundCanvas = useCanvas(); - const waveformBackgroundCanvas = useCanvas(); - const rendererConfig = config ?? getVitalsCanvasSizeAndDuration(); - - // Non waveform data states. - const [isOnline, setIsOnline] = useState(false); - const [peep, setPeep] = useState(); - const [respRate, setRespRate] = useState(); - const [inspTime, setInspTime] = useState(); - const [fio2, setFio2] = useState(); - - // Waveform data states. - const device = useRef(); - const renderer = useRef(null); - - const pressureOptionsRef = useRef(); - const flowOptionsRef = useRef(); - const volumeOptionsRef = useRef(); - - const connect = useCallback( - (socketUrl: string) => { - setIsOnline(false); - device.current?.disconnect(); - - device.current = new VentilatorDeviceClient(socketUrl); - device.current.connect(); - - function obtainRenderer() { - if ( - !pressureOptionsRef.current || - !flowOptionsRef.current || - !volumeOptionsRef.current || - !waveformForegroundCanvas.contextRef.current || - !waveformBackgroundCanvas.contextRef.current - ) - return; - - setIsOnline(true); - - renderer.current = new VentilatorVitalsRenderer({ - foregroundRenderContext: waveformForegroundCanvas.contextRef.current, - backgroundRenderContext: waveformBackgroundCanvas.contextRef.current, - animationInterval: 50, - pressure: pressureOptionsRef.current, - flow: flowOptionsRef.current, - volume: volumeOptionsRef.current, - ...rendererConfig, - }); - - const _renderer = renderer.current; - device.current?.on( - "pressure-waveform", - ingestTo(_renderer, "pressure"), - ); - device.current?.on("flow-waveform", ingestTo(_renderer, "flow")); - device.current?.on("volume-waveform", ingestTo(_renderer, "volume")); - - const hook = (set: (data: any) => void) => (d: VentilatorData) => - set(d); - device.current?.on("PEEP", hook(setPeep)); - device.current?.on("R.Rate", hook(setRespRate)); - device.current?.on("Insp-Time", hook(setInspTime)); - device.current?.on("FiO2", hook(setFio2)); - } - - device.current.once("pressure-waveform", (observation) => { - pressureOptionsRef.current = getChannel( - observation as VentilatorVitalsWaveformData, - ); - obtainRenderer(); - }); - - device.current.once("flow-waveform", (observation) => { - flowOptionsRef.current = getChannel( - observation as VentilatorVitalsWaveformData, - ); - obtainRenderer(); - }); - - device.current.once("volume-waveform", (observation) => { - volumeOptionsRef.current = getChannel( - observation as VentilatorVitalsWaveformData, - ); - obtainRenderer(); - }); - }, - [waveformForegroundCanvas.contextRef, waveformBackgroundCanvas], - ); - - return { - connect, - waveformCanvas: { - foreground: waveformForegroundCanvas, - background: waveformBackgroundCanvas, - size: rendererConfig.size, - }, - data: { - peep, - respRate, - inspTime, - fio2, - }, - device, - isOnline, - }; -} - -const ingestTo = ( - vitalsRenderer: VentilatorVitalsRenderer, - channel: "pressure" | "flow" | "volume", -) => { - return (observation: VentilatorData) => { - vitalsRenderer.append( - channel, - (observation as VentilatorVitalsWaveformData).data - .split(" ") - .map((x) => parseInt(x)) || [], - ); - }; -}; diff --git a/src/components/VitalsMonitor/useVitalsAspectRatioConfig.ts b/src/components/VitalsMonitor/useVitalsAspectRatioConfig.ts deleted file mode 100644 index 92271ba1d12..00000000000 --- a/src/components/VitalsMonitor/useVitalsAspectRatioConfig.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { getVitalsCanvasSizeAndDuration } from "@/components/VitalsMonitor/utils"; - -import useBreakpoints from "@/hooks/useBreakpoints"; - -export default function useVitalsAspectRatioConfig( - breakpointsMap: Parameters>[0], -) { - const vitalsAspectRatio = useBreakpoints(breakpointsMap); - - const config = getVitalsCanvasSizeAndDuration(vitalsAspectRatio); - const hash = JSON.stringify(config); - - return { config, hash }; -} diff --git a/src/components/VitalsMonitor/utils.ts b/src/components/VitalsMonitor/utils.ts deleted file mode 100644 index 9f9ec0ac023..00000000000 --- a/src/components/VitalsMonitor/utils.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { AssetClass, AssetData } from "@/components/Assets/AssetTypes"; -import { - ChannelOptions, - VitalsWaveformBase, -} from "@/components/VitalsMonitor/types"; - -/** - * Maps a value from one range to another. - * Or in mathematical terms, it performs a linear interpolation. - * - * @param x0 The lower bound of the input range. - * @param x1 The upper bound of the input range. - * @param y0 The lower bound of the output range. - * @param y1 The upper bound of the output range. - * @returns A function that maps a value from the input range to the output range. - * @example - * const transform = lerp(0, 100, 0, 1); - * transform(50); // 0.5 - * transform(100); // 1 - * transform(0); // 0 - * transform(200); // 2 - * transform(-100); // -1 - */ -export const lerp = (x0: number, x1: number, y0: number, y1: number) => { - // Original formula: - // y = y0 + (x - x0) * (y1 - y0) / (x1 - x0) - // - // Simplified formula: - // - // 1. Take the first order partial derivative out - // m = (y1 - y0) / (x1 - x0) - // ∴ y = y0 + (x - x0) * m - // - // 2. Expanding the (x - x0) term yields: - // ∴ y = y0 + x * m - x0 * m - // - // 3. Simplify the terms by grouping the constants together: - // c = y0 - x0 * m - // ∴ y = m * x + c - - const m = (y1 - y0) / (x1 - x0); - const c = y0 - x0 * m; - - return (x: number) => m * x + c; -}; - -export const getChannel = (observation: VitalsWaveformBase): ChannelOptions => { - return { - samplingRate: parseInt( - observation["sampling rate"]?.replace("/sec", "") ?? "-1", - ), - baseline: observation["data-baseline"] ?? 0, - lowLimit: observation["data-low-limit"] ?? 0, - highLimit: observation["data-high-limit"] ?? 0, - }; -}; - -const DEFAULT_RATIO = 13 / 11; -const DEFAULT_DURATION = 7; -const DEFAULT_SCALE = 38 * 11; - -/** - * Returns the size of the canvas for the vitals monitor. - * @param aspectRatio The aspect ratio of the canvas. Defaults to 13:11. - * @param scale The scale of the canvas. Defaults to 38 * 11. - * @returns The computed size of the canvas. - */ -export const getVitalsCanvasSizeAndDuration = ( - ratio = DEFAULT_RATIO, - scale = DEFAULT_SCALE, -) => { - return { - size: { - width: scale * ratio, - height: scale, - }, - duration: DEFAULT_DURATION * (ratio / DEFAULT_RATIO), - }; -}; - -export const getVitalsMonitorSocketUrl = (asset: AssetData) => { - if ( - asset.asset_class !== AssetClass.HL7MONITOR && - asset.asset_class !== AssetClass.VENTILATOR - ) { - throw "getVitalsMonitorSocketUrl can be invoked only for HL7MONITOR or VENTILATOR assets"; - } - - const middleware = asset.resolved_middleware?.hostname; - const ipAddress = asset.meta?.local_ip_address; - - if (middleware && ipAddress) { - return `wss://${middleware}/observations/${ipAddress}`; - } -}; diff --git a/src/pages/Encounters/tabs/EncounterNeurologicalMonitoringTab.tsx b/src/pages/Encounters/tabs/EncounterNeurologicalMonitoringTab.tsx deleted file mode 100644 index e3ad8642191..00000000000 --- a/src/pages/Encounters/tabs/EncounterNeurologicalMonitoringTab.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { NeurologicalTable } from "@/components/Facility/Consultations/NeurologicalTables"; - -import { EncounterTabProps } from "@/pages/Encounters/EncounterShow"; - -export const EncounterNeurologicalMonitoringTab = ( - props: EncounterTabProps, -) => { - return ( - - ); -}; diff --git a/src/pages/Encounters/tabs/EncounterNursingTab.tsx b/src/pages/Encounters/tabs/EncounterNursingTab.tsx deleted file mode 100644 index 8a5011d3294..00000000000 --- a/src/pages/Encounters/tabs/EncounterNursingTab.tsx +++ /dev/null @@ -1,262 +0,0 @@ -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import Loading from "@/components/Common/Loading"; -import Pagination from "@/components/Common/Pagination"; -import LogUpdateAnalyseTable from "@/components/Facility/Consultations/LogUpdateAnalyseTable"; -import { - NursingPlotFields, - NursingPlotRes, - RoutineAnalysisRes, - RoutineFields, -} from "@/components/Facility/models"; - -import { NURSING_CARE_PROCEDURES, PAGINATION_LIMIT } from "@/common/constants"; - -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import { EncounterTabProps } from "@/pages/Encounters/EncounterShow"; - -const REVERSE_CHOICES = { - appetite: { - 1: "INCREASED", - 2: "SATISFACTORY", - 3: "REDUCED", - 4: "NO_TASTE_FOR_FOOD", - 5: "CANNOT_BE_ASSESSED", - }, - bladder_drainage: { - 1: "NORMAL", - 2: "CONDOM_CATHETER", - 3: "DIAPER", - 4: "INTERMITTENT_CATHETER", - 5: "CONTINUOUS_INDWELLING_CATHETER", - 6: "CONTINUOUS_SUPRAPUBIC_CATHETER", - 7: "UROSTOMY", - }, - bladder_issue: { - 0: "NO_ISSUES", - 1: "INCONTINENCE", - 2: "RETENTION", - 3: "HESITANCY", - }, - bowel_issue: { - 0: "NO_DIFFICULTY", - 1: "CONSTIPATION", - 2: "DIARRHOEA", - }, - nutrition_route: { - 1: "ORAL", - 2: "RYLES_TUBE", - 3: "GASTROSTOMY_OR_JEJUNOSTOMY", - 4: "PEG", - 5: "PARENTERAL_TUBING_FLUID", - 6: "PARENTERAL_TUBING_TPN", - }, - oral_issue: { - 0: "NO_ISSUE", - 1: "DYSPHAGIA", - 2: "ODYNOPHAGIA", - }, - is_experiencing_dysuria: { - true: "yes", - false: "no", - }, - urination_frequency: { - 1: "NORMAL", - 2: "DECREASED", - 3: "INCREASED", - }, - sleep: { - 1: "EXCESSIVE", - 2: "SATISFACTORY", - 3: "UNSATISFACTORY", - 4: "NO_SLEEP", - }, -} as const; - -const ROUTINE_ROWS = [ - { field: "sleep" } as const, - { field: "bowel_issue" } as const, - { title: "Bladder" } as const, - { subField: true, field: "bladder_drainage" } as const, - { subField: true, field: "bladder_issue" } as const, - { subField: true, field: "is_experiencing_dysuria" } as const, - { subField: true, field: "urination_frequency" } as const, - { title: "Nutrition" } as const, - { subField: true, field: "nutrition_route" } as const, - { subField: true, field: "oral_issue" } as const, - { subField: true, field: "appetite" } as const, -]; - -const NursingPlot = (props: EncounterTabProps) => { - const { t } = useTranslation(); - const [results, setResults] = useState<{ [date: string]: NursingPlotRes }>( - {}, - ); - const [currentPage, setCurrentPage] = useState(1); - const [totalCount, setTotalCount] = useState(0); - - useEffect(() => { - const fetchDailyRounds = async ( - currentPage: number, - consultationId: string, - ) => { - const { res, data } = await request(routes.dailyRoundsAnalyse, { - body: { page: currentPage, fields: NursingPlotFields }, - pathParams: { consultationId }, - }); - if (res?.ok && data) { - setResults(data.results as { [date: string]: NursingPlotRes }); - setTotalCount(data.count); - } - }; - - fetchDailyRounds(currentPage, props.encounter.id); - }, [props.encounter.id, currentPage]); - - const handlePagination = (page: number) => setCurrentPage(page); - - let fieldsToDisplay = new Set(); - - /** - * Transforms nursing procedure results into a structured format where dates are mapped to procedures and their descriptions. - * Groups nursing data by date, collecting unique procedures and their corresponding descriptions. - */ - const tableData = Object.entries(results).reduce( - (acc: Record>, [date, result]) => { - if ("nursing" in result) { - result.nursing.forEach((field) => { - if (field.procedure && !acc[date]) acc[date] = {}; - acc[date][field.procedure] = field.description; - // Add procedure to the set of procedures to display - fieldsToDisplay.add(field.procedure); - }); - } - return acc; - }, - {}, - ); - - // First, let's get the type from NURSING_CARE_PROCEDURES - type NursingCareProcedure = (typeof NURSING_CARE_PROCEDURES)[number]; - - // Then update the fieldsToDisplay filtering with type assertion - fieldsToDisplay = new Set( - Array.from(fieldsToDisplay).filter((field): field is NursingCareProcedure => - NURSING_CARE_PROCEDURES.includes(field as NursingCareProcedure), - ), - ); - - const rows = Array.from(fieldsToDisplay).map((procedure) => ({ - field: procedure, - title: t(`NURSING_CARE_PROCEDURE__${procedure}`), - })); - - return ( -
-
- {fieldsToDisplay.size == 0 ? ( -
-
- {t("no_data_found")} -
-
- ) : ( - - )} -
- - {totalCount > PAGINATION_LIMIT && fieldsToDisplay.size > 0 && ( -
- -
- )} -
- ); -}; - -const RoutineSection = (props: EncounterTabProps) => { - const { t } = useTranslation(); - const [page, setPage] = useState(1); - const [totalCount, setTotalCount] = useState(); - const [results, setResults] = useState>(); - - useEffect(() => { - const getData = async () => { - const { data } = await request(routes.dailyRoundsAnalyse, { - body: { fields: RoutineFields, page }, - pathParams: { consultationId: props.encounter.id }, - }); - if (!data) { - return; - } - setTotalCount(data.count); - setResults( - Object.fromEntries( - Object.entries(data.results).filter(([_, value]) => - Object.entries(value).some(([k, v]) => k !== "id" && v != null), - ), - ) as typeof results, - ); - }; - - getData(); - }, [page, props.encounter.id]); - - if (results == null) { - return ; - } - - if (Object.keys(results).length === 0) { - return ( -
-
- {t("no_data_found")} -
-
- ); - } - - return ( -
- - - {totalCount != null && totalCount > PAGINATION_LIMIT && ( -
- -
- )} -
- ); -}; - -export default function EncounterNursingTab(props: EncounterTabProps) { - const { t } = useTranslation(); - return ( - <> -
-

{t("routine")}

- -
-
-

{t("nursing_care")}

- -
- - ); -} diff --git a/src/pages/Encounters/tabs/EncounterPressureSoreTab.tsx b/src/pages/Encounters/tabs/EncounterPressureSoreTab.tsx deleted file mode 100644 index 6ff02823176..00000000000 --- a/src/pages/Encounters/tabs/EncounterPressureSoreTab.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { PressureSoreDiagrams } from "@/components/Facility/Consultations/PressureSoreDiagrams"; - -import { EncounterTabProps } from "@/pages/Encounters/EncounterShow"; - -export const EncounterPressureSoreTab = (props: EncounterTabProps) => { - return ; -}; diff --git a/src/pages/Facility/FacilitiesPage.tsx b/src/pages/Facility/FacilitiesPage.tsx index 31df4d77f49..8e8ac4c9562 100644 --- a/src/pages/Facility/FacilitiesPage.tsx +++ b/src/pages/Facility/FacilitiesPage.tsx @@ -12,7 +12,6 @@ import { Card } from "@/components/ui/card"; import Loading from "@/components/Common/Loading"; import SearchByMultipleFields from "@/components/Common/SearchByMultipleFields"; -import FacilityFilter from "@/components/Facility/FacilityFilter"; import { FacilityModel } from "@/components/Facility/models"; import useFilters from "@/hooks/useFilters"; @@ -141,7 +140,6 @@ export function FacilitiesPage() { Filter -
{/* [ diff --git a/src/pages/Facility/FacilityDetailsPage.tsx b/src/pages/Facility/FacilityDetailsPage.tsx index 880fd607213..dfb2d7ff173 100644 --- a/src/pages/Facility/FacilityDetailsPage.tsx +++ b/src/pages/Facility/FacilityDetailsPage.tsx @@ -168,13 +168,7 @@ export function FacilityDetailsPage({ id }: Props) {

{facility.name}

- {[ - facility.address, - facility.local_body_object?.name, - facility.district_object?.name, - ] - .filter(Boolean) - .join(", ")} + {[facility.address].filter(Boolean).join(", ")}

diff --git a/src/pages/Facility/components/FacilityCard.tsx b/src/pages/Facility/components/FacilityCard.tsx index 50b67518dc2..e1b238efb0e 100644 --- a/src/pages/Facility/components/FacilityCard.tsx +++ b/src/pages/Facility/components/FacilityCard.tsx @@ -35,13 +35,7 @@ export function FacilityCard({ facility, className }: Props) { {/* @ts-expect-error Type is not defined properly */} {facility.facility_type?.name}

- {[ - facility.address, - facility.local_body_object?.name, - facility.district_object?.name, - ] - .filter(Boolean) - .join(", ")} + {[facility.address].filter(Boolean).join(", ")}

diff --git a/src/pages/Facility/components/FilterBadges.tsx b/src/pages/Facility/components/FilterBadges.tsx deleted file mode 100644 index 14cbc032de4..00000000000 --- a/src/pages/Facility/components/FilterBadges.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; - -import { DistrictModel, LocalBodyModel } from "@/components/Facility/models"; - -interface Props { - district?: DistrictModel; - localBody?: LocalBodyModel; - onRemove: (key: keyof FilterKeys) => void; -} - -type FilterKeys = { - district?: string; - local_body?: string; -}; - -export function FilterBadges({ district, localBody, onRemove }: Props) { - return ( -
- {district && ( - - {district.name} - - - )} - {localBody && ( - - {localBody.name} - - - )} -
- ); -} diff --git a/src/pages/Facility/hooks/useFacilityQueries.ts b/src/pages/Facility/hooks/useFacilityQueries.ts index bc5d7d76346..ff4fbaeb91e 100644 --- a/src/pages/Facility/hooks/useFacilityQueries.ts +++ b/src/pages/Facility/hooks/useFacilityQueries.ts @@ -1,6 +1,5 @@ import { useQuery } from "@tanstack/react-query"; -import { DistrictModel, LocalBodyModel } from "@/components/Facility/models"; import { FacilityModel } from "@/components/Facility/models"; import routes from "@/Utils/request/api"; @@ -26,29 +25,9 @@ export function useFacilityQueries(filters: Filters) { }), }); - const { data: districtResponse } = useQuery>({ - queryKey: ["district", filters.district], - queryFn: () => - request(routes.getDistrict, { - pathParams: { id: filters.district }, - }), - enabled: !!filters.district, - }); - - const { data: localBodyResponse } = useQuery>({ - queryKey: ["localBody", filters.local_body], - queryFn: () => - request(routes.getLocalBody, { - pathParams: { id: filters.local_body }, - }), - enabled: !!filters.local_body, - }); - return { facilities: facilitiesResponse?.data?.results || [], isLoading, totalCount: facilitiesResponse?.data?.count || 0, - district: districtResponse?.data, - localBody: localBodyResponse?.data, }; } diff --git a/src/pluginTypes.ts b/src/pluginTypes.ts index 9b3ee8416c6..28511669887 100644 --- a/src/pluginTypes.ts +++ b/src/pluginTypes.ts @@ -1,13 +1,12 @@ import { LazyExoticComponent } from "react"; -import { ConsultationModel, FacilityModel } from "@/components/Facility/models"; +import { FacilityModel } from "@/components/Facility/models"; import { UserAssignedModel } from "@/components/Users/models"; import { EncounterTabProps } from "@/pages/Encounters/EncounterShow"; import { AppRoutes } from "./Routers/AppRouter"; import { FormContextValue } from "./components/Form/FormContext"; -import { PatientInfoCardProps } from "./components/Patient/PatientInfoCard"; import { PatientMeta } from "./components/Patient/models"; import { pluginMap } from "./pluginMap"; import { PatientModel } from "./types/emr/patient"; @@ -19,17 +18,6 @@ export type DoctorConnectButtonComponentType = React.FC<{ user: UserAssignedModel; }>; -export type ExtendPatientInfoCardComponentType = React.FC; - -export type ManagePatientOptionsComponentType = React.FC<{ - consultation: ConsultationModel | undefined; - patient: PatientModel; -}>; - -export type AdditionalDischargeProceduresComponentType = React.FC<{ - consultation: ConsultationModel; -}>; - export type ScribeComponentType = React.FC; export type ManageFacilityOptionsComponentType = React.FC<{ facility?: FacilityModel; @@ -57,9 +45,6 @@ export type ExtendPatientRegisterFormComponentType = React.FC<{ // Define supported plugin components export type SupportedPluginComponents = { DoctorConnectButtons: DoctorConnectButtonComponentType; - ExtendPatientInfoCard: ExtendPatientInfoCardComponentType; - ManagePatientOptions: ManagePatientOptionsComponentType; - AdditionalDischargeProcedures: AdditionalDischargeProceduresComponentType; Scribe: ScribeComponentType; ManageFacilityOptions: ManageFacilityOptionsComponentType; EncounterContextEnabler: React.FC; diff --git a/src/types/emr/patient.ts b/src/types/emr/patient.ts index 092cc23776a..98ba1f582f4 100644 --- a/src/types/emr/patient.ts +++ b/src/types/emr/patient.ts @@ -1,6 +1,5 @@ import { t } from "i18next"; -import { ConsultationModel } from "@/components/Facility/models"; import { AssignedToObjectModel, PatientMeta, @@ -42,7 +41,6 @@ export interface PatientModel { district_object?: { id: number; name: string }; state_object?: { id: number; name: string }; tele_consultation_history?: Array; - last_consultation?: ConsultationModel; address?: string; permanent_address?: string; sameAddress?: boolean; From c92ab28982f4e6d5812a8f20e5500854298462b8 Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Tue, 31 Dec 2024 18:29:17 +0530 Subject: [PATCH 359/708] Use digital ocean backend for dev (#9630) This reverts commit cc43be6d8d511e5bf0e16295ec7689d073842075. --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 91ff05ed69e..a9d6e8e91b6 100644 --- a/.env +++ b/.env @@ -7,7 +7,7 @@ REACT_APP_COVER_IMAGE_ALT=https://cdn.ohc.network/care_logo.svg REACT_PUBLIC_URL=https://care.ohc.network # Care API URL without the /api prefix -REACT_CARE_API_URL=https://careapi.ohc.network +REACT_CARE_API_URL=https://care-api.do.ohc.network # Dev envs ESLINT_NO_DEV_ERRORS=true From a4cd14b71425190c3ad808682494f53c91e6578e Mon Sep 17 00:00:00 2001 From: Gigin George Date: Wed, 1 Jan 2025 21:43:20 +0530 Subject: [PATCH 360/708] Add Org filter for OrgPatients --- src/pages/Organization/OrganizationPatients.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/Organization/OrganizationPatients.tsx b/src/pages/Organization/OrganizationPatients.tsx index 0af81698472..6516df3580d 100644 --- a/src/pages/Organization/OrganizationPatients.tsx +++ b/src/pages/Organization/OrganizationPatients.tsx @@ -32,6 +32,7 @@ export default function OrganizationPatients({ id, navOrganizationId }: Props) { queryFn: query(routes.organization.listPatients, { pathParams: { id }, queryParams: { + geo_organization: id, page: qParams.page, limit: resultsPerPage, offset: (qParams.page - 1) * resultsPerPage, From 4e9f5fbff3e513865c82d3e6fbf5f6ef50013334 Mon Sep 17 00:00:00 2001 From: Gigin George Date: Wed, 1 Jan 2025 21:52:03 +0530 Subject: [PATCH 361/708] Wire Questionnaire Search API --- src/Utils/utils.ts | 7 +++++++ src/components/Questionnaire/QuestionnaireSearch.tsx | 10 ++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index c0e88b4a590..38ed3293a61 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -649,3 +649,10 @@ export const copyToClipboard = async (content: string) => { Notification.Error({ msg: "Copying is not allowed" }); } }; + +export const conditionalAttribute = ( + condition: boolean, + attributes: Record, +) => { + return condition ? attributes : {}; +}; diff --git a/src/components/Questionnaire/QuestionnaireSearch.tsx b/src/components/Questionnaire/QuestionnaireSearch.tsx index d067f191b23..23dd5965f46 100644 --- a/src/components/Questionnaire/QuestionnaireSearch.tsx +++ b/src/components/Questionnaire/QuestionnaireSearch.tsx @@ -13,6 +13,7 @@ import { Skeleton } from "@/components/ui/skeleton"; import routes from "@/Utils/request/api"; import query from "@/Utils/request/query"; +import { conditionalAttribute } from "@/Utils/utils"; import type { QuestionnaireDetail } from "@/types/questionnaire/questionnaire"; interface QuestionnaireSearchProps { @@ -36,9 +37,14 @@ export function QuestionnaireSearch({ const { data: questionnaires, isLoading } = useQuery({ - queryKey: ["questionnaires", "list"], + queryKey: ["questionnaires", "list", search, subjectType], queryFn: query(routes.questionnaire.list, { - queryParams: subjectType ? { subject_type: subjectType } : undefined, + queryParams: { + title: search, + ...conditionalAttribute(!!subjectType, { + subject_type: subjectType, + }), + }, }), }); From 3a9c7fea29169e2581d7b23238162c6e45bbf2e1 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Wed, 1 Jan 2025 16:47:14 +0000 Subject: [PATCH 362/708] Fixes deploy: missing plugin map missing import; calendar component; and other minor fixes (#9632) --- .github/workflows/cypress.yaml | 10 -- .github/workflows/deploy.yaml | 4 - Dockerfile | 6 +- package-lock.json | 115 +++++++++++---------- package.json | 18 ++-- scripts/setup-care-apps.ts | 3 +- src/Utils/request/errorHandler.ts | 12 ++- src/Utils/request/uploadFile.ts | 9 +- src/components/Resource/ResourceFilter.tsx | 8 +- src/components/Users/CreateUserForm.tsx | 3 +- src/components/ui/calendar.tsx | 6 +- 11 files changed, 99 insertions(+), 95 deletions(-) diff --git a/.github/workflows/cypress.yaml b/.github/workflows/cypress.yaml index 373e564cd50..d259f9e83c3 100644 --- a/.github/workflows/cypress.yaml +++ b/.github/workflows/cypress.yaml @@ -38,12 +38,6 @@ jobs: - name: Build ⚙️ run: npm run build - - name: Install Specific Chrome Version - run: | - sudo apt-get install -y wget - sudo wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb - sudo apt-get install ./google-chrome-stable_current_amd64.deb - - name: Cypress run for Non-Forked PRs 🥬 if: steps.pr_origin.outputs.is_forked == 'false' uses: cypress-io/github-action@v5 @@ -53,10 +47,8 @@ jobs: start: "npx vite preview --host" wait-on: "http://localhost:4000" wait-on-timeout: 300 - browser: chrome record: true parallel: true - group: "UI-Chrome" env: CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -73,10 +65,8 @@ jobs: start: "npx vite preview --host" wait-on: "http://localhost:4000" wait-on-timeout: 300 - browser: chrome record: true parallel: true - group: "UI-Chrome" env: CYPRESS_SPLIT_TESTS: "true" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index f2e0aa24c64..4d8ac14635a 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -54,10 +54,6 @@ jobs: cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new - - name: Run tests - run: | - echo "running tests..." - - name: Move cache run: | rm -rf /tmp/.buildx-cache diff --git a/Dockerfile b/Dockerfile index bf9997b55e4..607f36bd6c4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,10 +9,14 @@ RUN apt-get update && apt-get install -y git COPY package.json package-lock.json ./ -RUN npm install +RUN npm install --ignore-scripts COPY . . +RUN npm run postinstall + +RUN npm run setup + RUN npm run build diff --git a/package-lock.json b/package-lock.json index 8de4696b459..1d9c8667df9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,8 +17,6 @@ "@hello-pangea/dnd": "^17.0.0", "@hookform/resolvers": "^3.9.1", "@originjs/vite-plugin-federation": "^1.3.6", - "@pnotify/core": "^5.2.0", - "@pnotify/mobile": "^5.2.0", "@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-checkbox": "^1.1.3", @@ -74,7 +72,7 @@ "raviger": "^4.1.2", "react": "18.3.1", "react-copy-to-clipboard": "^5.1.0", - "react-day-picker": "^9.5.0", + "react-day-picker": "^8.10.1", "react-dom": "18.3.1", "react-google-recaptcha": "^3.1.0", "react-hook-form": "^7.53.2", @@ -3070,12 +3068,6 @@ "ms": "^2.1.1" } }, - "node_modules/@date-fns/tz": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.2.0.tgz", - "integrity": "sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==", - "license": "MIT" - }, "node_modules/@dependents/detective-less": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@dependents/detective-less/-/detective-less-5.0.0.tgz", @@ -3972,6 +3964,7 @@ "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", "dev": true, + "license": "BSD-3-Clause", "optional": true, "peer": true, "dependencies": { @@ -3994,6 +3987,7 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, + "license": "ISC", "optional": true, "peer": true }, @@ -4002,6 +3996,7 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, + "license": "MIT", "optional": true, "peer": true, "dependencies": { @@ -4016,6 +4011,7 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, + "license": "MIT", "optional": true, "peer": true, "dependencies": { @@ -4031,6 +4027,7 @@ "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", "dev": true, + "license": "ISC", "optional": true, "peer": true, "dependencies": { @@ -4425,21 +4422,6 @@ "url": "https://opencollective.com/unts" } }, - "node_modules/@pnotify/core": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@pnotify/core/-/core-5.2.0.tgz", - "integrity": "sha512-d9ZM7Q6ZxuwTJ14QbOa7pWmoUqObPis7S1K+TzISffrL8w2f7JigEPI281agLShVAMIsE5SsC6O9YhkutwekbA==", - "license": "Apache-2.0" - }, - "node_modules/@pnotify/mobile": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@pnotify/mobile/-/mobile-5.2.0.tgz", - "integrity": "sha512-uu0V7h41Y8UTQA6ZB26PcHU7wHu5nwP/FEmMyimHEpEqhrvkzIRD2CxB7zu9SYp3Jk2NFcJC3NiueO3Kh25CVA==", - "license": "Apache-2.0", - "dependencies": { - "@pnotify/core": "^5.2.0" - } - }, "node_modules/@poppinss/cliui": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/@poppinss/cliui/-/cliui-6.4.2.tgz", @@ -7353,6 +7335,7 @@ "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "dev": true, + "license": "ISC", "optional": true, "peer": true }, @@ -7382,6 +7365,7 @@ "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", "deprecated": "This package is no longer supported.", "dev": true, + "license": "ISC", "optional": true, "peer": true, "dependencies": { @@ -7831,6 +7815,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", "optional": true, "dependencies": { "buffer": "^5.5.0", @@ -8110,6 +8095,7 @@ "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "peer": true, "dependencies": { @@ -8260,6 +8246,7 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true, + "license": "ISC", "optional": true, "peer": true, "engines": { @@ -8446,6 +8433,7 @@ "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "dev": true, + "license": "ISC", "optional": true, "peer": true, "bin": { @@ -8516,6 +8504,7 @@ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true, + "license": "ISC", "optional": true, "peer": true }, @@ -9028,12 +9017,6 @@ "url": "https://github.com/sponsors/kossnocorp" } }, - "node_modules/date-fns-jalali": { - "version": "4.1.0-0", - "resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz", - "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==", - "license": "MIT" - }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", @@ -9145,6 +9128,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", "optional": true, "engines": { "node": ">=4.0.0" @@ -9237,6 +9221,7 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "dev": true, + "license": "MIT", "optional": true, "peer": true }, @@ -9282,6 +9267,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", "optional": true, "engines": { "node": ">=8" @@ -10585,6 +10571,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", "optional": true, "engines": { "node": ">=6" @@ -11042,6 +11029,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT", "optional": true }, "node_modules/fs-extra": { @@ -11064,6 +11052,7 @@ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, + "license": "ISC", "optional": true, "peer": true, "dependencies": { @@ -11078,6 +11067,7 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "optional": true, "peer": true, "dependencies": { @@ -11092,6 +11082,7 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, + "license": "ISC", "optional": true, "peer": true }, @@ -11162,6 +11153,7 @@ "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", "deprecated": "This package is no longer supported.", "dev": true, + "license": "ISC", "optional": true, "peer": true, "dependencies": { @@ -11184,6 +11176,7 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, + "license": "MIT", "optional": true, "peer": true }, @@ -11192,6 +11185,7 @@ "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, + "license": "MIT", "optional": true, "peer": true, "engines": { @@ -11203,6 +11197,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "optional": true, "peer": true, "dependencies": { @@ -11358,6 +11353,7 @@ "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT", "optional": true }, "node_modules/glob": { @@ -11617,6 +11613,7 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "dev": true, + "license": "ISC", "optional": true, "peer": true }, @@ -13872,6 +13869,7 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, + "license": "MIT", "optional": true, "peer": true, "dependencies": { @@ -13889,6 +13887,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "optional": true, "peer": true, "bin": { @@ -15409,6 +15408,7 @@ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, + "license": "MIT", "optional": true, "peer": true, "dependencies": { @@ -15424,6 +15424,7 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "optional": true, "peer": true, "dependencies": { @@ -15438,6 +15439,7 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, + "license": "ISC", "optional": true, "peer": true }, @@ -15446,6 +15448,7 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, + "license": "MIT", "optional": true, "peer": true, "bin": { @@ -15459,6 +15462,7 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT", "optional": true }, "node_modules/module-definition": { @@ -15585,6 +15589,7 @@ "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", "dev": true, + "license": "MIT", "optional": true, "peer": true }, @@ -15610,6 +15615,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "license": "MIT", "optional": true }, "node_modules/natural-compare": { @@ -15633,6 +15639,7 @@ "version": "3.71.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.71.0.tgz", "integrity": "sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==", + "license": "MIT", "optional": true, "dependencies": { "semver": "^7.3.5" @@ -15645,6 +15652,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", "optional": true }, "node_modules/node-fetch": { @@ -15831,6 +15839,7 @@ "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", "deprecated": "This package is no longer supported.", "dev": true, + "license": "ISC", "optional": true, "peer": true, "dependencies": { @@ -16277,6 +16286,7 @@ "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.0.0.tgz", "integrity": "sha512-NtcIBY88FjymQy+g2g5qnuP5IslrbWCQ3A6rSr1PeuYxVRapRZ3BZCrDyAakvI6CuDYidgZaf55ygulFVwROdg==", "hasInstallScript": true, + "license": "MIT", "optional": true, "dependencies": { "node-addon-api": "^7.0.0", @@ -16598,6 +16608,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "license": "MIT", "optional": true, "dependencies": { "detect-libc": "^2.0.0", @@ -16624,6 +16635,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", "optional": true, "dependencies": { "mimic-response": "^3.1.0" @@ -16639,6 +16651,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", "optional": true, "engines": { "node": ">=10" @@ -16665,6 +16678,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "optional": true, "dependencies": { "decompress-response": "^6.0.0", @@ -17032,6 +17046,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "optional": true, "dependencies": { "deep-extend": "^0.6.0", @@ -17047,12 +17062,14 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC", "optional": true }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", "optional": true, "engines": { "node": ">=0.10.0" @@ -17097,34 +17114,17 @@ } }, "node_modules/react-day-picker": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.5.0.tgz", - "integrity": "sha512-WmJnPFVLnKh5Qscm7wavMNg86rqPverSWjx+zgK8/ZmGRSQ8c8OoqW10RI+AzAfT2atIxImpCUU2R9Z7Xb2SUA==", + "version": "8.10.1", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz", + "integrity": "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==", "license": "MIT", - "dependencies": { - "@date-fns/tz": "^1.2.0", - "date-fns": "^4.1.0", - "date-fns-jalali": "^4.1.0-0" - }, - "engines": { - "node": ">=18" - }, "funding": { "type": "individual", "url": "https://github.com/sponsors/gpbl" }, "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/react-day-picker/node_modules/date-fns": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", - "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kossnocorp" + "date-fns": "^2.28.0 || ^3.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "node_modules/react-dom": { @@ -18373,6 +18373,7 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true, + "license": "ISC", "optional": true, "peer": true }, @@ -19431,6 +19432,7 @@ "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, + "license": "ISC", "optional": true, "peer": true, "dependencies": { @@ -19449,6 +19451,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "license": "MIT", "optional": true, "dependencies": { "chownr": "^1.1.1", @@ -19461,12 +19464,14 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC", "optional": true }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", "optional": true, "dependencies": { "bl": "^4.0.3", @@ -19484,6 +19489,7 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, + "license": "ISC", "optional": true, "peer": true, "engines": { @@ -19495,6 +19501,7 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, + "license": "ISC", "optional": true, "peer": true }, @@ -21887,6 +21894,7 @@ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", "dev": true, + "license": "ISC", "optional": true, "peer": true, "dependencies": { @@ -21898,6 +21906,7 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, + "license": "MIT", "optional": true, "peer": true }, @@ -21906,6 +21915,7 @@ "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, + "license": "MIT", "optional": true, "peer": true, "engines": { @@ -21917,6 +21927,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "optional": true, "peer": true, "dependencies": { diff --git a/package.json b/package.json index 17c7e930b3f..e687ccd06fa 100644 --- a/package.json +++ b/package.json @@ -59,9 +59,12 @@ "@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-checkbox": "^1.1.3", "@radix-ui/react-collapsible": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.4", "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-menubar": "^1.1.4", + "@radix-ui/react-popover": "^1.1.4", "@radix-ui/react-progress": "^1.1.1", "@radix-ui/react-radio-group": "^1.2.2", "@radix-ui/react-scroll-area": "^1.2.2", @@ -71,17 +74,12 @@ "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-switch": "^1.1.2", "@radix-ui/react-tabs": "^1.1.1", - "@vitejs/plugin-react": "^4.3.4", - "@pnotify/core": "^5.2.0", - "@pnotify/mobile": "^5.2.0", - "@radix-ui/react-dialog": "^1.1.4", - "@radix-ui/react-label": "^2.1.1", - "@radix-ui/react-popover": "^1.1.4", "@radix-ui/react-toast": "^1.2.4", "@radix-ui/react-tooltip": "^1.1.6", "@sentry/browser": "^8.47.0", "@tanstack/react-query": "^5.62.8", "@tanstack/react-query-devtools": "^5.62.7", + "@vitejs/plugin-react": "^4.3.4", "@yudiel/react-qr-scanner": "^2.1.0", "bowser": "^2.11.0", "browser-image-compression": "^2.0.2", @@ -91,8 +89,8 @@ "clsx": "^2.1.1", "cmdk": "^1.0.4", "cross-env": "^7.0.3", - "date-fns": "^3.6.0", "cypress": "^13.17.0", + "date-fns": "^3.6.0", "dayjs": "^1.11.13", "echarts": "^5.5.1", "echarts-for-react": "^3.0.2", @@ -112,13 +110,13 @@ "raviger": "^4.1.2", "react": "18.3.1", "react-copy-to-clipboard": "^5.1.0", - "react-day-picker": "^9.5.0", + "react-day-picker": "^8.10.1", "react-dom": "18.3.1", "react-google-recaptcha": "^3.1.0", "react-hook-form": "^7.53.2", - "react-intersection-observer": "^9.14.1", "react-i18next": "^15.2.0", "react-infinite-scroll-component": "^6.1.0", + "react-intersection-observer": "^9.14.1", "react-pdf": "^9.2.1", "react-webcam": "^7.2.0", "recharts": "^2.15.0", @@ -214,4 +212,4 @@ "node": ">=22.8.0" }, "packageManager": "npm@10.9.0" -} \ No newline at end of file +} diff --git a/scripts/setup-care-apps.ts b/scripts/setup-care-apps.ts index 003cc0117b6..7bfa14f9bad 100644 --- a/scripts/setup-care-apps.ts +++ b/scripts/setup-care-apps.ts @@ -46,7 +46,8 @@ const pluginMapContent = `// Use type assertion for the static import${plugins (plugin) => `// @ts-expect-error Remote module will be available at runtime\nimport ${plugin.camelCaseName}Manifest from "${plugin.repo}/manifest";`, ) - .join("\n")}import type { PluginManifest } from "./pluginTypes"; + .join("\n")} +import type { PluginManifest } from "./pluginTypes"; const pluginMap: PluginManifest[] = [${plugins.map((plugin) => `${plugin.camelCaseName}Manifest as PluginManifest`).join(",\n ")}]; diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index c5609181f13..31d54c3df6d 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -1,4 +1,5 @@ import { navigate } from "raviger"; +import { toast } from "sonner"; import * as Notifications from "@/Utils/Notifications"; import { HTTPError } from "@/Utils/request/types"; @@ -19,13 +20,18 @@ export function handleHttpError(error: Error) { const cause = error.cause; + if (isNotFound(error)) { + toast.error((cause?.detail as string) || "Not found"); + return; + } + if (isSessionExpired(cause)) { handleSessionExpired(); return; } if (isBadRequest(error)) { - Notifications.BadRequest({ errs: cause }); + Notifications.BadRequest({ errs: cause?.errors }); return; } @@ -52,3 +58,7 @@ function handleSessionExpired() { function isBadRequest(error: HTTPError) { return error.status === 400 || error.status === 406; } + +function isNotFound(error: HTTPError) { + return error.status === 404; +} diff --git a/src/Utils/request/uploadFile.ts b/src/Utils/request/uploadFile.ts index ea603e1754f..741325f41cf 100644 --- a/src/Utils/request/uploadFile.ts +++ b/src/Utils/request/uploadFile.ts @@ -29,13 +29,8 @@ const uploadFile = async ( } catch { error = xhr.responseText; } - if (typeof error === "object" && !Array.isArray(error)) { - Object.values(error).forEach((msg) => { - Notification.Error({ msg: msg || "Something went wrong!" }); - }); - } else { - Notification.Error({ msg: error || "Something went wrong!" }); - } + Notification.BadRequest({ errs: error.errors }); + reject(new Error("Client error")); reject(new Error("Client error")); } else { resolve(); diff --git a/src/components/Resource/ResourceFilter.tsx b/src/components/Resource/ResourceFilter.tsx index aa68136bf8b..e161fcf819a 100644 --- a/src/components/Resource/ResourceFilter.tsx +++ b/src/components/Resource/ResourceFilter.tsx @@ -25,13 +25,13 @@ const getDate = (value: any) => export default function ListFilter(props: any) { const { filter, onChange, closeFilter, removeFilters } = props; const [filterState, setFilterState] = useMergeState({ - origin_facility: filter.origin_facility || "", + origin_facility: filter.origin_facility || null, origin_facility_ref: null, - approving_facility: filter.approving_facility || "", + approving_facility: filter.approving_facility || null, approving_facility_ref: null, - assigned_facility: filter.assigned_facility || "", + assigned_facility: filter.assigned_facility || null, assigned_facility_ref: null, - emergency: filter.emergency || "--", + emergency: filter.emergency || null, created_date_before: filter.created_date_before || null, created_date_after: filter.created_date_after || null, modified_date_before: filter.modified_date_before || null, diff --git a/src/components/Users/CreateUserForm.tsx b/src/components/Users/CreateUserForm.tsx index 9bbaa1dbf57..09a3f5512f3 100644 --- a/src/components/Users/CreateUserForm.tsx +++ b/src/components/Users/CreateUserForm.tsx @@ -274,7 +274,7 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { Phone Number - + @@ -290,6 +290,7 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx index 83a7d187132..15cd3662a93 100644 --- a/src/components/ui/calendar.tsx +++ b/src/components/ui/calendar.tsx @@ -1,5 +1,3 @@ -"use client"; - import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons"; import * as React from "react"; import { DayPicker } from "react-day-picker"; @@ -62,8 +60,8 @@ function Calendar({ ...classNames, }} components={{ - PreviousMonthButton: () => , - NextMonthButton: () => , + IconLeft: () => , + IconRight: () => , }} {...props} /> From f4503109a7cde1289c27aa86edf81c4f2c6191e4 Mon Sep 17 00:00:00 2001 From: Bodhish Thomas Date: Thu, 2 Jan 2025 12:04:08 +0530 Subject: [PATCH 363/708] Refactor user API routes and update components to use new structure (#9638) --- src/Utils/request/api.tsx | 28 +++++-------------- src/components/Common/UserSelector.tsx | 4 +-- src/components/Users/CreateUserForm.tsx | 4 +-- .../components/LinkFacilityUserSheet.tsx | 3 +- .../Organization/components/LinkUserSheet.tsx | 3 +- src/types/user/userApi.ts | 24 ++++++++++++++++ 6 files changed, 39 insertions(+), 27 deletions(-) create mode 100644 src/types/user/userApi.ts diff --git a/src/Utils/request/api.tsx b/src/Utils/request/api.tsx index 7424d4b1983..25a11d135fc 100644 --- a/src/Utils/request/api.tsx +++ b/src/Utils/request/api.tsx @@ -90,7 +90,13 @@ export interface LoginCredentials { password: string; } -type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; +export enum HttpMethod { + GET = "GET", + POST = "POST", + PUT = "PUT", + PATCH = "PATCH", + DELETE = "DELETE", +} export const API = ( route: `${HttpMethod} ${string}`, @@ -923,26 +929,6 @@ const routes = { }, }, - // New user routes - user: { - list: { - path: "/api/v1/users/", - method: "GET", - TRes: Type>(), - }, - create: { - path: "/api/v1/users/", - method: "POST", - TRes: Type(), - TBody: Type(), - }, - get: { - path: "/api/v1/users/{username}/", - method: "GET", - TRes: Type(), - }, - }, - // OTP Routes otp: { sendOtp: { diff --git a/src/components/Common/UserSelector.tsx b/src/components/Common/UserSelector.tsx index b181c212022..986dfb5091d 100644 --- a/src/components/Common/UserSelector.tsx +++ b/src/components/Common/UserSelector.tsx @@ -23,10 +23,10 @@ import { Avatar } from "@/components/Common/Avatar"; import useDebouncedState from "@/hooks/useDebouncedState"; -import routes from "@/Utils/request/api"; import query from "@/Utils/request/query"; import { formatName } from "@/Utils/utils"; import { UserBase } from "@/types/user/user"; +import UserApi from "@/types/user/userApi"; interface Props { selected?: UserBase; @@ -47,7 +47,7 @@ export default function UserSelector({ const { data, isFetching } = useQuery({ queryKey: ["users", search], - queryFn: query(routes.user.list, { + queryFn: query(UserApi.list, { queryParams: { search_text: search }, }), }); diff --git a/src/components/Users/CreateUserForm.tsx b/src/components/Users/CreateUserForm.tsx index 09a3f5512f3..beafb4be950 100644 --- a/src/components/Users/CreateUserForm.tsx +++ b/src/components/Users/CreateUserForm.tsx @@ -26,10 +26,10 @@ import { import { GENDER_TYPES } from "@/common/constants"; import * as Notification from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import OrganizationSelector from "@/pages/Organization/components/OrganizationSelector"; import { UserBase } from "@/types/user/user"; +import UserApi from "@/types/user/userApi"; const userFormSchema = z .object({ @@ -122,7 +122,7 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { res, data: user, error, - } = await request(routes.user.create, { + } = await request(UserApi.create, { body: { ...data, // Omit c_password as it's not needed in the API diff --git a/src/pages/FacilityOrganization/components/LinkFacilityUserSheet.tsx b/src/pages/FacilityOrganization/components/LinkFacilityUserSheet.tsx index 29ea4b7db3b..0c1f999d251 100644 --- a/src/pages/FacilityOrganization/components/LinkFacilityUserSheet.tsx +++ b/src/pages/FacilityOrganization/components/LinkFacilityUserSheet.tsx @@ -29,6 +29,7 @@ import mutate from "@/Utils/request/mutate"; import query from "@/Utils/request/query"; import { formatName } from "@/Utils/utils"; import { UserBase } from "@/types/user/user"; +import UserApi from "@/types/user/userApi"; interface Props { organizationId: string; @@ -51,7 +52,7 @@ export default function LinkFacilityUserSheet({ const { data: preSelectedUser } = useQuery({ queryKey: ["user", preSelectedUsername], - queryFn: query(routes.user.get, { + queryFn: query(UserApi.get, { pathParams: { username: preSelectedUsername || "" }, }), enabled: !!preSelectedUsername, diff --git a/src/pages/Organization/components/LinkUserSheet.tsx b/src/pages/Organization/components/LinkUserSheet.tsx index 8c0634f8881..32cd87d2053 100644 --- a/src/pages/Organization/components/LinkUserSheet.tsx +++ b/src/pages/Organization/components/LinkUserSheet.tsx @@ -28,6 +28,7 @@ import routes from "@/Utils/request/api"; import mutate from "@/Utils/request/mutate"; import query from "@/Utils/request/query"; import { UserBase } from "@/types/user/user"; +import UserApi from "@/types/user/userApi"; interface Props { organizationId: string; @@ -48,7 +49,7 @@ export default function LinkUserSheet({ const { data: preSelectedUser } = useQuery({ queryKey: ["user", preSelectedUsername], - queryFn: query(routes.user.get, { + queryFn: query(UserApi.get, { pathParams: { username: preSelectedUsername || "" }, }), enabled: !!preSelectedUsername, diff --git a/src/types/user/userApi.ts b/src/types/user/userApi.ts new file mode 100644 index 00000000000..61292d5c473 --- /dev/null +++ b/src/types/user/userApi.ts @@ -0,0 +1,24 @@ +import { HttpMethod, Type } from "@/Utils/request/api"; +import { PaginatedResponse } from "@/Utils/request/types"; + +import { UserBase } from "./user"; + +export default { + list: { + path: "/api/v1/users/", + method: HttpMethod.GET, + TRes: Type>(), + }, + create: { + path: "/api/v1/users/", + method: HttpMethod.POST, + TRes: Type(), + TBody: Type(), + }, + get: { + path: "/api/v1/users/{username}/", + method: HttpMethod.GET, + + TRes: Type(), + }, +}; From 1f7410a78d24504d1d43fdf6e882b5d5af8d1b76 Mon Sep 17 00:00:00 2001 From: Bodhish Thomas Date: Thu, 2 Jan 2025 13:53:50 +0530 Subject: [PATCH 364/708] Rewamp UX for symptoms and diagnosis in EncounterQuestionnaire (#9644) * Refactor EncounterQuestionnaire and QuestionnaireForm components for improved layout and functionality; enhance SymptomQuestion with new dropdown menu and symptom management features. * Refactor DiagnosisQuestion component to enhance diagnosis management with a new UI structure, including dropdown menus for clinical status and verification, and improved handling of diagnosis items. Introduce a new DiagnosisItem component for better code organization and maintainability. Update SymptomQuestion to improve symptom display with truncation for long text. --- .../Patient/EncounterQuestionnaire.tsx | 42 +- .../QuestionTypes/DiagnosisQuestion.tsx | 474 ++++++++--------- .../QuestionTypes/SymptomQuestion.tsx | 493 +++++++++--------- .../Questionnaire/QuestionnaireForm.tsx | 247 +++++---- 4 files changed, 613 insertions(+), 643 deletions(-) diff --git a/src/components/Patient/EncounterQuestionnaire.tsx b/src/components/Patient/EncounterQuestionnaire.tsx index e2b5f9de727..85e4149722f 100644 --- a/src/components/Patient/EncounterQuestionnaire.tsx +++ b/src/components/Patient/EncounterQuestionnaire.tsx @@ -25,28 +25,26 @@ export default function EncounterQuestionnaire({ title="Questionnaire" backUrl={`/facility/${facilityId}/patient/${patientId}/encounter/${encounterId}`} > -
- - - { - if (encounterId) { - navigate( - `/facility/${facilityId}/encounter/${encounterId}/updates`, - ); - } else { - navigate(`/patient/${patientId}/updates`); - } - }} - /> - - -
+ + + { + if (encounterId) { + navigate( + `/facility/${facilityId}/encounter/${encounterId}/updates`, + ); + } else { + navigate(`/patient/${patientId}/updates`); + } + }} + /> + + ); } diff --git a/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx b/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx index 9f6ab12a8a7..71db5800a5c 100644 --- a/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx @@ -1,21 +1,20 @@ -import { PlusIcon, TrashIcon } from "@radix-ui/react-icons"; -import { useState } from "react"; +import { + DotsVerticalIcon, + MinusCircledIcon, + Pencil2Icon, +} from "@radix-ui/react-icons"; +import React, { useState } from "react"; import { Button } from "@/components/ui/button"; import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from "@/components/ui/command"; + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; import { Select, SelectContent, @@ -23,91 +22,70 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; -import routes from "@/Utils/request/api"; -import useQuery from "@/Utils/request/useQuery"; +import ValueSetSelect from "@/components/Questionnaire/ValueSetSelect"; + +import { Code } from "@/types/questionnaire/code"; import { DIAGNOSIS_CLINICAL_STATUS, DIAGNOSIS_VERIFICATION_STATUS, - type Diagnosis, + Diagnosis, } from "@/types/questionnaire/diagnosis"; -import type { QuestionnaireResponse } from "@/types/questionnaire/form"; +import { QuestionnaireResponse } from "@/types/questionnaire/form"; +import { Question } from "@/types/questionnaire/question"; interface DiagnosisQuestionProps { - question: any; + question: Question; questionnaireResponse: QuestionnaireResponse; updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; disabled?: boolean; } +const DIAGNOSIS_INITIAL_VALUE: Partial = { + code: { code: "", display: "", system: "" }, + clinical_status: "active", + verification_status: "confirmed", + onset: { onset_datetime: new Date().toISOString().split("T")[0] }, +}; + export function DiagnosisQuestion({ question, questionnaireResponse, updateQuestionnaireResponseCB, disabled, }: DiagnosisQuestionProps) { - const [diagnoses, setDiagnoses] = useState(() => { - return (questionnaireResponse.values?.[0]?.value as Diagnosis[]) || []; - }); - - const diagnosisSearch = useQuery(routes.valueset.expand, { - pathParams: { system: "system-condition-code" }, - body: { count: 10 }, - prefetch: false, - }); + const diagnoses = + (questionnaireResponse.values?.[0]?.value as Diagnosis[]) || []; - const handleAddDiagnosis = () => { + const handleAddDiagnosis = (code: Code) => { const newDiagnoses = [ ...diagnoses, - { code: { code: "", display: "", system: "" } } as Diagnosis, - ]; - setDiagnoses(newDiagnoses); + { ...DIAGNOSIS_INITIAL_VALUE, code }, + ] as Diagnosis[]; updateQuestionnaireResponseCB({ ...questionnaireResponse, - values: [ - { - type: "diagnosis", - value: newDiagnoses, - }, - ], + values: [{ type: "diagnosis", value: newDiagnoses }], }); }; const handleRemoveDiagnosis = (index: number) => { const newDiagnoses = diagnoses.filter((_, i) => i !== index); - setDiagnoses(newDiagnoses); updateQuestionnaireResponseCB({ ...questionnaireResponse, - values: [ - { - type: "diagnosis", - value: newDiagnoses, - }, - ], + values: [{ type: "diagnosis", value: newDiagnoses }], }); }; - const updateDiagnosis = (index: number, updates: Partial) => { + const handleUpdateDiagnosis = ( + index: number, + updates: Partial, + ) => { const newDiagnoses = diagnoses.map((diagnosis, i) => i === index ? { ...diagnosis, ...updates } : diagnosis, ); - setDiagnoses(newDiagnoses); updateQuestionnaireResponseCB({ ...questionnaireResponse, - values: [ - { - type: "diagnosis", - value: newDiagnoses, - }, - ], + values: [{ type: "diagnosis", value: newDiagnoses }], }); }; @@ -117,183 +95,205 @@ export function DiagnosisQuestion({ {question.text} {question.required && *} -
-
-
[role=checkbox]]:translate-y-[2px]", + className, + )} + {...props} + /> +)); +TableHead.displayName = "TableHead"; + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + [role=checkbox]]:translate-y-[2px]", + className, + )} + {...props} + /> +)); +TableCell.displayName = "TableCell"; + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableCaption.displayName = "TableCaption"; + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +}; diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx new file mode 100644 index 00000000000..2aa54478867 --- /dev/null +++ b/src/components/ui/textarea.tsx @@ -0,0 +1,24 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +export interface TextareaProps + extends React.TextareaHTMLAttributes {} + +const Textarea = React.forwardRef( + ({ className, ...props }, ref) => { + return ( + -
- { - setIsEditing(false); - setNoteField(note.note); - }} - id="cancel-update-note-button" - > - - Cancel - - - - Update Note - -
- - ) : ( -
- {noteField} -
- )} - - } - - {showEditHistory && ( - setShowEditHistory(false)} - title={t("edit_history")} - > -
-
-

- Edit History for note - {note.id} -

-
-
- {editHistory.length === 0 && ( -
- -
- )} - {editHistory?.map((edit, index) => { - const isLast = index === editHistory.length - 1; - return ( -
-
-
-

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

-

- {formatDateTime(edit.edited_date)} -

-
-
-
-

- Note -

-

{edit.note}

-
-
- ); - })} -
-
- { - setShowEditHistory(false); - }} - > - {t("close")} - -
-
-
- )} - - ); -}; - -export default PatientNoteCard; diff --git a/src/components/Facility/PatientNotesList.tsx b/src/components/Facility/PatientNotesList.tsx deleted file mode 100644 index d4de60512d1..00000000000 --- a/src/components/Facility/PatientNotesList.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { useInfiniteQuery } from "@tanstack/react-query"; -import { useEffect } from "react"; - -import CircularProgress from "@/components/Common/CircularProgress"; -import DoctorNote from "@/components/Facility/DoctorNote"; -import { - PatientNoteStateType, - PatientNotesModel, -} from "@/components/Facility/models"; - -import { RESULTS_PER_PAGE_LIMIT } from "@/common/constants"; - -import routes from "@/Utils/request/api"; -import { callApi } from "@/Utils/request/query"; - -interface PatientNotesProps { - state: PatientNoteStateType; - setState: any; - patientId: string; - facilityId: string; - reload?: boolean; - setReload?: any; - thread: PatientNotesModel["thread"]; - setReplyTo?: (reply_to: PatientNotesModel | undefined) => void; -} - -const PatientNotesList = (props: PatientNotesProps) => { - const { state, setState, thread, setReplyTo, setReload, patientId, reload } = - props; - - const { data, isLoading, fetchNextPage, hasNextPage } = useInfiniteQuery({ - queryKey: ["notes", patientId, thread], - queryFn: async ({ pageParam = 0, signal }) => { - const response = await callApi(routes.getPatientNotes, { - pathParams: { patientId }, - queryParams: { thread, offset: pageParam }, - signal, - }); - - return { - results: response?.results ?? [], - nextPage: pageParam + RESULTS_PER_PAGE_LIMIT, - totalResults: response?.count ?? 0, - }; - }, - getNextPageParam: (lastPage, allPages) => { - const currentResults = allPages.flatMap((page) => page.results).length; - if (currentResults < lastPage.totalResults) { - return lastPage.nextPage; - } - return undefined; - }, - initialPageParam: 0, - }); - - useEffect(() => { - if (data?.pages) { - const allNotes = data.pages.flatMap((page) => page.results); - - const notesMap = new Map(allNotes.map((note) => [note.id, note])); - - const deduplicatedNotes = Array.from(notesMap.values()); - - setState((prevState: any) => ({ - ...prevState, - notes: deduplicatedNotes, - })); - } - }, [data]); - - if (isLoading || reload) { - return ( -
- -
- ); - } - - return ( - - ); -}; - -export default PatientNotesList; diff --git a/src/components/Facility/PatientNotesSlideover.tsx b/src/components/Facility/PatientNotesSlideover.tsx deleted file mode 100644 index 5de161ad228..00000000000 --- a/src/components/Facility/PatientNotesSlideover.tsx +++ /dev/null @@ -1,298 +0,0 @@ -import { t } from "i18next"; -import { Link } from "raviger"; -import { Dispatch, SetStateAction, useEffect, useState } from "react"; -import useKeyboardShortcut from "use-keyboard-shortcut"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import ButtonV2 from "@/components/Common/ButtonV2"; -import DoctorNoteReplyPreviewCard from "@/components/Facility/DoctorNoteReplyPreviewCard"; -import PatientConsultationNotesList from "@/components/Facility/PatientConsultationNotesList"; -import { - PaitentNotesReplyModel, - PatientNoteStateType, -} from "@/components/Facility/models"; -import AutoExpandingTextInputFormField from "@/components/Form/FormFields/AutoExpandingTextInputFormField"; -import { useAddPatientNote } from "@/components/Patient/Utils"; - -import useAuthUser from "@/hooks/useAuthUser"; -import { useMessageListener } from "@/hooks/useMessageListener"; -import useNotificationSubscriptionState from "@/hooks/useNotificationSubscriptionState"; - -import { PATIENT_NOTES_THREADS } from "@/common/constants"; - -import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; -import * as Notification from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import { classNames, isAppleDevice, keysOf } from "@/Utils/utils"; - -interface PatientNotesProps { - patientId: string; - facilityId: string; - consultationId: string; - setShowPatientNotesPopup: Dispatch>; -} - -export default function PatientNotesSlideover(props: PatientNotesProps) { - const authUser = useAuthUser(); - const notificationSubscriptionState = useNotificationSubscriptionState(); - const [thread, setThread] = useState( - authUser.user_type === "Nurse" - ? PATIENT_NOTES_THREADS.Nurses - : PATIENT_NOTES_THREADS.Doctors, - ); - const [show, setShow] = useState(true); - const [patientActive, setPatientActive] = useState(true); - const [reload, setReload] = useState(false); - const [focused, setFocused] = useState(false); - const [reply_to, setReplyTo] = useState( - undefined, - ); - - useEffect(() => { - if (notificationSubscriptionState === "unsubscribed") { - Notification.Warn({ - msg: "Please subscribe to notifications to get live updates on discussion notes.", - }); - } else if (notificationSubscriptionState === "subscribed_on_other_device") { - Notification.Warn({ - msg: "Please subscribe to notifications on this device to get live updates on discussion notes.", - }); - } - }, [notificationSubscriptionState]); - - const initialData: PatientNoteStateType = { - notes: [], - patientId: props.patientId, - facilityId: props.facilityId, - }; - const [state, setState] = useState(initialData); - - const { facilityId, patientId, consultationId, setShowPatientNotesPopup } = - props; - - const localStorageKey = `patientNotesNoteField_${consultationId}`; - const [noteField, setNoteField] = useState( - localStorage.getItem(localStorageKey) || "", - ); - const { mutate: addNote } = useAddPatientNote({ - patientId, - thread, - consultationId, - }); - - const onAddNote = () => { - if (!/\S+/.test(noteField)) { - Notification.Error({ - msg: "Note Should Contain At Least 1 Character", - }); - return; - } - setReplyTo(undefined); - setNoteField(""); - addNote({ - note: noteField, - reply_to: reply_to?.id, - thread, - consultation: consultationId, - }); - }; - - useMessageListener((data) => { - const message = data?.message; - if ( - (message?.from == "patient/doctor_notes/create" || - message?.from == "patient/doctor_notes/edit") && - message?.facility_id == facilityId && - message?.patient_id == patientId - ) { - setReload(true); - } - }); - - useEffect(() => { - async function fetchPatientName() { - if (patientId) { - const { data } = await request(routes.getPatient, { - pathParams: { id: patientId }, - }); - if (data) { - setPatientActive(data.is_active ?? true); - } - } - } - fetchPatientName(); - }, [patientId]); - - useKeyboardShortcut( - [isAppleDevice ? "Meta" : "Shift", "Enter"], - () => { - if (focused) { - onAddNote(); - } - }, - { - ignoreInputFields: false, - }, - ); - - const notesActionIcons = ( -
- {show && ( - - - - {t("full_screen")} - - - )} -
setShow(!show)} - > - - - {t("minimize")} - -
-
setShowPatientNotesPopup(false)} - > - - - {t("close")} - -
-
- ); - - useEffect(() => { - localStorage.setItem(localStorageKey, noteField); - }, [noteField, localStorageKey]); - - return ( -
- {!show ? ( -
setShow(!show)} - > - Discussion Notes - {notesActionIcons} -
- ) : ( -
-
- Discussion Notes - {notesActionIcons} -
-
- {keysOf(PATIENT_NOTES_THREADS).map((current) => ( - - ))} -
- - setReplyTo(undefined)} - > -
- setNoteField(e.value)} - className="w-full grow" - errorClassName="hidden" - innerClassName="pr-10" - placeholder={t("notes_placeholder")} - disabled={!patientActive} - onFocus={() => setFocused(true)} - onBlur={() => setFocused(false)} - /> - - - - {t("send")} - - -
-
-
- )} -
- ); -} diff --git a/src/components/Facility/SetInventoryForm.tsx b/src/components/Facility/SetInventoryForm.tsx deleted file mode 100644 index ccaf60ef731..00000000000 --- a/src/components/Facility/SetInventoryForm.tsx +++ /dev/null @@ -1,218 +0,0 @@ -import { useEffect, useReducer, useState } from "react"; - -import Card from "@/CAREUI/display/Card"; - -import { Cancel, Submit } from "@/components/Common/ButtonV2"; -import Page from "@/components/Common/Page"; -import { InventoryItemsModel } from "@/components/Facility/models"; -import { SelectFormField } from "@/components/Form/FormFields/SelectFormField"; -import TextFormField from "@/components/Form/FormFields/TextFormField"; -import { FieldChangeEvent } from "@/components/Form/FormFields/Utils"; - -import useAppHistory from "@/hooks/useAppHistory"; - -import * as Notification from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; - -const initForm = { - id: "", - quantity: "", -}; -const initError = Object.assign( - {}, - ...Object.keys(initForm).map((k) => ({ [k]: "" })), -); -const initialState = { - form: { ...initForm }, - errors: { ...initError }, -}; - -const inventoryFormReducer = (state = initialState, action: any) => { - switch (action.type) { - case "set_form": { - return { - ...state, - form: action.form, - }; - } - case "set_error": { - return { - ...state, - errors: action.errors, - }; - } - default: - return state; - } -}; - -export const SetInventoryForm = (props: any) => { - const { goBack } = useAppHistory(); - const [state, dispatch] = useReducer(inventoryFormReducer, initialState); - const { facilityId } = props; - const [data, setData] = useState>([]); - const [currentUnit, setCurrentUnit] = useState(); - - const limit = 14; - const offset = 0; - - useTanStackQueryInstead(routes.getMinQuantity, { - pathParams: { - facilityId, - }, - prefetch: !!facilityId, - onResponse: async ({ data }) => { - const existingItemIDs: number[] = []; - - if (data?.results) { - data.results.map((item) => existingItemIDs.push(item.item_object.id)); - } - - await request(routes.getItems, { - query: { - limit, - offset, - }, - onResponse: ({ res, data }) => { - if (res && data) { - const filteredData = data.results.filter( - (item) => !existingItemIDs.includes(item.id as number), - ); - setData(filteredData); - dispatch({ - type: "set_form", - form: { ...state.form, id: filteredData[0]?.id }, - }); - } - }, - }); - }, - }); - - const { data: facilityObject } = useTanStackQueryInstead( - routes.getAnyFacility, - { - pathParams: { id: facilityId }, - prefetch: !!facilityId, - }, - ); - - useEffect(() => { - // set the default units according to the item - const item = data.find((item) => item.id === Number(state.form.id)); - if (item) { - dispatch({ - type: "set_form", - form: { ...state.form, unit: item.default_unit?.name }, - }); - setCurrentUnit(item.default_unit?.name); - } - }, [state.form.id]); - - const validateForm = () => { - const errors = { ...initError }; - let invalidForm = false; - - Object.keys(state.form).forEach((field) => { - switch (field) { - case "quantity": - if (!state.form[field]?.length) { - errors[field] = "Please select a quantity"; - invalidForm = true; - } else if (state.form[field] < 0) { - errors[field] = "Quantity can't be negative"; - invalidForm = true; - } - return; - } - }); - - dispatch({ type: "set_error", errors }); - return !invalidForm; - }; - - const handleSubmit = async (e: any) => { - e.preventDefault(); - const validated = validateForm(); - if (!validated) return; - await request(routes.setMinQuantity, { - pathParams: { facilityId }, - body: { - min_quantity: Number(state.form.quantity), - item: Number(state.form.id), - }, - onResponse: ({ res }) => { - if (res?.ok) { - Notification.Success({ - msg: "Minimum quantiy updated successfully", - }); - } - goBack(); - }, - }); - }; - - const handleChange = ({ name, value }: FieldChangeEvent) => { - dispatch({ type: "set_form", form: { ...state.form, [name]: value } }); - }; - - return ( - - -
handleSubmit(e)} className="mt-6 flex flex-col"> - item.id} - optionLabel={(item) => item.name} - /> - -
- - - -
- -
- goBack()} /> - -
- -
-
- ); -}; diff --git a/src/components/Facility/SpokeFacilityEditor.tsx b/src/components/Facility/SpokeFacilityEditor.tsx deleted file mode 100644 index 9d8364395f2..00000000000 --- a/src/components/Facility/SpokeFacilityEditor.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { FacilitySelect } from "@/components/Common/FacilitySelect"; -import FacilityBlock from "@/components/Facility/FacilityBlock"; -import { - FacilityModel, - FacilitySpokeErrors, - FacilitySpokeModel, - FacilitySpokeRequest, - SpokeRelationship, -} from "@/components/Facility/models"; -import { SelectFormField } from "@/components/Form/FormFields/SelectFormField"; -import ModelCrudEditor from "@/components/Form/ModelCrudEditor"; - -import { SPOKE_RELATION_TYPES } from "@/common/constants"; - -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; - -export interface SpokeFacilityEditorProps { - facility: Omit & { id: string }; -} - -export default function SpokeFacilityEditor(props: SpokeFacilityEditorProps) { - const { facility } = props; - - const { t } = useTranslation(); - - const spokesQuery = useTanStackQueryInstead(routes.getFacilitySpokes, { - pathParams: { - id: facility.id, - }, - }); - - const spokes = spokesQuery.data?.results; - - const createSpoke = (body: FacilitySpokeRequest) => - request(routes.createFacilitySpoke, { - body, - pathParams: { - id: facility.id, - }, - onResponse: ({ res }) => { - if (res?.ok) { - spokesQuery.refetch(); - } - }, - }); - - const deleteSpoke = (spokeFacilityId: string) => - request(routes.deleteFacilitySpoke, { - pathParams: { - id: facility.id, - spoke_id: spokeFacilityId, - }, - onResponse: ({ res }) => { - if (res?.ok) { - spokesQuery.refetch(); - } - }, - }); - - const updateSpoke = (spokeFacilityId: string, body: FacilitySpokeRequest) => - request(routes.updateFacilitySpokes, { - pathParams: { - id: facility.id, - spoke_id: spokeFacilityId, - }, - body, - onResponse: ({ res }) => { - if (res?.ok) { - spokesQuery.refetch(); - } - }, - }); - - const FormRender = ( - item: FacilitySpokeModel | FacilitySpokeRequest, - setItem: (item: FacilitySpokeModel | FacilitySpokeRequest) => void, - processing: boolean, - ) => { - const [selectedFacility, setSelectedFacility] = - useState(null); - - useEffect(() => { - setItem({ ...item, spoke: selectedFacility?.id }); - }, [selectedFacility]); - - return ( -
- {"id" in item ? ( -
- -
- ) : ( - - (v === null || !Array.isArray(v)) && setSelectedFacility(v) - } - errors="" - className="w-full" - disabled={processing} - filter={(f) => - !!f.id && - facility.id !== f.id && - !spokes?.flatMap((s) => s.spoke_object.id).includes(f.id) - } - /> - )} - v.text} - optionValue={(v) => v.value} - value={item.relationship} - onChange={(v) => setItem({ ...item, relationship: v.value })} - errorClassName="hidden" - className="w-full shrink-0 md:w-auto" - disabled={processing} - /> -
- ); - }; - - return ( - <> - - items={spokes} - onCreate={createSpoke} - onUpdate={updateSpoke} - onDelete={deleteSpoke} - loading={spokesQuery.loading} - errors={{}} - emptyText={"No Spokes"} - empty={{ - spoke: "", - relationship: SpokeRelationship.REGULAR, - }} - createText="Add Spoke" - allowCreate={(item) => !item.relationship || !item.spoke} - > - {FormRender} - - - ); -} diff --git a/src/components/Facility/TreatmentSummary.tsx b/src/components/Facility/TreatmentSummary.tsx deleted file mode 100644 index 3120c5c099f..00000000000 --- a/src/components/Facility/TreatmentSummary.tsx +++ /dev/null @@ -1,528 +0,0 @@ -import careConfig from "@careConfig"; -import { useMemo } from "react"; -import { useTranslation } from "react-i18next"; - -import PrintPreview from "@/CAREUI/misc/PrintPreview"; - -import PageHeadTitle from "@/components/Common/PageHeadTitle"; -import { - ActiveConditionVerificationStatuses, - ConsultationDiagnosis, -} from "@/components/Diagnosis/types"; -import { ConsultationModel } from "@/components/Facility/models"; -import MedicineRoutes from "@/components/Medicine/routes"; - -import { GENDER_TYPES } from "@/common/constants"; - -import routes from "@/Utils/request/api"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { formatDate, formatDateTime, formatPatientAge } from "@/Utils/utils"; -import { PatientModel } from "@/types/emr/patient"; - -export interface ITreatmentSummaryProps { - consultationId: string; - patientId: string; - facilityId: string; -} - -export default function TreatmentSummary({ - consultationId, - patientId, -}: ITreatmentSummaryProps) { - const { t } = useTranslation(); - const date = new Date(); - - const { data: patientData } = useTanStackQueryInstead(routes.getPatient, { - pathParams: { id: patientId }, - prefetch: patientId !== undefined, - }); - - const { data: consultationData } = useTanStackQueryInstead( - routes.getConsultation, - { - pathParams: { id: consultationId }, - prefetch: consultationId !== undefined, - }, - ); - - return ( -
- -
- -
-
-

{consultationData?.facility_name}

- care logo -
-

- {t("treatment_summary__heading")} -

- -
{formatDate(date)}
- -
- - - - - - - - - - - - - -
-
-
-
-
- ); -} - -interface IBasicDetailsSection { - patientData?: PatientModel; - consultationData?: ConsultationModel; -} - -function BasicDetailsSection({ - patientData, - consultationData, -}: IBasicDetailsSection) { - const { t } = useTranslation(); - - return ( - <> -
-
- {t("patient_registration__name")} : {patientData?.name ?? ""} -
-
- {t("patient_registration__address")} :{" "} - {patientData?.address ?? ""} -
-
- -
-
-
- {t("patient_registration__age")} :{" "} - {patientData ? formatPatientAge(patientData, true) : ""} -
-
- - {consultationData?.suggestion === "A" - ? t("patient_consultation__ip") - : t("patient_consultation__op")}{" "} - : - {" "} - {consultationData?.patient_no ?? ""} -
-
- -
- {consultationData?.suggestion === "DC" ? ( - {t("patient_consultation__dc_admission")} : - ) : ( - {t("patient_consultation__admission")} : - )}{" "} - - {consultationData?.encounter_date - ? formatDateTime(consultationData.encounter_date) - : t("empty_date_time")} - -
-
- -
-
- {t("patient_registration__gender")} :{" "} - {GENDER_TYPES.find((i) => i.id === patientData?.gender)?.text} -
- -
- {t("patient_registration__contact")} :{" "} - {patientData?.emergency_phone_number ?? ""} -
-
- - ); -} - -interface IComorbiditiesSection { - patientData?: PatientModel; -} - -function ComorbiditiesSection({ patientData }: IComorbiditiesSection) { - const { t } = useTranslation(); - - return patientData?.medical_history?.filter( - (comorbidities) => comorbidities.disease !== "NO", - ).length ? ( -
- {t("patient_registration__comorbidities")} -
- - - - - - - - - {patientData.medical_history.map((obj, index) => { - return ( - - - - - ); - })} - -
- {t("patient_registration__comorbidities__disease")} - - {t("patient_registration__comorbidities__details")} -
- {obj["disease"]} - - {obj["details"] || "---"} -
-
-
- ) : null; -} - -interface IDiagnosisSection { - consultationData?: ConsultationModel; -} - -type DiagnosisType = - | (typeof ActiveConditionVerificationStatuses)[number] - | "principal"; - -function DiagnosisSection({ consultationData }: IDiagnosisSection) { - const { t } = useTranslation(); - - const diagnoses = useMemo(() => { - return consultationData?.diagnoses?.reduce( - (acc, curr) => { - if (curr.is_principal) { - acc.principal.push(curr); - } else if ( - ActiveConditionVerificationStatuses.includes( - curr.verification_status as (typeof ActiveConditionVerificationStatuses)[number], - ) - ) { - acc[curr.verification_status as keyof typeof acc].push(curr); - } - - return acc; - }, - { - principal: [], - confirmed: [], - provisional: [], - unconfirmed: [], - differential: [], - } as Record, - ); - }, [consultationData?.diagnoses]); - - if (!diagnoses) { - return null; - } - - return ( -
- {t("diagnosis")} -
- {( - [ - "principal", - "confirmed", - "provisional", - "unconfirmed", - "differential", - ] as DiagnosisType[] - ).map( - (type) => - !!diagnoses[type].length && ( -
- - {t(`diagnosis__${type}`)} {t("diagnosis")} - -
    - {diagnoses[type].map((d) => ( -
  1. - {d.diagnosis_object.label} - {d.is_principal && ( - - {t(`diagnosis__${d.verification_status}`)} - - )} -
  2. - ))} -
-
- ), - )} -
-
- ); -} - -interface IInvestigationsSection { - consultationId: string; -} - -function InvestigationsSection({ consultationId }: IInvestigationsSection) { - const { t } = useTranslation(); - - const { data: investigations } = useTanStackQueryInstead( - routes.getInvestigation, - { - pathParams: { consultation_external_id: consultationId }, - prefetch: consultationId !== undefined, - }, - ); - - return investigations?.results.length ? ( -
- {t("suggested_investigations")} - -
- - - - - - - - - - - - - - {investigations?.results.map((value, index) => ( - - - - - - - - - ))} - -
- {t("investigations__date")} - - {t("investigations__name")} - - {t("investigations__result")} - - {t("investigations__ideal_value")} - - {t("investigations__range")} - - {t("investigations__unit")} -
- {formatDate(value["session_object"]["session_created_date"])} - - {value["investigation_object"]["name"]} - - {value["notes"] || value["value"]} - - {value["investigation_object"]["ideal_value"] || "-"} - - {value["investigation_object"]["min_value"]} -{" "} - {value["investigation_object"]["max_value"]} - - {value["investigation_object"]["unit"] || "-"} -
-
-
- ) : null; -} - -interface ITreatmentSection { - consultationData?: ConsultationModel; -} - -function TreatmentSection({ consultationData }: ITreatmentSection) { - const { t } = useTranslation(); - - const isTreatmentSummaryAvailable = useMemo(() => { - return ( - consultationData?.last_daily_round && - (consultationData.last_daily_round.ventilator_spo2 || - consultationData.last_daily_round.temperature) - ); - }, [consultationData]); - - return consultationData?.treatment_plan || isTreatmentSummaryAvailable ? ( -
- {consultationData?.treatment_plan && ( - <> - {t("patient_consultation__treatment__plan")} -

{consultationData.treatment_plan}

- - )} - - {isTreatmentSummaryAvailable && ( - <> - - {t("patient_consultation__treatment__summary")} - -
- - - - - - - - - - - - - - - - -
- {t("patient_consultation__treatment__summary__date")} - - {t("patient_consultation__treatment__summary__spo2")} - - {t("patient_consultation__treatment__summary__temperature")} -
- {formatDateTime( - consultationData?.last_daily_round?.modified_date, - )} - - {consultationData?.last_daily_round?.ventilator_spo2 || "-"} - - {consultationData?.last_daily_round?.temperature || "-"} -
-
- - )} -
- ) : null; -} - -interface IPrescriptionsSection { - consultationId: string; -} - -function PrescriptionsSection({ consultationId }: IPrescriptionsSection) { - const { t } = useTranslation(); - - const { data: prescriptions } = useTanStackQueryInstead( - MedicineRoutes.listPrescriptions, - { - pathParams: { consultation: consultationId }, - query: { discontinued: false }, - }, - ); - - return prescriptions?.results.length ? ( -
- {t("active_prescriptions")} - -
- - - - - - - - - - - - {prescriptions?.results.map((prescription, index) => ( - - - - - - - ))} - -
- {t("prescriptions__medicine")} - - {t("prescriptions__route")} - - {t("prescriptions__dosage_frequency")} - - {t("prescriptions__start_date")} -
- {prescription.medicine_object?.name ?? "-"} - - {prescription.route ?? "-"} - - {prescription.dosage_type !== "TITRATED" ? ( -

{prescription.base_dosage}

- ) : ( -

- {prescription.base_dosage} - {prescription.target_dosage} -

- )} - -

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

-
- {formatDate(prescription.created_date)} -
-
-
- ) : null; -} - -interface IInstructionsSection { - consultationData?: ConsultationModel; -} - -function InstructionsSection({ consultationData }: IInstructionsSection) { - const { t } = useTranslation(); - - return ( - <> - {consultationData?.consultation_notes && ( -
- {t("patient_consultation__consultation_notes")} - -
{consultationData.consultation_notes}
-
- )} - - {consultationData?.special_instruction && ( -
- {t("patient_consultation__special_instruction")} - -
{consultationData.special_instruction}
-
- )} - - ); -} diff --git a/src/components/Facility/models.tsx b/src/components/Facility/models.tsx index dd04258daa7..57832d1a512 100644 --- a/src/components/Facility/models.tsx +++ b/src/components/Facility/models.tsx @@ -1,61 +1,15 @@ -import { AssetData, AssetLocationType } from "@/components/Assets/AssetTypes"; -import { RouteToFacility } from "@/components/Common/RouteToFacilitySelect"; -import { InvestigationType } from "@/components/Common/prescription-builder/InvestigationBuilder"; -import { ProcedureType } from "@/components/Common/prescription-builder/ProcedureBuilder"; -import { - ConsultationDiagnosis, - CreateDiagnosis, -} from "@/components/Diagnosis/types"; -import { - AssignedToObjectModel, - BloodPressure, - DailyRoundsModel, - FacilityNameModel, - FileUploadModel, -} from "@/components/Patient/models"; -import { EncounterSymptom } from "@/components/Symptoms/types"; +import { FileUploadModel } from "@/components/Patient/models"; import { UserBareMinimum } from "@/components/Users/models"; import { CONSENT_PATIENT_CODE_STATUS_CHOICES, CONSENT_TYPE_CHOICES, - ConsultationSuggestionValue, - DISCHARGE_REASONS, - PATIENT_NOTES_THREADS, - SHIFTING_CHOICES_PEACETIME, UserRole, } from "@/common/constants"; import { FeatureFlag } from "@/Utils/featureFlags"; import { PatientModel } from "@/types/emr/patient"; -export interface LocalBodyModel { - id: number; - name: string; - body_type: number; - localbody_code: string; - district: number; -} - -export interface DistrictModel { - id: number; - name: string; - state: number; -} - -export interface StateModel { - id: number; - name: string; -} - -export interface WardModel { - id: number; - name: string; - number: number; - panchayath: string; - local_body_id: LocalBodyModel["id"]; -} - export interface FacilityModel { id?: string; name?: string; @@ -70,10 +24,6 @@ export interface FacilityModel { }; phone_number?: string; middleware_address?: string; - local_body_object?: LocalBodyModel; - district_object?: DistrictModel; - state_object?: StateModel; - ward_object?: WardModel; modified_date?: string; created_date?: string; geo_organization?: string; @@ -81,31 +31,8 @@ export interface FacilityModel { facility_flags?: FeatureFlag[]; latitude?: string; longitude?: string; - ward?: number; - local_body?: number; - district?: number; - state?: number; -} - -export enum SpokeRelationship { - REGULAR = 1, - TELE_ICU = 2, -} - -export interface FacilitySpokeModel { - id: string; - hub_object: FacilityNameModel; - spoke_object: FacilityNameModel; - relationship: SpokeRelationship; -} - -export interface FacilitySpokeRequest { - spoke?: string; - relationship?: SpokeRelationship; } -export interface FacilitySpokeErrors {} - export interface OptionsType { id: number; text: string; @@ -133,73 +60,6 @@ export interface PatientConsentModel { created_by: UserBareMinimum; } -export interface ConsultationModel { - encounter_date: string; - icu_admission_date?: string; - admitted?: boolean; - test_id?: string; - admitted_to?: string; - category?: PatientCategory; - created_date?: string; - discharge_date?: string; - new_discharge_reason?: (typeof DISCHARGE_REASONS)[number]["id"]; - discharge_notes?: string; - examination_details?: string; - history_of_present_illness?: string; - facility: string; - facility_name?: string; - id: string; - modified_date?: string; - patient: string; - treatment_plan?: string; - referred_to?: FacilityModel["id"]; - referred_to_object?: FacilityModel; - referred_to_external?: string; - referred_from_facility?: FacilityModel["id"]; - referred_from_facility_object?: FacilityModel; - referred_from_facility_external?: string; - referred_by_external?: string; - transferred_from_location?: LocationModel["id"]; - transferred_from_location_object?: LocationModel; - suggestion?: ConsultationSuggestionValue; - patient_no?: string; - route_to_facility?: RouteToFacility; - readonly diagnoses?: ConsultationDiagnosis[]; - create_diagnoses?: CreateDiagnosis[]; // Used for bulk creating diagnoses upon consultation creation - readonly symptoms?: EncounterSymptom[]; - create_symptoms?: CreateDiagnosis[]; // Used for bulk creating symptoms upon consultation creation - deprecated_verified_by?: string; - readonly treating_physician?: UserBareMinimum["id"]; - treating_physician_object?: UserBareMinimum; - suggestion_text?: string; - consultation_notes?: string; - is_telemedicine?: boolean; - procedure?: ProcedureType[]; - assigned_to?: string; - assigned_to_object?: AssignedToObjectModel; - created_by?: any; - last_edited_by?: any; - weight?: number | null; - height?: number | null; - operation?: string; - special_instruction?: string; - intubation_start_date?: string; - intubation_end_date?: string; - ett_tt?: number; - cuff_pressure?: number; - lines?: any; - last_daily_round?: DailyRoundsModel; - current_bed?: CurrentBed; - review_interval?: number; - cause_of_death?: string; - death_datetime?: string; - death_confirmed_doctor?: string; - is_readmission?: boolean; - medico_legal_case?: boolean; - investigation?: InvestigationType[]; - has_consents?: (typeof CONSENT_TYPE_CHOICES)[number]["id"][]; -} - export interface DupPatientModel { id: string; gender: string; @@ -227,304 +87,6 @@ export interface InventoryItemsModel { ]; } -export interface LocationModel { - id: string; - name: string; - description?: string; - middleware_address?: string; - location_type: AssetLocationType; - facility?: { - name: string; - }; - created_date?: string; - modified_date?: string; -} - -export interface BedModel { - id?: string; - bed_type?: string; - name?: string; - description?: string; - facility?: string; - location_object?: { - name: string; - id: string; - facility?: { name: string; id: string }; - }; - location?: string; - is_occupied?: boolean; - created_date?: string; - modified_date?: string; -} - -export interface CurrentBed { - id: string; - consultation: string; - bed?: string; - bed_object: BedModel; - assets_objects?: AssetData[]; - created_date: string; - modified_date: string; - start_date: string; - end_date: string; - meta: Record; -} - -export type ICD11DiagnosisModel = { - id: string; - label: string; -}; - -export const ABGPlotsFields = [ - "ph", - "pco2", - "po2", - "hco3", - "base_excess", - "lactate", - "sodium", - "potassium", - "ventilator_fio2", -] as const satisfies (keyof DailyRoundsModel)[]; - -export type ABGPlotsRes = { - ph: string; - pco2: number; - po2: number; - hco3: string; - base_excess: number; - lactate: string; - sodium: string; - potassium: string; - ventilator_fio2: number; -}; - -export const DialysisPlotsFields = [ - "dialysis_fluid_balance", - "dialysis_net_balance", -] as const satisfies (keyof DailyRoundsModel)[]; - -export type DialysisPlotsRes = { - dialysis_fluid_balance: number; - dialysis_net_balance: number; -}; - -export const NeurologicalTablesFields = [ - "consciousness_level", - "left_pupil_size", - "left_pupil_size_detail", - "right_pupil_size", - "right_pupil_size_detail", - "left_pupil_light_reaction", - "left_pupil_light_reaction_detail", - "right_pupil_light_reaction", - "right_pupil_light_reaction_detail", - "limb_response_upper_extremity_right", - "limb_response_upper_extremity_left", - "limb_response_lower_extremity_left", - "limb_response_lower_extremity_right", - "glasgow_eye_open", - "glasgow_verbal_response", - "glasgow_motor_response", - "glasgow_total_calculated", -] as const satisfies (keyof DailyRoundsModel)[]; - -export type NeurologicalTablesRes = { - consciousness_level: number; - left_pupil_size: number; - left_pupil_size_detail: string; - right_pupil_size: number; - right_pupil_size_detail: string; - left_pupil_light_reaction: number; - left_pupil_light_reaction_detail: string; - right_pupil_light_reaction: number; - right_pupil_light_reaction_detail: string; - limb_response_upper_extremity_right: number; - limb_response_upper_extremity_left: number; - limb_response_lower_extremity_left: number; - limb_response_lower_extremity_right: number; - glasgow_eye_open: number; - glasgow_verbal_response: number; - glasgow_motor_response: number; - glasgow_total_calculated: number; -}; - -export const NursingPlotFields = [ - "nursing", -] as const satisfies (keyof DailyRoundsModel)[]; - -export type NursingPlotRes = { - nursing: Array<{ - procedure: string; - description: string; - }>; -}; - -export const RoutineFields = [ - "sleep", - "bowel_issue", - "bladder_drainage", - "bladder_issue", - "is_experiencing_dysuria", - "urination_frequency", - "nutrition_route", - "oral_issue", - "appetite", -] as const satisfies (keyof DailyRoundsModel)[]; - -export type RoutineAnalysisRes = Record<(typeof RoutineFields)[number], any>; - -export const NutritionPlotsFields = [ - "infusions", - "iv_fluids", - "feeds", - "total_intake_calculated", - "total_output_calculated", - "output", -] as const satisfies (keyof DailyRoundsModel)[]; - -export type NutritionPlotsRes = { - infusions: any[]; - iv_fluids: any[]; - feeds: any[]; - total_intake_calculated: string; - total_output_calculated: string; - output: any[]; -}; - -export const PainDiagramsFields = [ - "pain_scale_enhanced", -] as const satisfies (keyof DailyRoundsModel)[]; - -export type PainDiagramsRes = { - pain_scale_enhanced: any[]; -}; - -export const PressureSoreDiagramsFields = [ - "pressure_sore", -] as const satisfies (keyof DailyRoundsModel)[]; - -export type PressureSoreDiagramsRes = { - pressure_sore: any[]; -}; - -export const PrimaryParametersPlotFields = [ - "bp", - "pulse", - "temperature", - "resp", - "blood_sugar_level", - "insulin_intake_frequency", - "insulin_intake_dose", - "ventilator_spo2", - "ventilator_fio2", - "rhythm", - "rhythm_detail", - "pain_scale_enhanced", -] as const satisfies (keyof DailyRoundsModel)[]; - -export type PrimaryParametersPlotRes = { - bp: BloodPressure; - pulse: number; - temperature: string; - resp: number; - blood_sugar_level: number; - insulin_intake_frequency: number; - insulin_intake_dose: string; - ventilator_spo2: number; - ventilator_fio2: number; - rhythm: number; - rhythm_detail: string; -}; - -export const VentilatorPlotFields = [ - "ventilator_pip", - "ventilator_mean_airway_pressure", - "ventilator_resp_rate", - "ventilator_pressure_support", - "ventilator_tidal_volume", - "ventilator_peep", - "ventilator_fio2", - "ventilator_spo2", - "etco2", - "bilateral_air_entry", - "ventilator_oxygen_modality_oxygen_rate", - "ventilator_oxygen_modality_flow_rate", -] as const satisfies (keyof DailyRoundsModel)[]; - -export type VentilatorPlotRes = { - ventilator_pip: number; - ventilator_mean_airway_pressure: number; - ventilator_resp_rate: number; - ventilator_pressure_support: number; - ventilator_tidal_volume: number; - ventilator_peep: string; - ventilator_fio2: number; - ventilator_spo2: number; - etco2: number; - bilateral_air_entry: boolean; - ventilator_oxygen_modality_oxygen_rate: number; - ventilator_oxygen_modality_flow_rate: number; -}; - -export interface DailyRoundsBody { - page?: number; - fields: - | typeof ABGPlotsFields - | typeof DialysisPlotsFields - | typeof NeurologicalTablesFields - | typeof NursingPlotFields - | typeof RoutineFields - | typeof NutritionPlotsFields - | typeof PainDiagramsFields - | typeof PressureSoreDiagramsFields - | typeof PrimaryParametersPlotFields - | typeof VentilatorPlotFields; -} - -export interface DailyRoundsRes { - count: number; - page_size: number; - results: { - [date: string]: - | PressureSoreDiagramsRes - | ABGPlotsRes - | DialysisPlotsRes - | NeurologicalTablesRes - | NursingPlotRes - | RoutineAnalysisRes - | NutritionPlotsRes - | PainDiagramsRes - | PrimaryParametersPlotRes - | VentilatorPlotRes; - }; -} - -export interface CreateBedBody { - start_date: string; - assets: string[]; - consultation: string; - bed: string; -} - -// Patient Notes Model -export interface BaseFacilityModel { - id: string; - name: string; - local_body: number; - district: number; - state: number; - ward_object: WardModel; - local_body_object?: LocalBodyModel; - district_object?: DistrictModel; - state_object?: StateModel; - facility_type: FacilityType; - read_cover_image_url: any; - features: any[]; - patient_count: number; - bed_count: number; -} - export interface FacilityType { id: number; name: string; @@ -555,25 +117,6 @@ export interface PaitentNotesReplyModel { created_date: string; } -export interface PatientNotesModel { - id: string; - note: string; - facility: BaseFacilityModel; - created_by_object: BaseUserModel; - user_type?: UserRole | "RemoteSpecialist"; - thread: (typeof PATIENT_NOTES_THREADS)[keyof typeof PATIENT_NOTES_THREADS]; - created_date: string; - last_edited_by?: BaseUserModel; - last_edited_date?: string; - reply_to_object?: PaitentNotesReplyModel; -} - -export interface PatientNoteStateType { - notes: PatientNotesModel[]; - patientId?: string; - facilityId?: string; -} - export type IFacilityNotificationRequest = { facility: string; message: string; @@ -642,53 +185,6 @@ export type PatientTransferRequest = { year_of_birth: string; }; -export type PatientTransferResponse = { - id: string; - patient: string; - date_of_birth: string; - facility_object: BaseFacilityModel; -}; - -export interface ShiftingModel { - shifting_approving_facility_object: FacilityModel | null; - status: (typeof SHIFTING_CHOICES_PEACETIME)[number]["text"]; - id: string; - patient_object: PatientModel; - emergency: boolean; - origin_facility_object: FacilityModel; - origin_facility: string; - shifting_approving_facility: string; - assigned_facility_external: string | null; - assigned_facility: string | null; - is_up_shift: boolean; - assigned_to: number; - patient_category: string; - assigned_facility_object: FacilityModel; - assigned_facility_external_object: FacilityModel; - modified_date: string; - external_id: string; - assigned_to_object?: AssignedToObjectModel; - refering_facility_contact_name: string; - refering_facility_contact_number: string; - vehicle_preference: string; - preferred_vehicle_choice: string; - assigned_facility_type: string; - breathlessness_level: string; - reason: string; - ambulance_driver_name: string; - ambulance_phone_number: string | undefined; - ambulance_number: string; - comments: string; - created_date: string; - created_by_object: UserBareMinimum; - last_edited_by_object: UserBareMinimum; - is_assigned_to_user: boolean; - created_by: number; - last_edited_by: number; - patient: string | PatientModel; - initial_status?: string; -} - export interface ResourceModel { approving_facility: string | null; approving_facility_object: FacilityModel | null; diff --git a/src/components/HCX/InsuranceDetailsBuilder.tsx b/src/components/HCX/InsuranceDetailsBuilder.tsx deleted file mode 100644 index 730981593ad..00000000000 --- a/src/components/HCX/InsuranceDetailsBuilder.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import careConfig from "@careConfig"; -import { useTranslation } from "react-i18next"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import ButtonV2 from "@/components/Common/ButtonV2"; -import FormField, { FieldLabel } from "@/components/Form/FormFields/FormField"; -import TextFormField from "@/components/Form/FormFields/TextFormField"; -import { - FieldChangeEvent, - FormFieldBaseProps, - useFormFieldPropsResolver, -} from "@/components/Form/FormFields/Utils"; -import InsurerAutocomplete from "@/components/HCX/InsurerAutocomplete"; -import { HCXPolicyModel } from "@/components/HCX/models"; - -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import { classNames } from "@/Utils/utils"; - -type Props = FormFieldBaseProps & { gridView?: boolean }; - -export default function InsuranceDetailsBuilder(props: Props) { - const { t } = useTranslation(); - - const field = useFormFieldPropsResolver(props); - - const handleUpdate = (index: number) => { - return (event: FieldChangeEvent) => { - field.handleChange( - (props.value || [])?.map((obj, i) => - i === index ? { ...obj, [event.name]: event.value } : obj, - ), - ); - }; - }; - - const handleUpdates = (index: number) => { - return (diffs: object) => { - field.handleChange( - (props.value || [])?.map((obj, i) => - i === index ? { ...obj, ...diffs } : obj, - ), - ); - }; - }; - - const handleRemove = (index: number) => { - return async () => { - const updatedPolicies = [...(props.value || [])]; - const policyToRemove = updatedPolicies[index]; - - if (policyToRemove?.id) { - try { - await request(routes.hcx.policies.delete, { - pathParams: { external_id: policyToRemove.id }, - }); - - updatedPolicies.splice(index, 1); - field.handleChange(updatedPolicies); - } catch (error) { - console.error("Failed to delete the policy", error); - } - } else { - updatedPolicies.splice(index, 1); - field.handleChange(updatedPolicies); - } - }; - }; - - return ( - -
    - {props.value?.length === 0 && ( - - {t("no_policy_added")} - - )} - {props.value?.map((policy, index) => ( -
  • - -
  • - ))} -
-
- ); -} - -const InsuranceDetailEditCard = ({ - policy, - handleUpdate, - handleUpdates, - handleRemove, - gridView, -}: { - policy: HCXPolicyModel; - handleUpdate: (event: FieldChangeEvent) => void; - handleUpdates: (diffs: object) => void; - handleRemove: () => void; - gridView?: boolean; -}) => { - const { t } = useTranslation(); - const seletedInsurer = - policy.insurer_id && policy.insurer_name - ? { code: policy.insurer_id, name: policy.insurer_name } - : undefined; - - return ( -
-
- {t("policy")} - - {t("remove")} - - -
- -
- - - {careConfig.hcx.enabled ? ( - - handleUpdates({ - insurer_id: value.code, - insurer_name: value.name, - }) - } - /> - ) : ( - <> - - - - )} -
-
- ); -}; diff --git a/src/components/HCX/InsurerAutocomplete.tsx b/src/components/HCX/InsurerAutocomplete.tsx deleted file mode 100644 index 45c3f61e691..00000000000 --- a/src/components/HCX/InsurerAutocomplete.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useState } from "react"; - -import { Autocomplete } from "@/components/Form/FormFields/Autocomplete"; -import FormField from "@/components/Form/FormFields/FormField"; -import { - FormFieldBaseProps, - useFormFieldPropsResolver, -} from "@/components/Form/FormFields/Utils"; - -import routes from "@/Utils/request/api"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { mergeQueryOptions } from "@/Utils/utils"; - -export type InsurerOptionModel = { - name: string; - code: string; -}; - -type Props = FormFieldBaseProps & { - placeholder?: string; -}; - -export default function InsurerAutocomplete(props: Props) { - const field = useFormFieldPropsResolver(props); - - const [query, setQuery] = useState(""); - - const { data, loading } = useTanStackQueryInstead( - routes.hcx.policies.listPayors, - { - query: { query, limit: 10 }, - }, - ); - - return ( - - obj.code, - )} - optionLabel={(option) => option.name} - optionDescription={(option) => option.code} - optionValue={(option) => option} - onQuery={setQuery} - isLoading={loading} - /> - - ); -} diff --git a/src/components/HCX/PatientInsuranceDetailsEditor.tsx b/src/components/HCX/PatientInsuranceDetailsEditor.tsx deleted file mode 100644 index 1fcaea13ad9..00000000000 --- a/src/components/HCX/PatientInsuranceDetailsEditor.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import ButtonV2, { Cancel, Submit } from "@/components/Common/ButtonV2"; -import InsuranceDetailsBuilder from "@/components/HCX/InsuranceDetailsBuilder"; -import { HCXPolicyModel } from "@/components/HCX/models"; -import HCXPolicyValidator from "@/components/HCX/validators"; - -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; - -interface Props { - patient: string; - onSubmitted?: () => void; - onCancel?: () => void; -} - -export default function PatientInsuranceDetailsEditor({ - patient, - onSubmitted, - onCancel, -}: Props) { - const { t } = useTranslation(); - - const [insuranceDetails, setInsuranceDetails] = useState( - [], - ); - const [insuranceDetailsError, setInsuranceDetailsError] = useState(); - const [isUpdating, setIsUpdating] = useState(false); - - useTanStackQueryInstead(routes.hcx.policies.list, { - query: { patient }, - onResponse(res) { - if (res?.res?.ok && res.data) { - if (res.data.results.length) { - setInsuranceDetails(res.data.results); - } - } - }, - }); - - const handleSubmit = async () => { - // Validate - if (!insuranceDetails) return; - const insuranceDetailsError = insuranceDetails - .map(HCXPolicyValidator) - .find((error) => !!error); - setInsuranceDetailsError(insuranceDetailsError); - if (insuranceDetailsError) return; - - // Submit - setIsUpdating(true); - await Promise.all( - insuranceDetails.map(async (obj) => { - const policy: HCXPolicyModel = { ...obj, patient }; - policy.id - ? await request(routes.hcx.policies.update, { - pathParams: { external_id: policy.id }, - body: policy, - }) - : await request(routes.hcx.policies.create, { - body: policy, - }); - }), - ); - setIsUpdating(false); - onSubmitted?.(); - onCancel?.(); - }; - - return ( -
- setInsuranceDetails(value)} - error={insuranceDetailsError} - gridView - disabled={isUpdating} - /> - -
- - setInsuranceDetails([ - ...(insuranceDetails || []), - { - id: "", - subscriber_id: "", - policy_id: "", - insurer_id: "", - insurer_name: "", - }, - ]) - } - > - - {t("add_policy")} - -
- - - {isUpdating ? ( - <> - - {t("updating")} - - ) : ( - {t("update")} - )} - -
-
- ); -} diff --git a/src/components/HCX/PolicyEligibilityCheck.tsx b/src/components/HCX/PolicyEligibilityCheck.tsx deleted file mode 100644 index 53d08a4aaf8..00000000000 --- a/src/components/HCX/PolicyEligibilityCheck.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import ButtonV2 from "@/components/Common/ButtonV2"; -import { SelectFormField } from "@/components/Form/FormFields/SelectFormField"; -import { HCXPolicyModel } from "@/components/HCX/models"; - -import { useMessageListener } from "@/hooks/useMessageListener"; - -import * as Notification from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; - -interface Props { - className?: string; - patient: string; - onEligiblePolicySelected: (policy: HCXPolicyModel | undefined) => void; -} - -export default function HCXPolicyEligibilityCheck({ - className, - patient, - onEligiblePolicySelected, -}: Props) { - const { t } = useTranslation(); - - const [selectedPolicy, setSelectedPolicy] = useState(); - const [isCheckingEligibility, setIsCheckingEligibility] = useState(false); - - const { - refetch, - data: policiesResponse, - loading, - } = useTanStackQueryInstead(routes.hcx.policies.list, { - query: { patient }, - }); - - useMessageListener((data) => { - if ( - data.type === "MESSAGE" && - data.from === "coverageelegibility/on_check" - ) { - refetch(); - } - }); - - useEffect(() => { - onEligiblePolicySelected( - isPolicyEligible(selectedPolicy) ? selectedPolicy : undefined, - ); - }, [selectedPolicy]); // eslint-disable-line react-hooks/exhaustive-deps - - const checkEligibility = async () => { - if (!selectedPolicy || isPolicyEligible()) return; - - setIsCheckingEligibility(true); - - const { res } = await request(routes.hcx.policies.checkEligibility, { - body: { policy: selectedPolicy.id }, - }); - - if (res?.ok) { - Notification.Success({ msg: t("checking_policy_eligibility") }); - } - - setIsCheckingEligibility(false); - }; - - return ( -
-
- option.id} - optionLabel={(option) => option.policy_id} - optionSelectedLabel={(option) => - option.outcome ? ( -
- {option.policy_id} - -
- ) : ( - option.policy_id - ) - } - optionIcon={(option) => - option.outcome && ( - - ) - } - onChange={({ value }) => { - setSelectedPolicy( - policiesResponse?.results.find((policy) => policy.id === value), - ); - }} - value={selectedPolicy?.id} - placeholder={ - loading - ? t("loading") - : policiesResponse?.results.length - ? t("select_policy") - : t("no_policy_found") - } - disabled={!policiesResponse?.results.length} - optionDescription={(option) => ( -
-
- - {t("policy__subscriber_id")} - - {option.subscriber_id} - - - - {t("policy__insurer_id")} - - {option.insurer_id} - - - - {t("policy__insurer_name")} - - {option.insurer_name} - - -
- {option.error_text && ( - - {option.error_text} - - )} -
- )} - /> - - {isCheckingEligibility ? ( - <> - - {t("checking_eligibility")} - - ) : ( - t("check_eligibility") - )} - -
-
- ); -} - -const EligibilityChip = ({ eligible }: { eligible: boolean }) => { - const { t } = useTranslation(); - - return ( -
- - - {eligible ? t("eligible") : t("not_eligible")} - -
- ); -}; - -const isPolicyEligible = (policy?: HCXPolicyModel) => - policy && !policy.error_text && policy.outcome === "Complete"; diff --git a/src/components/HCX/models.ts b/src/components/HCX/models.ts deleted file mode 100644 index 359ee8b886f..00000000000 --- a/src/components/HCX/models.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { PatientModel } from "@/types/emr/patient"; - -export type HCXPolicyPriority = "Immediate" | "Normal" | "Deferred"; -export type HCXPolicyStatus = - | "Active" - | "Cancelled" - | "Draft" - | "Entered In Error"; -export type HCXPolicyPurpose = - | "Auth Requirements" - | "Benefits" - | "Discovery" - | "Validation"; -export type HCXPolicyOutcome = - | "Queued" - | "Complete" - | "Error" - | "Partial Processing"; - -export interface HCXPolicyModel { - id: string; - patient?: string; - patient_object?: PatientModel; - subscriber_id: string; - policy_id: string; - insurer_id?: string; - insurer_name?: string; - status?: HCXPolicyStatus; - priority?: HCXPolicyPriority; - purpose?: HCXPolicyPurpose; - outcome?: HCXPolicyOutcome; - error_text?: string; - created_date?: string; - modified_date?: string; -} diff --git a/src/components/HCX/validators.ts b/src/components/HCX/validators.ts deleted file mode 100644 index 2dfb5bacc19..00000000000 --- a/src/components/HCX/validators.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { t } from "i18next"; - -import { FieldValidator } from "@/components/Form/FieldValidators"; -import { HCXPolicyModel } from "@/components/HCX/models"; - -const HCXPolicyValidator: FieldValidator = ( - value, - enable_hcx, -) => { - if (!value.subscriber_id.trim()) { - return t("member_id_required"); - } else if (!value.policy_id.trim()) { - return t("policy_id_required"); - } - if (enable_hcx) { - if (!value.insurer_id?.trim() || !value.insurer_name?.trim()) { - return t("insurer_name_required"); - } - } -}; - -export default HCXPolicyValidator; diff --git a/src/components/LogUpdate/CriticalCareEditor.tsx b/src/components/LogUpdate/CriticalCareEditor.tsx deleted file mode 100644 index 11978b06be8..00000000000 --- a/src/components/LogUpdate/CriticalCareEditor.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import { navigate } from "raviger"; -import React, { useState } from "react"; -import { useTranslation } from "react-i18next"; - -import Card from "@/CAREUI/display/Card"; -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import ButtonV2, { Submit } from "@/components/Common/ButtonV2"; -import Loading from "@/components/Common/Loading"; -import LogUpdateSections, { - RoundTypeSections, -} from "@/components/LogUpdate/Sections"; -import { DailyRoundsModel } from "@/components/Patient/models"; - -import { useSlugs } from "@/hooks/useSlug"; - -import { Success } from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { classNames } from "@/Utils/utils"; - -type Props = { - facilityId: string; - patientId: string; - consultationId: string; - id: string; -}; - -type SectionKey = keyof typeof LogUpdateSections; - -export default function CriticalCareEditor(props: Props) { - const { t } = useTranslation(); - - const query = useTanStackQueryInstead(routes.getDailyReport, { - pathParams: { consultationId: props.consultationId, id: props.id }, - }); - - const [completed, setCompleted] = useState([]); - const [current, setCurrent] = useState(); - - if (query.loading || !query.data) { - return ; - } - - const roundType = query.data.rounds_type ?? "VENTILATOR"; - - const sections = RoundTypeSections[roundType]; - const consultationDashboardUrl = `/facility/${props.facilityId}/patient/${props.patientId}/consultation/${props.consultationId}`; - - return ( -
-
- { - if (current) { - setCurrent(undefined); - } else { - navigate(consultationDashboardUrl); - } - }} - > - {t(current ? "back" : "back_to_consultation")} - -
- -

- {current - ? LogUpdateSections[current].meta.title - : t("record_updates")} -

- {current ? ( - { - if (!completed.find((o) => o === current)) { - setCompleted((c) => [...c, current]); - } - setCurrent(undefined); - query.refetch(); - }} - /> - ) : ( -
    -
  • - - - - Basic Editor - - - -
  • - {sections.map((key) => { - const isCompleted = completed.includes(key as SectionKey); - const section = LogUpdateSections[key as SectionKey]; - - return ( -
  • - setCurrent(key as SectionKey)} - > - {section.meta.icon && ( - - )} - - {section.meta.title} - - - -
  • - ); - })} -
- )} - - {!current && ( - { - if (roundType === "VENTILATOR") { - Success({ msg: "Detailed Log Update filed successfully" }); - } else if (roundType === "DOCTORS_LOG") { - Success({ msg: "Progress Note Log Update filed successfully" }); - } - - navigate(consultationDashboardUrl); - }} - /> - )} -
-
- ); -} - -type SectionEditorProps = { - log: DailyRoundsModel; - onComplete: () => void; - section: SectionKey; -}; - -const SectionEditor = ({ log, onComplete, section }: SectionEditorProps) => { - const [consultationId, id] = useSlugs("consultation", "log_updates"); - const [diff, setDiff] = useState>({}); - const [isProcessing, setIsProcessing] = useState(false); - - const Section = LogUpdateSections[section]; - - return ( -
-
setDiff((base) => ({ ...base, ...changes }))} - /> - { - setIsProcessing(true); - const { res } = await request(routes.updateDailyRound, { - pathParams: { consultationId, id }, - body: diff, - }); - setIsProcessing(false); - if (res?.ok) { - onComplete(); - Success({ - msg: `${Section.meta.title} details succesfully updated.`, - }); - } - }} - /> -
- ); -}; diff --git a/src/components/LogUpdate/CriticalCarePreview.tsx b/src/components/LogUpdate/CriticalCarePreview.tsx deleted file mode 100644 index 0ec8204868c..00000000000 --- a/src/components/LogUpdate/CriticalCarePreview.tsx +++ /dev/null @@ -1,685 +0,0 @@ -import { Link } from "raviger"; -import React, { useEffect } from "react"; -import { useTranslation } from "react-i18next"; - -import Card from "@/CAREUI/display/Card"; -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import { Button } from "@/components/ui/button"; - -import { meanArterialPressure } from "@/components/Common/BloodPressureFormField"; -import Loading from "@/components/Common/Loading"; -import { ABGAnalysisFields } from "@/components/LogUpdate/Sections/ABGAnalysis"; -import { IOBalanceSections } from "@/components/LogUpdate/Sections/IOBalance"; -import PressureSore from "@/components/LogUpdate/Sections/PressureSore/PressureSore"; -import { VentilatorFields } from "@/components/LogUpdate/Sections/RespiratorySupport/Ventilator"; -import PainChart from "@/components/LogUpdate/components/PainChart"; -import { DailyRoundsModel } from "@/components/Patient/models"; - -import routes from "@/Utils/request/api"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { - ValueDescription, - classNames, - properRoundOf, - rangeValueDescription, -} from "@/Utils/utils"; - -type Props = { - facilityId: string; - patientId: string; - consultationId: string; - id: string; -}; - -export default function CriticalCarePreview(props: Props) { - const { t } = useTranslation(); - const { data } = useTanStackQueryInstead(routes.getDailyReport, { - pathParams: { - consultationId: props.consultationId, - id: props.id, - }, - }); - - if (!data) { - return ; - } - - const tOption = (prefix: string, key: keyof typeof data) => { - const value = data[key]; - return value && t(`${prefix}__${value}`); - }; - - return ( -
-
- - -
- - -

- {t("log_updates")} -
- {t(`ROUNDS_TYPE__${data.rounds_type}`)} -
-

- -
- - - -
- -
- - -
- - - - -
-
- - - -
-
- -
- - {(data.left_pupil_light_reaction || - data.left_pupil_light_reaction_detail || - data.left_pupil_size || - data.left_pupil_size_detail || - data.right_pupil_light_reaction || - data.right_pupil_light_reaction_detail || - data.right_pupil_size || - data.right_pupil_size_detail) && ( -
- {(["left", "right"] as const).map((dir) => ( -
-
{dir} Pupil
- - {data[`${dir}_pupil_size`] === 0 && ( - - )} - - -
- ))} -
- )} -
- - - - -
-
Limb Response
-
- {( - [ - "upper_extremity_left", - "upper_extremity_right", - "lower_extremity_left", - "lower_extremity_right", - ] as const - ).map((key) => ( -
- - {key.replace("extremity", "").replaceAll("_", " ")} - - } - value={tOption("LIMB_RESPONSE", `limb_response_${key}`)} - /> -
- ))} -
-
- -
-
    - {ABGAnalysisFields.map((field) => { - const value = data[field.key]; - return ( -
  • - -
  • - ); - })} -
-
- -
- - - -
- -
- - -
- -
- {data.bp && ( -
-
Blood Pressure
- - - -
- )} - - SpO2 - - } - value={data.ventilator_spo2} - max={100} - unit="%" - valueDescriptions={rangeValueDescription({ low: 89 })} - /> - - - - - - {!!data.pain_scale_enhanced?.length && ( - <> -

Pain Scale

- - - )} -
- - {!!IOBalanceSections.flatMap((s) => - s.fields.flatMap((f) => data[f.key] ?? []), - ).length && ( -
- s.fields.map((f) => f.key), - ).some((field) => data[field]?.length)} - > -
- {IOBalanceSections.map(({ name, fields }) => ( -
-
{name}
- - - - - - - - - {fields.map((category) => - data[category.key]?.map((row) => ( - - - - - )), - )} - - - - - - - -
Name - Quantity -
- - {category.name} - - : {row.name} - - {row.quantity} ml -
Total {name} - {fields - .flatMap((f) => - (data[f.key] || []).map((f) => f.quantity), - ) - .reduce((a, b) => a + b, 0)}{" "} - ml -
-
- ))} - - s.fields - .flatMap((f) => (data[f.key] || []).map((f) => f.quantity)) - .reduce((a, b) => a + b, 0), - ).reduce((a, b) => a - b)} - suffix="ml" - /> -
-
- )} - - {!!data.nursing?.length && ( -
-
    - {data.nursing.map((care) => ( -
  • - -
  • - ))} -
-
- )} - -
- { - // - }} - /> -
- -
- - - EtCO2 - - } - value={data.etco2} - valueDescriptions={rangeValueDescription({ low: 34, high: 45 })} - max={200} - unit="mmHg" - /> - - - - {data.ventilator_interface === "OXYGEN_SUPPORT" && ( - <> - - {data.ventilator_oxygen_modality === "HIGH_FLOW_NASAL_CANNULA" ? ( - <> - - - FiO2 - - } - value={data.ventilator_fio2} - max={100} - unit="%" - valueDescriptions={rangeValueDescription({ high: 60 })} - /> - - ) : ( - - )} - - SpO2 - - } - value={data.ventilator_spo2} - max={100} - unit="%" - valueDescriptions={rangeValueDescription({ low: 89 })} - /> - - )} - - {(data.ventilator_interface === "INVASIVE" || - data.ventilator_interface === "NON_INVASIVE") && ( - <> -
- - {VentilatorFields.map((field) => { - const value = data[field.key]; - return ( -
- -
- ); - })} - - )} -
-
-
- ); -} - -type SectionContextType = { - hasValue: () => void; -}; - -const sectionContext = React.createContext(null); - -const Section = (props: { - title: string; - children: React.ReactNode; - subSection?: boolean; - show?: boolean; -}) => { - const parentContext = React.useContext(sectionContext); - const [hasValue, setHasValue] = React.useState(props.show ?? false); - - useEffect(() => { - if (parentContext && hasValue) { - parentContext.hasValue(); - } - }, [parentContext, hasValue]); - - return ( - setHasValue(true), - }} - > -
- {props.subSection ? ( -
{props.title}
- ) : ( -

{props.title}

- )} - {props.children} -
-
- ); -}; - -const Detail = (props: { - label: React.ReactNode; - value?: string | number | boolean | null; - suffix?: React.ReactNode; -}) => { - const context = React.useContext(sectionContext); - - let value = props.value; - value = value === "" ? null : value; - value = value === true ? "Yes" : value; - value = value === false ? "No" : value; - - React.useEffect(() => { - if (context && value != null) { - context.hasValue(); - } - }, [context, value]); - - if (value == null) { - // Skip showing detail if attribute not filled. - return null; - } - - value = typeof value === "string" ? parseFloat(value) || value : value; - value = typeof value === "number" ? properRoundOf(value) : value; - - return ( -

- {props.label}: - {value != null ? ( - - {value} {props.suffix} - - ) : ( - -- - )} -

- ); -}; - -const ChoiceDetail = (props: { - name: keyof DailyRoundsModel; - data: DailyRoundsModel; -}) => { - const { t } = useTranslation(); - const value = props.data[props.name]; - - if (value == null) { - return; - } - - return ( - - ); -}; - -const RangeDetail = (props: { - label: React.ReactNode; - value?: number; - unit: React.ReactNode; - max: number; - valueDescriptions: ValueDescription[]; -}) => { - const valueDescription = - props.value == null - ? null - : props.valueDescriptions.find( - (vd) => (vd.till || props.max) >= (props.value || 0), - ); - return ( - - {props.unit}{" "} - - {valueDescription?.text} - - - } - /> - ); -}; diff --git a/src/components/LogUpdate/Sections/ABGAnalysis.tsx b/src/components/LogUpdate/Sections/ABGAnalysis.tsx deleted file mode 100644 index 4a432368909..00000000000 --- a/src/components/LogUpdate/Sections/ABGAnalysis.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { ReactNode } from "react"; - -import RangeFormField from "@/components/Form/FormFields/RangeFormField"; -import { - LogUpdateSectionMeta, - LogUpdateSectionProps, -} from "@/components/LogUpdate/utils"; -import { DailyRoundsModel } from "@/components/Patient/models"; - -import { ValueDescription, rangeValueDescription } from "@/Utils/utils"; - -export const ABGAnalysisFields = [ - { - key: "po2", - label: ( - - PO2 - - ), - unit: "mmHg", - min: 10, - max: 400, - valueDescription: rangeValueDescription({ low: 49, high: 200 }), - }, - { - key: "pco2", - label: ( - - PCO2 - - ), - unit: "mmHg", - min: 10, - max: 200, - valueDescription: rangeValueDescription({ low: 34, high: 45 }), - }, - { - key: "ph", - label: "pH", - unit: "", - min: 0, - max: 10, - step: 0.1, - valueDescription: rangeValueDescription({ low: 7.35, high: 7.45 }), - }, - { - key: "hco3", - label: ( - - HCO3 - - ), - unit: "mmol/L", - min: 5, - max: 80, - step: 0.1, - valueDescription: rangeValueDescription({ low: 21.9, high: 26 }), - }, - { - key: "base_excess", - label: "Base Excess", - unit: "mmol/L", - min: -20, - max: 20, - valueDescription: rangeValueDescription({ low: -3, high: 2 }), - }, - { - key: "lactate", - label: "Lactate", - unit: "mmol/L", - min: 0, - max: 20, - step: 0.1, - valueDescription: rangeValueDescription({ low: 3 }), - }, - { - key: "sodium", - label: "Sodium", - unit: "mmol/L", - min: 100, - max: 170, - step: 0.1, - valueDescription: rangeValueDescription({ low: 134, high: 145 }), - }, - { - key: "potassium", - label: "Potassium", - unit: "mmol/L", - min: 0, - max: 10, - step: 0.1, - valueDescription: rangeValueDescription({ low: 3.4, high: 5.5 }), - }, -] satisfies { - key: keyof DailyRoundsModel; - label: ReactNode; - unit: string; - min: number; - max: number; - step?: number; - valueDescription: ValueDescription[]; -}[]; - -const ABGAnalysis = ({ log, onChange }: LogUpdateSectionProps) => { - return ( -
- {ABGAnalysisFields.map((field, index) => ( - onChange({ [field.key]: c.value })} - value={log[field.key] as number} - min={field.min} - max={field.max} - step={field.step || 1} - valueDescriptions={field.valueDescription} - /> - ))} -
- ); -}; - -ABGAnalysis.meta = { - title: "Arterial Blood Gas Analysis", - icon: "l-tear", -} as const satisfies LogUpdateSectionMeta; - -export default ABGAnalysis; diff --git a/src/components/LogUpdate/Sections/BloodSugar.tsx b/src/components/LogUpdate/Sections/BloodSugar.tsx deleted file mode 100644 index 8be63fb680f..00000000000 --- a/src/components/LogUpdate/Sections/BloodSugar.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import RadioFormField from "@/components/Form/FormFields/RadioFormField"; -import RangeFormField from "@/components/Form/FormFields/RangeFormField"; -import { - LogUpdateSectionMeta, - LogUpdateSectionProps, -} from "@/components/LogUpdate/utils"; - -import { INSULIN_INTAKE_FREQUENCY_OPTIONS } from "@/common/constants"; - -import { rangeValueDescription } from "@/Utils/utils"; - -const BloodSugar = ({ log, onChange }: LogUpdateSectionProps) => { - const { t } = useTranslation(); - - return ( -
- onChange({ blood_sugar_level: c.value })} - value={log.blood_sugar_level} - min={0} - max={700} - valueDescriptions={rangeValueDescription({ low: 69, high: 110 })} - /> -
-
-
-
Insulin Intake
- onChange({ insulin_intake_dose: c.value })} - value={log.insulin_intake_dose} - min={0} - max={100} - step={0.1} - /> - t(`INSULIN_INTAKE_FREQUENCY__${c}`)} - optionValue={(c) => c} - value={log.insulin_intake_frequency} - onChange={(c) => - onChange({ - insulin_intake_frequency: c.value || undefined, - }) - } - /> -
- ); -}; - -BloodSugar.meta = { - title: "Blood Sugar", - icon: "l-tear", -} as const satisfies LogUpdateSectionMeta; - -export default BloodSugar; diff --git a/src/components/LogUpdate/Sections/Dialysis.tsx b/src/components/LogUpdate/Sections/Dialysis.tsx deleted file mode 100644 index 784152d4ece..00000000000 --- a/src/components/LogUpdate/Sections/Dialysis.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import RangeFormField from "@/components/Form/FormFields/RangeFormField"; -import { - LogUpdateSectionMeta, - LogUpdateSectionProps, -} from "@/components/LogUpdate/utils"; - -const Dialysis = ({ log, onChange }: LogUpdateSectionProps) => { - return ( - <> - onChange({ dialysis_fluid_balance: c.value })} - value={log.dialysis_fluid_balance} - min={0} - max={5000} - /> -
- onChange({ dialysis_net_balance: c.value })} - value={log.dialysis_net_balance} - min={0} - max={5000} - /> - - ); -}; - -Dialysis.meta = { - title: "Dialysis", - icon: "l-jackhammer", -} as const satisfies LogUpdateSectionMeta; - -export default Dialysis; diff --git a/src/components/LogUpdate/Sections/IOBalance.tsx b/src/components/LogUpdate/Sections/IOBalance.tsx deleted file mode 100644 index 91d5e577d06..00000000000 --- a/src/components/LogUpdate/Sections/IOBalance.tsx +++ /dev/null @@ -1,201 +0,0 @@ -import { Fragment } from "react/jsx-runtime"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import ButtonV2 from "@/components/Common/ButtonV2"; -import { SelectFormField } from "@/components/Form/FormFields/SelectFormField"; -import TextFormField from "@/components/Form/FormFields/TextFormField"; -import { - LogUpdateSectionMeta, - LogUpdateSectionProps, -} from "@/components/LogUpdate/utils"; -import { DailyRoundsModel } from "@/components/Patient/models"; - -export const IOBalanceSections = [ - { - name: "Intake", - fields: [ - { - name: "Infusions", - options: [ - "Adrenalin", - "Noradrenalin", - "Vasopressin", - "Dopamine", - "Dobutamine", - ], - key: "infusions", - }, - { - name: "IV Fluids", - options: ["RL", "NS", "DNS"], - key: "iv_fluids", - }, - { - name: "Feed", - options: ["Ryles Tube", "Normal Feed"], - key: "feeds", - }, - ], - }, - { - name: "Outturn", - fields: [ - { - name: "Output", - options: ["Urine", "Ryles Tube Aspiration", "ICD", "Abdominal Drain"], - key: "output", - }, - ], - }, -] satisfies { - name: string; - fields: { - name: string; - options: string[]; - key: keyof DailyRoundsModel; - }[]; -}[]; - -const IOBalance = ({ log, onChange }: LogUpdateSectionProps) => { - return ( -
- {IOBalanceSections.map(({ name, fields }, k) => ( - -

{name}

- {fields.map((field, i) => ( -
-

{field.name}

- {log[field.key]?.map(({ name, quantity }, j) => ( -
-
- {j == 0 && ( -
- Type -
- )} - - !log[field.key] - ?.map((f) => f.name) - .includes(option), - ) - .concat(field.options.includes(name) ? [name] : [])} - optionLabel={(f) => f} - value={name} - onChange={({ value }) => - onChange({ - [field.key]: log[field.key]?.map((f, fi) => - j === fi ? { ...f, name: value } : f, - ), - }) - } - className="w-full" - errorClassName="hidden" - /> -
- - onChange({ - [field.key]: log[field.key]?.map((f, fi) => - j === fi - ? { ...f, quantity: parseInt(val.value) } - : f, - ), - }) - } - label={ - j == 0 && ( -
- Quantity (ml) -
- ) - } - /> - - onChange({ - [field.key]: log[field.key]?.filter( - (f, fi) => j !== fi, - ), - }) - } - > - - -
- ))} - - onChange({ - [field.key]: [ - ...(log[field.key] || []), - { name: null, quantity: 0 }, - ], - }) - } - disabled={field.options.length === log[field.key]?.length} - > - - Add {field.name} - -
- ))} -
-

Total

-
- {fields - .flatMap((f) => (log[f.key] || []).map((f) => f.quantity)) - .join("+")} - = - - {fields - .flatMap((f) => (log[f.key] || []).map((f) => f.quantity)) - .reduce((a, b) => a + b, 0)}{" "} - ml - -
-
-
- ))} -
-

I/O Balance

-
- {IOBalanceSections.map((s) => - s.fields - .flatMap((f) => (log[f.key] || []).map((f) => f.quantity)) - .reduce((a, b) => a + b, 0), - ).join("-")} - = - - {IOBalanceSections.map((s) => - s.fields - .flatMap((f) => (log[f.key] || []).map((f) => f.quantity)) - .reduce((a, b) => a + b, 0), - ).reduce((a, b) => a - b)}{" "} - ml - -
-
-
- ); -}; - -IOBalance.meta = { - title: "I/O Balance", - icon: "l-balance-scale", -} as const satisfies LogUpdateSectionMeta; - -export default IOBalance; diff --git a/src/components/LogUpdate/Sections/NeurologicalMonitoring.tsx b/src/components/LogUpdate/Sections/NeurologicalMonitoring.tsx deleted file mode 100644 index 64a800a4e39..00000000000 --- a/src/components/LogUpdate/Sections/NeurologicalMonitoring.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import CheckBoxFormField from "@/components/Form/FormFields/CheckBoxFormField"; -import RadioFormField from "@/components/Form/FormFields/RadioFormField"; -import TextAreaFormField from "@/components/Form/FormFields/TextAreaFormField"; -import PupilSizeSelect from "@/components/LogUpdate/components/PupilSizeSelect"; -import { - LogUpdateSectionMeta, - LogUpdateSectionProps, -} from "@/components/LogUpdate/utils"; - -import { - CONSCIOUSNESS_LEVEL, - EYE_OPEN_SCALE, - LIMB_RESPONSE_OPTIONS, - MOTOR_RESPONSE_SCALE, - PUPIL_REACTION_OPTIONS, - VERBAL_RESPONSE_SCALE, -} from "@/common/constants"; - -const NeurologicalMonitoring = ({ log, onChange }: LogUpdateSectionProps) => { - const { t } = useTranslation(); - return ( -
-
- onChange({ in_prone_position: e.value })} - errorClassName="hidden" - /> -
-
-

Level of Consciousness

- t(`CONSCIOUSNESS_LEVEL__${c.value}`)} - optionValue={(c) => c.value} - value={log.consciousness_level} - onChange={(c) => - onChange({ - consciousness_level: - c.value as (typeof CONSCIOUSNESS_LEVEL)[number]["value"], - }) - } - layout="vertical" - /> -
-
-
- {!log.in_prone_position && ( - <> -

Pupil

-
- {(["left", "right"] as const).map((d, i) => ( -
-

{d} Pupil

- -
-
Reaction
- o.value !== "UNKNOWN", - )} - id={`${d}_reaction`} - optionLabel={(c) => t(`PUPIL_REACTION__${c.value}`)} - optionValue={(c) => c.value} - name={`${d}_pupil_light_reaction`} - value={log[`${d}_pupil_light_reaction`]} - onChange={(c) => - onChange({ [`${d}_pupil_light_reaction`]: c.value }) - } - /> - {log[`${d}_pupil_light_reaction`] === "CANNOT_BE_ASSESSED" && ( -
- - onChange({ - [`${d}_pupil_light_reaction_detail`]: c.value, - }) - } - /> -
- )} -
- ))} -
-
-
-
- - )} -
-

Glasgow Coma Scale

-
-
- Eye Opening Response} - options={EYE_OPEN_SCALE} - optionLabel={(c) => c.value + " - " + c.text} - optionValue={(c) => `${c.value}`} - name="eye_opening_response" - value={`${log.glasgow_eye_open}`} - onChange={(c) => - onChange({ glasgow_eye_open: parseInt(`${c.value}`) }) - } - layout="vertical" - /> - Verbal Response} - options={VERBAL_RESPONSE_SCALE} - optionLabel={(c) => c.value + " - " + c.text} - optionValue={(c) => `${c.value}`} - name="verbal_response" - value={`${log.glasgow_verbal_response}`} - onChange={(c) => - onChange({ - glasgow_verbal_response: parseInt(`${c.value}`), - }) - } - layout="vertical" - /> - Motor Response} - options={MOTOR_RESPONSE_SCALE} - optionLabel={(c) => c.value + " - " + c.text} - optionValue={(c) => `${c.value}`} - name="motor_response" - value={`${log.glasgow_motor_response}`} - onChange={(c) => - onChange({ - glasgow_motor_response: parseInt(`${c.value}`), - }) - } - layout="vertical" - errorClassName="hidden" - /> -
-
- Total  - - {(log.glasgow_eye_open || 0) + - (log.glasgow_verbal_response || 0) + - (log.glasgow_motor_response || 0)} - -
-
-

Limb Response

-
- {( - [ - "limb_response_upper_extremity_left", - "limb_response_upper_extremity_right", - "limb_response_lower_extremity_left", - "limb_response_lower_extremity_right", - ] as const - ).map((key) => ( - - {key.replaceAll("limb_response_", "").replaceAll("_", " ")} - - } - options={LIMB_RESPONSE_OPTIONS.filter((o) => o.value !== "UNKNOWN")} - optionLabel={(c) => t(`LIMB_RESPONSE__${c.value}`)} - optionValue={(c) => c.value} - name={key} - value={log[key]} - onChange={(c) => onChange({ [key]: c.value })} - /> - ))} -
-
- ); -}; - -NeurologicalMonitoring.meta = { - title: "Neurological Monitoring", - icon: "l-brain", -} as const satisfies LogUpdateSectionMeta; - -export default NeurologicalMonitoring; diff --git a/src/components/LogUpdate/Sections/NursingCare.tsx b/src/components/LogUpdate/Sections/NursingCare.tsx deleted file mode 100644 index 52bc72372e5..00000000000 --- a/src/components/LogUpdate/Sections/NursingCare.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import AutoExpandingTextInputFormField from "@/components/Form/FormFields/AutoExpandingTextInputFormField"; -import AutocompleteMultiSelectFormField from "@/components/Form/FormFields/AutocompleteMultiselect"; -import { - LogUpdateSectionMeta, - LogUpdateSectionProps, -} from "@/components/LogUpdate/utils"; - -import { NURSING_CARE_PROCEDURES } from "@/common/constants"; - -const NursingCare = ({ log, onChange }: LogUpdateSectionProps) => { - const { t } = useTranslation(); - const nursing = log.nursing || []; - - return ( -
- p.procedure)} - onChange={({ value }) => { - onChange({ - nursing: value.map((procedure) => ({ - procedure, - description: - nursing.find((p) => p.procedure === procedure)?.description ?? - "", - })), - }); - }} - options={NURSING_CARE_PROCEDURES} - optionLabel={(procedure) => t(`NURSING_CARE_PROCEDURE__${procedure}`)} - optionValue={(o) => o} - errorClassName="hidden" - /> - {!!nursing.length && ( - - - {nursing.map((obj) => ( - - - - - ))} - -
- {t(`NURSING_CARE_PROCEDURE__${obj.procedure}`)} - - - onChange({ - nursing: nursing.map((n) => - n.procedure === obj.procedure - ? { ...n, description: val.value } - : n, - ), - }) - } - rows={1} - maxHeight={160} - placeholder={t("add_remarks")} - errorClassName="hidden" - /> -
- )} -
- ); -}; - -NursingCare.meta = { - title: "Nursing Care", - icon: "l-user-nurse", -} as const satisfies LogUpdateSectionMeta; - -export default NursingCare; diff --git a/src/components/LogUpdate/Sections/PressureSore/PressureSore.tsx b/src/components/LogUpdate/Sections/PressureSore/PressureSore.tsx deleted file mode 100644 index e34b62aa74e..00000000000 --- a/src/components/LogUpdate/Sections/PressureSore/PressureSore.tsx +++ /dev/null @@ -1,248 +0,0 @@ -import { useEffect, useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import PopupModal from "@/CAREUI/display/PopupModal"; -import HumanBodyChart from "@/CAREUI/interactive/HumanChart"; - -import { SelectFormField } from "@/components/Form/FormFields/SelectFormField"; -import TextAreaFormField from "@/components/Form/FormFields/TextAreaFormField"; -import TextFormField from "@/components/Form/FormFields/TextFormField"; -import { calculatePushScore } from "@/components/LogUpdate/Sections/PressureSore/utils"; -import { - LogUpdateSectionMeta, - LogUpdateSectionProps, -} from "@/components/LogUpdate/utils"; -import { IPressureSore } from "@/components/Patient/models"; - -import { - HumanBodyRegion, - PressureSoreExudateAmountOptions, - PressureSoreTissueTypeOptions, -} from "@/common/constants"; - -import { Error } from "@/Utils/Notifications"; -import { classNames, getValueDescription } from "@/Utils/utils"; - -const PressureSore = ({ log, onChange, readonly }: LogUpdateSectionProps) => { - const value = log.pressure_sore ?? []; - const containerRef = useRef(null); - const [current, setCurrent] = useState(); - - const regionPushScore = (region: IPressureSore["region"]) => { - const obj = value.find((obj) => obj.region === region); - if (obj) { - return calculatePushScore(obj); - } - }; - - const valueDescription = (region: IPressureSore["region"]) => { - const pushScore = regionPushScore(region); - if (pushScore != null) { - return getValueDescription(valueDescriptions, pushScore); - } - }; - - return ( -
- setCurrent(undefined)} - onSave={ - readonly - ? undefined - : (obj) => { - const pressure_sore = value.filter( - (v) => v.region !== obj.region, - ); - pressure_sore.push(obj); - onChange({ pressure_sore }); - setCurrent(undefined); - } - } - /> -

Braden Scale (Risk Severity)

-
- { - setCurrent( - value.find((o) => o.region === region) ?? - getRegionInitialData(region), - ); - }} - regionColor={(r) => valueDescription(r)?.color || "#ECECEC"} - regionLabelClassName={(r) => - classNames( - "border transition-all duration-200 ease-in-out", - valueDescription(r)?.className || - "border-secondary-400 bg-secondary-100", - current?.region === r && - "font-bold shadow-lg ring-2 ring-primary-400", - ) - } - regionText={(r) => regionPushScore(r)?.toString() ?? ""} - /> -
- ); -}; - -PressureSore.meta = { - title: "Pressure Sore", - icon: "l-user-md", -} as const satisfies LogUpdateSectionMeta; - -export default PressureSore; - -type RegionEditorProps = { - show: boolean; - value: IPressureSore; - anchorRef: React.RefObject; - onCancel: () => void; - onSave?: (value: IPressureSore) => void; -}; - -const RegionEditor = (props: RegionEditorProps) => { - const [value, setValue] = useState(props.value); - useEffect(() => setValue(props.value), [props.value]); - - const update = (diff: Partial) => { - setValue((base) => ({ ...base, ...diff })); - }; - - const isReadOnly = !props.onSave; - - const { t } = useTranslation(); - - return ( - { - if (value.width <= 0 || value.length <= 0) { - Error({ msg: "Width & Length must be greater than 0." }); - } else { - props.onSave?.(value); - } - } - : undefined - } - > -
-

- {value.region.split(/(?=[A-Z])/).join(" ")} -

-
-
- update({ width: parseFloat(e.value) })} - /> - update({ length: parseFloat(e.value) })} - /> - o} - optionValue={(o) => o} - disabled={isReadOnly} - labelClassName="text-xs" - label="Exudate Amount" - value={value.exudate_amount} - onChange={({ value }) => update({ exudate_amount: value })} - name="exudate_amount" - /> - o} - optionValue={(o) => o} - disabled={isReadOnly} - labelClassName="text-xs" - label="Tissue Type" - value={value.tissue_type} - onChange={({ value }) => update({ tissue_type: value })} - name="tissue_type" - /> -
-
- update({ description: e.value })} - /> -
- -
-
Push Score:
-
{calculatePushScore(value)}
-
-
-
- ); -}; - -const valueDescriptions = [ - { - till: 0, - color: "#ECECEC", - text: "", - className: "bg-secondary-300 border border-secondary-400", - }, - { - till: 3, - color: "#f87171", - text: "", - className: "bg-red-400 text-white border border-secondary-400", - }, - { - till: 7, - color: "#dc2626", - text: "", - className: "bg-red-600 text-white border border-secondary-400", - }, - { - till: 17, - color: "#991b1b", - text: "", - className: "bg-red-800 text-white border border-secondary-400", - }, -]; - -const getRegionInitialData = (region: HumanBodyRegion): IPressureSore => ({ - region, - width: 0, - length: 0, - exudate_amount: "None", - tissue_type: "Closed", - description: "", - scale: 1, -}); diff --git a/src/components/LogUpdate/Sections/PressureSore/utils.ts b/src/components/LogUpdate/Sections/PressureSore/utils.ts deleted file mode 100644 index 144b3fac0d9..00000000000 --- a/src/components/LogUpdate/Sections/PressureSore/utils.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { IPressureSore } from "@/components/Patient/models"; - -import { - PressureSoreExudateAmountOptions, - PressureSoreTissueTypeOptions, -} from "@/common/constants"; - -const areaIntervalPoints = [0.0, 0.3, 0.6, 1.0, 2.2, 3.0, 4.0, 8.0, 12.0, 24.0]; - -const getAreaScore = (area: number): number => { - const index = areaIntervalPoints.findIndex((p) => area <= p); - return index === -1 ? 10 : index; -}; - -const getIndexScore = (arr: readonly T[], value: T) => { - const index = arr.indexOf(value); - return index === -1 ? 0 : index; -}; - -export const calculatePushScore = (obj: IPressureSore): number => { - return ( - getAreaScore(obj.length * obj.width) + - getIndexScore(PressureSoreExudateAmountOptions, obj.exudate_amount) + - getIndexScore(PressureSoreTissueTypeOptions, obj.tissue_type) - ); -}; diff --git a/src/components/LogUpdate/Sections/RespiratorySupport/OxygenSupport.tsx b/src/components/LogUpdate/Sections/RespiratorySupport/OxygenSupport.tsx deleted file mode 100644 index dfc9b4eeb89..00000000000 --- a/src/components/LogUpdate/Sections/RespiratorySupport/OxygenSupport.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import RadioFormField from "@/components/Form/FormFields/RadioFormField"; -import RangeFormField from "@/components/Form/FormFields/RangeFormField"; -import { LogUpdateSectionProps } from "@/components/LogUpdate/utils"; - -import { OXYGEN_MODALITY_OPTIONS } from "@/common/constants"; - -import { rangeValueDescription } from "@/Utils/utils"; - -const OxygenRespiratorySupport = ({ log, onChange }: LogUpdateSectionProps) => { - const { t } = useTranslation(); - - const handleChange = (c: any) => { - let resetData = {}; - switch (c.value) { - case "HIGH_FLOW_NASAL_CANNULA": - resetData = { ventilator_oxygen_modality_oxygen_rate: null }; - break; - default: - resetData = { - ventilator_fio2: null, - ventilator_oxygen_modality_flow_rate: null, - }; - } - onChange({ - ventilator_oxygen_modality: - c.value as typeof log.ventilator_oxygen_modality, - ...resetData, - }); - }; - - return ( - <> - Oxygen Modality} - options={OXYGEN_MODALITY_OPTIONS} - optionLabel={(c) => t(`OXYGEN_MODALITY__${c.value}`)} - optionValue={(c) => c.value} - name="ventilator_oxygen_modality" - value={log.ventilator_oxygen_modality} - onChange={handleChange} - layout="vertical" - /> -
- {log.ventilator_oxygen_modality === "HIGH_FLOW_NASAL_CANNULA" ? ( - <> - - onChange({ - ventilator_oxygen_modality_flow_rate: c.value, - }) - } - value={log.ventilator_oxygen_modality_flow_rate} - min={0} - max={70} - valueDescriptions={rangeValueDescription({ low: 34, high: 60 })} - /> -
- - FiO2 - - } - unit="%" - name="ventilator_fio2" - onChange={(c) => onChange({ ventilator_fio2: c.value })} - value={log.ventilator_fio2} - min={21} - max={100} - valueDescriptions={rangeValueDescription({ high: 60 })} - /> - - ) : ( - - onChange({ - ventilator_oxygen_modality_oxygen_rate: c.value, - }) - } - value={log.ventilator_oxygen_modality_oxygen_rate} - min={0} - max={50} - valueDescriptions={rangeValueDescription({ low: 4, high: 10 })} - /> - )} -
- - SpO2 - - } - unit="%" - name="ventilator_spo2" - onChange={(c) => onChange({ ventilator_spo2: c.value })} - value={log.ventilator_spo2} - min={0} - max={100} - valueDescriptions={rangeValueDescription({ low: 89 })} - /> - - ); -}; - -export default OxygenRespiratorySupport; diff --git a/src/components/LogUpdate/Sections/RespiratorySupport/Ventilator.tsx b/src/components/LogUpdate/Sections/RespiratorySupport/Ventilator.tsx deleted file mode 100644 index 96315bdcf69..00000000000 --- a/src/components/LogUpdate/Sections/RespiratorySupport/Ventilator.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import RangeFormField from "@/components/Form/FormFields/RangeFormField"; -import VentilatorModeSelector from "@/components/LogUpdate/Sections/RespiratorySupport/VentilatorModeSelector"; -import { LogUpdateSectionProps } from "@/components/LogUpdate/utils"; -import { DailyRoundsModel } from "@/components/Patient/models"; - -import { ValueDescription, rangeValueDescription } from "@/Utils/utils"; - -export const VentilatorFields = [ - { - key: "ventilator_peep", - label: "PEEP", - unit: "cm/H2O", - min: 0, - max: 30, - step: 0.1, - valueDescription: rangeValueDescription({ low: 10 }), - }, - { - key: "ventilator_pip", - label: "Peak Inspiratory Pressure (PIP)", - unit: "cm H2O", - min: 0, - max: 100, - valueDescription: rangeValueDescription({ low: 11, high: 30 }), - }, - { - key: "ventilator_mean_airway_pressure", - label: "Mean Airway Pressure (MAP)", - unit: "cm H2O", - min: 0, - max: 40, - valueDescription: rangeValueDescription({ low: 11, high: 25 }), - }, - { - key: "ventilator_resp_rate", - label: "Respiratory Rate Ventilator", - unit: "bpm", - min: 0, - max: 100, - valueDescription: rangeValueDescription({ low: 39, high: 60 }), - }, - { - key: "ventilator_pressure_support", - label: "Pressure Support", - unit: "", - min: 0, - max: 40, - valueDescription: rangeValueDescription({ low: 6, high: 15 }), - }, - { - key: "ventilator_tidal_volume", - label: "Tidal Volume", - unit: "ml", - min: 0, - max: 1000, - valueDescription: rangeValueDescription({}), - }, - { - key: "ventilator_fio2", - label: ( - - FiO2 - - ), - unit: "%", - min: 21, - max: 100, - valueDescription: rangeValueDescription({ high: 60 }), - }, - { - key: "ventilator_spo2", - label: ( - - SpO2 - - ), - unit: "%", - min: 0, - max: 100, - valueDescription: rangeValueDescription({ low: 89 }), - }, -] satisfies { - key: keyof DailyRoundsModel; - label: React.ReactNode; - unit: string; - min: number; - max: number; - step?: number; - valueDescription: ValueDescription[]; -}[]; - -const VentilatorRespiratorySupport = ({ - log, - onChange, -}: LogUpdateSectionProps) => { - return ( -
-
-

Ventilator Mode

- onChange({ ventilator_mode })} - /> -
- {VentilatorFields.map((field, index) => ( - onChange({ [field.key]: c.value })} - value={log[field.key] as number} - min={field.min} - max={field.max} - step={field.step || 1} - valueDescriptions={field.valueDescription} - /> - ))} -
- ); -}; - -export default VentilatorRespiratorySupport; diff --git a/src/components/LogUpdate/Sections/RespiratorySupport/VentilatorModeSelector.tsx b/src/components/LogUpdate/Sections/RespiratorySupport/VentilatorModeSelector.tsx deleted file mode 100644 index 6214fa2e0c7..00000000000 --- a/src/components/LogUpdate/Sections/RespiratorySupport/VentilatorModeSelector.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import { RadioInput } from "@/components/Form/FormFields/RadioFormField"; -import { DailyRoundsModel } from "@/components/Patient/models"; - -type Value = DailyRoundsModel["ventilator_mode"]; - -type Props = { - value: Value; - onChange: (value: Value) => void; -}; - -export default function VentilatorModeSelector(props: Props) { - const { t } = useTranslation(); - return ( -
- -
-
- -
-
- -
- ); -} - -const Option = ({ props, value }: { props: Props; value: Value }) => { - const { t } = useTranslation(); - return ( -
- props.onChange(e.target.value as Value)} - label={t(`VENTILATOR_MODE__${value}`)} - /> -
- ); -}; diff --git a/src/components/LogUpdate/Sections/RespiratorySupport/index.tsx b/src/components/LogUpdate/Sections/RespiratorySupport/index.tsx deleted file mode 100644 index fb12b8a1667..00000000000 --- a/src/components/LogUpdate/Sections/RespiratorySupport/index.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { useEffect } from "react"; -import { useTranslation } from "react-i18next"; - -import { AssetClass } from "@/components/Assets/AssetTypes"; -import DialogModal from "@/components/Common/Dialog"; -import Beds from "@/components/Facility/Consultations/Beds"; -import RadioFormField from "@/components/Form/FormFields/RadioFormField"; -import RangeFormField from "@/components/Form/FormFields/RangeFormField"; -import OxygenRespiratorySupport from "@/components/LogUpdate/Sections/RespiratorySupport/OxygenSupport"; -import VentilatorRespiratorySupport from "@/components/LogUpdate/Sections/RespiratorySupport/Ventilator"; -import { - LogUpdateSectionMeta, - LogUpdateSectionProps, -} from "@/components/LogUpdate/utils"; - -import { useSlugs } from "@/hooks/useSlug"; - -import { RESPIRATORY_SUPPORT } from "@/common/constants"; - -import { Warn } from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { rangeValueDescription } from "@/Utils/utils"; - -const RespiratorySupport = ({ log, onChange }: LogUpdateSectionProps) => { - const { t } = useTranslation(); - const [facilityId, consultationId] = useSlugs("facility", "consultation"); - const consultationQuery = useTanStackQueryInstead(routes.getConsultation, { - pathParams: { id: consultationId }, - }); - - const warnForNoLinkedVentilator = (() => { - if (consultationQuery.loading) { - return false; - } - - if ( - !( - log.ventilator_interface === "INVASIVE" || - log.ventilator_interface === "NON_INVASIVE" - ) - ) { - return false; - } - - const hasLinkedVentilator = - consultationQuery.data?.current_bed?.assets_objects?.some( - (a) => a.asset_class === AssetClass.VENTILATOR, - ); - return hasLinkedVentilator === false; - })(); - - useEffect(() => { - if (warnForNoLinkedVentilator) { - Warn({ - msg: "No ventilator assets were found to be linked to the current bed.", - }); - } - }, [warnForNoLinkedVentilator]); - - const handleChange = (c: any) => { - let resetData = {}; - if (["OXYGEN_SUPPORT", "UNKNOWN"].includes(c.value)) { - resetData = { - ...resetData, - ventilator_spo2: undefined, - ventilator_fio2: undefined, - ventilator_peep: undefined, - ventilator_pip: undefined, - ventilator_mean_airway_pressure: undefined, - ventilator_resp_rate: undefined, - ventilator_pressure_support: undefined, - ventilator_tidal_volume: undefined, - ventilator_mode: undefined, - }; - } - if (["INVASIVE", "NON_INVASIVE", "UNKNOWN"].includes(c.value)) { - resetData = { - ...resetData, - ventilator_spo2: undefined, - ventilator_oxygen_modality_flow_rate: undefined, - ventilator_oxygen_modality_oxygen_rate: undefined, - ventilator_oxygen_modality: undefined, - }; - } - onChange({ - ventilator_interface: (c.value || - "UNKNOWN") as typeof log.ventilator_interface, - ...resetData, - }); - }; - - return ( -
- (c ? "Yes" : "No")} - optionValue={(c) => JSON.stringify(c)} - name="bilateral_air_entry" - value={ - log.bilateral_air_entry != null - ? JSON.stringify(log.bilateral_air_entry) - : undefined - } - onChange={(c) => - onChange({ - bilateral_air_entry: c.value == null ? null : JSON.parse(c.value), - }) - } - /> - - EtCO2 - - } - unit="mmHg" - name="etco2" - onChange={(c) => onChange({ etco2: c.value })} - value={log.etco2} - min={0} - max={200} - step={1} - valueDescriptions={rangeValueDescription({ low: 34, high: 45 })} - /> -
- Respiratory Support} - options={RESPIRATORY_SUPPORT} - optionLabel={(c) => t(`RESPIRATORY_SUPPORT__${c.value}`)} - optionValue={(c) => c.value} - name="respiratory_support" - value={log.ventilator_interface} - onChange={handleChange} - /> - - onChange({ ventilator_interface: "UNKNOWN" })} - className="md:max-w-3xl" - > - consultationQuery.refetch()} - /> - - - {log.ventilator_interface && log.ventilator_interface !== "UNKNOWN" && ( -
- {log.ventilator_interface === "OXYGEN_SUPPORT" && ( - - )} - {(log.ventilator_interface === "INVASIVE" || - log.ventilator_interface === "NON_INVASIVE") && ( - - )} -
- )} -
- ); -}; - -RespiratorySupport.meta = { - title: "Respiratory Support", - icon: "l-lungs", -} as const satisfies LogUpdateSectionMeta; - -export default RespiratorySupport; diff --git a/src/components/LogUpdate/Sections/Vitals.tsx b/src/components/LogUpdate/Sections/Vitals.tsx deleted file mode 100644 index dab997bf126..00000000000 --- a/src/components/LogUpdate/Sections/Vitals.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import { meanArterialPressure } from "@/components/Common/BloodPressureFormField"; -import RadioFormField from "@/components/Form/FormFields/RadioFormField"; -import RangeFormField from "@/components/Form/FormFields/RangeFormField"; -import TextAreaFormField from "@/components/Form/FormFields/TextAreaFormField"; -import PainChart from "@/components/LogUpdate/components/PainChart"; -import { - LogUpdateSectionMeta, - LogUpdateSectionProps, -} from "@/components/LogUpdate/utils"; -import { BloodPressure } from "@/components/Patient/models"; - -import { HEARTBEAT_RHYTHM_CHOICES } from "@/common/constants"; - -import { - celsiusToFahrenheit, - fahrenheitToCelsius, - rangeValueDescription, -} from "@/Utils/utils"; - -const Vitals = ({ log, onChange }: LogUpdateSectionProps) => { - const { t } = useTranslation(); - - return ( -
-
-

{t("LOG_UPDATE_FIELD_LABEL__bp")}

- - {t("map_acronym")}: {meanArterialPressure(log.bp)?.toFixed() ?? "--"}{" "} - mmHg - -
- - -
- onChange({ ventilator_spo2: c.value })} - value={log.ventilator_spo2} - min={0} - max={100} - step={1} - unit="%" - valueDescriptions={rangeValueDescription({ low: 89 })} - /> - onChange({ temperature: c.value })} - value={log.temperature} - min={95} - max={106} - step={0.1} - valueDescriptions={rangeValueDescription({ low: 97.4, high: 99.6 })} - units={[ - { label: "°F" }, - { - label: "°C", - conversionFn: fahrenheitToCelsius, - inversionFn: celsiusToFahrenheit, - }, - ]} - /> - onChange({ resp: c.value })} - value={log.resp} - min={0} - max={150} - step={1} - unit="bpm" - valueDescriptions={rangeValueDescription({ low: 11, high: 16 })} - /> -
-
-

{t("pain")}

- - {t("pain_chart_description")} - -
- onChange({ pain_scale_enhanced })} - /> -
- onChange({ pulse: c.value })} - value={log.pulse} - min={0} - max={200} - step={1} - unit="bpm" - valueDescriptions={[ - { - till: 40, - className: "text-red-500", - text: t("bradycardia"), - }, - { - till: 100, - className: "text-green-500", - text: t("normal"), - }, - { - className: "text-red-500", - text: t("tachycardia"), - }, - ]} - /> - t(`HEARTBEAT_RHYTHM__${c}`)} - optionValue={(c) => c} - value={log.rhythm} - onChange={(c) => onChange({ rhythm: c.value ?? undefined })} - /> - onChange({ rhythm_detail: c.value })} - /> -
- ); -}; - -const BPAttributeEditor = ({ - attribute, - log, - onChange, -}: LogUpdateSectionProps & { attribute: "systolic" | "diastolic" }) => { - const { t } = useTranslation(); - - return ( - { - const bp = { systolic: log.bp?.systolic, diastolic: log.bp?.diastolic }; - bp[event.name as keyof BloodPressure] = event.value; - onChange({ - bp: Object.values(bp).filter(Boolean).length ? bp : undefined, - }); - }} - value={log.bp?.[attribute] ?? undefined} - min={0} - max={400} - sliderMin={30} - sliderMax={270} - step={0.1} - unit="mmHg" - valueDescriptions={rangeValueDescription( - attribute === "systolic" - ? { low: 99, high: 139 } - : { low: 49, high: 89 }, - )} - hideUnitInLabel - /> - ); -}; - -Vitals.meta = { - title: "Vitals", - icon: "l-heartbeat", -} as const satisfies LogUpdateSectionMeta; - -export default Vitals; diff --git a/src/components/LogUpdate/Sections/index.tsx b/src/components/LogUpdate/Sections/index.tsx deleted file mode 100644 index 94df5e05640..00000000000 --- a/src/components/LogUpdate/Sections/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from "react"; - -import ABGAnalysis from "@/components/LogUpdate/Sections/ABGAnalysis"; -import BloodSugar from "@/components/LogUpdate/Sections/BloodSugar"; -import Dialysis from "@/components/LogUpdate/Sections/Dialysis"; -import IOBalance from "@/components/LogUpdate/Sections/IOBalance"; -import NeurologicalMonitoring from "@/components/LogUpdate/Sections/NeurologicalMonitoring"; -import NursingCare from "@/components/LogUpdate/Sections/NursingCare"; -import PressureSore from "@/components/LogUpdate/Sections/PressureSore/PressureSore"; -import RespiratorySupport from "@/components/LogUpdate/Sections/RespiratorySupport"; -import Vitals from "@/components/LogUpdate/Sections/Vitals"; -import { - LogUpdateSectionMeta, - LogUpdateSectionProps, -} from "@/components/LogUpdate/utils"; -import { DailyRoundTypes } from "@/components/Patient/models"; - -const LogUpdateSections = { - Vitals, - NeurologicalMonitoring, - RespiratorySupport, - ABGAnalysis, - BloodSugar, - IOBalance, - Dialysis, - PressureSore, - NursingCare, -} as const satisfies Record< - string, - React.FC & { meta: LogUpdateSectionMeta } ->; - -export default LogUpdateSections; - -export const RoundTypeSections = { - NORMAL: [], - AUTOMATED: [], - TELEMEDICINE: [], - VENTILATOR: [ - "Vitals", - "NeurologicalMonitoring", - "RespiratorySupport", - "ABGAnalysis", - "BloodSugar", - "IOBalance", - "Dialysis", - "PressureSore", - "NursingCare", - ], - DOCTORS_LOG: ["NeurologicalMonitoring", "RespiratorySupport"], - COMMUNITY_NURSES_LOG: [], -} as const satisfies Record< - (typeof DailyRoundTypes)[number], - (keyof typeof LogUpdateSections)[] ->; diff --git a/src/components/LogUpdate/components/PainChart.tsx b/src/components/LogUpdate/components/PainChart.tsx deleted file mode 100644 index e5ee128944f..00000000000 --- a/src/components/LogUpdate/components/PainChart.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import { useEffect, useRef, useState } from "react"; - -import PopupModal from "@/CAREUI/display/PopupModal"; -import HumanBodyChart from "@/CAREUI/interactive/HumanChart"; - -import RangeFormField from "@/components/Form/FormFields/RangeFormField"; -import TextAreaFormField from "@/components/Form/FormFields/TextAreaFormField"; -import { IPainScale } from "@/components/Patient/models"; - -import { HumanBodyRegion } from "@/common/constants"; - -import { Error } from "@/Utils/Notifications"; -import { classNames, getValueDescription } from "@/Utils/utils"; - -type Props = { - pain: IPainScale[]; - onChange?: (pain: IPainScale[]) => void; -}; - -export default function PainChart({ pain, onChange }: Props) { - const [current, setCurrent] = useState(); - const containerRef = useRef(null); - - const valueDescription = (region: IPainScale["region"]) => { - const scale = pain.find((obj) => obj.region === region)?.scale; - if (scale != null) { - return getValueDescription(valueDescriptions, scale); - } - }; - - return ( -
- setCurrent(undefined)} - onSave={ - onChange - ? (obj) => { - const mutated = pain.filter((v) => v.region !== obj.region); - mutated.push(obj); - onChange(mutated); - setCurrent(undefined); - } - : undefined - } - /> - - setCurrent( - pain.find((o) => o.region === region) ?? getInitialData(region), - ) - } - regionColor={(r) => valueDescription(r)?.color || "#ECECEC"} - regionLabelClassName={(r) => - classNames( - "border transition-all duration-200 ease-in-out", - valueDescription(r)?.className || - "border-secondary-400 bg-secondary-100", - current?.region === r && - "font-bold shadow-lg ring-2 ring-primary-400", - ) - } - regionText={(region) => - pain.find((p) => p.region === region)?.scale.toString() ?? "" - } - /> -
- ); -} - -type RegionEditorProps = { - show: boolean; - value: IPainScale; - anchorRef: React.RefObject; - onCancel: () => void; - onSave?: (value: IPainScale) => void; -}; - -const RegionEditor = (props: RegionEditorProps) => { - const [value, setValue] = useState(props.value); - useEffect(() => setValue(props.value), [props.value]); - - const update = (diff: Partial) => { - setValue((base) => ({ ...base, ...diff })); - }; - - const valueDescription = getValueDescription(valueDescriptions, value.scale); - - return ( - { - if (value.scale <= 0) { - Error({ msg: "Scale must be greater than 0." }); - } else { - props.onSave?.(value); - } - } - : undefined - } - > -
-

- {value.region.split(/(?=[A-Z])/).join(" ")} -

-
0 ? valueDescription?.color : undefined, - }} - > -
{value.scale}
-
{valueDescription?.text}
-
- {props.onSave && ( - <> - update({ scale: value })} - valueDescriptions={valueDescriptions.map((d) => ({ - ...d, - text: "", - }))} - /> - update({ description: value })} - /> - - )} -
-
- ); -}; - -const valueDescriptions = [ - { - till: 0, - color: "#ECECEC", - text: "No Pain", - className: "bg-secondary-300 border border-secondary-400", - }, - { - till: 3, - color: "#f87171", - text: "Low", - className: "bg-red-400 text-white", - }, - { - till: 7, - color: "#dc2626", - text: "Mild", - className: "bg-red-600 text-white", - }, - { - till: 10, - color: "#991b1b", - text: "High", - className: "bg-red-800 text-white", - }, -]; - -const getInitialData = (region: HumanBodyRegion): IPainScale => ({ - region, - description: "", - scale: 0, -}); diff --git a/src/components/LogUpdate/components/PupilSizeSelect.tsx b/src/components/LogUpdate/components/PupilSizeSelect.tsx deleted file mode 100644 index 24b6ad04a15..00000000000 --- a/src/components/LogUpdate/components/PupilSizeSelect.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import CheckBoxFormField from "@/components/Form/FormFields/CheckBoxFormField"; -import TextAreaFormField from "@/components/Form/FormFields/TextAreaFormField"; -import { LogUpdateSectionProps } from "@/components/LogUpdate/utils"; - -import { classNames, getValueDescription } from "@/Utils/utils"; - -const PupilSizeValueDescriptions = [ - { till: 2, text: "Constricted", color: "red" }, - { till: 6, text: "Normal", color: "green" }, - { till: 8, text: "Dilated", color: "red" }, -]; - -const min = 1; -const max = 8; - -export default function PupilSizeSelect({ - log, - onChange, - side, - readonly, -}: LogUpdateSectionProps & { side: "left" | "right" }) { - const pupilSize = log[`${side}_pupil_size`]; - const detail = log[`${side}_pupil_size_detail`]; - - const valueDescription = - pupilSize != null && min <= pupilSize && pupilSize <= max - ? getValueDescription(PupilSizeValueDescriptions, pupilSize) - : null; - - return ( -
-
-
Size
- - {valueDescription?.text} - -
-
- {Array.from({ length: max - min + 1 }, (_, i) => i + min).map( - (size) => ( - - ), - )} -
- {/* Rejected, but kept for future reference -
-
- Eye -
-
-
-
-
- */} - -
- - onChange({ [`${side}_pupil_size`]: value ? 0 : undefined }) - } - errorClassName="hidden" - /> - {pupilSize === 0 && ( - - onChange({ [`${side}_pupil_size_detail`]: value }) - } - disabled={readonly} - /> - )} -
-
- ); -} diff --git a/src/components/LogUpdate/utils.ts b/src/components/LogUpdate/utils.ts deleted file mode 100644 index 06dbb559ac2..00000000000 --- a/src/components/LogUpdate/utils.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { IconName } from "@/CAREUI/icons/CareIcon"; - -import { DailyRoundsModel } from "@/components/Patient/models"; - -export type LogUpdateSectionProps = { - log: DailyRoundsModel; - onChange: (log: DailyRoundsModel) => void; - readonly?: boolean; -}; - -export type LogUpdateSectionMeta = { - title: string; - description?: string; - icon?: IconName; -}; diff --git a/src/components/Medicine/CreatePrescriptionForm.tsx b/src/components/Medicine/CreatePrescriptionForm.tsx deleted file mode 100644 index 9e38c13b70b..00000000000 --- a/src/components/Medicine/CreatePrescriptionForm.tsx +++ /dev/null @@ -1,269 +0,0 @@ -import dayjs from "dayjs"; -import { useTranslation } from "react-i18next"; - -import { RequiredFieldValidator } from "@/components/Form/FieldValidators"; -import Form from "@/components/Form/Form"; -import CheckBoxFormField from "@/components/Form/FormFields/CheckBoxFormField"; -import DosageFormField from "@/components/Form/FormFields/DosageFormField"; -import { SelectFormField } from "@/components/Form/FormFields/SelectFormField"; -import TextAreaFormField from "@/components/Form/FormFields/TextAreaFormField"; -import TextFormField from "@/components/Form/FormFields/TextFormField"; -import MedibaseAutocompleteFormField from "@/components/Medicine/MedibaseAutocompleteFormField"; -import { - MedicineAdministrationRecord, - Prescription, -} from "@/components/Medicine/models"; -import MedicineRoutes from "@/components/Medicine/routes"; -import { PrescriptionFormValidator } from "@/components/Medicine/validators"; - -import useSlug from "@/hooks/useSlug"; - -import { Success } from "@/Utils/Notifications"; -import useDeprecatedMutation from "@/Utils/request/useMutation"; - -export default function CreatePrescriptionForm(props: { - prescription: Prescription; - onDone: () => void; -}) { - const { t } = useTranslation(); - const encounterId = useSlug("encounter"); - const mutation = useDeprecatedMutation(MedicineRoutes.createPrescription, { - pathParams: { consultation: encounterId }, - }); - - return ( - - disabled={mutation.isProcessing} - defaults={props.prescription} - onCancel={props.onDone} - onSubmit={async (body) => { - body["medicine"] = body.medicine_object?.id; - delete body.medicine_object; - - const { res, error } = await mutation.mutate({ body }); - if (!res?.ok) { - return error; - } - - Success({ msg: t("Medicine prescribed") }); - props.onDone(); - }} - noPadding - validate={PrescriptionFormValidator()} - className="max-w-3xl" - > - {(field) => ( - <> - - {props.prescription.dosage_type !== "PRN" && - props.prescription.prescription_type !== "DISCHARGE" && ( - { - if (e.value) { - field("dosage_type").onChange({ - name: "dosage_type", - value: "TITRATED", - }); - } else { - field("dosage_type").onChange({ - name: "dosage_type", - value: "REGULAR", - }); - } - }} - /> - )} -
- t("PRESCRIPTION_ROUTE_" + key)} - optionValue={(key) => key} - /> - {field("dosage_type").value === "TITRATED" ? ( -
- - -
- ) : ( - - )} -
- - {props.prescription.dosage_type === "PRN" ? ( - <> - - - `${hours} hrs.`} - optionValue={(hours) => hours} - position="above" - /> - - ) : ( -
- - t("PRESCRIPTION_FREQUENCY_" + key.toUpperCase()) - } - optionValue={([key]) => key} - /> - -
- )} - - {field("dosage_type").value === "TITRATED" && ( - - )} - - - - )} - - ); -} - -export const PRESCRIPTION_ROUTES = [ - "ORAL", - "IV", - "IM", - "SC", - "INHALATION", - "NASOGASTRIC", - "INTRATHECAL", - "TRANSDERMAL", - "RECTAL", - "SUBLINGUAL", -] as const; - -export const PRESCRIPTION_FREQUENCIES = { - STAT: { - slots: 1, - completed: (administrations: MedicineAdministrationRecord[]) => - administrations.filter((administration) => administration), - }, - OD: { - slots: 1, - completed: (administrations: MedicineAdministrationRecord[]) => - administrations.filter((administration) => - dayjs(administration.administered_date).isSame(dayjs(), "day"), - ), - }, - HS: { - slots: 1, - completed: (administrations: MedicineAdministrationRecord[]) => - administrations.filter((administration) => - dayjs(administration.administered_date).isSame(dayjs(), "day"), - ), - }, - BD: { - slots: 2, - completed: (administrations: MedicineAdministrationRecord[]) => - administrations.filter((administration) => - dayjs(administration.administered_date).isSame(dayjs(), "day"), - ), - }, - TID: { - slots: 3, - completed: (administrations: MedicineAdministrationRecord[]) => - administrations.filter((administration) => - dayjs(administration.administered_date).isSame(dayjs(), "day"), - ), - }, - QID: { - slots: 4, - completed: (administrations: MedicineAdministrationRecord[]) => - administrations.filter((administration) => - dayjs(administration.administered_date).isSame(dayjs(), "day"), - ), - }, - Q4H: { - slots: 6, - completed: (administrations: MedicineAdministrationRecord[]) => - administrations.filter((administration) => - dayjs(administration.administered_date).isSame(dayjs(), "day"), - ), - }, - QOD: { - slots: 1, - completed: (administrations: MedicineAdministrationRecord[]) => { - const lastAdministration = administrations[0]; - if (!lastAdministration) { - return []; - } - if ( - dayjs(lastAdministration.administered_date).isSame(dayjs(), "day") || - dayjs(lastAdministration.administered_date).isSame( - dayjs().subtract(1, "day"), - "day", - ) - ) { - return [lastAdministration]; - } else { - return [] as MedicineAdministrationRecord[]; - } - }, - }, - QWK: { - slots: 1, - completed: (administrations: MedicineAdministrationRecord[]) => - administrations.filter((administration) => - dayjs(administration.administered_date).isSame(dayjs(), "week"), - ), - }, -}; diff --git a/src/components/Medicine/ManagePrescriptions.tsx b/src/components/Medicine/ManagePrescriptions.tsx deleted file mode 100644 index dfdbbe3e325..00000000000 --- a/src/components/Medicine/ManagePrescriptions.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import ButtonV2 from "@/components/Common/ButtonV2"; -import Page from "@/components/Common/Page"; -import PrescriptionBuilder from "@/components/Medicine/PrescriptionBuilder"; - -import useAppHistory from "@/hooks/useAppHistory"; - -export default function ManagePrescriptions() { - const { t } = useTranslation(); - const { goBack } = useAppHistory(); - - return ( - -
-
-
-
-

- {t("prescription_medications")} -

- - - {t("print")} - -
- -
-
-

- {t("prn_prescriptions")} -

- -
-
-
- goBack()} - data-testid="return-to-patient-dashboard" - > - - {t("return_to_patient_dashboard")} - - - - {t("all_changes_have_been_saved")} - -
-
-
- ); -} diff --git a/src/components/Medicine/MedibaseAutocompleteFormField.tsx b/src/components/Medicine/MedibaseAutocompleteFormField.tsx deleted file mode 100644 index 185b3293b99..00000000000 --- a/src/components/Medicine/MedibaseAutocompleteFormField.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { useState } from "react"; - -import Switch from "@/CAREUI/interactive/Switch"; - -import { Autocomplete } from "@/components/Form/FormFields/Autocomplete"; -import FormField from "@/components/Form/FormFields/FormField"; -import { - FormFieldBaseProps, - useFormFieldPropsResolver, -} from "@/components/Form/FormFields/Utils"; -import { MedibaseMedicine } from "@/components/Medicine/models"; - -import routes from "@/Utils/request/api"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { mergeQueryOptions } from "@/Utils/utils"; - -export default function MedibaseAutocompleteFormField( - props: FormFieldBaseProps, -) { - const field = useFormFieldPropsResolver(props); - - const [query, setQuery] = useState(""); - const [type, setType] = useState(); - - const { data, loading } = useTanStackQueryInstead( - routes.listMedibaseMedicines, - { - query: { query, type }, - }, - ); - - return ( - { - setType(type === "all" ? undefined : type); - }} - /> - ), - }} - > - obj.id, - )} - optionLabel={(option) => option.name.toUpperCase()} - optionDescription={(option) => } - optionValue={(option) => option} - optionIcon={(option) => - option.type === "brand" ? ( - - ) : ( - - ) - } - onQuery={setQuery} - isLoading={loading} - /> - - ); -} - -const OptionDescription = ({ medicine }: { medicine: MedibaseMedicine }) => { - return ( -
- {medicine.atc_classification && ( - - ATC Class: {medicine.atc_classification} - - )} -
- - {medicine.generic && ( - - )} -
-
- ); -}; - -const OptionChip = (props: { name?: string; value: string }) => { - return ( -
- - {props.name && props.name + ":"} - - {props.value} -
- ); -}; diff --git a/src/components/Medicine/PrescriptionBuilder.tsx b/src/components/Medicine/PrescriptionBuilder.tsx deleted file mode 100644 index 4ca87d0e265..00000000000 --- a/src/components/Medicine/PrescriptionBuilder.tsx +++ /dev/null @@ -1,171 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-nocheck - File not in use -// TODO: Replace this with Question Form -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; -import { AuthorizedForConsultationRelatedActions } from "@/CAREUI/misc/AuthorizedChild"; - -import ButtonV2 from "@/components/Common/ButtonV2"; -import DialogModal from "@/components/Common/Dialog"; -import AdministerMedicine from "@/components/Medicine/AdministerMedicine"; -import CreatePrescriptionForm from "@/components/Medicine/CreatePrescriptionForm"; -import DiscontinuePrescription from "@/components/Medicine/DiscontinueMedication"; -import PrescriptionDetailCard from "@/components/Medicine/PrescriptionDetailCard"; -import { - NormalPrescription, - PRNPrescription, - Prescription, -} from "@/components/Medicine/models"; -import MedicineRoutes from "@/components/Medicine/routes"; - -import useSlug from "@/hooks/useSlug"; - -import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { compareBy } from "@/Utils/utils"; - -interface Props { - prescription_type?: Prescription["prescription_type"]; - is_prn?: boolean; - disabled?: boolean; - discontinued?: boolean; - actions?: ("discontinue" | "administer")[]; -} - -export default function PrescriptionBuilder({ - prescription_type, - is_prn = false, - disabled, - discontinued, - actions = ["administer", "discontinue"], -}: Props) { - const { t } = useTranslation(); - const encounterId = useSlug("encounter"); - const [showCreate, setShowCreate] = useState(false); - const [showDiscontinueFor, setShowDiscontinueFor] = useState(); - const [showAdministerFor, setShowAdministerFor] = useState(); - - const { data, refetch } = useTanStackQueryInstead( - MedicineRoutes.listPrescriptions, - { - pathParams: { consultation: encounterId }, - query: { - dosage_type: is_prn ? "PRN" : "REGULAR,TITRATED", - prescription_type, - discontinued, - limit: 100, - }, - }, - ); - - return ( -
- {showDiscontinueFor && ( - { - setShowDiscontinueFor(undefined); - if (success) { - refetch(); - } - }} - key={showDiscontinueFor.id} - /> - )} - {showAdministerFor && ( - { - setShowAdministerFor(undefined); - if (success) { - refetch(); - } - }} - key={showAdministerFor.id} - /> - )} -
- {data?.results - .sort(compareBy("discontinued")) - ?.map((obj) => ( - setShowDiscontinueFor(obj) - : undefined - } - onAdministerClick={ - actions.includes("administer") - ? () => setShowAdministerFor(obj) - : undefined - } - readonly={disabled} - /> - ))} -
- - setShowCreate(true)} - variant="secondary" - className="mt-4 w-full bg-secondary-200 text-secondary-700 hover:bg-secondary-300 hover:text-secondary-900 focus:bg-secondary-100 focus:text-secondary-900" - disabled={disabled} - > -
- - - {t( - is_prn ? "add_prn_prescription" : "add_prescription_medication", - )} - -
-
-
- {showCreate && ( - setShowCreate(false)} - show={showCreate} - title={t( - is_prn ? "add_prn_prescription" : "add_prescription_medication", - )} - description={ -
- - {t("modification_caution_note")} -
- } - className="w-full max-w-4xl lg:min-w-[768px]" - > - { - setShowCreate(false); - refetch(); - }} - /> -
- )} -
- ); -} - -const DefaultPrescription: Partial = { - dosage_type: "REGULAR", - route: "ORAL", -}; -const DefaultPRNPrescription: Partial = { - dosage_type: "PRN", - route: "ORAL", -}; diff --git a/src/components/Medicine/PrintPreview.tsx b/src/components/Medicine/PrintPreview.tsx deleted file mode 100644 index 5c9ef47f3fc..00000000000 --- a/src/components/Medicine/PrintPreview.tsx +++ /dev/null @@ -1,280 +0,0 @@ -import careConfig from "@careConfig"; -import { ReactNode } from "react"; -import { useTranslation } from "react-i18next"; - -import PrintPreview from "@/CAREUI/misc/PrintPreview"; - -import { Prescription } from "@/components/Medicine/models"; -import MedicineRoutes from "@/components/Medicine/routes"; - -import { useSlugs } from "@/hooks/useSlug"; - -import routes from "@/Utils/request/api"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { - classNames, - formatDate, - formatDateTime, - formatName, - patientAgeInYears, -} from "@/Utils/utils"; - -export default function PrescriptionsPrintPreview() { - const { t } = useTranslation(); - const [patientId, consultationId] = useSlugs("patient", "consultation"); - - const patientQuery = useTanStackQueryInstead(routes.getPatient, { - pathParams: { id: patientId }, - }); - - const encounterQuery = useTanStackQueryInstead(routes.getConsultation, { - pathParams: { id: consultationId }, - }); - - const prescriptionsQuery = useTanStackQueryInstead( - MedicineRoutes.listPrescriptions, - { - pathParams: { consultation: consultationId }, - query: { discontinued: false, limit: 100 }, - }, - ); - - const patient = patientQuery.data; - const encounter = encounterQuery.data; - - const items = prescriptionsQuery.data?.results; - const normalPrescriptions = items?.filter((p) => p.dosage_type !== "PRN"); - const prnPrescriptions = items?.filter((p) => p.dosage_type === "PRN"); - - return ( - -
-

{encounter?.facility_name}

- care logo -
-
- - {patient && ( - <> - {patient.name} -{" "} - {t(`GENDER__${patient.gender}`)},{" "} - {patientAgeInYears(patient).toString()}yrs - - )} - - - {encounter?.patient_no} - - - - {formatDate(encounter?.encounter_date)} - - - {encounter?.current_bed?.bed_object.location_object?.name} - {" - "} - {encounter?.current_bed?.bed_object.name} - - - - {patient?.allergies ?? "None"} - -
- - - - -
-

- Sign of the Consulting Doctor -

- - {encounter?.treating_physician_object && - formatName(encounter?.treating_physician_object)} - -

- Generated on: {formatDateTime(new Date())} -

-

- This is a computer generated prescription. It shall be issued to the - patient only after the concerned doctor has verified the content and - authorized the same by affixing signature. -

-
-
- ); -} - -const PatientDetail = ({ - name, - children, - className, -}: { - name: string; - children?: ReactNode; - className?: string; -}) => { - return ( -
-
{name}:
- {children != null ? ( - {children} - ) : ( -
- )} -
- ); -}; - -const PrescriptionsTable = ({ - items, - prn, -}: { - items?: Prescription[]; - prn?: boolean; -}) => { - if (!items) { - return ( -
- ); - } - - if (!items.length) { - return; - } - - return ( - - - - - - - - - - - - {items.map((item) => ( - - ))} - -
- {prn && "PRN"} Prescriptions -
MedicineDosageDirectionsNotes / Instructions
- ); -}; - -const PrescriptionEntry = ({ obj }: { obj: Prescription }) => { - const { t } = useTranslation(); - const medicine = obj.medicine_object; - - return ( -
-

- - {medicine?.name ?? obj.medicine_old} - {" "} -

- {medicine?.type === "brand" && ( - -

- Generic:{" "} - - {medicine.generic ?? "--"} - -

-

- Brand:{" "} - - {medicine.company ?? "--"} - -

-
- )} -
- {obj.dosage_type === "TITRATED" &&

Titrated

} -

- {obj.base_dosage}{" "} - {obj.target_dosage != null && `→ ${obj.target_dosage}`}{" "} -

- {obj.max_dosage && ( -

- Max. {obj.max_dosage} in - 24hrs -

- )} - {obj.min_hours_between_doses && ( -

- Min.{" "} - - {obj.min_hours_between_doses}hrs - {" "} - b/w doses -

- )} -
- {obj.route && ( -

- Route: - - {t(`PRESCRIPTION_ROUTE_${obj.route}`)} - -

- )} - {obj.frequency && ( -

- Freq: - - {t(`PRESCRIPTION_FREQUENCY_${obj.frequency}`)} - -

- )} - {obj.days && ( -

- Days: - {obj.days} day(s) -

- )} - {obj.indicator && ( -

- Indicator: - {obj.indicator} -

- )} -
- {obj.notes} - {obj.instruction_on_titration && ( -

- Titration instructions:{" "} - {obj.instruction_on_titration} -

- )} -
- - - Diagnosis - Clinical Status - Verification - Onset Date - Note - - - - - {diagnoses.map((diagnosis, index) => ( - - - - - - - - 1}> - - diagnosisSearch.refetch({ body: { search } }) - } - /> - - - {diagnosisSearch.loading - ? "Loading..." - : "No diagnoses found"} - - - {diagnosisSearch.data?.results.map( - (option) => - option.code && ( - { - updateDiagnosis(index, { - code: { - code: option.code, - display: option.display || "", - system: option.system || "", - }, - }); - }} - > - {option.display} - - ), - )} - - - - - - - - - - - - - - - updateDiagnosis(index, { - onset: { - onset_datetime: e.target.value, - }, - }) - } - disabled={disabled} - /> - - - - updateDiagnosis(index, { note: e.target.value }) - } - disabled={disabled} - /> - - - - - - ))} - -
+ {diagnoses.length > 0 && ( +
+
+
Diagnosis
+
Onset Date
+
Status
+
Verification
+
Action
+
+
+ {diagnoses.map((diagnosis, index) => ( + handleUpdateDiagnosis(index, updates)} + onRemove={() => handleRemoveDiagnosis(index)} + /> + ))} +
- -
+ )} +
); } + +interface DiagnosisItemProps { + diagnosis: Diagnosis; + disabled?: boolean; + onUpdate?: (diagnosis: Partial) => void; + onRemove?: () => void; +} + +const DiagnosisItem: React.FC = ({ + diagnosis, + disabled, + onUpdate, + onRemove, +}) => { + const [showNotes, setShowNotes] = useState(false); + + return ( +
+
+
+
+ {diagnosis.code.display} +
+
+ + + + + + setShowNotes(!showNotes)}> + + {showNotes ? "Hide Notes" : "Add Notes"} + + + + + Remove Diagnosis + + + +
+
+
+
+ + + onUpdate?.({ + onset: { onset_datetime: e.target.value }, + }) + } + disabled={disabled} + className="h-8 md:h-9" + /> +
+
+ + +
+
+ + +
+
+
+ + + + + + setShowNotes(!showNotes)}> + + {showNotes ? "Hide Notes" : "Add Notes"} + + + + + Remove Diagnosis + + + +
+
+ {showNotes && ( +
+ onUpdate?.({ note: e.target.value })} + disabled={disabled} + /> +
+ )} +
+ ); +}; diff --git a/src/components/Questionnaire/QuestionTypes/SymptomQuestion.tsx b/src/components/Questionnaire/QuestionTypes/SymptomQuestion.tsx index ad697aebf3f..0d3a6db503f 100644 --- a/src/components/Questionnaire/QuestionTypes/SymptomQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/SymptomQuestion.tsx @@ -1,21 +1,22 @@ -import { PlusIcon, TrashIcon } from "@radix-ui/react-icons"; -import { useState } from "react"; +"use client"; + +import { + DotsVerticalIcon, + MinusCircledIcon, + Pencil2Icon, +} from "@radix-ui/react-icons"; +import React, { useState } from "react"; import { Button } from "@/components/ui/button"; import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from "@/components/ui/command"; + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; import { Select, SelectContent, @@ -23,92 +24,69 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; -import routes from "@/Utils/request/api"; -import useQuery from "@/Utils/request/useQuery"; -import type { QuestionnaireResponse } from "@/types/questionnaire/form"; +import ValueSetSelect from "@/components/Questionnaire/ValueSetSelect"; + +import { Code } from "@/types/questionnaire/code"; +import { QuestionnaireResponse } from "@/types/questionnaire/form"; +import { Question } from "@/types/questionnaire/question"; import { SYMPTOM_CLINICAL_STATUS, SYMPTOM_SEVERITY, - SYMPTOM_VERIFICATION_STATUS, - type Symptom, + Symptom, + SymptomRequest, } from "@/types/questionnaire/symptom"; interface SymptomQuestionProps { - question: any; + question: Question; questionnaireResponse: QuestionnaireResponse; updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; disabled?: boolean; } +const SYMPTOM_INITIAL_VALUE: Omit = { + code: { code: "", display: "", system: "" }, + clinical_status: "active", + verification_status: "confirmed", + severity: "moderate", + onset: { onset_datetime: new Date().toISOString().split("T")[0] }, +}; + export function SymptomQuestion({ question, questionnaireResponse, updateQuestionnaireResponseCB, disabled, }: SymptomQuestionProps) { - const [symptoms, setSymptoms] = useState(() => { - return (questionnaireResponse.values?.[0]?.value as Symptom[]) || []; - }); + const symptoms = + (questionnaireResponse.values?.[0]?.value as Symptom[]) || []; - const symptomSearch = useQuery(routes.valueset.expand, { - pathParams: { system: "system-condition-code" }, - body: { count: 10 }, - prefetch: false, - }); - - const handleAddSymptom = () => { + const handleAddSymptom = (code: Code) => { const newSymptoms = [ ...symptoms, - { code: { code: "", display: "", system: "" } } as Symptom, - ]; - setSymptoms(newSymptoms); + { ...SYMPTOM_INITIAL_VALUE, code }, + ] as Symptom[]; updateQuestionnaireResponseCB({ ...questionnaireResponse, - values: [ - { - type: "symptom", - value: newSymptoms, - }, - ], + values: [{ type: "symptom", value: newSymptoms }], }); }; const handleRemoveSymptom = (index: number) => { const newSymptoms = symptoms.filter((_, i) => i !== index); - setSymptoms(newSymptoms); updateQuestionnaireResponseCB({ ...questionnaireResponse, - values: [ - { - type: "symptom", - value: newSymptoms, - }, - ], + values: [{ type: "symptom", value: newSymptoms }], }); }; - const updateSymptom = (index: number, updates: Partial) => { + const handleUpdateSymptom = (index: number, updates: Partial) => { const newSymptoms = symptoms.map((symptom, i) => i === index ? { ...symptom, ...updates } : symptom, ); - setSymptoms(newSymptoms); updateQuestionnaireResponseCB({ ...questionnaireResponse, - values: [ - { - type: "symptom", - value: newSymptoms, - }, - ], + values: [{ type: "symptom", value: newSymptoms }], }); }; @@ -118,205 +96,200 @@ export function SymptomQuestion({ {question.text} {question.required && *} -
-
- - - - Symptom - Clinical Status - Verification - Severity - Onset Date - Note - - - - - {symptoms.map((symptom, index) => ( - - - - - - - - 1}> - - symptomSearch.refetch({ body: { search } }) - } - /> - - - {symptomSearch.loading - ? "Loading..." - : "No symptoms found"} - - - {symptomSearch.data?.results.map( - (option) => - option.code && ( - { - updateSymptom(index, { - code: { - code: option.code, - display: option.display || "", - system: option.system || "", - }, - }); - }} - > - {option.display} - - ), - )} - - - - - - - - - - - - - - - - - - updateSymptom(index, { - onset: { - onset_datetime: e.target.value, - }, - }) - } - disabled={disabled} - /> - - - - updateSymptom(index, { note: e.target.value }) - } - disabled={disabled} - /> - - - - - - ))} - -
+ {symptoms.length > 0 && ( +
+
+
Symptom
+
Date
+
Status
+
Severity
+
Action
+
+
+ {symptoms.map((symptom, index) => ( + handleUpdateSymptom(index, updates)} + onRemove={() => handleRemoveSymptom(index)} + /> + ))} +
- -
+ )} +
); } + +interface SymptomItemProps { + symptom: Symptom; + disabled?: boolean; + onUpdate?: (symptom: Partial) => void; + onRemove?: () => void; +} + +const SymptomItem: React.FC = ({ + symptom, + disabled, + onUpdate, + onRemove, +}) => { + const [showNotes, setShowNotes] = useState(false); + + return ( +
+
+
+
+ {symptom.code.display} +
+
+ + + + + + setShowNotes(!showNotes)}> + + {showNotes ? "Hide Notes" : "Add Notes"} + + + + + Remove Symptom + + + +
+
+
+
+ + + onUpdate?.({ + onset: { onset_datetime: e.target.value }, + }) + } + disabled={disabled} + className="h-8 md:h-9" + /> +
+
+ + +
+
+ + +
+
+
+ + + + + + setShowNotes(!showNotes)}> + + {showNotes ? "Hide Notes" : "Add Notes"} + + + + + Remove Symptom + + + +
+
+ {showNotes && ( +
+ onUpdate?.({ note: e.target.value })} + disabled={disabled} + /> +
+ )} +
+ ); +}; diff --git a/src/components/Questionnaire/QuestionnaireForm.tsx b/src/components/Questionnaire/QuestionnaireForm.tsx index 21a363ae026..bb11c82ee77 100644 --- a/src/components/Questionnaire/QuestionnaireForm.tsx +++ b/src/components/Questionnaire/QuestionnaireForm.tsx @@ -297,140 +297,139 @@ export function QuestionnaireForm({
{/* Main Content */} -
-
- {/* Questionnaire Forms */} - {questionnaireForms.map((form, index) => ( -
-
-
-

- {form.questionnaire.title} -

- {form.questionnaire.description && ( -

- {form.questionnaire.description} -

- )} -
- - {form.questionnaire.id !== questionnaireData?.id && ( - +
+ {/* Questionnaire Forms */} + {questionnaireForms.map((form, index) => ( +
+
+
+

+ {form.questionnaire.title} +

+ {form.questionnaire.description && ( +

+ {form.questionnaire.description} +

)}
- { - setQuestionnaireForms((existingForms) => - existingForms.map((formItem) => - formItem.questionnaire.id === form.questionnaire.id - ? { ...formItem, responses } - : formItem, - ), - ); - }} - disabled={isProcessing} - activeGroupId={activeGroupId} - errors={form.errors} - clearError={(questionId: string) => { - setQuestionnaireForms((prev) => - prev.map((f) => - f.questionnaire.id === form.questionnaire.id - ? { - ...f, - errors: f.errors.filter( - (e) => e.question_id !== questionId, - ), - } - : f, - ), - ); - }} - /> + {form.questionnaire.id !== questionnaireData?.id && ( + + )}
- ))} - - {/* Search and Add Questionnaire */} - -
- { - if ( - questionnaireForms.some( - (form) => form.questionnaire.id === selected.id, - ) - ) { - return; - } - - setQuestionnaireForms((prev) => [ - ...prev, - { - questionnaire: selected, - responses: initializeResponses(selected.questions), - errors: [], - }, - ]); + + { + setQuestionnaireForms((existingForms) => + existingForms.map((formItem) => + formItem.questionnaire.id === form.questionnaire.id + ? { ...formItem, responses } + : formItem, + ), + ); }} disabled={isProcessing} + activeGroupId={activeGroupId} + errors={form.errors} + clearError={(questionId: string) => { + setQuestionnaireForms((prev) => + prev.map((f) => + f.questionnaire.id === form.questionnaire.id + ? { + ...f, + errors: f.errors.filter( + (e) => e.question_id !== questionId, + ), + } + : f, + ), + ); + }} />
+ ))} - {/* Submit and Cancel Buttons */} - {questionnaireForms.length > 0 && ( -
- - -
- )} + {/* Search and Add Questionnaire */} + +
+ { + if ( + questionnaireForms.some( + (form) => form.questionnaire.id === selected.id, + ) + ) { + return; + } + + setQuestionnaireForms((prev) => [ + ...prev, + { + questionnaire: selected, + responses: initializeResponses(selected.questions), + errors: [], + }, + ]); + }} + disabled={isProcessing} + />
+ + {/* Submit and Cancel Buttons */} + {questionnaireForms.length > 0 && ( +
+ + +
+ )} + {/* Add a Preview of the QuestionnaireForm */} {import.meta.env.DEV && (
From 66336df5e9c4c10da0b00cfa70a1ec1684d63a42 Mon Sep 17 00:00:00 2001 From: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:26:50 +0530 Subject: [PATCH 365/708] Update README with new creds (#9653) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2504bf47fb7..a5072dff755 100644 --- a/README.md +++ b/README.md @@ -58,9 +58,9 @@ Once the development server has started, open [localhost:4000](http://localhost: Authenticate to staging API with any of the following credentials ```yaml -- username: dev-districtadmin +- username: devdistrictadmin password: Coronasafe@123 - role: District Admin + role: Administrator - username: staffdev password: Coronasafe@123 From 1251c5f862acfdf4cd1a430b082747529ec7e720 Mon Sep 17 00:00:00 2001 From: Aditya Jindal Date: Thu, 2 Jan 2025 18:38:52 +0530 Subject: [PATCH 366/708] =?UTF-8?q?Fix:=20Pressing=20=E2=80=9CEnter?= =?UTF-8?q?=E2=80=9D=20After=20Entering=20Data=20in=20Patient=20Registrati?= =?UTF-8?q?on=20(#9655)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Patient/PatientRegistration.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/Patient/PatientRegistration.tsx b/src/components/Patient/PatientRegistration.tsx index 8960b2e3b70..3ec12d7ce11 100644 --- a/src/components/Patient/PatientRegistration.tsx +++ b/src/components/Patient/PatientRegistration.tsx @@ -758,7 +758,11 @@ export default function PatientRegistration(
*/}
- + )} + + + + Manage Organizations + + Add or remove organizations from this questionnaire + + + +
+ {/* Selected Organizations */} +
+

Selected Organizations

+
+ {selectedOrganizations?.map((org) => ( + + {org.name} + + + ))} + {!isLoading && + (!selectedOrganizations || + selectedOrganizations.length === 0) && ( +

+ No organizations selected +

+ )} +
+
+ + {/* Organization Selector */} +
+

Add Organizations

+ + + + No organizations found. + + {isLoadingOrganizations ? ( +
+ +
+ ) : ( + availableOrganizations?.results.map((org) => ( + handleToggleOrganization(org.id)} + > +
+ + {org.name} + {org.description && ( + + - {org.description} + + )} +
+ {selectedIds.includes(org.id) && ( + + )} +
+ )) + )} +
+
+
+
+
+ + +
+ + +
+
+
+ + ); +} diff --git a/src/components/Questionnaire/QuestionnaireForm.tsx b/src/components/Questionnaire/QuestionnaireForm.tsx index bb11c82ee77..278d12dcdf6 100644 --- a/src/components/Questionnaire/QuestionnaireForm.tsx +++ b/src/components/Questionnaire/QuestionnaireForm.tsx @@ -21,6 +21,7 @@ import { import type { QuestionnaireResponse } from "@/types/questionnaire/form"; import type { Question } from "@/types/questionnaire/question"; import { QuestionnaireDetail } from "@/types/questionnaire/questionnaire"; +import questionnaireApi from "@/types/questionnaire/questionnaireApi"; import { QuestionRenderer } from "./QuestionRenderer"; import { QuestionnaireSearch } from "./QuestionnaireSearch"; @@ -70,7 +71,7 @@ export function QuestionnaireForm({ data: questionnaireData, loading: isQuestionnaireLoading, error: questionnaireError, - } = useQuery(routes.questionnaire.detail, { + } = useQuery(questionnaireApi.detail, { pathParams: { id: questionnaireSlug ?? "" }, prefetch: !!questionnaireSlug && !FIXED_QUESTIONNAIRES[questionnaireSlug], }); diff --git a/src/components/Questionnaire/QuestionnaireSearch.tsx b/src/components/Questionnaire/QuestionnaireSearch.tsx index 23dd5965f46..fd92931c7b8 100644 --- a/src/components/Questionnaire/QuestionnaireSearch.tsx +++ b/src/components/Questionnaire/QuestionnaireSearch.tsx @@ -11,10 +11,10 @@ import { } from "@/components/ui/popover"; import { Skeleton } from "@/components/ui/skeleton"; -import routes from "@/Utils/request/api"; import query from "@/Utils/request/query"; import { conditionalAttribute } from "@/Utils/utils"; import type { QuestionnaireDetail } from "@/types/questionnaire/questionnaire"; +import questionnaireApi from "@/types/questionnaire/questionnaireApi"; interface QuestionnaireSearchProps { onSelect: (questionnaire: QuestionnaireDetail) => void; @@ -38,7 +38,7 @@ export function QuestionnaireSearch({ const { data: questionnaires, isLoading } = useQuery({ queryKey: ["questionnaires", "list", search, subjectType], - queryFn: query(routes.questionnaire.list, { + queryFn: query(questionnaireApi.list, { queryParams: { title: search, ...conditionalAttribute(!!subjectType, { diff --git a/src/components/Questionnaire/index.tsx b/src/components/Questionnaire/index.tsx index 288bd644623..3e3386f8cc7 100644 --- a/src/components/Questionnaire/index.tsx +++ b/src/components/Questionnaire/index.tsx @@ -3,15 +3,15 @@ import { useNavigate } from "raviger"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import routes from "@/Utils/request/api"; import useQuery from "@/Utils/request/useQuery"; import { QuestionnaireDetail } from "@/types/questionnaire/questionnaire"; +import questionnaireApi from "@/types/questionnaire/questionnaireApi"; import Loading from "../Common/Loading"; export function QuestionnaireList() { const navigate = useNavigate(); - const { data: response, loading } = useQuery(routes.questionnaire.list); + const { data: response, loading } = useQuery(questionnaireApi.list); if (loading) { return ; diff --git a/src/components/Questionnaire/show.tsx b/src/components/Questionnaire/show.tsx index 1b266c51058..a1f70a6128f 100644 --- a/src/components/Questionnaire/show.tsx +++ b/src/components/Questionnaire/show.tsx @@ -9,11 +9,12 @@ import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import routes from "@/Utils/request/api"; import useQuery from "@/Utils/request/useQuery"; import type { Question } from "@/types/questionnaire/question"; +import questionnaireApi from "@/types/questionnaire/questionnaireApi"; import Loading from "../Common/Loading"; +import ManageQuestionnaireOrganizationsSheet from "./ManageQuestionnaireOrganizationsSheet"; import { QuestionnaireForm } from "./QuestionnaireForm"; interface QuestionnaireShowProps { @@ -66,7 +67,7 @@ export function QuestionnaireShow({ id }: QuestionnaireShowProps) { data: questionnaire, loading, error, - } = useQuery(routes.questionnaire.detail, { + } = useQuery(questionnaireApi.detail, { pathParams: { id }, }); @@ -106,6 +107,7 @@ export function QuestionnaireShow({ id }: QuestionnaireShowProps) {

{questionnaire.description}

+ -
+ )} +
); } +interface AllergyItemProps { + allergy: AllergyIntolerance; + disabled?: boolean; + onUpdate?: (allergy: Partial) => void; + onRemove?: () => void; +} +const AllergyTableRow = ({ + allergy, + disabled, + onUpdate, + onRemove, +}: AllergyItemProps) => { + const [showNotes, setShowNotes] = useState(false); + + return ( + <> + + + + + + {allergy.code.display} + + + + + + + + + + + + onUpdate?.({ last_occurrence: e.target.value })} + disabled={disabled} + className="h-7 text-sm w-[100px] px-1" + /> + + + + + + + + setShowNotes(!showNotes)}> + + {showNotes ? "Hide Notes" : "Add Notes"} + + + + + Remove Allergy + + + + + + {showNotes && ( + + + + onUpdate?.({ note: e.target.value })} + disabled={disabled} + className="mt-0.5" + /> + + + )} + + ); +}; diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index 22c8252891b..a8f245154c7 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -13,6 +13,11 @@ const Input = React.forwardRef>( )} ref={ref} {...props} + onFocus={(e) => { + if (type === "date" || type === "time") { + e.target.showPicker(); + } + }} /> ); }, From 2dfac5cf5c6558f020eeb6de6268bbd974ecd120 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Thu, 2 Jan 2025 14:04:01 +0000 Subject: [PATCH 369/708] Hide sidebar for session expired (#9642) --- src/Routers/AppRouter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx index 9387f7d3da9..528f090dd6c 100644 --- a/src/Routers/AppRouter.tsx +++ b/src/Routers/AppRouter.tsx @@ -27,7 +27,7 @@ import OrganizationRoutes from "./routes/OrganizationRoutes"; import QuestionnaireRoutes from "./routes/questionnaireRoutes"; // List of paths where the sidebar should be hidden -const PATHS_WITHOUT_SIDEBAR = ["/"]; +const PATHS_WITHOUT_SIDEBAR = ["/", "/session-expired"]; export type RouteParams = T extends `${string}:${infer Param}/${infer Rest}` From b57e25c22404c2d9c635fed2e07bb488afcca80f Mon Sep 17 00:00:00 2001 From: Jacob John Jeevan <40040905+Jacobjeevan@users.noreply.github.com> Date: Thu, 2 Jan 2025 19:45:06 +0530 Subject: [PATCH 370/708] Minor enhancements (#9631) --- src/components/Common/UserSelector.tsx | 12 +++++++++--- src/pages/Encounters/EncounterList.tsx | 19 ++++++++++++++++--- .../Encounters/tabs/EncounterFilesTab.tsx | 6 +++--- .../components/LinkFacilityUserSheet.tsx | 11 +++++++++-- .../Organization/components/LinkUserSheet.tsx | 1 + 5 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/components/Common/UserSelector.tsx b/src/components/Common/UserSelector.tsx index 986dfb5091d..b28fbeb48c9 100644 --- a/src/components/Common/UserSelector.tsx +++ b/src/components/Common/UserSelector.tsx @@ -33,6 +33,7 @@ interface Props { onChange: (user: UserBase) => void; placeholder?: string; noOptionsMessage?: string; + popoverClassName?: string; } export default function UserSelector({ @@ -40,6 +41,7 @@ export default function UserSelector({ onChange, placeholder, noOptionsMessage, + popoverClassName, }: Props) { const { t } = useTranslation(); const [open, setOpen] = useState(false); @@ -55,8 +57,8 @@ export default function UserSelector({ const users = data?.results || []; return ( - - + + - + 1}> ({ + queryKey: ["encounter", encounter_id], + queryFn: query(routes.encounter.get, { + pathParams: { id: encounter_id }, + queryParams: { + facility: facilityId, + }, + }), + enabled: !!encounter_id, }); const searchOptions = [ @@ -220,7 +230,10 @@ export function EncounterList({ }, ]; - const encounters = propEncounters || queryEncounters?.results || []; + const encounters = + propEncounters || + queryEncounters?.results || + (queryEncounter ? [queryEncounter] : []); const { t } = useTranslation(); diff --git a/src/pages/Encounters/tabs/EncounterFilesTab.tsx b/src/pages/Encounters/tabs/EncounterFilesTab.tsx index 9b89fe926e4..053637b75d8 100644 --- a/src/pages/Encounters/tabs/EncounterFilesTab.tsx +++ b/src/pages/Encounters/tabs/EncounterFilesTab.tsx @@ -28,7 +28,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Tabs, TabsContent } from "@/components/ui/tabs"; import { Tooltip, TooltipContent, @@ -490,7 +490,7 @@ export const EncounterFilesTab = (props: EncounterTabProps) => { >
- + {/* {fileCategories.map((category) => ( { {category.label} ))} - + */}
diff --git a/src/pages/FacilityOrganization/components/LinkFacilityUserSheet.tsx b/src/pages/FacilityOrganization/components/LinkFacilityUserSheet.tsx index 0c1f999d251..c0cb464b2b1 100644 --- a/src/pages/FacilityOrganization/components/LinkFacilityUserSheet.tsx +++ b/src/pages/FacilityOrganization/components/LinkFacilityUserSheet.tsx @@ -118,7 +118,7 @@ export default function LinkFacilityUserSheet({ Link User - + Link User to Facility @@ -131,6 +131,7 @@ export default function LinkFacilityUserSheet({ onChange={handleUserChange} placeholder="Search for a user" noOptionsMessage="No users found" + popoverClassName="w-full" /> {selectedUser && (
@@ -173,7 +174,13 @@ export default function LinkFacilityUserSheet({ - + {roles?.results?.map((role) => (
diff --git a/src/pages/Organization/components/LinkUserSheet.tsx b/src/pages/Organization/components/LinkUserSheet.tsx index ef31f2d475f..ea60a7bfb32 100644 --- a/src/pages/Organization/components/LinkUserSheet.tsx +++ b/src/pages/Organization/components/LinkUserSheet.tsx @@ -130,6 +130,7 @@ export default function LinkUserSheet({ onChange={handleUserChange} placeholder="Search for a user" noOptionsMessage="No users found" + popoverClassName="w-full" /> {selectedUser && (
From b58db6a3f96decf82800f991c6f64d207fb9eb3d Mon Sep 17 00:00:00 2001 From: Jacob John Jeevan <40040905+Jacobjeevan@users.noreply.github.com> Date: Thu, 2 Jan 2025 19:45:26 +0530 Subject: [PATCH 371/708] Switching to schedulable users and user base (#9626) --- src/Routers/SessionRouter.tsx | 48 ++++++----------- src/Utils/request/api.tsx | 2 +- src/pages/Appoinments/PatientRegistration.tsx | 8 +-- src/pages/Appoinments/PatientSelect.tsx | 16 +++--- src/pages/Appoinments/Schedule.tsx | 51 +++++++++---------- src/pages/Appoinments/auth/PatientLogin.tsx | 14 ++--- src/pages/Facility/components/UserCard.tsx | 6 +-- 7 files changed, 62 insertions(+), 83 deletions(-) diff --git a/src/Routers/SessionRouter.tsx b/src/Routers/SessionRouter.tsx index 86b4770288f..56c36536b8d 100644 --- a/src/Routers/SessionRouter.tsx +++ b/src/Routers/SessionRouter.tsx @@ -20,52 +20,36 @@ export const routes = { "/": () => , "/facilities": () => , "/facility/:id": ({ id }: { id: string }) => , - "/facility/:facilityId/appointments/:staffUsername/otp/:page": ({ + "/facility/:facilityId/appointments/:staffId/otp/:page": ({ facilityId, - staffUsername, + staffId, page, }: { facilityId: string; - staffUsername: string; + staffId: string; page: string; - }) => ( - - ), - "/facility/:facilityId/appointments/:staffExternalId/book-appointment": ({ + }) => , + "/facility/:facilityId/appointments/:staffId/book-appointment": ({ facilityId, - staffExternalId, + staffId, }: { facilityId: string; - staffExternalId: string; - }) => ( - - ), - "/facility/:facilityId/appointments/:staffUsername/patient-select": ({ + staffId: string; + }) => , + "/facility/:facilityId/appointments/:staffId/patient-select": ({ facilityId, - staffUsername, + staffId, }: { facilityId: string; - staffUsername: string; - }) => , - "/facility/:facilityId/appointments/:staffUsername/patient-registration": ({ + staffId: string; + }) => , + "/facility/:facilityId/appointments/:staffId/patient-registration": ({ facilityId, - staffUsername, + staffId, }: { facilityId: string; - staffUsername: string; - }) => ( - - ), + staffId: string; + }) => , "/login": () => , "/forgot-password": () => , "/password_reset/:token": ({ token }: { token: string }) => ( diff --git a/src/Utils/request/api.tsx b/src/Utils/request/api.tsx index c1ee0c6c5a7..23e4897fd0e 100644 --- a/src/Utils/request/api.tsx +++ b/src/Utils/request/api.tsx @@ -366,7 +366,7 @@ const routes = { getScheduleAbleFacilityUser: { path: "/api/v1/facility/{facility_id}/schedulable_users/{user_id}/", - TRes: Type(), + TRes: Type(), }, getScheduleAbleFacilityUsers: { diff --git a/src/pages/Appoinments/PatientRegistration.tsx b/src/pages/Appoinments/PatientRegistration.tsx index c026bb124ef..512ae8ca42d 100644 --- a/src/pages/Appoinments/PatientRegistration.tsx +++ b/src/pages/Appoinments/PatientRegistration.tsx @@ -62,11 +62,11 @@ const initialForm: AppointmentPatientRegister & { type PatientRegistrationProps = { facilityId: string; - staffUsername: string; + staffId: string; }; export function PatientRegistration(props: PatientRegistrationProps) { - const { staffUsername } = props; + const { staffId } = props; const selectedSlot = JSON.parse( localStorage.getItem("selectedSlot") ?? "", ) as SlotAvailability; @@ -230,7 +230,7 @@ export function PatientRegistration(props: PatientRegistrationProps) { type="button" onClick={() => navigate( - `/facility/${props.facilityId}/appointments/${staffUsername}/patient-select`, + `/facility/${props.facilityId}/appointments/${staffId}/patient-select`, ) } > @@ -436,7 +436,7 @@ export function PatientRegistration(props: PatientRegistrationProps) { type="button" onClick={() => navigate( - `/facility/${props.facilityId}/appointments/${staffUsername}/patient-select`, + `/facility/${props.facilityId}/appointments/${staffId}/patient-select`, ) } > diff --git a/src/pages/Appoinments/PatientSelect.tsx b/src/pages/Appoinments/PatientSelect.tsx index 8506130ea67..de239cdda2a 100644 --- a/src/pages/Appoinments/PatientSelect.tsx +++ b/src/pages/Appoinments/PatientSelect.tsx @@ -27,10 +27,10 @@ import { TokenData } from "@/types/auth/otpToken"; export default function PatientSelect({ facilityId, - staffUsername, + staffId, }: { facilityId: string; - staffUsername: string; + staffId: string; }) { const { t } = useTranslation(); const selectedSlot = JSON.parse( @@ -44,16 +44,16 @@ export default function PatientSelect({ const queryClient = useQueryClient(); - if (!staffUsername) { - Notification.Error({ msg: "Staff Username Not Found" }); + if (!staffId) { + Notification.Error({ msg: "Staff Not Found" }); navigate(`/facility/${facilityId}/`); } else if (!tokenData) { Notification.Error({ msg: "Phone Number Not Found" }); - navigate(`/facility/${facilityId}/appointments/${staffUsername}/otp/send`); + navigate(`/facility/${facilityId}/appointments/${staffId}/otp/send`); } else if (!selectedSlot) { Notification.Error({ msg: "Selected Slot Not Found" }); navigate( - `/facility/${facilityId}/appointments/${staffUsername}/book-appointment`, + `/facility/${facilityId}/appointments/${staffId}/book-appointment`, ); } @@ -205,7 +205,7 @@ export default function PatientSelect({ className="border border-secondary-400" onClick={() => navigate( - `/facility/${facilityId}/appointments/${staffUsername}/book-appointment`, + `/facility/${facilityId}/appointments/${staffId}/book-appointment`, ) } > @@ -229,7 +229,7 @@ export default function PatientSelect({ className="w-1/2 self-center" onClick={() => navigate( - `/facility/${facilityId}/appointments/${staffUsername}/patient-registration`, + `/facility/${facilityId}/appointments/${staffId}/patient-registration`, ) } > diff --git a/src/pages/Appoinments/Schedule.tsx b/src/pages/Appoinments/Schedule.tsx index 2473a51fc8b..62d67b3db07 100644 --- a/src/pages/Appoinments/Schedule.tsx +++ b/src/pages/Appoinments/Schedule.tsx @@ -32,12 +32,12 @@ import { TokenData } from "@/types/auth/otpToken"; interface AppointmentsProps { facilityId: string; - staffExternalId: string; + staffId: string; } export function ScheduleAppointment(props: AppointmentsProps) { const { t } = useTranslation(); - const { facilityId, staffExternalId } = props; + const { facilityId, staffId } = props; const [selectedMonth, setSelectedMonth] = useState(new Date()); const [selectedDate, setSelectedDate] = useState(new Date()); const [selectedSlot, setSelectedSlot] = useState(); @@ -47,14 +47,12 @@ export function ScheduleAppointment(props: AppointmentsProps) { localStorage.getItem(CarePatientTokenKey) || "{}", ); - if (!staffExternalId) { + if (!staffId) { Notification.Error({ msg: "Staff username not found" }); navigate(`/facility/${facilityId}/`); } else if (!tokenData) { Notification.Error({ msg: "Phone number not found" }); - navigate( - `/facility/${facilityId}/appointments/${staffExternalId}/otp/send`, - ); + navigate(`/facility/${facilityId}/appointments/${staffId}/otp/send`); } const { data: facilityResponse, error: facilityError } = useQuery< @@ -73,12 +71,11 @@ export function ScheduleAppointment(props: AppointmentsProps) { } const { data: userData, error: userError } = useQuery({ - queryKey: ["user", staffExternalId], - queryFn: () => - request(routes.getScheduleAbleFacilityUser, { - pathParams: { facilityId: facilityId, user_id: staffExternalId }, - }), - enabled: !!staffExternalId, + queryKey: ["user", facilityId, staffId], + queryFn: query(routes.getScheduleAbleFacilityUser, { + pathParams: { facility_id: facilityId, user_id: staffId }, + }), + enabled: !!facilityId && !!staffId, }); if (userError) { @@ -86,11 +83,11 @@ export function ScheduleAppointment(props: AppointmentsProps) { } const slotsQuery = useQuery<{ results: SlotAvailability[] }>({ - queryKey: ["slots", facilityId, staffExternalId, selectedDate], + queryKey: ["slots", facilityId, staffId, selectedDate], queryFn: query(routes.otp.getSlotsForDay, { body: { facility: facilityId, - resource: staffExternalId, + resource: staffId, day: dateQueryString(selectedDate), }, headers: { @@ -135,12 +132,10 @@ export function ScheduleAppointment(props: AppointmentsProps) { ); }; - if (!userData?.data) { + if (!userData) { return ; } - const user = userData.data; - return (
@@ -162,24 +157,24 @@ export function ScheduleAppointment(props: AppointmentsProps) {

- {user.user_type === "Doctor" - ? `Dr. ${user.first_name} ${user.last_name}` - : `${user.first_name} ${user.last_name}`} + {userData.user_type === "doctor" + ? `Dr. ${userData.first_name} ${userData.last_name}` + : `${userData.first_name} ${userData.last_name}`}

- {user.user_type} + {userData.user_type}

{/*

Education:

- {user.qualification} + {userData.qualification}

*/}
@@ -198,9 +193,9 @@ export function ScheduleAppointment(props: AppointmentsProps) {
{t("book_an_appointment_with")}{" "} - {user.user_type === "Doctor" - ? `Dr. ${user.first_name} ${user.last_name}` - : `${user.first_name} ${user.last_name}`} + {userData.user_type === "doctor" + ? `Dr. ${userData.first_name} ${userData.last_name}` + : `${userData.first_name} ${userData.last_name}`}
@@ -299,7 +294,7 @@ export function ScheduleAppointment(props: AppointmentsProps) { ); localStorage.setItem("reason", reason); navigate( - `/facility/${facilityId}/appointments/${staffExternalId}/patient-select`, + `/facility/${facilityId}/appointments/${staffId}/patient-select`, ); }} > diff --git a/src/pages/Appoinments/auth/PatientLogin.tsx b/src/pages/Appoinments/auth/PatientLogin.tsx index b4963847471..5b1ae45f76e 100644 --- a/src/pages/Appoinments/auth/PatientLogin.tsx +++ b/src/pages/Appoinments/auth/PatientLogin.tsx @@ -47,11 +47,11 @@ const FormSchema = z.object({ export default function PatientLogin({ facilityId, - staffUsername, + staffId, page, }: { facilityId: string; - staffUsername: string; + staffId: string; page: string; }) { const { goBack } = useAppHistory(); @@ -74,7 +74,7 @@ export default function PatientLogin({ dayjs(tokenData.createdAt).isAfter(dayjs().subtract(14, "minutes")) ) { navigate( - `/facility/${facilityId}/appointments/${staffUsername}/book-appointment`, + `/facility/${facilityId}/appointments/${staffId}/book-appointment`, ); } const validate = (phoneNumber: string) => { @@ -110,11 +110,11 @@ export default function PatientLogin({ ) { Notification.Success({ msg: t("valid_otp_found") }); navigate( - `/facility/${facilityId}/appointments/${staffUsername}/book-appointment`, + `/facility/${facilityId}/appointments/${staffId}/book-appointment`, ); } else { navigate( - `/facility/${facilityId}/appointments/${staffUsername}/otp/verify`, + `/facility/${facilityId}/appointments/${staffId}/otp/verify`, ); } } @@ -162,7 +162,7 @@ export default function PatientLogin({ }; localStorage.setItem(CarePatientTokenKey, JSON.stringify(tokenData)); navigate( - `/facility/${facilityId}/appointments/${staffUsername}/book-appointment`, + `/facility/${facilityId}/appointments/${staffId}/book-appointment`, ); } }, @@ -289,7 +289,7 @@ export default function PatientLogin({ page === "send" ? goBack() : navigate( - `/facility/${facilityId}/appointments/${staffUsername}/otp/send`, + `/facility/${facilityId}/appointments/${staffId}/otp/send`, ) } > diff --git a/src/pages/Facility/components/UserCard.tsx b/src/pages/Facility/components/UserCard.tsx index 2cfe886d1c2..7ae0221e4ff 100644 --- a/src/pages/Facility/components/UserCard.tsx +++ b/src/pages/Facility/components/UserCard.tsx @@ -36,10 +36,10 @@ export function UserCard({ user, className, facilityId }: Props) { Object.keys(tokenData).length > 0 && dayjs(tokenData.createdAt).isAfter(dayjs().subtract(14, "minutes")) ) { - return `/facility/${facilityId}/appointments/${user.external_id}/book-appointment`; + return `/facility/${facilityId}/appointments/${user.id}/book-appointment`; } - return `/facility/${facilityId}/appointments/${user.external_id}/otp/send`; - }, [tokenData, facilityId, user.external_id]); + return `/facility/${facilityId}/appointments/${user.id}/otp/send`; + }, [tokenData, facilityId, user.id]); return ( From ec54284bbd946c23e90ee98f94b1dea817de8ec6 Mon Sep 17 00:00:00 2001 From: Amjith Titus Date: Thu, 2 Jan 2025 21:57:57 +0530 Subject: [PATCH 372/708] Medication Request | Convert dosage instruction to array (#9665) Co-authored-by: Bodhish Thomas Co-authored-by: Gigin George --- src/Routers/routes/ConsultationRoutes.tsx | 5 + .../Medicine/AdministerMedicine.tsx | 10 +- .../AdministrationTable.tsx | 2 +- .../AdministrationTableRow.tsx | 6 +- .../MedicineAdministrationSheet/index.tsx | 479 +++++++++++------- .../Medicine/PrescriptionDetailCard.tsx | 21 +- .../Medicine/PrescriptionsTable.tsx | 16 +- .../MedicationRequestQuestion.tsx | 51 +- src/pages/Encounters/PrintPrescription.tsx | 383 ++++++++++++++ .../Encounters/tabs/EncounterMedicinesTab.tsx | 7 +- src/types/emr/medicationRequest.ts | 2 +- 11 files changed, 743 insertions(+), 239 deletions(-) create mode 100644 src/pages/Encounters/PrintPrescription.tsx diff --git a/src/Routers/routes/ConsultationRoutes.tsx b/src/Routers/routes/ConsultationRoutes.tsx index f0163722e1d..a55b00b3513 100644 --- a/src/Routers/routes/ConsultationRoutes.tsx +++ b/src/Routers/routes/ConsultationRoutes.tsx @@ -5,8 +5,13 @@ import PatientConsentRecords from "@/components/Patient/PatientConsentRecords"; import { AppRoutes } from "@/Routers/AppRouter"; import { EncounterShow } from "@/pages/Encounters/EncounterShow"; +import { PrintPrescription } from "@/pages/Encounters/PrintPrescription"; const consultationRoutes: AppRoutes = { + "/facility/:facilityId/encounter/:encounterId/prescriptions/print": ({ + facilityId, + encounterId, + }) => , "/facility/:facilityId/encounter/:encounterId/:tab": ({ facilityId, encounterId, diff --git a/src/components/Medicine/AdministerMedicine.tsx b/src/components/Medicine/AdministerMedicine.tsx index 0eb80cf1cb1..d708abff6af 100644 --- a/src/components/Medicine/AdministerMedicine.tsx +++ b/src/components/Medicine/AdministerMedicine.tsx @@ -54,7 +54,7 @@ export default function AdministerMedicine({ prescription, onClose }: Props) { const encounterId = useSlug("encounter"); const { patient } = useEncounter(); - const doseAndRate = prescription.dosage_instruction.dose_and_rate!; + const doseAndRate = prescription.dosage_instruction[0].dose_and_rate!; const requiresDosage = doseAndRate.type === "calculated"; const formSchema = z @@ -154,10 +154,10 @@ export default function AdministerMedicine({ prescription, onClose }: Props) { occurrence_period_end: time.toISOString(), note: values.note, dosage: { - text: prescription.dosage_instruction.text, - site: prescription.dosage_instruction.site, - route: prescription.dosage_instruction.route, - method: prescription.dosage_instruction.method, + text: prescription.dosage_instruction[0].text, + site: prescription.dosage_instruction[0].site, + route: prescription.dosage_instruction[0].route, + method: prescription.dosage_instruction[0].method, dose: { value: doseValue!, unit: doseUnit!, diff --git a/src/components/Medicine/MedicineAdministrationSheet/AdministrationTable.tsx b/src/components/Medicine/MedicineAdministrationSheet/AdministrationTable.tsx index 2de092dbaf8..9db1f28e855 100644 --- a/src/components/Medicine/MedicineAdministrationSheet/AdministrationTable.tsx +++ b/src/components/Medicine/MedicineAdministrationSheet/AdministrationTable.tsx @@ -37,7 +37,7 @@ export default function MedicineAdministrationTable({

{t("dosage")} &

- {!prescriptions[0]?.dosage_instruction.as_needed_boolean + {!prescriptions[0]?.dosage_instruction[0]?.as_needed_boolean ? t("frequency") : t("indicator")}

diff --git a/src/components/Medicine/MedicineAdministrationSheet/AdministrationTableRow.tsx b/src/components/Medicine/MedicineAdministrationSheet/AdministrationTableRow.tsx index 12c8faa41f8..7b89d30ad54 100644 --- a/src/components/Medicine/MedicineAdministrationSheet/AdministrationTableRow.tsx +++ b/src/components/Medicine/MedicineAdministrationSheet/AdministrationTableRow.tsx @@ -145,9 +145,9 @@ export default function MedicineAdministrationTableRow({
)} - {prescription.dosage_instruction.route && ( + {prescription.dosage_instruction[0].route && ( - {displayCode(prescription.dosage_instruction.route)} + {displayCode(prescription.dosage_instruction[0].route)} )}
@@ -217,7 +217,7 @@ type DosageFrequencyInfoProps = { export function DosageFrequencyInfo({ prescription, }: DosageFrequencyInfoProps) { - const dosageInstruction = prescription.dosage_instruction; + const dosageInstruction = prescription.dosage_instruction[0]; return (
diff --git a/src/components/Medicine/MedicineAdministrationSheet/index.tsx b/src/components/Medicine/MedicineAdministrationSheet/index.tsx index 85d1b8ff2d1..0dad737cc9f 100644 --- a/src/components/Medicine/MedicineAdministrationSheet/index.tsx +++ b/src/components/Medicine/MedicineAdministrationSheet/index.tsx @@ -1,232 +1,341 @@ -import { useMemo, useState } from "react"; +import { Link } from "raviger"; +import { useState } from "react"; import SubHeading from "@/CAREUI/display/SubHeading"; import CareIcon from "@/CAREUI/icons/CareIcon"; -import ScrollOverlay from "@/CAREUI/interactive/ScrollOverlay"; -import ButtonV2 from "@/components/Common/ButtonV2"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; + import Loading from "@/components/Common/Loading"; import { useEncounter } from "@/components/Facility/ConsultationDetails/EncounterContext"; -import MedicineAdministrationTable from "@/components/Medicine/MedicineAdministrationSheet/AdministrationTable"; -import { computeActivityBounds } from "@/components/Medicine/MedicineAdministrationSheet/utils"; -import useBreakpoints from "@/hooks/useBreakpoints"; -import useRangePagination from "@/hooks/useRangePagination"; import useSlug from "@/hooks/useSlug"; import routes from "@/Utils/request/api"; import useTanStackQueryInstead from "@/Utils/request/useQuery"; +import { classNames } from "@/Utils/utils"; +import { MedicationRequest } from "@/types/emr/medicationRequest"; interface Props { readonly?: boolean; - isPrn: boolean; + facilityId: string; } -const DEFAULT_BOUNDS = { start: new Date(), end: new Date() }; +const FREQUENCY_DISPLAY: Record = { + "1-1-d": { code: "OD", meaning: "Once daily" }, + "2-1-d": { code: "BD", meaning: "Twice daily" }, + "1-1-wk": { code: "QWK", meaning: "Once a week" }, + "4-1-h": { code: "Q4H", meaning: "Every 4 hours" }, + "6-1-h": { code: "QID", meaning: "Four times a day" }, + "8-1-h": { code: "TID", meaning: "Three times a day" }, + "1-1-s": { code: "STAT", meaning: "Immediately" }, + "1-1-d-night": { code: "HS", meaning: "At bedtime" }, + "2-1-d-alt": { code: "QOD", meaning: "Every other day" }, +}; + +function getFrequencyDisplay( + timing?: MedicationRequest["dosage_instruction"][0]["timing"], +) { + if (!timing?.repeat) return undefined; + const key = `${timing.repeat.frequency}-${timing.repeat.period}-${timing.repeat.period_unit}`; + return FREQUENCY_DISPLAY[key]; +} -const MedicineAdministrationSheet = ({ readonly, isPrn }: Props) => { +const MedicineAdministrationSheet = ({ facilityId }: Props) => { const encounterId = useSlug("encounter"); const { patient } = useEncounter(); + const [searchQuery, setSearchQuery] = useState(""); - const [showDiscontinued, setShowDiscontinued] = useState(false); - - const filters = { - encounter: encounterId, - is_prn: isPrn, - limit: 100, - }; - - const activeMedicationRequests = useTanStackQueryInstead( + const { data: medications, loading } = useTanStackQueryInstead( routes.medicationRequest.list, { pathParams: { patientId: patient!.id }, query: { - ...filters, - status: ["active", "on_hold"], + encounter: encounterId, + limit: 100, }, }, ); - const discontinuedMedicationRequests = useTanStackQueryInstead( - routes.medicationRequest.list, - { - pathParams: { patientId: patient!.id }, - query: { - ...filters, - status: ["completed", "ended", "stopped", "cancelled"], - }, - prefetch: !showDiscontinued, + const filteredMedications = medications?.results?.filter( + (med: MedicationRequest) => { + if (!searchQuery.trim()) return true; + const searchTerm = searchQuery.toLowerCase().trim(); + const medicationName = med.medication?.display?.toLowerCase() || ""; + return medicationName.includes(searchTerm); }, ); - const discontinuedCount = discontinuedMedicationRequests.data?.count; - - const { activityTimelineBounds, prescriptions } = useMemo(() => { - const prescriptionList = [ - ...(activeMedicationRequests.data?.results.map((request) => ({ - ...request, - discontinued: false, - })) ?? []), - ]; - - if (showDiscontinued) { - prescriptionList.push( - ...(discontinuedMedicationRequests.data?.results.map((request) => ({ - ...request, - discontinued: true, - })) ?? []), - ); - } + const activeMedications = filteredMedications?.filter( + (med: MedicationRequest) => + ["active", "on_hold"].includes(med.status || ""), + ); + const discontinuedMedications = filteredMedications?.filter( + (med: MedicationRequest) => + !["active", "on_hold"].includes(med.status || ""), + ); - return { - prescriptions: prescriptionList.sort( - (a, b) => +a.discontinued - +b.discontinued, - ), - activityTimelineBounds: prescriptionList - ? computeActivityBounds(prescriptionList) - : undefined, - }; - }, [ - activeMedicationRequests.data, - discontinuedMedicationRequests.data, - showDiscontinued, - ]); - - const daysPerPage = useBreakpoints({ default: 1, "2xl": 2 }); - const pagination = useRangePagination({ - bounds: activityTimelineBounds ?? DEFAULT_BOUNDS, - perPage: daysPerPage * 24 * 60 * 60 * 1000, - slots: (daysPerPage * 24) / 4, // Grouped by 4 hours - defaultEnd: true, - }); + const EmptyState = ({ searching }: { searching?: boolean }) => ( +
+
+ +
+
+

+ {searching ? "No matches found" : "No Prescriptions"} +

+

+ {searching + ? `No medications match "${searchQuery}"` + : "No medications have been prescribed yet"} +

+
+
+ ); return ( -
+
- // - // - // - // - // {t("edit_prescriptions")} - // - // {t("edit")} - // - // activeMedicationRequests.refetch()} - // /> - // - // - // - // Print - // - // - // ) - // } + title="Prescriptions" + options={ +
+ +
+ } /> -
- - Scroll to view more prescriptions - -
- } - disableOverlay={ - activeMedicationRequests.loading || - !prescriptions?.length || - !(prescriptions?.length > 1) - } - > - {activeMedicationRequests.loading ? ( -
- -
- ) : ( - <> - {prescriptions?.length === 0 && } - {!!prescriptions?.length && ( - { - activeMedicationRequests.refetch(); - discontinuedMedicationRequests.refetch(); - }} - readonly={readonly || false} - /> - )} - + +
+
+ + setSearchQuery(e.target.value)} + className="flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground" + /> + {searchQuery && ( + )} - - {!!discontinuedCount && ( - setShowDiscontinued(!showDiscontinued)} - > - - - - {showDiscontinued ? "Hide" : "Show"}{" "} - {discontinuedCount} discontinued - prescription(s) - - - +
+ + {loading ? ( +
+ +
+ ) : !medications?.results?.length ? ( + + ) : !filteredMedications?.length ? ( + + ) : ( + + +
+ + + Active{" "} + + {activeMedications?.length || 0} + + + + Discontinued{" "} + + {discontinuedMedications?.length || 0} + + + +
+ + +
+ {activeMedications?.map((med) => ( + + ))} +
+
+ +
+ {discontinuedMedications?.map((med) => ( + + ))} +
+
+
+
)}
); }; -const NoPrescriptions = ({ prn }: { prn: boolean }) => { +const PrescriptionEntry = ({ + medication, +}: { + medication: MedicationRequest; +}) => { + const instruction = medication.dosage_instruction[0]; + if (!instruction) return null; + + const frequency = getFrequencyDisplay(instruction.timing); + const additionalInstructions = instruction.additional_instruction; + const isPrn = instruction.as_needed_boolean; + + // Get status variant + const getStatusVariant = ( + status: string = "", + ): "default" | "destructive" | "secondary" | "outline" | "primary" => { + switch (status) { + case "active": + return "default"; + case "on-hold": + return "secondary"; + case "cancelled": + return "destructive"; + case "completed": + return "primary"; + default: + return "secondary"; + } + }; + + // Get priority variant + const getPriorityVariant = ( + priority: string = "", + ): "default" | "destructive" | "secondary" | "outline" | "primary" => { + switch (priority) { + case "stat": + return "destructive"; + case "urgent": + case "asap": + return "primary"; + default: + return "outline"; + } + }; + return ( -
- -

- {prn - ? "No PRN Prescriptions Prescribed" - : "No Prescriptions Prescribed"} -

+
+ {/* Medicine Name and Status */} +
+
+
+
+ + {medication.medication?.display} + + {isPrn && ( + + + PRN + + )} +
+
+ {medication.priority && ( + + {medication.priority} + + )} + + {medication.status} + +
+
+

+ {medication.medication?.code} +

+
+
+ + {/* Dosage and Instructions */} +
+ {instruction.dose_and_rate && ( + + {instruction.dose_and_rate.type === "calculated" ? ( + + {instruction.dose_and_rate.dose_range?.low.value}{" "} + {instruction.dose_and_rate.dose_range?.low.unit} →{" "} + {instruction.dose_and_rate.dose_range?.high.value}{" "} + {instruction.dose_and_rate.dose_range?.high.unit} + + ) : ( + + {instruction.dose_and_rate.dose_quantity?.value}{" "} + {instruction.dose_and_rate.dose_quantity?.unit} + + )} + + )} + {instruction.route && ( + + Via:{" "} + {instruction.route.display} + + )} + {instruction.method && ( + + Method:{" "} + {instruction.method.display} + + )} + {instruction.site && ( + + Site:{" "} + {instruction.site.display} + + )} + {frequency && {frequency.code}} + {isPrn && instruction.as_needed_for && ( + + When:{" "} + {instruction.as_needed_for.display} + + )} +
+ + {/* Additional Instructions */} + {additionalInstructions && additionalInstructions.length > 0 && ( +
+ {additionalInstructions.map((instr, idx) => ( + + {idx > 0 && " • "} + {instr.display} + + ))} +
+ )}
); }; diff --git a/src/components/Medicine/PrescriptionDetailCard.tsx b/src/components/Medicine/PrescriptionDetailCard.tsx index e84a5ae39b1..af81ec8ae2e 100644 --- a/src/components/Medicine/PrescriptionDetailCard.tsx +++ b/src/components/Medicine/PrescriptionDetailCard.tsx @@ -75,7 +75,7 @@ export default function PrescriptionDetailCard({ {prescription.category === "discharge" && `${t("discharge")} `} {t( - prescription.dosage_instruction.as_needed_boolean + prescription.dosage_instruction[0].as_needed_boolean ? "prn_prescription" : "prescription", )} @@ -146,30 +146,31 @@ export default function PrescriptionDetailCard({
- {prescription.dosage_instruction.dose_and_rate?.dose_range && ( + {prescription.dosage_instruction[0].dose_and_rate?.dose_range && ( <> {displayQuantity( - prescription.dosage_instruction.dose_and_rate?.dose_range + prescription.dosage_instruction[0].dose_and_rate?.dose_range ?.low as Quantity, )} {displayQuantity( - prescription.dosage_instruction.dose_and_rate?.dose_range + prescription.dosage_instruction[0].dose_and_rate?.dose_range ?.high as Quantity, )} )} - {prescription.dosage_instruction.dose_and_rate?.dose_quantity && ( + {prescription.dosage_instruction[0].dose_and_rate + ?.dose_quantity && ( {displayQuantity( - prescription.dosage_instruction.dose_and_rate + prescription.dosage_instruction[0].dose_and_rate ?.dose_quantity as Quantity, )} @@ -179,19 +180,19 @@ export default function PrescriptionDetailCard({ className="col-span-10 break-all sm:col-span-6" label={t("route")} > - {displayCode(prescription.dosage_instruction.route)} + {displayCode(prescription.dosage_instruction[0].route)} - {prescription.dosage_instruction.as_needed_boolean ? ( + {prescription.dosage_instruction[0].as_needed_boolean ? ( - {displayCode(prescription.dosage_instruction.as_needed_for)} + {displayCode(prescription.dosage_instruction[0].as_needed_for)} ) : ( - {displayTiming(prescription.dosage_instruction.timing)} + {displayTiming(prescription.dosage_instruction[0].timing)} )} diff --git a/src/components/Medicine/PrescriptionsTable.tsx b/src/components/Medicine/PrescriptionsTable.tsx index da00128289e..29d3f0fec16 100644 --- a/src/components/Medicine/PrescriptionsTable.tsx +++ b/src/components/Medicine/PrescriptionsTable.tsx @@ -91,19 +91,19 @@ export default function PrescriptionsTable({ ...obj, medicine: displayCode(obj.medication), route__pretty: - obj.dosage_instruction.route && - displayCode(obj.dosage_instruction.route), + obj.dosage_instruction[0].route && + displayCode(obj.dosage_instruction[0].route), frequency__pretty: - obj.dosage_instruction.timing && - displayTiming(obj.dosage_instruction.timing), + obj.dosage_instruction[0].timing && + displayTiming(obj.dosage_instruction[0].timing), max_dose_per_period__pretty: - obj.dosage_instruction.max_dose_per_period && + obj.dosage_instruction[0].max_dose_per_period && displayDoseRange( - obj.dosage_instruction.max_dose_per_period, + obj.dosage_instruction[0].max_dose_per_period, ), indicator: - obj.dosage_instruction.as_needed_for && - displayCode(obj.dosage_instruction.as_needed_for), + obj.dosage_instruction[0].as_needed_for && + displayCode(obj.dosage_instruction[0].as_needed_for), })) || [] } objectKeys={Object.values(tkeys)} diff --git a/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx b/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx index b7260f13341..f96cd742b89 100644 --- a/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx @@ -20,6 +20,7 @@ import ValueSetSelect from "@/components/Questionnaire/ValueSetSelect"; import { MEDICATION_REQUEST_INTENT, MedicationRequest, + MedicationRequestDosageInstruction, MedicationRequestIntent, } from "@/types/emr/medicationRequest"; import { Code } from "@/types/questionnaire/code"; @@ -41,7 +42,7 @@ const MEDICATION_REQUEST_INITIAL_VALUE: MedicationRequest = { do_not_perform: false, medication: undefined, authored_on: new Date().toISOString(), - dosage_instruction: {}, + dosage_instruction: [], }; export function MedicationRequestQuestion({ @@ -56,7 +57,11 @@ export function MedicationRequestQuestion({ const handleAddMedication = (medication: Code) => { const newMedications: MedicationRequest[] = [ ...medications, - { ...MEDICATION_REQUEST_INITIAL_VALUE, medication }, + { + ...MEDICATION_REQUEST_INITIAL_VALUE, + medication, + dosage_instruction: [], + }, ]; updateQuestionnaireResponseCB({ ...questionnaireResponse, @@ -138,12 +143,12 @@ export const MedicationRequestItem: React.FC<{ onRemove?: () => void; index?: number; }> = ({ medication, disabled, onUpdate, onRemove, index = 0 }) => { - const dosageInstruction = medication.dosage_instruction; + const dosageInstruction = medication.dosage_instruction[0]; const handleUpdateDosageInstruction = ( - updates: Partial, + updates: Partial, ) => { onUpdate?.({ - dosage_instruction: { ...dosageInstruction, ...updates }, + dosage_instruction: [{ ...dosageInstruction, ...updates }], }); }; @@ -198,7 +203,7 @@ export const MedicationRequestItem: React.FC<{ handleUpdateDosageInstruction({ @@ -212,7 +217,7 @@ export const MedicationRequestItem: React.FC<{ handleUpdateDosageInstruction({ route })} placeholder="Select route" disabled={disabled} @@ -224,7 +229,7 @@ export const MedicationRequestItem: React.FC<{ handleUpdateDosageInstruction({ method })} placeholder="Select method" disabled={disabled} @@ -238,7 +243,7 @@ export const MedicationRequestItem: React.FC<{ handleUpdateDosageInstruction({ site })} placeholder="Select site" disabled={disabled} @@ -249,12 +254,14 @@ export const MedicationRequestItem: React.FC<{
handleUpdateDosageInstruction({ as_needed_boolean: !!checked, as_needed_for: checked - ? medication.dosage_instruction?.as_needed_for + ? medication.dosage_instruction[0]?.as_needed_for : undefined, }) } @@ -265,12 +272,12 @@ export const MedicationRequestItem: React.FC<{
- {medication.dosage_instruction?.as_needed_boolean ? ( + {medication.dosage_instruction[0]?.as_needed_boolean ? (
handleUpdateDosageInstruction({ as_needed_for: reason }) } @@ -320,13 +327,17 @@ export const MedicationRequestItem: React.FC<{ onUpdate?.({ - dosage_instruction: { - ...medication.dosage_instruction, - additional_instruction: [additionalInstruction], - }, + dosage_instruction: [ + { + ...medication.dosage_instruction[0], + additional_instruction: [additionalInstruction], + }, + ], }) } disabled={disabled} @@ -343,7 +354,7 @@ export const MedicationRequestItem: React.FC<{ }; const reverseFrequencyOption = ( - option: MedicationRequest["dosage_instruction"]["timing"], + option: MedicationRequest["dosage_instruction"][0]["timing"], ) => { return Object.entries(FREQUENCY_OPTIONS).find( ([, value]) => @@ -395,6 +406,6 @@ const FREQUENCY_OPTIONS = { string, { display: string; - timing: MedicationRequest["dosage_instruction"]["timing"]; + timing: MedicationRequest["dosage_instruction"][0]["timing"]; } >; diff --git a/src/pages/Encounters/PrintPrescription.tsx b/src/pages/Encounters/PrintPrescription.tsx new file mode 100644 index 00000000000..b5f8f8ec4e6 --- /dev/null +++ b/src/pages/Encounters/PrintPrescription.tsx @@ -0,0 +1,383 @@ +import careConfig from "@careConfig"; +import { useQuery } from "@tanstack/react-query"; +import { format } from "date-fns"; +import { useTranslation } from "react-i18next"; + +import PrintPreview from "@/CAREUI/misc/PrintPreview"; + +import { Badge } from "@/components/ui/badge"; +import { Separator } from "@/components/ui/separator"; + +import api from "@/Utils/request/api"; +import query from "@/Utils/request/query"; +import { formatPatientAge } from "@/Utils/utils"; +import { MedicationRequest } from "@/types/emr/medicationRequest"; + +const FREQUENCY_DISPLAY: Record = { + "1-1-d": { code: "OD", meaning: "Once daily" }, + "2-1-d": { code: "BD", meaning: "Twice daily" }, + "1-1-wk": { code: "QWK", meaning: "Once a week" }, + "4-1-h": { code: "Q4H", meaning: "Every 4 hours" }, + "6-1-h": { code: "QID", meaning: "Four times a day" }, + "8-1-h": { code: "TID", meaning: "Three times a day" }, + "1-1-s": { code: "STAT", meaning: "Immediately" }, + "1-1-d-night": { code: "HS", meaning: "At bedtime" }, + "2-1-d-alt": { code: "QOD", meaning: "Every other day" }, +}; + +function getFrequencyDisplay( + timing?: MedicationRequest["dosage_instruction"][0]["timing"], +) { + if (!timing?.repeat) return undefined; + const key = `${timing.repeat.frequency}-${timing.repeat.period}-${timing.repeat.period_unit}`; + return FREQUENCY_DISPLAY[key]; +} + +// Helper function to format dosage in Rx style +function formatDosage(instruction: MedicationRequest["dosage_instruction"][0]) { + if (!instruction.dose_and_rate) return ""; + + if (instruction.dose_and_rate.type === "calculated") { + const { dose_range } = instruction.dose_and_rate; + if (!dose_range) return ""; + return `${dose_range.low.value}${dose_range.low.unit} - ${dose_range.high.value}${dose_range.high.unit}`; + } + + const { dose_quantity } = instruction.dose_and_rate; + if (!dose_quantity?.value) return ""; + + return `${dose_quantity.value} ${dose_quantity.unit || ""}`.trim(); +} + +// Helper function to format dosage instructions in Rx style +function formatSig( + instruction: MedicationRequest["dosage_instruction"][0], + frequency?: { code: string; meaning: string }, +) { + const parts: string[] = []; + + // Add route if present + if (instruction.route?.display) { + parts.push(`Via ${instruction.route.display}`); + } + + // Add method if present + if (instruction.method?.display) { + parts.push(`by ${instruction.method.display}`); + } + + // Add site if present + if (instruction.site?.display) { + parts.push(`to ${instruction.site.display}`); + } + + // Add frequency + if (frequency) { + parts.push(frequency.code); + } else if (instruction.timing?.repeat) { + const { frequency, period_unit } = instruction.timing.repeat; + if (frequency) { + parts.push(`${frequency} time(s) per ${period_unit}`); + } + } + + return parts.join(" "); +} + +export const PrintPrescription = (props: { + facilityId: string; + encounterId: string; +}) => { + const { facilityId, encounterId } = props; + const { t } = useTranslation(); + + const { data: encounter } = useQuery({ + queryKey: ["encounter", encounterId], + queryFn: query(api.encounter.get, { + pathParams: { id: encounterId }, + queryParams: { facility: facilityId }, + }), + }); + + const { data: medications } = useQuery({ + queryKey: ["medications", encounter?.patient?.id], + queryFn: query(api.medicationRequest.list, { + pathParams: { patientId: encounter?.patient?.id || "" }, + queryParams: { encounter: encounterId }, + }), + enabled: !!encounter?.patient?.id, + }); + + const normalMedications = medications?.results?.filter( + (m) => !m.dosage_instruction[0]?.as_needed_boolean, + ); + const prnMedications = medications?.results?.filter( + (m) => m.dosage_instruction[0]?.as_needed_boolean, + ); + + // Collect all unique frequencies used in the prescription + const usedFrequencies = new Set(); + medications?.results?.forEach((med) => { + const timing = med.dosage_instruction[0]?.timing; + if (!timing?.repeat) return; + const key = `${timing.repeat.frequency}-${timing.repeat.period}-${timing.repeat.period_unit}`; + if (FREQUENCY_DISPLAY[key]) { + usedFrequencies.add(key); + } + }); + + if (!medications?.results?.length) { + return ( +
+ No medications found for this encounter. +
+ ); + } + + return ( + +
+ {/* Header */} +
+
+

{encounter?.facility?.name}

+
+ care logo +
+ + {/* Patient Details */} +
+
+ + {encounter?.patient && ( + <> + {encounter.patient.name} + + ({t(`GENDER__${encounter.patient.gender}`)},{" "} + {formatPatientAge(encounter.patient, true)}) + + + )} + + + {encounter?.period?.start && + format(new Date(encounter.period.start), "PPP")} + +
+ {encounter?.external_identifier && ( +
+ + {encounter.external_identifier} + +
+ )} +
+ + {/* Frequency Legend */} + {usedFrequencies.size > 0 && ( +
+

+ Frequency Guide: +

+
+ {Array.from(usedFrequencies).map((key) => ( +
+ + {FREQUENCY_DISPLAY[key].code} + + + = {FREQUENCY_DISPLAY[key].meaning} + +
+ ))} +
+
+ )} + + {/* Rx Symbol and Medications */} +
+
+ + +
+ + {/* Normal Medications */} + {normalMedications && normalMedications.length > 0 && ( +
+ {normalMedications.map((medication, index) => ( + + ))} +
+ )} + + {/* PRN Medications */} + {prnMedications && prnMedications.length > 0 && ( +
+
+

+ Take When Required (PRN) +

+ +
+ {prnMedications.map((medication, index) => ( + + ))} +
+ )} +
+ + {/* Footer */} +
+
+
+ + +

+ Sign of the Consulting Doctor +

+
+
+ +
+

Generated on: {format(new Date(), "PPP 'at' p")}

+

+ This is a computer generated prescription. It shall be issued to + the patient only after the concerned doctor has verified the + content and authorized the same by affixing signature. +

+
+
+
+
+ ); +}; + +const PatientDetail = ({ + name, + children, +}: { + name: string; + children?: React.ReactNode; +}) => { + return ( +
+

{name}

+ {children != null ? ( +

{children}

+ ) : ( +
+ )} +
+ ); +}; + +const PrescriptionEntry = ({ + medication, + index, +}: { + medication: MedicationRequest; + index: number; + prn?: boolean; +}) => { + const instruction = medication.dosage_instruction[0]; + + if (!instruction) return null; + + const frequency = getFrequencyDisplay(instruction.timing); + const dosage = formatDosage(instruction); + const sig = formatSig(instruction, frequency); + + const hasAdditionalInstructions = + (instruction.additional_instruction && + instruction.additional_instruction.length > 0) || + medication.note; + + return ( +
+
+ {index} +
+ + {/* Medicine Name and Status */} +
+
+
+

+ {medication.medication?.display} +

+

+ {medication.medication?.code} ({medication.medication?.system}) +

+
+
+ + {medication.status} + +
+
+ + {/* Dosage and Instructions */} +
+
+ Dosage: + {dosage} +
+
+ Instructions: + {sig} +
+ {instruction.as_needed_boolean && ( +
+ Take when: + + {instruction.as_needed_for?.display || "As needed (PRN)"} + +
+ )} +
+ + {/* Additional Instructions */} + {hasAdditionalInstructions && ( +
+ {instruction.additional_instruction?.map((instr, idx) => ( +
+ {instr.display} +
+ ))} + {medication.note && ( +
+ + {medication.note} +
+ )} +
+ )} +
+
+ ); +}; diff --git a/src/pages/Encounters/tabs/EncounterMedicinesTab.tsx b/src/pages/Encounters/tabs/EncounterMedicinesTab.tsx index 0c34325bb0d..a7b8202cb1e 100644 --- a/src/pages/Encounters/tabs/EncounterMedicinesTab.tsx +++ b/src/pages/Encounters/tabs/EncounterMedicinesTab.tsx @@ -3,14 +3,9 @@ import MedicineAdministrationSheet from "@/components/Medicine/MedicineAdministr import { EncounterTabProps } from "@/pages/Encounters/EncounterShow"; export const EncounterMedicinesTab = (props: EncounterTabProps) => { - const isDischarged = props.encounter.status_history.history.some( - ({ status }) => status === "DISCHARGED", - ); return (
- - - {/* */} +
); }; diff --git a/src/types/emr/medicationRequest.ts b/src/types/emr/medicationRequest.ts index a0f6b6ef212..e72e5410359 100644 --- a/src/types/emr/medicationRequest.ts +++ b/src/types/emr/medicationRequest.ts @@ -124,7 +124,7 @@ export interface MedicationRequest { patient?: string; // UUID encounter?: string; // UUID authored_on: string; - dosage_instruction: MedicationRequestDosageInstruction; + dosage_instruction: MedicationRequestDosageInstruction[]; note?: string; created_by?: UserBareMinimum; From a3a33d8f04c6584d49d4c15760fb76129e2de601 Mon Sep 17 00:00:00 2001 From: Yaswanth Kumar Bethu Date: Thu, 2 Jan 2025 22:02:55 +0530 Subject: [PATCH 373/708] Update installation instructions (#9664) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a5072dff755..61ad8de9895 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,9 @@ #### Install the required dependencies ```sh -npm run install-all +npm install ``` + #### First-time setup For first-time setup, run the following command to generate the pluginMap and install plugin configurations: From 5b1050c950e812598dd21153665e7e881700bce6 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Thu, 2 Jan 2025 16:34:13 +0000 Subject: [PATCH 374/708] Appointment as structured question and other enhancements (#9661) --- public/locale/en.json | 4 + .../FollowUpAppointmentQuestion.tsx | 216 ++++++++++++++ .../QuestionTypes/FollowUpVisitQuestion.tsx | 0 .../QuestionTypes/QuestionInput.tsx | 4 + .../Questionnaire/QuestionnaireForm.tsx | 3 +- .../Questionnaire/structured/handlers.ts | 19 +- .../Questionnaire/structured/types.ts | 7 + .../Appointments/AppointmentCreatePage.tsx | 2 +- .../Appointments/AppointmentDetailsPage.tsx | 60 +++- .../Appointments/AppointmentsPage.tsx | 27 +- src/components/Schedule/Appointments/utils.ts | 280 +++++++++++++++++- src/components/Schedule/types.ts | 5 + src/types/questionnaire/form.ts | 8 +- src/types/questionnaire/question.ts | 3 +- 14 files changed, 607 insertions(+), 31 deletions(-) create mode 100644 src/components/Questionnaire/QuestionTypes/FollowUpAppointmentQuestion.tsx create mode 100644 src/components/Questionnaire/QuestionTypes/FollowUpVisitQuestion.tsx diff --git a/public/locale/en.json b/public/locale/en.json index f20179944d1..6d2d774c921 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1588,6 +1588,8 @@ "select_register_patient": "Select/Register Patient", "select_seven_day_period": "Select a seven day period", "select_skills": "Select and add some skills", + "select_time": "Select time", + "select_time_slot": "Select time slot", "select_wards": "Select wards", "self_booked": "Self-booked", "send": "Send", @@ -1631,6 +1633,7 @@ "skill_add_error": "Error while adding skill", "skill_added_successfully": "Skill added successfully", "skills": "Skills", + "slots_left": "slots left", "social_profile": "Social Profile", "social_profile_detail": "Include occupation, ration card category, socioeconomic status, and domestic healthcare support for a complete profile.", "socioeconomic_status": "Socioeconomic status", @@ -1833,6 +1836,7 @@ "view_details": "View Details", "view_facility": "View Facility", "view_files": "View Files", + "view_patient": "View Patient", "view_patients": "View Patients", "view_update_patient_files": "View/Update patient files", "view_updates": "View Updates", diff --git a/src/components/Questionnaire/QuestionTypes/FollowUpAppointmentQuestion.tsx b/src/components/Questionnaire/QuestionTypes/FollowUpAppointmentQuestion.tsx new file mode 100644 index 00000000000..3fc6b63ab02 --- /dev/null +++ b/src/components/Questionnaire/QuestionTypes/FollowUpAppointmentQuestion.tsx @@ -0,0 +1,216 @@ +import { useQuery } from "@tanstack/react-query"; +import { format } from "date-fns"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; + +import { DatePicker } from "@/components/ui/date-picker"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Textarea } from "@/components/ui/textarea"; + +import { Avatar } from "@/components/Common/Avatar"; +import { groupSlotsByAvailability } from "@/components/Schedule/Appointments/utils"; +import { ScheduleAPIs } from "@/components/Schedule/api"; +import { FollowUpAppointmentRequest } from "@/components/Schedule/types"; + +import useSlug from "@/hooks/useSlug"; + +import query from "@/Utils/request/query"; +import { dateQueryString, formatDisplayName } from "@/Utils/utils"; +import { + QuestionnaireResponse, + ResponseValue, +} from "@/types/questionnaire/form"; +import { Question } from "@/types/questionnaire/question"; +import { UserBase } from "@/types/user/user"; + +interface FollowUpVisitQuestionProps { + question: Question; + questionnaireResponse: QuestionnaireResponse; + updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + disabled?: boolean; +} + +export function FollowUpAppointmentQuestion({ + question, + questionnaireResponse, + updateQuestionnaireResponseCB, + disabled, +}: FollowUpVisitQuestionProps) { + const { t } = useTranslation(); + const [resource, setResource] = useState(); + const [selectedDate, setSelectedDate] = useState(); + + const values = + (questionnaireResponse.values?.[0] + ?.value as unknown as FollowUpAppointmentRequest[]) || []; + + const value = values[0] ?? {}; + + const handleUpdate = (updates: Partial) => { + const followUpAppointment = { ...value, ...updates }; + updateQuestionnaireResponseCB({ + ...questionnaireResponse, + values: [ + { + type: "follow_up_appointment", + value: [followUpAppointment] as unknown as ResponseValue["value"], + }, + ], + }); + }; + + const facilityId = useSlug("facility"); + + const resourcesQuery = useQuery({ + queryKey: ["availableResources", facilityId], + queryFn: query(ScheduleAPIs.appointments.availableDoctors, { + pathParams: { facility_id: facilityId }, + }), + }); + + const slotsQuery = useQuery({ + queryKey: [ + "slots", + facilityId, + resource?.id, + dateQueryString(selectedDate), + ], + queryFn: query(ScheduleAPIs.slots.getSlotsForDay, { + pathParams: { facility_id: facilityId }, + body: { + resource: resource?.id, + day: dateQueryString(selectedDate), + }, + }), + enabled: !!resource && !!selectedDate, + }); + + return ( +
+ +
+
+ +