Skip to content

[feat] 모집 공고에 시간 추가 및 지원서 정렬 추가#131

Merged
ff1451 merged 8 commits intodevelopfrom
130-feat-모집-공고에-시간-추가-및-지원서-정렬-추가
Feb 22, 2026

Hidden character warning

The head ref may contain hidden characters: "130-feat-\ubaa8\uc9d1-\uacf5\uace0\uc5d0-\uc2dc\uac04-\ucd94\uac00-\ubc0f-\uc9c0\uc6d0\uc11c-\uc815\ub82c-\ucd94\uac00"
Merged

[feat] 모집 공고에 시간 추가 및 지원서 정렬 추가#131
ff1451 merged 8 commits intodevelopfrom
130-feat-모집-공고에-시간-추가-및-지원서-정렬-추가

Conversation

@ff1451
Copy link
Copy Markdown
Collaborator

@ff1451 ff1451 commented Feb 22, 2026

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 텍스트 내 URL·인스타 핸들 자동 링크화(채팅·모집 공고 포함)
    • 시간 선택 가능한 TimePicker 추가(모집 작성 UI에 통합)
    • 관리용 지원 조회에 페이지네이션·정렬·필터 파라미터 추가
  • Improvements

    • 지원 목록·상세에 지원 시각(HH:MM) 표시 추가
    • 지원서의 학번을 문자열로 취급하고 결제 이미지 URL 선택항목 추가
    • 모집 기간 표시를 startAt/endAt(날짜+시간)로 통일
  • Chores

    • 모집 관련 기간 필드 표준화 및 관련 UI 반영

@ff1451 ff1451 self-assigned this Feb 22, 2026
@ff1451 ff1451 linked an issue Feb 22, 2026 that may be closed by this pull request
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

Recruitment 관련 날짜 필드를 startDate/endDate에서 startAt/endAt로 교체했고, Manager 쪽에 시:분 단위 시간 입력·표시(타임피커 및 관련 유틸)를 추가했습니다. Application 타입에서 studentNumber를 number→string으로 변경하고 feePaymentImageUrl?: string을 추가했으며, ClubApplicationsParams와 PaginationResponse 확장을 도입해 애플리케이션 조회에 페이지네이션·정렬·필터를 지원하도록 API 시그니처를 변경했습니다. 메시지/콘텐츠 링크 자동화를 위한 LinkifiedText 컴포넌트도 추가되었습니다.

Possibly related issues

  • BCSDLab/KONECT_FRONT_END 130: recruitment에 시간(startAt/endAt) 추가 및 애플리케이션 정렬/파라미터 요구사항과 직접적으로 일치 — 본 PR이 시간-aware recruitment와 getManagedClubApplications의 params 적용을 포함합니다.

Possibly related PRs

  • BCSDLab/KONECT_FRONT_END 117: club API 엔티티(특히 recruitment/settings) 타입을 변경하는 점에서 강한 코드 레벨 연관 — 본 PR의 ClubSettingsRecruitment 리팩터와 중복/충돌 가능성이 있습니다.
  • BCSDLab/KONECT_FRONT_END 125: club API의 엔티티/응답 스키마를 수정한 PR로, Application 및 recruitment 관련 타입 변경이 상호 영향 가능성이 큽니다.
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 PR의 핵심 변경사항을 명확히 요약합니다: 모집 공고에 시간 기능 추가 및 지원서 정렬 기능 추가라는 두 가지 주요 변경을 간결하게 표현했습니다.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 130-feat-모집-공고에-시간-추가-및-지원서-정렬-추가

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

Copy link
Copy Markdown
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)
src/pages/Manager/ManagedRecruitmentWrite/index.tsx (1)

196-205: ⚠️ Potential issue | 🟡 Minor

handleDeleteImage에서 방어적 검사 추가 권장

images[currentImageIndex]가 undefined일 경우에 대한 방어적 처리가 없습니다. 경쟁 상태(race condition)에서 문제가 발생할 수 있습니다.

