Naprawia błędy BuildError w szablonach admin gdzie używano slug zamiast company_id. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
425 lines
15 KiB
HTML
Executable File
425 lines
15 KiB
HTML
Executable File
{% extends "base.html" %}
|
|
|
|
{% block title %}Dojrzałość Cyfrowa - Dashboard - Norda Biznes Hub{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.maturity-header {
|
|
margin-bottom: var(--spacing-xl);
|
|
}
|
|
|
|
.maturity-header h1 {
|
|
font-size: var(--font-size-3xl);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(220px, 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-value {
|
|
font-size: var(--font-size-3xl);
|
|
font-weight: 700;
|
|
color: var(--primary);
|
|
}
|
|
|
|
.stat-value.high { color: var(--success); }
|
|
.stat-value.medium { color: var(--warning); }
|
|
.stat-value.low { color: var(--error); }
|
|
|
|
.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-2xl);
|
|
margin-bottom: var(--spacing-lg);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.company-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.company-table th {
|
|
text-align: left;
|
|
padding: var(--spacing-md);
|
|
background: var(--background);
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
border-bottom: 2px solid var(--border);
|
|
}
|
|
|
|
.company-table td {
|
|
padding: var(--spacing-md);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.company-table tr:hover {
|
|
background: var(--hover);
|
|
}
|
|
|
|
.score-badge {
|
|
display: inline-block;
|
|
padding: var(--spacing-xs) var(--spacing-md);
|
|
border-radius: var(--radius-full);
|
|
font-weight: 600;
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
.score-excellent { background: #dcfce7; color: #166534; }
|
|
.score-good { background: #dbeafe; color: #1e40af; }
|
|
.score-fair { background: #fef3c7; color: #92400e; }
|
|
.score-poor { background: #fee2e2; color: #991b1b; }
|
|
|
|
.readiness-badge {
|
|
display: inline-block;
|
|
padding: var(--spacing-xs) var(--spacing-md);
|
|
border-radius: var(--radius-full);
|
|
font-weight: 600;
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
.readiness-warm { background: #fef3c7; color: #92400e; }
|
|
.readiness-cold { background: #dbeafe; color: #1e40af; }
|
|
.readiness-not-ready { background: #f3f4f6; color: #6b7280; }
|
|
|
|
.feature-icon {
|
|
font-size: var(--font-size-lg);
|
|
}
|
|
|
|
.feature-yes { color: var(--success); }
|
|
.feature-no { color: var(--error); }
|
|
|
|
.export-button {
|
|
display: inline-block;
|
|
padding: var(--spacing-sm) var(--spacing-lg);
|
|
background: var(--primary);
|
|
color: white;
|
|
border-radius: var(--radius-md);
|
|
text-decoration: none;
|
|
font-weight: 600;
|
|
margin-left: var(--spacing-md);
|
|
}
|
|
|
|
.export-button:hover {
|
|
background: var(--primary-hover);
|
|
}
|
|
|
|
.tabs {
|
|
display: flex;
|
|
gap: var(--spacing-md);
|
|
margin-bottom: var(--spacing-lg);
|
|
border-bottom: 2px solid var(--border);
|
|
}
|
|
|
|
.tab {
|
|
padding: var(--spacing-md) var(--spacing-lg);
|
|
cursor: pointer;
|
|
border: none;
|
|
background: none;
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
border-bottom: 2px solid transparent;
|
|
margin-bottom: -2px;
|
|
}
|
|
|
|
.tab.active {
|
|
color: var(--primary);
|
|
border-bottom-color: var(--primary);
|
|
}
|
|
|
|
.tab-content {
|
|
display: none;
|
|
}
|
|
|
|
.tab-content.active {
|
|
display: block;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container">
|
|
<div class="maturity-header">
|
|
<h1>📊 Dashboard Dojrzałości Cyfrowej</h1>
|
|
<p style="color: var(--text-secondary);">ETAP 1 - Analiza obecności online i jakości stron internetowych</p>
|
|
</div>
|
|
|
|
<!-- Stats Overview -->
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<div class="stat-value">{{ total_analyzed }}</div>
|
|
<div class="stat-label">Firm przeanalizowanych</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">{{ avg_score }}</div>
|
|
<div class="stat-label">Średni wynik /100</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value high">{{ warm_leads_count }}</div>
|
|
<div class="stat-label">Warm Leads</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value medium">{{ cold_leads_count }}</div>
|
|
<div class="stat-label">Cold Leads</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">{{ "{:,.0f}".format(total_opportunity) }} PLN</div>
|
|
<div class="stat-label">Wartość pipeline</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tabs -->
|
|
<div class="tabs">
|
|
<button class="tab active" onclick="showTab('top-performers')">🏆 Top 10</button>
|
|
<button class="tab" onclick="showTab('opportunities')">💰 Szanse sprzedażowe</button>
|
|
<button class="tab" onclick="showTab('bottom-performers')">⚠️ Wymagają wsparcia</button>
|
|
<button class="tab" onclick="showTab('all-companies')">📋 Wszystkie firmy</button>
|
|
</div>
|
|
|
|
<!-- Top Performers -->
|
|
<div id="top-performers" class="section tab-content active">
|
|
<h2>🏆 Top 10 Najlepszych Firm</h2>
|
|
<table class="company-table">
|
|
<thead>
|
|
<tr>
|
|
<th>#</th>
|
|
<th>Nazwa</th>
|
|
<th>Wynik</th>
|
|
<th>Blog</th>
|
|
<th>Portfolio</th>
|
|
<th>Kontakt</th>
|
|
<th>Bogactwo treści</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for company in top_performers %}
|
|
<tr>
|
|
<td>{{ loop.index }}</td>
|
|
<td>
|
|
<a href="{{ url_for('company_detail', company_id=company.id) }}" style="font-weight: 600;">
|
|
{{ company.name }}
|
|
</a>
|
|
</td>
|
|
<td>
|
|
{% if company.overall_score >= 80 %}
|
|
<span class="score-badge score-excellent">{{ company.overall_score }}/100</span>
|
|
{% elif company.overall_score >= 70 %}
|
|
<span class="score-badge score-good">{{ company.overall_score }}/100</span>
|
|
{% elif company.overall_score >= 60 %}
|
|
<span class="score-badge score-fair">{{ company.overall_score }}/100</span>
|
|
{% else %}
|
|
<span class="score-badge score-poor">{{ company.overall_score }}/100</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="feature-icon {% if company.has_blog %}feature-yes{% else %}feature-no{% endif %}">
|
|
{{ '✓' if company.has_blog else '✗' }}
|
|
</td>
|
|
<td class="feature-icon {% if company.has_portfolio %}feature-yes{% else %}feature-no{% endif %}">
|
|
{{ '✓' if company.has_portfolio else '✗' }}
|
|
</td>
|
|
<td class="feature-icon {% if company.has_contact_form %}feature-yes{% else %}feature-no{% endif %}">
|
|
{{ '✓' if company.has_contact_form else '✗' }}
|
|
</td>
|
|
<td>{{ company.content_richness_score }}/10</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Opportunities -->
|
|
<div id="opportunities" class="section tab-content">
|
|
<h2>💰 Top 10 Szans Sprzedażowych</h2>
|
|
<table class="company-table">
|
|
<thead>
|
|
<tr>
|
|
<th>#</th>
|
|
<th>Nazwa</th>
|
|
<th>Wartość projektu</th>
|
|
<th>Status</th>
|
|
<th>Opportunity Score</th>
|
|
<th>Kluczowe braki</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for company in top_opportunities %}
|
|
<tr>
|
|
<td>{{ loop.index }}</td>
|
|
<td>
|
|
<a href="{{ url_for('company_detail', company_id=company.id) }}" style="font-weight: 600;">
|
|
{{ company.name }}
|
|
</a>
|
|
</td>
|
|
<td style="font-weight: 700; color: var(--success);">
|
|
{{ "{:,.0f}".format(company.total_opportunity_value) }} PLN
|
|
</td>
|
|
<td>
|
|
{% if company.sales_readiness == 'warm' %}
|
|
<span class="readiness-badge readiness-warm">WARM</span>
|
|
{% elif company.sales_readiness == 'cold' %}
|
|
<span class="readiness-badge readiness-cold">COLD</span>
|
|
{% else %}
|
|
<span class="readiness-badge readiness-not-ready">Not Ready</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ company.opportunity_score }}/100</td>
|
|
<td style="font-size: var(--font-size-sm); color: var(--text-secondary);">
|
|
{% if company.critical_gaps %}
|
|
{{ company.critical_gaps|join(', ') }}
|
|
{% else %}
|
|
-
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Bottom Performers -->
|
|
<div id="bottom-performers" class="section tab-content">
|
|
<h2>⚠️ Firmy wymagające największego wsparcia</h2>
|
|
<table class="company-table">
|
|
<thead>
|
|
<tr>
|
|
<th>#</th>
|
|
<th>Nazwa</th>
|
|
<th>Wynik</th>
|
|
<th>Blog</th>
|
|
<th>Portfolio</th>
|
|
<th>Analytics</th>
|
|
<th>Braki funkcjonalne</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for company in bottom_performers %}
|
|
<tr>
|
|
<td>{{ loop.index }}</td>
|
|
<td>
|
|
<a href="{{ url_for('company_detail', company_id=company.id) }}" style="font-weight: 600;">
|
|
{{ company.name }}
|
|
</a>
|
|
</td>
|
|
<td>
|
|
<span class="score-badge score-poor">{{ company.overall_score }}/100</span>
|
|
</td>
|
|
<td class="feature-icon {% if company.has_blog %}feature-yes{% else %}feature-no{% endif %}">
|
|
{{ '✓' if company.has_blog else '✗' }}
|
|
</td>
|
|
<td class="feature-icon {% if company.has_portfolio %}feature-yes{% else %}feature-no{% endif %}">
|
|
{{ '✓' if company.has_portfolio else '✗' }}
|
|
</td>
|
|
<td class="feature-icon feature-no">✗</td>
|
|
<td style="font-size: var(--font-size-sm); color: var(--text-secondary);">
|
|
{% if company.missing_features %}
|
|
{{ company.missing_features[:3]|join(', ') }}...
|
|
{% else %}
|
|
-
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- All Companies -->
|
|
<div id="all-companies" class="section tab-content">
|
|
<h2>📋 Wszystkie firmy ({{ total_analyzed }})</h2>
|
|
<p style="margin-bottom: var(--spacing-lg); color: var(--text-secondary);">
|
|
Sortowane według wyniku ogólnego (malejąco)
|
|
</p>
|
|
<table class="company-table">
|
|
<thead>
|
|
<tr>
|
|
<th>#</th>
|
|
<th>Nazwa</th>
|
|
<th>Wynik</th>
|
|
<th>Online Presence</th>
|
|
<th>Status sprzedaży</th>
|
|
<th>Wartość</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for company in all_companies %}
|
|
<tr>
|
|
<td>{{ loop.index }}</td>
|
|
<td>
|
|
<a href="{{ url_for('company_detail', company_id=company.id) }}" style="font-weight: 600;">
|
|
{{ company.name }}
|
|
</a>
|
|
</td>
|
|
<td>
|
|
{% if company.overall_score >= 80 %}
|
|
<span class="score-badge score-excellent">{{ company.overall_score }}/100</span>
|
|
{% elif company.overall_score >= 70 %}
|
|
<span class="score-badge score-good">{{ company.overall_score }}/100</span>
|
|
{% elif company.overall_score >= 60 %}
|
|
<span class="score-badge score-fair">{{ company.overall_score }}/100</span>
|
|
{% else %}
|
|
<span class="score-badge score-poor">{{ company.overall_score }}/100</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ company.online_presence_score }}/100</td>
|
|
<td>
|
|
{% if company.sales_readiness == 'warm' %}
|
|
<span class="readiness-badge readiness-warm">WARM</span>
|
|
{% elif company.sales_readiness == 'cold' %}
|
|
<span class="readiness-badge readiness-cold">COLD</span>
|
|
{% else %}
|
|
<span class="readiness-badge readiness-not-ready">Not Ready</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ "{:,.0f}".format(company.total_opportunity_value) }} PLN</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function showTab(tabId) {
|
|
// Hide all tab contents
|
|
document.querySelectorAll('.tab-content').forEach(content => {
|
|
content.classList.remove('active');
|
|
});
|
|
|
|
// Remove active class from all tabs
|
|
document.querySelectorAll('.tab').forEach(tab => {
|
|
tab.classList.remove('active');
|
|
});
|
|
|
|
// Show selected tab content
|
|
document.getElementById(tabId).classList.add('active');
|
|
|
|
// Add active class to clicked tab
|
|
event.target.classList.add('active');
|
|
}
|
|
</script>
|
|
{% endblock %}
|