Skip to content

Commit b89a5aa

Browse files
authored
Merge pull request #3002 from Ajay-aot/FWF-5402/extra-buttons-in-designer-listing-page
FWF-5402[feature] - duplicate and delete case in form listing page
2 parents 885d849 + 4d92e47 commit b89a5aa

File tree

3 files changed

+205
-14
lines changed

3 files changed

+205
-14
lines changed

forms-flow-web/src/apiManager/services/bpmServices.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export const getTaskSubmitFormReq = (formUrl, applicationId, actionType, webForm
4040
export const formatForms = (forms) => {
4141
return forms.map((form) => {
4242
return {
43+
mapperId:form.id,
4344
_id: form.formId,
4445
title: form.formName,
4546
processKey: form.processKey,

forms-flow-web/src/components/Form/constants/FormTable.js

Lines changed: 188 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,22 @@ import * as React from "react";
22
import { useSelector, useDispatch } from "react-redux";
33
import { useTranslation } from "react-i18next";
44
import { push } from "connected-react-router";
5-
import { setBPMFormLimit, setBPMFormListPage, setBpmFormSort } from "../../../actions/formActions";
6-
import { resetFormProcessData } from "../../../apiManager/services/processServices";
7-
import { fetchBPMFormList } from "../../../apiManager/services/bpmFormServices";
5+
import { setBPMFormLimit, setBPMFormListPage, setBpmFormSort, setFormDeleteStatus } from "../../../actions/formActions";
6+
import { resetFormProcessData, unPublishForm, getApplicationCount, getProcessDetails } from "../../../apiManager/services/processServices";
7+
import { fetchBPMFormList, fetchFormById } from "../../../apiManager/services/bpmFormServices";
88
import { setFormSearchLoading } from "../../../actions/checkListActions";
99
import userRoles from "../../../constants/permissions";
1010
import { HelperServices, StyleServices } from "@formsflow/service";
1111
import { MULTITENANCY_ENABLED } from "../../../constants/constants";
12-
import { V8CustomButton, RefreshIcon, V8CustomDropdownButton, ReusableTable } from "@formsflow/components";
12+
import { V8CustomButton, RefreshIcon, V8CustomDropdownButton, PromptModal, ReusableTable } from "@formsflow/components";
13+
import { deleteForm } from "@aot-technologies/formio-react";
14+
import { formCreate } from "../../../apiManager/services/FormServices";
15+
import { manipulatingFormData } from "../../../apiManager/services/formFormatterService";
16+
import _cloneDeep from "lodash/cloneDeep";
17+
import { toast } from "react-toastify";
18+
import PropTypes from "prop-types";
1319

14-
15-
function FormTable() {
20+
function FormTable({ isDuplicating, setIsDuplicating, setDuplicateProgress }) {
1621
const dispatch = useDispatch();
1722
const tenantKey = useSelector(state => state.tenants?.tenantId);
1823
const bpmForms = useSelector(state => state.bpmForms);
@@ -24,10 +29,22 @@ function FormTable() {
2429
const searchFormLoading = useSelector(state => state.formCheckList.searchFormLoading);
2530
const isApplicationCountLoading = useSelector(state => state.process.isApplicationCountLoading);
2631
const searchText = useSelector(state => state.bpmForms.searchText);
32+
const applicationCount = useSelector((state) => state.process?.applicationCount);
33+
34+
// Get form access data from Redux store
35+
const formAccess = useSelector((state) => state.user?.formAccess || []);
36+
const submissionAccess = useSelector((state) => state.user?.submissionAccess || []);
37+
2738
const { createDesigns, viewDesigns } = userRoles();
2839
const { t } = useTranslation();
2940
const redirectUrl = MULTITENANCY_ENABLED ? `/tenant/${tenantKey}/` : "/";
3041
const iconColor = StyleServices.getCSSVariable('--ff-gray-medium-dark');
42+
43+
const [showDeleteModal, setShowDeleteModal] = React.useState(false);
44+
const [selectedRow, setSelectedRow] = React.useState(null);
45+
const [deleteMessage, setDeleteMessage] = React.useState("");
46+
const [disableDelete, setDisableDelete] = React.useState(false);
47+
const [isAppCountLoading, setIsAppCountLoading] = React.useState(false);
3148

3249
// Mapping between DataGrid field names and reducer sort keys
3350
const gridFieldToSortKey = {
@@ -44,6 +61,128 @@ function FormTable() {
4461
status: "status",
4562
};
4663

64+
// 🔹 Delete flow
65+
const deleteAction = (row) => {
66+
setSelectedRow(row);
67+
setShowDeleteModal(true);
68+
setIsAppCountLoading(true); // start loading state
69+
70+
dispatch(
71+
getApplicationCount(row.mapperId, () => {
72+
setIsAppCountLoading(false); // stop loading once response arrives
73+
})
74+
);
75+
};
76+
77+
const handleDelete = () => {
78+
if (!selectedRow) return;
79+
const formId = selectedRow._id;
80+
const mapperId = selectedRow.mapperId;
81+
if (!applicationCount || applicationCount === 0) {
82+
dispatch(
83+
deleteForm("form", formId, () => {
84+
dispatch(fetchBPMFormList({ pageNo, limit, formSort: formsort }));
85+
})
86+
);
87+
88+
if (mapperId) {
89+
dispatch(unPublishForm(mapperId));
90+
}
91+
dispatch(setFormDeleteStatus({ modalOpen: false, formId: "", formName: "" }));
92+
toast.success(t("Form deleted successfully"));
93+
}
94+
handleCloseDelete();
95+
};
96+
97+
const handleCloseDelete = () => {
98+
setShowDeleteModal(false);
99+
setSelectedRow(null);
100+
};
101+
102+
// 🔹 Duplicate flow
103+
const handleDuplicate = async (row) => {
104+
try {
105+
setIsDuplicating(true);
106+
setDuplicateProgress(0);
107+
// Fetch the full form data
108+
setDuplicateProgress(20);
109+
const formResponse = await fetchFormById(row._id);
110+
111+
setDuplicateProgress(40);
112+
const diagramResponse = await getProcessDetails({
113+
processKey: row.processKey,
114+
tenantKey,
115+
mapperId: row.mapperId
116+
});
117+
118+
setDuplicateProgress(60);
119+
const originalForm = formResponse.data;
120+
121+
// Create duplicated form data
122+
const duplicatedForm = _cloneDeep(originalForm);
123+
124+
// Modify title, name, and path
125+
duplicatedForm.title = `${originalForm.title}-copy`;
126+
duplicatedForm.name = `${originalForm.name}-copy`;
127+
duplicatedForm.path = `${originalForm.path}-copy`;
128+
129+
duplicatedForm.processData = diagramResponse.data.processData;
130+
duplicatedForm.processType = diagramResponse.data.processType;
131+
132+
// Remove _id and other fields that should not be copied
133+
delete duplicatedForm._id;
134+
delete duplicatedForm.machineName;
135+
delete duplicatedForm.parentFormId;
136+
137+
// Set as new version
138+
duplicatedForm.componentChanged = true;
139+
duplicatedForm.newVersion = true;
140+
141+
setDuplicateProgress(80);
142+
// Manipulate form data with proper formatting
143+
const newFormData = manipulatingFormData(
144+
duplicatedForm,
145+
MULTITENANCY_ENABLED,
146+
tenantKey,
147+
formAccess,
148+
submissionAccess
149+
);
150+
151+
setDuplicateProgress(90);
152+
// Create the duplicated form
153+
const createResponse = await formCreate(newFormData);
154+
155+
setDuplicateProgress(100);
156+
const createdForm = createResponse.data;
157+
158+
// Redirect to edit page of duplicated form
159+
dispatch(push(`${redirectUrl}formflow/${createdForm._id}/edit`));
160+
161+
} catch (err) {
162+
console.error("Error duplicating form:", err);
163+
// const errorMessage = err.response?.data?.message || err.message || "Failed to duplicate form";
164+
} finally {
165+
setIsDuplicating(false);
166+
}
167+
};
168+
169+
// Watch for application count updates
170+
React.useEffect(() => {
171+
if (selectedRow) {
172+
if (isAppCountLoading) {
173+
setDeleteMessage(t("Preparing for delete..."));
174+
setDisableDelete(true);
175+
} else if (applicationCount > 0) {
176+
setDeleteMessage(
177+
t(`This form has ${applicationCount} submission(s). You can’t delete it.`)
178+
);
179+
setDisableDelete(true);
180+
} else {
181+
setDeleteMessage(t("Are you sure you want to delete this form?"));
182+
setDisableDelete(false);
183+
}
184+
}
185+
}, [applicationCount, selectedRow, isAppCountLoading, t]);
47186
// Prepare DataGrid columns
48187
const columns = [
49188
{
@@ -115,17 +254,35 @@ function FormTable() {
115254
flex: 1,
116255
sortable: false,
117256
cellClassName: "last-column",
118-
renderCell: params => (
119-
(createDesigns || viewDesigns) && (
257+
renderCell: params => {
258+
const dropdownItems = [
259+
{
260+
label: t("Duplicate form"),
261+
onClick: () => handleDuplicate(params.row),
262+
},
263+
{
264+
label: params.row.status === "active" ? t("Unpublish") : t("Delete"),
265+
onClick: () => {
266+
if (params.row.status === "active") {
267+
// dispatch(unPublishForm(params.row.mapperId));
268+
} else {
269+
deleteAction(params.row);
270+
}
271+
},
272+
className: params.row.status === "active" ? "" : "delete-dropdown-item",
273+
},
274+
];
275+
return (createDesigns || viewDesigns) && (
120276
<V8CustomDropdownButton
121277
label={t("Edit")}
122278
variant="secondary"
123279
menuPosition="right"
124-
dropdownItems={[]}
280+
dropdownItems={dropdownItems}
281+
disabled={isDuplicating}
125282
onLabelClick= {() => viewOrEditForm(params.row._id, "edit")}
126283
/>
127-
)
128-
)
284+
);
285+
}
129286
},
130287
];
131288

@@ -197,6 +354,7 @@ const handlePageChange = (page) => {
197354
);
198355

199356
return (
357+
<>
200358
<ReusableTable
201359

202360
columns={columns}
@@ -209,7 +367,26 @@ const handlePageChange = (page) => {
209367
onPaginationModelChange={onPaginationModelChange}
210368
getRowId={(row) => row.id}
211369
/>
370+
<PromptModal
371+
show={showDeleteModal}
372+
onClose={handleCloseDelete}
373+
title={t("Delete Item")}
374+
message={deleteMessage}
375+
type="warning"
376+
primaryBtnText={t("Delete")}
377+
primaryBtnAction={handleDelete}
378+
primaryBtnDisable={disableDelete}
379+
secondaryBtnText={t("Cancel")}
380+
secondaryBtnAction={handleCloseDelete}
381+
/>
382+
</>
212383
);
213384
}
214385

386+
FormTable.propTypes = {
387+
isDuplicating: PropTypes.bool.isRequired,
388+
setIsDuplicating: PropTypes.func.isRequired,
389+
setDuplicateProgress: PropTypes.func.isRequired,
390+
};
391+
215392
export default FormTable;

forms-flow-web/src/routes/Design/Forms/List.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@ import {
4242
CustomSearch,
4343
CustomButton,
4444
useSuccessCountdown,
45-
V8CustomButton
45+
V8CustomButton,
46+
Alert,
47+
AlertVariant,
48+
CustomProgressBar
4649
} from "@formsflow/components";
4750
import { HelperServices } from '@formsflow/service';
4851
import { useMutation } from "react-query";
@@ -62,6 +65,8 @@ const List = React.memo((props) => {
6265
const [importLoader, setImportLoader] = useState(false);
6366
const [showSortModal, setShowSortModal] = useState(false);
6467
const { successState, startSuccessCountdown } = useSuccessCountdown();
68+
const [isDuplicating, setIsDuplicating] = useState(false);
69+
const [duplicateProgress, setDuplicateProgress] = useState(0);
6570

6671

6772
const handleFilterIconClick = () => {
@@ -345,7 +350,10 @@ const List = React.memo((props) => {
345350
};
346351
const renderTable = () => {
347352
if (createDesigns || viewDesigns) {
348-
return <FormTable />;
353+
return <FormTable
354+
isDuplicating={isDuplicating}
355+
setIsDuplicating={setIsDuplicating}
356+
setDuplicateProgress={setDuplicateProgress}/>;
349357
}
350358
if (createSubmissions) {
351359
return <ClientTable />;
@@ -364,7 +372,12 @@ const List = React.memo((props) => {
364372
) : (
365373
<>
366374
<div className="toast-section">
367-
{/* <p>Toast message</p> */}
375+
<Alert
376+
message={t("Duplicating the form")}
377+
variant={AlertVariant.FOCUS}
378+
isShowing={isDuplicating}
379+
rightContent={<CustomProgressBar progress={duplicateProgress} />}
380+
/>
368381
</div>
369382

370383
<div className="header-section-1">

0 commit comments

Comments
 (0)