Skip to content

미리배포#81

Merged
KimGyeongLock merged 5 commits intomainfrom
30-feature-ranking
Jan 25, 2026
Merged

미리배포#81
KimGyeongLock merged 5 commits intomainfrom
30-feature-ranking

Conversation

@KimGyeongLock
Copy link
Owner

.

@gemini-code-assist
Copy link

Summary of Changes

Hello @KimGyeongLock, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 전공 및 교양 과목 모두에 대한 서브 카테고리를 구현하여 과목 랭킹 기능을 크게 개선합니다. 프론트엔드에서는 전공 과목에 대한 드래그 기능을 포함한 서브 탭 UI 컴포넌트를 추가하고, 이러한 서브 카테고리에 따라 랭킹 표시 로직을 업데이트했습니다. 백엔드에서는 새로운 로드맵 인덱스를 사용하여 전공 과목을 학기별로 분류하고, 교양 과목을 특정 카테고리별로 분류하는 새로운 데이터 구조와 서비스를 개발하여 더욱 세분화되고 체계적인 랭킹 시스템을 제공합니다.

Highlights

  • 프론트엔드: Footer.tsx: NOTICE_SEEN_KEY를 v3에서 v4로 업데이트하여 사용자에게 새로운 공지 알림을 재설정합니다.
  • 프론트엔드: CurriculumTable.module.css: .rankingPane에서 bottom: 24px;를, .rankingCard에서 height: 100%;를 제거했습니다. 또한 .rankingBody의 min-height, max-height, overflow: auto;, flex: 1; 속성을 제거하고 overflow: visible;로 변경하여 랭킹 패널의 레이아웃과 스크롤 동작을 조정했습니다.
  • 프론트엔드: CourseRankingSection.tsx: RankingCard의 initialCategory를 "liberal"에서 "major"로 변경하여 랭킹 섹션이 기본적으로 전공 과목을 표시하도록 했습니다.
  • 프론트엔드: RankingCard.module.css: 서브 탭을 위한 새로운 CSS 클래스를 추가했으며, 특히 majorSubTabs에 가로 스크롤 및 드래그 UX를 위한 스타일을 포함했습니다.
  • 프론트엔드: RankingCard.tsx: useRef 훅을 임포트하고, LiberalSubTab 및 MajorSubTab 새 타입을 정의했습니다. 서브 탭 정의를 위한 LIBERAL_TABS 및 MAJOR_TABS 상수를 추가하고, 활성 서브 탭을 관리하기 위해 libTab 및 majorTab 상태를 도입했습니다. 메인 카테고리 전환 시 서브 탭을 재설정하도록 onChangeCat을 수정하고, 활성 서브 탭에 따라 데이터를 가져오도록 items useMemo 의존성을 업데이트했습니다. useRef 및 이벤트 핸들러를 사용하여 majorSubTabs에 드래그 기능을 구현했으며, 전공 및 교양 서브 탭에 대한 조건부 렌더링을 추가하고, 랭킹 목록 항목의 key 속성을 활성 서브 탭을 포함하도록 업데이트했습니다.
  • 백엔드: MajorRoadmapIndex.java (새 파일): JSON 파일에서 전공 로드맵 데이터를 로드하고, 주어진 과목명에 대한 학기(학년 및 학기)를 찾는 기능을 제공하는 MajorRoadmapIndex 새 Java 컴포넌트를 추가했습니다.
  • 백엔드: CourseRankingController.java: 사용되지 않는 CourseRankingDto 임포트를 제거하고 courseBanking 메서드 이름을 courseRanking으로 수정했습니다.
  • 백엔드: CourseRankingDto.java: RankingResponse를 MajorRanking 및 LiberalRanking 타입으로 리팩토링하여, 이제 특정 서브 카테고리(예: 교양의 faithWorldview, 전공의 y1s2)에 대한 RankingItem 목록을 포함하도록 했습니다.
  • 백엔드: CourseRankingService.java: MajorRoadmapIndex를 주입하고, getCourseRanking을 loadMajorByTerms를 사용하여 전공 랭킹을 로드하고, 각 교양 서브 카테고리에 대해 별도의 loadTop10 호출을 사용하도록 리팩토링했습니다. loadMajorByTerms 메서드를 도입하여 더 많은 전공 과목을 가져온 다음 MajorRoadmapIndex를 사용하여 학년/학기 버킷으로 분류합니다. 코스 행 목록에서 상위 10개 항목을 포맷하는 toTop10 헬퍼 메서드를 추가하고, loadTop10이 applyLiberalFilter 대신 applyFilter 불리언을 받도록 업데이트하여 더 일반적인 용도로 사용 가능하게 했습니다. 각 교양 서브 카테고리에 대한 특정 EnumSet 상수를 정의했습니다.
  • 백엔드: major_roadmap.json (새 파일): 학년, 학기, 과목 코드, 한국어 이름, 영어 이름을 포함하는 전공 과목 로드맵 데이터를 정의하는 새 JSON 파일을 추가했습니다.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

이번 PR은 과목 랭킹 기능을 전공과 교양 하위 카테고리별로 세분화하는 중요한 개선 작업을 포함하고 있네요. 백엔드에서는 학기/영역별로 데이터를 구조화하고, 프론트엔드에서는 서브 탭과 드래그 스크롤 같은 향상된 UX를 제공하여 사용성을 크게 높인 점이 인상적입니다. 전반적으로 좋은 방향의 변경이라고 생각합니다.

