From 34f0eb564c1793da572c2813a70d39678f4a70c1 Mon Sep 17 00:00:00 2001 From: shuhaib s <95394061+shuhaib-aot@users.noreply.github.com> Date: Wed, 17 May 2023 12:25:53 +0530 Subject: [PATCH] App history new ui design (#45) * application history and request * View Submission modified * reviewed timeline updated * padding * UI updation * fixed style * replaced bundle keyword by BUNDLED_FORM variable --------- Co-authored-by: AbijithS-aot Co-authored-by: Ajay krishna --- forms-flow-web/src/actions/actionConstants.js | 1 + .../actions/taskApplicationHistoryActions.js | 7 ++ .../services/applicationAuditServices.js | 8 +- .../components/Application/Application.scss | 104 ++++++++++++++++++ .../Application/ApplicationTimeline.js | 102 +++++++++++++++++ .../components/Application/BundleHistory.js | 62 +++++++++++ .../components/Application/RequestStatus.js | 84 ++++++++++++++ .../components/Application/ViewApplication.js | 20 +++- .../components/Form/constants/FormTable.js | 4 - .../ServiceFlow/details/ServiceTaskDetails.js | 66 ++++++----- forms-flow-web/src/modules/bpmTaskReducer.js | 3 + 11 files changed, 416 insertions(+), 45 deletions(-) create mode 100644 forms-flow-web/src/components/Application/ApplicationTimeline.js create mode 100644 forms-flow-web/src/components/Application/BundleHistory.js create mode 100644 forms-flow-web/src/components/Application/RequestStatus.js diff --git a/forms-flow-web/src/actions/actionConstants.js b/forms-flow-web/src/actions/actionConstants.js index ae5a584097..809cb3add4 100644 --- a/forms-flow-web/src/actions/actionConstants.js +++ b/forms-flow-web/src/actions/actionConstants.js @@ -71,6 +71,7 @@ const ACTION_CONSTANTS = { METRICS_SUBMISSIONS_COUNT: "METRICS_SUBMISSIONS_COUNT", // Application history LIST_APPLICATION_HISTORY: "LIST_APPLICATION_HISTORY", + LIST_REQUESTS:"LIST_REQUESTS", APPLICATION_HISTORY_DETAIL: "APPLICATION_HISTORY_DETAIL", PROCESS_LIST: "PROCESS_LIST", DMN_PROCESS_LIST: "DMN_PROCESS_LIST", diff --git a/forms-flow-web/src/actions/taskApplicationHistoryActions.js b/forms-flow-web/src/actions/taskApplicationHistoryActions.js index 2c7fc0c15a..23b00d6bf0 100644 --- a/forms-flow-web/src/actions/taskApplicationHistoryActions.js +++ b/forms-flow-web/src/actions/taskApplicationHistoryActions.js @@ -19,6 +19,13 @@ export const setApplicationHistoryList = (data) => (dispatch) => { }); }; +export const setRequestList = (data) => (dispatch) => { + dispatch({ + type: ACTION_CONSTANTS.LIST_REQUESTS, + payload: data, + }); +}; + export const setUpdateHistoryLoader = (data) => (dispatch) => { dispatch({ type: ACTION_CONSTANTS.IS_HISTORY_LOADING, diff --git a/forms-flow-web/src/apiManager/services/applicationAuditServices.js b/forms-flow-web/src/apiManager/services/applicationAuditServices.js index c5f796a798..74fdb2aef8 100644 --- a/forms-flow-web/src/apiManager/services/applicationAuditServices.js +++ b/forms-flow-web/src/apiManager/services/applicationAuditServices.js @@ -3,6 +3,7 @@ import { setApplicationHistoryList, serviceActionError, setUpdateHistoryLoader, + setRequestList, } from "../../actions/taskApplicationHistoryActions"; import { StorageService, RequestService } from "@formsflow/service"; @@ -16,7 +17,6 @@ export const fetchApplicationAuditHistoryList = (applicationId, ...rest) => { "", applicationId ); - RequestService.httpGETRequest( apiUrlAppHistory, {}, @@ -29,7 +29,13 @@ export const fetchApplicationAuditHistoryList = (applicationId, ...rest) => { let data = applications.map((app) => { return { ...app }; }); + const requests = res.data.requests; + let requestData = requests.map((app) => { + return { ...app }; + }); + dispatch(setApplicationHistoryList(data)); + dispatch(setRequestList(requestData)); dispatch(setUpdateHistoryLoader(false)); done(null, res.data); } else { diff --git a/forms-flow-web/src/components/Application/Application.scss b/forms-flow-web/src/components/Application/Application.scss index 5eccf7fed5..da60ffa532 100644 --- a/forms-flow-web/src/components/Application/Application.scss +++ b/forms-flow-web/src/components/Application/Application.scss @@ -25,3 +25,107 @@ min-height: 450px; overflow: auto; } + + .timeline { + list-style: none; + padding: 0; + margin: 50px 0; + position: relative; + } + + .timeline:before { + content: ''; + height: 80%; + width: 4px; + background: #c5c5c5; + position: absolute; + top: 0; + left: 50%; + margin-left: -2px; + } + + .timeline li { + margin-bottom: 50px; + position: relative; + } + + .timeline li:before { + content: ''; + height: 20px; + width: 20px; + border-radius: 50%; + background: #fff; + border: 4px solid #c5c5c5; + position: absolute; + top: 0; + left: 50%; + margin-left: -12px; + z-index: 1; + } + + .timeline li .timeline-circle { + display: flex; + align-items: center; + justify-content: center; + height: 24px; + width: 24px; + border-radius: 50%; + background: #c5c5c5; + position: absolute; + top: 0; + left: 50%; + margin-left: -12px; + z-index: 2; + } + + .timeline li .timeline-circle svg { + color: #fff; + font-size: 16px; + } + + .timeline li:last-child { + margin-bottom: 0; + } + +.history-panel{ + display: flex; + gap:15px; + margin-inline: 2rem; + margin-top: 5rem; +} + +.circles-container { + display: flex; + align-items: center; + justify-content: center; + height: 100px; /* set the height of the parent container */ + position: relative; /* add position relative to the parent container */ + } + + .circle-icon { + width: 25px; + height: 25px; + border-radius: 50%; + background-color: #ccc; + display: flex; + align-items: center; + justify-content: center; + } + + .fa-times { + color: red; + } + + .fa-check{ + color: #12782C; + } + + .line { + flex: 1; + height: 45px; + width: 1px; + margin-left: 12px; + border-right: 3px solid #CCCCCC; + } + + \ No newline at end of file diff --git a/forms-flow-web/src/components/Application/ApplicationTimeline.js b/forms-flow-web/src/components/Application/ApplicationTimeline.js new file mode 100644 index 0000000000..5228bf49b3 --- /dev/null +++ b/forms-flow-web/src/components/Application/ApplicationTimeline.js @@ -0,0 +1,102 @@ +import React from "react"; +import { Typography } from "@material-ui/core"; +import moment from "moment"; +import { MULTITENANCY_ENABLED } from "../../constants/constants"; +import { useSelector } from "react-redux"; + +function ApplicationTimeline({ applicationHistory }) { + const tenantKey = useSelector((state) => state.tenants?.tenantId); + const formName = useSelector((state) => state.form.form.title); + const redirectUrl = MULTITENANCY_ENABLED ? `/tenant/${tenantKey}/` : "/"; + function formatDate(dateString) { + let dateObj = new Date(dateString); + return moment(dateObj).format("DD/MM/YYYY"); + } + + function formatTime(dateString) { + let dateObj = new Date(dateString); + return moment(dateObj).format("h:mm:ssa"); + } + + const ptag = { + margin: "0", + }; + const postiveStatus = ["New", "Submitted", "Completed","Reviewed"]; + const negativeStatus = ["Rejected", "Returned"]; + + return ( + <> +
+ {applicationHistory.map((e, index) => ( + <> +
+ {postiveStatus.includes( + e.applicationStatus || e.requestStatus + ) ? ( + + ) : negativeStatus.includes( + e.applicationStatus || e.requestStatus + ) ? ( + + ) : ( + "" + )} +
+ {index !== applicationHistory.length - 1 && ( +
+ )} + + ))} +
+
+ {applicationHistory.map((e, index) => { + return ( +
+
+ + {" "} + + {formatDate(e.created)} + +

+ {formatTime(e.created)} +
+
+
+ + + {e.applicationStatus || e.requestStatus} + {" "} + {formName}{" "} + + {e.submittedBy} + + +
+ +
+ ); + })} +
+ + ); +} + +export default ApplicationTimeline; diff --git a/forms-flow-web/src/components/Application/BundleHistory.js b/forms-flow-web/src/components/Application/BundleHistory.js new file mode 100644 index 0000000000..1b9ece67ed --- /dev/null +++ b/forms-flow-web/src/components/Application/BundleHistory.js @@ -0,0 +1,62 @@ +import React, { useEffect, useState } from "react"; +import { Tab, Tabs } from "@material-ui/core"; +import ApplicationTimeline from "./ApplicationTimeline"; +import RequestStatus from "./RequestStatus"; +import { fetchApplicationAuditHistoryList } from "../../apiManager/services/applicationAuditServices"; +import { useDispatch, useSelector } from "react-redux"; + +const BundleHistory = ({ applicationId }) => { + const dispatch = useDispatch(); + const applicationHistory = useSelector((state) => state.bpmTasks.appHistory); + const requests = useSelector((state) => state.bpmTasks?.request); + useEffect(() => { + dispatch(fetchApplicationAuditHistoryList(applicationId)); + }, []); + + const [activeTab, setActiveTab] = useState(0); + + const handleChange = (event, newValue) => { + setActiveTab(newValue); + }; + + return ( +
+
+
+ + + + +
+
+ {activeTab == 0 ? ( + + ) : requests.length ? ( + + ) : ( + "" + )} +
+ ); +}; + +export default BundleHistory; diff --git a/forms-flow-web/src/components/Application/RequestStatus.js b/forms-flow-web/src/components/Application/RequestStatus.js new file mode 100644 index 0000000000..2e0536aed9 --- /dev/null +++ b/forms-flow-web/src/components/Application/RequestStatus.js @@ -0,0 +1,84 @@ +import React from "react"; +import Button from "@material-ui/core/Button"; +import { useState } from "react"; +import ApplicationTimeline from "./ApplicationTimeline"; + +function RequestStatus({ requests }) { + const [requestItems, setRequestItems] = useState([]); + const [selectedRow, setSelectedRow] = useState(null); + + + const arrow = { + paddingInline: "120px", + }; + const Statusbutton = { + padding: "0px", + marginTop: "14px", + backgroundColor: "#585858", + color: "white", + borderRadius: "10px", + paddingInline: "14px", + }; + + const handleRowExpansion = (items, index) => { + setSelectedRow(index === selectedRow ? null : index); + setRequestItems(items); + }; +const timeLine = ()=>{ + return( + + +
+ +
+ + + ); +}; + + return ( + + + + + + + + + + {requests?.map((e, index) => { + return ( + <> + + + + + + {selectedRow === index && <>{timeLine()}} + + ); + })} + + +
+ Request Name + + Status +
{e?.requestType} + handleRowExpansion(e?.items, index)} + > +
+ ); +} + +export default RequestStatus; diff --git a/forms-flow-web/src/components/Application/ViewApplication.js b/forms-flow-web/src/components/Application/ViewApplication.js index 6819edc5ba..627fa6bec5 100644 --- a/forms-flow-web/src/components/Application/ViewApplication.js +++ b/forms-flow-web/src/components/Application/ViewApplication.js @@ -16,16 +16,21 @@ import History from "./ApplicationHistory"; import View from "../Form/Item/Submission/Item/View"; import { getForm, getSubmission } from "react-formio"; import NotFound from "../NotFound"; -import { Translation,useTranslation } from "react-i18next"; -import { CUSTOM_SUBMISSION_URL,CUSTOM_SUBMISSION_ENABLE, MULTITENANCY_ENABLED } from "../../constants/constants"; +import { Translation, useTranslation } from "react-i18next"; +import { + CUSTOM_SUBMISSION_URL, + CUSTOM_SUBMISSION_ENABLE, + MULTITENANCY_ENABLED, +} from "../../constants/constants"; import { fetchAllBpmProcesses } from "../../apiManager/services/processServices"; import { getCustomSubmission } from "../../apiManager/services/FormServices"; import { setBundleSubmissionData } from "../../actions/bundleActions"; import BundleView from "../Bundle/item/submission/View"; +import BundleHistory from "./BundleHistory"; import { BUNDLED_FORM } from "../../constants/applicationConstants"; const ViewApplication = React.memo(() => { - const {t} = useTranslation(); + const { t } = useTranslation(); const { applicationId } = useParams(); const applicationDetail = useSelector( (state) => state.applications.applicationDetail @@ -48,7 +53,7 @@ const ViewApplication = React.memo(() => { dispatch( getApplicationById(applicationId, (err, res) => { if (!err) { - if (res.submissionId && res.formId) { + if (res.submissionId && res.formId) { dispatch(getForm("form", res.formId)); if(CUSTOM_SUBMISSION_URL && CUSTOM_SUBMISSION_ENABLE){ dispatch(getCustomSubmission(res.submissionId,res.formId,(err,data)=>{ @@ -124,13 +129,16 @@ const ViewApplication = React.memo(() => { { applicationDetail.formType === BUNDLED_FORM ? : } - {(t) => t("History")}} > - + {applicationDetail.formType === BUNDLED_FORM ? ( + + ) : ( + + )} state.bpmForms.searchText); - // const [openRows, setOpenRows] = useState([]); - // const [show,setShow] = useState(false); - // const [expand,setExpand] = useState(false); const [search, setSearch] = useState(searchText || ""); const [bundleData, setBundleData] = useState([]); const [selectedRow, setSelectedRow] = useState(null); @@ -70,7 +67,6 @@ function FormTable() { ]; const updateSort = (updatedSort) => { - // dispatch(setBpmFormLoading(false)); dispatch(setBPMFormListSort(updatedSort)); dispatch(setBPMFormListPage(1)); }; diff --git a/forms-flow-web/src/components/ServiceFlow/details/ServiceTaskDetails.js b/forms-flow-web/src/components/ServiceFlow/details/ServiceTaskDetails.js index 9ad7cc86b5..b90c1d6574 100644 --- a/forms-flow-web/src/components/ServiceFlow/details/ServiceTaskDetails.js +++ b/forms-flow-web/src/components/ServiceFlow/details/ServiceTaskDetails.js @@ -44,6 +44,7 @@ import { import { getCustomSubmission } from "../../../apiManager/services/FormServices"; import { getFormioRoleIds } from "../../../apiManager/services/userservices"; import { setBundleSubmissionData } from "../../../actions/bundleActions"; +import BundleHistory from "../../Application/BundleHistory"; import { BUNDLED_FORM } from "../../../constants/applicationConstants"; const ServiceFlowTaskDetails = React.memo(() => { @@ -109,7 +110,7 @@ const ServiceFlowTaskDetails = React.memo(() => { }, [task?.processInstanceId]); const getFormSubmissionData = useCallback( - (formUrl,taskDetail) => { + (formUrl, taskDetail) => { const { formId, submissionId } = getFormIdSubmissionIdFromURL(formUrl); Formio.clearCache(); dispatch(resetFormData("form")); @@ -159,14 +160,14 @@ const ServiceFlowTaskDetails = React.memo(() => { useEffect(() => { if (task?.formUrl) { - getFormSubmissionData(task?.formUrl,task); + getFormSubmissionData(task?.formUrl, task); } }, [task?.formUrl, dispatch, getFormSubmissionData]); useEffect(() => { if (task?.formUrl && taskFormSubmissionReload) { dispatch(setFormSubmissionLoading(false)); - getFormSubmissionData(task?.formUrl,task); + getFormSubmissionData(task?.formUrl, task); dispatch(reloadTaskFormSubmission(false)); } }, [ @@ -190,17 +191,16 @@ const ServiceFlowTaskDetails = React.memo(() => { getBPMTaskDetail(task.id, (err, taskDetail) => { if (!err) { dispatch(setFormSubmissionLoading(true)); - getFormSubmissionData(taskDetail?.formUrl,taskDetail); + getFormSubmissionData(taskDetail?.formUrl, taskDetail); } }) ); // Refresh the Task Selected dispatch(getBPMGroups(task.id)); dispatch(fetchServiceTaskList(selectedFilter.id, firstResult, reqData)); //Refreshes the Tasks - } }; - const onCustomEventCallBack = (customEvent) => { + const onCustomEventCallBack = (customEvent) => { switch (customEvent.type) { case CUSTOM_EVENT_TYPE.RELOAD_TASKS: reloadTasks(); @@ -216,7 +216,7 @@ const ServiceFlowTaskDetails = React.memo(() => { } }; - const onFormSubmitCallback = (actionType = "") => { + const onFormSubmitCallback = (actionType = "") => { if (bpmTaskId) { dispatch(setBPMTaskDetailLoader(true)); const { formId, submissionId } = getFormIdSubmissionIdFromURL( @@ -289,35 +289,33 @@ const ServiceFlowTaskDetails = React.memo(() => { } ) : ( - ({ - ...base, - background: "rgba(0, 0, 0, 0.2)", - cursor: "not-allowed !important", - }), - }} - > - { - task?.assignee === currentUser ? ( - - ) : ( - - ) - } - - ) - } - - - + ({ + ...base, + background: "rgba(0, 0, 0, 0.2)", + cursor: "not-allowed !important", + }), + }} + > + {task?.assignee === currentUser ? ( + + ) : ( + + )} + + )} - + {task?.formType === "bundle" ? ( + + ) : ( + + )}
diff --git a/forms-flow-web/src/modules/bpmTaskReducer.js b/forms-flow-web/src/modules/bpmTaskReducer.js index 2325099da8..49e410b543 100644 --- a/forms-flow-web/src/modules/bpmTaskReducer.js +++ b/forms-flow-web/src/modules/bpmTaskReducer.js @@ -14,6 +14,7 @@ const initialState = { taskDetail: null, isTaskUpdating: false, appHistory: [], + request:[], isHistoryListLoading: true, isTaskDetailLoading: true, isTaskDetailUpdating: false, @@ -62,6 +63,8 @@ const bpmTasks = (state = initialState, action) => { return { ...state, isHistoryListLoading: action.payload }; case ACTION_CONSTANTS.LIST_APPLICATION_HISTORY: return { ...state, appHistory: action.payload }; + case ACTION_CONSTANTS.LIST_REQUESTS: + return { ...state, request: action.payload }; case ACTION_CONSTANTS.BPM_FILTER_LIST: return { ...state, filterList: sortByPriorityList(action.payload) }; case ACTION_CONSTANTS.IS_BPM_FILTERS_LOADING: