diff --git a/src/components/cstmrService/inquiry/InquiryWrite.js b/src/components/cstmrService/inquiry/InquiryWrite.js index 9cce90e..1ff680b 100644 --- a/src/components/cstmrService/inquiry/InquiryWrite.js +++ b/src/components/cstmrService/inquiry/InquiryWrite.js @@ -19,9 +19,16 @@ import { import classnames from 'classnames'; import { X } from 'react-feather'; -function InquiryWrite({ isInquiryModalOpen, handlerInquiryModal, detail }) { +function InquiryWrite({ + isInquiryModalOpen, + handlerInquiryModal, + detail, + handlerChangeInquiryForm, + handlerSubmitInquiryForm, + memberName, + inquiryForm +}) { const fileInputRef = useRef(null); - const [value, setValue] = useState(''); const [selectedFile, setSelectedFile] = useState(null); const handleFileChange = event => { @@ -51,8 +58,10 @@ function InquiryWrite({ isInquiryModalOpen, handlerInquiryModal, detail }) { {}} + value={inquiryForm.category} + onChange={e => { + handlerChangeInquiryForm('category', e.target.value); + }} > @@ -67,10 +76,14 @@ function InquiryWrite({ isInquiryModalOpen, handlerInquiryModal, detail }) { + + handlerChangeInquiryForm('contact', e.target.value) + } />

@@ -87,28 +100,37 @@ function InquiryWrite({ isInquiryModalOpen, handlerInquiryModal, detail }) { type='text' bsSize='sm' placeholder='' - defaultValue={detail.createUserNm || ''} - disabled - /> - - - - - - + {detail.createDt && ( + + + + + + + )} - + + handlerChangeInquiryForm('title', e.target.value) + } + /> @@ -117,18 +139,21 @@ function InquiryWrite({ isInquiryModalOpen, handlerInquiryModal, detail }) { *내용 setValue(e.target.value)} - defaultValue={detail.content} + value={inquiryForm.content} + onChange={e => + handlerChangeInquiryForm('content', e.target.value) + } /> {/* 하단 필요없으면제거 */} 300 + 'bg-danger': inquiryForm.content.length > 300 })} > - {`${value.length}/300`} + {`${inquiryForm.content.length}/300`}

@@ -153,7 +178,7 @@ function InquiryWrite({ isInquiryModalOpen, handlerInquiryModal, detail }) { id='inputFile' name='' className='custom-file-input pal' - onChange={handleFileChange} + onChange={() => handleFileChange()} ref={fileInputRef} /> {/* 현재는 text만 바뀌는상태! input 초기화 작업해주세용 */} @@ -187,7 +212,9 @@ function InquiryWrite({ isInquiryModalOpen, handlerInquiryModal, detail }) { - +

diff --git a/src/containers/cstmrService/inquiry/UserInquiryContainer.js b/src/containers/cstmrService/inquiry/UserInquiryContainer.js index b1edd0f..3ee21d6 100644 --- a/src/containers/cstmrService/inquiry/UserInquiryContainer.js +++ b/src/containers/cstmrService/inquiry/UserInquiryContainer.js @@ -1,29 +1,62 @@ import { Button, Badge } from 'reactstrap'; import AppCollapse from '@components/app-collapse'; import { useDispatch, useSelector } from 'react-redux'; -import { - ADMIN_DETAIL, - ADMIN_DETAIL_INITAL, - USER_LIST -} from '../../../modules/cstmrService/inquiry/action'; -import { useEffect, useState } from 'react'; +import * as Actions from '../../../modules/cstmrService/inquiry/action'; +import { Fragment, useCallback, useEffect, useState } from 'react'; import moment from 'moment'; import InquiryWrite from '../../../components/cstmrService/inquiry/InquiryWrite'; +import { InfoModal } from '../../../components/modal/InfoModal'; +import { ErrorModal } from '../../../components/modal/ErrorModal'; -export default function UserInquiryContainer() { +export default function UserInquiryContainer({ memberName }) { const dispatch = useDispatch(); const { userList: lists, adminDetail: detail } = useSelector( state => state.qnaState ); const [isInquiryModalOpen, setIsInquiryModalOpen] = useState(false); + const [inquiryForm, setInquiryForm] = useState({ + category: '칭찬', + contact: '', + title: '', + content: '' + }); + const [validationModal, setValidationModal] = useState({ + errorType: { + isOpen: false, + desc: '', + title: '필수값 입력 오류' + }, + successType: { + isOpen: false, + desc: '', + title: '필수값 입력 오류' + }, + confirmType: { + isOpen: false, + desc: '', + title: '필수값 입력 오류' + } + }); useEffect(() => { - dispatch(USER_LIST.request({ category: '', searchType: '', word: '' })); + dispatch( + Actions.USER_LIST.request({ category: '', searchType: '', word: '' }) + ); }, []); + useEffect(() => { + if (detail) setInquiryForm({ ...detail }); + }, [detail]); + + // 줄바꿈 문자(\r\n)을
로 변환 const textLineBreaks = text => { - return text.replace((/\n/g, '
')); + return text.split('\r\n').map((line, index) => ( + + {line} + {index < text.split('\r\n').length - 1 &&
} +
+ )); }; // 문의 내역 list @@ -64,22 +97,17 @@ export default function UserInquiryContainer() { <>
Q - + {/* br처리? 줄바꿈.. 추후에 생각 */} + {textLineBreaks(i.content)}
{i.anserStatus === 'Y' && (
A - + + {textLineBreaks(i.anserContent)} +
)} @@ -88,16 +116,91 @@ export default function UserInquiryContainer() { }); }; - // 1:1 문의하기 Modal handler + // 1:1 문의/수정 Modal handler const handlerInquiryModal = qnaSno => { if (!isInquiryModalOpen && qnaSno) { - dispatch(ADMIN_DETAIL.request(qnaSno)); + dispatch(Actions.ADMIN_DETAIL.request(qnaSno)); } else { - dispatch(ADMIN_DETAIL_INITAL()); + dispatch(Actions.ADMIN_DETAIL_INITAL()); + setInquiryForm({ + category: '칭찬', + contact: '', + title: '', + content: '' + }); } setIsInquiryModalOpen(!isInquiryModalOpen); }; + // 문의 등록 Form value change handler + const handlerChangeInquiryForm = useCallback( + (type, val) => { + if (type === 'contact') { + const phoneNumber = val.replace(/[^0-9]/g, ''); + setInquiryForm({ ...inquiryForm, [type]: phoneNumber }); + } else { + setInquiryForm({ ...inquiryForm, [type]: val }); + } + }, + [inquiryForm] + ); + + // 문의 등록 event handler + const handlerSubmitInquiryForm = () => { + const { category, title, content, contact } = inquiryForm; + + if (!contact) { + setValidationModal({ + ...validationModal, + errorType: { + title: '필수값 입력 오류', + isOpen: true, + desc: '연락처를 입력해주세요.' + } + }); + return; + } else if (!title) { + setValidationModal({ + ...validationModal, + errorType: { + title: '필수값 입력 오류', + isOpen: true, + desc: '제목을 입력해주세요.' + } + }); + return; + } else if (!content) { + setValidationModal({ + ...validationModal, + errorType: { + title: '필수값 입력 오류', + isOpen: true, + desc: '내용을 입력해주세요.' + } + }); + return; + } else if (content.length > 300) { + setValidationModal({ + ...validationModal, + errorType: { + title: '필수값 입력 오류', + isOpen: true, + desc: '300자 이하로 작성해주세요.' + } + }); + return; + } + + let form = new FormData(); + form.append('category', category); + form.append('contact', contact); + form.append('title', title); + form.append('content', content); + + dispatch(Actions.USER_INQUIRY.request(form)); + setIsInquiryModalOpen(false); + }; + return (
@@ -119,6 +222,33 @@ export default function UserInquiryContainer() { isInquiryModalOpen={isInquiryModalOpen} handlerInquiryModal={handlerInquiryModal} detail={detail} + handlerChangeInquiryForm={handlerChangeInquiryForm} + handlerSubmitInquiryForm={handlerSubmitInquiryForm} + memberName={memberName} + inquiryForm={inquiryForm} + /> + + { + setValidationModal({ + ...validationModal, + successType: { + ...val + } + }); + }} + /> + { + setValidationModal({ + ...validationModal, + errorType: { + ...val + } + }); + }} />
); diff --git a/src/modules/cstmrService/inquiry/action/index.ts b/src/modules/cstmrService/inquiry/action/index.ts index 90d6526..0e7ebd9 100644 --- a/src/modules/cstmrService/inquiry/action/index.ts +++ b/src/modules/cstmrService/inquiry/action/index.ts @@ -8,7 +8,8 @@ import { IQnaAdminAnswer, IQnaAdminFileDown, IQnaUserList, - IQnaUserSearch + IQnaUserSearch, + IQnaUserInquiry } from '../model'; // 관리자 목록 조회 @@ -44,6 +45,11 @@ 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'; +// 사용자 문의 등록 +const USER_INQUIRY_REQUEST = 'cstmrService/qna/USER_INQUIRY_REQUEST'; +const USER_INQUIRY_SUCCESS = 'cstmrService/qna/USER_INQUIRY_SUCCESS'; +const USER_INQUIRY_FAILURE = 'cstmrService/qna/USER_INQUIRY_FAILURE'; + export const ADMIN_LIST = createAsyncAction( ADMIN_LIST_REQUEST, ADMIN_LIST_SUCCESS, @@ -91,6 +97,12 @@ export const USER_LIST = createAsyncAction( USER_LIST_FAILURE )(); +export const USER_INQUIRY = createAsyncAction( + USER_INQUIRY_REQUEST, + USER_INQUIRY_SUCCESS, + USER_INQUIRY_FAILURE +)(); + const actions = { ADMIN_LIST, ADMIN_DETAIL, @@ -98,7 +110,8 @@ const actions = { ADMIN_ANSWER, ADMIN_FILE_DOWN, ADMIN_DELETE, - USER_LIST + USER_LIST, + USER_INQUIRY }; export type QnaAction = ActionType; diff --git a/src/modules/cstmrService/inquiry/apis/index.ts b/src/modules/cstmrService/inquiry/apis/index.ts index 154af66..622b2a2 100644 --- a/src/modules/cstmrService/inquiry/apis/index.ts +++ b/src/modules/cstmrService/inquiry/apis/index.ts @@ -1,6 +1,6 @@ import axios from '../../../utils/customAxiosUtil'; import qs from 'qs'; -import { IQnaUserSearch } from '../model'; +import { IQnaUserInquiry, IQnaUserSearch } from '../model'; export const qnaAPI = { adminList: async (data: { @@ -61,5 +61,12 @@ export const qnaAPI = { }); return await axios.get(`api/cns/qna/user${queryString}`); + }, + userInquiry: async (data: IQnaUserInquiry) => { + return await axios.post('api/cns/qna', data, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); } }; diff --git a/src/modules/cstmrService/inquiry/model/index.ts b/src/modules/cstmrService/inquiry/model/index.ts index 13ea334..c55d788 100644 --- a/src/modules/cstmrService/inquiry/model/index.ts +++ b/src/modules/cstmrService/inquiry/model/index.ts @@ -89,6 +89,13 @@ export interface IQnaUserSearch { word: string; } +export interface IQnaUserInquiry { + category: string; + title: string; + contact: string; + content: string; +} + export const initalState = { adminList: [], adminDetail: { diff --git a/src/modules/cstmrService/inquiry/sagas/index.ts b/src/modules/cstmrService/inquiry/sagas/index.ts index 9db5d9e..74aa508 100644 --- a/src/modules/cstmrService/inquiry/sagas/index.ts +++ b/src/modules/cstmrService/inquiry/sagas/index.ts @@ -1,6 +1,10 @@ import { call, put, takeEvery } from '@redux-saga/core/effects'; import { ActionType } from 'typesafe-actions'; -import { DELETE_MESSAGE, HOST } from '../../../../configs/constants'; +import { + DELETE_MESSAGE, + HOST, + SAVE_MESSAGE +} from '../../../../configs/constants'; import * as MessageActions from '../../../comn/message/actions/comnMessageAction'; import * as Actions from '../action'; import * as Apis from '../apis'; @@ -155,7 +159,7 @@ function* userListSaga(action: ActionType) { try { const payload = action.payload; const res = yield call(Apis.qnaAPI.userList, payload); - const { data, count, errorCode } = res; + const { data, errorCode } = res; if (errorCode) { // 오류메시지 호출 @@ -176,6 +180,45 @@ function* userListSaga(action: ActionType) { } } +function* userInquirySaga( + action: ActionType +) { + try { + const payload = action.payload; + const res = yield call(Apis.qnaAPI.userInquiry, payload); + const { errorCode, data } = res; + + if (errorCode) { + // 오류메시지 호출 + yield put( + MessageActions.IS_ERROR({ + errorCode: errorCode, + errorMessage: '처리중 오류가 발생하였습니다', + isHistoryBack: false, + isRefresh: false + }) + ); + } + + if (data) { + yield put( + MessageActions.IS_MESSAGE({ + messageCode: SAVE_MESSAGE.code, + message: SAVE_MESSAGE.message, + isHistoryBack: false, + isRefresh: false + }) + ); + } + yield put( + Actions.USER_LIST.request({ category: '', searchType: '', word: '' }) + ); + // yield put(Actions.USER_INQUIRY.success(data)); + } catch (error) { + yield put(Actions.ADMIN_ANSWER.failure(error)); + } +} + export function* qnaSaga() { yield takeEvery(Actions.ADMIN_LIST.request, adminListSaga); yield takeEvery(Actions.ADMIN_DETAIL.request, adminDetailSaga); @@ -183,4 +226,5 @@ export function* qnaSaga() { yield takeEvery(Actions.ADMIN_FILE_DOWN.request, adminFileDownSaga); yield takeEvery(Actions.ADMIN_DELETE.request, adminDeleteSaga); yield takeEvery(Actions.USER_LIST.request, userListSaga); + yield takeEvery(Actions.USER_INQUIRY.request, userInquirySaga); } diff --git a/src/views/cstmrService/InquiryView.js b/src/views/cstmrService/InquiryView.js index 2e0c47e..c1cf70c 100644 --- a/src/views/cstmrService/InquiryView.js +++ b/src/views/cstmrService/InquiryView.js @@ -17,7 +17,7 @@ function InquiryView() { {user?.authId !== 'USER' ? ( ) : ( - + )}