/* * 파일명: main.js * 위치: /js/main.js * 기능: 로또 번호 생성기 프론트엔드 로직 * 작성일: 2025-05-24 * 수정일: 2025-05-24 */ // =================================== // 전역 변수 및 설정 // =================================== /* 전역 변수 */ let numberHistory = []; let previousWinningNumbers = []; let lottoHistory = []; let previewInterval = null; let isGenerating = false; /* 디버깅 로거 */ const logger = { log: (message, data) => { console.log(`[Lotto] ${message}`, data || ''); }, error: (message, error) => { console.error(`[Lotto Error] ${message}`, error); } }; // =================================== // 사용자 검색 기능 // =================================== /* 사용자 번호 검색 */ const userSearch = { async searchNumbers() { const searchTerm = document.getElementById('searchInitial').value.trim(); const resultsContainer = document.getElementById('searchResults'); if (!searchTerm) { alert('아이디를 입력해주세요.'); return; } resultsContainer.innerHTML = '
'; try { const response = await fetch(`api/search_user_numbers.php?term=${encodeURIComponent(searchTerm)}`); const data = await response.json(); if (data.success) { if (data.results.length === 0) { resultsContainer.innerHTML = `
"${searchTerm}"로 생성된 번호가 없습니다.
`; } else { let html = `
${data.count}개의 번호를 찾았습니다.
`; data.results.forEach((item, index) => { let rankBadge = ''; if (item.winningRank > 0) { rankBadge = `${item.winningRank}등 당첨!`; } html += `
${item.createdAt} ${rankBadge}
${item.numbers.map(num => `
${num}
`).join('')}
`; }); html += '
'; resultsContainer.innerHTML = html; } } else { resultsContainer.innerHTML = `
${data.message}
`; } } catch (error) { console.error('검색 오류:', error); resultsContainer.innerHTML = `
검색 중 오류가 발생했습니다.
`; } } }; // =================================== // 동적 번호 프리뷰 시스템 // =================================== /* 동적 번호 프리뷰 관리 */ const preview = { // 프리뷰 시작 start: function() { if (previewInterval) return; const container = document.getElementById('lottoNumbers'); if (!container) return; // 초기 프리뷰 HTML 생성 container.innerHTML = `
${Array.from({length: 6}, (_, i) => `
${Math.floor(Math.random() * 45) + 1}
`).join('')}
`; // 정기적으로 번호 변경 previewInterval = setInterval(() => { if (!isGenerating) { this.updateNumbers(); } }, 800); logger.log('동적 번호 프리뷰 시작됨'); }, // 번호 업데이트 updateNumbers: function() { const balls = document.querySelectorAll('.lotto-preview .lotto-ball'); balls.forEach((ball, index) => { // 번호 변경 애니메이션 적용 ball.classList.add('number-changing'); setTimeout(() => { const newNumber = Math.floor(Math.random() * 45) + 1; ball.textContent = newNumber; ball.className = `lotto-ball ${this.getNumberClass(newNumber)} number-changing`; setTimeout(() => { ball.classList.remove('number-changing'); }, 300); }, 300); }); }, // 번호 범위에 따른 클래스 반환 getNumberClass: function(num) { if (num <= 10) return 'number-1-10'; if (num <= 20) return 'number-11-20'; if (num <= 30) return 'number-21-30'; if (num <= 40) return 'number-31-40'; return 'number-41-45'; }, // 프리뷰 정지 stop: function() { if (previewInterval) { clearInterval(previewInterval); previewInterval = null; logger.log('동적 번호 프리뷰 정지됨'); } } }; // =================================== // 저장소 관리 // =================================== /* localStorage 관련 기능 */ const storage = { // 히스토리 저장 saveHistory: function(history) { try { localStorage.setItem('lottoNumberHistory', JSON.stringify(history)); logger.log('히스토리가 localStorage에 저장됨', history.length); } catch (error) { logger.error('localStorage 저장 실패', error); } }, // 히스토리 불러오기 loadHistory: function() { try { const savedHistory = localStorage.getItem('lottoNumberHistory'); if (savedHistory) { const parsed = JSON.parse(savedHistory); if (Array.isArray(parsed) && parsed.every(item => item && Array.isArray(item.numbers) && item.numbers.length > 0)) { logger.log('localStorage에서 히스토리 로드됨', parsed.length); return parsed; } else { logger.error('유효하지 않은 히스토리 데이터'); localStorage.removeItem('lottoNumberHistory'); } } } catch (error) { logger.error('localStorage 로드 실패', error); localStorage.removeItem('lottoNumberHistory'); } return []; }, // 히스토리 초기화 clearHistory: function() { try { localStorage.removeItem('lottoNumberHistory'); logger.log('히스토리가 초기화됨'); numberHistory = []; } catch (error) { logger.error('localStorage 삭제 실패', error); } } }; // =================================== // 번호 저장 및 공개 로그 기능 // =================================== /* 번호 저장 기능 */ const numberSaver = { // 번호 저장 async saveNumber(numbers, userInitial = '') { try { console.log('🔄 numberSaver.saveNumber 호출됨'); console.log('📤 요청 데이터:', { numbers, userInitial }); const requestData = { numbers: numbers, user_initial: userInitial }; console.log('📤 실제 전송 데이터:', requestData); // fetch 요청에 타임아웃 추가 const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); // 10초 타임아웃 const response = await fetch('api/save_number.php', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-cache' }, body: JSON.stringify(requestData), signal: controller.signal }); clearTimeout(timeoutId); console.log('📥 응답 상태:', response.status, response.statusText); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status} ${response.statusText}`); } const responseText = await response.text(); console.log('📥 원본 응답 텍스트:', responseText); // 응답이 비어있는지 확인 if (!responseText || responseText.trim() === '') { throw new Error('서버에서 빈 응답을 반환했습니다'); } let data; try { data = JSON.parse(responseText); console.log('📥 파싱된 응답:', data); } catch (parseError) { console.error('❌ JSON 파싱 실패:', parseError); console.error('❌ 원본 텍스트:', responseText); // HTML 응답인 경우 (에러 페이지 등) if (responseText.includes(' { if (typeof publicLog !== 'undefined' && publicLog.refresh) { publicLog.refresh(); } }, 1500); return data; } else { console.error('❌ 저장 실패:', data.message); return { success: false, message: data.message || '알 수 없는 서버 오류' }; } } catch (error) { console.error('❌ numberSaver 오류:', error); // 구체적인 오류 메시지 반환 let errorMessage = '알 수 없는 오류가 발생했습니다'; if (error.name === 'AbortError') { errorMessage = '요청이 시간 초과되었습니다'; } else if (error.message.includes('fetch')) { errorMessage = '네트워크 연결 오류입니다'; } else { errorMessage = error.message; } return { success: false, message: errorMessage }; } } }; /* 공개 로그 관리 */ const publicLog = { /* 공개 로그 조회 */ async refresh() { try { console.log('🔄 공개 로그 새로고침 시작'); // 타임스탬프 추가로 캐시 방지 const response = await fetch(`api/get_public_log.php?t=${Date.now()}`); const data = await response.json(); if (data.success) { this.displayLogs(data.logs); console.log('✅ 공개 로그 새로고침 성공'); } else { this.displayError(data.message); console.error('❌ 공개 로그 조회 실패:', data.message); } } catch (error) { logger.error('공개 로그 조회 실패', error); this.displayError('로그를 불러오는데 실패했습니다.'); } }, // 로그 표시 displayLogs(logs) { const container = document.getElementById('publicLog'); if (!container) { console.log('publicLog 컨테이너를 찾을 수 없음'); return; } if (logs.length === 0) { container.innerHTML = `
아직 생성된 번호가 없습니다
`; return; } // 실시간 번호 생성로그 const logHtml = logs.map(log => { let winningInfo = ''; let numbersHtml = ''; if (log.winning) { // 당첨 정보가 있는 경우 numbersHtml = log.numbers.map(num => { const isWinning = log.winning.winningNumbers.includes(num); const baseClass = preview.getNumberClass(num); const winningClass = isWinning ? 'winning-number' : ''; return `${num}`; }).join(''); winningInfo = `${log.winning.rank}등!`; } else { // 일반 번호 표시 numbersHtml = log.numbers.map(num => { return `${num}`; }).join(''); } return `
${log.userInitial} ${log.createdAt} ${winningInfo}
${numbersHtml}
`; }).join(''); container.innerHTML = logHtml; }, // 에러 표시 displayError(message) { const container = document.getElementById('publicLog'); if (container) { container.innerHTML = `
${message}
`; } } }; /* 공개 로그 새로고침 함수 */ function refreshPublicLog() { publicLog.refresh(); } // =================================== // 이미지 저장/공유 기능 // =================================== /* 히스토리 이미지 생성 및 공유 */ const imageExport = { // 캔버스에 히스토리 그리기 async createHistoryImage() { if (numberHistory.length === 0) { alert('생성된 번호가 없습니다. 먼저 번호를 생성해주세요.'); return null; } const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // 캔버스 크기 설정 canvas.width = 900; canvas.height = Math.max(600, numberHistory.length * 140 + 220); // 심플한 배경 - 깔끔한 흰색 ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, canvas.width, canvas.height); // 상단 헤더 영역 - 단색 배경 ctx.fillStyle = '#6c5ce7'; ctx.fillRect(0, 0, canvas.width, 120); // 제목 ctx.fillStyle = '#ffffff'; ctx.font = 'bold 32px Arial, sans-serif'; ctx.textAlign = 'center'; ctx.fillText('🎲 내가 생성한 로또 번호', canvas.width / 2, 50); // 부제목 ctx.font = '18px Arial, sans-serif'; ctx.fillStyle = '#f8f9fa'; const currentDate = new Date().toLocaleDateString('ko-KR'); ctx.fillText(`생성일: ${currentDate}`, canvas.width / 2, 80); // 각 번호 조합 그리기 let yPos = 170; numberHistory.forEach((item, index) => { // 번호별 카드 배경 - 깔끔한 회색 구분선 ctx.fillStyle = '#f8f9fa'; ctx.fillRect(40, yPos - 45, canvas.width - 80, 120); // 카드 테두리 ctx.strokeStyle = '#dee2e6'; ctx.lineWidth = 1; ctx.strokeRect(40, yPos - 45, canvas.width - 80, 120); // 순서 번호 - 색상 원형 배지 ctx.fillStyle = '#6c5ce7'; ctx.beginPath(); ctx.arc(80, yPos - 5, 22, 0, 2 * Math.PI); ctx.fill(); ctx.fillStyle = '#ffffff'; ctx.font = 'bold 18px Arial, sans-serif'; ctx.textAlign = 'center'; ctx.fillText((index + 1).toString(), 80, yPos + 1); // 생성 시간 - 어두운 회색으로 가독성 확보 if (item.timestamp) { ctx.font = '14px Arial, sans-serif'; ctx.fillStyle = '#495057'; ctx.textAlign = 'left'; const time = new Date(item.timestamp).toLocaleString('ko-KR'); ctx.fillText(time, 115, yPos - 15); } // 매칭 정보 - 강조 색상 if (item.matchingRound) { ctx.fillStyle = '#e74c3c'; ctx.font = 'bold 14px Arial, sans-serif'; ctx.fillText(`🎉 ${item.matchingRound.round}회차와 ${item.matchingRound.matchCount}개 일치!`, 115, yPos + 5); } // 로또 볼 그리기 const ballStartX = 320; item.numbers.forEach((num, ballIndex) => { const x = ballStartX + (ballIndex * 80); const y = yPos + 30; // 볼 색상 - 가독성 높은 색상 let ballColor; if (num <= 10) ballColor = '#e74c3c'; // 빨강 else if (num <= 20) ballColor = '#3498db'; // 파랑 else if (num <= 30) ballColor = '#2ecc71'; // 초록 else if (num <= 40) ballColor = '#f39c12'; // 주황 else ballColor = '#9b59b6'; // 보라 // 매칭된 번호면 특별 표시 const isMatching = item.matchingRound && item.matchingRound.matchingNumbers.includes(Number(num)); if (isMatching) { // 매칭 표시 - 두꺼운 금색 테두리 ctx.strokeStyle = '#f39c12'; ctx.lineWidth = 4; ctx.beginPath(); ctx.arc(x, y, 32, 0, 2 * Math.PI); ctx.stroke(); } // 볼 그림자 ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; ctx.beginPath(); ctx.arc(x + 1, y + 1, 28, 0, 2 * Math.PI); ctx.fill(); // 볼 그리기 ctx.fillStyle = ballColor; ctx.beginPath(); ctx.arc(x, y, 28, 0, 2 * Math.PI); ctx.fill(); // 미묘한 하이라이트 const highlight = ctx.createRadialGradient(x - 8, y - 8, 0, x - 8, y - 8, 20); highlight.addColorStop(0, 'rgba(255, 255, 255, 0.4)'); highlight.addColorStop(1, 'rgba(255, 255, 255, 0)'); ctx.fillStyle = highlight; ctx.fill(); // 숫자 - 흰색으로 명확하게 ctx.fillStyle = '#ffffff'; ctx.font = 'bold 18px Arial, sans-serif'; ctx.textAlign = 'center'; ctx.fillText(num.toString(), x, y + 6); }); yPos += 140; }); // 푸터 ctx.fillStyle = '#6c757d'; ctx.font = '16px Arial, sans-serif'; ctx.textAlign = 'center'; ctx.fillText('🍀 로또당첨번호 조합기 - https://URL.KR/lotto', canvas.width / 2, canvas.height - 30); return canvas; }, // 이미지 다운로드 async downloadImage() { const canvas = await this.createHistoryImage(); if (!canvas) return; // 다운로드 링크 생성 const link = document.createElement('a'); link.download = `로또번호_${new Date().toISOString().slice(0, 10)}.png`; link.href = canvas.toDataURL(); link.click(); }, // 이미지 공유 async shareImage() { const canvas = await this.createHistoryImage(); if (!canvas) return; canvas.toBlob(async (blob) => { if (navigator.share && navigator.canShare) { const file = new File([blob], 'lotto-numbers.png', { type: 'image/png' }); if (navigator.canShare({ files: [file] })) { try { await navigator.share({ title: '내가 생성한 로또 번호', text: '행운의 로또 번호를 생성했습니다!', files: [file] }); } catch (error) { console.log('공유가 취소되었습니다.'); } } else { this.fallbackShare(blob); } } else { this.fallbackShare(blob); } }); }, // 공유 폴백 fallbackShare(blob) { const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `로또번호_${new Date().toISOString().slice(0, 10)}.png`; link.click(); URL.revokeObjectURL(url); // 클립보드에 사이트 URL 복사 navigator.clipboard.writeText(window.location.href) .then(() => alert('이미지가 다운로드되고 사이트 URL이 클립보드에 복사되었습니다!')) .catch(() => alert('이미지가 다운로드되었습니다!')); } }; // =================================== // 통계 기능 // =================================== /* 통계 기능 모듈 */ const stats = { // 가장 자주 생성된 번호 통계 getMostFrequentNumbers: function(limit = 5) { const numberCounts = {}; numberHistory.forEach(item => { if (item.numbers && Array.isArray(item.numbers)) { item.numbers.forEach(num => { if (!numberCounts[num]) { numberCounts[num] = 0; } numberCounts[num]++; }); } }); const sortedNumbers = Object.keys(numberCounts) .map(num => ({ number: parseInt(num), count: numberCounts[num] })) .sort((a, b) => b.count - a.count) .slice(0, limit); return sortedNumbers; }, // 가장 적게 생성된 번호 통계 getLeastFrequentNumbers: function(limit = 5) { const numberCounts = {}; for (let i = 1; i <= 45; i++) { numberCounts[i] = 0; } numberHistory.forEach(item => { if (item.numbers && Array.isArray(item.numbers)) { item.numbers.forEach(num => { numberCounts[num]++; }); } }); const sortedNumbers = Object.keys(numberCounts) .map(num => ({ number: parseInt(num), count: numberCounts[num] })) .sort((a, b) => a.count - b.count) .slice(0, limit); return sortedNumbers; }, // 당첨 번호와 가장 많이 일치한 생성 번호 getMostMatchingCombination: function() { if (numberHistory.length === 0) return null; let bestMatch = { index: -1, totalMatches: 0, numbers: [] }; numberHistory.forEach((item, index) => { let matchCount = 0; if (item.matchingRound && item.matchingRound.matchCount) { matchCount = item.matchingRound.matchCount; } if (matchCount > bestMatch.totalMatches) { bestMatch = { index, totalMatches: matchCount, numbers: item.numbers, matchingRound: item.matchingRound }; } }); return bestMatch.index === -1 ? null : bestMatch; }, /* 통계 UI 업데이트 */ updateStatsUI: async function() { const topNumbersEl = document.getElementById('topNumbers'); const bottomNumbersEl = document.getElementById('bottomNumbers'); const bestMatchSectionEl = document.getElementById('bestMatchSection'); const statsInfoEl = document.getElementById('statsInfo'); if (!topNumbersEl || !bottomNumbersEl || !statsInfoEl) { logger.error('통계 요소를 찾을 수 없음'); return; } try { // DB 기반 통계 가져오기 const response = await fetch(`api/get_lotto_statistics.php?t=${Date.now()}`); const responseText = await response.text(); // JSON 파싱 시도 let data; try { data = JSON.parse(responseText); } catch (error) { console.error('JSON 파싱 실패:', responseText.substring(0, 200)); throw new Error('서버에서 올바른 JSON 응답을 받지 못했습니다.'); } if (data.success && data.statistics) { const stats = data.statistics; // 인기 번호 TOP 5 업데이트 const topNumbers = Object.entries(stats.topNumbers).slice(0, 5); topNumbersEl.innerHTML = topNumbers.map(([num, count]) => `${num} (${count}회)` ).join(' '); // 비인기 번호 TOP 5 업데이트 const bottomNumbers = Object.entries(stats.bottomNumbers).slice(0, 5); bottomNumbersEl.innerHTML = bottomNumbers.map(([num, count]) => `${num} (${count}회)` ).join(' '); statsInfoEl.textContent = `* 누적 총 ${Math.floor(stats.totalRecords)}개의 번호 생성 결과를 기반으로 합니다.`; } else { // 로컬 히스토리 기반 통계 (폴백) if (numberHistory.length === 0) { topNumbersEl.innerHTML = '데이터 없음'; bottomNumbersEl.innerHTML = '데이터 없음'; statsInfoEl.textContent = '* 번호를 생성하면 통계가 표시됩니다.'; return; } const frequentNumbers = this.getMostFrequentNumbers(); const leastFrequentNumbers = this.getLeastFrequentNumbers(); topNumbersEl.innerHTML = frequentNumbers.map(item => `${item.number} (${item.count}회)` ).join(' '); bottomNumbersEl.innerHTML = leastFrequentNumbers.map(item => `${item.number} (${item.count}회)` ).join(' '); statsInfoEl.textContent = `* 통계는 ${numberHistory.length}개의 번호 생성 결과를 기반으로 합니다.`; } } catch (error) { logger.error('통계 로드 실패', error); // 에러 시 로컬 통계 사용 const frequentNumbers = this.getMostFrequentNumbers(); const leastFrequentNumbers = this.getLeastFrequentNumbers(); if (frequentNumbers.length > 0) { topNumbersEl.innerHTML = frequentNumbers.map(item => `${item.number} (${item.count}회)` ).join(' '); bottomNumbersEl.innerHTML = leastFrequentNumbers.map(item => `${item.number} (${item.count}회)` ).join(' '); } } // 베스트 매치 정보는 기존 로직 유지 const bestMatch = this.getMostMatchingCombination(); if (bestMatch && bestMatch.totalMatches >= 3 && bestMatchSectionEl) { bestMatchSectionEl.innerHTML = `
최고 일치 조합
${bestMatch.numbers.map(num => { const isMatching = bestMatch.matchingRound && bestMatch.matchingRound.matchingNumbers && bestMatch.matchingRound.matchingNumbers.includes(Number(num)); return `${num}`; }).join(' ')}
${bestMatch.matchingRound.round}회차와 ${bestMatch.totalMatches}개 일치
`; } else if (bestMatchSectionEl) { bestMatchSectionEl.innerHTML = ''; } logger.log('통계 UI 업데이트 완료'); } }; // =================================== // API 요청 관리 // =================================== /* API 요청 함수들 */ const api = { // 번호 생성 API 호출 async generateNumbers() { const formData = new FormData(); formData.append('action', 'generate'); formData.append('count', 1); try { logger.log('번호 생성 중...'); const response = await fetch('api/lotto.php', { method: 'POST', body: formData }); const data = await response.json(); logger.log('API 응답:', data); if (data.success) { return data.numbers[0]; } else { throw new Error(data.message || '번호 생성 실패'); } } catch (error) { logger.error('번호 생성 실패', error); alert('번호 생성 중 오류가 발생했습니다.'); return null; } }, // 로또 히스토리 데이터 로드 async loadLottoHistory() { try { logger.log('로또 히스토리 데이터 로드 시작'); const formData = new FormData(); formData.append('action', 'get_history'); formData.append('limit', '50'); const response = await fetch('api/lotto.php', { method: 'POST', body: formData }); const data = await response.json(); if (data.success && data.history) { lottoHistory = data.history; logger.log('로또 히스토리 로드 완료:', lottoHistory.length, '개 회차'); } else { logger.error('로또 히스토리 로드 실패:', data.message); lottoHistory = []; } } catch (error) { logger.error('로또 히스토리 로드 중 오류:', error); lottoHistory = []; } }, // 현재 회차 정보 조회 API 호출 async getCurrentLottoInfo() { const formData = new FormData(); formData.append('action', 'current_info'); const now = new Date(); const isSaturday = now.getDay() === 6; const isBeforeDrawTime = now.getHours() < 22; if (isSaturday && isBeforeDrawTime) { formData.append('before_draw', 'true'); } try { logger.log('현재 회차 정보 조회 중...'); const response = await fetch('api/lotto.php', { method: 'POST', body: formData }); const data = await response.json(); logger.log('현재 회차 정보:', data); if (data.success) { return data.info; } else { throw new Error(data.message || '정보 조회 실패'); } } catch (error) { logger.error('현재 회차 정보 조회 실패', error); alert('당첨 정보 조회 중 오류가 발생했습니다.'); return null; } } }; // =================================== // UI 업데이트 관리 // =================================== /* UI 업데이트 함수들 */ const ui = { // 생성된 번호 표시 displayNumbers(numbers, containerId, userInitial = null) { const container = document.getElementById(containerId); if (!container) return; // 프리뷰 정지 preview.stop(); const matchingRound = this.findMatchingRound(numbers); logger.log('현재 번호:', numbers); logger.log('매칭 라운드:', matchingRound); let html = '
'; // 생성된 번호 표시 html += '
'; html += numbers.map(num => `
${num}
`).join(''); html += '
'; // 매칭된 번호가 있는 경우 if (matchingRound) { html += '
'; html += '
'; html += matchingRound.matchingNumbers.map(num => `
${num}
`).join(''); html += '
'; html += `
제 ${matchingRound.round}회와
${matchingRound.matchCount}개 일치
자세히 보기 ▶
`; } html += '
'; container.innerHTML = html; // 히스토리에 추가 (이니셜 정보 포함) this.addToHistory(numbers, matchingRound, userInitial); // 3초 후 프리뷰 재시작 setTimeout(() => { if (!isGenerating) { preview.start(); } }, 10000); }, // 이전 당첨 번호와 비교 findMatchingRound(numbers) { if (!lottoHistory || lottoHistory.length === 0) { logger.log('로또 히스토리 데이터가 없음'); return null; } let bestMatch = null; let maxMatches = 3; for (let round of lottoHistory) { const matchingNums = numbers.filter(num => round.numbers.includes(Number(num)) ); if (matchingNums.length >= maxMatches) { if (!bestMatch || matchingNums.length > bestMatch.matchCount) { bestMatch = { round: round.round, matchCount: matchingNums.length, winningNumbers: round.numbers, matchingNumbers: matchingNums }; } } } logger.log('매칭 결과:', bestMatch); return bestMatch; }, // 히스토리에 번호 추가 addToHistory(numbers, matchingRound, userInitial = null) { if (numbers && numbers.length > 0) { const historyItem = { numbers, matchingRound, timestamp: new Date().toISOString() }; // 이니셜 정보가 있으면 추가 if (userInitial) { historyItem.userInitial = userInitial; } numberHistory.unshift(historyItem); if (numberHistory.length > 10) { numberHistory.pop(); } storage.saveHistory(numberHistory); } this.updateHistoryUI(); }, /* UI 업데이트 관리의 히스토리 UI 업데이트 함수 수정 */ updateHistoryUI() { const historyContainer = document.getElementById('numberHistory'); if (!historyContainer) return; if (numberHistory.length === 0) { historyContainer.innerHTML = `
번호를 생성하면 히스토리가 표시됩니다
`; return; } historyContainer.innerHTML = numberHistory.map((item, index) => { // 이니셜 정보 const initialInfo = item.userInitial ? `[${item.userInitial}]` : `[익명]`; // 타임스탬프 정보 const timeInfo = item.timestamp ? `${new Date(item.timestamp).toLocaleString()}` : ''; // 매칭 정보 const matchingInfo = item.matchingRound ? ` ` : ''; return `
${initialInfo} | ${index + 1}번째 생성
${timeInfo} ${matchingInfo}
${item.numbers.map(num => `
${num}
`).join('')}
`; }).join(''); }, // 로딩 표시 displayLoading(containerId) { const container = document.getElementById(containerId); if (container) { container.innerHTML = `
Loading...
`; } }, // 에러 메시지 표시 displayError(containerId, message) { const container = document.getElementById(containerId); if (container) { container.innerHTML = ` `; } } }; // =================================== // 당첨번호 정보 로드 (기존 API 활용) // =================================== /* 당첨번호 정보 로드 */ async function loadCurrentLottoInfo() { try { console.log('🔄 당첨번호 정보 로드 시작'); // 기존 get_latest_round_stats.php 활용 const response = await fetch(`api/get_latest_round_stats.php?t=${Date.now()}`); const data = await response.json(); if (data.success && data.round_info) { updateCurrentLottoInfoUI(data.round_info); console.log('✅ 당첨번호 정보 로드 성공'); } else { displayCurrentLottoInfoError(data.message || '당첨 정보를 불러올 수 없습니다.'); console.error('❌ 당첨번호 정보 로드 실패:', data.message); } } catch (error) { logger.error('당첨번호 정보 로드 실패', error); displayCurrentLottoInfoError('당첨 정보를 불러오는데 실패했습니다.'); } } /* 당첨번호 정보 UI 업데이트 */ function updateCurrentLottoInfoUI(roundInfo) { const container = document.getElementById('currentLottoInfo'); if (!container) return; const winningNumbers = roundInfo.winning_numbers.map(num => `${num}` ).join(''); const bonusNumber = `${roundInfo.bonus_number}`; // 1등 당첨 정보만 표시 let firstPrizeHtml = ''; if (roundInfo.prize_info && roundInfo.prize_info.first) { const firstPrize = roundInfo.prize_info.first; firstPrizeHtml = `
1등 당첨 정보
${firstPrize.winners}명
당첨자 수
${firstPrize.amount}
당첨금액
`; } else { firstPrizeHtml = `
undefined명
1등 당첨자 수
undefined
1등 당첨금액
당첨금 정보는 동행복권 공식 발표 후 업데이트됩니다.
`; } const html = `