🛡️ 방어적 코드 추가
 const handleDeleteImage = () => {
+  const currentImage = images[currentImageIndex];
+  if (!currentImage) return;
+
-  URL.revokeObjectURL(images[currentImageIndex].previewUrl);
+  URL.revokeObjectURL(currentImage.previewUrl);
   const newImages = images.filter((_, index) => index !== currentImageIndex);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx` around lines 196 - 205,
handleDeleteImage currently assumes images[currentImageIndex] exists; add
defensive checks to avoid runtime errors by verifying images.length > 0 and
currentImageIndex is within bounds before calling URL.revokeObjectURL or slicing
the array. Specifically, in handleDeleteImage ensure images and
images[currentImageIndex] are defined (e.g., images.length > 0 &&
currentImageIndex >= 0 && currentImageIndex < images.length) before revoking the
preview URL and building newImages, then proceed to call setImages and adjust
setCurrentImageIndex as before.
🧹 Nitpick comments (2)
src/pages/Manager/ManagedRecruitmentWrite/index.tsx (1)

42-74: 헬퍼 함수들을 유틸리티 모듈로 분리 권장

isValidTimeFormat, normalizeTime, parseDateTimeDot, formatDateTimeDot, combineDateTime 함수들이 이 컴포넌트에만 정의되어 있습니다. src/utils/ts/date.ts로 이동하면 재사용성과 테스트 용이성이 향상됩니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx` around lines 42 - 74,
Extract the helper functions isValidTimeFormat, normalizeTime, parseDateTimeDot,
formatDateTimeDot, and combineDateTime into src/utils/ts/date.ts as exported
utilities; ensure you also export or import any constants they depend on (e.g.,
TIME_MINUTE_STEP, DEFAULT_START_TIME) and the formatDateDot helper so the logic
remains intact, then update ManagedRecruitmentWrite to import these functions
from the new module and remove the local definitions; keep function signatures
identical and add unit tests in the utils module to cover edge cases (invalid
dates, time normalization) if not already present.
src/pages/Manager/components/TimePicker/index.tsx (1)

162-188: 스크롤 영역에 접근성 속성 추가 권장

스크롤 가능한 시간 선택 영역에 role="listbox"aria-label 속성을 추가하면 스크린 리더 사용자 경험이 개선됩니다.

♿ 접근성 개선 제안
 <div
   ref={hourRef}
   onScroll={createScrollHandler(hourOptions, setDraftHour)}
+  role="listbox"
+  aria-label="시간 선택"
   className="h-full snap-y snap-mandatory overflow-y-auto [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
 >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Manager/components/TimePicker/index.tsx` around lines 162 - 188,
The scrollable hour container (the div using hourRef and hourOptions) needs ARIA
attributes for screen readers: add role="listbox" and a descriptive aria-label
(e.g., "Select hour") to that div, and ensure each hour button (created in the
hourOptions.map with onClick calling setDraftHour and scrollToItem) has
role="option" plus aria-selected={isSelected} so assistive tech can identify
selectable items and the current selection; keep existing behavior
(PADDING_HEIGHT, pad2, twMerge) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/apis/club/entity.ts`:
- Around line 140-145: The detail response type for student number is
inconsistent with the Application model: update the
ClubApplicationDetailResponse.studentNumber type from number to string so it
matches Application.studentNumber (and update any related DTOs/interfaces that
reference ClubApplicationDetailResponse.studentNumber to the string type as
well); locate the symbol ClubApplicationDetailResponse and change its
studentNumber declaration to string to ensure type consistency across
Application.studentNumber and all consumers.

In `@src/apis/club/index.ts`:
- Around line 34-35: The import list is misformatted causing Prettier lint
failures; reformat the import block that currently reads "type
ClubApplicationsParams, } from './entity';" into a properly spaced multi-line
import (for example: import { type ClubApplicationsParams } from './entity'; or
each specifier on its own line with consistent commas) and apply the same
Prettier-style formatting fix to the other import block around the Club-related
imports referenced (the block that spans the other occurrence with the same
specifiers). Ensure trailing commas, consistent spacing, and that import braces
and specifiers follow the project's Prettier rules so lint passes.

In `@src/pages/Club/ClubList/components/ClubCard.tsx`:
- Around line 10-11: The parsing of applicationDeadline in ClubCard is currently
splitting on '.' (datePart and the [year, month, day] extraction) which fails
for ISO timestamps and yields NaN; update ClubCard to normalize ISO dates before
splitting by either using the existing utility formatIsoDateToYYYYMMDD (call it
with applicationDeadline) or implement a short normalization step that extracts
the YYYY-MM-DD part (or replaces '-' with '.') to produce a consistent
YYYY.MM.DD string, then derive year/month/day from that normalized string so the
badge logic (datePart, year, month, day) works for both ISO and dotted formats.

