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
1 change: 1 addition & 0 deletions frontend/src/assets/images/icons/back_arrow_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions frontend/src/assets/images/icons/forward_arrow_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import QuestionContainer from '@/pages/ApplicationFormPage/components/QuestionCo
import QuestionAnswerer from '@/pages/ApplicationFormPage/components/QuestionAnswerer/QuestionAnswerer';
import { useGetApplication } from '@/hooks/queries/application/useGetApplication';
import Spinner from '@/components/common/Spinner/Spinner';
import backButtonIcon from '@/assets/images/icons/back_button_icon.svg';
import BackButton from '@/assets/images/icons/back_arrow_icon.svg';
import ForwardButton from '@/assets/images/icons/forward_arrow_icon.svg';
import debounce from '@/utils/debounce';
import updateApplicantMemo from '@/apis/application/updateApplicantDetail';
import { ApplicationStatus } from '@/types/applicants';
import mapStatusToGroup from '@/utils/mapStatusToGroup';
import { Question } from '@/types/application';

const AVAILABLE_STATUSES = [
ApplicationStatus.SCREENING, // 서류검토
Expand All @@ -25,14 +27,16 @@ const ApplicantDetailPage = () => {
const navigate = useNavigate();
const [applicantMemo, setAppMemo] = useState('');
const [applicantStatus, setApplicantStatus] = useState<ApplicationStatus>();
const { applicantsData, clubId } = useAdminClubContext();
const { applicantsData, clubId, setApplicantsData } = useAdminClubContext();

const { data: formData, isLoading, isError } = useGetApplication(clubId!);

const applicant = useMemo(
() => applicantsData?.applicants.find((a) => a.id === questionId),
[applicantsData, questionId],
);
const { applicant, applicantIndex } = useMemo(() => {
const index = applicantsData?.applicants.findIndex((a) => a.id === questionId) ?? -1;
const _applicant = applicantsData?.applicants[index];

return { applicant: _applicant, applicantIndex: index };
}, [applicantsData, questionId]);
Comment on lines +34 to +39
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

undefined 처리와 가독성을 개선하세요

findIndex가 -1을 반환할 때의 처리와 변수명 개선이 필요합니다.

  const { applicant, applicantIndex } = useMemo(() => {
    const index = applicantsData?.applicants.findIndex((a) => a.id === questionId) ?? -1;
-    const _applicant = applicantsData?.applicants[index];
+    const foundApplicant = index >= 0 ? applicantsData?.applicants[index] : undefined;

-    return { applicant: _applicant, applicantIndex: index };
+    return { applicant: foundApplicant, applicantIndex: index };
  }, [applicantsData, questionId]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { applicant, applicantIndex } = useMemo(() => {
const index = applicantsData?.applicants.findIndex((a) => a.id === questionId) ?? -1;
const _applicant = applicantsData?.applicants[index];
return { applicant: _applicant, applicantIndex: index };
}, [applicantsData, questionId]);
const { applicant, applicantIndex } = useMemo(() => {
const index = applicantsData?.applicants.findIndex((a) => a.id === questionId) ?? -1;
const foundApplicant = index >= 0
? applicantsData?.applicants[index]
: undefined;
return { applicant: foundApplicant, applicantIndex: index };
}, [applicantsData, questionId]);


useEffect(() => {
if (applicant) {
Expand Down Expand Up @@ -67,19 +71,43 @@ const ApplicantDetailPage = () => {
.map((ans) => ans.value);
};

const updateApplicantInContext = (memo: string, status: ApplicationStatus) => {
if (!applicantsData || applicantIndex === -1) return;

const updatedApplicants = [...applicantsData.applicants];
updatedApplicants[applicantIndex] = { ...applicant, memo, status };

const handleMemoChange = (e: any) => {
setApplicantsData({ ...applicantsData, applicants: updatedApplicants });
};

const handleMemoChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const newMemo = e.target.value;
setAppMemo(newMemo);
updateApplicantInContext(newMemo, applicantStatus!);
updateApplicantDetail(newMemo, applicantStatus);
};
Comment on lines +83 to 88
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

상태 업데이트 순서와 타입 안정성을 개선하세요

applicantStatus가 undefined일 수 있는데 non-null assertion(!)을 사용하고 있습니다. 또한 Context와 서버 업데이트 순서가 일치하지 않을 수 있습니다.

  const handleMemoChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    const newMemo = e.target.value;
+   if (!applicantStatus) return;
+   
    setAppMemo(newMemo);
-   updateApplicantInContext(newMemo, applicantStatus!);
+   updateApplicantInContext(newMemo, applicantStatus);
    updateApplicantDetail(newMemo, applicantStatus);
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleMemoChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const newMemo = e.target.value;
setAppMemo(newMemo);
updateApplicantInContext(newMemo, applicantStatus!);
updateApplicantDetail(newMemo, applicantStatus);
};
const handleMemoChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const newMemo = e.target.value;
if (!applicantStatus) return;
setAppMemo(newMemo);
updateApplicantInContext(newMemo, applicantStatus);
updateApplicantDetail(newMemo, applicantStatus);
};
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
around lines 83-88, remove the non-null assertion on applicantStatus and ensure
both context and server updates use the exact same, type-safe status value:
compute a single const status = applicantStatus ?? applicant?.status; if status
is still undefined, bail out or provide a clear default; call
updateApplicantInContext(newMemo, status) first to keep UI consistent, then call
updateApplicantDetail(newMemo, status) (await or handle the Promise and errors)
so server update follows the context change; update types/signatures if needed
to accept the status variable instead of forcing non-null.


const handleStatusChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const newStatus = e.target.value as ApplicationStatus;
setApplicantStatus(newStatus);
updateApplicantInContext(applicantMemo, newStatus);
updateApplicantDetail(applicantMemo, newStatus);
};

const previousApplicant = () => {
const previousData = applicantsData.applicants[applicantIndex - 1];
if (applicantIndex < 0 || !previousData) return;

navigate(`/admin/applicants/${previousData.id}`);
};

const nextApplicant = () => {
const nextData = applicantsData.applicants[applicantIndex + 1];
if (applicantIndex < 0 || !nextData) return;

navigate(`/admin/applicants/${nextData.id}`);
};
Comment on lines +97 to +109
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

네비게이션 함수의 경계 검사 로직을 수정하세요

applicantIndex < 0 체크가 잘못된 위치에 있습니다. index가 -1인 경우 배열 접근 전에 체크해야 합니다.

  const previousApplicant = () => {
-   const previousData = applicantsData.applicants[applicantIndex - 1];
-   if (applicantIndex < 0 || !previousData) return;
+   if (applicantIndex <= 0) return;
+   const previousData = applicantsData.applicants[applicantIndex - 1];
+   if (!previousData) return;

    navigate(`/admin/applicants/${previousData.id}`);
  };

  const nextApplicant = () => {
-   const nextData = applicantsData.applicants[applicantIndex + 1];
-   if (applicantIndex < 0 || !nextData) return;
+   if (applicantIndex < 0 || applicantIndex >= applicantsData.applicants.length - 1) return;
+   const nextData = applicantsData.applicants[applicantIndex + 1];
+   if (!nextData) return;

    navigate(`/admin/applicants/${nextData.id}`);
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const previousApplicant = () => {
const previousData = applicantsData.applicants[applicantIndex - 1];
if (applicantIndex < 0 || !previousData) return;
navigate(`/admin/applicants/${previousData.id}`);
};
const nextApplicant = () => {
const nextData = applicantsData.applicants[applicantIndex + 1];
if (applicantIndex < 0 || !nextData) return;
navigate(`/admin/applicants/${nextData.id}`);
};
const previousApplicant = () => {
// don’t try to go before the first applicant
if (applicantIndex <= 0) return;
const previousData = applicantsData.applicants[applicantIndex - 1];
if (!previousData) return;
navigate(`/admin/applicants/${previousData.id}`);
};
const nextApplicant = () => {
// don’t go past the last applicant
if (applicantIndex < 0 || applicantIndex >= applicantsData.applicants.length - 1) return;
const nextData = applicantsData.applicants[applicantIndex + 1];
if (!nextData) return;
navigate(`/admin/applicants/${nextData.id}`);
};
🤖 Prompt for AI Agents
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx around
lines 97-109: the boundary checks occur after accessing the array; move and
tighten them so you never index out-of-bounds. For previousApplicant, first
validate applicantsData and that applicantIndex > 0 (or applicantIndex <= 0
return) before reading applicants[applicantIndex - 1], then navigate. For
nextApplicant, first validate applicantsData and that applicantIndex >= 0 and
applicantIndex < applicants.length - 1 (otherwise return) before reading
applicants[applicantIndex + 1], then navigate.


return (
<>
<Header />
Expand All @@ -96,13 +124,19 @@ const ApplicantDetailPage = () => {
marginBottom: 16,
}}
>
<button
onClick={() => navigate(-1)}
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0 }}
aria-label="뒤로가기"
<img style={{cursor: 'pointer'}} onClick={previousApplicant} src={BackButton} alt="이전 지원자" />
<select
id="applicantSelect"
value={applicant.id}
onChange={(e) => navigate(`/admin/applicants/${e.target.value}`)}
>
<img src={backButtonIcon} alt="뒤로가기" style={{ width: 16, height: 16 }} />
</button>
{applicantsData.applicants.map((a) => (
<option key={a.id} value={a.id}>
{a.answers[0].value}
</option>
))}
</select>
<img style={{cursor: 'pointer'}} onClick={nextApplicant} src={ForwardButton} alt="다음 지원자" />
Comment on lines +127 to +139
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

네비게이션 버튼과 셀렉트의 접근성을 개선하세요

이미지 버튼과 셀렉트 요소에 적절한 접근성 속성이 누락되어 있습니다.

-          <img style={{cursor: 'pointer'}} onClick={previousApplicant} src={BackButton} alt="이전 지원자" />
+          <button 
+            onClick={previousApplicant} 
+            disabled={applicantIndex <= 0}
+            aria-label="이전 지원자로 이동"
+            style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0 }}
+          >
+            <img src={BackButton} alt="" />
+          </button>
           <select
             id="applicantSelect"
             value={applicant.id}
             onChange={(e) => navigate(`/admin/applicants/${e.target.value}`)}
