refactor: remove /admin/social-media panel, rename menu labels
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

Remove unused social-media analytics panel (replaced by social-audit).
Rename admin menu items for clarity:
- Forum → Moderacja forum
- Ogłoszenia → Moderacja ogłoszeń
- Rekomendacje → Moderacja rekomendacji
- Deklaracje → Zarządzanie deklaracjami
- Składki → Zarządzanie składkami
- Korzyści → Zarządzanie korzyściami
- Social Media → Audyt social media
- Social Dashboard → Publikacja social media
- Kalendarz → Zarządzanie kalendarzem

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-12 16:29:19 +01:00
parent 0394f9f17d
commit 1612deeb53
7 changed files with 14 additions and 650 deletions

View File

@ -330,7 +330,6 @@ def register_blueprints(app):
'admin_status': 'admin.admin_status', 'admin_status': 'admin.admin_status',
'admin_health': 'admin.admin_health', 'admin_health': 'admin.admin_health',
# Social Media (Phase 6.2e) # Social Media (Phase 6.2e)
'admin_social_media': 'admin.admin_social_media',
'admin_social_audit': 'admin.admin_social_audit', 'admin_social_audit': 'admin.admin_social_audit',
# Digital Maturity & KRS Audit (Phase 6.2f) # Digital Maturity & KRS Audit (Phase 6.2f)
'digital_maturity_dashboard': 'admin.digital_maturity_dashboard', 'digital_maturity_dashboard': 'admin.digital_maturity_dashboard',

View File

@ -181,98 +181,6 @@ def _compute_social_health_score(platform_details):
# ============================================================ # ============================================================
# SOCIAL MEDIA ANALYTICS DASHBOARD # SOCIAL MEDIA ANALYTICS DASHBOARD
# ============================================================
@bp.route('/social-media')
@login_required
@role_required(SystemRole.OFFICE_MANAGER)
def admin_social_media():
"""Admin dashboard for social media analytics"""
db = SessionLocal()
try:
# Total counts per platform
platform_stats = db.query(
CompanySocialMedia.platform,
func.count(CompanySocialMedia.id).label('count'),
func.count(distinct(CompanySocialMedia.company_id)).label('companies')
).filter(
CompanySocialMedia.is_valid == True
).group_by(CompanySocialMedia.platform).all()
# Companies with each platform combination
company_platforms = db.query(
Company.id,
Company.name,
Company.slug,
func.array_agg(distinct(CompanySocialMedia.platform)).label('platforms')
).outerjoin(
CompanySocialMedia,
(Company.id == CompanySocialMedia.company_id) & (CompanySocialMedia.is_valid == True)
).group_by(Company.id, Company.name, Company.slug).all()
# Analysis
total_companies = len(company_platforms)
companies_with_sm = [c for c in company_platforms if c.platforms and c.platforms[0] is not None]
companies_without_sm = [c for c in company_platforms if not c.platforms or c.platforms[0] is None]
# Platform combinations
platform_combos_raw = {}
for c in companies_with_sm:
platforms = sorted([p for p in c.platforms if p]) if c.platforms else []
key = ', '.join(platforms) if platforms else 'Brak'
if key not in platform_combos_raw:
platform_combos_raw[key] = []
platform_combos_raw[key].append({'id': c.id, 'name': c.name, 'slug': c.slug})
# Sort by number of companies (descending)
platform_combos = dict(sorted(platform_combos_raw.items(), key=lambda x: len(x[1]), reverse=True))
# Only Facebook
only_facebook = [c for c in companies_with_sm if set(c.platforms) == {'facebook'}]
# Only LinkedIn
only_linkedin = [c for c in companies_with_sm if set(c.platforms) == {'linkedin'}]
# Only Instagram
only_instagram = [c for c in companies_with_sm if set(c.platforms) == {'instagram'}]
# Has all major (FB + LI + IG)
has_all_major = [c for c in companies_with_sm if {'facebook', 'linkedin', 'instagram'}.issubset(set(c.platforms or []))]
# Get all social media entries with company info for detailed view
all_entries = db.query(
CompanySocialMedia,
Company.name.label('company_name'),
Company.slug.label('company_slug')
).join(Company).order_by(
Company.name, CompanySocialMedia.platform
).all()
# Freshness analysis
now = datetime.now()
fresh_30d = db.query(func.count(CompanySocialMedia.id)).filter(
CompanySocialMedia.verified_at >= now - timedelta(days=30)
).scalar()
stale_90d = db.query(func.count(CompanySocialMedia.id)).filter(
CompanySocialMedia.verified_at < now - timedelta(days=90)
).scalar()
return render_template('admin/social_media.html',
platform_stats=platform_stats,
total_companies=total_companies,
companies_with_sm=len(companies_with_sm),
companies_without_sm=companies_without_sm,
platform_combos=platform_combos,
only_facebook=only_facebook,
only_linkedin=only_linkedin,
only_instagram=only_instagram,
has_all_major=has_all_major,
all_entries=all_entries,
fresh_30d=fresh_30d,
stale_90d=stale_90d,
now=now
)
finally:
db.close()
# ============================================================ # ============================================================
# SOCIAL MEDIA AUDIT DASHBOARD # SOCIAL MEDIA AUDIT DASHBOARD
# ============================================================ # ============================================================

