Files
ergebnis-web/app.js
T
Thies Mueller 03e1c48832 initial commit
2026-06-20 00:37:22 +02:00

255 lines
7.3 KiB
JavaScript

let rennplanData = {};
let resultsData = {};
let messageData = {};
document.addEventListener("DOMContentLoaded", () => {
initTabs();
loadData();
setInterval(loadData, CONFIG.REFRESH_INTERVAL);
});
function initTabs() {
const rennplanBtn = document.getElementById("tab-rennplan");
const resultsBtn = document.getElementById("tab-ergebnisse");
const rennplanView = document.getElementById("rennplan-view");
const resultsView = document.getElementById("ergebnisse-view");
rennplanBtn.addEventListener("click", () => {
rennplanView.classList.remove("hidden");
resultsView.classList.add("hidden");
setTabActive(rennplanBtn, resultsBtn);
});
resultsBtn.addEventListener("click", () => {
resultsView.classList.remove("hidden");
rennplanView.classList.add("hidden");
setTabActive(resultsBtn, rennplanBtn);
});
}
function setTabActive(active, inactive) {
active.className =
"flex-1 py-4 text-indigo-600 border-b-4 border-indigo-600 font-medium";
inactive.className =
"flex-1 py-4 text-slate-500 font-medium";
}
async function loadData() {
try {
const scrollY = window.scrollY;
const [rennplanRes, resultsRes, messageRes] = await Promise.all([
fetch(CONFIG.RENNPLAN_URL),
fetch(CONFIG.RESULTS_URL),
fetch(CONFIG.MESSAGE_URL)
]);
rennplanData = await rennplanRes.json();
resultsData = await resultsRes.json();
messageData = await messageRes.json();
renderMessage();
renderRennplan();
renderResults();
requestAnimationFrame(() => {
window.scrollTo(0, scrollY);
});
} catch (err) {
console.error("API Fehler:", err);
}
}
function renderMessage() {
const box = document.getElementById("message-box");
if (!messageData?.message) {
box.classList.add("hidden");
return;
}
let style = "";
switch (messageData.type) {
case "danger":
style = "bg-red-50 border-red-400 text-red-900";
break;
case "warning":
style = "bg-amber-50 border-amber-400 text-amber-900";
break;
default:
style = "bg-blue-50 border-blue-400 text-blue-900";
}
box.className = `border-l-4 p-4 rounded-xl shadow-sm ${style}`;
box.innerHTML = `<div class="font-medium">${messageData.message}</div>`;
box.classList.remove("hidden");
}
function renderRennplan() {
const container = document.getElementById("rennplan-content");
container.innerHTML = "";
const rennplan = rennplanData.rennplan || {};
const sortedLaeufe = Object.keys(rennplan)
.sort((a, b) => {
return parseInt(b.replace("lauf", "")) - parseInt(a.replace("lauf", ""));
});
sortedLaeufe.forEach(laufKey => {
const laufNum = laufKey.replace("lauf", "");
const races = Object.entries(rennplan[laufKey])
.sort((a, b) => Number(a[0]) - Number(b[0]));
container.innerHTML += `
<section class="space-y-4">
<div class="flex items-center gap-3 mb-2">
<div class="h-10 w-10 rounded-full bg-indigo-600 text-white flex items-center justify-center font-semibold">
${laufNum}
</div>
<h2 class="text-xl font-medium text-slate-800">
Lauf ${laufNum}
</h2>
</div>
${races.map(([_, race]) => `
<div class="bg-white rounded-2xl shadow-sm overflow-hidden">
<div class="bg-indigo-50 px-5 py-4 flex justify-between">
<div>
<div class="text-lg font-medium">${race.name}</div>
<div class="text-sm text-slate-500">${race.art}</div>
</div>
<div class="text-indigo-600 font-semibold">
${race.zeit}
</div>
</div>
<div class="p-5 flex flex-wrap gap-2 text-sm">
<span class="bg-slate-100 px-3 py-1 rounded-full">
Bahn 1 · ${race.bahn1}
</span>
<span class="bg-slate-100 px-3 py-1 rounded-full">
Bahn 2 · ${race.bahn2}
</span>
<span class="bg-slate-100 px-3 py-1 rounded-full">
Bahn 3 · ${race.bahn3}
</span>
</div>
</div>
`).join("")}
</section>`;
});
}
function renderResults() {
const container = document.getElementById("results-content");
container.innerHTML = "";
const races = Object.entries(resultsData.ergebnisse || {})
.sort((a, b) => Number(b[0]) - Number(a[0]));
races.forEach(([_, race]) => {
const lane = (key, data, winner) => {
const isWinner = race.winner === key;
return `
<div class="
p-4 rounded-xl border
${isWinner ? "bg-green-50 border-green-400" : "bg-slate-50 border-slate-200"}
">
<div class="flex justify-between items-center">
<div class="font-medium">${data.boot}</div>
${isWinner ? `
<span class="bg-green-600 text-white text-xs px-2 py-1 rounded-full">
Sieger
</span>` : ""}
</div>
<div class="text-sm text-slate-500 mt-1">
${data.zeit}
</div>
<div class="text-xs text-slate-400 mt-1">
${formatLaneLabel(key)}
</div>
</div>`;
};
container.innerHTML += `
<div class="bg-white rounded-3xl shadow-sm overflow-hidden">
<div class="bg-indigo-600 text-white p-5">
<div class="flex justify-between">
<div>
<div class="text-lg font-medium">${race.title}</div>
<div class="text-indigo-100 text-sm">${race.art}</div>
</div>
<div class="text-sm">
Lauf ${race.lauf}
</div>
</div>
<div class="text-indigo-100 text-sm mt-2">
${formatDateTime(race.start)}
</div>
</div>
<div class="p-5 grid gap-3 md:grid-cols-3">
${lane("bahn1", race.bahn1)}
${lane("bahn2", race.bahn2)}
${lane("bahn3", race.bahn3)}
</div>
</div>`;
});
}
function formatLaneLabel(key) {
const map = {
bahn1: "Bahn 1",
bahn2: "Bahn 2",
bahn3: "Bahn 3"
};
return map[key] || key;
}
function formatDateTime(dateString) {
const match = dateString.match(
/^(\d{4})-(\d{2})-(\d{2})-(\d{2}):(\d{2}):(\d{2})$/
);
if (!match) return dateString;
const [, y, m, d, h, i, s] = match;
const date = new Date(y, m - 1, d, h, i, s);
return date.toLocaleString("de-DE", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit"
});
}