Container.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import { makeStyles, Theme } from '@material-ui/core';
  2. import { FC, ReactElement, useContext, useMemo, useState } from 'react';
  3. import { useTranslation } from 'react-i18next';
  4. import DialogTemplate from '../customDialog/DialogTemplate';
  5. import icons from '../icons/Icons';
  6. import { rootContext } from '../../context/Root';
  7. import InsertImport from './Import';
  8. import InsertPreview from './Preview';
  9. import InsertStatus from './Status';
  10. import {
  11. InsertContentProps,
  12. InsertStatusEnum,
  13. InsertStepperEnum,
  14. } from './Types';
  15. import { Option } from '../customSelector/Types';
  16. import { parse } from 'papaparse';
  17. const getStyles = makeStyles((theme: Theme) => ({
  18. icon: {
  19. fontSize: '16px',
  20. },
  21. }));
  22. /**
  23. * this component contains processes during insert
  24. * all datas and methods passed in as props, no interactions with server done in it
  25. */
  26. const InsertContainer: FC<InsertContentProps> = ({
  27. collections,
  28. selectedCollection,
  29. partitions,
  30. selectedPartition,
  31. schema,
  32. handleInsert,
  33. }) => {
  34. const classes = getStyles();
  35. // props children component needed:
  36. const collectionOptions: Option[] = collections.map(c => ({
  37. label: c._name,
  38. value: c._name,
  39. }));
  40. const partitionOptions: Option[] = partitions.map(p => ({
  41. label: p._name,
  42. value: p._name,
  43. }));
  44. const schemaOptions: Option[] = schema.map(s => ({
  45. label: s._fieldName,
  46. value: s._fieldId,
  47. }));
  48. const { t: insertTrans } = useTranslation('insert');
  49. const { t: btnTrans } = useTranslation('btn');
  50. const { handleCloseDialog, openSnackBar } = useContext(rootContext);
  51. const [activeStep, setActiveStep] = useState<InsertStepperEnum>(
  52. InsertStepperEnum.import
  53. );
  54. const [insertStatus, setInsertStauts] = useState<InsertStatusEnum>(
  55. InsertStatusEnum.init
  56. );
  57. // const [nextDisabled, setNextDisabled] = useState<boolean>(false);
  58. // selected collection name
  59. const [collectionValue, setCollectionValue] =
  60. useState<string>(selectedCollection);
  61. // selected partition name
  62. const [partitionValue, setPartitionValue] =
  63. useState<string>(selectedPartition);
  64. // use contain field names yes as default
  65. const [isContainFieldNames, setIsContainFieldNames] = useState<number>(1);
  66. // uploaded file name
  67. const [fileName, setFileName] = useState<string>('');
  68. // uploaded csv data (type: string)
  69. const [csvData, setCsvData] = useState<any[]>([]);
  70. const BackIcon = icons.back;
  71. // modal actions part, buttons label text or component
  72. const { confirm, cancel } = useMemo(() => {
  73. const labelMap: {
  74. [key in InsertStepperEnum]: {
  75. confirm: string;
  76. cancel: string | ReactElement;
  77. };
  78. } = {
  79. [InsertStepperEnum.import]: {
  80. confirm: btnTrans('next'),
  81. cancel: btnTrans('cancel'),
  82. },
  83. [InsertStepperEnum.preview]: {
  84. confirm: btnTrans('insert'),
  85. cancel: (
  86. <>
  87. <BackIcon classes={{ root: classes.icon }} />
  88. {btnTrans('previous')}
  89. </>
  90. ),
  91. },
  92. [InsertStepperEnum.status]: {
  93. confirm: btnTrans('done'),
  94. cancel: '',
  95. },
  96. };
  97. return labelMap[activeStep];
  98. }, [activeStep, btnTrans, BackIcon, classes.icon]);
  99. const { showActions, showCancel } = useMemo(() => {
  100. return {
  101. showActions: insertStatus !== InsertStatusEnum.loading,
  102. showCancel: insertStatus === InsertStatusEnum.init,
  103. };
  104. }, [insertStatus]);
  105. const checkUploadFileValidation = (fieldNamesLength: number): boolean => {
  106. return schemaOptions.length === fieldNamesLength;
  107. };
  108. const previewData = useMemo(() => {
  109. // we only show top 4 results of uploaded csv data
  110. const end = isContainFieldNames ? 5 : 4;
  111. return csvData.slice(0, end);
  112. }, [csvData, isContainFieldNames]);
  113. const handleUploadedData = (csv: string) => {
  114. const { data } = parse(csv);
  115. const uploadFieldNamesLength = (data as string[])[0].length;
  116. const validation = checkUploadFileValidation(uploadFieldNamesLength);
  117. if (!validation) {
  118. // open snackbar
  119. openSnackBar(insertTrans('uploadFieldNamesLenWarning'), 'error');
  120. // reset filename
  121. setFileName('');
  122. return;
  123. }
  124. setCsvData(data);
  125. };
  126. const handleInsertData = async () => {
  127. setInsertStauts(InsertStatusEnum.loading);
  128. const res = await handleInsert();
  129. const status = res ? InsertStatusEnum.success : InsertStatusEnum.error;
  130. setInsertStauts(status);
  131. };
  132. const handleNext = () => {
  133. switch (activeStep) {
  134. case InsertStepperEnum.import:
  135. setActiveStep(activeStep => activeStep + 1);
  136. break;
  137. case InsertStepperEnum.preview:
  138. setActiveStep(activeStep => activeStep + 1);
  139. handleInsertData();
  140. break;
  141. // default represent InsertStepperEnum.status
  142. default:
  143. handleCloseDialog();
  144. break;
  145. }
  146. };
  147. const handleBack = () => {
  148. switch (activeStep) {
  149. case InsertStepperEnum.import:
  150. handleCloseDialog();
  151. break;
  152. case InsertStepperEnum.preview:
  153. setActiveStep(activeStep => activeStep - 1);
  154. break;
  155. // default represent InsertStepperEnum.status
  156. // status don't have cancel button
  157. default:
  158. break;
  159. }
  160. };
  161. const generateContent = (activeStep: InsertStepperEnum) => {
  162. switch (activeStep) {
  163. case InsertStepperEnum.import:
  164. return (
  165. <InsertImport
  166. collectionOptions={collectionOptions}
  167. partitionOptions={partitionOptions}
  168. selectedCollection={collectionValue}
  169. selectedPartition={partitionValue}
  170. handleCollectionChange={setCollectionValue}
  171. handlePartitionChange={setPartitionValue}
  172. handleUploadedData={handleUploadedData}
  173. fileName={fileName}
  174. setFileName={setFileName}
  175. />
  176. );
  177. case InsertStepperEnum.preview:
  178. return (
  179. <InsertPreview
  180. schemaOptions={schemaOptions}
  181. data={previewData}
  182. isContainFieldNames={isContainFieldNames}
  183. handleIsContainedChange={setIsContainFieldNames}
  184. />
  185. );
  186. // default represents InsertStepperEnum.status
  187. default:
  188. return <InsertStatus status={insertStatus} />;
  189. }
  190. };
  191. return (
  192. <DialogTemplate
  193. title={insertTrans('import')}
  194. handleClose={handleCloseDialog}
  195. confirmLabel={confirm}
  196. cancelLabel={cancel}
  197. handleCancel={handleBack}
  198. handleConfirm={handleNext}
  199. confirmDisabled={false}
  200. showActions={showActions}
  201. showCancel={showCancel}
  202. // don't show close icon when insert not finish
  203. showCloseIcon={insertStatus !== InsertStatusEnum.loading}
  204. >
  205. {generateContent(activeStep)}
  206. </DialogTemplate>
  207. );
  208. };
  209. export default InsertContainer;