김지은 3 months ago
parent
commit
49ec7dc597
  1. 2
      src/assets/css/custom.css
  2. 6
      src/components/account/login/AccountLogin.js
  3. 17
      src/components/flight/FlightApprovalsReport.js
  4. 18
      src/components/flight/NewFlightApprovalsReport.js
  5. 692
      src/components/flight/NewFlightApprovalsTable.js
  6. 4033
      src/components/map/geojson/flatGimpoAirportAirArea.json
  7. 193
      src/containers/flight/NewFlightApprovalsContainer.js
  8. 159
      src/containers/flight/flightApprovalsContainer.js
  9. 8
      src/containers/rightMenuContainer.js
  10. 20
      src/redux/features/laanc/laancThunk.ts
  11. 4
      src/redux/rootReducer.ts
  12. 24
      src/router/hoc/authenticationCheck.tsx
  13. 7
      src/router/routes/RouteFlight.js
  14. 2
      src/views/flight/FlightView.js
  15. 2
      src/views/flight/NewFlightView.js

2
src/assets/css/custom.css

@ -1181,7 +1181,7 @@ caption {overflow: hidden; line-height: 0;text-indent: -2000px;}
.flight-approval .rdt_TableHeadRow>div{display:none}
.flight-approval .rdt_TableHeadRow .rdt_TableCol{display:block;font-size:0.8rem;}
.flight-approval .rdt_TableHeadRow .rdt_TableCol div{font-weight:600}
.flight-approval-layer{width:635px;overflow:scroll;}
.flight-approval-layer{width:665px;overflow:scroll;}
.layer-ti-sub{display:block;font-size:0.875rem;color:#777;margin-top:-4px;}
.flight-approval-layer .layer-ti-sub{margin-bottom:0.5rem}
.flight-approval-layer .calendar-flat svg{color:#8a1c05}

6
src/components/account/login/AccountLogin.js

@ -68,7 +68,11 @@ export const AccountLogin = ({ history }) => {
const { meta } = await dispatch(setLogin(loginForm));
if (meta.requestStatus === 'fulfilled') {
history.push('/control');
if (loginForm?.userId === 'dos-gmp') {
history.push('/flight/Approvals/new');
} else {
history.push('/control');
}
}
};

17
src/components/flight/FlightApprovalsReport.js

@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useState } from 'react';
import Flatpickr from 'react-flatpickr';
import { Button, Input, CustomInput, Col, Row } from '@component/ui';
import { Search, Calendar } from 'react-feather';
@ -16,19 +16,6 @@ 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);
@ -36,7 +23,7 @@ export default function FlightApprovalsReport(props) {
};
return (
<div className='layer-content' onDragEnd={props.handleDragEnd} draggable>
<div className='layer-content'>
<div className='layer-ti'>
<h4>비행승인 신청 검토결과 현황</h4>
</div>

18
src/components/flight/NewFlightApprovalsReport.js

@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } 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,20 @@ export default function NewFlightApprovalsReport(props) {
endDate: dayjs().format('YYYY-MM-DD')
});
useEffect(() => {
const popupSyncSearchData = JSON.parse(localStorage.getItem('popupState'));
if (popupSyncSearchData) {
setFilterArea(popupSyncSearchData.selected);
setFilterId(popupSyncSearchData.filter);
setSearchDate({
startDate: popupSyncSearchData.startDate,
endDate: popupSyncSearchData.endDate
});
localStorage.removeItem('popupState');
}
}, []);
const handleKeyDown = e => {
if (e.key === 'Enter') {
props.handlerSearch(filterId, searchDate, filterArea);
@ -23,7 +37,7 @@ export default function NewFlightApprovalsReport(props) {
};
return (
<div className='layer-content'>
<div className='layer-content' onDragEnd={props.handleDragEnd} draggable>
<div className='layer-ti'>
<h4>비행승인 신청 검토결과 현황</h4>
</div>

692
src/components/flight/NewFlightApprovalsTable.js

@ -3,8 +3,8 @@ import { useDispatch, useSelector } from '@src/redux/store';
import { Button, Card } from '@component/ui';
import dayjs from 'dayjs';
import { openModal } from '@src/redux/features/comn/message/messageSlice';
import { Table } from 'antd';
import { FaAngleDown, FaAngleUp } from 'react-icons/fa';
import { Form, Input, InputNumber, Popconfirm, Table, Typography } from 'antd';
export default function NewFlightApprovalsTable(props) {
const dispatch = useDispatch();
@ -22,11 +22,57 @@ export default function NewFlightApprovalsTable(props) {
// 확장된 행 키
const [expandedRowKeys, setExpandedRowKeys] = useState([]);
// 수정 키
const [editingKey, setEditingKey] = useState('');
const [form] = Form.useForm();
// 수정 키 확인
const isEditing = record => record.key === editingKey;
// 승인, 미승인, 비대상 건수 계산
useEffect(() => {
resApprovalCd();
}, [laancAprvList]);
// 수정 이벤트
const edit = record => {
form.setFieldsValue({
bufferZone: '',
fltElev: '',
...record
});
setEditingKey(record.key);
};
// 취소 이벤트
const cancel = () => {
setEditingKey('');
};
// 저장 이벤트
const save = async key => {
try {
const row = await form.validateFields();
const newData = [...laancAprvList];
const index = newData.findIndex(item => key === item.key);
if (index > -1) {
const item = newData[index];
newData.splice(index, 1, { ...item, ...row });
setData(newData);
setEditingKey('');
} else {
newData.push(row);
setData(newData);
setEditingKey('');
}
} catch (errInfo) {
console.log('Validate Failed:', errInfo);
}
};
// 데이터
const columns = [
{
title: (
@ -38,23 +84,29 @@ export default function NewFlightApprovalsTable(props) {
),
dataIndex: 'applyNo',
align: 'center',
width: '40px',
key: 'applyNo',
editable: true
width: '40px'
},
{
title: (
<>
신청 <br />
일자
신청 <br />
</>
),
dataIndex: 'applyDt',
width: '60px',
width: '80px',
align: 'center',
key: 'applyDt',
editable: true,
render: text => dayjs(text).format('YYYY-MM-DD')
render: text => dayjs(text).format(' M월')
},
{
title: (
<>
신청 <br />
</>
),
dataIndex: 'applyDt',
width: '80px',
align: 'center',
render: text => dayjs(text).format('DD일')
},
{
title: (
@ -66,32 +118,58 @@ export default function NewFlightApprovalsTable(props) {
dataIndex: 'areaList',
align: 'center',
width: '85px',
key: 'areaList',
editable: true,
render: areaList => <>{areaList.length}</>
},
{
title: <>신청자</>,
dataIndex: 'applyDt',
width: '78px',
width: '90px',
align: 'center',
key: 'applyDt',
editable: true,
render: text => '홍*동'
},
{
title: (
<>
<br />
<br />
구역
</>
),
dataIndex: 'applyDt',
width: '75px',
dataIndex: 'areaList',
width: '90px',
align: 'center',
key: 'applyDt',
editable: true,
render: text => '서울시 마포구상암동 1674 (원추)'
render: areaList => {
return areaList.length <= 1 ? '서울시특별시' : '-';
}
},
{
title: (
<>
행정 <br />
구역
<br />2
</>
),
dataIndex: 'areaList',
width: '90px',
align: 'center',
render: areaList => {
return areaList.length <= 1 ? '강서구' : '-';
}
},
{
title: (
<>
상세 <br />
주소
</>
),
dataIndex: 'areaList',
width: '100px',
align: 'center',
render: areaList => {
return areaList.length <= 1 ? '가양동439-1(원추)' : '-';
}
},
{
title: (
@ -103,73 +181,155 @@ export default function NewFlightApprovalsTable(props) {
dataIndex: 'areaList',
align: 'center',
width: '85px',
key: 'latLon',
editable: true,
render: areaList => (
render: areaList => {
return areaList.length <= 1 ? (
<>
{areaList[0].lat.toFixed(5)},
<br />
{areaList[0].lon.toFixed(5)}
</>
) : (
'-'
);
}
},
{
title: (
<>
{areaList[0].lat.toFixed(5)},
<br />
{areaList[0].lon.toFixed(5)}
비행 <br />
반경
</>
)
),
dataIndex: 'bufferZone',
align: 'center',
width: '70px',
editable: true,
render: (text, record) => {
return text ? text : '-';
}
},
{
title: (
<>
반경 <br />
(M)
해발 <br />
고도
</>
),
dataIndex: 'areaList',
dataIndex: 'fltElev',
align: 'center',
width: '70px',
key: 'bufferZone',
editable: true,
render: areaList => <>{areaList[0].bufferZone}</>
render: (text, record) => {
return text ? text : '-';
}
},
{
title: (
<>
고도 <br />
(M)
세부 <br />
사항
</>
),
dataIndex: 'areaList',
key: 'fltElev',
align: 'center',
width: '70px',
editable: true,
render: areaList => <>{areaList[0].fltElev}</>
width: '110px',
render: areaList => {
return areaList.length <= 1 ? <>-</> : '-';
}
},
{
title: (
<>
검토 <br />
결과
비행 <br />
목적
</>
),
dataIndex: 'areaList',
align: 'center',
width: '85px',
key: 'approvalCd',
width: '110px',
render: areaList => {
return areaList.length <= 1 ? <>기타</> : '-';
}
},
{
title: (
<>
긴급 <br />
구조 <br />
기관
</>
),
dataIndex: 'areaList',
align: 'center',
width: '110px',
editable: true,
render: areaList => {
return areaList.length <= 1 ? <></> : '-';
}
},
{
title: <>비고</>,
dataIndex: 'areaList',
align: 'center',
width: '110px',
editable: true,
render: areaList => (
render: areaList => {
return areaList.length <= 1 ? <>비행 고도 특별 주의 입니다.</> : '-';
}
},
{
title: (
<>
{areaList[0].approvalCd === 'U'
? '비대상'
: areaList[0].approvalCd === 'S'
? '승인'
: '미승인'}
검토 <br />
결과
</>
)
),
dataIndex: 'areaList',
align: 'center',
width: '110px',
render: areaList => {
const approvalCounts = areaList.reduce(
(counts, item) => {
if (item.approvalCd === 'U') {
counts.unapproved += 1;
} else if (item.approvalCd === 'S') {
counts.approved += 1;
} else {
counts.pending += 1;
}
return counts;
},
{ unapproved: 0, approved: 0, pending: 0 }
);
return (
<>
{areaList.length > 1 ? (
<>
승인: {approvalCounts.approved} <br />
미승인:
{approvalCounts.pending}<br /> 비대상:
{approvalCounts.unapproved}
</>
) : (
<>
{areaList[0].approvalCd === 'U'
? '비대상'
: areaList[0].approvalCd === 'S'
? '승인'
: '미승인'}
</>
)}
</>
);
}
},
{
title: <>더보기</>,
dataIndex: 'areaList',
align: 'center',
width: '80px',
key: 'more',
editable: true,
width: '130px',
render: (areaList, record) =>
areaList.length > 2 ? (
<Button color='flat-dark' onClick={() => handleExpand(record.key)}>
@ -188,34 +348,107 @@ export default function NewFlightApprovalsTable(props) {
) : (
<>-</>
)
},
{
title: '편집',
dataIndex: 'operation',
align: 'center',
width: '110px',
render: (_, record) => {
const editable = isEditing(record);
return record.areaList.length <= 1 ? (
editable ? (
<span>
<Typography.Link
onClick={() => save(record.key)}
style={{
marginRight: 8
}}
>
저장
</Typography.Link>
<Typography.Link onClick={() => cancel()}>취소</Typography.Link>
</span>
) : (
<Button color='flat-dark'>
<Typography.Link
disabled={editingKey !== ''}
onClick={() => edit(record)}
>
Edit
</Typography.Link>
</Button>
)
) : (
<>-</>
);
}
}
];
// 확장된 테이블 데이터
const expandedRowRender = record => {
const childColumns = [
{
dataIndex: 'applyNo',
width: '30px',
width: '40px',
align: 'center'
},
{
dataIndex: '6월',
width: '80px',
align: 'center',
key: 'applyNo'
render: text => dayjs(text).format('M월')
},
{
dataIndex: 'applyDt',
width: '85px',
dataIndex: '21일',
width: '80px',
align: 'center',
key: 'applyDt'
render: text => dayjs(text).format('DD일')
},
{
dataIndex: 'zoneNo',
align: 'center',
width: '85px',
key: 'zoneNo',
editable: true
render: text => {
return <>{text}</>;
}
},
{
dataIndex: '홍길동',
align: 'center',
width: '90px',
render: text => {
return <>*</>;
}
},
{
dataIndex: '서울시특별시',
align: 'center',
width: '90px',
render: text => {
return <>서울시특별시</>;
}
},
{
dataIndex: '강남구',
align: 'center',
width: '90px',
render: text => {
return <>강서구</>;
}
},
{
dataIndex: '가양동439-1(원추)',
align: 'center',
width: '100px',
render: text => {
return <>가양동439-1(원추)</>;
}
},
{
align: 'center',
width: '85px',
key: 'latLon',
dataIndex: ['lat', 'lon'],
render: (text, record) => {
const lat = record.lat;
@ -231,28 +464,101 @@ export default function NewFlightApprovalsTable(props) {
{
dataIndex: 'bufferZone',
align: 'center',
width: '85px',
key: 'bufferZone'
editable: true,
width: '70px'
},
{
dataIndex: 'fltElev',
align: 'center',
width: '85px',
key: 'fltElev'
editable: true,
width: '70px'
},
{
dataIndex: '세부사항',
align: 'center',
editable: true,
width: '110px',
render: text => {
return <>기타</>;
}
},
{
dataIndex: '비행목적',
align: 'center',
width: '110px',
render: text => {
return <>레저비행</>;
}
},
{
dataIndex: '긴급구조기관',
align: 'center',
editable: true,
width: '110px',
render: text => {
return <>-</>;
}
},
{
dataIndex: '비고',
align: 'center',
editable: true,
width: '110px',
render: text => {
return <>-</>;
}
},
{
dataIndex: 'approvalCd',
align: 'center',
key: 'approvalCd',
width: '110px',
render: text => (
<>{text === 'U' ? '비대상' : text === 'S' ? '승인' : '미승인'}</>
)
},
{
dataIndex: '더보기',
align: 'center',
editable: true,
width: '130px',
render: text => {
return <>-</>;
}
},
{
dataIndex: 'operation',
align: 'center',
width: '110px',
render: (_, record) => {
const editable = isEditing(record);
return editable ? (
<span>
<Typography.Link
onClick={() => save(record.key)}
style={{
marginRight: 8
}}
>
저장
</Typography.Link>
<Typography.Link onClick={() => cancel()}>취소</Typography.Link>
</span>
) : (
<Button color='flat-dark'>
<Typography.Link
disabled={editingKey !== ''}
onClick={() => edit(record)}
>
Edit
</Typography.Link>
</Button>
);
}
}
];
const data = [];
record.areaList.map((item, index) => {
if (index < 1) return;
data.push({
key: `${record.applyNo}-${index}`,
applyNo: item.applyNo,
@ -267,27 +573,90 @@ export default function NewFlightApprovalsTable(props) {
});
return (
<Table
rowClassName={record => {
let className = '';
if (record.approvalCd === 'S') {
className += 'flight-approval-row';
} else if (record.approvalCd === 'F') {
className += 'flight-not-approval-row';
} else className;
<Form form={form} component={false}>
<Table
rowClassName={record => {
let className = '';
if (record.approvalCd === 'S') {
className += 'flight-approval-row';
} else if (record.approvalCd === 'F') {
className += 'flight-not-approval-row';
} else className;
return className;
}}
size='small'
bordered
columns={childColumns}
dataSource={data}
pagination={false}
showHeader={false}
/>
if (record.planAreaSno === props.selected) {
className += ' flight-approval-row-click';
}
return className;
}}
size='small'
columns={childColumns.map(col => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: record => ({
record,
inputType:
col.dataIndex === 'bufferZone' || col.dataIndex === 'fltElev'
? 'number'
: 'text',
dataIndex: col.dataIndex,
title: col.title,
editing: isEditing(record)
})
};
})}
dataSource={data}
pagination={false}
components={{
body: {
cell: EditableCell
}
}}
onRow={record => ({
onClick: event => {
if (editingKey !== '') {
return; // edit 상태면 이벤트 실행 안 함
}
// 이벤트 버블링을 막기 위해 클릭된 요소가 'Edit' 버튼인지 확인
if (
event.target.tagName !== 'BUTTON' &&
event.target.tagName !== 'A' &&
!event.target.closest('.ant-input') && // input 요소를 감지
!event.target.closest('.ant-input-number') // inputNumber 요소를 감지
) {
handleInRowClick(record);
}
}
})}
showHeader={false}
rowHoverable={false}
/>
</Form>
);
};
// 테이블 데이터
const mergedColumns = columns.map(col => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: record => ({
record,
inputType:
col.dataIndex === 'bufferZone' || col.dataIndex === 'fltElev'
? 'number'
: 'text',
dataIndex: col.dataIndex,
title: col.title,
editing: isEditing(record)
})
};
});
// 모달 오픈 핸들러
const handlerOpenModal = (approval, fltElev, fltElevMax) => {
if (approval === 'F') {
@ -319,14 +688,6 @@ export default function NewFlightApprovalsTable(props) {
}
};
// 테이블 내부 행 클릭 이벤트
const handleInRowClick = row => {
console.log('>>', row);
handlerOpenModal(row.approvalCd, row.fltElev, row.fltElevMax);
props.handlerDetail(row);
};
// 날짜 포맷 변경
const formatDate = dateString => {
const date = new Date(dateString);
@ -359,6 +720,7 @@ export default function NewFlightApprovalsTable(props) {
});
};
//
const handleExpand = key => {
const expanded = expandedRowKeys.includes(key);
const keys = expanded
@ -367,13 +729,30 @@ export default function NewFlightApprovalsTable(props) {
setExpandedRowKeys(keys);
};
// 테이블 행 클릭 이벤트
const handleRowClick = row => {
if (row.areaList.length > 1) return;
handlerOpenModal(
row.areaList[0].approvalCd,
row.areaList[0].fltElev,
row.areaList[0].fltElevMax
);
props.handlerDetail(row.areaList[0]);
};
// 확장된 테이블 행 클릭 이벤트
const handleInRowClick = row => {
handlerOpenModal(row.approvalCd, row.fltElev, row.fltElevMax);
props.handlerDetail(row);
};
return (
<div className='layer-content'>
<div className='layer-ti d-flex justify-content-between align-items-center'>
<h4>비행승인 신청 검토결과 목록</h4>
<span className='search-case'>
{formatDate(props.startDate)}
{props.endDate && props.startDate != props.endDate
{props.endDate && props.startDate !== props.endDate
? '~' + formatDate(props.endDate) + ' '
: null}
{approvalCdValue.S + approvalCdValue.F + approvalCdValue.U}
@ -401,33 +780,63 @@ export default function NewFlightApprovalsTable(props) {
style={{ width: '100%' }}
>
{laancAprvList?.length > 0 ? (
<Table
dataSource={laancAprvList.map((item, index) => ({
...item,
key: index
}))}
columns={columns}
size='small'
rowClassName={record => {
let className = '';
if (record.areaList[0].approvalCd === 'S') {
className += 'flight-approval-row editable-row';
} else if (record.areaList[0].approvalCd === 'F') {
className += 'flight-not-approval-row editable-row';
} else className += 'editable-row';
<Form form={form} component={false}>
<Table
components={{
body: {
cell: EditableCell
}
}}
dataSource={laancAprvList.map((item, index) => ({
...item,
key: index
}))}
columns={mergedColumns}
rowClassName={record => {
let className = '';
if (record?.areaList[0]?.approvalCd === 'S') {
className += 'flight-approval-row editable-row';
} else if (record.areaList[0].approvalCd === 'F') {
className += 'flight-not-approval-row editable-row';
} else className += 'editable-row';
if (record.areaList[0].planAreaSno === props.selected) {
className += ' flight-approval-row-click';
}
if (expandedRowKeys.includes(record.key)) {
className += ' expanded-row';
}
return className;
}}
expandable={{
expandedRowRender,
expandedRowKeys: expandedRowKeys,
onExpand: handleExpand,
rowExpandable: record => record.areaList.length > 1 // areaList가 1개 이상인 경우에만 확장 가능
}}
tableLayout='auto'
rowHoverable={false}
expandIconColumnIndex={-1} // 기본 확장 아이콘을 숨김
/>
return className;
}}
onRow={record => ({
onClick: event => {
if (editingKey !== '') {
return; // edit 상태면 이벤트 실행 안 함
}
// 이벤트 버블링을 막기 위해 클릭된 요소가 'Edit' 버튼인지 확인
if (
event.target.tagName !== 'BUTTON' &&
event.target.tagName !== 'A' &&
!event.target.closest('.ant-input') && // input 요소를 감지
!event.target.closest('.ant-input-number') // inputNumber 요소를 감지
) {
handleRowClick(record);
}
}
})}
expandable={{
expandedRowRender,
expandedRowKeys: expandedRowKeys,
onExpand: (expanded, record) => handleExpand(record.key),
rowExpandable: record => record?.areaList?.length > 1
}}
scroll={{
x: 1500
}}
rowHoverable={false}
expandIconColumnIndex={-1}
/>
</Form>
) : (
<div
className='d-flex justify-content-center align-items-center '
@ -442,3 +851,50 @@ export default function NewFlightApprovalsTable(props) {
</div>
);
}
const EditableCell = ({
editing,
dataIndex,
title,
inputType,
record,
index,
children,
...restProps
}) => {
const inputNode =
inputType === 'number' ? (
<Input
min={0}
onKeyPress={e => {
if (!/[0-9]/.test(e.key)) {
e.preventDefault(); // 숫자 외의 입력을 막음
}
}}
/>
) : (
<Input />
);
return (
<td {...restProps}>
{editing ? (
<Form.Item
name={dataIndex}
style={{
margin: 0
}}
rules={[
{
required: true,
message: `값을 입력 해 주세요.`
}
]}
>
{inputNode}
</Form.Item>
) : (
children
)}
</td>
);
};

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

File diff suppressed because it is too large Load Diff

193
src/containers/flight/NewFlightApprovalsContainer.js

@ -20,7 +20,7 @@ import { getLaancAprvList } from '@src/redux/features/laanc/laancThunk';
import dayjs from 'dayjs';
import { setLogout } from '@src/redux/features/account/auth/authThunk';
export default function NewFlightApprovalsContainer() {
export default function NewFlightApprovalsContainer({ mode }) {
const dispatch = useDispatch();
const history = useHistory();
@ -33,7 +33,7 @@ export default function NewFlightApprovalsContainer() {
const [mapObject, setMapObject] = useState();
const { areaCoordList, isOpenModal } = useSelector(state => state.laancState);
const { user } = useSelector(state => state.authState);
//
const [startDate, setStartDate] = useState(dayjs().format('YYYY-MM-DD'));
const [endDate, setEndDate] = useState();
@ -44,6 +44,14 @@ export default function NewFlightApprovalsContainer() {
const map = useSelector(state => state.mapState.map);
// popup
const [isPopup, setIsPopup] = useState(false);
const [popup, setPopup] = useState(null);
const popupRef = useRef(null);
const rightMenuRef = useRef(null);
const savedRightMenuRef = useRef(null);
const [clientX, setClientX] = useState(0);
const previewGeo2 = {
type: 'FeatureCollection',
features: []
@ -61,20 +69,131 @@ export default function NewFlightApprovalsContainer() {
}, []);
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 (rightMenuRef.current) {
savedRightMenuRef.current = rightMenuRef.current.getBoundingClientRect();
}
if (popup) {
timer = setInterval(() => {
if (popup.closed) {
setIsPopup(false);
clearInterval(timer);
}
if (savedRightMenuRef.current) {
const popupX = popup.screenX;
const parentX = window.screenX + savedRightMenuRef.current.left - 70;
const parentWidth = savedRightMenuRef.current.width;
if (popupX >= parentX && popupX <= parentX + parentWidth) {
popup.close();
setIsPopup(false);
clearInterval(timer);
}
}
}, 500); // 1초마다 체크
}
window.addEventListener('message', childMessage);
return () => {
clearInterval(timer);
window.removeEventListener('message', childMessage);
};
}, [popup]);
console.log(history);
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
// );
// setClientX(window.screenX + window.outerWidth - e.clientX);
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);
@ -112,6 +231,12 @@ export default function NewFlightApprovalsContainer() {
}
// );
setFilter(search);
if (popup) {
popupRef.current.postMessage({
type: 'handlerSearchRs',
payload: { search }
});
}
};
const handlerDetail = area => {
@ -123,28 +248,32 @@ export default function NewFlightApprovalsContainer() {
};
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) {
const areas = areaCoordList[0];
@ -188,20 +317,28 @@ export default function NewFlightApprovalsContainer() {
<div className='map' style={{ width: '100%' }}>
<MapControl />
</div>
<div className='right-menu active'>
<div className='right-layer active flight-approval-layer'>
<div className='layer-content'>
<NewFlightApprovalsReport handlerSearch={handlerSearch} />
<NewFlightApprovalsTable
filter={filter}
startDate={startDate}
endDate={endDate}
selected={selected}
handlerDetail={handlerDetail}
/>
{!isPopup && (
<div className='right-menu active'>
<div
className='right-layer active flight-approval-layer'
ref={rightMenuRef}
>
<div className='layer-content'>
<NewFlightApprovalsReport
handlerSearch={handlerSearch}
handleDragEnd={handleDragEnd}
/>
<NewFlightApprovalsTable
filter={filter}
startDate={startDate}
endDate={endDate}
selected={selected}
handlerDetail={handlerDetail}
/>
</div>
</div>
</div>
</div>
)}
</>
);
}

159
src/containers/flight/flightApprovalsContainer.js

@ -48,10 +48,6 @@ export default function FlightApprovalsContainer({ mode }) {
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',
@ -77,107 +73,10 @@ export default function FlightApprovalsContainer({ mode }) {
useEffect(() => {
if (map) {
window._mapbox = map;
let mapInstance = mode === 'container' ? map : window.opener._mapbox;
setMapObject(mapInstance);
setMapObject(map);
}
}, [map]);
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);
setEndDate(searchDate.endDate);
@ -215,12 +114,6 @@ export default function FlightApprovalsContainer({ mode }) {
// );
setFilter(search);
if (popup) {
popupRef.current.postMessage({
type: 'handlerSearchRs',
payload: { search }
});
}
};
const handlerDetail = area => {
@ -232,30 +125,27 @@ export default function FlightApprovalsContainer({ mode }) {
};
const handlerMapInit = () => {
let mapInstance = mode === 'container' ? map : window.opener._mapbox;
if (mapInstance.getSource('preview')) {
if (map.getSource('preview')) {
} else {
mapInstance.addSource('preview', {
map.addSource('preview', {
type: 'geojson',
data: previewGeo2
});
mapInstance.addLayer(flightlayerWayPoint('preview'));
mapInstance.addLayer(flightlayerBuffer('preview'));
mapInstance.addLayer(flightlayerPolygon('preview'));
mapInstance.addLayer(flightlayerPolyline('preview'));
map.addLayer(flightlayerWayPoint('preview'));
map.addLayer(flightlayerBuffer('preview'));
map.addLayer(flightlayerPolygon('preview'));
map.addLayer(flightlayerPolyline('preview'));
}
dispatch(clientSetIsMapLoading(true));
const preview = mapInstance.getSource('preview');
const preview = map.getSource('preview');
if (preview) setPreviewLayer(preview);
setIsMapLoading(true);
setMapObject(mapInstance);
dispatch(clientMapInit(mapInstance));
setMapObject(map);
dispatch(clientMapInit(map));
};
const handlerPreviewDraw = () => {
if (areaCoordList.length > 0) {
@ -333,25 +223,20 @@ export default function FlightApprovalsContainer({ mode }) {
</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}
/>
</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>
</>
);
}

8
src/containers/rightMenuContainer.js

@ -2,8 +2,8 @@ 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';
import NewFlightApprovalsTable from '@src/components/flight/NewFlightApprovalsTable';
import NewFlightApprovalsReport from '@src/components/flight/NewFlightApprovalsReport';
function RightMenuContainer() {
const [filter, setFilter] = useState('');
@ -120,8 +120,8 @@ function RightMenuContainer() {
style={{ width: '100vw' }}
>
<div className='layer-content'>
<FlightApprovalsReport handlerSearch={handlerSearch} />
<FlightApprovalsTable
<NewFlightApprovalsReport handlerSearch={handlerSearch} />
<NewFlightApprovalsTable
filter={filter}
startDate={startDate}
endDate={endDate}

20
src/redux/features/laanc/laancThunk.ts

@ -21,6 +21,7 @@ import {
import { createAsyncThunk } from '@reduxjs/toolkit';
import { openModal } from '../comn/message/messageSlice';
import { ERROR_TITLE, ERROR_MESSAGE } from '@src/configs/msgConst';
import { area } from '@turf/turf';
// laanc 승인
export const setApprovalLaancFlight = createAsyncThunk(
@ -275,7 +276,24 @@ export const getLaancAprvList = createAsyncThunk(
const data: ILaancAprvListRs[] = await axios.get(`api/bas/dos/plan`, {
params: rq
});
return data;
const result = data.map(item => {
if (item.areaList.length <= 1) {
return {
...item,
fltElev: item.areaList[0].fltElev,
bufferZone: item.areaList[0].bufferZone,
areaList: item.areaList
};
} else {
return {
...item
};
}
});
console.log('>>', result);
return result;
} catch (error) {
openModal({
header: ERROR_TITLE,

4
src/redux/rootReducer.ts

@ -42,8 +42,8 @@ const saveSubsetFilter = createTransform<laancState, Partial<laancState>>(
const rootPersistConfig = {
key: 'root', // localStorage key
storage, // localStorage
whitelist: ['laancState'], // target (reducer name)
transforms: [saveSubsetFilter] // 특정 상태만 저장
whitelist: ['laancState'] // target (reducer name)
// transforms: [saveSubsetFilter] // 특정 상태만 저장
};
const rootReducer = (state: any, action: any) => {

24
src/router/hoc/authenticationCheck.tsx

@ -1,5 +1,5 @@
import { ReactNode, useEffect, useLayoutEffect, useState } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import { RouteComponentProps, useHistory } from 'react-router-dom';
import { useSelector, useDispatch } from '@src/redux/store';
import { checkToken } from '@src/redux/features/account/auth/authThunk';
import { ICheckAuthencationRs } from '@src/redux/features/account/auth/authState';
@ -23,6 +23,7 @@ interface IProps extends RouteComponentProps {
export default function (SpecificComponent: ReactNode, option: IOption) {
function AuthenticationCheck(props: RouteComponentProps) {
const dispatch = useDispatch();
const history = useHistory();
const { user } = useSelector(state => state.authState);
const [isRender, setIsRender] = useState<boolean>(false);
@ -30,11 +31,24 @@ export default function (SpecificComponent: ReactNode, option: IOption) {
props: IProps
) => JSX.Element;
useEffect(() => {
if (props.match.path === '/account/login') {
setIsRender(true);
return;
useLayoutEffect(() => {
if (user?.cptAuthCode === 'DF0002') {
const pathname = history.location.pathname;
if (
!(pathname == '/flight/Approvals/new' || pathname == '/rightMenu')
) {
props.history.replace('/flight/Approvals/new');
return;
}
}
}, [user]);
useEffect(() => {
// if (props.match.path === '/account/login') {
// setIsRender(true);
// return;
// }
dispatch(checkToken() as any).then(({ payload }) => {
try {
// 로그인 해야만 접근 가능한 페이지인지 체크하는 옵션

7
src/router/routes/RouteFlight.js

@ -1,13 +1,6 @@
import { lazy } from 'react';
const RouteFlight = [
{
path: '/flight/approvals',
component: lazy(() => import('../../views/flight/FlightView')),
meta: {
authRoute: true
}
},
{
path: '/flight/approvals/new',
component: lazy(() => import('../../views/flight/NewFlightView')),

2
src/views/flight/FlightView.js

@ -11,7 +11,7 @@ export default function FlightView() {
{/* <Helmet>
<title>관제시스템</title>
</Helmet> */}
<FlightApprovalsContainer mode='container' />;
<FlightApprovalsContainer />;
</div>
);
}

2
src/views/flight/NewFlightView.js

@ -9,7 +9,7 @@ export default function NewFlightView() {
{/* <Helmet>
<title>관제시스템</title>
</Helmet> */}
<NewFlightApprovalsContainer />;
<NewFlightApprovalsContainer mode='container' />;
</div>
);
}

Loading…
Cancel
Save