async function toggleInnings(mid) { const snap = await rtdb.ref(`playpulse/matches/${mid}`).once("value"); const m = snap.val(); if (!m) return; const nextInnings = m.currentInnings === 'B' ? 'A' : 'B'; await rtdb.ref(`playpulse/matches/${mid}`).update({ currentInnings: nextInnings, updatedBy: currentUser.uid, updatedAt: new Date().toISOString() }); } async function finishChess(mid, res) { const dec = decodeURIComponent(res); const isDraw = dec === "Draw"; await rtdb.ref(`playpulse/matches/${mid}`).update({ result: isDraw ? "1/2-1/2" : `${dec} Won`, winner: isDraw ? null : dec, winPts: isDraw ? 1 : 2, lossPts: isDraw ? 1 : 0, status: "completed", verificationStatus: "PENDING", updatedBy: currentUser.uid, updatedAt: new Date().toISOString() }); toast(`Chess Result: ${dec}`); } async function finishLudo(mid, name) { const snap = await rtdb.ref(`playpulse/matches/${mid}`).once("value"); const m = snap.val(); if (!m) return; const dec = decodeURIComponent(name); const lp = m.lp || []; if (!lp.includes(dec)) lp.push(dec); const updates = { lp: lp, updatedBy: currentUser.uid, updatedAt: new Date().toISOString() }; if (lp.length === 1) updates.winner = dec; if (lp.length >= 2) { updates.status = "completed"; updates.verificationStatus = "PENDING"; } await rtdb.ref(`playpulse/matches/${mid}`).update(updates); } async function undoScore(mid) { const snap = await rtdb.ref(`playpulse/matches/${mid}`).once("value"); const m = snap.val(); if (m?.history?.length) { const history = [...m.history]; const prev = history.pop(); await rtdb.ref(`playpulse/matches/${mid}`).update({ ...prev, history: history, updatedBy: currentUser.uid, updatedAt: new Date().toISOString() }); toast("Reverted last action"); } else { toast("Nothing to undo"); } } async function setMatchStatus(mid, status) { await rtdb.ref(`playpulse/matches/${mid}`).update({ status: status, updatedBy: currentUser.uid, updatedAt: new Date().toISOString() }); } async function updateWinner(mid, val) { await rtdb.ref(`playpulse/matches/${mid}`).update({ winner: val || null, status: val ? "completed" : "live", verificationStatus: "PENDING", updatedBy: currentUser.uid, updatedAt: new Date().toISOString() }); toast("Winner recorded."); } function updatePointsRule(mid) { const winPts = Number(document.getElementById(`winPtsInput_${mid}`).value); const lossPts = Number(document.getElementById(`lossPtsInput_${mid}`).value); rtdb.ref(`playpulse/matches/${mid}`).update({ winPts, lossPts }); toast("Points rule updated"); } async function verifyMatch(mid, status) { await rtdb.ref(`playpulse/matches/${mid}`).update({ verificationStatus: status, verifiedBy: currentUser.uid, updatedBy: currentUser.uid, updatedAt: new Date().toISOString() }); toast(`Match result ${status}`); } // --- ADMIN PANEL & SCHEDULER --- async function admin() { const container = getApp(); if (!container) return; const tourSnap = await rtdb.ref("playpulse/tournaments").once("value"); const tVal = tourSnap.val(); tournamentsCache = tVal ? Object.entries(tVal).map(([k, v]) => ({ ...v, id: k })) : []; const usersSnap = await rtdb.ref("playpulse/users").once("value"); const uVal = usersSnap.val(); usersCache = uVal ? Object.entries(uVal).map(([k, v]) => ({ ...v, id: k })) : []; container.innerHTML = `

Admin Control Panel

Create Tournament

Schedule Fixture (Singles & Doubles)

