Skip to content

#38 feat: 대시보드 api 연동#43

Merged
JiiminHa merged 14 commits intodevelopfrom
feature/38-dashboard-api
Feb 13, 2026
Merged

#38 feat: 대시보드 api 연동#43
JiiminHa merged 14 commits intodevelopfrom
feature/38-dashboard-api

Conversation

@suminb99
Copy link
Contributor

⚙️ Related ISSUE Number

Related #38



📄 Work Description

1. TanStack Query 도입

  • tanstack-query 패키지 추가 및 QueryClientProvider 설정

2. Entity API 레이어 구성

  • entities/course — 강의 목록 조회(getAllCourses), 강의 삭제(deleteCourse) API 및 courseQueryOptions 추가
  • entities/assignment — 스케줄 목록 조회(getAssignmentSchedules) API 및 assignmentQueryOptions 추가

3. 대시보드 API 연동

  • useSuspenseQueries를 사용하여 강의 목록 + 스케줄 목록 병렬 조회
  • 기존 mock 데이터 기반 렌더링에서 실제 API 응답 기반으로 전환
  • useMutation 기반 삭제 요청 처리
  • 삭제 성공 시 강의 목록 및 스케줄 목록 캐시 무효화

4. UI 개선

  • EmptyState 컴포넌트 추가 및 빈 상태 처리
  • CourseCard 스타일 개선 및 강의 관리 메뉴 위치 조정
  • 컴포넌트 props 타입 정의 (CourseCardProps, CourseManagementDropdownProps, ScheduleListProps)



📷 Screenshot



💬 To Reviewers



🔗 Reference

@suminb99 suminb99 requested a review from JiiminHa February 11, 2026 09:58
@suminb99 suminb99 self-assigned this Feb 11, 2026
@suminb99 suminb99 linked an issue Feb 11, 2026 that may be closed by this pull request
3 tasks
@coderabbitai
Copy link

coderabbitai bot commented Feb 11, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Summary by CodeRabbit

새로운 기능

  • 과정 삭제 기능 추가 및 삭제 시 자동 데이터 동기화
  • 과정 및 일정 데이터가 없을 때 빈 상태 화면 표시
  • React Query 기반 데이터 캐싱 및 관리 시스템 도입

의존성

  • React Query 라이브러리 추가로 향상된 데이터 관리

Walkthrough

React Query 의존성을 추가하고 대시보드 컴포넌트를 서버 기반 데이터 페칭으로 전환했습니다. 강좌 및 일정 데이터를 Suspense와 React Query를 통해 동적으로 로드하고, 강좌 삭제 기능을 뮤테이션으로 구현했습니다.

Changes

Cohort / File(s) Summary
의존성 추가
package.json
React Query(@tanstack/react-query) 및 ESLint 플러그인(@tanstack/eslint-plugin-query) 추가
공통 컴포넌트
src/components/common/EmptyState.tsx
빈 상태 표시용 새 컴포넌트: 기본 스타일(중앙 정렬, 중간 텍스트) 포함
API 및 쿼리 옵션
src/entities/assignment/api/assignmentApi.ts, src/entities/assignment/api/assignmentQueryOptions.ts, src/entities/assignment/index.ts
일정 조회 API 함수와 React Query 옵션 객체 추가; 배럴 파일 재내보내기
강좌 API
src/entities/course/api/courseApi.ts, src/entities/course/api/courseQueryOptions.ts, src/entities/course/index.ts
모든 강좌 조회 및 삭제 API 함수; React Query 옵션 객체; 배럴 파일 재내보내기
React Query 통합
src/main.tsx
QueryClient 생성 및 QueryClientProvider로 앱 래핑
대시보드 로직
src/pages/dashboard/Dashboard.tsx
useSuspenseQueries로 데이터 페칭; 삭제 뮤테이션 구현; 쿼리 무효화 및 UI 피드백 추가; 데이터 기반 조건부 렌더링
타입 정의
src/pages/dashboard/models/types.ts
CourseListProps에 onDelete 콜백 추가; ScheduleListProps, CourseCardProps, CourseManagementDropdownProps 새로 추가
UI 컴포넌트
src/pages/dashboard/ui/CourseCard.tsx, src/pages/dashboard/ui/CourseList.tsx, src/pages/dashboard/ui/CourseManagementDropdown.tsx, src/pages/dashboard/ui/ScheduleList.tsx
모든 컴포넌트가 props 기반 데이터 소비로 변환; 삭제 콜백 추가; 정적 데이터 의존성 제거

