Skip to content

Commit

Permalink
(feat) Visual tweaks to the service queues ChangeStatus modal
Browse files Browse the repository at this point in the history
  • Loading branch information
denniskigen committed Sep 21, 2024
1 parent 691f65b commit 6f9faff
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 146 deletions.
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
import React, { useMemo } from 'react';
import {
Button,
ContentSwitcher,
Form,
InlineLoading,
InlineNotification,
ModalBody,
ModalFooter,
ModalHeader,
Form,
ContentSwitcher,
Switch,
Select,
SelectItem,
InlineNotification,
RadioButton,
RadioButtonGroup,
InlineLoading,
Select,
SelectItem,
Stack,
Switch,
} from '@carbon/react';
import { useTranslation } from 'react-i18next';
import { navigate, showSnackbar, useConfig } from '@openmrs/esm-framework';
import { type MappedQueueEntry } from '../types';
import { updateQueueEntry } from './active-visits-table.resource';
import { useQueueLocations } from '../patient-search/hooks/useQueueLocations';
import styles from './change-status-dialog.scss';
import { useQueues } from '../hooks/useQueues';
import { useForm, Controller } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { navigate, showSnackbar, useConfig } from '@openmrs/esm-framework';
import { type MappedQueueEntry } from '../types';
import { type ConfigObject } from '../config-schema';
import { useQueues } from '../hooks/useQueues';
import { updateQueueEntry } from './active-visits-table.resource';
import { useMutateQueueEntries } from '../hooks/useQueueEntries';
import { useQueueLocations } from '../patient-search/hooks/useQueueLocations';
import styles from './change-status-dialog.scss';

