diff --git a/src/components/cstmrService/faq/FaqForm.js b/src/components/cstmrService/faq/FaqForm.js index 2db8e7a1..0c121978 100644 --- a/src/components/cstmrService/faq/FaqForm.js +++ b/src/components/cstmrService/faq/FaqForm.js @@ -1,4 +1,3 @@ -import { useState } from 'react'; import { Button, Input, @@ -14,9 +13,17 @@ import { } from 'reactstrap'; import classnames from 'classnames'; -export default function FaqForm({ isOpenFormModal, handlerOpenFormModal }) { - const [value, setValue] = useState(''); - +export default function FaqForm({ + isOpenFormModal, + handlerOpenFormModal, + tabList, + formData, + handlerChangeFormData, + handlerSubmitForm, + handlerDeleteConfirmFaq, + user, + formMode +}) { return ( *카테고리 - - - - - - + { + const { value } = e.target; + handlerChangeFormData('category', value); + }} + > + {tabList + .filter(i => i.value !== '전체') + .map(i => ( + + ))} @@ -46,14 +63,14 @@ export default function FaqForm({ isOpenFormModal, handlerOpenFormModal }) { - + {/* - + */} @@ -94,7 +118,16 @@ export default function FaqForm({ isOpenFormModal, handlerOpenFormModal }) { - + { + const { value } = e.target; + handlerChangeFormData('title', value); + }} + /> @@ -105,31 +138,37 @@ export default function FaqForm({ isOpenFormModal, handlerOpenFormModal }) { setValue(e.target.value)} + value={formData.content} + onChange={e => { + const { value } = e.target; + handlerChangeFormData('content', value); + }} /> {/* 하단 필요없으면제거 */} - 300 + 'bg-danger': formData.content.length > 300 })} > - {`${value.length}/300`} - + {`${formData.content.length}/300`} + */} - + {formMode === 'update' && ( + + )} - diff --git a/src/components/cstmrService/faq/FaqTab.js b/src/components/cstmrService/faq/FaqTab.js new file mode 100644 index 00000000..b9cec8a2 --- /dev/null +++ b/src/components/cstmrService/faq/FaqTab.js @@ -0,0 +1,20 @@ +import { Nav, NavItem, NavLink } from 'reactstrap'; + +export default function FaqTab({ activeTab, handlerChangeTab, tabList }) { + return ( +
+ +
+ ); +} diff --git a/src/components/map/mapbox/MapBoxMap.js b/src/components/map/mapbox/MapBoxMap.js index 2b490aa6..5d1a7bdf 100644 --- a/src/components/map/mapbox/MapBoxMap.js +++ b/src/components/map/mapbox/MapBoxMap.js @@ -495,10 +495,6 @@ export default function MapBoxMap() { } )); - map.on('click', e => { - console.log(e); - }); - map.on('style.load', () => { const layers = map.getStyle().layers; const labelLayerId = layers.find( diff --git a/src/containers/cstmrService/faq/FaqContainer.js b/src/containers/cstmrService/faq/FaqContainer.js index 45829927..d797b7e7 100644 --- a/src/containers/cstmrService/faq/FaqContainer.js +++ b/src/containers/cstmrService/faq/FaqContainer.js @@ -1,85 +1,78 @@ import { useCallback, useEffect, useLayoutEffect, useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { Button, Nav, NavItem, NavLink, TabContent, TabPane } from 'reactstrap'; +import { Button, TabContent, TabPane } from 'reactstrap'; import AppCollapse from '@components/app-collapse'; -import { LIST } from '../../../modules/cstmrService/faq/action'; +import { + LIST, + INSERT, + UPDATE, + DELETE +} from '../../../modules/cstmrService/faq/action'; import FaqSearchBox from '../../../components/cstmrService/faq/FaqSearchBox'; import FaqForm from '../../../components/cstmrService/faq/FaqForm'; - -const data = [ +import FaqTab from '../../../components/cstmrService/faq/FaqTab'; +import { ErrorModal } from '../../../components/modal/ErrorModal'; +import { InfoModal } from '../../../components/modal/InfoModal'; +import { ConfirmModal } from '../../../components/modal/ConfirmModal'; +const tabList = [ { - title: ( -
-
- Q - - [장치신고]드론을 구매했는데 기체신고를 해야하나요? - -
-
- 수정하기 -
-
- ), - content: ( -
- A - - {/* br처리? 줄바꿈.. 추후에 생각 */} - 네 그렇습니다. -
-
- - 사용용도가 영리 목적인 경우 : 무게에 상관없이 모두 신고 -
- - 사용용도가 비영리 목적인 경우 -
- · (무인멀티콥터, 무인비행기, 무인헬리콥터) 최대이륙중량 2kg 초과 시 - 신고 -
- · (무인비행선) 연료의 무게를 제외한 자체무게가 12kg 초과, 길이 7m 초과 - 시 신고
-
-
- ) + title: '전체', + value: '전체' }, { - title: ( -
-
- Q - - [비행승인]실내에서 비행할 때에도 비행승인을 받아야 - 할까? - -
-
- 수정하기 -
-
- ), - content: ( -
- A - - 사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지 - 않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이 - 가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가 - 없도록 주의하여 비행하여야 합니다. - -
- ) + title: '장치 신고', + value: '장치신고' + }, + { + title: '사업 등록', + value: '사업등록' + }, + { + title: '비행 승인', + value: '비행승인' + }, + { + title: '항공 촬영', + value: '항공촬영' + }, + { + title: '기타', + value: '기타' } ]; +const initFormData = { + category: '장치신고', + content: '', + expsrYn: 'Y', + title: '', + faqSno: 0 +}; + export default function FaqContainer() { - // ** States & Vars const [activeTab, setActiveTab] = useState('전체'); const { user } = useSelector(state => state.authState); const [searchText, setSearchText] = useState(''); - - // ** Function to toggle tabs - const toggle = tab => setActiveTab(tab); + const [formData, setFormData] = useState(initFormData); + const [formMode, setFormMode] = useState('create'); const [isOpenFormModal, setIsOpenFormModal] = useState(false); + const [validationModal, setValidationModal] = useState({ + errorType: { + isOpen: false, + desc: '', + title: '필수값 입력 오류' + }, + successType: { + isOpen: false, + desc: '', + title: '필수값 입력 오류' + }, + confirmType: { + isOpen: false, + desc: '', + title: '필수값 입력 오류' + } + }); const dispatch = useDispatch(); const { faqList } = useSelector(state => state.faqState); @@ -110,9 +103,128 @@ export default function FaqContainer() { }; const handlerOpenFormModal = () => { + setFormData(initFormData); setIsOpenFormModal(!isOpenFormModal); }; + const handlerChangeTab = useCallback( + val => { + setActiveTab(val); + }, + [activeTab] + ); + + const handlerChangeFormData = useCallback( + (type, val) => { + setFormData({ + ...formData, + [type]: val + }); + }, + [formData] + ); + + const handlerSubmitForm = () => { + if (!formData.title) { + setValidationModal({ + ...validationModal, + errorType: { + ...validationModal.errorType, + isOpen: true, + desc: '제목을 입력해주세요.' + } + }); + return; + } + + if (!formData.content) { + setValidationModal({ + ...validationModal, + errorType: { + ...validationModal.errorType, + isOpen: true, + desc: '내용을 입력해주세요.' + } + }); + return; + } + + if (formMode === 'create') { + dispatch( + INSERT.request({ + search: { + category: activeTab, + word: searchText + }, + form: { + ...formData + } + }) + ); + } else { + dispatch( + UPDATE.request({ + search: { + category: activeTab, + word: searchText + }, + form: { + ...formData + } + }) + ); + } + + setIsOpenFormModal(false); + setFormData(initFormData); + setValidationModal({ + ...validationModal, + successType: { + title: formMode === 'create' ? '등록' : '수정', + isOpen: true, + desc: `${formMode === 'create' ? '등록' : '수정'}되었습니다.` + } + }); + }; + + const handlerFaqModify = val => { + setFormMode('update'); + setFormData({ + ...formData, + category: val.category, + content: val.content, + expsrYn: val.expsrYn, + title: val.title, + faqSno: val.faqSno + }); + setIsOpenFormModal(true); + }; + + const handlerDeleteConfirmFaq = () => { + setValidationModal({ + ...validationModal, + confirmType: { + isOpen: true, + title: '삭제', + desc: '정말 삭제하시겠습니까?' + } + }); + }; + + const handlerDeleteFaq = () => { + dispatch( + DELETE.request({ + search: { + category: activeTab, + word: searchText + }, + faqSno: formData.faqSno + }) + ); + setIsOpenFormModal(false); + setFormData(initFormData); + }; + const handlerFaqList = () => { if (faqList?.length <= 0 || !faqList) return []; return faqList.map(i => { @@ -126,9 +238,16 @@ export default function FaqContainer() { {i.title} -
- 수정하기 -
+ {user?.authId !== 'USER' && ( +
+ handlerFaqModify(i)} + > + 수정하기 + +
+ )} ), content: ( @@ -156,7 +275,10 @@ export default function FaqContainer() { className='faq-admin-plus' outline color='primary' - onClick={() => setIsOpenFormModal(!isOpenFormModal)} + onClick={() => { + setFormMode('create'); + setIsOpenFormModal(!isOpenFormModal); + }} > 질문 등록하기 @@ -164,58 +286,11 @@ export default function FaqContainer() { {/* 탭 컨텐츠 */}
-
- -
+
@@ -223,17 +298,53 @@ export default function FaqContainer() {
- {/* 장치신고 - 사업 등록 - 비행 승인 - 항공 촬영 - 기타 */}
+ { + setValidationModal({ + ...validationModal, + errorType: { + ...val + } + }); + }} + /> + { + setValidationModal({ + ...validationModal, + successType: { + ...val + } + }); + }} + /> + { + setValidationModal({ + ...validationModal, + confirmType: { + ...val + } + }); + }} + handlerConfirm={handlerDeleteFaq} /> ); diff --git a/src/modules/cstmrService/faq/action/index.ts b/src/modules/cstmrService/faq/action/index.ts index 35ad16e4..00deeb20 100644 --- a/src/modules/cstmrService/faq/action/index.ts +++ b/src/modules/cstmrService/faq/action/index.ts @@ -1,21 +1,63 @@ import { AxiosError } from 'axios'; import { createAsyncAction, ActionType, createAction } from 'typesafe-actions'; -import { IFaqList } from '../model'; +import { + IFaqList, + IFaqSearch, + IFaqInsert, + IFaqUpdate, + IFaqDelete +} from '../model'; // 목록 조회 const LIST_REQUEST = 'cstmrService/faq/LIST_REQUEST'; const LIST_SUCCESS = 'cstmrService/faq/LIST_SUCCESS'; const LIST_FAILURE = 'cstmrService/faq/LIST_FAILURE'; +// 질문 등록 +const INSERT_REQUEST = 'cstmrService/faq/INSERT_REQUEST'; +const INSERT_SUCCESS = 'cstmrService/faq/INSERT_SUCCESS'; +const INSERT_FAILURE = 'cstmrService/faq/INSERT_FAILURE'; + +// 질문 수정 +const UPDATE_REQUEST = 'cstmrService/faq/UPDATE_REQUEST'; +const UPDATE_SUCCESS = 'cstmrService/faq/UPDATE_SUCCESS'; +const UPDATE_FAILURE = 'cstmrService/faq/UPDATE_FAILURE'; + +// 질문 수정 +const DELETE_REQUEST = 'cstmrService/faq/DELETE_REQUEST'; +const DELETE_SUCCESS = 'cstmrService/faq/DELETE_SUCCESS'; +const DELETE_FAILURE = 'cstmrService/faq/DELETE_FAILURE'; + export const LIST = createAsyncAction(LIST_REQUEST, LIST_SUCCESS, LIST_FAILURE)< - { category: string; word: string }, + IFaqSearch, IFaqList[], AxiosError >(); +export const INSERT = createAsyncAction( + INSERT_REQUEST, + INSERT_SUCCESS, + INSERT_FAILURE +)(); + +export const UPDATE = createAsyncAction( + UPDATE_REQUEST, + UPDATE_SUCCESS, + UPDATE_FAILURE +)(); + +export const DELETE = createAsyncAction( + DELETE_REQUEST, + DELETE_SUCCESS, + DELETE_FAILURE +)(); + const actions = { - LIST + LIST, + INSERT, + UPDATE, + DELETE }; export type FaqAction = ActionType; diff --git a/src/modules/cstmrService/faq/apis/index.ts b/src/modules/cstmrService/faq/apis/index.ts index 31a7fbf6..374ed66e 100644 --- a/src/modules/cstmrService/faq/apis/index.ts +++ b/src/modules/cstmrService/faq/apis/index.ts @@ -1,7 +1,5 @@ import axios from '../../../utils/customAxiosUtil'; -import qs from 'qs'; - export const faqAPI = { list: async ({ category, word }) => { const url = @@ -9,5 +7,14 @@ export const faqAPI = { ? `api/bas/cns/faq?word=${word}` : `api/bas/cns/faq?category=${category}&word=${word}`; return await axios.get(url); + }, + insert: async data => { + return await axios.post('api/bas/cns/faq', { ...data }); + }, + update: async data => { + return await axios.put('api/bas/cns/faq', { ...data }); + }, + delete: async data => { + return await axios.delete(`api/bas/cns/faq/${data}`); } }; diff --git a/src/modules/cstmrService/faq/model/index.ts b/src/modules/cstmrService/faq/model/index.ts index 1bf31697..f1358b5d 100644 --- a/src/modules/cstmrService/faq/model/index.ts +++ b/src/modules/cstmrService/faq/model/index.ts @@ -2,6 +2,37 @@ export interface IFaqState { faqList: IFaqList[]; } +export interface IFaqInsert { + search: IFaqSearch; + form: { + category: string; + content: string; + expsrYn: string; + title: string; + }; +} + +export interface IFaqUpdate { + search: IFaqSearch; + form: { + category: string; + content: string; + expsrYn: string; + faqSno: number; + title: string; + }; +} + +export interface IFaqDelete { + search: IFaqSearch; + faqSno: number; +} + +export interface IFaqSearch { + category: string; + word: string; +} + export interface IFaqList { faqSno: number; category: string; diff --git a/src/modules/cstmrService/faq/sagas/index.ts b/src/modules/cstmrService/faq/sagas/index.ts index 0a8ad9d0..e53f6237 100644 --- a/src/modules/cstmrService/faq/sagas/index.ts +++ b/src/modules/cstmrService/faq/sagas/index.ts @@ -35,6 +35,84 @@ function* listSaga(action: ActionType) { } } +function* insertSaga(action: ActionType) { + try { + const { search, form } = action.payload; + const res = yield call(Apis.faqAPI.insert, form); + const { errorCode } = res; + + if (errorCode) { + // 오류메시지 호출 + yield put( + MessageActions.IS_ERROR({ + errorCode: errorCode, + errorMessage: '처리중 오류가 발생하였습니다', + isHistoryBack: false, + isRefresh: false + }) + ); + + return; + } + yield put(Actions.LIST.request(search)); + } catch (error) { + yield put(Actions.LIST.failure(error)); + } +} + +function* updateSaga(action: ActionType) { + try { + const { search, form } = action.payload; + const res = yield call(Apis.faqAPI.update, form); + const { errorCode } = res; + + if (errorCode) { + // 오류메시지 호출 + yield put( + MessageActions.IS_ERROR({ + errorCode: errorCode, + errorMessage: '처리중 오류가 발생하였습니다', + isHistoryBack: false, + isRefresh: false + }) + ); + + return; + } + yield put(Actions.LIST.request(search)); + } catch (error) { + yield put(Actions.LIST.failure(error)); + } +} + +function* deleteSaga(action: ActionType) { + try { + const { faqSno, search } = action.payload; + const res = yield call(Apis.faqAPI.delete, faqSno); + const { errorCode } = res; + + if (errorCode) { + // 오류메시지 호출 + yield put( + MessageActions.IS_ERROR({ + errorCode: errorCode, + errorMessage: '처리중 오류가 발생하였습니다', + isHistoryBack: false, + isRefresh: false + }) + ); + + return; + } + yield put(Actions.LIST.request(search)); + } catch (error) { + yield put(Actions.LIST.failure(error)); + } +} + export function* faqSaga() { yield takeEvery(Actions.LIST.request, listSaga); + yield takeEvery(Actions.INSERT.request, insertSaga); + yield takeEvery(Actions.UPDATE.request, updateSaga); + yield takeEvery(Actions.DELETE.request, deleteSaga); }