Skip to content

[FEAT] 강사 수정사항 요청 페이지 퍼블리싱#30

Merged
waldls merged 19 commits into
mainfrom
feature/#29-instructor-revision
Jun 8, 2026
Merged

[FEAT] 강사 수정사항 요청 페이지 퍼블리싱#30
waldls merged 19 commits into
mainfrom
feature/#29-instructor-revision

Conversation

@waldls

@waldls waldls commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

📢 PR 유형

어떤 변경 사항이 있었나요?

  • 새로운 기능 추가
  • 버그 수정
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

📌 관련 이슈번호


✅ Key Changes

수정 요청 페이지 구현 (/instructor/revision/[commissionId])

  • commissionId 동적 라우팅으로 강사가 진행 중 외주 홈에서 "확인하기"를 누르면 해당 시안의 수정 요청 페이지로 이동하도록 연결
  • 디자이너 코멘트 유무에 따라 레이아웃이 분기되는 RevisionCategorySection과, 선택한 카테고리별로 수정 방향을 입력하는 RevisionCommentSection 위젯 추가
  • 카테고리는 최대 2개까지 선택 가능하며, 잔여 수정 횟수가 0회면 카테고리 선택을 막고 "수정 횟수를 추가하시겠어요?" 안내가 노출되도록 처리
  • "최종 시안으로 선택하기" 클릭 시 확인 모달을 띄우고, 확인하면 강사 홈으로 이동

시안 확인(원본 이미지) 모달 DraftModal 추가

  • 시안 썸네일의 "자세히 보기" 클릭 시 전체 이미지를 가로 스크롤로 확인할 수 있는 모달 구현
  • 이미지 우클릭 컨텍스트 메뉴 및 드래그 저장을 막아 무단 다운로드를 방지 (onContextMenu, draggable={false})
  • next/image의 fill 사용 시 불필요하게 큰 원본을 불러오지 않도록 sizes 속성 추가

커스텀 드래그 스크롤바 (DragScrollbar, useDragScrollbar)

  • 트랙을 드래그하거나 클릭해 가로 스크롤 위치를 제어할 수 있는 공용 스크롤바 컴포넌트와 훅 신설
  • 스크롤 이벤트와 동기화된 진행률(progress) 계산, 휠 스크롤을 가로 스크롤로 변환하는 로직 포함

공용 컴포넌트 추가

  • Thumbnail: 호버 시 오버레이와 "자세히 보기" 버튼이 노출되는 썸네일 컴포넌트
  • CommentCard: 디자이너 코멘트를 표시하는 카드 컴포넌트
  • Modalshared/ui 하위에서 shared/ui/modal 폴더로 이동하고 import 경로 정리

