From cd6ce2c313453a09aa799dea341435f8df87dd2b Mon Sep 17 00:00:00 2001 From: Khavin Shankar Date: Thu, 23 May 2024 14:50:01 +0530 Subject: [PATCH 1/6] fix placeholder overlapping in search input (#7865) --- src/Components/Form/SearchInput.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Components/Form/SearchInput.tsx b/src/Components/Form/SearchInput.tsx index e64aad207d1..2d146837ebf 100644 --- a/src/Components/Form/SearchInput.tsx +++ b/src/Components/Form/SearchInput.tsx @@ -80,7 +80,7 @@ const SearchInput = ({ props.trailing || (!props.secondary && (
- + {shortcutKeyIcon}
@@ -88,7 +88,7 @@ const SearchInput = ({ } trailingFocused={
- + Esc
From 63a2cdb7e2378a412835605ca96cd70b8802766d Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Fri, 24 May 2024 23:41:06 +0530 Subject: [PATCH 2/6] Separate page for patient consent records (#7882) * . * completed patient consent records * Update src/Components/Patient/PatientInfoCard.tsx Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> * removed delete in favour of archiving * fixed error * abstracted to file manager * refetch * removed group unarchive and changed layout * fixed archive refresh and fixed bed popup * lint fix * fixed archiving * added no records found and hidden archive button * added no records found and hidden archive button --- src/Common/constants.tsx | 2 +- src/Components/Facility/ConsultationForm.tsx | 207 +------- src/Components/Facility/models.tsx | 10 +- src/Components/Patient/FileUpload.tsx | 5 +- .../Patient/PatientConsentRecordBlock.tsx | 157 ++++++ .../Patient/PatientConsentRecords.tsx | 307 ++++++++++++ src/Components/Patient/PatientHome.tsx | 11 +- src/Components/Patient/PatientInfoCard.tsx | 28 +- src/Components/Patient/models.tsx | 2 +- src/Routers/routes/ConsultationRoutes.tsx | 9 + src/Utils/useFileManager.tsx | 256 ++++++++++ src/Utils/useFileUpload.tsx | 448 ++++++++++++++++++ 12 files changed, 1232 insertions(+), 210 deletions(-) create mode 100644 src/Components/Patient/PatientConsentRecordBlock.tsx create mode 100644 src/Components/Patient/PatientConsentRecords.tsx create mode 100644 src/Utils/useFileManager.tsx create mode 100644 src/Utils/useFileUpload.tsx diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index de0ab305f48..5fbff1b48cb 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -1308,7 +1308,7 @@ export const CONSENT_PATIENT_CODE_STATUS_CHOICES = [ { id: 1, text: "Do Not Hospitalise (DNH)" }, { id: 2, text: "Do Not Resuscitate (DNR)" }, { id: 3, text: "Comfort Care Only" }, - { id: 4, text: "Active treatment (Default)" }, + { id: 4, text: "Active treatment" }, ]; export const OCCUPATION_TYPES = [ { diff --git a/src/Components/Facility/ConsultationForm.tsx b/src/Components/Facility/ConsultationForm.tsx index 7e6fe345f7c..8a9a19db2bc 100644 --- a/src/Components/Facility/ConsultationForm.tsx +++ b/src/Components/Facility/ConsultationForm.tsx @@ -1,6 +1,6 @@ import * as Notification from "../../Utils/Notifications.js"; -import { BedModel, FacilityModel } from "./models"; +import { BedModel, ConsentRecord, FacilityModel } from "./models"; import { CONSULTATION_SUGGESTION, DISCHARGE_REASONS, @@ -8,8 +8,6 @@ import { PATIENT_CATEGORIES, REVIEW_AT_CHOICES, TELEMEDICINE_ACTIONS, - CONSENT_TYPE_CHOICES, - CONSENT_PATIENT_CODE_STATUS_CHOICES, } from "../../Common/constants"; import { Cancel, Submit } from "../Common/components/ButtonV2"; import { DraftSection, useAutoSaveReducer } from "../../Utils/AutoSave"; @@ -59,8 +57,6 @@ import { CreateDiagnosesBuilder, EditDiagnosesBuilder, } from "../Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisBuilder.js"; -import { FileUpload } from "../Patient/FileUpload.js"; -import ConfirmDialog from "../Common/ConfirmDialog.js"; import request from "../../Utils/request/request.js"; import routes from "../../Redux/api.js"; import useQuery from "../../Utils/request/useQuery.js"; @@ -71,13 +67,6 @@ const PageTitle = lazy(() => import("../Common/PageTitle")); type BooleanStrings = "true" | "false"; -export type ConsentRecord = { - id: string; - type: (typeof CONSENT_TYPE_CHOICES)[number]["id"]; - patient_code_status?: (typeof CONSENT_PATIENT_CODE_STATUS_CHOICES)[number]["id"]; - deleted?: boolean; -}; - type FormDetails = { symptoms: number[]; other_symptoms: string; @@ -125,6 +114,7 @@ type FormDetails = { death_datetime: string; death_confirmed_doctor: string; InvestigationAdvice: InvestigationType[]; + procedures: ProcedureType[]; consent_records: ConsentRecord[]; }; @@ -175,6 +165,7 @@ const initForm: FormDetails = { death_datetime: "", death_confirmed_doctor: "", InvestigationAdvice: [], + procedures: [], consent_records: [], }; @@ -226,7 +217,6 @@ type ConsultationFormSection = | "Consultation Details" | "Diagnosis" | "Treatment Plan" - | "Consent Records" | "Bed Status"; type Props = { @@ -259,14 +249,8 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { const [diagnosisVisible, diagnosisRef] = useVisibility(-300); const [treatmentPlanVisible, treatmentPlanRef] = useVisibility(-300); const [bedStatusVisible, bedStatusRef] = useVisibility(-300); - const [consentRecordsVisible, consentRecordsRef] = useVisibility(-300); + const [disabledFields, setDisabledFields] = useState([]); - const [collapsedConsentRecords, setCollapsedConsentRecords] = useState< - number[] - >([]); - const [showDeleteConsent, setShowDeleteConsent] = useState( - null, - ); const { min_encounter_date } = useConfig(); @@ -286,11 +270,6 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { visible: treatmentPlanVisible, ref: treatmentPlanRef, }, - "Consent Records": { - iconClass: "l-file-alt", - visible: consentRecordsVisible, - ref: consentRecordsRef, - }, "Bed Status": { iconClass: "l-bed", visible: bedStatusVisible, @@ -303,7 +282,6 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { if (consultationDetailsVisible) return "Consultation Details"; if (diagnosisVisible) return "Diagnosis"; if (treatmentPlanVisible) return "Treatment Plan"; - if (consentRecordsVisible) return "Consent Records"; if (bedStatusVisible) return "Bed Status"; return prev; }); @@ -311,7 +289,6 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { consultationDetailsVisible, diagnosisVisible, treatmentPlanVisible, - consentRecordsVisible, bedStatusVisible, ]); @@ -769,7 +746,6 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { height: Number(state.form.height), bed: bed && bed instanceof Array ? bed[0]?.id : bed?.id, patient_no: state.form.patient_no || null, - consent_records: state.form.consent_records || [], }; const { data: obj } = await request( @@ -917,64 +893,6 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { }; }; - const handleConsentTypeChange: FieldChangeEventHandler = async ( - event, - ) => { - if (!id) return; - const consentRecords = [...state.form.consent_records]; - if ( - consentRecords - .filter((cr) => cr.deleted !== true) - .map((cr) => cr.type) - .includes(event.value) - ) { - return; - } else { - const randomId = "consent-" + new Date().getTime().toString(); - const newRecords = [ - ...consentRecords, - { id: randomId, type: event.value }, - ]; - await request(routes.partialUpdateConsultation, { - pathParams: { id }, - body: { consent_records: newRecords }, - }); - dispatch({ - type: "set_form", - form: { ...state.form, consent_records: newRecords }, - }); - } - }; - - const handleConsentPCSChange: FieldChangeEventHandler = (event) => { - dispatch({ - type: "set_form", - form: { - ...state.form, - consent_records: state.form.consent_records.map((cr) => - cr.type === 2 ? { ...cr, patient_code_status: event.value } : cr, - ), - }, - }); - }; - - const handleDeleteConsent = async () => { - const consent_id = showDeleteConsent; - if (!consent_id || !id) return; - const newRecords = state.form.consent_records.map((cr) => - cr.id === consent_id ? { ...cr, deleted: true } : cr, - ); - await request(routes.partialUpdateConsultation, { - pathParams: { id }, - body: { consent_records: newRecords }, - }); - dispatch({ - type: "set_form", - form: { ...state.form, consent_records: newRecords }, - }); - setShowDeleteConsent(null); - }; - return (
{
{Object.keys(sections).map((sectionTitle) => { - if ( - !isUpdate && - ["Bed Status", "Consent Records"].includes(sectionTitle) - ) { + if (!isUpdate && ["Bed Status"].includes(sectionTitle)) { return null; } const isCurrent = currentSection === sectionTitle; @@ -1551,118 +1466,6 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { )}
- {id && ( - <> -
- {sectionTitle("Consent Records", true)} -
- setShowDeleteConsent(null)} - onConfirm={handleDeleteConsent} - action="Delete" - variant="danger" - description={ - "Are you sure you want to delete this consent record?" - } - title="Delete Consent" - className="w-auto" - /> - - !state.form.consent_records - .filter((r) => r.deleted !== true) - .map((record) => record.type) - .includes(c.id), - )} - /> -
- {state.form.consent_records - .filter((record) => record.deleted !== true) - .map((record, index) => ( -
-
- - -
-
-
- {record.type === 2 && ( - - )} -
- -
-
- ))} -
- - )}
diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index 49a2f1346af..252603ab89f 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -1,4 +1,6 @@ import { + CONSENT_PATIENT_CODE_STATUS_CHOICES, + CONSENT_TYPE_CHOICES, ConsultationSuggestionValue, DISCHARGE_REASONS, PATIENT_NOTES_THREADS, @@ -12,7 +14,6 @@ import { ConsultationDiagnosis, CreateDiagnosis } from "../Diagnosis/types"; import { NormalPrescription, PRNPrescription } from "../Medicine/models"; import { AssignedToObjectModel, DailyRoundsModel } from "../Patient/models"; import { UserBareMinimum } from "../Users/models"; -import { ConsentRecord } from "./ConsultationForm"; export interface LocalBodyModel { id: number; @@ -97,6 +98,13 @@ export interface OptionsType { export type PatientCategory = "Comfort Care" | "Mild" | "Moderate" | "Critical"; +export type ConsentRecord = { + id: string; + type: (typeof CONSENT_TYPE_CHOICES)[number]["id"]; + patient_code_status?: (typeof CONSENT_PATIENT_CODE_STATUS_CHOICES)[number]["id"]; + deleted?: boolean; +}; + export interface ConsultationModel { encounter_date: string; icu_admission_date?: string; diff --git a/src/Components/Patient/FileUpload.tsx b/src/Components/Patient/FileUpload.tsx index 81e92efdf8b..6f63a697229 100644 --- a/src/Components/Patient/FileUpload.tsx +++ b/src/Components/Patient/FileUpload.tsx @@ -57,7 +57,7 @@ export const header_content_type: URLS = { }; // Array of image extensions -const ExtImage: string[] = [ +export const ExtImage: string[] = [ "jpeg", "jpg", "png", @@ -119,12 +119,13 @@ interface URLS { [id: string]: string; } -interface ModalDetails { +export interface ModalDetails { name?: string; id?: string; reason?: string; userArchived?: string; archiveTime?: any; + associatedId?: string; } export interface StateInterface { diff --git a/src/Components/Patient/PatientConsentRecordBlock.tsx b/src/Components/Patient/PatientConsentRecordBlock.tsx new file mode 100644 index 00000000000..8f1d3715e8e --- /dev/null +++ b/src/Components/Patient/PatientConsentRecordBlock.tsx @@ -0,0 +1,157 @@ +import dayjs from "dayjs"; +import { + CONSENT_PATIENT_CODE_STATUS_CHOICES, + CONSENT_TYPE_CHOICES, +} from "../../Common/constants"; +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; +import { ConsentRecord } from "../Facility/models"; +import { FileUploadModel } from "./models"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import ButtonV2 from "../Common/components/ButtonV2"; +import { useEffect } from "react"; +import useAuthUser from "../../Common/hooks/useAuthUser"; + +export default function PatientConsentRecordBlockGroup(props: { + consentRecord: ConsentRecord; + previewFile: (file: FileUploadModel, file_associating_id: string) => void; + archiveFile: (file: FileUploadModel, file_associating_id: string) => void; + onDelete: (consentRecord: ConsentRecord) => void; + refreshTrigger: any; + showArchive: boolean; + onFilesFound: () => void; +}) { + const { + consentRecord, + previewFile, + archiveFile, + refreshTrigger, + showArchive, + } = props; + + const authUser = useAuthUser(); + + const filesQuery = useQuery(routes.viewUpload, { + query: { + file_type: "CONSENT_RECORD", + associating_id: consentRecord.id, + is_archived: false, + limit: 100, + offset: 0, + }, + }); + + const archivedFilesQuery = useQuery(routes.viewUpload, { + query: { + file_type: "CONSENT_RECORD", + associating_id: consentRecord.id, + is_archived: true, + limit: 100, + offset: 0, + }, + }); + + const consent = CONSENT_TYPE_CHOICES.find((c) => c.id === consentRecord.type); + const consentPCS = CONSENT_PATIENT_CODE_STATUS_CHOICES.find( + (c) => c.id === consentRecord.patient_code_status, + ); + + const data = showArchive + ? [ + ...(archivedFilesQuery.data?.results || []), + ...(filesQuery.data?.results || []), + ] + : filesQuery.data?.results; + + useEffect(() => { + filesQuery.refetch(); + archivedFilesQuery.refetch(); + }, [refreshTrigger]); + + useEffect(() => { + if ((data?.length || 0) > 1) { + props.onFilesFound(); + } + }, [data]); + + return ( +
+
+
+

+ {consent?.text} {consentPCS?.text && `(${consentPCS.text})`} +

+ {consentRecord.deleted && ( +
+
+ + Archived +
+
+ )} +
+ {/* + {!consentRecord.deleted && !showArchive && ( + + )} + */} +
+ + {data?.map((file: FileUploadModel, i: number) => ( +
+
+
+ +
+
+
+ {file.name} + {file.extension} {file.is_archived && "(Archived)"} +
+
+ {dayjs(file.created_date).format("DD MMM YYYY, hh:mm A")} +
+
+
+
+ {!file.is_archived && ( + previewFile(file, consentRecord.id)} + className="" + > + + View + + )} + {(file.is_archived || + file?.uploaded_by?.username === authUser.username || + authUser.user_type === "DistrictAdmin" || + authUser.user_type === "StateAdmin") && ( + archiveFile(file, consentRecord.id)} + className="" + > + + {file.is_archived ? "More Info" : "Archive"} + + )} +
+
+ ))} +
+ ); +} diff --git a/src/Components/Patient/PatientConsentRecords.tsx b/src/Components/Patient/PatientConsentRecords.tsx new file mode 100644 index 00000000000..7b81bb562c0 --- /dev/null +++ b/src/Components/Patient/PatientConsentRecords.tsx @@ -0,0 +1,307 @@ +import { useEffect, useState } from "react"; +import { + CONSENT_PATIENT_CODE_STATUS_CHOICES, + CONSENT_TYPE_CHOICES, +} from "../../Common/constants"; +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; +import Page from "../Common/components/Page"; +import { ConsentRecord } from "../Facility/models"; +import request from "../../Utils/request/request"; +import ConfirmDialog from "../Common/ConfirmDialog"; +import { SelectFormField } from "../Form/FormFields/SelectFormField"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import { formatDateTime } from "../../Utils/utils"; +import TextFormField from "../Form/FormFields/TextFormField"; +import ButtonV2 from "../Common/components/ButtonV2"; +import useFileUpload from "../../Utils/useFileUpload"; +import PatientConsentRecordBlockGroup from "./PatientConsentRecordBlock"; +import SwitchTabs from "../Common/components/SwitchTabs"; +import useFileManager from "../../Utils/useFileManager"; + +export default function PatientConsentRecords(props: { + facilityId: string; + patientId: string; + consultationId: string; +}) { + const { facilityId, patientId, consultationId } = props; + const [showArchived, setShowArchived] = useState(false); + const [filesFound, setFilesFound] = useState(false); + const [showPCSChangeModal, setShowPCSChangeModal] = useState( + null, + ); + const [newConsent, setNewConsent] = useState({ + type: 0, + patient_code_status: 4, + }); + + const fileUpload = useFileUpload({ + type: "CONSENT_RECORD", + }); + + const fileManager = useFileManager({ + type: "CONSENT_RECORD", + onArchive: async () => { + refetch(); + }, + }); + + const { data: patient } = useQuery(routes.getPatient, { + pathParams: { + id: patientId, + }, + }); + const { data: consultation, refetch } = useQuery(routes.getConsultation, { + pathParams: { id: consultationId! }, + onResponse: (data) => { + if (data.data && data.data.consent_records) { + setConsentRecords(data.data.consent_records); + } + }, + }); + + const [showDeleteConsent, setShowDeleteConsent] = useState( + null, + ); + + const [consentRecords, setConsentRecords] = useState( + null, + ); + + const handleDeleteConsent = async () => { + const consent_id = showDeleteConsent; + if (!consent_id || !consultationId || !consentRecords) return; + const newRecords = consentRecords.map((cr) => + cr.id === consent_id ? { ...cr, deleted: true } : cr, + ); + setConsentRecords(newRecords); + setShowDeleteConsent(null); + }; + + const selectField = (name: string) => { + return { + name, + optionValue: (option: any) => option.id, + optionLabel: (option: any) => option.text, + optionDescription: (option: any) => option.desc, + }; + }; + + const handleUpload = async (diffPCS?: ConsentRecord) => { + if (newConsent.type === 0) return; + const consentTypeExists = consentRecords?.find( + (record) => record.type === newConsent.type && record.deleted !== true, + ); + if (consentTypeExists && !diffPCS) { + await fileUpload.handleFileUpload(consentTypeExists.id); + } else { + const randomId = "consent-" + new Date().getTime().toString(); + const newRecords = [ + ...(consentRecords?.map((r) => + r.id === diffPCS?.id ? { ...r, deleted: true } : r, + ) || []), + { + id: randomId, + type: newConsent.type, + patient_code_status: + newConsent.type === 2 ? newConsent.patient_code_status : undefined, + }, + ]; + await request(routes.partialUpdateConsultation, { + pathParams: { id: consultationId }, + body: { consent_records: newRecords }, + }); + await fileUpload.handleFileUpload(randomId); + setConsentRecords(newRecords); + } + + refetch(); + }; + + useEffect(() => { + const timeout = setTimeout(async () => { + if (consentRecords) { + await request(routes.partialUpdateConsultation, { + pathParams: { id: consultationId }, + body: { consent_records: consentRecords }, + }); + } + }, 1000); + return () => clearTimeout(timeout); + }, [consentRecords]); + + const tabConsents = consentRecords?.filter( + (record) => showArchived || record.deleted !== true, + ); + + useEffect(() => { + setFilesFound(false); + }, [showArchived]); + + return ( + + + {fileManager.Dialogues} + setShowDeleteConsent(null)} + onConfirm={handleDeleteConsent} + action="Archive" + variant="danger" + description={ + "Are you sure you want to archive this consent record? You can find it in the archive section." + } + title="Archive Consent" + className="w-auto" + /> + setShowPCSChangeModal(null)} + onConfirm={() => { + if (showPCSChangeModal !== null) { + handleUpload( + consentRecords?.find( + (record) => + record.type === 2 && + !record.deleted && + record.patient_code_status !== showPCSChangeModal, + ), + ); + } + setShowPCSChangeModal(null); + }} + action="Change Patient Code Status" + variant="danger" + description={`Consent records exist with the "${CONSENT_PATIENT_CODE_STATUS_CHOICES.find((c) => consentRecords?.find((c) => c.type === 2 && !c.deleted)?.patient_code_status === c.id)?.text}" patient code status. Adding a new record for a different type will archive the existing records. Are you sure you want to proceed?`} + title="Archive Previous Records" + className="w-auto" + /> + setShowArchived(false)} + onClickTab2={() => setShowArchived(true)} + isTab2Active={showArchived} + /> +
+
+

Add New Record

+ { + setNewConsent({ ...newConsent, type: e.value }); + }} + value={newConsent.type} + label="Consent Type" + options={CONSENT_TYPE_CHOICES} + required + /> + {newConsent.type === 2 && ( + { + setNewConsent({ + ...newConsent, + patient_code_status: e.value, + }); + }} + label="Patient Code Status" + value={newConsent.patient_code_status} + options={CONSENT_PATIENT_CODE_STATUS_CHOICES} + required + /> + )} + fileUpload.setFileName(e.value)} + /> +
+ {fileUpload.file ? ( + <> + { + const diffPCS = consentRecords?.find( + (record) => + record.type === 2 && + record.patient_code_status !== + newConsent.patient_code_status && + record.deleted !== true, + ); + if (diffPCS) { + setShowPCSChangeModal(newConsent.patient_code_status); + } else { + handleUpload(); + } + }} + loading={!!fileUpload.progress} + className="flex-1" + > + + Upload + + + + + + ) : ( + <> + + + + )} +
+
+
+ {tabConsents?.length === 0 || + (!filesFound && ( +
+ No records found +
+ ))} +
+ {tabConsents?.map((record, index) => ( + setShowDeleteConsent(record.id)} + refreshTrigger={consultation} + showArchive={showArchived} + onFilesFound={() => setFilesFound(true)} + /> + ))} +
+
+
+
+ ); +} diff --git a/src/Components/Patient/PatientHome.tsx b/src/Components/Patient/PatientHome.tsx index 50fc5819314..991e5636106 100644 --- a/src/Components/Patient/PatientHome.tsx +++ b/src/Components/Patient/PatientHome.tsx @@ -552,7 +552,7 @@ export const PatientHome = (props: any) => { 0 && (
{

)} + {( + patientData.last_consultation?.consent_records?.filter( + (c) => !c.deleted, + ) || [] + ).length < 1 && ( +
+ Consent Records Missing +
+ )}
diff --git a/src/Components/Patient/PatientInfoCard.tsx b/src/Components/Patient/PatientInfoCard.tsx index 7a88c478bba..a2e6dbaf01c 100644 --- a/src/Components/Patient/PatientInfoCard.tsx +++ b/src/Components/Patient/PatientInfoCard.tsx @@ -352,6 +352,18 @@ export default function PatientInfoCard(props: {
)} + {( + consultation?.consent_records?.filter((c) => !c.deleted) || + [] + ).length < 1 && ( +
+
+ + Consent Records Missing + +
+
+ )} {consultation?.suggestion === "DC" && (
@@ -634,6 +646,12 @@ export default function PatientInfoCard(props: { consultation?.id && !consultation?.discharge_date, ], + [ + `/facility/${patient.facility}/patient/${patient.id}/consultation/${consultation?.id}/consent-records`, + "Consent Records", + "l-file-medical", + patient.is_active, + ], [ `/patient/${patient.id}/investigation_reports`, "Investigation Summary", @@ -667,7 +685,10 @@ export default function PatientInfoCard(props: { key={i} className="dropdown-item-primary pointer-events-auto m-2 flex cursor-pointer items-center justify-start gap-2 rounded border-0 p-2 text-sm font-normal transition-all duration-200 ease-in-out" href={ - action[1] !== "Treatment Summary" && + ![ + "Treatment Summary", + "Consent Records", + ].includes(action[1]) && consultation?.admitted && !consultation?.current_bed && i === 1 @@ -676,7 +697,10 @@ export default function PatientInfoCard(props: { } onClick={() => { if ( - action[1] !== "Treatment Summary" && + ![ + "Treatment Summary", + "Consent Records", + ].includes(action[1]) && consultation?.admitted && !consultation?.current_bed && i === 1 diff --git a/src/Components/Patient/models.tsx b/src/Components/Patient/models.tsx index b6f6fae7ea2..6d6e0b3c979 100644 --- a/src/Components/Patient/models.tsx +++ b/src/Components/Patient/models.tsx @@ -332,7 +332,7 @@ export interface FacilityNameModel { // File Upload Models -type FileCategory = "UNSPECIFIED" | "XRAY" | "AUDIO" | "IDENTITY_PROOF"; +export type FileCategory = "UNSPECIFIED" | "XRAY" | "AUDIO" | "IDENTITY_PROOF"; export interface CreateFileRequest { file_type: string | number; diff --git a/src/Routers/routes/ConsultationRoutes.tsx b/src/Routers/routes/ConsultationRoutes.tsx index 6dc5fa9c05d..8b75e3f147f 100644 --- a/src/Routers/routes/ConsultationRoutes.tsx +++ b/src/Routers/routes/ConsultationRoutes.tsx @@ -9,6 +9,7 @@ import { make as CriticalCareRecording } from "../../Components/CriticalCareReco import { ConsultationDetails } from "../../Components/Facility/ConsultationDetails"; import TreatmentSummary from "../../Components/Facility/TreatmentSummary"; import ConsultationDoctorNotes from "../../Components/Facility/ConsultationDoctorNotes"; +import PatientConsentRecords from "../../Components/Patient/PatientConsentRecords"; export default { "/facility/:facilityId/patient/:patientId/consultation": ({ @@ -22,6 +23,14 @@ export default { }: any) => ( ), + "/facility/:facilityId/patient/:patientId/consultation/:id/consent-records": + ({ facilityId, patientId, id }: any) => ( + + ), "/facility/:facilityId/patient/:patientId/consultation/:id/files/": ({ facilityId, patientId, diff --git a/src/Utils/useFileManager.tsx b/src/Utils/useFileManager.tsx new file mode 100644 index 00000000000..aa57e1918a8 --- /dev/null +++ b/src/Utils/useFileManager.tsx @@ -0,0 +1,256 @@ +import { useState } from "react"; +import FilePreviewDialog from "../Components/Common/FilePreviewDialog"; +import { FileUploadModel } from "../Components/Patient/models"; +import { ExtImage, StateInterface } from "../Components/Patient/FileUpload"; +import request from "./request/request"; +import routes from "../Redux/api"; +import DialogModal from "../Components/Common/Dialog"; +import CareIcon from "../CAREUI/icons/CareIcon"; +import TextAreaFormField from "../Components/Form/FormFields/TextAreaFormField"; +import { Cancel, Submit } from "../Components/Common/components/ButtonV2"; +import { formatDateTime } from "./utils"; +import * as Notification from "./Notifications.js"; + +export interface FileManagerOptions { + type: string; + onArchive?: () => void; +} + +export interface FileManagerResult { + viewFile: (file: FileUploadModel, associating_id: string) => void; + archiveFile: (file: FileUploadModel, associating_id: string) => void; + Dialogues: React.ReactNode; +} + +export default function useFileManager( + options: FileManagerOptions, +): FileManagerResult { + const { type: fileType, onArchive } = options; + + const [file_state, setFileState] = useState({ + open: false, + isImage: false, + name: "", + extension: "", + zoom: 4, + isZoomInDisabled: false, + isZoomOutDisabled: false, + rotation: 0, + }); + const [fileUrl, setFileUrl] = useState(""); + const [downloadURL, setDownloadURL] = useState(""); + const [archiveDialogueOpen, setArchiveDialogueOpen] = useState< + (FileUploadModel & { associating_id: string }) | null + >(null); + const [archiveReason, setArchiveReason] = useState(""); + const [archiveReasonError, setArchiveReasonError] = useState(""); + const [archiving, setArchiving] = useState(false); + + const getExtension = (url: string) => { + const div1 = url.split("?")[0].split("."); + const ext: string = div1[div1.length - 1].toLowerCase(); + return ext; + }; + + const viewFile = async (file: FileUploadModel, associating_id: string) => { + setFileUrl(""); + setFileState({ ...file_state, open: true }); + const { data } = await request(routes.retrieveUpload, { + query: { + file_type: fileType, + associating_id, + }, + pathParams: { id: file.id || "" }, + }); + + if (!data) return; + + const signedUrl = data.read_signed_url as string; + const extension = getExtension(signedUrl); + + const downloadFileUrl = (url: string) => { + fetch(url) + .then((res) => res.blob()) + .then((blob) => { + setDownloadURL(URL.createObjectURL(blob)); + }); + }; + + setFileState({ + ...file_state, + open: true, + name: data.name as string, + extension, + isImage: ExtImage.includes(extension), + }); + downloadFileUrl(signedUrl); + setFileUrl(signedUrl); + }; + + const validateArchiveReason = (name: any) => { + if (name.trim() === "") { + setArchiveReasonError("Please enter a valid reason!"); + return false; + } else { + setArchiveReasonError(""); + return true; + } + }; + + const handleFileArchive = async () => { + if (!validateArchiveReason(archiveReason)) { + setArchiving(false); + return; + } + + const { res } = await request(routes.editUpload, { + body: { is_archived: true, archive_reason: archiveReason }, + pathParams: { + id: archiveDialogueOpen?.id || "", + fileType, + associatingId: archiveDialogueOpen?.associating_id || "", + }, + }); + + if (res?.ok) { + Notification.Success({ msg: "File archived successfully" }); + } + + setArchiveDialogueOpen(null); + setArchiving(false); + onArchive && onArchive(); + return res; + }; + + const archiveFile = (file: FileUploadModel, associating_id: string) => { + setArchiveDialogueOpen({ ...file, associating_id }); + }; + + const handleFilePreviewClose = () => { + setDownloadURL(""); + setFileState({ + ...file_state, + open: false, + zoom: 4, + isZoomInDisabled: false, + isZoomOutDisabled: false, + }); + }; + + const Dialogues = ( + <> + + +
+ +
+
+

Archive File

+ This action is irreversible. Once a file is archived it cannot be + unarchived. +
+
+ } + onClose={() => setArchiveDialogueOpen(null)} + > +
{ + event.preventDefault(); + handleFileArchive(); + }} + className="mx-2 my-4 flex w-full flex-col" + > +
+ + State the reason for archiving{" "} + {archiveDialogueOpen?.name} file? + + } + rows={6} + required + placeholder="Type the reason..." + value={archiveReason} + onChange={(e) => setArchiveReason(e.value)} + error={archiveReasonError} + /> +
+
+ setArchiveDialogueOpen(null)} /> + +
+
+ + +
+ +
+
+

File Details

+ This file is archived. Once a file is archived it cannot be + unarchived. +
+
+ } + onClose={() => setArchiveDialogueOpen(null)} + > +
+
+
+ {archiveDialogueOpen?.name} file is + archived. +
+
+ Reason: {archiveDialogueOpen?.archive_reason} +
+
+ Archived by: {archiveDialogueOpen?.archived_by?.username} +
+
+ Time of Archive: + {formatDateTime(archiveDialogueOpen?.archived_datetime)} +
+
+
+ setArchiveDialogueOpen(null)} /> +
+
+ + + ); + + return { + viewFile, + archiveFile, + Dialogues, + }; +} diff --git a/src/Utils/useFileUpload.tsx b/src/Utils/useFileUpload.tsx new file mode 100644 index 00000000000..f6d0b837d0b --- /dev/null +++ b/src/Utils/useFileUpload.tsx @@ -0,0 +1,448 @@ +import { ChangeEvent, useCallback, useRef, useState } from "react"; +import { + CreateFileResponse, + FileCategory, + FileUploadModel, +} from "../Components/Patient/models"; +import DialogModal from "../Components/Common/Dialog"; +import CareIcon, { IconName } from "../CAREUI/icons/CareIcon"; +import Webcam from "react-webcam"; +import ButtonV2, { Submit } from "../Components/Common/components/ButtonV2"; +import { t } from "i18next"; +import useWindowDimensions from "../Common/hooks/useWindowDimensions"; +import { classNames } from "./utils"; +import request from "./request/request"; +import routes from "../Redux/api"; +import uploadFile from "./request/uploadFile"; +import * as Notification from "./Notifications.js"; +import imageCompression from "browser-image-compression"; + +export type FileUploadOptions = { + type: string; + category?: FileCategory; + onUpload?: (file: FileUploadModel) => void; +} & ( + | { + allowAllExtensions?: boolean; + } + | { + allowedExtensions?: string[]; + } +); + +export type FileUploadButtonProps = { + icon?: IconName; + content?: string; + className?: string; +}; + +export type FileUploadReturn = { + progress: null | number; + error: null | string; + handleCameraCapture: () => void; + handleAudioCapture: () => void; + handleFileUpload: (associating_id: string) => Promise; + Dialogues: () => JSX.Element; + UploadButton: (_: FileUploadButtonProps) => JSX.Element; + fileName: string; + file: File | null; + setFileName: (name: string) => void; + clearFile: () => void; +}; + +const videoConstraints = { + width: { ideal: 4096 }, + height: { ideal: 2160 }, + facingMode: "user", +}; + +// Array of image extensions +const ExtImage: string[] = [ + "jpeg", + "jpg", + "png", + "gif", + "svg", + "bmp", + "webp", + "jfif", +]; + +export default function useFileUpload( + options: FileUploadOptions, +): FileUploadReturn { + const { type, onUpload, category = "UNSPECIFIED" } = options; + + const [uploadFileName, setUploadFileName] = useState(""); + const [error, setError] = useState(null); + const [progress, setProgress] = useState(null); + const [cameraModalOpen, setCameraModalOpen] = useState(false); + const [cameraFacingFront, setCameraFacingFront] = useState(true); + const webRef = useRef(null); + const [previewImage, setPreviewImage] = useState(null); + const [file, setFile] = useState(null); + + const handleSwitchCamera = useCallback(() => { + setCameraFacingFront((prevState) => !prevState); + }, []); + + const { width } = useWindowDimensions(); + const LaptopScreenBreakpoint = 640; + const isLaptopScreen = width >= LaptopScreenBreakpoint ? true : false; + + const captureImage = () => { + setPreviewImage(webRef.current.getScreenshot()); + const canvas = webRef.current.getCanvas(); + canvas?.toBlob((blob: Blob) => { + const extension = blob.type.split("/").pop(); + const myFile = new File([blob], `capture.${extension}`, { + type: blob.type, + }); + setUploadFileName(uploadFileName || "capture"); + setFile(myFile); + }); + }; + + 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 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); + }; + + const validateFileUpload = () => { + const filenameLength = uploadFileName.trim().length; + const f = file; + if (f === undefined || f === null) { + setError("Please choose a file to upload"); + return false; + } + if (filenameLength === 0) { + setError("Please give a name !!"); + return false; + } + if (f.size > 10e7) { + setError("Maximum size of files is 100 MB"); + return false; + } + return true; + }; + const markUploadComplete = ( + data: CreateFileResponse, + associatingId: string, + ) => { + return request(routes.editUpload, { + body: { upload_completed: true }, + pathParams: { + id: data.id, + fileType: type, + associatingId, + }, + }); + }; + + const uploadfile = async (data: CreateFileResponse) => { + const url = data.signed_url; + const internal_name = data.internal_name; + const f = file; + if (!f) return; + const newFile = new File([f], `${internal_name}`); + console.log("filetype: ", newFile.type); + return new Promise((resolve, reject) => { + uploadFile( + url, + newFile, + "PUT", + { "Content-Type": file?.type }, + (xhr: XMLHttpRequest) => { + if (xhr.status >= 200 && xhr.status < 300) { + setProgress(null); + setFile(null); + setUploadFileName(""); + Notification.Success({ + msg: "File Uploaded Successfully", + }); + setError(null); + onUpload && onUpload(data); + resolve(); + } else { + Notification.Error({ + msg: "Error Uploading File: " + xhr.statusText, + }); + setProgress(null); + reject(); + } + }, + setProgress as any, + () => { + Notification.Error({ + msg: "Error Uploading File: Network Error", + }); + setProgress(null); + reject(); + }, + ); + }); + }; + + 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 ?? "", + }, + }); + + if (data) { + await uploadfile(data); + await markUploadComplete(data, associating_id); + } + }; + + const cameraFacingMode = cameraFacingFront + ? "user" + : { exact: "environment" }; + + const Dialogues = () => ( + +
+ +
+
+

Camera

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

Add New Record

record.type === 2 && + newConsent.type === 2 && record.patient_code_status !== newConsent.patient_code_status && record.deleted !== true, @@ -280,25 +283,27 @@ export default function PatientConsentRecords(props: {
- {tabConsents?.length === 0 || - (!filesFound && ( +
+ {loading ? ( +
+ ) : tabConsents?.length === 0 || !filesFound ? (
No records found
- ))} -
- {tabConsents?.map((record, index) => ( - setShowDeleteConsent(record.id)} - refreshTrigger={consultation} - showArchive={showArchived} - onFilesFound={() => setFilesFound(true)} - /> - ))} + ) : null} + {!loading && + tabConsents?.map((record, index) => ( + setShowDeleteConsent(record.id)} + refreshTrigger={consultation} + showArchive={showArchived} + onFilesFound={() => setFilesFound(true)} + /> + ))}
diff --git a/src/Utils/useFileManager.tsx b/src/Utils/useFileManager.tsx index aa57e1918a8..133b4d2533d 100644 --- a/src/Utils/useFileManager.tsx +++ b/src/Utils/useFileManager.tsx @@ -18,7 +18,11 @@ export interface FileManagerOptions { export interface FileManagerResult { viewFile: (file: FileUploadModel, associating_id: string) => void; - archiveFile: (file: FileUploadModel, associating_id: string) => void; + archiveFile: ( + file: FileUploadModel, + associating_id: string, + skipPrompt?: { reason: string }, + ) => void; Dialogues: React.ReactNode; } @@ -97,7 +101,7 @@ export default function useFileManager( } }; - const handleFileArchive = async () => { + const handleFileArchive = async (archiveFile: typeof archiveDialogueOpen) => { if (!validateArchiveReason(archiveReason)) { setArchiving(false); return; @@ -106,9 +110,9 @@ export default function useFileManager( const { res } = await request(routes.editUpload, { body: { is_archived: true, archive_reason: archiveReason }, pathParams: { - id: archiveDialogueOpen?.id || "", + id: archiveFile?.id || "", fileType, - associatingId: archiveDialogueOpen?.associating_id || "", + associatingId: archiveFile?.associating_id || "", }, }); @@ -118,11 +122,25 @@ export default function useFileManager( setArchiveDialogueOpen(null); setArchiving(false); + setArchiveReason(""); onArchive && onArchive(); return res; }; - const archiveFile = (file: FileUploadModel, associating_id: string) => { + const archiveFile = ( + file: FileUploadModel, + associating_id: string, + skipPrompt?: { reason: string }, + ) => { + if (skipPrompt) { + setArchiving(true); + setArchiveReason(skipPrompt.reason); + handleFileArchive({ + ...file, + associating_id, + }); + return; + } setArchiveDialogueOpen({ ...file, associating_id }); }; @@ -174,7 +192,7 @@ export default function useFileManager(
{ event.preventDefault(); - handleFileArchive(); + handleFileArchive(archiveDialogueOpen); }} className="mx-2 my-4 flex w-full flex-col" > diff --git a/src/style/index.css b/src/style/index.css index fe2c9c8edb1..b05417bf912 100644 --- a/src/style/index.css +++ b/src/style/index.css @@ -429,6 +429,12 @@ button:disabled, } } +.skeleton-animate-alpha { + animation: skeletonShimmer 3s infinite linear; + background: linear-gradient(to right, rgba(0, 0, 0, 0.1) 10%, rgba(0, 0, 0, 0.05) 40%, rgba(0, 0, 0, 0.1) 70%); + background-size: 1000px 100%; +} + @media print { body * {