1// Wrap in IIFE to avoid global namespace pollution 2(function() { 3'use strict'; 4 5// ========== CONFIG ========== 6const API_BASE_URL = 'https://api.1gom.kim'; 7const TOP_LEAGUE_CODES = ['ENG1', 'ITA1', 'GER1', 'SPA1', 'FRA1']; 8 9// ========== STATE ========== 10const state = { 11 leagues: [], // { id, name, code, flag, seasonYear, standings } 12 selectedLeagueId: null, 13 isLoading: false 14}; 15 16// ========== DOM ELEMENTS ========== 17const elements = { 18 loading: document.getElementById('sidebarLoading'), 19 leagueSelect: document.getElementById('leagueSelect'), 20 standingsTable: document.getElementById('standingsTable'), 21 container: document.getElementById('sidebarBxhContainer') 22}; 23 24// ========== UTILITIES ========== 25function getLeagueName(code) { 26 const names = { 27 'ENG1': 'Ngoại hạng Anh', 28 'ITA1': 'Serie A', 29 'GER1': 'Bundesliga', 30 'SPA1': 'La Liga', 31 'FRA1': 'Ligue 1' 32 }; 33 return names[code] || code; 34} 35 36function getRankColor(position) { 37 if (position === 1) return '#5793fc'; 38 if (position === 2) return '#5793fc'; 39 if (position === 3) return '#5793fc'; 40 return '#94a3b8'; 41} 42 43function getRowBackground(position) { 44 if (position === 1) return '#f0f9ff'; 45 if (position % 2 === 0) return '#f8fafc'; 46 return '#ffffff'; 47} 48 49// ========== API CALLS ========== 50async function fetchCompetitions() { 51 try { 52 // Build query params like CustomCardRank.tsx 53 const params = new URLSearchParams({ 54 limit: 5, 55 page: 1, 56 'populate[country]': '*', 57 'condition[tier][$lte]': 10, 58 'sort[tier]': 'asc', 59 'sort[seq]': 'asc', 60 'sort[updatedAt]': 'desc' 61 }); 62 63 const response = await fetch(`${API_BASE_URL}/competitions?${params}`); 64 const data = await response.json(); 65 66 console.log('ð¦ Competitions response:', data); 67 68 if (data.success && data.data?.items) { 69 // Filter chá» lấy top 5 leagues theo code 70 const topLeagues = data.data.items.filter(comp => 71 TOP_LEAGUE_CODES.includes(comp.code) 72 ); 73 74 return topLeagues.map(comp => ({ 75 id: comp._id, 76 name: getLeagueName(comp.code), 77 code: comp.code, 78 flag: comp.country?.flag || '', 79 seasonYear: comp.seasons?.[0]?.year || new Date().getFullYear() 80 })); 81 } 82 return []; 83 } catch (error) { 84 console.error('Error fetching competitions:', error); 85 return []; 86 } 87} 88 89async function fetchStandings(competitionId, seasonYear) { 90 try { 91 const params = new URLSearchParams({ 92 competitionId, 93 seasonYear, 94 type: 'all' 95 }); 96 97 const response = await fetch(`${API_BASE_URL}/standings/overall?${params}`); 98 const data = await response.json(); 99 100 console.log('ð Standings response:', { competitionId, data }); 101 102 if (data.success && data.data) { 103 if (Array.isArray(data.data)) { 104 return data.data; 105 } else if (data.data.items && Array.isArray(data.data.items)) { 106 return data.data.items; 107 } 108 } 109 return []; 110 } catch (error) { 111 console.error('Error fetching standings:', error); 112 return []; 113 } 114} 115 116// ========== RENDERING ========== 117function showLoading() { 118 if (elements.loading) elements.loading.style.display = 'flex'; 119 if (elements.standingsTable) elements.standingsTable.style.display = 'none'; 120} 121 122function hideLoading() { 123 if (elements.loading) elements.loading.style.display = 'none'; 124 if (elements.standingsTable) elements.standingsTable.style.display = 'block'; 125} 126 127function renderLeagueSelect() { 128 if (!elements.leagueSelect || state.leagues.length === 0) return; 129 130 const selectedLeague = state.leagues.find(l => l.id === state.selectedLeagueId) || state.leagues[0]; 131 132 elements.leagueSelect.innerHTML = ` 133 <button class="sidebar-league-select-btn" id="leagueSelectBtn"> 134 <div class="sidebar-league-select-content"> 135 ${selectedLeague.flag ? ` 136 <img src="${selectedLeague.flag}" 137 alt="${selectedLeague.name}" 138 class="sidebar-league-flag"> 139 ` : ''}
140 <span class="sidebar-league-name">${selectedLeague.name}</span> 141 </div> 142 <svg class="sidebar-select-arrow" width="12" height="12" viewBox="0 0 12 12" fill="none"> 143 <path d="M2.5 4.5L6 8L9.5 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> 144 </svg> 145 </button> 146 <div class="sidebar-league-dropdown" id="leagueDropdown" style="display: none;"> 147 ${state.leagues.map(league => ` 148 <div class="sidebar-league-option ${league.id === state.selectedLeagueId ? 'active' : ''}" 149 data-league-id="${league.id}"> 150 ${league.flag ? ` 151 <img src="${league.flag}" 152 alt="${league.name}" 153 class="sidebar-league-flag"> 154 ` : ''} 155 <span>${league.name}</span> 156 </div> 157 `).join('')} 158 </div> 159 `; 160 161 // Add click handlers 162 const btn = document.getElementById('leagueSelectBtn'); 163 const dropdown = document.getElementById('leagueDropdown'); 164 165 if (btn && dropdown) { 166 btn.addEventListener('click', (e) => { 167 e.stopPropagation(); 168 dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none'; 169 }); 170 171 document.addEventListener('click', () => { 172 dropdown.style.display = 'none'; 173 }); 174 175 dropdown.querySelectorAll('.sidebar-league-option').forEach(option => { 176 option.addEventListener('click', (e) => { 177 e.stopPropagation(); 178 const leagueId = option.getAttribute('data-league-id'); 179 state.selectedLeagueId = leagueId; 180 dropdown.style.display = 'none'; 181 renderLeagueSelect(); 182 renderStandings(); 183 }); 184 }); 185 } 186} 187
188function renderStandings() { 189 if (!elements.standingsTable) return; 190 191 const selectedLeague = state.leagues.find(l => l.id === state.selectedLeagueId); 192 if (!selectedLeague || !selectedLeague.standings) { 193 elements.standingsTable.innerHTML = '<p class="sidebar-empty">Không có dữ liá»u</p>'; 194 return; 195 } 196 197 const standings = selectedLeague.standings; 198 199 if (!Array.isArray(standings) || standings.length === 0) { 200 elements.standingsTable.innerHTML = '<p class="sidebar-empty">Không có dữ liá»u bảng xếp hạng</p>'; 201 return; 202 } 203 204 // Debug first team 205 if (standings.length > 0) { 206 console.log('ð¥ First team structure:', standings[0]); 207 } 208 209 elements.standingsTable.innerHTML = ` 210 <!-- Table Header --> 211 <div class="sidebar-table-header"> 212 <div class="sidebar-col-rank">#</div> 213 <div class="sidebar-col-team">Äá»i</div> 214 <div class="sidebar-col-stat">Tráºn</div> 215 <div class="sidebar-col-stat">HS</div> 216 <div class="sidebar-col-points">Äiá»m</div> 217 </div> 218 219 <!-- Table Body --> 220 ${standings.map((team, index) => { 221 const position = index + 1; 222 const bgColor = getRowBackground(position); 223 const rankColor = getRankColor(position); 224 225 return ` 226 <div class="sidebar-table-row" style="background: ${bgColor};"> 227 <div class="sidebar-col-rank"> 228 <span class="sidebar-rank-badge" style="background: ${rankColor};"> 229 ${position} 230 </span> 231 </div> 232 <div class="sidebar-col-team"> 233 ${team.team?.image ? ` 234 <img src="${team.team.image}" 235 alt="${team.team.name || team.team.nameAlt || ''}" 236 class="sidebar-team-logo" 237 onerror="this.style.display='none'"> 238 ` : ''} 239 <span class="sidebar-team-name">${team.team?.name || team.team?.nameAlt || team.nameAlt || team.name || 'Unknown'}</span> 240 </div> 241 <div class="sidebar-col-stat">${team.all?.played || team.played || 0}</div> 242 <div class="sidebar-col-stat">${(team.all?.goalsDiff !== undefined ? team.all.goalsDiff : (team.goalsDiff || 0)) >= 0 ? '+' : ''}${team.all?.goalsDiff !== undefined ? team.all.goalsDiff : (team.goalsDiff || 0)}</div> 243 <div class="sidebar-col-points"> 244 <span class="sidebar-points-badge" style="background: ${rankColor};"> 245 ${team.all?.points || team.points || 0} 246 </span> 247 </div> 248 </div> 249 `; 250 }).join('')} 251 `; 252} 253 254// ========== INITIALIZATION ========== 255async function loadData() { 256 if (state.isLoading) return; 257 258 try { 259 state.isLoading = true; 260 showLoading(); 261 262 // 1. Fetch competitions (như CustomCardRank.tsx) 263 const leagues = await fetchCompetitions(); 264 265 if (leagues.length === 0) { 266 console.error('No leagues found'); 267 elements.standingsTable.innerHTML = '<p class="sidebar-empty">Không tìm thấy giải Äấu</p>'; 268 hideLoading(); 269 return; 270 } 271 272 console.log('â Fetched leagues:', leagues); 273 274 // 2. Fetch standings for all leagues in parallel 275 const standingsPromises = leagues.map(league => 276 fetchStandings(league.id, league.seasonYear) 277 ); 278 const standingsResults = await Promise.all(standingsPromises); 279 280 // 3. Map standings to leagues 281 state.leagues = leagues.map((league, index) => ({ 282 ...league, 283 standings: standingsResults[index] 284 })); 285 286 // 4. Select first league by default 287 state.selectedLeagueId = state.leagues[0].id; 288 289 // 5. Render UI 290 renderLeagueSelect(); 291 renderStandings(); 292 293 } catch (error) { 294 console.error('Error loading sidebar standings:', error); 295 elements.standingsTable.innerHTML = '<p class="sidebar-empty">Có lá»i xảy ra khi tải dữ liá»u</p>'; 296 } finally { 297 state.isLoading = false;
298 hideLoading(); 299 } 300} 301 302// ========== START ========== 303function init() { 304 if (!elements.container) { 305 console.error('Sidebar BXH container not found'); 306 return; 307 } 308 309 loadData(); 310} 311 312// Auto-initialize when DOM is ready 313if (document.readyState === 'loading') { 314 document.addEventListener('DOMContentLoaded', init); 315} else { 316 init(); 317} 318 319})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.