Skip to content

#52 feat: 문제 관리 UI 구현 및 API 연동#61

Open
suminb99 wants to merge 33 commits intodevelopfrom
feat/52-manage-assignment-ui-api
Open

#52 feat: 문제 관리 UI 구현 및 API 연동#61
suminb99 wants to merge 33 commits intodevelopfrom
feat/52-manage-assignment-ui-api

Conversation

@suminb99
Copy link
Contributor

@suminb99 suminb99 commented Feb 26, 2026

⚙️ Related ISSUE Number

Related #52



📄 Work Description

  • 문제 관리 페이지 UI 구현
  • 과제 삭제 뮤테이션 추가 및 기능 구현
  • 문제 편집 및 추가 버튼 클릭 시 페이지 이동 로직 구현
  • 과제 목록 필터 로직을 useAssignmentList 커스텀 훅으로 분리



📷 Screenshot



💬 To Reviewers

TODOS:

  • API 응답/요청 타입을 zod 스키마 기반으로 변경
  • 저장/취소 버튼 -> 나가기 버튼 하나로 통일 (다음 회의 때 논의하면 좋을 거 같아요)

@JiiminHa UnitEditorPage.tsx의 뮤테이션의 onSuccess가 컴포넌트 상태에 의존하고 있어서 분리하는 것은 조금 더 고민해 보겠습니다!



🔗 Reference

@suminb99 suminb99 self-assigned this Feb 26, 2026
@suminb99 suminb99 added 🧩 feature 기능 개발 🎨 ui CSS 등 사용자 UI 디자인 변경 labels Feb 26, 2026
@suminb99 suminb99 linked an issue Feb 26, 2026 that may be closed by this pull request
4 tasks
@vercel
Copy link

vercel bot commented Feb 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
snow-code-client Ready Ready Preview, Comment Feb 28, 2026 10:11am

@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

관리자용 할당 관리 페이지와 관련 컴포넌트, API(과정·할당·유닛), React Query 쿼리/뮤테이션 래퍼, Zustand 기반 유닛 폼 퍼시스트 스토어, 그리고 여러 UI 타입/레이아웃 변경이 추가·통합되었습니다.

Changes

