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

Audyt Social Media

{{ company.name }}

Analiza obecności w mediach społecznościowych
Profil firmy {% if can_audit %} {% endif %}
{# Unified 5-level color scale: 0-29 red, 30-49 orange, 50-69 amber, 70-89 lime, 90-100 green #} {% set score = social_data.score %}
{{ score }} / 100
{% if score >= 90 %} Doskonała obecność w Social Media {% elif score >= 70 %} Dobra obecność w Social Media {% elif score >= 50 %} Przeciętna obecność w Social Media {% elif score >= 30 %} Obecność wymaga rozbudowy {% else %} Słaba obecność w Social Media {% endif %}

{% if score >= 80 %} Firma jest obecna na większości ważnych platform społecznościowych. Utrzymuj aktywność i rozwijaj zaangażowanie. {% elif score >= 60 %} Firma ma dobrą obecność w social media. Rozważ dodanie brakujących platform dla pełniejszego zasięgu. {% elif score >= 40 %} Firma jest obecna na kilku platformach. Warto rozszerzyć obecność o kolejne kanały komunikacji. {% else %} Firma ma ograniczoną obecność w social media. Zalecamy utworzenie profili na kluczowych platformach. {% endif %}

{{ social_data.platforms_count }} {{ 'platforma' if social_data.platforms_count == 1 else ('platformy' if social_data.platforms_count in [2,3,4] else 'platform') }}
{{ social_data.total_platforms - social_data.platforms_count }} brakujących

Platformy Social Media

{% set platform_names = {'facebook': 'Facebook', 'instagram': 'Instagram', 'linkedin': 'LinkedIn', 'youtube': 'YouTube', 'twitter': 'X (Twitter)', 'tiktok': 'TikTok'} %} {% set platform_icons = {'facebook': 'f', 'instagram': 'ig', 'linkedin': 'in', 'youtube': 'yt', 'twitter': 'X', 'tiktok': 'tk'} %} {% for platform in social_data.all_platforms %} {% set profile = social_data.profiles.get(platform) %}
{% if platform == 'facebook' %} {% elif platform == 'instagram' %} {% elif platform == 'linkedin' %} {% elif platform == 'youtube' %} {% elif platform == 'twitter' %} {% elif platform == 'tiktok' %} {% endif %}
{{ platform_names.get(platform, platform|title) }} {% if profile and profile.check_status == '404' %} Profil nie istnieje (404) {% elif profile and profile.check_status == 'blocked' %} Zablokowany {% elif profile and profile.check_status == 'redirect' %} Przekierowanie {% elif profile and profile.check_status == 'needs_verification' %} Do weryfikacji {% elif profile %} {% if profile.posting_frequency_score is not none and profile.posting_frequency_score >= 7 %} Aktywny {% elif profile.posting_frequency_score is not none and profile.posting_frequency_score >= 4 %} Umiarkowanie aktywny {% elif profile.posting_frequency_score is not none and profile.posting_frequency_score >= 1 %} Rzadko aktywny {% elif profile.posting_frequency_score is not none and profile.posting_frequency_score == 0 %} Nieaktywny {% else %} Brak danych {% endif %} {% else %} Brak profilu {% endif %}
{% if profile %} {% if profile.check_status == 'needs_verification' %}
Do weryfikacji — link do profilu wymaga ręcznego sprawdzenia. Profil może być nieaktywny lub wskazywać na niewłaściwe konto.
{% endif %} {% if platform == 'facebook' and 'profile.php?id=' in (profile.url or '') %}
Adres profilu zawiera numeryczne ID zamiast nazwy firmy. Zalecamy ustawienie niestandardowej nazwy użytkownika (np. facebook.com/NazwaFirmy) w ustawieniach strony na Facebooku.
{% endif %} {% if profile.source %}
Źródło: {% if profile.source == 'website_scrape' %}Ze strony WWW{% elif profile.source == 'manual' %}Ręcznie{% elif profile.source == 'facebook_api' %}Facebook API{% else %}{{ profile.source }}{% endif %}
{% endif %} {% if profile.source == 'facebook_api' %}
Dane z Facebook API {% if profile.engagement_rate is not none %} · Engagement: {{ '%.1f'|format(profile.engagement_rate) }}%{% endif %} {% if profile.profile_completeness_score %} · Kompletność profilu: {{ profile.profile_completeness_score }}%{% endif %}
{% endif %}
{% if profile.page_name %}
{{ profile.page_name }}
{% endif %} {% if profile.followers_count %}
{{ '{:,}'.format(profile.followers_count).replace(',', ' ') }} obserwujących
{% endif %} {% if profile.followers_history and profile.followers_history|length > 1 %}
Trend:
{% set max_val = profile.followers_history|map(attribute='count')|select('number')|max|default(1) %} {% for point in profile.followers_history[-12:] %}
{% endfor %}
{% endif %} {% if profile.verified_at %}
{{ profile.verified_at|local_time('%d.%m.%Y') }}
{% endif %} {% if profile.last_checked_at %}
Sprawdzono: {{ profile.last_checked_at|local_time('%d.%m.%Y') }}
{% endif %} {% if profile.has_bio %}
Opis
{% endif %} {% if profile.has_profile_photo %}
Avatar
{% endif %} {% if profile.has_cover_photo %}
Cover
{% endif %} {% if profile.posts_count_30d is not none and profile.posts_count_30d > 0 %}
{{ profile.posts_count_30d }} postów/30d
{% endif %} {% if profile.engagement_rate is not none %}
{{ '%.1f'|format(profile.engagement_rate) }}% engage
{% endif %} {% if profile.posts_count_365d is not none and profile.posts_count_365d > 0 %}
{{ profile.posts_count_365d }} postów/rok
{% endif %} {% if profile.last_post_date %}
Ostatni: {{ profile.last_post_date|local_time('%d.%m.%Y') }}
{% endif %} {% if profile.posting_frequency_score is not none %}
{{ profile.posting_frequency_score }}/10
{% endif %} {% if profile.content_types and profile.content_types is mapping %} {% for ctype, ccount in profile.content_types.items() %}
{% if ctype == 'videos' or ctype == 'video' %} {% else %} {% endif %} {{ ctype|capitalize }}: {{ ccount }}
{% endfor %} {% endif %} {% if profile.profile_description %}
{{ profile.profile_description[:80] }}{% if profile.profile_description|length > 80 %}...{% endif %}
{% endif %}
{% if profile and profile.profile_completeness_score is not none %}
Kompletność profilu {{ profile.profile_completeness_score }}%
{% endif %} {% else %}

Profil nie został wykryty automatycznie na stronie internetowej firmy.

Jeśli firma posiada profil na tej platformie, można go dodać ręcznie w edycji profilu firmy.

{% endif %}
{% endfor %}
{% set ns = namespace(active_count=0) %} {% for platform in social_data.all_platforms %} {% if social_data.profiles.get(platform) and social_data.profiles[platform].is_valid %} {% set ns.active_count = ns.active_count + 1 %} {% endif %} {% endfor %} {% if ns.active_count > 1 %}

Porównanie platform

{% for platform in social_data.all_platforms %} {% set cmp_profile = social_data.profiles.get(platform) %} {% if cmp_profile and cmp_profile.is_valid %} {% endif %} {% endfor %}
Platforma Obserwujący Engagement Posty (30d) Kompletność
{{ platform_names.get(platform, platform|title) }} {{ '{:,}'.format(cmp_profile.followers_count or 0).replace(',', ' ') }} {% if cmp_profile.engagement_rate is not none %} {{ '%.1f'|format(cmp_profile.engagement_rate) }}% {% else %}—{% endif %} {{ cmp_profile.posts_count_30d or 0 }} {% if cmp_profile.profile_completeness_score is not none %} {{ cmp_profile.profile_completeness_score }}% {% else %}—{% endif %}
{% endif %} {% if social_data.total_platforms - social_data.platforms_count > 0 %}

Rekomendacje

{% set missing_platforms = [] %} {% for platform in social_data.all_platforms %} {% if platform not in social_data.profiles %} {% set _ = missing_platforms.append(platform) %} {% endif %} {% endfor %} {% if 'facebook' in missing_platforms %}
!
Facebook - Najpopularniejsza platforma w Polsce. Założenie strony firmowej pozwoli dotrzeć do szerokiego grona klientów.
{% endif %} {% if 'linkedin' in missing_platforms %}
!
LinkedIn - Kluczowa platforma dla firm B2B. Idealna do budowania relacji biznesowych i employer brandingu.
{% endif %} {% if 'instagram' in missing_platforms %}
!
Instagram - Świetna platforma do prezentacji wizualnej firmy. Szczególnie ważna dla firm z produktami/usługami wizualnymi.
{% endif %} {% if 'youtube' in missing_platforms %}
!
YouTube - Druga największa wyszukiwarka na świecie. Video content buduje zaufanie i pokazuje ekspertyzę.
{% endif %} {% if 'tiktok' in missing_platforms %}
!
TikTok - Najszybciej rosnąca platforma. Warto rozważyć jeśli celujecie w młodszych odbiorców.
{% endif %} {% if 'twitter' in missing_platforms %}
!
X (Twitter) - Platforma do szybkiej komunikacji i budowania wizerunku eksperta. Przydatna w branży tech/media.
{% endif %}
{% endif %} {# Facebook numeric ID warning - shown regardless of missing platforms #} {% set fb_profile = social_data.profiles.get('facebook') %} {% if fb_profile and 'profile.php?id=' in (fb_profile.url or '') %} {% if social_data.total_platforms - social_data.platforms_count <= 0 %}

Rekomendacje

{% endif %}
!
Facebook - zmień adres profilu - Twój profil na Facebooku używa numerycznego ID zamiast nazwy firmy. Klienci łatwiej zapamiętają adres typu facebook.com/NazwaFirmy. Aby to zmienić: Ustawienia strony na Facebooku → Ogólne → Nazwa użytkownika → wpisz nazwę firmy.
{% endif %} {% if social_data %} {% with audit_type='social' %} {% include 'partials/audit_ai_actions.html' %} {% endwith %} {% endif %}

Audyt Social Media

Szukam profili w mediach społecznościowych...

Skanuję stronę WWW WWW
Szukam Facebook
Szukam Instagram IG
Szukam LinkedIn LI
Szukam YouTube YT
Szukam X (Twitter)
Szukam TikTok TK
Pobieram dane Google Google
Zapisuję wyniki
{% endblock %} {% block extra_js %} const csrfToken = '{{ csrf_token() }}'; const companySlug = '{{ company.slug }}'; // All step IDs in order const allSteps = [ 'step-website', 'step-facebook', 'step-instagram', 'step-linkedin', 'step-youtube', 'step-twitter', 'step-tiktok', 'step-google', 'step-save' ]; // Platform step mapping const platformSteps = { 'facebook': 'step-facebook', 'instagram': 'step-instagram', 'linkedin': 'step-linkedin', 'youtube': 'step-youtube', 'twitter': 'step-twitter', 'tiktok': 'step-tiktok' }; // SVG icons for different states const icons = { pending: '', in_progress: '
', complete: '', error: '', missing: '' }; function resetSteps() { allSteps.forEach((stepId, index) => { const stepEl = document.getElementById(stepId); if (stepEl) { const iconEl = stepEl.querySelector('.step-icon'); const textEl = stepEl.querySelector('.step-text'); if (index === 0) { iconEl.className = 'step-icon in_progress'; iconEl.innerHTML = icons.in_progress; textEl.className = 'step-text in_progress'; } else { iconEl.className = 'step-icon pending'; iconEl.innerHTML = icons.pending; textEl.className = 'step-text pending'; } } }); } function updateStep(stepId, status, message) { const stepEl = document.getElementById(stepId); if (!stepEl) return; const iconEl = stepEl.querySelector('.step-icon'); const textEl = stepEl.querySelector('.step-text'); iconEl.className = 'step-icon ' + status; iconEl.innerHTML = icons[status] || icons.pending; textEl.className = 'step-text ' + status; if (message) { textEl.innerHTML = message; } } function showLoading() { resetSteps(); document.getElementById('loadingOverlay').classList.add('active'); } function hideLoading() { document.getElementById('loadingOverlay').classList.remove('active'); } function showModal(title, description, isSuccess) { const modal = document.getElementById('modalOverlay'); const icon = document.getElementById('modalIcon'); const titleEl = document.getElementById('modalTitle'); const descEl = document.getElementById('modalDescription'); titleEl.textContent = title; descEl.textContent = description; icon.className = 'modal-icon ' + (isSuccess ? 'success' : 'error'); icon.innerHTML = isSuccess ? '' : ''; modal.classList.add('active'); } function closeModal() { document.getElementById('modalOverlay').classList.remove('active'); } // Animate step-by-step progress for social media platforms async function animatePlatformSteps(foundPlatforms, googleData) { const delay = 400; // ms between steps - slower for readability // Website scan complete updateStep('step-website', 'complete', 'Skanowanie strony WWW zakończone WWW'); await new Promise(r => setTimeout(r, delay)); // Process each platform const platforms = ['facebook', 'instagram', 'linkedin', 'youtube', 'twitter', 'tiktok']; for (const platform of platforms) { const stepId = platformSteps[platform]; const platformLabel = platform === 'twitter' ? 'X (Twitter)' : platform.charAt(0).toUpperCase() + platform.slice(1); const sourceTag = `${platform === 'twitter' ? 'X' : platform.substring(0,2).toUpperCase()}`; updateStep(stepId, 'in_progress', `Szukam ${platformLabel}... ${sourceTag}`); await new Promise(r => setTimeout(r, delay / 2)); if (foundPlatforms && foundPlatforms.includes(platform)) { updateStep(stepId, 'complete', `${platformLabel}: ZNALEZIONO ${sourceTag}`); } else { updateStep(stepId, 'missing', `${platformLabel}: brak profilu ${sourceTag}`); } await new Promise(r => setTimeout(r, delay / 2)); } // Google data updateStep('step-google', 'in_progress', 'Pobieram dane z Google... Google'); await new Promise(r => setTimeout(r, delay)); if (googleData && (googleData.google_rating || googleData.google_reviews_count)) { const rating = googleData.google_rating ? `${googleData.google_rating}/5` : ''; const reviews = googleData.google_reviews_count ? `${googleData.google_reviews_count} opinii` : ''; const details = [rating, reviews].filter(Boolean).join(', '); updateStep('step-google', 'complete', `Google: ${details} Google`); } else { updateStep('step-google', 'missing', 'Google: brak profilu GBP Google'); } } async function runAudit() { const btn = document.getElementById('runAuditBtn'); if (btn) { btn.disabled = true; } showLoading(); // Start animation await new Promise(r => setTimeout(r, 300)); try { const response = await fetch('/api/social/audit', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken }, body: JSON.stringify({ slug: companySlug }) }); const data = await response.json(); console.log('Audit response:', data); // Debug // Animate the steps with found platforms and Google data const foundPlatforms = data.platforms || []; const googleData = data.google_reviews || {}; await animatePlatformSteps(foundPlatforms, googleData); // Save step updateStep('step-save', 'in_progress', 'Zapisuję wyniki do bazy...'); await new Promise(r => setTimeout(r, 400)); if (response.ok && data.success) { const summary = `Zapisano: ${data.profiles_found || 0} profili, wynik: ${data.score || 0}/100`; updateStep('step-save', 'complete', summary); // Wait longer for user to see complete results await new Promise(r => setTimeout(r, 4000)); hideLoading(); showModal('Audyt zakończony', `Znaleziono ${data.profiles_found || 0} profili social media. Strona zostanie odświeżona.`, true); setTimeout(() => location.reload(), 2000); } else { updateStep('step-save', 'error', 'Błąd zapisu: ' + (data.error || 'nieznany błąd')); await new Promise(r => setTimeout(r, 4000)); hideLoading(); showModal('Błąd', data.error || 'Wystąpił nieznany błąd podczas audytu.', false); if (btn) btn.disabled = false; } } catch (error) { hideLoading(); showModal('Błąd połączenia', 'Nie udało się połączyć z serwerem: ' + error.message, false); if (btn) btn.disabled = false; } } // Close modal on overlay click document.getElementById('modalOverlay').addEventListener('click', function(e) { if (e.target === this) { closeModal(); } }); /* ============================================================ AI AUDIT ACTIONS ============================================================ */ const companyId = {{ company.id }}; const auditType = 'social'; function simpleMarkdown(text) { return text .replace(/&/g, '&').replace(//g, '>') .replace(/\*\*(.+?)\*\*/g, '$1') .replace(/\*(.+?)\*/g, '$1') .replace(/^### (.+)$/gm, '

$1

') .replace(/^## (.+)$/gm, '

$1

') .replace(/^- (.+)$/gm, '
  • $1
  • ') .replace(/(
  • .*<\/li>)/gs, '') .replace(/\n/g, '
    '); } async function runAIAnalysis(force) { const prompt = document.getElementById('aiAnalyzePrompt'); const loading = document.getElementById('aiLoading'); const results = document.getElementById('aiResults'); const btn = document.getElementById('aiAnalyzeBtn'); if (btn) btn.disabled = true; if (prompt) prompt.style.display = 'none'; if (results) results.style.display = 'none'; if (loading) loading.style.display = 'block'; let seconds = 0; const timerEl = document.getElementById('aiTimer'); const timerInterval = setInterval(() => { seconds++; if (timerEl) timerEl.textContent = seconds + 's'; }, 1000); try { const response = await fetch('/api/audit/analyze', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken }, body: JSON.stringify({ company_id: companyId, audit_type: auditType, force: !!force }) }); const data = await response.json(); clearInterval(timerInterval); if (loading) loading.style.display = 'none'; if (data.success) { renderAIResults(data); } else { if (prompt) prompt.style.display = 'none'; if (btn) btn.disabled = false; const results = document.getElementById('aiResults'); results.innerHTML = `

    Błąd analizy AI

    ${escapeHtml(data.error || 'Nieznany błąd')}

    `; results.style.display = 'block'; } } catch (error) { clearInterval(timerInterval); if (loading) loading.style.display = 'none'; if (prompt) prompt.style.display = 'none'; if (btn) btn.disabled = false; const results = document.getElementById('aiResults'); results.innerHTML = `

    Błąd połączenia

    ${escapeHtml(error.message)}

    `; results.style.display = 'block'; } } function renderAIResults(data) { const results = document.getElementById('aiResults'); const summaryEl = document.getElementById('aiSummaryText'); const cacheInfo = document.getElementById('aiCacheInfo'); const actionsList = document.getElementById('aiActionsList'); summaryEl.textContent = data.summary || ''; if (data.cached && data.generated_at) { const d = new Date(data.generated_at); document.getElementById('aiCacheDate').textContent = d.toLocaleDateString('pl-PL') + ' ' + d.toLocaleTimeString('pl-PL', {hour:'2-digit', minute:'2-digit'}); cacheInfo.style.display = 'block'; } else { cacheInfo.style.display = 'none'; } actionsList.innerHTML = ''; const actions = data.actions || []; const priorityLabels = {critical: 'KRYTYCZNE', high: 'WYSOKI', medium: 'ŚREDNI', low: 'NISKI'}; actions.forEach((action, idx) => { const impact = action.impact_score || 5; const effort = action.effort_score || 5; const card = document.createElement('div'); card.className = 'ai-action-card priority-' + (action.priority || 'medium'); card.id = 'ai-action-' + idx; card.innerHTML = `
    ${priorityLabels[action.priority] || 'ŚREDNI'} ${escapeHtml(action.title || '')}

    ${escapeHtml(action.description || '')}

    Wpływ: ${impact}/10
    Wysiłek: ${effort}/10
    `; actionsList.appendChild(card); }); // Render comparison with previous analysis if available if (typeof renderAIComparison === 'function') renderAIComparison(data); results.style.display = 'block'; document.getElementById('aiActionsSection').scrollIntoView({behavior: 'smooth', block: 'start'}); window._aiActions = actions; } async function generateContent(actionType, idx) { const container = document.getElementById('ai-content-' + idx); if (!container) return; if (container.dataset.loaded === 'true') { container.style.display = container.style.display === 'none' ? 'block' : 'none'; return; } container.innerHTML = '
    Generowanie treści...
    '; container.style.display = 'block'; try { const response = await fetch('/api/audit/generate-content', { method: 'POST', headers: {'Content-Type': 'application/json', 'X-CSRFToken': csrfToken}, body: JSON.stringify({company_id: companyId, action_type: actionType, context: {}}) }); const data = await response.json(); if (data.success && data.content) { const isCode = data.content.includes('{') && (data.content.includes('${escapeHtml(data.content)}`; } else { container.innerHTML = `
    ${simpleMarkdown(data.content)}
    `; } container.dataset.loaded = 'true'; container.scrollIntoView({behavior: 'smooth', block: 'nearest'}); } else { container.innerHTML = `
    ${escapeHtml(data.error || 'Błąd generowania')}
    `; } } catch (error) { container.innerHTML = `
    ${escapeHtml(error.message)}
    `; } } function copyContent(btn) { const code = btn.parentElement.querySelector('code') || btn.parentElement.querySelector('.ai-markdown-content'); if (!code) return; navigator.clipboard.writeText(code.textContent).then(() => { const orig = btn.textContent; btn.textContent = 'Skopiowano!'; setTimeout(() => { btn.textContent = orig; }, 2000); }); } function markAction(idx, status) { const card = document.getElementById('ai-action-' + idx); if (!card) return; if (status === 'implemented') card.classList.add('implemented'); else if (status === 'dismissed') card.classList.add('dismissed'); const actions = window._aiActions || []; if (actions[idx] && actions[idx].id) { fetch('/api/audit/actions/' + actions[idx].id + '/status', { method: 'POST', headers: {'Content-Type': 'application/json', 'X-CSRFToken': csrfToken}, body: JSON.stringify({status: status}) }).catch(() => {}); } } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } {% endblock %}