560 lines
20 KiB
PHP
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
|