thadillo
Enable sentence-level view for map and contributions
f037336
{% extends "admin/base.html" %}
{% block title %}Analytics Dashboard{% endblock %}
{% set get_category_color = {
'Vision': '#3b82f6',
'Problem': '#ef4444',
'Objectives': '#10b981',
'Directives': '#f59e0b',
'Values': '#8b5cf6',
'Actions': '#ec4899'
}.get %}
{% block admin_content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Analytics Dashboard</h2>
<div class="d-flex gap-3">
<!-- View Mode Selector -->
<div class="btn-group" role="group" aria-label="View mode">
<input type="radio" class="btn-check" name="viewMode" id="viewSubmissions"
{% if view_mode == 'submissions' %}checked{% endif %}
onchange="window.location.href='{{ url_for('admin.dashboard', mode='submissions') }}'">
<label class="btn btn-outline-primary" for="viewSubmissions">
By Submissions
</label>
<input type="radio" class="btn-check" name="viewMode" id="viewSentences"
{% if view_mode == 'sentences' %}checked{% endif %}
onchange="window.location.href='{{ url_for('admin.dashboard', mode='sentences') }}'">
<label class="btn btn-outline-primary" for="viewSentences">
By Sentences
</label>
</div>
<!-- Export PDF Button -->
<a href="{{ url_for('admin.export_dashboard_pdf', mode=view_mode) }}"
class="btn btn-success">
<i class="bi bi-file-earmark-pdf"></i> Export PDF
</a>
</div>
</div>
<div class="row g-4 mb-4">
<div class="col-lg-6">
<div class="card shadow-sm h-100">
<div class="card-body">
<h5 class="card-title">By Contributor Type</h5>
<canvas id="contributorChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card shadow-sm h-100">
<div class="card-body">
<h5 class="card-title">By Category</h5>
<canvas id="categoryChart"></canvas>
</div>
</div>
</div>
</div>
{% if geotagged_items %}
<div class="card shadow-sm mb-4">
<div class="card-body">
<h5 class="card-title mb-3">
Geographic Distribution ({{ geotagged_items|length }} geotagged)
</h5>
<div id="dashboardMap" class="dashboard-map-container border rounded"></div>
</div>
</div>
{% endif %}
<div class="card shadow-sm mb-4">
<div class="card-body">
<h5 class="card-title mb-4">Contributions by Category</h5>
{% for category in categories %}
{% set category_items = items|selectattr('category', 'equalto', category)|list %}
{% if category_items %}
<div class="mb-4">
<h6 class="border-bottom pb-2" style="border-color: {{ get_category_color(category) }}!important; border-width: 2px!important;">
<span class="badge" style="background-color: {{ get_category_color(category) }};">{{ category }}</span>
<small class="text-muted">({{ category_items|length }} contribution{{ 's' if category_items|length != 1 else '' }})</small>
</h6>
{% for sub in category_items %}
<div class="border-start border-3 ps-3 mb-3" style="border-color: {{ get_category_color(category) }}!important;">
<div class="d-flex justify-content-between align-items-start mb-1">
<small class="text-muted text-capitalize">{{ sub.contributor_type }}</small>
<small class="text-muted">{{ sub.timestamp.strftime('%Y-%m-%d') if sub.timestamp else '' }}</small>
</div>
<p class="mb-0">{{ sub.message }}</p>
{% if sub.latitude and sub.longitude %}
<p class="text-muted small mb-0 mt-1">
<i class="bi bi-geo-alt-fill"></i> {{ sub.latitude|round(4) }}, {{ sub.longitude|round(4) }}
</p>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
{% endfor %}
</div>
</div>
<div class="card shadow-sm">
<div class="card-body">
<h5 class="card-title mb-3">Category Breakdown by Contributor Type</h5>
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>Category</th>
{% for type in contributor_types %}
<th class="text-center">{{ type.label }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for category in categories %}
<tr>
<td class="fw-bold">{{ category }}</td>
{% for type in contributor_types %}
<td class="text-center">
{% set count = breakdown[category][type.value] %}
{{ count if count > 0 else '-' }}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Category colors
const categoryColors = {
'Vision': '#3b82f6',
'Problem': '#ef4444',
'Objectives': '#10b981',
'Directives': '#f59e0b',
'Values': '#8b5cf6',
'Actions': '#ec4899'
};
// Contributor Chart
const contributorData = {
labels: [{% for stat in contributor_stats %}'{{ stat[0] }}'{% if not loop.last %}, {% endif %}{% endfor %}],
datasets: [{
data: [{% for stat in contributor_stats %}{{ stat[1] }}{% if not loop.last %}, {% endif %}{% endfor %}],
backgroundColor: ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899']
}]
};
new Chart(document.getElementById('contributorChart'), {
type: 'pie',
data: contributorData,
options: {
responsive: true,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
// Category Chart
const categoryData = {
labels: [{% for stat in category_stats %}'{{ stat[0] }}'{% if not loop.last %}, {% endif %}{% endfor %}],
datasets: [{
label: 'Submissions',
data: [{% for stat in category_stats %}{{ stat[1] }}{% if not loop.last %}, {% endif %}{% endfor %}],
backgroundColor: [{% for stat in category_stats %}categoryColors['{{ stat[0] }}']{% if not loop.last %}, {% endif %}{% endfor %}]
}]
};
new Chart(document.getElementById('categoryChart'), {
type: 'bar',
data: categoryData,
options: {
responsive: true,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
stepSize: 1
}
}
}
}
});
{% if geotagged_items %}
// Dashboard Map
const dashMap = L.map('dashboardMap').setView([0, 0], 2);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap'
}).addTo(dashMap);
const bounds = [];
{% for sub in geotagged_items %}
{
const color = categoryColors['{{ sub.category }}'] || '#6b7280';
const customIcon = L.divIcon({
className: 'custom-marker',
html: `<div style="background-color: ${color}; width: 20px; height: 20px; border-radius: 50%; border: 2px solid white;"></div>`,
iconSize: [20, 20]
});
const marker = L.marker([{{ sub.latitude }}, {{ sub.longitude }}], { icon: customIcon })
.addTo(dashMap)
.bindPopup(`
<div>
<div style="color: ${color}; font-weight: bold;">{{ sub.category }}</div>
<div class="text-muted small text-capitalize">{{ sub.contributor_type }}</div>
<div class="mt-1">{{ sub.message[:100] }}{{ '...' if sub.message|length > 100 else '' }}</div>
</div>
`);
bounds.push([{{ sub.latitude }}, {{ sub.longitude }}]);
}
{% endfor %}
if (bounds.length > 0) {
dashMap.fitBounds(bounds, { padding: [50, 50] });
}
setTimeout(() => dashMap.invalidateSize(), 100);
{% endif %}
});
</script>
<style>
.custom-marker {
background: none;
border: none;
}
</style>
{% endblock %}