Skip to content

Commit 228e410

Browse files
committed
set react-hook-form on dialogs and forms
1 parent e063291 commit 228e410

File tree

1 file changed

+181
-72
lines changed

1 file changed

+181
-72
lines changed

packages/esm-patient-queues-app/src/active-visits/change-status-dialog.component.tsx

Lines changed: 181 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ import {
1212
Switch,
1313
InlineLoading,
1414
TextArea,
15+
Layer,
16+
InlineNotification,
1517
} from '@carbon/react';
1618
import {
1719
navigate,
1820
parseDate,
1921
showNotification,
2022
showSnackbar,
2123
showToast,
24+
useLayoutType,
2225
useSession,
2326
useVisit,
2427
} from '@openmrs/esm-framework';
@@ -30,6 +33,9 @@ import { ArrowUp, ArrowDown } from '@carbon/react/icons';
3033
import styles from './change-status-dialog.scss';
3134
import { QueueStatus, extractErrorMessagesFromResponse } from '../utils/utils';
3235
import { updateVisit, useProviders } from './patient-queues.resource';
36+
import { Controller, useForm } from 'react-hook-form';
37+
import { zodResolver } from '@hookform/resolvers/zod';
38+
import { CreateQueueEntryFormData, createQueueEntrySchema } from './patient-queue-validation-schema.resource';
3339

