diff --git a/src/frontend/src/api/DataConflation.ts b/src/frontend/src/api/DataConflation.ts
new file mode 100644
index 0000000000..37fca90805
--- /dev/null
+++ b/src/frontend/src/api/DataConflation.ts
@@ -0,0 +1,19 @@
+import axios from 'axios';
+import { DataConflationActions } from '@/store/slices/DataConflationSlice';
+
+export const SubmissionConflationGeojsonService: Function = (url: string) => {
+ return async (dispatch) => {
+ const getSubmissionGeojsonConflation = async (url) => {
+ try {
+ dispatch(DataConflationActions.SetSubmissionConflationGeojsonLoading(true));
+ const getSubmissionConflationGeojsonResponse = await axios.get(url);
+ dispatch(DataConflationActions.SetSubmissionConflationGeojson(getSubmissionConflationGeojsonResponse.data));
+ dispatch(DataConflationActions.SetSubmissionConflationGeojsonLoading(false));
+ } catch (error) {
+ dispatch(DataConflationActions.SetSubmissionConflationGeojsonLoading(false));
+ }
+ };
+
+ await getSubmissionGeojsonConflation(url);
+ };
+};
diff --git a/src/frontend/src/api/ProjectTaskStatus.ts b/src/frontend/src/api/ProjectTaskStatus.ts
index a87aff019a..09f0a008c8 100755
--- a/src/frontend/src/api/ProjectTaskStatus.ts
+++ b/src/frontend/src/api/ProjectTaskStatus.ts
@@ -4,41 +4,49 @@ import CoreModules from '@/shared/CoreModules';
import { CommonActions } from '@/store/slices/CommonSlice';
import { projectTaskBoundriesType } from '@/models/project/projectModel';
-const UpdateTaskStatus = (
+export const UpdateTaskStatus = (
url: string,
- style: any,
- existingData: projectTaskBoundriesType[],
currentProjectId: string,
- feature: Record,
- taskId: number,
+ taskId: string,
body: any,
params: { project_id: string },
+ style?: any,
+ existingData?: projectTaskBoundriesType[],
+ feature?: Record,
) => {
return async (dispatch) => {
- const updateTask = async (url: string, body: any, feature: Record, params: { project_id: string }) => {
+ const updateTask = async (
+ url: string,
+ body: any,
+ params: { project_id: string },
+ feature?: Record,
+ ) => {
try {
dispatch(CommonActions.SetLoading(true));
const response = await CoreModules.axios.post(url, body, { params });
dispatch(ProjectActions.UpdateProjectTaskActivity(response.data));
- await feature.setStyle(style);
+ if (feature && style) {
+ await feature.setStyle(style);
- // assign userId to locked_by_user if status is locked_for_mapping or locked_for_validation
- const prevProperties = feature.getProperties();
- const isTaskLocked = ['LOCKED_FOR_MAPPING', 'LOCKED_FOR_VALIDATION'].includes(response.data.status);
- const updatedProperties = { ...prevProperties, locked_by_user: isTaskLocked ? body.id : null };
- feature.setProperties(updatedProperties);
+ // assign userId to locked_by_user if status is locked_for_mapping or locked_for_validation
+ const prevProperties = feature.getProperties();
+ const isTaskLocked = ['LOCKED_FOR_MAPPING', 'LOCKED_FOR_VALIDATION'].includes(response.data.status);
+ const updatedProperties = { ...prevProperties, locked_by_user: isTaskLocked ? body.id : null };
+ feature.setProperties(updatedProperties);
+
+ dispatch(
+ ProjectActions.UpdateProjectTaskBoundries({
+ projectId: currentProjectId,
+ taskId,
+ locked_by_uid: body?.id,
+ locked_by_username: body?.username,
+ task_status: response.data.status,
+ }),
+ );
+ }
- dispatch(
- ProjectActions.UpdateProjectTaskBoundries({
- projectId: currentProjectId,
- taskId,
- locked_by_uid: body?.id,
- locked_by_username: body?.username,
- task_status: response.data.status,
- }),
- );
dispatch(CommonActions.SetLoading(false));
dispatch(
HomeActions.SetSnackBar({
@@ -60,8 +68,6 @@ const UpdateTaskStatus = (
);
}
};
- await updateTask(url, body, feature, params);
+ await updateTask(url, body, params, feature);
};
};
-
-export default UpdateTaskStatus;
diff --git a/src/frontend/src/components/DataConflation/ConflationMap/MapLegend.tsx b/src/frontend/src/components/DataConflation/ConflationMap/MapLegend.tsx
new file mode 100644
index 0000000000..bae2b5eec7
--- /dev/null
+++ b/src/frontend/src/components/DataConflation/ConflationMap/MapLegend.tsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import Accordion from '@/components/common/Accordion';
+import useOutsideClick from '@/hooks/useOutsideClick';
+
+const legendArray = [
+ { name: 'No conflicts', color: '#40AC8C' },
+ { name: 'Tag conflicts', color: '#DB9D35' },
+ { name: 'Geometry conflicts', color: '#3C4A5E' },
+ { name: 'Both conflicts', color: '#EB8B8B' },
+];
+
+const MapLegends = () => {
+ return (
+
+ {legendArray?.map((legend) => (
+
+ ))}
+
+ );
+};
+
+const MapLegend = () => {
+ const [legendRef, legendToggle, handleLegendToggle] = useOutsideClick();
+
+ return (
+ <>
+ }
+ header={
+
+ }
+ onToggle={() => {
+ handleLegendToggle();
+ }}
+ className="fmtm-py-0 !fmtm-pb-0 fmtm-rounded-lg hover:fmtm-bg-gray-50"
+ collapsed={!legendToggle}
+ />
+ >
+ );
+};
+
+export default MapLegend;
diff --git a/src/frontend/src/components/DataConflation/ConflationMap/index.tsx b/src/frontend/src/components/DataConflation/ConflationMap/index.tsx
new file mode 100644
index 0000000000..423279d9cb
--- /dev/null
+++ b/src/frontend/src/components/DataConflation/ConflationMap/index.tsx
@@ -0,0 +1,68 @@
+import React from 'react';
+import { MapContainer as MapComponent, useOLMap } from '@/components/MapComponent/OpenLayersComponent';
+import LayerSwitcherControl from '@/components/MapComponent/OpenLayersComponent/LayerSwitcher/index';
+import MapLegend from '@/components/DataConflation/ConflationMap/MapLegend';
+import Button from '@/components/common/Button';
+import { useAppSelector } from '@/types/reduxTypes';
+import { VectorLayer } from '@/components/MapComponent/OpenLayersComponent/Layers';
+import { useDispatch } from 'react-redux';
+import { DataConflationActions } from '@/store/slices/DataConflationSlice';
+
+const ConflationMap = () => {
+ const dispatch = useDispatch();
+
+ const submissionConflationGeojson = useAppSelector((state) => state.dataconflation.submissionConflationGeojson);
+ const submissionConflationGeojsonLoading = useAppSelector(
+ (state) => state.dataconflation.submissionConflationGeojsonLoading,
+ );
+
+ const { mapRef, map } = useOLMap({
+ center: [0, 0],
+ zoom: 4,
+ });
+
+ return (
+ <>
+
+ {
+ dispatch(
+ DataConflationActions.SetSelectedFeatureOSMId(
+ feature?.getProperties()?.xid
+ ? feature.getProperties().xid
+ : feature.getProperties()?.osm_id.toString(),
+ ),
+ );
+ }}
+ />
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default ConflationMap;
diff --git a/src/frontend/src/components/DataConflation/SubmissionConflation/MergeAttributes.tsx b/src/frontend/src/components/DataConflation/SubmissionConflation/MergeAttributes.tsx
new file mode 100644
index 0000000000..cbedb3b618
--- /dev/null
+++ b/src/frontend/src/components/DataConflation/SubmissionConflation/MergeAttributes.tsx
@@ -0,0 +1,145 @@
+import React, { useState } from 'react';
+import { Modal } from '@/components/common/Modal';
+import Button from '@/components/common/Button';
+import Table, { TableHeader } from '@/components/common/CustomTable';
+
+type mergeAttributesPropType = {
+ selectedConflateMethod: 'submission_feature' | 'osm_feature' | 'merge_attributes' | '';
+ setSelectedConflateMethod: (value: '') => void;
+ submissionTags: Record;
+ osmTags: Record;
+};
+
+const MergeAttributes = ({
+ selectedConflateMethod,
+ setSelectedConflateMethod,
+ submissionTags,
+ osmTags,
+}: mergeAttributesPropType) => {
+ const [chosenAttribute, setChosenAttribute] = useState({});
+
+ const tableData: any = [];
+ for (const [key, value] of Object.entries(osmTags)) {
+ if (submissionTags?.[key] && submissionTags?.[key] !== value) {
+ tableData.push({ name: key, osm: value, submission: submissionTags?.[key] });
+ }
+ }
+
+ return (
+ <>
+ Merge Data With OSM
}
+ description={
+
+
+
{}}
+ isLoading={false}
+ >
+ {row?.name}
}
+ />
+ (
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ setChosenAttribute((prev) => ({ ...prev, [row?.name]: row?.osm }));
+ }}
+ title={row?.osm}
+ >
+
+
+
+ )}
+ />
+ (
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ setChosenAttribute((prev) => ({ ...prev, [row?.name]: row?.submission }));
+ }}
+ title={row?.submission}
+ >
+
+
+
+ )}
+ />
+
+
+
+
+
+ }
+ open={selectedConflateMethod === 'merge_attributes'}
+ onOpenChange={() => setSelectedConflateMethod('')}
+ className="fmtm-max-w-[70rem] fmtm-rounded-xl"
+ />
+ >
+ );
+};
+
+export default MergeAttributes;
diff --git a/src/frontend/src/components/DataConflation/SubmissionConflation/index.tsx b/src/frontend/src/components/DataConflation/SubmissionConflation/index.tsx
new file mode 100644
index 0000000000..63ff235ac6
--- /dev/null
+++ b/src/frontend/src/components/DataConflation/SubmissionConflation/index.tsx
@@ -0,0 +1,139 @@
+import React, { useState } from 'react';
+import Button from '@/components/common/Button';
+import AssetModules from '@/shared/AssetModules';
+import MergeAttributes from '@/components/DataConflation/SubmissionConflation/MergeAttributes';
+import CoreModules from '@/shared/CoreModules';
+import { useAppSelector } from '@/types/reduxTypes';
+
+const TagsSkeleton = () => (
+ <>
+ {Array.from({ length: 6 }).map((_, index) => (
+
+
+
+
+ ))}
+ >
+);
+
+const RenderTags = ({ tag }: { tag: [string, any] }) => (
+
+
{tag?.[0]}
+
{tag?.[1]}
+
+);
+
+const SubmissionConflation = () => {
+ const [selectedConflateMethod, setSelectedConflateMethod] = useState<
+ 'submission_feature' | 'osm_feature' | 'merge_attributes' | ''
+ >('');
+
+ const submissionConflationGeojson = useAppSelector((state) => state.dataconflation.submissionConflationGeojson);
+ const selectedFeatureOSMId = useAppSelector((state) => state.dataconflation.selectedFeatureOSMId);
+ const submissionConflationGeojsonLoading = useAppSelector(
+ (state) => state.dataconflation.submissionConflationGeojsonLoading,
+ );
+
+ const selectedFeature = submissionConflationGeojson?.features?.find(
+ (feature) => feature.properties?.xid === selectedFeatureOSMId,
+ );
+ const filteredSubmissionTags = {};
+ for (const [key, value] of Object.entries(selectedFeature?.properties)) {
+ if (value !== null) {
+ filteredSubmissionTags[key] = value;
+ }
+ }
+
+ return (
+ <>
+
+
+
+
+
SUBMISSION #457
+
+ {submissionConflationGeojsonLoading ? (
+
+ ) : filteredSubmissionTags ? (
+ <>
+ {Object.entries(filteredSubmissionTags).map((tag) => {
+ const [key, value] = tag;
+ if (value) return ;
+ })}
+ >
+ ) : (
+ <>>
+ )}
+
+
+ )
+ }
+ onClick={() => setSelectedConflateMethod('submission_feature')}
+ />
+
+
+
+
OSM TAGS
+
+ {submissionConflationGeojsonLoading ? (
+
+ ) : selectedFeature?.tags ? (
+ <>
+ {Object.entries(selectedFeature?.tags).map((tag) => (
+
+ ))}
+ >
+ ) : (
+ <>>
+ )}
+
+
}
+ onClick={() => setSelectedConflateMethod('osm_feature')}
+ />
+
+
+
+
+
+
+ >
+ );
+};
+
+export default SubmissionConflation;
diff --git a/src/frontend/src/components/DataConflation/TaskInfo.tsx b/src/frontend/src/components/DataConflation/TaskInfo.tsx
new file mode 100644
index 0000000000..641b239dc8
--- /dev/null
+++ b/src/frontend/src/components/DataConflation/TaskInfo.tsx
@@ -0,0 +1,111 @@
+import React from 'react';
+import { useNavigate, useParams } from 'react-router-dom';
+import Button from '@/components/common/Button';
+import AssetModules from '@/shared/AssetModules';
+import CoreModules from '@/shared/CoreModules';
+import { useAppSelector } from '@/types/reduxTypes';
+
+const TaskInfo = () => {
+ const navigate = useNavigate();
+ const { taskId, projectId } = useParams();
+
+ const submissionConflationGeojson = useAppSelector((state) => state.dataconflation.submissionConflationGeojson);
+ const submissionConflationGeojsonLoading = useAppSelector(
+ (state) => state.dataconflation.submissionConflationGeojsonLoading,
+ );
+ const submissionFeatures = submissionConflationGeojson?.features;
+
+ const featureCount = submissionFeatures?.length || 0;
+ let geometryConflictCount = 0;
+ let tagConflictCount = 0;
+ let noTagConflictCount = 0;
+
+ submissionFeatures?.map((feature) => {
+ if (!feature) return;
+
+ // geom conflict count
+ if (feature?.properties?.overlap_percent < 90) {
+ geometryConflictCount += 1;
+ }
+
+ if (!feature?.tags) return;
+ let tagConflict = false;
+ let noConflict = false;
+
+ // tag conflict count
+ for (const [key, value] of Object.entries(feature?.tags)) {
+ if (feature?.properties?.[key] && feature?.properties?.[key] !== value) {
+ tagConflict = true;
+ } else if (feature?.properties?.overlap_percent > 90) {
+ noConflict = true;
+ }
+ }
+ if (tagConflict) tagConflictCount += 1;
+ if (noConflict) noTagConflictCount += 1;
+ });
+
+ const taskInfoConstants = [
+ { name: 'Total Feature', count: featureCount - geometryConflictCount },
+ { name: 'Number of geometry conflicts', count: geometryConflictCount },
+ { name: 'Number of tag conflicts', count: tagConflictCount },
+ { name: 'No conflicts', count: noTagConflictCount },
+ ];
+
+ return (
+
+
navigate(`/project/${projectId}`)}
+ className="fmtm-flex fmtm-items-center fmtm-mb-5 fmtm-cursor-pointer hover:fmtm-text-primaryRed fmtm-duration-300 fmtm-w-fit"
+ >
+
+
BACK
+
+
+
+
Task #{taskId}
+
+
+ {submissionConflationGeojsonLoading ? (
+ <>
+ {Array.from({ length: 4 }).map((_, index) => (
+
+ ))}
+ >
+ ) : (
+
+ {taskInfoConstants?.map((info) => (
+
+ {info?.name} |
+ : |
+ {info?.count} |
+
+ ))}
+
+ )}
+
+
+
+
+
+
+
+ );
+};
+
+export default TaskInfo;
diff --git a/src/frontend/src/components/DialogTaskActions.tsx b/src/frontend/src/components/DialogTaskActions.tsx
index cd5288c36c..e9e7d7fbeb 100755
--- a/src/frontend/src/components/DialogTaskActions.tsx
+++ b/src/frontend/src/components/DialogTaskActions.tsx
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import environment from '@/environment';
-import ProjectTaskStatus from '@/api/ProjectTaskStatus';
+import { UpdateTaskStatus } from '@/api/ProjectTaskStatus';
import MapStyles from '@/hooks/MapStyles';
import CoreModules from '@/shared/CoreModules';
import { CommonActions } from '@/store/slices/CommonSlice';
@@ -77,24 +77,32 @@ export default function Dialog({ taskId, feature }: dialogPropType) {
}
}, [projectTaskActivityList, taskId, feature]);
- const handleOnClick = (event) => {
- const status = taskStatusEnum[event.currentTarget.dataset.btnid];
+ const handleOnClick = async (event: React.MouseEvent) => {
+ const btnId = event.currentTarget.dataset.btnid;
+ if (!btnId) return;
+ const status = taskStatusEnum[btnId];
const authDetailsCopy = authDetails != null ? { ...authDetails } : {};
- const geoStyle = geojsonStyles[event.currentTarget.dataset.btnid];
- if (event.currentTarget.dataset.btnid != undefined) {
+ const geoStyle = geojsonStyles[btnId];
+ if (btnId != undefined) {
if (authDetailsCopy.hasOwnProperty('id')) {
- dispatch(
- ProjectTaskStatus(
+ // if (btnId === 'MERGE_WITH_OSM') {
+ // navigate(`/conflate-data/${currentProjectId}/${taskId}`);
+ // return;
+ // }
+ await dispatch(
+ UpdateTaskStatus(
`${import.meta.env.VITE_API_URL}/tasks/${currentStatus?.id}/new-status/${status}`,
- geoStyle,
- taskBoundaryData,
currentProjectId,
- feature,
- taskId,
+ taskId.toString(),
authDetailsCopy,
{ project_id: currentProjectId },
+ geoStyle,
+ taskBoundaryData,
+ feature,
),
);
+ if (btnId === 'LOCKED_FOR_VALIDATION')
+ 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 55ec04733e..c316488ccf 100644
--- a/src/frontend/src/components/ProjectDetailsV2/FeatureSelectionPopup.tsx
+++ b/src/frontend/src/components/ProjectDetailsV2/FeatureSelectionPopup.tsx
@@ -10,7 +10,7 @@ import environment from '@/environment';
import { useParams } from 'react-router-dom';
import { UpdateEntityStatus } from '@/api/Project';
import { TaskFeatureSelectionProperties } from '@/store/types/ITask';
-import ProjectTaskStatus from '@/api/ProjectTaskStatus';
+import { UpdateTaskStatus } from '@/api/ProjectTaskStatus';
import MapStyles from '@/hooks/MapStyles';
type TaskFeatureSelectionPopupPropType = {
@@ -28,7 +28,7 @@ const TaskFeatureSelectionPopup = ({ featureProperties, taskId, taskFeature }: T
const entityOsmMap = CoreModules.useAppSelector((state) => state.project.entityOsmMap);
const authDetails = CoreModules.useAppSelector((state) => state.login.authDetails);
- const currentProjectId = params.id;
+ const currentProjectId = params.id || '';
const [task_status, set_task_status] = useState('READY');
const projectData = CoreModules.useAppSelector((state) => state.project.projectTaskBoundries);
const projectIndex = projectData.findIndex((project) => project.id == currentProjectId);
@@ -134,15 +134,15 @@ const TaskFeatureSelectionPopup = ({ featureProperties, taskId, taskFeature }: T
if (task_status === 'READY') {
dispatch(
- ProjectTaskStatus(
+ UpdateTaskStatus(
`${import.meta.env.VITE_API_URL}/tasks/${currentTaskInfo?.id}/new-status/1`,
- geoStyle,
- taskBoundaryData,
currentProjectId,
- taskFeature,
- taskId,
+ taskId.toString(),
authDetails,
{ project_id: currentProjectId },
+ geoStyle,
+ taskBoundaryData,
+ taskFeature,
),
);
}
diff --git a/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx b/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx
index a81a041874..482a1905e2 100644
--- a/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx
+++ b/src/frontend/src/components/ProjectSubmissions/SubmissionsTable.tsx
@@ -21,6 +21,7 @@ import UpdateReviewStatusModal from '@/components/ProjectSubmissions/UpdateRevie
import { useAppSelector } from '@/types/reduxTypes';
import { camelToFlat } from '@/utilfunctions/commonUtils';
import useDocumentTitle from '@/utilfunctions/useDocumentTitle';
+import { UpdateTaskStatus } from '@/api/ProjectTaskStatus';
import { filterType } from '@/store/types/ISubmissions';
const SubmissionsTable = ({ toggleView }) => {
@@ -28,7 +29,7 @@ const SubmissionsTable = ({ toggleView }) => {
const [searchParams, setSearchParams] = useSearchParams();
const initialFilterState: filterType = {
- task_id: searchParams.get('task_id') ? searchParams?.get('task_id') : null,
+ task_id: searchParams.get('task_id') ? searchParams?.get('task_id') || null : null,
submitted_by: searchParams.get('submitted_by'),
review_state: searchParams.get('review_state'),
submitted_date: searchParams.get('submitted_date'),
@@ -50,9 +51,18 @@ const SubmissionsTable = ({ toggleView }) => {
const projectInfo = useAppSelector((state) => state.project.projectInfo);
const josmEditorError = useAppSelector((state) => state.task.josmEditorError);
const downloadSubmissionLoading = useAppSelector((state) => state.task.downloadSubmissionLoading);
- const projectTaskBoundries = useAppSelector((state) => state.project.projectTaskBoundries);
- const projectIndex = projectTaskBoundries.findIndex((project) => project.id == +projectId);
- const taskList = projectTaskBoundries[projectIndex]?.taskBoundries;
+ const authDetails = CoreModules.useAppSelector((state) => state.login.authDetails);
+ const updateTaskStatusLoading = useAppSelector((state) => state.common.loading);
+
+ const projectData = useAppSelector((state) => state.project.projectTaskBoundries);
+ const projectIndex = projectData.findIndex((project) => project.id == +projectId);
+ const taskBoundaryData = useAppSelector((state) => state.project.projectTaskBoundries);
+ const currentStatus = {
+ ...taskBoundaryData?.[projectIndex]?.taskBoundries?.filter((task) => {
+ return task?.index === +filter.task_id;
+ })?.[0],
+ };
+ const taskList = projectData[projectIndex]?.taskBoundries;
const [numberOfFilters, setNumberOfFilters] = useState(0);
const [paginationPage, setPaginationPage] = useState(1);
@@ -150,7 +160,7 @@ const SubmissionsTable = ({ toggleView }) => {
const clearFilters = () => {
setSearchParams({ tab: 'table' });
- setFilter({ task_id: null, submitted_by: null, review_state: null, submitted_date: null });
+ setFilter({ task_id: '', submitted_by: null, review_state: null, submitted_date: null });
};
function getValueByPath(obj: any, path: string) {
@@ -205,6 +215,19 @@ const SubmissionsTable = ({ toggleView }) => {
}
};
+ const handleTaskMap = async () => {
+ await dispatch(
+ UpdateTaskStatus(
+ `${import.meta.env.VITE_API_URL}/tasks/${currentStatus.id}/new-status/4`,
+ projectId,
+ filter?.task_id || '',
+ authDetails || {},
+ { project_id: projectId },
+ ),
+ );
+ navigate(`/project/${projectId}`);
+ };
+
useEffect(() => {
const filteredParams = filterParams(filter);
setSearchParams({ tab: 'table', ...filteredParams });
@@ -228,7 +251,7 @@ const SubmissionsTable = ({ toggleView }) => {
}}
/>
-
+
{
+
+ {(submissionTableDataLoading || submissionFormFieldsLoading) && submissionTableRefreshing ? (
+
+ ) : (
+
+ )}
+ REFRESH
+
-
- {(submissionTableDataLoading || submissionFormFieldsLoading) && submissionTableRefreshing ? (
-
- ) : (
-
+ {filter?.task_id &&
+ taskBoundaryData?.[projectIndex]?.taskBoundries?.find((task) => task?.index === +filter?.task_id)
+ ?.task_status === 'LOCKED_FOR_VALIDATION' && (
+
)}
- REFRESH
-
{toggleView}
diff --git a/src/frontend/src/components/common/Button.tsx b/src/frontend/src/components/common/Button.tsx
index f916ac273f..8f10befa02 100644
--- a/src/frontend/src/components/common/Button.tsx
+++ b/src/frontend/src/components/common/Button.tsx
@@ -52,7 +52,7 @@ const Button = ({
data-testid="test-button"
type={type === 'submit' ? 'submit' : 'button'}
onClick={onClick}
- className={`fmtm-text-lg fmtm-group fmtm-flex fmtm-items-center fmtm-gap-2 ${btnStyle(
+ className={`fmtm-text-lg fmtm-group fmtm-flex fmtm-items-center fmtm-gap-2 fmtm-outline-none ${btnStyle(
isLoading || disabled ? 'disabled' : btnType,
className,
)}`}
diff --git a/src/frontend/src/components/common/CustomTable.tsx b/src/frontend/src/components/common/CustomTable.tsx
index 0a3e6a0d31..e4411fb976 100644
--- a/src/frontend/src/components/common/CustomTable.tsx
+++ b/src/frontend/src/components/common/CustomTable.tsx
@@ -244,7 +244,9 @@ export default class Table extends Component {
style: { cursor: 'pointer' },
})}
className={`${trClassName && trClassName(row)} ${
- flag?.toLowerCase() === 'primarytable' ? 'hover:fmtm-bg-[#F2E3E3]' : ''
+ flag?.toLowerCase() === 'primarytable'
+ ? `${(index + 1) % 2 === 0 ? '!fmtm-bg-[#F3F3F3]' : 'fmtm-bg-white'}`
+ : ''
} fmtm-cursor-pointer fmtm-ease-in fmtm-duration-100 fmtm-h-[50px]
fmtm-items-baseline fmtm-relative fmtm-bg-white`}
>
diff --git a/src/frontend/src/components/common/Modal.tsx b/src/frontend/src/components/common/Modal.tsx
index 520d18fb95..e656117868 100644
--- a/src/frontend/src/components/common/Modal.tsx
+++ b/src/frontend/src/components/common/Modal.tsx
@@ -99,7 +99,7 @@ DialogDescription.displayName = DialogPrimitive.Description.displayName;
const Modal = ({ dialogOpen, title, description, open, onOpenChange, className }: IModalProps) => {
return (