diff --git a/cypress/e2e/facility_spec/locations.cy.ts b/cypress/e2e/facility_spec/locations.cy.ts index bad772ac410..8a8d0eb9877 100644 --- a/cypress/e2e/facility_spec/locations.cy.ts +++ b/cypress/e2e/facility_spec/locations.cy.ts @@ -92,7 +92,7 @@ describe("Location Management Section", () => { facilityLocation.selectBedType(bedType); assetPage.clickassetupdatebutton(); facilityLocation.verifyNotification( - "Name - Bed with same name already exists in location" + "Name - Bed with same name already exists in location", ); facilityHome.verifyAndCloseNotifyModal(); // edit the created bed @@ -185,7 +185,7 @@ describe("Location Management Section", () => { facilityLocation.deleteLocation("Test Location"); assetPage.clickassetupdatebutton(); facilityLocation.verifyNotification( - "Location Test Location deleted successfully" + "Location Test Location deleted successfully", ); facilityLocation.closeNotification(); }); @@ -204,7 +204,7 @@ describe("Location Management Section", () => { facilityLocation.deleteLocation("Test Location with Beds"); assetPage.clickassetupdatebutton(); facilityLocation.verifyNotification( - "Cannot delete a Location with associated Beds" + "Cannot delete a Location with associated Beds", ); facilityLocation.closeNotification(); @@ -219,7 +219,7 @@ describe("Location Management Section", () => { facilityLocation.deleteLocation("Test Location with Beds"); assetPage.clickassetupdatebutton(); facilityLocation.verifyNotification( - "Location Test Location with Beds deleted successfully" + "Location Test Location with Beds deleted successfully", ); facilityLocation.closeNotification(); }); @@ -249,14 +249,14 @@ describe("Location Management Section", () => { "Vendor's Name", serialNumber, "25122021", - "Test note for asset creation!" + "Test note for asset creation!", ); assetPage.clickassetupdatebutton(); facilityLocation.loadLocationManagementPage("Dummy Shifting Center"); facilityLocation.deleteLocation("Test Location with linked Assets"); assetPage.clickassetupdatebutton(); facilityLocation.verifyNotification( - "Cannot delete a Location with associated Assets" + "Cannot delete a Location with associated Assets", ); facilityLocation.closeNotification(); @@ -271,7 +271,7 @@ describe("Location Management Section", () => { facilityLocation.deleteLocation("Test Location with linked Assets"); assetPage.clickassetupdatebutton(); facilityLocation.verifyNotification( - "Location Test Location with linked Assets deleted successfully" + "Location Test Location with linked Assets deleted successfully", ); facilityLocation.closeNotification(); }); diff --git a/cypress/e2e/patient_spec/patient_registration.cy.ts b/cypress/e2e/patient_spec/patient_registration.cy.ts index f94dbbe46cd..48befda6086 100644 --- a/cypress/e2e/patient_spec/patient_registration.cy.ts +++ b/cypress/e2e/patient_spec/patient_registration.cy.ts @@ -177,16 +177,23 @@ describe("Patient Creation with consultation", () => { "policy_id", patientOneFirstPolicyId, ); - patientInsurance.typePatientInsuranceDetail( - patientOneFirstInsuranceId, - "insurer_id", - patientOneFirstInsurerId, - ); - patientInsurance.typePatientInsuranceDetail( - patientOneFirstInsuranceId, - "insurer_name", - patientOneFirstInsurerName, - ); + cy.checkConfig("enable_hcx").then((isHCXEnabled) => { + if (isHCXEnabled) { + patientInsurance.selectInsurer("test"); + } else { + patientInsurance.typePatientInsuranceDetail( + patientOneFirstInsuranceId, + "insurer_id", + patientOneFirstInsurerId, + ); + patientInsurance.typePatientInsuranceDetail( + patientOneFirstInsuranceId, + "insurer_name", + patientOneFirstInsurerName, + ); + } + }); + patientInsurance.clickAddInsruanceDetails(); patientInsurance.typePatientInsuranceDetail( patientOneSecondInsuranceId, @@ -198,16 +205,23 @@ describe("Patient Creation with consultation", () => { "policy_id", patientOneSecondPolicyId, ); - patientInsurance.typePatientInsuranceDetail( - patientOneSecondInsuranceId, - "insurer_id", - patientOneSecondInsurerId, - ); - patientInsurance.typePatientInsuranceDetail( - patientOneSecondInsuranceId, - "insurer_name", - patientOneSecondInsurerName, - ); + cy.checkConfig("enable_hcx").then((isHCXEnabled) => { + if (isHCXEnabled) { + patientInsurance.selectInsurer("Care"); + } else { + patientInsurance.typePatientInsuranceDetail( + patientOneSecondInsuranceId, + "insurer_id", + patientOneSecondInsurerId, + ); + patientInsurance.typePatientInsuranceDetail( + patientOneSecondInsuranceId, + "insurer_name", + patientOneSecondInsurerName, + ); + } + }); + patientPage.clickUpdatePatient(); cy.wait(3000); patientPage.verifyPatientUpdated(); @@ -231,26 +245,33 @@ describe("Patient Creation with consultation", () => { .contains(patientOneFirstSubscriberId) .scrollIntoView(); cy.wait(2000); - patientInsurance.verifyPatientPolicyDetails( - patientOneFirstSubscriberId, - patientOneFirstPolicyId, - patientOneFirstInsurerId, - patientOneFirstInsurerName, - ); + cy.checkConfig("enable_hcx").then((isHCXEnabled) => { + patientInsurance.verifyPatientPolicyDetails( + patientOneFirstSubscriberId, + patientOneFirstPolicyId, + patientOneFirstInsurerId, + patientOneFirstInsurerName, + isHCXEnabled, + ); + }); patientInsurance.clickPatientInsuranceViewDetail(); cy.wait(3000); - patientInsurance.verifyPatientPolicyDetails( - patientOneFirstSubscriberId, - patientOneFirstPolicyId, - patientOneFirstInsurerId, - patientOneFirstInsurerName, - ); - patientInsurance.verifyPatientPolicyDetails( - patientOneSecondSubscriberId, - patientOneSecondPolicyId, - patientOneSecondInsurerId, - patientOneSecondInsurerName, - ); + cy.checkConfig("enable_hcx").then((isHCXEnabled) => { + patientInsurance.verifyPatientPolicyDetails( + patientOneFirstSubscriberId, + patientOneFirstPolicyId, + patientOneFirstInsurerId, + patientOneFirstInsurerName, + isHCXEnabled, + ); + patientInsurance.verifyPatientPolicyDetails( + patientOneSecondSubscriberId, + patientOneSecondPolicyId, + patientOneSecondInsurerId, + patientOneSecondInsurerName, + isHCXEnabled, + ); + }); }); it("Patient Registration using the transfer with no consultation", () => { diff --git a/cypress/pageobject/Patient/PatientInsurance.ts b/cypress/pageobject/Patient/PatientInsurance.ts index be4c25c5535..bdd571e9d0c 100644 --- a/cypress/pageobject/Patient/PatientInsurance.ts +++ b/cypress/pageobject/Patient/PatientInsurance.ts @@ -2,13 +2,41 @@ class PatientInsurance { typePatientInsuranceDetail( containerId: string, fieldId: string, - value: string + value: string, ) { cy.get(`#${containerId}`).within(() => { cy.get(`#${fieldId}`).click().type(value); }); } + selectInsurer(insurer: string) { + cy.intercept("GET", "**/api/v1/hcx/payors/**", { + statusCode: 200, + body: [ + { + name: "test payor 2", + code: "testpayor2.swasthmock@swasth-hcx-staging", + }, + { + name: "Care Payor", + code: "khavinshankar.gmail@swasth-hcx-staging", + }, + { + name: "Alliance", + code: "hcxdemo.yopmail@swasth-hcx-staging", + }, + ], + }).as("getInsurer"); + cy.get("[name='insurer']") + .last() + .click() + .type(insurer) + .then(() => { + cy.wait("@getInsurer"); + cy.get("[role='option']").contains(insurer).click(); + }); + } + clickPatientInsuranceViewDetail() { cy.get("#insurance-view-details").scrollIntoView(); cy.get("#insurance-view-details").click(); @@ -18,13 +46,21 @@ class PatientInsurance { cy.get("[data-testid=add-insurance-button]").click(); } - verifyPatientPolicyDetails(subscriberId, policyId, insurerId, insurerName) { + verifyPatientPolicyDetails( + subscriberId, + policyId, + insurerId, + insurerName, + isHcxEnabled, + ) { cy.get("[data-testid=patient-details]").then(($dashboard) => { cy.url().should("include", "/facility/"); expect($dashboard).to.contain(subscriberId); expect($dashboard).to.contain(policyId); - expect($dashboard).to.contain(insurerId); - expect($dashboard).to.contain(insurerName); + if (!isHcxEnabled) { + expect($dashboard).to.contain(insurerId); + expect($dashboard).to.contain(insurerName); + } }); } } diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 8c1b60f3d88..108733e0e49 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -204,3 +204,9 @@ Cypress.Commands.add("verifyContentPresence", (selector, texts) => { }); }); }); + +Cypress.Commands.add("checkConfig", (configName) => { + return cy.request("/config.json").then((response) => { + return response.body[configName]; + }); +}); diff --git a/cypress/support/index.ts b/cypress/support/index.ts index c9af6a02c96..0b1522b2db8 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -44,6 +44,7 @@ declare global { selector: string, texts: string[], ): Chainable; + checkConfig(configName: string): Chainable; } } } diff --git a/netlify.toml b/netlify.toml index 4ab805434f5..0b2f1efd031 100644 --- a/netlify.toml +++ b/netlify.toml @@ -9,7 +9,7 @@ NODE_OPTIONS = "--max_old_space_size=4096" [[redirects]] from = "/api/*" -to = "https://careapi.ohc.network/api/:splat" +to = "https://work.khavinshankar.dev/api/:splat" status = 200 force = true diff --git a/public/config.json b/public/config.json index dd35eee6420..88ad2cdf5c8 100644 --- a/public/config.json +++ b/public/config.json @@ -22,7 +22,7 @@ "sample_format_asset_import": "/asset-import-template.xlsx", "sample_format_external_result_import": "/External-Results-Template.csv", "enable_abdm": true, - "enable_hcx": false, + "enable_hcx": true, "enable_scribe": false, "min_encounter_date": "2020-01-01" } \ No newline at end of file diff --git a/src/CAREUI/icons/CareIcon.tsx b/src/CAREUI/icons/CareIcon.tsx index 90a59d972cb..3bb67629bc2 100644 --- a/src/CAREUI/icons/CareIcon.tsx +++ b/src/CAREUI/icons/CareIcon.tsx @@ -1,8 +1,7 @@ +import iconData from "./UniconPaths.json"; import { transformIcons } from "./icon"; import { useEffect } from "react"; -import iconData from "./UniconPaths.json"; - export type IconName = keyof typeof iconData; export interface CareIconProps { diff --git a/src/Components/Facility/ConsultationClaims.tsx b/src/Components/Facility/ConsultationClaims.tsx index 3022b7a47a2..c9d617e795c 100644 --- a/src/Components/Facility/ConsultationClaims.tsx +++ b/src/Components/Facility/ConsultationClaims.tsx @@ -1,13 +1,14 @@ -import { useCallback, useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; -import { HCXActions } from "../../Redux/actions"; -import PageTitle from "../Common/PageTitle"; -import ClaimDetailCard from "../HCX/ClaimDetailCard"; +import * as Notification from "../../Utils/Notifications"; + +import { useState } from "react"; + +import ClaimCard from "../HCX/ClaimCard"; import CreateClaimCard from "../HCX/CreateClaimCard"; -import { HCXClaimModel } from "../HCX/models"; -import { useMessageListener } from "../../Common/hooks/useMessageListener"; +import PageTitle from "../Common/PageTitle"; import { navigate } from "raviger"; -import * as Notification from "../../Utils/Notifications"; +import { useMessageListener } from "../../Common/hooks/useMessageListener"; +import useQuery from "../../Utils/request/useQuery"; +import routes from "../../Redux/api"; interface Props { facilityId: string; @@ -20,32 +21,28 @@ export default function ConsultationClaims({ consultationId, patientId, }: Props) { - const dispatch = useDispatch(); - const [claims, setClaims] = useState(); const [isCreateLoading, setIsCreateLoading] = useState(false); - const fetchClaims = useCallback(async () => { - const res = await dispatch( - HCXActions.claims.list({ + const { data: claimsResult, refetch: refetchClaims } = useQuery( + routes.listHCXClaims, + { + query: { ordering: "-modified_date", consultation: consultationId, - }), - ); - - if (res.data && res.data.results) { - setClaims(res.data.results); - if (isCreateLoading) - Notification.Success({ msg: "Fetched Claim Approval Results" }); - } else { - if (isCreateLoading) - Notification.Success({ msg: "Error Fetched Claim Approval Results" }); - } - setIsCreateLoading(false); - }, [dispatch, consultationId]); - - useEffect(() => { - fetchClaims(); - }, [fetchClaims]); + }, + onResponse: (res) => { + if (res.data && res.data.results) { + if (isCreateLoading) + Notification.Success({ msg: "Fetched Claim Approval Results" }); + } else { + if (isCreateLoading) + Notification.Error({ + msg: "Error Fetching Claim Approval Results", + }); + } + }, + }, + ); useMessageListener((data) => { if ( @@ -53,7 +50,7 @@ export default function ConsultationClaims({ (data.from === "claim/on_submit" || data.from === "preauth/on_submit") && data.message === "success" ) { - fetchClaims(); + refetchClaims(); } }); @@ -81,9 +78,9 @@ export default function ConsultationClaims({
- {claims?.map((claim) => ( + {claimsResult?.results.map((claim) => (
- +
))}
diff --git a/src/Components/Facility/DischargeModal.tsx b/src/Components/Facility/DischargeModal.tsx index fe99b4ddd53..69cf2bc6c1d 100644 --- a/src/Components/Facility/DischargeModal.tsx +++ b/src/Components/Facility/DischargeModal.tsx @@ -4,7 +4,7 @@ import { Cancel, Submit } from "../Common/components/ButtonV2"; import { useCallback, useEffect, useState } from "react"; import CareIcon from "../../CAREUI/icons/CareIcon"; -import ClaimDetailCard from "../HCX/ClaimDetailCard"; +import ClaimCard from "../HCX/ClaimCard"; import { ConsultationModel } from "./models"; import CreateClaimCard from "../HCX/CreateClaimCard"; import { DISCHARGE_REASONS } from "../../Common/constants"; @@ -390,7 +390,7 @@ const DischargeModal = ({

Claim Insurance

{latestClaim ? ( - + ) : ( void; - onCapture: (file: File) => void; + onCapture: (file: File, fileName: string) => void; autoRecord?: boolean; } @@ -56,14 +56,11 @@ export default function AudioCaptureDialog(props: AudioCaptureDialogProps) { const handleSubmit = async () => { const response = await fetch(audioURL); const blob = await response.blob(); - const file = new File( - [blob], - `recording_${new Date().toISOString().replaceAll(".", "_").replaceAll(":", "_")}.mp3`, - { type: "audio/mpeg" }, - ); + const fileName = `recording_${new Date().toISOString().replaceAll(".", "_").replaceAll(":", "_")}.mp3`; + const file = new File([blob], fileName, { type: "audio/mpeg" }); resetRecording(); onHide(); - onCapture(file); + onCapture(file, fileName); }; useEffect(() => { diff --git a/src/Components/Files/CameraCaptureDialog.tsx b/src/Components/Files/CameraCaptureDialog.tsx index ee7d7da177c..96de547eebe 100644 --- a/src/Components/Files/CameraCaptureDialog.tsx +++ b/src/Components/Files/CameraCaptureDialog.tsx @@ -9,7 +9,7 @@ import useWindowDimensions from "../../Common/hooks/useWindowDimensions"; export interface CameraCaptureDialogProps { show: boolean; onHide: () => void; - onCapture: (file: File) => void; + onCapture: (file: File, fileName: string) => void; } export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { @@ -41,7 +41,7 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { const myFile = new File([blob], `capture.${extension}`, { type: blob.type, }); - onCapture(myFile); + onCapture(myFile, `capture.${extension}`); }); }; diff --git a/src/Components/Files/FileUpload.tsx b/src/Components/Files/FileUpload.tsx index 2e3d6ea58b4..d827391f650 100644 --- a/src/Components/Files/FileUpload.tsx +++ b/src/Components/Files/FileUpload.tsx @@ -250,15 +250,15 @@ export const FileUpload = (props: FileUploadProps) => { isAuthorized ? ( <>

{UPLOAD_HEADING[type]}

- {fileUpload.file ? ( + {fileUpload.files[0] ? (
- {fileUpload.file.name} + {fileUpload.files[0].name} +
+
+
+ ))} + + setInputText(e.value)} + placeholder={t("enter_message")} + rows={1} + className="-mb-3" + /> + +
+ +
+ + {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/ClaimDetailCard.tsx b/src/Components/HCX/ClaimCardInfo.tsx similarity index 91% rename from src/Components/HCX/ClaimDetailCard.tsx rename to src/Components/HCX/ClaimCardInfo.tsx index ee6671cc90a..c5f3ddc5466 100644 --- a/src/Components/HCX/ClaimDetailCard.tsx +++ b/src/Components/HCX/ClaimCardInfo.tsx @@ -1,11 +1,14 @@ import { classNames, formatCurrency, formatDateTime } from "../../Utils/utils"; -import { HCXClaimModel } from "../HCX/models"; + +import CareIcon from "../../CAREUI/icons/CareIcon"; +import { HCXClaimModel } from "./models"; interface IProps { claim: HCXClaimModel; + setShowMessages: (show: boolean) => void; } -export default function ClaimDetailCard({ claim }: IProps) { +export default function ClaimCardInfo({ claim, setShowMessages }: IProps) { const status = claim.outcome === "Processing Complete" ? claim.error_text @@ -14,7 +17,7 @@ export default function ClaimDetailCard({ claim }: IProps) { : "Pending"; return ( -
+ <>

@@ -29,7 +32,12 @@ export default function ClaimDetailCard({ claim }: IProps) { .

-
+
+ setShowMessages(true)} + /> {claim.use && ( {claim.use} @@ -159,6 +167,6 @@ export default function ClaimDetailCard({ claim }: IProps) { {claim.error_text}
)} -
+ ); } diff --git a/src/Components/HCX/CreateClaimCard.tsx b/src/Components/HCX/CreateClaimCard.tsx index 2ce866d261d..46a02cf7f86 100644 --- a/src/Components/HCX/CreateClaimCard.tsx +++ b/src/Components/HCX/CreateClaimCard.tsx @@ -37,8 +37,6 @@ export default function CreateClaimCard({ const [createdClaim, setCreatedClaim] = useState(); const [use_, setUse_] = useState(use); - console.log(items); - useEffect(() => { async function autoFill() { const latestApprovedPreAuthsRes = await dispatch( diff --git a/src/Components/HCX/InsuranceDetailsBuilder.tsx b/src/Components/HCX/InsuranceDetailsBuilder.tsx index 1e401410ff0..36db895e04f 100644 --- a/src/Components/HCX/InsuranceDetailsBuilder.tsx +++ b/src/Components/HCX/InsuranceDetailsBuilder.tsx @@ -136,7 +136,7 @@ const InsuranceDetailEditCard = ({ {enable_hcx ? ( fileUpload.setFileName(e.value)} />
- {fileUpload.file ? ( + {fileUpload.files[0] ? ( <> { @@ -215,7 +215,7 @@ export default function PatientConsentRecords(props: { diff --git a/src/Locale/en/Common.json b/src/Locale/en/Common.json index 656791c3788..4d3ce953d41 100644 --- a/src/Locale/en/Common.json +++ b/src/Locale/en/Common.json @@ -181,6 +181,11 @@ "feed_optimal_experience_for_phones": "For optimal viewing experience, consider rotating your device.", "feed_optimal_experience_for_apple_phones": "For optimal viewing experience, consider rotating your device. Ensure auto-rotate is enabled in your device settings.", "action_irreversible": "This action is irreversible", + "send_message": "Send Message", + "enter_message": "Start typing your message...", + "see_attachments": "See Attachments", + "no_attachments_found": "This communication has no attachments.", + "fetching": "Fetching", "GENDER__1": "Male", "GENDER__2": "Female", "GENDER__3": "Non-binary", diff --git a/src/Locale/en/FileUpload.json b/src/Locale/en/FileUpload.json index 93b61943944..33914134602 100644 --- a/src/Locale/en/FileUpload.json +++ b/src/Locale/en/FileUpload.json @@ -20,7 +20,8 @@ "file_list_headings__sample_report": "Sample Report", "file_list_headings__supporting_info": "Supporting Info", "file_error__choose_file": "Please choose a file to upload", - "file_error__file_name": "Please enter file name", + "file_error__file_name": "Please give a name for all files!", + "change_file": "Change File", "file_error__file_size": "Maximum size of files is 100 MB", "file_error__file_type": "Invalid file type \".{{extension}}\" Allowed types: {{allowedExtensions}}", "file_uploaded": "File Uploaded Successfully", diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index b03faf07e5a..729d9c62e53 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -4,25 +4,6 @@ import { CreateConsentTBody, } from "../Components/ABDM/types/consent"; import { HealthInformationModel } from "../Components/ABDM/types/health-information"; -import { - IAadhaarOtp, - IAadhaarOtpTBody, - ICheckAndGenerateMobileOtp, - IConfirmMobileOtp, - IcreateHealthFacilityTBody, - ICreateHealthIdRequest, - ICreateHealthIdResponse, - IGenerateMobileOtpTBody, - IgetAbhaCardTBody, - IHealthFacility, - IHealthId, - IinitiateAbdmAuthenticationTBody, - ILinkABHANumber, - ILinkViaQRBody, - IpartialUpdateHealthFacilityTBody, - ISearchByHealthIdTBody, - IVerifyAadhaarOtpTBody, -} from "../Components/ABDM/models"; import { AssetBedBody, AssetBedModel, @@ -34,15 +15,6 @@ import { AvailabilityRecord, PatientAssetBed, } from "../Components/Assets/AssetTypes"; -import { - IDeleteBedCapacity, - IDeleteExternalResult, - IExternalResult, - IExternalResultCsv, - ILocalBodies, - ILocalBodyByDistrict, - IPartialUpdateExternalResult, -} from "../Components/ExternalResult/models"; import { BedModel, CapacityModal, @@ -53,40 +25,86 @@ import { DailyRoundsRes, DistrictModel, DoctorModal, - DupPatientModel, FacilityModel, FacilityRequest, IFacilityNotificationRequest, IFacilityNotificationResponse, + IUserFacilityRequest, InventoryItemsModel, InventoryLogResponse, InventorySummaryResponse, - IUserFacilityRequest, LocalBodyModel, LocationModel, MinimumQuantityItemResponse, - PatientConsentModel, PatientNotesEditModel, PatientNotesModel, PatientStatsModel, - PatientTransferRequest, PatientTransferResponse, StateModel, WardModel, } from "../Components/Facility/models"; +import { + DailyRoundsModel, + PatientModel, + SampleReportModel, + SampleTestModel, +} from "../Components/Patient/models"; +import { + IAadhaarOtp, + IAadhaarOtpTBody, + ICheckAndGenerateMobileOtp, + IConfirmMobileOtp, + ICreateHealthIdRequest, + ICreateHealthIdResponse, + IGenerateMobileOtpTBody, + IHealthFacility, + IHealthId, + ILinkABHANumber, + ILinkViaQRBody, + ISearchByHealthIdTBody, + IVerifyAadhaarOtpTBody, + IcreateHealthFacilityTBody, + IgetAbhaCardTBody, + IinitiateAbdmAuthenticationTBody, + IpartialUpdateHealthFacilityTBody, +} from "../Components/ABDM/models"; +import { IComment, IResource } from "../Components/Resource/models"; +import { + IDeleteBedCapacity, + IDeleteExternalResult, + IExternalResult, + IExternalResultCsv, + ILocalBodies, + ILocalBodyByDistrict, + IPartialUpdateExternalResult, +} from "../Components/ExternalResult/models"; +import { + InvestigationGroup, + InvestigationType, +} from "../Components/Facility/Investigations"; +import { + DupPatientModel, + PatientConsentModel, + PatientTransferRequest, +} from "../Components/Facility/models"; import { MedibaseMedicine, Prescription } from "../Components/Medicine/models"; import { NotificationData, PNconfigData, } from "../Components/Notifications/models"; +import { + HCXClaimModel, + HCXCommunicationModel, + 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 { CreateFileRequest, CreateFileResponse, - DailyRoundsModel, FileUploadModel, - PatientModel, - SampleReportModel, - SampleTestModel, } from "../Components/Patient/models"; import { SkillModel, @@ -95,22 +113,11 @@ import { UserAssignedModel, UserModel, } from "../Components/Users/models"; -import { PaginatedResponse } from "../Utils/request/types"; - -import { ICD11DiagnosisModel } from "../Components/Diagnosis/types"; import { EventGeneric, type Type, } from "../Components/Facility/ConsultationDetails/Events/types"; -import { - InvestigationGroup, - InvestigationType, -} from "../Components/Facility/Investigations"; import { InvestigationSessionType } from "../Components/Facility/Investigations/investigationsTab"; -import { Investigation } from "../Components/Facility/Investigations/Reports/types"; -import { HCXPolicyModel } from "../Components/HCX/models"; -import { IComment, IResource } from "../Components/Resource/models"; -import { IShift } from "../Components/Shifting/models"; import { ScribeModel } from "../Components/Scribe/Scribe"; /** @@ -1626,6 +1633,7 @@ const routes = { listHCXClaims: { path: "/api/v1/hcx/claim/", method: "GET", + TRes: Type>(), }, createHCXClaim: { @@ -1657,6 +1665,57 @@ const routes = { path: "/api/v1/hcx/make_claim/", method: "POST", }, + + listHCXCommunications: { + path: "/api/v1/hcx/communication/", + method: "GET", + TRes: Type>(), + }, + + createHCXCommunication: { + path: "/api/v1/hcx/communication/", + method: "POST", + TRes: Type(), + TBody: Type<{ + claim: string; + content: { + type: string; + data: string; + }[]; + }>(), + }, + + getHCXCommunication: { + path: "/api/v1/hcx/communication/{external_id}/", + method: "GET", + TRes: Type(), + }, + + updateHCXCommunication: { + path: "/api/v1/hcx/communication/{external_id}/", + method: "PUT", + TRes: Type(), + }, + + partialUpdateHCXCommunication: { + path: "/api/v1/hcx/communication/{external_id}/", + method: "PATCH", + TRes: Type(), + }, + + deleteHCXCommunication: { + path: "/api/v1/hcx/communication/{external_id}/", + method: "DELETE", + }, + + hcxSendCommunication: { + path: "/api/v1/hcx/send_communication/", + method: "POST", + TRes: Type(), + TBody: Type<{ + communication: string; + }>(), + }, } as const; export default routes; diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx index bba3bd04899..422dfe6804f 100644 --- a/src/Routers/AppRouter.tsx +++ b/src/Routers/AppRouter.tsx @@ -64,7 +64,7 @@ export default function AppRouter() { let routes = Routes; if (enable_hcx) { - routes = { ...routes, ...HCXRoutes }; + routes = { ...HCXRoutes, ...routes }; } if ( diff --git a/src/Utils/useFileUpload.tsx b/src/Utils/useFileUpload.tsx index 49e4f9d99bd..377a057449d 100644 --- a/src/Utils/useFileUpload.tsx +++ b/src/Utils/useFileUpload.tsx @@ -20,6 +20,7 @@ import AudioCaptureDialog from "../Components/Files/AudioCaptureDialog"; import { t } from "i18next"; export type FileUploadOptions = { + multiple?: boolean; type: string; category?: FileCategory; onUpload?: (file: FileUploadModel) => void; @@ -41,15 +42,18 @@ export interface FileInputProps export type FileUploadReturn = { progress: null | number; error: null | string; + validateFiles: () => boolean; handleCameraCapture: () => void; handleAudioCapture: () => void; handleFileUpload: (associating_id: string) => Promise; Dialogues: JSX.Element; Input: (_: FileInputProps) => JSX.Element; - fileName: string; - file: File | null; - setFileName: (name: string) => void; - clearFile: () => void; + fileNames: string[]; + files: File[]; + setFileName: (names: string, index?: number) => void; + setFileNames: (names: string[]) => void; + removeFile: (index: number) => void; + clearFiles: () => void; }; // Array of image extensions @@ -67,71 +71,72 @@ const ExtImage: string[] = [ export default function useFileUpload( options: FileUploadOptions, ): FileUploadReturn { - const { type, onUpload, category = "UNSPECIFIED" } = options; + const { type, onUpload, category = "UNSPECIFIED", multiple } = options; - const [uploadFileName, setUploadFileName] = useState(""); + const [uploadFileNames, setUploadFileNames] = useState([]); const [error, setError] = useState(null); const [progress, setProgress] = useState(null); const [cameraModalOpen, setCameraModalOpen] = useState(false); const [audioModalOpen, setAudioModalOpen] = useState(false); - const [file, setFile] = useState(null); + const [files, setFiles] = useState([]); const onFileChange = (e: ChangeEvent): any => { if (!e.target.files?.length) { return; } - const f = e.target.files[0]; - const fileName = f.name; - setFile(e.target.files[0]); - setUploadFileName( - uploadFileName || - fileName.substring(0, fileName.lastIndexOf(".")) || - fileName, - ); + const selectedFiles = Array.from(e.target.files); + const fileNames = selectedFiles.map((file) => file.name); + setFiles((prev) => [...prev, ...selectedFiles]); + setUploadFileNames((prev) => [...prev, ...fileNames]); - const ext: string = fileName.split(".")[1]; - - if (ExtImage.includes(ext)) { - const options = { - initialQuality: 0.6, - alwaysKeepResolution: true, - }; - imageCompression(f, options).then((compressedFile: File) => { - setFile(compressedFile); - }); - return; - } - setFile(f); + selectedFiles.forEach((file) => { + const ext: string = file.name.split(".")[1]; + if (ExtImage.includes(ext)) { + const options = { + initialQuality: 0.6, + alwaysKeepResolution: true, + }; + imageCompression(file, options).then((compressedFile: File) => { + setFiles((prev) => + prev.map((f) => (f.name === file.name ? compressedFile : f)), + ); + }); + } + }); }; const validateFileUpload = () => { - const filenameLength = uploadFileName.trim().length; - const f = file; - if (f === undefined || f === null) { + if (files.length === 0) { setError(t("file_error__choose_file")); return false; } - if (filenameLength === 0) { - setError(t("file_error__file_name")); - return false; - } - if (f.size > 10e7) { - setError(t("file_error__file_size")); - return false; - } - const extension = f.name.split(".").pop(); - if ( - "allowedExtensions" in options && - !options.allowedExtensions?.includes(extension || "") - ) { - setError( - t("file_error__file_type", { - extension, - allowedExtensions: options.allowedExtensions?.join(", "), - }), - ); - return false; + + for (const file of files) { + const filenameLength = file.name.trim().length; + if (filenameLength === 0) { + setError(t("file_error__file_name")); + return false; + } + if (file.size > 10e7) { + setError(t("file_error__file_size")); + return false; + } + const extension = file.name.split(".").pop(); + if ( + "allowedExtensions" in options && + !options.allowedExtensions + ?.map((extension) => extension.replace(".", "")) + ?.includes(extension || "") + ) { + setError( + t("file_error__file_type", { + extension, + allowedExtensions: options.allowedExtensions?.join(", "), + }), + ); + return false; + } } return true; }; @@ -149,23 +154,20 @@ export default function useFileUpload( }); }; - const uploadfile = async (data: CreateFileResponse) => { + const uploadfile = async (data: CreateFileResponse, file: File) => { const url = data.signed_url; const internal_name = data.internal_name; - const f = file; - if (!f) return; - const newFile = new File([f], `${internal_name}`); + const newFile = new File([file], `${internal_name}`); + return new Promise((resolve, reject) => { uploadFile( url, newFile, "PUT", - { "Content-Type": file?.type }, + { "Content-Type": file.type }, (xhr: XMLHttpRequest) => { if (xhr.status >= 200 && xhr.status < 300) { setProgress(null); - setFile(null); - setUploadFileName(""); Notification.Success({ msg: t("file_uploaded"), }); @@ -194,27 +196,34 @@ export default function useFileUpload( const handleUpload = async (associating_id: string) => { if (!validateFileUpload()) return; - const f = file; - const filename = uploadFileName === "" && f ? f.name : uploadFileName; - const name = f?.name; setProgress(0); - const { data } = await request(routes.createUpload, { - body: { - original_name: name ?? "", - file_type: type, - name: filename, - associating_id, - file_category: category, - mime_type: f?.type ?? "", - }, - }); + for (const [index, file] of files.entries()) { + const filename = + uploadFileNames[index] === "" && file + ? file.name + : uploadFileNames[index]; - if (data) { - await uploadfile(data); - await markUploadComplete(data, associating_id); + const { data } = await request(routes.createUpload, { + body: { + original_name: file.name ?? "", + file_type: type, + name: filename, + associating_id, + file_category: category, + mime_type: file.type ?? "", + }, + }); + + if (data) { + await uploadfile(data, file); + await markUploadComplete(data, associating_id); + } } + + setFiles([]); + setUploadFileNames([]); }; const Dialogues = ( @@ -222,17 +231,17 @@ export default function useFileUpload( setCameraModalOpen(false)} - onCapture={(f) => { - setFile(f); - setUploadFileName(uploadFileName || ""); + onCapture={(file, fileName) => { + setFiles((prev) => [...prev, file]); + setUploadFileNames((prev) => [...prev, fileName]); }} /> setAudioModalOpen(false)} - onCapture={(f) => { - setFile(f); - setUploadFileName(uploadFileName || ""); + onCapture={(file, fileName) => { + setFiles((prev) => [...prev, file]); + setUploadFileNames((prev) => [...prev, fileName]); }} autoRecord /> @@ -243,9 +252,10 @@ export default function useFileUpload( setCameraModalOpen(true), handleAudioCapture: () => setAudioModalOpen(true), handleFileUpload: handleUpload, Dialogues, Input, - fileName: uploadFileName, - file: file, - setFileName: setUploadFileName, - clearFile: () => { - setFile(null); - setError(null); - setUploadFileName(""); + fileNames: uploadFileNames, + files: files, + setFileNames: setUploadFileNames, + setFileName: (name: string, index = 0) => { + setUploadFileNames((prev) => + prev.map((n, i) => (i === index ? name : n)), + ); + }, + removeFile: (index = 0) => { + setFiles((prev) => prev.filter((_, i) => i !== index)); + setUploadFileNames((prev) => prev.filter((_, i) => i !== index)); + }, + clearFiles: () => { + setFiles([]); + setUploadFileNames([]); }, }; }