Skip to content

Commit 1dc50c6

Browse files
committed
Multi Select specifically for Ordering created which excludes opposite option from options if it is selected.
1 parent 0bc7b27 commit 1dc50c6

3 files changed

Lines changed: 104 additions & 14 deletions

File tree

Tokenization/webapp/app/components/form/form-select.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,50 @@ export function FormSelectMulti<T extends string | number = string>(props: Selec
9292
/>
9393
);
9494
}
95+
96+
/**
97+
* FormSelectMultiOrdering
98+
*
99+
* Multi-select wrapper that filters out options that are already selected in opposite order.
100+
*
101+
* @template T
102+
* @param {object} props - component props
103+
* @param {string} props.id - unique id (from SelectInterface)
104+
* @param {import('~/utils/types').OptionType[]} props.options - array of available options
105+
* @param {T[]} props.value - array of selected raw values
106+
* @param {React.Dispatch<import('react').SetStateAction<T[]>>} props.setValue - setter to update the array of selected values
107+
* @param {string} [props.placeholder] - placeholder text
108+
* @param {string|null} [props.label] - optional label shown above select
109+
*
110+
* Behaviour:
111+
* - Computes `selected` as list of Option entries whose value is included in `value`.
112+
* - handleSelect adds an item to the value array; handleDeselect removes it.
113+
* - Filters out options that are already selected in opposite order (e.g., if 'id' is selected, '-id' is removed from options).
114+
*/
115+
export function FormSelectMultiOrdering<T extends string | number = string>(props: SelectInterface<T[]>) {
116+
const { value, setValue, options } = { ...props };
117+
let optionsFiltered = options;
118+
119+
// Typescipt safety check
120+
if (Array.isArray(value)) {
121+
for (const val of value) {
122+
const valStr = String(val);
123+
// There is django ordering convention where negative value means opposite order
124+
if (valStr.startsWith('-')) {
125+
const actualVal = valStr.substring(1);
126+
optionsFiltered = optionsFiltered.filter(opt => String(opt.value) !== actualVal);
127+
} else {
128+
optionsFiltered = optionsFiltered.filter(opt => String(opt.value) !== `-${ valStr}`);
129+
}
130+
}
131+
}
132+
133+
return (
134+
<FormSelectMulti
135+
{...props}
136+
value={value}
137+
setValue={setValue}
138+
options={optionsFiltered}
139+
/>
140+
);
141+
}

Tokenization/webapp/app/components/form/select-group.tsx

Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,37 +14,61 @@
1414

1515
import React, { type PropsWithChildren } from 'react';
1616
import type { SelectInterface } from './form.d';
17+
import type { OptionType } from '~/utils/types';
1718

1819
import { FormSelect } from './form-select';
1920
import { checkIsComponentOfType } from '~/utils/component-type-checker';
2021

2122
/**
22-
* SelectGroup
23+
* collectSelectsInfo
2324
*
24-
* Inspects children and deduplicates options across FormSelect children by cloning them.
25+
* Inspects children to collect FormSelect components and their options and values.
2526
*
26-
* @param {object} props - component props
27-
* @param {React.ReactNode} props.children - child nodes which should only include FormSelect components;
28-
* SelectGroup will detect those and clone them with filtered options
27+
* @param {React.ReactNode} children - child nodes which may include FormSelect components
2928
*
30-
* Behaviour notes:
31-
* - Looks for direct children of type FormSelect.
32-
* - Builds list of values used by other selects and removes them from each select's options to avoid duplicates.
33-
* - Clones and returns modified select children; non-select children are not displayed so they shouldn't be used.
29+
* @returns {object} - object containing:
30+
* - selects: array of FormSelect components found among children
31+
* - selectsLen: number of FormSelect components found
32+
* - optionsList: array of options arrays for each FormSelect
33+
* - values: array of selected values for each FormSelect
3434
*/
35-
export function SelectGroup({ children }: PropsWithChildren) {
35+
function collectSelectsInfo(children: React.ReactNode) {
3636
const arrChildren = React.Children.toArray(children);
3737
const selects = arrChildren.filter((component) => checkIsComponentOfType(component, FormSelect));
3838
const optionsList = selects.map((select) => React.isValidElement(select) ? (select.props as SelectInterface).options : null);
3939
const values = selects.map((select) => React.isValidElement(select) ? (select.props as SelectInterface).value : null);
40+
const selectsLen = selects.length;
41+
42+
return {
43+
selects,
44+
selectsLen,
45+
optionsList,
46+
values,
47+
};
48+
}
4049

50+
type valuesType = (string | number | (string | number)[] | null)[];
51+
52+
/**
53+
* FilterSelectedFromOptions
54+
*
55+
* Clones select components with filtered options to remove already selected values from other selects.
56+
*
57+
* @param {React.ReactNode[]} selects - array of select components
58+
* @param {number} selectsLen - length of selects array
59+
* @param {(OptionType[] | null)[]} optionsList - array of options arrays for each select
60+
* @param {valuesType} values - array of selected values for each select
61+
*
62+
* @returns {React.ReactNode[]} - array of cloned select components with filtered options
63+
*/
64+
function filterSelectedFromOptions(selects: React.ReactNode[], selectsLen: number, optionsList: (OptionType[] | null)[], values: valuesType) {
4165
const returnChildren = [];
42-
const noSelects = selects.length;
4366

44-
for (let i = 0; i < noSelects; i++) {
67+
for (let i = 0; i < selectsLen; i++) {
4568
let select = selects[i];
4669
let options = optionsList[i];
4770

71+
// Get all values selected in other selects
4872
const differentSelectValues = values.filter((_, idx) => idx != i);
4973

5074
if (options !== null) {
@@ -58,3 +82,22 @@ export function SelectGroup({ children }: PropsWithChildren) {
5882

5983
return [...returnChildren];
6084
}
85+
86+
/**
87+
* SelectGroup
88+
*
89+
* Inspects children and deduplicates options across FormSelect children by cloning them.
90+
*
91+
* @param {object} props - component props
92+
* @param {React.ReactNode} props.children - child nodes which should only include FormSelect components;
93+
* SelectGroup will detect those and clone them with filtered options
94+
*
95+
* Behaviour notes:
96+
* - Looks for direct children of type FormSelect.
97+
* - Builds list of values used by other selects and removes them from each select's options to avoid duplicates.
98+
* - Clones and returns modified select children; non-select children are not displayed so they shouldn't be used.
99+
*/
100+
export function SelectGroup({ children }: PropsWithChildren) {
101+
const { selects, selectsLen, optionsList, values } = collectSelectsInfo(children);
102+
return filterSelectedFromOptions(selects, selectsLen, optionsList, values);
103+
}

Tokenization/webapp/app/components/tokens/token-filters.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { useEffect } from 'react';
1616

1717
import { setStorageItem } from '~/utils/storage';
1818

19-
import { FormSelectMulti } from '../form/form-select';
19+
import { FormSelectMulti, FormSelectMultiOrdering } from '../form/form-select';
2020
import { FormInput } from '../form/form-input';
2121
import { useTokenFilters } from '~/hooks/tokens/token-filters';
2222
import { FlexGrowWrapper, FlexGrowWrapperElement } from '~/ui/flex';
@@ -155,7 +155,7 @@ export function TokenFilters() {
155155
/>
156156
</FlexGrowWrapper>
157157
<FlexGrowWrapper>
158-
<FormSelectMulti
158+
<FormSelectMultiOrdering
159159
id='tags'
160160
label="Order by:"
161161
options={orderingOptions}

0 commit comments

Comments
 (0)