Skip to content

[FEAT] 강사 시안 페이지 퍼블리싱#32

Merged
waldls merged 7 commits into
mainfrom
feature/#31-draft-check
Jun 9, 2026
Merged

[FEAT] 강사 시안 페이지 퍼블리싱#32
waldls merged 7 commits into
mainfrom
feature/#31-draft-check

Conversation

@waldls

@waldls waldls commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

📢 PR 유형

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

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

📌 관련 이슈번호


✅ Key Changes

  • 시안 선택 페이지 구현

    • commissionId 기반으로 목데이터 연결, 동적 타이틀 표시
    • 시안 선택 상태 관리 및 선택 시 제출하기 버튼 활성화
    • 제출하기 버튼 클릭 시 홈(/instructor)으로 라우팅
  • DraftCard컴포넌트

    • 시안 썸네일 및 선택 버튼 렌더링
    • 자세히보기 클릭 시 DraftModal 연결
  • DraftCheckSection 위젯

    • 시안 카드 목록 렌더링 및 페이지네이션 (3개씩 표시)
  • 홈 시안 제출 현황 행

    • 상태에 따른 버튼 조건부 렌더링 및 동적 라우팅 추가
  • index.ts 절대 경로 → 상대 경로 변경 (home, my, write, signup 등)


📸 스크린샷 or 실행영상

default.mp4

🎸 기타 사항 or 추가 코멘트

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 강사용 시안 선택 인터페이스 추가: 제출된 시안들을 페이지별로 조회하고 선택할 수 있는 새로운 페이지 구현
    • 개별 시안 카드에서 상세 이미지 모달 지원
  • Bug Fixes

    • 수정사항 전달 버튼이 검증 완료 상태에서만 활성화되도록 개선
    • 네비게이션 메뉴 활성화 상태 정확성 향상
  • UI Updates

    • 시안 제출 현황 영역 너비 조정

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

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

이 PR은 강사 커미션별 시안 선택 페이지를 새로 구현하고, 선택 흐름을 기존 기능과 통합하며, 모듈의 import 경로를 상대 경로로 정규화합니다. 시안 데이터 모델, 카드·선택 섹션 UI, 페이지 라우트를 추가하고 공유 컴포넌트를 확장합니다.

Changes

시안 선택 페이지 및 컴포넌트 구현

Layer / File(s) Summary
시안 데이터 모델 및 타입 정의
src/features/instructor/choose/model/choose.ts
Draft, CommissionWithDrafts, DraftDetail 타입을 정의하고 커미션별 시안 목록과 상세 정보를 mock 데이터로 제공합니다.
시안 카드 컴포넌트
src/features/instructor/choose/ui/DraftCard.tsx
개별 시안 썸네일과 상세 모달을 렌더링하며, 선택 상태와 onSelect 콜백으로 상위 컴포넌트와 통신합니다.
공유 UI 컴포넌트 확장
src/shared/ui/Button.tsx, src/shared/ui/SidebarMenu.tsx, src/shared/ui/Thumbnail.tsx
Button에 "choose" variant를 추가하고, SidebarMenu의 matchPrefix를 배열 지원으로 확장하며, Thumbnail의 텍스트 줄바꿈을 처리합니다.
시안 선택 섹션
src/widgets/instructor/choose/config/choose.ts, src/widgets/instructor/choose/ui/DraftCheckSection.tsx
시안 목록을 DRAFT_CARDS_PER_PAGE 단위로 페이지네이션하고, DraftCard를 그리드로 렌더링하며 이전/다음 네비게이션을 제공합니다.
시안 선택 페이지 라우트
src/app/instructor/choose/[commissionId]/page.tsx
[commissionId] 동적 라우트에서 params를 해석하여 커미션을 조회하고, DraftCheckSection에 선택 상태를 연결하며 버튼 활성화를 조건부로 제어합니다.
시안 선택 기능 모듈 노출
src/features/instructor/choose/index.ts
choose feature의 타입, mock 데이터, DraftCard를 재-export하여 다른 모듈의 import를 단순화합니다.
기존 기능 통합
src/app/instructor/layout.tsx, src/features/instructor/home/ui/DraftSubmissionStatusRow.tsx, src/features/instructor/home/model/home.ts
DraftSubmissionStatusRow에서 제출 완료 시 시안 선택 페이지로 이동하고, SidebarLayout에서 /instructor/choose를 메뉴 활성화 대상에 포함시킵니다.
수정사항 제출 버튼 로직 정리
src/app/instructor/revision/[commissionId]/page.tsx
최종 시안 제출 버튼이 isSubmitActive 상태에 따라 활성/비활성 variant를 선택하고 조건부로 라우팅합니다.

