Skip to content

Commit faa100f

Browse files
committed
FWF-5402[feature] - duplicate and delete case in form listing page
1 parent 4d85e4e commit faa100f

File tree

3 files changed

+199
-14
lines changed

3 files changed

+199
-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: 182 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,22 @@ import { DataGrid } from "@mui/x-data-grid";
44
import Paper from "@mui/material/Paper";
55
import { useTranslation } from "react-i18next";
66
import { push } from "connected-react-router";
7-
import { setBPMFormLimit, setBPMFormListPage, setBpmFormSort } from "../../../actions/formActions";
8-
import { resetFormProcessData } from "../../../apiManager/services/processServices";
7+
import { setBPMFormLimit, setBPMFormListPage, setBpmFormSort, setFormDeleteStatus } from "../../../actions/formActions";
8+
import { resetFormProcessData, unPublishForm, getApplicationCount, getProcessDetails } from "../../../apiManager/services/processServices";
99
import { fetchBPMFormList } from "../../../apiManager/services/bpmFormServices";
1010
import { setFormSearchLoading } from "../../../actions/checkListActions";
1111
import userRoles from "../../../constants/permissions";
12-
import { HelperServices,StyleServices } from "@formsflow/service";
12+
import { HelperServices, StyleServices } from "@formsflow/service";
1313
import { MULTITENANCY_ENABLED } from "../../../constants/constants";
14-
import { V8CustomButton,RefreshIcon,NewSortDownIcon,V8CustomDropdownButton } from "@formsflow/components";
14+
import { V8CustomButton, RefreshIcon, NewSortDownIcon, V8CustomDropdownButton, PromptModal } from "@formsflow/components";
15+
import { deleteForm } from "@aot-technologies/formio-react";
16+
import { fetchFormById } from "../../../apiManager/services/bpmFormServices";
17+
import { formCreate } from "../../../apiManager/services/FormServices";
18+
import { manipulatingFormData } from "../../../apiManager/services/formFormatterService";
19+
import _cloneDeep from "lodash/cloneDeep";
20+
import { toast } from "react-toastify";
1521

16-
17-
function FormTable() {
22+
function FormTable({ isDuplicating, setIsDuplicating, setDuplicateProgress }) {
1823
const dispatch = useDispatch();
1924
const tenantKey = useSelector(state => state.tenants?.tenantId);
2025
const bpmForms = useSelector(state => state.bpmForms);
@@ -26,10 +31,22 @@ function FormTable() {
2631
const searchFormLoading = useSelector(state => state.formCheckList.searchFormLoading);
2732
const isApplicationCountLoading = useSelector(state => state.process.isApplicationCountLoading);
2833
const searchText = useSelector(state => state.bpmForms.searchText);
34+
const applicationCount = useSelector((state) => state.process?.applicationCount);
35+
36+
// Get form access data from Redux store
37+
const formAccess = useSelector((state) => state.user?.formAccess || []);
38+
const submissionAccess = useSelector((state) => state.user?.submissionAccess || []);
39+
2940
const { createDesigns, viewDesigns } = userRoles();
3041
const { t } = useTranslation();
3142
const redirectUrl = MULTITENANCY_ENABLED ? `/tenant/${tenantKey}/` : "/";
3243
const iconColor = StyleServices.getCSSVariable('--ff-gray-medium-dark');
44+
45+
const [showDeleteModal, setShowDeleteModal] = React.useState(false);
46+
const [selectedRow, setSelectedRow] = React.useState(null);
47+
const [deleteMessage, setDeleteMessage] = React.useState("");
48+
const [disableDelete, setDisableDelete] = React.useState(false);
49+
const [isAppCountLoading, setIsAppCountLoading] = React.useState(false);
3350

3451
// Mapping between DataGrid field names and reducer sort keys
3552
const gridFieldToSortKey = {
@@ -46,6 +63,128 @@ function FormTable() {
4663
status: "status",
4764
};
4865

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

@@ -193,6 +350,7 @@ const viewOrEditForm = (formId, path) => {
193350

194351

195352
return (
353+
<>
196354
<Paper sx={{ height: {sm: 400, md: 510, lg: 665}, width: "100%" }}>
197355
<DataGrid
198356
disableColumnResize // disabed resizing
@@ -232,6 +390,19 @@ const viewOrEditForm = (formId, path) => {
232390
}}
233391
/>
234392
</Paper>
393+
<PromptModal
394+
show={showDeleteModal}
395+
onClose={handleCloseDelete}
396+
title={t("Delete Item")}
397+
message={deleteMessage}
398+
type="warning"
399+
primaryBtnText={t("Delete")}
400+
primaryBtnAction={handleDelete}
401+
primaryBtnDisable={disableDelete}
402+
secondaryBtnText={t("Cancel")}
403+
secondaryBtnAction={handleCloseDelete}
404+
/>
405+
</>
235406
);
236407
}
237408

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)