Skip to content

Commit d42e97c

Browse files
authored
Merge pull request #583 from AOT-Technologies/Feature/FWF-4550-Application_List_API_in_MF
Feature/fwf 4550 application list API and UI in mf
2 parents 6efcd2d + 0863674 commit d42e97c

File tree

21 files changed

+9099
-8453
lines changed

21 files changed

+9099
-8453
lines changed

forms-flow-nav/src/Navbar.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,8 @@ const isUserManager = userRoles?.includes("manage_users");
437437
data-testid="dashboards-nav-link"
438438
className={`nav-menu-item py-md-3 px-0 mx-2 ${
439439
pathname.match(createURLPathMatchExp("metrics", baseUrl)) ||
440-
pathname.match(createURLPathMatchExp("insights", baseUrl))
440+
pathname.match(createURLPathMatchExp("insights", baseUrl)) ||
441+
pathname.match(createURLPathMatchExp("submissions", baseUrl))
441442
? "active"
442443
: ""
443444
}`}

forms-flow-nav/src/sidenav/Sidebar.jsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,8 @@ const Sidebar = React.memo(({ props, sidenavHeight="100%" }) => {
210210
},
211211
ANALYZE: {
212212
value: "analyze",
213-
supportedRoutes: ["metrics", "insights"],
214-
},
213+
supportedRoutes: ["metrics", "insights", "submissions"],
214+
},
215215
MANAGE: {
216216
value: "manage",
217217
supportedRoutes: ["admin/dashboard", "admin/roles", "admin/users"],
@@ -386,6 +386,10 @@ const Sidebar = React.memo(({ props, sidenavHeight="100%" }) => {
386386
{
387387
name: "Insights",
388388
path: "insights",
389+
},
390+
{
391+
name: "Submissions",
392+
path: "submissions",
389393
}
390394
]}
391395
subscribe={props.subscribe}

forms-flow-submissions/package-lock.json

Lines changed: 8517 additions & 8376 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

forms-flow-submissions/package.json

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
"@testing-library/jest-dom": "^5.14.1",
2727
"@testing-library/react": "^12.0.0",
2828
"@types/jest": "^29.5.0",
29+
"@types/react": "^19.1.6",
30+
"@types/react-dom": "^19.1.5",
31+
"@types/react-redux": "^7.1.34",
32+
"@types/redux-logger": "^3.0.13",
2933
"@types/testing-library__jest-dom": "^5.14.1",
3034
"babel-jest": "^27.0.6",
3135
"concurrently": "^6.2.1",
@@ -39,10 +43,10 @@
3943
"jest-cli": "^27.0.6",
4044
"prettier": "^2.3.2",
4145
"pretty-quick": "^3.1.1",
42-
"sass": "^1.57.1",
43-
"sass-loader": "^13.2.0",
46+
"sass": "^1.89.0",
47+
"sass-loader": "^12.6.0",
4448
"ts-config-single-spa": "^3.0.0",
45-
"typescript": "^4.3.5",
49+
"typescript": "^5.8.3",
4650
"webpack": "^5.75.0",
4751
"webpack-cli": "^4.8.0",
4852
"webpack-config-single-spa-react": "^4.0.0",
@@ -52,15 +56,20 @@
5256
"webpack-merge": "^5.8.0"
5357
},
5458
"dependencies": {
55-
"@types/react": "^17.0.19",
56-
"@types/react-dom": "^17.0.9",
59+
"@reduxjs/toolkit": "^2.8.2",
60+
"@tanstack/react-query": "^4.29.15",
5761
"@types/systemjs": "^6.1.1",
5862
"@types/webpack-env": "^1.16.2",
63+
"connected-react-router": "^6.9.2",
64+
"history": "^4.10.1",
65+
"i18next": "^25.2.1",
5966
"i18next-browser-languagedetector": "^8.0.4",
60-
"react-i18next": "^12.1.4",
61-
"react-router-dom": "5.1.2",
67+
"react-i18next": "^12.3.1",
68+
"react-redux": "^7.2.9",
69+
"react-router-dom": "5.1.2",
70+
"redux-logger": "^3.0.6",
6271
"single-spa": "^5.9.3",
6372
"single-spa-react": "^4.3.1"
6473
},
6574
"types": "dist/formsflow-submissions.d.ts"
66-
}
75+
}
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
import React, { useState, useRef, useCallback, useMemo } from "react";
2+
import { useDispatch, useSelector } from "react-redux";
3+
import { useQuery } from "@tanstack/react-query";
4+
import { useTranslation } from "react-i18next";
5+
import { push } from "connected-react-router";
6+
import { Submission } from "../types/submissions";
7+
import { getSubmissionList } from "../api/queryServices/analyzeSubmissionServices";
8+
import { formatDate } from "../helper/helper";
9+
import {
10+
setAnalyzeSubmissionSort,
11+
setAnalyzeSubmissionPage,
12+
setAnalyzeSubmissionLimit,
13+
} from "../actions/analyzeSubmissionActions";
14+
import {
15+
ReusableResizableTable,
16+
TableFooter,
17+
CustomButton,
18+
SortableHeader,
19+
} from "@formsflow/components";
20+
import { MULTITENANCY_ENABLED } from "../constants";
21+
22+
interface Column {
23+
name: string;
24+
width: number;
25+
sortKey: string;
26+
resizable?: boolean;
27+
}
28+
29+
const TaskSubmissionList: React.FC = () => {
30+
const { t } = useTranslation();
31+
const dispatch = useDispatch();
32+
const scrollWrapperRef = useRef<HTMLDivElement>(null);
33+
const sortParams = useSelector(
34+
(state: any) => state?.analyzeSubmission.analyzeSubmissionSortParams ?? {}
35+
);
36+
const limit = useSelector(
37+
(state: any) => state?.analyzeSubmission.limit ?? 10
38+
);
39+
const { page } = useSelector(
40+
(state: any) => state?.analyzeSubmission.page ?? 1
41+
);
42+
const tenantKey = useSelector(
43+
(state: any) => state.tenants?.tenantData?.tenantkey
44+
);
45+
const redirectUrl = MULTITENANCY_ENABLED ? `/tenant/${tenantKey}/` : "/";
46+
47+
const columns: Column[] = useMemo(
48+
() => [
49+
{ name: "Submission ID", sortKey: "id", width: 200, resizable: true },
50+
{ name: "Form Name", sortKey: "formName", width: 200, resizable: true },
51+
{ name: "Submitter", sortKey: "createdBy", width: 200, resizable: true },
52+
{
53+
name: "Submission Date",
54+
sortKey: "submissionDate",
55+
width: 180,
56+
resizable: true,
57+
},
58+
{
59+
name: "Status",
60+
sortKey: "applicationStatus",
61+
width: 160,
62+
resizable: true,
63+
},
64+
{ name: "", sortKey: "actions", width: 100 },
65+
],
66+
[]
67+
);
68+
69+
const activeSortKey = sortParams.activeKey;
70+
const activeSortOrder = sortParams?.[activeSortKey]?.sortOrder ?? "asc";
71+
72+
const { data } = useQuery({
73+
queryKey: ["submissions", page, limit, activeSortKey, activeSortOrder],
74+
queryFn: () =>
75+
getSubmissionList(limit, page, activeSortOrder, activeSortKey),
76+
keepPreviousData: true,
77+
staleTime: 0,
78+
});
79+
80+
const submissions = data?.submissions ?? [];
81+
const totalCount = data?.totalCount ?? 0;
82+
83+
const handleSort = useCallback(
84+
(key: string) => {
85+
const newOrder = sortParams[key]?.sortOrder === "asc" ? "desc" : "asc";
86+
const updatedSort = Object.fromEntries(
87+
Object.keys(sortParams).map((k) => [
88+
k,
89+
{ sortOrder: k === key ? newOrder : "asc" },
90+
])
91+
);
92+
dispatch(setAnalyzeSubmissionSort({ ...updatedSort, activeKey: key }));
93+
},
94+
[dispatch, sortParams]
95+
);
96+
97+
const handlePageChange = useCallback(
98+
(pageNumber) => {
99+
dispatch(setAnalyzeSubmissionPage(pageNumber));
100+
},
101+
[dispatch, limit]
102+
);
103+
104+
const renderRow = (row: Submission) => (
105+
<tr key={row.id}>
106+
<td>{row.id}</td>
107+
<td>{row.formName}</td>
108+
<td>{row.createdBy}</td>
109+
<td>{formatDate(row.created)}</td>
110+
<td>{row.applicationStatus}</td>
111+
<td>
112+
<CustomButton
113+
size="table-sm"
114+
variant="secondary"
115+
label={t("View")}
116+
onClick={() => dispatch(push(`${redirectUrl}application/${row.id}`))}
117+
dataTestId={`view-task-${row.id}`}
118+
ariaLabel={t("View details for task {{taskName}}", {
119+
taskName: row.formName ?? t("unnamed"),
120+
})}
121+
/>
122+
</td>
123+
</tr>
124+
);
125+
126+
const renderHeaderCell = useCallback(
127+
(
128+
column: Column,
129+
index: number,
130+
columnsLength: number,
131+
currentResizingColumn: any,
132+
handleMouseDown: (
133+
index: number,
134+
column: Column,
135+
e: React.MouseEvent
136+
) => void
137+
) => {
138+
const isLast = index === columnsLength - 1;
139+
const headerKey = column.sortKey || `col-${index}`;
140+
141+
return (
142+
<th
143+
key={`header-${headerKey}`}
144+
className="resizable-column"
145+
style={{ width: column.width }}
146+
data-testid={`column-header-${column.sortKey || "actions"}`}
147+
aria-label={column.name ? `${t(column.name)} ${t("column")}` : ""}
148+
>
149+
{!isLast && column.name ? (
150+
<SortableHeader
151+
columnKey={column.sortKey}
152+
title={t(column.name)}
153+
currentSort={sortParams}
154+
handleSort={handleSort}
155+
className="w-100 d-flex justify-content-between align-items-center"
156+
dataTestId={`sort-header-${column.sortKey}`}
157+
ariaLabel={t("Sort by {{columnName}}", {
158+
columnName: t(column.name),
159+
})}
160+
/>
161+
) : (
162+
column.name && t(column.name)
163+
)}
164+
{column.resizable && (
165+
<div
166+
className={`column-resizer ${
167+
currentResizingColumn?.sortKey === column.sortKey
168+
? "resizing"
169+
: ""
170+
}`}
171+
onMouseDown={(e) => handleMouseDown(index, column, e)}
172+
tabIndex={0}
173+
role="separator"
174+
aria-orientation="horizontal"
175+
data-testid={`column-resizer-${column.sortKey}`}
176+
aria-label={t("Resize {{columnName}} column", {
177+
columnName: t(column.name),
178+
})}
179+
/>
180+
)}
181+
</th>
182+
);
183+
},
184+
[t, sortParams, handleSort]
185+
);
186+
187+
const handleLimitChange = (newLimit: number) => {
188+
setAnalyzeSubmissionLimit(newLimit);
189+
setAnalyzeSubmissionPage(1);
190+
};
191+
192+
return (
193+
<div className="container-wrapper" data-testid="table-container-wrapper">
194+
<div className="table-outer-container">
195+
<div
196+
className="table-scroll-wrapper resizable-scroll"
197+
ref={scrollWrapperRef}
198+
>
199+
<div className="resizable-table-container">
200+
<ReusableResizableTable
201+
columns={columns}
202+
data={submissions}
203+
renderRow={renderRow}
204+
renderHeaderCell={renderHeaderCell}
205+
emptyMessage={t(
206+
"No submissions have been found. Try a different filter combination or contact your admin."
207+
)}
208+
onColumnResize={(newWidths) =>
209+
//TBD
210+
console.log("Column resized:", newWidths)
211+
}
212+
tableClassName="resizable-table"
213+
headerClassName="resizable-header"
214+
containerClassName="resizable-table-container"
215+
scrollWrapperClassName="table-scroll-wrapper resizable-scroll"
216+
dataTestId="task-resizable-table"
217+
ariaLabel={t("submissions data table with resizable columns")}
218+
/>
219+
</div>
220+
</div>
221+
</div>
222+
223+
{submissions.length > 0 && (
224+
<table className="custom-tables" data-testid="table-footer-container">
225+
<tfoot>
226+
<TableFooter
227+
limit={limit}
228+
activePage={page}
229+
totalCount={totalCount}
230+
handlePageChange={handlePageChange}
231+
onLimitChange={handleLimitChange}
232+
pageOptions={[
233+
{ text: "5", value: 5 },
234+
{ text: "25", value: 25 },
235+
{ text: "50", value: 50 },
236+
{ text: "100", value: 100 },
237+
{ text: "All", value: totalCount },
238+
]}
239+
dataTestId="submission-table-footer"
240+
ariaLabel={t("Table pagination controls")}
241+
pageSizeDataTestId="submission-page-size-selector"
242+
pageSizeAriaLabel={t("Select number of submissions per page")}
243+
paginationDataTestId="submission-pagination-controls"
244+
paginationAriaLabel={t("Navigate between submission pages")}
245+
/>
246+
</tfoot>
247+
</table>
248+
)}
249+
</div>
250+
);
251+
};
252+
253+
export default TaskSubmissionList;

