Skip to content

Commit

Permalink
fix-fe: 지원자 공고 생성, 상태 입력 발생되는 버그 수정 (#386)
Browse files Browse the repository at this point in the history
Co-authored-by: Jeongwoo Park <[email protected]>
  • Loading branch information
2 people authored and seongjinme committed Aug 23, 2024
1 parent a05ce58 commit e4eeda8
Show file tree
Hide file tree
Showing 13 changed files with 134 additions and 87 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { useState } from 'react';

import { Question, QuestionChoice, QuestionControlActionType, QuestionOptionValue } from '@customTypes/dashboard';
import { Question, QuestionControlActionType, QuestionOptionValue } from '@customTypes/dashboard';

import InputField from '@components/common/InputField';
import Dropdown from '@components/common/Dropdown';
import ToggleSwitch from '@components/common/ToggleSwitch';
import { QUESTION_TYPE_NAME } from '@constants/constants';

import CheckBoxField from '@components/recruitment/CheckBoxField';
import RadioInputField from '@components/recruitment/RadioInputField';
import QuestionController from '../QuestionController';

import S from './style';
import QuestionChoicesBuilder from '../QuestionChoicesBuilder';

interface QuestionBuilderProps {
index: number;
Expand All @@ -23,10 +25,6 @@ interface QuestionBuilderProps {
deleteQuestion: (index: number) => void;
}

function getSortedChoices(choices: QuestionChoice[]): QuestionOptionValue[] {
return choices?.sort((a, b) => a.orderIndex - b.orderIndex).map((item) => ({ value: item.choice }));
}

export default function QuestionBuilder({
index,
question,
Expand All @@ -40,7 +38,6 @@ export default function QuestionBuilder({
}: QuestionBuilderProps) {
const [title, setTitle] = useState<string>(question?.question || '');
const [currentQuestionType, setCurrentQuestionType] = useState<Question['type']>(question?.type || 'SHORT_ANSWER');
const [choices, setChoices] = useState<QuestionOptionValue[]>(getSortedChoices(question?.choices) || []);
const [isRequired, setIsRequired] = useState<boolean>(question?.required || true);

const handleChangeTitle = (event: React.ChangeEvent<HTMLInputElement>) => {
Expand All @@ -50,14 +47,12 @@ export default function QuestionBuilder({

const handleChangeQuestionType = (type: Question['type']) => {
if (type === currentQuestionType) return;
if (type === 'SHORT_ANSWER' || type === 'LONG_ANSWER') setChoices([]);

setCurrentQuestionType(type);
setQuestionType(index)(type);
};

const handleUpdateQuestionChoices = (newChoices: QuestionOptionValue[]) => {
setChoices(newChoices);
setQuestionOptions(index)(newChoices);
};

Expand Down Expand Up @@ -99,10 +94,17 @@ export default function QuestionBuilder({
/>
</S.InputBox>

{(currentQuestionType === 'SINGLE_CHOICE' || currentQuestionType === 'MULTIPLE_CHOICE') && (
<QuestionChoicesBuilder
choices={choices}
onUpdate={handleUpdateQuestionChoices}
{currentQuestionType === 'SINGLE_CHOICE' && (
<RadioInputField
choices={question.choices}
setChoices={handleUpdateQuestionChoices}
/>
)}

{currentQuestionType === 'MULTIPLE_CHOICE' && (
<CheckBoxField
choices={question.choices}
setChoices={handleUpdateQuestionChoices}
/>
)}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ type Story = StoryObj<typeof QuestionChoicesBuilder>;
export const Default: Story = {
render: (args) => {
const [choices, setChoices] = useState<QuestionOptionValue[]>([
{ value: '첫 번째 옵션입니다.' },
{ value: '두 번째 옵션입니다.' },
{ value: '세 번째 옵션입니다.' },
{ choice: '첫 번째 옵션입니다.' },
{ choice: '두 번째 옵션입니다.' },
{ choice: '세 번째 옵션입니다.' },
]);

const onUpdate = (newChoices: QuestionOptionValue[]) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ export default function QuestionChoicesBuilder({ choices, onUpdate }: QuestionCh
const newChoices = [...choices];

if (index < newChoices.length) {
newChoices[index].value = newValue;
newChoices[index].choice = newValue;
} else {
newChoices.push({ value: newValue });
newChoices.push({ choice: newValue });
}

onUpdate(newChoices);
Expand All @@ -37,7 +37,7 @@ export default function QuestionChoicesBuilder({ choices, onUpdate }: QuestionCh
if (event.key === 'Enter') {
event.preventDefault();
if (index === choices.length - 1) {
onUpdate([...choices, { value: '' }]);
onUpdate([...choices, { choice: '' }]);
setTimeout(() => inputRefs.current[index + 1]?.focus(), 0);
} else {
inputRefs.current[index + 1]?.focus();
Expand All @@ -50,7 +50,7 @@ export default function QuestionChoicesBuilder({ choices, onUpdate }: QuestionCh
onUpdate(newChoices);
};

const choicesToRender = choices.length === 0 ? [{ value: '' }] : choices;
const choicesToRender = choices.length === 0 ? [{ choice: '' }] : choices;

return (
<S.Wrapper>
Expand All @@ -63,7 +63,7 @@ export default function QuestionChoicesBuilder({ choices, onUpdate }: QuestionCh
ref={(el) => setInputRef(el, index)}
type="text"
placeholder="옵션을 입력하세요."
value={choice.value}
value={choice.choice}
onChange={(event) => handleInputChange(event, index)}
onKeyDown={(event) => handleKeyDown(event, index)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ export default function Apply({
prevStep,
nextStep,
}: ApplyProps) {
const isNextBtnValid =
applyState.length === DEFAULT_QUESTION_LENGTH ||
applyState
.slice(DEFAULT_QUESTION_LENGTH)
.every((question) => question.question.trim() && question.choices.length !== 1);

return (
<S.Wrapper>
<S.Section>
Expand Down Expand Up @@ -106,7 +112,7 @@ export default function Apply({
</S.ButtonContent>
</Button>
<Button
disabled={false}
disabled={!isNextBtnValid}
onClick={nextStep}
size="sm"
color="white"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import Button from '@components/common/Button';
import ChevronButton from '@components/common/ChevronButton';
import DateInput from '@components/common/DateInput';
Expand All @@ -23,7 +23,12 @@ export default function Recruitment({ recruitmentInfoState, setRecruitmentInfoSt
const today = new Date().toISOString().split('T')[0];
const startDateText = startDate ? formatDate(startDate) : '';
const endDateText = endDate ? formatDate(endDate) : '';
const isNextButtonValid = !!(endDate && contentText && startDate && title);

useEffect(() => {
setContentText(quillRef.current?.unprivilegedEditor?.getText());
}, [quillRef]);

const isNextButtonValid = !!(endDate && contentText?.trim() && startDate && title.trim());

const handleStartDate = (e: React.ChangeEvent<HTMLInputElement>) => {
setRecruitmentInfoState((prev) => ({
Expand Down Expand Up @@ -51,9 +56,6 @@ export default function Recruitment({ recruitmentInfoState, setRecruitmentInfoSt
...prev,
postingContent: string,
}));
};

const handlePostingContentBlur = () => {
setContentText(quillRef.current?.unprivilegedEditor?.getText());
};

Expand Down Expand Up @@ -99,7 +101,6 @@ export default function Recruitment({ recruitmentInfoState, setRecruitmentInfoSt
quillRef={quillRef}
value={recruitmentInfoState.postingContent}
onChange={handlePostingContentChange}
onBlur={handlePostingContentBlur}
/>
</S.RecruitDetailContainer>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ const meta: Meta<typeof CheckBoxField> = {
},
tags: ['autodocs'],
argTypes: {
options: {
choices: {
description: 'CheckBoxOption으로 구성된 옵션 객체 배열입니다.',
control: { type: 'object' },
table: {
type: { summary: 'Option[]' },
},
},
setOptions: {
setChoices: {
description: '옵션 객체 배열을 설정하는 함수입니다.',
action: 'optionsChanged',
table: {
Expand All @@ -35,11 +35,11 @@ const meta: Meta<typeof CheckBoxField> = {
export default meta;
type Story = StoryObj<typeof meta>;

const defaultOptions = [{ value: '' }];
const defaultOptions = [{ choice: '' }];

export const Default: Story = {
args: {
options: defaultOptions,
choices: defaultOptions,
},
decorators: [
(Child) => {
Expand All @@ -49,8 +49,8 @@ export const Default: Story = {
<Child
args={{
...args,
setOptions: (options) => {
updateArgs({ options });
setChoices: (choices) => {
updateArgs({ choices });
},
}}
/>
Expand Down
43 changes: 22 additions & 21 deletions frontend/src/components/recruitment/CheckBoxField/index.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,40 @@
import React, { useCallback, useEffect, useRef } from 'react';
import { QuestionOptionValue } from '@customTypes/dashboard';
import CheckBoxOption from '../CheckBoxOption';

import S from './style';
import CheckBoxOption from '../CheckBoxOption';

interface Option {
value: string;
interface ChoiceOption {
choice: string;
}

interface Props {
options: Option[];
setOptions: React.Dispatch<React.SetStateAction<Option[]>>;
choices: ChoiceOption[];
setChoices: (newChoices: QuestionOptionValue[]) => void;
}

export default function CheckBoxField({ options, setOptions }: Props) {
export default function CheckBoxField({ choices, setChoices }: Props) {
const inputRefs = useRef<(HTMLInputElement | null)[]>([]);

const handleOptionChange = (index: number, value: string) => {
const newOptions = [...options];
newOptions[index].value = value;
setOptions(newOptions);
const newOptions = [...choices];
newOptions[index].choice = value;
setChoices(newOptions);
};

const addOption = () => {
setOptions([...options, { value: '' }]);
setChoices([...choices, { choice: '' }]);
};

const deleteOption = (index: number) => {
const newOptions = options.slice();
const newOptions = choices.slice();
newOptions.splice(index, 1);
setOptions(newOptions);
setChoices(newOptions);
};

const handleOptionBlur = (index: number) => {
const isLastOption = index === options.length - 1;
const isEmptyValue = options[index].value.trim() === '';
const isLastOption = index === choices.length - 1;
const isEmptyValue = choices[index].choice.trim() === '';
if (!isLastOption && isEmptyValue) {
deleteOption(index);
}
Expand All @@ -43,13 +44,13 @@ export default function CheckBoxField({ options, setOptions }: Props) {
};

const focusLastOption = useCallback(() => {
inputRefs.current[options.length - 1]?.focus();
}, [options.length]);
inputRefs.current[choices.length - 1]?.focus();
}, [choices.length]);

const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>, index: number) => {
if ((e.key === 'Tab' || e.key === 'Enter') && !e.shiftKey) {
e.preventDefault();
if (index === options.length - 1) {
if (index === choices.length - 1) {
addOption();
}
focusLastOption();
Expand All @@ -58,23 +59,23 @@ export default function CheckBoxField({ options, setOptions }: Props) {

useEffect(() => {
focusLastOption();
}, [options.length, focusLastOption]);
}, [choices.length, focusLastOption]);

const setInputRefCallback = (index: number) => (node: HTMLInputElement) => {
inputRefs.current[index] = node;
};

return (
<S.Container>
{options.map((option, index) => (
{choices.map((choice, index) => (
<CheckBoxOption
// eslint-disable-next-line react/no-array-index-key
key={index}
isDisabled={false}
isDeleteBtn={options.length - 1 !== index}
isDeleteBtn={choices.length - 1 !== index}
onDeleteBtnClick={() => deleteOption(index)}
inputAttrs={{
value: option.value,
value: choice.choice,
ref: setInputRefCallback(index),
onChange: (e) => handleOptionChange(index, e.target.value),
onKeyDown: (e) => handleKeyDown(e, index),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ const meta: Meta<typeof RadioInputField> = {
},
tags: ['autodocs'],
argTypes: {
options: {
choices: {
description: 'RadioInputOption으로 구성된 옵션 객체 배열입니다.',
control: { type: 'object' },
table: {
type: { summary: 'Option[]' },
},
},
setOptions: {
setChoices: {
description: '옵션 객체 배열을 설정하는 함수입니다.',
action: 'optionsChanged',
table: {
Expand All @@ -35,11 +35,11 @@ const meta: Meta<typeof RadioInputField> = {
export default meta;
type Story = StoryObj<typeof meta>;

const defaultOptions = [{ value: '' }];
const defaultOptions = [{ choice: '' }];

export const Default: Story = {
args: {
options: defaultOptions,
choices: defaultOptions,
},
decorators: [
(Child) => {
Expand All @@ -49,8 +49,8 @@ export const Default: Story = {
<Child
args={{
...args,
setOptions: (options) => {
updateArgs({ options });
setChoices: (choices) => {
updateArgs({ choices });
},
}}
/>
Expand Down
Loading

0 comments on commit e4eeda8

Please sign in to comment.