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

Panel Audyt Social Media

Analiza obecności w mediach społecznościowych członków Norda Biznes

Dane z: Facebook, Instagram, LinkedIn, YouTube, Twitter/X, TikTok
{{ stats.total_companies }} Wszystkich firm
{{ stats.companies_with_sm }} Z Social Media
{{ stats.companies_without_sm }} Bez Social Media
{{ stats.total_profiles }} Łącznie profili
{{ "{:,}".format(stats.total_followers).replace(",", " ") }} Łącznie obserwujących
{{ stats.avg_completeness }}% Śr. kompletność profilu
{{ stats.avg_engagement }}% Śr. engagement
{% if stats.inactive_30d > 0 %}
{{ stats.inactive_30d }} Nieaktywne >30 dni
{% endif %} {% if stats.needs_verification_count > 0 %}
{{ stats.needs_verification_count }} Do weryfikacji
{% endif %}
{% if needs_verification and needs_verification|length > 0 %}

Profile do ręcznej weryfikacji

{% for item in needs_verification %}
{{ item.platform }} {{ item.company_name }} {{ item.url|truncate(50) }}
{% endfor %}
{% endif %}

Pokrycie platform

{% set platform_icons = { 'facebook': '', 'instagram': '', 'linkedin': '', 'youtube': '', 'twitter': '', 'tiktok': '' } %} {% for platform in platforms %}
{{ platform_icons[platform]|safe }}
{{ platform|capitalize }}
{{ stats.platform_stats[platform].count }} firm
{{ stats.platform_stats[platform].percent }}%
{% endfor %}
{% if top_followers %}

Top 10 — Najwięcej obserwujących

{% for company in top_followers %}
{{ loop.index }}
{{ "{:,}".format(company.total_followers).replace(",", " ") }} obserwujących
{% endfor %}
{% endif %}
{% if companies %}
{% for company in companies %} {% set activity_days = (now - company.last_post).days if company.last_post else 9999 %} {% endfor %}
Firma Kategoria Platformy Obserwujący Aktywność Ocena Zalecenia Weryfikacja Akcje
{{ company.name }} {{ company.category or 'Inne' }}
{% if company.total_followers > 0 %} {{ "{:,}".format(company.total_followers).replace(",", " ") }} {% else %} - {% endif %} {% if company.platform_count > 0 %} {% if company.last_post %} {% set days = (now - company.last_post).days %} {% if days <= 7 %} {{ days }}d {% elif days <= 30 %} {{ days }}d {% else %} {{ days }}d {% endif %} {% else %} b/d {% endif %} {% if company.total_posts_30d > 0 %} ({{ company.total_posts_30d }}) {% endif %} {% else %} - {% endif %} {% if company.health_score > 0 or company.avg_completeness > 0 %}
{{ company.health_score }}
{% if company.avg_completeness > 0 %}
profil {{ company.avg_completeness }}%
{% endif %}
{% else %} - {% endif %}
{% if company.recommendations %} {% set ns = namespace(critical=0) %} {% for rec in company.recommendations %}{% if 'Brak profili' in rec %}{% set ns.critical = ns.critical + 1 %}{% endif %}{% endfor %} {% set warnings = company.recommendations|length - ns.critical %} {% if ns.critical %}❌ {{ ns.critical }} {% endif %} {% if warnings %}⚠ {{ warnings }}{% endif %} {% else %} {% endif %} {% if company.last_verified %} {% set days_ago = (now - company.last_verified).days %} {{ company.last_verified|local_time('%d.%m.%Y') }} {% else %} - {% endif %}
{% else %}

Brak firm do wyświetlenia

Nie znaleziono firm z danymi social media.