기타

  • SidebarMenu에 matchPrefix prop을 추가해 /instructor/revision/* 하위 경로에서도 "진행 중 외주" 메뉴가 활성 상태로 표시되도록 처리
  • globals.css에 오버레이 색상(overlay-hover), 블러 토큰(blur-hover, blur-button), radius-24 추가
  • mock 데이터(modifyingStatusData, draftRevisionDetailData, draftFilesData) 정비 및 시안 목업 이미지 추가

📸 스크린샷 or 실행영상

revision.mp4

🎸 기타 사항 or 추가 코멘트

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 강사용 외주 수정 리뷰 인터페이스 추가 - 카테고리별 피드백 및 코멘트 작성 가능
    • 드래그 가능한 스크롤바 추가 - 가로 스크롤 네비게이션 개선
    • 시안 파일 및 썸네일 상세 보기 모달 추가
  • 스타일

    • 디자인 시스템 확장 - 색상, 블러, 반경 토큰 신규 추가

@waldls waldls self-assigned this Jun 8, 2026
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ea79ee4c-51b5-4472-af8d-824ca05273c6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

둘러보기

강사 수정 사항 요청 페이지를 API 스펙 기반으로 완전히 구현합니다. 디자인 토큰 추가, 드래그 가능한 스크롤바 인프라, 이미지 상세 모달, 카테고리 선택 및 코멘트 입력 UI, 그리고 홈 화면에서의 네비게이션 통합을 포함합니다.

변경 사항

강사 수정 요청 기능 구현

레이어 / 파일 설명
디자인 토큰 및 라우팅 기반
src/app/globals.css, src/shared/ui/SidebarMenu.tsx
CSS 커스텀 속성(--color-overlay-hover, --blur-hover, --blur-button, --radius-24)을 @theme에 추가하고, SidebarMenumatchPrefix 필드를 추가해 경로 프리픽스 기반 선택 로직을 지원합니다.
페이지 진입점 및 데이터 모델
src/app/instructor/revision/[commissionId]/page.tsx, src/widgets/instructor/revision/model/revision.ts, src/widgets/instructor/revision/config/revision.ts
수정 요청 페이지를 구현하고, 시안 상세 정보(DraftRevisionDetail)와 파일 목록(DraftFiles) 타입 및 목업 데이터를 제공합니다. 카테고리(REVISION_CATEGORIES) 및 최대 선택 개수(MAX_SELECTABLE_COUNT = 2) 설정을 추가합니다.
드래그 스크롤 인프라
src/shared/lib/hooks/useDragScrollbar.ts, src/shared/ui/DragScrollbar.tsx
가로 스크롤 요소의 진행도 동기화와 마우스 드래그 제어를 처리하는 useDragScrollbar 훅을 구현하고, 스크롤 진행률을 시각적으로 표시하는 DragScrollbar UI 컴포넌트를 제공합니다.
공유 UI 컴포넌트
src/shared/ui/CommentCard.tsx, src/shared/ui/Thumbnail.tsx, src/shared/ui/modal/DraftModal.tsx
디자이너 코멘트를 표시하는 CommentCard, 썸네일 이미지와 상세 버튼을 제공하는 Thumbnail, 그리고 파일을 가로 스크롤로 표시하는 DraftModal을 구현합니다.
수정 요청 기능 위젯
src/widgets/instructor/revision/ui/RevisionCategorySection.tsx, src/widgets/instructor/revision/ui/RevisionCommentSection.tsx, src/widgets/instructor/revision/index.ts
카테고리 토글(최대 2개 선택), 선택된 카테고리별 코멘트 입력, 모달 제어를 처리하는 기능 위젯을 구현하고 인덱스를 통해 내보냅니다.
라우팅 및 상태 연동
src/features/instructor/home/ui/ModifyingCommissionsRow.tsx, src/app/instructor/write/page.tsx, src/features/instructor/home/model/home.ts
진행 중 외주 목록의 "확인하기" 버튼에 수정 요청 페이지로의 네비게이션을 추가하고, Modal 컴포넌트의 import 경로를 업데이트하며, 테스트 데이터를 정정합니다.

연관된 PR

  • Ditda-Official/Ditda-Frontend#21: 강사 외주 작성 페이지(src/app/instructor/write/page.tsx)에서 동일한 Modal 컴포넌트 import 경로를 업데이트합니다.
  • Ditda-Official/Ditda-Frontend#16: 사이드바 메뉴 설정(src/app/instructor/layout.tsx)에서 "진행 중 외주" 항목의 경로 매칭 로직을 변경합니다.

추천 레이블

♻️ Refactor

추천 리뷰어

  • KOJ50

예상 코드 리뷰 노력

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 강사 수정사항 요청 페이지 퍼블리싱이라는 주요 변경사항을 명확하게 설명하고 있습니다.
Linked Issues check ✅ Passed PR이 링크된 이슈 #29의 모든 주요 목표를 충족합니다: 썸네일 이미지, 상세 모달, 조건부 렌더링, 체크박스 기반 카테고리 선택(최대 2개), 잔여 수정 횟수 0일 때 처리가 모두 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 강사 수정사항 요청 페이지 구현과 관련된 범위 내에 있습니다. Modal 이동, 글로벌 스타일 토큰 추가, DragScrollbar 훅 등은 페이지 기능 구현을 위해 필요한 변경입니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#29-instructor-revision

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.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (8)
src/widgets/instructor/revision/model/revision.ts (1)

14-14: ⚡ Quick win

exported 상수의 네이밍 컨벤션을 프로젝트 표준에 맞춰주세요.

프로젝트 learnings에 따르면 exported constants는 SCREAMING_SNAKE_CASE를 사용해야 합니다. 같은 widgets/instructor/revision 폴더의 config/revision.ts에서 REVISION_CATEGORIES, MAX_SELECTABLE_COUNT가 이미 이 컨벤션을 따르고 있으므로, 일관성을 위해 이 파일의 mock data 상수들도 동일한 네이밍을 적용하는 것이 좋습니다.

♻️ 권장하는 네이밍 변경
-export const draftRevisionDetailData: DraftRevisionDetail[] = [
+export const DRAFT_REVISION_DETAIL_DATA: DraftRevisionDetail[] = [
-export const draftFilesData: DraftFiles[] = [
+export const DRAFT_FILES_DATA: DraftFiles[] = [

이 변경을 적용하면 상수를 import하는 모든 곳(src/app/instructor/revision/[commissionId]/page.tsx 등)에서도 동일하게 업데이트해야 합니다.

Based on learnings: "In Ditda-Frontend (TypeScript/Next.js), exported constants should be named in SCREAMING_SNAKE_CASE (e.g., BASIC_INFO_FIELDS, PAGE_OPTIONS)."

Also applies to: 70-70

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/widgets/instructor/revision/model/revision.ts` at line 14, The exported
mock constant draftRevisionDetailData should follow the project's
SCREAMING_SNAKE_CASE convention; rename draftRevisionDetailData to
DRAFT_REVISION_DETAIL_DATA (and update all imports/usages such as in
src/app/instructor/revision/[commissionId]/page.tsx) and ensure the exported
type DraftRevisionDetail references remain unchanged; update any tests or files
importing this constant to use the new name so compilation and imports stay
consistent.

Source: Learnings

src/shared/lib/hooks/useDragScrollbar.ts (1)

19-25: ⚡ Quick win

모달 접근성 개선을 고려해보세요.

현재 구현은 기본적인 키보드 닫기(Escape)와 백드롭 클릭을 지원하지만, 다음 접근성 패턴이 누락되어 있습니다:

  • Focus management: 모달이 열릴 때 첫 번째 포커스 가능한 요소로 focus 이동, 닫힐 때 원래 요소로 복원
  • Focus trap: Tab/Shift+Tab으로 모달 내부에서만 focus 순환

이러한 패턴은 키보드 사용자와 스크린 리더 사용자의 경험을 개선합니다. react-focus-lock이나 @radix-ui/react-dialog 같은 라이브러리를 활용하거나, 직접 구현을 고려해보세요.

Also applies to: 30-36

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/lib/hooks/useDragScrollbar.ts` around lines 19 - 25, Modal lacks
focus management and a focus trap; when opened move focus to the first focusable
element inside the modal (use modalRef or the component that renders the dialog)
and save/restore the previously focused element on close (store
previousActiveElement and restore in the close handler like closeModal or
onClose); implement a focus trap so Tab/Shift+Tab cycles inside the modal (use
react-focus-lock or focus-trap-react around the modal content or implement
keydown handling on modalRef to constrain focus); ensure Escape and
backdrop-click still call the existing closeModal/onClose and that focus is
restored after close.
src/shared/ui/modal/DraftModal.tsx (2)

52-59: 💤 Low value

이미지 다운로드 방지 구현 확인

onContextMenudraggable={false}로 이미지 다운로드를 방지하고 있습니다. 이는 기본적인 우클릭/드래그를 막지만 완벽한 보안 수단은 아닙니다:

  • 개발자 도구, 스크린샷, 브라우저 확장 등으로 여전히 이미지 저장 가능
  • pointer-events-noneselect-none으로 추가 보호

현재 구현이 의도한 사용 사례(일반 사용자의 우연한 저장 방지)에는 적절하지만, 중요한 저작권 보호가 필요하다면 워터마크나 서버 사이드 처리를 고려하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/ui/modal/DraftModal.tsx` around lines 52 - 59, The Image element
in DraftModal.tsx should also prevent the context menu and text selection to
better block casual downloads: add an onContextMenu handler that calls
preventDefault (e.g., onContextMenu={e => e.preventDefault()}) and include the
"select-none" utility in the className (alongside "pointer-events-none
object-cover") on the Image component so the element blocks right-click and
selection in the browser UI.

46-61: ⚡ Quick win

개선 제안: key 속성에 index 사용 지양

Line 48에서 key={${fileUrl}-${index}}로 index를 포함하고 있습니다. 만약 fileUrls 배열의 순서가 변경되거나 항목이 추가/삭제되면 React의 재조정(reconciliation)에 문제가 발생할 수 있습니다.

fileUrl이 고유하다면 index를 제거하고, 고유하지 않다면 각 파일에 고유 ID를 부여하는 것이 좋습니다.

🔑 개선 제안

fileUrl이 고유한 경우:

-           key={`${fileUrl}-${index}`}
+           key={fileUrl}

fileUrl이 중복될 수 있는 경우:

// 데이터 모델에 id 추가
interface DraftFile {
  id: string;
  url: string;
}

// 컴포넌트
fileUrls.map((file) => (
  <div key={file.id} ...>
    <Image src={file.url} ... />
  </div>
))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/ui/modal/DraftModal.tsx` around lines 46 - 61, The mapped elements
in DraftModal use key={`${fileUrl}-${index}`} which can break React
reconciliation when the array order changes; update the mapping to use a stable
unique key instead: if fileUrl is guaranteed unique, use fileUrl alone as the
key for the div inside the fileUrls.map, otherwise change the data model used by
the map to include a stable id (e.g., DraftFile.id) and use that id as the key
for the div and reference the file's url for the Image src; ensure you update
references in the map callback (fileUrls.map, the div key and Image src) to
reflect the new shape.
src/shared/ui/Thumbnail.tsx (2)

28-35: ⚡ Quick win

접근성 개선: 버튼에 명시적인 레이블 추가

"자세히 보기" 버튼의 텍스트가 시각적으로는 표시되지만, 아이콘(SearchIcon)에 대한 스크린리더 처리가 없습니다. 아이콘은 장식 요소이므로 aria-hidden="true"를 추가하는 것이 좋습니다.

♿ 접근성 개선 제안
      <button
        type="button"
        onClick={onDetailClick}
        className="backdrop-blur-button text-body2-m rounded-12 absolute top-1/2 left-1/2 flex -translate-x-1/2 -translate-y-1/2 cursor-pointer flex-row items-center gap-2.5 bg-white/18 px-4 py-2 text-white opacity-0 transition-opacity group-hover:opacity-100"
+       aria-label="썸네일 자세히 보기"
      >
        자세히 보기
-       <SearchIcon className="size-5" />
+       <SearchIcon className="size-5" aria-hidden="true" />
      </button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/ui/Thumbnail.tsx` around lines 28 - 35, The SearchIcon used inside
the "자세히 보기" button is decorative and needs to be hidden from screen readers:
update the SearchIcon element in Thumbnail (the button with onDetailClick) to
include aria-hidden="true"; if the SearchIcon component doesn't accept DOM
props, wrap it in a span with aria-hidden="true" or update the SearchIcon
implementation to forward aria-hidden to the underlying svg so the icon is
ignored by assistive tech while the button text remains the accessible label.

26-26: ⚡ Quick win

반응형 고려: Image sizes 속성 개선

sizes="250px" 고정값은 모든 뷰포트에서 동일한 크기의 이미지를 로드합니다. Thumbnail이 다양한 크기로 사용될 수 있다면 반응형 sizes 값을 고려하세요.

💡 반응형 sizes 예시
-     <Image src={src} alt={alt} fill sizes="250px" loading="eager" className="object-cover" />
+     <Image 
+       src={src} 
+       alt={alt} 
+       fill 
+       sizes="(max-width: 768px) 150px, 250px" 
+       loading="eager" 
+       className="object-cover" 
+     />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/ui/Thumbnail.tsx` at line 26, The fixed sizes="250px" on the Image
component in Thumbnail.tsx forces the same image size across all viewports;
update the Image props (the Image element using src, alt, fill, sizes) to
provide a responsive sizes value (e.g. media-query based like "(max-width:
600px) 100vw, 250px") or compute sizes based on Thumbnail usage so the browser
requests appropriately sized images; locate the Image in Thumbnail.tsx and
replace the hardcoded sizes string with a responsive sizes expression or remove
it and supply explicit width/height variants where appropriate.
src/shared/ui/CommentCard.tsx (2)

9-10: ⚡ Quick win

시맨틱 HTML 개선: 제목 레벨 조정

현재 h1h2를 사용하고 있지만, 이는 페이지의 주제목/부제목이 아니라 카드 내부의 레이블과 텍스트입니다:

  • Line 9: h1은 페이지당 하나만 사용해야 합니다. h3 또는 div가 적절합니다.
  • Line 10: comment 텍스트는 제목이 아니므로 <p> 태그가 더 시맨틱합니다.
🏷️ 시맨틱 개선 제안
  return (
    <div className="bg-purple-5 border-purple-10 rounded-12 flex w-full flex-col gap-2.5 border px-6 py-4">
-     <h1 className="text-main-main text-body1-sb">{title}</h1>
-     <h2 className="text-gray-80 text-body2-r scrollbar-hide h-16.5 overflow-y-auto">{comment}</h2>
+     <h3 className="text-main-main text-body1-sb">{title}</h3>
+     <p className="text-gray-80 text-body2-r scrollbar-hide h-16.5 overflow-y-auto">{comment}</p>
    </div>
  );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/ui/CommentCard.tsx` around lines 9 - 10, In CommentCard.tsx,
replace the semantic misuse of heading tags: change the element rendering title
(currently using <h1> with variable title) to a lower-level heading like <h3> or
a <div> (preserving className "text-main-main text-body1-sb"), and change the
comment text element (currently <h2> rendering variable comment) to a paragraph
<p> (preserving className "text-gray-80 text-body2-r scrollbar-hide h-16.5
overflow-y-auto") so the card content is semantically correct while retaining
existing styling.

10-10: ⚡ Quick win

UX 고려: 스크롤바 숨김 처리

scrollbar-hideoverflow-y-auto를 함께 사용하면 스크롤 가능 여부가 시각적으로 명확하지 않아 사용자가 혼란스러울 수 있습니다. 특히 긴 코멘트의 경우 더 많은 내용이 있다는 것을 알기 어렵습니다.

다음 대안을 고려하세요:

  1. 스크롤바를 표시하되 스타일링 (::-webkit-scrollbar 등)
  2. Fade gradient를 하단에 추가하여 더 많은 콘텐츠가 있음을 시각적으로 표시
  3. "더보기" 버튼 추가
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/ui/CommentCard.tsx` at line 10, The h2 in CommentCard rendering
{comment} uses both "scrollbar-hide" and "overflow-y-auto", which hides visual
affordance for additional content; update the CommentCard component to remove
"scrollbar-hide" and instead implement one of the suggested affordances: (a)
enable visible, styled scrollbars via CSS (target the h2 selector or its class
and add ::-webkit-scrollbar rules), (b) keep overflow-y-auto and add a bottom
fade gradient overlay to the same element to indicate more content, or (c)
replace the fixed-height scrollable view with a collapsible "Show more"/"Show
less" control that toggles a max-height style; ensure changes are applied where
the h2 is defined (the element with className containing "text-gray-80
text-body2-r h-16.5 overflow-y-auto") and keep accessibility in mind (keyboard
focus and aria-expanded for the toggle).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/instructor/revision/`[commissionId]/page.tsx:
- Around line 88-90: The Button currently rendering "수정사항 전달하기" lacks an onClick
handler; add a handler (e.g., handleSubmit or onSubmitRevisions) in the same
component and attach it to the Button so clicks do something, have the handler
first check isSubmitActive, gather the selected category state and comment input
state used in this page, call the existing submission API/function (or create
one like submitRevision) and handle loading/success/error states (disable button
or show spinner while submitting and show feedback on result). Ensure you
reference the Button element and isSubmitActive when wiring the onClick and
implement the handler near the component logic so it can access
selectedCategories/comment state and any API helper.

In `@src/shared/ui/DragScrollbar.tsx`:
- Around line 17-31: The custom scrollbar only supports mouse input; add
keyboard and screen-reader support by making the thumb focusable (add a thumbRef
and tabindex=0 on the thumb element) and giving the track or thumb appropriate
ARIA attributes (e.g., role="scrollbar" or role="slider", aria-valuemin="0",
aria-valuemax="100", aria-valuenow={numericPercent}, and aria-label or
aria-valuetext). Implement a key handler (e.g., handleThumbKeyDown) on the thumb
to respond to ArrowLeft/ArrowRight (and optionally Home/End/PageUp/PageDown) to
update percent and call the same update logic used by handleTrackMouseDown;
ensure percent is kept as a numeric value for aria-valuenow and converted to a
string for style left/width, and ensure trackRef/handleTrackMouseDown logic
updates the thumb position and aria attributes consistently.

In `@src/shared/ui/modal/DraftModal.tsx`:
- Around line 19-25: The useEffect in DraftModal that registers handleKeyDown
currently depends on onClose causing add/remove of the keydown listener every
render if the parent doesn't memoize onClose; fix by memoizing the close handler
in the parent (e.g., create handleCloseDraftModal with useCallback and pass it
as onClose to DraftModal) or, if you prefer the change inside DraftModal,
stabilize the callback by storing onClose in a ref and reading it from the ref
inside handleKeyDown (keep useEffect deps minimal so you don’t repeatedly
remove/add the listener). Ensure you reference the existing
handleKeyDown/useEffect/onClose/DraftModal symbols when applying the change.
- Around line 29-41: Add standard dialog ARIA attributes and focus management to
DraftModal: give the modal container (the inner div that currently has className
"rounded-24 ...") role="dialog", aria-modal="true" and aria-labelledby pointing
to the title element (the h1 rendering {title}), add an aria-label or
aria-labelledby to the CloseCircleIcon button (and ensure it is a focusable
button element that calls onClose), implement a focus trap so Tab/Shift+Tab
cannot escape the modal and move initial focus to the close button (or first
focusable child) when DraftModal mounts, and prevent background scrolling by
toggling document.body.style.overflow when the modal opens/closes; update
mounting/unmounting logic in DraftModal to clean up focus and body-scroll
changes on close.

In `@src/widgets/instructor/revision/ui/RevisionCategorySection.tsx`:
- Around line 42-44: The button shown when remainingRevisionCount === 0 has no
onClick handler, so add an onClick prop to the button in RevisionCategorySection
(the JSX snippet rendering "수정 횟수를 추가하시겠어요?") and wire it to a handler: either
accept a callback prop (e.g., onAddRevisionClick) on the RevisionCategorySection
component and call that, or import/use the navigation/modal-opening function
used elsewhere (e.g., openRevisionModal or router.push) to perform the desired
action; ensure the component's props/type definition is updated to include the
new callback and that the button uses onClick={onAddRevisionClick} (or the
selected handler).

---

Nitpick comments:
In `@src/shared/lib/hooks/useDragScrollbar.ts`:
- Around line 19-25: Modal lacks focus management and a focus trap; when opened
move focus to the first focusable element inside the modal (use modalRef or the
component that renders the dialog) and save/restore the previously focused
element on close (store previousActiveElement and restore in the close handler
like closeModal or onClose); implement a focus trap so Tab/Shift+Tab cycles
inside the modal (use react-focus-lock or focus-trap-react around the modal
content or implement keydown handling on modalRef to constrain focus); ensure
Escape and backdrop-click still call the existing closeModal/onClose and that
focus is restored after close.

In `@src/shared/ui/CommentCard.tsx`:
- Around line 9-10: In CommentCard.tsx, replace the semantic misuse of heading
tags: change the element rendering title (currently using <h1> with variable
title) to a lower-level heading like <h3> or a <div> (preserving className
"text-main-main text-body1-sb"), and change the comment text element (currently
<h2> rendering variable comment) to a paragraph <p> (preserving className
"text-gray-80 text-body2-r scrollbar-hide h-16.5 overflow-y-auto") so the card
content is semantically correct while retaining existing styling.
- Line 10: The h2 in CommentCard rendering {comment} uses both "scrollbar-hide"
and "overflow-y-auto", which hides visual affordance for additional content;
update the CommentCard component to remove "scrollbar-hide" and instead
implement one of the suggested affordances: (a) enable visible, styled
scrollbars via CSS (target the h2 selector or its class and add
::-webkit-scrollbar rules), (b) keep overflow-y-auto and add a bottom fade
gradient overlay to the same element to indicate more content, or (c) replace
the fixed-height scrollable view with a collapsible "Show more"/"Show less"
control that toggles a max-height style; ensure changes are applied where the h2
is defined (the element with className containing "text-gray-80 text-body2-r
h-16.5 overflow-y-auto") and keep accessibility in mind (keyboard focus and
aria-expanded for the toggle).

In `@src/shared/ui/modal/DraftModal.tsx`:
- Around line 52-59: The Image element in DraftModal.tsx should also prevent the
context menu and text selection to better block casual downloads: add an
onContextMenu handler that calls preventDefault (e.g., onContextMenu={e =>
e.preventDefault()}) and include the "select-none" utility in the className
(alongside "pointer-events-none object-cover") on the Image component so the
element blocks right-click and selection in the browser UI.
- Around line 46-61: The mapped elements in DraftModal use
key={`${fileUrl}-${index}`} which can break React reconciliation when the array
order changes; update the mapping to use a stable unique key instead: if fileUrl
is guaranteed unique, use fileUrl alone as the key for the div inside the
fileUrls.map, otherwise change the data model used by the map to include a
stable id (e.g., DraftFile.id) and use that id as the key for the div and
reference the file's url for the Image src; ensure you update references in the
map callback (fileUrls.map, the div key and Image src) to reflect the new shape.

In `@src/shared/ui/Thumbnail.tsx`:
- Around line 28-35: The SearchIcon used inside the "자세히 보기" button is
decorative and needs to be hidden from screen readers: update the SearchIcon
element in Thumbnail (the button with onDetailClick) to include
aria-hidden="true"; if the SearchIcon component doesn't accept DOM props, wrap
it in a span with aria-hidden="true" or update the SearchIcon implementation to
forward aria-hidden to the underlying svg so the icon is ignored by assistive
tech while the button text remains the accessible label.
- Line 26: The fixed sizes="250px" on the Image component in Thumbnail.tsx
forces the same image size across all viewports; update the Image props (the
Image element using src, alt, fill, sizes) to provide a responsive sizes value
(e.g. media-query based like "(max-width: 600px) 100vw, 250px") or compute sizes
based on Thumbnail usage so the browser requests appropriately sized images;
locate the Image in Thumbnail.tsx and replace the hardcoded sizes string with a
responsive sizes expression or remove it and supply explicit width/height
variants where appropriate.

In `@src/widgets/instructor/revision/model/revision.ts`:
- Line 14: The exported mock constant draftRevisionDetailData should follow the
project's SCREAMING_SNAKE_CASE convention; rename draftRevisionDetailData to
DRAFT_REVISION_DETAIL_DATA (and update all imports/usages such as in
src/app/instructor/revision/[commissionId]/page.tsx) and ensure the exported
type DraftRevisionDetail references remain unchanged; update any tests or files
importing this constant to use the new name so compilation and imports stay
consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 74fbd326-9764-47b1-8543-8063d2c9263d

📥 Commits

Reviewing files that changed from the base of the PR and between f257f3e and f1e4e79.

⛔ Files ignored due to path filters (1)
  • public/images/thumbnail_mock.jpg is excluded by !**/*.jpg
