[feature] 동아리 관리자는 지원자를 확인할 수 있다.#612
Conversation
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
|
Warning
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (6)
frontend/src/apis/applicants/getClubApplicants.ts (1)
4-18: API 함수가 올바르게 구현되었지만 에러 처리 개선 가능합니다.전체적인 구현은 기존 패턴을 잘 따르고 있으나, 9번 줄의 에러 메시지 추출에서 응답이 JSON이 아닐 경우 추가 에러가 발생할 수 있습니다.
다음과 같이 안전한 에러 처리로 개선할 수 있습니다:
if (!response.ok) { console.error(`Failed to fetch: ${response.statusText}`) - throw new Error((await response.json()).message); + try { + const errorData = await response.json(); + throw new Error(errorData.message || response.statusText); + } catch { + throw new Error(response.statusText); + } }frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx (2)
59-59: 인라인 타입 임포트를 상단으로 이동하세요.맵 함수 내에서 인라인으로 타입을 임포트하는 것보다 파일 상단에서 명시적으로 임포트하는 것이 더 명확합니다.
+import { Question } from '@/types/application'; import React from 'react'; import { useParams, useNavigate } from 'react-router-dom'; // ... other imports - {formData.questions.map((q: import('@/types/application').Question, i: number) => ( + {formData.questions.map((q: Question, i: number) => (
46-56: 매직 넘버를 상수로 추출하세요.코딩 가이드라인에 따라 인라인 스타일의 매직 넘버들을 명명된 상수로 교체하는 것이 좋습니다.
+const STYLES = { + PADDING_TOP: 80, + HEADER_GAP: 12, + HEADER_MARGIN_BOTTOM: 16, + ICON_SIZE: 16, +} as const; - <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 16 }}> + <div style={{ display: 'flex', alignItems: 'center', gap: STYLES.HEADER_GAP, marginBottom: STYLES.HEADER_MARGIN_BOTTOM }}> <button onClick={() => navigate(-1)} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0 }} aria-label="뒤로가기" > - <img src={backButtonIcon} alt="뒤로가기" style={{ width: 16, height: 16 }} /> + <img src={backButtonIcon} alt="뒤로가기" style={{ width: STYLES.ICON_SIZE, height: STYLES.ICON_SIZE }} /> </button> </div>frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (2)
2-2: 사용하지 않는 import를 제거하세요.
useGetApplicants훅이 임포트되었지만 컴포넌트에서 사용되지 않고 있습니다.-import { useGetApplicants } from "@/hooks/queries/applicants/useGetApplicants"; import { Applicant, ApplicantsInfo } from "@/types/applicants";
68-72: 매직 넘버를 상수로 추출하세요.코딩 가이드라인에 따라 테이블 컬럼 너비와 체크박스 크기의 매직 넘버들을 명명된 상수로 교체해야 합니다.
+const TABLE_COLUMN_WIDTHS = { + CHECKBOX: 40, + STATUS: 120, + NAME: 160, + DATE: 140, +} as const; + +const CHECKBOX_SIZE = 24; +const CHECKBOX_BORDER_RADIUS = 6; - <styled.Th style={{ width: 40 }}></styled.Th> - <styled.Th style={{ width: 120 }}>현재상태</styled.Th> - <styled.Th style={{ width: 160 }}>이름</styled.Th> + <styled.Th style={{ width: TABLE_COLUMN_WIDTHS.CHECKBOX }}></styled.Th> + <styled.Th style={{ width: TABLE_COLUMN_WIDTHS.STATUS }}>현재상태</styled.Th> + <styled.Th style={{ width: TABLE_COLUMN_WIDTHS.NAME }}>이름</styled.Th> <styled.Th>메모</styled.Th> - <styled.Th style={{ width: 140 }}>제출날짜</styled.Th> + <styled.Th style={{ width: TABLE_COLUMN_WIDTHS.DATE }}>제출날짜</styled.Th> - style={{ width: 24, height: 24, borderRadius: 6 }} + style={{ width: CHECKBOX_SIZE, height: CHECKBOX_SIZE, borderRadius: CHECKBOX_BORDER_RADIUS }}Also applies to: 86-86
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx (1)
118-133: StatusBadge의 조건부 스타일링을 개선하세요.현재 하드코딩된 문자열 비교보다는 상수 맵을 사용하는 것이 더 유지보수가 용이합니다.
+const STATUS_STYLES = { + 서류검토: { background: '#E6F4FB', color: '#222' }, + 면접예정: { background: '#E6FBF0', color: '#222' }, + 합격: { background: '#F5F5F5', color: '#888' }, + default: { background: '#eee', color: '#222' }, +} as const; export const StatusBadge = styled.span<{ status: string }>` display: inline-block; - border-radius: 8px; + border-radius: ${BORDER_RADIUS.SM}px; - padding: 4px 12px; + padding: 4px ${SPACING.MD}px; font-weight: 500; - font-size: 15px; - background: ${({ status }) => - status === "서류검토" - ? "#E6F4FB" - : status === "면접예정" - ? "#E6FBF0" - : status === "합격" - ? "#F5F5F5" - : "#eee"}; - color: ${({ status }) => (status === "합격" ? "#888" : "#222")}; + font-size: ${FONT_SIZES.SM}px; + background: ${({ status }) => STATUS_STYLES[status as keyof typeof STATUS_STYLES]?.background || STATUS_STYLES.default.background}; + color: ${({ status }) => STATUS_STYLES[status as keyof typeof STATUS_STYLES]?.color || STATUS_STYLES.default.color}; `;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
frontend/src/App.tsx(2 hunks)frontend/src/apis/applicants/getClubApplicants.ts(1 hunks)frontend/src/context/AdminClubContext.tsx(2 hunks)frontend/src/hooks/queries/applicants/useGetApplicants.ts(1 hunks)frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsx(1 hunks)frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx(1 hunks)frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx(1 hunks)frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx(1 hunks)frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx(1 hunks)frontend/src/types/applicants.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.
Files:
frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsxfrontend/src/hooks/queries/applicants/useGetApplicants.tsfrontend/src/pages/AdminPage/components/SideBar/SideBar.tsxfrontend/src/App.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsxfrontend/src/context/AdminClubContext.tsxfrontend/src/types/applicants.tsfrontend/src/apis/applicants/getClubApplicants.tsfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx
frontend/**/*.tsx
📄 CodeRabbit Inference Engine (frontend/.cursorrules)
frontend/**/*.tsx: Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Separate significantly different conditional UI/logic into distinct components.
Colocate simple, localized logic or use inline definitions to reduce context switching.
Choose field-level or form-level cohesion based on form requirements.
Break down broad state management into smaller, focused hooks or contexts.
Use component composition instead of props drilling.
Files:
frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsxfrontend/src/pages/AdminPage/components/SideBar/SideBar.tsxfrontend/src/App.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsxfrontend/src/context/AdminClubContext.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx
🧠 Learnings (11)
📓 Common learnings
Learnt from: seongwon030
PR: Moadong/moadong#195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
Learnt from: lepitaaar
PR: Moadong/moadong#406
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:34-38
Timestamp: 2025-05-19T05:45:52.957Z
Learning: The code duplication between createClubApplication and editClubApplication methods in ClubApplyService.java is acknowledged but will be addressed in a future refactoring, as per the developer's plan.
frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsx (3)
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Break down broad state management into smaller, focused hooks or contexts.
Learnt from: seongwon030
PR: #195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use consistent return types for similar functions and hooks.
frontend/src/hooks/queries/applicants/useGetApplicants.ts (2)
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use consistent return types for similar functions and hooks.
Learnt from: seongwon030
PR: #195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx (1)
Learnt from: seongwon030
PR: #195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
frontend/src/App.tsx (2)
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Separate significantly different conditional UI/logic into distinct components.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx (7)
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Separate significantly different conditional UI/logic into distinct components.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Break down broad state management into smaller, focused hooks or contexts.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Colocate simple, localized logic or use inline definitions to reduce context switching.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Choose field-level or form-level cohesion based on form requirements.
Learnt from: seongwon030
PR: #195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
Learnt from: seongwon030
PR: #541
File: frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx:41-43
Timestamp: 2025-07-20T11:48:50.207Z
Learning: moadong 프로젝트는 Next.js가 아닌 순수 React + react-router-dom 기반의 CSR(Client-Side Rendering) SPA이므로, window 객체 사용에 대한 SSR 호환성 문제를 제기하지 않아야 합니다.
frontend/src/context/AdminClubContext.tsx (2)
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Break down broad state management into smaller, focused hooks or contexts.
Learnt from: seongwon030
PR: #195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
frontend/src/types/applicants.ts (3)
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.{ts,tsx} : Replace magic numbers with named constants for clarity.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.{ts,tsx} : Define constants near related logic or ensure names link them clearly.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use consistent return types for similar functions and hooks.
frontend/src/apis/applicants/getClubApplicants.ts (1)
Learnt from: lepitaaar
PR: #406
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:34-38
Timestamp: 2025-05-19T05:45:52.957Z
Learning: The code duplication between createClubApplication and editClubApplication methods in ClubApplyService.java is acknowledged but will be addressed in a future refactoring, as per the developer's plan.
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (10)
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Separate significantly different conditional UI/logic into distinct components.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Break down broad state management into smaller, focused hooks or contexts.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Colocate simple, localized logic or use inline definitions to reduce context switching.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Choose field-level or form-level cohesion based on form requirements.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Use component composition instead of props drilling.
Learnt from: seongwon030
PR: #195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.{ts,tsx} : Use consistent return types for similar functions and hooks.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.{ts,tsx} : Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.{ts,tsx} : Replace magic numbers with named constants for clarity.
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx (8)
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Separate significantly different conditional UI/logic into distinct components.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.{ts,tsx} : Replace magic numbers with named constants for clarity.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.{ts,tsx} : Define constants near related logic or ensure names link them clearly.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Choose field-level or form-level cohesion based on form requirements.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Break down broad state management into smaller, focused hooks or contexts.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Use component composition instead of props drilling.
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Colocate simple, localized logic or use inline definitions to reduce context switching.
🧬 Code Graph Analysis (5)
frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsx (2)
frontend/src/context/AdminClubContext.tsx (1)
useAdminClubContext(30-37)frontend/src/hooks/queries/applicants/useGetApplicants.ts (1)
useGetApplicants(4-10)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx (3)
frontend/src/context/AdminClubContext.tsx (1)
useAdminClubContext(30-37)frontend/src/hooks/queries/application/useGetApplication.ts (1)
useGetApplication(4-10)frontend/src/pages/AdminPage/tabs/ApplicationEditTab/ApplicationEditTab.styles.ts (1)
QuestionContainer(20-25)
frontend/src/context/AdminClubContext.tsx (1)
frontend/src/types/applicants.ts (1)
ApplicantsInfo(19-25)
frontend/src/types/applicants.ts (1)
frontend/src/types/application.ts (1)
AnswerItem(55-58)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (2)
frontend/src/context/AdminClubContext.tsx (1)
useAdminClubContext(30-37)frontend/src/types/applicants.ts (1)
Applicant(27-31)
🪛 Biome (2.1.2)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
[error] 11-11: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.
Hooks should not be called after an early return.
For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
(lint/correctness/useHookAtTopLevel)
🔇 Additional comments (13)
frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx (1)
17-18: 의미적 구분이 명확한 네이밍 변경입니다."지원서 관리"와 "지원자 관리"로 구분하여 각각의 기능을 명확히 표현했고, 새로운 탭이 기존 구조와 일관성 있게 추가되었습니다.
frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsx (3)
6-6: 적절한 훅 임포트입니다.새로운 지원자 데이터 페칭을 위한 훅을 올바르게 임포트했습니다.
10-11: 컨텍스트와 훅 사용이 적절합니다.setApplicantsData를 컨텍스트에서 가져오고, clubId가 없을 때 빈 문자열로 처리하는 것이 올바릅니다.
14-17: 일관된 코드 스타일 적용입니다.if 문에 중괄호를 추가하여 코드 스타일의 일관성을 높였습니다.
frontend/src/hooks/queries/applicants/useGetApplicants.ts (1)
1-10: React Query 훅이 올바르게 구현되었습니다.쿼리 키 패턴, API 함수 호출, 그리고 retry 설정이 모두 적절하며 React Query 모범 사례를 따릅니다.
frontend/src/App.tsx (2)
20-21: 컴포넌트 임포트가 올바릅니다.새로운 지원자 관리 컴포넌트들이 기존 패턴에 맞게 적절히 임포트되었습니다.
81-88: 라우트 구성이 적절합니다.지원자 목록과 상세 페이지를 위한 중첩 라우트가 올바르게 구성되었으며, 기존 인증 및 컨텍스트 래퍼 내에 적절히 배치되었습니다.
frontend/src/context/AdminClubContext.tsx (1)
2-2: 컨텍스트 확장이 잘 구현되었습니다.지원자 데이터 관리를 위한 컨텍스트 확장이 기존 패턴을 잘 따르고 있으며, 타입 안전성도 적절히 보장되고 있습니다.
Also applies to: 7-8, 21-21, 24-24
frontend/src/types/applicants.ts (2)
3-17: 지원 상태 열거형이 잘 정의되었습니다.모든 지원 단계를 포괄하는 상태값들이 명확한 한국어 주석과 함께 체계적으로 정의되어 있습니다.
19-31: 인터페이스 구조가 적절합니다.
ApplicantsInfo와Applicant인터페이스가 기능 요구사항에 맞게 잘 설계되어 있으며, 기존 타입과의 연동도 적절합니다.frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx (1)
14-41: 컴포넌트 로직이 잘 구성되어 있습니다.URL 파라미터 처리, 에러 핸들링, 데이터 매핑 로직이 적절히 구현되어 있으며, 사용자 경험을 고려한 로딩 및 에러 상태 처리가 잘 되어 있습니다.
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (1)
77-100: 테이블 로직과 네비게이션이 잘 구현되어 있습니다.지원자 목록 렌더링, 클릭 이벤트 처리, 체크박스 이벤트 전파 방지 등이 적절히 구현되어 있습니다.
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx (1)
3-23: 스타일 컴포넌트 구조가 잘 설계되었습니다.TopBar, Table 등의 레이아웃 컴포넌트들이 재사용 가능하고 일관된 방식으로 구성되어 있습니다.
Also applies to: 88-116
| useEffect(() => { | ||
| if (clubId && applicantsData) { | ||
| setApplicantsData(applicantsData); | ||
| } | ||
| }, [clubId, applicantsData]); |
There was a problem hiding this comment.
의존성 배열에 setApplicantsData 추가 필요합니다.
useEffect의 의존성 배열에 setApplicantsData가 누락되었습니다. React의 exhaustive-deps 규칙에 따라 추가해야 합니다.
다음과 같이 수정하세요:
useEffect(() => {
if (clubId && applicantsData) {
setApplicantsData(applicantsData);
}
- }, [clubId, applicantsData]);
+ }, [clubId, applicantsData, setApplicantsData]);📝 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.
| useEffect(() => { | |
| if (clubId && applicantsData) { | |
| setApplicantsData(applicantsData); | |
| } | |
| }, [clubId, applicantsData]); | |
| useEffect(() => { | |
| if (clubId && applicantsData) { | |
| setApplicantsData(applicantsData); | |
| } | |
| }, [clubId, applicantsData, setApplicantsData]); |
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsx around lines
19 to 23, the useEffect hook's dependency array is missing setApplicantsData,
which is used inside the effect. To comply with React's exhaustive-deps rule,
add setApplicantsData to the dependency array so the effect properly tracks all
dependencies.
| display: flex; | ||
| align-items: center; | ||
| justify-content: space-between; | ||
| margin-bottom: 32px; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
매직 넘버들을 상수로 추출하세요.
코딩 가이드라인에 따라 스타일에서 사용되는 매직 넘버들을 명명된 상수로 교체해야 합니다.
+const SPACING = {
+ XS: 2,
+ SM: 8,
+ MD: 12,
+ LG: 16,
+ XL: 24,
+ XXL: 32,
+ XXXL: 40,
+} as const;
+
+const FONT_SIZES = {
+ SM: 15,
+ MD: 16,
+ LG: 18,
+ XL: 20,
+ XXL: 28,
+ XXXL: 40,
+} as const;
+
+const BORDER_RADIUS = {
+ SM: 8,
+ MD: 10,
+} as const;
export const TopBar = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
- margin-bottom: 32px;
+ margin-bottom: ${SPACING.XXL}px;
`;
export const PageTitle = styled.h2`
- font-size: 28px;
+ font-size: ${FONT_SIZES.XXL}px;
font-weight: 700;
margin: 0;
`;Also applies to: 28-28, 47-47, 61-61, 67-67, 84-84, 100-101, 114-115, 121-121
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx at
lines 7, 28, 47, 61, 67, 84, 100-101, 114-115, and 121, replace all magic
numbers used in styles with named constants. Define meaningful constant
variables for these numeric values at the top of the file or in a separate
constants file, then use these constants in place of the raw numbers to improve
code readability and maintainability.
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx (2)
22-26: 오류 메시지를 상수로 추출하여 관리하세요.하드코딩된 오류 메시지들을 상수로 분리하면 유지보수성이 향상됩니다.
파일 상단에 상수를 정의하세요:
+ const ERROR_MESSAGES = { + NO_APPLICANTS_DATA: '지원자 데이터를 불러올 수 없습니다.', + NO_FORM_DATA: '지원서 정보를 불러올 수 없습니다.', + NO_APPLICANT_FOUND: '해당 지원자를 찾을 수 없습니다.', + NO_CLUB_INFO: '클럽 정보를 불러올 수 없습니다.' + } as const;그리고 사용처에서 상수를 참조하도록 변경하세요:
- return <div>지원자 데이터를 불러올 수 없습니다.</div>; + return <div>{ERROR_MESSAGES.NO_APPLICANTS_DATA}</div>;
36-41: 답변 매핑 함수를 개선할 수 있습니다.함수 로직은 정확하지만, 성능과 가독성을 위해 소폭 개선할 수 있습니다.
다음과 같이 개선하세요:
- // 답변 매핑 함수 - const getAnswerByQuestionId = (qId: number) => { - return applicant.answers - .filter((ans) => ans.id === qId) - .map((ans) => ans.value); - }; + // 질문 ID에 해당하는 답변들을 반환하는 함수 + const getAnswersByQuestionId = (questionId: number): string[] => { + return applicant.answers + .filter((answer) => answer.id === questionId) + .map((answer) => answer.value); + };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
frontend/src/pages/AdminPage/components/SideBar/SideBar.styles.ts(0 hunks)frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx(1 hunks)frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx(1 hunks)frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx(1 hunks)frontend/src/types/applicants.ts(1 hunks)
💤 Files with no reviewable changes (1)
- frontend/src/pages/AdminPage/components/SideBar/SideBar.styles.ts
✅ Files skipped from review due to trivial changes (1)
- frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- frontend/src/types/applicants.ts
- frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}
📄 CodeRabbit Inference Engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.
Files:
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
frontend/**/*.tsx
📄 CodeRabbit Inference Engine (frontend/.cursorrules)
frontend/**/*.tsx: Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Separate significantly different conditional UI/logic into distinct components.
Colocate simple, localized logic or use inline definitions to reduce context switching.
Choose field-level or form-level cohesion based on form requirements.
Break down broad state management into smaller, focused hooks or contexts.
Use component composition instead of props drilling.
Files:
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
🧠 Learnings (8)
📓 Common learnings
Learnt from: seongwon030
PR: Moadong/moadong#195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
Learnt from: lepitaaar
PR: Moadong/moadong#406
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:34-38
Timestamp: 2025-05-19T05:45:52.957Z
Learning: The code duplication between createClubApplication and editClubApplication methods in ClubApplyService.java is acknowledged but will be addressed in a future refactoring, as per the developer's plan.
📚 Learning: applies to frontend/**/*.tsx : separate significantly different conditional ui/logic into distinct c...
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Separate significantly different conditional UI/logic into distinct components.
Applied to files:
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
📚 Learning: applies to frontend/**/*.tsx : abstract complex logic/interactions into dedicated components or high...
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Applied to files:
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
📚 Learning: applies to frontend/**/*.tsx : break down broad state management into smaller, focused hooks or cont...
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Break down broad state management into smaller, focused hooks or contexts.
Applied to files:
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
📚 Learning: applies to frontend/**/*.tsx : colocate simple, localized logic or use inline definitions to reduce ...
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Colocate simple, localized logic or use inline definitions to reduce context switching.
Applied to files:
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
📚 Learning: applies to frontend/**/*.tsx : choose field-level or form-level cohesion based on form requirements....
Learnt from: CR
PR: Moadong/moadong#0
File: frontend/.cursorrules:0-0
Timestamp: 2025-07-19T05:09:10.702Z
Learning: Applies to frontend/**/*.tsx : Choose field-level or form-level cohesion based on form requirements.
Applied to files:
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
📚 Learning: adminpage.tsx에서 현재 하드코딩된 클럽 id('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다....
Learnt from: seongwon030
PR: Moadong/moadong#195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.
Applied to files:
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
📚 Learning: moadong 프로젝트는 next.js가 아닌 순수 react + react-router-dom 기반의 csr(client-side rendering) spa이므로, window ...
Learnt from: seongwon030
PR: Moadong/moadong#541
File: frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx:41-43
Timestamp: 2025-07-20T11:48:50.207Z
Learning: moadong 프로젝트는 Next.js가 아닌 순수 React + react-router-dom 기반의 CSR(Client-Side Rendering) SPA이므로, window 객체 사용에 대한 SSR 호환성 문제를 제기하지 않아야 합니다.
Applied to files:
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
🔇 Additional comments (3)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx (3)
1-11: 임포트 구문이 잘 구성되어 있습니다.필요한 의존성들이 적절히 임포트되어 있고, 구조가 명확합니다.
28-34: 지원자 검색 로직이 적절합니다.questionId로 지원자를 찾는 로직이 명확하고, null 체크도 적절히 처리되어 있습니다.
85-85: 표준 default export입니다.컴포넌트 export가 적절합니다.
| const { applicantsData, clubId } = useAdminClubContext(); | ||
|
|
||
| // 지원서 질문 목록 fetch | ||
| const { data: formData, isLoading, isError } = useGetApplication(clubId!); |
There was a problem hiding this comment.
clubId의 null 안전성을 개선하세요.
Line 20에서 clubId!의 non-null assertion은 clubId가 undefined일 경우 런타임 오류를 발생시킬 수 있습니다.
다음과 같이 안전하게 처리하세요:
- const { data: formData, isLoading, isError } = useGetApplication(clubId!);
+ const { data: formData, isLoading, isError } = useGetApplication(clubId);
+
+ if (!clubId) {
+ return <div>클럽 정보를 불러올 수 없습니다.</div>;
+ }Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
around lines 17 to 20, the use of non-null assertion operator on clubId can
cause runtime errors if clubId is undefined. To fix this, add a conditional
check to ensure clubId is defined before calling useGetApplication, or handle
the case when clubId is undefined to avoid passing a null or undefined value.
| <PageContainer style={{ paddingTop: '80px' }}> | ||
| {/* FormTitle과 백아이콘을 한 줄에 배치 */} | ||
| <div | ||
| style={{ | ||
| position: 'sticky', | ||
| top: 25, | ||
| zIndex: 10, | ||
| background: '#fff', | ||
| display: 'flex', | ||
| alignItems: 'center', | ||
| gap: 12, | ||
| marginBottom: 16, | ||
| }} | ||
| > | ||
| <button | ||
| onClick={() => navigate(-1)} | ||
| style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0 }} | ||
| aria-label="뒤로가기" | ||
| > | ||
| <img src={backButtonIcon} alt="뒤로가기" style={{ width: 16, height: 16 }} /> | ||
| </button> | ||
| </div> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
인라인 스타일을 styled-components로 추출하세요.
복잡한 인라인 스타일들과 매직 넘버들을 별도의 스타일 컴포넌트로 분리해야 합니다.
스타일을 추출하세요:
+ const HEADER_STICKY_TOP = 25;
+ const HEADER_GAP = 12;
+ const HEADER_MARGIN_BOTTOM = 16;
+ const ICON_SIZE = 16;
+
+ const StickyHeader = styled.div`
+ position: sticky;
+ top: ${HEADER_STICKY_TOP}px;
+ z-index: 10;
+ background: #fff;
+ display: flex;
+ align-items: center;
+ gap: ${HEADER_GAP}px;
+ margin-bottom: ${HEADER_MARGIN_BOTTOM}px;
+ `;
+
+ const BackButton = styled.button`
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: 0;
+ `;
+
+ const BackIcon = styled.img`
+ width: ${ICON_SIZE}px;
+ height: ${ICON_SIZE}px;
+ `;그리고 JSX에서 사용하세요:
- <div
- style={{
- position: 'sticky',
- top: 25,
- zIndex: 10,
- background: '#fff',
- display: 'flex',
- alignItems: 'center',
- gap: 12,
- marginBottom: 16,
- }}
- >
+ <StickyHeader>
- <button
- onClick={() => navigate(-1)}
- style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0 }}
- aria-label="뒤로가기"
- >
- <img src={backButtonIcon} alt="뒤로가기" style={{ width: 16, height: 16 }} />
- </button>
+ <BackButton onClick={() => navigate(-1)} aria-label="뒤로가기">
+ <BackIcon src={backButtonIcon} alt="뒤로가기" />
+ </BackButton>
- </div>
+ </StickyHeader>📝 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.
| <PageContainer style={{ paddingTop: '80px' }}> | |
| {/* FormTitle과 백아이콘을 한 줄에 배치 */} | |
| <div | |
| style={{ | |
| position: 'sticky', | |
| top: 25, | |
| zIndex: 10, | |
| background: '#fff', | |
| display: 'flex', | |
| alignItems: 'center', | |
| gap: 12, | |
| marginBottom: 16, | |
| }} | |
| > | |
| <button | |
| onClick={() => navigate(-1)} | |
| style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0 }} | |
| aria-label="뒤로가기" | |
| > | |
| <img src={backButtonIcon} alt="뒤로가기" style={{ width: 16, height: 16 }} /> | |
| </button> | |
| </div> | |
| // Add near the top of the file, after imports | |
| const HEADER_STICKY_TOP = 25; | |
| const HEADER_GAP = 12; | |
| const HEADER_MARGIN_BOTTOM = 16; | |
| const ICON_SIZE = 16; | |
| const StickyHeader = styled.div` | |
| position: sticky; | |
| top: ${HEADER_STICKY_TOP}px; | |
| z-index: 10; | |
| background: #fff; | |
| display: flex; | |
| align-items: center; | |
| gap: ${HEADER_GAP}px; | |
| margin-bottom: ${HEADER_MARGIN_BOTTOM}px; | |
| `; | |
| const BackButton = styled.button` | |
| background: none; | |
| border: none; | |
| cursor: pointer; | |
| padding: 0; | |
| `; | |
| const BackIcon = styled.img` | |
| width: ${ICON_SIZE}px; | |
| height: ${ICON_SIZE}px; | |
| `; | |
| // In the JSX (lines 46-67) | |
| <PageContainer style={{ paddingTop: '80px' }}> | |
| {/* FormTitle과 백아이콘을 한 줄에 배치 */} | |
| - <div | |
| - style={{ | |
| - position: 'sticky', | |
| - top: 25, | |
| - zIndex: 10, | |
| - background: '#fff', | |
| - display: 'flex', | |
| - alignItems: 'center', | |
| - gap: 12, | |
| - marginBottom: 16, | |
| - }} | |
| - > | |
| - <button | |
| - onClick={() => navigate(-1)} | |
| - style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0 }} | |
| - aria-label="뒤로가기" | |
| - > | |
| - <img src={backButtonIcon} alt="뒤로가기" style={{ width: 16, height: 16 }} /> | |
| - </button> | |
| - </div> | |
| + <StickyHeader> | |
| + <BackButton onClick={() => navigate(-1)} aria-label="뒤로가기"> | |
| + <BackIcon src={backButtonIcon} alt="뒤로가기" /> | |
| + </BackButton> | |
| + </StickyHeader> | |
| </PageContainer> |
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
around lines 46 to 67, the inline styles for the container div and button should
be extracted into styled-components. Create styled components for the sticky
container and the back button with the same styles and replace the inline style
attributes in the JSX with these styled components to improve readability and
maintainability.
| {formData.questions.map((q: import('@/types/application').Question, i: number) => ( | ||
| <QuestionContainer key={q.id} hasError={false}> | ||
| <QuestionAnswerer | ||
| question={q} | ||
| selectedAnswers={getAnswerByQuestionId(q.id)} | ||
| onChange={() => {}} | ||
| /> | ||
| </QuestionContainer> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
타입 임포트를 파일 상단으로 이동하고 읽기 전용 상태를 명확히 하세요.
인라인 타입 임포트와 읽기 전용 구현을 개선할 수 있습니다.
타입 임포트를 상단으로 이동하세요:
+ import type { Question } from '@/types/application';그리고 읽기 전용 상태를 더 명확하게 처리하세요:
+ const READONLY_PROPS = {
+ onChange: () => {},
+ disabled: true,
+ } as const;
- {formData.questions.map((q: import('@/types/application').Question, i: number) => (
+ {formData.questions.map((question: Question, index: number) => (
- <QuestionContainer key={q.id} hasError={false}>
+ <QuestionContainer key={question.id} hasError={false}>
<QuestionAnswerer
- question={q}
- selectedAnswers={getAnswerByQuestionId(q.id)}
- onChange={() => {}}
+ question={question}
+ selectedAnswers={getAnswersByQuestionId(question.id)}
+ {...READONLY_PROPS}
/>
</QuestionContainer>
))}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
around lines 70 to 77, move the inline type import of Question to the top of the
file with other imports. Then, update the formData.questions mapping to use
readonly types or mark the question parameter as readonly to clearly indicate
immutability. This improves code clarity and type safety by separating type
imports and explicitly defining readonly state.
seongwon030
left a comment
There was a problem hiding this comment.
정말 고생하셨습니다 나중에 리팩토링 해 봅시다..!!
#️⃣연관된 이슈
#601
📝작업 내용
지원자 관리 페이지 추가
지원서 가져오기 & 지원서 보기
중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
🫡 참고사항
Summary by CodeRabbit
신규 기능
스타일
버그 수정