{% extends "base.twig" %}
{% block title %}{{ t('ui.admin.shoutbox.page_title') }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">{{ t('ui.admin.shoutbox.heading') }}</h1>
</div>
<div class="card shadow">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover" id="shoutboxTable">
<thead class="table-dark">
<tr>
<th>{{ t('ui.common.id', {}, locale, ['common']) }}</th>
<th>{{ t('ui.admin.shoutbox.user') }}</th>
<th>{{ t('ui.admin.shoutbox.message') }}</th>
<th>{{ t('ui.admin.shoutbox.status') }}</th>
<th>{{ t('ui.admin.shoutbox.created') }}</th>
<th>{{ t('ui.admin.shoutbox.actions') }}</th>
</tr>
</thead>
<tbody id="shoutboxTableBody">
<!-- Populated by JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
function uiT(key, fallback, params = {}) {
if (window.t) {
return window.t(key, params, fallback);
}
return fallback;
}
function apiMessage(payload, fallback) {
if (window.getApiMessage) {
return window.getApiMessage(payload, fallback);
}
return payload && payload.message ? payload.message : fallback;
}
function apiError(payload, fallback) {
if (window.getApiError) {
return window.getApiError(payload, fallback);
}
if (payload && payload.error) {
return String(payload.error);
}
return fallback;
}
document.addEventListener('DOMContentLoaded', function() {
loadI18nNamespaces(['common', 'errors']).then(function() {
loadShouts();
});
});
function loadShouts() {
fetch('/admin/api/shoutbox')
.then(response => response.json())
.then(data => {
displayShouts(data.messages || []);
})
.catch(error => {
console.error('Error loading shouts:', error);
showAlert(uiT('ui.admin.shoutbox.error_loading_shouts', 'Error loading shouts'), 'danger');
});
}
function displayShouts(messages) {
const tbody = document.getElementById('shoutboxTableBody');
tbody.innerHTML = '';
messages.forEach(msg => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${msg.id}</td>
<td>${escapeHtml(msg.username)}</td>
<td>${escapeHtml(msg.message)}</td>
<td>
<span class="badge bg-${msg.is_hidden ? 'secondary' : 'success'}">
${msg.is_hidden ? uiT('ui.admin.shoutbox.hidden', 'Hidden') : uiT('ui.admin.shoutbox.visible', 'Visible')}
</span>
</td>
<td>${new Date(msg.created_at).toLocaleString()}</td>
<td>
<div class="btn-group btn-group-sm" role="group">
${msg.is_hidden
? `<button type="button" class="btn btn-outline-success" onclick="toggleShout(${msg.id}, false)" title="${uiT('ui.admin.shoutbox.unhide', 'Unhide')}">
<i class="fas fa-eye"></i>
</button>`
: `<button type="button" class="btn btn-outline-warning" onclick="toggleShout(${msg.id}, true)" title="${uiT('ui.admin.shoutbox.hide', 'Hide')}">
<i class="fas fa-eye-slash"></i>
</button>`
}
<button type="button" class="btn btn-outline-danger" onclick="deleteShout(${msg.id})" title="${uiT('ui.common.delete', 'Delete')}">
<i class="fas fa-trash"></i>
</button>
</div>
</td>
`;
tbody.appendChild(row);
});
}
function toggleShout(id, hide) {
const endpoint = hide ? `/admin/api/shoutbox/${id}/hide` : `/admin/api/shoutbox/${id}/unhide`;
fetch(endpoint, { method: 'POST' })
.then(response => response.json())
.then(data => {
if (data.success) {
const fallbackMessage = hide
? uiT('ui.admin.shoutbox.hidden_success', 'Shout hidden successfully')
: uiT('ui.admin.shoutbox.unhidden_success', 'Shout unhidden successfully');
showAlert(apiMessage(data, fallbackMessage), 'success');
loadShouts();
} else {
showAlert(apiError(data, uiT('ui.admin.shoutbox.failed_to_update', 'Failed to update shout')), 'danger');
}
})
.catch(() => showAlert(uiT('ui.admin.shoutbox.failed_to_update', 'Failed to update shout'), 'danger'));
}
function deleteShout(id) {
if (!confirm(uiT('ui.admin.shoutbox.confirm_delete', 'Delete this shout?'))) {
return;
}
fetch(`/admin/api/shoutbox/${id}`, { method: 'DELETE' })
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert(apiMessage(data, uiT('ui.admin.shoutbox.deleted_success', 'Shout deleted successfully')), 'success');
loadShouts();
} else {
showAlert(apiError(data, uiT('ui.admin.shoutbox.failed_to_delete', 'Failed to delete shout')), 'danger');
}
})
.catch(() => showAlert(uiT('ui.admin.shoutbox.failed_to_delete', 'Failed to delete shout'), 'danger'));
}
function escapeHtml(text) {
const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
return String(text).replace(/[&<>"']/g, function(m) { return map[m]; });
}
function showAlert(message, type) {
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show`;
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
const container = document.querySelector('.container-fluid');
container.insertBefore(alertDiv, container.firstChild);
setTimeout(() => {
alertDiv.remove();
}, 5000);
}
</script>
{% endblock %}
|