Files
kintone-plugin-filelookup/src/config/Settings.tsx

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;