3440
interface ChangeStatusDialogProps {
3541
queueEntry: MappedQueueEntry;
@@ -40,7 +46,9 @@ interface ChangeStatusDialogProps {
4046
const ChangeStatus: React.FC<ChangeStatusDialogProps> = ({ queueEntry, currentEntry, closeModal }) => {
4147
const { t } = useTranslation();
4248

43-
const { providers } = useProviders();
49+
const isTablet = useLayoutType() === 'tablet';
50+
51+
const { providers, error: errorLoadingProviders } = useProviders();
4452

4553
const [contentSwitcherIndex, setContentSwitcherIndex] = useState(1);
4654

@@ -50,7 +58,11 @@ const ChangeStatus: React.FC<ChangeStatusDialogProps> = ({ queueEntry, currentEn
5058

5159
const sessionUser = useSession();
5260

53-
const { queueRoomLocations, mutate } = useQueueRoomLocations(sessionUser?.sessionLocation?.uuid);
61+
const {
62+
queueRoomLocations,
63+
mutate,
64+
error: errorLoadingQueueRooms,
65+
} = useQueueRoomLocations(sessionUser?.sessionLocation?.uuid);
5466

5567
const [selectedNextQueueLocation, setSelectedNextQueueLocation] = useState(queueRoomLocations[0]?.uuid);
5668

@@ -102,6 +114,13 @@ const ChangeStatus: React.FC<ChangeStatusDialogProps> = ({ queueEntry, currentEn
102114
[],
103115
);
104116

117+
const { handleSubmit, control, formState } = useForm<CreateQueueEntryFormData>({
118+
mode: 'all',
119+
resolver: zodResolver(createQueueEntrySchema),
120+
});
121+
122+
const { errors } = formState;
123+
105124
useEffect(() => {
106125
setPriorityComment(priorityLabels[contentSwitcherIndex]);
107126
}, [contentSwitcherIndex, priorityLabels]);
@@ -176,8 +195,8 @@ const ChangeStatus: React.FC<ChangeStatusDialogProps> = ({ queueEntry, currentEn
176195
}
177196
};
178197

179-
const changeQueueStatus = useCallback(
180-
async (event: { preventDefault: () => void; target: { [x: string]: { value: string } } }) => {
198+
const onSubmit = useCallback(
199+
async (event) => {
181200
event.preventDefault();
182201

183202
try {
@@ -293,7 +312,7 @@ const ChangeStatus: React.FC<ChangeStatusDialogProps> = ({ queueEntry, currentEn
293312
if (queueEntry && Object.keys(queueEntry)?.length > 0) {
294313
return (
295314
<div>
296-
<Form onSubmit={changeQueueStatus}>
315+
<Form onSubmit={handleSubmit(onSubmit)}>
297316
<ModalHeader closeModal={closeModal} />
298317
<ModalBody>
299318
<div className={styles.modalBody}>
@@ -335,86 +354,172 @@ const ChangeStatus: React.FC<ChangeStatusDialogProps> = ({ queueEntry, currentEn
335354
</div>
336355
<section className={styles.section}>
337356
<div className={styles.sectionTitle}>{t('priority', 'Priority')}</div>
338-
<ContentSwitcher
339-
selectedIndex={contentSwitcherIndex}
340-
className={styles.contentSwitcher}
341-
onChange={({ index }) => setContentSwitcherIndex(index)}
342-
>
343-
{priorityLabels.map((label, index) => (
344-
<Switch
345-
key={index}
346-
name={label.toLowerCase().replace(' ', '')}
347-
text={t(label.toLowerCase(), label)}
348-
/>
349-
))}
350-
</ContentSwitcher>
357+
<Controller
358+
name="priorityComment"
359+
control={control}
360+
render={({ field }) => (
361+
<ContentSwitcher
362+
{...field}
363+
selectedIndex={contentSwitcherIndex}
364+
className={styles.contentSwitcher}
365+
onChange={({ index }) => {
366+
field.onChange(index);
367+
setContentSwitcherIndex(index);
368+
}}
369+
>
370+
{priorityLabels.map((label, index) => (
371+
<Switch
372+
key={index}
373+
name={label.toLowerCase().replace(/\s+/g, '')}
374+
text={t(label.toLowerCase(), label)}
375+
/>
376+
))}
377+
</ContentSwitcher>
378+
)}
379+
/>
351380
</section>
352381

353382
<section className={styles.section}>
354383
<div className={styles.sectionTitle}>{t('status', 'Status')}</div>
355-
<ContentSwitcher
356-
selectedIndex={statusSwitcherIndex}
357-
className={styles.contentSwitcher}
358-
onChange={({ index }) => setStatusSwitcherIndex(index)}
359-
>
360-
{statusLabels.map((status, index) => (
361-
<Switch
362-
key={index}
363-
name={status.label.toLowerCase().replace(' ', '')}
364-
text={t(status.label.toLowerCase(), status.label)}
365-
/>
366-
))}
367-
</ContentSwitcher>
384+
<Controller
385+
name="status"
386+
control={control}
387+
render={({ field }) => (
388+
<ContentSwitcher
389+
{...field}
390+
selectedIndex={statusSwitcherIndex}
391+
className={styles.contentSwitcher}
392+
onChange={({ index }) => {
393+
field.onChange(index);
394+
setStatusSwitcherIndex(index);
395+
}}
396+
>
397+
{statusLabels.map((status, index) => (
398+
<Switch
399+
key={index}
400+
name={status.label.toLowerCase().replace(' ', '')}
401+
text={t(status.label.toLowerCase(), status.label)}
402+
/>
403+
))}
404+
</ContentSwitcher>
405+
)}
406+
/>
368407
</section>
369408

370409
{status === QueueStatus.Completed && (
371410
<>
372411
<section className={styles.section}>
373-
<Select
374-
labelText={t('selectNextQueueRoom', 'Select next queue room ')}
375-
id="nextQueueLocation"
376-
name="nextQueueLocation"
377-
invalidText="Required"
378-
value={selectedNextQueueLocation}
379-
onChange={(event) => setSelectedNextQueueLocation(event.target.value)}
380-
>
381-
{!selectedNextQueueLocation ? (
382-
<SelectItem text={t('selectNextServicePoint', 'Select next service point')} value="" />
383-
) : null}
384-
{filteredlocations.map((location) => (
385-
<SelectItem key={location.uuid} text={location.display} value={location.uuid}>
386-
{location.display}
387-
</SelectItem>
388-
))}
389-
</Select>
412+
<div className={styles.sectionTitle}>{t('nextServicePoint', 'Next service point')}</div>
413+
<ResponsiveWrapper isTablet={isTablet}>
414+
<Controller
415+
name="locationTo"
416+
control={control}
417+
defaultValue={filteredlocations.length > 0 ? filteredlocations[0].uuid : ''}
418+
render={({ field }) => (
419+
<Select
420+
{...field}
421+
labelText={''}
422+
id="nextQueueLocation"
423+
name="nextQueueLocation"
424+
disabled={errorLoadingQueueRooms}
425+
invalid={!!errors.locationTo}
426+
invalidText={errors.locationTo?.message}
427+
value={field.value}
428+
onChange={(event) => {
429+
field.onChange(event.target.value);
430+
setSelectedNextQueueLocation(event.target.value);
431+
}}
432+
>
433+
{!field.value ? (
434+
<SelectItem text={t('selectNextServicePoint', 'Choose next service point')} value="" />
435+
) : null}
436+
{filteredlocations.map((location) => (
437+
<SelectItem key={location.uuid} text={location.display} value={location.uuid}>
438+
{location.display}
439+
</SelectItem>
440+
))}
441+
</Select>
442+
)}
443+
/>
444+
445+
{errorLoadingQueueRooms && (
446+
<InlineNotification
447+
className={styles.errorNotification}
448+
kind="error"
449+
onClick={() => {}}
450+
subtitle={errorLoadingQueueRooms}
451+
title={t('errorFetchingQueueRooms', 'Error fetching queue rooms')}
452+
/>
453+
)}
454+
</ResponsiveWrapper>
390455
</section>
391456
<section className={styles.section}>
392-
<Select
393-
labelText={t('selectProvider', 'Select a provider')}
394-
id="providers-list"
395-
name="providers-list"
396-
invalidText="Required"
397-
value={selectedProvider}
398-
onChange={(event) => setSelectedProvider(event.target.value)}
399-
>
400-
{!selectedProvider ? <SelectItem text={t('selectProvider', 'Select a provider')} value="" /> : null}
401-
{filteredProviders.map((provider) => (
402-
<SelectItem key={provider.uuid} text={provider.display} value={provider.uuid}>
403-
{provider.display}
404-
</SelectItem>
405-
))}
406-
</Select>
457+
<div className={styles.sectionTitle}>{t('selectAProvider', 'Select a provider')}</div>
458+
<ResponsiveWrapper isTablet={isTablet}>
459+
<Controller
460+
name="provider"
461+
control={control}
462+
defaultValue={filteredProviders.length > 0 ? filteredProviders[0].uuid : ''}
463+
render={({ field }) => (
464+
<Select
465+
{...field}
466+
labelText={''}
467+
id="providers-list"
468+
name="providers-list"
469+
disabled={errorLoadingProviders}
470+
invalid={!!errors.provider}
471+
invalidText={errors.provider?.message}
472+
value={field.value}
473+
onChange={(event) => {
474+
field.onChange(event.target.value);
475+
setSelectedProvider(event.target.value);
476+
}}
477+
>
478+
{!field.value ? (
479+
<SelectItem text={t('selectProvider', 'choose a provider')} value="" />
480+
) : null}
481+
{filteredProviders.map((provider) => (
482+
<SelectItem key={provider.uuid} text={provider.display} value={provider.uuid}>
483+
{provider.display}
484+
</SelectItem>
485+
))}
486+
</Select>
487+
)}
488+
/>
489+
490+
{errorLoadingProviders && (
491+
<InlineNotification
492+
className={styles.errorNotification}
493+
kind="error"
494+
onClick={() => {}}
495+
subtitle={errorLoadingProviders}
496+
title={t('errorFetchingQueueRooms', 'Error fetching providers')}
497+
/>
498+
)}
499+
</ResponsiveWrapper>
407500
</section>
408501
<section className={styles.section}>
409-
<TextArea
410-
labelText={t('notes', 'Enter notes ')}
411-
id="nextNotes"
412-
name="nextNotes"
413-
invalidText="Required"
414-
helperText="Please enter notes"
415-
maxCount={500}
416-
enableCounter
417-
/>
502+
<div className={styles.sectionTitle}>{t('notes', 'Notes')}</div>
503+
<ResponsiveWrapper isTablet={isTablet}>
504+
<Controller
505+
name="comment"
506+
control={control}
507+
defaultValue="NA"
508+
render={({ field }) => (
509+
<TextArea
510+
{...field}
511+
aria-label={t('comment', 'Comment')}
512+
invalid={!!errors.comment}
513+
invalidText={errors.comment?.message}
514+
labelText=""
515+
id="comment"
516+
name="comment"
517+
maxCount={500}
518+
enableCounter
519+
/>
520+
)}
521+
/>
522+
</ResponsiveWrapper>
418523
</section>
419524
</>
420525
)}
@@ -438,4 +543,8 @@ const ChangeStatus: React.FC<ChangeStatusDialogProps> = ({ queueEntry, currentEn
438543
}
439544
};
440545

546+
function ResponsiveWrapper({ children, isTablet }) {
547+
return isTablet ? <Layer>{children}</Layer> : <div>{children}</div>;
548+
}
549+
441550
export default ChangeStatus;

0 commit comments

Comments
 (0)