From a133306f244714e15eddc29d8c9af2b5ea1ae03f Mon Sep 17 00:00:00 2001 From: Fredrick Kioko <67967749+its-kios09@users.noreply.github.com> Date: Thu, 25 Jul 2024 08:51:24 +0300 Subject: [PATCH] KHP3-6234: Enhanced Case Management Functionality: CRUD Operations, Table Integration, and Conditional Dropdown Behavior (#285) * (feat) added a dashboard link for case management encounter * (feat) added header and combox for selecting forms * (feat) added enhancement on case management and improved on CRUD on special clinics * (refactor)translation --- packages/esm-billing-app/translations/en.json | 1 - .../encounters/case-encounter-header.scss | 87 +++++++ .../case-encounter-overview.component.tsx | 231 ++++++++++++++++++ .../case-encounter-table.resource.ts | 103 ++++++++ .../src/config-schema.ts | 19 ++ .../src/dashboard.meta.tsx | 8 + .../src/index.ts | 11 +- .../src/routes.json | 22 ++ .../generic-dashboard.component.tsx | 136 ++++------- .../generic-table.component.tsx | 95 +++++++ .../translations/en.json | 9 +- 11 files changed, 626 insertions(+), 96 deletions(-) create mode 100644 packages/esm-patient-clinical-view-app/src/case-management/encounters/case-encounter-header.scss create mode 100644 packages/esm-patient-clinical-view-app/src/case-management/encounters/case-encounter-overview.component.tsx create mode 100644 packages/esm-patient-clinical-view-app/src/case-management/encounters/case-encounter-table.resource.ts create mode 100644 packages/esm-patient-clinical-view-app/src/specialized-clinics/generic-nav-links/generic-table.component.tsx diff --git a/packages/esm-billing-app/translations/en.json b/packages/esm-billing-app/translations/en.json index 6b73ebff1..30fb5001f 100644 --- a/packages/esm-billing-app/translations/en.json +++ b/packages/esm-billing-app/translations/en.json @@ -129,7 +129,6 @@ "selectExemptionCategory": "Select exemption category", "selectitemstobeclaimed": "Select items that are to be included in the claims", "selectPaymentMethod": "Select payment method", - "selectPaymentMethodPlaceholder": "Select payment method", "sellingAmount": "Enter selling price", "sellingPrice": "Selling Price", "sendClaim": "Claim sent successfully", diff --git a/packages/esm-patient-clinical-view-app/src/case-management/encounters/case-encounter-header.scss b/packages/esm-patient-clinical-view-app/src/case-management/encounters/case-encounter-header.scss new file mode 100644 index 000000000..474a2b705 --- /dev/null +++ b/packages/esm-patient-clinical-view-app/src/case-management/encounters/case-encounter-header.scss @@ -0,0 +1,87 @@ +@use '@carbon/layout'; +@use '@carbon/colors'; +@use '@carbon/type'; +@import '~@openmrs/esm-styleguide/src/vars'; + +.headerContainer { + display: flex; + justify-content: space-between; + background-color: $ui-02; + height: layout.$spacing-11; + align-items: center; + padding: 0 layout.$spacing-05; +} +.headerTitle { + color: $ui-05; + position: relative; +} + +.headerTitle::after { + content: ''; + display: block; + width: 2rem; + padding-top: 1.188rem; + border-bottom: 0.375rem solid var(--brand-03); + position: absolute; + left: 0; + bottom: -0.5rem; + top: 0.5rem; +} + +.actionBtn { + display: flex; + column-gap: 0.5rem; + justify-content: center; +} +.comboBox input[type='text'] { + background-color: white; + cursor: default; + ::placeholder { + color: black; + } +} + +.desktopHeading, +.tabletHeading { + text-align: left; + text-transform: capitalize; + + h4 { + @include type.type-style('heading-compact-02'); + color: colors.$gray-70; + + &:after { + content: ''; + display: block; + width: 2rem; + padding-top: 3px; + border-bottom: 0.375rem solid; + @include brand-03(border-bottom-color); + } + } +} + +.widgetContainer { + background-color: colors.$white-0; + border: 1px solid colors.$gray-20; + margin-bottom: 1rem; +} + +.widgetContainer :global(.cds--data-table) thead th button span { + height: unset !important; +} + +.tile { + text-align: center; +} + +.emptyStateContainer { + margin: 2rem 0; +} + +.content { + @include type.type-style('heading-compact-01'); + color: colors.$gray-70; + margin-top: layout.$layout-05; + margin-bottom: layout.$spacing-03; +} diff --git a/packages/esm-patient-clinical-view-app/src/case-management/encounters/case-encounter-overview.component.tsx b/packages/esm-patient-clinical-view-app/src/case-management/encounters/case-encounter-overview.component.tsx new file mode 100644 index 000000000..6c457cb07 --- /dev/null +++ b/packages/esm-patient-clinical-view-app/src/case-management/encounters/case-encounter-overview.component.tsx @@ -0,0 +1,231 @@ +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useConfig, formatDate, showModal, showSnackbar, isDesktop, useLayoutType } from '@openmrs/esm-framework'; +import { EmptyDataIllustration, launchPatientWorkspace, PatientChartPagination } from '@openmrs/esm-patient-common-lib'; +import { ComboBox, Dropdown, DataTableSkeleton, Layer, Tile } from '@carbon/react'; +import { KeyedMutator } from 'swr'; +import styles from './case-encounter-header.scss'; +import GenericTable from '../../specialized-clinics/generic-nav-links/generic-table.component'; +import { useInfiniteVisits, deleteEncounter } from './case-encounter-table.resource'; +import { ConfigObject } from '../../config-schema'; + +interface CaseEncounterProps { + mutate: KeyedMutator; + patientUuid: string; + onFilterChange: (formUuid: string) => void; +} + +interface CaseEncounterOverviewComponentProps { + patientUuid: string; +} + +const CaseEncounterHeader = ({ patientUuid, mutate, onFilterChange }: CaseEncounterProps) => { + const { t } = useTranslation(); + const title = t('caseEncounter', 'Case management encounters'); + const { caseManagementForms } = useConfig(); + + const handleOpenOrEditClinicalEncounterForm = (formUuid: string, encounterUUID = '') => { + launchPatientWorkspace('patient-form-entry-workspace', { + workspaceTitle: 'Clinical Encounter', + mutateForm: mutate, + formInfo: { + encounterUuid: encounterUUID, + formUuid, + patientUuid, + visitTypeUuid: '', + visitUuid: '', + }, + }); + }; + + const items = caseManagementForms.map((form) => ({ + id: form.id, + text: form.title, + formUuid: form.formUuid, + filterUuid: form.formUuid, + })); + + const handleComboBoxChange = ({ selectedItem }) => { + if (selectedItem) { + handleOpenOrEditClinicalEncounterForm(selectedItem.formUuid); + } + }; + + const handleEncounterTypeChange = ({ selectedItem }) => { + onFilterChange(selectedItem.filterUuid); + }; + + return ( +
+ {title} +
+ (item ? item.text : '')} + onChange={handleEncounterTypeChange} + size="lg" + /> + (item ? item.text : '')} + placeholder="Select forms" + className={styles.comboBox} + /> +
+
+ ); +}; + +const CaseEncounterOverviewComponent = ({ patientUuid }: CaseEncounterOverviewComponentProps) => { + const { visits, isLoading, error, hasMore, isValidating, mutateVisits, setSize, size } = + useInfiniteVisits(patientUuid); + const { t } = useTranslation(); + const { caseManagementForms } = useConfig(); + const [filterFormUuid, setFilterFormUuid] = useState(''); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const layout = useLayoutType(); + + const formUuids = caseManagementForms.map((form) => form.formUuid); + + const filteredEncounters = + visits + ?.flatMap((visit) => visit.encounters) + .filter((encounter) => + filterFormUuid ? encounter.form?.uuid === filterFormUuid : formUuids.includes(encounter.form?.uuid), + ) || []; + + const visitTypeMap = visits?.reduce((acc, visit) => { + visit.encounters.forEach((encounter) => { + acc[encounter.uuid] = visit.visitType?.display ?? '--'; + }); + return acc; + }, {} as Record); + + const paginatedEncounters = filteredEncounters.slice((currentPage - 1) * pageSize, currentPage * pageSize); + + const genericTableHeader = [ + { header: 'Date', key: 'encounterDatetime' }, + { header: 'Visit Type', key: 'visitType' }, + { header: 'Encounter type', key: 'encounterType' }, + { header: 'Form name', key: 'formName' }, + ]; + + function formatProviderName(display) { + if (!display) { + return '--'; + } + return display.split(':')[0].trim(); + } + + const rows = paginatedEncounters.map((encounter) => ({ + id: `${encounter.uuid}`, + encounterDatetime: formatDate(new Date(encounter.encounterDatetime)), + visitType: visitTypeMap[encounter.uuid] ?? '--', + encounterType: encounter.encounterType?.display ?? '--', + formName: encounter.form?.display ?? '--', + })); + + const handleWorkspaceEditForm = (encounterUuid: string) => { + const encounter = paginatedEncounters.find((enc) => enc.uuid === encounterUuid); + const workspaceTitle = encounter.form?.display ?? ''; + const encounterTypeUuid = encounter.encounterType?.uuid ?? ''; + const formUuid = encounter.form?.uuid ?? ''; + + launchPatientWorkspace('patient-form-entry-workspace', { + workspaceTitle, + mutateForm: mutateVisits, + formInfo: { + encounterUuid, + formUuid, + additionalProps: { + encounterTypeUuid, + }, + }, + }); + }; + + const handleDeleteEncounter = React.useCallback( + (encounterUuid: string, encounterTypeName?: string) => { + const close = showModal('delete-encounter-modal', { + close: () => close(), + encounterTypeName: encounterTypeName || '', + onConfirmation: () => { + const abortController = new AbortController(); + deleteEncounter(encounterUuid, abortController) + .then(() => { + mutateVisits?.(); + showSnackbar({ + isLowContrast: true, + title: t('encounterDeleted', 'Encounter deleted'), + subtitle: `Encounter ${t('successfullyDeleted', 'successfully deleted')}`, + kind: 'success', + }); + }) + .catch(() => { + showSnackbar({ + isLowContrast: false, + title: t('error', 'Error'), + subtitle: `Encounter ${t('failedDeleting', "couldn't be deleted")}`, + kind: 'error', + }); + }); + close(); + }, + }); + }, + [t, mutateVisits], + ); + + const handlePageChange = ({ page }) => { + setCurrentPage(page); + }; + + if (isLoading) { + return ; + } + + return ( + <> + + {filteredEncounters.length === 0 ? ( + + + +

+ {t('noEncounterToDisplay', 'There are no encounters to display for this patient.')} +

+
+
+ ) : ( + <> + + {filteredEncounters.length > pageSize && ( + + )} + + )} + + ); +}; + +export default CaseEncounterOverviewComponent; diff --git a/packages/esm-patient-clinical-view-app/src/case-management/encounters/case-encounter-table.resource.ts b/packages/esm-patient-clinical-view-app/src/case-management/encounters/case-encounter-table.resource.ts new file mode 100644 index 000000000..fa7f3c532 --- /dev/null +++ b/packages/esm-patient-clinical-view-app/src/case-management/encounters/case-encounter-table.resource.ts @@ -0,0 +1,103 @@ +import { openmrsFetch, restBaseUrl, useConfig } from '@openmrs/esm-framework'; +import useSWRInfinite from 'swr/infinite'; + +export interface ChartConfig { + freeTextFieldConceptUuid: string; + offlineVisitTypeUuid: string; + visitTypeResourceUrl: string; + showRecommendedVisitTypeTab: boolean; + visitAttributeTypes: Array<{ + uuid: string; + required: boolean; + displayInThePatientBanner: boolean; + showWhenExpression?: string; + }>; + showServiceQueueFields: boolean; + visitQueueNumberAttributeUuid: string; + showAllEncountersTab: boolean; + defaultFacilityUrl: string; + showUpcomingAppointments: boolean; + logo: { + src: string; + alt: string; + name: string; + }; + disableChangingVisitLocation: boolean; + numberOfVisitsToLoad: number; + showExtraVisitAttributesSlot: boolean; +} + +export interface Observation { + uuid: string; + concept: { + uuid: string; + display: string; + conceptClass: { + uuid: string; + display: string; + }; + }; + display: string; + groupMembers: null | Array<{ + uuid: string; + concept: { + uuid: string; + display: string; + }; + value: { + uuid: string; + display: string; + }; + display: string; + }>; + value: any; + obsDatetime?: string; +} + +export function useInfiniteVisits(patientUuid: string) { + const config = useConfig(); + const customRepresentation = + 'custom:(visitType:(uuid,name,display),uuid,encounters:(uuid,diagnoses:(uuid,display,rank,diagnosis),form:(uuid,display),encounterDatetime,orders:full,obs:(uuid,concept:(uuid,display,conceptClass:(uuid,display)),display,groupMembers:(uuid,concept:(uuid,display),value:(uuid,display),display),value,obsDatetime),encounterType:(uuid,display,viewPrivilege,editPrivilege),encounterProviders:(uuid,display,encounterRole:(uuid,display),provider:(uuid,person:(uuid,display)))),visitType:(uuid,name,display),startDatetime,stopDatetime,patient,attributes:(attributeType:ref,display,uuid,value)'; + + const getKey = (pageIndex, previousPageData) => { + const pageSize = config.numberOfVisitsToLoad; + + if (previousPageData && !previousPageData?.data?.links.some((link) => link.rel === 'next')) { + return null; + } + + let url = `${restBaseUrl}/visit?patient=${patientUuid}&v=${customRepresentation}&limit=${pageSize}`; + + if (pageIndex) { + url += `&startIndex=${pageIndex * pageSize}`; + } + + return url; + }; + + const { data, error, isLoading, isValidating, mutate, size, setSize } = useSWRInfinite( + patientUuid ? getKey : null, + openmrsFetch, + { parallel: true }, + ); + + return { + visits: data ? [].concat(data?.flatMap((page) => page.data.results)) : null, + error, + hasMore: data?.length ? !!data[data.length - 1].data?.links?.some((link) => link.rel === 'next') : false, + isLoading, + isValidating, + mutateVisits: mutate, + setSize, + size, + }; +} +export function deleteEncounter(encounterUuid: string, abortController: AbortController) { + return openmrsFetch(`${restBaseUrl}/encounter/${encounterUuid}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + signal: abortController.signal, + }); +} diff --git a/packages/esm-patient-clinical-view-app/src/config-schema.ts b/packages/esm-patient-clinical-view-app/src/config-schema.ts index 2085e061f..4023ba488 100644 --- a/packages/esm-patient-clinical-view-app/src/config-schema.ts +++ b/packages/esm-patient-clinical-view-app/src/config-schema.ts @@ -9,6 +9,24 @@ export const configSchema = { hivTestingServices: '9c0a7a57-62ff-4f75-babe-5835b0e921b7', }, }, + caseManagementForms: { + _type: Type.Array, + _description: 'List of form and encounter UUIDs', + _default: [ + { + id: 'high-iit-intervention', + title: 'High IIT Intervention Form', + formUuid: 'd86a77bd-769a-47ec-942a-716afbd907cc', + encounterTypeUuid: 'a0034eee-1940-4e35-847f-97537a35d05e', + }, + { + id: 'home-visit-checklist', + title: 'Home Visit Checklist Form', + formUuid: 'dbe7db2d-abed-47b6-904f-1308b997d7bf', + encounterTypeUuid: '4224f8bf-11b2-4e47-a958-1dbdfd7fa41d', + }, + ], + }, formsList: { _type: Type.Object, _description: 'List of form UUIDs', @@ -159,6 +177,7 @@ export const configSchema = { }; export interface ConfigObject { + caseManagementForms: Array<{ id: string; title: string; formUuid: string; encounterTypeUuid: string }>; encounterTypes: { mchMotherConsultation: string; hivTestingServices: string }; formsList: { labourAndDelivery: string; diff --git a/packages/esm-patient-clinical-view-app/src/dashboard.meta.tsx b/packages/esm-patient-clinical-view-app/src/dashboard.meta.tsx index b4d56ddd6..0ccfbd7af 100644 --- a/packages/esm-patient-clinical-view-app/src/dashboard.meta.tsx +++ b/packages/esm-patient-clinical-view-app/src/dashboard.meta.tsx @@ -82,3 +82,11 @@ export const caseManagementDashboardMeta = { moduleName: '@kenyaemr/esm-patient-clinical-view-app', config: {}, }; +export const caseEncounterDashboardMeta = { + slot: 'patient-chart-relationships-slot', + columns: 1, + title: 'Case management', + path: 'case-encounter', + moduleName: '@kenyaemr/esm-patient-clinical-view-app', + config: {}, +}; diff --git a/packages/esm-patient-clinical-view-app/src/index.ts b/packages/esm-patient-clinical-view-app/src/index.ts index c8f4c109c..da39193ed 100644 --- a/packages/esm-patient-clinical-view-app/src/index.ts +++ b/packages/esm-patient-clinical-view-app/src/index.ts @@ -8,7 +8,12 @@ import { configSchema } from './config-schema'; import BirthDateCalculator from './contact-list/birthdate-calculator.component'; import ContactList from './contact-list/contact-list.component'; import ContactListForm from './contact-list/contact-list.workspace'; -import { caseManagementDashboardMeta, contactListDashboardMeta, relationshipsDashboardMeta } from './dashboard.meta'; +import { + caseEncounterDashboardMeta, + caseManagementDashboardMeta, + contactListDashboardMeta, + relationshipsDashboardMeta, +} from './dashboard.meta'; import FamilyHistory from './family-partner-history/family-history.component'; import { familyHistoryDashboardMeta } from './family-partner-history/family-partner-dashboard.meta'; import AntenatalCare from './maternal-and-child-health/antenatal-care.component'; @@ -34,6 +39,7 @@ import { createLeftPanelLink } from './left-panel-link.component'; import WrapComponent from './case-management/wrap/wrap.component'; import CaseManagementForm from './case-management/workspace/case-management.workspace'; import Relationships from './relationships/relationships.component'; +import CaseEncounterOverviewComponent from './case-management/encounters/case-encounter-overview.component'; const moduleName = '@kenyaemr/esm-patient-clinical-view-app'; @@ -97,6 +103,9 @@ export const labourAndDelivery = getSyncLifecycle(LabourDelivery, options); export const caseManagementDashboardLink = getSyncLifecycle(createLeftPanelLink(caseManagementDashboardMeta), options); export const wrapComponent = getSyncLifecycle(WrapComponent, options); export const caseManagementForm = getSyncLifecycle(CaseManagementForm, options); +export const caseEncounterDashboardLink = getSyncLifecycle(createDashboardLink(caseEncounterDashboardMeta), options); +// export const caseEncounterHeader = getSyncLifecycle(CaseEncounterHeader, options); +export const caseEncounterTable = getSyncLifecycle(CaseEncounterOverviewComponent, options); // Dashboard links for Maternal and Child Health services export const antenatalCareLink = getSyncLifecycle(createDashboardLink(antenatalDashboardMeta), options); diff --git a/packages/esm-patient-clinical-view-app/src/routes.json b/packages/esm-patient-clinical-view-app/src/routes.json index 66d2a957c..335698785 100644 --- a/packages/esm-patient-clinical-view-app/src/routes.json +++ b/packages/esm-patient-clinical-view-app/src/routes.json @@ -209,6 +209,28 @@ { "name": "case-management-form", "component": "caseManagementForm" + }, + { + "name": "case-encounter-link", + "component": "caseEncounterDashboardLink", + "slot": "patient-chart-dashboard-slot", + "order": 14, + "meta": { + "columns": 1, + "columnSpan": 1, + "slot": "patient-chart-case-encounter-slot", + "path": "case-encounter", + + "layoutMode": "anchored" + } + }, + { + "name": "case-encounter-table", + "slot": "patient-chart-case-encounter-slot", + "component": "caseEncounterTable", + "order": 0, + "online": true, + "offline": false } ], "modals": [ diff --git a/packages/esm-patient-clinical-view-app/src/specialized-clinics/generic-nav-links/generic-dashboard.component.tsx b/packages/esm-patient-clinical-view-app/src/specialized-clinics/generic-nav-links/generic-dashboard.component.tsx index bf1dd2b4e..b571e229b 100644 --- a/packages/esm-patient-clinical-view-app/src/specialized-clinics/generic-nav-links/generic-dashboard.component.tsx +++ b/packages/esm-patient-clinical-view-app/src/specialized-clinics/generic-nav-links/generic-dashboard.component.tsx @@ -2,25 +2,12 @@ import React, { useEffect, useState } from 'react'; import { CardHeader, EmptyState, launchPatientWorkspace } from '@openmrs/esm-patient-common-lib'; import { useTranslation } from 'react-i18next'; import capitalize from 'lodash/capitalize'; -import { ErrorState, formatDate, launchWorkspace, useConfig } from '@openmrs/esm-framework'; +import { ErrorState, launchWorkspace, showModal, showSnackbar, useConfig } from '@openmrs/esm-framework'; import { ConfigObject } from '../../config-schema'; import { genericTableHeader, useEncounters } from './useEncounters'; -import { - DataTable, - TableContainer, - Table, - TableHead, - TableRow, - TableExpandHeader, - TableHeader, - TableBody, - TableExpandRow, - TableCell, - TableExpandedRow, - DataTableSkeleton, - Button, -} from '@carbon/react'; -import EncounterObservations from './encounter-observations/encounter-observations.component'; +import { DataTableSkeleton, Button } from '@carbon/react'; +import GenericTable from './generic-table.component'; +import { deleteEncounter } from '../../case-management/encounters/case-encounter-table.resource'; type GenericDashboardProps = { patientUuid: string }; @@ -65,9 +52,8 @@ const GenericDashboard: React.FC = ({ patientUuid }) => { }, }); }; - const handleWorkspaceEditForm = (encounterUuid: string = '') => { - launchWorkspace('patient-form-entry-workspace', { + launchPatientWorkspace('patient-form-entry-workspace', { workspaceTitle: clinicalFormTitle.replace('clinic', 'form'), mutateForm: mutate, formInfo: { @@ -78,16 +64,37 @@ const GenericDashboard: React.FC = ({ patientUuid }) => { }); }; - const rows = - encounters?.map((encounter) => ({ - id: `${encounter.uuid}`, - encounterDatetime: formatDate(new Date(encounter.encounterDatetime)), - visitType: encounter.visit?.visitType?.display ?? '--', - provider: - encounter.encounterProviders?.length > 0 - ? capitalize(encounter.encounterProviders[0].provider['display']) - : '--', - })) ?? []; + const handleDeleteEncounter = React.useCallback( + (encounterUuid: string, encounterTypeName?: string) => { + const close = showModal('delete-encounter-modal', { + close: () => close(), + encounterTypeName: encounterTypeName || '', + onConfirmation: () => { + const abortController = new AbortController(); + deleteEncounter(encounterUuid, abortController) + .then(() => { + mutate?.(); + showSnackbar({ + isLowContrast: true, + title: t('encounterDeleted', 'Encounter deleted'), + subtitle: `Encounter ${t('successfullyDeleted', 'successfully deleted')}`, + kind: 'success', + }); + }) + .catch(() => { + showSnackbar({ + isLowContrast: false, + title: t('error', 'Error'), + subtitle: `Encounter ${t('failedDeleting', "couldn't be deleted")}`, + kind: 'error', + }); + }); + close(); + }, + }); + }, + [t, mutate], + ); if (isLoading) { return ; @@ -105,74 +112,17 @@ const GenericDashboard: React.FC = ({ patientUuid }) => { return (
- + - - {({ - rows, - headers, - getHeaderProps, - getRowProps, - getExpandedRowProps, - getTableProps, - getTableContainerProps, - }) => ( - - - - - - {headers.map((header, i) => ( - - {header.header} - - ))} - - - - {rows.map((row, index) => ( - - - {row.cells.map((cell) => ( - {cell.value} - ))} - - - <> - - - - - - ))} - -
-
- )} -
+
); }; diff --git a/packages/esm-patient-clinical-view-app/src/specialized-clinics/generic-nav-links/generic-table.component.tsx b/packages/esm-patient-clinical-view-app/src/specialized-clinics/generic-nav-links/generic-table.component.tsx new file mode 100644 index 000000000..232fd8e73 --- /dev/null +++ b/packages/esm-patient-clinical-view-app/src/specialized-clinics/generic-nav-links/generic-table.component.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { + DataTable, + TableContainer, + Table, + TableHead, + TableRow, + TableExpandHeader, + TableHeader, + TableBody, + TableExpandRow, + TableCell, + TableExpandedRow, + Button, +} from '@carbon/react'; +import EncounterObservations from './encounter-observations/encounter-observations.component'; +import capitalize from 'lodash-es/capitalize'; +import { formatDate } from '@openmrs/esm-framework'; +import { useTranslation } from 'react-i18next'; + +type GenericTableProps = { + encounters: any[]; + onEdit: (encounterUuid: string) => void; + onDelete: (encounterUuid: string, encounterTypeName?: string) => void; + headers: { key: string; header: string }[]; + rows?: any[]; +}; + +const GenericTable: React.FC = ({ encounters, onEdit, onDelete, headers, rows }) => { + const { t } = useTranslation(); + function formatProviderName(display) { + if (!display) { + return '--'; + } + return display.split('-')[0].trim(); + } + const computedRows = + rows || + encounters.map((encounter) => ({ + id: `${encounter.uuid}`, + encounterDatetime: formatDate(new Date(encounter.encounterDatetime)), + visitType: encounter.visit?.visitType?.display ?? '--', + provider: + encounter.encounterProviders?.length > 0 + ? formatProviderName(capitalize(encounter.encounterProviders[0].provider['display'])) + : '--', + })); + return ( + + {({ rows, headers, getHeaderProps, getRowProps, getExpandedRowProps, getTableProps, getTableContainerProps }) => ( + + + + + + {headers.map((header, i) => ( + + {header.header} + + ))} + + + + {rows.map((row, index) => ( + + + {row.cells.map((cell) => ( + {cell.value} + ))} + + + <> + + + + + + + ))} + +
+
+ )} +
+ ); +}; + +export default GenericTable; diff --git a/packages/esm-patient-clinical-view-app/translations/en.json b/packages/esm-patient-clinical-view-app/translations/en.json index 545ddad57..abaf2dadf 100644 --- a/packages/esm-patient-clinical-view-app/translations/en.json +++ b/packages/esm-patient-clinical-view-app/translations/en.json @@ -12,6 +12,7 @@ "alcoholUse": "Alcohol Use", "alcoholUseDuration": "Alcohol Use Duration", "alive": "Alive", + "all": "All", "ancVisitNumber": "ANC Visit Number", "antenatalCare": "Antenatal Care", "babyGivenVitaminD": "Baby Given Vitamin K", @@ -21,6 +22,7 @@ "bloodLoss": "Blood Transfusion Done", "bloodTransfusion": "Blood Transfusion", "calculateBirthDate": "Calculate birth date", + "caseEncounter": "Case management encounters", "causeOfDeath": "Cause of Death", "cervicalDilation": "Cervical Dilation cm", "chartView": "Chart View", @@ -51,19 +53,22 @@ "editEncounter": "Edit", "editForm": "Edit Form", "encounterDate": "Date", + "encounterDeleted": "Encounter deleted", "encounterDetails": "Encounter details", "encounters": "", "encountersList": "Encounters list", + "error": "Error", "facility": "Facility", + "failedDeleting": "couldn't be deleted", "familyHistory": "Family history", "feedingOrders": "Feeding Orders", "fetalHeartRate": "Fetal Heart Rate", + "filterByForm": "Filter by form", "finalDiagnosis": "Final Diagnosis", "finalDischargeDiagnosis": "Final Discharge Diagnosis", "finalOutcome": "Final Outcome", "foetalPresentation": "Foetal Presentation", "followUpDate": "Next follow-up date", - "for": "for", "gestationalSize": "Gestational Size", "hivStatus": "HIV Status", "hivTestResults": "HIV Status", @@ -88,6 +93,7 @@ "neonatalSummary": "Neonatal Summary", "nextVisitDate": "Next visit date", "noContactToDisplay": "There is no contact data to display for this patient.", + "noEncounterToDisplay": "There are no encounters to display for this patient.", "noObservationsFound": "No observations found", "onDate": "On Date", "operatingDoctor": "Operating Doctor", @@ -115,6 +121,7 @@ "smokingDuration": "Smoking Duration", "socialHistory": "Social History", "statusAtDischarge": "Status at Discharge", + "successfullyDeleted": "successfully deleted", "surgicalHistory": "Surgical History", "surgicalSummary": "Surgical Summary", "tableView": "Table View",