+            aria-label="지원자 선택"
           >
             {applicantsData.applicants.map((a) => (
               <option key={a.id} value={a.id}>
                 {a.answers[0].value}
               </option>
             ))}
           </select>
-          <img style={{cursor: 'pointer'}} onClick={nextApplicant} src={ForwardButton} alt="다음 지원자" />
+          <button 
+            onClick={nextApplicant} 
+            disabled={applicantIndex >= applicantsData.applicants.length - 1}
+            aria-label="다음 지원자로 이동"
+            style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0 }}
+          >
+            <img src={ForwardButton} alt="" />
+          </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<img style={{cursor: 'pointer'}} onClick={previousApplicant} src={BackButton} alt="이전 지원자" />
<select
id="applicantSelect"
value={applicant.id}
onChange={(e) => navigate(`/admin/applicants/${e.target.value}`)}
>
<img src={backButtonIcon} alt="뒤로가기" style={{ width: 16, height: 16 }} />
</button>
{applicantsData.applicants.map((a) => (
<option key={a.id} value={a.id}>
{a.answers[0].value}
</option>
))}
</select>
<img style={{cursor: 'pointer'}} onClick={nextApplicant} src={ForwardButton} alt="다음 지원자" />
<button
onClick={previousApplicant}
disabled={applicantIndex <= 0}
aria-label="이전 지원자로 이동"
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0 }}
>
<img src={BackButton} alt="" />
</button>
<select
id="applicantSelect"
value={applicant.id}
onChange={(e) => navigate(`/admin/applicants/${e.target.value}`)}
aria-label="지원자 선택"
>
{applicantsData.applicants.map((a) => (
<option key={a.id} value={a.id}>
{a.answers[0].value}
</option>
))}
</select>
<button
onClick={nextApplicant}
disabled={applicantIndex >= applicantsData.applicants.length - 1}
aria-label="다음 지원자로 이동"
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0 }}
>
<img src={ForwardButton} alt="" />
</button>

