Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/components/org/OrgAdmin/AboutSection/Schedule/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { IconInfoCircle } from '@sopt-makers/icons';
import { Button, DialogOptionType, useDialog } from '@sopt-makers/ui';
import { useEffect } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';

import RequiredIcon from '../../assets/RequiredIcon';
// import Modal from '../../common/Modal';
import useModal from '../../common/Modal/useModal';
import { FIRST_SCHEDULE_SESSION_NAME } from '../scheduleConstants';
import { StWrapper } from '../style';
import {
StScheduleBody,
Expand Down Expand Up @@ -46,6 +48,15 @@ const Schedule = ({ isEditable = true }: ScheduleProps) => {
(index) => activitySchedule?.[index]?.date,
);

useEffect(() => {
if (activitySchedule?.[0]?.session !== FIRST_SCHEDULE_SESSION_NAME) {
setValue('activitySchedule.0.session', FIRST_SCHEDULE_SESSION_NAME, {
shouldDirty: false,
shouldValidate: false,
});
}
}, [activitySchedule, setValue]);

const handleResetDates = () => {
SCHEDULE_ROW_INDICES.forEach((index) => {
setValue(`activitySchedule.${index}.date`, '', {
Expand Down Expand Up @@ -121,7 +132,7 @@ const Schedule = ({ isEditable = true }: ScheduleProps) => {
{...register(`activitySchedule.${index}.session`)}
id={`schedule-session-${index}`}
placeholder="세션명을 입력해 주세요."
disabled={!isEditable}
disabled={!isEditable || index === 0}
/>
</StScheduleRow>
))}
Expand Down
16 changes: 9 additions & 7 deletions src/components/org/OrgAdmin/AboutSection/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ACTIVITY_GENERATION } from '@/utils/generation';

import { soptFetcher } from '../api';
import { EXEC_ROLE_LIST, toMemberRole } from './memberRole';
import { FIRST_SCHEDULE_SESSION_NAME } from './scheduleConstants';

type ImageField = { fileName?: string; file?: File } | undefined;

Expand Down Expand Up @@ -110,13 +111,14 @@ const SCHEDULE_ROW_COUNT = 16;
export const buildActivityScheduleFromForm = (
values: FieldValues,
): AddAdminActivityScheduleRequestDto[] =>
Array.from(
{ length: SCHEDULE_ROW_COUNT },
(_, index) => values.activitySchedule?.[index],
)
.filter((row) => row?.date && row?.session)
.map((row) => ({
name: row.session,
Array.from({ length: SCHEDULE_ROW_COUNT }, (_, index) => ({
index,
row: values.activitySchedule?.[index],
}))
.filter(({ row }) => row?.date && row?.session)
.map(({ index, row }) => ({
// 첫 일정은 지원서/공홈에서 이 이름으로 식별하므로 폼 값과 무관하게 고정한다.
name: index === 0 ? FIRST_SCHEDULE_SESSION_NAME : row.session,
startDate: row.date,
}));

Expand Down
33 changes: 31 additions & 2 deletions src/components/org/OrgAdmin/AboutSection/formMapper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { FieldValues, UseFormSetValue } from 'react-hook-form';

import { fromMemberRole } from './memberRole';
import { FIRST_SCHEDULE_SESSION_NAME } from './scheduleConstants';

type AdminAboutMember = {
role: string;
Expand Down Expand Up @@ -34,6 +35,25 @@ type AdminAboutData = {

const setValueOptions = { shouldDirty: false, shouldValidate: false };

const normalizeSessionName = (name?: string) => name?.trim().toUpperCase();

// (과거 데이터가 순서와 무관하게 저장됐을 가능성에 대비한 안전장치)
// 이름으로 못 찾으면 원래 순서를 그대로 두고, 이후 로직에서 0번째 행을 OT로 강제하는 걸로 폴백한다.
const reorderScheduleWithOtFirst = (
schedule: AdminAboutSchedule[],
): AdminAboutSchedule[] => {
const otIndex = schedule.findIndex(
(item) => normalizeSessionName(item.name) === FIRST_SCHEDULE_SESSION_NAME,
);

if (otIndex <= 0) return schedule;

const otEntry = schedule[otIndex];
const rest = schedule.filter((_, index) => index !== otIndex);

return [otEntry, ...rest];
};

export const syncAboutFormFromAdminData = (
data: AdminAboutData | undefined,
setValue: UseFormSetValue<FieldValues>,
Expand Down Expand Up @@ -89,12 +109,21 @@ export const syncAboutFormFromAdminData = (
);
});

data.activitySchedule?.forEach(({ name, startDate }, index) => {
const orderedSchedule = data.activitySchedule
? reorderScheduleWithOtFirst(data.activitySchedule)
: undefined;

orderedSchedule?.forEach(({ name, startDate }, index) => {
setValue(
`activitySchedule.${index}.date`,
startDate ?? '',
setValueOptions,
);
setValue(`activitySchedule.${index}.session`, name ?? '', setValueOptions);
// 이름으로 OT를 못 찾아 재정렬되지 않았더라도, 0번째 행은 항상 OT로 고정한다(폴백).
setValue(
`activitySchedule.${index}.session`,
index === 0 ? FIRST_SCHEDULE_SESSION_NAME : (name ?? ''),
setValueOptions,
);
});
};
3 changes: 3 additions & 0 deletions src/components/org/OrgAdmin/AboutSection/scheduleConstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// 다른 서비스(지원서, 공홈)가 전체 일정의 첫 번째 항목을 이 이름으로 식별해 사용하므로,
// 0번째 일정의 이름은 항상 이 값으로 고정되어야 한다.
export const FIRST_SCHEDULE_SESSION_NAME = 'OT';
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ const NewsSection = ({
key={deleteId ?? undefined}
variant="danger"
title="삭제하시겠습니까?"
description="최신 소식은 ‘배포’버튼을 거치지 않고 즉시 배포가 돼요."
isOpen={deleteId != null}
onCancel={() => setDeleteId(null)}
onAction={() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,24 @@ const FaqSection = ({
data.recruitQuestion.forEach(({ part, questions }) => {
if (!part || !questions?.length) return;

const count = Math.min(questions.length, FAQ_MAX_QUESTION_COUNT);
next[part as PART_KO] = Math.max(count, FAQ_DEFAULT_QUESTION_COUNT);
next[part as PART_KO] = Math.min(
questions.length,
FAQ_MAX_QUESTION_COUNT,
);
});

setQuestionCounts(next);
}, [data, restoreSignal]);

useEffect(() => {
PART_LIST.forEach((part) => {
setValue(`recruitQuestionCount.${part}`, questionCounts[part], {
shouldDirty: false,
shouldValidate: false,
});
});
}, [questionCounts, setValue]);

const currentCount = questionCounts[fnaPart];

const handleSetSelectedPart = (value: PART_KO) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,20 @@ export const syncRecruitFormFromAdminData = (
data.recruitQuestion?.forEach(({ part, questions }) => {
if (!part) return;

questions?.slice(0, FAQ_MAX_QUESTION_COUNT).forEach((qa, index) => {
// 서버에 없는 인덱스도 명시적으로 비워야 삭제된 질문이 폼에 남아있지 않음
for (let index = 0; index < FAQ_MAX_QUESTION_COUNT; index += 1) {
const qa = questions?.[index];

setValue(
`recruitQuestion.${part}.question${index}`,
qa.question ?? '',
qa?.question ?? '',
setValueOptions,
);
setValue(
`recruitQuestion.${part}.answer${index}`,
qa.answer ?? '',
qa?.answer ?? '',
setValueOptions,
);
});
}
});
};
28 changes: 12 additions & 16 deletions src/components/org/OrgAdmin/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,12 @@ export const validationRecruitInputs = (
onInvalidFaqPart: (faqPart: PART_KO) => void,
) => {
const values = getValues();
const { partCurriculum, recruitPartCurriculum, recruitQuestion } = values;
const {
partCurriculum,
recruitPartCurriculum,
recruitQuestion,
recruitQuestionCount,
} = values;
const setRequiredError = (name: string) => {
setError(name, {
type: 'required',
Expand Down Expand Up @@ -301,17 +306,19 @@ export const validationRecruitInputs = (
}

for (const part of PART_LIST) {
let hasCompletePair = false;
const partQuestions = recruitQuestion?.[part];
const count = Math.min(
recruitQuestionCount?.[part] ?? 1,
FAQ_MAX_QUESTION_COUNT,
);

for (let index = 0; index < FAQ_MAX_QUESTION_COUNT; index += 1) {
// 화면에 보이는 칸은 전부 필수. 안 쓸 칸은 채우거나 삭제 버튼으로 지워야 한다.
for (let index = 0; index < count; index += 1) {
const questionName = `recruitQuestion.${part}.question${index}`;
const answerName = `recruitQuestion.${part}.answer${index}`;
const question = (partQuestions?.[`question${index}`] ?? '').trim();
const answer = (partQuestions?.[`answer${index}`] ?? '').trim();

if (!question && !answer) continue;

if (!question) {
focusInvalidField(questionName, onInvalidFaqPart, part);
return false;
Expand All @@ -321,17 +328,6 @@ export const validationRecruitInputs = (
focusInvalidField(answerName, onInvalidFaqPart, part);
return false;
}

hasCompletePair = true;
}

if (!hasCompletePair) {
focusInvalidField(
`recruitQuestion.${part}.question0`,
onInvalidFaqPart,
part,
);
return false;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/utils/org.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const PART_LIST: PART_KO[] = [

// 모집안내 탭 FAQ 파트별 질문 최대 개수
export const FAQ_MAX_QUESTION_COUNT = 10;
export const FAQ_DEFAULT_QUESTION_COUNT = 3;
export const FAQ_DEFAULT_QUESTION_COUNT = 1;

export type EXEC_TYPE =
| (typeof 임원진_LIST)[number]
Expand Down
Loading