Skip to content

[refactor] 모집 기간 시간대(Timezone) 처리 개선 및 API 응답 형식 변경 대응#945

Merged
seongwon030 merged 13 commits intodevelop-fefrom
refactor/#944-recruitment-date-utc-kst-handling-MOA-433
Dec 21, 2025
Merged

[refactor] 모집 기간 시간대(Timezone) 처리 개선 및 API 응답 형식 변경 대응#945
seongwon030 merged 13 commits intodevelop-fefrom
refactor/#944-recruitment-date-utc-kst-handling-MOA-433

Conversation

@seongwon030
Copy link
Member

@seongwon030 seongwon030 commented Dec 21, 2025

#️⃣연관된 이슈

ex) #944

📝작업 내용

문제

사용자가 22시 설정했을 때 → 13시로 저장되었습니다.

이는 프론트-서버 간 시간대 처리가 일치하지 않았기 때문입니다.

1. API 응답 형식 변경 대응

  • 기존: recruitmentPeriod: "2025.12.07 10:00 ~ 2025.12.07 22:00" (단일 문자열)
  • 변경: recruitmentStart, recruitmentEnd (분리된 필드)

2. 시간대 처리 개선

  • 요청 시: toISOString()으로 UTC 형식(Z suffix) 전송
  • 응답 시: +0900 타임존으로 KST 파싱

3. 파일 리팩토링

  • recruitmentPeriodParser.tsrecruitmentDateParser.ts 파일명 변경
  • parseRecruitmentPeriod 함수 제거 (더 이상 불필요)
  • correctRequestKoreanDate, correctResponseKoreanDate 함수 제거

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

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

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

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

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

🫡 참고사항

Summary by CodeRabbit

  • 리팩토링

    • 모집 기간 표현을 단일 문자열에서 시작/종료 일시로 분리해 데이터 흐름과 UI 표시를 정리했습니다.
    • 클럽 상세/관리 화면의 모집 관련 prop·렌더링 로직과 모바일 헤더 조건을 단순화해 유지보수성을 향상했습니다.
    • 불필요한 임포트·주석·공백 정리를 통해 파일을 경량화했습니다.
  • 신규 기능

    • 엄격한 형식 검증과 시간대 처리를 적용한 새로운 모집 일시 파서를 도입했습니다.
  • 테스트

    • 신규 파서에 대한 단위 테스트를 추가하고 이전 관련 테스트를 정리했습니다.

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

- API 응답 형식이 recruitmentStart/End로 분리됨에 따라
  더 이상 "시작 ~ 종료" 문자열 파싱이 필요 없음
- parseRecruitmentDateString만 사용하도록 변경
…T 처리 추가

- parseRecruitmentPeriod 함수 제거로 인해 파일명 변경
- 서버에서 KST 시간을 보내므로 파싱 시 +0900 타임존 적용
- 관련 import 경로 수정
- API 응답 형식 변경에 따른 타입 수정
- 기존: recruitmentPeriod: string ("시작 ~ 종료" 단일 문자열)
- 변경: recruitmentStart, recruitmentEnd: string (분리된 필드)
- recruitmentPeriod 파싱 대신 recruitmentStart/End 직접 사용
- correctRequestKoreanDate, correctResponseKoreanDate 함수 제거
  - 시간 보정 로직을 recruitmentDateParser로 이동
- toISOString()으로 UTC 형식(Z suffix)으로 서버에 전송
- ClubDetailPage: props 전달 방식 변경
- ClubDetailHeader: interface 수정 (recruitmentPeriod → recruitmentStart/End)
- ClubDetailFooter: parseRecruitmentPeriod 대신 recruitmentDateParser 사용
- InfoBox: recruitmentStart/End를 직접 조합하여 표시
@vercel
Copy link

vercel bot commented Dec 21, 2025

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

Project Deployment Review Updated (UTC)
moadong Ready Ready Preview, Comment Dec 21, 2025 1:06pm

@seongwon030 seongwon030 added 🔨 Refactor 코드 리팩토링 💻 FE Frontend labels Dec 21, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 21, 2025

Warning

.coderabbit.yaml has a parsing error

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

💥 Parsing errors (1)
Validation error: Invalid regex pattern for base branch. Received: "**" at "reviews.auto_review.base_branches[0]"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • 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