</div>
<textarea onInput={handleMemoChange} placeholder='메모를 입력해주세요' value={applicantMemo}></textarea>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

textarea에 레이블과 접근성 속성을 추가하세요

textarea에 연결된 레이블이 없고 접근성 속성이 누락되어 있습니다.

-        <textarea onInput={handleMemoChange} placeholder='메모를 입력해주세요' value={applicantMemo}></textarea>
+        <label htmlFor="applicantMemo" className="sr-only">지원자 메모</label>
+        <textarea 
+          id="applicantMemo"
+          onInput={handleMemoChange} 
+          placeholder='메모를 입력해주세요' 
+          value={applicantMemo}
+          aria-label="지원자 메모"
+        />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<textarea onInput={handleMemoChange} placeholder='메모를 입력해주세요' value={applicantMemo}></textarea>
<label htmlFor="applicantMemo" className="sr-only">지원자 메모</label>
<textarea
id="applicantMemo"
onInput={handleMemoChange}
placeholder='메모를 입력해주세요'
value={applicantMemo}
aria-label="지원자 메모"
/>
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
around line 141, the textarea lacks a label and accessibility attributes; add a
proper label element associated via id (e.g., <label
htmlFor="applicant-memo">메모</label>) and give the textarea that id, or
alternatively provide aria-label or aria-labelledby if a visible label isn't
desired; ensure the placeholder is not the only accessible name, keep the
existing onInput and value props, and if the field is required or has a
description add aria-required or aria-describedby referencing an explanatory
element.


