From 7ac733d41e9cd1aafb5d12444968e4017f8d6806 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 15 Jan 2025 14:09:30 +0000 Subject: [PATCH 01/32] ISSUE #5271 - selector accepts multiple values --- frontend/src/v5/store/tickets/tickets.selectors.ts | 2 +- .../cardFilters/filterForm/filterForm.component.tsx | 5 +++-- .../filterFormValues/filterFormValues.component.tsx | 13 ++++++++++--- .../tickets/ticketFiltersSelection.component.tsx | 12 +++++++----- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/frontend/src/v5/store/tickets/tickets.selectors.ts b/frontend/src/v5/store/tickets/tickets.selectors.ts index bc0cb1509f..0c23d8d4ab 100644 --- a/frontend/src/v5/store/tickets/tickets.selectors.ts +++ b/frontend/src/v5/store/tickets/tickets.selectors.ts @@ -130,8 +130,8 @@ export const selectAllValuesByModuleAndProperty = createSelector( (state, modelId, module, property) => property, (state, modelId, module, property, type) => type, (templates, riskCategories, module, property, type) => { - if (type === 'template') return templates.map(({ name }) => name); if (!type) return; + if (type === 'template') return templates.map(({ name }) => name); const allValues = []; templates.forEach((template) => { const matchingModule = module ? template.modules.find((mod) => (mod.name || mod.type) === module)?.properties : template.properties; diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.component.tsx index a77edff3eb..5631323d95 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.component.tsx @@ -17,7 +17,7 @@ import { FormattedMessage } from 'react-intl'; import { CardFilterOperator, CardFilterValue, CardFilterType, BaseFilter, CardFilter } from '../cardFilters.types'; -import { getFilterFormTitle } from '../cardFilters.helpers'; +import { getFilterFormTitle, isSelectType } from '../cardFilters.helpers'; import { Container, ButtonsContainer, Button, TitleContainer } from './filterForm.styles'; import { FormProvider, useForm } from 'react-hook-form'; import { isEmpty } from 'lodash'; @@ -31,6 +31,7 @@ import { FilterFormOperators } from './filterFormValues/operators/filterFormOper const DEFAULT_OPERATOR = 'eq'; const DEFAULT_VALUES = ['']; +const DEFAULT_SELECT_VALUES = [[]]; type FormType = { values: { value: CardFilterValue }[], operator: CardFilterOperator }; type FilterFormProps = { module: string, @@ -43,7 +44,7 @@ type FilterFormProps = { export const FilterForm = ({ module, property, type, filter, onSubmit, onCancel }: FilterFormProps) => { const defaultValues = { operator: filter?.operator || DEFAULT_OPERATOR, - values: mapArrayToFormArray(filter?.values || DEFAULT_VALUES), + values: mapArrayToFormArray(filter?.values || (isSelectType(type) ? DEFAULT_SELECT_VALUES : DEFAULT_VALUES)), }; const formData = useForm({ diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx index 78e44defd5..0f4d5f5cb4 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx @@ -37,6 +37,7 @@ type FilterFolrmValuesType = { }; export const FilterFormValues = ({ module, property, type }: FilterFolrmValuesType) => { + if (!property) return null; const { containerOrFederation } = useParams(); const { control, watch, formState: { errors } } = useFormContext(); const { fields, append, remove } = useFieldArray({ @@ -47,7 +48,12 @@ export const FilterFormValues = ({ module, property, type }: FilterFolrmValuesTy const operator = watch('operator'); const maxFields = getOperatorMaxFieldsAllowed(operator); const isRangeOp = isRangeOperator(operator); - const emptyValue = { value: isRangeOp ? ['', ''] : '' }; + const getEmptyValue = () => { + if (isRangeOp) return ['', '']; + if (isSelectType(type)) return []; + return ''; + }; + const emptyValue = { value: getEmptyValue() }; const selectOptions = TicketsHooksSelectors.selectAllValuesByModuleAndProperty(containerOrFederation, module, property, type); useEffect(() => { @@ -106,9 +112,10 @@ export const FilterFormValues = ({ module, property, type }: FilterFolrmValuesTy ); } - if (isSelectType(type)) { + // @ts-ignore + if (isSelectType(type) && isArray(fields[0]?.value)) { return ( - + {selectOptions.map((val) => {val})} ); diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFiltersSelection.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFiltersSelection.component.tsx index 684e2c2630..67cfded1ef 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFiltersSelection.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFiltersSelection.component.tsx @@ -71,11 +71,13 @@ export const FilterSelection = () => { - + {selectedFilter && ( + + )} From cda906ff14e61ede500844cd75bca45951cac66d Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Thu, 23 Jan 2025 16:31:08 +0000 Subject: [PATCH 02/32] ISSUE #5271 - owner filter uses assignees select for value --- .../viewer/cards/cardFilters/cardFilters.helpers.ts | 4 ++-- .../components/viewer/cards/cardFilters/cardFilters.types.ts | 2 +- .../filtersSelection/tickets/ticketFilters.helpers.ts | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.helpers.ts b/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.helpers.ts index 7f577504d2..a3a91e25a5 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.helpers.ts +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.helpers.ts @@ -69,7 +69,7 @@ const DATE_FILTER_OPERATOR_LABEL: Record = { export const isDateType = (type: CardFilterType) => ['date', 'pastDate', 'sequencing'].includes(type); export const isTextType = (type: CardFilterType) => ['template', 'ticketId', 'ticketTitle', 'text', 'longText'].includes(type); -export const isSelectType = (type: CardFilterType) => ['template', 'oneOf', 'manyOf'].includes(type); +export const isSelectType = (type: CardFilterType) => ['template', 'owner', 'oneOf', 'manyOf'].includes(type); export const getFilterOperatorLabels = (type: CardFilterType) => isDateType(type) ? DATE_FILTER_OPERATOR_LABEL : FILTER_OPERATOR_LABEL; @@ -83,7 +83,7 @@ export const getValidOperators = (type: CardFilterType): CardFilterOperator[] => if (type === 'number') return ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'rng', 'nrng', 'ex', 'nex']; if (isDateType(type)) return ['eq', 'lte', 'gte', 'rng', 'nrng', 'ex', 'nex']; if (isSelectType(type)) { - if (type === 'template') return ['eq', 'neq']; + if (['template', 'owner'].includes(type)) return ['eq', 'neq']; return ['eq', 'neq', 'ex', 'nex']; } return Object.keys(FILTER_OPERATOR_LABEL) as CardFilterOperator[]; diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.types.ts b/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.types.ts index 2b1196657b..c1ec5fabab 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.types.ts +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.types.ts @@ -16,7 +16,7 @@ */ export type CardFilterOperator = 'ex' | 'nex' | 'eq' | 'neq' | 'ss' | 'nss' | 'rng' | 'nrng' | 'gt' | 'gte' | 'lt' | 'lte'; -export type CardFilterType = 'text' | 'longText' | 'date' | 'sequencing' | 'pastDate' | 'oneOf' | 'manyOf' | 'boolean' | 'number' | 'ticketTitle' | 'ticketId' | 'template'; +export type CardFilterType = 'text' | 'longText' | 'date' | 'sequencing' | 'pastDate' | 'oneOf' | 'manyOf' | 'boolean' | 'number' | 'ticketTitle' | 'ticketId' | 'template' | 'owner'; type ValueType = string | number | Date; export type CardFilterValue = ValueType | ValueType[]; export type BaseFilter = { operator: CardFilterOperator, values: CardFilterValue[] }; diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts index 159d99d2c8..f82d678d89 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts @@ -37,6 +37,7 @@ export const TYPE_TO_ICON: Record = { 'sequencing': CalendarIcon, 'oneOf': ListIcon, 'manyOf': ListIcon, + 'owner': ListIcon, 'boolean': BooleanIcon, 'number': NumberIcon, }; @@ -45,6 +46,7 @@ const DEFAULT_FILTERS: CardFilter[] = [ { module: '', type: 'ticketTitle', property: formatMessage({ defaultMessage: 'Ticket title', id: 'viewer.card.filters.element.ticketTitle' }) }, { module: '', type: 'ticketId', property: formatMessage({ defaultMessage: 'Ticket ID', id: 'viewer.card.filters.element.ticketId' }) }, { module: '', type: 'template', property: formatMessage({ defaultMessage: 'Ticket template', id: 'viewer.card.filters.element.ticketTemplate' }) }, + { module: '', type: 'owner', property: formatMessage({ defaultMessage: 'Owner', id: 'viewer.card.filters.element.ticketTemplate' }) }, ]; const propertiesToValidFilters = (properties: { name: string, type: string }[], module: string = ''): CardFilter[] => properties From eb0cbf92f22331cf5746e5c3948490d599f00c69 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Fri, 31 Jan 2025 11:52:01 +0000 Subject: [PATCH 03/32] ISSUE #5271 - remove duplicate Owner with text type --- .../filtersSelection/tickets/ticketFilters.helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts index f82d678d89..e39c34b97b 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts @@ -58,7 +58,7 @@ const propertiesToValidFilters = (properties: { name: string, type: string }[], }) as CardFilter); const templateToFilters = (template: ITemplate): CardFilter[] => [ - ...propertiesToValidFilters(template.properties, ''), + ...propertiesToValidFilters(template.properties.filter(({ name }) => name !== 'Owner'), ''), ...template.modules.flatMap(({ properties, name, type }) => propertiesToValidFilters(properties, name || type)), ]; From 57423aa3d97748739e83d4c9864469c9305ddda7 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Fri, 31 Jan 2025 18:24:50 +0000 Subject: [PATCH 04/32] ISSUE #5271 - jobsAndUsers types use assignee select circles input --- frontend/src/v5/store/jobs/jobs.types.ts | 4 ++ .../tickets/card/ticketsCard.selectors.ts | 27 +++++++++-- .../filterFormValues.component.tsx | 19 ++++++-- .../assigneesSelect.component.tsx | 46 ++++++++++--------- .../controls/inputs/formInputs.component.tsx | 3 +- 5 files changed, 71 insertions(+), 28 deletions(-) diff --git a/frontend/src/v5/store/jobs/jobs.types.ts b/frontend/src/v5/store/jobs/jobs.types.ts index 205657fe4b..1d596517c1 100644 --- a/frontend/src/v5/store/jobs/jobs.types.ts +++ b/frontend/src/v5/store/jobs/jobs.types.ts @@ -15,8 +15,12 @@ * along with this program. If not, see . */ +import { IUser } from '../users/users.redux'; + export type IJob = { _id: string; color: string; isViewer?: boolean; }; + +export type IJobOrUserList = Partial[]; \ No newline at end of file diff --git a/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts b/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts index ea0042e2b3..58a3cd6a28 100644 --- a/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts +++ b/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts @@ -25,6 +25,9 @@ import { IPin } from '@/v4/services/viewer/viewer'; import { selectSelectedDate } from '@/v4/modules/sequences'; import { uniq } from 'lodash'; import { toTicketCardFilter, templatesToFilters } from '@components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers'; +import { selectFederationById, selectFederationJobs, selectFederationUsers } from '../../federations/federations.selectors'; +import { selectContainerJobs, selectContainerUsers } from '../../containers/containers.selectors'; +import { IJobOrUserList } from '../../jobs/jobs.types'; const selectTicketsCardDomain = (state): ITicketsCardState => state.ticketsCard || {}; @@ -197,23 +200,41 @@ export const selectNewTicketPins = createSelector( selectSelectedTicketPinId, getTicketPins, ); +const selectJobsAndUsersByModelId = createSelector( + selectFederationById, + selectFederationJobs, + selectContainerJobs, + selectFederationUsers, + selectContainerUsers, + (fed, fedJobs, contJobs, fedUsers, contUsers) => { + const isFed = !!fed; + const jobs = isFed ? fedJobs : contJobs; + const users = isFed ? fedUsers : contUsers; + return [...jobs, ...users] as IJobOrUserList; + }, +); export const selectPropertyOptions = createSelector( selectTemplates, selectRiskCategories, + selectJobsAndUsersByModelId, (state, modelId, module) => module, (state, modelId, module, property) => property, - (templates, riskCategories, module, property) => { + (templates, riskCategories, jobsAndUsers, module, property) => { const allValues = []; templates.forEach((template) => { const matchingModule = module ? template.modules.find((mod) => (mod.name || mod.type) === module)?.properties : template.properties; const matchingProperty = matchingModule?.find(({ name, type: t }) => (name === property) && (['manyOf', 'oneOf'].includes(t))); if (!matchingProperty) return; if (matchingProperty.values === 'riskCategories') { - allValues.push(...riskCategories); + allValues.push(...riskCategories.map((value) => ({ value, type: 'riskCategories' }))); + return; + } + if (matchingProperty.values === 'jobsAndUsers') { + allValues.push(...jobsAndUsers.map((ju) => ({ value: ju?.user || ju?._id, type: 'jobsAndUsers' }))); return; } - allValues.push(...matchingProperty.values); + allValues.push(...matchingProperty.values.map((value) => ({ value, type: 'default' }))); }); return uniq(allValues); }, diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx index 74eb96d8d7..51a211f443 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx @@ -18,7 +18,7 @@ import { useFieldArray, useFormContext } from 'react-hook-form'; import { getOperatorMaxFieldsAllowed } from '../filterForm.helpers'; import { isRangeOperator, isTextType, isSelectType, isDateType } from '../../cardFilters.helpers'; -import { FormNumberField, FormTextField, FormMultiSelect, FormDateTime } from '@controls/inputs/formInputs.component'; +import { FormNumberField, FormTextField, FormMultiSelect, FormDateTime, FormAssigneesSelect } from '@controls/inputs/formInputs.component'; import { ArrayFieldContainer } from '@controls/inputs/arrayFieldContainer/arrayFieldContainer.component'; import { useEffect } from 'react'; import { compact, isArray, isEmpty } from 'lodash'; @@ -123,15 +123,28 @@ export const FilterFormValues = ({ module, property, type }: FilterFolrmValuesTy ); } + if (isSelectType(type)) { + const allJobsAndUsers = selectOptions.every(({ type: t }) => t === 'jobsAndUsers'); + if (allJobsAndUsers || type === 'owner') return ( + compact(mapFormArrayToArray(v))} + transformChangeEvent={(e) => mapArrayToFormArray(compact(e.target.value))} + formError={error?.[0]} + /> + ); return ( mapArrayToFormArray(compact(e.target.value))} + formError={error?.[0]} > - {(selectOptions || []).map((val) => {val})} + {(selectOptions || []).map(({ value: val }) => {val})} ); } diff --git a/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelect.component.tsx b/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelect.component.tsx index e203b50158..3e2fca0ba8 100644 --- a/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelect.component.tsx +++ b/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelect.component.tsx @@ -26,6 +26,7 @@ import { TicketContext } from '../../routes/viewer/tickets/ticket.context'; import { Spinner } from '@controls/spinnerLoader/spinnerLoader.styles'; import { AssigneesValuesDisplay } from './assigneeValuesDisplay/assigneeValuesDisplay.component'; import { getInvalidValues, getModelJobsAndUsers, getValidValues } from './assignees.helpers'; +import { FormControl, FormHelperText } from '@mui/material'; export type AssigneesSelectProps = Pick & SelectProps & { maxItems?: number; @@ -45,6 +46,7 @@ export const AssigneesSelect = ({ onBlur, className, excludeViewers = false, + helperText, onChange, ...props }: AssigneesSelectProps) => { @@ -89,26 +91,28 @@ export const AssigneesSelect = ({ ); return ( - - - invalidValues.includes(v)} - onChange={handleChange} - {...props} - /> - - - + + + + invalidValues.includes(v)} + onChange={handleChange} + {...props} + /> + + + {helperText} + + ); }; diff --git a/frontend/src/v5/ui/controls/inputs/formInputs.component.tsx b/frontend/src/v5/ui/controls/inputs/formInputs.component.tsx index 95a7b0167c..c1d0b7b573 100644 --- a/frontend/src/v5/ui/controls/inputs/formInputs.component.tsx +++ b/frontend/src/v5/ui/controls/inputs/formInputs.component.tsx @@ -30,6 +30,7 @@ import { TextAreaFixedSize, TextAreaFixedSizeProps } from './textArea/textAreaFi import { TextField, TextFieldProps } from './textField/textField.component'; import { DateTimePicker, DateTimePickerProps } from './datePicker/dateTimePicker.component'; import { MultiSelect } from './multiSelect/multiSelect.component'; +import { AssigneesSelect, AssigneesSelectProps } from '@controls/assigneesSelect/assigneesSelect.component'; // text inputs export const FormNumberField = (props: InputControllerProps) => (); @@ -48,6 +49,6 @@ export const FormSelect = (props: InputControllerProps) => () => (); export const FormChipSelect = (props: InputControllerProps) => ( } {...props} />); export const FormSearchSelect = (props: InputControllerProps) => (); - +export const FormAssigneesSelect = (props: InputControllerProps) => (); // control inputs export const FormCheckbox = (props: InputControllerProps) => ( } {...props} />); From 67a3e35e5d3b9db3c186b90d85eeb1eef82f45f9 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Mon, 3 Feb 2025 13:37:18 +0000 Subject: [PATCH 05/32] ISSUE #5271 - options values are sorted --- frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts b/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts index 58a3cd6a28..e1ca242024 100644 --- a/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts +++ b/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts @@ -236,6 +236,6 @@ export const selectPropertyOptions = createSelector( } allValues.push(...matchingProperty.values.map((value) => ({ value, type: 'default' }))); }); - return uniq(allValues); + return sortedUniq(sortBy(allValues, ({ value }) => value)); }, ); From 227f6790755943187793e3e500a6c0fa848e1ee9 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Mon, 3 Feb 2025 13:49:27 +0000 Subject: [PATCH 06/32] ISSUE #5271 - in a mixed select users are displayed with their full name --- .../v5/store/tickets/card/ticketsCard.selectors.ts | 11 +++++++++-- .../filterFormValues/filterFormValues.component.tsx | 6 ++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts b/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts index e1ca242024..6b94d0e519 100644 --- a/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts +++ b/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts @@ -23,7 +23,7 @@ import { ITicketsCardState } from './ticketsCard.redux'; import { DEFAULT_PIN, getPinColorHex, formatPin, getTicketPins } from '@/v5/ui/routes/viewer/tickets/ticketsForm/properties/coordsProperty/coordsProperty.helpers'; import { IPin } from '@/v4/services/viewer/viewer'; import { selectSelectedDate } from '@/v4/modules/sequences'; -import { uniq } from 'lodash'; +import { sortBy, sortedUniq } from 'lodash'; import { toTicketCardFilter, templatesToFilters } from '@components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers'; import { selectFederationById, selectFederationJobs, selectFederationUsers } from '../../federations/federations.selectors'; import { selectContainerJobs, selectContainerUsers } from '../../containers/containers.selectors'; @@ -231,7 +231,14 @@ export const selectPropertyOptions = createSelector( return; } if (matchingProperty.values === 'jobsAndUsers') { - allValues.push(...jobsAndUsers.map((ju) => ({ value: ju?.user || ju?._id, type: 'jobsAndUsers' }))); + allValues.push(...jobsAndUsers.map((ju) => { + const isUser = !!ju.firstName; + return ({ + value: isUser ? ju.user : ju._id, + displayValue: isUser ? `${ju?.firstName} ${ju?.lastName}` : null, + type: 'jobsAndUsers', + }); + })); return; } allValues.push(...matchingProperty.values.map((value) => ({ value, type: 'default' }))); diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx index 51a211f443..d644da1eb9 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx @@ -144,7 +144,9 @@ export const FilterFormValues = ({ module, property, type }: FilterFolrmValuesTy transformChangeEvent={(e) => mapArrayToFormArray(compact(e.target.value))} formError={error?.[0]} > - {(selectOptions || []).map(({ value: val }) => {val})} + {(selectOptions || []).map( + (option) => {option.displayValue ?? option.value}, + )} ); } @@ -155,4 +157,4 @@ export const FilterFormValues = ({ module, property, type }: FilterFolrmValuesTy ); -}; \ No newline at end of file +}; From 3148aff97c6d3219c9a24dba32772fdbdb74ce6d Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Mon, 3 Feb 2025 15:56:15 +0000 Subject: [PATCH 07/32] ISSUE #5271 - fix bad type name --- .../filterFormValues/filterFormValues.component.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx index d644da1eb9..a2c034b97a 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx @@ -31,7 +31,7 @@ import { DateRangeInput } from './rangeInput/dateRangeInput.component'; import { NumberRangeInput } from './rangeInput/numberRangeInput.component'; import { mapArrayToFormArray, mapFormArrayToArray } from '@/v5/helpers/form.helper'; -type FilterFolrmValuesType = { +type FilterFormValuesProps = { module: string, property: string, type: CardFilterType, @@ -44,7 +44,7 @@ const getInputField = (type: CardFilterType) => { }; const name = 'values'; -export const FilterFormValues = ({ module, property, type }: FilterFolrmValuesType) => { +export const FilterFormValues = ({ module, property, type }: FilterFormValuesProps) => { if (!property) return null; const { containerOrFederation } = useParams(); const { control, watch, formState: { errors, dirtyFields } } = useFormContext(); From 076ffcadc4d01699bc7cc4dc3c50ee79101a0355 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Mon, 3 Feb 2025 15:57:28 +0000 Subject: [PATCH 08/32] ISSUE #5271 - fixed duplicate values appearing in select --- frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts b/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts index 6b94d0e519..69d159d570 100644 --- a/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts +++ b/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts @@ -23,7 +23,7 @@ import { ITicketsCardState } from './ticketsCard.redux'; import { DEFAULT_PIN, getPinColorHex, formatPin, getTicketPins } from '@/v5/ui/routes/viewer/tickets/ticketsForm/properties/coordsProperty/coordsProperty.helpers'; import { IPin } from '@/v4/services/viewer/viewer'; import { selectSelectedDate } from '@/v4/modules/sequences'; -import { sortBy, sortedUniq } from 'lodash'; +import { sortBy, uniq, sortedUniqBy } from 'lodash'; import { toTicketCardFilter, templatesToFilters } from '@components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers'; import { selectFederationById, selectFederationJobs, selectFederationUsers } from '../../federations/federations.selectors'; import { selectContainerJobs, selectContainerUsers } from '../../containers/containers.selectors'; @@ -243,6 +243,6 @@ export const selectPropertyOptions = createSelector( } allValues.push(...matchingProperty.values.map((value) => ({ value, type: 'default' }))); }); - return sortedUniq(sortBy(allValues, ({ value }) => value)); + return sortedUniqBy(sortBy(allValues, ({ value }) => value), ({ value }) => value); }, ); From dcfe11917ccc35ca244b0951d6de3cabba623945 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Tue, 4 Feb 2025 13:18:52 +0000 Subject: [PATCH 09/32] ISSUE #5271 - fix many select values pushing out of action menu --- .../viewer/cards/cardFilters/filterForm/filterForm.styles.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.styles.ts b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.styles.ts index 53871194f8..c614dd6f42 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.styles.ts +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.styles.ts @@ -31,6 +31,7 @@ export const Container = styled.div` display: flex; flex-direction: column; gap: 10px; + max-width: 365px; `; export const TitleContainer = styled.div` From 5b7c0cb708c72094564c51b13a4f2eac98568624 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Tue, 4 Feb 2025 15:54:44 +0000 Subject: [PATCH 10/32] ISSUE #5271 - values that are users are displayed as a full name --- .../cards/cardFilters/cardFilters.types.ts | 2 +- .../filterChip/filterChip.component.tsx | 25 ++++-------------- .../filterForm/filterForm.component.tsx | 26 ++++++++++++++++--- .../filterFormValues.component.tsx | 8 +++--- .../tickets/ticketFilters.helpers.ts | 8 +++++- 5 files changed, 41 insertions(+), 28 deletions(-) diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.types.ts b/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.types.ts index c1ec5fabab..ade8c35d2a 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.types.ts +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.types.ts @@ -19,7 +19,7 @@ export type CardFilterOperator = 'ex' | 'nex' | 'eq' | 'neq' | 'ss' | 'nss' | 'r export type CardFilterType = 'text' | 'longText' | 'date' | 'sequencing' | 'pastDate' | 'oneOf' | 'manyOf' | 'boolean' | 'number' | 'ticketTitle' | 'ticketId' | 'template' | 'owner'; type ValueType = string | number | Date; export type CardFilterValue = ValueType | ValueType[]; -export type BaseFilter = { operator: CardFilterOperator, values: CardFilterValue[] }; +export type BaseFilter = { operator: CardFilterOperator, values: CardFilterValue[], displayValues: string }; export type CardFilter = { property: string, diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterChip/filterChip.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterChip/filterChip.component.tsx index 52a8ffb926..02048ecbae 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterChip/filterChip.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterChip/filterChip.component.tsx @@ -17,24 +17,10 @@ import CloseIcon from '@assets/icons/outlined/close-outlined.svg'; import { ChipContainer, DeleteButton, TextWrapper, OperatorIconContainer, DisplayValue, Property } from './filterChip.styles'; -import { FILTER_OPERATOR_ICON, getFilterOperatorLabels, isDateType, isRangeOperator } from '../cardFilters.helpers'; +import { FILTER_OPERATOR_ICON, getFilterOperatorLabels } from '../cardFilters.helpers'; import { Tooltip } from '@mui/material'; import { FormattedMessage } from 'react-intl'; -import { CardFilterType, BaseFilter, CardFilterOperator, CardFilterValue } from '../cardFilters.types'; -import { formatSimpleDate } from '@/v5/helpers/intl.helper'; -import { formatMessage } from '@/v5/services/intl'; - -const valueToDisplayDate = (value) => formatSimpleDate(new Date(value)); -const formatDateRange = ([from, to]) => formatMessage( - { defaultMessage: '{from} to {to}', id: 'cardFilter.dateRange.join' }, - { from: valueToDisplayDate(from), to: valueToDisplayDate(to) }, -); - -const getDisplayValue = (values: CardFilterValue[], operator: CardFilterOperator, type: CardFilterType) => { - const isRange = isRangeOperator(operator); - if (isDateType(type)) return values.map(isRange ? formatDateRange : valueToDisplayDate); - return (isRange ? values.map(([a, b]: any) => `[${a}, ${b}]`) : values).join(', ') ?? ''; -}; +import { CardFilterType, BaseFilter } from '../cardFilters.types'; type FilterChipProps = { property: string; @@ -44,10 +30,9 @@ type FilterChipProps = { onDelete: () => void; }; export const FilterChip = ({ property, onDelete, selected, type, filter }: FilterChipProps) => { - const { operator, values } = filter; + const { operator, values, displayValues } = filter; const OperatorIcon = FILTER_OPERATOR_ICON[operator]; const hasMultipleValues = values.length > 1; - const displayValue = getDisplayValue(values, operator, type); const labels = getFilterOperatorLabels(type); const handleDelete = (e) => { @@ -58,7 +43,7 @@ export const FilterChip = ({ property, onDelete, selected, type, filter }: Filte return ( - + {property} @@ -70,7 +55,7 @@ export const FilterChip = ({ property, onDelete, selected, type, filter }: Filte )} {!hasMultipleValues && !!values?.length && ( - {displayValue} + {displayValues} )} diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.component.tsx index c0a596d738..9b3775a059 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.component.tsx @@ -17,7 +17,7 @@ import { FormattedMessage } from 'react-intl'; import { CardFilterOperator, CardFilterValue, CardFilterType, BaseFilter, CardFilter } from '../cardFilters.types'; -import { getFilterFormTitle } from '../cardFilters.helpers'; +import { getFilterFormTitle, isDateType, isRangeOperator } from '../cardFilters.helpers'; import { Container, ButtonsContainer, Button, TitleContainer } from './filterForm.styles'; import { FormProvider, useForm } from 'react-hook-form'; import { isEmpty } from 'lodash'; @@ -27,10 +27,13 @@ import { mapArrayToFormArray, mapFormArrayToArray } from '@/v5/helpers/form.help import { yupResolver } from '@hookform/resolvers/yup'; import { FilterSchema } from '@/v5/validation/ticketSchemes/validators'; import { FilterFormOperators } from './filterFormValues/operators/filterFormOperators.component'; +import { getOptionFromValue } from '../filtersSelection/tickets/ticketFilters.helpers'; +import { formatSimpleDate } from '@/v5/helpers/intl.helper'; +import { formatMessage } from '@/v5/services/intl'; const DEFAULT_OPERATOR = 'eq'; const DEFAULT_VALUES = ['']; -type FormType = { values: { value: CardFilterValue }[], operator: CardFilterOperator }; +type FormType = { values: { value: CardFilterValue, displayValue?: string }[], operator: CardFilterOperator }; type FilterFormProps = { module: string, property: string, @@ -39,6 +42,13 @@ type FilterFormProps = { onSubmit: (newFilter: CardFilter) => void, onCancel: () => void, }; + +const valueToDisplayDate = (value) => formatSimpleDate(new Date(value)); +const formatDateRange = ([from, to]) => formatMessage( + { defaultMessage: '{from} to {to}', id: 'cardFilter.dateRange.join' }, + { from: valueToDisplayDate(from), to: valueToDisplayDate(to) }, +); + export const FilterForm = ({ module, property, type, filter, onSubmit, onCancel }: FilterFormProps) => { const defaultValues = { operator: filter?.operator || DEFAULT_OPERATOR, @@ -59,7 +69,17 @@ export const FilterForm = ({ module, property, type, filter, onSubmit, onCancel const handleSubmit = formData.handleSubmit((body: FormType) => { const newValues = mapFormArrayToArray(body.values) .filter((x) => ![undefined, ''].includes(x as any)); - onSubmit({ module, property, type, filter: { operator: body.operator, values: newValues } }); + const isRange = isRangeOperator(body.operator); + const displayValues = newValues.map((newVal) => { + const option = getOptionFromValue(newVal, body.values); + if (isDateType(type)) return (isRange ? formatDateRange(newVal) : valueToDisplayDate(newVal)); + if (isRange) { + const [a, b] = newVal; + return `[${a}, ${b}]`; + } + return option.displayValue ?? newVal; + }).join(', '); + onSubmit({ module, property, type, filter: { operator: body.operator, values: newValues, displayValues } }); }); const handleCancel = () => { diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx index a2c034b97a..ce55852350 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx @@ -29,7 +29,8 @@ import { ViewerParams } from '@/v5/ui/routes/routes.constants'; import { MultiSelectMenuItem } from '@controls/inputs/multiSelect/multiSelectMenuItem/multiSelectMenuItem.component'; import { DateRangeInput } from './rangeInput/dateRangeInput.component'; import { NumberRangeInput } from './rangeInput/numberRangeInput.component'; -import { mapArrayToFormArray, mapFormArrayToArray } from '@/v5/helpers/form.helper'; +import { mapFormArrayToArray } from '@/v5/helpers/form.helper'; +import { getOptionFromValue, selectTypeOnChange } from '../../filtersSelection/tickets/ticketFilters.helpers'; type FilterFormValuesProps = { module: string, @@ -133,7 +134,7 @@ export const FilterFormValues = ({ module, property, type }: FilterFormValuesPro maxItems={19} name={name} transformValueIn={(v) => compact(mapFormArrayToArray(v))} - transformChangeEvent={(e) => mapArrayToFormArray(compact(e.target.value))} + transformChangeEvent={(e) => selectTypeOnChange(e, selectOptions)} formError={error?.[0]} /> ); @@ -141,7 +142,8 @@ export const FilterFormValues = ({ module, property, type }: FilterFormValuesPro mapArrayToFormArray(compact(e.target.value))} + transformChangeEvent={(e) => selectTypeOnChange(e, selectOptions)} + renderValue={(values: string[]) => values.map((value) => getOptionFromValue(value, selectOptions)?.displayValue ?? value).join(', ')} formError={error?.[0]} > {(selectOptions || []).map( diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts index e39c34b97b..ae35519bd8 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts @@ -23,7 +23,7 @@ import NumberIcon from '@assets/icons/filters/number.svg'; import TemplateIcon from '@assets/icons/filters/template.svg'; import TextIcon from '@assets/icons/filters/text.svg'; import CalendarIcon from '@assets/icons/outlined/calendar-outlined.svg'; -import { sortBy, uniqBy } from 'lodash'; +import { compact, sortBy, uniqBy } from 'lodash'; import { CardFilterType, BaseFilter, CardFilter } from '../../cardFilters.types'; export const TYPE_TO_ICON: Record = { @@ -84,3 +84,9 @@ export const toTicketCardFilter = (filters: Record): CardFil filter, })) ); + +export const getOptionFromValue = (value, options) => options.find(({ value: optionValue }) => value === optionValue); +export const selectTypeOnChange = (event, options) => compact(event.target.value).map((value) => { + const option = getOptionFromValue(value, options); + return { value, displayValue: option?.displayValue ?? value }; +}); \ No newline at end of file From b5e28604e414acdc69346a96b910636883ed6407 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 5 Feb 2025 10:46:59 +0000 Subject: [PATCH 11/32] ISSUE #5271 - rename props to transformInputValue and transformOutputValue --- .../filterFormValues.component.tsx | 8 ++++---- .../controls/inputs/inputController.component.tsx | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx index ce55852350..ff9c1cec65 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx @@ -133,16 +133,16 @@ export const FilterFormValues = ({ module, property, type }: FilterFormValuesPro showAddButton maxItems={19} name={name} - transformValueIn={(v) => compact(mapFormArrayToArray(v))} - transformChangeEvent={(e) => selectTypeOnChange(e, selectOptions)} + transformInputValue={(v) => compact(mapFormArrayToArray(v))} + transformOutputValue={(e) => selectTypeOnChange(e, selectOptions)} formError={error?.[0]} /> ); return ( selectTypeOnChange(e, selectOptions)} + transformInputValue={mapFormArrayToArray} + transformOutputValue={(e) => selectTypeOnChange(e, selectOptions)} renderValue={(values: string[]) => values.map((value) => getOptionFromValue(value, selectOptions)?.displayValue ?? value).join(', ')} formError={error?.[0]} > diff --git a/frontend/src/v5/ui/controls/inputs/inputController.component.tsx b/frontend/src/v5/ui/controls/inputs/inputController.component.tsx index 58670ad833..6f82bf97fd 100644 --- a/frontend/src/v5/ui/controls/inputs/inputController.component.tsx +++ b/frontend/src/v5/ui/controls/inputs/inputController.component.tsx @@ -36,8 +36,8 @@ export type InputControllerProps = T & FormInputProps & { defaultValue?: any, onChange?: (event) => void, onBlur?: () => void, - transformValueIn?: (val) => any, - transformChangeEvent?: (val) => any, + transformInputValue?: (val) => any, + transformOutputValue?: (val) => any, children?: any, }; @@ -55,8 +55,8 @@ export const InputController: InputControllerType = forwardRef(({ defaultValue, onChange, onBlur, - transformValueIn = (val) => val, - transformChangeEvent = (val) => val, + transformInputValue = (val) => val, + transformOutputValue = (val) => val, ...props }: Props, ref) => { const ctx = useFormContext(); @@ -73,10 +73,10 @@ export const InputController: InputControllerType = forwardRef(({ { - field.onChange(transformChangeEvent(event)); - onChange?.(transformChangeEvent(event)); + field.onChange(transformOutputValue(event)); + onChange?.(transformOutputValue(event)); }} onBlur={() => { field.onBlur(); From a95ee0783b3f30849bb89380e87cdc055c6aa63b Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 5 Feb 2025 10:50:09 +0000 Subject: [PATCH 12/32] ISSUE #5271 - remove unnecessary return --- .../inputs/inputController.component.tsx | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/frontend/src/v5/ui/controls/inputs/inputController.component.tsx b/frontend/src/v5/ui/controls/inputs/inputController.component.tsx index 6f82bf97fd..1537a8d583 100644 --- a/frontend/src/v5/ui/controls/inputs/inputController.component.tsx +++ b/frontend/src/v5/ui/controls/inputs/inputController.component.tsx @@ -67,27 +67,25 @@ export const InputController: InputControllerType = forwardRef(({ name={name} control={control} defaultValue={defaultValue} - render={({ field: { ref: fieldRef, ...field } }) => { - return ( + render={({ field: { ref: fieldRef, ...field } }) => ( // @ts-ignore - { - field.onChange(transformOutputValue(event)); - onChange?.(transformOutputValue(event)); - }} - onBlur={() => { - field.onBlur(); - onBlur?.(); - }} - inputRef={ref || fieldRef} - error={!!error} - helperText={error?.message} - /> - ); - }} + { + field.onChange(transformOutputValue(event)); + onChange?.(transformOutputValue(event)); + }} + onBlur={() => { + field.onBlur(); + onBlur?.(); + }} + inputRef={ref || fieldRef} + error={!!error} + helperText={error?.message} + /> + )} /> ); }); From 40af17d95e2dfe784005c33a2daaf8eee1666ae8 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 5 Feb 2025 10:53:07 +0000 Subject: [PATCH 13/32] ISSUE #5271 - remove unnecessary formControl and helper text as it exists within select already --- .../multiSelect/multiSelect.component.tsx | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/frontend/src/v5/ui/controls/inputs/multiSelect/multiSelect.component.tsx b/frontend/src/v5/ui/controls/inputs/multiSelect/multiSelect.component.tsx index d2d390d563..4688756241 100644 --- a/frontend/src/v5/ui/controls/inputs/multiSelect/multiSelect.component.tsx +++ b/frontend/src/v5/ui/controls/inputs/multiSelect/multiSelect.component.tsx @@ -17,16 +17,12 @@ import { SearchSelect } from '@controls/searchSelect/searchSelect.component'; import { SelectProps } from '../select/select.component'; -import { FormControl, FormHelperText } from '@mui/material'; -export const MultiSelect = ({ defaultValue = [], className, helperText, ...props }: SelectProps) => ( - - (val as any[]).join(', ')} - {...props} - multiple - /> - {helperText} - +export const MultiSelect = ({ defaultValue = [], className, ...props }: SelectProps) => ( + (val as any[]).join(', ')} + {...props} + multiple + /> ); From 3c96d63ddf8bd8a0d72839d83ac4b35eeba6ab65 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 5 Feb 2025 14:18:17 +0000 Subject: [PATCH 14/32] ISSUE #5271 - fix failing tests after adding jobsAndUsers filter --- .../viewer/cards/cardFilters/cardFilters.types.ts | 2 +- frontend/test/tickets/card/ticketsCard.store.spec.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.types.ts b/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.types.ts index ade8c35d2a..3f5e0841a7 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.types.ts +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.types.ts @@ -19,7 +19,7 @@ export type CardFilterOperator = 'ex' | 'nex' | 'eq' | 'neq' | 'ss' | 'nss' | 'r export type CardFilterType = 'text' | 'longText' | 'date' | 'sequencing' | 'pastDate' | 'oneOf' | 'manyOf' | 'boolean' | 'number' | 'ticketTitle' | 'ticketId' | 'template' | 'owner'; type ValueType = string | number | Date; export type CardFilterValue = ValueType | ValueType[]; -export type BaseFilter = { operator: CardFilterOperator, values: CardFilterValue[], displayValues: string }; +export type BaseFilter = { operator: CardFilterOperator, values: CardFilterValue[], displayValues?: string }; export type CardFilter = { property: string, diff --git a/frontend/test/tickets/card/ticketsCard.store.spec.ts b/frontend/test/tickets/card/ticketsCard.store.spec.ts index 376de89fb9..4d7d44c897 100644 --- a/frontend/test/tickets/card/ticketsCard.store.spec.ts +++ b/frontend/test/tickets/card/ticketsCard.store.spec.ts @@ -64,7 +64,7 @@ describe('Tickets: store', () => { }); describe('filters', () => { - const [ticketTitleFilter, ticketIdFilter, templateIdFilter] = templatesToFilters([]); + const [ticketTitleFilter, ticketIdFilter, templateIdFilter, ownerFilter] = templatesToFilters([]); const baseFilter: BaseFilter = { operator: 'eq', values: [], @@ -102,15 +102,15 @@ describe('Tickets: store', () => { describe('available template filters', () => { const getAvailableFilters = () => selectAvailableTemplatesFilters(getState()); it('all the default filters should be available originally', () => { - expect(getAvailableFilters()).toEqual([ticketTitleFilter, ticketIdFilter, templateIdFilter]); + expect(getAvailableFilters()).toEqual([ticketTitleFilter, ticketIdFilter, templateIdFilter, ownerFilter]); }) it('adding filters should make the unavailable', () => { // add first filter dispatch(TicketsCardActions.upsertFilter(ticketTitleCardFilter)); - expect(getAvailableFilters()).toEqual([ticketIdFilter, templateIdFilter]); + expect(getAvailableFilters()).toEqual([ticketIdFilter, templateIdFilter, ownerFilter]); // add second filter dispatch(TicketsCardActions.upsertFilter(ticketIdCardFilter)); - expect(getAvailableFilters()).toEqual([templateIdFilter]); + expect(getAvailableFilters()).toEqual([templateIdFilter, ownerFilter]); }) it('editing a filter shouldn\'t affect the available filters', () => { dispatch(TicketsCardActions.upsertFilter(ticketTitleCardFilter)); @@ -125,7 +125,7 @@ describe('Tickets: store', () => { dispatch(TicketsCardActions.upsertFilter(ticketIdCardFilter)); // delete filter dispatch(TicketsCardActions.deleteFilter(ticketTitleCardFilter)); - expect(getAvailableFilters()).toEqual([ticketTitleFilter, templateIdFilter]); + expect(getAvailableFilters()).toEqual([ticketTitleFilter, templateIdFilter, ownerFilter]); }) }); }) From e0bea9b360bdf870107cef3d4d50dac36365579e Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 5 Feb 2025 14:18:43 +0000 Subject: [PATCH 15/32] ISSUE #5271 - fix templates filter showing blank values --- .../filterFormValues/filterFormValues.component.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx index ff9c1cec65..67bf7e3137 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx @@ -29,7 +29,7 @@ import { ViewerParams } from '@/v5/ui/routes/routes.constants'; import { MultiSelectMenuItem } from '@controls/inputs/multiSelect/multiSelectMenuItem/multiSelectMenuItem.component'; import { DateRangeInput } from './rangeInput/dateRangeInput.component'; import { NumberRangeInput } from './rangeInput/numberRangeInput.component'; -import { mapFormArrayToArray } from '@/v5/helpers/form.helper'; +import { mapArrayToFormArray, mapFormArrayToArray } from '@/v5/helpers/form.helper'; import { getOptionFromValue, selectTypeOnChange } from '../../filtersSelection/tickets/ticketFilters.helpers'; type FilterFormValuesProps = { @@ -60,7 +60,7 @@ export const FilterFormValues = ({ module, property, type }: FilterFormValuesPro const isRangeOp = isRangeOperator(operator); const emptyValue = { value: (isRangeOp ? ['', ''] : '') }; const selectOptions = type === 'template' ? - TicketsHooksSelectors.selectTemplatesNames(containerOrFederation) + mapArrayToFormArray(TicketsHooksSelectors.selectTemplatesNames(containerOrFederation)) : TicketsCardHooksSelectors.selectPropertyOptions(containerOrFederation, module, property); useEffect(() => { From 9ff9d9b2e17e8cd9857ecd4f56054130186ff3c9 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Mon, 10 Feb 2025 16:11:38 +0000 Subject: [PATCH 16/32] ISSUE #5271 - move owner property filter to propertiesToValidFilters --- .../filtersSelection/tickets/ticketFilters.helpers.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts index ae35519bd8..a7963ac06c 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts @@ -46,11 +46,11 @@ const DEFAULT_FILTERS: CardFilter[] = [ { module: '', type: 'ticketTitle', property: formatMessage({ defaultMessage: 'Ticket title', id: 'viewer.card.filters.element.ticketTitle' }) }, { module: '', type: 'ticketId', property: formatMessage({ defaultMessage: 'Ticket ID', id: 'viewer.card.filters.element.ticketId' }) }, { module: '', type: 'template', property: formatMessage({ defaultMessage: 'Ticket template', id: 'viewer.card.filters.element.ticketTemplate' }) }, - { module: '', type: 'owner', property: formatMessage({ defaultMessage: 'Owner', id: 'viewer.card.filters.element.ticketTemplate' }) }, + { module: '', type: 'owner', property: formatMessage({ defaultMessage: 'Owner', id: 'viewer.card.filters.element.owner' }) }, ]; const propertiesToValidFilters = (properties: { name: string, type: string }[], module: string = ''): CardFilter[] => properties - .filter(({ type }) => Object.keys(TYPE_TO_ICON).includes(type)) + .filter(({ name, type }) => name !== 'Owner' && Object.keys(TYPE_TO_ICON).includes(type)) .map(({ name, type }) => ({ module, property: name, @@ -58,7 +58,7 @@ const propertiesToValidFilters = (properties: { name: string, type: string }[], }) as CardFilter); const templateToFilters = (template: ITemplate): CardFilter[] => [ - ...propertiesToValidFilters(template.properties.filter(({ name }) => name !== 'Owner'), ''), + ...propertiesToValidFilters(template.properties, ''), ...template.modules.flatMap(({ properties, name, type }) => propertiesToValidFilters(properties, name || type)), ]; From a7086718d3cf6e0b74ea5eddfba1c515fd357d6b Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 12 Feb 2025 11:21:22 +0000 Subject: [PATCH 17/32] ISSUE #5271 - only use templates that have been used on the current model --- .../v5/store/tickets/card/ticketsCard.selectors.ts | 11 +++++------ .../filterFormValues/filterFormValues.component.tsx | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts b/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts index 9d7e3054cf..5d4bf8cda7 100644 --- a/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts +++ b/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts @@ -142,20 +142,19 @@ export const selectFilteredTickets = createSelector( }, ); -const selectTemplatesFilters = createSelector( +export const selectTemplatesWithTickets = createSelector( selectCurrentTemplates, selectFilteredTickets, (templates, tickets) => { const idsOfTemplatesWithAtLeastOneTicket = uniq(tickets.map((t) => t.type)); - const templatesWithAtLeastOneTicket = templates.filter((t) => idsOfTemplatesWithAtLeastOneTicket.includes(t._id)); - return templatesToFilters(templatesWithAtLeastOneTicket); + return templates.filter((t) => idsOfTemplatesWithAtLeastOneTicket.includes(t._id)); }, ); export const selectAvailableTemplatesFilters = createSelector( selectFilters, - selectTemplatesFilters, - (usedFilters, allFilters) => allFilters.filter(({ module, property, type }) => !usedFilters[`${module}.${property}.${type}`]), + selectTemplatesWithTickets, + (usedFilters, allFilters) => templatesToFilters(allFilters).filter(({ module, property, type }) => !usedFilters[`${module}.${property}.${type}`]), ); export const selectIsShowingPins = createSelector( @@ -225,7 +224,7 @@ const selectJobsAndUsersByModelId = createSelector( ); export const selectPropertyOptions = createSelector( - selectTemplates, + selectTemplatesWithTickets, selectRiskCategories, selectJobsAndUsersByModelId, (state, modelId, module) => module, diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx index 9fabc1cab5..0428403cb9 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx @@ -23,7 +23,7 @@ import { ArrayFieldContainer } from '@controls/inputs/arrayFieldContainer/arrayF import { useEffect } from 'react'; import { compact, isArray, isEmpty } from 'lodash'; import { CardFilterType } from '../../cardFilters.types'; -import { TicketsCardHooksSelectors, TicketsHooksSelectors } from '@/v5/services/selectorsHooks'; +import { TicketsCardHooksSelectors } from '@/v5/services/selectorsHooks'; import { useParams } from 'react-router-dom'; import { ViewerParams } from '@/v5/ui/routes/routes.constants'; import { MultiSelectMenuItem } from '@controls/inputs/multiSelect/multiSelectMenuItem/multiSelectMenuItem.component'; @@ -60,7 +60,7 @@ export const FilterFormValues = ({ module, property, type }: FilterFormValuesPro const isRangeOp = isRangeOperator(operator); const emptyValue = { value: (isRangeOp ? ['', ''] : '') }; const selectOptions = type === 'template' ? - mapArrayToFormArray(TicketsHooksSelectors.selectTemplatesNames(containerOrFederation)) + TicketsCardHooksSelectors.selectTemplatesWithTickets(); : TicketsCardHooksSelectors.selectPropertyOptions(containerOrFederation, module, property); useEffect(() => { From 43d44c9713293694aea47671a4aac86bf26ae72b Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 12 Feb 2025 11:28:39 +0000 Subject: [PATCH 18/32] ISSUE #5271 - give helper function more agnostic name --- .../filterFormValues/filterFormValues.component.tsx | 6 +++--- .../filtersSelection/tickets/ticketFilters.helpers.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx index 0428403cb9..fab96403e3 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx @@ -30,7 +30,7 @@ import { MultiSelectMenuItem } from '@controls/inputs/multiSelect/multiSelectMen import { DateRangeInput } from './rangeInput/dateRangeInput.component'; import { NumberRangeInput } from './rangeInput/numberRangeInput.component'; import { mapArrayToFormArray, mapFormArrayToArray } from '@/v5/helpers/form.helper'; -import { getOptionFromValue, selectTypeOnChange } from '../../filtersSelection/tickets/ticketFilters.helpers'; +import { getOptionFromValue, getFilterFromEvent } from '../../filtersSelection/tickets/ticketFilters.helpers'; type FilterFormValuesProps = { module: string, @@ -134,7 +134,7 @@ export const FilterFormValues = ({ module, property, type }: FilterFormValuesPro maxItems={19} name={name} transformInputValue={(v) => compact(mapFormArrayToArray(v))} - transformOutputValue={(e) => selectTypeOnChange(e, selectOptions)} + transformOutputValue={(e) => getFilterFromEvent(e, selectOptions)} formError={error?.[0]} /> ); @@ -142,7 +142,7 @@ export const FilterFormValues = ({ module, property, type }: FilterFormValuesPro selectTypeOnChange(e, selectOptions)} + transformOutputValue={(e) => getFilterFromEvent(e, selectOptions)} renderValue={(values: string[]) => values.map((value) => getOptionFromValue(value, selectOptions)?.displayValue ?? value).join(', ')} formError={error?.[0]} > diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts index 4f5fc3e082..ef81354b00 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts @@ -86,7 +86,7 @@ export const toTicketCardFilter = (filters: Record): CardFil ); export const getOptionFromValue = (value, options) => options.find(({ value: optionValue }) => value === optionValue); -export const selectTypeOnChange = (event, options) => compact(event.target.value).map((value) => { +export const getFilterFromEvent = (event, options) => compact(event.target.value).map((value) => { //TODO DAN rename const option = getOptionFromValue(value, options); return { value, displayValue: option?.displayValue ?? value }; }); From a0fa774108580dcf5cda2f521f102056c601bb4a Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 12 Feb 2025 11:31:10 +0000 Subject: [PATCH 19/32] ISSUE #5271 - fix template filter not working --- .../filterFormValues/filterFormValues.component.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx index fab96403e3..3e5d93e792 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx @@ -29,7 +29,7 @@ import { ViewerParams } from '@/v5/ui/routes/routes.constants'; import { MultiSelectMenuItem } from '@controls/inputs/multiSelect/multiSelectMenuItem/multiSelectMenuItem.component'; import { DateRangeInput } from './rangeInput/dateRangeInput.component'; import { NumberRangeInput } from './rangeInput/numberRangeInput.component'; -import { mapArrayToFormArray, mapFormArrayToArray } from '@/v5/helpers/form.helper'; +import { mapFormArrayToArray } from '@/v5/helpers/form.helper'; import { getOptionFromValue, getFilterFromEvent } from '../../filtersSelection/tickets/ticketFilters.helpers'; type FilterFormValuesProps = { @@ -60,7 +60,7 @@ export const FilterFormValues = ({ module, property, type }: FilterFormValuesPro const isRangeOp = isRangeOperator(operator); const emptyValue = { value: (isRangeOp ? ['', ''] : '') }; const selectOptions = type === 'template' ? - TicketsCardHooksSelectors.selectTemplatesWithTickets(); + TicketsCardHooksSelectors.selectTemplatesWithTickets().map(({ code: value, name: displayValue }) => ({ value, displayValue, type: 'template' })) : TicketsCardHooksSelectors.selectPropertyOptions(containerOrFederation, module, property); useEffect(() => { From 2fe92030336be87182eef08ebbcf4d08ed7e575d Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 12 Feb 2025 12:14:02 +0000 Subject: [PATCH 20/32] ISSUE #5271 - owner filter now displays full names --- .../store/tickets/card/ticketsCard.selectors.ts | 15 +++++---------- .../filterFormValues.component.tsx | 3 ++- .../tickets/ticketFilters.helpers.ts | 9 +++++++++ 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts b/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts index 5d4bf8cda7..66474449bf 100644 --- a/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts +++ b/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts @@ -24,7 +24,7 @@ import { DEFAULT_PIN, getPinColorHex, formatPin, getTicketPins } from '@/v5/ui/r import { IPin } from '@/v4/services/viewer/viewer'; import { selectSelectedDate } from '@/v4/modules/sequences'; import { sortBy, uniq, sortedUniqBy } from 'lodash'; -import { toTicketCardFilter, templatesToFilters } from '@components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers'; +import { toTicketCardFilter, templatesToFilters, getFiltersFromJobsAndUsers } from '@components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers'; import { selectFederationById, selectFederationJobs, selectFederationUsers } from '../../federations/federations.selectors'; import { selectContainerJobs, selectContainerUsers } from '../../containers/containers.selectors'; import { IJobOrUserList } from '../../jobs/jobs.types'; @@ -231,6 +231,8 @@ export const selectPropertyOptions = createSelector( (state, modelId, module, property) => property, (templates, riskCategories, jobsAndUsers, module, property) => { const allValues = []; + if (!module && property === 'Owner') return getFiltersFromJobsAndUsers( + jobsAndUsers.filter((ju) => !!ju.firstName)); templates.forEach((template) => { const matchingModule = module ? template.modules.find((mod) => (mod.name || mod.type) === module)?.properties : template.properties; const matchingProperty = matchingModule?.find(({ name, type: t }) => (name === property) && (['manyOf', 'oneOf'].includes(t))); @@ -240,18 +242,11 @@ export const selectPropertyOptions = createSelector( return; } if (matchingProperty.values === 'jobsAndUsers') { - allValues.push(...jobsAndUsers.map((ju) => { - const isUser = !!ju.firstName; - return ({ - value: isUser ? ju.user : ju._id, - displayValue: isUser ? `${ju?.firstName} ${ju?.lastName}` : null, - type: 'jobsAndUsers', - }); - })); + allValues.push(...getFiltersFromJobsAndUsers(jobsAndUsers)); return; } allValues.push(...matchingProperty.values.map((value) => ({ value, type: 'default' }))); }); - return sortedUniqBy(sortBy(allValues, ({ value }) => value), ({ value }) => value); + return sortedUniqBy(sortBy(allValues, 'value'), 'value'); }, ); diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx index 3e5d93e792..e6749ed200 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx @@ -131,6 +131,7 @@ export const FilterFormValues = ({ module, property, type }: FilterFormValuesPro compact(mapFormArrayToArray(v))} @@ -146,7 +147,7 @@ export const FilterFormValues = ({ module, property, type }: FilterFormValuesPro renderValue={(values: string[]) => values.map((value) => getOptionFromValue(value, selectOptions)?.displayValue ?? value).join(', ')} formError={error?.[0]} > - {(selectOptions || []).map( + {(selectOptions).map( (option) => {option.displayValue ?? option.value}, )} diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts index ef81354b00..ae6c62c407 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts @@ -91,6 +91,15 @@ export const getFilterFromEvent = (event, options) => compact(event.target.value return { value, displayValue: option?.displayValue ?? value }; }); +export const getFiltersFromJobsAndUsers = (jobsAndUsers) => jobsAndUsers.map((ju) => { + const isUser = !!ju.firstName; + return ({ + value: isUser ? ju.user : ju._id, + displayValue: isUser ? `${ju?.firstName} ${ju?.lastName}` : null, + type: 'jobsAndUsers', + }); +}); + const wrapWith = (text, wrappingChar) => wrappingChar + text + wrappingChar; // This code, copied from MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#encoding_for_rfc3986 // is due to `encodeURIComponent` not encoding all the chars From 0abecd05329307cbc01be6bdbc85c16cf6ff2fae Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 12 Feb 2025 12:14:52 +0000 Subject: [PATCH 21/32] ISSUE #5271 - exclude jobs from Owner filter --- .../assigneesSelect.component.tsx | 1 + .../assigneesSelectMenu.component.tsx | 37 +++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelect.component.tsx b/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelect.component.tsx index 3e2fca0ba8..9a522af3b6 100644 --- a/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelect.component.tsx +++ b/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelect.component.tsx @@ -34,6 +34,7 @@ export type AssigneesSelectProps = Pick & SelectProps & showEmptyText?: boolean; onBlur?: () => void; excludeViewers?: boolean; + excludeJobs?: boolean; }; export const AssigneesSelect = ({ diff --git a/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelectMenu/assigneesSelectMenu.component.tsx b/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelectMenu/assigneesSelectMenu.component.tsx index 92d473f753..4f4ab7ae23 100644 --- a/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelectMenu/assigneesSelectMenu.component.tsx +++ b/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelectMenu/assigneesSelectMenu.component.tsx @@ -41,6 +41,7 @@ const preventPropagation = (e) => { }; type AssigneesSelectMenuProps = SelectProps & { isInvalid: (val: string) => boolean; + excludeJobs?: boolean; }; export const AssigneesSelectMenu = ({ open, @@ -48,6 +49,7 @@ export const AssigneesSelectMenu = ({ onClick, multiple, isInvalid, + excludeJobs, ...props }: AssigneesSelectMenuProps) => { const { filteredItems } = useContext(SearchContext); @@ -89,23 +91,26 @@ export const AssigneesSelectMenu = ({ /> ))} {notFound.length > 0 && ()} + {!excludeJobs && ( + <> + + + + {jobs.length > 0 && jobs.map(({ _id }) => ( + + ))} + {!jobs.length && ()} - - - - {jobs.length > 0 && jobs.map(({ _id }) => ( - - ))} - {!jobs.length && ()} - - + + + )} From 4e7a1257c5ac2253eb789d5017c9200cf5f1b217 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 12 Feb 2025 12:20:21 +0000 Subject: [PATCH 22/32] ISSUE #5271 - tidy selector using switch --- .../tickets/card/ticketsCard.selectors.ts | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts b/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts index 66474449bf..735d13d7c1 100644 --- a/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts +++ b/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts @@ -237,15 +237,23 @@ export const selectPropertyOptions = createSelector( const matchingModule = module ? template.modules.find((mod) => (mod.name || mod.type) === module)?.properties : template.properties; const matchingProperty = matchingModule?.find(({ name, type: t }) => (name === property) && (['manyOf', 'oneOf'].includes(t))); if (!matchingProperty) return; - if (matchingProperty.values === 'riskCategories') { - allValues.push(...riskCategories.map((value) => ({ value, type: 'riskCategories' }))); - return; + switch (matchingProperty.values) { + case 'riskCategories': + allValues.push(...riskCategories.map((value) => ({ value, type: 'riskCategories' }))); + break; + case 'jobsAndUsers': + allValues.push(...jobsAndUsers.map((ju) => { + const isUser = !!ju.firstName; + return ({ + value: isUser ? ju.user : ju._id, + displayValue: isUser ? `${ju?.firstName} ${ju?.lastName}` : null, + type: 'jobsAndUsers', + }); + })); + break; + default: + allValues.push(...matchingProperty.values.map((value) => ({ value, type: 'default' }))); } - if (matchingProperty.values === 'jobsAndUsers') { - allValues.push(...getFiltersFromJobsAndUsers(jobsAndUsers)); - return; - } - allValues.push(...matchingProperty.values.map((value) => ({ value, type: 'default' }))); }); return sortedUniqBy(sortBy(allValues, 'value'), 'value'); }, From 4adfaf171720cc343da4fcb6cc0d9a24e29145dd Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 12 Feb 2025 14:18:53 +0000 Subject: [PATCH 23/32] ISSUE #5271 - fix jobsAndUsers error text causing UI problems --- .../filterFormValues.component.tsx | 1 - .../assigneesSelect.component.tsx | 44 +++++++++---------- .../controls/inputs/formInputs.component.tsx | 4 +- .../jobsAndUsersProperty.component.tsx | 2 +- 4 files changed, 23 insertions(+), 28 deletions(-) diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx index e6749ed200..01c3828234 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx @@ -46,7 +46,6 @@ const getInputField = (type: CardFilterType) => { const name = 'values'; export const FilterFormValues = ({ module, property, type }: FilterFormValuesProps) => { - if (!property) return null; const { containerOrFederation } = useParams(); const { control, watch, formState: { errors, dirtyFields } } = useFormContext(); const { fields, append, remove } = useFieldArray({ diff --git a/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelect.component.tsx b/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelect.component.tsx index 9a522af3b6..137a672077 100644 --- a/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelect.component.tsx +++ b/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelect.component.tsx @@ -26,7 +26,6 @@ import { TicketContext } from '../../routes/viewer/tickets/ticket.context'; import { Spinner } from '@controls/spinnerLoader/spinnerLoader.styles'; import { AssigneesValuesDisplay } from './assigneeValuesDisplay/assigneeValuesDisplay.component'; import { getInvalidValues, getModelJobsAndUsers, getValidValues } from './assignees.helpers'; -import { FormControl, FormHelperText } from '@mui/material'; export type AssigneesSelectProps = Pick & SelectProps & { maxItems?: number; @@ -92,28 +91,25 @@ export const AssigneesSelect = ({ ); return ( - - - - invalidValues.includes(v)} - onChange={handleChange} - {...props} - /> - - - {helperText} - - + + + invalidValues.includes(v)} + onChange={handleChange} + {...props} + /> + + + ); }; diff --git a/frontend/src/v5/ui/controls/inputs/formInputs.component.tsx b/frontend/src/v5/ui/controls/inputs/formInputs.component.tsx index 26130cba5d..35c2208a13 100644 --- a/frontend/src/v5/ui/controls/inputs/formInputs.component.tsx +++ b/frontend/src/v5/ui/controls/inputs/formInputs.component.tsx @@ -31,7 +31,7 @@ import { TextField, TextFieldProps } from './textField/textField.component'; import { DateTimePicker, DateTimePickerProps } from './datePicker/dateTimePicker.component'; import { BooleanSelect, BooleanSelectProps } from './booleanSelect/booleanSelect.component'; import { MultiSelect } from './multiSelect/multiSelect.component'; -import { AssigneesSelect, AssigneesSelectProps } from '@controls/assigneesSelect/assigneesSelect.component'; +import { JobsAndUsersProperty, JobsAndUsersPropertyProps } from '../../routes/viewer/tickets/ticketsForm/properties/jobsAndUsersProperty.component'; // text inputs export const FormNumberField = (props: InputControllerProps) => (); @@ -50,7 +50,7 @@ export const FormSelect = (props: InputControllerProps) => () => (); export const FormChipSelect = (props: InputControllerProps) => ( } {...props} />); export const FormSearchSelect = (props: InputControllerProps) => (); -export const FormAssigneesSelect = (props: InputControllerProps) => (); +export const FormAssigneesSelect = (props: InputControllerProps) => (); export const FormBooleanSelect = (props: InputControllerProps) => (); // control inputs diff --git a/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/jobsAndUsersProperty.component.tsx b/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/jobsAndUsersProperty.component.tsx index 6c8cdf09d6..f3cd98ee5b 100644 --- a/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/jobsAndUsersProperty.component.tsx +++ b/frontend/src/v5/ui/routes/viewer/tickets/ticketsForm/properties/jobsAndUsersProperty.component.tsx @@ -18,7 +18,7 @@ import { AssigneesSelect, AssigneesSelectProps } from '@controls/assigneesSelect import { FormInputProps } from '@controls/inputs/inputController.component'; import { FormControl, FormHelperText, InputLabel } from '@mui/material'; -type JobsAndUsersPropertyProps = FormInputProps & AssigneesSelectProps; +export type JobsAndUsersPropertyProps = FormInputProps & AssigneesSelectProps; export const JobsAndUsersProperty = ({ value, ...props }: JobsAndUsersPropertyProps) => ( {props.label} From d213c117a15edc8f58bf783d03d6c59196dc43b6 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 12 Feb 2025 17:18:30 +0000 Subject: [PATCH 24/32] ISSUE #5271 - default ticket setter only uses custom statuses if they exist on tickets within the model --- frontend/src/v5/store/tickets/tickets.selectors.ts | 5 ----- .../controls/assigneesSelect/assigneesSelect.component.tsx | 4 ++-- .../defaultTicketFiltersSetter.component.tsx | 4 ++-- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/frontend/src/v5/store/tickets/tickets.selectors.ts b/frontend/src/v5/store/tickets/tickets.selectors.ts index 77f4a94d82..da48d32cde 100644 --- a/frontend/src/v5/store/tickets/tickets.selectors.ts +++ b/frontend/src/v5/store/tickets/tickets.selectors.ts @@ -53,11 +53,6 @@ export const selectTemplates = createSelector( (state, modelId) => state.templatesByModelId[modelId] || [], ); -export const selectTemplatesNames = createSelector( - selectTemplates, - (templates) => templates.map(({ name }) => name), -); - export const selectTemplateById = createSelector( selectTicketsDomain, selectTemplates, diff --git a/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelect.component.tsx b/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelect.component.tsx index 137a672077..086a297e40 100644 --- a/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelect.component.tsx +++ b/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelect.component.tsx @@ -91,8 +91,8 @@ export const AssigneesSelect = ({ ); return ( - - + + { const [ticketSearchParam, setTicketSearchParam] = useSearchParam('ticketSearch', Transformers.STRING_ARRAY); const tickets = TicketsHooksSelectors.selectTickets(containerOrFederation); - const templates = TicketsHooksSelectors.selectTemplates(containerOrFederation); + const templates = TicketsCardHooksSelectors.selectTemplatesWithTickets(); const hasTicketData = !isEmpty(tickets) && !isEmpty(templates); const getTicketFiltersFromURL = (values): CardFilter[] => [{ From b7f75d8f5cb7acfbd41b5743e25f8049658f3a28 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 12 Feb 2025 17:20:15 +0000 Subject: [PATCH 25/32] ISSUE #5271 - fix default filters displaying as undefined because they don't have a displayValue --- .../cards/cardFilters/filterChip/filterChip.component.tsx | 2 +- .../filterForm/filterFormValues/filterFormValues.component.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterChip/filterChip.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterChip/filterChip.component.tsx index 02048ecbae..1ec04c5203 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterChip/filterChip.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterChip/filterChip.component.tsx @@ -30,7 +30,7 @@ type FilterChipProps = { onDelete: () => void; }; export const FilterChip = ({ property, onDelete, selected, type, filter }: FilterChipProps) => { - const { operator, values, displayValues } = filter; + const { operator, values, displayValues = values.join(', ') } = filter; const OperatorIcon = FILTER_OPERATOR_ICON[operator]; const hasMultipleValues = values.length > 1; const labels = getFilterOperatorLabels(type); diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx index 01c3828234..6a28eb8924 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx @@ -146,7 +146,7 @@ export const FilterFormValues = ({ module, property, type }: FilterFormValuesPro renderValue={(values: string[]) => values.map((value) => getOptionFromValue(value, selectOptions)?.displayValue ?? value).join(', ')} formError={error?.[0]} > - {(selectOptions).map( + {selectOptions.map( (option) => {option.displayValue ?? option.value}, )} From a061df0fd03ef9aa1814acdb703831bb75dfc303 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 12 Feb 2025 17:21:22 +0000 Subject: [PATCH 26/32] ISSUE #5271 - fix crash when changing select filter to ex/nex --- .../viewer/cards/cardFilters/filterForm/filterForm.component.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.component.tsx index 8f6bc9c801..ce3ab99f71 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.component.tsx @@ -61,6 +61,7 @@ export const FilterForm = ({ module, property, type, filter, onSubmit, onCancel mode: 'onChange', resolver: yupResolver(FilterSchema), context: { type }, + shouldUnregister: true, }); const { formState: { isValid, dirtyFields }, reset } = formData; From 8a5c1854de3f791b3cb88ee4153523b96abf3b7a Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 5 Mar 2025 10:15:28 +0000 Subject: [PATCH 27/32] ISSUE #5271 - rename formassignees -> formjobsandusers --- .../filterFormValues/filterFormValues.component.tsx | 4 ++-- frontend/src/v5/ui/controls/inputs/formInputs.component.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx index 6a28eb8924..6c6e7d9e9e 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx @@ -18,7 +18,7 @@ import { useFieldArray, useFormContext } from 'react-hook-form'; import { getOperatorMaxFieldsAllowed } from '../filterForm.helpers'; import { isRangeOperator, isTextType, isSelectType, isDateType } from '../../cardFilters.helpers'; -import { FormBooleanSelect, FormMultiSelect, FormDateTime, FormNumberField, FormTextField, FormAssigneesSelect } from '@controls/inputs/formInputs.component'; +import { FormBooleanSelect, FormMultiSelect, FormDateTime, FormNumberField, FormTextField, FormJobsAndUsersSelect } from '@controls/inputs/formInputs.component'; import { ArrayFieldContainer } from '@controls/inputs/arrayFieldContainer/arrayFieldContainer.component'; import { useEffect } from 'react'; import { compact, isArray, isEmpty } from 'lodash'; @@ -127,7 +127,7 @@ export const FilterFormValues = ({ module, property, type }: FilterFormValuesPro if (isSelectType(type)) { const allJobsAndUsers = selectOptions.every(({ type: t }) => t === 'jobsAndUsers'); if (allJobsAndUsers || type === 'owner') return ( - ) => () => (); export const FormChipSelect = (props: InputControllerProps) => ( } {...props} />); export const FormSearchSelect = (props: InputControllerProps) => (); -export const FormAssigneesSelect = (props: InputControllerProps) => (); +export const FormJobsAndUsersSelect = (props: InputControllerProps) => (); export const FormBooleanSelect = (props: InputControllerProps) => (); // control inputs From 4fa859e60dac6aa898018b5a0638ec008dfc75ee Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 5 Mar 2025 11:17:36 +0000 Subject: [PATCH 28/32] ISSUE #5271 - do not use filtered tickets when getting a select's property values --- .../v5/store/tickets/card/ticketsCard.selectors.ts | 14 +++++++++++--- .../tickets/ticketFilters.helpers.ts | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts b/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts index 735d13d7c1..ff8297b086 100644 --- a/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts +++ b/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts @@ -143,6 +143,15 @@ export const selectFilteredTickets = createSelector( ); export const selectTemplatesWithTickets = createSelector( + selectCurrentTemplates, + selectCurrentTickets, + (templates, tickets) => { + const idsOfTemplatesWithAtLeastOneTicket = uniq(tickets.map((t) => t.type)); + return templates.filter((t) => idsOfTemplatesWithAtLeastOneTicket.includes(t._id)); + }, +); + +export const selectTemplatesWithFilteredTickets = createSelector( selectCurrentTemplates, selectFilteredTickets, (templates, tickets) => { @@ -153,7 +162,7 @@ export const selectTemplatesWithTickets = createSelector( export const selectAvailableTemplatesFilters = createSelector( selectFilters, - selectTemplatesWithTickets, + selectTemplatesWithFilteredTickets, (usedFilters, allFilters) => templatesToFilters(allFilters).filter(({ module, property, type }) => !usedFilters[`${module}.${property}.${type}`]), ); @@ -231,8 +240,7 @@ export const selectPropertyOptions = createSelector( (state, modelId, module, property) => property, (templates, riskCategories, jobsAndUsers, module, property) => { const allValues = []; - if (!module && property === 'Owner') return getFiltersFromJobsAndUsers( - jobsAndUsers.filter((ju) => !!ju.firstName)); + if (!module && property === 'Owner') return getFiltersFromJobsAndUsers(jobsAndUsers.filter((ju) => !!ju.firstName)); templates.forEach((template) => { const matchingModule = module ? template.modules.find((mod) => (mod.name || mod.type) === module)?.properties : template.properties; const matchingProperty = matchingModule?.find(({ name, type: t }) => (name === property) && (['manyOf', 'oneOf'].includes(t))); diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts index ae6c62c407..e1f5eadaec 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filtersSelection/tickets/ticketFilters.helpers.ts @@ -86,7 +86,7 @@ export const toTicketCardFilter = (filters: Record): CardFil ); export const getOptionFromValue = (value, options) => options.find(({ value: optionValue }) => value === optionValue); -export const getFilterFromEvent = (event, options) => compact(event.target.value).map((value) => { //TODO DAN rename +export const getFilterFromEvent = (event, options) => compact(event.target.value).map((value) => { const option = getOptionFromValue(value, options); return { value, displayValue: option?.displayValue ?? value }; }); From b117264e4c9dabc96e47d40ba3a1f911670ff996 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 5 Mar 2025 12:07:57 +0000 Subject: [PATCH 29/32] ISSUE #5271 - simplify duplicated code --- .../src/v5/store/tickets/card/ticketsCard.selectors.ts | 9 +-------- .../filterFormValues/filterFormValues.component.tsx | 4 ++-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts b/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts index ff8297b086..9f0e8d3b4f 100644 --- a/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts +++ b/frontend/src/v5/store/tickets/card/ticketsCard.selectors.ts @@ -250,14 +250,7 @@ export const selectPropertyOptions = createSelector( allValues.push(...riskCategories.map((value) => ({ value, type: 'riskCategories' }))); break; case 'jobsAndUsers': - allValues.push(...jobsAndUsers.map((ju) => { - const isUser = !!ju.firstName; - return ({ - value: isUser ? ju.user : ju._id, - displayValue: isUser ? `${ju?.firstName} ${ju?.lastName}` : null, - type: 'jobsAndUsers', - }); - })); + allValues.push(...getFiltersFromJobsAndUsers(jobsAndUsers)); break; default: allValues.push(...matchingProperty.values.map((value) => ({ value, type: 'default' }))); diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx index 6c6e7d9e9e..709fb48fa4 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterFormValues/filterFormValues.component.tsx @@ -126,11 +126,11 @@ export const FilterFormValues = ({ module, property, type }: FilterFormValuesPro if (isSelectType(type)) { const allJobsAndUsers = selectOptions.every(({ type: t }) => t === 'jobsAndUsers'); - if (allJobsAndUsers || type === 'owner') return ( + if (allJobsAndUsers) return ( compact(mapFormArrayToArray(v))} From da849d738466e5d08dc86f2e6d473a806d203a26 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 5 Mar 2025 12:08:45 +0000 Subject: [PATCH 30/32] ISSUE #5271 - use only direct children within assignees select --- .../assigneesSelectMenu.component.tsx | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelectMenu/assigneesSelectMenu.component.tsx b/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelectMenu/assigneesSelectMenu.component.tsx index 4f4ab7ae23..ae77821a51 100644 --- a/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelectMenu/assigneesSelectMenu.component.tsx +++ b/frontend/src/v5/ui/controls/assigneesSelect/assigneesSelectMenu/assigneesSelectMenu.component.tsx @@ -92,24 +92,23 @@ export const AssigneesSelectMenu = ({ ))} {notFound.length > 0 && ()} {!excludeJobs && ( - <> - - - - {jobs.length > 0 && jobs.map(({ _id }) => ( - - ))} - {!jobs.length && ()} - - - + + + + )} + {!excludeJobs && jobs.length > 0 && jobs.map(({ _id }) => ( + + ))} + {!excludeJobs && !jobs.length && ()} + {!excludeJobs && ( + )} From ddb778014344945d76b49df3d167719568759926 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Wed, 5 Mar 2025 16:03:31 +0000 Subject: [PATCH 31/32] ISSUE #5271 - template and owner filters use is/nis instead of eq/neq --- .../components/viewer/cards/cardFilters/cardFilters.helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.helpers.ts b/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.helpers.ts index fe7e5460a0..2468ca3d3f 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.helpers.ts +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/cardFilters.helpers.ts @@ -90,7 +90,7 @@ export const getValidOperators = (type: CardFilterType): CardFilterOperator[] => if (isDateType(type)) return ['ex', 'nex', 'eq', 'neq', 'gte', 'lte', 'rng', 'nrng']; if (type === 'boolean') return ['eq', 'ex', 'nex']; if (isSelectType(type)) { - if (['template', 'owner'].includes(type)) return ['eq', 'neq']; + if (['template', 'owner'].includes(type)) return ['is', 'nis']; return ['ex', 'nex', 'is', 'nis']; } return Object.keys(FILTER_OPERATOR_LABEL) as CardFilterOperator[]; From 9d0d3bfbdd35e7b6d981961d33897435f42be270 Mon Sep 17 00:00:00 2001 From: The-Daniel Date: Thu, 6 Mar 2025 13:13:41 +0000 Subject: [PATCH 32/32] ISSUE #5271 - fixed default operator sometimes being an invalid operator for a property type --- .../filterForm/filterForm.component.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.component.tsx b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.component.tsx index ce3ab99f71..b7f5b36e46 100644 --- a/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.component.tsx +++ b/frontend/src/v5/ui/components/viewer/cards/cardFilters/filterForm/filterForm.component.tsx @@ -17,10 +17,10 @@ import { FormattedMessage } from 'react-intl'; import { CardFilterOperator, CardFilterValue, CardFilterType, BaseFilter, CardFilter } from '../cardFilters.types'; -import { getFilterFormTitle, isDateType, isRangeOperator } from '../cardFilters.helpers'; +import { getFilterFormTitle, getValidOperators, isDateType, isRangeOperator } from '../cardFilters.helpers'; import { Container, ButtonsContainer, Button, TitleContainer } from './filterForm.styles'; import { FormProvider, useForm } from 'react-hook-form'; -import { isBoolean, isEmpty } from 'lodash'; +import { intersection, isBoolean, isEmpty } from 'lodash'; import { ActionMenuItem } from '@controls/actionMenu'; import { FilterFormValues } from './filterFormValues/filterFormValues.component'; import { mapArrayToFormArray, mapFormArrayToArray } from '@/v5/helpers/form.helper'; @@ -32,7 +32,7 @@ import { formatSimpleDate } from '@/v5/helpers/intl.helper'; import { formatMessage } from '@/v5/services/intl'; import { TRUE_LABEL, FALSE_LABEL } from '@controls/inputs/booleanSelect/booleanSelect.component'; -const DEFAULT_OPERATOR = 'is'; +const DEFAULT_OPERATORS: CardFilterOperator[] = ['is', 'eq']; const DEFAULT_VALUES = ['']; type FormType = { values: { value: CardFilterValue, displayValue?: string }[], operator: CardFilterOperator }; type FilterFormProps = { @@ -51,8 +51,8 @@ const formatDateRange = ([from, to]) => formatMessage( ); export const FilterForm = ({ module, property, type, filter, onSubmit, onCancel }: FilterFormProps) => { - const defaultValues = { - operator: filter?.operator || DEFAULT_OPERATOR, + const defaultValues: FormType = { + operator: filter?.operator || intersection(getValidOperators(type), DEFAULT_OPERATORS)[0], values: mapArrayToFormArray(filter?.values || DEFAULT_VALUES), }; @@ -63,7 +63,12 @@ export const FilterForm = ({ module, property, type, filter, onSubmit, onCancel context: { type }, shouldUnregister: true, }); - const { formState: { isValid, dirtyFields }, reset } = formData; + const { formState: { isValid, dirtyFields }, reset, getValues } = formData; + + const operatorValue = getValues('operator'); + if (!getValidOperators(type).includes(operatorValue)) { + reset(defaultValues); + } const isUpdatingFilter = !!filter; const canSubmit = isValid && !isEmpty(dirtyFields);