Skip to content

Commit 1d39c79

Browse files
authored
Merge pull request #531 from fahad-aot/feature/fwf-4431-Add-form-selection-modal
Feature/fwf 4431 add form selection modal
2 parents a1f8307 + 7619628 commit 1d39c79

File tree

11 files changed

+448
-132
lines changed

11 files changed

+448
-132
lines changed

forms-flow-components/src/components/CustomComponents/Search.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
55

66

77
interface CustomSearchProps {
8-
searchLoading: boolean;
8+
searchLoading?: boolean;
99
handleClearSearch: () => void;
1010
search: string;
1111
setSearch: (value: string) => void;

forms-flow-review/src/Routes/TaskListing.tsx

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useEffect, useCallback, useState } from "react";
22
import SocketIOService from "../services/SocketIOService";
3-
import { CustomButton } from '@formsflow/components';
3+
import { CustomButton } from "@formsflow/components";
44
import TaskFilterModal from "../components/TaskFilterModal";
55

66
interface SocketUpdateParams {
@@ -10,11 +10,13 @@ interface SocketUpdateParams {
1010
}
1111

1212
const TaskList = () => {
13-
const [showTaskFilterModal, setShowTaskFilterModal] = useState(false);
13+
14+
const [showTaskFilterModal, setShowTaskFilterModal] = useState(false);
15+
1416

1517
const handleToggleFilterModal = () => {
16-
setShowTaskFilterModal(prevState => !prevState);
17-
};
18+
setShowTaskFilterModal((prevState) => !prevState);
19+
};
1820

1921
const SocketIOCallback = useCallback(
2022
({ refreshedTaskId, forceReload, isUpdateEvent }: SocketUpdateParams) => {
@@ -51,7 +53,7 @@ const TaskList = () => {
5153

5254
return (
5355
<div>
54-
<h1>Hello World</h1>
56+
<h1> Hello world</h1>
5557
<CustomButton
5658
variant="secondary"
5759
size="md"
@@ -65,6 +67,5 @@ const TaskList = () => {
6567
onClose={handleToggleFilterModal}
6668
/>
6769
</div>
68-
);
69-
};
70-
export default TaskList;
70+
);
71+
};export default TaskList;

forms-flow-review/src/api/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ declare global {
44
}
55
}
66
export const WEB_BASE_URL = window._env_?.REACT_APP_WEB_BASE_URL
7+
export const API_PROJECT_URL = window._env_?.REACT_APP_API_PROJECT_URL
78
export const KEYCLOAK_URL = window._env_?.REACT_APP_KEYCLOAK_URL
89
export const KEYCLOAK_URL_AUTH = `${KEYCLOAK_URL}/auth`
910
export const KEYCLOAK_URL_REALM = window._env_?.REACT_APP_KEYCLOAK_URL_REALM
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import { WEB_BASE_URL, BPM_URL, BPM_BASE_URL_EXT } from "./config";
2-
3-
1+
import { WEB_BASE_URL, BPM_URL, BPM_BASE_URL_EXT , API_PROJECT_URL} from "./config";
42

53
const API = {
6-
BPM_BASE_URL_SOCKET_IO: `${BPM_URL}/forms-flow-bpm-socket`,
4+
BPM_BASE_URL_SOCKET_IO :`${BPM_URL}/forms-flow-bpm-socket`,
5+
FORM: `${WEB_BASE_URL}/form`,
6+
FORM_PROCESSES: `${WEB_BASE_URL}/form/formid`,
77
GET_API_USER_LIST: `${WEB_BASE_URL}/user`,
88
GET_BPM_PROCESS_LIST: `${BPM_BASE_URL_EXT}/v1/process-definition`,
99
USER_ROLES: `${WEB_BASE_URL}/roles`,
1010
GET_BPM_TASK_FILTERS: `${BPM_BASE_URL_EXT}/v1/task-filters`,
1111
GET_FILTERS: `${WEB_BASE_URL}/filter`,
1212
UPDATE_DEFAULT_FILTER: `${WEB_BASE_URL}/user/default-filter`,
13-
FORM_PROCESSES: `${WEB_BASE_URL}/form/formid`
14-
}
13+
GET_FORM_BY_ID: `${API_PROJECT_URL}/form`,
14+
}
1515

1616
export default API;