📒 Files selected for processing (18)
  • src/app/globals.css
  • src/app/instructor/layout.tsx
  • src/app/instructor/revision/[commissionId]/page.tsx
  • src/app/instructor/write/page.tsx
  • src/features/instructor/home/model/home.ts
  • src/features/instructor/home/ui/ModifyingCommissionsRow.tsx
  • src/shared/lib/hooks/useDragScrollbar.ts
  • src/shared/ui/CommentCard.tsx
  • src/shared/ui/DragScrollbar.tsx
  • src/shared/ui/SidebarMenu.tsx
  • src/shared/ui/Thumbnail.tsx
  • src/shared/ui/modal/DraftModal.tsx
  • src/shared/ui/modal/Modal.tsx
  • src/widgets/instructor/revision/config/revision.ts
  • src/widgets/instructor/revision/index.ts
  • src/widgets/instructor/revision/model/revision.ts
  • src/widgets/instructor/revision/ui/RevisionCategorySection.tsx
  • src/widgets/instructor/revision/ui/RevisionCommentSection.tsx

Comment on lines +88 to +90
<Button className="w-fit" variant={isSubmitActive ? "medium_primary" : "medium_disabled"}>
수정사항 전달하기
</Button>

@coderabbitai coderabbitai Bot Jun 8, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

