From 012410c92d0ae9334ee0004d334e2fe49ff5cea4 Mon Sep 17 00:00:00 2001 From: Nishit Suwal <81785002+NSUWAL123@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:26:25 +0545 Subject: [PATCH] fix(frontend): after backend refactoring to events (#1844) * fix(dialogTaskActions): use task_event enum to compare btnId * fix(submissionTable): on task validate, dispatch GOOD event * fix(featureSelectionPopup): pass geojsonStyles as args * fix(project): error toast display on error * feat(submission): api integration for mappedVsValidated task chart * fix(projectSubmissions: empty dependency add on api call) --- src/frontend/src/api/Project.ts | 8 ++ src/frontend/src/api/SubmissionService.ts | 17 +++ .../src/components/DialogTaskActions.tsx | 5 +- .../FeatureSelectionPopup.tsx | 3 +- .../SubmissionsInfographics.tsx | 100 ++---------------- .../ProjectSubmissions/SubmissionsTable.tsx | 2 +- .../src/store/slices/SubmissionSlice.ts | 12 +++ src/frontend/src/store/types/ISubmissions.ts | 9 ++ src/frontend/src/views/ProjectSubmissions.tsx | 8 +- 9 files changed, 66 insertions(+), 98 deletions(-) diff --git a/src/frontend/src/api/Project.ts b/src/frontend/src/api/Project.ts index a0c6cf4488..f3ac9648db 100755 --- a/src/frontend/src/api/Project.ts +++ b/src/frontend/src/api/Project.ts @@ -329,6 +329,14 @@ export const UpdateEntityState = (url: string, payload: { entity_id: string; sta dispatch(ProjectActions.UpdateEntityState(response.data)); dispatch(ProjectActions.UpdateEntityStateLoading(false)); } catch (error) { + dispatch( + CommonActions.SetSnackBar({ + open: true, + message: error?.response?.data?.detail || 'Failed to update entity state.', + variant: 'error', + duration: 2000, + }), + ); dispatch(ProjectActions.UpdateEntityStateLoading(false)); } }; diff --git a/src/frontend/src/api/SubmissionService.ts b/src/frontend/src/api/SubmissionService.ts index dc9b58ad31..18928f1509 100644 --- a/src/frontend/src/api/SubmissionService.ts +++ b/src/frontend/src/api/SubmissionService.ts @@ -104,3 +104,20 @@ export const UpdateReviewStateService: Function = (url: string, payload: object) await UpdateReviewState(url); }; }; + +export const MappedVsValidatedTaskService: Function = (url: string) => { + return async (dispatch) => { + const MappedVsValidatedTask = async (url: string) => { + try { + dispatch(SubmissionActions.SetMappedVsValidatedTaskLoading(true)); + const response = await CoreModules.axios.get(url); + dispatch(SubmissionActions.SetMappedVsValidatedTask(response.data)); + dispatch(SubmissionActions.SetMappedVsValidatedTaskLoading(false)); + } catch (error) { + dispatch(SubmissionActions.SetMappedVsValidatedTaskLoading(false)); + } + }; + + await MappedVsValidatedTask(url); + }; +}; diff --git a/src/frontend/src/components/DialogTaskActions.tsx b/src/frontend/src/components/DialogTaskActions.tsx index 18006400b3..1c03bc0899 100755 --- a/src/frontend/src/components/DialogTaskActions.tsx +++ b/src/frontend/src/components/DialogTaskActions.tsx @@ -4,7 +4,7 @@ import { CreateTaskEvent } from '@/api/TaskEvent'; import MapStyles from '@/hooks/MapStyles'; import CoreModules from '@/shared/CoreModules'; import { CommonActions } from '@/store/slices/CommonSlice'; -import { task_event as taskEventEnum, task_state as taskStateEnum } from '@/types/enums'; +import { task_event as taskEventEnum, task_state as taskStateEnum, task_event } from '@/types/enums'; import Button from '@/components/common/Button'; import { useNavigate } from 'react-router-dom'; import { GetProjectTaskActivity } from '@/api/Project'; @@ -109,8 +109,7 @@ export default function Dialog({ taskId, feature }: dialogPropType) { feature, ), ); - if (btnId === taskStateEnum.LOCKED_FOR_VALIDATION) - navigate(`/project-submissions/${params.id}?tab=table&task_id=${taskId}`); + if (btnId === task_event.VALIDATE) navigate(`/project-submissions/${params.id}?tab=table&task_id=${taskId}`); } else { dispatch( CommonActions.SetSnackBar({ diff --git a/src/frontend/src/components/ProjectDetailsV2/FeatureSelectionPopup.tsx b/src/frontend/src/components/ProjectDetailsV2/FeatureSelectionPopup.tsx index 8e7011a51f..068b0332a0 100644 --- a/src/frontend/src/components/ProjectDetailsV2/FeatureSelectionPopup.tsx +++ b/src/frontend/src/components/ProjectDetailsV2/FeatureSelectionPopup.tsx @@ -40,7 +40,6 @@ const TaskFeatureSelectionPopup = ({ featureProperties, taskId, taskFeature }: T return task?.id == taskId; })?.[0], }; - const geoStyle = geojsonStyles[taskStateEnum.LOCKED_FOR_MAPPING]; const entity = entityOsmMap.find((x) => x.osm_id === featureProperties?.osm_id); useEffect(() => { @@ -142,7 +141,7 @@ const TaskFeatureSelectionPopup = ({ featureProperties, taskId, taskFeature }: T taskId.toString(), authDetails, { project_id: currentProjectId }, - geoStyle, + geojsonStyles, taskFeature, ), ); diff --git a/src/frontend/src/components/ProjectSubmissions/SubmissionsInfographics.tsx b/src/frontend/src/components/ProjectSubmissions/SubmissionsInfographics.tsx index ece8ba84f4..0a78fcdf52 100644 --- a/src/frontend/src/components/ProjectSubmissions/SubmissionsInfographics.tsx +++ b/src/frontend/src/components/ProjectSubmissions/SubmissionsInfographics.tsx @@ -8,9 +8,8 @@ import CoreModules from '@/shared/CoreModules'; import InfographicsCard from '@/components/ProjectSubmissions/InfographicsCard'; import useDocumentTitle from '@/utilfunctions/useDocumentTitle'; import { useAppSelector } from '@/types/reduxTypes'; -import { taskHistoryTypes } from '@/models/project/projectModel'; -import { formSubmissionType, validatedMappedType } from '@/models/submission/submissionModel'; -import { dateNDaysAgo, generateLast30Days, getMonthDate } from '@/utilfunctions/commonUtils'; +import { formSubmissionType } from '@/models/submission/submissionModel'; +import { dateNDaysAgo, getMonthDate } from '@/utilfunctions/commonUtils'; const SubmissionsInfographics = ({ toggleView, entities }) => { useDocumentTitle('Submission Infographics'); @@ -19,98 +18,17 @@ const SubmissionsInfographics = ({ toggleView, entities }) => { const totalContributorsRef = useRef(null); const plannedVsActualRef = useRef(null); - const params = CoreModules.useParams(); - const projectId = params.projectId; - const submissionContributorsData = useAppSelector((state) => state.submission.submissionContributors); const submissionContributorsLoading = useAppSelector((state) => state.submission.submissionContributorsLoading); const [submissionProjection, setSubmissionProjection] = useState<10 | 30>(10); const taskInfo = useAppSelector((state) => state.task.taskInfo); const taskLoading = useAppSelector((state) => state.task.taskLoading); const entityOsmMapLoading = useAppSelector((state) => state.project.entityOsmMapLoading); - const projectTaskList = useAppSelector((state) => state.project.projectTaskBoundries); - const projectDetailsLoading = useAppSelector((state) => state.project.projectDetailsLoading); + const mappedVsValidatedTaskList = useAppSelector((state) => state.submission.mappedVsValidatedTask); + const mappedVsValidatedTaskLoading = useAppSelector((state) => state.submission.mappedVsValidatedTaskLoading); const today = new Date().toISOString(); const [formSubmissionsData, setFormSubmissionsData] = useState([]); - const [validatedVsMappedInfographics, setValidatedVsMappedInfographics] = useState([]); - - useEffect(() => { - if (!projectTaskList || (projectTaskList && projectTaskList?.length === 0)) return; - - const projectIndex = projectTaskList.findIndex((project) => project.id == +projectId); - // task activities history list - const taskActivities = projectTaskList?.[projectIndex]?.taskBoundries?.reduce((acc: taskHistoryTypes[], task) => { - return [...acc, ...(task?.task_history ?? [])]; - }, []); - - // filter activities for last 30 days - const taskActivities30Days = taskActivities?.filter((activity) => { - const actionDate = new Date(activity?.created_at).toISOString(); - return actionDate >= dateNDaysAgo(30) && actionDate <= today; - }); - - // only filter MAPPED & VALIDATED activities - const groupedData: validatedMappedType[] = taskActivities30Days?.reduce((acc: validatedMappedType[], activity) => { - const date = activity?.created_at.split('T')[0]; - const index = acc.findIndex((submission) => submission.date === date); - if (acc?.find((submission) => submission.date === date)) { - if (activity?.comment?.includes('LOCKED_FOR_MAPPING to MAPPED')) { - acc[index].Mapped += 1; - } - if (activity?.comment?.includes('LOCKED_FOR_VALIDATION to VALIDATED')) { - acc[index].Validated += 1; - } - } else { - const splittedDate = date?.split('-'); - const label = `${splittedDate[1]}/${splittedDate[2]}`; - if (activity?.comment?.includes('LOCKED_FOR_MAPPING to MAPPED')) { - acc.push({ date: date, Validated: 0, Mapped: 1, label }); - } - if (activity?.comment?.includes('LOCKED_FOR_VALIDATION to VALIDATED')) { - acc.push({ date: date, Validated: 1, Mapped: 0, label }); - } - } - return acc; - }, []); - - // generate validatedMapped data for last 30 days - const last30Days = generateLast30Days().map((datex) => { - const mappedVsValidatedValue = groupedData?.find((group) => { - return group?.date === datex; - }); - - if (mappedVsValidatedValue) { - return mappedVsValidatedValue; - } else { - // if no validated-mapped date - return count of 0 for both - const splittedDate = datex?.split('-'); - const label = `${splittedDate[1]}/${splittedDate[2]}`; - return { date: datex, Validated: 0, Mapped: 0, label: label }; - } - }); - - // sort by ascending date - const sortedValidatedMapped = last30Days?.sort((a, b) => { - const date1: any = new Date(a.date); - const date2: any = new Date(b.date); - return date1 - date2; - }); - - const cumulativeCount = { - validated: 0, - mapped: 0, - }; - - // generate cumulative count data - const finalData = sortedValidatedMapped?.map((submission) => { - cumulativeCount.validated += submission.Validated; - cumulativeCount.mapped += submission.Mapped; - return { ...submission, Validated: cumulativeCount.validated, Mapped: cumulativeCount.mapped }; - }); - - setValidatedVsMappedInfographics(finalData); - }, [projectTaskList]); // data for validated vs mapped graph useEffect(() => { @@ -231,14 +149,14 @@ const SubmissionsInfographics = ({ toggleView, entities }) => { cardRef={plannedVsActualRef} header="Validated vs Mapped Task" body={ - projectDetailsLoading ? ( + mappedVsValidatedTaskLoading ? ( - ) : validatedVsMappedInfographics.length > 0 ? ( + ) : mappedVsValidatedTaskList.length > 0 ? ( diff --git a/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx b/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx index 19abb901bb..b638dc4022 100644 --- a/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx +++ b/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx @@ -219,7 +219,7 @@ const SubmissionsTable = ({ toggleView }) => { await dispatch( CreateTaskEvent( `${import.meta.env.VITE_API_URL}/tasks/${currentStatus.id}/event`, - task_event.MAP, + task_event.GOOD, projectId, filter?.task_id || '', authDetails || {}, diff --git a/src/frontend/src/store/slices/SubmissionSlice.ts b/src/frontend/src/store/slices/SubmissionSlice.ts index f42559ec2a..dcf2917c73 100644 --- a/src/frontend/src/store/slices/SubmissionSlice.ts +++ b/src/frontend/src/store/slices/SubmissionSlice.ts @@ -30,6 +30,8 @@ const initialState: SubmissionStateTypes = { taskUid: null, }, updateReviewStateLoading: false, + mappedVsValidatedTask: [], + mappedVsValidatedTaskLoading: false, }; const SubmissionSlice = createSlice({ @@ -94,6 +96,16 @@ const SubmissionSlice = createSlice({ }; } }, + SetMappedVsValidatedTask(state, action) { + const MappedVsValidatedTask = action.payload; + state.mappedVsValidatedTask = MappedVsValidatedTask?.map((task) => ({ + ...task, + label: task?.date?.split('/').slice(0, 2).join('/'), + })); + }, + SetMappedVsValidatedTaskLoading(state, action) { + state.mappedVsValidatedTaskLoading = action.payload; + }, }, }); diff --git a/src/frontend/src/store/types/ISubmissions.ts b/src/frontend/src/store/types/ISubmissions.ts index dd2f63c956..a966ea8eec 100644 --- a/src/frontend/src/store/types/ISubmissions.ts +++ b/src/frontend/src/store/types/ISubmissions.ts @@ -16,6 +16,8 @@ export type SubmissionStateTypes = { submissionTableRefreshing: boolean; updateReviewStatusModal: updateReviewStatusModal; updateReviewStateLoading: boolean; + mappedVsValidatedTask: mappedVsValidatedTaskType[]; + mappedVsValidatedTaskLoading: boolean; }; type updateReviewStatusModal = { @@ -33,3 +35,10 @@ export type filterType = { review_state: string | null; submitted_date: string | null; }; + +type mappedVsValidatedTaskType = { + date: string; + mapped: number; + validated: number; + label: string; +}; diff --git a/src/frontend/src/views/ProjectSubmissions.tsx b/src/frontend/src/views/ProjectSubmissions.tsx index 28779f0717..172bb94890 100644 --- a/src/frontend/src/views/ProjectSubmissions.tsx +++ b/src/frontend/src/views/ProjectSubmissions.tsx @@ -8,7 +8,7 @@ import { ProjectActions } from '@/store/slices/ProjectSlice'; import { ProjectById, GetEntityInfo } from '@/api/Project'; import { useSearchParams } from 'react-router-dom'; import { useAppSelector } from '@/types/reduxTypes'; -import { ProjectContributorsService } from '@/api/SubmissionService'; +import { ProjectContributorsService, MappedVsValidatedTaskService } from '@/api/SubmissionService'; const ProjectSubmissions = () => { const dispatch = CoreModules.useAppDispatch(); @@ -51,6 +51,12 @@ const ProjectSubmissions = () => { dispatch(ProjectContributorsService(`${import.meta.env.VITE_API_URL}/projects/contributors/${projectId}`)); }, []); + useEffect(() => { + dispatch( + MappedVsValidatedTaskService(`${import.meta.env.VITE_API_URL}/tasks/activity/?project_id=${projectId}&days=30`), + ); + }, []); + useEffect(() => { if (!searchParams.get('tab')) { setSearchParams({ tab: 'infographics' });