313 lines
8.9 KiB
JavaScript
313 lines
8.9 KiB
JavaScript
let rennplanData = {};
|
||
let resultsData = {};
|
||
let messageData = {};
|
||
|
||
document.addEventListener("DOMContentLoaded", () => {
|
||
initAppUI();
|
||
initTabs();
|
||
initAppPromotion();
|
||
loadData();
|
||
setInterval(loadData, CONFIG.REFRESH_INTERVAL);
|
||
});
|
||
function initAppUI() {
|
||
const fullTitle = `${CONFIG.EVENT_NAME} – Rennplan & Ergebnisse`;
|
||
|
||
document.title = fullTitle;
|
||
|
||
const titleEl = document.getElementById("app-title");
|
||
if (titleEl) {
|
||
titleEl.textContent = fullTitle;
|
||
}
|
||
}
|
||
|
||
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"
|
||
});
|
||
}
|
||
|
||
function initAppPromotion() {
|
||
if (CONFIG.IOS_APP_ID) {
|
||
const meta = document.createElement("meta");
|
||
meta.name = "apple-itunes-app";
|
||
meta.content = `app-id=${CONFIG.IOS_APP_ID}`;
|
||
document.head.appendChild(meta);
|
||
}
|
||
renderAndroidPromo();
|
||
}
|
||
|
||
|
||
function isAndroidDevice() {
|
||
return /android/i.test(navigator.userAgent || "");
|
||
}
|
||
|
||
function renderAndroidPromo() {
|
||
const promo = document.getElementById("app-promo");
|
||
if (!promo) return;
|
||
|
||
const isAndroid = isAndroidDevice();
|
||
|
||
console.log("Android detected:", isAndroid);
|
||
|
||
if (!isAndroid || !CONFIG.ANDROID_PACKAGE_NAME) {
|
||
promo.classList.add("hidden");
|
||
return;
|
||
}
|
||
|
||
const playStoreUrl =
|
||
`https://play.google.com/store/apps/details?id=${CONFIG.ANDROID_PACKAGE_NAME}`;
|
||
|
||
promo.innerHTML = `
|
||
<div class="bg-indigo-50 border border-indigo-200 text-indigo-800 p-4 rounded-xl flex items-center justify-between shadow-sm">
|
||
<div class="font-medium">
|
||
Jetzt die Android App herunterladen
|
||
</div>
|
||
|
||
<a href="${playStoreUrl}" target="_blank"
|
||
class="bg-indigo-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-indigo-700">
|
||
Öffnen
|
||
</a>
|
||
</div>
|
||
`;
|
||
|
||
promo.classList.remove("hidden");
|
||
} |