모집 기간 표현을 단일 recruitmentPeriod에서 문자열 기반의 recruitmentStart/recruitmentEnd로 분리하고, KST 형식 검증·파싱을 수행하는 신규 유틸 recruitmentDateParser를 도입했습니다. 관련 타입·컴포넌트·관리 UI가 이를 따르도록 업데이트하고 기존 recruitmentPeriodParser와 관련 테스트를 제거했습니다.

Changes

Cohort / File(s) 변경 요약
관리자 페이지 탭
frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx, frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx
ClubInfoEditTab에서 기본 React import 제거 및 공백 정리. PhotoEditTab에서 인라인 주석 제거(실행 로직 미변경).
모집 편집 로직 (관리자)
frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx
기존 parseRecruitmentPeriod 사용을 recruitmentDateParser로 교체; FAR_FUTURE_YEAR, isFarFuture, backupRangeRef 추가; useQueryClient 도입 및 업데이트 성공 시 캐시 무효화 추가; 한글 날짜 보정 헬퍼 제거; 업데이트 페이로드에 ISO 직렬화 사용.
클럽 상세 페이지 진입 및 전달
frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
Header/Footer에 전달하던 recruitmentPeriod/recruitmentForm 호출을 recruitmentStartrecruitmentEnd로 분리하여 전달하도록 변경; 모바일 헤더 감지 로직을 useDevice로 단순화.
클럽 상세 구성요소 변경
frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx, frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx, frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx
Header/Footer/InfoBox 인터페이스에서 recruitmentPeriod 제거. Footer·InfoBox는 recruitmentStart/recruitmentEnd를 직접 파싱해 데드라인·표시 로직으로 사용하도록 변경(ClubApplyButton 전달값 조정).
타입 정의 변경
frontend/src/types/club.ts
ClubDetail에서 recruitmentPeriod: string 제거, recruitmentStart: stringrecruitmentEnd: string 추가.
신규 파서 및 테스트 추가
frontend/src/utils/recruitmentDateParser.ts, frontend/src/utils/recruitmentDateParser.test.ts
recruitmentDateParser 추가: YYYY.MM.DD HH:mm 형식 검증(정규식) 및 KST 기반 파싱 → UTC Date 반환; 정상/오류 케이스 단위테스트 추가.
레거시 파서 및 테스트 제거
frontend/src/utils/recruitmentPeriodParser.ts (삭제), frontend/src/utils/parseRecruitmentPeriod.test.ts (삭제)
기존 parseRecruitmentPeriod 유틸과 관련 테스트 파일 삭제.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Admin as 관리자(UI)
participant RecruitTab as RecruitEditTab
participant Parser as recruitmentDateParser
participant API as 서버(API)
participant Query as ReactQuery (queryClient)
participant ClubPage as ClubDetailPage
participant Footer as ClubDetailFooter

Admin->>RecruitTab: 모집 시작/종료 입력 및 저장 트리거
RecruitTab->>Parser: 입력값 검증·KST→UTC 파싱 (recruitmentDateParser)
Parser-->>RecruitTab: Date(UTC)
RecruitTab->>API: 수정 요청 (ISO 문자열로 전송)
API-->>RecruitTab: 200 OK
RecruitTab->>Query: 관련 쿼리 무효화(invalidateQueries)
Query-->>ClubPage: 최신 데이터로 리패치
ClubPage->>Footer: recruitmentStart/recruitmentEnd 전달
Footer->>Parser: (선택적) 날짜 파싱/데드라인 계산
Footer-->>ClubPage: 데드라인/버튼 렌더링

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45분

  • 추가로 주의할 파일/영역:
    • frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx — 초기 파싱 로직, 상시모집(FAR_FUTURE) 동작, backupRangeRef 보존/복원, 쿼리 무효화 흐름
    • frontend/src/utils/recruitmentDateParser.tsrecruitmentDateParser.test.ts — 정규식 에지케이스와 KST→UTC 변환(테스트 기대값 일치)
    • 타입 전파 영향: frontend/src/types/club.ts 변경으로 인한 컴포넌트(ClubDetailPage, Header/Footer, InfoBox 등) 호환성

Possibly related issues

Possibly related PRs

Suggested labels

📬 API

