Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthrough관리자용 할당 관리 페이지와 관련 컴포넌트, API(과정·할당·유닛), React Query 쿼리/뮤테이션 래퍼, Zustand 기반 유닛 폼 퍼시스트 스토어, 그리고 여러 UI 타입/레이아웃 변경이 추가·통합되었습니다. Changes
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: 성공 알림
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: 폼에 반영
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
간단 피드백: 전체 구조 잘 정리되었어요 — 다만 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)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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: 6
🧹 Nitpick comments (10)
src/pages/select-assignment/ui/AssignmentListContainer.tsx (1)
4-10: 타입 불일치 가능성:T와Assignment타입이 다를 수 있습니다.
T는AssignmentSelectCourse['assignments'][number]로 정의되어 있고,onSelect는Assignment타입을 받습니다. 두 타입이 동일하다고 가정하고 있지만, 타입 시스템에서 이를 보장하지 않습니다. 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: 매개변수 네이밍 불일치:unitFormvsunit
createUnit은unitForm을 사용하고,updateUnit은unit을 사용합니다. 같은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 처리 개선
selectedCourseId가null일 때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
📒 Files selected for processing (25)
src/App.tsxsrc/entities/assignment/api/assignmentApi.tssrc/entities/assignment/api/assignmentMutations.tssrc/entities/assignment/api/assignmentQueries.tssrc/entities/assignment/api/assignmentQueryOptions.tssrc/entities/assignment/model/types.tssrc/entities/course/api/courseApi.tssrc/entities/course/api/courseMutations.tssrc/entities/course/api/courseQueries.tssrc/entities/course/api/courseQueryOptions.tssrc/entities/course/index.tssrc/entities/unit/api/unitApi.tssrc/entities/unit/api/unitMutations.tssrc/entities/unit/model/useUnitStore.tssrc/features/assignment/filter-assignmnet/lib/useAssignmentList.tssrc/pages/dashboard/Dashboard.tsxsrc/pages/manage-assignment/AssignmentManagePage.tsxsrc/pages/manage-assignment/ui/AssignmentManageActionsBar.tsxsrc/pages/select-assignment/AssignmentSelectPage.tsxsrc/pages/select-assignment/ui/AssignmentListContainer.tsxsrc/pages/unit-editor/UnitEditorPage.tsxsrc/pages/unit-editor/ui/UnitForm.tsxsrc/shared/ui/Layout.tsxsrc/shared/ui/list-row/ListRow.tsxsrc/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
| import {useQuery} from '@tanstack/react-query'; | ||
| import {assignmentQueries} from '@/entities/assignment/api/assignmentQueries'; | ||
| import type {Assignment} from '@/entities/assignment/model/types'; |
There was a problem hiding this comment.
🧩 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-assignmnet → filter-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', |
There was a problem hiding this comment.
🧩 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=tsxRepository: 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=30Repository: 2025-snowCode/snowCode-Client
Length of output: 12211
ListRow 기본 스타일에 cursor-pointer 재추가 또는 사용처에서 명시적으로 적용 필요
현재 코드에서 cursor-pointer가 기본 스타일에서 제거되어 있으나, ListRow가 사용되는 대부분의 컨텍스트에서 일관성 있게 처리되지 않고 있습니다:
UnitAssignmentList.tsx:cursor-grab클래스로 오버라이드 (드래그/드롭 시나리오)AssignmentSelectPage.tsx,AssignmentManagePage.tsx: 커서 스타일 완전히 없음 (선택 가능한 리스트이지만)
제안:
-
옵션 A (권장): ListRow 기본 스타일에
cursor-pointer유지 → UnitAssignmentList에서만cursor-grab으로 오버라이드base: '...cursor-pointer...' // UnitAssignmentList에서: className=`cursor-grab ...` -
옵션 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).
There was a problem hiding this comment.
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
onSelect와handleSelect를 제네릭T로 통일하세요.Line 5에서
type T = AssignmentSelectCourse['assignments'][number]로 선언했지만, Line 11과 Line 20에서 콜백을Assignment로 고정하면 제네릭의 의도가 훼손됩니다. 실제 데이터는Pick<Assignment, 'id' | 'title'>타입이므로submittedStatus필드를 기대하지만 런타임에는 존재하지 않습니다.onSelect/handleSelect를T로 맞추면 타입 안전성과 재사용성이 개선됩니다.🔧 제안 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); };🤖 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
📒 Files selected for processing (9)
src/entities/unit/api/unitQueries.tssrc/pages/manage-assignment/AssignmentManagePage.tsxsrc/pages/select-assignment/AssignmentSelectPage.tsxsrc/pages/select-assignment/ui/AssignmentListContainer.tsxsrc/pages/unit-editor/UnitEditorPage.tsxsrc/pages/unit-editor/model/types.tssrc/pages/unit-editor/ui/UnitForm.tsxsrc/pages/unit-editor/ui/UnitList.tsxsrc/shared/ui/button/Button.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/pages/manage-assignment/AssignmentManagePage.tsx
| <li onClick={(e) => handleSelect(item, e)} key={item.id}> | ||
| {renderItem(item)} | ||
| </li> |
There was a problem hiding this comment.
클릭 타깃이 li라서 키보드 선택이 막힙니다.
Line 29에서 li에 onClick만 걸면 키보드 사용자(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.
| <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.
| const assignmentListKey = | ||
| mode === 'editing' ? `edit-${unit?.id}` : 'creating-assignments'; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd 'UnitEditorPage.tsx' --type fRepository: 2025-snowCode/snowCode-Client
Length of output: 113
🏁 Script executed:
cat -n src/pages/unit-editor/UnitEditorPage.tsx | head -150Repository: 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 tsRepository: 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 5Repository: 2025-snowCode/snowCode-Client
Length of output: 100
🏁 Script executed:
# Check how unitQueries.getUnitList is structured
fd 'unitQueries' --type fRepository: 2025-snowCode/snowCode-Client
Length of output: 109
🏁 Script executed:
# Look at the unitQueries file
cat -n src/entities/unit/api/unitQueries.ts | head -40Repository: 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.
There was a problem hiding this comment.
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>; }공식 문서:
- TanStack Query
enabled: https://tanstack.com/query/latest/docs/framework/react/guides/disabling-queries- React Router
useParams: https://reactrouter.com/api/hooks/useParams#!/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.tsxAlso 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
📒 Files selected for processing (7)
src/App.tsxsrc/entities/assignment/api/assignmentQueries.tssrc/entities/course/api/courseQueries.tssrc/features/assignment/filter-assignment/lib/useAssignmentList.tssrc/pages/manage-assignment/AssignmentManagePage.tsxsrc/pages/select-assignment/AssignmentSelectPage.tsxsrc/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
| const {data: allAssignments} = useQuery( | ||
| assignmentQueries.getAllAssignments() | ||
| ); | ||
| const {data: assignments} = useQuery( | ||
| assignmentQueries.getAssignmentsByCourse(selectedCourseId ?? 0) | ||
| ); | ||
|
|
||
| return unique( | ||
| selectedCourseId ? (assignments ?? []) : (allAssignments ?? []) | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/features/assignment/filter-assignment/lib/useAssignmentList.tsRepository: 2025-snowCode/snowCode-Client
Length of output: 917
🏁 Script executed:
# assignmentQueries 정의 확인
find . -type f -name "*.ts" -o -name "*.tsx" | xargs grep -l "assignmentQueries" | head -5Repository: 2025-snowCode/snowCode-Client
Length of output: 283
🏁 Script executed:
# useQuery 사용 패턴 확인
rg "useQuery" --type ts --type tsx -A 3 | head -50Repository: 2025-snowCode/snowCode-Client
Length of output: 100
🏁 Script executed:
cat -n src/entities/assignment/api/assignmentQueries.tsRepository: 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 5Repository: 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 -20Repository: 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 -20Repository: 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.
| onSuccess: () => { | ||
| queryClient.invalidateQueries({ | ||
| queryKey: assignmentQueries.getAllAssignments().queryKey, | ||
| }); | ||
| queryClient.invalidateQueries({ | ||
| queryKey: assignmentQueries.getAssignmentsByCourse( | ||
| selectedCourseId ?? 0 | ||
| ).queryKey, | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's find and examine the AssignmentManagePage.tsx file
find . -name "AssignmentManagePage.tsx" -type fRepository: 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 -20Repository: 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 -20Repository: 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.tsxRepository: 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 -10Repository: 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 -20Repository: 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.tsRepository: 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.tsRepository: 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 -5Repository: 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 -80Repository: 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.tsRepository: 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 2Repository: 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.
There was a problem hiding this comment.
♻️ 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
📒 Files selected for processing (5)
src/entities/assignment/api/assignmentQueries.tssrc/entities/course/api/courseQueries.tssrc/pages/dashboard/Dashboard.tsxsrc/pages/manage-assignment/AssignmentManagePage.tsxsrc/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
There was a problem hiding this comment.
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
📒 Files selected for processing (3)
src/pages/manage-assignment/AssignmentManagePage.tsxsrc/pages/select-assignment/AssignmentSelectPage.tsxsrc/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
| const returnToPreviousPage = () => { | ||
| navigate(location.state?.backPath ?? -1); | ||
| }; |
There was a problem hiding this comment.
🧩 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 2Repository: 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 2Repository: 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.
There was a problem hiding this comment.
이 파일을 AssignmentManagePage.tsx에서도 참조하고 있으니 entities/assignment/ui나 shared/ui로 이동해서 관리해야 FSD 계층에서 안위반될 것 같아요!
There was a problem hiding this comment.
뭔가 이 파일도 useMutation이랑 onSuccess처리가 직접 작성되어 있어서
커스텀 뮤테이션 훅으로 빼서 이 페이지에서는 호출만 하면 어떨까 해요!
There was a problem hiding this comment.
지금
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'], |
There was a problem hiding this comment.
흠 이거 쿼리 키 그냥 따로 파일 만들고 얘도 그냥 상수로 여기에 쓸까하는데 어떠세요?
저번 회의 때 이렇게 하신 이유가 있었던 것 같은데 제가 기억이 안나서 다시 여쭤봅니다!
⚙️ Related ISSUE Number
Related #52
📄 Work Description
useAssignmentList커스텀 훅으로 분리📷 Screenshot
💬 To Reviewers
TODOS:
@JiiminHa
UnitEditorPage.tsx의 뮤테이션의onSuccess가 컴포넌트 상태에 의존하고 있어서 분리하는 것은 조금 더 고민해 보겠습니다!🔗 Reference