mein-sterntours/resources/views/navigation/index.blade.php
2026-01-23 17:34:40 +01:00

560 lines
20 KiB
PHP

@extends('layouts.layout-2')
@section('content')
<h4 class="font-weight-bold py-3 mb-1">
<i class="ion ion-md-map"></i> Navigation API - Navigationsbaum (Frontend-Struktur)
</h4>
<div class="alert alert-info">
<i class="fa fa-info-circle"></i> Dieser Baum zeigt die Navigation genau so, wie sie im Frontend (header.html.twig)
angezeigt wird.
<br>
<strong>Bereiche:</strong>
<ul class="mb-0 mt-2">
<li><strong>Länder-Navigation:</strong> Länderseiten mit Children, gruppiert nach "Haupt" und "Infos"</li>
<li><strong>USEDOM Ferienwohnungen:</strong> Ferienwohnungs-Übersicht mit einzelnen FeWos</li>
<li><strong>Weitere Seiten:</strong> Seiten aus dem "Mehr"-Menü (Über uns, Reiseversicherung, Reiseführer, etc.)
</li>
</ul>
</div>
<!-- Statistiken -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card">
<div class="card-body text-center">
<div class="display-4 text-primary" id="stat-total">-</div>
<small class="text-muted">Gesamt Seiten</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card">
<div class="card-body text-center">
<div class="display-4 text-success" id="stat-active">-</div>
<small class="text-muted">Aktive Seiten</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card">
<div class="card-body text-center">
<div class="display-4 text-warning" id="stat-programs">-</div>
<small class="text-muted">Reiseprogramme</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card">
<div class="card-body text-center">
<div class="display-4 text-info" id="stat-countries">-</div>
<small class="text-muted">Länderseiten</small>
</div>
</div>
</div>
</div>
<!-- Toolbar -->
<div class="card mb-3">
<div class="card-body">
<div class="row align-items-center">
<div class="col-md-4">
<div class="input-group">
<input type="text" id="search-input" class="form-control"
placeholder="Suche nach Titel, Slug oder URL...">
<div class="input-group-append">
<button class="btn btn-primary" id="search-btn"><i class="fa fa-search"></i></button>
</div>
</div>
</div>
<div class="col-md-8 text-right">
<div class="btn-group" role="group">
<button class="btn btn-secondary" id="expand-all-btn"><i class="fa fa-plus-square"></i> Alle
aufklappen</button>
<button class="btn btn-secondary" id="collapse-all-btn"><i class="fa fa-minus-square"></i> Alle
zuklappen</button>
</div>
<div class="btn-group ml-2" role="group">
<button class="btn btn-info" id="filter-hidden-btn" data-filter="with_hidden"><i
class="fa fa-eye"></i>
Mit ausgeblendeten</button>
</div>
<div class="btn-group ml-2" role="group">
<button class="btn btn-success" id="export-btn"><i class="fa fa-download"></i> Export JSON</button>
<button class="btn btn-warning" id="clear-cache-btn"><i class="fa fa-sync"></i> Cache
leeren</button>
</div>
</div>
</div>
</div>
</div>
<!-- Navigationsbaum -->
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<i class="fa fa-sitemap"></i> Navigationsbaum
<span class="badge badge-primary ml-2" id="node-count">0 Knoten</span>
</h5>
</div>
<div class="card-body">
<div id="loading" class="text-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="sr-only">Lade...</span>
</div>
<p class="mt-3">Lade Navigationsbaum...</p>
</div>
<div id="tree-container" style="display: none;">
<div id="navigation-tree"></div>
</div>
<div id="no-results" style="display: none;" class="text-center py-5 text-muted">
<i class="fa fa-search fa-3x mb-3"></i>
<p>Keine Ergebnisse gefunden.</p>
</div>
</div>
</div>
<!-- Styles -->
<style>
.tree-node {
margin-left: 0;
padding: 5px 0;
}
.tree-node-content {
display: flex;
align-items: center;
padding: 8px 12px;
border-radius: 4px;
transition: background-color 0.2s;
cursor: pointer;
}
.tree-node-content:hover {
background-color: #f5f5f5;
}
.tree-toggle {
width: 20px;
min-width: 20px;
height: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin-right: 8px;
color: #666;
user-select: none;
}
.tree-toggle:hover {
color: #000;
}
.tree-toggle.empty {
visibility: hidden;
}
.tree-icon {
width: 24px;
min-width: 24px;
margin-right: 8px;
text-align: center;
}
.tree-title {
flex: 1;
font-weight: 500;
}
.tree-badges {
display: flex;
gap: 5px;
flex-wrap: wrap;
}
.tree-url {
font-size: 12px;
color: #999;
margin-left: 8px;
}
.tree-children {
margin-left: 30px;
border-left: 2px solid #e0e0e0;
padding-left: 10px;
}
.tree-children.collapsed {
display: none;
}
.tree-node.inactive .tree-node-content {
opacity: 0.5;
}
.tree-node.filtered {
display: none;
}
.tree-node.highlight .tree-node-content {
background-color: #fff3cd;
border-left: 3px solid #ffc107;
}
.badge-small {
font-size: 10px;
padding: 2px 6px;
}
/* Separator-Styles */
.tree-separator {
margin: 15px 0 10px 30px;
padding: 8px 12px;
background: linear-gradient(to right, #f8f9fa 0%, #e9ecef 100%);
border-left: 4px solid #6c757d;
border-radius: 4px;
font-weight: bold;
color: #495057;
}
.tree-separator i {
margin-right: 8px;
color: #6c757d;
}
/* Section-Separator (größere Trennung) */
.tree-section-separator {
margin: 30px 0 20px 0;
padding: 12px 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 6px;
font-weight: bold;
font-size: 18px;
color: white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.tree-section-separator i {
margin-right: 10px;
}
/* Frontend-spezifische Styles */
.tree-node.country-page>.tree-node-content {
background-color: #f8f9fa;
font-weight: 600;
font-size: 16px;
border-left: 4px solid #007bff;
padding-left: 16px;
}
.tree-node.country-page>.tree-node-content:hover {
background-color: #e9ecef;
}
.tree-node.info-page>.tree-node-content {
background-color: #fff9e6;
}
</style>
<!-- Scripts -->
<script>
let navigationData = null;
let currentFilter = 'with_hidden';
let searchQuery = '';
// Lade Statistiken
function loadStats() {
fetch('/navigation-api/stats')
.then(response => response.json())
.then(data => {
if (data.success) {
$('#stat-total').text(data.data.total_pages);
$('#stat-active').text(data.data.active_nodes);
$('#stat-programs').text(data.data.travel_programs);
$('#stat-countries').text(data.data.country_pages);
}
})
.catch(error => console.error('Error loading stats:', error));
}
// Lade Navigationsbaum
function loadNavigationTree(includeHidden = true) {
$('#loading').show();
$('#tree-container').hide();
$('#no-results').hide();
const url = `/navigation-api/data?include_hidden=${includeHidden ? '1' : '0'}`;
fetch(url)
.then(response => response.json())
.then(data => {
if (data.success) {
navigationData = data.data;
renderTree(navigationData);
updateNodeCount(data.meta.total_nodes);
$('#loading').hide();
$('#tree-container').show();
}
})
.catch(error => {
console.error('Error loading tree:', error);
$('#loading').hide();
alert('Fehler beim Laden des Navigationsbaums');
});
}
// Rendere Baum
function renderTree(nodes) {
const container = $('#navigation-tree');
container.empty();
if (nodes.length === 0) {
$('#tree-container').hide();
$('#no-results').show();
return;
}
// Rendere Nodes - unterscheide zwischen Separators und Pages
nodes.forEach(node => {
// Section-Separators und normale Separators haben keine Children-Logik
if (node.is_section_separator || node.is_separator) {
container.append(renderNode(node, false));
} else {
// Länderseiten (section = "Länder-Navigation") haben Children mit Toggle
const isCountryPage = node.section === 'Länder-Navigation';
container.append(renderNode(node, isCountryPage));
}
});
}
// Rendere einzelnen Knoten
function renderNode(node, isCountryPage = false) {
// Prüfe ob es ein Section-Separator ist
if (node.is_section_separator) {
return $('<div>')
.addClass('tree-section-separator')
.html(`<i class="${node.icon}"></i>${node.title}`);
}
// Prüfe ob es ein Separator ist
if (node.is_separator) {
return $('<div>')
.addClass('tree-separator')
.html(`<i class="${node.icon}"></i>${node.title}`);
}
const hasChildren = node.has_children;
const isActive = node.status === 1;
const showInNavi = node.show_in_navi === 1;
const nodeDiv = $('<div>')
.addClass('tree-node')
.addClass(isActive ? 'active' : 'inactive')
.addClass(isCountryPage ? 'country-page' : '')
.addClass(node.before_title === 'Infos' ? 'info-page' : '')
.data('node', node);
// Node Content
const contentDiv = $('<div>')
.addClass('tree-node-content');
// Toggle Button (nur für Country-Pages mit Children)
const toggle = $('<span>')
.addClass('tree-toggle')
.addClass(hasChildren && isCountryPage ? '' : 'empty')
.html(hasChildren && isCountryPage ? '<i class="fa fa-chevron-down"></i>' : '')
.on('click', function(e) {
e.stopPropagation();
toggleNode(nodeDiv);
});
// Icon
const icon = $('<span>').addClass('tree-icon');
if (node.is_country_page) {
icon.html('<i class="fa fa-star text-warning"></i>');
} else if (node.is_travel_program) {
icon.html('<i class="fa fa-plane text-primary"></i>');
} else if (node.is_fewo_lodging) {
icon.html('<i class="fa fa-home text-info"></i>');
} else if (node.before_title === 'Infos') {
icon.html('<i class="fa fa-info-circle text-muted"></i>');
} else {
icon.html('<i class="fa fa-file-alt text-muted"></i>');
}
// Title & URL
const titleDiv = $('<div>').addClass('tree-title');
// Verwende titleShort wenn vorhanden, sonst title
const displayTitle = node.title_short || node.title;
titleDiv.append($('<span>').text(displayTitle));
if (node.url) {
titleDiv.append($('<span>').addClass('tree-url').text(node.url));
}
// Badges
const badges = $('<div>').addClass('tree-badges');
if (!isActive) {
badges.append($('<span>').addClass('badge badge-danger badge-small').text('Inaktiv'));
}
if (!showInNavi) {
badges.append($('<span>').addClass('badge badge-warning badge-small').text('Ausgeblendet'));
}
if (node.is_travel_program) {
badges.append($('<span>').addClass('badge badge-primary badge-small').text('Reiseprogramm'));
}
if (node.is_fewo_lodging) {
badges.append($('<span>').addClass('badge badge-info badge-small').text('FeWo'));
}
if (node.is_country_page) {
badges.append($('<span>').addClass('badge badge-success badge-small').text('Land'));
}
if (node.before_title) {
badges.append($('<span>').addClass('badge badge-secondary badge-small').text('Gruppe: ' + node
.before_title));
}
contentDiv.append(toggle, icon, titleDiv, badges);
nodeDiv.append(contentDiv);
// Children (nur für Country-Pages)
if (hasChildren && node.children && node.children.length > 0 && isCountryPage) {
const childrenDiv = $('<div>').addClass('tree-children');
node.children.forEach(child => {
childrenDiv.append(renderNode(child, false));
});
nodeDiv.append(childrenDiv);
}
return nodeDiv;
}
// Toggle Node
function toggleNode(nodeDiv) {
const childrenDiv = nodeDiv.find('> .tree-children');
const toggle = nodeDiv.find('> .tree-node-content > .tree-toggle i');
if (childrenDiv.hasClass('collapsed')) {
childrenDiv.removeClass('collapsed');
toggle.removeClass('fa-chevron-right').addClass('fa-chevron-down');
} else {
childrenDiv.addClass('collapsed');
toggle.removeClass('fa-chevron-down').addClass('fa-chevron-right');
}
}
// Alle auf/zuklappen
function expandAll() {
$('.tree-children').removeClass('collapsed');
$('.tree-toggle i').removeClass('fa-chevron-right').addClass('fa-chevron-down');
}
function collapseAll() {
$('.tree-children').addClass('collapsed');
$('.tree-toggle i').removeClass('fa-chevron-down').addClass('fa-chevron-right');
}
// Suche
function performSearch(query) {
searchQuery = query.toLowerCase();
if (!query) {
$('.tree-node').removeClass('highlight filtered');
return;
}
$('.tree-node').each(function() {
const node = $(this).data('node');
const matches =
node.title.toLowerCase().includes(searchQuery) ||
(node.slug && node.slug.toLowerCase().includes(searchQuery)) ||
(node.url && node.url.toLowerCase().includes(searchQuery));
if (matches) {
$(this).removeClass('filtered').addClass('highlight');
// Zeige Parent-Nodes
$(this).parents('.tree-children').removeClass('collapsed');
$(this).parents('.tree-node').find('> .tree-node-content > .tree-toggle i')
.removeClass('fa-chevron-right').addClass('fa-chevron-down');
} else {
$(this).removeClass('highlight');
}
});
}
// Node Count Update
function updateNodeCount(count) {
$('#node-count').text(`${count} Knoten`);
}
// Export
function exportJSON() {
window.location.href = '/navigation-api/export?include_hidden=' + (currentFilter === 'with_hidden' ? '1' : '0');
}
// Cache leeren
function clearCache() {
if (!confirm('Möchten Sie wirklich den Navigation-Cache leeren?')) {
return;
}
fetch('/navigation-api/clear-cache', {
method: 'POST'
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Cache erfolgreich gelöscht');
loadNavigationTree(currentFilter === 'active');
} else {
alert('Fehler beim Löschen des Cache');
}
})
.catch(error => {
console.error('Error clearing cache:', error);
alert('Fehler beim Löschen des Cache');
});
}
// Event Listeners
$(document).ready(function() {
loadStats();
loadNavigationTree();
$('#expand-all-btn').on('click', expandAll);
$('#collapse-all-btn').on('click', collapseAll);
$('#export-btn').on('click', exportJSON);
$('#clear-cache-btn').on('click', clearCache);
$('#search-btn').on('click', function() {
performSearch($('#search-input').val());
});
$('#search-input').on('keyup', function(e) {
if (e.key === 'Enter') {
performSearch($(this).val());
}
});
$('#filter-hidden-btn').on('click', function() {
if (currentFilter === 'with_hidden') {
currentFilter = 'visible_only';
$(this).html('<i class="fa fa-eye-slash"></i> Nur sichtbare').removeClass('btn-info')
.addClass('btn-primary');
} else {
currentFilter = 'with_hidden';
$(this).html('<i class="fa fa-eye"></i> Mit ausgeblendeten').removeClass('btn-primary')
.addClass('btn-info');
}
loadNavigationTree(currentFilter === 'with_hidden');
});
});
</script>
@endsection