Sequence Diagram(s)

sequenceDiagram
    participant User as 사용자
    participant Dashboard as Dashboard
    participant QueryClient as QueryClient
    participant API as API
    participant Server as 서버

    User->>Dashboard: 페이지 진입
    Dashboard->>QueryClient: useSuspenseQueries 호출
    QueryClient->>API: getAssignmentSchedules(), getAllCourses()
    API->>Server: GET /assignments/schedule, GET /courses/my
    Server-->>API: 응답 데이터
    API-->>QueryClient: Promise 반환
    QueryClient-->>Dashboard: 데이터 해석
    Dashboard->>Dashboard: CourseList/ScheduleList 렌더링

    User->>Dashboard: 강좌 삭제 버튼 클릭
    Dashboard->>QueryClient: deleteCourse 뮤테이션 실행
    QueryClient->>API: privateAxios.delete(/courses/{id})
    API->>Server: DELETE 요청
    Server-->>API: 성공 응답
    API-->>QueryClient: 데이터 반환
    QueryClient->>QueryClient: 쿼리 무효화 (courses, schedules)
    QueryClient->>Dashboard: 데이터 갱신
    Dashboard->>Dashboard: UI 업데이트
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

이유: 다양한 파일에 걸친 혼합 복잡도의 변경사항입니다. React Query 통합은 명확하지만, 대시보드 컴포넌트의 상태 관리 변경과 여러 하위 컴포넌트의 props 전파를 함께 검증해야 합니다. 삭제 뮤테이션의 에러 처리, 쿼리 무효화 전략, 그리고 Suspense 경계 처리를 세심하게 확인할 필요가 있습니다.

Possibly related PRs

  • PR #30: useUserStore 경로 변경과 공통 컴포넌트(Header, SurfaceCard) 임포트가 이 PR에서 사용되며, 직접 의존 관계가 있습니다.
  • PR #33: 동일한 대시보드 UI 컴포넌트(CourseCard, CourseList, ScheduleList)와 타입 정의를 수정하고, 드롭다운 기반 강좌 관리 흐름을 추가하여 겹치는 변경사항이 있습니다.
  • PR #43: package.json의 React Query 의존성, EmptyState 컴포넌트, 모든 API 함수, QueryClientProvider 설정, 그리고 대시보드 컴포넌트/타입이 동일하게 변경되어 완전히 관련된 PR입니다.

💡 코드 리뷰 포인트

개선 권장사항:

  1. 에러 처리 강화

    • getAssignmentSchedulesgetAllCourses에 try-catch가 없습니다. React Query의 에러 처리 가이드를 참고하여 queryFn에서 에러 처리를 구현하거나, Dashboard에서 에러 바운더리를 추가하세요.
  2. 쿼리 무효화 전략 검토

    • onSuccess에서 두 쿼리를 모두 무효화하는 것은 올바르지만, 강좌 삭제 시 일정(schedules) 쿼리도 무효화하는 이유가 문서화되면 좋습니다. 이들이 같은 강좌에 속할 경우 설명을 추가하세요.
  3. Suspense 대체 경계

    • useSuspenseQueries 사용 시 폴백 UI가 필요합니다. 현재 EmptyState는 데이터가 없을 때만 표시되므로, Suspense 경계 설정을 문서에 명시하거나 코드 주석으로 추가하세요.
  4. 타입 안정성

    • courseId: number가 일관되게 사용되고 있는데, 타입 정의 파일에 명시적인 타입 별칭 (예: type CourseId = number) 추가를 고려하면 유지보수성이 향상됩니다.

