add soft delete with trash page and group photo chronology with styling
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Корзина — Админ-панель</title>
|
||||
<link rel="stylesheet" href="admin.css">
|
||||
</head>
|
||||
<body data-page="trash">
|
||||
<div class="layout">
|
||||
<div class="sidebar" id="sidebar"></div>
|
||||
<div class="main">
|
||||
<h2>Корзина</h2>
|
||||
<div class="trash-head">
|
||||
<span class="trash-muted" id="trashInfo"></span>
|
||||
<button class="danger-btn" id="emptyBtn" onclick="emptyTrash()">Очистить корзину</button>
|
||||
</div>
|
||||
<div class="grid" id="grid"><div class="empty">Загрузка...</div></div>
|
||||
<div class="pager" id="pager"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-overlay" id="imgModal" onclick="this.classList.remove('open')">
|
||||
<img id="imgModalSrc">
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
<script src="admin.js"></script>
|
||||
<script>
|
||||
const PAGE_SIZE = 20;
|
||||
let currentEntries = [];
|
||||
let page = 1;
|
||||
let totalEntries = 0;
|
||||
|
||||
function isImageFile(name) {
|
||||
return /\.(jpe?g|png|gif|webp|bmp|avif|svg|ico)$/i.test(name || '');
|
||||
}
|
||||
|
||||
function filesHTML(files) {
|
||||
if (!files || !files.length) return '';
|
||||
return `<div class="entry-files">${files.map(f =>
|
||||
isImageFile(f.name)
|
||||
? `<a class="f-link" href="#" onclick="showImg('${API}/api/files/${f.token}');return false;" title="Открыть">${esc(f.name)}</a>`
|
||||
: `<a class="f-link" href="${API}/api/files/${f.token}" download title="Скачать">${esc(f.name)}</a>`
|
||||
).join('')}</div>`;
|
||||
}
|
||||
|
||||
function entryHTML(e) {
|
||||
return `
|
||||
<div class="card">
|
||||
${e.photo_path ? `<img src="${API}${e.photo_path}" onclick="showImg('${API}${e.photo_path}')" alt="">` : ''}
|
||||
<div class="info">
|
||||
<div class="name">${esc(e.student_name)}</div>
|
||||
<div class="group">${esc(e.group_name)}</div>
|
||||
<div class="desc">${esc(e.description)}</div>
|
||||
${filesHTML(e.files)}
|
||||
<div class="trash-time">Удалена: ${new Date(e.deleted_at).toLocaleString('ru')}</div>
|
||||
</div>
|
||||
<button class="restore-btn" onclick="restoreEntry(${e.id})" title="Восстановить">Восстановить</button>
|
||||
<button class="del-btn" onclick="hardDelete(${e.id})" title="Удалить навсегда (вместе с файлами)">×</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderGrid() {
|
||||
const grid = document.getElementById('grid');
|
||||
grid.className = 'grid';
|
||||
grid.innerHTML = currentEntries.length
|
||||
? currentEntries.map(entryHTML).join('')
|
||||
: '<div class="empty">Корзина пуста</div>';
|
||||
document.getElementById('trashInfo').textContent =
|
||||
totalEntries > 0 ? `Записей в корзине: ${totalEntries}` : '';
|
||||
document.getElementById('emptyBtn').disabled = totalEntries === 0;
|
||||
renderPager(document.getElementById('pager'), page, Math.max(1, Math.ceil(totalEntries / PAGE_SIZE)), goPage);
|
||||
}
|
||||
|
||||
async function loadTrash() {
|
||||
const p = new URLSearchParams();
|
||||
p.set('limit', PAGE_SIZE);
|
||||
p.set('offset', (page - 1) * PAGE_SIZE);
|
||||
const res = await fetch(`${API}/api/trash?${p}`, { headers: hdr() });
|
||||
const data = await res.json();
|
||||
currentEntries = data.entries;
|
||||
totalEntries = data.total;
|
||||
renderGrid();
|
||||
}
|
||||
|
||||
function goPage(p) { page = p; loadTrash(); }
|
||||
|
||||
async function restoreEntry(id) {
|
||||
const res = await fetch(`${API}/api/entries/${id}/restore`, { method: 'PUT', headers: hdr() });
|
||||
if (res.ok) { showToast('Запись восстановлена'); loadTrash(); }
|
||||
else { const err = await res.json(); alert(err.error || 'Ошибка'); }
|
||||
}
|
||||
|
||||
async function hardDelete(id) {
|
||||
if (!confirm('Удалить запись навсегда? Файлы и фото будут удалены безвозвратно.')) return;
|
||||
await fetch(`${API}/api/entries/${id}/permanent`, { method: 'DELETE', headers: hdr() });
|
||||
showToast('Запись удалена навсегда');
|
||||
loadTrash();
|
||||
}
|
||||
|
||||
async function emptyTrash() {
|
||||
if (totalEntries === 0) return;
|
||||
if (!confirm(`Удалить все записи из корзины (${totalEntries})? Файлы будут удалены безвозвратно.`)) return;
|
||||
const res = await fetch(`${API}/api/trash`, { method: 'DELETE', headers: hdr() });
|
||||
if (res.ok) { showToast('Корзина очищена'); page = 1; loadTrash(); }
|
||||
else { const err = await res.json(); alert(err.error || 'Ошибка'); }
|
||||
}
|
||||
|
||||
(async () => {
|
||||
if (await checkAuth()) {
|
||||
buildSidebar(document.body.dataset.page);
|
||||
loadTrash();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user