Skip to content

Commit

Permalink
refactor: useDnD 및 useRedux 적용
Browse files Browse the repository at this point in the history
  • Loading branch information
luke-lemon committed Jun 11, 2024
1 parent 1a687b4 commit 03d94a9
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 237 deletions.
130 changes: 51 additions & 79 deletions apps/googleFormClone/src/component/editor/AnswerItemManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,26 @@

import { useEffect, useRef } from 'react';
import { EDITOR_QUESTION_TYPE } from '../../constants';
import { useAppDispatch, useAppSelector } from '../../hook/useRedux';
import {
AnswerInterface,
addAnswer,
editAnswer,
removeAnswer,
} from '../../store/reducer/answerSlice';
import { AnswerInterface, addAnswer, editAnswer, removeAnswer } from '../../store/reducer/answerSlice';
import ChooseAnswer from './ChooseAnswer';
import { v4 as uuidv4 } from 'uuid';
import useDnDList from '../../hook/headless/useDnDList';
import { editAnswerOrder } from '../../store/reducer/questionSlice';
import useChangeEditBlockID from '../../hook/useChangeEditBlockID';
import { Checkbox, Radio, classMerge } from '@google-form-clone/shared-ui';
import { store } from '../../store/store';
import { useDnDList, useRedux } from '@google-form-clone/hooks';

interface AnswerManagerProps {
questionID: string;
}

