diff --git a/commitlint.config.js b/commitlint.config.js index 5bdf4d19..a664aefd 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -20,6 +20,7 @@ module.exports = { "report", "moderation", "search", + "stats", "friend", "notify", "infra", @@ -116,6 +117,9 @@ module.exports = { search: { description: 'πŸ” 검색 도메인 (예: 질문/λ‹΅λ³€ 검색, 졜근 검색어)' }, + stats: { + description: 'πŸ“Š 톡계 도메인 (예: 일간 톡계 νŽ˜μ΄μ§€)' + }, friend: { description: 'πŸ‘₯ 친ꡬ 도메인 (예: 친ꡬ μ‹ μ²­, 수락, λͺ©λ‘ 관리)' }, diff --git a/src/main/java/com/devkor/ifive/nadab/domain/stats/application/TotalStatsService.java b/src/main/java/com/devkor/ifive/nadab/domain/stats/application/TotalStatsService.java index 4f52c7a1..da821983 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/stats/application/TotalStatsService.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/stats/application/TotalStatsService.java @@ -35,7 +35,7 @@ public TotalStatsViewModel getTotalStats() { providerMap.put("NORMAL", normal); // ν‘œμ‹œ μˆœμ„œ κ³ μ • - List providerLabels = List.of("GOOGLE", "KAKAO", "NAVER", "NORMAL"); + List providerLabels = List.of("APPLE", "GOOGLE", "KAKAO", "NAVER", "NORMAL"); List providerCounts = providerLabels.stream() .map(l -> providerMap.getOrDefault(l, 0L)) .toList(); diff --git a/src/main/java/com/devkor/ifive/nadab/domain/stats/application/TypeStatsService.java b/src/main/java/com/devkor/ifive/nadab/domain/stats/application/TypeStatsService.java new file mode 100644 index 00000000..f88ee9d7 --- /dev/null +++ b/src/main/java/com/devkor/ifive/nadab/domain/stats/application/TypeStatsService.java @@ -0,0 +1,42 @@ +package com.devkor.ifive.nadab.domain.stats.application; + +import com.devkor.ifive.nadab.domain.stats.core.dto.total.LabelCountDto; +import com.devkor.ifive.nadab.domain.stats.core.dto.type.TypeStatsViewModel; +import com.devkor.ifive.nadab.domain.stats.core.repository.TypeStatsRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class TypeStatsService { + + private final TypeStatsRepository repo; + + private static final ZoneId SEOUL = ZoneId.of("Asia/Seoul"); + private static final DateTimeFormatter FMT = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + public TypeStatsViewModel getTypeStats() { + long inProgressTypeReportCount = repo.countInProgressTypeReportsNow(); + List completedByInterest = repo.countCompletedTypeReportsByInterest(); + + List interestLabels = completedByInterest.stream() + .map(LabelCountDto::label) + .toList(); + List completedTypeReportCounts = completedByInterest.stream() + .map(LabelCountDto::count) + .toList(); + + return new TypeStatsViewModel( + inProgressTypeReportCount, + interestLabels, + completedTypeReportCounts, + OffsetDateTime.now(SEOUL).format(FMT) + ); + } +} diff --git a/src/main/java/com/devkor/ifive/nadab/domain/stats/controller/StatsController.java b/src/main/java/com/devkor/ifive/nadab/domain/stats/controller/StatsController.java index da7d6e9d..b0650050 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/stats/controller/StatsController.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/stats/controller/StatsController.java @@ -3,10 +3,12 @@ import com.devkor.ifive.nadab.domain.stats.application.DailyStatsService; import com.devkor.ifive.nadab.domain.stats.application.MonthlyStatsService; import com.devkor.ifive.nadab.domain.stats.application.TotalStatsService; +import com.devkor.ifive.nadab.domain.stats.application.TypeStatsService; import com.devkor.ifive.nadab.domain.stats.application.WeeklyStatsService; import com.devkor.ifive.nadab.domain.stats.core.dto.daily.DailyStatsViewModel; import com.devkor.ifive.nadab.domain.stats.core.dto.monthly.MonthlyStatsViewModel; import com.devkor.ifive.nadab.domain.stats.core.dto.total.TotalStatsViewModel; +import com.devkor.ifive.nadab.domain.stats.core.dto.type.TypeStatsViewModel; import com.devkor.ifive.nadab.domain.stats.core.dto.weekly.WeeklyStatsViewModel; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; @@ -21,6 +23,7 @@ public class StatsController { private final WeeklyStatsService weeklyStatsService; private final MonthlyStatsService monthlyStatsService; private final TotalStatsService totalStatsService; + private final TypeStatsService typeStatsService; @GetMapping("stats/daily") @@ -54,4 +57,12 @@ public String totalStats(Model model) { model.addAttribute("activeTab", "total"); return "stats/total"; } + + @GetMapping("/stats/type") + public String typeStats(Model model) { + TypeStatsViewModel vm = typeStatsService.getTypeStats(); + model.addAttribute("vm", vm); + model.addAttribute("activeTab", "type"); + return "stats/type"; + } } diff --git a/src/main/java/com/devkor/ifive/nadab/domain/stats/core/dto/total/TotalStatsViewModel.java b/src/main/java/com/devkor/ifive/nadab/domain/stats/core/dto/total/TotalStatsViewModel.java index e3cc362e..23e5187e 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/stats/core/dto/total/TotalStatsViewModel.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/stats/core/dto/total/TotalStatsViewModel.java @@ -6,7 +6,7 @@ public record TotalStatsViewModel( long totalUserCount, - // provider labels: ["GOOGLE","KAKAO","NAVER","NORMAL"] + // provider labels: ["APPLE","GOOGLE","KAKAO","NAVER","NORMAL"] List providerLabels, List providerCounts, diff --git a/src/main/java/com/devkor/ifive/nadab/domain/stats/core/dto/type/TypeStatsViewModel.java b/src/main/java/com/devkor/ifive/nadab/domain/stats/core/dto/type/TypeStatsViewModel.java new file mode 100644 index 00000000..c2e6042f --- /dev/null +++ b/src/main/java/com/devkor/ifive/nadab/domain/stats/core/dto/type/TypeStatsViewModel.java @@ -0,0 +1,10 @@ +package com.devkor.ifive.nadab.domain.stats.core.dto.type; + +import java.util.List; + +public record TypeStatsViewModel( + long inProgressTypeReportCount, + List interestLabels, + List completedTypeReportCounts, + String refreshedAt +) {} diff --git a/src/main/java/com/devkor/ifive/nadab/domain/stats/core/repository/TypeStatsRepository.java b/src/main/java/com/devkor/ifive/nadab/domain/stats/core/repository/TypeStatsRepository.java new file mode 100644 index 00000000..22895953 --- /dev/null +++ b/src/main/java/com/devkor/ifive/nadab/domain/stats/core/repository/TypeStatsRepository.java @@ -0,0 +1,41 @@ +package com.devkor.ifive.nadab.domain.stats.core.repository; + +import com.devkor.ifive.nadab.domain.stats.core.dto.total.LabelCountDto; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class TypeStatsRepository { + + private final EntityManager em; + + public long countInProgressTypeReportsNow() { + return em.createQuery(""" + select count(tr.id) + from TypeReport tr + where tr.status = com.devkor.ifive.nadab.domain.typereport.core.entity.TypeReportStatus.IN_PROGRESS + and tr.deletedAt is null + """, Long.class) + .getSingleResult(); + } + + public List countCompletedTypeReportsByInterest() { + return em.createQuery(""" + select new com.devkor.ifive.nadab.domain.stats.core.dto.total.LabelCountDto( + cast(i.code as string), + count(tr.id) + ) + from Interest i + left join TypeReport tr on tr.interestCode = i.code + and tr.status = com.devkor.ifive.nadab.domain.typereport.core.entity.TypeReportStatus.COMPLETED + and tr.deletedAt is null + group by i.code + order by i.code + """, LabelCountDto.class) + .getResultList(); + } +} diff --git a/src/main/resources/templates/stats/daily.html b/src/main/resources/templates/stats/daily.html index 96079224..ea79772a 100644 --- a/src/main/resources/templates/stats/daily.html +++ b/src/main/resources/templates/stats/daily.html @@ -277,6 +277,9 @@ μ›”κ°„ + μœ ν˜• 전체 diff --git a/src/main/resources/templates/stats/monthly.html b/src/main/resources/templates/stats/monthly.html index a59fb5d0..09ab5cd9 100644 --- a/src/main/resources/templates/stats/monthly.html +++ b/src/main/resources/templates/stats/monthly.html @@ -254,6 +254,9 @@ μ›”κ°„ + μœ ν˜• 전체 diff --git a/src/main/resources/templates/stats/total.html b/src/main/resources/templates/stats/total.html index b49ec5a2..f99411eb 100644 --- a/src/main/resources/templates/stats/total.html +++ b/src/main/resources/templates/stats/total.html @@ -286,6 +286,9 @@ μ›”κ°„ + μœ ν˜• 전체 @@ -359,6 +362,17 @@ 'rgba(77,195,255,0.85)', ]; + const providerPaletteByLabel = { + APPLE: 'rgba(180,120,255,0.85)', + GOOGLE: 'rgba(108,143,255,0.85)', + KAKAO: 'rgba(255,126,179,0.85)', + NAVER: 'rgba(77,232,194,0.85)', + NORMAL: 'rgba(255,196,77,0.85)', + }; + const providerPalette = providerLabels.map( + (label, index) => providerPaletteByLabel[label] ?? PALETTE[index % PALETTE.length] + ); + const tooltipDefaults = { backgroundColor: '#1e2230', borderColor: 'rgba(255,255,255,0.1)', @@ -378,7 +392,7 @@ datasets: [{ label: 'μœ μ € 수', data: providerCounts, - backgroundColor: PALETTE, + backgroundColor: providerPalette, borderRadius: 6, borderSkipped: false, }] diff --git a/src/main/resources/templates/stats/type.html b/src/main/resources/templates/stats/type.html new file mode 100644 index 00000000..8396ed5f --- /dev/null +++ b/src/main/resources/templates/stats/type.html @@ -0,0 +1,350 @@ + + + + + + NADAB - Type Stats + + + + + + + + + + + +
+
+
ν˜„μž¬ 생성 쀑 μœ ν˜• 리포트
+
0
+
+
+ +
+
+
관심 μ£Όμ œλ³„ μƒμ„±λœ 총 μœ ν˜• 리포트 수
+
전체 λˆ„μ  (COMPLETED κΈ°μ€€)
+
+
+ +
+
+ + + + + diff --git a/src/main/resources/templates/stats/weekly.html b/src/main/resources/templates/stats/weekly.html index b3ecc92d..e57225fa 100644 --- a/src/main/resources/templates/stats/weekly.html +++ b/src/main/resources/templates/stats/weekly.html @@ -254,6 +254,9 @@ μ›”κ°„ + μœ ν˜• 전체