랭킹표시 수정
This commit is contained in:
@@ -30,7 +30,6 @@
|
||||
<Button type="submit" label="클럽 생성하기" class="w-full" :loading="loading" />
|
||||
</form>
|
||||
</div>
|
||||
<Toast />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
@remove-participant="removeParticipant"
|
||||
/>
|
||||
|
||||
<Toast />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -103,7 +103,6 @@
|
||||
<Button label="예" icon="pi pi-check" class="p-button-danger" @click="deleteEvent" />
|
||||
</template>
|
||||
</Dialog>
|
||||
<Toast />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -25,7 +25,17 @@
|
||||
sortField="name"
|
||||
:sortOrder="1"
|
||||
stripedRows responsiveLayout="scroll">
|
||||
<Column field="name" header="이름" sortable style="width: 15%"></Column>
|
||||
<Column field="name" header="이름" sortable style="width: 15%">
|
||||
<template #body="slotProps">
|
||||
<div class="member-name">
|
||||
<div class="name">{{ slotProps.data.name }}</div>
|
||||
<div class="contact-info">
|
||||
<div v-if="slotProps.data.phone" class="phone">{{ slotProps.data.phone }}</div>
|
||||
<div v-if="slotProps.data.email" class="email">{{ slotProps.data.email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="gender" header="성별" sortable style="width: 10%"></Column>
|
||||
<Column field="memberType" header="회원 유형" sortable style="width: 15%">
|
||||
<template #body="slotProps">
|
||||
@@ -177,13 +187,12 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import apiClient from '@/services/api';
|
||||
import clubService from '@/services/clubService';
|
||||
import { useClubStore } from '@/stores/clubStore';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
|
||||
const toast = useToast();
|
||||
const clubStore = useClubStore();
|
||||
const toast = useToast();
|
||||
|
||||
// 회원 유형 목록
|
||||
const memberTypes = ref([
|
||||
@@ -412,23 +421,27 @@ const hideDialog = () => {
|
||||
// 회원 저장
|
||||
const saveMember = async () => {
|
||||
try {
|
||||
const memberData = {
|
||||
...member.value
|
||||
};
|
||||
const memberData = { ...member.value };
|
||||
|
||||
if (dialogMode.value === 'new') {
|
||||
await clubService.addClubMember(clubId.value, memberData);
|
||||
toast.add({ severity: 'success', summary: '성공', detail: '새 회원이 추가되었습니다.', life: 3000 });
|
||||
} else {
|
||||
await clubService.updateClubMember(clubId.value, memberData.id, memberData);
|
||||
toast.add({ severity: 'success', summary: '성공', detail: '회원 정보가 수정되었습니다.', life: 3000 });
|
||||
}
|
||||
const isNew = dialogMode.value === 'new';
|
||||
const response = isNew
|
||||
? await clubService.addClubMember(clubId.value, memberData)
|
||||
: await clubService.updateClubMember(clubId.value, memberData.id, memberData);
|
||||
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: '성공',
|
||||
detail: isNew ? '새 회원이 추가되었습니다.' : '회원 정보가 수정되었습니다.',
|
||||
life: 3000
|
||||
});
|
||||
|
||||
hideDialog();
|
||||
await loadMembers();
|
||||
} catch (error) {
|
||||
console.error('회원 저장 중 오류:', error);
|
||||
toast.add({ severity: 'error', summary: '오류', detail: '회원 정보 저장 중 오류가 발생했습니다.', life: 3000 });
|
||||
// 서버에서 오류 메시지가 있는 경우 해당 메시지 표시
|
||||
const errorMessage = error.response?.data?.message || '회원 정보 저장 중 오류가 발생했습니다.';
|
||||
toast.add({ severity: 'error', summary: '오류', detail: errorMessage, life: 3000 });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -498,6 +511,31 @@ const statusOptions = ref([
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 회원 이름, 연락처 스타일 */
|
||||
.member-name {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.member-name .name {
|
||||
font-weight: bold;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.member-name .contact-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
.member-name .phone,
|
||||
.member-name .email {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* 필터 다이얼로그 스타일 */
|
||||
:deep(.p-dialog-content) {
|
||||
padding: 1rem;
|
||||
|
||||
@@ -283,19 +283,16 @@
|
||||
|
||||
/* 순위별 색상 */
|
||||
.rank-badge.rank-1 {
|
||||
color: var(--p-yellow-500);
|
||||
color: gold;
|
||||
font-weight: 900;
|
||||
}
|
||||
.rank-badge.rank-2 {
|
||||
color: var(--p-gray-400);
|
||||
color: silver;
|
||||
font-weight: 900;
|
||||
}
|
||||
.rank-badge.rank-3 {
|
||||
color: var(--p-orange-500);
|
||||
}
|
||||
.rank-badge.rank-4 {
|
||||
color: var(--p-blue-400);
|
||||
}
|
||||
.rank-badge.rank-5 {
|
||||
color: var(--p-green-400);
|
||||
color: #cd7f32;
|
||||
font-weight: 900;
|
||||
}
|
||||
/* 필요시 더 추가 가능 */
|
||||
|
||||
@@ -324,9 +321,8 @@
|
||||
}
|
||||
|
||||
.rank-badge {
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--p-yellow-500);
|
||||
color: var(--p-black-500);
|
||||
}
|
||||
|
||||
.score-table th,
|
||||
@@ -456,6 +452,14 @@
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.score-status-icon-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
min-width: 20px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.score-input-hc-wrap > .score-input,
|
||||
@@ -499,6 +503,9 @@
|
||||
.member-score-summary {
|
||||
margin-top: 4px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<h1 v-if="clubName" class="p-text-primary p-text-bold p-text-lg p-mb-2">{{ clubName }}</h1>
|
||||
<div class="event-public-card">
|
||||
<div v-if="loading" class="text-center">로딩 중...</div>
|
||||
<div v-else-if="error" class="text-center">
|
||||
<i class="pi pi-exclamation-triangle text-red block"></i>
|
||||
<div v-else-if="error" class="text-center text-4xl">
|
||||
<i class="pi pi-exclamation-triangle text-red block text-8xl"></i>
|
||||
<span class="text-red">{{ error }}</span>
|
||||
</div>
|
||||
<div v-else-if="!isAuthenticated">
|
||||
@@ -63,11 +63,11 @@
|
||||
</div>
|
||||
<div class="p-text-secondary mb-4">{{ event.description || '' }}</div>
|
||||
<Divider />
|
||||
<!-- 팀별 게임별 점수/등수 표 -->
|
||||
</div>
|
||||
<template v-if="teams.length > 0">
|
||||
<Card class="team-score-table-wrapper mb-4">
|
||||
<template #content>
|
||||
<!-- 팀별 게임별 점수/등수 표 -->
|
||||
<DataTable :value="getAllTeamsWithUnassigned()" class="team-score-table" :responsiveLayout="'scroll'">
|
||||
<Column header="팀">
|
||||
<template #body="slotProps">
|
||||
@@ -99,14 +99,67 @@
|
||||
</DataTable>
|
||||
</template>
|
||||
</Card>
|
||||
<div v-for="team in getAllTeamsWithUnassigned()" :key="team.id" class="mb-4">
|
||||
<template v-if="event.status != '완료'">
|
||||
<div v-for="team in getAllTeamsWithUnassigned()" :key="team.id" class="mb-4">
|
||||
<Card>
|
||||
<template #content>
|
||||
<MemberListCard
|
||||
:title="parseInt(team.teamNumber) ? '팀 ' + team.teamNumber : '미배정 인원'"
|
||||
:members="team.members"
|
||||
:teamHandicap="team.handicap"
|
||||
:teamNumber="team.teamNumber"
|
||||
:gameCount="event.gameCount"
|
||||
:canInputScore="canInputScore"
|
||||
:scoresProxy="scoresProxy"
|
||||
:scoreStates="scoreStates"
|
||||
:onScoreInput="onScoreInput"
|
||||
:onScoreBlur="onScoreBlur"
|
||||
:onScoreEnter="onScoreEnter"
|
||||
:getMemberTotalScore="getMemberTotalScore"
|
||||
:getMemberAverageScore="getMemberAverageScore"
|
||||
:scoreColorClass="scoreColorClass"
|
||||
:memberRanks="memberRankMap"
|
||||
/>
|
||||
<!-- 팀 카드 요약(총점/등수/게임별 점수)은 '팀'만 노출 -->
|
||||
<Divider />
|
||||
<div v-if="team.teamNumber" class="team-game-scores">
|
||||
<div class="team-total-row team-total-row-responsive team-total-row-block">
|
||||
<div class="team-games-grid"
|
||||
:style="{ gridTemplateColumns: `repeat(${event.gameCount > 3 ? 3 : event.gameCount}, 1fr)` }">
|
||||
<template v-for="n in event.gameCount" :key="n">
|
||||
<div class="team-game-cell">
|
||||
<b>{{ n }}G:</b> {{ getTeamTotalScore(team, n) }}
|
||||
<span v-if="getRanksByGame(n)[(team.id || team.teamNumber) + ''] !== undefined"
|
||||
class="rank-badge"
|
||||
:class="'rank-' + getRanksByGame(n)[(team.id || team.teamNumber) + '']">
|
||||
({{ getRanksByGame(n)[(team.id || team.teamNumber) + ''] }}위)
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="team-total-main">
|
||||
<span class="team-total-label">총점</span>
|
||||
<span class="team-total-scores">{{ Array.from({ length: event.gameCount }, (_, i) => getTeamTotalScore(team, i + 1)).reduce((a, b) => a + b, 0) }}</span>
|
||||
<span v-if="getTeamOverallRank({ ...team, id: (team.id || team.teamNumber) + '' })"
|
||||
class="rank-badge"
|
||||
:class="'rank-' + getTeamOverallRank({ ...team, id: (team.id || team.teamNumber) + '' })">
|
||||
{{ getTeamOverallRank({ ...team, id: (team.id || team.teamNumber) + '' }) }}위
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="event.status != '완료'">
|
||||
<Card>
|
||||
<template #content>
|
||||
<MemberListCard
|
||||
:title="parseInt(team.teamNumber) ? '팀 ' + team.teamNumber : '미배정 인원'"
|
||||
:members="team.members"
|
||||
:teamHandicap="team.handicap"
|
||||
:teamNumber="team.teamNumber"
|
||||
title="참가자"
|
||||
:members="sortedParticipants"
|
||||
:gameCount="event.gameCount"
|
||||
:canInputScore="canInputScore"
|
||||
:scoresProxy="scoresProxy"
|
||||
@@ -117,71 +170,90 @@
|
||||
:getMemberTotalScore="getMemberTotalScore"
|
||||
:getMemberAverageScore="getMemberAverageScore"
|
||||
:scoreColorClass="scoreColorClass"
|
||||
:memberRanks="memberRankMap"
|
||||
/>
|
||||
<!-- 팀 카드 요약(총점/등수/게임별 점수)은 '팀'만 노출 -->
|
||||
<Divider />
|
||||
<div v-if="team.teamNumber" class="team-game-scores">
|
||||
<div class="team-total-row team-total-row-responsive team-total-row-block">
|
||||
<div class="team-games-grid"
|
||||
:style="{ gridTemplateColumns: `repeat(${event.gameCount > 3 ? 3 : event.gameCount}, 1fr)` }">
|
||||
<template v-for="n in event.gameCount" :key="n">
|
||||
<div class="team-game-cell">
|
||||
<b>{{ n }}G:</b> {{ getTeamTotalScore(team, n) }}
|
||||
<span v-if="getRanksByGame(n)[(team.id || team.teamNumber) + ''] !== undefined"
|
||||
class="rank-badge"
|
||||
:class="'rank-' + getRanksByGame(n)[(team.id || team.teamNumber) + '']">
|
||||
({{ getRanksByGame(n)[(team.id || team.teamNumber) + ''] }}위)
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="team-total-main">
|
||||
<span class="team-total-label">총점</span>
|
||||
<span class="team-total-scores">{{ Array.from({ length: event.gameCount }, (_, i) => getTeamTotalScore(team, i + 1)).reduce((a, b) => a + b, 0) }}</span>
|
||||
<span v-if="getTeamOverallRank({ ...team, id: (team.id || team.teamNumber) + '' })"
|
||||
class="rank-badge"
|
||||
:class="'rank-' + getTeamOverallRank({ ...team, id: (team.id || team.teamNumber) + '' })">
|
||||
{{ getTeamOverallRank({ ...team, id: (team.id || team.teamNumber) + '' }) }}위
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-if="event.status == '완료'">
|
||||
<Card>
|
||||
<template #title>
|
||||
<div class="card-title-with-button">
|
||||
<h2>최종 순위</h2>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<MemberListCard
|
||||
title="참가자"
|
||||
:members="sortedParticipants"
|
||||
:gameCount="event.gameCount"
|
||||
:canInputScore="canInputScore"
|
||||
:scoresProxy="scoresProxy"
|
||||
:scoreStates="scoreStates"
|
||||
:onScoreInput="onScoreInput"
|
||||
:onScoreBlur="onScoreBlur"
|
||||
:onScoreEnter="onScoreEnter"
|
||||
:getMemberTotalScore="getMemberTotalScore"
|
||||
:getMemberAverageScore="getMemberAverageScore"
|
||||
:scoreColorClass="scoreColorClass"
|
||||
/>
|
||||
<DataTable :value="getFinalRankingData()" stripedRows :paginator="true" :rows="30"
|
||||
paginatorTemplate="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown"
|
||||
:rowsPerPageOptions="[5,10,20,30,50]" responsiveLayout="scroll">
|
||||
|
||||
<!-- 순위 열 -->
|
||||
<Column field="rank" header="순위" :sortable="true" style="width: 3%;">
|
||||
<template #body="slotProps">
|
||||
<span :class="'rank-badge rank-' + slotProps.data.rank">
|
||||
{{ slotProps.data.rank }}
|
||||
</span>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<!-- 이름 열 -->
|
||||
<Column field="name" header="이름" :sortable="true" style="min-width: 7rem;"></Column>
|
||||
|
||||
<!-- 핸디캡 열 -->
|
||||
<Column field="handicap" header="핸디캡" :sortable="true"></Column>
|
||||
|
||||
<!-- 게임별 점수 열 -->
|
||||
<template v-for="n in event.gameCount" :key="n">
|
||||
<Column :field="'game' + n" :header="n + 'G'" :sortable="true">
|
||||
<template #body="slotProps">
|
||||
<span :class="{ 'score-red': slotProps.data['game' + n] >= 200 }">
|
||||
{{ slotProps.data['game' + n] }}
|
||||
</span>
|
||||
</template>
|
||||
</Column>
|
||||
</template>
|
||||
|
||||
<!-- 총점 열 -->
|
||||
<Column field="totalScore" header="총점" :sortable="true">
|
||||
<template #body="slotProps">
|
||||
<b>{{ slotProps.data.totalScore }}</b>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<!-- 에버리지 열 -->
|
||||
<Column field="average" header="에버리지" :sortable="true">
|
||||
<template #body="slotProps">
|
||||
<span :class="{ 'score-red': slotProps.data.average >= 200 }">
|
||||
{{ slotProps.data.average }}
|
||||
</span>
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
</template>
|
||||
</Card>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<JoinDialog v-if="showJoinDialog && canJoin" v-model:visible="showJoinDialog" :publicHash="publicHash"
|
||||
:participants="participants"
|
||||
:availableMembers="availableMembers" :eventStatus="event.status"
|
||||
@close="showJoinDialog = false" @updated="reload" />
|
||||
@close="showJoinDialog = false" @updated="reload" @guest-added="handleGuestAdded" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, reactive, onMounted } from 'vue';
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue';
|
||||
import {
|
||||
calculateIndividualRankings,
|
||||
calculateGameRankings,
|
||||
calculateParticipantTotalScore,
|
||||
calculateMemberAverageScore,
|
||||
calculateTeamGameScore,
|
||||
calculateTeamTotalHandicap,
|
||||
calculateTeamOverallRankings
|
||||
} from '@/utils/rankingUtils';
|
||||
import { debounce } from 'lodash-es'; // lodash 설치 필요 (이미 있다면 무시)
|
||||
import { useRoute } from 'vue-router';
|
||||
import MemberListCard from '@/components/public/MemberListCard.vue';
|
||||
@@ -236,6 +308,12 @@ const joinButtonLabel = computed(() => {
|
||||
// 점수 상태 관리: idle, saving, saved, error
|
||||
const scoreStates = reactive({});
|
||||
|
||||
// 총점 기준 순위 맵 (reactive 값)
|
||||
const memberRankMap = reactive({});
|
||||
|
||||
// 게임별 순위 맵
|
||||
const gameRanks = reactive({});
|
||||
|
||||
// 점수 색상 클래스 계산
|
||||
// 점수 색상 클래스 계산 (핸디캡 포함)
|
||||
function scoreColorClass(state, value, handicap) {
|
||||
@@ -271,12 +349,81 @@ const saveScore = debounce(async (member, n, oldValue, newValue, teamNumber) =>
|
||||
member.scores[n-1] = Number(newValue);
|
||||
}
|
||||
setTimeout(() => { scoreStates[key] = 'idle'; }, 1200); // 일정 시간 후 아이콘 사라지게
|
||||
|
||||
// 점수 저장 후 순위 업데이트
|
||||
updateMemberRanks();
|
||||
} catch (e) {
|
||||
scoreStates[key] = 'error';
|
||||
setTimeout(() => { scoreStates[key] = 'idle'; }, 2000);
|
||||
}
|
||||
}, 200);
|
||||
|
||||
// 최종 순위 데이터 생성 함수
|
||||
const getFinalRankingData = () => {
|
||||
if (!participants.value || participants.value.length === 0) return [];
|
||||
|
||||
// 순위 계산
|
||||
const activeParticipants = participants.value.filter(p => p && p.status !== '취소');
|
||||
const ranks = calculateIndividualRankings(activeParticipants, scoresProxy.value, event.value.gameCount);
|
||||
|
||||
// 순위 맵을 memberRankMap에도 적용하여 팀 카드에서도 동일한 순위 사용
|
||||
Object.keys(ranks).forEach(participantId => {
|
||||
memberRankMap[participantId] = ranks[participantId];
|
||||
});
|
||||
|
||||
// 참가자별 데이터 생성
|
||||
const rankingData = activeParticipants.map(participant => {
|
||||
const participantId = participant.participantId;
|
||||
const handicap = Number(participant.handicap) || 0;
|
||||
|
||||
// 게임별 점수 계산
|
||||
const gameScores = {};
|
||||
let totalScore = 0;
|
||||
let playedGames = 0;
|
||||
|
||||
for (let i = 1; i <= event.value.gameCount; i++) {
|
||||
const scoreKey = `${participantId}-${i}`;
|
||||
const rawScore = scoresProxy.value[scoreKey];
|
||||
|
||||
if (rawScore !== undefined && rawScore !== null && rawScore !== '') {
|
||||
const score = Number(rawScore);
|
||||
// 핸디캡 포함 점수가 300점을 넘지 않도록 제한
|
||||
const scoreWithHc = Math.min(score + handicap, 300);
|
||||
|
||||
gameScores[`rawGame${i}`] = score;
|
||||
gameScores[`game${i}`] = scoreWithHc;
|
||||
totalScore += scoreWithHc;
|
||||
playedGames++;
|
||||
} else {
|
||||
gameScores[`rawGame${i}`] = 0;
|
||||
gameScores[`game${i}`] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 평균 점수 계산 (소수점 1자리까지 표시)
|
||||
const average = playedGames > 0 ? (totalScore / playedGames).toFixed(1) : 0;
|
||||
|
||||
return {
|
||||
participantId,
|
||||
rank: ranks[participantId] || '-',
|
||||
name: participant.name || '이름없음',
|
||||
handicap,
|
||||
...gameScores,
|
||||
totalScore,
|
||||
average
|
||||
};
|
||||
});
|
||||
|
||||
// 순위로 정렬
|
||||
rankingData.sort((a, b) => {
|
||||
if (a.rank === '-') return 1;
|
||||
if (b.rank === '-') return -1;
|
||||
return a.rank - b.rank;
|
||||
});
|
||||
|
||||
return rankingData;
|
||||
};
|
||||
|
||||
|
||||
function onScoreBlur(member, n, teamNumber) {
|
||||
const key = member.participantId + '-' + n;
|
||||
@@ -306,31 +453,123 @@ function onScoreEnter(member, n, teamNumber) {
|
||||
saveScore(member, n, member.scores?.[n-1] ?? '', val, teamNumber);
|
||||
}
|
||||
|
||||
// 멤버별 평균 점수 계산 (입력된 점수만 평균, 소수점 1자리)
|
||||
// 멤버별 평균 점수 계산 (입력된 점수만 평균, 소수점 1자리) - rankingUtils 사용
|
||||
function getMemberAverageScore(member) {
|
||||
if (!member || !event.value.gameCount) return 0;
|
||||
let total = 0, count = 0;
|
||||
for (let i = 1; i <= event.value.gameCount; i++) {
|
||||
const score = Number(scoresProxy.value[member.participantId + '-' + i]);
|
||||
const hc = Number(member.handicap) || 0;
|
||||
if (!isNaN(score) && score > 0) {
|
||||
total += score + hc;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count > 0 ? Math.round((total / count) * 10) / 10 : 0;
|
||||
return calculateMemberAverageScore(member, scoresProxy.value, event.value.gameCount);
|
||||
}
|
||||
|
||||
// 멤버별 핸디캡 포함 총점 계산
|
||||
// 멤버별 핸디캡 포함 총점 계산 - rankingUtils 사용
|
||||
function getMemberTotalScore(member) {
|
||||
if (!member || !event.value.gameCount) return 0;
|
||||
let total = 0;
|
||||
for (let i = 1; i <= event.value.gameCount; i++) {
|
||||
const score = Number(scoresProxy.value[member.participantId + '-' + i]) || 0;
|
||||
const hc = Number(member.handicap) || 0;
|
||||
total += score + hc;
|
||||
return calculateParticipantTotalScore(member, scoresProxy.value, event.value.gameCount);
|
||||
}
|
||||
|
||||
// 총점 기준 순위 계산 (일반 함수로 변경)
|
||||
function updateMemberRanks() {
|
||||
// 순위 계산에 사용할 참가자 목록 가져오기
|
||||
const activeParticipants = [];
|
||||
|
||||
// 참가자 배열 직접 사용
|
||||
if (participants.value && participants.value.length > 0) {
|
||||
// 취소되지 않은 참가자만 필터링
|
||||
activeParticipants.push(...participants.value.filter(p => p && p.status !== '취소'));
|
||||
} else {
|
||||
|
||||
// 백업 방법: 팀과 미배정 멤버 사용
|
||||
if (teams.value && teams.value.length > 0) {
|
||||
teams.value.forEach(team => {
|
||||
if (team.members && team.members.length > 0) {
|
||||
activeParticipants.push(...team.members.filter(m => m && m.status !== '취소'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (unassignedMembers.value && unassignedMembers.value.length > 0) {
|
||||
activeParticipants.push(...unassignedMembers.value.filter(m => m && m.status !== '취소'));
|
||||
}
|
||||
}
|
||||
return total;
|
||||
|
||||
// 순위 계산 유틸리티 함수 호출
|
||||
const ranks = calculateIndividualRankings(
|
||||
activeParticipants,
|
||||
scoresProxy.value, // .value 추가
|
||||
event.value?.gameCount || 3
|
||||
);
|
||||
|
||||
// 순위 맵 초기화
|
||||
Object.keys(memberRankMap).forEach(key => {
|
||||
delete memberRankMap[key];
|
||||
});
|
||||
|
||||
// 순위 맵에 계산된 순위 적용
|
||||
Object.keys(ranks).forEach(participantId => {
|
||||
memberRankMap[participantId] = ranks[participantId];
|
||||
|
||||
// 참가자 이름 찾기
|
||||
const participant = activeParticipants.find(p => p.participantId == participantId);
|
||||
const name = participant ? (participant.name || '이름없음') : '알 수 없음';
|
||||
});
|
||||
|
||||
|
||||
// 게임별 순위도 계산
|
||||
updateGameRankings();
|
||||
}
|
||||
|
||||
// 게임별 순위 계산 함수
|
||||
function updateGameRankings() {
|
||||
// 게임 수 가져오기
|
||||
const gameCount = event.value?.gameCount || 3;
|
||||
|
||||
// 팀 데이터 가져오기 (미배정 포함)
|
||||
const allTeams = getAllTeamsWithUnassigned();
|
||||
|
||||
// 각 게임별로 순위 계산
|
||||
for (let i = 1; i <= gameCount; i++) {
|
||||
// 게임별 팀 점수 계산
|
||||
const teamsWithGameScores = allTeams.map(team => {
|
||||
const gameScore = getTeamTotalScore(team, i);
|
||||
return {
|
||||
teamId: team.id || team.teamNumber,
|
||||
teamNumber: team.teamNumber,
|
||||
gameScore: gameScore,
|
||||
isUnassigned: team.isUnassigned || false
|
||||
};
|
||||
});
|
||||
|
||||
// 게임 점수 기준 내림차순 정렬
|
||||
teamsWithGameScores.sort((a, b) => b.gameScore - a.gameScore);
|
||||
|
||||
// 순위 맵 초기화
|
||||
gameRanks[i] = {};
|
||||
|
||||
// 순위 부여
|
||||
let rank = 1;
|
||||
let prevScore = -1;
|
||||
let sameRankCount = 0;
|
||||
|
||||
teamsWithGameScores.forEach((team, index) => {
|
||||
// 동점자 처리
|
||||
if (prevScore === team.gameScore) {
|
||||
sameRankCount++;
|
||||
} else {
|
||||
rank = index + 1 - sameRankCount;
|
||||
prevScore = team.gameScore;
|
||||
sameRankCount = 0;
|
||||
}
|
||||
|
||||
// 순위 저장
|
||||
gameRanks[i][team.teamId] = rank;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 게임별 순위 가져오기
|
||||
function getRanksByGame(gameNumber) {
|
||||
if (!gameRanks[gameNumber]) {
|
||||
updateGameRankings();
|
||||
}
|
||||
return gameRanks[gameNumber] || {};
|
||||
}
|
||||
|
||||
// 각 참가자별로 점수 배열(scores) 생성 (점수 미정시 null)
|
||||
@@ -423,6 +662,30 @@ async function fetchEventData(password) {
|
||||
// 점수 상태는 항상 'idle'로 초기화 (저장됨 아이콘이 안나오게)
|
||||
scoreStates[key] = 'idle';
|
||||
});
|
||||
|
||||
// 순위 업데이트
|
||||
updateMemberRanks();
|
||||
|
||||
// 순위 맵이 비어있으면 순위 계산 유틸리티 함수 사용
|
||||
if (Object.keys(memberRankMap).length === 0) {
|
||||
// 참가자 데이터가 있는 경우
|
||||
if (participants.value && participants.value.length > 0) {
|
||||
// 취소되지 않은 참가자만 필터링
|
||||
const activeParticipants = participants.value.filter(p => p && p.status !== '취소');
|
||||
|
||||
// 순위 계산 유틸리티 함수 호출
|
||||
const initialRanks = calculateIndividualRankings(
|
||||
activeParticipants,
|
||||
scoresProxy.value, // .value 추가
|
||||
event.value?.gameCount || 3
|
||||
);
|
||||
|
||||
// 계산된 순위 적용
|
||||
Object.keys(initialRanks).forEach(participantId => {
|
||||
memberRankMap[participantId] = initialRanks[participantId];
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
error.value = e.message;
|
||||
event.value = null;
|
||||
@@ -459,6 +722,12 @@ function reload() {
|
||||
fetchEventData();
|
||||
}
|
||||
|
||||
// 게스트 추가 시 처리 함수
|
||||
function handleGuestAdded(guest) {
|
||||
// 게스트 추가 후 전체 데이터 다시 불러오기
|
||||
reload();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const token = sessionStorage.getItem('event_token_' + publicHash);
|
||||
if (token) {
|
||||
@@ -502,93 +771,34 @@ function getAllTeamsWithUnassigned() {
|
||||
return teamList;
|
||||
}
|
||||
|
||||
// 팀별 게임 총점 계산 함수 - rankingUtils 사용
|
||||
function getTeamTotalScore(team, gameIdx) {
|
||||
let total = 0;
|
||||
if (!team || !team.members) return 0;
|
||||
team.members.forEach(member => {
|
||||
const score = Number(scoresProxy.value[member.participantId + '-' + gameIdx]) || 0;
|
||||
const memberHc = Number(member.handicap) || 0;
|
||||
total += score + memberHc;
|
||||
});
|
||||
total += Number(team.handicap) || 0;
|
||||
return total;
|
||||
return calculateTeamGameScore(team, scoresProxy.value, gameIdx);
|
||||
}
|
||||
|
||||
// 팀별 핸디캡 합산 계산 함수 (단순 값 반환)
|
||||
// 팀별 핸디캡 합산 계산 함수 - rankingUtils 사용
|
||||
function getTeamTotalHandicap(team) {
|
||||
let total = 0;
|
||||
if (!team || !team.members) return 0;
|
||||
team.members.forEach(member => {
|
||||
const memberHc = Number(member.handicap) || 0;
|
||||
total += memberHc;
|
||||
});
|
||||
total += Number(team.handicap) || 0;
|
||||
return total;
|
||||
return calculateTeamTotalHandicap(team);
|
||||
}
|
||||
|
||||
// 전체 게임 합계 기준 팀별 등수 계산 함수
|
||||
// 전체 게임 합계 기준 팀별 등수 계산 함수 - rankingUtils 사용
|
||||
function getTeamOverallRank(team) {
|
||||
if (!team || !team.id) return '';
|
||||
|
||||
// 실제 팀만 포함하여 순위 계산
|
||||
const allTeams = getAllTeamsWithUnassigned();
|
||||
const teamTotals = allTeams.map(t => ({
|
||||
teamId: t.id || t.teamNumber,
|
||||
total: Array.from({ length: event.value.gameCount }, (_, i) => getTeamTotalScore(t, i + 1)).reduce((a, b) => a + b, 0),
|
||||
handicapSum: getTeamTotalHandicap(t),
|
||||
memberCount: (t.members ? t.members.length : 0)
|
||||
}));
|
||||
teamTotals.sort((a, b) => {
|
||||
if (b.total !== a.total) return b.total - a.total;
|
||||
if (a.handicapSum !== b.handicapSum) return a.handicapSum - b.handicapSum;
|
||||
return b.memberCount - a.memberCount;
|
||||
});
|
||||
let rank = 1;
|
||||
for (let i = 0; i < teamTotals.length; i++) {
|
||||
if (
|
||||
i > 0 && (
|
||||
teamTotals[i].total !== teamTotals[i - 1].total ||
|
||||
teamTotals[i].handicapSum !== teamTotals[i - 1].handicapSum ||
|
||||
teamTotals[i].memberCount !== teamTotals[i - 1].memberCount
|
||||
)
|
||||
) {
|
||||
rank = i + 1;
|
||||
}
|
||||
if ((teamTotals[i].teamId + '') === (team.id + '')) {
|
||||
return rank;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
const rankMap = calculateTeamOverallRankings(allTeams, scoresProxy.value, event.value.gameCount);
|
||||
|
||||
// 해당 팀의 순위 반환
|
||||
const teamId = team.id || team.teamNumber;
|
||||
return rankMap[teamId] || '';
|
||||
}
|
||||
|
||||
// 게임별 등수 계산 함수 (computed)
|
||||
// 통합 등수 산정 함수
|
||||
function getRanksByGame(gameIdx) {
|
||||
const allTeams = getAllTeamsWithUnassigned();
|
||||
const scores = allTeams.map(team => ({
|
||||
teamId: team.id,
|
||||
score: getTeamTotalScore(team, gameIdx),
|
||||
handicapSum: getTeamTotalHandicap(team),
|
||||
memberCount: (team.members ? team.members.length : 0)
|
||||
}));
|
||||
scores.sort((a, b) => {
|
||||
if (b.score !== a.score) return b.score - a.score;
|
||||
if (a.handicapSum !== b.handicapSum) return a.handicapSum - b.handicapSum;
|
||||
return b.memberCount - a.memberCount;
|
||||
});
|
||||
const ranks = {};
|
||||
let rank = 1;
|
||||
scores.forEach((item, i) => {
|
||||
if (
|
||||
i > 0 && (
|
||||
item.score !== scores[i - 1].score ||
|
||||
item.handicapSum !== scores[i - 1].handicapSum ||
|
||||
item.memberCount !== scores[i - 1].memberCount
|
||||
)
|
||||
) {
|
||||
rank = i + 1;
|
||||
}
|
||||
ranks[item.teamId] = rank;
|
||||
});
|
||||
return ranks;
|
||||
}
|
||||
// 게임별 팀 등수 계산 함수 (rankingUtils로 대체됨)
|
||||
// 이 함수는 삭제되었으며 새로운 getRanksByGame 함수를 사용합니다.
|
||||
|
||||
|
||||
</script>
|
||||
<style src="./EventPublicPage.css"></style>
|
||||
Reference in New Issue
Block a user