initial commit
All checks were successful
Build and Push Image / build-and-push (push) Successful in 1m26s
All checks were successful
Build and Push Image / build-and-push (push) Successful in 1m26s
This commit is contained in:
parent
3bba1f6db6
commit
b56e866071
36 changed files with 4160 additions and 0 deletions
104
app/templates/add_incident.html
Normal file
104
app/templates/add_incident.html
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Incident Toevoegen - Snauw Counter{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<h2>📝 Incident Registreren</h2>
|
||||
<p>Registreer een nieuw incident met alle details.</p>
|
||||
|
||||
<form action="{{ url_for('main.add_incident') }}" method="POST" onsubmit="return validateIncidentForm()">
|
||||
<div class="form-group">
|
||||
<label>Snauw-index (1-5 schaal) *:</label>
|
||||
<div class="severity-selector">
|
||||
{% set snauw_descriptions = [
|
||||
'Lichte correctie',
|
||||
'Geprikkelde waarschuwing',
|
||||
'Openlijke snauw',
|
||||
'Bijtende aanval',
|
||||
'Explosieve uitval'
|
||||
] %}
|
||||
{% for i in range(1, 6) %}
|
||||
<button type="button" class="severity-btn" data-severity="{{ i }}" title="Fase {{ i }}: {{ snauw_descriptions[i-1] }}">
|
||||
{{ i }}
|
||||
</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<input type="hidden" id="severity-input" name="severity" required>
|
||||
|
||||
<!-- Snauw-index uitleg -->
|
||||
<div class="snauw-index-info" style="margin-top: 1rem; font-size: 0.9rem; color: #6c757d;">
|
||||
<details>
|
||||
<summary style="cursor: pointer; font-weight: 500;">📖 Uitleg Snauw-index</summary>
|
||||
<div style="margin-top: 0.5rem; padding: 1rem; background: #f8f9fa; border-radius: 8px;">
|
||||
<div class="snauw-phases">
|
||||
<div style="margin-bottom: 0.5rem;"><strong>Fase 1 - Lichte correctie:</strong><br><small>Licht geïrriteerd, kortaf of subtiel sarcastisch. Nog beheerst en goed bij te sturen.</small></div>
|
||||
<div style="margin-bottom: 0.5rem;"><strong>Fase 2 - Geprikkelde waarschuwing:</strong><br><small>Irritatie wordt uitgesproken, toon scherper. Duidelijk signaal dat grenzen naderen.</small></div>
|
||||
<div style="margin-bottom: 0.5rem;"><strong>Fase 3 - Openlijke snauw:</strong><br><small>Emotie overheerst, stem verheft zich. Verwijtend. Gesprek wordt gespannen.</small></div>
|
||||
<div style="margin-bottom: 0.5rem;"><strong>Fase 4 - Bijtende aanval:</strong><br><small>Persoonlijk, cynisch en scherp. Kans op defensief of escalatie.</small></div>
|
||||
<div style="margin-bottom: 0.5rem;"><strong>Fase 5 - Explosieve uitval:</strong><br><small>Volledige ontlading, hard en fel. Communicatie niet meer mogelijk.</small></div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="incident_time">Tijdstip incident:</label>
|
||||
<input type="datetime-local" id="incident_time" name="incident_time">
|
||||
<script>((f)=>f())(()=>{const d=new Date(),i=document.getElementById('incident_time');if(i)i.value=`${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}T${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`})</script>
|
||||
<small style="color: #6c757d;">Automatisch ingevuld, pas aan indien nodig</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="notes">Beschrijving (optioneel):</label>
|
||||
<textarea id="notes" name="notes" rows="4"
|
||||
placeholder="Wat gebeurde er precies? Wat was de context? Hoe reageerde je?..."></textarea>
|
||||
<small style="color: #6c757d;">Deze informatie kan helpen bij het herkennen van patronen</small>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<button type="submit" class="btn btn-success">💾 Opslaan</button>
|
||||
<a href="{{ url_for('main.index') }}" class="btn btn-secondary">❌ Annuleren</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card" style="background: #f8f9fa; border: 1px dashed #dee2e6;">
|
||||
<h4>💡 Tips voor nauwkeurige registratie</h4>
|
||||
<ul style="margin-left: 1rem; color: #495057;">
|
||||
<li><strong>Snauw-index:</strong> Gebruik de 5-punts schaal consistent</li>
|
||||
<li><strong>Timing:</strong> Registreer zo snel mogelijk na het incident</li>
|
||||
<li><strong>Context:</strong> Noteer relevante omstandigheden (stress, vermoeidheid, etc.)</li>
|
||||
<li><strong>Objectiviteit:</strong> Beschrijf wat er gebeurde, niet hoe je je voelde</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Auto-focus on severity selector for mobile
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Scroll to severity selector for mobile
|
||||
if (window.innerWidth <= 768) {
|
||||
const severitySelector = document.querySelector('.severity-selector');
|
||||
setTimeout(() => {
|
||||
severitySelector.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
// Quick keyboard shortcuts for desktop
|
||||
document.addEventListener('keydown', function(e) {
|
||||
// Numbers 1-5 for Snauw-index
|
||||
if (e.key >= '1' && e.key <= '5') {
|
||||
const severity = parseInt(e.key);
|
||||
const btn = document.querySelector(`[data-severity="${severity}"]`);
|
||||
if (btn) {
|
||||
btn.click();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
225
app/templates/base.html
Normal file
225
app/templates/base.html
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="nl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Snauw Counter{% endblock %}</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<meta name="description" content="Bijhouden van onvriendelijk gedrag - privé en veilig">
|
||||
<meta name="theme-color" content="#667eea">
|
||||
|
||||
<!-- PWA metadata -->
|
||||
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="Snauw Counter">
|
||||
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<header class="header">
|
||||
<div class="container">
|
||||
<!-- Snauw Counter Banner -->
|
||||
<div class="banner-section">
|
||||
<div class="banner-image">
|
||||
<svg class="snauw-banner" viewBox="0 0 1133 576" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Metal frame background -->
|
||||
<defs>
|
||||
<linearGradient id="metalGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#4a4a4a;stop-opacity:1" />
|
||||
<stop offset="50%" style="stop-color:#2c2c2c;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#1a1a1a;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<radialGradient id="boltGrad" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" style="stop-color:#ffffff;stop-opacity:1" />
|
||||
<stop offset="20%" style="stop-color:#e0e0e0;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#a0a0a0;stop-opacity:1" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background frame -->
|
||||
<rect x="20" y="20" width="1093" height="536" fill="url(#metalGrad)" stroke="#666" stroke-width="4" rx="15"/>
|
||||
|
||||
<!-- Corner bolts -->
|
||||
<circle cx="45" cy="45" r="12" fill="url(#boltGrad)" stroke="#333" stroke-width="2"/>
|
||||
<circle cx="45" cy="45" r="8" fill="none" stroke="#222" stroke-width="1"/>
|
||||
<circle cx="45" cy="45" r="4" fill="none" stroke="#222" stroke-width="1"/>
|
||||
|
||||
<circle cx="1088" cy="45" r="12" fill="url(#boltGrad)" stroke="#333" stroke-width="2"/>
|
||||
<circle cx="1088" cy="45" r="8" fill="none" stroke="#222" stroke-width="1"/>
|
||||
<circle cx="1088" cy="45" r="4" fill="none" stroke="#222" stroke-width="1"/>
|
||||
|
||||
<circle cx="45" cy="531" r="12" fill="url(#boltGrad)" stroke="#333" stroke-width="2"/>
|
||||
<circle cx="45" cy="531" r="8" fill="none" stroke="#222" stroke-width="1"/>
|
||||
<circle cx="45" cy="531" r="4" fill="none" stroke="#222" stroke-width="1"/>
|
||||
|
||||
<circle cx="1088" cy="531" r="12" fill="url(#boltGrad)" stroke="#333" stroke-width="2"/>
|
||||
<circle cx="1088" cy="531" r="8" fill="none" stroke="#222" stroke-width="1"/>
|
||||
<circle cx="1088" cy="531" r="4" fill="none" stroke="#222" stroke-width="1"/>
|
||||
|
||||
<!-- Dark stormy background -->
|
||||
<rect x="40" y="40" width="1053" height="496" fill="#1a1a2e" rx="10"/>
|
||||
|
||||
<!-- Storm clouds -->
|
||||
<ellipse cx="150" cy="100" rx="60" ry="30" fill="#3d3d5c" opacity="0.8"/>
|
||||
<ellipse cx="180" cy="85" rx="45" ry="25" fill="#4a4a6b" opacity="0.7"/>
|
||||
<ellipse cx="950" cy="120" rx="70" ry="35" fill="#3d3d5c" opacity="0.8"/>
|
||||
<ellipse cx="980" cy="105" rx="50" ry="28" fill="#4a4a6b" opacity="0.7"/>
|
||||
<ellipse cx="500" cy="80" rx="40" ry="20" fill="#4a4a6b" opacity="0.6"/>
|
||||
|
||||
<!-- Lightning effects -->
|
||||
<path d="M 120 150 L 140 180 L 130 180 L 150 220 L 135 200 L 145 200 L 125 170 Z" fill="#f1c40f" opacity="0.9"/>
|
||||
<path d="M 980 160 L 1000 190 L 990 190 L 1010 230 L 995 210 L 1005 210 L 985 180 Z" fill="#f1c40f" opacity="0.9"/>
|
||||
|
||||
<!-- Swear symbols -->
|
||||
<text x="180" y="250" fill="#e74c3c" font-size="32" font-weight="bold" font-family="Arial, sans-serif">@#$!?!</text>
|
||||
|
||||
<!-- Main title banner -->
|
||||
<path d="M 300 180 L 833 180 L 853 220 L 833 260 L 300 260 L 280 220 Z" fill="#d35400" stroke="#a04000" stroke-width="3"/>
|
||||
<path d="M 305 185 L 828 185 L 845 220 L 828 255 L 305 255 L 285 220 Z" fill="#e67e22"/>
|
||||
|
||||
<text x="566" y="235" text-anchor="middle" fill="white" font-size="42" font-weight="bold" font-family="Arial, sans-serif" text-shadow="2px 2px 4px rgba(0,0,0,0.5)">SNAUW COUNTER!</text>
|
||||
|
||||
<!-- Counter display -->
|
||||
<rect x="410" y="350" width="313" height="80" fill="#2c3e50" stroke="#ecf0f1" stroke-width="4" rx="8"/>
|
||||
<rect x="418" y="358" width="297" height="64" fill="#34495e" rx="4"/>
|
||||
|
||||
<text x="440" y="385" fill="#bdc3c7" font-size="24" font-weight="bold" font-family="Arial, sans-serif">STAND:</text>
|
||||
|
||||
<!-- Individual counter digits -->
|
||||
<rect x="520" y="368" width="50" height="48" fill="#1a1a1a" stroke="#ecf0f1" stroke-width="2" rx="4"/>
|
||||
<text x="545" y="402" text-anchor="middle" fill="#e74c3c" font-size="36" font-weight="bold" id="counter-digit-1">0</text>
|
||||
|
||||
<rect x="580" y="368" width="50" height="48" fill="#1a1a1a" stroke="#ecf0f1" stroke-width="2" rx="4"/>
|
||||
<text x="605" y="402" text-anchor="middle" fill="#e74c3c" font-size="36" font-weight="bold" id="counter-digit-2">1</text>
|
||||
|
||||
<rect x="640" y="368" width="50" height="48" fill="#1a1a1a" stroke="#ecf0f1" stroke-width="2" rx="4"/>
|
||||
<text x="665" y="402" text-anchor="middle" fill="#e74c3c" font-size="36" font-weight="bold" id="counter-digit-3">5</text>
|
||||
|
||||
<!-- Woman (angry, left side) -->
|
||||
<!-- Hair -->
|
||||
<path d="M 150 280 Q 120 270 110 300 Q 105 330 115 350 Q 125 360 140 365 Q 160 370 180 365 Q 200 360 210 350 Q 220 330 215 300 Q 205 270 175 280" fill="#8B4513" stroke="#654321" stroke-width="2"/>
|
||||
|
||||
<!-- Face -->
|
||||
<ellipse cx="162" cy="320" rx="35" ry="40" fill="#FDBCB4" stroke="#E6A8A0" stroke-width="2"/>
|
||||
|
||||
<!-- Eyes (angry) -->
|
||||
<ellipse cx="150" cy="310" rx="8" ry="6" fill="white"/>
|
||||
<ellipse cx="174" cy="310" rx="8" ry="6" fill="white"/>
|
||||
<circle cx="150" cy="310" r="4" fill="#2c3e50"/>
|
||||
<circle cx="174" cy="310" r="4" fill="#2c3e50"/>
|
||||
|
||||
<!-- Angry eyebrows -->
|
||||
<path d="M 140 300 L 158 305" stroke="#8B4513" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M 166 305 L 184 300" stroke="#8B4513" stroke-width="3" stroke-linecap="round"/>
|
||||
|
||||
<!-- Nose -->
|
||||
<ellipse cx="162" cy="325" rx="3" ry="5" fill="#E6A8A0"/>
|
||||
|
||||
<!-- Mouth (shouting) -->
|
||||
<ellipse cx="162" cy="340" rx="12" ry="8" fill="#8B0000"/>
|
||||
<ellipse cx="162" cy="340" rx="10" ry="6" fill="#000000"/>
|
||||
|
||||
<!-- Pointing finger -->
|
||||
<rect x="200" y="335" width="25" height="8" fill="#FDBCB4" rx="4"/>
|
||||
<ellipse cx="228" cy="339" rx="6" ry="4" fill="#FDBCB4"/>
|
||||
|
||||
<!-- Arm -->
|
||||
<ellipse cx="190" cy="360" rx="15" ry="30" fill="#2D4A22" transform="rotate(20 190 360)"/>
|
||||
|
||||
<!-- Body -->
|
||||
<ellipse cx="162" cy="420" rx="40" ry="60" fill="#2D4A22"/>
|
||||
|
||||
<!-- Man (worried, right side) -->
|
||||
<!-- Hair -->
|
||||
<path d="M 920 280 Q 890 270 880 300 Q 875 320 885 340 Q 900 355 920 360 Q 945 365 970 360 Q 990 355 1005 340 Q 1015 320 1010 300 Q 1000 270 970 280" fill="#D2691E" stroke="#A0522D" stroke-width="2"/>
|
||||
|
||||
<!-- Face -->
|
||||
<ellipse cx="950" cy="320" rx="40" ry="45" fill="#FDBCB4" stroke="#E6A8A0" stroke-width="2"/>
|
||||
|
||||
<!-- Eyes (worried) -->
|
||||
<ellipse cx="935" cy="310" rx="9" ry="7" fill="white"/>
|
||||
<ellipse cx="965" cy="310" rx="9" ry="7" fill="white"/>
|
||||
<circle cx="935" cy="312" r="4" fill="#2c3e50"/>
|
||||
<circle cx="965" cy="312" r="4" fill="#2c3e50"/>
|
||||
|
||||
<!-- Worried eyebrows -->
|
||||
<path d="M 925 300 Q 935 295 945 300" stroke="#D2691E" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M 955 300 Q 965 295 975 300" stroke="#D2691E" stroke-width="3" stroke-linecap="round"/>
|
||||
|
||||
<!-- Nose -->
|
||||
<ellipse cx="950" cy="325" rx="3" ry="5" fill="#E6A8A0"/>
|
||||
|
||||
<!-- Mouth (worried) -->
|
||||
<path d="M 935 340 Q 950 350 965 340" stroke="#8B0000" stroke-width="3" fill="none" stroke-linecap="round"/>
|
||||
|
||||
<!-- Hand on head -->
|
||||
<ellipse cx="920" cy="300" rx="8" ry="12" fill="#FDBCB4" transform="rotate(-30 920 300)"/>
|
||||
<rect x="910" y="290" width="20" height="8" fill="#FDBCB4" rx="4" transform="rotate(-30 920 294)"/>
|
||||
|
||||
<!-- Arm -->
|
||||
<ellipse cx="925" cy="350" rx="12" ry="25" fill="#1E3A8A" transform="rotate(-20 925 350)"/>
|
||||
|
||||
<!-- Body/shirt -->
|
||||
<ellipse cx="950" cy="420" rx="45" ry="65" fill="#1E3A8A"/>
|
||||
|
||||
<!-- Update header counter with JavaScript hook -->
|
||||
<text x="566" y="500" text-anchor="middle" fill="transparent" font-size="1" id="header-counter">015</text>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav">
|
||||
<a href="{{ url_for('main.index') }}" {% if request.endpoint == 'main.index' %}class="active"{% endif %}>
|
||||
🏠 Home
|
||||
</a>
|
||||
<a href="{{ url_for('main.statistics') }}" {% if request.endpoint == 'main.statistics' %}class="active"{% endif %}>
|
||||
📈 Statistieken
|
||||
</a>
|
||||
{% if current_user.is_authenticated %}
|
||||
<a href="{{ url_for('main.logout') }}">🚪 Uitloggen</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('main.login') }}" {% if request.endpoint == 'main.login' %}class="active"{% endif %}>
|
||||
🔐 Inloggen
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<!-- Flash messages -->
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="flash-messages">
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ 'error' if category == 'error' else category }}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<!-- Privacy notice voor anonieme gebruikers -->
|
||||
{% if not current_user.is_authenticated %}
|
||||
<div class="privacy-notice" id="privacy-notice">
|
||||
<strong>🔒 Privacy:</strong> Als anonieme gebruiker worden je gegevens alleen lokaal opgeslagen in je browser.
|
||||
<a href="{{ url_for('main.register') }}" style="color: #856404; text-decoration: underline;">Maak een account</a>
|
||||
om je gegevens veilig op te slaan.
|
||||
<button onclick="hidePrivacyNotice()" style="float: right; background: none; border: none; color: #856404; cursor: pointer;">✕</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<!-- Floating Action Button voor snelle incident toevoeging -->
|
||||
{% if request.endpoint != 'main.add_incident' %}
|
||||
<button class="fab" onclick="quickAddIncident()" title="Snel incident toevoegen">+</button>
|
||||
{% endif %}
|
||||
|
||||
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
138
app/templates/index.html
Normal file
138
app/templates/index.html
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Home - Snauw Counter{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<h2>🔥 Nieuw incident registreren</h2>
|
||||
<p>Houd bij wanneer er je partner weer eens tegen je snauwt!.</p>
|
||||
|
||||
<form action="{{ url_for('main.add_incident') }}" method="POST" onsubmit="return validateIncidentForm()">
|
||||
<div class="form-group">
|
||||
<label for="severity">Snauw-index (1-5 schaal):</label>
|
||||
<div class="severity-selector">
|
||||
{% set snauw_descriptions = [
|
||||
'Lichte correctie',
|
||||
'Geprikkelde waarschuwing',
|
||||
'Openlijke snauw',
|
||||
'Bijtende aanval',
|
||||
'Explosieve uitval'
|
||||
] %}
|
||||
{% for i in range(1, 6) %}
|
||||
<button type="button" class="severity-btn" data-severity="{{ i }}" title="Fase {{ i }}: {{ snauw_descriptions[i-1] }}">
|
||||
{{ i }}
|
||||
</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<input type="hidden" id="severity-input" name="severity" required>
|
||||
|
||||
<!-- Snauw-index uitleg -->
|
||||
<div class="snauw-index-info" style="margin-top: 1rem; font-size: 0.9rem; color: #6c757d;">
|
||||
<details>
|
||||
<summary style="cursor: pointer; font-weight: 500;">📖 Uitleg Snauw-index</summary>
|
||||
<div style="margin-top: 0.5rem; padding: 1rem; background: #f8f9fa; border-radius: 8px;">
|
||||
<div class="snauw-phases">
|
||||
<div><strong>Fase 1 - Lichte correctie:</strong><br>Licht geïrriteerd, kortaf of subtiel sarcastisch. Nog beheerst en goed bij te sturen.</div>
|
||||
<div><strong>Fase 2 - Geprikkelde waarschuwing:</strong><br>Irritatie wordt uitgesproken, toon scherper. Duidelijk signaal dat grenzen naderen.</div>
|
||||
<div><strong>Fase 3 - Openlijke snauw:</strong><br>Emotie overheerst, stem verheft zich. Verwijtend. Gesprek wordt gespannen.</div>
|
||||
<div><strong>Fase 4 - Bijtende aanval:</strong><br>Persoonlijk, cynisch en scherp. Kans op defensief of escalatie.</div>
|
||||
<div><strong>Fase 5 - Explosieve uitval:</strong><br>Volledige ontlading, hard en fel. Communicatie niet meer mogelijk.</div>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="incident_time">Tijdstip incident:</label>
|
||||
<input type="datetime-local" id="incident_time" name="incident_time">
|
||||
<script>((f)=>f())(()=>{const d=new Date(),i=document.getElementById('incident_time');if(i)i.value=`${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}T${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`})</script>
|
||||
<small style="color: #6c757d;">Automatisch ingevuld, pas aan indien nodig</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="notes">Wat gebeurde er? (optioneel):</label>
|
||||
<textarea id="notes" name="notes" placeholder="Beschrijf kort wat er gebeurde..."></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn">📝 Incident opslaan</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>📊 Snelle statistieken</h3>
|
||||
{% if current_user.is_authenticated %}
|
||||
<p>Bekijk je <a href="{{ url_for('main.statistics') }}">volledige statistieken</a> om trends te zien.</p>
|
||||
{% else %}
|
||||
<p>Je gegevens worden <strong>alleen lokaal</strong> opgeslagen. <a href="{{ url_for('main.register') }}">Maak een account</a> om je gegevens veilig te bewaren.</p>
|
||||
{% endif %}
|
||||
|
||||
<div id="quick-stats">
|
||||
<!-- Quick stats will be loaded here via JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if not current_user.is_authenticated %}
|
||||
<div class="card" style="border-left: 4px solid #28a745;">
|
||||
<h4>🔒 Privacybescherming</h4>
|
||||
<ul style="margin-left: 1rem;">
|
||||
<li>✅ Geen persoonlijke gegevens vereist</li>
|
||||
<li>✅ Data blijft op jouw apparaat</li>
|
||||
<li>✅ Geen tracking of analytics</li>
|
||||
<li>✅ Optioneel account voor sync tussen apparaten</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Load quick stats
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadQuickStats();
|
||||
});
|
||||
|
||||
function loadQuickStats() {
|
||||
const statsDiv = document.getElementById('quick-stats');
|
||||
|
||||
if ({{ 'true' if current_user.is_authenticated else 'false' }}) {
|
||||
// For authenticated users - would make API call
|
||||
statsDiv.innerHTML = '<p>📈 Laad volleidig statistieken pagina voor details</p>';
|
||||
} else {
|
||||
// For anonymous users - use localStorage
|
||||
const incidents = JSON.parse(localStorage.getItem('snauw_incidents') || '[]');
|
||||
|
||||
if (incidents.length === 0) {
|
||||
statsDiv.innerHTML = '<p style="color: #6c757d;">Nog geen incidenten geregistreerd</p>';
|
||||
} else {
|
||||
const today = new Date().toDateString();
|
||||
const todayIncidents = incidents.filter(inc =>
|
||||
new Date(inc.timestamp).toDateString() === today
|
||||
);
|
||||
|
||||
const avgSeverity = incidents.reduce((sum, inc) => sum + inc.severity, 0) / incidents.length;
|
||||
|
||||
statsDiv.innerHTML = `
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<span class="stat-number">${incidents.length}</span>
|
||||
<span class="stat-label">Totaal incidenten</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-number">${todayIncidents.length}</span>
|
||||
<span class="stat-label">Vandaag</span>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<span class="stat-number">${avgSeverity.toFixed(1)}</span>
|
||||
<span class="stat-label">Gem. Snauw-index</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark body as authenticated or not for JavaScript
|
||||
document.body.dataset.isAuthenticated = {{ 'true' if current_user.is_authenticated else 'false' }};
|
||||
</script>
|
||||
{% endblock %}
|
||||
41
app/templates/login.html
Normal file
41
app/templates/login.html
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Inloggen - Snauw Counter{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<h2>🔐 Inloggen</h2>
|
||||
<p>Log in om je gegevens veilig op te slaan en te synchroniseren tussen apparaten.</p>
|
||||
|
||||
<form action="{{ url_for('main.login') }}" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="email">E-mailadres:</label>
|
||||
<input type="email" id="email" name="email" required
|
||||
value="{{ request.form.email if request.form.email }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Wachtwoord:</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn">🚪 Inloggen</button>
|
||||
</form>
|
||||
|
||||
<p style="margin-top: 1.5rem; text-align: center; color: #6c757d;">
|
||||
Nog geen account?
|
||||
<a href="{{ url_for('main.register') }}" style="color: #667eea; text-decoration: none; font-weight: 500;">Registreren</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card" style="border-left: 4px solid #28a745;">
|
||||
<h4>✨ Voordelen van een account</h4>
|
||||
<ul style="margin-left: 1rem;">
|
||||
<li>🔄 Synchroniseer tussen meerdere apparaten</li>
|
||||
<li>☁️ Veilige opslag in de cloud</li>
|
||||
<li>📊 Uitgebreidere statistieken en trends</li>
|
||||
<li>💾 Automatische backup van je gegevens</li>
|
||||
<li>🔒 End-to-end versleuteling</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock %}
|
||||
185
app/templates/privacy.html
Normal file
185
app/templates/privacy.html
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Privacy Beleid - Snauw Counter{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<h1>🔒 Privacy Beleid</h1>
|
||||
<p><em>Laatst bijgewerkt: 9 januari 2026</em></p>
|
||||
|
||||
<h2>Onze Inzet voor Privacy</h2>
|
||||
<p>Snauw Counter is ontworpen met privacy als uitgangspunt. We begrijpen dat de gegevens die je met ons deelt gevoelig zijn, en we nemen de bescherming ervan zeer serieus.</p>
|
||||
|
||||
<h2>Welke Gegevens Verzamelen We?</h2>
|
||||
|
||||
<h3>Voor Anonieme Gebruikers</h3>
|
||||
<ul>
|
||||
<li>✅ <strong>Lokale opslag</strong>: Incidenten worden alleen opgeslagen in je browser (localStorage)</li>
|
||||
<li>✅ <strong>Geen server opslag</strong>: Je gegevens verlaten je apparaat niet</li>
|
||||
<li>✅ <strong>Geen tracking</strong>: We volgen geen gebruikersgedrag</li>
|
||||
<li>✅ <strong>Geen analytics</strong>: Geen Google Analytics of vergelijkbare diensten</li>
|
||||
</ul>
|
||||
|
||||
<h3>Voor Geregistreerde Gebruikers</h3>
|
||||
<ul>
|
||||
<li>📧 <strong>E-mailadres</strong>: Voor account identificatie</li>
|
||||
<li>🔐 <strong>Gehashed wachtwoord</strong>: Veilig opgeslagen (niet leesbaar)</li>
|
||||
<li>📊 <strong>Incident gegevens</strong>: Datum/tijd, ernstscore, opmerkingen</li>
|
||||
<li>🕒 <strong>Account metadata</strong>: Aanmaakdatum, laatste login</li>
|
||||
</ul>
|
||||
|
||||
<h2>Hoe Gebruiken We Je Gegevens?</h2>
|
||||
<ul>
|
||||
<li>📈 <strong>Statistieken tonen</strong>: Om je persoonlijke trends te berekenen</li>
|
||||
<li>🔐 <strong>Account beheer</strong>: Voor inloggen en authenticatie</li>
|
||||
<li>💾 <strong>Data synchronisatie</strong>: Tussen je verschillende apparaten</li>
|
||||
<li>🛡️ <strong>Beveiliging</strong>: Om je account te beschermen</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>We delen NOOIT je gegevens met derden voor marketing of commerciële doeleinden.</strong></p>
|
||||
|
||||
<h2>Gegevensbeveiliging</h2>
|
||||
<ul>
|
||||
<li>🔒 <strong>Versleuteling</strong>: Alle gevoelige gegevens worden versleuteld opgeslagen</li>
|
||||
<li>🛡️ <strong>Beveiligde verbindingen</strong>: HTTPS voor alle communicatie</li>
|
||||
<li>⚡ <strong>Beperkte toegang</strong>: Alleen essentiële systemen hebben toegang</li>
|
||||
<li>🔄 <strong>Reguliere backups</strong>: Met versleuteling voor disaster recovery</li>
|
||||
</ul>
|
||||
|
||||
<h2>Je Rechten (GDPR/AVG)</h2>
|
||||
|
||||
<div style="background: #e3f2fd; padding: 1rem; border-radius: 8px; border-left: 4px solid #2196f3; margin: 1rem 0;">
|
||||
<h4>🇪🇺 Je GDPR Rechten:</h4>
|
||||
<ul>
|
||||
<li><strong>Recht op informatie</strong>: Dit privacy beleid</li>
|
||||
<li><strong>Recht op toegang</strong>: Inzage in je opgeslagen gegevens</li>
|
||||
<li><strong>Recht op correctie</strong>: Wijzigen van onjuiste gegevens</li>
|
||||
<li><strong>Recht op verwijdering</strong>: "Recht om vergeten te worden"</li>
|
||||
<li><strong>Recht op overdraagbaarheid</strong>: Export van je gegevens</li>
|
||||
<li><strong>Recht op bezwaar</strong>: Tegen verwerking van je gegevens</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h3>Hoe Oefen Je Deze Rechten Uit?</h3>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<div class="stats-grid" style="margin: 1rem 0;">
|
||||
<button onclick="window.location.href='{{ url_for('main.export_data') }}'" class="btn btn-secondary">
|
||||
📥 Gegevens Exporteren
|
||||
</button>
|
||||
<button onclick="showDeleteConfirmation()" class="btn btn-danger">
|
||||
🗑️ Account Verwijderen
|
||||
</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="stats-grid" style="margin: 1rem 0;">
|
||||
<button onclick="exportData()" class="btn btn-secondary">
|
||||
📥 Lokale Gegevens Exporteren
|
||||
</button>
|
||||
<button onclick="deleteAllData()" class="btn btn-danger">
|
||||
🗑️ Lokale Gegevens Wissen
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h2>Cookies & Lokale Opslag</h2>
|
||||
|
||||
<h3>Essentiële Cookies (Altijd Actief)</h3>
|
||||
<ul>
|
||||
<li>🍪 <strong>Sessie cookie</strong>: Voor ingelogde gebruikers (session management)</li>
|
||||
<li>📱 <strong>Voorkeuren</strong>: UI instellingen en privacy notice status</li>
|
||||
</ul>
|
||||
|
||||
<h3>Lokale Browser Opslag</h3>
|
||||
<ul>
|
||||
<li>💾 <strong>localStorage</strong>: Voor anonieme gebruikers om incidenten bij te houden</li>
|
||||
<li>⚙️ <strong>App instellingen</strong>: Gebruikersvoorkeuren en UI state</li>
|
||||
</ul>
|
||||
|
||||
<p style="background: #fff3cd; padding: 1rem; border-radius: 8px; border: 1px solid #ffeaa7;">
|
||||
<strong>⚠️ Let op:</strong> Als je lokale browser gegevens wist (cache/cookies leegmaken),
|
||||
verlies je als anonieme gebruiker al je opgeslagen incidenten.
|
||||
<a href="{{ url_for('main.register') }}">Maak een account</a> om dit te voorkomen.
|
||||
</p>
|
||||
|
||||
<h2>Gegevensretentie</h2>
|
||||
<ul>
|
||||
<li>📊 <strong>Incident gegevens</strong>: Onbeperkt (of tot je account verwijdert)</li>
|
||||
<li>🔐 <strong>Account gegevens</strong>: Tot account verwijdering</li>
|
||||
<li>💾 <strong>Backups</strong>: Maximaal 90 dagen na verwijdering</li>
|
||||
<li>📱 <strong>Lokale gegevens</strong>: Tot je ze handmatig wist of browser cache leegt</li>
|
||||
</ul>
|
||||
|
||||
<h2>Contact & Vragen</h2>
|
||||
<p>Heb je vragen over je privacy of dit beleid? </p>
|
||||
|
||||
<div style="background: #f8f9fa; padding: 1rem; border-radius: 8px; border: 1px solid #dee2e6;">
|
||||
<p><strong>📧 Data Protection Officer:</strong></p>
|
||||
<p>E-mail: privacy@snauwcounter.app<br>
|
||||
Reactietijd: Binnen 72 uur</p>
|
||||
|
||||
<p><strong>🏢 Verwerkingsverantwoordelijke:</strong></p>
|
||||
<p>Snauw Counter Development Team<br>
|
||||
Nederland</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" style="background: linear-gradient(135deg, #28a745 0%, #20c997 100%); color: white;">
|
||||
<h3>✅ Privacy First Belofte</h3>
|
||||
<p>Snauw Counter is gebouwd vanuit de overtuiging dat privacy een fundamenteel recht is. We verzamelen alleen wat noodzakelijk is, beveiligen alles wat we opslaan, en geven jou altijd volledige controle over je gegevens.</p>
|
||||
|
||||
<p><strong>Jouw vertrouwen is onze verantwoordelijkheid.</strong></p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
function showDeleteConfirmation() {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'modal';
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3>⚠️ Account Verwijderen</h3>
|
||||
<button onclick="closeModal()" class="close-btn">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p><strong>Dit is permanent en kan niet ongedaan worden gemaakt.</strong></p>
|
||||
<p>We zullen verwijderen:</p>
|
||||
<ul>
|
||||
<li>✅ Je account en login gegevens</li>
|
||||
<li>✅ Alle geregistreerde incidenten</li>
|
||||
<li>✅ Alle statistieken en trends</li>
|
||||
<li>✅ Backups binnen 90 dagen</li>
|
||||
</ul>
|
||||
|
||||
<form action="{{ url_for('main.delete_account') }}" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="confirm-delete">Type "DELETE" om te bevestigen:</label>
|
||||
<input type="text" id="confirm-delete" name="confirm" required
|
||||
placeholder="DELETE" style="text-transform: uppercase;">
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 0.5rem; margin-top: 1rem;">
|
||||
<button type="submit" class="btn btn-danger">🗑️ Permanent Verwijderen</button>
|
||||
<button type="button" onclick="closeModal()" class="btn btn-secondary">Annuleren</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
const modal = document.querySelector('.modal');
|
||||
if (modal) {
|
||||
modal.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Mark body as authenticated for JavaScript
|
||||
document.body.dataset.isAuthenticated = {{ 'true' if current_user.is_authenticated else 'false' }};
|
||||
</script>
|
||||
{% endblock %}
|
||||
97
app/templates/register.html
Normal file
97
app/templates/register.html
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Registreren - Snauw Counter{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<h2>👤 Account aanmaken</h2>
|
||||
<p>Maak een gratis account om je gegevens veilig op te slaan.</p>
|
||||
|
||||
<form action="{{ url_for('main.register') }}" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="email">E-mailadres:</label>
|
||||
<input type="email" id="email" name="email" required
|
||||
value="{{ request.form.email if request.form.email }}">
|
||||
<small style="color: #6c757d;">We sturen nooit spam en delen je e-mail niet</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Wachtwoord:</label>
|
||||
<input type="password" id="password" name="password" required minlength="6">
|
||||
<small style="color: #6c757d;">Minimaal 6 karakters</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password_confirm">Bevestig wachtwoord:</label>
|
||||
<input type="password" id="password_confirm" name="password_confirm" required minlength="6">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label style="display: flex; align-items: center; font-weight: normal;">
|
||||
<input type="checkbox" required style="margin-right: 0.5rem; width: auto;">
|
||||
Ik ga akkoord met het <a href="{{ url_for('main.privacy') }}" target="_blank" style="color: #667eea;">privacy beleid</a>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn">✨ Account aanmaken</button>
|
||||
</form>
|
||||
|
||||
<p style="margin-top: 1.5rem; text-align: center; color: #6c757d;">
|
||||
Al een account?
|
||||
<a href="{{ url_for('main.login') }}" style="color: #667eea; text-decoration: none; font-weight: 500;">Inloggen</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card" style="border-left: 4px solid #17a2b8;">
|
||||
<h4>🔒 Privacy & Beveiliging</h4>
|
||||
<ul style="margin-left: 1rem;">
|
||||
<li>✅ Minimale gegevensverzameling</li>
|
||||
<li>✅ Sterke wachtwoord versleuteling</li>
|
||||
<li>✅ Geen tracking of advertenties</li>
|
||||
<li>✅ GDPR/AVG compliant</li>
|
||||
<li>✅ Data export en verwijdering mogelijk</li>
|
||||
<li>✅ Optionele anonieme modus beschikbaar</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% if request.endpoint == 'main.register' %}
|
||||
<div class="card" id="migration-info" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; display: none;">
|
||||
<h4>💾 Data migratie</h4>
|
||||
<p>We hebben lokale gegevens gevonden! Na registratie migreren we automatisch je bestaande incidenten naar je nieuwe account.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Check for local data and show migration info
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const localIncidents = localStorage.getItem('snauw_incidents');
|
||||
if (localIncidents) {
|
||||
const incidents = JSON.parse(localIncidents);
|
||||
if (incidents.length > 0) {
|
||||
const migrationInfo = document.getElementById('migration-info');
|
||||
if (migrationInfo) {
|
||||
migrationInfo.style.display = 'block';
|
||||
migrationInfo.innerHTML = `
|
||||
<h4>💾 Data migratie</h4>
|
||||
<p>We hebben ${incidents.length} lokaal opgeslagen incidenten gevonden! Na registratie migreren we deze automatisch naar je nieuwe account.</p>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Password confirmation validation
|
||||
const password = document.getElementById('password');
|
||||
const passwordConfirm = document.getElementById('password_confirm');
|
||||
|
||||
passwordConfirm.addEventListener('input', function() {
|
||||
if (password.value !== passwordConfirm.value) {
|
||||
passwordConfirm.setCustomValidity('Wachtwoorden komen niet overeen');
|
||||
} else {
|
||||
passwordConfirm.setCustomValidity('');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
223
app/templates/statistics.html
Normal file
223
app/templates/statistics.html
Normal 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 %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue