Browse Source

QR CODE 임시 브런치

pull/2/head
박상현 10 months ago
parent
commit
1855087fed
  1. 127
      src/components/laanc/LaancQr.js
  2. 5
      src/components/laanc/list/LaancDetail.js
  3. 6
      src/components/laanc/list/LaancGrid.js
  4. 12
      src/components/laanc/list/LaancSearch.js
  5. 5
      src/components/laanc/step/LaacnStep3.js
  6. 572
      src/components/laanc/step/LaancStep1.js
  7. 5
      src/components/laanc/step/LaancStep2.js
  8. 15
      src/containers/laanc/LaancContainer.js
  9. 447
      src/containers/laanc/LaancPlanContainer.js
  10. 17
      src/modules/laanc/actions/laancActions.ts
  11. 5
      src/modules/laanc/apis/laancApi.ts
  12. 11
      src/modules/laanc/models/laancModels.ts
  13. 6
      src/modules/laanc/reducers/laancReducers.ts
  14. 17
      src/modules/laanc/sagas/laancSagas.ts

127
src/components/laanc/LaancQr.js

@ -0,0 +1,127 @@
import { useEffect, useState, useRef } from 'react';
import { ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap';
import { ErrorModal } from '../../components/modal/ErrorModal';
import axios from '../../modules/utils/customAxiosUtil';
import { debounce } from 'lodash';
// 이제 handleUserEvent는 300ms 동안 추가 호출이 없을 때만 실행됩니다.
export default function LaancQr({ isPopUp, setIsPopUp, data, handlerStep }) {
const [isPolling, setIsPolling] = useState(true);
const [isErrorModal, setIsErrorModal] = useState({
isOpen: false,
title: '',
desc: ''
});
const pollingIntervalRef = useRef(null);
// 언마운트시 폴링 중지
useEffect(() => {
return () => {
stopPolling();
};
}, []);
// 폴링 로직
useEffect(() => {
if (isPolling) {
startPolling();
} else {
stopPolling();
}
}, [isPolling]);
// 폴링 시작
const startPolling = () => {
pollingIntervalRef.current = setInterval(axiosData, 3000);
};
// 폴링 중지
const stopPolling = () => {
clearInterval(pollingIntervalRef.current);
};
// QR 인증 폴링
const axiosData = async () => {
try {
const res = await axios.get(`api/bas/laanc/ts/qr/${data.confirmKey}`);
handleResponse(res, 'polling');
} catch (error) {
handleError(error);
}
};
// axios 호출 처리 로직
const handleResponse = (res, type) => {
if (res.data.result === true) {
// dispatch(LaancAction.LAANC_TS_QR.success(res.data));
setIsPolling(false);
setIsPopUp(false);
handlerStep(2);
} else if (res.data.result === '시간 만료') {
setIsErrorModal({
isOpen: true,
title: '인증 만료',
desc: <>인증 시간이 만료되었습니다.</>
});
setIsPopUp(false);
} else if (type === 'user') {
// setIsPolling(true);
startPolling();
}
};
// axios 호출 에러 처리 로직
const handleError = error => {
console.log('>>', error);
setIsErrorModal({
isOpen: true,
title: '오류',
desc: <>처리중 오류가 발생하였습니다</>
});
};
// 사용자 확인 버튼 헨들러
const handleUserEvent = debounce(async event => {
stopPolling();
try {
const res = await axios.get(`api/bas/laanc/ts/qr/${data.confirmKey}`);
handleResponse(res, 'user');
} catch (error) {
handleError(error);
}
}, 3000);
return (
<>
<ModalHeader
toggle={() => {
setIsPopUp(!isPopUp);
}}
>
QR인증
</ModalHeader>
<ModalBody
className='notam-info'
style={{
display: 'flex',
justifyContent: 'center',
height: '18vw'
}}
>
<img src={data?.qrcode} alt='QR Code' />
</ModalBody>
<ModalFooter>
<Button
color='primary'
// onClick={() => {
// setIsPopUp(!isPopUp);
// }}
onClick={handleUserEvent}
>
확인
</Button>
</ModalFooter>
<ErrorModal modal={isErrorModal} setModal={setIsErrorModal} />
</>
);
}

5
src/components/laanc/list/LaancDetail.js

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { import {
Row, Row,
@ -7,7 +7,6 @@ import {
ModalHeader, ModalHeader,
ModalBody, ModalBody,
ModalFooter, ModalFooter,
Alert,
FormGroup, FormGroup,
Label, Label,
Input Input
@ -33,6 +32,8 @@ export default function LaancDetail({ data, handlerLaancClose }) {
}; };
const { user } = useSelector(state => state.authState); const { user } = useSelector(state => state.authState);
const { termsList } = useSelector(state => state.accountState); const { termsList } = useSelector(state => state.accountState);
// Laanc 약관 동의
useEffect(() => { useEffect(() => {
dispatch( dispatch(
TermsActions.termsList.request({ TermsActions.termsList.request({

6
src/components/laanc/list/LaancGrid.js

@ -1,10 +1,9 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { Document, Page, pdfjs } from 'react-pdf'; import { pdfjs } from 'react-pdf';
import { GridDatabase } from '@src/components/crud/grid/GridDatatable'; import { GridDatabase } from '@src/components/crud/grid/GridDatatable';
import { Row, Col, Card, Button, Spinner, Modal } from 'reactstrap'; import { Row, Col, Card, Button, Spinner, Modal } from 'reactstrap';
import * as LaancAction from '../../../modules/laanc/actions/laancActions'; import * as LaancAction from '../../../modules/laanc/actions/laancActions';
import LaancStep2 from '../step/LaancStep2';
import moment from 'moment'; import moment from 'moment';
import { import {
AREA_COORDINATE_LIST_SAVE, AREA_COORDINATE_LIST_SAVE,
@ -26,10 +25,12 @@ export default function LaancGrid() {
); );
const { loading } = useSelector(state => state.loadingReducer); const { loading } = useSelector(state => state.loadingReducer);
// Laanc 승인 신청 목록 조회
useEffect(() => { useEffect(() => {
return () => dispatch(LaancAction.LAANC_APPROVAL_DETAIL_INIT()); return () => dispatch(LaancAction.LAANC_APPROVAL_DETAIL_INIT());
}, []); }, []);
// Laanc 승인 신청 목록 비행 구역 조회
useEffect(() => { useEffect(() => {
if (laancDetail) { if (laancDetail) {
dispatch(FLIGHT_PLAN_AREA_BUFFER_LIST.request(laancDetail?.areaList)); dispatch(FLIGHT_PLAN_AREA_BUFFER_LIST.request(laancDetail?.areaList));
@ -43,6 +44,7 @@ export default function LaancGrid() {
dispatch(LaancAction.LAANC_DETAIL.request(planSno)); dispatch(LaancAction.LAANC_DETAIL.request(planSno));
}; };
// Laanc 승인 신청 목록 닫기
const handlerLaancClose = () => { const handlerLaancClose = () => {
dispatch(drawTypeChangeAction('')); dispatch(drawTypeChangeAction(''));
dispatch(AREA_DETAIL_INIT()); dispatch(AREA_DETAIL_INIT());

12
src/components/laanc/list/LaancSearch.js

@ -6,7 +6,7 @@ import Flatpickr from 'react-flatpickr';
import moment from 'moment'; import moment from 'moment';
import * as LaancAction from '../../../modules/laanc/actions/laancActions'; import * as LaancAction from '../../../modules/laanc/actions/laancActions';
function LaancSearch() { function LaancSearch({ isSearch }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [date, setDate] = useState({ const [date, setDate] = useState({
@ -14,10 +14,19 @@ function LaancSearch() {
createEndDate: moment().subtract(-7, 'day').format('YYYY-MM-DD') createEndDate: moment().subtract(-7, 'day').format('YYYY-MM-DD')
}); });
// Laanc 승인 신청 목록 조회
useEffect(() => { useEffect(() => {
dispatch(LaancAction.LAANC_APRV_LIST.request({ ...date })); dispatch(LaancAction.LAANC_APRV_LIST.request({ ...date }));
}, []); }, []);
// Laanc 승인 신청시 검색
useEffect(() => {
if (isSearch) {
dispatch(LaancAction.LAANC_APRV_LIST.request({ ...date }));
}
}, [isSearch]);
// 날짜 선택 헨들러
const handlerChangeDate = selectedDates => { const handlerChangeDate = selectedDates => {
if (selectedDates.length === 2) { if (selectedDates.length === 2) {
const createStDate = moment(selectedDates[0]).format('YYYY-MM-DD'); const createStDate = moment(selectedDates[0]).format('YYYY-MM-DD');
@ -26,6 +35,7 @@ function LaancSearch() {
} }
}; };
// 검색 헨들러
const handlerClick = () => { const handlerClick = () => {
dispatch(LaancAction.LAANC_APRV_LIST.request({ ...date })); dispatch(LaancAction.LAANC_APRV_LIST.request({ ...date }));
}; };

5
src/components/laanc/step/LaacnStep3.js

@ -40,12 +40,14 @@ export default function LaacnStep3({
11: '25kg초과' 11: '25kg초과'
} }
}; };
const [centeredModal2, setCenteredModal2] = useState(false); const [centeredModal2, setCenteredModal2] = useState(false);
const [formModal, setFormModal] = useState(false); const [formModal, setFormModal] = useState(false);
const [numPages, setNumPages] = useState(null); // total const [numPages, setNumPages] = useState(null); // total
const { user } = useSelector(state => state.authState); const { user } = useSelector(state => state.authState);
const { laancPdf } = useSelector(state => state.laancState); const { laancPdf } = useSelector(state => state.laancState);
// PDF 다운로드
const handlerPdfDownload = e => { const handlerPdfDownload = e => {
if (laancPdf.pdfUrl) { if (laancPdf.pdfUrl) {
let alink = document.createElement('a'); let alink = document.createElement('a');
@ -55,6 +57,7 @@ export default function LaacnStep3({
} }
}; };
// PDF 페이지 로드
const onDocumentLoadSuccess = ({ numPages: nextNumPages }) => { const onDocumentLoadSuccess = ({ numPages: nextNumPages }) => {
setNumPages(nextNumPages); setNumPages(nextNumPages);
}; };
@ -238,7 +241,6 @@ export default function LaacnStep3({
<span className='on'></span> <span className='on'></span>
</li> </li>
</ul> </ul>
{/* outline onClick={() => setCenteredModal2(!centeredModal2)} */}
<Button outline onClick={() => setCenteredModal2(!centeredModal2)}> <Button outline onClick={() => setCenteredModal2(!centeredModal2)}>
완료 완료
</Button> </Button>
@ -261,7 +263,6 @@ export default function LaacnStep3({
<Page key={`page_${index + 1}`} pageNumber={index + 1} /> <Page key={`page_${index + 1}`} pageNumber={index + 1} />
))} ))}
</div> </div>
{/* <Page pageNumber={pageNum} /> */}
</Document> </Document>
<Row> <Row>

572
src/components/laanc/step/LaancStep1.js

@ -9,6 +9,9 @@ import FlightArea from '../map/FlightArea';
import { ErrorModal } from '../../modal/ErrorModal'; import { ErrorModal } from '../../modal/ErrorModal';
import { InfoModal } from '../../modal/InfoModal'; import { InfoModal } from '../../modal/InfoModal';
import { LaancModal } from '../LaancModal'; import { LaancModal } from '../LaancModal';
import { FLIGHT_PLAN_AREA_BUFFER_LIST } from '../../../modules/basis/flight/actions/basisFlightAction';
import LaancQr from '../../../components/laanc/LaancQr';
import axios from '../../../modules/utils/customAxiosUtil';
import moment from 'moment'; import moment from 'moment';
import { import {
Row, Row,
@ -21,18 +24,18 @@ import {
UncontrolledPopover, UncontrolledPopover,
PopoverBody, PopoverBody,
Label, Label,
Input Input,
Modal
} from 'reactstrap'; } from 'reactstrap';
export default function LaancStep1({ export default function LaancStep1({
handleChange, detailData,
handlerNext, setDetailData,
data,
centeredModal, centeredModal,
setCenteredModal, setCenteredModal,
handlerStep,
currentParm, currentParm,
handlerLaancClose, handlerLaancClose
handlerBufferApply
}) { }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -49,6 +52,8 @@ export default function LaancStep1({
const queryParams = new URLSearchParams(location.search); const queryParams = new URLSearchParams(location.search);
const mapParam = queryParams.get('map'); const mapParam = queryParams.get('map');
const [qrData, setQrData] = useState(); // qr 인증 데이터
const [isPopUp, setIsPopUp] = useState(false);
const [popoverCommercial, setPopoverCommercial] = useState(false); const [popoverCommercial, setPopoverCommercial] = useState(false);
const [popoverSchFltStDt, setPopoverSchFltStDt] = useState(false); const [popoverSchFltStDt, setPopoverSchFltStDt] = useState(false);
const [popoverSchFltEndDt, setPopoverSchFltEndDt] = useState(false); const [popoverSchFltEndDt, setPopoverSchFltEndDt] = useState(false);
@ -70,11 +75,12 @@ export default function LaancStep1({
url: '' url: ''
}); });
// URL 쿼리 파라미터 중 'map' 값을 가져옵니다.
useEffect(() => { useEffect(() => {
// URL 쿼리 파라미터 중 'map' 값을 가져옵니다.
if (!currentParm) setCenteredModal(mapParam != 'true' ? false : true); if (!currentParm) setCenteredModal(mapParam != 'true' ? false : true);
}, [location]); }, [location]);
// 일물 일출 데이터 호출
useEffect(() => { useEffect(() => {
if (areaCoordList) { if (areaCoordList) {
if (areaCoordList[0].coordList[0].lat !== 0) { if (areaCoordList[0].coordList[0].lat !== 0) {
@ -88,6 +94,330 @@ export default function LaancStep1({
} }
}, [areaCoordList]); }, [areaCoordList]);
// 적용 버튼 Reducer 업데이트 될때마다 검사 로직
useEffect(() => {
if (detailData.areaList[0].fltElev != 0) {
const maxElev = 150;
const controlledAltitudeExceededWarning =
laancArea?.duplicated &&
parseInt(
detailData.areaList[0].fltElev.replace('/^0+/', 'm', ''),
10
) >= laancElev[0] &&
parseInt(detailData.areaList[0].fltElev.replace('/^0+/', 'm', ''), 10) <
maxElev;
if (controlledAltitudeExceededWarning) {
setIsErrorModal({
isOpen: true,
title: '검토 결과 사전안내',
desc: (
<>
유효성 검사에 실패하여 승인 대상입니다.
<br />
제출하신 비행계획서의 고도는 {laancElev[0]}m이하에서만 비행이
가능합니다.
<br />
고도 설정을 다시 확인해주시기 바랍니다.
</>
)
});
handleChange({
type: 'area',
name: 'fltElev',
value: 0
});
}
}
}, [[laancElev]]);
// 비행계획서 작성 핸들러
const handleChange = ({ name, value, type, index, pIndex }) => {
const arrName = `${type}List`;
switch (type) {
case 'coord':
setDetailData(prevState => {
return {
...prevState,
areaList: [
{
...prevState.areaList[0],
coordList: value
}
]
};
});
break;
case 'area':
if (name === 'fltMethod' && value != '직접입력') {
setDetailData(prevState => {
const arr = [...prevState[arrName]];
const updatedetailData = {
...prevState[arrName][0],
[name]: value,
fltMothoeRm: ''
};
arr[0] = updatedetailData;
return {
...prevState,
[arrName]: arr
};
});
} else if (
detailData.areaList[0].areaType === 'LINE' ||
name === 'bufferZone'
) {
setDetailData(prevState => {
const arr = [...prevState[arrName]];
const prevBufferZone = prevState[arrName][0].bufferZone;
const updatedetailData = {
...prevState[arrName][0],
[name]: value,
concatBufferZone: prevBufferZone
};
arr[0] = updatedetailData;
return {
...prevState,
[arrName]: arr
};
});
} else {
setDetailData(prevState => {
const arr = [...prevState[arrName]];
const updatedetailData = {
...prevState[arrName][0],
[name]: value
};
arr[0] = updatedetailData;
return {
...prevState,
[arrName]: arr
};
});
}
break;
case 'pilot':
case 'arcrft':
{
setDetailData(prevState => {
const arr = [...prevState[arrName]];
const updatedetailData = {
...prevState[arrName][0],
[name]: value
};
arr[0] = updatedetailData;
return {
...prevState,
[arrName]: arr
};
});
}
break;
case 'plan':
default:
setDetailData(prevState => ({
...prevState,
[name]: value
}));
break;
}
};
// 스텝 1 다음 버튼 이벤트
const handlerNext = () => {
// 시작일자
const schFltStDt = moment(detailData.schFltStDt, 'YYYY-MM-DD HH:mm:ss');
// 종료일자
const schFltEndDt = moment(detailData.schFltEndDt, 'YYYY-MM-DD HH:mm:ss');
const currentDate = moment(); // 현재 날짜와 시간을 가져옵니다.
const validateAircraftWeightCode =
!detailData.arcrftList[0].arcrftTypeCd &&
(detailData.commercial === 'COMMERCIAL' ||
detailData.arcrftList[0].arcrftWghtCd == '9' ||
detailData.arcrftList[0].arcrftWghtCd == '10' ||
detailData.arcrftList[0].arcrftWghtCd == '11');
const validateidntfNumCode =
!detailData.arcrftList[0].idntfNum &&
(detailData.commercial === 'COMMERCIAL' ||
detailData.arcrftList[0].arcrftWghtCd == '9' ||
detailData.arcrftList[0].arcrftWghtCd == '10' ||
detailData.arcrftList[0].arcrftWghtCd == '11');
if (!detailData.fltType) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '비행 종류(상업/비상업)를 선택해주세요.'
});
return false;
} else if (
!schFltStDt.isAfter(currentDate) ||
!schFltEndDt.isAfter(currentDate)
) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '비행 일자가 이미 지난 일자입니다.'
});
return false;
} else if (schFltStDt.isAfter(schFltEndDt)) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '비행일자를 확인해주세요.'
});
return false;
} else if (schFltStDt.format('A h:mm') === 'PM 11:00') {
setIsErrorModal({
isOpen: true,
title: '특별 비행',
desc: (
<>
야간 비행은 특별 비행에 해당됩니다.
<br />
특별 비행의 경우 드론원스톱을 통해서 신청해주시기 바랍니다.
</>
)
});
return false;
} else if (schFltStDt.format('A h:mm') === 'PM 5:00') {
setIsErrorModal({
isOpen: true,
title: '비행구역 및 비행일자 중복',
desc: (
<>
설정하신 비행구역 비행시간에 이미 승인완료된 신청건이 있습니다.
<br /> 다시 설정 부탁드립니다.
</>
)
});
return false;
} else if (!detailData.fltPurpose) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '비행목적을 선택해 주세요.'
});
return false;
} else if (
!detailData.areaList[0].fltElev ||
detailData.areaList[0].fltElev === 0
) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '고도를 입력해 주세요.'
});
return false;
} else if (!detailData.areaList[0].bufferZone) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '반경을 입력해 주세요.'
});
return false;
} else if (
detailData.areaList[0].concatBufferZone !=
detailData.areaList[0].bufferZone &&
detailData.areaList[0].areaType === 'LINE'
) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: <>적용 버튼을 누르지 않고 값을 변경 없습니다.</>
});
} else if (!detailData.areaList[0].fltMethod) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '비행방식를 입력해 주세요.'
});
return false;
} else if (
detailData.areaList[0].fltMethod === '00' &&
!detailData.areaList[0].fltMothoeRm
) {
// 비행 방식 직접 입력칸 활성화 후 작성 시 조건문
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '비행방식을 입력해 주세요.'
});
return false;
} else if (validateAircraftWeightCode) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '기체 종류를 입력해 주세요.'
});
return false;
} else if (validateidntfNumCode) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '기체 신고 번호를 입력해 주세요.'
});
return false;
} else {
handlerLaanc();
}
};
// 비행 구역 적용 버튼 핸들러
const handlerBufferApply = async () => {
if (areaCoordList) {
if (areaCoordList[0].coordList.length > 0) {
dispatch(LaancAction.LAANC_VALID_AREA.request(detailData.areaList));
const array = [];
const copy = { ...areaCoordList[0] };
copy.bufferZone = detailData.areaList[0].bufferZone;
array.push(copy);
dispatch(FLIGHT_PLAN_AREA_BUFFER_LIST.request(array));
try {
const elev = await axios.post(
`api/bas/laanc/valid/elev`,
detailData.areaList
);
if (elev.data[0] === 0) {
setIsErrorModal({
title: '비행 불가 지역',
desc: (
<>
설정하신 비행구역 허용고도가 0m인 구역이 있습니다.
<br />
버퍼존을 다시 확인해주시기 바랍니다.
</>
),
isOpen: true
});
}
dispatch(LaancAction.LAANC_ALTITUDE.success(elev.data));
} catch (error) {
{
setIsErrorModal({
isOpen: true,
title: '오류',
desc: '처리중 오류가 발생하였습니다'
});
}
}
}
}
};
// Input 요소가 포커스될 때 커서를 맨 뒤로 이동 // Input 요소가 포커스될 때 커서를 맨 뒤로 이동
const handleInputClick = type => { const handleInputClick = type => {
switch (type) { switch (type) {
@ -95,7 +425,7 @@ export default function LaancStep1({
const drawFlightZone = const drawFlightZone =
fltElevRef.current && fltElevRef.current &&
type === 'fltElev' && type === 'fltElev' &&
data.areaList[0].coordList[0].lat != 0; detailData.areaList[0].coordList[0].lat != 0;
if (drawFlightZone) { if (drawFlightZone) {
const input = fltElevRef.current; const input = fltElevRef.current;
@ -103,7 +433,7 @@ export default function LaancStep1({
input.setSelectionRange(inputValue.length - 1, inputValue.length - 1); input.setSelectionRange(inputValue.length - 1, inputValue.length - 1);
input.focus(); input.focus();
} else if (data.areaList[0].coordList[0].lat === 0) { } else if (detailData.areaList[0].coordList[0].lat === 0) {
fltElevRef.current.blur(); fltElevRef.current.blur();
setIsErrorModal({ setIsErrorModal({
isOpen: true, isOpen: true,
@ -127,25 +457,26 @@ export default function LaancStep1({
// 비사업 클릭시 기존 값 초기화 작업 // 비사업 클릭시 기존 값 초기화 작업
const initialValue = () => { const initialValue = () => {
if (data.arcrftList[0].idntfNum) { if (detailData.arcrftList[0].idntfNum) {
handleChange({ handleChange({
type: 'arcrft', type: 'arcrft',
name: 'idntfNum', name: 'idntfNum',
value: '' value: ''
}); });
} }
if (data.arcrftList[0].arcrftTypeCd) { if (detailData.arcrftList[0].arcrftTypeCd) {
handleChange({ handleChange({
type: 'arcrft', type: 'arcrft',
name: 'arcrftTypeCd', name: 'arcrftTypeCd',
value: '' value: ''
}); });
} }
return;
}; };
// 날짜 선택 핸들러 // 날짜 선택 핸들러
const handleOpenFlatpickr = () => { const handleOpenFlatpickr = () => {
if (data.areaList[0].coordList[0].lat === 0) { if (detailData.areaList[0].coordList[0].lat === 0) {
setIsErrorModal({ setIsErrorModal({
isOpen: true, isOpen: true,
title: '비행 구역 설정', title: '비행 구역 설정',
@ -156,7 +487,7 @@ export default function LaancStep1({
} }
}; };
// 고도 150 미만 핸들러 // 고도, 비행방식 핸들러
const handleBlur = (value, type) => { const handleBlur = (value, type) => {
const maxElev = 150; const maxElev = 150;
@ -170,7 +501,7 @@ export default function LaancStep1({
case 'fltElev': case 'fltElev':
if ( if (
parseInt(value.replace('/^0+/', 'm', ''), 10) > maxElev && parseInt(value.replace('/^0+/', 'm', ''), 10) > maxElev &&
data.areaList[0].coordList[0].lat != 0 detailData.areaList[0].coordList[0].lat != 0
) { ) {
handleChange({ handleChange({
type: 'area', type: 'area',
@ -248,12 +579,87 @@ export default function LaancStep1({
url: 'https://drone.onestop.go.kr/introduce/systemintro3 ' url: 'https://drone.onestop.go.kr/introduce/systemintro3 '
}); });
} }
}
};
// laanc 승인 api 200 시 step 이동
const handlerLaanc = async () => {
if (laancArea && laancElev[0]) {
// laanc 필요 없이 날 수 있음
const laancNotRequired =
!laancArea.duplicated &&
detailData.fltType != 'COMMERCIAL' &&
detailData.arcrftList[0].arcrftWghtCd != '11';
const maxElev = 150;
if (laancNotRequired) {
setIsErrorModal({
isOpen: true,
title: '검토 결과 사전안내',
desc: (
<>
검토 결과 승인 대상입니다.
<p>
제줄하신 비행계획서는 별도의 승인이 필요없습니다.
<br />
조종자 준수사항에 유의하여 비행하시기 바랍니다.
</p>
</>
)
});
return;
} else if (detailData.areaList[0].fltMethod === '군집비행') {
handleChange({
type: 'area',
name: 'fltMethod',
value: ''
});
setIsLaancModal({
isOpen: true,
title: '군집 비행 목적',
desc: (
<>
군집 비행의 경우 담당자와 협의가 필요합니다. <br />
아래 링크를 통해 담당자와 협의 부탁드립니다.
</>
),
type: '처리부서안내 바로가기',
url: 'https://drone.onestop.go.kr/introduce/systemintro3 '
});
} else if (
parseInt(detailData.areaList[0].fltElev) <= laancElev[0] &&
parseInt(detailData.areaList[0].fltElev) < maxElev
) {
try {
// 성공적으로 응답 받았을 때 처리할 내용 추가
const tsData = await axios.get(
detailData.arcrftList[0].idntfNum
? `api/bas/laanc/ts/qr?idntfNum=${detailData.arcrftList[0].idntfNum}`
: `api/bas/laanc/ts/qr`
);
// dispatch(
// LaancAction.LAANC_TS_QR.success({
// confirmKey: tsData.confirmKey,
// qrcode: `data:image/png;base64,${tsData.qrcode}`
// })
// );
setQrData({
confirmKey: tsData.data.confirmKey,
qrcode: `data:image/png;base64,${tsData.data.qrcode}`
});
// handleChange({ setIsPopUp(true);
// type: 'area', return;
// name: 'bufferZone', } catch (error) {
// value: value console.log('>>', error);
// }); setIsErrorModal({
isOpen: true,
title: '오류',
desc: <>처리중 오류가 발생하였습니다</>
});
}
}
} }
}; };
@ -263,6 +669,7 @@ export default function LaancStep1({
schFltEndDtRef.current.flatpickr.close(); schFltEndDtRef.current.flatpickr.close();
}; };
// 승인 유형, 비행 시작 일자, 비행 종료 일자 아이콘 팝오버 핸들러
const toggle = type => { const toggle = type => {
switch (type) { switch (type) {
case 'commercial': case 'commercial':
@ -306,7 +713,7 @@ export default function LaancStep1({
</FormGroup> </FormGroup>
</Col> </Col>
<Col className='list-input' md='6'> <Col className='list-input' md='6'>
<FormGroup> <a>
<div className='ti'>비행 유형</div> <div className='ti'>비행 유형</div>
<Label for='test' className='pal-popover'> <Label for='test' className='pal-popover'>
<span className='necessary'>*</span> <span className='necessary'>*</span>
@ -337,7 +744,7 @@ export default function LaancStep1({
bsSize='sm' bsSize='sm'
name='fltType' name='fltType'
id='fltType' id='fltType'
value={data.fltType} value={detailData.fltType}
onChange={e => { onChange={e => {
const { name, value } = e.target; const { name, value } = e.target;
handleChange({ handleChange({
@ -351,7 +758,7 @@ export default function LaancStep1({
<option value='COMMERCIAL'>사업</option> <option value='COMMERCIAL'>사업</option>
<option value='NON_COMMERCIAL'>비사업</option> <option value='NON_COMMERCIAL'>비사업</option>
</Input> </Input>
</FormGroup> </a>
</Col> </Col>
<Col className='list-input' md='12'> <Col className='list-input' md='12'>
<div className='ti'>비행 계획 정보</div> <div className='ti'>비행 계획 정보</div>
@ -387,11 +794,13 @@ export default function LaancStep1({
id='schFltStDt' id='schFltStDt'
name='schFltStDt' name='schFltStDt'
data-enable-time data-enable-time
defaultValue={data.schFltStDt} defaultValue={detailData.schFltStDt}
value={data.schFltStDt} value={detailData.schFltStDt}
ref={schFltStDtRef} ref={schFltStDtRef}
onFocus={() => handleOpenFlatpickr()} onFocus={() => handleOpenFlatpickr()}
options={{ options={{
enableTime: true,
time_24hr: true,
minDate: moment().format('YYYY-MM-DD'), minDate: moment().format('YYYY-MM-DD'),
maxDate: moment().add(90, 'day').format('YYYY-MM-DD') maxDate: moment().add(90, 'day').format('YYYY-MM-DD')
}} }}
@ -403,21 +812,23 @@ export default function LaancStep1({
value value
}); });
if (laancSun.length > 0) { if (laancSun.length > 0) {
const filteredData = laancSun.filter(data => { const filtereddetailData = laancSun.filter(
const dataDateTime = moment( detailData => {
data.locDate, const detailDataDateTime = moment(
'YYYYMMDD' detailData.locDate,
); 'YYYYMMDD'
return dataDateTime.isSame( );
moment(value, 'YYYYMMDD') return detailDataDateTime.isSame(
); moment(value, 'YYYYMMDD')
}); );
}
);
const schFltStDt = moment(value).format('HHmmss'); const schFltStDt = moment(value).format('HHmmss');
filteredData.forEach(data => { filtereddetailData.forEach(detailData => {
if ( if (
schFltStDt <= data.civilm || schFltStDt <= detailData.civilm ||
schFltStDt >= data.civile schFltStDt >= detailData.civile
) { ) {
setIsLaancModal({ setIsLaancModal({
isOpen: true, isOpen: true,
@ -436,9 +847,9 @@ export default function LaancStep1({
handleChange({ handleChange({
name: 'schFltStDt', name: 'schFltStDt',
value: value:
schFltStDt <= data.civilm || schFltStDt <= detailData.civilm ||
schFltStDt >= data.civile schFltStDt >= detailData.civile
? moment(data.civilm, 'HHmmss') ? moment(detailData.civilm, 'HHmmss')
.add(5, 'minute') .add(5, 'minute')
.format('YYYY-MM-DD HH:mm:ss') .format('YYYY-MM-DD HH:mm:ss')
: moment() : moment()
@ -485,11 +896,13 @@ export default function LaancStep1({
id='schFltEndDt' id='schFltEndDt'
name='schFltEndDt' name='schFltEndDt'
data-enable-time data-enable-time
defaultValue={data.schFltEndDt} defaultValue={detailData.schFltEndDt}
ref={schFltEndDtRef} ref={schFltEndDtRef}
value={data.schFltEndDt} value={detailData.schFltEndDt}
onFocus={handleOpenFlatpickr} onFocus={handleOpenFlatpickr}
options={{ options={{
enableTime: true,
time_24hr: true,
minDate: moment().format('YYYY-MM-DD'), minDate: moment().format('YYYY-MM-DD'),
maxDate: moment().add(6, 'month').format('YYYY-MM-DD') maxDate: moment().add(6, 'month').format('YYYY-MM-DD')
}} }}
@ -501,21 +914,23 @@ export default function LaancStep1({
value value
}); });
if (laancSun.length > 0) { if (laancSun.length > 0) {
const filteredData = laancSun.filter(data => { const filtereddetailData = laancSun.filter(
const dataDateTime = moment( detailData => {
data.locDate, const detailDataDateTime = moment(
'YYYYMMDD' detailData.locDate,
); 'YYYYMMDD'
return dataDateTime.isSame( );
moment(value, 'YYYYMMDD') return detailDataDateTime.isSame(
); moment(value, 'YYYYMMDD')
}); );
}
);
const schFltEndDt = moment(value).format('HHmmss'); const schFltEndDt = moment(value).format('HHmmss');
filteredData.forEach(data => { filtereddetailData.forEach(detailData => {
if ( if (
schFltEndDt <= data.civilm || schFltEndDt <= detailData.civilm ||
schFltEndDt >= data.civile schFltEndDt >= detailData.civile
) { ) {
setIsLaancModal({ setIsLaancModal({
isOpen: true, isOpen: true,
@ -534,9 +949,9 @@ export default function LaancStep1({
handleChange({ handleChange({
name: 'schFltEndDt', name: 'schFltEndDt',
value: value:
schFltEndDt <= data.civilm || schFltEndDt <= detailData.civilm ||
schFltEndDt >= data.civile schFltEndDt >= detailData.civile
? moment(data.civile, 'HHmmss') ? moment(detailData.civile, 'HHmmss')
.add(-5, 'minute') .add(-5, 'minute')
.format('YYYY-MM-DD HH:mm:ss') .format('YYYY-MM-DD HH:mm:ss')
: moment() : moment()
@ -561,7 +976,7 @@ export default function LaancStep1({
type='select' type='select'
id='fltPurpose' id='fltPurpose'
name='fltPurpose' name='fltPurpose'
value={data.fltPurpose} value={detailData.fltPurpose}
bsSize='sm' bsSize='sm'
onChange={e => { onChange={e => {
const { name, value } = e.target; const { name, value } = e.target;
@ -622,8 +1037,8 @@ export default function LaancStep1({
type='text' type='text'
id='fltElev' id='fltElev'
name='fltElev' name='fltElev'
// defaultValue={data.email || ''} // defaultValue={detailData.email || ''}
value={data.areaList[0].fltElev + 'm'} value={detailData.areaList[0].fltElev + 'm'}
bsSize='sm' bsSize='sm'
onBlur={e => handleBlur(e.target.value, 'fltElev')} onBlur={e => handleBlur(e.target.value, 'fltElev')}
onChange={e => { onChange={e => {
@ -653,8 +1068,8 @@ export default function LaancStep1({
type='text' type='text'
id='bufferZone' id='bufferZone'
name='bufferZone' name='bufferZone'
// defaultValue={data.email || ''} // defaultValue={detailData.email || ''}
value={data.areaList[0].bufferZone + 'm'} value={detailData.areaList[0].bufferZone + 'm'}
bsSize='sm' bsSize='sm'
onChange={e => { onChange={e => {
const { name, value } = e.target; const { name, value } = e.target;
@ -693,7 +1108,7 @@ export default function LaancStep1({
id='fltMethod' id='fltMethod'
name='fltMethod' name='fltMethod'
onBlur={e => handleBlur(e.target.value, 'fltMethod')} onBlur={e => handleBlur(e.target.value, 'fltMethod')}
value={data.areaList[0].fltMethod} value={detailData.areaList[0].fltMethod}
bsSize='sm' bsSize='sm'
onChange={e => { onChange={e => {
const { name, value } = e.target; const { name, value } = e.target;
@ -739,10 +1154,10 @@ export default function LaancStep1({
value value
}); });
}} }}
value={data.areaList[0].fltMothoeRm} value={detailData.areaList[0].fltMothoeRm}
placeholder='직접입력 선택 후 활성화' placeholder='직접입력 선택 후 활성화'
disabled={ disabled={
data.areaList[0].fltMethod === '00' ? false : true detailData.areaList[0].fltMethod === '00' ? false : true
} }
/> />
</FormGroup> </FormGroup>
@ -763,7 +1178,7 @@ export default function LaancStep1({
name='arcrftWghtCd' name='arcrftWghtCd'
bsSize='sm' bsSize='sm'
placeholder='' placeholder=''
value={data.arcrftList[0].arcrftWghtCd} value={detailData.arcrftList[0].arcrftWghtCd}
onChange={e => { onChange={e => {
const { name, value } = e.target; const { name, value } = e.target;
handleChange({ handleChange({
@ -782,10 +1197,10 @@ export default function LaancStep1({
</Input> </Input>
</FormGroup> </FormGroup>
</Col> </Col>
{data.fltType === 'COMMERCIAL' || {detailData.fltType === 'COMMERCIAL' ||
data.arcrftList[0].arcrftWghtCd == '11' || detailData.arcrftList[0].arcrftWghtCd == '11' ||
data.arcrftList[0].arcrftWghtCd == '10' || detailData.arcrftList[0].arcrftWghtCd == '10' ||
data.arcrftList[0].arcrftWghtCd == '9' ? ( detailData.arcrftList[0].arcrftWghtCd == '9' ? (
<> <>
<Col className='list-input' md='4'> <Col className='list-input' md='4'>
<FormGroup> <FormGroup>
@ -796,7 +1211,7 @@ export default function LaancStep1({
type='select' type='select'
id='arcrftTypeCd' id='arcrftTypeCd'
name='arcrftTypeCd' name='arcrftTypeCd'
value={data.arcrftList[0].arcrftTypeCd} value={detailData.arcrftList[0].arcrftTypeCd}
bsSize='sm' bsSize='sm'
onChange={e => { onChange={e => {
const { name, value } = e.target; const { name, value } = e.target;
@ -824,7 +1239,7 @@ export default function LaancStep1({
type='text' type='text'
id='idntfNum' id='idntfNum'
name='idntfNum' name='idntfNum'
value={data.arcrftList[0].idntfNum} value={detailData.arcrftList[0].idntfNum}
bsSize='sm' bsSize='sm'
onChange={e => { onChange={e => {
const { name, value } = e.target; const { name, value } = e.target;
@ -858,7 +1273,7 @@ export default function LaancStep1({
centeredModal={centeredModal} centeredModal={centeredModal}
setCenteredModal={setCenteredModal} setCenteredModal={setCenteredModal}
handleChange={handleChange} handleChange={handleChange}
data={data} detailData={detailData}
page={1} page={1}
/> />
</div> </div>
@ -908,6 +1323,19 @@ export default function LaancStep1({
다음 다음
</Button> </Button>
</ModalFooter> </ModalFooter>
<Modal
isOpen={isPopUp}
toggle={() => setIsPopUp(!isPopUp)}
className='modal-dialog-centered modal-lg notam-modal'
style={{ height: '25vh', width: '25vw' }}
>
<LaancQr
isPopUp={isPopUp}
setIsPopUp={setIsPopUp}
data={qrData}
handlerStep={handlerStep}
/>
</Modal>
<ErrorModal modal={isErrorModal} setModal={setIsErrorModal} /> <ErrorModal modal={isErrorModal} setModal={setIsErrorModal} />
<InfoModal modal={isInfoModal} setModal={setIsInfoModal} /> <InfoModal modal={isInfoModal} setModal={setIsInfoModal} />
<LaancModal modal={isLaancModal} setModal={setIsLaancModal} /> <LaancModal modal={isLaancModal} setModal={setIsLaancModal} />

5
src/components/laanc/step/LaancStep2.js

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import '@styles/react/libs/flatpickr/flatpickr.scss'; import '@styles/react/libs/flatpickr/flatpickr.scss';
import { AlertCircle, CheckCircle } from 'react-feather'; import { CheckCircle } from 'react-feather';
import FlightArea from '../map/FlightArea'; import FlightArea from '../map/FlightArea';
import { import {
Row, Row,
@ -48,6 +48,7 @@ export default function LaancStep2({
const { laancPdf } = useSelector(state => state.laancState); const { laancPdf } = useSelector(state => state.laancState);
const dispatch = useDispatch(); const dispatch = useDispatch();
// 약관 동의 데이터
useEffect(() => { useEffect(() => {
dispatch( dispatch(
TermsActions.termsList.request({ TermsActions.termsList.request({
@ -58,12 +59,14 @@ export default function LaancStep2({
); );
}, []); }, []);
// 비행 승인 요청
useEffect(() => { useEffect(() => {
if (flightData && Object.keys(flightData).length > 0) { if (flightData && Object.keys(flightData).length > 0) {
dispatch(LaancAction.LAANC_FLIGHT_CREATE.request(flightData)); dispatch(LaancAction.LAANC_FLIGHT_CREATE.request(flightData));
} }
}, [flightData]); }, [flightData]);
// 비행 승인 요청 성공
useEffect(() => { useEffect(() => {
if (laancPdf && flightData && Object.keys(flightData).length > 0) { if (laancPdf && flightData && Object.keys(flightData).length > 0) {
handlerStep(3); handlerStep(3);

15
src/containers/laanc/LaancContainer.js

@ -17,18 +17,27 @@ export default function LaancContainer() {
const [currentParm, setCurrentParm] = useState(false); const [currentParm, setCurrentParm] = useState(false);
const [disabledAnimation, setDisabledAnimation] = useState(false); const [disabledAnimation, setDisabledAnimation] = useState(false);
const [isSearch, setIsSearch] = useState(false);
const queryParams = new URLSearchParams(location.search); const queryParams = new URLSearchParams(location.search);
const mapParam = queryParams.get('map'); const mapParam = queryParams.get('map');
// URL 쿼리 파라미터 중 'map' 값을 가져옵니다.
useEffect(() => { useEffect(() => {
// URL 쿼리 파라미터 중 'map' 값을 가져옵니다. // Redux Store 초기화
dispatch(drawTypeChangeAction('')); dispatch(drawTypeChangeAction(''));
dispatch(LaancAction.LAANC_APPROVAL_INIT()); dispatch(LaancAction.LAANC_APPROVAL_INIT());
dispatch(AreaAction.AREA_DETAIL_INIT()); dispatch(AreaAction.AREA_DETAIL_INIT());
setDisabledAnimation(mapParam != 'true' ? false : true); setDisabledAnimation(mapParam != 'true' ? false : true);
}, [location]); }, [location]);
// Laanc 신청 이후 자동 검색
useEffect(() => {
if (disabledAnimation) {
setIsSearch(false);
} else setIsSearch(true);
}, [disabledAnimation]);
// LAANC 신청하기 버튼 클릭 헨들러
const handleApply = () => { const handleApply = () => {
dispatch(drawTypeChangeAction('')); dispatch(drawTypeChangeAction(''));
dispatch(LaancAction.LAANC_APPROVAL_INIT()); dispatch(LaancAction.LAANC_APPROVAL_INIT());
@ -64,7 +73,7 @@ export default function LaancContainer() {
) : null} ) : null}
</div> </div>
<LaancSearch /> <LaancSearch isSearch={isSearch} />
<LaancGrid /> <LaancGrid />
</CustomMainLayout> </CustomMainLayout>
); );

447
src/containers/laanc/LaancPlanContainer.js

@ -2,20 +2,13 @@ import { useEffect, useState } from 'react';
import LaancStep1 from '../../components/laanc/step/LaancStep1'; // laanc step 1 import LaancStep1 from '../../components/laanc/step/LaancStep1'; // laanc step 1
import LaancStep2 from '../../components/laanc/step/LaancStep2'; // laanc step 2 import LaancStep2 from '../../components/laanc/step/LaancStep2'; // laanc step 2
import LaancStep3 from '../../components/laanc/step/LaacnStep3'; // laanc step 3 import LaancStep3 from '../../components/laanc/step/LaacnStep3'; // laanc step 3
import moment from 'moment';
import { ErrorModal } from '../../components/modal/ErrorModal'; import { ErrorModal } from '../../components/modal/ErrorModal';
import { LaancModal } from '../../components/laanc/LaancModal'; import { LaancModal } from '../../components/laanc/LaancModal';
import { initFlightBas } from '../../modules/laanc/models/laancModels'; import { initFlightBas } from '../../modules/laanc/models/laancModels';
import { Modal } from 'reactstrap'; import { Modal } from 'reactstrap';
import { import { AREA_DETAIL_INIT } from '../../modules/basis/flight/actions/basisFlightAction';
AREA_DETAIL_INIT,
FLIGHT_PLAN_AREA_BUFFER_LIST
} from '../../modules/basis/flight/actions/basisFlightAction';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { drawTypeChangeAction } from '../../modules/control/map/actions/controlMapActions'; import { drawTypeChangeAction } from '../../modules/control/map/actions/controlMapActions';
import * as LaancAction from '../../modules/laanc/actions/laancActions';
import * as AreaAction from '../../modules/basis/flight/actions/basisFlightAction';
import axios from '../../modules/utils/customAxiosUtil';
export default function LaancPlanContainer({ export default function LaancPlanContainer({
currentParm, currentParm,
@ -24,9 +17,7 @@ export default function LaancPlanContainer({
}) { }) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { areaCoordList } = useSelector(state => state.flightState);
const { user } = useSelector(state => state.authState); const { user } = useSelector(state => state.authState);
const { laancArea, laancElev } = useSelector(state => state.laancState);
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
const [detailData, setDetailData] = useState(initFlightBas.initDetail); const [detailData, setDetailData] = useState(initFlightBas.initDetail);
@ -44,6 +35,7 @@ export default function LaancPlanContainer({
url: '' url: ''
}); });
// 로그인 회원 정보 세팅
useEffect(() => { useEffect(() => {
if (user) { if (user) {
setDetailData({ setDetailData({
@ -57,434 +49,12 @@ export default function LaancPlanContainer({
}; };
}, []); }, []);
// 적용 버튼 Reducer 업데이트 될때마다 검사 로직
useEffect(() => {
if (detailData.areaList[0].fltElev != 0) {
const maxElev = 150;
const controlledAltitudeExceededWarning =
laancArea?.duplicated &&
parseInt(
detailData.areaList[0].fltElev.replace('/^0+/', 'm', ''),
10
) >= laancElev[0] &&
parseInt(detailData.areaList[0].fltElev.replace('/^0+/', 'm', ''), 10) <
maxElev;
if (controlledAltitudeExceededWarning) {
setIsErrorModal({
isOpen: true,
title: '검토 결과 사전안내',
desc: (
<>
유효성 검사에 실패하여 승인 대상입니다.
<br />
제출하신 비행계획서의 고도는 {laancElev[0]}m이하에서만 비행이
가능합니다.
<br />
고도 설정을 다시 확인해주시기 바랍니다.
</>
)
});
handleChange({
type: 'area',
name: 'fltElev',
value: 0
});
}
}
}, [[laancElev]]);
// laanc 승인 api 200 시 step 이동
const handlerLaanc = async () => {
if (laancArea && laancElev[0]) {
// laanc 필요 없이 날 수 있음
const laancNotRequired =
!laancArea.duplicated &&
detailData.fltType != 'COMMERCIAL' &&
detailData.arcrftList[0].arcrftWghtCd != '11';
const maxElev = 150;
if (laancNotRequired) {
setIsErrorModal({
isOpen: true,
title: '검토 결과 사전안내',
desc: (
<>
검토 결과 승인 대상입니다.
<p>
제줄하신 비행계획서는 별도의 승인이 필요없습니다.
<br />
조종자 준수사항에 유의하여 비행하시기 바랍니다.
</p>
</>
)
});
return;
} else if (detailData.areaList[0].fltMethod === '군집비행') {
handleChange({
type: 'area',
name: 'fltMethod',
value: ''
});
setIsLaancModal({
isOpen: true,
title: '군집 비행 목적',
desc: (
<>
군집 비행의 경우 담당자와 협의가 필요합니다. <br />
아래 링크를 통해 담당자와 협의 부탁드립니다.
</>
),
type: '처리부서안내 바로가기',
url: 'https://drone.onestop.go.kr/introduce/systemintro3 '
});
} else if (
parseInt(detailData.areaList[0].fltElev) <= laancElev[0] &&
parseInt(detailData.areaList[0].fltElev) < maxElev
) {
try {
// 성공적으로 응답 받았을 때 처리할 내용 추가
const tsData = await axios.post(`api/bas/laanc/valid/ts/pilot`, [
detailData.arcrftList[0].idntfNum
]);
if (!tsData.data.valid) {
setIsErrorModal({
isOpen: true,
title: '검토 결과 사전안내',
desc: (
<>
유효성 검사에 실패하여 승인 대상입니다.
<p>
기체가 보험에 가입되어 있지 않거나 유효기간이
만료되었습니다.
<br />
기체 번호를 다시 확인해주시기 바랍니다.
</p>
</>
)
});
return;
} else {
setStep(2);
}
} catch (error) {
setIsErrorModal({
isOpen: true,
title: '오류',
desc: <>처리중 오류가 발생하였습니다</>
});
}
}
}
};
// step 핸들러 // step 핸들러
const handlerStep = step => { const handlerStep = step => {
setStep(step); setStep(step);
}; };
// 날씨 핸들러 // Laanc 승인 요청 취소 버튼 헨들러
const handlerWeather = () => {
setFormModal(!formModal);
};
// 비행계획서 작성 핸들러
const handleChange = ({ name, value, type, index, pIndex }) => {
const arrName = `${type}List`;
switch (type) {
case 'coord':
setDetailData(prevState => {
return {
...prevState,
areaList: [
{
...prevState.areaList[0],
coordList: value
}
]
};
});
break;
case 'area':
if (name === 'fltMethod' && value != '직접입력') {
setDetailData(prevState => {
const arr = [...prevState[arrName]];
const updateData = {
...prevState[arrName][0],
[name]: value,
fltMothoeRm: ''
};
arr[0] = updateData;
return {
...prevState,
[arrName]: arr
};
});
} else if (
detailData.areaList[0].areaType === 'LINE' ||
name === 'bufferZone'
) {
setDetailData(prevState => {
const arr = [...prevState[arrName]];
const prevBufferZone = prevState[arrName][0].bufferZone;
const updateData = {
...prevState[arrName][0],
[name]: value,
concatBufferZone: prevBufferZone
};
arr[0] = updateData;
return {
...prevState,
[arrName]: arr
};
});
} else {
setDetailData(prevState => {
const arr = [...prevState[arrName]];
const updateData = {
...prevState[arrName][0],
[name]: value
};
arr[0] = updateData;
return {
...prevState,
[arrName]: arr
};
});
}
break;
case 'pilot':
case 'arcrft':
{
setDetailData(prevState => {
const arr = [...prevState[arrName]];
const updateData = {
...prevState[arrName][0],
[name]: value
};
arr[0] = updateData;
return {
...prevState,
[arrName]: arr
};
});
}
break;
case 'plan':
default:
setDetailData(prevState => ({
...prevState,
[name]: value
}));
break;
}
};
// 스텝 1 다음 버튼 이벤트
const handlerNext = () => {
// 시작일자
const schFltStDt = moment(detailData.schFltStDt, 'YYYY-MM-DD HH:mm:ss');
// 종료일자
const schFltEndDt = moment(detailData.schFltEndDt, 'YYYY-MM-DD HH:mm:ss');
const currentDate = moment(); // 현재 날짜와 시간을 가져옵니다.
const validateAircraftWeightCode =
!detailData.arcrftList[0].arcrftTypeCd &&
(detailData.commercial === 'COMMERCIAL' ||
detailData.arcrftList[0].arcrftWghtCd == '9' ||
detailData.arcrftList[0].arcrftWghtCd == '10' ||
detailData.arcrftList[0].arcrftWghtCd == '11');
const validateidntfNumCode =
!detailData.arcrftList[0].idntfNum &&
(detailData.commercial === 'COMMERCIAL' ||
detailData.arcrftList[0].arcrftWghtCd == '9' ||
detailData.arcrftList[0].arcrftWghtCd == '10' ||
detailData.arcrftList[0].arcrftWghtCd == '11');
if (!detailData.fltType) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '비행 종류(상업/비상업)를 선택해주세요.'
});
return false;
} else if (
!schFltStDt.isAfter(currentDate) ||
!schFltEndDt.isAfter(currentDate)
) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '비행 일자가 이미 지난 일자입니다.'
});
return false;
} else if (schFltStDt.isAfter(schFltEndDt)) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '비행일자를 확인해주세요.'
});
return false;
} else if (schFltStDt.format('A h:mm') === 'PM 11:00') {
setIsErrorModal({
isOpen: true,
title: '특별 비행',
desc: (
<>
야간 비행은 특별 비행에 해당됩니다.
<br />
특별 비행의 경우 드론원스톱을 통해서 신청해주시기 바랍니다.
</>
)
});
return false;
} else if (schFltStDt.format('A h:mm') === 'PM 5:00') {
setIsErrorModal({
isOpen: true,
title: '비행구역 및 비행일자 중복',
desc: (
<>
설정하신 비행구역 비행시간에 이미 승인완료된 신청건이 있습니다.
<br /> 다시 설정 부탁드립니다.
</>
)
});
return false;
} else if (!detailData.fltPurpose) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '비행목적을 선택해 주세요.'
});
return false;
} else if (
!detailData.areaList[0].fltElev ||
detailData.areaList[0].fltElev === 0
) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '고도를 입력해 주세요.'
});
return false;
} else if (!detailData.areaList[0].bufferZone) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '반경을 입력해 주세요.'
});
return false;
} else if (
detailData.areaList[0].concatBufferZone !=
detailData.areaList[0].bufferZone &&
detailData.areaList[0].areaType === 'LINE'
) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: <>적용 버튼을 누르지 않고 값을 변경 없습니다.</>
});
// handleChange({
// type: 'area',
// name: 'bufferZone',
// value: detailData.areaList[0].concatBufferZone
// });
} else if (!detailData.areaList[0].fltMethod) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '비행방식를 입력해 주세요.'
});
return false;
} else if (
detailData.areaList[0].fltMethod === '00' &&
!detailData.areaList[0].fltMothoeRm
) {
// 비행 방식 직접 입력칸 활성화 후 작성 시 조건문
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '비행방식을 입력해 주세요.'
});
return false;
} else if (validateAircraftWeightCode) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '기체 종류를 입력해 주세요.'
});
return false;
} else if (validateidntfNumCode) {
setIsErrorModal({
isOpen: true,
title: '필수값 입력 오류',
desc: '기체 신고 번호를 입력해 주세요.'
});
return false;
} else {
handlerLaanc();
}
};
const handlerBufferApply = async () => {
if (areaCoordList) {
if (areaCoordList[0].coordList.length > 0) {
// dispatch(LaancAction.LAANC_ALTITUDE.request(detailData.areaList));
dispatch(LaancAction.LAANC_VALID_AREA.request(detailData.areaList));
const array = [];
const copy = { ...areaCoordList[0] };
copy.bufferZone = detailData.areaList[0].bufferZone;
array.push(copy);
dispatch(FLIGHT_PLAN_AREA_BUFFER_LIST.request(array));
try {
const elev = await axios.post(
`api/bas/laanc/valid/elev`,
detailData.areaList
);
if (elev.data[0] === 0) {
// dispatch(AREA_DETAIL_INIT());
// dispatch(AreaAction.AREA_DETAIL_INIT());
// dispatch(drawTypeChangeAction(''));
// dispatch(LaancAction.LAANC_APPROVAL_INIT());
setIsErrorModal({
title: '비행 불가 지역',
desc: (
<>
설정하신 비행구역 허용고도가 0m인 구역이 있습니다.
<br />
버퍼존을 다시 확인해주시기 바랍니다.
</>
),
isOpen: true
});
}
dispatch(LaancAction.LAANC_ALTITUDE.success(elev.data));
} catch (error) {
{
setIsErrorModal({
isOpen: true,
title: '오류',
desc: '처리중 오류가 발생하였습니다'
});
}
}
}
}
};
const handlerLaancClose = () => { const handlerLaancClose = () => {
setStep(1); setStep(1);
setDisabledAnimation(!disabledAnimation); setDisabledAnimation(!disabledAnimation);
@ -504,17 +74,13 @@ export default function LaancPlanContainer({
{step === 1 && ( {step === 1 && (
<> <>
<LaancStep1 <LaancStep1
data={detailData} detailData={detailData}
handleChange={handleChange} setDetailData={setDetailData}
handlerNext={handlerNext}
handlerWeather={handlerWeather}
setDisabledAnimation={setDisabledAnimation}
disabledAnimation={disabledAnimation}
centeredModal={centeredModal} centeredModal={centeredModal}
setCenteredModal={setCenteredModal} setCenteredModal={setCenteredModal}
currentParm={currentParm} currentParm={currentParm}
handlerStep={handlerStep}
handlerLaancClose={handlerLaancClose} handlerLaancClose={handlerLaancClose}
handlerBufferApply={handlerBufferApply}
/> />
</> </>
)} )}
@ -541,6 +107,7 @@ export default function LaancPlanContainer({
/> />
)} )}
</Modal> </Modal>
<ErrorModal modal={isErrorModal} setModal={setIsErrorModal} /> <ErrorModal modal={isErrorModal} setModal={setIsErrorModal} />
<LaancModal modal={isLaancModal} setModal={setIsLaancModal} /> <LaancModal modal={isLaancModal} setModal={setIsLaancModal} />
</div> </div>

17
src/modules/laanc/actions/laancActions.ts

@ -14,7 +14,8 @@ import {
FlightPlanAreaData, FlightPlanAreaData,
VaildElevData, VaildElevData,
VaildAreaData, VaildAreaData,
LaancTsData LaancTsData,
LaancTsQrData
} from '../models/laancModels'; } from '../models/laancModels';
// laanc 비행계획서 승인 // laanc 비행계획서 승인
@ -57,11 +58,17 @@ const LAANC_VALID_TS_REQUEST = 'laanc/valid/ts/REQUEST';
const LAANC_VALID_TS_SUCCESS = 'laanc/valid/ts/SUCCESS'; const LAANC_VALID_TS_SUCCESS = 'laanc/valid/ts/SUCCESS';
const LAANC_VALID_TS_FAILURE = 'laanc/valid/ts/FAILURE'; const LAANC_VALID_TS_FAILURE = 'laanc/valid/ts/FAILURE';
// laanc ts qr
const LAANC_TS_QR_REQUEST = 'laanc/ts/qr/REQUEST';
const LAANC_TS_QR_SUCCESS = 'laanc/ts/qr/SUCCESS';
const LAANC_TS_QR_FAILURE = 'laanc/ts/qr/FAILURE';
// laanc 초기화 // laanc 초기화
const INIT_LAANC = 'laanc/init'; const INIT_LAANC = 'laanc/init';
// laanc approval detail 초기화 // laanc approval detail 초기화
const INIT_APPROVAL_DETAIL = 'laanc/init/approval/detail'; const INIT_APPROVAL_DETAIL = 'laanc/init/approval/detail';
// 허뎓 고도 초기화 // 허뎓 고도 초기화
// const INIT_ALTITUDE = 'laanc/init/altitude'; // const INIT_ALTITUDE = 'laanc/init/altitude';
@ -142,6 +149,13 @@ export const LAANC_VALID_TS = createAsyncAction(
LAANC_VALID_TS_FAILURE LAANC_VALID_TS_FAILURE
)<string, LaancTsData, AxiosError>(); )<string, LaancTsData, AxiosError>();
// laanc ts qr
export const LAANC_TS_QR = createAsyncAction(
LAANC_TS_QR_REQUEST,
LAANC_TS_QR_SUCCESS,
LAANC_TS_QR_FAILURE
)<string, LaancTsQrData, AxiosError>();
const actions = { const actions = {
LAANC_FLIGHT_Approval, LAANC_FLIGHT_Approval,
LAANC_FLIGHT_CREATE, LAANC_FLIGHT_CREATE,
@ -152,6 +166,7 @@ const actions = {
LAANC_ALTITUDE, LAANC_ALTITUDE,
LAANC_VALID_AREA, LAANC_VALID_AREA,
LAANC_VALID_TS, LAANC_VALID_TS,
LAANC_TS_QR,
LAANC_APPROVAL_DETAIL_INIT LAANC_APPROVAL_DETAIL_INIT
}; };
export type LaancAction = ActionType<typeof actions>; export type LaancAction = ActionType<typeof actions>;

5
src/modules/laanc/apis/laancApi.ts

@ -54,5 +54,10 @@ export const laancApi = {
postValidTs: async (data: string) => { postValidTs: async (data: string) => {
const res = await axios.post(`api/bas/laanc/valid/ts/pilot/${data}`); const res = await axios.post(`api/bas/laanc/valid/ts/pilot/${data}`);
return res; return res;
},
// laanc ts qr
getTsQr: async (data: string) => {
const res = await axios.get(`api/bas/laanc/ts/qr/${data}`);
return res.data;
} }
}; };

11
src/modules/laanc/models/laancModels.ts

@ -10,6 +10,7 @@ export interface laancState {
laancElev: number[] | undefined; laancElev: number[] | undefined;
laancArea: VaildAreaData | undefined; laancArea: VaildAreaData | undefined;
laancTs: LaancTsData | undefined; laancTs: LaancTsData | undefined;
laancQrData: LaancTsQrData | undefined;
} }
// laanc계획서 초기값 // laanc계획서 초기값
@ -377,6 +378,7 @@ export const laancControlData = {
laancElev: undefined, laancElev: undefined,
laancArea: undefined, laancArea: undefined,
laancTs: undefined, laancTs: undefined,
laancQrData: undefined,
detail: { detail: {
planSno: 0, planSno: 0,
groupId: '', groupId: '',
@ -817,3 +819,12 @@ export interface LaancTsData {
]; ];
valid: boolean; valid: boolean;
} }
// laanc Ts QR
export interface LaancTsQrData {
rspCode: string;
rspMessage: string;
arcrftinsuranceyn: string;
arcrftdeclaration: string;
corpregyn: string;
}

6
src/modules/laanc/reducers/laancReducers.ts

@ -83,6 +83,12 @@ export const laancReducer = createReducer<laancState, Actions.LaancAction>(
const data = action.payload; const data = action.payload;
draft.laancTs = data; draft.laancTs = data;
}) })
)
.handleAction(Actions.LAANC_TS_QR.success, (state, action) =>
produce(state, draft => {
const data = action.payload;
draft.laancQrData = data;
})
); );
// .handleAction(Actions.LAANC_ALTITUDE_INIT, (state, action) => // .handleAction(Actions.LAANC_ALTITUDE_INIT, (state, action) =>
// produce(state, draft => { // produce(state, draft => {

17
src/modules/laanc/sagas/laancSagas.ts

@ -171,6 +171,23 @@ function* postValidTsSaga(
); );
} }
} }
// laanc ts qr
function* getTsQrSaga(action: ActionType<typeof Actions.LAANC_TS_QR.request>) {
try {
const detail = action.payload;
const res = yield call(Apis.laancApi.getTsQr, detail);
// yield put(Actions.LAANC_TS_QR.success(res.data));
} catch (error) {
yield put(
MessageActions.IS_ERROR({
errorCode: ERROR_MESSAGE.code,
errorMessage: ERROR_MESSAGE.message,
isHistoryBack: false,
isRefresh: false
})
);
}
}
export function* laancSaga() { export function* laancSaga() {
yield takeEvery(Actions.LAANC_FLIGHT_Approval.request, postApprovalSaga); yield takeEvery(Actions.LAANC_FLIGHT_Approval.request, postApprovalSaga);
yield debounce(500, Actions.LAANC_FLIGHT_CREATE.request, postCreateSaga); yield debounce(500, Actions.LAANC_FLIGHT_CREATE.request, postCreateSaga);

Loading…
Cancel
Save