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

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
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
19 changes: 19 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-select-event": "^5.5.1",
"sass": "^1.75.0",
"sass-loader": "^14.2.0",
"storybook": "^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
85 changes: 85 additions & 0 deletions src/components/builder/values/referentielijsten/code.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
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;
einddatumGeldigheid: string | null;
}

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

function transformItems(items: ReferentielijstenTabelOption[]) {
return items.map(item => {
const {code, naam, einddatumGeldigheid} = item;
return {
value: code,
label: einddatumGeldigheid ? `${naam} (${einddatumGeldigheid})` : naam,
};
});
}

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';
68 changes: 68 additions & 0 deletions src/components/builder/values/referentielijsten/service.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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';

// TODO transform this to id and label?
export interface ReferentielijstenServiceOption {
url: string;
slug: string;
label: string;
apiRoot: string;
apiType: string;
}

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

/**
* 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 {getServices} = useContext(BuilderContext);
const {
value: options,
loading,
error,
} = useAsync(async () => await getServices('referentielijsten'), []);
if (error) {
throw error;
}
const _options = isServiceOptions(options) ? options : [];

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}
options={_options}
valueProperty="slug"
required
/>
);
};

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

import selectboxes from '@/registry/selectboxes';
import {BuilderContextDecorator} from '@/sb-decorators';
import {withFormik} from '@/sb-decorators';
import {DEFAULT_SERVICES} from '@/tests/sharedUtils';

import ValuesConfig from './values-config';

Expand Down Expand Up @@ -300,3 +303,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
Loading