feat: Enhance topic management and synchronization features
This commit is contained in:
@@ -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,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user