김장현
11 months ago
13 changed files with 585 additions and 108 deletions
@ -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 ( |
||||
<Modal |
||||
isOpen={isDetailModal} |
||||
toggle={handlerDetailModal} |
||||
className='modal-dialog-centered modal-xl faq-modal' |
||||
> |
||||
<ModalHeader toggle={handlerDetailModal}>문의내역 답변</ModalHeader> |
||||
<div className='pal-form'> |
||||
<ModalBody> |
||||
<Row> |
||||
<Col className='list-input' md='4'> |
||||
<FormGroup> |
||||
<Label for='test'>작성자</Label> |
||||
<Input |
||||
type='text' |
||||
bsSize='sm' |
||||
placeholder='' |
||||
value={adminDetailForm.createUserNm} |
||||
disabled |
||||
/> |
||||
</FormGroup> |
||||
</Col> |
||||
<Col className='list-input' md='8'> |
||||
<FormGroup> |
||||
<Label for='test'> |
||||
<span className='necessary'></span>제목 |
||||
</Label> |
||||
<Input |
||||
type='text' |
||||
bsSize='sm' |
||||
placeholder='' |
||||
disabled |
||||
value={adminDetailForm.title} |
||||
/> |
||||
</FormGroup> |
||||
</Col> |
||||
</Row> |
||||
<Row> |
||||
<Col className='list-input' md='4'> |
||||
<FormGroup className='feedback-input'> |
||||
<Label for='test'> |
||||
<span className='necessary'></span>연락처 |
||||
</Label> |
||||
<Input |
||||
type='text' |
||||
bsSize='sm' |
||||
placeholder='' |
||||
value='010-1111-1111' |
||||
disabled |
||||
/> |
||||
</FormGroup> |
||||
</Col> |
||||
<Col className='list-input' md='4'> |
||||
<FormGroup> |
||||
<Label for='test'>문의 유형</Label> |
||||
<Input |
||||
type='text' |
||||
bsSize='sm' |
||||
placeholder='' |
||||
value={adminDetailForm.category} |
||||
disabled |
||||
/> |
||||
</FormGroup> |
||||
</Col> |
||||
<Col className='list-input' md='4'> |
||||
<FormGroup> |
||||
<Label for='test'>첨부 파일</Label> |
||||
<Input |
||||
type='text' |
||||
bsSize='sm' |
||||
placeholder='' |
||||
value='파일명.jpg' |
||||
disabled |
||||
/> |
||||
<div className='user-phone-btn'> |
||||
<Button size='sm' type='button' color='primary'> |
||||
<span className='d-sm-inline-block'>다운로드</span> |
||||
</Button> |
||||
</div> |
||||
</FormGroup> |
||||
</Col> |
||||
|
||||
<Col className='list-input' md='12'> |
||||
<FormGroup className='feedback-input'> |
||||
<Label for='test'> |
||||
<span className='necessary'></span>내용 |
||||
</Label> |
||||
<Input |
||||
className='faq-textarea' |
||||
type='textarea' |
||||
placeholder='' |
||||
disabled |
||||
value={adminDetailForm.content} |
||||
onChange={e => {}} |
||||
/> |
||||
</FormGroup> |
||||
</Col> |
||||
<Col className='list-input' md='4'> |
||||
<FormGroup className='feedback-input'> |
||||
<Label for='test'> |
||||
<span className='necessary'></span>답변자 |
||||
</Label> |
||||
<Input |
||||
type='text' |
||||
bsSize='sm' |
||||
placeholder='' |
||||
value={adminDetailForm.anserUserNm} |
||||
disabled |
||||
/> |
||||
</FormGroup> |
||||
</Col> |
||||
<Col className='list-input' md='4'> |
||||
<FormGroup> |
||||
<Label for='test'>답변 일자</Label> |
||||
<Input |
||||
type='text' |
||||
bsSize='sm' |
||||
placeholder='' |
||||
value={adminDetailForm.anserProcDt} |
||||
disabled |
||||
/> |
||||
</FormGroup> |
||||
</Col> |
||||
<Col className='list-input' md='4'> |
||||
<FormGroup> |
||||
<Label for='test'> |
||||
<span className='necessary'></span>답변 상태 |
||||
</Label> |
||||
<div className='input-radio-inline'> |
||||
<CustomInput |
||||
type='radio' |
||||
id='exampleCustomRadio' |
||||
name='customRadio' |
||||
inline |
||||
label='답변대기' |
||||
checked={adminDetailForm.anserStatus === 'N'} |
||||
onClick={() => handlerChangeDetailForm('anserStatus', 'N')} |
||||
readOnly |
||||
/> |
||||
<CustomInput |
||||
type='radio' |
||||
id='exampleCustomRadio2' |
||||
name='customRadio' |
||||
inline |
||||
label='답변완료' |
||||
checked={adminDetailForm.anserStatus === 'Y'} |
||||
onClick={() => handlerChangeDetailForm('anserStatus', 'Y')} |
||||
readOnly |
||||
/> |
||||
</div> |
||||
</FormGroup> |
||||
</Col> |
||||
<Col className='list-input' md='12'> |
||||
<FormGroup className='feedback-input'> |
||||
<Label for='test'> |
||||
<span className='necessary'></span>답변 내용 |
||||
</Label> |
||||
<Input |
||||
// className='faq-textarea'
|
||||
type='textarea' |
||||
placeholder='' |
||||
value={adminDetailForm.anserContent} |
||||
onChange={e => { |
||||
const { value } = e.target; |
||||
handlerChangeDetailForm('anserContent', value); |
||||
}} |
||||
/> |
||||
</FormGroup> |
||||
</Col> |
||||
</Row> |
||||
</ModalBody> |
||||
<ModalFooter> |
||||
<div className='pal-form-btn'> |
||||
<Button color='danger'>삭제</Button> |
||||
<Button color='secondary' onClick={handlerDetailModal}> |
||||
취소 |
||||
</Button> |
||||
<Button color='primary' onClick={handlerSubmitDetailForm}> |
||||
저장 |
||||
</Button> |
||||
</div> |
||||
</ModalFooter> |
||||
</div> |
||||
</Modal> |
||||
); |
||||
} |
@ -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 ( |
||||
<Row> |
||||
<Col sm='12' lg='12'> |
||||
<QnaSearchBox /> |
||||
<QnaSearchBox |
||||
searchData={searchData} |
||||
handlerChangeSearchData={handlerChangeSearchData} |
||||
handlerSubmitSearch={handlerSubmitSearch} |
||||
/> |
||||
</Col> |
||||
<Col sm='12' lg='12'> |
||||
<QnaGrid /> |
||||
<QnaGrid lists={lists} handlerDetailModal={handlerDetailModal} /> |
||||
</Col> |
||||
|
||||
<QnaDetail |
||||
isDetailModal={isDetailModal} |
||||
handlerDetailModal={handlerDetailModal} |
||||
adminDetailForm={adminDetailForm} |
||||
handlerChangeDetailForm={handlerChangeDetailForm} |
||||
handlerSubmitDetailForm={handlerSubmitDetailForm} |
||||
/> |
||||
</Row> |
||||
); |
||||
} |
||||
|
@ -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 |
||||
)<IQnaAdminSearch, IQnaAdminList[], AxiosError>(); |
||||
|
||||
export const ADMIN_DETAIL = createAsyncAction( |
||||
ADMIN_DETAIL_REQUEST, |
||||
ADMIN_DETAIL_SUCCESS, |
||||
ADMIN_DETAIL_FAILURE |
||||
)<number, IQnaAdminDetail, AxiosError>(); |
||||
|
||||
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<typeof actions>; |
||||
|
@ -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}`); |
||||
} |
||||
}; |
||||
|
@ -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: [] |
||||
} |
||||
}; |
||||
|
@ -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<IQnaState, Actions.QnaAction>({ |
||||
adminList: [] |
||||
}) |
||||
// 목록
|
||||
export const qnaReducer = createReducer<IQnaState, Actions.QnaAction>( |
||||
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; |
||||
}) |
||||
); |
||||
|
Loading…
Reference in new issue