${roundInfo.round}회 당첨결과

(${roundInfo.draw_date} 추첨)
${winningNumbers}
당첨번호
+
${bonusNumber}
보너스
${firstPrizeHtml}
`; container.innerHTML = html; // 당첨금액 텍스트 길이에 따른 클래스 추가 setTimeout(() => { const prizeValues = document.querySelectorAll('.prize-value'); prizeValues.forEach(element => { const text = element.textContent || ''; const length = text.length; // 텍스트 길이에 따른 클래스 추가 if (length > 15) { element.setAttribute('data-length', 'very-long'); element.classList.add('prize-value-responsive'); } else if (length > 10) { element.setAttribute('data-length', 'long'); element.classList.add('prize-value-responsive'); } // 제목 속성에 전체 텍스트 추가 (접근성) element.setAttribute('title', text); // 당첨금액인 경우 특별 처리 if (element.classList.contains('text-success')) { if (text.includes('억') || text.includes('조')) { element.classList.add('prize-value-responsive'); } } }); }, 100); } /* 볼 색상 클래스 반환 */ function getBallColor(num) { if (num <= 10) return '1-10'; if (num <= 20) return '11-20'; if (num <= 30) return '21-30'; if (num <= 40) return '31-40'; return '41-45'; } /* 당첨번호 정보 에러 표시 */ function displayCurrentLottoInfoError(message) { const container = document.getElementById('currentLottoInfo'); if (container) { container.innerHTML = `
${message}
`; } } // =================================== // 당첨 통계 관련 함수 // =================================== /* 당첨 통계 로드 함수 */ async function loadWinningStats() { try { console.log('🔄 주간 당첨 통계 로드 시작'); // 새로운 주간 통계 API 호출 const response = await fetch(`api/get_weekly_winning_stats.php?t=${Date.now()}`); const data = await response.json(); if (data.success) { updateWeeklyWinningStatsUI(data); console.log('✅ 주간 당첨 통계 로드 성공'); } else { displayWeeklyStatsError(data.message); console.error('❌ 주간 당첨 통계 로드 실패:', data.message); } } catch (error) { logger.error('주간 당첨 통계 로드 실패', error); displayWeeklyStatsError('주간 통계를 불러오는데 실패했습니다.'); } } /* 등급별 당첨 현황 로드 함수 */ async function loadLatestRoundWinningStats() { try { console.log('🔄 최신 회차 등급별 당첨 현황 로드 시작'); // 최신 완료된 회차 정보 조회 const response = await fetch(`api/get_latest_round_stats.php?t=${Date.now()}`); const data = await response.json(); if (data.success) { updateLatestRoundStatsUI(data); console.log('✅ 최신 회차 등급별 당첨 현황 로드 성공'); } else { displayLatestRoundStatsError(data.message); console.error('❌ 최신 회차 등급별 당첨 현황 로드 실패:', data.message); } } catch (error) { logger.error('최신 회차 등급별 당첨 현황 로드 실패', error); displayLatestRoundStatsError('등급별 당첨 현황을 불러오는데 실패했습니다.'); } } /* 최신 회차 등급별 당첨 현황 UI 업데이트 */ function updateLatestRoundStatsUI(data) { const container = document.getElementById('weeklyRankStats'); if (!container) return; const stats = data.stats; const roundInfo = data.round_info; let html = `
${roundInfo.round}회 명예의 전당 (${roundInfo.draw_date} 추첨 기준)
`; // 회차 정보가 있는 경우 당첨번호 표시 if (roundInfo.winning_numbers) { html += `
당첨번호
${roundInfo.winning_numbers.map(num => `
${num}
`).join('')} +
${roundInfo.bonus_number}
`; } // 등급별 당첨 현황 - PC용 (5개 한 줄) html += '
'; html += '
'; for (let rank = 1; rank <= 5; rank++) { const rankData = stats.ranks[rank] || { count: 0, percentage: '0.0' }; const rankEmojis = ['', '🥇', '🥈', '🥉', '🏅', '🎖️']; html += `
${rankEmojis[rank]} ${rank}등
${rankData.count.toLocaleString()}명
${rankData.percentage}%
${rankData.count > 0 ? ` ` : ` 당첨자 없음 `}
`; } html += '
'; // 모바일용 - 1,2,3등 (첫 번째 줄) html += '
'; for (let rank = 1; rank <= 3; rank++) { const rankData = stats.ranks[rank] || { count: 0, percentage: '0.0' }; const rankEmojis = ['', '🥇', '🥈', '🥉']; html += `
${rankEmojis[rank]} ${rank}등
${rankData.count.toLocaleString()}명
${rankData.percentage}%
${rankData.count > 0 ? ` ` : ` 당첨자 없음 `}
`; } html += '
'; // 모바일용 - 4,5등 (두 번째 줄) html += '
'; for (let rank = 4; rank <= 5; rank++) { const rankData = stats.ranks[rank] || { count: 0, percentage: '0.0' }; const rankEmojis = ['', '🥇', '🥈', '🥉', '🏅', '🎖️']; html += `
${rankEmojis[rank]} ${rank}등
${rankData.count.toLocaleString()}명
${rankData.percentage}%
${rankData.count > 0 ? ` ` : ` 당첨자 없음 `}
`; } html += '
'; html += '
'; // rank-stats-container 종료 // 전체 통계 요약 if (stats.total) { html += `
${stats.total.total_numbers.toLocaleString()}
총 생성 번호
${stats.total.total_winners.toLocaleString()}
총 당첨 번호
${stats.total.winning_rate}%
전체 당첨률
총 생성 번호 ${stats.total.total_numbers.toLocaleString()}
총 당첨 번호 ${stats.total.total_winners.toLocaleString()}
전체 당첨률 ${stats.total.winning_rate}%
`; } container.innerHTML = html; } /* 최신 회차 통계 에러 표시 */ function displayLatestRoundStatsError(message) { const container = document.getElementById('weeklyRankStats'); if (container) { container.innerHTML = `
${message}
`; } } /* 특정 회차 당첨자 상세 보기 함수 (모달 중복 방지) */ async function showRoundWinnerDetails(round, rank = null) { try { // 기존 모달 완전히 제거 (배경 포함) const existingModal = document.getElementById('roundWinnerModal'); if (existingModal) { // Bootstrap 모달 인스턴스 찾아서 제거 const modalInstance = bootstrap.Modal.getInstance(existingModal); if (modalInstance) { modalInstance.dispose(); // 모달 인스턴스 완전 삭제 } existingModal.remove(); // DOM에서 제거 } // 모든 모달 배경 제거 (혹시 남아있을 수 있는 배경들) const backdrops = document.querySelectorAll('.modal-backdrop'); backdrops.forEach(backdrop => backdrop.remove()); // body 클래스 정리 document.body.classList.remove('modal-open'); document.body.style.overflow = ''; document.body.style.paddingRight = ''; // 잠시 대기 (DOM 정리 시간) await new Promise(resolve => setTimeout(resolve, 100)); //const url = `api/get_winner_details.php?round=${round}${rank ? `&rank=${rank}` : ''}`; const url = `?action=get_winners&round=${round}${rank ? `&rank=${rank}` : ''}`; const response = await fetch(url); const data = await response.json(); if (data.success) { displayRoundWinnerDetailsModal(data.winners, round, rank); } else { alert(`${round}회차 당첨자 정보를 불러오는데 실패했습니다: ` + data.message); } } catch (error) { console.error(`${round}회차 당첨자 상세 정보 로드 실패:`, error); alert(`${round}회차 당첨자 정보를 불러오는데 실패했습니다.`); } } /* 특정 회차 당첨자 상세 정보 모달 표시 */ function displayRoundWinnerDetailsModal(winners, round, rank) { const title = rank ? `${round}회차 ${rank}등 당첨자` : `${round}회차 전체 당첨자`; // 당첨번호 정보 추출 (첫 번째 당첨자에서) let winningNumbersHtml = ''; let winningNumbers = []; let bonusNumber = null; if (winners.length > 0 && winners[0].num1) { winningNumbers = [ parseInt(winners[0].num1), parseInt(winners[0].num2), parseInt(winners[0].num3), parseInt(winners[0].num4), parseInt(winners[0].num5), parseInt(winners[0].num6) ]; bonusNumber = winners[0].bonus_num ? parseInt(winners[0].bonus_num) : null; winningNumbersHtml = `
${round}회차 당첨번호
${winningNumbers.map(num => `
${num}
`).join('')} +
${bonusNumber}
당첨번호 + 보너스번호

