feat(audit): Enhance GBP and Social Media dashboard displays
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
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
GBP: Add recent reviews widget (5 reviews with ratings, sentiment, owner responses), photo status indicators (logo/cover), business attributes section (payment, parking, accessibility tags). Social Media: Show all check_status types (404, blocked, redirect), add profile source indicator (website scrape/search/manual), add followers history trend visualization with CSS bars. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ef39ebf8a3
commit
c11e69eb97
@ -256,6 +256,7 @@ def social_audit_dashboard(slug):
|
||||
'content_types': profile.content_types,
|
||||
'profile_completeness_score': profile.profile_completeness_score,
|
||||
'followers_history': profile.followers_history,
|
||||
'source': profile.source,
|
||||
}
|
||||
|
||||
# Calculate score (platforms with profiles / total platforms)
|
||||
|
||||
@ -1183,50 +1183,44 @@
|
||||
</div>
|
||||
|
||||
{% if recent_reviews and recent_reviews|length > 0 %}
|
||||
<!-- Recent Reviews Section -->
|
||||
<div class="fields-section">
|
||||
<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="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
|
||||
</svg>
|
||||
Ostatnie opinie ({{ recent_reviews|length }})
|
||||
</h2>
|
||||
<div style="display: flex; flex-direction: column; gap: var(--spacing-sm);">
|
||||
{% for review in recent_reviews %}
|
||||
<div class="field-card {{ 'complete' if review.sentiment == 'positive' else ('partial' if review.sentiment == 'neutral' else 'missing') }}">
|
||||
<div class="field-header">
|
||||
<span class="field-name">
|
||||
{% for i in range(review.rating) %}
|
||||
<span style="color: #f59e0b;">★</span>
|
||||
<!-- Recent Reviews -->
|
||||
<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="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/>
|
||||
</svg>
|
||||
Ostatnie recenzje
|
||||
</h2>
|
||||
|
||||
<div style="background: var(--surface); border-radius: var(--radius-lg); box-shadow: var(--shadow); overflow: hidden; margin-bottom: var(--spacing-xl);">
|
||||
{% for review in recent_reviews %}
|
||||
<div style="padding: var(--spacing-md); border-bottom: 1px solid var(--border); {% if loop.last %}border-bottom: none;{% endif %}">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: var(--spacing-xs);">
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-sm);">
|
||||
<strong style="font-size: var(--font-size-sm);">{{ review.author_name or 'Anonim' }}</strong>
|
||||
<div style="display: flex; gap: 2px;">
|
||||
{% for i in range(5) %}
|
||||
<span style="color: {{ '#f59e0b' if i < review.rating else '#d1d5db' }}; font-size: 14px;">★</span>
|
||||
{% endfor %}
|
||||
{% for i in range(5 - review.rating) %}
|
||||
<span style="color: #d1d5db;">★</span>
|
||||
{% endfor %}
|
||||
</span>
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-xs);">
|
||||
{% if review.sentiment %}
|
||||
<span class="field-status-badge {{ 'complete' if review.sentiment == 'positive' else ('partial' if review.sentiment == 'neutral' else 'missing') }}">
|
||||
{{ 'Pozytywna' if review.sentiment == 'positive' else ('Neutralna' if review.sentiment == 'neutral' else 'Negatywna') }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if review.has_owner_response %}
|
||||
<span style="display: inline-flex; align-items: center; gap: 2px; font-size: 11px; padding: 2px 8px; background: #dbeafe; color: #1e40af; border-radius: var(--radius-sm);">
|
||||
<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="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"/></svg>
|
||||
Odpowiedz
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if review.sentiment %}
|
||||
<span style="font-size: var(--font-size-xs); padding: 1px 6px; border-radius: var(--radius-sm); background: {{ '#dcfce7' if review.sentiment == 'positive' else ('#fef3c7' if review.sentiment == 'neutral' else '#fee2e2') }}; color: {{ '#10b981' if review.sentiment == 'positive' else ('#f59e0b' if review.sentiment == 'neutral' else '#ef4444') }};">{{ review.sentiment }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; font-size: var(--font-size-xs); color: var(--text-tertiary); margin-bottom: var(--spacing-xs);">
|
||||
<span>{{ review.author_name or 'Anonim' }}</span>
|
||||
<span>{{ review.publish_time.strftime('%d.%m.%Y') if review.publish_time else '' }}</span>
|
||||
</div>
|
||||
{% if review.text %}
|
||||
<div class="field-value">{{ review.text[:200] }}{% if review.text|length > 200 %}...{% endif %}</div>
|
||||
{% if review.publish_time %}
|
||||
<span style="font-size: var(--font-size-xs); color: var(--text-tertiary);">{{ review.publish_time.strftime('%d.%m.%Y') }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if review.text %}
|
||||
<p style="font-size: var(--font-size-sm); color: var(--text-secondary); margin: 0 0 var(--spacing-xs) 0; line-height: 1.5;">{{ review.text[:300] }}{% if review.text|length > 300 %}...{% endif %}</p>
|
||||
{% endif %}
|
||||
{% if review.has_owner_response and review.owner_response_text %}
|
||||
<div style="margin-top: var(--spacing-xs); padding: var(--spacing-xs) var(--spacing-sm); background: var(--bg-tertiary); border-left: 3px solid var(--primary); border-radius: 0 var(--radius-sm) var(--radius-sm) 0;">
|
||||
<div style="font-size: var(--font-size-xs); font-weight: 600; color: var(--text-primary); margin-bottom: 2px;">Odpowiedz wlasciciela:</div>
|
||||
<p style="font-size: var(--font-size-xs); color: var(--text-secondary); margin: 0; line-height: 1.4;">{{ review.owner_response_text[:200] }}{% if review.owner_response_text|length > 200 %}...{% endif %}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@ -1386,6 +1380,23 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if audit.logo_present is not none or audit.cover_photo_present is not none %}
|
||||
<div style="display: flex; gap: var(--spacing-sm); margin-bottom: var(--spacing-sm); margin-top: var(--spacing-lg);">
|
||||
{% if audit.logo_present is not none %}
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-xs); padding: var(--spacing-xs) var(--spacing-sm); border-radius: var(--radius); background: {{ '#dcfce7' if audit.logo_present else '#fee2e2' }};">
|
||||
<span style="color: {{ '#10b981' if audit.logo_present else '#ef4444' }};">{{ '✓' if audit.logo_present else '✗' }}</span>
|
||||
<span style="font-size: var(--font-size-sm);">Logo</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if audit.cover_photo_present is not none %}
|
||||
<div style="display: flex; align-items: center; gap: var(--spacing-xs); padding: var(--spacing-xs) var(--spacing-sm); border-radius: var(--radius); background: {{ '#dcfce7' if audit.cover_photo_present else '#fee2e2' }};">
|
||||
<span style="color: {{ '#10b981' if audit.cover_photo_present else '#ef4444' }};">{{ '✓' if audit.cover_photo_present else '✗' }}</span>
|
||||
<span style="font-size: var(--font-size-sm);">Zdjecie w tle</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if audit.photo_categories %}
|
||||
<h3 style="font-size: var(--font-size-base); font-weight: 600; margin-top: var(--spacing-lg); margin-bottom: var(--spacing-sm);">Kategorie zdjec</h3>
|
||||
<div style="display: flex; flex-wrap: wrap; gap: var(--spacing-sm);">
|
||||
@ -1396,6 +1407,23 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if audit.attributes %}
|
||||
<!-- Business Attributes -->
|
||||
<h3 style="font-size: var(--font-size-base); font-weight: 600; margin-top: var(--spacing-lg); margin-bottom: var(--spacing-sm); display: flex; align-items: center; gap: var(--spacing-xs);">
|
||||
<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="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"/>
|
||||
</svg>
|
||||
Atrybuty Google Business
|
||||
</h3>
|
||||
<div style="display: flex; flex-wrap: wrap; gap: var(--spacing-xs);">
|
||||
{% for key, value in audit.attributes.items() %}
|
||||
<span style="padding: 4px 10px; background: {{ '#dcfce7' if value else '#f3f4f6' }}; color: {{ '#10b981' if value else '#6b7280' }}; border-radius: var(--radius-sm); font-size: var(--font-size-xs);">
|
||||
{{ key|replace('_', ' ')|title }}{% if value is string %}: {{ value }}{% elif not value %} ✗{% endif %}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@ -761,27 +761,41 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<span class="platform-name">{{ platform_names.get(platform, platform|title) }}</span>
|
||||
{% if profile and profile.check_status == 'needs_verification' %}
|
||||
{% 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 and profile.last_post_date and (now - profile.last_post_date).days < 90 %}
|
||||
{% elif profile %}
|
||||
<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 %}
|
||||
<span class="platform-status" style="background: var(--bg-secondary); color: var(--text-secondary);">
|
||||
<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>
|
||||
Znaleziony
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="platform-status inactive">
|
||||
<svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@ -809,6 +823,14 @@
|
||||
<strong>Adres profilu zawiera numeryczne ID</strong> zamiast nazwy firmy. Zalecamy ustawienie niestandardowej nazwy uzytkownika (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);">Zrodlo:</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 == 'brave_search' %}Wyszukiwarka{% elif profile.source == 'manual' %}Recznie{% 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">
|
||||
@ -826,6 +848,19 @@
|
||||
{{ '{:,}'.format(profile.followers_count).replace(',', ' ') }} obserwujacych
|
||||
</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">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user