Skip to content

Commit b396760

Browse files
Merge pull request #5335 from 3drepo/ISSUE_5266
ISSUE #5266 - Ticket filters: select filter form
2 parents 1b1e655 + 443c80b commit b396760

File tree

10 files changed

+112
-37
lines changed

10 files changed

+112
-37
lines changed

frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import { selectCurrentModel } from '@/v4/modules/model';
1919
import { SequencingProperties, TicketsCardViews } from '@/v5/ui/routes/viewer/tickets/tickets.constants';
2020
import { createSelector } from 'reselect';
21-
import { selectTemplateById, selectTemplates, selectTicketById, selectTickets } from '../tickets.selectors';
21+
import { selectRiskCategories, selectTemplateById, selectTemplates, selectTicketById, selectTickets } from '../tickets.selectors';
2222
import { ITicketsCardState } from './ticketsCard.redux';
2323
import { DEFAULT_PIN, getPinColorHex, formatPin, getTicketPins } from '@/v5/ui/routes/viewer/tickets/ticketsForm/properties/coordsProperty/coordsProperty.helpers';
2424
import { IPin } from '@/v4/services/viewer/viewer';
@@ -197,3 +197,24 @@ export const selectNewTicketPins = createSelector(
197197
selectSelectedTicketPinId,
198198
getTicketPins,
199199
);
200+
201+
export const selectPropertyOptions = createSelector(
202+
selectTemplates,
203+
selectRiskCategories,
204+
(state, modelId, module) => module,
205+
(state, modelId, module, property) => property,
206+
(templates, riskCategories, module, property) => {
207+
const allValues = [];
208+
templates.forEach((template) => {
209+
const matchingModule = module ? template.modules.find((mod) => (mod.name || mod.type) === module)?.properties : template.properties;
210+
const matchingProperty = matchingModule?.find(({ name, type: t }) => (name === property) && (['manyOf', 'oneOf'].includes(t)));
211+
if (!matchingProperty) return;
212+
if (matchingProperty.values === 'riskCategories') {
213+
allValues.push(...riskCategories);
214+
return;
215+
}
216+
allValues.push(...matchingProperty.values);
217+
});
218+
return uniq(allValues);
219+
},
220+
);

frontend/src/v5/store/tickets/tickets.selectors.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ export const selectTemplates = createSelector(
5252
(state, modelId) => state.templatesByModelId[modelId] || [],
5353
);
5454

55+
export const selectTemplatesNames = createSelector(
56+
selectTemplates,
57+
(templates) => templates.map(({ name }) => name),
58+
);
59+
5560
export const selectTemplateById = createSelector(
5661
selectTicketsDomain,
5762
selectTemplates,

frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.helpers.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,23 @@ const DATE_FILTER_OPERATOR_LABEL: Record<CardFilterOperator, string> = {
6868
};
6969

7070
export const isDateType = (type: CardFilterType) => ['date', 'pastDate', 'sequencing'].includes(type);
71-
export const isTextType = (type: CardFilterType) => ['template', 'ticketId', 'ticketTitle', 'text', 'longText'].includes(type);
71+
export const isTextType = (type: CardFilterType) => ['ticketId', 'ticketTitle', 'text', 'longText'].includes(type);
72+
export const isSelectType = (type: CardFilterType) => ['template', 'oneOf', 'manyOf'].includes(type);
7273

7374
export const getFilterOperatorLabels = (type: CardFilterType) => isDateType(type) ? DATE_FILTER_OPERATOR_LABEL : FILTER_OPERATOR_LABEL;
7475

7576
export const getFilterFormTitle = (elements: string[]) => compact(elements).join(' : ');
7677

7778
export const isRangeOperator = (operator: CardFilterOperator) => ['rng', 'nrng'].includes(operator);
79+
80+
7881
export const getValidOperators = (type: CardFilterType): CardFilterOperator[] => {
7982
if (isTextType(type)) return ['eq', 'neq', 'ss', 'nss', 'ex', 'nex'];
8083
if (type === 'number') return ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'rng', 'nrng', 'ex', 'nex'];
8184
if (isDateType(type)) return ['eq', 'lte', 'gte', 'rng', 'nrng', 'ex', 'nex'];
85+
if (isSelectType(type)) {
86+
if (type === 'template') return ['eq', 'neq'];
87+
return ['eq', 'neq', 'ex', 'nex'];
88+
}
8289
return Object.keys(FILTER_OPERATOR_LABEL) as CardFilterOperator[];
8390
};

frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
export type CardFilterOperator = 'ex' | 'nex' | 'eq' | 'neq' | 'ss' | 'nss' | 'rng' | 'nrng' | 'gt' | 'gte' | 'lt' | 'lte';
1919
export type CardFilterType = 'text' | 'longText' | 'date' | 'sequencing' | 'pastDate' | 'oneOf' | 'manyOf' | 'boolean' | 'number' | 'ticketTitle' | 'ticketId' | 'template';
2020
type ValueType = string | number | Date;
21-
export type CardFilterValue = ValueType | [ValueType, ValueType];
21+
export type CardFilterValue = ValueType | ValueType[];
2222
export type BaseFilter = { operator: CardFilterOperator, values: CardFilterValue[] };
2323

