344 lines
14 KiB
HTML
344 lines
14 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>ランキング</title>
|
|
<style>
|
|
.box2 { margin: 10px; padding: 10px; border: 1px solid #ccc; }
|
|
.best3 { margin: 5px 0; padding: 5px; }
|
|
.span2 { margin-left: 20px; }
|
|
.span3 { font-weight: bold; }
|
|
.span6 { display: inline-block; width: 30px; }
|
|
.black { background-color: #f0f0f0; padding: 10px; margin-bottom: 20px; }
|
|
.arrow { margin: 10px 0; }
|
|
.arrow2 { margin: 10px 0; }
|
|
.disqualified { color: #999; }
|
|
.status {
|
|
display: inline-block;
|
|
padding: 2px 6px;
|
|
border-radius: 3px;
|
|
font-size: 0.9em;
|
|
margin-left: 8px;
|
|
}
|
|
.status-retired {
|
|
background-color: #ffebee;
|
|
color: #c62828;
|
|
}
|
|
.status-finished {
|
|
background-color: #e8f5e9;
|
|
color: #2e7d32;
|
|
}
|
|
.status-running {
|
|
background-color: #e3f2fd;
|
|
color: #1565c0;
|
|
}
|
|
select {
|
|
padding: 8px;
|
|
margin: 5px 0;
|
|
min-width: 200px;
|
|
}
|
|
button {
|
|
padding: 8px 16px;
|
|
margin: 10px 0;
|
|
background-color: #4a90e2;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
button:hover {
|
|
background-color: #357abd;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="ranking">
|
|
<div class="black">
|
|
<span class="span3"></span>
|
|
<span style="font-size: 24px">ランキング</span>
|
|
</div>
|
|
|
|
<div class="arrow">
|
|
<div>イベントを選択してください</div>
|
|
<select id="eventSelect">
|
|
<option value="">イベントを選択してください</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="arrow2">
|
|
<div>クラスを選択してください</div>
|
|
<select id="classSelect" disabled>
|
|
<option value="">クラスを選択してください</option>
|
|
</select>
|
|
</div>
|
|
|
|
<button id="toggleButton" onclick="toggleView()">TOP3表示</button>
|
|
|
|
<div id="normalRanking">
|
|
<div id="teamList"></div>
|
|
</div>
|
|
|
|
<div id="top3Ranking" style="display: none;">
|
|
<div id="top3List"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const API_BASE_URL = 'https://rogaining.sumasen.net';
|
|
let showTop3 = false;
|
|
|
|
// ページ読み込み時にイベント一覧を取得
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
await loadEvents();
|
|
setupEventListeners();
|
|
});
|
|
|
|
// 日時のフォーマット
|
|
function formatDateTime(dateStr) {
|
|
if (!dateStr) return '未ゴール';
|
|
const date = new Date(dateStr);
|
|
return date.toLocaleString('ja-JP', {
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
}
|
|
|
|
// イベントリスナーの設定
|
|
function setupEventListeners() {
|
|
document.getElementById('eventSelect').addEventListener('change', async function() {
|
|
const eventCode = this.value;
|
|
if (eventCode) {
|
|
await loadClasses(eventCode);
|
|
await updateRankings();
|
|
}
|
|
});
|
|
|
|
document.getElementById('classSelect').addEventListener('change', async function() {
|
|
await updateRankings();
|
|
});
|
|
}
|
|
|
|
// イベント一覧の取得と表示
|
|
async function loadEvents() {
|
|
try {
|
|
const response = await fetch(`${API_BASE_URL}/api/new-events/`, {
|
|
method: 'GET',
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
},
|
|
mode: 'cors'
|
|
});
|
|
const events = await response.json();
|
|
const select = document.getElementById('eventSelect');
|
|
|
|
events.filter(event => event.public).forEach(event => {
|
|
const option = document.createElement('option');
|
|
option.value = event.event_name;
|
|
option.textContent = event.event_name;
|
|
select.appendChild(option);
|
|
});
|
|
} catch (error) {
|
|
console.error('イベント一覧の取得に失敗:', error);
|
|
}
|
|
}
|
|
|
|
// クラス一覧の取得と表示
|
|
async function loadClasses(eventCode) {
|
|
try {
|
|
const response = await fetch(`${API_BASE_URL}/api/categories/${eventCode}/`, {
|
|
method: 'GET',
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
},
|
|
mode: 'cors'
|
|
});
|
|
const classes = await response.json();
|
|
const select = document.getElementById('classSelect');
|
|
|
|
select.innerHTML = '<option value="">クラスを選択してください</option>';
|
|
select.disabled = false;
|
|
|
|
classes.forEach(cls => {
|
|
const option = document.createElement('option');
|
|
option.value = cls.category_name;
|
|
option.textContent = cls.category_name;
|
|
select.appendChild(option);
|
|
});
|
|
} catch (error) {
|
|
console.error('クラス一覧の取得に失敗:', error);
|
|
}
|
|
}
|
|
|
|
// ランキングの更新
|
|
async function updateRankings() {
|
|
const eventCode = document.getElementById('eventSelect').value;
|
|
if (!eventCode) return;
|
|
|
|
try {
|
|
if (showTop3) {
|
|
await loadTop3Rankings(eventCode);
|
|
} else {
|
|
const classCode = document.getElementById('classSelect').value;
|
|
if (classCode) {
|
|
await loadClassRankings(eventCode, classCode);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('ランキングの取得に失敗:', error);
|
|
}
|
|
}
|
|
|
|
// クラス別ランキングの表示
|
|
async function loadClassRankings(eventCode, classCode) {
|
|
const response = await fetch(`${API_BASE_URL}/api/rankings/${eventCode}/${classCode}/`, {
|
|
method: 'GET',
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
},
|
|
mode: 'cors'
|
|
});
|
|
const rankingData = await response.json();
|
|
displayNormalRankings(rankingData);
|
|
}
|
|
|
|
// TOP3ランキングの表示
|
|
async function loadTop3Rankings(eventCode) {
|
|
const response = await fetch(`${API_BASE_URL}/api/rankings/top3/${eventCode}/`, {
|
|
method: 'GET',
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'Content-Type': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
},
|
|
mode: 'cors'
|
|
});
|
|
const rankingData = await response.json();
|
|
displayTop3Rankings(rankingData);
|
|
}
|
|
|
|
// 通常ランキングの表示処理
|
|
function displayNormalRankings(rankingData) {
|
|
const container = document.getElementById('teamList');
|
|
container.innerHTML = '';
|
|
|
|
// 有効なランキングの表示
|
|
rankingData.rankings.forEach((team, index) => {
|
|
const div = document.createElement('div');
|
|
div.className = 'best';
|
|
const statusClass = team.status === '棄権' ? 'status-retired' :
|
|
team.status === 'ゴール' ? 'status-finished' : 'status-running';
|
|
div.innerHTML = `
|
|
${index + 1}. ${team.team_name} (${team.zekken_number})
|
|
<span class="span2">合計得点:${team.final_point}</span>
|
|
<span class="span2">獲得ポイント:${team.point}</span>
|
|
<span class="span2">遅刻減点: ${team.late_point}</span>
|
|
<span class="span2">最終更新: ${formatDateTime(team.last_checkin)}</span>
|
|
<span class="status ${statusClass}">(${team.status})</span>
|
|
`;
|
|
container.appendChild(div);
|
|
});
|
|
|
|
// 失格チームの表示
|
|
if (rankingData.disqualified && rankingData.disqualified.length > 0) {
|
|
const disqHeader = document.createElement('div');
|
|
disqHeader.className = 'disqualified-header';
|
|
disqHeader.innerHTML = '<h3>失格チーム</h3>';
|
|
container.appendChild(disqHeader);
|
|
|
|
rankingData.disqualified.forEach(team => {
|
|
const div = document.createElement('div');
|
|
div.className = 'best disqualified';
|
|
const statusClass = team.status === '棄権' ? 'status-retired' :
|
|
team.status === 'ゴール' ? 'status-finished' : 'status-running';
|
|
div.innerHTML = `
|
|
${team.team_name} (${team.zekken_number})
|
|
<span class="span2">獲得ポイント:${team.point}</span>
|
|
<span class="span2">理由: ${team.reason}</span>
|
|
<span class="span2">最終更新: ${formatDateTime(team.last_checkin)}</span>
|
|
<span class="status ${statusClass}">(${team.status})</span>
|
|
`;
|
|
container.appendChild(div);
|
|
});
|
|
}
|
|
}
|
|
|
|
// TOP3ランキングの表示処理
|
|
function displayTop3Rankings(rankingData) {
|
|
const container = document.getElementById('top3List');
|
|
container.innerHTML = '';
|
|
|
|
Object.entries(rankingData).forEach(([category, data]) => {
|
|
const categoryDiv = document.createElement('div');
|
|
categoryDiv.className = 'box2';
|
|
|
|
const categoryHeader = document.createElement('h3');
|
|
categoryHeader.textContent = category;
|
|
categoryDiv.appendChild(categoryHeader);
|
|
|
|
// 有効なランキングの表示
|
|
data.rankings.forEach((team, index) => {
|
|
const teamDiv = document.createElement('div');
|
|
teamDiv.className = 'best3';
|
|
const statusClass = team.status === '棄権' ? 'status-retired' :
|
|
team.status === 'ゴール' ? 'status-finished' : 'status-running';
|
|
teamDiv.innerHTML = `
|
|
<span class="span6">${index + 1}</span>
|
|
${team.team_name} (${team.zekken_number})
|
|
<span class="status ${statusClass}">(${team.status})</span><br>
|
|
合計得点:${team.final_point} (獲得:${team.point} 減点:${team.late_point})<br>
|
|
最終更新: ${formatDateTime(team.last_checkin)}
|
|
`;
|
|
categoryDiv.appendChild(teamDiv);
|
|
});
|
|
|
|
// 失格チームの表示
|
|
if (data.disqualified && data.disqualified.length > 0) {
|
|
const disqHeader = document.createElement('div');
|
|
disqHeader.className = 'disqualified-header';
|
|
disqHeader.innerHTML = '<h4>失格チーム</h4>';
|
|
categoryDiv.appendChild(disqHeader);
|
|
|
|
data.disqualified.forEach(team => {
|
|
const teamDiv = document.createElement('div');
|
|
teamDiv.className = 'best3 disqualified';
|
|
const statusClass = team.status === '棄権' ? 'status-retired' :
|
|
team.status === 'ゴール' ? 'status-finished' : 'status-running';
|
|
teamDiv.innerHTML = `
|
|
${team.team_name} (${team.zekken_number})<br>
|
|
獲得ポイント:${team.point} 理由:${team.reason}<br>
|
|
最終更新: ${formatDateTime(team.last_checkin)}
|
|
<span class="status ${statusClass}">(${team.status})</span>
|
|
`;
|
|
categoryDiv.appendChild(teamDiv);
|
|
});
|
|
}
|
|
|
|
container.appendChild(categoryDiv);
|
|
});
|
|
}
|
|
|
|
// 表示モードの切り替え
|
|
function toggleView() {
|
|
showTop3 = !showTop3;
|
|
const button = document.getElementById('toggleButton');
|
|
const normalRanking = document.getElementById('normalRanking');
|
|
const top3Ranking = document.getElementById('top3Ranking');
|
|
const classSelect = document.getElementById('classSelect');
|
|
|
|
button.textContent = showTop3 ? 'クラス別ランキング' : 'TOP3表示';
|
|
normalRanking.style.display = showTop3 ? 'none' : 'block';
|
|
top3Ranking.style.display = showTop3 ? 'block' : 'none';
|
|
classSelect.disabled = showTop3;
|
|
|
|
updateRankings();
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |