From 9fc96f1a0babfba982aa20e8360a4cbbc103907a Mon Sep 17 00:00:00 2001 From: Norbert Csaba Herczeg Date: Thu, 31 Oct 2024 17:12:30 +0100 Subject: [PATCH] JNG-5994 typeahead enum combo --- .../action_group_test__god/pom.xml | 1 + .../Matter/Form/ViewMatterForm.tsx.snapshot | 273 ++++++++++++++++++ .../ui/generator/react/UiImportHelper.java | 2 +- .../widget-fragments/enumerationcombo.hbs | 90 +++--- 4 files changed, 325 insertions(+), 41 deletions(-) create mode 100644 judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Matter/Form/ViewMatterForm.tsx.snapshot diff --git a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/pom.xml b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/pom.xml index 88fbc48f..c3f22d6b 100644 --- a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/pom.xml +++ b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/pom.xml @@ -227,6 +227,7 @@ src/containers/View/Galaxy/View/ViewGalaxyView.tsx src/containers/View/Galaxy/View/ViewGalaxyViewDialogContainer.tsx src/containers/View/Galaxy/View/ViewGalaxyViewPageContainer.tsx + src/containers/View/Matter/Form/ViewMatterForm.tsx src/containers/Planet/View/PlanetView.tsx src/containers/Planet/View/PlanetViewPageContainer.tsx src/pages/God/God/Earth/AccessViewPage/index.tsx diff --git a/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Matter/Form/ViewMatterForm.tsx.snapshot b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Matter/Form/ViewMatterForm.tsx.snapshot new file mode 100644 index 00000000..664c5d7b --- /dev/null +++ b/judo-ui-react-itest/ActionGroupTest/action_group_test__god/src/test/resources/snapshots/frontend-react/src/containers/View/Matter/Form/ViewMatterForm.tsx.snapshot @@ -0,0 +1,273 @@ +////////////////////////////////////////////////////////////////////////////// +// G E N E R A T E D S O U R C E +// -------------------------------- +// Factory expression: #application.pageContainers +// Path expression: 'src/containers/'+#containerPath(#self)+'/'+#containerComponentName(#self)+'.tsx' +// Template name: actor/src/containers/container.tsx +// Template file: actor/src/containers/container.tsx.hbs + +import LoadingButton from '@mui/lab/LoadingButton'; +import Autocomplete from '@mui/material/Autocomplete'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import ButtonGroup from '@mui/material/ButtonGroup'; +import Container from '@mui/material/Container'; +import Grid from '@mui/material/Grid'; +import IconButton from '@mui/material/IconButton'; +import InputAdornment from '@mui/material/InputAdornment'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; +import { OBJECTCLASS } from '@pandino/pandino-api'; +import { useTrackService } from '@pandino/react-hooks'; +import { clsx } from 'clsx'; +import type { Dispatch, FC, SetStateAction } from 'react'; +import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { DropdownButton, MdiIcon, useJudoNavigation } from '~/components'; +import { useConfirmDialog } from '~/components/dialog'; +import { useL10N } from '~/l10n/l10n-context'; +import type { EnumOption } from '~/utilities'; +import { isErrorOperationFault, useErrorHandler } from '~/utilities'; + +import {} from '@mui/x-date-pickers'; +import type {} from '@mui/x-date-pickers'; +import { NumericInput } from '~/components/widgets'; +import { Tags } from '~/components/widgets'; +import { autoFocusRefDelay } from '~/config'; +import { useConfirmationBeforeChange } from '~/hooks'; +import type { MatterType } from '~/services/data-api/model/MatterType'; +import type { ViewMatter, ViewMatterStored } from '~/services/data-api/model/ViewMatter'; +import { VIEW_MATTER_FORM_CONTAINER_ACTIONS_HOOK_INTERFACE_KEY } from './customization'; +import type { ViewMatterFormContainerHook } from './customization'; +import type { ViewMatterFormActionDefinitions, ViewMatterFormProps } from './types'; + +// XMIID: God/(esm/_D0xk0Oq_EeuMzos2n42msw)/TransferObjectFormPageContainer +// Name: View::Matter::Form +export default function ViewMatterForm(props: ViewMatterFormProps) { + // Container props + const { + refreshCounter, + isLoading, + isDraft, + dataPath, + actions: pageActions, + data, + isFormUpdateable, + isFormDeleteable, + storeDiff, + editMode, + validation, + setValidation, + submit, + } = props; + + // Container hooks + const { t } = useTranslation(); + const { navigate, back } = useJudoNavigation(); + const { locale: l10nLocale } = useL10N(); + const { openConfirmDialog } = useConfirmDialog(); + + useConfirmationBeforeChange( + editMode, + t('judo.form.navigation.confirmation', { + defaultValue: 'You have potential unsaved changes in your form, are you sure you would like to navigate away?', + }), + ); + // Pandino Container Action overrides + const { service: customContainerHook } = useTrackService( + `(${OBJECTCLASS}=${VIEW_MATTER_FORM_CONTAINER_ACTIONS_HOOK_INTERFACE_KEY})`, + ); + const containerActions: ViewMatterFormActionDefinitions = customContainerHook?.(data, editMode, storeDiff) || {}; + const actions = useMemo(() => ({ ...pageActions, ...containerActions }), [pageActions, containerActions]); + const typeOptions = useMemo[]>( + () => [ + { + id: 'God/(esm/_zK1VMORqEeuSU8xLq1yYbw)/EnumerationTypeMember', + value: 'dark', + i18nKey: 'enumerations.MatterType.dark', + i18nDefaultValue: 'dark', + }, + { + id: 'God/(esm/_0PAvAORqEeuSU8xLq1yYbw)/EnumerationTypeMember', + value: 'interstellarMedium', + i18nKey: 'enumerations.MatterType.interstellarMedium', + i18nDefaultValue: 'interstellarMedium', + }, + { + id: 'God/(esm/_17vWkORqEeuSU8xLq1yYbw)/EnumerationTypeMember', + value: 'intergalacticDust', + i18nKey: 'enumerations.MatterType.intergalacticDust', + i18nDefaultValue: 'intergalacticDust', + }, + ], + [], + ); + const effectiveTypeOptions = useMemo[]>(() => { + if (actions.filterTypeOptions) { + return actions.filterTypeOptions(data, typeOptions); + } + return typeOptions; + }, [actions.filterTypeOptions]); + const autoFocusInputRef = useRef(null); + + useEffect(() => { + const timeout = setTimeout(() => { + if (typeof autoFocusInputRef?.current?.focus === 'function') { + autoFocusInputRef.current.focus(); + } + }, autoFocusRefDelay); + + return () => clearTimeout(timeout); + }, []); + + return ( + + + + + + + ({ + label: t(o.i18nKey, { defaultValue: o.i18nDefaultValue }), + value: o.value, + key: o.id, + })), + ]} + isOptionEqualToValue={(option) => option.value === data.type} + getOptionKey={(option: any) => option.key} + getOptionLabel={(option: any) => { + if (typeof option === 'string') { + const o = effectiveTypeOptions.find((o) => o.value === option)!; + return t(o.i18nKey, { defaultValue: o.i18nDefaultValue }); + } + return option.label; + }} + onChange={(_: any, newValue: { label: string; value: string | null; key: string } | null) => { + storeDiff('type', newValue?.value || null); + }} + disableClearable={actions?.isTypeRequired ? actions.isTypeRequired(data, editMode) : false} + renderInput={(params) => ( + + + + ), + }} + className={clsx({ + 'JUDO-viewMode': !editMode, + 'JUDO-required': actions?.isTypeRequired ? actions.isTypeRequired(data, editMode) : true, + })} + /> + )} + /> + + + + { + const newValue = values.floatValue === undefined ? null : values.floatValue; + if (data.mass !== newValue) { + storeDiff('mass', newValue); + } + }} + InputLabelProps={{ shrink: true }} + InputProps={{ + readOnly: actions?.isMassReadonly + ? actions.isMassReadonly(data, editMode, isLoading) + : false || !isFormUpdateable(), + startAdornment: ( + + + + ), + }} + /> + + + + { + const realValue = event.target.value?.length === 0 ? null : event.target.value; + storeDiff('shortNote', realValue); + }} + InputLabelProps={{ shrink: true }} + InputProps={{ + readOnly: actions?.isShortNoteReadonly + ? actions.isShortNoteReadonly(data, editMode, isLoading) + : false || !isFormUpdateable(), + startAdornment: ( + + + + ), + }} + inputProps={{ + maxLength: 127, + }} + /> + + + + + + + ); +} diff --git a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiImportHelper.java b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiImportHelper.java index ab074759..a82267cd 100644 --- a/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiImportHelper.java +++ b/judo-ui-react/src/main/java/hu/blackbelt/judo/ui/generator/react/UiImportHelper.java @@ -43,7 +43,7 @@ public class UiImportHelper { Map.entry("dateinput", Set.of("InputAdornment")), Map.entry("datetimeinput", Set.of("InputAdornment")), Map.entry("divider", Set.of("Divider")), - Map.entry("enumerationcombo", Set.of("TextField", "MenuItem", "InputAdornment")), + Map.entry("enumerationcombo", Set.of("Autocomplete", "TextField", "InputAdornment")), Map.entry("enumerationradio", Set.of("RadioGroup", "FormControlLabel", "Radio", "FormControl", "FormHelperText", "InputLabel")), Map.entry("formatted", Set.of("Typography")), Map.entry("label", Set.of("Typography")), diff --git a/judo-ui-react/src/main/resources/actor/src/containers/widget-fragments/enumerationcombo.hbs b/judo-ui-react/src/main/resources/actor/src/containers/widget-fragments/enumerationcombo.hbs index 72dc632c..4d026c71 100644 --- a/judo-ui-react/src/main/resources/actor/src/containers/widget-fragments/enumerationcombo.hbs +++ b/judo-ui-react/src/main/resources/actor/src/containers/widget-fragments/enumerationcombo.hbs @@ -11,56 +11,66 @@ actions={actions} > {{/ if }} - ({ + label: t(o.i18nKey, { defaultValue: o.i18nDefaultValue }), + value: o.value, + key: o.id, + })) + ]} + isOptionEqualToValue={ (option) => option.value === data.{{ child.attributeType.name }} } + getOptionKey={(option: any) => option.key } + getOptionLabel={(option: any) => { + if (typeof option === 'string') { + const o = effective{{ firstToUpper child.attributeType.name }}Options.find(o => o.value === option)!; + return t(o.i18nKey, { defaultValue: o.i18nDefaultValue }) + } + return option.label; + } } + onChange={(_: any, newValue: { label: string, value: string | null, key: string } | null) => { + storeDiff('{{ child.attributeType.name }}', newValue?.value || null); + }} + disableClearable={actions?.is{{ firstToUpper child.attributeType.name }}Required ? actions.is{{ firstToUpper child.attributeType.name }}Required(data, editMode) : false} + renderInput={(params) => { + onBlur={ () => { if (actions?.on{{ firstToUpper child.attributeType.name }}BlurAction) { - actions.on{{ firstToUpper child.attributeType.name }}BlurAction(data, storeDiff, editMode, submit); + actions.on{{ firstToUpper child.attributeType.name }}BlurAction(data, storeDiff, editMode, submit); } - } } + } } {{/ if }} - onChange={ (event) => { - storeDiff('{{ child.attributeType.name }}', event.target.value); - } } + error={ !!validation.get('{{ child.attributeType.name }}') } + helperText={ validation.get('{{ child.attributeType.name }}') } InputLabelProps={ { shrink: true } } InputProps={ { - readOnly: actions?.is{{ firstToUpper child.attributeType.name }}Readonly ? actions.is{{ firstToUpper child.attributeType.name }}Readonly(data, editMode, isLoading) : ({{ boolValue child.attributeType.isReadOnly }} || !isFormUpdateable()), - {{# if child.icon }} - startAdornment: ( - - - - ), - {{/ if }} + ...params.InputProps, + {{# if child.icon }} + startAdornment: ( + + + + ), + {{/ if }} } } - select - > - {!(actions?.is{{ firstToUpper child.attributeType.name }}Required ? actions.is{{ firstToUpper child.attributeType.name }}Required(data, editMode) : ({{# if child.requiredBy }}data.{{ child.requiredBy.name }} ||{{/ if }} {{ boolValue child.attributeType.isRequired }})) && ( - - {t('enumerations.{{ restParamName child.attributeType.dataType }}._null', { defaultValue: 'None' })} - - )} - {effective{{ firstToUpper child.attributeType.name }}Options.map((o) => ( - - {t(o.i18nKey, { defaultValue: o.i18nDefaultValue })} - - ))} - + className={clsx({ + 'JUDO-viewMode': !editMode, + 'JUDO-required': actions?.is{{ firstToUpper child.attributeType.name }}Required ? actions.is{{ firstToUpper child.attributeType.name }}Required(data, editMode) : {{ boolValue child.attributeType.isRequired }}, + })} + />} + /> {{# if child.customImplementation }} {{/ if }}