Skip to content

[feature] 지원서 복제 기능 구현#1022

Merged
lepitaaar merged 6 commits intodevelop-fefrom
feture/#1021-add-duplicate-buttton-MOA-499
Jan 12, 2026
Merged

[feature] 지원서 복제 기능 구현#1022
lepitaaar merged 6 commits intodevelop-fefrom
feture/#1021-add-duplicate-buttton-MOA-499

Conversation

@seongwon030
Copy link
Member

@seongwon030 seongwon030 commented Jan 11, 2026

#️⃣연관된 이슈

ex) #1021

📝작업 내용

2026-01-11.11.09.37.mov

1. API 및 훅 구현

  • POST /api/club/application/{id}/duplicate 엔드포인트 호출
  • useDuplicateApplication tanstack 훅 구현

2. UI 연결

  • ApplicationMenu: '제목 수정하기' 메뉴를 '지원서 복제하기'로 변경
  • ApplicationRowItem: onDuplicate prop 추가 및 메뉴에 전달
  • ApplicationListTab: 복제 핸들러 구현 및 성공/실패 피드백 추가

3. 개선사항

  • 복제 성공 시 사용자에게 알림 표시
  • 복제 실패 시 에러 메시지 표시
  • 삭제 실패 시에도 에러 핸들링 추가 (onError)

4. 유틸 함수 개선

  • formatRelativeDateTime 유틸 함수 추가
    • 날짜를 오늘/과거 기준으로 다르게 포맷팅
    • 오늘: 시간 형식 (예: 오후 2:30)
    • 과거/미래: 날짜 형식 (예: 2024.01.15)
    • ApplicationRowItem의 날짜 포맷팅 로직을 재사용 가능한 유틸로 분리
    • 단위 테스트 추가 (10개 테스트 케이스, 100% 통과)

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

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

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

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

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

🫡 참고사항

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 관리 페이지에 지원서 복제 기능이 추가되었습니다. 메뉴에서 복제 가능하며, 복제 성공 시 관련 데이터가 자동 갱신되고 성공/실패 알림이 표시됩니다.
    • 항목의 수정일 표시가 상대적 날짜/시간 형식으로 개선되었습니다.
  • Tests

    • 상대적 날짜/시간 유틸에 대한 단위 테스트가 추가되었습니다.

✏️ Tip: You can customize this high-level summary in your review settings.

- duplicateApplication API 함수 구현
- useDuplicateApplication TanStack Query 훅 추가
- '제목 수정하기' 메뉴를 '지원서 복제하기'로 변경
- onDuplicate prop 추가 및 핸들러 연결
- 불필요한 React import 제거
- onDuplicate prop 추가 및 ApplicationMenu에 전달
@seongwon030 seongwon030 self-assigned this Jan 11, 2026
@seongwon030 seongwon030 added ✨ Feature 기능 개발 💻 FE Frontend labels Jan 11, 2026
@vercel
Copy link

vercel bot commented Jan 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
moadong Ready Ready Preview, Comment Jan 11, 2026 2:08pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 11, 2026

Warning

.coderabbit.yaml has a parsing error

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

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

Walkthrough

지원서 관리 페이지에서 기존 제목 수정 버튼을 복제 기능으로 변경합니다. 새로운 API 헬퍼, React Query 훅, 그리고 UI 컴포넌트 연결을 통해 지원서 복제 기능을 구현합니다.

Changes

Cohort / File(s) 변경 요약
API 및 데이터 계층
frontend/src/apis/application/duplicateApplication.ts, frontend/src/hooks/queries/application/useDuplicateApplication.ts
새로운 POST 엔드포인트 호출 함수와 useMutation 기반 훅 추가. 성공 시 applicationForm 쿼리 무효화
UI 컴포넌트 - 소품 추가
frontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsx, frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationMenu.tsx
ApplicationRowItem과 ApplicationMenu에 onDuplicate 콜백 소품 추가 및 전달
UI 컴포넌트 - 통합 및 핸들링
frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx
useDuplicateApplication 훅 통합, 복제 핸들러 구현, Active/grouped 목록 항목에 onDuplicate 연결 및 MAX_INITIAL_ITEMS 상수화

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

  • MOA-499: 동아리 지원서 복제 버튼 추가 — 이 PR이 요청된 지원서 관리에서의 복제 기능 구현을 완료합니다.

