feat: replace custom progress with stepper + modals matching dashboard UX
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions

Uses vertical stepper with checkmarks (connect → analyze → save → done),
confirmation modal before audit, info modal for results. Matches the
pattern used in admin_seo_dashboard.html.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-11 05:50:35 +01:00
parent 51cddebd1f
commit 9fa364bfeb

View File

@ -146,19 +146,94 @@
{% endif %}
</div>
<!-- Pasek postepu audytu -->
<!-- Stepper postepu audytu -->
<div id="auditProgress" style="display: none; margin-bottom: var(--spacing-lg); padding: var(--spacing-lg); background: var(--surface); border-radius: var(--radius); border: 1px solid var(--border);">
<div style="display: flex; align-items: center; gap: var(--spacing-sm); margin-bottom: var(--spacing-md);">
<div id="auditSpinner" style="width: 20px; height: 20px; border: 3px solid var(--border); border-top-color: var(--primary); border-radius: 50%; animation: spin 0.8s linear infinite;"></div>
<strong id="auditTitle">Audyt SEO w toku...</strong>
<strong style="display: block; margin-bottom: var(--spacing-md);">Audyt SEO — {{ company.name }}</strong>
<div id="auditSteps" style="display: flex; flex-direction: column; gap: 2px;">
<div class="audit-step" id="step-connect">
<span class="step-icon"></span>
<span>Laczenie z Google PageSpeed Insights...</span>
</div>
<div class="audit-step" id="step-analyze">
<span class="step-icon"></span>
<span>Analiza strony internetowej...</span>
</div>
<div class="audit-step" id="step-save">
<span class="step-icon"></span>
<span>Zapisywanie wynikow...</span>
</div>
<div class="audit-step" id="step-done">
<span class="step-icon"></span>
<span>Gotowe</span>
</div>
</div>
<div style="background: var(--border); border-radius: 999px; height: 8px; overflow: hidden; margin-bottom: var(--spacing-sm);">
<div id="auditBar" style="height: 100%; background: var(--primary); border-radius: 999px; width: 0%; transition: width 0.3s ease;"></div>
</div>
<div id="auditMessage" style="font-size: var(--font-size-sm); color: var(--text-secondary);"></div>
<div id="auditLog" style="margin-top: var(--spacing-md); font-size: var(--font-size-xs); max-height: 200px; overflow-y: auto;"></div>
<div id="auditResult" style="margin-top: var(--spacing-md); display: none;"></div>
</div>
<style>@keyframes spin { to { transform: rotate(360deg); } }</style>
<style>
.audit-step { display: flex; align-items: center; gap: var(--spacing-sm); padding: 6px 0; font-size: var(--font-size-sm); color: var(--text-secondary); }
.audit-step.active { color: var(--text-primary); font-weight: 600; }
.audit-step.active .step-icon { color: var(--primary); }
.audit-step.done { color: var(--success); }
.audit-step.done .step-icon { color: var(--success); }
.audit-step.error { color: var(--danger); }
.audit-step.error .step-icon { color: var(--danger); }
.step-icon { font-size: 16px; width: 20px; text-align: center; }
@keyframes spin { to { transform: rotate(360deg); } }
</style>
<!-- Modal potwierdzenia -->
<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">Uruchom audyt SEO</div>
</div>
<div class="modal-body">
Czy na pewno chcesz uruchomic audyt SEO dla {{ company.name }}? Jesli firma juz poprawila strone, wyniki zostana nadpisane nowymi danymi.
</div>
<div class="modal-footer">
<button class="btn btn-outline" onclick="closeModal()">Anuluj</button>
<button class="btn btn-primary" onclick="confirmAudit()">Uruchom audyt</button>
</div>
</div>
</div>
<!-- Modal informacyjny -->
<div class="modal" id="infoModal">
<div class="modal-content">
<div class="modal-header">
<div class="modal-icon info" id="infoModalIcon">
<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"></div>
<div class="modal-footer">
<button class="btn btn-primary" onclick="closeInfoModal()">OK</button>
</div>
</div>
</div>
<style>
.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, 12px); max-width: 480px; width: 90%; box-shadow: 0 20px 60px rgba(0,0,0,0.3); 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-lg, 18px); font-weight: 600; }
.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>
{% if analysis and analysis.seo_audited_at %}
<div class="audit-info">
@ -1054,81 +1129,98 @@
{% block extra_js %}
var csrfToken = '{{ csrf_token() }}';
// Modal functions
function closeModal() {
document.getElementById('confirmModal').classList.remove('active');
}
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');
}
document.getElementById('confirmModal').addEventListener('click', function(e) {
if (e.target.id === 'confirmModal') closeModal();
});
document.getElementById('infoModal').addEventListener('click', function(e) {
if (e.target.id === 'infoModal') closeInfoModal();
});
// Stepper helpers
function setStep(stepId, state) {
var el = document.getElementById(stepId);
var icon = el.querySelector('.step-icon');
el.className = 'audit-step ' + state;
if (state === 'active') icon.textContent = '◉';
else if (state === 'done') icon.textContent = '✓';
else if (state === 'error') icon.textContent = '✗';
else icon.textContent = '○';
}
function runAudit() {
document.getElementById('confirmModal').classList.add('active');
}
function confirmAudit() {
closeModal();
var btn = document.getElementById('auditBtn');
var progress = document.getElementById('auditProgress');
var bar = document.getElementById('auditBar');
var msg = document.getElementById('auditMessage');
var title = document.getElementById('auditTitle');
var spinner = document.getElementById('auditSpinner');
var log = document.getElementById('auditLog');
var result = document.getElementById('auditResult');
// Pokazanie postepu
btn.disabled = true;
btn.textContent = 'Audyt w toku...';
progress.style.display = 'block';
progress.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
log.innerHTML = '';
result.style.display = 'none';
function addLog(text, type) {
var entry = document.createElement('div');
entry.textContent = text;
entry.style.padding = '2px 0';
entry.style.borderBottom = '1px solid var(--border)';
if (type === 'success') { entry.style.color = 'var(--success)'; entry.style.fontWeight = '600'; }
if (type === 'error') { entry.style.color = 'var(--danger)'; entry.style.fontWeight = '600'; }
if (type === 'info') { entry.style.color = 'var(--text-secondary)'; entry.style.fontStyle = 'italic'; }
log.appendChild(entry);
log.scrollTop = log.scrollHeight;
}
// Reset steps
['step-connect', 'step-analyze', 'step-save', 'step-done'].forEach(function(id) { setStep(id, ''); });
function setProgress(pct, text) {
bar.style.width = pct + '%';
if (text) msg.textContent = text;
}
setProgress(10, 'Laczenie z Google PageSpeed Insights...');
addLog('Rozpoczynam audyt dla: {{ company.name }}', 'info');
// Step 1: Connecting
setStep('step-connect', 'active');
fetch('/api/seo/audit', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken },
body: JSON.stringify({ slug: '{{ company.slug }}' })
}).then(function(r) {
setProgress(70, 'Przetwarzanie wynikow...');
setStep('step-connect', 'done');
setStep('step-analyze', 'done');
setStep('step-save', 'active');
return r.json();
}).then(function(data) {
if (data.success) {
setProgress(100, 'Audyt zakonczony pomyslnie!');
title.textContent = 'Audyt zakonczony';
spinner.style.animation = 'none';
spinner.style.border = '3px solid var(--success)';
spinner.innerHTML = '<svg viewBox="0 0 20 20" style="width:14px;height:14px;margin:0"><path d="M5 10l3 3 7-7" stroke="var(--success)" fill="none" stroke-width="2"/></svg>';
setStep('step-save', 'done');
setStep('step-done', 'done');
var seo = data.scores ? data.scores.seo : null;
var perf = data.scores ? data.scores.performance : null;
if (seo !== null || perf !== null) {
addLog('Wyniki: SEO ' + (seo || '-') + ' | Performance ' + (perf || '-'), 'success');
} else {
addLog('Audyt zakonczony pomyslnie', 'success');
// Show scores in result area
var scores = data.seo_audit ? data.seo_audit.pagespeed : null;
if (scores) {
result.style.display = 'block';
result.innerHTML = '<div style="padding: var(--spacing-md); background: #f0fdf4; border-radius: var(--radius); border-left: 3px solid var(--success);">' +
'<strong style="color: var(--success);">Audyt zakonczony pomyslnie</strong>' +
'<div style="margin-top: var(--spacing-sm); font-size: var(--font-size-sm); display: flex; gap: var(--spacing-lg); flex-wrap: wrap;">' +
'<span>SEO: <strong>' + (scores.seo_score || '-') + '</strong></span>' +
'<span>Performance: <strong>' + (scores.performance_score || '-') + '</strong></span>' +
'<span>Dostepnosc: <strong>' + (scores.accessibility_score || '-') + '</strong></span>' +
'<span>Best Practices: <strong>' + (scores.best_practices_score || '-') + '</strong></span>' +
'</div></div>';
}
addLog('Strona zostanie odswiezona za 2 sekundy...', 'info');
showInfoModal('Audyt zakonczony', 'Audyt SEO zakonczony pomyslnie! Strona zostanie odswiezona.');
setTimeout(function() { location.reload(); }, 2000);
} else {
setProgress(100, 'Wystapil blad podczas audytu');
title.textContent = 'Blad audytu';
spinner.style.animation = 'none';
spinner.style.border = '3px solid var(--danger)';
addLog('Blad: ' + (data.error || 'Nieznany blad'), 'error');
setStep('step-save', 'error');
showInfoModal('Blad', 'Wystapil blad: ' + (data.error || 'Nieznany blad'));
btn.disabled = false;
btn.textContent = 'Uruchom audyt ponownie';
}
}).catch(function(e) {
setProgress(100, 'Blad polaczenia z serwerem');
title.textContent = 'Blad polaczenia';
spinner.style.animation = 'none';
spinner.style.border = '3px solid var(--danger)';
addLog('Blad: ' + e.message, 'error');
setStep('step-connect', 'error');
showInfoModal('Blad polaczenia', 'Nie udalo sie polaczyc z serwerem: ' + e.message);
btn.disabled = false;
btn.textContent = 'Uruchom audyt ponownie';
});