301 lines
10 KiB
TypeScript
301 lines
10 KiB
TypeScript
import React from 'react';
|
|
|
|
import { produce } from 'immer';
|
|
import moize from 'moize';
|
|
import { useTranslation } from 'react-i18next';
|
|
import invariant from 'tiny-invariant';
|
|
import {
|
|
loadPluginConfigLookup,
|
|
PluginConfigLookup,
|
|
PluginConfigLookupItem,
|
|
savePluginConfigLookup,
|
|
} from '../common/config';
|
|
import { filterConfigByPluginContext, getPluginContext, PluginContext } from '../common/context';
|
|
import KintonePluginAlert from '../common/ui/KintonePluginAlert';
|
|
import KintonePluginButton from '../common/ui/KintonePluginButton';
|
|
import KintonePluginDesc from '../common/ui/KintonePluginDesc';
|
|
import KintonePluginLabel from '../common/ui/KintonePluginLabel';
|
|
import KintonePluginRequire from '../common/ui/KintonePluginRequire';
|
|
import KintonePluginRow from '../common/ui/KintonePluginRow';
|
|
import KintonePluginSelect, { KintonePluginSelectOptionData } from '../common/ui/KintonePluginSelect';
|
|
import KintonePluginTable from '../common/ui/KintonePluginTable';
|
|
import KintonePluginTableTd from '../common/ui/KintonePluginTableTd';
|
|
import KintonePluginTableTh from '../common/ui/KintonePluginTableTh';
|
|
import KintonePluginTitle from '../common/ui/KintonePluginTitle';
|
|
|
|
import styles from './Settings.module.css';
|
|
|
|
const hasDuplicate = <T,>(items: T[], func: (a: T, b: T) => boolean = (a, b) => a === b): boolean => {
|
|
return items.find((item, idx) => items.some((item2, idx2) => func(item, item2) && idx !== idx2)) != null;
|
|
};
|
|
|
|
const hasDuplicateDestAttachmentFields = (rows: PluginConfigLookup): boolean => {
|
|
return hasDuplicate<PluginConfigLookupItem>(rows, (a, b) => a.destAttachmentFieldCode === b.destAttachmentFieldCode);
|
|
};
|
|
|
|
interface FieldSelectTableRowProps {
|
|
context: PluginContext;
|
|
row: PluginConfigLookupItem;
|
|
onUpdate: (item: PluginConfigLookupItem) => void;
|
|
onClickAddRow: () => void;
|
|
onClickRemoveRow: () => void;
|
|
}
|
|
|
|
const FieldSelectTableRow: React.FC<FieldSelectTableRowProps> = (props) => {
|
|
const { context, row, onUpdate, onClickAddRow, onClickRemoveRow } = props;
|
|
const defaultOption: KintonePluginSelectOptionData = React.useMemo(() => {
|
|
return { value: '', label: '--', disabled: true };
|
|
}, []);
|
|
const lookupOptions: KintonePluginSelectOptionData[] = React.useMemo(() => {
|
|
return [
|
|
defaultOption,
|
|
...context.lookupFields.map((property) => ({
|
|
value: property.code,
|
|
label: `${property.label} (${property.code})`,
|
|
})),
|
|
];
|
|
}, [context.lookupFields, defaultOption]);
|
|
|
|
const srcAppId = context.lookupFields.find((field) => field.code === row.lookupFieldCode)?.lookup.relatedApp.app;
|
|
const srcFileOptions: KintonePluginSelectOptionData[] = React.useMemo(() => {
|
|
return [
|
|
defaultOption,
|
|
...(srcAppId != null
|
|
? context.attachmentFields[srcAppId].map((property) => ({
|
|
value: property.code,
|
|
label: `${property.label} (${property.code})`,
|
|
}))
|
|
: []),
|
|
];
|
|
}, [srcAppId, context.attachmentFields, defaultOption]);
|
|
|
|
const destFileOptions: KintonePluginSelectOptionData[] = React.useMemo(() => {
|
|
return [
|
|
defaultOption,
|
|
...(srcAppId != null
|
|
? context.attachmentFields[context.appId].map((property) => ({
|
|
value: property.code,
|
|
label: `${property.label} (${property.code})`,
|
|
}))
|
|
: []),
|
|
];
|
|
}, [context.appId, context.attachmentFields, defaultOption, srcAppId]);
|
|
|
|
const handleOnChangeLookup = (code: string) => {
|
|
const draft = {
|
|
lookupFieldCode: code,
|
|
srcAttachmentFieldCode: '',
|
|
destAttachmentFieldCode: '',
|
|
};
|
|
onUpdate(draft);
|
|
};
|
|
const handleOnChangeSrcFile = (code: string) => {
|
|
const draft = {
|
|
lookupFieldCode: row.lookupFieldCode,
|
|
srcAttachmentFieldCode: code,
|
|
destAttachmentFieldCode: '',
|
|
};
|
|
onUpdate(draft);
|
|
};
|
|
|
|
const handleOnChangeDestFile = (code: string) => {
|
|
const draft = {
|
|
lookupFieldCode: row.lookupFieldCode,
|
|
srcAttachmentFieldCode: row.srcAttachmentFieldCode,
|
|
destAttachmentFieldCode: code,
|
|
};
|
|
onUpdate(draft);
|
|
};
|
|
|
|
const handleOnClickAddRow: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
|
e.preventDefault();
|
|
onClickAddRow();
|
|
};
|
|
|
|
const handleOnClickRemoveRow: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
|
e.preventDefault();
|
|
onClickRemoveRow();
|
|
};
|
|
|
|
return (
|
|
<tr>
|
|
<KintonePluginTableTd variant="control">
|
|
<KintonePluginSelect value={row.lookupFieldCode} onChange={handleOnChangeLookup} options={lookupOptions} />
|
|
</KintonePluginTableTd>
|
|
<KintonePluginTableTd variant="control">
|
|
<KintonePluginSelect
|
|
value={row.srcAttachmentFieldCode}
|
|
onChange={handleOnChangeSrcFile}
|
|
options={srcFileOptions}
|
|
/>
|
|
</KintonePluginTableTd>
|
|
<KintonePluginTableTd variant="control">
|
|
<KintonePluginSelect
|
|
value={row.destAttachmentFieldCode}
|
|
onChange={handleOnChangeDestFile}
|
|
options={destFileOptions}
|
|
/>
|
|
</KintonePluginTableTd>
|
|
<KintonePluginTableTd variant="operation">
|
|
<KintonePluginButton variant="add-row-image" onClick={handleOnClickAddRow} />
|
|
<KintonePluginButton variant="remove-row-image" onClick={handleOnClickRemoveRow} />
|
|
</KintonePluginTableTd>
|
|
</tr>
|
|
);
|
|
};
|
|
|
|
const cachedPluginContext = moize.promise(getPluginContext);
|
|
|
|
interface SettingsProps {
|
|
pluginId: string;
|
|
}
|
|
|
|
const Settings: React.FC<SettingsProps> = (props) => {
|
|
const { pluginId } = props;
|
|
const { t } = useTranslation();
|
|
const appId = kintone.app.getId();
|
|
invariant(appId, 'The app ID is not available. Please ensure you are on a Kintone app page.');
|
|
const context = React.use(cachedPluginContext(appId, true));
|
|
|
|
const [mappings, setMappings] = React.useState<PluginConfigLookup>(
|
|
() =>
|
|
filterConfigByPluginContext(loadPluginConfigLookup(pluginId), context) ?? [
|
|
{ lookupFieldCode: '', srcAttachmentFieldCode: '', destAttachmentFieldCode: '' },
|
|
],
|
|
);
|
|
|
|
const [error, setError] = React.useState<string>('');
|
|
|
|
const handleOnUpdateFieldSelect = (idx: number, item: PluginConfigLookupItem) => {
|
|
setError('');
|
|
setMappings(
|
|
produce((draft) => {
|
|
draft[idx] = item;
|
|
}),
|
|
);
|
|
};
|
|
|
|
const handleOnAddFieldSelect = (idx: number) => {
|
|
setError('');
|
|
if (mappings.length < context.attachmentFields[context.appId].length) {
|
|
setMappings(
|
|
produce((draft) => {
|
|
draft.splice(idx + 1, 0, {
|
|
lookupFieldCode: '',
|
|
srcAttachmentFieldCode: '',
|
|
destAttachmentFieldCode: '',
|
|
});
|
|
}),
|
|
);
|
|
}
|
|
};
|
|
|
|
const handleOnRemoveFieldSelect = (idx: number) => {
|
|
setError('');
|
|
if (mappings.length === 1) {
|
|
setMappings(
|
|
produce((draft) => {
|
|
draft[idx] = {
|
|
lookupFieldCode: '',
|
|
srcAttachmentFieldCode: '',
|
|
destAttachmentFieldCode: '',
|
|
};
|
|
}),
|
|
);
|
|
} else if (mappings.length > 1) {
|
|
setMappings(
|
|
produce((draft) => {
|
|
draft.splice(idx, 1);
|
|
}),
|
|
);
|
|
}
|
|
};
|
|
|
|
const handleOnSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
|
e.preventDefault();
|
|
if (mappings.find((item) => item.destAttachmentFieldCode === '') != null) {
|
|
setError(t('settings.lookup.errors.incomplete-field-mapping-found'));
|
|
} else if (hasDuplicateDestAttachmentFields(mappings)) {
|
|
setError(t('settings.lookup.errors.same-destination-attachment-field-found'));
|
|
} else {
|
|
setError('');
|
|
savePluginConfigLookup(mappings, () => {
|
|
alert(t('on-saved'));
|
|
window.location.href = `../../flow?app=${appId}`;
|
|
});
|
|
}
|
|
};
|
|
|
|
const handleOnClickCancel = () => {
|
|
setError('');
|
|
window.location.href = `../../${appId}/plugin/`;
|
|
};
|
|
|
|
return (
|
|
<section className="settings">
|
|
<KintonePluginLabel>{t('title')}</KintonePluginLabel>
|
|
{error !== '' && (
|
|
<KintonePluginRow>
|
|
<KintonePluginAlert>{error}</KintonePluginAlert>
|
|
</KintonePluginRow>
|
|
)}
|
|
<form onSubmit={handleOnSubmit}>
|
|
<KintonePluginRow>
|
|
<KintonePluginTitle>
|
|
{t('settings.lookup.title')}
|
|
<KintonePluginRequire>*</KintonePluginRequire>
|
|
</KintonePluginTitle>
|
|
<KintonePluginDesc>{t('settings.lookup.description')}</KintonePluginDesc>
|
|
{context.lookupFields.length === 0 ? (
|
|
<KintonePluginAlert>{t('settings.lookup.errors.no-lookup-field-found')}</KintonePluginAlert>
|
|
) : (
|
|
<KintonePluginTable>
|
|
<thead>
|
|
<tr>
|
|
<KintonePluginTableTh variant="title">
|
|
{t('settings.lookup.messages.lookup-field')}
|
|
</KintonePluginTableTh>
|
|
<KintonePluginTableTh variant="title">
|
|
{t('settings.lookup.messages.source-attachment-field')}
|
|
</KintonePluginTableTh>
|
|
<KintonePluginTableTh variant="title">
|
|
{t('settings.lookup.messages.destination-attachment-field')}
|
|
</KintonePluginTableTh>
|
|
<KintonePluginTableTh variant="blankspace" />
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{mappings.map((row, idx) => (
|
|
<FieldSelectTableRow
|
|
key={`${row.lookupFieldCode}-${row.srcAttachmentFieldCode}-${row.destAttachmentFieldCode}-${idx}`}
|
|
context={context}
|
|
row={row}
|
|
onUpdate={(draft) => {
|
|
handleOnUpdateFieldSelect(idx, draft);
|
|
}}
|
|
onClickAddRow={() => {
|
|
handleOnAddFieldSelect(idx);
|
|
}}
|
|
onClickRemoveRow={() => {
|
|
handleOnRemoveFieldSelect(idx);
|
|
}}
|
|
/>
|
|
))}
|
|
</tbody>
|
|
</KintonePluginTable>
|
|
)}
|
|
</KintonePluginRow>
|
|
<KintonePluginRow className={styles.buttons}>
|
|
<KintonePluginButton variant="dialog-cancel" type="button" onClick={handleOnClickCancel}>
|
|
{t('buttons.cancel')}
|
|
</KintonePluginButton>
|
|
<KintonePluginButton variant="dialog-ok" type="submit">
|
|
{t('buttons.save')}
|
|
</KintonePluginButton>
|
|
</KintonePluginRow>
|
|
</form>
|
|
</section>
|
|
);
|
|
};
|
|
export default Settings;
|