`; } let winnersHtml = ''; if (winners.length === 0) { winnersHtml = `
${round}회차 ${rank ? `${rank}등 당첨자가` : '당첨자가'} 없습니다.
`; } else { winnersHtml = winners.map((winner, index) => { const userNumbers = winner.numbers.split(',').map(num => parseInt(num.trim())); // 매칭된 번호 계산 const matchedNumbers = userNumbers.filter(num => winningNumbers.includes(num)); const hasBonus = bonusNumber && userNumbers.includes(bonusNumber) && winner.winning_rank === 2; const numbersHtml = userNumbers.map(num => { let className = `number-badge`; // 새 CSS 클래스 사용 if (winningNumbers.includes(num)) { className += ' matched'; } else if (bonusNumber && num === bonusNumber && winner.winning_rank === 2) { className += ' bonus-matched'; } return `${num}`; }).join(''); const badgeColors = { 1: 'bg-warning text-dark', 2: 'bg-info', 3: 'bg-success', 4: 'bg-primary', 5: 'bg-secondary' }; return `
${winner.winning_rank}등 ${winner.user_initial}
${new Date(winner.created_at).toLocaleString('ko-KR')}
${numbersHtml}
일치번호: ${matchedNumbers.join(', ')} ${hasBonus ? ` + 보너스(${bonusNumber})` : ''} (총 ${winner.matching_count}개 일치)
`; }).join(''); } const modalHtml = ` `; // 기존 모달 완전히 제거 const existingModal = document.getElementById('roundWinnerModal'); if (existingModal) { const modalInstance = bootstrap.Modal.getInstance(existingModal); if (modalInstance) { modalInstance.dispose(); } existingModal.remove(); } // 남아있는 배경 제거 const backdrops = document.querySelectorAll('.modal-backdrop'); backdrops.forEach(backdrop => backdrop.remove()); // 새 모달 추가 document.body.insertAdjacentHTML('beforeend', modalHtml); // 새 모달 인스턴스 생성 const newModal = new bootstrap.Modal(document.getElementById('roundWinnerModal'), { backdrop: true, keyboard: true, focus: true }); // 모달 이벤트 리스너 추가 const modalElement = document.getElementById('roundWinnerModal'); modalElement.addEventListener('hidden.bs.modal', function () { // 모달이 완전히 닫힌 후 정리 const modalInstance = bootstrap.Modal.getInstance(this); if (modalInstance) { modalInstance.dispose(); } this.remove(); // 남은 배경 정리 const remainingBackdrops = document.querySelectorAll('.modal-backdrop'); remainingBackdrops.forEach(backdrop => backdrop.remove()); // body 스타일 정리 document.body.classList.remove('modal-open'); document.body.style.overflow = ''; document.body.style.paddingRight = ''; }); newModal.show(); } /* 회차 필터 클릭 핸들러 (모달 중복 방지) */ async function handleRoundFilterClick(round, rank = null) { // 클릭 시 잠시 비활성화 const clickedButton = event.target; const originalText = clickedButton.textContent; clickedButton.disabled = true; clickedButton.textContent = '로딩...'; try { await showRoundWinnerDetails(round, rank); } catch (error) { console.error('필터 변경 실패:', error); } finally { // 버튼 복원 (혹시 모달이 교체되어도 안전하게) setTimeout(() => { if (clickedButton) { clickedButton.disabled = false; clickedButton.textContent = originalText; } }, 300); } } // =================================== // 이번 주 추첨 통계 관련 함수 // =================================== /* 이번 주 추첨 통계 로드 함수 */ async function loadUpcomingDrawStats() { try { console.log('🔄 이번 주 추첨 통계 로드 시작'); const response = await fetch(`api/get_upcoming_draw_stats.php?t=${Date.now()}`); const data = await response.json(); if (data.success) { updateUpcomingDrawStatsUI(data); console.log('✅ 이번 주 추첨 통계 로드 성공'); } else { displayUpcomingDrawStatsError(data.message); console.error('❌ 이번 주 추첨 통계 로드 실패:', data.message); } } catch (error) { logger.error('이번 주 추첨 통계 로드 실패', error); displayUpcomingDrawStatsError('이번 주 추첨 통계를 불러오는데 실패했습니다.'); } } /* 추첨 통계용 번호 클래스 반환 함수 */ const upcomingStats = { getNumberClass: function(num) { if (num <= 10) return 'upcoming-1-10'; if (num <= 20) return 'upcoming-11-20'; if (num <= 30) return 'upcoming-21-30'; if (num <= 40) return 'upcoming-31-40'; return 'upcoming-41-45'; } }; /* 이번 주 추첨 통계 UI 업데이트 */ function updateUpcomingDrawStatsUI(data) { const container = document.getElementById('upcomingDrawStats'); if (!container) { console.log('upcomingDrawStats 컨테이너를 찾을 수 없음'); return; } const weekInfo = data.week_info; const stats = data.stats; let html = `
제${weekInfo.upcoming_round}회 추첨을 위한 이번 주 현황
${weekInfo.collection_period}
추첨일: ${weekInfo.draw_date} (토요일) 오후 8시 45분
${weekInfo.days_left > 0 ? `D-${weekInfo.days_left}` : '추첨완료'}

