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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react';
import deleteButton from '@/assets/images/icons/delete_button_icon.svg';
import selectIcon from '@/assets/images/icons/selectArrow.svg';
import { CustomDropDown } from '@/components/common/CustomDropDown/CustomDropDown';
import { Award } from '@/types/club';
import { Award, SemesterTerm, SemesterTermType } from '@/types/club';
import * as Styled from './AwardEditor.styles';

interface AwardEditorProps {
Expand All @@ -12,12 +12,15 @@ interface AwardEditorProps {

const START_YEAR = 2020;

const parseSemester = (semester: string): number => {
const match = semester.match(/(\d{4})\s+(\d)학기/);
if (!match) return 0;
const year = parseInt(match[1], 10);
const semesterNumber = parseInt(match[2], 10);
return year * 10 + semesterNumber;
const getSemesterSortValue = (award: Award): number => {
const semesterValue = award.semester === SemesterTerm.FIRST ? 1 : 2;
return award.year * 10 + semesterValue;
};

const formatSemesterLabel = (award: Award): string => {
const semesterLabel =
award.semester === SemesterTerm.FIRST ? '1학기' : '2학기';
return `${award.year} ${semesterLabel}`;
};

const generateYearOptions = (currentYear: number) => {
Expand All @@ -32,44 +35,51 @@ const generateYearOptions = (currentYear: number) => {
};

const SEMESTER_OPTIONS = [
{ value: '1', label: '1학기' },
{ value: '2', label: '2학기' },
{ value: SemesterTerm.FIRST, label: '1학기' },
{ value: SemesterTerm.SECOND, label: '2학기' },
] as const;

const AwardEditor = ({ awards, onChange }: AwardEditorProps) => {
const currentYear = new Date().getFullYear();
const [selectedYear, setSelectedYear] = useState(currentYear.toString());
const [selectedSemester, setSelectedSemester] = useState('1');
const [selectedSemester, setSelectedSemester] = useState<SemesterTermType>(
SemesterTerm.FIRST,
);
const [isYearDropdownOpen, setIsYearDropdownOpen] = useState(false);
const [isSemesterDropdownOpen, setIsSemesterDropdownOpen] = useState(false);
const [lastAddedSemester, setLastAddedSemester] = useState<string | null>(
null,
);
const [lastAddedKey, setLastAddedKey] = useState<string | null>(null);
const inputRefs = useRef<Record<string, HTMLInputElement | null>>({});

const yearOptions = generateYearOptions(currentYear);

const sortedAwards = [...awards].sort(
(awardA, awardB) =>
parseSemester(awardB.semester) - parseSemester(awardA.semester),
getSemesterSortValue(awardB) - getSemesterSortValue(awardA),
);

const getAwardKey = (award: Award, index: number): string =>
`${award.year}-${award.semester}-${index}`;

const handleAddSemester = () => {
const semesterText = `${selectedYear} ${selectedSemester}학기`;
const year = parseInt(selectedYear, 10);

const isDuplicate = awards.some((award) => award.semester === semesterText);
const isDuplicate = awards.some(
(award) => award.year === year && award.semester === selectedSemester,
);
if (isDuplicate) {
alert('이미 추가된 학기입니다.');
return;
}

const newAward: Award = {
semester: semesterText,
year,
semester: selectedSemester,
achievements: [''],
};

onChange([...awards, newAward]);
setLastAddedSemester(semesterText);
const newAwards = [...awards, newAward];
onChange(newAwards);
setLastAddedKey(getAwardKey(newAward, newAwards.length - 1));
};

const handleRemoveSemester = (semesterIndex: number) => {
Expand All @@ -83,7 +93,7 @@ const AwardEditor = ({ awards, onChange }: AwardEditorProps) => {
: award,
);
onChange(updatedAwards);
setLastAddedSemester(awards[semesterIndex].semester);
setLastAddedKey(getAwardKey(awards[semesterIndex], semesterIndex));
};

const handleRemoveAchievement = (
Expand Down Expand Up @@ -122,21 +132,22 @@ const AwardEditor = ({ awards, onChange }: AwardEditorProps) => {
};

useEffect(() => {
if (lastAddedSemester) {
const award = awards.find(
(award) => award.semester === lastAddedSemester,
if (lastAddedKey) {
const awardIndex = awards.findIndex(
(award, index) => getAwardKey(award, index) === lastAddedKey,
);
const award = awardIndex !== -1 ? awards[awardIndex] : null;
if (award) {
const lastIndex = award.achievements.length - 1;
const key = `${lastAddedSemester}-${lastIndex}`;
const key = `${lastAddedKey}-${lastIndex}`;
const inputRef = inputRefs.current[key];
if (inputRef) {
inputRef.focus();
}
}
setLastAddedSemester(null);
setLastAddedKey(null);
}
}, [awards, lastAddedSemester]);
}, [awards, lastAddedKey]);

return (
<Styled.Container>
Expand Down Expand Up @@ -174,7 +185,7 @@ const AwardEditor = ({ awards, onChange }: AwardEditorProps) => {
<CustomDropDown
options={SEMESTER_OPTIONS}
selected={selectedSemester}
onSelect={setSelectedSemester}
onSelect={(value) => setSelectedSemester(value as SemesterTermType)}
open={isSemesterDropdownOpen}
onToggle={(isOpen) => setIsSemesterDropdownOpen(!isOpen)}
style={{ width: '100px' }}
Expand Down Expand Up @@ -206,14 +217,20 @@ const AwardEditor = ({ awards, onChange }: AwardEditorProps) => {
</Styled.AddSemesterSection>

<Styled.AwardsList>
{sortedAwards.map((award) => {
{sortedAwards.map((award, sortedIndex) => {
const originalIndex = awards.findIndex(
(originalAward) => originalAward.semester === award.semester,
(originalAward, idx) =>
originalAward.year === award.year &&
originalAward.semester === award.semester &&
originalAward.achievements === award.achievements,
);
const awardKey = getAwardKey(award, originalIndex);
return (
<Styled.AwardItem key={award.semester}>
<Styled.AwardItem key={awardKey}>
<Styled.SemesterHeader>
<Styled.SemesterTitle>{award.semester}</Styled.SemesterTitle>
<Styled.SemesterTitle>
{formatSemesterLabel(award)}
</Styled.SemesterTitle>
<Styled.RemoveButton
onClick={() => handleRemoveSemester(originalIndex)}
>
Expand All @@ -226,7 +243,7 @@ const AwardEditor = ({ awards, onChange }: AwardEditorProps) => {
<Styled.AchievementItem key={achievementIndex}>
<Styled.AchievementInput
ref={(element) => {
const key = `${award.semester}-${achievementIndex}`;
const key = `${awardKey}-${achievementIndex}`;
inputRefs.current[key] = element;
}}
placeholder='수상 내역을 입력하세요'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,24 @@
import { useState } from 'react';
import { USER_EVENT } from '@/constants/eventName';
import useMixpanelTrack from '@/hooks/Mixpanel/useMixpanelTrack';
import { Award, FAQ, IdealCandidate, SemesterTerm } from '@/types/club';
import * as Styled from './ClubIntroContent.styles';

export interface Award {
semester: string;
achievements: string[];
}

export interface IdealCandidate {
tags?: string[]; // TODO: tags가 추가될수도 있음
content: string;
}
const formatSemesterLabel = (award: Award): string => {
const semesterLabel =
award.semester === SemesterTerm.FIRST ? '1학기' : '2학기';
return `${award.year} ${semesterLabel}`;
};

export interface Faq {
question: string;
answer: string;
}
const getAwardKey = (award: Award, index: number): string =>
`${award.year}-${award.semester}-${index}`;

interface ClubIntroContentProps {
activityDescription?: string;
awards?: Award[];
idealCandidate?: IdealCandidate;
benefits?: string;
faqs?: Faq[];
faqs?: FAQ[];
}

const ClubIntroContent = ({
Expand All @@ -35,11 +30,11 @@ const ClubIntroContent = ({
}: ClubIntroContentProps) => {
const trackEvent = useMixpanelTrack();

const [openFaqIndices, setOpenFaqIndices] = useState<number[]>([]);
const [openFaqIndexes, setOpenFaqIndexes] = useState<number[]>([]);

const handleToggleFaq = (index: number) => {
const isOpening = !openFaqIndices.includes(index);
setOpenFaqIndices((prev) =>
const isOpening = !openFaqIndexes.includes(index);
setOpenFaqIndexes((prev) =>
prev.includes(index) ? prev.filter((i) => i !== index) : [...prev, index],
);

Expand All @@ -66,18 +61,23 @@ const ClubIntroContent = ({
<Styled.Section>
<Styled.SectionTitle>동아리 성과</Styled.SectionTitle>
<Styled.TextContainer>
{awards.map((award) => (
<Styled.AwardGroup key={award.semester}>
<Styled.SemesterBadge>{award.semester}</Styled.SemesterBadge>
<Styled.AwardList>
{award.achievements.map((item, idx) => (
<Styled.AwardItem key={`${award.semester}-${idx}`}>
{item}
</Styled.AwardItem>
))}
</Styled.AwardList>
</Styled.AwardGroup>
))}
{awards.map((award, index) => {
const awardKey = getAwardKey(award, index);
return (
<Styled.AwardGroup key={awardKey}>
<Styled.SemesterBadge>
{formatSemesterLabel(award)}
</Styled.SemesterBadge>
<Styled.AwardList>
{award.achievements.map((item, idx) => (
<Styled.AwardItem key={`${awardKey}-${idx}`}>
{item}
</Styled.AwardItem>
))}
</Styled.AwardList>
</Styled.AwardGroup>
);
})}
</Styled.TextContainer>
</Styled.Section>
)}
Expand All @@ -102,7 +102,7 @@ const ClubIntroContent = ({
<Styled.FaqHeader>FAQ</Styled.FaqHeader>
<Styled.FaqList>
{faqs.map((faq, index) => {
const isOpen = openFaqIndices.includes(index);
const isOpen = openFaqIndexes.includes(index);
return (
<Styled.FaqItem key={faq.question}>
<Styled.QuestionRow onClick={() => handleToggleFaq(index)}>
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/types/club.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,16 @@ export interface ClubDescription {
recruitmentTarget: string;
}

export const SemesterTerm = {
FIRST: 'FIRST',
SECOND: 'SECOND',
} as const;

export type SemesterTermType = (typeof SemesterTerm)[keyof typeof SemesterTerm];

export interface Award {
semester: string;
year: number;
semester: SemesterTermType;
achievements: string[];
}

Expand Down