"수정사항 전달하기" 버튼에 onClick 핸들러가 누락되었습니다.

isSubmitActive 로직으로 버튼 활성화 상태는 제어하고 있지만, 실제로 수정사항을 제출하는 onClick 핸들러가 구현되지 않았습니다. 사용자가 카테고리를 선택하고 코멘트를 입력한 후 이 버튼을 클릭해도 아무 동작이 발생하지 않습니다.

🔧 onClick 핸들러 추가 예시
+  const handleSubmitRevision = () => {
+    // TODO: API 호출로 수정사항 전달
+    console.log('Submit revision:', { selectedCategories, comments });
+  };

   <Button 
     className="w-fit" 
-    variant={isSubmitActive ? "medium_primary" : "medium_disabled"}
+    variant={isSubmitActive ? "medium_primary" : "medium_disabled"}
+    onClick={isSubmitActive ? handleSubmitRevision : undefined}
   >
     수정사항 전달하기
   </Button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/instructor/revision/`[commissionId]/page.tsx around lines 88 - 90,
The Button currently rendering "수정사항 전달하기" lacks an onClick handler; add a
handler (e.g., handleSubmit or onSubmitRevisions) in the same component and
attach it to the Button so clicks do something, have the handler first check
isSubmitActive, gather the selected category state and comment input state used
in this page, call the existing submission API/function (or create one like
submitRevision) and handle loading/success/error states (disable button or show
spinner while submitting and show feedback on result). Ensure you reference the
Button element and isSubmitActive when wiring the onClick and implement the
handler near the component logic so it can access selectedCategories/comment
state and any API helper.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

추후 로직 연결 예정

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@waldls, 이해했습니다. 나중에 API 연결 시 isSubmitActive 조건 체크와 함께 핸들러를 붙여주시면 됩니다.

추후 작업 추적을 위해 GitHub 이슈를 생성해 드릴까요?


🧠 Learnings used
Learnt from: waldls
Repo: Ditda-Official/Ditda-Frontend PR: 28
File: src/widgets/instructor/my/model/my.ts:14-17
Timestamp: 2026-06-07T17:37:21.449Z
Learning: In Ditda-Frontend, `myInfoData` in `src/widgets/instructor/my/model/my.ts` and `commissionHistoryData` in `src/features/instructor/my/model/my.ts` are independent mock/dummy data objects. Their stat fields (e.g., `totalCommissionCount`, `ongoingCommissionCount`) do not need to match the actual length or contents of the commission history mock array — they are placeholder values only.

Learnt from: waldls
Repo: Ditda-Official/Ditda-Frontend PR: 18
File: src/constants/write.ts:65-82
Timestamp: 2026-05-27T08:26:49.054Z
Learning: In Ditda-Frontend (TypeScript/Next.js), exported constants should be named in SCREAMING_SNAKE_CASE (e.g., BASIC_INFO_FIELDS, PAGE_OPTIONS). The camelCase naming guideline should apply to functions and non-exported/regular variables, but not to exported constants.

Comment on lines +17 to +31
return (
<div className={cn("flex h-12 items-center", className)}>
<div
ref={trackRef}
className="bg-purple-10 relative h-2 w-full cursor-pointer"
onMouseDown={e => handleTrackMouseDown(e.clientX)}
>
<div className="bg-purple-30 absolute inset-y-0 left-0" style={{ width: percent }} />
<div
className="rounded-48 border-main-main absolute top-1/2 size-12 -translate-x-1/2 -translate-y-1/2 cursor-grab border-4 bg-white active:cursor-grabbing"
style={{ left: percent }}
/>
</div>
</div>
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

접근성 개선 필요: 키보드 탐색 및 스크린리더 지원

커스텀 스크롤바가 마우스 인터랙션만 지원하고 키보드 접근성과 스크린리더 지원이 없습니다:

  • 스크롤바 트랙과 썸에 적절한 ARIA role이 없습니다 (예: role="scrollbar", aria-valuemin, aria-valuemax, aria-valuenow)
  • 키보드로 포커스하고 조작할 수 없습니다 (화살표 키로 스크롤 조정)
  • 스크린리더 사용자가 현재 스크롤 위치를 알 수 없습니다
♿ 접근성 개선 제안
  return (
    <div className={cn("flex h-12 items-center", className)}>
      <div
        ref={trackRef}
        className="bg-purple-10 relative h-2 w-full cursor-pointer"
        onMouseDown={e => handleTrackMouseDown(e.clientX)}
+       role="scrollbar"
+       aria-orientation="horizontal"
+       aria-valuemin={0}
+       aria-valuemax={100}
+       aria-valuenow={Math.round(progress * 100)}
+       aria-label="수평 스크롤"
+       tabIndex={0}
+       onKeyDown={e => {
+         // 화살표 키로 스크롤 조정 로직 추가
+       }}
      >
        <div className="bg-purple-30 absolute inset-y-0 left-0" style={{ width: percent }} />
        <div
          className="rounded-48 border-main-main absolute top-1/2 size-12 -translate-x-1/2 -translate-y-1/2 cursor-grab border-4 bg-white active:cursor-grabbing"
          style={{ left: percent }}
+         aria-hidden="true"
        />
      </div>
    </div>
  );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/ui/DragScrollbar.tsx` around lines 17 - 31, The custom scrollbar
