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
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:
parent
51cddebd1f
commit
9fa364bfeb
@ -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';
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user