${weekInfo.days_left_text}

${stats.total_numbers.toLocaleString()}

이번 주 생성 번호

총 조합 수

${stats.unique_users.toLocaleString()}

참여자 수

실명 ${stats.named_users}명 · 익명 ${stats.anonymous_users}명

${stats.unselected_count}

미선택 번호

아직 선택되지 않은 번호

${weekInfo.current_day_name}

오늘

${weekInfo.days_left > 0 ? '추첨 대기중' : '추첨 완료'}
이번 주 인기 번호 TOP 5
`; // 인기 번호 TOP 5 표시 (유니크 클래스 사용) Object.entries(stats.top_numbers).forEach(([num, count]) => { html += `
${num}
`; }); html += `
* 이번 주 가장 많이 선택된 번호들
이번 주 비인기 번호 TOP 5
`; // 비인기 번호 TOP 5 표시 (유니크 클래스 사용) Object.entries(stats.bottom_numbers).forEach(([num, count]) => { html += `
${num}
${count}회
`; }); html += `
* 이번 주 적게 선택된 번호들
`; // 미선택 번호가 있는 경우 표시 if (stats.unselected_count > 0) { html += `
아직 선택되지 않은 번호 (${stats.unselected_count}개)
`; stats.unselected_numbers.slice(0, 10).forEach(num => { html += `
${num}
`; }); if (stats.unselected_count > 10) { html += `외 ${stats.unselected_count - 10}개`; } html += `
* 이번 주 아직 한 번도 선택되지 않은 번호들
`; } container.innerHTML = html; } /* 이번 주 추첨 통계 에러 표시 */ function displayUpcomingDrawStatsError(message) { const container = document.getElementById('upcomingDrawStats'); if (container) { container.innerHTML = `
${message}
`; } } /* 주간 당첨 통계 UI 업데이트 */ function updateWeeklyWinningStatsUI(data) { const container = document.getElementById('winningStats'); if (!container) return; const stats = data.stats; const weekInfo = data.week_info; const winners = data.winners || []; const checkResult = data.check_result; let html = '
'; // 주간 정보 헤더 html += `
${weekInfo.is_current_week ? '이번 주' : ''} 주간 통계
${weekInfo.korean_period} (${weekInfo.current_day_name}요일)
${weekInfo.status_message} ${checkResult && checkResult.has_draw_info ? `
제${checkResult.draw_round}회 반영완료 ` : ''}
`; // 기본 통계 카드들 html += `

