[feature] 관리자 페이지 모집기간 선택에 상시모집 처리 버튼을 추가한다#774
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. Warning
|
| Cohort / File(s) | Summary |
|---|---|
RecruitEditTab 상시모집 로직 & UIfrontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx, frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts |
FAR_FUTURE_YEAR(2999) 기반의 상시모집 모드 도입; always 플래그와 backupRangeRef 추가, 초기화 및 toggleAlways 토글 로직 구현; 저장 시 종료일을 2999로 오버라이드하는 저장 로직 적용; UI에 RecruitPeriodContainer 및 AlwaysRecruitButton 스타일/컴포넌트 추가 |
Calendar 컴포넌트 변경frontend/src/pages/AdminPage/tabs/RecruitEditTab/components/Calendar/Calendar.tsx |
CalendarProps에 disabledEnd?: boolean 추가(기본 false); 종료일 변경 핸들러에서 disabledEnd 검사 및 End DatePicker에 disabled={disabledEnd} 전달; 컨테이너에 data-disabled 적용 |
Calendar 스타일 업데이트frontend/src/pages/AdminPage/tabs/RecruitEditTab/components/Calendar/Calendar.styles.ts |
disabledGray, inputDisabledBg 상수 추가 및 date-picker 입력의 disabled 스타일 규칙 추가 |
클럽 상세 모집기간 표시 변경frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx |
모집 상태('상시모집')를 감지해 사용자 표시 문자열을 recruitmentPeriodDisplay로 선택하도록 변경(직접 clubDetail.recruitmentPeriod 대신 사용) |
Sequence Diagram(s)
sequenceDiagram
actor Admin as 관리자
participant RecruitEdit as RecruitEditTab
participant Calendar as Calendar
participant API as Backend API
Admin->>RecruitEdit: 페이지 로드
Note right of RecruitEdit #E8F0FF: 초기값 검사\n종료 연도가 2999이면 always=true로 설정
RecruitEdit->>Calendar: render(disabledEnd = always)
Admin->>RecruitEdit: 상시모집 버튼 클릭
alt 활성화
RecruitEdit->>RecruitEdit: 현재 기간을 backupRange에 저장
RecruitEdit->>RecruitEdit: recruitmentStart/recruitmentEnd 설정 (end = setYear(now,2999))
RecruitEdit->>Calendar: render(disabledEnd = true)
else 비활성화
RecruitEdit->>RecruitEdit: backupRange에서 기간 복원 (없으면 현재 날짜 사용)
RecruitEdit->>Calendar: render(disabledEnd = false)
end
Admin->>RecruitEdit: 저장 클릭
RecruitEdit->>RecruitEdit: startForSave/endForSave 계산 (always면 endForSave=2999)
RecruitEdit->>API: PUT /clubs (startForSave, endForSave)
API-->>RecruitEdit: 200 OK
RecruitEdit-->>Admin: 저장 완료 표시
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~25 minutes
- 주의할 파일:
RecruitEditTab.tsx(토글/backup 복원 로직, 날짜 변환 및 저장 로직),Calendar.tsx(disabledEnd 처리), 스타일 변경으로 인한 UI 영향 확인.
Possibly related issues
- MOA-264: 관리자 페이지 모집기간 선택에 상시모집 처리 버튼을 추가한다 — 본 PR은 관리자 모집기간에 상시모집 토글을 추가하는 동일 목표를 구현함.
- [feature] MOA-264 관리자 페이지 모집기간 선택에 상시모집 처리 버튼을 추가한다 #767 — 동일한 "상시모집" 기능(2999 연도 사용, 캘린더 종료 비활성화, 토글 로직)을 다루므로 관련됨.
Possibly related PRs
- [feature] 동아리 상시모집 상태 추가 #707 — 프론트엔드에서 ALWAYS 상태 스타일/표시를 다루는 변경으로, 상시모집 UI 표시에 코드 레벨 교차 가능성 있음.
- [release] v1.1.0 #676 — RecruitEditTab 및 Calendar 변경을 포함하는 PR로, 캘린더/기간 처리 충돌 가능성이 높음.
- [feature] 모집기간 달력 UX 개선: 시간 선택 추가, 날짜 로직 변경 및 시간대 오류 수정 #664 — Calendar 컴포넌트 및 스타일 변경을 다룬 PR로 스타일/입력 비활성화 관련 겹침 가능성 있음.
Suggested reviewers
- Zepelown
- seongje973
- oesnuj
Pre-merge checks and finishing touches
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Out of Scope Changes Check | ClubDetailPage의 InfoBox 컴포넌트에서 모집기간 표시 로직을 변경한 부분은 MOA-264 이슈의 관리자 페이지 상시모집 기능 범위를 벗어난 변경사항입니다. | InfoBox 변경은 본 PR에서 분리하여 별도 이슈나 PR로 관리하거나 이 요구사항을 범위에 포함시킬지 검토해야 합니다. |
✅ Passed checks (4 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title Check | ✅ Passed | PR 제목이 관리자 페이지 모집기간 선택에 상시모집 처리 버튼 추가라는 주 변경사항을 명확하고 간결하게 요약하고 있어 스캔 시 핵심을 이해하기 쉽습니다. |
| Linked Issues Check | ✅ Passed | PR이 MOA-264 이슈에서 요구한 관리자 페이지 모집기간 UI에 상시모집 버튼을 추가하고 버튼 활성화 시 캘린더 비활성화 및 날짜 설정 로직을 완전하게 구현하여 모든 주요 요구사항을 충족합니다. |
| Docstring Coverage | ✅ Passed | No functions found in the changes. Docstring coverage check skipped. |
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
📜 Recent review details
Configuration used: CodeRabbit 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.
📒 Files selected for processing (2)
frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx(6 hunks)frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx(1 hunks)
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 @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit 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.
📒 Files selected for processing (4)
frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.ts(1 hunks)frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx(5 hunks)frontend/src/pages/AdminPage/tabs/RecruitEditTab/components/Calendar/Calendar.styles.ts(2 hunks)frontend/src/pages/AdminPage/tabs/RecruitEditTab/components/Calendar/Calendar.tsx(3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.
Files:
frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsxfrontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.styles.tsfrontend/src/pages/AdminPage/tabs/RecruitEditTab/components/Calendar/Calendar.tsxfrontend/src/pages/AdminPage/tabs/RecruitEditTab/components/Calendar/Calendar.styles.ts
frontend/**/*.tsx
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.tsx: Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Separate significantly different conditional UI/logic into distinct components.
Colocate simple, localized logic or use inline definitions to reduce context switching.
Choose field-level or form-level cohesion based on form requirements.
Break down broad state management into smaller, focused hooks or contexts.
Use component composition instead of props drilling.
Files:
frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsxfrontend/src/pages/AdminPage/tabs/RecruitEditTab/components/Calendar/Calendar.tsx
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx (1)
27-33: FAR_FUTURE_YEAR 상수를 공통 모듈로 이동해주세요.
InfoBox와RecruitEditTab모두에서 2999 상수를 직접 선언하고 있어 향후 기준 연도를 조정할 때 누락 위험이 큽니다. 별도의 공통 상수 파일로 추출해 두 컴포넌트가 같은 값을 참조하도록 바꾸는 편이 안전합니다. 아래와 같이 공통 상수를 정의하고 import해 주세요.-const FAR_FUTURE_YEAR = 2999; +import { FAR_FUTURE_YEAR } from '@/constants/recruitment';+// frontend/src/constants/recruitment.ts +export const FAR_FUTURE_YEAR = 2999;
📜 Review details
Configuration used: CodeRabbit 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.
📒 Files selected for processing (3)
frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx(5 hunks)frontend/src/pages/AdminPage/tabs/RecruitEditTab/components/Calendar/Calendar.tsx(4 hunks)frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx(2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
frontend/**/*.{ts,tsx}
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.{ts,tsx}: Replace magic numbers with named constants for clarity.
Replace complex or nested ternary operators with if/else statements or IIFEs for readability.
Assign complex boolean conditions to named variables.
Use consistent return types for similar functions and hooks.
Avoid hidden side effects; functions should only perform actions implied by their signature (Single Responsibility Principle).
Use unique, descriptive names for custom wrappers and functions to avoid ambiguity.
Define constants near related logic or ensure names link them clearly.
Files:
frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsxfrontend/src/pages/AdminPage/tabs/RecruitEditTab/components/Calendar/Calendar.tsxfrontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx
frontend/**/*.tsx
📄 CodeRabbit inference engine (frontend/.cursorrules)
frontend/**/*.tsx: Abstract complex logic/interactions into dedicated components or higher-order components (HOCs).
Separate significantly different conditional UI/logic into distinct components.
Colocate simple, localized logic or use inline definitions to reduce context switching.
Choose field-level or form-level cohesion based on form requirements.
Break down broad state management into smaller, focused hooks or contexts.
Use component composition instead of props drilling.
Files:
frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsxfrontend/src/pages/AdminPage/tabs/RecruitEditTab/components/Calendar/Calendar.tsxfrontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx
🧬 Code graph analysis (1)
frontend/src/pages/ClubDetailPage/components/InfoBox/InfoBox.tsx (1)
frontend/src/utils/recruitmentPeriodParser.ts (1)
parseRecruitmentPeriod(19-31)
| const toggleAlways = () => { | ||
| setAlways((prev) => { | ||
| const now = new Date(); | ||
|
|
||
| if (!prev) { | ||
| // 상시모집 활성화 | ||
| setBackupRange({ start: recruitmentStart, end: recruitmentEnd }); | ||
| } else { | ||
| // 상시모집 비활성화 | ||
| const backupWasAlways = isFarFuture(backupRange.end); | ||
| if (backupWasAlways) { | ||
| // 백업이 상시모집인 경우 | ||
| const base = backupRange.start ?? now; | ||
| setRecruitmentStart(base); | ||
| setRecruitmentEnd(base); | ||
| } else { | ||
| // 백업이 상시모집이 아닌 경우 | ||
| setRecruitmentStart(backupRange.start ?? now); | ||
| setRecruitmentEnd(backupRange.end ?? now); | ||
| } |
There was a problem hiding this comment.
상시모집 해제 시 현재 날짜로 초기화되지 않습니다.
기존에 상시모집으로 저장된 동아리를 해제하면 backupRange.start 값(예: 과거 날짜)을 그대로 되살려 현재 날짜로 초기화된다는 요구사항을 충족하지 못합니다. 사용자는 즉시 편집하려고 해도 오래된 날짜부터 다시 맞춰야 하는 문제가 생기니, 백업이 상시모집이었던 경우에는 무조건 현재 시각으로 초기화하도록 조정해주세요.
if (!prev) {
// 상시모집 활성화
setBackupRange({ start: recruitmentStart, end: recruitmentEnd });
} else {
// 상시모집 비활성화
const backupWasAlways = isFarFuture(backupRange.end);
if (backupWasAlways) {
- // 백업이 상시모집인 경우
- const base = backupRange.start ?? now;
- setRecruitmentStart(base);
- setRecruitmentEnd(base);
+ // 백업이 상시모집인 경우: 현재 날짜로 초기화
+ setRecruitmentStart(now);
+ setRecruitmentEnd(now);
} else {
// 백업이 상시모집이 아닌 경우
setRecruitmentStart(backupRange.start ?? now);
setRecruitmentEnd(backupRange.end ?? now);
}
}📝 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.
| const toggleAlways = () => { | |
| setAlways((prev) => { | |
| const now = new Date(); | |
| if (!prev) { | |
| // 상시모집 활성화 | |
| setBackupRange({ start: recruitmentStart, end: recruitmentEnd }); | |
| } else { | |
| // 상시모집 비활성화 | |
| const backupWasAlways = isFarFuture(backupRange.end); | |
| if (backupWasAlways) { | |
| // 백업이 상시모집인 경우 | |
| const base = backupRange.start ?? now; | |
| setRecruitmentStart(base); | |
| setRecruitmentEnd(base); | |
| } else { | |
| // 백업이 상시모집이 아닌 경우 | |
| setRecruitmentStart(backupRange.start ?? now); | |
| setRecruitmentEnd(backupRange.end ?? now); | |
| } | |
| const toggleAlways = () => { | |
| setAlways((prev) => { | |
| const now = new Date(); | |
| if (!prev) { | |
| // 상시모집 활성화 | |
| setBackupRange({ start: recruitmentStart, end: recruitmentEnd }); | |
| } else { | |
| // 상시모집 비활성화 | |
| const backupWasAlways = isFarFuture(backupRange.end); | |
| if (backupWasAlways) { | |
| // 백업이 상시모집인 경우: 현재 날짜로 초기화 | |
| setRecruitmentStart(now); | |
| setRecruitmentEnd(now); | |
| } else { | |
| // 백업이 상시모집이 아닌 경우 | |
| setRecruitmentStart(backupRange.start ?? now); | |
| setRecruitmentEnd(backupRange.end ?? now); | |
| } | |
| } | |
| }); | |
| }; |
🤖 Prompt for AI Agents
In frontend/src/pages/AdminPage/tabs/RecruitEditTab/RecruitEditTab.tsx around
lines 69–88, the logic that runs when toggling off "always recruiting" uses
backupRange.start (which may be a past date) instead of the current time when
the backup was previously "always"; change the branch so that if backupWasAlways
is true you set recruitmentStart and recruitmentEnd to the current time (now)
unconditionally (or fallback to now if backupRange.start is null), and ensure
the setAlways updater returns the toggled boolean (e.g., return !prev) so the
state actually flips.
lepitaaar
left a comment
There was a problem hiding this comment.
상시모집버튼 추가 수고하셨습니다
개인적으로 모집정보 수정할때 귀찮았던거같네요
There was a problem hiding this comment.
따로 기준을둬서 Always 판단하는것보단 clubDetail.state부분에 현재 모집상태를 반환합니다. 해당부분 사용해서 상시모집 판단하시면될꺼같습니다
There was a problem hiding this comment.
clubDetail.recruitmentState 이용해서 판별하는거로 수정했습니다ㅏ 감사합니당
There was a problem hiding this comment.
영구저장은아니지만 임시로 날짜 백업해두는거 좋습니다
| let startForSave: Date | null = recruitmentStart; | ||
| let endForSave: Date | null = recruitmentEnd; | ||
|
|
||
| if (always) { | ||
| const base = recruitmentStart ?? new Date(); | ||
| startForSave = base; | ||
| endForSave = setYear(base, FAR_FUTURE_YEAR); | ||
| } |
There was a problem hiding this comment.
always가 업데이트될때 recruiment정보가 업데이트되는데 여기서 한번더 확인하는코드가 필요하나요?
There was a problem hiding this comment.
없어도 큰 문제가 발생하지는 않습니다! 다만 사용자가 상태모집 토글 버튼을 클릭 후 2999년이 반영되기 전에 수정하기 버튼을 누를 경우의 문제나 여러 예외 입력이 있을 수 있어서 한 번 더 확인했습니다ㅏ
There was a problem hiding this comment.
이코드도 위와 같이 clubDetail.state 사용하시면될꺼같습니다
There was a problem hiding this comment.
하드코딩했던 코드가 훨씬 간결해진거 같아요! 감사합니당
| const FAR_FUTURE_YEAR = 2999; | ||
| const isFarFuture = (date: Date | null) => !!date && date.getFullYear() === FAR_FUTURE_YEAR; |
There was a problem hiding this comment.
제가 생각해도 관리자에게 2999년이 보이는 건 이상한 것 같네요.
일단 클라이언트에서 UI로만 처리해도 될 것 같습니다.
ex. 2999년 -> 상시모집
There was a problem hiding this comment.
!! 연산자로 null, undefined 상태를 모두 검사하는 부분 좋습니다.
| const [recruitmentEnd, setRecruitmentEnd] = useState<Date | null>(null); | ||
| const [recruitmentTarget, setRecruitmentTarget] = useState(''); | ||
| const [description, setDescription] = useState(''); |
There was a problem hiding this comment.
이렇게 state가 많을 때는 useReducer 도입을 고려해보는 것도 좋을 것 같습니다.
There was a problem hiding this comment.
덕분에 Reducer에 대해 알게되었습니다ㅏ 좀 더 공부해보고 적용해보겠습니다.
There was a problem hiding this comment.
백업 데이터는 렌더링과 상관없는 데이터가 될 것 같은데 오로지 복원용이라면 useRef가 더 낫지 않을까요?
There was a problem hiding this comment.
렌더를 불필요하게 발생시키지 않아서 훨씬 나은 것 같아요! 수정했습니당
fix: 상시모집 상태 판별 조건 수정
#️⃣연관된 이슈
📝작업 내용
수정 사항
중점적으로 리뷰받고 싶은 부분(선택)
논의하고 싶은 부분(선택)
🫡 참고사항
Summary by CodeRabbit
신기능
UI/UX