Browse Source

Merge branch 'master' of https://git.oslog.id/ordo/adw-frontend

pull/2/head
farhan048 2 years ago
parent
commit
22a99b10be
  1. 5
      package.json
  2. 5
      src/App.js
  3. 192
      src/appredux/modules/map/actions.js
  4. 54
      src/appredux/modules/map/reducers.js
  5. 46
      src/appredux/reducers.js
  6. 46
      src/appredux/store.js
  7. 162
      src/components/MapLeftContent/index.js
  8. 103
      src/components/MapRightContent/index.js
  9. 9
      src/components/MapRightContent/styles.css
  10. 32
      src/components/PopupButtonActions/index.js
  11. 77
      src/components/RoutingBarV2/RoutingBarV2.css
  12. 89
      src/components/RoutingBarV2/index.js
  13. 1
      src/const/ApiConst.js
  14. 3
      src/routes.js
  15. 99
      src/services/api/base.js
  16. 117
      src/services/api/modules/map_monitoring/index.js
  17. 62
      src/services/api/modules/project/index.js
  18. 41
      src/services/base.js
  19. 27
      src/utils/MapUtils.js
  20. 9
      src/views/MapMonitoring/MapMonitoring.css
  21. 217
      src/views/MapMonitoring/index.js

5
package.json

@ -28,6 +28,7 @@
"@iconify/icons-uil": "^1.0.6", "@iconify/icons-uil": "^1.0.6",
"@iconify/react": "^1.1.1", "@iconify/react": "^1.1.1",
"@nicholasadamou/react-iframe": "^1.0.3", "@nicholasadamou/react-iframe": "^1.0.3",
"@reduxjs/toolkit": "^1.9.2",
"@terrestris/ol-util": "^7.2.0", "@terrestris/ol-util": "^7.2.0",
"@terrestris/react-geo": "^12.0.0", "@terrestris/react-geo": "^12.0.0",
"alasql": "^1.7.3", "alasql": "^1.7.3",
@ -76,12 +77,16 @@
"react-leaflet-draw": "^0.19.8", "react-leaflet-draw": "^0.19.8",
"react-loader-spinner": "^3.1.5", "react-loader-spinner": "^3.1.5",
"react-notifications": "^1.7.2", "react-notifications": "^1.7.2",
"react-redux": "^8.0.5",
"react-router-dom": "^5.0.1", "react-router-dom": "^5.0.1",
"react-select": "^4.3.1", "react-select": "^4.3.1",
"react-slick": "^0.28.1", "react-slick": "^0.28.1",
"react-tiny-fab": "^4.0.4", "react-tiny-fab": "^4.0.4",
"react-toastify": "^5.5.0", "react-toastify": "^5.5.0",
"reactstrap": "^8.0.0", "reactstrap": "^8.0.0",
"redux": "^4.2.1",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.4.2",
"simple-line-icons": "^2.4.1", "simple-line-icons": "^2.4.1",
"slick-carousel": "^1.8.1", "slick-carousel": "^1.8.1",
"underscore": "^1.13.1", "underscore": "^1.13.1",

5
src/App.js

@ -2,6 +2,9 @@ import React, { Component } from 'react';
import { HashRouter, Route, Switch } from 'react-router-dom'; import { HashRouter, Route, Switch } from 'react-router-dom';
import './App.scss'; import './App.scss';
import 'react-notifications/lib/notifications.css'; import 'react-notifications/lib/notifications.css';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { persistor, store } from './appredux/store';
const loading = () => <div className="animated fadeIn pt-3 text-center">Loading...</div>; const loading = () => <div className="animated fadeIn pt-3 text-center">Loading...</div>;
@ -18,6 +21,7 @@ class App extends Component {
render() { render() {
return ( return (
<Provider store={store}>
<HashRouter> <HashRouter>
<React.Suspense fallback={loading()}> <React.Suspense fallback={loading()}>
<Switch> <Switch>
@ -34,6 +38,7 @@ class App extends Component {
</Switch> </Switch>
</React.Suspense> </React.Suspense>
</HashRouter> </HashRouter>
</Provider>
); );
} }
} }

192
src/appredux/modules/map/actions.js

@ -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));
}

54
src/appredux/modules/map/reducers.js

@ -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;

46
src/appredux/reducers.js

@ -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;

46
src/appredux/store.js

@ -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)

162
src/components/MapLeftContent/index.js

@ -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;

103
src/components/MapRightContent/index.js

@ -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;

9
src/components/MapRightContent/styles.css