2424
export type CardFilter = {

frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.component.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ export const FilterForm = ({ module, property, type, filter, onSubmit, onCancel
7474
{getFilterFormTitle([module, property])}
7575
</TitleContainer>
7676
<FilterFormOperators type={type} />
77-
<FilterFormValues type={type} />
77+
{property && (
78+
<FilterFormValues module={module} property={property} type={type} />
79+
)}
7880
<ButtonsContainer>
7981
<Button onClick={handleCancel} color="secondary">
8082
{isUpdatingFilter

frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,25 @@
1717

1818
import { useFieldArray, useFormContext } from 'react-hook-form';
1919
import { getOperatorMaxFieldsAllowed } from '../filterForm.helpers';
20-
import { isRangeOperator, isDateType, isTextType } from '../../cardFilters.helpers';
21-
import { FormDateTime, FormNumberField, FormTextField } from '@controls/inputs/formInputs.component';
20+
import { isRangeOperator, isTextType, isSelectType, isDateType } from '../../cardFilters.helpers';
21+
import { FormNumberField, FormTextField, FormMultiSelect, FormDateTime } from '@controls/inputs/formInputs.component';
2222
import { ArrayFieldContainer } from '@controls/inputs/arrayFieldContainer/arrayFieldContainer.component';
2323
import { useEffect } from 'react';
24-
import { isArray, isEmpty } from 'lodash';
24+
import { compact, isArray, isEmpty } from 'lodash';
2525
import { CardFilterType } from '../../cardFilters.types';
26-
import { NumberRangeInput } from './rangeInput/numberRangeInput.component';
26+
import { TicketsCardHooksSelectors, TicketsHooksSelectors } from '@/v5/services/selectorsHooks';
27+
import { useParams } from 'react-router-dom';
28+
import { ViewerParams } from '@/v5/ui/routes/routes.constants';
29+
import { MultiSelectMenuItem } from '@controls/inputs/multiSelect/multiSelectMenuItem/multiSelectMenuItem.component';
2730
import { DateRangeInput } from './rangeInput/dateRangeInput.component';
31+
import { NumberRangeInput } from './rangeInput/numberRangeInput.component';
32+
import { mapArrayToFormArray, mapFormArrayToArray } from '@/v5/helpers/form.helper';
33+
34+
type FilterFolrmValuesType = {
35+
module: string,
36+
property: string,
37+
type: CardFilterType,
38+
};
2839

2940
const getInputField = (type: CardFilterType) => {
3041
if (type === 'number') return FormNumberField;
@@ -33,17 +44,22 @@ const getInputField = (type: CardFilterType) => {
3344
};
3445

3546
const name = 'values';
36-
export const FilterFormValues = ({ type }: { type: CardFilterType }) => {
47+
export const FilterFormValues = ({ module, property, type }: FilterFolrmValuesType) => {
48+
const { containerOrFederation } = useParams<ViewerParams>();
3749
const { control, watch, formState: { errors, dirtyFields } } = useFormContext();
3850
const { fields, append, remove } = useFieldArray({
3951
control,
4052
name,
4153
});
4254
const error = errors.values || {};
4355
const operator = watch('operator');
56+
4457
const maxFields = getOperatorMaxFieldsAllowed(operator);
4558
const isRangeOp = isRangeOperator(operator);
46-
const emptyValue = { value: isRangeOp ? ['', ''] : '' };
59+
const emptyValue = { value: (isRangeOp ? ['', ''] : '') };
60+
const selectOptions = type === 'template' ?
61+
TicketsHooksSelectors.selectTemplatesNames(containerOrFederation)
62+
: TicketsCardHooksSelectors.selectPropertyOptions(containerOrFederation, module, property);
4763

4864
useEffect(() => {
4965
if (!fields.length && maxFields > 0) {
@@ -106,6 +122,18 @@ export const FilterFormValues = ({ type }: { type: CardFilterType }) => {
106122
</>
107123
);
108124
}
125+
if (isSelectType(type)) {
126+
return (
127+
<FormMultiSelect
128+
name={name}
129+
formError={error?.[0]}
130+
transformValueIn={mapFormArrayToArray}
131+
transformChangeEvent={(e) => mapArrayToFormArray(compact(e.target.value))}
132+
>
133+
{(selectOptions || []).map((val) => <MultiSelectMenuItem key={val} value={val}>{val}</MultiSelectMenuItem>)}
134+
</FormMultiSelect>
135+
);
136+
}
109137

110138
return (
111139
<>

frontend/src/v5/ui/controls/inputs/formInputs.component.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { TextArea, TextAreaProps } from './textArea/textArea.component';
2929
import { TextAreaFixedSize, TextAreaFixedSizeProps } from './textArea/textAreaFixedSize.component';
3030
import { TextField, TextFieldProps } from './textField/textField.component';
3131
import { DateTimePicker, DateTimePickerProps } from './datePicker/dateTimePicker.component';
32+
import { MultiSelect } from './multiSelect/multiSelect.component';
3233

3334
// text inputs
3435
export const FormNumberField = (props: InputControllerProps<TextFieldProps>) => (<InputController Input={NumberField} {...props} />);
@@ -44,6 +45,7 @@ export const FormDateTime = (props: InputControllerProps<DateTimePickerProps>) =
4445
// select inputs
4546
export const FormSelectView = (props: InputControllerProps<SelectViewProps>) => (<InputController Input={SelectView} {...props} />);
4647
export const FormSelect = (props: InputControllerProps<SelectProps>) => (<InputController Input={Select} {...props} />);
48+
export const FormMultiSelect = (props: InputControllerProps<SelectProps>) => (<InputController Input={MultiSelect} {...props} />);
4749
export const FormChipSelect = (props: InputControllerProps<ChipSelectProps>) => (<InputController Input={({ inputRef, ...chipProps }: any) => <ChipSelect {...chipProps} />} {...props} />);
4850
export const FormSearchSelect = (props: InputControllerProps<SelectProps>) => (<InputController Input={SearchSelect} {...props} />);
4951

frontend/src/v5/ui/controls/inputs/inputController.component.tsx

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,16 @@ export type InputControllerProps<T,> = T & FormInputProps & {
3636
defaultValue?: any,
3737
onChange?: (event) => void,
3838
onBlur?: () => void,
39+
transformValueIn?: (val) => any,
40+
transformChangeEvent?: (val) => any,
3941
children?: any,
4042
};
4143

4244
type Props<T> = InputControllerProps<T> & {
4345
Input: (props: T) => JSX.Element,
4446
};
4547

46-
type InputControllerType = <T>(Component: Props<T>, ref) => JSX.Element;
48+
export type InputControllerType = <T>(Component: Props<T>, ref) => JSX.Element;
4749
// eslint-disable-next-line @typescript-eslint/comma-dangle
4850
export const InputController: InputControllerType = forwardRef(<T,>({
4951
Input,
@@ -53,6 +55,8 @@ export const InputController: InputControllerType = forwardRef(<T,>({
5355
defaultValue,
5456
onChange,
5557
onBlur,
58+
transformValueIn = (val) => val,
59+
transformChangeEvent = (val) => val,
5660
...props
5761
}: Props<T>, ref) => {
5862
const ctx = useFormContext();
@@ -63,25 +67,27 @@ export const InputController: InputControllerType = forwardRef(<T,>({
6367
name={name}
6468
control={control}
6569
defaultValue={defaultValue}
66-
render={({ field: { ref: fieldRef, ...field } }) => (
70+
render={({ field: { ref: fieldRef, ...field } }) => {
71+
return (
6772
// @ts-ignore
68-
<Input
69-
{...field}
70-
{...props}
71-
value={field.value ?? ''}
72-
onChange={(event) => {
73-
field.onChange(event);
74-
onChange?.(event);
75-
}}
76-
onBlur={() => {
77-
field.onBlur();
78-
onBlur?.();
79-
}}
80-
inputRef={ref || fieldRef}
81-
error={!!error}
82-
helperText={error?.message}
83-
/>
84-
)}
73+
<Input
74+
{...field}
75+
{...props}
76+
value={transformValueIn(field.value) ?? ''}
77+
onChange={(event) => {
78+
field.onChange(transformChangeEvent(event));
79+
onChange?.(transformChangeEvent(event));
80+
}}
81+
onBlur={() => {
82+
field.onBlur();
83+
onBlur?.();
84+
}}
85+
inputRef={ref || fieldRef}
86+
error={!!error}
87+
helperText={error?.message}
88+
/>
89+
);
90+
}}
8591
/>
8692
);
8793
});

frontend/src/v5/ui/controls/inputs/multiSelect/multiSelect.component.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@
1717

1818
import { SearchSelect } from '@controls/searchSelect/searchSelect.component';
1919
import { SelectProps } from '../select/select.component';
20+
import { FormControl, FormHelperText } from '@mui/material';
2021

21-
export const MultiSelect = ({ defaultValue = [], ...props }: SelectProps) => (
22-
<SearchSelect
23-
defaultValue={defaultValue}
24-
renderValue={(val) => (val as any[]).join(', ')}
25-
{...props}
26-
multiple
27-
/>
22+
export const MultiSelect = ({ defaultValue = [], className, helperText, ...props }: SelectProps) => (
23+
<FormControl required={false} disabled={props.disabled} error={props.error} className={className}>
24+
<SearchSelect
25+
defaultValue={defaultValue}
26+
renderValue={(val) => (val as any[]).join(', ')}
27+
{...props}
28+
multiple
29+
/>
30+
<FormHelperText>{helperText}</FormHelperText>
31+
</FormControl>
2832
);

frontend/src/v5/validation/ticketSchemes/validators.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const getValueValidator = (type: CardFilterType) => {
3232
maxLength,
3333
formatMessage({
3434
defaultMessage: 'This must be at max {maxLength} characters',
35-
id: 'validators.text.maxLenght',
35+
id: 'validators.text.maxLength',
3636
}, { maxLength }),
3737
);
3838
}

0 commit comments

Comments
 (0)