Browse Source

QNA 사용자 문의 목록 api 연결

pull/2/head
hhjk00 11 months ago
parent
commit
83af51c473
  1. 519
      src/containers/cstmrService/inquiry/UserInquiryContainer.js
  2. 18
      src/modules/cstmrService/inquiry/action/index.ts
  3. 15
      src/modules/cstmrService/inquiry/apis/index.ts
  4. 26
      src/modules/cstmrService/inquiry/model/index.ts
  5. 7
      src/modules/cstmrService/inquiry/reducers/index.ts
  6. 26
      src/modules/cstmrService/inquiry/sagas/index.ts

519
src/containers/cstmrService/inquiry/UserInquiryContainer.js

@ -1,465 +1,83 @@
import { Button, Badge } from 'reactstrap';
import AppCollapse from '@components/app-collapse';
import { useDispatch, useSelector } from 'react-redux';
import { USER_LIST } from '../../../modules/cstmrService/inquiry/action';
import { useEffect } from 'react';
import moment from 'moment';
const data = [
{
title: (
<div className='faq-q'>
<h5>
<Badge color='light-secondary'>답변대기</Badge>
<span className='ti'>
<span>[불만]</span> ?
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
<span className='date'>2022-02-02</span>
</div>
</div>
),
content: (
<div className='faq-a'>
<span className='faq-a-text'>
{/* br처리? 줄바꿈.. 추후에 생각 */}
그렇습니다.
<br />
<br />
- 사용용도가 영리 목적인 경우 : 무게에 상관없이 모두 신고
<br />
- 사용용도가 비영리 목적인 경우
<br />
· (무인멀티콥터, 무인비행기, 무인헬리콥터) 최대이륙중량 2kg 초과
신고
<br />
· (무인비행선) 연료의 무게를 제외한 자체무게가 12kg 초과, 길이 7m 초과
신고 <br />
</span>
</div>
)
},
{
title: (
<div className='faq-q'>
<h5>
<Badge color='light-primary'>답변완료</Badge>
<span className='ti'>
<span>[문의]</span> ?
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
<span className='date'>2022-02-02</span>
</div>
</div>
),
content: (
<div className='faq-a'>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
</span>
</div>
)
},
{
title: (
<div className='faq-q'>
<h5>
<Badge color='light-primary'>답변완료</Badge>
<span className='ti'>
<span>[문의]</span> ?
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
<span className='date'>2022-02-02</span>
</div>
</div>
),
content: (
<div className='faq-a'>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
</span>
</div>
)
},
{
title: (
<div className='faq-q'>
<h5>
<Badge color='light-primary'>답변완료</Badge>
<span className='ti'>
<span>[문의]</span> ?
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
<span className='date'>2022-02-02</span>
</div>
</div>
),
content: (
<div className='faq-a'>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
</span>
</div>
)
},
{
title: (
<div className='faq-q'>
<h5>
<Badge color='light-primary'>답변완료</Badge>
<span className='ti'>
<span>[문의]</span> ?
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
<span className='date'>2022-02-02</span>
</div>
</div>
),
content: (
<div className='faq-a'>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
</span>
</div>
)
},
{
title: (
<div className='faq-q'>
<h5>
<Badge color='light-primary'>답변완료</Badge>
<span className='ti'>
<span>[문의]</span> ?
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
<span className='date'>2022-02-02</span>
</div>
</div>
),
content: (
<div className='faq-a'>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
</span>
</div>
)
},
{
title: (
<div className='faq-q'>
<h5>
<Badge color='light-primary'>답변완료</Badge>
<span className='ti'>
<span>[문의]</span> ?
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
<span className='date'>2022-02-02</span>
</div>
</div>
),
content: (
<div className='faq-a'>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
</span>
</div>
)
},
{
title: (
<div className='faq-q'>
<h5>
<Badge color='light-primary'>답변완료</Badge>
<span className='ti'>
<span>[문의]</span> ?
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
<span className='date'>2022-02-02</span>
</div>
</div>
),
content: (
<div className='faq-a'>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
</span>
</div>
)
},
{
title: (
<div className='faq-q'>
<h5>
<Badge color='light-primary'>답변완료</Badge>
<span className='ti'>
<span>[문의]</span> ?
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
<span className='date'>2022-02-02</span>
</div>
</div>
),
content: (
<div className='faq-a'>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
</span>
</div>
)
},
{
title: (
<div className='faq-q'>
<h5>
<Badge color='light-primary'>답변완료</Badge>
<span className='ti'>
<span>[문의]</span> ?
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
<span className='date'>2022-02-02</span>
</div>
</div>
),
content: (
<div className='faq-a'>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
</span>
</div>
)
},
{
title: (
<div className='faq-q'>
<h5>
<Badge color='light-primary'>답변완료</Badge>
<span className='ti'>
<span>[문의]</span> ?
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
<span className='date'>2022-02-02</span>
</div>
</div>
),
content: (
<div className='faq-a'>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
</span>
</div>
)
},
{
title: (
<div className='faq-q'>
<h5>
<Badge color='light-primary'>답변완료</Badge>
<span className='ti'>
<span>[문의]</span> ?
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
<span className='date'>2022-02-02</span>
</div>
</div>
),
content: (
<div className='faq-a'>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
</span>
</div>
)
},
{
title: (
<div className='faq-q'>
<h5>
<Badge color='light-primary'>답변완료</Badge>
<span className='ti'>
<span>[문의]</span> ?
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
<span className='date'>2022-02-02</span>
</div>
</div>
),
content: (
<div className='faq-a'>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
</span>
</div>
)
},
{
export default function UserInquiryContainer() {
const dispatch = useDispatch();
const { userList: lists } = useSelector(state => state.qnaState);
const textLineBreaks = text => {
return text.replace((/\n/g, '<br>'));
};
const handlerQnaList = () => {
return lists.map(i => {
return {
title: (
<div className='faq-q'>
<h5>
{i.anserStatus === 'Y' ? (
<Badge color='light-primary'>답변완료</Badge>
) : (
<Badge color='light-secondary'>답변대기</Badge>
)}
<span className='ti'>
<span>[문의]</span> ?
<span>[{i.category}]</span>
{i.title}
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
<span className='date'>2022-02-02</span>
</div>
</div>
),
content: (
<div className='faq-a'>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
</span>
</div>
)
},
{
title: (
<div className='faq-q'>
<h5>
<Badge color='light-primary'>답변완료</Badge>
<span className='ti'>
<span>[문의]</span> ?
<span className='date'>
{moment(i.createDt).format('YYYY-MM-DD')}
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
<span className='date'>2022-02-02</span>
</div>
</div>
),
content: (
<>
<div className='faq-a'>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
</span>
</div>
)
},
{
title: (
<div className='faq-q'>
<h5>
<Badge color='light-primary'>답변완료</Badge>
<span className='ti'>
<span>[문의]</span> ?
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
<span className='date'>2022-02-02</span>
</div>
</div>
),
content: (
<div className='faq-a'>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
</span>
</div>
)
},
{
title: (
<div className='faq-q'>
<h5>
<Badge color='light-primary'>답변완료</Badge>
<span className='ti'>
<span>[문의]</span> ?
<span className='faq-icon-q'>Q</span>
<span
className='faq-a-text'
dangerouslySetInnerHTML={{ __html: textLineBreaks(i.content) }}
>
{/* br처리? 줄바꿈.. 추후에 생각 */}
</span>
</h5>
<div>
<Button.Ripple color='flat-primary'>수정하기</Button.Ripple>
<span className='date'>2022-02-02</span>
</div>
</div>
),
content: (
{i.anserStatus === 'Y' && (
<>
<br />
<div className='faq-a'>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
</span>
<span className='faq-icon-a'>A</span>
<span
className='faq-a-text'
dangerouslySetInnerHTML={{
__html: textLineBreaks(i.anserContent)
}}
></span>
</div>
</>
)}
</>
)
},
};
});
};
const data = [
{
title: (
<div className='faq-q'>
<h5>
<Badge color='light-primary'>답변완료</Badge>
<Badge color='light-secondary'>답변대기</Badge>
<span className='ti'>
<span>[문의]</span> ?
<span>[불만]</span> ?
</span>
</h5>
<div>
@ -471,10 +89,19 @@ const data = [
content: (
<div className='faq-a'>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
{/* br처리? 줄바꿈.. 추후에 생각 */}
그렇습니다.
<br />
<br />
- 사용용도가 영리 목적인 경우 : 무게에 상관없이 모두 신고
<br />
- 사용용도가 비영리 목적인 경우
<br />
· (무인멀티콥터, 무인비행기, 무인헬리콥터) 최대이륙중량 2kg 초과
신고
<br />
· (무인비행선) 연료의 무게를 제외한 자체무게가 12kg 초과, 길이 7m
초과 신고 <br />
</span>
</div>
)
@ -497,20 +124,24 @@ const data = [
content: (
<div className='faq-a'>
<span className='faq-a-text'>
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로 하지
않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도 비행이
가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할 우려가
없도록 주의하여 비행하여야 합니다.
사방/천장이 막혀있는 밀폐된 실내 공간에서의 비행은 승인을 필요로
하지 않습니다. 또한, 적절한 조명장치가 있는 실내 공간이라면 야간에도
비행이 가능합니다. 다면 어떠한 경우에도 인명과 재산에 위험을 초래할
우려가 없도록 주의하여 비행하여야 합니다.
</span>
</div>
)
}
];
export default function UserInquiryContainer() {
useEffect(() => {
dispatch(USER_LIST.request({ category: '', searchType: '', word: '' }));
}, []);
return (
<div className='faq'>
<div className='my-faq'>
<AppCollapse data={data} accordion type='margin' />
<AppCollapse data={handlerQnaList()} accordion type='margin' />
</div>
<div className='inquiry-btn'>
{/* 링크 연결 임시 하드코딩 */}

18
src/modules/cstmrService/inquiry/action/index.ts

@ -6,7 +6,9 @@ import {
IQnaAdminList,
IQnaAdminDetail,
IQnaAdminAnswer,
IQnaAdminFileDown
IQnaAdminFileDown,
IQnaUserList,
IQnaUserSearch
} from '../model';
// 관리자 목록 조회
@ -37,6 +39,11 @@ const ADMIN_DELETE_REQUEST = 'cstmrService/qna/ADMIN_DELETE_REQUEST';
const ADMIN_DELETE_SUCCESS = 'cstmrService/qna/ADMIN_DELETE_SUCCESS';
const ADMIN_DELETE_FAILURE = 'cstmrService/qna/ADMIN_DELETE_FAILURE';
// 사용자 목록 조회
const USER_LIST_REQUEST = 'cstmrService/qna/USER_LIST_REQUEST';
const USER_LIST_SUCCESS = 'cstmrService/qna/USER_LIST_SUCCESS';
const USER_LIST_FAILURE = 'cstmrService/qna/USER_LIST_FAILURE';
export const ADMIN_LIST = createAsyncAction(
ADMIN_LIST_REQUEST,
ADMIN_LIST_SUCCESS,
@ -78,13 +85,20 @@ export const ADMIN_DELETE = createAsyncAction(
AxiosError
>();
export const USER_LIST = createAsyncAction(
USER_LIST_REQUEST,
USER_LIST_SUCCESS,
USER_LIST_FAILURE
)<IQnaUserSearch, IQnaUserList[], AxiosError>();
const actions = {
ADMIN_LIST,
ADMIN_DETAIL,
ADMIN_DETAIL_INITAL,
ADMIN_ANSWER,
ADMIN_FILE_DOWN,
ADMIN_DELETE
ADMIN_DELETE,
USER_LIST
};
export type QnaAction = ActionType<typeof actions>;

15
src/modules/cstmrService/inquiry/apis/index.ts

@ -1,5 +1,6 @@
import axios from '../../../utils/customAxiosUtil';
import qs from 'qs';
import { IQnaUserSearch } from '../model';
export const qnaAPI = {
adminList: async (data: {
@ -46,5 +47,19 @@ export const qnaAPI = {
},
adminDelete: async (qnaSno: number) => {
return await axios.delete(`api/cns/qna/${qnaSno}`);
},
userList: async (data: IQnaUserSearch) => {
const params = {};
Object.keys(data).forEach(i => {
if (data[i]) {
params[`${i}`] = data[i];
}
});
const queryString = qs.stringify(params, {
addQueryPrefix: true,
arrayFormat: 'repeat'
});
return await axios.get(`api/cns/qna/user${queryString}`);
}
};

26
src/modules/cstmrService/inquiry/model/index.ts

@ -1,6 +1,7 @@
export interface IQnaState {
adminList: IQnaAdminList[];
adminDetail: IQnaAdminDetail;
userList: IQnaUserList[];
}
export interface IQnaAdminList {
@ -66,6 +67,28 @@ export interface IQnaAdminFileDown {
fileSno: number;
}
export interface IQnaUserList {
qnaSno: number;
targetSno: number;
category: string;
title: string;
content: string;
anserStatus: string;
expsrYn: string;
viewCnt: number;
createUserNm: string;
createUserId: string;
createDt: string;
updateUserId: string;
updateDt: string;
}
export interface IQnaUserSearch {
category: string;
searchType: string;
word: string;
}
export const initalState = {
adminList: [],
adminDetail: {
@ -87,5 +110,6 @@ export const initalState = {
updateUserId: '',
updateDt: '',
files: []
}
},
userList: []
};

7
src/modules/cstmrService/inquiry/reducers/index.ts

@ -25,4 +25,11 @@ export const qnaReducer = createReducer<IQnaState, Actions.QnaAction>(
produce(state, draft => {
draft.adminDetail = state.adminDetail;
})
)
// 사용자 목록
.handleAction(Actions.USER_LIST.success, (state, action) =>
produce(state, draft => {
const data = action.payload;
draft.userList = data || [];
})
);

26
src/modules/cstmrService/inquiry/sagas/index.ts

@ -158,10 +158,36 @@ function* adminDeleteSaga(
}
}
function* userListSaga(action: ActionType<typeof Actions.USER_LIST.request>) {
try {
const payload = action.payload;
const res = yield call(Apis.qnaAPI.userList, payload);
const { data, count, errorCode } = res;
if (errorCode) {
// 오류메시지 호출
yield put(
MessageActions.IS_ERROR({
errorCode: errorCode,
errorMessage: '처리중 오류가 발생하였습니다',
isHistoryBack: false,
isRefresh: false
})
);
return;
}
yield put(Actions.USER_LIST.success(data));
} catch (error) {
yield put(Actions.USER_LIST.failure(error));
}
}
export function* qnaSaga() {
yield takeEvery(Actions.ADMIN_LIST.request, adminListSaga);
yield takeEvery(Actions.ADMIN_DETAIL.request, adminDetailSaga);
yield takeEvery(Actions.ADMIN_ANSWER.request, adminAnswerSaga);
yield takeEvery(Actions.ADMIN_FILE_DOWN.request, adminFileDownSaga);
yield takeEvery(Actions.ADMIN_DELETE.request, adminDeleteSaga);
yield takeEvery(Actions.USER_LIST.request, userListSaga);
}

Loading…
Cancel
Save