// TODO 기타 추가 구현
const AnswerManager = ({ questionID }: AnswerManagerProps) => {
const dispatch = useAppDispatch();
const { type, answerIDList } = useAppSelector(
(store) => store.question[questionID]
);
const answerMap = useAppSelector((store) => store.answer);
const { useDispatch, useSelector } = useRedux<typeof store>();

const dispatch = useDispatch();
const { type, answerIDList } = useSelector((store) => store.question[questionID]);
const answerMap = useSelector((store) => store.answer);
const prevAnswerLength = useRef(answerIDList.length);

const { isEditing } = useChangeEditBlockID(questionID);
Expand All @@ -42,10 +37,7 @@ const AnswerManager = ({ questionID }: AnswerManagerProps) => {

const chooseAnswerRef = useRef<(null | HTMLInputElement)[]>([]);

const changeAnswer = (
e: React.ChangeEvent<HTMLInputElement>,
{ answerID, questionID }: AnswerInterface
) => {
const changeAnswer = (e: React.ChangeEvent<HTMLInputElement>, { answerID, questionID }: AnswerInterface) => {
dispatch(editAnswer({ answerID, content: e.target.value, questionID }));
};

Expand All @@ -56,20 +48,15 @@ const AnswerManager = ({ questionID }: AnswerManagerProps) => {

const removeAnswerItem = (aID: string, idx: number) => {
if (chooseAnswerRef && chooseAnswerRef.current) {
if (
idx < chooseAnswerRef.current.length &&
chooseAnswerRef.current[idx + 1] instanceof HTMLInputElement
) {
if (idx < chooseAnswerRef.current.length && chooseAnswerRef.current[idx + 1] instanceof HTMLInputElement) {
chooseAnswerRef.current[idx + 1]!.focus();
} else {
chooseAnswerRef.current[idx - 1]!.focus();
}
}

if (chooseAnswerRef && chooseAnswerRef.current) {
chooseAnswerRef.current = chooseAnswerRef.current.filter(
(_, i) => i !== idx
);
chooseAnswerRef.current = chooseAnswerRef.current.filter((_, i) => i !== idx);
}

dispatch(removeAnswer(answerMap[aID]));
Expand All @@ -89,66 +76,51 @@ const AnswerManager = ({ questionID }: AnswerManagerProps) => {
<fieldset
className={classMerge([
'flex flex-col gap-4',
(type === EDITOR_QUESTION_TYPE.short ||
type === EDITOR_QUESTION_TYPE.long) &&
'mx-[32px]',
(type === EDITOR_QUESTION_TYPE.short || type === EDITOR_QUESTION_TYPE.long) && 'mx-[32px]',
])}
>
{type === EDITOR_QUESTION_TYPE.short && (
<div className="text-gray-400">단답형 메시지</div>
)}
{type === EDITOR_QUESTION_TYPE.long && (
<div className="text-gray-400">장문형 메시지</div>
)}
{type !== EDITOR_QUESTION_TYPE.short &&
type !== EDITOR_QUESTION_TYPE.long && (
<>
<div ref={dragListContainerRef}>
{answerIDList.map((aID, idx) => {
const answerInfo = answerMap[aID];
return (
<ChooseAnswer
key={aID}
idx={idx + 1}
type={type}
value={answerInfo.content}
placeholder={`옵션 ${idx + 1}`}
onChange={(e) => changeAnswer(e, answerInfo)}
isEditing={isEditing}
deletable={answerIDList.length > 1}
onDeleteButton={() => removeAnswerItem(aID, idx)}
handleDrag={handleDrag}
innerRef={(el) => (chooseAnswerRef.current[idx] = el)}
/>
);
})}
</div>
{isEditing && (
<div className="items-center flex gap-4 mx-[32px]">
<div className="flex items-center gap-4">
{type === EDITOR_QUESTION_TYPE.radio && <Radio disabled />}
{type === EDITOR_QUESTION_TYPE.checkbox && (
<Checkbox disabled />
)}
{type === EDITOR_QUESTION_TYPE.dropdown && (
<div className="w-[24px]"></div>
)}
<span
className="w-full text-gray-500 cursor-text hover:underline decoration-gray-400"
onClick={addAnswerItem}
tabIndex={0}
>
옵션 추가
</span>
</div>
{/* <span>또는</span>
{type === EDITOR_QUESTION_TYPE.short && <div className="text-gray-400">단답형 메시지</div>}
{type === EDITOR_QUESTION_TYPE.long && <div className="text-gray-400">장문형 메시지</div>}
{type !== EDITOR_QUESTION_TYPE.short && type !== EDITOR_QUESTION_TYPE.long && (
<>
<div ref={dragListContainerRef}>
{answerIDList.map((aID, idx) => {
const answerInfo = answerMap[aID];
return (
<ChooseAnswer
key={aID}
idx={idx + 1}
type={type}
value={answerInfo.content}
placeholder={`옵션 ${idx + 1}`}
onChange={(e) => changeAnswer(e, answerInfo)}
isEditing={isEditing}
deletable={answerIDList.length > 1}
onDeleteButton={() => removeAnswerItem(aID, idx)}
handleDrag={handleDrag}
innerRef={(el) => (chooseAnswerRef.current[idx] = el)}
/>
);
})}
</div>
{isEditing && (
<div className="items-center flex gap-4 mx-[32px]">
<div className="flex items-center gap-4">
{type === EDITOR_QUESTION_TYPE.radio && <Radio disabled />}
{type === EDITOR_QUESTION_TYPE.checkbox && <Checkbox disabled />}
{type === EDITOR_QUESTION_TYPE.dropdown && <div className="w-[24px]"></div>}
<span className="w-full text-gray-500 cursor-text hover:underline decoration-gray-400" onClick={addAnswerItem} tabIndex={0}>
옵션 추가
</span>
</div>
{/* <span>또는</span>
<button type="button" className="text-blue-500">
'기타' 추가
</button> */}
</div>
)}
</>
)}
</div>
)}
</>
)}
</fieldset>
);
};
Expand Down
40 changes: 7 additions & 33 deletions apps/googleFormClone/src/component/editor/ChooseAnswer.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
import { RiCloseFill, RiDraggable } from 'react-icons/ri';
import { EDITOR_QUESTION_TYPE, ICON_CLASS } from '../../constants';
import { isTouchScreen } from '../../hook/headless/useDnDList';
import {
Checkbox,
IconButton,
Input,
Radio,
classMerge,
} from '@google-form-clone/shared-ui';
import { Checkbox, IconButton, Input, Radio, classMerge } from '@google-form-clone/shared-ui';
import { isTouchScreen } from '@google-form-clone/hooks';

interface RadioAnswerProps
extends Omit<React.ComponentPropsWithRef<'input'>, 'type'> {
type:
| EDITOR_QUESTION_TYPE.radio
| EDITOR_QUESTION_TYPE.checkbox
| EDITOR_QUESTION_TYPE.dropdown;
interface RadioAnswerProps extends Omit<React.ComponentPropsWithRef<'input'>, 'type'> {
type: EDITOR_QUESTION_TYPE.radio | EDITOR_QUESTION_TYPE.checkbox | EDITOR_QUESTION_TYPE.dropdown;
idx?: number;
deletable?: boolean;
isEditing?: boolean;
Expand All @@ -23,16 +13,7 @@ interface RadioAnswerProps
handleDrag?: (e: React.MouseEvent) => void;
}

const ChooseAnswer = ({
type,
idx,
deletable = true,
isEditing,
onDeleteButton,
handleDrag,
innerRef,
...props
}: RadioAnswerProps) => {
const ChooseAnswer = ({ type, idx, deletable = true, isEditing, onDeleteButton, handleDrag, innerRef, ...props }: RadioAnswerProps) => {
return (
<div className="flex items-center justify-between group/item">
<div className="relative flex items-center w-full gap-4">
Expand All @@ -41,20 +22,13 @@ const ChooseAnswer = ({
onMouseDown={handleDrag}
onTouchStart={handleDrag}
tabIndex={0}
className={classMerge([
`absolute outline-none cursor-move ${ICON_CLASS}`,
!isTouchScreen && 'hidden group-hover/item:flex',
])}
className={classMerge([`absolute outline-none cursor-move ${ICON_CLASS}`, !isTouchScreen && 'hidden group-hover/item:flex'])}
/>
)}
<div className="flex gap-4 px-[32px] w-full items-center">
{type === EDITOR_QUESTION_TYPE.radio && <Radio disabled />}
{type === EDITOR_QUESTION_TYPE.checkbox && <Checkbox disabled />}
{type === EDITOR_QUESTION_TYPE.dropdown && (
<div className="w-[20px] h-[20px] justify-center flex text-gray-500">
{idx}
</div>
)}
{type === EDITOR_QUESTION_TYPE.dropdown && <div className="w-[20px] h-[20px] justify-center flex text-gray-500">{idx}</div>}
<Input innerRef={innerRef} className="w-full" {...props} />
{deletable && isEditing && (
<IconButton onClick={onDeleteButton} className="flex">
Expand Down
3 changes: 1 addition & 2 deletions apps/googleFormClone/src/component/editor/QuestionBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ import { addAnswer, removeAnswer } from '../../store/reducer/answerSlice';
import { v4 } from 'uuid';
import useChangeEditBlockID from '../../hook/useChangeEditBlockID';
import useBlockAutoFocus from '../../hook/useBlockAutoFocus';
import { isTouchScreen } from '../../hook/headless/useDnDList';
import { Block, Divider, Dropdown, IconButton, Input, Switch, classMerge } from '@google-form-clone/shared-ui';
import { useRedux } from '@google-form-clone/hooks';
import { isTouchScreen, useRedux } from '@google-form-clone/hooks';
import { store } from '../../store/store';

interface QuestionBlockProps extends React.ComponentPropsWithRef<'div'> {
Expand Down
13 changes: 6 additions & 7 deletions apps/googleFormClone/src/component/preview/QuestionBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { useRedux } from '@google-form-clone/hooks';
import AnswerManager from './AnswerItemManager';
import { useAppSelector } from '../../hook/useRedux';
import { Block } from '@google-form-clone/shared-ui';
import { store } from '../../store/store';

interface QuestionBlockProps extends React.ComponentPropsWithRef<'div'> {
questionID: string;
handleDrag?: (e: React.MouseEvent) => void;
}

const QuestionBlock = ({ questionID, ...props }: QuestionBlockProps) => {
const questionInfo = useAppSelector((store) => store.question[questionID]);
const { useSelector } = useRedux<typeof store>();

const questionInfo = useSelector((store) => store.question[questionID]);

const { questionContent, required } = questionInfo;

Expand All @@ -21,11 +24,7 @@ const QuestionBlock = ({ questionID, ...props }: QuestionBlockProps) => {
</div>
</div>
<AnswerManager questionID={questionID} />
{required && (
<div className="font-bold text-right text-red-600">
* 필수 질문입니다
</div>
)}
{required && <div className="font-bold text-right text-red-600">* 필수 질문입니다</div>}
</Block>
);
};
Expand Down
6 changes: 4 additions & 2 deletions apps/googleFormClone/src/component/preview/TitleBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Fragment } from 'react';
import { useAppSelector } from '../../hook/useRedux';
import { Block } from '@google-form-clone/shared-ui';
import { useRedux } from '@google-form-clone/hooks';
import { store } from '../../store/store';

const TitleBlock = () => {
const { title, content } = useAppSelector((store) => store.docs);
const { useSelector } = useRedux<typeof store>();
const { title, content } = useSelector((store) => store.docs);

return (
<Block className="flex flex-col w-full gap-2 p-6" isTitleBlock>
Expand Down
3 changes: 1 addition & 2 deletions apps/googleFormClone/src/pages/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import { ICON_CLASS, EDITOR_QUESTION_TYPE } from '../constants';

import { addQuestion } from '../store/reducer/questionSlice';
import { addAnswer } from '../store/reducer/answerSlice';
import useDnDList from '../hook/headless/useDnDList';
import { editQuestionBlockOrder } from '../store/reducer/docsSlice';
import { Link } from 'react-router-dom';
import { useEffect, useRef } from 'react';
import { IconButton } from '@google-form-clone/shared-ui';
import { store } from '../store/store';
import { useRedux } from '@google-form-clone/hooks';
import { useDnDList, useRedux } from '@google-form-clone/hooks';

// TODO 관련 컴포넌트 EDITOR로 변경
const Editor = () => {
Expand Down
34 changes: 13 additions & 21 deletions apps/googleFormClone/src/pages/Result.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,29 @@
import { Link } from 'react-router-dom';
import { EDITOR_QUESTION_TYPE } from '../constants';
import { useAppSelector } from '../hook/useRedux';
import { RiEdit2Line, RiEyeLine } from 'react-icons/ri';
import { Block, IconButton } from '@google-form-clone/shared-ui';
import { useRedux } from '@google-form-clone/hooks';
import { store } from '../store/store';

const Result = () => {
const docs = useAppSelector((store) => store.docs);
const question = useAppSelector((store) => store.question);
const answer = useAppSelector((store) => store.answer);
const response = useAppSelector((store) => store.response);
const { useSelector } = useRedux<typeof store>();

const docs = useSelector((store) => store.docs);
const question = useSelector((store) => store.question);
const answer = useSelector((store) => store.answer);
const response = useSelector((store) => store.response);

const makeAnswerView = (qID: string) => {
if (response[qID] === null) return '미응답';

if (
question[qID].type === EDITOR_QUESTION_TYPE.short ||
question[qID].type === EDITOR_QUESTION_TYPE.long
) {
if (question[qID].type === EDITOR_QUESTION_TYPE.short || question[qID].type === EDITOR_QUESTION_TYPE.long) {
return response[qID];
} else if (
question[qID].type === EDITOR_QUESTION_TYPE.radio ||
question[qID].type === EDITOR_QUESTION_TYPE.dropdown
) {
return answer[question[qID].answerIDList[response[qID] as number]]
.content;
} else if (question[qID].type === EDITOR_QUESTION_TYPE.radio || question[qID].type === EDITOR_QUESTION_TYPE.dropdown) {
return answer[question[qID].answerIDList[response[qID] as number]].content;
} else {
const answerArray: string[] = [];

(response[qID] as number[]).map((rID) =>
answerArray.push(answer[question[qID].answerIDList[rID]].content)
);
(response[qID] as number[]).map((rID) => answerArray.push(answer[question[qID].answerIDList[rID]].content));

return answerArray.join(',');
}
Expand Down Expand Up @@ -58,9 +52,7 @@ const Result = () => {
return (
<Block className="flex flex-col gap-3 p-6" key={qID}>
<div className="text-xl font-bold">
{question[qID].questionContent.length === 0
? '질문'
: question[qID].questionContent}
{question[qID].questionContent.length === 0 ? '질문' : question[qID].questionContent}
</div>
<div>{makeAnswerView(qID)}</div>
</Block>
Expand Down
Loading

0 comments on commit 03d94a9

Please sign in to comment.