모듈 import 경로 정규화

Layer / File(s) Summary
instructor/home, instructor/write 경로 정규화
src/features/instructor/home/index.ts, src/features/instructor/write/index.ts
home 및 write feature의 모든 재-export를 절대 경로에서 상대 경로로 변경합니다.
instructor/my, signup 경로 정규화
src/features/instructor/my/index.ts, src/features/signup/index.ts
my feature와 signup feature의 모든 재-export를 절대 경로에서 상대 경로로 변경합니다.
widget 레이어 경로 정규화
src/widgets/instructor/home/index.ts, src/widgets/instructor/my/index.ts, src/widgets/instructor/revision/index.ts, src/widgets/instructor/write/index.ts
instructor 폴더 아래 모든 widget의 재-export를 절대 경로에서 상대 경로로 변경합니다.
DraftSubmissionStatusSection 레이아웃 조정
src/widgets/instructor/home/ui/DraftSubmissionStatusSection.tsx
CommissionsHeader의 rightClassName을 w-53에서 w-85.75로 확장하여 헤더 영역 너비를 조정합니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Ditda-Official/Ditda-Frontend#30: 두 PR 모두 src/app/instructor/revision/[commissionId]/page.tsx의 버튼 활성화·라우팅 로직을 수정하며 선택/입력 상태 기반의 조건부 동작을 다룹니다.
  • Ditda-Official/Ditda-Frontend#28: 메인 PR은 src/features/instructor/my/index.tssrc/widgets/instructor/my/index.ts의 index re-export 매핑을 수정하며, 리트리브드 PR도 동일 파일의 export 구성을 다루므로 모듈 구조 변경 범위가 겹칩니다.

Suggested reviewers

  • KOJ50
🚥 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 연결된 이슈 #31의 요구사항인 Draft 카드 구현, 시안 모달 연결, API SPEC 기반 퍼블리싱이 모두 충족되었습니다.
Out of Scope Changes check ✅ Passed 절대 경로를 상대 경로로 변경하는 작업은 코드베이스 정리이며, 핵심 기능 구현(Draft 페이지, DraftCard, DraftCheckSection)과 함께 포함되어 있어 관련성이 있습니다.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#31-draft-check

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 9, 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: 1

🧹 Nitpick comments (3)
src/shared/ui/SidebarMenu.tsx (1)

26-38: ⚡ Quick win

조건부 Tailwind 클래스 조합을 cn()으로 통일해주세요.

현재는 템플릿 문자열과 문자열 덧셈이 혼용되어 있어 조건이 늘어날 때 유지보수가 어려워집니다. cn()으로 통일하면 선택/호버 상태 클래스 관리가 더 안전해집니다.

