Dodaj stylizowane modale i link Audyt SEO w menu admina

- Zamieniono natywne confirm/alert na ładne modale CSS
- Dodano link "Audyt SEO" w menu administracyjnym
- Modal z animacją slide-in, ikony ostrzeżenia/info
- Obsługa zamykania przez backdrop

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-01-08 15:34:06 +01:00
parent 892cfcc39a
commit 90ca2bc618
2 changed files with 212 additions and 35 deletions

View File

@ -347,6 +347,95 @@
grid-template-columns: 1fr 1fr;
}
}
/* Modal styles */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal.active {
display: flex;
}
.modal-content {
background: var(--surface);
padding: var(--spacing-xl);
border-radius: var(--radius-lg);
max-width: 480px;
width: 90%;
box-shadow: var(--shadow-lg);
animation: modalSlideIn 0.2s ease-out;
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.modal-header {
display: flex;
align-items: center;
gap: var(--spacing-md);
margin-bottom: var(--spacing-md);
}
.modal-icon {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.modal-icon.warning {
background: #fef3c7;
color: #d97706;
}
.modal-icon.success {
background: #dcfce7;
color: #16a34a;
}
.modal-icon.info {
background: #dbeafe;
color: #2563eb;
}
.modal-title {
font-size: var(--font-size-xl);
font-weight: 600;
color: var(--text-primary);
}
.modal-body {
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: var(--spacing-lg);
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: var(--spacing-sm);
}
</style>
{% endblock %}
@ -570,11 +659,92 @@
<p>Nie znaleziono firm z danymi SEO.</p>
</div>
{% endif %}
<!-- Confirmation Modal -->
<div class="modal" id="confirmModal">
<div class="modal-content">
<div class="modal-header">
<div class="modal-icon warning">
<svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
</svg>
</div>
<div class="modal-title" id="modalTitle">Potwierdz operacje</div>
</div>
<div class="modal-body" id="modalBody">
Czy na pewno chcesz wykonac te operacje?
</div>
<div class="modal-footer">
<button class="btn btn-outline" onclick="closeModal()">Anuluj</button>
<button class="btn btn-primary" onclick="confirmModalAction()">Potwierdz</button>
</div>
</div>
</div>
<!-- Info Modal -->
<div class="modal" id="infoModal">
<div class="modal-content">
<div class="modal-header">
<div class="modal-icon info">
<svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<div class="modal-title" id="infoModalTitle">Informacja</div>
</div>
<div class="modal-body" id="infoModalBody">
Tresc informacji.
</div>
<div class="modal-footer">
<button class="btn btn-primary" onclick="closeInfoModal()">OK</button>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
const csrfToken = '{{ csrf_token() }}';
// Modal functions
let pendingModalAction = null;
function showModal(title, body, onConfirm) {
document.getElementById('modalTitle').textContent = title;
document.getElementById('modalBody').textContent = body;
pendingModalAction = onConfirm;
document.getElementById('confirmModal').classList.add('active');
}
function closeModal() {
document.getElementById('confirmModal').classList.remove('active');
pendingModalAction = null;
}
function confirmModalAction() {
if (pendingModalAction) {
pendingModalAction();
}
closeModal();
}
function showInfoModal(title, body) {
document.getElementById('infoModalTitle').textContent = title;
document.getElementById('infoModalBody').textContent = body;
document.getElementById('infoModal').classList.add('active');
}
function closeInfoModal() {
document.getElementById('infoModal').classList.remove('active');
}
// Close modal on backdrop click
document.getElementById('confirmModal')?.addEventListener('click', (e) => {
if (e.target.id === 'confirmModal') closeModal();
});
document.getElementById('infoModal')?.addEventListener('click', (e) => {
if (e.target.id === 'infoModal') closeInfoModal();
});
// Sorting state
let currentSort = { column: 'overall', direction: 'desc' };
@ -676,47 +846,53 @@ function resetFilters() {
}
// Audit functions
async function runSingleAudit(slug) {
if (!confirm('Czy na pewno chcesz uruchomic audyt SEO dla tej firmy? Moze to potrwac kilka minut.')) {
return;
}
function runSingleAudit(slug) {
showModal(
'Uruchom audyt SEO',
'Czy na pewno chcesz uruchomić audyt SEO dla tej firmy? Analiza może potrwać kilka minut.',
async () => {
try {
const response = await fetch('/api/seo/audit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify({ slug: slug })
});
try {
const response = await fetch('/api/seo/audit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify({ slug: slug })
});
const data = await response.json();
const data = await response.json();
if (response.ok && data.success) {
alert('Audyt SEO zakonczony pomyslnie! Odswiezam strone...');
location.reload();
} else {
alert('Blad: ' + (data.error || 'Nieznany blad'));
if (response.ok && data.success) {
showInfoModal('Audyt zakończony', 'Audyt SEO zakończony pomyślnie! Strona zostanie odświeżona.');
setTimeout(() => location.reload(), 1500);
} else {
showInfoModal('Błąd', 'Wystąpił błąd: ' + (data.error || 'Nieznany błąd'));
}
} catch (error) {
showInfoModal('Błąd połączenia', 'Nie udało się połączyć z serwerem: ' + error.message);
}
}
} catch (error) {
alert('Blad polaczenia: ' + error.message);
}
);
}
async function runBatchAudit() {
if (!confirm('Czy na pewno chcesz uruchomic audyt SEO dla wszystkich firm? To moze potrwac dluzszy czas.')) {
return;
}
function runBatchAudit() {
showModal(
'Uruchom audyt wsadowy',
'Czy na pewno chcesz uruchomić audyt SEO dla wszystkich firm? To może potrwać dłuższy czas.',
() => {
const btn = document.getElementById('batchAuditBtn');
const originalText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span>Audyt w toku...</span>';
const btn = document.getElementById('batchAuditBtn');
const originalText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span>Audyt w toku...</span>';
showInfoModal('Audyt uruchomiony', 'Audyt wsadowy został uruchomiony w tle. Może to potrwać kilkadziesiąt minut. Sprawdź wyniki później.');
alert('Audyt wsadowy zostanie uruchomiony w tle. Moze to potrwac kilkadziesiat minut. Sprawdz wyniki pozniej.');
btn.disabled = false;
btn.innerHTML = originalText;
setTimeout(() => {
btn.disabled = false;
btn.innerHTML = originalText;
}, 2000);
}
);
}
{% endblock %}

View File

@ -858,6 +858,7 @@
<a href="{{ url_for('admin_fees') }}" class="user-menu-item">Składki członkowskie</a>
<a href="{{ url_for('admin_calendar') }}" class="user-menu-item">Kalendarz</a>
<a href="{{ url_for('admin_social_media') }}" class="user-menu-item">Social Media</a>
<a href="{{ url_for('admin_seo') }}" class="user-menu-item">Audyt SEO</a>
<a href="{{ url_for('chat_analytics') }}" class="user-menu-item">Analityka Chatu</a>
<a href="{{ url_for('debug_panel') }}" class="user-menu-item">Debug Panel</a>
{% endif %}