Initial commit
This commit is contained in:
27
templates/add_board.html
Normal file
27
templates/add_board.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Nieuw Liturgiebord Toevoegen - Digitale Liturgie{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="space-y-8 max-w-6xl mx-auto px-4">
|
||||
|
||||
<h2 class="text-3xl font-semibold mb-6">Nieuw Liturgiebord Toevoegen</h2>
|
||||
|
||||
<form method="post" class="space-y-6 max-w-xl">
|
||||
|
||||
<div>
|
||||
<label for="name" class="block mb-3 font-semibold text-gray-700">Naam van het bord</label>
|
||||
<input type="text" id="name" name="name" required class="w-full rounded-md border border-gray-300 p-4 text-lg placeholder-gray-400 shadow-sm focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 transition">
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<button type="submit" class="bg-[#f7d91a] text-black px-8 py-3 rounded-md font-semibold shadow hover:bg-yellow-300 transition">Toevoegen</button>
|
||||
<a href="{{ url_for('portal') }}" class="bg-white text-black border border-black px-8 py-3 rounded-md font-semibold shadow hover:bg-gray-100 transition">Annuleren</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
107
templates/admin_dashboard.html
Normal file
107
templates/admin_dashboard.html
Normal file
@@ -0,0 +1,107 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Admin Dashboard - Digitale Liturgie{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="space-y-8 max-w-6xl mx-auto px-4">
|
||||
|
||||
<section>
|
||||
|
||||
<h2 class="text-3xl font-semibold mb-6">Admin Dashboard</h2>
|
||||
|
||||
<form method="POST" class="space-y-6">
|
||||
|
||||
<div>
|
||||
<h3 class="text-2xl font-semibold mb-4">Kerken</h3>
|
||||
<div class="overflow-x-auto rounded-lg border border-gray-300">
|
||||
<table class="min-w-full divide-y divide-gray-200 bg-white">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3 text-left text-sm font-semibold text-gray-700">Kerknaam</th>
|
||||
<th scope="col" class="px-6 py-3 text-center text-sm font-semibold text-gray-700">Actief</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-sm font-semibold text-gray-700">URL</th>
|
||||
<th scope="col" class="px-6 py-3 text-center text-sm font-semibold text-gray-700">Acties</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
{% for church in churches %}
|
||||
<tr>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-900">{{ church.name }}</td>
|
||||
<td class="text-center px-6 py-4">
|
||||
<input type="checkbox" name="church_active_{{ church.id }}" {% if church.is_active %}checked{% endif %} class="h-5 w-5 text-blue-600">
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-blue-600">
|
||||
{% if church.is_active %}
|
||||
{% for board in church.boards %}
|
||||
<div class="mb-1 break-all"><a href="{{ url_for('display_board_unique', unique_id=board.unique_id) }}" target="_blank" class="underline hover:text-blue-800 transition">{{ url_for('display_board_unique', unique_id=board.unique_id, _external=True) }}</a></div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center px-6 py-4">
|
||||
<a href="#" class="bg-[#f7d91a] text-black px-6 py-3 rounded font-semibold hover:bg-yellow-300 transition">Weergeven</a>
|
||||
<form action="{{ url_for('admin_delete_church', church_id=church.id) }}" method="post" style="display:inline;margin-left:8px;" onsubmit="return confirm('Weet je zeker dat je deze kerk en alle bijbehorende data wilt verwijderen?');">
|
||||
<button type="submit" class="bg-red-600 text-white px-6 py-3 rounded font-semibold hover:bg-red-700 transition">Verwijderen</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
<button type="submit" class="bg-[#f7d91a] text-black px-6 py-3 rounded-md font-semibold shadow hover:bg-yellow-300 transition">Opslaan</button>
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
||||
<h3 class="text-2xl font-semibold mb-4">Gebruikers</h3>
|
||||
|
||||
<div class="overflow-x-auto rounded-lg border border-gray-300">
|
||||
<table class="min-w-full divide-y divide-gray-200 bg-white">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3 text-left text-sm font-semibold text-gray-700">Gebruikersnaam</th>
|
||||
<th scope="col" class="px-6 py-3 text-sm font-semibold text-gray-700">Kerk</th>
|
||||
<th scope="col" class="px-6 py-3 text-sm font-semibold text-gray-700">Admin</th>
|
||||
<th scope="col" class="px-6 py-3 text-center text-sm font-semibold text-gray-700">Impersonalisatie</th>
|
||||
<th scope="col" class="px-6 py-3 text-center text-sm font-semibold text-gray-700">Acties</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-900">{{ user.username }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-900">{{ user.church.name }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-900">{% if user.is_admin %}Ja{% else %}Nee{% endif %}</td>
|
||||
<td class="text-center px-6 py-4">
|
||||
{% if not user.is_admin %}
|
||||
<a href="{{ url_for('admin_impersonate', user_id=user.id) }}" class="bg-[#f7d91a] text-black text-xs px-6 py-3 rounded font-semibold hover:bg-yellow-300 transition">Impersonate</a>
|
||||
{% else %}-{% endif %}
|
||||
</td>
|
||||
<td class="text-center px-6 py-4">
|
||||
{% if not user.is_admin %}
|
||||
<form action="{{ url_for('admin_delete_user', user_id=user.id) }}" method="post" style="display:inline;" onsubmit="return confirm('Weet je zeker dat je deze gebruiker en alle bijbehorende kerkdata wilt verwijderen?');">
|
||||
<button type="submit" class="bg-red-600 text-white text-xs px-6 py-3 rounded font-semibold hover:bg-red-700 transition">Verwijderen</button>
|
||||
</form>
|
||||
{% else %}-{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
18
templates/admin_login.html
Normal file
18
templates/admin_login.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}Admin Login - Digitale Liturgie{% endblock %}
|
||||
{% block content %}
|
||||
<div class="space-y-8 max-w-md mx-auto">
|
||||
<h2 class="text-3xl font-semibold tracking-tight">Admin Login</h2>
|
||||
<form method="post" class="space-y-6">
|
||||
<div>
|
||||
<label for="username" class="block mb-2 font-semibold text-gray-700">Gebruikersnaam</label>
|
||||
<input type="text" id="username" name="username" required class="w-full rounded-md border border-gray-300 p-3 text-base placeholder-gray-400 shadow-sm focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 transition">
|
||||
</div>
|
||||
<div>
|
||||
<label for="password" class="block mb-2 font-semibold text-gray-700">Wachtwoord</label>
|
||||
<input type="password" id="password" name="password" required class="w-full rounded-md border border-gray-300 p-3 text-base placeholder-gray-400 shadow-sm focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 transition">
|
||||
</div>
|
||||
<button type="submit" class="bg-blue-600 text-white w-full rounded-md py-3 font-semibold shadow hover:bg-blue-700 transition">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
50
templates/admin_users.html
Normal file
50
templates/admin_users.html
Normal file
@@ -0,0 +1,50 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Gebruikersbeheer - Digitale Liturgie{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="space-y-8 max-w-6xl mx-auto px-4">
|
||||
|
||||
<h2 class="text-3xl font-semibold mb-6">Gebruikersbeheer</h2>
|
||||
|
||||
<div class="overflow-x-auto rounded-lg border border-gray-300">
|
||||
<table class="min-w-full divide-y divide-gray-200 bg-white">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3 text-left text-sm font-semibold text-gray-700">Gebruikersnaam</th>
|
||||
<th scope="col" class="px-6 py-3 text-sm font-semibold text-gray-700">Kerk</th>
|
||||
<th scope="col" class="px-6 py-3 text-sm font-semibold text-gray-700">Admin</th>
|
||||
<th scope="col" class="px-6 py-3 text-center text-sm font-semibold text-gray-700">Impersonalisatie</th>
|
||||
<th scope="col" class="px-6 py-3 text-center text-sm font-semibold text-gray-700">Acties</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-900">{{ user.username }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-900">{{ user.church.name }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-900">{% if user.is_admin %}Ja{% else %}Nee{% endif %}</td>
|
||||
<td class="text-center px-6 py-4">
|
||||
{% if not user.is_admin %}
|
||||
<a href="{{ url_for('admin_impersonate', user_id=user.id) }}" class="bg-[#f7d91a] text-black text-xs px-6 py-3 rounded font-semibold hover:bg-yellow-300 transition">Impersonate</a>
|
||||
{% else %}-{% endif %}
|
||||
</td>
|
||||
<td class="text-center px-6 py-4">
|
||||
{% if not user.is_admin %}
|
||||
<form action="{{ url_for('admin_delete_user', user_id=user.id) }}" method="post" style="display:inline;" onsubmit="return confirm('Weet je zeker dat je deze gebruiker en alle bijbehorende kerkdata wilt verwijderen?');">
|
||||
<button type="submit" class="bg-red-600 text-white text-xs px-6 py-3 rounded font-semibold hover:bg-red-700 transition">Verwijderen</button>
|
||||
</form>
|
||||
{% else %}-{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<a href="{{ url_for('admin_dashboard') }}" class="bg-white text-black border border-black px-6 py-3 rounded-md font-semibold shadow hover:bg-gray-100 transition inline-block mt-6">Terug naar dashboard</a>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
151
templates/base.html
Normal file
151
templates/base.html
Normal file
@@ -0,0 +1,151 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}PsalmbordOnline{% endblock %}</title>
|
||||
<link rel="icon" type="image/png" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||
<!-- Google Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Lora&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400..700;1,400..700&family=Quicksand:wght@300..700&display=swap" rel="stylesheet">
|
||||
<!-- Tailwind CSS CDN -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<style>
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: 'Lora', serif;
|
||||
font-weight: 600;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Quicksand';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
/* Floating scrollbar style for WebKit browsers */
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
/* Webkit Floating Scrollbar */
|
||||
body::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
background: transparent;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(0,0,0,0.3);
|
||||
border-radius: 4px;
|
||||
border: 2px solid transparent;
|
||||
background-clip: content-box;
|
||||
}
|
||||
|
||||
/* Firefox Floating scrollbar */
|
||||
@-moz-document url-prefix() {
|
||||
html {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0, 0, 0, 0.3) transparent;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-white text-gray-900 font-sans min-h-screen flex flex-col">
|
||||
<nav class="bg-white border-b border-gray-200 shadow-sm">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between h-20">
|
||||
<!-- Logo -->
|
||||
<div class="flex-shrink-0 flex items-center w-24">
|
||||
{% if current_user.is_authenticated and current_user.church and current_user.church.logo_filename %}
|
||||
<a href="{{ url_for('index') }}"><img src="{{ url_for('uploaded_file', filename=current_user.church.logo_filename) }}" alt="Logo" class="h-10 w-auto object-contain"></a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('index') }}"><img src="{{ url_for('static', filename='default_logo.png') }}" alt="Default Logo" class="h-10 w-auto object-contain"></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- Desktop Nav -->
|
||||
<div class="hidden sm:flex flex-1 justify-center">
|
||||
<div id="nav-links" class="flex flex-row gap-2 items-center">
|
||||
{% if current_user.is_authenticated %}
|
||||
<a href="/portal" class="text-black px-6 py-3 rounded font-semibold hover:bg-[#f7d91a] hover:text-black transition">Mijn schermen</a>
|
||||
<a href="{{ url_for('global_schedules') }}" class="text-black px-6 py-3 rounded font-semibold hover:bg-[#f7d91a] hover:text-black transition">Geplande diensten</a>
|
||||
<a href="{{ url_for('church_settings') }}" class="text-black px-6 py-3 rounded font-semibold hover:bg-[#f7d91a] hover:text-black transition">Instellingen</a>
|
||||
{% if current_user.is_admin %}
|
||||
<a href="{{ url_for('admin_dashboard') }}" class="text-black px-6 py-3 rounded font-semibold hover:bg-[#f7d91a] hover:text-black transition">Admin</a>
|
||||
{% endif %}
|
||||
<a href="/logout" class="text-black px-6 py-3 rounded font-semibold hover:bg-[#f7d91a] hover:text-black transition">Uitloggen</a>
|
||||
{% else %}
|
||||
<a href="/login" class="text-black px-6 py-3 rounded font-semibold hover:bg-[#f7d91a] hover:text-black transition">Inloggen</a>
|
||||
<a href="/register" class="text-black px-6 py-3 rounded font-semibold hover:bg-[#f7d91a] hover:text-black transition">Registreren</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Current Time -->
|
||||
<div id="current-time" class="text-gray-700 font-semibold text-right text-sm w-24" title="Huidige datum en tijd">
|
||||
<div id="current-date"></div>
|
||||
<div id="current-clock" class="mt-0"></div>
|
||||
</div>
|
||||
<!-- Mobile hamburger -->
|
||||
<div class="flex sm:hidden">
|
||||
<button id="menu-button" class="p-2 rounded-md focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-600" aria-label="Open menu">
|
||||
<svg class="h-7 w-7 text-gray-700" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Mobile nav menu -->
|
||||
<div id="nav-mobile" class="sm:hidden hidden">
|
||||
<div class="flex flex-col gap-2 py-3 items-center border-t border-gray-100">
|
||||
{% if current_user.is_authenticated %}
|
||||
<a href="/portal" class="bg-[#f7d91a] text-black px-6 py-3 rounded font-semibold hover:bg-yellow-300 transition w-full text-center">Mijn schermen</a>
|
||||
<a href="{{ url_for('global_schedules') }}" class="bg-[#f7d91a] text-black px-6 py-3 rounded font-semibold hover:bg-yellow-300 transition w-full text-center">Geplande diensten</a>
|
||||
<a href="{{ url_for('church_settings') }}" class="bg-[#f7d91a] text-black px-6 py-3 rounded font-semibold hover:bg-yellow-300 transition w-full text-center">Kerk instellingen</a>
|
||||
{% if current_user.is_admin %}
|
||||
<a href="{{ url_for('admin_dashboard') }}" class="bg-[#f7d91a] text-black px-6 py-3 rounded font-semibold hover:bg-yellow-300 transition w-full text-center">Admin instellingen</a>
|
||||
{% endif %}
|
||||
<a href="/logout" class="bg-[#f7d91a] text-black px-6 py-3 rounded font-semibold hover:bg-yellow-300 transition w-full text-center">Logout</a>
|
||||
{% else %}
|
||||
<a href="/login" class="bg-[#f7d91a] text-black px-6 py-3 rounded font-semibold hover:bg-yellow-300 transition w-full text-center">Login</a>
|
||||
<a href="/register" class="bg-[#f7d91a] text-black px-6 py-3 rounded font-semibold hover:bg-yellow-300 transition w-full text-center">Register</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<script>
|
||||
const menuButton = document.getElementById('menu-button');
|
||||
const navMobile = document.getElementById('nav-mobile');
|
||||
|
||||
menuButton.addEventListener('click', () => {
|
||||
navMobile.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
function updateTime() {
|
||||
const now = new Date();
|
||||
const dateOptions = { year: 'numeric', month: 'short', day: 'numeric' };
|
||||
const timeOptions = { hour: '2-digit', minute: '2-digit', second: '2-digit' };
|
||||
const dateStr = now.toLocaleDateString('nl-NL', dateOptions);
|
||||
const timeStr = now.toLocaleTimeString('nl-NL', timeOptions);
|
||||
document.getElementById('current-date').textContent = dateStr;
|
||||
document.getElementById('current-clock').textContent = timeStr;
|
||||
}
|
||||
|
||||
setInterval(updateTime, 1000);
|
||||
updateTime();
|
||||
</script>
|
||||
<main class="flex-grow max-w-7xl mx-auto w-full px-6 py-8 sm:py-12">
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
<div class="bg-blue-100 border border-blue-400 text-blue-700 px-6 py-4 rounded mb-6">
|
||||
{% for message in messages %}
|
||||
<div>{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
<footer class="bg-gray-100 text-gray-600 text-center py-4 text-sm">
|
||||
© {{ current_year }} PsalmbordOnline | Contact: <a href="mailto:info@psalmbord.online" class="text-blue-600 hover:underline">info@psalmbord.online</a>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
48
templates/board_details.html
Normal file
48
templates/board_details.html
Normal file
@@ -0,0 +1,48 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="space-y-8 max-w-6xl mx-auto px-4">
|
||||
|
||||
<h1 class="text-3xl font-semibold mb-6">Details voor {{ board.name }}</h1>
|
||||
|
||||
<!-- You can add more board settings above here as needed -->
|
||||
|
||||
<h2 class="text-2xl font-semibold mb-4">Schedules for this display</h2>
|
||||
|
||||
<div class="overflow-x-auto rounded-lg border border-gray-300">
|
||||
|
||||
<table class="min-w-full divide-y divide-gray-200 bg-white">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700">Days</th>
|
||||
<th class="px-6 py-3 text-center text-sm font-semibold text-gray-700">Start Time</th>
|
||||
<th class="px-6 py-3 text-center text-sm font-semibold text-gray-700">End Time</th>
|
||||
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700">Content</th>
|
||||
<th class="px-6 py-3 text-center text-sm font-semibold text-gray-700">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
{% for s in schedules %}
|
||||
<tr>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-900">{{ s.days_of_week }}</td>
|
||||
<td class="text-center px-6 py-4 text-sm text-gray-900">{{ s.start_time.strftime('%H:%M') }}</td>
|
||||
<td class="text-center px-6 py-4 text-sm text-gray-900">{{ s.end_time.strftime('%H:%M') }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-900">{{ s.content[:50] }}{% if s.content|length > 50 %}...{% endif %}</td>
|
||||
<td class="text-center px-6 py-4">
|
||||
<a href="{{ url_for('edit_schedule', board_id=board.id, schedule_id=s.id) }}" class="bg-[#f7d91a] text-black text-xs px-6 py-3 rounded font-semibold hover:bg-yellow-300 transition">Edit</a>
|
||||
<form method="post" action="{{ url_for('delete_schedule', board_id=board.id, schedule_id=s.id) }}" style="display:inline;">
|
||||
<button type="submit" class="bg-red-600 text-white text-xs px-6 py-3 rounded font-semibold hover:bg-red-700 transition">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<a href="{{ url_for('add_schedule', board_id=board.id) }}" class="bg-[#f7d91a] text-black px-6 py-3 rounded-md font-semibold shadow hover:bg-yellow-300 transition inline-block">Add Schedule</a>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
45
templates/board_schedules.html
Normal file
45
templates/board_schedules.html
Normal file
@@ -0,0 +1,45 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="space-y-8 max-w-6xl mx-auto px-4">
|
||||
|
||||
<h1 class="text-3xl font-semibold mb-6">Manage Schedules for {{ board.name }}</h1>
|
||||
|
||||
<div class="overflow-x-auto rounded-lg border border-gray-300">
|
||||
|
||||
<table class="min-w-full divide-y divide-gray-200 bg-white">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700">Naam</th>
|
||||
<th class="px-6 py-3 text-sm font-semibold text-gray-700">Datum</th>
|
||||
<th class="px-6 py-3 text-center text-sm font-semibold text-gray-700">Starttijd</th>
|
||||
<th class="px-6 py-3 text-center text-sm font-semibold text-gray-700">Eindtijd</th>
|
||||
<th class="px-6 py-3 text-center text-sm font-semibold text-gray-700">Acties</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
{% for s in schedules %}
|
||||
<tr>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-900">{{ s.name }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-900">{{ s.date.strftime('%Y-%m-%d') }}</td>
|
||||
<td class="text-center px-6 py-4 text-sm text-gray-900">{{ s.start_time.strftime('%H:%M') }}</td>
|
||||
<td class="text-center px-6 py-4 text-sm text-gray-900">{{ s.end_time.strftime('%H:%M') }}</td>
|
||||
<td class="text-center px-6 py-4">
|
||||
<a href="{{ url_for('edit_schedule', board_id=board.id, schedule_id=s.id) }}" class="bg-[#f7d91a] text-black text-xs px-6 py-3 rounded font-semibold hover:bg-yellow-300 transition">Edit</a>
|
||||
<form action="{{ url_for('delete_schedule', board_id=board.id, schedule_id=s.id) }}" method="POST" style="display:inline;">
|
||||
<button type="submit" class="bg-red-600 text-white text-xs px-6 py-3 rounded font-semibold hover:bg-red-700 transition">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<a href="{{ url_for('add_schedule', board_id=board.id) }}" class="bg-[#f7d91a] text-black px-6 py-3 rounded-md font-semibold shadow hover:bg-yellow-300 transition inline-block">Add Schedule</a>
|
||||
<a href="{{ url_for('edit_board', board_id=board.id) }}" class="bg-white text-black border border-black px-6 py-3 rounded-md font-semibold shadow hover:bg-gray-100 transition inline-block ml-4">Terug naar bord bewerken</a>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
36
templates/change_password.html
Normal file
36
templates/change_password.html
Normal file
@@ -0,0 +1,36 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Wachtwoord wijzigen{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="space-y-8 max-w-6xl mx-auto px-4">
|
||||
<div class="max-w-md mx-auto">
|
||||
<h2 class="text-3xl font-semibold mb-6">Wijzig Wachtwoord</h2>
|
||||
|
||||
{% if msg %}
|
||||
<div class="bg-blue-100 border border-blue-400 text-blue-700 px-6 py-4 rounded mb-6">{{ msg }}</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" class="space-y-6">
|
||||
<div>
|
||||
<label for="current_password" class="block mb-3 font-semibold text-gray-700">Huidig wachtwoord</label>
|
||||
<input type="password" id="current_password" name="current_password" required class="w-full rounded-md border border-gray-300 p-4 text-lg placeholder-gray-400 shadow-sm focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 transition">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="new_password" class="block mb-3 font-semibold text-gray-700">Nieuw wachtwoord</label>
|
||||
<input type="password" id="new_password" name="new_password" required class="w-full rounded-md border border-gray-300 p-4 text-lg placeholder-gray-400 shadow-sm focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 transition">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="confirm_password" class="block mb-3 font-semibold text-gray-700">Bevestig nieuw wachtwoord</label>
|
||||
<input type="password" id="confirm_password" name="confirm_password" required class="w-full rounded-md border border-gray-300 p-4 text-lg placeholder-gray-400 shadow-sm focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 transition">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="bg-[#f7d91a] text-black w-full rounded-md py-4 font-semibold shadow hover:bg-yellow-300 transition">Opslaan</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
104
templates/church_settings.html
Normal file
104
templates/church_settings.html
Normal file
@@ -0,0 +1,104 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Kerk Instellingen{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="space-y-8 max-w-6xl mx-auto px-4">
|
||||
|
||||
|
||||
{% if msg %}
|
||||
<div class="bg-blue-100 border border-blue-400 text-blue-700 px-6 py-4 rounded mb-6">{{ msg }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="flex flex-wrap gap-10">
|
||||
|
||||
<form method="post" class="flex-1 min-w-[300px] max-w-lg space-y-6" enctype="multipart/form-data">
|
||||
|
||||
<input type="hidden" name="form_type" value="contact">
|
||||
|
||||
<h3 class="text-2xl font-semibold">Kerkgegevens</h3>
|
||||
|
||||
<div>
|
||||
<label for="name" class="block mb-3 font-semibold text-gray-700">Kerknaam</label>
|
||||
<input type="text" id="name" name="name" value="{{ church.name }}" class="w-full rounded-md border border-gray-300 p-4 text-lg placeholder-gray-400 shadow-sm focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 transition">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="contact_email" class="block mb-3 font-semibold text-gray-700">Contact Email</label>
|
||||
<input type="email" id="contact_email" name="contact_email" value="{{ church.contact_email }}" class="w-full rounded-md border border-gray-300 p-4 text-lg placeholder-gray-400 shadow-sm focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 transition">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="contact_phone" class="block mb-3 font-semibold text-gray-700">Telefoon</label>
|
||||
<input type="text" id="contact_phone" name="contact_phone" value="{{ church.contact_phone }}" class="w-full rounded-md border border-gray-300 p-4 text-lg placeholder-gray-400 shadow-sm focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 transition">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="contact_address" class="block mb-3 font-semibold text-gray-700">Adres</label>
|
||||
<input type="text" id="contact_address" name="contact_address" value="{{ church.contact_address }}" class="w-full rounded-md border border-gray-300 p-4 text-lg placeholder-gray-400 shadow-sm focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 transition">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="logo" class="block mb-3 font-semibold text-gray-700">Logo uploaden</label>
|
||||
<input type="file" id="logo" name="logo" accept="image/png, image/jpeg, image/jpg, image/gif" class="w-full border border-gray-300 rounded-md p-4">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="bg-[#f7d91a] text-black px-6 py-3 rounded-md font-semibold shadow hover:bg-yellow-300 transition">Opslaan</button>
|
||||
|
||||
</form>
|
||||
|
||||
<div class="flex-1 min-w-[300px] max-w-md">
|
||||
|
||||
<h3 class="text-2xl font-semibold mb-4">Gebruikers van deze kerk</h3>
|
||||
|
||||
<div class="overflow-x-auto rounded-lg border border-gray-300 mb-6">
|
||||
<table class="min-w-full divide-y divide-gray-200 bg-white">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700">Gebruikersnaam</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm text-gray-900 flex items-center justify-between">
|
||||
{{ user.username }}
|
||||
{% if user.id != current_user.id %}
|
||||
<form method="post" action="{{ url_for('church_delete_user', user_id=user.id) }}" onsubmit="return confirm('Weet je zeker dat je deze gebruiker wilt verwijderen?');">
|
||||
<button type="submit" class="bg-red-600 text-white px-6 py-3 rounded font-semibold hover:bg-red-700 ml-4 transition">Verwijderen</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h3 class="text-2xl font-semibold mb-4">Nieuwe gebruiker toevoegen aan deze kerk</h3>
|
||||
|
||||
<form method="post" class="space-y-6" >
|
||||
<input type="hidden" name="form_type" value="user">
|
||||
<div>
|
||||
<label for="username" class="block mb-3 font-semibold text-gray-700">Emailadres</label>
|
||||
<input type="email" id="username" name="username" placeholder="email@voorbeeld.com" class="w-full rounded-md border border-gray-300 p-4 text-lg placeholder-gray-400 shadow-sm focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 transition">
|
||||
</div>
|
||||
<div>
|
||||
<label for="password" class="block mb-3 font-semibold text-gray-700">Wachtwoord</label>
|
||||
<input type="password" id="password" name="password" class="w-full rounded-md border border-gray-300 p-4 text-lg placeholder-gray-400 shadow-sm focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 transition">
|
||||
</div>
|
||||
<button type="submit" class="bg-[#f7d91a] text-black px-6 py-3 rounded-md font-semibold shadow hover:bg-yellow-300 transition">Toevoegen</button>
|
||||
</form>
|
||||
|
||||
<a href="{{ url_for('change_password') }}" class="bg-white text-black border border-black px-4 py-2 rounded-md font-semibold shadow hover:bg-gray-100 transition inline-block mt-6 inline-block text-base">Wachtwoord wijzigen</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<a href="{{ url_for('portal') }}" class="bg-white text-black border border-black px-6 py-3 rounded-md font-semibold shadow hover:bg-gray-100 transition inline-block">Terug naar portal</a>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
228
templates/display_board.html
Normal file
228
templates/display_board.html
Normal file
@@ -0,0 +1,228 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="nl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Liturgiebord</title>
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
body {
|
||||
background: #222;
|
||||
color: #fff;
|
||||
font-family: 'Lora', serif;
|
||||
height: 1920px;
|
||||
width: 1080px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.liturgie-board {
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start; /* Align all rows from the top */
|
||||
align-items: flex-start;
|
||||
|
||||
gap: 0vh;
|
||||
padding-left: 0;
|
||||
position: relative;
|
||||
}
|
||||
.liturgie-line {
|
||||
margin: auto;
|
||||
width: 1080px;
|
||||
justify-items: center;
|
||||
|
||||
text-align: left;
|
||||
font-size: 130px;
|
||||
line-height: 1.1;
|
||||
white-space: nowrap;
|
||||
height: 190px; /* Fixed height ensures every row occupies space, even when empty */
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
z-index: 2;
|
||||
text-shadow: 2px 2px 8px #000, 0 0 4px #000;
|
||||
/* Optional: Uncomment for visible empty row borders */
|
||||
/* border-bottom: 1px solid rgba(255,255,255,0.15); */
|
||||
}
|
||||
.liturgie-bg-overlay {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.3);
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="transform-origin: top left;">
|
||||
<div class="liturgie-board" {% if board.background_image %}
|
||||
style="background-image: url('{% if board.background_image.startswith('default_') %}{{ url_for('static', filename=board.background_image) }}{% else %}{{ url_for('uploaded_file', filename=board.background_image) }}{% endif %}'); background-size: cover; background-position: center; background-repeat: no-repeat;"
|
||||
{% endif %}>
|
||||
{% if board.background_image %}<div class="liturgie-bg-overlay"></div>{% endif %}
|
||||
{% if schedule_content %}
|
||||
{% set lines = schedule_content.split('\n') %}
|
||||
{% for i in range(10) %}
|
||||
<div class="liturgie-line">{{ lines[i] if lines|length > i else ' ' }}</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="liturgie-line">{{ board.line1 or ' ' }}</div>
|
||||
<div class="liturgie-line">{{ board.line2 or ' ' }}</div>
|
||||
<div class="liturgie-line">{{ board.line3 or ' ' }}</div>
|
||||
<div class="liturgie-line">{{ board.line4 or ' ' }}</div>
|
||||
<div class="liturgie-line">{{ board.line5 or ' ' }}</div>
|
||||
<div class="liturgie-line">{{ board.line6 or ' ' }}</div>
|
||||
<div class="liturgie-line">{{ board.line7 or ' ' }}</div>
|
||||
<div class="liturgie-line">{{ board.line8 or ' ' }}</div>
|
||||
<div class="liturgie-line">{{ board.line9 or ' ' }}</div>
|
||||
<div class="liturgie-line">{{ board.line10 or ' ' }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// LIVE PREVIEW FEATURE: Accept messages with new data and update the lines/background
|
||||
window.addEventListener('message', function(event) {
|
||||
// You may want to restrict this for security:
|
||||
// if (event.origin !== 'YOUR_DOMAIN') return;
|
||||
var data = event.data;
|
||||
if (!data || typeof data !== 'object' || !data.type || data.type !== 'updateBoard') return;
|
||||
|
||||
// Update all lines
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
var el = document.querySelector('.liturgie-line:nth-child(' + (data.bg ? i+1 : i) + ')');
|
||||
if (el) el.textContent = (data['line'+i] || ' ');
|
||||
}
|
||||
// Update background if sent
|
||||
if ('bg' in data) {
|
||||
document.querySelector('.liturgie-board').style.backgroundImage = data.bg ? ("url('" + data.bg + "')") : 'none';
|
||||
// Show/hide overlay
|
||||
var overlay = document.querySelector('.liturgie-bg-overlay');
|
||||
if (overlay) {
|
||||
overlay.style.display = data.bg ? '' : 'none';
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
</script>
|
||||
<script>
|
||||
// Poll every 5 seconds to check for active schedule update
|
||||
(function() {
|
||||
let currentLines = [];
|
||||
let currentBackground = null;
|
||||
{% if schedule_content %}
|
||||
currentLines = {{ schedule_content.split('\n')|tojson }};
|
||||
{% else %}
|
||||
currentLines = Array(10).fill(' ');
|
||||
{% endif %}
|
||||
|
||||
const boardId = {{ board.id }};
|
||||
|
||||
function updateBoardFromData(data) {
|
||||
// Use postMessage event format to update lines & background
|
||||
const msg = { type: 'updateBoard' };
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
msg['line'+i] = data.lines[i-1] || ' ';
|
||||
}
|
||||
if (data.background) {
|
||||
msg.bg = data.background;
|
||||
} else {
|
||||
msg.bg = null;
|
||||
}
|
||||
window.postMessage(msg, '*');
|
||||
currentLines = data.lines;
|
||||
currentBackground = data.background || null;
|
||||
}
|
||||
|
||||
async function pollActiveSchedule() {
|
||||
try {
|
||||
const response = await fetch(`/board/${boardId}/active_schedule_json`);
|
||||
if (!response.ok) throw new Error('Network response not ok');
|
||||
const data = await response.json();
|
||||
|
||||
// Compare new lines with current lines
|
||||
let changed = false;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
if ((data.lines[i] || ' ') !== (currentLines[i] || ' ')) {
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for background change
|
||||
if ((data.background || null) !== currentBackground) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
updateBoardFromData(data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error polling active schedule:', e);
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(pollActiveSchedule, 5000);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Poll every 5 seconds to check for active schedule update
|
||||
(function() {
|
||||
let currentLines = [];
|
||||
{% if schedule_content %}
|
||||
currentLines = {{ schedule_content.split('\n')|tojson }};
|
||||
{% else %}
|
||||
currentLines = Array(10).fill(' ');
|
||||
{% endif %}
|
||||
|
||||
const boardId = {{ board.id }};
|
||||
|
||||
function updateBoardFromData(data) {
|
||||
// Use postMessage event format to update lines & background
|
||||
const msg = { type: 'updateBoard' };
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
msg['line'+i] = data.lines[i-1] || ' ';
|
||||
}
|
||||
if (data.background) {
|
||||
msg.bg = data.background;
|
||||
}
|
||||
window.postMessage(msg, '*');
|
||||
currentLines = data.lines;
|
||||
}
|
||||
|
||||
async function pollActiveSchedule() {
|
||||
try {
|
||||
const response = await fetch(`/board/${boardId}/active_schedule_json`);
|
||||
if (!response.ok) throw new Error('Network response not ok');
|
||||
const data = await response.json();
|
||||
|
||||
// Compare new lines with current lines
|
||||
let changed = false;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
if ((data.lines[i] || ' ') !== (currentLines[i] || ' ')) {
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Also consider background change
|
||||
// We can extend this if needed, skipping for now
|
||||
|
||||
if (changed) {
|
||||
updateBoardFromData(data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error polling active schedule:', e);
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(pollActiveSchedule, 5000);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
391
templates/edit_board.html
Normal file
391
templates/edit_board.html
Normal file
@@ -0,0 +1,391 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}Bewerk Liturgiebord - Digitale Liturgie{% endblock %}
|
||||
{% block content %}
|
||||
<style>
|
||||
.edit-board-container {
|
||||
padding: 1rem 1rem;
|
||||
background: white;
|
||||
border-radius: 0.5rem;
|
||||
margin: 0 auto 1rem auto;
|
||||
}
|
||||
|
||||
.board-title {
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
color: #000000;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.lines-section {
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.line-input-group {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.line-input-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.75rem;
|
||||
font-weight: 700;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.line-input-group input {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 1.125rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid #d1d5db;
|
||||
box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.1);
|
||||
transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.line-input-group input:focus {
|
||||
outline: none;
|
||||
border-color: #2563eb;
|
||||
box-shadow: 0 0 5px #93c5fd;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: flex-start;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.button-group button {
|
||||
padding: 0.75rem 1.75rem;
|
||||
font-size: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 700;
|
||||
transition: background-color 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.schedule-section {
|
||||
max-width: 100%;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.schedule-section h3 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.schedule-section table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.preview-section {
|
||||
max-width: 100%;
|
||||
text-align: center;
|
||||
font-family: 'Lora', serif;
|
||||
}
|
||||
|
||||
.preview-wrapper {
|
||||
width: 250px;
|
||||
height: 444px; /* 9:16 aspect ratio */
|
||||
margin: 0 auto;
|
||||
background: #222;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
iframe#live-preview-iframe {
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
border: 0;
|
||||
pointer-events: none;
|
||||
transform-origin: top left;
|
||||
transform: scale(0.23);
|
||||
margin: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.edit-board-container {
|
||||
padding: 15px;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.board-title {
|
||||
font-size: 1.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.button-group button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.preview-wrapper {
|
||||
width: 180px;
|
||||
height: 320px;
|
||||
margin: 0 auto;
|
||||
border-radius: 0.5rem;
|
||||
background: #222;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
iframe#live-preview-iframe {
|
||||
transform: scale(0.23);
|
||||
position: static;
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
/* Highlight for drag-over between lines in preview */
|
||||
.liturgie-line.line-over {
|
||||
background: rgba(255,255,255,0.22);
|
||||
border: 2px dashed #fff;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
transition: background 0.12s, border 0.12s;
|
||||
}
|
||||
|
||||
.liturgie-line.swap-animate {
|
||||
animation: swap-pulse 0.35s;
|
||||
}
|
||||
@keyframes swap-pulse {
|
||||
0% { background: #ffe066; }
|
||||
60% { background: #fff3cd; }
|
||||
100% { background: none; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="edit-board-container">
|
||||
<div style="display: flex; gap: 2rem; align-items: flex-start; flex-wrap: wrap;">
|
||||
<!-- Left: Live (editable) preview -->
|
||||
<form method="post" id="boardForm" style="flex: 1 1 300px; min-width: 250px; max-width:250px;">
|
||||
<div class="preview-section">
|
||||
<div class="preview-wrapper"
|
||||
style="width:250px; height:444px; background: #222; border-radius: 0.5rem; overflow: hidden; position:relative;
|
||||
background-image: {% if board.background_image %}url('{% if board.background_image.startswith('default_') %}{{ url_for('static', filename=board.background_image) }}{% else %}{{ url_for('uploaded_file', filename=board.background_image) }}{% endif %}'){% else %}none{% endif %}; background-size:cover; background-position:center;">
|
||||
{% if board.background_image %}
|
||||
<div class="liturgie-bg-overlay" style="
|
||||
content:''; position:absolute; top:0; left:0; right:0; bottom:0;
|
||||
background:rgba(0,0,0,0.3); z-index:1; pointer-events:none;">
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if schedule_active %}
|
||||
{% set matched_schedules = schedules|selectattr('content', 'equalto', schedule_content)|list %}
|
||||
<div style="position:absolute; top:0; left:0; right:0; bottom:0; z-index:10; display:flex; justify-content:center; align-items:center; background: rgba(0,0,0,0.7); color:white; font-weight:bold; font-size:1rem; text-align:center; padding: 1rem;">
|
||||
Geplande dienst ({{ matched_schedules[0].name if matched_schedules|length > 0 else 'Onbekend' }}) is nu actief
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="liturgie-board"
|
||||
style="width:100%; height:100%; display:flex; flex-direction:column; justify-content:center; align-items:flex-start; gap:0.6vh; padding-left:5%; position:relative; z-index:2;">
|
||||
{% if schedule_active %}
|
||||
{% set lines = schedule_content.split('\\n') %}
|
||||
{% for i in range(10) %}
|
||||
<div class="liturgie-line" style="width:90%; font-size:28px; line-height:1.2; text-shadow:2px 2px 8px #000, 0 0 4px #000; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin:0; padding:0; color:#fff; text-align:left;">
|
||||
{{ lines[i] if lines|length > i else ' ' }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for i in range(1, 11) %}
|
||||
<div class="liturgie-line"
|
||||
contenteditable="true"
|
||||
data-line="line{{ i }}"
|
||||
draggable="true"
|
||||
style="width:90%; font-size:28px; line-height:1.2; text-shadow:2px 2px 8px #000, 0 0 4px #000; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin:0; padding:0; color:#fff; text-align:left;"
|
||||
>{{ board['line' ~ i] or ' ' }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if schedule_active %}
|
||||
<p style="color: red;">Er is een dienst actief! Pas deze aan via de planning.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
<!-- Right: Schedule section -->
|
||||
<div class="schedule-section" style="flex: 1 1 380px; min-width:330px;">
|
||||
<form method="post" style="margin-bottom: 1rem;">
|
||||
<input type="hidden" name="form_type" value="name">
|
||||
<div style="display:flex; gap:8px; align-items:center;">
|
||||
<input id="boardNameInput" name="name" type="text" value="{{ board.name }}" style="font-size:1.1rem; padding:0.3em 0.6em; border-radius:0.3em; border:1px solid #aaa; width:60%;">
|
||||
<button type="submit" class="bg-[#f7d91a] text-black text-xs px-6 py-3 rounded hover:bg-yellow-300 font-semibold transition" style="margin-left:5px;">Opslaan</button>
|
||||
</div>
|
||||
</form>
|
||||
<h3 class="text-xl font-semibold mb-3">Geplande diensten</h3>
|
||||
<div class="overflow-x-auto rounded-lg border border-gray-300">
|
||||
{% if schedules|length == 0 %}
|
||||
<p class="p-4">Geen geplande inhoud.</p>
|
||||
{% else %}
|
||||
<table class="min-w-full divide-y divide-gray-200 bg-white">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700">Naam</th>
|
||||
<th class="px-6 py-3 text-sm font-semibold text-gray-700 text-center">Datum</th>
|
||||
<th class="px-6 py-3 text-sm font-semibold text-gray-700 text-center">Starttijd</th>
|
||||
<th class="px-6 py-3 text-sm font-semibold text-gray-700 text-center">Eindtijd</th>
|
||||
<th class="px-6 py-3 text-sm font-semibold text-gray-700 text-center">Acties</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
{% for s in schedules %}
|
||||
<tr>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-gray-900 flex items-center gap-2">
|
||||
<span style="display:inline-block; width:0.75rem; height:0.75rem; border-radius:9999px;"
|
||||
title="{% if schedule_active and active_schedule and s.id == active_schedule.id %}Actief{% else %}Niet actief{% endif %}"
|
||||
class="{% if schedule_active and active_schedule and s.id == active_schedule.id %}bg-green-500{% else %}bg-red-500{% endif %}">
|
||||
</span>
|
||||
{{ s.name }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center text-gray-900">{{ s.date.strftime('%Y-%m-%d') }}</td>
|
||||
<td class="px-6 py-4 text-center text-gray-900">{{ s.start_time.strftime('%H:%M') }}</td>
|
||||
<td class="px-6 py-4 text-center text-gray-900">{{ s.end_time.strftime('%H:%M') }}</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<!-- Removed delete button here: schedules can't be deleted from board edit page -->
|
||||
<a href="{{ url_for('edit_global_schedule', schedule_id=s.id) }}" class="bg-[#f7d91a] text-black text-xs px-6 py-3 rounded hover:bg-yellow-300 font-semibold transition">Bewerken</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
<a href="{{ url_for('add_global_schedule') }}" class="bg-[#f7d91a] text-black text-xs px-6 py-3 rounded hover:bg-yellow-300 font-semibold inline-block mt-3 transition">Dienst toevoegen</a>
|
||||
<div class="button-group mt-3">
|
||||
<button type="button" class="bg-white text-black border border-black px-6 py-3 rounded font-semibold hover:bg-gray-100 transition"
|
||||
id="save-and-upload-bg"
|
||||
data-edit-url="{{ url_for('edit_board', board_id=board.id) }}"
|
||||
data-upload-url="{{ url_for('upload_background', board_id=board.id) }}">
|
||||
Achtergrondafbeelding wijzigen
|
||||
</button>
|
||||
<!-- Removed Opslaan button as live save is implemented -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{% if not schedule_active %}
|
||||
<script>
|
||||
// --- Line drag-and-swap logic ---
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
let dragSrc = null;
|
||||
const lines = document.querySelectorAll('.liturgie-line[data-line]');
|
||||
lines.forEach(line => {
|
||||
line.addEventListener('dragstart', function (e) {
|
||||
dragSrc = this;
|
||||
this.classList.add('dragging');
|
||||
});
|
||||
line.addEventListener('dragend', function (e) {
|
||||
this.classList.remove('dragging');
|
||||
dragSrc = null;
|
||||
lines.forEach(l => l.classList.remove('line-over'));
|
||||
});
|
||||
line.addEventListener('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
if (this !== dragSrc) this.classList.add('line-over');
|
||||
});
|
||||
line.addEventListener('dragleave', function (e) {
|
||||
this.classList.remove('line-over');
|
||||
});
|
||||
line.addEventListener('drop', function (e) {
|
||||
e.preventDefault();
|
||||
this.classList.remove('line-over');
|
||||
if (dragSrc && dragSrc !== this) {
|
||||
// Only swap textContent, not the whole element
|
||||
const tmp = this.textContent;
|
||||
this.textContent = dragSrc.textContent;
|
||||
dragSrc.textContent = tmp;
|
||||
// animation for both lines
|
||||
this.classList.add('swap-animate');
|
||||
dragSrc.classList.add('swap-animate');
|
||||
setTimeout(() => {
|
||||
this.classList.remove('swap-animate');
|
||||
dragSrc.classList.remove('swap-animate');
|
||||
}, 400);
|
||||
// trigger input event for autosave
|
||||
this.dispatchEvent(new Event('input'));
|
||||
dragSrc.dispatchEvent(new Event('input'));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
// --- End drag-and-swap logic ---
|
||||
|
||||
// On submit, copy preview lines to hidden fields
|
||||
const boardForm = document.getElementById('boardForm');
|
||||
if (boardForm) {
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
// create hidden inputs for each line
|
||||
let hidden = document.createElement('input');
|
||||
hidden.type = 'hidden';
|
||||
hidden.name = 'line'+i;
|
||||
hidden.id = 'hidden-line'+i;
|
||||
boardForm.appendChild(hidden);
|
||||
}
|
||||
|
||||
async function saveLines() {
|
||||
const formData = new FormData();
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const text = document.querySelector('.liturgie-line[data-line="line'+i+'"]').textContent;
|
||||
formData.append('line'+i, text);
|
||||
}
|
||||
|
||||
const response = await fetch(boardForm.action, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Failed to save lines.');
|
||||
}
|
||||
}
|
||||
|
||||
// Add auto save event listeners to editable lines with debounce
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const editableLine = document.querySelector('.liturgie-line[data-line="line'+i+'"]');
|
||||
if (editableLine) {
|
||||
editableLine.addEventListener('input', () => {
|
||||
if (window.autoSaveTimeout) clearTimeout(window.autoSaveTimeout);
|
||||
window.autoSaveTimeout = setTimeout(saveLines, 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent form's default submit to avoid page reload on autosave
|
||||
boardForm.addEventListener('submit', function(e) {
|
||||
if (document.getElementById('redirect_to_upload').value === '0') {
|
||||
e.preventDefault();
|
||||
saveLines();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Added script for wallpaper button
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const btn = document.getElementById('save-and-upload-bg');
|
||||
if (btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
const uploadUrl = this.getAttribute('data-upload-url');
|
||||
if (uploadUrl) {
|
||||
window.location.href = uploadUrl;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% else %}
|
||||
<script>
|
||||
// When schedule is active, there's nothing to submit for the lines and nothing to sync.
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
43
templates/global_schedules.html
Normal file
43
templates/global_schedules.html
Normal file
@@ -0,0 +1,43 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Globale Planningen{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="space-y-8 max-w-6xl mx-auto px-4">
|
||||
<h1 class="text-3xl font-semibold mb-6">Globale planningen</h1>
|
||||
<div class="overflow-x-auto rounded-lg border border-gray-300">
|
||||
<table class="min-w-full divide-y divide-gray-200 bg-white">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700">Naam</th>
|
||||
<th class="px-6 py-3 text-sm font-semibold text-gray-700 text-center">Datum</th>
|
||||
<th class="px-6 py-3 text-sm font-semibold text-gray-700 text-center">Starttijd</th>
|
||||
<th class="px-6 py-3 text-sm font-semibold text-gray-700 text-center">Eindtijd</th>
|
||||
<th class="px-6 py-3 text-sm font-semibold text-gray-700 text-center">Toegewezen borden</th>
|
||||
<th class="px-6 py-3 text-sm font-semibold text-gray-700 text-center">Acties</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
{% for schedule in schedules %}
|
||||
<tr>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-gray-900">{{ schedule.name }}</td>
|
||||
<td class="px-6 py-4 text-center text-gray-900">{{ schedule.date.strftime('%Y-%m-%d') }}</td>
|
||||
<td class="px-6 py-4 text-center text-gray-900">{{ schedule.start_time.strftime('%H:%M') }}</td>
|
||||
<td class="px-6 py-4 text-center text-gray-900">{{ schedule.end_time.strftime('%H:%M') }}</td>
|
||||
<td class="px-6 py-4 text-center text-gray-900">
|
||||
{% for board in schedule.boards %}{{ board.name }}{% if not loop.last %}, {% endif %}{% endfor %}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<a href="{{ url_for('edit_global_schedule', schedule_id=schedule.id) }}" class="bg-[#f7d91a] text-black text-xs px-6 py-3 rounded font-semibold hover:bg-yellow-300 transition">Bewerken</a>
|
||||
<form action="{{ url_for('delete_global_schedule', schedule_id=schedule.id) }}" method="POST" style="display:inline;" onsubmit="return confirm('Weet je zeker dat je deze planning wilt verwijderen?');">
|
||||
<button type="submit" class="bg-red-600 text-white text-xs px-6 py-3 rounded font-semibold hover:bg-red-700 transition">Verwijderen</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<a href="{{ url_for('add_global_schedule') }}" class="bg-[#f7d91a] text-black px-6 py-3 rounded-md font-semibold shadow hover:bg-yellow-300 inline-block mb-6 transition">Nieuwe planning toevoegen</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
337
templates/index.html
Normal file
337
templates/index.html
Normal file
@@ -0,0 +1,337 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Welkom bij Digitale Liturgie{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
/* Modern Google Fonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
|
||||
body { font-family: 'Inter', sans-serif; }
|
||||
|
||||
.hero-section {
|
||||
position: relative;
|
||||
background: url("{{ url_for('static', filename='Hero.jpg') }}") no-repeat center center/cover;
|
||||
min-height: 30rem;
|
||||
border-radius: 1.15rem;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
animation: fadeInHero 1.2s cubic-bezier(.77,0,.18,1) both;
|
||||
}
|
||||
@keyframes fadeInHero {
|
||||
from { opacity: 0; transform: translateY(-30px);}
|
||||
to { opacity: 1; transform: translateY(0);}
|
||||
}
|
||||
.hero-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(135deg, rgba(40,40,50,0.75) 0%, rgba(10,0,40,0.40) 70%);
|
||||
z-index: 1;
|
||||
border-radius: 1.15rem;
|
||||
}
|
||||
.hero-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
max-width: 42rem;
|
||||
text-align: center;
|
||||
padding: 2rem 2rem;
|
||||
animation: fadeUp 1.2s .3s cubic-bezier(.77,0,.18,1) both;
|
||||
}
|
||||
.hero-content h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
text-shadow: 0 5px 32px rgba(0,0,0,0.32);
|
||||
margin-bottom: 0.5rem;
|
||||
letter-spacing: -1.2px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.hero-content p {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 2rem;
|
||||
color: #f4f4f4;
|
||||
}
|
||||
.modern-btn {
|
||||
background: linear-gradient(90deg, #f7d91a 0%, #ffe486 100%);
|
||||
color: #181818;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
padding: 1rem 2.3rem;
|
||||
border-radius: 0.66rem;
|
||||
font-size: 1.14rem;
|
||||
box-shadow: 0 4px 24px rgba(247,217,26,0.13);
|
||||
transition: box-shadow 0.23s, background 0.23s, transform 0.18s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.modern-btn:hover {
|
||||
background: linear-gradient(90deg,#ffe486 0%, #f7d91a 100%);
|
||||
box-shadow: 0 8px 28px rgba(247,217,26,0.19);
|
||||
transform: translateY(-2px) scale(1.045);
|
||||
color: #25201b;
|
||||
}
|
||||
|
||||
/* Contact section styling */
|
||||
.contact-section {
|
||||
background: white;
|
||||
border-radius: 1.15rem;
|
||||
margin-top: 1rem;
|
||||
padding: 2.2rem 1.5rem;
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.09);
|
||||
max-width: 98vw;
|
||||
min-width: 280px;
|
||||
animation: fadeUp 1.1s .6s cubic-bezier(.77,0,.18,1) both;
|
||||
}
|
||||
.contact-section h2 {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.contact-section input,
|
||||
.contact-section textarea {
|
||||
width: 100%;
|
||||
border: 1.3px solid #e6e6e6;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.9rem 1.14rem;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
background: #f8f9fa;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
.contact-section input:focus,
|
||||
.contact-section textarea:focus {
|
||||
border-color: #f7d91a;
|
||||
outline: none;
|
||||
background: #fffde6;
|
||||
}
|
||||
.contact-section button {
|
||||
margin-top: 0.3rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Cards Area */
|
||||
.features-area {
|
||||
display: grid;
|
||||
gap: 2.2rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
margin-top: 2.2rem;
|
||||
margin-bottom: 2.5rem;
|
||||
animation: fadeInStagger 1s cubic-bezier(.77,0,.18,1) both;
|
||||
}
|
||||
@keyframes fadeUp {
|
||||
from { opacity: 0; transform: translateY(50px);}
|
||||
to { opacity: 1; transform: translateY(0);}
|
||||
}
|
||||
@keyframes fadeInStagger {
|
||||
from { opacity: 0;}
|
||||
to { opacity: 1;}
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: white;
|
||||
border-radius: 1.09rem;
|
||||
box-shadow: 0 6px 20px rgba(0,0,0,0.06);
|
||||
padding: 2rem 1.2rem 1.4rem 1.2rem;
|
||||
text-align: center;
|
||||
min-height: 19.5rem;
|
||||
animation: fadeUp 0.8s cubic-bezier(.77,0,.18,1) both;
|
||||
transition: transform 0.22s, box-shadow 0.22s;
|
||||
will-change: transform;
|
||||
/* Animation delay stagger per card (manual in HTML) */
|
||||
}
|
||||
.feature-card:hover {
|
||||
transform: translateY(-7px) scale(1.02);
|
||||
box-shadow: 0 16px 40px rgba(247,217,26,0.13);
|
||||
}
|
||||
.feature-card img {
|
||||
max-width: 55px;
|
||||
margin-bottom: 1.1rem;
|
||||
margin-top: -0.8rem;
|
||||
filter: drop-shadow(0 0 2px rgba(40,40,40,0.06));
|
||||
transition: transform 0.17s;
|
||||
}
|
||||
.feature-card:hover img {
|
||||
transform: scale(1.095) rotate(2deg);
|
||||
}
|
||||
.feature-card h3 {
|
||||
font-size: 1.23rem;
|
||||
margin-bottom: 0.9rem;
|
||||
font-weight: 700;
|
||||
color: #181a1d;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.feature-card ul {
|
||||
text-align: left;
|
||||
color: #434243;
|
||||
font-size: 1.02rem;
|
||||
list-style: none;
|
||||
padding-left: 0.7em;
|
||||
line-height: 1.7;
|
||||
}
|
||||
.feature-card ul li::before {
|
||||
content: '• ';
|
||||
color: #f7d91a;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Responsive hero/contact split */
|
||||
@media (min-width: 1000px) {
|
||||
.hero-container {
|
||||
display: flex;
|
||||
gap: 2.4rem;
|
||||
max-width: 1330px;
|
||||
margin: 0 auto 3.3rem auto;
|
||||
}
|
||||
.hero-section, .contact-section {
|
||||
flex: 1.2;
|
||||
min-height: 32rem;
|
||||
}
|
||||
.contact-section {
|
||||
min-width: 350px;
|
||||
margin-top: 0;
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 780px) {
|
||||
.hero-section {
|
||||
min-height: 22rem;
|
||||
}
|
||||
.hero-content h1 { font-size: 2rem;}
|
||||
.features-area { gap: 1rem;}
|
||||
.feature-card { padding: 1.3rem 0.5rem; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="hero-section" style="margin-bottom:2.5rem;">
|
||||
<div class="hero-overlay"></div>
|
||||
<div class="hero-content">
|
||||
<h1>Psalmbord Online</h1>
|
||||
<p>
|
||||
Welkom bij Psalmbord Online.<br>
|
||||
Beheer, plan en presenteer moeiteloos je kerkdiensten.<br>
|
||||
Start direct met een eigen account!
|
||||
</p>
|
||||
<a href="/login" class="modern-btn">Log in</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="section-intro" style="margin-top: 3.7rem; margin-bottom:1.5rem; animation: fadeUp 1.1s .3s cubic-bezier(.77,0,.18,1) both;">
|
||||
<h2 class="text-3xl font-bold text-center mb-2" style="font-size:2.1rem;">Wat kan Digitale Liturgie voor u doen?</h2>
|
||||
<p class="text-center text-md mb-8" style="color:#62666b;">Beheer en presenteer moeiteloos uw kerkdiensten, gebruikers en schermen.</p>
|
||||
</div>
|
||||
|
||||
<div class="features-area">
|
||||
<div class="feature-card" style="animation-delay:.09s;">
|
||||
<img src="{{ url_for('static', filename='icons/group.png') }}" alt="Gebruikersbeheer">
|
||||
<h3>Gebruikersbeheer</h3>
|
||||
<ul>
|
||||
<li>Aanmelden & Registreren</li>
|
||||
<li>Wachtwoordbeheer</li>
|
||||
<li>Lid & Admin-rollen</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="feature-card" style="animation-delay:.20s;">
|
||||
<img src="{{ url_for('static', filename='icons/church.png') }}" alt="Kerkenbeheer">
|
||||
<h3>Kerkenbeheer</h3>
|
||||
<ul>
|
||||
<li>Kerk aanmaken & aanpassen</li>
|
||||
<li>Logo uploaden</li>
|
||||
<li>Activatie beheren</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="feature-card" style="animation-delay:.35s;">
|
||||
<img src="{{ url_for('static', filename='icons/board.png') }}" alt="Bordenbeheer">
|
||||
<h3>Bordenbeheer</h3>
|
||||
<ul>
|
||||
<li>Borden toevoegen, bewerken, verwijderen</li>
|
||||
<li>Max. 10 tekstregels per bord</li>
|
||||
<li>Achtergrond instellen of uploaden</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="feature-card" style="animation-delay:.50s;">
|
||||
<img src="{{ url_for('static', filename='icons/task.png') }}" alt="Planningen">
|
||||
<h3>Planningen</h3>
|
||||
<ul>
|
||||
<li>Diensten & liturgie-invulling per datum</li>
|
||||
<li>Meerdere borden per planning</li>
|
||||
<li>Automatisch tonen volgens tijd</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contact form at BOTTOM -->
|
||||
<div class="contact-section" style="max-width:none; margin:3.8rem 0 0 0; width:100%;">
|
||||
<h2>Contacteer ons</h2>
|
||||
<form method="POST" action="/contact" autocomplete="off">
|
||||
<div class="contact-form-fields">
|
||||
<div class="inputs-left">
|
||||
<input type="text" id="name" name="name" placeholder="Naam" required>
|
||||
<input type="text" id="church" name="church" placeholder="Kerknaam" required>
|
||||
<input type="tel" id="phone" name="phone" placeholder="Telefoonnummer" required>
|
||||
<input type="email" id="email" name="email" placeholder="E-mail" required>
|
||||
</div>
|
||||
<div class="textarea-right">
|
||||
<textarea id="message" name="message" rows="7" placeholder="Uw bericht..." required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="modern-btn">Verstuur</button>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
.contact-form-fields {
|
||||
display: flex;
|
||||
gap: 2.2rem;
|
||||
align-items: stretch;
|
||||
}
|
||||
.inputs-left {
|
||||
flex: 1 1 210px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
min-width: 190px;
|
||||
}
|
||||
.inputs-left input {
|
||||
opacity: 0;
|
||||
transform: translateY(32px);
|
||||
animation: fadeFormInput 0.8s cubic-bezier(.77,0,.18,1) forwards;
|
||||
}
|
||||
.inputs-left input:nth-child(1) { animation-delay: 0.18s; }
|
||||
.inputs-left input:nth-child(2) { animation-delay: 0.32s; }
|
||||
.inputs-left input:nth-child(3) { animation-delay: 0.46s; }
|
||||
.inputs-left input:nth-child(4) { animation-delay: 0.6s; }
|
||||
.textarea-right textarea {
|
||||
opacity: 0;
|
||||
transform: translateY(32px);
|
||||
animation: fadeFormInput 0.8s 0.74s cubic-bezier(.77,0,.18,1) forwards;
|
||||
height: 100%;
|
||||
min-height: 150px;
|
||||
resize: vertical;
|
||||
}
|
||||
.contact-section button.modern-btn {
|
||||
opacity: 0;
|
||||
transform: translateY(32px);
|
||||
animation: fadeFormInput 0.8s 1s cubic-bezier(.77,0,.18,1) forwards;
|
||||
}
|
||||
@keyframes fadeFormInput {
|
||||
from { opacity: 0; transform: translateY(32px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
@media (max-width: 800px) {
|
||||
.contact-form-fields {
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
.textarea-right, .inputs-left {
|
||||
min-width: 0;
|
||||
}
|
||||
.textarea-right textarea {
|
||||
min-height: 100px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
{% endblock %}
|
||||
27
templates/login.html
Normal file
27
templates/login.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Login - Digitale Liturgie{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="space-y-8 max-w-6xl mx-auto px-4">
|
||||
<div class="max-w-md mx-auto">
|
||||
<h2 class="text-3xl font-semibold mb-6">Login</h2>
|
||||
|
||||
<form method="post" class="space-y-6">
|
||||
<div>
|
||||
<label for="username" class="block mb-3 font-semibold text-gray-700">Gebruikersnaam</label>
|
||||
<input type="text" id="username" name="username" required class="w-full rounded-md border border-gray-300 p-4 text-lg placeholder-gray-400 shadow-sm focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 transition">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password" class="block mb-3 font-semibold text-gray-700">Wachtwoord</label>
|
||||
<input type="password" id="password" name="password" required class="w-full rounded-md border border-gray-300 p-4 text-lg placeholder-gray-400 shadow-sm focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 transition">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="bg-[#f7d91a] text-black w-full rounded-md py-4 font-semibold shadow hover:bg-yellow-300 transition">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
106
templates/portal.html
Normal file
106
templates/portal.html
Normal file
@@ -0,0 +1,106 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="space-y-8 max-w-6xl mx-auto px-4">
|
||||
<h2 class="text-3xl font-semibold mb-6">Welkom, {{ current_user.username }}!</h2>
|
||||
<div class="overflow-x-auto rounded-lg border border-gray-300">
|
||||
<table class="min-w-full divide-y divide-gray-200 bg-white">
|
||||
<thead class="bg-gray-100">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700">Naam</th>
|
||||
<th class="px-6 py-3 text-sm font-semibold text-gray-700">URL</th>
|
||||
<th class="px-6 py-3 text-center text-sm font-semibold text-gray-700">Bewerken</th>
|
||||
<th class="px-6 py-3 text-center text-sm font-semibold text-gray-700">Weergeven</th>
|
||||
<th class="px-6 py-3 text-center text-sm font-semibold text-gray-700">Verwijderen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
{% for board in boards %}
|
||||
<tr>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-gray-900">{{ board.name }}</td>
|
||||
<td class="px-6 py-4 text-blue-600 break-words">
|
||||
{% if board.church.is_active %}
|
||||
<a href="{{ url_for('display_board_unique', unique_id=board.unique_id) }}" target="_blank" class="underline hover:text-blue-800 transition">display/{{ board.unique_id }}</a>
|
||||
{% else %}
|
||||
<em>Geen actieve licentie voor dit scherm</em>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center px-6 py-4">
|
||||
<a href="{{ url_for('edit_board', board_id=board.id) }}" class="bg-[#f7d91a] text-black text-xs px-2 py-1 rounded hover:bg-yellow-300 transition">Bewerken</a>
|
||||
</td>
|
||||
<td class="relative text-center px-6 py-4">
|
||||
{% if board.church.is_active %}
|
||||
<a href="{{ url_for('display_board_unique', unique_id=board.unique_id) }}" class="bg-black text-white text-xs px-2 py-1 rounded border border-black hover:bg-gray-800 transition show-iframe-preview" target="_blank" data-url="{{ url_for('display_board_unique', unique_id=board.unique_id) }}">Weergeven</a>
|
||||
<div class="iframe-preview-popup" style="display:none; position:absolute; top:0; left:100%; margin-left:8px; width:400px; height:225px; border:1px solid #ccc; box-shadow:0 2px 8px rgba(0,0,0,0.2); z-index:50;">
|
||||
<iframe src="" frameborder="0" style="width:100%; height:100%; transform-origin: top left; transform: scale(0.2);"></iframe>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center px-6 py-4">
|
||||
<form method="post" action="{{ url_for('delete_board', board_id=board.id) }}" style="display:inline;">
|
||||
<button type="submit" class="bg-white text-black text-xs px-2 py-1 rounded border border-black hover:bg-gray-100 transition" onclick="return confirm('Weet je zeker dat je dit bord wilt verwijderen?');">Verwijderen</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<a href="{{ url_for('add_board') }}" class="bg-[#f7d91a] text-black px-6 py-3 rounded-md font-semibold shadow hover:bg-yellow-300 inline-block mb-6 transition">Nieuw liturgiebord toevoegen</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const showIframeLinks = document.querySelectorAll('.show-iframe-preview');
|
||||
|
||||
// Create a single popup container to be reused
|
||||
const popup = document.createElement('div');
|
||||
popup.style.position = 'absolute';
|
||||
popup.style.width = '1080px';
|
||||
popup.style.height = '1920px';
|
||||
popup.style.border = '1px solid #ccc';
|
||||
popup.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';
|
||||
popup.style.zIndex = '9999';
|
||||
popup.style.display = 'none';
|
||||
popup.style.background = 'white';
|
||||
popup.style.transformOrigin = 'top left';
|
||||
popup.style.transform = 'scale(0.2)';
|
||||
|
||||
// iframe inside popup
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.frameBorder = 0;
|
||||
iframe.style.width = '100%';
|
||||
iframe.style.height = '100%';
|
||||
iframe.style.transform = '';
|
||||
iframe.style.transformOrigin = 'top left';
|
||||
popup.appendChild(iframe);
|
||||
|
||||
document.body.appendChild(popup);
|
||||
|
||||
showIframeLinks.forEach(link => {
|
||||
link.addEventListener('mouseenter', (event) => {
|
||||
const url = link.getAttribute('data-url');
|
||||
iframe.src = url;
|
||||
const rect = link.getBoundingClientRect();
|
||||
|
||||
// Position popup to the right and vertically aligned with the link
|
||||
popup.style.left = (window.scrollX + rect.right + 8) + 'px';
|
||||
popup.style.top = (window.scrollY + rect.top) + 'px';
|
||||
popup.style.display = 'block';
|
||||
});
|
||||
|
||||
link.addEventListener('mouseleave', () => {
|
||||
popup.style.display = 'none';
|
||||
iframe.src = '';
|
||||
});
|
||||
});
|
||||
|
||||
// Also hide popup if mouse moves out of popup itself
|
||||
popup.addEventListener('mouseleave', () => {
|
||||
popup.style.display = 'none';
|
||||
iframe.src = '';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
32
templates/register.html
Normal file
32
templates/register.html
Normal file
@@ -0,0 +1,32 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Register - Digitale Liturgie{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="space-y-8 max-w-6xl mx-auto px-4">
|
||||
<div class="max-w-md mx-auto">
|
||||
<h2 class="text-3xl font-semibold mb-6">Register</h2>
|
||||
|
||||
<form method="post" class="space-y-6">
|
||||
<div>
|
||||
<label for="username" class="block mb-3 font-semibold text-gray-700">Emailadres</label>
|
||||
<input type="email" id="username" name="username" required class="w-full rounded-md border border-gray-300 p-4 text-lg placeholder-gray-400 shadow-sm focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 transition">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password" class="block mb-3 font-semibold text-gray-700">Wachtwoord</label>
|
||||
<input type="password" id="password" name="password" required class="w-full rounded-md border border-gray-300 p-4 text-lg placeholder-gray-400 shadow-sm focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 transition">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="church" class="block mb-3 font-semibold text-gray-700">Kerk</label>
|
||||
<input type="text" id="church" name="church" placeholder="Kies of maak een kerk" required class="w-full rounded-md border border-gray-300 p-4 text-lg placeholder-gray-400 shadow-sm focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 transition">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="bg-[#f7d91a] text-black w-full rounded-md py-4 font-semibold shadow hover:bg-yellow-300 transition">Register</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
274
templates/schedule_form.html
Normal file
274
templates/schedule_form.html
Normal file
@@ -0,0 +1,274 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}{% if schedule %}Schema Bijwerken{% else %}Schema Toevoegen{% endif %}{% endblock %}
|
||||
{% block content %}
|
||||
<style>
|
||||
.preview-container {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
max-width: 900px;
|
||||
margin: 0 auto 2rem auto;
|
||||
background: white;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
.preview-wrapper {
|
||||
width: 250px;
|
||||
height: 444px;
|
||||
background: #222;
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
.liturgie-board {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
gap: 0.6vh;
|
||||
padding-left: 5%;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.liturgie-line {
|
||||
width: 90%;
|
||||
font-size: 28px;
|
||||
line-height: 1.2;
|
||||
text-shadow: 2px 2px 8px #000, 0 0 4px #000;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #fff;
|
||||
text-align: left;
|
||||
outline: none;
|
||||
cursor: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
/* Highlight for drag-over between lines in preview */
|
||||
.liturgie-line.line-over {
|
||||
background: rgba(255,255,255,0.22);
|
||||
border: 2px dashed #fff;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
transition: background 0.12s, border 0.12s;
|
||||
}
|
||||
|
||||
.liturgie-line.swap-animate {
|
||||
animation: swap-pulse 0.35s;
|
||||
}
|
||||
@keyframes swap-pulse {
|
||||
0% { background: #ffe066; }
|
||||
60% { background: #fff3cd; }
|
||||
100% { background: none; }
|
||||
}
|
||||
.input-section {
|
||||
flex: 1 1 260px;
|
||||
min-width: 260px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
.input-section label {
|
||||
font-weight: 700;
|
||||
color: #374151;
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
.input-section input {
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.1);
|
||||
transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
||||
}
|
||||
.input-section input:focus {
|
||||
border-color: #2563eb;
|
||||
box-shadow: 0 0 5px #93c5fd;
|
||||
}
|
||||
.save-button {
|
||||
padding: 0.75rem 1.25rem;
|
||||
background-color: #16a34a;
|
||||
color: white;
|
||||
border-radius: 0.375rem;
|
||||
font-weight: 700;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
margin-top: auto;
|
||||
}
|
||||
.save-button:hover {
|
||||
background-color: #15803d;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.preview-container {
|
||||
flex-direction: column;
|
||||
max-width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.preview-wrapper {
|
||||
width: 200px;
|
||||
height: 355px;
|
||||
margin: 0 auto 1rem auto;
|
||||
}
|
||||
.input-section {
|
||||
min-width: unset;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<h1 class="text-3xl font-bold mb-6 text-center">{% if schedule %}Schema Bijwerken{% else %}Schema Toevoegen{% endif %}</h1>
|
||||
<form method="post" id="scheduleForm">
|
||||
<div class="preview-container">
|
||||
<!-- Left: Live preview with editable lines -->
|
||||
<div class="preview-wrapper" style="font-family: 'Lora', serif; position: relative; background-image: {% if schedule and schedule.boards_list|length > 0 %}
|
||||
{% set board_bg = schedule.boards_list[0].background_image if schedule.boards_list[0].background_image else None %}
|
||||
{% if board_bg %}
|
||||
url('{% if board_bg.startswith('default_') %}{{ url_for('static', filename=board_bg) }}{% else %}{{ url_for('uploaded_file', filename=board_bg) }}{% endif %}')
|
||||
{% else %}none
|
||||
{% endif %}
|
||||
{% else %}none
|
||||
{% endif %}; background-color:#222; background-size: cover; background-position: center;">
|
||||
<div class="liturgie-bg-overlay" style="
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
"></div>
|
||||
<div class="liturgie-board" contenteditable="false" style="position: relative; z-index: 2; font-family: 'Lora', serif;">
|
||||
{% set lines = schedule.content.split('\n') if schedule and schedule.content else [' ']*10 %}
|
||||
{% for i in range(10) %}
|
||||
<div class="liturgie-line" contenteditable="true" data-line="line{{ i+1 }}" spellcheck="false">{{ lines[i] if i < lines|length else ' ' }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right: Input fields for schedule name, date, time, board assignment, and save button -->
|
||||
<div class="input-section">
|
||||
<label for="name">Naam:</label>
|
||||
<input type="text" id="name" name="name" required value="{{ schedule.name if schedule else '' }}">
|
||||
|
||||
<label for="date">Datum:</label>
|
||||
<input type="date" id="date" name="date" required value="{{ schedule.date.strftime('%Y-%m-%d') if schedule and schedule.date else '' }}">
|
||||
|
||||
<div style="display: flex; gap: 1rem; align-items: center;">
|
||||
<div style="flex: 1;">
|
||||
<label for="start_time">Starttijd:</label>
|
||||
<input type="time" id="start_time" name="start_time" required value="{{ schedule.start_time.strftime('%H:%M') if schedule and schedule.start_time else '' }}">
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<label for="end_time">Eindtijd:</label>
|
||||
<input type="time" id="end_time" name="end_time" required value="{{ schedule.end_time.strftime('%H:%M') if schedule and schedule.end_time else '' }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label>Toegewezen borden:</label>
|
||||
<div class="overflow-hidden rounded-lg max-h-56 overflow-y-auto mb-6 border border-gray-200">
|
||||
<table class="min-w-full divide-y divide-gray-200 border-collapse">
|
||||
<thead class="bg-gray-50">
|
||||
<tr class="divide-x divide-gray-200">
|
||||
<th scope="col" class="py-3.5 pl-4 pr-4 text-left text-sm font-semibold text-gray-900 sm:pl-6">Naam</th>
|
||||
<th scope="col" class="px-4 py-3.5 text-center text-sm font-semibold text-gray-900">Toewijzen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
{% for board in boards %}
|
||||
<tr class="divide-x divide-gray-200 hover:bg-gray-50 cursor-pointer">
|
||||
<td class="whitespace-nowrap py-4 pl-4 pr-4 text-sm font-semibold text-gray-900 sm:pl-6">{{ board.name }}</td>
|
||||
<td class="whitespace-nowrap px-4 py-4 text-center text-sm font-semibold text-gray-900 flex justify-center items-center">
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input type="checkbox" value="{{ board.id }}" name="boards" class="sr-only peer" {% if schedule and board in schedule.boards_list %}checked{% endif %}>
|
||||
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 rounded-full peer peer-checked:bg-blue-600 transition-all duration-300"></div>
|
||||
<div class="absolute left-1 top-1 bg-white w-4 h-4 rounded-full transition-transform duration-300 peer-checked:translate-x-5"></div>
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="bg-[#f7d91a] text-black px-8 py-3 rounded font-semibold hover:bg-yellow-300 transition">Opslaan</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden inputs to store lines for form submission -->
|
||||
{% for i in range(1, 11) %}
|
||||
<input type="hidden" name="line{{ i }}" id="hidden-line{{ i }}" value="">
|
||||
{% endfor %}
|
||||
</form>
|
||||
|
||||
<script>
|
||||
// --- Line drag-and-swap logic ---
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
let dragSrc = null;
|
||||
const lines = document.querySelectorAll('.liturgie-line[data-line]');
|
||||
lines.forEach(line => {
|
||||
line.addEventListener('dragstart', function (e) {
|
||||
dragSrc = this;
|
||||
this.classList.add('dragging');
|
||||
});
|
||||
line.addEventListener('dragend', function (e) {
|
||||
this.classList.remove('dragging');
|
||||
dragSrc = null;
|
||||
lines.forEach(l => l.classList.remove('line-over'));
|
||||
});
|
||||
line.addEventListener('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
if (this !== dragSrc) this.classList.add('line-over');
|
||||
});
|
||||
line.addEventListener('dragleave', function (e) {
|
||||
this.classList.remove('line-over');
|
||||
});
|
||||
line.addEventListener('drop', function (e) {
|
||||
e.preventDefault();
|
||||
this.classList.remove('line-over');
|
||||
if (dragSrc && dragSrc !== this) {
|
||||
// Only swap textContent, not the whole element
|
||||
const tmp = this.textContent;
|
||||
this.textContent = dragSrc.textContent;
|
||||
dragSrc.textContent = tmp;
|
||||
// animation for both lines
|
||||
this.classList.add('swap-animate');
|
||||
dragSrc.classList.add('swap-animate');
|
||||
setTimeout(() => {
|
||||
this.classList.remove('swap-animate');
|
||||
dragSrc.classList.remove('swap-animate');
|
||||
}, 400);
|
||||
}
|
||||
});
|
||||
// Make lines draggable
|
||||
line.setAttribute('draggable', 'true');
|
||||
});
|
||||
});
|
||||
// --- End drag-and-swap logic ---
|
||||
|
||||
document.getElementById('scheduleForm').addEventListener('submit', function(e) {
|
||||
// Copy content from editable divs to hidden inputs
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
let div = document.querySelector('.liturgie-line[data-line="line' + i + '"]');
|
||||
let hiddenInput = document.getElementById('hidden-line' + i);
|
||||
if (div && hiddenInput) {
|
||||
hiddenInput.value = div.textContent.trim() || ' ';
|
||||
}
|
||||
}
|
||||
});
|
||||
// Optional: keyboard navigation and basic shortcuts could be added here
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
44
templates/upload_background.html
Normal file
44
templates/upload_background.html
Normal file
@@ -0,0 +1,44 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}Achtergrondafbeelding wijzigen - Digitale Liturgie{% endblock %}
|
||||
{% block content %}
|
||||
<div class="max-w-lg mx-auto">
|
||||
<h2 class="text-3xl font-semibold mb-6">Achtergrondafbeelding wijzigen voor: {{ board.name }}</h2>
|
||||
<div class="flex space-x-8">
|
||||
<form method="post" enctype="multipart/form-data" class="space-y-6 flex-1">
|
||||
<div>
|
||||
<label for="background" class="block mb-2 font-semibold text-gray-700">Kies een afbeelding</label>
|
||||
<input type="file" id="background" name="background" accept="image/*" required class="w-full rounded-md border border-gray-300 p-3 text-base placeholder-gray-400 shadow-sm focus:border-blue-600 focus:ring focus:ring-blue-300 focus:ring-opacity-50 transition">
|
||||
</div>
|
||||
<button type="submit" class="bg-[#f7d91a] text-black w-full rounded-md py-3 font-semibold shadow hover:bg-yellow-300 transition">Uploaden</button>
|
||||
<a href="{{ url_for('edit_board', board_id=board.id) }}" class="block text-center bg-white text-black border border-black rounded-md py-3 mt-2 font-semibold shadow hover:bg-gray-100 transition w-full">Annuleren</a>
|
||||
</form>
|
||||
{% if board.background_image %}
|
||||
<div class="flex-1">
|
||||
<p class="mb-2">Huidige achtergrond:</p>
|
||||
<img src="{{ url_for('uploaded_file', filename=board.background_image) }}" alt="Achtergrond" class="rounded-lg max-w-full max-h-72 object-contain">
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="mt-10">
|
||||
<h3 class="text-xl font-semibold mb-4">Kies een standaard achtergrond:</h3>
|
||||
<div class="grid grid-cols-4 gap-4">
|
||||
<form method="post" class="inline-block">
|
||||
<input type="hidden" name="default_background" value="default_wall_1.jpg">
|
||||
<img src="/static/default_wall_1.jpg" alt="Standaard achtergrond 1" class="cursor-pointer rounded-lg border border-gray-300 hover:border-blue-600 transition" onclick="this.closest('form').submit()">
|
||||
</form>
|
||||
<form method="post" class="inline-block">
|
||||
<input type="hidden" name="default_background" value="default_wall_2.jpg">
|
||||
<img src="/static/default_wall_2.jpg" alt="Standaard achtergrond 2" class="cursor-pointer rounded-lg border border-gray-300 hover:border-blue-600 transition" onclick="this.closest('form').submit()">
|
||||
</form>
|
||||
<form method="post" class="inline-block">
|
||||
<input type="hidden" name="default_background" value="default_wall_3.jpg">
|
||||
<img src="/static/default_wall_3.jpg" alt="Standaard achtergrond 3" class="cursor-pointer rounded-lg border border-gray-300 hover:border-blue-600 transition" onclick="this.closest('form').submit()">
|
||||
</form>
|
||||
<form method="post" class="inline-block">
|
||||
<input type="hidden" name="default_background" value="default_wall_4.jpg">
|
||||
<img src="/static/default_wall_4.jpg" alt="Standaard achtergrond 4" class="cursor-pointer rounded-lg border border-gray-300 hover:border-blue-600 transition" onclick="this.closest('form').submit()">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user