Suggested reviewers

  • Zepelown
  • lepitaaar
  • oesnuj
  • suhyun113

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 PR의 주요 변경사항을 정확히 반영합니다: 모집 기간 시간대 처리 개선과 API 응답 형식 변경이 모두 포함되어 있습니다.
Linked Issues check ✅ Passed recruitmentPeriod를 recruitmentStart/recruitmentEnd로 분리했고, UTC 형식 전송 및 KST 파싱 로직을 구현하여 모든 요구사항을 충족합니다.
Out of Scope Changes check ✅ Passed React import 제거, 주석 삭제, 날짜 파싱 유틸리티 교체 등 모든 변경사항이 시간대 처리 개선 목표와 관련되어 있습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/#944-recruitment-date-utc-kst-handling-MOA-433

📜 Recent 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 f561d9e and 15a2d57.

📒 Files selected for processing (2)
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (3 hunks)
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx (1 hunks)
🧰 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/ClubDetailPage/ClubDetailPage.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.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/ClubDetailPage/ClubDetailPage.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (frontend/.cursorrules)

Use consistent return types for similar functions/hooks

Files:

  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
🧠 Learnings (2)
📚 Learning: 2025-03-19T05:18:07.818Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.

Applied to files:

  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
📚 Learning: 2025-07-19T05:05:10.196Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 548
File: frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx:17-57
Timestamp: 2025-07-19T05:05:10.196Z
Learning: ClubDetailPage.tsx에서 notJoinedClubNames 배열의 하드코딩은 의도적인 설계 결정입니다. 개발자가 명시적으로 하드코딩을 선택했으므로 이에 대한 리팩토링 제안을 하지 않아야 합니다.

Applied to files:

  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
🧬 Code graph analysis (1)
frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx (1)
frontend/src/utils/recruitmentDateParser.ts (1)
  • recruitmentDateParser (3-17)
🔇 Additional comments (5)
frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx (3)

3-4: LGTM! import 변경이 적절합니다.

기존 recruitmentPeriodParser 대신 새로운 recruitmentDateParser 유틸리티를 사용하도록 import가 올바르게 업데이트되었습니다.


6-9: LGTM! 인터페이스가 API 응답 형식 변경에 맞게 업데이트되었습니다.

recruitmentPeriod 단일 문자열 대신 recruitmentStartrecruitmentEnd로 분리된 필드를 사용하도록 변경되었습니다. 이전 리뷰에서 지적된 사용하지 않는 recruitmentForm prop도 제거되었습니다.


21-21: LGTM! 마감일 텍스트 계산 로직이 적절합니다.

파싱된 startDateendDategetDeadlineText에 전달하여 마감일 텍스트를 올바르게 계산합니다.

frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (2)

17-17: LGTM! 헤더 표시 로직이 간소화되었습니다.

이전 리뷰에서 제안된 대로 useDevice 훅을 사용하여 isMobile 상태를 직접 계산하고, 별도의 useStateuseEffect 없이 !isMobile로 헤더 표시 여부를 결정합니다. 코드가 더 간결하고 유지보수하기 쉬워졌습니다.

Also applies to: 22-22, 38-38


62-65: LGTM! API 응답 형식 변경에 맞게 props가 업데이트되었습니다.

ClubDetailFooterrecruitmentStartrecruitmentEnd를 전달하여 새로운 API 응답 형식과 일관성을 유지합니다.


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

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/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx (1)

12-28: recruitmentDateParser 호출 시 에러 처리 필수

recruitmentDateParser는 유효하지 않은 날짜 형식에서 예외를 throw하며, ClubDetailFooter에서 렌더 단계 중에 이를 try-catch 없이 호출합니다. 현재 null 체크만 있을 뿐 형식 검증이 없으므로, API가 잘못된 형식을 반환하면 컴포넌트가 충돌합니다. 전체 앱에 에러 바운더리가 없으므로 전체 React 트리가 언마운트될 수 있습니다.

다음 중 하나를 선택하여 구현하세요:

  • recruitmentDateParser 호출을 try-catch로 감싸고 유효하지 않은 경우 null 또는 기본값 반환
  • 데이터 검증 유틸리티를 추가하여 parser 호출 전 형식 확인
  • 앱 최상위에 Error Boundary 추가 (라우트 레벨)
🧹 Nitpick comments (2)
frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx (1)

17-17: 상수에 대한 설명 주석 추가 권장

FAR_FUTURE_YEAR = 2999가 '상시모집' 상태를 나타내기 위한 특수 값임을 명시하는 주석을 추가하면 코드 이해도가 높아집니다. 코딩 가이드라인에 따라 상수와 관련 로직의 연결을 명확히 해주세요.

