Skip to content

Commit

Permalink
Merge pull request #204 from open-formulieren/feature/5016-data-sourc…
Browse files Browse the repository at this point in the history
…e-referentielijsten

✨ [#5016] Referentielijsten dataSrc for options
  • Loading branch information
stevenbal authored Feb 6, 2025
2 parents 0775a41 + c1ef2f9 commit 622ecca
Show file tree
Hide file tree
Showing 30 changed files with 1,038 additions and 74 deletions.
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],
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

0 comments on commit 622ecca

Please sign in to comment.