feat: Enhance topic management and synchronization features

This commit is contained in:
2026-02-21 00:09:48 +01:00
parent b2ac61c904
commit 10ddd2ac1c
4 changed files with 116 additions and 120 deletions

View File

@@ -109,13 +109,21 @@ class SiteController extends Controller
$topics = Topic::findBySiteWithGlobal((int) $id);
$globalTopics = GlobalTopic::findAllGrouped();
$assignedGlobalIds = array_filter(array_column($topics, 'global_topic_id'));
$assignedGlobalIds = array_values(array_unique(array_map(
static fn($v) => (int) $v,
array_filter(array_column($topics, 'global_topic_id'))
)));
$assignedTopicNames = array_values(array_unique(array_filter(array_map(
static fn($name) => mb_strtolower(trim((string) $name)),
array_column($topics, 'name')
))));
$this->view('sites/edit', [
'site' => $site,
'topics' => $topics,
'globalTopics' => $globalTopics,
'assignedGlobalIds' => $assignedGlobalIds,
'assignedTopicNames' => $assignedTopicNames,
]);
}

View File

@@ -24,8 +24,22 @@ class TopicController extends Controller
$topics = Topic::findBySiteWithGlobal((int) $id);
$globalTopics = GlobalTopic::findAllGrouped();
$assignedGlobalIds = array_values(array_unique(array_map(
static fn($v) => (int) $v,
array_filter(array_column($topics, 'global_topic_id'))
)));
$assignedTopicNames = array_values(array_unique(array_filter(array_map(
static fn($name) => mb_strtolower(trim((string) $name)),
array_column($topics, 'name')
))));
$this->view('topics/index', ['site' => $site, 'topics' => $topics, 'globalTopics' => $globalTopics]);
$this->view('topics/index', [
'site' => $site,
'topics' => $topics,
'globalTopics' => $globalTopics,
'assignedGlobalIds' => $assignedGlobalIds,
'assignedTopicNames' => $assignedTopicNames,
]);
}
public function store(string $id): void
@@ -51,7 +65,7 @@ class TopicController extends Controller
]);
$this->flash('success', 'Temat został dodany.');
$this->redirect("/sites/{$id}/topics");
$this->redirect("/sites/{$id}/topics?topic_added=1");
}
public function update(string $id): void

View File