🔎 주석 추가 제안
+/** 상시모집 상태를 나타내기 위한 먼 미래 연도 값 */
 const FAR_FUTURE_YEAR = 2999;
frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx (1)

10-14: 불필요한 props 정의 제거 필요

recruitmentStart, recruitmentEnd, recruitmentForm, presidentPhoneNumber props가 인터페이스에 정의되어 있지만 컴포넌트 내에서 destructuring되거나 사용되지 않으며, 자식 컴포넌트(ClubProfile)에 전달되지도 않습니다. Props Drilling 패턴을 방지하기 위해 사용되지 않는 props는 인터페이스에서 제거하는 것이 좋습니다.

📜 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 4778611 and 45d96f4.

📒 Files selected for processing (12)
  • frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx (1 hunks)
  • frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.tsx (0 hunks)
  • frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx (7 hunks)
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (3 hunks)
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx (1 hunks)
  • frontend/src/types/club.ts (1 hunks)
  • frontend/src/utils/parseRecruitmentPeriod.test.ts (0 hunks)
  • frontend/src/utils/recruitmentDateParser.test.ts (1 hunks)
  • frontend/src/utils/recruitmentDateParser.ts (1 hunks)
  • frontend/src/utils/recruitmentPeriodParser.ts (0 hunks)
💤 Files with no reviewable changes (3)
  • frontend/src/utils/parseRecruitmentPeriod.test.ts
  • frontend/src/utils/recruitmentPeriodParser.ts
  • frontend/src/pages/AdminPage/tabs/PhotoEditTab/PhotoEditTab.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/ClubDetailPage/components/InfoBox/InfoBox.tsx
  • frontend/src/utils/recruitmentDateParser.ts
  • frontend/src/utils/recruitmentDateParser.test.ts
  • frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
  • frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx
  • frontend/src/types/club.ts
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.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/ClubDetailPage/components/InfoBox/InfoBox.tsx
  • frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
  • frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (frontend/.cursorrules)

Use consistent return types for similar functions/hooks

Files:

  • frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx
  • frontend/src/utils/recruitmentDateParser.ts
  • frontend/src/utils/recruitmentDateParser.test.ts
  • frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
  • frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx
  • frontend/src/types/club.ts
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
🧠 Learnings (7)
📚 Learning: 2025-03-19T05:18:07.818Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 195
File: frontend/src/pages/AdminPage/AdminPage.tsx:7-7
Timestamp: 2025-03-19T05:18:07.818Z
Learning: AdminPage.tsx에서 현재 하드코딩된 클럽 ID('67d2e3b9b15c136c6acbf20b')는 로그인 기능 구현 후 동적으로 가져오는 방식으로 수정될 예정입니다.

Applied to files:

  • frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx
  • frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
📚 Learning: 2025-11-25T14:08:23.253Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-11-25T14:08:23.253Z
Learning: Applies to frontend/**/*.{tsx,jsx} : Colocate simple, localized logic or use inline definitions to reduce context switching

Applied to files:

  • frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx
📚 Learning: 2025-11-25T14:08:23.253Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-11-25T14:08:23.253Z
Learning: Applies to frontend/**/*.{ts,tsx,js,jsx} : Break down broad state management into smaller, focused hooks/contexts to reduce coupling

Applied to files:

  • frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx
📚 Learning: 2025-11-25T14:08:23.253Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-11-25T14:08:23.253Z
Learning: Applies to frontend/**/*.{tsx,jsx} : Abstract complex logic/interactions into dedicated components/HOCs

Applied to files:

  • frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx
📚 Learning: 2025-11-25T14:08:23.253Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-11-25T14:08:23.253Z
Learning: Applies to frontend/**/*.{tsx,jsx} : Use Component Composition instead of Props Drilling to reduce coupling

Applied to files:

  • frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx
📚 Learning: 2025-11-25T14:08:23.253Z
Learnt from: CR
Repo: Moadong/moadong PR: 0
File: frontend/.cursorrules:0-0
Timestamp: 2025-11-25T14:08:23.253Z
Learning: Applies to frontend/**/*.{tsx,jsx} : Choose field-level or form-level cohesion based on form requirements when using form libraries like react-hook-form

Applied to files:

  • frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx
