{% extends "base.html" %} {% block title %}Panel SEO - Norda Biznes Partner{% endblock %} {% block extra_css %} {% endblock %} {% block content %}

Panel SEO

Analiza jakosci SEO stron internetowych czlonkow Norda Biznes

Dane z Google PageSpeed Insights (Lighthouse)
{% if current_user.can_access_admin_panel() %} {% endif %}
{% set total_companies = stats.good_count + stats.medium_count + stats.poor_count + stats.not_audited_count %}
{{ total_companies }} Firm ogolnie
{{ stats.good_count }} Dobre SEO (90+)
{{ stats.poor_count }} Slabe SEO (ponizej 50)
{{ stats.not_audited_count }} Niezbadane
{{ stats.avg_score|default('-', true) }}{% if stats.avg_score %}/100{% endif %} Sredni wynik
Audyt SEO w toku...
0%
Przygotowywanie...
90-100 (dobry)
50-89 (sredni)
0-49 (slaby)
SEO — widocznosc w wyszukiwarkach Performance — szybkosc ladowania Accessibility — dostepnosc dla osob z niepelnosprawnosciami Best Practices — bezpieczenstwo i standardy Szczegoly — pelne informacje i zalecenia co poprawic
{% if companies %}
{% for company in companies %} {% endfor %}
Firma Kategoria SEO Performance Accessibility Best Practices Problemy Audyt Akcje
{{ company.name }} {% if company.website %} {{ company.website }} {% endif %} {{ company.category or 'Inne' }} {% if company.seo_score is not none %} {{ company.seo_score }} {% else %} - {% endif %} {% if company.performance_score is not none %} {{ company.performance_score }} {% else %} - {% endif %} {% if company.accessibility_score is not none %} {{ company.accessibility_score }} {% else %} - {% endif %} {% if company.best_practices_score is not none %} {{ company.best_practices_score }} {% else %} - {% endif %} {% if company.seo_score is not none %} {% set problems = [] %} {% if company.seo_score is not none and company.seo_score < 50 %} {% set _ = problems.append('Slabe SEO') %} {% endif %} {% if company.performance_score is not none and company.performance_score < 50 %} {% set _ = problems.append('Wolna strona') %} {% endif %} {% if company.accessibility_score is not none and company.accessibility_score < 50 %} {% set _ = problems.append('Niska dostepnosc') %} {% endif %} {% if company.best_practices_score is not none and company.best_practices_score < 50 %} {% set _ = problems.append('Brak standardow') %} {% endif %} {% if problems %} {% for p in problems %} {{ p }} {% endfor %} {% elif company.seo_score >= 90 and (company.performance_score is none or company.performance_score >= 90) %} Wszystko OK {% else %} Do poprawy {% endif %} {% elif not company.website %} Brak strony WWW {% else %} Brak danych {% endif %} {% if company.seo_audited_at %} {% set days_ago = (now - company.seo_audited_at).days %} {{ company.seo_audited_at|local_time('%d.%m.%Y') }} {% else %} Nigdy {% endif %}
Szczegoly {% if current_user.can_access_admin_panel() %} {% endif %}
{% else %}

Brak firm do wyswietlenia

Nie znaleziono firm z danymi SEO.

