+
}>
+
+
+
+
+
+ ),
+ },
// {
// path: '/create-project',
// element: (
diff --git a/src/frontend/src/shared/AssetModules.js b/src/frontend/src/shared/AssetModules.js
index 6ac7ad56c5..adf038da20 100755
--- a/src/frontend/src/shared/AssetModules.js
+++ b/src/frontend/src/shared/AssetModules.js
@@ -49,6 +49,13 @@ import {
CropFree as CropFreeIcon,
LegendToggle as LegendToggleIcon,
Delete as DeleteIcon,
+ Launch as LaunchIcon,
+ Edit as EditIcon,
+ FmdGood as FmdGoodIcon,
+ Map as MapIcon,
+ AccessTime as AccessTimeIcon,
+ ImportExport as ImportExportIcon,
+ Check as CheckIcon,
} from '@mui/icons-material';
import LockPng from '../assets/images/lock.png';
import RedLockPng from '../assets/images/red-lock.png';
@@ -108,4 +115,11 @@ export default {
CropFreeIcon,
LegendToggleIcon,
DeleteIcon,
+ LaunchIcon,
+ EditIcon,
+ FmdGoodIcon,
+ MapIcon,
+ AccessTimeIcon,
+ ImportExportIcon,
+ CheckIcon,
};
diff --git a/src/frontend/src/store/slices/ProjectSlice.ts b/src/frontend/src/store/slices/ProjectSlice.ts
index bcc7be7985..20a82bdf03 100755
--- a/src/frontend/src/store/slices/ProjectSlice.ts
+++ b/src/frontend/src/store/slices/ProjectSlice.ts
@@ -21,6 +21,9 @@ const ProjectSlice = createSlice({
taskModalStatus: false,
mobileFooterSelection: 'explore',
geolocationStatus: false,
+ projectDetailsLoading: true,
+ projectDashboardDetail: {},
+ projectDashboardLoading: false,
},
reducers: {
SetProjectTaskBoundries(state, action) {
@@ -78,6 +81,15 @@ const ProjectSlice = createSlice({
ToggleGeolocationStatus(state, action) {
state.geolocationStatus = action.payload;
},
+ SetProjectDetialsLoading(state, action) {
+ state.projectDetailsLoading = action.payload;
+ },
+ SetProjectDashboardDetail(state, action) {
+ state.projectDashboardDetail = action.payload;
+ },
+ SetProjectDashboardLoading(state, action) {
+ state.projectDashboardLoading = action.payload;
+ },
},
});
diff --git a/src/frontend/src/store/slices/ThemeSlice.ts b/src/frontend/src/store/slices/ThemeSlice.ts
index de67e64a6d..781cdcdb57 100755
--- a/src/frontend/src/store/slices/ThemeSlice.ts
+++ b/src/frontend/src/store/slices/ThemeSlice.ts
@@ -71,6 +71,14 @@ const ThemeSlice = CoreModules.createSlice({
split_rgb: 'rgb(112, 67, 67,0.5)',
},
},
+ statusTextTheme: {
+ READY: '#a3a2a2',
+ LOCKED_FOR_MAPPING: '#097085',
+ MAPPED: '#64cfe3',
+ LOCKED_FOR_VALIDATION: '#C5BD0A',
+ VALIDATED: '#44c9a2',
+ INVALIDATED: '#D73F37',
+ },
READY: '#fff',
LOCKED_FOR_MAPPING: '#fff',
MAPPED: '#ade6ef',
diff --git a/src/frontend/src/views/NewProjectDetails.jsx b/src/frontend/src/views/NewProjectDetails.jsx
index dbfb93bb04..7bcc0af6ba 100644
--- a/src/frontend/src/views/NewProjectDetails.jsx
+++ b/src/frontend/src/views/NewProjectDetails.jsx
@@ -284,7 +284,7 @@ const Home = () => {
{/* Top project details heading medium dimension*/}
{windowSize.width >= 640 && (
-
+
{
+
)}
diff --git a/src/frontend/src/views/ProjectDetailsV2.tsx b/src/frontend/src/views/ProjectDetailsV2.tsx
new file mode 100644
index 0000000000..cbb0ffda7c
--- /dev/null
+++ b/src/frontend/src/views/ProjectDetailsV2.tsx
@@ -0,0 +1,502 @@
+import React, { useEffect, useState } from 'react';
+import '../../node_modules/ol/ol.css';
+import '../styles/home.scss';
+import WindowDimension from '../hooks/WindowDimension';
+import MapDescriptionComponents from '../components/MapDescriptionComponents';
+import ActivitiesPanel from '../components/ProjectDetailsV2/ActivitiesPanel';
+import environment from '../environment';
+import { ProjectById, GetProjectDashboard } from '../api/Project';
+import { ProjectActions } from '../store/slices/ProjectSlice';
+import CustomizedSnackbar from '../utilities/CustomizedSnackbar';
+import OnScroll from '../hooks/OnScroll';
+import { HomeActions } from '../store/slices/HomeSlice';
+import CoreModules from '../shared/CoreModules';
+import AssetModules from '../shared/AssetModules';
+import FmtmLogo from '../assets/images/hotLog.png';
+import GenerateBasemap from '../components/GenerateBasemap';
+import { ProjectBuildingGeojsonService } from '../api/SubmissionService';
+import TaskSectionPopup from '../components/ProjectDetailsV2/TaskSectionPopup';
+import DialogTaskActions from '../components/DialogTaskActions';
+import QrcodeComponent from '../components/QrcodeComponent';
+import MobileFooter from '../components/ProjectDetailsV2/MobileFooter';
+import MobileActivitiesContents from '../components/ProjectDetailsV2/MobileActivitiesContents';
+import BottomSheet from '../components/common/BottomSheet';
+import MobileProjectInfoContent from '../components/ProjectDetailsV2/MobileProjectInfoContent';
+import { useNavigate } from 'react-router-dom';
+import ProjectOptions from '../components/ProjectDetails/ProjectOptions';
+import { MapContainer as MapComponent, useOLMap } from '../components/MapComponent/OpenLayersComponent';
+import LayerSwitcherControl from '../components/MapComponent/OpenLayersComponent/LayerSwitcher/index';
+import MapControlComponent from '../components/ProjectDetailsV2/MapControlComponent';
+import { VectorLayer } from '../components/MapComponent/OpenLayersComponent/Layers';
+import { geojsonObjectModel } from '../constants/geojsonObjectModal';
+import { basicGeojsonTemplate } from '../utilities/mapUtils';
+import getTaskStatusStyle from '../utilfunctions/getTaskStatusStyle';
+import { defaultStyles } from '../components/MapComponent/OpenLayersComponent/helpers/styleUtils';
+import MapLegends from '../components/MapLegends';
+import Accordion from '../components/common/Accordion';
+import { Geolocation } from '@capacitor/geolocation';
+import { Icon, Style } from 'ol/style';
+import { Motion } from '@capacitor/motion';
+import locationArc from '../assets/images/locationArc.png';
+import { CommonActions } from '../store/slices/CommonSlice';
+import Button from '../components/common/Button';
+import ProjectInfo from '../components/ProjectDetailsV2/ProjectInfo';
+
+const Home = () => {
+ const dispatch = CoreModules.useAppDispatch();
+ const params = CoreModules.useParams();
+ const navigate = useNavigate();
+ const { windowSize, type } = WindowDimension();
+
+ const [taskId, setTaskId] = useState();
+ const [mainView, setView] = useState();
+ const [featuresLayer, setFeaturesLayer] = useState();
+ const [toggleGenerateModal, setToggleGenerateModal] = useState(false);
+ const [taskBuildingGeojson, setTaskBuildingGeojson] = useState(null);
+ const [initialFeaturesLayer, setInitialFeaturesLayer] = useState(null);
+ const [currentCoordinate, setCurrentCoordinate] = useState({ latitude: null, longitude: null });
+ const [positionGeojson, setPositionGeojson] = useState(null);
+ const [deviceRotation, setDeviceRotation] = useState(0);
+ const [viewState, setViewState] = useState('project_info');
+
+ const encodedId = params.id;
+ const decodedId = environment.decode(encodedId);
+ const defaultTheme = CoreModules.useAppSelector((state) => state.theme.hotTheme);
+ const state = CoreModules.useAppSelector((state) => state.project);
+ const projectInfo = CoreModules.useAppSelector((state) => state.home.selectedProject);
+ const stateSnackBar = CoreModules.useAppSelector((state) => state.home.snackbar);
+ const projectBuildingGeojson = CoreModules.useAppSelector((state) => state.project.projectBuildingGeojson);
+ const mobileFooterSelection = CoreModules.useAppSelector((state) => state.project.mobileFooterSelection);
+ const mapTheme = CoreModules.useAppSelector((state) => state.theme.hotTheme);
+ const geolocationStatus = CoreModules.useAppSelector((state) => state.project.geolocationStatus);
+ const projectDetailsLoading = CoreModules.useAppSelector((state) => state?.project?.projectDetailsLoading);
+
+ //snackbar handle close funtion
+ const handleClose = (event, reason) => {
+ if (reason === 'clickaway') {
+ return;
+ }
+ dispatch(
+ HomeActions.SetSnackBar({
+ open: false,
+ message: stateSnackBar.message,
+ variant: stateSnackBar.variant,
+ duration: 0,
+ }),
+ );
+ };
+ //Fetch project for the first time
+ useEffect(() => {
+ dispatch(ProjectActions.SetNewProjectTrigger());
+ if (state.projectTaskBoundries.findIndex((project) => project.id == environment.decode(encodedId)) == -1) {
+ dispatch(ProjectActions.SetProjectTaskBoundries([]));
+ dispatch(ProjectById(state.projectTaskBoundries, environment.decode(encodedId)));
+ } else {
+ dispatch(ProjectActions.SetProjectTaskBoundries([]));
+ dispatch(ProjectById(state.projectTaskBoundries, environment.decode(encodedId)));
+ }
+ if (Object.keys(state.projectInfo)?.length == 0) {
+ dispatch(ProjectActions.SetProjectInfo(projectInfo));
+ } else {
+ if (state.projectInfo.id != environment.decode(encodedId)) {
+ dispatch(ProjectActions.SetProjectInfo(projectInfo));
+ }
+ }
+ return () => {
+ dispatch(ProjectActions.SetProjectBuildingGeojson(null));
+ };
+ }, [params.id]);
+
+ const { mapRef, map } = useOLMap({
+ center: [0, 0],
+ zoom: 4,
+ });
+
+ const { y } = OnScroll(map, windowSize.width);
+
+ useEffect(() => {
+ if (!map) return;
+
+ const features = state.projectTaskBoundries[0]?.taskBoundries?.map((feature) => ({
+ type: 'Feature',
+ geometry: { ...feature.outline_geojson.geometry },
+ properties: {
+ ...feature.outline_geojson.properties,
+ centroid: feature.bbox,
+ },
+ id: `${feature.project_task_name}_${feature.task_status}`,
+ }));
+ const taskBuildingGeojsonFeatureCollection = {
+ ...geojsonObjectModel,
+ features: features,
+ };
+ setInitialFeaturesLayer(taskBuildingGeojsonFeatureCollection);
+ }, [state.projectTaskBoundries[0]?.taskBoundries?.length]);
+
+ useEffect(() => {
+ if (!map) return;
+ if (!projectBuildingGeojson) return;
+
+ const taskBuildingGeojsonFeatureCollection = {
+ ...basicGeojsonTemplate,
+ features: [
+ ...projectBuildingGeojson?.map((feature) => ({
+ ...feature.geometry,
+ id: feature.id,
+ })),
+ ],
+ };
+
+ setTaskBuildingGeojson(taskBuildingGeojsonFeatureCollection);
+ }, [map, projectBuildingGeojson]);
+
+ useEffect(() => {
+ dispatch(GetProjectDashboard(`${import.meta.env.VITE_API_URL}/projects/project_dashboard/${decodedId}`));
+ }, []);
+
+ // TasksLayer(map, mainView, featuresLayer);
+ const projectClickOnMap = (properties, feature) => {
+ setFeaturesLayer(feature, 'feature');
+ let extent = properties.geometry.getExtent();
+ dispatch(
+ ProjectBuildingGeojsonService(
+ `${import.meta.env.VITE_API_URL}/projects/${decodedId}/features?task_id=${properties.uid}`,
+ ),
+ );
+ mapRef.current?.scrollIntoView({
+ block: 'center',
+ behavior: 'smooth',
+ });
+ setTaskId(properties.uid);
+ dispatch(ProjectActions.ToggleTaskModalStatus(true));
+ if (windowSize.width < 768) {
+ map.getView().fit(extent, {
+ padding: [10, 20, 300, 20],
+ });
+ } else {
+ map.getView().fit(extent, {
+ padding: [20, 350, 50, 10],
+ });
+ }
+ };
+
+ const buildingStyle = {
+ ...defaultStyles,
+ lineColor: '#FF0000',
+ fillOpacity: '0',
+ };
+
+ useEffect(() => {
+ if (mobileFooterSelection !== 'explore') {
+ setToggleGenerateModal(false);
+ }
+ }, [mobileFooterSelection]);
+
+ const handlePositionChange = (position) => {
+ setCurrentCoordinate({
+ latitude: position.coords.latitude,
+ longitude: position.coords.longitude,
+ });
+
+ const geojson = {
+ type: 'Point',
+ coordinates: [position.coords.longitude, position.coords.latitude],
+ };
+ setPositionGeojson(geojson);
+ };
+
+ useEffect(async () => {
+ if (geolocationStatus) {
+ const checkPermission = await Geolocation.checkPermissions();
+ if (checkPermission.location === 'denied') {
+ await Geolocation.requestPermissions(['location']);
+ }
+ }
+ }, [geolocationStatus]);
+
+ useEffect(() => {
+ if (geolocationStatus) {
+ const getCurrentPosition = async () => {
+ try {
+ const position = await Geolocation.getCurrentPosition();
+ handlePositionChange(position);
+ // Watch for position changes
+ const watchId = Geolocation.watchPosition({ enableHighAccuracy: true }, handlePositionChange);
+ // Clean up the watchPosition when the component unmounts
+ return () => {
+ Geolocation.clearWatch({ id: watchId });
+ };
+ } catch (error) {
+ dispatch(
+ CommonActions.SetSnackBar({
+ open: true,
+ message: `Error getting current position. Please ensure location permissions has been granted.`,
+ variant: 'error',
+ duration: 2000,
+ }),
+ );
+ dispatch(ProjectActions.ToggleGeolocationStatus(false));
+ }
+ };
+
+ getCurrentPosition();
+ }
+ }, [geolocationStatus]);
+
+ const locationArcStyle = new Style({
+ image: new Icon({
+ src: locationArc,
+ }),
+ });
+
+ const startOrientation = async () => {
+ const handler = await Motion.addListener('orientation', (event) => {
+ var alphaRad = event?.alpha * (Math.PI / 180);
+ var betaRad = event?.beta * (Math.PI / 180);
+ var gammaRad = event?.gamma * (Math.PI / 180);
+
+ setDeviceRotation(alphaRad + betaRad + gammaRad);
+ });
+ };
+
+ useEffect(() => {
+ // Cleanup when the component unmounts
+ if (geolocationStatus) {
+ startOrientation();
+ }
+ return () => {};
+ }, [geolocationStatus]);
+
+ return (
+
+ {/* Customized Modal For Generate Tiles */}
+
+
+
+ {/* Home snackbar */}
+
+
+
+
+
+
+ {projectDetailsLoading ? (
+
+ ) : (
+
{`#${state.projectInfo.id}`}
+ )}
+
navigate(`/projectInfo/${encodedId}`)}
+ >
+ View Submissions
+
+
+
+ {projectDetailsLoading ? (
+
+ ) : (
+
+
+ {state.projectInfo.title}
+
+
+
+
navigate(`/edit-project/project-details/${encodedId}`)}
+ />
+
+
+ )}
+
+
+
+
+
+ {viewState === 'project_info' ? (
+
+ ) : (
+
+ )}
+
+
+ {params?.id && (
+
+
+
+
+ {initialFeaturesLayer && initialFeaturesLayer?.features?.length > 0 && (
+ getTaskStatusStyle(feature, mapTheme)}
+ />
+ )}
+ {taskBuildingGeojson && taskBuildingGeojson?.features?.length > 0 && (
+
+ )}
+ {geolocationStatus && currentCoordinate?.latitude && currentCoordinate?.longitude && (
+
+ )}
+ {window.DeviceMotionEvent}
+
+
}
+ header={
+
+ }
+ onToggle={() => {}}
+ className="fmtm-py-0 !fmtm-pb-0 fmtm-rounded-lg hover:fmtm-bg-gray-50"
+ collapsed={true}
+ />
+
+
+
+
+ {mobileFooterSelection === 'projectInfo' && (
+
}
+ onClose={() => dispatch(ProjectActions.SetMobileFooterSelection('explore'))}
+ />
+ )}
+ {mobileFooterSelection === 'activities' && (
+
}
+ onClose={() => dispatch(ProjectActions.SetMobileFooterSelection('explore'))}
+ />
+ )}
+ {mobileFooterSelection === 'explore' && (
+
+
+
+ )}
+ {mobileFooterSelection === 'mapLegend' && (
+
+
+
+ }
+ onClose={() => dispatch(ProjectActions.SetMobileFooterSelection('explore'))}
+ />
+ )}
+ {mobileFooterSelection === 'others' && (
+
+
+
+ }
+ onClose={() => dispatch(ProjectActions.SetMobileFooterSelection('explore'))}
+ />
+ )}
+
+
+
+ )}
+
+ {featuresLayer != undefined && (
+
+
+
+