diff --git a/forms-flow-components/src/components/CustomComponents/ImportModal.tsx b/forms-flow-components/src/components/CustomComponents/ImportModal.tsx index b0d0e730..bbe333cd 100644 --- a/forms-flow-components/src/components/CustomComponents/ImportModal.tsx +++ b/forms-flow-components/src/components/CustomComponents/ImportModal.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react"; import Modal from "react-bootstrap/Modal"; import ProgressBar from "react-bootstrap/ProgressBar"; import Dropdown from "react-bootstrap/Dropdown"; -import { Translation } from "react-i18next"; +import { useTranslation } from "react-i18next"; import { CloseIcon, UploadIcon, @@ -14,6 +14,7 @@ import { import { CustomButton } from "../CustomComponents/Button"; // Define the types for props +const { t } = useTranslation(); interface FileItem { form?: { majorVersion: number; @@ -32,7 +33,7 @@ interface ProcessVersion { } interface ImportModalProps { - importModal: boolean; + showModal: boolean; onClose: () => void; uploadActionType: { IMPORT: string; @@ -42,7 +43,12 @@ interface ImportModalProps { importLoader: boolean; formName: string; description: string; - handleImport: (file: File, uploadActionType: string, layoutVersion: string | null, flowVersion: string | null) => void + handleImport: ( + file: File, + uploadActionType: string, + layoutVersion: string | null, + flowVersion: string | null + ) => void; fileItems: FileItem | null; fileType: string; primaryButtonText: string; @@ -50,282 +56,435 @@ interface ImportModalProps { processVersion: ProcessVersion | null; } -export const ImportModal: React.FC = React.memo(({ - importModal, - onClose, - uploadActionType, - importError, - importLoader, - formName, - description, - handleImport, - fileItems, - fileType, - primaryButtonText, - headerText, - processVersion -}) => { - const computedStyle = getComputedStyle(document.documentElement); - const redColor = computedStyle.getPropertyValue("--ff-red-000"); - const [selectedFile, setSelectedFile] = useState(null); - const [uploadProgress, setUploadProgress] = useState(0); - const [selectedLayoutVersion, setSelectedLayoutOption] = useState<{ value: any; label: string } | null>(null); - const [selectedFlowVersion, setSelectedFlowOption] = useState<{ value: any; label: string } | null>(null); - const [showFileItems, setShowFileItems] = useState(false); - const [inprogress, setInprogress] = useState(true); - - const layoutOptions = [ - { value: true, label: 'Skip, do not import' }, - { value: 'major', label: `import as version ${fileItems?.form?.majorVersion + 1}.0 (only impacts new submissions)` }, - { value: 'minor', label: `import as version ${fileItems?.form?.majorVersion}.${fileItems?.form?.minorVersion} (impacts previous and new submissions)` } - ]; - - const flowOptions = [ - { value: true, label: 'Skip, do not import' }, - { value: 'major', label: `import as version ${fileItems?.workflow?.majorVersion ?? 1}.${fileItems?.workflow?.minorVersion ?? 0} (only impacts new submissions)` } - ]; - - const handleLayoutChange = (option: { value: any; label: string }) => { - setSelectedLayoutOption(option); - }; +export const ImportModal: React.FC = React.memo( + ({ + showModal, + onClose, + uploadActionType, + importError, + importLoader, + formName, + description, + handleImport, + fileItems, + fileType, + primaryButtonText, + headerText, + processVersion, + }) => { + const computedStyle = getComputedStyle(document.documentElement); + const redColor = computedStyle.getPropertyValue("--ff-red-000"); + const [selectedFile, setSelectedFile] = useState(null); + const [uploadProgress, setUploadProgress] = useState(0); + const [selectedLayoutVersion, setSelectedLayoutOption] = useState<{ + value: any; + label: string; + } | null>(null); + const [selectedFlowVersion, setSelectedFlowOption] = useState<{ + value: any; + label: string; + } | null>(null); + const [showFileItems, setShowFileItems] = useState(false); + const [inprogress, setInprogress] = useState(true); - const handFlowChange = (option: { value: any; label: string }) => { - setSelectedFlowOption(option); - }; + const layoutOptions = [ + { value: true, label: "Skip, do not import" }, + { + value: "major", + label: `import as version ${ + fileItems?.form?.majorVersion + 1 + }.0 (only impacts new submissions)`, + }, + { + value: "minor", + label: `import as version ${fileItems?.form?.majorVersion}.${fileItems?.form?.minorVersion} (impacts previous and new submissions)`, + }, + ]; - const onUpload = (evt: React.ChangeEvent) => { - const file = evt.target.files ? evt.target.files[0] : null; - setSelectedFile(file); - }; + const flowOptions = [ + { value: true, label: "Skip, do not import" }, + { + value: "major", + label: `import as version ${fileItems?.workflow?.majorVersion ?? 1}.${ + fileItems?.workflow?.minorVersion ?? 0 + } (only impacts new submissions)`, + }, + ]; - const resetState = () => { - setSelectedFile(null); - setUploadProgress(0); - }; + const handleLayoutChange = (option: { value: any; label: string }) => { + setSelectedLayoutOption(option); + }; - const closeModal = () => { - setSelectedFile(null); - setUploadProgress(0); - setSelectedLayoutOption(null); - setSelectedFlowOption(null); - setShowFileItems(false); - onClose(); - }; + const handFlowChange = (option: { value: any; label: string }) => { + setSelectedFlowOption(option); + }; - const onImport = () => { - if (selectedFile) { - handleImport(selectedFile, uploadActionType.IMPORT, - selectedLayoutVersion?.value, selectedFlowVersion?.value); - } - }; + const onUpload = (evt: React.ChangeEvent) => { + const file = evt.target.files ? evt.target.files[0] : null; + setSelectedFile(file); + }; - useEffect(() => { - if (fileItems && !importError && Object.values(fileItems).some(item => - item?.majorVersion != null || item?.minorVersion != null)) { - setShowFileItems(true); - } else if (processVersion?.majorVersion != null || processVersion?.minorVersion != null) { - setShowFileItems(true); - } else { + const resetState = () => { + setSelectedFile(null); + setUploadProgress(0); + }; + + const closeModal = () => { + setSelectedFile(null); + setUploadProgress(0); + setSelectedLayoutOption(null); + setSelectedFlowOption(null); setShowFileItems(false); - } - }, [importError, fileItems]); - - useEffect(() => { - if (!importModal) { - closeModal(); - } - }, [importModal]); - - useEffect(() => { - let isMounted = true; - - if (selectedFile) { - handleImport( - selectedFile, - uploadActionType.VALIDATE, - selectedLayoutVersion?.value ?? null, - selectedFlowVersion?.value ?? null + onClose(); + }; + + const onImport = () => { + if (selectedFile) { + handleImport( + selectedFile, + uploadActionType.IMPORT, + selectedLayoutVersion?.value, + selectedFlowVersion?.value + ); + } + }; + + useEffect(() => { + if ( + fileItems && + !importError && + Object.values(fileItems).some( + (item) => item?.majorVersion != null || item?.minorVersion != null + ) + ) { + setShowFileItems(true); + } else if ( + processVersion?.majorVersion != null || + processVersion?.minorVersion != null + ) { + setShowFileItems(true); + } else { + setShowFileItems(false); + } + }, [importError, fileItems]); + + useEffect(() => { + if (!showModal) { + closeModal(); + } + }, [showModal]); + + useEffect(() => { + let isMounted = true; + + if (selectedFile) { + handleImport( + selectedFile, + uploadActionType.VALIDATE, + selectedLayoutVersion?.value ?? null, + selectedFlowVersion?.value ?? null + ); + + let start: number | null = null; + const duration = 2000; + + const animateProgress = (timestamp: number) => { + if (!start) start = timestamp; + const progress = Math.min( + ((timestamp - start) / duration) * 100, + 100 + ); + + if (isMounted) { + setUploadProgress(progress); + setInprogress(progress < 100); + } + + if (progress < 100) { + requestAnimationFrame(animateProgress); + } + }; + + const animation = requestAnimationFrame(animateProgress); + + return () => { + isMounted = false; + cancelAnimationFrame(animation); + }; + } + }, [selectedFile]); + + const renderUploadDetails = () => { + return ( +
+
+