칭찬:
✨ React Query 통합과 컴포넌트 리팩토링이 깔끔하게 진행되었습니다. Props 기반 데이터 주입으로 테스트 가능성도 개선되었네요!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항인 대시보드 API 연동을 명확하게 설명하고 있으며, 변경사항의 핵심을 적절히 반영합니다.
Description check ✅ Passed PR 설명이 충분한 상세도로 작업 내용을 구분하여 설명하고 있으며, 변경사항의 각 부분(TanStack Query 도입, Entity API 구성, 대시보드 API 연동, UI 개선)을 명확히 설명합니다.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/38-dashboard-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 added 🧩 feature 기능 개발 🎨 ui CSS 등 사용자 UI 디자인 변경 labels Feb 11, 2026
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: 3

Caution

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

⚠️ Outside diff range comments (1)
src/pages/dashboard/ui/CourseManagementDropdown.tsx (1)

25-29: ⚠️ Potential issue | 🟠 Major

드롭다운 트리거의 접근성을 개선하세요.

현재 CourseMenuTrigger<div>로 구성되어 있고, Dropdown 컴포넌트도 이를 일반 <div>로 래핑하고 있어 다음과 같은 접근성 문제가 발생합니다:

  • 키보드 사용자: Tab으로 포커스할 수 없고, Enter/Space로 활성화할 수 없습니다.
  • 스크린 리더 사용자: 상호작용 가능한 요소로 인식되지 않습니다.
  • WCAG 2.1 위반: 키보드 접근성(Level A)의 기본 요구사항을 충족하지 않습니다.

권장 해결책:

Dropdown 컴포넌트 자체를 개선하는 것이 근본적 해결책입니다. 다음과 같이 수정하면 모든 Dropdown 사용처에서 접근성이 확보됩니다:

- <div onClick={() => setIsOpen(!isOpen)}>{dropDownButton}</div>
+ <button
+   type='button'
+   onClick={() => setIsOpen(!isOpen)}
+   className='w-full text-left'
+   aria-haspopup='listbox'
+   aria-expanded={isOpen}>
+   {dropDownButton}
+ </button>

즉각적 해결이 필요하다면, CourseManagementDropdown에서 트리거를 <button>으로 변경할 수도 있습니다:

♻️ 임시 개선 제안
  const CourseMenuTrigger = (
-   <div className='cursor-pointer p-2'>
+   <button type='button' className='cursor-pointer p-2' aria-label='강의 관리 메뉴'>
      <EllipsisIcon className='w-[21.2px] h-[5px]' />
-   </div>
+   </button>
  );

참고 자료:

🤖 Fix all issues with AI agents
In `@src/features/course/filter-course/lib/useCourseFilter.ts`:
- Around line 11-25: The current courseOptionMap build uses
formatCourseOptionLabel as the Map key so duplicate labels overwrite entries
(see courseOptionMap and Map.set); change to a stable ID-based mapping by
producing options as { label: string; value: number }[] (build from courses
using formatCourseOptionLabel for label and course.id for value) instead of
mapping label→id, and update CourseSelector to read option.value as the selected
course id; alternatively append course.id to the label string to guarantee
uniqueness if you cannot change CourseSelector.

In `@src/pages/dashboard/Dashboard.tsx`:
- Around line 23-25: Dashboard.tsx uses useSuspenseQueries (const [{data:
courses}, {data: schedules}] = useSuspenseQueries(...)) but the app lacks a
surrounding Suspense + ErrorBoundary which can cause blank screens or crashes;
wrap your route outlet (e.g., in Layout.tsx or App.tsx) with a <Suspense
fallback={...}> and an <ErrorBoundary fallback={...}> around <Outlet /> so all
children including Dashboard can suspend or surface errors safely. Also consider
the optional refinements called out: simplify mutationFn from (courseId: number)
=> deleteCourse(courseId) to mutationFn: deleteCourse when signatures match, and
replace confirm()/alert() uses with a toast library for better UX.