{% endif %} {% 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: 'asc' }; // Sort table function sortTable(column) { const tbody = document.getElementById('seoTableBody'); const rows = Array.from(tbody.querySelectorAll('tr')); const headers = document.querySelectorAll('.seo-table th[data-sort]'); // Toggle direction if same column if (currentSort.column === column) { currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc'; } else { currentSort.column = column; currentSort.direction = 'desc'; } // Update header classes headers.forEach(h => { h.classList.remove('sorted', 'sorted-asc', 'sorted-desc'); if (h.dataset.sort === column) { h.classList.add('sorted', `sorted-${currentSort.direction}`); } }); // Sort rows (unaudited companies always at the bottom) rows.sort((a, b) => { let aVal, bVal; if (column === 'name') { aVal = a.dataset.name || ''; bVal = b.dataset.name || ''; } else if (column === 'category') { aVal = a.dataset.category || ''; bVal = b.dataset.category || ''; } else if (column === 'date') { aVal = new Date(a.dataset.date).getTime(); bVal = new Date(b.dataset.date).getTime(); } else { aVal = parseFloat(a.dataset[column]); bVal = parseFloat(b.dataset[column]); // Push unaudited (-1) to the bottom regardless of sort direction if (aVal < 0 && bVal >= 0) return 1; if (bVal < 0 && aVal >= 0) return -1; if (aVal < 0 && bVal < 0) return 0; } if (aVal < bVal) return currentSort.direction === 'asc' ? -1 : 1; if (aVal > bVal) return currentSort.direction === 'asc' ? 1 : -1; return 0; }); // Re-append rows rows.forEach(row => tbody.appendChild(row)); } // Setup sorting click handlers document.querySelectorAll('.seo-table th[data-sort]').forEach(th => { th.addEventListener('click', () => sortTable(th.dataset.sort)); }); // Initial sort — worst scores first sortTable('overall'); // Filtering function applyFilters() { const category = document.getElementById('filterCategory').value; const score = document.getElementById('filterScore').value; const search = document.getElementById('filterSearch').value.toLowerCase(); const rows = document.querySelectorAll('#seoTableBody tr'); rows.forEach(row => { let show = true; // Category filter if (category && row.dataset.category !== category) { show = false; } // Score filter if (score && show) { const overallScore = parseFloat(row.dataset.overall); if (score === 'good' && (overallScore < 90 || overallScore < 0)) show = false; else if (score === 'medium' && (overallScore < 50 || overallScore >= 90)) show = false; else if (score === 'poor' && (overallScore < 0 || overallScore >= 50)) show = false; else if (score === 'none' && overallScore >= 0) show = false; } // Search filter if (search && show) { if (!row.dataset.name.includes(search)) { show = false; } } row.style.display = show ? '' : 'none'; }); } function resetFilters() { document.getElementById('filterCategory').value = ''; document.getElementById('filterScore').value = ''; document.getElementById('filterSearch').value = ''; applyFilters(); } // Helper functions let auditInProgress = false; function addLogEntry(logElement, message, type) { const entry = document.createElement('div'); entry.className = 'progress-log-entry ' + type; entry.textContent = message; logElement.appendChild(entry); logElement.scrollTop = logElement.scrollHeight; } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function getScoreClass(score) { if (score >= 90) return '🟢'; if (score >= 50) return '🟡'; return '🔴'; } function cancelAudit() { auditInProgress = false; document.getElementById('progressSection').classList.remove('active'); document.getElementById('batchAuditBtn').disabled = false; document.getElementById('batchAuditBtn').innerHTML = ` Uruchom audyt `; } // Audit functions function runSingleAudit(slug) { showModal( 'Uruchom audyt SEO', 'Czy na pewno chcesz uruchomić audyt SEO dla tej firmy? Analiza może potrwać kilka sekund.', async () => { 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(); 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); } } ); } async function runBatchAudit() { showModal( 'Uruchom audyt wszystkich firm', 'Czy chcesz uruchomić audyt SEO dla wszystkich firm z adresem WWW? Każda firma będzie analizowana osobno.', async () => { auditInProgress = true; const btn = document.getElementById('batchAuditBtn'); btn.disabled = true; btn.innerHTML = 'Audyt w toku...'; const progressSection = document.getElementById('progressSection'); progressSection.classList.add('active'); const progressBar = document.getElementById('progressBar'); const progressMessage = document.getElementById('progressMessage'); const progressLog = document.getElementById('progressLog'); progressBar.style.width = '0%'; progressBar.textContent = '0%'; progressMessage.textContent = 'Pobieranie listy firm...'; progressLog.innerHTML = ''; // Get all companies with website from the table const rows = document.querySelectorAll('#seoTableBody tr[data-slug]'); const companies = []; rows.forEach(row => { const website = row.dataset.website; if (website && website.trim() !== '') { companies.push({ slug: row.dataset.slug, name: row.dataset.companyName || row.dataset.name, website: website }); } }); if (companies.length === 0) { progressSection.classList.remove('active'); showInfoModal('Brak firm', 'Nie znaleziono firm z adresem WWW do audytu.'); auditInProgress = false; btn.disabled = false; btn.innerHTML = ` Uruchom audyt `; return; } addLogEntry(progressLog, `Znaleziono ${companies.length} firm z adresem WWW`, 'info'); let success = 0; let failed = 0; let skipped = 0; for (let i = 0; i < companies.length; i++) { if (!auditInProgress) { addLogEntry(progressLog, 'Audyt anulowany przez użytkownika', 'error'); break; } const company = companies[i]; const percent = Math.round(((i + 1) / companies.length) * 100); progressBar.style.width = `${percent}%`; progressBar.textContent = `${percent}%`; progressMessage.textContent = `[${i + 1}/${companies.length}] Analizuję: ${company.name}...`; try { const response = await fetch('/api/seo/audit', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken }, body: JSON.stringify({ slug: company.slug }) }); const data = await response.json(); if (response.ok && data.success) { success++; const scores = data.seo_audit?.pagespeed || {}; const seo = scores.seo_score ?? '-'; const perf = scores.performance_score ?? '-'; const acc = scores.accessibility_score ?? '-'; const bp = scores.best_practices_score ?? '-'; addLogEntry(progressLog, `✓ ${company.name}`, 'success'); addLogEntry(progressLog, ` SEO: ${seo !== '-' ? getScoreClass(seo) + ' ' + seo : '-'} | Perf: ${perf !== '-' ? getScoreClass(perf) + ' ' + perf : '-'} | Dostępność: ${acc !== '-' ? getScoreClass(acc) + ' ' + acc : '-'} | BP: ${bp !== '-' ? getScoreClass(bp) + ' ' + bp : '-'}`, 'detail'); } else { if (data.error && data.error.includes('Brak strony WWW')) { skipped++; addLogEntry(progressLog, `⊘ ${company.name} - Brak WWW`, 'skipped'); } else { failed++; addLogEntry(progressLog, `✗ ${company.name} - ${data.error || 'Błąd'}`, 'error'); } } } catch (error) { failed++; addLogEntry(progressLog, `✗ ${company.name} - Błąd połączenia`, 'error'); } // Delay between requests to avoid overwhelming the API await sleep(500); } progressBar.style.width = '100%'; progressBar.textContent = '100%'; progressMessage.textContent = `Audyt zakończony! Sukces: ${success}, Błędy: ${failed}, Pominięte: ${skipped}`; addLogEntry(progressLog, `─────────────────────────────`, 'info'); addLogEntry(progressLog, `PODSUMOWANIE: Sukces: ${success}, Błędy: ${failed}, Pominięte: ${skipped}`, 'info'); showInfoModal( 'Audyt zakończony', `Przetworzono ${companies.length} firm.\n\nSukces: ${success}\nBłędy: ${failed}\nPominięte: ${skipped}` ); auditInProgress = false; btn.disabled = false; btn.innerHTML = ` Uruchom audyt `; // Refresh page after a delay setTimeout(() => location.reload(), 3000); } ); } {% endblock %}