View File

@ -637,7 +637,7 @@ def admin_health():
('/health', 'Health check', 'api'), ('/health', 'Health check', 'api'),
('/admin/security', 'Bezpieczeństwo', 'admin'), ('/admin/security', 'Bezpieczeństwo', 'admin'),
('/admin/seo', 'SEO Audit', 'admin'), ('/admin/seo', 'SEO Audit', 'admin'),
('/admin/social-media', 'Social Media', 'admin'), ('/admin/social-audit', 'Audyt social media', 'admin'),
('/admin/analytics', 'Analityka', 'admin'), ('/admin/analytics', 'Analityka', 'admin'),
('/admin/forum', 'Forum', 'admin'), ('/admin/forum', 'Forum', 'admin'),
('/admin/kalendarz', 'Kalendarz', 'admin'), ('/admin/kalendarz', 'Kalendarz', 'admin'),

View File

@ -524,12 +524,6 @@
Uruchom audyt Uruchom audyt
</button> </button>
<span id="enrichMiniStatus" style="display: none; font-size: 12px; color: #2563eb; font-weight: 500;"></span> <span id="enrichMiniStatus" style="display: none; font-size: 12px; color: #2563eb; font-weight: 500;"></span>
<a href="{{ url_for('admin.admin_social_media') }}" 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 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
</svg>
Szczegóły
</a>
</div> </div>
</div> </div>

View File