${stats.basic.total_numbers.toLocaleString()}

이번 주 생성

${stats.basic.unchecked_numbers > 0 ? `미체크 ${stats.basic.unchecked_numbers}개` : '모두 체크완료'}

${stats.basic.total_winners.toLocaleString()}

이번 주 당첨

${checkResult && checkResult.new_winners > 0 ? `신규 ${checkResult.new_winners}명` : ''}

${stats.basic.winning_rate}%

이번 주 당첨률

${stats.basic.total_numbers > 0 ? `${stats.basic.total_winners}/${stats.basic.total_numbers}` : '데이터 없음'}

${Object.values(stats.ranks).reduce((sum, rank) => sum + rank.count, 0)}

총 당첨자

모든 등급 합계
`; // 등급별 당첨 현황 html += '
이번 주 등급별 당첨 현황
'; for (let rank = 1; rank <= 5; rank++) { const rankData = stats.ranks[rank]; const badgeColors = ['', 'warning', 'info', 'success', 'primary', 'secondary']; html += `
${rank}등
${rankData.count}명
`; } html += '
'; // 번호 분석 (인기/비인기 번호) if (stats.numbers && stats.numbers.top_numbers) { html += `
이번 주 인기 번호 TOP 5
`; const topNumbers = Object.entries(stats.numbers.top_numbers).slice(0, 5); topNumbers.forEach(([num, count]) => { html += `${num} (${count}회)`; }); html += `
이번 주 비인기 번호 TOP 5
`; const bottomNumbers = Object.entries(stats.numbers.bottom_numbers).slice(0, 5); bottomNumbers.forEach(([num, count]) => { html += `${num} (${count}회)`; }); html += '
'; } // 이번 주 당첨자 명예의 전당 if (winners.length > 0) { html += `
이번 주 당첨자 명예의 전당
`; winners.slice(0, 5).forEach((winner, index) => { const rankEmojis = ['', '🥇', '🥈', '🥉', '🏅', '🎖️']; const rankColors = ['', 'warning', 'info', 'success', 'primary', 'secondary']; html += `
${rankEmojis[winner.winning_rank]} ${winner.winning_rank}등 ${winner.user_initial} ${winner.matching_count}개 일치
${new Date(winner.created_at).toLocaleDateString('ko-KR')} 상세보기
`; }); // 더 많은 당첨자가 있는 경우 "더보기" 버튼 const totalWinners = Object.values(stats.ranks).reduce((sum, rank) => sum + rank.count, 0); if (totalWinners > 5) { html += `
`; } html += '
'; } // 처리 정보 (개발용) if (checkResult && checkResult.processing_time) { html += `
마지막 체크: ${checkResult.total_checked}개 처리 (${checkResult.processing_time}ms) ${checkResult.error_count > 0 ? `, 오류 ${checkResult.error_count}개` : ''}
`; } html += '
'; container.innerHTML = html; } /* 주간 통계 에러 표시 */ function displayWeeklyStatsError(message) { const container = document.getElementById('winningStats'); if (container) { container.innerHTML = `
${message}
`; } } /* 당첨 통계 UI 업데이트 */ function updateWinningStatsUI(stats) { const container = document.getElementById('winningStats'); if (!container) return; let html = '
'; // 전체 통계 html += `

