| Current Path : /home/seto/testhive.pm/admin/ |
| Current File : /home/seto/testhive.pm/admin/assignments.php |
<?php
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/db.php';
require_once __DIR__ . '/_layout.php';
// Clear all assignments
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['clear_all'])) {
db()->exec("DELETE FROM page_assignments");
header('Location: /admin/assignments.php'); exit;
}
$campaignId = (int)($_GET['campaign_id'] ?? 0);
// Campaigns for filter dropdown
$campaigns = db()->query("SELECT id, name FROM campaigns ORDER BY name")->fetchAll();
// Assignments query — filter by campaign if selected, limit 500
$today = date('Y-m-d');
$where = $campaignId ? "AND pa.campaign_id = " . $campaignId : '';
$assignments = db()->query(
"SELECT pa.expires_at, pa.assigned_at, pa.page_id, pa.link_id,
d.domain, p.url,
c.name AS campaign_name,
l.text AS link_text,
(SELECT COUNT(*) FROM impressions_daily WHERE page_id=pa.page_id AND date='$today') AS page_imp_today,
(SELECT COUNT(*) FROM impressions_daily WHERE page_id=pa.page_id) AS page_imp_total,
(SELECT COUNT(*) FROM impressions_daily WHERE link_id=pa.link_id AND date='$today') AS link_imp_today,
(SELECT COUNT(*) FROM impressions_daily WHERE link_id=pa.link_id) AS link_imp_total
FROM page_assignments pa
JOIN pages p ON p.id = pa.page_id
JOIN donors d ON d.id = p.donor_id
JOIN campaigns c ON c.id = pa.campaign_id
JOIN links l ON l.id = pa.link_id
WHERE 1=1 $where
ORDER BY pa.expires_at ASC
LIMIT 500"
)->fetchAll();
$totalAssigned = (int)db()->query("SELECT COUNT(*) FROM page_assignments")->fetchColumn();
$totalPages = (int)db()->query("SELECT COUNT(*) FROM pages")->fetchColumn();
layout_head('Assignments');
?>
<!-- Toolbar -->
<div class="d-flex gap-2 mb-3 align-items-center flex-wrap">
<form method="post" class="d-inline" onsubmit="return confirm('Удалить ВСЕ assignments?')">
<input type="hidden" name="clear_all" value="1">
<button class="btn btn-outline-danger btn-sm">Clear All</button>
</form>
<button id="btn-redistribute" class="btn btn-primary btn-sm">Redistribute</button>
<div id="progress-wrap" class="d-none align-items-center gap-2">
<div class="progress" style="width:200px;height:20px">
<div id="progress-bar" class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" style="width:0%"></div>
</div>
<span id="progress-text" class="small text-muted">0 / 0</span>
</div>
<span class="text-muted small ms-2">
Assigned: <?= number_format($totalAssigned) ?> / <?= number_format($totalPages) ?> pages
</span>
</div>
<!-- Campaign filter -->
<div class="mb-3" style="max-width:300px">
<form method="get">
<select name="campaign_id" class="form-select form-select-sm" onchange="this.form.submit()">
<option value="0">— Все кампании —</option>
<?php foreach ($campaigns as $c): ?>
<option value="<?= $c['id'] ?>" <?= $campaignId === (int)$c['id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($c['name']) ?>
</option>
<?php endforeach ?>
</select>
</form>
</div>
<!-- Table -->
<div class="card shadow-sm">
<div class="table-responsive">
<table class="table table-sm table-hover mb-0 small">
<thead class="table-light">
<tr>
<th>Domain</th>
<th>URL</th>
<th>Campaign</th>
<th>Link text</th>
<th class="text-center">Стр. 👁 <span tabindex="0" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-html="true" data-bs-placement="top" data-bs-content="<div class='small'><b>Показы страницы</b><br>Сколько раз эта страница донора показала любую ссылку.<br><br>Формат: <b>сегодня</b> / всего</div>" style="display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;border-radius:50%;background:#adb5bd;color:#fff;font-size:10px;font-weight:600;cursor:default;vertical-align:middle;user-select:none">?</span></th>
<th class="text-center">Ссыл. 👁 <span tabindex="0" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-html="true" data-bs-placement="top" data-bs-content="<div class='small'><b>Показы ссылки</b><br>Сколько раз конкретный текст этой ссылки был показан на любой странице.<br><br>Формат: <b>сегодня</b> / всего</div>" style="display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;border-radius:50%;background:#adb5bd;color:#fff;font-size:10px;font-weight:600;cursor:default;vertical-align:middle;user-select:none">?</span></th>
<th>Expires</th>
</tr>
</thead>
<tbody>
<?php if (empty($assignments)): ?>
<tr><td colspan="7" class="text-center text-muted py-3">Нет assignments</td></tr>
<?php endif ?>
<?php
$sumPageToday = $sumPageTotal = $sumLinkToday = $sumLinkTotal = 0;
foreach ($assignments as $a):
$expired = time() > (int)$a['expires_at'];
$sumPageToday += (int)$a['page_imp_today'];
$sumPageTotal += (int)$a['page_imp_total'];
$sumLinkToday += (int)$a['link_imp_today'];
$sumLinkTotal += (int)$a['link_imp_total'];
?>
<tr class="<?= $expired ? 'table-warning' : '' ?>">
<td><?= htmlspecialchars($a['domain']) ?></td>
<td class="text-truncate" style="max-width:180px">
<?= htmlspecialchars($a['url']) ?>
<a href="<?= 'http://' . htmlspecialchars($a['domain']) . htmlspecialchars($a['url']) ?>" target="_blank" rel="noopener" class="ms-1 text-muted" title="Открыть страницу" style="opacity:.5;vertical-align:middle"><svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg></a>
</td>
<td><?= htmlspecialchars($a['campaign_name']) ?></td>
<td class="text-truncate" style="max-width:180px"><?= htmlspecialchars(mb_substr($a['link_text'], 0, 70)) ?></td>
<td class="text-center text-nowrap">
<span class="fw-semibold"><?= (int)$a['page_imp_today'] ?></span>
<span class="text-muted"> / <?= (int)$a['page_imp_total'] ?></span>
</td>
<td class="text-center text-nowrap">
<span class="fw-semibold"><?= (int)$a['link_imp_today'] ?></span>
<span class="text-muted"> / <?= (int)$a['link_imp_total'] ?></span>
</td>
<td class="text-nowrap"><?= $expired ? '<span class="text-danger">истёк</span> ' : '' ?><?= date('Y-m-d', (int)$a['expires_at']) ?></td>
</tr>
<?php endforeach ?>
<?php if (!empty($assignments)): ?>
<tr class="table-light fw-semibold">
<td colspan="4" class="text-end text-muted">Итого:</td>
<td class="text-center text-nowrap"><?= $sumPageToday ?> / <?= $sumPageTotal ?></td>
<td class="text-center text-nowrap"><?= $sumLinkToday ?> / <?= $sumLinkTotal ?></td>
<td></td>
</tr>
<?php endif ?>
</tbody>
</table>
</div>
</div>
<?php if (count($assignments) === 500): ?>
<p class="text-muted small mt-2">Показаны первые 500 записей. Используйте фильтр по кампании для детального просмотра.</p>
<?php endif ?>
<script>
document.getElementById('btn-redistribute').addEventListener('click', function () {
if (!confirm('Удалить все текущие assignments и перераспределить заново?')) return;
const btn = this;
const wrap = document.getElementById('progress-wrap');
const bar = document.getElementById('progress-bar');
const txt = document.getElementById('progress-text');
btn.disabled = true;
wrap.classList.remove('d-none');
wrap.classList.add('d-flex');
function runBatch(offset) {
fetch('/api/redistribute.php?offset=' + offset)
.then(r => r.json())
.then(data => {
const pct = data.total > 0 ? Math.round(data.done / data.total * 100) : 100;
bar.style.width = pct + '%';
txt.textContent = data.done + ' / ' + data.total;
if (data.finished) {
bar.classList.remove('progress-bar-animated');
bar.classList.add('bg-success');
txt.textContent = 'Готово! ' + data.done + ' страниц, assigned: ' + data.assigned;
btn.disabled = false;
// Reload after 1.5s to show updated counts
setTimeout(() => location.reload(), 1500);
} else {
runBatch(data.done);
}
})
.catch(err => {
txt.textContent = 'Ошибка: ' + err;
btn.disabled = false;
});
}
runBatch(0);
});
</script>
<script>
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('[data-bs-toggle="popover"]').forEach(function (el) {
new bootstrap.Popover(el, { sanitize: false });
});
});
</script>
<?php layout_foot() ?>