Browse Source

비행승인 신청 검토결과 현황 POPUP 작업

master
김장현 3 months ago
parent
commit
66986615af
  1. 12
      package-lock.json
  2. 1
      package.json
  3. 17
      src/components/flight/FlightApprovalsReport.js
  4. 5
      src/components/flight/FlightApprovalsTable.js
  5. 147
      src/containers/flight/flightApprovalsContainer.js
  6. 137
      src/containers/rightMenuContainer.js
  7. 6
      src/index.js
  8. 26
      src/redux/rootReducer.ts
  9. 8
      src/router/routes/index.js
  10. 3
      src/views/flight/FlightView.js
  11. 13
      src/views/rightMenuView.js

12
package-lock.json generated

@ -10572,9 +10572,9 @@
"integrity": "sha512-007VucCkqNOMMb9ggRLNuJowwaJcyOh4sKAFcdGfahfGc7JQbf94zSzjdBq/wVyHWUEs5o3+idhFZ0wbZMRmVQ=="
},
"flatted": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.0.tgz",
"integrity": "sha512-XprP7lDrVT+kE2c2YlfiV+IfS9zxukiIOvNamPNsImNhXadSsQEbosItdL9bUQlCZXR13SvPk20BjWSWLA7m4A=="
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="
},
"flatten": {
"version": "1.0.3",
@ -23486,6 +23486,12 @@
"resolved": "https://registry.npmjs.org/redux-debounced/-/redux-debounced-0.5.0.tgz",
"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": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.1.3.tgz",

1
package.json

@ -153,6 +153,7 @@
"react-app-rewire-sass-rule": "^2.1.1",
"react-app-rewired": "^2.1.6",
"react-snap": "1.23.0",
"redux-persist": "^6.0.0",
"sass-loader": "^8.0.2",
"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 { Button, Input, CustomInput, Col, Row } from '@component/ui';
import { Search, Calendar } from 'react-feather';
@ -16,6 +16,19 @@ export default function FlightApprovalsReport(props) {
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 => {
if (e.key === 'Enter') {
props.handlerSearch(filterId, searchDate, filterArea);
@ -23,7 +36,7 @@ export default function FlightApprovalsReport(props) {
};
return (
<div className='layer-content'>
<div className='layer-content' onDragEnd={props.handleDragEnd} draggable>
<div className='layer-ti'>
<h4>비행승인 신청 검토결과 현황</h4>
</div>

5
src/components/flight/FlightApprovalsTable.js

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

147
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 { clientDispatchTopMenu } from '@src/redux/features/layout/layoutSlice';
export default function FlightApprovalsContainer() {
export default function FlightApprovalsContainer({ mode }) {
const dispatch = useDispatch();
const history = useHistory();
@ -48,6 +48,10 @@ export default function FlightApprovalsContainer() {
const { laancAprvList } = useSelector(state => state.laancState);
const map = useSelector(state => state.mapState.map);
// popup
const [isPopup, setIsPopup] = useState(false);
const [popup, setPopup] = useState(null);
const popupRef = useRef(null);
const previewGeo2 = {
type: 'FeatureCollection',
@ -66,20 +70,113 @@ export default function FlightApprovalsContainer() {
}, []);
useEffect(() => {
if (areaCoordList.length != 0) {
if (areaCoordList.length !== 0) {
handlerPreviewDraw();
}
}, [areaCoordList]);
useEffect(() => {
if (map) {
setMapObject(map);
window._mapbox = map;
let mapInstance = mode === 'container' ? map : window.opener._mapbox;
setMapObject(mapInstance);
}
}, [map]);
useEffect(async () => {
if (areaCoordList.length === 0) return;
}, [areaCoordList]);
useEffect(() => {
const childMessage = e => {
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) => {
setStartDate(searchDate.startDate);
@ -116,7 +213,14 @@ export default function FlightApprovalsContainer() {
);
}
// );
setFilter(search);
if (popup) {
popupRef.current.postMessage({
type: 'handlerSearchRs',
payload: { search }
});
}
};
const handlerDetail = area => {
@ -128,27 +232,30 @@ export default function FlightApprovalsContainer() {
};
const handlerMapInit = () => {
if (map.getSource('preview')) {
let mapInstance = mode === 'container' ? map : window.opener._mapbox;
if (mapInstance.getSource('preview')) {
} else {
map.addSource('preview', {
mapInstance.addSource('preview', {
type: 'geojson',
data: previewGeo2
});
map.addLayer(flightlayerWayPoint('preview'));
map.addLayer(flightlayerBuffer('preview'));
map.addLayer(flightlayerPolygon('preview'));
map.addLayer(flightlayerPolyline('preview'));
mapInstance.addLayer(flightlayerWayPoint('preview'));
mapInstance.addLayer(flightlayerBuffer('preview'));
mapInstance.addLayer(flightlayerPolygon('preview'));
mapInstance.addLayer(flightlayerPolyline('preview'));
}
dispatch(clientSetIsMapLoading(true));
const preview = map.getSource('preview');
const preview = mapInstance.getSource('preview');
if (preview) setPreviewLayer(preview);
setIsMapLoading(true);
setMapObject(map);
dispatch(clientMapInit(map));
setMapObject(mapInstance);
dispatch(clientMapInit(mapInstance));
};
const handlerPreviewDraw = () => {
if (areaCoordList.length > 0) {
@ -189,6 +296,7 @@ export default function FlightApprovalsContainer() {
};
return (
<>
<>
<div className='left-menu'>
<h1 className='logo'>
@ -223,10 +331,16 @@ export default function FlightApprovalsContainer() {
<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} />
<FlightApprovalsReport
handlerSearch={handlerSearch}
handleDragEnd={handleDragEnd}
/>
<FlightApprovalsTable
filter={filter}
startDate={startDate}
@ -237,6 +351,7 @@ export default function FlightApprovalsContainer() {
</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;

6
src/index.js

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

26
src/redux/rootReducer.ts

@ -1,4 +1,6 @@
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 { dashboardReducer } from './features/dashboard/dashboardSlice';
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 { controlGpCountReducer } from './features/control/gp/gpSlice';
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 combineReducer = combineReducers({
@ -65,4 +89,4 @@ const rootReducer = (state: any, action: any) => {
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
}
},
{
path: '/rightMenu',
component: lazy(() => import('../../views/rightMenuView')),
layout: 'BlankLayout',
meta: {
authRoute: true
}
},
{
path: '/history/record/sample1',
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/tables/react-dataTable-component.scss';
import '../../assets/css/custom.css';
@ -10,7 +11,7 @@ export default function FlightView() {
{/* <Helmet>
<title>관제시스템</title>
</Helmet> */}
<FlightApprovalsContainer />;
<FlightApprovalsContainer mode='container' />;
</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