Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ module.exports = {
"report",
"moderation",
"search",
"stats",
"friend",
"notify",
"infra",
Expand Down Expand Up @@ -116,6 +117,9 @@ module.exports = {
search: {
description: '🔍 검색 도메인 (예: 질문/답변 검색, 최근 검색어)'
},
stats: {
description: '📊 통계 도메인 (예: 일간 통계 페이지)'
},
friend: {
description: '👥 친구 도메인 (예: 친구 신청, 수락, 목록 관리)'
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public TotalStatsViewModel getTotalStats() {
providerMap.put("NORMAL", normal);

// 표시 순서 고정
List<String> providerLabels = List.of("GOOGLE", "KAKAO", "NAVER", "NORMAL");
List<String> providerLabels = List.of("APPLE", "GOOGLE", "KAKAO", "NAVER", "NORMAL");
List<Long> providerCounts = providerLabels.stream()
.map(l -> providerMap.getOrDefault(l, 0L))
.toList();
Expand Down
Original file line number Diff line number Diff line change
@@ -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<LabelCountDto> completedByInterest = repo.countCompletedTypeReportsByInterest();

List<String> interestLabels = completedByInterest.stream()
.map(LabelCountDto::label)
.toList();
List<Long> completedTypeReportCounts = completedByInterest.stream()
.map(LabelCountDto::count)
.toList();

return new TypeStatsViewModel(
inProgressTypeReportCount,
interestLabels,
completedTypeReportCounts,
OffsetDateTime.now(SEOUL).format(FMT)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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")
Expand Down Expand Up @@ -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";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public record TotalStatsViewModel(

long totalUserCount,

// provider labels: ["GOOGLE","KAKAO","NAVER","NORMAL"]
// provider labels: ["APPLE","GOOGLE","KAKAO","NAVER","NORMAL"]
List<String> providerLabels,
List<Long> providerCounts,

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.devkor.ifive.nadab.domain.stats.core.dto.type;

import java.util.List;

public record TypeStatsViewModel(
long inProgressTypeReportCount,
List<String> interestLabels,
List<Long> completedTypeReportCounts,
String refreshedAt
) {}
Original file line number Diff line number Diff line change
@@ -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<LabelCountDto> 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();
}
}
3 changes: 3 additions & 0 deletions src/main/resources/templates/stats/daily.html
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,9 @@
<a class="tab-link"
th:classappend="${activeTab} == 'monthly' ? ' active' : ''"
th:href="@{/stats/monthly}">월간</a>
<a class="tab-link"
th:classappend="${activeTab} == 'type' ? ' active' : ''"
th:href="@{/stats/type}">유형</a>
<a class="tab-link"
th:classappend="${activeTab} == 'total' ? ' active' : ''"
th:href="@{/stats/total}">전체</a>
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/templates/stats/monthly.html
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@
<a class="tab-link"
th:classappend="${activeTab} == 'monthly' ? ' active' : ''"
th:href="@{/stats/monthly}">월간</a>
<a class="tab-link"
th:classappend="${activeTab} == 'type' ? ' active' : ''"
th:href="@{/stats/type}">유형</a>
<a class="tab-link"
th:classappend="${activeTab} == 'total' ? ' active' : ''"
th:href="@{/stats/total}">전체</a>
Expand Down
16 changes: 15 additions & 1 deletion src/main/resources/templates/stats/total.html
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,9 @@
<a class="tab-link"
th:classappend="${activeTab} == 'monthly' ? ' active' : ''"
th:href="@{/stats/monthly}">월간</a>
<a class="tab-link"
th:classappend="${activeTab} == 'type' ? ' active' : ''"
th:href="@{/stats/type}">유형</a>
<a class="tab-link"
th:classappend="${activeTab} == 'total' ? ' active' : ''"
th:href="@{/stats/total}">전체</a>
Expand Down Expand Up @@ -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)',
Expand All @@ -378,7 +392,7 @@
datasets: [{
label: '유저 수',
data: providerCounts,
backgroundColor: PALETTE,
backgroundColor: providerPalette,
borderRadius: 6,
borderSkipped: false,
}]
Expand Down
Loading
Loading