interface ChangeStatusDialogProps {
queueEntry: MappedQueueEntry;
Expand All @@ -41,9 +42,9 @@ const ChangeStatus: React.FC<ChangeStatusDialogProps> = ({ queueEntry, closeModa
() =>
z.object({
location: z.string({ required_error: t('queueLocationRequired', 'Queue location is required') }),
priority: z.string({ required_error: t('priorityIsRequired', 'Priority is required') }),
service: z.string({ required_error: t('serviceIsRequired', 'Service is required') }),
status: z.string({ required_error: t('statusIsRequired', 'Status is required') }),
priority: z.string({ required_error: t('priorityIsRequired', 'Priority is required') }),
}),
[],
);
Expand Down Expand Up @@ -82,18 +83,16 @@ const ChangeStatus: React.FC<ChangeStatusDialogProps> = ({ queueEntry, closeModa
endDate,
sortWeight,
).then(
({ status }) => {
if (status === 201) {
showSnackbar({
isLowContrast: true,
title: t('updateEntry', 'Update entry'),
kind: 'success',
subtitle: t('queueEntryUpdateSuccessfully', 'Queue Entry Updated Successfully'),
});
closeModal();
mutateQueueEntries();
navigate({ to: `${window.spaBase}/home/service-queues` });
}
() => {
showSnackbar({
isLowContrast: true,
title: t('updateEntry', 'Update entry'),
kind: 'success',
subtitle: t('queueEntryUpdateSuccessfully', 'Queue Entry Updated Successfully'),
});
closeModal();
mutateQueueEntries();
navigate({ to: `${window.spaBase}/home/service-queues` });
},
(error) => {
showSnackbar({
Expand Down Expand Up @@ -121,127 +120,131 @@ const ChangeStatus: React.FC<ChangeStatusDialogProps> = ({ queueEntry, closeModa
<ModalBody>
<div className={styles.modalBody}>
<h5>
{queueEntry.name} &nbsp; · &nbsp;{queueEntry.patientSex} &nbsp; · &nbsp;{queueEntry.patientAge}&nbsp;
{t('years', 'Years')}
{t('patientInfo', '{{name}}{{sexInfo}}{{ageInfo}}', {
name: queueEntry.name,
sexInfo: queueEntry.patientSex ? ` · ${queueEntry.patientSex} · ` : '',
ageInfo: queueEntry.patientAge ? `${queueEntry.patientAge} ${t('years', 'Years')}` : '',
})}
</h5>
</div>
<section>
<Controller
name="location"
control={control}
render={({ field: { onChange, value } }) => (
<Select
labelText={t('selectQueueLocation', 'Select a queue location')}
id="location"
invalid={!!errors.location}
invalidText={errors.location?.message}
value={value}
onChange={(event) => {
onChange(event.target.value);
}}>
{!getValues()?.location && (
<SelectItem text={t('selectQueueLocation', 'Select a queue location')} value="" />
)}
{queueLocations?.length > 0 &&
queueLocations.map((location) => (
<SelectItem key={location.id} text={location.name} value={location.id}>
{location.name}
</SelectItem>
))}
</Select>
)}
/>
</section>

<section className={styles.section}>
<div className={styles.sectionTitle}>{t('queueService', 'Queue service')}</div>
<Controller
name="service"
control={control}
render={({ field: { onChange, value } }) => (
<Select
labelText={t('selectService', 'Select a service')}
id="service"
invalid={!!errors.service}
invalidText={errors.service?.message}
value={value}
onChange={(event) => onChange(event.target.value)}>
{!getValues()?.service && <SelectItem text={t('selectService', 'Select a service')} value="" />}
{queues?.length > 0 &&
queues.map((service) => (
<SelectItem key={service.uuid} text={service.display} value={service.uuid}>
{service.display}
</SelectItem>
))}
</Select>
)}
/>
</section>

<section className={styles.section}>
<div className={styles.sectionTitle}>{t('queueStatus', 'Queue status')}</div>
{!allowedStatuses?.length ? (
<InlineNotification
className={styles.inlineNotification}
kind={'error'}
lowContrast
subtitle={t('configureStatus', 'Please configure status to continue.')}
title={t('noStatusConfigured', 'No status configured')}
/>
) : (
<Stack gap={4}>
<section>
<div className={styles.sectionTitle}>{t('queueLocation', 'Queue location')}</div>
<Controller
name="status"
name="location"
control={control}
render={({ field: { value, onChange } }) => (
<RadioButtonGroup
className={styles.radioButtonWrapper}
name="status"
invalid={!!errors.status}
invalidText={errors.status?.message}
defaultSelected={value}
onChange={(uuid) => {
onChange(uuid);
render={({ field: { onChange, value } }) => (
<Select
labelText={t('selectQueueLocation', 'Select a queue location')}
id="location"
invalid={!!errors.location}
invalidText={errors.location?.message}
value={value}
onChange={(event) => {
onChange(event.target.value);
}}>
{allowedStatuses?.length > 0 &&
allowedStatuses.map(({ uuid, display }) => (
<RadioButton key={uuid} labelText={display} value={uuid} />
{!getValues()?.location && (
<SelectItem text={t('selectQueueLocation', 'Select a queue location')} value="" />
)}
{queueLocations?.length > 0 &&
queueLocations.map((location) => (
<SelectItem key={location.id} text={location.name} value={location.id}>
{location.name}
</SelectItem>
))}
</RadioButtonGroup>
</Select>
)}
/>
)}
</section>

<section className={styles.section}>
<div className={styles.sectionTitle}>{t('queuePriority', 'Queue priority')}</div>
<Controller
control={control}
name="priority"
render={({ field: { onChange } }) => (
<>
<ContentSwitcher
size="sm"
selectedIndex={1}
onChange={(event) => {
onChange(event.name as any);
}}>
{allowedPriorities?.length > 0 ? (
allowedPriorities.map(({ uuid, display }) => {
return <Switch name={uuid} text={display} key={uuid} value={uuid} />;
})
) : (
<Switch
name={t('noPriorityFound', 'No priority found')}
text={t('noPriorityFound', 'No priority found')}
value={null}
/>
)}
</ContentSwitcher>
{errors.priority && <div className={styles.error}>{errors.priority.message}</div>}
</>
</section>
<section className={styles.section}>
<div className={styles.sectionTitle}>{t('queueService', 'Queue service')}</div>
<Controller
name="service"
control={control}
render={({ field: { onChange, value } }) => (
<Select
labelText={t('selectService', 'Select a service')}
id="service"
invalid={!!errors.service}
invalidText={errors.service?.message}
value={value}
onChange={(event) => onChange(event.target.value)}>
{!getValues()?.service && <SelectItem text={t('selectService', 'Select a service')} value="" />}
{queues?.length > 0 &&
queues.map((service) => (
<SelectItem key={service.uuid} text={service.display} value={service.uuid}>
{service.display}
</SelectItem>
))}
</Select>
)}
/>
</section>
<section className={styles.section}>
<div className={styles.sectionTitle}>{t('queueStatus', 'Queue status')}</div>
{!allowedStatuses?.length ? (
<InlineNotification
className={styles.inlineNotification}
kind={'error'}
lowContrast
subtitle={t('configureStatus', 'Please configure status to continue.')}
title={t('noStatusConfigured', 'No status configured')}
/>
) : (
<Controller
name="status"
control={control}
render={({ field: { value, onChange } }) => (
<RadioButtonGroup
className={styles.radioButtonWrapper}
id="status"
name="status"
invalid={!!errors.status}
invalidText={errors.status?.message}
defaultSelected={value}
onChange={(uuid) => {
onChange(uuid);
}}>
{allowedStatuses?.length > 0 &&
allowedStatuses.map(({ uuid, display }) => (
<RadioButton key={uuid} labelText={display} value={uuid} />
))}
</RadioButtonGroup>
)}
/>
)}
/>
</section>
</section>
<section className={styles.section}>
<div className={styles.sectionTitle}>{t('queuePriority', 'Queue priority')}</div>
<Controller
control={control}
name="priority"
render={({ field: { onChange } }) => (
<>
<ContentSwitcher
size="sm"
selectedIndex={1}
onChange={(event) => {
onChange(event.name as any);
}}>
{allowedPriorities?.length > 0 ? (
allowedPriorities.map(({ uuid, display }) => {
return <Switch name={uuid} text={display} key={uuid} value={uuid} />;
})
) : (
<Switch
name={t('noPriorityFound', 'No priority found')}
text={t('noPriorityFound', 'No priority found')}
value={null}
/>
)}
</ContentSwitcher>
{errors.priority && <div className={styles.error}>{errors.priority.message}</div>}
</>
)}
/>
</section>
</Stack>
</ModalBody>
<ModalFooter>
<Button kind="secondary" onClick={closeModal}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jest.mock('../hooks/useQueues', () => {
};
});

describe('Queue entry details', () => {
describe('ChangeStatusDialog', () => {
beforeEach(() => {
mockUseConfig.mockReturnValue({
...getDefaultsFromConfigSchema(configSchema),
Expand All @@ -62,14 +62,14 @@ describe('Queue entry details', () => {
expect(screen.getByText(/queue service/i)).toBeInTheDocument();
expect(screen.getByText(/queue priority/i)).toBeInTheDocument();

// user selects a service
const queueServiceTypes = screen.getByRole('combobox', { name: /select a service/i });
await user.selectOptions(queueServiceTypes, '176052c7-5fd4-4b33-89cc-7bae6848c65a');

// user selects queue location
const queueLocation = screen.getByRole('combobox', { name: /Select a queue location/i });
await user.selectOptions(queueLocation, 'some-uuid1');

// user selects a service
const queueServiceTypes = screen.getByRole('combobox', { name: /select a service/i });
await user.selectOptions(queueServiceTypes, '176052c7-5fd4-4b33-89cc-7bae6848c65a');

// user selects queue status
const queueStatus = screen.getByRole('radio', { name: /Waiting/i });
await user.click(queueStatus);
Expand Down
7 changes: 4 additions & 3 deletions packages/esm-service-queues-app/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
"patientAlreadyInQueue": "Patient is already in the queue",
"patientAttendingService": "Patient attending service",
"patientHasActiveVisit": "The patient already has an active visit",
"patientInfo": "{{name}}{{sexInfo}}{{ageInfo}}",
"patientList": "Patient list",
"patientName": "Patient name",
"patientNotInQueue": "The patient is not in the queue",
Expand Down Expand Up @@ -203,7 +204,7 @@
"queueEntryTransitionUndoSuccessful": "Queue entry transition undo success",
"queueEntryUpdateFailed": "Error updating queue entry status",
"queueEntryUpdateSuccessfully": "Queue Entry Updated Successfully",
"queueLocation": "Queue Location",
"queueLocation": "Queue location",
"queueLocationRequired": "Queue location is required",
"queueName": "Queue name",
"queuePriority": "Queue priority",
Expand All @@ -212,7 +213,7 @@
"queueRoomAddFailed": "Error adding queue room",
"queueRoomName": "Queue room name",
"queueRoomUpdatedSuccessfully": "Queue room updated successfully",
"queuesClearedSuccessfully": "Queues Cleared Successfully",
"queuesClearedSuccessfully": "Queues cleared successfully",
"queueScreen": "Queue screen",
"queueService": "Queue service",
"queueStatus": "Queue status",
Expand Down Expand Up @@ -254,7 +255,7 @@
"serve": "Serve",
"servePatient": "Serve patient",
"service": "Service",
"serviceIsRequired": "Status is required",
"serviceIsRequired": "Service is required",
"serviceQueue": "Service queue",
"serviceQueues": "Service queues",
"sex": "Sex",
Expand Down

0 comments on commit 6f9faff

Please sign in to comment.