sanguu516 3 months ago
parent
commit
48a2f58db9
  1. 12
      package-lock.json
  2. 1
      package.json
  3. 17
      src/components/flight/FlightApprovalsReport.js
  4. 5
      src/components/flight/FlightApprovalsTable.js
  5. 8164
      src/components/map/geojson/flatGimpoAirportAirArea.json
  6. 233
      src/containers/flight/flightApprovalsContainer.js
  7. 137
      src/containers/rightMenuContainer.js
  8. 22
      src/index.js
  9. 26
      src/redux/rootReducer.ts
  10. 8
      src/router/routes/index.js
  11. 3
      src/views/flight/FlightView.js
  12. 13
      src/views/rightMenuView.js

12
package-lock.json generated

@ -10987,9 +10987,9 @@
"integrity": "sha512-007VucCkqNOMMb9ggRLNuJowwaJcyOh4sKAFcdGfahfGc7JQbf94zSzjdBq/wVyHWUEs5o3+idhFZ0wbZMRmVQ==" "integrity": "sha512-007VucCkqNOMMb9ggRLNuJowwaJcyOh4sKAFcdGfahfGc7JQbf94zSzjdBq/wVyHWUEs5o3+idhFZ0wbZMRmVQ=="
}, },
"flatted": { "flatted": {
"version": "3.2.0", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.0.tgz", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
"integrity": "sha512-XprP7lDrVT+kE2c2YlfiV+IfS9zxukiIOvNamPNsImNhXadSsQEbosItdL9bUQlCZXR13SvPk20BjWSWLA7m4A==" "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="
}, },
"flatten": { "flatten": {
"version": "1.0.3", "version": "1.0.3",
@ -24517,6 +24517,12 @@
"resolved": "https://registry.npmjs.org/redux-debounced/-/redux-debounced-0.5.0.tgz", "resolved": "https://registry.npmjs.org/redux-debounced/-/redux-debounced-0.5.0.tgz",
"integrity": "sha512-O2anhB0A6yQZH19uLETFtajcUQLcyiJcgC0hHSoFr5T3hWGtt0C5s6KNnb2RX51MwCh5VCl9ehZTv91F/rsZww==" "integrity": "sha512-O2anhB0A6yQZH19uLETFtajcUQLcyiJcgC0hHSoFr5T3hWGtt0C5s6KNnb2RX51MwCh5VCl9ehZTv91F/rsZww=="
}, },
"redux-persist": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz",
"integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==",
"dev": true
},
"redux-saga": { "redux-saga": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.1.3.tgz", "resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.1.3.tgz",

1
package.json

@ -156,6 +156,7 @@
"react-app-rewire-sass-rule": "^2.1.1", "react-app-rewire-sass-rule": "^2.1.1",
"react-app-rewired": "^2.1.6", "react-app-rewired": "^2.1.6",
"react-snap": "1.23.0", "react-snap": "1.23.0",
"redux-persist": "^6.0.0",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"typescript": "4.3.5" "typescript": "4.3.5"
}, },

17
src/components/flight/FlightApprovalsReport.js