only supports mouse input; add keyboard and screen-reader support by making the
thumb focusable (add a thumbRef and tabindex=0 on the thumb element) and giving
the track or thumb appropriate ARIA attributes (e.g., role="scrollbar" or
role="slider", aria-valuemin="0", aria-valuemax="100",
aria-valuenow={numericPercent}, and aria-label or aria-valuetext). Implement a
key handler (e.g., handleThumbKeyDown) on the thumb to respond to
ArrowLeft/ArrowRight (and optionally Home/End/PageUp/PageDown) to update percent
and call the same update logic used by handleTrackMouseDown; ensure percent is
kept as a numeric value for aria-valuenow and converted to a string for style
left/width, and ensure trackRef/handleTrackMouseDown logic updates the thumb
position and aria attributes consistently.

Comment on lines +19 to +25
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
if (isOpen) document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [isOpen, onClose]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

잠재적 성능 이슈: useEffect 의존성 배열의 onClose

onClose가 의존성 배열에 포함되어 있는데, 만약 부모 컴포넌트에서 onClose를 메모이제이션하지 않으면 매 렌더마다 이벤트 리스너가 제거/재등록됩니다.

🔧 해결 방법

방법 1: 부모에서 useCallback 사용 (권장)

// RevisionCategorySection.tsx에서
const handleCloseDraftModal = useCallback(() => {
  setIsDraftModalOpen(false);
}, []);