---

Outside diff comments:
In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx`:
- Around line 196-205: handleDeleteImage currently assumes
images[currentImageIndex] exists; add defensive checks to avoid runtime errors
by verifying images.length > 0 and currentImageIndex is within bounds before
calling URL.revokeObjectURL or slicing the array. Specifically, in
handleDeleteImage ensure images and images[currentImageIndex] are defined (e.g.,
images.length > 0 && currentImageIndex >= 0 && currentImageIndex <
images.length) before revoking the preview URL and building newImages, then
proceed to call setImages and adjust setCurrentImageIndex as before.

---

Nitpick comments:
In `@src/pages/Manager/components/TimePicker/index.tsx`:
- Around line 162-188: The scrollable hour container (the div using hourRef and
hourOptions) needs ARIA attributes for screen readers: add role="listbox" and a
descriptive aria-label (e.g., "Select hour") to that div, and ensure each hour
button (created in the hourOptions.map with onClick calling setDraftHour and
scrollToItem) has role="option" plus aria-selected={isSelected} so assistive
tech can identify selectable items and the current selection; keep existing
behavior (PADDING_HEIGHT, pad2, twMerge) unchanged.

In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx`:
- Around line 42-74: Extract the helper functions isValidTimeFormat,
normalizeTime, parseDateTimeDot, formatDateTimeDot, and combineDateTime into
src/utils/ts/date.ts as exported utilities; ensure you also export or import any
constants they depend on (e.g., TIME_MINUTE_STEP, DEFAULT_START_TIME) and the
formatDateDot helper so the logic remains intact, then update
ManagedRecruitmentWrite to import these functions from the new module and remove
the local definitions; keep function signatures identical and add unit tests in
the utils module to cover edge cases (invalid dates, time normalization) if not
already present.

Comment thread src/apis/club/entity.ts
Comment thread src/apis/club/index.ts
Comment thread src/pages/Club/ClubList/components/ClubCard.tsx Outdated
Copy link
Copy Markdown
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

🧹 Nitpick comments (4)
src/apis/club/entity.ts (2)

154-160: PaginationResponse 재사용 고려.

페이지네이션 필드들이 PaginationResponse와 유사해 보입니다. 기존 타입을 extend하거나 재사용할 수 있는지 확인해 보세요.

