Browse Source

자주묻는 질문 crud 작업

pull/2/head
김장현 11 months ago
parent
commit
7ac941f389
  1. 91
      src/components/cstmrService/faq/FaqForm.js
  2. 20
      src/components/cstmrService/faq/FaqTab.js
  3. 4
      src/components/map/mapbox/MapBoxMap.js
  4. 365
      src/containers/cstmrService/faq/FaqContainer.js
  5. 48
      src/modules/cstmrService/faq/action/index.ts
  6. 11
      src/modules/cstmrService/faq/apis/index.ts
  7. 31
      src/modules/cstmrService/faq/model/index.ts
  8. 78
      src/modules/cstmrService/faq/sagas/index.ts

91
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 (
<Modal
isOpen={isOpenFormModal}
@ -32,12 +39,22 @@ export default function FaqForm({ isOpenFormModal, handlerOpenFormModal }) {
<Label for='test'>
<span className='necessary'>*</span>
</Label>
<Input type='select' size='sm'>
<option value=''>장치 신고</option>
<option value=''>사업 등록</option>
<option value=''>비행 승인</option>
<option value=''>항공 촬영</option>
<option value=''>기타</option>
<Input
type='select'
besize='sm'
value={formData.category}
onChange={e => {
const { value } = e.target;
handlerChangeFormData('category', value);
}}
>
{tabList
.filter(i => i.value !== '전체')
.map(i => (
<option key={i.value} value={i.value}>
{i.title}
</option>
))}
</Input>
</FormGroup>
</Col>
@ -46,14 +63,14 @@ export default function FaqForm({ isOpenFormModal, handlerOpenFormModal }) {
<Label for='test'>생성 사용자</Label>
<Input
type='text'
size='sm'
besize='sm'
placeholder=''
value='홍길동'
value={user?.userId}
disabled
/>
</FormGroup>
</Col>
<Col className='list-input' md='3'>
{/* <Col className='list-input' md='3'>
<FormGroup>
<Label for='test'>생성 일자</Label>
<Input
@ -64,7 +81,7 @@ export default function FaqForm({ isOpenFormModal, handlerOpenFormModal }) {
disabled
/>
</FormGroup>
</Col>
</Col> */}
<Col className='list-input' md='3'>
<FormGroup>
<Label for='test'>
@ -77,7 +94,10 @@ export default function FaqForm({ isOpenFormModal, handlerOpenFormModal }) {
name='customRadio'
inline
label='표출'
defaultChecked
defaultChecked={formData.expsrYn === 'Y'}
onClick={() => {
handlerChangeFormData('expsrYn', 'Y');
}}
/>
<CustomInput
type='radio'
@ -85,6 +105,10 @@ export default function FaqForm({ isOpenFormModal, handlerOpenFormModal }) {
name='customRadio'
inline
label='미표출'
defaultChecked={formData.expsrYn === 'N'}
onClick={() => {
handlerChangeFormData('expsrYn', 'N');
}}
/>
</div>
</FormGroup>
@ -94,7 +118,16 @@ export default function FaqForm({ isOpenFormModal, handlerOpenFormModal }) {
<Label for='test'>
<span className='necessary'>*</span>
</Label>
<Input type='text' size='sm' placeholder='' />
<Input
type='text'
besize='sm'
placeholder=''
value={formData.title}
onChange={e => {
const { value } = e.target;
handlerChangeFormData('title', value);
}}
/>
</FormGroup>
</Col>
<Col className='list-input' md='12'>
@ -105,31 +138,37 @@ export default function FaqForm({ isOpenFormModal, handlerOpenFormModal }) {
<Input
className='faq-textarea'
type='textarea'
size='sm'
besize='sm'
placeholder=''
onChange={e => setValue(e.target.value)}
value={formData.content}
onChange={e => {
const { value } = e.target;
handlerChangeFormData('content', value);
}}
/>
{/* 하단 필요없으면제거 */}
<span
{/* <span
className={classnames('textarea-counter-value float-right', {
'bg-danger': value.length > 300
'bg-danger': formData.content.length > 300
})}
>
{`${value.length}/300`}
</span>
{`${formData.content.length}/300`}
</span> */}
</FormGroup>
</Col>
</Row>
</div>
</ModalBody>
<ModalFooter>
<Button color='danger' onClick={handlerOpenFormModal}>
삭제
</Button>
{formMode === 'update' && (
<Button color='danger' onClick={handlerDeleteConfirmFaq}>
삭제
</Button>
)}
<Button color='secondary' onClick={handlerOpenFormModal}>
취소
</Button>
<Button color='primary' onClick={handlerOpenFormModal}>
<Button color='primary' onClick={handlerSubmitForm}>
저장
</Button>
</ModalFooter>

20
src/components/cstmrService/faq/FaqTab.js

@ -0,0 +1,20 @@
import { Nav, NavItem, NavLink } from 'reactstrap';
export default function FaqTab({ activeTab, handlerChangeTab, tabList }) {
return (
<div className='tab-menu'>
<Nav pills>
{tabList.map(i => (
<NavItem key={i.title}>
<NavLink
active={i.value === activeTab}
onClick={() => handlerChangeTab(i.value)}
>
<span>{i.title}</span>
</NavLink>
</NavItem>
))}
</Nav>
</div>
);
}

4
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(

365
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: (
<div className='faq-q'>
<h5>
<span className='faq-icon-q'>Q</span>
<span className='ti'>
<span>[장치신고]</span> ?
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
</div>
</div>
),
content: (
<div className='faq-a'>
<span className='faq-icon-a'>A</span>
<span className='faq-a-text'>
{/* br처리? 줄바꿈.. 추후에 생각 */}
그렇습니다.
<br />
<br />
- 사용용도가 영리 목적인 경우 : 무게에 상관없이 모두 신고
<br />
- 사용용도가 비영리 목적인 경우
<br />
· (무인멀티콥터, 무인비행기, 무인헬리콥터) 최대이륙중량 2kg 초과
신고
<br />
· (무인비행선) 연료의 무게를 제외한 자체무게가 12kg 초과, 길이 7m 초과
신고 <br />
</span>
</div>
)
title: '전체',
value: '전체'
},
{
title: (
<div className='faq-q'>
<h5>
<span className='faq-icon-q'>Q</span>
<span className='ti'>
<span>[비행승인]</span>
할까?
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
</div>
</div>
),
content: (
<div className='faq-a'>
<span className='faq-icon-a'>A</span>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
</span>
</div>
)
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}
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
</div>
{user?.authId !== 'USER' && (
<div>
<Button.Ripple
color='flat-primary'
onClick={() => handlerFaqModify(i)}
>
수정하기
</Button.Ripple>
</div>
)}
</div>
),
content: (
@ -156,7 +275,10 @@ export default function FaqContainer() {
className='faq-admin-plus'
outline
color='primary'
onClick={() => setIsOpenFormModal(!isOpenFormModal)}
onClick={() => {
setFormMode('create');
setIsOpenFormModal(!isOpenFormModal);
}}
>
질문 등록하기
</Button>
@ -164,58 +286,11 @@ export default function FaqContainer() {
</div>
{/* 탭 컨텐츠 */}
<div>
<div className='tab-menu'>
<Nav pills>
<NavItem>
<NavLink
active={activeTab === '전체'}
onClick={() => toggle('전체')}
>
<span className=''>전체</span>
</NavLink>
</NavItem>
<NavItem>
<NavLink
active={activeTab === '장치신고'}
onClick={() => toggle('장치신고')}
>
<span className=''>장치 신고</span>
</NavLink>
</NavItem>
<NavItem>
<NavLink
active={activeTab === '사업등록'}
onClick={() => toggle('사업등록')}
>
<span className=''>사업 등록</span>
</NavLink>
</NavItem>
<NavItem>
<NavLink
active={activeTab === '비행승인'}
onClick={() => toggle('비행승인')}
>
<span className=''>비행 승인</span>
</NavLink>
</NavItem>
<NavItem>
<NavLink
active={activeTab === '항공촬영'}
onClick={() => toggle('항공촬영')}
>
<span className=''>항공 촬영</span>
</NavLink>
</NavItem>
<NavItem>
<NavLink
active={activeTab === '기타'}
onClick={() => toggle('기타')}
>
<span className=''>기타</span>
</NavLink>
</NavItem>
</Nav>
</div>
<FaqTab
activeTab={activeTab}
handlerChangeTab={handlerChangeTab}
tabList={tabList}
/>
<div>
<TabContent activeTab={activeTab}>
<TabPane tabId={activeTab}>
@ -223,17 +298,53 @@ export default function FaqContainer() {
<AppCollapse data={handlerFaqList()} accordion type='margin' />
</div>
</TabPane>
{/* <TabPane tabId='2'></TabPane>
<TabPane tabId='3'>사업 등록</TabPane>
<TabPane tabId='4'>비행 승인</TabPane>
<TabPane tabId='5'>항공 촬영</TabPane>
<TabPane tabId='6'>기타</TabPane> */}
</TabContent>
</div>
</div>
<FaqForm
isOpenFormModal={isOpenFormModal}
handlerOpenFormModal={handlerOpenFormModal}
tabList={tabList}
formData={formData}
handlerChangeFormData={handlerChangeFormData}
handlerSubmitForm={handlerSubmitForm}
handlerDeleteConfirmFaq={handlerDeleteConfirmFaq}
user={user}
formMode={formMode}
/>
<ErrorModal
modal={validationModal.errorType}
setModal={val => {
setValidationModal({
...validationModal,
errorType: {
...val
}
});
}}
/>
<InfoModal
modal={validationModal.successType}
setModal={val => {
setValidationModal({
...validationModal,
successType: {
...val
}
});
}}
/>
<ConfirmModal
modal={validationModal.confirmType}
setModal={val => {
setValidationModal({
...validationModal,
confirmType: {
...val
}
});
}}
handlerConfirm={handlerDeleteFaq}
/>
</div>
);

48
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
)<IFaqInsert, {}, AxiosError>();
export const UPDATE = createAsyncAction(
UPDATE_REQUEST,
UPDATE_SUCCESS,
UPDATE_FAILURE
)<IFaqUpdate, {}, AxiosError>();
export const DELETE = createAsyncAction(
DELETE_REQUEST,
DELETE_SUCCESS,
DELETE_FAILURE
)<IFaqDelete, {}, AxiosError>();
const actions = {
LIST
LIST,
INSERT,
UPDATE,
DELETE
};
export type FaqAction = ActionType<typeof actions>;

11
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}`);
}
};

31
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;

78
src/modules/cstmrService/faq/sagas/index.ts

@ -35,6 +35,84 @@ function* listSaga(action: ActionType<typeof Actions.LIST.request>) {
}
}
function* insertSaga(action: ActionType<typeof Actions.INSERT.request>) {
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<typeof Actions.UPDATE.request>) {
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<typeof Actions.DELETE.request>) {
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);
}

Loading…
Cancel
Save