diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index 058c821e49..1dd9ba97ef 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -43,7 +43,15 @@ jobs: - { name: "redash", tag: "24.04.0" } - { name: "forms-flow-data-analysis-api", tag: "latest" } - { name: "forms-flow-documents-api", tag: "latest" } + steps: + - name: Authenticate with Docker Hub + env: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_ACCESS_TOKEN }} + run: | + echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin + - name: Install Trivy run: | sudo apt-get update diff --git a/forms-flow-web/src/components/CustomComponents/MultiSelect.js b/forms-flow-web/src/components/CustomComponents/MultiSelect.js deleted file mode 100644 index 62d34bcca9..0000000000 --- a/forms-flow-web/src/components/CustomComponents/MultiSelect.js +++ /dev/null @@ -1,120 +0,0 @@ -import React, { useState, useRef, useEffect } from "react"; -import { useSelector } from "react-redux"; -import { ListGroup } from "react-bootstrap"; -import { CustomPill,DeleteIcon } from "@formsflow/components"; -import PropTypes from 'prop-types'; -import { HelperServices, StyleServices } from "@formsflow/service"; - -const RoleSelector = ({ - allRoles = [], - selectedRoles = [], - setSelectedRoles, - openByDefault = false, -}) => { - const primaryColor = StyleServices.getCSSVariable('--ff-primary'); - const [roleInput, setRoleInput] = useState(""); - const [filteredRoles, setFilteredRoles] = useState([]); - const [isDropdownOpen, setIsDropdownOpen] = useState(false); // To control dropdown visibility - const dropDownRef = useRef(null); - const inputRef = useRef(null); - const tenantKey = useSelector((state) => state.tenants?.tenantId); - useEffect(() => { - if (openByDefault && inputRef.current && !selectedRoles.length) { - inputRef.current.focus(); - } - }, [openByDefault]); - // Filter roles based on input - useEffect(() => { - const filtered = allRoles.filter( - (role) => - role.toLowerCase().includes(roleInput.toLowerCase()) && - !selectedRoles.includes(role) - ); - setFilteredRoles(filtered); - }, [roleInput, allRoles, selectedRoles]); - - // Handle input change - const handleRoleInputChange = (e) => { - setRoleInput(e.target.value); - }; - - // Handle role selection from dropdown - const handleRoleSelect = (role) => { - setSelectedRoles([...selectedRoles, role]); - setRoleInput(""); // Clear input - setIsDropdownOpen(false); // Close the dropdown after selecting a role - }; - - // Handle role removal - const removeRole = (roleToRemove) => { - setSelectedRoles(selectedRoles.filter((role) => role !== roleToRemove)); - }; - - // Close dropdown when clicking outside - useEffect(() => { - const handleClickOutside = (event) => { - if ( - dropDownRef.current && - !dropDownRef.current.contains(event.target) && - !inputRef.current.contains(event.target) - ) { - setIsDropdownOpen(false); // Close dropdown if clicked outside - } - }; - - document.addEventListener("mousedown", handleClickOutside); - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, []); - - return ( -
-
- {selectedRoles.map((role, index) => ( - } - bg="primary" - onClick={() => removeRole(role)} - /> - ))} - setIsDropdownOpen(true)} // Open dropdown when input is focused - className="role-input" - ref={inputRef} // Reference to input for outside click handling - /> -
- - {isDropdownOpen && filteredRoles.length > 0 && ( -
- - {filteredRoles.map((role, index) => ( - handleRoleSelect(role)} - > - {HelperServices.removeTenantKeyFromData(role,tenantKey)} - - ))} - -
- )} -
- ); -}; - - -RoleSelector.propTypes = { - allRoles: PropTypes.array, - selectedRoles: PropTypes.array.isRequired, - setSelectedRoles: PropTypes.func.isRequired, - openByDefault: PropTypes.bool, -}; - -export default RoleSelector; diff --git a/forms-flow-web/src/components/CustomComponents/SortableHeader.js b/forms-flow-web/src/components/CustomComponents/SortableHeader.js index 0e33e360b0..7fb475f7bd 100644 --- a/forms-flow-web/src/components/CustomComponents/SortableHeader.js +++ b/forms-flow-web/src/components/CustomComponents/SortableHeader.js @@ -1,6 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import { useTranslation } from "react-i18next"; +import { SortIcon } from "@formsflow/components"; +import { StyleServices } from "@formsflow/service"; const SortableHeader = ({ columnKey, title, currentSort, handleSort,className = "" }) => { const { t } = useTranslation(); @@ -11,6 +13,7 @@ const SortableHeader = ({ columnKey, title, currentSort, handleSort,className = handleSort(columnKey); } }; + const grayColor = StyleServices.getCSSVariable('--ff-gray-400'); return ( ); diff --git a/forms-flow-web/src/components/Form/EditForm/FormEdit.js b/forms-flow-web/src/components/Form/EditForm/FormEdit.js index b0355b7ffc..5a45cfbb48 100644 --- a/forms-flow-web/src/components/Form/EditForm/FormEdit.js +++ b/forms-flow-web/src/components/Form/EditForm/FormEdit.js @@ -61,7 +61,8 @@ import NewVersionModal from "../../Modals/NewVersionModal"; import { currentFormReducer } from "../../../modules/formReducer.js"; import { toast } from "react-toastify"; import userRoles from "../../../constants/permissions.js"; -import { generateUniqueId, isFormComponentsChanged, addTenantkey, textTruncate } from "../../../helper/helper.js"; +import { generateUniqueId, isFormComponentsChanged, addTenantkey, textTruncate, + convertMultiSelectOptionToValue } from "../../../helper/helper.js"; import { useMutation } from "react-query"; import NavigateBlocker from "../../CustomComponents/NavigateBlocker"; import { setProcessData, setFormPreviosData, setFormProcessesData } from "../../../actions/processActions.js"; @@ -538,12 +539,13 @@ const handleSaveLayout = () => { /* ----------- save settings function to be used in settings modal ---------- */ + const filterAuthorizationData = (authorizationData) => { if(authorizationData.selectedOption === "submitter"){ return {roles: [], userName:null, resourceDetails:{submitter:true}}; } if (authorizationData.selectedOption === "specifiedRoles") { - return { roles: authorizationData.selectedRoles, userName: "" }; + return { roles: convertMultiSelectOptionToValue(authorizationData.selectedRoles, "role"), userName: "" }; } return { roles: [], userName: preferred_username }; }; @@ -582,7 +584,7 @@ const handleSaveLayout = () => { resourceDetails: {}, roles: rolesState.FORM.selectedOption === "specifiedRoles" - ? rolesState.FORM.selectedRoles + ? convertMultiSelectOptionToValue(rolesState.FORM.selectedRoles, "role") : [], }, }; diff --git a/forms-flow-web/src/components/Form/EditForm/FormSettings.js b/forms-flow-web/src/components/Form/EditForm/FormSettings.js index 1e8652bf2a..534f08c129 100644 --- a/forms-flow-web/src/components/Form/EditForm/FormSettings.js +++ b/forms-flow-web/src/components/Form/EditForm/FormSettings.js @@ -3,6 +3,7 @@ import React, { useState, useImperativeHandle, forwardRef, + useRef } from "react"; import { Form, FormControl, InputGroup } from "react-bootstrap"; import { @@ -11,11 +12,11 @@ import { CustomRadioButton, FormInput, FormTextArea, + MultipleSelect } from "@formsflow/components"; -import MultiSelectComponent from "../../CustomComponents/MultiSelect"; import { MULTITENANCY_ENABLED } from "../../../constants/constants"; -import { addTenantkeyAsSuffix } from "../../../helper/helper"; +import { addTenantkeyAsSuffix, convertSelectedValueToMultiSelectOption } from "../../../helper/helper"; import { useDispatch, useSelector } from "react-redux"; import { getUserRoles } from "../../../apiManager/services/authorizationService"; import { useTranslation } from "react-i18next"; @@ -45,6 +46,8 @@ const FormSettings = forwardRef((props, ref) => { /* --------------------------- useState Variables --------------------------- */ const [userRoles, setUserRoles] = useState([]); const [copied, setCopied] = useState(false); + // to check if already vlidated and passed. + const blurStatus = useRef({ title: false, path: false }); const [formDetails, setFormDetails] = useState({ title: processListData.formName, path: path, @@ -64,22 +67,26 @@ const FormSettings = forwardRef((props, ref) => { const publicUrlPath = `${window.location.origin}/public/form/`; const [urlPath,setUrlPath] = useState(publicUrlPath); - const setSelectedOption = (roles, option)=> roles.length ? "specifiedRoles" : option; + const setSelectedOption = (option, roles = [])=> roles.length ? "specifiedRoles" : option; + const multiSelectOptionKey = "role"; /* ------------------------- authorization variables ------------------------ */ const [rolesState, setRolesState] = useState({ DESIGN: { - selectedRoles: formAuthorization.DESIGNER?.roles, - selectedOption: setSelectedOption(formAuthorization.DESIGNER?.roles,"onlyYou"), + selectedRoles: convertSelectedValueToMultiSelectOption(formAuthorization.DESIGNER?.roles, + multiSelectOptionKey), + selectedOption: setSelectedOption("onlyYou", formAuthorization.DESIGNER?.roles), }, FORM: { roleInput: "", - selectedRoles: formAuthorization.FORM?.roles, - selectedOption: setSelectedOption(formAuthorization.FORM?.roles,"registeredUsers"), + selectedRoles: convertSelectedValueToMultiSelectOption(formAuthorization.FORM?.roles, + multiSelectOptionKey), + selectedOption: setSelectedOption("registeredUsers", formAuthorization.FORM?.roles), }, APPLICATION: { roleInput: "", - selectedRoles: formAuthorization.APPLICATION?.roles, - selectedOption: setSelectedOption(formAuthorization.APPLICATION?.roles, "submitter"), + selectedRoles: convertSelectedValueToMultiSelectOption(formAuthorization.APPLICATION?.roles, + multiSelectOptionKey), + selectedOption: setSelectedOption("submitter", formAuthorization.APPLICATION?.roles), /* The 'submitter' key is stored in 'resourceDetails'. If the roles array is not empty we assume that the submitter is true. */ } @@ -101,40 +108,49 @@ const FormSettings = forwardRef((props, ref) => { } },[MULTITENANCY_ENABLED]); - /* ------------------------- validating form name and path ------------------------ */ + /* ------------------------- validating form name and path ------------------------ */ const validateField = async (field, value) => { - let errorMessage = ""; + if (!value.trim()) { + const errorMessage = `${field.charAt(0).toUpperCase() + field.slice(1)} is required.`; + setErrors((prev) => ({ ...prev, [field]: errorMessage })); + return false; + } + setIsValidating((prev) => ({ ...prev, [field]: true })); + let errorMessage = ""; - if (!value.trim()) { - errorMessage = `${field.charAt(0).toUpperCase() + field.slice(1)} is required`; - } else { - try { - const response = field === 'name' ? await validateFormName(value,parentFormId) : await validatePathName(value,formId); - const data = response?.data; - if (data && data.code === "FORM_EXISTS") { - errorMessage = data.message; - } - } catch (error) { - errorMessage = error.response?.data?.message || - `An error occurred while validating the ${field}.`; - console.error(`Error validating ${field}:`, errorMessage); + try { + const response = + field === "title" + ? await validateFormName(value, parentFormId) + : await validatePathName(value, formId); + + const data = response?.data; + if (data?.code === "FORM_EXISTS") { + errorMessage = data.message; } + } catch (error) { + errorMessage = + error.response?.data?.message || + `An error occurred while validating the ${field}.`; + console.error(`Error validating ${field}:`,errorMessage); } + setErrors((prev) => ({ ...prev, [field]: errorMessage })); setIsValidating((prev) => ({ ...prev, [field]: false })); - + return !errorMessage; // Return true if no error }; - - /* ---------------------- handling form details change ---------------------- */ const handleFormDetailsChange = (e) => { const { name, value, type } = e.target; - let updatedValue = - name === "path" ? _camelCase(value).toLowerCase() : value; + let field = name === "title" ? "name" : name; // Use 'name' for title case + // Clear errors and reset blurStatus for the relevant field + setErrors((prev) => ({ ...prev, [field]: "" })); + blurStatus.current[name] = false; + let updatedValue = name === "path" ? _camelCase(value).toLowerCase() : value; if (type === "checkbox") { setFormDetails((prev) => ({ ...prev, [name]: e.target.checked ? "wizard" : "form" })); @@ -154,7 +170,7 @@ const FormSettings = forwardRef((props, ref) => { .then((res) => { if (res) { const { data = [] } = res; - setUserRoles(data.map((role) => role.name)); + setUserRoles(data.map((role,index) => ({[multiSelectOptionKey]:role.name, id: index}))); } }) .catch((error) => console.error("error", error)); @@ -162,7 +178,7 @@ const FormSettings = forwardRef((props, ref) => { - const handleRoleStateChange = (section, key, value) => { + const handleRoleStateChange = (section, key, value = []) => { setRolesState((prevState) => ({ ...prevState, [section]: { @@ -188,6 +204,7 @@ const FormSettings = forwardRef((props, ref) => { return { formDetails: { ...formDetails, anonymous: isAnonymous }, rolesState: rolesState, + validateField, }; }); @@ -210,6 +227,10 @@ const FormSettings = forwardRef((props, ref) => { props.setIsSaveButtonDisabled(shouldDisableSaveButton); }, [rolesState, errors, formDetails]); + const handleRoleSelectForDesign = (roles) => handleRoleStateChange(DESIGN, "selectedRoles", roles); + const handleRoleSelectForForm = (roles) => handleRoleStateChange(FORM, "selectedRoles", roles); + const handleRoleSelectForApplication = (roles) => + handleRoleStateChange(APPLICATION, "selectedRoles", roles); return ( <> @@ -289,14 +310,15 @@ const FormSettings = forwardRef((props, ref) => { )} {rolesState.DESIGN.selectedOption === "specifiedRoles" && ( - - handleRoleStateChange(DESIGN, "selectedRoles", roles) - } + + )} @@ -338,14 +360,15 @@ const FormSettings = forwardRef((props, ref) => { )} {rolesState.FORM.selectedOption === "specifiedRoles" && ( - - handleRoleStateChange(FORM, "selectedRoles", roles) - } - /> + + )} @@ -378,14 +401,15 @@ const FormSettings = forwardRef((props, ref) => { )} {rolesState.APPLICATION.selectedOption === "specifiedRoles" && ( - - handleRoleStateChange(APPLICATION, "selectedRoles", roles) - } + + )} diff --git a/forms-flow-web/src/components/Form/constants/FormTable.js b/forms-flow-web/src/components/Form/constants/FormTable.js index 279226c3ca..d76d469be4 100644 --- a/forms-flow-web/src/components/Form/constants/FormTable.js +++ b/forms-flow-web/src/components/Form/constants/FormTable.js @@ -108,7 +108,7 @@ function FormTable() {
- +
@@ -127,6 +127,7 @@ function FormTable() { title="Last Edited" currentSort={currentFormSort} handleSort={handleSort} + className="gap-2" /> @@ -156,7 +159,7 @@ function FormTable() { -
@@ -117,7 +117,7 @@ function FormTable() { title="Form Name" currentSort={currentFormSort} handleSort={handleSort} - className="ms-4" + className="gap-2" /> {t("Description")} @@ -134,14 +135,16 @@ function FormTable() { columnKey="visibility" title="Visibility" currentSort={currentFormSort} - handleSort={handleSort} /> + handleSort={handleSort} + className="gap-2"/> + handleSort={handleSort} + className="gap-2"/>
- {e.title} + {e.title}
@@ -177,7 +180,7 @@ function FormTable() { {e.status === "active" ? t("Live") : t("Draft")} + {(createDesigns || viewDesigns) && ( { - handleConfirm(FormSettingsRef.current); + const handleConfirmFunction = async () => { + const { formDetails, validateField } = FormSettingsRef.current; + const fieldsToValidate = ["title", "path"]; + + // Reset validation error state at the beginning + let validationError = false; + + for (const field of fieldsToValidate) { + const fieldValue = formDetails?.[field]; + if (!fieldValue || !(await validateField(field, fieldValue))) { + validationError = true; + break; // Stop further validation if any field fails + } + } + if (!validationError) { + handleConfirm(FormSettingsRef.current); + } }; return ( { - const dispatch = useDispatch(); - const { t } = useTranslation(); - const dmn = useSelector((state) => state.process?.dmnProcessList); - const [isLoading, setIsLoading] = useState(true); - const searchText = useSelector((state) => state.process?.dmnSearchText); - const [activePage, setActivePage] = useState(1); - const [limit, setLimit] = useState(5); - const tenantKey = useSelector((state) => state.tenants?.tenantId); - const totalCount = useSelector((state) => state.process.totalDmnCount); - const [currentDmnSort, setCurrentDmnSort] = useState({ - activeKey: "name", - name: { sortOrder: "asc" }, - processKey: { sortOrder: "asc" }, - modified: { sortOrder: "asc" }, - status: { sortOrder: "asc" }, - }); - const [importDecisionTable, setImportDecisionTable] = useState(false); - const closeDmnImport = () => { - setImportDecisionTable(false); - }; - const [searchDmnLoading, setSearchDmnLoading] = useState(false); - const redirectUrl = MULTITENANCY_ENABLED ? `/tenant/${tenantKey}/` : "/"; - const [search, setSearch] = useState(searchText || ""); - const [showBuildModal, setShowBuildModal] = useState(false); - const handleBuildClick = () => { - dispatch(push(`${redirectUrl}decision-table/create`)); - }; - const handleImportClick = () => { - setShowBuildModal(false); - setImportDecisionTable(true); - }; - const contents = [ - { - id: 1, - heading: "Build", - body: "Create the DMN from scratch", - onClick: handleBuildClick - }, - { - id: 2, - heading: "Import", - body: "Upload DMN from a file", - onClick: handleImportClick - } - ]; - - - useEffect(() => { - if (!search?.trim()) { - dispatch(setDmnSearchText("")); - } - }, [search]); - - - useEffect(() => { - setIsLoading(true); - dispatch( - fetchAllProcesses( - { - pageNo: activePage, - tenant_key: tenantKey, - processType: "DMN", - limit: limit, - searchKey: search, - sortBy: currentDmnSort.activeKey, - sortOrder: currentDmnSort[currentDmnSort.activeKey].sortOrder, - }, - () => { - setIsLoading(false); - setSearchDmnLoading(false); - } - ) - ); - }, [dispatch, activePage, limit, searchText, currentDmnSort]); - - - const handleSort = (key) => { - setCurrentDmnSort((prevSort) => { - const newSortOrder = prevSort[key].sortOrder === "asc" ? "desc" : "asc"; - return { - ...prevSort, - activeKey: key, - [key]: { sortOrder: newSortOrder }, - }; - }); - }; - - const pageOptions = [ - { text: "5", value: 5 }, - { text: "10", value: 10 }, - { text: "25", value: 25 }, - { text: "50", value: 50 }, - { text: "100", value: 100 }, - { text: "All", value: totalCount }, - ]; - - const handleClearSearch = () => { - setSearch(""); - setActivePage(1); - dispatch(setDmnSearchText("")); - }; - const handleSearch = () => { - setSearchDmnLoading(true); - setActivePage(1); - dispatch(setDmnSearchText(search)); - }; - const onLimitChange = (newLimit) => { - setLimit(newLimit); - handlePageChange(1); - }; - const handlePageChange = (page) => setActivePage(page); - const gotoEdit = (data) => { - if (MULTITENANCY_ENABLED) { - dispatch(setIsPublicDiagram(!!data.tenantId)); - } - dispatch(push(`${redirectUrl}decision-table/edit/${data.processKey}`)); - }; - - const handleCreateDMN = () => { - setShowBuildModal(true); - }; - const handleBuildModal = () => { - setShowBuildModal(false); - }; - - - return ( - <> -
-
- -
-
- handleCreateDMN()} - /> -
- -
-
- - - - - - - - - - - {dmn.length ? - - {dmn.map((dmnItem) => ( - - ))} - - : !isLoading ? ( - - ) : null} -
- - - - - - - -
-
-
-
-
- - {importDecisionTable && ( - - )} - ); -}); - -DecisionTable.propTypes = {}; - -export default DecisionTable; diff --git a/forms-flow-web/src/components/Modeler/ProcessTable.js b/forms-flow-web/src/components/Modeler/ProcessTable.js new file mode 100644 index 0000000000..aec12e7238 --- /dev/null +++ b/forms-flow-web/src/components/Modeler/ProcessTable.js @@ -0,0 +1,322 @@ +import React, { useEffect, useState } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { + CustomButton, + CustomSearch, + ReusableProcessTableRow, + TableFooter, + NoDataFound, + BuildModal, +} from "@formsflow/components"; +import LoadingOverlay from "react-loading-overlay-ts"; +import { useTranslation } from "react-i18next"; +import { useParams } from "react-router-dom"; +import SortableHeader from "../CustomComponents/SortableHeader"; +import { fetchAllProcesses } from "../../apiManager/services/processServices"; +import { MULTITENANCY_ENABLED } from "../../constants/constants"; +import { push } from "connected-react-router"; +import ImportProcess from "../Modals/ImportProcess"; +import { + setBpmnSearchText, + setDmnSearchText, + setIsPublicDiagram, +} from "../../actions/processActions"; + +const ProcessTable = React.memo(() => { + const { viewType } = useParams(); + const isBPMN = viewType === "subflow"; + const dispatch = useDispatch(); + const { t } = useTranslation(); + const ProcessContents = isBPMN + ? { + processType: "BPMN", + extension: ".bpmn", + } + : { + processType: "DMN", + extension: ".dmn", + }; + + // States and selectors + const processList = useSelector((state) => + isBPMN ? state.process.processList : state.process.dmnProcessList + ); + const searchText = useSelector((state) => + isBPMN ? state.process.bpmnSearchText : state.process.dmnSearchText + ); + const totalCount = useSelector((state) => + isBPMN ? state.process.totalBpmnCount : state.process.totalDmnCount + ); + const tenantKey = useSelector((state) => state.tenants?.tenantId); + + const initialSortConfig = { + activeKey: "name", + name: { sortOrder: "asc" }, + processKey: { sortOrder: "asc" }, + modified: { sortOrder: "asc" }, + status: { sortOrder: "asc" }, + }; + + const [bpmnState, setBpmnState] = useState({ + activePage: 1, + limit: 5, + sortConfig: initialSortConfig, + }); + + const [dmnState, setDmnState] = useState({ + activePage: 1, + limit: 5, + sortConfig: initialSortConfig, + }); + const [searchDMN, setSearchDMN] = useState(searchText || ""); + const [searchBPMN, setSearchBPMN] = useState(searchText || ""); + const search = isBPMN ? searchBPMN : searchDMN; + + const [showBuildModal, setShowBuildModal] = useState(false); + const [importProcess, setImportProcess] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [searchLoading, setSearchLoading] = useState(false); + + const currentState = isBPMN ? bpmnState : dmnState; + const setCurrentState = isBPMN ? setBpmnState : setDmnState; + + const redirectUrl = MULTITENANCY_ENABLED ? `/tenant/${tenantKey}/` : "/"; + + //fetching bpmn or dmn + useEffect(() => { + setIsLoading(true); + dispatch( + fetchAllProcesses( + { + pageNo: currentState.activePage, + tenant_key: tenantKey, + processType: ProcessContents.processType, + limit: currentState.limit, + searchKey: search, + sortBy: currentState.sortConfig.activeKey, + sortOrder: currentState.sortConfig[currentState.sortConfig.activeKey].sortOrder, + }, + () => { + setIsLoading(false); + setSearchLoading(false); + } + ) + ); + }, [dispatch, currentState, tenantKey,searchText, isBPMN]); + + //Update api call when search field is empty + useEffect(() => { + if (!search.trim()) { + dispatch(isBPMN ? setBpmnSearchText("") : setDmnSearchText("")); + } + }, [search, dispatch, isBPMN]); + + const handleSort = (key) => { + setCurrentState((prevState) => ({ + ...prevState, + sortConfig: { + ...prevState.sortConfig, + activeKey: key, + [key]: { + sortOrder: prevState.sortConfig[key]?.sortOrder === "asc" ? "desc" : "asc", + }, + }, + })); + }; + + const handleSearch = () => { + setSearchLoading(true); + if (isBPMN) { + dispatch(setBpmnSearchText(searchBPMN)); + } else { + dispatch(setDmnSearchText(searchDMN)); + } + handlePageChange(1); + }; + + const handleClearSearch = () => { + if (isBPMN) { + setSearchBPMN(""); + dispatch(setBpmnSearchText("")); + } else { + setSearchDMN(""); + dispatch(setDmnSearchText("")); + } + handlePageChange(1); + }; + + const handlePageChange = (page) => { + setCurrentState((prevState) => ({ + ...prevState, + activePage: page, + })); + }; + + const onLimitChange = (newLimit) => { + setCurrentState((prevState) => ({ + ...prevState, + limit: newLimit, + activePage: 1, + })); + }; + + const gotoEdit = (data) => { + if (MULTITENANCY_ENABLED) { + dispatch(setIsPublicDiagram(!!data.tenantId)); + } + dispatch( + push( + `${redirectUrl}${viewType}/edit/${data.processKey}` + ) + ); + }; + + const handleCreateProcess = () => { + setShowBuildModal(true); + }; + + const handleBuildModal = () => { + setShowBuildModal(false); + }; + + const showImportModal = () => { + setShowBuildModal(false); + setImportProcess(true); + }; + + // contents for import of BPMN or DMN + const modalContents = [ + { + id: 1, + heading: "Build", + body: `Create the ${ProcessContents.processType} from scratch`, + onClick: () => dispatch(push(`${redirectUrl}${viewType}/create`)), + }, + { + id: 2, + heading: "Import", + body: `Upload ${ProcessContents.processType} from a file`, + onClick: showImportModal, + }, + ]; + + return ( + <> +
+
+ +
+
+ +
+
+ +
+
+ + + + + + + + + + + {processList.length ? ( + + {processList.map((processItem) => ( + + ))} + + + ) : !isLoading && } +
+ + + + + + + +
+
+
+
+ + {importProcess && ( + setImportProcess(false)} + fileType={ProcessContents.extension} + /> + )} + + ); +}); + +export default ProcessTable; diff --git a/forms-flow-web/src/components/Modeler/SubFlowTable.js b/forms-flow-web/src/components/Modeler/SubFlowTable.js deleted file mode 100644 index c966f1ffb6..0000000000 --- a/forms-flow-web/src/components/Modeler/SubFlowTable.js +++ /dev/null @@ -1,246 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { - CustomButton, - CustomSearch, - TableFooter, - ReusableProcessTableRow, - NoDataFound, - BuildModal, -} from "@formsflow/components"; -import { useSelector, useDispatch } from "react-redux"; -import { useTranslation } from "react-i18next"; -import { fetchAllProcesses } from "../../apiManager/services/processServices"; -import LoadingOverlay from "react-loading-overlay-ts"; -import { - setBpmnSearchText, - setIsPublicDiagram, -} from "../../actions/processActions"; -import { push } from "connected-react-router"; -import { MULTITENANCY_ENABLED } from "../../constants/constants"; -import SortableHeader from "../CustomComponents/SortableHeader"; -import ImportProcess from "../Modals/ImportProcess"; - -const SubFlow = React.memo(() => { - const dispatch = useDispatch(); - const { t } = useTranslation(); - const searchText = useSelector((state) => state.process.bpmnSearchText); - const tenantKey = useSelector((state) => state.tenants?.tenantId); - const processList = useSelector((state) => state.process.processList); - const totalCount = useSelector((state) => state.process.totalBpmnCount); - const [importSubflow, setImportSubflow] = useState(false); - - // Local states - const [activePage, setActivePage] = useState(1); - const [isLoading, setIsLoading] = useState(true); - const [limit, setLimit] = useState(5); - const [search, setSearch] = useState(searchText || ""); - const [searchBpmnLoading, setSearchBpmnLoading] = useState(false); - const [currentBpmnSort, setCurrentBpmnSort] = useState({ - activeKey: "name", - name: { sortOrder: "asc" }, - processKey: { sortOrder: "asc" }, - modified: { sortOrder: "asc" }, - status: { sortOrder: "asc" }, - }); - const [showBuildModal, setShowBuildModal] = useState(false); - const redirectUrl = MULTITENANCY_ENABLED ? `/tenant/${tenantKey}/` : "/"; - - const ShowImportModal = () => { - setShowBuildModal(false); - setImportSubflow(true); - }; - - // Modal contents - const modalContents = [ - { - id: 1, - heading: "Build", - body: "Create the BPMN from scratch", - onClick: () => dispatch(push(`${redirectUrl}subflow/create`)), - }, - { - id: 2, - heading: "Import", - body: "Upload BPMN from a file", - onClick: ShowImportModal, - }, - ]; - - useEffect(() => { - if (!search.trim()) dispatch(setBpmnSearchText("")); - }, [search, dispatch]); - - useEffect(() => { - setIsLoading(true); - dispatch( - fetchAllProcesses( - { - pageNo: activePage, - tenant_key: tenantKey, - processType: "BPMN", - limit, - searchKey: search, - sortBy: currentBpmnSort.activeKey, - sortOrder: currentBpmnSort[currentBpmnSort.activeKey].sortOrder, - }, - () => { - setIsLoading(false); - setSearchBpmnLoading(false); - } - ) - ); - }, [dispatch, activePage, limit, searchText, tenantKey, currentBpmnSort]); - - const handleSort = (key) => { - setCurrentBpmnSort((prevConfig) => ({ - ...prevConfig, - activeKey: key, - [key]: { sortOrder: prevConfig[key].sortOrder === "asc" ? "desc" : "asc" }, - })); - }; - - const pageOptions = [ - { text: "5", value: 5 }, - { text: "25", value: 25 }, - { text: "50", value: 50 }, - { text: "100", value: 100 }, - { text: "All", value: totalCount }, - ]; - - const handlePageChange = (page) => setActivePage(page); - const onLimitChange = (newLimit) => { - setLimit(newLimit); - setActivePage(1); - }; - const handleClearSearch = () => { - setSearch(""); - setActivePage(1); - dispatch(setBpmnSearchText("")); - }; - - const handleSearch = () => { - setSearchBpmnLoading(true); - setActivePage(1); - dispatch(setBpmnSearchText(search)); - }; - - const gotoEdit = (data) => { - if (MULTITENANCY_ENABLED) dispatch(setIsPublicDiagram(!!data.tenantId)); - dispatch(push(`${redirectUrl}subflow/edit/${data.processKey}`)); - }; - - const handleCreateBPMN = () => { - setShowBuildModal(true); - }; - const handleBuildModal = () => { - setShowBuildModal(false); - }; - - return ( - <> -
-
- -
-
- -
-
- -
-
- - - - - - - - - - - {processList.length ? ( - - {processList.map((processItem) => ( - - ))} - - - ) : !isLoading && - } -
- - - - - - - -
-
-
-
- - {importSubflow && ( - setImportSubflow(false)} fileType=".bpmn" /> - )} - - ); -}); - -export default SubFlow; diff --git a/forms-flow-web/src/components/Modeler/index.js b/forms-flow-web/src/components/Modeler/index.js index a9c866af5b..f81ff723c0 100644 --- a/forms-flow-web/src/components/Modeler/index.js +++ b/forms-flow-web/src/components/Modeler/index.js @@ -2,8 +2,7 @@ import React from "react"; import { Route, Switch, Redirect } from "react-router-dom"; import { useSelector } from "react-redux"; import ProcessCreateEdit from "./ProcessCreateEdit"; -import SubFlowList from './SubFlowTable'; -import DecisionTable from './DecisionTable'; +import ProcessTable from './ProcessTable'; import { BASE_ROUTE } from "../../constants/constants"; import Loading from "../../containers/Loading"; import AccessDenied from "../AccessDenied"; @@ -38,8 +37,7 @@ const Processes = () => { return (
- - + { } }; -export { generateUniqueId, replaceUrl, addTenantkey, removeTenantKey, textTruncate, renderPage, - filterSelectOptionByLabel, isFormComponentsChanged,addTenantkeyAsSuffix}; +/* ----------------- convert data from and into multiselect ----------------- */ +const convertMultiSelectOptionToValue = (selectedValues = [], key = null) => + selectedValues.map(i=> i[key]); + +const convertSelectedValueToMultiSelectOption = (values = [], key = null) => + values.map((value)=>({[key]:value, id:_.uniqueId(value)})); +/* ----------------------------------- --- ---------------------------------- */ +export { generateUniqueId, + replaceUrl, + addTenantkey, + removeTenantKey, + textTruncate, + renderPage, + filterSelectOptionByLabel, + isFormComponentsChanged, + addTenantkeyAsSuffix, + convertMultiSelectOptionToValue, + convertSelectedValueToMultiSelectOption +};