팀배정 성별, 에버표시

This commit is contained in:
2025-06-19 19:03:04 +09:00
parent 32267893d5
commit 4ce6b4dd68
3 changed files with 95 additions and 12 deletions
+1 -1
View File
@@ -1369,7 +1369,7 @@ exports.getEventParticipants = async (req, res) => {
include: [ include: [
{ {
model: Member, model: Member,
attributes: ['id', 'name', 'email', 'phone', 'memberType'] attributes: ['id', 'name', 'email', 'phone', 'memberType', 'gender']
} }
], ],
order: [ order: [
@@ -94,6 +94,7 @@
<script setup> <script setup>
import { getEventTypeLabel, getEventStatusLabel } from '@/utils/enumMappings'; import { getEventTypeLabel, getEventStatusLabel } from '@/utils/enumMappings';
import { ref, computed, watch } from 'vue'; import { ref, computed, watch } from 'vue';
import eventService from '../../services/eventService';
import TeamGeneration from './TeamGeneration.vue'; import TeamGeneration from './TeamGeneration.vue';
import Dialog from 'primevue/dialog'; import Dialog from 'primevue/dialog';
import ParticipantManagement from './ParticipantManagement.vue'; import ParticipantManagement from './ParticipantManagement.vue';
@@ -157,10 +158,34 @@ watch(() => eventModel.value.id, async (eventId) => {
// 참가확정 참가자 목록을 계산하는 computed 속성 // 참가확정 참가자 목록을 계산하는 computed 속성
const confirmedParticipants = ref([]); const confirmedParticipants = ref([]);
function updateConfirmedParticipants() { async function updateConfirmedParticipants() {
try {
// 이벤트 참가자 필터링
const arr = eventModel.value.EventParticipants?.filter(p => p.status === 'confirmed' || p.status === 'pending') || []; const arr = eventModel.value.EventParticipants?.filter(p => p.status === 'confirmed' || p.status === 'pending') || [];
// 참가자 데이터에 에버리지 정보 추가
if (arr.length > 0 && eventModel.value.id) {
// 최신 참가자 데이터 가져오기
const latestParticipants = await eventService.getEventParticipants(eventModel.value.id);
// 참가자 데이터 매핑 (에버리지 정보 포함)
confirmedParticipants.value = arr.map(p => {
// 최신 참가자 데이터에서 해당 참가자 찾기
const latestData = latestParticipants.find(lp => lp.id === p.id);
if (latestData) {
// 최신 데이터로 업데이트 (에버리지 정보 포함)
return latestData;
}
return p;
});
} else {
confirmedParticipants.value = arr; confirmedParticipants.value = arr;
} }
} catch (error) {
console.error('참가자 데이터 업데이트 중 오류:', error);
confirmedParticipants.value = eventModel.value.EventParticipants?.filter(p => p.status === 'confirmed' || p.status === 'pending') || [];
}
}
// 최초 및 eventModel 변경 시 동기화 // 최초 및 eventModel 변경 시 동기화
watch(() => eventModel.value.EventParticipants, updateConfirmedParticipants, { immediate: true, deep: true }); watch(() => eventModel.value.EventParticipants, updateConfirmedParticipants, { immediate: true, deep: true });
@@ -44,6 +44,8 @@
<span class="badge bg-primary text-white mr-1">{{ mIdx + 1 }}</span> <span class="badge bg-primary text-white mr-1">{{ mIdx + 1 }}</span>
<span>{{ member.name }}</span> <span>{{ member.name }}</span>
<span v-if="getMemberAverage(member) !== null" class="text-xs text-gray-700 ml-1">({{ getMemberAverage(member) }})</span> <span v-if="getMemberAverage(member) !== null" class="text-xs text-gray-700 ml-1">({{ getMemberAverage(member) }})</span>
<span v-if="getMemberGender(member) === 'male'" class="gender-icon male ml-1" title="남성"></span>
<span v-else-if="getMemberGender(member) === 'female'" class="gender-icon female ml-1" title="여성"></span>
<Button icon="pi pi-times" class="p-button-text p-button-danger p-0" @click="removeMemberFromTeam(idx, mIdx)" v-if="team.members.length > 0" /> <Button icon="pi pi-times" class="p-button-text p-button-danger p-0" @click="removeMemberFromTeam(idx, mIdx)" v-if="team.members.length > 0" />
</li> </li>
</template> </template>
@@ -62,6 +64,8 @@
<span v-if="getMemberAverage(slotProps.option) !== null" class="text-xs text-gray-700 ml-1"> <span v-if="getMemberAverage(slotProps.option) !== null" class="text-xs text-gray-700 ml-1">
({{ getMemberAverage(slotProps.option) }}) ({{ getMemberAverage(slotProps.option) }})
</span> </span>
<span v-if="getMemberGender(slotProps.option) === 'male'" class="gender-icon male ml-1" title="남성"></span>
<span v-else-if="getMemberGender(slotProps.option) === 'female'" class="gender-icon female ml-1" title="여성"></span>
</template> </template>
<template #value="slotProps"> <template #value="slotProps">
<span v-if="slotProps.value"> <span v-if="slotProps.value">
@@ -69,6 +73,8 @@
<span v-if="getMemberAverage(slotProps.value) !== null" class="text-xs text-gray-700 ml-1"> <span v-if="getMemberAverage(slotProps.value) !== null" class="text-xs text-gray-700 ml-1">
({{ getMemberAverage(slotProps.value) }}) ({{ getMemberAverage(slotProps.value) }})
</span> </span>
<span v-if="getMemberGender(slotProps.value) === 'male'" class="gender-icon male ml-1" title="남성"></span>
<span v-else-if="getMemberGender(slotProps.value) === 'female'" class="gender-icon female ml-1" title="여성"></span>
</span> </span>
</template> </template>
</Select> </Select>
@@ -102,19 +108,62 @@ import { ref, computed, watch, unref } from 'vue';
// 참가자 에버리지 구하기 // 참가자 에버리지 구하기
function getMemberAverage(member) { function getMemberAverage(member) {
if (!member.id) return null; if (!member || !member.id) return null;
// 객체 자체에서 에버리지 값 추출 시도
if (member.average !== undefined && member.average !== null) {
return Number(member.average);
}
// 원본 참가자 객체에서 id로 찾아서 평균값 추출 // 원본 참가자 객체에서 id로 찾아서 평균값 추출
const arr = unref(props.participants); const arr = unref(props.participants);
const p = arr.find(x => x.id === member.id); const p = arr.find(x => x.id === member.id || x.participantId === member.id || x.memberId === member.id);
if (!p) return null; if (!p) return null;
if (p.Member && typeof p.Member.average === 'number') return p.Member.average;
if (typeof p.average === 'number') return p.average; // 다양한 구조에서 값 추출 시도
if (typeof p.averageScore === 'number') return p.averageScore; if (p.Member && p.Member.average !== undefined && p.Member.average !== null) {
return Number(p.Member.average);
}
if (p.average !== undefined && p.average !== null) {
return Number(p.average);
}
if (p.averageScore !== undefined && p.averageScore !== null) {
return Number(p.averageScore);
}
return null; return null;
} }
// 팀 에버리지(평균)
// 참가자 성별 정보 가져오기
function getMemberGender(member) {
if (!member) return null;
// 객체 자체에서 성별 정보 추출 시도
if (member.gender) {
return member.gender;
}
// 원본 참가자 객체에서 찾기
const arr = unref(props.participants);
const p = arr.find(x => x.id === member.id || x.participantId === member.id || x.memberId === member.id);
if (!p) return null;
// Member 객체에서 성별 정보 확인
if (p.Member && p.Member.gender) {
return p.Member.gender;
}
return null;
}
// 팀 에버리지(평균) - 에버리지가 0인 사람은 제외
function getTeamAverage(team) { function getTeamAverage(team) {
const avgs = team.members.map(getMemberAverage).filter(v => typeof v === 'number'); // 에버리지가 0보다 큰 값만 필터링
const avgs = team.members
.map(getMemberAverage)
.filter(v => typeof v === 'number' && v > 0);
if (!avgs.length) return 0; if (!avgs.length) return 0;
return avgs.reduce((a, b) => a + b, 0) / avgs.length; return avgs.reduce((a, b) => a + b, 0) / avgs.length;
} }
@@ -345,5 +394,14 @@ watch(() => props.participants, () => {
color: #fff; color: #fff;
margin-right: 0.25em; margin-right: 0.25em;
} }
.gender-icon {
font-size: 0.9em;
font-weight: bold;
}
.gender-icon.male {
color: #2196F3;
}
.gender-icon.female {
color: #E91E63;
}
</style> </style>