${!tournamentsCache.length ? '

Create a tournament above first.

' : `
`}
${isSuperAdmin() ? `

๐Ÿ‘‘ User Roles (Super Admin)

${usersCache.map(u => ` `).join("")}
UserEmailRoleAction
${esc(u.displayName)} ${esc(u.email)} ${u.role}
` : ''}
`; } function toggleDoublesInputs(val) { const isDoubles = val === 'doubles'; const a2 = document.getElementById("doublesA2Field"); const b2 = document.getElementById("doublesB2Field"); if (a2) a2.style.display = isDoubles ? "block" : "none"; if (b2) b2.style.display = isDoubles ? "block" : "none"; } function toggleDoublesField(tid) { const sel = document.getElementById("tourSelect"); const opt = sel.options[sel.selectedIndex]; const sport = opt.getAttribute("data-sport"); const mt = document.getElementById("matchTypeSelect"); if (mt && (sport !== "badminton" && sport !== "carrom")) { mt.value = "singles"; toggleDoublesInputs("singles"); } } async function assignUserRole(targetUid, newRole) { if (!isSuperAdmin()) return toast("Unauthorized"); await rtdb.ref(`playpulse/users/${targetUid}`).update({ role: newRole, updatedAt: new Date().toISOString() }); toast("Role assigned."); } async function handleCreateTournament(e) { e.preventDefault(); const f = new FormData(e.target); const newId = id(); await rtdb.ref(`playpulse/tournaments/${newId}`).set({ name: f.get("name"), sport: f.get("sport"), date: f.get("date"), createdBy: currentUser.uid, updatedBy: currentUser.uid, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }); e.target.reset(); toast("Tournament created."); admin(); } async function handleCreateMatch(e) { e.preventDefault(); const f = new FormData(e.target); const t = tournamentsCache.find(x => x.id === f.get("tid")); if (!t) return toast("Tournament not found"); const newId = id(); const matchRecord = { tournamentId: t.id, sport: t.sport, matchType: f.get("matchType") || "singles", a: f.get("a"), a2: f.get("a2") || null, b: f.get("b"), b2: f.get("b2") || null, venue: f.get("venue"), assignedScorerId: f.get("assignedScorerId") || null, assignedOfficialId: f.get("assignedOfficialId") || null, status: "scheduled", verificationStatus: "PENDING", winPts: 2, lossPts: 0, winner: null, runsA: 0, runsB: 0, wkA: 0, wkB: 0, ballsA: 0, ballsB: 0, sa: 0, sb: 0, carromBoards: [], lastCover: null, ga: 0, gb: 0, currentSet: 1, setsWonA: 0, setsWonB: 0, b_set1: null, b_set2: null, b_set3: null, result: null, lp: [], history: [], createdBy: currentUser.uid, updatedBy: currentUser.uid, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; await rtdb.ref(`playpulse/matches/${newId}`).set(matchRecord); e.target.reset(); toast("Fixture scheduled."); } async function delTournament(tid) { if (!confirm("Delete tournament?")) return; await rtdb.ref(`playpulse/tournaments/${tid}`).remove(); toast("Tournament removed."); tournaments(); } // --- AUTHENTICATION VIEWS --- function loginView(redirectHash = "#/") { const container = getApp(); if (!container) return; container.innerHTML = `

๐Ÿ” Secure Portal Login

Sign in with verified credentials.

Need an account? Register
`; } function registerView() { const container = getApp(); if (!container) return; container.innerHTML = `

๐Ÿ“ Register Portal Account

Account will be initialized as PENDING until approved by Super Admin.

Already registered? Sign In
`; } async function handleLogin(e, redirectHash) { e.preventDefault(); const f = new FormData(e.target); try { const cred = await auth.signInWithEmailAndPassword(f.get("email"), f.get("password")); toast("Welcome back!"); location.hash = redirectHash; } catch (err) { toast(err.message); } } async function handleRegister(e) { e.preventDefault(); const f = new FormData(e.target); const email = f.get("email"); const password = f.get("password"); const displayName = f.get("displayName"); try { const cred = await auth.createUserWithEmailAndPassword(email, password); await cred.user.updateProfile({ displayName }); await rtdb.ref(`playpulse/users/${cred.user.uid}`).set({ uid: cred.user.uid, email: email, displayName: displayName, role: ROLES.PENDING, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }); toast("Registration submitted."); location.hash = "#/"; } catch (err) { toast(err.message); } } function logout() { auth.signOut().then(() => { toast("Signed out."); location.hash = "#/"; }); } window.addEventListener("DOMContentLoaded", render); window.addEventListener("hashchange", render);