In `@src/pages/select-assignment/AssignmentSelectPage.tsx`:
- Around line 12-13: selectedCourseId from the useCourseFilter hook is declared
but never used, causing a lint error; either remove selectedCourseId from the
destructuring (change "const {courseOptions, handleCourseSelect,
selectedCourseId} = useCourseFilter(courses)" to omit selectedCourseId) or
implement the intended filtering logic that uses selectedCourseId (e.g., apply
it when deriving courseOptions or when calling the API) so that selectedCourseId
is actually referenced; update code around useCourseFilter, courseOptions, and
handleCourseSelect accordingly to keep usage consistent and eliminate the unused
variable.
🧹 Nitpick comments (14)
src/models/course.ts (1)

92-97: CourseOptionsResponseDashboardCourseListResponse와 동일한 타입입니다.

Line 76-79의 DashboardCourseListResponse와 Line 94-97의 CourseOptionsResponseApiResponse<{ count: number; courses: DashboardCourse[] }>로 완전히 동일한 shape입니다. 중복 타입은 향후 유지보수 시 불일치를 유발할 수 있으므로, 기존 타입을 재사용하거나 type alias로 연결하는 것을 권장합니다.

♻️ 개선 제안
-// 강의 옵션 목록 응답 타입 정의
-export type CourseOptionsResponse = ApiResponse<{
-  count: number;
-  courses: DashboardCourse[];
-}>;
+// 강의 옵션 목록 응답 타입 정의 (대시보드 강의 목록과 동일 shape)
+export type CourseOptionsResponse = DashboardCourseListResponse;

만약 향후 shape이 달라질 예정이라면 현재 상태도 괜찮지만, 그 의도를 주석으로 명시해주세요.

src/features/course/filter-course/ui/CourseSelector.tsx (1)

3-9: Props 타입을 CourseSelectorProps 인터페이스로 분리하는 것을 권장합니다.

코딩 가이드라인에 따르면 Props 타입은 컴포넌트명Props 네이밍 컨벤션을 따라야 합니다. 인라인 타입 대신 명명된 인터페이스를 사용하면 재사용성과 가독성이 향상됩니다.

