-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor/next cache rsc #116
base: refactor/frontend-structure
Are you sure you want to change the base?
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
Walkthrough이번 변경 사항은 학사 상세 정보와 관련된 새로운 API와 React 컴포넌트를 추가하고, 기존 관련 파일들을 대체하는 내용을 포함합니다. 또한 API 라우트 및 Supabase 클라이언트의 생성 과정을 동기식에서 비동기식으로 전환하고, 서비스 클래스들과 파사드에서 정적 비동기 팩토리 메서드를 도입하는 리팩토링이 이루어졌습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant AcademicDetailPage
participant API_GetSemesters as getSemesters API
participant API_GetAcademicDetail as getAcademicDetail API
participant SemesterSlider
participant AcademicDetailContent
User->>AcademicDetailPage: 페이지 요청
AcademicDetailPage->>API_GetSemesters: 학기 데이터 요청
AcademicDetailPage->>API_GetAcademicDetail: 학사 상세 데이터 요청
API_GetSemesters-->>AcademicDetailPage: 학기 데이터 응답
API_GetAcademicDetail-->>AcademicDetailPage: 학사 상세 데이터 응답
AcademicDetailPage->>SemesterSlider: 데이터 전달 후 렌더링
AcademicDetailPage->>AcademicDetailContent: 데이터 전달 후 렌더링
sequenceDiagram
participant Caller
participant DashboardFacade
participant SupabaseClient
participant StudentService
participant AcademicRecordService
Caller->>DashboardFacade: await DashboardFacade.create()
DashboardFacade->>SupabaseClient: await createClient()
DashboardFacade->>StudentService: await StudentService.create()
DashboardFacade->>AcademicRecordService: await AcademicRecordService.create()
DashboardFacade-->>Caller: DashboardFacade 인스턴스 반환
Poem
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 15
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (31)
app/(main)/academic-detail/[year]/[semester]/apis/getAcademicDetail.ts
(1 hunks)app/(main)/academic-detail/[year]/[semester]/apis/getSemesters.ts
(1 hunks)app/(main)/academic-detail/[year]/[semester]/components/AcademicDetailContent.tsx
(1 hunks)app/(main)/academic-detail/[year]/[semester]/components/NavBar.tsx
(1 hunks)app/(main)/academic-detail/[year]/[semester]/components/SemesterSlider.tsx
(2 hunks)app/(main)/academic-detail/[year]/[semester]/layout.tsx
(1 hunks)app/(main)/academic-detail/[year]/[semester]/page.tsx
(1 hunks)app/(main)/academic-detail/components/AcademicDetailContent.tsx
(0 hunks)app/(main)/academic-detail/layout.tsx
(0 hunks)app/(main)/academic-detail/page.tsx
(0 hunks)app/api/get-academic/[year]/[semester]/route.ts
(1 hunks)app/api/get-dashboard/route.ts
(1 hunks)app/api/get-semesters/route.ts
(1 hunks)app/api/graduation-progress/route.ts
(1 hunks)app/api/suwon-scrape/resync/route.ts
(1 hunks)app/api/suwon-scrape/start/route.ts
(1 hunks)app/api/target-gpa/route.ts
(1 hunks)app/api/users/delete/route.ts
(1 hunks)lib/auth/supabase.ts
(1 hunks)lib/supabase/facades/dashboard-facade.ts
(1 hunks)lib/supabase/server.ts
(1 hunks)lib/supabase/services/academic-record-service.ts
(1 hunks)lib/supabase/services/course-offering-service.ts
(1 hunks)lib/supabase/services/course-service.ts
(1 hunks)lib/supabase/services/department-service.ts
(1 hunks)lib/supabase/services/graduation-progress-service.ts
(1 hunks)lib/supabase/services/professor-service.ts
(1 hunks)lib/supabase/services/student-course-service.ts
(1 hunks)lib/supabase/services/student-service.ts
(1 hunks)package.json
(1 hunks)server/infrastructure/supabase/repository/SupabaseProfessorRepository.ts
(1 hunks)
💤 Files with no reviewable changes (3)
- app/(main)/academic-detail/layout.tsx
- app/(main)/academic-detail/components/AcademicDetailContent.tsx
- app/(main)/academic-detail/page.tsx
🧰 Additional context used
🧬 Code Definitions (17)
app/api/suwon-scrape/start/route.ts (2)
lib/supabase/server.ts (1)
createClient
(4-23)lib/supabase/client.ts (1)
createClient
(4-9)
app/(main)/academic-detail/[year]/[semester]/layout.tsx (2)
app/(main)/academic-detail/[year]/[semester]/page.tsx (1)
fetchCache
(8-8)app/(main)/academic-detail/[year]/[semester]/components/NavBar.tsx (1)
NavBar
(6-11)
lib/supabase/services/professor-service.ts (2)
types/supabase.ts (1)
Database
(3-857)lib/supabase/server.ts (1)
createClient
(4-23)
lib/supabase/services/student-service.ts (4)
types/supabase.ts (1)
Database
(3-857)lib/supabase/services/department-service.ts (1)
DepartmentService
(5-79)lib/supabase/server.ts (1)
createClient
(4-23)lib/supabase/client.ts (1)
createClient
(4-9)
lib/auth/supabase.ts (1)
lib/supabase/server.ts (1)
createClient
(4-23)
app/api/get-dashboard/route.ts (1)
lib/supabase/facades/dashboard-facade.ts (1)
DashboardFacade
(11-61)
app/(main)/academic-detail/[year]/[semester]/page.tsx (2)
app/(main)/academic-detail/[year]/[semester]/layout.tsx (1)
fetchCache
(1-1)app/(main)/academic-detail/[year]/[semester]/components/AcademicDetailContent.tsx (1)
AcademicDetailContent
(21-41)
lib/supabase/services/graduation-progress-service.ts (2)
types/supabase.ts (1)
Database
(3-857)lib/supabase/server.ts (1)
createClient
(4-23)
app/api/get-semesters/route.ts (1)
lib/supabase/server.ts (1)
createClient
(4-23)
lib/supabase/services/course-offering-service.ts (2)
types/supabase.ts (1)
Database
(3-857)lib/supabase/server.ts (1)
createClient
(4-23)
app/api/target-gpa/route.ts (1)
lib/supabase/services/student-service.ts (1)
StudentService
(18-196)
lib/supabase/facades/dashboard-facade.ts (4)
lib/supabase/services/student-service.ts (1)
StudentService
(18-196)lib/supabase/services/academic-record-service.ts (1)
AcademicRecordService
(22-164)lib/supabase/server.ts (1)
createClient
(4-23)server/infrastructure/supabase/repository/SupabaseUserRepository.ts (1)
SupabaseUserRepository
(8-78)
lib/supabase/services/academic-record-service.ts (1)
lib/supabase/server.ts (1)
createClient
(4-23)
lib/supabase/services/course-service.ts (2)
types/supabase.ts (1)
Database
(3-857)lib/supabase/server.ts (1)
createClient
(4-23)
lib/supabase/services/student-course-service.ts (2)
types/supabase.ts (1)
Database
(3-857)lib/supabase/server.ts (1)
createClient
(4-23)
app/api/get-academic/[year]/[semester]/route.ts (3)
app/api/get-semesters/route.ts (1)
GET
(4-34)app/api/graduation-progress/route.ts (1)
GET
(10-54)lib/supabase/server.ts (1)
createClient
(4-23)
lib/supabase/services/department-service.ts (3)
types/supabase.ts (1)
Database
(3-857)lib/supabase/server.ts (1)
createClient
(4-23)lib/supabase/client.ts (1)
createClient
(4-9)
🪛 Biome (1.9.4)
app/(main)/academic-detail/[year]/[semester]/page.tsx
[error] 19-19: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing.
Unsafe fix: Use a SelfClosingElement instead
(lint/style/useSelfClosingElements)
[error] 20-20: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing.
Unsafe fix: Use a SelfClosingElement instead
(lint/style/useSelfClosingElements)
[error] 22-22: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing.
Unsafe fix: Use a SelfClosingElement instead
(lint/style/useSelfClosingElements)
app/(main)/academic-detail/[year]/[semester]/components/AcademicDetailContent.tsx
[error] 33-34: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing.
Unsafe fix: Use a SelfClosingElement instead
(lint/style/useSelfClosingElements)
[error] 38-38: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing.
Unsafe fix: Use a SelfClosingElement instead
(lint/style/useSelfClosingElements)
🔇 Additional comments (41)
app/(main)/academic-detail/[year]/[semester]/layout.tsx (2)
1-1
: Next.js의 캐싱 설정이 적절합니다.
fetchCache
를 'auto'로 설정하여 Next.js가 요청에 따라 캐싱 전략을 최적화할 수 있도록 하였습니다. 이는 성능 향상에 도움이 됩니다.
3-4
: 컴포넌트 임포트가 적절합니다.필요한 NavBar 컴포넌트와 스타일을 올바르게 가져오고 있습니다.
package.json (1)
6-6
:❓ Verification inconclusive
Turbopack 플래그 제거에 대한 검토 필요
개발 스크립트에서
--turbopack
플래그가 제거되었습니다. 이는 개발 환경에서의 빌드 성능에 영향을 줄 수 있습니다. 의도적인 변경인지 확인이 필요합니다.
🌐 Web query:
Next.js 15.2.4에서 Turbopack 사용이 권장되지 않는 이유가 있나요?
💡 Result:
Next.js 15.2.4에서 Turbopack 사용 시 주의가 필요한 이유는 다음과 같습니다:
주요 이슈 및 제한사항
1. 프로덕션 빌드 미지원
- Turbopack은 여전히
next dev
(개발 서버) 환경에서만 작동하며,next build
를 통한 프로덕션 빌드는 지원되지 않습니다[2][7]. 이로 인해 개발과 배포 환경 간 차이가 발생할 수 있습니다.2. 특정 환경에서의 안정성 문제
- Windows + Firefox 조합에서 Sourcemap 경고가 발생하며, URL 파싱 오류로 인한 콘솔 에러가 확인되었습니다[9]. 이 문제는 Turbopack이 생성한 소스맵 경로가 Windows 파일 시스템과 호환되지 않아 발생합니다.
3. 일부 기능 미구현
- Babel 변환 지원이 제한적이며
.babelrc
파일을 직접 사용할 수 없습니다[2]- App Router의 root layout 자동 생성 기능이 작동하지 않아 수동 설정이 필요합니다[1][2]
@next/font
(구버전) 및 AMP 지원이 중단되었습니다[2]4. 성능 이슈
- 대형 프로젝트에서 메모리 사용량이 증가하는 사례가 보고되었으며[5][7], 디스크 캐싱 미지원으로 인한 초기 컴파일 속도 저하가 발생할 수 있습니다[6][7].
권장 사용 가이드
# Turbopack 활성화 시 next dev --turbo
- 사용이 권장되는 경우: 대규모 애플리케이션의 빠른 개발 반복이 필요한 경우[4][6]
- 지양해야 하는 경우: Windows 개발 환경, 프로덕션 빌드 필요 시, 특정 최적화 기능이 필요한 경우[2][9]
Next.js 팀은 Turbopack의 지속적인 개선을 위해
.next/trace-turbopack
파일 생성 기능을 제공하며[1][2], 성능 이슈 발생 시 해당 파일을 GitHub 이슈에 첨부할 것을 권장합니다[1][5]. 프로덕션 환경 사용을 위해서는 공식 릴리즈 노트에서 지속적인 업데이트 확인이 필요합니다[7][8].Citations:
- 1: https://nextjs.org/docs/app/api-reference/turbopack
- 2: https://nextjs-ko.org/docs/architecture/turbopack
- 3: https://dev.to/dimeloper/whats-new-in-nextjs-15-new-hooks-turbopack-and-more-2lo8
- 4: https://velog.io/@mayowall/%EB%82%98%EB%8A%94-Next14%EA%B0%80-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80-feat.-Metadata-Server-Actions-Turbopack
- 5: https://nextjs.org/blog/next-15-2
- 6: https://de-velop-humus.tistory.com/39
- 7: https://nextjs.org/blog/next-15
- 8: https://rainbowjyp.tistory.com/5
- 9: Source map warning on Next 15.2.4 with turbopack, Windows, Firefox vercel/next.js#77670
- 10: https://www.frontoverflow.com/magazine/15
검토: Turbopack 플래그 제거 의도 재확인 요청
패키지의
"dev": "next dev",
스크립트에서 이전에 사용되던--turbopack
플래그가 제거된 것을 확인했습니다.
Next.js 15.2.4에서는 Turbopack이 프로덕션 빌드를 지원하지 않으며, Windows+Firefox 환경의 안정성 문제, 일부 기능 미구현 및 성능 이슈 등이 보고되고 있습니다.
따라서 이번 변경이 이러한 제약 사항을 반영한 의도적인 결정이라면 문제가 없으나, 다음 사항을 재확인해주시기 바랍니다:
- 프로젝트 요구사항에 따라 Turbopack을 사용해야 하는지 여부
- Turbopack 사용이 필요한 경우, 권장 플래그인
--turbo
적용 여부app/(main)/academic-detail/[year]/[semester]/components/NavBar.tsx (3)
1-1
: 클라이언트 컴포넌트 정의가 적절합니다.라우팅 기능을 사용하기 위해 'use client' 지시어를 올바르게 선언했습니다.
3-4
: 필요한 컴포넌트와 훅 임포트가 적절합니다.UI 컴포넌트와 내부 라우터 훅을 올바르게 가져오고 있습니다.
6-11
: NavBar 컴포넌트 구현이 깔끔합니다.
useInternalRouter
를 올바르게 사용하고 있으며, TopNavigation.Preset 컴포넌트에 적절한 속성을 제공하고 있습니다.app/api/users/delete/route.ts (1)
11-11
: 비동기 Supabase 클라이언트 생성으로 개선됐습니다.Supabase 클라이언트 생성 코드를 비동기 방식으로 수정하여 클라이언트가 완전히 초기화된 후에 사용되도록 변경했습니다. 이는 안정성을 높이는 좋은 개선사항입니다.
app/api/suwon-scrape/resync/route.ts (1)
38-38
: 비동기 처리 방식으로 적절히 변경되었습니다.
createClient()
가 비동기 함수로 변경되었으므로await
를 사용하여 Promise가 resolve될 때까지 기다리도록 수정한 것이 적절합니다. 이 변경으로 Supabase 클라이언트가 완전히 초기화된 후에 다른 리포지토리 인스턴스를 생성하고 포털 데이터를 가져오는 작업이 진행됩니다.app/api/get-semesters/route.ts (1)
6-6
: 비동기 처리 방식으로 적절히 변경되었습니다.
createClient()
가 이제 비동기 함수이므로await
를 추가하여 Promise가 resolve될 때까지 기다리도록 수정한 것이 적절합니다. 이 변경으로 Supabase 클라이언트가 완전히 초기화된 후에 인증 및 데이터 조회 작업이 진행됩니다.app/api/graduation-progress/route.ts (1)
12-12
: 비동기 처리 방식으로 적절히 변경되었습니다.
createClient()
가 비동기 함수로 변경되었으므로await
를 사용하여 Promise가 resolve될 때까지 기다리도록 수정한 것이 올바른 접근 방식입니다. 이 변경으로 Supabase 클라이언트가 완전히 초기화된 후에 서비스 인스턴스 생성 및 후속 작업이 진행됩니다.lib/auth/supabase.ts (1)
7-7
: 비동기 처리 방식으로 적절히 변경되었습니다.
createClient()
가 비동기 함수로 변경되었으므로await
를 사용하여 Promise가 resolve될 때까지 기다리도록 수정한 것이 적절합니다. 이 변경으로 Supabase 클라이언트가 완전히 초기화된 후에 nonce를 검색하고 로그인 작업을 수행하게 됩니다.app/api/suwon-scrape/start/route.ts (1)
39-39
: Supabase 클라이언트 생성 방식 변경이 적절히 적용되었습니다.
createClient
함수가 비동기 함수로 변경되었으므로,await
를 사용하여 올바르게 호출하는 방식으로 업데이트되었습니다. 이는lib/supabase/server.ts
에서의 변경사항과 일치합니다.app/(main)/academic-detail/[year]/[semester]/apis/getSemesters.ts (1)
1-7
: 인터페이스 정의와 캐시 설정이 적절합니다.React의
cache
함수를 사용하여 데이터 중복 요청을 방지하는 방식은 적절합니다.Semester
인터페이스도 명확하게 정의되어 있습니다.lib/supabase/server.ts (1)
1-5
: 비동기 함수로의 리팩토링이 적절히 이루어졌습니다.
createClient
함수를 비동기 함수로 변경하고cookies()
함수를 올바르게 await 처리한 것은 좋은 개선입니다. 이로 인해 쿠키 저장소가 제대로 초기화된 후에 Supabase 클라이언트를 생성할 수 있게 되었습니다.app/(main)/academic-detail/[year]/[semester]/page.tsx (2)
10-16
: 병렬 데이터 페칭 구현이 효율적입니다.
Promise.all
을 사용하여 여러 데이터 요청을 병렬로 처리하는 것은 성능 측면에서 효율적입니다. 또한params
에서year
와semester
를 올바르게 추출하고 있습니다.
8-8
:❓ Verification inconclusive
캐싱 전략 확인 필요
fetchCache
값이'force-cache'
로 설정되어 있습니다. 이는 데이터가 항상 캐시에서 제공되도록 하며, 새로운 데이터를 가져오지 않을 수 있습니다. 동적 데이터를 다루는 경우, 적절한 재검증 전략을 고려해야 합니다.
🌐 Web query:
Next.js 14에서 fetchCache 옵션과 force-cache의 동작 방식
💡 Result:
Next.js 14에서
fetchCache
옵션과force-cache
설정은 데이터 캐싱 동작을 세밀하게 제어하기 위한 핵심 메커니즘입니다. 이들의 상호작용과 기본 동작 방식을 다음과 같이 정리할 수 있습니다:1. 기본 캐싱 동작
- Next.js 14에서는
fetch
요청 시cache: "force-cache"
가 기본값으로 설정되어 있습니다[1][4]. 이는 명시적으로cache: "no-store"
를 지정하지 않는 한 모든fetch
요청 결과가 자동으로 캐싱됨을 의미합니다.- 캐시된 데이터는 재검증(revalidation) 주기 없이 계속 사용됩니다[1][8].
2.
force-cache
의 세부 동작const res = await fetch('https://api.example.com/data', { cache: 'force-cache' });
- 캐시 적중 시: 저장된 데이터를 즉시 반환하며 백엔드 요청을 수행하지 않습니다[3][7].
- 캐시 미스 시: 백엔드에서 데이터를 가져온 후 캐시에 저장합니다[7].
- 재검증:
next.revalidate
옵션으로 시간 기준 재검증 주기를 설정할 수 있습니다(예:{ next: { revalidate: 3600 } }
)[10].3.
fetchCache
옵션의 영향력페이지/레이아웃에서
export const fetchCache
를 설정하면 개별fetch
호출의 캐시 옵션을 전역적으로 제어할 수 있습니다[2][5]:
설정 값 효과 force-no-store
모든 fetch
요청을no-store
로 강제 적용(개별 설정 무시)[2][5]force-cache
모든 fetch
요청을force-cache
로 강제 적용(개별 설정 무시)[2][5]default-no-store
기본값을 no-store
로 설정하되, 개별force-cache
설정은 허용[2][5]4. 동적 렌더링과의 상호작용
- 동적 경로(
dynamic = 'force-dynamic'
) 사용 시 풀 라우트 캐시가 무효화되지만,force-cache
가 설정된fetch
요청은 데이터 캐시를 계속 활용합니다[4][8].revalidatePath
/revalidateTag
를 사용하면 특정 태그와 연관된 캐시를 수동으로 갱신할 수 있습니다[4][10].5. 디버깅 및 모니터링
// next.config.mjs const nextConfig = { logging: { fetches: { fullUrl: true } } };
- 이 설정을 통해 콘솔에서
cache: skip
(캐시 미사용) 또는cache: hit
(캐시 적중) 상태를 확인할 수 있습니다[1][7].6. 버전별 차이점
- Next.js 15 RC부터는
fetch
기본값이no-store
로 변경되었으나, Next.js 14에서는 여전히force-cache
가 기본 동작입니다[4][8].이 메커니즘들을 조합하면 정적 생산성과 동적 최신성 사이의 균형을 유연하게 조절할 수 있습니다. 예를 들어 자주 변경되지 않는 CMS 데이터는
force-cache
로 캐십하고, 실시간 주가 정보는no-store
로 처리하는 식의 전략이 가능합니다[3][8].Citations:
- 1: https://velog.io/@codns1223/Nextjs-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%BA%90%EC%8B%9CData-Cache
- 2: https://mycodings.fly.dev/blog/2024-03-02-nextjs-fetchcache
- 3: https://wha-haha.tistory.com/315
- 4: https://velog.io/@leejpsd/Next.js-14.1-cache-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%B5%9C%EC%A0%81%ED%99%94
- 5: https://falsy.me/%EC%86%8C%EC%86%8C%ED%95%9C-%EA%B0%9C%EB%B0%9C-%EC%9D%BC%EC%A7%80-next-js%EC%9D%98-ssr%EC%99%80-rsc%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90/
- 6: https://seholee.com/blog/next-js-cache-part-1
- 7: https://www.wooglim.dev/snippets/Next14-Learning-book-08
- 8: https://shawnkim.tistory.com/137
- 9: https://velog.io/@hamjw0122/Next.js-Next.js%EC%97%90%EC%84%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-Fetching%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95
- 10: https://unsangu.com/posts/13
캐싱 옵션 검토 요청
- 현재 페이지(
app/(main)/academic-detail/[year]/[semester]/page.tsx
)에서 전역 캐싱 옵션이force-cache
로 설정되어 있습니다.- Next.js 14에서
force-cache
는 기본 캐싱 정책으로, 별도의 재검증(revalidation) 옵션이 없으면 캐시된 데이터가 계속 재사용됩니다.- 만약 이 페이지의 데이터가 동적으로 변경되어 최신 데이터가 필요하다면,
no-store
옵션이나next.revalidate
를 활용해 재검증 전략을 추가하는 방향을 고려해보시기 바랍니다.app/api/target-gpa/route.ts (1)
14-14
: 비동기 팩토리 메서드를 사용한 서비스 초기화 개선
StudentService
인스턴스 생성 방식이 동기식 생성자 호출에서 비동기 팩토리 메서드로 변경되었습니다. 이는 Supabase 클라이언트의 비동기 초기화를 올바르게 처리하여 코드의 안정성을 향상시킵니다.app/api/get-academic/[year]/[semester]/route.ts (1)
84-84
: GET 함수 시그니처 개선함수 시그니처가 보다 간결한 형태로 재구성되었습니다. 이는 코드 가독성을 향상시키며 기능적 변화 없이 코드 스타일을 개선합니다.
app/api/get-dashboard/route.ts (1)
8-8
: 팩토리 메서드를 통한 의존성 주입 개선여러 서비스를 수동으로 초기화하는 대신
DashboardFacade.create()
팩토리 메서드를 사용하여 코드가 간결해졌습니다. 이 방식은 의존성 주입을 더 명확하게 하고 초기화 로직을 캡슐화하여 코드 유지보수성을 향상시킵니다.lib/supabase/services/graduation-progress-service.ts (2)
22-22
: 생성자 매개변수 요구사항 개선생성자에서
supabase
매개변수의 기본값이 제거되어 명시적으로 SupabaseClient 인스턴스를 전달해야 합니다. 이는 의존성을 더 명확하게 하고 잠재적인 초기화 문제를 방지합니다.
24-27
: 정적 팩토리 메서드 추가비동기
create()
메서드가 추가되어 Supabase 클라이언트를 올바르게 초기화하고 서비스 인스턴스를 생성합니다. 이 패턴은 다른 서비스 클래스와 일관성을 유지하며, 비동기 초기화를 보다 안전하게 처리합니다.lib/supabase/services/professor-service.ts (2)
6-11
: 정적 팩토리 메서드 패턴 적용 - 좋은 변경Supabase 클라이언트를 생성하고 서비스 인스턴스를 반환하는 정적
create
메서드 추가는 좋은 패턴입니다. 의존성 주입을 명시적으로 하여 코드의 가독성과 테스트 가능성을 향상시켰습니다.
21-57
: 기존 메서드 로직에 대한 검토
getOrCreateProfessorByName
메서드의 로직은 적절하게 구현되어 있습니다. 교수를 이름으로 조회하고, 없으면 새로 생성하는 로직이 명확합니다. 에러 처리도 잘 되어 있습니다.lib/supabase/services/student-service.ts (3)
21-26
: 생성자 의존성 주입 패턴 적용 - 좋은 변경Supabase 클라이언트를 명시적으로 주입받도록 생성자를 변경한 것은 좋은 패턴입니다. 코드의 명확성과 테스트 가능성이 향상되었습니다.
28-31
: 정적 팩토리 메서드 패턴 적용 - 좋은 변경비동기적으로 Supabase 클라이언트를 생성하고 서비스 인스턴스를 반환하는 정적
create
메서드 추가는 좋은 패턴입니다. 초기화 로직을 한 곳에 집중시키고 사용하기 쉬운 API를 제공합니다.
33-195
: 기존 메서드 로직에 대한 검토
StudentService
클래스의 메서드 로직은 전반적으로 잘 구현되어 있습니다. 인증된 사용자 ID를 가져오는 메서드, 학생 정보 초기화, 포털 연동 해제, 연동 상태 확인, 목표 학점 업데이트, 학생 정보 조회 등의 기능이 명확하게 구현되어 있습니다. 각 메서드의 에러 처리도 적절합니다.lib/supabase/services/course-offering-service.ts (1)
28-33
: 비동기 초기화 패턴으로의 전환이 잘 되었습니다.기존 동기식 생성자에서 명시적인 의존성 주입 방식과 비동기 static factory 메서드로 변경한 것은 좋은 접근법입니다. 이렇게 하면 Supabase 클라이언트의 생성과 관리가 더 명확해지고, 서버 컴포넌트에서 활용하기에 더 적합합니다.
특히
createClient()
가 비동기 함수이므로, 생성자에서 직접 호출하는 대신 정적 팩토리 메서드를 통해 비동기적으로 처리하는 것이 올바른 방식입니다.lib/supabase/services/academic-record-service.ts (1)
23-28
: 비동기 생성 패턴 구현이 일관성 있게 적용되었습니다.생성자에서 의존성을 명시적으로 받도록 변경하고, 정적 팩토리 메서드를 통해 비동기 초기화를 처리하는 방식이
CourseOfferingService
와 일관되게 적용되었습니다. 이는 코드의 일관성과 예측 가능성을 높여줍니다.이 패턴은 서버 컴포넌트 환경에서 Supabase 클라이언트를 안전하게 생성하고 사용할 수 있게 해줍니다.
app/(main)/academic-detail/[year]/[semester]/components/SemesterSlider.tsx (3)
12-16
: Props 인터페이스 확장이 적절하게 이루어졌습니다.
SemesterSliderProps
인터페이스에semesters
배열을 추가한 것은 컴포넌트의 책임을 데이터 소스로부터 분리하는 좋은 접근법입니다.
21-24
: 라우팅 로직이 개선되었습니다.파라미터 전달 방식을 객체에서 배열로 변경한 것은 Next.js의 동적 라우팅 패턴과 더 잘 맞습니다. 이는 URL 구조가
/academic-detail/[year]/[semester]
형식으로 정의되어 있기 때문에 적절한 변경입니다.
41-41
: 상태 관리 로직이 제거되었습니다.기존 상태 관리 및 데이터 페칭 로직이 제거되고, 부모 컴포넌트로부터
semesters
데이터를 props로 받도록 변경된 것은 컴포넌트의 책임을 명확히 분리하는 좋은 리팩토링입니다. 이제 이 컴포넌트는 데이터 표시에만 집중할 수 있습니다.lib/supabase/services/course-service.ts (1)
13-18
: 비동기 생성 패턴이 일관되게 적용되었습니다.다른 서비스 클래스들과 마찬가지로,
CourseService
도 명시적 의존성 주입과 비동기 정적 팩토리 메서드 패턴을 도입했습니다. 이러한 일관된 패턴 적용은 코드베이스의 유지보수성을 높여줍니다.특히
createClient()
가 비동기 함수이므로, 이를 사용하여 서비스 객체를 생성하는 정적 메서드를 제공하는 것은 적절한 접근법입니다.lib/supabase/services/department-service.ts (2)
6-6
: 생성자가 명시적으로 SupabaseClient를 요구하도록 변경되었습니다.이전에는 생성자 내부에서
createClient()
를 통해 기본 Supabase 클라이언트를 생성했으나, 이제는 외부에서 주입받도록 변경되었습니다. 이는 의존성 주입 패턴을 따르는 좋은 변화입니다.
8-11
: 정적 팩토리 메서드 추가는 좋은 패턴입니다.새로 추가된
create()
정적 메서드는 비동기적으로 Supabase 클라이언트를 생성하고 이를 사용하여 DepartmentService 인스턴스를 반환합니다. 이는 비동기 초기화를 깔끔하게 처리하며, 다음과 같은 장점이 있습니다:
- 클라이언트 생성 로직을 캡슐화
- 일관된 인스턴스 생성 패턴 제공
- 비동기 초기화 프로세스를 명확하게 표현
server/infrastructure/supabase/repository/SupabaseProfessorRepository.ts (2)
10-10
: 생성자 매개변수가 명시적으로 변경되었습니다.생성자가 이제 명시적으로
supabase
클라이언트를 필요로 하며, private readonly로 선언되어 있습니다. 이는 의존성 주입을 명확히 하고 클래스의 불변성을 향상시키는 좋은 변화입니다.
12-15
: 비동기 팩토리 메서드 추가는 적절합니다.새로 추가된
create()
정적 메서드는createClient()
를 비동기적으로 호출하여 Supabase 클라이언트를 얻고, 이를 사용하여 새 SupabaseProfessorRepository 인스턴스를 반환합니다. 이 패턴은 다음과 같은 이점이 있습니다:
- 클라이언트 생성의 비동기 특성을 적절히 처리
- 사용하기 쉬운 API 제공
- 다른 서비스 클래스와의 일관성 유지
lib/supabase/facades/dashboard-facade.ts (3)
7-9
: 필요한 의존성이 적절하게 추가되었습니다.새로운 의존성(SupabaseAuthService, SupabaseUserRepository, createClient)을 import하여 static create 메서드에서 사용할 수 있도록 했습니다. 이는 DashboardFacade의 기능을 확장하면서도 코드의 구조를 개선하는 좋은 변화입니다.
15-16
: 생성자 매개변수가 명시적 의존성 주입을 위해 개선되었습니다.이전에는 StudentService와 AcademicRecordService를 생성자 내부에서 직접 초기화했지만, 이제는 외부에서 주입받도록 변경되었습니다. 이는 다음과 같은 이점이 있습니다:
- 단위 테스트 용이성 향상
- 의존성 관리의 유연성 증가
- 관심사의 분리(SoC) 강화
19-26
: 정적 팩토리 메서드 추가는 효과적인 의존성 관리를 제공합니다.새로 추가된
create()
정적 메서드는 DashboardFacade의 모든 의존성을 생성하고 이를 사용하여 새 인스턴스를 반환합니다. 이 접근 방식은:
- 복잡한 의존성 트리를 캡슐화하여 사용자 코드를 단순화
- 비동기 초기화를 깔끔하게 처리
- 모든 의존성이 적절히 초기화되도록 보장
이 방식으로 API 라우트나 컴포넌트에서 DashboardFacade를 사용할 때 의존성 생성에 대한 걱정 없이 단순히
await DashboardFacade.create()
를 호출하면 됩니다.lib/supabase/services/student-course-service.ts (2)
14-14
: 의존성 주입을 위한 생성자 개선이 이루어졌습니다.이전에는 생성자에서 기본값으로
createClient()
를 사용했지만, 이제는 외부에서 SupabaseClient를 명시적으로 주입받도록 변경되었습니다. 이는 코드의 테스트 용이성을 향상시키고 의존성 관리를 명확히 하는 좋은 변화입니다.
16-19
: 비동기 팩토리 메서드 추가는 일관된 패턴을 유지합니다.새로 추가된
create()
정적 메서드는 다른 서비스 클래스들과 일관된 패턴을 유지하며, Supabase 클라이언트의 비동기 생성을 적절히 처리합니다. 이 접근 방식은:
- 서비스 인스턴스화 과정의 일관성 유지
- 비동기 클라이언트 생성의 복잡성 추상화
- 사용자 코드를 더 단순하고 명확하게 만듦
다른 서비스 클래스들과 함께 이러한 변경은 전체 애플리케이션의 아키텍처를 개선합니다.
export default function GraduationProgressLayout({ children }: { children: React.ReactNode }) { | ||
|
||
return ( | ||
<div className={styles.container}> | ||
<NavBar /> | ||
<div className={styles.content}>{children}</div> | ||
</div> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
컴포넌트 이름과 기능 간의 불일치가 있습니다.
컴포넌트 이름은 GraduationProgressLayout
이지만 NavBar의 제목은 "학기별 세부 성적"입니다. 이는 명명 규칙에 혼란을 줄 수 있습니다.
-export default function GraduationProgressLayout({ children }: { children: React.ReactNode }) {
+export default function AcademicDetailLayout({ children }: { children: React.ReactNode }) {
📝 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.
export default function GraduationProgressLayout({ children }: { children: React.ReactNode }) { | |
return ( | |
<div className={styles.container}> | |
<NavBar /> | |
<div className={styles.content}>{children}</div> | |
</div> | |
); | |
} | |
export default function AcademicDetailLayout({ children }: { children: React.ReactNode }) { | |
return ( | |
<div className={styles.container}> | |
<NavBar /> | |
<div className={styles.content}>{children}</div> | |
</div> | |
); | |
} |
const res = await fetch(`http://localhost:3000/api/get-semesters`, { | ||
cache: 'force-cache', | ||
headers: { | ||
'Cookie': `${abtoken}; ${token}`, | ||
}, | ||
credentials: 'include', | ||
next: { tags: ['semesters'], revalidate: 60 * 60 * 24 } | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
하드코딩된 URL 및 캐싱 전략 재검토 필요
로컬호스트 URL이 하드코딩되어 있어 운영 환경에서 문제가 될 수 있습니다. 상대 경로나 환경 변수를 사용하는 것이 더 적절합니다. 또한 force-cache
와 next.revalidate
를 동시에 사용하고 있는데, 이 두 설정의 상호작용을 확인해야 합니다.
- const res = await fetch(`http://localhost:3000/api/get-semesters`, {
+ const res = await fetch(`/api/get-semesters`, {
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const res = await fetch(`http://localhost:3000/api/get-semesters`, { | |
cache: 'force-cache', | |
headers: { | |
'Cookie': `${abtoken}; ${token}`, | |
}, | |
credentials: 'include', | |
next: { tags: ['semesters'], revalidate: 60 * 60 * 24 } | |
}); | |
const res = await fetch(`/api/get-semesters`, { | |
cache: 'force-cache', | |
headers: { | |
'Cookie': `${abtoken}; ${token}`, | |
}, | |
credentials: 'include', | |
next: { tags: ['semesters'], revalidate: 60 * 60 * 24 } | |
}); |
if (!res.ok) {throw new Error('Failed to fetch');} | ||
return res.json(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
오류 처리 개선 필요
현재 오류 처리는 기본적인 메시지만 제공합니다. 사용자에게 더 명확한 오류 메시지를 제공하고, 가능하다면 오류 상황에 맞는 구체적인 처리 로직을 추가하는 것이 좋습니다.
- if (!res.ok) {throw new Error('Failed to fetch');}
+ if (!res.ok) {
+ const errorText = await res.text().catch(() => 'Unknown error');
+ throw new Error(`학기 정보를 가져오는데 실패했습니다: ${res.status} ${errorText}`);
+ }
📝 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.
if (!res.ok) {throw new Error('Failed to fetch');} | |
return res.json(); | |
}); | |
if (!res.ok) { | |
const errorText = await res.text().catch(() => 'Unknown error'); | |
throw new Error(`학기 정보를 가져오는데 실패했습니다: ${res.status} ${errorText}`); | |
} | |
return res.json(); | |
}); |
export const getSemesters = cache(async () : Promise<Semester[]> => { | ||
const abtoken = process.env.TEST_TOKEN; | ||
const token = process.env.TEST_TOKEN2; | ||
console.log('🔥 실제 fetch 발생! [getSemesters]'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
환경 변수 사용 방식 검토 필요
개발 목적으로 보이는 TEST_TOKEN
과 TEST_TOKEN2
환경 변수명이 사용되고 있습니다. 운영 환경에서는 더 명확한 변수명을 사용하는 것이 좋습니다. 또한 콘솔 로그는 운영 환경에서 제거하는 것이 좋습니다.
- const abtoken = process.env.TEST_TOKEN;
- const token = process.env.TEST_TOKEN2;
- console.log('🔥 실제 fetch 발생! [getSemesters]');
+ const authToken = process.env.AUTH_TOKEN;
+ const sessionToken = process.env.SESSION_TOKEN;
📝 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.
export const getSemesters = cache(async () : Promise<Semester[]> => { | |
const abtoken = process.env.TEST_TOKEN; | |
const token = process.env.TEST_TOKEN2; | |
console.log('🔥 실제 fetch 발생! [getSemesters]'); | |
export const getSemesters = cache(async () : Promise<Semester[]> => { | |
const authToken = process.env.AUTH_TOKEN; | |
const sessionToken = process.env.SESSION_TOKEN; |
return ( | ||
<Suspense fallback={<div></div>}> | ||
<div className="gap-8"></div> | ||
<SemesterSlider currentYear={year} currentSemester={semester} semesters={semesters} /> | ||
<div className="gap-20"></div> | ||
<AcademicDetailContent data={data} /> | ||
</Suspense> | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Suspense 사용이 적절합니다.
Suspense를 사용하여 로딩 상태를 처리하는 방식은 적절합니다. 다만, 현재 fallback은 비어 있는데, 사용자 경험을 향상시키기 위해 로딩 인디케이터나 스켈레톤 UI를 추가하는 것을 고려해보세요.
- <Suspense fallback={<div></div>}>
+ <Suspense fallback={<div className="p-4 text-center">학업 정보를 불러오는 중...</div>}>
📝 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.
return ( | |
<Suspense fallback={<div></div>}> | |
<div className="gap-8"></div> | |
<SemesterSlider currentYear={year} currentSemester={semester} semesters={semesters} /> | |
<div className="gap-20"></div> | |
<AcademicDetailContent data={data} /> | |
</Suspense> | |
); | |
return ( | |
<Suspense fallback={<div className="p-4 text-center">학업 정보를 불러오는 중...</div>}> | |
<div className="gap-8"></div> | |
<SemesterSlider currentYear={year} currentSemester={semester} semesters={semesters} /> | |
<div className="gap-20"></div> | |
<AcademicDetailContent data={data} /> | |
</Suspense> | |
); |
🧰 Tools
🪛 Biome (1.9.4)
[error] 19-19: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing.
Unsafe fix: Use a SelfClosingElement instead
(lint/style/useSelfClosingElements)
[error] 20-20: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing.
Unsafe fix: Use a SelfClosingElement instead
(lint/style/useSelfClosingElements)
[error] 22-22: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing.
Unsafe fix: Use a SelfClosingElement instead
(lint/style/useSelfClosingElements)
<AcademicSummaryCard | ||
earnedCredits={data.semesterGrades[0].earnedCredits} | ||
gpa={data.semesterGrades[0].semesterGpa} | ||
classRank={data.semesterGrades[0].classRank} | ||
totalStudents={data.semesterGrades[0].totalStudents} | ||
/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
예외 처리 필요
data.semesterGrades
가 비어 있거나 undefined
인 경우에 대한 예외 처리가 없습니다. 데이터가 없는 경우 semesterGrades[0]
에 접근하면 오류가 발생할 수 있습니다.
+ {data.semesterGrades?.length > 0 ? (
<AcademicSummaryCard
earnedCredits={data.semesterGrades[0].earnedCredits}
gpa={data.semesterGrades[0].semesterGpa}
classRank={data.semesterGrades[0].classRank}
totalStudents={data.semesterGrades[0].totalStudents}
/>
+ ) : (
+ <div>학기 성적 정보가 없습니다.</div>
+ )}
📝 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.
<AcademicSummaryCard | |
earnedCredits={data.semesterGrades[0].earnedCredits} | |
gpa={data.semesterGrades[0].semesterGpa} | |
classRank={data.semesterGrades[0].classRank} | |
totalStudents={data.semesterGrades[0].totalStudents} | |
/> | |
{data.semesterGrades?.length > 0 ? ( | |
<AcademicSummaryCard | |
earnedCredits={data.semesterGrades[0].earnedCredits} | |
gpa={data.semesterGrades[0].semesterGpa} | |
classRank={data.semesterGrades[0].classRank} | |
totalStudents={data.semesterGrades[0].totalStudents} | |
/> | |
) : ( | |
<div>학기 성적 정보가 없습니다.</div> | |
)} |
{/* 전공 과목 목록 */} | ||
<SectionCourses title="전공" courses={data.courses.major} /> | ||
{/* 교양 과목 목록 */} | ||
<div className="gap-24"></div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
자식 요소가 없는 JSX 요소는 self-closing 태그로 사용
자식 요소가 없는 div 요소는 self-closing 태그로 사용하는 것이 권장됩니다.
- <div className="gap-24"></div>
+ <div className="gap-24" />
📝 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.
<div className="gap-24"></div> | |
<div className="gap-24" /> |
classRank={data.semesterGrades[0].classRank} | ||
totalStudents={data.semesterGrades[0].totalStudents} | ||
/> | ||
<div className="gap-24"></div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
자식 요소가 없는 JSX 요소는 self-closing 태그로 사용
자식 요소가 없는 div 요소는 self-closing 태그로 사용하는 것이 권장됩니다.
- <div className="gap-24"></div>
+ <div className="gap-24" />
📝 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.
<div className="gap-24"></div> | |
<div className="gap-24" /> |
🧰 Tools
🪛 Biome (1.9.4)
[error] 33-34: JSX elements without children should be marked as self-closing. In JSX, it is valid for any element to be self-closing.
Unsafe fix: Use a SelfClosingElement instead
(lint/style/useSelfClosingElements)
|
||
export default function AcademicDetailContent({data}: {data: AcademicDetailProps}) { | ||
|
||
console.log('data',data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
프로덕션 코드에서 디버깅 콘솔 로그 제거 필요
디버깅을 위한 콘솔 로그는 프로덕션 코드에서 제거해야 합니다.
- console.log('data',data);
📝 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.
console.log('data',data); |
} | ||
|
||
export default function SemesterSlider({ currentYear, currentSemester }: SemesterSliderProps) { | ||
export default function SemesterSlider({ currentYear, currentSemester , semesters}: SemesterSliderProps) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
컴포넌트 서명이 업데이트되었지만 포맷이 개선될 수 있습니다.
함수 서명에서 currentSemester
다음에 공백이 누락되어 있습니다. 포맷을 일관되게 유지하기 위해 수정하는 것이 좋겠습니다.
-export default function SemesterSlider({ currentYear, currentSemester , semesters}: SemesterSliderProps) {
+export default function SemesterSlider({ currentYear, currentSemester, semesters }: SemesterSliderProps) {
📝 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.
export default function SemesterSlider({ currentYear, currentSemester , semesters}: SemesterSliderProps) { | |
export default function SemesterSlider({ currentYear, currentSemester, semesters }: SemesterSliderProps) { |
Summary by CodeRabbit
신규 기능
개선 사항