Possibly related PRs

Suggested labels

📬 API

Suggested reviewers

  • oesnuj
  • suhyun113
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목 '[feature] 지원서 복제 기능 구현'은 변경 사항의 핵심을 명확하게 설명하고 있으며, 추가된 중복 복제 기능을 직접적으로 반영하고 있습니다.
Linked Issues check ✅ Passed PR이 MOA-499의 모든 요구사항을 충족하고 있습니다: (1) ApplicationMenu에서 '제목 수정하기' 버튼을 '지원서 복제하기'로 교체, (2) duplicateApplication API 구현 및 useDuplicateApplication 훅을 통한 API 연동.
Out of Scope Changes check ✅ Passed 모든 변경 사항이 MOA-499의 요구사항 범위 내에 있으며, 문제 해결을 위한 필요한 기능들만 포함되어 있습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @frontend/src/apis/application/duplicateApplication.ts:
- Around line 16-17: The code returns result.data without validating the parsed
JSON; update the duplicateApplication flow to validate the response and shape
before returning: after const result = await response.json() check response.ok
and that result is an object with a defined data property (e.g., result &&
typeof result === 'object' && 'data' in result); if validation fails throw a
descriptive error or return a safe default, and include the response status/text
in the error for easier debugging.

In @frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationMenu.tsx:
- Around line 33-35: The MenuItem currently renders unconditionally with
onClick={onDuplicate} which can be undefined; update the component so it either
(a) only renders the Duplicate menu item when the prop onDuplicate is provided
(conditional render using the prop), or (b) renders a visually disabled MenuItem
when onDuplicate is undefined (add a disabled state/aria-disabled, remove or
noop the onClick handler, and apply disabled styling), targeting Styled.MenuItem
and the onDuplicate prop (keep Styled.MenuIcon and Pencil as-is).
🧹 Nitpick comments (6)
frontend/src/apis/application/duplicateApplication.ts (1)

4-4: 반환 타입을 명시하세요.

함수의 반환 타입이 명시되지 않았습니다. 타입 안전성을 높이기 위해 반환 타입을 추가하는 것이 좋습니다.

♻️ 제안하는 수정

복제된 지원서의 타입을 정의하고 반환 타입에 명시하세요:

