@@ -2,17 +2,22 @@ import * as React from "react";
22import { useSelector , useDispatch } from "react-redux" ;
33import { useTranslation } from "react-i18next" ;
44import { 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" ;
88import { setFormSearchLoading } from "../../../actions/checkListActions" ;
99import userRoles from "../../../constants/permissions" ;
1010import { HelperServices , StyleServices } from "@formsflow/service" ;
1111import { 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+
215392export default FormTable ;
0 commit comments