Click on the classes to go to the registration in our membership system. During the registration process you will be asked to log in or create a user account.
Henter holdoversigt...
const bdApp = {
// ----------------------------------------------------
// 1. INDSTILLINGER
// ----------------------------------------------------
// RET DENNE STI TIL HVOR DINE JSON FILER LIGGER PÅ SERVEREN:
basePath: 'https://app.bendixendans.dk/program/',
// ----------------------------------------------------
// 2. DATA VARIABLER
// ----------------------------------------------------
lang: 'da',
data: {},
occ: [], // NYT: én forekomst pr. træningstid (bruges af skema, ugedage og sale)
content: {},
hidden: [],
translations: {},
periods: [], // NYT: kursusperioder fra perioder.json
view: 'kategori',
// UI Fallback tekster (Hvis translations.json ikke har dem)
ui: {
da: {
btn_cat: "Kategorier", btn_sch: "Ugeskema", btn_day: "Ugedage", btn_room: "Salsoversigt",
th_time: "Tidspunkt", th_team: "Hold", th_start: "Opstart", th_room: "Sal",
fast_hold: "Fast hold", loading: "Henter holdoversigt...",
days: ['Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag', 'Søndag']
},
en: {
btn_cat: "Categories", btn_sch: "Schedule", btn_day: "Weekdays", btn_room: "Studios",
th_time: "Time", th_team: "Class", th_start: "Start Date", th_room: "Studio",
fast_hold: "Full Season", loading: "Loading schedule...",
days: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
}
},
// ----------------------------------------------------
// 3. SIKKERHED (NYT): HTML-escaping af alt data-indhold
// ----------------------------------------------------
esc: function(s) {
return String(s ?? '').replace(/[&"']/g, c => ({'&':'&','':'>','"':'"',"'":'''}[c]));
},
escUrl: function(u) {
u = String(u ?? '').trim();
return (u.startsWith('https://') || u.startsWith('http://') || u.startsWith('/')) ? this.esc(u) : '';
},
// ----------------------------------------------------
// 4. INITIALISERING
// ----------------------------------------------------
init: async function() {
// Tjek sprog fra HTML tag (WPML sætter dette)
const docLang = document.documentElement.lang.toLowerCase();
if(docLang.includes('en')) this.lang = 'en';
// Sæt starttekster på knapper
this.updateUI();
const ts = Date.now(); // Cache busting
try {
// Hent alle filer parallelt (NYT: perioder.json er med)
const [km, cust, hid, cont, trans, per] = await Promise.all([
fetch(this.basePath + 'klubmodulhold.json?v=' + ts),
fetch(this.basePath + 'customhold.json?v=' + ts),
fetch(this.basePath + 'skjulte_hold.json?v=' + ts),
fetch(this.basePath + 'content_v2.json?v=' + ts),
fetch(this.basePath + 'translations.json?v=' + ts),
fetch(this.basePath + 'perioder.json?v=' + ts)
]);
// Parse JSON
const kmData = km.ok ? await km.json() : [];
const custData = cust.ok ? await cust.json() : [];
this.hidden = hid.ok ? await hid.json() : [];
if (!Array.isArray(this.hidden)) this.hidden = [];
this.hidden = this.hidden.filter(id => id); // RETTET: tomme id'er må ikke skjule hold
this.content = cont.ok ? await cont.json() : {};
this.translations = trans.ok ? await trans.json() : {};
this.periods = per.ok ? await per.json() : [];
if (!Array.isArray(this.periods)) this.periods = [];
// Behandl data
this.processData(kmData, custData);
// Opdater UI igen (nu med oversættelser fra filen)
this.updateUI();
document.getElementById('bd-loader').style.display = 'none';
this.render();
} catch (e) {
console.error(e);
document.getElementById('bd-loader').innerHTML = 'Kunne ikke hente data. Tjek stien i scriptet eller .htaccess filen.';
}
},
// ----------------------------------------------------
// 5. OVERSÆTTELSES MOTOR
// ----------------------------------------------------
t: function(text) {
if (this.lang !== 'en') return text; // Hvis dansk, gør intet
if (!text) return '';
const cleanText = text.trim();
// 1. Tjek translations.json (Admin ordbog)
if (this.translations[cleanText]) return this.translations[cleanText];
// 2. Tjek hardcoded UI fallback
if (this.ui.en[cleanText]) return this.ui.en[cleanText];
// 3. Giv op -> vis original (dansk)
return text;
},
updateUI: function() {
const txt = this.ui[this.lang];
document.getElementById('btn-cat').innerText = this.t('Kategorier') || txt.btn_cat;
document.getElementById('btn-sch').innerText = this.t('Ugeskema') || txt.btn_sch;
document.getElementById('btn-day').innerText = this.t('Ugedage') || txt.btn_day;
const roomBtn = document.getElementById('btn-room');
if (roomBtn) roomBtn.innerText = (this.lang === 'en') ? (this.translations['Salsoversigt'] || txt.btn_room) : 'Salsoversigt';
document.getElementById('bd-loader').innerText = this.t('Henter holdoversigt...') || txt.loading;
},
// ----------------------------------------------------
// 6. DATA BEHANDLING
// ----------------------------------------------------
// NYT: Et hold kan have flere træningstider.
// Nye data: h.tider = [{dag, tid, room}, ...] | Gamle data: h.dag / h.tid / h.room
getTider: function(h) {
if (Array.isArray(h.tider) && h.tider.length > 0) return h.tider;
return [{ dag: h.dag || '', tid: h.tid || '', room: h.room || '' }];
},
// NYT: Kursusperiode-opslag (perioder.json), med oversættelse
periodLabel: function(h) {
if (h.periode_id) {
const p = this.periods.find(p => p.id === h.periode_id);
if (p) {
let label = this.esc(this.t(p.navn));
if (p.visning) label += `${this.esc(p.visning)}`;
return label;
}
}
return this.esc(this.t(h.periode || 'Fast hold'));
},
processData: function(km, cust) {
let map = {};
let occ = [];
const daDays = this.ui.da.days;
[...km, ...cust].forEach(cat => {
if (!cat || !Array.isArray(cat.hold)) return;
let name = cat.kategori_navn;
if(!map[name]) map[name] = [];
// Filtrer skjulte hold fra
let valid = cat.hold.filter(h => !this.hidden.includes(h.id));
// Sorter efter første træningstid
valid.sort((a,b) => this.parseTime(this.getTider(a)[0].tid) - this.parseTime(this.getTider(b)[0].tid));
map[name].push(...valid);
// NYT: én forekomst pr. træningstid til skema/ugedage/sale
valid.forEach(h => {
this.getTider(h).forEach(t => {
let d = String(t.dag || '').trim();
if (d.endsWith('e')) d = d.slice(0, -1);
const day = daDays.find(od => d.includes(od));
if (day) occ.push({ hold: h, category: name, day: day, tid: t.tid || '', room: (t.room || '').trim() });
});
});
});
this.data = map;
this.occ = occ;
},
parseTime: function(t) {
if(!t) return 9999;
let c = String(t).replace(/[^0-9]/g, '');
return parseInt(c.substring(0,4)) || 9999;
},
// ----------------------------------------------------
// 7. VISNINGS LOGIK
// ----------------------------------------------------
switchView: function(v, btn) {
this.view = v;
document.querySelectorAll('.bd-view-btn').forEach(b => b.classList.remove('active'));
if (btn) btn.classList.add('active'); // RETTET: brug knappen direkte i stedet for global event
this.render();
},
render: function() {
const con = document.getElementById('bd-content');
con.innerHTML = '';
if(this.view === 'kategori') this.renderCategories(con);
else if(this.view === 'skema') this.renderSchedule(con);
else if(this.view === 'sale') this.renderRooms(con);
else this.renderDayList(con);
},
// Hjælper: byg link eller span om et holdnavn (med escaping)
nameHtml: function(h, cls) {
const name = this.esc(this.t(h.holdnavn));
const url = this.escUrl(h.tilmeld_url);
if (url) return `${name}`;
return `${name}`;
},
// --- VISNING: KATEGORIER ---
renderCategories: function(con) {
const sorted = Object.keys(this.data).sort();
let html = '';
sorted.forEach(catName => {
const holds = this.data[catName];
if(holds.length === 0) return;
// Hent info fra content_v2.json
const c = (this.content.cats && this.content.cats[catName]) ? this.content.cats[catName] : {};
// Bestem tekster baseret på sprog
let displayTitle = this.t(catName);
if(this.lang === 'en' && c.title_en) displayTitle = c.title_en;
let displayDesc = c.desc_da || '';
if(this.lang === 'en') {
displayDesc = c.desc_en ? c.desc_en : this.t(c.desc_da);
}
const img = this.escUrl(c.image) || 'https://bendixendans.dk/web/wp-content/uploads/2023/02/logo-black.png';
html += `
${this.esc(displayTitle)}
${displayDesc ? `
${this.esc(displayDesc).replace(/\n/g, ' ')}
` : ''}
${this.t('Tidspunkt')}
${this.t('Hold')}
${this.t('Opstart')}
`;
holds.forEach(h => {
// NYT: vis alle træningstider - én pr. linje, med sal
const tidHtml = this.getTider(h).map(t => {
const dag = this.esc(this.t(t.dag));
const room = t.room ? `${this.esc(this.t(t.room))}` : '';
return `${dag} ${this.esc(t.tid)}${room}`;
}).join('');
html += `
${tidHtml}
${this.nameHtml(h, 'bd-hold-link')}
${this.periodLabel(h)}
`;
});
html += `
`;
// Ferie info
let holiday = (this.lang === 'en') ? this.content.holiday_en : this.content.holiday_da;
if(!holiday && this.lang === 'en') holiday = this.t(this.content.holiday_da);
if(holiday) {
html += `
* ${this.esc(holiday)}
`;
}
html += `
`;
});
con.innerHTML = html;
},
// Hjælper: kategorinavn med evt. engelsk override fra admin
catDisplayName: function(catName) {
if(this.lang === 'en' && this.content.cats && this.content.cats[catName] && this.content.cats[catName].title_en) {
return this.content.cats[catName].title_en;
}
return this.t(catName);
},
// Hjælper: byg ét ugeskema-grid ud fra en liste af forekomster
scheduleGridHtml: function(occList, showRoom) {
const daDays = this.ui.da.days;
let html = '
';
daDays.forEach((day) => {
let dayOcc = occList.filter(o => o.day === day);
dayOcc.sort((a,b) => this.parseTime(a.tid) - this.parseTime(b.tid));
html += `
${this.esc(this.t(day))}
`;
dayOcc.forEach(o => {
const h = o.hold;
let cardInner = `
${this.esc(o.tid)}${this.esc(this.t(h.holdnavn))}${this.esc(this.catDisplayName(o.category))}
${showRoom && o.room ? `${this.esc(this.t(o.room))}` : ''}
`;
const url = this.escUrl(h.tilmeld_url);
if(url) {
html += `