From 1855087fed42d2619def625164774e13e3c172b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=83=81=ED=98=84?= Date: Thu, 7 Dec 2023 11:14:24 +0900 Subject: [PATCH 1/2] =?UTF-8?q?QR=20CODE=20=EC=9E=84=EC=8B=9C=20=EB=B8=8C?= =?UTF-8?q?=EB=9F=B0=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/laanc/LaancQr.js | 127 +++++ src/components/laanc/list/LaancDetail.js | 5 +- src/components/laanc/list/LaancGrid.js | 6 +- src/components/laanc/list/LaancSearch.js | 12 +- src/components/laanc/step/LaacnStep3.js | 5 +- src/components/laanc/step/LaancStep1.js | 572 +++++++++++++++++--- src/components/laanc/step/LaancStep2.js | 5 +- src/containers/laanc/LaancContainer.js | 15 +- src/containers/laanc/LaancPlanContainer.js | 447 +-------------- src/modules/laanc/actions/laancActions.ts | 17 +- src/modules/laanc/apis/laancApi.ts | 5 + src/modules/laanc/models/laancModels.ts | 11 + src/modules/laanc/reducers/laancReducers.ts | 6 + src/modules/laanc/sagas/laancSagas.ts | 17 + 14 files changed, 726 insertions(+), 524 deletions(-) create mode 100644 src/components/laanc/LaancQr.js diff --git a/src/components/laanc/LaancQr.js b/src/components/laanc/LaancQr.js new file mode 100644 index 00000000..74af9769 --- /dev/null +++ b/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 ( + <> + { + setIsPopUp(!isPopUp); + }} + > + QR인증 + + + QR Code + + + + + + + ); +} diff --git a/src/components/laanc/list/LaancDetail.js b/src/components/laanc/list/LaancDetail.js index c933e7d9..904de477 100644 --- a/src/components/laanc/list/LaancDetail.js +++ b/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 { Row, @@ -7,7 +7,6 @@ import { ModalHeader, ModalBody, ModalFooter, - Alert, FormGroup, Label, Input @@ -33,6 +32,8 @@ export default function LaancDetail({ data, handlerLaancClose }) { }; const { user } = useSelector(state => state.authState); const { termsList } = useSelector(state => state.accountState); + + // Laanc 약관 동의 useEffect(() => { dispatch( TermsActions.termsList.request({ diff --git a/src/components/laanc/list/LaancGrid.js b/src/components/laanc/list/LaancGrid.js index 0f3bfd93..65f46986 100644 --- a/src/components/laanc/list/LaancGrid.js +++ b/src/components/laanc/list/LaancGrid.js @@ -1,10 +1,9 @@ import { useEffect, useState } from 'react'; 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 { Row, Col, Card, Button, Spinner, Modal } from 'reactstrap'; import * as LaancAction from '../../../modules/laanc/actions/laancActions'; -import LaancStep2 from '../step/LaancStep2'; import moment from 'moment'; import { AREA_COORDINATE_LIST_SAVE, @@ -26,10 +25,12 @@ export default function LaancGrid() { ); const { loading } = useSelector(state => state.loadingReducer); + // Laanc 승인 신청 목록 조회 useEffect(() => { return () => dispatch(LaancAction.LAANC_APPROVAL_DETAIL_INIT()); }, []); + // Laanc 승인 신청 목록 비행 구역 조회 useEffect(() => { if (laancDetail) { dispatch(FLIGHT_PLAN_AREA_BUFFER_LIST.request(laancDetail?.areaList)); @@ -43,6 +44,7 @@ export default function LaancGrid() { dispatch(LaancAction.LAANC_DETAIL.request(planSno)); }; + // Laanc 승인 신청 목록 닫기 const handlerLaancClose = () => { dispatch(drawTypeChangeAction('')); dispatch(AREA_DETAIL_INIT()); diff --git a/src/components/laanc/list/LaancSearch.js b/src/components/laanc/list/LaancSearch.js index 80a4e3ca..649281e9 100644 --- a/src/components/laanc/list/LaancSearch.js +++ b/src/components/laanc/list/LaancSearch.js @@ -6,7 +6,7 @@ import Flatpickr from 'react-flatpickr'; import moment from 'moment'; import * as LaancAction from '../../../modules/laanc/actions/laancActions'; -function LaancSearch() { +function LaancSearch({ isSearch }) { const dispatch = useDispatch(); const [date, setDate] = useState({ @@ -14,10 +14,19 @@ function LaancSearch() { createEndDate: moment().subtract(-7, 'day').format('YYYY-MM-DD') }); + // Laanc 승인 신청 목록 조회 useEffect(() => { dispatch(LaancAction.LAANC_APRV_LIST.request({ ...date })); }, []); + // Laanc 승인 신청시 검색 + useEffect(() => { + if (isSearch) { + dispatch(LaancAction.LAANC_APRV_LIST.request({ ...date })); + } + }, [isSearch]); + + // 날짜 선택 헨들러 const handlerChangeDate = selectedDates => { if (selectedDates.length === 2) { const createStDate = moment(selectedDates[0]).format('YYYY-MM-DD'); @@ -26,6 +35,7 @@ function LaancSearch() { } }; + // 검색 헨들러 const handlerClick = () => { dispatch(LaancAction.LAANC_APRV_LIST.request({ ...date })); }; diff --git a/src/components/laanc/step/LaacnStep3.js b/src/components/laanc/step/LaacnStep3.js index a08aee6a..96c0ca8a 100644 --- a/src/components/laanc/step/LaacnStep3.js +++ b/src/components/laanc/step/LaacnStep3.js @@ -40,12 +40,14 @@ export default function LaacnStep3({ 11: '25kg초과' } }; + const [centeredModal2, setCenteredModal2] = useState(false); const [formModal, setFormModal] = useState(false); const [numPages, setNumPages] = useState(null); // total const { user } = useSelector(state => state.authState); const { laancPdf } = useSelector(state => state.laancState); + // PDF 다운로드 const handlerPdfDownload = e => { if (laancPdf.pdfUrl) { let alink = document.createElement('a'); @@ -55,6 +57,7 @@ export default function LaacnStep3({ } }; + // PDF 페이지 로드 const onDocumentLoadSuccess = ({ numPages: nextNumPages }) => { setNumPages(nextNumPages); }; @@ -238,7 +241,6 @@ export default function LaacnStep3({ - {/* outline onClick={() => setCenteredModal2(!centeredModal2)} */} @@ -261,7 +263,6 @@ export default function LaacnStep3({ ))} - {/* */} diff --git a/src/components/laanc/step/LaancStep1.js b/src/components/laanc/step/LaancStep1.js index 73d23540..a3c89214 100644 --- a/src/components/laanc/step/LaancStep1.js +++ b/src/components/laanc/step/LaancStep1.js @@ -9,6 +9,9 @@ import FlightArea from '../map/FlightArea'; import { ErrorModal } from '../../modal/ErrorModal'; import { InfoModal } from '../../modal/InfoModal'; 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 { Row, @@ -21,18 +24,18 @@ import { UncontrolledPopover, PopoverBody, Label, - Input + Input, + Modal } from 'reactstrap'; export default function LaancStep1({ - handleChange, - handlerNext, - data, + detailData, + setDetailData, centeredModal, setCenteredModal, + handlerStep, currentParm, - handlerLaancClose, - handlerBufferApply + handlerLaancClose }) { const dispatch = useDispatch(); @@ -49,6 +52,8 @@ export default function LaancStep1({ const queryParams = new URLSearchParams(location.search); const mapParam = queryParams.get('map'); + const [qrData, setQrData] = useState(); // qr 인증 데이터 + const [isPopUp, setIsPopUp] = useState(false); const [popoverCommercial, setPopoverCommercial] = useState(false); const [popoverSchFltStDt, setPopoverSchFltStDt] = useState(false); const [popoverSchFltEndDt, setPopoverSchFltEndDt] = useState(false); @@ -70,11 +75,12 @@ export default function LaancStep1({ url: '' }); + // URL 쿼리 파라미터 중 'map' 값을 가져옵니다. useEffect(() => { - // URL 쿼리 파라미터 중 'map' 값을 가져옵니다. if (!currentParm) setCenteredModal(mapParam != 'true' ? false : true); }, [location]); + // 일물 일출 데이터 호출 useEffect(() => { if (areaCoordList) { if (areaCoordList[0].coordList[0].lat !== 0) { @@ -88,6 +94,330 @@ export default function LaancStep1({ } }, [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: ( + <> + 유효성 검사에 실패하여 미 승인 대상입니다. +
+ 제출하신 비행계획서의 고도는 {laancElev[0]}m이하에서만 비행이 + 가능합니다. +
+ 고도 설정을 다시 확인해주시기 바랍니다. + + ) + }); + 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: ( + <> + 야간 비행은 특별 비행에 해당됩니다. +
+ 특별 비행의 경우 드론원스톱을 통해서 신청해주시기 바랍니다. + + ) + }); + return false; + } else if (schFltStDt.format('A h:mm') === 'PM 5:00') { + setIsErrorModal({ + isOpen: true, + title: '비행구역 및 비행일자 중복', + desc: ( + <> + 설정하신 비행구역 및 비행시간에 이미 승인완료된 신청건이 있습니다. +
다시 설정 부탁드립니다. + + ) + }); + 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인 구역이 있습니다. +
+ 버퍼존을 다시 확인해주시기 바랍니다. + + ), + isOpen: true + }); + } + dispatch(LaancAction.LAANC_ALTITUDE.success(elev.data)); + } catch (error) { + { + setIsErrorModal({ + isOpen: true, + title: '오류', + desc: '처리중 오류가 발생하였습니다' + }); + } + } + } + } + }; + // Input 요소가 포커스될 때 커서를 맨 뒤로 이동 const handleInputClick = type => { switch (type) { @@ -95,7 +425,7 @@ export default function LaancStep1({ const drawFlightZone = fltElevRef.current && type === 'fltElev' && - data.areaList[0].coordList[0].lat != 0; + detailData.areaList[0].coordList[0].lat != 0; if (drawFlightZone) { const input = fltElevRef.current; @@ -103,7 +433,7 @@ export default function LaancStep1({ input.setSelectionRange(inputValue.length - 1, inputValue.length - 1); input.focus(); - } else if (data.areaList[0].coordList[0].lat === 0) { + } else if (detailData.areaList[0].coordList[0].lat === 0) { fltElevRef.current.blur(); setIsErrorModal({ isOpen: true, @@ -127,25 +457,26 @@ export default function LaancStep1({ // 비사업 클릭시 기존 값 초기화 작업 const initialValue = () => { - if (data.arcrftList[0].idntfNum) { + if (detailData.arcrftList[0].idntfNum) { handleChange({ type: 'arcrft', name: 'idntfNum', value: '' }); } - if (data.arcrftList[0].arcrftTypeCd) { + if (detailData.arcrftList[0].arcrftTypeCd) { handleChange({ type: 'arcrft', name: 'arcrftTypeCd', value: '' }); } + return; }; // 날짜 선택 핸들러 const handleOpenFlatpickr = () => { - if (data.areaList[0].coordList[0].lat === 0) { + if (detailData.areaList[0].coordList[0].lat === 0) { setIsErrorModal({ isOpen: true, title: '비행 구역 설정', @@ -156,7 +487,7 @@ export default function LaancStep1({ } }; - // 고도 150 미만 핸들러 + // 고도, 비행방식 핸들러 const handleBlur = (value, type) => { const maxElev = 150; @@ -170,7 +501,7 @@ export default function LaancStep1({ case 'fltElev': if ( parseInt(value.replace('/^0+/', 'm', ''), 10) > maxElev && - data.areaList[0].coordList[0].lat != 0 + detailData.areaList[0].coordList[0].lat != 0 ) { handleChange({ type: 'area', @@ -248,12 +579,87 @@ export default function LaancStep1({ 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: ( + <> + 검토 결과 미 승인 대상입니다. +

