diff --git a/resources/main.py b/resources/main.py index 00e8c6b9..6ebeb7db 100644 --- a/resources/main.py +++ b/resources/main.py @@ -39,6 +39,5 @@ def main(): f.write(ndjson.dumps(resources).encode()) - if __name__ == "__main__": main() diff --git a/src/components/BaseLayout/Sidebar/SidebarBottom/index.tsx b/src/components/BaseLayout/Sidebar/SidebarBottom/index.tsx index 57e74bb6..ebe7af2d 100644 --- a/src/components/BaseLayout/Sidebar/SidebarBottom/index.tsx +++ b/src/components/BaseLayout/Sidebar/SidebarBottom/index.tsx @@ -35,10 +35,11 @@ interface Props extends React.HTMLAttributes { collapsed: boolean; toggleCollapsed?: () => void; onItemClick?: () => void; + enableLocaleSwitcher?: boolean; } export function SidebarBottom(props: Props) { - const { collapsed, toggleCollapsed, onItemClick, ...other } = props; + const { collapsed, toggleCollapsed, onItemClick, enableLocaleSwitcher = true, ...other } = props; const appToken = getToken(); const isAnonymousUser = !appToken; @@ -50,7 +51,7 @@ export function SidebarBottom(props: Props) { {...other} > - + {enableLocaleSwitcher && } {!isAnonymousUser ? ( <> diff --git a/src/components/BaseQuestionnaireResponseForm/widgets/Group/RepeatableGroups/index.tsx b/src/components/BaseQuestionnaireResponseForm/widgets/Group/RepeatableGroups/index.tsx index e99ac91b..bae7cf20 100644 --- a/src/components/BaseQuestionnaireResponseForm/widgets/Group/RepeatableGroups/index.tsx +++ b/src/components/BaseQuestionnaireResponseForm/widgets/Group/RepeatableGroups/index.tsx @@ -13,6 +13,11 @@ import s from './RepeatableGroups.module.scss'; interface RepeatableGroupsProps { groupItem: GroupItemProps; renderGroup?: (props: RepeatableGroupProps) => ReactNode; + buildValue?: (existingItems:Array, value:any) => any; +} + +function defaultBuildValue(exisingItems:Array, _value:any) { + return [...exisingItems, {}]; } export function RepeatableGroups(props: RepeatableGroupsProps) { @@ -22,6 +27,7 @@ export function RepeatableGroups(props: RepeatableGroupsProps) { const fieldName = [...parentPath, linkId]; const { value, onChange } = useFieldController(fieldName, questionItem); const items = value.items && value.items.length ? value.items : required ? [{}] : []; + const buildValue = props.buildValue ?? defaultBuildValue; return (
@@ -57,7 +63,7 @@ export function RepeatableGroups(props: RepeatableGroupsProps) { className={s.addButton} onClick={() => { const existingItems = items || []; - const updatedInput = { items: [...existingItems, {}] }; + const updatedInput = { items: buildValue(existingItems, value) }; onChange(updatedInput); }} size="small" diff --git a/src/components/BaseQuestionnaireResponseForm/widgets/choice/index.tsx b/src/components/BaseQuestionnaireResponseForm/widgets/choice/index.tsx index c2326ba2..0c58af0a 100644 --- a/src/components/BaseQuestionnaireResponseForm/widgets/choice/index.tsx +++ b/src/components/BaseQuestionnaireResponseForm/widgets/choice/index.tsx @@ -3,7 +3,11 @@ import _, { debounce } from 'lodash'; import { useCallback, useContext } from 'react'; import { QuestionItemProps } from 'sdc-qrf'; -import { QuestionnaireItemAnswerOption, QuestionnaireResponseItemAnswer } from '@beda.software/aidbox-types'; +import { + QuestionnaireItemAnswerOption, + QuestionnaireItemChoiceColumn, + QuestionnaireResponseItemAnswer, +} from '@beda.software/aidbox-types'; import { AsyncSelect, Select } from 'src/components/Select'; import { ValueSetExpandProvider } from 'src/contexts'; @@ -43,7 +47,7 @@ export function ChoiceQuestionSelect(props: ChoiceQuestionSelectProps) { } export function QuestionChoice({ parentPath, questionItem }: QuestionItemProps) { - const { linkId, answerOption, repeats, answerValueSet } = questionItem; + const { linkId, answerOption, repeats, answerValueSet, choiceColumn } = questionItem; const fieldName = [...parentPath, linkId]; const { value, formItem, onChange, placeholder } = useFieldController(fieldName, questionItem); @@ -55,6 +59,7 @@ export function QuestionChoice({ parentPath, questionItem }: QuestionItemProps) void; repeats?: boolean; placeholder?: string; + choiceColumn?: QuestionnaireItemChoiceColumn[]; } export function ChoiceQuestionValueSet(props: ChoiceQuestionValueSetProps) { - const { answerValueSet, value, onChange, repeats = false, placeholder } = props; + const { answerValueSet, value, onChange, repeats = false, placeholder, choiceColumn } = props; const expand = useContext(ValueSetExpandProvider); const loadOptions = useCallback( @@ -108,7 +114,7 @@ export function ChoiceQuestionValueSet(props: ChoiceQuestionValueSetProps) { onChange={(v) => onChange(v)} isOptionSelected={(option) => !!value && value?.findIndex((v) => _.isEqual(v?.value, option.value)) !== -1} isMulti={repeats} - getOptionLabel={(o) => (getDisplay(o.value) as string) || ''} + getOptionLabel={(o) => (getDisplay(o.value, choiceColumn) as string) || ''} placeholder={placeholder} /> ); diff --git a/src/components/BaseQuestionnaireResponseForm/widgets/date.tsx b/src/components/BaseQuestionnaireResponseForm/widgets/date.tsx index 22487943..3751585e 100644 --- a/src/components/BaseQuestionnaireResponseForm/widgets/date.tsx +++ b/src/components/BaseQuestionnaireResponseForm/widgets/date.tsx @@ -4,7 +4,6 @@ import { useCallback, useMemo } from 'react'; import { QuestionItemProps } from 'sdc-qrf'; import { - FHIRDateFormat, FHIRTimeFormat, FHIRDateTimeFormat, formatFHIRDate, @@ -17,6 +16,8 @@ import { TimePicker } from 'src/components/TimePicker'; import { useFieldController } from '../hooks'; +const USDateFormat = 'MM/DD/YYYY'; + export function QuestionDateTime({ parentPath, questionItem }: QuestionItemProps) { const { linkId, type } = questionItem; const fieldName = [...parentPath, linkId, 0, 'value', type]; @@ -63,7 +64,7 @@ function DateTimePickerWrapper(props: DateTimePickerWrapperProps) { let formatFunction: (value: Moment) => string; if (type === 'date') { - format = FHIRDateFormat; + format = USDateFormat; showTime = false; formatFunction = formatFHIRDate; } else if (type === 'time') { diff --git a/src/components/BaseQuestionnaireResponseForm/widgets/reference.tsx b/src/components/BaseQuestionnaireResponseForm/widgets/reference.tsx index 84d629e8..29651922 100644 --- a/src/components/BaseQuestionnaireResponseForm/widgets/reference.tsx +++ b/src/components/BaseQuestionnaireResponseForm/widgets/reference.tsx @@ -187,7 +187,7 @@ function QuestionReferenceUnsafe, ) { const { debouncedLoadOptions, fieldController, repeats, placeholder } = useAnswerReference(props); - const { formItem } = fieldController; + const { formItem, disabled } = fieldController; return ( @@ -200,6 +200,7 @@ function QuestionReferenceUnsafe getAnswerCode(option.value)} isMulti={repeats} placeholder={placeholder} + isDisabled={disabled} /> ); diff --git a/src/components/QuestionnaireResponseForm/index.tsx b/src/components/QuestionnaireResponseForm/index.tsx index d2e6ebb1..f21af859 100644 --- a/src/components/QuestionnaireResponseForm/index.tsx +++ b/src/components/QuestionnaireResponseForm/index.tsx @@ -35,6 +35,7 @@ interface Props extends QuestionnaireResponseFormProps { FormFooterComponent?: React.ElementType; saveButtonTitle?: string; cancelButtonTitle?: string; + autoSave?: boolean; } export const saveQuestionnaireResponseDraft = async ( @@ -79,18 +80,26 @@ export function onFormResponse(props: { if (isSuccess(response)) { if (response.data.extracted) { - let warnings: string[] = []; response.data.extractedBundle.forEach((bundle, index) => { bundle.entry?.forEach((entry, jndex) => { - if (entry.resource.resourceType === "OperationOutcome") { - warnings.push(`Error extrating on ${index}, ${jndex}`); - } - }); + if (entry.resource.resourceType === 'OperationOutcome') { + warnings.push(`Error extrating on ${index}, ${jndex}`); + } + }); }); if (warnings.length > 0) { notification.warning({ - message: (
{warnings.map((w) =>
{w}
)}
), + message: ( +
+ {warnings.map((w) => ( +
+ {w} +
+
+ ))} +
+ ), }); } diff --git a/src/theme/palette.ts b/src/theme/palette.ts index c43e7a43..712d8110 100644 --- a/src/theme/palette.ts +++ b/src/theme/palette.ts @@ -3,7 +3,7 @@ import _ from 'lodash'; import { DefaultTheme } from 'styled-components'; export const brandColors: Pick = { - primary: '#3366ff', + primary: '#892CE2', secondary: '#05BDB1', }; @@ -98,12 +98,12 @@ export function getPalette({ dark }: { dark?: boolean }): DefaultTheme { dark: DefaultTheme['iconColors']; } = { light: { - primary: primaryPalette.bcp_6, - secondary: secondaryPalette.bcs_2, + primary: '#4E1AEF', + secondary: '#D4B3F5', }, dark: { - primary: neutralPalette.dark.gray_12, - secondary: neutralPalette.dark.gray_6, + primary: '#49B5FF', + secondary: '#A76EB1', }, }; diff --git a/src/utils/date.ts b/src/utils/date.ts index 469a5915..e5619bac 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -8,6 +8,11 @@ export const humanDateYearMonth = 'MMM YYYY'; export const humanTime = 'HH:mm'; export const humanDateTime = 'DD MMM YYYY HH:mm'; +export const humanDateUS = 'MM/DD/YYYY'; +export const humanDateYearMonthUS = 'MM/YYYY'; +export const humanTimeUS = 'hh:mm A'; +export const humanDateTimeUS = 'MM/DD/YYYY hh:mm A'; + export const formatHumanDateTime = (date?: string) => { if (!date) { return ''; @@ -16,6 +21,14 @@ export const formatHumanDateTime = (date?: string) => { return parseFHIRDateTime(date).format(humanDateTime); }; +export const formatHumanDateTimeUS = (date?: string) => { + if (!date) { + return ''; + } + + return parseFHIRDateTime(date).format(humanDateTimeUS); +}; + export const formatHumanDate = (date?: string) => { if (!date) { return ''; @@ -38,6 +51,28 @@ export const formatHumanDate = (date?: string) => { return parseFHIRDateTime(date).format(humanDate); }; +export const formatHumanDateUS = (date?: string) => { + if (!date) { + return ''; + } + + // Year only 2000 + if (date.length === 4) { + return date; + } + + // Year and month 2000-01 + if (date.length === 7) { + return parseFHIRDate(date + '-01').format(humanDateYearMonthUS); + } + + if (date.length === 10) { + return parseFHIRDate(date).format(humanDateUS); + } + + return parseFHIRDateTime(date).format(humanDateUS); +}; + export function formatPeriodDateTime(period?: Period) { const timeRange = _.compact([ period?.start ? parseFHIRDateTime(period.start).format('HH:mm') : undefined, diff --git a/src/utils/fhir.ts b/src/utils/fhir.ts index 231d5bab..b69cdd87 100644 --- a/src/utils/fhir.ts +++ b/src/utils/fhir.ts @@ -6,7 +6,7 @@ export function renderHumanName(name?: HumanName) { } return ( - `${name?.prefix?.[0] ?? ''} ${name?.given?.[0] ?? ''} ${name?.family ?? ''} ${ + `${name?.prefix?.[0] ?? ''} ${name?.given?.[0] ?? ''} ${name?.given?.[1] ?? ''} ${name?.family ?? ''} ${ name?.suffix?.[0] ?? '' } `.trim() || name.text || diff --git a/src/utils/questionnaire.ts b/src/utils/questionnaire.ts index 746d8720..33159b51 100644 --- a/src/utils/questionnaire.ts +++ b/src/utils/questionnaire.ts @@ -5,19 +5,31 @@ import * as yup from 'yup'; import { Questionnaire, QuestionnaireItemAnswerOption, + QuestionnaireItemChoiceColumn, QuestionnaireResponseItemAnswer, QuestionnaireResponseItemAnswerValue, } from '@beda.software/aidbox-types'; import { parseFHIRTime } from '@beda.software/fhir-react'; import { formatHumanDate, formatHumanDateTime } from './date'; +import fhirpath from 'fhirpath'; -export function getDisplay(value?: QuestionnaireResponseItemAnswerValue): string | number | null { +export function getDisplay( + value?: QuestionnaireResponseItemAnswerValue, + choiceColumn?: QuestionnaireItemChoiceColumn[], +): string | number | null { if (!value) { return null; } if (value.Coding) { + if (choiceColumn && choiceColumn.length) { + const expression = choiceColumn[0]!.path; + if (expression) { + const calculatedValue = fhirpath.evaluate(value.Coding, expression)[0]; + return calculatedValue ?? ''; + } + } return value.Coding.display ?? ''; }