diff --git a/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts b/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts index 6f9bf2b03e0..9911090eba0 100644 --- a/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts +++ b/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts @@ -86,7 +86,7 @@ describe("Patient Consultation in multiple combination", () => { patientConsultationPage.selectPatientPrincipalDiagnosis(diagnosis4); patientTreatmentPlan.clickAddProcedure(); patientTreatmentPlan.typeProcedureName(procedureName); - patientTreatmentPlan.typeProcedureTime("2024-02-22T12:30"); + patientTreatmentPlan.typeProcedureTime("220220241230"); patientTreatmentPlan.typeTreatmentPlan(patientTreatment); patientTreatmentPlan.typePatientGeneralInstruction(generalInstruction); patientTreatmentPlan.typeSpecialInstruction(specialInstruction); @@ -182,12 +182,12 @@ describe("Patient Consultation in multiple combination", () => { patientConsultationPage.typeCauseOfDeath("Cause of Death"); patientConsultationPage.typePatientConsultationDate( "#death_datetime", - "2024-02-22T12:45", + "220220241230", ); patientConsultationPage.typeDeathConfirmedBy(doctorName); patientConsultationPage.typePatientConsultationDate( "#encounter_date", - "2024-02-22T12:30", + "220220241230", ); cy.submitButton("Create Consultation"); cy.verifyNotification( @@ -245,7 +245,7 @@ describe("Patient Consultation in multiple combination", () => { ); patientConsultationPage.typePatientConsultationDate( "#icu_admission_date", - "2024-02-23T12:30", + "230220241230", ); // add investigation patientInvestigation.clickAddInvestigation(); diff --git a/cypress/e2e/patient_spec/PatientRegistration.cy.ts b/cypress/e2e/patient_spec/PatientRegistration.cy.ts index 4e493a23bff..36c08497c2c 100644 --- a/cypress/e2e/patient_spec/PatientRegistration.cy.ts +++ b/cypress/e2e/patient_spec/PatientRegistration.cy.ts @@ -25,7 +25,7 @@ const getRelativeDateString = (deltaDays = 0) => { month: "2-digit", year: "numeric", }) - .replace("/", ""); + .replace(/\//g, ""); }; describe("Patient Creation with consultation", () => { diff --git a/cypress/e2e/users_spec/UsersCreation.cy.ts b/cypress/e2e/users_spec/UsersCreation.cy.ts index 392c9ff987d..8e7236835e7 100644 --- a/cypress/e2e/users_spec/UsersCreation.cy.ts +++ b/cypress/e2e/users_spec/UsersCreation.cy.ts @@ -145,7 +145,7 @@ describe("User Creation", () => { userCreationPage.typeIntoElementById("password", "Test@123"); userCreationPage.selectHomeFacility("Dummy Shifting Center"); userCreationPage.typeIntoElementById("phone_number", phone_number); - userCreationPage.setInputDate("date_of_birth", "date-input", "25081999"); + userCreationPage.setInputDate("date_of_birth", "25081999"); userCreationPage.selectDropdownOption("user_type", "Doctor"); userCreationPage.typeIntoElementById("c_password", "Test@123"); userCreationPage.typeIntoElementById("qualification", "MBBS"); diff --git a/cypress/pageobject/Asset/AssetCreation.ts b/cypress/pageobject/Asset/AssetCreation.ts index f0b2f1b74eb..ec631768154 100644 --- a/cypress/pageobject/Asset/AssetCreation.ts +++ b/cypress/pageobject/Asset/AssetCreation.ts @@ -59,10 +59,10 @@ export class AssetPage { cy.get("[data-testid=asset-support-email-input] input").type(supportEmail); cy.get("[data-testid=asset-vendor-name-input] input").type(vendorName); cy.get("[data-testid=asset-serial-number-input] input").type(serialNumber); - cy.get( - "[data-testid=asset-last-serviced-on-input] input[type='text']", - ).click(); - cy.get("#date-input").click().type(lastServicedOn); + cy.clickAndTypeDate( + "[data-testid=asset-last-serviced-on-input]", + lastServicedOn, + ); cy.get("[data-testid=asset-notes-input] textarea").type(notes); } @@ -117,10 +117,10 @@ export class AssetPage { cy.get("[data-testid=asset-vendor-name-input] input") .clear() .type(vendorName); - cy.get( - "[data-testid=asset-last-serviced-on-input] input[type='text']", - ).click(); - cy.get("#date-input").click().clear().type(lastServicedOn); + cy.clickAndTypeDate( + "[data-testid=asset-last-serviced-on-input]", + lastServicedOn, + ); cy.get("[data-testid=asset-notes-input] textarea").clear().type(notes); } @@ -267,8 +267,7 @@ export class AssetPage { } enterAssetservicedate(text: string) { - cy.get("input[name='last_serviced_on']").click(); - cy.get("#date-input").click().type(text); + cy.clickAndTypeDate("input[name='last_serviced_on']", text); } scrollintoWarrantyDetails() { diff --git a/cypress/pageobject/Facility/FacilityCreation.ts b/cypress/pageobject/Facility/FacilityCreation.ts index 159b8660b43..a48ffe0856b 100644 --- a/cypress/pageobject/Facility/FacilityCreation.ts +++ b/cypress/pageobject/Facility/FacilityCreation.ts @@ -245,8 +245,7 @@ class FacilityPage { } fillEntryDate(date: string) { - cy.get("#entry_date").click(); - cy.get("#date-input").click().type(date); + cy.clickAndTypeDate("#entry_date", date); } clickEditButton() { diff --git a/cypress/pageobject/Patient/PatientConsultation.ts b/cypress/pageobject/Patient/PatientConsultation.ts index 71a0fbb3909..e0b51b9265e 100644 --- a/cypress/pageobject/Patient/PatientConsultation.ts +++ b/cypress/pageobject/Patient/PatientConsultation.ts @@ -61,7 +61,7 @@ export class PatientConsultationPage { } typePatientConsultationDate(selector: string, date: string) { - cy.get(selector).clear().click().type(date); + cy.clickAndTypeDate(selector, date); } clickPatientDetails() { diff --git a/cypress/pageobject/Patient/PatientCreation.ts b/cypress/pageobject/Patient/PatientCreation.ts index a583844a632..41b3c02977d 100644 --- a/cypress/pageobject/Patient/PatientCreation.ts +++ b/cypress/pageobject/Patient/PatientCreation.ts @@ -52,9 +52,7 @@ export class PatientPage { typePatientDateOfBirth(dateOfBirth: string) { cy.clickAndSelectOption("#patientAge", "DOB"); - cy.get("#date_of_birth").scrollIntoView(); - cy.get("#date_of_birth").should("be.visible").click(); - cy.get("#date-input").click().type(dateOfBirth); + cy.clickAndTypeDate("#date_of_birth", dateOfBirth); } typePatientAge(age: string) { @@ -80,13 +78,11 @@ export class PatientPage { } typeLastMenstruationStartDate(date: string) { - cy.get("#last_menstruation_start_date").click(); - cy.get("#date-input").click().type(date); + cy.clickAndTypeDate("#last_menstruation_start_date", date); } typeDateOfDelivery(date: string) { - cy.get("#date_of_delivery").click(); - cy.get("#date-input").click().type(date); + cy.clickAndTypeDate("#date_of_delivery", date); } clickPermanentAddress() { diff --git a/cypress/pageobject/Patient/PatientTreatmentPlan.ts b/cypress/pageobject/Patient/PatientTreatmentPlan.ts index 0bbddbf70bc..02b2f9b150d 100644 --- a/cypress/pageobject/Patient/PatientTreatmentPlan.ts +++ b/cypress/pageobject/Patient/PatientTreatmentPlan.ts @@ -33,7 +33,7 @@ class PatientTreatmentPlan { } typeProcedureTime(time: string) { - cy.get("#procedure-time").type(time); + cy.clickAndTypeDate("#procedure-time", time); } typeTreatmentPlan(treatment: string) { diff --git a/cypress/pageobject/Shift/ShiftFilters.ts b/cypress/pageobject/Shift/ShiftFilters.ts index 1f824cebbb3..fe20b97bd9f 100644 --- a/cypress/pageobject/Shift/ShiftFilters.ts +++ b/cypress/pageobject/Shift/ShiftFilters.ts @@ -139,18 +139,12 @@ class ShiftingPage { modified_date_end: string, ) { this.createdDateStartInput().click(); - cy.get("[id^='headlessui-popover-panel-'] .care-l-angle-left-b") - .eq(0) - .closest("button") - .click(); + cy.get("[data-test-id='increment-date-range']").click(); cy.get(created_date_start).click(); cy.get(created_date_end).click(); this.modifiedDateStartInput().click(); - cy.get("[id^='headlessui-popover-panel-'] .care-l-angle-left-b") - .eq(0) - .closest("button") - .click(); + cy.get("[data-test-id='increment-date-range']").click(); cy.get(modified_date_start).click(); cy.get(modified_date_end).click(); diff --git a/cypress/pageobject/Users/UserCreation.ts b/cypress/pageobject/Users/UserCreation.ts index 7503ea3fb07..906c07e797a 100644 --- a/cypress/pageobject/Users/UserCreation.ts +++ b/cypress/pageobject/Users/UserCreation.ts @@ -18,8 +18,7 @@ export class UserCreationPage { .type(value); } typeIntoElementByIdPostClearDob(elementId: string, value: string) { - cy.get("#" + elementId).click(); - cy.get("#date-input").clear().type(value); + cy.clickAndTypeDate("#" + elementId, value); } clearIntoElementById(elementId: string) { cy.get("#" + elementId) @@ -54,13 +53,8 @@ export class UserCreationPage { this.selectOptionContainingText(name); } - setInputDate( - dateElementId: string, - inputElementId: string, - dateValue: string, - ) { - this.clickElementById(dateElementId); - this.typeIntoElementById(inputElementId, dateValue); + setInputDate(dateElementId: string, dateValue: string) { + cy.clickAndTypeDate("#" + dateElementId, dateValue); } selectDropdownOption(dropdownId: string, optionText: string) { diff --git a/cypress/pageobject/Users/UserProfilePage.ts b/cypress/pageobject/Users/UserProfilePage.ts index 20fd1911c49..3744c5a5d82 100644 --- a/cypress/pageobject/Users/UserProfilePage.ts +++ b/cypress/pageobject/Users/UserProfilePage.ts @@ -16,9 +16,7 @@ export default class UserProfilePage { } typedate_of_birth(date_of_birth: string) { - //check - cy.get("#date_of_birth").click(); - cy.get("#date-input").clear().type(date_of_birth); + cy.clickAndTypeDate("#date_of_birth", date_of_birth); } selectGender(gender: string) { diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index da78361f6e8..9af5f97e5d4 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -183,10 +183,16 @@ Cypress.Commands.add("selectRadioOption", (name: string, value: string) => { cy.get(`input[type='radio'][name='${name}'][value=${value}]`).click(); }); -Cypress.Commands.add("clickAndTypeDate", (selector: string, date: string) => { +Cypress.Commands.add("clickAndTypeDate", (selector, date) => { cy.get(selector).scrollIntoView(); cy.get(selector).click(); - cy.get("#date-input").click().type(date); + cy.get('[data-test-id="date-input"]:visible [data-time-input]').each((el) => + cy.wrap(el).clear(), + ); + cy.get(`[data-test-id="date-input"]:visible [data-time-input="0"]`) + .click() + .type(date); + cy.get("body").click(0, 0); }); Cypress.Commands.add( diff --git a/src/CAREUI/display/Timeline.tsx b/src/CAREUI/display/Timeline.tsx index eb5f4c56ae5..aa3fd06e7de 100644 --- a/src/CAREUI/display/Timeline.tsx +++ b/src/CAREUI/display/Timeline.tsx @@ -1,14 +1,15 @@ -import { createContext, useContext } from "react"; -import { useTranslation } from "react-i18next"; -import { PerformedByModel } from "@/components/HCX/misc"; -import { classNames, formatName } from "../../Utils/utils"; import CareIcon, { IconName } from "../icons/CareIcon"; +import { classNames, formatName } from "../../Utils/utils"; +import { createContext, useContext } from "react"; + import RecordMeta from "./RecordMeta"; +import { UserBareMinimum } from "@/components/Users/models"; +import { useTranslation } from "react-i18next"; export interface TimelineEvent { type: TType; timestamp: string; - by: PerformedByModel | undefined; + by: UserBareMinimum | undefined; icon: IconName; iconStyle?: string; iconWrapperStyle?: string; diff --git a/src/CAREUI/interactive/SlideOver.tsx b/src/CAREUI/interactive/SlideOver.tsx index bd38b32137a..78982efc39d 100644 --- a/src/CAREUI/interactive/SlideOver.tsx +++ b/src/CAREUI/interactive/SlideOver.tsx @@ -124,7 +124,12 @@ export default function SlideOver({

{title}

-
{children}
+
+ {children} +
)} diff --git a/src/Locale/en.json b/src/Locale/en.json index fa5bdeb6aff..9a7f4d750c2 100644 --- a/src/Locale/en.json +++ b/src/Locale/en.json @@ -316,6 +316,7 @@ "audio__allow_permission": "Please allow microphone permission in site settings", "audio__allow_permission_button": "Click here to know how to allow", "audio__allow_permission_helper": "You might have denied microphone access in the past.", + "audio__permission_message": "Please grant microphone permission to record audio.", "audio__record": "Record Audio", "audio__record_helper": "Click the button to start recording", "audio__recorded": "Audio Recorded", @@ -348,6 +349,7 @@ "bed_type__300": "Oxygen Supported Bed", "bed_type__400": "Isolation Bed", "bed_type__500": "Others", + "beta": "beta", "bladder": "Bladder", "blood_group": "Blood Group", "blood_pressure_error": { @@ -508,6 +510,7 @@ "customer_support_number": "Customer support number", "cylinders": "Cylinders", "cylinders_per_day": "Cylinders/day", + "daily_rounds": "Daily Rounds", "date_and_time": "Date and Time", "date_declared_positive": "Date of declaring positive", "date_of_admission": "Date of Admission", @@ -624,6 +627,7 @@ "error_while_deleting_record": "Error while deleting record", "escape": "Escape", "estimated_contact_date": "Estimated contact date", + "events": "Events", "expand_sidebar": "Expand Sidebar", "expected_burn_rate": "Expected Burn Rate", "expired_on": "Expired On", @@ -808,6 +812,8 @@ "max_dosage_24_hrs": "Max. dosage in 24 hrs.", "max_dosage_in_24hrs_gte_base_dosage_error": "Max. dosage in 24 hours must be greater than or equal to base dosage", "max_size_for_image_uploaded_should_be": "Max size for image uploaded should be", + "measured_after": "Measured after", + "measured_before": "Measured before", "medical_council_registration": "Medical Council Registration", "medical_worker": "Medical Worker", "medicine": "Medicine", @@ -1156,6 +1162,7 @@ "summary": "Summary", "support": "Support", "switch": "Switch", + "switch_camera_is_not_available": "Switch camera is not available.", "systolic": "Systolic", "tachycardia": "Tachycardia", "target_dosage": "Target Dosage", @@ -1165,6 +1172,7 @@ "to_be_conducted": "To be conducted", "total_amount": "Total Amount", "total_beds": "Total Beds", + "total_staff": "Total Staff", "total_users": "Total Users", "transfer_in_progress": "TRANSFER IN PROGRESS", "transfer_to_receiving_facility": "Transfer to receiving facility", diff --git a/src/PluginEngine.tsx b/src/PluginEngine.tsx index 43b8e558f8f..a56fe21b5b8 100644 --- a/src/PluginEngine.tsx +++ b/src/PluginEngine.tsx @@ -1,8 +1,8 @@ +import { CareAppsContext, useCareApps } from "@/common/hooks/useCareApps"; /* eslint-disable i18next/no-literal-string */ import React, { Suspense } from "react"; -import { CareAppsContext, useCareApps } from "@/common/hooks/useCareApps"; -import { pluginMap } from "./pluginTypes"; -import { UserAssignedModel } from "@/components/Users/models"; +import { SupportedPluginComponents, pluginMap } from "./pluginTypes"; + import ErrorBoundary from "@/components/Common/ErrorBoundary"; export default function PluginEngine({ @@ -27,25 +27,28 @@ export default function PluginEngine({ ); } -export function PLUGIN_DoctorConnectButtons({ - user, -}: { - user: UserAssignedModel; -}) { +type PluginProps = + React.ComponentProps; + +export function PLUGIN_Component({ + __name, + ...props +}: { __name: K } & PluginProps) { const plugins = useCareApps(); + return ( -
- {plugins.map((plugin, index) => { - const DoctorConnectButtons = plugin.components.DoctorConnectButtons; - if (!DoctorConnectButtons) { + <> + {plugins.map((plugin) => { + const Component = plugin.components[ + __name + ] as React.ComponentType; + + if (!Component) { return null; } - return ( -
- -
- ); + + return ; })} -
+ ); } diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 22f0285d22d..a2794e35203 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -17,6 +17,7 @@ import { import { BedModel, CapacityModal, + CommentModel, ConsultationModel, CreateBedBody, CurrentBed, @@ -41,6 +42,8 @@ import { PatientNotesModel, PatientStatsModel, PatientTransferResponse, + ResourceModel, + ShiftingModel, StateModel, WardModel, } from "@/components/Facility/models"; @@ -50,7 +53,6 @@ import { SampleReportModel, SampleTestModel, } from "@/components/Patient/models"; -import { IComment, IResource } from "@/components/Resource/models"; import { IDeleteBedCapacity, ILocalBodies, @@ -70,13 +72,8 @@ import { NotificationData, PNconfigData, } from "@/components/Notifications/models"; -import { - HCXClaimModel, - HCXCommunicationModel, - HCXPolicyModel, -} from "@/components/HCX/models"; +import { HCXPolicyModel } from "@/components/HCX/models"; import { ICD11DiagnosisModel } from "@/components/Diagnosis/types"; -import { IShift } from "@/components/Shifting/models"; import { Investigation } from "@/components/Facility/Investigations/Reports/types"; import { PaginatedResponse } from "../Utils/request/types"; import { @@ -103,7 +100,6 @@ import { IHealthFacility, IpartialUpdateHealthFacilityTBody, } from "@/components/ABDM/types/health-facility"; -import { PMJAYPackageItem } from "@/components/Common/PMJAYProcedurePackageAutocomplete"; import { InsurerOptionModel } from "@/components/HCX/InsurerAutocomplete"; /** @@ -1059,14 +1055,14 @@ const routes = { createShift: { path: "/api/v1/shift/", method: "POST", - TBody: Type>(), + TBody: Type>(), TRes: Type(), }, updateShift: { path: "/api/v1/shift/{id}/", method: "PUT", - TBody: Type(), - TRes: Type(), + TBody: Type(), + TRes: Type(), }, deleteShiftRecord: { path: "/api/v1/shift/{id}/", @@ -1076,17 +1072,17 @@ const routes = { listShiftRequests: { path: "/api/v1/shift/", method: "GET", - TRes: Type>(), + TRes: Type>(), }, getShiftDetails: { path: "/api/v1/shift/{id}/", method: "GET", - TRes: Type(), + TRes: Type(), }, completeTransfer: { path: "/api/v1/shift/{externalId}/transfer/", method: "POST", - TBody: Type(), + TBody: Type(), TRes: Type>(), }, downloadShiftRequests: { @@ -1097,13 +1093,13 @@ const routes = { getShiftComments: { path: "/api/v1/shift/{id}/comment/", method: "GET", - TRes: Type>(), + TRes: Type>(), }, addShiftComments: { path: "/api/v1/shift/{id}/comment/", method: "POST", - TBody: Type>(), - TRes: Type(), + TBody: Type>(), + TRes: Type(), }, // Notifications @@ -1235,14 +1231,14 @@ const routes = { createResource: { path: "/api/v1/resource/", method: "POST", - TRes: Type(), - TBody: Type>(), + TRes: Type(), + TBody: Type>(), }, updateResource: { path: "/api/v1/resource/{id}/", method: "PUT", - TRes: Type(), - TBody: Type>(), + TRes: Type(), + TBody: Type>(), }, deleteResourceRecord: { path: "/api/v1/resource/{id}/", @@ -1254,12 +1250,12 @@ const routes = { listResourceRequests: { path: "/api/v1/resource/", method: "GET", - TRes: Type>(), + TRes: Type>(), }, getResourceDetails: { path: "/api/v1/resource/{id}/", method: "GET", - TRes: Type(), + TRes: Type(), }, downloadResourceRequests: { path: "/api/v1/resource/", @@ -1269,13 +1265,13 @@ const routes = { getResourceComments: { path: "/api/v1/resource/{id}/comment/", method: "GET", - TRes: Type>(), + TRes: Type>(), }, addResourceComments: { path: "/api/v1/resource/{id}/comment/", method: "POST", - TRes: Type(), - TBody: Type>(), + TRes: Type(), + TBody: Type>(), }, // Assets endpoints @@ -1695,117 +1691,6 @@ const routes = { TRes: Type(), }, }, - - claims: { - list: { - path: "/api/hcx/claim/", - method: "GET", - TRes: Type>(), - }, - - create: { - path: "/api/hcx/claim/", - method: "POST", - TBody: Type<{ - policy: string; - items: { - id: string; - price: number; - category?: string; - name: string; - }[]; - consultation: string; - use: "preauthorization" | "claim"; - }>(), - TRes: Type(), - }, - - get: { - path: "/api/hcx/claim/{external_id}/", - method: "GET", - }, - - update: { - path: "/api/hcx/claim/{external_id}/", - method: "PUT", - }, - - partialUpdate: { - path: "/api/hcx/claim/{external_id}/", - method: "PATCH", - }, - - delete: { - path: "/api/hcx/claim/{external_id}/", - method: "DELETE", - }, - - listPMJYPackages: { - path: "/api/hcx/pmjy_packages/", - method: "GET", - TRes: Type(), - }, - - makeClaim: { - path: "/api/hcx/make_claim/", - method: "POST", - TBody: Type<{ claim: string }>(), - TRes: Type(), - }, - }, - - communications: { - list: { - path: "/api/hcx/communication/", - method: "GET", - TRes: Type>(), - }, - - create: { - path: "/api/hcx/communication/", - method: "POST", - TRes: Type(), - TBody: Type<{ - claim: string; - content: { - type: string; - data: string; - }[]; - }>(), - }, - - get: { - path: "/api/hcx/communication/{external_id}/", - method: "GET", - TRes: Type(), - }, - - update: { - path: "/api/hcx/communication/{external_id}/", - method: "PUT", - TRes: Type(), - }, - - partialUpdate: { - path: "/api/hcx/communication/{external_id}/", - method: "PATCH", - TRes: Type(), - }, - - delete: { - path: "/api/hcx/communication/{external_id}/", - method: "DELETE", - }, - - send: { - path: "/api/hcx/send_communication/", - method: "POST", - TRes: Type(), - TBody: Type<{ - communication: string; - }>(), - }, - }, }, } as const; diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx index 5a7b2cbb312..7275c7c2860 100644 --- a/src/Routers/AppRouter.tsx +++ b/src/Routers/AppRouter.tsx @@ -1,32 +1,30 @@ -import { useRedirect, useRoutes, usePath, Redirect } from "raviger"; -import { useState, useEffect } from "react"; - -import ShowPushNotification from "@/components/Notifications/ShowPushNotification"; -import { NoticeBoard } from "@/components/Notifications/NoticeBoard"; -import Error404 from "@/components/ErrorPages/404"; import { DesktopSidebar, MobileSidebar, SIDEBAR_SHRINK_PREFERENCE_KEY, SidebarShrinkContext, } from "@/components/Common/Sidebar/Sidebar"; +import { Redirect, usePath, useRedirect, useRoutes } from "raviger"; +import { useEffect, useState } from "react"; + +import ABDMFacilityRecords from "@/components/ABDM/ABDMFacilityRecords"; +import AssetRoutes from "./routes/AssetRoutes"; import { BLACKLISTED_PATHS } from "@/common/constants"; -import SessionExpired from "@/components/ErrorPages/SessionExpired"; +import ConsultationRoutes from "./routes/ConsultationRoutes"; +import Error404 from "@/components/ErrorPages/404"; +import FacilityRoutes from "./routes/FacilityRoutes"; import HealthInformation from "@/components/ABDM/HealthInformation"; -import ABDMFacilityRecords from "@/components/ABDM/ABDMFacilityRecords"; - -import UserRoutes from "./routes/UserRoutes"; +import IconIndex from "../CAREUI/icons/Index"; +import { NoticeBoard } from "@/components/Notifications/NoticeBoard"; import PatientRoutes from "./routes/PatientRoutes"; +import ResourceRoutes from "./routes/ResourceRoutes"; import SampleRoutes from "./routes/SampleRoutes"; -import FacilityRoutes from "./routes/FacilityRoutes"; -import ConsultationRoutes from "./routes/ConsultationRoutes"; -import HCXRoutes from "./routes/HCXRoutes"; +import SessionExpired from "@/components/ErrorPages/SessionExpired"; import ShiftingRoutes from "./routes/ShiftingRoutes"; -import AssetRoutes from "./routes/AssetRoutes"; -import ResourceRoutes from "./routes/ResourceRoutes"; -import { usePluginRoutes } from "@/common/hooks/useCareApps"; +import ShowPushNotification from "@/components/Notifications/ShowPushNotification"; +import UserRoutes from "./routes/UserRoutes"; import careConfig from "@careConfig"; -import IconIndex from "../CAREUI/icons/Index"; +import { usePluginRoutes } from "@/common/hooks/useCareApps"; export type RouteParams = T extends `${string}:${infer Param}/${infer Rest}` @@ -78,16 +76,12 @@ export default function AppRouter() { let routes = Routes; - if (careConfig.hcx.enabled) { - routes = { ...HCXRoutes, ...routes }; - } - useRedirect("/user", "/users"); // Merge in Plugin Routes routes = { - ...routes, ...pluginRoutes, + ...routes, }; const pages = useRoutes(routes) || ; diff --git a/src/Routers/routes/HCXRoutes.tsx b/src/Routers/routes/HCXRoutes.tsx deleted file mode 100644 index 00339161a24..00000000000 --- a/src/Routers/routes/HCXRoutes.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import ConsultationClaims from "@/components/Facility/ConsultationClaims"; -import { AppRoutes } from "../AppRouter"; - -const HCXRoutes: AppRoutes = { - "/facility/:facilityId/patient/:patientId/consultation/:consultationId/claims": - ({ facilityId, patientId, consultationId }) => ( - - ), -}; - -export default HCXRoutes; diff --git a/src/Routers/routes/ResourceRoutes.tsx b/src/Routers/routes/ResourceRoutes.tsx index a1cc70f9aa7..24e60ec5ad9 100644 --- a/src/Routers/routes/ResourceRoutes.tsx +++ b/src/Routers/routes/ResourceRoutes.tsx @@ -1,9 +1,9 @@ import ResourceDetails from "@/components/Resource/ResourceDetails"; import { ResourceDetailsUpdate } from "@/components/Resource/ResourceDetailsUpdate"; -import ListView from "@/components/Resource/ListView"; -import BoardView from "@/components/Resource/ResourceBoardView"; import { Redirect } from "raviger"; import { AppRoutes } from "../AppRouter"; +import BoardView from "@/components/Resource/ResourceBoard"; +import ListView from "@/components/Resource/ResourceList"; const getDefaultView = () => localStorage.getItem("defaultResourceView") === "list" ? "list" : "board"; diff --git a/src/Routers/routes/ShiftingRoutes.tsx b/src/Routers/routes/ShiftingRoutes.tsx index c4a3235857a..b5448cc527f 100644 --- a/src/Routers/routes/ShiftingRoutes.tsx +++ b/src/Routers/routes/ShiftingRoutes.tsx @@ -1,8 +1,8 @@ import { ShiftCreate } from "@/components/Patient/ShiftCreate"; import ShiftDetails from "@/components/Shifting/ShiftDetails"; import { ShiftDetailsUpdate } from "@/components/Shifting/ShiftDetailsUpdate"; -import ListView from "@/components/Shifting/ListView"; -import BoardView from "@/components/Shifting/BoardView"; +import ListView from "@/components/Shifting/ShiftingList"; +import BoardView from "@/components/Shifting/ShiftingBoard"; import { Redirect } from "raviger"; import { AppRoutes } from "../AppRouter"; diff --git a/src/Utils/Notifications.js b/src/Utils/Notifications.js index 87de7991a6e..ce57ab5edd9 100644 --- a/src/Utils/Notifications.js +++ b/src/Utils/Notifications.js @@ -1,6 +1,6 @@ import { alert, Stack, defaultModules } from "@pnotify/core"; import * as PNotifyMobile from "@pnotify/mobile"; -import _ from "lodash-es"; +import { startCase, camelCase } from "lodash-es"; defaultModules.set(PNotifyMobile, {}); @@ -44,7 +44,7 @@ const notifyError = (error) => { errorMsg = error.detail; } else { for (let [key, value] of Object.entries(error)) { - let keyName = _.startCase(_.camelCase(key)); + let keyName = startCase(camelCase(key)); if (Array.isArray(value)) { const uniques = [...new Set(value)]; errorMsg += `${keyName} - ${uniques.splice(0, 5).join(", ")}`; diff --git a/src/Utils/types.ts b/src/Utils/types.ts index 5ac29c5862c..71ddd41330e 100644 --- a/src/Utils/types.ts +++ b/src/Utils/types.ts @@ -1,11 +1,11 @@ -import { PerformedByModel } from "@/components/HCX/misc"; +import { UserBareMinimum } from "@/components/Users/models"; export interface BaseModel { readonly id: string; readonly modified_date: string; readonly created_date: string; - readonly created_by: PerformedByModel; - readonly updated_by: PerformedByModel; + readonly created_by: UserBareMinimum; + readonly updated_by: UserBareMinimum; } export type Writable = { diff --git a/src/Utils/useRecorder.js b/src/Utils/useRecorder.js index 446f824259f..8ad90ebe8cb 100644 --- a/src/Utils/useRecorder.js +++ b/src/Utils/useRecorder.js @@ -2,13 +2,14 @@ import { useEffect, useState } from "react"; import { Error } from "./Notifications"; +import { useTranslation } from "react-i18next"; const useRecorder = (handleMicPermission) => { const [audioURL, setAudioURL] = useState(""); const [isRecording, setIsRecording] = useState(false); const [recorder, setRecorder] = useState(null); const [newBlob, setNewBlob] = useState(null); - + const { t } = useTranslation(); useEffect(() => { if (!isRecording && recorder && audioURL) { setRecorder(null); @@ -26,7 +27,7 @@ const useRecorder = (handleMicPermission) => { }, () => { Error({ - msg: "Please grant microphone permission to record audio.", + msg: t("audio__permission_message"), }); setIsRecording(false); handleMicPermission(false); diff --git a/src/Utils/useSegmentedRecorder.ts b/src/Utils/useSegmentedRecorder.ts index 9434ea8383c..fe38afb3b06 100644 --- a/src/Utils/useSegmentedRecorder.ts +++ b/src/Utils/useSegmentedRecorder.ts @@ -1,11 +1,13 @@ import { useState, useEffect } from "react"; import * as Notify from "./Notifications"; +import { useTranslation } from "react-i18next"; const useSegmentedRecording = () => { const [isRecording, setIsRecording] = useState(false); const [recorder, setRecorder] = useState(null); const [audioBlobs, setAudioBlobs] = useState([]); const [restart, setRestart] = useState(false); + const { t } = useTranslation(); const bufferInterval = 1 * 1000; const splitSizeLimit = 20 * 1000000; // 20MB @@ -28,7 +30,7 @@ const useSegmentedRecording = () => { }, () => { Notify.Error({ - msg: "Please grant microphone permission to record audio.", + msg: t("audio__permission_message"), }); setIsRecording(false); }, diff --git a/src/common/constants.tsx b/src/common/constants.tsx index 252d5cb22eb..87432fcaadb 100644 --- a/src/common/constants.tsx +++ b/src/common/constants.tsx @@ -236,7 +236,7 @@ export const DOCTOR_SPECIALIZATION: Array = [ { id: 16, text: "General Surgeon" }, { id: 17, text: "Geriatrician" }, { id: 18, text: "Hematologist" }, - { id: 29, text: "Immunologist" }, + { id: 19, text: "Immunologist" }, { id: 20, text: "Infectious Disease Specialist" }, { id: 21, text: "MBBS doctor" }, { id: 22, text: "Medical Officer" }, diff --git a/src/components/ABDM/FetchRecordsModal.tsx b/src/components/ABDM/FetchRecordsModal.tsx index 34d70255846..441bee0ddbd 100644 --- a/src/components/ABDM/FetchRecordsModal.tsx +++ b/src/components/ABDM/FetchRecordsModal.tsx @@ -193,7 +193,6 @@ export default function FetchRecordsModal({ abha, show, onClose }: IProps) { label={t("consent_request__expiry")} required disablePast - position="TOP-RIGHT" />
diff --git a/src/components/Assets/AssetServiceEditModal.tsx b/src/components/Assets/AssetServiceEditModal.tsx index 9accf5c3d66..fecea8ad124 100644 --- a/src/components/Assets/AssetServiceEditModal.tsx +++ b/src/components/Assets/AssetServiceEditModal.tsx @@ -196,7 +196,6 @@ export const AssetServiceEditModal = (props: { label={t("serviced_on")} name="serviced_on" className="mt-2" - position="LEFT" value={new Date(form.serviced_on)} max={new Date(props.service_record.created_date)} onChange={(date) => { diff --git a/src/components/Assets/AssetTypes.tsx b/src/components/Assets/AssetTypes.tsx index 2f8e086a813..24f3f3e9352 100644 --- a/src/components/Assets/AssetTypes.tsx +++ b/src/components/Assets/AssetTypes.tsx @@ -1,7 +1,7 @@ -import { IconName } from "../../CAREUI/icons/CareIcon"; import { BedModel } from "../Facility/models"; -import { PerformedByModel } from "../HCX/misc"; +import { IconName } from "../../CAREUI/icons/CareIcon"; import { PatientModel } from "../Patient/models"; +import { UserBareMinimum } from "@/components/Users/models"; export enum AssetLocationType { OTHER = "OTHER", @@ -169,7 +169,7 @@ export interface AssetServiceEdit { serviced_on: string; note: string; edited_on: string; - edited_by: PerformedByModel; + edited_by: UserBareMinimum; } export interface AssetService { id: string; diff --git a/src/components/CameraFeed/routes.ts b/src/components/CameraFeed/routes.ts index db983d6a383..bffcb0fc9b0 100644 --- a/src/components/CameraFeed/routes.ts +++ b/src/components/CameraFeed/routes.ts @@ -1,9 +1,10 @@ -import { Type } from "../../Redux/api"; +import { OperationAction, PTZPayload } from "./useOperateCamera"; + +import { AssetBedModel } from "../Assets/AssetTypes"; import { PaginatedResponse } from "../../Utils/request/types"; +import { Type } from "../../Redux/api"; +import { UserBareMinimum } from "@/components/Users/models"; import { WritableOnly } from "../../Utils/types"; -import { AssetBedModel } from "../Assets/AssetTypes"; -import { PerformedByModel } from "../HCX/misc"; -import { OperationAction, PTZPayload } from "./useOperateCamera"; export type GetStatusResponse = { result: { @@ -32,8 +33,8 @@ export type CameraPreset = { name: string; readonly asset_bed: AssetBedModel; position: PTZPayload; - readonly created_by: PerformedByModel; - readonly updated_by: PerformedByModel; + readonly created_by: UserBareMinimum; + readonly updated_by: UserBareMinimum; readonly created_date: string; readonly modified_date: string; readonly is_migrated: boolean; diff --git a/src/components/Common/Avatar.tsx b/src/components/Common/Avatar.tsx index 1d20c3ad18f..7a7ded17265 100644 --- a/src/components/Common/Avatar.tsx +++ b/src/components/Common/Avatar.tsx @@ -78,6 +78,7 @@ const Avatar: React.FC = ({ xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 100 100" + className="aspect-square h-full w-full object-cover" > { + setSelectedFile(undefined); + setPreview(undefined); + setPreviewImage(null); setIsCaptureImgBeingUploaded(false); setIsProcessing(false); }); diff --git a/src/components/Common/DateInputV2.tsx b/src/components/Common/DateInputV2.tsx index 37f67c8ad80..87a29debe5b 100644 --- a/src/components/Common/DateInputV2.tsx +++ b/src/components/Common/DateInputV2.tsx @@ -1,4 +1,4 @@ -import { MutableRefObject, useEffect, useState } from "react"; +import { MutableRefObject, useEffect, useRef, useState } from "react"; import CareIcon from "../../CAREUI/icons/CareIcon"; import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react"; @@ -6,15 +6,9 @@ import { classNames } from "../../Utils/utils"; import dayjs from "../../Utils/dayjs"; import * as Notification from "../../Utils/Notifications"; import { t } from "i18next"; +import DateTextInput from "./DateTextInput"; type DatePickerType = "date" | "month" | "year"; -export type DatePickerPosition = - | "LEFT" - | "RIGHT" - | "CENTER" - | "TOP-LEFT" - | "TOP-RIGHT" - | "TOP-CENTER"; interface Props { id?: string; @@ -25,12 +19,13 @@ interface Props { min?: Date; max?: Date; outOfLimitsErrorMessage?: string; - onChange: (date: Date) => void; - position?: DatePickerPosition; + onChange: (date: Date | undefined) => void; disabled?: boolean; placeholder?: string; isOpen?: boolean; setIsOpen?: (isOpen: boolean) => void; + allowTime?: boolean; + popOverClassName?: string; } const DAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]; @@ -45,21 +40,42 @@ const DateInputV2: React.FC = ({ max, outOfLimitsErrorMessage, onChange, - position = "CENTER", disabled, placeholder, - isOpen, setIsOpen, + allowTime, + isOpen, + popOverClassName, }) => { const [dayCount, setDayCount] = useState>([]); const [blankDays, setBlankDays] = useState>([]); - const [datePickerHeaderDate, setDatePickerHeaderDate] = useState(new Date()); + const [datePickerHeaderDate, setDatePickerHeaderDate] = useState( + value || new Date(), + ); const [type, setType] = useState("date"); const [year, setYear] = useState(new Date()); - const [displayValue, setDisplayValue] = useState( - value ? dayjs(value).format("DDMMYYYY") : "", - ); + + const [popOverOpen, setPopOverOpen] = useState(false); + + const hours = dayjs(value).hour() % 12; + const minutes = dayjs(value).minute(); + const ampm = dayjs(value).hour() > 11 ? "PM" : "AM"; + + const hourScrollerRef = useRef(null); + const minuteScrollerRef = useRef(null); + + const popoverButtonRef = useRef(null); + + const getDayStart = (date: Date) => { + const newDate = new Date(date); + newDate.setHours(0, 0, 0, 0); + return newDate; + }; + + const handleChange = (date: Date) => { + onChange(allowTime ? date : getDayStart(date)); + }; const decrement = () => { switch (type) { @@ -97,14 +113,6 @@ const DateInputV2: React.FC = ({ } }; - const isSelectedDate = (date: number) => { - if (value) { - return dayjs( - new Date(value.getFullYear(), value.getMonth(), date), - ).isSame(dayjs(value)); - } - }; - type CloseFunction = ( focusableElement?: HTMLElement | MutableRefObject, ) => void; @@ -112,21 +120,43 @@ const DateInputV2: React.FC = ({ const setDateValue = (date: number, close: CloseFunction) => () => { isDateWithinConstraints(date) ? (() => { - onChange( + handleChange( new Date( datePickerHeaderDate.getFullYear(), datePickerHeaderDate.getMonth(), date, + datePickerHeaderDate.getHours(), + datePickerHeaderDate.getMinutes(), + datePickerHeaderDate.getSeconds(), ), ); - close(); - setIsOpen?.(false); + if (!allowTime) { + close(); + setIsOpen?.(false); + } })() : Notification.Error({ msg: outOfLimitsErrorMessage ?? "Cannot select date out of range", }); }; + const handleTimeChange = (options: { + newHours?: typeof hours; + newMinutes?: typeof minutes; + newAmpm?: typeof ampm; + }) => { + const { newHours = hours, newMinutes = minutes, newAmpm = ampm } = options; + handleChange( + new Date( + datePickerHeaderDate.getFullYear(), + datePickerHeaderDate.getMonth(), + datePickerHeaderDate.getDate(), + newAmpm === "PM" ? (newHours % 12) + 12 : newHours % 12, + newMinutes, + ), + ); + }; + const getDayCount = (date: Date) => { const daysInMonth = dayjs(date).daysInMonth(); @@ -165,22 +195,6 @@ const DateInputV2: React.FC = ({ return true; }; - const isDateWithinLimits = (parsedDate: dayjs.Dayjs): boolean => { - if (parsedDate?.isValid()) { - if ( - (max && parsedDate.toDate() > max) || - (min && parsedDate.toDate() < min) - ) { - Notification.Error({ - msg: outOfLimitsErrorMessage ?? "Cannot select date out of range", - }); - return false; - } - return true; - } - return false; - }; - const isSelectedMonth = (month: number) => month === datePickerHeaderDate.getMonth(); @@ -209,35 +223,58 @@ const DateInputV2: React.FC = ({ setType("date"); }; - const showMonthPicker = () => setType("month"); - - const showYearPicker = () => setType("year"); - useEffect(() => { getDayCount(datePickerHeaderDate); }, [datePickerHeaderDate]); + const scrollTime = (smooth: boolean = true) => { + const timeScrollers = [hourScrollerRef, minuteScrollerRef]; + timeScrollers.forEach((scroller) => { + if (!scroller.current) return; + const selected = scroller.current.querySelector("[data-selected=true]"); + if (selected) { + const selectedPosition = ( + selected as HTMLDivElement + ).getBoundingClientRect().top; + + const toScroll = + selectedPosition - scroller.current.getBoundingClientRect().top; + + selected.parentElement?.scrollBy({ + top: toScroll, + behavior: smooth ? "smooth" : "instant", + }); + } + }); + }; + useEffect(() => { - value && setDatePickerHeaderDate(new Date(value)); + value && setDatePickerHeaderDate(value); + scrollTime(); }, [value]); + useEffect(() => { + if (!popOverOpen) return; + scrollTime(false); + }, [popOverOpen]); + + useEffect(() => { + isOpen && popoverButtonRef.current?.click(); + }, [isOpen]); + + const dateFormat = `DD/MM/YYYY${allowTime ? " hh:mm a" : ""}`; + const getPosition = () => { - switch (position) { - case "LEFT": - return "left-0"; - case "RIGHT": - return "right-0 transform translate-x-1/2"; - case "CENTER": - return "transform -translate-x-1/2"; - case "TOP-LEFT": - return "bottom-full left-full"; - case "TOP-RIGHT": - return "bottom-full right-0"; - case "TOP-CENTER": - return "bottom-full left-1/2 transform -translate-x-1/2"; - default: - return "left-0"; - } + const viewportWidth = document.documentElement.clientWidth; + const viewportHeight = document.documentElement.clientHeight; + + const popOverX = popoverButtonRef.current?.getBoundingClientRect().x || 0; + const popOverY = popoverButtonRef.current?.getBoundingClientRect().y || 0; + + const right = popOverX > viewportWidth - (allowTime ? 420 : 300); + const top = popOverY > viewportHeight - 400; + + return `${right ? "md:-translate-x-1/2" : ""} ${top ? "md:-translate-y-[calc(100%+50px)]" : ""}`; }; return ( @@ -246,238 +283,357 @@ const DateInputV2: React.FC = ({ className={`${containerClassName ?? "container mx-auto text-black"}`} > - {({ open, close }) => ( -
- - - { + setPopOverOpen(open); + return ( +
+ -
- -
-
- - {(open || isOpen) && ( - -
+ +
+ +
+ + {open && ( + - - [dd, mm, yyyy].filter(Boolean).join("/"), - ) || "" - } // Display the value in DD/MM/YYYY format - placeholder={t("DD/MM/YYYY")} - onChange={(e) => { - setDisplayValue(e.target.value.replaceAll("/", "")); - const value = dayjs(e.target.value, "DD/MM/YYYY", true); - if (isDateWithinLimits(value)) { - onChange(value.toDate()); - close(); - setIsOpen?.(false); +
+ close()} + error={ + value && + (!dayjs(value).isValid() || + (!!max && value > max) || + (!!min && value < min)) + ? "Cannot select date out of range" + : undefined } - }} - /> -
-
- - -
- {type === "date" && ( -
+ +
+
+
+
- )} -
-

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

-
-
- -
+ + - {type === "date" && ( - <> -
- {DAYS.map((day, i) => ( +
+ {type === "date" && ( +
setType("month")} + className="cursor-pointer rounded px-3 py-1 text-center font-medium text-black hover:bg-secondary-300" + > + {dayjs(datePickerHeaderDate).format("MMMM")} +
+ )}
setType("year")} + className="cursor-pointer rounded px-3 py-1 font-medium text-black hover:bg-secondary-300" > -
- {day} -
+

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

- ))} -
-
- {blankDays.map((_, i) => ( -
- ))} - {dayCount.map((d, i) => { - const withinConstraints = - isDateWithinConstraints(d); - const selected = value && isSelectedDate(d); - - const baseClasses = - "flex h-full items-center justify-center rounded text-center text-sm leading-loose transition duration-100 ease-in-out"; - let conditionalClasses = ""; - - if (withinConstraints) { - if (selected) { - conditionalClasses = - "bg-primary-500 font-bold text-white"; - } else { - conditionalClasses = - "hover:bg-secondary-300 cursor-pointer"; - } - } else { - conditionalClasses = - "!cursor-not-allowed !text-secondary-400"; +
+ +
+ + {type === "date" && ( + <> +
+ {DAYS.map((day, i) => (
- {d} +
+ {day} +
-
- ); - })} -
- - )} - {type === "month" && ( -
- {Array(12) - .fill(null) - .map((_, i) => ( + ))} +
+
+ {blankDays.map((_, i) => ( +
+ ))} + {dayCount.map((d, i) => { + const withinConstraints = + isDateWithinConstraints(d); + let selected; + if (value) { + const newDate = new Date( + datePickerHeaderDate, + ); + newDate.setDate(d); + selected = + value.toDateString() === + newDate.toDateString(); + } + + const baseClasses = + "flex h-full items-center justify-center rounded text-center text-sm leading-loose transition duration-100 ease-in-out"; + let conditionalClasses = ""; + + if (withinConstraints) { + if (selected) { + conditionalClasses = + "bg-primary-500 font-bold text-white"; + } else { + conditionalClasses = + "hover:bg-secondary-300 cursor-pointer"; + } + } else { + conditionalClasses = + "!cursor-not-allowed !text-secondary-400"; + } + return ( +
+ +
+ ); + })} +
+ + )} + {type === "month" && ( +
+ {Array(12) + .fill(null) + .map((_, i) => ( +
+ {dayjs( + new Date( + datePickerHeaderDate.getFullYear(), + i, + 1, + ), + ).format("MMM")} +
+ ))} +
+ )} + {type === "year" && ( +
+ {Array(12) + .fill(null) + .map((_, i) => { + const y = year.getFullYear() - 11 + i; + return ( +
+ {y} +
+ ); + })} +
+ )} +
+ {allowTime && ( +
+ {( + [ + { + name: "Hours", + value: hours, + options: Array.from( + { length: 12 }, + (_, i) => i + 1, + ), + onChange: (val: any) => { + handleTimeChange({ + newHours: val, + }); + }, + ref: hourScrollerRef, + }, + { + name: "Minutes", + value: minutes, + options: Array.from( + { length: 60 }, + (_, i) => i, + ), + onChange: (val: any) => { + handleTimeChange({ + newMinutes: val, + }); + }, + ref: minuteScrollerRef, + }, + { + name: "am/pm", + value: ampm, + options: ["AM", "PM"], + onChange: (val: any) => { + handleTimeChange({ + newAmpm: val, + }); + }, + ref: undefined, + }, + ] as const + ).map((input, i) => (
{ + const optionsHeight = + e.currentTarget.scrollHeight / 3; + const scrollTop = e.currentTarget.scrollTop; + const containerHeight = + e.currentTarget.clientHeight; + if (scrollTop >= optionsHeight * 2) { + e.currentTarget.scrollTo({ + top: optionsHeight, + }); + } + if ( + scrollTop + containerHeight <= + optionsHeight + ) { + e.currentTarget.scrollTo({ + top: optionsHeight + scrollTop, + }); + } + }} > - {dayjs( - new Date( - datePickerHeaderDate.getFullYear(), - i, - 1, - ), - ).format("MMM")} + {[ + ...input.options, + ...(input.name === "am/pm" + ? [] + : input.options), + ...(input.name === "am/pm" + ? [] + : input.options), + ].map((option, j) => ( + + ))}
))} -
- )} - {type === "year" && ( -
- {Array(12) - .fill(null) - .map((_, i) => { - const y = year.getFullYear() - 11 + i; - return ( -
- {y} -
- ); - })} -
- )} +
+ )} +
-
- - )} -
- )} +
+ )} +
+ ); + }}
diff --git a/src/components/Common/DateRangeInputV2.tsx b/src/components/Common/DateRangeInputV2.tsx index 0c3a7559ff9..aa426ad0cb2 100644 --- a/src/components/Common/DateRangeInputV2.tsx +++ b/src/components/Common/DateRangeInputV2.tsx @@ -14,6 +14,7 @@ type Props = { disabled?: boolean; max?: Date; min?: Date; + allowTime?: boolean; }; const DateRangeInputV2 = ({ value, onChange, ...props }: Props) => { @@ -33,9 +34,9 @@ const DateRangeInputV2 = ({ value, onChange, ...props }: Props) => { }} min={props.min} max={end || props.max} - position="RIGHT" placeholder="Start date" disabled={props.disabled} + allowTime={props.allowTime} />
@@ -46,11 +47,11 @@ const DateRangeInputV2 = ({ value, onChange, ...props }: Props) => { onChange={(end) => onChange({ start, end })} min={start || props.min} max={props.max} - position="CENTER" disabled={props.disabled || !start} placeholder="End date" isOpen={showEndPicker} setIsOpen={setShowEndPicker} + allowTime={props.allowTime} />
diff --git a/src/components/Common/DateTextInput.tsx b/src/components/Common/DateTextInput.tsx new file mode 100644 index 00000000000..38e13b23283 --- /dev/null +++ b/src/components/Common/DateTextInput.tsx @@ -0,0 +1,203 @@ +import CareIcon from "@/CAREUI/icons/CareIcon"; +import { classNames } from "@/Utils/utils"; +import dayjs from "dayjs"; +import { Fragment, KeyboardEvent, useEffect, useState } from "react"; + +/** + * DateTextInput component. + * + * @param {Object} props - Component properties. + * @param {boolean} props.allowTime - If true, shows time input fields (hour and minute). + * @param {Date} props.value - The current date value. + * @param {function(Date):void} props.onChange - Callback function when date value changes. + * @param {function():void} props.onFinishInitialTyping - Callback function when a user successfuly types in the date on the first input + * @param {String} props.error - Shows an error if specified + * + * @returns {JSX.Element} The date text input component. + */ +export default function DateTextInput(props: { + allowTime: boolean; + value?: Date; + onChange: (date: Date | undefined) => unknown; + onFinishInitialTyping?: () => unknown; + error?: string; +}) { + const { value, onChange, allowTime, error, onFinishInitialTyping } = props; + + const [editingText, setDirtyEditingText] = useState({ + date: `${value ? value?.getDate() : ""}`, + month: `${value ? value.getMonth() + 1 : ""} `, + year: `${value ? value.getFullYear() : ""}`, + hour: `${value ? value.getHours() : ""}`, + minute: `${value ? value.getMinutes() : ""}`, + }); + + const setEditingText = (et: typeof editingText) => { + setDirtyEditingText(et); + const newDate = new Date( + parseInt(et.year), + parseInt(et.month) - 1, + parseInt(et.date), + allowTime ? parseInt(et.hour) : 0, + allowTime ? parseInt(et.minute) : 0, + ); + if (et.year.length > 3 && dayjs(newDate).isValid()) { + if (!value && !allowTime) onFinishInitialTyping?.(); + if (!value && allowTime && et.minute.length > 1) + onFinishInitialTyping?.(); + onChange(newDate); + } + }; + + const handleBlur = (rawValue: string, key: string) => { + const val = getBlurredValue(rawValue, key); + setEditingText({ + ...editingText, + [key]: val, + }); + }; + + const getBlurredValue = (rawValue: string, key: string) => { + const maxMap = [31, 12, 2999, 23, 59]; + const index = Object.keys(editingText).findIndex((et) => et === key); + const value = Math.min(maxMap[index], parseInt(rawValue)); + const finalValue = + rawValue.trim() !== "" + ? ("000" + value).slice(key === "year" ? -4 : -2) + : ""; + return finalValue; + }; + + const goToInput = (i: number) => { + if (i < 0 || i > 4) return; + ( + document.querySelectorAll( + `[data-time-input]`, + ) as NodeListOf + ).forEach((i) => i.blur()); + ( + document.querySelector(`[data-time-input="${i}"]`) as HTMLInputElement + )?.focus(); + }; + + const handleKeyDown = (event: KeyboardEvent, i: number) => { + const keyboardKey: number = event.keyCode || event.charCode; + const target = event.target as HTMLInputElement; + + // check for backspace + if ([8].includes(keyboardKey) && target.value === "") goToInput(i - 1); + + // check for delete + if ([46].includes(keyboardKey) && target.value === "") goToInput(i + 1); + + // check for left arrow key + if ([37].includes(keyboardKey) && (target.selectionStart || 0) < 1) + goToInput(i - 1); + + // check for right arrow key + if ([39].includes(keyboardKey) && (target.selectionStart || 0) > 1) + goToInput(i + 1); + }; + + useEffect(() => { + const formatUnfocused = (value: number, id: number, digits: number = 2) => { + const activeElementIdRaw = + document.activeElement?.getAttribute("data-time-input"); + const activeElementId = activeElementIdRaw + ? parseInt(activeElementIdRaw) + : undefined; + if (id === activeElementId) return value; + return ("000" + value).slice(-digits); + }; + + setDirtyEditingText({ + date: `${value ? formatUnfocused(value.getDate(), 0) : ""}`, + month: `${value ? formatUnfocused(value.getMonth() + 1, 1) : ""}`, + year: `${value ? formatUnfocused(value.getFullYear(), 2, 4) : ""}`, + hour: `${value ? formatUnfocused(value.getHours(), 3) : ""}`, + minute: `${value ? formatUnfocused(value.getMinutes(), 4) : ""}`, + }); + }, [value]); + + return ( +
+
+ e.target === e.currentTarget && + (value ? goToInput(allowTime ? 4 : 2) : goToInput(0)) + } + data-test-id="date-input" + > + {Object.entries(editingText) + .slice(0, allowTime ? 5 : 3) + .map(([key, val], i) => ( + + handleKeyDown(e, i)} + data-time-input={i} + onChange={(e) => { + const value = e.target.value; + if ( + (value.endsWith("/") || + value.endsWith(" ") || + value.endsWith(":") || + value.length > (key === "year" ? 3 : 1)) && + i < 4 + ) { + goToInput(i + 1); + } else { + setEditingText({ + ...editingText, + [key]: value + .replace(/\D/g, "") + .slice(0, key === "year" ? 4 : 2), + }); + } + }} + onBlur={(e) => handleBlur(e.target.value, key)} + /> + + {["date", "month"].includes(key) + ? "/" + : key === "hour" + ? ":" + : " "} + + + ))} + + +
+ {error && {error}} +
+ ); +} diff --git a/src/components/Common/ExcelFIleDragAndDrop.tsx b/src/components/Common/ExcelFIleDragAndDrop.tsx index 566c8d0e600..d79cab62d34 100644 --- a/src/components/Common/ExcelFIleDragAndDrop.tsx +++ b/src/components/Common/ExcelFIleDragAndDrop.tsx @@ -1,4 +1,4 @@ -import * as _ from "lodash-es"; +import { forIn } from "lodash-es"; import { useEffect, useRef, useState } from "react"; import * as Notification from "../../Utils/Notifications"; import { useTranslation } from "react-i18next"; @@ -63,7 +63,7 @@ export default function ExcelFileDragAndDrop({ const data = XLSX.utils.sheet_to_json(worksheet, { defval: "" }); //converts the date to string data.forEach((row: any) => { - _.forIn(row, (value: any, key: string) => { + forIn(row, (value: any, key: string) => { if (value instanceof Date) { row[key] = value.toISOString().split("T")[0]; } diff --git a/src/components/Common/PMJAYProcedurePackageAutocomplete.tsx b/src/components/Common/PMJAYProcedurePackageAutocomplete.tsx deleted file mode 100644 index 0da23ac3d18..00000000000 --- a/src/components/Common/PMJAYProcedurePackageAutocomplete.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { - FormFieldBaseProps, - useFormFieldPropsResolver, -} from "../Form/FormFields/Utils"; - -import { Autocomplete } from "../Form/FormFields/Autocomplete"; -import FormField from "../Form/FormFields/FormField"; -import routes from "../../Redux/api"; -import { useState } from "react"; -import useQuery from "../../Utils/request/useQuery"; -import { mergeQueryOptions } from "../../Utils/utils"; - -export type PMJAYPackageItem = { - name?: string; - code?: string; - price?: number; - package_name?: string; -}; - -type Props = FormFieldBaseProps; - -export default function PMJAYProcedurePackageAutocomplete(props: Props) { - const field = useFormFieldPropsResolver(props); - - const [query, setQuery] = useState(""); - - const { data, loading } = useQuery(routes.hcx.claims.listPMJYPackages, { - query: { query, limit: 10 }, - }); - - return ( - - ({ - ...o, - price: - o.price && parseFloat(o.price?.toString().replaceAll(",", "")), - })), - data ?? [], - (obj) => obj.code, - )} - optionLabel={optionLabel} - optionDescription={optionDescription} - optionValue={(option) => option} - onQuery={setQuery} - isLoading={loading} - /> - - ); -} - -const optionLabel = (option: PMJAYPackageItem) => { - if (option.name) return option.name; - if (option.package_name) return `${option.package_name} (Package)`; - return "Unknown"; -}; - -const optionDescription = (option: PMJAYPackageItem) => { - const code = option.code || "Unknown"; - const packageName = option.package_name || "Unknown"; - return `Package: ${packageName} (${code})`; -}; diff --git a/src/components/Common/RelativeDateUserMention.tsx b/src/components/Common/RelativeDateUserMention.tsx index b2fc8b2e241..f68f002eee5 100644 --- a/src/components/Common/RelativeDateUserMention.tsx +++ b/src/components/Common/RelativeDateUserMention.tsx @@ -1,10 +1,11 @@ -import CareIcon from "../../CAREUI/icons/CareIcon"; import { formatDateTime, formatName, relativeDate } from "../../Utils/utils"; -import { PerformedByModel } from "../HCX/misc"; + +import CareIcon from "../../CAREUI/icons/CareIcon"; +import { UserBareMinimum } from "@/components/Users/models"; function RelativeDateUserMention(props: { actionDate?: string; - user?: PerformedByModel; + user?: UserBareMinimum; tooltipPosition?: "top" | "bottom" | "left" | "right"; withoutSuffix?: boolean; }) { diff --git a/src/components/Common/SortDropdown.tsx b/src/components/Common/SortDropdown.tsx index 0c4fddec58c..7f190fde646 100644 --- a/src/components/Common/SortDropdown.tsx +++ b/src/components/Common/SortDropdown.tsx @@ -27,9 +27,9 @@ export default function SortDropdownMenu(props: Props) { icon={} containerClassName="w-full md:w-auto" > - {props.options.map(({ isAscending, value }) => ( + {props.options.map(({ isAscending, value }, i) => ( ) : ( -
- Time{" *"} - { - setItem( - { - ...investigation, - time: e.currentTarget.value, - }, - i, - ); - }} - onFocus={() => setActiveIdx(i)} - onBlur={() => setActiveIdx(null)} - /> -
+ + setItem( + { + ...investigation, + time: dayjs(e.value).format("YYYY-MM-DDTHH:mm"), + }, + i, + ) + } + allowTime + errorClassName="hidden" + className="w-full" + onFocus={() => setActiveIdx(i)} + onBlur={() => setActiveIdx(null)} + /> )}
diff --git a/src/components/Common/prescription-builder/ProcedureBuilder.tsx b/src/components/Common/prescription-builder/ProcedureBuilder.tsx index fe7ec3cda3c..36536c2435b 100644 --- a/src/components/Common/prescription-builder/ProcedureBuilder.tsx +++ b/src/components/Common/prescription-builder/ProcedureBuilder.tsx @@ -1,6 +1,8 @@ import { useState } from "react"; import { PrescriptionDropdown } from "./PrescriptionDropdown"; import CareIcon from "../../../CAREUI/icons/CareIcon"; +import dayjs from "dayjs"; +import DateFormField from "../../Form/FormFields/DateFormField"; export type ProcedureType = { procedure?: string; @@ -131,28 +133,29 @@ export default function ProcedureBuilder(props: Props) { />
) : ( -
-
- Time{" *"} -
- setActiveIdx(i)} - onBlur={() => setActiveIdx(null)} - onChange={(e) => { - setItem( - { - ...procedure, - time: e.currentTarget.value, - }, - i, - ); - }} - /> -
+ + setItem( + { + ...procedure, + time: dayjs(e.value).format("YYYY-MM-DDTHH:mm"), + }, + i, + ) + } + allowTime + errorClassName="hidden" + className="w-full" + onFocus={() => setActiveIdx(i)} + onBlur={() => setActiveIdx(null)} + /> )} diff --git a/src/components/DeathReport/DeathReport.tsx b/src/components/DeathReport/DeathReport.tsx index 702a486bdfb..00f6d6bd773 100644 --- a/src/components/DeathReport/DeathReport.tsx +++ b/src/components/DeathReport/DeathReport.tsx @@ -410,7 +410,6 @@ export default function PrintDeathReport(props: { id: string }) { @@ -428,14 +427,12 @@ export default function PrintDeathReport(props: { id: string }) {
@@ -477,7 +474,6 @@ export default function PrintDeathReport(props: { id: string }) { @@ -485,7 +481,6 @@ export default function PrintDeathReport(props: { id: string }) { @@ -534,7 +529,6 @@ export default function PrintDeathReport(props: { id: string }) { diff --git a/src/components/Diagnosis/types.ts b/src/components/Diagnosis/types.ts index 694e7a482d6..4a67e28efab 100644 --- a/src/components/Diagnosis/types.ts +++ b/src/components/Diagnosis/types.ts @@ -1,4 +1,4 @@ -import { PerformedByModel } from "../HCX/misc"; +import { UserBareMinimum } from "@/components/Users/models"; export type ICD11DiagnosisModel = { id: string; @@ -33,7 +33,7 @@ export interface ConsultationDiagnosis { verification_status: ConditionVerificationStatus; is_principal: boolean; readonly is_migrated: boolean; - readonly created_by: PerformedByModel; + readonly created_by: UserBareMinimum; readonly created_date: string; readonly modified_date: string; } diff --git a/src/components/ErrorPages/SessionExpired.tsx b/src/components/ErrorPages/SessionExpired.tsx index 441412ba1e9..2b50a8bcc6e 100644 --- a/src/components/ErrorPages/SessionExpired.tsx +++ b/src/components/ErrorPages/SessionExpired.tsx @@ -17,7 +17,7 @@ export default function SessionExpired() { {t("session_expired")}

{t("session_expired")}

@@ -26,7 +26,7 @@ export default function SessionExpired() {

{t("return_to_login")}
diff --git a/src/components/Facility/AssetCreate.tsx b/src/components/Facility/AssetCreate.tsx index 40bf4d276dd..60676f9ca18 100644 --- a/src/components/Facility/AssetCreate.tsx +++ b/src/components/Facility/AssetCreate.tsx @@ -834,7 +834,6 @@ const AssetCreate = (props: AssetProps) => { label={t("last_serviced_on")} name="last_serviced_on" className="mt-2" - position="RIGHT" disableFuture value={last_serviced_on && new Date(last_serviced_on)} onChange={(date) => { diff --git a/src/components/Facility/ConsultationClaims.tsx b/src/components/Facility/ConsultationClaims.tsx deleted file mode 100644 index a449f4c18f1..00000000000 --- a/src/components/Facility/ConsultationClaims.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import * as Notification from "../../Utils/Notifications"; - -import ClaimCard from "../HCX/ClaimCard"; -import CreateClaimCard from "../HCX/CreateClaimCard"; -import PageTitle from "@/components/Common/PageTitle"; -import { navigate } from "raviger"; -import routes from "../../Redux/api"; -import { useMessageListener } from "@/common/hooks/useMessageListener"; -import useQuery from "../../Utils/request/useQuery"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -export interface IConsultationClaimsProps { - facilityId: string; - patientId: string; - consultationId: string; -} - -export default function ConsultationClaims({ - facilityId, - consultationId, - patientId, -}: IConsultationClaimsProps) { - const { t } = useTranslation(); - - const [isCreateLoading, setIsCreateLoading] = useState(false); - - const { data: claimsResult, refetch: refetchClaims } = useQuery( - routes.hcx.claims.list, - { - query: { - ordering: "-modified_date", - consultation: consultationId, - }, - onResponse: (res) => { - if (!isCreateLoading) return; - - if (res.data?.results) { - Notification.Success({ - msg: t("claim__fetched_claim_approval_results"), - }); - return; - } - - Notification.Error({ - msg: t("claim__error_fetching_claim_approval_results"), - }); - }, - }, - ); - - useMessageListener((data) => { - if ( - data.type === "MESSAGE" && - (data.from === "claim/on_submit" || data.from === "preauth/on_submit") && - data.message === "success" - ) { - refetchClaims(); - } - }); - - return ( -
- { - navigate( - `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}`, - ); - return false; - }} - /> - -
-
- -
- -
- {claimsResult?.results.map((claim) => ( -
- -
- ))} -
-
-
- ); -} diff --git a/src/components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx b/src/components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx index de2fc6a7e5a..66cc40e4c71 100644 --- a/src/components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx +++ b/src/components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx @@ -23,6 +23,19 @@ import useQuery from "../../../Utils/request/useQuery"; import routes from "../../../Redux/api"; import CareIcon from "../../../CAREUI/icons/CareIcon"; import EncounterSymptomsCard from "../../Symptoms/SymptomsCard"; +import { QueryParams } from "../../../Utils/request/types"; +import { EVENTS_SORT_OPTIONS } from "@/common/constants"; +import DailyRoundsFilter from "../Consultations/DailyRoundsFilter"; +import ButtonV2 from "../../Common/components/ButtonV2"; +import { classNames } from "../../../Utils/utils"; + +import { useTranslation } from "react-i18next"; +import { + Popover, + PopoverButton, + PopoverPanel, + Transition, +} from "@headlessui/react"; import Tabs from "@/components/Common/components/Tabs"; import PageTitle from "@/components/Common/PageTitle"; @@ -33,6 +46,9 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => { const [monitorBedData, setMonitorBedData] = useState(); const [ventilatorBedData, setVentilatorBedData] = useState(); const [showEvents, setShowEvents] = useState(true); + const [eventsQuery, setEventsQuery] = useState(); + const [dailyRoundsQuery, setDailyRoundsQuery] = useState(); + const { t } = useTranslation(); const vitals = useVitalsAspectRatioConfig({ default: undefined, @@ -651,32 +667,110 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => {
- - Events - - beta - -
- ), - value: 1, - }, - { text: "Daily Rounds", value: 0 }, - ]} - onTabChange={(v) => setShowEvents(!!v)} - currentTab={showEvents ? 1 : 0} - /> +
+ + {t("events")} + + {t("beta")} + +
+ ), + value: 1, + }, + { text: t("daily_rounds"), value: 0 }, + ]} + onTabChange={(v) => setShowEvents(!!v)} + currentTab={showEvents ? 1 : 0} + /> + {showEvents ? ( + + + + + + + + +
+
+ {EVENTS_SORT_OPTIONS.map(({ isAscending, value }) => { + return ( +
{ + setEventsQuery({ + ordering: value, + }); + }} + > + + {t("SORT_OPTIONS__" + value)} +
+ ); + })} +
+
+
+
+
+ ) : ( + + )} + + {showEvents ? ( - + ) : ( - + )} ); }; + +function DailyRoundsSortDropdown({ + setDailyRoundsQuery, +}: { + setDailyRoundsQuery: (query: QueryParams) => void; +}) { + return ( + { + setDailyRoundsQuery(query); + }} + /> + ); +} diff --git a/src/components/Facility/ConsultationDetails/Events/EventsList.tsx b/src/components/Facility/ConsultationDetails/Events/EventsList.tsx index 81eced70e87..02c0c5131dd 100644 --- a/src/components/Facility/ConsultationDetails/Events/EventsList.tsx +++ b/src/components/Facility/ConsultationDetails/Events/EventsList.tsx @@ -7,15 +7,11 @@ import LoadingLogUpdateCard from "../../Consultations/DailyRounds/LoadingCard"; import GenericEvent from "./GenericEvent"; import { getEventIcon } from "./iconMap"; import { EventGeneric } from "./types"; -import SortDropdownMenu from "@/components/Common/SortDropdown"; -import { EVENTS_SORT_OPTIONS } from "@/common/constants"; import { QueryParams } from "../../../../Utils/request/types"; -import { useState } from "react"; -export default function EventsList() { +export default function EventsList({ query }: { query: QueryParams }) { const [consultationId] = useSlugs("consultation"); const { t } = useTranslation(); - const [query, setQuery] = useState(); return ( {() => ( <> -
- -
-
diff --git a/src/components/Facility/ConsultationForm.tsx b/src/components/Facility/ConsultationForm.tsx index 47ba2eea77f..9fe281635f2 100644 --- a/src/components/Facility/ConsultationForm.tsx +++ b/src/components/Facility/ConsultationForm.tsx @@ -65,6 +65,7 @@ import { CreateSymptomsBuilder, } from "../Symptoms/SymptomsBuilder"; import careConfig from "@careConfig"; +import DateFormField from "../Form/FormFields/DateFormField.js"; import Loading from "@/components/Common/Loading"; import PageTitle from "@/components/Common/PageTitle"; @@ -1178,13 +1179,24 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { className="col-span-6" ref={fieldRef["death_datetime"]} > - + field("death_datetime").onChange({ + ...e, + value: dayjs(e.value).format("YYYY-MM-DDTHH:mm"), + }) + } + allowTime + errorClassName="hidden" />
{ )} ref={fieldRef["encounter_date"]} > - { label={t( `encounter_date_field_label__${state.form.suggestion}`, )} - type="datetime-local" - value={dayjs(state.form.encounter_date).format( - "YYYY-MM-DDTHH:mm", - )} - max={dayjs().format("YYYY-MM-DDTHH:mm")} - min={dayjs(careConfig.minEncounterDate).format( - "YYYY-MM-DDTHH:mm", - )} + value={ + !state.form.encounter_date + ? new Date() + : state.form.encounter_date + } + max={new Date()} + min={careConfig.minEncounterDate} + onChange={(e) => + field("encounter_date").onChange({ + ...e, + value: dayjs(e.value).format("YYYY-MM-DDTHH:mm"), + }) + } + allowTime + errorClassName="hidden" /> {dayjs().diff(state.form.encounter_date, "day") > 30 && (
@@ -1252,16 +1271,18 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { )} ref={fieldRef["icu_admission_date"]} > - + field("icu_admission_date").onChange({ + ...e, + value: dayjs(e.value).format("YYYY-MM-DDTHH:mm"), + }) } + allowTime + errorClassName="hidden" />
)} diff --git a/src/components/Facility/Consultations/Beds.tsx b/src/components/Facility/Consultations/Beds.tsx index 4c8409fbebf..5d43ffff7fd 100644 --- a/src/components/Facility/Consultations/Beds.tsx +++ b/src/components/Facility/Consultations/Beds.tsx @@ -12,7 +12,6 @@ import CareIcon from "../../../CAREUI/icons/CareIcon"; import CircularProgress from "@/components/Common/components/CircularProgress"; import { FieldLabel } from "../../Form/FormFields/FormField"; import Loading from "@/components/Common/Loading"; -import TextFormField from "../../Form/FormFields/TextFormField"; import dayjs from "../../../Utils/dayjs"; import { AssetSelect } from "@/components/Common/AssetSelect"; import DialogModal from "@/components/Common/Dialog"; @@ -24,6 +23,7 @@ import { } from "../../Assets/AssetTypes"; import Chip from "../../../CAREUI/display/Chip"; import BedActivityTimeline from "./BedActivityTimeline"; +import DateFormField from "../../Form/FormFields/DateFormField.js"; interface BedsProps { facilityId: string; @@ -204,16 +204,18 @@ const Beds = (props: BedsProps) => { unoccupiedOnly />
- setStartDate(e.value)} - max={dayjs().format("YYYY-MM-DDTHH:mm")} + value={startDate ? new Date(startDate) : new Date()} + onChange={(e) => + setStartDate(dayjs(e.value).format("YYYY-MM-DDTHH:mm")) + } + max={new Date()} error="" errorClassName="hidden" + allowTime />
Link Assets diff --git a/src/components/Facility/Consultations/DailyRoundsFilter.tsx b/src/components/Facility/Consultations/DailyRoundsFilter.tsx index 5e9104b5992..503952eacc1 100644 --- a/src/components/Facility/Consultations/DailyRoundsFilter.tsx +++ b/src/components/Facility/Consultations/DailyRoundsFilter.tsx @@ -6,13 +6,13 @@ import { } from "@headlessui/react"; import ButtonV2 from "@/components/Common/components/ButtonV2"; import { SelectFormField } from "../../Form/FormFields/SelectFormField"; -import TextFormField from "../../Form/FormFields/TextFormField"; import CareIcon from "../../../CAREUI/icons/CareIcon"; import dayjs from "dayjs"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { DailyRoundTypes, DailyRoundsModel } from "../../Patient/models"; import { FieldChangeEvent } from "../../Form/FormFields/Utils"; +import DateFormField from "../../Form/FormFields/DateFormField"; type FilterState = { rounds_type?: DailyRoundsModel["rounds_type"]; @@ -42,84 +42,105 @@ export default function DailyRoundsFilter(props: Props) { ); return ( -
- - - - - {t("filter")} - - - + + - -
-
-
- - {t("filter_by")} - -
+ + + + + +
+
+
+ + {t("filter_by")} +
-
- t(`ROUNDS_TYPE__${o}`)} - optionValue={(o) => o} - /> - - +
+
+ t(`ROUNDS_TYPE__${o}`)} + optionValue={(o) => o} + /> + + field("taken_at_after").onChange({ + ...e, + value: dayjs(e.value).format("YYYY-MM-DDTHH:mm"), + }) + } + max={new Date()} + errorClassName="hidden" + allowTime + /> + + field("taken_at_before").onChange({ + ...e, + value: dayjs(e.value).format("YYYY-MM-DDTHH:mm"), + }) + } + max={new Date()} + errorClassName="hidden" + allowTime + /> - - { - setFilter({}); - props.onApply({}); - }} - border - className="w-full" - > - {t("clear")} - - - - props.onApply(filter)} - border - className="w-full" - > - {t("apply")} - - -
+ + { + setFilter({}); + props.onApply({}); + }} + border + className="w-full" + > + {t("clear")} + + + + props.onApply(filter)} + border + className="w-full" + > + {t("apply")} + +
- - - -
+
+ + + ); } diff --git a/src/components/Facility/Consultations/DailyRoundsList.tsx b/src/components/Facility/Consultations/DailyRoundsList.tsx index 0ae6ffb1765..a91764e581b 100644 --- a/src/components/Facility/Consultations/DailyRoundsList.tsx +++ b/src/components/Facility/Consultations/DailyRoundsList.tsx @@ -5,23 +5,21 @@ import { useTranslation } from "react-i18next"; import LoadingLogUpdateCard from "./DailyRounds/LoadingCard"; import routes from "../../../Redux/api"; import PaginatedList from "../../../CAREUI/misc/PaginatedList"; -import DailyRoundsFilter from "./DailyRoundsFilter"; import { ConsultationModel } from "../models"; import { useSlugs } from "@/common/hooks/useSlug"; import Timeline, { TimelineNode } from "../../../CAREUI/display/Timeline"; -import { useState } from "react"; import { QueryParams } from "../../../Utils/request/types"; import { UserRole } from "@/common/constants"; interface Props { consultation: ConsultationModel; + query: QueryParams; } -export default function DailyRoundsList({ consultation }: Props) { +export default function DailyRoundsList({ consultation, query }: Props) { const [consultationId] = useSlugs("consultation"); const { t } = useTranslation(); - const [query, setQuery] = useState(); return ( {() => ( <> -
- { - setQuery(query); - }} - /> -
- -
+
diff --git a/src/components/Facility/DischargeModal.tsx b/src/components/Facility/DischargeModal.tsx index 122aacd1368..e3240c08751 100644 --- a/src/components/Facility/DischargeModal.tsx +++ b/src/components/Facility/DischargeModal.tsx @@ -5,31 +5,28 @@ import { useEffect, useState } from "react"; import CareIcon from "../../CAREUI/icons/CareIcon"; import CircularProgress from "@/components/Common/components/CircularProgress"; -import ClaimCard from "../HCX/ClaimCard"; +import ConfirmDialog from "@/components/Common/ConfirmDialog"; import { ConsultationModel } from "./models"; -import CreateClaimCard from "../HCX/CreateClaimCard"; import { DISCHARGE_REASONS } from "@/common/constants"; +import DateFormField from "../Form/FormFields/DateFormField"; import DialogModal from "@/components/Common/Dialog"; +import { EditDiagnosesBuilder } from "../Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisBuilder"; import { FacilityModel } from "./models"; import { FacilitySelect } from "@/components/Common/FacilitySelect"; import { FieldError } from "../Form/FieldValidators"; import { FieldLabel } from "../Form/FormFields/FormField"; -import { HCXClaimModel } from "../HCX/models"; +import Loading from "@/components/Common/Loading"; +import { PLUGIN_Component } from "@/PluginEngine"; import PrescriptionBuilder from "../Medicine/PrescriptionBuilder"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; import TextFormField from "../Form/FormFields/TextFormField"; import dayjs from "../../Utils/dayjs"; -import { useMessageListener } from "@/common/hooks/useMessageListener"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import useConfirmedAction from "@/common/hooks/useConfirmedAction"; import useQuery from "../../Utils/request/useQuery"; import { useTranslation } from "react-i18next"; -import useConfirmedAction from "@/common/hooks/useConfirmedAction"; -import ConfirmDialog from "@/components/Common/ConfirmDialog"; -import routes from "../../Redux/api"; -import { EditDiagnosesBuilder } from "../Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisBuilder"; -import Loading from "@/components/Common/Loading"; -import careConfig from "@careConfig"; -import request from "../../Utils/request/request"; interface PreDischargeFormInterface { new_discharge_reason: number | null; @@ -75,40 +72,10 @@ const DischargeModal = ({ referred_to_external: !referred_to?.id ? referred_to?.name : null, referred_to: referred_to?.id ? referred_to.id : null, }); - const [latestClaim, setLatestClaim] = useState(); - const [isCreateClaimLoading, setIsCreateClaimLoading] = useState(false); const [isSendingDischargeApi, setIsSendingDischargeApi] = useState(false); const [facility, setFacility] = useState(referred_to); const [errors, setErrors] = useState({}); - const { refetch: refetchLatestClaim } = useQuery(routes.hcx.claims.list, { - query: { - consultation: consultationData.id, - ordering: "-modified_date", - use: "claim", - outcome: "complete", - limit: 1, - }, - onResponse: (res) => { - if (!isCreateClaimLoading) return; - - setIsCreateClaimLoading(false); - - if (res?.data?.results?.length !== 0) { - setLatestClaim(res?.data?.results[0]); - Notification.Success({ - msg: t("claim__fetched_claim_approval_results"), - }); - return; - } - - setLatestClaim(undefined); - Notification.Success({ - msg: t("claim__error_fetching_claim_approval_results"), - }); - }, - }); - useEffect(() => { setPreDischargeForm((prev) => ({ ...prev, @@ -130,16 +97,6 @@ const DischargeModal = ({ const discharge_reason = new_discharge_reason ?? preDischargeForm.new_discharge_reason; - useMessageListener((data) => { - if ( - data.type === "MESSAGE" && - (data.from === "claim/on_submit" || data.from === "preauth/on_submit") && - data.message === "success" - ) { - refetchLatestClaim(); - } - }); - const validate = () => { if (!new_discharge_reason && !discharge_reason) { setErrors({ @@ -210,6 +167,13 @@ const DischargeModal = ({ const confirmationRequired = encounterDuration.asDays() >= 30; + const dischargeOrDeathTime = + preDischargeForm[ + discharge_reason === + DISCHARGE_REASONS.find((i) => i.text == "Expired")?.id + ? "death_datetime" + : "discharge_date" + ]; if (initialDiagnoses == null) { return ; } @@ -297,7 +261,7 @@ const DischargeModal = ({ />
)} - i.text == "Expired")?.id @@ -310,34 +274,28 @@ const DischargeModal = ({ ? "Date of Death" : "Date and Time of Discharge" } - type="datetime-local" value={ - preDischargeForm[ - discharge_reason === - DISCHARGE_REASONS.find((i) => i.text == "Expired")?.id - ? "death_datetime" - : "discharge_date" - ] + dischargeOrDeathTime ? new Date(dischargeOrDeathTime) : new Date() } + popOverClassName="max-h-[50vh]" onChange={(e) => { const updates: Record = { discharge_date: undefined, death_datetime: undefined, }; - updates[e.name] = e.value; + updates[e.name] = dayjs(e.value).format("YYYY-MM-DDTHH:mm"); setPreDischargeForm((form) => ({ ...form, ...updates })); }} required - min={dayjs(consultationData?.encounter_date).format( - "YYYY-MM-DDTHH:mm", - )} - max={dayjs().format("YYYY-MM-DDTHH:mm")} + min={new Date(consultationData?.encounter_date)} + max={new Date()} error={ discharge_reason === DISCHARGE_REASONS.find((i) => i.text == "Expired")?.id ? errors?.death_datetime : errors?.discharge_date } + allowTime /> {discharge_reason !== @@ -406,23 +364,10 @@ const DischargeModal = ({ error={errors?.discharge_notes} /> - {careConfig.hcx.enabled && ( - // TODO: if policy and approved pre-auth exists -
-

Claim Insurance

- {latestClaim ? ( - - ) : ( - - )} -
- )} +
diff --git a/src/components/Facility/DoctorVideoSlideover.tsx b/src/components/Facility/DoctorVideoSlideover.tsx index b45bdbeb59e..0ed5e7df877 100644 --- a/src/components/Facility/DoctorVideoSlideover.tsx +++ b/src/components/Facility/DoctorVideoSlideover.tsx @@ -1,22 +1,23 @@ -import React, { useState } from "react"; -import SlideOver from "../../CAREUI/interactive/SlideOver"; -import { UserAssignedModel } from "../Users/models"; -import { SkillObjectModel } from "../Users/models"; import CareIcon, { IconName } from "../../CAREUI/icons/CareIcon"; +import React, { useState } from "react"; import { classNames, formatName, isUserOnline, relativeTime, } from "../../Utils/utils"; -import useAuthUser from "@/common/hooks/useAuthUser"; -import { triggerGoal } from "../../Integrations/Plausible"; -import { Warn } from "../../Utils/Notifications"; + +import Loading from "@/components/Common/Loading"; +import { PLUGIN_Component } from "@/PluginEngine"; +import { SkillObjectModel } from "../Users/models"; +import SlideOver from "../../CAREUI/interactive/SlideOver"; import Switch from "../../CAREUI/interactive/Switch"; -import useQuery from "../../Utils/request/useQuery"; +import { UserAssignedModel } from "../Users/models"; +import { Warn } from "../../Utils/Notifications"; import routes from "../../Redux/api"; -import Loading from "@/components/Common/Loading"; -import { PLUGIN_DoctorConnectButtons } from "@/PluginEngine"; +import { triggerGoal } from "../../Integrations/Plausible"; +import useAuthUser from "@/common/hooks/useAuthUser"; +import useQuery from "../../Utils/request/useQuery"; const UserGroups = { ALL: "All", @@ -367,7 +368,7 @@ function DoctorConnectButtons(props: {
- +
); } diff --git a/src/components/Facility/FacilityHome.tsx b/src/components/Facility/FacilityHome.tsx index 13505889173..030763b24c4 100644 --- a/src/components/Facility/FacilityHome.tsx +++ b/src/components/Facility/FacilityHome.tsx @@ -133,6 +133,8 @@ export const FacilityHome = ({ facilityId }: Props) => { facilityFetch(); Notification.Success({ msg: "Cover image updated." }); setEditCoverImage(false); + } else { + onError(); } }, null, diff --git a/src/components/Facility/FacilityStaffList.tsx b/src/components/Facility/FacilityStaffList.tsx index 7b51568a871..2d116e0789f 100644 --- a/src/components/Facility/FacilityStaffList.tsx +++ b/src/components/Facility/FacilityStaffList.tsx @@ -11,14 +11,21 @@ import DoctorsCountCard from "./StaffCountCard"; import { DoctorIcon } from "../TeleIcu/Icons/DoctorIcon"; import CareIcon from "../../CAREUI/icons/CareIcon"; import { useTranslation } from "react-i18next"; +import useFilters from "@/common/hooks/useFilters"; +import Pagination from "../Common/Pagination"; export const FacilityStaffList = (props: any) => { const { t } = useTranslation(); const [doctorCapacityModalOpen, setDoctorCapacityModalOpen] = useState(false); + const { qParams, resultsPerPage, updatePage } = useFilters({ limit: 15 }); const [totalDoctors, setTotalDoctors] = useState(0); - const doctorQuery = useQuery(routes.listDoctor, { + const { data: doctorsList, refetch } = useQuery(routes.listDoctor, { pathParams: { facilityId: props.facilityId }, + query: { + limit: resultsPerPage, + offset: (qParams.page - 1) * resultsPerPage, + }, onResponse: ({ res, data }) => { if (res?.ok && data) { let totalCount = 0; @@ -33,7 +40,7 @@ export const FacilityStaffList = (props: any) => { }); let doctorList: any = null; - if (!doctorQuery.data || !doctorQuery.data.results.length) { + if (!doctorsList || !doctorsList.results.length) { doctorList = (
{t("no_staff")} @@ -50,7 +57,7 @@ export const FacilityStaffList = (props: any) => {
- Total Staff + {t("total_staff")}

{totalDoctors}

@@ -58,25 +65,16 @@ export const FacilityStaffList = (props: any) => {
- {doctorQuery.data.results.map((data: DoctorModal) => { - const removeCurrentDoctorData = (doctorId: number | undefined) => { - if (doctorQuery.data !== undefined) { - doctorQuery.data?.results.filter( - (i: DoctorModal) => i.id !== doctorId, - ); - doctorQuery.refetch(); - } - }; - + {doctorsList.results.map((data: DoctorModal) => { return ( { - doctorQuery.refetch(); + refetch(); }} {...data} - removeDoctor={removeCurrentDoctorData} + removeDoctor={() => refetch()} /> ); })} @@ -116,11 +114,17 @@ export const FacilityStaffList = (props: any) => { facilityId={props.facilityId} handleClose={() => setDoctorCapacityModalOpen(false)} handleUpdate={async () => { - doctorQuery.refetch(); + refetch(); }} /> )} + updatePage(page)} + /> ); }; diff --git a/src/components/Facility/Investigations/InvestigationsPrintPreview.tsx b/src/components/Facility/Investigations/InvestigationsPrintPreview.tsx index 107b0831bed..56f6f4a14e5 100644 --- a/src/components/Facility/Investigations/InvestigationsPrintPreview.tsx +++ b/src/components/Facility/Investigations/InvestigationsPrintPreview.tsx @@ -1,4 +1,3 @@ -import * as _ from "lodash-es"; import { lazy } from "react"; import routes from "../../../Redux/api"; import useQuery from "../../../Utils/request/useQuery"; diff --git a/src/components/Facility/Investigations/Reports/index.tsx b/src/components/Facility/Investigations/Reports/index.tsx index e4e54c36994..246d50f94bd 100644 --- a/src/components/Facility/Investigations/Reports/index.tsx +++ b/src/components/Facility/Investigations/Reports/index.tsx @@ -1,7 +1,7 @@ import { useCallback, useReducer, useState } from "react"; import { InvestigationGroup, InvestigationType } from ".."; -import _ from "lodash"; +import { chain } from "lodash-es"; import { useTranslation } from "react-i18next"; import routes from "../../../../Redux/api"; import * as Notification from "../../../../Utils/Notifications"; @@ -172,7 +172,7 @@ const InvestigationReports = ({ id }: any) => { ), ); - const investigationList = _.chain(data) + const investigationList = chain(data) .flatMap((i) => i?.data?.results) .compact() .flatten() diff --git a/src/components/Facility/Investigations/Reports/utils.tsx b/src/components/Facility/Investigations/Reports/utils.tsx index c6c6a0f6d45..eed1afc571e 100644 --- a/src/components/Facility/Investigations/Reports/utils.tsx +++ b/src/components/Facility/Investigations/Reports/utils.tsx @@ -1,8 +1,8 @@ -import * as _ from "lodash-es"; +import { memoize, chain, findIndex } from "lodash-es"; import { InvestigationResponse } from "./types"; -export const transformData = _.memoize((data: InvestigationResponse) => { - const sessions = _.chain(data) +export const transformData = memoize((data: InvestigationResponse) => { + const sessions = chain(data) .map((value: any) => { return { ...value.session_object, @@ -13,14 +13,14 @@ export const transformData = _.memoize((data: InvestigationResponse) => { .uniqBy("session_external_id") .orderBy("session_created_date", "desc") .value(); - const groupByInvestigation = _.chain(data) + const groupByInvestigation = chain(data) .groupBy("investigation_object.external_id") .values() .value(); const reqData = groupByInvestigation.map((value: any) => { const sessionValues = Array.from({ length: sessions.length }); value.forEach((val: any) => { - const sessionIndex = _.findIndex(sessions, [ + const sessionIndex = findIndex(sessions, [ "session_external_id", val.session_object.session_external_id, ]); @@ -59,7 +59,7 @@ export const transformData = _.memoize((data: InvestigationResponse) => { return { sessions, data: reqData }; }); -export const getColorIndex = _.memoize( +export const getColorIndex = memoize( ({ max, min, value }: { min?: number; max?: number; value?: number }) => { if (!max && min && value) { // 1 => yellow color diff --git a/src/components/Facility/Investigations/ShowInvestigation.tsx b/src/components/Facility/Investigations/ShowInvestigation.tsx index 0755b3687cd..00fe2c33549 100644 --- a/src/components/Facility/Investigations/ShowInvestigation.tsx +++ b/src/components/Facility/Investigations/ShowInvestigation.tsx @@ -1,4 +1,4 @@ -import * as _ from "lodash-es"; +import { set, chain } from "lodash-es"; import { useCallback, useReducer } from "react"; import routes from "../../../Redux/api"; import * as Notification from "../../../Utils/Notifications"; @@ -89,7 +89,7 @@ export default function ShowInvestigation(props: ShowInvestigationProps) { const handleValueChange = (value: any, name: string) => { const changedFields = { ...state.changedFields }; - _.set(changedFields, name, value); + set(changedFields, name, value); dispatch({ type: "set_changed_fields", changedFields }); }; @@ -147,7 +147,7 @@ export default function ShowInvestigation(props: ShowInvestigationProps) { }; const handleUpdateCancel = useCallback(() => { - const changedValues = _.chain(state.initialValues) + const changedValues = chain(state.initialValues) .map((val: any, _key: string) => ({ id: val?.id, initialValue: val?.notes || val?.value || null, diff --git a/src/components/Facility/LocationManagement.tsx b/src/components/Facility/LocationManagement.tsx index 82184887332..6933a760969 100644 --- a/src/components/Facility/LocationManagement.tsx +++ b/src/components/Facility/LocationManagement.tsx @@ -14,9 +14,8 @@ import DialogModal from "@/components/Common/Dialog"; import Uptime from "@/components/Common/Uptime"; import useAuthUser from "@/common/hooks/useAuthUser"; import useQuery from "../../Utils/request/useQuery"; - import Loading from "@/components/Common/Loading"; -import { cn } from "@/lib/utils"; + interface Props { facilityId: string; } @@ -287,11 +286,9 @@ const Location = ({ id="manage-bed-button" variant="secondary" border - className={cn( - "mt-3 flex w-full items-center justify-between", - totalBeds != null && "opacity-50", - )} + className="mt-3 flex w-full items-center justify-between" href={`location/${id}/beds`} + disabled={totalBeds == null} > Manage Beds diff --git a/src/components/Facility/SpokeFacilityEditor.tsx b/src/components/Facility/SpokeFacilityEditor.tsx index a6dfe9e00aa..bf07ec4e9bb 100644 --- a/src/components/Facility/SpokeFacilityEditor.tsx +++ b/src/components/Facility/SpokeFacilityEditor.tsx @@ -78,7 +78,8 @@ export default function SpokeFacilityEditor(props: SpokeFacilityEditorProps) { setItem: (item: FacilitySpokeModel | FacilitySpokeRequest) => void, processing: boolean, ) => { - const [selectedFacility, setSelectedFacility] = useState(); + const [selectedFacility, setSelectedFacility] = + useState(null); useEffect(() => { setItem({ ...item, spoke: selectedFacility?.id }); @@ -99,7 +100,7 @@ export default function SpokeFacilityEditor(props: SpokeFacilityEditorProps) { showNOptions={8} selected={selectedFacility} setSelected={(v) => - v && !Array.isArray(v) && setSelectedFacility(v) + (v === null || !Array.isArray(v)) && setSelectedFacility(v) } errors="" className="w-full" diff --git a/src/components/Facility/StaffCapacity.tsx b/src/components/Facility/StaffCapacity.tsx index 602ec9a33f7..99be198dd74 100644 --- a/src/components/Facility/StaffCapacity.tsx +++ b/src/components/Facility/StaffCapacity.tsx @@ -66,6 +66,9 @@ export const StaffCapacity = (props: DoctorCapacityProps) => { const specializationsQuery = useQuery(routes.listDoctor, { pathParams: { facilityId }, + query: { + limit: DOCTOR_SPECIALIZATION.length - 1, + }, }); const { loading } = useQuery(routes.getDoctor, { diff --git a/src/components/Facility/TriageForm.tsx b/src/components/Facility/TriageForm.tsx index 72b391f2318..fe4499e3758 100644 --- a/src/components/Facility/TriageForm.tsx +++ b/src/components/Facility/TriageForm.tsx @@ -250,7 +250,6 @@ export const TriageForm = ({ facilityId, id }: Props) => { value={state.form.entry_date} disableFuture onChange={handleFormFieldChange} - position="LEFT" placeholder="Entry Date" error={state.errors.entry_date} /> diff --git a/src/components/Facility/models.tsx b/src/components/Facility/models.tsx index e64bbfda99f..84617e35ed7 100644 --- a/src/components/Facility/models.tsx +++ b/src/components/Facility/models.tsx @@ -19,7 +19,7 @@ import { PatientModel, } from "../Patient/models"; import { EncounterSymptom } from "../Symptoms/types"; -import { UserBareMinimum, UserModel } from "../Users/models"; +import { UserBareMinimum } from "../Users/models"; import { InvestigationType } from "@/components/Common/prescription-builder/InvestigationBuilder"; import { ProcedureType } from "@/components/Common/prescription-builder/ProcedureBuilder"; import { RouteToFacility } from "@/components/Common/RouteToFacilitySelect"; @@ -681,20 +681,44 @@ export type PatientTransferResponse = { }; export interface ShiftingModel { - assigned_facility: string; + 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; - created_date: string; - emergency: boolean; - external_id: string; - id: string; + assigned_facility_external_object: FacilityModel; modified_date: string; - origin_facility_object: FacilityModel; - patient: string; - patient_object: PatientModel; - shifting_approving_facility_object: FacilityModel | null; - status: (typeof SHIFTING_CHOICES_PEACETIME)[number]["text"]; - assigned_to_object?: UserModel; + external_id: string; + assigned_to_object?: AssignedToObjectModel; + refering_facility_contact_name: string; + refering_facility_contact_number: string; + is_kasp: boolean; + 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 { @@ -704,16 +728,12 @@ export interface ResourceModel { assigned_facility_object: FacilityModel | null; assigned_quantity: number; assigned_to: string | null; - assigned_to_object: UserModel | null; category: string; created_by: number; - created_by_object: UserModel; - created_date: string; emergency: boolean; id: string; is_assigned_to_user: boolean; last_edited_by: number; - last_edited_by_object: UserModel; modified_date: string; origin_facility: string; origin_facility_object: FacilityModel; @@ -725,4 +745,17 @@ export interface ResourceModel { status: string; sub_category: string; title: string; + assigned_to_object: UserBareMinimum | null; + created_by_object: UserBareMinimum | null; + created_date: string; + last_edited_by_object: UserBareMinimum; +} + +export interface CommentModel { + id: string; + created_by_object: UserBareMinimum; + created_date: string; + modified_date: string; + comment: string; + created_by: number; } diff --git a/src/components/Files/AudioCaptureDialog.tsx b/src/components/Files/AudioCaptureDialog.tsx index 097de0088aa..f84fc01c28b 100644 --- a/src/components/Files/AudioCaptureDialog.tsx +++ b/src/components/Files/AudioCaptureDialog.tsx @@ -3,7 +3,8 @@ import useRecorder from "../../Utils/useRecorder"; import { Link } from "raviger"; import CareIcon from "../../CAREUI/icons/CareIcon"; import { useTimer } from "../../Utils/useTimer"; -import { t } from "i18next"; +import { useTranslation } from "react-i18next"; +import * as Notify from "../../Utils/Notifications"; export interface AudioCaptureDialogProps { show: boolean; @@ -20,8 +21,8 @@ export default function AudioCaptureDialog(props: AudioCaptureDialogProps) { | "RECORDED"; const { show, onHide, onCapture, autoRecord = false } = props; - const [status, setStatus] = useState(null); + const { t } = useTranslation(); const [audioURL, , startRecording, stopRecording, , resetRecording] = useRecorder((permission: boolean) => { @@ -35,9 +36,19 @@ export default function AudioCaptureDialog(props: AudioCaptureDialogProps) { const timer = useTimer(); const handleStartRecording = () => { - setStatus("RECORDING"); - startRecording(); - timer.start(); + navigator.mediaDevices + .getUserMedia({ audio: true }) + .then(() => { + setStatus("RECORDING"); + startRecording(); + timer.start(); + }) + .catch(() => { + Notify.Error({ + msg: t("audio__permission_message"), + }); + setStatus("PERMISSION_DENIED"); + }); }; const handleStopRecording = () => { @@ -87,7 +98,7 @@ export default function AudioCaptureDialog(props: AudioCaptureDialogProps) { }, [show]); useEffect(() => { - if (autoRecord && show && status === "WAITING_TO_RECORD") { + if (autoRecord && show && status === "RECORDING") { handleStartRecording(); } }, [autoRecord, status, show]); diff --git a/src/components/Files/CameraCaptureDialog.tsx b/src/components/Files/CameraCaptureDialog.tsx index b5af6ace8d5..81981c9bf3a 100644 --- a/src/components/Files/CameraCaptureDialog.tsx +++ b/src/components/Files/CameraCaptureDialog.tsx @@ -3,8 +3,9 @@ import CareIcon from "../../CAREUI/icons/CareIcon"; import DialogModal from "@/components/Common/Dialog"; import ButtonV2, { Submit } from "@/components/Common/components/ButtonV2"; import { t } from "i18next"; -import { useCallback, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import useWindowDimensions from "@/common/hooks/useWindowDimensions"; +import * as Notify from "../../Utils/Notifications"; export interface CameraCaptureDialogProps { show: boolean; @@ -24,9 +25,29 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { height: { ideal: 2160 }, facingMode: "user", }; - + useEffect(() => { + if (!show) return; + navigator.mediaDevices.getUserMedia({ video: true }).catch(() => { + Notify.Warn({ + msg: t("camera_permission_denied"), + }); + onHide(); + }); + }, [show]); const handleSwitchCamera = useCallback(() => { - setCameraFacingFront((prevState) => !prevState); + const supportedConstraints = + navigator.mediaDevices.getSupportedConstraints(); + if ( + !isLaptopScreen && + typeof supportedConstraints.facingMode === "string" && + (supportedConstraints.facingMode as string).includes("environment") + ) { + setCameraFacingFront((prevState) => !prevState); + } else { + Notify.Warn({ + msg: t("switch_camera_is_not_available"), + }); + } }, []); const { width } = useWindowDimensions(); diff --git a/src/components/Form/AutoCompleteAsync.tsx b/src/components/Form/AutoCompleteAsync.tsx index ddec1503042..839a74c880c 100644 --- a/src/components/Form/AutoCompleteAsync.tsx +++ b/src/components/Form/AutoCompleteAsync.tsx @@ -100,7 +100,7 @@ const AutoCompleteAsync = (props: Props) => { immediate >
-
+
{ /> {!disabled && ( -
+
{hasSelection && !loading && !required && (
{ e.preventDefault(); onChange(null); @@ -142,16 +142,9 @@ const AutoCompleteAsync = (props: Props) => {
)} {loading ? ( - + ) : ( - + )}
diff --git a/src/components/Form/FormFields/Autocomplete.tsx b/src/components/Form/FormFields/Autocomplete.tsx index a93186ef080..0ed6bd4d1f1 100644 --- a/src/components/Form/FormFields/Autocomplete.tsx +++ b/src/components/Form/FormFields/Autocomplete.tsx @@ -14,7 +14,6 @@ import FormField from "./FormField"; import { classNames } from "../../../Utils/utils"; import { dropdownOptionClassNames } from "../MultiSelectMenuV2"; import { useTranslation } from "react-i18next"; - type OptionCallback = (option: T) => R; type AutocompleteFormFieldProps = FormFieldBaseProps & { @@ -84,6 +83,7 @@ type AutocompleteProps = { isLoading?: boolean; allowRawInput?: boolean; error?: string; + avatar?: boolean; } & ( | { required?: false; diff --git a/src/components/Form/FormFields/DateFormField.tsx b/src/components/Form/FormFields/DateFormField.tsx index 674d2572b6d..c27e7421cbb 100644 --- a/src/components/Form/FormFields/DateFormField.tsx +++ b/src/components/Form/FormFields/DateFormField.tsx @@ -1,6 +1,4 @@ -import DateInputV2, { - DatePickerPosition, -} from "@/components/Common/DateInputV2"; +import DateInputV2 from "@/components/Common/DateInputV2"; import { FormFieldBaseProps, useFormFieldPropsResolver } from "./Utils"; import FormField from "./FormField"; @@ -11,9 +9,10 @@ type Props = FormFieldBaseProps & { placeholder?: string; max?: Date; min?: Date; - position?: DatePickerPosition; disableFuture?: boolean; disablePast?: boolean; + allowTime?: boolean; + popOverClassName?: string; }; /** @@ -44,12 +43,13 @@ const DateFormField = (props: Props) => { ? new Date(field.value) : field.value } - onChange={field.handleChange} + onChange={field.handleChange as (d?: Date) => void} disabled={field.disabled} max={props.max ?? (props.disableFuture ? new Date() : undefined)} min={props.min ?? (props.disablePast ? yesterday() : undefined)} - position={props.position ?? "RIGHT"} placeholder={props.placeholder} + allowTime={props.allowTime} + popOverClassName={props.popOverClassName} /> ); diff --git a/src/components/Form/FormFields/DateRangeFormField.tsx b/src/components/Form/FormFields/DateRangeFormField.tsx index d7b7a62dbce..a5e814278b8 100644 --- a/src/components/Form/FormFields/DateRangeFormField.tsx +++ b/src/components/Form/FormFields/DateRangeFormField.tsx @@ -10,6 +10,7 @@ type Props = FormFieldBaseProps & { min?: Date; disableFuture?: boolean; disablePast?: boolean; + allowTime?: boolean; }; /** @@ -23,6 +24,7 @@ type Props = FormFieldBaseProps & { * label="Predicted date of birth" * required * disablePast // equivalent to min={new Date()} + * time={true} // allows picking time as well * /> * ``` */ @@ -38,6 +40,7 @@ const DateRangeFormField = (props: Props) => { disabled={field.disabled} min={props.min || (props.disableFuture ? new Date() : undefined)} max={props.max || (props.disablePast ? new Date() : undefined)} + allowTime={props.allowTime} /> ); diff --git a/src/components/Form/FormFields/RangeFormField.tsx b/src/components/Form/FormFields/RangeFormField.tsx index e7e9b1e9a53..c09cbfb2293 100644 --- a/src/components/Form/FormFields/RangeFormField.tsx +++ b/src/components/Form/FormFields/RangeFormField.tsx @@ -93,7 +93,8 @@ export default function RangeFormField(props: Props) { sliderDelta) * 100; - const handleChange = (v: number) => field.handleChange(unit.inversionFn(v)); + const handleChange = (v: number) => + field.handleChange(unit.inversionFn(props.step === 1 ? Math.round(v) : v)); const displayValue = value != null ? properRoundOf(value) : ""; diff --git a/src/components/Form/FormFields/Utils.ts b/src/components/Form/FormFields/Utils.ts index ed01f01c4ab..d7d2848a51e 100644 --- a/src/components/Form/FormFields/Utils.ts +++ b/src/components/Form/FormFields/Utils.ts @@ -1,3 +1,4 @@ +import { FocusEvent } from "react"; import { FieldError } from "../FieldValidators"; export type FieldChangeEvent = { name: string; value: T }; @@ -29,6 +30,8 @@ export type FormFieldBaseProps = { onChange: FieldChangeEventHandler; value?: T; error?: FieldError; + onFocus?: (event: FocusEvent) => void; + onBlur?: (event: FocusEvent) => void; }; /** diff --git a/src/components/HCX/ClaimCard.tsx b/src/components/HCX/ClaimCard.tsx deleted file mode 100644 index b45e7b3d503..00000000000 --- a/src/components/HCX/ClaimCard.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useLayoutEffect, useRef, useState } from "react"; - -import CareIcon from "../../CAREUI/icons/CareIcon"; -import ClaimCardCommunication from "./ClaimCardCommunication"; -import ClaimCardInfo from "./ClaimCardInfo"; -import { HCXClaimModel } from "./models"; - -interface IProps { - claim: HCXClaimModel; -} - -export default function ClaimCard({ claim }: IProps) { - const [showMessages, setShowMessages] = useState(false); - const [containerDimensions, setContainerDimensions] = useState({ - width: 0, - height: 0, - }); - const cardContainerRef = useRef(null); - - useLayoutEffect(() => { - if (cardContainerRef.current) { - setContainerDimensions({ - width: cardContainerRef.current.offsetWidth, - height: cardContainerRef.current.offsetHeight, - }); - } - }, [cardContainerRef]); - - return ( - <> -
- setShowMessages((prev) => !prev)} - /> -
- {showMessages ? ( -
- -
- ) : ( -
- -
- )} - - ); -} diff --git a/src/components/HCX/ClaimCardCommunication.tsx b/src/components/HCX/ClaimCardCommunication.tsx deleted file mode 100644 index 39edd63ce62..00000000000 --- a/src/components/HCX/ClaimCardCommunication.tsx +++ /dev/null @@ -1,300 +0,0 @@ -import * as Notification from "../../Utils/Notifications"; - -import { HCXClaimModel, HCXCommunicationModel } from "./models"; - -import ButtonV2 from "@/components/Common/components/ButtonV2"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import { FileUploadModel } from "../Patient/models"; -import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; -import { classNames } from "../../Utils/utils"; -import request from "../../Utils/request/request"; -import routes from "../../Redux/api"; -import useFileUpload from "../../Utils/useFileUpload"; -import useQuery from "../../Utils/request/useQuery"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -interface IProps { - claim: HCXClaimModel; -} - -export default function ClaimCardCommunication({ claim }: IProps) { - const { t } = useTranslation(); - const [inputText, setInputText] = useState(""); - const [isSendingCommunication, setIsSendingCommunication] = useState(false); - - const { - Input, - files, - error, - removeFile, - clearFiles, - handleFileUpload, - validateFiles, - } = useFileUpload({ - multiple: true, - type: "COMMUNICATION", - allowedExtensions: [".pdf", ".jpg", ".jpeg", ".png"], - }); - - const { data: communicationsResult, refetch: refetchCommunications } = - useQuery(routes.hcx.communications.list, { - query: { - claim: claim.id, - ordering: "-created_date", - }, - }); - - const handleSubmit = async () => { - if (!claim.id) return; - - if (!validateFiles()) return; - - setIsSendingCommunication(true); - - const { res, data } = await request(routes.hcx.communications.create, { - body: { - claim: claim.id, - content: [ - { - type: "text", - data: inputText, - }, - ], - }, - }); - - if (res?.status === 201 && data) { - await handleFileUpload(data.id as string); - - const { res } = await request(routes.hcx.communications.send, { - body: { - communication: data.id, - }, - }); - - if (res?.ok) { - Notification.Success({ msg: t("communication__sent_to_hcx") }); - - await refetchCommunications(); - - setInputText(""); - clearFiles(); - } - } - - setIsSendingCommunication(false); - }; - - return ( -
- - -
-
-
- {files.map((file, i) => ( -
-
- {file.type.includes("image") ? ( - {file.name} - ) : ( -
- -
- )} -
-
-

{file.name}

-
-

- {(file.size / 1024).toFixed(2)} KB -

- -
-
-
- ))} -
- setInputText(e.value)} - placeholder={t("enter_message")} - rows={1} - className="-mb-3 flex-1" - /> -
-
- -
- - {t("send_message")} - -
- {error && ( -

{error}

- )} -
- ); -} - -interface ICommunicationChatInterfaceProps { - communications: HCXCommunicationModel[]; -} - -function CommunicationChatInterface({ - communications, -}: ICommunicationChatInterfaceProps) { - return ( -
- {communications?.map((communication) => ( - - ))} -
- ); -} - -interface ICommunicationChatMessageProps { - communication: HCXCommunicationModel; -} - -function CommunicationChatMessage({ - communication, -}: ICommunicationChatMessageProps) { - const { t } = useTranslation(); - const [attachments, setAttachments] = useState( - null, - ); - const [isFetchingAttachments, setIsFetchingAttachments] = useState(false); - const [isDownloadingAttachment, setIsDownloadingAttachment] = useState(false); - - return ( -
- {communication.content?.map((message) => ( -

- {message.data} -

- ))} - {attachments ? ( -
- {attachments.length === 0 ? ( -

- {t("no_attachments_found")} -

- ) : ( - attachments.map((attachment) => ( -
-
-
- -
-
-
-

{attachment.name}

- -
-
- )) - )} -
- ) : ( - - )} -
- ); -} diff --git a/src/components/HCX/ClaimCardInfo.tsx b/src/components/HCX/ClaimCardInfo.tsx deleted file mode 100644 index 1b348a28f4f..00000000000 --- a/src/components/HCX/ClaimCardInfo.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { classNames, formatCurrency, formatDateTime } from "../../Utils/utils"; - -import { HCXClaimModel } from "./models"; -import { useTranslation } from "react-i18next"; - -interface IProps { - claim: HCXClaimModel; -} - -const claimStatus = { - PENDING: "pending", - APPROVED: "approved", - REJECTED: "rejected", -}; - -export default function ClaimCardInfo({ claim }: IProps) { - const { t } = useTranslation(); - - const status = - claim.outcome === "Complete" - ? claim.error_text - ? claimStatus.REJECTED - : claimStatus.APPROVED - : claimStatus.PENDING; - - return ( - <> -
-
-

- #{claim.id?.slice(0, 5)} -

- -

- {t("created_on")}{" "} - - . -

-
-
- {claim.use && ( - - {claim.use} - - )} - - {t(`claim__status__${status}`)} - -
-
-
-
-

- {claim.policy_object?.policy_id || "NA"} -

-

{t("policy__policy_id")}

-
-
-

- {claim.policy_object?.subscriber_id || "NA"} -

-

- {t("policy__subscriber_id")} -

-
-
-

- {claim.policy_object?.insurer_id?.split("@").shift() || "NA"} -

-

- {t("policy__insurer_id")} -

-
-
-

- {claim.policy_object?.insurer_name || "NA"} -

-

- {t("policy__insurer_name")} -

-
-
-
- - - - - - - - - - - {claim.items?.map((item) => ( - - - - - - - ))} - - - - - - - - - - - - -
- {t("claim__items")} - - {t("claim__item__price")} -
-
- {item.name} -
-
{item.id}
-
- {formatCurrency(item.price)} -
- {t("claim__total_claim_amount")} - - {claim.total_claim_amount && - formatCurrency(claim.total_claim_amount)} -
- {t("claim__total_approved_amount")} - - {claim.total_amount_approved - ? formatCurrency(claim.total_amount_approved) - : "NA"} -
-
- {claim.error_text && ( -
- {claim.error_text} -
- )} - - ); -} diff --git a/src/components/HCX/ClaimCreatedModal.tsx b/src/components/HCX/ClaimCreatedModal.tsx deleted file mode 100644 index 4edd4fb11f8..00000000000 --- a/src/components/HCX/ClaimCreatedModal.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import * as Notification from "../../Utils/Notifications"; - -import CareIcon from "../../CAREUI/icons/CareIcon"; -import DialogModal from "@/components/Common/Dialog"; -import { FileUpload } from "../Files/FileUpload"; -import { HCXClaimModel } from "./models"; -import { Submit } from "@/components/Common/components/ButtonV2"; -import request from "../../Utils/request/request"; -import routes from "../../Redux/api"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -interface Props { - claim: HCXClaimModel; - show: boolean; - onClose: () => void; -} - -export default function ClaimCreatedModal({ claim, ...props }: Props) { - const { t } = useTranslation(); - - const [isMakingClaim, setIsMakingClaim] = useState(false); - - const { use } = claim; - - const handleSubmit = async () => { - setIsMakingClaim(true); - - const { res } = await request(routes.hcx.claims.makeClaim, { - body: { claim: claim.id }, - }); - - if (res?.ok) { - Notification.Success({ msg: `${use} requested` }); - props.onClose(); - } - - setIsMakingClaim(false); - }; - return ( - - {isMakingClaim && ( - - )} - {isMakingClaim - ? t("claim__requesting_claim") - : t("claim__request_claim")} - - } - > -
- -
-
- ); -} diff --git a/src/components/HCX/ClaimsItemsBuilder.tsx b/src/components/HCX/ClaimsItemsBuilder.tsx deleted file mode 100644 index b4158c4443b..00000000000 --- a/src/components/HCX/ClaimsItemsBuilder.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import { - FieldChangeEvent, - FormFieldBaseProps, - useFormFieldPropsResolver, -} from "../Form/FormFields/Utils"; -import FormField, { FieldLabel } from "../Form/FormFields/FormField"; - -import AutocompleteFormField from "../Form/FormFields/Autocomplete"; -import ButtonV2 from "@/components/Common/components/ButtonV2"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import { HCXItemModel } from "./models"; -import { ITEM_CATEGORIES } from "./constants"; -import PMJAYProcedurePackageAutocomplete from "@/components/Common/PMJAYProcedurePackageAutocomplete"; -import TextFormField from "../Form/FormFields/TextFormField"; -import { useTranslation } from "react-i18next"; - -type Props = FormFieldBaseProps; - -export default function ClaimsItemsBuilder(props: Props) { - const { t } = useTranslation(); - - const field = useFormFieldPropsResolver(props); - - const handleUpdate = (index: number) => { - return (event: FieldChangeEvent) => { - if (event.name === "hbp") { - field.handleChange( - (props.value || [])?.map((obj, i) => - i === index - ? { - ...obj, - id: event.value.code, - name: event.value.name, - price: event.value.price, - } - : obj, - ), - ); - } else { - field.handleChange( - (props.value || [])?.map((obj, i) => - i === index ? { ...obj, [event.name]: event.value } : obj, - ), - ); - } - }; - }; - - const handleRemove = (index: number) => { - return () => { - field.handleChange((props.value || [])?.filter((obj, i) => i !== index)); - }; - }; - - return ( - -
- {props.value?.map((obj, index) => { - return ( -
-
- - {t("claim__item")} {index + 1} - - {!props.disabled && ( - - {t("remove")} - - - )} -
- -
- o.display} - optionValue={(o) => o.code} - value={obj.category} - onChange={handleUpdate(index)} - disabled={props.disabled} - errorClassName="hidden" - /> - -
- {obj.category === "HBP" && !obj.id ? ( - <> - - - ) : ( - <> - - - - handleUpdate(index)({ - name: event.name, - value: parseFloat(event.value), - }) - } - disabled={props.disabled} - errorClassName="hidden" - /> - - )} -
-
-
- ); - })} -
-
- ); -} diff --git a/src/components/HCX/CreateClaimCard.tsx b/src/components/HCX/CreateClaimCard.tsx deleted file mode 100644 index 7d4b7d3edb0..00000000000 --- a/src/components/HCX/CreateClaimCard.tsx +++ /dev/null @@ -1,262 +0,0 @@ -import * as Notification from "../../Utils/Notifications"; - -import ButtonV2, { Submit } from "@/components/Common/components/ButtonV2"; -import { HCXClaimModel, HCXItemModel, HCXPolicyModel } from "./models"; -import { classNames, formatCurrency } from "../../Utils/utils"; - -import CareIcon from "../../CAREUI/icons/CareIcon"; -import ClaimCreatedModal from "./ClaimCreatedModal"; -import ClaimsItemsBuilder from "./ClaimsItemsBuilder"; -import DialogModal from "@/components/Common/Dialog"; -import HCXPolicyEligibilityCheck from "./PolicyEligibilityCheck"; -import PatientInsuranceDetailsEditor from "./PatientInsuranceDetailsEditor"; -import { ProcedureType } from "@/components/Common/prescription-builder/ProcedureBuilder"; -import { SelectFormField } from "../Form/FormFields/SelectFormField"; -import request from "../../Utils/request/request"; -import routes from "../../Redux/api"; -import useQuery from "../../Utils/request/useQuery"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; - -interface Props { - consultationId: string; - patientId: string; - setIsCreating: (creating: boolean) => void; - isCreating: boolean; - use?: "preauthorization" | "claim"; -} - -export default function CreateClaimCard({ - consultationId, - patientId, - setIsCreating, - isCreating, - use = "preauthorization", -}: Props) { - const { t } = useTranslation(); - - const [showAddPolicy, setShowAddPolicy] = useState(false); - const [policy, setPolicy] = useState(); - const [items, setItems] = useState(); - const [itemsError, setItemsError] = useState(); - const [createdClaim, setCreatedClaim] = useState(); - const [use_, setUse_] = useState(use); - - const { res: consultationRes, data: consultationData } = useQuery( - routes.getConsultation, - { pathParams: { id: consultationId }, prefetch: !!consultationId }, - ); - - const autoFill = async (policy?: HCXPolicyModel) => { - if (!policy) { - setItems([]); - return; - } - - const { res, data: latestApprovedPreAuth } = await request( - routes.hcx.claims.list, - { - query: { - consultation: consultationId, - policy: policy.id, - ordering: "-modified_date", - use: "preauthorization", - outcome: "complete", - limit: 1, - }, - }, - ); - - if (res?.ok && latestApprovedPreAuth?.results.length !== 0) { - setItems(latestApprovedPreAuth?.results[0].items ?? []); - return; - } - if (consultationRes?.ok && Array.isArray(consultationData?.procedure)) { - setItems( - consultationData.procedure.map((obj: ProcedureType) => { - return { - id: obj.procedure ?? "", - name: obj.procedure ?? "", - price: 0.0, - category: "900000", // provider's packages - }; - }), - ); - } else { - setItems([]); - } - }; - - const validate = () => { - if (!policy) { - Notification.Error({ msg: t("select_policy") }); - return false; - } - if (policy?.outcome !== "Complete") { - Notification.Error({ msg: t("select_eligible_policy") }); - return false; - } - if (!items || items.length === 0) { - setItemsError(t("claim__item__add_at_least_one")); - return false; - } - if (items?.some((p) => !p.id || !p.name || p.price === 0 || !p.category)) { - setItemsError(t("claim__item__fill_all_details")); - return false; - } - - return true; - }; - - const handleSubmit = async () => { - if (!validate()) return; - - setIsCreating(true); - - const { res, data } = await request(routes.hcx.claims.create, { - body: { - policy: policy?.id, - items, - consultation: consultationId, - use: use_, - }, - silent: true, - }); - - if (res?.ok && data) { - setItems([]); - setItemsError(undefined); - setPolicy(undefined); - setCreatedClaim(data); - } else { - Notification.Error({ msg: t(`claim__failed_to_create_${use_}`) }); - } - - setIsCreating(false); - }; - - return ( -
- {createdClaim && ( - setCreatedClaim(undefined)} - /> - )} - setShowAddPolicy(false)} - description={t("edit_policy_description")} - className="w-full max-w-screen-md" - > - setShowAddPolicy(false)} - /> - - - {/* Check Insurance Policy Eligibility */} -
-
-

{t("check_policy_eligibility")}

- setShowAddPolicy(true)} - ghost - border - > - - {t("edit_policy")} - -
- { - setPolicy(policy); - autoFill(policy); - }} - /> -
- - {/* Procedures */} -
-
-

{t("claim__items")}

- - setItems([...(items ?? []), { name: "", id: "", price: 0 }]) - } - > - - {t("claim__add_item")} - -
- - {t("select_policy_to_add_items")} - - setItems(value)} - error={itemsError} - /> -
- {t("total_amount")} :{" "} - {items ? ( - - {formatCurrency( - items.map((p) => p.price).reduce((a, b) => a + b, 0.0), - )} - - ) : ( - "--" - )} -
-
- -
- setUse_(value)} - position="below" - className="w-52 max-sm:w-full" - optionLabel={(value) => value.label} - optionValue={(value) => value.id as "preauthorization" | "claim"} - /> - - {isCreating && } - {isCreating - ? t(`claim__creating_${use_}`) - : t(`claim__create_${use_}`)} - -
-
- ); -} diff --git a/src/components/HCX/InsuranceDetailsBuilder.tsx b/src/components/HCX/InsuranceDetailsBuilder.tsx index 1d963200ee3..ca7473c9dc1 100644 --- a/src/components/HCX/InsuranceDetailsBuilder.tsx +++ b/src/components/HCX/InsuranceDetailsBuilder.tsx @@ -10,11 +10,11 @@ import CareIcon from "../../CAREUI/icons/CareIcon"; import { HCXPolicyModel } from "./models"; import InsurerAutocomplete from "./InsurerAutocomplete"; import TextFormField from "../Form/FormFields/TextFormField"; +import careConfig from "@careConfig"; import { classNames } from "../../Utils/utils"; import request from "../../Utils/request/request"; import routes from "../../Redux/api"; import { useTranslation } from "react-i18next"; -import careConfig from "@careConfig"; type Props = FormFieldBaseProps & { gridView?: boolean }; diff --git a/src/components/HCX/constants.ts b/src/components/HCX/constants.ts deleted file mode 100644 index ed4235a3386..00000000000 --- a/src/components/HCX/constants.ts +++ /dev/null @@ -1,58 +0,0 @@ -interface ItemCategory { - code: string; - system: string; - display: string; -} - -export const ITEM_CATEGORIES: ItemCategory[] = [ - { - code: "100000", - system: "https://irdai.gov.in/benefit-billing-group-code", - display: "Room & Nursing Charges", - }, - { - code: "200000", - system: "https://irdai.gov.in/benefit-billing-group-code", - display: "ICU Charges", - }, - { - code: "300000", - system: "https://irdai.gov.in/benefit-billing-group-code", - display: "OT Charges", - }, - { - code: "400000", - system: "https://irdai.gov.in/benefit-billing-group-code", - display: "Medicine & Consumables Charges", - }, - { - code: "500000", - system: "https://irdai.gov.in/benefit-billing-group-code", - display: "Professional Fees Charges", - }, - { - code: "600000", - system: "https://irdai.gov.in/benefit-billing-group-code", - display: "Investigation Charges", - }, - { - code: "700000", - system: "https://irdai.gov.in/benefit-billing-group-code", - display: "Ambulance Charges", - }, - { - code: "800000", - system: "https://irdai.gov.in/benefit-billing-group-code", - display: "Miscellaneous Charges", - }, - { - code: "900000", - system: "https://irdai.gov.in/benefit-billing-group-code", - display: "Provider Package Charges", - }, - { - code: "HBP", - system: "https://pmjay.gov.in/benefit-billing-group-code", - display: "NHA Package Charges", - }, -]; diff --git a/src/components/HCX/misc.ts b/src/components/HCX/misc.ts deleted file mode 100644 index dba0a290b85..00000000000 --- a/src/components/HCX/misc.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { UserBareMinimum } from "../Users/models"; - -export type PerformedByModel = UserBareMinimum; diff --git a/src/components/HCX/models.ts b/src/components/HCX/models.ts index e75fc28214d..416b3ec6abe 100644 --- a/src/components/HCX/models.ts +++ b/src/components/HCX/models.ts @@ -1,9 +1,6 @@ -import { ConsultationModel } from "../Facility/models"; -import { PatientModel } from "../Patient/models"; -import { PerformedByModel } from "./misc"; - -export type HCXPriority = "Immediate" | "Normal" | "Deferred"; +import { PatientModel } from "@/components/Patient/models"; +export type HCXPolicyPriority = "Immediate" | "Normal" | "Deferred"; export type HCXPolicyStatus = | "Active" | "Cancelled" @@ -29,60 +26,10 @@ export interface HCXPolicyModel { insurer_id?: string; insurer_name?: string; status?: HCXPolicyStatus; - priority?: HCXPriority; + priority?: HCXPolicyPriority; purpose?: HCXPolicyPurpose; outcome?: HCXPolicyOutcome; error_text?: string; created_date?: string; modified_date?: string; } - -export interface HCXCommunicationModel { - id?: string; - identifier?: string; - claim?: string; - claim_object?: HCXClaimModel; - content?: { type: string; data: string }[]; - created_by?: string | null; - last_modified_by?: string | null; - created_date?: string; - modified_date?: string; -} - -export interface HCXItemModel { - id: string; - name: string; - price: number; - category?: string; -} - -export type HCXClaimUse = "Claim" | "Pre Authorization" | "Pre Determination"; -export type HCXClaimStatus = HCXPolicyStatus; -export type HCXClaimType = - | "Institutional" - | "Oral" - | "Pharmacy" - | "Professional" - | "Vision"; -export type HCXClaimOutcome = HCXPolicyOutcome; - -export interface HCXClaimModel { - id?: string; - consultation: string; - consultation_object?: ConsultationModel; - policy: string; - policy_object?: HCXPolicyModel; - items?: HCXItemModel[]; - total_claim_amount?: number; - total_amount_approved?: number; - use?: HCXClaimUse; - status?: HCXClaimStatus; - priority?: HCXPriority; - type?: HCXClaimType; - outcome?: HCXClaimOutcome; - error_text?: string; - created_by?: PerformedByModel; - last_modified_by?: PerformedByModel; - created_date?: string; - modified_date?: string; -} diff --git a/src/components/HCX/validators.ts b/src/components/HCX/validators.ts index 5d5c979a405..3f10ac156d1 100644 --- a/src/components/HCX/validators.ts +++ b/src/components/HCX/validators.ts @@ -1,6 +1,6 @@ -import { t } from "i18next"; import { FieldValidator } from "../Form/FieldValidators"; import { HCXPolicyModel } from "./models"; +import { t } from "i18next"; const HCXPolicyValidator: FieldValidator = ( value, diff --git a/src/components/LogUpdate/Sections/BloodSugar.tsx b/src/components/LogUpdate/Sections/BloodSugar.tsx index aecfe887f51..9af7ad32069 100644 --- a/src/components/LogUpdate/Sections/BloodSugar.tsx +++ b/src/components/LogUpdate/Sections/BloodSugar.tsx @@ -32,7 +32,7 @@ const BloodSugar = ({ log, onChange }: LogUpdateSectionProps) => { value={log.insulin_intake_dose} min={0} max={100} - step={1} + step={0.1} /> - setCustomTime(value)} + value={customTime ? new Date(customTime) : new Date()} + onChange={({ value }) => + setCustomTime(dayjs(value).format("YYYY-MM-DDTHH:mm")) + } disabled={!isCustomTime} - min={dayjs(prescription.created_date).format("YYYY-MM-DDTHH:mm")} - max={dayjs().format("YYYY-MM-DDTHH:mm")} + min={new Date(prescription.created_date)} + max={new Date()} + errorClassName="hidden" + allowTime />
diff --git a/src/components/Medicine/MedicineAdministration.tsx b/src/components/Medicine/MedicineAdministration.tsx index 1b29fa7d108..d2daf370207 100644 --- a/src/components/Medicine/MedicineAdministration.tsx +++ b/src/components/Medicine/MedicineAdministration.tsx @@ -9,12 +9,12 @@ import { Error, Success } from "../../Utils/Notifications"; import { formatDateTime } from "../../Utils/utils"; import { useTranslation } from "react-i18next"; import dayjs from "../../Utils/dayjs"; -import TextFormField from "../Form/FormFields/TextFormField"; import request from "../../Utils/request/request"; import MedicineRoutes from "./routes"; import useSlug from "@/common/hooks/useSlug"; import DosageFormField from "../Form/FormFields/DosageFormField"; import { AdministrationDosageValidator } from "./validators"; +import DateFormField from "../Form/FormFields/DateFormField"; interface Props { prescriptions: Prescription[]; @@ -179,7 +179,7 @@ export default function MedicineAdministration(props: Props) { } errorClassName="hidden" /> -
+
- { setCustomTime((arr) => { const newArr = [...arr]; - newArr[index] = value; + newArr[index] = dayjs(value).format("YYYY-MM-DDTHH:mm"); return newArr; }); }} disabled={!shouldAdminister[index] || !isCustomTime[index]} - min={dayjs(obj.created_date).format("YYYY-MM-DDTHH:mm")} - max={dayjs().format("YYYY-MM-DDTHH:mm")} + min={new Date(obj.created_date)} + max={new Date()} + className="w-full" + errorClassName="hidden" + allowTime />
diff --git a/src/components/Medicine/models.ts b/src/components/Medicine/models.ts index bb596e88b3e..68e34d09798 100644 --- a/src/components/Medicine/models.ts +++ b/src/components/Medicine/models.ts @@ -1,5 +1,5 @@ -import { PerformedByModel } from "../HCX/misc"; import { PRESCRIPTION_ROUTES } from "./CreatePrescriptionForm"; +import { UserBareMinimum } from "@/components/Users/models"; export const DOSAGE_UNITS = [ "mg", @@ -29,7 +29,7 @@ interface BasePrescription { readonly prescription_type?: "DISCHARGE" | "REGULAR"; readonly discontinued: boolean; discontinued_reason?: string; - readonly prescribed_by: PerformedByModel; + readonly prescribed_by: UserBareMinimum; readonly discontinued_date: string; readonly last_administration?: MedicineAdministrationRecord; readonly is_migrated: boolean; @@ -72,8 +72,8 @@ export type MedicineAdministrationRecord = { notes: string; dosage?: DosageValue; administered_date?: string; - readonly administered_by: PerformedByModel; - readonly archived_by: PerformedByModel | undefined; + readonly administered_by: UserBareMinimum; + readonly archived_by: UserBareMinimum | undefined; readonly archived_on: string | undefined; readonly created_date: string; readonly modified_date: string; diff --git a/src/components/Patient/DailyRounds.tsx b/src/components/Patient/DailyRounds.tsx index d37e942c109..f6ac117c2ec 100644 --- a/src/components/Patient/DailyRounds.tsx +++ b/src/components/Patient/DailyRounds.tsx @@ -29,7 +29,6 @@ import Page from "@/components/Common/components/Page"; import RangeAutocompleteFormField from "../Form/FormFields/RangeAutocompleteFormField"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; -import TextFormField from "../Form/FormFields/TextFormField"; import { FieldChangeEvent } from "../Form/FormFields/Utils"; import PatientCategorySelect from "./PatientCategorySelect"; import RadioFormField from "../Form/FormFields/RadioFormField"; @@ -55,6 +54,7 @@ import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField"; import SymptomsApi from "../Symptoms/api"; import { scrollTo } from "../../Utils/utils"; import { ICD11DiagnosisModel } from "../Facility/models"; +import DateFormField from "../Form/FormFields/DateFormField"; import NursingCare from "../LogUpdate/Sections/NursingCare"; import Loading from "@/components/Common/Loading"; @@ -634,16 +634,24 @@ export const DailyRounds = (props: any) => { />
- + handleFormFieldChange({ + ...e, + value: dayjs(e.value).format("YYYY-MM-DDTHH:mm"), + }) + } + allowTime + errorClassName="hidden" />
diff --git a/src/components/Patient/InsuranceDetails.tsx b/src/components/Patient/InsuranceDetails.tsx index 58efe498712..d90c5dbcd27 100644 --- a/src/components/Patient/InsuranceDetails.tsx +++ b/src/components/Patient/InsuranceDetails.tsx @@ -1,10 +1,10 @@ import { HCXPolicyModel } from "../HCX/models"; import { InsuranceDetialsCard } from "./InsuranceDetailsCard"; +import Loading from "@/components/Common/Loading"; import Page from "@/components/Common/components/Page"; import routes from "../../Redux/api"; import useQuery from "../../Utils/request/useQuery"; -import Loading from "@/components/Common/Loading"; interface IProps { facilityId: string; id: string; diff --git a/src/components/Patient/ManagePatients.tsx b/src/components/Patient/ManagePatients.tsx index 22399b081f1..2c1119be7e8 100644 --- a/src/components/Patient/ManagePatients.tsx +++ b/src/components/Patient/ManagePatients.tsx @@ -834,7 +834,7 @@ export const PatientManager = () => { >

- Add Patient Details + Add Patient

diff --git a/src/components/Patient/PatientInfoCard.tsx b/src/components/Patient/PatientInfoCard.tsx index bdc8b0e672d..f5bae4aba54 100644 --- a/src/components/Patient/PatientInfoCard.tsx +++ b/src/components/Patient/PatientInfoCard.tsx @@ -1,10 +1,5 @@ import * as Notification from "../../Utils/Notifications"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; + import { CONSULTATION_SUGGESTION, DISCHARGE_REASONS, @@ -13,11 +8,14 @@ import { TELEMEDICINE_ACTIONS, } from "@/common/constants"; import { ConsultationModel, PatientCategory } from "../Facility/models"; -import { Switch, MenuItem, Field, Label } from "@headlessui/react"; +import { Field, Label, MenuItem, Switch } from "@headlessui/react"; import { Link, navigate } from "raviger"; -import { useState } from "react"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import dayjs from "../../Utils/dayjs"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { classNames, formatDate, @@ -26,29 +24,33 @@ import { formatPatientAge, humanizeStrings, } from "../../Utils/utils"; + import ABHAProfileModal from "../ABDM/ABHAProfileModal"; -import DialogModal from "@/components/Common/Dialog"; -import ButtonV2 from "@/components/Common/components/ButtonV2"; +import { AbhaNumberModel } from "../ABDM/types/abha"; +import { AuthorizedForConsultationRelatedActions } from "../../CAREUI/misc/AuthorizedChild"; import Beds from "../Facility/Consultations/Beds"; +import ButtonV2 from "@/components/Common/components/ButtonV2"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import DialogModal from "@/components/Common/Dialog"; +import DischargeModal from "../Facility/DischargeModal"; +import DischargeSummaryModal from "../Facility/DischargeSummaryModal"; +import DropdownMenu from "@/components/Common/components/Menu"; +import FetchRecordsModal from "../ABDM/FetchRecordsModal"; +import LinkAbhaNumber from "../ABDM/LinkAbhaNumber/index"; +import { Mews } from "../Facility/Consultations/Mews"; +import { PLUGIN_Component } from "@/PluginEngine"; import { PatientModel } from "./models"; +import { SkillModel } from "../Users/models"; +import careConfig from "@careConfig"; +import { cn } from "@/lib/utils.js"; +import dayjs from "../../Utils/dayjs"; import request from "../../Utils/request/request"; import routes from "../../Redux/api"; -import DropdownMenu from "@/components/Common/components/Menu"; import { triggerGoal } from "../../Integrations/Plausible"; - import useAuthUser from "@/common/hooks/useAuthUser"; -import { Mews } from "../Facility/Consultations/Mews"; -import DischargeSummaryModal from "../Facility/DischargeSummaryModal"; -import DischargeModal from "../Facility/DischargeModal"; -import { useTranslation } from "react-i18next"; import useQuery from "../../Utils/request/useQuery"; -import FetchRecordsModal from "../ABDM/FetchRecordsModal"; -import { AbhaNumberModel } from "../ABDM/types/abha"; -import { SkillModel } from "../Users/models"; -import { AuthorizedForConsultationRelatedActions } from "../../CAREUI/misc/AuthorizedChild"; -import LinkAbhaNumber from "../ABDM/LinkAbhaNumber/index"; -import careConfig from "@careConfig"; -import { cn } from "@/lib/utils.js"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; const formatSkills = (arr: SkillModel[]) => { const skills = arr.map((skill) => skill.skill_object.name); @@ -673,27 +675,25 @@ export default function PatientInfoCard(props: { "l-file-medical", consultation?.id, ], - ] - .concat( - careConfig.hcx.enabled - ? [ - [ - `/facility/${patient.facility}/patient/${patient.id}/consultation/${consultation?.id}/claims`, - "Claims", - "l-copy-landscape", - consultation?.id, - ], - ] - : [], - ) - .map( - (action: any, i) => - action[3] && ( -
- + action[3] && ( +
+ { + if ( ![ "Treatment Summary", "Consent Records", @@ -701,47 +701,35 @@ export default function PatientInfoCard(props: { consultation?.admitted && !consultation?.current_bed && i === 1 - ? "" - : `${action[0]}` - } - onClick={() => { - if ( - ![ - "Treatment Summary", - "Consent Records", - ].includes(action[1]) && - consultation?.admitted && - !consultation?.current_bed && - i === 1 - ) { - Notification.Error({ - msg: "Please assign a bed to the patient", - }); - setOpen(true); - } - triggerGoal("Patient Card Button Clicked", { - buttonName: action[1], - consultationId: consultation?.id, - userId: authUser?.id, + ) { + Notification.Error({ + msg: "Please assign a bed to the patient", }); - }} - > - - {action[1]} - - {action?.[4]?.[0] && ( - <> -

- {action[4][1]} -

- - )} -
- ), - )} + setOpen(true); + } + triggerGoal("Patient Card Button Clicked", { + buttonName: action[1], + consultationId: consultation?.id, + userId: authUser?.id, + }); + }} + > + + {action[1]} + + {action?.[4]?.[0] && ( + <> +

+ {action[4][1]} +

+ + )} +
+ ), + )}
@@ -926,6 +914,13 @@ export default function PatientInfoCard(props: { )}
+ + +
{ ? formData.last_vaccinated_date : null : null, - name: _.startCase(_.toLower(formData.name)), + name: startCase(toLower(formData.name)), pincode: formData.pincode ? formData.pincode : undefined, gender: Number(formData.gender), nationality: formData.nationality, @@ -1219,7 +1219,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { {...field("date_of_birth")} errorClassName="hidden" required - position="LEFT" disableFuture />
@@ -1330,7 +1329,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { containerClassName="w-full" {...field("last_menstruation_start_date")} label="Last Menstruation Start Date" - position="LEFT" disableFuture required /> @@ -1355,7 +1353,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { containerClassName="w-full" {...field("date_of_delivery")} label="Date of Delivery" - position="LEFT" disableFuture required /> @@ -1727,7 +1724,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { {...field("last_vaccinated_date")} label="Last Date of Vaccination" disableFuture={true} - position="LEFT" />
@@ -1759,7 +1755,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { {...field("date_declared_positive")} label="Date Patient is Declared Positive for COVID" disableFuture - position="LEFT" />
@@ -1770,7 +1765,6 @@ export const PatientRegister = (props: PatientRegisterProps) => { id="date_of_test" label="Date of Sample given for COVID Test" disableFuture - position="LEFT" />
diff --git a/src/components/Patient/SampleDetails.tsx b/src/components/Patient/SampleDetails.tsx index 2e8b93c7bb4..aaf524ef0ae 100644 --- a/src/components/Patient/SampleDetails.tsx +++ b/src/components/Patient/SampleDetails.tsx @@ -5,7 +5,7 @@ import ButtonV2 from "@/components/Common/components/ButtonV2"; import Card from "../../CAREUI/display/Card"; import { FileUpload } from "../Files/FileUpload"; import Page from "@/components/Common/components/Page"; -import * as _ from "lodash-es"; +import { startCase, camelCase, capitalize } from "lodash-es"; import { formatDateTime, formatPatientAge } from "../../Utils/utils"; import { navigate } from "raviger"; @@ -244,11 +244,11 @@ export const SampleDetails = ({ id }: DetailRoute) => {
Status: {" "} - {_.startCase(_.camelCase(flow.status))} + {startCase(camelCase(flow.status))}
Label:{" "} - {_.capitalize(flow.notes)} + {capitalize(flow.notes)}
Created On :{" "} @@ -332,7 +332,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { Doctor's Name:{" "} - {_.startCase(_.camelCase(sampleDetails.doctor_name))} + {startCase(camelCase(sampleDetails.doctor_name))}
)} {sampleDetails?.diagnosis && ( @@ -415,7 +415,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { Sample Type:{" "} - {_.startCase(_.camelCase(sampleDetails.sample_type))} + {startCase(camelCase(sampleDetails.sample_type))}
)} diff --git a/src/components/Patient/SampleTestCard.tsx b/src/components/Patient/SampleTestCard.tsx index ad54850ea10..e4b29350ff8 100644 --- a/src/components/Patient/SampleTestCard.tsx +++ b/src/components/Patient/SampleTestCard.tsx @@ -4,7 +4,7 @@ import { SampleTestModel } from "./models"; import { SAMPLE_TEST_STATUS } from "@/common/constants"; import * as Notification from "../../Utils/Notifications"; import UpdateStatusDialog from "./UpdateStatusDialog"; -import * as _ from "lodash-es"; +import { startCase, camelCase } from "lodash-es"; import { formatDateTime } from "../../Utils/utils"; import ButtonV2 from "@/components/Common/components/ButtonV2"; import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; @@ -98,7 +98,7 @@ export const SampleTestCard = (props: SampleDetailsProps) => { Status{" "}
- {_.startCase(_.camelCase(itemData.status))} + {startCase(camelCase(itemData.status))}
@@ -133,7 +133,7 @@ export const SampleTestCard = (props: SampleDetailsProps) => { Result{" "}
- {_.startCase(_.camelCase(itemData.result))} + {startCase(camelCase(itemData.result))}
diff --git a/src/components/Patient/models.tsx b/src/components/Patient/models.tsx index 8c27199461b..b4f35c4dc9b 100644 --- a/src/components/Patient/models.tsx +++ b/src/components/Patient/models.tsx @@ -1,5 +1,3 @@ -import { ConsultationModel, PatientCategory } from "../Facility/models"; -import { PerformedByModel } from "../HCX/misc"; import { APPETITE_CHOICES, BLADDER_DRAINAGE_CHOICES, @@ -25,6 +23,9 @@ import { URINATION_FREQUENCY_CHOICES, VENTILATOR_MODE_OPTIONS, } from "@/common/constants"; +import { ConsultationModel, PatientCategory } from "../Facility/models"; + +import { UserBareMinimum } from "@/components/Users/models"; export interface FlowModel { id?: number; @@ -130,8 +131,8 @@ export interface PatientModel { fit_for_blood_donation?: boolean; date_declared_positive?: string; is_declared_positive?: boolean; - last_edited?: PerformedByModel; - created_by?: PerformedByModel; + last_edited?: UserBareMinimum; + created_by?: UserBareMinimum; assigned_to?: { first_name?: string; username?: string; last_name?: string }; assigned_to_object?: AssignedToObjectModel; occupation?: Occupation; @@ -325,8 +326,8 @@ export interface DailyRoundsModel { rounds_type?: (typeof DailyRoundTypes)[number]; last_updated_by_telemedicine?: boolean; created_by_telemedicine?: boolean; - created_by?: PerformedByModel; - last_edited_by?: PerformedByModel; + created_by?: UserBareMinimum; + last_edited_by?: UserBareMinimum; bed?: string; pain_scale_enhanced?: IPainScale[]; in_prone_position?: boolean; @@ -429,13 +430,13 @@ export interface FileUploadModel { associating_id?: string; created_date?: string; upload_completed?: boolean; - uploaded_by?: PerformedByModel; + uploaded_by?: UserBareMinimum; file_category?: FileCategory; read_signed_url?: string; is_archived?: boolean; archive_reason?: string; extension?: string; - archived_by?: PerformedByModel; + archived_by?: UserBareMinimum; archived_datetime?: string; } diff --git a/src/components/Resource/ListView.tsx b/src/components/Resource/ListView.tsx deleted file mode 100644 index 8c05c1c665f..00000000000 --- a/src/components/Resource/ListView.tsx +++ /dev/null @@ -1,235 +0,0 @@ -import { navigate } from "raviger"; -import ListFilter from "./ListFilter"; -import { formatFilter } from "./Commons"; -import BadgesList from "./BadgesList"; -import { formatDateTime } from "../../Utils/utils"; -import useFilters from "@/common/hooks/useFilters"; -import { ExportButton } from "@/components/Common/Export"; -import ButtonV2 from "@/components/Common/components/ButtonV2"; -import { useTranslation } from "react-i18next"; -import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import dayjs from "../../Utils/dayjs"; -import useQuery from "../../Utils/request/useQuery"; -import routes from "../../Redux/api"; -import Page from "@/components/Common/components/Page"; -import SearchInput from "../Form/SearchInput"; -import request from "../../Utils/request/request"; - -import Loading from "@/components/Common/Loading"; -export default function ListView() { - const { - qParams, - Pagination, - FilterBadges, - advancedFilter, - resultsPerPage, - updateQuery, - } = useFilters({ cacheBlacklist: ["title"] }); - - const { t } = useTranslation(); - - const onBoardViewBtnClick = () => - navigate("/resource/board", { query: qParams }); - const appliedFilters = formatFilter(qParams); - - const { loading, data, refetch } = useQuery(routes.listResourceRequests, { - query: formatFilter({ - ...qParams, - offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, - }), - }); - - const showResourceCardList = (data: any) => { - if (data && !data.length) { - return ( -
- No requests to show. -
- ); - } - - return data.map((resource: any) => ( -
-
-
-
-
-
- {resource.title} -
-
- {resource.emergency && ( - - Emergency - - )} -
-
-
-
-
- -
- {resource.status} -
- -
-
-
- -
- {(resource.origin_facility_object || {}).name} -
- -
-
-
- -
- {(resource.approving_facility_object || {}).name} -
- -
-
-
- - -
- {(resource.assigned_facility_object || {}).name || - "Yet to be decided"} -
- -
- -
-
- -
- {formatDateTime(resource.modified_date) || "--"} -
- -
-
-
- -
- -
-
-
-
- )); - }; - - return ( - { - const { data } = await request(routes.downloadResourceRequests, { - query: { ...appliedFilters, csv: true }, - }); - return data ?? null; - }} - filenamePrefix="resource_requests" - /> - } - breadcrumbs={false} - options={ - <> -
- updateQuery({ [e.name]: e.value })} - placeholder={t("search_resource")} - /> -
-
- {/* dummy div to align space as per board view */} -
-
- - - {t("board_view")} - - - advancedFilter.setShow(true)} - /> -
- - } - > - - -
- {loading ? ( - - ) : ( -
-
- -
- -
- {data?.results && showResourceCardList(data?.results)} -
-
- -
-
- )} -
- -
- ); -} diff --git a/src/components/Resource/BadgesList.tsx b/src/components/Resource/ResourceBadges.tsx similarity index 100% rename from src/components/Resource/BadgesList.tsx rename to src/components/Resource/ResourceBadges.tsx diff --git a/src/components/Resource/ResourceBlock.tsx b/src/components/Resource/ResourceBlock.tsx new file mode 100644 index 00000000000..df6fa14934f --- /dev/null +++ b/src/components/Resource/ResourceBlock.tsx @@ -0,0 +1,96 @@ +import { useTranslation } from "react-i18next"; +import { ResourceModel } from "../Facility/models"; +import { classNames, formatDateTime, formatName } from "@/Utils/utils"; +import dayjs from "dayjs"; +import CareIcon from "@/CAREUI/icons/CareIcon"; +import { Link } from "raviger"; + +export default function ResourceBlock(props: { resource: ResourceModel }) { + const { resource } = props; + const { t } = useTranslation(); + + return ( +
+
+
+
+
{resource.title}
+
+
+ {resource.emergency && ( + + {t("emergency")} + + )} +
+
+
+ {( + [ + { + title: "origin_facility", + icon: "l-plane-departure", + data: resource.origin_facility_object.name, + }, + { + title: "resource_approving_facility", + icon: "l-user-check", + data: resource.approving_facility_object?.name, + }, + { + title: "assigned_facility", + icon: "l-plane-arrival", + data: + resource.assigned_facility_object?.name || + t("yet_to_be_decided"), + }, + { + title: "last_modified", + icon: "l-stopwatch", + data: formatDateTime(resource.modified_date), + className: dayjs() + .subtract(2, "hours") + .isBefore(resource.modified_date) + ? "text-secondary-900" + : "rounded bg-red-500 border border-red-600 text-white w-full font-bold", + }, + { + title: "assigned_to", + icon: "l-user", + data: resource.assigned_to_object + ? formatName(resource.assigned_to_object) + + " - " + + resource.assigned_to_object.user_type + : undefined, + }, + ] as const + ) + .filter((d) => d.data) + .map((datapoint, i) => ( +
+
+ +
+
{datapoint.data}
+
+ ))} +
+
+
+ + {t("all_details")} + +
+
+ ); +} diff --git a/src/components/Resource/ResourceBoardView.tsx b/src/components/Resource/ResourceBoard.tsx similarity index 54% rename from src/components/Resource/ResourceBoardView.tsx rename to src/components/Resource/ResourceBoard.tsx index 8d5299fd3f8..f69fd02b555 100644 --- a/src/components/Resource/ResourceBoardView.tsx +++ b/src/components/Resource/ResourceBoard.tsx @@ -1,9 +1,9 @@ import { useState } from "react"; -import { Link, navigate } from "raviger"; -import ListFilter from "./ListFilter"; +import { navigate } from "raviger"; +import ListFilter from "./ResourceFilter"; import { RESOURCE_CHOICES } from "@/common/constants"; -import BadgesList from "./BadgesList"; -import { formatFilter } from "./Commons"; +import BadgesList from "./ResourceBadges"; +import { formatFilter } from "./ResourceCommons"; import useFilters from "@/common/hooks/useFilters"; import { ExportButton } from "@/components/Common/Export"; import ButtonV2 from "@/components/Common/components/ButtonV2"; @@ -16,10 +16,9 @@ import request from "../../Utils/request/request"; import routes from "../../Redux/api"; import KanbanBoard from "../Kanban/Board"; import { ResourceModel } from "../Facility/models"; -import { classNames, formatDateTime, formatName } from "../../Utils/utils"; -import dayjs from "dayjs"; import PageTitle from "@/components/Common/PageTitle"; +import ResourceBlock from "./ResourceBlock"; const resourceStatusOptions = RESOURCE_CHOICES.map((obj) => obj.text); const COMPLETED = ["COMPLETED", "REJECTED"]; @@ -72,6 +71,7 @@ export default function BoardView() { value={qParams.title} onChange={(e) => updateQuery({ [e.name]: e.value })} placeholder={t("search_resource")} + className="w-full md:w-60" /> ( -
-
-
-
-
- {resource.title} -
-
-
- {resource.emergency && ( - - {t("emergency")} - - )} -
-
-
- {( - [ - { - title: "origin_facility", - icon: "l-plane-departure", - data: resource.origin_facility_object.name, - }, - { - title: "resource_approving_facility", - icon: "l-user-check", - data: resource.approving_facility_object?.name, - }, - { - title: "assigned_facility", - icon: "l-plane-arrival", - data: - resource.assigned_facility_object?.name || - t("yet_to_be_decided"), - }, - { - title: "last_modified", - icon: "l-stopwatch", - data: formatDateTime(resource.modified_date), - className: dayjs() - .subtract(2, "hours") - .isBefore(resource.modified_date) - ? "text-secondary-900" - : "rounded bg-red-500 border border-red-600 text-white w-full font-bold", - }, - { - title: "assigned_to", - icon: "l-user", - data: resource.assigned_to_object - ? formatName(resource.assigned_to_object) + - " - " + - resource.assigned_to_object.user_type - : undefined, - }, - ] as const - ) - .filter((d) => d.data) - .map((datapoint, i) => ( -
-
- -
-
- {datapoint.data} -
-
- ))} -
-
-
- - {t("all_details")} - -
-
- )} + itemRender={(resource) => } /> diff --git a/src/components/Resource/CommentSection.tsx b/src/components/Resource/ResourceCommentSection.tsx similarity index 96% rename from src/components/Resource/CommentSection.tsx rename to src/components/Resource/ResourceCommentSection.tsx index 632d608cd14..a2c10864b3b 100644 --- a/src/components/Resource/CommentSection.tsx +++ b/src/components/Resource/ResourceCommentSection.tsx @@ -6,8 +6,8 @@ import ButtonV2 from "@/components/Common/components/ButtonV2"; import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; import routes from "../../Redux/api"; import PaginatedList from "../../CAREUI/misc/PaginatedList"; -import { IComment } from "./models"; import request from "../../Utils/request/request"; +import { CommentModel } from "../Facility/models"; const CommentSection = (props: { id: string }) => { const [commentBox, setCommentBox] = useState(""); @@ -63,7 +63,7 @@ const CommentSection = (props: { id: string }) => { - > + > {(item) => }
@@ -83,7 +83,7 @@ export const Comment = ({ comment, created_by_object, modified_date, -}: IComment) => ( +}: CommentModel) => (

{comment}

diff --git a/src/components/Resource/Commons.tsx b/src/components/Resource/ResourceCommons.tsx similarity index 97% rename from src/components/Resource/Commons.tsx rename to src/components/Resource/ResourceCommons.tsx index 853aa611c61..e63eb9a71b2 100644 --- a/src/components/Resource/Commons.tsx +++ b/src/components/Resource/ResourceCommons.tsx @@ -29,7 +29,7 @@ export const formatFilter = (params: any) => { : filter.emergency === "yes" ? "true" : "false", - limit: 14, + limit: filter.limit || 14, offset: filter.offset, created_date_before: filter.created_date_before || undefined, created_date_after: filter.created_date_after || undefined, diff --git a/src/components/Resource/ResourceDetails.tsx b/src/components/Resource/ResourceDetails.tsx index 4cfe959b7b9..f3c318102a6 100644 --- a/src/components/Resource/ResourceDetails.tsx +++ b/src/components/Resource/ResourceDetails.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { classNames, formatDateTime, formatName } from "../../Utils/utils"; import { navigate } from "raviger"; import * as Notification from "../../Utils/Notifications"; -import CommentSection from "./CommentSection"; +import CommentSection from "./ResourceCommentSection"; import ButtonV2 from "@/components/Common/components/ButtonV2"; import Page from "@/components/Common/components/Page"; import ConfirmDialog from "@/components/Common/ConfirmDialog"; @@ -357,7 +357,7 @@ export default function ResourceDetails(props: { id: string }) {
- {formatName(data.created_by_object)} + {data.created_by_object && formatName(data.created_by_object)}
{data.created_date && formatDateTime(data.created_date)} diff --git a/src/components/Resource/ListFilter.tsx b/src/components/Resource/ResourceFilter.tsx similarity index 100% rename from src/components/Resource/ListFilter.tsx rename to src/components/Resource/ResourceFilter.tsx diff --git a/src/components/Resource/ResourceList.tsx b/src/components/Resource/ResourceList.tsx new file mode 100644 index 00000000000..e7dffb1dfe2 --- /dev/null +++ b/src/components/Resource/ResourceList.tsx @@ -0,0 +1,138 @@ +import { navigate } from "raviger"; +import ListFilter from "./ResourceFilter"; +import { formatFilter } from "./ResourceCommons"; +import BadgesList from "./ResourceBadges"; +import useFilters from "@/common/hooks/useFilters"; +import { ExportButton } from "@/components/Common/Export"; +import ButtonV2 from "@/components/Common/components/ButtonV2"; +import { useTranslation } from "react-i18next"; +import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import useQuery from "../../Utils/request/useQuery"; +import routes from "../../Redux/api"; +import Page from "@/components/Common/components/Page"; +import SearchInput from "../Form/SearchInput"; +import request from "../../Utils/request/request"; +import Loading from "@/components/Common/Loading"; +import { ResourceModel } from "../Facility/models"; +import ResourceBlock from "./ResourceBlock"; +export default function ListView() { + const { + qParams, + Pagination, + FilterBadges, + advancedFilter, + resultsPerPage, + updateQuery, + } = useFilters({ cacheBlacklist: ["title"], limit: 12 }); + + const { t } = useTranslation(); + + const onBoardViewBtnClick = () => + navigate("/resource/board", { query: qParams }); + const appliedFilters = formatFilter(qParams); + + const { loading, data, refetch } = useQuery(routes.listResourceRequests, { + query: formatFilter({ + ...qParams, + limit: resultsPerPage, + offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, + }), + }); + + const showResourceCardList = (data: ResourceModel[]) => { + if (data && !data.length) { + return ( +
+ {t("no_results_found")} +
+ ); + } + + return data.map((resource, i) => ( +
+ +
+ )); + }; + + return ( + { + const { data } = await request(routes.downloadResourceRequests, { + query: { ...appliedFilters, csv: true }, + }); + return data ?? null; + }} + filenamePrefix="resource_requests" + /> + } + breadcrumbs={false} + options={ + <> +
+ updateQuery({ [e.name]: e.value })} + placeholder={t("search_resource")} + /> +
+
+ {/* dummy div to align space as per board view */} +
+
+ + + {t("board_view")} + + + advancedFilter.setShow(true)} + /> +
+ + } + > + + +
+ {loading ? ( + + ) : ( +
+
+ +
+ +
+ {data?.results && showResourceCardList(data?.results)} +
+
+ +
+
+ )} +
+ +
+ ); +} diff --git a/src/components/Resource/models.ts b/src/components/Resource/models.ts deleted file mode 100644 index f10ac988552..00000000000 --- a/src/components/Resource/models.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { PerformedByModel } from "../HCX/misc"; - -export interface IComment { - id: string; - created_by_object: PerformedByModel; - created_date: string; - modified_date: string; - comment: string; - created_by: number; -} - -export interface IResource { - id: string; - title: string; - emergency: boolean; - status?: string; - origin_facility_object: { - name: string; - }; - approving_facility_object: { - name: string; - }; - assigned_facility_object: { - name: string; - }; - assigned_quantity: number; - modified_date: string; - category: any; - sub_category: number; - origin_facility: string; - approving_facility: string; - assigned_facility: string; - reason: string; - refering_facility_contact_name: string; - refering_facility_contact_number: string; - requested_quantity: number; - assigned_to_object: PerformedByModel; - created_by_object: PerformedByModel; - created_date: string; - last_edited_by_object: PerformedByModel; -} diff --git a/src/components/Shifting/BoardView.tsx b/src/components/Shifting/BoardView.tsx deleted file mode 100644 index d05fcab2a9e..00000000000 --- a/src/components/Shifting/BoardView.tsx +++ /dev/null @@ -1,326 +0,0 @@ -import { - SHIFTING_CHOICES_PEACETIME, - SHIFTING_CHOICES_WARTIME, -} from "@/common/constants"; - -import BadgesList from "./BadgesList"; -import { ExportButton } from "@/components/Common/Export"; -import ListFilter from "./ListFilter"; -import SearchInput from "../Form/SearchInput"; -import { formatFilter } from "./Commons"; - -import { Link, navigate } from "raviger"; -import useFilters from "@/common/hooks/useFilters"; -import { useState } from "react"; -import { useTranslation } from "react-i18next"; -import ButtonV2 from "@/components/Common/components/ButtonV2"; -import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import Tabs from "@/components/Common/components/Tabs"; -import careConfig from "@careConfig"; -import KanbanBoard from "../Kanban/Board"; -import { classNames, formatDateTime, formatName } from "../../Utils/utils"; -import dayjs from "dayjs"; -import ConfirmDialog from "@/components/Common/ConfirmDialog"; -import { ShiftingModel } from "../Facility/models"; -import useAuthUser from "@/common/hooks/useAuthUser"; -import request from "../../Utils/request/request"; -import routes from "../../Redux/api"; -import PageTitle from "@/components/Common/PageTitle"; - -export default function BoardView() { - const { qParams, updateQuery, FilterBadges, advancedFilter } = useFilters({ - limit: -1, - cacheBlacklist: ["patient_name"], - }); - - const [modalFor, setModalFor] = useState<{ - externalId?: string; - loading: boolean; - }>({ - externalId: undefined, - loading: false, - }); - - const authUser = useAuthUser(); - - const handleTransferComplete = async (shift: any) => { - setModalFor({ ...modalFor, loading: true }); - await request(routes.completeTransfer, { - pathParams: { externalId: shift.external_id }, - }); - navigate( - `/facility/${shift.assigned_facility}/patient/${shift.patient}/consultation`, - ); - }; - - const shiftStatusOptions = careConfig.wartimeShifting - ? SHIFTING_CHOICES_WARTIME - : SHIFTING_CHOICES_PEACETIME; - - const COMPLETED = careConfig.wartimeShifting - ? [ - "COMPLETED", - "REJECTED", - "CANCELLED", - "DESTINATION REJECTED", - "PATIENT EXPIRED", - ] - : ["CANCELLED", "PATIENT EXPIRED"]; - - const completedBoards = shiftStatusOptions.filter((option) => - COMPLETED.includes(option.text), - ); - const activeBoards = shiftStatusOptions.filter( - (option) => !COMPLETED.includes(option.text), - ); - - const [boardFilter, setBoardFilter] = useState(activeBoards); - const { t } = useTranslation(); - - return ( -
-
-
- { - const { data } = await request(routes.downloadShiftRequests, { - query: { ...formatFilter(qParams), csv: true }, - }); - return data ?? null; - }} - filenamePrefix="shift_requests" - /> - } - breadcrumbs={false} - /> -
-
- updateQuery({ [e.name]: e.value })} - placeholder={t("search_patient")} - /> - - - setBoardFilter(tab ? completedBoards : activeBoards) - } - currentTab={boardFilter[0].text !== activeBoards[0].text ? 1 : 0} - /> - -
- navigate("/shifting/list", { query: qParams })} - > - - {t("list_view")} - - advancedFilter.setShow(true)} - /> -
-
-
- - title={} - sections={boardFilter.map((board) => ({ - id: board.text, - title: ( -

- {board.label || board.text}{" "} - { - const { data } = await request(routes.downloadShiftRequests, { - query: { - ...formatFilter({ ...qParams, status: board.text }), - csv: true, - }, - }); - return data ?? null; - }} - filenamePrefix={`shift_requests_${board.label || board.text}`} - /> -

- ), - fetchOptions: (id) => ({ - route: routes.listShiftRequests, - options: { - query: formatFilter({ - ...qParams, - status: id, - }), - }, - }), - }))} - onDragEnd={(result) => { - if (result.source.droppableId !== result.destination?.droppableId) - navigate( - `/shifting/${result.draggableId}/update?status=${result.destination?.droppableId}`, - ); - }} - itemRender={(shift) => ( -
-
-
-
-
- {shift.patient_object.name} -
-
- {shift.patient_object.age} old -
-
-
- {shift.emergency && ( - - {t("emergency")} - - )} -
-
-
- {( - [ - { - title: "phone_number", - icon: "l-mobile-android", - data: shift.patient_object.phone_number, - }, - { - title: "origin_facility", - icon: "l-plane-departure", - data: shift.origin_facility_object.name, - }, - { - title: "shifting_approving_facility", - icon: "l-user-check", - data: careConfig.wartimeShifting - ? shift.shifting_approving_facility_object?.name - : undefined, - }, - { - title: "assigned_facility", - icon: "l-plane-arrival", - data: - shift.assigned_facility_external || - shift.assigned_facility_object?.name || - t("yet_to_be_decided"), - }, - { - title: "last_modified", - icon: "l-stopwatch", - data: formatDateTime(shift.modified_date), - className: dayjs() - .subtract(2, "hours") - .isBefore(shift.modified_date) - ? "text-secondary-900" - : "rounded bg-red-500 border border-red-600 text-white w-full font-bold", - }, - { - title: "patient_address", - icon: "l-home", - data: shift.patient_object.address, - }, - { - title: "assigned_to", - icon: "l-user", - data: shift.assigned_to_object - ? formatName(shift.assigned_to_object) + - " - " + - shift.assigned_to_object.user_type - : undefined, - }, - { - title: "patient_state", - icon: "l-map-marker", - data: shift.patient_object.state_object?.name, - }, - ] as const - ) - .filter((d) => d.data) - .map((datapoint, i) => ( -
-
- -
-
- {datapoint.data} -
-
- ))} -
-
-
- - {t("all_details")} - - - {shift.status === "COMPLETED" && shift.assigned_facility && ( - <> - - - - setModalFor({ externalId: undefined, loading: false }) - } - action={t("confirm")} - onConfirm={() => handleTransferComplete(shift)} - > -

- {t("redirected_to_create_consultation")} -

-
- - )} -
-
- )} - /> - -
- ); -} diff --git a/src/components/Shifting/ListView.tsx b/src/components/Shifting/ListView.tsx deleted file mode 100644 index e74f80107b0..00000000000 --- a/src/components/Shifting/ListView.tsx +++ /dev/null @@ -1,332 +0,0 @@ -import { useState } from "react"; -import BadgesList from "./BadgesList"; -import ButtonV2 from "@/components/Common/components/ButtonV2"; -import ConfirmDialog from "@/components/Common/ConfirmDialog"; -import { ExportButton } from "@/components/Common/Export"; -import ListFilter from "./ListFilter"; -import Page from "@/components/Common/components/Page"; -import SearchInput from "../Form/SearchInput"; -import { formatDateTime } from "../../Utils/utils"; -import { formatFilter } from "./Commons"; -import { navigate } from "raviger"; -import useFilters from "@/common/hooks/useFilters"; -import { useTranslation } from "react-i18next"; -import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import dayjs from "../../Utils/dayjs"; -import useAuthUser from "@/common/hooks/useAuthUser"; -import request from "../../Utils/request/request"; -import routes from "../../Redux/api"; -import useQuery from "../../Utils/request/useQuery"; -import careConfig from "@careConfig"; -import Loading from "@/components/Common/Loading"; -import { IShift } from "./models"; - -export default function ListView() { - const { - qParams, - updateQuery, - Pagination, - FilterBadges, - advancedFilter, - resultsPerPage, - } = useFilters({ cacheBlacklist: ["patient_name"] }); - - const [modalFor, setModalFor] = useState<{ - external_id: string | undefined; - loading: boolean; - }>({ - external_id: undefined, - loading: false, - }); - const authUser = useAuthUser(); - const { t } = useTranslation(); - - const handleTransferComplete = async (shift: IShift) => { - setModalFor({ ...modalFor, loading: true }); - await request(routes.completeTransfer, { - pathParams: { externalId: shift.external_id }, - }); - navigate( - `/facility/${shift.assigned_facility}/patient/${shift.patient}/consultation`, - ); - }; - - const { - data: shiftData, - loading, - refetch: fetchData, - } = useQuery(routes.listShiftRequests, { - query: formatFilter({ - ...qParams, - offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, - }), - }); - - const showShiftingCardList = (data: IShift[]) => { - if (loading) { - return ; - } - - if (!data || data.length === 0) { - return ( -
-
- {t("no_patients_to_show")} -
-
- ); - } - - return ( -
- {data.map((shift: IShift) => ( -
-
-
-
-
-
- {shift.patient_object.name} - {shift.patient_object.age} -
-
- {shift.emergency && ( - - {t("emergency")} - - )} -
-
-
-
-
- -
- {shift.status} -
- -
-
-
- -
- {shift.patient_object.phone_number || ""} -
- -
-
-
- -
- {(shift.origin_facility_object || {}).name} -
- -
- {careConfig.wartimeShifting && ( -
-
- -
- { - (shift.shifting_approving_facility_object || {}) - .name - } -
- -
- )} -
-
- - -
- {shift.assigned_facility_external || - shift.assigned_facility_object?.name || - t("yet_to_be_decided")} -
- -
- -
-
- -
- {formatDateTime(shift.modified_date) || "--"} -
- -
- -
-
- -
- {shift.patient_object.address || "--"} -
- -
-
-
- -
- navigate(`/shifting/${shift.external_id}`)} - variant="secondary" - border - className="w-full" - > - {" "} - {t("all_details")} - -
- {shift.status === "COMPLETED" && shift.assigned_facility && ( -
- - setModalFor({ - external_id: shift.external_id, - loading: false, - }) - } - > - {t("transfer_to_receiving_facility")} - - - setModalFor({ external_id: undefined, loading: false }) - } - onConfirm={() => handleTransferComplete(shift)} - /> -
- )} -
-
-
- ))} -
- ); - }; - - return ( - { - const { data } = await request(routes.downloadShiftRequests, { - query: { ...formatFilter(qParams), csv: true }, - }); - return data ?? null; - }} - filenamePrefix="shift_requests" - /> - } - breadcrumbs={false} - options={ - <> -
- updateQuery({ [e.name]: e.value })} - placeholder={t("search_patient")} - /> -
-
- {/* dummy div to align space as per board view */} -
-
- navigate("/shifting/board", { query: qParams })} - > - - {t("board_view")} - - - advancedFilter.setShow(true)} - /> -
- - } - > - -
- {loading ? ( - - ) : ( -
-
- -
- - {showShiftingCardList(shiftData?.results || [])} - -
- -
-
- )} -
- -
- ); -} diff --git a/src/components/Shifting/ShiftDetails.tsx b/src/components/Shifting/ShiftDetails.tsx index 6698b37beb9..666bd1bcb38 100644 --- a/src/components/Shifting/ShiftDetails.tsx +++ b/src/components/Shifting/ShiftDetails.tsx @@ -8,7 +8,7 @@ import { import { Link, navigate } from "raviger"; import { useState } from "react"; import ButtonV2 from "@/components/Common/components/ButtonV2"; -import CommentSection from "./CommentsSection"; +import CommentSection from "./ShiftingCommentsSection"; import ConfirmDialog from "@/components/Common/ConfirmDialog"; import { CopyToClipboard } from "react-copy-to-clipboard"; import Page from "@/components/Common/components/Page"; diff --git a/src/components/Shifting/ShiftDetailsUpdate.tsx b/src/components/Shifting/ShiftDetailsUpdate.tsx index 4f30e3b345d..e34a3cb62f8 100644 --- a/src/components/Shifting/ShiftDetailsUpdate.tsx +++ b/src/components/Shifting/ShiftDetailsUpdate.tsx @@ -13,7 +13,7 @@ import { import { Cancel, Submit } from "@/components/Common/components/ButtonV2"; import { navigate, useQueryParams } from "raviger"; import { useReducer, useState } from "react"; -import { ConsultationModel } from "../Facility/models"; +import { ConsultationModel, ShiftingModel } from "../Facility/models"; import DischargeModal from "../Facility/DischargeModal"; import { FacilitySelect } from "@/components/Common/FacilitySelect"; import { FieldChangeEvent } from "../Form/FormFields/Utils"; @@ -34,7 +34,6 @@ import { LinkedFacilityUsers } from "@/components/Common/UserAutocompleteFormFie import { UserBareMinimum } from "../Users/models"; import useQuery from "../../Utils/request/useQuery"; import routes from "../../Redux/api"; -import { IShift } from "./models"; import request from "../../Utils/request/request"; import { PatientModel } from "../Patient/models"; import useAuthUser from "@/common/hooks/useAuthUser"; @@ -228,7 +227,7 @@ export const ShiftDetailsUpdate = (props: patientShiftProps) => { } setIsLoading(true); - const data: Partial = { + const data: Partial = { origin_facility: state.form.origin_facility_object?.id, shifting_approving_facility: state.form?.shifting_approving_facility_object?.id, diff --git a/src/components/Shifting/BadgesList.tsx b/src/components/Shifting/ShiftingBadges.tsx similarity index 97% rename from src/components/Shifting/BadgesList.tsx rename to src/components/Shifting/ShiftingBadges.tsx index a06788781c6..adc3bcd7752 100644 --- a/src/components/Shifting/BadgesList.tsx +++ b/src/components/Shifting/ShiftingBadges.tsx @@ -1,5 +1,5 @@ import { SHIFTING_FILTER_ORDER } from "@/common/constants"; -import { useFacilityQuery } from "../Resource/BadgesList"; +import { useFacilityQuery } from "../Resource/ResourceBadges"; import { useTranslation } from "react-i18next"; import useQuery from "../../Utils/request/useQuery"; import routes from "../../Redux/api"; diff --git a/src/components/Shifting/ShiftingBlock.tsx b/src/components/Shifting/ShiftingBlock.tsx new file mode 100644 index 00000000000..20cd431d9f6 --- /dev/null +++ b/src/components/Shifting/ShiftingBlock.tsx @@ -0,0 +1,144 @@ +import { useTranslation } from "react-i18next"; +import { ShiftingModel } from "../Facility/models"; +import careConfig from "@careConfig"; +import { classNames, formatDateTime, formatName } from "@/Utils/utils"; +import dayjs from "dayjs"; +import CareIcon from "@/CAREUI/icons/CareIcon"; +import { Link } from "raviger"; +import useAuthUser from "@/common/hooks/useAuthUser"; + +export default function ShiftingBlock(props: { + shift: ShiftingModel; + onTransfer: () => unknown; +}) { + const { shift, onTransfer } = props; + const { t } = useTranslation(); + const authUser = useAuthUser(); + + return ( +
+
+
+
+
+ {shift.patient_object.name} +
+
+ {shift.patient_object.age} old +
+
+
+ {shift.emergency && ( + + {t("emergency")} + + )} +
+
+
+ {( + [ + { + title: "phone_number", + icon: "l-mobile-android", + data: shift.patient_object.phone_number, + }, + { + title: "origin_facility", + icon: "l-plane-departure", + data: shift.origin_facility_object.name, + }, + { + title: "shifting_approving_facility", + icon: "l-user-check", + data: careConfig.wartimeShifting + ? shift.shifting_approving_facility_object?.name + : undefined, + }, + { + title: "assigned_facility", + icon: "l-plane-arrival", + data: + shift.assigned_facility_external || + shift.assigned_facility_object?.name || + t("yet_to_be_decided"), + }, + { + title: "last_modified", + icon: "l-stopwatch", + data: formatDateTime(shift.modified_date), + className: dayjs() + .subtract(2, "hours") + .isBefore(shift.modified_date) + ? "text-secondary-900" + : "rounded bg-red-500 border border-red-600 text-white w-full font-bold", + }, + { + title: "patient_address", + icon: "l-home", + data: shift.patient_object.address, + }, + { + title: "assigned_to", + icon: "l-user", + data: shift.assigned_to_object + ? formatName(shift.assigned_to_object) + + " - " + + shift.assigned_to_object.user_type + : undefined, + }, + { + title: "patient_state", + icon: "l-map-marker", + data: shift.patient_object.state_object?.name, + }, + ] as const + ) + .filter((d) => d.data) + .map((datapoint, i) => ( +
+
+ +
+
{datapoint.data}
+
+ ))} +
+
+
+ + {t("all_details")} + + + {shift.status === "COMPLETED" && shift.assigned_facility && ( + <> + + + )} +
+
+ ); +} diff --git a/src/components/Shifting/ShiftingBoard.tsx b/src/components/Shifting/ShiftingBoard.tsx new file mode 100644 index 00000000000..ee65090191a --- /dev/null +++ b/src/components/Shifting/ShiftingBoard.tsx @@ -0,0 +1,184 @@ +import { + SHIFTING_CHOICES_PEACETIME, + SHIFTING_CHOICES_WARTIME, +} from "@/common/constants"; + +import BadgesList from "./ShiftingBadges"; +import { ExportButton } from "@/components/Common/Export"; +import ListFilter from "./ShiftingFilters"; +import SearchInput from "../Form/SearchInput"; +import { formatFilter } from "./ShiftingCommons"; + +import { navigate } from "raviger"; +import useFilters from "@/common/hooks/useFilters"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import ButtonV2 from "@/components/Common/components/ButtonV2"; +import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import Tabs from "@/components/Common/components/Tabs"; +import careConfig from "@careConfig"; +import KanbanBoard from "../Kanban/Board"; + +import ConfirmDialog from "@/components/Common/ConfirmDialog"; +import { ShiftingModel } from "../Facility/models"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import PageTitle from "@/components/Common/PageTitle"; +import ShiftingBlock from "./ShiftingBlock"; + +export default function BoardView() { + const { qParams, updateQuery, FilterBadges, advancedFilter } = useFilters({ + limit: -1, + cacheBlacklist: ["patient_name"], + }); + + const [modalFor, setModalFor] = useState(); + + const handleTransferComplete = async (shift?: ShiftingModel) => { + if (!shift) return; + await request(routes.completeTransfer, { + pathParams: { externalId: shift.external_id }, + }); + navigate( + `/facility/${shift.assigned_facility}/patient/${shift.patient}/consultation`, + ); + }; + + const shiftStatusOptions = careConfig.wartimeShifting + ? SHIFTING_CHOICES_WARTIME + : SHIFTING_CHOICES_PEACETIME; + + const COMPLETED = careConfig.wartimeShifting + ? [ + "COMPLETED", + "REJECTED", + "CANCELLED", + "DESTINATION REJECTED", + "PATIENT EXPIRED", + ] + : ["CANCELLED", "PATIENT EXPIRED"]; + + const completedBoards = shiftStatusOptions.filter((option) => + COMPLETED.includes(option.text), + ); + const activeBoards = shiftStatusOptions.filter( + (option) => !COMPLETED.includes(option.text), + ); + + const [boardFilter, setBoardFilter] = useState(activeBoards); + const { t } = useTranslation(); + + return ( +
+
+
+ { + const { data } = await request(routes.downloadShiftRequests, { + query: { ...formatFilter(qParams), csv: true }, + }); + return data ?? null; + }} + filenamePrefix="shift_requests" + /> + } + breadcrumbs={false} + /> +
+
+ updateQuery({ [e.name]: e.value })} + placeholder={t("search_patient")} + className="w-full md:w-60" + /> + + + setBoardFilter(tab ? completedBoards : activeBoards) + } + currentTab={boardFilter[0].text !== activeBoards[0].text ? 1 : 0} + /> + +
+ navigate("/shifting/list", { query: qParams })} + > + + {t("list_view")} + + advancedFilter.setShow(true)} + /> +
+
+
+ + title={} + sections={boardFilter.map((board) => ({ + id: board.text, + title: ( +

+ {board.label || board.text}{" "} + { + const { data } = await request(routes.downloadShiftRequests, { + query: { + ...formatFilter({ ...qParams, status: board.text }), + csv: true, + }, + }); + return data ?? null; + }} + filenamePrefix={`shift_requests_${board.label || board.text}`} + /> +

+ ), + fetchOptions: (id) => ({ + route: routes.listShiftRequests, + options: { + query: formatFilter({ + ...qParams, + status: id, + }), + }, + }), + }))} + onDragEnd={(result) => { + if (result.source.droppableId !== result.destination?.droppableId) + navigate( + `/shifting/${result.draggableId}/update?status=${result.destination?.droppableId}`, + ); + }} + itemRender={(shift) => ( + setModalFor(shift)} shift={shift} /> + )} + /> + setModalFor(undefined)} + action={t("confirm")} + onConfirm={() => handleTransferComplete(modalFor)} + > +

+ {t("redirected_to_create_consultation")} +

+
+ +
+ ); +} diff --git a/src/components/Shifting/CommentsSection.tsx b/src/components/Shifting/ShiftingCommentsSection.tsx similarity index 96% rename from src/components/Shifting/CommentsSection.tsx rename to src/components/Shifting/ShiftingCommentsSection.tsx index 5d6aefb98fb..9453052d2d9 100644 --- a/src/components/Shifting/CommentsSection.tsx +++ b/src/components/Shifting/ShiftingCommentsSection.tsx @@ -5,9 +5,9 @@ import { formatDateTime, formatName } from "../../Utils/utils"; import { useTranslation } from "react-i18next"; import ButtonV2 from "@/components/Common/components/ButtonV2"; import routes from "../../Redux/api"; -import { IComment } from "../Resource/models"; import PaginatedList from "../../CAREUI/misc/PaginatedList"; import request from "../../Utils/request/request"; +import { CommentModel } from "../Facility/models"; interface CommentSectionProps { id: string; @@ -67,7 +67,7 @@ const CommentSection = (props: CommentSectionProps) => { - > + > {(item) => }
@@ -87,7 +87,7 @@ export const Comment = ({ comment, created_by_object, modified_date, -}: IComment) => { +}: CommentModel) => { const { t } = useTranslation(); return (
{ : filter.is_up_shift === "yes" ? "true" : "false", - limit: limit, + limit: filter.limit || limit, offset: filter.offset, patient_name: filter.patient_name || undefined, created_date_before: filter.created_date_before || undefined, diff --git a/src/components/Shifting/ListFilter.tsx b/src/components/Shifting/ShiftingFilters.tsx similarity index 100% rename from src/components/Shifting/ListFilter.tsx rename to src/components/Shifting/ShiftingFilters.tsx diff --git a/src/components/Shifting/ShiftingList.tsx b/src/components/Shifting/ShiftingList.tsx new file mode 100644 index 00000000000..fda6d6fcaa6 --- /dev/null +++ b/src/components/Shifting/ShiftingList.tsx @@ -0,0 +1,179 @@ +import { useState } from "react"; +import BadgesList from "./ShiftingBadges"; +import ButtonV2 from "@/components/Common/components/ButtonV2"; +import ConfirmDialog from "@/components/Common/ConfirmDialog"; +import { ExportButton } from "@/components/Common/Export"; +import ListFilter from "./ShiftingFilters"; +import Page from "@/components/Common/components/Page"; +import SearchInput from "../Form/SearchInput"; +import { formatFilter } from "./ShiftingCommons"; +import { navigate } from "raviger"; +import useFilters from "@/common/hooks/useFilters"; +import { useTranslation } from "react-i18next"; +import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; +import Loading from "@/components/Common/Loading"; +import { ShiftingModel } from "../Facility/models"; +import ShiftingBlock from "./ShiftingBlock"; + +export default function ListView() { + const { + qParams, + updateQuery, + Pagination, + FilterBadges, + advancedFilter, + resultsPerPage, + } = useFilters({ cacheBlacklist: ["patient_name"], limit: 12 }); + + const [modalFor, setModalFor] = useState(); + const { t } = useTranslation(); + + const handleTransferComplete = async (shift?: ShiftingModel) => { + if (!shift) return; + await request(routes.completeTransfer, { + pathParams: { externalId: shift.external_id }, + }); + navigate( + `/facility/${shift.assigned_facility}/patient/${shift.patient}/consultation`, + ); + }; + + const { + data: shiftData, + loading, + refetch: fetchData, + } = useQuery(routes.listShiftRequests, { + query: formatFilter({ + ...qParams, + limit: resultsPerPage, + offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, + }), + }); + + const showShiftingCardList = (data: ShiftingModel[]) => { + if (loading) { + return ; + } + + if (!data || data.length === 0) { + return ( +
+
+ {t("no_patients_to_show")} +
+
+ ); + } + + return ( +
+
+ {data.map((shift, i) => ( +
+ setModalFor(shift)} + shift={shift} + /> +
+ ))} +
+ setModalFor(undefined)} + onConfirm={() => handleTransferComplete(modalFor)} + /> +
+ ); + }; + + return ( + { + const { data } = await request(routes.downloadShiftRequests, { + query: { ...formatFilter(qParams), csv: true }, + }); + return data ?? null; + }} + filenamePrefix="shift_requests" + /> + } + breadcrumbs={false} + options={ + <> +
+ updateQuery({ [e.name]: e.value })} + placeholder={t("search_patient")} + /> +
+
+ {/* dummy div to align space as per board view */} +
+
+ navigate("/shifting/board", { query: qParams })} + > + + {t("board_view")} + + + advancedFilter.setShow(true)} + /> +
+ + } + > + +
+ {loading ? ( + + ) : ( +
+
+ +
+ + {showShiftingCardList(shiftData?.results || [])} + +
+ +
+
+ )} +
+ +
+ ); +} diff --git a/src/components/Shifting/models.ts b/src/components/Shifting/models.ts deleted file mode 100644 index 010db870ab8..00000000000 --- a/src/components/Shifting/models.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { FacilityModel } from "../Facility/models"; -import { PerformedByModel } from "../HCX/misc"; -import { AssignedToObjectModel, PatientModel } from "../Patient/models"; - -export interface IShift { - id: string; - patient_object: PatientModel; - emergency: boolean; - status: string; - 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; - shifting_approving_facility_object: FacilityModel; - 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; - is_kasp: boolean; - 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: PerformedByModel; - last_edited_by_object: PerformedByModel; - is_assigned_to_user: boolean; - created_by: number; - last_edited_by: number; - patient: string | PatientModel; - initial_status?: string; -} diff --git a/src/components/Symptoms/SymptomsBuilder.tsx b/src/components/Symptoms/SymptomsBuilder.tsx index 1be74797278..3676e2ad607 100644 --- a/src/components/Symptoms/SymptomsBuilder.tsx +++ b/src/components/Symptoms/SymptomsBuilder.tsx @@ -191,7 +191,6 @@ const SymptomEntry = (props: { name="cure_date" value={symptom.cure_date ? new Date(symptom.cure_date) : undefined} disableFuture - position="CENTER" placeholder="Date of cure" min={new Date(symptom.onset_date)} disabled={disabled} @@ -293,7 +292,6 @@ const AddSymptom = (props: { return (
+
+ +
{user.username && (
{ required value={getDate(state.form.date_of_birth)} onChange={handleDateChange} - position="LEFT" disableFuture /> diff --git a/src/components/Users/UserProfile.tsx b/src/components/Users/UserProfile.tsx index 7aa3c9eff34..edbb5dafdfa 100644 --- a/src/components/Users/UserProfile.tsx +++ b/src/components/Users/UserProfile.tsx @@ -494,6 +494,8 @@ export default function UserProfile() { refetchUser(); Notification.Success({ msg: "Profile picture updated." }); setEditAvatar(false); + } else { + onError(); } }, null, @@ -775,7 +777,6 @@ export default function UserProfile() { required className="col-span-6 sm:col-span-3" value={getDate(states.form.date_of_birth)} - position="LEFT" disableFuture={true} /> ; +export type ManagePatientOptionsComponentType = React.FC<{ + consultation: ConsultationModel | undefined; + patient: PatientModel; +}>; + +export type AdditionalDischargeProceduresComponentType = React.FC<{ + consultation: ConsultationModel; +}>; + // Define supported plugin components export type SupportedPluginComponents = { DoctorConnectButtons: DoctorConnectButtonComponentType; + ManagePatientOptions: ManagePatientOptionsComponentType; + AdditionalDischargeProcedures: AdditionalDischargeProceduresComponentType; }; // Create a type for lazy-loaded components diff --git a/src/style/index.css b/src/style/index.css index 28b3ef31718..e80d1b2733f 100644 --- a/src/style/index.css +++ b/src/style/index.css @@ -602,6 +602,10 @@ button:disabled, } } +.scrollbar-hide::-webkit-scrollbar { + width: 0px; + height: 0px; +} /* Range Sliders for Camera Feed @@ -609,6 +613,7 @@ button:disabled, #feed-range { -webkit-appearance: none; + appearance: none; margin: 18px 0; width: 100%; } diff --git a/tailwind.config.js b/tailwind.config.js index 3df126955a1..32b25642022 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -83,7 +83,11 @@ module.exports = { }, }, }, - content: ["./src/**/*.{html,md,js,jsx,ts,tsx,res}", "./index.html"], + content: [ + "./src/**/*.{html,md,js,jsx,ts,tsx,res}", + "./apps/**/*.{html,md,js,jsx,ts,tsx,res}", + "./index.html", + ], plugins: [ require("@tailwindcss/forms"), require("@tailwindcss/typography"),