[FEAT] 강사 대시보드 퍼블리싱 및 새 외주 작성 수정사항 반영#24
Conversation
📝 Walkthrough전체 변경 사항이 PR은 강사용 대시보드 페이지를 새로 구현하고, 외주 작성 폼의 상태 스키마를 업데이트하며, 공통 UI 컴포넌트를 확장합니다. 라우팅을 정리하고 폼 검증 조건을 완화합니다. 변경 사항강사 대시보드 페이지
공통 UI 컴포넌트 확장
외주 작성 폼 리팩터링
🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
src/components/instructor/home/MatchingCommissionsRow.tsx (1)
11-11:⚠️ Potential issue | 🟠 Major | ⚡ Quick winTailwind 클래스 문법을 확인해주세요.
DraftSubmissionStatusRow와 동일하게border-b-gray-10문법이 사용되었습니다. 표준 Tailwind에서는border-b border-gray-10과 같이 방향과 색상을 분리합니다. 커스텀 유틸리티인지 확인이 필요합니다.🔧 표준 Tailwind 문법으로 수정 제안
- <div className="border-b-gray-10 hover:bg-gray-5 flex h-15 cursor-pointer items-center border-b py-3 transition-colors duration-150"> + <div className="border-b border-gray-10 hover:bg-gray-5 flex h-15 cursor-pointer items-center py-3 transition-colors duration-150">🤖 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/components/instructor/home/MatchingCommissionsRow.tsx` at line 11, The Tailwind class "border-b-gray-10" in the MatchingCommissionsRow component is likely invalid; update the class string on the root div in MatchingCommissionsRow.tsx to use standard Tailwind utility separation (add "border-b" and keep the color as "border-gray-10") unless "border-b-gray-10" is a defined custom utility; also check and align the same pattern with DraftSubmissionStatusRow to keep consistency across components.
🧹 Nitpick comments (8)
src/components/common/Tag.tsx (1)
14-22: ⚡ Quick win조건부 클래스 조합 시
cn()유틸리티 사용 권장.템플릿 리터럴로 클래스를 직접 이어붙이고 있습니다. 가독성과 안전성을 위해
cn()유틸리티 함수 사용을 고려해보세요.또한
h-6.5같은 소수점 값이 Tailwind 기본 스케일에 포함되어 있는지 확인이 필요합니다. 작동하지 않는 경우h-[1.625rem]또는h-[26px]형태로 명시해야 할 수 있습니다.♻️ cn() 유틸 사용 예시
+import { cn } from "`@/lib/utils`"; + const Tag = ({ variant, label }: TagProps) => { return ( <div - className={`rounded-4 text-body2-sb flex h-6.5 w-11 items-center justify-center ${tagStyleMap[variant]}`} + className={cn( + "rounded-4 text-body2-sb flex h-6.5 w-11 items-center justify-center", + tagStyleMap[variant] + )} > {label} </div>코딩 가이드라인에서 조건부 클래스 문자열을 직접 이어붙이는 경우 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/components/common/Tag.tsx` around lines 14 - 22, The Tag component is concatenating classes with a template literal; switch it to use the cn() utility (importing it where Tag is defined) to combine the static classes and the dynamic class from tagStyleMap[variant] (e.g., cn('rounded-4 text-body2-sb flex items-center justify-center', tagStyleMap[variant], ...)). Also verify and replace the non-standard Tailwind class h-6.5 with a bracketed value like h-[1.625rem] or h-[26px] if your Tailwind scale doesn't include 6.5. Update the Tag component and ensure tagStyleMap usage and cn import are correct.src/components/instructor/write/PlanChooseCard.tsx (1)
17-21: 💤 Low value조건부 클래스에
cn()유틸리티 사용 권장코딩 가이드라인에 따라 조건부 클래스 문자열을 직접 이어붙이는 경우
cn()유틸리티 사용을 권장합니다.♻️ cn() 사용 제안
+import { cn } from "`@/lib/utils/cn`"; + const PlanChooseCard = ({ plan, isSelected = false, onClick }: PlanChooseCardProps) => { const { label, size, price, description } = PLAN_MAP[plan]; return ( <div onClick={onClick} tabIndex={0} - className={`rounded-14 w-full cursor-pointer border p-6 transition-colors duration-150 outline-none ${ - isSelected - ? "border-main-main bg-purple-5" - : "border-gray-40 hover:bg-gray-10 bg-white hover:border-transparent" - }`} + className={cn( + "rounded-14 w-full cursor-pointer border p-6 transition-colors duration-150 outline-none", + isSelected + ? "border-main-main bg-purple-5" + : "border-gray-40 hover:bg-gray-10 bg-white hover:border-transparent" + )} >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/components/instructor/write/PlanChooseCard.tsx` around lines 17 - 21, Replace the inline conditional template literal used for className in PlanChooseCard (the expression that switches on isSelected to apply "border-main-main bg-purple-5" vs "border-gray-40 hover:bg-gray-10 bg-white hover:border-transparent") with the cn() utility; import cn if it's not already imported and build the classes by passing the static classes ("rounded-14 w-full cursor-pointer border p-6 transition-colors duration-150 outline-none") plus an object or conditional arguments that apply the selected classes when isSelected is true and the alternate classes when false so behavior remains identical.src/components/instructor/write/SizeRecommendedCard.tsx (1)
24-24: 💤 Low value조건부 클래스에
cn()유틸리티 사용 권장코딩 가이드라인에 따라 조건부 클래스 문자열을 직접 이어붙이는 경우
cn()유틸리티 사용을 권장합니다.♻️ cn() 사용 제안
+import { cn } from "`@/lib/utils/cn`"; + const SizeRecommendedCard = ({ ... }: SizeRecommendedCardProps) => { return ( <div onClick={onClick} tabIndex={0} - className={`rounded-8 w-49.25 cursor-pointer border px-4 py-3 transition-colors duration-150 ${isSelected ? "border-main-main bg-purple-10" : "border-gray-20 hover:bg-gray-10 bg-white"}`} + className={cn( + "rounded-8 w-49.25 cursor-pointer border px-4 py-3 transition-colors duration-150", + isSelected ? "border-main-main bg-purple-10" : "border-gray-20 hover:bg-gray-10 bg-white" + )} >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/components/instructor/write/SizeRecommendedCard.tsx` at line 24, The JSX className string in SizeRecommendedCard (the template using isSelected: className={`rounded-8 w-49.25 cursor-pointer border px-4 py-3 transition-colors duration-150 ${isSelected ? "border-main-main bg-purple-10" : "border-gray-20 hover:bg-gray-10 bg-white"}`}) should be refactored to use the cn() utility; replace the inline conditional string concatenation with a cn(...) call that always includes the static classes and conditionally includes "border-main-main bg-purple-10" when isSelected is true and "border-gray-20 hover:bg-gray-10 bg-white" when false, referencing the SizeRecommendedCard component and the isSelected prop to locate the code.src/store/writeFormStore.ts (1)
133-140: 💤 Low value필수 값(null) → 빈 문자열 폴백은 UI 경로에서 전송 위험이 낮음
src/store/writeFormStore.ts의buildOrderRequest는selectedPlan/firstDate/finalDate가null이면 각각""로 매핑합니다. 다만src/containers/instructor/write/Steps/Step3Content.tsx에서결제하기버튼은selectedPlan !== null && firstDate !== null && finalDate !== null일 때만 활성화되므로, 해당 경로에서는 빈 문자열이 실제 요청 payload로 이어질 가능성이 낮습니다. 그래도buildOrderRequest가 상태 변경마다sessionStorage에 동기화되기 때문에, 이후 이 값을 소비/전송에 사용한다면""대신 필드 미포함/런타임 가드로 스키마 안정성을 확보하는 개선을 권장합니다.🤖 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/store/writeFormStore.ts` around lines 133 - 140, buildOrderRequest currently maps nullable fields selectedPlan/firstDate/finalDate to empty strings which can produce invalid payloads when persisted to sessionStorage; change buildOrderRequest so that plan and dates entries are omitted (or set to undefined) when selectedPlan, firstDate or finalDate are null instead of mapping to "", e.g. only include plan when selectedPlan != null (using PLAN_API_MAP[selectedPlan]), only push date objects when firstDate/finalDate != null (using toApiDate), and preserve term as-is; update any sessionStorage sync logic that assumes those keys always exist to handle missing fields safely.src/data/instructor/home.ts (1)
17-17: ⚡ Quick win내보내기 상수는 SCREAMING_SNAKE_CASE를 사용해야 합니다.
draftSubmissionStatusData,matchingStatusData,modifyingStatusData는 내보내기된 상수이므로DRAFT_SUBMISSION_STATUS_DATA,MATCHING_STATUS_DATA,MODIFYING_STATUS_DATA로 명명되어야 합니다.♻️ 제안하는 수정
-export const draftSubmissionStatusData: DraftSubmissionItem[] = [ +export const DRAFT_SUBMISSION_STATUS_DATA: DraftSubmissionItem[] = [-export const matchingStatusData: MatchingItem[] = [ +export const MATCHING_STATUS_DATA: MatchingItem[] = [-export const modifyingStatusData: ModifyingItem[] = [ +export const MODIFYING_STATUS_DATA: ModifyingItem[] = [사용하는 곳에서도 임포트명을 함께 변경해야 합니다.
Based on learnings: 프로젝트에서 내보내기된 상수는 SCREAMING_SNAKE_CASE (예: BASIC_INFO_FIELDS, PAGE_OPTIONS)를 사용하며, camelCase 명명 가이드라인은 함수와 내보내지 않는 일반 변수에 적용됩니다.
Also applies to: 66-66, 108-108
🤖 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/data/instructor/home.ts` at line 17, Exported constant names draftSubmissionStatusData, matchingStatusData, and modifyingStatusData use camelCase but must follow SCREAMING_SNAKE_CASE; rename them to DRAFT_SUBMISSION_STATUS_DATA, MATCHING_STATUS_DATA, and MODIFYING_STATUS_DATA in their declarations (e.g., the exported arrays in the file) and update all import sites to use the new names so references to these symbols (draftSubmissionStatusData, matchingStatusData, modifyingStatusData) are replaced with DRAFT_SUBMISSION_STATUS_DATA, MATCHING_STATUS_DATA, and MODIFYING_STATUS_DATA respectively.src/components/instructor/home/DraftSubmissionStatusRow.tsx (1)
24-29: ⚡ Quick win제출 개수 유효성 검증 추가를 고려해보세요.
total - submitted가 음수가 될 경우Array.from에서 문제가 발생할 수 있습니다. API 데이터의 무결성이 보장되지 않는다면 방어 로직을 추가하는 것이 좋습니다.🛡️ 방어 로직 추가 제안
const DraftSubmissionStatusRow = ({ item }: { item: DraftSubmissionItem }) => { const { title, category, draftSubmission, firstDraftDeadline } = item; const { submitted, total } = draftSubmission; + const validSubmitted = Math.min(Math.max(0, submitted), total); + const remaining = total - validSubmitted; const categoryLabel = CATEGORY_DISPLAY_MAP[category] ?? category; return ( ... <div className="flex flex-row"> - {Array.from({ length: submitted }).map((_, i) => ( + {Array.from({ length: validSubmitted }).map((_, i) => ( <MatchingOnIcon key={i} className="size-8" /> ))} - {Array.from({ length: total - submitted }).map((_, i) => ( + {Array.from({ length: remaining }).map((_, i) => ( <MatchingOffIcon key={i} className="size-8" /> ))} </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/components/instructor/home/DraftSubmissionStatusRow.tsx` around lines 24 - 29, The rendering can break if total - submitted is negative; update DraftSubmissionStatusRow to sanitize counts by computing a safeSubmitted = Math.max(0, Math.min(submitted, total)) and safeRemaining = Math.max(0, total - safeSubmitted) (or equivalent clamping) before mapping to icons (refer to MatchingOnIcon and MatchingOffIcon render blocks), then use those safe values in the Array.from length arguments so neither map gets a negative length.src/components/common/PageIndicator.tsx (1)
12-12: 💤 Low value조건부 클래스 결합 시
cn()유틸리티 사용 권장템플릿 리터럴로 조건부 클래스를 직접 이어붙이고 있습니다. 일관성과 가독성을 위해
cn()유틸리티를 사용하는 것을 권장합니다.♻️ 제안 수정안
+import { cn } from "`@/lib/utils/cn`"; + interface PageIndicatorProps { total: number; current: number; } const PageIndicator = ({ total, current }: PageIndicatorProps) => { return ( <div className="flex items-center gap-2"> {Array.from({ length: total }, (_, i) => ( <div key={i} - className={`size-2 rounded-full transition-colors duration-300 ${i === current ? "bg-gray-700" : "bg-gray-300"}`} + className={cn( + "size-2 rounded-full transition-colors duration-300", + i === current ? "bg-gray-700" : "bg-gray-300" + )} /> ))} </div> ); };코딩 가이드라인에 따르면, 조건부 클래스 문자열을 직접 이어붙이는 경우 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/components/common/PageIndicator.tsx` at line 12, Replace the template-literal conditional class construction in the PageIndicator component (the className currently using `${i === current ? "bg-gray-700" : "bg-gray-300"}`) with the project's cn() utility: import cn if missing and call cn("size-2 rounded-full transition-colors duration-300", { "bg-gray-700": i === current, "bg-gray-300": i !== current }) so the conditional classes are composed via cn() rather than string interpolation; update the className prop on the element that uses variables i and current accordingly.src/containers/instructor/home/ModifyingCommissionsSection.tsx (1)
34-38: ⚡ Quick win페이지네이션 버튼에
disabled상태 추가 권장현재 페이지가 첫 페이지 또는 마지막 페이지일 때도 이전/다음 버튼이 활성 상태로 표시됩니다. 훅 내부에서 경계 처리는 되지만, 시각적 피드백이 없어 사용자가 버튼을 클릭해도 변화가 없으면 혼란을 겪을 수 있습니다.
♻️ 제안 수정안
<div className="flex flex-row justify-between"> - <PrevButton className="size-12 cursor-pointer" onClick={handlePrev} /> + <PrevButton + className={cn("size-12", current > 0 ? "cursor-pointer" : "cursor-not-allowed opacity-40")} + onClick={current > 0 ? handlePrev : undefined} + /> <PageIndicator total={totalPages} current={current} /> - <NextButton className="size-12 cursor-pointer" onClick={handleNext} /> + <NextButton + className={cn("size-12", current < totalPages - 1 ? "cursor-pointer" : "cursor-not-allowed opacity-40")} + onClick={current < totalPages - 1 ? handleNext : undefined} + /> </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/containers/instructor/home/ModifyingCommissionsSection.tsx` around lines 34 - 38, The Prev/Next buttons are always active visually; update the PrevButton and NextButton JSX to pass a disabled prop when at the list boundaries (use current === 1 for PrevButton and current === totalPages for NextButton) and adjust their className or aria-disabled so they visually appear disabled (e.g., reduced opacity and pointer-events disabled) and prevent click actions when disabled so handlePrev/handleNext won't be invoked; reference PrevButton, NextButton, current, totalPages, handlePrev and handleNext when making the change.
🤖 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/components/instructor/home/ModifyingCommissionsRow.tsx`:
- Line 11: The row in ModifyingCommissionsRow.tsx uses visual affordances
(cursor-pointer, hover:bg-gray-5) but lacks interaction—fix by either wiring a
real handler or removing affordances: add an onClick prop to the component and
attach it to the div (e.g., implement an onRowClick handler in
ModifyingCommissionsRow and pass it down), ensure keyboard accessibility by
adding role="button", tabIndex={0}, and handling onKeyDown for Enter/Space, and
include an aria-label; alternatively, wrap the content in a Link if navigation
is intended or remove cursor-pointer/hover classes if no interaction should be
available.
In `@src/constants/write.ts`:
- Around line 146-151: The SIZE_DISPLAY_MAP entry for the key "국배판" is currently
set to the API code "KUKBAE", causing the UI (InfoRow in
src/components/instructor/write/PaymentModal/Step1.tsx which renders
SIZE_DISPLAY_MAP[selectedSize]) to show the code instead of a user-friendly
label; update the value for the "국배판" key in the SIZE_DISPLAY_MAP constant (in
src/constants/write.ts) to the appropriate display string (e.g., "국배판") so
selectedSize renders a user-facing label rather than the API code.
In `@src/lib/hooks/usePagination.ts`:
- Around line 8-9: When computing next page, guard against totalPages being 0 to
avoid negative pages: compute a non-negative lastPage (e.g., const lastPage =
Math.max(0, totalPages - 1)) and use it in handleNext (setCurrent(prev =>
Math.min(lastPage, prev + 1))). Keep handlePrev as setCurrent(prev =>
Math.max(0, prev - 1)) so current never goes below 0; update references to
totalPages, lastPage, handleNext, handlePrev, and setCurrent accordingly.
---
Duplicate comments:
In `@src/components/instructor/home/MatchingCommissionsRow.tsx`:
- Line 11: The Tailwind class "border-b-gray-10" in the MatchingCommissionsRow
component is likely invalid; update the class string on the root div in
MatchingCommissionsRow.tsx to use standard Tailwind utility separation (add
"border-b" and keep the color as "border-gray-10") unless "border-b-gray-10" is
a defined custom utility; also check and align the same pattern with
DraftSubmissionStatusRow to keep consistency across components.
---
Nitpick comments:
In `@src/components/common/PageIndicator.tsx`:
- Line 12: Replace the template-literal conditional class construction in the
PageIndicator component (the className currently using `${i === current ?
"bg-gray-700" : "bg-gray-300"}`) with the project's cn() utility: import cn if
missing and call cn("size-2 rounded-full transition-colors duration-300", {
"bg-gray-700": i === current, "bg-gray-300": i !== current }) so the conditional
classes are composed via cn() rather than string interpolation; update the
className prop on the element that uses variables i and current accordingly.
In `@src/components/common/Tag.tsx`:
- Around line 14-22: The Tag component is concatenating classes with a template
literal; switch it to use the cn() utility (importing it where Tag is defined)
to combine the static classes and the dynamic class from tagStyleMap[variant]
(e.g., cn('rounded-4 text-body2-sb flex items-center justify-center',
tagStyleMap[variant], ...)). Also verify and replace the non-standard Tailwind
class h-6.5 with a bracketed value like h-[1.625rem] or h-[26px] if your
Tailwind scale doesn't include 6.5. Update the Tag component and ensure
tagStyleMap usage and cn import are correct.
In `@src/components/instructor/home/DraftSubmissionStatusRow.tsx`:
- Around line 24-29: The rendering can break if total - submitted is negative;
update DraftSubmissionStatusRow to sanitize counts by computing a safeSubmitted
= Math.max(0, Math.min(submitted, total)) and safeRemaining = Math.max(0, total
- safeSubmitted) (or equivalent clamping) before mapping to icons (refer to
MatchingOnIcon and MatchingOffIcon render blocks), then use those safe values in
the Array.from length arguments so neither map gets a negative length.
In `@src/components/instructor/write/PlanChooseCard.tsx`:
- Around line 17-21: Replace the inline conditional template literal used for
className in PlanChooseCard (the expression that switches on isSelected to apply
"border-main-main bg-purple-5" vs "border-gray-40 hover:bg-gray-10 bg-white
hover:border-transparent") with the cn() utility; import cn if it's not already
imported and build the classes by passing the static classes ("rounded-14 w-full
cursor-pointer border p-6 transition-colors duration-150 outline-none") plus an
object or conditional arguments that apply the selected classes when isSelected
is true and the alternate classes when false so behavior remains identical.
In `@src/components/instructor/write/SizeRecommendedCard.tsx`:
- Line 24: The JSX className string in SizeRecommendedCard (the template using
isSelected: className={`rounded-8 w-49.25 cursor-pointer border px-4 py-3
transition-colors duration-150 ${isSelected ? "border-main-main bg-purple-10" :
"border-gray-20 hover:bg-gray-10 bg-white"}`}) should be refactored to use the
cn() utility; replace the inline conditional string concatenation with a cn(...)
call that always includes the static classes and conditionally includes
"border-main-main bg-purple-10" when isSelected is true and "border-gray-20
hover:bg-gray-10 bg-white" when false, referencing the SizeRecommendedCard
component and the isSelected prop to locate the code.
In `@src/containers/instructor/home/ModifyingCommissionsSection.tsx`:
- Around line 34-38: The Prev/Next buttons are always active visually; update
the PrevButton and NextButton JSX to pass a disabled prop when at the list
boundaries (use current === 1 for PrevButton and current === totalPages for
NextButton) and adjust their className or aria-disabled so they visually appear
disabled (e.g., reduced opacity and pointer-events disabled) and prevent click
actions when disabled so handlePrev/handleNext won't be invoked; reference
PrevButton, NextButton, current, totalPages, handlePrev and handleNext when
making the change.
In `@src/data/instructor/home.ts`:
- Line 17: Exported constant names draftSubmissionStatusData,
matchingStatusData, and modifyingStatusData use camelCase but must follow
SCREAMING_SNAKE_CASE; rename them to DRAFT_SUBMISSION_STATUS_DATA,
MATCHING_STATUS_DATA, and MODIFYING_STATUS_DATA in their declarations (e.g., the
exported arrays in the file) and update all import sites to use the new names so
references to these symbols (draftSubmissionStatusData, matchingStatusData,
modifyingStatusData) are replaced with DRAFT_SUBMISSION_STATUS_DATA,
MATCHING_STATUS_DATA, and MODIFYING_STATUS_DATA respectively.
In `@src/store/writeFormStore.ts`:
- Around line 133-140: buildOrderRequest currently maps nullable fields
selectedPlan/firstDate/finalDate to empty strings which can produce invalid
payloads when persisted to sessionStorage; change buildOrderRequest so that plan
and dates entries are omitted (or set to undefined) when selectedPlan, firstDate
or finalDate are null instead of mapping to "", e.g. only include plan when
selectedPlan != null (using PLAN_API_MAP[selectedPlan]), only push date objects
when firstDate/finalDate != null (using toApiDate), and preserve term as-is;
update any sessionStorage sync logic that assumes those keys always exist to
handle missing fields safely.
🪄 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: 193786d8-57c0-4ccb-97bb-9d915e0ba2b6
⛔ Files ignored due to path filters (2)
src/assets/icons/icon_matching_off.svgis excluded by!**/*.svgsrc/assets/icons/icon_matching_on.svgis excluded by!**/*.svg
📒 Files selected for processing (31)
src/app/globals.csssrc/app/instructor/layout.tsxsrc/app/instructor/ongoing/page.tsxsrc/app/instructor/page.tsxsrc/assets/icons/index.tssrc/components/common/Badge.tsxsrc/components/common/PageIndicator.tsxsrc/components/common/Tag.tsxsrc/components/instructor/home/CommissionsHeader.tsxsrc/components/instructor/home/DraftSubmissionStatusRow.tsxsrc/components/instructor/home/MatchingCommissionsRow.tsxsrc/components/instructor/home/ModifyingCommissionsRow.tsxsrc/components/instructor/write/PaperSizeCard.tsxsrc/components/instructor/write/PaymentModal/Step2.tsxsrc/components/instructor/write/PlanChooseCard.tsxsrc/components/instructor/write/SizeRecommendedCard.tsxsrc/constants/home.tssrc/constants/write.tssrc/containers/instructor/home/DraftSubmissionStatusSection.tsxsrc/containers/instructor/home/MatchingCommissionsSection.tsxsrc/containers/instructor/home/ModifyingCommissionsSection.tsxsrc/containers/instructor/write/AttachFileSection.tsxsrc/containers/instructor/write/ColorChooseSection.tsxsrc/containers/instructor/write/NecessaryPageChooseSection.tsxsrc/containers/instructor/write/ReferenceSection.tsxsrc/containers/instructor/write/Steps/Step2Content.tsxsrc/data/instructor/home.tssrc/lib/hooks/usePagination.tssrc/lib/utils/date.tssrc/store/writeFormStore.tssrc/types/write.ts
💤 Files with no reviewable changes (1)
- src/app/instructor/ongoing/page.tsx
| const { title, finalDeadline, isSubmitted, hasUpdate } = item; | ||
|
|
||
| return ( | ||
| <div className="border-b-gray-10 hover:bg-gray-5 flex h-15 cursor-pointer items-center border-b py-3 transition-colors duration-150"> |
There was a problem hiding this comment.
클릭 가능한 시각적 힌트가 있지만 실제 상호작용 핸들러가 없습니다
cursor-pointer와 hover:bg-gray-5 스타일이 적용되어 클릭 가능한 요소처럼 보이지만, 실제 onClick 핸들러나 링크가 없습니다. 이는 다음 문제를 야기합니다:
- 사용자가 클릭해도 아무 일도 일어나지 않아 혼란 발생
- 키보드 내비게이션 불가능 (접근성 문제)
🔧 제안 수정안
옵션 1: 링크로 래핑 (외주 상세 페이지로 이동)
+import Link from "next/link";
+
const ModifyingCommissionsRow = ({ item }: { item: ModifyingItem }) => {
const { title, finalDeadline, isSubmitted, hasUpdate } = item;
return (
- <div className="border-b-gray-10 hover:bg-gray-5 flex h-15 cursor-pointer items-center border-b py-3 transition-colors duration-150">
+ <Link
+ href={`/instructor/commissions/${item.commissionId}`}
+ className="border-b-gray-10 hover:bg-gray-5 flex h-15 cursor-pointer items-center border-b py-3 transition-colors duration-150"
+ >
<div className="flex w-full flex-row justify-between">
...
</div>
- </div>
+ </Link>
);
};옵션 2: onClick 핸들러 추가
-const ModifyingCommissionsRow = ({ item }: { item: ModifyingItem }) => {
+const ModifyingCommissionsRow = ({
+ item,
+ onClick
+}: {
+ item: ModifyingItem;
+ onClick?: () => void;
+}) => {
const { title, finalDeadline, isSubmitted, hasUpdate } = item;
return (
<div
className="border-b-gray-10 hover:bg-gray-5 flex h-15 cursor-pointer items-center border-b py-3 transition-colors duration-150"
+ onClick={onClick}
+ role="button"
+ tabIndex={0}
+ onKeyDown={(e) => e.key === 'Enter' && onClick?.()}
>옵션 3: 향후 구현 예정이라면 시각적 힌트 제거
- <div className="border-b-gray-10 hover:bg-gray-5 flex h-15 cursor-pointer items-center border-b py-3 transition-colors duration-150">
+ <div className="border-b-gray-10 flex h-15 items-center border-b py-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/components/instructor/home/ModifyingCommissionsRow.tsx` at line 11, The
row in ModifyingCommissionsRow.tsx uses visual affordances (cursor-pointer,
hover:bg-gray-5) but lacks interaction—fix by either wiring a real handler or
removing affordances: add an onClick prop to the component and attach it to the
div (e.g., implement an onRowClick handler in ModifyingCommissionsRow and pass
it down), ensure keyboard accessibility by adding role="button", tabIndex={0},
and handling onKeyDown for Enter/Space, and include an aria-label;
alternatively, wrap the content in a Link if navigation is intended or remove
cursor-pointer/hover classes if no interaction should be available.
There was a problem hiding this comment.
@waldls, 알겠습니다! 추후 경로 연결 시 Link 컴포넌트 래핑 또는 onClick 핸들러와 함께 접근성 속성(role="button", tabIndex={0}, onKeyDown)도 함께 추가해 주시면 좋을 것 같습니다.
해당 사항을 추적하기 위해 GitHub 이슈를 생성해 드릴까요?
🧠 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.
| export const SIZE_DISPLAY_MAP: Record<string, string> = { | ||
| a4: "A4", | ||
| 국배판: "국배판", | ||
| 국배판: "KUKBAE", | ||
| b5: "B5", | ||
| a5: "A5", | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check how SIZE_DISPLAY_MAP is used in the codebase
rg -n "SIZE_DISPLAY_MAP" --type=ts -A3 -B1Repository: Ditda-Official/Ditda-Frontend
Length of output: 1469
SIZE_DISPLAY_MAP의 “국배판” 값이 API 코드(KUKBAE)로 표시될 수 있습니다
src/components/instructor/write/PaymentModal/Step1.tsx에서InfoRow의 사이즈 값이SIZE_DISPLAY_MAP[selectedSize]로 렌더링됩니다. 그런데src/constants/write.ts에서SIZE_DISPLAY_MAP['국배판']이"KUKBAE"라서,selectedSize가국배판일 경우 화면에KUKBAE가 노출될 수 있습니다.SIZE_DISPLAY_MAP['국배판']을 사용자 표시값으로 맞춰주세요.
🤖 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/constants/write.ts` around lines 146 - 151, The SIZE_DISPLAY_MAP entry
for the key "국배판" is currently set to the API code "KUKBAE", causing the UI
(InfoRow in src/components/instructor/write/PaymentModal/Step1.tsx which renders
SIZE_DISPLAY_MAP[selectedSize]) to show the code instead of a user-friendly
label; update the value for the "국배판" key in the SIZE_DISPLAY_MAP constant (in
src/constants/write.ts) to the appropriate display string (e.g., "국배판") so
selectedSize renders a user-facing label rather than the API code.
There was a problem hiding this comment.
@waldls 이해했습니다. SIZE_DISPLAY_MAP의 값이 백엔드 ENUM 스펙에 맞게 의도적으로 "KUKBAE"로 설정된 것이군요. 이 내용을 기억해 두겠습니다.
🐇 (•ω•)
✏️ Learnings added
Learnt from: waldls
Repo: Ditda-Official/Ditda-Frontend PR: 24
File: src/constants/write.ts:146-151
Timestamp: 2026-06-02T09:42:27.963Z
Learning: In Ditda-Frontend (`src/constants/write.ts`), `SIZE_DISPLAY_MAP` intentionally uses backend ENUM code strings (e.g., `국배판: "KUKBAE"`) rather than user-friendly Korean labels. These values are aligned with the backend ENUM specification, not display purposes.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 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.
| const handlePrev = () => setCurrent(prev => Math.max(0, prev - 1)); | ||
| const handleNext = () => setCurrent(prev => Math.min(totalPages - 1, prev + 1)); |
There was a problem hiding this comment.
빈 배열 케이스 방어 로직 추가 권장
items가 빈 배열일 때 totalPages가 0이 되면, Line 9의 Math.min(totalPages - 1, prev + 1)이 Math.min(-1, prev + 1)이 되어 current가 음수가 될 수 있습니다. 실제로는 pageItems가 빈 배열을 반환해 화면에 문제가 없을 수 있지만, 상태가 논리적으로 부정확합니다.
🛡️ 제안 수정안
- const handlePrev = () => setCurrent(prev => Math.max(0, prev - 1));
- const handleNext = () => setCurrent(prev => Math.min(totalPages - 1, prev + 1));
+ const handlePrev = () => setCurrent(prev => (totalPages > 0 ? Math.max(0, prev - 1) : 0));
+ const handleNext = () => setCurrent(prev => (totalPages > 0 ? Math.min(totalPages - 1, prev + 1) : 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.
| const handlePrev = () => setCurrent(prev => Math.max(0, prev - 1)); | |
| const handleNext = () => setCurrent(prev => Math.min(totalPages - 1, prev + 1)); | |
| const handlePrev = () => setCurrent(prev => (totalPages > 0 ? Math.max(0, prev - 1) : 0)); | |
| const handleNext = () => setCurrent(prev => (totalPages > 0 ? Math.min(totalPages - 1, prev + 1) : 0)); |
🤖 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/lib/hooks/usePagination.ts` around lines 8 - 9, When computing next page,
guard against totalPages being 0 to avoid negative pages: compute a non-negative
lastPage (e.g., const lastPage = Math.max(0, totalPages - 1)) and use it in
handleNext (setCurrent(prev => Math.min(lastPage, prev + 1))). Keep handlePrev
as setCurrent(prev => Math.max(0, prev - 1)) so current never goes below 0;
update references to totalPages, lastPage, handleNext, handlePrev, and
setCurrent accordingly.
📢 PR 유형
어떤 변경 사항이 있었나요?
📌 관련 이슈번호
✅ Key Changes
강사 홈 페이지 대시보드 (/instructor)
신규 컴포넌트
PageIndicator— 현재 페이지 위치를 점(dot)으로 표시하는 공통 컴포넌트Tag— 디데이 등 레이블 표시용 공통 컴포넌트 (default / black / gray variant)CommissionsHeader— 대시보드 각 섹션 공통 헤더 컴포넌트DraftSubmissionStatusRow/MatchingCommissionsRow/ModifyingCommissionsRow— 각 섹션 행 컴포넌트기존 컴포넌트 수정
Badge— size prop을 variant로 변경, pass / fail / waiting variant 추가유틸 및 훅
usePagination— 페이지네이션 상태 관리 커스텀 훅getDDay— 마감일 기반 D-day 문자열 반환 유틸새 외주 작성 수정사항
📸 스크린샷 or 실행영상
대시보드
home.mp4
새 외주 작성
write.mp4
🎸 기타 사항 or 추가 코멘트
대시보드 섹션에 데이터 없을 때의 화면은 추후 디자인팀에서 작업 예정입니다!
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선 사항