Skip to content

[feature] 동아리 관리자는 지원자를 확인할 수 있다.#612

Merged
lepitaaar merged 12 commits intodevelop-fefrom
feature/info-applicants
Aug 7, 2025
Merged

[feature] 동아리 관리자는 지원자를 확인할 수 있다.#612
lepitaaar merged 12 commits intodevelop-fefrom
feature/info-applicants

Conversation

@lepitaaar
Copy link
Contributor

@lepitaaar lepitaaar commented Jul 30, 2025

#️⃣연관된 이슈

#601

📝작업 내용

지원자 관리 페이지 추가
지원서 가져오기 & 지원서 보기

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

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

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

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

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

🫡 참고사항

Summary by CodeRabbit

  • 신규 기능

    • 관리자 페이지에 "지원자 관리" 탭과 지원자 상세 페이지가 추가되었습니다.
    • 지원자 목록, 상태별 통계, 필터 및 검색 UI가 제공됩니다.
    • 지원자 클릭 시 상세 답변을 확인할 수 있습니다.
    • 클럽별 지원자 데이터를 조회하는 기능이 도입되었습니다.
    • 지원자 데이터를 관리하는 컨텍스트 및 관련 훅이 추가되었습니다.
  • 스타일

    • 지원자 관리 페이지에 전용 스타일이 적용되어 시각적으로 개선되었습니다.
  • 버그 수정

    • 기존 "지원 관리" 탭이 "지원서 관리"로 이름이 변경되었습니다.

@lepitaaar lepitaaar added ✨ Feature 기능 개발 📬 API 서버 API 통신 작업 💻 FE Frontend labels Jul 30, 2025
@vercel
Copy link

vercel bot commented Jul 30, 2025

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

Name Status Preview Comments Updated (UTC)
moadong ✅ Ready (Inspect) Visit Preview 💬 Add feedback Aug 6, 2025 3:08pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 30, 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

Warning

Rate limit exceeded

@lepitaaar has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 23 minutes and 1 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 99f7032 and 1129623.

📒 Files selected for processing (1)
  • frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx (2 hunks)
## Walkthrough

이 변경사항은 동아리 지원자 관리 기능을 프론트엔드에 추가합니다. `/admin/applicants``/admin/applicants/:questionId` 경로가 새로 도입되어 지원자 목록과 상세 정보를 볼 수 있습니다. 관련 API 호출, 컨텍스트 상태, 타입, 스타일, 컴포넌트, 훅이 추가 및 확장되었습니다.

## Changes

| Cohort / File(s) | Change Summary |
|---|---|
| **라우팅 및 네비게이션 확장**<br>`frontend/src/App.tsx`, `frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx` | `/admin/applicants``/admin/applicants/:questionId` 경로 추가, 사이드바에 "지원자 관리" 탭 신설 및 기존 "지원 관리" → "지원서 관리"로 명칭 변경 |
| **지원자 API 및 데이터 훅**<br>`frontend/src/apis/applicants/getClubApplicants.ts`, `frontend/src/hooks/queries/applicants/useGetApplicants.ts` | 지원자 목록을 동아리별로 조회하는 API 함수 및 React Query 기반 데이터 패칭 훅 추가 |
| **컨텍스트 확장**<br>`frontend/src/context/AdminClubContext.tsx` | 지원자 데이터(`applicantsData`)와 setter를 컨텍스트에 추가, 관련 타입 및 Provider 값 확장 |
| **PrivateRoute 데이터 연동**<br>`frontend/src/pages/AdminPage/auth/PrivateRoute/PrivateRoute.tsx` | clubId 기반 지원자 데이터 패칭 및 컨텍스트에 저장하는 로직 effect로 추가 |
| **지원자 탭 및 상세 페이지**<br>`frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx`, `frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx` | 지원자 목록 및 상세정보를 보여주는 신규 컴포넌트 추가, 목록에서 상세 페이지로 네비게이션 구현 |
| **지원자 탭 스타일**<br>`frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsx` | 지원자 탭 UI를 위한 styled-components 스타일 정의 (레이아웃, 카드, 테이블, 뱃지 등) |
| **타입 정의**<br>`frontend/src/types/applicants.ts` | 지원자 상태 enum, 지원자/지원자 정보 인터페이스 등 타입 정의 신설 |
| **사이드바 스타일 미세 수정**<br>`frontend/src/pages/AdminPage/components/SideBar/SideBar.styles.ts` | `SidebarWrapper` 컴포넌트 내 불필요한 빈 줄 제거 |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Admin as Admin User
    participant UI as ApplicantsTab/ApplicantDetailPage
    participant Context as AdminClubContext
    participant Hook as useGetApplicants
    participant API as getClubApplicants
    participant Server as Backend API

    Admin->>UI: /admin/applicants 접속
    UI->>Context: clubId, applicantsData 조회
    UI->>Hook: useGetApplicants(clubId)
    Hook->>API: getClubApplicants(clubId)
    API->>Server: GET /api/clubs/{clubId}/applicants
    Server-->>API: applicants 데이터 반환
    API-->>Hook: 데이터 반환
    Hook-->>Context: setApplicantsData(data)
    Context-->>UI: applicantsData 제공
    Admin->>UI: 지원자 행 클릭
    UI->>UI: /admin/applicants/:questionId로 이동
    UI->>Context: applicantsData에서 해당 지원자 정보 조회
    UI->>Server: (필요시) 지원서 질문 데이터 패칭
    UI-->>Admin: 상세 답변 및 정보 표시

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20분

