Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) - O3-3096 service queues - configuration-driven queue table #1114

Merged
merged 6 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/esm-service-queues-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color",
"coverage": "yarn test --coverage",
"typescript": "tsc",
"extract-translations": "i18next 'src/**/*.component.tsx' --config ../../tools/i18next-parser.config.js"
"extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.resource.ts' --config ../../tools/i18next-parser.config.js"
},
"browserslist": [
"extends browserslist-config-openmrs"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import React from 'react';
import TransitionMenu from '../queue-entry-table-components/transition-entry.component';
import { type QueueTableCellComponentProps, type QueueTableColumn } from '../types';
import { type QueueEntry } from '../types';
import ActionsMenu from '../queue-entry-table-components/actions-menu.component';
import EditMenu from '../queue-entry-table-components/edit-entry.component';
import { useConfig } from '@openmrs/esm-framework';
import { type ConfigObject } from '../config-schema';
import { mapVisitQueueEntryProperties } from './active-visits-table.resource';
import styles from './active-visits-row-actions.scss';
interface ActiveVisitRowActionsCellProps {
state: {
queueEntry: QueueEntry;
};
}

// table column definition containing actions user can perform to each row.
export const ActiveVisitRowActionsCell = ({ queueEntry }: QueueTableCellComponentProps) => {
// This component is meant to be mounted as an extension in the queue-table-extension-column-slot.
// Defines the following actions the user can perform on a queue entry:
// - queue / requeue (to the in-service status)
// - transfer to a different queue
// - Overflow menu action to edit patient detail
// - Overflow menu action to end patient visit
const ActiveVisitRowActionsCell = ({ state: { queueEntry } }: ActiveVisitRowActionsCellProps) => {
const { visitQueueNumberAttributeUuid } = useConfig<ConfigObject>();

const mappedQueueEntry = mapVisitQueueEntryProperties(queueEntry, visitQueueNumberAttributeUuid);
const mappedQueueEntry = mapVisitQueueEntryProperties(queueEntry as QueueEntry, visitQueueNumberAttributeUuid);

return (
<>
Expand All @@ -25,8 +35,4 @@ export const ActiveVisitRowActionsCell = ({ queueEntry }: QueueTableCellComponen
);
};

export const activeVisitActionsColumn: QueueTableColumn = (t) => ({
header: t('actions', 'Actions'),
CellComponent: ActiveVisitRowActionsCell,
getFilterableValue: null,
});
export default ActiveVisitRowActionsCell;
165 changes: 146 additions & 19 deletions packages/esm-service-queues-app/src/config-schema.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,85 @@
import { Type, restBaseUrl, validators } from '@openmrs/esm-framework';
import { Type, validators } from '@openmrs/esm-framework';
import vitalsConfigSchema, { type VitalsConfigObject } from './current-visit/visit-details/vitals-config-schema';
import biometricsConfigSchema, {
type BiometricsConfigObject,
} from './current-visit/visit-details/biometrics-config-schema';

export const configSchema = {
priorityConfigs: {
_type: Type.Array,
_element: {
_type: Type.Object,
// Not all of the columnDefinitions are used below, but they are defined anyway
// for demonstration purpose. Implementors can copy this JSON as a starting point
// to configure the queue tables
export const defaultTablesConfig: TablesConfig = {
columnDefinitions: [
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definition of default columns to show in the queue table of the service queues app main page.

{
id: 'patient-name',
columnType: 'patient-name-column',
},
_description: 'Allows customization of how specific priorities are rendered',
_default: [],
},
statusConfigs: {
_type: Type.Array,
_element: {
_type: Type.Object,
{
id: 'patient-age',
columnType: 'patient-age-column',
},
_description: 'Allows customization of how specific statuses are rendered',
_default: [],
},
{
id: 'patient-identifier',
columnType: 'patient-identifier-column',
config: {
identifierType: 'patient-identifier-uuid',
},
},
mseaton marked this conversation as resolved.
Show resolved Hide resolved
{
id: 'priority',
columnType: 'priority-column',
config: {
priorities: [
{
conceptUuid: 'priority-concept-uuid',
tagClassName: 'tag',
tagType: 'red',
},
],
},
},
{
id: 'status',
columnType: 'status-column',
config: {
statuses: [
{
conceptUuid: 'status-concept-uuid',
iconComponent: 'InProgress',
},
],
},
},
{
id: 'visit-start-time',
columnType: 'visit-start-time-column',
},
{
id: 'comingFrom',
columnType: 'queue-coming-from-column',
},
{
id: 'queue',
columnType: 'current-queue-column',
},
{
id: 'wait-time',
columnType: 'wait-time-column',
},
{
id: 'actions',
columnType: 'extension-column',
header: 'Actions',
},
],
tableDefinitions: [
{
columns: ['patient-name', 'comingFrom', 'priority', 'status', 'queue', 'wait-time', 'actions'],
appliedTo: [{ queue: null, status: null }],
},
],
};

export const configSchema = {
concepts: {
defaultPriorityConceptUuid: {
_type: Type.ConceptUuid,
Expand Down Expand Up @@ -129,11 +188,21 @@ export const configSchema = {
_default: '',
_description: 'Custom URL to load default facility if it is not in the session',
},
tablesConfig: {
_type: Type.Object,
_description: `Configurations of columns to show for the queue table.
Multiple configurations can be provided, each can be applied generally, or to tables
for a particular queue, particular status, or even particular queue+status combination.
If multiple configs are defined, the first config with matching appliedTo condition
is used.
See https://github.com/openmrs/openmrs-esm-patient-management/blob/main/packages/esm-service-queues-app/src/config-schema.ts
for full schema definition and example.
`,
_default: defaultTablesConfig,
},
};

export interface ConfigObject {
priorityConfigs: Array<PriorityConfig>;
statusConfigs: Array<StatusConfig>;
concepts: {
defaultPriorityConceptUuid: string;
defaultStatusConceptUuid: string;
Expand All @@ -157,17 +226,75 @@ export interface ConfigObject {
defaultIdentifierTypes: Array<string>;
showRecommendedVisitTypeTab: boolean;
customPatientChartUrl: string;
defaultFacilityUrl: string;
visitTypeResourceUrl: string;
tablesConfig: TablesConfig;
}

interface TablesConfig {
columnDefinitions: ColumnDefinition[];

/*
A list of table definitions. A queue table (whether it is displaying entries from a
particular queue+status combination, from a particular queue, or from multiple queues)
will determine what columns to show based on these definitions. If multiple TableDefinitions
have matching appliedTo criteria, the first one will be used.
*/
tableDefinitions: TableDefinitions[];
}

export type ColumnDefinition = {
id: string;
header?: string; // optional custom i18n translation key for the column's header; overrides the default one
headerModule?: string; // optional custom i18n translation module for the column's header. Must be used with the header option
} & (
| { columnType: 'patient-name-column' }
| { columnType: 'patient-identifier-column'; config: PatientIdentifierColumnConfig }
| { columnType: 'patient-age-column' }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could probably be optional if we want, and if the type is not supplied, there is some default behavior (eg. get the first preferred identifier, or whatever is the primary identifier in the rest response, etc)

| { columnType: 'priority-column'; config?: PriorityColumnConfig }
| { columnType: 'status-column'; config?: StatusColumnConfig }
| { columnType: 'queue-coming-from-column' }
| { columnType: 'current-queue-column' }
| { columnType: 'wait-time-column' }
| { columnType: 'visit-start-time-column' }
| { columnType: 'actions-column' }
| { columnType: 'extension-column'; config?: object } // column that contains the extension slot queue-table-extension-column-slot
);

export interface VisitAttributeQueueNumberColumnConfig {
visitQueueNumberAttributeUuid: string;
}
mseaton marked this conversation as resolved.
Show resolved Hide resolved

export interface PatientIdentifierColumnConfig {
identifierType: string; // uuid of the identifier type
}
export interface PriorityConfig {
conceptUuid: string;
tagType: string;
tagClassName: 'priorityTag' | 'tag' | null;
}

export interface PriorityColumnConfig {
priorities: PriorityConfig[];
}

export interface StatusConfig {
conceptUuid: string;
iconComponent: 'Group' | 'InProgress' | null;
}

export interface StatusColumnConfig {
statuses: StatusConfig[];
}

export interface ExtensionColumnConfig {
state: any; // state to pass into the extension
}

export interface TableDefinitions {
// a list of column ids defined in columnDefinitions
columns: string[];

// apply the columns to tables of any of the specified queue and status
// (if appliedTo is null, apply to all tables, including the one in the service queue app home page)
appliedTo?: Array<{ queue?: string; status?: string }>;
}
17 changes: 8 additions & 9 deletions packages/esm-service-queues-app/src/home.test.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import Home from './home.component';
import { useConfig } from '@openmrs/esm-framework';
import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
import { configSchema } from './config-schema';

const mockedUseConfig = useConfig as jest.Mock;

jest.mock('@openmrs/esm-framework', () => ({
...jest.requireActual('@openmrs/esm-framework'),
useConfig: jest.fn(() => ({
concepts: {
visitQueueNumberAttributeUuid: 'c61ce16f-272a-41e7-9924-4c555d0932c5',
},
})),
}));
mockedUseConfig.mockReturnValue({
...getDefaultsFromConfigSchema(configSchema),
concepts: {
visitQueueNumberAttributeUuid: 'c61ce16f-272a-41e7-9924-4c555d0932c5',
},
});

jest.mock('./helpers/helpers', () => ({
...jest.requireActual('./helpers/helpers'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { type QueueEntry, type QueueEntrySearchCriteria } from '../types';
import useSWR from 'swr';

const repString =
'custom:(uuid,display,queue,status,patient,visit:(uuid,display,encounters:(uuid,display,diagnoses,encounterDatetime,encounterType,obs,encounterProviders,voided)),priority,priorityComment,sortWeight,startedAt,endedAt,locationWaitingFor,queueComingFrom,providerWaitingFor,previousQueueEntry)';
'custom:(uuid,display,queue,status,patient,visit:(uuid,display,startDatetime,encounters:(uuid,display,diagnoses,encounterDatetime,encounterType,obs,encounterProviders,voided)),priority,priorityComment,sortWeight,startedAt,endedAt,locationWaitingFor,queueComingFrom,providerWaitingFor,previousQueueEntry)';

export function useQueueEntries(searchCriteria?: QueueEntrySearchCriteria, rep: string = repString) {
const searchParam = new URLSearchParams();
Expand Down
9 changes: 9 additions & 0 deletions packages/esm-service-queues-app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,15 @@ export const voidQueueEntryModal = getAsyncLifecycle(

export const addQueueEntry = getSyncLifecycle(addQueueEntryComponent, options);

export const activeVisitsRowActions = getAsyncLifecycle(
() => import('./active-visits/active-visits-row-actions.component'),
{
featureName:
'quick actions to queue, requeue and transfer patients. With overflow menu actions to edit patient and end visit',
moduleName,
},
);

export function startupApp() {
registerBreadcrumbs([]);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import React from 'react';
import { DefinitionTooltip, Tag } from '@carbon/react';
import styles from './queue-priority.scss';
import { type ConfigObject } from '../config-schema';
import { type PriorityConfig } from '../config-schema';
import { type Concept } from '../types';
import { useConfig } from '@openmrs/esm-framework';

interface QueuePriorityProps {
priority: Concept;
priorityComment?: string;
priorityConfigs: PriorityConfig[];
}

const QueuePriority: React.FC<QueuePriorityProps> = ({ priority, priorityComment }) => {
const { priorityConfigs } = useConfig<ConfigObject>();
const QueuePriority: React.FC<QueuePriorityProps> = ({ priority, priorityComment, priorityConfigs }) => {
const priorityConfig = priorityConfigs?.find((c) => c.conceptUuid === priority.uuid);
return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@ import React from 'react';
import { Group, InProgress } from '@carbon/react/icons';
import styles from '../queue-table/queue-table.scss';
import { type Concept, type Queue } from '../types';
import { useConfig } from '@openmrs/esm-framework';
import { type ConfigObject, type StatusConfig } from '../config-schema';
import { type StatusConfig } from '../config-schema';

interface QueueStatusProps {
status: Concept;
queue?: Queue;
statusConfigs: StatusConfig[];
}

const QueueStatus: React.FC<QueueStatusProps> = ({ status, queue }) => {
const { statusConfigs } = useConfig<ConfigObject>();
const QueueStatus: React.FC<QueueStatusProps> = ({ status, queue, statusConfigs }) => {
const statusConfig = statusConfigs?.find((c) => c.conceptUuid === status.uuid);
return (
<span className={styles.statusContainer}>
Expand Down