{selectedFile?.name}

+ + {renderUploadStatusText()} + + {renderUploadStatusIcon()} +
+
+ {formName} +
+ {!importError && description && ( +
{description}
+ )} + {renderImportError()} +
+ ); + }; + + // Function to determine the upload status class based on conditions + const getUploadStatusClass = () => { + return !importLoader && !importError && !inprogress + ? "upload-status-success" + : !importLoader && importError && !inprogress + ? "upload-status-error" + : inprogress + ? "upload-status-progress" + : ""; + }; + + // Function to render the status text based on the upload condition + const renderUploadStatusText = () => { + if (!importLoader && !importError && !inprogress) { + return t("Upload Successful"); + } + if (!importLoader && importError && !inprogress) { + return t("Upload Failed"); + } + if (inprogress) { + return t("Import in progress"); + } + return null; + }; + + // Function to render the correct status icon based on upload progress + const renderUploadStatusIcon = () => { + if (!importLoader && importError) { + return ; + } + if (!importLoader && !inprogress) { + return ; + } + return null; + }; + + // Function to render import errors + const renderImportError = () => { + return ( + importError && ( + {importError} + ) + ); + }; + + // Function to render the import file items and version options + const renderFileItems = () => { + if (showFileItems && !importError) { + return ( +
+ {renderImportNote()} + {renderFileItemDetails()} + {renderLayoutOptions()} + {renderFlowOptions()} +
+ ); + } + return null; + }; + + // Function to render import note when file items are shown + const renderImportNote = () => { + return ( +
+
+ + + {t("Import will create a new version.")} + +
+
); + }; - let start: number | null = null; - const duration = 2000; - - const animateProgress = (timestamp: number) => { - if (!start) start = timestamp; - const progress = Math.min(((timestamp - start) / duration) * 100, 100); - - if (isMounted) { - setUploadProgress(progress); - setInprogress(progress < 100); - } - - if (progress < 100) { - requestAnimationFrame(animateProgress); - } - }; - - const animation = requestAnimationFrame(animateProgress); - - return () => { - isMounted = false; - cancelAnimationFrame(animation); - }; - } - }, [selectedFile]); - - return ( - - - - {(t) => t(headerText)} - -
- { resetState(); closeModal(); }} /> + // Function to render the file item details (e.g. type and import version) + const renderFileItemDetails = () => { + return ( +
+
{t("Type")}
+
{t("Import")}
- - - {selectedFile ? ( - <> - -
-
-

{selectedFile.name}

- - {!importLoader && !importError && !inprogress ? ( - {(t) => t("Upload Successful")} - ) : !importLoader && importError && !inprogress ? ( - {(t) => t("Upload Failed")} - ) : inprogress ? ( - {(t) => t("Import in progress")} - ) : null} - - {!importLoader && importError ? - : !importLoader && !inprogress ? : null} -
-
{formName}
- {!importError && description &&
{description}
} -
{importError && {importError}}
- {importError && importError.includes("already exists") && fileType === ".json" && -
-
- - {(t) => t("Note")} -
-
- {(t) => t(`If you want to replace an existing form, open the form in the design menu that you want to update, click "Actions", and then click "Import".`)} + ); + }; + + // Function to render layout version options + const renderLayoutOptions = () => { + return ( + fileItems?.form?.majorVersion && ( +
+
{t("Layout")}
+
+ + +
+
+ {selectedLayoutVersion + ? selectedLayoutVersion.label + : "Skip, do not import"} +
+
-
} -
- {importError && !importError.includes("already exists") && - - {(t) => t("A system error occurred during import. Please try again to import.")} - } -
+ + + {layoutOptions.map((option, index) => ( + handleLayoutChange(option)} + > + {option.label} + + ))} + +
- {showFileItems && !importError && ( -
-
-
- - - {(t) => t("Import will create a new version.")} - -
-
-
-
Type
-
Import
-
- - {processVersion?.majorVersion &&
-
{processVersion.type}
-
{`Import as Version ${processVersion?.majorVersion}.${processVersion?.minorVersion} (only impacts new submissions)`}
-
} - {fileItems?.form?.majorVersion &&
-
Layout
-
- - -
-
- {selectedLayoutVersion ? selectedLayoutVersion.label : 'Skip, do not import'} -
- -
-
- - - {layoutOptions.map((option, index) => ( - handleLayoutChange(option)}> - {option.label} - - ))} - -
-
-
} - - {fileItems?.workflow?.majorVersion &&
-
Flow
-
- - -
-
- {selectedFlowVersion ? selectedFlowVersion.label : 'Skip, do not import'} -
- -
-
- - {flowOptions.map((option, index) => ( - handFlowChange(option)}> - {option.label} - - ))} - -
+
+ ) + ); + }; + + // Function to render flow version options + const renderFlowOptions = () => { + return ( + fileItems?.workflow?.majorVersion && ( +
+
Flow
+
+ + +
+
+ {selectedFlowVersion + ? selectedFlowVersion.label + : "Skip, do not import"} +
+
-
} -
- )} - - ) : ( -
document.getElementById('file-input')?.click()}> - -
- -