${stats.total.totalNumbers.toLocaleString()}

총 생성 번호

${stats.total.totalWinners.toLocaleString()}

총 당첨 번호

${stats.total.winningRate}%

전체 당첨률

${stats.recent.rate}%

최근 7일 당첨률

`; // 등급별 통계 html += '
등급별 당첨 현황
'; for (let rank = 1; rank <= 5; rank++) { html += `
${rank}등
${stats.ranks[rank] || 0}명
${stats.ranks[rank] > 0 ? ` ` : ''}
`; } html += '
'; // 최고 당첨자 if (stats.topWinners && stats.topWinners.length > 0) { html += '
최근 1등 당첨자
'; stats.topWinners.forEach(winner => { html += `
${winner.user_initial}님이 ${winner.round_number}회차에서 1등 당첨! (${winner.draw_date})
`; }); html += '
'; } html += '
'; container.innerHTML = html; } // =================================== // 이벤트 핸들러 // =================================== /* 회차 정보 모달 표시 */ async function showRoundInfo(round) { const roundInfo = lottoHistory.find(info => info.round === round); if (!roundInfo) { logger.error('회차 정보를 찾을 수 없음:', round); alert('회차 정보를 찾을 수 없습니다.'); return; } logger.log('회차 정보 표시:', roundInfo); const modalHtml = ` `; const existingModal = document.getElementById('roundInfoModal'); if (existingModal) { existingModal.remove(); } document.body.insertAdjacentHTML('beforeend', modalHtml); const modal = new bootstrap.Modal(document.getElementById('roundInfoModal')); modal.show(); } /* 번호 생성 이벤트 핸들러 */ async function generateNumbers() { if (isGenerating) return; isGenerating = true; preview.stop(); ui.displayLoading('lottoNumbers'); console.log('=== 번호 생성 시작 ==='); try { const numbers = await api.generateNumbers(); console.log('생성된 번호:', numbers); if (numbers) { const userInitialInput = document.getElementById('userInitial'); const userInitial = userInitialInput ? userInitialInput.value.trim() : ''; const matchingRound = ui.findMatchingRound(numbers); ui.displayNumbers(numbers, 'lottoNumbers', userInitial); // 번호 저장 (강화된 에러 처리) let saveAttempts = 0; let saveSuccess = false; const maxAttempts = 3; while (saveAttempts < maxAttempts && !saveSuccess) { saveAttempts++; console.log(`💾 저장 시도 ${saveAttempts}/${maxAttempts}`); try { // 각 시도 사이에 짧은 지연 if (saveAttempts > 1) { await new Promise(resolve => setTimeout(resolve, 1000)); } const saveResult = await numberSaver.saveNumber(numbers, userInitial || '익명'); console.log(`저장 시도 ${saveAttempts} 결과:`, saveResult); if (saveResult && saveResult.success) { console.log('✅ 저장 성공!'); saveSuccess = true; // 주간 정보 표시 (당첨 알림 대신) if (saveResult.week_info) { displayWeekInfo(saveResult.week_info); } // 성공적으로 저장된 경우 통계 업데이트 setTimeout(() => { stats.updateStatsUI(); loadWinningStats(); if (typeof publicLog !== 'undefined' && publicLog.refresh) { publicLog.refresh(); } }, 2000); } else { console.warn(`❌ 저장 시도 ${saveAttempts} 실패:`, saveResult?.message || '알 수 없는 오류'); // 마지막 시도가 아니면 계속 시도 if (saveAttempts < maxAttempts) { console.log('다시 시도합니다...'); continue; } } } catch (error) { console.error(`❌ 저장 시도 ${saveAttempts} 중 예외 발생:`, error); // 마지막 시도가 아니면 계속 시도 if (saveAttempts < maxAttempts) { console.log('예외 발생으로 인한 재시도...'); continue; } } } // 모든 시도가 실패한 경우 사용자에게 알림 if (!saveSuccess) { console.error('❌ 모든 저장 시도 실패'); // 사용자에게 실패 알림 표시 const alertHtml = `
저장 실패

