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-2793 add actions to transition queue entries in queue table #997

Merged
merged 6 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion __mocks__/address.mock.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export const mockAddress = {
import { type PersonAddress } from '../packages/esm-service-queues-app/src/types';

export const mockAddress: Partial<PersonAddress> = {
postalCode: '12345',
address1: '123 Main St',
cityVillage: 'City',
Expand Down
52 changes: 52 additions & 0 deletions __mocks__/patient.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { type Patient, type PersonAddress } from '../packages/esm-service-queues-app/src/types';
import { mockAddress } from './address.mock';

export const mockPatientAlice: Patient = {
uuid: '00000000-0000-0001-0000-000000000000',
display: 'Alice Johnson',
identifiers: [],
person: {
uuid: '00000000-0001-0000-0000-000000000000',
display: 'Alice Johnson',
gender: 'F',
age: 24,
birthdate: '2000-01-01T00:00:00.000+0000',
birthdateEstimated: false,
dead: false,
deathDate: null,
causeOfDeath: null,
preferredName: null,
preferredAddress: mockAddress as PersonAddress,
names: [null],
addresses: [],
attributes: [],
birthtime: null,
deathdateEstimated: null,
causeOfDeathNonCoded: null,
},
};

export const mockPatientBrian: Patient = {
uuid: '00000000-0000-0002-0000-000000000000',
display: 'Brian Johnson',
identifiers: [],
person: {
uuid: '00000000-0001-0000-0000-000000000000',
display: 'Brian Johnson',
gender: 'M',
age: 24,
birthdate: '2000-01-01T00:00:00.000+0000',
birthdateEstimated: false,
dead: false,
deathDate: null,
causeOfDeath: null,
preferredName: null,
preferredAddress: mockAddress as PersonAddress,
names: [null],
addresses: [],
attributes: [],
birthtime: null,
deathdateEstimated: null,
causeOfDeathNonCoded: null,
},
};
93 changes: 91 additions & 2 deletions __mocks__/queue-entry.mock.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,95 @@
export const mockQueueEntry = {
import {
type Concept,
type MappedQueueEntry,
type Queue,
type QueueEntry,
} from '../packages/esm-service-queues-app/src/types';
import { mockPatientAlice, mockPatientBrian } from './patient.mock';

// Priorities:
export const mockPriorityNonUrgent: Concept = {
uuid: '00000000-0000-0000-0000-000000000001',
display: 'Non urgent',
};
export const mockPriorityUrgent: Concept = {
uuid: '00000000-0000-0000-0000-000000000002',
display: 'Urgent',
};

// Statuses:
export const mockStatusWaiting: Concept = {
uuid: '00000000-0000-0000-0000-000000000010',
display: 'Waiting',
};
export const mockStatusInService: Concept = {
uuid: '00000000-0000-0000-0000-000000000020',
display: 'In Service',
};
export const mockStatusWaitingForTransfer: Concept = {
uuid: '00000000-0000-0000-0000-000000000030',
display: 'Waiting for Transfer',
};

// Queues:
export const mockQueueTriage: Queue = {
uuid: '00000000-0000-0000-0001-000000000000',
display: 'Triage',
name: 'Triage',
description: '',
allowedPriorities: [mockPriorityNonUrgent, mockPriorityUrgent],
allowedStatuses: [mockStatusWaiting, mockStatusInService],
};

export const mockQueueSurgery: Queue = {
uuid: '00000000-0000-0000-0002-000000000000',
display: 'Surgery',
name: 'Surgery',
description: '',
allowedPriorities: [mockPriorityNonUrgent, mockPriorityUrgent],
allowedStatuses: [mockStatusWaiting, mockStatusInService, mockStatusWaitingForTransfer],
};

export const mockQueues = [mockQueueTriage, mockQueueSurgery];

// Queues Entries:
export const mockQueueEntryBrian: QueueEntry = {
uuid: '8824d1e4-8513-4a78-bcec-37173f417f18',
display: mockPatientBrian.display,
endedAt: null,
locationWaitingFor: null,
patient: mockPatientBrian,
priority: mockPriorityNonUrgent,
priorityComment: null,
providerWaitingFor: null,
queue: mockQueueTriage,
startedAt: '2024-01-01T00:00:00.000+0000',
status: mockStatusWaiting,
visit: null,
sortWeight: 0,
queueComingFrom: null,
};

export const mockQueueEntryAlice: QueueEntry = {
uuid: '00000000-8513-4a78-bcec-37173f417f18',
display: mockPatientAlice.display,
endedAt: null,
locationWaitingFor: null,
patient: mockPatientAlice,
priority: mockPriorityNonUrgent,
priorityComment: null,
providerWaitingFor: null,
queue: mockQueueSurgery,
startedAt: '2024-01-02T00:00:00.000+0000',
status: mockStatusWaiting,
visit: null,
sortWeight: 0,
queueComingFrom: mockQueueTriage,
};

export const mockQueueEntries = [mockQueueEntryBrian, mockQueueEntryAlice];

export const mockMappedQueueEntry: MappedQueueEntry = {
id: '8824d1e4-8513-4a78-bcec-37173f417f18',
encounters: [],
name: 'Brian Johnson',
patientAge: '32',
patientSex: 'F',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import userEvent from '@testing-library/user-event';
import { screen, render, within } from '@testing-library/react';
import { mockServices, mockPriorities, mockStatus, mockSession, mockLocations, mockQueueEntry } from '__mocks__';
import { mockServices, mockPriorities, mockStatus, mockSession, mockLocations, mockMappedQueueEntry } from '__mocks__';
import { type ConfigObject, showSnackbar, useConfig } from '@openmrs/esm-framework';
import { updateQueueEntry } from './active-visits-table.resource';
import ChangeStatus from './change-status-dialog.component';
Expand Down Expand Up @@ -53,7 +53,7 @@ describe('Queue entry details', () => {
it('should update a queue entry and display toast message', async () => {
const user = userEvent.setup();

mockUpdateQueueEntry.mockResolvedValueOnce({ data: mockQueueEntry, status: 201, statusText: 'Updated' });
mockUpdateQueueEntry.mockResolvedValueOnce({ data: mockMappedQueueEntry, status: 201, statusText: 'Updated' });

renderUpdateQueueEntryDialog();
expect(screen.getByText(/queue service/i)).toBeInTheDocument();
Expand Down Expand Up @@ -102,5 +102,5 @@ describe('Queue entry details', () => {
});

const renderUpdateQueueEntryDialog = () => {
render(<ChangeStatus queueEntry={mockQueueEntry} closeModal={() => false} />);
render(<ChangeStatus queueEntry={mockMappedQueueEntry} closeModal={() => false} />);
};
7 changes: 5 additions & 2 deletions packages/esm-service-queues-app/src/helpers/useQueues.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { openmrsFetch } from '@openmrs/esm-framework';
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
import useSWRImmutable from 'swr/immutable';
import { type QueueServiceInfo } from '../types';

// deprecated. use useQueues from ../hooks
export function useQueues(locationUuid?: string) {
const apiUrl = '/ws/rest/v1/queue' + (locationUuid ? `?location=${locationUuid}` : '');
const customRepresentation =
'custom:(uuid,display,name,description,allowedPriorities:(uuid,display),allowedStatuses:(uuid,display),location:(uuid,display))';
const apiUrl = `${restBaseUrl}/queue?v=${customRepresentation}` + (locationUuid ? `&location=${locationUuid}` : '');

const { data } = useSWRImmutable<{ data: { results: Array<QueueServiceInfo> } }, Error>(apiUrl, openmrsFetch);

Expand Down
14 changes: 14 additions & 0 deletions packages/esm-service-queues-app/src/hooks/useMutateQueueEntries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { restBaseUrl } from '@openmrs/esm-framework';
import { useSWRConfig } from 'swr';

export function useMutateQueueEntries() {
const { mutate } = useSWRConfig();
const mutateQueueEntries = () =>
mutate((key) => {
return typeof key === 'string' && key.startsWith(`${restBaseUrl}/queue-entry`);
});

return {
mutateQueueEntries,
};
}
4 changes: 2 additions & 2 deletions packages/esm-service-queues-app/src/hooks/useQueueEntries.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { openmrsFetch } from '@openmrs/esm-framework';
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
import { type QueueEntry, type QueueEntrySearchCriteria } from '../types';
import useSWR from 'swr';

Expand All @@ -9,7 +9,7 @@ export function useQueueEntries(searchCriteria?: QueueEntrySearchCriteria, rep:
}
searchParam.append('v', rep);

const apiUrl = `/ws/rest/v1/queue-entry?` + searchParam.toString();
const apiUrl = `${restBaseUrl}/queue-entry?` + searchParam.toString();
const { data, ...rest } = useSWR<{ data: { results: Array<QueueEntry> } }, Error>(apiUrl, openmrsFetch);

return {
Expand Down
6 changes: 3 additions & 3 deletions packages/esm-service-queues-app/src/hooks/useQueues.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { openmrsFetch } from '@openmrs/esm-framework';
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
import useSWRImmutable from 'swr/immutable';
import { type Queue } from '../types';

export function useQueues(locationUuid?: string) {
const customRepresentation =
'custom:(uuid,display,name,description,allowedPriorities:(uuid,display),allowedStatuses:(uuid,display))';
const apiUrl = `/ws/rest/v1/queue?v=${customRepresentation}` + (locationUuid ? `&location=${locationUuid}` : '');
'custom:(uuid,display,name,description,allowedPriorities:(uuid,display),allowedStatuses:(uuid,display),location:(uuid,display))';
const apiUrl = `${restBaseUrl}/queue?v=${customRepresentation}` + (locationUuid ? `&location=${locationUuid}` : '');

const { data, ...rest } = useSWRImmutable<{ data: { results: Array<Queue> } }, Error>(apiUrl, openmrsFetch);

Expand Down
8 changes: 8 additions & 0 deletions packages/esm-service-queues-app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ export const addProviderToRoomModal = getAsyncLifecycle(
},
);

export const transitionQueueEntryModal = getAsyncLifecycle(
() => import('./queue-table/transitions/transition-queue-entry-modal.component'),
{
featureName: 'transfer patient to a different queue',
moduleName,
},
);

export const addQueueEntry = getSyncLifecycle(addQueueEntryComponent, options);

export function startupApp() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Button, OverflowMenu, OverflowMenuItem } from '@carbon/react';
import { showModal } from '@openmrs/esm-framework';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { type QueueTableCellComponentProps, type QueueTableColumn } from '../../types';

export function QueueTableTransitionCell({ queueEntry }: QueueTableCellComponentProps) {
const { t } = useTranslation();

return (
<div>
<Button
kind="ghost"
aria-label="Actions"
onClick={() => {
const dispose = showModal('transition-queue-entry-modal', {
closeModal: () => dispose(),
queueEntry,
});
}}>
{t('transition', 'Transition')}
</Button>
</div>
);
}

export const queueTableTransitionColumn: QueueTableColumn = {
headerI18nKey: '',
CellComponent: QueueTableTransitionCell,
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { queueTableStatusColumn } from './cells/queue-table-status-cell.componen
import { queueTableWaitTimeColumn } from './cells/queue-table-wait-time-cell.component';
import { QueueTableByStatusSkeleton } from './queue-table-by-status-skeleton.component';
import QueueTable from './queue-table.component';
import { queueTableTransitionColumn } from './cells/queue-table-transition-cell.component';

interface QueueTableByStatusProps {
selectedQueue: Queue; // the selected queue
Expand All @@ -25,13 +26,14 @@ interface QueueTableByStatusProps {
allStatusTabConfig?: QueueTableTabConfig; // If provided, we display an additional tab for *all* statuses with the given config
}

const defaultQueueTableConfig: QueueTableTabConfig = {
export const defaultQueueTableConfig: QueueTableTabConfig = {
columns: [
queueTableNameColumn,
queueTableComingFromColumn,
queueTablePriorityColumn,
queueTableStatusColumn,
queueTableWaitTimeColumn,
queueTableTransitionColumn,
],
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function QueueTable({ queueEntries, queueTableColumns }: QueueTableProps) {
}) ?? [];

return (
<DataTable rows={rowsData} headers={headers}>
<DataTable rows={rowsData} headers={headers} useZebraStyles>
{({ rows, headers, getTableProps, getHeaderProps, getRowProps, getToolbarProps, onInputChange }) => (
<TableContainer className={styles.tableContainer}>
<TableToolbar {...getToolbarProps()}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { defineConfigSchema, usePagination, useSession } from '@openmrs/esm-framework';
import { screen, within } from '@testing-library/react';
import { mockQueueEntries, mockSession } from '__mocks__';
import React from 'react';
import { renderWithSwr } from 'tools';
import { defaultQueueTableConfig } from './queue-table-by-status.component';
import QueueTable from './queue-table.component';
import { configSchema } from '../config-schema';

const mockUsePagination = usePagination as jest.Mock;
const mockGoToPage = jest.fn();
const mockUseSession = useSession as jest.Mock;

describe('QueueTable: ', () => {
beforeAll(() => {
defineConfigSchema('@openmrs/esm-service-queues-app', configSchema);
});

beforeEach(() => {
mockUseSession.mockReturnValue(mockSession);
});

it('renders an empty table with default columns when there are no queue entries', () => {
renderWithSwr(<QueueTable queueEntries={[]} queueTableColumns={defaultQueueTableConfig.columns} />);

const rows = screen.queryAllByRole('row');
expect(rows).toHaveLength(1); // should only have the header row

const headerRow = rows[0];
for (const column of defaultQueueTableConfig.columns) {
if (column.headerI18nKey) {
expect(within(headerRow).getByText(column.headerI18nKey)).toBeInTheDocument();
}
}
});

it('renders queue entries with default columns', () => {
mockUsePagination.mockReturnValue({
results: mockQueueEntries,
goTo: mockGoToPage,
currentPage: 1,
});

renderWithSwr(<QueueTable queueEntries={mockQueueEntries} queueTableColumns={defaultQueueTableConfig.columns} />);

for (const entry of mockQueueEntries) {
const patientName = entry.patient.display;
const row = screen.getByText(patientName).closest('tr');

expect(within(row).getByText(entry.status.display)).toBeInTheDocument();
expect(within(row).getByText(entry.priority.display)).toBeInTheDocument();
}
});
});
Loading