{(t) => t(`Click or drag a file to this area to import${fileType === ".json, .bpmn" ? " (form, layout or bpmn)" : ""}`)}

-

{(t) => t(`Support for a single ${fileType} file upload. Maximum file size 20MB.`)}

+ + + {flowOptions.map((option, index) => ( + handFlowChange(option)} + > + {option.label} + + ))} + +
- )} - - - { primaryButtonText === "Try Again" ? closeModal() : onImport(); }} - buttonLoading={!importError && importLoader} - /> - { resetState(); closeModal(); }} - /> - - - ); -}); + ) + ); + }; + // Function to render the file upload area when no file is selected + const renderFileUploadArea = () => { + return ( +
document.getElementById("file-input")?.click()} + > + +
+ +

+ {t( + `Click or drag a file to this area to import${ + fileType === ".json, .bpmn" ? " (form, layout or bpmn)" : "" + }` + )} +

+

+ {t( `Support for a single ${fileType} file upload. Maximum file + size 20MB.` )} +

+
+
+ ); + }; + return ( + + + + {t(headerText)} + +
+ { + resetState(); + closeModal(); + }} + /> +
+
+ + {selectedFile ? ( + <> + + {renderUploadDetails()} + {renderFileItems()} + + ) : ( + renderFileUploadArea() + )} + + + { + primaryButtonText === "Try Again" ? closeModal() : onImport(); + }} + buttonLoading={!importError && importLoader} + /> + { + resetState(); + closeModal(); + }} + /> + +
+ ); + } +); diff --git a/forms-flow-components/src/components/SvgIcons/index.tsx b/forms-flow-components/src/components/SvgIcons/index.tsx index 32ff4415..19293b89 100644 --- a/forms-flow-components/src/components/SvgIcons/index.tsx +++ b/forms-flow-components/src/components/SvgIcons/index.tsx @@ -635,7 +635,7 @@ export const TickIcon = ({ color = baseColor, ...props }) => ( ); -export const DropdownIcon = () => ( +export const DropdownIcon = ({color = baseColor}) => ( ( >