Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ [#5016] Referentielijsten dataSrc for options #204

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
14 changes: 14 additions & 0 deletions .storybook/decorators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import {
DEFAULT_MAP_TILE_LAYERS,
DEFAULT_PREFILL_ATTRIBUTES,
DEFAULT_PREFILL_PLUGINS,
DEFAULT_REFERENTIELIJSTEN_TABELLEN,
DEFAULT_REGISTRATION_ATTRIBUTES,
DEFAULT_SERVICES,
DEFAULT_VALIDATOR_PLUGINS,
sleep,
} from '@/tests/sharedUtils';
Expand Down Expand Up @@ -61,6 +63,10 @@ export const BuilderContextDecorator: Decorator = (Story, context) => {
context.parameters.builder?.defaultValidatorPlugins || DEFAULT_VALIDATOR_PLUGINS;
const defaultRegistrationAttributes =
context.parameters.builder?.defaultRegistrationAttributes || DEFAULT_REGISTRATION_ATTRIBUTES;
const defaultServices = context.parameters.builder?.defaultServices || DEFAULT_SERVICES;
const defaultReferentielijstenTabellen =
context.parameters.builder?.defaultReferentielijstenTabellen ||
DEFAULT_REFERENTIELIJSTEN_TABELLEN;
const defaultPrefillPlugins =
context.parameters.builder?.defaultPrefillPlugins || DEFAULT_PREFILL_PLUGINS;
const defaultPrefillAttributes =
Expand All @@ -86,6 +92,14 @@ export const BuilderContextDecorator: Decorator = (Story, context) => {
await sleep(context.parameters?.builder?.registrationAttributesDelay || 0);
return context?.args?.registrationAttributes || defaultRegistrationAttributes;
},
getServices: async () => {
await sleep(context.parameters?.builder?.servicesDelay || 0);
return context?.args?.services || defaultServices;
},
getReferentielijstenTabellen: async () => {
await sleep(context.parameters?.builder?.referentielijstenTabellenDelay || 0);
return context?.args?.referentielijstenTabellen || defaultReferentielijstenTabellen;
},
getPrefillPlugins: async () => {
await sleep(context.parameters?.builder?.prefillPluginsDelay || 0);
return context?.args?.prefillPlugins || defaultPrefillPlugins;
Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"@formatjs/cli": "^6.1.1",
"@formatjs/ts-transformer": "^3.12.0",
"@fortawesome/fontawesome-free": "^6.4.0",
"@open-formulieren/types": "^0.40.0",
"@open-formulieren/types": "^0.41.0",
"@storybook/addon-actions": "^8.3.5",
"@storybook/addon-essentials": "^8.3.5",
"@storybook/addon-interactions": "^8.3.5",
Expand Down
4 changes: 4 additions & 0 deletions src/components/ComponentConfiguration.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {Meta, StoryFn, StoryObj} from '@storybook/react';
import {expect, fireEvent, fn, userEvent, waitFor, within} from '@storybook/test';
import React from 'react';

import {ReferentielijstenServiceOption} from '@/components/builder/values/referentielijsten/service';
import {
CONFIDENTIALITY_LEVELS,
DEFAULT_AUTH_PLUGINS,
Expand Down Expand Up @@ -75,6 +76,7 @@ interface TemplateArgs {
validatorPlugins: ValidatorOption[];
registrationAttributes: RegistrationAttributeOption[];
prefillPlugins: PrefillPluginOption[];
services: ReferentielijstenServiceOption[];
prefillAttributes: Record<string, PrefillAttributeOption[]>;
fileTypes: Array<{value: string; label: string}>;
isNew: boolean;
Expand All @@ -91,6 +93,7 @@ const Template: StoryFn<TemplateArgs> = ({
registrationAttributes,
prefillPlugins,
prefillAttributes,
services,
supportedLanguageCodes,
fileTypes,
isNew,
Expand All @@ -107,6 +110,7 @@ const Template: StoryFn<TemplateArgs> = ({
getFormComponents={() => otherComponents}
getValidatorPlugins={async () => validatorPlugins}
getRegistrationAttributes={async () => registrationAttributes}
getServices={async () => services}
getPrefillPlugins={async () => prefillPlugins}
getPrefillAttributes={async (plugin: string) => prefillAttributes[plugin]}
getFileTypes={async () => fileTypes}
Expand Down
4 changes: 4 additions & 0 deletions src/components/ComponentConfiguration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ const ComponentConfiguration: React.FC<ComponentConfigurationProps> = ({
getFormComponents,
getValidatorPlugins,
getRegistrationAttributes,
getServices,
getReferentielijstenTabellen,
getPrefillPlugins,
getPrefillAttributes,
getFileTypes,
Expand Down Expand Up @@ -66,6 +68,8 @@ const ComponentConfiguration: React.FC<ComponentConfigurationProps> = ({
getFormComponents,
getValidatorPlugins,
getRegistrationAttributes,
getServices,
getReferentielijstenTabellen,
getPrefillPlugins,
getPrefillAttributes,
getFileTypes,
Expand Down
91 changes: 91 additions & 0 deletions src/components/builder/values/referentielijsten/code.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import {useFormikContext} from 'formik';
import {useContext} from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import useAsync from 'react-use/esm/useAsync';

import Select from '@/components/formio/select';
import {BuilderContext} from '@/context';

export interface ReferentielijstenTabelOption {
code: string;
naam: string;
isGeldig: boolean;
}

function isTabelOptions(
options: ReferentielijstenTabelOption[] | undefined
): options is ReferentielijstenTabelOption[] {
return options !== undefined;
}

function transformItems(items: ReferentielijstenTabelOption[]) {
const intl = useIntl();
return items.map(item => {
const {code, naam, isGeldig} = item;
return {
value: code,
label: !isGeldig
? `${naam} ${intl.formatMessage({
description: 'Message to indicate that Referentielijsten tabel is expired',
defaultMessage: '(niet meer geldig)',
})}`
: naam,
};
});
}

export interface ComponentWithReferentielijsten {
openForms?: {
dataSrc: 'referentielijsten';
service: string;
code: string;
};
}

/**
* The `ReferentielijstenTabelCode` component is used to specify the code of the tabel
* in Referentielijsten API for which the items will be fetched
*/
export const ReferentielijstenTabelCode: React.FC = () => {
const intl = useIntl();
const {values} = useFormikContext<ComponentWithReferentielijsten>();
const service = values?.openForms?.service;
const {getReferentielijstenTabellen} = useContext(BuilderContext);
const {
value: options,
loading,
error,
} = useAsync(async () => {
if (service) {
return await getReferentielijstenTabellen(service);
}
return [];
}, [service]);

if (error) {
throw error;
}
const _options = isTabelOptions(options) ? transformItems(options) : [];

return (
<Select
name="openForms.code"
label={
<FormattedMessage
description="Label for 'openForms.code' builder field"
defaultMessage="Referentielijsten table code"
/>
}
tooltip={intl.formatMessage({
description: "Description for the 'openForms.code' builder field",
defaultMessage: `The code of the table from which the options will be retrieved.`,
})}
isLoading={loading}
options={_options}
valueProperty="value"
required
/>
);
};

export default ReferentielijstenTabelCode;
9 changes: 9 additions & 0 deletions src/components/builder/values/referentielijsten/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Components to manage options/values for fields that support this, such as:
*
* - select
* - selectboxes
* - radio
*/
export {default as ReferentielijstenServiceSelect} from './service';
export {default as ReferentielijstenTabelCode} from './code';
63 changes: 63 additions & 0 deletions src/components/builder/values/referentielijsten/service.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {useFormikContext} from 'formik';
import {useContext} from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import useAsync from 'react-use/esm/useAsync';

import Select from '@/components/formio/select';
import {BuilderContext} from '@/context';

import {ComponentWithReferentielijsten} from './code';

export interface ReferentielijstenServiceOption {
url: string;
slug: string;
label: string;
apiRoot: string;
apiType: string;
}

/**
* Fetch the available Referentielijsten Services and display them in a Select
*
* The selected service is used at runtime to retrieve options to populate a Select
*
* This requires an async function `getServices` to be provided to the
* BuilderContext which is responsible for retrieving the list of available plugins.
*
* If a fetch error occurs, it is thrown during rendering - you should provide your
* own error boundary to catch this.
*/
const ReferentielijstenServiceSelect: React.FC = () => {
const intl = useIntl();
const {values, setFieldValue} = useFormikContext<ComponentWithReferentielijsten>();
const {getServices} = useContext(BuilderContext);
const {value: options = [], loading} = useAsync(async () => {
const options = await getServices('referentielijsten');
if (options.length === 1 && !values?.openForms?.service) {
setFieldValue('openForms.service', options[0].slug);
}
return options;
}, [getServices, setFieldValue]); // values is deliberately excluded from the dependency array

return (
<Select
name="openForms.service"
label={
<FormattedMessage
description="Label for 'openForms.service' builder field"
defaultMessage="Referentielijsten service"
/>
}
tooltip={intl.formatMessage({
description: "Description for the 'openForms.service' builder field",
defaultMessage: `The identifier of the Referentielijsten service from which the options will be retrieved.`,
})}
isLoading={loading}
valueProperty="slug"
options={options}
required
/>
);
};

export default ReferentielijstenServiceSelect;
55 changes: 55 additions & 0 deletions src/components/builder/values/values-config.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {Meta, StoryObj} from '@storybook/react';
import {expect, fireEvent, fn, userEvent, waitFor, within} from '@storybook/test';
import {Form, Formik} from 'formik';

import {BuilderContextDecorator} from '@/sb-decorators';
import {withFormik} from '@/sb-decorators';

import ValuesConfig from './values-config';
Expand Down Expand Up @@ -300,3 +301,57 @@ export const SelectVariable: SelectStory = {
},
},
};

export const SelectReferentielijsten: SelectStory = {
...Select,

decorators: [withFormik, BuilderContextDecorator],
sergei-maertens marked this conversation as resolved.
Show resolved Hide resolved
parameters: {
formik: {
initialValues: {
openForms: {
dataSrc: 'referentielijsten',
itemsExpression: {code: 'table1', service: 'referentielijsten-api'},
},
data: {},
},
},
builder: {enableContext: true},
},
};

export const SelectboxesReferentielijsten: SelectboxesStory = {
...SelectBoxes,

decorators: [withFormik, BuilderContextDecorator],
parameters: {
formik: {
initialValues: {
openForms: {
dataSrc: 'referentielijsten',
itemsExpression: {code: 'table1', service: 'referentielijsten-api'},
},
data: {},
},
},
builder: {enableContext: true},
},
};

export const RadioReferentielijsten: RadioStory = {
...Radio,

decorators: [withFormik, BuilderContextDecorator],
parameters: {
formik: {
initialValues: {
openForms: {
dataSrc: 'referentielijsten',
itemsExpression: {code: 'table1', service: 'referentielijsten-api'},
},
data: {},
},
},
builder: {enableContext: true},
},
};
Loading