📚 Learning: 2025-07-19T05:05:10.196Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 548
File: frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx:17-57
Timestamp: 2025-07-19T05:05:10.196Z
Learning: ClubDetailPage.tsx에서 notJoinedClubNames 배열의 하드코딩은 의도적인 설계 결정입니다. 개발자가 명시적으로 하드코딩을 선택했으므로 이에 대한 리팩토링 제안을 하지 않아야 합니다.

Applied to files:

  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx
🧬 Code graph analysis (3)
frontend/src/utils/recruitmentDateParser.test.ts (1)
frontend/src/utils/recruitmentDateParser.ts (1)
  • recruitmentDateParser (3-17)
frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx (4)
frontend/src/types/club.ts (1)
  • ClubDetail (16-29)
frontend/src/apis/updateClubDescription.ts (1)
  • updateClubDescription (5-37)
frontend/src/hooks/queries/club/useUpdateClubDescription.ts (1)
  • useUpdateClubDescription (5-14)
frontend/src/utils/recruitmentDateParser.ts (1)
  • recruitmentDateParser (3-17)
frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx (1)
frontend/src/utils/recruitmentDateParser.ts (1)
  • recruitmentDateParser (3-17)
🔇 Additional comments (9)
frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTab.tsx (1)

1-1: LGTM!

React 19의 새로운 JSX 런타임에 맞게 기본 React import를 제거하고 named import만 사용하도록 변경되었습니다.

frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx (1)

27-32: recruitmentStart 또는 recruitmentEnd가 빈 값일 때 예외 처리 검토 필요

recruitmentStatus가 '상시모집'이 아닐 때, recruitmentStartrecruitmentEnd가 빈 문자열이거나 undefined인 경우 "undefined ~ undefined" 또는 " ~ " 같은 잘못된 문자열이 표시될 수 있습니다.

🔎 방어적 처리 제안
 const recruitmentPeriodDisplay = (() => {
   const isAlways = clubDetail.recruitmentStatus === '상시모집';
-  return isAlways
-    ? '상시모집'
-    : clubDetail.recruitmentStart + ' ~ ' + clubDetail.recruitmentEnd;
+  if (isAlways) return '상시모집';
+  if (!clubDetail.recruitmentStart || !clubDetail.recruitmentEnd) {
+    return '모집기간 미정';
+  }
+  return `${clubDetail.recruitmentStart} ~ ${clubDetail.recruitmentEnd}`;
 })();
frontend/src/utils/recruitmentDateParser.ts (1)

1-17: LGTM - KST → UTC 변환 로직 올바르게 구현됨

+0900 타임존 오프셋을 추가하여 date-fns로 파싱하는 방식이 적절합니다. regex 검증과 isValid 체크로 이중 검증을 수행하여 안정성이 확보되었습니다.

frontend/src/types/club.ts (1)

23-24: LGTM - API 응답 형식 변경에 맞춘 타입 정의

recruitmentPeriod 단일 문자열에서 recruitmentStartrecruitmentEnd 분리 필드로 타입이 올바르게 업데이트되었습니다.

frontend/src/utils/recruitmentDateParser.test.ts (1)

4-14: LGTM - KST → UTC 변환 테스트 케이스 검증 완료

자정(00:00 KST → 전날 15:00 UTC) 케이스를 포함하여 타임존 변환 로직이 올바르게 테스트되고 있습니다.

frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (1)

60-61: LGTM - recruitmentStart/recruitmentEnd props 전달 변경 완료

ClubDetailHeaderClubDetailFooter에 분리된 모집 기간 props가 올바르게 전달되고 있습니다.

Also applies to: 79-80

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

116-123: toISOString() 호출 시 null 케이스 처리 확인 필요

startForSaveendForSave가 null일 경우 toISOString()은 호출되지 않고 undefined가 됩니다. 서버 API가 이 값들을 필수로 요구하는지 확인이 필요합니다. null인 경우 요청 자체를 막거나, 기본값을 제공하는 방어 로직을 추가하는 것이 안전합니다.

