Current Directory:
/home/kridsana/webapp.cm.in.th/673190902/u67319090017/project-ipsc
Upload
Create File
File Name
Size
Actions
admin.html
16719 bytes
Edit
|
Delete
|
Rename
|
Download
index.html
9939 bytes
Edit
|
Delete
|
Rename
|
Download
<!DOCTYPE html> <html lang="th"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>🔐 Admin Panel - IPSC</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css"> <style> /* CSS Styling */ body { font-family: 'Kanit', sans-serif; margin: 0; padding: 20px; background-color: #343131; color: #f4f7f6; } .container { max-width: 900px; margin: auto; background: #222; padding: 20px; border-radius: 8px; box-shadow: 0 4px 10px rgba(0,0,0,0.5); } h1 { color: #A04747; text-align: center; } h3 { color: #A04747; border-bottom: 1px solid #A04747; padding-bottom: 5px; margin-bottom: 15px; } .admin-input-group { margin-bottom: 15px; padding: 10px; border: 1px solid #444; border-radius: 4px; } .admin-input-group label { display: block; margin-bottom: 5px; font-weight: bold; color: #ccc; } .admin-input-group input, .admin-input-group select { padding: 8px; width: 100%; box-sizing: border-box; margin-top: 5px; border: 1px solid #555; background-color: #333; color: white; border-radius: 4px; } .admin-input-group button, #advanced-edit-grid button { padding: 10px; width: 100%; margin-top: 5px; background-color: #00796b; color: white; cursor: pointer; border: none; /* ลบขอบปุ่ม */ border-radius: 4px; transition: background 0.3s; } .admin-input-group button:hover, #advanced-edit-grid button:hover { background-color: #004d40; /* สีเข้มขึ้นเมื่อ hover */ } /* Login/Tab Style */ #auth-panel { text-align: center; } #admin-controls { display: none; } .filename-display { font-size: 0.8em; color: #888; margin-top: 5px; } .tabs { display: flex; margin-bottom: 20px; } .tab-button { flex-grow: 1; padding: 12px; cursor: pointer; background-color: #444; color: #ccc; border: none; border-bottom: 3px solid transparent; font-size: 1.1em; transition: all 0.3s; } .tab-button:hover { background-color: #555; } .tab-button.active { background-color: #ccc; color: #A04747; border-bottom: 3px solid #A04747; } .tab-content { padding-top: 15px; } .hide { display: none; } /* --- Back Button (bottom-right) --- */ #back-link { position: fixed; bottom: 20px; right: 20px; background: #222; padding: 12px 18px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.4); z-index: 999; } #back-link a { color: #4CAF50; text-decoration: none; font-weight: bold; } /* --- DQ CARD STYLES --- */ #advanced-edit-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 15px; } .dq-card { background-color: #333; border-radius: 8px; padding: 15px; display: flex; flex-direction: column; align-items: center; border: 2px solid #555; transition: border-color 0.3s; } .dq-card.is-dq { background-color: #A04747; border-color: #D8A25E; } .dq-card-photo-container { width: 60px; height: 60px; margin-bottom: 10px; display: flex; align-items: center; justify-content: center; border-radius: 50%; overflow: hidden; background-color: #444; } .dq-card-photo-container img { width: 100%; height: 100%; object-fit: cover; } .dq-card-photo-container i { font-size: 40px; color: #A04747; } .dq-card.is-dq .dq-card-photo-container i { color: white; } .dq-card-name { font-size: 1.1em; font-weight: 600; margin-bottom: 5px; } .dq-card-time { font-size: 1em; color: #4CAF50; margin-bottom: 10px; } </style> </head> <body> <!-- 🔽 ลิงก์มุมขวาล่าง --> <p id="back-link"><a href="index.html">กลับไปยังหน้า Scoreboard</a></p> <div class="container"> <h1>🔐 IPSC Admin Panel</h1> <section id="auth-panel"> <h3>กรุณากรอกรหัสผ่านเพื่อเข้าสู่ระบบ</h3> <div class="admin-input-group"> <label for="password-input">รหัสผ่าน:</label> <input type="password" id="password-input"> <button onclick="checkPassword()">เข้าสู่ระบบ</button> </div> </section> <section id="admin-controls"> <div class="tabs"> <button class="tab-button active" onclick="openTab(event, 'basic-config')">1. กรอกข้อมูลผู้แข่งขัน / Excel</button> <button class="tab-button" onclick="openTab(event, 'advanced-edit')">2. จัดการสถานะ DQ</button> </div> <div id="basic-config" class="tab-content"> <h3>กำหนดจำนวนผู้เข้าแข่งขัน 👤</h3> <div class="admin-input-group"> <label for="num-competitors">จำนวน:</label> <input type="number" id="num-competitors" min="0" value="0"> <button onclick="setCompetitorCount()">บันทึก/ปรับจำนวน</button> </div> <hr> <h3>แก้ไขชื่อและอัปโหลดรูปภาพ 🖼️</h3> <div id="competitor-forms"></div> <hr> <h3>นำเข้าข้อมูลจาก Excel (.xlsx, .csv) 📥</h3> <div class="admin-input-group"> <label for="excel-file">เลือกไฟล์ Excel:</label> <input type="file" id="excel-file" accept=".xlsx, .xls, .csv"> <button onclick="handleExcelUpload()">อัปโหลดและประมวลผล</button> <small style="color:#aaa;">**(ไฟล์ควรมีคอลัมน์: Name)**</small> </div> </div> <div id="advanced-edit" class="tab-content hide"> <h3>จัดการสถานะ **ตกรอบ (DQ)** เท่านั้น 🚨</h3> <p style="color: #D8A25E;">* ข้อมูลเวลาจะถูกดึงจากระบบจับเวลาจริงเท่านั้น ไม่สามารถแก้ไขได้</p> <div id="advanced-edit-grid"></div> </div> <hr> <h3>สถานะการดึงข้อมูล 📡</h3> <div class="admin-input-group"> <small>หน้า Scoreboard (`index.html`) จะเริ่มดึงคะแนนอัตโนมัติเมื่อเปิดหน้าต่าง</small> </div> </section> </div> <script> const STORAGE_KEY = 'ipscCompetitors'; const MAX_IMAGE_SIZE = 2 * 1024 * 1024; const adminPassword = '1234'; let competitors = []; /* TAB */ function openTab(evt, tabName) { var tabcontent = document.getElementsByClassName("tab-content"); for (let t of tabcontent) t.classList.add("hide"); var tablinks = document.getElementsByClassName("tab-button"); for (let t of tablinks) t.classList.remove("active"); document.getElementById(tabName).classList.remove("hide"); evt.currentTarget.classList.add("active"); if (tabName === 'advanced-edit') renderAdvancedEdit(); } /* LOGIN */ function checkPassword() { const input = document.getElementById('password-input').value; if (input === adminPassword) { document.getElementById('auth-panel').style.display = 'none'; document.getElementById('admin-controls').style.display = 'block'; loadCompetitors(); } else { alert('รหัสผ่านไม่ถูกต้อง!'); document.getElementById('password-input').value = ''; } } /* LOAD */ function loadCompetitors() { const data = localStorage.getItem(STORAGE_KEY); if (data) { competitors = JSON.parse(data); competitors.forEach(c => { if (typeof c.isDQ === 'undefined') c.isDQ = false; }); document.getElementById('num-competitors').value = competitors.length; } renderAdminForms(); } function saveCompetitors() { localStorage.setItem(STORAGE_KEY, JSON.stringify(competitors)); } /* SET COUNT */ function setCompetitorCount() { const count = parseInt(document.getElementById('num-competitors').value); if (isNaN(count) || count < 0) return; const newCompetitors = []; for (let i = 0; i < count; i++) { if (competitors[i]) { newCompetitors.push(competitors[i]); } else { newCompetitors.push({ id: Date.now() + i, name: `Competitor ${i + 1}`, photo: '', photoFilename: '', time: 0, isDQ: false }); } } competitors = newCompetitors; saveCompetitors(); renderAdminForms(); } /* RENDER FORMS */ function renderAdminForms() { const formsDiv = document.getElementById('competitor-forms'); formsDiv.innerHTML = ''; competitors.forEach((c, index) => { const imageHtml = c.photo ? `<img src="${c.photo}" style="width: 30px; height: 30px; border-radius: 50%; margin-right: 10px;">` : `<i class="bi bi-person-fill" style="font-size: 30px; color: #d32f2f; margin-right: 10px;"></i>`; const card = document.createElement('div'); card.className = 'admin-input-group'; card.innerHTML = ` <h4>${imageHtml} ผู้เข้าแข่งขันคนที่ ${index + 1} (${c.id})</h4> <label>ชื่อ-นามสกุล:</label> <input type="text" value="${c.name}" onchange="updateCompetitor(${c.id}, 'name', this.value)"> <label>อัปโหลดรูปภาพ:</label> <input type="file" accept=".png,.jpg,.jpeg" onchange="handleImageUpload(${c.id}, this.files[0])"> <div class="filename-display">ชื่อไฟล์: ${c.photoFilename || 'ยังไม่มีรูปภาพ'}</div> `; formsDiv.appendChild(card); }); } function updateCompetitor(id, key, value) { const index = competitors.findIndex(c => c.id === id); if (index !== -1) { competitors[index][key] = value; saveCompetitors(); } } function handleImageUpload(id, file) { if (!file) return; if (file.size > MAX_IMAGE_SIZE) { alert('ขนาดรูปต้องไม่เกิน 2MB'); return; } const reader = new FileReader(); reader.onload = e => { const index = competitors.findIndex(c => c.id === id); if (index !== -1) { competitors[index].photo = e.target.result; competitors[index].photoFilename = file.name; saveCompetitors(); renderAdminForms(); } }; reader.readAsDataURL(file); } /* EXCEL UPLOAD */ function handleExcelUpload() { const file = document.getElementById('excel-file').files[0]; if (!file) return alert('กรุณาเลือกไฟล์ก่อน'); const reader = new FileReader(); reader.onload = e => { const workbook = XLSX.read(new Uint8Array(e.target.result), { type: 'array' }); const sheet = workbook.Sheets[workbook.SheetNames[0]]; const rows = XLSX.utils.sheet_to_json(sheet, { header: 1 }); if (rows.length < 2) return alert('ไฟล์ว่างเปล่า'); const headers = rows[0]; const nameIdx = headers.findIndex(h => h.toLowerCase().includes('name') || h.includes('ชื่อ')); if (nameIdx === -1) return alert('ต้องมีคอลัมน์ Name หรือ ชื่อ'); const newList = []; rows.slice(1).forEach((r, i) => { const name = String(r[nameIdx] || '').trim(); if (name && !competitors.some(c => c.name === name)) { newList.push({ id: Date.now() + i, name, photo: '', photoFilename: '', time: 0, isDQ: false }); } }); competitors = competitors.concat(newList); document.getElementById('num-competitors').value = competitors.length; saveCompetitors(); renderAdminForms(); alert(`เพิ่มผู้เข้าแข่งขันใหม่ ${newList.length} คน`); }; reader.readAsArrayBuffer(file); } /* DQ PANEL */ function renderAdvancedEdit() { const grid = document.getElementById('advanced-edit-grid'); grid.innerHTML = ''; competitors.forEach(c => { const card = document.createElement('div'); card.className = c.isDQ ? 'dq-card is-dq' : 'dq-card'; card.onclick = () => toggleDisqualification(c.id); const photoHtml = c.photo ? `<img src="${c.photo}">` : `<i class="bi bi-person-fill"></i>`; const timeText = c.isDQ ? 'DISQUALIFIED' : `${c.time.toFixed(3)} วินาที`; card.innerHTML = ` <div class="dq-card-photo-container">${photoHtml}</div> <div class="dq-card-name">${c.name}</div> <div class="dq-card-time">${timeText}</div> <button style="background:${c.isDQ ? '#FFC107' : '#d32f2f'}" onclick="event.stopPropagation(); toggleDisqualification(${c.id});"> ${c.isDQ ? 'ยกเลิก DQ' : 'ตั้งค่า DQ'} </button> <small style="margin-top:6px;">คลิกที่ Card เพื่อสลับสถานะ</small> `; grid.appendChild(card); }); } function toggleDisqualification(id) { const index = competitors.findIndex(c => c.id === id); if (index === -1) return; competitors[index].isDQ = !competitors[index].isDQ; if (competitors[index].isDQ) { competitors[index].time = 0; alert(`${competitors[index].name} ถูกตั้งค่า DQ แล้ว`); } else { alert(`ยกเลิกสถานะ DQ ของ ${competitors[index].name}`); } saveCompetitors(); renderAdvancedEdit(); } </script> </body> </html>
Save Changes