From d0eb4b03b31850385418c736b33555c32dd297a0 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Mon, 12 Jan 2026 21:05:26 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20users=20=ED=85=8C=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=EC=97=90=20level=20=EC=BB=AC=EB=9F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../baekjoon/service/BaekjoonService.java | 6 +- .../baektracker/domain/user/model/User.java | 4 + .../migration/V6__add_column_user_level.sql | 2 + .../db/procedure/init_user_streak.sql | 6 +- .../db/procedure/reset_weekly_result.sql | 76 +++++++++++++++++++ 5 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 src/main/resources/db/migration/V6__add_column_user_level.sql create mode 100644 src/main/resources/db/procedure/reset_weekly_result.sql diff --git a/src/main/java/com/baektracker/domain/baekjoon/service/BaekjoonService.java b/src/main/java/com/baektracker/domain/baekjoon/service/BaekjoonService.java index 2e4add60..406ac2f2 100644 --- a/src/main/java/com/baektracker/domain/baekjoon/service/BaekjoonService.java +++ b/src/main/java/com/baektracker/domain/baekjoon/service/BaekjoonService.java @@ -12,6 +12,7 @@ import com.baektracker.global.exception.CustomException; import java.io.IOException; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -37,8 +38,6 @@ public class BaekjoonService { public void loadBaekjoonProblemStatus() { List users = userRepository.findAll(); for (User user : users) { - int count = 0; - List scrappedProblems = new ArrayList<>(); int top = -1; do { @@ -60,9 +59,8 @@ public void loadBaekjoonProblemStatus() { System.out.printf("qwe (%s) scrapped : %d, last-read: %d\n", user.getUsername(), scrappedProblems.size(), updatedLastRead); user.updateLastRead(updatedLastRead); + user.updateLastReadTime(LocalDateTime.now()); solvedProblemRepository.saveAll(scrappedProblems); -// solvedProblemRepository.flush(); -// userRepository.flush(); } } } diff --git a/src/main/java/com/baektracker/domain/user/model/User.java b/src/main/java/com/baektracker/domain/user/model/User.java index 2ffc57ea..cfc6c645 100644 --- a/src/main/java/com/baektracker/domain/user/model/User.java +++ b/src/main/java/com/baektracker/domain/user/model/User.java @@ -54,6 +54,10 @@ public void updateLastRead(Integer lastRead) { this.lastRead = lastRead; } + public void updateLastReadTime(LocalDateTime lastReadTime) { + this.lastReadTime = lastReadTime; + } + public void setStreak(int streak) { this.streak = streak; } diff --git a/src/main/resources/db/migration/V6__add_column_user_level.sql b/src/main/resources/db/migration/V6__add_column_user_level.sql new file mode 100644 index 00000000..58fae4d7 --- /dev/null +++ b/src/main/resources/db/migration/V6__add_column_user_level.sql @@ -0,0 +1,2 @@ +alter table users + add column level int null diff --git a/src/main/resources/db/procedure/init_user_streak.sql b/src/main/resources/db/procedure/init_user_streak.sql index c08c9006..5ccfb2b1 100644 --- a/src/main/resources/db/procedure/init_user_streak.sql +++ b/src/main/resources/db/procedure/init_user_streak.sql @@ -7,9 +7,9 @@ set streak=(with recursive base as (select @start_date as dt, 0 as cnt, 0 as state union all - select date_sub(dt, interval 1 week) as dt, - cnt + (ws.state = 1) as cnt, - ws.state as state + select date_sub(dt, interval 1 week) as dt, + IF(ws.state in (1, 3), cnt + 1, cnt) as cnt, + ws.state as state from base left join weekly_results ws on ws.year_week = diff --git a/src/main/resources/db/procedure/reset_weekly_result.sql b/src/main/resources/db/procedure/reset_weekly_result.sql new file mode 100644 index 00000000..02ae8004 --- /dev/null +++ b/src/main/resources/db/procedure/reset_weekly_result.sql @@ -0,0 +1,76 @@ +set @date := CURDATE(); +set @max_date := (select date_sub(@date, interval 1 week)); + +truncate weekly_results; + +insert into weekly_results + (year_week, week_dt, user_id, score, state, fine) +select * +from (with recursive + dates as (select '2025-06-09' as dt + union all + select date_add(dt, interval 1 week) as dt + from dates + where dt < @max_date), + user_date as (select dt, + id as user_id + from dates, + users), + problems_by_date as (select distinct dt, + ud.user_id, + sp.problem_id + from user_date ud + left outer join solved_problems sp + on try_dt between dt and date_add(dt, interval 6 day) and + sp.result_id = 4 + and sp.user_id = ud.user_id), + score_by_date as (select dt, + user_id, + IFNULL(sum(case + when level <= 0 then 10 + when level > 0 and level <= 5 then 20 + when level > 5 and level <= 10 then 30 + when level > 10 and level <= 15 then 50 + when level > 15 then 80 + else 0 + end), 0) as score + from problems_by_date + left outer join problems pb on pb.id = problem_id + group by dt, user_id) + + select date_format(dt, '%Y-%u') as year_week, + dt, + user_id, + score, + IF(score >= 60, 1, 2) as state, + IF(score < 60, 3000, 0) as fine + from score_by_date) t; + +insert into weekly_results + (year_week, week_dt, user_id, score, state, fine) +select date_format(@date, '%Y-%u'), + date_sub(@date, interval weekday(@date) day), + id, + 0, + 0, + 0 +from users; + +set @start_date := (select date_sub(@date, interval weekday(@date) day)); + +update users u +set streak=(with recursive base as (select @start_date as dt, + 0 as cnt, + 0 as state + union all + select date_sub(dt, interval 1 week) as dt, + IF(ws.state in (1, 3), cnt + 1, cnt) as cnt, + ws.state as state + from base + left join weekly_results ws + on ws.year_week = + date_format(date_sub(dt, interval 1 week), '%Y-%u') and + ws.user_id = u.id + where ws.state in (1, 3)) + select max(cnt) + from base) From cbbe2450f6d2a707875d28042e953b1a24a24d95 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Mon, 12 Jan 2026 21:28:37 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20=EB=A7=A4=EC=9D=BC=2000:01=20?= =?UTF-8?q?=EC=97=90=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EB=A0=88=EB=B2=A8=20?= =?UTF-8?q?=EA=B0=B1=EC=8B=A0=ED=95=98=EB=8A=94=20job=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/js/components/UserProgress.jsx | 4 +-- .../test/controller/TestController.java | 10 ++++++- .../user/controller/UserController.java | 4 +-- .../baektracker/domain/user/dto/UserInfo.java | 1 + .../baektracker/domain/user/model/User.java | 5 ++++ .../user/repository/UserRepository.java | 2 ++ .../domain/user/service/UserService.java | 26 ++++++++++++------- .../baektracker/global/config/JobConfig.java | 13 ++++++---- .../global/job/RecordUserLevelJob.java | 21 +++++++++++++++ .../job/RecordWeeklyResultJob.java | 4 +-- .../global/job/RecordUserLevelJobTest.java | 4 +++ 11 files changed, 71 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/baektracker/global/job/RecordUserLevelJob.java rename src/main/java/com/baektracker/{domain/weekly_result => global}/job/RecordWeeklyResultJob.java (93%) create mode 100644 src/test/java/com/baektracker/global/job/RecordUserLevelJobTest.java diff --git a/src/main/frontend/src/js/components/UserProgress.jsx b/src/main/frontend/src/js/components/UserProgress.jsx index e3135aea..941c1eb9 100644 --- a/src/main/frontend/src/js/components/UserProgress.jsx +++ b/src/main/frontend/src/js/components/UserProgress.jsx @@ -213,12 +213,12 @@ export function UserProgress({fromDate, toDate}) { return (
= 60 && styles.completed}`)}>
+ className={cm(styles.tierIcon, `${DesignUtils.getTierIconClass(user.level)}`)}> {/**/} {user.nickname} {isWeekPass ? diff --git a/src/main/java/com/baektracker/domain/test/controller/TestController.java b/src/main/java/com/baektracker/domain/test/controller/TestController.java index b5fd57aa..54ac1037 100644 --- a/src/main/java/com/baektracker/domain/test/controller/TestController.java +++ b/src/main/java/com/baektracker/domain/test/controller/TestController.java @@ -1,5 +1,6 @@ package com.baektracker.domain.test.controller; +import com.baektracker.domain.user.service.UserService; import com.baektracker.domain.weekly_result.service.WeeklyResultService; import java.time.LocalDate; import lombok.RequiredArgsConstructor; @@ -15,12 +16,19 @@ public class TestController { private final WeeklyResultService weeklyResultService; + private final UserService userService; - @GetMapping("/job") + @GetMapping("/job/weekly-result") public ResponseEntity testWeeklyResultJob(@RequestParam LocalDate date) { LocalDate fromDate = date.minusDays(6); System.out.printf("test: %s - %s", fromDate, date); weeklyResultService.updateWeeklyResults(fromDate, date); return ResponseEntity.noContent().build(); } + + @GetMapping("/job/user-level") + public ResponseEntity testUserLevelJob() { + userService.updateUserInfoFromSolvedAc(); + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/com/baektracker/domain/user/controller/UserController.java b/src/main/java/com/baektracker/domain/user/controller/UserController.java index 9f751148..7e786e73 100644 --- a/src/main/java/com/baektracker/domain/user/controller/UserController.java +++ b/src/main/java/com/baektracker/domain/user/controller/UserController.java @@ -1,6 +1,6 @@ package com.baektracker.domain.user.controller; -import com.baektracker.domain.user.dto.UserProfile; +import com.baektracker.domain.user.dto.UserInfo; import com.baektracker.domain.user.dto.request.UpdatePasswordRequestDto; import com.baektracker.domain.user.service.UserService; import java.util.List; @@ -22,7 +22,7 @@ public class UserController { private final UserService userService; @GetMapping("") - public ResponseEntity> getAllUsers() { + public ResponseEntity> getAllUsers() { return ResponseEntity.ok(userService.getUsers()); } diff --git a/src/main/java/com/baektracker/domain/user/dto/UserInfo.java b/src/main/java/com/baektracker/domain/user/dto/UserInfo.java index ab0ef6da..0c694048 100644 --- a/src/main/java/com/baektracker/domain/user/dto/UserInfo.java +++ b/src/main/java/com/baektracker/domain/user/dto/UserInfo.java @@ -5,6 +5,7 @@ public record UserInfo( String username, String nickname, String password, + Integer level, Integer lastRead, Integer streak, Long weekPassCount diff --git a/src/main/java/com/baektracker/domain/user/model/User.java b/src/main/java/com/baektracker/domain/user/model/User.java index cfc6c645..d0a48c84 100644 --- a/src/main/java/com/baektracker/domain/user/model/User.java +++ b/src/main/java/com/baektracker/domain/user/model/User.java @@ -11,6 +11,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import lombok.ToString; import org.hibernate.annotations.ColumnDefault; @@ -39,6 +40,10 @@ public class User { @Column(name = "streak", nullable = false) private Integer streak; + @Setter + @Column(name = "level", nullable = true) + private Integer level; + @ColumnDefault("-1") @Column(name = "last_read", nullable = false) private Integer lastRead; diff --git a/src/main/java/com/baektracker/domain/user/repository/UserRepository.java b/src/main/java/com/baektracker/domain/user/repository/UserRepository.java index 6f6e84e1..4981515f 100644 --- a/src/main/java/com/baektracker/domain/user/repository/UserRepository.java +++ b/src/main/java/com/baektracker/domain/user/repository/UserRepository.java @@ -16,6 +16,7 @@ public interface UserRepository extends JpaRepository { u.username, u.nickname, u.password, + u.level, u.lastRead, u.streak, count(wr.state) @@ -23,6 +24,7 @@ public interface UserRepository extends JpaRepository { from User u left join WeeklyResult wr on u.id=wr.user.id and wr.state=3 group by u.id + order by u.level desc """) List getUserInfo(); } diff --git a/src/main/java/com/baektracker/domain/user/service/UserService.java b/src/main/java/com/baektracker/domain/user/service/UserService.java index e9446573..df31a221 100644 --- a/src/main/java/com/baektracker/domain/user/service/UserService.java +++ b/src/main/java/com/baektracker/domain/user/service/UserService.java @@ -2,14 +2,12 @@ import com.baektracker.domain.problem.service.SolvedAcService; import com.baektracker.domain.user.dto.UserInfo; -import com.baektracker.domain.user.dto.UserProfile; import com.baektracker.domain.user.dto.request.UpdatePasswordRequestDto; import com.baektracker.domain.user.model.User; import com.baektracker.domain.user.repository.UserRepository; import com.baektracker.domain.weekly_result.dto.SolvedAcUser; import com.baektracker.global.code.ApiResponseCode; import com.baektracker.global.exception.CustomException; -import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; @@ -38,15 +36,23 @@ public void updatePassword(UpdatePasswordRequestDto dto) { user.updatePassword(passwordEncoder.encode(dto.newPwd())); } - public List getUsers() { - List users = userRepository.getUserInfo(); - List userProfiles = new ArrayList<>(); - for (UserInfo user : users) { - SolvedAcUser solvedAcUser = solvedAcService.searchUser(user.username()); - userProfiles.add(UserProfile.of(user, solvedAcUser)); + public List getUsers() { + return userRepository.getUserInfo(); + } + + @Transactional + public void updateUserInfoFromSolvedAc() { + List users = userRepository.findAll(); + for (User user : users) { + SolvedAcUser solvedAcUser = solvedAcService.searchUser(user.getUsername()); + + try { + user.setLevel(solvedAcUser.items().get(0).tier()); + } catch (NullPointerException e) { + e.printStackTrace(); + continue; + } } - userProfiles.sort((u1, u2) -> u2.tier() - u1.tier()); - return userProfiles; } } diff --git a/src/main/java/com/baektracker/global/config/JobConfig.java b/src/main/java/com/baektracker/global/config/JobConfig.java index a015a82b..4f093e9d 100644 --- a/src/main/java/com/baektracker/global/config/JobConfig.java +++ b/src/main/java/com/baektracker/global/config/JobConfig.java @@ -1,6 +1,7 @@ package com.baektracker.global.config; -import com.baektracker.domain.weekly_result.job.RecordWeeklyResultJob; +import com.baektracker.global.job.RecordUserLevelJob; +import com.baektracker.global.job.RecordWeeklyResultJob; import jakarta.annotation.PostConstruct; import java.util.HashMap; import java.util.Map; @@ -22,10 +23,12 @@ public class JobConfig { @PostConstruct // '의존성 주입이 완료 된 후 실행되는 메소드'를 지정하는 어노테이션 public void run() { - JobDetail codingStudyWeeklyJob = getJobDetail(RecordWeeklyResultJob.class, new HashMap()); + JobDetail recordWeeklyResultJob = getJobDetail(RecordWeeklyResultJob.class, new HashMap()); + JobDetail userLevelJob = getJobDetail(RecordUserLevelJob.class, new HashMap()); try { - scheduler.scheduleJob(codingStudyWeeklyJob, getCronTrigger("0 59 23 ? * SUN")); // 매주 일요일 23:59 + scheduler.scheduleJob(recordWeeklyResultJob, getCronTrigger("0 59 23 ? * SUN")); // 매주 일요일 23:59 + scheduler.scheduleJob(userLevelJob, getCronTrigger("0 1 0 ? * *")); // 매일 23:59 } catch (SchedulerException e) { throw new RuntimeException(e); } @@ -41,13 +44,13 @@ public void run() { // L : 일에서 사용하면 마지막 일, 요일에서는 마지막 요일(토요일) // W : 가장 가까운 평일 (예) 15W는 15일에서 가장 가까운 평일 (월 ~ 금)을 찾음 // # : 몇째주의 무슨 요일을 표현 (예) 3#2 : 2번째주 수요일 - public Trigger getCronTrigger(String scheduleExp) { + private Trigger getCronTrigger(String scheduleExp) { return TriggerBuilder.newTrigger() .withSchedule(CronScheduleBuilder.cronSchedule(scheduleExp)) .startNow().build(); } - public JobDetail getJobDetail(Class job, Map params) { + private JobDetail getJobDetail(Class job, Map params) { JobDataMap jobDataMap = new JobDataMap(); jobDataMap.putAll(params); return JobBuilder.newJob(job).usingJobData(jobDataMap).build(); diff --git a/src/main/java/com/baektracker/global/job/RecordUserLevelJob.java b/src/main/java/com/baektracker/global/job/RecordUserLevelJob.java new file mode 100644 index 00000000..2be75f30 --- /dev/null +++ b/src/main/java/com/baektracker/global/job/RecordUserLevelJob.java @@ -0,0 +1,21 @@ +package com.baektracker.global.job; + +import com.baektracker.domain.user.service.UserService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@Component +public class RecordUserLevelJob implements Job { + private final UserService userService; + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + userService.updateUserInfoFromSolvedAc(); + } +} diff --git a/src/main/java/com/baektracker/domain/weekly_result/job/RecordWeeklyResultJob.java b/src/main/java/com/baektracker/global/job/RecordWeeklyResultJob.java similarity index 93% rename from src/main/java/com/baektracker/domain/weekly_result/job/RecordWeeklyResultJob.java rename to src/main/java/com/baektracker/global/job/RecordWeeklyResultJob.java index bd727eaf..e7c841fd 100644 --- a/src/main/java/com/baektracker/domain/weekly_result/job/RecordWeeklyResultJob.java +++ b/src/main/java/com/baektracker/global/job/RecordWeeklyResultJob.java @@ -1,4 +1,4 @@ -package com.baektracker.domain.weekly_result.job; +package com.baektracker.global.job; import com.baektracker.domain.baekjoon.service.BaekjoonService; import com.baektracker.domain.weekly_result.service.WeeklyResultService; @@ -21,8 +21,6 @@ public class RecordWeeklyResultJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { - log.info("[Job] Weekly Job is processing!"); - LocalDate thisWeekDate = LocalDate.now(); LocalDate fromDate = thisWeekDate.minusDays(6); if (thisWeekDate.getDayOfWeek() != DayOfWeek.SUNDAY) { diff --git a/src/test/java/com/baektracker/global/job/RecordUserLevelJobTest.java b/src/test/java/com/baektracker/global/job/RecordUserLevelJobTest.java new file mode 100644 index 00000000..535afc94 --- /dev/null +++ b/src/test/java/com/baektracker/global/job/RecordUserLevelJobTest.java @@ -0,0 +1,4 @@ +package com.baektracker.global.job; + +public class RecordUserLevelJobTest { +} From 4693429f06b03fba62cd013b3a750be567814346 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Mon, 12 Jan 2026 21:49:39 +0900 Subject: [PATCH 3/9] =?UTF-8?q?fix:=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../baektracker/domain/user/dto/UserInfo.java | 3 +-- .../domain/user/dto/UserProfile.java | 27 ------------------- .../user/repository/UserRepository.java | 4 +-- 3 files changed, 2 insertions(+), 32 deletions(-) delete mode 100644 src/main/java/com/baektracker/domain/user/dto/UserProfile.java diff --git a/src/main/java/com/baektracker/domain/user/dto/UserInfo.java b/src/main/java/com/baektracker/domain/user/dto/UserInfo.java index 0c694048..fb6df36b 100644 --- a/src/main/java/com/baektracker/domain/user/dto/UserInfo.java +++ b/src/main/java/com/baektracker/domain/user/dto/UserInfo.java @@ -7,7 +7,6 @@ public record UserInfo( String password, Integer level, Integer lastRead, - Integer streak, - Long weekPassCount + Integer streak ) { } diff --git a/src/main/java/com/baektracker/domain/user/dto/UserProfile.java b/src/main/java/com/baektracker/domain/user/dto/UserProfile.java deleted file mode 100644 index 83d4d245..00000000 --- a/src/main/java/com/baektracker/domain/user/dto/UserProfile.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.baektracker.domain.user.dto; - -import com.baektracker.domain.weekly_result.dto.SolvedAcUser; - -public record UserProfile( - Long id, - String username, - String nickname, - Integer streak, - Integer solvedCount, - Long weekPassCount, - Integer tier -) { - - public static UserProfile of(UserInfo user, SolvedAcUser solvedAcUser) { - SolvedAcUser.InnerUserItem item = solvedAcUser.items().get(0); - return new UserProfile( - user.id(), - user.username(), - user.nickname(), - user.streak(), - item.solvedCount(), - user.weekPassCount(), - item.tier() - ); - } -} diff --git a/src/main/java/com/baektracker/domain/user/repository/UserRepository.java b/src/main/java/com/baektracker/domain/user/repository/UserRepository.java index 4981515f..4ab3cd0e 100644 --- a/src/main/java/com/baektracker/domain/user/repository/UserRepository.java +++ b/src/main/java/com/baektracker/domain/user/repository/UserRepository.java @@ -18,11 +18,9 @@ public interface UserRepository extends JpaRepository { u.password, u.level, u.lastRead, - u.streak, - count(wr.state) + u.streak ) from User u - left join WeeklyResult wr on u.id=wr.user.id and wr.state=3 group by u.id order by u.level desc """) From 484903302541c39a9cbedaa72b00b201e20718f0 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Mon, 12 Jan 2026 21:55:01 +0900 Subject: [PATCH 4/9] =?UTF-8?q?fix:=20=EC=97=B0=EB=8F=84=20=EB=B0=94?= =?UTF-8?q?=EB=80=8C=EB=8A=94=20=EB=82=A0=EC=A7=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EC=8B=9C=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/frontend/src/js/components/Header.jsx | 2 +- src/main/frontend/src/js/setup/utils/DateUtils.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/frontend/src/js/components/Header.jsx b/src/main/frontend/src/js/components/Header.jsx index d1fc22d9..1211a11c 100644 --- a/src/main/frontend/src/js/components/Header.jsx +++ b/src/main/frontend/src/js/components/Header.jsx @@ -110,4 +110,4 @@ const FineInfo = ({totalFine, onShowReceipt, className}) => {
) -} \ No newline at end of file +} diff --git a/src/main/frontend/src/js/setup/utils/DateUtils.js b/src/main/frontend/src/js/setup/utils/DateUtils.js index b9d035ef..a66c28f8 100644 --- a/src/main/frontend/src/js/setup/utils/DateUtils.js +++ b/src/main/frontend/src/js/setup/utils/DateUtils.js @@ -154,7 +154,7 @@ export const DateUtils = { if (orgMonth === 12 && (days) >= (totalDays - orgDate)) { date.setFullYear(date.getFullYear() + 1); - date.setMonth(1); + date.setMonth(0); date.setDate((days) - (totalDays - orgDate)); } else { date.setDate(orgDate + (days)); @@ -209,4 +209,4 @@ export const DateUtils = { newDate.setDate(newDate.getDate() + addOffset); return newDate; } -} \ No newline at end of file +} From eab9d35f020cef0ddb7b5c80b71e8d391d8c24d3 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Sat, 24 Jan 2026 22:29:35 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20log=20=EC=83=89=EC=83=81=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/logback-spring.xml | 56 ++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 4e8e5bdf..649b62b1 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -13,7 +13,61 @@ value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){green} %highlight(%-5level) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx"/> - + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + + + + + + + ${LOG_DIR}/app.log + + ${LOG_DIR}/app.%d{yyyy-MM-dd}.log + 30 + + + ERROR + ACCEPT + DENY + + + ${CONSOLE_PATTERN} + + + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} From 2501d4481769bfeb081aabb3924702b62af78aa8 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Sat, 24 Jan 2026 23:38:09 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20=EC=83=88=EB=A1=9C=EA=B3=A0?= =?UTF-8?q?=EC=B9=A8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20=EB=B0=8F?= =?UTF-8?q?=20=EB=A1=9C=EB=94=A9=20=EC=A7=80=EC=97=B0=20=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=20=EA=B0=90=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/frontend/src/css/styles.module.css | 31 +++++ .../frontend/src/images/relaod_blue_icon.png | Bin 0 -> 13308 bytes .../src/js/components/UserProgress.jsx | 21 +++- .../baekjoon/service/BaekjoonService.java | 9 +- .../problem/controller/ProblemController.java | 4 +- .../service/WeeklyResultService.java | 32 +++--- .../mapper/WeeklyResultMapper.java | 4 +- src/main/resources/application-prod.yml | 2 +- src/main/resources/logback-spring.xml | 59 +--------- src/main/resources/logback-spring2.xml | 108 ++++++++++++++++++ .../resources/mapper/WeeklyResultMapper.xml | 39 ++++--- 11 files changed, 203 insertions(+), 106 deletions(-) create mode 100644 src/main/frontend/src/images/relaod_blue_icon.png create mode 100644 src/main/resources/logback-spring2.xml diff --git a/src/main/frontend/src/css/styles.module.css b/src/main/frontend/src/css/styles.module.css index e147a190..75de1598 100644 --- a/src/main/frontend/src/css/styles.module.css +++ b/src/main/frontend/src/css/styles.module.css @@ -65,6 +65,37 @@ min-height: 600px; } +.progressSection .titleGroup { + display: flex; +} + +.progressSection .titleGroup .reloadIcon { + margin-left: 14px; + width: 26px; + height: 26px; + background: url(../images/relaod_blue_icon.png) no-repeat center / 100%; + opacity: 0.3; + cursor: pointer; +} + +.progressSection .titleGroup .reloadIcon.loading { + opacity: 1; + animation: loadingAnim 1s linear infinite; +} + +@keyframes loadingAnim { + 0% { + rotate: 0deg; + } + 100% { + rotate: 360deg; + } +} + +.progressSection .titleGroup .reloadIcon:hover { + opacity: 1; +} + .progressSection .sectionTitle { font-size: 18px; font-weight: 600; diff --git a/src/main/frontend/src/images/relaod_blue_icon.png b/src/main/frontend/src/images/relaod_blue_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2e8755d3ac790b09d3e9d9db599fc364d5e3d6f5 GIT binary patch literal 13308 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

$W^z=jcuuW%0a5(wVwV4N%s?Md;48KX2#6# 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 { getAllUsers() + initLoad(); }, []); useEffect(() => { @@ -32,21 +33,22 @@ export function UserProgress({fromDate, toDate}) { }, [fromDate, toDate]); const getAllUsers = () => { - setIsLoadingBaekjoon(true) + // setIsLoadingBaekjoon(true) userApi.getUsers().then(({data}) => { if (data) { setUsers(data); } - initLoad(); + getWeeklyUsersProgress() }) } const initLoad = () => { - + setIsLoadingBaekjoon(true) problemApi.loadBaekjoon().then(({data}) => { - setIsLoadingBaekjoon(false) if (data) { getWeeklyUsersProgress() + } else { + setIsLoadingBaekjoon(false) } }).catch(() => { setIsLoadingBaekjoon(false) @@ -62,6 +64,7 @@ export function UserProgress({fromDate, toDate}) { } setProblems(ob); } + setIsLoadingBaekjoon(false) }) } const getProgressPercentage = (current, target) => { @@ -197,9 +200,17 @@ export function UserProgress({fromDate, toDate}) { ) } + const reload = () => { + initLoad() + } + return (

-

개별 진행 현황

+
+

개별 진행 현황

+ +
{isLoadingBaekjoon ? ( users && users.map((user, i) => renderSkeletonCard(i)) diff --git a/src/main/java/com/baektracker/domain/baekjoon/service/BaekjoonService.java b/src/main/java/com/baektracker/domain/baekjoon/service/BaekjoonService.java index 406ac2f2..8120597d 100644 --- a/src/main/java/com/baektracker/domain/baekjoon/service/BaekjoonService.java +++ b/src/main/java/com/baektracker/domain/baekjoon/service/BaekjoonService.java @@ -35,7 +35,8 @@ public class BaekjoonService { private static final String Baekjoon_Problem_Status_Page_URL = "https://www.acmicpc.net/status"; @Transactional - public void loadBaekjoonProblemStatus() { + public int loadBaekjoonProblemStatus() { + int count = 0; List users = userRepository.findAll(); for (User user : users) { List scrappedProblems = new ArrayList<>(); @@ -56,13 +57,15 @@ public void loadBaekjoonProblemStatus() { if (!scrappedProblems.isEmpty()) { int updatedLastRead = scrappedProblems.get(0).getSubmitId(); - System.out.printf("qwe (%s) scrapped : %d, last-read: %d\n", user.getUsername(), - scrappedProblems.size(), updatedLastRead); +// System.out.printf("qwe (%s) scrapped : %d, last-read: %d\n", user.getUsername(), +// scrappedProblems.size(), updatedLastRead); user.updateLastRead(updatedLastRead); user.updateLastReadTime(LocalDateTime.now()); solvedProblemRepository.saveAll(scrappedProblems); + count = scrappedProblems.size(); } } + return count; } private List scrapProblemPage(User user, int top, int lastReadIndex) diff --git a/src/main/java/com/baektracker/domain/problem/controller/ProblemController.java b/src/main/java/com/baektracker/domain/problem/controller/ProblemController.java index f92eb600..3b71b293 100644 --- a/src/main/java/com/baektracker/domain/problem/controller/ProblemController.java +++ b/src/main/java/com/baektracker/domain/problem/controller/ProblemController.java @@ -7,7 +7,6 @@ import java.time.LocalDate; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -29,8 +28,7 @@ public ResponseEntity getProblems(@RequestParam Loc @GetMapping("/reload") public ResponseEntity loadBaekjoonProblems() { - baekjoonService.loadBaekjoonProblemStatus(); - return ResponseEntity.status(HttpStatus.OK).build(); + return ResponseEntity.ok(baekjoonService.loadBaekjoonProblemStatus() > 0); } @GetMapping("/search") diff --git a/src/main/java/com/baektracker/domain/weekly_result/service/WeeklyResultService.java b/src/main/java/com/baektracker/domain/weekly_result/service/WeeklyResultService.java index aab9ce79..0b5f58fd 100644 --- a/src/main/java/com/baektracker/domain/weekly_result/service/WeeklyResultService.java +++ b/src/main/java/com/baektracker/domain/weekly_result/service/WeeklyResultService.java @@ -9,7 +9,6 @@ import com.baektracker.domain.user.repository.UserRepository; import com.baektracker.domain.weekly_result.code.WeeklyResultState; import com.baektracker.domain.weekly_result.dto.UserFine; -import com.baektracker.domain.weekly_result.dto.UserStreak; import com.baektracker.domain.weekly_result.dto.response.MonthFineStatusResponse; import com.baektracker.domain.weekly_result.dto.response.MonthFineStatusResponse.InnerUserMonthFineItem; import com.baektracker.domain.weekly_result.dto.response.TotalFineStatusResponse; @@ -149,22 +148,23 @@ public void updateWeeklyResults(LocalDate fromDate, LocalDate toDate) { } } - @Transactional + @Transactional(transactionManager = "mybatisTxManager") public void updateUserStreaks(LocalDate fromDate) { - List users = userRepository.findAll(); - List streaks = weeklyResultMapper.getStreaks(fromDate); - Map userStreakMap = streaks.stream() - .collect(Collectors.toMap( - UserStreak::id, - UserStreak::streak - )); - for (User user : users) { - if (!userStreakMap.containsKey(user.getId())) { - continue; - } - int streak = userStreakMap.get(user.getId()); - user.setStreak(streak); - } + weeklyResultMapper.updateStreaks(fromDate); +// List users = userRepository.findAll(); +// List streaks = weeklyResultMapper.updateStreaks(fromDate); +// Map userStreakMap = streaks.stream() +// .collect(Collectors.toMap( +// UserStreak::id, +// UserStreak::streak +// )); +// for (User user : users) { +// if (!userStreakMap.containsKey(user.getId())) { +// continue; +// } +// int streak = userStreakMap.get(user.getId()); +// user.setStreak(streak); +// } } private WeeklyResultState getWeeklyResultState(int score) { diff --git a/src/main/java/com/baektracker/mapper/WeeklyResultMapper.java b/src/main/java/com/baektracker/mapper/WeeklyResultMapper.java index e4db2ddf..1db49ed0 100644 --- a/src/main/java/com/baektracker/mapper/WeeklyResultMapper.java +++ b/src/main/java/com/baektracker/mapper/WeeklyResultMapper.java @@ -1,11 +1,9 @@ package com.baektracker.mapper; -import com.baektracker.domain.weekly_result.dto.UserStreak; import java.time.LocalDate; -import java.util.List; import org.apache.ibatis.annotations.Mapper; @Mapper public interface WeeklyResultMapper { - public List getStreaks(LocalDate date); + public void updateStreaks(LocalDate date); } diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 2ef8404b..6c23b715 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -3,7 +3,7 @@ server: ssl: enabled: true key-store-type: JKS - key-store: file:${SSL_KEY_PATH} + key-store: ${SSL_KEY_PATH} key-store-password: ${SSL_KEY_PASSWORD} spring: diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 649b62b1..91909bc9 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -2,7 +2,8 @@ - + - - - - - - - - - - - - - - ${CONSOLE_LOG_PATTERN} - - - - - - - ${LOG_DIR}/app.log - - ${LOG_DIR}/app.%d{yyyy-MM-dd}.log - 30 - - - ERROR - ACCEPT - DENY - - - ${CONSOLE_PATTERN} - - - - - - - - - - - - - - - - - - - + ${CONSOLE_LOG_PATTERN} diff --git a/src/main/resources/logback-spring2.xml b/src/main/resources/logback-spring2.xml new file mode 100644 index 00000000..ff7a1e4a --- /dev/null +++ b/src/main/resources/logback-spring2.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + + + + + + + ${LOG_DIR}/app.log + + ${LOG_DIR}/app.%d{yyyy-MM-dd}.log + 30 + + + ERROR + ACCEPT + DENY + + + ${CONSOLE_PATTERN} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + + + + + + + ${LOG_DIR}/app.log + + ${LOG_DIR}/app.%d{yyyy-MM-dd}.log + 30 + + + ERROR + ACCEPT + DENY + + + ${CONSOLE_PATTERN} + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/WeeklyResultMapper.xml b/src/main/resources/mapper/WeeklyResultMapper.xml index 6ac6a586..8d879ecb 100644 --- a/src/main/resources/mapper/WeeklyResultMapper.xml +++ b/src/main/resources/mapper/WeeklyResultMapper.xml @@ -3,25 +3,26 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - + update users u + set streak=(with recursive base as (select @start_date as dt, + 0 as cnt, + 0 as state + union all + select date_sub(dt, interval 1 week) as dt, + cnt + (ws.state = 1) as cnt, + ws.state as state + from base + left join weekly_results ws + on ws.year_week = + date_format(date_sub(dt, interval 1 week), '%Y-%u') and + ws.user_id = u.id + where ws.state in (1, 3)) + select max(cnt) + from base) + From 98502b9567eb917b48625a8c712a361c3918e84a Mon Sep 17 00:00:00 2001 From: chanrhan Date: Sat, 24 Jan 2026 23:49:05 +0900 Subject: [PATCH 7/9] =?UTF-8?q?fix:=20=EA=B0=99=EC=9D=B4=20=ED=91=BC=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=A0=95=EB=B3=B4=EA=B0=80=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=88=EC=A3=BC=20=EB=82=A0=EC=A7=9C=20=EB=82=B4=EB=A1=9C=20?= =?UTF-8?q?=ED=95=9C=EC=A0=95=EB=90=98=EB=8A=94=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../problem/repository/SolvedProblemRepository.java | 2 ++ .../domain/problem/service/BaekjoonProblemService.java | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/baektracker/domain/problem/repository/SolvedProblemRepository.java b/src/main/java/com/baektracker/domain/problem/repository/SolvedProblemRepository.java index d3a1b6a8..95f3df6e 100644 --- a/src/main/java/com/baektracker/domain/problem/repository/SolvedProblemRepository.java +++ b/src/main/java/com/baektracker/domain/problem/repository/SolvedProblemRepository.java @@ -7,4 +7,6 @@ public interface SolvedProblemRepository extends JpaRepository { List findSolvedProblemsByTryDtBetween(LocalDate tryDtAfter, LocalDate tryDtBefore); + + List findSolvedProblemByResultId(Integer resultId); } diff --git a/src/main/java/com/baektracker/domain/problem/service/BaekjoonProblemService.java b/src/main/java/com/baektracker/domain/problem/service/BaekjoonProblemService.java index 65110015..39d247c7 100644 --- a/src/main/java/com/baektracker/domain/problem/service/BaekjoonProblemService.java +++ b/src/main/java/com/baektracker/domain/problem/service/BaekjoonProblemService.java @@ -13,6 +13,7 @@ import com.baektracker.domain.problem.model.SolvedProblem; import com.baektracker.domain.problem.queryRepository.SolvedProblemQueryRepository; import com.baektracker.domain.problem.repository.ProblemRepository; +import com.baektracker.domain.problem.repository.SolvedProblemRepository; import com.baektracker.domain.user.model.User; import com.baektracker.domain.user.repository.UserRepository; import com.baektracker.domain.weekly_result.code.WeeklyResultState; @@ -41,6 +42,7 @@ public class BaekjoonProblemService { private final ProblemRepository problemRepository; private final ProblemMapper problemMapper; private final SolvedProblemQueryRepository solvedProblemQueryRepository; + private final SolvedProblemRepository solvedProblemRepository; private final UserRepository userRepository; private final WeeklyResultRepository weeklyResultRepository; @@ -63,7 +65,7 @@ public ProblemsResponse searchProblems(String keyword) { public Optional getProblemInfo(int problemId) { SolvedAcProblem problem = solvedAcService.getProblemInfo(problemId); - System.out.printf("qwe get problem (%d) : %s\n", problemId, problem.titleKo()); +// System.out.printf("qwe get problem (%d) : %s\n", problemId, problem.titleKo()); return Optional.of(ProblemInfo.from(problem)); } @@ -73,6 +75,8 @@ public WeeklyUsersProgressResponse getWeeklyUsersProgress(LocalDate fromDate) { LocalDate toDate = fromDate.plusDays(6); List users = userRepository.findAll(); List solvedProblems = solvedProblemQueryRepository.fetchUserProgresses(fromDate, toDate); + List coSolvedUsers = solvedProblemRepository.findSolvedProblemByResultId( + SolvedAcResultType.CORRECT.getStatus()); String yearWeek = DateUtil.toYearWeek(fromDate); List weeklyResults = weeklyResultRepository.findWeeklyResultByYearWeek(yearWeek); @@ -86,7 +90,7 @@ public WeeklyUsersProgressResponse getWeeklyUsersProgress(LocalDate fromDate) { this::isWeekPass )); - Map> coSolverMap = solvedProblems.stream() + Map> coSolverMap = coSolvedUsers.stream() .collect(Collectors.groupingBy( SolvedProblem::getProblemId, Collectors.mapping(SolvedProblem::getUserNickname, Collectors.toList()) From c647c3345e94af371b87fc2973491d80d1cce0ce Mon Sep 17 00:00:00 2001 From: chanrhan Date: Sun, 25 Jan 2026 09:18:55 +0900 Subject: [PATCH 8/9] =?UTF-8?q?fix:=20coSolvers=EA=B0=80=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=EC=9C=BC=EB=A1=9C=20=EB=82=98=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20(distinct=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/problem/repository/SolvedProblemRepository.java | 2 +- .../domain/problem/service/BaekjoonProblemService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/baektracker/domain/problem/repository/SolvedProblemRepository.java b/src/main/java/com/baektracker/domain/problem/repository/SolvedProblemRepository.java index 95f3df6e..362cbe6b 100644 --- a/src/main/java/com/baektracker/domain/problem/repository/SolvedProblemRepository.java +++ b/src/main/java/com/baektracker/domain/problem/repository/SolvedProblemRepository.java @@ -8,5 +8,5 @@ public interface SolvedProblemRepository extends JpaRepository { List findSolvedProblemsByTryDtBetween(LocalDate tryDtAfter, LocalDate tryDtBefore); - List findSolvedProblemByResultId(Integer resultId); + List findDistinctByResultId(Integer resultId); } diff --git a/src/main/java/com/baektracker/domain/problem/service/BaekjoonProblemService.java b/src/main/java/com/baektracker/domain/problem/service/BaekjoonProblemService.java index 39d247c7..676fe7f3 100644 --- a/src/main/java/com/baektracker/domain/problem/service/BaekjoonProblemService.java +++ b/src/main/java/com/baektracker/domain/problem/service/BaekjoonProblemService.java @@ -75,7 +75,7 @@ public WeeklyUsersProgressResponse getWeeklyUsersProgress(LocalDate fromDate) { LocalDate toDate = fromDate.plusDays(6); List users = userRepository.findAll(); List solvedProblems = solvedProblemQueryRepository.fetchUserProgresses(fromDate, toDate); - List coSolvedUsers = solvedProblemRepository.findSolvedProblemByResultId( + List coSolvedUsers = solvedProblemRepository.findDistinctByResultId( SolvedAcResultType.CORRECT.getStatus()); String yearWeek = DateUtil.toYearWeek(fromDate); List weeklyResults = weeklyResultRepository.findWeeklyResultByYearWeek(yearWeek); From fa3c16342bfe9d6f02a574e35204590f139f701c Mon Sep 17 00:00:00 2001 From: chanrhan Date: Sat, 31 Jan 2026 14:25:23 +0900 Subject: [PATCH 9/9] =?UTF-8?q?feat:=20rating=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EB=B0=8F=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/frontend/src/css/styles.module.css | 6 ++ .../src/js/components/UserProgress.jsx | 21 +++-- src/main/frontend/src/js/utils/DesignUtils.js | 92 +++++++++++-------- .../baekjoon/service/BaekjoonService.java | 2 +- .../test/controller/TestController.java | 2 +- .../baektracker/domain/user/dto/UserInfo.java | 1 + .../baektracker/domain/user/model/User.java | 4 + .../user/repository/UserRepository.java | 3 +- .../domain/user/service/UserService.java | 1 + .../migration/V7__add_column_rating_users.sql | 2 + .../global/job/RecordUserLevelJobTest.java | 49 ++++++++++ 11 files changed, 137 insertions(+), 46 deletions(-) create mode 100644 src/main/resources/db/migration/V7__add_column_rating_users.sql diff --git a/src/main/frontend/src/css/styles.module.css b/src/main/frontend/src/css/styles.module.css index 75de1598..e24a86de 100644 --- a/src/main/frontend/src/css/styles.module.css +++ b/src/main/frontend/src/css/styles.module.css @@ -478,6 +478,12 @@ padding: 1px 5px; } +.userProgressName .rating_text { + margin: 0 6px; + font-size: 14px; + cursor: pointer; +} + .userProgressName .pass_text { font-size: 16px; font-weight: 600; diff --git a/src/main/frontend/src/js/components/UserProgress.jsx b/src/main/frontend/src/js/components/UserProgress.jsx index f46f3071..4cfe8aa1 100644 --- a/src/main/frontend/src/js/components/UserProgress.jsx +++ b/src/main/frontend/src/js/components/UserProgress.jsx @@ -7,6 +7,7 @@ import {useApi} from "../api/useApi"; import useModal from "../setup/hook/useModal"; import {ModalType} from "../setup/modal/ModalType"; import {DateUtils} from "../setup/utils/DateUtils"; +import {useTooltipHandlers} from "../setup/utils/TooltipUtils"; export function UserProgress({fromDate, toDate}) { const modal = useModal() @@ -32,6 +33,12 @@ export function UserProgress({fromDate, toDate}) { getWeeklyUsersProgress(); }, [fromDate, toDate]); + const tooltip = useTooltipHandlers(
+ 사용자의 레이팅입니다. +
) + const getAllUsers = () => { // setIsLoadingBaekjoon(true) userApi.getUsers().then(({data}) => { @@ -232,13 +239,13 @@ export function UserProgress({fromDate, toDate}) { className={cm(styles.tierIcon, `${DesignUtils.getTierIconClass(user.level)}`)}> {/**/} - {user.nickname} {isWeekPass ? - 이번주 패스 : ''} - {/*{user.weekPassCount > 0 && (*/} - {/* */} - {/* 🛡️{user.weekPassCount}*/} - {/* */} - {/*)}*/} + {user.nickname} + {user.rating} + {isWeekPass ? + PASS : ''}