Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 29 additions & 23 deletions src/components/shared/DropDown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
ref?: React.RefObject<SelectInstance<any, boolean, GroupBase<any>> | null>
value: T
text: string,
options: DropDownOption[],
options?: DropDownOption[],
required: boolean,
handleChange: (option: {value: T, label: string} | null) => void
placeholder: string
Expand All @@ -66,7 +66,7 @@
optionPaddingTop?: number,
optionLineHeight?: string
},
fetchOptions?: () => { label: string, value: string}[]
fetchOptions?: (inputValue: string) => Promise<{ label: string, value: string }[]>
}) => {
const { t } = useTranslation();

Expand Down Expand Up @@ -157,14 +157,29 @@
) : null;
};

const filterOptions = (inputValue: string) => {
if (options) {
return options.filter(option =>
option.label.toLowerCase().includes(inputValue.toLowerCase()),
);
}
return [];
};

const loadOptionsAsync = (inputValue: string, callback: (options: DropDownOption[]) => void) => {
setTimeout(async () => {
callback(formatOptions(
fetchOptions ? await fetchOptions(inputValue) : filterOptions(inputValue),
required,
));
}, 1000);
};

const loadOptions = (
inputValue: string,
callback: (options: DropDownOption[]) => void,
) => {
callback(formatOptions(
fetchOptions ? fetchOptions() : options,
required,
));
callback(formatOptions(filterOptions(inputValue), required));

Check failure on line 182 in src/components/shared/DropDown.tsx

View workflow job for this annotation

GitHub Actions / build

Cannot find name 'inputValue'. Did you mean '_inputValue'?
};


Expand All @@ -176,10 +191,14 @@
autoFocus: autoFocus,
isSearchable: true,
value: { value: value, label: text === "" ? placeholder : text },
options: formatOptions(
options,
required,
),
defaultOptions: options
? formatOptions(
options,
required,
)
: true,
cacheOptions: true,
loadOptions: fetchOptions ? loadOptionsAsync : loadOptions,
placeholder: placeholder,
onChange: element => handleChange(element as {value: T, label: string}),
menuIsOpen: menuIsOpen,
Expand All @@ -191,31 +210,18 @@

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

return creatable ? (
<AsyncCreatableSelect
ref={selectRef}
{...commonProps}
cacheOptions
defaultOptions={formatOptions(
options,
required,
)}
loadOptions={loadOptions}
/>
) : (
<AsyncSelect
ref={selectRef}
{...commonProps}
noOptionsMessage={() => t("SELECT_NO_MATCHING_RESULTS")}
cacheOptions
defaultOptions={formatOptions(
options,
required,
)}
loadOptions={loadOptions}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,11 +543,6 @@ export const AccessPolicyTable = <T extends AccessPolicyTabFormikProps>({
? formatAclRolesForDropdown(rolesFilteredbyPolicies)
: []
}
fetchOptions={() =>
roles.length > 0
? formatAclRolesForDropdown(rolesFilteredbyPolicies)
: []
}
required={true}
creatable={true}
handleChange={element => {
Expand Down
101 changes: 86 additions & 15 deletions src/components/shared/wizard/RenderField.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import React, { useRef, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import DatePicker from "react-datepicker";
import cn from "classnames";
import { getMetadataCollectionFieldName } from "../../../utils/resourceUtils";
import { getMetadataCollectionFieldName, transformListProvider } from "../../../utils/resourceUtils";
import { getCurrentLanguageInformation } from "../../../utils/utils";
import DropDown from "../DropDown";
import { parseISO } from "date-fns";
import { FieldProps } from "formik";
import { MetadataField } from "../../../slices/eventSlice";
import { GroupBase, SelectInstance } from "react-select";
import TextareaAutosize from "react-textarea-autosize";
import axios from "axios";

/**
* This component renders an editable field for single values depending on the type of the corresponding metadata
Expand Down Expand Up @@ -65,7 +66,7 @@ const RenderField = ({
)}
{metadataField.type === "text" &&
!!metadataField.collection &&
metadataField.collection.length > 0 && (
(
<EditableSingleSelect
metadataField={metadataField}
field={field}
Expand All @@ -91,7 +92,7 @@ const RenderField = ({
)}
{metadataField.type === "text" &&
!(
!!metadataField.collection && metadataField.collection.length !== 0
metadataField.collection
) && (
<EditableSingleValue
field={field}
Expand Down Expand Up @@ -195,16 +196,7 @@ const EditableDateValue = ({
};

// renders editable field for selecting value via dropdown
const EditableSingleSelect = ({
field,
metadataField,
text,
form: { setFieldValue },
isFirstField,
focused,
setFocused,
ref,
}: {
type EditableSingleSelectProps = ({
field: FieldProps["field"]
metadataField: MetadataField
text: string
Expand All @@ -213,9 +205,25 @@ const EditableSingleSelect = ({
focused: boolean,
setFocused: (open: boolean) => void
ref: React.RefObject<SelectInstance<any, boolean, GroupBase<any>>>
}) => {
})
const EditableSingleSelect = (props: EditableSingleSelectProps) => {
const { t } = useTranslation();

const {
field,
metadataField,
text,
form: { setFieldValue },
isFirstField,
focused,
setFocused,
ref,
} = props;

if (metadataField.id === "isPartOf") {
return <EditableSingleSelectSeries {...props} />;
}

return (
<DropDown
ref={ref}
Expand Down Expand Up @@ -321,4 +329,67 @@ const EditableSingleValueTime = ({
);
};

/**
* Special case for series. Uses an async selector to fetch options.
*
* Ideally we could generalize this for all metadata fields with listproviders,
* but other listproviders do not offer the required filtering capabilities.
*/
const EditableSingleSelectSeries = ({
field,
metadataField,
text,
form: { setFieldValue },
isFirstField,
focused,
setFocused,
ref,
}: EditableSingleSelectProps) => {
const { t } = useTranslation();

const [label, setLabel] = useState("");

useEffect(() => {
// The metadata catalog only contains the field value, so we need to fetch the label ourselves
const fetchLabelById = async () => {
if (field.value) {
const res = await axios.get<{ [key: string]: string }>(`/admin-ng/resources/SERIES.WRITE_ONLY.json?limit=1&filter=textFilter:${field.value}`);
const data = res.data;
const transformedData = transformListProvider(data);
if (transformedData.length > 0) {
setLabel(transformedData[0].label);
}
}
};
fetchLabelById();
}, [field.value]);

// Fetch collection
const fetchOptions = async (inputValue: string) => {
const res = await axios.get<{ [key: string]: string }>(`/admin-ng/resources/SERIES.WRITE_ONLY.json?filter=textFilter:${inputValue}`);
const data = res.data;
return transformListProvider(data);
};

return (
<DropDown
ref={ref}
value={field.value as string}
text={label}
fetchOptions={fetchOptions}
required={metadataField.required}
handleChange={element => element && setFieldValue(field.name, element.value)}
placeholder={focused
? `-- ${t("SELECT_NO_OPTION_SELECTED")} --`
: `${t("SELECT_NO_OPTION_SELECTED")}`
}
customCSS={{ isMetadataStyle: focused ? false : true }}
handleMenuIsOpen={(open: boolean) => setFocused(open)}
openMenuOnFocus
autoFocus={isFirstField}
skipTranslate={!metadataField.translatable}
/>
);
};

export default RenderField;
21 changes: 21 additions & 0 deletions src/utils/resourceUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,27 @@ export const transformMetadataFields = (metadata: MetadataField[]) => {
return metadata;
};

export const transformListProvider = (collection: { [key: string]: string }) => {
return Object.entries(collection)
.map(([key, value]) => {
if (isJson(value)) {
// TODO: Handle JSON parsing errors
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const collectionParsed: { [key: string]: string } = JSON.parse(value);
return {
label: collectionParsed.label || value,
value: key,
...collectionParsed,
};
} else {
return {
label: value,
value: key,
};
}
});
};

// transform metadata catalog for update via post request
export const transformMetadataForUpdate = (catalog: MetadataCatalog, values: { [key: string]: MetadataCatalog["fields"][0]["value"] }) => {
const fields: MetadataCatalog["fields"] = [];
Expand Down
Loading