394 lines
19 KiB
PHP
394 lines
19 KiB
PHP
<?php
|
|
// =====================================================================
|
|
// WebGIS Citizen Participation Portal — Main Page
|
|
// Loads Municipality Configuration from the Database.
|
|
// Renders Leaflet Map Interface including Leaflet Plugins
|
|
// =====================================================================
|
|
|
|
require_once __DIR__ . '/api/db.php';
|
|
require_once __DIR__ . '/api/auth.php';
|
|
|
|
// -----------------------------------------------------------------
|
|
// Loads Municipality Configuration
|
|
// ToDo's: Dynamic Loading via URL Slug once multi-tenant Routing
|
|
// is implemented. Hardcoded Slug for now.
|
|
// -----------------------------------------------------------------
|
|
$pdo = get_db();
|
|
$stmt = $pdo->prepare("SELECT * FROM municipalities WHERE slug = :slug");
|
|
$stmt->execute([':slug' => getenv('MUNICIPALITY_SLUG')]);
|
|
$municipality = $stmt->fetch();
|
|
|
|
if (!$municipality) {
|
|
http_response_code(404);
|
|
echo "<!DOCTYPE html><html><body><h1>404 — Municipality not listed in Database.</h1></body></html>";
|
|
exit;
|
|
}
|
|
|
|
// Loads News for Sidebar
|
|
$stmt = $pdo->prepare("SELECT * FROM news WHERE municipality_id = :mid ORDER BY published_at DESC LIMIT 10");
|
|
$stmt->execute([':mid' => $municipality['municipality_id']]);
|
|
$news_items = $stmt->fetchAll();
|
|
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Bürgerbeteiligungsportal <?= htmlspecialchars($municipality['name']) ?></title>
|
|
<link rel="icon" href="<?= htmlspecialchars($municipality['logo_path'] ?? 'assets/icon-municipality.png') ?>" type="image/png">
|
|
<meta name="description" content="Bürgerbeteiligungsportal. Hinweise und Vorschläge auf der Karte eintragen.">
|
|
|
|
|
|
<!-- ============================================================= -->
|
|
<!-- Loads CSS Dependencies -->
|
|
<!-- ============================================================= -->
|
|
|
|
<!-- Leaflet -->
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css">
|
|
|
|
<!-- Geoman Drawing Tools -->
|
|
<link rel="stylesheet" href="https://unpkg.com/@geoman-io/leaflet-geoman-free@2.17.0/dist/leaflet-geoman.css">
|
|
|
|
<!-- Leaflet Sidebar -->
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet-sidebar-v2@3.2.3/css/leaflet-sidebar.min.css">
|
|
|
|
<!-- Leaflet Fullscreen -->
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.fullscreen/3.0.2/Control.FullScreen.css">
|
|
|
|
<!-- Leaflet Geocoder for Address Search -->
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder@2.4.0/dist/Control.Geocoder.css">
|
|
|
|
<!-- Leaflet Polyline Measurement Tool -->
|
|
<!-- <link rel="stylesheet" href="https://ppete2.github.io/Leaflet.PolylineMeasure/Leaflet.PolylineMeasure.css"> -->
|
|
|
|
<!-- Font Awesome for Icons -->
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
|
|
|
<!-- Application Styles -->
|
|
<link rel="stylesheet" href="styles.css">
|
|
|
|
<!-- ============================================================= -->
|
|
<!-- Municipality Theme loaded from Database -->
|
|
<!-- ============================================================= -->
|
|
<style>
|
|
:root {
|
|
--color-primary: <?= htmlspecialchars($municipality['primary_color']) ?>;
|
|
--color-primary-light: <?= htmlspecialchars($municipality['primary_color']) ?>22;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body class="portal-page">
|
|
|
|
<!-- ============================================================= -->
|
|
<!-- Header -->
|
|
<!-- ============================================================= -->
|
|
<header id="app-header">
|
|
<div class="header-left">
|
|
<?php if (!empty($municipality['logo_path'])): ?>
|
|
<img src="<?= htmlspecialchars($municipality['logo_path']) ?>" alt="<?= htmlspecialchars($municipality['name']) ?>" class="header-logo" onerror="this.style.display='none'">
|
|
<?php endif; ?>
|
|
<h1 class="header-title">Mitmachkarte <?= htmlspecialchars($municipality['name']) ?></h1>
|
|
</div>
|
|
|
|
<nav class="header-nav">
|
|
<button class="nav-btn" onclick="showInfoModal()">
|
|
<i class="fa-solid fa-circle-info"></i>
|
|
<span class="nav-label">Informationen</span>
|
|
</button>
|
|
<a href="privacy.php" class="nav-btn" target="_blank">
|
|
<i class="fa-solid fa-shield-halved"></i>
|
|
<span class="nav-label">Datenschutz</span>
|
|
</a>
|
|
<a href="imprint.php" class="nav-btn" target="_blank">
|
|
<i class="fa-solid fa-scale-balanced"></i>
|
|
<span class="nav-label">Impressum</span>
|
|
</a>
|
|
<a href="admin.php" class="nav-btn nav-btn-admin" title="Moderationsbereich" target="_blank">
|
|
<i class="fa-solid fa-lock"></i>
|
|
</a>
|
|
</nav>
|
|
|
|
<!-- Mobile Hamburger Menu -->
|
|
<button class="header-menu-toggle" onclick="toggleMobileNav()">
|
|
<i class="fa-solid fa-bars"></i>
|
|
</button>
|
|
</header>
|
|
|
|
|
|
<!-- ============================================================= -->
|
|
<!-- Map Container with Sidebar -->
|
|
<!-- ============================================================= -->
|
|
<main id="app-main">
|
|
|
|
<!-- Leaflet Sidebar -->
|
|
<div id="sidebar" class="leaflet-sidebar collapsed">
|
|
|
|
<!-- Sidebar Tab Icons -->
|
|
<div class="leaflet-sidebar-tabs">
|
|
<ul role="tablist">
|
|
<li><a href="#tab-home" role="tab"><i class="fa-solid fa-house"></i></a></li>
|
|
<li><a href="#tab-help" role="tab"><i class="fa-solid fa-circle-question"></i></a></li>
|
|
<li><a href="#tab-list" role="tab"><i class="fa-solid fa-list"></i></a></li>
|
|
<li><a href="#tab-news" role="tab"><i class="fa-solid fa-newspaper"></i></a></li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- Sidebar Tab Content -->
|
|
<div class="leaflet-sidebar-content">
|
|
|
|
<!-- Home Tab -->
|
|
<div class="leaflet-sidebar-pane" id="tab-home">
|
|
<h2 class="leaflet-sidebar-header">
|
|
Start
|
|
<span class="leaflet-sidebar-close"><i class="fa-solid fa-xmark"></i></span>
|
|
</h2>
|
|
<div class="sidebar-body">
|
|
<p>Willkommen beim Bürgerbeteiligungsportal <strong><?= htmlspecialchars($municipality['name']) ?></strong>.</p>
|
|
<p>Verwenden Sie die Karte, um Hinweise und Aufgaben für die Stadtverwaltung hinzuzufügen oder bestehende Beiträge der Bürgerschaft zu betrachten.</p>
|
|
|
|
<h3>Kategorien</h3>
|
|
<div id="category-filter">
|
|
<!-- Category Filter Checkboxes — populated by app.js -->
|
|
</div>
|
|
|
|
<h3>Statistik</h3>
|
|
<div id="stats-container">
|
|
<!-- Contribution Statistics — populated by app.js -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- List Tab -->
|
|
<div class="leaflet-sidebar-pane" id="tab-list">
|
|
<h2 class="leaflet-sidebar-header">
|
|
Beiträge
|
|
<span class="leaflet-sidebar-close"><i class="fa-solid fa-xmark"></i></span>
|
|
</h2>
|
|
<div class="sidebar-body">
|
|
<div class="list-search">
|
|
<input type="text" id="list-search-input" placeholder="Beiträge durchsuchen..." class="form-input">
|
|
</div>
|
|
<div id="contributions-list">
|
|
<!-- Contribution Cards — populated by app.js -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Help Tab -->
|
|
<div class="leaflet-sidebar-pane" id="tab-help">
|
|
<h2 class="leaflet-sidebar-header">
|
|
Hilfe
|
|
<span class="leaflet-sidebar-close"><i class="fa-solid fa-xmark"></i></span>
|
|
</h2>
|
|
<div class="sidebar-body">
|
|
<h3><i class="fa-solid fa-map-location-dot"></i> Karte bedienen</h3>
|
|
<p>Verschieben Sie die Karte per Mausklick und Ziehen. Zoomen Sie mit dem Mausrad oder den Zoom-Buttons.</p>
|
|
|
|
<h3><i class="fa-solid fa-plus"></i> Beitrag erstellen</h3>
|
|
<p>Verwenden Sie die Zeichenwerkzeuge rechts, um Beiträge als Punkte, Linien oder Flächen zu zeichnen. Anschließend können Sie Kategorie und Beschreibung hinzufügen.</p>
|
|
|
|
<h3><i class="fa-solid fa-thumbs-up"></i> Abstimmen</h3>
|
|
<p>Klicken Sie auf bestehende Beiträge und nutzen Sie die Like/Dislike Funktion, um Ihre Meinung kundzugeben.</p>
|
|
|
|
<h3><i class="fa-solid fa-magnifying-glass"></i> Suchen</h3>
|
|
<p>Verwenden Sie die Adresssuche rechts, um bestimmte Orte auf der Karte zu finden.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- News Tab -->
|
|
<div class="leaflet-sidebar-pane" id="tab-news">
|
|
<h2 class="leaflet-sidebar-header">
|
|
Neuigkeiten
|
|
<span class="leaflet-sidebar-close"><i class="fa-solid fa-xmark"></i></span>
|
|
</h2>
|
|
<div class="sidebar-body">
|
|
<!-- News Search -->
|
|
<div class="list-search">
|
|
<input type="text" id="news-search-input" placeholder="Neuigkeiten durchsuchen..." class="form-input" oninput="filterNews()">
|
|
</div>
|
|
|
|
<!-- News Items Container -->
|
|
<div id="news-list">
|
|
<?php if (empty($news_items)): ?>
|
|
<p style="text-align:center;color:#999;padding:20px;">Noch keine Neuigkeiten veröffentlicht.</p>
|
|
<?php else: ?>
|
|
<?php foreach ($news_items as $news): ?>
|
|
<div class="news-item"
|
|
data-title="<?= htmlspecialchars(strtolower($news['title'])) ?>"
|
|
data-content="<?= htmlspecialchars(strtolower($news['content'])) ?>"
|
|
data-author="<?= htmlspecialchars(strtolower($news['author_name'])) ?>">
|
|
<h3><?= htmlspecialchars($news['title']) ?></h3>
|
|
<p><?= nl2br(htmlspecialchars($news['content'])) ?></p>
|
|
<span class="news-date">
|
|
<?= htmlspecialchars($news['author_name']) ?>
|
|
· <?= date('d.m.Y', strtotime($news['published_at'])) ?>
|
|
</span>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Leaflet Map -->
|
|
<div id="map"></div>
|
|
|
|
</main>
|
|
|
|
|
|
<!-- ============================================================= -->
|
|
<!-- Footer -->
|
|
<!-- ============================================================= -->
|
|
<footer id="app-footer">
|
|
<span class="dev-warning">
|
|
<i class="fa-solid fa-triangle-exclamation"></i> Pilotprojekt - nicht offiziell von der Stadt Lohne (Oldenburg) beauftragt
|
|
</span>
|
|
<div class="footer-content">
|
|
<span class="footer-text">© <a href="https://endex-geodaten.de" target="_blank" style="color:inherit;">endex GmbH</a></span>
|
|
</div>
|
|
</footer>
|
|
|
|
|
|
<!-- ============================================================= -->
|
|
<!-- Welcome Modal shown on first Visit -->
|
|
<!-- ============================================================= -->
|
|
<div id="welcome-modal" class="modal-overlay" style="display:none;">
|
|
<div class="modal-content">
|
|
<h2><i class="fa-solid fa-hand-wave"></i> Willkommen!</h2>
|
|
<p>Herzlich willkommen beim Bürgerbeteiligungsportal <strong><?= htmlspecialchars($municipality['name']) ?></strong>.</p>
|
|
<p>Hier können Sie:</p>
|
|
<ul>
|
|
<li>Hinweise und Verbesserungsvorschläge für die Stadtverwaltung hinzufügen</li>
|
|
<li>Bestehende Beiträge der Bürgerschaft betrachten und bewerten</li>
|
|
</ul>
|
|
<p style="background:#fff3cd;padding:10px;border-radius:6px;border:1px solid #ffc107;font-size:0.85rem;color:#856404;">
|
|
<i class="fa-solid fa-triangle-exclamation"></i> <strong>Hinweis:</strong> Dieses Bürgerbeteiligungsportal befindet sich noch in der Entwicklung und wurde nicht offiziell beauftragt.
|
|
</p>
|
|
<p>Zum Hinzufügen von Beiträgen geben Sie bitte zunächst Ihren Namen ein.</p> <div class="modal-actions">
|
|
<button class="btn btn-primary" onclick="closeWelcomeAndShowLogin()">Loslegen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- ============================================================= -->
|
|
<!-- Login Modal for Identification -->
|
|
<!-- ToDo's: User Authentification and Administration -->
|
|
<!-- ============================================================= -->
|
|
<div id="login-modal" class="modal-overlay" style="display:none;">
|
|
<div class="modal-content modal-small">
|
|
<h2><i class="fa-solid fa-user"></i> Anmelden</h2>
|
|
<p>Bitte geben Sie Ihren Namen ein, um Beiträge hinzufügen und abstimmen zu können.</p>
|
|
<div class="form-group">
|
|
<label for="user-name-input">Ihr Name</label>
|
|
<input type="text" id="user-name-input" class="form-input" placeholder="Vor- und Nachname">
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button class="btn btn-secondary" onclick="skipLogin()">Gastuser</button>
|
|
<button class="btn btn-primary" onclick="submitLogin()">Anmelden</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- ============================================================= -->
|
|
<!-- Create Contribution Modal -->
|
|
<!-- ============================================================= -->
|
|
<div id="create-modal" class="modal-overlay" style="display:none;">
|
|
<div class="modal-content">
|
|
<h2><i class="fa-solid fa-plus-circle"></i> Beitrag</h2>
|
|
|
|
<div class="form-group">
|
|
<label for="create-category">Kategorie</label>
|
|
<select id="create-category" class="form-input">
|
|
<option value="">— Bitte wählen —</option>
|
|
<!-- Categories populated dynamically -->
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="create-title">Titel</label>
|
|
<input type="text" id="create-title" class="form-input" placeholder="Kurze Beschreibung des Anliegens">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="create-description">Beschreibung</label>
|
|
<textarea id="create-description" class="form-input" rows="4" placeholder="Detaillierte Beschreibung (optional)"></textarea>
|
|
</div>
|
|
|
|
<!-- Photo Upload -->
|
|
<div class="form-group">
|
|
<label for="create-photo"></i> Foto</label>
|
|
<input type="file" id="create-photo" class="form-input" accept="image/jpeg,image/png,image/gif,image/webp">
|
|
<div id="photo-preview" style="margin-top:8px;display:none;">
|
|
<img id="photo-preview-img" style="max-width:100%;max-height:200px;border-radius:6px;border:1px solid var(--color-border);">
|
|
</div>
|
|
</div>
|
|
|
|
<input type="hidden" id="create-geom">
|
|
<input type="hidden" id="create-geom-type">
|
|
|
|
<div class="modal-actions">
|
|
<button class="btn btn-secondary" onclick="cancelCreate()">Abbrechen</button>
|
|
<button class="btn btn-primary" onclick="submitCreate()">Beitrag einreichen</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- ============================================================= -->
|
|
<!-- Loads JavaScript Dependencies -->
|
|
<!-- ============================================================= -->
|
|
|
|
<!-- Leaflet 1.9.4 -->
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
|
|
|
|
<!-- Geoman Drawing Tools -->
|
|
<script src="https://unpkg.com/@geoman-io/leaflet-geoman-free@2.17.0/dist/leaflet-geoman.min.js"></script>
|
|
|
|
<!-- Leaflet Sidebar v2 -->
|
|
<script src="https://unpkg.com/leaflet-sidebar-v2@3.2.3/js/leaflet-sidebar.min.js"></script>
|
|
|
|
<!-- Leaflet Fullscreen -->
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.fullscreen/3.0.2/Control.FullScreen.min.js"></script>
|
|
|
|
<!-- Leaflet Geocoder (Address Search) -->
|
|
<script src="https://unpkg.com/leaflet-control-geocoder@2.4.0/dist/Control.Geocoder.min.js"></script>
|
|
|
|
<!-- Leaflet PolylineMeasure -->
|
|
<!-- <script src="https://ppete2.github.io/Leaflet.PolylineMeasure/Leaflet.PolylineMeasure.js"></script> -->
|
|
|
|
<!-- SweetAlert2 -->
|
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.14.0/dist/sweetalert2.all.min.js"></script>
|
|
|
|
<!-- ============================================================= -->
|
|
<!-- Municipality Configuration passed to JavaScript -->
|
|
<!-- ============================================================= -->
|
|
<script>
|
|
// Municipality Configuration from Database — used by app.js
|
|
const MUNICIPALITY = {
|
|
id: <?= $municipality['municipality_id'] ?>,
|
|
name: "<?= htmlspecialchars($municipality['name'], ENT_QUOTES) ?>",
|
|
slug: "<?= htmlspecialchars($municipality['slug'], ENT_QUOTES) ?>",
|
|
center: [<?= $municipality['center_lat'] ?>, <?= $municipality['center_lng'] ?>],
|
|
zoom: <?= $municipality['default_zoom'] ?>,
|
|
primaryColor: "<?= htmlspecialchars($municipality['primary_color'], ENT_QUOTES) ?>"
|
|
};
|
|
|
|
// Category Definitions from Database
|
|
const CATEGORIES = <?= json_encode(get_categories(), JSON_UNESCAPED_UNICODE) ?>;
|
|
|
|
// Admin Status from PHP Session
|
|
const IS_ADMIN = <?= (function_exists('is_admin') && is_admin()) ? 'true' : 'false' ?>;
|
|
</script>
|
|
|
|
<!-- Application Logic -->
|
|
<script src="js/app.js"></script>
|
|
|
|
</body>
|
|
</html>
|