@ -1,4 +1,4 @@
import { useState } from 'react'; import { useEffect, useState } from 'react';
import Flatpickr from 'react-flatpickr'; import Flatpickr from 'react-flatpickr';
import { Button, Input, CustomInput, Col, Row } from '@component/ui'; import { Button, Input, CustomInput, Col, Row } from '@component/ui';
import { Search, Calendar } from 'react-feather'; import { Search, Calendar } from 'react-feather';
@ -16,6 +16,19 @@ export default function FlightApprovalsReport(props) {
endDate: dayjs().format('YYYY-MM-DD') endDate: dayjs().format('YYYY-MM-DD')
}); });
useEffect(() => {
const popupSyncSearchData = JSON.parse(localStorage.getItem('popupState'));
if (popupSyncSearchData) {
setSearchDate({
startDate: popupSyncSearchData.startDate,
endDate: popupSyncSearchData.endDate
});
setFilterArea(popupSyncSearchData.selected);
setFilterId(popupSyncSearchData.filter);
localStorage.removeItem('popupState');
}
}, []);
const handleKeyDown = e => { const handleKeyDown = e => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
props.handlerSearch(filterId, searchDate, filterArea); props.handlerSearch(filterId, searchDate, filterArea);
@ -23,7 +36,7 @@ export default function FlightApprovalsReport(props) {
}; };
return ( return (
<div className='layer-content'> <div className='layer-content' onDragEnd={props.handleDragEnd} draggable>
<div className='layer-ti'> <div className='layer-ti'>
<h4>비행승인 신청 검토결과 현황</h4> <h4>비행승인 신청 검토결과 현황</h4>
</div> </div>

5
src/components/flight/FlightApprovalsTable.js

@ -23,7 +23,6 @@ export default function FlightApprovalsTable(props) {
// 행 토글 // 행 토글
const [expandedRows, setExpandedRows] = useState({}); const [expandedRows, setExpandedRows] = useState({});
console.log('>>', expandedRows);
// 승인, 미승인, 비대상 건수 계산 // 승인, 미승인, 비대상 건수 계산
useEffect(() => { useEffect(() => {
resApprovalCd(); resApprovalCd();
@ -364,16 +363,12 @@ export default function FlightApprovalsTable(props) {
// 테이블 내부 행 클릭 이벤트 // 테이블 내부 행 클릭 이벤트
const handleInRowClick = row => { const handleInRowClick = row => {
console.log('>>', row);
handlerOpenModal(row.approvalCd, row.fltElev, row.fltElevMax); handlerOpenModal(row.approvalCd, row.fltElev, row.fltElevMax);
props.handlerDetail(row); props.handlerDetail(row);
}; };
// 테이블 행 클릭 이벤트 // 테이블 행 클릭 이벤트
const handleRowClick = row => { const handleRowClick = row => {
console.log('>>', row);
handlerOpenModal( handlerOpenModal(
row.areaList[0].approvalCd, row.areaList[0].approvalCd,
row.areaList[0].fltElev, row.areaList[0].fltElev,

8164
src/components/map/geojson/flatGimpoAirportAirArea.json

File diff suppressed because it is too large Load Diff

233
src/containers/flight/flightApprovalsContainer.js

@ -25,7 +25,7 @@ import { setLogout } from '@src/redux/features/account/auth/authThunk';
import WebsocketClient from '../../components/websocket/WebsocketClient'; import WebsocketClient from '../../components/websocket/WebsocketClient';
import { clientDispatchTopMenu } from '@src/redux/features/layout/layoutSlice'; import { clientDispatchTopMenu } from '@src/redux/features/layout/layoutSlice';
export default function FlightApprovalsContainer() { export default function FlightApprovalsContainer({ mode }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const history = useHistory(); const history = useHistory();
@ -48,6 +48,10 @@ export default function FlightApprovalsContainer() {
const { laancAprvList } = useSelector(state => state.laancState); const { laancAprvList } = useSelector(state => state.laancState);
const map = useSelector(state => state.mapState.map); const map = useSelector(state => state.mapState.map);
// popup
const [isPopup, setIsPopup] = useState(false);
const [popup, setPopup] = useState(null);
const popupRef = useRef(null);
const previewGeo2 = { const previewGeo2 = {
type: 'FeatureCollection', type: 'FeatureCollection',
@ -66,20 +70,113 @@ export default function FlightApprovalsContainer() {
}, []); }, []);
useEffect(() => { useEffect(() => {
if (areaCoordList.length != 0) { if (areaCoordList.length !== 0) {
handlerPreviewDraw(); handlerPreviewDraw();
} }
}, [areaCoordList]); }, [areaCoordList]);
useEffect(() => { useEffect(() => {
if (map) { if (map) {
setMapObject(map); window._mapbox = map;
let mapInstance = mode === 'container' ? map : window.opener._mapbox;
setMapObject(mapInstance);
} }
}, [map]); }, [map]);
useEffect(async () => { useEffect(() => {
if (areaCoordList.length === 0) return; const childMessage = e => {
}, [areaCoordList]); if (e.data.type) {
const { type } = e.data;
const { payload } = e.data;
console.log(payload);
switch (type) {
case 'initalState':
popupRef.current.postMessage({
type: 'initalState',
payload: {
filter,
selected,
startDate,
endDate
}
});
return;
case 'search':
const { search, searchDate, filterArea } = payload;
handlerSearch(search, searchDate, filterArea);
return;
case 'detail':
const { area } = payload;
handlerDetail(area);
return;
case 'closedSync':
popupRef.current.close();
// localStorage.removeItem('popupState');
return;
default:
break;
}
}
};
let timer;
if (popup) {
timer = setInterval(() => {
if (popup.closed) {
setIsPopup(false);
clearInterval(timer);
}
}, 1000); // 1초마다 체크
}
window.addEventListener('message', childMessage);
return () => {
clearInterval(timer);
window.removeEventListener('message', childMessage);
};
}, [popup]);
useEffect(() => {
const handleBeforeUnload = e => {
localStorage.removeItem('persist:root');
if (popupRef.current) {
popupRef.current.close();
}
};
window.addEventListener('beforeunload', handleBeforeUnload);
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload);
};
}, []);
const handleDragEnd = e => {
setIsPopup(true);
const el = document.querySelector('.flight-approval-layer');
const popupWidth = el.offsetWidth; // 팝업의 너비
const popupHeight = el.offsetHeight; // 팝업의 너비
// const popupX = window.screenX + window.outerWidth - e.clientX; // 오른쪽 끝에 띄우기
// const popupY = Math.round(
// window.screenY + window.outerHeight / 2 - popupHeight / 2
// );
const popupX =
window.screenX +
(window.outerWidth - popupWidth) / 2 +
e.clientX -
window.innerWidth / 2; // 드래그 끝나는 지점
const popupY = Math.round(
window.screenY + window.outerHeight / 2 - popupHeight / 2
);
const option = `width=${popupWidth},height=${popupHeight},left=${popupX},top=${popupY}`;
popupRef.current = window.open('/rightMenu', 'NewWindow', option);
// setPopupOption(option);
setPopup(popupRef.current);
};
const handlerSearch = (search, searchDate, filterArea) => { const handlerSearch = (search, searchDate, filterArea) => {
setStartDate(searchDate.startDate); setStartDate(searchDate.startDate);
@ -116,7 +213,14 @@ export default function FlightApprovalsContainer() {
); );
} }
// ); // );
setFilter(search); setFilter(search);
if (popup) {
popupRef.current.postMessage({
type: 'handlerSearchRs',
payload: { search }
});
}
}; };
const handlerDetail = area => { const handlerDetail = area => {
@ -128,27 +232,30 @@ export default function FlightApprovalsContainer() {
}; };
const handlerMapInit = () => { const handlerMapInit = () => {
if (map.getSource('preview')) { let mapInstance = mode === 'container' ? map : window.opener._mapbox;
if (mapInstance.getSource('preview')) {
} else { } else {
map.addSource('preview', { mapInstance.addSource('preview', {
type: 'geojson', type: 'geojson',
data: previewGeo2 data: previewGeo2
}); });
map.addLayer(flightlayerWayPoint('preview')); mapInstance.addLayer(flightlayerWayPoint('preview'));
map.addLayer(flightlayerBuffer('preview')); mapInstance.addLayer(flightlayerBuffer('preview'));
map.addLayer(flightlayerPolygon('preview')); mapInstance.addLayer(flightlayerPolygon('preview'));
map.addLayer(flightlayerPolyline('preview')); mapInstance.addLayer(flightlayerPolyline('preview'));
} }
dispatch(clientSetIsMapLoading(true)); dispatch(clientSetIsMapLoading(true));
const preview = map.getSource('preview'); const preview = mapInstance.getSource('preview');
if (preview) setPreviewLayer(preview); if (preview) setPreviewLayer(preview);
setIsMapLoading(true); setIsMapLoading(true);
setMapObject(map); setMapObject(mapInstance);
dispatch(clientMapInit(map));
dispatch(clientMapInit(mapInstance));
}; };
const handlerPreviewDraw = () => { const handlerPreviewDraw = () => {
if (areaCoordList.length > 0) { if (areaCoordList.length > 0) {
@ -190,53 +297,61 @@ export default function FlightApprovalsContainer() {
return ( return (
<> <>
<div className='left-menu'> <>
<h1 className='logo'> <div className='left-menu'>
<img src={logo} width='80' /> <h1 className='logo'>
<span>UTM</span> <img src={logo} width='80' />
</h1> <span>UTM</span>
<ul className='left-menu-nav'> </h1>
<li> <ul className='left-menu-nav'>
<button> <li>
<Grid <button>
onClick={() => { <Grid
dispatch(clientDispatchTopMenu('/')); onClick={() => {
history.push('/main/dashboard'); dispatch(clientDispatchTopMenu('/'));
}} history.push('/main/dashboard');
}}
/>
</button>
</li>
</ul>
<ul className='left-menu-footer'>
<li>
<AiOutlinePoweroff
size={25}
className='logout-btn'
onClick={handlerLogout}
/>
</li>
<li>
<WebsocketClient />
</li>
</ul>
</div>
<div className='map'>
<MapControl />
</div>
</>
{!isPopup && (
<div className='right-menu active'>
<div className='right-layer active flight-approval-layer'>
<div className='layer-content'>
<FlightApprovalsReport
handlerSearch={handlerSearch}
handleDragEnd={handleDragEnd}
/>
<FlightApprovalsTable
filter={filter}
startDate={startDate}
endDate={endDate}
selected={selected}
handlerDetail={handlerDetail}
/> />
</button> </div>
</li>
</ul>
<ul className='left-menu-footer'>
<li>
<AiOutlinePoweroff
size={25}
className='logout-btn'
onClick={handlerLogout}
/>
</li>
<li>
<WebsocketClient />
</li>
</ul>
</div>
<div className='map'>
<MapControl />
</div>
<div className='right-menu active'>
<div className='right-layer active flight-approval-layer'>
<div className='layer-content'>
<FlightApprovalsReport handlerSearch={handlerSearch} />
<FlightApprovalsTable
filter={filter}
startDate={startDate}
endDate={endDate}
selected={selected}
handlerDetail={handlerDetail}
/>
</div> </div>
</div> </div>
</div> )}
</> </>
); );
} }

137
src/containers/rightMenuContainer.js

@ -0,0 +1,137 @@
import { useEffect, useState } from 'react';
import dayjs from 'dayjs';
import { useDispatch } from '@src/redux/store';
import { getLaancAprvList } from '@src/redux/features/laanc/laancThunk';
import FlightApprovalsTable from '@src/components/flight/FlightApprovalsTable';
import FlightApprovalsReport from '@src/components/flight/FlightApprovalsReport';
function RightMenuContainer() {
const [filter, setFilter] = useState('');
const [startDate, setStartDate] = useState(dayjs().format('YYYY-MM-DD'));
const [endDate, setEndDate] = useState();
const [selected, setSelected] = useState(null);
const dispatch = useDispatch();
useEffect(() => {
handlerOpnerPostMessage('initalState', null);
window.addEventListener('message', opnerMessage);
return () => {
window.removeEventListener('message', opnerMessage);
};
}, []);
const opnerMessage = e => {
const { type } = e.data;
const { payload } = e.data;
switch (type) {
case 'initalState':
setFilter(payload.filter);
setSelected(payload.selected);
setStartDate(payload.startDate);
setEndDate(payload.endDate);
return;
case 'handlerSearchRs':
console.log(payload.filter);
setFilter(payload.filter);
return;
default:
break;
}
};
const handlerOpnerPostMessage = (type, payload) => {
switch (type) {
case 'initalState':
window.opener.postMessage({ type, payload });
return;
case 'search':
window.opener.postMessage({ type, payload });
return;
case 'detail':
window.opener.postMessage({ type, payload });
return;
case 'closedSync':
window.opener.postMessage({ type, payload });
default:
break;
}
};
const handlerSearch = (search, searchDate, filterArea) => {
if (
search != '' &&
(search === '승인' || search === '미승인' || search === '비대상')
) {
dispatch(
getLaancAprvList({
searchStDt: searchDate.startDate,
searchEndDt: searchDate.endDate,
selectZone: filterArea,
approvalCd: search === '승인' ? 'S' : search === '미승인' ? 'F' : 'U'
})
);
} else if (search != '') {
dispatch(
getLaancAprvList({
searchStDt: searchDate.startDate,
searchEndDt: searchDate.endDate,
selectZone: filterArea,
applyNo: search
})
);
} else {
dispatch(
getLaancAprvList({
searchStDt: searchDate.startDate,
searchEndDt: searchDate.endDate,
selectZone: filterArea
})
);
}
localStorage.setItem(
'popupState',
JSON.stringify({
filter: search,
selected: filterArea,
startDate: searchDate.startDate,
endDate: searchDate.endDate
})
);
handlerOpnerPostMessage('search', { search, searchDate, filterArea });
};
const handlerDetail = area => {
handlerOpnerPostMessage('detail', { area });
};
const handleBeforeUnload = () => {
handlerOpnerPostMessage('closedSync', '');
};
return (
<div className='right-menu active'>
<div
className='right-layer active flight-approval-layer'
style={{ width: '100vw' }}
>
<div className='layer-content'>
<FlightApprovalsReport handlerSearch={handlerSearch} />
<FlightApprovalsTable
filter={filter}
startDate={startDate}
endDate={endDate}
selected={selected}
handlerDetail={handlerDetail}
/>
</div>
</div>
</div>
);
}
export default RightMenuContainer;

22
src/index.js

@ -8,6 +8,8 @@ import { HelmetProvider } from 'react-helmet-async';
// ** Redux Imports // ** Redux Imports
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { store } from '@src/redux/store'; import { store } from '@src/redux/store';
import { persistStore } from 'redux-persist';
import { PersistGate } from 'redux-persist/integration/react';
// ** Toast & ThemeColors Context // ** Toast & ThemeColors Context
import { ToastContainer } from 'react-toastify'; import { ToastContainer } from 'react-toastify';
@ -43,16 +45,20 @@ import * as serviceWorker from './serviceWorker';
// ** Lazy load app // ** Lazy load app
const LazyApp = lazy(() => import('./App')); const LazyApp = lazy(() => import('./App'));
const rootElement = document.getElementById('root'); const rootElement = document.getElementById('root');
const persistor = persistStore(store);
const element = ( const element = (
<Provider store={store}> <Provider store={store}>
<Suspense fallback={<Spinner />}> <PersistGate loading={null} persistor={persistor}>
<ThemeContext> <Suspense fallback={<Spinner />}>
<HelmetProvider> <ThemeContext>
<LazyApp /> <HelmetProvider>
</HelmetProvider> <LazyApp />
<ToastContainer newestOnTop /> </HelmetProvider>
</ThemeContext> <ToastContainer newestOnTop />
</Suspense> </ThemeContext>
</Suspense>
</PersistGate>
</Provider> </Provider>
); );

26
src/redux/rootReducer.ts

@ -1,4 +1,6 @@
import { combineReducers } from '@reduxjs/toolkit'; import { combineReducers } from '@reduxjs/toolkit';
import { persistReducer, createTransform } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { layoutReducer } from './features/layout/layoutSlice'; import { layoutReducer } from './features/layout/layoutSlice';
import { dashboardReducer } from './features/dashboard/dashboardSlice'; import { dashboardReducer } from './features/dashboard/dashboardSlice';
import { droneReducer } from './features/basis/drone/droneSlice'; import { droneReducer } from './features/basis/drone/droneSlice';
@ -21,6 +23,28 @@ import { controlGpHisReducer } from './features/control/gp/gpSlice';
import { controlGpDtlReducer } from './features/control/gp/gpSlice'; import { controlGpDtlReducer } from './features/control/gp/gpSlice';
import { controlGpCountReducer } from './features/control/gp/gpSlice'; import { controlGpCountReducer } from './features/control/gp/gpSlice';
import { crtfyhpReducer } from './features/comn/crtfyhp/crtfyhpSlice'; import { crtfyhpReducer } from './features/comn/crtfyhp/crtfyhpSlice';
import { laancState } from './features/laanc/laancState';
// Transform 정의 - laancReducers 리듀서에서 laancAprvList 상태만 저장
const saveSubsetFilter = createTransform<laancState, Partial<laancState>>(
inboundState => {
return {
laancAprvList: inboundState.laancAprvList,
areaCoordList: inboundState.areaCoordList
};
},
outboundState => {
return outboundState as laancState;
},
{ whitelist: ['laancState'] }
);
const rootPersistConfig = {
key: 'root', // localStorage key
storage, // localStorage
whitelist: ['laancState'], // target (reducer name)
transforms: [saveSubsetFilter] // 특정 상태만 저장
};
const rootReducer = (state: any, action: any) => { const rootReducer = (state: any, action: any) => {
const combineReducer = combineReducers({ const combineReducer = combineReducers({
@ -65,4 +89,4 @@ const rootReducer = (state: any, action: any) => {
return combineReducer(state, action); return combineReducer(state, action);
}; };
export default rootReducer; export default persistReducer(rootPersistConfig, rootReducer);

8
src/router/routes/index.js

@ -97,6 +97,14 @@ const Routes = [
authRoute: true authRoute: true
} }
}, },
{
path: '/rightMenu',
component: lazy(() => import('../../views/rightMenuView')),
layout: 'BlankLayout',
meta: {
authRoute: true
}
},
{ {
path: '/history/record/sample1', path: '/history/record/sample1',
component: lazy(() => component: lazy(() =>

3
src/views/flight/FlightView.js

@ -1,3 +1,4 @@
import { useEffect, useRef } from 'react';
import '@styles/react/libs/flatpickr/flatpickr.scss'; import '@styles/react/libs/flatpickr/flatpickr.scss';
import '@styles/react/libs/tables/react-dataTable-component.scss'; import '@styles/react/libs/tables/react-dataTable-component.scss';
import '../../assets/css/custom.css'; import '../../assets/css/custom.css';
@ -10,7 +11,7 @@ export default function FlightView() {
{/* <Helmet> {/* <Helmet>
<title>관제시스템</title> <title>관제시스템</title>
</Helmet> */} </Helmet> */}
<FlightApprovalsContainer />; <FlightApprovalsContainer mode='container' />;
</div> </div>
); );
} }

13
src/views/rightMenuView.js

@ -0,0 +1,13 @@
import '@styles/react/libs/flatpickr/flatpickr.scss';
import '@styles/react/libs/tables/react-dataTable-component.scss';
import '../assets/css/custom.css';
import RightMenuContainer from '../containers/rightMenuContainer';
import FlightApprovalsContainer from '../containers/flight/flightApprovalsContainer';
export default function rightMenuView() {
return (
<div className='pal-container'>
<RightMenuContainer />
</div>
);
}
Loading…
Cancel
Save