nordabiz/templates/social_audit.html
Maciej Pienczyn ce9513b4bb
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
fix: Remove Brave Search from social media audit — too many false positives
Brave Search matched unrelated companies by name token (e.g. VINDOR matched
vindorclothing, vindormusic, beautybyneyador). Social media profiles are now
sourced only from website scraping and manual admin entry.

- Disabled BraveSearcher initialization and call in audit_company()
- Removed Brave Search step from audit progress animation
- Updated missing profile message with explanation and link to profile editor
- Added migration 071 to clean up existing brave_search entries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 18:20:52 +01:00

1714 lines
77 KiB
HTML

{% extends "base.html" %}
{% block title %}Audyt Social Media - {{ company.name }} - Norda Biznes Partner{% endblock %}
{% block extra_css %}
<style>
.audit-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: var(--spacing-xl);
flex-wrap: wrap;
gap: var(--spacing-md);
}
.audit-header-info h1 {
font-size: var(--font-size-2xl);
color: var(--text-primary);
margin-bottom: var(--spacing-xs);
}
.audit-header-info p {
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.data-source-info {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
margin-top: var(--spacing-sm);
padding: var(--spacing-xs) var(--spacing-sm);
background: rgba(168, 85, 247, 0.1);
border-radius: var(--radius);
font-size: var(--font-size-sm);
color: #a855f7;
}
.header-actions {
display: flex;
gap: var(--spacing-sm);
align-items: center;
}
/* Score Section */
.score-section {
display: grid;
grid-template-columns: auto 1fr;
gap: var(--spacing-xl);
margin-bottom: var(--spacing-xl);
background: var(--surface);
padding: var(--spacing-xl);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
}
@media (max-width: 768px) {
.score-section {
grid-template-columns: 1fr;
text-align: center;
}
}
.score-circle {
width: 180px;
height: 180px;
border-radius: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
background: conic-gradient(
var(--score-color, var(--secondary)) calc(var(--score-percent, 0) * 3.6deg),
#e2e8f0 0deg
);
margin: 0 auto;
}
.score-circle::before {
content: '';
position: absolute;
width: 150px;
height: 150px;
border-radius: 50%;
background: var(--surface);
}
.score-value {
position: relative;
z-index: 1;
font-size: 3rem;
font-weight: 700;
line-height: 1;
}
/* Unified 5-level color scale: 0-29 red, 30-49 orange, 50-69 amber, 70-89 lime, 90-100 green */
.score-value.score-excellent { color: #10b981; }
.score-value.score-good { color: #84cc16; }
.score-value.score-average { color: #f59e0b; }
.score-value.score-needs-work { color: #f97316; }
.score-value.score-poor { color: #ef4444; }
.score-label {
position: relative;
z-index: 1;
font-size: var(--font-size-sm);
color: var(--text-secondary);
margin-top: var(--spacing-xs);
}
.score-details {
display: flex;
flex-direction: column;
justify-content: center;
}
.score-category {
font-size: var(--font-size-lg);
font-weight: 600;
margin-bottom: var(--spacing-sm);
}
/* Unified 5-level color scale */
.score-category.excellent { color: #10b981; }
.score-category.good { color: #84cc16; }
.score-category.average { color: #f59e0b; }
.score-category.needs-work { color: #f97316; }
.score-category.poor { color: #ef4444; }
.score-description {
color: var(--text-secondary);
font-size: var(--font-size-sm);
line-height: 1.6;
margin-bottom: var(--spacing-md);
}
.platforms-summary {
display: flex;
gap: var(--spacing-lg);
font-size: var(--font-size-sm);
color: var(--text-secondary);
flex-wrap: wrap;
}
.platforms-summary-item {
display: flex;
align-items: center;
gap: var(--spacing-xs);
}
.platforms-summary-item svg {
width: 16px;
height: 16px;
}
.platforms-summary-item.found { color: var(--success); }
.platforms-summary-item.missing { color: var(--text-secondary); }
/* Section Title */
.section-title {
display: flex;
align-items: center;
gap: var(--spacing-sm);
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-lg);
}
.section-title svg {
width: 24px;
height: 24px;
color: #a855f7;
}
/* Platforms Grid */
.platforms-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: var(--spacing-md);
margin-bottom: var(--spacing-xl);
}
.platform-card {
background: var(--surface);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
box-shadow: var(--shadow);
border-left: 4px solid;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.platform-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.platform-card.found {
border-left-color: var(--success);
}
.platform-card.missing {
border-left-color: var(--text-tertiary);
opacity: 0.7;
}
.platform-header {
display: flex;
align-items: center;
gap: var(--spacing-sm);
margin-bottom: var(--spacing-md);
}
.platform-icon {
width: 40px;
height: 40px;
border-radius: var(--radius);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
}
.platform-icon.facebook { background: #1877f2; color: white; }
.platform-icon.instagram { background: linear-gradient(45deg, #f09433, #e6683c, #dc2743, #cc2366, #bc1888); color: white; }
.platform-icon.linkedin { background: #0a66c2; color: white; }
.platform-icon.youtube { background: #ff0000; color: white; }
.platform-icon.twitter { background: #000000; color: white; }
.platform-icon.tiktok { background: #000000; color: white; }
.platform-name {
font-weight: 600;
font-size: var(--font-size-base);
color: var(--text-primary);
}
.platform-status {
margin-left: auto;
display: flex;
align-items: center;
gap: var(--spacing-xs);
font-size: var(--font-size-sm);
padding: 2px 8px;
border-radius: var(--radius);
}
.platform-status.active {
background: rgba(34, 197, 94, 0.1);
color: var(--success);
}
.platform-status.inactive {
background: rgba(107, 114, 128, 0.1);
color: var(--text-tertiary);
}
.platform-status.needs-verification {
background: rgba(245, 158, 11, 0.1);
color: #b45309;
}
.platform-details {
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
.platform-url {
display: flex;
align-items: center;
gap: var(--spacing-xs);
margin-bottom: var(--spacing-xs);
word-break: break-all;
}
.platform-url a {
color: var(--primary);
text-decoration: none;
}
.platform-url a:hover {
text-decoration: underline;
}
.platform-meta {
display: flex;
gap: var(--spacing-md);
margin-top: var(--spacing-sm);
flex-wrap: wrap;
}
.platform-meta-item {
display: flex;
align-items: center;
gap: 4px;
font-size: var(--font-size-xs);
color: var(--text-tertiary);
}
.platform-meta-item svg {
width: 14px;
height: 14px;
}
.platform-missing-text {
color: var(--text-tertiary);
font-style: italic;
}
/* No data state */
.no-data {
text-align: center;
padding: var(--spacing-2xl);
background: var(--surface);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
}
.no-data svg {
width: 64px;
height: 64px;
color: var(--text-tertiary);
margin-bottom: var(--spacing-md);
}
.no-data h3 {
color: var(--text-primary);
margin-bottom: var(--spacing-sm);
}
.no-data p {
color: var(--text-secondary);
margin-bottom: var(--spacing-lg);
}
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius);
font-weight: 500;
font-size: var(--font-size-sm);
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid transparent;
text-decoration: none;
}
.btn svg {
width: 16px;
height: 16px;
}
.btn-primary {
background: #a855f7;
color: white;
border-color: #a855f7;
}
.btn-primary:hover {
background: #9333ea;
border-color: #9333ea;
}
.btn-outline {
background: transparent;
color: var(--text-secondary);
border-color: var(--border);
}
.btn-outline:hover {
background: var(--background);
color: var(--text-primary);
}
.btn-sm {
padding: var(--spacing-xs) var(--spacing-sm);
font-size: var(--font-size-xs);
}
/* Recommendations */
.recommendations {
background: var(--surface);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
box-shadow: var(--shadow);
}
.recommendation-item {
display: flex;
gap: var(--spacing-sm);
padding: var(--spacing-sm) 0;
border-bottom: 1px solid var(--border);
}
.recommendation-item:last-child {
border-bottom: none;
}
.recommendation-icon {
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
font-size: 12px;
}
.recommendation-icon.priority-high {
background: rgba(239, 68, 68, 0.1);
color: var(--error);
}
.recommendation-icon.priority-medium {
background: rgba(245, 158, 11, 0.1);
color: var(--warning);
}
.recommendation-icon.priority-low {
background: rgba(34, 197, 94, 0.1);
color: var(--success);
}
.recommendation-text {
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
.recommendation-text strong {
color: var(--text-primary);
}
/* Loading Overlay */
.loading-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.95);
z-index: 9999;
align-items: center;
justify-content: center;
flex-direction: column;
gap: var(--spacing-lg);
}
.loading-overlay.active {
display: flex;
}
.loading-content {
background: var(--surface);
padding: var(--spacing-xl);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
max-width: 450px;
width: 90%;
text-align: center;
}
.loading-header {
margin-bottom: var(--spacing-lg);
}
.loading-header h3 {
font-size: var(--font-size-lg);
color: var(--text-primary);
margin-bottom: var(--spacing-xs);
}
.loading-header p {
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.loading-steps {
text-align: left;
margin-bottom: var(--spacing-lg);
}
.loading-step {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm) 0;
border-bottom: 1px solid var(--border-light, #f1f5f9);
}
.loading-step:last-child {
border-bottom: none;
}
.step-icon {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.step-icon.pending { color: var(--text-tertiary); }
.step-icon.in_progress { color: #a855f7; }
.step-icon.complete { color: var(--success); }
.step-icon.error { color: var(--error); }
.step-icon.missing { color: var(--text-tertiary); opacity: 0.6; }
.step-icon.skipped { color: var(--text-tertiary); opacity: 0.5; }
.step-spinner {
width: 18px;
height: 18px;
border: 2px solid var(--border);
border-top-color: #a855f7;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.step-text {
flex: 1;
font-size: var(--font-size-sm);
}
.step-text.pending { color: var(--text-tertiary); }
.step-text.in_progress { color: var(--text-primary); font-weight: 500; }
.step-text.complete { color: var(--text-secondary); }
.step-text.error { color: var(--error); }
.step-text.missing { color: var(--text-tertiary); font-style: italic; }
.step-text.skipped { color: var(--text-tertiary); opacity: 0.6; }
/* Source tag styling */
.source-tag {
display: inline-block;
font-size: 9px;
font-weight: 600;
padding: 1px 5px;
border-radius: 3px;
margin-left: 4px;
text-transform: uppercase;
vertical-align: middle;
}
.source-tag.website { background: #6366f1; color: white; }
.source-tag.google { background: #4285f4; color: white; }
.source-tag.facebook { background: #1877f2; color: white; }
.source-tag.instagram { background: #e4405f; color: white; }
.source-tag.linkedin { background: #0a66c2; color: white; }
.source-tag.youtube { background: #ff0000; color: white; }
.source-tag.twitter { background: #000000; color: white; }
.source-tag.tiktok { background: #000000; color: white; }
/* Modal */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
.modal-overlay.active {
opacity: 1;
visibility: visible;
}
.modal-content {
background: var(--surface);
padding: var(--spacing-xl);
border-radius: var(--radius-lg);
max-width: 400px;
width: 90%;
text-align: center;
}
.modal-icon {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto var(--spacing-md);
}
.modal-icon.success {
background: rgba(34, 197, 94, 0.1);
color: var(--success);
}
.modal-icon.error {
background: rgba(239, 68, 68, 0.1);
color: var(--error);
}
.modal-title {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-sm);
}
.modal-description {
color: var(--text-secondary);
font-size: var(--font-size-sm);
margin-bottom: var(--spacing-lg);
}
.modal-btn {
background: #a855f7;
color: white;
border: none;
padding: var(--spacing-sm) var(--spacing-lg);
border-radius: var(--radius);
font-weight: 500;
cursor: pointer;
transition: background 0.2s ease;
}
.modal-btn:hover {
background: #9333ea;
}
</style>
{% endblock %}
{% block content %}
<!-- Breadcrumb -->
<div class="breadcrumb">
<a href="{{ url_for('index') }}">Firmy</a>
<span class="breadcrumb-separator">/</span>
<a href="{{ url_for('company_detail', company_id=company.id) }}">{{ company.name }}</a>
<span class="breadcrumb-separator">/</span>
<span>Audyt Social Media</span>
</div>
<div class="audit-header">
<div class="audit-header-info">
<h1>Audyt Social Media</h1>
<p>{{ company.name }}</p>
<div class="data-source-info">
<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="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z"/>
</svg>
<span>Analiza obecności w mediach społecznościowych</span>
</div>
</div>
<div class="header-actions">
<a href="{{ url_for('company_detail', company_id=company.id) }}" class="btn btn-outline btn-sm">
<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="M10 19l-7-7m0 0l7-7m-7 7h18"/>
</svg>
Profil firmy
</a>
{% if can_audit %}
<button class="btn btn-primary btn-sm" onclick="runAudit()" id="runAuditBtn">
<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="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>
{% endif %}
</div>
</div>
<!-- Score Section -->
{# 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 %}
<div class="score-section">
<div class="score-circle" style="--score-percent: {{ score }}; --score-color: {% if score >= 90 %}#10b981{% elif score >= 70 %}#84cc16{% elif score >= 50 %}#f59e0b{% elif score >= 30 %}#f97316{% else %}#ef4444{% endif %};">
<span class="score-value" style="color: {% if score >= 90 %}#10b981{% elif score >= 70 %}#84cc16{% elif score >= 50 %}#f59e0b{% elif score >= 30 %}#f97316{% else %}#ef4444{% endif %};">{{ score }}</span>
<span class="score-label">/ 100</span>
</div>
<div class="score-details">
<div class="score-category" style="color: {% if score >= 90 %}#10b981{% elif score >= 70 %}#84cc16{% elif score >= 50 %}#f59e0b{% elif score >= 30 %}#f97316{% else %}#ef4444{% endif %};">
{% 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 %}
</div>
<p class="score-description">
{% 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 %}
</p>
<div class="platforms-summary">
<div class="platforms-summary-item found">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>
<span>{{ social_data.platforms_count }} {{ 'platforma' if social_data.platforms_count == 1 else ('platformy' if social_data.platforms_count in [2,3,4] else 'platform') }}</span>
</div>
<div class="platforms-summary-item missing">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
<span>{{ social_data.total_platforms - social_data.platforms_count }} brakujących</span>
</div>
</div>
</div>
</div>
<!-- Platforms Grid -->
<h2 class="section-title">
<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="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z"/>
</svg>
Platformy Social Media
</h2>
<div class="platforms-grid">
{% 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) %}
<div class="platform-card {{ 'found' if profile else 'missing' }}">
<div class="platform-header">
<div class="platform-icon {{ platform }}">
{% if platform == '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>
{% elif platform == '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-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.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.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/></svg>
{% elif platform == '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.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 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>
{% elif platform == 'youtube' %}
<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.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>
{% elif platform == '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>
{% elif platform == '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>
{% endif %}
</div>
<span class="platform-name">{{ platform_names.get(platform, platform|title) }}</span>
{% if profile and profile.check_status == '404' %}
<span class="platform-status" style="background: rgba(239, 68, 68, 0.1); color: #dc2626;">
<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="M6 18L18 6M6 6l12 12"/>
</svg>
Profil nie istnieje (404)
</span>
{% elif profile and profile.check_status == 'blocked' %}
<span class="platform-status" style="background: rgba(239, 68, 68, 0.1); color: #dc2626;">
<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="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>
Zablokowany
</span>
{% elif profile and profile.check_status == 'redirect' %}
<span class="platform-status needs-verification">
<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="M13 7l5 5m0 0l-5 5m5-5H6"/>
</svg>
Przekierowanie
</span>
{% elif profile and profile.check_status == 'needs_verification' %}
<span class="platform-status needs-verification">
<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="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>
Do weryfikacji
</span>
{% elif profile %}
{% if profile.posting_frequency_score is not none and profile.posting_frequency_score >= 7 %}
<span class="platform-status active">
<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="M5 13l4 4L19 7"/>
</svg>
Aktywny
</span>
{% elif profile.posting_frequency_score is not none and profile.posting_frequency_score >= 4 %}
<span class="platform-status" style="background: rgba(245, 158, 11, 0.1); color: #b45309;">
<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="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
Umiarkowanie aktywny
</span>
{% elif profile.posting_frequency_score is not none and profile.posting_frequency_score >= 1 %}
<span class="platform-status" style="background: rgba(249, 115, 22, 0.1); color: #c2410c;">
<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="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
Rzadko aktywny
</span>
{% elif profile.posting_frequency_score is not none and profile.posting_frequency_score == 0 %}
<span class="platform-status" style="background: rgba(239, 68, 68, 0.1); color: #dc2626;">
<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="M6 18L18 6M6 6l12 12"/>
</svg>
Nieaktywny
</span>
{% else %}
<span class="platform-status" style="background: rgba(107, 114, 128, 0.1); color: #6b7280;">
<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>
Brak danych
</span>
{% endif %}
{% else %}
<span class="platform-status inactive">
<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="M6 18L18 6M6 6l12 12"/>
</svg>
Brak profilu
</span>
{% endif %}
</div>
<div class="platform-details">
{% if profile %}
<div class="platform-url">
<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="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>
<a href="{{ profile.url }}" target="_blank" rel="noopener">{{ profile.url|truncate(40) }}</a>
</div>
{% if profile.check_status == 'needs_verification' %}
<div style="margin: 6px 0; padding: 8px 12px; background: #fef3c7; border-left: 3px solid #f59e0b; border-radius: 4px; font-size: 12px; color: #92400e;">
<strong>Do weryfikacji</strong> — link do profilu wymaga ręcznego sprawdzenia. Profil może być nieaktywny lub wskazywać na niewłaściwe konto.
</div>
{% endif %}
{% if platform == 'facebook' and 'profile.php?id=' in (profile.url or '') %}
<div style="margin: 6px 0; padding: 8px 12px; background: #fef3c7; border-left: 3px solid #f59e0b; border-radius: 4px; font-size: 12px; color: #92400e;">
<strong>Adres profilu zawiera numeryczne ID</strong> zamiast nazwy firmy. Zalecamy ustawienie niestandardowej nazwy użytkownika (np. facebook.com/NazwaFirmy) w ustawieniach strony na Facebooku.
</div>
{% endif %}
{% if profile.source %}
<div style="display: flex; align-items: center; gap: var(--spacing-xs); margin-bottom: var(--spacing-xs);">
<span style="font-size: var(--font-size-xs); color: var(--text-tertiary);">Źródło:</span>
<span style="font-size: var(--font-size-xs); padding: 1px 6px; border-radius: var(--radius-sm); background: #f3f4f6; color: #6b7280;">
{% 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 %}
</span>
</div>
{% endif %}
<div class="platform-meta">
{% if profile.page_name %}
<div class="platform-meta-item">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
</svg>
{{ profile.page_name }}
</div>
{% endif %}
{% if profile.followers_count %}
<div class="platform-meta-item">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
</svg>
{{ '{:,}'.format(profile.followers_count).replace(',', ' ') }} obserwujących
</div>
{% endif %}
{% if profile.followers_history and profile.followers_history|length > 1 %}
<div class="platform-meta-item" style="flex-basis: 100%;">
<div style="width: 100%;">
<span style="font-size: var(--font-size-xs); color: var(--text-tertiary);">Trend:</span>
<div style="display: flex; align-items: flex-end; gap: 2px; height: 24px; margin-top: 2px;">
{% set max_val = profile.followers_history|map(attribute='count')|select('number')|max|default(1) %}
{% for point in profile.followers_history[-12:] %}
<div style="width: 6px; background: var(--primary); border-radius: 1px; opacity: 0.7; height: {{ ((point.count|default(0) / max_val) * 100)|int if max_val else 0 }}%;" title="{{ point.date|default('?') }}: {{ point.count|default(0) }}"></div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
{% if profile.verified_at %}
<div class="platform-meta-item">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
{{ profile.verified_at.strftime('%d.%m.%Y') }}
</div>
{% endif %}
{% if profile.last_checked_at %}
<div class="platform-meta-item">
<svg 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>
<span style="font-size: var(--font-size-xs); color: var(--text-tertiary);">Sprawdzono: {{ profile.last_checked_at.strftime('%d.%m.%Y') }}</span>
</div>
{% endif %}
{% if profile.has_bio %}
<div class="platform-meta-item" title="Ma opis profilu">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7"/>
</svg>
Opis
</div>
{% endif %}
{% if profile.has_profile_photo %}
<div class="platform-meta-item" title="Ma zdjęcie profilowe">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
Avatar
</div>
{% endif %}
{% if profile.has_cover_photo %}
<div class="platform-meta-item" title="Ma zdjęcie w tle">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
Cover
</div>
{% endif %}
{% if profile.posts_count_30d is not none and profile.posts_count_30d > 0 %}
<div class="platform-meta-item" title="Posty w ostatnich 30 dniach">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9.5a2 2 0 00-2-2h-2"/>
</svg>
{{ profile.posts_count_30d }} postów/30d
</div>
{% endif %}
{% if profile.engagement_rate is not none %}
<div class="platform-meta-item" title="Wskaźnik zaangażowania">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"/>
</svg>
{{ '%.1f'|format(profile.engagement_rate) }}% engage
</div>
{% endif %}
{% if profile.posts_count_365d is not none and profile.posts_count_365d > 0 %}
<div class="platform-meta-item" title="Posty w ostatnim roku">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
{{ profile.posts_count_365d }} postów/rok
</div>
{% endif %}
{% if profile.last_post_date %}
<div class="platform-meta-item" title="Ostatni post">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
Ostatni: {{ profile.last_post_date.strftime('%d.%m.%Y') }}
</div>
{% endif %}
{% if profile.posting_frequency_score is not none %}
<div class="platform-meta-item" title="Częstość postowania: {{ profile.posting_frequency_score }}/10">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
</svg>
<span style="padding: 1px 6px; border-radius: 3px; font-size: 10px; font-weight: 600; background: {% if profile.posting_frequency_score >= 7 %}#dcfce7; color: #166534{% elif profile.posting_frequency_score >= 4 %}#fef3c7; color: #92400e{% else %}#fee2e2; color: #991b1b{% endif %};">{{ profile.posting_frequency_score }}/10</span>
</div>
{% endif %}
{% if profile.content_types and profile.content_types is mapping %}
{% for ctype, ccount in profile.content_types.items() %}
<div class="platform-meta-item" title="{{ ctype|capitalize }}: {{ ccount }}">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
{% if ctype == 'videos' or ctype == 'video' %}
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
{% else %}
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/>
{% endif %}
</svg>
{{ ctype|capitalize }}: {{ ccount }}
</div>
{% endfor %}
{% endif %}
{% if profile.profile_description %}
<div class="platform-meta-item" title="{{ profile.profile_description }}" style="max-width: 200px;">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ profile.profile_description[:80] }}{% if profile.profile_description|length > 80 %}...{% endif %}</span>
</div>
{% endif %}
</div>
{% if profile and profile.profile_completeness_score is not none %}
<div style="margin-top: var(--spacing-sm);">
<div style="display: flex; justify-content: space-between; font-size: var(--font-size-xs); color: var(--text-tertiary); margin-bottom: 4px;">
<span>Kompletność profilu</span>
<span>{{ profile.profile_completeness_score }}%</span>
</div>
<div style="height: 6px; background: var(--border); border-radius: 3px; overflow: hidden;">
<div style="height: 100%; width: {{ profile.profile_completeness_score }}%; border-radius: 3px; background: {% if profile.profile_completeness_score >= 80 %}#10b981{% elif profile.profile_completeness_score >= 50 %}#f59e0b{% else %}#ef4444{% endif %};"></div>
</div>
</div>
{% endif %}
{% else %}
<p class="platform-missing-text">Profil nie został wykryty automatycznie na stronie internetowej firmy.</p>
<p style="font-size: var(--font-size-xs); color: var(--text-tertiary); margin-top: var(--spacing-xs);">Jeśli firma posiada profil na tej platformie, można go dodać ręcznie w <a href="{{ url_for('public.company_edit', company_id=company.id) }}" style="color: var(--primary); text-decoration: underline;">edycji profilu firmy</a>.</p>
{% endif %}
</div>
</div>
{% endfor %}
</div>
<!-- Platform Comparison -->
{% 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 %}
<h2 class="section-title">
<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="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
</svg>
Porównanie platform
</h2>
<div style="background: var(--surface); border-radius: var(--radius-lg); box-shadow: var(--shadow); overflow: hidden; margin-bottom: var(--spacing-xl);">
<div style="overflow-x: auto;">
<table style="width: 100%; border-collapse: collapse; font-size: var(--font-size-sm);">
<thead>
<tr style="background: var(--bg-tertiary);">
<th style="padding: var(--spacing-sm) var(--spacing-md); text-align: left; font-weight: 600; color: var(--text-primary);">Platforma</th>
<th style="padding: var(--spacing-sm) var(--spacing-md); text-align: right; font-weight: 600; color: var(--text-primary);">Obserwujący</th>
<th style="padding: var(--spacing-sm) var(--spacing-md); text-align: right; font-weight: 600; color: var(--text-primary);">Engagement</th>
<th style="padding: var(--spacing-sm) var(--spacing-md); text-align: right; font-weight: 600; color: var(--text-primary);">Posty (30d)</th>
<th style="padding: var(--spacing-sm) var(--spacing-md); text-align: right; font-weight: 600; color: var(--text-primary);">Kompletność</th>
</tr>
</thead>
<tbody>
{% for platform in social_data.all_platforms %}
{% set cmp_profile = social_data.profiles.get(platform) %}
{% if cmp_profile and cmp_profile.is_valid %}
<tr style="border-top: 1px solid var(--border);">
<td style="padding: var(--spacing-sm) var(--spacing-md); font-weight: 500;">{{ platform_names.get(platform, platform|title) }}</td>
<td style="padding: var(--spacing-sm) var(--spacing-md); text-align: right;">{{ '{:,}'.format(cmp_profile.followers_count or 0).replace(',', ' ') }}</td>
<td style="padding: var(--spacing-sm) var(--spacing-md); text-align: right;">
{% if cmp_profile.engagement_rate is not none %}
<span style="color: {{ '#10b981' if cmp_profile.engagement_rate >= 3 else ('#f59e0b' if cmp_profile.engagement_rate >= 1 else '#ef4444') }};">{{ '%.1f'|format(cmp_profile.engagement_rate) }}%</span>
{% else %}&mdash;{% endif %}
</td>
<td style="padding: var(--spacing-sm) var(--spacing-md); text-align: right;">{{ cmp_profile.posts_count_30d or 0 }}</td>
<td style="padding: var(--spacing-sm) var(--spacing-md); text-align: right;">
{% if cmp_profile.profile_completeness_score is not none %}
<span style="color: {{ '#10b981' if cmp_profile.profile_completeness_score >= 80 else ('#f59e0b' if cmp_profile.profile_completeness_score >= 50 else '#ef4444') }};">{{ cmp_profile.profile_completeness_score }}%</span>
{% else %}&mdash;{% endif %}
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
{% if social_data.total_platforms - social_data.platforms_count > 0 %}
<!-- Recommendations -->
<h2 class="section-title">
<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="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
</svg>
Rekomendacje
</h2>
<div class="recommendations">
{% 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 %}
<div class="recommendation-item">
<div class="recommendation-icon priority-high">!</div>
<div class="recommendation-text">
<strong>Facebook</strong> - Najpopularniejsza platforma w Polsce. Założenie strony firmowej pozwoli dotrzeć do szerokiego grona klientów.
</div>
</div>
{% endif %}
{% if 'linkedin' in missing_platforms %}
<div class="recommendation-item">
<div class="recommendation-icon priority-high">!</div>
<div class="recommendation-text">
<strong>LinkedIn</strong> - Kluczowa platforma dla firm B2B. Idealna do budowania relacji biznesowych i employer brandingu.
</div>
</div>
{% endif %}
{% if 'instagram' in missing_platforms %}
<div class="recommendation-item">
<div class="recommendation-icon priority-medium">!</div>
<div class="recommendation-text">
<strong>Instagram</strong> - Świetna platforma do prezentacji wizualnej firmy. Szczególnie ważna dla firm z produktami/usługami wizualnymi.
</div>
</div>
{% endif %}
{% if 'youtube' in missing_platforms %}
<div class="recommendation-item">
<div class="recommendation-icon priority-medium">!</div>
<div class="recommendation-text">
<strong>YouTube</strong> - Druga największa wyszukiwarka na świecie. Video content buduje zaufanie i pokazuje ekspertyzę.
</div>
</div>
{% endif %}
{% if 'tiktok' in missing_platforms %}
<div class="recommendation-item">
<div class="recommendation-icon priority-low">!</div>
<div class="recommendation-text">
<strong>TikTok</strong> - Najszybciej rosnąca platforma. Warto rozważyć jeśli celujecie w młodszych odbiorców.
</div>
</div>
{% endif %}
{% if 'twitter' in missing_platforms %}
<div class="recommendation-item">
<div class="recommendation-icon priority-low">!</div>
<div class="recommendation-text">
<strong>X (Twitter)</strong> - Platforma do szybkiej komunikacji i budowania wizerunku eksperta. Przydatna w branży tech/media.
</div>
</div>
{% endif %}
</div>
{% 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 %}
<h2 class="section-title">
<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="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
</svg>
Rekomendacje
</h2>
{% endif %}
<div class="recommendations">
<div class="recommendation-item">
<div class="recommendation-icon priority-high">!</div>
<div class="recommendation-text">
<strong>Facebook - zmień adres profilu</strong> - Twój profil na Facebooku używa numerycznego ID zamiast nazwy firmy. Klienci łatwiej zapamiętają adres typu <em>facebook.com/NazwaFirmy</em>. Aby to zmienić: Ustawienia strony na Facebooku &rarr; Ogólne &rarr; Nazwa użytkownika &rarr; wpisz nazwę firmy.
</div>
</div>
</div>
{% endif %}
{% if social_data %}
{% with audit_type='social' %}
{% include 'partials/audit_ai_actions.html' %}
{% endwith %}
{% endif %}
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay">
<div class="loading-content">
<div class="loading-header">
<h3>Audyt Social Media</h3>
<p>Szukam profili w mediach społecznościowych...</p>
</div>
<div class="loading-steps" id="loadingSteps">
<!-- Step 1: Scan Website -->
<div class="loading-step" id="step-website">
<div class="step-icon in_progress">
<div class="step-spinner"></div>
</div>
<span class="step-text in_progress">Skanuję stronę WWW <span class="source-tag website">WWW</span></span>
</div>
<!-- Step 2: Facebook -->
<div class="loading-step" id="step-facebook">
<div class="step-icon pending">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke-width="2"/>
</svg>
</div>
<span class="step-text pending">Szukam Facebook <span class="source-tag facebook">FB</span></span>
</div>
<!-- Step 3: Instagram -->
<div class="loading-step" id="step-instagram">
<div class="step-icon pending">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke-width="2"/>
</svg>
</div>
<span class="step-text pending">Szukam Instagram <span class="source-tag instagram">IG</span></span>
</div>
<!-- Step 4: LinkedIn -->
<div class="loading-step" id="step-linkedin">
<div class="step-icon pending">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke-width="2"/>
</svg>
</div>
<span class="step-text pending">Szukam LinkedIn <span class="source-tag linkedin">LI</span></span>
</div>
<!-- Step 5: YouTube -->
<div class="loading-step" id="step-youtube">
<div class="step-icon pending">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke-width="2"/>
</svg>
</div>
<span class="step-text pending">Szukam YouTube <span class="source-tag youtube">YT</span></span>
</div>
<!-- Step 6: Twitter/X -->
<div class="loading-step" id="step-twitter">
<div class="step-icon pending">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke-width="2"/>
</svg>
</div>
<span class="step-text pending">Szukam X (Twitter) <span class="source-tag twitter">X</span></span>
</div>
<!-- Step 7: TikTok -->
<div class="loading-step" id="step-tiktok">
<div class="step-icon pending">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke-width="2"/>
</svg>
</div>
<span class="step-text pending">Szukam TikTok <span class="source-tag tiktok">TK</span></span>
</div>
<!-- Step 8: Google Business -->
<div class="loading-step" id="step-google">
<div class="step-icon pending">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke-width="2"/>
</svg>
</div>
<span class="step-text pending">Pobieram dane Google <span class="source-tag google">Google</span></span>
</div>
<!-- Step 9: Save -->
<div class="loading-step" id="step-save">
<div class="step-icon pending">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke-width="2"/>
</svg>
</div>
<span class="step-text pending">Zapisuję wyniki</span>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal-overlay" id="modalOverlay">
<div class="modal-content">
<div class="modal-icon" id="modalIcon">
<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="M5 13l4 4L19 7"/>
</svg>
</div>
<div class="modal-title" id="modalTitle">Sukces</div>
<div class="modal-description" id="modalDescription">Operacja zakończona pomyślnie.</div>
<button class="modal-btn" onclick="closeModal()">OK</button>
</div>
</div>
{% 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: '<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke-width="2"/></svg>',
in_progress: '<div class="step-spinner"></div>',
complete: '<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>',
error: '<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>',
missing: '<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"/></svg>'
};
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
? '<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="M5 13l4 4L19 7"/></svg>'
: '<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="M6 18L18 6M6 6l12 12"/></svg>';
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 <span class="source-tag website">WWW</span>');
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 = `<span class="source-tag ${platform}">${platform === 'twitter' ? 'X' : platform.substring(0,2).toUpperCase()}</span>`;
updateStep(stepId, 'in_progress', `Szukam ${platformLabel}... ${sourceTag}`);
await new Promise(r => setTimeout(r, delay / 2));
if (foundPlatforms && foundPlatforms.includes(platform)) {
updateStep(stepId, 'complete', `<strong>${platformLabel}</strong>: 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... <span class="source-tag google">Google</span>');
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} <span class="source-tag google">Google</span>`);
} else {
updateStep('step-google', 'missing', 'Google: brak profilu GBP <span class="source-tag google">Google</span>');
}
}
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.+?)\*/g, '<em>$1</em>')
.replace(/^### (.+)$/gm, '<h4>$1</h4>')
.replace(/^## (.+)$/gm, '<h3>$1</h3>')
.replace(/^- (.+)$/gm, '<li>$1</li>')
.replace(/(<li>.*<\/li>)/gs, '<ul>$1</ul>')
.replace(/\n/g, '<br>');
}
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 = `
<div style="background: #fef2f2; border: 1px solid #fecaca; padding: var(--spacing-lg); border-radius: var(--radius-lg); text-align: center;">
<p style="color: #dc2626; font-weight: 600; margin-bottom: var(--spacing-sm);">Błąd analizy AI</p>
<p style="color: #991b1b; font-size: var(--font-size-sm);">${escapeHtml(data.error || 'Nieznany błąd')}</p>
<button class="btn btn-outline btn-sm" onclick="runAIAnalysis()" style="margin-top: var(--spacing-md);">Spróbuj ponownie</button>
</div>`;
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 = `
<div style="background: #fef2f2; border: 1px solid #fecaca; padding: var(--spacing-lg); border-radius: var(--radius-lg); text-align: center;">
<p style="color: #dc2626; font-weight: 600; margin-bottom: var(--spacing-sm);">Błąd połączenia</p>
<p style="color: #991b1b; font-size: var(--font-size-sm);">${escapeHtml(error.message)}</p>
<button class="btn btn-outline btn-sm" onclick="runAIAnalysis()" style="margin-top: var(--spacing-md);">Spróbuj ponownie</button>
</div>`;
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 = `
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: var(--spacing-sm); flex-wrap: wrap; gap: var(--spacing-xs);">
<div style="display: flex; align-items: center; gap: var(--spacing-sm);">
<span class="ai-priority-badge ${action.priority || 'medium'}">${priorityLabels[action.priority] || 'ŚREDNI'}</span>
<span class="ai-action-title" style="font-weight: 600; color: var(--text-primary);">${escapeHtml(action.title || '')}</span>
</div>
</div>
<p style="color: var(--text-secondary); font-size: var(--font-size-sm); margin-bottom: var(--spacing-sm);">${escapeHtml(action.description || '')}</p>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-md); margin-bottom: var(--spacing-sm);">
<div>
<div style="font-size: var(--font-size-xs); color: var(--text-tertiary); margin-bottom: 2px;">Wpływ: ${impact}/10</div>
<div class="ai-score-bar"><div class="ai-score-bar-fill impact" style="width: ${impact * 10}%;"></div></div>
</div>
<div>
<div style="font-size: var(--font-size-xs); color: var(--text-tertiary); margin-bottom: 2px;">Wysiłek: ${effort}/10</div>
<div class="ai-score-bar"><div class="ai-score-bar-fill effort" style="width: ${effort * 10}%;"></div></div>
</div>
</div>
<div class="ai-action-buttons">
<button class="btn btn-outline btn-sm" onclick="generateContent('${action.action_type}', ${idx})">
<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="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/></svg>
Wygeneruj treść
</button>
<button class="btn btn-outline btn-sm" onclick="markAction(${idx}, 'implemented')" style="color: #10b981; border-color: #10b981;">
<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="M5 13l4 4L19 7"/></svg>
Zrobione
</button>
<button class="btn btn-outline btn-sm" onclick="markAction(${idx}, 'dismissed')" style="color: var(--text-tertiary); border-color: var(--border);">Odrzuć</button>
</div>
<div id="ai-content-${idx}" style="display: none;"></div>
`;
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 = '<div style="padding: var(--spacing-md); color: var(--text-secondary); font-size: var(--font-size-sm);">Generowanie treści...</div>';
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('<script') || data.content.includes('"@type"') || data.content.trim().startsWith('{') || data.content.trim().startsWith('<'));
if (isCode) {
container.innerHTML = `<div class="ai-content-output"><button class="ai-copy-btn" onclick="copyContent(this)">Kopiuj</button><code>${escapeHtml(data.content)}</code></div>`;
} else {
container.innerHTML = `
<div style="background: var(--surface); border: 1px solid var(--border); padding: var(--spacing-md); border-radius: var(--radius); margin-top: var(--spacing-md); position: relative; line-height: 1.6; font-size: var(--font-size-sm); color: var(--text-primary);">
<button class="ai-copy-btn" style="position: absolute; top: 8px; right: 8px; background: var(--bg-tertiary); border: 1px solid var(--border); color: var(--text-secondary); padding: 4px 10px; border-radius: var(--radius-sm); font-size: var(--font-size-xs); cursor: pointer;" onclick="copyContent(this)">Kopiuj</button>
<div class="ai-markdown-content">${simpleMarkdown(data.content)}</div>
</div>`;
}
container.dataset.loaded = 'true';
container.scrollIntoView({behavior: 'smooth', block: 'nearest'});
} else {
container.innerHTML = `
<div style="padding: var(--spacing-sm); background: #fef2f2; border-radius: var(--radius-sm); margin-top: var(--spacing-sm);">
<span style="color: #dc2626;">${escapeHtml(data.error || 'Błąd generowania')}</span>
<button class="btn btn-outline btn-sm" onclick="generateContent('${actionType}', ${idx})" style="margin-left: var(--spacing-sm);">Ponów</button>
</div>`;
}
} catch (error) {
container.innerHTML = `
<div style="padding: var(--spacing-sm); background: #fef2f2; border-radius: var(--radius-sm); margin-top: var(--spacing-sm);">
<span style="color: #dc2626;">${escapeHtml(error.message)}</span>
<button class="btn btn-outline btn-sm" onclick="generateContent('${actionType}', ${idx})" style="margin-left: var(--spacing-sm);">Ponów</button>
</div>`;
}
}
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 %}