몇 가지 코드 품질 및 잠재적 버그 개선을 위한 제안 사항을 코멘트로 남겼습니다. 확인 부탁드립니다.

추가로, PR 제목과 설명이 없어 변경의도 파악에 어려움이 있었습니다. 저장소 스타일 가이드(131, 134-137번 라인)에 따라 Conventional Commits 형식의 커밋 메시지와 상세한 PR 설명을 작성해주시면 동료들이 변경 사항을 이해하고 리뷰하는 데 큰 도움이 될 것입니다.

};

export type MajorRanking = {
y1s1: RankingItem[];

Choose a reason for hiding this comment

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

medium

백엔드 API 응답의 major 객체에는 y1s1 필드가 포함되지 않습니다. 프론트엔드와 백엔드의 데이터 모델을 일치시키기 위해 이 라인을 제거하는 것이 좋습니다. 현재 CourseRankingService를 보면 전공 과목 랭킹은 1학년 2학기(y1s2)부터 집계하고 있습니다.

Suggested change
y1s1: RankingItem[];
y1s2: RankingItem[];

// ✅ 공백만 제거 + trim
private static String norm(String s) {
if (s == null) return "";
return s.trim().replaceAll("\\s+", "");

Choose a reason for hiding this comment

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

medium

과목명으로 학기를 조회할 때, 현재 정규화 로직(norm)이 대소문자를 구분하고 있습니다. 예를 들어 'data structure'와 'Data Structure'를 다르게 취급하여 일치하지 않을 수 있습니다. toLowerCase()를 추가하여 대소문자 구분 없이 비교하면 더 안정적으로 과목을 찾을 수 있습니다.

Suggested change
return s.trim().replaceAll("\\s+", "");
return s.trim().replaceAll("\\s+", "").toLowerCase();

this.majorRoadmapIndex = majorRoadmapIndex;
}

public RankingResponse getCourseRanking() {

Choose a reason for hiding this comment

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

medium

스타일 가이드 66번 라인에 따라, 읽기 전용 로직을 처리하는 서비스 메서드에는 @Transactional(readOnly = true)를 명시하는 것을 권장합니다. 이를 통해 불필요한 쓰기 트랜잭션을 방지하고 성능상 이점을 얻을 수 있습니다.

Suggested change
public RankingResponse getCourseRanking() {
@org.springframework.transaction.annotation.Transactional(readOnly = true)
public RankingResponse getCourseRanking() {
References
  1. 읽기 전용 쿼리는 성능 향상을 위해 @Transactional(readOnly = true)를 명시해야 합니다. (link)

@@ -19,7 +18,7 @@ public CourseRankingController(CourseRankingService rankingService) {
}

@GetMapping("/courses")

Choose a reason for hiding this comment

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

low

메서드 이름에 오타가 있습니다. courseBankingcourseRanking으로 수정하여 의미를 명확하게 하는 것이 좋겠습니다.

    public RankingResponse courseRanking() {

Comment on lines +73 to +111
PageRequest.of(0, 300)
);

for (var r : fetchedRows) {
var termOpt = majorRoadmapIndex.findTermByCourseName(r.getName());
if (termOpt.isEmpty()) continue;

var key = termOpt.get().toBucketKey();
var list = bucket.get(key);
if (list != null) list.add(r); // 혹시 1-1 같은게 들어오면 null이라 무시
}

return new MajorRanking(
toTop10(bucket.get("y1s2")),
toTop10(bucket.get("y2s1")),
toTop10(bucket.get("y2s2")),
toTop10(bucket.get("y3s1")),
toTop10(bucket.get("y3s2")),
toTop10(bucket.get("y4s1")),
toTop10(bucket.get("y4s2"))
);
}

private List<RankingItem> loadTop10(Set<Category> categories, boolean applyLiberalFilter) {
int fetchSize = applyLiberalFilter ? 30 : 10;
private List<RankingItem> toTop10(List<CourseRankingRepository.CourseCountRow> rows) {
if (rows == null) return List.of();
var finalRows = rows.stream().limit(10).toList();

return IntStream.range(0, finalRows.size())
.mapToObj(i -> new RankingItem(
i + 1,
finalRows.get(i).getName(),
finalRows.get(i).getTakenCount(),
0
))
.toList();
}

private List<RankingItem> loadTop10(Set<Category> categories, boolean applyFilter) {
int fetchSize = applyFilter ? 30 : 10;

Choose a reason for hiding this comment

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

low

코드 내에 300, 30, 10과 같은 매직 넘버가 사용되었습니다. 이 값들을 의미를 명확하게 나타내는 private static final 상수로 추출하면 가독성과 유지보수성이 향상됩니다.

예시:

private static final int MAJOR_FETCH_SIZE = 300;
private static final int LIBERAL_FETCH_SIZE = 30;
private static final int RANKING_LIMIT = 10;
References
  1. 코드의 가독성을 위해 매직넘버 사용을 지양하고, 의미있는 이름의 상수로 정의해야 합니다. (link)

@sonarqubecloud
Copy link

@KimGyeongLock KimGyeongLock merged commit 0b1c7f7 into main Jan 25, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant