From c39e929613dcc4e2e3bd44b53c9877099efc1d2b Mon Sep 17 00:00:00 2001 From: Matt Bemis Date: Mon, 7 Oct 2024 15:07:20 -0400 Subject: [PATCH] [JN-1375] UX tweaks for kit scanning (#1129) Co-authored-by: Devon --- ui-admin/src/api/api-utils.tsx | 10 +- ui-admin/src/components/forms/Textarea.tsx | 2 +- ui-admin/src/navbar/AdminSidebar.tsx | 17 +- .../study/kits/kitcollection/KitScanner.tsx | 208 ++++++++++-------- 4 files changed, 140 insertions(+), 97 deletions(-) diff --git a/ui-admin/src/api/api-utils.tsx b/ui-admin/src/api/api-utils.tsx index 39b76382fd..a98e68b5c9 100644 --- a/ui-admin/src/api/api-utils.tsx +++ b/ui-admin/src/api/api-utils.tsx @@ -63,15 +63,21 @@ export const doApiLoad = async (loadingFunc: () => Promise, opts: { setIsLoading?: (isLoading: boolean) => void, setIsError?: (isError: boolean) => void, - customErrorMsg?: string + customErrorMsg?: string, + alertErrors?: boolean, + setError?: (error: string) => void } = {}) => { + if (opts.alertErrors === undefined) { + opts.alertErrors = true + } if (opts.setIsLoading) { opts.setIsLoading(true) } try { await loadingFunc() if (opts.setIsError) { opts.setIsError(false) } } catch (e) { - defaultApiErrorHandle(e as ApiErrorResponse, opts.customErrorMsg) + if (opts.alertErrors) { defaultApiErrorHandle(e as ApiErrorResponse, opts.customErrorMsg) } if (opts.setIsError) { opts.setIsError(true) } + if (opts.setError) { opts.setError((e as ApiErrorResponse).message) } } if (opts.setIsLoading) { opts.setIsLoading(false) } } diff --git a/ui-admin/src/components/forms/Textarea.tsx b/ui-admin/src/components/forms/Textarea.tsx index a90c799ea6..25a99e7e63 100644 --- a/ui-admin/src/components/forms/Textarea.tsx +++ b/ui-admin/src/components/forms/Textarea.tsx @@ -5,7 +5,7 @@ import InfoPopup from './InfoPopup' export type TextareaProps = Omit & { description?: string infoContent?: React.ReactNode - label: string + label?: string labelClassname?: string, required?: boolean onChange?: (value: string) => void diff --git a/ui-admin/src/navbar/AdminSidebar.tsx b/ui-admin/src/navbar/AdminSidebar.tsx index 6984a931a8..5036e6f455 100644 --- a/ui-admin/src/navbar/AdminSidebar.tsx +++ b/ui-admin/src/navbar/AdminSidebar.tsx @@ -1,8 +1,8 @@ -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import { useUser } from '../user/UserProvider' import { Link, NavLink, useParams } from 'react-router-dom' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faArrowRightFromBracket } from '@fortawesome/free-solid-svg-icons' +import { faCaretRight } from '@fortawesome/free-solid-svg-icons' import { Study } from '@juniper/ui-core' import { studyShortcodeFromPath } from '../study/StudyRouter' import { useNavContext } from './NavContextProvider' @@ -44,6 +44,13 @@ const AdminSidebar = ({ config }: { config: Config }) => { const color = ZONE_COLORS[config.deploymentZone] || ZONE_COLORS['prod'] + // automatically collapse the sidebar for mobile-first routes + useEffect(() => { + if (isMobileFirstRoute()) { + setOpen(false) + } + }, []) + return
<> @@ -55,7 +62,7 @@ const AdminSidebar = ({ config }: { config: Config }) => { localStorage.setItem(SHOW_SIDEBAR_KEY, (!open).toString()) }} tooltip={open ? 'Hide sidebar' : 'Show sidebar'}> -
@@ -83,4 +90,8 @@ const AdminSidebar = ({ config }: { config: Config }) => { } +const isMobileFirstRoute = () => { + return window.location.pathname.endsWith('kits/scan') +} + export default AdminSidebar diff --git a/ui-admin/src/study/kits/kitcollection/KitScanner.tsx b/ui-admin/src/study/kits/kitcollection/KitScanner.tsx index 0969b1a8b7..4d354891dc 100644 --- a/ui-admin/src/study/kits/kitcollection/KitScanner.tsx +++ b/ui-admin/src/study/kits/kitcollection/KitScanner.tsx @@ -21,6 +21,8 @@ import { import { failureNotification, successNotification } from 'util/notifications' import { Store } from 'react-notifications-component' import { Checkbox } from 'components/forms/Checkbox' +import { Textarea } from 'components/forms/Textarea' +import LoadingSpinner from 'util/LoadingSpinner' const kitScanModeOptions = [ { value: 'ASSIGN', label: 'Assign a new kit' }, @@ -39,6 +41,8 @@ export const KitScanner = ({ studyEnvContext }: { studyEnvContext: StudyEnvConte const [kitCodeError, setKitCodeError] = useState() const [returnTrackingNumber, setReturnTrackingNumber] = useState() const [returnTrackingNumberError, setReturnTrackingNumberError] = useState() + const [loading, setLoading] = useState(false) + const [submitError, setSubmitError] = useState() const [enableManualShortcodeOverride, setEnableManualShortcodeOverride] = useState(false) const [enableManualKitLabelOverride, setEnableManualKitLabelOverride] = useState(false) @@ -69,11 +73,12 @@ export const KitScanner = ({ studyEnvContext }: { studyEnvContext: StudyEnvConte ) Store.addNotification(successNotification('Kit successfully assigned')) }, { - setIsError: error => { + setError: error => { if (error) { - Store.addNotification(failureNotification('Error assigning kit')) + setSubmitError(`Error assigning kit: ${error}`) } - } + }, + setIsLoading: setLoading }) } @@ -95,11 +100,12 @@ export const KitScanner = ({ studyEnvContext }: { studyEnvContext: StudyEnvConte ) Store.addNotification(successNotification('Kit successfully collected')) }, { - setIsError: error => { + setError: error => { if (error) { - Store.addNotification(failureNotification('Error collecting kit')) + setSubmitError(`Error collecting kit: ${error}`) } - } + }, + setIsLoading: setLoading }) } @@ -124,7 +130,8 @@ export const KitScanner = ({ studyEnvContext }: { studyEnvContext: StudyEnvConte setEnrolleeCodeError('Error loading enrollee. Did you scan the correct QR code?') setEnrollee(undefined) } - } + }, + alertErrors: false }) } @@ -151,6 +158,7 @@ export const KitScanner = ({ studyEnvContext }: { studyEnvContext: StudyEnvConte setEnrollee(undefined) setIsEnrolleeIdentityConfirmed(false) setKitLabel(undefined) + setSubmitError(undefined) }} /> @@ -162,11 +170,31 @@ export const KitScanner = ({ studyEnvContext }: { studyEnvContext: StudyEnvConte
Click the button below to open the camera and scan the enrollee's unique QR code. The enrollee can find their unique QR code by going to the kits page on their profile. - If you are unable to use your camera or scan the barcode for any reason, you may override - manually and enter the participant code (shortcode) found directly under their QR code.}/> +
+ The enrollee can find their unique QR code by going to the kits page on their profile. + If you are unable to use your camera or scan the barcode for any reason, you may override + manually and enter the participant code (shortcode) found directly under their QR code. +
+ Note: participant shortcodes only contain letters, and will not contain any numbers. +
+
+ }/>
- + { setEnableManualShortcodeOverride(e) }}/> - { showEnrolleeCodeScanner && - { - setEnrolleeCodeError(error) - setShowEnrolleeCodeScanner(false) - setEnrollee(undefined) - }} - onSuccess={result => { - loadEnrollee(result.rawValue) - setEnrolleeCodeError(undefined) - setShowEnrolleeCodeScanner(false) - }}/> - } - { enableManualShortcodeOverride &&
+ {enableManualShortcodeOverride &&
-
} - { enrolleeCodeError && -
{enrolleeCodeError}
+
} + {enrolleeCodeError && +
{enrolleeCodeError}
} @@ -217,19 +234,23 @@ export const KitScanner = ({ studyEnvContext }: { studyEnvContext: StudyEnvConte status={isEnrolleeIdentityConfirmed && enrollee ? 'COMPLETE' : 'INCOMPLETE'} >
Confirm the enrollee's identity using their full name and date of birth.
- - - - + {enrollee ? <> + +
+ {enrollee ? + `${enrollee.profile.givenName} ${enrollee.profile.familyName}` : 'not yet entered'} +
+ +
+ {enrollee.profile.birthDate ? + dateToDefaultString(enrollee.profile.birthDate) : 'not yet entered'} +
+ : +
Please scan an enrollee QR code
+ } @@ -238,7 +259,16 @@ export const KitScanner = ({ studyEnvContext }: { studyEnvContext: StudyEnvConte status={kitLabel ? 'COMPLETE' : 'INCOMPLETE'} >
Click to open the camera and scan the label on the kit.
- - { - setEnableManualReturnLabelOverride(e) + {selectedScanMode?.value === 'COLLECT' && + +
Place the kit in the provided return packaging. Afterwards, + click to open the camera and scan the return label on the return packaging. +
+ {showReturnTrackingNumberScanner && + setReturnTrackingNumberError(error)} + onSuccess={result => { + setReturnTrackingNumber(result.rawValue) + setShowReturnTrackingNumberScanner(false) }}/> - { showReturnTrackingNumberScanner && - setReturnTrackingNumberError(error)} - onSuccess={result => { - setReturnTrackingNumber(result.rawValue) - setShowReturnTrackingNumberScanner(false) - }}/> - } - setReturnTrackingNumber(e)}> - - { returnTrackingNumberError && -
{returnTrackingNumberError}
- } -
} -
+ } + + { + setEnableManualReturnLabelOverride(e) + }}/> + + {returnTrackingNumberError && +
{returnTrackingNumberError}
+ } + } +
+ { submitError && {submitError}} +