diff --git a/packages/esm-service-queues-app/src/patient-queue-metrics/metrics-header.component.tsx b/packages/esm-service-queues-app/src/patient-queue-metrics/metrics-header.component.tsx index 96f590afd..09192ad80 100644 --- a/packages/esm-service-queues-app/src/patient-queue-metrics/metrics-header.component.tsx +++ b/packages/esm-service-queues-app/src/patient-queue-metrics/metrics-header.component.tsx @@ -42,7 +42,7 @@ const MetricsHeader = () => { menuAlignment="bottom-end" onClick={navigateToQueueScreen} size={isDesktop(layout) ? 'sm' : 'lg'} - tooltipAlignment="top-end"> + tooltipAlignment="left"> ({ @@ -37,31 +40,88 @@ describe('QueueServiceForm', () => { mockUseLayoutType.mockReturnValue('tablet'); }); - it('should display required error messages when form is submitted with missing fields', async () => { + it('renders validation errors when form is submitted with missing fields', async () => { const user = userEvent.setup(); - render(); - const submitButton = screen.getByText('Save'); - await user.click(submitButton); - expect(screen.getByText('Queue name is required')).toBeInTheDocument(); + const queueNameInput = screen.getByRole('textbox', { name: /queue name/i }); + const serviceTypeSelect = screen.getByRole('combobox', { name: /select a service type/i }); + const locationSelect = screen.getByRole('combobox', { name: /select a location/i }); + const cancelButton = screen.getByRole('button', { name: /cancel/i }); + const saveButton = screen.getByRole('button', { name: /save/i }); + expect(cancelButton).toBeInTheDocument(); + expect(saveButton).toBeInTheDocument(); + expect(queueNameInput).toBeInTheDocument(); + expect(queueNameInput).not.toBeInvalid(); + expect(serviceTypeSelect).toBeInTheDocument(); + expect(serviceTypeSelect).not.toBeInvalid(); + + await user.click(saveButton); + expect(queueNameInput).toBeInvalid(); + + await user.type(queueNameInput, 'Test Queue'); + expect(queueNameInput).not.toBeInvalid(); + expect(serviceTypeSelect).toBeInvalid(); + + await user.selectOptions(serviceTypeSelect, '6f017eb0-b035-4acd-b284-da45f5067502'); + await user.selectOptions(locationSelect, '34567eb0-b035-4acd-b284-da45f5067502'); + await user.click(saveButton); + + expect(serviceTypeSelect).not.toBeInvalid(); + expect(queueNameInput).not.toBeInvalid(); + expect(locationSelect).not.toBeInvalid(); }); - it('should submit the form when all fields are filled', async () => { + it('submits the form when all required fields are filled', async () => { const user = userEvent.setup(); + render(); + + const queueNameInput = screen.getByRole('textbox', { name: /queue name/i }); + const serviceTypeSelect = screen.getByRole('combobox', { name: /select a service type/i }); + const locationSelect = screen.getByRole('combobox', { name: /select a location/i }); + const saveButton = screen.getByRole('button', { name: /save/i }); + + await user.type(queueNameInput, 'Test Queue'); + await user.selectOptions(serviceTypeSelect, '6f017eb0-b035-4acd-b284-da45f5067502'); + await user.selectOptions(locationSelect, '34567eb0-b035-4acd-b284-da45f5067502'); + await user.click(saveButton); + + expect(mockSaveQueue).toHaveBeenCalledTimes(1); + expect(mockSaveQueue).toHaveBeenCalledWith( + 'Test Queue', + '6f017eb0-b035-4acd-b284-da45f5067502', + '', + '34567eb0-b035-4acd-b284-da45f5067502', + ); + expect(mockShowSnackbar).toHaveBeenCalledTimes(1); + expect(mockShowSnackbar).toHaveBeenCalledWith({ + kind: 'success', + title: expect.stringMatching(/queue service created/i), + subtitle: expect.stringMatching(/queue service created successfully/i), + }); + }); + it('renders an error message when the queue service creation fails', async () => { + const user = userEvent.setup(); + mockSaveQueue.mockRejectedValueOnce(new Error('Internal server error')); render(); - const queueNameInput = screen.getByLabelText('Queue name'); - const serviceSelect = screen.getByLabelText('Select a service type'); - const locationSelect = screen.getByLabelText('Select a location'); + const queueNameInput = screen.getByRole('textbox', { name: /queue name/i }); + const serviceTypeSelect = screen.getByRole('combobox', { name: /select a service type/i }); + const locationSelect = screen.getByRole('combobox', { name: /select a location/i }); + const saveButton = screen.getByRole('button', { name: /save/i }); await user.type(queueNameInput, 'Test Queue'); - await user.selectOptions(serviceSelect, '6f017eb0-b035-4acd-b284-da45f5067502'); + await user.selectOptions(serviceTypeSelect, '6f017eb0-b035-4acd-b284-da45f5067502'); await user.selectOptions(locationSelect, '34567eb0-b035-4acd-b284-da45f5067502'); + await user.click(saveButton); - expect(queueNameInput).toHaveValue('Test Queue'); - expect(serviceSelect).toHaveValue('6f017eb0-b035-4acd-b284-da45f5067502'); - expect(locationSelect).toHaveValue('34567eb0-b035-4acd-b284-da45f5067502'); + expect(mockShowSnackbar).toHaveBeenCalledTimes(1); + expect(mockShowSnackbar).toHaveBeenCalledWith({ + isLowContrast: false, + kind: 'error', + title: expect.stringMatching(/error creating queue service/i), + subtitle: expect.stringMatching(/internal server error/i), + }); }); }); diff --git a/packages/esm-service-queues-app/src/queue-services/queue-service-form.workspace.tsx b/packages/esm-service-queues-app/src/queue-services/queue-service-form.workspace.tsx index 4fc9d90a1..d27d4feef 100644 --- a/packages/esm-service-queues-app/src/queue-services/queue-service-form.workspace.tsx +++ b/packages/esm-service-queues-app/src/queue-services/queue-service-form.workspace.tsx @@ -4,18 +4,17 @@ import type { TFunction } from 'react-i18next'; import { useForm, Controller } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; - import { Button, ButtonSet, Column, Form, + InlineLoading, Layer, Select, SelectItem, Stack, TextInput, - InlineLoading, } from '@carbon/react'; import { mutate } from 'swr'; import { type DefaultWorkspaceProps, restBaseUrl, showSnackbar } from '@openmrs/esm-framework'; @@ -31,7 +30,7 @@ const createQueueServiceSchema = (t: TFunction) => }) .trim() .min(1, t('queueNameRequired', 'Queue name is required')), - queueConcept: z + queueServiceType: z .string({ required_error: t('queueConceptRequired', 'Queue concept is required'), }) @@ -51,6 +50,7 @@ const QueueServiceForm: React.FC = ({ closeWorkspace }) = const { t } = useTranslation(); const { queueConcepts } = useServiceConcepts(); const { queueLocations } = useQueueLocations(); + const QueueServiceSchema = createQueueServiceSchema(t); const { @@ -61,13 +61,13 @@ const QueueServiceForm: React.FC = ({ closeWorkspace }) = resolver: zodResolver(QueueServiceSchema), defaultValues: { queueName: '', - queueConcept: '', + queueServiceType: '', userLocation: '', }, }); const createQueue = (data: QueueServiceFormData) => { - saveQueue(data.queueName, data.queueConcept, '', data.userLocation) + saveQueue(data.queueName, data.queueServiceType, '', data.userLocation) .then(() => { showSnackbar({ title: t('queueServiceCreated', 'Queue service created'), @@ -80,10 +80,10 @@ const QueueServiceForm: React.FC = ({ closeWorkspace }) = }) .catch((error) => { showSnackbar({ - title: t('errorAddingQueue', 'Error adding queue'), + title: t('errorCreatingQueueService', 'Error creating queue service'), kind: 'error', isLowContrast: false, - subtitle: error?.message, + subtitle: error?.responseBody?.message || error?.message, }); }); }; @@ -108,25 +108,19 @@ const QueueServiceForm: React.FC = ({ closeWorkspace }) = /> - ( - {!field.value && } + {queueLocations?.length > 0 && queueLocations.map((location) => ( diff --git a/packages/esm-service-queues-app/src/queue-services/queue-service.resource.ts b/packages/esm-service-queues-app/src/queue-services/queue-service.resource.ts index a5f5da634..9343c6b70 100644 --- a/packages/esm-service-queues-app/src/queue-services/queue-service.resource.ts +++ b/packages/esm-service-queues-app/src/queue-services/queue-service.resource.ts @@ -12,10 +12,15 @@ export function useServiceConcepts() { }; } -export function saveQueue(queueName: string, queueConcept: string, queueDescription?: string, queueLocation?: string) { +export function saveQueue( + queueName: string, + queueServiceType: string, + queueDescription?: string, + queueLocation?: string, +) { const abortController = new AbortController(); - return openmrsFetch(`${restBaseUrl}/queue`, { + return openmrsFetch(`${restBaseUrl}/queued`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -24,7 +29,7 @@ export function saveQueue(queueName: string, queueConcept: string, queueDescript body: { name: queueName, description: queueDescription, - service: { uuid: queueConcept }, + service: { uuid: queueServiceType }, location: { uuid: queueLocation, }, diff --git a/packages/esm-service-queues-app/translations/en.json b/packages/esm-service-queues-app/translations/en.json index 82ff7f707..3b74f8191 100644 --- a/packages/esm-service-queues-app/translations/en.json +++ b/packages/esm-service-queues-app/translations/en.json @@ -8,7 +8,6 @@ "addNewServiceRoom": "Add new service room", "addPatientToQueue": "Add patient to queue", "addProviderQueueRoom": "Add provider queue room", - "addQueue": "Add queue", "addQueueRoom": "Add queue room", "addQueueRoomName": "Please add a queue room name", "addQueueRoomService": "Please add a queue room service", @@ -57,9 +56,9 @@ "endVisit": "End visit", "endVisitWarningMessage": "Ending this visit will not allow you to fill another encounter form for this patient", "enterCommentHere": "Enter Comment here", - "errorAddingQueue": "Error adding queue", "errorAddingQueueRoom": "Error adding queue room", "errorClearingQueues": "Error clearing queues", + "errorCreatingQueueService": "Error creating queue service", "errorFetchingVisit": "Error fetching patient visit", "errorLoadingQueueEntries": "Error loading queue entries", "errorPostingToScreen": "Error posting to screen", @@ -102,7 +101,6 @@ "noPrioritiesForServiceTitle": "No priorities available", "noPriorityFound": "No priority found", "noReturnDate": "There is no return date to display for this patient", - "noServicesAvailable": "No services available", "noServicesConfigured": "No services configured", "noStatusConfigured": "No status configured", "notableConfig": "No table configuration", @@ -131,7 +129,6 @@ "priorityIsRequired": "Priority is required", "provider": "Provider", "quantity": "Quantity", - "queueAddedSuccessfully": "Queue added successfully", "queueConceptRequired": "Queue concept is required", "queueEntryAddedSuccessfully": "Queue entry added successfully", "queueEntryDeleteFailed": "Error deleting queue entry", @@ -163,6 +160,8 @@ "queuesClearedSuccessfully": "Queues cleared successfully", "queueScreen": "Queue screen", "queueService": "Queue service", + "queueServiceCreated": "Queue service created", + "queueServiceCreatedSuccessfully": "Queue service created successfully", "queueStatus": "Queue status", "refills": "Refills", "removeFromQueueAndEndVisit": "Remove patient from queue and end active visit",