KRAKKEMAT Rondevoorbereider Kies de competitie, ronde, datum, aanwezigen en eventuele bye vóór de pairing.
OPGELET: meerdere simultane voorbereidingen dienen binnen hetzelfde tabblad te gebeuren via wissel van de competitie. NIET via een nieuw tabblad.
Rondegegevens Kies een competitie om de rondecontrole uit te voeren.
Status Aanwezig 0
Afwezig 0
Bye 0
Te pairen 0
Bye Bij een oneven aantal aanwezige spelers moet één speler bye krijgen. Als de competitie dit vereist, kunnen enkel bestuursleden bye krijgen.
Bye-speler Geen bye
Geen bye nodig zolang het aantal aanwezige spelers even is.
Volgende ronde Naar pairing Wis voorbereiding
✓ Actie uitgevoerd
Aanwezige spelers Nog geen aanwezige spelers aangeduid.
window.kmRoundSetupApp = (function(){ "use strict"; const app = {}; app.STORAGE_KEY = "kmRoundSetup"; app.PLAYERS_URL = "https://api.krakkemat.be/spelers-lezen.php"; app.COMPETITIONS_URL = "https://api.krakkemat.be/competities-lezen.php"; app.ROUNDS_URL = "https://api.krakkemat.be/rondes-lezen.php"; /*================================================== DATA ==================================================*/ app.players = []; app.competitions = []; app.rounds = []; app.presentPlayers = []; app.absentPlayers = []; app.byePlayer = null; /*================================================== ELEMENTEN ==================================================*/ app.el = { competition: document.getElementById("kmRoundCompetition"), round: document.getElementById("kmRoundNumber"), date: document.getElementById("kmRoundDate"), controlMessage: document.getElementById("kmRoundControlMessage"), presentCount: document.getElementById("kmRoundPresentCount"), absentCount: document.getElementById("kmRoundAbsentCount"), byeCount: document.getElementById("kmRoundByeCount"), pairingCount: document.getElementById("kmRoundPairingCount"), presentList: document.getElementById("kmRoundPresentList"), absentList: document.getElementById("kmRoundAbsentList"), byeSelect: document.getElementById("kmRoundByePlayer"), byeMessage: document.getElementById("kmRoundByeMessage"), search: document.getElementById("kmRoundPlayerSearch"), playerCount: document.getElementById("kmRoundPlayerCount"), nextRoundBtn: document.getElementById("kmNextRoundBtn"), saveBtn: document.getElementById("kmSaveRoundSetupBtn"), clearBtn: document.getElementById("kmClearRoundSetupBtn"), saveMessage: document.getElementById("kmRoundSaveMessage") }; /*================================================== LADEN ==================================================*/ app.loadPlayers = async function(){ try{ const response = await fetch( app.PLAYERS_URL, { method: "GET", cache: "no-store" } ); if(!response.ok){ throw new Error( "HTTP " + response.status ); } const data = await response.json(); if( !data || !Array.isArray(data.players) ){ throw new Error( "Ongeldige spelersdata" ); } app.players = data.players .filter(function(player){ return player.active === true; }) .slice(); return true; }catch(error){ console.error( "Rondevoorbereider: spelers laden mislukt:", error ); app.players = []; if(app.el.controlMessage){ app.el.controlMessage.textContent = "De spelerslijst kon niet geladen worden."; } return false; } }; app.loadCompetitions = async function(){ try{ const response = await fetch( app.COMPETITIONS_URL, { method: "GET", cache: "no-store" } ); if(!response.ok){ throw new Error( "HTTP " + response.status ); } const data = await response.json(); if( !data || !Array.isArray(data.competitions) ){ throw new Error( "Ongeldige competitiedata" ); } app.competitions = data.competitions.slice(); return true; }catch(error){ console.error( "Rondevoorbereider: competities laden mislukt:", error ); app.competitions = []; if(app.el.controlMessage){ app.el.controlMessage.textContent = "De competitie-instellingen konden niet geladen worden."; } return false; } }; app.loadRounds = async function(){ try{ const response = await fetch( app.ROUNDS_URL, { method: "GET", cache: "no-store" } ); if(!response.ok){ throw new Error( "HTTP " + response.status ); } const data = await response.json(); if( !data || !Array.isArray(data.rounds) ){ throw new Error( "Ongeldige rondehistoriek" ); } app.rounds = data.rounds.slice(); return true; }catch(error){ console.error( "Rondevoorbereider: rondehistoriek laden mislukt:", error ); app.rounds = []; if(app.el.controlMessage){ app.el.controlMessage.textContent = "De rondehistoriek kon niet geladen worden."; } return false; } }; /*================================================== COMPETITIES ==================================================*/ app.fillCompetitionSelect = function(){ const select = app.el.competition; select.innerHTML = 'Kies competitie... '; app.competitions .filter(function(comp){ return comp.archived === false; }) .forEach(function(comp){ const option = document.createElement("option"); option.value = comp.id; option.textContent = comp.name; select.appendChild(option); }); }; /*================================================== RONDECONTROLE ==================================================*/ app.getLastRound = function(competitionId){ let last = 0; app.rounds.forEach(function(round){ const roundCompetitionId = String( round.competitionId ?? round.c ?? "" ); const roundNumber = Number( round.round ?? round.r ?? 0 ); if( roundCompetitionId === competitionId && roundNumber > last ){ last = roundNumber; } }); return last; }; app.updateRoundControl = function(){ const competitionId = app.el.competition.value; if(!competitionId){ app.el.controlMessage.textContent = "Kies eerst een competitie."; return; } const comp = app.getCompetition(); if(!comp){ app.el.controlMessage.textContent = "Competitie niet gevonden."; return; } const historicalLast = app.getLastRound( competitionId ); const singleDay = app.isSingleDayCompetition(comp); /*================================================== LANGDURIGE COMPETITIE Enige bron voor vorige rondes: window.kmCompetitionRounds ==================================================*/ if(!singleDay){ const expected = historicalLast + 1; if(!app.readRound()){ app.writeRound( expected ); } const current = app.readRound(); if(current === expected){ app.el.controlMessage.textContent = historicalLast > 0 ? "Laatste gespeelde ronde is " + historicalLast + ". Volgende ronde is " + expected + "." : "Dit is de eerste ronde van deze competitie."; return; } if(current <= historicalLast){ app.el.controlMessage.textContent = "FOUT: ronde " + current + " bestaat reeds in de gepubliceerde historiek."; return; } app.el.controlMessage.textContent = "FOUT: volgende ronde moet ronde " + expected + " zijn."; return; } /*================================================== 1 DAG / BLITZ Hier mag de lokale voorbereiding wel mee bepalen waar de avond gebleven is. ==================================================*/ let savedRound = 0; try{ const raw = localStorage.getItem( app.STORAGE_KEY ); const saved = raw ? JSON.parse(raw) : null; if( saved && saved.competitionId === competitionId ){ savedRound = Number(saved.round) || 0; } }catch(e){} const lastKnown = Math.max( historicalLast, savedRound ); const expected = lastKnown + 1; if(!app.readRound()){ app.writeRound( expected ); } const current = app.readRound(); if( savedRound > 0 && current === savedRound ){ app.el.controlMessage.textContent = "Ronde " + current + " is de huidige ronde van deze blitz/ééndagscompetitie."; return; } if(current === expected){ app.el.controlMessage.textContent = "Volgende ronde is correct (" + expected + ")."; return; } if(current <= historicalLast){ app.el.controlMessage.textContent = "FOUT: ronde " + current + " bestaat reeds in de historiek."; return; } app.el.controlMessage.textContent = "FOUT: volgende ronde moet ronde " + expected + " zijn."; }; app.escapeHtml = function(value){ return String(value); }; return app; })();
(function(){ "use strict"; const app = window.kmRoundSetupApp; if(!app) return; /*================================================== HELPERS SPELERS ==================================================*/ app.playerName = function(player){ return [ player.lastName || "", player.firstName || "" ].join(" ").trim(); }; app.sortPlayers = function(list){ return list.slice().sort(function(a,b){ const an = String(a.lastName || "").localeCompare(String(b.lastName || ""),"nl"); if(an !== 0) return an; return String(a.firstName || "").localeCompare(String(b.firstName || ""),"nl"); }); }; app.getPlayerById = function(id){ return app.players.find(function(player){ return player.id === id; }) || null; }; app.isPresent = function(id){ return app.presentPlayers.indexOf(id) >= 0; }; app.isBye = function(id){ return app.byePlayer === id; }; app.getCompetition = function(){ const id = app.el.competition.value; return app.competitions.find(function(comp){ return comp.id === id; }) || null; }; /*================================================== RONDEVELD UITLEZEN / SCHRIJVEN ==================================================*/ app.getRoundField = function(){ const fields = Array.from( document.querySelectorAll( 'input#kmRoundNumber, input[type="number"][id="kmRoundNumber"]' ) ); /* Neem bij voorkeur het zichtbare rondeveld. */ const visibleField = fields.find(function(field){ return field.offsetParent !== null; }); /* Anders terugvallen op het oorspronkelijk gekoppelde veld. */ return ( visibleField || fields[0] || app.el.round || null ); }; app.readRound = function(){ const field = app.getRoundField(); if(!field){ return 0; } return ( Number(field.value) || 0 ); }; app.writeRound = function(value){ const field = app.getRoundField(); if(!field){ return; } field.value = value === null || value === undefined ? "" : String(value); }; /*================================================== COMPETITIETYPE ==================================================*/ app.isSingleDayCompetition = function(comp){ return !!( comp && comp.competitionMode === "singleDay" ); }; app.updateCompetitionModeUi = function(){ const comp = app.getCompetition(); const singleDay = app.isSingleDayCompetition(comp); if(!app.el.nextRoundBtn){ return; } /* Geen competitie gekozen: knop niet tonen. */ if(!comp){ app.el.nextRoundBtn.style.display = "none"; return; } /* 1 dag / blitz: Volgende ronde is beschikbaar. Langdurig: Volgende ronde bestaat functioneel niet. */ app.el.nextRoundBtn.style.display = singleDay ? "" : "none"; }; /*================================================== AANWEZIG / AFWEZIG ==================================================*/ app.setAllAbsent = function(){ app.presentPlayers = []; app.byePlayer = null; app.renderPlayers(); }; app.addPresent = function(id){ if(app.presentPlayers.indexOf(id) < 0){ app.presentPlayers.push(id); } app.renderPlayers(); }; app.removePresent = function(id){ app.presentPlayers = app.presentPlayers.filter(function(playerId){ return playerId !== id; }); if(app.byePlayer === id){ app.byePlayer = null; } app.renderPlayers(); }; /*================================================== BYE ==================================================*/ app.getByeCandidates = function(){ const comp = app.getCompetition(); let candidates = app.presentPlayers .map(app.getPlayerById) .filter(Boolean); if(comp && comp.byeBoardOnly){ candidates = candidates.filter(function(player){ return !!player.boardMember; }); } return app.sortPlayers(candidates); }; app.updateByeSelect = function(){ const select = app.el.byeSelect; const comp = app.getCompetition(); const presentCount = app.presentPlayers.length; select.innerHTML = 'Geen bye '; if(presentCount % 2 === 0){ app.byePlayer = null; app.el.byeMessage.textContent = "Geen bye nodig: het aantal aanwezige spelers is even."; select.disabled = true; return; } const candidates = app.getByeCandidates(); candidates.forEach(function(player){ const option = document.createElement("option"); option.value = player.id; option.textContent = app.playerName(player); select.appendChild(option); }); select.disabled = false; if(app.byePlayer){ const valid = candidates.some(function(player){ return player.id === app.byePlayer; }); if(!valid){ app.byePlayer = null; } } select.value = app.byePlayer || ""; if(candidates.length === 0){ app.el.byeMessage.textContent = "FOUT: er is een bye nodig, maar er is geen geldige bye-speler beschikbaar."; return; } if(comp && comp.byeBoardOnly){ app.el.byeMessage.textContent = "Er is een bye nodig. Volgens deze competitie mogen enkel bestuursleden bye krijgen."; }else{ app.el.byeMessage.textContent = "Er is een bye nodig. Kies één aanwezige speler."; } }; /*================================================== RENDER SPELERS ==================================================*/ app.createPlayerRow = function(player,type){ const isPresent = type === "present"; const row = document.createElement("div"); row.className = "km-round-player-row"; const btn = document.createElement("button"); btn.type = "button"; btn.className = isPresent ? "km-round-player-btn is-remove" : "km-round-player-btn"; btn.textContent = isPresent ? "−" : "+"; if(isPresent){ btn.setAttribute("data-remove-present",player.id); }else{ btn.setAttribute("data-add-present",player.id); } const middle = document.createElement("div"); const name = document.createElement("div"); name.className = "km-round-player-name"; name.textContent = app.playerName(player); const meta = document.createElement("div"); meta.className = "km-round-player-meta"; meta.innerHTML = player.boardMember ? "Bestuurslid" : " "; middle.appendChild(name); middle.appendChild(meta); const status = document.createElement("div"); status.className = "km-round-player-meta"; status.textContent = app.isBye(player.id) ? "Bye" : (isPresent ? "Aanwezig" : "Afwezig"); row.appendChild(btn); row.appendChild(middle); row.appendChild(status); return row; }; app.renderPresentList = function(){ app.el.presentList.innerHTML = ""; const present = app.sortPlayers( app.presentPlayers .map(app.getPlayerById) .filter(Boolean) ); if(present.length === 0){ const empty = document.createElement("div"); empty.className = "km-empty-state"; empty.textContent = "Nog geen aanwezige spelers aangeduid."; app.el.presentList.appendChild(empty); return; } present.forEach(function(player){ app.el.presentList.appendChild( app.createPlayerRow(player,"present") ); }); }; app.renderAbsentList = function(){ app.el.absentList.innerHTML = ""; const q = app.el.search.value.trim().toLowerCase(); const absent = app.sortPlayers( app.players.filter(function(player){ return !app.isPresent(player.id); }) ).filter(function(player){ if(!q) return true; return app.playerName(player).toLowerCase().includes(q); }); app.el.playerCount.textContent = absent.length === 1 ? "1 afwezige speler" : absent.length + " afwezige spelers"; if(absent.length === 0){ const empty = document.createElement("div"); empty.className = "km-empty-state"; empty.textContent = "Geen afwezige spelers gevonden."; app.el.absentList.appendChild(empty); return; } let currentLetter = ""; absent.forEach(function(player){ const rawLetter = String(player.lastName || player.firstName || "?").trim(); const letter = rawLetter ? rawLetter.charAt(0).toUpperCase() : "?"; if(letter !== currentLetter){ currentLetter = letter; const letterEl = document.createElement("div"); letterEl.className = "km-round-letter"; letterEl.textContent = letter; app.el.absentList.appendChild(letterEl); } app.el.absentList.appendChild( app.createPlayerRow(player,"absent") ); }); }; app.updateCounts = function(){ const presentCount = app.presentPlayers.length; const absentCount = app.players.length - presentCount; const byeCount = app.byePlayer ? 1 : 0; const pairingCount = presentCount - byeCount; app.el.presentCount.textContent = presentCount; app.el.absentCount.textContent = absentCount; app.el.byeCount.textContent = byeCount; app.el.pairingCount.textContent = pairingCount; /*---------------------------------- VISUELE PAIRINGSTATUS ----------------------------------*/ const pairingCard = app.el.pairingCount ? app.el.pairingCount.closest( ".km-round-status-card" ) : null; if(pairingCard){ pairingCard.classList.remove( "is-pairing-ok", "is-pairing-error" ); /* Nog geen spelers: neutrale toestand. */ if(presentCount === 0){ return; } /* Geldig: minstens twee spelers te pairen en een even aantal. */ if( pairingCount >= 2 && pairingCount % 2 === 0 ){ pairingCard.classList.add( "is-pairing-ok" ); return; } /* Oneven / nog geen geldige bye: actie vereist. */ pairingCard.classList.add( "is-pairing-error" ); } }; app.renderPlayers = function(){ app.updateByeSelect(); app.renderPresentList(); app.renderAbsentList(); app.updateCounts(); }; /*================================================== EVENTS SPELERS ==================================================*/ app.bindPlayerEvents = function(){ app.el.absentList.addEventListener("click",function(e){ const btn = e.target.closest("[data-add-present]"); if(!btn) return; app.addPresent(btn.getAttribute("data-add-present")); }); app.el.presentList.addEventListener("click",function(e){ const btn = e.target.closest("[data-remove-present]"); if(!btn) return; app.removePresent(btn.getAttribute("data-remove-present")); }); app.el.byeSelect.addEventListener("change",function(){ app.byePlayer = app.el.byeSelect.value || null; app.renderPlayers(); }); app.el.search.addEventListener("input",function(){ app.renderAbsentList(); }); }; })();
(function(){ "use strict"; const app = window.kmRoundSetupApp; if(!app) return; /*================================================== AANWEZIGHEDEN PER COMPETITIE ==================================================*/ /* Elke competitie krijgt zijn eigen lijst. Bijvoorbeeld: { "club-26-27": ["12","18","25"], "vrij-26-27": ["7","25"] } Een speler kan dus in beide competities aanwezig zijn, maar dat gebeurt uitsluitend wanneer hij in beide lijsten afzonderlijk werd aangeduid. */ app.presentPlayersByCompetition = {}; app.currentCompetitionId = null; /*================================================== AANWEZIGHEID OPSLAAN ==================================================*/ app.saveCurrentCompetitionAttendance = function(){ const competitionId = app.currentCompetitionId || app.el.competition.value; if(!competitionId){ return; } app.presentPlayersByCompetition[ competitionId ] = (app.presentPlayers || []).slice(); }; /*================================================== AANWEZIGHEID LADEN ==================================================*/ app.loadCompetitionAttendance = function(competitionId){ if(!competitionId){ app.presentPlayers = []; return; } const saved = app.presentPlayersByCompetition[ competitionId ]; app.presentPlayers = Array.isArray(saved) ? saved.slice() : []; }; /*================================================== DATUM UITLEZEN ==================================================*/ app.readDate = function(){ const fields = Array.from( document.querySelectorAll( 'input#kmRoundDate, input[type="date"][id="kmRoundDate"]' ) ); const visibleField = fields.find(function(field){ return ( field.value && field.offsetParent !== null ); }); const filledField = visibleField || fields.find(function(field){ return !!field.value; }); const field = filledField || app.el.date; return field ? String(field.value || "").trim() : ""; }; /*================================================== RONDENUMMER UITLEZEN ==================================================*/ app.readRound = function(){ const fields = Array.from( document.querySelectorAll( 'input#kmRoundNumber, input[type="number"][id="kmRoundNumber"]' ) ); const visibleField = fields.find(function(field){ return ( field.value && field.offsetParent !== null ); }); const filledField = visibleField || fields.find(function(field){ return !!field.value; }); const field = filledField || app.el.round; return field ? Number(field.value) || 0 : 0; }; /*================================================== SPELERSFILTER PER COMPETITIE ==================================================*/ app.applyPlayerGroupFilter = function(){ const comp = app.getCompetition(); const source = app.allActivePlayers || app.players || []; let filtered = source.slice(); /* Geen playerGroup: oude werking behouden. C: actieve spelers met competition:true. V: actieve spelers met freeChess:true. */ if( comp && comp.playerGroup === "C" ){ filtered = source.filter(function(player){ return player.competition === true; }); }else if( comp && comp.playerGroup === "V" ){ filtered = source.filter(function(player){ return player.freeChess === true; }); } app.players = filtered; /* Laad de aanwezigheid die specifiek bij deze competitie hoort. NOOIT de aanwezigheid van een andere competitie overnemen. */ if(comp){ app.loadCompetitionAttendance( comp.id ); }else{ app.presentPlayers = []; } /* Alleen spelers die momenteel zichtbaar zijn mogen in presentPlayers staan. */ const allowedIds = new Set( filtered.map(function(player){ return String(player.id); }) ); app.presentPlayers = app.presentPlayers.filter(function(id){ return allowedIds.has( String(id) ); }); /* Een bye hoort eveneens bij de huidige competitie. */ if( app.byePlayer !== null && app.byePlayer !== undefined ){ if( !allowedIds.has( String(app.byePlayer) ) ){ app.byePlayer = null; } } }; /*================================================== AANWEZIGHEID OVERSCHRIJVEN ==================================================*/ /* JS 2 beheert de gewone kliklogica. We vervangen alleen de twee functies zodat de gewijzigde lijst ook in de juiste competitie wordt opgeslagen. */ app.addPresent = function(id){ if( app.presentPlayers.indexOf(id) < 0 ){ app.presentPlayers.push(id); } app.saveCurrentCompetitionAttendance(); app.renderPlayers(); }; app.removePresent = function(id){ app.presentPlayers = app.presentPlayers.filter(function(playerId){ return playerId !== id; }); if( app.byePlayer === id ){ app.byePlayer = null; } app.saveCurrentCompetitionAttendance(); app.renderPlayers(); }; /*================================================== VALIDATIE ==================================================*/ app.validate = function(){ const comp = app.getCompetition(); if(!comp){ alert( "Kies eerst een competitie." ); return false; } if(!app.readRound()){ alert( "Vul een ronde in." ); return false; } const selectedDate = app.readDate(); if(!selectedDate){ alert( "Vul een datum in." ); return false; } const historicalLast = app.getLastRound( comp.id ); const current = app.readRound(); const singleDay = app.isSingleDayCompetition( comp ); /*---------------------------------- LANGDURIG ----------------------------------*/ if(!singleDay){ const expected = historicalLast + 1; if(current !== expected){ alert( "Deze ronde sluit niet aan op de gepubliceerde historiek. " + "Verwachte ronde: " + expected + "." ); return false; } /*---------------------------------- 1 DAG / BLITZ ----------------------------------*/ }else{ let savedRound = 0; try{ const raw = localStorage.getItem( app.STORAGE_KEY ); const saved = raw ? JSON.parse(raw) : null; if( saved && saved.competitionId === comp.id ){ savedRound = Number(saved.round) || 0; } }catch(e){} const expected = Math.max( historicalLast, savedRound ) + 1; const isSavedPreparation = savedRound > 0 && current === savedRound; const isNextRound = current === expected; if( !isSavedPreparation && !isNextRound ){ alert( "Deze ronde sluit niet aan op de blitzhistoriek. " + "Verwachte ronde: " + expected + "." ); return false; } } if( app.presentPlayers.length < 2 ){ alert( "Duid minstens twee aanwezige spelers aan." ); return false; } if( app.presentPlayers.length % 2 === 1 && !app.byePlayer ){ alert( "Er is een oneven aantal aanwezigen. Kies eerst een bye-speler." ); return false; } const pairingCount = app.presentPlayers.length - ( app.byePlayer ? 1 : 0 ); if(pairingCount < 2){ alert( "Er blijven te weinig spelers over om te pairen." ); return false; } if(pairingCount % 2 !== 0){ alert( "Het aantal te pairen spelers moet even zijn." ); return false; } if(app.byePlayer){ const bye = app.getPlayerById( app.byePlayer ); if(!bye){ alert( "De gekozen bye-speler bestaat niet meer." ); return false; } if( app.presentPlayers.indexOf( app.byePlayer ) < 0 ){ alert( "De gekozen bye-speler is niet aanwezig." ); return false; } if( comp.byeBoardOnly && !bye.boardMember ){ alert( "Volgens deze competitie mag enkel een bestuurslid bye krijgen." ); return false; } } return true; }; /*================================================== BEWAREN ==================================================*/ app.getPairingPlayers = function(){ return app.presentPlayers.filter( function(id){ return id !== app.byePlayer; } ); }; app.buildSetup = function(){ /* Voor het opslaan nog één keer expliciet de huidige competitie bijwerken. */ app.saveCurrentCompetitionAttendance(); return { competitionId: app.el.competition.value, round: app.readRound(), date: app.readDate(), presentPlayers: app.presentPlayers.slice(), absentPlayers: app.players .filter(function(player){ return ( app.presentPlayers.indexOf( player.id ) < 0 ); }) .map(function(player){ return player.id; }), byePlayer: app.byePlayer, pairingPlayers: app.getPairingPlayers() }; }; app.saveSetup = function(){ if(!app.validate()) return; const setup = app.buildSetup(); localStorage.setItem( app.STORAGE_KEY, JSON.stringify(setup) ); window.dispatchEvent( new CustomEvent( "km:round-setup-saved", { detail:setup } ) ); const pairingWindow = window.open( window.location.origin + window.location.pathname + "#pairing", "_blank" ); if(!pairingWindow){ alert( "Het pairingtabblad kon niet worden geopend. Sta pop-ups toe voor deze website." ); } }; /*================================================== LOKALE VOORBEREIDING LADEN ==================================================*/ app.loadSetup = function(){ try{ const raw = localStorage.getItem( app.STORAGE_KEY ); if(!raw) return; const setup = JSON.parse(raw); if( !setup || typeof setup !== "object" ){ return; } const comp = app.competitions.find( function(item){ return ( item.id === setup.competitionId ); } ); if(!comp){ return; } app.el.competition.value = setup.competitionId || ""; if(setup.round){ app.writeRound( setup.round ); } if(setup.date){ app.el.date.value = setup.date; } /* De opgeslagen aanwezigheid hoort uitsluitend bij deze competitie. */ if( Array.isArray( setup.presentPlayers ) ){ app.presentPlayers = setup.presentPlayers.slice(); app.presentPlayersByCompetition[ setup.competitionId ] = setup.presentPlayers.slice(); } if( setup.byePlayer && app.getPlayerById( setup.byePlayer ) ){ app.byePlayer = setup.byePlayer; } }catch(e){} }; /*================================================== WISSEN ==================================================*/ app.clearSetup = function(){ if( !confirm( "Rondevoorbereiding wissen?" ) ){ return; } localStorage.removeItem( app.STORAGE_KEY ); app.presentPlayersByCompetition = {}; app.presentPlayers = []; app.currentCompetitionId = null; app.byePlayer = null; app.el.competition.value = ""; app.writeRound( "" ); app.el.date.value = ""; app.updateCompetitionModeUi(); app.updateRoundControl(); app.renderPlayers(); }; /*================================================== VOLGENDE RONDE ==================================================*/ app.startNextRound = function(){ const comp = app.getCompetition(); if(!comp){ alert( "Kies eerst een competitie." ); return; } const historicalLast = app.getLastRound( comp.id ); let savedRound = 0; try{ const raw = localStorage.getItem( app.STORAGE_KEY ); const saved = raw ? JSON.parse(raw) : null; if( saved && saved.competitionId === comp.id ){ savedRound = Number(saved.round) || 0; } }catch(e){} const nextRound = Math.max( historicalLast, savedRound, app.readRound() ) + 1; const maxRounds = Number( comp.maxRounds ); if( Number.isFinite(maxRounds) && maxRounds > 0 && nextRound > maxRounds ){ alert( "Deze competitie bestaat uit maximaal " + maxRounds + " rondes." ); return; } app.writeRound( nextRound ); app.byePlayer = null; app.el.search.value = ""; app.updateRoundControl(); app.renderPlayers(); const setup = app.buildSetup(); localStorage.setItem( app.STORAGE_KEY, JSON.stringify(setup) ); app.el.saveMessage.textContent = "✓ Ronde " + nextRound + " is voorbereid"; app.el.saveMessage.classList.add( "show" ); setTimeout(function(){ app.el.saveMessage.classList.remove( "show" ); },1800); const roundSection = app.el.round.closest( ".km-section" ); if(roundSection){ roundSection.scrollIntoView({ behavior:"smooth", block:"start" }); } }; /*================================================== EVENTS ALGEMEEN ==================================================*/ app.bindMainEvents = function(){ app.el.competition.addEventListener( "change", function(){ /* Eerst de aanwezigheid van de competitie waarvan we vertrekken bewaren. */ if( app.currentCompetitionId ){ app.saveCurrentCompetitionAttendance(); } /* Nieuwe competitie bepalen. */ const comp = app.getCompetition(); app.currentCompetitionId = comp ? comp.id : null; app.writeRound(""); app.updateCompetitionModeUi(); app.el.search.value = ""; /* Filter én daarna de afzonderlijke aanwezigheidslijst van de nieuwe competitie laden. */ app.applyPlayerGroupFilter(); app.updateRoundControl(); app.renderPlayers(); } ); document.addEventListener( "input", function(event){ if( event.target && event.target.id === "kmRoundNumber" ){ app.updateRoundControl(); } } ); app.el.nextRoundBtn.addEventListener( "click", app.startNextRound ); app.el.saveBtn.addEventListener( "click", app.saveSetup ); app.el.clearBtn.addEventListener( "click", app.clearSetup ); }; /*================================================== INIT ==================================================*/ app.init = async function(){ localStorage.removeItem( app.STORAGE_KEY ); const playersLoaded = await app.loadPlayers(); if(!playersLoaded){ console.error( "Rondevoorbereider: spelerslijst kon niet geladen worden." ); return; } /* Volledige actieve spelerslijst als vaste bron bewaren. */ app.allActivePlayers = app.players.slice(); const competitionsLoaded = await app.loadCompetitions(); if(!competitionsLoaded){ console.error( "Rondevoorbereider: competitie-instellingen konden niet geladen worden." ); return; } const roundsLoaded = await app.loadRounds(); if(!roundsLoaded){ console.error( "Rondevoorbereider: rondehistoriek kon niet geladen worden." ); return; } app.fillCompetitionSelect(); /* Bestaande lokale voorbereiding laden. */ app.loadSetup(); /* Als loadSetup een competitie heeft geladen, onthouden we dat dit de huidige competitie is. */ if(app.el.competition.value){ app.currentCompetitionId = app.el.competition.value; } /* Nu pas de juiste C/V-spelersgroep én de eigen aanwezigheid van die competitie laden. */ app.applyPlayerGroupFilter(); app.updateCompetitionModeUi(); app.updateRoundControl(); app.renderPlayers(); app.bindPlayerEvents(); app.bindMainEvents(); }; app.init(); })();