[feature] 지원서 관리 메인 페이지 및 년도별 서브페이지 구현 - #1881
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지원서 관리 화면을 데스크톱·모바일 환경으로 분리하고, 활성 지원서 고정 및 연도별 그룹화를 적용했습니다. 모바일 카드·메뉴·연도 상세 화면과 지원서 생성·수정 화면, 공통 테마와 플로팅 버튼도 추가했습니다. Changes지원서 목록 관리
Estimated code review effort: 4 (Complex) | ~60 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 |
There was a problem hiding this comment.
Actionable comments posted: 14
🧹 Nitpick comments (8)
frontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsx (1)
2-2: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win아이콘 import 바인딩을 camelCase로 변경해 주세요.
Morebutton은 로컬 값 바인딩이므로moreButtonIcon처럼 camelCase로 이름을 바꾸고 사용처도 함께 수정해 주세요.As per coding guidelines:
frontend/**/*.{ts,tsx}에서는 변수와 함수 이름에 camelCase를 사용합니다.🤖 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/components/ApplicationRow/ApplicationRowItem.tsx` at line 2, Rename the imported SVG binding Morebutton to a camelCase name such as moreButtonIcon, and update every usage of that binding in the ApplicationRowItem component while leaving the icon source unchanged.Source: Coding guidelines
frontend/src/pages/AdminPage/components/ApplicationFormList/ApplicationFormList.tsx (1)
1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReact 이벤트 타입 import를 명시적으로 통일해 주세요.
기본
Reactimport를 제거했지만 Line 132에서 여전히React.MouseEvent를 사용합니다.ApplicationRowItem.tsx처럼import type { MouseEvent } from 'react'와e: MouseEvent를 사용하거나 React namespace import를 유지해 타입 해석이 전역 typings/tsconfig 설정에 의존하지 않도록 해 주세요.🤖 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/components/ApplicationFormList/ApplicationFormList.tsx` at line 1, Update the event type usage in ApplicationFormList to match the existing React import style: replace the React.MouseEvent reference with an explicit type-only MouseEvent import from react and use MouseEvent for the handler parameter, or consistently restore the React namespace import. Ensure type resolution does not depend on global typings or tsconfig settings.frontend/src/pages/AdminPage/components/MobileFloatingButton/MobileFloatingButton.tsx (1)
3-8: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
ariaLabel을 필수 prop으로 변경 권장.아이콘이
alt=''(장식용)이라ariaLabel이 버튼의 유일한 접근 가능한 이름입니다. 현재 호출부는 모두 값을 전달하지만, optional로 두면 향후 호출부에서 누락 시 스크린리더에 이름 없는 버튼이 될 수 있습니다.♻️ 필수 prop으로 변경
interface MobileFloatingButtonProps { onClick: () => void; icon: string; - ariaLabel?: string; + ariaLabel: string; bottom?: string; }🤖 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/components/MobileFloatingButton/MobileFloatingButton.tsx` around lines 3 - 8, Update the MobileFloatingButtonProps interface to make ariaLabel required instead of optional, ensuring every MobileFloatingButton usage must provide an accessible button name while leaving the other props unchanged.frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx (2)
25-31: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueStorybook 레이아웃도 styled-components와 테마를 사용하세요. 인라인
style은 저장소 스타일링 규칙을 우회합니다.
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx#L25-L31: 폭 335px 래퍼를 styled-component로 추출하세요.frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationCardMobile/ApplicationCardMobile.stories.tsx#L22-L28: 폭 335px 래퍼를 styled-component로 추출하세요.frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationListCardMobile/ApplicationListCardMobile.stories.tsx#L22-L28: 폭 335px 래퍼를 styled-component로 추출하세요.frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.stories.tsx#L10-L16: 메뉴 캔버스의 위치·크기 스타일을 styled-component로 추출하세요.🤖 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/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx` around lines 25 - 31, Replace the inline Storybook layout styles with themed styled-components: in frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx:25-31, frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationCardMobile/ApplicationCardMobile.stories.tsx:22-28, and frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationListCardMobile/ApplicationListCardMobile.stories.tsx:22-28, extract the 335px wrapper into a styled component and use it in each decorator. In frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.stories.tsx:10-16, extract the menu canvas position and dimensions into a styled component and use that wrapper instead of inline styles.Source: Coding guidelines
1-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value타입 import를 내부 모듈 뒤로 이동하세요. 저장소 규칙의 순서(외부 라이브러리 → 내부 모듈 → 타입 → 스타일)와 다릅니다.
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx#L1-L9:ApplicationActiveSectionMobileimport 뒤에 타입 import를 배치하세요.frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.tsx#L1-L7:ApplicationCardMobileimport 뒤에 타입 import를 배치하세요.frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationCardMobile/ApplicationCardMobile.stories.tsx#L1-L6:ApplicationCardMobileimport 뒤에 타입 import를 배치하세요.frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationCardMobile/ApplicationCardMobile.tsx#L1-L9: 모든 내부 값 import를 타입 import보다 앞에 배치하세요.frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationListCardMobile/ApplicationListCardMobile.stories.tsx#L1-L6:ApplicationListCardMobileimport 뒤에 타입 import를 배치하세요.frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.stories.tsx#L1-L3:ApplicationMenuimport 뒤에 Storybook 타입 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/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx` around lines 1 - 9, Reorder imports to follow the repository convention of external libraries, internal value modules, type imports, then styles. In frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx:1-9, place type imports after ApplicationActiveSectionMobile; in ApplicationActiveSectionMobile.tsx:1-7, place them after ApplicationCardMobile; in ApplicationCardMobile.stories.tsx:1-6, after ApplicationCardMobile; in ApplicationCardMobile.tsx:1-9, move all internal value imports before type imports; in ApplicationListCardMobile.stories.tsx:1-6, after ApplicationListCardMobile; and in frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.stories.tsx:1-3, place Storybook type imports after ApplicationMenu.Source: Coding guidelines
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.tsx (1)
1-26: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueimport 순서가 가이드라인과 다릅니다.
@/styles/PageContainer.styles(17번째 줄)가 타입 import(@/types/application, 18-23번째 줄)보다 먼저 위치해 있습니다. "external libraries → internal modules → types → styles" 순서를 지켜야 합니다. 또한 24-25번째 줄에서 동일 파일(./ApplicationEditTab.styles)을 두 번에 나눠 import하고 있어 하나로 합칠 수 있습니다.As per coding guidelines, "Order imports as external libraries, internal modules, types, then styles."
♻️ 제안 수정
-import { useAdminClubId } from '`@/store/useAdminClubStore`'; -import { PageContainer } from '`@/styles/PageContainer.styles`'; -import { +import { useAdminClubId } from '`@/store/useAdminClubStore`'; +import { ApplicationFormData, ApplicationFormMode, Question, QuestionType, } from '`@/types/application`'; -import * as Styled from './ApplicationEditTab.styles'; -import { QuestionDivider } from './ApplicationEditTab.styles'; +import { PageContainer } from '`@/styles/PageContainer.styles`'; +import * as Styled, { QuestionDivider } from './ApplicationEditTab.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/ApplicationTab/ApplicationEditTab/ApplicationEditTab.tsx` around lines 1 - 26, Reorder imports in ApplicationEditTab.tsx to follow external libraries, internal modules, types, then styles, moving the ApplicationFormData-related type import before PageContainer styles. Merge the two imports from ./ApplicationEditTab.styles into a single import.Source: Coding guidelines
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/components/mobile/ApplicationTypeTab/ApplicationTypeTab.styles.ts (1)
29-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win폰트 스타일이 테마 타이포그래피 대신 하드코딩되어 있습니다.
colors테마는 잘 사용하고 있지만,font-family/font-weight/font-size/line-height/letter-spacing은 값이 직접 하드코딩되어 있습니다. 이번 PR 계열에서frontend/src/styles/theme/typography.ts가 공통 타이포그래피 목적으로 확장되었으므로, 가능하다면 해당 토큰을 재사용하는 것이 일관성 측면에서 좋습니다.
typography.ts에 이 스타일(14px/600/140%/-0.02em)에 대응하는 토큰이 있는지 확인해 주세요.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/ApplicationTab/ApplicationEditTab/components/mobile/ApplicationTypeTab/ApplicationTypeTab.styles.ts` around lines 29 - 34, Update the typography styling in the relevant styled component of ApplicationTypeTab to reuse the matching token from the theme typography definitions for 14px, weight 600, 140% line height, and -0.02em letter spacing, including the font family where supported. Remove the corresponding hardcoded typography values while preserving the existing colors.gray[700] color.Source: Coding guidelines
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.styles.ts (1)
58-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win제출 버튼 타이포그래피도 토큰으로 통일하세요.
Line 62-63의 하드코딩된 글꼴 크기·굵기를
typography.paragraph.p1등 대응 토큰으로 교체하세요.수정 예시
- font-size: 1.25rem; - font-weight: 600; + ${setTypography(typography.paragraph.p1)}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/ApplicationTab/ApplicationEditTab/ApplicationEditTab.styles.ts` around lines 58 - 69, Update the submit button styles in the relevant styled component to replace the hardcoded font-size and font-weight with the corresponding project typography token, such as typography.paragraph.p1, while preserving the existing button appearance and other styles.Source: Coding guidelines
🤖 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/docs/features/admin/application/mobile-components.md`:
- Around line 51-63: 문서의 컴포넌트명을 ApplicationFAB에서 실제 공통 컴포넌트인
MobileFloatingButton으로 변경하고, 관련 코드 목록에
src/pages/AdminPage/components/MobileFloatingButton/ 경로를 추가하세요.
In
`@frontend/src/pages/AdminPage/components/ApplicationFormList/ApplicationFormList.tsx`:
- Around line 228-231: 지원서 상태 용어를 “활성화된 지원서”와 “활성화/비활성화” 기준으로 통일하세요.
frontend/src/pages/AdminPage/components/ApplicationFormList/ApplicationFormList.tsx의
빈 상태 문구와 Line 193 섹션 제목을 동일한 용어로 맞추고,
frontend/docs/features/admin/application/desktop.md의 7-8행 “게시된 지원서” 표현과 34-37행
상태 규칙도 UI와 일관되게 수정하세요.
In
`@frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.styles.ts`:
- Around line 23-44: Change MenuItem from a styled div to a styled button so
ApplicationMenu click actions are keyboard accessible and expose button
semantics. Remove the button’s default border, and add a visible :focus-visible
style while preserving the existing layout, colors, hover behavior, and
ToggleMenuItem styling.
In `@frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.tsx`:
- Around line 30-53: Update Styled.MenuItem and Styled.ToggleMenuItem usages in
ApplicationMenu so the interactive entries use native button semantics,
preferably by configuring their styled components to render as button elements
while preserving existing click handlers and styling. Ensure all menu actions
remain keyboard-focusable and activate correctly with Enter/Space.
- Line 3: Rename the imported binding Delete_applicant to the camelCase name
deleteApplicantIcon and update its usage in Styled.MenuIcon so the asset
reference remains unchanged.
In
`@frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.tsx`:
- Around line 42-47: Update the nextId initialization in ApplicationEditTab so
it is derived from the current question IDs in INITIAL_FORM_DATA (and
persisted/local-storage form data when available), using the maximum existing ID
plus one rather than a fixed 1. Preserve the existing useEffect synchronization
for existingFormData and ensure addQuestion always receives an ID unique from
the current question set.
In
`@frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.styles.ts`:
- Around line 6-88: Extract the duplicated MobileContainer/Container, SortRow,
SortButton, SortText, and CardList definitions into a shared mobile list layout
styles module. Update
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.styles.ts
lines 6-88 and
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationYearDetailPage/ApplicationYearDetailPage.styles.ts
lines 6-56 to import and reuse those shared styles, preserving each component’s
existing exported names and behavior.
- Around line 6-21: Extract the duplicated MobileContainer/Container, SortRow,
SortButton, SortText, and CardList styled definitions from
ApplicationListTabMobile.styles.ts and ApplicationYearDetailPage.styles.ts into
a shared style module. Update both consumers to import and reuse the shared
definitions, preserving their current styling and responsive behavior.
In
`@frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.tsx`:
- Around line 50-58: Extract the duplicated menu behavior from
ApplicationListTabMobile and ApplicationYearDetailPage into reusable custom
hooks, covering handleMenuToggle, outside-click detection, and scroll-trigger
disabling. Update both components to use the shared hooks while preserving their
existing open-menu key and toggle behavior.
- Around line 50-123: Extract the duplicated menu toggle, scroll-trigger
disabling, and outside-click logic into reusable hooks named
useApplicationCardMenu, useDisableScrollTrigger, and useOutsideClick. Update
ApplicationListTabMobile.tsx (lines 50-123) to use the hooks, and replace the
equivalent logic in ApplicationYearDetailPage.tsx (lines 43-73) with the same
hooks, preserving existing behavior and state handling.
- Around line 205-230: Update the latestForm calculation in the groupedByYear
map to safely handle empty group.forms arrays by providing an initial reduce
value or guarding empty groups before rendering ApplicationListCardMobile.
Preserve the existing newest-editedAt selection for non-empty groups and ensure
empty groups cannot crash rendering or produce an invalid application.
In
`@frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationListCardMobile/ApplicationListCardMobile.tsx`:
- Around line 11-23: Update ApplicationListCardMobile to accept an explicit year
prop and use it for the displayed academic year instead of deriving the year
from application.editedAt. In ApplicationListTabMobile, pass group.semesterYear
as year when rendering each card, keeping onNavigate’s existing group-semester
navigation unchanged.
In `@frontend/src/types/application.ts`:
- Around line 71-77: Update ApplicationFormItem.createdAt to be optional, and
adjust every “recent creation” sort or display path that consumes it to safely
handle a missing value by applying the existing fallback or disabling that
behavior until the backend supplies the field. Ensure date sorting never passes
an absent value to new Date and preserve other ApplicationFormItem behavior.
In `@frontend/src/utils/formatKSTDateTime.ts`:
- Around line 38-49: Update the date conversion in the KST formatting function
to avoid reparsing the toLocaleString() output with Date. Use
Intl.DateTimeFormat.formatToParts() with the Asia/Seoul timezone to extract
year, month, day, hour, and minute directly, then preserve the existing Korean
AM/PM and display formatting.
---
Nitpick comments:
In
`@frontend/src/pages/AdminPage/components/ApplicationFormList/ApplicationFormList.tsx`:
- Line 1: Update the event type usage in ApplicationFormList to match the
existing React import style: replace the React.MouseEvent reference with an
explicit type-only MouseEvent import from react and use MouseEvent for the
handler parameter, or consistently restore the React namespace import. Ensure
type resolution does not depend on global typings or tsconfig settings.
In
`@frontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsx`:
- Line 2: Rename the imported SVG binding Morebutton to a camelCase name such as
moreButtonIcon, and update every usage of that binding in the ApplicationRowItem
component while leaving the icon source unchanged.
In
`@frontend/src/pages/AdminPage/components/MobileFloatingButton/MobileFloatingButton.tsx`:
- Around line 3-8: Update the MobileFloatingButtonProps interface to make
ariaLabel required instead of optional, ensuring every MobileFloatingButton
usage must provide an accessible button name while leaving the other props
unchanged.
In
`@frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.styles.ts`:
- Around line 58-69: Update the submit button styles in the relevant styled
component to replace the hardcoded font-size and font-weight with the
corresponding project typography token, such as typography.paragraph.p1, while
preserving the existing button appearance and other styles.
In
`@frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.tsx`:
- Around line 1-26: Reorder imports in ApplicationEditTab.tsx to follow external
libraries, internal modules, types, then styles, moving the
ApplicationFormData-related type import before PageContainer styles. Merge the
two imports from ./ApplicationEditTab.styles into a single import.
In
`@frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/components/mobile/ApplicationTypeTab/ApplicationTypeTab.styles.ts`:
- Around line 29-34: Update the typography styling in the relevant styled
component of ApplicationTypeTab to reuse the matching token from the theme
typography definitions for 14px, weight 600, 140% line height, and -0.02em
letter spacing, including the font family where supported. Remove the
corresponding hardcoded typography values while preserving the existing
colors.gray[700] color.
In
`@frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx`:
- Around line 25-31: Replace the inline Storybook layout styles with themed
styled-components: in
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx:25-31,
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationCardMobile/ApplicationCardMobile.stories.tsx:22-28,
and
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationListCardMobile/ApplicationListCardMobile.stories.tsx:22-28,
extract the 335px wrapper into a styled component and use it in each decorator.
In
frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.stories.tsx:10-16,
extract the menu canvas position and dimensions into a styled component and use
that wrapper instead of inline styles.
- Around line 1-9: Reorder imports to follow the repository convention of
external libraries, internal value modules, type imports, then styles. In
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx:1-9,
place type imports after ApplicationActiveSectionMobile; in
ApplicationActiveSectionMobile.tsx:1-7, place them after ApplicationCardMobile;
in ApplicationCardMobile.stories.tsx:1-6, after ApplicationCardMobile; in
ApplicationCardMobile.tsx:1-9, move all internal value imports before type
imports; in ApplicationListCardMobile.stories.tsx:1-6, after
ApplicationListCardMobile; and in
frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.stories.tsx:1-3,
place Storybook type imports after ApplicationMenu.
🪄 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: d39d014d-325f-473b-a2f4-943a0596a86d
⛔ Files ignored due to path filters (10)
frontend/src/assets/images/icons/Delete_applicant.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/Morebutton.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/checkBox.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/check_inactive.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/check_square_icon.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/copy_icon.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/ellipsis_icon.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/pencil_icon_3.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/sort_asc_icon.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/sort_desc_icon.svgis excluded by!**/*.svg
📒 Files selected for processing (42)
frontend/docs/features/admin/application/desktop.mdfrontend/docs/features/admin/application/mobile-components.mdfrontend/src/pages/AdminPage/AdminRoutes.tsxfrontend/src/pages/AdminPage/components/ApplicationFormList/ApplicationFormList.tsxfrontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.stories.tsxfrontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.styles.tsfrontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.tsxfrontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.style.tsfrontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsxfrontend/src/pages/AdminPage/components/MobileFloatingButton/MobileFloatingButton.stories.tsxfrontend/src/pages/AdminPage/components/MobileFloatingButton/MobileFloatingButton.styles.tsfrontend/src/pages/AdminPage/components/MobileFloatingButton/MobileFloatingButton.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationMenu.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/components/mobile/ApplicationTypeTab/ApplicationTypeTab.stories.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/components/mobile/ApplicationTypeTab/ApplicationTypeTab.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/components/mobile/ApplicationTypeTab/ApplicationTypeTab.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTab.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTab.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationCardMobile/ApplicationCardMobile.stories.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationCardMobile/ApplicationCardMobile.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationCardMobile/ApplicationCardMobile.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationListCardMobile/ApplicationListCardMobile.stories.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationListCardMobile/ApplicationListCardMobile.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationListCardMobile/ApplicationListCardMobile.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationYearDetailPage/ApplicationYearDetailPage.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationYearDetailPage/ApplicationYearDetailPage.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationTab.tsxfrontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTabMobile.styles.tsfrontend/src/pages/AdminPage/tabs/ClubIntroEditTab/components/mobile/AwardEditPage/AwardEditPage.styles.tsfrontend/src/pages/AdminPage/tabs/ClubIntroEditTab/components/mobile/AwardEditPage/AwardEditPage.tsxfrontend/src/styles/theme/typography.tsfrontend/src/types/application.tsfrontend/src/utils/formatKSTDateTime.ts
💤 Files with no reviewable changes (2)
- frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationMenu.tsx
- frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/components/mobile/AwardEditPage/AwardEditPage.styles.ts
| export const MobileContainer = styled.div` | ||
| display: flex; | ||
| flex-direction: column; | ||
| width: 100%; | ||
| max-width: 500px; | ||
| min-height: 100vh; | ||
| margin: 0 auto; | ||
| padding-bottom: calc(80px + env(safe-area-inset-bottom)); | ||
| box-shadow: 0px 2px 12px rgba(0, 0, 0, 0.04); | ||
|
|
||
| ${media.mobile} { | ||
| max-width: 100%; | ||
| margin: 0; | ||
| box-shadow: none; | ||
| } | ||
| `; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
ApplicationYearDetailPage.styles.ts와 스타일 정의가 완전히 중복됩니다.
MobileContainer/Container, SortRow, SortButton, SortText, CardList가 두 파일에 동일하게 작성되어 있습니다. 공통 스타일 모듈로 추출하는 것을 권장합니다. 상세 내용은 통합 코멘트에서 다룹니다.
Also applies to: 61-88
🤖 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/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.styles.ts`
around lines 6 - 21, Extract the duplicated MobileContainer/Container, SortRow,
SortButton, SortText, and CardList styled definitions from
ApplicationListTabMobile.styles.ts and ApplicationYearDetailPage.styles.ts into
a shared style module. Update both consumers to import and reuse the shared
definitions, preserving their current styling and responsive behavior.
| export const MobileContainer = styled.div` | ||
| display: flex; | ||
| flex-direction: column; | ||
| width: 100%; | ||
| max-width: 500px; | ||
| min-height: 100vh; | ||
| margin: 0 auto; | ||
| padding-bottom: calc(80px + env(safe-area-inset-bottom)); | ||
| box-shadow: 0px 2px 12px rgba(0, 0, 0, 0.04); | ||
|
|
||
| ${media.mobile} { | ||
| max-width: 100%; | ||
| margin: 0; | ||
| box-shadow: none; | ||
| } | ||
| `; | ||
|
|
||
| export const Content = styled.div` | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 20px; | ||
| padding: 32px 20px; | ||
| `; | ||
|
|
||
| export const SectionHeader = styled.div` | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 4px; | ||
| `; | ||
|
|
||
| export const SectionTitle = styled.h2` | ||
| ${setTypography(typography.title.title5)} | ||
| letter-spacing: -0.02em; | ||
| color: ${colors.base.black}; | ||
| margin: 0; | ||
| `; | ||
|
|
||
| export const SectionSubtitle = styled.p` | ||
| ${setTypography(typography.button.button1)} | ||
| letter-spacing: -0.02em; | ||
| color: ${colors.gray[700]}; | ||
| margin: 0; | ||
| `; | ||
|
|
||
| export const MainContent = styled.div` | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 51px; | ||
| `; | ||
|
|
||
| export const ListSection = styled.div` | ||
| display: flex; | ||
| flex-direction: column; | ||
| `; | ||
|
|
||
| export const SortRow = styled.div` | ||
| display: flex; | ||
| justify-content: flex-end; | ||
| padding-bottom: 12px; | ||
| `; | ||
|
|
||
| export const SortButton = styled.button` | ||
| display: flex; | ||
| flex-direction: row; | ||
| align-items: center; | ||
| gap: 4px; | ||
| background: none; | ||
| border: none; | ||
| cursor: pointer; | ||
| padding: 0; | ||
| `; | ||
|
|
||
| export const SortText = styled.span` | ||
| ${setTypography(typography.etc.medium12)} | ||
| letter-spacing: -0.02em; | ||
| color: ${colors.base.black}; | ||
| `; | ||
|
|
||
| export const CardList = styled.div` | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 8px; | ||
| `; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
두 파일의 Container/SortRow/SortButton/SortText/CardList 스타일 정의가 완전히 동일합니다.
공통 스타일 파일(예: MobileListLayout.styles.ts)로 분리해 두 곳에서 import하는 것을 권장합니다.
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.styles.ts#L6-L88:MobileContainer(6-21),SortRow/SortButton/SortText/CardList(61-88)를 공통 모듈로 이동.frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationYearDetailPage/ApplicationYearDetailPage.styles.ts#L6-L56:Container(6-21),SortRow/SortButton/SortText/CardList(29-56)를 동일 공통 모듈에서 import.
📍 Affects 2 files
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.styles.ts#L6-L88(this comment)frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationYearDetailPage/ApplicationYearDetailPage.styles.ts#L6-L56
🤖 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/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.styles.ts`
around lines 6 - 88, Extract the duplicated MobileContainer/Container, SortRow,
SortButton, SortText, and CardList definitions into a shared mobile list layout
styles module. Update
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.styles.ts
lines 6-88 and
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationYearDetailPage/ApplicationYearDetailPage.styles.ts
lines 6-56 to import and reuse those shared styles, preserving each component’s
existing exported names and behavior.
| const handleMenuToggle = ( | ||
| e: React.MouseEvent, | ||
| id: string, | ||
| prefix: string, | ||
| ) => { | ||
| e.stopPropagation(); | ||
| const key = `${prefix}-${id}`; | ||
| setOpenMenuId((prev) => (prev === key ? null : key)); | ||
| }; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
ApplicationYearDetailPage.tsx와 로직이 그대로 중복됩니다.
handleMenuToggle, 스크롤 트리거 비활성화 이펙트, 바깥 클릭 감지 이펙트가 ApplicationYearDetailPage.tsx(43-73줄)에도 동일하게 존재합니다. 커스텀 훅(useOutsideClick, useDisableScrollTrigger, useApplicationMenu 등)으로 추출해 재사용하는 것을 권장합니다. 상세 내용은 통합 코멘트에서 다룹니다.
Also applies to: 103-123
🤖 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/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.tsx`
around lines 50 - 58, Extract the duplicated menu behavior from
ApplicationListTabMobile and ApplicationYearDetailPage into reusable custom
hooks, covering handleMenuToggle, outside-click detection, and scroll-trigger
disabling. Update both components to use the shared hooks while preserving their
existing open-menu key and toggle behavior.
| const handleMenuToggle = ( | ||
| e: React.MouseEvent, | ||
| id: string, | ||
| prefix: string, | ||
| ) => { | ||
| e.stopPropagation(); | ||
| const key = `${prefix}-${id}`; | ||
| setOpenMenuId((prev) => (prev === key ? null : key)); | ||
| }; | ||
|
|
||
| const handleToggleStatus = (id: string, status: ApplicationFormStatus) => { | ||
| updateStatus( | ||
| { applicationFormId: id, currentStatus: status }, | ||
| { | ||
| onSuccess: () => setOpenMenuId(null), | ||
| onError: () => alert('상태 변경에 실패했습니다.'), | ||
| }, | ||
| ); | ||
| }; | ||
|
|
||
| const handleEdit = (id: string) => { | ||
| navigate(`/admin/application-list/${id}/edit`); | ||
| }; | ||
|
|
||
| const handleDelete = (id: string) => { | ||
| if ( | ||
| window.confirm( | ||
| '지원서 양식을 정말 삭제하시겠습니까?\n삭제된 양식은 복구할 수 없습니다.', | ||
| ) | ||
| ) { | ||
| deleteApplication(id, { | ||
| onSuccess: () => setOpenMenuId(null), | ||
| onError: () => alert('지원서 삭제에 실패했습니다.'), | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| const handleDuplicate = (id: string) => { | ||
| duplicateApplication(id, { | ||
| onSuccess: () => { | ||
| setOpenMenuId(null); | ||
| alert('지원서가 성공적으로 복제되었습니다.'); | ||
| }, | ||
| onError: () => alert('지원서 복제에 실패했습니다.'), | ||
| }); | ||
| }; | ||
|
|
||
| const handleNavigateToYear = (year: number) => { | ||
| savedScrollY.current = window.scrollY; | ||
| setSelectedYear(year); | ||
| setActivePage('year-detail'); | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| document.body.dataset[SCROLL_TRIGGER_DISABLED] = 'true'; | ||
| window.dispatchEvent(new Event('scroll')); | ||
| return () => { | ||
| delete document.body.dataset[SCROLL_TRIGGER_DISABLED]; | ||
| }; | ||
| }, []); | ||
|
|
||
| useEffect(() => { | ||
| const handleOutsideClick = (e: MouseEvent) => { | ||
| if (menuRef.current && !menuRef.current.contains(e.target as Node)) { | ||
| setOpenMenuId(null); | ||
| } | ||
| }; | ||
| if (openMenuId !== null) { | ||
| document.addEventListener('mousedown', handleOutsideClick); | ||
| } | ||
| return () => { | ||
| document.removeEventListener('mousedown', handleOutsideClick); | ||
| }; | ||
| }, [openMenuId]); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
모바일 목록/연도 상세 페이지 간 상태·이펙트 로직이 그대로 복사되어 있습니다.
메뉴 토글 핸들러, 스크롤 트리거 비활성화 이펙트, 바깥 클릭 감지 이펙트가 두 컴포넌트에 동일하게 작성되어 있습니다. 공통 훅으로 추출하면 유지보수성이 크게 개선되고, 향후 유사 모바일 화면(예: 지원서 편집 탭) 추가 시 재사용할 수 있습니다.
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.tsx#L50-L123:handleMenuToggle, 스크롤 트리거 비활성화 이펙트(103-109), 바깥 클릭 감지 이펙트(111-123)를 공통 훅(useApplicationCardMenu,useDisableScrollTrigger,useOutsideClick)으로 추출.frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationYearDetailPage/ApplicationYearDetailPage.tsx#L43-L73: 동일한 공통 훅을 재사용하도록 교체.
📍 Affects 2 files
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.tsx#L50-L123(this comment)frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationYearDetailPage/ApplicationYearDetailPage.tsx#L43-L73
🤖 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/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.tsx`
around lines 50 - 123, Extract the duplicated menu toggle, scroll-trigger
disabling, and outside-click logic into reusable hooks named
useApplicationCardMenu, useDisableScrollTrigger, and useOutsideClick. Update
ApplicationListTabMobile.tsx (lines 50-123) to use the hooks, and replace the
equivalent logic in ApplicationYearDetailPage.tsx (lines 43-73) with the same
hooks, preserving existing behavior and state handling.
| {groupedByYear.map((group) => { | ||
| const latestForm = group.forms.reduce((latest, form) => | ||
| new Date(form.editedAt) > new Date(latest.editedAt) | ||
| ? form | ||
| : latest, | ||
| ); | ||
| return ( | ||
| <ApplicationListCardMobile | ||
| key={group.semesterYear} | ||
| application={latestForm} | ||
| isActive={latestForm.status === 'ACTIVE'} | ||
| uniqueKeyPrefix={`yeargroup-${group.semesterYear}`} | ||
| openMenuId={openMenuId} | ||
| menuRef={menuRef} | ||
| onToggleStatus={handleToggleStatus} | ||
| onEdit={handleEdit} | ||
| onMenuToggle={handleMenuToggle} | ||
| onDelete={handleDelete} | ||
| onDuplicate={handleDuplicate} | ||
| onNavigate={() => | ||
| handleNavigateToYear(group.semesterYear) | ||
| } | ||
| /> | ||
| ); | ||
| })} | ||
| </Styled.CardList> |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
초기값 없는 reduce로 인한 크래시 위험.
group.forms가 빈 배열인 연도 그룹이 존재하면(예: 백엔드가 해당 연도에 지원서가 0건인 그룹을 반환하는 경우) 초기값 없는 reduce가 TypeError: Reduce of empty array with no initial value를 던져 모바일 목록 페이지 전체 렌더링이 크래시됩니다.
🐛 초기값 추가 및 빈 배열 가드
{groupedByYear.map((group) => {
- const latestForm = group.forms.reduce((latest, form) =>
- new Date(form.editedAt) > new Date(latest.editedAt)
- ? form
- : latest,
- );
+ if (group.forms.length === 0) return null;
+ const latestForm = group.forms.reduce((latest, form) =>
+ new Date(form.editedAt) > new Date(latest.editedAt)
+ ? form
+ : latest,
+ , group.forms[0]);📝 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.
| {groupedByYear.map((group) => { | |
| const latestForm = group.forms.reduce((latest, form) => | |
| new Date(form.editedAt) > new Date(latest.editedAt) | |
| ? form | |
| : latest, | |
| ); | |
| return ( | |
| <ApplicationListCardMobile | |
| key={group.semesterYear} | |
| application={latestForm} | |
| isActive={latestForm.status === 'ACTIVE'} | |
| uniqueKeyPrefix={`yeargroup-${group.semesterYear}`} | |
| openMenuId={openMenuId} | |
| menuRef={menuRef} | |
| onToggleStatus={handleToggleStatus} | |
| onEdit={handleEdit} | |
| onMenuToggle={handleMenuToggle} | |
| onDelete={handleDelete} | |
| onDuplicate={handleDuplicate} | |
| onNavigate={() => | |
| handleNavigateToYear(group.semesterYear) | |
| } | |
| /> | |
| ); | |
| })} | |
| </Styled.CardList> | |
| {groupedByYear.map((group) => { | |
| if (group.forms.length === 0) return null; | |
| const latestForm = group.forms.reduce((latest, form) => | |
| new Date(form.editedAt) > new Date(latest.editedAt) | |
| ? form | |
| : latest, | |
| , group.forms[0]); | |
| return ( | |
| <ApplicationListCardMobile | |
| key={group.semesterYear} | |
| application={latestForm} | |
| isActive={latestForm.status === 'ACTIVE'} | |
| uniqueKeyPrefix={`yeargroup-${group.semesterYear}`} | |
| openMenuId={openMenuId} | |
| menuRef={menuRef} | |
| onToggleStatus={handleToggleStatus} | |
| onEdit={handleEdit} | |
| onMenuToggle={handleMenuToggle} | |
| onDelete={handleDelete} | |
| onDuplicate={handleDuplicate} | |
| onNavigate={() => | |
| handleNavigateToYear(group.semesterYear) | |
| } | |
| /> | |
| ); | |
| })} | |
| </Styled.CardList> |
🤖 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/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.tsx`
around lines 205 - 230, Update the latestForm calculation in the groupedByYear
map to safely handle empty group.forms arrays by providing an initial reduce
value or guarding empty groups before rendering ApplicationListCardMobile.
Preserve the existing newest-editedAt selection for non-empty groups and ensure
empty groups cannot crash rendering or produce an invalid application.
| export interface ApplicationFormItem { | ||
| id: ApplicationFormId; | ||
| title: string; | ||
| createdAt: string; | ||
| editedAt: string; | ||
| status: ApplicationFormStatus; | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg 'frontend/src/types/application\.ts|frontend/src/.*/ApplicationYearDetailPage\.tsx|frontend/src/hooks|frontend/src/apis' || true
echo "== application types =="
if [ -f frontend/src/types/application.ts ]; then
cat -n frontend/src/types/application.ts | sed -n '1,140p'
fi
echo "== ApplicationYearDetailPage occurrences =="
rg -n "ApplicationYearDetailPage|createdAt|recent|recently|최근 생성순|getTime\(|new Date\(a\.createdAt|new Date\(b\.createdAt" frontend/src || trueRepository: Moadong/moadong
Length of output: 10244
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from datetime import datetime
def sort(items):
return sorted(items, key=lambda a: datetime.fromisoformat(a.get('createdAt') or '1970-01-01') if a.get('createdAt') else datetime.min)
for name, items in {
'with createdAt from backend': [{'createdAt': '2025-07-01'}, {'createdAt': '2024-01-01'}],
'without createdAt before backend': [{}, {}],
}.items():
print(name, sort(items))
PYRepository: Moadong/moadong
Length of output: 282
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from datetime import datetime
def sort_with_invalid_date_logic(items):
return sorted(items, key=lambda a: datetime.fromisoformat(a.get('createdAt')) if a.get('createdAt') is not None else datetime.min)
for name, items in {
'with createdAt from backend': [{'createdAt': '2025-07-01'}, {'createdAt': '2024-01-01'}],
'without createdAt before backend': [{}, {}],
}.items():
try:
print(name, sort_with_invalid_date_logic(items), 'new Date(undefined).getTime()=', datetime.fromisoformat(None))
except Exception as e:
print(name, 'throws', type(e).__name__, str(e), 'new Date(undefined).getTime()=', float('nan') if not float('nan') else 'NaN')
PYRepository: Moadong/moadong
Length of output: 390
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== hooks useApplication and useApplicants relevant types/calls =="
for f in frontend/src/apis/application.ts frontend/src/hooks/Queries/useApplication.ts frontend/src/hooks/Queries/useApplicants.ts frontend/src/apis/applicants.ts; do
if [ -f "$f" ]; then
echo "--- $f ---"
cat -n "$f" | sed -n '1,220p'
fi
done
echo "== ApplicationYearDetailPage relevant sections =="
cat -n frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationYearDetailPage/ApplicationYearDetailPage.tsx | sed -n '1,150p'Repository: Moadong/moadong
Length of output: 17811
백엔드 응답 변경 전에 createdAt 필수 선언/종속 정렬을 막아 주세요.
createdAt은 백엔드 응답에만 있는 필드인데 타입을 필수로 선언하면 new Date(b.createdAt).getTime()에서 값이 없어도 타입 오류가 잡히지 않고 Invalid Date 상태로 정렬이 깨집니다. 당분간 createdAt?: string으로 처리하고 “최근 생성순” 정렬/표시는 폴백하거나 비활성화하는 편이 안전합니다.
🤖 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/application.ts` around lines 71 - 77, Update
ApplicationFormItem.createdAt to be optional, and adjust every “recent creation”
sort or display path that consumes it to safely handle a missing value by
applying the existing fallback or disabling that behavior until the backend
supplies the field. Ensure date sorting never passes an absent value to new Date
and preserve other ApplicationFormItem behavior.
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 14
🧹 Nitpick comments (8)
frontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsx (1)
2-2: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win아이콘 import 바인딩을 camelCase로 변경해 주세요.
Morebutton은 로컬 값 바인딩이므로moreButtonIcon처럼 camelCase로 이름을 바꾸고 사용처도 함께 수정해 주세요.As per coding guidelines:
frontend/**/*.{ts,tsx}에서는 변수와 함수 이름에 camelCase를 사용합니다.🤖 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/components/ApplicationRow/ApplicationRowItem.tsx` at line 2, Rename the imported SVG binding Morebutton to a camelCase name such as moreButtonIcon, and update every usage of that binding in the ApplicationRowItem component while leaving the icon source unchanged.Source: Coding guidelines
frontend/src/pages/AdminPage/components/ApplicationFormList/ApplicationFormList.tsx (1)
1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReact 이벤트 타입 import를 명시적으로 통일해 주세요.
기본
Reactimport를 제거했지만 Line 132에서 여전히React.MouseEvent를 사용합니다.ApplicationRowItem.tsx처럼import type { MouseEvent } from 'react'와e: MouseEvent를 사용하거나 React namespace import를 유지해 타입 해석이 전역 typings/tsconfig 설정에 의존하지 않도록 해 주세요.🤖 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/components/ApplicationFormList/ApplicationFormList.tsx` at line 1, Update the event type usage in ApplicationFormList to match the existing React import style: replace the React.MouseEvent reference with an explicit type-only MouseEvent import from react and use MouseEvent for the handler parameter, or consistently restore the React namespace import. Ensure type resolution does not depend on global typings or tsconfig settings.frontend/src/pages/AdminPage/components/MobileFloatingButton/MobileFloatingButton.tsx (1)
3-8: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
ariaLabel을 필수 prop으로 변경 권장.아이콘이
alt=''(장식용)이라ariaLabel이 버튼의 유일한 접근 가능한 이름입니다. 현재 호출부는 모두 값을 전달하지만, optional로 두면 향후 호출부에서 누락 시 스크린리더에 이름 없는 버튼이 될 수 있습니다.♻️ 필수 prop으로 변경
interface MobileFloatingButtonProps { onClick: () => void; icon: string; - ariaLabel?: string; + ariaLabel: string; bottom?: string; }🤖 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/components/MobileFloatingButton/MobileFloatingButton.tsx` around lines 3 - 8, Update the MobileFloatingButtonProps interface to make ariaLabel required instead of optional, ensuring every MobileFloatingButton usage must provide an accessible button name while leaving the other props unchanged.frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx (2)
25-31: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueStorybook 레이아웃도 styled-components와 테마를 사용하세요. 인라인
style은 저장소 스타일링 규칙을 우회합니다.
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx#L25-L31: 폭 335px 래퍼를 styled-component로 추출하세요.frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationCardMobile/ApplicationCardMobile.stories.tsx#L22-L28: 폭 335px 래퍼를 styled-component로 추출하세요.frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationListCardMobile/ApplicationListCardMobile.stories.tsx#L22-L28: 폭 335px 래퍼를 styled-component로 추출하세요.frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.stories.tsx#L10-L16: 메뉴 캔버스의 위치·크기 스타일을 styled-component로 추출하세요.🤖 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/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx` around lines 25 - 31, Replace the inline Storybook layout styles with themed styled-components: in frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx:25-31, frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationCardMobile/ApplicationCardMobile.stories.tsx:22-28, and frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationListCardMobile/ApplicationListCardMobile.stories.tsx:22-28, extract the 335px wrapper into a styled component and use it in each decorator. In frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.stories.tsx:10-16, extract the menu canvas position and dimensions into a styled component and use that wrapper instead of inline styles.Source: Coding guidelines
1-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value타입 import를 내부 모듈 뒤로 이동하세요. 저장소 규칙의 순서(외부 라이브러리 → 내부 모듈 → 타입 → 스타일)와 다릅니다.
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx#L1-L9:ApplicationActiveSectionMobileimport 뒤에 타입 import를 배치하세요.frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.tsx#L1-L7:ApplicationCardMobileimport 뒤에 타입 import를 배치하세요.frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationCardMobile/ApplicationCardMobile.stories.tsx#L1-L6:ApplicationCardMobileimport 뒤에 타입 import를 배치하세요.frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationCardMobile/ApplicationCardMobile.tsx#L1-L9: 모든 내부 값 import를 타입 import보다 앞에 배치하세요.frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationListCardMobile/ApplicationListCardMobile.stories.tsx#L1-L6:ApplicationListCardMobileimport 뒤에 타입 import를 배치하세요.frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.stories.tsx#L1-L3:ApplicationMenuimport 뒤에 Storybook 타입 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/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx` around lines 1 - 9, Reorder imports to follow the repository convention of external libraries, internal value modules, type imports, then styles. In frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx:1-9, place type imports after ApplicationActiveSectionMobile; in ApplicationActiveSectionMobile.tsx:1-7, place them after ApplicationCardMobile; in ApplicationCardMobile.stories.tsx:1-6, after ApplicationCardMobile; in ApplicationCardMobile.tsx:1-9, move all internal value imports before type imports; in ApplicationListCardMobile.stories.tsx:1-6, after ApplicationListCardMobile; and in frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.stories.tsx:1-3, place Storybook type imports after ApplicationMenu.Source: Coding guidelines
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.tsx (1)
1-26: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueimport 순서가 가이드라인과 다릅니다.
@/styles/PageContainer.styles(17번째 줄)가 타입 import(@/types/application, 18-23번째 줄)보다 먼저 위치해 있습니다. "external libraries → internal modules → types → styles" 순서를 지켜야 합니다. 또한 24-25번째 줄에서 동일 파일(./ApplicationEditTab.styles)을 두 번에 나눠 import하고 있어 하나로 합칠 수 있습니다.As per coding guidelines, "Order imports as external libraries, internal modules, types, then styles."
♻️ 제안 수정
-import { useAdminClubId } from '`@/store/useAdminClubStore`'; -import { PageContainer } from '`@/styles/PageContainer.styles`'; -import { +import { useAdminClubId } from '`@/store/useAdminClubStore`'; +import { ApplicationFormData, ApplicationFormMode, Question, QuestionType, } from '`@/types/application`'; -import * as Styled from './ApplicationEditTab.styles'; -import { QuestionDivider } from './ApplicationEditTab.styles'; +import { PageContainer } from '`@/styles/PageContainer.styles`'; +import * as Styled, { QuestionDivider } from './ApplicationEditTab.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/ApplicationTab/ApplicationEditTab/ApplicationEditTab.tsx` around lines 1 - 26, Reorder imports in ApplicationEditTab.tsx to follow external libraries, internal modules, types, then styles, moving the ApplicationFormData-related type import before PageContainer styles. Merge the two imports from ./ApplicationEditTab.styles into a single import.Source: Coding guidelines
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/components/mobile/ApplicationTypeTab/ApplicationTypeTab.styles.ts (1)
29-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win폰트 스타일이 테마 타이포그래피 대신 하드코딩되어 있습니다.
colors테마는 잘 사용하고 있지만,font-family/font-weight/font-size/line-height/letter-spacing은 값이 직접 하드코딩되어 있습니다. 이번 PR 계열에서frontend/src/styles/theme/typography.ts가 공통 타이포그래피 목적으로 확장되었으므로, 가능하다면 해당 토큰을 재사용하는 것이 일관성 측면에서 좋습니다.
typography.ts에 이 스타일(14px/600/140%/-0.02em)에 대응하는 토큰이 있는지 확인해 주세요.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/ApplicationTab/ApplicationEditTab/components/mobile/ApplicationTypeTab/ApplicationTypeTab.styles.ts` around lines 29 - 34, Update the typography styling in the relevant styled component of ApplicationTypeTab to reuse the matching token from the theme typography definitions for 14px, weight 600, 140% line height, and -0.02em letter spacing, including the font family where supported. Remove the corresponding hardcoded typography values while preserving the existing colors.gray[700] color.Source: Coding guidelines
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.styles.ts (1)
58-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win제출 버튼 타이포그래피도 토큰으로 통일하세요.
Line 62-63의 하드코딩된 글꼴 크기·굵기를
typography.paragraph.p1등 대응 토큰으로 교체하세요.수정 예시
- font-size: 1.25rem; - font-weight: 600; + ${setTypography(typography.paragraph.p1)}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/ApplicationTab/ApplicationEditTab/ApplicationEditTab.styles.ts` around lines 58 - 69, Update the submit button styles in the relevant styled component to replace the hardcoded font-size and font-weight with the corresponding project typography token, such as typography.paragraph.p1, while preserving the existing button appearance and other styles.Source: Coding guidelines
🤖 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/docs/features/admin/application/mobile-components.md`:
- Around line 51-63: 문서의 컴포넌트명을 ApplicationFAB에서 실제 공통 컴포넌트인
MobileFloatingButton으로 변경하고, 관련 코드 목록에
src/pages/AdminPage/components/MobileFloatingButton/ 경로를 추가하세요.
In
`@frontend/src/pages/AdminPage/components/ApplicationFormList/ApplicationFormList.tsx`:
- Around line 228-231: 지원서 상태 용어를 “활성화된 지원서”와 “활성화/비활성화” 기준으로 통일하세요.
frontend/src/pages/AdminPage/components/ApplicationFormList/ApplicationFormList.tsx의
빈 상태 문구와 Line 193 섹션 제목을 동일한 용어로 맞추고,
frontend/docs/features/admin/application/desktop.md의 7-8행 “게시된 지원서” 표현과 34-37행
상태 규칙도 UI와 일관되게 수정하세요.
In
`@frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.styles.ts`:
- Around line 23-44: Change MenuItem from a styled div to a styled button so
ApplicationMenu click actions are keyboard accessible and expose button
semantics. Remove the button’s default border, and add a visible :focus-visible
style while preserving the existing layout, colors, hover behavior, and
ToggleMenuItem styling.
In `@frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.tsx`:
- Around line 30-53: Update Styled.MenuItem and Styled.ToggleMenuItem usages in
ApplicationMenu so the interactive entries use native button semantics,
preferably by configuring their styled components to render as button elements
while preserving existing click handlers and styling. Ensure all menu actions
remain keyboard-focusable and activate correctly with Enter/Space.
- Line 3: Rename the imported binding Delete_applicant to the camelCase name
deleteApplicantIcon and update its usage in Styled.MenuIcon so the asset
reference remains unchanged.
In
`@frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.tsx`:
- Around line 42-47: Update the nextId initialization in ApplicationEditTab so
it is derived from the current question IDs in INITIAL_FORM_DATA (and
persisted/local-storage form data when available), using the maximum existing ID
plus one rather than a fixed 1. Preserve the existing useEffect synchronization
for existingFormData and ensure addQuestion always receives an ID unique from
the current question set.
In
`@frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.styles.ts`:
- Around line 6-88: Extract the duplicated MobileContainer/Container, SortRow,
SortButton, SortText, and CardList definitions into a shared mobile list layout
styles module. Update
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.styles.ts
lines 6-88 and
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationYearDetailPage/ApplicationYearDetailPage.styles.ts
lines 6-56 to import and reuse those shared styles, preserving each component’s
existing exported names and behavior.
- Around line 6-21: Extract the duplicated MobileContainer/Container, SortRow,
SortButton, SortText, and CardList styled definitions from
ApplicationListTabMobile.styles.ts and ApplicationYearDetailPage.styles.ts into
a shared style module. Update both consumers to import and reuse the shared
definitions, preserving their current styling and responsive behavior.
In
`@frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.tsx`:
- Around line 50-58: Extract the duplicated menu behavior from
ApplicationListTabMobile and ApplicationYearDetailPage into reusable custom
hooks, covering handleMenuToggle, outside-click detection, and scroll-trigger
disabling. Update both components to use the shared hooks while preserving their
existing open-menu key and toggle behavior.
- Around line 50-123: Extract the duplicated menu toggle, scroll-trigger
disabling, and outside-click logic into reusable hooks named
useApplicationCardMenu, useDisableScrollTrigger, and useOutsideClick. Update
ApplicationListTabMobile.tsx (lines 50-123) to use the hooks, and replace the
equivalent logic in ApplicationYearDetailPage.tsx (lines 43-73) with the same
hooks, preserving existing behavior and state handling.
- Around line 205-230: Update the latestForm calculation in the groupedByYear
map to safely handle empty group.forms arrays by providing an initial reduce
value or guarding empty groups before rendering ApplicationListCardMobile.
Preserve the existing newest-editedAt selection for non-empty groups and ensure
empty groups cannot crash rendering or produce an invalid application.
In
`@frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationListCardMobile/ApplicationListCardMobile.tsx`:
- Around line 11-23: Update ApplicationListCardMobile to accept an explicit year
prop and use it for the displayed academic year instead of deriving the year
from application.editedAt. In ApplicationListTabMobile, pass group.semesterYear
as year when rendering each card, keeping onNavigate’s existing group-semester
navigation unchanged.
In `@frontend/src/types/application.ts`:
- Around line 71-77: Update ApplicationFormItem.createdAt to be optional, and
adjust every “recent creation” sort or display path that consumes it to safely
handle a missing value by applying the existing fallback or disabling that
behavior until the backend supplies the field. Ensure date sorting never passes
an absent value to new Date and preserve other ApplicationFormItem behavior.
In `@frontend/src/utils/formatKSTDateTime.ts`:
- Around line 38-49: Update the date conversion in the KST formatting function
to avoid reparsing the toLocaleString() output with Date. Use
Intl.DateTimeFormat.formatToParts() with the Asia/Seoul timezone to extract
year, month, day, hour, and minute directly, then preserve the existing Korean
AM/PM and display formatting.
---
Nitpick comments:
In
`@frontend/src/pages/AdminPage/components/ApplicationFormList/ApplicationFormList.tsx`:
- Line 1: Update the event type usage in ApplicationFormList to match the
existing React import style: replace the React.MouseEvent reference with an
explicit type-only MouseEvent import from react and use MouseEvent for the
handler parameter, or consistently restore the React namespace import. Ensure
type resolution does not depend on global typings or tsconfig settings.
In
`@frontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsx`:
- Line 2: Rename the imported SVG binding Morebutton to a camelCase name such as
moreButtonIcon, and update every usage of that binding in the ApplicationRowItem
component while leaving the icon source unchanged.
In
`@frontend/src/pages/AdminPage/components/MobileFloatingButton/MobileFloatingButton.tsx`:
- Around line 3-8: Update the MobileFloatingButtonProps interface to make
ariaLabel required instead of optional, ensuring every MobileFloatingButton
usage must provide an accessible button name while leaving the other props
unchanged.
In
`@frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.styles.ts`:
- Around line 58-69: Update the submit button styles in the relevant styled
component to replace the hardcoded font-size and font-weight with the
corresponding project typography token, such as typography.paragraph.p1, while
preserving the existing button appearance and other styles.
In
`@frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.tsx`:
- Around line 1-26: Reorder imports in ApplicationEditTab.tsx to follow external
libraries, internal modules, types, then styles, moving the
ApplicationFormData-related type import before PageContainer styles. Merge the
two imports from ./ApplicationEditTab.styles into a single import.
In
`@frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/components/mobile/ApplicationTypeTab/ApplicationTypeTab.styles.ts`:
- Around line 29-34: Update the typography styling in the relevant styled
component of ApplicationTypeTab to reuse the matching token from the theme
typography definitions for 14px, weight 600, 140% line height, and -0.02em
letter spacing, including the font family where supported. Remove the
corresponding hardcoded typography values while preserving the existing
colors.gray[700] color.
In
`@frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx`:
- Around line 25-31: Replace the inline Storybook layout styles with themed
styled-components: in
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx:25-31,
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationCardMobile/ApplicationCardMobile.stories.tsx:22-28,
and
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationListCardMobile/ApplicationListCardMobile.stories.tsx:22-28,
extract the 335px wrapper into a styled component and use it in each decorator.
In
frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.stories.tsx:10-16,
extract the menu canvas position and dimensions into a styled component and use
that wrapper instead of inline styles.
- Around line 1-9: Reorder imports to follow the repository convention of
external libraries, internal value modules, type imports, then styles. In
frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsx:1-9,
place type imports after ApplicationActiveSectionMobile; in
ApplicationActiveSectionMobile.tsx:1-7, place them after ApplicationCardMobile;
in ApplicationCardMobile.stories.tsx:1-6, after ApplicationCardMobile; in
ApplicationCardMobile.tsx:1-9, move all internal value imports before type
imports; in ApplicationListCardMobile.stories.tsx:1-6, after
ApplicationListCardMobile; and in
frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.stories.tsx:1-3,
place Storybook type imports after ApplicationMenu.
🪄 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: d39d014d-325f-473b-a2f4-943a0596a86d
⛔ Files ignored due to path filters (10)
frontend/src/assets/images/icons/Delete_applicant.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/Morebutton.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/checkBox.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/check_inactive.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/check_square_icon.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/copy_icon.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/ellipsis_icon.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/pencil_icon_3.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/sort_asc_icon.svgis excluded by!**/*.svgfrontend/src/assets/images/icons/sort_desc_icon.svgis excluded by!**/*.svg
📒 Files selected for processing (42)
frontend/docs/features/admin/application/desktop.mdfrontend/docs/features/admin/application/mobile-components.mdfrontend/src/pages/AdminPage/AdminRoutes.tsxfrontend/src/pages/AdminPage/components/ApplicationFormList/ApplicationFormList.tsxfrontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.stories.tsxfrontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.styles.tsfrontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.tsxfrontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.style.tsfrontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsxfrontend/src/pages/AdminPage/components/MobileFloatingButton/MobileFloatingButton.stories.tsxfrontend/src/pages/AdminPage/components/MobileFloatingButton/MobileFloatingButton.styles.tsfrontend/src/pages/AdminPage/components/MobileFloatingButton/MobileFloatingButton.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsxfrontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsTab.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationMenu.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/components/mobile/ApplicationTypeTab/ApplicationTypeTab.stories.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/components/mobile/ApplicationTypeTab/ApplicationTypeTab.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/components/mobile/ApplicationTypeTab/ApplicationTypeTab.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTab.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTab.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/ApplicationListTabMobile.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.stories.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationActiveSectionMobile/ApplicationActiveSectionMobile.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationCardMobile/ApplicationCardMobile.stories.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationCardMobile/ApplicationCardMobile.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationCardMobile/ApplicationCardMobile.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationListCardMobile/ApplicationListCardMobile.stories.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationListCardMobile/ApplicationListCardMobile.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationListCardMobile/ApplicationListCardMobile.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationYearDetailPage/ApplicationYearDetailPage.styles.tsfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationYearDetailPage/ApplicationYearDetailPage.tsxfrontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationTab.tsxfrontend/src/pages/AdminPage/tabs/ClubIntroEditTab/ClubIntroEditTabMobile.styles.tsfrontend/src/pages/AdminPage/tabs/ClubIntroEditTab/components/mobile/AwardEditPage/AwardEditPage.styles.tsfrontend/src/pages/AdminPage/tabs/ClubIntroEditTab/components/mobile/AwardEditPage/AwardEditPage.tsxfrontend/src/styles/theme/typography.tsfrontend/src/types/application.tsfrontend/src/utils/formatKSTDateTime.ts
💤 Files with no reviewable changes (2)
- frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationMenu.tsx
- frontend/src/pages/AdminPage/tabs/ClubIntroEditTab/components/mobile/AwardEditPage/AwardEditPage.styles.ts
🛑 Comments failed to post (8)
frontend/docs/features/admin/application/mobile-components.md (1)
51-63: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
실제 컴포넌트명으로 문서를 맞추세요.
PR에서 추가한 공통 컴포넌트는
MobileFloatingButton인데 문서는ApplicationFAB로 표기합니다. 제목을 변경하고src/pages/AdminPage/components/MobileFloatingButton/경로도 관련 코드에 추가하세요.🤖 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/docs/features/admin/application/mobile-components.md` around lines 51 - 63, 문서의 컴포넌트명을 ApplicationFAB에서 실제 공통 컴포넌트인 MobileFloatingButton으로 변경하고, 관련 코드 목록에 src/pages/AdminPage/components/MobileFloatingButton/ 경로를 추가하세요.frontend/src/pages/AdminPage/components/ApplicationFormList/ApplicationFormList.tsx (1)
228-231: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
지원서 상태 용어를 문서와 데스크톱 UI에서 일관되게 맞춰 주세요.
frontend/src/pages/AdminPage/components/ApplicationFormList/ApplicationFormList.tsx#L228-L231: 빈 상태 문구와 함께 Line 193의 섹션 제목도 “활성화된 지원서”로 변경하거나, 게시 용어를 유지하도록 문서 규칙을 수정해 주세요.frontend/docs/features/admin/application/desktop.md#L7-L8: 섹션 설명의 “게시된 지원서” 표현을 실제 상태 용어와 맞춰 주세요.frontend/docs/features/admin/application/desktop.md#L34-L37: “활성화/비활성화” 규칙과 위 섹션 표현이 일관되도록 정리해 주세요.📍 Affects 2 files
frontend/src/pages/AdminPage/components/ApplicationFormList/ApplicationFormList.tsx#L228-L231(this comment)frontend/docs/features/admin/application/desktop.md#L7-L8frontend/docs/features/admin/application/desktop.md#L34-L37🤖 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/components/ApplicationFormList/ApplicationFormList.tsx` around lines 228 - 231, 지원서 상태 용어를 “활성화된 지원서”와 “활성화/비활성화” 기준으로 통일하세요. frontend/src/pages/AdminPage/components/ApplicationFormList/ApplicationFormList.tsx의 빈 상태 문구와 Line 193 섹션 제목을 동일한 용어로 맞추고, frontend/docs/features/admin/application/desktop.md의 7-8행 “게시된 지원서” 표현과 34-37행 상태 규칙도 UI와 일관되게 수정하세요.frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.styles.ts (1)
23-44: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
메뉴 항목을
button으로 변경하세요.
ApplicationMenu.tsx에서 클릭 핸들러를 연결하지만 현재 기반 요소가div라 키보드 포커스·Enter/Space 실행·컨트롤 의미가 없습니다.styled.button으로 바꾸고 기본 테두리를 제거하며:focus-visible상태를 추가하세요.수정 예시
-export const MenuItem = styled.div<{ $danger?: boolean }>` +export const MenuItem = styled.button<{ $danger?: boolean }>` + border: 0; display: flex; align-items: center; align-self: stretch; padding: 4px 12px; + text-align: left; gap: 11px; ${setTypography(typography.paragraph.p6)} letter-spacing: -0.02em; color: ${({ $danger }) => ($danger ? '`#FF3B30`' : colors.gray[800])}; background-color: transparent; cursor: pointer; box-sizing: border-box; &:hover { background-color: ${colors.gray[100]}; } + + &:focus-visible { + outline: 2px solid ${colors.primary[800]}; + 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 MenuItem = styled.button<{ $danger?: boolean }>` border: 0; display: flex; align-items: center; align-self: stretch; padding: 4px 12px; text-align: left; gap: 11px; ${setTypography(typography.paragraph.p6)} letter-spacing: -0.02em; color: ${({ $danger }) => ($danger ? '`#FF3B30`' : colors.gray[800])}; background-color: transparent; cursor: pointer; box-sizing: border-box; &:hover { background-color: ${colors.gray[100]}; } &:focus-visible { outline: 2px solid ${colors.primary[800]}; outline-offset: -2px; } `; export const ToggleMenuItem = styled(MenuItem)<{ $active?: boolean }>` ${setTypography(typography.paragraph.p5)} letter-spacing: -0.02em; `;🤖 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/components/ApplicationMenu/ApplicationMenu.styles.ts` around lines 23 - 44, Change MenuItem from a styled div to a styled button so ApplicationMenu click actions are keyboard accessible and expose button semantics. Remove the button’s default border, and add a visible :focus-visible style while preserving the existing layout, colors, hover behavior, and ToggleMenuItem styling.frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.tsx (2)
3-3: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
camelCase 네이밍 위반:
Delete_applicant.As per coding guidelines, "Use camelCase for variables and functions". import 바인딩 이름은 자산 파일명과 별개로 자유롭게 지정 가능하므로 camelCase로 변경하는 것을 권장합니다.
♻️ 네이밍 수정
-import Delete_applicant from '`@/assets/images/icons/Delete_applicant.svg`'; +import deleteApplicantIcon from '`@/assets/images/icons/Delete_applicant.svg`';(사용처:
<Styled.MenuIcon src={Delete_applicant} />→src={deleteApplicantIcon})📝 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 deleteApplicantIcon from '`@/assets/images/icons/Delete_applicant.svg`';🤖 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/components/ApplicationMenu/ApplicationMenu.tsx` at line 3, Rename the imported binding Delete_applicant to the camelCase name deleteApplicantIcon and update its usage in Styled.MenuIcon so the asset reference remains unchanged.Source: Coding guidelines
30-53: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash ast-grep outline frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.styles.ts --items allRepository: Moadong/moadong
Length of output: 782
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "ApplicationMenu component:" cat -n frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.tsx echo echo "ApplicationMenu styles:" cat -n frontend/src/pages/AdminPage/components/ApplicationMenu/ApplicationMenu.styles.tsRepository: Moadong/moadong
Length of output: 4535
인터랙티브 메뉴 항목을 네이티브 버튼으로 교체하세요.
MenuItem과ToggleMenuItem이styled.div이기 때문에 기본적으로 포커스/엔터 클릭이 처리되지 않습니다.button요소나role="button"+ 키보드 핸들러와tabIndex를 추가해 접근성 문제를 해결해야 합니다.🤖 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/components/ApplicationMenu/ApplicationMenu.tsx` around lines 30 - 53, Update Styled.MenuItem and Styled.ToggleMenuItem usages in ApplicationMenu so the interactive entries use native button semantics, preferably by configuring their styled components to render as button elements while preserving existing click handlers and styling. Ensure all menu actions remain keyboard-focusable and activate correctly with Enter/Space.frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.tsx (1)
42-47: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash fd -a 'initialFormData.ts' fd -a 'initialFormData.ts' --exec cat -n {}Repository: Moadong/moadong
Length of output: 1485
🏁 Script executed:
#!/bin/bash set -euo pipefail file="frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.tsx" wc -l "$file" printf '\n--- outline ---\n' ast-grep outline "$file" || true printf '\n--- relevant source 1-110 ---\n' sed -n '1,110p' "$file" | cat -n printf '\n--- relevant source 210-265 ---\n' sed -n '210,265p' "$file" | cat -nRepository: Moadong/moadong
Length of output: 6905
🏁 Script executed:
#!/bin/bash set -euo pipefail file="frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.tsx" printf '\n--- relevant source 110-220 ---\n' sed -n '110,220p' "$file" | cat -n printf '\n--- locate ApplicationFormMode declarations and use ---\n' rg -n "ApplicationFormMode|INITIAL_FORM_DATA|nextId|addQuestion|removeQuestion|updateQuestionField" frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.tsx frontend/src/constants frontend/src/types -SRepository: Moadong/moadong
Length of output: 9191
🏁 Script executed:
#!/bin/bash set -euo pipefail file="frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationEditTab/ApplicationEditTab.tsx" printf '\n--- relevant source 244-360 ---\n' sed -n '244,360p' "$file" | cat -n printf '\n--- question builder render for questions ---\n' sed -n '282,350p' "$file" | cat -nRepository: Moadong/moadong
Length of output: 7311
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n--- check whether existingFormData.mode exists in other files/types ---\n' rg -n "mode:|formMode|formId|useGetApplication|createApplication\\(" frontend/src -S printf '\n--- package/tooling availability ---\n' node -v 2>/dev/null || true npm --version 2>/dev/null || trueRepository: Moadong/moadong
Length of output: 4565
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n--- ApplicationFormPage initialization state ---\n' file="frontend/src/pages/ApplicationFormPage/ApplicationFormPage.tsx" wc -l "$file" sed -n '1,120p' "$file" | cat -n printf '\n--- ApplicationFormPage submit id normalization ---\n' sed -n '120,220p' "$file" | cat -nRepository: Moadong/moadong
Length of output: 7316
nextId초기값을 시드 질문 id와 겹치지 않게 맞추세요.신규 생성 모드에서는
existingFormData가 없어useEffect에서nextId재생산이 안 되고, 현재NEXT_ID가 고정된1입니다.INITIAL_FORM_DATA에는 이미id: 1시드 질문이 있으므로addQuestion로 새 질문을 추가하면 같은 id가 반복되어updateQuestionField/removeQuestion/handleTypeChange/handleRequiredChange에서 이전 질문까지 영향을 주거나 Reactkey중복 경고를 일으킬 수 있습니다. 초기값/로컬 스토리지의 현재 질문 set을 기준으로 한 뒤+1 값을 사용하세요.🤖 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/ApplicationTab/ApplicationEditTab/ApplicationEditTab.tsx` around lines 42 - 47, Update the nextId initialization in ApplicationEditTab so it is derived from the current question IDs in INITIAL_FORM_DATA (and persisted/local-storage form data when available), using the maximum existing ID plus one rather than a fixed 1. Preserve the existing useEffect synchronization for existingFormData and ensure addQuestion always receives an ID unique from the current question set.frontend/src/pages/AdminPage/tabs/ApplicationTab/ApplicationListTab/components/mobile/ApplicationListCardMobile/ApplicationListCardMobile.tsx (1)
11-23: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
표시되는 연도가 실제 이동 대상 연도와 어긋날 수 있습니다.
ApplicationListTabMobile.tsx에서 이 카드는 연도 그룹의 최신 수정 폼(latestForm)을application으로 받지만, 실제 그룹 연도(group.semesterYear)는 전달되지 않습니다. 이 컴포넌트는 40번째 줄에서application.editedAt의 연도를 계산해 표시하는데,latestForm의 수정 시점이 실제 학기 연도와 다른 해로 넘어가면(예: 연말에 생성된 폼을 다음 해 초에 수정) 카드에 표시되는 "OOOO년도"와onNavigate가 실제로 이동시키는 연도(group.semesterYear)가 서로 달라집니다.
year를 prop으로 명시적으로 받아 표시하도록 수정하는 것을 권장합니다.🐛 `year`를 명시적 prop으로 전달
interface ApplicationListCardMobileProps { application: ApplicationFormItem; isActive: boolean; + year: number; uniqueKeyPrefix: string; ... } const ApplicationListCardMobile = ({ application, isActive, + year, uniqueKeyPrefix, ... }: ApplicationListCardMobileProps) => { const currentMenuKey = `${uniqueKeyPrefix}-${application.id}`; const isMenuOpen = openMenuId === currentMenuKey; - const year = new Date(application.editedAt).getFullYear();호출부(
ApplicationListTabMobile.tsx)에서는year={group.semesterYear}를 전달합니다.📝 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.interface ApplicationListCardMobileProps { application: ApplicationFormItem; isActive: boolean; year: number; uniqueKeyPrefix: string; openMenuId: string | null; menuRef: RefObject<HTMLDivElement | null>; onToggleStatus: (id: string, status: ApplicationFormStatus) => void; onEdit: (id: string) => void; onMenuToggle: (e: MouseEvent, id: string, prefix: string) => void; onDelete: (id: string) => void; onDuplicate?: (id: string) => void; onNavigate?: () => 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/ApplicationTab/ApplicationListTab/components/mobile/ApplicationListCardMobile/ApplicationListCardMobile.tsx` around lines 11 - 23, Update ApplicationListCardMobile to accept an explicit year prop and use it for the displayed academic year instead of deriving the year from application.editedAt. In ApplicationListTabMobile, pass group.semesterYear as year when rendering each card, keeping onNavigate’s existing group-semester navigation unchanged.frontend/src/utils/formatKSTDateTime.ts (1)
38-49: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash rg -nP 'new Date\(\s*.*\.toLocaleString\(' frontend/srcRepository: Moadong/moadong
Length of output: 153
🏁 Script executed:
#!/bin/bash set -euo pipefail printf 'Locate file:\n' fd -a 'formatKSTDateTime.ts' . || true printf '\nRepository files relevant:\n' git ls-files | rg 'frontend/src/utils/formatKSTDateTime\.ts|formatKSTDateTime' || true if [ -f frontend/src/utils/formatKSTDateTime.ts ]; then printf '\nFile contents with line numbers:\n' cat -n frontend/src/utils/formatKSTDateTime.ts fi printf '\nAll references:\n' rg -n "formatKSTDateTime|formatKSTDate|KST" frontend/src || true printf '\nAll new Date(toLocaleString patterns, broad):\n' rg -n "new Date\s*\(\s*.*toLocaleString|toLocaleString\s*\([^)]*\)\s*\)\s*,?\s*new Date" frontend/src || trueRepository: Moadong/moadong
Length of output: 5542
🌐 Web query:
MDN Date constructor parses locale-specific date string unsupported behavior💡 Result:
The JavaScript Date constructor and Date.parse method officially support only a specific subset of the ISO 8601 format (specifically YYYY-MM-DDTHH:mm:ss.sssZ) [1][2][3][4]. While these are the only formats guaranteed by the ECMAScript specification, browser engines are permitted to implement support for other, non-standard date formats at their discretion [1][2][4]. Because of this "implementation-defined" behavior, locale-specific date strings (such as "Mercredi 8 Septembre 2021" or "MM/DD/YYYY") are not reliably supported across different environments [2][5][4]. Key points regarding this behavior include: - Inconsistency Across Engines: Engines like V8 (Chrome, Edge, Node.js) and SpiderMonkey (Firefox) often use different heuristics to parse non-standard strings [5][6]. A string that parses correctly in one browser may return "Invalid Date" (resulting in NaN) in another [5]. - No Guarantee of Success: The ECMAScript specification does not require support for locale-specific formats, such as those produced by toLocaleString [2][7]. Consequently, relying on the Date constructor to parse such strings is considered unsafe practice [3][6]. - Implementation-Specific Heuristics: When an engine attempts to parse a non-standard string, it often relies on legacy code paths that may guess the format based on character patterns, which is prone to error and variation between browser versions [2][8][6]. - Recommendation: Developers should avoid using the Date constructor or Date.parse for any string format other than the required ISO 8601 subset [3][6]. For locale-specific or user-provided input, use dedicated date-parsing libraries (like date-fns, Luxon, or Day.js) or the modern Temporal API, which offer robust, predictable, and internationalized parsing capabilities [6].
Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date
- 2: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global%5FObjects/Date/parse
- 3: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Representing_dates_times
- 4: https://stackoverflow.com/questions/77034369/invalid-time-message-when-creating-date-object
- 5: https://bugzilla.mozilla.org/show_bug.cgi?id=1730155
- 6: https://futuresearch.ai/blog/javascript-thinks-everythings-a-date/
- 7: https://github.com/mdn/content/blob/main/files/en-us/web/javascript/reference/global_objects/date/parse/index.md
- 8: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse
🌐 Web query:
MDN Intl.DateTimeFormat formatToParts dayPeriod value PM AM documentation💡 Result:
In the Intl.DateTimeFormat.prototype.formatToParts method, the dayPeriod part type represents the period of the day, such as AM, PM, "in the morning," or "noon" [1][2]. Key details regarding the dayPeriod value: - The value is returned as a string within the object representing that component in the array returned by formatToParts [1]. - The inclusion of the dayPeriod component—and its specific string value—is governed by the dayPeriod option provided to the Intl.DateTimeFormat constructor [3][4]. - This option only takes effect when using a 12-hour clock (hourCycle: "h12" or "h11") [3][4]. - The possible values for the dayPeriod configuration option are "narrow", "short", and "long" [3][4]. - Note that for many locales, the resulting string value (e.g., "AM", "PM") may be identical regardless of whether you specify "narrow", "short", or "long", as implementations often follow specific locale data standards (CLDR) [3][4][5]. Example usage: const formatter = new Intl.DateTimeFormat("en-US", { hour: "numeric", dayPeriod: "short" }); const parts = formatter.formatToParts(new Date); // Example output for parts: [{ type: "hour", value: "3" },..., { type: "dayPeriod", value: "AM" }] [1][2]
Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/formatToParts
- 2: https://github.com/mdn/content/blob/main/files/en-us/web/javascript/reference/global_objects/intl/datetimeformat/formattoparts/index.md
- 3: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
- 4: https://github.com/mdn/content/blob/main/files/en-us/web/javascript/reference/global_objects/intl/datetimeformat/datetimeformat/index.md
- 5: https://stackoverflow.com/questions/74448762/using-variations-of-the-dayperiod-option-in-intl-datetimeformat-always-returns
locale 문자열을
Date로 재파싱하지 마세요.
toLocaleString()결과는Date생성자가 이식성 있게 파싱한다고 보장되지 않아요. 브라우저에 따라Invalid Date/NaN으로 표시될 수 있으니,formatToParts()로 KST 시계열 구성 요소를 직접 추출하세요.🤖 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/utils/formatKSTDateTime.ts` around lines 38 - 49, Update the date conversion in the KST formatting function to avoid reparsing the toLocaleString() output with Date. Use Intl.DateTimeFormat.formatToParts() with the Asia/Seoul timezone to extract year, month, day, hour, and minute directly, then preserve the existing Korean AM/PM and display formatting.
Summary
ApplicationListTabMobile)MobileFloatingButton(새 지원서 만들기) 공통 컴포넌트 재사용ApplicationFormItem타입에createdAt필드 추가 (백엔드 연동 필요 — 아래 참고)SCROLL_TRIGGER_DISABLED패턴)ApplicationMenu복제하기 기능 모바일 카드에 연결연도 상세 페이지의 최근 생성순 정렬 및 날짜 표시 기능은
createdAt필드를 필요로 합니다.현재
/api/club/application응답의forms[].forms[]각 항목에createdAt이 포함되지 않아 해당 기능은 아직 동작하지 않습니다. 백엔드에서createdAt필드를 추가하면 프론트엔드 변경 없이 즉시 동작합니다.Screenshots
Test plan