{% endif %} {% endblock %} {% block extra_js %} // Sorting state let currentSort = { column: 'platforms', direction: 'desc' }; // Sort table function sortTable(column) { const tbody = document.getElementById('socialTableBody'); const rows = Array.from(tbody.querySelectorAll('tr')); const headers = document.querySelectorAll('.social-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 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 if (column === 'activity') { // Lower days = more active, sort ascending by default aVal = parseFloat(a.dataset.activity) || 9999; bVal = parseFloat(b.dataset.activity) || 9999; } else { aVal = parseFloat(a.dataset[column]) || -1; bVal = parseFloat(b.dataset[column]) || -1; } 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('.social-table th[data-sort]').forEach(th => { th.addEventListener('click', () => sortTable(th.dataset.sort)); }); // Filtering function applyFilters() { const category = document.getElementById('filterCategory').value; const platform = document.getElementById('filterPlatform').value; const activity = document.getElementById('filterActivity').value; const search = document.getElementById('filterSearch').value.toLowerCase(); const rows = document.querySelectorAll('#socialTableBody tr'); rows.forEach(row => { let show = true; // Category filter if (category && row.dataset.category !== category) { show = false; } // Platform filter if (platform && show) { if (platform === 'none') { if (parseInt(row.dataset.platforms) > 0) show = false; } else { const hasPlatform = row.dataset[`has${platform.charAt(0).toUpperCase() + platform.slice(1)}`] === 'true'; if (!hasPlatform) show = false; } } // Activity filter if (activity && show) { const days = parseInt(row.dataset.activity) || 9999; if (activity === 'active' && days > 7) show = false; else if (activity === 'warning' && (days <= 7 || days > 30)) show = false; else if (activity === 'inactive' && days <= 30) show = false; else if (activity === 'unknown' && days !== 9999) 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('filterPlatform').value = ''; document.getElementById('filterActivity').value = ''; document.getElementById('filterSearch').value = ''; applyFilters(); } // Enrichment var _enrichSince = 0; var _enrichPendingCount = 0; function startEnrichment() { document.getElementById('enrichConfirm').style.display = 'flex'; } function doStartEnrichment() { var btn = document.getElementById('enrichBtn'); btn.disabled = true; btn.textContent = 'Uruchamianie...'; _enrichSince = 0; _enrichPendingCount = 0; var platforms = Array.from(document.querySelectorAll('.enrich-platform:checked')).map(function(cb) { return cb.value; }); if (platforms.length === 0) { btn.disabled = false; btn.textContent = 'Uruchom audyt'; return; } fetch('{{ url_for("admin.admin_social_audit_run_enrichment") }}', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRFToken': '{{ csrf_token() }}'}, body: 'platforms=' + platforms.join(','), }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.status === 'started') { // Show live panel document.getElementById('enrichPanel').style.display = 'block'; document.getElementById('enrichCounter').textContent = '0 / ' + data.total; document.getElementById('enrichFeed').innerHTML = ''; document.getElementById('enrichMiniStatus').style.display = 'inline'; document.getElementById('enrichMiniStatus').textContent = 'Skanowanie...'; pollEnrichment(); } else { document.getElementById('enrichPanel').style.display = 'block'; document.getElementById('enrichTitle').textContent = data.error || 'Błąd uruchamiania'; document.getElementById('enrichTitle').style.color = '#dc2626'; document.getElementById('enrichSubtitle').textContent = ''; document.getElementById('enrichSpinner').style.display = 'none'; btn.disabled = false; btn.textContent = 'Uruchom audyt'; } }) .catch(function(e) { document.getElementById('enrichPanel').style.display = 'block'; document.getElementById('enrichTitle').textContent = 'Błąd połączenia: ' + e.message; document.getElementById('enrichTitle').style.color = '#dc2626'; document.getElementById('enrichSpinner').style.display = 'none'; btn.disabled = false; btn.textContent = 'Uruchom audyt'; }); } function _statusIcon(status) { if (status === 'changes') return ''; if (status === 'skipped') return ''; if (status === 'error') return ''; if (status === 'no_changes') return ''; return ''; } function _platformBadge(p) { var colors = { 'changes': 'background:#dbeafe;color:#1d4ed8;', 'skipped': 'background:#f3f4f6;color:#6b7280;', 'error': 'background:#fee2e2;color:#991b1b;', 'no_changes': 'background:#f0fdf4;color:#15803d;', 'no_data': 'background:#f3f4f6;color:#9ca3af;' }; var style = colors[p.status] || 'background:#f3f4f6;color:#6b7280;'; var name = p.platform.charAt(0).toUpperCase() + p.platform.slice(1); return '' + name + ': ' + p.desc + ''; } function pollEnrichment() { fetch('{{ url_for("admin.admin_social_audit_enrichment_status") }}?since=' + _enrichSince) .then(function(r) { return r.json(); }) .then(function(data) { // Update progress document.getElementById('enrichCounter').textContent = data.completed + ' / ' + data.total; document.getElementById('enrichBar').style.width = data.progress + '%'; document.getElementById('enrichMiniStatus').textContent = data.completed + '/' + data.total; // Append new feed entries var feed = document.getElementById('enrichFeed'); if (data.feed && data.feed.length > 0) { for (var i = 0; i < data.feed.length; i++) { var r = data.feed[i]; var row = document.createElement('div'); row.style.cssText = 'display:flex;align-items:center;gap:8px;padding:6px 0;border-bottom:1px solid #f3f4f6;'; var badges = r.profiles.map(_platformBadge).join(' '); var nameStyle = r.has_changes ? 'font-weight:600;color:#1d4ed8;' : 'color:var(--text-secondary);'; row.innerHTML = '' + (_enrichSince + i + 1) + '.' + '' + r.company_name + '' + '' + badges + ''; feed.appendChild(row); feed.scrollTop = feed.scrollHeight; } _enrichSince += data.feed.length; } if (data.pending_count > _enrichPendingCount) { _enrichPendingCount = data.pending_count; document.getElementById('enrichSubtitle').textContent = _enrichPendingCount + ' profili z nowymi danymi (do zatwierdzenia)'; } if (data.running) { setTimeout(pollEnrichment, 2000); } else { // Scan complete document.getElementById('enrichSpinner').style.animation = 'none'; document.getElementById('enrichSpinner').style.borderTopColor = '#22c55e'; document.getElementById('enrichSpinner').style.borderColor = '#22c55e'; var btn = document.getElementById('enrichBtn'); btn.disabled = false; btn.innerHTML = ' Uruchom audyt'; if (data.pending_count > 0) { document.getElementById('enrichTitle').textContent = 'Skanowanie zakończone — ' + data.pending_count + ' zmian do zatwierdzenia'; document.getElementById('enrichSubtitle').innerHTML = 'Przejdź do raportu →'; document.getElementById('enrichMiniStatus').innerHTML = '' + data.pending_count + ' zmian →'; // Add review link row var linkRow = document.createElement('div'); linkRow.style.cssText = 'padding:12px 0;text-align:center;font-weight:600;'; linkRow.innerHTML = 'Przejdź do raportu ze zmianami (' + data.pending_count + ' profili) →'; feed.appendChild(linkRow); feed.scrollTop = feed.scrollHeight; } else { document.getElementById('enrichTitle').textContent = 'Skanowanie zakończone — brak nowych danych'; document.getElementById('enrichSubtitle').textContent = data.errors > 0 ? data.errors + ' błędów' : 'Wszystkie profile aktualne'; document.getElementById('enrichMiniStatus').textContent = 'Zakończono'; } } }); } {% endblock %}