<DraftModal onClose={handleCloseDraftModal} ... />

방법 2: useCallback으로 onClose 래핑

  const DraftModal = ({ isOpen, onClose, title, fileUrls }: DraftModalProps) => {
    const scrollRef = useRef<HTMLDivElement>(null);
+   const handleClose = useCallback(() => onClose(), [onClose]);

    useEffect(() => {
      const handleKeyDown = (e: KeyboardEvent) => {
-       if (e.key === "Escape") onClose();
+       if (e.key === "Escape") handleClose();
      };
-     if (isOpen) document.addEventListener("keydown", handleKeyDown);
+     if (isOpen) document.addEventListener("keydown", handleKeyDown);
      return () => document.removeEventListener("keydown", handleKeyDown);
-   }, [isOpen, onClose]);
+   }, [isOpen, handleClose]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/ui/modal/DraftModal.tsx` around lines 19 - 25, The useEffect in
DraftModal that registers handleKeyDown currently depends on onClose causing
add/remove of the keydown listener every render if the parent doesn't memoize
onClose; fix by memoizing the close handler in the parent (e.g., create
handleCloseDraftModal with useCallback and pass it as onClose to DraftModal) or,
if you prefer the change inside DraftModal, stabilize the callback by storing
onClose in a ref and reading it from the ref inside handleKeyDown (keep
useEffect deps minimal so you don’t repeatedly remove/add the listener). Ensure
you reference the existing handleKeyDown/useEffect/onClose/DraftModal symbols
when applying the change.

Comment on lines +29 to +41
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60"
onClick={onClose}
>
<div
className="rounded-24 flex h-191 w-303.5 flex-col bg-white py-10"
onClick={e => e.stopPropagation()}
>
<div className="flex justify-between px-14 pb-10">
<h1 className="text-heading1-sb flex items-center text-black">{title}</h1>
<CloseCircleIcon className="text-gray-70 size-12 cursor-pointer" onClick={onClose} />
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

접근성 필수 개선: 모달 ARIA 속성 및 포커스 관리

모달 컴포넌트에 필수적인 접근성 기능이 누락되었습니다:

  1. ARIA 속성: role="dialog", aria-modal="true", aria-labelledby 누락
  2. 닫기 버튼 레이블: CloseCircleIcon 버튼에 aria-label 없음
  3. 포커스 트랩: 모달이 열렸을 때 포커스가 모달 내부에 갇혀야 하는데 현재는 Tab 키로 배경 콘텐츠로 이동 가능
  4. 초기 포커스: 모달이 열릴 때 닫기 버튼이나 첫 번째 포커스 가능 요소로 포커스 이동 필요
  5. Body 스크롤 방지: 모달이 열렸을 때 배경 스크롤 방지 필요
♿ 접근성 개선 제안
+ import { useEffect, useRef, useCallback } from "react";
+
  const DraftModal = ({ isOpen, onClose, title, fileUrls }: DraftModalProps) => {
    const scrollRef = useRef<HTMLDivElement>(null);
+   const modalRef = useRef<HTMLDivElement>(null);
+   const closeButtonRef = useRef<HTMLButtonElement>(null);

    useEffect(() => {
      const handleKeyDown = (e: KeyboardEvent) => {
        if (e.key === "Escape") onClose();
      };
      if (isOpen) document.addEventListener("keydown", handleKeyDown);
      return () => document.removeEventListener("keydown", handleKeyDown);
    }, [isOpen, onClose]);

+   // Body 스크롤 방지
+   useEffect(() => {
+     if (isOpen) {
+       document.body.style.overflow = "hidden";
+       return () => {
+         document.body.style.overflow = "";
+       };
+     }
+   }, [isOpen]);
+
+   // 초기 포커스 설정
+   useEffect(() => {
+     if (isOpen && closeButtonRef.current) {
+       closeButtonRef.current.focus();
+     }
+   }, [isOpen]);
+
+   // 포커스 트랩 (간단한 구현 - 라이브러리 사용 권장)
+   const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
+     if (e.key === "Tab" && modalRef.current) {
+       const focusableElements = modalRef.current.querySelectorAll(
+         'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
+       );
+       const firstElement = focusableElements[0] as HTMLElement;
+       const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;
+
+       if (e.shiftKey && document.activeElement === firstElement) {
+         e.preventDefault();
+         lastElement.focus();
+       } else if (!e.shiftKey && document.activeElement === lastElement) {
+         e.preventDefault();
+         firstElement.focus();
+       }
+     }
+   }, []);

    if (!isOpen) return null;

    return (
      <div
        className="fixed inset-0 z-50 flex items-center justify-center bg-black/60"
        onClick={onClose}
+       role="presentation"
      >
        <div
+         ref={modalRef}
          className="rounded-24 flex h-191 w-303.5 flex-col bg-white py-10"
          onClick={e => e.stopPropagation()}
+         role="dialog"
+         aria-modal="true"
+         aria-labelledby="draft-modal-title"
+         onKeyDown={handleKeyDown}
        >
          <div className="flex justify-between px-14 pb-10">
-           <h1 className="text-heading1-sb flex items-center text-black">{title}</h1>
+           <h1 id="draft-modal-title" className="text-heading1-sb flex items-center text-black">{title}</h1>
-           <CloseCircleIcon className="text-gray-70 size-12 cursor-pointer" onClick={onClose} />
+           <button
+             ref={closeButtonRef}
+             type="button"
+             onClick={onClose}
+             aria-label="모달 닫기"
+             className="text-gray-70"
+           >
+             <CloseCircleIcon className="size-12 cursor-pointer" />
+           </button>
          </div>

참고: 프로덕션 환경에서는 focus-trap-react 같은 라이브러리 사용을 권장합니다.

📝 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
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60"
onClick={onClose}
>
<div
className="rounded-24 flex h-191 w-303.5 flex-col bg-white py-10"
onClick={e => e.stopPropagation()}
>
<div className="flex justify-between px-14 pb-10">
<h1 className="text-heading1-sb flex items-center text-black">{title}</h1>
<CloseCircleIcon className="text-gray-70 size-12 cursor-pointer" onClick={onClose} />
</div>
import { useEffect, useRef, useCallback } from "react";
const DraftModal = ({ isOpen, onClose, title, fileUrls }: DraftModalProps) => {
const scrollRef = useRef<HTMLDivElement>(null);
const modalRef = useRef<HTMLDivElement>(null);
const closeButtonRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
if (isOpen) document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [isOpen, onClose]);
// Body 스크롤 방지
useEffect(() => {
if (isOpen) {
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = "";
};
}
}, [isOpen]);
// 초기 포커스 설정
useEffect(() => {
if (isOpen && closeButtonRef.current) {
closeButtonRef.current.focus();
}
}, [isOpen]);
// 포커스 트랩 (간단한 구현 - 라이브러리 사용 권장)
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
if (e.key === "Tab" && modalRef.current) {
const focusableElements = modalRef.current.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0] as HTMLElement;
const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
} else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
}, []);
if (!isOpen) return null;
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60"
onClick={onClose}
role="presentation"
>
<div
ref={modalRef}
className="rounded-24 flex h-191 w-303.5 flex-col bg-white py-10"
onClick={e => e.stopPropagation()}
role="dialog"
aria-modal="true"
aria-labelledby="draft-modal-title"
onKeyDown={handleKeyDown}
>
<div className="flex justify-between px-14 pb-10">
<h1 id="draft-modal-title" className="text-heading1-sb flex items-center text-black">{title}</h1>
<button
ref={closeButtonRef}
type="button"
onClick={onClose}
aria-label="모달 닫기"
className="text-gray-70"
>
<CloseCircleIcon className="size-12 cursor-pointer" />
</button>
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/ui/modal/DraftModal.tsx` around lines 29 - 41, Add standard dialog
ARIA attributes and focus management to DraftModal: give the modal container
(the inner div that currently has className "rounded-24 ...") role="dialog",
aria-modal="true" and aria-labelledby pointing to the title element (the h1
rendering {title}), add an aria-label or aria-labelledby to the CloseCircleIcon
button (and ensure it is a focusable button element that calls onClose),
implement a focus trap so Tab/Shift+Tab cannot escape the modal and move initial
focus to the close button (or first focusable child) when DraftModal mounts, and
prevent background scrolling by toggling document.body.style.overflow when the
modal opens/closes; update mounting/unmounting logic in DraftModal to clean up
focus and body-scroll changes on close.