♻️ 개선 제안
+interface CourseSelectorProps {
+  options: string[];
+  onSelect: (value: string) => void;
+}
+
-export const CourseSelector = ({
-  options,
-  onSelect,
-}: {
-  options: string[];
-  onSelect: (value: string) => void;
-}) => {
+export const CourseSelector = ({options, onSelect}: CourseSelectorProps) => {

As per coding guidelines, "Props 타입: 컴포넌트명Props".

src/entities/course/index.ts (1)

1-1: Barrel export 확인 완료.

courseQueryOptions도 이 barrel에서 함께 re-export하면 소비자 측에서 일관된 경로로 import할 수 있습니다. 현재 구조가 의도된 것이라면 무시하셔도 됩니다.

#!/bin/bash
# courseQueryOptions가 어디서 import되고 있는지 확인
rg -n "courseQueryOptions" --type=ts -C2
src/widgets/assignment-page-layout/ui/AssignmentPageLayout.tsx (1)

5-12: React.ReactNode 사용 시 React import가 필요할 수 있습니다.

Line 7에서 React.ReactNode를 타입으로 사용하고 있지만, 파일 상단에 React import가 없습니다. 새로운 JSX Transform에서는 JSX를 위한 React import는 불필요하지만, React.ReactNode 같은 타입 참조는 명시적 import가 필요할 수 있습니다(tsconfig 설정에 따라 다름).

♻️ 개선 제안
 import SurfaceCard from '@/components/common/SurfaceCard';
 import Button from '@/components/common/Button';
 import {CourseSelector} from '@/features/course/filter-course';
+import type {ReactNode} from 'react';

 interface AssignmentPageLayoutProps {
   title: string;
-  list: React.ReactNode;
+  list: ReactNode;
   courseOptions: string[];

type-only import을 활용하면 코딩 가이드라인의 "type-only import 적극 사용" 원칙도 함께 준수할 수 있습니다. 참고: React TypeScript Cheatsheet

As per coding guidelines, "type-only import 적극 사용".

src/App.tsx (1)

43-43: 주석 처리된 라우트를 정리해 주세요.

AssignmentsPage 라우트가 주석 처리되어 있는데, 이렇게 남겨두면 나중에 dead code가 될 수 있어요. 아직 작업 중이라면 TODO 주석과 함께 이슈 번호를 명시하거나, 사용하지 않는다면 삭제하는 것이 좋겠습니다.

-          {/* <Route path='assignments' element={<AssignmentsPage />} /> */}
+          {/* TODO(`#XX`): assignments 목록 페이지 API 연동 후 복원 */}
src/components/common/SelectableItem.tsx (1)

3-3: Fast Refresh 호환성을 위해 selectableItemStyles를 별도 파일로 분리하세요.

ESLint가 지적한 대로, 컴포넌트와 상수를 같은 파일에서 export하면 React Fast Refresh가 정상 동작하지 않아요. 개발 중 HMR이 전체 리로드로 fallback되어 DX가 저하됩니다.

selectableItemStyles를 별도 파일(예: selectableItem.styles.ts)로 분리하고, 컴포넌트 파일에서 import하는 구조를 권장합니다.

참고: Vite React Refresh Plugin

src/main.tsx (1)

7-7: QueryClient에 기본 옵션 설정을 고려해 보세요.

현재 기본값(staleTime: 0, retry: 3 등)으로 동작하는데, 프로젝트 특성에 맞게 기본 옵션을 설정하면 불필요한 refetch를 줄이고 UX를 개선할 수 있어요.

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60, // 1분
      retry: 1,
    },
  },
});

참고: TanStack Query - Important Defaults

src/entities/course/api/courseQueryOptions.ts (1)

1-9: 파일명이 코딩 가이드라인의 kebab-case 규칙과 다릅니다.

프로젝트 네이밍 컨벤션에 따르면 일반 파일명은 kebab-case를 사용해야 합니다. courseQueryOptions.tscourse-query-options.ts로 변경을 고려해 주세요. 같은 디렉토리의 courseApi.ts도 동일하게 해당됩니다.

As per coding guidelines: "일반 파일명: kebab-case"

src/entities/assignment/api/assignmentApi.ts (1)

4-8: privateAxios.get에 제네릭 타입 파라미터 추가를 권장합니다.

현재 response.dataany로 추론되고 리턴 타입 선언으로만 타입이 보장됩니다. get<DashboardScheduleListResponse>를 명시하면 Axios 레벨에서 타입 안전성을 확보할 수 있습니다.

참고: Axios TypeScript 문서

♻️ 개선 제안
 export const getAssignmentSchedules =
   async (): Promise<DashboardScheduleListResponse> => {
-    const response = await privateAxios.get('/assignments/schedule');
+    const response = await privateAxios.get<DashboardScheduleListResponse>('/assignments/schedule');
     return response.data;
   };
src/components/common/EmptyState.tsx (1)

6-12: className 병합 시 유틸리티 함수 사용을 고려해 보세요.

현재 템플릿 리터럴 방식도 동작하지만, classNameundefined일 때 불필요한 공백이 포함될 수 있습니다. 프로젝트에 clsxcn 유틸리티가 있다면 활용하면 더 깔끔합니다.

♻️ 개선 제안 (cn 유틸리티 사용 시)
+import {cn} from '@/utils/cn'; // 프로젝트에 해당 유틸이 있다면
+
 export const EmptyState = ({children, className}: EmptyStateProps) => {
   return (
     <div
-      className={`text-center text-light-black text-base font-medium ${className ?? ''}`}>
+      className={cn('text-center text-light-black text-base font-medium', className)}>
       {children}
     </div>
   );
 };
