156 lines
6.3 KiB
HTML
156 lines
6.3 KiB
HTML
<!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="links">
|
||
<div class="layout">
|
||
<div class="sidebar" id="sidebar"></div>
|
||
<div class="main">
|
||
<h2>Ссылки</h2>
|
||
<p style="color:var(--muted);font-size:.85rem;margin-bottom:20px;max-width:680px">Каждая ссылка открывает публичную страницу с карточками записей выбранной группы за указанный период — без входа и выбора параметров.</p>
|
||
<div class="list" id="linksList"><div class="empty">Загрузка...</div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-overlay" id="linkModal" onclick="if(event.target===this)closeLinkModal()">
|
||
<div class="edit-modal">
|
||
<h3 id="linkModalTitle">Новая ссылка</h3>
|
||
<div>
|
||
<label>Название</label>
|
||
<input type="text" id="linkName" placeholder="Например: СБ1400 05.09">
|
||
</div>
|
||
<div>
|
||
<label>Группа</label>
|
||
<select id="linkGroup"></select>
|
||
</div>
|
||
<div style="display:flex;gap:12px">
|
||
<div style="flex:1">
|
||
<label>С</label>
|
||
<input type="date" id="linkDateFrom">
|
||
</div>
|
||
<div style="flex:1">
|
||
<label>По</label>
|
||
<input type="date" id="linkDateTo">
|
||
</div>
|
||
</div>
|
||
<div class="actions">
|
||
<button class="cancel" onclick="closeLinkModal()">Отмена</button>
|
||
<button class="save" onclick="saveLink()">Сохранить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toast" id="toast"></div>
|
||
<script src="admin.js"></script>
|
||
<script>
|
||
let links = [];
|
||
let groups = [];
|
||
let editingId = null;
|
||
|
||
function linkUrl(token) { return `${location.origin}/s/${token}`; }
|
||
|
||
async function loadLinks() {
|
||
const res = await fetch(`${API}/api/links`, { headers: hdr() });
|
||
links = await res.json();
|
||
const el = document.getElementById('linksList');
|
||
if (!links.length) { el.innerHTML = '<div class="empty">Ссылок пока нет. Создайте её в Журнале кнопкой «Ссылка».</div>'; return; }
|
||
el.innerHTML = links.map(l => `
|
||
<div class="item">
|
||
<div style="min-width:0">
|
||
<div class="name">${esc(l.name)}</div>
|
||
<div style="font-size:.8rem;color:var(--muted);margin-top:2px">
|
||
Группа: <b>${esc(l.group_name || '—')}</b>
|
||
${l.date_from || l.date_to ? ` · период: <b>${l.date_from ? l.date_from.split('-').reverse().join('.') : '…'}</b> — <b>${l.date_to ? l.date_to.split('-').reverse().join('.') : '…'}</b>` : ' · весь период'}
|
||
· создана ${new Date(l.created_at).toLocaleString('ru')}
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;gap:8px;flex-shrink:0;align-items:center">
|
||
<a class="btn-link" style="text-decoration:none" href="/s/${l.token}" target="_blank" title="Открыть">Открыть</a>
|
||
<button class="btn-link" onclick="copyText('${linkUrl(l.token)}')" title="Копировать ссылку">Копировать</button>
|
||
<button class="btn-link" onclick="openLinkEdit(${l.id})" title="Редактировать">✎</button>
|
||
<button class="btn-link" style="color:var(--muted)" onclick="delLink(${l.id})" title="Удалить">×</button>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function fmtDate(d) { const [y, m, day] = (d || '').split('-'); return d ? `${day}.${m}.${y}` : ''; }
|
||
|
||
async function openLinkModal() {
|
||
editingId = null;
|
||
document.getElementById('linkModalTitle').textContent = 'Новая ссылка';
|
||
document.getElementById('linkName').value = '';
|
||
document.getElementById('linkDateFrom').value = '';
|
||
document.getElementById('linkDateTo').value = '';
|
||
if (!groups.length) {
|
||
const res = await fetch(`${API}/api/groups`, { headers: hdr() });
|
||
groups = await res.json();
|
||
document.getElementById('linkGroup').innerHTML = groups.map(g => `<option value="${g.id}">${esc(g.name)}</option>`).join('');
|
||
}
|
||
document.getElementById('linkModal').classList.add('open');
|
||
}
|
||
|
||
async function openLinkEdit(id) {
|
||
const l = links.find(x => x.id === id);
|
||
if (!l) return;
|
||
editingId = id;
|
||
document.getElementById('linkModalTitle').textContent = 'Редактирование ссылки';
|
||
document.getElementById('linkName').value = l.name;
|
||
document.getElementById('linkDateFrom').value = l.date_from || '';
|
||
document.getElementById('linkDateTo').value = l.date_to || '';
|
||
if (!groups.length) {
|
||
const res = await fetch(`${API}/api/groups`, { headers: hdr() });
|
||
groups = await res.json();
|
||
document.getElementById('linkGroup').innerHTML = groups.map(g => `<option value="${g.id}">${esc(g.name)}</option>`).join('');
|
||
}
|
||
document.getElementById('linkGroup').value = l.group_id || '';
|
||
document.getElementById('linkModal').classList.add('open');
|
||
}
|
||
|
||
function closeLinkModal() {
|
||
document.getElementById('linkModal').classList.remove('open');
|
||
editingId = null;
|
||
}
|
||
|
||
async function saveLink() {
|
||
const name = document.getElementById('linkName').value.trim();
|
||
const group_id = document.getElementById('linkGroup').value;
|
||
const date_from = document.getElementById('linkDateFrom').value;
|
||
const date_to = document.getElementById('linkDateTo').value;
|
||
if (!name) { alert('Введите название'); return; }
|
||
const body = JSON.stringify({ name, group_id: group_id || null, date_from: date_from || null, date_to: date_to || null });
|
||
const res = await fetch(editingId ? `${API}/api/links/${editingId}` : `${API}/api/links`, {
|
||
method: editingId ? 'PUT' : 'POST',
|
||
headers: hdrJson(),
|
||
body
|
||
});
|
||
if (res.ok) {
|
||
closeLinkModal();
|
||
loadLinks();
|
||
showToast(editingId ? 'Ссылка обновлена' : 'Ссылка создана');
|
||
} else {
|
||
const err = await res.json();
|
||
alert(err.error || 'Ошибка');
|
||
}
|
||
}
|
||
|
||
async function delLink(id) {
|
||
if (!confirm('Удалить ссылку?')) return;
|
||
await fetch(`${API}/api/links/${id}`, { method: 'DELETE', headers: hdr() });
|
||
loadLinks();
|
||
showToast('Ссылка удалена');
|
||
}
|
||
|
||
(async () => {
|
||
if (await checkAuth()) {
|
||
buildSidebar(document.body.dataset.page);
|
||
loadLinks();
|
||
}
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html> |