🔎 방어 로직 제안
 const handleUpdateClub = async () => {
   trackEvent(ADMIN_EVENT.UPDATE_RECRUIT_BUTTON_CLICKED);
   if (!clubDetail) return;

+  if (!recruitmentStart) {
+    alert('모집 시작일을 설정해주세요.');
+    return;
+  }
+
   let startForSave: Date | null = recruitmentStart;
   let endForSave: Date | null = recruitmentEnd;

34-40: LGTM - 상시모집 토글 시 날짜 백업/복원 로직 잘 구현됨

backupRangeRef를 사용하여 상시모집 전환 시 이전 날짜 범위를 보존하고, 비활성화 시 복원하는 로직이 적절합니다. 백업 자체가 상시모집이었던 엣지 케이스도 isFarFuture 헬퍼로 처리하고 있습니다.

Also applies to: 73-101

frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx (1)

1-4: LGTM! 임포트 변경이 올바르게 적용되었습니다.

새로운 recruitmentDateParser 유틸리티로의 마이그레이션이 PR 목표와 일치하며, 코드가 명확합니다.

@seongwon030 seongwon030 requested a review from oesnuj December 21, 2025 11:08
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

📜 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 45d96f4 and 83b9725.

📒 Files selected for processing (3)
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx (3 hunks)
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx (1 hunks)
  • frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/pages/ClubDetailPage/ClubDetailPage.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/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.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/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (frontend/.cursorrules)

Use consistent return types for similar functions/hooks

Files:

  • frontend/src/pages/ClubDetailPage/components/ClubDetailHeader/ClubDetailHeader.tsx
  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
🧠 Learnings (1)
📚 Learning: 2025-07-19T05:05:10.196Z
Learnt from: seongwon030
Repo: Moadong/moadong PR: 548
File: frontend/src/pages/ClubDetailPage/ClubDetailPage.tsx:17-57
Timestamp: 2025-07-19T05:05:10.196Z
Learning: ClubDetailPage.tsx에서 notJoinedClubNames 배열의 하드코딩은 의도적인 설계 결정입니다. 개발자가 명시적으로 하드코딩을 선택했으므로 이에 대한 리팩토링 제안을 하지 않아야 합니다.

Applied to files:

  • frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx
🧬 Code graph analysis (1)
frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx (1)
frontend/src/utils/recruitmentDateParser.ts (1)
  • recruitmentDateParser (3-17)
🔇 Additional comments (2)
frontend/src/pages/ClubDetailPage/components/ClubDetailFooter/ClubDetailFooter.tsx (2)

6-9: 이전 리뷰 코멘트가 해결되었습니다.

이전 리뷰에서 지적되었던 사용되지 않는 recruitmentForm prop이 제거되었고, 새로운 recruitmentStartrecruitmentEnd prop으로 대체되었습니다. 두 prop 모두 컴포넌트 본문에서 실제로 사용되고 있어 문제가 해결되었습니다.


20-20: getDeadlineText 함수가 null 파라미터를 올바르게 처리합니다.

함수의 타입 시그니처에서 파라미터가 Date | null로 명시되어 있으며, 첫 번째 줄에서 if (!recruitmentStart || !recruitmentEnd) return '모집 마감';으로 null 값을 처리합니다. 테스트 코드에서도 getDeadlineText(null, null)에 대해 올바르게 '모집 마감'을 반환하는 것이 확인되었습니다.

Likely an incorrect or invalid review comment.

- 사용자 관점의 테스트명으로 변경
- "모집 마감 날짜 입력 처리" describe 블록으로 통일
- ClubDetailPage.ts
- ClubDetailHeader
- 하드코딩된 `MobileWindowWidth` 상수와 `window.innerWidth` 직접 비교 로직 제거
- `useDevice` 훅의 `isMobile`을 사용하여 일관된 모바일 환경 감지 적용
- 불필요한 코드 정리 및 가독성 향상
Copy link
Member

@oesnuj oesnuj left a comment

Choose a reason for hiding this comment

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

드디어 해결되었네요...! 수고하셨습니다

기존: recruitmentPeriod: "2025.12.07 10:00 ~ 2025.12.07 22:00" (단일 문자열)
변경: recruitmentStart, recruitmentEnd (분리된 필드)

- 불필요한 resize 이벤트 리스너 및 local state(`showHeader`) 제거
- useDevice 훅의 isMobile값을 직접 사용하여 헤더 렌더링 조건
@seongwon030 seongwon030 merged commit be2a333 into develop-fe Dec 21, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FE Frontend 🔨 Refactor 코드 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[refactor] MOA-433 모집 기간 API 응답 형식 변경 및 KST/UTC 시간대 처리 개선

2 participants

Comments