src/entities/assignment/api/assignmentQueryOptions.ts (1)

4-9: queryKey를 좀 더 계층적으로 구성하는 것을 권장합니다.

현재 ['schedules']는 범용적이어서, 추후 스케줄 관련 쿼리가 추가되면 의도치 않은 캐시 충돌이 발생할 수 있습니다. TanStack Query의 Query Key 계층 구조 패턴을 참고해 보세요.

♻️ 개선 제안
 export default function assignmentQueryOptions() {
   return queryOptions({
-    queryKey: ['schedules'],
+    queryKey: ['assignments', 'schedules'],
     queryFn: getAssignmentSchedules,
   });
 }

변경 시 Dashboard.tsxinvalidateQueries 호출부도 자동으로 반영됩니다 (assignmentQueryOptions().queryKey를 사용하고 있으므로).

src/pages/dashboard/Dashboard.tsx (1)

28-44: mutationFn 간소화 및 alert/confirm 개선 여지

  1. mutationFn의 래퍼 함수는 불필요합니다 — deleteCourse를 직접 전달할 수 있습니다.
  2. alert()/confirm()은 브라우저 모달로 UX가 제한적입니다. 당장은 괜찮지만, 추후 토스트 라이브러리(예: react-hot-toast)로 전환을 고려해 보세요.