-export const duplicateApplication = async (applicationFormId: string) => {
+export const duplicateApplication = async (applicationFormId: string): Promise<any> => {

더 나은 방법으로, 실제 반환 데이터의 타입을 정의하여 사용하세요.

코딩 가이드라인: "Use consistent return types for similar functions/hooks"에 따라 명확한 반환 타입을 지정하는 것이 좋습니다.

frontend/src/hooks/queries/application/useDuplicateApplication.ts (1)

4-19: 훅의 반환 타입을 명시하세요.

useDuplicateApplication 훅의 반환 타입이 명시되지 않았습니다. 타입 안전성과 IDE 자동완성을 개선하기 위해 반환 타입을 추가하는 것이 좋습니다.

♻️ 제안하는 수정
+import { UseMutationResult } from '@tanstack/react-query';
+
-export const useDuplicateApplication = () => {
+export const useDuplicateApplication = (): UseMutationResult<any, Error, string> => {

코딩 가이드라인: "Use consistent return types for similar functions/hooks"에 따라 다른 훅들과 일관되게 반환 타입을 지정하는 것이 좋습니다.

frontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsx (1)

17-17: ApplicationMenu와의 타입 일관성을 확인하세요.

ApplicationRowItem에서 onDuplicate는 필수 prop으로 정의되어 있지만, ApplicationMenu에서는 onDuplicate?: () => void로 선택적 prop으로 정의되어 있습니다.

타입 일관성을 위해 두 컴포넌트에서 동일한 선택성(required vs optional)을 사용하는 것이 좋습니다. 현재 구조에서는 ApplicationRowItem이 항상 onDuplicate를 제공하므로 ApplicationMenu에서도 필수로 만들거나, 또는 ApplicationRowItem에서 선택적으로 만들어 유연성을 높일 수 있습니다.

♻️ 제안하는 수정 (선택적으로 만드는 경우)
-  onDuplicate: (id: string) => void;
+  onDuplicate?: (id: string) => void;

그리고 line 80에서:

-              onDuplicate={() => onDuplicate(application.id)}
+              onDuplicate={onDuplicate ? () => onDuplicate(application.id) : undefined}
frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationMenu.tsx (1)

34-34: 복제 작업에 적합한 아이콘을 사용하세요.

현재 연필(Pencil) 아이콘을 사용하고 있는데, 이는 일반적으로 "편집" 작업을 나타냅니다. "복제" 작업에는 복사(copy) 또는 복제(duplicate) 아이콘을 사용하는 것이 사용자 경험 측면에서 더 직관적입니다.

💡 제안

복제를 나타내는 아이콘(예: Copy 아이콘)을 import하여 사용하세요:

+import Copy from '@/assets/images/icons/copy_icon.svg';
 import Pencil from '@/assets/images/icons/pencil_icon_3.svg';

-        <Styled.MenuIcon src={Pencil} /> 지원서 복제하기
+        <Styled.MenuIcon src={Copy} /> 지원서 복제하기

(적절한 복제 아이콘이 없다면 디자인 팀에 요청하거나, 기존 아이콘을 그대로 사용하되 추후 개선하는 것을 고려하세요.)

frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx (2)

53-63: 사용자 피드백을 토스트 알림으로 개선하세요.

alert()를 사용한 사용자 피드백은 페이지를 차단하며 현대적인 UX 패턴이 아닙니다. 토스트(toast) 알림 시스템을 사용하는 것이 더 나은 사용자 경험을 제공합니다.

💡 제안하는 개선

토스트 라이브러리(예: react-toastify, sonner 등)를 사용하거나, 프로젝트에 이미 토스트 컴포넌트가 있다면 그것을 활용하세요:

+import { toast } from '@/components/common/Toast'; // 또는 사용 중인 토스트 라이브러리

 const handleDuplicateApplication = (applicationFormId: string) => {
   duplicateApplication(applicationFormId, {
     onSuccess: () => {
       setOpenMenuId(null);
-      alert('지원서가 성공적으로 복제되었습니다.');
+      toast.success('지원서가 성공적으로 복제되었습니다.');
     },
     onError: () => {
-      alert('지원서 복제에 실패했습니다.');
+      toast.error('지원서 복제에 실패했습니다.');
     },
   });
 };

참고: 동일한 개선을 handleDeleteApplicationonError (line 47)에도 적용할 수 있습니다.


53-63: 로딩 상태 처리를 고려하세요.

복제 작업 중 사용자에게 진행 상태를 알려주는 로딩 인디케이터가 없습니다. 네트워크가 느린 경우 사용자가 작업이 진행 중인지 알 수 없습니다.

💡 제안하는 개선

useDuplicateApplication 훅에서 제공하는 isPending 상태를 활용하여 로딩 상태를 표시할 수 있습니다:

-  const { mutate: duplicateApplication } = useDuplicateApplication();
+  const { mutate: duplicateApplication, isPending: isDuplicating } = useDuplicateApplication();

그리고 UI에 로딩 상태를 반영:

  • 메뉴 아이템을 비활성화
  • 로딩 스피너 표시
  • 또는 전역 로딩 인디케이터 사용
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4933eb8 and 0d6a9cb.

📒 Files selected for processing (5)
  • frontend/src/apis/application/duplicateApplication.ts
  • frontend/src/hooks/queries/application/useDuplicateApplication.ts
  • frontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationMenu.tsx
🧰 Additional context used
📓 Path-based instructions (3)
frontend/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (frontend/.cursorrules)

frontend/**/*.{ts,tsx,js,jsx}: Replace magic numbers with named constants for clarity
Replace complex/nested ternaries with if/else or IIFEs for readability
Assign complex boolean conditions to named variables for explicit meaning
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle)
Use unique and descriptive names for custom wrappers/functions to avoid ambiguity
Define constants near related logic or ensure names link them clearly to avoid silent failures
Break down broad state management into smaller, focused hooks/contexts to reduce coupling

Files:

  • frontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsx
  • frontend/src/apis/application/duplicateApplication.ts
  • frontend/src/hooks/queries/application/useDuplicateApplication.ts
  • frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationMenu.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx
frontend/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (frontend/.cursorrules)

frontend/**/*.{tsx,jsx}: Abstract complex logic/interactions into dedicated components/HOCs
Separate significantly different conditional UI/logic into distinct components
Colocate simple, localized logic or use inline definitions to reduce context switching
Choose field-level or form-level cohesion based on form requirements when using form libraries like react-hook-form
Use Component Composition instead of Props Drilling to reduce coupling

Files:

  • frontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationMenu.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (frontend/.cursorrules)

Use consistent return types for similar functions/hooks

Files:

  • frontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsx
  • frontend/src/apis/application/duplicateApplication.ts
  • frontend/src/hooks/queries/application/useDuplicateApplication.ts
  • frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationMenu.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx
🧠 Learnings (1)
📚 Learning: 2025-05-19T05:45:52.957Z
Learnt from: lepitaaar
Repo: Moadong/moadong PR: 406
File: backend/src/main/java/moadong/club/service/ClubApplyService.java:34-38
Timestamp: 2025-05-19T05:45:52.957Z
Learning: The code duplication between createClubApplication and editClubApplication methods in ClubApplyService.java is acknowledged but will be addressed in a future refactoring, as per the developer's plan.

Applied to files:

  • frontend/src/apis/application/duplicateApplication.ts
🧬 Code graph analysis (2)
frontend/src/apis/application/duplicateApplication.ts (1)
frontend/src/apis/auth/secureFetch.ts (1)
  • secureFetch (3-41)
frontend/src/hooks/queries/application/useDuplicateApplication.ts (1)
frontend/src/apis/application/duplicateApplication.ts (1)
  • duplicateApplication (4-22)
🔇 Additional comments (5)
frontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsx (1)

80-80: 코드 변경사항이 올바르게 구현되었습니다.

onDuplicate prop이 ApplicationMenu에 올바르게 전달되고 있으며, 클로저를 통해 application.id가 정확히 바인딩되어 있습니다.

frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationListTab.tsx (3)

17-17: 상수 추출이 잘 되었습니다.

매직 넘버를 모듈 레벨의 명명된 상수로 추출하여 가독성과 유지보수성이 향상되었습니다.

코딩 가이드라인: "Replace magic numbers with named constants for clarity"를 잘 따르고 있습니다.


46-48: 삭제 실패 시 에러 핸들링이 추가되었습니다.

onError 콜백을 추가하여 삭제 실패 시 사용자에게 피드백을 제공하도록 개선되었습니다. 좋은 개선사항입니다.

참고: 앞서 언급한 것처럼 alert() 대신 토스트 알림을 사용하면 더 나은 UX를 제공할 수 있습니다.


166-166: 복제 핸들러가 올바르게 연결되었습니다.

handleDuplicateApplication이 활성 목록과 그룹 목록의 모든 ApplicationRowItem에 올바르게 전달되고 있습니다. 다른 핸들러들과 일관된 패턴을 따르고 있습니다.

Also applies to: 223-223

frontend/src/hooks/queries/application/useDuplicateApplication.ts (1)

11-13: 쿼리 키 구조의 일관성을 조정하세요.

useDuplicateApplication에서 ['applicationForm']으로 무효화할 때, 실제로는 다음과 같은 쿼리 키들이 사용 중입니다:

  • useGetApplicationlist: ['applicationForm']
  • useGetApplication: ['applicationForm', clubId, applicationFormId]
  • useDeleteApplication: 역시 ['applicationForm']로 무효화

React Query의 prefix 매칭 방식으로 인해 현재 코드는 함수적으로 작동하지만, 쿼리 키 구조가 일관성이 없습니다. 목록 조회와 상세 조회의 쿼리 키 구조를 명확하게 정의하거나, 모든 관련 쿼리 키를 문서화하여 의도를 명확히 하세요.

- TOGGLE_BUTTON_TEXT 상수 객체로 추출
- 가독성 및 유지보수성 향상
- 재사용 가능한 유틸 함수로 추출
- 단위 테스트 추가
- ApplicationRowItem 코드 간소화
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsx (1)

7-18: ApplicantsListTab.tsx에서 onDuplicate prop이 누락되었습니다.

frontend/src/pages/AdminPage/tabs/ApplicantsTab/ApplicantsListTab/ApplicantsListTab.tsx 라인 198-209의 <ApplicationRowItem> 컴포넌트 호출부에 onDuplicate prop을 전달해야 합니다. 현재 onDelete까지만 전달되고 있어 필수 prop 부재로 인한 타입 에러가 발생합니다.

현재 상태 (ApplicantsListTab.tsx)
<ApplicationRowItem
  key={application.id}
  application={application}
  isActive={application.status === 'ACTIVE'}
  uniqueKeyPrefix={groupUniqueKeyPrefix}
  openMenuId={openMenuId}
  menuRef={menuRef}
  onEdit={handleGoToDetailForm}
  onMenuToggle={handleMenuToggle}
  onToggleStatus={handleToggleClick}
  onDelete={handleDeleteApplication}
  // onDuplicate prop 누락 ← 필수 추가
/>

또한 useDuplicateApplication 훅을 import 및 호출하여 duplicate 기능을 구현해야 합니다 (ApplicationListTab.tsx와 유사하게).

🤖 Fix all issues with AI agents
In @frontend/src/utils/formatRelativeDateTime.ts:
- Around line 6-18: The function formatRelativeDateTime should guard against
empty or invalid dateTimeString inputs: before using date.toLocaleString, check
if dateTimeString is falsy or if new Date(dateTimeString) yields an invalid Date
(isNaN(date.getTime())), and in that case return a safe fallback (e.g. empty
string or '-' ) instead of letting "Invalid Date" reach the UI; keep the
existing logic for computing isToday and options unchanged and only add the
validation early in formatRelativeDateTime around the date/dateTimeString
handling.
🧹 Nitpick comments (2)
frontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsx (1)

46-49: application.editedAt 비어있음/비정상 값일 때 표시가 깨질 수 있어 fallback 고려

✅ 제안 수정(diff)
         <Styled.ApplicationDate>
-          {formatRelativeDateTime(application.editedAt)}
+          {application.editedAt
+            ? formatRelativeDateTime(application.editedAt)
+            : '-'}
         </Styled.ApplicationDate>
frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationMenu.tsx (1)

12-17: onDuplicate가 optional인데 메뉴는 항상 노출되어 “눌러도 아무 반응 없음” 상태가 될 수 있습니다.
둘 중 하나를 추천합니다: (1) onDuplicate를 required로 변경, 또는 (2) 핸들러 없으면 메뉴 아이템을 숨김.

✅ 옵션(2): 조건부 렌더링(diff)
 interface ApplicationMenuProps {
   isActive: boolean;
   onDelete: () => void;
   onToggleStatus?: () => void;
   onDuplicate?: () => void;
 }
@@
       <Styled.Separator />
-      <Styled.MenuItem onClick={onDuplicate}>
-        <Styled.MenuIcon src={Pencil} /> 지원서 복제하기
-      </Styled.MenuItem>
+      {onDuplicate && (
+        <Styled.MenuItem onClick={onDuplicate}>
+          <Styled.MenuIcon src={Pencil} /> 지원서 복제하기
+        </Styled.MenuItem>
+      )}
       <Styled.MenuItem onClick={onDelete} className='delete'>

Also applies to: 40-42

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0d6a9cb and 0b84845.

📒 Files selected for processing (4)
  • frontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsx
  • frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationMenu.tsx
  • frontend/src/utils/formatRelativeDateTime.test.ts
  • frontend/src/utils/formatRelativeDateTime.ts
🧰 Additional context used
📓 Path-based instructions (3)
frontend/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (frontend/.cursorrules)

frontend/**/*.{ts,tsx,js,jsx}: Replace magic numbers with named constants for clarity
Replace complex/nested ternaries with if/else or IIFEs for readability
Assign complex boolean conditions to named variables for explicit meaning
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle)
Use unique and descriptive names for custom wrappers/functions to avoid ambiguity
Define constants near related logic or ensure names link them clearly to avoid silent failures
Break down broad state management into smaller, focused hooks/contexts to reduce coupling

Files:

  • frontend/src/utils/formatRelativeDateTime.ts
  • frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationMenu.tsx
  • frontend/src/utils/formatRelativeDateTime.test.ts
  • frontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsx
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (frontend/.cursorrules)

Use consistent return types for similar functions/hooks

Files:

  • frontend/src/utils/formatRelativeDateTime.ts
  • frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationMenu.tsx
  • frontend/src/utils/formatRelativeDateTime.test.ts
  • frontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsx
frontend/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (frontend/.cursorrules)

frontend/**/*.{tsx,jsx}: Abstract complex logic/interactions into dedicated components/HOCs
Separate significantly different conditional UI/logic into distinct components
Colocate simple, localized logic or use inline definitions to reduce context switching
Choose field-level or form-level cohesion based on form requirements when using form libraries like react-hook-form
Use Component Composition instead of Props Drilling to reduce coupling

Files:

  • frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationMenu.tsx
  • frontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsx
🧬 Code graph analysis (2)
frontend/src/utils/formatRelativeDateTime.test.ts (1)
frontend/src/utils/formatRelativeDateTime.ts (1)
  • formatRelativeDateTime (6-19)
frontend/src/pages/AdminPage/components/ApplicationRow/ApplicationRowItem.tsx (1)
frontend/src/utils/formatRelativeDateTime.ts (1)
  • formatRelativeDateTime (6-19)
🔇 Additional comments (4)
frontend/src/utils/formatRelativeDateTime.ts (1)

9-18: “오늘” 판단 기준(timezone)이 서비스 기대와 일치하는지 확인 필요
서버가 UTC(Z)로 내려주거나 사용자가 다른 timezone이면 “오늘/과거” 분기가 의도와 다를 수 있어요(특히 자정 근처).

frontend/src/utils/formatRelativeDateTime.test.ts (2)

3-99: 케이스 커버리지 구성이 깔끔합니다(오늘/과거/미래/경계).


4-10: jest.setSystemTime가 동작하는 “modern fake timers” 설정인지 확인 필요
프로젝트 설정이 legacy timers면 해당 API가 기대대로 동작하지 않을 수 있습니다.

Also applies to: 14-18

frontend/src/pages/AdminPage/tabs/ApplicationListTab/ApplicationMenu.tsx (1)

7-10: 토글 텍스트 상수화는 유지보수에 도움 됩니다.

Also applies to: 25-28

Comment on lines +6 to +18
export const formatRelativeDateTime = (dateTimeString: string): string => {
const now = new Date();
const date = new Date(dateTimeString);
const isToday =
now.getFullYear() === date.getFullYear() &&
now.getMonth() === date.getMonth() &&
now.getDate() === date.getDate();

const options: Intl.DateTimeFormatOptions = isToday
? { hour: 'numeric', minute: '2-digit', hour12: true }
: { year: 'numeric', month: '2-digit', day: '2-digit' };

return date.toLocaleString('ko-KR', options);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Invalid Date 입력/파싱 실패 케이스를 UI로 노출할 수 있어 가드가 있으면 좋겠습니다.
현재 dateTimeString가 빈 값/비정상 값이면 toLocaleString 결과가 그대로 노출될 수 있습니다(UX 이슈).

✅ 제안 수정(diff)
 export const formatRelativeDateTime = (dateTimeString: string): string => {
   const now = new Date();
   const date = new Date(dateTimeString);
+  if (Number.isNaN(date.getTime())) return '-';
+
   const isToday =
     now.getFullYear() === date.getFullYear() &&
     now.getMonth() === date.getMonth() &&
     now.getDate() === date.getDate();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const formatRelativeDateTime = (dateTimeString: string): string => {
const now = new Date();
const date = new Date(dateTimeString);
const isToday =
now.getFullYear() === date.getFullYear() &&
now.getMonth() === date.getMonth() &&
now.getDate() === date.getDate();
const options: Intl.DateTimeFormatOptions = isToday
? { hour: 'numeric', minute: '2-digit', hour12: true }
: { year: 'numeric', month: '2-digit', day: '2-digit' };
return date.toLocaleString('ko-KR', options);
export const formatRelativeDateTime = (dateTimeString: string): string => {
const now = new Date();
const date = new Date(dateTimeString);
if (Number.isNaN(date.getTime())) return '-';
const isToday =
now.getFullYear() === date.getFullYear() &&
now.getMonth() === date.getMonth() &&
now.getDate() === date.getDate();
const options: Intl.DateTimeFormatOptions = isToday
? { hour: 'numeric', minute: '2-digit', hour12: true }
: { year: 'numeric', month: '2-digit', day: '2-digit' };
return date.toLocaleString('ko-KR', options);
};
🤖 Prompt for AI Agents
In @frontend/src/utils/formatRelativeDateTime.ts around lines 6 - 18, The
function formatRelativeDateTime should guard against empty or invalid
dateTimeString inputs: before using date.toLocaleString, check if dateTimeString
is falsy or if new Date(dateTimeString) yields an invalid Date
(isNaN(date.getTime())), and in that case return a safe fallback (e.g. empty
string or '-' ) instead of letting "Invalid Date" reach the UI; keep the
existing logic for computing isToday and options unchanged and only add the
validation early in formatRelativeDateTime around the date/dateTimeString
handling.

Copy link
Contributor

@lepitaaar lepitaaar left a comment

Choose a reason for hiding this comment

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

복제하기 추가 뿐아니라 전체적인 코드 리팩토링 잘봤습니다

Copy link
Contributor

Choose a reason for hiding this comment

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

유틸함수로 분리하신거 좋습니다

<Styled.MenuItem onClick={onEditTitle}>
<Styled.MenuIcon src={Pencil} /> 제목 수정하기
<Styled.MenuItem onClick={onDuplicate}>
<Styled.MenuIcon src={Pencil} /> 지원서 복제하기
Copy link
Contributor

Choose a reason for hiding this comment

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

임시로 변경한거라 아이콘이 Pencil인데 이것도 추가로 변경하면좋을꺼같습니다

Comment on lines +7 to +11
const TOGGLE_BUTTON_TEXT = {
ACTIVE: '지원서 비활성화',
INACTIVE: '지원서 활성화',
} as const;

Copy link
Contributor

Choose a reason for hiding this comment

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

상수화 하신거 좋네요

@@ -39,13 +42,26 @@ const ApplicationListTab = () => {
deleteApplication(applicationFormId, {
onSuccess: () => {
setOpenMenuId(null);
Copy link
Contributor

Choose a reason for hiding this comment

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

혹시 성공 alert를 삭제하신 이유가뭘까요?

@lepitaaar lepitaaar merged commit 5c1a0e9 into develop-fe Jan 12, 2026
3 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jan 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FE Frontend ✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feture] MOA-499 동아리 지원서 복제 기능을 추가한다

2 participants

Comments