farhan048
2 years ago
21 changed files with 1374 additions and 62 deletions
@ -0,0 +1,192 @@ |
|||||||
|
import moment from "moment"; |
||||||
|
import { toast } from "react-toastify"; |
||||||
|
import ApiMapMonitoring from "../../../services/api/modules/map_monitoring"; |
||||||
|
import { store } from "../../store"; |
||||||
|
|
||||||
|
export const SET_MYMAP = 'SET_MYMAP'; |
||||||
|
export const SET_OPEN_LEFT = 'SET_OPEN_LEFT'; |
||||||
|
export const SET_OPEN_RIGHT = 'SET_OPEN_RIGHT'; |
||||||
|
export const SET_MAP_LOADING = 'SET_MAP_LOADING'; |
||||||
|
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({ |
||||||
|
type: SET_MYMAP, |
||||||
|
payload: obj |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export const setOpenLeft = obj => dispatch => { |
||||||
|
dispatch({ |
||||||
|
type: SET_OPEN_LEFT, |
||||||
|
payload: obj |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export const setOpenRight = obj => dispatch => { |
||||||
|
dispatch({ |
||||||
|
type: SET_OPEN_RIGHT, |
||||||
|
payload: obj |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export const setMapLoading = obj => dispatch => { |
||||||
|
dispatch({ |
||||||
|
type: SET_MAP_LOADING, |
||||||
|
payload: obj |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export const setUserHistory = obj => dispatch => { |
||||||
|
dispatch({ |
||||||
|
type: SET_USER_HISTORY, |
||||||
|
payload: obj |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export const setProjectTree = obj => dispatch => { |
||||||
|
dispatch({ |
||||||
|
type: SET_PROJECT_TREE, |
||||||
|
payload: obj |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export const setUserPoints = obj => dispatch => { |
||||||
|
dispatch({ |
||||||
|
type: SET_USER_POINTS, |
||||||
|
payload: obj |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export const setSelectedFeature = obj => dispatch => { |
||||||
|
dispatch({ |
||||||
|
type: SET_SELECTED_FEATURE, |
||||||
|
payload: obj |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
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);
|
||||||
|
if (userPoints.status && userPoints.data && userPoints.data.length > 0) { |
||||||
|
let featureCollection = { |
||||||
|
"type": "FeatureCollection", |
||||||
|
"features": [] |
||||||
|
} |
||||||
|
|
||||||
|
userPoints.data.map(n => { |
||||||
|
let feature = { |
||||||
|
"type": "Feature", |
||||||
|
"properties": null, |
||||||
|
"geometry": null |
||||||
|
} |
||||||
|
|
||||||
|
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 : '-', |
||||||
|
"Clock out time": n.clock_out ? moment(n.clock_out).format('YYYY-MM-DD HH:mm:ss') : '-', |
||||||
|
"Clock out location": n.clock_out_loc ? n.clock_out_loc : '-', |
||||||
|
} |
||||||
|
|
||||||
|
feature.geometry = { |
||||||
|
"type": "Point", |
||||||
|
"coordinates": [n.clock_in_lng, n.clock_in_lat] |
||||||
|
} |
||||||
|
|
||||||
|
featureCollection.features.push(feature); |
||||||
|
}); |
||||||
|
|
||||||
|
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)); |
||||||
|
} |
@ -0,0 +1,54 @@ |
|||||||
|
import { |
||||||
|
SET_MYMAP, |
||||||
|
SET_MAP_LOADING, |
||||||
|
SET_USER_HISTORY, |
||||||
|
SET_PROJECT_TREE, |
||||||
|
SET_USER_POINTS, |
||||||
|
SET_SELECTED_FEATURE, |
||||||
|
SET_OPEN_LEFT, |
||||||
|
SET_OPEN_RIGHT, |
||||||
|
SET_ROUTINGBAR_VISIBLE, |
||||||
|
SET_IS_SEARCHING_ROUTE |
||||||
|
} from "./actions"; |
||||||
|
|
||||||
|
const initialState = { |
||||||
|
mymap: null, |
||||||
|
openLeft: true, |
||||||
|
openRight: false, |
||||||
|
mapLoading: false, |
||||||
|
userHistory: null, |
||||||
|
projectTree: null, |
||||||
|
userPoints: null, |
||||||
|
selectedFeature: null, |
||||||
|
routingBarVisible: false, |
||||||
|
isSearchingRoute: false |
||||||
|
} |
||||||
|
|
||||||
|
function mapReducer(state = initialState, action) { |
||||||
|
switch (action.type) { |
||||||
|
case SET_MYMAP: |
||||||
|
return { ...state, mymap: action.payload } |
||||||
|
case SET_OPEN_LEFT: |
||||||
|
return { ...state, openLeft: action.payload } |
||||||
|
case SET_OPEN_RIGHT: |
||||||
|
return { ...state, openRight: action.payload } |
||||||
|
case SET_MAP_LOADING: |
||||||
|
return { ...state, mapLoading: action.payload } |
||||||
|
case SET_USER_HISTORY: |
||||||
|
return { ...state, userHistory: action.payload } |
||||||
|
case SET_PROJECT_TREE: |
||||||
|
return { ...state, projectTree: action.payload } |
||||||
|
case SET_USER_POINTS: |
||||||
|
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; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default mapReducer; |
@ -0,0 +1,46 @@ |
|||||||
|
// import { parse, stringify } from 'flatted';
|
||||||
|
import { combineReducers } from 'redux'; |
||||||
|
import { persistReducer, createTransform } from 'redux-persist'; |
||||||
|
import storage from 'redux-persist/lib/storage'; |
||||||
|
|
||||||
|
// modules
|
||||||
|
import mapReducer from './modules/map/reducers'; |
||||||
|
|
||||||
|
const rootReducer = combineReducers({ |
||||||
|
mapReducer |
||||||
|
}); |
||||||
|
|
||||||
|
// export const transformCircular = createTransform(
|
||||||
|
// // (inboundState, key) => JSON.stringify(inboundState),
|
||||||
|
// // (outboundState, key) => JSON.parse(outboundState),
|
||||||
|
// (inboundState, key) => stringify(inboundState),
|
||||||
|
// (outboundState, key) => parse(outboundState),
|
||||||
|
// )
|
||||||
|
|
||||||
|
|
||||||
|
// const SetTransform = createTransform(
|
||||||
|
// // transform state on its way to being serialized and persisted.
|
||||||
|
// (inboundState, key) => {
|
||||||
|
// // convert mySet to an Array.
|
||||||
|
// return { ...inboundState, mySet: [...inboundState.mySet] };
|
||||||
|
// },
|
||||||
|
// // transform state being rehydrated
|
||||||
|
// (outboundState, key) => {
|
||||||
|
// // convert mySet back to a Set.
|
||||||
|
// return { ...outboundState, mySet: new Set(outboundState.mySet) };
|
||||||
|
// },
|
||||||
|
// // define which reducers this transform gets called for.
|
||||||
|
// { whitelist: ['mapReducer'] }
|
||||||
|
// );
|
||||||
|
|
||||||
|
const persistConfig = { |
||||||
|
key: 'root', |
||||||
|
storage, |
||||||
|
// blacklist: [], // to be not persisted
|
||||||
|
// transforms: [SetTransform]
|
||||||
|
// transforms: [transformCircular]
|
||||||
|
}; |
||||||
|
|
||||||
|
// export default persistReducer(persistConfig, rootReducer);
|
||||||
|
|
||||||
|
export default rootReducer; |
@ -0,0 +1,46 @@ |
|||||||
|
import { configureStore } from "@reduxjs/toolkit"; |
||||||
|
import { applyMiddleware, compose, createStore } from "redux"; |
||||||
|
// import thunk from "redux-thunk";
|
||||||
|
import { persistStore } from 'redux-persist'; |
||||||
|
import thunk from "redux-thunk"; |
||||||
|
import thunkMiddleware from 'redux-thunk'; |
||||||
|
import persistedReducer from "./reducers"; |
||||||
|
|
||||||
|
// const enhancers = [
|
||||||
|
// applyMiddleware(
|
||||||
|
// thunkMiddleware,
|
||||||
|
// // createLogger({
|
||||||
|
// // collapsed: true,
|
||||||
|
// // // eslint-disable-next-line no-undef
|
||||||
|
// // predicate: () => __DEV__,
|
||||||
|
// // }),
|
||||||
|
// ),
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// /* eslint-disable no-undef */
|
||||||
|
// const composeEnhancers =
|
||||||
|
// (__DEV__ &&
|
||||||
|
// typeof window !== 'undefined' &&
|
||||||
|
// window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||
|
||||||
|
// compose;
|
||||||
|
// /* eslint-enable no-undef */
|
||||||
|
|
||||||
|
// const enhancer = composeEnhancers(...enhancers);
|
||||||
|
|
||||||
|
// export const store = createStore(persistedReducer, {}, enhancer)
|
||||||
|
// // export const store = configureStore(persistedReducer, enhancer);
|
||||||
|
// // export const store = configureStore( {
|
||||||
|
// // persistedReducer,
|
||||||
|
// // enhancers: enhancer
|
||||||
|
// // })
|
||||||
|
// export const persistor = persistStore(store);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const store = configureStore({ |
||||||
|
reducer: persistedReducer, |
||||||
|
devTools: process.env.NODE_ENV !== 'production', |
||||||
|
middleware: [thunk] |
||||||
|
}) |
||||||
|
|
||||||
|
export const persistor = persistStore(store) |
@ -0,0 +1,162 @@ |
|||||||
|
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 { getUserPoints, setMapLoading, setOpenRight, setProjectTree, setSelectedFeature, setUserPoints } from '../../appredux/modules/map/actions'; |
||||||
|
import ContentLoader from 'react-content-loader'; |
||||||
|
import { useSelector } from 'react-redux'; |
||||||
|
import ApiMapMonitoring from '../../services/api/modules/map_monitoring'; |
||||||
|
|
||||||
|
const { Search } = Input; |
||||||
|
|
||||||
|
const MapLeftContent = () => { |
||||||
|
// const { mapLoading } = store.getState().mapReducer;
|
||||||
|
const { mapLoading, projectTree } = useSelector(state => state.mapReducer); |
||||||
|
// const [expandedKeys, setExpandedKeys] = useState([]);
|
||||||
|
// const [searchValue, setSearchValue] = useState('');
|
||||||
|
// const [autoExpandParent, setAutoExpandParent] = useState(true);
|
||||||
|
// const onExpand = (newExpandedKeys) => {
|
||||||
|
// setExpandedKeys(newExpandedKeys);
|
||||||
|
// setAutoExpandParent(false);
|
||||||
|
// };
|
||||||
|
|
||||||
|
const onSelect = (selectedKeys, info) => { |
||||||
|
console.log('selected', selectedKeys, info); |
||||||
|
}; |
||||||
|
const onCheck = (checkedKeys, info) => { |
||||||
|
console.log('onCheck', checkedKeys, info); |
||||||
|
|
||||||
|
if (checkedKeys.length < 1) { |
||||||
|
store.dispatch(setUserPoints(null)); |
||||||
|
store.dispatch(setOpenRight(false)); |
||||||
|
store.dispatch(setSelectedFeature(null)); |
||||||
|
return; |
||||||
|
} |
||||||
|
getUserPoints(); |
||||||
|
}; |
||||||
|
|
||||||
|
// const getParentKey = (key, tree) => {
|
||||||
|
// let parentKey;
|
||||||
|
// for (let i = 0; i < tree.length; i++) {
|
||||||
|
// const node = tree[i];
|
||||||
|
// if (node.children) {
|
||||||
|
// if (node.children.some((item) => item.key === key)) {
|
||||||
|
// parentKey = node.key;
|
||||||
|
// } else if (getParentKey(key, node.children)) {
|
||||||
|
// parentKey = getParentKey(key, node.children);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return parentKey;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const treeData = useMemo(() => {
|
||||||
|
// const loop = (data) =>
|
||||||
|
// data.map((item) => {
|
||||||
|
// const strTitle = item.title;
|
||||||
|
// const index = strTitle.indexOf(searchValue);
|
||||||
|
// const beforeStr = strTitle.substring(0, index);
|
||||||
|
// const afterStr = strTitle.slice(index + searchValue.length);
|
||||||
|
// const title =
|
||||||
|
// index > -1 ? (
|
||||||
|
// <span>
|
||||||
|
// {beforeStr}
|
||||||
|
// <span className="site-tree-search-value">{searchValue}</span>
|
||||||
|
// {afterStr}
|
||||||
|
// </span>
|
||||||
|
// ) : (
|
||||||
|
// <span>{strTitle}</span>
|
||||||
|
// );
|
||||||
|
// if (item.children) {
|
||||||
|
// return {
|
||||||
|
// title,
|
||||||
|
// key: item.key,
|
||||||
|
// children: loop(item.children),
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// return {
|
||||||
|
// title,
|
||||||
|
// key: item.key,
|
||||||
|
// };
|
||||||
|
// });
|
||||||
|
// return loop(projectTree);
|
||||||
|
// }, [searchValue]);
|
||||||
|
|
||||||
|
// const onChangeSearch = (e) => {
|
||||||
|
// const { value } = e.target;
|
||||||
|
// const newExpandedKeys = projectTree
|
||||||
|
// .map((item) => {
|
||||||
|
// if (item.title.indexOf(value) > -1) {
|
||||||
|
// return getParentKey(item.key, projectTree);
|
||||||
|
// }
|
||||||
|
// return null;
|
||||||
|
// })
|
||||||
|
// .filter((item, i, self) => item && self.indexOf(item) === i);
|
||||||
|
// setExpandedKeys(newExpandedKeys);
|
||||||
|
// setSearchValue(value);
|
||||||
|
// setAutoExpandParent(true);
|
||||||
|
// };
|
||||||
|
|
||||||
|
const Content = useMemo(() => { |
||||||
|
return ( |
||||||
|
<div style={{ paddingRight: 10 }}> |
||||||
|
<div style={{ backgroundColor: '#f0f0f0', padding: 10, marginBottom: 5, fontWeight: 'bold', fontSize: 16 }}> |
||||||
|
Project List |
||||||
|
</div> |
||||||
|
|
||||||
|
<div style={{ maxHeight: '80vh', overflow: 'auto' }}> |
||||||
|
{mapLoading ? |
||||||
|
<ContentLoader |
||||||
|
speed={2} |
||||||
|
width="100%" |
||||||
|
height="200" |
||||||
|
viewBox="0 0 200 200" |
||||||
|
backgroundColor="#f3f3f3" |
||||||
|
foregroundColor="#ecebeb" |
||||||
|
> |
||||||
|
<circle cx="10" cy="20" r="8" /> |
||||||
|
<rect x="25" y="15" rx="5" ry="5" width="220" height="10" /> |
||||||
|
<circle cx="10" cy="50" r="8" /> |
||||||
|
<rect x="25" y="45" rx="5" ry="5" width="220" height="10" /> |
||||||
|
<circle cx="10" cy="80" r="8" /> |
||||||
|
<rect x="25" y="75" rx="5" ry="5" width="220" height="10" /> |
||||||
|
<circle cx="10" cy="110" r="8" /> |
||||||
|
<rect x="25" y="105" rx="5" ry="5" width="220" height="10" /> |
||||||
|
</ContentLoader> |
||||||
|
: |
||||||
|
<> |
||||||
|
{/* <Search |
||||||
|
style={{ |
||||||
|
marginBottom: 8, |
||||||
|
}} |
||||||
|
placeholder="Search" |
||||||
|
onChange={onChangeSearch} |
||||||
|
/> */} |
||||||
|
{ |
||||||
|
projectTree ? |
||||||
|
<Tree |
||||||
|
checkable |
||||||
|
defaultExpandedKeys={['all']} |
||||||
|
defaultCheckedKeys={['all']} |
||||||
|
onSelect={onSelect} |
||||||
|
onCheck={onCheck} |
||||||
|
// onExpand={onExpand}
|
||||||
|
// expandedKeys={expandedKeys}
|
||||||
|
// autoExpandParent={autoExpandParent}
|
||||||
|
treeData={projectTree} |
||||||
|
/> |
||||||
|
: |
||||||
|
null |
||||||
|
} |
||||||
|
</> |
||||||
|
} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
}, [mapLoading]) |
||||||
|
|
||||||
|
return Content; |
||||||
|
} |
||||||
|
|
||||||
|
export default MapLeftContent; |
@ -0,0 +1,103 @@ |
|||||||
|
import React, { useEffect, useMemo, useRef, useState } from 'react' |
||||||
|
import { useSelector } from 'react-redux'; |
||||||
|
import PopupButtonActions from '../PopupButtonActions'; |
||||||
|
import './styles.css' |
||||||
|
|
||||||
|
const MapRightContent = () => { |
||||||
|
const { mapLoading, selectedFeature } = useSelector(state => state.mapReducer); |
||||||
|
|
||||||
|
const PopupContent = useMemo(() => { |
||||||
|
console.log('selectedFeature', selectedFeature); |
||||||
|
if (selectedFeature && selectedFeature.properties) { |
||||||
|
// let content = [];
|
||||||
|
// for (let key in selectedFeature.properties) {
|
||||||
|
// content.push(<tr key={key}>
|
||||||
|
// <td>{`${key}`}</td>
|
||||||
|
// <td>:</td>
|
||||||
|
// <td>{`${selectedFeature.properties[key]}`}</td>
|
||||||
|
// </tr>)
|
||||||
|
// }
|
||||||
|
// console.log('content', content);
|
||||||
|
|
||||||
|
return ( |
||||||
|
<table className="table popup-table"> |
||||||
|
<tbody> |
||||||
|
<tr> |
||||||
|
<td>Name</td> |
||||||
|
<td>:</td> |
||||||
|
<td>{selectedFeature?.properties['Name']}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>Clock in time</td> |
||||||
|
<td>:</td> |
||||||
|
<td>{selectedFeature?.properties['Clock in time']}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>Clock in location</td> |
||||||
|
<td>:</td> |
||||||
|
<td>{selectedFeature?.properties['Clock in location']}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>Clock out time</td> |
||||||
|
<td>:</td> |
||||||
|
<td>{selectedFeature?.properties['Clock out time']}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td>Clock out location</td> |
||||||
|
<td>:</td> |
||||||
|
<td>{selectedFeature?.properties['Clock out location']}</td> |
||||||
|
</tr> |
||||||
|
</tbody> |
||||||
|
</table> |
||||||
|
) |
||||||
|
} |
||||||
|
}, [selectedFeature]) |
||||||
|
|
||||||
|
const Content = useMemo(() => { |
||||||
|
return ( |
||||||
|
<div style={{ paddingLeft: 10 }}> |
||||||
|
<div style={{ backgroundColor: '#f0f0f0', padding: 10, marginBottom: 5, fontWeight: 'bold', fontSize: 16 }}> |
||||||
|
Detail Information |
||||||
|
</div> |
||||||
|
|
||||||
|
<div style={{height: '75vh', overflow: 'auto'}}> |
||||||
|
<table className="table popup-table"> |
||||||
|
<tbody> |
||||||
|
<tr> |
||||||
|
<td className='td-popup-label'>Name</td> |
||||||
|
<td>:</td> |
||||||
|
<td>{selectedFeature?.properties['Name']}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td className='td-popup-label'>Clock in time</td> |
||||||
|
<td>:</td> |
||||||
|
<td>{selectedFeature?.properties['Clock in time']}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td className='td-popup-label'>Clock in location</td> |
||||||
|
<td>:</td> |
||||||
|
<td>{selectedFeature?.properties['Clock in location']}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td className='td-popup-label'>Clock out time</td> |
||||||
|
<td>:</td> |
||||||
|
<td>{selectedFeature?.properties['Clock out time']}</td> |
||||||
|
</tr> |
||||||
|
<tr> |
||||||
|
<td className='td-popup-label'>Clock out location</td> |
||||||
|
<td>:</td> |
||||||
|
<td>{selectedFeature?.properties['Clock out location']}</td> |
||||||
|
</tr> |
||||||
|
</tbody> |
||||||
|
</table> |
||||||
|
</div> |
||||||
|
|
||||||
|
<PopupButtonActions /> |
||||||
|
</div> |
||||||
|
) |
||||||
|
}, [selectedFeature]) |
||||||
|
|
||||||
|
return Content; |
||||||
|
} |
||||||
|
|
||||||
|
export default MapRightContent; |
@ -0,0 +1,9 @@ |
|||||||
|
table th, .table td { |
||||||
|
padding: 0.25rem !important; |
||||||
|
vertical-align: top; |
||||||
|
border-top: 1px solid #c8ced3; |
||||||
|
} |
||||||
|
|
||||||
|
.td-popup-label { |
||||||
|
color: #808080; |
||||||
|
} |
@ -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 ( |
||||||
|
<div style={{ paddingLeft: 10, bottom: 10, textAlign: 'center', margin: 'auto' }}> |
||||||
|
<Tooltip title="View Tracking History"> |
||||||
|
<Button shape="circle" color='' icon={<Icon icon={routeIcon} width={15} height={15} />} size="large"
|
||||||
|
onClick={handleRoute} |
||||||
|
/> |
||||||
|
</Tooltip> |
||||||
|
</div> |
||||||
|
) |
||||||
|
}, [selectedFeature]) |
||||||
|
|
||||||
|
return Content; |
||||||
|
} |
||||||
|
|
||||||
|
export default PopupButtonActions; |
@ -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; |
||||||
|
} |
@ -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 ( |
||||||
|
<div className="routingbar-container"> |
||||||
|
<div className="routingbar-content"> |
||||||
|
<p className="routingbar-header-content"> |
||||||
|
<span className="routingbar-title">User History</span><br /> |
||||||
|
<span className="routingbar-sales-name">{selectedFeature?.properties.Name}</span><br /> |
||||||
|
</p> |
||||||
|
<span className="routingbar-close" onClick={handleCloseRoutingBar}><Icon icon={closeCircleOutline} width={25} height={25} /></span> |
||||||
|
|
||||||
|
<div className="routingbar-label-container"> |
||||||
|
<span className="routingbar-label">Select Start and End Time</span> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="routingbar-body-content"> |
||||||
|
<div className="routingbar-range-picker"> |
||||||
|
<RangePicker |
||||||
|
showTime={{ format: 'HH:mm' }} |
||||||
|
format="YYYY-MM-DD HH:mm" |
||||||
|
placeholder={['Start Time', 'End Time']} |
||||||
|
onChange={onChange} |
||||||
|
onOk={onOk} |
||||||
|
disabled={isSearchingRoute} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="routingbar-apply-button-container"> |
||||||
|
{/* <Button |
||||||
|
color="primary" size="sm" className="routingbar-apply-button" |
||||||
|
disabled={isSearchingRoute} |
||||||
|
onClick={applyRouting} |
||||||
|
>Search</Button> */} |
||||||
|
|
||||||
|
<Button
|
||||||
|
type="primary" |
||||||
|
size="small" |
||||||
|
disabled={isSearchingRoute} |
||||||
|
loading={isSearchingRoute} |
||||||
|
onClick={applyRouting}> |
||||||
|
Search |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
) |
||||||
|
} |
||||||
|
export default RoutingBar; |
@ -0,0 +1,99 @@ |
|||||||
|
import axios from 'axios'; |
||||||
|
export default class RequestApi { |
||||||
|
// static Request() {
|
||||||
|
// // axios.interceptors.request.use(function (config) {
|
||||||
|
// // const token = localStorage.getItem('token')
|
||||||
|
// // config.headers.Authorization = token;
|
||||||
|
|
||||||
|
// // return config;
|
||||||
|
// // });
|
||||||
|
// const token = localStorage.getItem('token')
|
||||||
|
|
||||||
|
// let instance = axios.create({
|
||||||
|
// headers: {
|
||||||
|
// 'Content-Type': 'application/json',
|
||||||
|
// "Authorization": `Bearer ${token}`
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
// instance.interceptors.response.use(
|
||||||
|
// (response) => response,
|
||||||
|
// async (error) => {
|
||||||
|
// // const originalRequest = error.config;
|
||||||
|
// if (error.response.status === 307 || error.response.status === 403) {
|
||||||
|
// console.log(error.response);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return Promise.reject(error);
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
|
||||||
|
// return instance;
|
||||||
|
// }
|
||||||
|
|
||||||
|
static Request() { |
||||||
|
axios.interceptors.response.use( |
||||||
|
response => response, |
||||||
|
async (error) => { |
||||||
|
// console.log('error axios', error);
|
||||||
|
if (error) { |
||||||
|
// console.log('stringify', JSON.stringify(error));
|
||||||
|
const err = JSON.parse(JSON.stringify(error)); |
||||||
|
if (err.name === 'AxiosError' && err.code === 'ERR_NETWORK') { |
||||||
|
alert(err.message); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (err.response) { |
||||||
|
if (err.response.status === 307 || err.response.status === 403) { |
||||||
|
// console.log(err.response);
|
||||||
|
alert('Token expired, please re-login'); |
||||||
|
// clearAllState();
|
||||||
|
window.localStorage.clear(); |
||||||
|
// this.props.history.replace('/login');
|
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
return Promise.reject(error); |
||||||
|
} |
||||||
|
} |
||||||
|
); |
||||||
|
return axios; |
||||||
|
} |
||||||
|
|
||||||
|
static Header() { |
||||||
|
let header = { |
||||||
|
headers: { |
||||||
|
"Content-Type": "application/json", |
||||||
|
// 'Cache-Control': 'no-cache'
|
||||||
|
} |
||||||
|
} |
||||||
|
return header; |
||||||
|
} |
||||||
|
|
||||||
|
static HeaderWithToken() { |
||||||
|
const token = localStorage.getItem('token') |
||||||
|
let header = { |
||||||
|
headers: { |
||||||
|
"Content-Type": "application/json", |
||||||
|
"Authorization": `Bearer ${token}`, |
||||||
|
// 'Cache-Control': 'no-cache',
|
||||||
|
} |
||||||
|
} |
||||||
|
return header; |
||||||
|
} |
||||||
|
|
||||||
|
static HeaderMultipart() { |
||||||
|
const token = localStorage.getItem('token') |
||||||
|
let header = { |
||||||
|
headers: { |
||||||
|
"Content-Type": "multipart/form-data", |
||||||
|
"Authorization": `Bearer ${token}`, |
||||||
|
// 'Cache-Control': 'no-cache',
|
||||||
|
} |
||||||
|
} |
||||||
|
return header; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
export const AXIOS = RequestApi.Request(); |
||||||
|
|
@ -0,0 +1,117 @@ |
|||||||
|
import moment from "moment"; |
||||||
|
import { BASE_SIMPRO_LUMEN } from "../../../../const/ApiConst"; |
||||||
|
import RequestApi from '../../base'; |
||||||
|
|
||||||
|
export default class ApiMapMonitoring extends RequestApi { |
||||||
|
static async search() { |
||||||
|
const URL = `${BASE_SIMPRO_LUMEN}/presence/search` |
||||||
|
const dateFrom = moment().subtract(7,'d').format('YYYY-MM-DD 00:00:00'); |
||||||
|
const dateTo = moment().format('YYYY-MM-DD 23:59:00'); |
||||||
|
const payload = { |
||||||
|
"columns": [ |
||||||
|
{ |
||||||
|
"logic_operator": "range", |
||||||
|
"name": "created_at", |
||||||
|
"operator": "AND", |
||||||
|
"value": dateFrom, |
||||||
|
"value1": dateTo |
||||||
|
} |
||||||
|
], |
||||||
|
"joins": [ |
||||||
|
{ |
||||||
|
"column_join": "user_id", |
||||||
|
"column_results": [ |
||||||
|
"username", "name" |
||||||
|
], |
||||||
|
"name": "m_users" |
||||||
|
} |
||||||
|
], |
||||||
|
"orders": { |
||||||
|
"ascending": false, |
||||||
|
"columns": [ |
||||||
|
"created_at" |
||||||
|
] |
||||||
|
}, |
||||||
|
"paging": { |
||||||
|
"length": 25, |
||||||
|
"start": 0 |
||||||
|
} |
||||||
|
} |
||||||
|
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}; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
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}; |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
import { BASE_SIMPRO_LUMEN } from "../../../../const/ApiConst"; |
||||||
|
import RequestApi from '../../base'; |
||||||
|
|
||||||
|
export default class ApiProject extends RequestApi { |
||||||
|
static async list() { |
||||||
|
// const user_id = store.getState().userReducer && store.getState().userReducer.user && store.getState().userReducer.user.data_user ? store.getState().userReducer.user.data_user.id : 0;
|
||||||
|
const URL = `${BASE_SIMPRO_LUMEN}/project/list` |
||||||
|
// const payload = {
|
||||||
|
// "paging": { "start": 0, "length": 25 },
|
||||||
|
// // "columns": [
|
||||||
|
// // { "name": "user_id", "logic_operator": "=", "value": user_id }
|
||||||
|
// // ],
|
||||||
|
// // "joins": [
|
||||||
|
// // {
|
||||||
|
// // "name": "m_proyek",
|
||||||
|
// // "column_join": "proyek_id",
|
||||||
|
// // "column_results": [
|
||||||
|
// // "nama", "kode_sortname", "mulai_proyek", "akhir_proyek"
|
||||||
|
// // ]
|
||||||
|
// // },
|
||||||
|
// // {
|
||||||
|
// // "name": "m_activity",
|
||||||
|
// // "column_join": "activity_id",
|
||||||
|
// // "column_results": [
|
||||||
|
// // "name", "start_date", "end_date", "persentase_progress"
|
||||||
|
// // ]
|
||||||
|
// // }
|
||||||
|
// // ],
|
||||||
|
// "orders": { "columns": ["id"], "ascending": false }
|
||||||
|
// }
|
||||||
|
return await RequestApi.Request().get( |
||||||
|
URL, |
||||||
|
RequestApi.HeaderWithToken()).then(res => { |
||||||
|
if (res) { |
||||||
|
if (res && res.data && res.data.data) { |
||||||
|
// console.log('ApiProject 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 project', e);
|
||||||
|
let error = JSON.parse(JSON.stringify(e)); |
||||||
|
console.log('error search project', error); |
||||||
|
if (error.message) { |
||||||
|
alert(error.message); |
||||||
|
} |
||||||
|
return {status: false, data: null}; |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -1,41 +0,0 @@ |
|||||||
import axios from 'axios'; |
|
||||||
export default class RequestApi { |
|
||||||
// constructor() {
|
|
||||||
// this.Request = this.Request.bind(this);
|
|
||||||
// }
|
|
||||||
|
|
||||||
static Request() { |
|
||||||
// axios.interceptors.request.use(function (config) {
|
|
||||||
// const token = localStorage.getItem('token')
|
|
||||||
// config.headers.Authorization = token;
|
|
||||||
|
|
||||||
// return config;
|
|
||||||
// });
|
|
||||||
const token = localStorage.getItem('token') |
|
||||||
|
|
||||||
let instance = axios.create({ |
|
||||||
headers: { |
|
||||||
'Content-Type': 'application/json', |
|
||||||
"Authorization": `Bearer ${token}` |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
instance.interceptors.response.use( |
|
||||||
(response) => response, |
|
||||||
async (error) => { |
|
||||||
// const originalRequest = error.config;
|
|
||||||
if (error.response.status === 307 || error.response.status === 403) { |
|
||||||
console.log(error.response); |
|
||||||
} |
|
||||||
|
|
||||||
return Promise.reject(error); |
|
||||||
} |
|
||||||
); |
|
||||||
|
|
||||||
return instance; |
|
||||||
} |
|
||||||
|
|
||||||
static getToken() { } |
|
||||||
} |
|
||||||
export const AXIOS = RequestApi.Request(); |
|
||||||
|
|
@ -0,0 +1,27 @@ |
|||||||
|
import { store } from "../appredux/store"; |
||||||
|
|
||||||
|
export const removeLayerByName = (layerName) => { |
||||||
|
const {mymap} = store.getState().mapReducer; |
||||||
|
var layerToRemove = []; |
||||||
|
mymap.eachLayer(function(layer) { |
||||||
|
if (layer.wmsParams) { |
||||||
|
if (layer.wmsParams.layers) { |
||||||
|
let layerWmsName = layer.wmsParams.layers.split(':')[1]; |
||||||
|
if (layerName === layerWmsName) { |
||||||
|
layerToRemove.push(layer) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
if (layer.options && layer.options.name && layer.options.name === layerName) { |
||||||
|
layerToRemove.push(layer); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
if (layerToRemove.length > 0) { |
||||||
|
for (let i = 0; i < layerToRemove.length; i++) { |
||||||
|
mymap.removeLayer(layerToRemove[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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%; |
||||||
|
} |
@ -0,0 +1,217 @@ |
|||||||
|
import React, { useEffect, useMemo, useRef, useState } from 'react' |
||||||
|
import L from 'leaflet'; |
||||||
|
import { Button, Col, Drawer, Row, Spin, Tree } from 'antd'; |
||||||
|
import ApiProject from '../../services/api/modules/project'; |
||||||
|
import { store } from '../../appredux/store'; |
||||||
|
import { getUserPoints, setMapLoading, setMymap, setOpenLeft, setOpenRight, setProjectTree, setSelectedFeature, setUserPoints } from '../../appredux/modules/map/actions'; |
||||||
|
import ContentLoader from 'react-content-loader'; |
||||||
|
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 = () => { |
||||||
|
|
||||||
|
const GRID_LEFT = 6; |
||||||
|
const GRID_MIDDLE = 12; |
||||||
|
const GRID_RIGHT = 6;
|
||||||
|
const GRID_TOTAL = GRID_LEFT+GRID_MIDDLE+GRID_RIGHT;
|
||||||
|
const mapRef = useRef() |
||||||
|
const center = { |
||||||
|
// lat: -6.200000,
|
||||||
|
// lng: 106.816666
|
||||||
|
lat: -2.4809807277842437, |
||||||
|
lng: 120.13025155062624 |
||||||
|
|
||||||
|
} |
||||||
|
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); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
initMap(); |
||||||
|
getMapLeftContent(); |
||||||
|
}, []) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
renderGridMap(); |
||||||
|
}, [openLeft, openRight]) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
// console.log('userPoints changes', userPoints);
|
||||||
|
if (mymap) { |
||||||
|
removeLayerByName('popupTemp'); |
||||||
|
removeLayerByName('userPointLayer'); |
||||||
|
if (userPoints) { |
||||||
|
let userPointLayer = L.geoJson(userPoints, { |
||||||
|
name: 'userPointLayer', |
||||||
|
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, |
||||||
|
zoom: 5 |
||||||
|
}); |
||||||
|
store.dispatch(setMymap(mymap)); |
||||||
|
// setMymap(mymap);
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' }).addTo(mymap); |
||||||
|
|
||||||
|
mymap.on('click', (e) => { |
||||||
|
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)); |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// init for left content panel, get projects and build tree select antd
|
||||||
|
const getMapLeftContent = async () => { |
||||||
|
store.dispatch(setMapLoading(true)); |
||||||
|
let project = await ApiProject.list(); |
||||||
|
console.log('project', project); |
||||||
|
if (project && project.status && project.data && project.data.length > 0) { |
||||||
|
let projectData = [ |
||||||
|
{ |
||||||
|
"title": 'All', |
||||||
|
"key": 'all', |
||||||
|
"children": [] |
||||||
|
} |
||||||
|
]; |
||||||
|
project.data.map(n => { |
||||||
|
n.key = n.id; |
||||||
|
n.title = n.nama; |
||||||
|
projectData[0].children.push(n); |
||||||
|
}) |
||||||
|
store.dispatch(setProjectTree(projectData)); |
||||||
|
// console.log('projectData', projectData);
|
||||||
|
getUserPoints(); |
||||||
|
store.dispatch(setMapLoading(false)); |
||||||
|
} |
||||||
|
store.dispatch(setMapLoading(false)); |
||||||
|
} |
||||||
|
|
||||||
|
const onEachFeatureUserPoints = (feature, layer) => { |
||||||
|
layer.on('click', function(e) { |
||||||
|
L.DomEvent.stopPropagation(e); |
||||||
|
if (!store.getState().mapReducer.routingBarVisible) { |
||||||
|
// proceed only when routing mode is not visible
|
||||||
|
showHighLight(feature); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function showHighLight(feature) { |
||||||
|
removeLayerByName('popupTemp'); |
||||||
|
// add highlight
|
||||||
|
L.geoJSON(feature, { |
||||||
|
style: function(feature) { |
||||||
|
return {color: 'blue'} |
||||||
|
}, |
||||||
|
name: 'popupTemp', |
||||||
|
onEachFeature: function (feature, layer) { |
||||||
|
layer.name = 'popupTemp' |
||||||
|
}, |
||||||
|
}).addTo(mymap); |
||||||
|
|
||||||
|
// opening right panel
|
||||||
|
store.dispatch(setOpenRight(!openRight)); |
||||||
|
store.dispatch(setSelectedFeature(feature)); |
||||||
|
} |
||||||
|
|
||||||
|
const MapContent = useMemo(() => { |
||||||
|
return ( |
||||||
|
<Row> |
||||||
|
<Col span={gridLeft}> |
||||||
|
<MapLeftContent /> |
||||||
|
</Col> |
||||||
|
<Col span={gridMiddle}> |
||||||
|
<div id="map-area" style={{ height: '90vh', width: '100%' }} ref={mapRef}></div> |
||||||
|
<button
|
||||||
|
title='Project List' |
||||||
|
style={{position: 'absolute', top: 80, left: 10, zIndex: 999, backgroundColor:'white', backgroundSize:'34px 34px', width: '34px', height: '34px', borderRadius: '2px', borderWidth: '1px', borderColor: 'grey'}}
|
||||||
|
onClick={() => store.dispatch(setOpenLeft(!openLeft))}> |
||||||
|
<i className='fa fa-list'></i> |
||||||
|
</button> |
||||||
|
{ routingBarVisible && <RoutingBar /> } |
||||||
|
{ isSearchingRoute && ( |
||||||
|
<div className="loader-container"> |
||||||
|
<Loader |
||||||
|
type="TailSpin" |
||||||
|
color="#36D7B7" |
||||||
|
height={100} |
||||||
|
width={100} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
)} |
||||||
|
</Col> |
||||||
|
<Col span={gridRight}> |
||||||
|
<MapRightContent /> |
||||||
|
</Col> |
||||||
|
<ToastContainer autoClose={5000} /> |
||||||
|
</Row> |
||||||
|
) |
||||||
|
}, [openLeft, openRight, gridLeft, gridMiddle, gridRight, routingBarVisible ]) |
||||||
|
|
||||||
|
const renderGridMap = () => { |
||||||
|
let middle = GRID_MIDDLE; |
||||||
|
let left = GRID_LEFT; |
||||||
|
let right = GRID_RIGHT; |
||||||
|
if (openLeft && openRight){ |
||||||
|
middle = GRID_MIDDLE; |
||||||
|
left = GRID_LEFT; |
||||||
|
right = GRID_RIGHT; |
||||||
|
} |
||||||
|
else if (!openLeft && openRight) { |
||||||
|
middle = GRID_TOTAL - GRID_RIGHT; |
||||||
|
left = 0; |
||||||
|
right = GRID_RIGHT; |
||||||
|
}
|
||||||
|
else if (openLeft && !openRight) { |
||||||
|
middle = GRID_TOTAL - GRID_LEFT; |
||||||
|
left = GRID_LEFT; |
||||||
|
right = 0; |
||||||
|
} |
||||||
|
else if (!openLeft && !openRight) { |
||||||
|
middle = GRID_TOTAL; |
||||||
|
left = 0; |
||||||
|
right = 0; |
||||||
|
} |
||||||
|
setGridMiddle(middle); |
||||||
|
setGridLeft(left); |
||||||
|
setGridRight(right); |
||||||
|
} |
||||||
|
|
||||||
|
return MapContent |
||||||
|
} |
||||||
|
|
||||||
|
export default MapMonitoring; |
Loading…
Reference in new issue