번호는 생성되었지만 서버 저장에 실패했습니다.

히스토리에는 저장되었으며, 잠시 후 다시 시도해보세요.
`; document.body.insertAdjacentHTML('beforeend', alertHtml); setTimeout(() => { const alert = document.querySelector('.alert-warning'); if (alert) { alert.remove(); } }, 7000); } } else { ui.displayError('lottoNumbers', '번호 생성에 실패했습니다.'); preview.start(); } } catch (error) { console.error('❌ 번호 생성 중 예외 발생:', error); ui.displayError('lottoNumbers', '번호 생성 중 오류가 발생했습니다.'); preview.start(); } finally { isGenerating = false; console.log('=== 번호 생성 종료 ==='); } } /* 주간 정보 표시 함수 */ function displayWeekInfo(weekInfo) { if (weekInfo.is_before_draw) { const alertId = 'alert-' + Date.now(); // 고유 ID 생성 const alertHtml = `
번호 생성 완료!

${weekInfo.message}

이번 주 총 ${weekInfo.total_this_week}개 번호 생성됨
`; document.body.insertAdjacentHTML('beforeend', alertHtml); // 3초 후 자동 제거 setTimeout(() => { const alertElement = document.getElementById(alertId); if (alertElement) { // 페이드 아웃 효과 alertElement.style.transition = 'opacity 0.5s ease'; alertElement.style.opacity = '0'; // 페이드 완료 후 DOM에서 제거 setTimeout(() => { if (alertElement && alertElement.parentNode) { alertElement.parentNode.removeChild(alertElement); } }, 500); } }, 3000); } } /* 번호 생성 후 상단 이동 */ async function generateNumbersAndScrollToTop() { // 상단으로 부드럽게 스크롤 window.scrollTo({ top: 0, behavior: 'smooth' }); // 스크롤 완료 후 번호 생성 setTimeout(() => { generateNumbers(); }, 500); } // =================================== // 페이지 초기화 // =================================== /* 페이지 초기화 */ async function initializePage() { console.log('=== 페이지 초기화 시작 ==='); // 저장된 히스토리 로드 const savedHistory = storage.loadHistory(); if (savedHistory && savedHistory.length > 0) { numberHistory = savedHistory; ui.updateHistoryUI(); } // 로또 히스토리 데이터 로드 (수정) console.log('로또 히스토리 로드 시작...'); try { await api.loadLottoHistory(); console.log('로또 히스토리 로드 완료. lottoHistory:', lottoHistory); } catch (error) { console.error('로또 히스토리 로드 실패:', error); } // 현재 당첨번호 정보 로드 loadCurrentLottoInfo(); // 통계 업데이트 stats.updateStatsUI(); // 최신 회차 등급별 당첨 현황 로드 loadLatestRoundWinningStats(); // 당첨 통계 로드 loadWinningStats(); // 이번 주 추첨 통계 로드 loadUpcomingDrawStats(); // 동적 번호 프리뷰 시작 preview.start(); // FAQ 검색 기능 초기화 initFAQSearch(); // 공개 로그 초기 로드 (2초 후) setTimeout(() => { if (typeof publicLog !== 'undefined') { publicLog.refresh(); } }, 2000); // 30초마다 자동 새로고침 setInterval(() => { if (typeof publicLog !== 'undefined') { publicLog.refresh(); } }, 30000); // 30초마다 백그라운드 자동 업데이트 setInterval(() => { // 사용자가 번호 생성 중이 아닐 때만 업데이트 if (!isGenerating) { loadCurrentLottoInfo(); loadLatestRoundWinningStats(); loadWinningStats(); loadUpcomingDrawStats(); } }, 30000); // 30초 } // =================================== // 추가 기능 // =================================== /* FAQ 검색 기능 */ function initFAQSearch() { const searchInput = document.getElementById('faqSearch'); if (!searchInput) return; searchInput.addEventListener('input', function(e) { const query = e.target.value.toLowerCase(); const accordionItems = document.querySelectorAll('.accordion-item'); accordionItems.forEach(item => { const title = item.querySelector('.accordion-button').textContent.toLowerCase(); const content = item.querySelector('.accordion-body').textContent.toLowerCase(); if (title.includes(query) || content.includes(query)) { item.style.display = 'block'; } else { item.style.display = query ? 'none' : 'block'; } }); }); } /* 스크롤 관련 기능 */ function scrollToTop() { window.scrollTo({ top: 0, behavior: 'smooth' }); } /* 공유 기능 */ function shareApp() { if (navigator.share) { navigator.share({ title: '로또당첨번호 조합기', text: '무료 로또 번호 생성기/추출기로 행운의 번호를 만들어보세요!', url: window.location.href }).catch(() => fallbackShare()); } else { fallbackShare(); } } /* 공유 폴백 기능 */ function fallbackShare() { navigator.clipboard.writeText(window.location.href) .then(() => alert('URL이 복사되었습니다!')) .catch(() => alert('URL 복사에 실패했습니다.')); } /* 스크롤 이벤트 최적화 */ document.addEventListener('DOMContentLoaded', function() { const floatingButtons = document.querySelector('.floating-buttons'); let lastScrollY = window.scrollY; let ticking = false; if (floatingButtons) { window.addEventListener('scroll', () => { if (!ticking) { window.requestAnimationFrame(() => { floatingButtons.style.display = window.scrollY > 200 ? 'flex' : 'none'; ticking = false; }); ticking = true; } }); } }); // =================================== // 이벤트 리스너 등록 // =================================== /* 페이지 로드 시 초기화 */ document.addEventListener('DOMContentLoaded', initializePage); /* window 객체에 함수 노출 */ window.showRoundInfo = showRoundInfo; window.generateNumbers = generateNumbers; window.generateNumbersAndScrollToTop = generateNumbersAndScrollToTop; window.storage = storage; window.scrollToTop = scrollToTop; window.shareApp = shareApp; window.refreshPublicLog = refreshPublicLog; window.imageExport = imageExport; window.searchUserNumbers = function() { userSearch.searchNumbers(); }; window.loadWinningStats = loadWinningStats; window.showWeeklyWinnerDetails = showWeeklyWinnerDetails; window.showWinnerDetailPopup = showWinnerDetailPopup; window.loadLatestRoundWinningStats = loadLatestRoundWinningStats; window.showRoundWinnerDetails = showRoundWinnerDetails; window.handleRoundFilterClick = handleRoundFilterClick; window.loadUpcomingDrawStats = loadUpcomingDrawStats; window.loadCurrentLottoInfo = loadCurrentLottoInfo; /* 당첨자 상세 보기 함수 */ async function showWinnerDetails(rank = null) { try { const url = rank ? `api/get_winner_details.php?rank=${rank}` : 'api/get_winner_details.php'; const response = await fetch(url); const data = await response.json(); if (data.success) { displayWinnerDetailsModal(data.winners, rank); } else { alert('당첨자 정보를 불러오는데 실패했습니다: ' + data.message); } } catch (error) { console.error('당첨자 상세 정보 로드 실패:', error); alert('당첨자 정보를 불러오는데 실패했습니다.'); } } /* 주간 당첨자 상세 보기 함수 */ async function showWeeklyWinnerDetails(rank = null) { try { const url = `api/get_winner_details.php?week=current${rank ? `&rank=${rank}` : ''}`; const response = await fetch(url); const data = await response.json(); if (data.success) { displayWeeklyWinnerDetailsModal(data.winners, rank); } else { alert('이번 주 당첨자 정보를 불러오는데 실패했습니다: ' + data.message); } } catch (error) { console.error('주간 당첨자 상세 정보 로드 실패:', error); alert('이번 주 당첨자 정보를 불러오는데 실패했습니다.'); } } /* 주간 당첨자 상세 정보 모달 표시 */ function displayWeeklyWinnerDetailsModal(winners, rank) { const title = rank ? `이번 주 ${rank}등 당첨자` : '이번 주 전체 당첨자'; // 기존 모달과 동일한 구조이지만 "이번 주" 텍스트 추가 let winnersHtml = ''; if (winners.length === 0) { winnersHtml = `
이번 주 ${rank ? `${rank}등 당첨자가` : '당첨자가'} 아직 없습니다.
`; } else { winnersHtml = winners.map((winner, index) => { // 기존 displayWinnerDetailsModal과 동일한 로직 const numbers = winner.numbers.split(',').map(num => parseInt(num.trim())); const winningNumbers = winner.winning_numbers ? winner.winning_numbers.split(',').map(num => parseInt(num.trim())) : []; const bonusNumber = winner.bonus_number ? parseInt(winner.bonus_number) : null; const numbersHtml = numbers.map(num => { const isWinning = winningNumbers.includes(num); const isBonus = num === bonusNumber && winner.winning_rank === 2; let className = `lotto-mini-ball ${preview.getNumberClass(num)}`; if (isWinning) className += ' winning-highlight'; if (isBonus) className += ' bonus-highlight'; return `${num}`; }).join(''); const badgeColors = { 1: 'bg-warning text-dark', 2: 'bg-info', 3: 'bg-success', 4: 'bg-primary', 5: 'bg-secondary' }; return `
${winner.winning_rank}등 ${winner.user_initial} 이번 주 생성 • ${winner.matching_count}개 일치
${new Date(winner.created_at).toLocaleString('ko-KR')}
${numbersHtml}
`; }).join(''); } const modalHtml = ` `; // 기존 모달 제거 const existingModal = document.getElementById('weeklyWinnerModal'); if (existingModal) { existingModal.remove(); } // 새 모달 추가 document.body.insertAdjacentHTML('beforeend', modalHtml); const modal = new bootstrap.Modal(document.getElementById('weeklyWinnerModal')); modal.show(); } /* 개별 당첨자 상세 팝업 */ function showWinnerDetailPopup(userInitial, winningRank, numbers, matchingCount, createdAt) { const numberArray = numbers.split(',').map(num => parseInt(num.trim())); const rankEmojis = ['', '🥇', '🥈', '🥉', '🏅', '🎖️']; const rankNames = ['', '1등', '2등', '3등', '4등', '5등']; const rankColors = ['', 'warning', 'info', 'success', 'primary', 'secondary']; const numbersHtml = numberArray.map(num => { return `
${num}
`; }).join(''); const modalHtml = ` `; // 기존 팝업 제거 const existingPopup = document.getElementById('winnerDetailPopup'); if (existingPopup) { existingPopup.remove(); } // 새 팝업 추가 document.body.insertAdjacentHTML('beforeend', modalHtml); const modal = new bootstrap.Modal(document.getElementById('winnerDetailPopup')); modal.show(); }