Skip to content

Commit

Permalink
🚧 [#5016] Referentielijsten dataSrc for options
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenbal committed Jan 14, 2025
1 parent ac9ae4b commit d1b0564
Show file tree
Hide file tree
Showing 10 changed files with 235 additions and 13 deletions.
24 changes: 24 additions & 0 deletions src/components/ComponentPreview.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,30 @@ export const SelectVariable: Story = {
},
};

// /**
// * A select component with options sources from Referentielijsten API.
// */
// export const SelectReferentielijsten: Story = {
// name: 'Select: values from Referentielijsten API',
// render: Template,

// args: {
// component: {
// type: 'select',
// id: 'select',
// key: 'selectPreview',
// label: 'Select preview',
// description: 'A preview of the select Formio component',
// openForms: {
// dataSrc: 'referentielijsten',
// service: 'referentielijsten-api',
// code: 'tabel1',
// translations: {},
// },
// },
// },
// };

export const BSN: Story = {
name: 'BSN',
render: Template,
Expand Down
34 changes: 34 additions & 0 deletions src/components/builder/values/referentielijsten/code.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {useFormikContext} from 'formik';
import {FormattedMessage, useIntl} from 'react-intl';

import {TextField} from '@/components/formio';

const NAME = 'openForms.code';

/**
* 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 {setFieldValue} = useFormikContext();
const name = `editform-${NAME}`;
return (
<TextField
name={name}
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.`,
})}
onChange={event => setFieldValue(NAME, event.target.value)}
/>
);
};

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 ReferentielijstenService} from './service';
export {default as ReferentielijstenTabelCode} from './code';
42 changes: 42 additions & 0 deletions src/components/builder/values/referentielijsten/service.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {useFormikContext} from 'formik';
import {FormattedMessage, useIntl} from 'react-intl';

import {Select} from '@/components/formio';

const NAME = 'openForms.service';

/**
* The `ReferentielijstenService` component is used to specify the slug of the service
* that is used to retrieve options from
*/
export const ReferentielijstenService: React.FC = () => {
const intl = useIntl();
const {setFieldValue} = useFormikContext();
const name = `editform-${NAME}`;
return (
<Select
name={name}
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.`,
})}
onChange={event => setFieldValue(NAME, event.target.value)}
isClearable
// TODO should be dynamic
options={[
{label: 'Foo', value: 'foo'},
{label: 'Bar', value: 'bar'},
]}
valueProperty="value"
emptyValue=""
/>
);
};

export default ReferentielijstenService;
16 changes: 16 additions & 0 deletions src/components/builder/values/values-config.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,19 @@ export const SelectVariable: SelectStory = {
},
},
};

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

parameters: {
formik: {
initialValues: {
openForms: {
dataSrc: 'referentielijsten',
itemsExpression: {code: 'table1', service: 'referentielijsten-api'},
},
data: {},
},
},
},
};
25 changes: 25 additions & 0 deletions src/components/builder/values/values-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {useLayoutEffect} from 'react';
import {hasOwnProperty} from '@/types';

import ItemsExpression from './items-expression';
import {ReferentielijstenService, ReferentielijstenTabelCode} from './referentielijsten';
import {SchemaWithDataSrc} from './types';
import ValuesSrc from './values-src';
import ValuesTable, {ValuesTableProps} from './values-table';
Expand Down Expand Up @@ -59,6 +60,12 @@ export function ValuesConfig<T extends SchemaWithDataSrc>({
if (values.openForms.hasOwnProperty('itemsExpression')) {
setFieldValue('openForms.itemsExpression', undefined);
}
if (values.openForms.hasOwnProperty('code')) {
setFieldValue('openForms.itemsExpression', undefined);
}
if (values.openForms.hasOwnProperty('service')) {
setFieldValue('openForms.itemsExpression', undefined);
}
if (!isNestedKeySet(values, name)) {
setFieldValue(name, [{value: '', label: '', openForms: {translations: {}}}]);
}
Expand All @@ -68,6 +75,18 @@ export function ValuesConfig<T extends SchemaWithDataSrc>({
if (isNestedKeySet(values, name)) {
setFieldValue(name, undefined);
}
if (values.openForms.hasOwnProperty('code')) {
setFieldValue('openForms.itemsExpression', undefined);
}
if (values.openForms.hasOwnProperty('service')) {
setFieldValue('openForms.itemsExpression', undefined);
}
break;
}
case 'referentielijsten': {

Check failure on line 86 in src/components/builder/values/values-config.tsx

View workflow job for this annotation

GitHub Actions / Create 'production' build

Type '"referentielijsten"' is not comparable to type '"manual" | "variable"'.
if (values.openForms.hasOwnProperty('itemsExpression')) {

Check failure on line 87 in src/components/builder/values/values-config.tsx

View workflow job for this annotation

GitHub Actions / Create 'production' build

Property 'hasOwnProperty' does not exist on type 'never'.
setFieldValue('openForms.itemsExpression', undefined);
}
break;
}
}
Expand All @@ -82,6 +101,12 @@ export function ValuesConfig<T extends SchemaWithDataSrc>({
<ValuesTable<T> name={name} withOptionDescription={withOptionDescription} />
)}
{dataSrc === 'variable' && <ItemsExpression />}
{dataSrc === 'referentielijsten' && (

Check failure on line 104 in src/components/builder/values/values-config.tsx

View workflow job for this annotation

GitHub Actions / Create 'production' build

This comparison appears to be unintentional because the types '"manual" | "variable"' and '"referentielijsten"' have no overlap.
<>
<ReferentielijstenService />
<ReferentielijstenTabelCode />
</>
)}
</>
);
}
Expand Down
6 changes: 5 additions & 1 deletion src/components/builder/values/values-src.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ const OPTION_LABELS = defineMessages<OptionValue>({
description: "Data source option label for value 'variable'",
defaultMessage: 'From variable',
},
referentielijsten: {

Check failure on line 16 in src/components/builder/values/values-src.tsx

View workflow job for this annotation

GitHub Actions / Create 'production' build

Argument of type '{ manual: { description: string; defaultMessage: string; }; variable: { description: string; defaultMessage: string; }; referentielijsten: { description: string; defaultMessage: string; }; }' is not assignable to parameter of type 'Record<OptionValue, MessageDescriptor>'.
description: "Data source option label for value 'referentielijsten'",
defaultMessage: 'Referentielijsten API',
},
});

// define the values with the the desired correct order
const OPTION_VALUES = ['manual', 'variable'] as const;
const OPTION_VALUES = ['manual', 'variable', 'referentielijsten'] as const;

/**
* The `ValuesSrc` component is used to configure on the component where options/values
Expand Down
6 changes: 5 additions & 1 deletion src/registry/select/edit-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ const buildValuesSchema = (intl: IntlShape) =>
values: optionSchema(intl).array().min(1).optional(),
}),
openForms: z.object({
dataSrc: z.union([z.literal('manual'), z.literal('variable')]),
dataSrc: z.union([
z.literal('manual'),
z.literal('variable'),
z.literal('referentielijsten'),
]),
// TODO: wire up infernologic type checking
itemsExpression: jsonSchema.optional(),
}),
Expand Down
25 changes: 25 additions & 0 deletions src/registry/select/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {SelectComponentSchema} from '@open-formulieren/types';
import {Option} from '@open-formulieren/types/lib/formio/common';
import {JSONObject} from '@open-formulieren/types/lib/types';

// A type guard is needed because TS cannot figure out it's a discriminated union
// when the discriminator is nested.
Expand All @@ -9,3 +10,27 @@ export const checkIsManualOptions = (
): component is SelectComponentSchema & {data: {values: Option[] | undefined}} => {
return component.openForms.dataSrc === 'manual';
};

// A type guard is needed because TS cannot figure out it's a discriminated union
// when the discriminator is nested.
// See https://github.com/microsoft/TypeScript/issues/18758
export const checkIsReferentielijstenOptions = (
component: SelectComponentSchema
): component is SelectComponentSchema & {
data: {values: Option[] | undefined};
openForms: {code: string; service: string};
} => {
return component.openForms.dataSrc === 'referentielijsten';

Check failure on line 23 in src/registry/select/helpers.ts

View workflow job for this annotation

GitHub Actions / Create 'production' build

This comparison appears to be unintentional because the types '"manual" | "variable"' and '"referentielijsten"' have no overlap.
};

// A type guard is needed because TS cannot figure out it's a discriminated union
// when the discriminator is nested.
// See https://github.com/microsoft/TypeScript/issues/18758
export const checkIsVariableOptions = (
component: SelectComponentSchema
): component is SelectComponentSchema & {
data: {values: Option[] | undefined};
openForms: {itemsExpression: string | JSONObject};
} => {
return component.openForms.dataSrc === 'referentielijsten';

Check failure on line 35 in src/registry/select/helpers.ts

View workflow job for this annotation

GitHub Actions / Create 'production' build

This comparison appears to be unintentional because the types '"manual" | "variable"' and '"referentielijsten"' have no overlap.
};
61 changes: 50 additions & 11 deletions src/registry/select/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import {useIntl} from 'react-intl';
import {Select} from '@/components/formio';

import {ComponentPreviewProps} from '../types';
import {checkIsManualOptions} from './helpers';
import {
checkIsManualOptions,
checkIsReferentielijstenOptions,
checkIsVariableOptions,
} from './helpers';

/**
* Show a formio select component preview.
Expand All @@ -17,13 +21,45 @@ const Preview: React.FC<ComponentPreviewProps<SelectComponentSchema>> = ({compon
const intl = useIntl();
const {key, label, description, tooltip, validate, multiple} = component;
const {required = false} = validate || {};
const isManualOptions = checkIsManualOptions(component);
const options = isManualOptions
? component?.data?.values || []
: [
{
value: 'itemsExpression',
label: intl.formatMessage(

let options;
if (checkIsManualOptions(component)) {
options = component?.data?.values || [];
} else if (checkIsReferentielijstenOptions(component)) {
options = [
{
value: 'service',
label: intl.formatMessage(
{
description: 'Select dummy option for service',
defaultMessage: 'Options from service: <code>{service}</code>',
},
{
service: component.openForms.service,
}
),
},
{
value: 'code',
label: intl
.formatMessage(
{
description: 'Select dummy option for code',
defaultMessage: 'Options from code: <code>{code}</code>',
},
{
code: component.openForms.code,
}
)
.toString(),
},
];
} else if (checkIsVariableOptions(component)) {
options = [
{
value: 'itemsExpression',
label: intl
.formatMessage(
{
description: 'Select dummy option for itemsExpression',
defaultMessage: 'Options from expression: <code>{expression}</code>',
Expand All @@ -32,9 +68,12 @@ const Preview: React.FC<ComponentPreviewProps<SelectComponentSchema>> = ({compon
expression: JSON.stringify(component.openForms.itemsExpression),
code: chunks => <code>{chunks}</code>,
}
),
},
];
)
.toString(),
},
];
}

return (
<Select
name={key}
Expand Down

0 comments on commit d1b0564

Please sign in to comment.