♻️ mutationFn 간소화 제안
   const {mutate} = useMutation({
-    mutationFn: (courseId: number) => deleteCourse(courseId),
+    mutationFn: deleteCourse,
     onSuccess: () => {
src/pages/dashboard/ui/ScheduleList.tsx (1)

11-12: 리스트 keyindex 사용은 주의가 필요합니다.

schedule 항목에 고유 식별자(예: date 또는 서버 제공 id)가 있다면 index 대신 사용하는 것이 안전합니다. 리스트가 재정렬되거나 항목이 추가/삭제될 때 index 기반 key는 불필요한 리렌더링이나 상태 불일치를 유발할 수 있습니다.

참고: React 공식 문서 - Key

♻️ 개선 제안 (date가 유니크하다면)
       {scheduleList.map((schedule, index) => (
-        <li className='flex items-start justify-start gap-5' key={index}>
+        <li className='flex items-start justify-start gap-5' key={schedule.date}>

As per coding guidelines, "리스트 렌더링 시 key 안정성 확인".

src/pages/dashboard/ui/CourseCard.tsx (1)

38-38: className 문자열 앞 불필요한 공백

className에 앞쪽 공백이 포함되어 있습니다. 기능에는 영향 없지만 정리하면 깔끔합니다.

-      <div className=' bg-gray flex-center px-7.5 py-6 rounded-r-3xl text-center text-base font-normal whitespace-nowrap'>
+      <div className='bg-gray flex-center px-7.5 py-6 rounded-r-3xl text-center text-base font-normal whitespace-nowrap'>

Comment on lines 11 to 25
const courseOptionMap = useMemo(() => {
const map = new Map<string, number>();

courses.forEach((course) => {
const label = formatCourseOptionLabel(
course.title,
course.year,
course.semester,
course.section
);
map.set(label, course.id);
});

return map;
}, [courses]);
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

동일한 라벨을 가진 강의가 있을 경우 Map에서 덮어씌워집니다.

formatCourseOptionLabel로 생성한 라벨이 동일한 강의가 2개 이상 존재하면(예: 같은 제목·연도·학기·분반), Map.set이 이전 항목을 덮어써서 하나의 강의만 선택 가능해집니다. 현재 데이터에서 발생 가능성이 낮더라도, 방어적으로 라벨에 고유성을 부여하거나 ID 기반 매핑으로 전환하는 것이 안전합니다.

♻️ 대안: ID를 라벨 키에 포함

라벨 충돌을 방지하려면 내부적으로 ID를 함께 관리하는 방식을 고려해보세요. 예를 들어 options{ label: string; value: number }[] 형태로 변경하면 드롭다운에서 label을 표시하고 value(ID)로 선택을 처리할 수 있습니다. 이 경우 CourseSelector 컴포넌트도 함께 수정이 필요합니다.

🤖 Prompt for AI Agents
In `@src/features/course/filter-course/lib/useCourseFilter.ts` around lines 11 -
25, The current courseOptionMap build uses formatCourseOptionLabel as the Map
key so duplicate labels overwrite entries (see courseOptionMap and Map.set);
change to a stable ID-based mapping by producing options as { label: string;
value: number }[] (build from courses using formatCourseOptionLabel for label
and course.id for value) instead of mapping label→id, and update CourseSelector
to read option.value as the selected course id; alternatively append course.id
to the label string to guarantee uniqueness if you cannot change CourseSelector.

Comment on lines +23 to +25
const [{data: courses}, {data: schedules}] = useSuspenseQueries({
queries: [courseQueryOptions(), assignmentQueryOptions()],
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find Dashboard.tsx and examine its content
find . -name "Dashboard.tsx" -type f

Repository: 2025-snowCode/snowCode-Client

Length of output: 108


🏁 Script executed:

# Check the structure around the queries and examine the actual code
cat -n src/pages/dashboard/Dashboard.tsx | head -60

Repository: 2025-snowCode/snowCode-Client

Length of output: 2472


🏁 Script executed:

# Search for Suspense and ErrorBoundary usage in the codebase
rg -n "Suspense|ErrorBoundary" --type tsx --type ts -C 2

Repository: 2025-snowCode/snowCode-Client

Length of output: 100


🏁 Script executed:

# Check how Dashboard is imported and used in routing or parent components
rg -n "from.*Dashboard|import.*Dashboard" --type tsx --type ts -C 3

Repository: 2025-snowCode/snowCode-Client

Length of output: 100


🏁 Script executed:

# Search for Suspense and ErrorBoundary with correct syntax
rg "Suspense|ErrorBoundary" --type=tsx --type=ts -C 2

Repository: 2025-snowCode/snowCode-Client

Length of output: 100


🏁 Script executed:

# Check how Dashboard is imported and used - corrected syntax
rg "Dashboard" --type=tsx --type=ts -C 3 | grep -v "Dashboard.tsx" | head -50

Repository: 2025-snowCode/snowCode-Client

Length of output: 100


🏁 Script executed:

# Look for route configuration files
find . -name "*route*" -o -name "*layout*" -o -name "*app*" | grep -E "\.(tsx|ts|jsx|js)$" | head -20

Repository: 2025-snowCode/snowCode-Client

Length of output: 2991


🏁 Script executed:

# Check main.tsx or app entry point
find . -name "main.tsx" -o -name "App.tsx" -o -name "index.tsx" | head -10

Repository: 2025-snowCode/snowCode-Client

Length of output: 101


🏁 Script executed:

# Check App.tsx for routing and Suspense/ErrorBoundary setup
cat -n src/App.tsx

Repository: 2025-snowCode/snowCode-Client

Length of output: 2731


🏁 Script executed:

# Check main.tsx
cat -n src/main.tsx

Repository: 2025-snowCode/snowCode-Client

Length of output: 591


🏁 Script executed:

# Search for Suspense in the codebase using basic grep
grep -r "Suspense" src/ --include="*.tsx" --include="*.ts" -n | head -20

Repository: 2025-snowCode/snowCode-Client

Length of output: 236


🏁 Script executed:

# Search for ErrorBoundary in the codebase
grep -r "ErrorBoundary" src/ --include="*.tsx" --include="*.ts" -n | head -20

Repository: 2025-snowCode/snowCode-Client

Length of output: 55


🏁 Script executed:

# Check Layout component for Suspense/ErrorBoundary
cat -n src/layout/Layout.tsx

Repository: 2025-snowCode/snowCode-Client

Length of output: 921


🏁 Script executed:

# Check if there are any other files in the layout directory
ls -la src/layout/

Repository: 2025-snowCode/snowCode-Client

Length of output: 243


useSuspenseQueries 사용 시 상위에 Suspense + ErrorBoundary가 필수입니다.

현재 코드는 useSuspenseQueries를 사용하지만, 앱 전체에 <Suspense> 또는 <ErrorBoundary>가 없어 다음 문제가 발생할 수 있습니다:

  • 데이터 로딩 중 빈 화면 또는 런타임 에러
  • 쿼리 에러 발생 시 컴포넌트 전체 크래시

해결책: Layout.tsx 또는 App.tsx<Outlet /> 위에 Suspense와 ErrorBoundary를 추가하세요.

<Suspense fallback={<LoadingSpinner />}>
  <ErrorBoundary fallback={<ErrorPage />}>
    <Outlet />
  </ErrorBoundary>
</Suspense>

참고: TanStack Query - Suspense


추가 개선 사항 (선택):

  • mutationFn: (courseId: number) => deleteCourse(courseId)mutationFn: deleteCourse (서명이 일치하면 간소화 가능)
  • confirm(), alert() 대신 토스트 라이브러리 사용 추천 (더 나은 UX)
🤖 Prompt for AI Agents
In `@src/pages/dashboard/Dashboard.tsx` around lines 23 - 25, Dashboard.tsx uses
useSuspenseQueries (const [{data: courses}, {data: schedules}] =
useSuspenseQueries(...)) but the app lacks a surrounding Suspense +
ErrorBoundary which can cause blank screens or crashes; wrap your route outlet
(e.g., in Layout.tsx or App.tsx) with a <Suspense fallback={...}> and an
<ErrorBoundary fallback={...}> around <Outlet /> so all children including
Dashboard can suspend or surface errors safely. Also consider the optional
refinements called out: simplify mutationFn from (courseId: number) =>
deleteCourse(courseId) to mutationFn: deleteCourse when signatures match, and
replace confirm()/alert() uses with a toast library for better UX.

Comment on lines 12 to 13
const {courseOptions, handleCourseSelect, selectedCourseId} =
useCourseFilter(courses);
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

selectedCourseId가 선언만 되고 사용되지 않고 있어요.

이전 리뷰에서 "API 연동 시 필터링 로직 구현 예정"이라고 했는데, 이번 PR이 바로 그 API 연동 PR이네요! 아직 이 페이지는 mock 데이터를 사용 중이라면, 린트 에러 방지를 위해 사용하지 않는 변수는 destructuring에서 제거하거나, 필터링 로직을 함께 구현하는 것을 권장합니다.

🔧 린트 에러 해결을 위한 임시 수정
- const {courseOptions, handleCourseSelect, selectedCourseId} =
+ const {courseOptions, handleCourseSelect} =
    useCourseFilter(courses);
📝 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
const {courseOptions, handleCourseSelect, selectedCourseId} =
useCourseFilter(courses);
const {courseOptions, handleCourseSelect} =
useCourseFilter(courses);
🧰 Tools
🪛 ESLint

[error] 12-12: 'selectedCourseId' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

🤖 Prompt for AI Agents
In `@src/pages/select-assignment/AssignmentSelectPage.tsx` around lines 12 - 13,
selectedCourseId from the useCourseFilter hook is declared but never used,
causing a lint error; either remove selectedCourseId from the destructuring
(change "const {courseOptions, handleCourseSelect, selectedCourseId} =
useCourseFilter(courses)" to omit selectedCourseId) or implement the intended
filtering logic that uses selectedCourseId (e.g., apply it when deriving
courseOptions or when calling the API) so that selectedCourseId is actually
referenced; update code around useCourseFilter, courseOptions, and
handleCourseSelect accordingly to keep usage consistent and eliminate the unused
variable.

@JiiminHa JiiminHa merged commit 4f49576 into develop Feb 13, 2026
1 check was pending
@JiiminHa JiiminHa deleted the feature/38-dashboard-api branch February 14, 2026 00:29
@JiiminHa JiiminHa mentioned this pull request Feb 26, 2026
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