initial commit
All checks were successful
Build and Push Image / build-and-push (push) Successful in 1m26s

This commit is contained in:
Michael Trip 2026-01-09 21:58:53 +01:00
parent 3bba1f6db6
commit b56e866071
36 changed files with 4160 additions and 0 deletions

View file

@ -0,0 +1,223 @@
{% extends "base.html" %}
{% block title %}Statistieken - Snauw Counter{% endblock %}
{% block head %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
{% endblock %}
{% block content %}
<h2>📈 Statistieken & Trends</h2>
<!-- Statistiek overzicht -->
<div class="stats-grid">
<div class="stat-card">
<span class="stat-number">{{ stats.total_incidents }}</span>
<span class="stat-label">Totaal incidenten</span>
</div>
<div class="stat-card">
<span class="stat-number">{{ stats.incidents_today }}</span>
<span class="stat-label">Vandaag</span>
</div>
<div class="stat-card">
<span class="stat-number">{{ stats.avg_severity }}</span>
<span class="stat-label">Gemiddelde Snauw-index</span>
</div>
<div class="stat-card">
<span class="stat-number">{{ stats.most_severe }}</span>
<span class="stat-label">Hoogste Snauw-index</span>
</div>
</div>
<!-- Trend grafiek -->
{% if stats.total_incidents > 0 %}
<div class="card">
<h3>📊 Trend laatste 7 dagen</h3>
<div class="chart-container">
<canvas id="trendChart"></canvas>
</div>
</div>
<!-- Periode statistieken -->
<div class="card">
<h3>📅 Periode overzicht</h3>
<div class="stats-grid">
<div class="stat-card">
<span class="stat-number">{{ stats.incidents_this_week }}</span>
<span class="stat-label">Deze week</span>
</div>
<div class="stat-card">
<span class="stat-number">{{ stats.incidents_this_month }}</span>
<span class="stat-label">Deze maand</span>
</div>
</div>
</div>
<!-- Recent incidents -->
<div class="card">
<h3>🕒 Recente incidenten</h3>
{% if incidents %}
<div style="max-height: 300px; overflow-y: auto;">
{% for incident in incidents[:10] %}
<div style="display: flex; justify-content: space-between; align-items: center; padding: 0.5rem 0; border-bottom: 1px solid #e9ecef;">
<div>
<strong>Snauw-index {{ incident.severity }}/5</strong>
{% if incident.notes %}
<br><small style="color: #6c757d;">{{ incident.notes[:50] }}{% if incident.notes|length > 50 %}...{% endif %}</small>
{% endif %}
</div>
<small style="color: #6c757d;">
{{ incident.timestamp.strftime('%d/%m %H:%M') }}
</small>
</div>
{% endfor %}
</div>
{% if incidents|length > 10 %}
<p style="margin-top: 1rem; color: #6c757d; text-align: center;">
En {{ incidents|length - 10 }} andere incidenten...
</p>
{% endif %}
{% else %}
<p style="color: #6c757d;">Nog geen incidenten geregistreerd</p>
{% endif %}
</div>
{% else %}
<!-- Geen data yet -->
<div class="card" style="text-align: center; padding: 3rem;">
<h3>📊 Nog geen statistieken</h3>
<p style="color: #6c757d; margin-bottom: 2rem;">
Registreer je eerste incident om statistieken te zien
</p>
<a href="{{ url_for('main.index') }}" class="btn">📝 Eerste incident toevoegen</a>
</div>
{% endif %}
<!-- GDPR compliance section -->
<div class="card" style="border-left: 4px solid #ffc107;">
<h4>🔒 Privacy & Gegevensbeheer</h4>
<p>Je hebt altijd controle over je gegevens:</p>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 1rem;">
<button onclick="exportData()" class="btn btn-secondary">📥 Data exporteren</button>
{% if current_user.is_authenticated %}
<form action="{{ url_for('main.delete_account') }}" method="POST" style="display: inline;"
onsubmit="return confirm('Type DELETE om te bevestigen') && prompt('Type DELETE:') === 'DELETE'">
<input type="hidden" name="confirm" value="DELETE">
<button type="submit" class="btn btn-danger">🗑️ Account verwijderen</button>
</form>
{% else %}
<button onclick="deleteAllData()" class="btn btn-danger">🗑️ Alle data wissen</button>
{% endif %}
</div>
<p style="margin-top: 1rem; font-size: 0.9rem; color: #6c757d;">
<a href="{{ url_for('main.privacy') }}" style="color: inherit;">Privacy beleid</a> |
Conform AVG/GDPR
</p>
</div>
{% endblock %}
{% block scripts %}
<script>
// Mark body as authenticated for JavaScript
document.body.dataset.isAuthenticated = {{ 'true' if current_user.is_authenticated else 'false' }};
{% if stats.total_incidents > 0 %}
// Chart.js implementation
const trendData = {{ stats.trend_data | tojson }};
const ctx = document.getElementById('trendChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: trendData.map(d => d.date),
datasets: [{
label: 'Aantal incidenten',
data: trendData.map(d => d.count),
backgroundColor: 'rgba(102, 126, 234, 0.6)',
borderColor: 'rgba(102, 126, 234, 1)',
borderWidth: 2,
yAxisID: 'y'
}, {
label: 'Gemiddelde Snauw-index',
data: trendData.map(d => d.avg_severity),
type: 'line',
borderColor: 'rgba(220, 53, 69, 1)',
backgroundColor: 'rgba(220, 53, 69, 0.1)',
borderWidth: 3,
fill: false,
yAxisID: 'y1'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: 'Incident Trends (Laatste 7 dagen)'
},
legend: {
display: true
}
},
scales: {
y: {
type: 'linear',
display: true,
position: 'left',
beginAtZero: true,
title: {
display: true,
text: 'Aantal incidenten'
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
min: 0,
max: 5,
title: {
display: true,
text: 'Gemiddelde Snauw-index'
},
grid: {
drawOnChartArea: false,
}
}
},
interaction: {
mode: 'index',
intersect: false,
}
}
});
{% endif %}
// Load anonymous user data if not authenticated
if (!{{ 'true' if current_user.is_authenticated else 'false' }}) {
// This would enhance the statistics with localStorage data
enhanceWithLocalData();
}
function enhanceWithLocalData() {
// For anonymous users, we could enhance server statistics
// with localStorage data that might not be synced yet
const localIncidents = JSON.parse(localStorage.getItem('snauw_incidents') || '[]');
if (localIncidents.length > 0) {
// Add notification about local data
const notice = document.createElement('div');
notice.className = 'alert alert-info';
notice.innerHTML = `
Je hebt ${localIncidents.length} lokaal opgeslagen incidenten.
<a href="{{ url_for('main.register') }}">Maak een account</a> om deze veilig op te slaan.
`;
document.querySelector('main').insertBefore(notice, document.querySelector('main').firstChild);
}
}
</script>
{% endblock %}