MpakoPhone/web_app.html

405 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>МракоЗвон — управление</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background: #0f0f0f;
color: #fff;
padding: 16px;
margin: 0;
}
.container {
max-width: 700px;
margin: 0 auto;
}
.card {
background: #1e1e1e;
border-radius: 16px;
padding: 20px;
margin-bottom: 16px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 10px;
}
h2 {
font-size: 22px;
color: #6ab3f3;
}
h3 {
font-size: 18px;
margin-bottom: 12px;
color: #aaa;
}
.total {
background: #2d7a2d;
padding: 6px 14px;
border-radius: 20px;
font-size: 14px;
font-weight: 600;
}
.record-card {
background: #2a2a2a;
border-radius: 12px;
padding: 14px 16px;
margin-bottom: 10px;
border-left: 4px solid;
}
.record-card.phone {
border-left-color: #2d7a2d;
}
.record-card.qr {
border-left-color: #6ab3f3;
}
.record-card.both {
border-left-color: #f3b33d;
}
.phone-number {
font-size: 16px;
font-weight: 500;
font-family: monospace;
color: #fff;
}
.qr-data {
font-size: 12px;
font-family: monospace;
color: #888;
word-break: break-all;
}
.badge {
display: inline-block;
padding: 2px 8px;
border-radius: 12px;
font-size: 10px;
font-weight: 600;
margin-right: 8px;
}
.badge-phone {
background: #2d7a2d;
color: #fff;
}
.badge-qr {
background: #6ab3f3;
color: #000;
}
.badge-both {
background: #f3b33d;
color: #000;
}
.meta {
font-size: 11px;
color: #888;
margin-top: 6px;
}
.btn-group {
display: flex;
gap: 8px;
margin-top: 10px;
}
.show-qr-btn {
background: #6ab3f3;
border: none;
color: #000;
padding: 6px 12px;
border-radius: 20px;
font-size: 12px;
cursor: pointer;
}
.delete-btn {
background: #dc2626;
border: none;
color: white;
padding: 6px 12px;
border-radius: 20px;
font-size: 12px;
cursor: pointer;
}
.show-qr-btn:active {
background: #4a8bc0;
}
.delete-btn:active {
background: #b91c1c;
}
.loading {
text-align: center;
padding: 60px 20px;
color: #aaa;
}
.empty {
text-align: center;
padding: 40px;
color: #666;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.tab-btn {
flex: 1;
background: #2d2d2d;
border: none;
color: white;
padding: 12px;
border-radius: 12px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
}
.tab-btn.active {
background: #2d7a2d;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
</style>
<script src="https://telegram.org/js/telegram-web-app.js"></script>
</head>
<body>
<div class="container">
<div class="card">
<div class="header">
<h2>🌑 МракоЗвон</h2>
<div class="total" id="totalCount">Загрузка...</div>
</div>
<div class="tabs">
<button class="tab-btn active" onclick="showTab('all')">📋 Все</button>
<button class="tab-btn" onclick="showTab('phones')">📞 Номера</button>
<button class="tab-btn" onclick="showTab('qrs')">🃏 Карты</button>
<button class="tab-btn" onclick="showTab('both')">🔗 Номер+Карта</button>
</div>
<div id="tab-all" class="tab-content active">
<div id="list-all"></div>
</div>
<div id="tab-phones" class="tab-content">
<div id="list-phones"></div>
</div>
<div id="tab-qrs" class="tab-content">
<div id="list-qrs"></div>
</div>
<div id="tab-both" class="tab-content">
<div id="list-both"></div>
</div>
</div>
</div>
<script>
const tg = window.Telegram.WebApp;
tg.expand();
let currentData = [];
async function loadData() {
try {
const response = await fetch('/get_records');
const data = await response.json();
currentData = data.records || [];
updateCounters();
renderAll();
renderPhones();
renderQRs();
renderBoth();
} catch (e) {
console.error(e);
document.getElementById('list-all').innerHTML = '<div class="empty">⚠️ Ошибка загрузки</div>';
}
}
function updateCounters() {
const total = currentData.length;
const phones = currentData.filter(r => r.type === 'phone').length;
const qrs = currentData.filter(r => r.type === 'qr').length;
const both = currentData.filter(r => r.type === 'both').length;
document.getElementById('totalCount').innerHTML = `📊 ${total} | 📞 ${phones} | 🃏 ${qrs} | 🔗 ${both}`;
}
function getTypeBadge(type) {
if (type === 'phone') return '<span class="badge badge-phone">📞 Только номер</span>';
if (type === 'qr') return '<span class="badge badge-qr">🃏 Только карта</span>';
return '<span class="badge badge-both">🔗 Номер + карта</span>';
}
function getRecordHTML(record) {
const created = new Date(record.created_at).toLocaleString('ru');
const used = record.used_at ? new Date(record.used_at).toLocaleString('ru') : '—';
const badge = getTypeBadge(record.type);
let content = '';
if (record.type === 'phone' || record.type === 'both') {
content += `<div class="phone-number">📱 ${record.phone}</div>`;
}
if (record.type === 'qr' || record.type === 'both') {
const shortData = record.qr_data ? record.qr_data.slice(0, 50) + (record.qr_data.length > 50 ? '...' : '') : '—';
content += `<div class="qr-data">🃏 Данные карты: ${shortData}</div>`;
}
const showQRBtn = (record.type === 'qr' || record.type === 'both') && record.qr_code ?
`<button class="show-qr-btn" data-id="${record.id}">🖼️ Показать QR</button>` : '';
return `
<div class="record-card ${record.type}">
${badge}
${content}
<div class="meta"> Добавлен: ${created}</div>
<div class="meta">🔄 Использован: ${record.usage_count} раз, последний: ${used}</div>
<div class="btn-group">
${showQRBtn}
<button class="delete-btn" data-id="${record.id}">🗑 Удалить</button>
</div>
</div>
`;
}
function renderAll() {
const container = document.getElementById('list-all');
if (currentData.length === 0) {
container.innerHTML = '<div class="empty">📭 Нет записей</div>';
return;
}
container.innerHTML = currentData.map(r => getRecordHTML(r)).join('');
attachEvents();
}
function renderPhones() {
const container = document.getElementById('list-phones');
const phones = currentData.filter(r => r.type === 'phone');
if (phones.length === 0) {
container.innerHTML = '<div class="empty">📭 Нет номеров</div>';
return;
}
container.innerHTML = phones.map(r => getRecordHTML(r)).join('');
attachEvents();
}
function renderQRs() {
const container = document.getElementById('list-qrs');
const qrs = currentData.filter(r => r.type === 'qr');
if (qrs.length === 0) {
container.innerHTML = '<div class="empty">🃏 Нет бонусных карт</div>';
return;
}
container.innerHTML = qrs.map(r => getRecordHTML(r)).join('');
attachEvents();
}
function renderBoth() {
const container = document.getElementById('list-both');
const both = currentData.filter(r => r.type === 'both');
if (both.length === 0) {
container.innerHTML = '<div class="empty">🔗 Нет связок номер+карта</div>';
return;
}
container.innerHTML = both.map(r => getRecordHTML(r)).join('');
attachEvents();
}
async function showQRModal(recordId) {
try {
const response = await fetch(`/get_qr_code/${recordId}`);
const data = await response.json();
if (data.qr_code) {
const modal = document.createElement('div');
modal.style.position = 'fixed';
modal.style.top = '0';
modal.style.left = '0';
modal.style.width = '100%';
modal.style.height = '100%';
modal.style.background = 'rgba(0,0,0,0.95)';
modal.style.display = 'flex';
modal.style.justifyContent = 'center';
modal.style.alignItems = 'center';
modal.style.zIndex = '1000';
modal.style.cursor = 'pointer';
const img = document.createElement('img');
img.src = data.qr_code;
img.style.maxWidth = '85%';
img.style.maxHeight = '85%';
img.style.borderRadius = '12px';
modal.onclick = () => modal.remove();
modal.appendChild(img);
document.body.appendChild(modal);
} else {
alert('QR-код отсутствует');
}
} catch (e) {
alert('Ошибка загрузки QR');
}
}
async function deleteRecord(recordId) {
if (!confirm('Удалить запись?')) return;
try {
const response = await fetch('/delete_record', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({id: recordId})
});
if (response.ok) {
loadData();
} else {
alert('Ошибка при удалении');
}
} catch (e) {
alert('Ошибка');
}
}
function attachEvents() {
document.querySelectorAll('.show-qr-btn').forEach(btn => {
btn.removeEventListener('click', btn._handler);
btn._handler = () => showQRModal(btn.dataset.id);
btn.addEventListener('click', btn._handler);
});
document.querySelectorAll('.delete-btn').forEach(btn => {
btn.removeEventListener('click', btn._handler);
btn._handler = () => deleteRecord(btn.dataset.id);
btn.addEventListener('click', btn._handler);
});
}
function showTab(tabName) {
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
if (tabName === 'all') {
document.querySelector('.tab-btn:first-child').classList.add('active');
document.getElementById('tab-all').classList.add('active');
} else if (tabName === 'phones') {
document.querySelectorAll('.tab-btn')[1].classList.add('active');
document.getElementById('tab-phones').classList.add('active');
} else if (tabName === 'qrs') {
document.querySelectorAll('.tab-btn')[2].classList.add('active');
document.getElementById('tab-qrs').classList.add('active');
} else if (tabName === 'both') {
document.querySelectorAll('.tab-btn')[3].classList.add('active');
document.getElementById('tab-both').classList.add('active');
}
}
loadData();
</script>
</body>
</html>