[feature] 관리자 모바일 지원자 현황 리스트 컴포넌트 추가 - #1886
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Walkthrough모바일 지원자 관리 UI에 지원자 행, 검색, 상태 필터, 정렬, 지원서 선택, 일괄 작업, 상태 요약 컴포넌트와 Storybook 스토리를 추가했다. 지원자 상태 데이터와 라벨을 확장하고 기존 확장 화살표 렌더링을 변경했다. Changes모바일 지원자 관리
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
✅ UI 변경사항 없음
전체 128개 스토리 · 49개 컴포넌트 |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (5)
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.stories.tsx (1)
21-27: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueStorybook 레이아웃에도 styled-components를 사용해야 합니다.
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.stories.tsx#L21-L27: decorator의 인라인style을StoryViewportstyled component로 교체하십시오.frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.stories.tsx#L152-L164: 다중 행 컨테이너의 인라인style을 같은 styled component로 재사용하십시오.frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/StatusSummaryCard/StatusSummaryCard.stories.tsx#L9-L15: decorator의 인라인style을 styled component로 교체하십시오.As per coding guidelines,
Use styled-components and the project theme system for styling.규칙을 적용해야 합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.stories.tsx` around lines 21 - 27, Replace the inline width styles in ApplicantListRow.stories.tsx at lines 21-27 and 152-164 with a shared StoryViewport styled component, defining its styling through the project’s styled-components/theme system and reusing it for both the decorator and multi-row container. Also replace the decorator’s inline style in StatusSummaryCard.stories.tsx at lines 9-15 with the appropriate StoryViewport styled component, creating or reusing the same styled-component pattern.Source: Coding guidelines
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.tsx (1)
1-3: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueimport 순서와 type-only import를 정리해야 합니다.
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.tsx#L1-L3:mapStatusToGroup를 먼저 import하고,Applicant를import type으로 분리한 뒤 styles import를 마지막에 두십시오.frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.stories.tsx#L1-L5: 내부 모듈 import 뒤에Meta,StoryObj등 type import를 두십시오.frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/BulkActionBar/BulkActionBar.stories.tsx#L1-L2:BulkActionBarimport 뒤에 Storybook type import를 두십시오.frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/StatusSummaryCard/StatusSummaryCard.stories.tsx#L1-L2:StatusSummaryCardimport 뒤에 Storybook type import를 두십시오.As per coding guidelines,
external libraries, internal modules, types, then styles순서를 적용해야 합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.tsx` around lines 1 - 3, 정해진 import 순서와 type-only import 규칙에 맞게 네 곳의 import를 정리하십시오. ApplicantListRow.tsx에서는 mapStatusToGroup, type Applicant, Styled 순으로 배치하고, ApplicantListRow.stories.tsx, BulkActionBar.stories.tsx, StatusSummaryCard.stories.tsx에서는 각 내부 컴포넌트 import를 먼저 둔 뒤 Meta·StoryObj 등 Storybook type import를 배치하십시오. 변경 대상은 frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.tsx 1-3, frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.stories.tsx 1-5, frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/BulkActionBar/BulkActionBar.stories.tsx 1-2, frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/StatusSummaryCard/StatusSummaryCard.stories.tsx 1-2입니다.Source: Coding guidelines
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/FormDropdownSelector/FormDropdownSelector.tsx (1)
2-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
ApplicationFormId타입을 유지하세요.라인 10-11은
ApplicationFormItem.id가 사용하는ApplicationFormId대신string을 노출합니다. 유효하지 않은 ID가 선택 상태와onSelect경계를 통과할 수 있습니다.selectedFormId와onSelect의 인자를ApplicationFormId로 지정하세요.수정 예시
-import { ApplicationFormItem } from '`@/types/application`'; +import type { ApplicationFormId, ApplicationFormItem } from '`@/types/application`'; interface FormDropdownSelectorProps { forms: ApplicationFormItem[]; - selectedFormId: string; - onSelect: (formId: string) => void; + selectedFormId: ApplicationFormId; + onSelect: (formId: ApplicationFormId) => void; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/FormDropdownSelector/FormDropdownSelector.tsx` around lines 2 - 12, Update FormDropdownSelectorProps so selectedFormId and the onSelect callback parameter use ApplicationFormId instead of string, importing that type alongside ApplicationFormItem. Preserve the existing forms and selection behavior while keeping the component’s ID boundary type-safe.Source: Coding guidelines
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantSearchBox/ApplicantSearchBox.stories.tsx (1)
1-3: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winStorybook 파일의 import 순서를 통일하세요.
타입 전용 import는 내부 컴포넌트 import 뒤에 와야 합니다.
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantSearchBox/ApplicantSearchBox.stories.tsx#L1-L3:ApplicantSearchBoximport 뒤로Meta,StoryObj타입 import를 이동하세요.frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/SortDropdown/SortDropdown.stories.tsx#L1-L3:SortDropdownimport 뒤로Meta,StoryObj타입 import를 이동하세요.frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/FormDropdownSelector/FormDropdownSelector.stories.tsx#L1-L4:FormDropdownSelectorimport 뒤로Meta,StoryObj타입 import를 이동하세요.As per coding guidelines, imports must be ordered as external libraries, internal modules, types, then styles.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantSearchBox/ApplicantSearchBox.stories.tsx` around lines 1 - 3, Reorder imports in ApplicantSearchBox.stories.tsx (lines 1-3), SortDropdown.stories.tsx (lines 1-3), and FormDropdownSelector.stories.tsx (lines 1-4) so each internal component import (ApplicantSearchBox, SortDropdown, or FormDropdownSelector) precedes the type-only Meta and StoryObj import, following the external libraries, internal modules, types, then styles convention.Source: Coding guidelines
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/StatusFilterPills/StatusFilterPills.stories.tsx (1)
1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
React.ComponentProps의 전역 namespace 의존성을 제거하세요.이 파일은 React 값을 import하지 않지만
React.ComponentProps에 전역 UMD namespace을 의존합니다. 설정 의존성을 없애기 위해ComponentProps를 type import로 가져와서 사용하세요.수정 예시
-import { useState } from 'react'; +import { useState, type ComponentProps } from 'react'; -const Interactive = (args: React.ComponentProps<typeof StatusFilterPills>) => { +const Interactive = (args: ComponentProps<typeof StatusFilterPills>) => {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/StatusFilterPills/StatusFilterPills.stories.tsx` at line 1, Update StatusFilterPills.stories.tsx to type-import ComponentProps from React and replace the React.ComponentProps usage with the imported type, removing reliance on the global React namespace while preserving the existing prop typing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.stories.tsx`:
- Around line 33-35: Update the InteractiveRow args type to use a type-only
ComponentProps import from react instead of React.ComponentProps, then reference
ComponentProps<typeof ApplicantListRow> directly while preserving the existing
component behavior.
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.tsx`:
- Around line 30-38: ApplicantListRow의 Styled.Row와 Styled.CheckboxWrapper를 스크립트
이벤트 전용 div 대신 네이티브 버튼과 연결된 label/input type="checkbox" 구조로 변경하십시오. 행 버튼은 키보드로 열
수 있게 하고, 체크박스는 onCheck(applicant.id)와 isChecked 상태를 연결하며 접근 가능한 label과 ARIA 상태를
제공하십시오. 체크박스 조작이 행 클릭을 함께 실행하지 않도록 기존 이벤트 분리 동작도 유지하십시오.
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/BulkActionBar/BulkActionBar.tsx`:
- Around line 25-28: Update BulkActionBar’s handleStatusSelect and status-menu
interaction so disabled bulk actions cannot trigger onStatusChange: when enabled
is false, close the menu and return without invoking the callback. Ensure the
menu also closes when enabled becomes false after opening, including the related
enabled-dependent logic around the status menu.
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/FormDropdownSelector/FormDropdownSelector.tsx`:
- Around line 19-21: Reset thumbOffset whenever the dropdown is opened or before
the conditionally rendered MenuList is recreated, so reopening starts with a
thumb position of 0 matching the list’s reset scrollTop. Update the state flow
around isOpen, thumbOffset, and the MenuList rendering without changing the
existing open/close behavior.
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/SortDropdown/SortDropdown.tsx`:
- Around line 25-33: Remove the setIsOpen(false) call from the CustomDropDown
onSelect handler so it only invokes onChange(v); keep menu closing delegated to
CustomDropDown’s existing onToggle behavior.
In `@frontend/src/types/applicants.ts`:
- Line 16: ApplicantsInfo 생성 경로에서 declined 집계를 실제 값으로 유지하도록 수정하세요.
getClubApplicants의 API 응답 매핑과 useApplicantSSE의 prevData 기반 인라인 객체에 declined를
포함하고, 지원자 상태가 ApplicationStatus.DECLINED로 변경될 때 해당 집계를 갱신해 초기값 0에 고정되지 않도록 하세요.
---
Nitpick comments:
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.stories.tsx`:
- Around line 21-27: Replace the inline width styles in
ApplicantListRow.stories.tsx at lines 21-27 and 152-164 with a shared
StoryViewport styled component, defining its styling through the project’s
styled-components/theme system and reusing it for both the decorator and
multi-row container. Also replace the decorator’s inline style in
StatusSummaryCard.stories.tsx at lines 9-15 with the appropriate StoryViewport
styled component, creating or reusing the same styled-component pattern.
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.tsx`:
- Around line 1-3: 정해진 import 순서와 type-only import 규칙에 맞게 네 곳의 import를 정리하십시오.
ApplicantListRow.tsx에서는 mapStatusToGroup, type Applicant, Styled 순으로 배치하고,
ApplicantListRow.stories.tsx, BulkActionBar.stories.tsx,
StatusSummaryCard.stories.tsx에서는 각 내부 컴포넌트 import를 먼저 둔 뒤 Meta·StoryObj 등
Storybook type import를 배치하십시오. 변경 대상은
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.tsx
1-3,
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.stories.tsx
1-5,
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/BulkActionBar/BulkActionBar.stories.tsx
1-2,
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/StatusSummaryCard/StatusSummaryCard.stories.tsx
1-2입니다.
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantSearchBox/ApplicantSearchBox.stories.tsx`:
- Around line 1-3: Reorder imports in ApplicantSearchBox.stories.tsx (lines
1-3), SortDropdown.stories.tsx (lines 1-3), and FormDropdownSelector.stories.tsx
(lines 1-4) so each internal component import (ApplicantSearchBox, SortDropdown,
or FormDropdownSelector) precedes the type-only Meta and StoryObj import,
following the external libraries, internal modules, types, then styles
convention.
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/FormDropdownSelector/FormDropdownSelector.tsx`:
- Around line 2-12: Update FormDropdownSelectorProps so selectedFormId and the
onSelect callback parameter use ApplicationFormId instead of string, importing
that type alongside ApplicationFormItem. Preserve the existing forms and
selection behavior while keeping the component’s ID boundary type-safe.
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/StatusFilterPills/StatusFilterPills.stories.tsx`:
- Line 1: Update StatusFilterPills.stories.tsx to type-import ComponentProps
from React and replace the React.ComponentProps usage with the imported type,
removing reliance on the global React namespace while preserving the existing
prop typing.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 5076e2be-4914-4ec7-b32c-270fc8197888
⛔ Files ignored due to path filters (3)
frontend/src/assets/images/icons/ExpandArrow.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/more_arraw_icon.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/triangle_down.svgis excluded by!**/*.svg
📒 Files selected for processing (25)
frontend/src/pages/AdminPage/components/ApplicationFormList/ApplicationFormList.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.stories.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantSearchBox/ApplicantSearchBox.stories.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantSearchBox/ApplicantSearchBox.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantSearchBox/ApplicantSearchBox.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/BulkActionBar/BulkActionBar.stories.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/BulkActionBar/BulkActionBar.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/BulkActionBar/BulkActionBar.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/FormDropdownSelector/FormDropdownSelector.stories.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/FormDropdownSelector/FormDropdownSelector.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/FormDropdownSelector/FormDropdownSelector.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/SortDropdown/SortDropdown.stories.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/SortDropdown/SortDropdown.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/SortDropdown/SortDropdown.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/StatusFilterPills/StatusFilterPills.stories.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/StatusFilterPills/StatusFilterPills.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/StatusFilterPills/StatusFilterPills.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/StatusSummaryCard/StatusSummaryCard.stories.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/StatusSummaryCard/StatusSummaryCard.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/StatusSummaryCard/StatusSummaryCard.tsxfrontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.styles.tsfrontend/src/types/applicants.tsfrontend/src/utils/mapStatusToGroup.ts
| const InteractiveRow = ( | ||
| args: React.ComponentProps<typeof ApplicantListRow>, | ||
| ) => { |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fd -HI -t f 'tsconfig.*\.json$' . -x sh -c '
echo "== $1 =="
rg -n "\"allowUmdGlobalAccess\"" "$1" || true
' sh {}
rg -n -C 2 'React\.ComponentProps' \
frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.stories.tsxRepository: Moadong/moadong
Length of output: 1980
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== frontend/tsconfig.json =="
cat -n frontend/tsconfig.json
echo "== ApplicantListRow.stories.tsx imports and ComponentProps usages =="
sed -n '1,80p' frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.stories.tsx | cat -n
echo "== package versions relevant to TypeScript/React =="
node - <<'JS'
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('frontend/package.json','utf8'));
for (const k of ['typescript','react','gatsby','vite']) {
console.log(k, pkg.dependencies?.[k] || pkg.devDependencies?.[k] || '(not listed)');
}
JS
echo "== vite.config storybook/ts context =="
fd -HI -t f 'vite.config.*|storybook.*|tsconfig.*' frontend -x sh -c 'echo "== $1 =="; sed -n "1,220p" "$1"' sh {}Repository: Moadong/moadong
Length of output: 50372
🌐 Web query:
TypeScript allowUmdGlobalAccess allows accessing React like module UMD global exports
💡 Result:
The allowUmdGlobalAccess compiler option in TypeScript enables access to UMD global exports from within module files [1][2]. By default, TypeScript prevents code inside a module file (a file containing import or export declarations) from accessing global variables declared by UMD libraries [1][3]. This design enforces the practice of importing dependencies explicitly [1]. However, when allowUmdGlobalAccess is set to true in your tsconfig.json, TypeScript will allow you to reference these UMD globals (such as React, if it is available as a global) from anywhere in your project, including within modules [1][3]. Key details regarding this flag: - Purpose: It serves as an escape hatch for scenarios where a library is provided as a global variable at runtime (e.g., via a script tag) but you cannot or choose not to import it [1][4]. - Scope: It affects all UMD global declarations, not just a specific library [3]. - Safety: Enabling this flag is generally considered less safe than explicit imports, as it may hide errors where you intended to import a module but forgot, or where a library is not actually present in the global scope at runtime [4][3]. To use it, add the flag to your tsconfig.json under compilerOptions: { "compilerOptions": { "allowUmdGlobalAccess": true } } This feature was introduced in TypeScript 3.5 [3][5].
Citations:
- 1: https://www.typescriptlang.org/tsconfig/allowUmdGlobalAccess.html
- 2: https://www.typescriptlang.org/tsconfig/
- 3: https://devblogs.microsoft.com/typescript/announcing-typescript-3-5-rc/
- 4: Add flag to allow access to UMD globals from modules microsoft/TypeScript#30776
- 5: https://typescript-v2-729.vercel.app/docs/handbook/release-notes/typescript-3-5.html
React.ComponentProps 대신 타입 import를 사용해 주세요.
이 파일은 이미 import문을 사용해 TypeScript 모듈입니다. 현재 설정에서 React 전역 네임스페이스는 타입에 의존하지 않는 런타임 네임스페이스로 해석되기 때문에 React.ComponentProps<typeof ApplicantListRow>가 선언되지 않습니다. import type { ComponentProps } from 'react';와 함께 args: ComponentProps<typeof ApplicantListRow>로 변경해 Type-only import 규칙을 따르세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.stories.tsx`
around lines 33 - 35, Update the InteractiveRow args type to use a type-only
ComponentProps import from react instead of React.ComponentProps, then reference
ComponentProps<typeof ApplicantListRow> directly while preserving the existing
component behavior.
| <Styled.Row onClick={onClick}> | ||
| <Styled.CheckboxWrapper | ||
| onClick={(e) => { | ||
| e.stopPropagation(); | ||
| onCheck(applicant.id); | ||
| }} | ||
| > | ||
| <Styled.Checkbox $checked={isChecked} /> | ||
| </Styled.CheckboxWrapper> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.tsx"
if [ -f "$file" ]; then
echo "== file exists =="
wc -l "$file"
echo "== outline =="
ast-grep outline "$file" --view expanded || true
echo "== full file =="
cat -n "$file"
else
echo "missing $file"
echo "== similar files =="
fd -a 'ApplicantListRow\.tsx$' .
fi
echo "== related styled components definitions =="
rg -n "Styled\.Row|Styled\.CheckboxWrapper|Checkbox\$\|styled\.div|styled\.Checkbox|Checkbox\\\(\\\$checked|onClick=" frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow -S || trueRepository: Moadong/moadong
Length of output: 2788
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.styles.ts"
echo "== styles file =="
if [ -f "$file" ]; then
wc -l "$file"
cat -n "$file"
else
echo "missing $file"
fd -a 'ApplicantListRow\.styles\.ts$' .
fi
echo "== broader style definitions in file =="
rg -n "styled\.(div|button|input)|\$\$|Checkbox|\$checked|checkbox|radio|Row|CheckboxWrapper|Info|Name|StatusTag|Date|Tag" "$file" -S || true
echo "== prop/event definitions in parent =="
rowParent="$(fd 'ApplicantListTab|ApplicantsListTab|ApplicantsTab' frontend/src/pages | head -30)"
echo "$rowParent"
rg -n "ApplicantListRow|onCheck|onClick=|isSelected|isChecked|checkbox|radio" frontend/src/pages/AdminPage/tabs/ApplicantsTab -S || trueRepository: Moadong/moadong
Length of output: 16161
행과 선택 컨트롤에 키보드 접근성을 구현하십시오.
Styled.Row와 Styled.CheckboxWrapper가 스크립트 이벤트만으로 동작하는 div라서 키보드 사용자는 행 열기와 지원자 선택을 사용할 수 없습니다. 선택 상태를 알리는 ARIA 상태도 없습니다. 행 열기는 버튼을, 선택 전환은 연결된 label이 있는 input type="checkbox"로 구현해 네이티브 키보드/스크린 리더 동작을 사용하십시오.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/ApplicantListRow/ApplicantListRow.tsx`
around lines 30 - 38, ApplicantListRow의 Styled.Row와 Styled.CheckboxWrapper를 스크립트
이벤트 전용 div 대신 네이티브 버튼과 연결된 label/input type="checkbox" 구조로 변경하십시오. 행 버튼은 키보드로 열
수 있게 하고, 체크박스는 onCheck(applicant.id)와 isChecked 상태를 연결하며 접근 가능한 label과 ARIA 상태를
제공하십시오. 체크박스 조작이 행 클릭을 함께 실행하지 않도록 기존 이벤트 분리 동작도 유지하십시오.
| const handleStatusSelect = (status: ApplicationStatus) => { | ||
| onStatusChange(status); | ||
| setIsStatusMenuOpen(false); | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
비활성화된 일괄 작업에서 상태 변경을 차단하세요.
상태 메뉴를 연 뒤 선택이 해제되면 enabled는 false가 되어도 메뉴는 남습니다. 이 상태에서 메뉴 항목을 누르면 onStatusChange가 실행됩니다. enabled가 false일 때 메뉴를 닫고 handleStatusSelect에서도 callback을 호출하지 마세요.
수정 예시
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
const [isStatusMenuOpen, setIsStatusMenuOpen] = useState(false);
+ useEffect(() => {
+ if (!enabled) setIsStatusMenuOpen(false);
+ }, [enabled]);
+
const handleStatusSelect = (status: ApplicationStatus) => {
+ if (!enabled) return;
onStatusChange(status);
setIsStatusMenuOpen(false);
};Also applies to: 40-51
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/BulkActionBar/BulkActionBar.tsx`
around lines 25 - 28, Update BulkActionBar’s handleStatusSelect and status-menu
interaction so disabled bulk actions cannot trigger onStatusChange: when enabled
is false, close the menu and return without invoking the callback. Ensure the
menu also closes when enabled becomes false after opening, including the related
enabled-dependent logic around the status menu.
| const [isOpen, setIsOpen] = useState(false); | ||
| const [thumbOffset, setThumbOffset] = useState(0); | ||
| const listRef = useRef<HTMLDivElement>(null); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
메뉴를 다시 열기 전에 thumb 위치를 초기화하세요.
라인 52의 조건부 렌더링은 닫힌 MenuList를 제거합니다. 다시 열면 목록의 scrollTop은 0이지만 thumbOffset은 이전 값을 유지합니다. 사용자가 다시 열면 thumb가 실제 목록 위치와 다르게 표시됩니다.
수정 예시
const [thumbOffset, setThumbOffset] = useState(0);
const listRef = useRef<HTMLDivElement>(null);
+ const handleToggle = () => {
+ if (!isOpen) setThumbOffset(0);
+ setIsOpen((prev) => !prev);
+ };
+
return (
<Styled.Wrapper>
<Styled.Trigger
$isOpen={isOpen}
- onClick={() => setIsOpen((prev) => !prev)}
+ onClick={handleToggle}
>Also applies to: 42-57
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/FormDropdownSelector/FormDropdownSelector.tsx`
around lines 19 - 21, Reset thumbOffset whenever the dropdown is opened or
before the conditionally rendered MenuList is recreated, so reopening starts
with a thumb position of 0 matching the list’s reset scrollTop. Update the state
flow around isOpen, thumbOffset, and the MenuList rendering without changing the
existing open/close behavior.
| <CustomDropDown | ||
| options={SORT_OPTIONS} | ||
| selected={value} | ||
| onSelect={(v) => { | ||
| onChange(v); | ||
| setIsOpen(false); | ||
| }} | ||
| open={isOpen} | ||
| onToggle={() => setIsOpen((prev) => !prev)} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
선택 시 중복된 상태 변경을 제거하세요.
CustomDropDown은 onSelect 뒤에 onToggle(open)를 호출합니다. 라인 30의 setIsOpen(false) 뒤에 라인 33의 함수형 토글이 실행됩니다. React 배치 업데이트에서 메뉴 상태가 다시 열림 상태가 됩니다.
onSelect에서는 onChange(v)만 호출하세요. 메뉴 닫기는 CustomDropDown의 토글 처리로 맡기세요.
수정 예시
options={SORT_OPTIONS}
selected={value}
onSelect={(v) => {
onChange(v);
- setIsOpen(false);
}}📝 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.
| <CustomDropDown | |
| options={SORT_OPTIONS} | |
| selected={value} | |
| onSelect={(v) => { | |
| onChange(v); | |
| setIsOpen(false); | |
| }} | |
| open={isOpen} | |
| onToggle={() => setIsOpen((prev) => !prev)} | |
| <CustomDropDown | |
| options={SORT_OPTIONS} | |
| selected={value} | |
| onSelect={(v) => { | |
| onChange(v); | |
| }} | |
| open={isOpen} | |
| onToggle={() => setIsOpen((prev) => !prev)} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/components/mobile/SortDropdown/SortDropdown.tsx`
around lines 25 - 33, Remove the setIsOpen(false) call from the CustomDropDown
onSelect handler so it only invokes onChange(v); keep menu closing delegated to
CustomDropDown’s existing onToggle behavior.
| reviewRequired: number; | ||
| scheduledInterview: number; | ||
| accepted: number; | ||
| declined: number; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# ApplicantsInfo 선언과 객체 생성 지점을 확인합니다.
rg -n -C 4 '\bApplicantsInfo\b|reviewRequired:|scheduledInterview:|declined:' frontend
# 지원자 집계 API와 mock 응답을 찾습니다.
rg -n -C 4 'applicants.*info|ApplicantsInfo|reviewRequired|scheduledInterview' frontendRepository: Moadong/moadong
Length of output: 21094
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import json, pathlib, re
print("Repository files:")
for f in json.loads(__import__("subprocess").check_output(["git","ls-files","frontend"], text=True).split(b"\n")[0:-1]):
pass
types = pathlib.Path("frontend/src/types/applicants.ts")
use_sse = pathlib.Path("frontend/src/hooks/useApplicantSSE.ts")
api = pathlib.Path("frontend/src/apis/applicants.ts")
applicants_tab = pathlib.Path("frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx")
print("\napplicant type declaration:")
print(types.read_text())
print("\nuseApplicantSSE relevant section:")
txt = use_sse.read_text()
for m in re.finditer(r'applicantsData:|const useApplicantSSE|const eventSourceRef|setApplicantsData\((.*?)\);|setApplicantsData\((?:\([^)]*\)|[^)]){0,1200}\);\s*(?:const|});', txt, re.S):
pass
lines = txt.splitlines()
for i, line in enumerate(lines, 1):
if any(s in line for s in ["useState<ApplicantsInfo", "createApplicantSSE", "setApplicantsData", "reviewRequired", "scheduledInterview", "declined", "ApplicationStatus.DECLINED"]):
lo=max(1,i-4); hi=min(len(lines),i+5)
print(f"\n-- useApplicantSSE.ts:{lo}-{hi} --")
for j in range(lo, hi+1):
print(f"{j:4}: {lines[j-1]}")
print("\nApplicantsTab relevant API + StatusSummaryCard usages:")
lines = applicants_tab.read_text().splitlines()
for i, line in enumerate(lines, 1):
if any(s in line for s in ["getClubApplicants", "<StatusSummaryCard", "declined", "applicantsData"]):
lo=max(1,i-2); hi=min(len(lines),i+7)
print(f"\n-- ApplicantsTab.tsx:{lo}-{hi} --")
for j in range(lo, hi+1):
print(f"{j:4}: {lines[j-1]}")
print("\nAPI implementation:")
print(api.read_text())
print("\nApplicantsInfo object literal constructor occurrences with declined (text):")
occurrences = []
for file in pathlib.Path("frontend").rglob("*"):
if file.suffix not in (".ts", ".tsx"):
continue
text = file.read_text(errors="ignore")
if re.search(r"\bdeclined\s*:", text):
occurrences.append(file)
print(f"\n-- {file} --")
for lineno, line in enumerate(text.splitlines(), 1):
if "declined:" in line:
lo=max(1,lineno-8); hi=min(len(text.splitlines()),lineno+8)
for k in range(lo, hi+1):
print(f"{k}: {text.splitlines()[k-1]}")
print("\nApplicantsInfo declarations/constructors without declined:")
def is_literal_like(text):
return bool(re.search(r"\b(total|reviewRequired|scheduledInterview|accepted):", text))
for file in pathlib.Path("frontend").rglob("*"):
if file.suffix not in (".ts", ".tsx"):
continue
text = file.read_text(errors="ignore")
if "setApplicantsData" in text and not re.search(r"\bdeclined\s*:", text):
print(f"\n-- {file} lacks declined assignment --")
for lineno, line in enumerate(text.splitlines(), 1):
if "setApplicantsData" in line or "ApplicantsInfo" in line:
print(f"{lineno}: {line}")
PYRepository: Moadong/moadong
Length of output: 286
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import json, pathlib, re
files = json.loads(__import__("subprocess").check_output(["git","ls-files","frontend"], text=True))
print("Repository files count:", len(files))
types = pathlib.Path("frontend/src/types/applicants.ts")
use_sse = pathlib.Path("frontend/src/hooks/useApplicantSSE.ts")
api = pathlib.Path("frontend/src/apis/applicants.ts")
applicants_tab = pathlib.Path("frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.tsx")
print("\napplicant type declaration:")
print(types.read_text())
print("\nuseApplicantSSE relevant occurrences:")
txt = use_sse.read_text()
for i, line in enumerate(txt.splitlines(), 1):
if any(s in line for s in ["useState<ApplicantsInfo", "createApplicantSSE", "setApplicantsData", "reviewRequired", "scheduledInterview", "declined", "ApplicationStatus.DECLINED"]):
lo=max(1,i-4); hi=min(len(txt.splitlines()),i+5)
print(f"\n-- useApplicantSSE.ts:{lo}-{hi} --")
for j in range(lo, hi+1):
print(f"{j:4}: {txt.splitlines()[j-1]}")
print("\nApplicantsTab relevant API + StatusSummaryCard usages:")
lines = applicants_tab.read_text().splitlines()
for i, line in enumerate(lines, 1):
if any(s in line for s in ["getClubApplicants", "<StatusSummaryCard", "declined", "applicantsData"]):
lo=max(1,i-2); hi=min(len(lines),i+7)
print(f"\n-- ApplicantsTab.tsx:{lo}-{hi} --")
for j in range(lo, hi+1):
print(f"{j:4}: {lines[j-1]}")
print("\nAPI implementation:")
print(api.read_text())
print("\nApplicantsInfo object literal constructor occurrences with declined:")
occurrences = []
for file in pathlib.Path("frontend").rglob("*"):
if file.suffix not in (".ts", ".tsx"):
continue
text = file.read_text(errors="ignore")
if re.search(r"\bdeclined\s*:", text):
occurrences.append(file)
print(f"\n-- {file} --")
for lineno, line in enumerate(text.splitlines(), 1):
if "declined:" in line:
lo=max(1,lineno-8); hi=min(len(text.splitlines()),lineno+8)
for k in range(lo, hi+1):
print(f"{k}: {text.splitlines()[k-1]}")
if not occurrences:
print("\nNo file contains a 'declined:' assignment in frontend/src.")
print("\nApplicantsInfo setApplicantsData assignments without declined:")
for file in pathlib.Path("frontend").rglob("*"):
if file.suffix not in (".ts", ".tsx"):
continue
text = file.read_text(errors="ignore")
if "setApplicantsData" in text and not re.search(r"\bdeclined\s*:", text):
print(f"\n-- {file} lacks declined assignment --")
for lineno, line in enumerate(text.splitlines(), 1):
if "setApplicantsData" in line or "ApplicantsInfo" in line:
print(f"{lineno}: {line}")
PYRepository: Moadong/moadong
Length of output: 635
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp=$(mktemp)
printf '\napplicant type declaration:\n'
cat -n frontend/src/types/applicants.ts
printf '\ncreateApplicantSSE and applicantsData assignments:\n'
rg -n -C 8 'createApplicantSSE|useState<ApplicantsInfo|setApplicantsData|reviewRequired|scheduledInterview|declined|ApplicationStatus.DECLINED' frontend/src/hooks frontend/src/apis frontend/src/pages/AdminPage || true
printf '\nApplicantsInfo object literal occurrences:\n'
rg -n -C 6 'declined\s*:' frontend/src || true
printf '\nApplicantsTab getClubApplicants and StatusSummaryCard usage:\n'
rg -n -C 6 'getClubApplicants|<StatusSummaryCard|applicantsData\?\.declined|declined' frontend/src/pages/AdminPage/tabs/ApplicantsTab || true
printf '\nAPI implementation:\n'
cat -n frontend/src/apis/applicants.tsRepository: Moadong/moadong
Length of output: 42431
declined를 ApplicantsInfo 생성 경로에 추가하세요.
getClubApplicants가 /api/club/apply/info/...에서 ApplicantsInfo를 받아오고, useApplicantSSE도 prevData 기반 인라인 객체를 만들지만 declined는 현재 0이면 항상 0으로 표시됩니다. ApplicationStatus.DECLINED로 상태가 변경될 때 집계 값을 갱신하도록 조정해야 실제 불합격 지원자 수가 반영됩니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/types/applicants.ts` at line 16, ApplicantsInfo 생성 경로에서 declined
집계를 실제 값으로 유지하도록 수정하세요. getClubApplicants의 API 응답 매핑과 useApplicantSSE의 prevData
기반 인라인 객체에 declined를 포함하고, 지원자 상태가 ApplicationStatus.DECLINED로 변경될 때 해당 집계를 갱신해
초기값 0에 고정되지 않도록 하세요.
#️⃣연관된 이슈
#1885
📝작업 내용
지원자 현황 페이지 모바일 구현에 필요한 공통 컴포넌트 7개를 추가했습니다.
추가된 컴포넌트 (
components/mobile/)ApplicantListRowApplicantSearchBoxBulkActionBarFormDropdownSelectorSortDropdownStatusFilterPillsStatusSummaryCard기타 변경사항
ExpandArrow.svg제거 →more_arraw_icon.svg?react로 통일 (stroke="currentColor"적용)triangle_down.svg추가 (BulkActionBar 드롭다운 아이콘)ApplicantsInfo타입에declined필드 추가mapStatusToGroup라벨 수정:서류검토→검토 전,불합→불합격각 컴포넌트에 Storybook 스토리 포함 (상태별 / 인터랙티브)
🔗 Storybook: https://67904e61c16daa99a63b44a7-glatxlhpyz.chromatic.com/
중점적으로 리뷰받고 싶은 부분(선택)
FormDropdownSelector커스텀 스크롤바 구현 방식 (scrollTop 기반 thumb offset 계산)BulkActionBar활성/비활성 상태 처리 방식🫡 참고사항
각 컴포넌트는 독립적으로 구현되어 있으며, 페이지 조합은 별도 브랜치에서 진행 예정입니다.
Summary by CodeRabbit