feat(audit): Complete remaining display gaps in GBP and Social Media dashboards
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: Places API data section, audit errors banner, special hours display Social: Platform comparison table, refresh timestamp, 5-level activity status Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c11e69eb97
commit
f459a6fee2
@ -339,6 +339,18 @@ def gbp_audit_dashboard(slug):
|
||||
GBPReview.company_id == company.id
|
||||
).order_by(GBPReview.publish_time.desc()).limit(5).all() if audit else []
|
||||
|
||||
# Get Places API enrichment data from CompanyWebsiteAnalysis
|
||||
places_data = {}
|
||||
analysis = db.query(CompanyWebsiteAnalysis).filter(
|
||||
CompanyWebsiteAnalysis.company_id == company.id
|
||||
).order_by(CompanyWebsiteAnalysis.analyzed_at.desc()).first()
|
||||
if analysis:
|
||||
places_data = {
|
||||
'primary_type': getattr(analysis, 'google_primary_type', None),
|
||||
'editorial_summary': getattr(analysis, 'google_editorial_summary', None),
|
||||
'price_level': getattr(analysis, 'google_price_level', None),
|
||||
}
|
||||
|
||||
# If no audit exists, we still render the page (template handles this)
|
||||
# The user can trigger an audit from the dashboard
|
||||
|
||||
@ -351,6 +363,7 @@ def gbp_audit_dashboard(slug):
|
||||
company=company,
|
||||
audit=audit,
|
||||
recent_reviews=recent_reviews,
|
||||
places_data=places_data,
|
||||
can_audit=can_audit,
|
||||
gbp_audit_available=GBP_AUDIT_AVAILABLE,
|
||||
gbp_audit_version=GBP_AUDIT_VERSION
|
||||
|
||||
@ -996,6 +996,44 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if places_data and (places_data.primary_type or places_data.editorial_summary or places_data.price_level) %}
|
||||
<!-- Google Places Enrichment Data -->
|
||||
<div style="background: var(--surface); padding: var(--spacing-lg); border-radius: var(--radius-lg); box-shadow: var(--shadow); margin-bottom: var(--spacing-xl);">
|
||||
<h3 style="font-size: var(--font-size-sm); font-weight: 600; color: var(--text-primary); margin: 0 0 var(--spacing-sm) 0; display: flex; align-items: center; gap: var(--spacing-xs);">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="#4285f4">
|
||||
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
|
||||
</svg>
|
||||
Dane z Google Places
|
||||
</h3>
|
||||
{% if places_data.primary_type %}
|
||||
<div style="margin-bottom: var(--spacing-xs); font-size: var(--font-size-sm);">
|
||||
<span style="color: var(--text-tertiary);">Typ:</span>
|
||||
<span style="color: var(--text-primary); font-weight: 500;">{{ places_data.primary_type|replace('_', ' ')|title }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if places_data.editorial_summary %}
|
||||
<div style="margin-bottom: var(--spacing-xs); font-size: var(--font-size-sm);">
|
||||
<span style="color: var(--text-tertiary);">Opis Google:</span>
|
||||
<span style="color: var(--text-secondary);">{{ places_data.editorial_summary }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if places_data.price_level is not none and places_data.price_level %}
|
||||
<div style="font-size: var(--font-size-sm);">
|
||||
<span style="color: var(--text-tertiary);">Poziom cen:</span>
|
||||
<span style="color: var(--text-primary);">
|
||||
{% if places_data.price_level == 'PRICE_LEVEL_FREE' %}Bezplatne
|
||||
{% elif places_data.price_level == 'PRICE_LEVEL_INEXPENSIVE' %}$ Niedrogi
|
||||
{% elif places_data.price_level == 'PRICE_LEVEL_MODERATE' %}$$ Umiarkowany
|
||||
{% elif places_data.price_level == 'PRICE_LEVEL_EXPENSIVE' %}$$$ Drogi
|
||||
{% elif places_data.price_level == 'PRICE_LEVEL_VERY_EXPENSIVE' %}$$$$ Bardzo drogi
|
||||
{% else %}{{ places_data.price_level|replace('PRICE_LEVEL_', '')|replace('_', ' ')|title }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Fields Section -->
|
||||
<div class="fields-section">
|
||||
<h2 class="section-title">
|
||||
@ -1376,6 +1414,13 @@
|
||||
<span class="field-name">Godziny specjalne</span>
|
||||
<span class="field-status-badge {{ 'complete' if audit.has_special_hours else 'partial' }}">{{ 'Ustawione' if audit.has_special_hours else 'Brak' }}</span>
|
||||
</div>
|
||||
{% if audit.special_hours %}
|
||||
<div style="margin-top: var(--spacing-xs); padding: var(--spacing-xs) var(--spacing-sm); background: var(--bg-tertiary); border-radius: var(--radius-sm); font-size: var(--font-size-xs); color: var(--text-secondary);">
|
||||
{% for entry in audit.special_hours %}
|
||||
<div>{{ entry.get('date', '') }}: {% if entry.get('closed') %}Zamkniete{% else %}{{ entry.get('open', '') }} - {{ entry.get('close', '') }}{% endif %}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -1549,6 +1594,14 @@
|
||||
{% include 'partials/audit_ai_actions.html' %}
|
||||
{% endwith %}
|
||||
|
||||
{% if audit.audit_errors %}
|
||||
<!-- Audit Errors / Warnings -->
|
||||
<div style="background: #fef3c7; padding: var(--spacing-md); border-radius: var(--radius-lg); margin-bottom: var(--spacing-xl); border-left: 4px solid #f59e0b;">
|
||||
<div style="font-size: var(--font-size-sm); font-weight: 600; color: #92400e; margin-bottom: var(--spacing-xs);">Uwagi z audytu</div>
|
||||
<p style="font-size: var(--font-size-xs); color: #78350f; margin: 0;">{{ audit.audit_errors }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
<!-- No Audit State -->
|
||||
<div class="no-audit-state">
|
||||
|
||||
@ -790,12 +790,42 @@
|
||||
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">
|
||||
@ -869,6 +899,14 @@
|
||||
{{ 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">
|
||||
@ -976,6 +1014,61 @@
|
||||
{% 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>
|
||||
Porownanie 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);">Obserwujacy</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);">Kompletnosc</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 %}—{% 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 %}—{% 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">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user