diff --git a/src/appredux/modules/map/actions.js b/src/appredux/modules/map/actions.js
index 72eca0e..42d9f5f 100644
--- a/src/appredux/modules/map/actions.js
+++ b/src/appredux/modules/map/actions.js
@@ -1,4 +1,5 @@
import moment from "moment";
+import { toast } from "react-toastify";
import ApiMapMonitoring from "../../../services/api/modules/map_monitoring";
import { store } from "../../store";
@@ -10,6 +11,8 @@ export const SET_USER_HISTORY = 'SET_USER_HISTORY';
export const SET_PROJECT_TREE = 'SET_PROJECT_TREE';
export const SET_USER_POINTS = 'SET_USER_POINTS';
export const SET_SELECTED_FEATURE = 'SET_SELECTED_FEATURE';
+export const SET_ROUTINGBAR_VISIBLE = 'SET_ROUTINGBAR_VISIBLE';
+export const SET_IS_SEARCHING_ROUTE = 'SET_IS_SEARCHING_ROUTE';
export const setMymap = obj => dispatch => {
dispatch({
@@ -67,6 +70,22 @@ export const setSelectedFeature = obj => dispatch => {
})
}
+export const setRoutingBarVisible = obj => dispatch => {
+ dispatch({
+ type: SET_ROUTINGBAR_VISIBLE,
+ payload: obj
+ })
+}
+
+export const setIsSearchingRoute = obj => dispatch => {
+ dispatch({
+ type: SET_IS_SEARCHING_ROUTE,
+ payload: obj
+ })
+}
+
+
+
export const getUserPoints = async () => {
let userPoints = await ApiMapMonitoring.search();
// console.log('userPoints', userPoints);
@@ -84,6 +103,7 @@ export const getUserPoints = async () => {
}
feature.properties = {
+ "user_id": n.user_id,
"Name": n.join_first_name ? n.join_first_name : '-',
"Clock in time": n.clock_in ? moment(n.clock_in).format('YYYY-MM-DD HH:mm:ss') : '-',
"Clock in location": n.clock_in_loc ? n.clock_in_loc : '-',
@@ -101,4 +121,72 @@ export const getUserPoints = async () => {
store.dispatch(setUserPoints(featureCollection));
}
+}
+
+export const getUserHistory = async (userId, dateString) => {
+ console.log('[map->action.js] getUserHistory', userId, dateString);
+
+ store.dispatch(setIsSearchingRoute(true));
+ let userHistory = await ApiMapMonitoring.searchUserHistory(userId, dateString);
+ console.log('userHistory', userHistory);
+
+ if (userHistory.status && userHistory.data && userHistory.data.length > 0) {
+ let startIdx = 0;
+ let endIdx = userHistory.data.length - 1;
+ let featureCollection = {
+ "type": "FeatureCollection",
+ "features": []
+ }
+
+ // set waypoint start
+ let featurePointStart = {
+ "type": "Feature",
+ "properties": {
+ "type": "start",
+ "wptime": userHistory.data[startIdx].wptime
+ },
+ "geometry": {
+ "type": "Point",
+ "coordinates": [parseFloat(userHistory.data[startIdx].lon), parseFloat(userHistory.data[startIdx].lat)]
+ }
+ }
+ featureCollection.features.push(featurePointStart);
+
+ // set waypoint end
+ let featurePointEnd = {
+ "type": "Feature",
+ "properties": {
+ "type": "end",
+ "wptime": userHistory.data[endIdx].wptime
+ },
+ "geometry": {
+ "type": "Point",
+ "coordinates": [parseFloat(userHistory.data[endIdx].lon), parseFloat(userHistory.data[endIdx].lat)]
+ }
+ }
+ featureCollection.features.push(featurePointEnd);
+
+ // build waypoint line
+ let featureLine = {
+ "type": "Feature",
+ "properties": {},
+ "geometry": {
+ "type": "LineString",
+ "coordinates": []
+ }
+ }
+
+ userHistory.data.map(n => {
+ featureLine.geometry.coordinates.push([parseFloat(n.lon), parseFloat(n.lat)])
+ });
+ featureCollection.features.push(featureLine);
+
+ store.dispatch(setUserHistory(featureCollection));
+ store.dispatch(setIsSearchingRoute(false));
+ }
+ // kalo gak ada historynya
+ else if (!userHistory.status && !userHistory.data) {
+ toast.warn("Couldn't find user history at the selected time. Please select another range of time.");
+ }
+ store.dispatch(setIsSearchingRoute(false));
}
\ No newline at end of file
diff --git a/src/appredux/modules/map/reducers.js b/src/appredux/modules/map/reducers.js
index ba7ad8e..26f9e57 100644
--- a/src/appredux/modules/map/reducers.js
+++ b/src/appredux/modules/map/reducers.js
@@ -6,7 +6,9 @@ import {
SET_USER_POINTS,
SET_SELECTED_FEATURE,
SET_OPEN_LEFT,
- SET_OPEN_RIGHT
+ SET_OPEN_RIGHT,
+ SET_ROUTINGBAR_VISIBLE,
+ SET_IS_SEARCHING_ROUTE
} from "./actions";
const initialState = {
@@ -17,7 +19,9 @@ const initialState = {
userHistory: null,
projectTree: null,
userPoints: null,
- selectedFeature: null
+ selectedFeature: null,
+ routingBarVisible: false,
+ isSearchingRoute: false
}
function mapReducer(state = initialState, action) {
@@ -38,6 +42,10 @@ function mapReducer(state = initialState, action) {
return { ...state, userPoints: action.payload }
case SET_SELECTED_FEATURE:
return { ...state, selectedFeature: action.payload }
+ case SET_ROUTINGBAR_VISIBLE:
+ return { ...state, routingBarVisible: action.payload }
+ case SET_IS_SEARCHING_ROUTE:
+ return { ...state, isSearchingRoute: action.payload }
default:
return state;
}
diff --git a/src/components/MapLeftContent/index.js b/src/components/MapLeftContent/index.js
index edcd37b..76590d4 100644
--- a/src/components/MapLeftContent/index.js
+++ b/src/components/MapLeftContent/index.js
@@ -25,7 +25,7 @@ const MapLeftContent = () => {
console.log('selected', selectedKeys, info);
};
const onCheck = (checkedKeys, info) => {
- console.log('onCheck', checkedKeys, info);
+ console.log('onCheck', checkedKeys, info);
if (checkedKeys.length < 1) {
store.dispatch(setUserPoints(null));
@@ -100,59 +100,58 @@ const MapLeftContent = () => {
const Content = useMemo(() => {
return (
-
+
Project List
- {mapLoading ?
-
-
-
-
-
-
-
-
-
-
- :
- <>
- {/*
+ {mapLoading ?
+
+
+
+
+
+
+
+
+
+
+ :
+ <>
+ {/* */}
- {
- projectTree ?
-
- :
- null
- }
-
- >
-
- }
-
+ {
+ projectTree ?
+
+ :
+ null
+ }
+ >
+ }
+
)
}, [mapLoading])
diff --git a/src/components/MapRightContent/index.js b/src/components/MapRightContent/index.js
index 4e30b9b..2ea2b57 100644
--- a/src/components/MapRightContent/index.js
+++ b/src/components/MapRightContent/index.js
@@ -1,15 +1,8 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'
-import L from 'leaflet';
-import { Button, Col, Drawer, Input, Row, Spin, Tree } from 'antd';
-import ApiProject from '../../services/api/modules/project';
-import { store } from '../../appredux/store';
-import { setMapLoading, setProjectTree } from '../../appredux/modules/map/actions';
-import ContentLoader from 'react-content-loader';
import { useSelector } from 'react-redux';
+import PopupButtonActions from '../PopupButtonActions';
import './styles.css'
-const { Search } = Input;
-
const MapRightContent = () => {
const { mapLoading, selectedFeature } = useSelector(state => state.mapReducer);
@@ -62,40 +55,44 @@ const MapRightContent = () => {
const Content = useMemo(() => {
return (
-
+
Detail Information
-
-
-
- Name |
- : |
- {selectedFeature?.properties['Name']} |
-
-
- Clock in time |
- : |
- {selectedFeature?.properties['Clock in time']} |
-
-
- Clock in location |
- : |
- {selectedFeature?.properties['Clock in location']} |
-
-
- Clock out time |
- : |
- {selectedFeature?.properties['Clock out time']} |
-
-
- Clock out location |
- : |
- {selectedFeature?.properties['Clock out location']} |
-
-
-
+
+
+
+
+ Name |
+ : |
+ {selectedFeature?.properties['Name']} |
+
+
+ Clock in time |
+ : |
+ {selectedFeature?.properties['Clock in time']} |
+
+
+ Clock in location |
+ : |
+ {selectedFeature?.properties['Clock in location']} |
+
+
+ Clock out time |
+ : |
+ {selectedFeature?.properties['Clock out time']} |
+
+
+ Clock out location |
+ : |
+ {selectedFeature?.properties['Clock out location']} |
+
+
+
+
+
+
)
}, [selectedFeature])
diff --git a/src/components/PopupButtonActions/index.js b/src/components/PopupButtonActions/index.js
new file mode 100644
index 0000000..3c0b7d4
--- /dev/null
+++ b/src/components/PopupButtonActions/index.js
@@ -0,0 +1,32 @@
+import { Button, Tooltip } from 'antd';
+import React, { useEffect, useMemo, useRef, useState } from 'react'
+import { useSelector } from 'react-redux';
+import routeIcon from '@iconify/icons-fa-solid/route';
+import {Icon} from '@iconify/react';
+import { store } from '../../appredux/store';
+import { setRoutingBarVisible } from '../../appredux/modules/map/actions';
+
+const PopupButtonActions = () => {
+ const { selectedFeature } = useSelector(state => state.mapReducer);
+
+ const handleRoute = () => {
+ console.log('handleRoute');
+ store.dispatch(setRoutingBarVisible(true));
+ }
+
+ const Content = useMemo(() => {
+ return (
+
+
+ } size="large"
+ onClick={handleRoute}
+ />
+
+
+ )
+ }, [selectedFeature])
+
+ return Content;
+}
+
+export default PopupButtonActions;
\ No newline at end of file
diff --git a/src/components/RoutingBarV2/RoutingBarV2.css b/src/components/RoutingBarV2/RoutingBarV2.css
new file mode 100644
index 0000000..2653fe4
--- /dev/null
+++ b/src/components/RoutingBarV2/RoutingBarV2.css
@@ -0,0 +1,77 @@
+.routingbar-container {
+ z-index: 999;
+ position: absolute;
+ top: 0;
+}
+
+.routingbar-content {
+ text-align: center;
+ display: block;
+ position: relative;
+ top: 10px;
+ left: 15%;
+ padding: 5px;
+ z-index: 50;
+ background-color: rgba(0,0,0,0.5);
+ border-radius: 10px;
+ width: 470px;
+}
+
+.routingbar-header-content {
+ color: #ffffff;
+ line-height: 17px;
+ margin-top: 10px;
+}
+
+.routingbar-title {
+ font-size: 18px;
+ font-weight: bold;
+}
+
+.routingbar-sales-name {
+ font-size: 12px;
+}
+
+.routingbar-close {
+ float: right;
+ margin-right: 5px;
+ margin-top: -60px;
+ cursor: pointer;
+ color: #ffffff;
+}
+
+.routingbar-body-content {
+ margin-bottom: 5px;
+}
+
+.routingbar-label-container {
+ text-align: center !important;
+ /* margin-left: 63px; */
+ margin-bottom: 5px;
+ font-weight: 700;
+}
+
+.routingbar-label {
+ color: #ffffff;
+ font-size: 12px;
+}
+
+.routingbar-range-picker {
+ margin-bottom: 10px;
+ display: inline-block;
+}
+
+.routingbar-apply-button-container {
+ display: inline-block;
+ margin-left: 10px;
+ margin-top: -100px;
+ top: -100px;
+}
+
+.routingbar-apply-button {
+ margin-top: -5px !important;
+}
+
+.text-white {
+ color: #FFFFFF;
+}
\ No newline at end of file
diff --git a/src/components/RoutingBarV2/index.js b/src/components/RoutingBarV2/index.js
new file mode 100644
index 0000000..f9c9d46
--- /dev/null
+++ b/src/components/RoutingBarV2/index.js
@@ -0,0 +1,89 @@
+import React, { Component, useState } from 'react';
+// import { Button, ButtonGroup, UncontrolledTooltip } from 'reactstrap';
+import { Icon, InlineIcon } from '@iconify/react';
+import closeCircleOutline from '@iconify/icons-ion/close-circle-outline';
+import moment from 'moment';
+import './RoutingBarV2.css';
+import { useSelector } from 'react-redux';
+import { getUserHistory, setRoutingBarVisible, setUserHistory } from '../../appredux/modules/map/actions';
+import { store } from '../../appredux/store';
+import { Button, DatePicker } from 'antd';
+import { removeLayerByName } from '../../utils/MapUtils';
+
+const { RangePicker } = DatePicker;
+
+const RoutingBar = () => {
+
+ const { selectedFeature, routingBarVisible, isSearchingRoute } = useSelector(state => state.mapReducer);
+ const [dateString, setDateString] = useState(null);
+
+ const onChange = (value, dateString) => {
+ console.log('Selected Time: ', value);
+ console.log('Formatted Selected Time: ', dateString);
+ }
+
+ const onOk = (value) => {
+ let dateString = [];
+ dateString[0] = moment(value[0]).format("YYYY-MM-DD HH:mm");
+ dateString[1] = moment(value[1]).format("YYYY-MM-DD HH:mm");
+ setDateString(dateString)
+ }
+
+ const applyRouting = () => {
+ let userId = selectedFeature?.properties.user_id;
+ getUserHistory(userId, dateString);
+ }
+
+ const handleCloseRoutingBar = () => {
+ store.dispatch(setRoutingBarVisible(false))
+ removeLayerByName('userHistoryLayer');
+ store.dispatch(setUserHistory(null));
+ }
+
+ return (
+
+
+
+ User History
+ {selectedFeature?.properties.Name}
+
+
+
+
+ Select Start and End Time
+
+
+
+
+
+
+
+
+ {/* */}
+
+
+
+
+
+
+ )
+}
+export default RoutingBar;
\ No newline at end of file
diff --git a/src/services/api/modules/map_monitoring/index.js b/src/services/api/modules/map_monitoring/index.js
index 1f1936b..6a989cb 100644
--- a/src/services/api/modules/map_monitoring/index.js
+++ b/src/services/api/modules/map_monitoring/index.js
@@ -69,4 +69,49 @@ export default class ApiMapMonitoring extends RequestApi {
return {status: false, data: null};
});
}
+
+
+ static async searchUserHistory(userId, dateString) {
+ const URL = `${BASE_SIMPRO_LUMEN}/waypoint/search`
+ const payload = {
+ "paging": { "start": 0, "length": -1 },
+ "columns": [
+ { "name": "user_id", "logic_operator": "=", "value": userId },
+ { "name": "wptime", "logic_operator": "range", "value": dateString[0], "value1": dateString[1] }
+ ],
+ "orders": { "columns": ["wptime"], "ascending": true }
+ }
+
+ return await RequestApi.Request().post(
+ URL,
+ payload,
+ RequestApi.HeaderWithToken()).then(res => {
+ if (res) {
+ if (res && res.data && res.data.data) {
+ // console.log('ApiPresence search', res.data.data)
+ if (res.data.data.length > 0) {
+ return {status: true, data: res.data.data};
+ }
+ else {
+ return {status: false, data: null}
+ }
+ }
+ else {
+ return {status: false, data: null};
+ }
+ }
+ else {
+ alert("Please check your internet connection.", "error");
+ return {status: false, data: null};
+ }
+ }).catch(e => {
+ // console.log('error search presence', e);
+ let error = JSON.parse(JSON.stringify(e));
+ console.log('error search presence', error);
+ if (error.message) {
+ alert(error.message);
+ }
+ return {status: false, data: null};
+ });
+ }
}
\ No newline at end of file
diff --git a/src/views/MapMonitoring/MapMonitoring.css b/src/views/MapMonitoring/MapMonitoring.css
new file mode 100644
index 0000000..1ad7e4c
--- /dev/null
+++ b/src/views/MapMonitoring/MapMonitoring.css
@@ -0,0 +1,9 @@
+.loader-container {
+ text-align: center;
+ align-content: center;
+ top: 100px;
+ position: fixed;
+ z-index: 999999;
+ align-self: center;
+ margin-left: 45%;
+}
\ No newline at end of file
diff --git a/src/views/MapMonitoring/index.js b/src/views/MapMonitoring/index.js
index 10985c2..3623a77 100644
--- a/src/views/MapMonitoring/index.js
+++ b/src/views/MapMonitoring/index.js
@@ -9,6 +9,13 @@ import { useSelector } from 'react-redux';
import MapLeftContent from '../../components/MapLeftContent';
import MapRightContent from '../../components/MapRightContent';
import { removeLayerByName } from '../../utils/MapUtils';
+import RoutingBar from '../../components/RoutingBarV2';
+import Loader from "react-loader-spinner";
+import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
+import { ToastContainer, toast } from "react-toastify";
+import "react-toastify/dist/ReactToastify.css";
+import './MapMonitoring.css';
+
const MapMonitoring = () => {
@@ -24,7 +31,7 @@ const MapMonitoring = () => {
lng: 120.13025155062624
}
- const {userPoints, mymap, openLeft, openRight} = useSelector(state => state.mapReducer);
+ const {userPoints, mymap, openLeft, openRight, routingBarVisible, userHistory, isSearchingRoute} = useSelector(state => state.mapReducer);
const [gridMiddle, setGridMiddle] = useState(GRID_MIDDLE);
const [gridLeft, setGridLeft] = useState(0);
const [gridRight, setGridRight] = useState(0);
@@ -46,14 +53,28 @@ const MapMonitoring = () => {
if (userPoints) {
let userPointLayer = L.geoJson(userPoints, {
name: 'userPointLayer',
- onEachFeature: onEachFeatureUserPoints,
+ onEachFeature: onEachFeatureUserPoints
});
userPointLayer.addTo(mymap);
+ mymap.fitBounds(userPointLayer.getBounds());
}
}
}, [userPoints])
+ useEffect(() => {
+ if (mymap) {
+ removeLayerByName('userHistoryLayer');
+ if (userHistory) {
+ let userHistoryLayer = L.geoJson(userHistory, {
+ name: 'userHistoryLayer'
+ });
+ userHistoryLayer.addTo(mymap);
+ mymap.fitBounds(userHistoryLayer.getBounds());
+ }
+ }
+ }, [userHistory])
+
const initMap = () => {
let mymap = L.map('map-area', {
center: center,
@@ -64,9 +85,12 @@ const MapMonitoring = () => {
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '©
OpenStreetMap contributors' }).addTo(mymap);
mymap.on('click', (e) => {
- removeLayerByName('popupTemp');
- store.dispatch(setOpenRight(false));
- store.dispatch(setSelectedFeature(null));
+ if (!store.getState().mapReducer.routingBarVisible) {
+ // only can close popup when routing mode is not visible
+ removeLayerByName('popupTemp');
+ store.dispatch(setOpenRight(false));
+ store.dispatch(setSelectedFeature(null));
+ }
})
}
@@ -99,7 +123,10 @@ const MapMonitoring = () => {
const onEachFeatureUserPoints = (feature, layer) => {
layer.on('click', function(e) {
L.DomEvent.stopPropagation(e);
- showHighLight(feature);
+ if (!store.getState().mapReducer.routingBarVisible) {
+ // proceed only when routing mode is not visible
+ showHighLight(feature);
+ }
});
}
@@ -130,18 +157,30 @@ const MapMonitoring = () => {
+ { routingBarVisible &&
}
+ { isSearchingRoute && (
+
+
+
+ )}
+
)
- }, [openLeft, openRight, gridLeft, gridMiddle, gridRight])
+ }, [openLeft, openRight, gridLeft, gridMiddle, gridRight, routingBarVisible ])
const renderGridMap = () => {
let middle = GRID_MIDDLE;