Files
dodep-simulator/templates/admin/cases.html
T

370 lines
18 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.
{% extends "admin/base.html" %}
{% block title %}Кейсы — Админ-панель{% endblock %}
{% block nav_cases %}active{% endblock %}
{% block page_title %}📦 Управление кейсами{% endblock %}
{% block top_actions %}
<button class="a-btn a-btn-primary" onclick="openCreateCaseModal()"> Создать кейс</button>
{% endblock %}
{% block content %}
<div class="a-card">
<div class="a-card-header">📂 Секции главной страницы</div>
<div id="mainpageSections">
{% for section in mainpage %}
<div class="a-card" style="margin-bottom:0.75rem;padding:1rem" data-tab="{{ section.tab }}">
<div class="a-flex" style="justify-content:space-between">
<strong style="font-size:0.9rem">{{ section.tab }}</strong>
<div class="td-actions">
<button class="a-btn a-btn-sm" onclick="openAddCaseModal('{{ section.tab }}')"> Добавить кейс</button>
<button class="a-btn a-btn-danger a-btn-sm" onclick="deleteSection('{{ section.tab }}')"></button>
</div>
</div>
<div class="a-flex" style="flex-wrap:wrap;gap:0.5rem;margin-top:0.5rem">
{% for case_name in section.cases %}
<span style="background:rgba(255,255,255,0.04);border-radius:6px;padding:0.2rem 0.5rem;font-size:0.78rem;display:flex;align-items:center;gap:0.3rem">
🎲 {{ case_name }}
<span onclick="removeCaseFromSection('{{ section.tab }}','{{ case_name }}')" style="cursor:pointer;color:#ef4444;font-size:0.7rem"></span>
</span>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
<button class="a-btn a-mt" onclick="openAddSectionModal()"> Добавить секцию</button>
</div>
<div class="a-card">
<div class="a-card-header">🎲 Все кейсы</div>
<div class="a-table-wrap">
<table class="a-table">
<thead><tr><th>Название</th><th>Цена</th><th>Тип</th><th>Предметов</th><th></th></tr></thead>
<tbody>
{% for c in cases %}
<tr>
<td><strong>{{ c.name }}</strong></td>
<td>{{ c.price_open }} ₽</td>
<td style="font-size:0.75rem">{{ c.display_name|default('') }}</td>
<td>{{ c.items_count }}</td>
<td class="td-actions">
<button class="a-btn a-btn-sm" onclick="openEditCaseModal('{{ c.name }}')">✏️</button>
<button class="a-btn a-btn-danger a-btn-sm" onclick="deleteCase('{{ c.name }}')">🗑️</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
{% block modals %}
<!-- Create/Edit Case Modal -->
<div class="a-overlay" id="caseModal">
<div class="a-modal" style="max-width:600px">
<div class="a-modal-title" id="caseModalTitle">🎲 Создать кейс</div>
<input type="hidden" id="editCaseName">
<div class="a-row a-gap-sm">
<div class="a-mb" style="flex:1">
<label class="a-label">Название кейса (ID)</label>
<input type="text" id="caseNameInput" class="a-input" placeholder="my_case">
</div>
<div class="a-mb" style="flex:1">
<label class="a-label">Отображаемое название</label>
<input type="text" id="caseDisplayName" class="a-input" placeholder="Мой кейс">
</div>
</div>
<div class="a-row a-gap-sm">
<div class="a-mb" style="flex:1">
<label class="a-label">Цена открытия</label>
<input type="number" id="casePrice" class="a-input" value="250">
</div>
<div class="a-mb" style="flex:1">
<label class="a-label">Ссылка на изображение</label>
<input type="text" id="caseImage" class="a-input" placeholder="/static/cases/default.png">
</div>
</div>
<div class="a-mb">
<label class="a-label">Описание (необязательно)</label>
<input type="text" id="caseDescription" class="a-input" placeholder="Описание кейса...">
</div>
<div class="a-card" style="background:rgba(0,0,0,0.2);padding:0.75rem">
<div class="a-card-header" style="margin-bottom:0.5rem">📋 Предметы кейса</div>
<div id="caseItemsList"></div>
<div class="a-flex a-mt" style="flex-wrap:wrap">
<input type="text" id="caseItemSearch" class="a-input a-input-sm" style="flex:1;min-width:120px" placeholder="Поиск предмета..." oninput="searchCaseItems()">
<button class="a-btn a-btn-sm" onclick="openAllItemsModal()">📦 Все предметы</button>
</div>
</div>
<div class="a-modal-actions">
<button class="a-btn" onclick="closeModal('caseModal')">Отмена</button>
<button class="a-btn a-btn-primary" id="saveCaseBtn" onclick="saveCase()">💾 Сохранить</button>
</div>
</div>
</div>
<!-- All items browser modal -->
<div class="a-overlay" id="allItemsModal">
<div class="a-modal" style="max-width:700px;max-height:80vh;display:flex;flex-direction:column">
<div class="a-modal-title">📦 Все предметы</div>
<input type="text" id="allItemsSearch" class="a-input a-input-sm a-mb" placeholder="Поиск..." oninput="searchAllItems()">
<div id="allItemsGrid" style="flex:1;overflow-y:auto;display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:0.5rem"></div>
</div>
</div>
<!-- Add case to section modal -->
<div class="a-overlay" id="addCaseModal">
<div class="a-modal" style="max-width:400px">
<div class="a-modal-title">➕ Добавить кейс в секцию</div>
<p style="font-size:0.82rem;color:rgba(255,255,255,0.5);margin-bottom:0.75rem">Секция: <strong id="addCaseSectionName"></strong></p>
<div class="a-mb">
<label class="a-label">Выберите кейс</label>
<select id="addCaseSelect" class="a-input"></select>
</div>
<div class="a-modal-actions">
<button class="a-btn" onclick="closeModal('addCaseModal')">Отмена</button>
<button class="a-btn a-btn-primary" onclick="confirmAddCase()">Добавить</button>
</div>
</div>
</div>
<!-- Add section modal -->
<div class="a-overlay" id="addSectionModal">
<div class="a-modal" style="max-width:400px">
<div class="a-modal-title"> Новая секция</div>
<div class="a-mb">
<label class="a-label">Название вкладки</label>
<input type="text" id="newSectionName" class="a-input" placeholder="Новая коллекция">
</div>
<div class="a-modal-actions">
<button class="a-btn" onclick="closeModal('addSectionModal')">Отмена</button>
<button class="a-btn a-btn-primary" onclick="confirmAddSection()">Создать</button>
</div>
</div>
</div>
{% endblock %}
{% block extra_styles %}
<style>
.ci-item { display:flex; align-items:center; gap:0.5rem; padding:0.35rem 0; border-bottom:1px solid rgba(255,255,255,0.03); font-size:0.78rem; }
.ci-item img { width:32px; height:24px; object-fit:contain; }
.ci-item .ci-info { flex:1; }
.ci-item .ci-rarity { font-size:0.65rem; color:rgba(255,255,255,0.35); }
.all-item-card { background:rgba(0,0,0,0.15); border-radius:6px; padding:0.35rem; cursor:pointer; text-align:center; font-size:0.7rem; transition:all 0.15s; border:1px solid transparent; }
.all-item-card:hover { border-color:rgba(245,158,11,0.3); background:rgba(245,158,11,0.05); }
.all-item-card img { width:100%; height:40px; object-fit:contain; }
</style>
{% endblock %}
{% block scripts %}
<script>
let currentCaseItems = [];
let editingCaseName = null;
// ─── Modals ──────────────────────────────────────────────────────────────────
function closeModal(id) { document.getElementById(id).classList.remove('open'); }
function openModal(id) { document.getElementById(id).classList.add('open'); }
// ─── Load page data ──────────────────────────────────────────────────────────
let allCasesList = [];
document.addEventListener('DOMContentLoaded', async () => {
const r = await fetch('/admin/api/cases/list');
const d = await r.json();
allCasesList = d.cases || [];
});
// ─── Create / Edit Case ──────────────────────────────────────────────────────
function openCreateCaseModal() {
editingCaseName = null;
document.getElementById('caseModalTitle').textContent = '🎲 Создать кейс';
document.getElementById('saveCaseBtn').textContent = '💾 Создать';
document.getElementById('editCaseName').value = '';
document.getElementById('caseNameInput').value = '';
document.getElementById('caseDisplayName').value = '';
document.getElementById('casePrice').value = '250';
document.getElementById('caseImage').value = '';
document.getElementById('caseDescription').value = '';
currentCaseItems = [];
renderCaseItems();
openModal('caseModal');
}
function openEditCaseModal(caseName) {
editingCaseName = caseName;
document.getElementById('caseModalTitle').textContent = '✏️ Редактировать кейс';
document.getElementById('saveCaseBtn').textContent = '💾 Сохранить';
document.getElementById('editCaseName').value = caseName;
const cfg = {{ cases_config|tojson }};
const c = cfg[caseName] || {};
document.getElementById('caseNameInput').value = caseName;
document.getElementById('caseDisplayName').value = c.display_name || '';
document.getElementById('casePrice').value = c.price_open || 250;
document.getElementById('caseImage').value = c.image_url || '';
document.getElementById('caseDescription').value = c.description || '';
currentCaseItems = (c.items || []).filter(i => typeof i === 'object');
renderCaseItems();
openModal('caseModal');
}
function renderCaseItems() {
const div = document.getElementById('caseItemsList');
div.innerHTML = currentCaseItems.map((item, i) => `
<div class="ci-item">
<img src="${item.image_url || '/static/placeholder.png'}" onerror="this.src='/static/placeholder.png'">
<div class="ci-info">
<div>${item.market_hash_name || item.name || '???'}</div>
<div class="ci-rarity">${item.rarity || ''}${item.weapon || ''}</div>
</div>
<button class="a-btn a-btn-danger a-btn-sm" onclick="removeCaseItem(${i})">✕</button>
</div>
`).join('');
}
function removeCaseItem(idx) {
currentCaseItems.splice(idx, 1);
renderCaseItems();
}
function addCaseItem(item) {
if (currentCaseItems.some(i => (i.market_hash_name || i.name) === (item.market_hash_name || item.name))) return;
currentCaseItems.push(item);
renderCaseItems();
}
async function searchCaseItems() {
const q = document.getElementById('caseItemSearch').value;
if (q.length < 2) return;
const r = await fetch(`/admin/api/items/search?q=${encodeURIComponent(q)}`);
const items = await r.json();
// show as dropdown
let html = '<div style="margin-top:0.5rem;max-height:200px;overflow-y:auto">';
items.forEach(item => {
html += `<div class="ci-item" style="cursor:pointer" onclick="addCaseItem(${JSON.stringify(item).replace(/"/g,'&quot;')})">
<img src="${item.image_url || '/static/placeholder.png'}" onerror="this.src='/static/placeholder.png'">
<div class="ci-info"><div>${item.name}</div><div class="ci-rarity">${item.rarity}${item.price_rub||0} ₽</div></div>
</div>`;
});
html += '</div>';
const existing = document.getElementById('caseItemSearchResults');
if (!existing) {
const d = document.createElement('div'); d.id = 'caseItemSearchResults';
document.getElementById('caseItemSearch').parentNode.appendChild(d);
}
document.getElementById('caseItemSearchResults').innerHTML = html;
}
<script>if(typeof Notify==="undefined"){window.Notify={toast:function(m){alert(m)},error:function(m){alert(m)},success:function(m){alert(m)},info:function(m){alert(m)},confirm:function(m,t,c){if(c&&confirm(m))c(!0)}}}</script>
<script>
async function saveCase() {
const name = document.getElementById('caseNameInput').value.trim();
if (!name) { Notify.error('Введите название кейса'); return; }
const fd = new FormData();
fd.append('case_name', name);
fd.append('display_name', document.getElementById('caseDisplayName').value);
fd.append('price_open', document.getElementById('casePrice').value);
fd.append('image_url', document.getElementById('caseImage').value);
fd.append('description', document.getElementById('caseDescription').value);
fd.append('items_json', JSON.stringify(currentCaseItems));
const isEdit = editingCaseName !== null && editingCaseName !== name;
const url = isEdit
? `/admin/api/cases/update/${encodeURIComponent(editingCaseName)}`
: '/admin/api/cases/create';
const r = await fetch(url, { method:'POST', body:fd });
const d = await r.json();
if (d.success) { window.location.reload(); }
else Notify.error(d.error || 'Ошибка');
}
async function deleteCase(name) {
Notify.confirm(`Удалить кейс "${name}"?`, 'Подтверждение', async function(ok) {
if (!ok) return;
const fd = new FormData(); fd.append('case_name', name);
const r = await fetch(`/admin/api/cases/delete/${encodeURIComponent(name)}`, { method:'POST', body:fd });
const d = await r.json();
if (d.success) window.location.reload();
else Notify.error(d.error);
});
}
// ─── All Items Browser ───────────────────────────────────────────────────────
async function openAllItemsModal() {
openModal('allItemsModal');
document.getElementById('allItemsSearch').value = '';
await searchAllItems();
}
async function searchAllItems() {
const q = document.getElementById('allItemsSearch').value;
const r = await fetch(`/admin/api/items/search?q=${encodeURIComponent(q)}&limit=200`);
const items = await r.json();
document.getElementById('allItemsGrid').innerHTML = items.map(item => `
<div class="all-item-card" onclick="addCaseItemFromAll(${JSON.stringify(item).replace(/"/g,'&quot;')})">
<img src="${item.image_url || '/static/placeholder.png'}" onerror="this.src='/static/placeholder.png'">
<div style="margin-top:0.2rem;line-height:1.15">${item.name}</div>
<div style="font-size:0.6rem;color:rgba(255,255,255,0.35)">${item.rarity}</div>
</div>
`).join('');
}
function addCaseItemFromAll(item) {
addCaseItem(item);
closeModal('allItemsModal');
}
// ─── Mainpage Sections ───────────────────────────────────────────────────────
function openAddSectionModal() {
document.getElementById('newSectionName').value = '';
openModal('addSectionModal');
}
async function confirmAddSection() {
const name = document.getElementById('newSectionName').value.trim();
if (!name) return;
const fd = new FormData(); fd.append('tab', name);
const r = await fetch('/admin/api/mainpage/section/add', { method:'POST', body:fd });
const d = await r.json();
if (d.success) window.location.reload();
else Notify.error(d.error);
}
async function deleteSection(tab) {
Notify.confirm(`Удалить секцию "${tab}"?`, 'Подтверждение', async function(ok) {
if (!ok) return;
const fd = new FormData(); fd.append('tab', tab);
const r = await fetch('/admin/api/mainpage/section/delete', { method:'POST', body:fd });
const d = await r.json();
if (d.success) window.location.reload();
else Notify.error(d.error);
});
}
function openAddCaseModal(tab) {
document.getElementById('addCaseSectionName').textContent = tab;
const sel = document.getElementById('addCaseSelect');
sel.innerHTML = allCasesList.map(c => `<option value="${c}">${c}</option>`).join('');
sel.dataset.tab = tab;
openModal('addCaseModal');
}
async function confirmAddCase() {
const tab = document.getElementById('addCaseSelect').dataset.tab;
const caseName = document.getElementById('addCaseSelect').value;
const fd = new FormData();
fd.append('tab', tab);
fd.append('case_name', caseName);
const r = await fetch('/admin/api/mainpage/section/add-case', { method:'POST', body:fd });
const d = await r.json();
if (d.success) window.location.reload();
else Notify.error(d.error);
}
async function removeCaseFromSection(tab, caseName) {
Notify.confirm(`Убрать "${caseName}" из "${tab}"?`, 'Подтверждение', async function(ok) {
if (!ok) return;
const fd = new FormData();
fd.append('tab', tab);
fd.append('case_name', caseName);
const r = await fetch('/admin/api/mainpage/section/remove-case', { method:'POST', body:fd });
const d = await r.json();
if (d.success) window.location.reload();
else Notify.error(d.error);
});
}
</script>
{% endblock %}