대시보드, 통계 정리
This commit is contained in:
@@ -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
|
||||
}));
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user