대시보드, 통계 정리

This commit is contained in:
2025-06-09 15:35:05 +09:00
parent 141a0de6d0
commit 05312c7d93
4 changed files with 321 additions and 182 deletions
+58 -41
View File
@@ -626,45 +626,39 @@ exports.getMemberStats = async (req, res) => {
}
};
// 다음 모임 정보 조회
exports.getNextMeeting = async (req, res) => {
// 다음 모임 정보 여러 개 조회
exports.getNextMeetings = async (req, res) => {
try {
const { clubId } = req.body;
if (!clubId) {
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
}
const nextMeeting = await Event.findOne({
// 오늘 이후의 예정된 모임 중 status가 active인 것 3개까지 오름차순 정렬
const nextMeetings = await Event.findAll({
where: {
clubId,
startDate: { [Op.gt]: new Date() },
status: 'active'
status: { [Op.or]: ['ready', 'active'] }
},
include: [{
model: EventParticipant,
where: {
status: { [Op.ne]: '취소' }
status: { [Op.ne]: 'canceled' }
},
required: false
}],
order: [['startDate', 'ASC']]
order: [['startDate', 'ASC']],
limit: 3
});
if (!nextMeeting) {
return res.json(null);
}
const meetingData = {
date: nextMeeting.startDate,
participantsCount: nextMeeting.participants ? nextMeeting.participants.length : 0,
title: nextMeeting.title,
location: nextMeeting.location
};
res.json(meetingData);
const meetings = nextMeetings.map(meeting => ({
date: meeting.startDate,
participantsCount: meeting.EventParticipants ? meeting.EventParticipants.length : 0,
title: meeting.title,
location: meeting.location
}));
res.json(meetings);
} catch (error) {
console.error('다음 모임 정보 조회 오류:', error);
console.error('다음 모임 정보(여러 개) 조회 오류:', error);
res.status(500).json({ message: '다음 모임 정보를 가져오는 중 오류가 발생했습니다.' });
}
};
@@ -687,7 +681,7 @@ exports.getLastMeeting = async (req, res) => {
include: [{
model: EventParticipant,
where: {
status: { [Op.ne]: '취소' }
status: { [Op.ne]: 'canceled' }
},
required: false
}],
@@ -700,7 +694,7 @@ exports.getLastMeeting = async (req, res) => {
const meetingData = {
date: lastMeeting.startDate,
participantsCount: lastMeeting.participants ? lastMeeting.participants.length : 0,
participantsCount: lastMeeting.EventParticipants ? lastMeeting.EventParticipants.length : 0,
title: lastMeeting.title,
games: lastMeeting.gameCount || 0
};
@@ -743,10 +737,10 @@ exports.getScoreStats = async (req, res) => {
let totalGames = 0;
events.forEach(event => {
event.participants.forEach(participant => {
if (participant.scores && participant.scores.length > 0) {
allScores = allScores.concat(participant.scores.map(score => score.score));
totalGames += participant.scores.length;
(event.EventParticipants || []).forEach(participant => {
if (participant.EventScores && participant.EventScores.length > 0) {
allScores = allScores.concat(participant.EventScores.map(score => score.score));
totalGames += participant.EventScores.length;
}
});
});
@@ -779,31 +773,55 @@ exports.getTopMembers = async (req, res) => {
return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' });
}
// 활동적인 회원들 가져오기 (게임 수로 정렬)
const activeMembers = await Member.findAll({
// 활동 회원들 전체 조회 (games 컬럼 제거)
const members = await Member.findAll({
where: {
clubId,
status: 'active',
games: { [Op.gt]: 0 } // 최소 1게임 이상 플레이한 회원
status: 'active'
},
attributes: ['id', 'name', 'games', 'handicap', 'memberType'],
order: [['games', 'DESC']],
limit: 20 // 에버리지 계산을 위해 여유있게 가져오기
attributes: ['id', 'name', 'handicap', 'memberType'],
limit: 100 // 충분히 넉넉하게 조회
});
// 각 회원별 실제 게임 수 집계 (EventScore → EventParticipant 조인)
const memberIds = members.map(m => m.id);
// 1. 회원별 EventParticipant 조회
const participants = await EventParticipant.findAll({
where: { memberId: memberIds },
attributes: ['id', 'memberId']
});
const participantIdToMemberId = {};
participants.forEach(p => { participantIdToMemberId[p.id] = p.memberId; });
// 2. EventScore에서 참가자별 점수 조회
const scores = await EventScore.findAll({
where: { participantId: Object.keys(participantIdToMemberId) },
attributes: ['participantId']
});
// 3. memberId별로 게임수 카운트
const gamesMap = {};
scores.forEach(s => {
const memberId = participantIdToMemberId[s.participantId];
gamesMap[memberId] = (gamesMap[memberId] || 0) + 1;
});
// 1게임 이상 플레이한 회원만 필터링
const filteredMembers = members.filter(m => (gamesMap[m.id] || 0) > 0);
// 각 회원의 에버리지 계산
const membersWithAverage = await Promise.all(
activeMembers.map(async (member) => {
filteredMembers.map(async (member) => {
try {
const averageData = await averageService.calculateMemberAverageByClubSetting(member.id, clubId);
return {
...member.toJSON(),
games: gamesMap[member.id] || 0,
average: averageData.average
};
} catch (error) {
console.error(`회원 ${member.id}의 에버리지 계산 오류:`, error);
return {
...member.toJSON(),
games: gamesMap[member.id] || 0,
average: 0
};
}
@@ -834,24 +852,23 @@ exports.getRecentMeetings = async (req, res) => {
const recentMeetings = await Event.findAll({
where: {
clubId,
status: 'active',
status: { [Op.notIn]: ['canceled', 'deleted'] },
startDate: { [Op.lt]: new Date() }
},
include: [{
model: EventParticipant,
where: {
status: { [Op.ne]: '취소' }
status: { [Op.ne]: 'canceled' }
},
required: false
}],
order: [['startDate', 'DESC']],
limit: 5
});
const formattedMeetings = recentMeetings.map(meeting => ({
date: meeting.startDate,
title: meeting.title,
participants: meeting.participants ? meeting.participants.length : 0,
participantsCount: meeting.EventParticipants ? meeting.EventParticipants.length : 0,
games: meeting.gameCount || 0
}));
+1 -1
View File
@@ -39,7 +39,7 @@ router.post('/members/update', authenticateJWT, clubController.updateClubMember)
router.post('/members/delete', authenticateJWT, clubController.deleteClubMember);
// 다음 모임 정보 조회
router.post('/meetings/next', authenticateJWT, clubController.getNextMeeting);
router.post('/meetings/next', authenticateJWT, clubController.getNextMeetings);
// 최근 모임 정보 조회
router.post('/meetings/last', authenticateJWT, clubController.getLastMeeting);