@ -1,537 +0,0 @@
{% extends "base.html" %}
{% block title %}Social Media Analytics - Norda Biznes Partner{% endblock %}
{% block extra_css %}
<style>
.analytics-header {
margin-bottom: var(--spacing-xl);
}
.analytics-header h1 {
font-size: var(--font-size-3xl);
color: var(--text-primary);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: var(--spacing-lg);
margin-bottom: var(--spacing-2xl);
}
.stat-card {
background: var(--surface);
padding: var(--spacing-lg);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
text-align: center;
}
.stat-card.facebook { border-top: 4px solid #1877f2; }
.stat-card.linkedin { border-top: 4px solid #0a66c2; }
.stat-card.instagram { border-top: 4px solid #e4405f; }
.stat-card.youtube { border-top: 4px solid #ff0000; }
.stat-card.twitter { border-top: 4px solid #1da1f2; }
.stat-card.success { border-top: 4px solid var(--success); }
.stat-card.warning { border-top: 4px solid var(--warning); }
.stat-card.error { border-top: 4px solid var(--error); }
.stat-value {
font-size: var(--font-size-3xl);
font-weight: 700;
color: var(--primary);
}
.stat-label {
color: var(--text-secondary);
font-size: var(--font-size-sm);
margin-top: var(--spacing-xs);
}
.section {
background: var(--surface);
padding: var(--spacing-xl);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
margin-bottom: var(--spacing-xl);
}
.section h2 {
font-size: var(--font-size-xl);
margin-bottom: var(--spacing-lg);
color: var(--text-primary);
border-bottom: 2px solid var(--border);
padding-bottom: var(--spacing-sm);
}
.combo-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: var(--spacing-lg);
}
.combo-card {
background: var(--background);
border-radius: var(--radius);
padding: var(--spacing-md);
}
.combo-card h3 {
font-size: var(--font-size-base);
color: var(--text-primary);
margin-bottom: var(--spacing-sm);
display: flex;
justify-content: space-between;
align-items: center;
}
.combo-card h3 .count {
background: var(--primary);
color: white;
padding: 2px 8px;
border-radius: var(--radius-sm);
font-size: var(--font-size-sm);
}
.company-list {
max-height: 200px;
overflow-y: auto;
font-size: var(--font-size-sm);
}
.company-list a {
display: block;
padding: var(--spacing-xs) 0;
color: var(--text-secondary);
text-decoration: none;
border-bottom: 1px solid var(--border);
}
.company-list a:hover {
color: var(--primary);
}
.company-list a:last-child {
border-bottom: none;
}
.platform-badge {
display: inline-block;
padding: 2px 8px;
border-radius: var(--radius-sm);
font-size: var(--font-size-xs);
font-weight: 600;
margin-right: 4px;
}
.platform-badge.facebook { background: #e7f3ff; color: #1877f2; }
.platform-badge.linkedin { background: #e8f4fc; color: #0a66c2; }
.platform-badge.instagram { background: #fce7ed; color: #e4405f; }
.platform-badge.youtube { background: #ffe8e8; color: #ff0000; }
.platform-badge.twitter { background: #e8f6fc; color: #1da1f2; }
.data-table {
width: 100%;
border-collapse: collapse;
}
.data-table th, .data-table td {
padding: var(--spacing-sm) var(--spacing-md);
text-align: left;
border-bottom: 1px solid var(--border);
}
.data-table th {
background: var(--background);
font-weight: 600;
color: var(--text-primary);
position: sticky;
top: 0;
}
.data-table tr:hover {
background: var(--background);
}
.freshness-indicator {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 8px;
}
.freshness-indicator.fresh { background: var(--success); }
.freshness-indicator.stale { background: var(--warning); }
.freshness-indicator.old { background: var(--error); }
.table-container {
max-height: 500px;
overflow-y: auto;
}
.tabs {
display: flex;
gap: var(--spacing-sm);
margin-bottom: var(--spacing-lg);
border-bottom: 2px solid var(--border);
padding-bottom: var(--spacing-sm);
}
.tab {
padding: var(--spacing-sm) var(--spacing-lg);
border-radius: var(--radius) var(--radius) 0 0;
cursor: pointer;
background: var(--background);
color: var(--text-secondary);
border: none;
font-size: var(--font-size-sm);
}
.tab.active {
background: var(--primary);
color: white;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.no-sm-list {
columns: 2;
gap: var(--spacing-xl);
}
.no-sm-list a {
display: block;
padding: var(--spacing-xs) 0;
color: var(--text-secondary);
text-decoration: none;
}
.no-sm-list a:hover {
color: var(--primary);
}
@media (max-width: 768px) {
.no-sm-list {
columns: 1;
}
}
/* Platform Filter Buttons */
.platform-filters {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--spacing-xs);
}
.filter-btn {
padding: var(--spacing-xs) var(--spacing-sm);
border: 2px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
color: var(--text-secondary);
cursor: pointer;
font-size: var(--font-size-sm);
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 4px;
}
.filter-btn:hover {
border-color: var(--primary);
color: var(--primary);
}
.filter-btn.active {
background: var(--primary);
border-color: var(--primary);
color: white;
}
.filter-btn.active .platform-badge {
background: rgba(255,255,255,0.3) !important;
color: white !important;
}
.filter-btn[data-platform="facebook"].active { background: #1877f2; border-color: #1877f2; }
.filter-btn[data-platform="instagram"].active { background: #e4405f; border-color: #e4405f; }
.filter-btn[data-platform="linkedin"].active { background: #0a66c2; border-color: #0a66c2; }
.filter-btn[data-platform="youtube"].active { background: #ff0000; border-color: #ff0000; }
.filter-btn[data-platform="twitter"].active { background: #1da1f2; border-color: #1da1f2; }
.filter-btn[data-platform="tiktok"].active { background: #000000; border-color: #000000; }
.data-table tr.hidden-row {
display: none;
}
</style>
{% endblock %}
{% block content %}
<div class="analytics-header">
<h1>Social Media Analytics</h1>
<p class="text-muted">Pelny przeglad kont social media firm czlonkowskich</p>
</div>
<!-- Main Stats -->
<div class="stats-grid">
<div class="stat-card success">
<div class="stat-value">{{ companies_with_sm }}</div>
<div class="stat-label">Firm z Social Media</div>
</div>
<div class="stat-card warning">
<div class="stat-value">{{ companies_without_sm|length }}</div>
<div class="stat-label">Firm BEZ Social Media</div>
</div>
{% for stat in platform_stats %}
<div class="stat-card {{ stat.platform }}">
<div class="stat-value">{{ stat.companies }}</div>
<div class="stat-label">{{ stat.platform|title }}</div>
</div>
{% endfor %}
</div>
<!-- Freshness Stats -->
<div class="stats-grid">
<div class="stat-card success">
<div class="stat-value">{{ fresh_30d }}</div>
<div class="stat-label">Zweryfikowane < 30 dni</div>
</div>
<div class="stat-card error">
<div class="stat-value">{{ stale_90d }}</div>
<div class="stat-label">Nieaktualne > 90 dni</div>
</div>
</div>
<!-- Tabs -->
<div class="section">
<div class="tabs">
<button class="tab active" onclick="showTab('combos', this)">Kombinacje platform</button>
<button class="tab" onclick="showTab('missing', this)">Firmy bez SM ({{ companies_without_sm|length }})</button>
<button class="tab" onclick="showTab('all', this)">Wszystkie wpisy ({{ all_entries|length }})</button>
</div>
<!-- Tab: Combinations -->
<div id="tab-combos" class="tab-content active">
<h2>Kombinacje platform Social Media</h2>
<div class="combo-grid">
{% for combo, companies in platform_combos.items() %}
<div class="combo-card">
<h3>
{% for platform in combo.split(', ') %}
<span class="platform-badge {{ platform }}">{{ platform|upper }}</span>
{% endfor %}
<span class="count">{{ companies|length }}</span>
</h3>
<div class="company-list">
{% for company in companies %}
<a href="{{ url_for('company_detail', company_id=company.id) }}">{{ company.name }}</a>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
<!-- Special categories -->
<h2 style="margin-top: var(--spacing-xl);">Analiza szczegolowa</h2>
<div class="combo-grid">
<div class="combo-card">
<h3>Tylko Facebook <span class="count">{{ only_facebook|length }}</span></h3>
<div class="company-list">
{% for c in only_facebook %}
<a href="{{ url_for('company_detail', company_id=c.id) }}">{{ c.name }}</a>
{% endfor %}
</div>
</div>
<div class="combo-card">
<h3>Tylko LinkedIn <span class="count">{{ only_linkedin|length }}</span></h3>
<div class="company-list">
{% for c in only_linkedin %}
<a href="{{ url_for('company_detail', company_id=c.id) }}">{{ c.name }}</a>
{% endfor %}
</div>
</div>
<div class="combo-card">
<h3>Tylko Instagram <span class="count">{{ only_instagram|length }}</span></h3>
<div class="company-list">
{% for c in only_instagram %}
<a href="{{ url_for('company_detail', company_id=c.id) }}">{{ c.name }}</a>
{% endfor %}
</div>
</div>
<div class="combo-card" style="border-left: 4px solid var(--success);">
<h3>Wszystkie glowne (FB+LI+IG) <span class="count">{{ has_all_major|length }}</span></h3>
<div class="company-list">
{% for c in has_all_major %}
<a href="{{ url_for('company_detail', company_id=c.id) }}">{{ c.name }}</a>
{% endfor %}
</div>
</div>
</div>
</div>
<!-- Tab: Missing -->
<div id="tab-missing" class="tab-content">
<h2>Firmy bez danych Social Media ({{ companies_without_sm|length }})</h2>
<p class="text-muted" style="margin-bottom: var(--spacing-lg);">Te firmy wymagaja uzupelnienia danych o kontach social media.</p>
<div class="no-sm-list">
{% for c in companies_without_sm|sort(attribute='name') %}
<a href="{{ url_for('company_detail', company_id=c.id) }}">{{ c.name }}</a>
{% endfor %}
</div>
</div>
<!-- Tab: All Entries -->
<div id="tab-all" class="tab-content">
<h2>Wszystkie wpisy Social Media</h2>
<!-- Platform Filter Buttons -->
<div class="platform-filters" style="margin-bottom: var(--spacing-lg);">
<span style="margin-right: var(--spacing-sm); color: var(--text-secondary);">Filtruj platformy:</span>
<button type="button" class="filter-btn active" data-platform="all" onclick="toggleFilter('all', this)">Wszystkie</button>
<button type="button" class="filter-btn" data-platform="facebook" onclick="toggleFilter('facebook', this)">
<span class="platform-badge facebook">FB</span> Facebook
</button>
<button type="button" class="filter-btn" data-platform="instagram" onclick="toggleFilter('instagram', this)">
<span class="platform-badge instagram">IG</span> Instagram
</button>
<button type="button" class="filter-btn" data-platform="linkedin" onclick="toggleFilter('linkedin', this)">
<span class="platform-badge linkedin">LI</span> LinkedIn
</button>
<button type="button" class="filter-btn" data-platform="youtube" onclick="toggleFilter('youtube', this)">
<span class="platform-badge youtube">YT</span> YouTube
</button>
<button type="button" class="filter-btn" data-platform="twitter" onclick="toggleFilter('twitter', this)">
<span class="platform-badge twitter">X</span> Twitter/X
</button>
<button type="button" class="filter-btn" data-platform="tiktok" onclick="toggleFilter('tiktok', this)">
<span style="background:#000;color:#fff;padding:2px 6px;border-radius:3px;font-size:10px;margin-right:4px;">TT</span> TikTok
</button>
<span id="filter-count" style="margin-left: var(--spacing-md); color: var(--text-secondary);"></span>
</div>
<div class="table-container">
<table class="data-table" id="sm-table">
<thead>
<tr>
<th>Firma</th>
<th>Platforma</th>
<th>URL</th>
<th>Zrodlo</th>
<th>Zweryfikowano</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for entry in all_entries %}
{% set sm = entry[0] %}
<tr data-platform="{{ sm.platform }}">
<td><a href="{{ url_for('company_detail', company_id=sm.company_id) }}">{{ entry.company_name }}</a></td>
<td><span class="platform-badge {{ sm.platform }}">{{ sm.platform|upper }}</span></td>
<td><a href="{{ sm.url }}" target="_blank" rel="noopener">{{ sm.url[:50] }}{% if sm.url|length > 50 %}...{% endif %}</a></td>
<td>{{ sm.source or '-' }}</td>
<td>
{% set days_ago = (now - sm.verified_at).days if sm.verified_at else 999 %}
<span class="freshness-indicator {% if days_ago < 30 %}fresh{% elif days_ago < 90 %}stale{% else %}old{% endif %}"></span>
{{ sm.verified_at.strftime('%Y-%m-%d') if sm.verified_at else '-' }}
</td>
<td>
{% if sm.check_status == 'needs_verification' %}
<span style="background: #fef3c7; color: #92400e; padding: 2px 8px; border-radius: 4px; font-size: 0.8rem;">Do weryfikacji</span>
{% elif not sm.is_valid %}
<span style="background: #fee2e2; color: #991b1b; padding: 2px 8px; border-radius: 4px; font-size: 0.8rem;">Nieaktywny</span>
{% else %}
OK
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
const now = new Date();
// Active platform filters (empty = all)
let activeFilters = new Set();
function showTab(tabName, clickedBtn) {
// Hide all tabs
document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));
document.querySelectorAll('.tab').forEach(el => el.classList.remove('active'));
// Show selected tab
document.getElementById('tab-' + tabName).classList.add('active');
if (clickedBtn) clickedBtn.classList.add('active');
}
function toggleFilter(platform, btn) {
const allBtn = document.querySelector('.filter-btn[data-platform="all"]');
if (platform === 'all') {
// Clear all filters, show everything
activeFilters.clear();
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
allBtn.classList.add('active');
} else {
// Toggle specific platform
allBtn.classList.remove('active');
if (activeFilters.has(platform)) {
activeFilters.delete(platform);
btn.classList.remove('active');
} else {
activeFilters.add(platform);
btn.classList.add('active');
}
// If no filters active, activate "all"
if (activeFilters.size === 0) {
allBtn.classList.add('active');
}
}
applyFilters();
}
function applyFilters() {
const table = document.getElementById('sm-table');
if (!table) return;
const rows = table.querySelectorAll('tbody tr');
let visibleCount = 0;
rows.forEach(row => {
const platform = row.dataset.platform;
if (activeFilters.size === 0 || activeFilters.has(platform)) {
row.classList.remove('hidden-row');
visibleCount++;
} else {
row.classList.add('hidden-row');
}
});
// Update count display
const countEl = document.getElementById('filter-count');
if (countEl) {
if (activeFilters.size === 0) {
countEl.textContent = '';
} else {
countEl.textContent = `Pokazano: ${visibleCount} z ${rows.length}`;
}
}
}
{% endblock %}

View File

@ -1654,45 +1654,45 @@
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 9v3m0 0v3m0-3h3m-3 0h-3m-2-5a4 4 0 11-8 0 4 4 0 018 0zM3 20a6 6 0 0112 0v1H3v-1z"/>
</svg> </svg>
Deklaracje Zarządzanie deklaracjami
</a> </a>
<a href="{{ url_for('admin.admin_fees') }}"> <a href="{{ url_for('admin.admin_fees') }}">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg> </svg>
Składki Zarządzanie składkami
</a> </a>
{% if current_user.has_role(SystemRole.ADMIN) %} {% if current_user.has_role(SystemRole.ADMIN) %}
<a href="{{ url_for('admin.admin_benefits') }}"> <a href="{{ url_for('admin.admin_benefits') }}">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v13m0-13V6a2 2 0 112 2h-2zm0 0V5.5A2.5 2.5 0 109.5 8H12zm-7 4h14M5 12a2 2 0 110-4h14a2 2 0 110 4M5 12v7a2 2 0 002 2h10a2 2 0 002-2v-7"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v13m0-13V6a2 2 0 112 2h-2zm0 0V5.5A2.5 2.5 0 109.5 8H12zm-7 4h14M5 12a2 2 0 110-4h14a2 2 0 110 4M5 12v7a2 2 0 002 2h10a2 2 0 002-2v-7"/>
</svg> </svg>
Korzyści Zarządzanie korzyściami
</a> </a>
{% endif %} {% endif %}
<a href="{{ url_for('admin.admin_recommendations') }}"> <a href="{{ url_for('admin.admin_recommendations') }}">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"/>
</svg> </svg>
Rekomendacje Moderacja rekomendacji
</a> </a>
<a href="{{ url_for('admin.admin_calendar') }}"> <a href="{{ url_for('admin.admin_calendar') }}">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"> <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"/> <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> </svg>
Kalendarz Zarządzanie kalendarzem
</a> </a>
<a href="{{ url_for('admin_forum') }}"> <a href="{{ url_for('admin_forum') }}">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg 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"/> <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> </svg>
Forum Moderacja forum
</a> </a>
<a href="{{ url_for('admin.admin_announcements') }}"> <a href="{{ url_for('admin.admin_announcements') }}">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5.882V19.24a1.76 1.76 0 01-3.417.592l-2.147-6.15M18 13a3 3 0 100-6M5.436 13.683A4.001 4.001 0 017 6h1.832c4.1 0 7.625-1.234 9.168-3v14c-1.543-1.766-5.067-3-9.168-3H7a3.988 3.988 0 01-1.564-.317z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5.882V19.24a1.76 1.76 0 01-3.417.592l-2.147-6.15M18 13a3 3 0 100-6M5.436 13.683A4.001 4.001 0 017 6h1.832c4.1 0 7.625-1.234 9.168-3v14c-1.543-1.766-5.067-3-9.168-3H7a3.988 3.988 0 01-1.564-.317z"/>
</svg> </svg>
Ogłoszenia Moderacja ogłoszeń
</a> </a>
<a href="{{ url_for('admin.admin_insights') }}"> <a href="{{ url_for('admin.admin_insights') }}">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -1700,17 +1700,17 @@
</svg> </svg>
Insights AI Insights AI
</a> </a>
<a href="{{ url_for('admin.admin_social_media') }}"> <a href="{{ url_for('admin.admin_social_audit') }}">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4v16M17 4v16M3 8h4m10 0h4M3 12h18M3 16h4m10 0h4M4 20h16a1 1 0 001-1V5a1 1 0 00-1-1H4a1 1 0 00-1 1v14a1 1 0 001 1z"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4v16M17 4v16M3 8h4m10 0h4M3 12h18M3 16h4m10 0h4M4 20h16a1 1 0 001-1V5a1 1 0 00-1-1H4a1 1 0 00-1 1v14a1 1 0 001 1z"/>
</svg> </svg>
Social Media Audyt social media
</a> </a>
<a href="{{ url_for('admin.social_publisher_list') }}"> <a href="{{ url_for('admin.social_publisher_list') }}">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg 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"/> <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> </svg>
Social Dashboard <span class="nav-badge-beta">beta</span> Publikacja social media <span class="nav-badge-beta">beta</span>
</a> </a>
<a href="{{ url_for('admin_zopk') }}"> <a href="{{ url_for('admin_zopk') }}">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">

View File

@ -932,12 +932,12 @@
<span class="btn btn-sm btn-info">Zarządzaj</span> <span class="btn btn-sm btn-info">Zarządzaj</span>
</a> </a>
<a href="{{ url_for('admin.admin_social_media') }}" class="admin-function-card"> <a href="{{ url_for('admin.admin_social_audit') }}" class="admin-function-card">
<svg fill="none" stroke="#8b5cf6" stroke-width="2" viewBox="0 0 24 24"> <svg fill="none" stroke="#8b5cf6" stroke-width="2" viewBox="0 0 24 24">
<path d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14"/> <path d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14"/>
</svg> </svg>
<h6>Social Media</h6> <h6>Audyt social media</h6>
<span class="btn btn-sm" style="background: #8b5cf6; color: white;">Edytuj</span> <span class="btn btn-sm" style="background: #8b5cf6; color: white;">Otwórz</span>
</a> </a>
<a href="{{ url_for('admin.user_insights') }}" class="admin-function-card"> <a href="{{ url_for('admin.user_insights') }}" class="admin-function-card">