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)}

${this.esc(displayTitle)}

${displayDesc ? `

${this.esc(displayDesc).replace(/\n/g, '
')}

` : ''}
`; 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 += ``; }); html += `
${this.t('Tidspunkt')} ${this.t('Hold')} ${this.t('Opstart')}
${tidHtml} ${this.nameHtml(h, 'bd-hold-link')} ${this.periodLabel(h)}
`; // 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 += `
${cardInner}
`; } else { html += `
${cardInner}
`; } }); html += `
`; }); html += '
'; return html; }, // --- VISNING: UGESKEMA --- renderSchedule: function(con) { con.innerHTML = this.scheduleGridHtml(this.occ, true); }, // --- NYT - VISNING: SALSOVERSIGT (ugeskema pr. sal) --- renderRooms: function(con) { const rooms = [...new Set(this.occ.map(o => o.room).filter(r => r))].sort(); if (rooms.length === 0) { con.innerHTML = '
' + this.esc(this.lang === 'en' ? 'No studio information available yet.' : 'Der er ikke angivet sale på holdene endnu.') + '
'; return; } let html = ''; rooms.forEach(room => { const roomOcc = this.occ.filter(o => o.room === room); html += `

${this.esc(this.t(room))}

${this.scheduleGridHtml(roomOcc, false)}
`; }); con.innerHTML = html; }, // --- VISNING: UGEDAGE --- renderDayList: function(con) { let html = ''; const daDays = this.ui.da.days; const txt = this.ui[this.lang]; daDays.forEach((day) => { let dayOcc = this.occ.filter(o => o.day === day); if(dayOcc.length === 0) return; dayOcc.sort((a,b) => this.parseTime(a.tid) - this.parseTime(b.tid)); html += `

${this.esc(this.t(day))}

`; html += `
`; dayOcc.forEach(o => { const h = o.hold; html += ``; }); html += `
${this.t('Tidspunkt')} ${this.t('Hold')} ${this.lang === 'en' ? (this.translations['Sal'] || txt.th_room) : 'Sal'} ${this.t('Kategorier')}
${this.esc(o.tid)} ${this.nameHtml(h, 'bd-hold-link')} ${this.esc(this.t(o.room || '-'))} ${this.esc(this.catDisplayName(o.category))}
`; }); con.innerHTML = html; } }; // Start bdApp.init();