+ 제줄하신 비행계획서는 별도의 승인이 필요없습니다. +
+ 조종자 준수사항에 유의하여 비행하시기 바랍니다. +

+ + ) + }); + return; + } else if (detailData.areaList[0].fltMethod === '군집비행') { + handleChange({ + type: 'area', + name: 'fltMethod', + value: '' + }); + setIsLaancModal({ + isOpen: true, + title: '군집 비행 목적', + desc: ( + <> + 군집 비행의 경우 담당자와 협의가 필요합니다.
+ 아래 링크를 통해 담당자와 협의 부탁드립니다. + + ), + 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({ - // type: 'area', - // name: 'bufferZone', - // value: value - // }); + setIsPopUp(true); + return; + } catch (error) { + console.log('>>', error); + setIsErrorModal({ + isOpen: true, + title: '오류', + desc: <>처리중 오류가 발생하였습니다 + }); + } + } } }; @@ -263,6 +669,7 @@ export default function LaancStep1({ schFltEndDtRef.current.flatpickr.close(); }; + // 승인 유형, 비행 시작 일자, 비행 종료 일자 아이콘 팝오버 핸들러 const toggle = type => { switch (type) { case 'commercial': @@ -306,7 +713,7 @@ export default function LaancStep1({ - +
비행 유형
+
비행 계획 정보
@@ -387,11 +794,13 @@ export default function LaancStep1({ id='schFltStDt' name='schFltStDt' data-enable-time - defaultValue={data.schFltStDt} - value={data.schFltStDt} + defaultValue={detailData.schFltStDt} + value={detailData.schFltStDt} ref={schFltStDtRef} onFocus={() => handleOpenFlatpickr()} options={{ + enableTime: true, + time_24hr: true, minDate: moment().format('YYYY-MM-DD'), maxDate: moment().add(90, 'day').format('YYYY-MM-DD') }} @@ -403,21 +812,23 @@ export default function LaancStep1({ value }); if (laancSun.length > 0) { - const filteredData = laancSun.filter(data => { - const dataDateTime = moment( - data.locDate, - 'YYYYMMDD' - ); - return dataDateTime.isSame( - moment(value, 'YYYYMMDD') - ); - }); + const filtereddetailData = laancSun.filter( + detailData => { + const detailDataDateTime = moment( + detailData.locDate, + 'YYYYMMDD' + ); + return detailDataDateTime.isSame( + moment(value, 'YYYYMMDD') + ); + } + ); const schFltStDt = moment(value).format('HHmmss'); - filteredData.forEach(data => { + filtereddetailData.forEach(detailData => { if ( - schFltStDt <= data.civilm || - schFltStDt >= data.civile + schFltStDt <= detailData.civilm || + schFltStDt >= detailData.civile ) { setIsLaancModal({ isOpen: true, @@ -436,9 +847,9 @@ export default function LaancStep1({ handleChange({ name: 'schFltStDt', value: - schFltStDt <= data.civilm || - schFltStDt >= data.civile - ? moment(data.civilm, 'HHmmss') + schFltStDt <= detailData.civilm || + schFltStDt >= detailData.civile + ? moment(detailData.civilm, 'HHmmss') .add(5, 'minute') .format('YYYY-MM-DD HH:mm:ss') : moment() @@ -485,11 +896,13 @@ export default function LaancStep1({ id='schFltEndDt' name='schFltEndDt' data-enable-time - defaultValue={data.schFltEndDt} + defaultValue={detailData.schFltEndDt} ref={schFltEndDtRef} - value={data.schFltEndDt} + value={detailData.schFltEndDt} onFocus={handleOpenFlatpickr} options={{ + enableTime: true, + time_24hr: true, minDate: moment().format('YYYY-MM-DD'), maxDate: moment().add(6, 'month').format('YYYY-MM-DD') }} @@ -501,21 +914,23 @@ export default function LaancStep1({ value }); if (laancSun.length > 0) { - const filteredData = laancSun.filter(data => { - const dataDateTime = moment( - data.locDate, - 'YYYYMMDD' - ); - return dataDateTime.isSame( - moment(value, 'YYYYMMDD') - ); - }); + const filtereddetailData = laancSun.filter( + detailData => { + const detailDataDateTime = moment( + detailData.locDate, + 'YYYYMMDD' + ); + return detailDataDateTime.isSame( + moment(value, 'YYYYMMDD') + ); + } + ); const schFltEndDt = moment(value).format('HHmmss'); - filteredData.forEach(data => { + filtereddetailData.forEach(detailData => { if ( - schFltEndDt <= data.civilm || - schFltEndDt >= data.civile + schFltEndDt <= detailData.civilm || + schFltEndDt >= detailData.civile ) { setIsLaancModal({ isOpen: true, @@ -534,9 +949,9 @@ export default function LaancStep1({ handleChange({ name: 'schFltEndDt', value: - schFltEndDt <= data.civilm || - schFltEndDt >= data.civile - ? moment(data.civile, 'HHmmss') + schFltEndDt <= detailData.civilm || + schFltEndDt >= detailData.civile + ? moment(detailData.civile, 'HHmmss') .add(-5, 'minute') .format('YYYY-MM-DD HH:mm:ss') : moment() @@ -561,7 +976,7 @@ export default function LaancStep1({ type='select' id='fltPurpose' name='fltPurpose' - value={data.fltPurpose} + value={detailData.fltPurpose} bsSize='sm' onChange={e => { const { name, value } = e.target; @@ -622,8 +1037,8 @@ export default function LaancStep1({ type='text' id='fltElev' name='fltElev' - // defaultValue={data.email || ''} - value={data.areaList[0].fltElev + 'm'} + // defaultValue={detailData.email || ''} + value={detailData.areaList[0].fltElev + 'm'} bsSize='sm' onBlur={e => handleBlur(e.target.value, 'fltElev')} onChange={e => { @@ -653,8 +1068,8 @@ export default function LaancStep1({ type='text' id='bufferZone' name='bufferZone' - // defaultValue={data.email || ''} - value={data.areaList[0].bufferZone + 'm'} + // defaultValue={detailData.email || ''} + value={detailData.areaList[0].bufferZone + 'm'} bsSize='sm' onChange={e => { const { name, value } = e.target; @@ -693,7 +1108,7 @@ export default function LaancStep1({ id='fltMethod' name='fltMethod' onBlur={e => handleBlur(e.target.value, 'fltMethod')} - value={data.areaList[0].fltMethod} + value={detailData.areaList[0].fltMethod} bsSize='sm' onChange={e => { const { name, value } = e.target; @@ -739,10 +1154,10 @@ export default function LaancStep1({ value }); }} - value={data.areaList[0].fltMothoeRm} + value={detailData.areaList[0].fltMothoeRm} placeholder='직접입력 선택 후 활성화' disabled={ - data.areaList[0].fltMethod === '00' ? false : true + detailData.areaList[0].fltMethod === '00' ? false : true } /> @@ -763,7 +1178,7 @@ export default function LaancStep1({ name='arcrftWghtCd' bsSize='sm' placeholder='' - value={data.arcrftList[0].arcrftWghtCd} + value={detailData.arcrftList[0].arcrftWghtCd} onChange={e => { const { name, value } = e.target; handleChange({ @@ -782,10 +1197,10 @@ export default function LaancStep1({ - {data.fltType === 'COMMERCIAL' || - data.arcrftList[0].arcrftWghtCd == '11' || - data.arcrftList[0].arcrftWghtCd == '10' || - data.arcrftList[0].arcrftWghtCd == '9' ? ( + {detailData.fltType === 'COMMERCIAL' || + detailData.arcrftList[0].arcrftWghtCd == '11' || + detailData.arcrftList[0].arcrftWghtCd == '10' || + detailData.arcrftList[0].arcrftWghtCd == '9' ? ( <> @@ -796,7 +1211,7 @@ export default function LaancStep1({ type='select' id='arcrftTypeCd' name='arcrftTypeCd' - value={data.arcrftList[0].arcrftTypeCd} + value={detailData.arcrftList[0].arcrftTypeCd} bsSize='sm' onChange={e => { const { name, value } = e.target; @@ -824,7 +1239,7 @@ export default function LaancStep1({ type='text' id='idntfNum' name='idntfNum' - value={data.arcrftList[0].idntfNum} + value={detailData.arcrftList[0].idntfNum} bsSize='sm' onChange={e => { const { name, value } = e.target; @@ -858,7 +1273,7 @@ export default function LaancStep1({ centeredModal={centeredModal} setCenteredModal={setCenteredModal} handleChange={handleChange} - data={data} + detailData={detailData} page={1} /> @@ -908,6 +1323,19 @@ export default function LaancStep1({ 다음 + setIsPopUp(!isPopUp)} + className='modal-dialog-centered modal-lg notam-modal' + style={{ height: '25vh', width: '25vw' }} + > + + diff --git a/src/components/laanc/step/LaancStep2.js b/src/components/laanc/step/LaancStep2.js index 14f68de2..e6880e54 100644 --- a/src/components/laanc/step/LaancStep2.js +++ b/src/components/laanc/step/LaancStep2.js @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import '@styles/react/libs/flatpickr/flatpickr.scss'; -import { AlertCircle, CheckCircle } from 'react-feather'; +import { CheckCircle } from 'react-feather'; import FlightArea from '../map/FlightArea'; import { Row, @@ -48,6 +48,7 @@ export default function LaancStep2({ const { laancPdf } = useSelector(state => state.laancState); const dispatch = useDispatch(); + // 약관 동의 데이터 useEffect(() => { dispatch( TermsActions.termsList.request({ @@ -58,12 +59,14 @@ export default function LaancStep2({ ); }, []); + // 비행 승인 요청 useEffect(() => { if (flightData && Object.keys(flightData).length > 0) { dispatch(LaancAction.LAANC_FLIGHT_CREATE.request(flightData)); } }, [flightData]); + // 비행 승인 요청 성공 useEffect(() => { if (laancPdf && flightData && Object.keys(flightData).length > 0) { handlerStep(3); diff --git a/src/containers/laanc/LaancContainer.js b/src/containers/laanc/LaancContainer.js index 3655d5e1..58604613 100644 --- a/src/containers/laanc/LaancContainer.js +++ b/src/containers/laanc/LaancContainer.js @@ -17,18 +17,27 @@ export default function LaancContainer() { const [currentParm, setCurrentParm] = useState(false); const [disabledAnimation, setDisabledAnimation] = useState(false); - + const [isSearch, setIsSearch] = useState(false); const queryParams = new URLSearchParams(location.search); const mapParam = queryParams.get('map'); + // URL 쿼리 파라미터 중 'map' 값을 가져옵니다. useEffect(() => { - // URL 쿼리 파라미터 중 'map' 값을 가져옵니다. + // Redux Store 초기화 dispatch(drawTypeChangeAction('')); dispatch(LaancAction.LAANC_APPROVAL_INIT()); dispatch(AreaAction.AREA_DETAIL_INIT()); setDisabledAnimation(mapParam != 'true' ? false : true); }, [location]); + // Laanc 신청 이후 자동 검색 + useEffect(() => { + if (disabledAnimation) { + setIsSearch(false); + } else setIsSearch(true); + }, [disabledAnimation]); + + // LAANC 신청하기 버튼 클릭 헨들러 const handleApply = () => { dispatch(drawTypeChangeAction('')); dispatch(LaancAction.LAANC_APPROVAL_INIT()); @@ -64,7 +73,7 @@ export default function LaancContainer() { ) : null} - + ); diff --git a/src/containers/laanc/LaancPlanContainer.js b/src/containers/laanc/LaancPlanContainer.js index 958d2cd7..3941ea62 100644 --- a/src/containers/laanc/LaancPlanContainer.js +++ b/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 LaancStep2 from '../../components/laanc/step/LaancStep2'; // laanc step 2 import LaancStep3 from '../../components/laanc/step/LaacnStep3'; // laanc step 3 -import moment from 'moment'; import { ErrorModal } from '../../components/modal/ErrorModal'; import { LaancModal } from '../../components/laanc/LaancModal'; import { initFlightBas } from '../../modules/laanc/models/laancModels'; import { Modal } from 'reactstrap'; -import { - AREA_DETAIL_INIT, - FLIGHT_PLAN_AREA_BUFFER_LIST -} from '../../modules/basis/flight/actions/basisFlightAction'; +import { AREA_DETAIL_INIT } from '../../modules/basis/flight/actions/basisFlightAction'; import { useDispatch, useSelector } from 'react-redux'; 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({ currentParm, @@ -24,9 +17,7 @@ export default function LaancPlanContainer({ }) { const dispatch = useDispatch(); - const { areaCoordList } = useSelector(state => state.flightState); const { user } = useSelector(state => state.authState); - const { laancArea, laancElev } = useSelector(state => state.laancState); const [step, setStep] = useState(1); const [detailData, setDetailData] = useState(initFlightBas.initDetail); @@ -44,6 +35,7 @@ export default function LaancPlanContainer({ url: '' }); + // 로그인 회원 정보 세팅 useEffect(() => { if (user) { 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: ( - <> - 유효성 검사에 실패하여 미 승인 대상입니다. -
- 제출하신 비행계획서의 고도는 {laancElev[0]}m이하에서만 비행이 - 가능합니다. -
- 고도 설정을 다시 확인해주시기 바랍니다. - - ) - }); - 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: ( - <> - 검토 결과 미 승인 대상입니다. -

- 제줄하신 비행계획서는 별도의 승인이 필요없습니다. -
- 조종자 준수사항에 유의하여 비행하시기 바랍니다. -

- - ) - }); - return; - } else if (detailData.areaList[0].fltMethod === '군집비행') { - handleChange({ - type: 'area', - name: 'fltMethod', - value: '' - }); - setIsLaancModal({ - isOpen: true, - title: '군집 비행 목적', - desc: ( - <> - 군집 비행의 경우 담당자와 협의가 필요합니다.
- 아래 링크를 통해 담당자와 협의 부탁드립니다. - - ), - 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: ( - <> - 유효성 검사에 실패하여 미 승인 대상입니다. -

- 기체가 보험에 가입되어 있지 않거나 유효기간이 - 만료되었습니다. -
- 기체 번호를 다시 확인해주시기 바랍니다. -

- - ) - }); - return; - } else { - setStep(2); - } - } catch (error) { - setIsErrorModal({ - isOpen: true, - title: '오류', - desc: <>처리중 오류가 발생하였습니다 - }); - } - } - } - }; - // step 핸들러 const handlerStep = step => { setStep(step); }; - // 날씨 핸들러 - 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: ( - <> - 야간 비행은 특별 비행에 해당됩니다. -
- 특별 비행의 경우 드론원스톱을 통해서 신청해주시기 바랍니다. - - ) - }); - return false; - } else if (schFltStDt.format('A h:mm') === 'PM 5:00') { - setIsErrorModal({ - isOpen: true, - title: '비행구역 및 비행일자 중복', - desc: ( - <> - 설정하신 비행구역 및 비행시간에 이미 승인완료된 신청건이 있습니다. -
다시 설정 부탁드립니다. - - ) - }); - 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인 구역이 있습니다. -
- 버퍼존을 다시 확인해주시기 바랍니다. - - ), - isOpen: true - }); - } - dispatch(LaancAction.LAANC_ALTITUDE.success(elev.data)); - } catch (error) { - { - setIsErrorModal({ - isOpen: true, - title: '오류', - desc: '처리중 오류가 발생하였습니다' - }); - } - } - } - } - }; - + // Laanc 승인 요청 취소 버튼 헨들러 const handlerLaancClose = () => { setStep(1); setDisabledAnimation(!disabledAnimation); @@ -504,17 +74,13 @@ export default function LaancPlanContainer({ {step === 1 && ( <> )} @@ -541,6 +107,7 @@ export default function LaancPlanContainer({ /> )} + diff --git a/src/modules/laanc/actions/laancActions.ts b/src/modules/laanc/actions/laancActions.ts index d5680b9b..6ced9c7d 100644 --- a/src/modules/laanc/actions/laancActions.ts +++ b/src/modules/laanc/actions/laancActions.ts @@ -14,7 +14,8 @@ import { FlightPlanAreaData, VaildElevData, VaildAreaData, - LaancTsData + LaancTsData, + LaancTsQrData } from '../models/laancModels'; // 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_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 초기화 const INIT_LAANC = 'laanc/init'; // laanc approval detail 초기화 const INIT_APPROVAL_DETAIL = 'laanc/init/approval/detail'; + // 허뎓 고도 초기화 // const INIT_ALTITUDE = 'laanc/init/altitude'; @@ -142,6 +149,13 @@ export const LAANC_VALID_TS = createAsyncAction( LAANC_VALID_TS_FAILURE )(); +// laanc ts qr +export const LAANC_TS_QR = createAsyncAction( + LAANC_TS_QR_REQUEST, + LAANC_TS_QR_SUCCESS, + LAANC_TS_QR_FAILURE +)(); + const actions = { LAANC_FLIGHT_Approval, LAANC_FLIGHT_CREATE, @@ -152,6 +166,7 @@ const actions = { LAANC_ALTITUDE, LAANC_VALID_AREA, LAANC_VALID_TS, + LAANC_TS_QR, LAANC_APPROVAL_DETAIL_INIT }; export type LaancAction = ActionType; diff --git a/src/modules/laanc/apis/laancApi.ts b/src/modules/laanc/apis/laancApi.ts index 0e0e028d..2e1b5020 100644 --- a/src/modules/laanc/apis/laancApi.ts +++ b/src/modules/laanc/apis/laancApi.ts @@ -54,5 +54,10 @@ export const laancApi = { postValidTs: async (data: string) => { const res = await axios.post(`api/bas/laanc/valid/ts/pilot/${data}`); return res; + }, + // laanc ts qr + getTsQr: async (data: string) => { + const res = await axios.get(`api/bas/laanc/ts/qr/${data}`); + return res.data; } }; diff --git a/src/modules/laanc/models/laancModels.ts b/src/modules/laanc/models/laancModels.ts index 98d6a89c..693ef285 100644 --- a/src/modules/laanc/models/laancModels.ts +++ b/src/modules/laanc/models/laancModels.ts @@ -10,6 +10,7 @@ export interface laancState { laancElev: number[] | undefined; laancArea: VaildAreaData | undefined; laancTs: LaancTsData | undefined; + laancQrData: LaancTsQrData | undefined; } // laanc계획서 초기값 @@ -377,6 +378,7 @@ export const laancControlData = { laancElev: undefined, laancArea: undefined, laancTs: undefined, + laancQrData: undefined, detail: { planSno: 0, groupId: '', @@ -817,3 +819,12 @@ export interface LaancTsData { ]; valid: boolean; } + +// laanc Ts QR +export interface LaancTsQrData { + rspCode: string; + rspMessage: string; + arcrftinsuranceyn: string; + arcrftdeclaration: string; + corpregyn: string; +} diff --git a/src/modules/laanc/reducers/laancReducers.ts b/src/modules/laanc/reducers/laancReducers.ts index 8a7e5364..cba4abdc 100644 --- a/src/modules/laanc/reducers/laancReducers.ts +++ b/src/modules/laanc/reducers/laancReducers.ts @@ -83,6 +83,12 @@ export const laancReducer = createReducer( const data = action.payload; 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) => // produce(state, draft => { diff --git a/src/modules/laanc/sagas/laancSagas.ts b/src/modules/laanc/sagas/laancSagas.ts index e9dc4b30..f5d516ec 100644 --- a/src/modules/laanc/sagas/laancSagas.ts +++ b/src/modules/laanc/sagas/laancSagas.ts @@ -171,6 +171,23 @@ function* postValidTsSaga( ); } } +// laanc ts qr +function* getTsQrSaga(action: ActionType) { + 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() { yield takeEvery(Actions.LAANC_FLIGHT_Approval.request, postApprovalSaga); yield debounce(500, Actions.LAANC_FLIGHT_CREATE.request, postCreateSaga); -- 2.30.3 From 783336d0c31725bdf2938b9451a8cac5f831e3e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=83=81=ED=98=84?= Date: Fri, 8 Dec 2023 11:15:40 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=EC=B6=A9=EB=8F=8C=20=EB=B3=91=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 28 +- src/containers/laanc/LaancContainer.js | 7 +- src/containers/laanc/LaancPlanContainer.js | 307 --------------------- 3 files changed, 4 insertions(+), 338 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3932cd61..8afcb53f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6324,9 +6324,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001242", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001242.tgz", - "integrity": "sha512-KvNuZ/duufelMB3w2xtf9gEWCSxJwUgoxOx5b6ScLXC4kPc9xsczUVCPrQU26j5kOsHM4pSUL54tAZt5THQKug==" + "version": "1.0.30001566", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz", + "integrity": "sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA==" }, "capture-exit": { "version": "2.0.0", @@ -8487,14 +8487,6 @@ } } }, - "dom7": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/dom7/-/dom7-3.0.0.tgz", - "integrity": "sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==", - "requires": { - "ssr-window": "^3.0.0-alpha.1" - } - }, "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", @@ -24397,11 +24389,6 @@ "tweetnacl": "~0.14.0" } }, - "ssr-window": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-3.0.0.tgz", - "integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==" - }, "ssri": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", @@ -24943,15 +24930,6 @@ "resolved": "https://registry.npmjs.org/sweetalert2-react-content/-/sweetalert2-react-content-3.0.1.tgz", "integrity": "sha512-VBybIRTIzY2bTkUddcp2wMJ3mp3gfGGX6+BfW2dDrEv6bXM2WtzJpFkM2imFpcPhpkOIf2/J8gLxEu0jBZq0DQ==" }, - "swiper": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/swiper/-/swiper-6.0.4.tgz", - "integrity": "sha512-D+DBxgg81+uocgsvhmdzrpr4GHzhAt2yImArqzunrC80y7+/yCEAq/EJw1VASD+CBFNacF4F8FEIqJMLyDFM0g==", - "requires": { - "dom7": "^3.0.0-alpha.7", - "ssr-window": "^3.0.0-alpha.4" - } - }, "symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", diff --git a/src/containers/laanc/LaancContainer.js b/src/containers/laanc/LaancContainer.js index 6e0d4d7d..1acb2bd1 100644 --- a/src/containers/laanc/LaancContainer.js +++ b/src/containers/laanc/LaancContainer.js @@ -17,13 +17,11 @@ export default function LaancContainer() { const [currentParm, setCurrentParm] = useState(false); //LAANC 신청하기 모달 const [disabledAnimation, setDisabledAnimation] = useState(false); -<<<<<<< HEAD + // laanc 신청시 자동 검색 const [isSearch, setIsSearch] = useState(false); -======= // 마운트 시 지도 표출 여부 const location = useLocation(); ->>>>>>> 1ed9d9e3865eef0d42bac8f02dddb2a221ff8d58 const queryParams = new URLSearchParams(location.search); const mapParam = queryParams.get('map'); @@ -36,7 +34,6 @@ export default function LaancContainer() { setDisabledAnimation(mapParam != 'true' ? false : true); }, [location]); -<<<<<<< HEAD // Laanc 신청 이후 자동 검색 useEffect(() => { if (disabledAnimation) { @@ -44,8 +41,6 @@ export default function LaancContainer() { } else setIsSearch(true); }, [disabledAnimation]); -======= ->>>>>>> 1ed9d9e3865eef0d42bac8f02dddb2a221ff8d58 // LAANC 신청하기 버튼 클릭 헨들러 const handleApply = () => { dispatch(drawTypeChangeAction('')); diff --git a/src/containers/laanc/LaancPlanContainer.js b/src/containers/laanc/LaancPlanContainer.js index 14761b03..766ce36f 100644 --- a/src/containers/laanc/LaancPlanContainer.js +++ b/src/containers/laanc/LaancPlanContainer.js @@ -17,12 +17,8 @@ export default function LaancPlanContainer({ }) { const dispatch = useDispatch(); - // 비행 구역 정보 - const { areaCoordList } = useSelector(state => state.flightState); // 로그인 정보 const { user } = useSelector(state => state.authState); - // 관제권안 정보,고도 정보 - const { laancArea, laancElev } = useSelector(state => state.laancState); // laanc step const [step, setStep] = useState(1); // laanc 초기값 @@ -63,309 +59,6 @@ export default function LaancPlanContainer({ setStep(step); }; - // 날씨 핸들러 - 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: ( - <> - 야간 비행은 특별 비행에 해당됩니다. -
- 특별 비행의 경우 드론원스톱을 통해서 신청해주시기 바랍니다. - - ) - }); - return false; - } else if (schFltStDt.format('A h:mm') === 'PM 5:00') { - setIsErrorModal({ - isOpen: true, - title: '비행구역 및 비행일자 중복', - desc: ( - <> - 설정하신 비행구역 및 비행시간에 이미 승인완료된 신청건이 있습니다. -
다시 설정 부탁드립니다. - - ) - }); - 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인 구역이 있습니다. -
- 버퍼존을 다시 확인해주시기 바랍니다. - - ), - isOpen: true - }); - } - dispatch(LaancAction.LAANC_ALTITUDE.success(elev.data)); - } catch (error) { - { - setIsErrorModal({ - isOpen: true, - title: '오류', - desc: '처리중 오류가 발생하였습니다' - }); - } - } - } - } - }; - // Laanc 승인 요청 취소 버튼 헨들러 const handlerLaancClose = () => { setStep(1); -- 2.30.3