forms-flow-submissions/src/Routes/SubmissionsListing/List.tsx

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default {
2+
UPDATE_SUBMISSION_SORT_PARAMS:"UPDATE_SUBMISSION_SORT_PARAMS",
3+
UPDATE_SUBMISSION_PAGE: "UPDATE_SUBMISSION_PAGE",
4+
UPDATE_SUBMISSION_LIMIT: "UPDATE_SUBMISSION_LIMIT"
5+
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import ACTION_CONSTANTS from "../constants/actionConstants";
2+
3+
export const setAnalyzeSubmissionSort = (data) => (dispatch) => {
4+
dispatch({
5+
type: ACTION_CONSTANTS.UPDATE_SUBMISSION_SORT_PARAMS,
6+
payload: data,
7+
});
8+
};
9+
10+
export const setAnalyzeSubmissionPage = (data) => (dispatch) => {
11+
dispatch({
12+
type: ACTION_CONSTANTS.UPDATE_SUBMISSION_PAGE,
13+
payload: data,
14+
});
15+
};
16+
export const setAnalyzeSubmissionLimit = (data) => (dispatch) => {
17+
dispatch({
18+
type: ACTION_CONSTANTS.UPDATE_SUBMISSION_LIMIT,
19+
payload: data,
20+
});
21+
};
22+
23+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
declare global {
2+
interface Window {
3+
_env_?: any;
4+
}
5+
}
6+
export const GRAPHQL_API =
7+
window._env_?.REACT_APP_API_GRAPHQL_URL ??
8+
(typeof process !== "undefined" ? process.env.REACT_APP_API_GRAPHQL_URL : undefined) ??
9+
"http://localhost:5500/queries";
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { GRAPHQL_API} from "./config";
2+
const API = {
3+
GRAPHQL_API: GRAPHQL_API,
4+
}
5+
6+
export default API;

0 commit comments

Comments
 (0)