Browse Source

qna 사용자 문의 등록 api 연결 (파일 제외)

pull/2/head
hhjk00 11 months ago
parent
commit
9a7ff12bca
  1. 77
      src/components/cstmrService/inquiry/InquiryWrite.js
  2. 174
      src/containers/cstmrService/inquiry/UserInquiryContainer.js
  3. 17
      src/modules/cstmrService/inquiry/action/index.ts
  4. 9
      src/modules/cstmrService/inquiry/apis/index.ts
  5. 7
      src/modules/cstmrService/inquiry/model/index.ts
  6. 48
      src/modules/cstmrService/inquiry/sagas/index.ts
  7. 2
      src/views/cstmrService/InquiryView.js

77
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 }) {
<Input
type='select'
bsSize='sm'
value={detail.category}
onChange={() => {}}
value={inquiryForm.category}
onChange={e => {
handlerChangeInquiryForm('category', e.target.value);
}}
>
<option value='칭찬'>칭찬</option>
<option value='불만'>불만</option>
@ -67,10 +76,14 @@ function InquiryWrite({ isInquiryModalOpen, handlerInquiryModal, detail }) {
<Label for='test'>
<span className='necessary'>*</span>
</Label>
<Input
type='number'
type='tel'
bsSize='sm'
defaultValue={detail.contact || ''}
value={inquiryForm.contact || ''}
onChange={e =>
handlerChangeInquiryForm('contact', e.target.value)
}
/>
<div className='feedback'>
<p>
@ -87,28 +100,37 @@ function InquiryWrite({ isInquiryModalOpen, handlerInquiryModal, detail }) {
type='text'
bsSize='sm'
placeholder=''
defaultValue={detail.createUserNm || ''}
disabled
/>
</FormGroup>
</Col>
<Col className='list-input' md='3'>
<FormGroup>
<Label for='test'>생성 일자</Label>
<Input
type='text'
bsSize='sm'
defaultValue={detail.createDt.slice(0, -3) || ''}
value={memberName}
disabled
/>
</FormGroup>
</Col>
{detail.createDt && (
<Col className='list-input' md='3'>
<FormGroup>
<Label for='test'>생성 일자</Label>
<Input
type='text'
bsSize='sm'
value={detail.createDt.slice(0, -3)}
disabled
/>
</FormGroup>
</Col>
)}
<Col className='list-input' md='12'>
<FormGroup>
<Label for='test'>
<span className='necessary'>*</span>
</Label>
<Input type='text' bsSize='sm' defaultValue={detail.title} />
<Input
type='text'
bsSize='sm'
value={inquiryForm.title}
onChange={e =>
handlerChangeInquiryForm('title', e.target.value)
}
/>
</FormGroup>
</Col>
<Col className='list-input' md='12'>
@ -117,18 +139,21 @@ function InquiryWrite({ isInquiryModalOpen, handlerInquiryModal, detail }) {
<span className='necessary'>*</span>
</Label>
<Input
// className='faq-textarea'
className='faq-textarea'
type='textarea'
onChange={e => setValue(e.target.value)}
defaultValue={detail.content}
value={inquiryForm.content}
onChange={e =>
handlerChangeInquiryForm('content', e.target.value)
}
/>
{/* 하단 필요없으면제거 */}
<span
className={classnames('textarea-counter-value float-right', {
'bg-danger': value.length > 300
'bg-danger': inquiryForm.content.length > 300
})}
>
{`${value.length}/300`}
{`${inquiryForm.content.length}/300`}
</span>
<div className='feedback'>
<p>
@ -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 }) {
<Button color='secondary' onClick={() => handlerInquiryModal()}>
취소
</Button>
<Button color='primary'>저장</Button>
<Button color='primary' onClick={() => handlerSubmitInquiryForm()}>
저장
</Button>
</div>
</ModalFooter>
</div>

174
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)을 <br>로 변환
const textLineBreaks = text => {
return text.replace((/\n/g, '<br/>'));
return text.split('\r\n').map((line, index) => (
<Fragment key={index}>
{line}
{index < text.split('\r\n').length - 1 && <br />}
</Fragment>
));
};
// 문의 내역 list
@ -64,22 +97,17 @@ export default function UserInquiryContainer() {
<>
<div className='faq-a'>
<span className='faq-icon-q'>Q</span>
<span
className='faq-a-text'
dangerouslySetInnerHTML={{ __html: textLineBreaks(i.content) }}
>
<span className='faq-a-text'>
{/* br처리? 줄바꿈.. 추후에 생각 */}
{textLineBreaks(i.content)}
</span>
</div>
{i.anserStatus === 'Y' && (
<div className='faq-a' style={{ marginTop: '7px' }}>
<span className='faq-icon-a'>A</span>
<span
className='faq-a-text'
dangerouslySetInnerHTML={{
__html: textLineBreaks(i.anserContent)
}}
></span>
<span className='faq-a-text'>
{textLineBreaks(i.anserContent)}
</span>
</div>
)}
</>
@ -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 (
<div className='faq'>
<div className='my-faq'>
@ -119,6 +222,33 @@ export default function UserInquiryContainer() {
isInquiryModalOpen={isInquiryModalOpen}
handlerInquiryModal={handlerInquiryModal}
detail={detail}
handlerChangeInquiryForm={handlerChangeInquiryForm}
handlerSubmitInquiryForm={handlerSubmitInquiryForm}
memberName={memberName}
inquiryForm={inquiryForm}
/>
<InfoModal
modal={validationModal.successType}
setModal={val => {
setValidationModal({
...validationModal,
successType: {
...val
}
});
}}
/>
<ErrorModal
modal={validationModal.errorType}
setModal={val => {
setValidationModal({
...validationModal,
errorType: {
...val
}
});
}}
/>
</div>
);

17
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
)<IQnaUserSearch, IQnaUserList[], AxiosError>();
export const USER_INQUIRY = createAsyncAction(
USER_INQUIRY_REQUEST,
USER_INQUIRY_SUCCESS,
USER_INQUIRY_FAILURE
)<IQnaUserInquiry, { count: number; data: boolean }, AxiosError>();
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<typeof actions>;

9
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'
}
});
}
};

7
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: {

48
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<typeof Actions.USER_LIST.request>) {
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<typeof Actions.USER_LIST.request>) {
}
}
function* userInquirySaga(
action: ActionType<typeof Actions.USER_INQUIRY.request>
) {
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);
}

2
src/views/cstmrService/InquiryView.js

@ -17,7 +17,7 @@ function InquiryView() {
{user?.authId !== 'USER' ? (
<AdminInquiryContainer />
) : (
<UserInquiryContainer />
<UserInquiryContainer memberName={user.memberName} />
)}
</CustomMainLayout>
</>

Loading…
Cancel
Save