From a152c423fa804a591a7d045a7c4aa52e4865cb9b Mon Sep 17 00:00:00 2001 From: Ojwang' Antony Date: Fri, 19 Jan 2024 12:52:53 +0300 Subject: [PATCH 1/2] initial module setup. relax login location requirement for ward locations (#1) --- .../active-patients-table.component.tsx | 114 ++----- .../active-patients/active-visits.resource.ts | 171 +++++++++++ .../patient-queues.resource.ts | 51 ++++ src/bed-admission/active-patients/styles.scss | 2 +- .../active-admissions.resource.ts | 121 ++++++++ .../admitted-patients-table.component.tsx | 280 +++++++++++++++++ .../admitted-patients.component.tsx | 8 +- .../location-combo-box.component.tsx | 13 +- .../admitted-patients/styles.scss | 284 ++++++++++++++++++ .../bed-admission-tabs.component.tsx | 9 +- .../bed-layout/bed-layout-list.component.tsx | 12 +- src/config-schema.ts | 13 +- src/types.ts | 9 + .../allocate-bed-workspace.component.tsx | 52 ++-- src/workspace/overlay.scss | 5 + 15 files changed, 1008 insertions(+), 136 deletions(-) create mode 100644 src/bed-admission/active-patients/active-visits.resource.ts create mode 100644 src/bed-admission/admitted-patients/active-admissions.resource.ts create mode 100644 src/bed-admission/admitted-patients/admitted-patients-table.component.tsx create mode 100644 src/bed-admission/admitted-patients/styles.scss diff --git a/src/bed-admission/active-patients/active-patients-table.component.tsx b/src/bed-admission/active-patients/active-patients-table.component.tsx index 3142338..3c48b0e 100644 --- a/src/bed-admission/active-patients/active-patients-table.component.tsx +++ b/src/bed-admission/active-patients/active-patients-table.component.tsx @@ -22,6 +22,7 @@ import { import { isDesktop, + useConfig, useLayoutType, usePagination, useSession, @@ -37,6 +38,7 @@ import { } from "../helpers/functions"; import styles from "./styles.scss"; import { usePatientQueuesList } from "./patient-queues.resource"; +import { useActiveVisits } from "./active-visits.resource"; import EmptyState from "../../empty-state/empty-state.component"; import AssignBedWorkSpace from "../../workspace/allocate-bed-workspace.component"; import AdmissionActionButton from "./admission-action-button.component"; @@ -63,10 +65,9 @@ const ActivePatientsTable: React.FC = ({ useState(); const layout = useLayoutType(); - const { patientQueueEntries, isLoading } = usePatientQueuesList( - session?.sessionLocation?.uuid, - status - ); + + const { patientQueueEntries, isLoading } = useActiveVisits(); + const { restrictWardAdministrationToLoginLocation } = useConfig(); const handleBedAssigmentModal = useCallback( (entry) => { @@ -113,37 +114,37 @@ const ActivePatientsTable: React.FC = ({ () => [ { id: 0, - header: t("visitNumber", "Visit Number"), - key: "visitNumber", + header: t("name", "Name"), + key: "name", }, { id: 1, - header: t("name", "Name"), - key: "name", + header: t("idNumber", "ID Number"), + key: "idNumber", }, { id: 2, - header: t("locationFrom", "Location From"), - key: "locationFrom", + header: t("gender", "Gender"), + key: "gender", }, { id: 3, - header: t("priority", "Priority"), - key: "priority", + header: t("age", "Age"), + key: "age", }, { id: 4, - header: t("priorityLevel", "Priority Level"), - key: "priorityLevel", + header: t("visitType", "Visit type"), + key: "visitType", }, { id: 5, - header: t("waitTime", "Wait time"), - key: "waitTime", + header: t("visitStartTime", "Visit start date/time"), + key: "visitStartTime", }, { id: 6, - header: t("actions", "Actions"), + header: t("action", "Action"), key: "actions", }, ], @@ -153,82 +154,11 @@ const ActivePatientsTable: React.FC = ({ const tableRows = useMemo(() => { return paginatedQueueEntries?.map((entry) => ({ ...entry, - visitNumber: { - content: {trimVisitNumber(entry.visitNumber)}, - }, - name: { - content: entry.name, - }, - locationFrom: { - content: entry.locationFromName, - }, - priority: { - content: ( - <> - {entry?.priorityComment ? ( - - - {entry.priority} - - - ) : ( - - {entry.priority} - - )} - - ), - }, - priorityLevel: { - content: {entry.priorityLevel}, - }, - waitTime: { - content: ( - - - {formatWaitTime(entry.waitTime, t)} - - - ), - }, actions: { content: ( -
- {renderActionButton(entry)} - {status === "completed" && ( - - )} -
+
{renderActionButton(entry)}
), }, - notes: { - content: entry.comment, - }, })); }, [paginatedQueueEntries, status, t, renderActionButton, fromPage]); @@ -308,7 +238,7 @@ const ActivePatientsTable: React.FC = ({ colSpan={headers.length + 2} > <> - {tableRows[index]?.comment ?? ""} + {/* {tableRows[index]?.comment ?? ""} */} ) : ( @@ -349,7 +279,9 @@ const ActivePatientsTable: React.FC = ({ queueStatus={status} headerTitle={t( "assignBedToPatient", - `Assign Bed to Patient ${selectedPatientDetails.name} in the ${session?.sessionLocation?.display} Ward` + restrictWardAdministrationToLoginLocation === true + ? `Assign Bed to Patient ${selectedPatientDetails.name} in the ${session?.sessionLocation?.display} Ward` + : `Assign Bed to Patient ${selectedPatientDetails.name}` )} /> )} diff --git a/src/bed-admission/active-patients/active-visits.resource.ts b/src/bed-admission/active-patients/active-visits.resource.ts new file mode 100644 index 0000000..e67bff4 --- /dev/null +++ b/src/bed-admission/active-patients/active-visits.resource.ts @@ -0,0 +1,171 @@ +import { useEffect } from "react"; +import useSWRInfinite from "swr/infinite"; +import dayjs from "dayjs"; +import isToday from "dayjs/plugin/isToday"; +import last from "lodash-es/last"; +import { + openmrsFetch, + type Visit, + useSession, + type FetchResponse, + formatDatetime, + parseDate, + useConfig, +} from "@openmrs/esm-framework"; + +dayjs.extend(isToday); + +export interface ActiveVisit { + age: string; + id: string; + idNumber: string; + gender: string; + location: string; + name: string; + patientUuid: string; + visitStartTime: string; + visitType: string; + visitUuid: string; + [identifier: string]: string; +} + +interface VisitResponse { + results: Array; + links: Array<{ rel: "prev" | "next" }>; + totalCount: number; +} + +export function useActiveVisits() { + const session = useSession(); + const config = useConfig(); + const { inpatientVisitUuid } = useConfig(); + const sessionLocation = session?.sessionLocation?.uuid; + + const customRepresentation = + "custom:(uuid,patient:(uuid,identifiers:(identifier,uuid,identifierType:(name,uuid)),person:(age,display,gender,uuid,attributes:(value,attributeType:(uuid,display))))," + + "visitType:(uuid,name,display),location:(uuid,name,display),startDatetime,stopDatetime)"; + + const getUrl = ( + pageIndex, + previousPageData: FetchResponse + ) => { + if ( + pageIndex && + !previousPageData?.data?.links?.some((link) => link.rel === "next") + ) { + return null; + } + + const url = `/ws/rest/v1/visit?v=${customRepresentation}&`; + const urlSearchParams = new URLSearchParams(); + + urlSearchParams.append("includeInactive", "false"); + urlSearchParams.append("visitType", `${inpatientVisitUuid}`); + urlSearchParams.append("totalCount", "true"); + //urlSearchParams.append("location", `${sessionLocation}`); + + if (pageIndex) { + urlSearchParams.append("startIndex", `${pageIndex * 50}`); + } + + return url + urlSearchParams.toString(); + }; + + const { + data, + error, + isLoading, + isValidating, + size: pageNumber, + setSize, + mutate, + } = useSWRInfinite, Error>( + sessionLocation ? getUrl : null, + openmrsFetch + ); + + useEffect(() => { + if ( + data && + data?.[pageNumber - 1]?.data?.links?.some((link) => link.rel === "next") + ) { + setSize((currentSize) => currentSize + 1); + } + }, [data, pageNumber]); + + const mapVisitProperties = (visit: Visit): ActiveVisit => { + // create base object + const activeVisits: ActiveVisit = { + age: visit?.patient?.person?.age, + id: visit.uuid, + idNumber: null, + gender: visit?.patient?.person?.gender, + location: visit?.location?.uuid, + name: visit?.patient?.person?.display, + patientUuid: visit?.patient?.uuid, + visitStartTime: formatDatetime(parseDate(visit?.startDatetime)), + visitType: visit?.visitType?.display, + visitUuid: visit.uuid, + }; + + // in case no configuration is given the previous behavior remains the same + if (!config?.activeVisits?.identifiers) { + activeVisits.idNumber = + visit?.patient?.identifiers[0]?.identifier ?? "--"; + } else { + // map identifiers on config + config?.activeVisits?.identifiers?.map((configIdentifier) => { + // check if in the current visit the patient has in his identifiers the current identifierType name + const visitIdentifier = visit?.patient?.identifiers.find( + (visitIdentifier) => + visitIdentifier?.identifierType?.name === + configIdentifier?.identifierName + ); + + // add the new identifier or rewrite existing one to activeVisit object + // the parameter will corresponds to the name of the key value of the configuration + // and the respective value is the visit identifier + // If there isn't a identifier we display this default text '--' + activeVisits[configIdentifier.header?.key] = + visitIdentifier?.identifier ?? "--"; + }); + } + + // map attributes on config + config?.activeVisits?.attributes?.map(({ display, header }) => { + // check if in the current visit the person has in his attributes the current display + const personAttributes = visit?.patient?.person?.attributes.find( + (personAttributes) => + personAttributes?.attributeType?.display === display + ); + + // add the new attribute or rewrite existing one to activeVisit object + // the parameter will correspond to the name of the key value of the configuration + // and the respective value is the persons value + // If there isn't a attribute we display this default text '--' + activeVisits[header?.key] = personAttributes?.value ?? "--"; + }); + + return activeVisits; + }; + + const formattedActiveVisits: Array = data + ? [].concat( + ...data?.map((res) => res?.data?.results?.map(mapVisitProperties)) + ) + : []; + + return { + patientQueueEntries: formattedActiveVisits, + isError: error, + isLoading, + isValidating, + patientQueueCount: data?.[0]?.data?.totalCount ?? 0, + mutate, + }; +} + +export const getOriginFromPathName = (pathname = "") => { + const from = pathname.split("/"); + return last(from); +}; diff --git a/src/bed-admission/active-patients/patient-queues.resource.ts b/src/bed-admission/active-patients/patient-queues.resource.ts index 70ffa36..6bdf7de 100644 --- a/src/bed-admission/active-patients/patient-queues.resource.ts +++ b/src/bed-admission/active-patients/patient-queues.resource.ts @@ -83,3 +83,54 @@ export function usePatientQueueRequest(apiUrl: string) { mutate, }; } + +export function usePatientActiveListRequest(apiUrl: string) { + const { data, error, isLoading, isValidating, mutate } = useSWR< + { data: { results: Array } }, + Error + >(apiUrl, openmrsFetch, { refreshInterval: 3000 }); + + const mapppedQueues = data?.data?.results.map((queue: PatientQueue) => { + return { + ...queue, + id: queue.uuid, + name: queue.patient?.person.display, + patientUuid: queue.patient?.uuid, + priorityComment: queue.priorityComment, + priority: + queue.priorityComment === "Urgent" ? "Priority" : queue.priorityComment, + priorityLevel: queue.priority, + waitTime: queue.dateCreated + ? `${dayjs().diff(dayjs(queue.dateCreated), "minutes")}` + : "--", + status: queue.status, + patientAge: queue.patient?.person?.age, + patientSex: queue.patient?.person?.gender === "M" ? "MALE" : "FEMALE", + patientDob: queue.patient?.person?.birthdate + ? formatDate(parseDate(queue.patient.person.birthdate), { time: false }) + : "--", + identifiers: queue.patient?.identifiers, + locationFrom: queue.locationFrom?.uuid, + locationTo: queue.locationTo?.uuid, + locationFromName: queue.locationFrom?.name, + locationToName: queue.locationTo?.name, + queueRoom: queue.locationTo?.display, + visitNumber: queue.visitNumber, + dateCreated: queue.dateCreated + ? formatDate(parseDate(queue.dateCreated), { time: false }) + : "--", + creatorUuid: queue.creator?.uuid, + creatorUsername: queue.creator?.username, + creatorDisplay: queue.creator?.display, + }; + }); + + return { + patientQueueEntries: mapppedQueues || [], + patientQueueCount: mapppedQueues?.length, + isLoading, + isError: error, + isValidating, + mutate, + }; +} diff --git a/src/bed-admission/active-patients/styles.scss b/src/bed-admission/active-patients/styles.scss index 8507700..5711826 100644 --- a/src/bed-admission/active-patients/styles.scss +++ b/src/bed-admission/active-patients/styles.scss @@ -281,4 +281,4 @@ th[colspan] td[colspan]>div:first-child { padding: 0 1rem; } -} \ No newline at end of file +} diff --git a/src/bed-admission/admitted-patients/active-admissions.resource.ts b/src/bed-admission/admitted-patients/active-admissions.resource.ts new file mode 100644 index 0000000..8001d73 --- /dev/null +++ b/src/bed-admission/admitted-patients/active-admissions.resource.ts @@ -0,0 +1,121 @@ +import { useEffect } from "react"; +import useSWRInfinite from "swr/infinite"; +import dayjs from "dayjs"; +import isToday from "dayjs/plugin/isToday"; +import last from "lodash-es/last"; +import { + openmrsFetch, + useSession, + type FetchResponse, +} from "@openmrs/esm-framework"; +import { AdmissionLocation } from "../../types"; + +dayjs.extend(isToday); + +interface VisitResponse { + results: Array; + links: Array<{ rel: "prev" | "next" }>; + totalCount: number; +} + +export interface BedPatientAssignment { + patientUuid: string; + age: number; + gender: string; + patientName: string; + ward: string; + bedNumber: string; +} +export function useActiveAdmissions() { + const session = useSession(); + const sessionLocation = session?.sessionLocation?.uuid; + + const getUrl = ( + pageIndex, + previousPageData: FetchResponse + ) => { + if ( + pageIndex && + !previousPageData?.data?.links?.some((link) => link.rel === "next") + ) { + return null; + } + + const url = `/ws/rest/v1/admissionLocation?v=full`; + const urlSearchParams = new URLSearchParams(); + + if (pageIndex) { + urlSearchParams.append("startIndex", `${pageIndex * 50}`); + } + + return url + urlSearchParams.toString(); + }; + + const { + data, + error, + isLoading, + isValidating, + size: pageNumber, + setSize, + mutate, + } = useSWRInfinite, Error>( + sessionLocation ? getUrl : null, + openmrsFetch + ); + + useEffect(() => { + if ( + data && + data?.[pageNumber - 1]?.data?.links?.some((link) => link.rel === "next") + ) { + setSize((currentSize) => currentSize + 1); + } + }, [data, pageNumber]); + + const formattedActiveVisits: any = (data: FetchResponse) => { + const result = data + ? data[0].data?.results?.map((wardEntry) => { + return wardEntry.bedLayouts; + }) + : []; + + const allBeds = []; + + result?.forEach((elemnt) => { + elemnt.forEach((b) => { + allBeds.push(b); + }); + }); + const finalFinalList = allBeds + ?.filter((b) => b.status === "OCCUPIED") + .map((bedEntry) => { + const patient: BedPatientAssignment = { + patientUuid: bedEntry.patient.uuid, + age: bedEntry.patient.person.age, + gender: bedEntry.patient.person.gender, + patientName: bedEntry.patient.person.preferredName.givenName + .concat(" ") + .concat(bedEntry.patient.person.preferredName.familyName), + ward: bedEntry.location, + bedNumber: bedEntry.bedNumber, + }; + return patient; + }); + return finalFinalList; + }; + + return { + patientQueueEntries: formattedActiveVisits(data), + isError: error, + isLoading, + isValidating, + patientQueueCount: data?.[0]?.data?.totalCount ?? 0, + mutate, + }; +} + +export const getOriginFromPathName = (pathname = "") => { + const from = pathname.split("/"); + return last(from); +}; diff --git a/src/bed-admission/admitted-patients/admitted-patients-table.component.tsx b/src/bed-admission/admitted-patients/admitted-patients-table.component.tsx new file mode 100644 index 0000000..4b58be9 --- /dev/null +++ b/src/bed-admission/admitted-patients/admitted-patients-table.component.tsx @@ -0,0 +1,280 @@ +import { + DataTable, + DataTableSkeleton, + DefinitionTooltip, + Pagination, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableHeader, + TableRow, + Tag, + Layer, + TableToolbar, + TableToolbarContent, + TableToolbarSearch, + TableExpandedRow, + TableExpandHeader, + TableExpandRow, +} from "@carbon/react"; + +import { + isDesktop, + useConfig, + useLayoutType, + usePagination, + useSession, +} from "@openmrs/esm-framework"; +import React, { useCallback, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { getOriginFromPathName } from "../helpers/functions"; +import styles from "../active-patients/styles.scss"; +import { useActiveAdmissions } from "./active-admissions.resource"; +import EmptyState from "../../empty-state/empty-state.component"; +import { patientDetailsProps } from "../types"; + +interface ActiveVisitsTableProps { + status: string; + setPatientCount?: (value: number) => void; +} + +const AdmittedPatientsTable: React.FC = ({ + status, + setPatientCount, +}) => { + const { t } = useTranslation(); + const session = useSession(); + const currentPathName: string = window.location.pathname; + const fromPage: string = getOriginFromPathName(currentPathName); + const pageSizes = [10, 20, 30, 40, 50]; + const [currentPageSize, setPageSize] = useState(10); + const [showOverlay, setShowOverlay] = useState(false); + const [selectedPatientDetails, setSelectedPatientDetails] = + useState(); + + const layout = useLayoutType(); + + const { patientQueueEntries, isLoading } = useActiveAdmissions(); + const { restrictWardAdministrationToLoginLocation } = useConfig(); + + const handleBedAssigmentModal = useCallback( + (entry) => { + setSelectedPatientDetails({ + name: entry.name, + patientUuid: entry.patientUuid, + encounter: entry.encounter, + locationUuid: session?.sessionLocation?.uuid, + locationTo: entry.locationTo, + locationFrom: entry.locationFrom, + queueUuid: entry.uuid, + }); + setShowOverlay(true); + }, + [session?.sessionLocation?.uuid] + ); + + const renderActionButton = useCallback( + (entry) => { + const buttonTexts = { + pending: "Assign Bed", + completed: "Transfer", + }; + const buttonText = buttonTexts[status] || "Un-assign"; + + // return ( + // + // ); + }, + [handleBedAssigmentModal, status] + ); + + const { + goTo, + results: paginatedQueueEntries, + currentPage, + } = usePagination(patientQueueEntries, currentPageSize); + + const tableHeaders = useMemo( + () => [ + { + id: 0, + header: t("name", "Name"), + key: "patientName", + }, + { + id: 1, + header: t("age", "Age"), + key: "age", + }, + { + id: 2, + header: t("gender", "Gender"), + key: "gender", + }, + { + id: 3, + header: t("ward", "Ward"), + key: "ward", + }, + { + id: 4, + header: t("bedNumber", "Bed Number"), + key: "bedNumber", + }, + { + id: 5, + header: t("actions", "Action"), + key: "actions", + }, + ], + [t] + ); + + const tableRows = useMemo(() => { + return paginatedQueueEntries?.map((entry: any, index) => ({ + ...(entry as object), + id: `${entry?.patientUuid}`, + actions: { + content: ( +
+ {/* {renderActionButton(entry)} */} +
+ ), + }, + })); + }, [paginatedQueueEntries, status, t, renderActionButton, fromPage]); + + if (isLoading) { + return ; + } + + if ( + (!isLoading && patientQueueEntries && status === "pending") || + status === "completed" || + status === "" + ) { + setPatientCount(patientQueueEntries.length); + } + + if (patientQueueEntries?.length) { + return ( +
+
+ + + {({ rows, headers, getTableProps, getRowProps, onInputChange }) => ( + + + + + + + + + + + + + {headers.map((header) => ( + + {header.header?.content ?? header.header} + + ))} + + + + {rows.map((row, index) => { + return ( + <> + + {row.cells.map((cell) => ( + + {cell.value?.content ?? cell.value} + + ))} + + + {row.isExpanded ? ( + + ) : ( + + )} + + ); + })} + +
+ { + if (pageSize !== currentPageSize) { + setPageSize(pageSize); + } + if (page !== currentPage) { + goTo(page); + } + }} + /> +
+ )} +
+ {/* {showOverlay && ( + setShowOverlay(false)} + queueStatus={status} + headerTitle={t( + "assignBedToPatient", + restrictWardAdministrationToLoginLocation === true + ? `Assign Bed to Patient ${selectedPatientDetails.name} in the ${session?.sessionLocation?.display} Ward` + : `Assign Bed to Patient ${selectedPatientDetails.name}` + )} + /> + )} */} +
+ ); + } + + return ( + + ); +}; +export default AdmittedPatientsTable; diff --git a/src/bed-admission/admitted-patients/admitted-patients.component.tsx b/src/bed-admission/admitted-patients/admitted-patients.component.tsx index d433581..560c15f 100644 --- a/src/bed-admission/admitted-patients/admitted-patients.component.tsx +++ b/src/bed-admission/admitted-patients/admitted-patients.component.tsx @@ -1,6 +1,5 @@ import React from "react"; -import ActivePatientsTable from "../active-patients/active-patients-table.component"; - +import AdmittedPatientsTable from "./admitted-patients-table.component"; interface AdmittedPatientsListProps { status: string; setPatientCount: (value: number) => void; @@ -12,7 +11,10 @@ const AdmittedPatientsList: React.FC = ({ }) => { return ( <> - + ); }; diff --git a/src/bed-admission/admitted-patients/location-combo-box.component.tsx b/src/bed-admission/admitted-patients/location-combo-box.component.tsx index 6d668bd..39d0857 100644 --- a/src/bed-admission/admitted-patients/location-combo-box.component.tsx +++ b/src/bed-admission/admitted-patients/location-combo-box.component.tsx @@ -6,7 +6,10 @@ import { useConfig, useSession } from "@openmrs/esm-framework"; const LocationComboBox = ({ setLocationUuid }) => { const { t } = useTranslation(); - const { admissionLocationTagUuid } = useConfig(); + const { + admissionLocationTagUuid, + restrictWardAdministrationToLoginLocation, + } = useConfig(); const session = useSession(); const [selectedLocationId] = useState(""); @@ -37,9 +40,13 @@ const LocationComboBox = ({ setLocationUuid }) => { (location) => location?.uuid === selectedLocationId )} itemToString={(location) => location?.display ?? ""} - placeholder={t("selectNewLocation", "Select a new location")} + placeholder={t("selectNewLocation", "Select an admission location")} title={selectedLocationId} - initialSelectedItem={session.sessionLocation ?? ""} + initialSelectedItem={ + restrictWardAdministrationToLoginLocation + ? session.sessionLocation + : "" + } /> ); diff --git a/src/bed-admission/admitted-patients/styles.scss b/src/bed-admission/admitted-patients/styles.scss new file mode 100644 index 0000000..5711826 --- /dev/null +++ b/src/bed-admission/admitted-patients/styles.scss @@ -0,0 +1,284 @@ +@use '@carbon/type'; +@use '@carbon/colors'; +@use '@carbon/styles/scss/spacing'; +@import '~@openmrs/esm-styleguide/src/vars'; + +.container { + background-color: $ui-01; +} + +.displayFlex { + display: flex; +} + +.section { + border-right: 1px solid colors.$gray-20; +} + +.activePatientsTable tr:last-of-type { + td { + border-bottom: none; + } +} + +.headerContainer { + display: flex; + justify-content: space-between; + align-items: center; + background-color: $ui-background; +} + +.headerButtons { + display: flex; + flex-flow: column; +} + +.heading { + font-size: 20px; + font-weight: bold; +} + +.filterContainer { + :global(.cds--dropdown__wrapper--inline) { + gap: 0; + } + + :global(.cds--list-box__menu-icon) { + height: 1rem; + } + + :global(.cds--dropdown--inline) :global(.cds--list-box__field) { + min-width: 12rem; + } +} + +.tooltip :global(.cds--tooltip__trigger.cds--tooltip__trigger--definition) { + border-bottom: none; +} + +.tag { + margin: 0.25rem 0; +} + +.priorityTag { + @extend .tag; + @include type.type-style('label-01'); + color: #943d00; + background-color: #ffc9a3; +} + +.backgroundDataFetchingIndicator { + align-items: center; + display: flex; + flex: 1 1 0%; + justify-content: center; +} + +.search { + max-width: 16rem; + + input { + background-color: $ui-02 !important; + } +} + +.tableContainer { + background-color: $ui-01; + margin: 0 spacing.$spacing-05; + padding: 0; + + a { + text-decoration: none; + } + + th { + color: $text-02; + } + + :global(.cds--data-table) { + background-color: $ui-03; + } + + :global(.cds--data-table-content) { + display: contents; + } + + .toolbarContent { + height: spacing.$spacing-07; + margin-bottom: spacing.$spacing-02; + } +} + +.emptyRow { + padding: 0 1rem; + display: flex; + align-items: center; +} + +.activeVisitsTable tr:last-of-type { + td { + border-bottom: none; + } +} + +.expandedActiveVisitRow { + :global(.cds--tab-content) { + padding: 0.5rem 0; + } + + td { + padding: 0.5rem; + + >div { + max-height: max-content !important; + background-color: $ui-02; + } + } + + th[colspan] td[colspan]>div:first-child { + padding: 0 1rem; + } +} + +.hiddenRow { + display: none; +} + +.content { + @include type.type-style('heading-compact-02'); + color: $text-02; + margin-bottom: 0.5rem; +} + +.helper { + @include type.type-style('body-compact-01'); + color: $text-02; +} + +.separator { + @include type.type-style('body-compact-02'); + color: $text-02; + width: 80%; + margin: 1.5rem auto; + overflow: hidden; + text-align: center; + + &::before, + &::after { + background-color: $text-03; + content: ''; + display: inline-block; + height: 1px; + position: relative; + vertical-align: middle; + width: 50%; + } + + &::before { + right: 0.5rem; + margin-left: -50%; + } + + &::after { + left: 0.5rem; + margin-right: -50%; + } +} + +.tileContainer { + background-color: $ui-02; + border-top: 1px solid $ui-03; + padding: 5rem 0; +} + +.tile { + margin: auto; + width: fit-content; +} + +.tileContent { + display: flex; + flex-direction: column; + align-items: center; +} + +.menuItem { + max-width: none; +} + +.desktopHeading { + h4 { + @include type.type-style('heading-compact-02'); + color: $text-02; + } +} + +.tabletHeading { + h4 { + @include type.type-style('heading-03'); + color: $text-02; + } +} + +.desktopHeading, +.tabletHeading { + text-align: left; + text-transform: capitalize; + margin-bottom: spacing.$spacing-05; + + h4:after { + content: ''; + display: block; + width: 2rem; + padding-top: 3px; + border-bottom: 0.375rem solid var(--brand-03); + } +} + +.statusContainer { + display: flex; + align-items: center; + + svg { + margin-right: 0.5rem; + } +} + +.visitType { + @include type.type-style('heading-compact-02'); +} + +.headerBtnContainer { + background-color: $ui-background; + text-align: right; +} + +.addPatientToListBtn { + margin-left: spacing.$spacing-05; + height: spacing.$spacing-09; +} + +.editIcon { + color: $interactive-01; + margin-top: 0.5rem; + cursor: pointer; +} + +.expandedLabQueueVisitRow { + :global(.cds--tab-content) { + padding: 0.5rem 0; + } + + td { + padding: 0.5rem; + + >div { + max-height: max-content !important; + background-color: $ui-02; + } + } + + th[colspan] td[colspan]>div:first-child { + padding: 0 1rem; + } +} diff --git a/src/bed-admission/bed-admission-tabs.component.tsx b/src/bed-admission/bed-admission-tabs.component.tsx index 38899ff..aa23019 100644 --- a/src/bed-admission/bed-admission-tabs.component.tsx +++ b/src/bed-admission/bed-admission-tabs.component.tsx @@ -42,7 +42,7 @@ const BedAdmissionTabs: React.FC = () => { {t("admitted", `Admitted (${toAdmitCount})`)} - {t("discharged", `To Discharge (${toDischargeCount})`)} + {t("discharged", `To Discharge (0)`)} @@ -58,12 +58,7 @@ const BedAdmissionTabs: React.FC = () => { setPatientCount={setToAdmitCount} /> - - - + diff --git a/src/bed-admission/bed-layout/bed-layout-list.component.tsx b/src/bed-admission/bed-layout/bed-layout-list.component.tsx index 3d2e8cc..dc2f6d2 100644 --- a/src/bed-admission/bed-layout/bed-layout-list.component.tsx +++ b/src/bed-admission/bed-layout/bed-layout-list.component.tsx @@ -53,7 +53,17 @@ const BedLayoutList: React.FC = React.memo( ); } - if (!bedData?.length) { + if (locationUuid === undefined) { + return ( +
+ +
+ ); + } + if (locationUuid !== undefined && !bedData?.length) { return (
; }; } diff --git a/src/workspace/allocate-bed-workspace.component.tsx b/src/workspace/allocate-bed-workspace.component.tsx index 5c21af7..12dcacc 100644 --- a/src/workspace/allocate-bed-workspace.component.tsx +++ b/src/workspace/allocate-bed-workspace.component.tsx @@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next"; import { showNotification, showToast, + useConfig, useLayoutType, } from "@openmrs/esm-framework"; import styles from "./allocate-bed.scss"; @@ -45,43 +46,34 @@ const AllocateBedWorkSpace: React.FC = ({ const [selectedBed, setSelectedBed] = useState(); const [isBedAssigned, setIsBedAssigned] = useState(false); const [isQueueEnded, setIsQueueEnded] = useState(false); - const [locationUuid, setLocation] = useState(patientDetails.locationUuid); + const { restrictWardAdministrationToLoginLocation } = useConfig(); + const [locationUuid, setLocation] = useState( + restrictWardAdministrationToLoginLocation ? patientDetails.locationUuid : "" + ); const handleClick = (bed) => { setSelectedBed(bed); }; - if (isBedAssigned) { - endPatientQueue({ status: "completed" }, patientDetails.queueUuid) - .then(() => setIsQueueEnded(true)) - .catch((error) => { - showNotification({ - title: t("errorEndingQueue", "Error Ending Queve"), - kind: "error", - critical: true, - description: error?.message, - }); - }); - } - - if (isQueueEnded) { - showToast({ - title: t("bedAssigned", "Bed Assigned"), - kind: "success", - critical: true, - description: `Bed ${selectedBed.bedNumber} was assigned to ${patientDetails.name} successfully.`, - }); - closePanel(false); - } - const handleAssignBedToPatient = useCallback(() => { const patientAndEncounterUuids = { - encounterUuid: patientDetails?.encounter?.uuid, + encounterUuid: + patientDetails?.encounter?.uuid ?? + "84d26085-da4c-461a-8481-7c95ed3f4558", patientUuid: patientDetails.patientUuid, }; assignPatientBed(patientAndEncounterUuids, selectedBed.bedId) - .then(() => setIsBedAssigned(true)) + .then(() => { + setIsBedAssigned(true); + showToast({ + title: t("bedAssigned", "Bed Assigned"), + kind: "success", + critical: true, + description: `Bed ${selectedBed.bedNumber} was assigned to ${patientDetails.name} successfully.`, + }); + closePanel(false); + }) .catch((error) => { showNotification({ title: t("errorAssigningBed", "Error assigning bed"), @@ -98,10 +90,12 @@ const AllocateBedWorkSpace: React.FC = ({
- {queueStatus !== "completed" ? ( - "" - ) : ( + {restrictWardAdministrationToLoginLocation ? ( + ) : ( + <> + + )} Date: Fri, 19 Jan 2024 13:17:34 +0300 Subject: [PATCH 2/2] (ops): CI changes to allow us deploy to Kenyaemr registry Signed-off-by: murithijoshua --- .eslintrc | 26 ++-- .github/workflows/ci.yml | 28 +++- .idea/.gitignore | 5 + .idea/inspectionProfiles/Project_Default.xml | 6 + .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 + package.json | 4 +- src/index.ts | 2 +- yarn.lock | 132 +++++++++---------- 9 files changed, 128 insertions(+), 89 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.eslintrc b/.eslintrc index 0c50a86..7941ced 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,34 +1,34 @@ { "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint"], - "root": true, - "extends": [ - "eslint:recommended", - "plugin:prettier/recommended", - "plugin:@typescript-eslint/recommended", - "ts-react-important-stuff" - ], + "extends": ["ts-react-important-stuff", "plugin:prettier/recommended"], "rules": { + "curly": ["error", "all"], "no-restricted-imports": [ "error", { "paths": [ { "name": "lodash", - "message": "Import specific methods from `lodash`. e.g. `import map from 'lodash/map'`" + "message": "Import specific methods from `lodash-es`. e.g. `import map from 'lodash-es/map'`" }, { "name": "lodash-es", - "importNames": ["default"], - "message": "Import specific methods from `lodash-es`. e.g. `import { map } from 'lodash-es'`" - }, + "message": "Import specific methods from `lodash-es`. e.g. `import map from 'lodash-es/map'`" + } + ], + "patterns": [ { - "name": "carbon-components-react", + "group": ["carbon-components-react"], "message": "Import from `@carbon/react` directly. e.g. `import { Toggle } from '@carbon/react'`" }, { - "name": "@carbon/icons-react", + "group": ["@carbon/icons-react"], "message": "Import from `@carbon/react/icons`. e.g. `import { ChevronUp } from '@carbon/react/icons'`" + }, + { + "group": ["@import '~carbon-components/src/globals/scss/vars'"], + "message": "Import from `@carbon/styles/scss`. e.g. `@use '@carbon/styles/scss/type'`" } ] } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b12b429..42df77b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: UgandaEMR CI +name: Kenyaemr CI on: workflow_dispatch: # enables the workflow to be triggered manually @@ -11,8 +11,8 @@ on: - created env: - ESM_NAME: "@ugandaemr/esm-bed-management-app" - JS_NAME: "esm-ugandaemr-bed-management-app.js" + ESM_NAME: "@kenyaemr/esm-bed-management-app" + JS_NAME: "esm-kenyaemr-bed-management-app.js" jobs: build: @@ -36,11 +36,11 @@ jobs: with: path: "**/node_modules" key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }} - + - name: Install dependencies if: steps.cache.outputs.cache-hit != 'true' run: yarn install --immutable - + - name: Setup local cache server for Turborepo uses: felixmosh/turborepo-gh-artifacts@v2 with: @@ -85,7 +85,7 @@ jobs: - name: Install dependencies if: steps.cache.outputs.cache-hit != 'true' run: yarn install --immutable - + - name: Setup local cache server for Turborepo uses: felixmosh/turborepo-gh-artifacts@v2 with: @@ -113,6 +113,21 @@ jobs: path: | dist + Trigger_server_build: + runs-on: ubuntu-latest + needs: pre_release + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Invoke workflow without inputs. Don't wait for result + uses: aurelien-baudet/workflow-dispatch@v2 + with: + workflow: KenyaEMR CI + repo: palladiumkenya/openmrs-config-kenyaemr + token: ${{ secrets.PERSONAL_TOKENS }} + wait-for-completion: false + release: runs-on: ubuntu-latest @@ -133,4 +148,3 @@ jobs: - run: yarn config set npmAuthToken "${NODE_AUTH_TOKEN}" && yarn npm publish env: NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} - \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..cc02f0c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/package.json b/package.json index ed0fdd5..bfd46ae 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { - "name": "@ugandaemr/esm-bed-management-app", + "name": "@kenyaemr/esm-bed-management-app", "version": "1.0.0", "license": "MPL-2.0", "description": "A frontend module for managing beds in UgandaEMR+", - "browser": "dist/esm-ugandaemr-bed-management-app.js", + "browser": "dist/esm-kenyaemr-bed-management-app.js", "main": "src/index.ts", "source": true, "scripts": { diff --git a/src/index.ts b/src/index.ts index e4dbe95..11a233f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ import { configSchema } from "./config-schema"; import { createLeftPanelLink } from "./left-panel-link.component"; import { createDashboardLink } from "./bed-admission/createDashboardLink"; -const moduleName = "@ugandaemr/esm-bed-management-app"; +const moduleName = "@kenyaemr/esm-bed-management-app"; const options = { featureName: "bed-management", diff --git a/yarn.lock b/yarn.lock index a65b921..6b0a5a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3129,6 +3129,72 @@ __metadata: languageName: node linkType: hard +"@kenyaemr/esm-bed-management-app@workspace:.": + version: 0.0.0-use.local + resolution: "@kenyaemr/esm-bed-management-app@workspace:." + dependencies: + "@carbon/react": ^1.33.1 + "@hookform/resolvers": ^3.3.1 + "@openmrs/esm-framework": next + "@openmrs/esm-patient-common-lib": next + "@openmrs/esm-styleguide": next + "@openmrs/openmrs-form-engine-lib": ^1.0.0-pre.44 + "@swc/cli": ^0.1.62 + "@swc/core": ^1.3.68 + "@swc/jest": ^0.2.26 + "@testing-library/dom": ^8.20.1 + "@testing-library/jest-dom": ^5.16.5 + "@testing-library/react": ^13.4.0 + "@testing-library/user-event": ^14.4.3 + "@types/jest": ^28.1.8 + "@types/react": ^18.2.14 + "@types/react-dom": ^18.2.6 + "@types/react-router": ^5.1.20 + "@types/react-router-dom": ^5.3.3 + "@types/webpack-env": ^1.18.1 + "@typescript-eslint/eslint-plugin": ^5.61.0 + "@typescript-eslint/parser": ^5.61.0 + classnames: ^2.3.2 + css-loader: ^6.8.1 + dayjs: ^1.11.9 + eslint: ^8.44.0 + eslint-config-prettier: ^8.8.0 + eslint-config-ts-react-important-stuff: ^3.0.0 + eslint-plugin-prettier: ^4.2.1 + husky: ^8.0.0 + i18next: ^23.2.8 + i18next-parser: ^8.0.0 + identity-obj-proxy: ^3.0.0 + jest: ^28.1.3 + jest-cli: ^28.1.3 + jest-environment-jsdom: ^28.1.3 + lodash-es: ^4.17.21 + openmrs: next + prettier: ^2.8.8 + pretty-quick: ^3.1.3 + react: ^18.2.0 + react-dom: ^18.2.0 + react-hook-form: ^7.47.0 + react-i18next: ^11.18.6 + react-image-annotate: ^1.8.0 + react-router-dom: ^6.14.1 + rxjs: ^6.6.7 + swc-loader: ^0.2.3 + turbo: ^1.10.7 + typescript: ^4.9.5 + webpack: ^5.88.1 + webpack-cli: ^5.1.4 + zod: ^3.22.2 + peerDependencies: + "@openmrs/esm-framework": "*" + "@openmrs/openmrs-form-engine-lib": 5.x + react: 18.x + react-i18next: 11.x + react-router-dom: 6.x + rxjs: 6.x + languageName: unknown + linkType: soft + "@leichtgewicht/ip-codec@npm:^2.0.1": version: 2.0.4 resolution: "@leichtgewicht/ip-codec@npm:2.0.4" @@ -5315,72 +5381,6 @@ __metadata: languageName: node linkType: hard -"@ugandaemr/esm-bed-management-app@workspace:.": - version: 0.0.0-use.local - resolution: "@ugandaemr/esm-bed-management-app@workspace:." - dependencies: - "@carbon/react": ^1.33.1 - "@hookform/resolvers": ^3.3.1 - "@openmrs/esm-framework": next - "@openmrs/esm-patient-common-lib": next - "@openmrs/esm-styleguide": next - "@openmrs/openmrs-form-engine-lib": ^1.0.0-pre.44 - "@swc/cli": ^0.1.62 - "@swc/core": ^1.3.68 - "@swc/jest": ^0.2.26 - "@testing-library/dom": ^8.20.1 - "@testing-library/jest-dom": ^5.16.5 - "@testing-library/react": ^13.4.0 - "@testing-library/user-event": ^14.4.3 - "@types/jest": ^28.1.8 - "@types/react": ^18.2.14 - "@types/react-dom": ^18.2.6 - "@types/react-router": ^5.1.20 - "@types/react-router-dom": ^5.3.3 - "@types/webpack-env": ^1.18.1 - "@typescript-eslint/eslint-plugin": ^5.61.0 - "@typescript-eslint/parser": ^5.61.0 - classnames: ^2.3.2 - css-loader: ^6.8.1 - dayjs: ^1.11.9 - eslint: ^8.44.0 - eslint-config-prettier: ^8.8.0 - eslint-config-ts-react-important-stuff: ^3.0.0 - eslint-plugin-prettier: ^4.2.1 - husky: ^8.0.0 - i18next: ^23.2.8 - i18next-parser: ^8.0.0 - identity-obj-proxy: ^3.0.0 - jest: ^28.1.3 - jest-cli: ^28.1.3 - jest-environment-jsdom: ^28.1.3 - lodash-es: ^4.17.21 - openmrs: next - prettier: ^2.8.8 - pretty-quick: ^3.1.3 - react: ^18.2.0 - react-dom: ^18.2.0 - react-hook-form: ^7.47.0 - react-i18next: ^11.18.6 - react-image-annotate: ^1.8.0 - react-router-dom: ^6.14.1 - rxjs: ^6.6.7 - swc-loader: ^0.2.3 - turbo: ^1.10.7 - typescript: ^4.9.5 - webpack: ^5.88.1 - webpack-cli: ^5.1.4 - zod: ^3.22.2 - peerDependencies: - "@openmrs/esm-framework": "*" - "@openmrs/openmrs-form-engine-lib": 5.x - react: 18.x - react-i18next: 11.x - react-router-dom: 6.x - rxjs: 6.x - languageName: unknown - linkType: soft - "@webassemblyjs/ast@npm:1.11.6, @webassemblyjs/ast@npm:^1.11.5": version: 1.11.6 resolution: "@webassemblyjs/ast@npm:1.11.6"