랭킹표시 수정
This commit is contained in:
@@ -130,13 +130,40 @@ const props = defineProps({
|
||||
visible: Boolean, // Dialog의 v-model:visible과 연결
|
||||
eventStatus: String
|
||||
});
|
||||
const emits = defineEmits(['close', 'updated', 'update:visible']);
|
||||
const emits = defineEmits(['close', 'updated', 'update:visible', 'guest-added']);
|
||||
|
||||
const selectedMember = ref(null);
|
||||
const memberOptions = computed(() => {
|
||||
// 참가자 목록에서 참가자 그룹 생성
|
||||
const participantOptions = (props.participants || []).map(m => ({
|
||||
...m,
|
||||
group: '참가자',
|
||||
id: m.participantId,
|
||||
participantId: m.participantId,
|
||||
memberId: m.memberId
|
||||
}));
|
||||
|
||||
// 미신청 그룹 생성 (참가자에 없는 회원만)
|
||||
const unregisteredOptions = (props.availableMembers || [])
|
||||
.filter(m => {
|
||||
// 이미 참가자로 등록된 회원은 제외
|
||||
return !participantOptions.some(p =>
|
||||
p.memberId === m.memberId ||
|
||||
(p.Member && m.name && p.Member.name === m.name)
|
||||
);
|
||||
})
|
||||
.map(m => ({
|
||||
...m,
|
||||
group: '미신청',
|
||||
id: m.memberId,
|
||||
participantId: null,
|
||||
memberId: m.memberId
|
||||
}));
|
||||
|
||||
// 두 그룹 합치기
|
||||
const baseOptions = [
|
||||
...(props.participants || []).map(m => ({ ...m, group: '참가자', id: m.participantId, participantId: m.participantId, memberId: m.memberId })),
|
||||
...(props.availableMembers || []).map(m => ({ ...m, group: '미신청', id: m.memberId, participantId: null, memberId: m.memberId }))
|
||||
...participantOptions,
|
||||
...unregisteredOptions
|
||||
].sort((a, b) => {
|
||||
const nameA = a.name || '';
|
||||
const nameB = b.name || '';
|
||||
@@ -185,8 +212,8 @@ import PublicEventService from '@/services/PublicEventService';
|
||||
|
||||
const newGuest = ref({ name: '', phone: '', gender: '' });
|
||||
const genderOptions = [
|
||||
{ label: '남', value: '남성' },
|
||||
{ label: '여', value: '여성' }
|
||||
{ label: '남성', value: '남성' },
|
||||
{ label: '여성', value: '여성' }
|
||||
];
|
||||
|
||||
async function addNewGuest() {
|
||||
@@ -204,24 +231,66 @@ async function addNewGuest() {
|
||||
average: newGuest.value.average
|
||||
});
|
||||
if (!res || !res.data || !res.data.memberId) throw new Error('게스트 등록 실패');
|
||||
|
||||
// 새 게스트를 멤버 옵션에 추가하고 자동 선택
|
||||
const guest = {
|
||||
...res.data,
|
||||
group: '참가자',
|
||||
id: res.data.participantId,
|
||||
participantId: res.data.participantId,
|
||||
memberId: res.data.memberId
|
||||
memberId: res.data.memberId,
|
||||
status: res.data.status || '참가예정', // 참가 확정 상태 추가
|
||||
paymentStatus: res.data.paymentStatus || '미납', // 결제 상태 추가
|
||||
Member: {
|
||||
name: newGuest.value.name,
|
||||
gender: newGuest.value.gender,
|
||||
average: newGuest.value.average || 0,
|
||||
handicap: res.data.handicap || 0
|
||||
}
|
||||
};
|
||||
// options에 직접 push (reactivity 보장 위해 availableMembers에도 추가)
|
||||
props.availableMembers.push(guest);
|
||||
// 참가자 목록에도 추가
|
||||
props.participants.push(guest);
|
||||
|
||||
// 이미 있는 게스트 업데이트 (선택 가능한 회원 목록)
|
||||
const existingGuestIndex = props.availableMembers.findIndex(m =>
|
||||
m.name === guest.Member.name && m.group === '게스트');
|
||||
|
||||
// 이미 참가자 목록에 있는 게스트 업데이트
|
||||
const existingParticipantIndex = props.participants.findIndex(p =>
|
||||
p.memberId === guest.memberId ||
|
||||
(p.Member && p.Member.name === guest.Member.name && p.status === '미신청'));
|
||||
|
||||
// 기존 데이터 업데이트 또는 새 데이터 추가
|
||||
if (existingGuestIndex !== -1) {
|
||||
// 기존 게스트 정보 업데이트
|
||||
Object.assign(props.availableMembers[existingGuestIndex], guest);
|
||||
} else {
|
||||
// 새 게스트 추가
|
||||
props.availableMembers.push(guest);
|
||||
}
|
||||
|
||||
if (existingParticipantIndex !== -1) {
|
||||
// 기존 참가자 정보 업데이트
|
||||
Object.assign(props.participants[existingParticipantIndex], guest);
|
||||
} else {
|
||||
// 새 참가자 추가
|
||||
props.participants.push(guest);
|
||||
}
|
||||
|
||||
// 선택값 세팅
|
||||
selectedMember.value = guest;
|
||||
selectedParticipantId.value = guest.participantId;
|
||||
selectedMemberId.value = guest.memberId;
|
||||
|
||||
// 참가 상태와 회비 납부 상태 자동 선택
|
||||
console.log(guest);
|
||||
status.value = guest.status;
|
||||
paymentStatus.value = guest.paymentStatus;
|
||||
comment.value = guest.comment || '';
|
||||
|
||||
// 입력폼 초기화
|
||||
newGuest.value = { name: '', phone: '', gender: '', average: '' };
|
||||
|
||||
// 부모 컴포넌트에 이벤트 발생
|
||||
emits('guest-added', guest);
|
||||
} catch (e) {
|
||||
error.value = e.message || '게스트 등록 중 오류 발생';
|
||||
}
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
<div v-for="member in members" :key="member.participantId" class="member-row">
|
||||
<div class="member-info-block">
|
||||
<div class="member-header-row">
|
||||
<span v-if="member && (member.participantId || member.id) && (memberRanksRef[member.participantId] || memberRanksRef[member.id])"
|
||||
class="medal-icon-container">
|
||||
<i class="pi pi-trophy medal-icon" :class="'medal-' + (memberRanksRef[member.participantId] || memberRanksRef[member.id])"></i>
|
||||
<span class="medal-rank" :class="'medal-rank-' + (memberRanksRef[member.participantId] || memberRanksRef[member.id])">{{ memberRanksRef[member.participantId] || memberRanksRef[member.id] }}</span>
|
||||
</span>
|
||||
<span class="member-name">{{ member.name }}</span>
|
||||
<Tag v-if="member.isGuest" value="G" severity="warning" class="ml-2" />
|
||||
</div>
|
||||
@@ -31,6 +36,14 @@
|
||||
<template v-for="n in gameCount" :key="n">
|
||||
<div class="score-input-hc-wrap">
|
||||
<div class="score-input-status-wrap">
|
||||
<!-- 상태 아이콘을 입력 필드 앞으로 이동 -->
|
||||
<span class="score-status-icon-container">
|
||||
<i v-if="scoreStates[member.participantId + '-' + n] === 'saving'" class="pi pi-spin pi-spinner score-status-icon" />
|
||||
<i v-else-if="scoreStates[member.participantId + '-' + n] === 'saved'" class="pi pi-check score-status-icon" />
|
||||
<i v-else-if="scoreStates[member.participantId + '-' + n] === 'error'" class="pi pi-times score-status-icon" />
|
||||
</span>
|
||||
|
||||
<!-- 점수 입력 필드 -->
|
||||
<InputText
|
||||
v-if="canInputScore"
|
||||
v-model.number="scoresProxy[member.participantId + '-' + n]"
|
||||
@@ -43,29 +56,29 @@
|
||||
@keydown.enter="onScoreEnter(member, n, teamNumber)"
|
||||
/>
|
||||
<span v-else>{{ member.scores?.[n - 1] ?? '-' }}</span>
|
||||
|
||||
<!-- 핸디캡 포함 점수 표시 -->
|
||||
<span :class="scoreColorClass(scoreStates[member.participantId + '-' + n], scoresProxy[member.participantId + '-' + n], member.handicap)"
|
||||
class="score-hc-value score-status-text"
|
||||
:key="scoreStates[member.participantId + '-' + n] + '-' + scoresProxy[member.participantId + '-' + n]">
|
||||
{{ (scoresProxy[member.participantId + '-' + n] !== undefined && scoresProxy[member.participantId + '-' + n] !== '') ?
|
||||
Number(scoresProxy[member.participantId + '-' + n]) + (Number(member.handicap) || 0) : ''
|
||||
}}
|
||||
<i v-if="scoreStates[member.participantId + '-' + n] === 'saving'" class="pi pi-spin pi-spinner score-status-icon" />
|
||||
<i v-else-if="scoreStates[member.participantId + '-' + n] === 'saved'" class="pi pi-check score-status-icon" />
|
||||
<i v-else-if="scoreStates[member.participantId + '-' + n] === 'error'" class="pi pi-times score-status-icon" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="member-score-summary">
|
||||
<span>총점: {{ getMemberTotalScore(member) }}</span>
|
||||
<span class="member-score-avg">/ 에버리지: {{ getMemberAverageScore(member) }}</span>
|
||||
<span class="member-score-avg">/ 평균: {{ getMemberAverageScore(member) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { toRefs } from 'vue'
|
||||
import { computed, onMounted, watch, ref } from 'vue';
|
||||
import { calculateIndividualRankings, getRankColorClass } from '@/utils/rankingUtils';
|
||||
const props = defineProps({
|
||||
title: String,
|
||||
members: Array,
|
||||
@@ -80,6 +93,127 @@ const props = defineProps({
|
||||
onScoreEnter: Function,
|
||||
getMemberTotalScore: Function,
|
||||
getMemberAverageScore: Function,
|
||||
scoreColorClass: Function
|
||||
scoreColorClass: Function,
|
||||
memberRanks: Object
|
||||
})
|
||||
|
||||
// 디버깅을 위한 코드
|
||||
// 순위 데이터를 저장할 로컬 변수
|
||||
const localRanks = ref({});
|
||||
|
||||
// 점수 변경 감지를 위한 참조 변수
|
||||
const lastScores = ref({});
|
||||
|
||||
// 순위 계산 함수 - 순위 계산 유틸리티 사용
|
||||
const updateLocalRanks = () => {
|
||||
if (!props.members || props.members.length === 0) return;
|
||||
|
||||
// 순위 계산 유틸리티 함수 호출
|
||||
const newRanks = calculateIndividualRankings(
|
||||
props.members,
|
||||
props.scoresProxy || {},
|
||||
props.gameCount || 3
|
||||
);
|
||||
|
||||
// 순위 맵 갱신
|
||||
localRanks.value = newRanks;
|
||||
|
||||
// 현재 점수 저장
|
||||
lastScores.value = {};
|
||||
props.members.forEach(member => {
|
||||
if (member && member.participantId) {
|
||||
lastScores.value[member.participantId] = props.getMemberTotalScore ? props.getMemberTotalScore(member) : 0;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 프록스와 로컬 데이터를 통합한 계산된 순위 객체
|
||||
const memberRanksRef = computed(() => {
|
||||
// 무조건 props.memberRanks 사용 - 최종 순위 테이블과 동일한 순위 표시를 위해
|
||||
return props.memberRanks || {};
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (props.members && props.members.length > 0) {
|
||||
// props가 undefined인 경우 기본값 설정
|
||||
if (!props.memberRanks) {
|
||||
updateLocalRanks();
|
||||
}
|
||||
|
||||
// 초기 점수 저장
|
||||
props.members.forEach(member => {
|
||||
if (member && member.participantId) {
|
||||
lastScores.value[member.participantId] = props.getMemberTotalScore ? props.getMemberTotalScore(member) : 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
// props.memberRanks 변경 감지
|
||||
watch(() => props.memberRanks, (newVal) => {
|
||||
}, { deep: true })
|
||||
|
||||
// 점수 변경 감지
|
||||
watch(() => props.scoresProxy, () => {
|
||||
// 점수가 변경되었는지 확인
|
||||
let scoresChanged = false;
|
||||
|
||||
props.members.forEach(member => {
|
||||
if (member && member.participantId) {
|
||||
const currentScore = props.getMemberTotalScore ? props.getMemberTotalScore(member) : 0;
|
||||
const previousScore = lastScores.value[member.participantId] || 0;
|
||||
|
||||
if (currentScore !== previousScore) {
|
||||
scoresChanged = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 점수가 변경되었으면 순위 재계산
|
||||
if (scoresChanged) {
|
||||
updateLocalRanks();
|
||||
}
|
||||
}, { deep: true })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.medal-icon-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.medal-icon {
|
||||
font-size: 1.8rem;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
/* 메달 색상 */
|
||||
.medal-1 {
|
||||
color: gold;
|
||||
}
|
||||
|
||||
.medal-2 {
|
||||
color: silver;
|
||||
}
|
||||
|
||||
.medal-3 {
|
||||
color: #cd7f32; /* 동메달 색상 */
|
||||
}
|
||||
|
||||
.medal-rank {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -80%);
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
/* 글자 테두리 효과 */
|
||||
text-shadow:
|
||||
-1px -1px 0 rgba(255, 255, 255, 0.7),
|
||||
1px -1px 0 rgba(255, 255, 255, 0.7),
|
||||
-1px 1px 0 rgba(255, 255, 255, 0.7),
|
||||
1px 1px 0 rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user