Comment on lines +42 to +44
<button className="text-gray-80 text-body2-m cursor-pointer underline underline-offset-2">
수정 횟수를 추가하시겠어요?
</button>

@coderabbitai coderabbitai Bot Jun 8, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

onClick 핸들러가 누락되었습니다.

remainingRevisionCount === 0일 때 표시되는 "수정 횟수를 추가하시겠어요?" 버튼에 onClick 핸들러가 없어 클릭해도 아무 동작이 발생하지 않습니다. 이 버튼이 특정 동작(예: 페이지 이동, 모달 열기 등)을 수행해야 한다면 핸들러를 추가해야 합니다.

🔧 onClick 핸들러 추가 예시
-        <button className="text-gray-80 text-body2-m cursor-pointer underline underline-offset-2">
+        <button 
+          onClick={() => {/* TODO: 수정 횟수 추가 로직 */}}
+          className="text-gray-80 text-body2-m cursor-pointer underline underline-offset-2"
+        >
           수정 횟수를 추가하시겠어요?
         </button>
📝 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
<button className="text-gray-80 text-body2-m cursor-pointer underline underline-offset-2">
수정 횟수를 추가하시겠어요?
</button>
<button
onClick={() => {/* TODO: 수정 횟수 추가 로직 */}}
className="text-gray-80 text-body2-m cursor-pointer underline underline-offset-2"
>
수정 횟수를 추가하시겠어요?
</button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/widgets/instructor/revision/ui/RevisionCategorySection.tsx` around lines
42 - 44, The button shown when remainingRevisionCount === 0 has no onClick
handler, so add an onClick prop to the button in RevisionCategorySection (the
JSX snippet rendering "수정 횟수를 추가하시겠어요?") and wire it to a handler: either accept
a callback prop (e.g., onAddRevisionClick) on the RevisionCategorySection
component and call that, or import/use the navigation/modal-opening function
used elsewhere (e.g., openRevisionModal or router.push) to perform the desired
action; ensure the component's props/type definition is updated to include the
new callback and that the button uses onClick={onAddRevisionClick} (or the
selected handler).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

추후 외부 링크 연결 예정

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@waldls, 알겠습니다! 추후 외부 링크 연결 시 onClick 핸들러 및 필요하다면 onAddRevisionClick prop도 함께 추가해 주시면 좋을 것 같습니다.

원하신다면 이 작업을 추적하기 위한 후속 이슈를 생성해 드릴까요?


🧠 Learnings used
Learnt from: waldls
Repo: Ditda-Official/Ditda-Frontend PR: 18
File: src/constants/write.ts:65-82
Timestamp: 2026-05-27T08:26:49.054Z
Learning: In Ditda-Frontend (TypeScript/Next.js), exported constants should be named in SCREAMING_SNAKE_CASE (e.g., BASIC_INFO_FIELDS, PAGE_OPTIONS). The camelCase naming guideline should apply to functions and non-exported/regular variables, but not to exported constants.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

@waldls waldls merged commit 9d3391a into main Jun 8, 2026
2 checks passed
@waldls waldls deleted the feature/#29-instructor-revision branch June 8, 2026 12:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 강사 수정 사항 요청 페이지 퍼블리싱

1 participant