nordabiz/templates/admin/social_audit_detail.html
Maciej Pienczyn 599e4bde83
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
chore(facebook): migrate to *_media_view metrics (Meta deprecation 2026-06-30)
Meta deprecates page_impressions, post_impressions, page_video_views et al.
on 2026-06-30. Replaced by *_media_view family. Both old and new metrics
are requested during the transition window so historical data and fresh
data coexist without UI gaps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 12:59:45 +02:00

1137 lines
59 KiB
HTML

{% extends "base.html" %}
{% block title %}Audyt Social Media - {{ company.name }} - Norda Biznes Partner{% endblock %}
{% block extra_css %}
<style>
.enrichment-spinner {
display: inline-block;
width: 12px; height: 12px;
border: 2px solid rgba(255,255,255,0.3);
border-top-color: #fff;
border-radius: 50%;
animation: spin 0.8s linear infinite;
vertical-align: middle;
margin-right: 4px;
}
@keyframes spin { to { transform: rotate(360deg); } }
.audit-detail {
max-width: 1000px;
margin: 0 auto;
}
.audit-header {
display: flex;
align-items: center;
gap: var(--spacing-md);
margin-bottom: var(--spacing-xl);
flex-wrap: wrap;
}
.audit-header h1 {
font-size: var(--font-size-2xl);
color: var(--text-primary);
margin: 0;
}
.back-link {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
color: var(--text-secondary);
text-decoration: none;
font-size: var(--font-size-sm);
}
.back-link:hover {
color: var(--primary);
}
.audit-header .actions {
margin-left: auto;
display: flex;
gap: var(--spacing-sm);
}
/* Summary strip */
.summary-strip {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: var(--spacing-md);
margin-bottom: var(--spacing-xl);
}
.summary-item {
background: var(--surface);
padding: var(--spacing-md);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
text-align: center;
}
.summary-value {
font-size: var(--font-size-xl);
font-weight: 700;
display: block;
}
.summary-label {
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
/* Recommendations */
.recommendations {
margin-bottom: var(--spacing-xl);
}
.rec-item {
display: flex;
align-items: flex-start;
gap: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius);
margin-bottom: var(--spacing-xs);
font-size: var(--font-size-sm);
}
.rec-item.critical {
background: #fef2f2;
border-left: 3px solid #ef4444;
color: #991b1b;
}
.rec-item.warning {
background: #fffbeb;
border-left: 3px solid #f59e0b;
color: #92400e;
}
.rec-item.info {
background: #eff6ff;
border-left: 3px solid #3b82f6;
color: #1e40af;
}
/* Platform cards */
.platform-section {
margin-bottom: var(--spacing-xl);
}
.platform-detail-card {
background: var(--surface);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
overflow: hidden;
margin-bottom: var(--spacing-md);
}
.platform-detail-header {
display: flex;
align-items: center;
gap: var(--spacing-md);
padding: var(--spacing-md) var(--spacing-lg);
border-bottom: 1px solid var(--border-color, #e5e7eb);
}
.platform-icon-lg {
width: 40px;
height: 40px;
border-radius: var(--radius);
display: flex;
align-items: center;
justify-content: center;
color: white;
flex-shrink: 0;
}
.platform-icon-lg.facebook { background: #1877f2; }
.platform-icon-lg.instagram { background: linear-gradient(45deg, #f09433, #e6683c, #dc2743, #cc2366, #bc1888); }
.platform-icon-lg.linkedin { background: #0a66c2; }
.platform-icon-lg.youtube { background: #ff0000; }
.platform-icon-lg.twitter { background: #000000; }
.platform-icon-lg.tiktok { background: #000000; }
.platform-detail-title {
flex: 1;
}
.platform-detail-title h3 {
margin: 0;
font-size: var(--font-size-lg);
font-weight: 600;
}
.platform-detail-title a {
color: var(--primary);
font-size: var(--font-size-sm);
text-decoration: none;
word-break: break-all;
}
.platform-detail-title a:hover {
text-decoration: underline;
}
.platform-detail-body {
padding: var(--spacing-lg);
}
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: var(--spacing-md);
margin-bottom: var(--spacing-md);
}
.metric {
padding: var(--spacing-sm) var(--spacing-md);
background: var(--background);
border-radius: var(--radius);
}
.metric-value {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--text-primary);
}
.metric-label {
font-size: var(--font-size-xs);
color: var(--text-secondary);
}
.profile-checklist {
display: flex;
gap: var(--spacing-md);
flex-wrap: wrap;
margin-top: var(--spacing-sm);
}
.check-item {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: var(--font-size-sm);
}
.check-item.ok { color: #059669; }
.check-item.missing { color: #dc2626; }
.check-item.unknown { color: var(--text-secondary); }
.completeness-bar-lg {
display: flex;
align-items: center;
gap: var(--spacing-sm);
margin-top: var(--spacing-sm);
}
.completeness-track-lg {
flex: 1;
height: 10px;
background: var(--border-color, #e5e7eb);
border-radius: 5px;
overflow: hidden;
}
.completeness-fill-lg {
height: 100%;
border-radius: 5px;
transition: width 0.3s;
}
.completeness-fill-lg.high { background: #22c55e; }
.completeness-fill-lg.medium { background: #f59e0b; }
.completeness-fill-lg.low { background: #ef4444; }
.meta-info {
display: flex;
gap: var(--spacing-lg);
flex-wrap: wrap;
padding-top: var(--spacing-sm);
border-top: 1px solid var(--border-color, #e5e7eb);
margin-top: var(--spacing-md);
font-size: var(--font-size-xs);
color: var(--text-secondary);
}
/* Data provenance */
.provenance-section {
margin-top: var(--spacing-md);
padding: var(--spacing-md);
background: var(--background);
border-radius: var(--radius);
border: 1px solid var(--border-color, #e5e7eb);
}
.provenance-header {
display: flex;
align-items: center;
gap: var(--spacing-sm);
margin-bottom: var(--spacing-sm);
}
.source-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
color: white;
}
.provenance-details {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--spacing-xs) var(--spacing-md);
font-size: var(--font-size-xs);
color: var(--text-secondary);
}
.provenance-detail {
display: flex;
align-items: center;
gap: 4px;
}
.provenance-detail .label {
font-weight: 500;
color: var(--text-primary);
}
.field-source-list {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: var(--spacing-xs);
}
.field-tag {
display: inline-flex;
align-items: center;
gap: 2px;
padding: 1px 6px;
border-radius: 4px;
font-size: 10px;
font-weight: 500;
}
.field-tag.api { background: #dcfce7; color: #15803d; }
.field-tag.scrape { background: #fef3c7; color: #92400e; }
.field-tag.empty { background: #fee2e2; color: #991b1b; }
.oauth-prompt {
display: flex;
align-items: center;
gap: var(--spacing-sm);
margin-top: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
background: #eff6ff;
border: 1px solid #bfdbfe;
border-radius: var(--radius);
font-size: var(--font-size-xs);
}
.oauth-prompt a {
color: #2563eb;
font-weight: 600;
text-decoration: none;
}
.oauth-prompt a:hover {
text-decoration: underline;
}
.oauth-prompt.connected {
background: #f0fdf4;
border-color: #bbf7d0;
}
.oauth-prompt.expired {
background: #fef2f2;
border-color: #fecaca;
}
.sync-btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 3px 10px;
background: #2563eb;
color: white;
border: none;
border-radius: var(--radius);
font-size: 11px;
font-weight: 500;
cursor: pointer;
text-decoration: none;
}
.sync-btn:hover {
background: #1d4ed8;
}
.provenance-toggle {
font-size: 10px;
color: var(--text-secondary);
cursor: pointer;
background: none;
border: none;
padding: 0;
text-decoration: underline;
margin-left: auto;
}
.hidden { display: none !important; }
/* Invalid profiles */
.invalid-section {
background: #fef2f2;
border: 1px solid #fecaca;
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
margin-bottom: var(--spacing-xl);
}
.invalid-section h3 {
color: #991b1b;
margin: 0 0 var(--spacing-md);
font-size: var(--font-size-base);
}
.invalid-item {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-xs) 0;
font-size: var(--font-size-sm);
color: #7f1d1d;
}
/* Health score circle */
.health-ring {
width: 80px;
height: 80px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: var(--font-size-2xl);
font-weight: 700;
flex-shrink: 0;
}
.health-ring.high { background: #dcfce7; color: #15803d; border: 3px solid #22c55e; }
.health-ring.medium { background: #fef3c7; color: #92400e; border: 3px solid #f59e0b; }
.health-ring.low { background: #fef2f2; color: #991b1b; border: 3px solid #ef4444; }
.scores-strip {
display: flex;
gap: var(--spacing-lg);
align-items: center;
background: var(--surface);
padding: var(--spacing-lg);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
margin-bottom: var(--spacing-xl);
flex-wrap: wrap;
}
.score-details {
flex: 1;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
gap: var(--spacing-sm);
}
.score-item {
padding: var(--spacing-sm);
background: var(--background);
border-radius: var(--radius);
text-align: center;
}
.score-item-value {
font-size: var(--font-size-lg);
font-weight: 600;
}
.score-item-label {
font-size: var(--font-size-xs);
color: var(--text-secondary);
}
.growth-indicator {
display: inline-flex;
align-items: center;
gap: 2px;
font-size: var(--font-size-sm);
font-weight: 500;
}
.growth-indicator.up { color: #22c55e; }
.growth-indicator.down { color: #ef4444; }
.growth-indicator.stable { color: var(--text-secondary); }
.growth-indicator.unknown { color: var(--text-secondary); }
/* Empty state */
.empty-profiles {
text-align: center;
padding: var(--spacing-2xl);
background: var(--surface);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
color: var(--text-secondary);
}
@media (max-width: 640px) {
.audit-header {
flex-direction: column;
align-items: flex-start;
}
.audit-header .actions {
margin-left: 0;
}
.metrics-grid {
grid-template-columns: 1fr 1fr;
}
}
</style>
{% endblock %}
{% block content %}
<div class="audit-detail">
<a href="{{ url_for('admin.admin_social_audit') }}" class="back-link">
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
Powrót do audytu
</a>
<div class="audit-header">
<div>
<h1>{{ company.name }}</h1>
{% if company.website %}
<a href="{{ company.website }}" target="_blank" rel="noopener" style="color: var(--primary); font-size: var(--font-size-sm);">{{ company.website }}</a>
{% endif %}
</div>
<div class="actions">
<button id="enrichSingleBtn" class="btn btn-primary btn-sm" onclick="runSingleEnrichment({{ company.id }})">
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
Uruchom audyt
</button>
<a href="{{ url_for('company_detail', company_id=company.id) }}" class="btn btn-outline btn-sm">Profil firmy</a>
</div>
</div>
<!-- Summary -->
<div class="summary-strip">
<div class="summary-item">
<span class="summary-value">{{ platform_details|length }}</span>
<span class="summary-label">Platform</span>
</div>
<div class="summary-item">
<span class="summary-value">{{ "{:,}".format(platform_details|sum(attribute='followers_count')).replace(",", " ") }}</span>
<span class="summary-label">Obserwujących</span>
</div>
<div class="summary-item">
{% set total_posts = platform_details|sum(attribute='posts_count_30d') %}
<span class="summary-value">{{ total_posts }}</span>
<span class="summary-label">Postów (30 dni)</span>
</div>
<div class="summary-item">
{% set comp_scores = platform_details|selectattr('profile_completeness_score', 'gt', 0)|map(attribute='profile_completeness_score')|list %}
{% set avg_comp = (comp_scores|sum / comp_scores|length)|round|int if comp_scores else 0 %}
<span class="summary-value {{ 'green' if avg_comp >= 60 else ('yellow' if avg_comp >= 30 else 'red') }}">{{ avg_comp }}%</span>
<span class="summary-label">Śr. kompletność</span>
</div>
<div class="summary-item">
{% set eng_rates = platform_details|selectattr('engagement_rate', 'gt', 0)|map(attribute='engagement_rate')|list %}
{% set avg_eng = (eng_rates|sum / eng_rates|length)|round(2) if eng_rates else 0 %}
<span class="summary-value">{{ avg_eng }}%</span>
<span class="summary-label">Śr. engagement</span>
</div>
</div>
<!-- Health Score & Computed Metrics -->
{% if company_scores and platform_details %}
<div class="scores-strip">
<div class="health-ring {{ 'high' if company_scores.health_score >= 60 else ('medium' if company_scores.health_score >= 30 else 'low') }}" title="Social Health Score">
{{ company_scores.health_score }}
</div>
<div style="flex-shrink: 0;">
<div style="font-weight: 600; font-size: var(--font-size-lg);">Social Health Score</div>
<div style="font-size: var(--font-size-sm); color: var(--text-secondary);">
{% if company_scores.health_score >= 60 %}Dobra kondycja
{% elif company_scores.health_score >= 30 %}Wymaga uwagi
{% else %}Pilne do poprawy{% endif %}
</div>
</div>
<div class="score-details">
<div class="score-item">
<div class="score-item-value" style="color: {{ company_scores.activity_color }};">{{ company_scores.activity_label }}</div>
<div class="score-item-label">Aktywność</div>
</div>
<div class="score-item">
<div class="score-item-value">{{ company_scores.cross_platform_score }}%</div>
<div class="score-item-label">Cross-platform</div>
</div>
{% for p in platform_details %}
{% if p.followers_growth_trend != 'unknown' %}
<div class="score-item">
<div class="score-item-value">
<span class="growth-indicator {{ p.followers_growth_trend }}">
{% if p.followers_growth_trend == 'up' %}&#9650;{% elif p.followers_growth_trend == 'down' %}&#9660;{% else %}&#9679;{% endif %}
{{ p.followers_growth_rate }}%
</span>
</div>
<div class="score-item-label">{{ p.platform|capitalize }} wzrost</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
<!-- Recommendations -->
{% if recommendations %}
<div class="recommendations">
<h2 style="font-size: var(--font-size-lg); font-weight: 600; margin-bottom: var(--spacing-sm);">Zalecenia</h2>
{% for rec in recommendations %}
<div class="rec-item {{ rec.severity }}">
{% if rec.severity == 'critical' %}
<svg width="16" height="16" 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-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>
{% elif rec.severity == 'warning' %}
<svg width="16" height="16" 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-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>
{% else %}
<svg width="16" height="16" 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>
{% endif %}
<span>{{ rec.text }}</span>
</div>
{% endfor %}
</div>
{% endif %}
<!-- Platform Details -->
<div class="platform-section">
<h2 style="font-size: var(--font-size-lg); font-weight: 600; margin-bottom: var(--spacing-md);">Profile social media</h2>
{% if platform_details %}
{% set platform_icons = {
'facebook': '<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>',
'instagram': '<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>',
'linkedin': '<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>',
'youtube': '<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><path d="M23.498 6.186a3.016 3.016 0 00-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 00.502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 002.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 002.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>',
'twitter': '<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>',
'tiktok': '<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><path d="M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z"/></svg>'
} %}
{% for p in platform_details %}
<div class="platform-detail-card">
<div class="platform-detail-header">
<div class="platform-icon-lg {{ p.platform }}">
{{ platform_icons[p.platform]|safe }}
</div>
<div class="platform-detail-title">
<h3>{{ p.platform|capitalize }}{% if p.page_name %} — {{ p.page_name }}{% endif %}</h3>
<a href="{{ p.url }}" target="_blank" rel="noopener">{{ p.url }}</a>
</div>
{% if p.check_status == 'needs_verification' %}
<span style="background: #fef3c7; color: #b45309; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 500;">Do weryfikacji</span>
{% endif %}
</div>
<div class="platform-detail-body">
<div class="metrics-grid">
<div class="metric">
<div class="metric-value">
{{ "{:,}".format(p.followers_count).replace(",", " ") }}
{% if p.followers_growth_trend != 'unknown' %}
<span class="growth-indicator {{ p.followers_growth_trend }}" style="font-size: 13px; margin-left: 4px;">
{% if p.followers_growth_trend == 'up' %}&#9650;{% elif p.followers_growth_trend == 'down' %}&#9660;{% else %}&#8212;{% endif %}
{{ p.followers_growth_rate }}%
</span>
{% endif %}
</div>
<div class="metric-label">Obserwujących</div>
</div>
<div class="metric">
<div class="metric-value">{{ p.engagement_rate }}%</div>
<div class="metric-label">Engagement rate</div>
</div>
<div class="metric">
<div class="metric-value" style="color: {{ p.activity_color }};">{{ p.activity_label }}</div>
<div class="metric-label">Status aktywności</div>
</div>
<div class="metric">
<div class="metric-value">{{ p.posts_count_30d }}</div>
<div class="metric-label">Postów (30 dni)</div>
</div>
<div class="metric">
<div class="metric-value">{{ p.posts_count_365d }}</div>
<div class="metric-label">Postów (rok)</div>
</div>
<div class="metric">
<div class="metric-value">{{ p.posting_frequency_score }}/10</div>
<div class="metric-label">Regularność</div>
</div>
<div class="metric">
<div class="metric-value">
{% if p.last_post_date %}
{{ p.last_post_date.strftime('%d.%m.%Y') }}
{% else %}
<span style="color: var(--text-secondary);">b/d</span>
{% endif %}
</div>
<div class="metric-label">Ostatni post</div>
</div>
</div>
<!-- Profile completeness -->
{% if p.profile_completeness_score > 0 %}
<div style="margin-bottom: var(--spacing-md);">
<span style="font-size: var(--font-size-sm); font-weight: 500;">Kompletność profilu: {{ p.profile_completeness_score }}%</span>
<div class="completeness-bar-lg">
<div class="completeness-track-lg">
<div class="completeness-fill-lg {{ 'high' if p.profile_completeness_score >= 60 else ('medium' if p.profile_completeness_score >= 30 else 'low') }}"
style="width: {{ p.profile_completeness_score }}%"></div>
</div>
</div>
</div>
{% endif %}
<!-- Profile checklist -->
<div class="profile-checklist">
{% if p.has_profile_photo is not none %}
<span class="check-item {{ 'ok' if p.has_profile_photo else 'missing' }}">
{{ '✓' if p.has_profile_photo else '✗' }} Zdjęcie profilowe
</span>
{% else %}
<span class="check-item unknown">? Zdjęcie profilowe</span>
{% endif %}
{% if p.has_cover_photo is not none %}
<span class="check-item {{ 'ok' if p.has_cover_photo else 'missing' }}">
{{ '✓' if p.has_cover_photo else '✗' }} Zdjęcie w tle
</span>
{% else %}
<span class="check-item unknown">? Zdjęcie w tle</span>
{% endif %}
{% if p.has_bio is not none %}
<span class="check-item {{ 'ok' if p.has_bio else 'missing' }}">
{{ '✓' if p.has_bio else '✗' }} Opis / bio
</span>
{% else %}
<span class="check-item unknown">? Opis / bio</span>
{% endif %}
</div>
{% if p.profile_description %}
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-sm) var(--spacing-md); background: var(--background); border-radius: var(--radius); font-size: var(--font-size-sm); color: var(--text-secondary);">
{{ p.profile_description[:300] }}{% if p.profile_description|length > 300 %}...{% endif %}
</div>
{% endif %}
{% if p.content_types %}
{% set ct = p.content_types %}
<!-- Contact & Business Info -->
{% set info_fields = [] %}
{% if ct.get('phone') %}{% if info_fields.append(('📞', ct.phone)) %}{% endif %}{% endif %}
{% if ct.get('emails') %}{% for e in ct.emails %}{% if info_fields.append(('✉️', e)) %}{% endif %}{% endfor %}{% endif %}
{% if ct.get('address') %}{% if info_fields.append(('📍', ct.address)) %}{% endif %}{% endif %}
{% if ct.get('website') %}{% if info_fields.append(('🌐', ct.website)) %}{% endif %}{% endif %}
{% if ct.get('category') %}{% if info_fields.append(('🏷️', ct.category)) %}{% endif %}{% endif %}
{% if ct.get('founded') %}{% if info_fields.append(('📅', 'Założono: ' ~ ct.founded)) %}{% endif %}{% endif %}
{% if ct.get('price_range') %}{% if info_fields.append(('💰', 'Ceny: ' ~ ct.price_range)) %}{% endif %}{% endif %}
{% if ct.get('hours') %}{% if info_fields.append(('🕐', 'Godziny otwarcia ustawione')) %}{% endif %}{% endif %}
{% if ct.get('username') %}{% if info_fields.append(('🔗', '@' ~ ct.username)) %}{% endif %}{% endif %}
{% if ct.get('verification_status') == 'blue_verified' or ct.get('is_verified') %}{% if info_fields.append(('✅', 'Zweryfikowana')) %}{% endif %}{% endif %}
{% if info_fields %}
<div style="margin-top: var(--spacing-sm); display: flex; gap: var(--spacing-xs); flex-wrap: wrap;">
{% for icon, val in info_fields %}
<span style="background: var(--background); padding: 3px 10px; border-radius: var(--radius); font-size: 12px; color: var(--text-secondary); border: 1px solid var(--border-color, #e5e7eb);">
{{ icon }} {{ val }}
</span>
{% endfor %}
</div>
{% endif %}
<!-- Activity & Reach -->
{% if ct.get('talking_about_count') or ct.get('overall_star_rating') or ct.get('were_here_count') or ct.get('category_list') %}
<div style="margin-top: var(--spacing-sm); display: flex; gap: var(--spacing-md); flex-wrap: wrap; font-size: var(--font-size-sm);">
{% if ct.get('talking_about_count') %}
<span>🗣️ {{ ct.talking_about_count }} interakcji (7 dni)</span>
{% endif %}
{% if ct.get('overall_star_rating') %}
<span>⭐ {{ ct.overall_star_rating }}/5 {% if ct.get('rating_count') %}({{ ct.rating_count }} ocen){% endif %}</span>
{% endif %}
{% if ct.get('were_here_count') %}
<span>📌 {{ "{:,}".format(ct.were_here_count).replace(",", " ") }} zameldowań</span>
{% endif %}
{% if ct.get('category_list') %}
<span>🏷️ {{ ct.category_list|join(', ') }}</span>
{% endif %}
</div>
{% endif %}
<!-- Engagement stats (from posts in last 365 days) -->
{% if ct.get('total_likes') or ct.get('total_comments') or ct.get('total_shares') %}
<div style="margin-top: var(--spacing-sm);">
<div style="font-size: 11px; font-weight: 600; color: var(--text-secondary); margin-bottom: 4px;">Zaangażowanie w posty (rok)</div>
<div style="display: flex; gap: var(--spacing-sm); flex-wrap: wrap;">
<span style="background: #dbeafe; color: #1d4ed8; padding: 3px 10px; border-radius: var(--radius); font-size: 12px; font-weight: 500;">👍 {{ ct.total_likes }} polubień postów</span>
<span style="background: #dcfce7; color: #166534; padding: 3px 10px; border-radius: var(--radius); font-size: 12px; font-weight: 500;">💬 {{ ct.total_comments }} komentarzy</span>
<span style="background: #fef3c7; color: #92400e; padding: 3px 10px; border-radius: var(--radius); font-size: 12px; font-weight: 500;">🔄 {{ ct.total_shares }} udostępnień</span>
</div>
</div>
{% endif %}
<!-- Post types breakdown -->
{% if ct.get('post_types') %}
<div style="margin-top: var(--spacing-sm); display: flex; gap: var(--spacing-xs); flex-wrap: wrap;">
{% for ptype, count in ct.post_types.items() %}
<span style="background: var(--background); padding: 2px 8px; border-radius: var(--radius-sm); font-size: 11px; color: var(--text-secondary);">
{{ ptype }}: {{ count }}
</span>
{% endfor %}
</div>
{% endif %}
{# Meta deprecated impressions metrics on 2026-06-30. New: page_media_view, page_total_media_view_unique. Fallback to old keys for historical data. #}
{% set page_views_metric = ct.get('insights_page_total_media_view_unique') or ct.get('insights_page_impressions') %}
{% set page_video_metric = ct.get('insights_page_media_view') or ct.get('insights_page_video_views') %}
{% set page_reach_metric = ct.get('insights_page_posts_impressions') %}
{% set has_insights = page_views_metric or ct.get('insights_page_views_total') or ct.get('insights_page_post_engagements') or page_reach_metric or ct.get('insights_page_daily_follows') %}
{% if has_insights %}
<div style="margin-top: var(--spacing-sm); padding: var(--spacing-sm) var(--spacing-md); background: #f0f9ff; border-radius: var(--radius); border: 1px solid #bae6fd;">
<div style="font-size: 11px; font-weight: 600; color: #0369a1; margin-bottom: 4px;">📊 Insights (28 dni)</div>
<div style="display: flex; gap: var(--spacing-md); flex-wrap: wrap; font-size: var(--font-size-sm); color: #0c4a6e;">
{% if page_reach_metric %}<span>{{ "{:,}".format(page_reach_metric).replace(",", " ") }} zasięg postów</span>{% endif %}
{% if page_views_metric %}<span>{{ "{:,}".format(page_views_metric).replace(",", " ") }} wyświetleń strony</span>{% endif %}
{% if ct.get('insights_page_views_total') %}<span>{{ "{:,}".format(ct.insights_page_views_total).replace(",", " ") }} odsłon strony</span>{% endif %}
{% if ct.get('insights_page_post_engagements') %}<span>{{ "{:,}".format(ct.insights_page_post_engagements).replace(",", " ") }} interakcji</span>{% endif %}
{% if page_video_metric %}<span>{{ "{:,}".format(page_video_metric).replace(",", " ") }} odsłon wideo</span>{% endif %}
{% if ct.get('insights_page_daily_follows') %}<span>+{{ ct.insights_page_daily_follows }} nowych obserwujących</span>{% endif %}
{% if ct.get('insights_page_daily_unfollows') %}<span>-{{ ct.insights_page_daily_unfollows }} utraconych</span>{% endif %}
{% if ct.get('insights_page_fan_adds') %}<span>+{{ ct.insights_page_fan_adds }} nowych fanów</span>{% endif %}
{% if ct.get('insights_page_fan_removes') %}<span>-{{ ct.insights_page_fan_removes }} utraconych fanów</span>{% endif %}
</div>
</div>
{% endif %}
<!-- Recent posts -->
{% if ct.get('recent_posts') %}
<div style="margin-top: var(--spacing-sm);">
<div style="font-size: 11px; font-weight: 600; color: var(--text-secondary); margin-bottom: 4px;">Ostatnie posty</div>
{% for rp in ct.recent_posts %}
<div style="padding: 4px 0; border-bottom: 1px solid #f3f4f6; font-size: 12px; display: flex; gap: var(--spacing-sm); align-items: baseline;">
<span style="color: var(--text-secondary); white-space: nowrap;">{{ rp.date }}</span>
<span style="color: var(--text-secondary); font-size: 10px; text-transform: uppercase;">{{ rp.type }}</span>
<span style="flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ rp.title or '(bez tekstu)' }}</span>
<span style="white-space: nowrap; color: var(--text-secondary); font-size: 11px;">👍{{ rp.likes }} 💬{{ rp.comments }} 🔄{{ rp.shares }}</span>
</div>
{% endfor %}
</div>
{% endif %}
<!-- Instagram extra data (from Graph API) -->
{% if p.platform == 'instagram' and (ct.get('follows_count') or ct.get('media_count') or ct.get('media_types')) %}
<div style="margin-top: var(--spacing-sm);">
{% set ig_info = [] %}
{% if ct.get('media_count') %}{% if ig_info.append(('📸', '{:,}'.format(ct.media_count).replace(',', ' ') ~ ' postów łącznie')) %}{% endif %}{% endif %}
{% if ct.get('follows_count') %}{% if ig_info.append(('👤', '{:,}'.format(ct.follows_count).replace(',', ' ') ~ ' obserwowanych')) %}{% endif %}{% endif %}
{% if ct.get('website') %}{% if ig_info.append(('🔗', ct.website)) %}{% endif %}{% endif %}
{% if ct.get('media_types') %}
{% set types_str = [] %}
{% for mt, cnt in ct.media_types.items() %}{% if types_str.append(mt ~ ': ' ~ cnt) %}{% endif %}{% endfor %}
{% if ig_info.append(('🎬', types_str|join(', '))) %}{% endif %}
{% endif %}
{% if ct.get('ig_reach_total') %}{% if ig_info.append(('📊', '{:,}'.format(ct.ig_reach_total).replace(',', ' ') ~ ' zasięg (28d)')) %}{% endif %}{% endif %}
{% if ct.get('ig_impressions_total') %}{% if ig_info.append(('👁️', '{:,}'.format(ct.ig_impressions_total).replace(',', ' ') ~ ' wyświetleń (28d)')) %}{% endif %}{% endif %}
{% if ig_info %}
<div style="display: flex; gap: var(--spacing-xs); flex-wrap: wrap; margin-bottom: var(--spacing-sm);">
{% for icon, val in ig_info %}
<span style="background: var(--background); padding: 3px 10px; border-radius: var(--radius); font-size: 12px; color: var(--text-secondary); border: 1px solid var(--border-color, #e5e7eb);">
{{ icon }} {{ val }}
</span>
{% endfor %}
</div>
{% endif %}
{% if ct.get('recent_posts') %}
<div style="font-size: 11px; font-weight: 600; color: var(--text-secondary); margin-bottom: 4px;">Ostatnie posty</div>
{% for post in ct.recent_posts %}
<div style="padding: 4px 0; border-bottom: 1px solid #f3f4f6; font-size: 12px; display: flex; gap: var(--spacing-sm); align-items: baseline;">
<span style="color: var(--text-secondary); white-space: nowrap;">{{ post.date }}</span>
<span style="flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ post.caption or '(bez opisu)' }}</span>
<span style="white-space: nowrap; color: var(--text-secondary); font-size: 11px;">❤️{{ post.likes }} 💬{{ post.comments }}</span>
{% if post.permalink %}
<a href="{{ post.permalink }}" target="_blank" rel="noopener" style="font-size: 11px; color: #E4405F; white-space: nowrap;">↗ Zobacz</a>
{% endif %}
</div>
{% endfor %}
{% endif %}
</div>
{% endif %}
<!-- Twitter/X extra data -->
{% if p.platform == 'twitter' and (ct.get('following_count') or ct.get('location') or ct.get('media_count')) %}
<div style="margin-top: var(--spacing-sm);">
{% set tw_info = [] %}
{% if ct.get('following_count') %}{% if tw_info.append(('👤', '{:,}'.format(ct.following_count).replace(',', ' ') ~ ' obserwowanych')) %}{% endif %}{% endif %}
{% if ct.get('media_count') %}{% if tw_info.append(('🖼️', '{:,}'.format(ct.media_count).replace(',', ' ') ~ ' mediów')) %}{% endif %}{% endif %}
{% if ct.get('favourites_count') %}{% if tw_info.append(('❤️', '{:,}'.format(ct.favourites_count).replace(',', ' ') ~ ' polubień')) %}{% endif %}{% endif %}
{% if ct.get('listed_count') %}{% if tw_info.append(('📋', '{:,}'.format(ct.listed_count).replace(',', ' ') ~ ' list')) %}{% endif %}{% endif %}
{% if ct.get('location') %}{% if tw_info.append(('📍', ct.location)) %}{% endif %}{% endif %}
{% if ct.get('created_at') %}{% if tw_info.append(('📅', 'Konto od: ' ~ ct.created_at[:16])) %}{% endif %}{% endif %}
{% if ct.get('verified') %}{% if tw_info.append(('✓', 'Zweryfikowany')) %}{% endif %}{% endif %}
{% if tw_info %}
<div style="display: flex; gap: var(--spacing-xs); flex-wrap: wrap; margin-bottom: var(--spacing-sm);">
{% for icon, val in tw_info %}
<span style="background: var(--background); padding: 3px 10px; border-radius: var(--radius); font-size: 12px; color: var(--text-secondary); border: 1px solid var(--border-color, #e5e7eb);">
{{ icon }} {{ val }}
</span>
{% endfor %}
</div>
{% endif %}
</div>
{% endif %}
<!-- YouTube extra data -->
{% if ct.get('view_count') or ct.get('recent_videos') %}
<div style="margin-top: var(--spacing-sm);">
{% set yt_info = [] %}
{% if ct.get('view_count') %}{% if yt_info.append(('👁️', '{:,}'.format(ct.view_count).replace(',', ' ') ~ ' wyświetleń łącznie')) %}{% endif %}{% endif %}
{% if ct.get('country') %}{% if yt_info.append(('🌍', ct.country)) %}{% endif %}{% endif %}
{% if ct.get('published_at') %}{% if yt_info.append(('📅', 'Kanał od: ' ~ ct.published_at)) %}{% endif %}{% endif %}
{% if ct.get('custom_url') %}{% if yt_info.append(('🔗', ct.custom_url)) %}{% endif %}{% endif %}
{% if yt_info %}
<div style="display: flex; gap: var(--spacing-xs); flex-wrap: wrap; margin-bottom: var(--spacing-sm);">
{% for icon, val in yt_info %}
<span style="background: var(--background); padding: 3px 10px; border-radius: var(--radius); font-size: 12px; color: var(--text-secondary); border: 1px solid var(--border-color, #e5e7eb);">
{{ icon }} {{ val }}
</span>
{% endfor %}
</div>
{% endif %}
{% if ct.get('recent_videos') %}
<div style="font-size: 11px; font-weight: 600; color: var(--text-secondary); margin-bottom: 4px;">Ostatnie filmy</div>
{% for vid in ct.recent_videos %}
<div style="padding: 4px 0; border-bottom: 1px solid #f3f4f6; font-size: 12px; display: flex; gap: var(--spacing-sm); align-items: baseline;">
<span style="color: var(--text-secondary); white-space: nowrap;">{{ vid.date }}</span>
<span style="flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ vid.title or '(bez tytułu)' }}</span>
{% if vid.video_id %}
<a href="https://youtube.com/watch?v={{ vid.video_id }}" target="_blank" rel="noopener" style="font-size: 11px; color: #dc2626; white-space: nowrap;">▶ Oglądaj</a>
{% endif %}
</div>
{% endfor %}
{% endif %}
</div>
{% endif %}
{% endif %}
<!-- Data Provenance Section -->
<div class="provenance-section">
<div class="provenance-header">
<span class="source-badge" style="background: {{ p.source_color }};">
{% if p.source_icon == 'api' %}
<svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/></svg>
{% elif p.source_icon == 'scrape' %}
<svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9"/></svg>
{% elif p.source_icon == 'manual' %}
<svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
{% else %}
<svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
{% endif %}
{{ p.source_label }}
</span>
<span style="font-size: 10px; color: var(--text-secondary);" title="{{ p.source_description }}">
Priorytet: {{ p.source_priority }}/3
</span>
<button class="provenance-toggle" onclick="this.closest('.provenance-section').querySelector('.provenance-expanded').classList.toggle('hidden')">
szczegóły &#9662;
</button>
</div>
<div class="provenance-details">
{% if p.verified_at %}
<div class="provenance-detail">
<span class="label">Zweryfikowano:</span>
{{ p.verified_at|local_time('%d.%m.%Y %H:%M') }}
</div>
{% endif %}
{% if p.last_checked_at %}
<div class="provenance-detail">
<span class="label">Ostatni check:</span>
{{ p.last_checked_at|local_time('%d.%m.%Y %H:%M') }}
</div>
{% endif %}
<div class="provenance-detail">
<span class="label">Status HTTP:</span>
{{ p.check_status or 'ok' }}
</div>
</div>
<!-- Expanded details (hidden by default) -->
<div class="provenance-expanded hidden" style="margin-top: var(--spacing-sm);">
<div style="font-size: 11px; color: var(--text-secondary); margin-bottom: var(--spacing-xs); font-style: italic;">
{{ p.source_description }}
</div>
{% if p.fields_from_api or p.fields_from_scraping or p.fields_empty %}
<div style="font-size: 11px; font-weight: 500; margin-bottom: 4px;">Pochodzenie danych per pole:</div>
<div class="field-source-list">
{% set field_names = {
'followers_count': 'Followers',
'engagement_rate': 'Engagement',
'profile_completeness_score': 'Kompletność',
'posts_count_30d': 'Posty 30d',
'last_post_date': 'Ostatni post',
'has_bio': 'Bio',
'page_name': 'Nazwa',
'has_profile_photo': 'Zdjęcie'
} %}
{% for f in p.fields_from_api %}
<span class="field-tag api" title="Dane z autoryzowanego API">&#10003; {{ field_names.get(f, f) }}</span>
{% endfor %}
{% for f in p.fields_from_scraping %}
<span class="field-tag scrape" title="Dane ze scrapingu (szacunkowe)">~ {{ field_names.get(f, f) }}</span>
{% endfor %}
{% for f in p.fields_empty %}
<span class="field-tag empty" title="Brak danych — wymaga audytu lub połączenia OAuth">&#10007; {{ field_names.get(f, f) }}</span>
{% endfor %}
</div>
{% endif %}
</div>
<!-- OAuth status / prompt -->
{% if p.oauth_available %}
{% if p.oauth_connected and not p.oauth_expired %}
<div class="oauth-prompt connected">
<svg width="14" height="14" fill="none" stroke="#15803d" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/></svg>
<span style="color: #15803d; font-weight: 500;">OAuth połączony</span>
{% if p.oauth_account_name %}
<span style="color: var(--text-secondary);">({{ p.oauth_account_name }})</span>
{% endif %}
{% if p.oauth_page_configured %}
<span style="color: #15803d;">&#10003; Strona skonfigurowana</span>
{% else %}
<a href="{{ url_for('admin.social_publisher_company_settings', company_id=company.id) }}">Wybierz stronę &rarr;</a>
{% endif %}
{% if p.oauth_last_sync %}
<span style="margin-left: auto; color: var(--text-secondary);">Sync: {{ p.oauth_last_sync|local_time('%d.%m.%Y') }}</span>
{% endif %}
</div>
{% elif p.oauth_connected and p.oauth_expired %}
<div class="oauth-prompt expired">
<svg width="14" height="14" fill="none" stroke="#dc2626" 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-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>
<span style="color: #dc2626; font-weight: 500;">Token OAuth wygasł</span>
<a href="{{ url_for('admin.social_publisher_company_settings', company_id=company.id) }}">Odnów połączenie &rarr;</a>
</div>
{% else %}
<div class="oauth-prompt">
<svg width="14" height="14" fill="none" stroke="#2563eb" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/></svg>
<span>Połącz <strong>{{ p.platform|capitalize }} API</strong> przez OAuth, aby uzyskać dokładne dane (followers, engagement, insights).</span>
<a href="{{ url_for('admin.social_publisher_company_settings', company_id=company.id) }}">Połącz OAuth &rarr;</a>
</div>
{% endif %}
{% else %}
<div class="oauth-prompt" style="background: #f9fafb; border-color: #e5e7eb;">
<svg width="14" height="14" fill="none" stroke="#9ca3af" 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>
<span style="color: var(--text-secondary);">
{% if p.platform == 'instagram' %}
Dane Instagram wymagają połączenia z Meta API. Aby pobierać statystyki: (1) konto IG musi być typu Business/Creator, (2) musi być powiązane ze stroną Facebook firmy, (3) firma musi mieć podłączony Facebook OAuth na portalu.
{% elif p.platform == 'twitter' %}
Dane pobierane automatycznie przez Twitter API (bez OAuth). Uruchom skan, aby pobrać aktualne statystyki.
{% elif p.platform == 'youtube' %}
Dane pobierane automatycznie przez YouTube Data API v3 (bez OAuth). Uruchom skan, aby pobrać aktualne statystyki.
{% elif p.platform == 'facebook' %}
Facebook blokuje publiczny dostęp. Podłącz Graph API (OAuth) w ustawieniach firmy, aby pobierać dane.
{% else %}
OAuth API niedostępne dla {{ p.platform|capitalize }}. Dane pochodzą ze scrapingu publicznych profili.
{% endif %}
</span>
</div>
{% endif %}
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="empty-profiles">
<svg width="48" height="48" fill="none" stroke="currentColor" viewBox="0 0 24 24" style="margin-bottom: var(--spacing-md); opacity: 0.3;">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"/>
</svg>
<p>Firma nie ma żadnych profili social media.</p>
</div>
{% endif %}
</div>
<!-- Invalid profiles -->
{% if invalid_profiles %}
<div class="invalid-section">
<h3>Nieaktywne / nieważne profile ({{ invalid_profiles|length }})</h3>
{% for p in invalid_profiles %}
<div class="invalid-item">
<span style="font-weight: 500; min-width: 80px; text-transform: capitalize;">{{ p.platform }}</span>
<a href="{{ p.url }}" target="_blank" rel="noopener" style="color: #7f1d1d;">{{ p.url|truncate(60) }}</a>
<span style="margin-left: auto; font-size: 11px;">{{ p.check_status or 'invalid' }}</span>
</div>
{% endfor %}
</div>
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
function runSingleEnrichment(companyId) {
var btn = document.getElementById('enrichSingleBtn');
btn.disabled = true;
btn.innerHTML = '<span class="enrichment-spinner"></span> Audytowanie...';
fetch('{{ url_for("admin.admin_social_audit_run_enrichment") }}', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRFToken': '{{ csrf_token() }}'},
body: 'company_ids=' + companyId
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.status === 'started') {
pollSingleEnrichment();
} else {
btn.disabled = false;
btn.textContent = data.error || 'Błąd';
setTimeout(function() { btn.textContent = 'Uruchom audyt'; }, 3000);
}
})
.catch(function(e) {
btn.disabled = false;
btn.textContent = 'Błąd: ' + e.message;
setTimeout(function() { btn.textContent = 'Uruchom audyt'; }, 3000);
});
}
function pollSingleEnrichment() {
var btn = document.getElementById('enrichSingleBtn');
fetch('{{ url_for("admin.admin_social_audit_enrichment_status") }}')
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.running) {
var pct = data.progress || 0;
btn.innerHTML = '<span class="enrichment-spinner"></span> ' + pct + '% (' + data.completed + '/' + data.total + ')';
setTimeout(pollSingleEnrichment, 1500);
} else if (data.pending_count > 0) {
window.location.href = '{{ url_for("admin.admin_social_audit_enrichment_review") }}';
} else if (data.api_synced_count > 0) {
btn.textContent = 'Zsynchronizowano! Odświeżam...';
setTimeout(function() { window.location.reload(); }, 1000);
} else {
btn.disabled = false;
btn.textContent = 'Brak nowych danych';
setTimeout(function() { btn.textContent = 'Uruchom audyt'; }, 5000);
}
});
}
{% endblock %}