@ -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;
}

32
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 (
<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;

77
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;
}

89
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 (
<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;

1
src/const/ApiConst.js

@ -113,6 +113,7 @@ export const TOKEN_ADW = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIxMjAyI
// export let BASE_OSPRO = "https://ospro-api.ospro.id"; // export let BASE_OSPRO = "https://ospro-api.ospro.id";
export let BASE_OSPRO = "https://adw-api.ospro.id"; export let BASE_OSPRO = "https://adw-api.ospro.id";
// export let BASE_OSPRO = "http://localhost:8444";
// export let BASE_OSPRO = "http://192.168.1.123:8444"; // local // export let BASE_OSPRO = "http://192.168.1.123:8444"; // local
// export let BASE_OSPRO = "http://103.73.125.81:8444"; // ip public adw // export let BASE_OSPRO = "http://103.73.125.81:8444"; // ip public adw
export let BASE_SIMPRO_LUMEN = `${BASE_OSPRO}/api`; export let BASE_SIMPRO_LUMEN = `${BASE_OSPRO}/api`;

3
src/routes.js

@ -46,6 +46,7 @@ const UserShift = React.lazy(() => import('./views/SimproV2/UserShift'));
const DashboardBOD = React.lazy(() => import('./views/Dashboard/DashboardBOD')); const DashboardBOD = React.lazy(() => import('./views/Dashboard/DashboardBOD'));
const DashboardCustomer = React.lazy(() => import('./views/Dashboard/DashboardCustomer')); const DashboardCustomer = React.lazy(() => import('./views/Dashboard/DashboardCustomer'));
const DashboardProject = React.lazy(() => import('./views/Dashboard/DashboardProject')); const DashboardProject = React.lazy(() => import('./views/Dashboard/DashboardProject'));
const MapMonitoring = React.lazy(() => import('./views/MapMonitoring'));
const routes = [ const routes = [
{ path: '/', exact: true, name: 'Home' }, { path: '/', exact: true, name: 'Home' },
@ -77,7 +78,6 @@ const routes = [
{ path: '/divisi', exact: true, name: 'Divisi', component: Divisi }, { path: '/divisi', exact: true, name: 'Divisi', component: Divisi },
{ path: '/satuan', exact: true, name: 'Satuan', component: Satuan }, { path: '/satuan', exact: true, name: 'Satuan', component: Satuan },
{ path: '/config-alert', exact: true, name: 'Config Alert', component: ConfigAlert }, { path: '/config-alert', exact: true, name: 'Config Alert', component: ConfigAlert },
{ path: '/checklist-k3', exact: true, name: 'Checklist K3', component: ChecklistK3 }, { path: '/checklist-k3', exact: true, name: 'Checklist K3', component: ChecklistK3 },
{ path: '/absensi', exact: true, name: 'Absensi', component: Absensi }, { path: '/absensi', exact: true, name: 'Absensi', component: Absensi },
{ path: '/divisi-karyawan', exact: true, name: 'Divisi Karyawan', component: DivisiKaryawan }, { path: '/divisi-karyawan', exact: true, name: 'Divisi Karyawan', component: DivisiKaryawan },
@ -100,6 +100,7 @@ const routes = [
{ path: '/user-admin', exact: true, name: 'User Admin', component: UserAdmin }, { path: '/user-admin', exact: true, name: 'User Admin', component: UserAdmin },
{ path: '/user-shift', exact: true, name: 'Shift', component: UserShift }, { path: '/user-shift', exact: true, name: 'Shift', component: UserShift },
{ path: '/working-hour', exact: true, name: 'Working Hour', component: Shift }, { path: '/working-hour', exact: true, name: 'Working Hour', component: Shift },
{ path: '/map-monitoring', exact: true, name: 'Map Monitoring', component: MapMonitoring },
// { path: '/dashboard-project/:ID/:GANTTID', exact: true, name: 'Dashboard Project', component: DashboardProject }, // { path: '/dashboard-project/:ID/:GANTTID', exact: true, name: 'Dashboard Project', component: DashboardProject },
]; ];

99
src/services/api/base.js

@ -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();

117
src/services/api/modules/map_monitoring/index.js

@ -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};
});
}
}

62
src/services/api/modules/project/index.js

@ -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};
});
}
}

41
src/services/base.js

@ -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();

27
src/utils/MapUtils.js

@ -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]);
}
}
}

9
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%;
}

217
src/views/MapMonitoring/index.js

@ -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: '&copy; <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…
Cancel
Save