제안 diff
-  const content = (
+  const content = (
     <div
-      className={`flex flex-row items-center gap-2 ${isSelected ? "text-gray-90" : "text-gray-80"}`}
+      className={cn("flex flex-row items-center gap-2", isSelected ? "text-gray-90" : "text-gray-80")}
     >
@@
-  const className =
-    "bg-gray-5 rounded-8 hover:bg-gray-20 group w-58 cursor-pointer px-5 py-3 block transition-colors duration-150" +
-    (isSelected ? " bg-gray-20" : "");
+  const className = cn(
+    "bg-gray-5 rounded-8 hover:bg-gray-20 group w-58 cursor-pointer px-5 py-3 block transition-colors duration-150",
+    isSelected && "bg-gray-20",
+  );

As per coding guidelines "TailwindCSS - 조건부 클래스 문자열을 직접 이어붙이는 경우 cn() 유틸리티 사용을 제안해주세요."

🤖 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/SidebarMenu.tsx` around lines 26 - 38, The component mixes
template strings and string concatenation for Tailwind classes (the inline div's
className and the outer const className), so replace both with the cn() utility
to compose conditional classes: import cn if missing, use cn(...) to combine
base classes and conditional ones driven by isSelected/isActive (preserve
group-hover and hover classes and the size class on icons), and update the JSX
to pass cn(...) as the className for the element and to set the const className
via cn(...) instead of string concatenation.

Source: Coding guidelines

src/widgets/instructor/choose/ui/DraftCheckSection.tsx (2)

35-38: ⚡ Quick win

조건부 클래스 문자열에 cn() 유틸리티 사용을 권장합니다.

템플릿 리터럴로 조건부 클래스를 직접 이어붙이는 대신 cn() 유틸리티를 사용하면 더 안전하고 읽기 쉬운 코드가 됩니다.

♻️ 제안하는 수정
+import { cn } from "`@/shared/lib/utils`";
+
 ...
           <NextButton
-            className={`size-12 ${page < totalPages - 1 ? "cursor-pointer" : "cursor-default opacity-30"}`}
+            className={cn(
+              "size-12",
+              page < totalPages - 1 ? "cursor-pointer" : "cursor-default opacity-30"
+            )}
             onClick={() => setPage(p => Math.min(totalPages - 1, p + 1))}
           />

As per coding guidelines: "조건부 클래스 문자열을 직접 이어붙이는 경우 cn() 유틸리티 사용을 제안해주세요."

🤖 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/choose/ui/DraftCheckSection.tsx` around lines 35 - 38,
The NextButton component is being given a conditional className via a template
literal; replace that with the cn() utility to produce a safer, clearer class
string. Update the className on NextButton to call cn('size-12', {
'cursor-pointer': page < totalPages - 1, 'cursor-default opacity-30': page >=
totalPages - 1 }) (and ensure cn is imported where DraftCheckSection.tsx uses
NextButton), leaving the onClick handler (setPage(...)) unchanged.

Source: Coding guidelines


31-34: ⚡ Quick win

조건부 클래스 문자열에 cn() 유틸리티 사용을 권장합니다.

템플릿 리터럴로 조건부 클래스를 직접 이어붙이는 대신 cn() 유틸리티를 사용하면 더 안전하고 읽기 쉬운 코드가 됩니다.

♻️ 제안하는 수정
+import { cn } from "`@/shared/lib/utils`";
+
 ...
           <PrevButton
-            className={`size-12 ${page > 0 ? "cursor-pointer" : "cursor-default opacity-30"}`}
+            className={cn(
+              "size-12",
+              page > 0 ? "cursor-pointer" : "cursor-default opacity-30"
+            )}
             onClick={() => setPage(p => Math.max(0, p - 1))}
           />

As per coding guidelines: "조건부 클래스 문자열을 직접 이어붙이는 경우 cn() 유틸리티 사용을 제안해주세요."

🤖 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/choose/ui/DraftCheckSection.tsx` around lines 31 - 34,
Replace the inline template literal used for PrevButton's className with the
cn() utility to build conditional classes: find the PrevButton JSX that uses
className={`size-12 ${page > 0 ? "cursor-pointer" : "cursor-default
opacity-30"}`} and change it to call cn("size-12", { "cursor-pointer": page > 0,
"cursor-default opacity-30": page <= 0 }) so class logic is clearer and safer
(ensure cn is imported where DraftCheckSection.tsx defines PrevButton and
setPage/page are still used as-is).

Source: Coding guidelines

🤖 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/features/instructor/choose/model/choose.ts`:
- Around line 13-35: The exported constant names commissionDraftsData (and the
related exported draftDetailsData) violate the SCREAMING_SNAKE_CASE rule; rename
them to COMMISSION_DRAFTS_DATA and DRAFT_DETAILS_DATA respectively, update all
internal references and exports in the defining module (e.g., change the export
identifiers in choose.ts) and then update every import site that consumes these
symbols (notably index.ts, ui/DraftCard.tsx, and
app/instructor/choose/[commissionId]/page.tsx) so imports use the new names.

---

Nitpick comments:
In `@src/shared/ui/SidebarMenu.tsx`:
- Around line 26-38: The component mixes template strings and string
concatenation for Tailwind classes (the inline div's className and the outer
const className), so replace both with the cn() utility to compose conditional
classes: import cn if missing, use cn(...) to combine base classes and
conditional ones driven by isSelected/isActive (preserve group-hover and hover
classes and the size class on icons), and update the JSX to pass cn(...) as the
className for the element and to set the const className via cn(...) instead of
string concatenation.

In `@src/widgets/instructor/choose/ui/DraftCheckSection.tsx`:
- Around line 35-38: The NextButton component is being given a conditional
className via a template literal; replace that with the cn() utility to produce
a safer, clearer class string. Update the className on NextButton to call
cn('size-12', { 'cursor-pointer': page < totalPages - 1, 'cursor-default
opacity-30': page >= totalPages - 1 }) (and ensure cn is imported where
DraftCheckSection.tsx uses NextButton), leaving the onClick handler
(setPage(...)) unchanged.
- Around line 31-34: Replace the inline template literal used for PrevButton's
className with the cn() utility to build conditional classes: find the
PrevButton JSX that uses className={`size-12 ${page > 0 ? "cursor-pointer" :
"cursor-default opacity-30"}`} and change it to call cn("size-12", {
"cursor-pointer": page > 0, "cursor-default opacity-30": page <= 0 }) so class
logic is clearer and safer (ensure cn is imported where DraftCheckSection.tsx
defines PrevButton and setPage/page are still used as-is).
🪄 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: 4c6f724b-0b82-4293-af29-ee7b9dcaa26b

📥 Commits

Reviewing files that changed from the base of the PR and between 9d3391a and e30f198.

📒 Files selected for processing (23)
  • src/app/instructor/choose/[commissionId]/page.tsx
  • src/app/instructor/layout.tsx
  • src/app/instructor/revision/[commissionId]/page.tsx
  • src/features/instructor/choose/index.ts
  • src/features/instructor/choose/model/choose.ts
  • src/features/instructor/choose/ui/DraftCard.tsx
  • src/features/instructor/home/index.ts
  • src/features/instructor/home/model/home.ts
  • src/features/instructor/home/ui/DraftSubmissionStatusRow.tsx
  • src/features/instructor/my/index.ts
  • src/features/instructor/write/index.ts
  • src/features/signup/index.ts
  • src/shared/ui/Button.tsx
  • src/shared/ui/SidebarMenu.tsx
  • src/shared/ui/Thumbnail.tsx
  • src/widgets/instructor/choose/config/choose.ts
  • src/widgets/instructor/choose/index.ts
  • src/widgets/instructor/choose/ui/DraftCheckSection.tsx
  • src/widgets/instructor/home/index.ts
  • src/widgets/instructor/home/ui/DraftSubmissionStatusSection.tsx
  • src/widgets/instructor/my/index.ts
  • src/widgets/instructor/revision/index.ts
  • src/widgets/instructor/write/index.ts

Comment on lines +13 to +35
export const commissionDraftsData: CommissionWithDrafts[] = [
{
commissionId: 11,
title: "해커스톡 왕초보 영어 - 기초 문법편",
drafts: [
{ draftId: 41, thumbnailUrl: "/images/thumbnail_mock.jpg" },
{ draftId: 42, thumbnailUrl: "/images/thumbnail_mock.jpg" },
{ draftId: 43, thumbnailUrl: "/images/thumbnail_mock.jpg" },
{ draftId: 44, thumbnailUrl: "/images/thumbnail_mock.jpg" },
],
},
{
commissionId: 15,
title: "고등 국어 문학 - 현대시 집중",
drafts: [
{ draftId: 51, thumbnailUrl: "/images/thumbnail_mock.jpg" },
{ draftId: 52, thumbnailUrl: "/images/thumbnail_mock.jpg" },
{ draftId: 53, thumbnailUrl: "/images/thumbnail_mock.jpg" },
{ draftId: 54, thumbnailUrl: "/images/thumbnail_mock.jpg" },
{ draftId: 55, thumbnailUrl: "/images/thumbnail_mock.jpg" },
],
},
];

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 | ⚡ Quick win

exported 상수는 SCREAMING_SNAKE_CASE 명명 규칙을 따라야 합니다.

commissionDraftsDatadraftDetailsData는 exported 상수이므로 프로젝트의 명명 규칙에 따라 COMMISSION_DRAFTS_DATADRAFT_DETAILS_DATA로 작성해야 합니다.

♻️ 제안하는 수정
-export const commissionDraftsData: CommissionWithDrafts[] = [
+export const COMMISSION_DRAFTS_DATA: CommissionWithDrafts[] = [
   {
     commissionId: 11,
-export const draftDetailsData: Record<number, DraftDetail> = {
+export const DRAFT_DETAILS_DATA: Record<number, DraftDetail> = {
     commissionId: 11,

이 변경을 적용하는 경우, 해당 상수를 import하는 모든 파일도 함께 수정해야 합니다:

  • src/features/instructor/choose/index.ts
  • src/features/instructor/choose/ui/DraftCard.tsx
  • src/app/instructor/choose/[commissionId]/page.tsx

Based on learnings: "exported constants should be named in SCREAMING_SNAKE_CASE (e.g., BASIC_INFO_FIELDS, PAGE_OPTIONS)."

Also applies to: 44-144

🤖 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/features/instructor/choose/model/choose.ts` around lines 13 - 35, The
exported constant names commissionDraftsData (and the related exported
draftDetailsData) violate the SCREAMING_SNAKE_CASE rule; rename them to
COMMISSION_DRAFTS_DATA and DRAFT_DETAILS_DATA respectively, update all internal
references and exports in the defining module (e.g., change the export
identifiers in choose.ts) and then update every import site that consumes these
symbols (notably index.ts, ui/DraftCard.tsx, and
app/instructor/choose/[commissionId]/page.tsx) so imports use the new names.

Source: Learnings

@waldls waldls merged commit 065aab9 into main Jun 9, 2026
2 checks passed
@waldls waldls deleted the feature/#31-draft-check branch June 9, 2026 17:42
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