Files
Opkomstenplanner/index.html
T
2026-10-04 10:11:48 +02:00

1963 lines
68 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Victorie Opkomsten Planner</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-color: #e95551;
--secondary-color: #f0f3f8;
--text-color: #2d3436;
--border-color: #dfe6e9;
--delete-color: #d63031;
--add-color: #00b894;
--copy-color: #fdcb6e;
--move-color: #e95551;
--category-color: #d63031;
--info-color: #2ecc71;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: #e9ecef;
color: var(--text-color);
margin: 0;
padding: 10px;
display: flex;
justify-content: center;
min-height: 100vh;
-webkit-text-size-adjust: 100%;
}
.container {
width: 100%;
max-width: 850px;
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
position: relative;
box-sizing: border-box;
margin-bottom: 20vh;
}
#loading-overlay {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(255,255,255,0.95);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 2000;
border-radius: 12px;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid var(--primary-color);
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin-bottom: 15px;
}
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
header {
text-align: center;
margin-bottom: 20px;
border-bottom: 3px solid var(--secondary-color);
padding-bottom: 15px;
}
h1 { margin: 0; color: var(--primary-color); font-size: 2rem; line-height: 1.2; }
p.subtitle {
color: #636e72;
margin-top: 10px;
font-size: 0.9rem;
line-height: 1.4;
cursor: pointer;
text-decoration: underline;
transition: color 0.2s;
}
p.subtitle:hover {
color: var(--primary-color);
}
.setup-section {
background: var(--primary-color);
padding: 15px;
border-radius: 10px;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
color: white;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.setup-label {
font-weight: 600;
font-size: 1rem;
display: flex;
align-items: center;
gap: 5px;
white-space: nowrap;
}
input[type="time"] {
padding: 10px;
border-radius: 5px;
border: none;
font-size: 1rem;
width: 140px;
cursor: pointer;
outline: none;
color: #333;
}
.extra-info-container {
background-color: var(--secondary-color);
padding: 15px;
border-radius: 10px;
margin: 20px 0;
border: 1px solid var(--border-color);
}
.extra-info-title {
font-weight: 600;
color: var(--primary-color);
margin-bottom: 10px;
font-size: 1.1rem;
}
.extra-info-list {
margin: 0;
padding-left: 20px;
}
.extra-info-item {
margin-bottom: 8px;
color: var(--text-color);
}
.extra-info-item a {
color: var(--primary-color);
text-decoration: underline;
word-break: break-word;
}
.sort-controls {
display: flex;
justify-content: space-between;
padding: 8px 12px;
background-color: #f8f9fa;
border-bottom: 1px solid #eee;
margin-bottom: 5px;
}
.sort-btn {
background: none;
border: none;
color: var(--primary-color);
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
padding: 4px 8px;
border-radius: 4px;
transition: background 0.2s;
}
.sort-btn:hover, .sort-btn.active {
background-color: rgba(233, 85, 81, 0.1);
}
.category-filter {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 8px 12px;
background-color: #f8f9fa;
border-bottom: 1px solid #eee;
margin-bottom: 5px;
}
.category-chip {
background-color: var(--category-color);
color: white;
padding: 4px 10px;
border-radius: 16px;
font-size: 0.8rem;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 4px;
}
.category-chip:hover {
background-color: #b91c18;
transform: scale(1.05);
}
.category-chip.active {
background-color: var(--primary-color);
font-weight: 600;
}
.category-chip .close-btn {
font-size: 0.8rem;
cursor: pointer;
}
.action-buttons {
display: flex;
gap: 10px;
margin-top: 20px;
border-top: 2px solid var(--secondary-color);
padding-top: 15px;
}
button {
padding: 10px 16px;
cursor: pointer;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 8px;
color: white;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
touch-action: manipulation;
box-sizing: border-box;
}
.btn-add {
background-color: var(--add-color);
width: 100%;
justify-content: center;
font-size: 16px;
padding: 15px;
margin-top: 15px;
border: 2px solid var(--border-color);
border-radius: 12px;
box-sizing: border-box;
}
.btn-add:hover { background-color: #00a884; }
@media (max-width: 600px) {
.btn-add {
font-size: 14px;
padding: 12px;
margin-top: 10px;
}
.action-buttons {
flex-direction: column;
gap: 10px;
}
.btn-share, .btn-copy {
width: 100%;
justify-content: center;
}
.btn-copy[onclick="addDay()"] {
background-color: var(--add-color);
color: white;
width: 100%;
justify-content: center;
font-size: 14px;
padding: 12px;
}
}
.btn-share { background-color: var(--primary-color); justify-content: center; flex-grow: 1; }
.btn-share:hover { background-color: #d63031; }
.btn-copy { background-color: var(--copy-color); color: #333; flex-grow: 1; justify-content: center; }
.btn-copy:hover { background-color: #e1b12c; }
.activities-list {
display: flex;
flex-direction: column;
gap: 15px;
margin-top: 10px;
min-height: 200px;
}
.activity-row {
background: #fff;
padding: 15px;
border: 2px solid var(--border-color);
border-radius: 12px;
transition: transform 0.2s, box-shadow 0.2s;
position: relative;
touch-action: manipulation;
box-sizing: border-box;
}
.activity-row:hover {
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.activity-row {
display: grid;
grid-template-columns: 40px 100px 1fr 40px;
grid-template-rows: auto auto;
gap: 10px;
align-items: center;
}
@media (max-width: 600px) {
.activity-row {
grid-template-columns: 35px 80px 1fr 35px;
padding: 12px;
}
h1 { font-size: 1.8rem; }
.setup-section {
flex-direction: column;
align-items: stretch;
text-align: center;
}
.setup-label { width: 100%; justify-content: center; margin-bottom: 5px; }
input[type="time"] { width: 100%; box-sizing: border-box; }
}
.move-controls {
display: flex;
flex-direction: column;
gap: 5px;
justify-content: center;
align-items: center;
color: var(--move-color);
}
.move-btn {
background: none;
border: none;
font-size: 1.2rem;
cursor: pointer;
padding: 2px;
color: #ccc;
transition: color 0.2s;
}
.move-btn.up:hover { color: var(--add-color); }
.move-btn.down:hover { color: var(--primary-color); }
.time-column {
background-color: var(--secondary-color);
padding: 8px 5px;
border-radius: 8px;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: 1px solid #dcdcdc;
}
.time-label { font-size: 8px; text-transform: uppercase; color: #777; }
.time-value { font-family: 'Courier New', Courier, monospace; font-weight: bold; font-size: 1rem; color: var(--primary-color); }
.duration-wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
}
.duration-label { font-size: 8px; color: #888; }
input[type="number"] {
width: 70px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 16px;
text-align: center;
font-weight: bold;
color: var(--text-color);
box-sizing: border-box;
}
.activity-input-wrapper {
width: 100%;
grid-column: 1 / -1;
}
.activity-input {
width: 100%;
font-size: 18px;
font-weight: 600;
border: none;
border-bottom: 2px solid var(--secondary-color);
padding: 10px 0;
color: var(--text-color);
background: transparent;
box-sizing: border-box;
}
.activity-input:focus {
border-bottom: 2px solid var(--primary-color);
outline: none;
background-color: #fafafa;
}
.btn-delete {
background-color: var(--delete-color);
padding: 8px;
font-size: 16px;
width: 35px;
height: 35px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
flex-shrink: 0;
}
.btn-delete:hover { background-color: #c0392b; transform: scale(1.1); }
.suggestions-list {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: #fff;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 100;
max-height: 400px;
overflow-y: auto;
display: none;
width: 100%;
}
.suggestions-list.show { display: block; }
.suggestion-item {
padding: 12px;
cursor: pointer;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
transition: background 0.2s;
}
.suggestion-item:hover { background-color: var(--secondary-color); }
.suggestion-item .duration-suggestion { font-size: 0.8rem; color: #666; background: #e0e0e0; padding: 2px 8px; border-radius: 10px; }
.suggestion-item .category-badge { font-size: 0.7rem; color: #fff; background: var(--category-color); padding: 2px 6px; border-radius: 4px; margin-right: 8px; }
.suggestion-item .info-badge {
font-size: 0.7rem;
color: #fff;
background: var(--info-color);
padding: 2px 6px;
border-radius: 4px;
margin-left: 8px;
}
/* Mobile-specific suggestion item layout */
@media (max-width: 600px) {
.suggestion-item {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.suggestion-item > span:first-child {
width: 100%;
}
.suggestion-item > span:last-child {
display: flex;
justify-content: flex-end;
width: 100%;
gap: 8px;
}
.suggestion-item .info-badge {
margin-left: 0;
margin-right: auto;
order: -1;
}
}
.scroll-bottom-btn {
position: fixed;
bottom: 20px;
right: 20px;
background-color: var(--primary-color);
color: white;
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 10px rgba(0,0,0,0.3);
z-index: 1000;
font-size: 24px;
cursor: pointer;
transition: transform 0.2s;
}
.scroll-bottom-btn:hover { transform: scale(1.1); background-color: #d63031; }
#toast {
visibility: hidden;
min-width: 250px;
background-color: #333;
color: #fff;
text-align: center;
border-radius: 25px;
padding: 16px;
position: fixed;
z-index: 1000;
left: 50%;
bottom: 30px;
transform: translateX(-50%);
font-size: 17px;
max-width: 90%;
width: auto;
}
#toast.show { visibility: visible; animation: fadein 0.5s, fadeout 0.5s 2.5s; }
@keyframes fadein { from {bottom: 0; opacity: 0;} to {bottom: 30px; opacity: 1;} }
@keyframes fadeout { from {bottom: 30px; opacity: 1;} to {bottom: 0; opacity: 0;} }
@media print {
body { background: white; padding: 0; }
.container { box-shadow: none; max-width: 100%; padding: 0; border: none; }
.setup-section, .btn-share, .btn-copy, .move-controls, .duration-wrapper, .suggestions-list, #loading-overlay, .scroll-bottom-btn { display: none !important; }
.activity-row { break-inside: avoid; border: 1px solid #ccc; margin-bottom: 15px; padding: 15px; background: white; }
.time-column { border: 1px solid #000; background: #eee; }
.activity-input { border-bottom: 1px solid #000; }
}
.day-header {
background: var(--primary-color);
color: white;
padding: 15px;
border-radius: 10px;
margin-bottom: 15px;
display: flex;
justify-content: space-between;
align-items: center;
}
.day-header h3 {
margin: 0;
font-size: 1.2rem;
}
.day-setup {
display: flex;
align-items: center;
gap: 10px;
}
.day-header .setup-label {
color: white;
font-weight: 600;
display: flex;
align-items: center;
gap: 5px;
}
.day-header input[type="time"] {
padding: 5px;
border-radius: 4px;
border: none;
font-size: 0.9rem;
}
.btn-delete-day:hover {
background-color: #c0392b !important;
transform: scale(1.05);
}
.day-controls {
display: flex;
flex-direction: column;
gap: 2px;
margin-right: 15px;
}
.day-move-btn {
background: none;
border: none;
font-size: 0.8rem;
cursor: pointer;
padding: 2px;
color: #ccc;
transition: color 0.2s;
border-radius: 2px;
}
.day-move-btn:hover:not(:disabled) {
color: var(--add-color);
background-color: rgba(0, 184, 148, 0.1);
}
.day-move-btn:disabled {
color: #eee;
cursor: not-allowed;
opacity: 0.5;
}
.day-container {
transition: transform 0.5s ease, opacity 0.5s ease;
}
.day-container.moving-up {
transform: translateY(-100%);
opacity: 0.7;
}
.day-container.moving-down {
transform: translateY(100%);
opacity: 0.7;
}
.activity-row {
transition: transform 0.4s ease, opacity 0.4s ease;
}
.activity-row.moving-up {
transform: translateY(-80px);
opacity: 0.6;
}
.activity-row.moving-down {
transform: translateY(80px);
opacity: 0.6;
}
</style>
</head>
<body>
<div class="container" id="planner-content">
<div id="loading-overlay">
<div class="spinner"></div>
<p id="loading-text">Verbinden met Google Sheets...</p>
</div>
<header>
<h1><a href="/" style="color: var(--primary-color); text-decoration: none;">Victorie Opkomsten Planner</a></h1>
<p class="subtitle" onclick="window.open('https://docs.google.com/spreadsheets/d/1Bqln-FYGSEkxDi7c34HacnLAjXIDX2kB3KaUTC5Iqy4/edit?usp=sharing', '_blank')">
Live gekoppeld aan de activiteiten tabel in de drive
</p>
</header>
<div class="setup-section">
<div class="setup-label">
<i class="fa-solid fa-clock"></i> Starttijd Dag
</div>
<input type="time" id="dayStartTime" value="08:00" onchange="recalculateEntireSchedule()">
</div>
<div id="activitiesContainer" class="activities-list">
<div style="text-align: center; color: #aaa; padding: 40px; border: 2px dashed #ddd; border-radius: 10px; display: flex; flex-direction: column; align-items: center;">
<i class="fa-solid fa-list-ul" style="font-size: 3rem; margin-bottom: 15px; color: #ddd;"></i>
<p>Je planning is leeg.</p>
</div>
</div>
<!-- Add Activity Button - only visible in single day mode -->
<button class="btn-add" onclick="addActivityRow()" id="mainAddButton" style="display: none;">
<i class="fa-solid fa-plus"></i> Activiteit Toevoegen
</button>
<!-- Extra Info Container -->
<div id="extraInfoContainer" class="extra-info-container" style="display: none;">
<h3 class="extra-info-title">Extra Informatie</h3>
<ul id="extraInfoList" class="extra-info-list"></ul>
</div>
<div class="action-buttons">
<button class="btn-share" onclick="shareSchedule()">
<i class="fa-solid fa-share-nodes"></i> Deel Planning
</button>
<button class="btn-copy" onclick="copySchedule()">
<i class="fa-solid fa-copy"></i> Kopieer Tekst
</button>
<button class="btn-copy" onclick="addDay()" style="background-color: var(--add-color); color: white; ">
<i class="fa-solid fa-plus"></i> Dag Toevoegen
</button>
</div>
</div>
<div class="scroll-bottom-btn" onclick="window.scrollTo({top: document.body.scrollHeight, behavior: 'smooth'})">
<i class="fa-solid fa-arrow-down"></i>
</div>
<div id="toast"></div>
<script>
let activitiesDatabase = [];
let activityExtraInfo = {};
let currentSort = 'name';
let activeCategories = [];
let activityIdMap = {}; // Map to store activity ID for each row
let days = [{ startTime: '08:00', activities: [] }]; // Multi-day structure
let currentDayIndex = 0; // Track current day for adding activities
// Changed to TSV URL (tab-separated values)
const CSV_URL = "https://docs.google.com/spreadsheets/d/1Bqln-FYGSEkxDi7c34HacnLAjXIDX2kB3KaUTC5Iqy4/export?format=tsv";
// Function to capitalize each word in a string
function capitalizeWords(str) {
return str.split(' ').map(word =>
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
).join(' ');
}
// Function to convert / to , in benodigheden
function formatBenodigheden(str) {
if (!str) return '';
return str.split('/').map(item => item.trim()).join(', ');
}
// Updated parseTSV function to handle tab-separated values
function parseTSV(text) {
const lines = text.split(/\r\n|\n/);
const result = [];
const allCategories = new Set();
for (let i = 1; i < lines.length; i++) {
if (lines[i].trim() === "") continue;
// Split by tab character instead of comma
const row = lines[i].split("\t");
if (row.length >= 3) {
const id = row[0].trim();
const name = capitalizeWords(row[1].trim());
let duration = parseInt(row[2].trim());
if (isNaN(duration) || duration < 1) duration = 60;
// Parse and capitalize categories (now using tab separation)
const category = row.length >= 4 ? row[3].trim() : "";
const categories = category.split('/').map(c => capitalizeWords(c.trim())).filter(c => c);
categories.forEach(cat => allCategories.add(cat));
// Get doel (column F - index 5)
const doel = row.length >= 6 ? row[5].trim() : "";
// Get benodigheden (column G - index 6) and convert / to ,
const benodigheden = row.length >= 7 ? formatBenodigheden(row[6].trim()) : "";
// Get extra info (column E - index 4)
const extraInfo = row.length >= 5 ? row[4].trim() : "";
result.push({
id,
name,
duration,
categories,
category,
doel,
benodigheden,
extraInfo
});
// Store all extra info in a structured way with ID as key
const infoObj = {};
if (extraInfo) infoObj.extra = extraInfo;
if (doel) infoObj.doel = doel;
if (benodigheden) infoObj.benodigheden = benodigheden;
if (Object.keys(infoObj).length > 0) {
activityExtraInfo[id] = infoObj; // Use ID as key instead of name
}
}
}
// Store all unique categories
window.allCategories = Array.from(allCategories).sort();
return result;
}
// Function to make URLs clickable
function makeUrlsClickable(text) {
const urlRegex = /(https?:\/\/[^\s]+)/g;
return text.replace(urlRegex, function(url) {
return `<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>`;
});
}
// Function to sort activities
function sortActivities(activities, sortBy) {
return [...activities].sort((a, b) => {
if (sortBy === 'name') {
return a.name.localeCompare(b.name);
} else if (sortBy === 'category') {
const aCat = a.categories.length > 0 ? a.categories[0] : '';
const bCat = b.categories.length > 0 ? b.categories[0] : '';
if (!aCat && !bCat) return 0;
if (!aCat) return 1;
if (!bCat) return -1;
return aCat.localeCompare(bCat);
} else if (sortBy === 'duration') {
return a.duration - b.duration;
}
return 0;
});
}
// Function to filter activities by category
function filterByCategories(activities, categories) {
if (categories.length === 0) return activities;
return activities.filter(activity => {
// Include activities with no category
if (activity.categories.length === 0) return true;
// Include activities that match any of the selected categories
return categories.some(cat => activity.categories.includes(cat));
});
}
// Function to update URL parameters for multi-day support
function updateURLParams() {
const params = new URLSearchParams();
console.log('Updating URL params. Current days:', days);
// Add days data in a simpler format
days.forEach((day, dayIndex) => {
console.log(`Processing day ${dayIndex}:`, day);
params.set(`s${dayIndex}`, day.startTime);
day.activities.forEach((activity, activityIndex) => {
// Save all activities, but handle empty ones specially
const activityData = `${activity.name || ''}|${activity.duration}`;
const paramName = `a${dayIndex}_${activityIndex}`;
console.log(`Setting param ${paramName} = ${activityData}`);
if (activity.id) {
params.set(paramName, activityData + `|${activity.id}`);
} else {
params.set(paramName, activityData);
}
});
});
// Update URL without reloading - force immediate update
const newUrl = `${window.location.pathname}?${params.toString()}`;
console.log('New URL being set:', newUrl);
console.log('Params string:', params.toString());
// Try multiple methods to force URL update
try {
// Method 1: Direct replace
window.history.replaceState({}, '', newUrl);
// Method 2: If that didn't work, try push then replace
setTimeout(() => {
if (window.location.href !== newUrl) {
console.log('URL mismatch detected, trying alternative method');
window.history.pushState({}, '', newUrl);
window.history.replaceState({}, '', newUrl);
}
}, 0);
} catch (e) {
console.error('Error updating URL:', e);
}
console.log('URL updated. Current URL:', window.location.href);
}
// Function to get URL parameters for multi-day support
function getURLParams() {
const params = new URLSearchParams(window.location.search);
const result = {
days: []
};
// Parse days from URL
let dayIndex = 0;
while (true) {
const startParam = `s${dayIndex}`;
const startTime = params.get(startParam);
if (!startTime) break;
const day = {
startTime: startTime,
activities: []
};
// Parse activities for this day
let activityIndex = 0;
while (true) {
const activityParam = `a${dayIndex}_${activityIndex}`;
const activityValue = params.get(activityParam);
if (!activityValue) break;
const parts = activityValue.split('|');
day.activities.push({
name: parts[0] || '',
duration: parseInt(parts[1]) || 60,
id: parts[2] || undefined
});
activityIndex++;
}
result.days.push(day);
dayIndex++;
}
// If no days in URL, create default day
if (result.days.length === 0) {
result.days = [{ startTime: '08:00', activities: [] }];
}
return result;
}
async function initApp() {
const loadingOverlay = document.getElementById('loading-overlay');
const loadingText = document.getElementById('loading-text');
try {
loadingText.innerText = "Bezig met ophalen van activiteiten...";
const response = await fetch(CSV_URL);
if (!response.ok) throw new Error(`Server Error: ${response.status}`);
const tsvText = await response.text();
loadingText.innerText = "Bezig met verwerken...";
activitiesDatabase = parseTSV(tsvText);
if (activitiesDatabase.length > 0) {
loadingText.innerText = `Geladen: ${activitiesDatabase.length} activiteiten.`;
}
// Load days and activities from URL if present
const urlParams = getURLParams();
days = urlParams.days;
// Render all days and activities
renderAllDays();
setTimeout(() => {
loadingOverlay.style.display = 'none';
}, 500);
} catch (error) {
console.error("Import Error:", error);
loadingText.innerText = "Verbinding Mislukt.";
setTimeout(() => {
loadingOverlay.style.display = 'none';
alert("Verbinding Mislukt. Controleer internetverbinding en Google Sheet rechten.");
}, 2500);
}
}
// Function to render all days
function renderAllDays() {
const container = document.getElementById('activitiesContainer');
container.innerHTML = '';
// Remove the main start time container in multi-day mode
const startTimeContainer = document.querySelector('.start-time-container');
if (startTimeContainer) {
startTimeContainer.remove();
}
// Hide the setup-section in multi-day mode
const setupSection = document.querySelector('.setup-section');
if (setupSection) {
setupSection.style.display = days.length === 1 ? 'flex' : 'none';
}
// Hide the main add button in multi-day mode
const mainAddButton = document.getElementById('mainAddButton');
if (mainAddButton) {
mainAddButton.style.display = 'none';
}
// If only one day, render in single-day mode
if (days.length === 1) {
renderSingleDayMode();
return;
}
// Multi-day mode
days.forEach((day, dayIndex) => {
// Create day container
const dayContainer = document.createElement('div');
dayContainer.className = 'day-container';
dayContainer.setAttribute('data-day-index', dayIndex);
// Create day header
const dayHeader = document.createElement('div');
dayHeader.className = 'day-header';
dayHeader.innerHTML = `
<div class="day-controls">
<button class="day-move-btn up" onclick="moveDay(${dayIndex}, -1)" title="Dag Omhoog" ${dayIndex === 0 ? 'disabled' : ''}>
<i class="fa-solid fa-arrow-up"></i>
</button>
<button class="day-move-btn down" onclick="moveDay(${dayIndex}, 1)" title="Dag Omlaag" ${dayIndex === days.length - 1 ? 'disabled' : ''}>
<i class="fa-solid fa-arrow-down"></i>
</button>
</div>
<h3>Dag ${dayIndex + 1}</h3>
<div class="day-setup">
<div class="setup-label">
<i class="fa-solid fa-clock"></i> Starttijd Dag ${dayIndex + 1}
</div>
<input type="time" class="day-start-time" value="${day.startTime}" onchange="updateDayStartTime(${dayIndex}, this.value)">
${days.length > 1 ? `<button class="btn-delete-day" onclick="deleteDay(${dayIndex})" title="Dag verwijderen" style="background-color: var(--delete-color); color: white; border: none; border-radius: 6px; padding: 8px 12px; cursor: pointer; font-size: 12px; margin-left: 10px;"><i class="fa-solid fa-trash"></i> Verwijder</button>` : ''}
</div>
`;
dayContainer.appendChild(dayHeader);
// Create activities container for this day
const activitiesContainer = document.createElement('div');
activitiesContainer.className = 'activities-list day-activities';
activitiesContainer.setAttribute('data-day', dayIndex);
// Add activities or empty state
if (day.activities.length === 0) {
activitiesContainer.innerHTML = `
<div style="text-align: center; color: #aaa; padding: 40px; border: 2px dashed #ddd; border-radius: 10px; display: flex; flex-direction: column; align-items: center;">
<i class="fa-solid fa-list-ul" style="font-size: 3rem; margin-bottom: 15px; color: #ddd;"></i>
<p>Dag ${dayIndex + 1} is leeg.</p>
</div>
`;
} else {
day.activities.forEach(activity => {
const row = createActivityRow(activity, dayIndex);
activitiesContainer.appendChild(row);
});
}
dayContainer.appendChild(activitiesContainer);
// Add activity button for this day
const addButton = document.createElement('button');
addButton.className = 'btn-add';
addButton.setAttribute('data-day', dayIndex);
addButton.innerHTML = '<i class="fa-solid fa-plus"></i> Activiteit Toevoegen';
addButton.onclick = () => addActivityRow(dayIndex);
dayContainer.appendChild(addButton);
container.appendChild(dayContainer);
});
updateExtraInfoDisplay();
}
// Function to render single day mode (original layout)
function renderSingleDayMode() {
console.log('Rendering single day mode. Days:', days);
const container = document.getElementById('activitiesContainer');
const day = days[0];
console.log('Day 0 activities:', day.activities);
// Update the main start time input
const mainStartInput = document.getElementById('dayStartTime');
if (mainStartInput) {
mainStartInput.value = day.startTime;
}
// Show the main add button in single day mode
const mainAddButton = document.getElementById('mainAddButton');
if (mainAddButton) {
mainAddButton.style.display = 'block';
}
// Add activities or empty state
if (day.activities.length === 0) {
console.log('No activities, showing empty state');
container.innerHTML = `
<div style="text-align: center; color: #aaa; padding: 40px; border: 2px dashed #ddd; border-radius: 10px; display: flex; flex-direction: column; align-items: center;">
<i class="fa-solid fa-list-ul" style="font-size: 3rem; margin-bottom: 15px; color: #ddd;"></i>
<p>Je planning is leeg.</p>
</div>
`;
} else {
console.log('Rendering', day.activities.length, 'activities');
container.innerHTML = '';
day.activities.forEach((activity, index) => {
console.log(`Creating row ${index} for activity:`, activity);
const row = createActivityRow(activity, 0);
container.appendChild(row);
});
}
updateExtraInfoDisplay();
}
// Function to create activity row
function createActivityRow(activity, dayIndex) {
const row = document.createElement('div');
row.className = 'activity-row';
row.setAttribute('data-day', dayIndex);
if (activity.id) {
row.setAttribute('data-id', activity.id);
}
const day = days[dayIndex];
const startTimeValue = day.startTime || "08:00";
const defaultDuration = activity.duration || 60;
const endTimeValue = calculateTime(startTimeValue, defaultDuration);
row.innerHTML = `
<div class="move-controls">
<button class="move-btn up" onclick="moveRow(this, -1)" title="Naar Boven"><i class="fa-solid fa-arrow-up"></i></button>
<button class="move-btn down" onclick="moveRow(this, 1)" title="Naar beneden"><i class="fa-solid fa-arrow-down"></i></button>
</div>
<div class="time-column">
<span class="time-label">Start</span>
<span class="time-value time-start-display">${startTimeValue}</span>
<span class="time-label">Eind</span>
<span class="time-value time-end-display">${endTimeValue}</span>
</div>
<div class="duration-wrapper">
<span class="duration-label">Min</span>
<input type="number" class="duration-input" value="${defaultDuration}" min="1" onchange="handleDurationChange(this)" placeholder="60">
</div>
<button class="btn-delete" onclick="deleteRow(this)" title="Verwijderen"><i class="fa-solid fa-trash"></i></button>
<div class="activity-input-wrapper">
<input type="text" class="activity-input" placeholder="Activiteit Naam..." value="${activity.name || ''}" oninput="updateActivityData(this)">
<div class="suggestions-list"></div>
</div>
`;
initSearchLogic(row);
return row;
}
// Function to update activity data from input
function updateActivityData(input) {
console.log('updateActivityData called with input value:', input.value);
const row = input.closest('.activity-row');
const dayIndex = parseInt(row.getAttribute('data-day'));
console.log('Updating activity data:', input.value, 'dayIndex:', dayIndex);
console.log('Current days structure:', days);
let rowIndex;
if (days.length === 1) {
// Single day mode - get all rows and find the index
const rows = document.querySelectorAll('.activity-row');
rowIndex = Array.from(rows).indexOf(row);
console.log('Single day mode - rowIndex:', rowIndex);
if (rowIndex >= 0 && rowIndex < days[dayIndex].activities.length) {
days[dayIndex].activities[rowIndex].name = input.value;
// Clear the ID when name is edited to remove extra info
if (days[dayIndex].activities[rowIndex].id) {
days[dayIndex].activities[rowIndex].id = undefined;
row.removeAttribute('data-id');
}
console.log('Updated activity:', days[dayIndex].activities[rowIndex]);
console.log('About to call updateURLParams from updateActivityData');
}
} else {
// Multi-day mode - get rows from the specific day container
const dayContainer = row.closest('.day-container');
if (dayContainer) {
const rows = dayContainer.querySelectorAll('.activity-row');
rowIndex = Array.from(rows).indexOf(row);
console.log('Multi day mode - rowIndex:', rowIndex);
if (rowIndex >= 0 && rowIndex < days[dayIndex].activities.length) {
days[dayIndex].activities[rowIndex].name = input.value;
// Clear the ID when name is edited to remove extra info
if (days[dayIndex].activities[rowIndex].id) {
days[dayIndex].activities[rowIndex].id = undefined;
row.removeAttribute('data-id');
}
console.log('Updated activity:', days[dayIndex].activities[rowIndex]);
console.log('About to call updateURLParams from updateActivityData');
}
} else {
console.error('Day container not found in multi-day mode');
return;
}
}
updateExtraInfoDisplay();
updateURLParams();
console.log('updateActivityData completed');
}
// Function to add activity row to specific day
function addActivityRow(dayIndex = 0) {
console.log('Adding activity row to day:', dayIndex);
console.log('Days before adding:', days);
// Add a new activity to the data structure
const newActivity = { name: '', duration: 60 };
days[dayIndex].activities.push(newActivity);
console.log('Days after adding:', days);
// Don't update URL yet - wait until user types something
// updateURLParams();
// Re-render to show the new row
renderAllDays();
recalculateEntireSchedule();
// Focus on the new input
setTimeout(() => {
let targetRow;
if (days.length === 1) {
// Single day mode - focus on last activity row
targetRow = document.querySelector('.activity-row:last-child');
} else {
// Multi-day mode - focus on specific day
const dayContainer = document.querySelector(`[data-day-index="${dayIndex}"]`);
targetRow = dayContainer.querySelector('.activity-row:last-child');
}
if (targetRow) {
const input = targetRow.querySelector('.activity-input');
input.focus();
}
}, 100);
}
// Function to delete activity row
function deleteRow(button) {
const row = button.closest('.activity-row');
const dayIndex = parseInt(row.getAttribute('data-day'));
if (days.length === 1) {
// Single day mode - find the activity index
const rows = document.querySelectorAll('.activity-row');
const rowIndex = Array.from(rows).indexOf(row);
// Remove activity from data structure
days[dayIndex].activities.splice(rowIndex, 1);
// Re-render to show updated state
renderAllDays();
recalculateEntireSchedule();
updateURLParams();
return;
}
// Multi-day mode
const dayContainer = row.closest('.day-container');
const rows = dayContainer.querySelectorAll('.activity-row');
const rowIndex = Array.from(rows).indexOf(row);
// Remove activity from data structure
days[dayIndex].activities.splice(rowIndex, 1);
// Check if this was the last activity of a day (and there are multiple days)
if (days[dayIndex].activities.length === 0 && days.length > 1) {
// Automatically delete the day without confirmation
deleteDay(dayIndex);
return;
}
renderAllDays();
recalculateEntireSchedule();
updateURLParams();
}
// Function to add a new day
function addDay() {
console.log('Adding new day. Current days:', days);
const newDay = {
startTime: '08:00',
activities: []
};
days.push(newDay);
console.log('Days after adding new day:', days);
// Update URL to include the new day structure
updateURLParams();
renderAllDays();
updateURLParams(); // Update again after rendering
showToast(`Dag ${days.length} toegevoegd!`);
}
// Function to move a day up or down
function moveDay(dayIndex, direction) {
const newIndex = dayIndex + direction;
// Check if the move is valid
if (newIndex < 0 || newIndex >= days.length) {
return;
}
// Get the day containers
const container = document.getElementById('activitiesContainer');
const dayContainers = Array.from(container.querySelectorAll('.day-container'));
const currentDayContainer = dayContainers[dayIndex];
const targetDayContainer = dayContainers[newIndex];
// Add animation classes - current moves in direction, target moves opposite
const currentAnimationClass = direction === 1 ? 'moving-down' : 'moving-up';
const targetAnimationClass = direction === 1 ? 'moving-up' : 'moving-down';
currentDayContainer.classList.add(currentAnimationClass);
targetDayContainer.classList.add(targetAnimationClass);
// Swap the days in the data structure
const tempDay = days[dayIndex];
days[dayIndex] = days[newIndex];
days[newIndex] = tempDay;
// After animation completes, re-render
setTimeout(() => {
renderAllDays();
recalculateEntireSchedule();
updateURLParams();
showToast(`Dag ${dayIndex + 1} verplaatst naar positie ${newIndex + 1}`);
}, 500);
}
// Function to delete a day
function deleteDay(dayIndex) {
if (days.length <= 1) {
alert("Je moet minstens één dag hebben.");
return;
}
if (confirm(`Weet je zeker dat je Dag ${dayIndex + 1} wilt verwijderen? Alle activiteiten op deze dag worden ook verwijderd.`)) {
days.splice(dayIndex, 1);
renderAllDays();
recalculateEntireSchedule();
updateURLParams();
showToast(`Dag ${dayIndex + 1} verwijderd!`);
}
}
// Function to update day start time
function updateDayStartTime(dayIndex, newStartTime) {
days[dayIndex].startTime = newStartTime;
recalculateEntireSchedule();
updateURLParams();
}
// Updated moveRow function to support cross-day movement
function moveRow(button, direction) {
const currentRow = button.closest('.activity-row');
let currentDayIndex, rows, currentRowIndex;
if (days.length === 1) {
// Single day mode
currentDayIndex = 0;
rows = document.querySelectorAll('.activity-row');
currentRowIndex = Array.from(rows).indexOf(currentRow);
} else {
// Multi-day mode
const dayContainer = currentRow.closest('.day-container');
if (!dayContainer) {
console.error('Day container not found in moveRow');
return;
}
currentDayIndex = parseInt(dayContainer.getAttribute('data-day-index'));
rows = dayContainer.querySelectorAll('.activity-row');
currentRowIndex = Array.from(rows).indexOf(currentRow);
}
let targetDayIndex = currentDayIndex;
let targetRowIndex = currentRowIndex + direction;
// Check if we need to move to a different day
if (targetRowIndex < 0 && currentDayIndex > 0) {
// Move to previous day, end of that day
targetDayIndex = currentDayIndex - 1;
targetRowIndex = days[targetDayIndex].activities.length;
} else if (targetRowIndex >= days[currentDayIndex].activities.length && currentDayIndex < days.length - 1) {
// Move to next day, beginning of that day
targetDayIndex = currentDayIndex + 1;
targetRowIndex = 0;
}
// Only proceed if we're actually moving
if (targetDayIndex !== currentDayIndex || targetRowIndex !== currentRowIndex) {
// Find target row for animation
let targetRow;
if (targetDayIndex === currentDayIndex) {
// Same day movement
const allRows = days.length === 1 ?
document.querySelectorAll('.activity-row') :
document.querySelectorAll(`[data-day-index="${currentDayIndex}"] .activity-row`);
targetRow = allRows[targetRowIndex];
} else {
// Cross-day movement
const targetDayContainer = document.querySelector(`[data-day-index="${targetDayIndex}"]`);
if (targetDayContainer) {
const targetRows = targetDayContainer.querySelectorAll('.activity-row');
targetRow = targetRows[targetRowIndex];
}
}
// Add animation classes - current moves in direction, target moves opposite
const currentAnimationClass = direction === 1 ? 'moving-down' : 'moving-up';
const targetAnimationClass = direction === 1 ? 'moving-up' : 'moving-down';
currentRow.classList.add(currentAnimationClass);
if (targetRow) {
targetRow.classList.add(targetAnimationClass);
}
// Update data structure
const activity = days[currentDayIndex].activities.splice(currentRowIndex, 1)[0];
days[targetDayIndex].activities.splice(targetRowIndex, 0, activity);
// After animation completes, re-render
setTimeout(() => {
renderAllDays();
recalculateEntireSchedule();
updateURLParams();
}, 400);
}
}
function handleDurationChange(durationInput) {
const row = durationInput.closest('.activity-row');
const dayIndex = parseInt(row.getAttribute('data-day'));
let rowIndex;
if (days.length === 1) {
// Single day mode - get all rows globally
const rows = document.querySelectorAll('.activity-row');
rowIndex = Array.from(rows).indexOf(row);
} else {
// Multi-day mode - get rows from the specific day container
const dayContainer = row.closest('.day-container');
if (dayContainer) {
const rows = Array.from(dayContainer.querySelectorAll('.activity-row'));
rowIndex = rows.indexOf(row);
} else {
console.error('Day container not found in handleDurationChange');
return;
}
}
// Update duration in data structure
let duration = parseInt(durationInput.value);
if (isNaN(duration) || duration < 1) {
duration = 60;
durationInput.value = 60;
}
if (rowIndex >= 0 && rowIndex < days[dayIndex].activities.length) {
days[dayIndex].activities[rowIndex].duration = duration;
}
recalculateEntireSchedule();
updateExtraInfoDisplay();
updateURLParams();
}
function initSearchLogic(row) {
const input = row.querySelector('.activity-input');
const wrapper = row.querySelector('.activity-input-wrapper');
const list = row.querySelector('.suggestions-list');
const durationInput = row.querySelector('.duration-input');
// Create sort controls
const sortControls = document.createElement('div');
sortControls.className = 'sort-controls';
sortControls.innerHTML = `
<button class="sort-btn ${currentSort === 'name' ? 'active' : ''}" onclick="setSort('name')">
<i class="fa-solid fa-sort-alpha-down"></i> Naam
</button>
<button class="sort-btn ${currentSort === 'category' ? 'active' : ''}" onclick="setSort('category')">
<i class="fa-solid fa-tags"></i> Categorie
</button>
<button class="sort-btn ${currentSort === 'duration' ? 'active' : ''}" onclick="setSort('duration')">
<i class="fa-solid fa-clock"></i> Duur
</button>
`;
// Create category filter
const categoryFilter = document.createElement('div');
categoryFilter.className = 'category-filter';
// Add "All" category
const allChip = document.createElement('span');
allChip.className = 'category-chip' + (activeCategories.length === 0 ? ' active' : '');
allChip.innerHTML = `<i class="fa-solid fa-check"></i> Alle`;
allChip.onclick = () => toggleCategory('');
categoryFilter.appendChild(allChip);
// Add category chips (capitalized)
window.allCategories.forEach(category => {
const chip = document.createElement('span');
chip.className = 'category-chip' + (activeCategories.includes(category) ? ' active' : '');
chip.innerHTML = `${category} <span class="close-btn">×</span>`;
chip.onclick = (e) => {
if (e.target.className === 'close-btn') {
e.stopPropagation();
removeCategory(category);
} else {
toggleCategory(category);
}
};
categoryFilter.appendChild(chip);
});
function showSuggestions() {
const query = input.value.toLowerCase().trim();
list.innerHTML = '';
// Clear previous sort controls and category filter
const existingSortControls = list.querySelector('.sort-controls');
if (existingSortControls) existingSortControls.remove();
const existingCategoryFilter = list.querySelector('.category-filter');
if (existingCategoryFilter) existingCategoryFilter.remove();
// Clear filters when searching (not when showing full list)
if (query.length > 0) {
activeCategories = [];
document.querySelectorAll('.category-chip').forEach(chip => {
chip.classList.remove('active');
const chipText = chip.textContent.replace('×', '').trim();
if (chipText === 'Alle') {
chip.classList.add('active');
}
});
}
// Show full list when input is empty
const matches = query.length === 0 ? activitiesDatabase : activitiesDatabase.filter(item =>
item.name.toLowerCase().includes(query) ||
item.categories.some(cat => cat.toLowerCase().includes(query))
);
// Add category filter when showing full list
if (query.length === 0) {
list.appendChild(categoryFilter);
}
// Add sort controls when showing full list
if (query.length === 0) {
list.appendChild(sortControls);
}
// Filter by active categories
const filteredMatches = filterByCategories(matches, activeCategories);
// Sort the matches
const sortedMatches = sortActivities(filteredMatches, currentSort);
if (sortedMatches.length > 0) {
sortedMatches.forEach(match => {
const itemDiv = document.createElement('div');
itemDiv.className = 'suggestion-item';
itemDiv.innerHTML = `
<span>${match.name}${match.extraInfo ? ' <span class="info-badge"><i class="fa-solid fa-info-circle"></i> Info</span>' : ''}</span>
<span>
${match.categories.length > 0 ? match.categories.map(cat => `<span class="category-badge">${cat}</span>`).join('') : ''}
<span class="duration-suggestion">${match.duration} min</span>
</span>
`;
itemDiv.onclick = () => {
console.log('Suggestion clicked:', match.name);
input.value = match.name;
durationInput.value = match.duration;
// Store the ID in a data attribute on the row
row.setAttribute('data-id', match.id);
// Update data structure
const dayIndex = parseInt(row.getAttribute('data-day'));
let rowIndex;
if (days.length === 1) {
// Single day mode - get all rows globally
const rows = document.querySelectorAll('.activity-row');
rowIndex = Array.from(rows).indexOf(row);
} else {
// Multi-day mode - get rows from the specific day container
const dayContainer = row.closest('.day-container');
if (dayContainer) {
const rows = Array.from(dayContainer.querySelectorAll('.activity-row'));
rowIndex = rows.indexOf(row);
} else {
console.error('Day container not found in multi-day mode');
return;
}
}
if (rowIndex >= 0 && rowIndex < days[dayIndex].activities.length) {
days[dayIndex].activities[rowIndex].name = match.name;
days[dayIndex].activities[rowIndex].duration = match.duration;
days[dayIndex].activities[rowIndex].id = match.id;
} else {
console.error('Invalid row index:', rowIndex, 'for day', dayIndex);
}
console.log('Updated days structure:', days);
list.classList.remove('show');
handleDurationChange(durationInput);
input.focus();
updateExtraInfoDisplay();
// Force URL update immediately
updateURLParams();
// Double-check the URL was updated
setTimeout(() => {
console.log('Current URL after suggestion click:', window.location.href);
}, 100);
};
list.appendChild(itemDiv);
});
list.classList.add('show');
} else {
list.innerHTML = '<div style="padding:10px; color:#888; text-align:center;">Geen matches gevonden</div>';
list.classList.add('show');
}
}
// Add event listener for input changes to update extra info display
input.addEventListener('input', () => {
showSuggestions();
updateExtraInfoDisplay();
updateURLParams();
});
input.addEventListener('focus', () => { showSuggestions(); });
document.addEventListener('click', (e) => { if (!wrapper.contains(e.target)) list.classList.remove('show'); });
input.addEventListener('keydown', (e) => { if (e.key === 'Escape') { list.classList.remove('show'); input.blur(); } });
}
function updateExtraInfoDisplay() {
const extraInfoContainer = document.getElementById('extraInfoContainer');
const extraInfoList = document.getElementById('extraInfoList');
// Clear previous content
extraInfoList.innerHTML = '';
// Get all activity names and IDs from the current schedule
let hasExtraInfo = false;
days.forEach((day, dayIndex) => {
day.activities.forEach((activity, activityIndex) => {
if (activity.id && activityExtraInfo[activity.id]) {
hasExtraInfo = true;
const info = activityExtraInfo[activity.id];
const listItem = document.createElement('li');
listItem.className = 'extra-info-item';
// Build the info HTML - use just activity name for single day
const displayLabel = days.length === 1 ? activity.name : `Dag ${dayIndex + 1} - ${activity.name}`;
let infoHtml = `<strong>${displayLabel}:</strong><br>`;
// Add extra info if available
if (info.extra) {
infoHtml += `<div style="margin-top: 5px;">${makeUrlsClickable(info.extra)}</div>`;
}
// Add doel if available
if (info.doel) {
infoHtml += `<div style="margin-top: 8px;"><strong>Doel:</strong> ${info.doel}</div>`;
}
// Add benodigheden if available
if (info.benodigheden) {
infoHtml += `<div style="margin-top: 5px;"><strong>Benodigheden:</strong> ${info.benodigheden}</div>`;
}
listItem.innerHTML = infoHtml;
extraInfoList.appendChild(listItem);
}
});
});
// Show or hide the container based on whether there's extra info
extraInfoContainer.style.display = hasExtraInfo ? 'block' : 'none';
}
window.onload = initApp;
// Add event listeners
document.addEventListener('DOMContentLoaded', () => {
// Add event listener to main start time input for single day mode
const mainStartInput = document.getElementById('dayStartTime');
if (mainStartInput) {
mainStartInput.addEventListener('change', () => {
if (days.length === 1) {
days[0].startTime = mainStartInput.value;
recalculateEntireSchedule();
updateURLParams();
}
});
}
// Add event listener to main add day button
const addDayButton = document.querySelector('[onclick="addDay()"]');
if (addDayButton) {
addDayButton.addEventListener('click', updateURLParams);
}
});
function calculateTime(startTimeString, durationMinutes) {
const [hours, minutes] = startTimeString.split(':').map(Number);
const date = new Date();
date.setHours(hours, minutes, 0);
date.setMinutes(date.getMinutes() + durationMinutes);
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
}
function recalculateEntireSchedule() {
days.forEach((day, dayIndex) => {
let currentStartTime = day.startTime;
if (days.length === 1) {
// Single day mode - use main start time input
const mainStartInput = document.getElementById('dayStartTime');
if (mainStartInput) {
currentStartTime = mainStartInput.value;
day.startTime = currentStartTime;
}
const rows = document.querySelectorAll('.activity-row');
rows.forEach((row, index) => {
const startDisplay = row.querySelector('.time-start-display');
if (startDisplay) startDisplay.innerText = currentStartTime;
const durationInput = row.querySelector('.duration-input');
let duration = 60;
if (durationInput && durationInput.value > 0) duration = parseInt(durationInput.value);
const endTime = calculateTime(currentStartTime, duration);
const endDisplay = row.querySelector('.time-end-display');
if (endDisplay) endDisplay.innerText = endTime;
currentStartTime = endTime;
});
} else {
// Multi-day mode
const dayContainer = document.querySelector(`[data-day-index="${dayIndex}"]`);
if (!dayContainer) return;
const rows = dayContainer.querySelectorAll('.activity-row');
rows.forEach((row, index) => {
const startDisplay = row.querySelector('.time-start-display');
if (startDisplay) startDisplay.innerText = currentStartTime;
const durationInput = row.querySelector('.duration-input');
let duration = 60;
if (durationInput && durationInput.value > 0) duration = parseInt(durationInput.value);
const endTime = calculateTime(currentStartTime, duration);
const endDisplay = row.querySelector('.time-end-display');
if (endDisplay) endDisplay.innerText = endTime;
currentStartTime = endTime;
});
}
});
updateExtraInfoDisplay();
}
// Category functions
function setSort(sortBy) {
currentSort = sortBy;
document.querySelectorAll('.sort-btn').forEach(btn => btn.classList.remove('active'));
document.querySelector(`.sort-btn[onclick="setSort('${sortBy}')"]`).classList.add('active');
// Trigger suggestions update for all visible inputs
document.querySelectorAll('.activity-input:focus').forEach(input => {
const event = new Event('input');
input.dispatchEvent(event);
});
}
function toggleCategory(category) {
if (category === '') {
activeCategories = [];
} else {
const index = activeCategories.indexOf(category);
if (index > -1) {
activeCategories.splice(index, 1);
} else {
activeCategories.push(category);
}
}
// Update UI for all category chips across all suggestion lists
document.querySelectorAll('.category-chip').forEach(chip => {
const chipText = chip.textContent.replace('×', '').trim();
if (chipText === 'Alle') {
chip.classList.toggle('active', activeCategories.length === 0);
} else {
chip.classList.toggle('active', activeCategories.includes(chipText));
}
});
// Trigger suggestions update for all focused inputs
document.querySelectorAll('.activity-input:focus').forEach(input => {
const event = new Event('input', { bubbles: true });
input.dispatchEvent(event);
});
// Also trigger update for the input that might have the category filter open
document.querySelectorAll('.activity-input').forEach(input => {
const wrapper = input.closest('.activity-input-wrapper');
if (wrapper && wrapper.querySelector('.suggestions-list.show')) {
const event = new Event('input', { bubbles: true });
input.dispatchEvent(event);
}
});
}
function removeCategory(category) {
const index = activeCategories.indexOf(category);
if (index > -1) {
activeCategories.splice(index, 1);
}
// Update UI
document.querySelectorAll('.category-chip').forEach(chip => {
const chipText = chip.textContent.replace('×', '').trim();
chip.classList.toggle('active', activeCategories.includes(chipText));
});
// Trigger suggestions update
document.querySelectorAll('.activity-input:focus').forEach(input => {
const event = new Event('input');
input.dispatchEvent(event);
});
}
function generateTextPlan() {
if (days.length === 0 || days.every(day => day.activities.length === 0)) return "";
let text = "Victorie Opkomsten Planner:\n\n";
const extraInfoMap = {};
days.forEach((day, dayIndex) => {
if (day.activities.length === 0) return;
// Use "Opkomst" for single day, "Dag X" for multiple days
const dayLabel = days.length === 1 ? "Opkomst" : `Dag ${dayIndex + 1}`;
text += `=== ${dayLabel} (Start: ${day.startTime}) ===\n`;
let currentStartTime = day.startTime;
day.activities.forEach(activity => {
const endTime = calculateTime(currentStartTime, activity.duration);
const activityName = activity.name || "Naamloos";
text += `${currentStartTime} - ${endTime}: ${activityName}\n`;
currentStartTime = endTime;
// Find matching activity in database using ID
if (activity.id && activityExtraInfo[activity.id]) {
// For single day, use just activity name; for multi-day, use "Dag X - activity name"
let infoKey;
if (days.length === 1) {
infoKey = activityName;
} else {
infoKey = `Dag ${dayIndex + 1} - ${activityName}`;
}
extraInfoMap[infoKey] = activityExtraInfo[activity.id];
}
});
text += "\n";
});
// Add extra info section if any exists
const extraInfoKeys = Object.keys(extraInfoMap);
if (extraInfoKeys.length > 0) {
text += "Activiteit Uitleg:\n";
extraInfoKeys.forEach(activityKey => {
const info = extraInfoMap[activityKey];
text += `\n${activityKey}\n`;
if (info.extra) {
text += `Uitleg: ${info.extra}\n`;
}
if (info.doel) {
text += `Doel: ${info.doel}\n`;
}
if (info.benodigheden) {
text += `Benodigheden: ${info.benodigheden}\n`;
}
});
}
// Add URL to the export text (remove the website line)
text += `\nJe kunt deze planning opslaan en later terug openen met deze link:\n${window.location.href}\n`;
// Removed: text += "\nopkomstenplanner.marijnjong.de";
return text;
}
function shareSchedule() {
const textToShare = generateTextPlan();
if (!textToShare) { alert("Voeg activiteiten toe voordat je deelt."); return; }
if (navigator.share) {
navigator.share({
title: 'Victorie Opkomsten Planner',
text: textToShare,
url: window.location.href
}).catch(console.error);
} else {
copySchedule();
}
}
function copySchedule() {
const textToCopy = generateTextPlan();
if (!textToCopy) { alert("Voeg activiteiten toe voordat je kopieert."); return; }
const textArea = document.createElement("textarea");
textArea.value = textToCopy;
textArea.style.top = "0";
textArea.style.left = "0";
textArea.style.position = "fixed";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
showToast("Gekopieerd naar klembord!");
} else {
throw new Error('Kopiëren mislukt');
}
} catch (err) {
console.error('Kopiëren niet mogelijk', err);
alert("Kopiëren naar klembord is mislukt. Selecteer de tekst handmatig.");
}
document.body.removeChild(textArea);
}
function showToast(message) {
const x = document.getElementById("toast");
x.innerText = message;
x.className = "show";
setTimeout(function(){ x.className = x.className.replace("show", ""); }, 3000);
}
// Initialize the app
recalculateEntireSchedule();
</script>
</body>
</html>