// 예시: PaginationResponse 구조와 동일하다면
export interface ClubApplicationsResponse extends PaginationResponse {
  applications: Application[];
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/apis/club/entity.ts` around lines 154 - 160, The ClubApplicationsResponse
duplicates pagination fields that match the existing PaginationResponse; update
ClubApplicationsResponse to reuse or extend PaginationResponse (e.g., make
ClubApplicationsResponse extend PaginationResponse and keep the applications:
Application[] field) so you remove duplicated fields and keep pagination logic
centralized; update any code that constructs or types ClubApplicationsResponse
to rely on the new shape.

304-308: ClubSettingsRecruitment 타입을 조건부로 정의해야 합니다.

isAlwaysRecruiting: true일 때 startAt, endAt는 불필요합니다. 이미 존재하는 ClubRecruitmentRequest처럼 판별 조합(discriminated union) 타입으로 변경하는 것이 좋습니다:

type ClubSettingsRecruitment =
  | {
      isAlwaysRecruiting: true;
      startAt?: never;
      endAt?: never;
    }
  | {
      isAlwaysRecruiting: false;
      startAt: string;
      endAt: string;
    };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/apis/club/entity.ts` around lines 304 - 308, Replace the flat interface
ClubSettingsRecruitment with a discriminated union similar to
ClubRecruitmentRequest: provide one variant where isAlwaysRecruiting: true and
startAt/endAt are disallowed (startAt?: never; endAt?: never) and another where
isAlwaysRecruiting: false and startAt/endAt are required strings; update any
code expecting the old shape to use the discriminated union for proper
type-narrowing (look for usages of ClubSettingsRecruitment).
src/pages/Manager/ManagedRecruitmentWrite/index.tsx (2)

36-39: 중복 스타일 상수

compactDateButtonStylecompactTimeButtonStyle이 동일합니다. 하나로 통합하는 것을 고려해 보세요.

♻️ 통합 제안
-const compactDateButtonStyle =
-  'group flex h-10 min-w-0 w-full items-center justify-between rounded-lg border border-indigo-50 bg-white px-3 text-left shadow-[0_2px_6px_rgba(2,23,48,0.06)]';
-const compactTimeButtonStyle =
-  'group flex h-10 min-w-0 w-full items-center justify-between rounded-lg border border-indigo-50 bg-white px-3 text-left shadow-[0_2px_6px_rgba(2,23,48,0.06)]';
+const compactInputButtonStyle =
+  'group flex h-10 min-w-0 w-full items-center justify-between rounded-lg border border-indigo-50 bg-white px-3 text-left shadow-[0_2px_6px_rgba(2,23,48,0.06)]';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx` around lines 36 - 39,
compactDateButtonStyle and compactTimeButtonStyle are identical; replace them
with a single shared constant (e.g., compactButtonStyle) and update all
references to compactDateButtonStyle and compactTimeButtonStyle to use the new
constant; ensure the new constant preserves the current string value and keep
the original names removed to avoid duplicates (search for usages of
compactDateButtonStyle/compactTimeButtonStyle and update imports/consumers
accordingly).

161-170: 기존 이미지 URL 처리

isExistingtrue인 경우 previewUrl은 서버 URL이므로 URL.revokeObjectURL 호출은 무의미합니다 (해롭지는 않음). 명시적으로 체크하면 의도가 더 명확해집니다.

♻️ 선택적 개선
   const handleDeleteImage = () => {
-    URL.revokeObjectURL(images[currentImageIndex].previewUrl);
+    const target = images[currentImageIndex];
+    if (!target.isExisting) {
+      URL.revokeObjectURL(target.previewUrl);
+    }
     const newImages = images.filter((_, index) => index !== currentImageIndex);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx` around lines 161 - 170,
The deletion logic in handleDeleteImage currently always calls
URL.revokeObjectURL on images[currentImageIndex].previewUrl even when that image
is an existing server URL; update handleDeleteImage to first guard that
images[currentImageIndex] exists and then only call URL.revokeObjectURL if
images[currentImageIndex].isExisting is false (i.e., it's a local object URL),
then proceed to create newImages, call setImages(newImages) and adjust
setCurrentImageIndex as before (using currentImageIndex and newImages.length).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/utils/ts/date.ts`:
- Around line 12-13: timePart splitting assumes a colon and two parts so calling
minute.padStart can throw; update the logic around the destructuring of timePart
(the line using const [hour, minute] = timePart.split(':')) to validate and/or
normalize the split parts before calling padStart: ensure timePart contains both
hour and minute (fallback minute to '00' and hour to '0' or throw a clear
error), then call hour.padStart(2,'0') and minute.padStart(2,'0'); reference the
timePart, hour, minute and padStart symbols when editing.

---

Nitpick comments:
In `@src/apis/club/entity.ts`:
- Around line 154-160: The ClubApplicationsResponse duplicates pagination fields
that match the existing PaginationResponse; update ClubApplicationsResponse to
reuse or extend PaginationResponse (e.g., make ClubApplicationsResponse extend
PaginationResponse and keep the applications: Application[] field) so you remove
duplicated fields and keep pagination logic centralized; update any code that
constructs or types ClubApplicationsResponse to rely on the new shape.
- Around line 304-308: Replace the flat interface ClubSettingsRecruitment with a
discriminated union similar to ClubRecruitmentRequest: provide one variant where
isAlwaysRecruiting: true and startAt/endAt are disallowed (startAt?: never;
endAt?: never) and another where isAlwaysRecruiting: false and startAt/endAt are
required strings; update any code expecting the old shape to use the
discriminated union for proper type-narrowing (look for usages of
ClubSettingsRecruitment).

In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx`:
- Around line 36-39: compactDateButtonStyle and compactTimeButtonStyle are
identical; replace them with a single shared constant (e.g., compactButtonStyle)
and update all references to compactDateButtonStyle and compactTimeButtonStyle
to use the new constant; ensure the new constant preserves the current string
value and keep the original names removed to avoid duplicates (search for usages
of compactDateButtonStyle/compactTimeButtonStyle and update imports/consumers
accordingly).
- Around line 161-170: The deletion logic in handleDeleteImage currently always
calls URL.revokeObjectURL on images[currentImageIndex].previewUrl even when that
image is an existing server URL; update handleDeleteImage to first guard that
images[currentImageIndex] exists and then only call URL.revokeObjectURL if
images[currentImageIndex].isExisting is false (i.e., it's a local object URL),
then proceed to create newImages, call setImages(newImages) and adjust
setCurrentImageIndex as before (using currentImageIndex and newImages.length).

Comment thread src/utils/ts/date.ts
Comment on lines +12 to +13
const [hour, minute] = timePart.split(':');
return `${date} ${hour.padStart(2, '0')}:${minute.padStart(2, '0')}`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

timePart가 불완전할 경우 런타임 에러 발생 가능

timePart"12" (콜론 없음)처럼 불완전하면 minuteundefined가 되어 padStart 호출 시 에러가 발생합니다.

🛡️ 방어 코드 추가 제안
   const [hour, minute] = timePart.split(':');
+  if (!hour || !minute) return date;
   return `${date} ${hour.padStart(2, '0')}:${minute.padStart(2, '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.

Suggested change
const [hour, minute] = timePart.split(':');
return `${date} ${hour.padStart(2, '0')}:${minute.padStart(2, '0')}`;
const [hour, minute] = timePart.split(':');
if (!hour || !minute) return date;
return `${date} ${hour.padStart(2, '0')}:${minute.padStart(2, '0')}`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/ts/date.ts` around lines 12 - 13, timePart splitting assumes a
colon and two parts so calling minute.padStart can throw; update the logic
around the destructuring of timePart (the line using const [hour, minute] =
timePart.split(':')) to validate and/or normalize the split parts before calling
padStart: ensure timePart contains both hour and minute (fallback minute to '00'
and hour to '0' or throw a clear error), then call hour.padStart(2,'0') and
minute.padStart(2,'0'); reference the timePart, hour, minute and padStart
symbols when editing.

Copy link
Copy Markdown
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.

Caution

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

⚠️ Outside diff range comments (2)
src/pages/Manager/ManagedRecruitmentWrite/index.tsx (1)

159-168: ⚠️ Potential issue | 🟡 Minor

기존 이미지의 URL.revokeObjectURL 호출 문제

isExisting 이미지는 blob URL이 아닌 서버 URL이므로 revokeObjectURL 호출이 불필요합니다. handleReset(line 118-120)에서는 올바르게 처리하고 있으나, handleDeleteImage에서는 체크가 빠져있어요.

🔧 수정 제안
 const handleDeleteImage = () => {
-  URL.revokeObjectURL(images[currentImageIndex].previewUrl);
+  const target = images[currentImageIndex];
+  if (!target.isExisting) {
+    URL.revokeObjectURL(target.previewUrl);
+  }
   const newImages = images.filter((_, index) => index !== currentImageIndex);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx` around lines 159 - 168,
handleDeleteImage currently always calls
URL.revokeObjectURL(images[currentImageIndex].previewUrl) even for existing
images that use server URLs; modify handleDeleteImage to first check
images[currentImageIndex].isExisting (or similar flag) and only call
URL.revokeObjectURL when isExisting is false (i.e., the image is a blob/object
URL), guard against missing previewUrl, then proceed with the existing filtering
and currentImageIndex adjustment logic (using images, currentImageIndex,
setImages, setCurrentImageIndex) as before.
src/apis/club/entity.ts (1)

113-123: ⚠️ Potential issue | 🟡 Minor

ClubRecruitment 응답 타입에 조건부 필드 처리 추가

ClubRecruitmentRequestClubSettingsRecruitmentisAlwaysRecruiting 필드로 discriminated union을 구성해 isAlwaysRecruiting: true일 때 startAt/endAt를 제외하고 있습니다. 하지만 ClubRecruitment 응답 타입에는 이 필드가 없어 타입이 inconsistent합니다. isAlwaysRecruiting 필드를 응답 타입에도 추가하거나, startAt/endAt를 optional로 변경해 일관성을 맞춰주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/apis/club/entity.ts` around lines 113 - 123, The ClubRecruitment response
type is inconsistent with ClubRecruitmentRequest/ClubSettingsRecruitment: add
the discriminant and make the date fields conditional — update the
ClubRecruitment interface to include isAlwaysRecruiting: boolean and make
startAt and endAt optional (or nullable) so the shape matches the union used
elsewhere (referencing ClubRecruitment, ClubRecruitmentRequest, and
ClubSettingsRecruitment); ensure any callers/serializers handle the optional
startAt/endAt when isAlwaysRecruiting is true.
🧹 Nitpick comments (1)
src/pages/Manager/ManagedRecruitmentWrite/index.tsx (1)

415-427: 이미지 인디케이터에 고유 key 사용 권장이미지 인디케이터(dot buttons)에서 index를 key로 사용 중입니다. "if list of items is supposed to have features of deleting, resorting, adding new items— in this case, you have to take care of the key in a proper way to avoid any performance issues and avoid unexpected bugs."

이미지 배열은 추가/삭제가 가능하므로, previewUrl이나 고유 ID를 key로 사용하는 것이 더 안전합니다.

♻️ 수정 제안
-{images.map((_, index) => (
+{images.map((img, index) => (
   <button
-    key={index}
+    key={img.previewUrl}
     type="button"
     onClick={() => setCurrentImageIndex(index)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx` around lines 415 - 427,
The image indicator uses index as the React key which can break when images are
added/removed; update the map over images in the component that renders the dot
buttons to use a stable unique property from each image (e.g., image.previewUrl
or image.id) instead of index (keep using currentImageIndex and
setCurrentImageIndex for selection), e.g., change images.map((_, index) => ...)
to images.map((image, index) => ...) and set key to image.previewUrl or image.id
so keys remain stable across adds/removes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/apis/club/entity.ts`:
- Around line 113-123: The ClubRecruitment response type is inconsistent with
ClubRecruitmentRequest/ClubSettingsRecruitment: add the discriminant and make
the date fields conditional — update the ClubRecruitment interface to include
isAlwaysRecruiting: boolean and make startAt and endAt optional (or nullable) so
the shape matches the union used elsewhere (referencing ClubRecruitment,
ClubRecruitmentRequest, and ClubSettingsRecruitment); ensure any
callers/serializers handle the optional startAt/endAt when isAlwaysRecruiting is
true.

In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx`:
- Around line 159-168: handleDeleteImage currently always calls
URL.revokeObjectURL(images[currentImageIndex].previewUrl) even for existing
images that use server URLs; modify handleDeleteImage to first check
images[currentImageIndex].isExisting (or similar flag) and only call
URL.revokeObjectURL when isExisting is false (i.e., the image is a blob/object
URL), guard against missing previewUrl, then proceed with the existing filtering
and currentImageIndex adjustment logic (using images, currentImageIndex,
setImages, setCurrentImageIndex) as before.

---

Nitpick comments:
In `@src/pages/Manager/ManagedRecruitmentWrite/index.tsx`:
- Around line 415-427: The image indicator uses index as the React key which can
break when images are added/removed; update the map over images in the component
that renders the dot buttons to use a stable unique property from each image
(e.g., image.previewUrl or image.id) instead of index (keep using
currentImageIndex and setCurrentImageIndex for selection), e.g., change
images.map((_, index) => ...) to images.map((image, index) => ...) and set key
to image.previewUrl or image.id so keys remain stable across adds/removes.

@ff1451 ff1451 merged commit 80402c5 into develop Feb 22, 2026
2 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Feb 23, 2026
ff1451 added a commit that referenced this pull request Feb 23, 2026
* chore: 타입 변경 반영

* [feat] 모집 공고에 시간 추가 및 지원서 정렬 추가 (#131)

* feat: 모집 공고에 시작, 종료 시간 추가

* feat: 지원자 목록 정렬 추가 및 시간 표시

* feat: 채팅, 모집 공고 링크 파싱 기능 추가

* feat: 인스타그램 파싱 추가

* fix: prettier error

* refactor: 유틸 분리 및 타입 변경

* chore: 타입 변경 반영

* chore: 동일 스타일 통합 및 entity 확장 사용

* chore:ios 토큰 테스트를 위한 로직 추가

* Revert "chore:ios 토큰 테스트를 위한 로직 추가"

This reverts commit 98570fc.
@coderabbitai coderabbitai Bot mentioned this pull request Feb 25, 2026
ff1451 added a commit that referenced this pull request Feb 25, 2026
* chore: 타입 변경 반영

* [feat] 모집 공고에 시간 추가 및 지원서 정렬 추가 (#131)

* feat: 모집 공고에 시작, 종료 시간 추가

* feat: 지원자 목록 정렬 추가 및 시간 표시

* feat: 채팅, 모집 공고 링크 파싱 기능 추가

* feat: 인스타그램 파싱 추가

* fix: prettier error

* refactor: 유틸 분리 및 타입 변경

* chore: 타입 변경 반영

* chore: 동일 스타일 통합 및 entity 확장 사용

* chore:ios 토큰 테스트를 위한 로직 추가

* Revert "chore:ios 토큰 테스트를 위한 로직 추가"

This reverts commit 98570fc.

* hotfix: 모집 공고 textarea 길이 자동 재계산 추가

* Reapply "chore:ios 토큰 테스트를 위한 로직 추가"

This reverts commit 3a56f4a.

* chore: ios토큰

* feat: 가이드 페이지 이미지 변경

* chore: 푸시알림 로직 임시 변경

* refactor: 푸시 알림 토큰 로직 브릿지 방식으로 수정 (#134)

* refactor: 푸시 알림 토큰 로직 브릿지 방식으로 수정

* fix: try catch 적용

* feat: 페이지네이션 추가

* feat: 수정

* feat: 페이지네이션 수정

* feat: 디자인 수정

* feat: 로직 수정

* fix: key 수정

* chore: 스테이지 용 임시 UI 제거

---------

Co-authored-by: hyejun <hyejunkkim228@gmail.com>
ff1451 added a commit that referenced this pull request Mar 6, 2026
* develop → main 배포 (#125)

* feat: 알림설정 구현 (#108)

* chore: min-w 추가

* [chore] 코드래빗 설정 파일 추가 (#110)

* chore: 코드래빗 설정 파일 추가

* chore: 불필요한 tools 설정 제거

* [feat] preMember 목록 분리 및 삭제 기능 추가 (#115)

* feat: preMember 목록 분리 및 삭제 기능 추가

* chore: Pascal case

* chore: 변경된 변수명 반영

* feat: 학번 숫자 필터링 추가

* [feat] 채팅 목차 알림 상태 추가 (#113)

* chore: svg 추가

* feat: 채팅목록 알림 끄기 설정시 UI 추가

* feat: 목차 열릴시 애니메이션 추가

* [feat] 달력 카테고리 분류 추가 (#118)

* feat: 카테고리 추가

* chore: 불필요한 코드 삭제

* [refactor] 관리자 페이지 및 마이페이지 UI 수정 및 리팩토링 (#117)

* feat: 정보 카드에서 정보 페이지로 이동하도록 기능 추가

* feat: 토스트 전역상태 추가

* refactor: query 훅 분리 및 onSuccess 콜백 옵션 제거

* refactor: query 훅 사용처 수정

* refactor: 사용처 수정 2

* fix: 채팅창 스크롤 초기화 문제 수정 및 줄바꿈 기준 변경

* feat: 토글 스위치 구현

* refactor: 모집 공고 관련 목록 페이지 디자인 수정

* feat: 컴포넌트 구현 및 icon 추가

* refactor: z-index 값 수정

* refactor: API 필드 변경 사항 반영

* refactor: 모집 공고 페이지 디자인 수정 및 라우트 백 수정

* refactor: 학교 목록에 없을 시 문구 디자인 수정

* fix: lint error

* fix: 타입 변경

* feat: 모집 관련 페이지 API 추가사항 반영

* refactor: 토스트 타이머 클린업 추라

* refactor: 전역토스트 사용 변경

* refactor: 관리자 클럽 조회 훅 호출 범위 줄이기

* feat: onError handler add

* chore: add button type and remove fragment

* [feat] 문의하기 버튼 로직 추가 (#120)

* feat: API 추가 및 연결

* chore: placeholder 제거

* [fix] 채팅 스크롤 미갱신 버그 및 멤버 직위 렌더링 수정 (#122)

* fix: 채팅 스크롤 미작동 수정

* fix: 한글 출력으로 수정

* feat: 첫 글자 보여주도록 수정 (#124)

* hotfix: 동아리 소개 줄바꿈 적용 및 한 줄 소개 글자수 제한 상향

* chore: 리뷰 반영

* fix: disabled 조건 변경

* 126 fix 배포 전 qa 사항 반영 (#127)

* fix: 가입 버튼 문구 수정

* fix: 검색창 placeholder 수정

* fix: 불필요해진 UI 비활성화

* fix: 모집공고 없을 때 빈 화면 처리

* fix: 무의미한 회비페이지 수정

* feat: 토글 시 자동 페이지 이동 추가

* fix: 페이지 이동 조건 수정

---------

Co-authored-by: 김혜준 <114041848+hyejun0228@users.noreply.github.com>
Co-authored-by: hyejun <hyejunkkim228@gmail.com>

* develop -> main 배포 (#132)

* chore: 타입 변경 반영

* [feat] 모집 공고에 시간 추가 및 지원서 정렬 추가 (#131)

* feat: 모집 공고에 시작, 종료 시간 추가

* feat: 지원자 목록 정렬 추가 및 시간 표시

* feat: 채팅, 모집 공고 링크 파싱 기능 추가

* feat: 인스타그램 파싱 추가

* fix: prettier error

* refactor: 유틸 분리 및 타입 변경

* chore: 타입 변경 반영

* chore: 동일 스타일 통합 및 entity 확장 사용

* chore:ios 토큰 테스트를 위한 로직 추가

* Revert "chore:ios 토큰 테스트를 위한 로직 추가"

This reverts commit 98570fc.

* hotfix: 모집 공고 textarea 길이 자동 재계산 추가

* feat: 페이지네이션 추가

* feat: 수정

* feat: 페이지네이션 수정

* feat: 디자인 수정

* feat: 로직 수정

* fix: key 수정

* develop-> main (#135)

* chore: 타입 변경 반영

* [feat] 모집 공고에 시간 추가 및 지원서 정렬 추가 (#131)

* feat: 모집 공고에 시작, 종료 시간 추가

* feat: 지원자 목록 정렬 추가 및 시간 표시

* feat: 채팅, 모집 공고 링크 파싱 기능 추가

* feat: 인스타그램 파싱 추가

* fix: prettier error

* refactor: 유틸 분리 및 타입 변경

* chore: 타입 변경 반영

* chore: 동일 스타일 통합 및 entity 확장 사용

* chore:ios 토큰 테스트를 위한 로직 추가

* Revert "chore:ios 토큰 테스트를 위한 로직 추가"

This reverts commit 98570fc.

* hotfix: 모집 공고 textarea 길이 자동 재계산 추가

* Reapply "chore:ios 토큰 테스트를 위한 로직 추가"

This reverts commit 3a56f4a.

* chore: ios토큰

* feat: 가이드 페이지 이미지 변경

* chore: 푸시알림 로직 임시 변경

* refactor: 푸시 알림 토큰 로직 브릿지 방식으로 수정 (#134)

* refactor: 푸시 알림 토큰 로직 브릿지 방식으로 수정

* fix: try catch 적용

* feat: 페이지네이션 추가

* feat: 수정

* feat: 페이지네이션 수정

* feat: 디자인 수정

* feat: 로직 수정

* fix: key 수정

* chore: 스테이지 용 임시 UI 제거

---------

Co-authored-by: hyejun <hyejunkkim228@gmail.com>

* [feat] sentry 모니터링 추가 (#141) (#142)

* chore: sentry 의존성 추가

* feat: sentry 세팅

* chore: 워크플로우 수정 및 vite 세팅

* chore: 임시 추가

* refactor: 색상 토큰 파일 분리

* refactor: 타이포그래피 스타일 파일 분리

* refactor: 피그마 타이포그래피 네이밍으로 클래스 정렬

* fix: 리뷰 코멘트 기반 접근성 및 토큰 정리

---------

Co-authored-by: 김혜준 <114041848+hyejun0228@users.noreply.github.com>
Co-authored-by: hyejun <hyejunkkim228@gmail.com>
@ff1451 ff1451 deleted the 130-feat-모집-공고에-시간-추가-및-지원서-정렬-추가 branch April 7, 2026 09:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 모집 공고에 시간 추가 및 지원서 정렬 추가

1 participant