Possibly related PRs

Suggested reviewers

  • seongwon030
  • Zepelown

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings
<details>
<summary>🧪 Generate unit tests</summary>

- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Create PR with unit tests
- [ ] <!-- {"checkboxId": "07f1e7d6-8a8e-4e23-9900-8731c2c87f58", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Post copyable unit tests in a comment
- [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} -->   Commit unit tests in branch `feature/info-applicants`

</details>

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=Moadong/moadong&utm_content=612):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `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. Examples:
  - `@coderabbitai explain this code block.`
- 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 src/utils.ts and explain its main purpose.`
  - `@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](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai generate unit tests` to generate unit tests for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### 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.

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5494b01 and 2519381.

📒 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.tsx
  • frontend/src/hooks/queries/applicants/useGetApplicants.ts
  • frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx
  • frontend/src/App.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
  • frontend/src/context/AdminClubContext.tsx
  • frontend/src/types/applicants.ts
  • frontend/src/apis/applicants/getClubApplicants.ts
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
  • frontend/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.tsx
  • frontend/src/pages/AdminPage/components/SideBar/SideBar.tsx
  • frontend/src/App.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage.tsx
  • frontend/src/context/AdminClubContext.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
  • frontend/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: 인터페이스 구조가 적절합니다.

ApplicantsInfoApplicant 인터페이스가 기능 요구사항에 맞게 잘 설계되어 있으며, 기존 타입과의 연동도 적절합니다.

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

Comment on lines +19 to +23
useEffect(() => {
if (clubId && applicantsData) {
setApplicantsData(applicantsData);
}
}, [clubId, applicantsData]);
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

의존성 배열에 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.

Suggested change
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;
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

매직 넘버들을 상수로 추출하세요.

코딩 가이드라인에 따라 스타일에서 사용되는 매직 넘버들을 명명된 상수로 교체해야 합니다.

+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.

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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2b6f5cd and 99f7032.

📒 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가 적절합니다.

Comment on lines +17 to +20
const { applicantsData, clubId } = useAdminClubContext();

// 지원서 질문 목록 fetch
const { data: formData, isLoading, isError } = useGetApplication(clubId!);
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

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.

Comment on lines +46 to +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>
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

인라인 스타일을 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.

Suggested change
<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.

Comment on lines +70 to +77
{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>
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

타입 임포트를 파일 상단으로 이동하고 읽기 전용 상태를 명확히 하세요.

인라인 타입 임포트와 읽기 전용 구현을 개선할 수 있습니다.

타입 임포트를 상단으로 이동하세요:

+ 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.

Copy link
Member

@Zepelown Zepelown 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
Member

@seongwon030 seongwon030 left a comment

Choose a reason for hiding this comment

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

정말 고생하셨습니다 나중에 리팩토링 해 봅시다..!!

@lepitaaar lepitaaar merged commit 5382f19 into develop-fe Aug 7, 2025
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📬 API 서버 API 통신 작업 💻 FE Frontend ✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments