diff --git a/.env.development b/.env.development index 6962a04b..d399d909 100644 --- a/.env.development +++ b/.env.development @@ -4,7 +4,8 @@ # REACT_APP_IMAGE_HOST = https://palnet-file.s3.ap-northeast-2.amazonaws.com/ REACT_APP_HOST = http://211.253.11.189:8080/ -REACT_APP_WS_HOST = ws://211.253.11.189:8081/ws +# REACT_APP_WS_HOST = ws://211.253.11.189:8081/ws +REACT_APP_WS_HOST = ws://pav.palntour.com:8081/ws REACT_APP_IMAGE_HOST = https://palnet-file.s3.ap-northeast-2.amazonaws.com/ # Naver Search API HOST diff --git a/src/components/cstmrService/faq/FaqForm.js b/src/components/cstmrService/faq/FaqForm.js index dcb77980..d5ca8abf 100644 --- a/src/components/cstmrService/faq/FaqForm.js +++ b/src/components/cstmrService/faq/FaqForm.js @@ -11,7 +11,6 @@ import { Label, CustomInput } from 'reactstrap'; -import classnames from 'classnames'; export default function FaqForm({ isOpenFormModal, diff --git a/src/components/cstmrService/inquiry/QnaDetail.js b/src/components/cstmrService/inquiry/QnaDetail.js new file mode 100644 index 00000000..e4704ae3 --- /dev/null +++ b/src/components/cstmrService/inquiry/QnaDetail.js @@ -0,0 +1,207 @@ +import { + Button, + Input, + Modal, + ModalHeader, + ModalBody, + ModalFooter, + Row, + Col, + FormGroup, + Label, + CustomInput +} from 'reactstrap'; + +export default function QnaDetatil({ + isDetailModal, + handlerDetailModal, + adminDetailForm, + handlerChangeDetailForm, + handlerSubmitDetailForm +}) { + return ( + + 문의내역 답변 +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + + + + {}} + /> + + + + + + + + + + + + + + + + + +
+ handlerChangeDetailForm('anserStatus', 'N')} + readOnly + /> + handlerChangeDetailForm('anserStatus', 'Y')} + readOnly + /> +
+
+ + + + + { + const { value } = e.target; + handlerChangeDetailForm('anserContent', value); + }} + /> + + +
+
+ +
+ + + +
+
+
+
+ ); +} diff --git a/src/components/cstmrService/inquiry/QnaGrid.js b/src/components/cstmrService/inquiry/QnaGrid.js index 2d95a767..fede65c7 100644 --- a/src/components/cstmrService/inquiry/QnaGrid.js +++ b/src/components/cstmrService/inquiry/QnaGrid.js @@ -1,73 +1,68 @@ import { GridDatabase } from '../../crud/grid/GridDatatable'; import { Button, Card } from 'reactstrap'; -const data = [ - { - title: '문의 1', - category: '칭찬', - writer: 'palnet', - createDt: '2023-10-19 10:00', - status: '답변 대기' - } -]; - -const columns = [ - { - id: 'sno', - name: '번호', - cell: (row, i) =>
{i + 1}
- }, - { - id: 'title', - name: '제목', - minWidth: '150px', - cell: (row, i) =>
{row.title}
- }, - { - id: 'category', - name: '문의 유형', - minWidth: '200px', - cell: row => { - return
{row.category}
; +export default function QnaGrid({ lists, handlerDetailModal }) { + const columns = [ + { + id: 'sno', + name: '번호', + maxWidth: '50px', + cell: (row, i) =>
{i + 1}
+ }, + { + id: 'title', + name: '제목', + minWidth: '300px', + cell: (row, i) =>
{row.title}
+ }, + { + id: 'category', + name: '문의 유형', + minWidth: '100px', + cell: row => { + return
{row.category}
; + } + }, + { + id: 'createUserNm', + name: '작성자', + minWidth: '100px', + cell: row =>
{row.createUserNm}
+ }, + { + id: 'createDt', + name: '작성 일시', + cell: row =>
{row.createDt}
+ }, + { + id: 'anserStatus', + name: '답변 상태', + minWidth: '50px', + cell: row =>
{row.anserStatus}
+ }, + { + id: '', + name: '', + minWidth: '150px', + cell: row => ( + { + handlerDetailModal(row.qnaSno); + }} + > + 상세보기 + + ) } - }, - { - id: 'writer', - name: '작성자', - minWidth: '200px', - cell: row =>
{row.writer}
- }, - { id: 'createDt', name: '작성 일시', cell: row =>
{row.createDt}
}, - { - id: 'status', - name: '답변 상태', - cell: row =>
{row.status}
- }, - { - id: '', - name: '', - cell: row => ( - { - location.href = '/cstmrService/admininquirywrite'; - }} - > - 상세보기 - - ) - } -]; + ]; -export default function QnaGrid() { return ( <> -
+

문의 목록

- 검색결과 총 1건 + 검색결과 총 {lists.length}건
@@ -76,10 +71,10 @@ export default function QnaGrid() {
diff --git a/src/components/cstmrService/inquiry/QnaSearchBox.js b/src/components/cstmrService/inquiry/QnaSearchBox.js index 4868c9ae..b9603478 100644 --- a/src/components/cstmrService/inquiry/QnaSearchBox.js +++ b/src/components/cstmrService/inquiry/QnaSearchBox.js @@ -1,9 +1,45 @@ import { Button, Card, CardBody, Col, Row, Input } from 'reactstrap'; import { Search } from 'react-feather'; -import Flatpickr from 'react-flatpickr'; -import moment from 'moment'; -export default function QnaSearchBox() { +const categoryList = [ + { + title: '칭찬', + value: '칭찬' + }, + { + title: '불만', + value: '불만' + }, + { + title: '문의', + value: '문의' + }, + { + title: '제안', + value: '제안' + }, + { + title: '기타', + value: '기타' + } +]; + +const anserStatusList = [ + { + title: '답변 대기', + value: 'N' + }, + { + title: '답변 완료', + value: 'Y' + } +]; + +export default function QnaSearchBox({ + searchData, + handlerChangeSearchData, + handlerSubmitSearch +}) { return (
@@ -13,7 +49,11 @@ export default function QnaSearchBox() {

검색조건

- {}}> + 검색 @@ -33,17 +73,17 @@ export default function QnaSearchBox() { { const { value } = e.target; - // handlerChangeFormData('category', value); + handlerChangeSearchData('category', value); }} > - - - - - + {categoryList.map(i => ( + + ))} @@ -61,14 +101,17 @@ export default function QnaSearchBox() { { const { value } = e.target; - // handlerChangeFormData('category', value); + handlerChangeSearchData('anserStatus', value); }} > - - + {anserStatusList.map(i => ( + + ))} @@ -86,10 +129,13 @@ export default function QnaSearchBox() { { const { value } = e.target; - // handlerChangeFormData('category', value); + handlerChangeSearchData( + 'createUserNm', + value + ); }} /> diff --git a/src/containers/cstmrService/faq/FaqContainer.js b/src/containers/cstmrService/faq/FaqContainer.js index d797b7e7..6a5e0afa 100644 --- a/src/containers/cstmrService/faq/FaqContainer.js +++ b/src/containers/cstmrService/faq/FaqContainer.js @@ -242,7 +242,10 @@ export default function FaqContainer() {
handlerFaqModify(i)} + onClick={e => { + e.stopPropagation(); + handlerFaqModify(i); + }} > 수정하기 diff --git a/src/containers/cstmrService/inquiry/AdminInquiryContainer.js b/src/containers/cstmrService/inquiry/AdminInquiryContainer.js index 4d9c095e..901eac78 100644 --- a/src/containers/cstmrService/inquiry/AdminInquiryContainer.js +++ b/src/containers/cstmrService/inquiry/AdminInquiryContainer.js @@ -1,16 +1,99 @@ -import { Button, Card, CardBody, Col, Row, Input } from 'reactstrap'; +import { useEffect, useState, useCallback } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { Col, Row } from 'reactstrap'; +import { + ADMIN_LIST, + ADMIN_DETAIL +} from '../../../modules/cstmrService/inquiry/action'; import QnaSearchBox from '../../../components/cstmrService/inquiry/QnaSearchBox'; import QnaGrid from '../../../components/cstmrService/inquiry/QnaGrid'; +import QnaDetail from '../../../components/cstmrService/inquiry/QnaDetail'; + +const initalSearch = { + category: '칭찬', + anserStatus: 'N', + createUserNm: '' +}; export default function AdminInquiryContainer() { + const [searchData, setSearchData] = useState(initalSearch); + const [isDetailModal, setIsDetailModal] = useState(false); + const [adminDetailForm, setAdminDetailForm] = useState({}); + const { adminList: lists, adminDetail } = useSelector( + state => state.qnaState + ); + const dispatch = useDispatch(); + + useEffect(() => { + dispatch( + ADMIN_LIST.request({ category: '', anserStatus: '', createUserNm: '' }) + ); + }, []); + + useEffect(() => { + setAdminDetailForm({ ...adminDetail }); + }, [adminDetail]); + + // 관리자 문의 목록 조회 + const handlerGetQnaList = () => { + const { category, anserStatus, createUserNm } = searchData; + dispatch(ADMIN_LIST.request({ category, anserStatus, createUserNm })); + }; + + // 검색 조건 변경 handler + const handlerChangeSearchData = useCallback( + (type, val) => { + setSearchData({ ...searchData, [type]: val }); + }, + [searchData] + ); + + // 검색 버튼 클릭 hancler + const handlerSubmitSearch = () => { + handlerGetQnaList(); + }; + + // 상세보기 Form Modal handler + const handlerDetailModal = qnaSno => { + if (!isDetailModal) { + dispatch(ADMIN_DETAIL.request(qnaSno)); + } + setIsDetailModal(!isDetailModal); + }; + + // 상세보기 Form value change handler + const handlerChangeDetailForm = useCallback( + (type, val) => { + setAdminDetailForm({ ...adminDetailForm, [type]: val }); + }, + [adminDetailForm] + ); + + // 상세보기 수정 + const handlerSubmitDetailForm = () => { + console.log('수정'); + }; + return ( - + - + + + ); } diff --git a/src/modules/cstmrService/inquiry/action/index.ts b/src/modules/cstmrService/inquiry/action/index.ts index 031e8dc3..e80a7076 100644 --- a/src/modules/cstmrService/inquiry/action/index.ts +++ b/src/modules/cstmrService/inquiry/action/index.ts @@ -1,21 +1,39 @@ import { AxiosError } from 'axios'; import { createAsyncAction, ActionType, createAction } from 'typesafe-actions'; -import { IQnaAdminSearch, IQnaAdminList } from '../model'; +import { IQnaAdminSearch, IQnaAdminList, IQnaAdminDetail } from '../model'; -// 목록 조회 +// 관리자 목록 조회 const ADMIN_LIST_REQUEST = 'cstmrService/qna/ADMIN_LIST_REQUEST'; const ADMIN_LIST_SUCCESS = 'cstmrService/qna/ADMIN_LIST_SUCCESS'; const ADMIN_LIST_FAILURE = 'cstmrService/qna/ADMIN_LIST_FAILURE'; +// 관리자 문의 상세보기 +const ADMIN_DETAIL_REQUEST = 'cstmrService/qna/ADMIN_DETAIL_REQUEST'; +const ADMIN_DETAIL_SUCCESS = 'cstmrService/qna/ADMIN_DETAIL_SUCCESS'; +const ADMIN_DETAIL_FAILURE = 'cstmrService/qna/ADMIN_DETAIL_FAILURE'; + +// 관리자 문의 상세보기 초기화 +const ADMIN_DETAIL_INITAL_ACTION = 'cstmrService/qna/ADMIN_DETAIL_INITAL'; + export const ADMIN_LIST = createAsyncAction( ADMIN_LIST_REQUEST, ADMIN_LIST_SUCCESS, ADMIN_LIST_FAILURE )(); +export const ADMIN_DETAIL = createAsyncAction( + ADMIN_DETAIL_REQUEST, + ADMIN_DETAIL_SUCCESS, + ADMIN_DETAIL_FAILURE +)(); + +export const ADMIN_DETAIL_INITAL = createAction(ADMIN_DETAIL_INITAL_ACTION)(); + const actions = { - ADMIN_LIST + ADMIN_LIST, + ADMIN_DETAIL, + ADMIN_DETAIL_INITAL }; export type QnaAction = ActionType; diff --git a/src/modules/cstmrService/inquiry/apis/index.ts b/src/modules/cstmrService/inquiry/apis/index.ts index 4d40fd4e..1ad7ff90 100644 --- a/src/modules/cstmrService/inquiry/apis/index.ts +++ b/src/modules/cstmrService/inquiry/apis/index.ts @@ -1,11 +1,26 @@ import axios from '../../../utils/customAxiosUtil'; +import qs from 'qs'; export const qnaAPI = { - adminList: async ({ category, searchType, word }) => { - // const url = - // category === '전체' - // ? `api/bas/cns/faq?word=${word}` - // : `api/bas/cns/faq?category=${category}&word=${word}`; - return await axios.get(``); + adminList: async (data: { + category: string; + anserStatus: string; + createUserNm: string; + }) => { + 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${queryString}`); + }, + adminDetail: async (qnaSno: number) => { + return await axios.get(`api/cns/qna/${qnaSno}`); } }; diff --git a/src/modules/cstmrService/inquiry/model/index.ts b/src/modules/cstmrService/inquiry/model/index.ts index 821c1d6f..3e8c6c45 100644 --- a/src/modules/cstmrService/inquiry/model/index.ts +++ b/src/modules/cstmrService/inquiry/model/index.ts @@ -1,11 +1,76 @@ export interface IQnaState { - adminList: []; + adminList: IQnaAdminList[]; + adminDetail: IQnaAdminDetail; } -export interface IQnaAdminList {} +export interface IQnaAdminList { + qnaSno: number; + targetSno: number; + category: string; + title: string; + content: string; + expsrYn: string; + viewCnt: number; + createUserNm: string; + createUserId: string; + createDt: string; + updateUserId: string; + updateDt: string; +} export interface IQnaAdminSearch { category: string; - searchType: string; - word: string; + anserStatus: string; + createUserNm: string; +} + +export interface IQnaFiles { + fileSno: number; + downloadUrl: string; + fileName: string; +} + +export interface IQnaAdminDetail { + qnaSno: number; + targetSno: number; + category: string; + title: string; + content: string; + anserContent: string; + anserStatus: string; + anserProcDt: string; + anserUserNm: string; + viewCnt: number; + fileGroupNo: number; + expsrYn: string; + createUserNm: string; + createUserId: string; + createDt: string; + updateUserId: string; + updateDt: string; + files: IQnaFiles[]; } + +export const initalState = { + adminList: [], + adminDetail: { + qnaSno: 0, + targetSno: 0, + category: '', + title: '', + content: '', + anserContent: '', + anserStatus: '', + anserProcDt: '', + anserUserNm: '', + viewCnt: 0, + fileGroupNo: 0, + expsrYn: '', + createUserNm: '', + createUserId: '', + createDt: '', + updateUserId: '', + updateDt: '', + files: [] + } +}; diff --git a/src/modules/cstmrService/inquiry/reducers/index.ts b/src/modules/cstmrService/inquiry/reducers/index.ts index e515374d..d8475dcc 100644 --- a/src/modules/cstmrService/inquiry/reducers/index.ts +++ b/src/modules/cstmrService/inquiry/reducers/index.ts @@ -1,15 +1,28 @@ import { createReducer } from 'typesafe-actions'; import produce from 'immer'; import * as Actions from '../action'; -import { IQnaState } from '../model'; +import { IQnaState, initalState } from '../model'; -export const faqReducer = createReducer({ - adminList: [] -}) - // 목록 +export const qnaReducer = createReducer( + initalState +) + // 관리자 목록 .handleAction(Actions.ADMIN_LIST.success, (state, action) => produce(state, draft => { const data = action.payload; - // draft.adminList = data; + draft.adminList = data || []; + }) + ) + // 관리자 상세 + .handleAction(Actions.ADMIN_DETAIL.success, (state, action) => + produce(state, draft => { + const data = action.payload; + draft.adminDetail = { ...state.adminDetail, ...data }; + }) + ) + // 관리자 상세 초기화 + .handleAction(Actions.ADMIN_DETAIL_INITAL, (state, action) => + produce(state, draft => { + draft.adminDetail = state.adminDetail; }) ); diff --git a/src/modules/cstmrService/inquiry/sagas/index.ts b/src/modules/cstmrService/inquiry/sagas/index.ts index 73408a67..50b14c28 100644 --- a/src/modules/cstmrService/inquiry/sagas/index.ts +++ b/src/modules/cstmrService/inquiry/sagas/index.ts @@ -35,6 +35,34 @@ function* adminListSaga(action: ActionType) { } } -export function* faqSaga() { +function* adminDetailSaga( + action: ActionType +) { + try { + const payload = action.payload; + const res = yield call(Apis.qnaAPI.adminDetail, payload); + const { data, count, errorCode } = res; + + if (errorCode) { + // 오류메시지 호출 + yield put( + MessageActions.IS_ERROR({ + errorCode: errorCode, + errorMessage: '처리중 오류가 발생하였습니다', + isHistoryBack: false, + isRefresh: false + }) + ); + + return; + } + yield put(Actions.ADMIN_DETAIL.success(data)); + } catch (error) { + yield put(Actions.ADMIN_DETAIL.failure(error)); + } +} + +export function* qnaSaga() { yield takeEvery(Actions.ADMIN_LIST.request, adminListSaga); + yield takeEvery(Actions.ADMIN_DETAIL.request, adminDetailSaga); } diff --git a/src/redux/reducers/rootReducer.ts b/src/redux/reducers/rootReducer.ts index d61ff476..3a913c97 100644 --- a/src/redux/reducers/rootReducer.ts +++ b/src/redux/reducers/rootReducer.ts @@ -48,6 +48,8 @@ import { laancSaga } from '../../modules/laanc/sagas/laancSagas'; import laancReducer from '../../modules/laanc/reducers/laancReducers'; import { faqSaga } from '../../modules/cstmrService/faq/sagas'; import { faqReducer } from '../../modules/cstmrService/faq/reducers'; +import { qnaSaga } from '../../modules/cstmrService/inquiry/sagas'; +import { qnaReducer } from '../../modules/cstmrService/inquiry/reducers'; export interface StoreState { controlGpState: ControlGpState; @@ -66,6 +68,7 @@ export function* saga() { yield all([fork(findSaga)]); yield all([fork(laancSaga)]); yield all([fork(faqSaga)]); + yield all([fork(qnaSaga)]); } const rootReducer = combineReducers({ @@ -96,7 +99,8 @@ const rootReducer = combineReducers({ ControlGpWeatherState: controlGpReducer, flightState: flightReducer, findState: findAccountReducer, - faqState: faqReducer + faqState: faqReducer, + qnaState: qnaReducer }); export default rootReducer;