@@ -180,128 +180,18 @@
</div>
<div class="col-lg-5">
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Tematy (<?= count($topics) ?>)</h5>
<a href="/sites/<?= $site['id'] ?>/topics" class="btn btn-sm btn-outline-info">
<i class="bi bi-pencil me-1"></i>Zarządzaj
</a>
</div>
<div class="card-body p-0">
<?php if (empty($topics)): ?>
<div class="p-3 text-muted text-center">Brak tematów. Dodaj z biblioteki poniżej.</div>
<?php else: ?>
<table class="table table-hover table-sm mb-0">
<thead>
<tr>
<th>Temat</th>
<th>Kategoria</th>
<th>Art.</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($topics as $topic): ?>
<tr>
<td>
<?= htmlspecialchars($topic['name']) ?>
<?php if (!$topic['is_active']): ?>
<span class="badge bg-secondary">off</span>
<?php endif; ?>
</td>
<td class="small text-muted"><?= htmlspecialchars($topic['global_category_name'] ?? '') ?></td>
<td><span class="badge bg-primary"><?= $topic['article_count'] ?></span></td>
<td>
<button class="btn btn-sm btn-outline-danger py-0 px-1 btn-delete-topic" data-id="<?= $topic['id'] ?>" data-name="<?= htmlspecialchars($topic['name']) ?>">
<i class="bi bi-x"></i>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
<div class="card">
<div class="card-header"><h5 class="mb-0">Dodaj temat z biblioteki</h5></div>
<div class="card-header"><h5 class="mb-0">Tematy strony</h5></div>
<div class="card-body">
<form method="post" action="/sites/<?= $site['id'] ?>/topics">
<div class="mb-3">
<select class="form-select" name="global_topic_id" id="quick_global_topic" onchange="quickFillTopic(this)" required>
<option value="">Wybierz temat...</option>
<?php foreach ($globalTopics as $cat): ?>
<optgroup label="<?= htmlspecialchars($cat['name']) ?>">
<?php foreach ($cat['children'] as $child): ?>
<?php $alreadyAssigned = in_array($child['id'], $assignedGlobalIds); ?>
<option value="<?= $child['id'] ?>"
data-name="<?= htmlspecialchars($child['name']) ?>"
data-desc="<?= htmlspecialchars($child['description'] ?? '') ?>"
<?= $alreadyAssigned ? 'disabled' : '' ?>>
<?= htmlspecialchars($child['name']) ?><?= $alreadyAssigned ? ' (dodany)' : '' ?>
</option>
<?php endforeach; ?>
</optgroup>
<?php endforeach; ?>
</select>
</div>
<input type="hidden" name="name" id="quick_topic_name">
<input type="hidden" name="description" id="quick_topic_desc">
<input type="hidden" name="is_active" value="1">
<button type="submit" class="btn btn-success btn-sm w-100">
<i class="bi bi-plus-lg me-1"></i>Dodaj
</button>
</form>
<p class="text-muted small mb-3">
Zarządzanie tematami zostało przeniesione do dedykowanego widoku, aby nie duplikować miejsc edycji.
</p>
<a href="/sites/<?= $site['id'] ?>/topics" class="btn btn-outline-info w-100">
<i class="bi bi-tags me-1"></i>Przejdź do zarządzania tematami
</a>
</div>
</div>
</div>
</div>
<script>
function quickFillTopic(select) {
var opt = select.options[select.selectedIndex];
document.getElementById('quick_topic_name').value = opt.dataset.name || '';
document.getElementById('quick_topic_desc').value = opt.dataset.desc || '';
}
document.querySelectorAll('.btn-delete-topic').forEach(function (btn) {
btn.addEventListener('click', async function () {
var id = this.dataset.id;
var name = this.dataset.name;
var ok = await backproConfirm('Usunac temat "' + name + '"?', {
title: 'Usuwanie tematu',
confirmText: 'Usun',
cancelText: 'Anuluj',
confirmClass: 'btn-danger'
});
if (!ok) return;
var row = this.closest('tr');
btn.disabled = true;
fetch('/topics/' + id + '/delete', {
method: 'POST',
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(function (r) { return r.json(); })
.then(function (data) {
if (data.success) {
row.style.transition = 'opacity .3s';
row.style.opacity = '0';
setTimeout(function () { row.remove(); }, 300);
backproNotify('Temat zostal usuniety.', 'success', { delay: 2500 });
} else {
backproNotify(data.message || 'Blad usuwania', 'danger');
btn.disabled = false;
}
})
.catch(function () {
backproNotify('Blad polaczenia', 'danger');
btn.disabled = false;
});
});
});
</script>

View File

@@ -97,6 +97,16 @@
</div>
<div class="card-body">
<form method="post" action="/sites/<?= $site['id'] ?>/topics" id="topicForm">
<?php
$assignedGlobalIdSet = [];
foreach (($assignedGlobalIds ?? []) as $gid) {
$assignedGlobalIdSet[(int) $gid] = true;
}
$assignedNameSet = [];
foreach (($assignedTopicNames ?? []) as $tname) {
$assignedNameSet[mb_strtolower(trim((string) $tname))] = true;
}
?>
<div class="mb-3">
<label class="form-label">Wybierz z biblioteki</label>
@@ -105,6 +115,12 @@
<?php foreach ($globalTopics as $cat): ?>
<optgroup label="<?= htmlspecialchars($cat['name']) ?>">
<?php foreach ($cat['children'] as $child): ?>
<?php
$childId = (int) $child['id'];
$childNameKey = mb_strtolower(trim((string) ($child['name'] ?? '')));
$alreadyAssigned = isset($assignedGlobalIdSet[$childId]) || isset($assignedNameSet[$childNameKey]);
?>
<?php if ($alreadyAssigned) continue; ?>
<option value="<?= $child['id'] ?>"
data-name="<?= htmlspecialchars($child['name']) ?>"
data-desc="<?= htmlspecialchars($child['description'] ?? '') ?>">
@@ -151,6 +167,8 @@
</div>
<script>
var siteId = <?= (int) $site['id'] ?>;
function fillFromLibrary(select) {
var opt = select.options[select.selectedIndex];
if (opt.value) {
@@ -205,4 +223,70 @@ document.querySelectorAll('.btn-delete-topic').forEach(function (btn) {
});
});
});
(function () {
var params = new URLSearchParams(window.location.search);
if (params.get('topic_added') !== '1') {
return;
}
// Prevent repeated prompt on refresh.
params.delete('topic_added');
var nextQuery = params.toString();
var nextUrl = window.location.pathname + (nextQuery ? ('?' + nextQuery) : '');
window.history.replaceState({}, '', nextUrl);
setTimeout(async function () {
function notify(message, type) {
if (typeof window.backproNotify === 'function') {
window.backproNotify(message, type || 'info');
return;
}
window.alert(message);
}
var ok = false;
if (typeof window.backproConfirm === 'function') {
ok = await window.backproConfirm(
'Temat został dodany. Czy chcesz od razu zsynchronizować kategorie w WordPressie na podstawie tematów?',
{
title: 'Synchronizacja kategorii',
confirmText: 'Tak, synchronizuj',
cancelText: 'Nie teraz',
confirmClass: 'btn-success'
}
);
} else {
ok = window.confirm('Temat został dodany. Czy chcesz od razu zsynchronizować kategorie w WordPressie?');
}
if (!ok) {
return;
}
fetch('/sites/' + siteId + '/categories/from-topics', {
method: 'POST',
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(function (r) { return r.json(); })
.then(function (data) {
if (data && data.success) {
var hasErrors = Array.isArray(data.errors) && data.errors.length > 0;
if (hasErrors) {
notify(data.message || 'Synchronizacja zakończona częściowo (z błędami).', 'warning');
} else {
notify(data.message || 'Kategorie zostały zsynchronizowane.', 'success');
}
setTimeout(function () { window.location.reload(); }, 1800);
return;
}
notify((data && data.message) ? data.message : 'Nie udało się zsynchronizować kategorii.', 'danger');
})
.catch(function () {
notify('Błąd połączenia podczas synchronizacji kategorii.', 'danger');
});
}, 120);
})();
</script>