Skip to content

Commit 7a75d11

Browse files
committed
Merge branch 'r/18.x' into develop
2 parents 586ff7a + 3d3bd0b commit 7a75d11

4 files changed

Lines changed: 136 additions & 43 deletions

File tree

src/components/shared/DropDown.tsx

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const DropDown = <T, >({
4545
ref?: React.RefObject<SelectInstance<any, boolean, GroupBase<any>> | null>
4646
value: T
4747
text: string,
48-
options: DropDownOption[],
48+
options?: DropDownOption[],
4949
required: boolean,
5050
handleChange: (option: {value: T, label: string} | null) => void
5151
placeholder: string
@@ -66,7 +66,7 @@ const DropDown = <T, >({
6666
optionPaddingTop?: number,
6767
optionLineHeight?: string
6868
},
69-
fetchOptions?: () => { label: string, value: string}[]
69+
fetchOptions?: (inputValue: string) => Promise<{ label: string, value: string }[]>
7070
}) => {
7171
const { t } = useTranslation();
7272

@@ -157,14 +157,29 @@ const DropDown = <T, >({
157157
) : null;
158158
};
159159

160+
const filterOptions = (inputValue: string) => {
161+
if (options) {
162+
return options.filter(option =>
163+
option.label.toLowerCase().includes(inputValue.toLowerCase()),
164+
);
165+
}
166+
return [];
167+
};
168+
169+
const loadOptionsAsync = (inputValue: string, callback: (options: DropDownOption[]) => void) => {
170+
setTimeout(async () => {
171+
callback(formatOptions(
172+
fetchOptions ? await fetchOptions(inputValue) : filterOptions(inputValue),
173+
required,
174+
));
175+
}, 1000);
176+
};
177+
160178
const loadOptions = (
161179
_inputValue: string,
162180
callback: (options: DropDownOption[]) => void,
163181
) => {
164-
callback(formatOptions(
165-
fetchOptions ? fetchOptions() : options,
166-
required,
167-
));
182+
callback(formatOptions(filterOptions(inputValue), required));
168183
};
169184

170185

@@ -176,10 +191,14 @@ const DropDown = <T, >({
176191
autoFocus: autoFocus,
177192
isSearchable: true,
178193
value: { value: value, label: text === "" ? placeholder : text },
179-
options: formatOptions(
180-
options,
181-
required,
182-
),
194+
defaultOptions: options
195+
? formatOptions(
196+
options,
197+
required,
198+
)
199+
: true,
200+
cacheOptions: true,
201+
loadOptions: fetchOptions ? loadOptionsAsync : loadOptions,
183202
placeholder: placeholder,
184203
onChange: element => handleChange(element as {value: T, label: string}),
185204
menuIsOpen: menuIsOpen,
@@ -191,32 +210,19 @@ const DropDown = <T, >({
191210

192211
// @ts-expect-error: React-Select typing does not account for the typing of option it itself requires
193212
components: { MenuList },
194-
filterOption: createFilter({ ignoreAccents: false }), // To improve performance on filtering
195213
};
196214

197215
return creatable ? (
198216
<AsyncCreatableSelect
199217
ref={selectRef}
200218
{...commonProps}
201-
cacheOptions
202-
defaultOptions={formatOptions(
203-
options,
204-
required,
205-
)}
206-
loadOptions={loadOptions}
207219
/>
208220
) : (
209221
<AsyncSelect
210222
ref={selectRef}
211223
{...commonProps}
212224
openMenuOnFocus={false}
213225
noOptionsMessage={() => t("SELECT_NO_MATCHING_RESULTS")}
214-
cacheOptions
215-
defaultOptions={formatOptions(
216-
options,
217-
required,
218-
)}
219-
loadOptions={loadOptions}
220226
/>
221227
);
222228
};

src/components/shared/modals/ResourceDetailsAccessPolicyTab.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -544,11 +544,6 @@ export const AccessPolicyTable = <T extends AccessPolicyTabFormikProps>({
544544
? formatAclRolesForDropdown(rolesFilteredbyPolicies)
545545
: []
546546
}
547-
fetchOptions={() =>
548-
roles.length > 0
549-
? formatAclRolesForDropdown(rolesFilteredbyPolicies)
550-
: []
551-
}
552547
required={true}
553548
creatable={true}
554549
handleChange={element => {

src/components/shared/wizard/RenderField.tsx

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import React, { useRef, useState } from "react";
1+
import React, { useEffect, useRef, useState } from "react";
22
import { useTranslation } from "react-i18next";
33
import DatePicker from "react-datepicker";
44
import cn from "classnames";
5-
import { getMetadataCollectionFieldName } from "../../../utils/resourceUtils";
5+
import { getMetadataCollectionFieldName, transformListProvider } from "../../../utils/resourceUtils";
66
import { getCurrentLanguageInformation } from "../../../utils/utils";
77
import DropDown from "../DropDown";
88
import { parseISO } from "date-fns";
@@ -11,6 +11,7 @@ import { MetadataField } from "../../../slices/eventSlice";
1111
import { GroupBase, SelectInstance } from "react-select";
1212
import TextareaAutosize from "react-textarea-autosize";
1313
import { LuCheck, LuSquarePen } from "react-icons/lu";
14+
import axios from "axios";
1415

1516
/**
1617
* This component renders an editable field for single values depending on the type of the corresponding metadata
@@ -66,7 +67,7 @@ const RenderField = ({
6667
)}
6768
{metadataField.type === "text" &&
6869
!!metadataField.collection &&
69-
metadataField.collection.length > 0 && (
70+
(
7071
<EditableSingleSelect
7172
metadataField={metadataField}
7273
field={field}
@@ -92,7 +93,7 @@ const RenderField = ({
9293
)}
9394
{metadataField.type === "text" &&
9495
!(
95-
!!metadataField.collection && metadataField.collection.length !== 0
96+
metadataField.collection
9697
) && (
9798
<EditableSingleValue
9899
field={field}
@@ -197,16 +198,7 @@ const EditableDateValue = ({
197198
};
198199

199200
// renders editable field for selecting value via dropdown
200-
const EditableSingleSelect = ({
201-
field,
202-
metadataField,
203-
text,
204-
form: { setFieldValue },
205-
isFirstField,
206-
focused,
207-
setFocused,
208-
ref,
209-
}: {
201+
type EditableSingleSelectProps = ({
210202
field: FieldProps["field"]
211203
metadataField: MetadataField
212204
text: string
@@ -215,9 +207,25 @@ const EditableSingleSelect = ({
215207
focused: boolean,
216208
setFocused: (open: boolean) => void
217209
ref: React.RefObject<SelectInstance<any, boolean, GroupBase<any>>>
218-
}) => {
210+
})
211+
const EditableSingleSelect = (props: EditableSingleSelectProps) => {
219212
const { t } = useTranslation();
220213

214+
const {
215+
field,
216+
metadataField,
217+
text,
218+
form: { setFieldValue },
219+
isFirstField,
220+
focused,
221+
setFocused,
222+
ref,
223+
} = props;
224+
225+
if (metadataField.id === "isPartOf") {
226+
return <EditableSingleSelectSeries {...props} />;
227+
}
228+
221229
return (
222230
<DropDown
223231
ref={ref}
@@ -323,4 +331,67 @@ const EditableSingleValueTime = ({
323331
);
324332
};
325333

334+
/**
335+
* Special case for series. Uses an async selector to fetch options.
336+
*
337+
* Ideally we could generalize this for all metadata fields with listproviders,
338+
* but other listproviders do not offer the required filtering capabilities.
339+
*/
340+
const EditableSingleSelectSeries = ({
341+
field,
342+
metadataField,
343+
text,
344+
form: { setFieldValue },
345+
isFirstField,
346+
focused,
347+
setFocused,
348+
ref,
349+
}: EditableSingleSelectProps) => {
350+
const { t } = useTranslation();
351+
352+
const [label, setLabel] = useState("");
353+
354+
useEffect(() => {
355+
// The metadata catalog only contains the field value, so we need to fetch the label ourselves
356+
const fetchLabelById = async () => {
357+
if (field.value) {
358+
const res = await axios.get<{ [key: string]: string }>(`/admin-ng/resources/SERIES.WRITE_ONLY.json?limit=1&filter=textFilter:${field.value}`);
359+
const data = res.data;
360+
const transformedData = transformListProvider(data);
361+
if (transformedData.length > 0) {
362+
setLabel(transformedData[0].label);
363+
}
364+
}
365+
};
366+
fetchLabelById();
367+
}, [field.value]);
368+
369+
// Fetch collection
370+
const fetchOptions = async (inputValue: string) => {
371+
const res = await axios.get<{ [key: string]: string }>(`/admin-ng/resources/SERIES.WRITE_ONLY.json?filter=textFilter:${inputValue}`);
372+
const data = res.data;
373+
return transformListProvider(data);
374+
};
375+
376+
return (
377+
<DropDown
378+
ref={ref}
379+
value={field.value as string}
380+
text={label}
381+
fetchOptions={fetchOptions}
382+
required={metadataField.required}
383+
handleChange={element => element && setFieldValue(field.name, element.value)}
384+
placeholder={focused
385+
? `-- ${t("SELECT_NO_OPTION_SELECTED")} --`
386+
: `${t("SELECT_NO_OPTION_SELECTED")}`
387+
}
388+
customCSS={{ isMetadataStyle: focused ? false : true }}
389+
handleMenuIsOpen={(open: boolean) => setFocused(open)}
390+
openMenuOnFocus
391+
autoFocus={isFirstField}
392+
skipTranslate={!metadataField.translatable}
393+
/>
394+
);
395+
};
396+
326397
export default RenderField;

src/utils/resourceUtils.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,27 @@ export const transformMetadataFields = (metadata: MetadataField[]) => {
177177
return metadata;
178178
};
179179

180+
export const transformListProvider = (collection: { [key: string]: string }) => {
181+
return Object.entries(collection)
182+
.map(([key, value]) => {
183+
if (isJson(value)) {
184+
// TODO: Handle JSON parsing errors
185+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
186+
const collectionParsed: { [key: string]: string } = JSON.parse(value);
187+
return {
188+
label: collectionParsed.label || value,
189+
value: key,
190+
...collectionParsed,
191+
};
192+
} else {
193+
return {
194+
label: value,
195+
value: key,
196+
};
197+
}
198+
});
199+
};
200+
180201
// transform metadata catalog for update via post request
181202
export const transformMetadataForUpdate = (catalog: MetadataCatalog, values: { [key: string]: MetadataCatalog["fields"][0]["value"] }) => {
182203
const fields: MetadataCatalog["fields"] = [];

0 commit comments

Comments
 (0)