Deze deploy doet het
Deployment successfully updated.
Version 48 on Sep 19, 2026, 11:41 AM
Deployment ID
AKfycbxlwcsO5gG93zdxB6_bnH2zpTjAM38hNJZ3_OOCFjlWsva9yBKtEt-Uc_LD8sGhpovolQ
Web app
URL
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>G20 — Dagelijks Geluksbesef | NextLevel Coaching by Gabeler.eu</title>
<style>
:root {
--primary: #1e3a8a;
--primary-light: #3b82f6;
--accent: #10b981;
--bg: #f8fafc;
--card-bg: #ffffff;
--text: #1e293b;
--text-light: #64748b;
--border: #e2e8f0;
--radius: 12px;
}
* { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
body { background-color: var(--bg); color: var(--text); min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; padding: 20px; }
.container { width: 100%; max-width: 800px; background: var(--card-bg); border-radius: var(--radius); box-shadow: 0 10px 25px -5px rgba(0,0,0,0.05), 0 8px 10px -6px rgba(0,0,0,0.05); padding: 30px; margin-top: 20px; margin-bottom: 40px; }
header { text-align: center; margin-bottom: 30px; border-bottom: 2px solid var(--border); padding-bottom: 20px; }
header h1 { color: var(--primary); font-size: 1.8rem; margin-bottom: 5px; }
header p { color: var(--text-light); font-size: 0.95rem; }
.header-links { margin-top: 10px; font-size: 0.9rem; }
.header-links a { color: var(--primary-light); text-decoration: none; font-weight: 600; margin: 0 8px; }
.header-links a:hover { text-decoration: underline; }
.screen { display: none; }
.screen.active { display: block; animation: fadeIn 0.3s ease-in-out; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
h2 { color: var(--primary); font-size: 1.4rem; margin-bottom: 15px; }
p.sub { color: var(--text-light); font-size: 0.9rem; margin-bottom: 20px; }
.form-group { margin-bottom: 15px; }
label { display: block; font-weight: 600; font-size: 0.85rem; margin-bottom: 5px; color: var(--text); }
input, select, textarea { width: 100%; padding: 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 0.95rem; outline: none; transition: border-color 0.2s; }
input:focus, select:focus, textarea:focus { border-color: var(--primary-light); }
/* Wachtwoord wrapper met oogje */
.password-wrapper { position: relative; display: flex; align-items: center; }
.password-wrapper input { padding-right: 45px; }
.toggle-password { position: absolute; right: 12px; background: none; border: none; cursor: pointer; font-size: 1.2rem; color: var(--text-light); padding: 4px; }
.toggle-password:hover { color: var(--primary); }
.btn { display: inline-block; width: 100%; background: var(--primary); color: white; border: none; padding: 12px 20px; font-size: 1rem; font-weight: 600; border-radius: 8px; cursor: pointer; text-align: center; transition: background 0.2s; margin-top: 10px; }
.btn:hover { background: #1d4ed8; }
.btn-secondary { background: #64748b; }
.btn-secondary:hover { background: #475569; }
.btn-accent { background: var(--accent); }
.btn-accent:hover { background: #059669; }
.link-text { text-align: center; margin-top: 15px; font-size: 0.9rem; color: var(--text-light); }
.link-text a { color: var(--primary-light); text-decoration: none; font-weight: 600; cursor: pointer; }
.link-text a:hover { text-decoration: underline; }
/* Navbar & Menu */
.navbar { display: flex; justify-content: space-between; align-items: center; background: var(--primary); color: white; padding: 12px 20px; border-radius: 8px; margin-bottom: 25px; }
.navbar span { font-weight: 600; font-size: 0.9rem; }
.navbar button { background: rgba(255,255,255,0.2); border: none; color: white; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 0.85rem; }
.navbar button:hover { background: rgba(255,255,255,0.3); }
.menu-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 15px; margin-bottom: 25px; }
.menu-card { background: var(--bg); border: 1px solid var(--border); padding: 20px; border-radius: 8px; text-align: center; cursor: pointer; transition: all 0.2s; }
.menu-card:hover { border-color: var(--primary-light); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.05); }
.menu-card h3 { color: var(--primary); font-size: 1.1rem; margin-bottom: 5px; }
.menu-card p { font-size: 0.85rem; color: var(--text-light); }
/* G20 Form Fields */
.g20-item { background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 15px; margin-bottom: 15px; }
.g20-item-header { display: flex; align-items: center; margin-bottom: 8px; }
.g20-emoji { font-size: 1.4rem; margin-right: 10px; }
.g20-title { font-weight: 600; font-size: 1rem; color: var(--primary); }
.g20-question { font-size: 0.9rem; color: var(--text); margin-bottom: 10px; font-style: italic; }
/* Overzicht / Geschiedenis */
.history-card { background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 15px; margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center; }
.history-info h4 { color: var(--primary); font-size: 1rem; margin-bottom: 4px; }
.history-info p { font-size: 0.85rem; color: var(--text-light); }
.spinner { display: none; text-align: center; margin: 15px 0; color: var(--primary-light); font-weight: 600; }
.alert { padding: 12px; border-radius: 8px; margin-bottom: 15px; font-size: 0.9rem; display: none; }
.alert-success { background: #d1fae5; color: #065f46; border: 1px solid #a7f3d0; }
.alert-error { background: #fee2e2; color: #991b1b; border: 1px solid #fecaca; }
footer { text-align: center; margin-top: auto; font-size: 0.8rem; color: var(--text-light); padding: 20px; }
footer a { color: var(--primary-light); text-decoration: none; }
footer a:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="container">
<header>
<h1>G20 — Dagelijks Geluksbesef</h1>
<p>Coaching by Gabeler.eu | Coach Stijn Gabeler</p>
<div class="header-links">
<a href="https://sites.google.com/view/gabeler" target="_blank">Gabeler.eu</a> |
<a href="https://sites.google.com/view/coachstijn" target="_blank">Coach Stijn</a>
</div>
</header>
<div id="alertBox" class="alert"></div>
<div id="spinner" class="spinner">Even geduld, bezig met verwerken...</div>
<!-- SCHERM 1: LOGIN -->
<div id="screen-login" class="screen active">
<h2>Inloggen</h2>
<p class="sub">Log in met je e-mailadres en gekozen wachtwoord.</p>
<div class="form-group">
<label>E-mailadres</label>
<input type="email" id="loginEmail" placeholder="jouw@email.nl">
</div>
<div class="form-group">
<label>Wachtwoord</label>
<div class="password-wrapper">
<input type="password" id="loginPassword" placeholder="••••••••">
<button type="button" class="toggle-password" onclick="togglePasswordVisibility('loginPassword', this)" title="Toon/verberg wachtwoord">👁️</button>
</div>
</div>
<button type="button" class="btn" onclick="handleLogin()">Inloggen</button>
<div class="link-text">
Nog geen account? <a onclick="showScreen('screen-register-req')">Registreer hier (Gratis voorproefje)</a><br>
<a onclick="showScreen('screen-forgot')" style="margin-top: 8px; display: inline-block;">Wachtwoord vergeten?</a>
</div>
</div>
<!-- SCHERM 2A: REGISTRATIE AANVRAAG -->
<div id="screen-register-req" class="screen">
<h2>Account Registreren</h2>
<p class="sub">Vul je gegevens in om je gratis voorproefje te starten. Je ontvangt een verificatiecode per e-mail.</p>
<div class="form-group">
<label>Volledige Naam</label>
<input type="text" id="regName" placeholder="Stijn Gabeler">
</div>
<div class="form-group">
<label>E-mailadres</label>
<input type="email" id="regEmail" placeholder="jouw@email.nl">
</div>
<div class="form-group">
<label>Kies een Wachtwoord</label>
<div class="password-wrapper">
<input type="password" id="regPassword" placeholder="••••••••">
<button type="button" class="toggle-password" onclick="togglePasswordVisibility('regPassword', this)" title="Toon/verberg wachtwoord">👁️</button>
</div>
</div>
<button type="button" class="btn" onclick="handleRegisterRequest()">Verstuur Verificatiecode</button>
<div class="link-text">
Al een account? <a onclick="showScreen('screen-login')">Direct inloggen</a>
</div>
</div>
<!-- SCHERM 2B: REGISTRATIE VERIFICATIE -->
<div id="screen-register-verify" class="screen">
<h2>E-mail Verifiëren</h2>
<p class="sub">We hebben een 6-cijferige verificatiecode gestuurd naar je e-mailadres. Vul deze hieronder in.</p>
<div class="form-group">
<label>Verificatiecode (6 cijfers)</label>
<input type="text" id="verifyCodeInput" placeholder="123456" maxlength="6">
</div>
<button type="button" class="btn btn-accent" onclick="handleVerifyCode()">Account Activeren</button>
<div class="link-text">
<a onclick="showScreen('screen-login')">Terug naar inloggen</a>
</div>
</div>
<!-- SCHERM 3: WACHTWOORD HERSTELLEN -->
<div id="screen-forgot" class="screen">
<h2>Wachtwoord Herstellen</h2>
<p class="sub">Vul je e-mailadres in om een herstelcode te ontvangen.</p>
<div class="form-group">
<label>E-mailadres</label>
<input type="email" id="forgotEmail" placeholder="jouw@email.nl">
</div>
<button type="button" class="btn" onclick="handleRequestForgot()">Stuur Herstelcode</button>
<div id="resetSection" style="display:none; margin-top:20px; border-top: 1px solid var(--border); padding-top: 15px;">
<div class="form-group">
<label>Herstelcode (6 cijfers)</label>
<input type="text" id="resetCode" placeholder="123456" maxlength="6">
</div>
<div class="form-group">
<label>Nieuw Wachtwoord</label>
<div class="password-wrapper">
<input type="password" id="newPassword" placeholder="••••••••">
<button type="button" class="toggle-password" onclick="togglePasswordVisibility('newPassword', this)" title="Toon/verberg wachtwoord">👁️</button>
</div>
</div>
<button type="button" class="btn btn-accent" onclick="handleExecuteReset()">Wachtwoord Opslaan</button>
</div>
<div class="link-text" style="margin-top: 15px;">
<a onclick="showScreen('screen-login')">Terug naar inloggen</a>
</div>
</div>
<!-- SCHERM 4: HOOFDDASHBOARD -->
<div id="screen-dashboard" class="screen">
<div class="navbar">
<span id="welcomeUser">Welkom</span>
<button type="button" onclick="handleLogout()">Uitloggen</button>
</div>
<div class="menu-grid">
<div class="menu-card" onclick="openG20Form()">
<h3>📅 Dagelijks G20</h3>
<p>Vul je dagboek en de 20 onderdelen in.</p>
</div>
<div class="menu-card" onclick="openHistory()">
<h3>📊 Mijn Dagen & Overzicht</h3>
<p>Bekijk al je eerdere invullingen en analyses.</p>
</div>
<div class="menu-card" onclick="loadCards()">
<h3>🃏 Coachingkaarten</h3>
<p>Bekijk inzichten en mindfulness kaarten.</p>
</div>
<div class="menu-card" onclick="openProfile()">
<h3>👤 Mijn Profiel</h3>
<p>Beheer je quote, thema en instellingen.</p>
</div>
</div>
</div>
<!-- SCHERM 5: G20 DAGELIJKS FORMULIER -->
<div id="screen-g20-form" class="screen">
<div class="navbar">
<span>G20 Dagboek invullen</span>
<button type="button" onclick="showScreen('screen-dashboard')">Terug</button>
</div>
<div class="form-group">
<label>Selecteer Datum</label>
<input type="date" id="dailyDate" onchange="loadDailyForDate()">
</div>
<div class="form-group">
<label>Algemeen Inzicht van de dag</label>
<textarea id="dailyInzicht" rows="3" placeholder="Wat is je belangrijkste inzicht vandaag?"></textarea>
</div>
<div class="form-group">
<label>Mooiste Moment</label>
<input type="text" id="dailyMooiste" placeholder="Wat was het mooiste moment?">
</div>
<div class="form-group">
<label>Stap voor Morgen</label>
<input type="text" id="dailyStap" placeholder="Welke stap zet je morgen?">
</div>
<hr style="margin: 25px 0; border:0; border-top:1px solid var(--border);">
<h3>De 20 G's van Bewustwording</h3>
<p class="sub">Vul hieronder jouw persoonlijke reflecties in op de 20 G-thema's.</p>
<div id="g20QuestionsContainer"></div>
<button type="button" class="btn btn-accent" onclick="saveG20Daily(event)">Dagboek Opslaan</button>
<button type="button" class="btn btn-secondary" onclick="showScreen('screen-dashboard')" style="margin-top: 10px;">Terug naar Menu</button>
</div>
<!-- SCHERM 6: GESCHIEDENIS & ANALYSE -->
<div id="screen-history" class="screen">
<div class="navbar">
<span>Mijn Ingevulde Dagen</span>
<button type="button" onclick="showScreen('screen-dashboard')">Terug</button>
</div>
<p class="sub">Hier zie je een overzicht van al jouw opgeslagen dagen. Klik op 'Bewerken' om een dag in te zien of bij te werken.</p>
<div id="historyListContainer"></div>
<button type="button" class="btn btn-secondary" onclick="showScreen('screen-dashboard')">Terug naar Menu</button>
</div>
<!-- SCHERM 7: KAARTEN -->
<div id="screen-cards" class="screen">
<div class="navbar">
<span>Coachingkaarten</span>
<button type="button" onclick="showScreen('screen-dashboard')">Terug</button>
</div>
<div id="cardsContainer"></div>
<button type="button" class="btn btn-secondary" onclick="showScreen('screen-dashboard')">Terug naar Menu</button>
</div>
<!-- SCHERM 8: PROFIEL -->
<div id="screen-profile" class="screen">
<div class="navbar">
<span>Mijn Profiel</span>
<button type="button" onclick="showScreen('screen-dashboard')">Terug</button>
</div>
<div class="form-group">
<label>Naam</label>
<input type="text" id="profileName">
</div>
<div class="form-group">
<label>E-mailadres</label>
<input type="email" id="profileEmail" disabled>
</div>
<div class="form-group">
<label>Persoonlijke Quote</label>
<input type="text" id="profileQuote" placeholder="Jij bepaalt je eigen koers.">
</div>
<div class="form-group">
<label>Favoriet Thema</label>
<input type="text" id="profileTheme" placeholder="Geluksbesef">
</div>
<button type="button" class="btn btn-accent" onclick="saveProfile()">Profiel Opslaan</button>
<button type="button" class="btn btn-secondary" onclick="showScreen('screen-dashboard')">Terug naar Menu</button>
</div>
<footer>
<p>© 2026 Coaching by Gabeler.eu — Stijn Gabeler | <a href="https://sites.google.com/view/gabeler" target="_blank">Bezoek Gabeler.eu</a></p>
</footer>
</div>
<script>
let currentUser = null;
let cachedQuestions = [];
let tempRegEmail = '';
let tempForgotEmail = '';
function showScreen(screenId) {
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
document.getElementById(screenId).classList.add('active');
hideAlert();
}
function showAlert(msg, type = 'success') {
const box = document.getElementById('alertBox');
box.className = 'alert ' + (type === 'success' ? 'alert-success' : 'alert-error');
box.innerText = msg;
box.style.display = 'block';
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function hideAlert() {
document.getElementById('alertBox').style.display = 'none';
}
function showSpinner(show) {
document.getElementById('spinner').style.display = show ? 'block' : 'none';
}
function togglePasswordVisibility(inputId, btn) {
const input = document.getElementById(inputId);
if (input.type === 'password') {
input.type = 'text';
btn.innerText = '🙈';
} else {
input.type = 'password';
btn.innerText = '👁️';
}
}
// --- AUTH LOGICA ---
function handleLogin() {
const email = document.getElementById('loginEmail').value.trim();
const password = document.getElementById('loginPassword').value.trim();
if (!email || !password) {
showAlert('Vul zowel e-mailadres als wachtwoord in.', 'error');
return;
}
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
if (res.success) {
currentUser = res.user;
document.getElementById('welcomeUser').innerText = 'Welkom, ' + currentUser.name;
showScreen('screen-dashboard');
} else {
showAlert(res.message, 'error');
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Technische fout: ' + err.message, 'error');
})
.g20Login(email, password);
}
function handleLogout() {
currentUser = null;
document.getElementById('loginEmail').value = '';
document.getElementById('loginPassword').value = '';
showScreen('screen-login');
}
function handleRegisterRequest() {
const name = document.getElementById('regName').value.trim();
const email = document.getElementById('regEmail').value.trim();
const password = document.getElementById('regPassword').value.trim();
if (!name || !email || !password) {
showAlert('Vul alle velden in om te registreren.', 'error');
return;
}
tempRegEmail = email;
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
showAlert(res.message, res.success ? 'success' : 'error');
if (res.success) {
showScreen('screen-register-verify');
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout bij registratie: ' + err.message, 'error');
})
.g20RequestRegistrationCode(name, email, password);
}
function handleVerifyCode() {
const code = document.getElementById('verifyCodeInput').value.trim();
if (!code) {
showAlert('Vul de verificatiecode in.', 'error');
return;
}
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
showAlert(res.message, res.success ? 'success' : 'error');
if (res.success) {
showScreen('screen-login');
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout bij verifiëren: ' + err.message, 'error');
})
.g20VerifyCode(tempRegEmail, code);
}
function handleRequestForgot() {
const email = document.getElementById('forgotEmail').value.trim();
if (!email) {
showAlert('Vul je e-mailadres in.', 'error');
return;
}
tempForgotEmail = email;
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
showAlert(res.message, res.success ? 'success' : 'error');
if (res.success) {
document.getElementById('resetSection').style.display = 'block';
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout: ' + err.message, 'error');
})
.g20RequestPasswordReset(email);
}
function handleExecuteReset() {
const code = document.getElementById('resetCode').value.trim();
const newPassword = document.getElementById('newPassword').value.trim();
if (!code || !newPassword) {
showAlert('Vul de herstelcode en je nieuwe wachtwoord in.', 'error');
return;
}
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
showAlert(res.message, res.success ? 'success' : 'error');
if (res.success) {
showScreen('screen-login');
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout: ' + err.message, 'error');
})
.g20ResetPassword(tempForgotEmail, code, newPassword);
}
// --- G20 FORMULIER & VRAGEN (GEÜPDATET) ---
function openG20Form() {
const today = new Date().toISOString().split('T')[0];
document.getElementById('dailyDate').value = today;
showScreen('screen-g20-form');
loadQuestionsAndData(today);
}
function loadDailyForDate() {
const datum = document.getElementById('dailyDate').value;
loadQuestionsAndData(datum);
}
function renderQuestions(questions) {
const container = document.getElementById('g20QuestionsContainer');
if (!container) {
console.error('g20QuestionsContainer niet gevonden.');
return;
}
const canonicalCodes = [
'G1_Geluksbesef', 'G2_Gevoelens', 'G3_Gedroomd', 'G4_Gemis', 'G5_Gewenst',
'G6_Gehoopt', 'G7_Gedachten', 'G8_Gedrag', 'G9_Gezegd', 'G10_Gedaan',
'G11_Geschreven', 'G12_Gelezen', 'G13_Geleerd', 'G14_Geweten', 'G15_Gehoord',
'G16_Gezien', 'G17_Geproefd', 'G18_Geroken', 'G19_GedachtOver', 'G20_Geluksgetal'
];
let html = '';
for (let i = 0; i < 20; i++) {
const q = questions[i] || {};
const canonicalCode = canonicalCodes[i];
html += `
<div class="g20-item">
<div class="g20-item-header">
<span class="g20-emoji">${q.emoji || ''}</span>
<span class="g20-title">${i + 1}. ${q.title || getFallbackGTitle_(i + 1)}</span>
</div>
<div class="g20-question">${q.question || ''}</div>
<div class="form-group" style="margin-bottom:0;">
<textarea
id="input_${canonicalCode}"
data-g20-code="${canonicalCode}"
data-g20-number="${i + 1}"
rows="2"
autocomplete="off"
placeholder="Jouw antwoord..."
></textarea>
</div>
</div>
`;
}
container.innerHTML = html;
}
function getFallbackGTitle_(number) {
const titles = [
'', 'Geluksbesef', 'Gevoelens', 'Gedroomd', 'Gemis', 'Gewenst',
'Gehoopt', 'Gedachten', 'Gedrag', 'Gezegd', 'Gedaan',
'Geschreven', 'Gelezen', 'Geleerd', 'Geweten', 'Gehoord',
'Gezien', 'Geproefd', 'Geroken', 'Gedacht over', 'Geluksgetal'
];
return titles[number] || '';
}
function loadQuestionsAndData(datum) {
if (!currentUser || !currentUser.userId) {
showAlert('Je bent niet ingelogd.', 'error');
return;
}
if (!datum) {
showAlert('Selecteer eerst een datum.', 'error');
return;
}
const canonicalCodes = [
'G1_Geluksbesef', 'G2_Gevoelens', 'G3_Gedroomd', 'G4_Gemis', 'G5_Gewenst',
'G6_Gehoopt', 'G7_Gedachten', 'G8_Gedrag', 'G9_Gezegd', 'G10_Gedaan',
'G11_Geschreven', 'G12_Gelezen', 'G13_Geleerd', 'G14_Geweten', 'G15_Gehoord',
'G16_Gezien', 'G17_Geproefd', 'G18_Geroken', 'G19_GedachtOver', 'G20_Geluksgetal'
];
showSpinner(true);
google.script.run
.withSuccessHandler(function(qRes) {
if (!qRes || !qRes.success) {
showSpinner(false);
showAlert('Kon de 20 G-vragen niet laden.', 'error');
return;
}
cachedQuestions = qRes.questions || [];
renderQuestions(cachedQuestions);
google.script.run
.withSuccessHandler(function(dRes) {
// Eerst ALLES leegmaken
document.getElementById('dailyInzicht').value = '';
document.getElementById('dailyMooiste').value = '';
document.getElementById('dailyStap').value = '';
canonicalCodes.forEach(function(code) {
const field = document.getElementById('input_' + code);
if (field) {
field.value = '';
}
});
// Daarna eventueel opgeslagen gegevens terugzetten
if (dRes && dRes.success && dRes.data) {
document.getElementById('dailyInzicht').value = dRes.data.Inzicht || '';
document.getElementById('dailyMooiste').value = dRes.data.MooisteMoment || '';
document.getElementById('dailyStap').value = dRes.data.MorgenStap || '';
canonicalCodes.forEach(function(code) {
const field = document.getElementById('input_' + code);
if (!field) return;
if (dRes.data[code] !== undefined && dRes.data[code] !== null) {
field.value = dRes.data[code];
}
});
}
showSpinner(false);
})
.withFailureHandler(function(err) {
showSpinner(false);
showAlert('Fout bij ophalen dagboek: ' + (err && err.message ? err.message : err), 'error');
})
.g20GetDaily(currentUser.userId, datum);
})
.withFailureHandler(function(err) {
showSpinner(false);
showAlert('Fout bij ophalen vragen: ' + (err && err.message ? err.message : err), 'error');
})
.g20GetQuestions();
}
function saveG20Daily(event) {
if (event) {
event.preventDefault();
event.stopPropagation();
}
if (!currentUser || !currentUser.userId) {
showAlert('Je bent niet ingelogd.', 'error');
return false;
}
const dateElement = document.getElementById('dailyDate');
const datum = dateElement ? dateElement.value : '';
if (!datum) {
showAlert('Selecteer een datum.', 'error');
return false;
}
const gCodes = [
'G1_Geluksbesef', 'G2_Gevoelens', 'G3_Gedroomd', 'G4_Gemis', 'G5_Gewenst',
'G6_Gehoopt', 'G7_Gedachten', 'G8_Gedrag', 'G9_Gezegd', 'G10_Gedaan',
'G11_Geschreven', 'G12_Gelezen', 'G13_Geleerd', 'G14_Geweten', 'G15_Gehoord',
'G16_Gezien', 'G17_Geproefd', 'G18_Geroken', 'G19_GedachtOver', 'G20_Geluksgetal'
];
const payload = {
UserID: String(currentUser.userId || ''),
Datum: datum,
Inzicht: document.getElementById('dailyInzicht') ? document.getElementById('dailyInzicht').value : '',
MooisteMoment: document.getElementById('dailyMooiste') ? document.getElementById('dailyMooiste').value : '',
MorgenStap: document.getElementById('dailyStap') ? document.getElementById('dailyStap').value : ''
};
gCodes.forEach(function(code) {
const field = document.getElementById('input_' + code);
if (field) {
payload[code] = field.value !== undefined && field.value !== null ? String(field.value) : '';
} else {
payload[code] = '';
}
});
showSpinner(true);
google.script.run
.withSuccessHandler(function(res) {
showSpinner(false);
if (res && res.success) {
showAlert('Alle 20 G-velden en de overige dagvelden zijn opgeslagen.', 'success');
} else {
showAlert(res && res.message ? res.message : 'Opslaan is niet gelukt.', 'error');
}
})
.withFailureHandler(function(err) {
showSpinner(false);
showAlert('Fout bij opslaan: ' + (err && err.message ? err.message : err), 'error');
})
.g20SaveDaily(payload);
return false;
}
// --- GESCHIEDENIS ---
function openHistory() {
showScreen('screen-history');
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
const container = document.getElementById('historyListContainer');
if (!res.success || !res.history || res.history.length === 0) {
container.innerHTML = '<p class="sub">Je hebt nog geen dagen ingevuld.</p>';
return;
}
let html = '';
res.history.sort((a, b) => new Date(b.Datum) - new Date(a.Datum)).forEach(row => {
let formattedDate = row.Datum;
try { formattedDate = new Date(row.Datum).toISOString().split('T')[0]; } catch(e){}
html += `
<div class="history-card">
<div class="history-info">
<h4>📅 Datum: ${formattedDate}</h4>
<p><strong>Inzicht:</strong> ${row.Inzicht ? row.Inzicht.substring(0, 60) + '...' : 'Geen inzicht ingevuld'}</p>
</div>
<button type="button" class="btn" style="width: auto; padding: 8px 15px; margin-top:0;" onclick="editDay('${formattedDate}')">Bewerken</button>
</div>
`;
});
container.innerHTML = html;
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout bij ophalen geschiedenis: ' + err.message, 'error');
})
.g20GetUserHistory(currentUser.userId);
}
function editDay(datum) {
showScreen('screen-g20-form');
document.getElementById('dailyDate').value = datum;
loadQuestionsAndData(datum);
}
// --- KAARTEN & PROFIEL ---
function loadCards() {
showScreen('screen-cards');
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
const container = document.getElementById('cardsContainer');
if (!res.success || !res.cards) {
container.innerHTML = '<p class="sub">Geen kaarten beschikbaar.</p>';
return;
}
let html = '';
res.cards.forEach(c => {
html += `
<div class="g20-item">
<div class="g20-title">${c.title} <span style="font-size:0.8rem; color:var(--text-light);">(${c.category})</span></div>
<p style="margin-top:8px; font-size:0.9rem;">${c.description}</p>
</div>
`;
});
container.innerHTML = html;
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout: ' + err.message, 'error');
})
.g20GetCards();
}
function openProfile() {
showScreen('screen-profile');
document.getElementById('profileEmail').value = currentUser.email;
document.getElementById('profileName').value = currentUser.name;
document.getElementById('profileQuote').value = currentUser.quote || '';
document.getElementById('profileTheme').value = currentUser.favoriteTheme || '';
}
function saveProfile() {
const profileData = {
name: document.getElementById('profileName').value,
quote: document.getElementById('profileQuote').value,
favoriteTheme: document.getElementById('profileTheme').value
};
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
showAlert(res.message, res.success ? 'success' : 'error');
if (res.success) {
currentUser.name = profileData.name;
document.getElementById('welcomeUser').innerText = 'Welkom, ' + currentUser.name;
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout bij opslaan profiel: ' + err.message, 'error');
})
.g20SaveProfile(currentUser.userId, profileData);
}
</script>
</body>
</html>
Embedded versie
resolve issue 19092026 1108 Index problem, not Code issue.
new error, ``` ?
Coaching by Gabeler.eu | Stijn Gabeler
```
```
```
Log in met je e-mailadres en gekozen wachtwoord.
E-mailadres
Wachtwoord
👁️
Inloggen
Nog geen account? Registreer hier
```
``` © 2026 Coaching by Gabeler.eu — Stijn Gabeler ```
<div class="header-links">
<a href="https://sites.google.com/view/gabeler" target="_blank">Gabeler.eu</a>
|
<a href="https://sites.google.com/view/coachstijn" target="_blank">Coach Stijn</a>
</div>
<h2>Inloggen</h2>
<p class="sub">
Log in met je e-mailadres en gekozen wachtwoord.
</p>
<div class="form-group">
<label for="loginEmail">E-mailadres</label>
<input
type="email"
id="loginEmail"
autocomplete="username"
placeholder="jouw@email.nl">
</div>
<div class="form-group">
<label for="loginPassword">Wachtwoord</label>
<div class="password-wrapper">
<input
type="password"
id="loginPassword"
autocomplete="current-password"
placeholder="••••••••">
<button
type="button"
class="toggle-password"
onclick="togglePasswordVisibility('loginPassword',this)">
👁️
</button>
</div>
</div>
<button
type="button"
class="btn"
onclick="handleLogin()">
Inloggen
</button>
<div class="link-text">
Nog geen account?
<a href="#" onclick="navigateTo('screen-register-req');return false;">
Registreer hier
</a>
<br>
<a href="#" onclick="navigateTo('screen-forgot');return false;">
Wachtwoord vergeten?
</a>
</div>
<h2>Account Registreren</h2>
<p class="sub">
Vul je gegevens in. Je ontvangt een verificatiecode per e-mail.
</p>
<div class="form-group">
<label for="regName">Volledige Naam</label>
<input
type="text"
id="regName"
autocomplete="name"
placeholder="Je naam">
</div>
<div class="form-group">
<label for="regEmail">E-mailadres</label>
<input
type="email"
id="regEmail"
autocomplete="email"
placeholder="jouw@email.nl">
</div>
<div class="form-group">
<label for="regPassword">Kies een Wachtwoord</label>
<div class="password-wrapper">
<input
type="password"
id="regPassword"
autocomplete="new-password"
placeholder="••••••••">
<button
type="button"
class="toggle-password"
onclick="togglePasswordVisibility('regPassword',this)">
👁️
</button>
</div>
</div>
<button
type="button"
class="btn"
onclick="handleRegisterRequest()">
Verstuur Verificatiecode
</button>
<div class="link-text">
<a href="#" onclick="navigateTo('screen-login');return false;">
Terug naar inloggen
</a>
</div>
<h2>E-mail Verifiëren</h2>
<p class="sub">
Vul de 6-cijferige verificatiecode in die naar je e-mailadres is gestuurd.
</p>
<div class="form-group">
<label for="verifyCodeInput">Verificatiecode</label>
<input
type="text"
id="verifyCodeInput"
inputmode="numeric"
maxlength="6"
placeholder="123456">
</div>
<button
type="button"
class="btn btn-accent"
onclick="handleVerifyCode()">
Account Activeren
</button>
<div class="link-text">
<a href="#" onclick="navigateTo('screen-login');return false;">
Terug naar inloggen
</a>
</div>
<h2>Wachtwoord Herstellen</h2>
<p class="sub">
Vul je e-mailadres in om een herstelcode te ontvangen.
</p>
<div class="form-group">
<label for="forgotEmail">E-mailadres</label>
<input
type="email"
id="forgotEmail"
autocomplete="email"
placeholder="jouw@email.nl">
</div>
<button
type="button"
class="btn"
onclick="handleRequestForgot()">
Stuur Herstelcode
</button>
<div
id="resetSection"
style="display:none;margin-top:20px;border-top:1px solid var(--border);padding-top:20px;">
<div class="form-group">
<label for="resetCode">Herstelcode</label>
<input
type="text"
id="resetCode"
inputmode="numeric"
maxlength="6"
placeholder="123456">
</div>
<div class="form-group">
<label for="newPassword">Nieuw Wachtwoord</label>
<div class="password-wrapper">
<input
type="password"
id="newPassword"
autocomplete="new-password"
placeholder="••••••••">
<button
type="button"
class="toggle-password"
onclick="togglePasswordVisibility('newPassword',this)">
👁️
</button>
</div>
</div>
<button
type="button"
class="btn btn-accent"
onclick="handleExecuteReset()">
Wachtwoord Opslaan
</button>
</div>
<div class="link-text">
<a href="#" onclick="navigateTo('screen-login');return false;">
Terug naar inloggen
</a>
</div>
<div class="navbar">
<span id="welcomeUser">
Welkom
</span>
<button
type="button"
onclick="handleLogout()">
Uitloggen
</button>
</div>
<div class="menu-grid">
<div
class="menu-card"
onclick="openG20Form()">
<h3>📅 Dagelijks G20</h3>
<p>
Vul je dagboek en alle 20 G-onderdelen in.
</p>
</div>
<div
class="menu-card"
onclick="openHistory()">
<h3>📊 Mijn Dagen</h3>
<p>
Bekijk eerdere ingevulde dagen.
</p>
</div>
<div
class="menu-card"
onclick="loadCards()">
<h3>🃏 Coachingkaarten</h3>
<p>
Bekijk coachingkaarten en inzichten.
</p>
</div>
<div
class="menu-card"
onclick="openProfile()">
<h3>👤 Mijn Profiel</h3>
<p>
Beheer je profiel en voorkeuren.
</p>
</div>
</div>
<div class="navbar">
<span>
G20 Dagboek
</span>
<button
type="button"
onclick="navigateTo('screen-dashboard')">
Terug
</button>
</div>
<div class="form-group">
<label for="dailyDate">
Selecteer Datum
</label>
<input
type="date"
id="dailyDate">
</div>
<div class="form-group">
<label for="dailyInzicht">
Algemeen Inzicht
</label>
<textarea
id="dailyInzicht"
rows="3"
placeholder="Wat is je belangrijkste inzicht vandaag?"></textarea>
</div>
<div class="form-group">
<label for="dailyMooiste">
Mooiste Moment
</label>
<input
type="text"
id="dailyMooiste"
placeholder="Wat was het mooiste moment?">
</div>
<div class="form-group">
<label for="dailyStap">
Stap voor Morgen
</label>
<input
type="text"
id="dailyStap"
placeholder="Welke stap zet je morgen?">
</div>
<hr style="margin:25px 0;border:0;border-top:1px solid var(--border);">
<h3>
De 20 G's van Bewustwording
</h3>
<p class="sub">
Vul hieronder jouw persoonlijke reflecties in.
</p>
<!--
BELANGRIJK:
Deze container wordt slechts één keer gevuld bij het openen
of wisselen van datum.
De HTML-ID's worden NIET bepaald door G20_Questions.
-->
<div id="g20QuestionsContainer"></div>
<div
id="saveStatus"
class="save-status">
</div>
<button
type="button"
id="saveG20Button"
class="btn btn-accent"
onclick="saveG20Daily()">
Dagboek Opslaan
</button>
<button
type="button"
class="btn btn-secondary"
onclick="navigateTo('screen-dashboard')">
Terug naar Menu
</button>
<div class="navbar">
<span>
Mijn Ingevulde Dagen
</span>
<button
type="button"
onclick="navigateTo('screen-dashboard')">
Terug
</button>
</div>
<p class="sub">
Hier zie je jouw eerder opgeslagen dagen.
</p>
<div id="historyListContainer"></div>
<button
type="button"
class="btn btn-secondary"
onclick="navigateTo('screen-dashboard')">
Terug naar Menu
</button>
<div class="navbar">
<span>
Coachingkaarten
</span>
<button
type="button"
onclick="navigateTo('screen-dashboard')">
Terug
</button>
</div>
<div id="cardsContainer"></div>
<button
type="button"
class="btn btn-secondary"
onclick="navigateTo('screen-dashboard')">
Terug naar Menu
</button>
<div class="navbar">
<span>
Mijn Profiel
</span>
<button
type="button"
onclick="navigateTo('screen-dashboard')">
Terug
</button>
</div>
<div class="form-group">
<label for="profileName">
Naam
</label>
<input
type="text"
id="profileName">
</div>
<div class="form-group">
<label for="profileEmail">
E-mailadres
</label>
<input
type="email"
id="profileEmail"
disabled>
</div>
<div class="form-group">
<label for="profileQuote">
Persoonlijke Quote
</label>
<input
type="text"
id="profileQuote">
</div>
<div class="form-group">
<label for="profileTheme">
Favoriet Thema
</label>
<input
type="text"
id="profileTheme">
</div>
<button
type="button"
class="btn btn-accent"
onclick="saveProfile()">
Profiel Opslaan
</button>
<button
type="button"
class="btn btn-secondary"
onclick="navigateTo('screen-dashboard')">
Terug naar Menu
</button>
© 2026 Coaching by Gabeler.eu — Stijn Gabeler
'</div>' +
'</div>';
});
container.innerHTML = html;
g20FormRendered = true;
}
/* =========================================================
HTML VEILIG MAKEN
========================================================= */
function escapeHtml(value){
if(value === null || value === undefined){
return "";
}
return String(value)
.replace(/&/g,"&")
.replace(/</g,"<")
.replace(/>/g,">")
.replace(/"/g,""")
.replace(/'/g,"'");
}
/* =========================================================
FORMULIER LEZEN
========================================================= */
function readG20Form(){
const payload = {
UserID:
currentUser
? currentUser.userId
: "",
Datum:
document.getElementById(
"dailyDate"
).value,
Inzicht:
document.getElementById(
"dailyInzicht"
).value,
MooisteMoment:
document.getElementById(
"dailyMooiste"
).value,
MorgenStap:
document.getElementById(
"dailyStap"
).value
};
/*
* BELANGRIJK:
*
* Hier worden ALLE 20 velden
* rechtstreeks op hun CANONIEKE ID
* uitgelezen.
*/
G20_FIELDS.forEach(function(g){
const element =
document.getElementById(
"input_" + g.code
);
payload[g.code] =
element
? element.value
: "";
});
return payload;
}
/* =========================================================
CONTROLEER DAT ALLE 20 VELDEN BESTAAN
========================================================= */
function validateG20Fields(){
const missing = [];
G20_FIELDS.forEach(function(g){
const element =
document.getElementById(
"input_" + g.code
);
if(!element){
missing.push(g.code);
}
});
return missing;
}
/* =========================================================
G20 OPSLAAN
========================================================= */
function saveG20Daily(){
if(!currentUser){
showAlert(
"Je bent niet ingelogd.",
"error"
);
return;
}
const datum =
document.getElementById(
"dailyDate"
).value;
if(!datum){
showAlert(
"Selecteer eerst een datum.",
"error"
);
return;
}
/*
* Controleer eerst of alle 20
* echte inputvelden aanwezig zijn.
*/
const missing =
validateG20Fields();
if(missing.length > 0){
showAlert(
"Niet alle 20 G-velden zijn geladen. Ontbrekend: " +
missing.join(", "),
"error"
);
return;
}
/*
* Maak de payload.
*/
const payload =
readG20Form();
/*
* Extra controle:
* alle 20 keys moeten in payload zitten.
*/
const emptyKeys = [];
G20_FIELDS.forEach(function(g){
if(
payload[g.code] === undefined
){
emptyKeys.push(g.code);
}
});
if(emptyKeys.length > 0){
showAlert(
"Technische fout: ontbrekende velden " +
emptyKeys.join(", "),
"error"
);
return;
}
/*
* Opslaan.
*/
const saveButton =
document.getElementById(
"saveG20Button"
);
if(saveButton){
saveButton.disabled = true;
saveButton.textContent =
"Bezig met opslaan...";
}
showSpinner(true);
google.script.run
.withSuccessHandler(function(res){
showSpinner(false);
if(saveButton){
saveButton.disabled = false;
saveButton.textContent =
"Dagboek Opslaan";
}
if(res && res.success){
showSaveStatus(
"success",
"Alle 20 G-velden en de overige dagvelden zijn opgeslagen."
);
showAlert(
"Alle 20 G-velden en de overige dagvelden zijn opgeslagen.",
"success"
);
}else{
showSaveStatus(
"error",
res && res.message
? res.message
: "Opslaan mislukt."
);
showAlert(
res && res.message
? res.message
: "Opslaan mislukt.",
"error"
);
}
})
.withFailureHandler(function(err){
showSpinner(false);
if(saveButton){
saveButton.disabled = false;
saveButton.textContent =
"Dagboek Opslaan";
}
const message =
"Technische fout bij opslaan: " +
(err.message || err);
showSaveStatus(
"error",
message
);
showAlert(
message,
"error"
);
})
.g20SaveDaily(payload);
}
/* =========================================================
OPSLAASTATUS
========================================================= */
function showSaveStatus(type,message){
const box =
document.getElementById(
"saveStatus"
);
if(!box) return;
box.className =
"save-status " + type;
box.textContent = message;
}
/* =========================================================
G20 VRAGEN + BESTAANDE DATA LADEN
========================================================= */
function loadQuestionsAndData(datum){
if(!currentUser || !datum){
return;
}
if(g20Loading){
return;
}
g20Loading = true;
currentLoadedDate = datum;
showSpinner(true);
/*
* BELANGRIJK:
* Eerst bouwen we de 20 velden uit
* de vaste G20_FIELDS.
*
* We wachten dus niet op de Code-kolom
* uit G20_Questions om IDs te bepalen.
*/
if(!g20FormRendered){
renderQuestions();
}
/*
* Eerst alles leegmaken voor de nieuwe datum.
*/
clearDailyFields();
/*
* Daarna halen we alleen de opgeslagen
* daggegevens op.
*/
google.script.run
.withSuccessHandler(function(res){
g20Loading = false;
showSpinner(false);
if(
res &&
res.success &&
res.data
){
fillDailyFields(
res.data
);
}
})
.withFailureHandler(function(err){
g20Loading = false;
showSpinner(false);
showAlert(
"Fout bij ophalen dagboek: " +
(err.message || err),
"error"
);
})
.g20GetDaily(
currentUser.userId,
datum
);
}
/* =========================================================
VELDEN LEEGMAKEN
========================================================= */
function clearDailyFields(){
const inzicht =
document.getElementById(
"dailyInzicht"
);
const mooiste =
document.getElementById(
"dailyMooiste"
);
const stap =
document.getElementById(
"dailyStap"
);
if(inzicht){
inzicht.value = "";
}
if(mooiste){
mooiste.value = "";
}
if(stap){
stap.value = "";
}
G20_FIELDS.forEach(function(g){
const field =
document.getElementById(
"input_" + g.code
);
if(field){
field.value = "";
}
});
const status =
document.getElementById(
"saveStatus"
);
if(status){
status.className =
"save-status";
status.textContent = "";
}
}
/* =========================================================
OPGESLAGEN DATA IN FORMULIER ZETTEN
========================================================= */
function fillDailyFields(data){
const inzicht =
document.getElementById(
"dailyInzicht"
);
const mooiste =
document.getElementById(
"dailyMooiste"
);
const stap =
document.getElementById(
"dailyStap"
);
if(inzicht){
inzicht.value =
data.Inzicht !== undefined
? data.Inzicht
: "";
}
if(mooiste){
mooiste.value =
data.MooisteMoment !== undefined
? data.MooisteMoment
: "";
}
if(stap){
stap.value =
data.MorgenStap !== undefined
? data.MorgenStap
: "";
}
/*
* ALLE 20 velden worden uitsluitend
* met hun vaste kolomnaam gevuld.
*/
G20_FIELDS.forEach(function(g){
const field =
document.getElementById(
"input_" + g.code
);
if(!field){
return;
}
const value =
data[g.code];
if(
value !== undefined &&
value !== null
){
field.value = value;
}else{
field.value = "";
}
});
}
/* =========================================================
GESCHIEDENIS
========================================================= */
function openHistory(){
if(!currentUser){
return;
}
navigateTo("screen-history");
showSpinner(true);
google.script.run
.withSuccessHandler(function(res){
showSpinner(false);
const container =
document.getElementById(
"historyListContainer"
);
if(
!res ||
!res.success ||
!res.history ||
res.history.length === 0
){
container.innerHTML =
'<p class="sub">Je hebt nog geen dagen ingevuld.</p>';
return;
}
const history =
res.history.slice().sort(
function(a,b){
return String(b.Datum)
.localeCompare(
String(a.Datum)
);
}
);
let html = "";
history.forEach(function(row){
const date =
formatDateValue(
row.Datum
);
const inzicht =
row.Inzicht
? String(row.Inzicht)
: "Geen inzicht ingevuld";
html +=
'<div class="history-card">' +
'<h4>📅 ' +
escapeHtml(date) +
'</h4>' +
'<p>' +
escapeHtml(
inzicht.substring(0,120)
) +
(
inzicht.length > 120
? "..."
: ""
) +
'</p>' +
'<button ' +
'type="button" ' +
'class="btn" ' +
'style="margin-top:10px;" ' +
'onclick="editDay(\'' +
escapeJs(date) +
'\')"' +
'>' +
'Bewerken' +
'</button>' +
'</div>';
});
container.innerHTML = html;
})
.withFailureHandler(function(err){
showSpinner(false);
showAlert(
"Fout bij ophalen geschiedenis: " +
(err.message || err),
"error"
);
})
.g20GetUserHistory(
currentUser.userId
);
}
/* =========================================================
DAG BEWERKEN
========================================================= */
function editDay(datum){
const dateInput =
document.getElementById(
"dailyDate"
);
dateInput.value = datum;
navigateTo(
"screen-g20-form"
);
loadQuestionsAndData(
datum
);
}
/* =========================================================
DATUM FORMATTEREN
========================================================= */
function formatDateValue(value){
if(!value){
return "";
}
const text =
String(value);
if(
/^\d{4}-\d{2}-\d{2}/.test(text)
){
return text.substring(0,10);
}
const date =
new Date(value);
if(!isNaN(date.getTime())){
return (
date.getFullYear() +
"-" +
String(
date.getMonth()+1
).padStart(2,"0") +
"-" +
String(
date.getDate()
).padStart(2,"0")
);
}
return text;
}
function escapeJs(value){
return String(value)
.replace(/\\/g,"\\\\")
.replace(/'/g,"\\'");
}
/* =========================================================
KAARTEN
========================================================= */
function loadCards(){
navigateTo("screen-cards");
showSpinner(true);
google.script.run
.withSuccessHandler(function(res){
showSpinner(false);
const container =
document.getElementById(
"cardsContainer"
);
if(
!res ||
!res.success ||
!res.cards
){
container.innerHTML =
'<p class="sub">Geen kaarten beschikbaar.</p>';
return;
}
let html = "";
res.cards.forEach(function(card){
html +=
'<div class="g20-item">' +
'<div class="g20-title">' +
escapeHtml(
card.title || ""
) +
'</div>' +
'<p style="margin-top:8px;">' +
escapeHtml(
card.description || ""
) +
'</p>' +
'</div>';
});
container.innerHTML =
html ||
'<p class="sub">Geen kaarten beschikbaar.</p>';
})
.withFailureHandler(function(err){
showSpinner(false);
showAlert(
"Fout bij laden kaarten: " +
(err.message || err),
"error"
);
})
.g20GetCards();
}
/* =========================================================
PROFIEL
========================================================= */
function openProfile(){
if(!currentUser){
return;
}
navigateTo(
"screen-profile"
);
document.getElementById(
"profileEmail"
).value =
currentUser.email || "";
document.getElementById(
"profileName"
).value =
currentUser.name || "";
document.getElementById(
"profileQuote"
).value =
currentUser.quote || "";
document.getElementById(
"profileTheme"
).value =
currentUser.favoriteTheme || "";
}
/* =========================================================
PROFIEL OPSLAAN
========================================================= */
function saveProfile(){
if(!currentUser){
return;
}
const profileData = {
name:
document.getElementById(
"profileName"
).value,
quote:
document.getElementById(
"profileQuote"
).value,
favoriteTheme:
document.getElementById(
"profileTheme"
).value
};
showSpinner(true);
google.script.run
.withSuccessHandler(function(res){
showSpinner(false);
showAlert(
res.message,
res.success
? "success"
: "error"
);
if(res.success){
currentUser.name =
profileData.name;
currentUser.quote =
profileData.quote;
currentUser.favoriteTheme =
profileData.favoriteTheme;
document.getElementById(
"welcomeUser"
).textContent =
"Welkom, " +
currentUser.name;
}
})
.withFailureHandler(function(err){
showSpinner(false);
showAlert(
"Fout bij opslaan profiel: " +
(err.message || err),
"error"
);
})
.g20SaveProfile(
currentUser.userId,
profileData
);
}
/* =========================================================
EXTRA VEILIGHEID:
GEEN FORM-SUBMIT / GEEN PAGINA-REFRESH.
Enter in een textarea moet gewoon nieuwe regel
geven. Enter in gewone inputs mag geen webapp
opnieuw laden.
========================================================= */
document.addEventListener(
"keydown",
function(event){
const target =
event.target;
if(
target &&
target.tagName === "INPUT" &&
event.key === "Enter"
){
event.preventDefault();
}
}
);
/* =========================================================
START
========================================================= */
document.addEventListener(
"DOMContentLoaded",
function(){
/*
* De 20 velden worden pas gemaakt wanneer
* het G20-scherm daadwerkelijk geopend wordt.
*
* Hierdoor ontstaat er geen her-render terwijl
* iemand al aan het typen is.
*/
g20FormRendered = false;
}
);
Ik heb de oorzaak gevonden: de Index gebruikt bij het renderen de Code uit G20_Questions als HTML-id. In jouw scherm komt daardoor bijvoorbeeld GELUKSBESEF terecht, terwijl saveG20Daily() zoekt naar input_G1_Geluksbesef. Daardoor worden de velden leeg naar Code.gs gestuurd.
In onderstaande versie gebruik ik daarom altijd de vaste 20 canonieke veldnamen, onafhankelijk van wat er eventueel in G20_Questions staat. Ook wordt het formulier niet opnieuw opgebouwd terwijl je aan het typen bent.
geef me geen Code.gs wijzigingen meer maar dus de Index.html wijzigingen want daar gaat het mis.
Ja. Ik heb nu de oorzaak uit de huidige Index.html gehaald.
De belangrijke fout is dat de Index momenteel de q.code uit G20_Questions gebruikt als opslagnaam. In jouw deployed scherm zie ik bijvoorbeeld:
1. Geluksbesef (GELUKSBESEF)
19. Gedacht over (GEDACHT_OVER)
Terwijl jouw G20_Daily exact deze kolomnamen heeft:
G1_Geluksbesef t/m G20_Geluksgetal.
Daardoor kan de Index bijvoorbeeld GELUKSBESEF naar Apps Script sturen, terwijl de Sheet wacht op G1_Geluksbesef.
Code.gs hoeft hiervoor niet gewijzigd te worden.
Nieuwe renderQuestions functie
function renderQuestions(questions) {
const container = document.getElementById('g20QuestionsContainer');
if (!container) {
console.error('g20QuestionsContainer niet gevonden.');
return;
}
const canonicalCodes = [
'G1_Geluksbesef',
'G2_Gevoelens',
'G3_Gedroomd',
'G4_Gemis',
'G5_Gewenst',
'G6_Gehoopt',
'G7_Gedachten',
'G8_Gedrag',
'G9_Gezegd',
'G10_Gedaan',
'G11_Geschreven',
'G12_Gelezen',
'G13_Geleerd',
'G14_Geweten',
'G15_Gehoord',
'G16_Gezien',
'G17_Geproefd',
'G18_Geroken',
'G19_GedachtOver',
'G20_Geluksgetal'
];
let html = '';
for (let i = 0; i < 20; i++) {
const q = questions[i] || {};
// BELANGRIJK:
// De opslagnaam wordt NIET meer uit q.code gehaald.
// De opslagnaam wordt altijd bepaald door het G-nummer.
const canonicalCode = canonicalCodes[i];
html += `
<div class="g20-item">
<div class="g20-item-header">
<span class="g20-emoji">
${q.emoji || ''}
</span>
<span class="g20-title">
${i + 1}. ${q.title || getFallbackGTitle_(i + 1)}
</span>
</div>
<div class="g20-question">
${q.question || ''}
</div>
<div class="form-group" style="margin-bottom:0;">
<textarea
id="input_${canonicalCode}"
data-g20-code="${canonicalCode}"
data-g20-number="${i + 1}"
rows="2"
autocomplete="off"
placeholder="Jouw antwoord..."
></textarea>
</div>
</div>
`;
}
container.innerHTML = html;
}
function getFallbackGTitle_(number) {
const titles = [
'',
'Geluksbesef',
'Gevoelens',
'Gedroomd',
'Gemis',
'Gewenst',
'Gehoopt',
'Gedachten',
'Gedrag',
'Gezegd',
'Gedaan',
'Geschreven',
'Gelezen',
'Geleerd',
'Geweten',
'Gehoord',
'Gezien',
'Geproefd',
'Geroken',
'Gedacht over',
'Geluksgetal'
];
return titles[number] || '';
}
Deze versie zorgt ervoor dat bestaande gegevens ook altijd met de juiste G1–G20-kolomnamen terug in het formulier komen.
Nieuwe loadQuestionsAndData functie
function loadQuestionsAndData(datum) {
if (!currentUser || !currentUser.userId) {
showAlert('Je bent niet ingelogd.', 'error');
return;
}
if (!datum) {
showAlert('Selecteer eerst een datum.', 'error');
return;
}
const canonicalCodes = [
'G1_Geluksbesef',
'G2_Gevoelens',
'G3_Gedroomd',
'G4_Gemis',
'G5_Gewenst',
'G6_Gehoopt',
'G7_Gedachten',
'G8_Gedrag',
'G9_Gezegd',
'G10_Gedaan',
'G11_Geschreven',
'G12_Gelezen',
'G13_Geleerd',
'G14_Geweten',
'G15_Gehoord',
'G16_Gezien',
'G17_Geproefd',
'G18_Geroken',
'G19_GedachtOver',
'G20_Geluksgetal'
];
showSpinner(true);
google.script.run
.withSuccessHandler(function(qRes) {
if (!qRes || !qRes.success) {
showSpinner(false);
showAlert('Kon de 20 G-vragen niet laden.', 'error');
return;
}
cachedQuestions = qRes.questions || [];
renderQuestions(cachedQuestions);
google.script.run
.withSuccessHandler(function(dRes) {
// Eerst ALLES leegmaken
document.getElementById('dailyInzicht').value = '';
document.getElementById('dailyMooiste').value = '';
document.getElementById('dailyStap').value = '';
canonicalCodes.forEach(function(code) {
const field =
document.getElementById('input_' + code);
if (field) {
field.value = '';
}
});
// Daarna eventueel opgeslagen gegevens terugzetten
if (dRes && dRes.success && dRes.data) {
document.getElementById('dailyInzicht').value =
dRes.data.Inzicht || '';
document.getElementById('dailyMooiste').value =
dRes.data.MooisteMoment || '';
document.getElementById('dailyStap').value =
dRes.data.MorgenStap || '';
canonicalCodes.forEach(function(code) {
const field =
document.getElementById('input_' + code);
if (!field) {
return;
}
if (
dRes.data[code] !== undefined &&
dRes.data[code] !== null
) {
field.value = dRes.data[code];
}
});
}
showSpinner(false);
})
.withFailureHandler(function(err) {
showSpinner(false);
showAlert(
'Fout bij ophalen dagboek: ' +
(err && err.message ? err.message : err),
'error'
);
})
.g20GetDaily(
currentUser.userId,
datum
);
})
.withFailureHandler(function(err) {
showSpinner(false);
showAlert(
'Fout bij ophalen vragen: ' +
(err && err.message ? err.message : err),
'error'
);
})
.g20GetQuestions();
}
Dit is de belangrijkste wijziging. De Index bouwt nu zelf gegarandeerd het payload op met exact dezelfde 20 namen als jouw Sheet.
Nieuwe saveG20Daily functie
function saveG20Daily(event) {
// Voorkom een eventuele submit/refresh van de pagina.
if (event) {
event.preventDefault();
event.stopPropagation();
}
if (!currentUser || !currentUser.userId) {
showAlert('Je bent niet ingelogd.', 'error');
return false;
}
const dateElement =
document.getElementById('dailyDate');
const datum =
dateElement ? dateElement.value : '';
if (!datum) {
showAlert(
'Selecteer een datum.',
'error'
);
return false;
}
/*
* DIT ZIJN DE EXACTE KOLOMNAMEN
* VAN G20_Daily.
*/
const gCodes = [
'G1_Geluksbesef',
'G2_Gevoelens',
'G3_Gedroomd',
'G4_Gemis',
'G5_Gewenst',
'G6_Gehoopt',
'G7_Gedachten',
'G8_Gedrag',
'G9_Gezegd',
'G10_Gedaan',
'G11_Geschreven',
'G12_Gelezen',
'G13_Geleerd',
'G14_Geweten',
'G15_Gehoord',
'G16_Gezien',
'G17_Geproefd',
'G18_Geroken',
'G19_GedachtOver',
'G20_Geluksgetal'
];
/*
* Basisgegevens
*/
const payload = {
UserID:
String(currentUser.userId || ''),
Datum:
datum,
Inzicht:
document.getElementById('dailyInzicht')
? document.getElementById('dailyInzicht').value
: '',
MooisteMoment:
document.getElementById('dailyMooiste')
? document.getElementById('dailyMooiste').value
: '',
MorgenStap:
document.getElementById('dailyStap')
? document.getElementById('dailyStap').value
: ''
};
/*
* ALLE 20 G-VELDEN
*
* Niet kijken naar q.code.
* Niet kijken naar de titel.
* Niet kijken naar de tekst.
*
* Alleen de vaste G1-G20 opslagcodes gebruiken.
*/
gCodes.forEach(function(code) {
const field =
document.getElementById('input_' + code);
if (field) {
payload[code] =
field.value !== undefined &&
field.value !== null
? String(field.value)
: '';
} else {
// Veld bestaat niet:
// toch de sleutel meesturen.
payload[code] = '';
}
});
/*
* Controle in de browser.
*
* Dit laat zien dat de Index daadwerkelijk
* alle 20 G-velden verzamelt.
*/
console.log(
'G20 payload voor opslaan:',
JSON.stringify(payload, null, 2)
);
showSpinner(true);
google.script.run
.withSuccessHandler(function(res) {
showSpinner(false);
if (res && res.success) {
showAlert(
'Alle 20 G-velden en de overige dagvelden zijn opgeslagen.',
'success'
);
} else {
showAlert(
res && res.message
? res.message
: 'Opslaan is niet gelukt.',
'error'
);
}
})
.withFailureHandler(function(err) {
showSpinner(false);
showAlert(
'Fout bij opslaan: ' +
(
err && err.message
? err.message
: err
),
'error'
);
})
.g20SaveDaily(payload);
return false;
}
Verander de knop:
<button class="btn btn-accent" onclick="saveG20Daily()">
Dagboek Opslaan
</button>
naar:
<button
type="button"
class="btn btn-accent"
onclick="saveG20Daily(event)">
Dagboek Opslaan
</button>
En maak ook de andere knoppen in het G20-scherm expliciet type="button":
<button
type="button"
class="btn btn-secondary"
onclick="showScreen('screen-dashboard')"
style="margin-top: 10px;">
Terug naar Menu
</button>
Jouw huidige Index doet:
let el = document.getElementById('input_' + q.code);
payload[code] = el ? el.value : '';
Dat is precies waar het mis kan gaan.
Als G20_Questions bijvoorbeeld dit bevat:
GELUKSBESEF
GEVOELENS
GEDROOMD
...
GEDACHT_OVER
GELUKSGETAL
dan maakt de Index bijvoorbeeld:
payload.GELUKSBESEF
payload.GEVOELENS
payload.GEDROOMD
...
Maar jouw G20_Daily heeft:
G1_Geluksbesef
G2_Gevoelens
G3_Gedroomd
...
G19_GedachtOver
G20_Geluksgetal
De nieuwe Index gebruikt daarom helemaal niet meer de codes uit G20_Questions voor opslag.
Hij maakt altijd:
payload.G1_Geluksbesef
payload.G2_Gevoelens
payload.G3_Gedroomd
payload.G4_Gemis
payload.G5_Gewenst
payload.G6_Gehoopt
payload.G7_Gedachten
payload.G8_Gedrag
payload.G9_Gezegd
payload.G10_Gedaan
payload.G11_Geschreven
payload.G12_Gelezen
payload.G13_Geleerd
payload.G14_Geweten
payload.G15_Gehoord
payload.G16_Gezien
payload.G17_Geproefd
payload.G18_Geroken
payload.G19_GedachtOver
payload.G20_Geluksgetal
Dat sluit 1-op-1 aan op de 20 kolommen van G20_Daily.
Ook wordt de waarde bij het laden weer via precies diezelfde namen teruggezet.
Dus: Code.gs niet meer aanpassen. Alleen deze Index-wijzigingen doorvoeren en daarna een nieuwe versie van de webapp-deployment maken. De oude /exec-deployment blijft anders de oude Index tonen
NextLevel USERS BASE 20 G Coaching by Gabeler.eu Coach Stijn Gabeler ~ Stichting GODOGOOD.eu
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>G20 — Dagelijks Geluksbesef | Coaching by Gabeler.eu</title>
<style>
:root {
--primary: #1e3a8a;
--primary-light: #3b82f6;
--accent: #10b981;
--bg: #f8fafc;
--card-bg: #ffffff;
--text: #1e293b;
--text-light: #64748b;
--border: #e2e8f0;
--radius: 12px;
}
* { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
body { background-color: var(--bg); color: var(--text); min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; padding: 20px; }
.container { width: 100%; max-width: 800px; background: var(--card-bg); border-radius: var(--radius); box-shadow: 0 10px 25px -5px rgba(0,0,0,0.05), 0 8px 10px -6px rgba(0,0,0,0.05); padding: 30px; margin-top: 20px; margin-bottom: 40px; }
header { text-align: center; margin-bottom: 30px; border-bottom: 2px solid var(--border); padding-bottom: 20px; }
header h1 { color: var(--primary); font-size: 1.8rem; margin-bottom: 5px; }
header p { color: var(--text-light); font-size: 0.95rem; }
.header-links { margin-top: 10px; font-size: 0.9rem; }
.header-links a { color: var(--primary-light); text-decoration: none; font-weight: 600; margin: 0 8px; }
.header-links a:hover { text-decoration: underline; }
.screen { display: none; }
.screen.active { display: block; animation: fadeIn 0.3s ease-in-out; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
h2 { color: var(--primary); font-size: 1.4rem; margin-bottom: 15px; }
p.sub { color: var(--text-light); font-size: 0.9rem; margin-bottom: 20px; }
.form-group { margin-bottom: 15px; }
label { display: block; font-weight: 600; font-size: 0.85rem; margin-bottom: 5px; color: var(--text); }
input, select, textarea { width: 100%; padding: 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 0.95rem; outline: none; transition: border-color 0.2s; }
input:focus, select:focus, textarea:focus { border-color: var(--primary-light); }
.btn { display: inline-block; width: 100%; background: var(--primary); color: white; border: none; padding: 12px 20px; font-size: 1rem; font-weight: 600; border-radius: 8px; cursor: pointer; text-align: center; transition: background 0.2s; margin-top: 10px; }
.btn:hover { background: #1d4ed8; }
.btn-secondary { background: #64748b; }
.btn-secondary:hover { background: #475569; }
.btn-accent { background: var(--accent); }
.btn-accent:hover { background: #059669; }
.link-text { text-align: center; margin-top: 15px; font-size: 0.9rem; color: var(--text-light); }
.link-text a { color: var(--primary-light); text-decoration: none; font-weight: 600; cursor: pointer; }
.link-text a:hover { text-decoration: underline; }
/* Navbar & Menu */
.navbar { display: flex; justify-content: space-between; align-items: center; background: var(--primary); color: white; padding: 12px 20px; border-radius: 8px; margin-bottom: 25px; }
.navbar span { font-weight: 600; font-size: 0.9rem; }
.navbar button { background: rgba(255,255,255,0.2); border: none; color: white; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 0.85rem; }
.navbar button:hover { background: rgba(255,255,255,0.3); }
.menu-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 15px; margin-bottom: 25px; }
.menu-card { background: var(--bg); border: 1px solid var(--border); padding: 20px; border-radius: 8px; text-align: center; cursor: pointer; transition: all 0.2s; }
.menu-card:hover { border-color: var(--primary-light); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.05); }
.menu-card h3 { color: var(--primary); font-size: 1.1rem; margin-bottom: 5px; }
.menu-card p { font-size: 0.85rem; color: var(--text-light); }
/* G20 Form Fields */
.g20-item { background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 15px; margin-bottom: 15px; }
.g20-item-header { display: flex; align-items: center; margin-bottom: 8px; }
.g20-emoji { font-size: 1.4rem; margin-right: 10px; }
.g20-title { font-weight: 600; font-size: 1rem; color: var(--primary); }
.g20-question { font-size: 0.9rem; color: var(--text); margin-bottom: 10px; font-style: italic; }
/* Overzicht / Geschiedenis */
.history-card { background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 15px; margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center; }
.history-info h4 { color: var(--primary); font-size: 1rem; margin-bottom: 4px; }
.history-info p { font-size: 0.85rem; color: var(--text-light); }
.spinner { display: none; text-align: center; margin: 15px 0; color: var(--primary-light); font-weight: 600; }
.alert { padding: 12px; border-radius: 8px; margin-bottom: 15px; font-size: 0.9rem; display: none; }
.alert-success { background: #d1fae5; color: #065f46; border: 1px solid #a7f3d0; }
.alert-error { background: #fee2e2; color: #991b1b; border: 1px solid #fecaca; }
footer { text-align: center; margin-top: auto; font-size: 0.8rem; color: var(--text-light); padding: 20px; }
footer a { color: var(--primary-light); text-decoration: none; }
footer a:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="container">
<header>
<h1>G20 — Dagelijks Geluksbesef</h1>
<p>Coaching by Gabeler.eu | Coach Stijn Gabeler</p>
<div class="header-links">
<a href="https://sites.google.com/view/gabeler" target="_blank">Gabeler.eu</a> |
<a href="https://sites.google.com/view/coachstijn" target="_blank">Coach Stijn</a>
</div>
</header>
<div id="alertBox" class="alert"></div>
<div id="spinner" class="spinner">Even geduld, bezig met verwerken...</div>
<!-- SCHERM 1: LOGIN -->
<div id="screen-login" class="screen active">
<h2>Inloggen</h2>
<p class="sub">Log in met je e-mailadres en gekozen wachtwoord.</p>
<div class="form-group">
<label>E-mailadres</label>
<input type="email" id="loginEmail" placeholder="jouw@email.nl">
</div>
<div class="form-group">
<label>Wachtwoord</label>
<input type="password" id="loginPassword" placeholder="••••••••">
</div>
<button class="btn" onclick="handleLogin()">Inloggen</button>
<div class="link-text">
Nog geen account? <a onclick="showScreen('screen-register-req')">Registreer hier (Gratis voorproefje)</a><br>
<a onclick="showScreen('screen-forgot')" style="margin-top: 8px; display: inline-block;">Wachtwoord vergeten?</a>
</div>
</div>
<!-- SCHERM 2A: REGISTRATIE AANVRAAG -->
<div id="screen-register-req" class="screen">
<h2>Account Registreren</h2>
<p class="sub">Vul je gegevens in om je gratis voorproefje te starten. Je ontvangt een verificatiecode per e-mail.</p>
<div class="form-group">
<label>Volledige Naam</label>
<input type="text" id="regName" placeholder="Stijn Gabeler">
</div>
<div class="form-group">
<label>E-mailadres</label>
<input type="email" id="regEmail" placeholder="jouw@email.nl">
</div>
<div class="form-group">
<label>Kies een Wachtwoord</label>
<input type="password" id="regPassword" placeholder="••••••••">
</div>
<button class="btn" onclick="handleRegisterRequest()">Verstuur Verificatiecode</button>
<div class="link-text">
Al een account? <a onclick="showScreen('screen-login')">Direct inloggen</a>
</div>
</div>
<!-- SCHERM 2B: REGISTRATIE VERIFICATIE -->
<div id="screen-register-verify" class="screen">
<h2>E-mail Verifiëren</h2>
<p class="sub">We hebben een 6-cijferige verificatiecode gestuurd naar je e-mailadres. Vul deze hieronder in.</p>
<div class="form-group">
<label>Verificatiecode (6 cijfers)</label>
<input type="text" id="verifyCodeInput" placeholder="123456" maxlength="6">
</div>
<button class="btn btn-accent" onclick="handleVerifyCode()">Account Activeren</button>
<div class="link-text">
<a onclick="showScreen('screen-login')">Terug naar inloggen</a>
</div>
</div>
<!-- SCHERM 3: WACHTWOORD HERSTELLEN -->
<div id="screen-forgot" class="screen">
<h2>Wachtwoord Herstellen</h2>
<p class="sub">Vul je e-mailadres in om een herstelcode te ontvangen.</p>
<div class="form-group">
<label>E-mailadres</label>
<input type="email" id="forgotEmail" placeholder="jouw@email.nl">
</div>
<button class="btn" onclick="handleRequestForgot()">Stuur Herstelcode</button>
<div id="resetSection" style="display:none; margin-top:20px; border-top: 1px solid var(--border); padding-top: 15px;">
<div class="form-group">
<label>Herstelcode</label>
<input type="text" id="resetCode" placeholder="123456">
</div>
<div class="form-group">
<label>Nieuw Wachtwoord</label>
<input type="password" id="newPassword" placeholder="••••••••">
</div>
<button class="btn btn-accent" onclick="handleExecuteReset()">Wachtwoord Opslaan</button>
</div>
<div class="link-text" style="margin-top: 15px;">
<a onclick="showScreen('screen-login')">Terug naar inloggen</a>
</div>
</div>
<!-- SCHERM 4: HOOFDDASHBOARD -->
<div id="screen-dashboard" class="screen">
<div class="navbar">
<span id="welcomeUser">Welkom</span>
<button onclick="handleLogout()">Uitloggen</button>
</div>
<div class="menu-grid">
<div class="menu-card" onclick="openG20Form()">
<h3>📅 Dagelijks G20</h3>
<p>Vul je dagboek en de 20 onderdelen in.</p>
</div>
<div class="menu-card" onclick="openHistory()">
<h3>📊 Mijn Dagen & Overzicht</h3>
<p>Bekijk al je eerdere invullingen en analyses.</p>
</div>
<div class="menu-card" onclick="loadCards()">
<h3>🃏 Coachingkaarten</h3>
<p>Bekijk inzichten en mindfulness kaarten.</p>
</div>
<div class="menu-card" onclick="openProfile()">
<h3>👤 Mijn Profiel</h3>
<p>Beheer je quote, thema en instellingen.</p>
</div>
</div>
</div>
<!-- SCHERM 5: G20 DAGELIJKS FORMULIER -->
<div id="screen-g20-form" class="screen">
<div class="navbar">
<span>G20 Dagboek invullen</span>
<button onclick="showScreen('screen-dashboard')">Terug</button>
</div>
<div class="form-group">
<label>Selecteer Datum</label>
<input type="date" id="dailyDate" onchange="loadDailyForDate()">
</div>
<div class="form-group">
<label>Algemeen Inzicht van de dag</label>
<textarea id="dailyInzicht" rows="3" placeholder="Wat is je belangrijkste inzicht vandaag?"></textarea>
</div>
<div class="form-group">
<label>Mooiste Moment</label>
<input type="text" id="dailyMooiste" placeholder="Wat was het mooiste moment?">
</div>
<div class="form-group">
<label>Stap voor Morgen</label>
<input type="text" id="dailyStap" placeholder="Welke stap zet je morgen?">
</div>
<hr style="margin: 25px 0; border:0; border-top:1px solid var(--border);">
<h3>De 20 G's van Bewustwording</h3>
<p class="sub">Vul hieronder jouw persoonlijke reflecties in op de 20 G-thema's.</p>
<div id="g20QuestionsContainer"></div>
<button class="btn btn-accent" onclick="saveG20Daily()">Dagboek Opslaan</button>
<button class="btn btn-secondary" onclick="showScreen('screen-dashboard')" style="margin-top: 10px;">Terug naar Menu</button>
</div>
<!-- SCHERM 6: GESCHIEDENIS & ANALYSE -->
<div id="screen-history" class="screen">
<div class="navbar">
<span>Mijn Ingevulde Dagen</span>
<button onclick="showScreen('screen-dashboard')">Terug</button>
</div>
<p class="sub">Hier zie je een overzicht van al jouw opgeslagen dagen. Klik op een dag om deze in te zien of bij te werken.</p>
<div id="historyListContainer"></div>
<button class="btn btn-secondary" onclick="showScreen('screen-dashboard')">Terug naar Menu</button>
</div>
<!-- SCHERM 7: KAARTEN -->
<div id="screen-cards" class="screen">
<div class="navbar">
<span>Coachingkaarten</span>
<button onclick="showScreen('screen-dashboard')">Terug</button>
</div>
<div id="cardsContainer"></div>
<button class="btn btn-secondary" onclick="showScreen('screen-dashboard')">Terug naar Menu</button>
</div>
<!-- SCHERM 8: PROFIEL -->
<div id="screen-profile" class="screen">
<div class="navbar">
<span>Mijn Profiel</span>
<button onclick="showScreen('screen-dashboard')">Terug</button>
</div>
<div class="form-group">
<label>Naam</label>
<input type="text" id="profileName">
</div>
<div class="form-group">
<label>E-mailadres</label>
<input type="email" id="profileEmail" disabled>
</div>
<div class="form-group">
<label>Persoonlijke Quote</label>
<input type="text" id="profileQuote" placeholder="Jij bepaalt je eigen koers.">
</div>
<div class="form-group">
<label>Favoriet Thema</label>
<input type="text" id="profileTheme" placeholder="Geluksbesef">
</div>
<button class="btn btn-accent" onclick="saveProfile()">Profiel Opslaan</button>
<button class="btn btn-secondary" onclick="showScreen('screen-dashboard')">Terug naar Menu</button>
</div>
<footer>
<p>© 2026 Coaching by Gabeler.eu — Stijn Gabeler | <a href="https://sites.google.com/view/gabeler" target="_blank">Bezoek Gabeler.eu</a></p>
</footer>
</div>
<script>
let currentUser = null;
let cachedQuestions = [];
let tempRegEmail = '';
function showScreen(screenId) {
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
document.getElementById(screenId).classList.add('active');
hideAlert();
}
function showAlert(msg, type = 'success') {
const box = document.getElementById('alertBox');
box.className = 'alert ' + (type === 'success' ? 'alert-success' : 'alert-error');
box.innerText = msg;
box.style.display = 'block';
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function hideAlert() {
document.getElementById('alertBox').style.display = 'none';
}
function showSpinner(show) {
document.getElementById('spinner').style.display = show ? 'block' : 'none';
}
// --- AUTH LOGICA ---
function handleLogin() {
const email = document.getElementById('loginEmail').value.trim();
const password = document.getElementById('loginPassword').value.trim();
if (!email || !password) {
showAlert('Vul zowel e-mailadres als wachtwoord in.', 'error');
return;
}
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
if (res.success) {
currentUser = res.user;
document.getElementById('welcomeUser').innerText = 'Welkom, ' + currentUser.name;
showScreen('screen-dashboard');
} else {
showAlert(res.message, 'error');
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Technische fout: ' + err.message, 'error');
})
.g20Login(email, password);
}
function handleLogout() {
currentUser = null;
document.getElementById('loginEmail').value = '';
document.getElementById('loginPassword').value = '';
showScreen('screen-login');
}
function handleRegisterRequest() {
const name = document.getElementById('regName').value.trim();
const email = document.getElementById('regEmail').value.trim();
const password = document.getElementById('regPassword').value.trim();
if (!name || !email || !password) {
showAlert('Vul alle velden in om te registreren.', 'error');
return;
}
tempRegEmail = email;
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
showAlert(res.message, res.success ? 'success' : 'error');
if (res.success) {
showScreen('screen-register-verify');
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout bij registratie: ' + err.message, 'error');
})
.g20RequestRegistrationCode(name, email, password);
}
function handleVerifyCode() {
const code = document.getElementById('verifyCodeInput').value.trim();
if (!code) {
showAlert('Vul de verificatiecode in.', 'error');
return;
}
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
showAlert(res.message, res.success ? 'success' : 'error');
if (res.success) {
showScreen('screen-login');
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout bij verifiëren: ' + err.message, 'error');
})
.g20VerifyCode(tempRegEmail, code);
}
function handleRequestForgot() {
const email = document.getElementById('forgotEmail').value.trim();
if (!email) {
showAlert('Vul je e-mailadres in.', 'error');
return;
}
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
showAlert(res.message, res.success ? 'success' : 'error');
if (res.success) {
document.getElementById('resetSection').style.display = 'block';
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout: ' + err.message, 'error');
})
.g20RequestPasswordReset(email);
}
function handleExecuteReset() {
const email = document.getElementById('forgotEmail').value.trim();
const code = document.getElementById('resetCode').value.trim();
const newPassword = document.getElementById('newPassword').value.trim();
if (!email || !code || !newPassword) {
showAlert('Vul alle velden in voor herstel.', 'error');
return;
}
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
showAlert(res.message, res.success ? 'success' : 'error');
if (res.success) {
showScreen('screen-login');
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout: ' + err.message, 'error');
})
.g20ResetPassword(email, code, newPassword);
}
// --- G20 FORMULIER & VRAGEN ---
function openG20Form() {
const today = new Date().toISOString().split('T')[0];
document.getElementById('dailyDate').value = today;
showScreen('screen-g20-form');
loadQuestionsAndData(today);
}
function loadDailyForDate() {
const datum = document.getElementById('dailyDate').value;
loadQuestionsAndData(datum);
}
function loadQuestionsAndData(datum) {
showSpinner(true);
google.script.run
.withSuccessHandler(qRes => {
if (qRes.success) {
cachedQuestions = qRes.questions;
renderQuestions(cachedQuestions);
// Haal opgeslagen data op voor deze gebruiker en datum
google.script.run
.withSuccessHandler(dRes => {
showSpinner(false);
document.getElementById('dailyInzicht').value = '';
document.getElementById('dailyMooiste').value = '';
document.getElementById('dailyStap').value = '';
cachedQuestions.forEach(q => {
const field = document.getElementById('input_' + q.code);
if (field) field.value = '';
});
if (dRes.success && dRes.data) {
document.getElementById('dailyInzicht').value = dRes.data.Inzicht || '';
document.getElementById('dailyMooiste').value = dRes.data.MooisteMoment || '';
document.getElementById('dailyStap').value = dRes.data.MorgenStap || '';
cachedQuestions.forEach(q => {
const field = document.getElementById('input_' + q.code);
if (field && dRes.data[q.code] !== undefined) {
field.value = dRes.data[q.code];
}
});
}
})
.g20GetDaily(currentUser.userId, datum);
} else {
showSpinner(false);
showAlert('Kon vragen niet laden.', 'error');
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout: ' + err.message, 'error');
})
.g20GetQuestions();
}
function renderQuestions(questions) {
const container = document.getElementById('g20QuestionsContainer');
let html = '';
questions.forEach(q => {
html += `
<div class="g20-item">
<div class="g20-item-header">
<span class="g20-emoji">${q.emoji}</span>
<span class="g20-title">${q.number}. ${q.title} (${q.code})</span>
</div>
<div class="g20-question">${q.question}</div>
<div class="form-group" style="margin-bottom:0;">
<textarea id="input_${q.code}" rows="2" placeholder="Jouw antwoord..."></textarea>
</div>
</div>
`;
});
container.innerHTML = html;
}
function saveG20Daily() {
const datum = document.getElementById('dailyDate').value;
if (!datum) {
showAlert('Selecteer een datum.', 'error');
return;
}
let payload = {
UserID: currentUser.userId,
Datum: datum,
Inzicht: document.getElementById('dailyInzicht').value,
MooisteMoment: document.getElementById('dailyMooiste').value,
MorgenStap: document.getElementById('dailyStap').value
};
cachedQuestions.forEach(q => {
const val = document.getElementById('input_' + q.code);
payload[q.code] = val ? val.value : '';
});
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
showAlert(res.message, res.success ? 'success' : 'error');
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout bij opslaan: ' + err.message, 'error');
})
.g20SaveDaily(payload);
}
// --- GESCHIEDENIS / OVERZICHT ---
function openHistory() {
showScreen('screen-history');
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
const container = document.getElementById('historyListContainer');
if (!res.success || !res.history || res.history.length === 0) {
container.innerHTML = '<p class="sub">Je hebt nog geen dagen ingevuld.</p>';
return;
}
let html = '';
res.history.sort((a, b) => new Date(b.Datum) - new Date(a.Datum)).forEach(row => {
let formattedDate = row.Datum;
try { formattedDate = new Date(row.Datum).toISOString().split('T')[0]; } catch(e){}
html += `
<div class="history-card">
<div class="history-info">
<h4>📅 Datum: ${formattedDate}</h4>
<p><strong>Inzicht:</strong> ${row.Inzicht ? row.Inzicht.substring(0, 60) + '...' : 'Geen inzicht ingevuld'}</p>
</div>
<button class="btn" style="width: auto; padding: 8px 15px; margin-top:0;" onclick="editDay('${formattedDate}')">Bewerken</button>
</div>
`;
});
container.innerHTML = html;
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout bij ophalen geschiedenis: ' + err.message, 'error');
})
.g20GetUserHistory(currentUser.userId);
}
function editDay(datum) {
showScreen('screen-g20-form');
document.getElementById('dailyDate').value = datum;
loadQuestionsAndData(datum);
}
// --- KAARTEN & PROFIEL ---
function loadCards() {
showScreen('screen-cards');
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
const container = document.getElementById('cardsContainer');
if (!res.success || !res.cards) {
container.innerHTML = '<p class="sub">Geen kaarten beschikbaar.</p>';
return;
}
let html = '';
res.cards.forEach(c => {
html += `
<div class="g20-item">
<div class="g20-title">${c.title} <span style="font-size:0.8rem; color:var(--text-light);">(${c.category})</span></div>
<p style="margin-top:8px; font-size:0.9rem;">${c.description}</p>
</div>
`;
});
container.innerHTML = html;
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout: ' + err.message, 'error');
})
.g20GetCards();
}
function openProfile() {
showScreen('screen-profile');
document.getElementById('profileEmail').value = currentUser.email;
document.getElementById('profileName').value = currentUser.name;
document.getElementById('profileQuote').value = currentUser.quote || '';
document.getElementById('profileTheme').value = currentUser.favoriteTheme || '';
}
function saveProfile() {
const profileData = {
name: document.getElementById('profileName').value,
quote: document.getElementById('profileQuote').value,
favoriteTheme: document.getElementById('profileTheme').value
};
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
showAlert(res.message, res.success ? 'success' : 'error');
if (res.success) {
currentUser.name = profileData.name;
document.getElementById('welcomeUser').innerText = 'Welkom, ' + currentUser.name;
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout bij opslaan profiel: ' + err.message, 'error');
})
.g20SaveProfile(currentUser.userId, profileData);
}
</script>
</body>
</html>
422
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>G20 — Dagelijks Geluksbesef | NextLevel Coaching by Gabeler.eu</title>
<style>
:root {
--primary: #1e3a8a;
--primary-light: #3b82f6;
--accent: #10b981;
--bg: #f8fafc;
--card-bg: #ffffff;
--text: #1e293b;
--text-light: #64748b;
--border: #e2e8f0;
--radius: 12px;
}
* { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
body { background-color: var(--bg); color: var(--text); min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; padding: 20px; }
.container { width: 100%; max-width: 800px; background: var(--card-bg); border-radius: var(--radius); box-shadow: 0 10px 25px -5px rgba(0,0,0,0.05), 0 8px 10px -6px rgba(0,0,0,0.05); padding: 30px; margin-top: 20px; margin-bottom: 40px; }
header { text-align: center; margin-bottom: 30px; border-bottom: 2px solid var(--border); padding-bottom: 20px; }
header h1 { color: var(--primary); font-size: 1.8rem; margin-bottom: 5px; }
header p { color: var(--text-light); font-size: 0.95rem; }
.header-links { margin-top: 10px; font-size: 0.9rem; }
.header-links a { color: var(--primary-light); text-decoration: none; font-weight: 600; margin: 0 8px; }
.header-links a:hover { text-decoration: underline; }
.screen { display: none; }
.screen.active { display: block; animation: fadeIn 0.3s ease-in-out; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
h2 { color: var(--primary); font-size: 1.4rem; margin-bottom: 15px; }
p.sub { color: var(--text-light); font-size: 0.9rem; margin-bottom: 20px; }
.form-group { margin-bottom: 15px; }
label { display: block; font-weight: 600; font-size: 0.85rem; margin-bottom: 5px; color: var(--text); }
input, select, textarea { width: 100%; padding: 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 0.95rem; outline: none; transition: border-color 0.2s; }
input:focus, select:focus, textarea:focus { border-color: var(--primary-light); }
/* Wachtwoord wrapper met oogje */
.password-wrapper { position: relative; display: flex; align-items: center; }
.password-wrapper input { padding-right: 45px; }
.toggle-password { position: absolute; right: 12px; background: none; border: none; cursor: pointer; font-size: 1.2rem; color: var(--text-light); padding: 4px; }
.toggle-password:hover { color: var(--primary); }
.btn { display: inline-block; width: 100%; background: var(--primary); color: white; border: none; padding: 12px 20px; font-size: 1rem; font-weight: 600; border-radius: 8px; cursor: pointer; text-align: center; transition: background 0.2s; margin-top: 10px; }
.btn:hover { background: #1d4ed8; }
.btn-secondary { background: #64748b; }
.btn-secondary:hover { background: #475569; }
.btn-accent { background: var(--accent); }
.btn-accent:hover { background: #059669; }
.link-text { text-align: center; margin-top: 15px; font-size: 0.9rem; color: var(--text-light); }
.link-text a { color: var(--primary-light); text-decoration: none; font-weight: 600; cursor: pointer; }
.link-text a:hover { text-decoration: underline; }
/* Navbar & Menu */
.navbar { display: flex; justify-content: space-between; align-items: center; background: var(--primary); color: white; padding: 12px 20px; border-radius: 8px; margin-bottom: 25px; }
.navbar span { font-weight: 600; font-size: 0.9rem; }
.navbar button { background: rgba(255,255,255,0.2); border: none; color: white; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 0.85rem; }
.navbar button:hover { background: rgba(255,255,255,0.3); }
.menu-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 15px; margin-bottom: 25px; }
.menu-card { background: var(--bg); border: 1px solid var(--border); padding: 20px; border-radius: 8px; text-align: center; cursor: pointer; transition: all 0.2s; }
.menu-card:hover { border-color: var(--primary-light); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.05); }
.menu-card h3 { color: var(--primary); font-size: 1.1rem; margin-bottom: 5px; }
.menu-card p { font-size: 0.85rem; color: var(--text-light); }
/* G20 Form Fields */
.g20-item { background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 15px; margin-bottom: 15px; }
.g20-item-header { display: flex; align-items: center; margin-bottom: 8px; }
.g20-emoji { font-size: 1.4rem; margin-right: 10px; }
.g20-title { font-weight: 600; font-size: 1rem; color: var(--primary); }
.g20-question { font-size: 0.9rem; color: var(--text); margin-bottom: 10px; font-style: italic; }
/* Overzicht / Geschiedenis */
.history-card { background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 15px; margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center; }
.history-info h4 { color: var(--primary); font-size: 1rem; margin-bottom: 4px; }
.history-info p { font-size: 0.85rem; color: var(--text-light); }
.spinner { display: none; text-align: center; margin: 15px 0; color: var(--primary-light); font-weight: 600; }
.alert { padding: 12px; border-radius: 8px; margin-bottom: 15px; font-size: 0.9rem; display: none; }
.alert-success { background: #d1fae5; color: #065f46; border: 1px solid #a7f3d0; }
.alert-error { background: #fee2e2; color: #991b1b; border: 1px solid #fecaca; }
footer { text-align: center; margin-top: auto; font-size: 0.8rem; color: var(--text-light); padding: 20px; }
footer a { color: var(--primary-light); text-decoration: none; }
footer a:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="container">
<header>
<h1>G20 — Dagelijks Geluksbesef</h1>
<p>Coaching by Gabeler.eu | Coach Stijn Gabeler</p>
<div class="header-links">
<a href="https://sites.google.com/view/gabeler" target="_blank">Gabeler.eu</a> |
<a href="https://sites.google.com/view/coachstijn" target="_blank">Coach Stijn</a>
</div>
</header>
<div id="alertBox" class="alert"></div>
<div id="spinner" class="spinner">Even geduld, bezig met verwerken...</div>
<!-- SCHERM 1: LOGIN -->
<div id="screen-login" class="screen active">
<h2>Inloggen</h2>
<p class="sub">Log in met je e-mailadres en gekozen wachtwoord.</p>
<div class="form-group">
<label>E-mailadres</label>
<input type="email" id="loginEmail" placeholder="jouw@email.nl">
</div>
<div class="form-group">
<label>Wachtwoord</label>
<div class="password-wrapper">
<input type="password" id="loginPassword" placeholder="••••••••">
<button type="button" class="toggle-password" onclick="togglePasswordVisibility('loginPassword', this)" title="Toon/verberg wachtwoord">👁️</button>
</div>
</div>
<button class="btn" onclick="handleLogin()">Inloggen</button>
<div class="link-text">
Nog geen account? <a onclick="showScreen('screen-register-req')">Registreer hier (Gratis voorproefje)</a><br>
<a onclick="showScreen('screen-forgot')" style="margin-top: 8px; display: inline-block;">Wachtwoord vergeten?</a>
</div>
</div>
<!-- SCHERM 2A: REGISTRATIE AANVRAAG -->
<div id="screen-register-req" class="screen">
<h2>Account Registreren</h2>
<p class="sub">Vul je gegevens in om je gratis voorproefje te starten. Je ontvangt een verificatiecode per e-mail.</p>
<div class="form-group">
<label>Volledige Naam</label>
<input type="text" id="regName" placeholder="Stijn Gabeler">
</div>
<div class="form-group">
<label>E-mailadres</label>
<input type="email" id="regEmail" placeholder="jouw@email.nl">
</div>
<div class="form-group">
<label>Kies een Wachtwoord</label>
<div class="password-wrapper">
<input type="password" id="regPassword" placeholder="••••••••">
<button type="button" class="toggle-password" onclick="togglePasswordVisibility('regPassword', this)" title="Toon/verberg wachtwoord">👁️</button>
</div>
</div>
<button class="btn" onclick="handleRegisterRequest()">Verstuur Verificatiecode</button>
<div class="link-text">
Al een account? <a onclick="showScreen('screen-login')">Direct inloggen</a>
</div>
</div>
<!-- SCHERM 2B: REGISTRATIE VERIFICATIE -->
<div id="screen-register-verify" class="screen">
<h2>E-mail Verifiëren</h2>
<p class="sub">We hebben een 6-cijferige verificatiecode gestuurd naar je e-mailadres. Vul deze hieronder in.</p>
<div class="form-group">
<label>Verificatiecode (6 cijfers)</label>
<input type="text" id="verifyCodeInput" placeholder="123456" maxlength="6">
</div>
<button class="btn btn-accent" onclick="handleVerifyCode()">Account Activeren</button>
<div class="link-text">
<a onclick="showScreen('screen-login')">Terug naar inloggen</a>
</div>
</div>
<!-- SCHERM 3: WACHTWOORD HERSTELLEN -->
<div id="screen-forgot" class="screen">
<h2>Wachtwoord Herstellen</h2>
<p class="sub">Vul je e-mailadres in om een herstelcode te ontvangen.</p>
<div class="form-group">
<label>E-mailadres</label>
<input type="email" id="forgotEmail" placeholder="jouw@email.nl">
</div>
<button class="btn" onclick="handleRequestForgot()">Stuur Herstelcode</button>
<div id="resetSection" style="display:none; margin-top:20px; border-top: 1px solid var(--border); padding-top: 15px;">
<div class="form-group">
<label>Herstelcode (6 cijfers)</label>
<input type="text" id="resetCode" placeholder="123456" maxlength="6">
</div>
<div class="form-group">
<label>Nieuw Wachtwoord</label>
<div class="password-wrapper">
<input type="password" id="newPassword" placeholder="••••••••">
<button type="button" class="toggle-password" onclick="togglePasswordVisibility('newPassword', this)" title="Toon/verberg wachtwoord">👁️</button>
</div>
</div>
<button class="btn btn-accent" onclick="handleExecuteReset()">Wachtwoord Opslaan</button>
</div>
<div class="link-text" style="margin-top: 15px;">
<a onclick="showScreen('screen-login')">Terug naar inloggen</a>
</div>
</div>
<!-- SCHERM 4: HOOFDDASHBOARD -->
<div id="screen-dashboard" class="screen">
<div class="navbar">
<span id="welcomeUser">Welkom</span>
<button onclick="handleLogout()">Uitloggen</button>
</div>
<div class="menu-grid">
<div class="menu-card" onclick="openG20Form()">
<h3>📅 Dagelijks G20</h3>
<p>Vul je dagboek en de 20 onderdelen in.</p>
</div>
<div class="menu-card" onclick="openHistory()">
<h3>📊 Mijn Dagen & Overzicht</h3>
<p>Bekijk al je eerdere invullingen en analyses.</p>
</div>
<div class="menu-card" onclick="loadCards()">
<h3>🃏 Coachingkaarten</h3>
<p>Bekijk inzichten en mindfulness kaarten.</p>
</div>
<div class="menu-card" onclick="openProfile()">
<h3>👤 Mijn Profiel</h3>
<p>Beheer je quote, thema en instellingen.</p>
</div>
</div>
</div>
<!-- SCHERM 5: G20 DAGELIJKS FORMULIER -->
<div id="screen-g20-form" class="screen">
<div class="navbar">
<span>G20 Dagboek invullen</span>
<button onclick="showScreen('screen-dashboard')">Terug</button>
</div>
<div class="form-group">
<label>Selecteer Datum</label>
<input type="date" id="dailyDate" onchange="loadDailyForDate()">
</div>
<div class="form-group">
<label>Algemeen Inzicht van de dag</label>
<textarea id="dailyInzicht" rows="3" placeholder="Wat is je belangrijkste inzicht vandaag?"></textarea>
</div>
<div class="form-group">
<label>Mooiste Moment</label>
<input type="text" id="dailyMooiste" placeholder="Wat was het mooiste moment?">
</div>
<div class="form-group">
<label>Stap voor Morgen</label>
<input type="text" id="dailyStap" placeholder="Welke stap zet je morgen?">
</div>
<hr style="margin: 25px 0; border:0; border-top:1px solid var(--border);">
<h3>De 20 G's van Bewustwording</h3>
<p class="sub">Vul hieronder jouw persoonlijke reflecties in op de 20 G-thema's.</p>
<div id="g20QuestionsContainer"></div>
<button class="btn btn-accent" onclick="saveG20Daily()">Dagboek Opslaan</button>
<button class="btn btn-secondary" onclick="showScreen('screen-dashboard')" style="margin-top: 10px;">Terug naar Menu</button>
</div>
<!-- SCHERM 6: GESCHIEDENIS & ANALYSE -->
<div id="screen-history" class="screen">
<div class="navbar">
<span>Mijn Ingevulde Dagen</span>
<button onclick="showScreen('screen-dashboard')">Terug</button>
</div>
<p class="sub">Hier zie je een overzicht van al jouw opgeslagen dagen. Klik op 'Bewerken' om een dag in te zien of bij te werken.</p>
<div id="historyListContainer"></div>
<button class="btn btn-secondary" onclick="showScreen('screen-dashboard')">Terug naar Menu</button>
</div>
<!-- SCHERM 7: KAARTEN -->
<div id="screen-cards" class="screen">
<div class="navbar">
<span>Coachingkaarten</span>
<button onclick="showScreen('screen-dashboard')">Terug</button>
</div>
<div id="cardsContainer"></div>
<button class="btn btn-secondary" onclick="showScreen('screen-dashboard')">Terug naar Menu</button>
</div>
<!-- SCHERM 8: PROFIEL -->
<div id="screen-profile" class="screen">
<div class="navbar">
<span>Mijn Profiel</span>
<button onclick="showScreen('screen-dashboard')">Terug</button>
</div>
<div class="form-group">
<label>Naam</label>
<input type="text" id="profileName">
</div>
<div class="form-group">
<label>E-mailadres</label>
<input type="email" id="profileEmail" disabled>
</div>
<div class="form-group">
<label>Persoonlijke Quote</label>
<input type="text" id="profileQuote" placeholder="Jij bepaalt je eigen koers.">
</div>
<div class="form-group">
<label>Favoriet Thema</label>
<input type="text" id="profileTheme" placeholder="Geluksbesef">
</div>
<button class="btn btn-accent" onclick="saveProfile()">Profiel Opslaan</button>
<button class="btn btn-secondary" onclick="showScreen('screen-dashboard')">Terug naar Menu</button>
</div>
<footer>
<p>© 2026 Coaching by Gabeler.eu — Stijn Gabeler | <a href="https://sites.google.com/view/gabeler" target="_blank">Bezoek Gabeler.eu</a></p>
</footer>
</div>
<script>
let currentUser = null;
let cachedQuestions = [];
let tempRegEmail = '';
let tempForgotEmail = '';
function showScreen(screenId) {
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
document.getElementById(screenId).classList.add('active');
hideAlert();
}
function showAlert(msg, type = 'success') {
const box = document.getElementById('alertBox');
box.className = 'alert ' + (type === 'success' ? 'alert-success' : 'alert-error');
box.innerText = msg;
box.style.display = 'block';
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function hideAlert() {
document.getElementById('alertBox').style.display = 'none';
}
function showSpinner(show) {
document.getElementById('spinner').style.display = show ? 'block' : 'none';
}
// Hulpfunctie om wachtwoord zichtbaar/onzichtbaar te maken (het oogje)
function togglePasswordVisibility(inputId, btn) {
const input = document.getElementById(inputId);
if (input.type === 'password') {
input.type = 'text';
btn.innerText = '🙈'; // Ander icoontje als het zichtbaar is
} else {
input.type = 'password';
btn.innerText = '👁️';
}
}
// --- AUTH LOGICA ---
function handleLogin() {
const email = document.getElementById('loginEmail').value.trim();
const password = document.getElementById('loginPassword').value.trim();
if (!email || !password) {
showAlert('Vul zowel e-mailadres als wachtwoord in.', 'error');
return;
}
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
if (res.success) {
currentUser = res.user;
document.getElementById('welcomeUser').innerText = 'Welkom, ' + currentUser.name;
showScreen('screen-dashboard');
} else {
showAlert(res.message, 'error');
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Technische fout: ' + err.message, 'error');
})
.g20Login(email, password);
}
function handleLogout() {
currentUser = null;
document.getElementById('loginEmail').value = '';
document.getElementById('loginPassword').value = '';
showScreen('screen-login');
}
function handleRegisterRequest() {
const name = document.getElementById('regName').value.trim();
const email = document.getElementById('regEmail').value.trim();
const password = document.getElementById('regPassword').value.trim();
if (!name || !email || !password) {
showAlert('Vul alle velden in om te registreren.', 'error');
return;
}
tempRegEmail = email;
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
showAlert(res.message, res.success ? 'success' : 'error');
if (res.success) {
showScreen('screen-register-verify');
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout bij registratie: ' + err.message, 'error');
})
.g20RequestRegistrationCode(name, email, password);
}
function handleVerifyCode() {
const code = document.getElementById('verifyCodeInput').value.trim();
if (!code) {
showAlert('Vul de verificatiecode in.', 'error');
return;
}
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
showAlert(res.message, res.success ? 'success' : 'error');
if (res.success) {
showScreen('screen-login');
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout bij verifiëren: ' + err.message, 'error');
})
.g20VerifyCode(tempRegEmail, code);
}
function handleRequestForgot() {
const email = document.getElementById('forgotEmail').value.trim();
if (!email) {
showAlert('Vul je e-mailadres in.', 'error');
return;
}
tempForgotEmail = email;
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
showAlert(res.message, res.success ? 'success' : 'error');
if (res.success) {
document.getElementById('resetSection').style.display = 'block';
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout: ' + err.message, 'error');
})
.g20RequestPasswordReset(email);
}
function handleExecuteReset() {
const code = document.getElementById('resetCode').value.trim();
const newPassword = document.getElementById('newPassword').value.trim();
if (!code || !newPassword) {
showAlert('Vul de herstelcode en je nieuwe wachtwoord in.', 'error');
return;
}
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
showAlert(res.message, res.success ? 'success' : 'error');
if (res.success) {
showScreen('screen-login');
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout: ' + err.message, 'error');
})
.g20ResetPassword(tempForgotEmail, code, newPassword);
}
// --- G20 FORMULIER & VRAGEN ---
function openG20Form() {
const today = new Date().toISOString().split('T')[0];
document.getElementById('dailyDate').value = today;
showScreen('screen-g20-form');
loadQuestionsAndData(today);
}
function loadDailyForDate() {
const datum = document.getElementById('dailyDate').value;
loadQuestionsAndData(datum);
}
function loadQuestionsAndData(datum) {
showSpinner(true);
google.script.run
.withSuccessHandler(qRes => {
if (qRes.success) {
cachedQuestions = qRes.questions;
renderQuestions(cachedQuestions);
google.script.run
.withSuccessHandler(dRes => {
showSpinner(false);
document.getElementById('dailyInzicht').value = '';
document.getElementById('dailyMooiste').value = '';
document.getElementById('dailyStap').value = '';
cachedQuestions.forEach(q => {
const field = document.getElementById('input_' + q.code);
if (field) field.value = '';
});
if (dRes.success && dRes.data) {
document.getElementById('dailyInzicht').value = dRes.data.Inzicht || '';
document.getElementById('dailyMooiste').value = dRes.data.MooisteMoment || '';
document.getElementById('dailyStap').value = dRes.data.MorgenStap || '';
cachedQuestions.forEach(q => {
const field = document.getElementById('input_' + q.code);
if (field && dRes.data[q.code] !== undefined) {
field.value = dRes.data[q.code];
}
});
}
})
.g20GetDaily(currentUser.userId, datum);
} else {
showSpinner(false);
showAlert('Kon vragen niet laden.', 'error');
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout: ' + err.message, 'error');
})
.g20GetQuestions();
}
function renderQuestions(questions) {
const container = document.getElementById('g20QuestionsContainer');
let html = '';
questions.forEach(q => {
html += `
<div class="g20-item">
<div class="g20-item-header">
<span class="g20-emoji">${q.emoji}</span>
<span class="g20-title">${q.number}. ${q.title} (${q.code})</span>
</div>
<div class="g20-question">${q.question}</div>
<div class="form-group" style="margin-bottom:0;">
<textarea id="input_${q.code}" rows="2" placeholder="Jouw antwoord..."></textarea>
</div>
</div>
`;
});
container.innerHTML = html;
}
function saveG20Daily() {
const datum = document.getElementById('dailyDate').value;
if (!datum) {
showAlert('Selecteer een datum.', 'error');
return;
}
let payload = {
UserID: currentUser.userId,
Datum: datum,
Inzicht: document.getElementById('dailyInzicht') ? document.getElementById('dailyInzicht').value : '',
MooisteMoment: document.getElementById('dailyMooiste') ? document.getElementById('dailyMooiste').value : '',
MorgenStap: document.getElementById('dailyStap') ? document.getElementById('dailyStap').value : ''
};
const gCodes = [
'G1_Geluksbesef', 'G2_Gevoelens', 'G3_Gedroomd', 'G4_Gemis', 'G5_Gewenst',
'G6_Gehoopt', 'G7_Gedachten', 'G8_Gedrag', 'G9_Gezegd', 'G10_Gedaan',
'G11_Geschreven', 'G12_Gelezen', 'G13_Geleerd', 'G14_Geweten', 'G15_Gehoord',
'G16_Gezien', 'G17_Geproefd', 'G18_Geroken', 'G19_GedachtOver', 'G20_Geluksgetal'
];
gCodes.forEach(code => {
let el = document.getElementById('input_' + code);
payload[code] = el ? el.value : '';
});
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
showAlert(res.message, res.success ? 'success' : 'error');
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout bij opslaan: ' + err.message, 'error');
})
.g20SaveDaily(payload);
}
// --- GESCHIEDENIS ---
function openHistory() {
showScreen('screen-history');
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
const container = document.getElementById('historyListContainer');
if (!res.success || !res.history || res.history.length === 0) {
container.innerHTML = '<p class="sub">Je hebt nog geen dagen ingevuld.</p>';
return;
}
let html = '';
res.history.sort((a, b) => new Date(b.Datum) - new Date(a.Datum)).forEach(row => {
let formattedDate = row.Datum;
try { formattedDate = new Date(row.Datum).toISOString().split('T')[0]; } catch(e){}
html += `
<div class="history-card">
<div class="history-info">
<h4>📅 Datum: ${formattedDate}</h4>
<p><strong>Inzicht:</strong> ${row.Inzicht ? row.Inzicht.substring(0, 60) + '...' : 'Geen inzicht ingevuld'}</p>
</div>
<button class="btn" style="width: auto; padding: 8px 15px; margin-top:0;" onclick="editDay('${formattedDate}')">Bewerken</button>
</div>
`;
});
container.innerHTML = html;
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout bij ophalen geschiedenis: ' + err.message, 'error');
})
.g20GetUserHistory(currentUser.userId);
}
function editDay(datum) {
showScreen('screen-g20-form');
document.getElementById('dailyDate').value = datum;
loadQuestionsAndData(datum);
}
// --- KAARTEN & PROFIEL ---
function loadCards() {
showScreen('screen-cards');
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
const container = document.getElementById('cardsContainer');
if (!res.success || !res.cards) {
container.innerHTML = '<p class="sub">Geen kaarten beschikbaar.</p>';
return;
}
let html = '';
res.cards.forEach(c => {
html += `
<div class="g20-item">
<div class="g20-title">${c.title} <span style="font-size:0.8rem; color:var(--text-light);">(${c.category})</span></div>
<p style="margin-top:8px; font-size:0.9rem;">${c.description}</p>
</div>
`;
});
container.innerHTML = html;
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout: ' + err.message, 'error');
})
.g20GetCards();
}
function openProfile() {
showScreen('screen-profile');
document.getElementById('profileEmail').value = currentUser.email;
document.getElementById('profileName').value = currentUser.name;
document.getElementById('profileQuote').value = currentUser.quote || '';
document.getElementById('profileTheme').value = currentUser.favoriteTheme || '';
}
function saveProfile() {
const profileData = {
name: document.getElementById('profileName').value,
quote: document.getElementById('profileQuote').value,
favoriteTheme: document.getElementById('profileTheme').value
};
showSpinner(true);
google.script.run
.withSuccessHandler(res => {
showSpinner(false);
showAlert(res.message, res.success ? 'success' : 'error');
if (res.success) {
currentUser.name = profileData.name;
document.getElementById('welcomeUser').innerText = 'Welkom, ' + currentUser.name;
}
})
.withFailureHandler(err => {
showSpinner(false);
showAlert('Fout bij opslaan profiel: ' + err.message, 'error');
})
.g20SaveProfile(currentUser.userId, profileData);
}
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>G20 — Dagelijks Geluksbesef | Coaching by Gabeler.eu</title>
<style>
:root {
--primary: #1e3a8a;
--primary-light: #3b82f6;
--accent: #10b981;
--bg: #f8fafc;
--card-bg: #ffffff;
--text: #1e293b;
--text-light: #64748b;
--border: #e2e8f0;
--radius: 12px;
}
* { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
body { background-color: var(--bg); color: var(--text); min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; padding: 20px; }
.container { width: 100%; max-width: 800px; background: var(--card-bg); border-radius: var(--radius); box-shadow: 0 10px 25px -5px rgba(0,0,0,0.05), 0 8px 10px -6px rgba(0,0,0,0.05); padding: 30px; margin-top: 20px; margin-bottom: 40px; }
header { text-align: center; margin-bottom: 30px; border-bottom: 2px solid var(--border); padding-bottom: 20px; }
header h1 { color: var(--primary); font-size: 1.8rem; margin-bottom: 5px; }
header p { color: var(--text-light); font-size: 0.95rem; }
.header-links { margin-top: 10px; font-size: 0.9rem; }
.header-links a { color: var(--primary-light); text-decoration: none; font-weight: 600; margin: 0 8px; }
.header-links a:hover { text-decoration: underline; }
.screen { display: none; }
.screen.active { display: block; animation: fadeIn 0.3s ease-in-out; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
h2 { color: var(--primary); font-size: 1.4rem; margin-bottom: 15px; }
p.sub { color: var(--text-light); font-size: 0.9rem; margin-bottom: 20px; }
.form-group { margin-bottom: 15px; }
label { display: block; font-weight: 600; font-size: 0.85rem; margin-bottom: 5px; color: var(--text); }
input, select, textarea { width: 100%; padding: 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 0.95rem; outline: none; transition: border-color 0.2s; }
input:focus, select:focus, textarea:focus { border-color: var(--primary-light); }
.btn { display: inline-block; width: 100%; background: var(--primary); color: white; border: none; padding: 12px 20px; font-size: 1rem; font-weight: 600; border-radius: 8px; cursor: pointer; text-align: center; transition: background 0.2s; margin-top: 10px; }
.btn:hover { background: #1d4ed8; }
.btn-secondary { background: #64748b; }
.btn-secondary:hover { background: #475569; }
.btn-accent { background: var(--accent); }
.btn-accent:hover { background: #059669; }
.link-text { text-align: center; margin-top: 15px; font-size: 0.9rem; color: var(--text-light); }
.link-text a { color: var(--primary-light); text-decoration: none; font-weight: 600; cursor: pointer; }
.link-text a:hover { text-decoration: underline; }
/* Dashboard & Nav */
.navbar { display: flex; justify-content: space-between; align-items: center; background: var(--primary); color: white; padding: 12px 20px; border-radius: 8px; margin-bottom: 25px; }
.navbar span { font-weight: 600; font-size: 0.9rem; }
.navbar button { background: rgba(255,255,255,0.2); border: none; color: white; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 0.85rem; }
.navbar button:hover { background: rgba(255,255,255,0.3); }
.menu-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 15px; margin-bottom: 25px; }
.menu-card { background: var(--bg); border: 1px solid var(--border); padding: 20px; border-radius: 8px; text-align: center; cursor: pointer; transition: all 0.2s; }
.menu-card:hover { border-color: var(--primary-light); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.05); }
.menu-card h3 { color: var(--primary); font-size: 1.1rem; margin-bottom: 5px; }
.menu-card p { font-size: 0.85rem; color: var(--text-light); }
/* G20 Form Fields */
.g20-item { background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 15px; margin-bottom: 15px; }
.g20-item-header { display: flex; align-items: center; margin-bottom: 8px; }
.g20-emoji { font-size: 1.4rem; margin-right: 10px; }
.g20-title { font-weight: 600; font-size: 1rem; color: var(--primary); }
.g20-question { font-size: 0.9rem; color: var(--text); margin-bottom: 10px; font-style: italic; }
.spinner { display: none; text-align: center; margin: 15px 0; color: var(--primary-light); font-weight: 600; }
.alert { padding: 12px; border-radius: 8px; margin-bottom: 15px; font-size: 0.9rem; display: none; }
.alert-success { background: #d1fae5; color: #065f46; border: 1px solid #a7f3d0; }
.alert-error { background: #fee2e2; color: #991b1b; border: 1px solid #fecaca; }
footer { text-align: center; margin-top: auto; font-size: 0.8rem; color: var(--text-light); padding: 20px; }
footer a { color: var(--primary-light); text-decoration: none; }
footer a:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="container">
<header>
<h1>G20 — Dagelijks Geluksbesef</h1>
<p>Coaching by Gabeler.eu | Coach Stijn Gabeler</p>
<div class="header-links">
<a href="https://sites.google.com/view/gabeler" target="_blank">Gabeler.eu</a> |
<a href="https://sites.google.com/view/coachstijn" target="_blank">Coach Stijn</a>
</div>
</header>
<div id="alertBox" class="alert"></div>
<div id="spinner" class="spinner">Even geduld, bezig met verwerken...</div>
<!-- SCHERM 1: LOGIN -->
<div id="screen-login" class="screen active">
<h2>Inloggen</h2>
<p class="sub">Log in met je e-mailadres en gekozen wachtwoord.</p>
<div class="form-group">
<label>E-mailadres</label>
<input type="email" id="loginEmail" placeholder="jouw@email.nl">
</div>
<div class="form-group">
<label>Wachtwoord</label>
<input type="password" id="loginPassword" placeholder="••••••••">
</div>
<button class="btn" onclick="handleLogin()">Inloggen</button>
<div class="link-text">
Nog geen account? <a onclick="showScreen('screen-register-req')">Registreer hier (Gratis voorproefje)</a><br>
<a onclick="showScreen('screen-forgot')" style="margin-top: 8px; display: inline-block;">Wachtwoord vergeten?</a>
</div>
</div>
<!-- SCHERM 2A: REGISTRATIE AANVRAAG -->
<div id="screen-register-req" class="screen">
<h2>Account Registreren</h2>
<p class="sub">Vul je gegevens in om je gratis voorproefje te starten. Je ontvangt een verificatiecode per e-mail.</p>
<div class="form-group">
<label>Volledige Naam</label>
<input type="text" id="regName" placeholder="Stijn Gabeler">
</div>
<div class="form-group">
<label>E-mailadres</label>
<input type="email" id="regEmail" placeholder="jouw@email.nl">
</div>
<div class="form-group">
<label>Kies een Wachtwoord</label>
<input type="password" id="regPassword" placeholder="••••••••">
</div>
<button class="btn" onclick="handleRegisterRequest()">Verstuur Verificatiecode</button>
<div class="link-text">
Al een account? <a onclick="showScreen('screen-login')">Direct inloggen</a>
</div>
</div>
<!-- SCHERM 2B: REGISTRATIE VERIFICATIE -->
<div id="screen-register-verify" class="screen">
<h2>E-mail Verifiëren</h2>
<p class="sub">We hebben een 6-cijferige verificatiecode gestuurd naar je e-mailadres. Vul deze hieronder in.</p>
<div class="form-group">
<label>Verificatiecode (6 cijfers)</label>
<input type="text" id="verifyCodeInput" placeholder="123456" maxlength="6">
</div>
<button class="btn btn-accent" onclick="handleVerifyCode()">Account Activeren</button>
<div class="link-text">
<a onclick="showScreen('screen-login')">Terug naar inloggen</a>
</div>
</div>
<!-- SCHERM 3: WACHTWOORD HERSTELLEN -->
<div id="screen-forgot" class="screen">
<h2>Wachtwoord Herstellen</h2>
<p class="sub">Vul je e-mailadres in om een herstelcode te ontvangen.</p>
<div class="form-group">
<label>E-mailadres</label>
<input type="email" id="forgotEmail" placeholder="jouw@email.nl">
</div>
<button class="btn" onclick="handleRequestForgot()">Stuur Herstelcode</button>
<div id="resetSection" style="display:none; margin-top:20px; border-top: 1px solid var(--border); padding-top: 15px;">
<div class="form-group">
<label>Herstelcode</label>
<input type="text" id="resetCode" placeholder="123456">
</div>
<div class="form-group">
<label>Nieuw Wachtwoord</label>
<input type="password" id="newPassword" placeholder="••••••••">
</div>
<button class="btn btn-accent" onclick="handleExecuteReset()">Wachtwoord Opslaan</button>
</div>
<div class="link-text" style="margin-top: 15px;">
<a onclick="showScreen('screen-login')">Terug naar inloggen</a>
</div>
</div>
<!-- SCHERM 4: HOOFDDASHBOARD -->
<div id="screen-dashboard" class="screen">
<div class="navbar">
<span id="welcomeUser">Welkom</span>
<button onclick="handleLogout()">Uitloggen</button>
</div>
<div class="menu-grid">
<div class="menu-card" onclick="openG20Form()">
<h3>📅 Dagelijks G20</h3>
<p>Vul je dagboek en de 20 onderdelen in.</p>
</div>
<div class="menu-card" onclick="loadCards()">
<h3>🃏 Coachingkaarten</h3>
<p>Bekijk inzichten en mindfulness kaarten.</p>
</div>
<div class="menu-card" onclick="openProfile()">
<h3>👤 Mijn Profiel</h3>
<p>Beheer je quote, thema en instellingen.</p>
</div>
<div class="menu-card" onclick="openExport()">
<h3>📥 Data Exporteren</h3>
<p>Download je data als JSON, CSV of TXT.</p>
</div>
</div>
<div style="text-align: center; margin-top: 20px;">
<a href="https://wa.me/" target="_blank" style="color: var(--accent); font-weight: 600; text-decoration: none;">💬 Contact Coach Stijn via WhatsApp</a>
</div>
</div>
<!-- SCHERM 5: G20 DAGBOEK FORMULIER -->
<div id="screen-g20form" class="screen">
<div class="navbar">
<span>G20 Dagboek & Vragen</span>
<button onclick="showScreen('screen-dashboard')">Terug naar Menu</button>
</div>
<div class="form-group">
<label>Datum</label>
<input type="date" id="g20Date">
</div>
<div id="g20QuestionsContainer"></div>
<div class="form-group">
<label>Algemeen Inzicht van de dag</label>
<textarea id="g20Inzicht" rows="3" placeholder="Wat viel je vandaag op?"></textarea>
</div>
<div class="form-group">
<label>Het mooiste moment</label>
<input type="text" id="g20Moment" placeholder="Waar werd je blij van?">
</div>
<div class="form-group">
<label>Stap voor morgen</label>
<input type="text" id="g20Stap" placeholder="Wat neem je mee?">
</div>
<button class="btn btn-accent" onclick="saveG20Data()">Opslaan in Spreadsheet</button>
</div>
<!-- SCHERM 6: COACHINGKAARTEN -->
<div id="screen-cards" class="screen">
<div class="navbar">
<span>Coachingkaarten</span>
<button onclick="showScreen('screen-dashboard')">Terug naar Menu</button>
</div>
<div id="cardsContainer"></div>
</div>
<!-- SCHERM 7: PROFIEL -->
<div id="screen-profile" class="screen">
<div class="navbar">
<span>Mijn Profiel</span>
<button onclick="showScreen('screen-dashboard')">Terug naar Menu</button>
</div>
<div class="form-group">
<label>Naam</label>
<input type="text" id="profileName">
</div>
<div class="form-group">
<label>E-mailadres (niet wijzigbaar)</label>
<input type="email" id="profileEmail" disabled style="background:#f1f5f9;">
</div>
<div class="form-group">
<label>Persoonlijke Quote</label>
<input type="text" id="profileQuote" placeholder="Jij bepaalt je eigen koers.">
</div>
<div class="form-group">
<label>Favoriete Thema</label>
<input type="text" id="profileTheme" placeholder="Bijv. Verstilling of Geluksbesef">
</div>
<button class="btn" onclick="saveProfileData()">Profiel Opslaan</button>
</div>
<!-- SCHERM 8: EXPORT -->
<div id="screen-export" class="screen">
<div class="navbar">
<span>Data Exporteren</span>
<button onclick="showScreen('screen-dashboard')">Terug naar Menu</button>
</div>
<p class="sub">Kies je gewenste exportformaat om je dagboekgegevens direct op te slaan of te bekijken.</p>
<div style="display: flex; gap: 10px; margin-bottom: 20px;">
<button class="btn" onclick="executeExport('json')">Exporteer JSON</button>
<button class="btn btn-accent" onclick="executeExport('csv')">Exporteer CSV</button>
<button class="btn btn-secondary" onclick="executeExport('txt')">Exporteer TXT</button>
</div>
<div class="form-group">
<textarea id="exportOutput" rows="10" readonly placeholder="Export resultaat verschijnt hier..." style="font-family: monospace; font-size: 0.85rem;"></textarea>
</div>
</div>
</div>
<footer>
© 2026 <a href="https://sites.google.com/view/gabeler" target="_blank">Gabeler.eu</a> —
<a href="https://sites.google.com/view/coachstijn" target="_blank">Coach Stijn</a> —
Alle rechten voorbehouden.
</footer>
<script>
let currentUser = null;
let cachedQuestions = [];
let pendingRegEmail = '';
function showScreen(screenId) {
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
document.getElementById(screenId).classList.add('active');
hideAlert();
}
function showAlert(msg, isError = false) {
const box = document.getElementById('alertBox');
box.innerText = msg;
box.className = 'alert ' + (isError ? 'alert-error' : 'alert-success');
box.style.display = 'block';
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function hideAlert() {
document.getElementById('alertBox').style.display = 'none';
}
function setSpinner(show) {
document.getElementById('spinner').style.display = show ? 'block' : 'none';
}
// --- AUTH LOGICA ---
function handleLogin() {
const email = document.getElementById('loginEmail').value;
const pass = document.getElementById('loginPassword').value;
if (!email || !pass) {
showAlert('Vul zowel e-mailadres als wachtwoord in.', true);
return;
}
setSpinner(true);
google.script.run
.withSuccessHandler(res => {
setSpinner(false);
if (res.success) {
currentUser = res.user;
if (currentUser.status !== 'ACTIVE') {
showAlert('Je account is nog niet geverifieerd met de mailcode.', true);
pendingRegEmail = email;
showScreen('screen-register-verify');
return;
}
document.getElementById('welcomeUser').innerText = 'Welkom, ' + currentUser.name;
showScreen('screen-dashboard');
} else {
showAlert(res.message, true);
}
})
.withFailureHandler(err => {
setSpinner(false);
showAlert('Fout bij inloggen: ' + err.message, true);
})
.g20Login(email, pass);
}
function handleRegisterRequest() {
const name = document.getElementById('regName').value;
const email = document.getElementById('regEmail').value;
const pass = document.getElementById('regPassword').value;
if (!name || !email || !pass) {
showAlert('Vul alle velden in voor registratie.', true);
return;
}
pendingRegEmail = email;
setSpinner(true);
google.script.run
.withSuccessHandler(res => {
setSpinner(false);
if (res.success) {
showAlert(res.message);
showScreen('screen-register-verify');
} else {
showAlert(res.message, true);
}
})
.withFailureHandler(err => {
setSpinner(false);
showAlert('Fout: ' + err.message, true);
})
.g20RequestRegistrationCode(name, email, pass);
}
function handleVerifyCode() {
const code = document.getElementById('verifyCodeInput').value;
if (!code) {
showAlert('Vul de verificatiecode in.', true);
return;
}
if (!pendingRegEmail) {
pendingRegEmail = document.getElementById('loginEmail').value;
}
setSpinner(true);
google.script.run
.withSuccessHandler(res => {
setSpinner(false);
if (res.success) {
showAlert(res.message);
showScreen('screen-login');
} else {
showAlert(res.message, true);
}
})
.withFailureHandler(err => {
setSpinner(false);
showAlert('Fout: ' + err.message, true);
})
.g20VerifyCode(pendingRegEmail, code);
}
function handleRequestForgot() {
const email = document.getElementById('forgotEmail').value;
if (!email) {
showAlert('Vul je e-mailadres in.', true);
return;
}
setSpinner(true);
google.script.run
.withSuccessHandler(res => {
setSpinner(false);
if (res.success) {
showAlert(res.message);
document.getElementById('resetSection').style.display = 'block';
} else {
showAlert(res.message, true);
}
})
.withFailureHandler(err => {
setSpinner(false);
showAlert('Fout: ' + err.message, true);
})
.g20RequestPasswordReset(email);
}
function handleExecuteReset() {
const email = document.getElementById('forgotEmail').value;
const code = document.getElementById('resetCode').value;
const newPass = document.getElementById('newPassword').value;
if (!code || !newPass) {
showAlert('Vul de herstelcode en het nieuwe wachtwoord in.', true);
return;
}
setSpinner(true);
google.script.run
.withSuccessHandler(res => {
setSpinner(false);
if (res.success) {
showAlert(res.message);
showScreen('screen-login');
} else {
showAlert(res.message, true);
}
})
.withFailureHandler(err => {
setSpinner(false);
showAlert('Fout: ' + err.message, true);
})
.g20ResetPassword(email, code, newPass);
}
function handleLogout() {
currentUser = null;
showScreen('screen-login');
}
// --- G20 DAGBOEK & VRAGEN ---
function openG20Form() {
document.getElementById('g20Date').valueAsDate = new Date();
setSpinner(true);
google.script.run
.withSuccessHandler(res => {
setSpinner(false);
if (res.success) {
cachedQuestions = res.questions;
renderG20Form(res.questions);
loadExistingDaily();
showScreen('screen-g20form');
} else {
showAlert('Kon vragen niet laden: ' + res.message, true);
}
})
.withFailureHandler(err => {
setSpinner(false);
showAlert('Fout: ' + err.message, true);
})
.g20GetQuestions();
}
function renderG20Form(questions) {
const container = document.getElementById('g20QuestionsContainer');
let html = '';
questions.forEach(q => {
html += `
<div class="g20-item">
<div class="g20-item-header">
<span class="g20-emoji">${q.emoji}</span>
<span class="g20-title">${q.number}. ${q.title}</span>
</div>
<div class="g20-question">${q.question}</div>
<input type="text" id="g_answer_${q.number}" placeholder="Jouw antwoord of ervaring...">
</div>
`;
});
container.innerHTML = html;
}
function loadExistingDaily() {
const datum = document.getElementById('g20Date').value;
google.script.run
.withSuccessHandler(res => {
if (res.success && res.data) {
document.getElementById('g20Inzicht').value = res.data.Inzicht || '';
document.getElementById('g20Moment').value = res.data.MooisteMoment || '';
document.getElementById('g20Stap').value = res.data.MorgenStap || '';
cachedQuestions.forEach(q => {
const field = document.getElementById('g_answer_' + q.number);
if (field) field.value = res.data['G' + q.number] || '';
});
}
})
.g20GetDaily(currentUser.userId, datum);
}
function saveG20Data() {
const payload = {
UserID: currentUser.userId,
Datum: document.getElementById('g20Date').value,
Inzicht: document.getElementById('g20Inzicht').value,
MooisteMoment: document.getElementById('g20Moment').value,
MorgenStap: document.getElementById('g20Stap').value
};
cachedQuestions.forEach(q => {
const field = document.getElementById('g_answer_' + q.number);
payload['G' + q.number] = field ? field.value : '';
});
setSpinner(true);
google.script.run
.withSuccessHandler(res => {
setSpinner(false);
if (res.success) {
showAlert(res.message);
} else {
showAlert(res.message, true);
}
})
.withFailureHandler(err => {
setSpinner(false);
showAlert('Fout bij opslaan: ' + err.message, true);
})
.g20SaveDaily(payload);
}
// --- KAARTEN ---
function loadCards() {
setSpinner(true);
google.script.run
.withSuccessHandler(res => {
setSpinner(false);
if (res.success) {
const container = document.getElementById('cardsContainer');
let html = '';
res.cards.forEach(c => {
html += `
<div class="g20-item">
<div class="g20-title">${c.title} <span style="font-size:0.75rem; color:var(--text-light);">(${c.category})</span></div>
<p style="margin-top:5px; font-size:0.9rem;">${c.description}</p>
</div>
`;
});
container.innerHTML = html;
showScreen('screen-cards');
} else {
showAlert(res.message, true);
}
})
.g20GetCards();
}
// --- PROFIEL ---
function openProfile() {
setSpinner(true);
google.script.run
.withSuccessHandler(res => {
setSpinner(false);
if (res.success) {
document.getElementById('profileName').value = res.profile.name;
document.getElementById('profileEmail').value = res.profile.email;
document.getElementById('profileQuote').value = res.profile.quote;
document.getElementById('profileTheme').value = res.profile.favoriteTheme;
showScreen('screen-profile');
} else {
showAlert(res.message, true);
}
})
.g20GetProfile(currentUser.userId);
}
function saveProfileData() {
const profileData = {
name: document.getElementById('profileName').value,
quote: document.getElementById('profileQuote').value,
favoriteTheme: document.getElementById('profileTheme').value
};
setSpinner(true);
google.script.run
.withSuccessHandler(res => {
setSpinner(false);
if (res.success) {
showAlert(res.message);
currentUser.name = profileData.name;
document.getElementById('welcomeUser').innerText = 'Welkom, ' + currentUser.name;
} else {
showAlert(res.message, true);
}
})
.g20SaveProfile(currentUser.userId, profileData);
}
// --- EXPORT ---
function openExport() {
document.getElementById('exportOutput').value = '';
showScreen('screen-export');
}
function executeExport(format) {
setSpinner(true);
google.script.run
.withSuccessHandler(res => {
setSpinner(false);
if (res.success) {
document.getElementById('exportOutput').value = res.data;
showAlert('Export succesvol gegenereerd!');
} else {
showAlert(res.message, true);
}
})
.g20ExportData(currentUser.userId, format);
}
</script>
</body>
</html>