Skip to content

Commit

Permalink
(draft) - O3-3096 PoC configuration-driven queue table
Browse files Browse the repository at this point in the history
  • Loading branch information
chibongho committed Apr 26, 2024
1 parent f3692db commit d772cb3
Show file tree
Hide file tree
Showing 21 changed files with 411 additions and 201 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import TransitionMenu from '../queue-entry-table-components/transition-entry.component';
import { type QueueTableCellComponentProps, type QueueTableColumn } from '../types';
import { type QueueTableColumnFunction, type QueueTableCellComponentProps, type QueueTableColumn } 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';
Expand All @@ -25,8 +25,9 @@ export const ActiveVisitRowActionsCell = ({ queueEntry }: QueueTableCellComponen
);
};

export const activeVisitActionsColumn: QueueTableColumn = (t) => ({
header: t('actions', 'Actions'),
export const activeVisitActionsColumn: QueueTableColumnFunction = (key, header) => ({
key,
header,
CellComponent: ActiveVisitRowActionsCell,
getFilterableValue: null,
});
106 changes: 104 additions & 2 deletions packages/esm-service-queues-app/src/config-schema.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,60 @@
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 defaultTablesConfig: TablesConfig = {
columnDefinitions: [
{
id: 'patient-name',
columnType: 'patient-name-column',
},
{
id: 'patient-id',
columnType: 'patient-id-column',
config: {
identifierType: 'openmrs-id-uuid',
},
},
{
id: 'priority',
columnType: 'priority-column',
config: [],
},
{
id: 'comingFrom',
columnType: 'queue-coming-from-column',
},
{
id: 'status',
columnType: 'status-column',
config: [],
},
{
id: 'queue',
columnType: 'current-queue-column',
},
{
id: 'wait-time',
columnType: 'wait-time-column',
},
{
id: 'actions',
columnType: 'active-visits-actions-column',
},
],
tableDefinitions: [
{
columns: ['patient-name', 'priority', 'comingFrom', 'status', 'wait-time', 'actions'],
appliedTo: { queue: '3113f164-68f0-11ee-ab8d-0242ac120002' },
},
{
columns: ['patient-name', 'patient-id', 'comingFrom', 'priority', 'status', 'queue', 'wait-time', 'actions'],
},
],
};

export const configSchema = {
priorityConfigs: {
_type: Type.Array,
Expand Down Expand Up @@ -129,6 +180,11 @@ export const configSchema = {
_default: '',
_description: 'Custom URL to load default facility if it is not in the session',
},
tablesConfig: {
_type: Type.Object,
_description: 'TODO',
_default: defaultTablesConfig,
},
};

export interface ConfigObject {
Expand Down Expand Up @@ -157,17 +213,63 @@ 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; // custom translation key for the column's header; overrides the default one
} & (
| { columnType: 'patient-name-column' }
| { columnType: 'patient-id-column'; config: PatientIdColumnConfig }
| { columnType: 'patient-age-column' }
| { 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: 'active-visits-actions-column' }
| { columnType: 'extension-column'; config: object }
);

export interface VisitAttributeQueueNumberColmnConfig {
visitQueueNumberAttributeUuid: string;
}

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

export type PriorityColumnConfig = PriorityConfig[];

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

export type StatusColumnConfig = StatusConfig[];

export interface TableDefinitions {
columns: string[]; // a list of columnIds
appliedTo?: { 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
@@ -0,0 +1,93 @@
import { type TFunction, useTranslation } from 'react-i18next';
import { type QueueTableColumn } from '../../types';
import { queueTableNameColumn } from './queue-table-name-cell.component';
import { queueTablePatientIdColumn } from './queue-table-patient-id-cell.component';
import { queueTablePriorityColumn } from './queue-table-priority-cell.component';
import { queueTableStatusColumn } from './queue-table-status-cell.component';
import { queueTableComingFromColumn } from './queue-table-coming-from-cell.component';
import { queueTableQueueColumn } from './queue-table-queue-cell.component';
import { queueTableWaitTimeColumn } from './queue-table-wait-time-cell.component';
import { useMemo } from 'react';
import { type ColumnDefinition, type ConfigObject } from '../../config-schema';
import { useConfig } from '@openmrs/esm-framework';
import { activeVisitActionsColumn } from '../../active-visits/active-visits-row-actions.component';

// returns the columns to display for a queue table of a particular queue + status.
// For a table displaying all entries of a particular queue, the status param should be null
// For a table displaying all entries from all queues, both params should be null
export function useColumns(queue: string, status: string): QueueTableColumn[] {
const { t } = useTranslation();
const config = useConfig<ConfigObject>();
const { tablesConfig } = config;
const { columnDefinitions, tableDefinitions } = tablesConfig;

const columnsMap = useMemo(() => {
const map = new Map<string, QueueTableColumn>();
for (const columnDef of columnDefinitions) {
map.set(columnDef.id, getColumnFromDefinition(t, columnDef));
}
return map;
}, [columnDefinitions, t]);

const tableDefinition = useMemo(
() =>
tableDefinitions.find((tableDef) => {
const appliedTo = tableDef.appliedTo ?? { queue: null, status: null };
return (
(appliedTo.queue == null || appliedTo.queue == queue) &&
(appliedTo.status == null || appliedTo.status == status)
);
}),
[tableDefinitions, queue, status],
);

const columns = tableDefinition?.columns?.map((column) => columnsMap.get(column));
return columns;
}

function getColumnFromDefinition(t: TFunction, columnDef: ColumnDefinition): QueueTableColumn {
const { id, header, columnType } = columnDef;

switch (columnType) {
case 'patient-name-column': {
return queueTableNameColumn(id, header ?? t('name', 'Name'));
}
case 'patient-id-column': {
return queueTablePatientIdColumn(id, header ?? t('patientId', 'Patient Id'), columnDef.config);
}
case 'patient-age-column': {
return null; // TODO
}
case 'priority-column': {
return queueTablePriorityColumn(id, header ?? t('priority', 'Priority'), columnDef.config);
}
case 'status-column': {
return queueTableStatusColumn(id, header ?? t('status', 'Status'), columnDef.config);
}
case 'queue-coming-from-column': {
return queueTableComingFromColumn(id, header ?? t('comingFrom', 'Coming from'));
}
case 'current-queue-column': {
return queueTableQueueColumn(id, header ?? t('queue', 'Queue'));
}
case 'wait-time-column': {
return queueTableWaitTimeColumn(id, header ?? t('waitTime', 'Wait time'));
}
case 'visit-start-time-column': {
return null; // TODO
}
case 'active-visits-actions-column': {
return activeVisitActionsColumn(id, header ?? t('actions', 'Actions'));
}
case 'actions-column': {
return null; // TODO: a more configurable actions column to define quick actions and actions in overflow menu
}
case 'extension-column': {
return null; // TODO: this is a column that only has an extension slot
// it can do whatever it needs to based on columnDef.config
}
default: {
throw new Error('Unknown column type from configuration: ' + columnType);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { Button } from '@carbon/react';
import { showModal } from '@openmrs/esm-framework';
import { useTranslation } from 'react-i18next';
import { type QueueTableCellComponentProps, type QueueTableColumn } from '../../types';
import { type QueueTableColumnFunction, type QueueTableCellComponentProps } from '../../types';

export function QueueTableActionCell({ queueEntry }: QueueTableCellComponentProps) {
const { t } = useTranslation();
Expand Down Expand Up @@ -60,8 +60,9 @@ export function QueueTableActionCell({ queueEntry }: QueueTableCellComponentProp
);
}

export const queueTableActionColumn: QueueTableColumn = (t) => ({
header: t('actions', 'Actions'),
export const queueTableActionColumn: QueueTableColumnFunction = (key, header) => ({
key,
header,
CellComponent: QueueTableActionCell,
getFilterableValue: null,
});
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React from 'react';
import { type QueueTableCellComponentProps, type QueueTableColumn } from '../../types';
import { type QueueTableColumnFunction, type QueueTableCellComponentProps } from '../../types';

export const QueueTableComingFromCell = ({ queueEntry }: QueueTableCellComponentProps) => {
return <>{queueEntry.queueComingFrom?.display}</>;
};

export const queueTableComingFromColumn: QueueTableColumn = (t) => ({
header: t('queueComingFrom', 'Coming from'),
export const queueTableComingFromColumn: QueueTableColumnFunction = (key, header) => ({
key,
header,
CellComponent: QueueTableComingFromCell,
getFilterableValue: (queueEntry) => queueEntry.queueComingFrom?.display,
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { ConfigurableLink, useConfig } from '@openmrs/esm-framework';
import { type ConfigObject } from '../../config-schema';
import { type QueueTableCellComponentProps, type QueueTableColumn } from '../../types';
import { type QueueTableColumnFunction, type QueueTableCellComponentProps } from '../../types';

export const QueueTableNameCell = ({ queueEntry }: QueueTableCellComponentProps) => {
const { customPatientChartUrl } = useConfig<ConfigObject>();
Expand All @@ -12,8 +12,9 @@ export const QueueTableNameCell = ({ queueEntry }: QueueTableCellComponentProps)
);
};

export const queueTableNameColumn: QueueTableColumn = (t) => ({
header: t('name', 'Name'),
export const queueTableNameColumn: QueueTableColumnFunction = (key, header) => ({
key,
header,
CellComponent: QueueTableNameCell,
getFilterableValue: (queueEntry) => queueEntry.patient.person.display,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import { type PatientIdColumnConfig } from '../../config-schema';
import { type QueueEntry, type QueueTableColumnFunction, type QueueTableCellComponentProps } from '../../types';

export const queueTablePatientIdColumn: QueueTableColumnFunction = (key, header, config: PatientIdColumnConfig) => {
const { identifierType } = config;

const getPatientId = (queueEntry: QueueEntry) => {
for (const identifier of queueEntry.patient.identifiers) {
if (identifier.identifierType?.uuid == identifierType) {
return identifier.identifier;
}
}
return null;
};

const QueueTablePatientIdCell = ({ queueEntry }: QueueTableCellComponentProps) => {
return <span>{getPatientId(queueEntry)}</span>;
};

return {
key,
header,
CellComponent: QueueTablePatientIdCell,
getFilterableValue: getPatientId,
};
};
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React from 'react';
import QueuePriority from '../../queue-entry-table-components/queue-priority.component';
import { type QueueTableCellComponentProps, type QueueTableColumn } from '../../types';
import { type QueueTableColumnFunction, type QueueTableCellComponentProps } from '../../types';

export const QueueTablePriorityCell = ({ queueEntry }: QueueTableCellComponentProps) => {
return <QueuePriority priority={queueEntry.priority} priorityComment={queueEntry.priorityComment} />;
};

export const queueTablePriorityColumn: QueueTableColumn = (t) => ({
header: t('priority', 'Priority'),
export const queueTablePriorityColumn: QueueTableColumnFunction = (key, header) => ({
key,
header,
CellComponent: QueueTablePriorityCell,
getFilterableValue: (queueEntry) => queueEntry.priority.display,
});

0 comments on commit d772cb3

Please sign in to comment.