forms-flow-review/src/api/services/filterServices.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ export const fetchUserList = (...rest) => {
9191
});
9292
};
9393
};
94-
94+
95+
9596
export const fetchBPMTaskCount = (data) => {
9697
return RequestService.httpPOSTRequest(
9798
`${API.GET_BPM_TASK_FILTERS}/count`,
@@ -142,4 +143,20 @@ export const fetchUserList = (...rest) => {
142143
export const fetchTaskVariables = (formId) =>{
143144
let url = `${API.FORM_PROCESSES}/${formId}`;
144145
return RequestService.httpGETRequest(url);
145-
};
146+
};
147+
148+
export const fetchAllForms = ()=>{
149+
//activeForms means published forms only : status = Active
150+
return RequestService.httpGETRequest(`${API.FORM}?activeForms=true`);
151+
};
152+
153+
154+
export const fetchFormById = (id) => {
155+
let formioToken = sessionStorage.getItem("formioToken");
156+
let token = formioToken ? { "x-jwt-token": formioToken } : {};
157+
return RequestService.httpGETRequest(`${API.GET_FORM_BY_ID}/${id}`, {}, "", false, {
158+
...token
159+
});
160+
161+
};
162+
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import React, { useEffect, useState } from "react";
2+
import Modal from "react-bootstrap/Modal";
3+
import { useTranslation } from "react-i18next";
4+
import { CloseIcon, CustomSearch, CustomButton } from "@formsflow/components";
5+
import { Form } from "@aot-technologies/formio-react";
6+
import { fetchAllForms, fetchFormById } from "../api/services/filterServices";
7+
interface FormSelectionModalProps {
8+
showModal: boolean;
9+
onClose: () => void;
10+
onSelectForm: ({formId,formName}) => void;
11+
}
12+
13+
export const FormSelectionModal: React.FC<FormSelectionModalProps> = React.memo(
14+
({ showModal, onClose, onSelectForm }) => {
15+
16+
const { t } = useTranslation();
17+
const [searchFormName, setSearchFormName] = useState<string>("");
18+
const [loadingForm, setLoadingForm] = useState<boolean>(false);
19+
const [selectedForm, setSelectedForm] = useState({ formId: "", formName: "" });
20+
const [loading, setLoading] = useState(false);
21+
const [form, setForm] = useState<any>(null);
22+
const [formNames, setFormNames] = useState({ data: [], isLoading: true });
23+
const [filteredFormNames, setFilteredFormNames] = useState<any[]>(
24+
formNames.data
25+
);
26+
27+
useEffect(() => {
28+
handleFormNameSearch();
29+
}, [searchFormName]);
30+
useEffect(() => {
31+
fetchAllForms().then((res) => {
32+
const data = res.data?.forms ?? [];
33+
setFormNames({
34+
data: data.map((i) => ({ formName: i.formName, formId: i.formId })),
35+
isLoading: false,
36+
});
37+
});
38+
}, []);
39+
useEffect (()=>{
40+
if(formNames.data.length > 0) {
41+
setSelectedForm( ({ formId: formNames.data[0].formId, formName: formNames.data[0].formName }));
42+
}
43+
},[formNames.data])
44+
const handleFormNameSearch = () => {
45+
setLoadingForm(true);
46+
if (searchFormName?.trim()) {
47+
setFilteredFormNames(
48+
formNames?.data.filter((i) =>
49+
i.formName
50+
.toLowerCase()
51+
?.includes(searchFormName?.trim()?.toLowerCase())
52+
)
53+
);
54+
}
55+
setLoadingForm(false);
56+
};
57+
const handleClearSearch = () => {
58+
setSearchFormName("");
59+
setFilteredFormNames(formNames.data);
60+
};
61+
const getFormOptions = () => {
62+
return searchFormName ? filteredFormNames : formNames.data;
63+
};
64+
useEffect(() => {
65+
if (selectedForm.formId) {
66+
setLoading(true);
67+
// Fetch form data by ID
68+
fetchFormById(selectedForm.formId)
69+
.then((res) => {
70+
if (res.data) {
71+
const { data } = res;
72+
setForm(data);
73+
}
74+
})
75+
.catch((err) => {
76+
console.error(
77+
"Error fetching form data:",
78+
err.response?.data ?? err.message
79+
);
80+
})
81+
.finally(() => {
82+
setLoading(false);
83+
});
84+
}
85+
}, [selectedForm]);
86+
87+
88+
function renderFormList() {
89+
if (loadingForm || formNames.isLoading) {
90+
return <div className="form-selection-spinner"></div>;
91+
}
92+
93+
const formOptions = getFormOptions();
94+
if (formOptions.length > 0) {
95+
return formOptions.map((item) => (
96+
<button
97+
className={`form-list-item button-as-div ${
98+
selectedForm.formId === item.formId ? "active-form" : ""
99+
}`}
100+
onClick={() => setSelectedForm({ formId: item.formId, formName: item.formName })}
101+
key={item.formId}
102+
data-testid="form-selection-modal-form-item"
103+
>
104+
{item.formName}
105+
</button>
106+
));
107+
}
108+
109+
return <span className="nothing-found-text">{t("Nothing is found. Please try again.")}</span>;
110+
}
111+
112+
return (
113+
<Modal
114+
show={showModal}
115+
centered
116+
size="lg"
117+
className="form-selection-modal">
118+
<Modal.Header className="form-selection-header">
119+
<Modal.Title> {t("Select a Form")} </Modal.Title>
120+
<div className="d-flex align-items-center">
121+
<CloseIcon
122+
onClick={onClose}
123+
data-testid="form-selection-modal-close-icon"
124+
/>
125+
</div>
126+
</Modal.Header>
127+
<Modal.Body className="form-selection-modal-body">
128+
<div className="form-selection-left">
129+
<div className="search-form">
130+
<CustomSearch
131+
placeholder={t("Search ...")}
132+
search={searchFormName}
133+
setSearch={setSearchFormName}
134+
handleClearSearch={handleClearSearch}
135+
handleSearch={handleFormNameSearch}
136+
dataTestId="form-custom-search"
137+
/>
138+
</div>
139+
<div className="form-list">
140+
{renderFormList()}
141+
</div>
142+
</div>
143+
<div className="form-selection-right">
144+
<div className="form-selection-preview custom-scroll">
145+
{loading ? (
146+
<div className="form-selection-spinner"></div>
147+
) : (
148+
<Form
149+
form={form}
150+
options={{
151+
noAlerts: true,
152+
viewAsHtml: true,
153+
readOnly: true,
154+
showHiddenFields:true
155+
} as any}
156+
157+
/>
158+
)}
159+
</div>
160+
<div className="form-select-btn">
161+
<CustomButton
162+
onClick={() => {
163+
onSelectForm(selectedForm);
164+
}}
165+
variant="primary"
166+
label={t("Select This Form")}
167+
size="md"
168+
dataTestid="select-form-btn"
169+
/>
170+
</div>
171+
</div>
172+
</Modal.Body>
173+
</Modal>
174+
);
175+
}
176+
);

0 commit comments

Comments
 (0)