G2GDataTransferExportForm.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. import React, {
  2. type JSX,
  3. useCallback,
  4. useEffect,
  5. useMemo,
  6. useState,
  7. } from 'react';
  8. import { useTranslation } from 'next-i18next';
  9. import { GrowiArchiveImportOption } from '~/models/admin/growi-archive-import-option';
  10. import { ImportOptionForPages } from '~/models/admin/import-option-for-pages';
  11. import { ImportOptionForRevisions } from '~/models/admin/import-option-for-revisions';
  12. import ImportCollectionConfigurationModal from './ImportData/GrowiArchive/ImportCollectionConfigurationModal';
  13. import ImportCollectionItem, {
  14. DEFAULT_MODE,
  15. MODE_RESTRICTED_COLLECTION,
  16. } from './ImportData/GrowiArchive/ImportCollectionItem';
  17. const GROUPS_PAGE = ['pages', 'revisions', 'tags', 'pagetagrelations'];
  18. const GROUPS_USER = [
  19. 'users',
  20. 'externalaccounts',
  21. 'usergroups',
  22. 'usergrouprelations',
  23. ];
  24. const GROUPS_CONFIG = ['configs', 'updateposts', 'globalnotificationsettings'];
  25. const ALL_GROUPED_COLLECTIONS =
  26. GROUPS_PAGE.concat(GROUPS_USER).concat(GROUPS_CONFIG);
  27. const IMPORT_OPTION_CLASS_MAPPING: Record<
  28. string,
  29. typeof GrowiArchiveImportOption
  30. > = {
  31. pages: ImportOptionForPages,
  32. revisions: ImportOptionForRevisions,
  33. };
  34. type Props = {
  35. allCollectionNames: string[];
  36. selectedCollections: Set<string>;
  37. updateSelectedCollections: (newSelectedCollections: Set<string>) => void;
  38. optionsMap: any;
  39. updateOptionsMap: (newOptionsMap: any) => void;
  40. };
  41. type ImportItemsProps = {
  42. collectionNames: string[];
  43. selectedCollections: Set<string>;
  44. optionsMap: Record<string, GrowiArchiveImportOption>;
  45. onToggleCollection: (collectionName: string, isChecked: boolean) => void;
  46. onOptionChange: (collectionName: string, data: any) => void;
  47. onConfigButtonClicked: (collectionName: string) => void;
  48. };
  49. const ImportItems = ({
  50. collectionNames,
  51. selectedCollections,
  52. optionsMap,
  53. onToggleCollection,
  54. onOptionChange,
  55. onConfigButtonClicked,
  56. }: ImportItemsProps): JSX.Element => {
  57. return (
  58. <div className="row">
  59. {collectionNames.map((collectionName) => {
  60. const isConfigButtonAvailable = Object.keys(
  61. IMPORT_OPTION_CLASS_MAPPING,
  62. ).includes(collectionName);
  63. if (optionsMap[collectionName] == null) {
  64. return null;
  65. }
  66. return (
  67. <div className="col-md-6 my-1" key={collectionName}>
  68. <ImportCollectionItem
  69. isImporting={false}
  70. isImported={false}
  71. insertedCount={0}
  72. modifiedCount={0}
  73. errorsCount={0}
  74. collectionName={collectionName}
  75. isSelected={selectedCollections.has(collectionName)}
  76. option={optionsMap[collectionName]}
  77. isConfigButtonAvailable={isConfigButtonAvailable}
  78. // events
  79. onChange={onToggleCollection}
  80. onOptionChange={onOptionChange}
  81. onConfigButtonClicked={onConfigButtonClicked}
  82. // TODO: show progress
  83. isHideProgress
  84. />
  85. </div>
  86. );
  87. })}
  88. </div>
  89. );
  90. };
  91. type WarnForGroupsProps = {
  92. errors: Error[];
  93. };
  94. const WarnForGroups = ({ errors }: WarnForGroupsProps): JSX.Element => {
  95. if (errors.length === 0) {
  96. return <></>;
  97. }
  98. return (
  99. <div className="alert alert-warning">
  100. <ul>
  101. {errors.map((error, index) => {
  102. return <li key={`${error.message}-${index}`}>{error.message}</li>;
  103. })}
  104. </ul>
  105. </div>
  106. );
  107. };
  108. type GroupImportItemsProps = {
  109. groupList: string[];
  110. groupName: string;
  111. errors: Error[];
  112. allCollectionNames: string[];
  113. selectedCollections: Set<string>;
  114. optionsMap: Record<string, GrowiArchiveImportOption>;
  115. onToggleCollection: (collectionName: string, isChecked: boolean) => void;
  116. onOptionChange: (collectionName: string, data: any) => void;
  117. onConfigButtonClicked: (collectionName: string) => void;
  118. };
  119. const GroupImportItems = ({
  120. groupList,
  121. groupName,
  122. errors,
  123. allCollectionNames,
  124. selectedCollections,
  125. optionsMap,
  126. onToggleCollection,
  127. onOptionChange,
  128. onConfigButtonClicked,
  129. }: GroupImportItemsProps): JSX.Element => {
  130. const collectionNames = groupList.filter((groupCollectionName) => {
  131. return allCollectionNames.includes(groupCollectionName);
  132. });
  133. if (collectionNames.length === 0) {
  134. return <></>;
  135. }
  136. return (
  137. <div className="mt-4">
  138. <legend>{groupName} Collections</legend>
  139. <ImportItems
  140. collectionNames={collectionNames}
  141. selectedCollections={selectedCollections}
  142. optionsMap={optionsMap}
  143. onToggleCollection={onToggleCollection}
  144. onOptionChange={onOptionChange}
  145. onConfigButtonClicked={onConfigButtonClicked}
  146. />
  147. <WarnForGroups errors={errors} />
  148. </div>
  149. );
  150. };
  151. type OtherImportItemsProps = {
  152. allCollectionNames: string[];
  153. selectedCollections: Set<string>;
  154. optionsMap: Record<string, GrowiArchiveImportOption>;
  155. onToggleCollection: (collectionName: string, isChecked: boolean) => void;
  156. onOptionChange: (collectionName: string, data: any) => void;
  157. onConfigButtonClicked: (collectionName: string) => void;
  158. };
  159. const OtherImportItems = ({
  160. allCollectionNames,
  161. selectedCollections,
  162. optionsMap,
  163. onToggleCollection,
  164. onOptionChange,
  165. onConfigButtonClicked,
  166. }: OtherImportItemsProps): JSX.Element => {
  167. const collectionNames = allCollectionNames.filter((collectionName) => {
  168. return !ALL_GROUPED_COLLECTIONS.includes(collectionName);
  169. });
  170. // TODO: エラー対応
  171. return (
  172. <GroupImportItems
  173. groupList={collectionNames}
  174. groupName="Other"
  175. errors={[]}
  176. allCollectionNames={allCollectionNames}
  177. selectedCollections={selectedCollections}
  178. optionsMap={optionsMap}
  179. onToggleCollection={onToggleCollection}
  180. onOptionChange={onOptionChange}
  181. onConfigButtonClicked={onConfigButtonClicked}
  182. />
  183. );
  184. };
  185. const G2GDataTransferExportForm = (props: Props): JSX.Element => {
  186. const { t } = useTranslation('admin');
  187. const {
  188. allCollectionNames,
  189. selectedCollections,
  190. updateSelectedCollections,
  191. optionsMap,
  192. updateOptionsMap,
  193. } = props;
  194. const [isConfigurationModalOpen, setConfigurationModalOpen] = useState(false);
  195. const [collectionNameForConfiguration, setCollectionNameForConfiguration] =
  196. useState<any>();
  197. const checkAll = useCallback(() => {
  198. updateSelectedCollections(new Set(allCollectionNames));
  199. }, [allCollectionNames, updateSelectedCollections]);
  200. const uncheckAll = useCallback(() => {
  201. updateSelectedCollections(new Set());
  202. }, [updateSelectedCollections]);
  203. const updateOption = useCallback(
  204. (collectionName, data) => {
  205. const options = optionsMap[collectionName];
  206. // merge
  207. Object.assign(options, data);
  208. const updatedOptionsMap = {};
  209. updatedOptionsMap[collectionName] = options;
  210. updateOptionsMap((prev) => {
  211. return { ...prev, updatedOptionsMap };
  212. });
  213. },
  214. [optionsMap, updateOptionsMap],
  215. );
  216. const toggleCheckbox = useCallback(
  217. (collectionName, bool) => {
  218. const collections = new Set(selectedCollections);
  219. if (bool) {
  220. collections.add(collectionName);
  221. } else {
  222. collections.delete(collectionName);
  223. }
  224. updateSelectedCollections(collections);
  225. // TODO: validation
  226. // this.validate();
  227. },
  228. [selectedCollections, updateSelectedCollections],
  229. );
  230. const openConfigurationModal = useCallback((collectionName) => {
  231. setConfigurationModalOpen(true);
  232. setCollectionNameForConfiguration(collectionName);
  233. }, []);
  234. const configurationModal = useMemo(() => {
  235. if (collectionNameForConfiguration == null) {
  236. return <></>;
  237. }
  238. return (
  239. <ImportCollectionConfigurationModal
  240. isOpen={isConfigurationModalOpen}
  241. onClose={() => setConfigurationModalOpen(false)}
  242. onOptionChange={updateOption}
  243. collectionName={collectionNameForConfiguration}
  244. option={optionsMap[collectionNameForConfiguration]}
  245. />
  246. );
  247. }, [
  248. collectionNameForConfiguration,
  249. isConfigurationModalOpen,
  250. optionsMap,
  251. updateOption,
  252. ]);
  253. const setInitialOptionsMap = useCallback(() => {
  254. const initialOptionsMap = {};
  255. allCollectionNames.forEach((collectionName) => {
  256. const initialMode =
  257. MODE_RESTRICTED_COLLECTION[collectionName] != null
  258. ? MODE_RESTRICTED_COLLECTION[collectionName][0]
  259. : DEFAULT_MODE;
  260. const ImportOption =
  261. IMPORT_OPTION_CLASS_MAPPING[collectionName] || GrowiArchiveImportOption;
  262. initialOptionsMap[collectionName] = new ImportOption(
  263. collectionName,
  264. initialMode,
  265. );
  266. });
  267. updateOptionsMap(initialOptionsMap);
  268. }, [allCollectionNames, updateOptionsMap]);
  269. useEffect(() => {
  270. setInitialOptionsMap();
  271. }, [setInitialOptionsMap]);
  272. return (
  273. <>
  274. <form className="mt-3 row row-cols-lg-auto g-3 align-items-center">
  275. <div className="col-12">
  276. <button
  277. type="button"
  278. className="btn btn-sm btn-outline-secondary me-2"
  279. onClick={checkAll}
  280. >
  281. <span className="material-symbols-outlined">check_box</span>,{' '}
  282. {t('admin:export_management.check_all')}
  283. </button>
  284. </div>
  285. <div className="col-12">
  286. <button
  287. type="button"
  288. className="btn btn-sm btn-outline-secondary me-2"
  289. onClick={uncheckAll}
  290. >
  291. <span className="material-symbols-outlined">
  292. check_box_outline_blank
  293. </span>{' '}
  294. {t('admin:export_management.uncheck_all')}
  295. </button>
  296. </div>
  297. </form>
  298. <div className="card custom-card small my-4">
  299. <ul>
  300. <li>
  301. {t(
  302. 'admin:importer_management.growi_settings.description_of_import_mode.about',
  303. )}
  304. </li>
  305. <ul>
  306. <li>
  307. {t(
  308. 'admin:importer_management.growi_settings.description_of_import_mode.insert',
  309. )}
  310. </li>
  311. <li>
  312. {t(
  313. 'admin:importer_management.growi_settings.description_of_import_mode.upsert',
  314. )}
  315. </li>
  316. <li>
  317. {t(
  318. 'admin:importer_management.growi_settings.description_of_import_mode.flash_and_insert',
  319. )}
  320. </li>
  321. </ul>
  322. </ul>
  323. </div>
  324. {/* TODO: エラー追加 */}
  325. <GroupImportItems
  326. groupList={GROUPS_PAGE}
  327. groupName="Page"
  328. errors={[]}
  329. allCollectionNames={allCollectionNames}
  330. selectedCollections={selectedCollections}
  331. optionsMap={optionsMap}
  332. onToggleCollection={toggleCheckbox}
  333. onOptionChange={updateOption}
  334. onConfigButtonClicked={openConfigurationModal}
  335. />
  336. <GroupImportItems
  337. groupList={GROUPS_USER}
  338. groupName="User"
  339. errors={[]}
  340. allCollectionNames={allCollectionNames}
  341. selectedCollections={selectedCollections}
  342. optionsMap={optionsMap}
  343. onToggleCollection={toggleCheckbox}
  344. onOptionChange={updateOption}
  345. onConfigButtonClicked={openConfigurationModal}
  346. />
  347. <GroupImportItems
  348. groupList={GROUPS_CONFIG}
  349. groupName="Config"
  350. errors={[]}
  351. allCollectionNames={allCollectionNames}
  352. selectedCollections={selectedCollections}
  353. optionsMap={optionsMap}
  354. onToggleCollection={toggleCheckbox}
  355. onOptionChange={updateOption}
  356. onConfigButtonClicked={openConfigurationModal}
  357. />
  358. <OtherImportItems
  359. allCollectionNames={allCollectionNames}
  360. selectedCollections={selectedCollections}
  361. optionsMap={optionsMap}
  362. onToggleCollection={toggleCheckbox}
  363. onOptionChange={updateOption}
  364. onConfigButtonClicked={openConfigurationModal}
  365. />
  366. {configurationModal}
  367. </>
  368. );
  369. };
  370. export default G2GDataTransferExportForm;