220 lines
7.5 KiB
Vue
220 lines
7.5 KiB
Vue
<template>
|
|
<div class="team-title-row">
|
|
<span class="team-name-strong">{{ title }}</span>
|
|
<span v-if="teamHandicap" class="team-hc">팀 핸디캡: {{ teamHandicap }}</span>
|
|
</div>
|
|
<div class="member-list">
|
|
<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>
|
|
<div class="info-under-name">
|
|
{{ member.gender || '-' }}
|
|
<span v-if="member.status" :class="{
|
|
'status-cancel': member.status === '취소',
|
|
'status-pending': member.status === '참가예정'
|
|
}">
|
|
· {{ member.status }}
|
|
</span>
|
|
<span v-if="member.paymentStatus" :class="{
|
|
'payment-unpaid': member.paymentStatus === '미납'
|
|
}">
|
|
· {{ member.paymentStatus }}
|
|
</span>
|
|
</div>
|
|
<div class="member-meta">핸디캡: {{ member.handicap ?? '-' }} / 에버리지: {{ member.average ?? '-' }}</div>
|
|
<div class="member-meta">{{ member.comment }}</div>
|
|
</div>
|
|
<div class="score-inputs-vertical">
|
|
<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]"
|
|
type="number"
|
|
class="score-input"
|
|
:placeholder="n + 'G'"
|
|
maxlength="3"
|
|
@input="onScoreInput(member, n, teamNumber)"
|
|
@blur="onScoreBlur(member, n, teamNumber)"
|
|
@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) : ''
|
|
}}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<div class="member-score-summary">
|
|
<span>총점: {{ getMemberTotalScore(member) }}</span>
|
|
<span class="member-score-avg">/ 평균: {{ getMemberAverageScore(member) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script setup>
|
|
import { computed, onMounted, watch, ref } from 'vue';
|
|
import { calculateIndividualRankings, getRankColorClass } from '@/utils/rankingUtils';
|
|
const props = defineProps({
|
|
title: String,
|
|
members: Array,
|
|
teamHandicap: [Number, String],
|
|
teamNumber: [Number, String, null],
|
|
gameCount: Number,
|
|
canInputScore: Boolean,
|
|
scoresProxy: Object,
|
|
scoreStates: Object,
|
|
onScoreInput: Function,
|
|
onScoreBlur: Function,
|
|
onScoreEnter: Function,
|
|
getMemberTotalScore: Function,
|
|
getMemberAverageScore: 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>
|