diff --git a/cypress/e2e/assets_spec/asset_homepage.cy.ts b/cypress/e2e/assets_spec/asset_homepage.cy.ts index 388f19424a4..5710df08e83 100644 --- a/cypress/e2e/assets_spec/asset_homepage.cy.ts +++ b/cypress/e2e/assets_spec/asset_homepage.cy.ts @@ -63,7 +63,6 @@ describe("Asset Tab", () => { it("Filter Asset", () => { assetFilters.filterAssets( "Dummy Facility 40", - "INTERNAL", "ACTIVE", "ONVIF Camera", "Camera Loc" @@ -71,7 +70,6 @@ describe("Asset Tab", () => { assetFilters.clickadvancefilter(); assetFilters.clickslideoverbackbutton(); // to verify the back button doesn't clear applied filters assetFilters.assertFacilityText("Dummy Facility 40"); - assetFilters.assertAssetTypeText("INTERNAL"); assetFilters.assertAssetClassText("ONVIF"); assetFilters.assertStatusText("ACTIVE"); assetFilters.assertLocationText("Camera Loc"); diff --git a/cypress/e2e/assets_spec/assets_creation.cy.ts b/cypress/e2e/assets_spec/assets_creation.cy.ts index 63f8bee50c8..f8edc2bb172 100644 --- a/cypress/e2e/assets_spec/assets_creation.cy.ts +++ b/cypress/e2e/assets_spec/assets_creation.cy.ts @@ -29,7 +29,6 @@ describe("Asset", () => { assetPage.clickCreateAsset(); assetPage.verifyEmptyAssetNameError(); - assetPage.verifyEmptyAssetTypeError(); assetPage.verifyEmptyLocationError(); assetPage.verifyEmptyStatusError(); assetPage.verifyEmptyPhoneError(); @@ -41,7 +40,6 @@ describe("Asset", () => { assetPage.createAsset(); assetPage.selectFacility("Dummy Facility 40"); assetPage.selectLocation("Camera Loc"); - assetPage.selectAssetType("Internal"); assetPage.selectAssetClass("ONVIF Camera"); const qr_id_1 = uuidv4(); @@ -68,7 +66,6 @@ describe("Asset", () => { const qr_id_2 = uuidv4(); assetPage.selectLocation("Camera Loc"); - assetPage.selectAssetType("Internal"); assetPage.selectAssetClass("ONVIF Camera"); assetPage.enterAssetDetails( "New Test Asset 2", @@ -141,7 +138,6 @@ describe("Asset", () => { assetPage.createAsset(); assetPage.selectFacility("Dummy Facility 40"); assetPage.selectLocation("Camera Loc"); - assetPage.selectAssetType("Internal"); assetPage.selectAssetClass("HL7 Vitals Monitor"); const qr_id_1 = uuidv4(); diff --git a/cypress/e2e/sample_test_spec/filter.cy.ts b/cypress/e2e/sample_test_spec/filter.cy.ts index a015d1ba7c5..df934c641bb 100644 --- a/cypress/e2e/sample_test_spec/filter.cy.ts +++ b/cypress/e2e/sample_test_spec/filter.cy.ts @@ -20,13 +20,6 @@ describe("Sample Filter", () => { .click(); }); - it("Filter by Asset Type", () => { - cy.get("#result").click(); - cy.get("li[role='option']") - .contains(/^POSITIVE$/) - .click(); - }); - it("Filter by sample type", () => { cy.get("#sample_type").click(); cy.get("li[role='option']") diff --git a/cypress/pageobject/Asset/AssetCreation.ts b/cypress/pageobject/Asset/AssetCreation.ts index 6932b5ed15e..8f611e97d92 100644 --- a/cypress/pageobject/Asset/AssetCreation.ts +++ b/cypress/pageobject/Asset/AssetCreation.ts @@ -24,14 +24,6 @@ export class AssetPage { }); } - selectAssetType(assetType: string) { - cy.get("[data-testid=asset-type-input] button") - .click() - .then(() => { - cy.get("[role='option']").contains(assetType).click(); - }); - } - selectAssetClass(assetClass: string) { cy.get("[data-testid=asset-class-input] button") .click() @@ -205,13 +197,6 @@ export class AssetPage { ); } - verifyEmptyAssetTypeError() { - cy.get("[data-testid=asset-type-input] span").should( - "contain", - "Select an asset type" - ); - } - verifyEmptyStatusError() { cy.get("[data-testid=asset-working-status-input] span").should( "contain", diff --git a/cypress/pageobject/Asset/AssetFilters.ts b/cypress/pageobject/Asset/AssetFilters.ts index 5ded59f4f63..33363f2d161 100644 --- a/cypress/pageobject/Asset/AssetFilters.ts +++ b/cypress/pageobject/Asset/AssetFilters.ts @@ -1,7 +1,6 @@ export class AssetFilters { filterAssets( facilityName: string, - assetType: string, assetStatus: string, assetClass: string, assetLocation: string @@ -13,11 +12,6 @@ export class AssetFilters { .then(() => { cy.get("[role='option']").contains(facilityName).click(); }); - cy.get("#asset-type") - .click() - .then(() => { - cy.get("[role='option']").contains(assetType).click(); - }); cy.get("#asset-status") .click() .then(() => { @@ -65,9 +59,6 @@ export class AssetFilters { assertFacilityText(text) { cy.get("[data-testid=Facility]").should("contain", text); } - assertAssetTypeText(text) { - cy.get("[data-testid='Asset Type']").should("contain", text); - } assertAssetClassText(text) { cy.get("[data-testid='Asset Class']").should("contain", text); } diff --git a/src/Components/Assets/AssetFilter.tsx b/src/Components/Assets/AssetFilter.tsx index 3edde4ab0cd..1fca1475269 100644 --- a/src/Components/Assets/AssetFilter.tsx +++ b/src/Components/Assets/AssetFilter.tsx @@ -21,9 +21,6 @@ const getDate = (value: any) => function AssetFilter(props: any) { const { filter, onChange, closeFilter, removeFilters } = props; const [facility, setFacility] = useState(null); - const [asset_type, setAssetType] = useState( - filter.asset_type ? filter.asset_type : "" - ); const [asset_status, setAssetStatus] = useState(filter.status || ""); const [asset_class, setAssetClass] = useState( filter.asset_class || "" @@ -61,7 +58,6 @@ function AssetFilter(props: any) { const applyFilter = () => { const data = { facility: facilityId, - asset_type: asset_type ?? "", asset_class: asset_class ?? "", status: asset_status ?? "", location: locationId ?? "", @@ -125,18 +121,6 @@ function AssetFilter(props: any) { )} - o} - optionValue={(o) => o} - value={asset_type} - onChange={({ value }) => setAssetType(value)} - /> - { {asset?.description}
- {asset?.asset_type === "INTERNAL" ? ( - - ) : ( - - )} {asset?.status === "ACTIVE" ? ( ) : ( @@ -503,7 +498,12 @@ const AssetManage = (props: AssetManageProps) => {
{asset?.id && asset?.asset_class && - asset?.asset_class != AssetClass.NONE && } + asset?.asset_class != AssetClass.NONE && ( + + )}
Service History
{ const [isScannerActive, setIsScannerActive] = useState(false); const [totalCount, setTotalCount] = useState(0); const [facility, setFacility] = useState(); - const [asset_type, setAssetType] = useState(); const [status, setStatus] = useState(); const [asset_class, setAssetClass] = useState(); const [importAssetModalOpen, setImportAssetModalOpen] = useState(false); @@ -60,7 +59,6 @@ const AssetsList = () => { offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, search_text: qParams.search || "", facility: qParams.facility || "", - asset_type: qParams.asset_type || "", asset_class: qParams.asset_class || "", location: qParams.facility ? qParams.location || "" : "", status: qParams.status || "", @@ -91,10 +89,6 @@ const AssetsList = () => { prefetch: !!qParams.facility, }); - useEffect(() => { - setAssetType(qParams.asset_type); - }, [qParams.asset_type]); - useEffect(() => { setStatus(qParams.status); }, [qParams.status]); @@ -387,7 +381,6 @@ const AssetsList = () => { qParams.facility && facilityObject?.name ), badge("Name/Serial No./QR ID", "search"), - value("Asset Type", "asset_type", asset_type ?? ""), value("Asset Class", "asset_class", asset_class ?? ""), value("Status", "status", status?.replace(/_/g, " ") ?? ""), value( diff --git a/src/Components/Common/Uptime.tsx b/src/Components/Common/Uptime.tsx index e3c7dd7a439..e7272e2e1f4 100644 --- a/src/Components/Common/Uptime.tsx +++ b/src/Components/Common/Uptime.tsx @@ -1,10 +1,10 @@ import { Popover } from "@headlessui/react"; import { useEffect, useRef, useState } from "react"; -import { AssetStatus, AssetUptimeRecord } from "../Assets/AssetTypes"; +import { AssetStatus, AvailabilityRecord } from "../Assets/AssetTypes"; import { classNames } from "../../Utils/utils"; import dayjs from "../../Utils/dayjs"; import useQuery from "../../Utils/request/useQuery.js"; -import routes from "../../Redux/api.js"; +import { PaginatedResponse, QueryRoute } from "../../Utils/request/types"; const STATUS_COLORS = { Operational: "bg-green-500", @@ -37,7 +37,7 @@ function UptimeInfo({ records, date, }: { - records: AssetUptimeRecord[]; + records: AvailabilityRecord[]; date: string; }) { const incidents = @@ -66,7 +66,7 @@ function UptimeInfo({ let endTimestamp; let ongoing = false; - if (prevIncident?.id) { + if (prevIncident?.linked_id) { endTimestamp = dayjs(prevIncident.timestamp); } else if (dayjs(incident.timestamp).isSame(now, "day")) { endTimestamp = dayjs(); @@ -141,7 +141,7 @@ function UptimeInfoPopover({ date, numDays, }: { - records: AssetUptimeRecord[]; + records: AvailabilityRecord[]; day: number; date: string; numDays: number; @@ -165,12 +165,17 @@ function UptimeInfoPopover({ ); } -export default function Uptime(props: { assetId: string }) { +export default function Uptime( + props: Readonly<{ + route: QueryRoute>; + params?: Record; + }> +) { const [summary, setSummary] = useState<{ - [key: number]: AssetUptimeRecord[]; + [key: number]: AvailabilityRecord[]; }>([]); - const { data, loading } = useQuery(routes.listAssetAvailability, { - query: { external_id: props.assetId }, + const { data, loading } = useQuery(props.route, { + pathParams: props.params, onResponse: ({ data }) => setUptimeRecord(data?.results.reverse() ?? []), }); const availabilityData = data?.results ?? []; @@ -186,8 +191,8 @@ export default function Uptime(props: { assetId: string }) { setNumDays(Math.min(newNumDays, 100)); }; - const setUptimeRecord = (records: AssetUptimeRecord[]): void => { - const recordsByDayBefore: { [key: number]: AssetUptimeRecord[] } = {}; + const setUptimeRecord = (records: AvailabilityRecord[]): void => { + const recordsByDayBefore: { [key: number]: AvailabilityRecord[] } = {}; records.forEach((record) => { const timestamp = dayjs(record.timestamp).startOf("day"); @@ -207,10 +212,8 @@ export default function Uptime(props: { assetId: string }) { recordsByDayBefore[i] = []; if (statusToCarryOver) { recordsByDayBefore[i].push({ - id: "", - asset: { id: "", name: "" }, - created_date: "", - modified_date: "", + linked_id: "", + linked_model: "", status: statusToCarryOver, timestamp: dayjs() .subtract(i, "days") @@ -225,10 +228,8 @@ export default function Uptime(props: { assetId: string }) { ).length === 0 ) { recordsByDayBefore[i].unshift({ - id: "", - asset: { id: "", name: "" }, - created_date: "", - modified_date: "", + linked_id: "", + linked_model: "", status: statusToCarryOver, timestamp: dayjs() .subtract(i, "days") @@ -284,7 +285,7 @@ export default function Uptime(props: { assetId: string }) { const statusColors: (typeof STATUS_COLORS)[keyof typeof STATUS_COLORS][] = []; let dayUptimeScore = 0; - const recordsInPeriodCache: { [key: number]: AssetUptimeRecord[] } = {}; + const recordsInPeriodCache: { [key: number]: AvailabilityRecord[] } = {}; for (let i = 0; i < 3; i++) { const start = i * 8; const end = (i + 1) * 8; diff --git a/src/Components/Facility/AssetCreate.tsx b/src/Components/Facility/AssetCreate.tsx index 784174cd2f7..0c422d1f646 100644 --- a/src/Components/Facility/AssetCreate.tsx +++ b/src/Components/Facility/AssetCreate.tsx @@ -40,7 +40,6 @@ const Loading = lazy(() => import("../Common/Loading")); const formErrorKeys = [ "name", - "asset_type", "asset_class", "description", "is_working", @@ -103,12 +102,10 @@ const AssetCreate = (props: AssetProps) => { const { goBack } = useAppHistory(); const { facilityId, assetId } = props; - let assetTypeInitial: AssetType; let assetClassInitial: AssetClass; const [state, dispatch] = useReducer(asset_create_reducer, initialState); const [name, setName] = useState(""); - const [asset_type, setAssetType] = useState(); const [asset_class, setAssetClass] = useState(); const [not_working_reason, setNotWorkingReason] = useState(""); const [description, setDescription] = useState(""); @@ -177,7 +174,6 @@ const AssetCreate = (props: AssetProps) => { setName(asset.name); setDescription(asset.description); setLocation(asset.location_object.id!); - setAssetType(asset.asset_type); setAssetClass(asset.asset_class); setIsWorking(String(asset.is_working)); setNotWorkingReason(asset.not_working_reason); @@ -219,12 +215,6 @@ const AssetCreate = (props: AssetProps) => { invalidForm = true; } return; - case "asset_type": - if (!asset_type || asset_type == "NONE") { - errors[field] = "Select an asset type"; - invalidForm = true; - } - return; case "support_phone": { if (!support_phone) { errors[field] = "Field is required"; @@ -282,7 +272,6 @@ const AssetCreate = (props: AssetProps) => { setName(""); setDescription(""); setLocation(""); - setAssetType(assetTypeInitial); setAssetClass(assetClassInitial); setIsWorking(undefined); setNotWorkingReason(""); @@ -307,7 +296,7 @@ const AssetCreate = (props: AssetProps) => { setIsLoading(true); const data: any = { name: name, - asset_type: asset_type, + asset_type: AssetType.INTERNAL, asset_class: asset_class || "", description: description, is_working: is_working, @@ -547,68 +536,34 @@ const AssetCreate = (props: AssetProps) => { errors={state.errors.location} />
- {/* Asset Type */} -
-
- title} - optionDescription={({ description }) => description} - optionValue={({ value }) => value} - onChange={({ value }) => setAssetType(value)} - error={state.errors.asset_type} - /> -
- {/* Asset Class */} -
- title} - optionValue={({ value }) => value} - onChange={({ value }) => setAssetClass(value)} - error={state.errors.asset_class} - /> -
+ {/* Asset Class */} +
+ title} + optionValue={({ value }) => value} + onChange={({ value }) => setAssetClass(value)} + error={state.errors.asset_class} + />
{/* Description */}
{
-
-
- + {!!consultationData.diagnoses?.length && ( +
+
+ +
-
+ )}
diff --git a/src/Components/Facility/ConsultationDoctorNotes/index.tsx b/src/Components/Facility/ConsultationDoctorNotes/index.tsx index 8e39ee04e4e..c17ee3fb2db 100644 --- a/src/Components/Facility/ConsultationDoctorNotes/index.tsx +++ b/src/Components/Facility/ConsultationDoctorNotes/index.tsx @@ -1,7 +1,6 @@ import { useState } from "react"; import * as Notification from "../../../Utils/Notifications.js"; import Page from "../../Common/components/Page"; -import TextFormField from "../../Form/FormFields/TextFormField"; import ButtonV2 from "../../Common/components/ButtonV2"; import CareIcon from "../../../CAREUI/icons/CareIcon"; import { NonReadOnlyUsers } from "../../../Utils/AuthorizeFor"; @@ -13,6 +12,7 @@ import request from "../../../Utils/request/request.js"; import useQuery from "../../../Utils/request/useQuery.js"; import useKeyboardShortcut from "use-keyboard-shortcut"; import { isAppleDevice } from "../../../Utils/utils.js"; +import AutoExpandingTextInputFormField from "../../Form/FormFields/AutoExpandingTextInputFormField.js"; interface ConsultationDoctorNotesProps { patientId: string; @@ -120,12 +120,14 @@ const ConsultationDoctorNotes = (props: ConsultationDoctorNotesProps) => { />
- setNoteField(e.value)} className="grow" - type="text" errorClassName="hidden" placeholder="Type your Note" disabled={!patientActive} diff --git a/src/Components/Facility/Investigations/Reports/ReportTable.tsx b/src/Components/Facility/Investigations/Reports/ReportTable.tsx index 1f20ec94180..c15eec4e33b 100644 --- a/src/Components/Facility/Investigations/Reports/ReportTable.tsx +++ b/src/Components/Facility/Investigations/Reports/ReportTable.tsx @@ -124,9 +124,17 @@ const ReportTable: FC = ({ - {formatDateTime(session.session_created_date)} +
+ {formatDateTime(session.session_created_date)} + + {session.facility_name} + +
))} { const sessions = _.chain(data) - .map((value) => value.session_object) + .map((value) => { + return { + ...value.session_object, + facility_name: value.consultation_object?.facility_name, + facility_id: value.consultation_object?.facility, + }; + }) .uniqBy("session_external_id") .orderBy("session_created_date", "desc") .value(); @@ -28,7 +34,6 @@ export const transformData = _.memoize((data: InvestigationResponse) => { } }); const { - consultation, group, group_object, id, @@ -40,7 +45,6 @@ export const transformData = _.memoize((data: InvestigationResponse) => { } = value[0]; return { - consultation, group, group_object, id, diff --git a/src/Components/Facility/PatientNotesSlideover.tsx b/src/Components/Facility/PatientNotesSlideover.tsx index 5ecf30cfcdf..2e231576eb2 100644 --- a/src/Components/Facility/PatientNotesSlideover.tsx +++ b/src/Components/Facility/PatientNotesSlideover.tsx @@ -3,7 +3,6 @@ import * as Notification from "../../Utils/Notifications.js"; import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; import CareIcon from "../../CAREUI/icons/CareIcon"; import { classNames, isAppleDevice } from "../../Utils/utils"; -import TextFormField from "../Form/FormFields/TextFormField"; import ButtonV2 from "../Common/components/ButtonV2"; import { make as Link } from "../Common/components/Link.bs"; import { useMessageListener } from "../../Common/hooks/useMessageListener"; @@ -12,6 +11,7 @@ import request from "../../Utils/request/request"; import routes from "../../Redux/api"; import { PatientNoteStateType } from "./models"; import useKeyboardShortcut from "use-keyboard-shortcut"; +import AutoExpandingTextInputFormField from "../Form/FormFields/AutoExpandingTextInputFormField.js"; interface PatientNotesProps { patientId: string; @@ -169,13 +169,14 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { setReload={setReload} />
- setNoteField(e.value)} className="grow" - type="text" errorClassName="hidden" placeholder="Type your Note" disabled={!patientActive} diff --git a/src/Components/Form/FormFields/AutoExpandingTextInputFormField.tsx b/src/Components/Form/FormFields/AutoExpandingTextInputFormField.tsx new file mode 100644 index 00000000000..22707f133bc --- /dev/null +++ b/src/Components/Form/FormFields/AutoExpandingTextInputFormField.tsx @@ -0,0 +1,30 @@ +import React, { useEffect, useRef } from "react"; +import TextAreaFormField, { TextAreaFormFieldProps } from "./TextAreaFormField"; + +type AutoExpandingTextInputFormFieldProps = TextAreaFormFieldProps & { + maxHeight?: number; +}; + +const AutoExpandingTextInputFormField = ( + props: AutoExpandingTextInputFormFieldProps +) => { + const myref = useRef(null); + useEffect(() => { + if (myref.current == null) return; + const text = myref.current.textContent?.split("\n"); + const len = text?.length || 1; + // 46 is height of the textarea when there is only 1 line + // getting line height from window + const lineHeight = + window.getComputedStyle(myref.current).lineHeight.slice(0, -2) || "20"; + // added 26 for padding (20+26 = 46) + const height = + Math.min(len * parseInt(lineHeight), (props.maxHeight || 160) - 26) + 26; + // 160 is the max height of the textarea if not specified + myref.current.style.cssText = "height:" + height + "px"; + }); + + return ; +}; + +export default AutoExpandingTextInputFormField; diff --git a/src/Components/Form/FormFields/TextAreaFormField.tsx b/src/Components/Form/FormFields/TextAreaFormField.tsx index 23a7d025938..5c23bfec764 100644 --- a/src/Components/Form/FormFields/TextAreaFormField.tsx +++ b/src/Components/Form/FormFields/TextAreaFormField.tsx @@ -1,33 +1,44 @@ +import { forwardRef } from "react"; import FormField from "./FormField"; import { FormFieldBaseProps, useFormFieldPropsResolver } from "./Utils"; -type TextAreaFormFieldProps = FormFieldBaseProps & { +export type TextAreaFormFieldProps = FormFieldBaseProps & { placeholder?: string; value?: string | number; rows?: number; // prefixIcon?: React.ReactNode; // suffixIcon?: React.ReactNode; + onFocus?: (event: React.FocusEvent) => void; + onBlur?: (event: React.FocusEvent) => void; }; -const TextAreaFormField = ({ rows = 3, ...props }: TextAreaFormFieldProps) => { - const field = useFormFieldPropsResolver(props as any); - return ( - -