Cohort / File(s) Summary
라우팅
src/App.tsx
/admin/assignments/manage 경로 추가 및 AssignmentManagePage import/연결.
할당 API / 쿼리 / 뮤테이션 / 타입
src/entities/assignment/api/assignmentApi.ts, src/entities/assignment/api/assignmentQueries.ts, src/entities/assignment/api/assignmentMutations.ts, src/entities/assignment/model/types.ts, src/entities/assignment/api/assignmentQueryOptions.ts
getAllAssignments, getAssignmentsByCourse, deleteAssignment 추가. assignmentQueries·assignmentMutations로 중앙화. AssignmentsResponse 타입 추가. 기존 assignmentQueryOptions.ts 삭제.
과정 API / 쿼리 / 뮤테이션 / 인덱스 변경
src/entities/course/api/courseApi.ts, src/entities/course/api/courseQueries.ts, src/entities/course/api/courseMutations.ts, src/entities/course/api/courseQueryOptions.ts, src/entities/course/index.ts
getAllCourses, deleteCourse API 추가 및 쿼리/뮤테이션 통합. 기존 courseQueryOptions.ts 삭제 및 index.ts에서 해당 함수들 re-export 제거.
유닛 API / 뮤테이션 / 쿼리
src/entities/unit/api/unitApi.ts, src/entities/unit/api/unitMutations.ts, src/entities/unit/api/unitQueries.ts
deleteAssignmentFromUnit(unitId, assignmentId) 추가. unitMutations의 create 파라미터명 변경(unitunitForm). 쿼리의 select 변환 추가(정규화된 반환).
유닛 폼 상태 스토어
src/entities/unit/model/useUnitStore.ts
Zustand + persist 기반 useUnitStore 추가(세션스토리지에 title/releaseDate/dueDate/assignments 저장·복원, storeFormData/setAssignments/resetStore 제공).
관리 페이지 / 액션 바 UI
src/pages/manage-assignment/AssignmentManagePage.tsx, src/pages/manage-assignment/ui/AssignmentManageActionsBar.tsx
할당 관리 페이지 및 액션바 컴포넌트 추가(조회·삭제 뮤테이션, 확인/알림, 편집 네비게이션).
할당 선택 흐름 / 리스트 컨테이너 / 훅
src/pages/select-assignment/AssignmentSelectPage.tsx, src/pages/select-assignment/ui/AssignmentListContainer.tsx, src/features/assignment/filter-assignment/lib/useAssignmentList.ts
실 데이터로 전환. 선택 항목을 Assignment[]로 관리하도록 변경. AssignmentListContainer의 onSelect 시그니처가 item 기반으로 변경. useAssignmentList 훅 추가(중복 제거 포함).
유닛 에디터/폼/리스트 타입·플로우 변경
src/pages/unit-editor/UnitEditorPage.tsx, src/pages/unit-editor/ui/UnitForm.tsx, src/pages/unit-editor/ui/UnitList.tsx, src/pages/unit-editor/model/types.ts
유닛 에디터의 모드 관리 refactor(예: idle 제거 → activeMode/currentMode), 스토어 연동, create/update/delete 흐름·네비게이션 변경, onChangeIndex 제거, UnitForm의 스토어 기반 프리필 및 Assignment 선택 네비게이션 추가.
대시보드 업데이트
src/pages/dashboard/Dashboard.tsx
기존 queryOptions 사용을 courseQueries·assignmentQueries·courseMutations 등 공개 API로 대체. 캐시 무효화 키와 오류 핸들러 업데이트.
공유 UI 타입/스타일/버튼
src/shared/ui/Layout.tsx, src/shared/ui/list-row/ListRow.tsx, src/shared/ui/list-row/list-row-styles.ts, src/shared/ui/button/Button.tsx
Layout 정렬 클래스 변경, ReactNode 타입 도입, ListRow의 left/right icon 타입 변경, list-row 스타일에서 cursor-pointer 제거, Button prop명 formIDform 변경.
AssignmentPageLayout 변경
src/widgets/assignment-page-layout/ui/AssignmentPageLayout.tsx
내부 Cancel/Confirm 버튼 제거, 외부에서 전달하는 buttons: ReactNode prop 으로 하단 액션 영역 렌더링하도록 변경. 리스트 prop 타입도 ReactNode로 조정.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant AMP as AssignmentManagePage
    participant Mut as assignmentMutations
    participant API as assignmentApi
    participant Server

    User->>AMP: 삭제 클릭
    AMP->>AMP: 확인 모달
    User->>AMP: 확인
    AMP->>Mut: mutate(deleteAssignment)
    Mut->>API: deleteAssignment(assignmentId)
    API->>Server: DELETE /assignments/{id}
    Server-->>API: ApiResponse<string>
    API-->>Mut: 응답
    Mut->>AMP: onSuccess -> invalidate queries (assignments, courses' assignments)
    AMP->>User: 성공 알림
Loading
sequenceDiagram
    participant UF as UnitForm
    participant Store as useUnitStore
    participant Router
    participant SP as AssignmentSelectPage
    participant API as courseQueries/assignmentQueries

    User->>UF: 할당 연결 버튼 클릭
    UF->>UF: getValues() 캡처
    UF->>Store: storeFormData(values)
    Store-->>Store: sessionStorage에 저장
    UF->>Router: navigate('/admin/assignments/select', {backPath})
    Router-->>SP: 렌더
    SP->>API: courseQueries.getAllCourses(), useAssignmentList(...)
    User->>SP: 할당 선택
    SP->>Store: setAssignments(selectedAssignments)
    Store-->>Store: sessionStorage 업데이트
    SP->>Router: navigate(backPath)
    Router-->>UF: 복귀
    UF->>Store: persisted assignments 읽기
    Store-->>UF: assignments 반환
    UF->>UF: 폼에 반영
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • JiiminHa

간단 피드백: 전체 구조 잘 정리되었어요 — 다만 API 반환형 변환(예: getAllAssignments의 id 매핑)과 쿼리 select 결과 타입이 여러 파일에서 기대 형태를 바꾸므로, 타입 정의와 사용 지점(특히 select/invalidates에 사용되는 queryKey 타입)을 한번 더 검증하시고, React Query 공식 가이드를 참고해 캐시 무효화 키 일관성을 확인하세요: https://tanstack.com/query/latest/docs/react/guides/query-keys.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 PR의 주요 변경사항을 명확하게 설명하고 있습니다. '문제 관리 UI 구현 및 API 연동'은 추가된 AssignmentManagePage, 삭제 뮤테이션, 페이지 이동 로직, 필터 훅 분리 등 모든 핵심 변경사항을 포괄합니다.
Description check ✅ Passed PR 설명이 작업 내용, 관련 이슈 번호, 추가 작업 항목 등 변경사항과 관련된 정보를 충분히 포함하고 있습니다. 설명 내용이 실제 코드 변경과 잘 맞아떨어집니다.
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
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/52-manage-assignment-ui-api

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.

@suminb99 suminb99 requested a review from JiiminHa February 26, 2026 13:59
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (10)
src/pages/select-assignment/ui/AssignmentListContainer.tsx (1)

4-10: 타입 불일치 가능성: TAssignment 타입이 다를 수 있습니다.

TAssignmentSelectCourse['assignments'][number]로 정의되어 있고, onSelectAssignment 타입을 받습니다. 두 타입이 동일하다고 가정하고 있지만, 타입 시스템에서 이를 보장하지 않습니다. Line 28에서 item(타입 T)을 handleSelect(타입 Assignment 기대)에 전달하면서 잠재적 타입 불일치가 발생할 수 있습니다.

♻️ 타입 일관성 개선 제안
-import type {Assignment} from '@/entities/assignment/model/types';
 import type {AssignmentSelectCourse} from '@/entities/course/model/types';

 type T = AssignmentSelectCourse['assignments'][number];

-interface AssignmentListContainerProps<T> {
+interface AssignmentListContainerProps {
   items: T[];
   renderItem: (item: T) => React.ReactNode;
   title: string;
-  onSelect?: (item: Assignment) => void;
+  onSelect?: (item: T) => void;
 }

또는 AssignmentSelectCourse['assignments'][number]Assignment와 동일하다면, 제네릭 T를 제거하고 Assignment를 직접 사용하는 것이 더 명확합니다.

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

In `@src/pages/select-assignment/ui/AssignmentListContainer.tsx` around lines 4 -
10, The prop types are inconsistent: the generic T (alias for
AssignmentSelectCourse['assignments'][number]) may not equal Assignment but
onSelect expects an Assignment; either constrain the generic or align the
callback type — update AssignmentListContainerProps to declare T extends
Assignment (e.g., interface AssignmentListContainerProps<T extends Assignment>)
so handleSelect(item: T) can be passed to onSelect(item: Assignment) safely, or
change onSelect signature to onSelect?: (item: T) => void; alternatively, if
those types are identical in practice, remove the generic and use Assignment
directly in AssignmentListContainerProps and related symbols (T, renderItem,
onSelect, handleSelect) for clarity.
src/entities/unit/api/unitMutations.ts (1)

8-14: 매개변수 네이밍 불일치: unitForm vs unit

createUnitunitForm을 사용하고, updateUnitunit을 사용합니다. 같은 TUnitFormSchema 타입인데 네이밍이 다릅니다. 일관성을 위해 둘 다 같은 이름을 사용하는 것이 좋습니다.

♻️ 네이밍 일관성 개선 제안
   // 단원 수정 뮤테이션 옵션
   updateUnit: {
     mutationKey: ['updateUnit'],
-    mutationFn: ({unitId, unit}: {unitId: number; unit: TUnitFormSchema}) =>
-      updateUnit(unitId, unit),
+    mutationFn: ({unitId, unitForm}: {unitId: number; unitForm: TUnitFormSchema}) =>
+      updateUnit(unitId, unitForm),
   },

Also applies to: 20-21

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

In `@src/entities/unit/api/unitMutations.ts` around lines 8 - 14, The parameter
name is inconsistent between the create and update mutations: mutationFn
currently passes { courseId, unitForm } into createUnit while updateUnit expects
{ courseId, unit }. Make the naming consistent by choosing one name (e.g.,
unitForm) and updating the other callsite(s) to match; update the mutationFn
argument destructuring and any other occurrences (including the updateUnit call
at the other mutation) so both use the same identifier (createUnit and
updateUnit signatures/usages must align).
src/entities/unit/model/useUnitStore.ts (1)

15-51: 중복 export 패턴 정리 권장

useUnitStore가 named export (Line 15)와 default export (Line 51) 두 가지로 내보내지고 있습니다. 일관성을 위해 하나의 방식만 사용하는 것이 좋습니다.

♻️ 단일 export 방식으로 정리
-export const useUnitStore = create<UnitState>()(
+const useUnitStore = create<UnitState>()(
   persist(
     // ... store implementation
   )
 );

 export default useUnitStore;

또는 named export만 사용:

 export const useUnitStore = create<UnitState>()(
   // ... store implementation
 );
-
-export default useUnitStore;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/unit/model/useUnitStore.ts` around lines 15 - 51, The file
currently exports useUnitStore both as a named export and as a default export;
pick one export style and remove the other—prefer keeping the named export
useUnitStore and delete the trailing default export (remove the line exporting
useUnitStore as default), then update any consumers to import { useUnitStore }
instead of a default import if needed; ensure the create<UnitState>()(... )
export remains as the single named export.
src/pages/unit-editor/ui/UnitForm.tsx (2)

155-163: 조건부 렌더링 로직 가독성 개선 권장

중첩된 삼항 연산자가 가독성을 저해합니다. 조기 반환 패턴이나 별도 변수를 사용하면 더 명확해집니다.

♻️ 가독성 개선 제안
+          {/* 드래그 앤 드롭 가능한 문제 리스트 */}
+          {(() => {
+            const editingAssignments = mode === 'editing' && unit?.assignmentCount > 0 
+              ? unit.assignments 
+              : null;
+            const displayAssignments = editingAssignments ?? (assignments.length > 0 ? assignments : null);
+            
+            return displayAssignments ? (
+              <UnitAssignmentList assignmentList={displayAssignments} />
+            ) : (
+              <EmptyState className='mt-4 mb-5'>
+                등록된 문제가 없습니다.
+              </EmptyState>
+            );
+          })()}
-            {mode === 'editing' && unit && unit.assignmentCount > 0 ? (
-              <UnitAssignmentList assignmentList={unit.assignments} />
-            ) : assignments.length > 0 ? (
-              <UnitAssignmentList assignmentList={assignments} />
-            ) : (
-              <EmptyState className='mt-4 mb-5'>
-                등록된 문제가 없습니다.
-              </EmptyState>
-            )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/unit-editor/ui/UnitForm.tsx` around lines 155 - 163, The nested
ternary in UnitForm.tsx (checking mode, unit.assignmentCount, and assignments)
is hard to read; replace it with a clearer conditional flow by computing a
single render variable or using early returns inside the UnitForm component:
determine which list to show by evaluating mode === 'editing' && unit &&
unit.assignmentCount > 0 to choose UnitAssignmentList with unit.assignments,
else if assignments.length > 0 choose UnitAssignmentList with assignments,
otherwise render the EmptyState; reference the symbols mode, unit,
unit.assignmentCount, UnitAssignmentList, assignments, and EmptyState when
implementing the change.

167-175: 비활성화된 버튼에 대한 사용자 피드백 개선

편집 모드에서 "문제 연결" 버튼이 비활성화되는데, 사용자가 그 이유를 알 수 없습니다. 툴팁이나 안내 문구를 추가하면 UX가 개선됩니다.

💡 UX 개선 제안
+            <div className='mt-3.5' title={mode === 'editing' ? '편집 모드에서는 문제를 연결할 수 없습니다' : undefined}>
               <Button
                 onClick={handleAssignmentSelect}
                 color='tonal'
                 size='compact'
                 content='mixed'
                 disabled={mode === 'editing'}>
                 <AddIcon className='w-3 h-3' />
                 문제 연결
               </Button>
+            </div>
-            </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/unit-editor/ui/UnitForm.tsx` around lines 167 - 175, The "문제 연결"
Button (component rendering with onClick={handleAssignmentSelect}, disabled when
mode === 'editing') lacks feedback explaining why it's disabled; update the UI
to show a tooltip or helper text when disabled (e.g., wrap Button with a Tooltip
component or render a small caption next to it) that explains "편집 모드에서는 문제를 연결할
수 없습니다" (or similar), ensure the tooltip is only shown when mode === 'editing'
and does not change the onClick behavior of handleAssignmentSelect when enabled;
keep the change localized to the UnitForm component and its Button rendering.
src/features/assignment/filter-assignmnet/lib/useAssignmentList.ts (1)

15-17: 조건부 쿼리 호출 방식 개선 권장

selectedCourseId ?? 0으로 0을 전달하는 방식은 의도가 불명확합니다. assignmentQueries.getAssignmentsByCourse에서 enabled: !!courseId로 처리되지만, 더 명시적인 방식을 권장합니다.

♻️ 개선 제안
-  const {data: assignments} = useQuery(
-    assignmentQueries.getAssignmentsByCourse(selectedCourseId ?? 0)
-  );
+  const {data: assignments} = useQuery({
+    ...assignmentQueries.getAssignmentsByCourse(selectedCourseId!),
+    enabled: !!selectedCourseId,
+  });

참고: TanStack Query - Dependent Queries

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

In `@src/features/assignment/filter-assignmnet/lib/useAssignmentList.ts` around
lines 15 - 17, The call to useQuery passes
assignmentQueries.getAssignmentsByCourse(selectedCourseId ?? 0) which hides
intent by sending 0; instead make the query dependent/conditional: call useQuery
with assignmentQueries.getAssignmentsByCourse(selectedCourseId) (or its query
key/function) and pass an options object with enabled: !!selectedCourseId so the
query only runs when selectedCourseId is truthy; update any related helpers in
assignmentQueries.getAssignmentsByCourse to accept undefined/null gracefully if
needed and ensure the enabled flag is used to prevent calling the backend with
an invalid id.
src/pages/manage-assignment/AssignmentManagePage.tsx (1)

30-34: 쿼리 무효화 시 null 처리 개선

selectedCourseIdnull일 때 0을 전달하면 의도치 않은 쿼리 키가 생성될 수 있습니다. 선택된 강의가 있을 때만 해당 쿼리를 무효화하는 것이 더 안전합니다.

♻️ 조건부 무효화 제안
       queryClient.invalidateQueries({
         queryKey: assignmentQueries.getAllAssignments().queryKey,
       });
-      queryClient.invalidateQueries({
-        queryKey: assignmentQueries.getAssignmentsByCourse(
-          selectedCourseId ?? 0
-        ).queryKey,
-      });
+      if (selectedCourseId) {
+        queryClient.invalidateQueries({
+          queryKey: assignmentQueries.getAssignmentsByCourse(
+            selectedCourseId
+          ).queryKey,
+        });
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/manage-assignment/AssignmentManagePage.tsx` around lines 30 - 34,
The invalidate call creates a query key using selectedCourseId fallback 0 which
can produce wrong keys when selectedCourseId is null; update the logic so
queryClient.invalidateQueries is only called when selectedCourseId is
non-null/defined (e.g., check selectedCourseId != null) and then pass
assignmentQueries.getAssignmentsByCourse(selectedCourseId).queryKey to
invalidateQueries, referencing the existing queryClient.invalidateQueries and
assignmentQueries.getAssignmentsByCourse usages to locate the change.
src/pages/select-assignment/AssignmentSelectPage.tsx (1)

38-47: 네비게이션 폴백 처리 개선 고려

location.state?.backPath ?? -1에서 히스토리가 없을 경우 예상치 못한 동작이 발생할 수 있습니다. 명시적인 기본 경로를 설정하는 것이 더 안전합니다.

♻️ 안전한 폴백 경로 제안
   const returnToPreviousPage = () => {
-    navigate(location.state?.backPath ?? -1, {
+    navigate(location.state?.backPath ?? '/admin/units', {
       state: {
         mode: location.state?.mode,
         unitId: location.state?.unitId,
         currentIndex: location.state?.currentIndex,
       },
     });
   };

참고: React Router - useNavigate

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

In `@src/pages/select-assignment/AssignmentSelectPage.tsx` around lines 38 - 47,
The returnToPreviousPage function currently calls
navigate(location.state?.backPath ?? -1) which can produce unexpected behavior
when history is absent; change it to use a safe explicit fallback (e.g.,
navigate('/') or another known route) or check history length before using -1.
Update the logic inside returnToPreviousPage to prefer location.state?.backPath
when present, otherwise call navigate with a defined default path (or
conditionally use navigate(-1) only when a previous entry exists), and preserve
the existing state payload (mode, unitId, currentIndex) when navigating.
src/pages/manage-assignment/ui/AssignmentManageActionsBar.tsx (1)

28-33: 접근성 개선을 위한 aria-label 추가 권장

아이콘만 있는 버튼은 스크린 리더 사용자를 위해 aria-label을 추가하는 것이 좋습니다.

♿ 접근성 개선 제안
-      <button onClick={handleOnEdit}>
+      <button onClick={handleOnEdit} aria-label="수정">
         <EditIcon className='w-3.5 h-4' />
       </button>
-      <button onClick={handleOnDelete}>
+      <button onClick={handleOnDelete} aria-label="삭제">
         <DeleteIcon className='w-3.5 h-4' />
       </button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/manage-assignment/ui/AssignmentManageActionsBar.tsx` around lines
28 - 33, The two icon-only buttons in AssignmentManageActionsBar (the button
elements with onClick handlers handleOnEdit and handleOnDelete rendering
EditIcon and DeleteIcon) need accessible labels: add descriptive aria-label
attributes (e.g., aria-label="Edit assignment" and aria-label="Delete
assignment" or use your i18n strings) to each button so screen readers can
announce their purpose; keep the existing handlers and icons unchanged.
src/entities/assignment/api/assignmentApi.ts (1)

39-45: API 호출에 명시적 타입 지정 권장

getAssignmentsByCourse에서 privateAxios.get 호출 시 제네릭 타입을 명시하면 타입 안전성이 향상됩니다. getAllAssignments에서는 이미 이 패턴을 사용하고 있습니다.

♻️ 타입 명시 제안
 export const getAssignmentsByCourse = async (
   courseId: number
 ): Promise<AssignmentSelectResponse> => {
-  const response = await privateAxios.get(`/courses/${courseId}/assignments`);
+  const response = await privateAxios.get<AssignmentSelectResponse>(
+    `/courses/${courseId}/assignments`
+  );
   return response.data;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/assignment/api/assignmentApi.ts` around lines 39 - 45, Update
getAssignmentsByCourse to pass an explicit generic to privateAxios.get to
improve type safety: call
privateAxios.get<AssignmentSelectResponse>(`/courses/${courseId}/assignments`)
and keep returning response.data (or adjust the local response variable type to
AxiosResponse<AssignmentSelectResponse> if you prefer). This change targets the
getAssignmentsByCourse function to mirror the typed pattern used in
getAllAssignments.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/App.tsx`:
- Line 16: 현재 App.tsx에서 AssignmentManagePage를 상대경로로 임포트하고 있어 프로젝트 컨벤션과 다릅니다;
Import 문에서 AssignmentManagePage를
상대경로('./pages/manage-assignment/AssignmentManagePage') 대신 절대경로
형식('@/pages/manage-assignment/AssignmentManagePage')로 변경하여 다른 페이지 컴포넌트들과 경로 스타일을
통일하세요.

In `@src/features/assignment/filter-assignmnet/lib/useAssignmentList.ts`:
- Around line 1-3: The folder name contains a typo: rename the directory from
"filter-assignmnet" to "filter-assignment" and update all imports referencing
that module (e.g., the module that exports useAssignmentList in
useAssignmentList.ts and any import lines like import {useQuery}... from
'@/features/assignment/filter-assignmnet/...') to the corrected kebab-case path;
ensure your VCS commit records the folder rename (not a delete/add) so imports
(and any path aliases) are updated consistently across the codebase.

In `@src/pages/manage-assignment/AssignmentManagePage.tsx`:
- Around line 88-89: The empty callbacks on AssignmentManagePage.tsx (props
onCancel and onConfirm) should be handled explicitly: either wire real handlers
in AssignmentManagePage (implement functions used for cancel/confirm and pass
them to onCancel/onConfirm) or update the AssignmentPageLayout component to make
onCancel and onConfirm optional (adjust prop types and internal usage to guard
for undefined), and if you intentionally want no-op behavior leave a clear
comment in AssignmentManagePage referencing onCancel/onConfirm to document the
intent; ensure you modify the AssignmentPageLayout prop signature (and any
places that call those handlers) or replace the empty lambdas in
AssignmentManagePage with meaningful handlers.

In `@src/pages/unit-editor/UnitEditorPage.tsx`:
- Around line 82-85: After deleting a unit you call invalidateUnitList(),
setSelectedUnitId(null), and setMode('creating') but you never reset
currentIndex; update the deletion flow (where invalidateUnitList,
setSelectedUnitId, setMode are called) to also clear/reset currentIndex by
invoking the appropriate setter (e.g., setCurrentIndex(null) or
setCurrentIndex(0) consistent with how currentIndex is used) so the UI shows the
correct new-unit index when mode is 'creating'.
- Around line 26-30: The router location state is used without runtime type
checks (state, state.mode, state.unitId, state.currentIndex) which can break the
editor when unexpected shapes arrive; add a type guard or runtime validation
after useLocation() to assert the state shape (e.g., ensure state is an object,
state.mode is a valid string/enum value, state.unitId is the expected type, and
state.currentIndex is a number) before calling setMode, setSelectedUnitId, and
setCurrentIndex, and fall back to safe defaults if validation fails so the
UnitEditorPage remains type-safe and robust.

In `@src/shared/ui/list-row/list-row-styles.ts`:
- Line 4: The ListRow base style removed cursor-pointer causing inconsistent
interactive affordance; restore cursor-pointer in the ListRow style definition
(the "base" entry in src/shared/ui/list-row/list-row-styles.ts) so clickable
rows default to pointer, and leave UnitAssignmentList to explicitly override
with cursor-grab via its className when needed; alternatively, if you prefer the
current removal, then add cursor-pointer to the className usages in
AssignmentSelectPage and AssignmentManagePage where rows are selectable
(reference the "base" symbol in list-row-styles and the UnitAssignmentList,
AssignmentSelectPage, AssignmentManagePage components to locate places to
adjust).

---

Nitpick comments:
In `@src/entities/assignment/api/assignmentApi.ts`:
- Around line 39-45: Update getAssignmentsByCourse to pass an explicit generic
to privateAxios.get to improve type safety: call
privateAxios.get<AssignmentSelectResponse>(`/courses/${courseId}/assignments`)
and keep returning response.data (or adjust the local response variable type to
AxiosResponse<AssignmentSelectResponse> if you prefer). This change targets the
getAssignmentsByCourse function to mirror the typed pattern used in
getAllAssignments.

In `@src/entities/unit/api/unitMutations.ts`:
- Around line 8-14: The parameter name is inconsistent between the create and
update mutations: mutationFn currently passes { courseId, unitForm } into
createUnit while updateUnit expects { courseId, unit }. Make the naming
consistent by choosing one name (e.g., unitForm) and updating the other
callsite(s) to match; update the mutationFn argument destructuring and any other
occurrences (including the updateUnit call at the other mutation) so both use
the same identifier (createUnit and updateUnit signatures/usages must align).

In `@src/entities/unit/model/useUnitStore.ts`:
- Around line 15-51: The file currently exports useUnitStore both as a named
export and as a default export; pick one export style and remove the
other—prefer keeping the named export useUnitStore and delete the trailing
default export (remove the line exporting useUnitStore as default), then update
any consumers to import { useUnitStore } instead of a default import if needed;
ensure the create<UnitState>()(... ) export remains as the single named export.

In `@src/features/assignment/filter-assignmnet/lib/useAssignmentList.ts`:
- Around line 15-17: The call to useQuery passes
assignmentQueries.getAssignmentsByCourse(selectedCourseId ?? 0) which hides
intent by sending 0; instead make the query dependent/conditional: call useQuery
with assignmentQueries.getAssignmentsByCourse(selectedCourseId) (or its query
key/function) and pass an options object with enabled: !!selectedCourseId so the
query only runs when selectedCourseId is truthy; update any related helpers in
assignmentQueries.getAssignmentsByCourse to accept undefined/null gracefully if
needed and ensure the enabled flag is used to prevent calling the backend with
an invalid id.

In `@src/pages/manage-assignment/AssignmentManagePage.tsx`:
- Around line 30-34: The invalidate call creates a query key using
selectedCourseId fallback 0 which can produce wrong keys when selectedCourseId
is null; update the logic so queryClient.invalidateQueries is only called when
selectedCourseId is non-null/defined (e.g., check selectedCourseId != null) and
then pass assignmentQueries.getAssignmentsByCourse(selectedCourseId).queryKey to
invalidateQueries, referencing the existing queryClient.invalidateQueries and
assignmentQueries.getAssignmentsByCourse usages to locate the change.

In `@src/pages/manage-assignment/ui/AssignmentManageActionsBar.tsx`:
- Around line 28-33: The two icon-only buttons in AssignmentManageActionsBar
(the button elements with onClick handlers handleOnEdit and handleOnDelete
rendering EditIcon and DeleteIcon) need accessible labels: add descriptive
aria-label attributes (e.g., aria-label="Edit assignment" and aria-label="Delete
assignment" or use your i18n strings) to each button so screen readers can
announce their purpose; keep the existing handlers and icons unchanged.

In `@src/pages/select-assignment/AssignmentSelectPage.tsx`:
- Around line 38-47: The returnToPreviousPage function currently calls
navigate(location.state?.backPath ?? -1) which can produce unexpected behavior
when history is absent; change it to use a safe explicit fallback (e.g.,
navigate('/') or another known route) or check history length before using -1.
Update the logic inside returnToPreviousPage to prefer location.state?.backPath
when present, otherwise call navigate with a defined default path (or
conditionally use navigate(-1) only when a previous entry exists), and preserve
the existing state payload (mode, unitId, currentIndex) when navigating.

In `@src/pages/select-assignment/ui/AssignmentListContainer.tsx`:
- Around line 4-10: The prop types are inconsistent: the generic T (alias for
AssignmentSelectCourse['assignments'][number]) may not equal Assignment but
onSelect expects an Assignment; either constrain the generic or align the
callback type — update AssignmentListContainerProps to declare T extends
Assignment (e.g., interface AssignmentListContainerProps<T extends Assignment>)
so handleSelect(item: T) can be passed to onSelect(item: Assignment) safely, or
change onSelect signature to onSelect?: (item: T) => void; alternatively, if
those types are identical in practice, remove the generic and use Assignment
directly in AssignmentListContainerProps and related symbols (T, renderItem,
onSelect, handleSelect) for clarity.

In `@src/pages/unit-editor/ui/UnitForm.tsx`:
- Around line 155-163: The nested ternary in UnitForm.tsx (checking mode,
unit.assignmentCount, and assignments) is hard to read; replace it with a
clearer conditional flow by computing a single render variable or using early
returns inside the UnitForm component: determine which list to show by
evaluating mode === 'editing' && unit && unit.assignmentCount > 0 to choose
UnitAssignmentList with unit.assignments, else if assignments.length > 0 choose
UnitAssignmentList with assignments, otherwise render the EmptyState; reference
the symbols mode, unit, unit.assignmentCount, UnitAssignmentList, assignments,
and EmptyState when implementing the change.
- Around line 167-175: The "문제 연결" Button (component rendering with
onClick={handleAssignmentSelect}, disabled when mode === 'editing') lacks
feedback explaining why it's disabled; update the UI to show a tooltip or helper
text when disabled (e.g., wrap Button with a Tooltip component or render a small
caption next to it) that explains "편집 모드에서는 문제를 연결할 수 없습니다" (or similar), ensure
the tooltip is only shown when mode === 'editing' and does not change the
onClick behavior of handleAssignmentSelect when enabled; keep the change
localized to the UnitForm component and its Button rendering.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c2b834 and e7ea6cb.

📒 Files selected for processing (25)
  • src/App.tsx
  • src/entities/assignment/api/assignmentApi.ts
  • src/entities/assignment/api/assignmentMutations.ts
  • src/entities/assignment/api/assignmentQueries.ts
  • src/entities/assignment/api/assignmentQueryOptions.ts
  • src/entities/assignment/model/types.ts
  • src/entities/course/api/courseApi.ts
  • src/entities/course/api/courseMutations.ts
  • src/entities/course/api/courseQueries.ts
  • src/entities/course/api/courseQueryOptions.ts
  • src/entities/course/index.ts
  • src/entities/unit/api/unitApi.ts
  • src/entities/unit/api/unitMutations.ts
  • src/entities/unit/model/useUnitStore.ts
  • src/features/assignment/filter-assignmnet/lib/useAssignmentList.ts
  • src/pages/dashboard/Dashboard.tsx
  • src/pages/manage-assignment/AssignmentManagePage.tsx
  • src/pages/manage-assignment/ui/AssignmentManageActionsBar.tsx
  • src/pages/select-assignment/AssignmentSelectPage.tsx
  • src/pages/select-assignment/ui/AssignmentListContainer.tsx
  • src/pages/unit-editor/UnitEditorPage.tsx
  • src/pages/unit-editor/ui/UnitForm.tsx
  • src/shared/ui/Layout.tsx
  • src/shared/ui/list-row/ListRow.tsx
  • src/shared/ui/list-row/list-row-styles.ts
💤 Files with no reviewable changes (3)
  • src/entities/course/index.ts
  • src/entities/assignment/api/assignmentQueryOptions.ts
  • src/entities/course/api/courseQueryOptions.ts

Comment on lines +1 to +3
import {useQuery} from '@tanstack/react-query';
import {assignmentQueries} from '@/entities/assignment/api/assignmentQueries';
import type {Assignment} from '@/entities/assignment/model/types';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for the typo in folder name and find all references

# Find the folder with typo
echo "=== Searching for folder with typo 'filter-assignmnet' ==="
fd -t d "filter-assignmnet" src/ 2>/dev/null || echo "No 'filter-assignmnet' folder found"

# Find correct spelling folder
echo -e "\n=== Searching for correctly spelled 'filter-assignment' ==="
fd -t d "filter-assignment" src/ 2>/dev/null || echo "No 'filter-assignment' folder found"

# Find all imports referencing the typo path
echo -e "\n=== Finding all references to 'filter-assignmnet' ==="
rg "filter-assignmnet" src/ --type ts --type tsx 2>/dev/null | head -20

# Find all assignment-related folders
echo -e "\n=== Finding all assignment-related folders ==="
fd -t d "filter-assign" src/

# Check the actual file path
echo -e "\n=== Checking if file exists at mentioned path ==="
test -f "src/features/assignment/filter-assignmnet/lib/useAssignmentList.ts" && echo "File exists at typo path" || echo "File NOT found at typo path"

Repository: 2025-snowCode/snowCode-Client

Length of output: 457


🏁 Script executed:

#!/bin/bash
# Check folder contents and verify if it's being used

echo "=== Contents of folder with typo ==="
find src/features/assignment/filter-assignmnet -type f | head -20

echo -e "\n=== Checking for any imports of files from this folder ==="
rg "from.*filter-assignmnet" src/ --type ts --type tsx 2>/dev/null || echo "No imports found"

echo -e "\n=== Checking for useAssignmentList imports (broader search) ==="
rg "useAssignmentList" src/ --type ts --type tsx 2>/dev/null || echo "No useAssignmentList imports found"

echo -e "\n=== Checking folder structure ==="
tree src/features/assignment/filter-assignmnet/ 2>/dev/null || find src/features/assignment/filter-assignmnet -type f -exec echo {} \;

Repository: 2025-snowCode/snowCode-Client

Length of output: 491


폴더명 오타: filter-assignmnetfilter-assignment로 수정 필요

src/features/assignment/ 하위의 filter-assignmnet 폴더명에 철자 오류가 있습니다. 현재 이 모듈은 아직 프로젝트 내에서 사용되지 않고 있으므로, 지금 수정하면 영향도가 없을 것으로 보입니다.

수정사항:

  • 폴더명: filter-assignmnet/filter-assignment/
  • 이에 따라 import 경로도 함께 업데이트 필요

coding guidelines 에서 규정한 kebab-case 명명 규칙을 따르되, 정확한 철자 사용도 중요합니다. 초기 단계에서 수정하는 것이 좋습니다! ✨

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

In `@src/features/assignment/filter-assignmnet/lib/useAssignmentList.ts` around
lines 1 - 3, The folder name contains a typo: rename the directory from
"filter-assignmnet" to "filter-assignment" and update all imports referencing
that module (e.g., the module that exports useAssignmentList in
useAssignmentList.ts and any import lines like import {useQuery}... from
'@/features/assignment/filter-assignmnet/...') to the corrected kebab-case path;
ensure your VCS commit records the folder rename (not a delete/add) so imports
(and any path aliases) are updated consistently across the codebase.


export const ListRowStyles = tv({
base: 'cursor-pointer bg-background w-full flex items-center rounded-[9px] pl-4.5 pr-7.5 py-4 gap-4 border',
base: 'bg-background w-full flex items-center rounded-[9px] pl-4.5 pr-7.5 py-4 gap-4 border',
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# ListRow 파일 찾기 및 구조 확인
fd 'list-row' --type f

# ListRow 스타일 파일 전체 내용 확인
cat -n src/shared/ui/list-row/list-row-styles.ts

# ListRow 컴포넌트 파일 확인
cat -n src/shared/ui/list-row/list-row.tsx 2>/dev/null || echo "Component file not found at expected location"

# ListRow 사용처 검색
rg -n '<ListRow' --type=tsx --type=ts -A3 -B1

# cursor 관련 스타일이 다른 곳에서 적용되는지 확인
rg -n 'cursor-pointer' src/shared/ui/list-row/ --type=ts --type=tsx

Repository: 2025-snowCode/snowCode-Client

Length of output: 758


🏁 Script executed:

# list-row 관련 모든 파일 찾기 (확장자 제한 없음)
find . -path ./node_modules -prune -o -name '*list-row*' -type f -print

# ListRow 컴포넌트 파일 내용 확인
find . -path ./node_modules -prune -o -name 'list-row*' -type f -exec head -30 {} \; -print

# ListRow 사용처 검색 (더 광범위한 검색)
rg 'ListRow' -A3 -B1 --max-count=20

# 커서 포인터 관련 스타일이 부모나 래퍼에서 적용되는지 확인
rg 'cursor-pointer' --max-count=20

# ListRow를 감싸는 컨테이너 패턴 확인
rg '<ListRow|className.*cursor' -A2 -B2 --max-count=30

Repository: 2025-snowCode/snowCode-Client

Length of output: 12211


ListRow 기본 스타일에 cursor-pointer 재추가 또는 사용처에서 명시적으로 적용 필요

현재 코드에서 cursor-pointer가 기본 스타일에서 제거되어 있으나, ListRow가 사용되는 대부분의 컨텍스트에서 일관성 있게 처리되지 않고 있습니다:

  • UnitAssignmentList.tsx: cursor-grab 클래스로 오버라이드 (드래그/드롭 시나리오)
  • AssignmentSelectPage.tsx, AssignmentManagePage.tsx: 커서 스타일 완전히 없음 (선택 가능한 리스트이지만)

제안:

  1. 옵션 A (권장): ListRow 기본 스타일에 cursor-pointer 유지 → UnitAssignmentList에서만 cursor-grab으로 오버라이드

    base: '...cursor-pointer...'
    // UnitAssignmentList에서: className=`cursor-grab ...`
    
  2. 옵션 B: 현재 방식 유지 → AssignmentSelectPage, AssignmentManagePage에서 명시적으로 cursor-pointer 추가

이유: 선택/클릭 가능한 UI 요소는 사용자에게 상호작용 가능성을 시각적으로 알려야 합니다. 현재 상태는 UX 관점에서 사용자 혼동을 초래할 수 있습니다. Tailwind CSS 상호작용 상태 문서를 참고하세요.

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

In `@src/shared/ui/list-row/list-row-styles.ts` at line 4, The ListRow base style
removed cursor-pointer causing inconsistent interactive affordance; restore
cursor-pointer in the ListRow style definition (the "base" entry in
src/shared/ui/list-row/list-row-styles.ts) so clickable rows default to pointer,
and leave UnitAssignmentList to explicitly override with cursor-grab via its
className when needed; alternatively, if you prefer the current removal, then
add cursor-pointer to the className usages in AssignmentSelectPage and
AssignmentManagePage where rows are selectable (reference the "base" symbol in
list-row-styles and the UnitAssignmentList, AssignmentSelectPage,
AssignmentManagePage components to locate places to adjust).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
src/pages/select-assignment/ui/AssignmentListContainer.tsx (1)

7-22: ⚠️ Potential issue | 🟠 Major

onSelecthandleSelect를 제네릭 T로 통일하세요.

Line 5에서 type T = AssignmentSelectCourse['assignments'][number]로 선언했지만, Line 11과 Line 20에서 콜백을 Assignment로 고정하면 제네릭의 의도가 훼손됩니다. 실제 데이터는 Pick<Assignment, 'id' | 'title'> 타입이므로 submittedStatus 필드를 기대하지만 런타임에는 존재하지 않습니다. onSelect/handleSelectT로 맞추면 타입 안전성과 재사용성이 개선됩니다.

🔧 제안 diff
 import type {Assignment} from '@/entities/assignment/model/types';
 import type {AssignmentSelectCourse} from '@/entities/course/model/types';
 import type {MouseEvent, ReactNode} from 'react';
 
 type T = AssignmentSelectCourse['assignments'][number];
 
 interface AssignmentListContainerProps<T> {
   items: T[];
   renderItem: (item: T) => ReactNode;
   title: string;
-  onSelect?: (item: Assignment) => void;
+  onSelect?: (item: T) => void;
 }
 
 const AssignmentListContainer = ({
   onSelect,
   items,
   renderItem,
   title,
 }: AssignmentListContainerProps<T>) => {
-  const handleSelect = (item: Assignment, event: MouseEvent) => {
+  const handleSelect = (item: T, event: MouseEvent) => {
     event.stopPropagation();
     onSelect?.(item);
   };

TypeScript 제네릭 핸드북 참고

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

In `@src/pages/select-assignment/ui/AssignmentListContainer.tsx` around lines 7 -
22, The onSelect and handleSelect signatures are incorrectly fixed to Assignment
which breaks the generic T; update AssignmentListContainerProps's onSelect to
onSelect?: (item: T) => void and change the handleSelect parameter from (item:
Assignment, event: MouseEvent) to (item: T, event: MouseEvent) inside
AssignmentListContainer so both the prop and internal handler use the generic T
(adjust any related types/imports if needed) to restore type safety and
reusability for AssignmentListContainerProps, onSelect, and handleSelect.
🧹 Nitpick comments (2)
src/pages/select-assignment/AssignmentSelectPage.tsx (2)

67-69: onCancel은 함수 참조를 바로 넘겨도 됩니다.

Line 67-69는 동작은 동일하지만 래퍼를 제거하면 더 읽기 쉽습니다.

🧹 제안 diff
-      onCancel={() => {
-        returnToPreviousPage();
-      }}
+      onCancel={returnToPreviousPage}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/select-assignment/AssignmentSelectPage.tsx` around lines 67 - 69,
The onCancel prop is passing a wrapper function that just calls
returnToPreviousPage(); replace the wrapper with a direct function reference by
passing returnToPreviousPage to the onCancel prop (locate the onCancel usage in
AssignmentSelectPage.tsx and remove the arrow wrapper so
onCancel={returnToPreviousPage}).

16-24: useQuery의 로딩/에러 상태를 명시적으로 처리하세요.

현재 코드는 data만 추출하고 isPending, isError를 사용하지 않아, 로딩 중, 요청 실패, 빈 목록을 모두 동일하게 처리합니다. 사용자 입장에서 원인을 알 수 없으므로 명확한 상태 분기가 필요합니다:

const {data: courseList, isPending, isError} = useQuery(courseQueries.getAllCourses());

이후 AssignmentPageLayout에 로딩/에러 상태를 전달하여 UI에 반영하면 UX가 훨씬 명확해집니다. 참고: TanStack Query - Queries 가이드

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

In `@src/pages/select-assignment/AssignmentSelectPage.tsx` around lines 16 - 24,
Extract loading and error flags from the useQuery call (e.g., const {data:
courseList, isPending, isError} = useQuery(courseQueries.getAllCourses())) and
propagate them into the component UI flow—pass isPending/isError (or derived
flags like isLoading/hasError) into AssignmentPageLayout and/or render explicit
branches for loading, error, and empty states instead of only using courseList;
update any logic that calls useCourseFilter/courseOptions/select handlers to
tolerate undefined courseList while loading or show an appropriate placeholder
message when isError is true.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/pages/select-assignment/ui/AssignmentListContainer.tsx`:
- Around line 29-31: The clickable target is currently the <li> with onClick,
which prevents keyboard activation; update the markup so the interactive element
is a button inside the list item: keep the <li> wrapper (key stays on item.id)
but move the onClick={(e) => handleSelect(item, e)} onto a <button
type="button"> that wraps or contains renderItem(item) (or adjust renderItem to
return the button) and ensure handleSelect is called from the button; reference
AssignmentListContainer.tsx, the handleSelect function, renderItem, and the <li>
element when making this change.

In `@src/pages/unit-editor/ui/UnitForm.tsx`:
- Around line 39-40: The UnitAssignmentList in creating mode keeps stale
internal state because it initializes via useState(assignmentList) and the
parent uses a fixed key 'creating-assignments' so React won't remount it; change
the key generation (assignmentListKey) to include a value that changes when the
selected assignments change (e.g., include a stable representation of
assignmentList such as assignmentList.map(a => a.id).join(',') or a
selectionVersion/timestamp) so mode === 'creating' produces a different key when
selections change, or alternatively remove the one-time useState pattern and
sync UnitAssignmentList to prop changes (add a useEffect to update internal
state when the assignmentList prop changes). Ensure you update the
assignmentListKey logic where it’s defined and/or adjust UnitAssignmentList’s
internal initialization to keep UI in sync.

In `@src/pages/unit-editor/UnitEditorPage.tsx`:
- Around line 20-22: 초안 감지 로직이 제목(storedTitle)과 과제(storedAssignments)만 검사하고 있어
공개일/마감일이 채워져 있을 때도 생성 중으로 처리되지 않습니다; useUnitStore에서 꺼내는 공개일/마감일 필드(예:
storedReleaseDate, storedDueDate)를 초안 판정 조건에 추가하여 "제목 또는 과제 또는 공개일 또는 마감일 중 하나라도
존재하면 생성 중" 으로 판단하도록 변경하고 동일한 변경을 두 번째 검사 지점(현재 30-32번 블록에 해당하는 조건)에도 적용하세요.
- Line 16: The code converts id to courseId with const courseId = Number(id) but
doesn't prevent queries when id is missing; update the queries that use courseId
(e.g., the getUnitDetails query and any other use sites) to use TanStack Query's
enabled flag so they only run when the id/Number(id) is valid (e.g., enabled: ()
=> Number.isFinite(courseId) or a shared isValidCourseId check). Replace the
existing ad-hoc validation later in the file with the shared isValidCourseId
predicate and use that in the enabled condition to keep validation consistent
across the component.

---

Outside diff comments:
In `@src/pages/select-assignment/ui/AssignmentListContainer.tsx`:
- Around line 7-22: The onSelect and handleSelect signatures are incorrectly
fixed to Assignment which breaks the generic T; update
AssignmentListContainerProps's onSelect to onSelect?: (item: T) => void and
change the handleSelect parameter from (item: Assignment, event: MouseEvent) to
(item: T, event: MouseEvent) inside AssignmentListContainer so both the prop and
internal handler use the generic T (adjust any related types/imports if needed)
to restore type safety and reusability for AssignmentListContainerProps,
onSelect, and handleSelect.

---

Nitpick comments:
In `@src/pages/select-assignment/AssignmentSelectPage.tsx`:
- Around line 67-69: The onCancel prop is passing a wrapper function that just
calls returnToPreviousPage(); replace the wrapper with a direct function
reference by passing returnToPreviousPage to the onCancel prop (locate the
onCancel usage in AssignmentSelectPage.tsx and remove the arrow wrapper so
onCancel={returnToPreviousPage}).
- Around line 16-24: Extract loading and error flags from the useQuery call
(e.g., const {data: courseList, isPending, isError} =
useQuery(courseQueries.getAllCourses())) and propagate them into the component
UI flow—pass isPending/isError (or derived flags like isLoading/hasError) into
AssignmentPageLayout and/or render explicit branches for loading, error, and
empty states instead of only using courseList; update any logic that calls
useCourseFilter/courseOptions/select handlers to tolerate undefined courseList
while loading or show an appropriate placeholder message when isError is true.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e7ea6cb and 52b6f14.

📒 Files selected for processing (9)
  • src/entities/unit/api/unitQueries.ts
  • src/pages/manage-assignment/AssignmentManagePage.tsx
  • src/pages/select-assignment/AssignmentSelectPage.tsx
  • src/pages/select-assignment/ui/AssignmentListContainer.tsx
  • src/pages/unit-editor/UnitEditorPage.tsx
  • src/pages/unit-editor/model/types.ts
  • src/pages/unit-editor/ui/UnitForm.tsx
  • src/pages/unit-editor/ui/UnitList.tsx
  • src/shared/ui/button/Button.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/manage-assignment/AssignmentManagePage.tsx

Comment on lines +29 to 31
<li onClick={(e) => handleSelect(item, e)} key={item.id}>
{renderItem(item)}
</li>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

클릭 타깃이 li라서 키보드 선택이 막힙니다.

Line 29에서 lionClick만 걸면 키보드 사용자(Enter/Space) 접근이 어렵습니다. button을 사용해 상호작용 요소를 명확히 해주세요.
참고: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button

♿ 제안 diff
-          <li onClick={(e) => handleSelect(item, e)} key={item.id}>
-            {renderItem(item)}
-          </li>
+          <li key={item.id}>
+            <button
+              type='button'
+              onClick={(e) => handleSelect(item, e)}
+              className='w-full text-left'
+            >
+              {renderItem(item)}
+            </button>
+          </li>
📝 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
<li onClick={(e) => handleSelect(item, e)} key={item.id}>
{renderItem(item)}
</li>
<li key={item.id}>
<button
type='button'
onClick={(e) => handleSelect(item, e)}
className='w-full text-left'
>
{renderItem(item)}
</button>
</li>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/select-assignment/ui/AssignmentListContainer.tsx` around lines 29 -
31, The clickable target is currently the <li> with onClick, which prevents
keyboard activation; update the markup so the interactive element is a button
inside the list item: keep the <li> wrapper (key stays on item.id) but move the
onClick={(e) => handleSelect(item, e)} onto a <button type="button"> that wraps
or contains renderItem(item) (or adjust renderItem to return the button) and
ensure handleSelect is called from the button; reference
AssignmentListContainer.tsx, the handleSelect function, renderItem, and the <li>
element when making this change.

Comment on lines +39 to +40
const assignmentListKey =
mode === 'editing' ? `edit-${unit?.id}` : 'creating-assignments';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

creating 모드에서 과제 재선택 시 목록이 이전 상태로 고정될 수 있습니다

UnitAssignmentList가 초기 prop으로 내부 state를 만들고(useState(assignmentList)), 현재 key는 creating-assignments로 고정이라 재마운트가 안 됩니다. 과제를 다시 고르면 UI가 최신 선택을 반영하지 못할 수 있습니다.
React 공식 문서(키로 state 리셋): https://react.dev/learn/preserving-and-resetting-state

🔧 제안 수정안
-  const assignmentListKey =
-    mode === 'editing' ? `edit-${unit?.id}` : 'creating-assignments';
+  const assignmentListKey =
+    mode === 'editing'
+      ? `edit-${unit?.id ?? 'unknown'}-${(unit?.assignments ?? []).map((a) => a.id).join(',')}`
+      : `creating-${storedAssignments.map((a) => a.id).join(',')}`;

Also applies to: 156-160

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

In `@src/pages/unit-editor/ui/UnitForm.tsx` around lines 39 - 40, The
UnitAssignmentList in creating mode keeps stale internal state because it
initializes via useState(assignmentList) and the parent uses a fixed key
'creating-assignments' so React won't remount it; change the key generation
(assignmentListKey) to include a value that changes when the selected
assignments change (e.g., include a stable representation of assignmentList such
as assignmentList.map(a => a.id).join(',') or a selectionVersion/timestamp) so
mode === 'creating' produces a different key when selections change, or
alternatively remove the one-time useState pattern and sync UnitAssignmentList
to prop changes (add a useEffect to update internal state when the
assignmentList prop changes). Ensure you update the assignmentListKey logic
where it’s defined and/or adjust UnitAssignmentList’s internal initialization to
keep UI in sync.


const UnitEditorPage = () => {
const {id} = useParams(); // 강의 ID
const courseId = Number(id);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd 'UnitEditorPage.tsx' --type f

Repository: 2025-snowCode/snowCode-Client

Length of output: 113


🏁 Script executed:

cat -n src/pages/unit-editor/UnitEditorPage.tsx | head -150

Repository: 2025-snowCode/snowCode-Client

Length of output: 5459


🏁 Script executed:

# Search for similar patterns with TanStack Query 'enabled' in the codebase
rg -A 3 'enabled:\s*(true|false|[a-zA-Z])' --type tsx --type ts

Repository: 2025-snowCode/snowCode-Client

Length of output: 100


🏁 Script executed:

# Check for other useQuery patterns with id validation
rg 'useQuery.*id|Number\(id\)' --type tsx -B 5 -A 5

Repository: 2025-snowCode/snowCode-Client

Length of output: 100


🏁 Script executed:

# Check how unitQueries.getUnitList is structured
fd 'unitQueries' --type f

Repository: 2025-snowCode/snowCode-Client

Length of output: 109


🏁 Script executed:

# Look at the unitQueries file
cat -n src/entities/unit/api/unitQueries.ts | head -40

Repository: 2025-snowCode/snowCode-Client

Length of output: 1030


courseId가 유효하지 않을 때도 목록 조회가 실행됩니다

id 검증이 선행되지 않아 id가 없을 때 Number(undefined) = NaN이 되고, 이 값으로 API 요청이 발생합니다. 다른 쿼리(getUnitDetails)에서 사용하는 enabled 패턴을 적용해 이를 방지하세요.

TanStack Query 가이드: https://tanstack.com/query/latest/docs/framework/react/guides/disabling-queries

🔧 제안 수정안
   const {id} = useParams(); // 강의 ID
   const courseId = Number(id);
+  const isValidCourseId = Number.isInteger(courseId) && courseId > 0;

-  const {data} = useQuery(unitQueries.getUnitList(courseId));
+  const {data} = useQuery({
+    ...unitQueries.getUnitList(courseId),
+    enabled: isValidCourseId,
+  });

또한 라인 133의 검증을 isValidCourseId로 변경하면 더욱 일관성 있습니다.

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

In `@src/pages/unit-editor/UnitEditorPage.tsx` at line 16, The code converts id to
courseId with const courseId = Number(id) but doesn't prevent queries when id is
missing; update the queries that use courseId (e.g., the getUnitDetails query
and any other use sites) to use TanStack Query's enabled flag so they only run
when the id/Number(id) is valid (e.g., enabled: () => Number.isFinite(courseId)
or a shared isValidCourseId check). Replace the existing ad-hoc validation later
in the file with the shared isValidCourseId predicate and use that in the
enabled condition to keep validation consistent across the component.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/pages/unit-editor/UnitEditorPage.tsx (1)

15-17: ⚠️ Potential issue | 🟠 Major

유효하지 않은 courseId로 쿼리/생성 요청이 실행될 수 있습니다

Line 16에서 Number(id)NaN이 될 수 있는데, Line 25 쿼리와 Line 124 생성 뮤테이션에 그대로 사용됩니다. !id 체크(Line 140)는 숫자 유효성까지 보장하지 못합니다. 공통 검증값(isValidCourseId)을 만들고 쿼리 enabled/생성 가드/렌더 가드에 동일하게 적용해 주세요.

🔧 제안 수정안
   const {id} = useParams(); // 강의 ID
   const courseId = Number(id);
+  const isValidCourseId = Number.isInteger(courseId) && courseId > 0;
 
-  const {data} = useQuery(unitQueries.getUnitList(courseId));
+  const {data} = useQuery({
+    ...unitQueries.getUnitList(courseId),
+    enabled: isValidCourseId,
+  });
@@
   const onCreateUnit = (unitForm: TUnitFormSchema) => {
+    if (!isValidCourseId) return;
     addUnit({courseId, unitForm});
   };
@@
-  if (!id) {
+  if (!isValidCourseId) {
     return <EmptyState>선택한 강의가 없습니다.</EmptyState>;
   }

공식 문서:

#!/bin/bash
# 검증 목적: courseId 유효성 검증 + enabled 적용 여부 확인
sed -n '1,220p' src/pages/unit-editor/UnitEditorPage.tsx
rg -n -C2 'const courseId = Number\(id\)|isValidCourseId|useQuery\(unitQueries\.getUnitList|enabled:\s*isValidCourseId|addUnit\(\{courseId' src/pages/unit-editor/UnitEditorPage.tsx

Also applies to: 25-27, 123-125, 139-141

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

In `@src/pages/unit-editor/UnitEditorPage.tsx` around lines 15 - 17, Create a
shared validity check for the route param instead of using Number(id) directly:
derive courseId = Number(id) and compute isValidCourseId =
Number.isInteger(courseId) && courseId > 0 (or whatever domain rule you need)
based on useParams(); then use isValidCourseId in the unitQueries.getUnitList
useQuery's enabled option, guard the addUnit mutation so it never runs when
isValidCourseId is false, and add a render guard (return early or show a
validation error) where the component currently checks !id so all three places
(query enabled, mutation invocation, and render) rely on the same
isValidCourseId boolean. Ensure you reference the same symbol names: courseId,
isValidCourseId, unitQueries.getUnitList, and addUnit.
src/pages/manage-assignment/AssignmentManagePage.tsx (1)

88-89: ⚠️ Potential issue | 🟡 Minor

빈 콜백 전달은 의도를 숨깁니다

Line [88]-[89]의 no-op 함수는 “의도적으로 비활성”인지 “구현 누락”인지 구분이 어렵습니다. AssignmentPageLayout에서 optional props로 처리하거나, 의도 주석과 함께 명시적으로 처리해 주세요.

공식 문서: https://www.typescriptlang.org/docs/handbook/2/objects.html#optional-properties

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

In `@src/pages/manage-assignment/AssignmentManagePage.tsx` around lines 88 - 89,
The empty inline callbacks passed as onCancel and onConfirm to
AssignmentPageLayout hide intent; either make these props optional in
AssignmentPageLayout's prop type (handle undefined inside AssignmentPageLayout)
or replace the inline no-ops with an explicit, documented noop handler (e.g.,
named noop or onCancelNoop/onConfirmNoop) so intent is clear; update the
AssignmentPageLayout prop definitions (and any callers) to allow optional
onCancel/onConfirm or use the documented noop constant and add a short comment
like "intentionally no-op" to clarify the behavior.
🧹 Nitpick comments (1)
src/pages/manage-assignment/AssignmentManagePage.tsx (1)

2-2: 절대 경로 import 규칙과 맞지 않습니다

Line [2]는 상대 경로 대신 alias 경로(@/...)로 맞추는 편이 프로젝트 규칙과 일관됩니다.

🔧 제안 diff
-import AssignmentListContainer from '../select-assignment/ui/AssignmentListContainer';
+import AssignmentListContainer from '@/pages/select-assignment/ui/AssignmentListContainer';

As per coding guidelines "절대 경로 임포트(@/...) 사용".

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

In `@src/pages/manage-assignment/AssignmentManagePage.tsx` at line 2, The import
in AssignmentManagePage.tsx uses a relative path for AssignmentListContainer;
update the import to use the project alias (start with '@/') instead of
'../select-assignment/ui/AssignmentListContainer' so it follows the absolute
import rule—replace the import statement that references AssignmentListContainer
in AssignmentManagePage.tsx with the corresponding alias path (e.g., import
AssignmentListContainer from
'@/pages/select-assignment/ui/AssignmentListContainer' or the repository's
root-alias variant) and ensure any ESLint/TS path mappings still resolve.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/features/assignment/filter-assignment/lib/useAssignmentList.ts`:
- Around line 12-21: The getAllAssignments query is always running and causing
extra network traffic; update the useQuery call that uses
assignmentQueries.getAllAssignments() to accept the shared queryOptions object
(spread into the second arg) and include an enabled flag that is true only when
selectedCourseId === null (i.e., enabled: selectedCourseId === null), mirror the
existing enabled pattern used for getAssignmentsByCourse, and change the return
selector so the conditional uses selectedCourseId !== null to decide between
(assignments ?? []) and (allAssignments ?? []) before passing into unique;
reference the useQuery calls, getAllAssignments, getAssignmentsByCourse,
selectedCourseId, and unique when making the change.

In `@src/pages/manage-assignment/AssignmentManagePage.tsx`:
- Around line 26-34: The onSuccess callback captures selectedCourseId causing
stale data if the user switches courses during deletion; replace the
closure-based invalidateQueries call for
assignmentQueries.getAssignmentsByCourse with a predicate-based invalidation so
you invalidate all course-specific assignment queries by matching the base query
key pattern (use queryClient.invalidateQueries with a predicate that detects
queries produced by assignmentQueries.getAssignmentsByCourse/getAllAssignments
rather than relying on selectedCourseId). Locate the onSuccess handler and
update the second invalidateQueries to use a predicate that inspects
query.queryKey to identify the getAssignmentsByCourse pattern (referencing
assignmentQueries.getAssignmentsByCourse and queryClient.invalidateQueries) and
invalidate all matching queries to avoid stale course-specific caches.

---

Duplicate comments:
In `@src/pages/manage-assignment/AssignmentManagePage.tsx`:
- Around line 88-89: The empty inline callbacks passed as onCancel and onConfirm
to AssignmentPageLayout hide intent; either make these props optional in
AssignmentPageLayout's prop type (handle undefined inside AssignmentPageLayout)
or replace the inline no-ops with an explicit, documented noop handler (e.g.,
named noop or onCancelNoop/onConfirmNoop) so intent is clear; update the
AssignmentPageLayout prop definitions (and any callers) to allow optional
onCancel/onConfirm or use the documented noop constant and add a short comment
like "intentionally no-op" to clarify the behavior.

In `@src/pages/unit-editor/UnitEditorPage.tsx`:
- Around line 15-17: Create a shared validity check for the route param instead
of using Number(id) directly: derive courseId = Number(id) and compute
isValidCourseId = Number.isInteger(courseId) && courseId > 0 (or whatever domain
rule you need) based on useParams(); then use isValidCourseId in the
unitQueries.getUnitList useQuery's enabled option, guard the addUnit mutation so
it never runs when isValidCourseId is false, and add a render guard (return
early or show a validation error) where the component currently checks !id so
all three places (query enabled, mutation invocation, and render) rely on the
same isValidCourseId boolean. Ensure you reference the same symbol names:
courseId, isValidCourseId, unitQueries.getUnitList, and addUnit.

---

Nitpick comments:
In `@src/pages/manage-assignment/AssignmentManagePage.tsx`:
- Line 2: The import in AssignmentManagePage.tsx uses a relative path for
AssignmentListContainer; update the import to use the project alias (start with
'@/') instead of '../select-assignment/ui/AssignmentListContainer' so it follows
the absolute import rule—replace the import statement that references
AssignmentListContainer in AssignmentManagePage.tsx with the corresponding alias
path (e.g., import AssignmentListContainer from
'@/pages/select-assignment/ui/AssignmentListContainer' or the repository's
root-alias variant) and ensure any ESLint/TS path mappings still resolve.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52b6f14 and d3ddd54.

📒 Files selected for processing (7)
  • src/App.tsx
  • src/entities/assignment/api/assignmentQueries.ts
  • src/entities/course/api/courseQueries.ts
  • src/features/assignment/filter-assignment/lib/useAssignmentList.ts
  • src/pages/manage-assignment/AssignmentManagePage.tsx
  • src/pages/select-assignment/AssignmentSelectPage.tsx
  • src/pages/unit-editor/UnitEditorPage.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/entities/course/api/courseQueries.ts
  • src/entities/assignment/api/assignmentQueries.ts
  • src/App.tsx

Comment on lines +12 to +21
const {data: allAssignments} = useQuery(
assignmentQueries.getAllAssignments()
);
const {data: assignments} = useQuery(
assignmentQueries.getAssignmentsByCourse(selectedCourseId ?? 0)
);

return unique(
selectedCourseId ? (assignments ?? []) : (allAssignments ?? [])
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/features/assignment/filter-assignment/lib/useAssignmentList.ts

Repository: 2025-snowCode/snowCode-Client

Length of output: 917


🏁 Script executed:

# assignmentQueries 정의 확인
find . -type f -name "*.ts" -o -name "*.tsx" | xargs grep -l "assignmentQueries" | head -5

Repository: 2025-snowCode/snowCode-Client

Length of output: 283


🏁 Script executed:

# useQuery 사용 패턴 확인
rg "useQuery" --type ts --type tsx -A 3 | head -50

Repository: 2025-snowCode/snowCode-Client

Length of output: 100


🏁 Script executed:

cat -n src/entities/assignment/api/assignmentQueries.ts

Repository: 2025-snowCode/snowCode-Client

Length of output: 1178


🏁 Script executed:

cat -n package.json | grep -A 2 -B 2 "react-query\|@tanstack"

Repository: 2025-snowCode/snowCode-Client

Length of output: 493


🏁 Script executed:

# Check how assignmentQueries returns query config
rg "getAllAssignments|getAssignmentsByCourse" --type ts -A 5

Repository: 2025-snowCode/snowCode-Client

Length of output: 4462


🏁 Script executed:

# Verify that queryOptions can be spread and extended
rg "useQuery.*\.\.\." --type ts -A 2 | head -20

Repository: 2025-snowCode/snowCode-Client

Length of output: 55


🏁 Script executed:

# Check if there are other examples of spreading queryOptions in the codebase
rg "queryOptions" --type ts -A 3 | grep -E "enabled|spread|\.\.\." | head -20

Repository: 2025-snowCode/snowCode-Client

Length of output: 210


getAllAssignments 쿼리가 항상 실행되어 불필요한 네트워크 비용이 발생합니다

현재 구현에서 getAllAssignments는 과제 필터 선택 여부와 관계없이 항상 활성화됩니다. getAssignmentsByCourse에는 이미 enabled 옵션이 있지만, getAllAssignments에는 없어서 필터가 선택될 때 두 쿼리가 동시에 실행됩니다.

queryOptions 객체를 spread하여 enabled 조건을 명시적으로 제어하고, Line 20의 truthy 체크를 selectedCourseId !== null로 변경해 주세요:

🔧 제안 diff
 export const useAssignmentList = (
   selectedCourseId: number | null
 ): Assignment[] => {
-  const {data: allAssignments} = useQuery(
-    assignmentQueries.getAllAssignments()
-  );
-  const {data: assignments} = useQuery(
-    assignmentQueries.getAssignmentsByCourse(selectedCourseId ?? 0)
-  );
+  const {data: allAssignments} = useQuery({
+    ...assignmentQueries.getAllAssignments(),
+    enabled: selectedCourseId === null,
+  });
+  const {data: assignments} = useQuery({
+    ...assignmentQueries.getAssignmentsByCourse(selectedCourseId ?? 0),
+    enabled: selectedCourseId !== null,
+  });
 
   return unique(
-    selectedCourseId ? (assignments ?? []) : (allAssignments ?? [])
+    selectedCourseId !== null ? (assignments ?? []) : (allAssignments ?? [])
   );
 };

참고: React Query - Disabling Queries

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

In `@src/features/assignment/filter-assignment/lib/useAssignmentList.ts` around
lines 12 - 21, The getAllAssignments query is always running and causing extra
network traffic; update the useQuery call that uses
assignmentQueries.getAllAssignments() to accept the shared queryOptions object
(spread into the second arg) and include an enabled flag that is true only when
selectedCourseId === null (i.e., enabled: selectedCourseId === null), mirror the
existing enabled pattern used for getAssignmentsByCourse, and change the return
selector so the conditional uses selectedCourseId !== null to decide between
(assignments ?? []) and (allAssignments ?? []) before passing into unique;
reference the useQuery calls, getAllAssignments, getAssignmentsByCourse,
selectedCourseId, and unique when making the change.

Comment on lines +26 to +34
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: assignmentQueries.getAllAssignments().queryKey,
});
queryClient.invalidateQueries({
queryKey: assignmentQueries.getAssignmentsByCourse(
selectedCourseId ?? 0
).queryKey,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the AssignmentManagePage.tsx file
find . -name "AssignmentManagePage.tsx" -type f

Repository: 2025-snowCode/snowCode-Client

Length of output: 127


🏁 Script executed:

# Also find the query definition files
find . -name "*queries*" -o -name "*query*" | grep -E "\.(ts|tsx)$" | head -20

Repository: 2025-snowCode/snowCode-Client

Length of output: 960


🏁 Script executed:

# Let's search more specifically in src for query definitions
find ./src -type f \( -name "*query*" -o -name "*queries*" \) | head -20

Repository: 2025-snowCode/snowCode-Client

Length of output: 55


🏁 Script executed:

# Now let's examine the AssignmentManagePage.tsx file
cat -n ./src/pages/manage-assignment/AssignmentManagePage.tsx

Repository: 2025-snowCode/snowCode-Client

Length of output: 3760


🏁 Script executed:

# Find the assignmentQueries definition
find ./src -type f -name "*.ts" -o -name "*.tsx" | xargs grep -l "assignmentQueries\|getAssignmentsByCourse" | head -10

Repository: 2025-snowCode/snowCode-Client

Length of output: 330


🏁 Script executed:

# Let's look for the api folder structure
find ./src/entities/assignment -type f -name "*.ts" -o -name "*.tsx" 2>/dev/null | head -20

Repository: 2025-snowCode/snowCode-Client

Length of output: 299


🏁 Script executed:

# Let's examine the assignmentQueries file to understand the query key structure
cat -n ./src/entities/assignment/api/assignmentQueries.ts

Repository: 2025-snowCode/snowCode-Client

Length of output: 1178


🏁 Script executed:

# Also check assignmentMutations to understand the deletion flow
cat -n ./src/entities/assignment/api/assignmentMutations.ts

Repository: 2025-snowCode/snowCode-Client

Length of output: 359


🏁 Script executed:

# Let's check the useCourseFilter hook to understand selectedCourseId management
find ./src -type f -name "*.ts" -o -name "*.tsx" | xargs grep -l "useCourseFilter" | head -5

Repository: 2025-snowCode/snowCode-Client

Length of output: 286


🏁 Script executed:

# Look for other mutation invalidations in the codebase to check patterns
grep -r "invalidateQueries" ./src --include="*.ts" --include="*.tsx" -B 3 -A 5 | head -80

Repository: 2025-snowCode/snowCode-Client

Length of output: 2564


🏁 Script executed:

# Let's examine the useCourseFilter hook
cat -n ./src/features/course/filter-course/lib/useCourseFilter.ts

Repository: 2025-snowCode/snowCode-Client

Length of output: 1562


🏁 Script executed:

# Let's also check if there are any other query patterns in the codebase using predicates
grep -r "predicate:" ./src --include="*.ts" --include="*.tsx" -B 2 -A 2

Repository: 2025-snowCode/snowCode-Client

Length of output: 55


강의 전환 시 선택적 캐시 무효화로 인한 스테일 데이터 위험

Lines [30]-[34]의 selectedCourseId 클로저 캡처가 문제입니다. 삭제 요청 중 사용자가 강의를 바꾸면, onSuccess 콜백은 여전히 이전 강의의 캐시 키를 무효화하게 되어 새로운 강의의 과제 목록이 스테일 상태로 남을 수 있습니다.

TanStack Query의 predicate 기반 무효화로 강의별 과제 쿼리 전체를 패턴 매칭해서 안전하게 처리하세요:

🔧 제안 diff
     onSuccess: () => {
       queryClient.invalidateQueries({
         queryKey: assignmentQueries.getAllAssignments().queryKey,
       });
       queryClient.invalidateQueries({
-        queryKey: assignmentQueries.getAssignmentsByCourse(
-          selectedCourseId ?? 0
-        ).queryKey,
+        predicate: (query) =>
+          query.queryKey[0] === 'courses' && query.queryKey[2] === 'assignments',
       });
       alert('문제가 성공적으로 삭제되었습니다.');
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/manage-assignment/AssignmentManagePage.tsx` around lines 26 - 34,
The onSuccess callback captures selectedCourseId causing stale data if the user
switches courses during deletion; replace the closure-based invalidateQueries
call for assignmentQueries.getAssignmentsByCourse with a predicate-based
invalidation so you invalidate all course-specific assignment queries by
matching the base query key pattern (use queryClient.invalidateQueries with a
predicate that detects queries produced by
assignmentQueries.getAssignmentsByCourse/getAllAssignments rather than relying
on selectedCourseId). Locate the onSuccess handler and update the second
invalidateQueries to use a predicate that inspects query.queryKey to identify
the getAssignmentsByCourse pattern (referencing
assignmentQueries.getAssignmentsByCourse and queryClient.invalidateQueries) and
invalidate all matching queries to avoid stale course-specific caches.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/pages/manage-assignment/AssignmentManagePage.tsx (1)

32-40: ⚠️ Potential issue | 🟠 Major

강의별 과제 캐시 무효화 범위는 이전 리뷰 이슈와 동일합니다.

selectedCourseId 단일 키 기반 무효화는 강의 전환 타이밍에 누락 가능성이 있어, 강의별 과제 키 패턴 전체 무효화 방식이 더 안전합니다.

무효화 범위 개선 제안
       queryClient.invalidateQueries({
-        queryKey: assignmentQueries.getAssignmentsByCourse(
-          selectedCourseId ?? 0
-        ).queryKey,
+        predicate: (query) =>
+          query.queryKey[0] === 'courses' && query.queryKey[2] === 'assignments',
       });
TanStack Query v5에서 invalidateQueries({ queryKey: ['courses', id, 'assignments'] })는 기본적으로 어느 범위(정확 일치 vs 부분 일치)를 무효화하나요? 그리고 강의 전환 같은 동시성 상황에서 predicate 기반 무효화가 권장되는 공식 가이드는 무엇인가요?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/manage-assignment/AssignmentManagePage.tsx` around lines 32 - 40,
The current onSuccess invalidation narrows the course-specific cache by a single
selectedCourseId which can miss concurrent course switches; replace the
single-key invalidation with a pattern/predicate-based invalidation to cover all
per-course assignment queries: call queryClient.invalidateQueries with either a
matching queryKey pattern for assignmentQueries.getAssignmentsByCourse (e.g.,
the common prefix that identifies course assignments) or with a predicate that
checks each query.queryKey to detect the assignments key shape (for example,
inspect query.queryKey elements to find the course-assignment identifier) so all
course-specific assignment caches are invalidated safely regardless of
selectedCourseId timing.
🧹 Nitpick comments (1)
src/pages/manage-assignment/AssignmentManagePage.tsx (1)

2-2: 상대 경로 import를 절대 경로로 통일해 주세요.

현재 경로 하나만 상대 경로라 일관성이 깨집니다. @/...로 맞추는 편이 유지보수에 안전합니다.

경로 통일 제안
-import AssignmentListContainer from '../select-assignment/ui/AssignmentListContainer';
+import AssignmentListContainer from '@/pages/select-assignment/ui/AssignmentListContainer';

As per coding guidelines 절대 경로 임포트(@/...) 사용.

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

In `@src/pages/manage-assignment/AssignmentManagePage.tsx` at line 2, The import
for AssignmentListContainer is using a relative path; change it to the project's
absolute import convention (use
'@/select-assignment/ui/AssignmentListContainer') so it matches other imports
and restores consistency. Update the import statement that references
AssignmentListContainer accordingly and run a quick build to ensure module
resolution still works.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/pages/manage-assignment/AssignmentManagePage.tsx`:
- Around line 32-40: The current onSuccess invalidation narrows the
course-specific cache by a single selectedCourseId which can miss concurrent
course switches; replace the single-key invalidation with a
pattern/predicate-based invalidation to cover all per-course assignment queries:
call queryClient.invalidateQueries with either a matching queryKey pattern for
assignmentQueries.getAssignmentsByCourse (e.g., the common prefix that
identifies course assignments) or with a predicate that checks each
query.queryKey to detect the assignments key shape (for example, inspect
query.queryKey elements to find the course-assignment identifier) so all
course-specific assignment caches are invalidated safely regardless of
selectedCourseId timing.

---

Nitpick comments:
In `@src/pages/manage-assignment/AssignmentManagePage.tsx`:
- Line 2: The import for AssignmentListContainer is using a relative path;
change it to the project's absolute import convention (use
'@/select-assignment/ui/AssignmentListContainer') so it matches other imports
and restores consistency. Update the import statement that references
AssignmentListContainer accordingly and run a quick build to ensure module
resolution still works.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3ddd54 and e06d73e.

📒 Files selected for processing (5)
  • src/entities/assignment/api/assignmentQueries.ts
  • src/entities/course/api/courseQueries.ts
  • src/pages/dashboard/Dashboard.tsx
  • src/pages/manage-assignment/AssignmentManagePage.tsx
  • src/pages/select-assignment/AssignmentSelectPage.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/pages/select-assignment/AssignmentSelectPage.tsx
  • src/entities/course/api/courseQueries.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/pages/select-assignment/AssignmentSelectPage.tsx`:
- Around line 42-44: The returnToPreviousPage handler uses
navigate(location.state?.backPath ?? -1) which falls back to history.back (-1)
and can behave unpredictably when location.state is undefined (direct/bookmark
access); change it to use an explicit safe default path instead of -1 (e.g.,
navigate(location.state?.backPath ?? '/units' or another app-appropriate route)
in the returnToPreviousPage function so navigate() always receives a concrete
path; update the reference in AssignmentSelectPage (returnToPreviousPage,
navigate, location.state?.backPath) accordingly.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e06d73e and 6550556.

📒 Files selected for processing (3)
  • src/pages/manage-assignment/AssignmentManagePage.tsx
  • src/pages/select-assignment/AssignmentSelectPage.tsx
  • src/widgets/assignment-page-layout/ui/AssignmentPageLayout.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/manage-assignment/AssignmentManagePage.tsx

Comment on lines +42 to +44
const returnToPreviousPage = () => {
navigate(location.state?.backPath ?? -1);
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# 1. AssignmentSelectPage 파일 위치 및 내용 확인
fd -t f "AssignmentSelectPage" src

# 2. 해당 파일의 42-50줄 내용 확인
head -50 "src/pages/select-assignment/AssignmentSelectPage.tsx" | tail -20

# 3. backPath 사용 패턴 찾기
rg -n "backPath" src --context 2

# 4. AssignmentSelectPage로 이동하는 지점 찾기
rg -n "AssignmentSelectPage|select-assignment" src --context 2

# 5. navigate 호출 패턴 확인
rg -n "navigate.*AssignmentSelectPage" src --context 2

Repository: 2025-snowCode/snowCode-Client

Length of output: 3200


🏁 Script executed:

# 모든 'assignments/select' 경로 참조 확인
rg -n "assignments/select" src

# useNavigate 사용하여 이 페이지로 이동하는 모든 지점 확인
rg -n "navigate.*select" src --context 3

# location.state 사용 패턴 확인
rg -n "location\.state" src --context 2

Repository: 2025-snowCode/snowCode-Client

Length of output: 1137


navigate(-1) 기본값은 직접 URL 접근 시 불안정한 동작을 초래합니다

현재 코드는 UnitForm.tsx에서만 진입하므로 정상 작동하지만, 사용자가 URL을 직접 입력하거나 북마크로 접근하면 location.state가 undefined가 되어 -1로 이동합니다. 이는 브라우저 히스토리에 따라 예측 불가능하게 작동합니다. 명시적인 기본 경로를 제공하면 더 안전합니다.

참고: React Router useNavigate 문서

🔧 제안 수정안
+ const DEFAULT_BACK_PATH = '/';
+
  const returnToPreviousPage = () => {
-   navigate(location.state?.backPath ?? -1);
+   const backPath =
+     typeof location.state?.backPath === 'string'
+       ? location.state.backPath
+       : null;
+   navigate(backPath ?? DEFAULT_BACK_PATH);
  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/select-assignment/AssignmentSelectPage.tsx` around lines 42 - 44,
The returnToPreviousPage handler uses navigate(location.state?.backPath ?? -1)
which falls back to history.back (-1) and can behave unpredictably when
location.state is undefined (direct/bookmark access); change it to use an
explicit safe default path instead of -1 (e.g.,
navigate(location.state?.backPath ?? '/units' or another app-appropriate route)
in the returnToPreviousPage function so navigate() always receives a concrete
path; update the reference in AssignmentSelectPage (returnToPreviousPage,
navigate, location.state?.backPath) accordingly.

Copy link
Contributor

@JiiminHa JiiminHa left a comment

Choose a reason for hiding this comment

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

고생하셨습니다!

Copy link
Contributor

Choose a reason for hiding this comment

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

이 파일을 AssignmentManagePage.tsx에서도 참조하고 있으니 entities/assignment/ui나 shared/ui로 이동해서 관리해야 FSD 계층에서 안위반될 것 같아요!

Copy link
Contributor

Choose a reason for hiding this comment

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

뭔가 이 파일도 useMutation이랑 onSuccess처리가 직접 작성되어 있어서
커스텀 뮤테이션 훅으로 빼서 이 페이지에서는 호출만 하면 어떨까 해요!

Copy link
Contributor

Choose a reason for hiding this comment

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

지금

assignmentApi.ts
에서 서버 데이터를 가져올 때 TypeScript의 interface만 사용하고 있는데요,

추천하는 구조:

entities/assignment/model/schema.ts (신규 생성)

import { z } from 'zod';
// 서버 응답 스키마 정의
export const assignmentSchema = z.object({
  assignmentId: z.number(),
  title: z.string(),
});
// 스키마에서 타입을 자동으로 뽑아내기 (interface 대신 사용 가능!)
export type TAssignment = z.infer<typeof assignmentSchema>;

entities/assignment/api/assignmentApi.ts
 (수정)
// 기존 interface 대신 .parse() 사용
const raw = response.data.response;
const validated = z.array(assignmentSchema).parse(raw.assignments);
return {
  count: raw.count,
  assignments: validated.map(a => ({ id: a.assignmentId, title: a.title }))
};

이미 unit 도메인 쪽에서도 zod를 조금씩 쓰고 계시니까, API 응답 영역까지 확장해 보면 좋을 것 같아요!

// 과제 일정 조회 쿼리 옵션
getAssignmentSchedules: () =>
queryOptions({
queryKey: ['schedules'],
Copy link
Contributor

Choose a reason for hiding this comment

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

흠 이거 쿼리 키 그냥 따로 파일 만들고 얘도 그냥 상수로 여기에 쓸까하는데 어떠세요?
저번 회의 때 이렇게 하신 이유가 있었던 것 같은데 제가 기억이 안나서 다시 여쭤봅니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🧩 feature 기능 개발 🎨 ui CSS 등 사용자 UI 디자인 변경

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 과제 API 연동

2 participants