Expand All @@ -115,7 +149,7 @@ const ApplicantDetailPage = () => {
</select>

<Styled.QuestionsWrapper style={{ cursor: 'default' }}>
{formData.questions.map((q: import('@/types/application').Question, i: number) => (
{formData.questions.map((q: Question, i: number) => (
<QuestionContainer key={q.id} hasError={false}>
<QuestionAnswerer
question={q}
Expand Down
88 changes: 45 additions & 43 deletions frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,50 +102,52 @@ const ApplicantsTab = () => {
</Styled.ApplicantTableHeader>
</Styled.ApplicantTableRow>
</Styled.ApplicantTableHeaderWrapper>
{filteredApplicants.map((item: Applicant, index: number) => (
<Styled.ApplicantTableRow
key={index}
onClick={() => navigate(`/admin/applicants/${item.id}`)}
>
<Styled.ApplicantTableCol>
<input
type='checkbox'
style={{ width: 24, height: 24, borderRadius: 6 }}
onClick={(e: React.MouseEvent<HTMLInputElement>) =>
e.stopPropagation()
<tbody>
{filteredApplicants.map((item: Applicant, index: number) => (
<Styled.ApplicantTableRow
key={index}
onClick={() => navigate(`/admin/applicants/${item.id}`)}
>
<Styled.ApplicantTableCol>
<input
type='checkbox'
style={{ width: 24, height: 24, borderRadius: 6 }}
onClick={(e: React.MouseEvent<HTMLInputElement>) =>
e.stopPropagation()
}
/>
Comment on lines +112 to +118
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

체크박스에 접근성 속성과 상태 관리를 추가하세요

현재 체크박스는 시각적으로만 존재하고 실제 기능이나 상태 관리가 없습니다. aria-label과 checked 상태를 추가하여 접근성과 기능을 개선하세요.

                  <input
                    type='checkbox'
                    style={{ width: 24, height: 24, borderRadius: 6 }}
+                   aria-label={`${item.answers[0].value} 선택`}
+                   checked={false}
+                   onChange={() => {/* TODO: 선택 로직 구현 */}}
                    onClick={(e: React.MouseEvent<HTMLInputElement>) =>
                      e.stopPropagation()
                    }
                  />

체크박스의 선택 기능을 구현하는 코드를 생성해드릴까요? 또는 이 작업을 추적하는 이슈를 생성해드릴까요?

🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx around
lines 112–118, the checkbox is purely visual and lacks accessibility attributes
and state handling; replace the implicit uncontrolled checkbox with a controlled
input: add an aria-label (or aria-labelledby), wire a checked prop tied to the
row's selection state (e.g., selectedIds or a prop like isSelected(rowId)),
replace the onClick stopPropagation with an onChange handler that toggles the
selection state (and still call e.stopPropagation()), and ensure keyboard
activation works (use onChange not onClick) so the checkbox is both accessible
and functional.

</Styled.ApplicantTableCol>
<Styled.ApplicantTableCol>
<Styled.ApplicantStatusBadge status={mapStatusToGroup(item.status).label}>{mapStatusToGroup(item.status).label}</Styled.ApplicantStatusBadge>
</Styled.ApplicantTableCol>
<Styled.ApplicantTableCol>
{item.answers[0].value}
</Styled.ApplicantTableCol>
<Styled.ApplicantTableCol>
{
item.memo && item.memo.length > 0 ? (
item.memo
) : (
<span style={{ color: '#989898' }}>메모를 입력하지 않았습니다.</span>
)
}
/>
</Styled.ApplicantTableCol>
<Styled.ApplicantTableCol>
<Styled.ApplicantStatusBadge status={mapStatusToGroup(item.status).label}>{mapStatusToGroup(item.status).label}</Styled.ApplicantStatusBadge>
</Styled.ApplicantTableCol>
<Styled.ApplicantTableCol>
{item.answers[0].value}
</Styled.ApplicantTableCol>
<Styled.ApplicantTableCol>
{
item.memo && item.memo.length > 0 ? (
item.memo
) : (
<span style={{ color: '#989898' }}>메모를 입력하지 않았습니다.</span>
)
}
</Styled.ApplicantTableCol>
<Styled.ApplicantTableCol>
{
// createdAt을 yyyy-mm-dd 형식으로 변환
// 임시로.. 나중에 변경해야함
(() => {
const date = new Date(item.createdAt);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
})()
}
</Styled.ApplicantTableCol>
</Styled.ApplicantTableRow>
))}
</Styled.ApplicantTableCol>
<Styled.ApplicantTableCol>
{
// createdAt을 yyyy-mm-dd 형식으로 변환
// 임시로.. 나중에 변경해야함
(() => {
const date = new Date(item.createdAt);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
})()
Comment on lines +139 to +145
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

날짜 포맷팅을 유틸리티 함수로 추출하세요

날짜 포맷팅 로직이 IIFE로 인라인에 작성되어 있어 재사용성이 떨어지고 가독성이 좋지 않습니다. 전용 유틸리티 함수로 추출하는 것이 좋습니다.

utils 파일에 날짜 포맷팅 함수를 추가하세요:

// utils/dateFormatter.ts
export const formatDateToYYYYMMDD = (dateString: string): string => {
  const date = new Date(dateString);
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
};

그리고 컴포넌트에서 사용하세요:

-                  {
-                    // createdAt을 yyyy-mm-dd 형식으로 변환
-                    // 임시로.. 나중에 변경해야함
-                    (() => {
-                      const date = new Date(item.createdAt);
-                      const year = date.getFullYear();
-                      const month = String(date.getMonth() + 1).padStart(2, '0');
-                      const day = String(date.getDate()).padStart(2, '0');
-                      return `${year}-${month}-${day}`;
-                    })()
-                  }
+                  {formatDateToYYYYMMDD(item.createdAt)}
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx around
lines 139 to 145, the inline IIFE date formatting should be extracted to a
reusable utility; create a utils/dateFormatter.ts exporting
formatDateToYYYYMMDD(dateString: string): string that constructs a Date from the
string, pads month/day to 2 digits and returns "YYYY-MM-DD", then replace the
IIFE in the component with a call to formatDateToYYYYMMDD(item.createdAt) and
import the utility.

}
</Styled.ApplicantTableCol>
</Styled.ApplicantTableRow>
))}
</tbody>
</Styled.ApplicantTable>
</Styled.ApplicantListWrapper>
</>
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/utils/mapStatusToGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ import { ApplicationStatus } from "@/types/applicants";

const mapStatusToGroup = (status: ApplicationStatus): { status: ApplicationStatus, label: string } => {
switch (status) {
case ApplicationStatus.DRAFT:
case ApplicationStatus.SUBMITTED:
case ApplicationStatus.SCREENING:
return { status: ApplicationStatus.DRAFT, label: '서류검토' };
return { status: ApplicationStatus.SUBMITTED, label: '서류검토' };
case ApplicationStatus.SCREENING_PASSED:
case ApplicationStatus.INTERVIEW_SCHEDULED:
case ApplicationStatus.INTERVIEW_IN_PROGRESS:
Expand All @@ -15,7 +14,7 @@ const mapStatusToGroup = (status: ApplicationStatus): { status: ApplicationStatu
case ApplicationStatus.ACCEPTED:
return { status: ApplicationStatus.ACCEPTED, label: '합격' };
default:
return { status: ApplicationStatus.DRAFT, label: '서류검토'};
return { status: ApplicationStatus.SUBMITTED, label: '서류검토'};
Comment on lines 16 to +17
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

불필요한 default 케이스를 제거하세요

TypeScript의 exhaustive 체크를 활용하면 모든 ApplicationStatus 열거형 값이 처리되고 있음을 컴파일 타임에 보장할 수 있습니다. default 케이스를 제거하면 새로운 상태가 추가될 때 컴파일 오류로 즉시 알 수 있습니다.

-    default:
-      return { status: ApplicationStatus.SUBMITTED, label: '서류검토'};

추가로 TypeScript의 never 타입을 활용한 exhaustive 체크를 추가하는 것을 고려해보세요:

const exhaustiveCheck: never = status;
throw new Error(`Unhandled status: ${exhaustiveCheck}`);
🤖 Prompt for AI Agents
In frontend/src/utils/mapStatusToGroup.ts around lines 16-17, remove the
redundant default branch from the switch so TypeScript can perform exhaustive
checking over ApplicationStatus; instead list and return for every
ApplicationStatus enum member explicitly and add a final exhaustive guard that
assigns the status to a never (exhaustiveCheck: never = status) and throws an
Error with the unhandled status to fail compilation if a new enum member is
added but not handled.

}
}

Expand Down