랭킹표시 수정

This commit is contained in:
2025-05-17 07:53:05 +09:00
parent a2b5ce88a6
commit b03f8d2d35
13 changed files with 1284 additions and 203 deletions
-1
View File
@@ -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>
+53 -15
View File
@@ -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;
+18 -11
View File
@@ -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;
}
+358 -148
View File
@@ -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>