diff --git a/backend/controllers/clubController.js b/backend/controllers/clubController.js index 31e5171..a15b56a 100644 --- a/backend/controllers/clubController.js +++ b/backend/controllers/clubController.js @@ -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 })); diff --git a/backend/routes/clubRoutes.js b/backend/routes/clubRoutes.js index 3a5ac6e..923975c 100644 --- a/backend/routes/clubRoutes.js +++ b/backend/routes/clubRoutes.js @@ -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); diff --git a/frontend/src/views/club/Dashboard.vue b/frontend/src/views/club/Dashboard.vue index b4ab6b7..a0eebe5 100644 --- a/frontend/src/views/club/Dashboard.vue +++ b/frontend/src/views/club/Dashboard.vue @@ -8,7 +8,7 @@
일시: {{ formatDate(nextMeeting.date) }}
-장소: {{ nextMeeting.location || '미정' }}
-참가 예정: {{ nextMeeting.participantsCount }}명
-예정된 모임이 없습니다.
-일시: {{ formatDate(lastMeeting.date) }}
-참가자: {{ lastMeeting.participantsCount }}명
-게임 수: {{ lastMeeting.games }}게임
-최근 모임 기록이 없습니다.
-| 일시 | +제목 | +장소 | +참가자 | +
|---|---|---|---|
| {{ formatDate(meeting.date) }} | +{{ meeting.title || '-' }} | +{{ meeting.location || '미정' }} | +{{ meeting.participantsCount || meeting.participants || 0 }}명 | +
예정된 모임이 없습니다.
+| 일시 | +제목 | +참가자 | +게임 수 | +
|---|---|---|---|
| {{ formatDate(meeting.date) }} | +{{ meeting.title }} | +{{ meeting.participantsCount || meeting.participants || 0 }}명 | +{{ meeting.games }}게임 | +
| 최근 모임 데이터가 없습니다. | +|||
| 이름 | 최고점 | 최저점 | 총점 |
|---|---|---|---|
| {{ m.name }} | -{{ m.maxScore }} | -{{ m.minScore }} | -{{ m.totalScore }} | -
| 모임명 | 최고점 | 최저점 | 평균점 |
|---|---|---|---|
| {{ e.title }} | -{{ e.maxScore }} | -{{ e.minScore }} | -{{ e.avgScore }} | -
| 이름 | 참가 횟수 |
|---|---|
| {{ m.name }} | -{{ m.attendCount }} | -
| 이벤트ID | 팀ID | 평균점수 | 최고점 | 참가자수 |
|---|---|---|---|---|
| {{ t.eventId }} | -{{ t.teamId }} | -{{ t.avgScore }} | -{{ t.maxScore }} | -{{ t.memberCount }} | -
| 이름 | 최고점 | 최저점 | 총점 |
|---|---|---|---|
| {{ m.name }} | +{{ m.maxScore }} | +{{ m.minScore }} | +{{ m.totalScore }} | +
| 모임명 | 최고점 | 최저점 | 평균점 |
|---|---|---|---|
| {{ e.title }} | +{{ e.maxScore }} | +{{ e.minScore }} | +{{ e.avgScore }} | +
| 이름 | 참가 횟수 |
|---|---|
| {{ m.name }} | +{{ m.attendCount }} | +
| 이벤트ID | 팀ID | 평균점수 | 최고점 | 참가자수 |
|---|---|---|---|---|
| {{ t.eventId }} | +{{ t.teamId }} | +{{ t.avgScore }} | +{{ t.maxScore }} | +{{ t.memberCount }} | +