Skip to content

[feature] 지원자를 검색할 수 있다#650

Merged
seongwon030 merged 6 commits intodevelop-fefrom
feature/#648-search-applicants-MOA-154
Aug 13, 2025
Merged

[feature] 지원자를 검색할 수 있다#650
seongwon030 merged 6 commits intodevelop-fefrom
feature/#648-search-applicants-MOA-154

Conversation

@seongwon030
Copy link
Member

@seongwon030 seongwon030 commented Aug 13, 2025

#️⃣연관된 이슈

ex) #648

📝작업 내용

이번 PR에서 작업한 내용을 간략히 설명해주세요(이미지/동영상 첨부 가능)

  • 검색 입력 UI를 공통 컴포넌트(SearchField)로 분리해 재사용성과 일관성 확보 a4187df
  • 지원자 목록 실시간 검색 적용 935d106
  • 지원서 헤더 UI 수정 93712ab
스크린샷 2025-08-13 16 45 41

중점적으로 리뷰받고 싶은 부분(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요?

논의하고 싶은 부분(선택)

논의하고 싶은 부분이 있다면 작성해주세요.

🫡 참고사항

Summary by CodeRabbit

  • New Features

    • 지원자 탭에 클라이언트 사이드 검색 및 키워드 기반 필터 추가, 요약 카드 대시보드 도입
    • 메인 페이지에 검색 실행 동작 추가: 홈으로 이동, 키워드·카테고리 반영, 이벤트 트래킹
  • Refactor

    • 헤더에서 검색 컴포넌트 참조 경로 변경 및 공용 SearchField 컴포넌트로 교체
  • Style

    • 지원자 탭 헤더 정렬 개선, 행 중앙 정렬 및 클릭 가능 처리, 필터 영역 레이아웃 정비

- SearchField.styles.ts 도입으로 페이지 전용 스타일 의존 제거
- SearchField가 공통 스타일을 직접 참조하도록 변경
- 검색 입력 UI의 재사용성/일관성 향상
@seongwon030 seongwon030 self-assigned this Aug 13, 2025
@seongwon030 seongwon030 added ✨ Feature 기능 개발 💻 FE Frontend labels Aug 13, 2025
@vercel
Copy link

vercel bot commented Aug 13, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Project Deployment Preview Comments Updated (UTC)
moadong Ready Preview Comment Aug 13, 2025 7:58am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 13, 2025

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. Received: "**" at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

검색 UI를 공통 SearchField로 대체하고 ApplicantsTab에 클라이언트 측 검색 및 요약 대시보드를 추가했다. 기존 공통 SearchBox는 삭제되고 MainPage 전용 SearchBox로 재배치되며 Header의 import 경로가 갱신되었다. ApplicantsTab 행 구조와 스타일이 변경되었다.

Changes

Cohort / File(s) Summary
공통 SearchField 추가 + ApplicantsTab 연동
frontend/src/components/common/SearchField/SearchField.tsx, frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx, frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.ts
공통 SearchField 컴포넌트 추가(컨트롤드 입력, 포커스/블러, onSubmit 처리). ApplicantsTab에 keyword 상태 및 useMemo 기반 필터링 적용, SearchField 연결, 요약 카드 추가, 테이블 행 구조(체크박스·상태 배지·메모·생성일) 및 스타일 수정.
SearchBox 리로케이트 및 Header 경로 갱신
frontend/src/components/common/Header/Header.tsx, frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx, frontend/src/components/common/SearchBox/SearchBox.tsx
기존 공통 SearchBox 파일 삭제. MainPage 전용 SearchBox 신규 추가(검색 컨텍스트·카테고리 리셋·라우팅·믹스패널 트래킹 포함). Header의 SearchBox import 경로를 MainPage용 컴포넌트로 변경.

Sequence Diagram(s)

sequenceDiagram
  participant U as 사용자
  participant SF as SearchField
  participant AT as ApplicantsTab

  U->>SF: 입력 변경
  SF-->>AT: onChange(value)
  AT->>AT: keyword 업데이트 (useState)
  AT->>AT: filteredApplicants = useMemo(필터)
  U->>SF: 제출(버튼/엔터)
  SF-->>AT: onSubmit()
  AT->>AT: 필터링 적용(클라이언트 사이드)
Loading
sequenceDiagram
  participant U as 사용자
  participant SB as MainPage SearchBox
  participant SC as SearchContext
  participant CC as CategoryContext
  participant R as Router
  participant MX as Mixpanel

  U->>SB: 제출
  SB->>R: 현재 경로 확인 및 '/'로 이동(필요 시)
  SB->>SC: setKeyword(inputValue), setIsSearching(true)
  SB->>CC: setSelectedCategory('all')
  SB->>MX: track("Search Executed", { inputValue, pathname })
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15–20 minutes

Assessment against linked issues

Objective Addressed Explanation
지원자를 검색할 수 있다 (MOA-154)
지원자 검색 UI를 공통 검색창 컴포넌트로 변경 (MOA-154)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
MainPage 전용 SearchBox 추가 및 검색 실행 로직/믹스패널 트래킹 추가 (frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx) MOA-154는 ApplicantsTab 대상 변경이 목적이며 MainPage 전용 트래킹/라우팅 로직은 이 이슈의 요구사항에 포함되지 않음.
Header의 SearchBox import 경로 변경 (frontend/src/components/common/Header/Header.tsx) ApplicantsTab 검색 도입의 직접적 요구사항이 아니며 파일 재배치에 따른 파생 변경으로 보임.

Possibly related issues

  • #645: 공통 SearchField 도입 및 ApplicantsTab 연동 관련 — 동일한 공통 컴포넌트 적용 목적.
  • #642: ApplicantsTab 클라이언트 검색 구현 관련 — 클라이언트 측 필터링 로직과 연관.
  • [feature] MOA-154 지원자를 검색할 수 있다 #648: ApplicantsTab에서 공통 SearchField로 UI 교체 요구 — UI 교체 목표와 일치.

Possibly related PRs

Suggested labels

🔨 Refactor, 🎨 Design

Suggested reviewers

  • lepitaaar
  • Zepelown
  • oesnuj
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#648-search-applicants-MOA-154

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🔭 Outside diff range comments (3)
frontend/src/components/common/Header/Header.tsx (2)

59-76: 모바일 헤더에서 관리 페이지에도 SearchBox가 노출되는 문제

데스크톱 헤더는 isAdminPage에서 검색창을 숨기지만(Line 101), 모바일 헤더는 항상 를 렌더링합니다. SearchBox는 SearchContext/CategoryContext가 필요하므로, Admin 영역이 해당 Provider로 감싸져 있지 않다면 런타임 에러를 유발합니다. UX 일관성 측면에서도 Admin에서 검색창을 숨기는 현재 정책과 맞지 않습니다.

다음과 같이 MobileHeader에 isAdminPage를 전달하여 조건부 렌더링하세요.

 interface MobileHeaderProps {
   handleHomeClick: (device: 'mobile' | 'desktop') => void;
   handleMenuClick: () => void;
+  isAdminPage: boolean;
 }

 const MobileHeader = ({
   handleHomeClick,
   handleMenuClick,
+  isAdminPage,
 }: MobileHeaderProps) => (
   <Styled.MobileHeaderContainer>
     <Styled.MobileHeaderWrapper>
       <Styled.MobileMainIcon>
         <img
           src={MobileMainIcon}
           alt='홈 버튼'
           onClick={() => handleHomeClick('mobile')}
         />
       </Styled.MobileMainIcon>
-      <SearchBox />
+      {!isAdminPage && <SearchBox />}
       <Styled.MobileMenu aria-label='메뉴 버튼'>
         <img src={MenuBar} alt='메뉴 버튼' onClick={handleMenuClick} />
       </Styled.MobileMenu>
     </Styled.MobileHeaderWrapper>
   </Styled.MobileHeaderContainer>
 );

헤더 컴포넌트 사용처도 함께 수정해 주세요(아래 별도 코멘트 참고).


118-137: MobileHeader에 isAdminPage 전달 필요

위 수정에 맞춰 Header에서 MobileHeader 호출부도 isAdminPage를 전달해주세요.

   return isMobile ? (
     <>
       <MobileHeader
         handleHomeClick={handleHomeClick}
         handleMenuClick={openMenu}
+        isAdminPage={isAdminPage}
       />
       <MobileMenuDrawer
         isOpen={isMenuOpen}
         onClose={closeMenu}
         handleHomeClick={handleHomeClick}
         handleIntroduceClick={handleIntroduceClick}
       />
     </>
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.ts (1)

129-136: 중첩 삼항 연산자 제거로 가독성 향상

가이드라인(frontend/**/*.{ts,tsx})에 따라 중첩 삼항 대신 명시적 매핑을 권장합니다. 스타일 계산을 상수 매핑으로 정리하면 읽기 쉬워지고 오타를 줄일 수 있습니다.

아래처럼 매핑 상수와 제한된 타입을 도입해 주세요.

-export const ApplicantStatusBadge = styled.span<{ status: string }>`
+type ApplicantUiStatus = '서류검토' | '면접예정' | '합격';
+const STATUS_BG: Record<ApplicantUiStatus, string> = {
+  서류검토: '#E6F4FB',
+  면접예정: '#E6FBF0',
+  합격: '#F5F5F5',
+};
+
+export const ApplicantStatusBadge = styled.span<{ status: ApplicantUiStatus }>`
   display: inline-block;
   border-radius: 8px;
   padding: 4px 12px;
   font-weight: 500;
   font-size: 15px;
-  background: ${({ status }) =>
-    status === '서류검토'
-      ? '#E6F4FB'
-      : status === '면접예정'
-        ? '#E6FBF0'
-        : status === '합격'
-          ? '#F5F5F5'
-          : '#eee'};
+  background: ${({ status }) => STATUS_BG[status] ?? '#eee'};
   color: ${({ status }) => (status === '합격' ? '#888' : '#222')};
 `;
🧹 Nitpick comments (4)
frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx (2)

26-30: window 전역 대신 useLocation 값 사용

SSR/테스트 호환성과 일관성을 위해 window.location.pathname 대신 useLocation으로 얻은 location.pathname을 사용하세요.

-    trackEvent('Search Executed', {
-      inputValue: inputValue,
-      page: window.location.pathname,
-    });
+    trackEvent('Search Executed', {
+      inputValue,
+      page: location.pathname,
+    });

33-39: 불필요한 람다 제거로 간결화

setInputValue는 동일 시그니처이므로 그대로 전달해도 됩니다.

   return (
     <SearchField
       value={inputValue}
-      onChange={(v) => setInputValue(v)}
+      onChange={setInputValue}
       onSubmit={handleSearch}
       placeholder='어떤 동아리를 찾으세요?'
       ariaLabel='동아리 검색창'
     />
   );
frontend/src/components/common/SearchField/SearchField.tsx (1)

48-49: ALT 텍스트 현지화 및 일관성

다른 레이블이 한국어인 만큼 이미지 alt도 한국어로 통일하는 것이 좋습니다.

-        <img src={searchButtonIcon} alt='Search Button' />
+        <img src={searchButtonIcon} alt='검색 버튼' />
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (1)

147-156: 날짜 포맷 유틸로 분리 제안

IIFE 내에서 직접 포맷팅하기보다 유틸 함수로 분리하면 재사용과 테스트가 용이합니다. 또한 타임존 고려(Date만 사용 시 로컬 타임존 영향)를 명시적으로 다룰 수 있습니다.

원한다면 공용 utils/date.ts에 formatYmd(dateString: string): string 형태로 생성해드릴 수 있습니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25fbe15 and 935d106.

📒 Files selected for processing (6)
  • frontend/src/components/common/Header/Header.tsx (1 hunks)
  • frontend/src/components/common/SearchBox/SearchBox.tsx (0 hunks)
  • frontend/src/components/common/SearchField/SearchField.tsx (1 hunks)
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.ts (2 hunks)
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (4 hunks)
  • frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx (1 hunks)
💤 Files with no reviewable changes (1)
  • frontend/src/components/common/SearchBox/SearchBox.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/components/common/Header/Header.tsx
  • frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.ts
  • frontend/src/components/common/SearchField/SearchField.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.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/components/common/Header/Header.tsx
  • frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx
  • frontend/src/components/common/SearchField/SearchField.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
🧬 Code Graph Analysis (2)
frontend/src/pages/MainPage/components/SearchBox/SearchBox.tsx (2)
frontend/src/context/SearchContext.tsx (1)
  • useSearch (39-45)
frontend/src/context/CategoryContext.tsx (1)
  • useCategory (11-18)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (1)
frontend/src/types/applicants.ts (1)
  • Applicant (27-33)
🪛 Biome (2.1.2)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx

[error] 36-36: 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 (3)
frontend/src/components/common/Header/Header.tsx (1)

3-3: SearchBox 경로 변경 LGTM

새 경로로의 import 변경이 적절합니다. 새 SearchBox가 default export임도 일관적입니다.

frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.ts (1)

109-116: 클릭 가능한 행 표시 추가 LGTM

테이블 행에 cursor: pointer와 hover 배경을 추가해 상호작용 affordance가 좋아졌습니다.

frontend/src/components/common/SearchField/SearchField.tsx (1)

31-33: SearchBoxContainer는 이미 form으로 정의되어 있습니다
SearchField.styles.ts에서 styled.form으로 선언된 것이 확인되어 onSubmit 핸들러가 정상 동작합니다. 추가 변경은 필요하지 않습니다.

@seongwon030 seongwon030 linked an issue Aug 13, 2025 that may be closed by this pull request
1 task
Copy link
Contributor

@lepitaaar lepitaaar left a comment

Choose a reason for hiding this comment

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

수고하셧씁니다

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (4)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (4)

96-102: SearchField의 불필요한 blur 동작 개선

onSubmit에 빈 함수를 전달하면서 autoBlur 기본값(true)이 적용되어, Enter 키 입력 시 불필요하게 포커스가 해제됩니다. 실시간 검색에서는 이런 동작이 사용자 경험을 저해할 수 있습니다.

   <SearchField
     value={keyword}
     onChange={setKeyword}
-    onSubmit={() => {}}
+    onSubmit={() => {}}
+    autoBlur={false}
     placeholder='지원자 이름을 입력해주세요'
     ariaLabel='지원자 검색창'
   />

30-31: 훅 호출 순서 위반으로 인한 React 규칙 위배

if (!clubId) return null;useMemo 훅 호출보다 먼저 위치하여 React 훅 규칙을 위반합니다. 모든 훅은 조건문 없이 컴포넌트 최상단에서 호출되어야 합니다.

   const { clubId, applicantsData } = useAdminClubContext();
   const [keyword, setKeyword] = useState('');
-  if (!clubId) return null;

   const applicants: Applicant[] = applicantsData?.applicants ?? [];
   const filteredApplicants = useMemo(() => {
     const lower = keyword.trim().toLowerCase();
     if (!lower) return applicants;
     return applicants.filter((item) => {
       const name = (item.answers?.[0]?.value ?? '').toString().toLowerCase();
       return name.includes(lower);
     });
   }, [applicants, keyword]);
+
+  if (!clubId) return null;

122-126: 배열 인덱스를 key로 사용하지 말 것

리스트 아이템의 key로 배열 인덱스를 사용하면 재정렬이나 필터링 시 React가 컴포넌트를 잘못 매칭하여 UI 버그가 발생할 수 있습니다. 고유한 item.id를 사용하세요.

-          {filteredApplicants.map((item: Applicant, index: number) => (
+          {filteredApplicants.map((item: Applicant) => (
             <Styled.ApplicantTableRow
-              key={index}
+              key={item.id}
               onClick={() => navigate(`/admin/applicants/${item.id}`)}
             >

143-145: answers 배열 접근 시 안전성 확보 필요

item.answers[0].value에 직접 접근하면 answers가 빈 배열이거나 undefined일 때 런타임 에러가 발생할 수 있습니다. 옵셔널 체이닝과 기본값을 사용하세요.

               <Styled.ApplicantTableCol>
-                {item.answers[0].value}
+                {item.answers?.[0]?.value ?? '-'}
               </Styled.ApplicantTableCol>
🧹 Nitpick comments (3)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.ts (1)

73-78: FilterContainer 네이밍 개선 필요

FilterContainer는 너무 일반적인 이름입니다. 이 컴포넌트가 지원자 탭 전용이라는 것을 명확히 하기 위해 ApplicantFilterContainer로 변경하는 것이 좋습니다.

-export const FilterContainer = styled.div`
+export const ApplicantFilterContainer = styled.div`
   display: flex;
   justify-content: center;
   align-items: center;
   gap: 8px;
 `;
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (2)

127-135: 체크박스 동작 미완성

체크박스가 UI에만 존재하고 실제 선택 상태 관리나 일괄 작업 기능이 구현되지 않았습니다. 향후 기능 구현이 필요합니다.

체크박스를 통한 다중 선택 및 일괄 작업(일괄 상태 변경, 일괄 삭제 등) 기능 구현이 필요하신가요? 관련 상태 관리 코드를 생성해드릴 수 있습니다.


89-95: 비활성화된 필터 옵션 처리 필요

필터 셀렉트 박스들이 단일 옵션만 가지고 있어 실제로 동작하지 않습니다. 추가 옵션이 구현될 때까지 disabled 처리하거나 TODO 주석을 추가하세요.

           <Styled.FilterContainer>
-            <Styled.ApplicantFilterSelect>
+            {/* TODO: 필터 옵션 구현 예정 */}
+            <Styled.ApplicantFilterSelect disabled>
               <option>전체</option>
             </Styled.ApplicantFilterSelect>
-            <Styled.ApplicantFilterSelect>
+            <Styled.ApplicantFilterSelect disabled>
               <option>제출순</option>
             </Styled.ApplicantFilterSelect>
           </Styled.FilterContainer>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 935d106 and 93712ab.

📒 Files selected for processing (2)
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.ts (2 hunks)
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (4 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/tabs/ApplicantsTab/ApplicantsTab.styles.ts
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.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/ApplicantsTab.tsx
🧬 Code Graph Analysis (1)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (1)
frontend/src/types/applicants.ts (1)
  • Applicant (27-33)
🪛 Biome (2.1.2)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx

[error] 35-35: 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)

- answers가 비어 있는 경우 대비해 String(item.answers?.[0]?.value ?? '') 사용
- null/undefined도 안전하게 문자열화 후 toLowerCase 적용
- 드물게 answers[0]가 없는 데이터로 인한 크래시 예방
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (2)

124-127: 리스트 key에 index 사용은 버그를 유발 — 고유 id로 교체 필요

필터링/정렬 시 index key는 재사용 문제로 렌더링 버그를 유발할 수 있습니다. 안정적인 고유 식별자인 item.id를 사용하세요.

-          {filteredApplicants.map((item: Applicant, index: number) => (
+          {filteredApplicants.map((item: Applicant) => (
             <Styled.ApplicantTableRow
-              key={index}
+              key={item.id}
               onClick={() => navigate(`/admin/applicants/${item.id}`)}
             >

146-147: answers 접근 시 런타임 에러 가능 — 안전한 옵셔널 체이닝 적용

answers 또는 answers[0]이 없을 경우 접근 시 크래시가 발생합니다. 필터 로직에서는 안전하게 처리하셨으므로 렌더링도 동일하게 맞추세요.

-              <Styled.ApplicantTableCol>
-                {item.answers[0].value}
-              </Styled.ApplicantTableCol>
+              <Styled.ApplicantTableCol>
+                {item.answers?.[0]?.value ?? '-'}
+              </Styled.ApplicantTableCol>
🧹 Nitpick comments (3)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (3)

89-104: SearchField에 no-op onSubmit 전달 제거 제안

실시간 필터만 사용한다면 onSubmit={() => {}}는 불필요합니다. 유지하면 코드 소음이 늘고 오해를 일으킬 수 있습니다. autoBlur={false}는 잘 설정되어 있습니다.

아래처럼 onSubmit을 제거하세요.

           <SearchField
             value={keyword}
             onChange={setKeyword}
-            onSubmit={() => {}}
             autoBlur={false}
             placeholder='지원자 이름을 입력해주세요'
             ariaLabel='지원자 검색창'
           />

130-136: 체크박스 인라인 스타일의 매직 넘버 상수화

가이드라인(frontend/**/*.{ts,tsx})에 따라 매직 넘버를 상수로 치환해 의미를 드러내세요.

                 <input
                   type='checkbox'
-                  style={{ width: 24, height: 24, borderRadius: 6 }}
+                  style={{ width: CHECKBOX_SIZE, height: CHECKBOX_SIZE, borderRadius: CHECKBOX_RADIUS }}
                   onClick={(e: React.MouseEvent<HTMLInputElement>) =>
                     e.stopPropagation()
                   }
                 />

파일 상단(컴포넌트 근처)에 상수를 선언하세요:

// 파일 상단 (import 아래)
const CHECKBOX_SIZE = 24;
const CHECKBOX_RADIUS = 6;

139-144: status 매핑 중복 호출 제거 제안

applicationStatusMapping(item.status)를 두 번 호출합니다. 가독성을 위해 한 번만 계산해 변수로 재사용하세요.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 93712ab and 01615cc.

📒 Files selected for processing (1)
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (4 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/tabs/ApplicantsTab/ApplicantsTab.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/ApplicantsTab.tsx
🧬 Code Graph Analysis (1)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (1)
frontend/src/types/applicants.ts (1)
  • Applicant (27-33)
🔇 Additional comments (1)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (1)

30-41: 훅 순서 문제 해결 및 필터 로직 적절

early return을 훅 이후로 이동해 훅 규칙 위반이 해소되었고, useMemo 기반의 소문자/trim 검색도 간결하고 명확합니다.

Comment on lines +150 to +160
{
// 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}`;
})()
}
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 제거하고 유틸 함수로 분리

인라인 IIFE는 가독성과 재사용성에 불리합니다. 공용 유틸로 추출해 사용하세요.

아래처럼 포맷팅 호출로 대체:

               <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}`;
-                  })()
-                }
+                {formatDate(item.createdAt)}
               </Styled.ApplicantTableCol>

임포트 추가:

 import React, { useMemo, useState } from 'react';
+import { formatDate } from '@/utils/date';
 import * as Styled from './ApplicantsTab.styles';
 import { useNavigate } from 'react-router-dom';
 import SearchField from '@/components/common/SearchField/SearchField';

유틸 파일 생성:

// frontend/src/utils/date.ts
export const formatDate = (dateString: string): string => {
  const date = new Date(dateString);
  if (Number.isNaN(date.getTime())) return '-';
  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}`;
};

Also applies to: 3-6

🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx around
lines 150-160, remove the inline IIFE date formatting and replace it with a call
to a shared util; create frontend/src/utils/date.ts exporting
formatDate(dateString: string): string which parses the date, returns '-' for
invalid dates, and formats as yyyy-mm-dd, then import formatDate into
ApplicantsTab.tsx and replace the IIFE with formatDate(item.createdAt); apply
the same replacement to the other similar IIFE occurrences in this file (lines
referenced as 3-6) so all inline date formatting uses the shared util.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FE Frontend ✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] MOA-154 지원자를 검색할 수 있다

2 participants

Comments