Conversation
|
Warning
|
| Cohort / File(s) | Change Summary |
|---|---|
API 배치 업데이트 전환frontend/src/apis/application/updateApplicantDetail.ts |
단건 → 배치 시그니처로 변경(UpdateApplicantParams[]), 엔드포인트 /api/club/{clubId}/applicant 로 전환, 응답 ok 검사 및 result.data 반환, 예외 처리 및 기본 내보내기 추가 |
리액트 훅 업데이트frontend/src/hooks/queries/applicants/useUpdateApplicant.ts |
훅 파라미터에서 applicantId 제거, mutationFn이 UpdateApplicantParams[] 수용, 새로운 API 시그니처 사용, 성공 시 ['clubApplicants'] 캐시 무효화 |
페이지 로직(상세/목록) 반영frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx, frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx |
mutate를 배열 페이로드로 호출하도록 변경(각 항목에 applicantId 포함), 체크박스 기반 배치 상태 변경 기능 및 외부 클릭으로 드롭다운 닫기 처리 추가, 선택 상태에 따른 버튼/드롭다운 활성화 제어 |
스타일/드롭다운 UI 교체frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.ts |
StatusSelect를 select → p로 변경(비활성 처리), 커스텀 드롭다운(StatusSelectMenu, StatusSelectMenuItem) 추가 및 관련 스타일 변경 |
타입 추가frontend/src/types/applicants.ts |
UpdateApplicantParams 타입 신설({ memo, status, applicantId }) |
Sequence Diagram(s)
sequenceDiagram
actor Admin as 관리자 UI
participant Page as ApplicantsTab / ApplicantDetailPage
participant Hook as useUpdateApplicant
participant API as updateApplicantDetail
participant BE as 서버 (/api/club/{clubId}/applicant)
participant Cache as Query Cache
Admin->>Page: 여러 항목 선택 후 상태/메모 변경
Page->>Hook: mutate([ { applicantId, status, memo }, ... ])
Hook->>API: updateApplicantDetail(payload[], clubId)
API->>BE: PUT /api/club/{clubId}/applicant (JSON Array)
BE-->>API: 200 OK { data }
API-->>Hook: result.data
Hook->>Cache: invalidate(['clubApplicants'])
Cache-->>Page: 데이터 재패치
Page-->>Admin: UI 갱신
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~60 minutes
Assessment against linked issues
| Objective | Addressed | Explanation |
|---|---|---|
| 여러명의 지원서를 변경할 수 있다 (MOA-199) | ✅ | |
| 상태변경 API 연동 (MOA-199) | ✅ |
Possibly related issues
- [refactor] 지원서를 여러개 수정할 수 있다. -BE #703: 서버측 /applicant 배치 업데이트와 프론트의 배치 호출이 직접 연계됨.
- [feature] MOA-199 여러명의 지원서를 변경할 수 있다 #701: 다중 지원서 상태 변경 기능 범위 일치.
- [refactor] MOA-200 상태변경할 지원자를 여러명 받을 수 있다. #702: 배치 업데이트 흐름과 타입 추가가 해당 이슈 목적과 부합.
Possibly related PRs
- [refactor] 지원서를 여러개 수정할 수 있다. -BE #703: 백엔드의 배치 업데이트 도입과 1:1 매칭 가능.
- [feature] 지원서의 정보를 수정하는 즉시 서버와 동기화된다. #670: 동일 훅(useUpdateApplicant) 경로 변경과 연관.
- [feature] 지원자의 상태와 메모를 변경한다. #651: 기존 단건 업데이트 도입 PR과 연속된 변경 사항.
Suggested reviewers
- oesnuj
- seongwon030
- Zepelown
- PororoAndFriends
Tip
🔌 Remote MCP (Model Context Protocol) integration is now available!
Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.
✨ 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/#701-change-multiple-status-applicants-MOA-199
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.
🪧 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
@coderabbitin a new review comment at the desired location with your query. - PR comments: Tag
@coderabbitin 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:@coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.@coderabbit 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 @coderabbit help to get the list of available commands.
Other keywords and placeholders
- Add
@coderabbit ignoreor@coderabbitai ignoreanywhere in the PR description to prevent this PR from being reviewed. - Add
@coderabbit summaryor@coderabbitai summaryto generate the high-level summary at a specific location in the PR description. - Add
@coderabbitor@coderabbitaianywhere 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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (9)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.ts (1)
130-143: 드롭다운 컨텐트 스크롤/레이어 우선순위 보강.옵션 수가 많을 때 뷰포트 밖으로 넘칠 수 있습니다. max-height/스크롤 및 z-index 상향을 권장합니다.
export const StatusSelectMenu = styled.div<{ open: boolean }>` display: ${({ open }) => (open ? 'block' : 'none')}; position: absolute; top: 100%; height: auto; background: #fff; left: 0; border: 1px solid #dcdcdc; border-radius: 6px; - box-shadow: 0px 1px 8px 0px #0000001f; - z-index: 10; + box-shadow: 0px 1px 8px 0px #0000001f; + z-index: 1000; padding: 8px 0; color: #787878; + min-width: 120px; + max-height: 320px; + overflow-y: auto; `frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx (1)
60-82: 디바운스 상수화 + 의존성 보강(+ 취소 처리 권장).
- 매직넘버 400을 상수로 분리하세요.
- mutate는 안정적이지만 의존성 배열에 포함하는 편이 안전합니다.
- 디바운스 유틸이 cancel을 제공한다면 언마운트 시 취소를 권장합니다(페이지 전환 중 늦은 요청 방지).
- }, 400), - [clubId, questionId], + }, DEBOUNCE_MS), + [clubId, questionId, updateApplicant], );추가(파일 상단 인접 영역):
const DEBOUNCE_MS = 400;선택(유틸이 지원한다면):
useEffect(() => () => updateApplicantDetail.cancel?.(), [updateApplicantDetail]);frontend/src/apis/application/updateApplicantDetail.ts (1)
17-18: 요청 본문 변수명 동기화.- body: JSON.stringify(applicant), + body: JSON.stringify(updates),frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (3)
26-27: 요청 중 UI 비활성화(+작성자 질문 답변: 로딩 표시 권장).배치 변경 요청 동안 버튼/삭제를 잠시 비활성화하면 중복 요청을 방지합니다. 필요 시 버튼 내부에 작은 스피너를 노출해도 좋습니다.
- const { mutate: updateDetailApplicants } = useUpdateApplicant(clubId!); + const { mutate: updateDetailApplicants, isPending: isUpdatingStatus } = + useUpdateApplicant(clubId!);- <Styled.StatusSelect disabled={!isChecked}> + <Styled.StatusSelect disabled={!isChecked || isUpdatingStatus}> 상태변경 </Styled.StatusSelect>- disabled={!isChecked} + disabled={!isChecked || isUpdatingStatus}원하시면 인라인 스피너 컴포넌트 삽입 코드도 제공하겠습니다.
Also applies to: 237-237, 275-275
42-67: 바깥 클릭 핸들러 등록/해제 최적화 여지.open/statusOpen에 따라 매 렌더마다 리스너를 재등록합니다. 한 번 등록하고 ref로 상태를 추적하거나, 포커스 아웃(onBlur/capture) 방식으로 단순화할 수 있습니다. 체감 이슈가 없다면 유지해도 무방합니다.
116-116: isAllChecked 계산 의미 명확화(가독성).이미 prev에서 every(Boolean) 결과를 사용하므로 변수명을 willToggleAll 등으로 의미를 드러내면 읽기 쉬워집니다. 동작 문제는 없습니다.
frontend/src/hooks/queries/applicants/useUpdateApplicant.ts (3)
1-3: 타입 전용 임포트로 미세 최적화
UpdateApplicantParams는 타입이므로import type사용을 권장합니다.-import { UpdateApplicantParams } from '@/types/applicants'; +import type { UpdateApplicantParams } from '@/types/applicants';
5-5: 훅 이름이 실제 동작(배치 업데이트)과 불일치단건 → 배치로 변경되었으니 가독성을 위해
useUpdateApplicants(또는useBulkUpdateApplicants)로의 리네이밍을 고려해 주세요. 시그니처는 그대로여도 의미 전달이 선명해집니다.-export const useUpdateApplicant = (clubId: string) => { +export const useUpdateApplicants = (clubId: string) => {추가로, 제네릭을 지정해 반환/에러/변수 타입을 명시하면 사용처에서 이점이 큽니다.
- return useMutation({ + return useMutation<unknown, Error, UpdateApplicantParams[]>({
15-16: 에러 로깅 개선 및 안전한 출력템플릿 리터럴로
unknown에러를 문자열화하면[object Object]가 될 수 있습니다. 원본 객체를 함께 로깅하세요.- console.log(`Error updating applicant detail: ${error}`); + console.error('Error updating applicant detail:', error);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
frontend/src/apis/application/updateApplicantDetail.ts(1 hunks)frontend/src/hooks/queries/applicants/useUpdateApplicant.ts(1 hunks)frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx(2 hunks)frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.ts(1 hunks)frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx(9 hunks)frontend/src/types/applicants.ts(2 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/types/applicants.tsfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsxfrontend/src/hooks/queries/applicants/useUpdateApplicant.tsfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsxfrontend/src/apis/application/updateApplicantDetail.ts
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/ApplicantDetailPage.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx
🧬 Code graph analysis (5)
frontend/src/types/applicants.ts (1)
frontend/src/types/application.ts (1)
AnswerItem(55-58)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx (3)
frontend/src/context/AdminClubContext.tsx (1)
useAdminClubContext(30-37)frontend/src/hooks/queries/application/useGetApplication.ts (1)
useGetApplication(4-11)frontend/src/hooks/queries/applicants/useUpdateApplicant.ts (1)
useUpdateApplicant(5-18)
frontend/src/hooks/queries/applicants/useUpdateApplicant.ts (2)
frontend/src/types/applicants.ts (1)
UpdateApplicantParams(26-30)frontend/src/apis/application/updateApplicantDetail.ts (1)
updateApplicantDetail(5-31)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (2)
frontend/src/hooks/queries/applicants/useDeleteApplicants.ts (1)
useDeleteApplicants(4-17)frontend/src/hooks/queries/applicants/useUpdateApplicant.ts (1)
useUpdateApplicant(5-18)
frontend/src/apis/application/updateApplicantDetail.ts (2)
frontend/src/types/applicants.ts (1)
UpdateApplicantParams(26-30)frontend/src/apis/auth/secureFetch.ts (1)
secureFetch(3-41)
🔇 Additional comments (3)
frontend/src/types/applicants.ts (1)
26-30: UpdateApplicantParams 수정: applicantId 필수, memo 옵션 처리
타입을 아래와 같이 변경하고, 백엔드 스펙이 해당 변경을 지원하는지 확인 바랍니다.-export interface UpdateApplicantParams { - memo: string; - status: ApplicationStatus; - applicantId: string | undefined; -} +export interface UpdateApplicantParams { + applicantId: string; + status: ApplicationStatus; + memo?: string; +}frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantDetailPage/ApplicantDetailPage.tsx (1)
18-23: 상세 페이지 상태 옵션에 DECLINED 누락 — 목록 탭과 불일치.ApplicantsTab.tsx에서는 불합격(DECLINED) 변경을 제공하지만 상세 페이지 드롭다운에는 없습니다. 의도된 제한인지 확인 바랍니다.
frontend/src/hooks/queries/applicants/useUpdateApplicant.ts (1)
17-18: useUpdateApplicant 호출부 인자 검토 완료
전체 코드베이스에 2개 인자 사용 호출 패턴이 존재하지 않음을 확인했습니다.
| export const updateApplicantDetail = async ( | ||
| memo: string, | ||
| status: ApplicationStatus, | ||
| applicant: UpdateApplicantParams[], | ||
| clubId: string, | ||
| applicantId: string, | ||
| ) => { | ||
| try { |
There was a problem hiding this comment.
🛠️ Refactor suggestion
배치 업데이트 입력 검증 추가 + 파라미터 명확화.
빈 배열/누락된 applicantId를 조기에 차단하면 서버 4xx를 줄일 수 있습니다. 또한 변수명을 updates로 명확화하세요.
-export const updateApplicantDetail = async (
- applicant: UpdateApplicantParams[],
+export const updateApplicantDetail = async (
+ updates: UpdateApplicantParams[],
clubId: string,
) => {
try {
+ if (!Array.isArray(updates) || updates.length === 0) {
+ return [];
+ }
+ if (updates.some((u) => !u.applicantId)) {
+ throw new Error('유효하지 않은 요청: applicantId 누락');
+ }📝 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.
| export const updateApplicantDetail = async ( | |
| memo: string, | |
| status: ApplicationStatus, | |
| applicant: UpdateApplicantParams[], | |
| clubId: string, | |
| applicantId: string, | |
| ) => { | |
| try { | |
| export const updateApplicantDetail = async ( | |
| updates: UpdateApplicantParams[], | |
| clubId: string, | |
| ) => { | |
| try { | |
| if (!Array.isArray(updates) || updates.length === 0) { | |
| return []; | |
| } | |
| if (updates.some((u) => !u.applicantId)) { | |
| throw new Error('유효하지 않은 요청: applicantId 누락'); | |
| } | |
| // ...rest of implementation |
🤖 Prompt for AI Agents
In frontend/src/apis/application/updateApplicantDetail.ts around lines 5 to 9,
the function accepts applicant: UpdateApplicantParams[] without validating
inputs and uses an unclear parameter name; rename the parameter to updates and
add early validation that throws or returns a 4xx-friendly error when updates is
missing or an empty array, and iterate the array to verify each entry contains a
non-empty applicantId (and any required fields) before making the request;
return or throw a clear validation error message so callers get immediate
feedback and server-side 4xx calls are reduced.
| mutationFn: (applicant: UpdateApplicantParams[]) => | ||
| updateApplicantDetail(applicant, clubId!), |
There was a problem hiding this comment.
🛠️ Refactor suggestion
배치 페이로드 검증 및 널 단언 제거
- 변수명을 복수형으로 변경해 의도를 명확히.
clubId!는 매개변수가string이므로 불필요합니다.UpdateApplicantParams.applicantId가string | undefined이므로 런타임 검증이 필요합니다. 서버로undefined가 전달되면 실패합니다.
- mutationFn: (applicant: UpdateApplicantParams[]) =>
- updateApplicantDetail(applicant, clubId!),
+ mutationFn: (applicants: UpdateApplicantParams[]) => {
+ if (applicants.length === 0) {
+ throw new Error('업데이트할 지원자가 없습니다.');
+ }
+ if (applicants.some(a => !a.applicantId)) {
+ throw new Error('applicantId가 없는 항목이 있습니다.');
+ }
+ return updateApplicantDetail(applicants, clubId);
+ },또한, 디버깅 편의를 위해 mutationKey를 추가하는 것을 권장합니다.
return useMutation({
+ mutationKey: ['updateApplicants', clubId],📝 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.
| mutationFn: (applicant: UpdateApplicantParams[]) => | |
| updateApplicantDetail(applicant, clubId!), | |
| return useMutation({ | |
| mutationKey: ['updateApplicants', clubId], | |
| mutationFn: (applicants: UpdateApplicantParams[]) => { | |
| if (applicants.length === 0) { | |
| throw new Error('업데이트할 지원자가 없습니다.'); | |
| } | |
| if (applicants.some(a => !a.applicantId)) { | |
| throw new Error('applicantId가 없는 항목이 있습니다.'); | |
| } | |
| return updateApplicantDetail(applicants, clubId); | |
| }, | |
| // …other options (onSuccess, onError, etc.) remain unchanged | |
| }); |
🤖 Prompt for AI Agents
In frontend/src/hooks/queries/applicants/useUpdateApplicant.ts around lines
9–10, the mutation currently uses a singular variable name, a non-null assertion
on clubId, and can send undefined applicantId to the server; rename the
parameter to a plural (e.g., applicants), remove the unnecessary clubId!
non-null assertion, validate the batch payload before calling
updateApplicantDetail by checking it's a non-empty array and that each item has
a defined applicantId (either filter out invalid entries or throw a clear
error), and add a descriptive mutationKey to the hook options for easier
debugging.
| updateApplicantDetail(applicant, clubId!), | ||
| onSuccess: () => { | ||
| queryClient.invalidateQueries({ queryKey: ["clubApplicants"] }); | ||
| queryClient.invalidateQueries({ queryKey: ['clubApplicants'] }); |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
쿼리 무효화 범위 축소 필요 (clubId 키 포함 권장)
다중 클럽 환경에서 불필요한 광범위 무효화를 막기 위해 키에 clubId를 포함하세요. 기존 조회 훅의 키 형식과도 일치시켜 주세요.
- queryClient.invalidateQueries({ queryKey: ['clubApplicants'] });
+ queryClient.invalidateQueries({ queryKey: ['clubApplicants', clubId] });검증 스크립트: 현재 조회 훅이 어떤 키를 쓰는지 확인하여 키 형식을 맞춰 주세요.
🏁 Script executed:
#!/bin/bash
# 'clubApplicants' 관련 useQuery 키 사용 패턴 확인
rg -nP -C2 "(useQuery|useInfiniteQuery)\s*\(\s*\{[^\}]*queryKey\s*:\s*\[[^\]]*'clubApplicants'[^\]]*\]" --type ts --type tsxLength of output: 160
🏁 Script executed:
#!/bin/bash
# 'clubApplicants' 사용 파일 목록 검색
rg -l "clubApplicants" -g "*.ts*"
# 'clubApplicants' 쿼리Key 패턴 확인
rg -nP -C2 "queryKey\s*:\s*\[[^]]*['\"]clubApplicants['\"]" -g "*.ts*"Length of output: 1758
쿼리 무효화에 clubId 추가 필요
두 곳의 invalidateQueries 호출에서 clubApplicants만 사용하여 모든 클럽의 캐시를 무효화하고 있습니다. useGetApplicants 훅의 키와 동일하게 범위를 좁히려면 clubId를 함께 전달하세요.
- frontend/src/hooks/queries/applicants/useUpdateApplicant.ts (12행)
- frontend/src/hooks/queries/applicants/useDeleteApplicants.ts (11행)
- queryClient.invalidateQueries({ queryKey: ['clubApplicants'] });
+ queryClient.invalidateQueries({ queryKey: ['clubApplicants', clubId] });📝 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.
| queryClient.invalidateQueries({ queryKey: ['clubApplicants'] }); | |
| queryClient.invalidateQueries({ queryKey: ['clubApplicants', clubId] }); |
🤖 Prompt for AI Agents
In frontend/src/hooks/queries/applicants/useUpdateApplicant.ts around line 12,
the call to queryClient.invalidateQueries({ queryKey: ['clubApplicants'] }) is
too broad and should include the clubId to match useGetApplicants' key; update
the invalidateQueries call to pass the same key shape, e.g. ['clubApplicants',
clubId], and apply the same change in
frontend/src/hooks/queries/applicants/useDeleteApplicants.ts (around line 11) so
only the specific club's applicants cache is invalidated.
| export const StatusSelect = styled.p<{ disabled: boolean }>` | ||
| height: 30px; | ||
| border: 1px solid #dcdcdc; | ||
| background: #fff; | ||
| border-radius: 55px; | ||
| padding: 0px 22px 0px 8px; | ||
| margin: 5px 0px 5px 0px; | ||
| font-weight: 700; | ||
| color: ${({ disabled }) => (disabled ? '#DCDCDC' : '#000')}; | ||
| padding: 0 22px 0 8px; | ||
| margin: 5px 0; | ||
|
|
||
| -webkit-appearance: none; | ||
| -moz-appearance: none; | ||
| appearance: none; | ||
| ${({ disabled }) => | ||
| disabled | ||
| ? 'color: #DCDCDC' | ||
| : 'color: #000; &:hover { background: #f5f5f5; }'}; | ||
|
|
||
| &:not(:disabled):hover { | ||
| font-size: 12px; | ||
| font-weight: 600; | ||
| white-space: nowrap; | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; | ||
| cursor: default; | ||
|
|
||
| display: inline-flex; | ||
| align-items: center; | ||
| `; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
StatusSelect는 인터랙션 요소입니다. p → button으로 전환하고 접근성/포커스/비활성 처리를 명시하세요.
현재 styled.p에 클릭 동작을 부여하는 패턴은 접근성/키보드 내비게이션에 취약합니다. button으로 전환하고 disabled 시 pointer-events 차단, focus-visible 스타일을 추가하는 편이 안전합니다. ApplicantsTab.tsx에서 onClick을 래퍼가 아닌 버튼에 부여하는 변경과 세트로 적용해 주세요.
-export const StatusSelect = styled.p<{ disabled: boolean }>`
+export const StatusSelect = styled.button<{ disabled: boolean }>`
height: 30px;
border: 1px solid #dcdcdc;
background: #fff;
border-radius: 55px;
padding: 0 22px 0 8px;
margin: 5px 0;
- ${({ disabled }) =>
- disabled
- ? 'color: #DCDCDC'
- : 'color: #000; &:hover { background: #f5f5f5; }'};
+ ${({ disabled }) =>
+ disabled
+ ? `
+ color: #DCDCDC;
+ pointer-events: none;
+ `
+ : `
+ color: #000;
+ &:hover { background: #f5f5f5; }
+ &:focus-visible { outline: 2px solid #99c2ff; outline-offset: 2px; }
+ `};
font-size: 12px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
- cursor: default;
+ cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
display: inline-flex;
align-items: center;
`📝 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.
| export const StatusSelect = styled.p<{ disabled: boolean }>` | |
| height: 30px; | |
| border: 1px solid #dcdcdc; | |
| background: #fff; | |
| border-radius: 55px; | |
| padding: 0px 22px 0px 8px; | |
| margin: 5px 0px 5px 0px; | |
| font-weight: 700; | |
| color: ${({ disabled }) => (disabled ? '#DCDCDC' : '#000')}; | |
| padding: 0 22px 0 8px; | |
| margin: 5px 0; | |
| -webkit-appearance: none; | |
| -moz-appearance: none; | |
| appearance: none; | |
| ${({ disabled }) => | |
| disabled | |
| ? 'color: #DCDCDC' | |
| : 'color: #000; &:hover { background: #f5f5f5; }'}; | |
| &:not(:disabled):hover { | |
| font-size: 12px; | |
| font-weight: 600; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| cursor: default; | |
| display: inline-flex; | |
| align-items: center; | |
| `; | |
| export const StatusSelect = styled.button<{ disabled: boolean }>` | |
| height: 30px; | |
| border: 1px solid #dcdcdc; | |
| background: #fff; | |
| border-radius: 55px; | |
| padding: 0 22px 0 8px; | |
| margin: 5px 0; | |
| ${({ disabled }) => | |
| disabled | |
| ? ` | |
| color: #DCDCDC; | |
| pointer-events: none; | |
| ` | |
| : ` | |
| color: #000; | |
| &:hover { background: #f5f5f5; } | |
| &:focus-visible { outline: 2px solid #99c2ff; outline-offset: 2px; } | |
| `}; | |
| font-size: 12px; | |
| font-weight: 600; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')}; | |
| display: inline-flex; | |
| align-items: center; | |
| ` |
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.ts
around lines 106 to 128, the StatusSelect is currently a styled.p used as an
interactive control; change it to a styled.button, ensure it accepts and applies
the disabled prop properly (use disabled attribute instead of custom prop where
possible), add CSS to disable pointer-events and set aria-disabled/opacity for
disabled state, and add visible focus styles (focus-visible outline/box-shadow)
for keyboard users; also update ApplicantsTab.tsx so onClick is attached
directly to this button (not a wrapper) and ensure the component renders with
role/button semantics and proper disabled handling.
| export const StatusSelectMenuItem = styled.div` | ||
| font-size: 12px; | ||
| font-weight: 600; | ||
| padding: 8px 13px; | ||
| cursor: pointer; | ||
| text-align: left; | ||
| &:hover { | ||
| background: #f5f5f5; | ||
| } | ||
| `; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
메뉴 항목도 div → button으로 전환해 키보드 접근성을 확보하세요.
role/button semantics 없이 div 클릭은 스크린리더/키보드 접근성이 떨어집니다.
-export const StatusSelectMenuItem = styled.div`
+export const StatusSelectMenuItem = styled.button`
font-size: 12px;
font-weight: 600;
padding: 8px 13px;
cursor: pointer;
text-align: left;
+ width: 100%;
+ border: 0;
+ background: transparent;
&:hover {
background: #f5f5f5;
}
+ &:focus-visible {
+ outline: 2px solid #99c2ff;
+ outline-offset: 2px;
+ }
`📝 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.
| export const StatusSelectMenuItem = styled.div` | |
| font-size: 12px; | |
| font-weight: 600; | |
| padding: 8px 13px; | |
| cursor: pointer; | |
| text-align: left; | |
| &:hover { | |
| background: #f5f5f5; | |
| } | |
| `; | |
| export const StatusSelectMenuItem = styled.button` | |
| font-size: 12px; | |
| font-weight: 600; | |
| padding: 8px 13px; | |
| cursor: pointer; | |
| text-align: left; | |
| width: 100%; | |
| border: 0; | |
| background: transparent; | |
| &:hover { | |
| background: #f5f5f5; | |
| } | |
| &:focus-visible { | |
| outline: 2px solid #99c2ff; | |
| outline-offset: 2px; | |
| } | |
| `; |
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.ts
around lines 145 to 154, the StatusSelectMenuItem is a div which harms keyboard
and screen-reader accessibility; change it to a styled button element, add
type="button" where it’s rendered, reset default browser button styles
(background, border, padding) to preserve current visual styling, ensure cursor
and hover styles remain, and add visible focus styles (outline or box-shadow)
and appropriate ARIA attributes if this item functions as a selectable menu
option.
| <Styled.SelectWrapper | ||
| ref={statusSelectRef} | ||
| onClick={() => { | ||
| if (!isChecked) return; | ||
| setStatusOpen((prev) => !prev); | ||
| }} | ||
| > | ||
| <Styled.StatusSelect disabled={!isChecked}> | ||
| 상태변경 | ||
| </Styled.StatusSelect> |
There was a problem hiding this comment.
🛠️ Refactor suggestion
클릭 핸들러를 래퍼에서 버튼으로 이동하고 ARIA를 부여하세요.
래퍼 div에 onClick을 두면 스크린리더/키보드 접근성이 떨어집니다. 버튼에 aria-haspopup/aria-expanded를 명시하고 메뉴/항목에 role을 부여하세요. styles에서 button 전환과 함께 적용하세요.
- <Styled.SelectWrapper
- ref={statusSelectRef}
- onClick={() => {
- if (!isChecked) return;
- setStatusOpen((prev) => !prev);
- }}
- >
- <Styled.StatusSelect disabled={!isChecked}>
+ <Styled.SelectWrapper ref={statusSelectRef}>
+ <Styled.StatusSelect
+ disabled={!isChecked || isUpdatingStatus}
+ aria-haspopup="menu"
+ aria-expanded={statusOpen}
+ type="button"
+ onClick={() => setStatusOpen((prev) => !prev)}
+ >
상태변경
</Styled.StatusSelect>
<Styled.Arrow width={8} height={8} src={selectIcon} />
- <Styled.StatusSelectMenu open={statusOpen}>
- <Styled.StatusSelectMenuItem
+ <Styled.StatusSelectMenu open={statusOpen} role="menu">
+ <Styled.StatusSelectMenuItem
+ role="menuitem"
onClick={() => {
updateAllApplicants(ApplicationStatus.SUBMITTED);
}}
>
서류검토
</Styled.StatusSelectMenuItem>
- <Styled.StatusSelectMenuItem
+ <Styled.StatusSelectMenuItem
+ role="menuitem"
onClick={() => {
updateAllApplicants(
ApplicationStatus.INTERVIEW_SCHEDULED,
);
}}
>
면접예정
</Styled.StatusSelectMenuItem>
- <Styled.StatusSelectMenuItem
+ <Styled.StatusSelectMenuItem
+ role="menuitem"
onClick={() => {
updateAllApplicants(ApplicationStatus.ACCEPTED);
}}
>
합격
</Styled.StatusSelectMenuItem>
- <Styled.StatusSelectMenuItem
+ <Styled.StatusSelectMenuItem
+ role="menuitem"
onClick={() => {
updateAllApplicants(ApplicationStatus.DECLINED);
}}
>
불합격
</Styled.StatusSelectMenuItem>
</Styled.StatusSelectMenu>
</Styled.SelectWrapper>Also applies to: 241-269
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx around
lines 230-239 (also apply same changes to 241-269), move the click handler from
the wrapping Styled.SelectWrapper div to an actual button element, update styles
to render the button visually identical to the current wrapper, and add proper
ARIA attributes: aria-haspopup="menu" and aria-expanded={statusOpen} on the
button; ensure the menu and each option have appropriate roles (e.g.,
role="menu" and role="menuitem") and keyboard handlers (Enter/Space to
toggle/open and arrow/escape support as needed); remove onClick from the wrapper
and ensure the button is disabled when !isChecked so it is keyboard and
screen-reader accessible.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (4)
30-40: 검색 필터에서 answers[0] 가정으로 인한 런타임 크래시 가능성answers가 비어있거나 value가 없는 경우 NPE 발생 여지.
다음과 같이 옵셔널 체이닝/기본값으로 방어하세요.
- return applicantsData.applicants.filter((user: Applicant) => - user.answers[0].value - .toLowerCase() - .includes(keyword.trim().toLowerCase()), - ); + return applicantsData.applicants.filter((user: Applicant) => { + const name = user.answers?.[0]?.value ?? ''; + return name.toLowerCase().includes(keyword.trim().toLowerCase()); + });
299-303: 체크박스는 onChange 사용 권장폼 컨트롤에는 onChange가 표준이며 보조기기 호환성이 더 높습니다.
- onClick={(e: React.MouseEvent<HTMLInputElement>) => { + onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); selectApplicantsByStatus('all'); }}
367-372: 리스트 key에 index 사용 지양 → id 사용리렌더링/필터링 시 의도치 않은 재마운트 방지.
- <Styled.ApplicantTableRow - key={index} + <Styled.ApplicantTableRow + key={item.id} onClick={() => navigate(`/admin/applicants/${item.id}`)} style={{ cursor: 'pointer' }} >
375-384: 개별 행 체크박스도 onChange로 전환일관성과 접근성 향상.
- <Styled.ApplicantTableCheckbox + <Styled.ApplicantTableCheckbox checked={checkedItem.get(item.id)} - onClick={(e: React.MouseEvent<HTMLInputElement>) => { + onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); setCheckedItem((prev) => { const newMap = new Map(prev); newMap.set(item.id, !newMap.get(item.id)); return newMap; }); }} />
♻️ Duplicate comments (2)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (2)
148-166: mutate 옵션(thisArg) 전달 버그가 해결되었습니다.옵션 객체가 map의 thisArg로 가는 문제는 더 이상 없고, onSuccess/onError가 정상 동작합니다. 굿 픽스.
228-269: 상태 변경 트리거를 wrapper div에서 button으로 이동 + ARIA 롤/키보드 지원 추가현재 onClick이 래퍼 div에 있어 접근성이 떨어집니다. 버튼에 포커스/키보드 연동과 aria 속성을 부여하세요. 또한 업데이트 중 비활성화/aria-busy 적용을 권장합니다.
- <Styled.SelectWrapper - ref={statusSelectRef} - onClick={() => { - if (!isChecked) return; - setStatusOpen((prev) => !prev); - }} - > - <Styled.StatusSelect disabled={!isChecked}> + <Styled.SelectWrapper ref={statusSelectRef}> + <Styled.StatusSelect + type="button" + disabled={!isChecked || isUpdatingStatus} + aria-haspopup="menu" + aria-expanded={statusOpen} + aria-busy={isUpdatingStatus} + onClick={() => setStatusOpen((prev) => !prev)} + > 상태변경 </Styled.StatusSelect> <Styled.Arrow width={8} height={8} src={selectIcon} /> - <Styled.StatusSelectMenu open={statusOpen}> - <Styled.StatusSelectMenuItem - onClick={() => { + <Styled.StatusSelectMenu open={statusOpen} role="menu"> + <Styled.StatusSelectMenuItem role="menuitem" + onClick={() => { updateAllApplicants(ApplicationStatus.SUBMITTED); }} > 서류검토 </Styled.StatusSelectMenuItem> - <Styled.StatusSelectMenuItem - onClick={() => { + <Styled.StatusSelectMenuItem role="menuitem" + onClick={() => { updateAllApplicants(ApplicationStatus.INTERVIEW_SCHEDULED); }} > 면접예정 </Styled.StatusSelectMenuItem> - <Styled.StatusSelectMenuItem - onClick={() => { + <Styled.StatusSelectMenuItem role="menuitem" + onClick={() => { updateAllApplicants(ApplicationStatus.ACCEPTED); }} > 합격 </Styled.StatusSelectMenuItem> - <Styled.StatusSelectMenuItem - onClick={() => { + <Styled.StatusSelectMenuItem role="menuitem" + onClick={() => { updateAllApplicants(ApplicationStatus.DECLINED); }} > 불합격 </Styled.StatusSelectMenuItem> </Styled.StatusSelectMenu> </Styled.SelectWrapper>키보드 핸들러(Enter/Space, Escape, Arrow)도 후속으로 추가하세요.
🧹 Nitpick comments (6)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (6)
42-67: 외부 클릭 리스너: pointer 이벤트와 1회 바인딩 고려모바일/펜 입력 대응성과 불필요한 바인딩/언바인딩을 줄이기 위해 pointerdown 사용 및 의존성 축소를 권장합니다.
예시:
- useEffect(() => { + useEffect(() => { const handleOutsideClick = (e: MouseEvent) => { const target = e.target as Node; @@ - document.addEventListener('mousedown', handleOutsideClick); + document.addEventListener('pointerdown', handleOutsideClick as any, { passive: true }); return () => { - document.removeEventListener('mousedown', handleOutsideClick); + document.removeEventListener('pointerdown', handleOutsideClick as any); }; - }, [open, statusOpen]); + }, [open, statusOpen]);참고: 상태 의존성을 없애고 ref.current와 setState만 쓰는 방식으로 고정 리스너 1회 등록도 가능합니다.
68-75: 필터 변경 시 선택 초기화 UX 확인 필요검색어 변경으로 filteredApplicants가 바뀔 때 선택이 전부 초기화됩니다. 의도라면 OK, 아니라면 원본 선택 상태를 유지하는 구조(예: 전역 selection Map) 검토 필요.
원하시면 선택 상태를 뷰(필터)와 분리하는 리팩터를 제안하겠습니다.
150-156: 선택된 대상 집합 축소를 위해 filteredApplicants 사용 권장전체 applicants를 순회할 필요 없이 표시/관리 중인 집합만 대상으로 충분합니다.
- applicantsData!.applicants + filteredApplicants .filter((applicant) => checkedItem.get(applicant.id)) .map((applicant) => ({ applicantId: applicant.id, memo: applicant.memo, - status: status, + status, })),또한 memo가 undefined일 수 있다면
memo: applicant.memo ?? ''로 방어하세요.UpdateApplicantParams.memo가 필수 문자열인지 확인 바랍니다.
271-278: 삭제 버튼도 삭제 진행 중 비활성화 및 접근성 속성 부여중복 클릭 방지 및 SR 노출 개선.
- <Styled.DeleteButton + <Styled.DeleteButton src={deleteIcon} alt='삭제' - disabled={!isChecked} + disabled={!isChecked || isDeleting} + aria-busy={isDeleting} onClick={() => { const toBeDeleted = Array.from(checkedItem.entries()) .filter(([_, isChecked]) => isChecked) .map(([id, _]) => id);(상단 코멘트의 isDeleting 구조분해가 전제됩니다.)
407-419: 날짜 포맷 간소화 및 매직 넘버 제거IIFE/패딩 매직 넘버 대신 ISO 슬라이스로 단순화.
- (() => { - 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}`; - })() + (() => new Date(item.createdAt).toISOString().slice(0, 10))()타임존 보정이 필요하면 dayjs/date-fns 등 공용 유틸로 대체를 권장합니다.
148-166: 로딩 표시(Spinner) 제안에 대한 답변예, 권장합니다. StatusSelect 내부 우측에 인라인 스피너를 배치하거나 메뉴 항목 클릭 시 로딩 오버레이를 표시하세요. 위 코멘트의 isUpdatingStatus를 사용해 제어하면 됩니다.
원하시면 공용 Spinner 컴포넌트 연동용 패치도 드리겠습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx(9 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 (2)
frontend/src/hooks/queries/applicants/useDeleteApplicants.ts (1)
useDeleteApplicants(4-17)frontend/src/hooks/queries/applicants/useUpdateApplicant.ts (1)
useUpdateApplicant(5-18)
🔇 Additional comments (4)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (4)
3-3: 불필요 코멘트 없음 — 적절한 React 훅 임포트현재 사용 범위에 맞는 임포트입니다.
23-29: 상태 플래그와 ref 추가 적절statusOpen/isChecked 및 외부 클릭 감지를 위한 refs 추가는 목적에 부합합니다.
76-81: 선택 상태 파생 계산 LGTMselectAll/isChecked 계산이 명확합니다.
107-136: isAllChecked 파생 값 계산 적절토글 전/후 일관성 확보에 기여합니다.
| import selectIcon from '@/assets/images/icons/selectArrow.svg'; | ||
| import deleteIcon from '@/assets/images/icons/applicant_delete.svg'; | ||
| import selectAllIcon from '@/assets/images/icons/applicant_select_arrow.svg'; | ||
| import { useUpdateApplicant } from '@/hooks/queries/applicants/useUpdateApplicant'; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
상태 변경 중 UI 비활성화/스피너 연계를 위해 mutation 로딩 상태를 노출하세요.
로직 중복 클릭 방지와 접근성(aria-busy) 강화를 위해 isPending(or isLoading)을 구조분해 해 사용 권장.
다음 패치를 적용하세요(React Query v5 기준 isPending, v4면 isLoading으로 교체 필요).
-const { mutate: deleteApplicants } = useDeleteApplicants(clubId!);
-const { mutate: updateDetailApplicants } = useUpdateApplicant(clubId!);
+const { mutate: deleteApplicants, isPending: isDeleting } = useDeleteApplicants(clubId!);
+const { mutate: updateDetailApplicants, isPending: isUpdatingStatus } = useUpdateApplicant(clubId!);React Query 버전(v4/v5) 확인 후 isPending/isLoading 네이밍을 맞춰주세요.
Also applies to: 25-26
🏁 Script executed:
#!/bin/bash
# React Query 버전 확인 (v4/v5 여부 결정)
grep -E '"(@tanstack/react-query|react-query)":' -n package.jsonLength of output: 112
🏁 Script executed:
#!/bin/bash
# package.json 위치 검색 및 React Query 버전 추출
pkg_file=$(find . -maxdepth 3 -type f -name "package.json" | head -n1)
if [ -z "$pkg_file" ]; then
echo "package.json 파일을 찾을 수 없습니다."
exit 1
fi
grep -E '"(@tanstack/react-query|react-query)"' -n "$pkg_file"Length of output: 252
React Query v5 isPending 구조분해 사용
변경 중 스피너/aria-busy 연계를 위해 mutate 호출부에서 isPending을 구조분해하여 사용하세요.
- const { mutate: deleteApplicants } = useDeleteApplicants(clubId!);
- const { mutate: updateDetailApplicants } = useUpdateApplicant(clubId!);
+ const { mutate: deleteApplicants, isPending: isDeleting } = useDeleteApplicants(clubId!);
+ const { mutate: updateDetailApplicants, isPending: isUpdatingStatus } = useUpdateApplicant(clubId!);위치: frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx (12, 25–26)
📝 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.
| import { useUpdateApplicant } from '@/hooks/queries/applicants/useUpdateApplicant'; | |
| // at the top of ApplicantsTab.tsx | |
| import { useDeleteApplicants } from '@/hooks/queries/applicants/useDeleteApplicants'; | |
| import { useUpdateApplicant } from '@/hooks/queries/applicants/useUpdateApplicant'; | |
| … | |
| const { | |
| mutate: deleteApplicants, | |
| isPending: isDeleting | |
| } = useDeleteApplicants(clubId!); | |
| const { | |
| mutate: updateDetailApplicants, | |
| isPending: isUpdatingStatus | |
| } = useUpdateApplicant(clubId!); |
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx around
lines 12 and 25–26, the React Query v5 isPending flag from useUpdateApplicant
should be destructured and used to drive the UI during mutation; change the hook
usage to const { mutate, isPending } = useUpdateApplicant() (or equivalent) and
then use isPending at the mutate call site to control the spinner/aria-busy
state (replace any current boolean or indirect loading checks with isPending so
the spinner and aria-busy reflect the actual mutation pending state).
| <Styled.StatusSelectMenu open={statusOpen}> | ||
| <Styled.StatusSelectMenuItem | ||
| onClick={() => { | ||
| updateAllApplicants(ApplicationStatus.SUBMITTED); | ||
| }} | ||
| > | ||
| 서류검토 | ||
| </Styled.StatusSelectMenuItem> | ||
| <Styled.StatusSelectMenuItem | ||
| onClick={() => { | ||
| updateAllApplicants(ApplicationStatus.INTERVIEW_SCHEDULED); | ||
| }} | ||
| > | ||
| 면접예정 | ||
| </Styled.StatusSelectMenuItem> | ||
| <Styled.StatusSelectMenuItem | ||
| onClick={() => { | ||
| updateAllApplicants(ApplicationStatus.ACCEPTED); | ||
| }} | ||
| > | ||
| 합격 | ||
| </Styled.StatusSelectMenuItem> | ||
| <Styled.StatusSelectMenuItem | ||
| onClick={() => { | ||
| updateAllApplicants(ApplicationStatus.DECLINED); | ||
| }} | ||
| > | ||
| 불합격 | ||
| </Styled.StatusSelectMenuItem> | ||
| </Styled.StatusSelectMenu> |
There was a problem hiding this comment.
상태 항목들이 중복되어 코드가 길어지는 느낌이에용
상태 옵션을 상수로 분리해두고 map 렌더링 하는 방식은 어떤가용?
한번 같이 생각해봐요
#️⃣연관된 이슈
#701
📝작업 내용
코드 변경점
applicantId를 파라미터로 넘겨받게 하였습니다.
이전코드는
useUpdateApplicant훅을 초기화할때 현재 context의 applicantId 기준으로 초기화가 되어updateApplicant의 함수가 호출될때 debounce 적용 시간(400ms) 이전에 페이지를 옮기게되면훅이 바뀐 applicantId 기준으로 초기화 되어 이전 지원자를 변경하지 못하는 버그가 존재하였습니다.
따라서 applicantId를 함수 인자로 옮김에 따라 다른 페이지 변경시에도 이전 사용자를 호출하게 변경하였습니다
useRef로 드롭메뉴를 펼친후 다른 부분을 클릭했을때 꺼지게 변경하였습니다.
기존 disabled에 사용한 조건을 상태로 옮겼습니다.
disabled를 이용하는 컴포넌트가 3개로 많아짐에 따라 한개의 상태로 반복하는 코드를 줄였습니다.
현재 체크된 applicantId를 기준으로 필터 후 map을 이용해
UpdateApplicantParams형식에 맞게 변환후 배열로 응답해줬습니다.중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
API를 호출하고 응답받는 딜레이가 존재하는데, Spinner를 추가해서 응답중임을 나타내는것이 좋을까요?
🫡 참고사항
Summary by CodeRabbit