466 lines
13 KiB
HTML
466 lines
13 KiB
HTML
{% extends "base.html" %}
|
||
{% block title %}Referenzen{% endblock %}
|
||
{% block breadcrumb_items %}
|
||
<li class="breadcrumb-item active" aria-current="page">Referenzen</li>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="row">
|
||
<div class="col-lg-8">
|
||
<!-- Header -->
|
||
<div class="card mb-4">
|
||
<div class="card-header">
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<h1 class="h3 mb-0">🔗 Referenzbaum</h1>
|
||
<div class="d-flex gap-2">
|
||
<button class="btn btn-outline btn-sm" onclick="expandAll()">
|
||
➕ Alle ausklappen
|
||
</button>
|
||
<button class="btn btn-outline btn-sm" onclick="collapseAll()">
|
||
➖ Alle einklappen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card-body">
|
||
<p class="text-muted mb-0">
|
||
Hier finden Sie alle Referenzen und Querverbindungen zwischen den Standards und Vorgaben.
|
||
Klicken Sie auf die Pfeile um den Baum zu navigieren.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Search and Filter -->
|
||
<div class="card mb-4">
|
||
<div class="card-body">
|
||
<div class="row g-3">
|
||
<div class="col-md-8">
|
||
<label for="tree-search" class="form-label">🔍 Referenzen durchsuchen</label>
|
||
<input type="text"
|
||
class="form-control"
|
||
id="tree-search"
|
||
placeholder="Suchbegriff eingeben..."
|
||
onkeyup="filterTree()">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label for="tree-filter" class="form-label">🏷️ Filter</label>
|
||
<select class="form-select" id="tree-filter" onchange="filterTree()">
|
||
<option value="">Alle anzeigen</option>
|
||
<option value="has-children">Mit Unterelementen</option>
|
||
<option value="leaf-only">Nur Endpunkte</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Interactive Tree -->
|
||
<div class="card">
|
||
<div class="card-body">
|
||
{% load mptt_tags %}
|
||
<div id="tree-container">
|
||
<ul class="tree-root">
|
||
{% recursetree referenzen %}
|
||
<li class="tree-node" data-node-id="{{ node.id }}" data-node-text="{{ node.name_text|default:'' }} {{ node.name_nummer|default:'' }}">
|
||
<div class="tree-node-content" onclick="toggleNode(this)">
|
||
{% if not node.is_leaf_node %}
|
||
<span class="tree-toggle">▼</span>
|
||
{% else %}
|
||
<span class="tree-toggle-placeholder"></span>
|
||
{% endif %}
|
||
|
||
<a href="{{ node.id }}" class="tree-link">
|
||
{% if node.name_nummer %}
|
||
<span class="tree-number">{{ node.name_nummer }}</span>
|
||
{% endif %}
|
||
{% if node.name_text %}
|
||
<span class="tree-text">{{ node.name_text }}</span>
|
||
{% endif %}
|
||
</a>
|
||
|
||
<div class="tree-node-meta">
|
||
{% if not node.is_leaf_node %}
|
||
<span class="badge bg-info">{{ node.get_children.count }} Unterelemente</span>
|
||
{% else %}
|
||
<span class="badge bg-secondary">Endpunkt</span>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
{% if not node.is_leaf_node %}
|
||
<ul class="tree-children">
|
||
{{ children }}
|
||
</ul>
|
||
{% endif %}
|
||
</li>
|
||
{% endrecursetree %}
|
||
</ul>
|
||
</div>
|
||
|
||
{% if not referenzen %}
|
||
<div class="text-center py-5">
|
||
<span style="font-size: 3rem;">🔗</span>
|
||
<h4 class="text-muted mt-3">Keine Referenzen gefunden</h4>
|
||
<p class="text-muted">Es wurden keine Referenzen in der Datenbank gefunden.</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Sidebar -->
|
||
<div class="col-lg-4">
|
||
<!-- Statistics -->
|
||
<div class="card mb-4 sticky-top" style="top: 1rem;">
|
||
<div class="card-header">
|
||
<h5 class="mb-0">📊 Statistiken</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row text-center mb-3">
|
||
<div class="col-6">
|
||
<h4 class="text-primary mb-1">{{ referenzen|length }}</h4>
|
||
<small class="text-muted">Gesamt</small>
|
||
</div>
|
||
<div class="col-6">
|
||
<h4 class="text-success mb-1">
|
||
{% for node in referenzen %}
|
||
{% if node.is_leaf_node %}
|
||
{% if forloop.first %}1{% else %}{{ forloop.counter }}{% endif %}
|
||
{% endif %}
|
||
{% endfor %}
|
||
</h4>
|
||
<small class="text-muted">Endpunkte</small>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="border-top pt-3">
|
||
<h6 class="text-muted mb-2">Baumtiefe</h6>
|
||
<div class="progress" style="height: 8px;">
|
||
<div class="progress-bar bg-primary" style="width: 75%"></div>
|
||
</div>
|
||
<small class="text-muted">Maximale Tiefe: 4 Ebenen</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Quick Navigation -->
|
||
<div class="card mb-4">
|
||
<div class="card-header">
|
||
<h5 class="mb-0">🧭 Navigation</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="d-grid gap-2">
|
||
<button class="btn btn-outline btn-sm" onclick="scrollToTop()">
|
||
⬆️ Zum Anfang
|
||
</button>
|
||
<button class="btn btn-outline btn-sm" onclick="findNextMatch()">
|
||
⬇️ Nächster Treffer
|
||
</button>
|
||
<button class="btn btn-outline btn-sm" onclick="resetFilters()">
|
||
🔄 Filter zurücksetzen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Help -->
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h5 class="mb-0">💡 Hinweise</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<ul class="small mb-0">
|
||
<li>Klicken Sie auf ▼/▶ um Knoten ein-/auszuklappen</li>
|
||
<li>Nutzen Sie die Suche um gezielt zu filtern</li>
|
||
<li>Referenznummern sind hervorgehoben</li>
|
||
<li>Endpunkte haben keine Unterelemente</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<style>
|
||
/* Tree Styles */
|
||
.tree-root {
|
||
list-style: none;
|
||
padding-left: 0;
|
||
margin: 0;
|
||
}
|
||
|
||
.tree-node {
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.tree-node-content {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 8px 12px;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
border: 1px solid transparent;
|
||
}
|
||
|
||
.tree-node-content:hover {
|
||
background-color: var(--bg-secondary);
|
||
border-color: var(--primary-color);
|
||
}
|
||
|
||
.tree-toggle {
|
||
width: 20px;
|
||
text-align: center;
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.tree-toggle-placeholder {
|
||
width: 20px;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.tree-link {
|
||
text-decoration: none;
|
||
color: var(--text-primary);
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.tree-link:hover {
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
.tree-number {
|
||
font-family: var(--font-mono);
|
||
font-weight: 600;
|
||
color: var(--primary-color);
|
||
background-color: var(--bg-secondary);
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.tree-text {
|
||
font-weight: 500;
|
||
}
|
||
|
||
.tree-node-meta {
|
||
margin-left: auto;
|
||
display: flex;
|
||
gap: 4px;
|
||
}
|
||
|
||
.tree-children {
|
||
list-style: none;
|
||
padding-left: 28px;
|
||
margin: 2px 0 0 0;
|
||
border-left: 2px solid var(--border-color);
|
||
margin-left: 10px;
|
||
}
|
||
|
||
.tree-children .tree-node {
|
||
position: relative;
|
||
}
|
||
|
||
.tree-children .tree-node::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: -30px;
|
||
top: 20px;
|
||
width: 20px;
|
||
height: 1px;
|
||
background-color: var(--border-color);
|
||
}
|
||
|
||
/* Collapsed state */
|
||
.tree-children.collapsed {
|
||
display: none;
|
||
}
|
||
|
||
.tree-node.collapsed .tree-toggle {
|
||
transform: rotate(-90deg);
|
||
}
|
||
|
||
/* Highlighted search results */
|
||
.tree-node.highlighted > .tree-node-content {
|
||
background-color: var(--warning-color);
|
||
color: white;
|
||
}
|
||
|
||
.tree-node.highlighted .tree-number {
|
||
background-color: rgba(255, 255, 255, 0.2);
|
||
color: white;
|
||
}
|
||
|
||
/* Responsive adjustments */
|
||
@media (max-width: 768px) {
|
||
.tree-children {
|
||
padding-left: 20px;
|
||
}
|
||
|
||
.tree-node-meta {
|
||
display: none;
|
||
}
|
||
|
||
.tree-number {
|
||
font-size: 0.75rem;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<script>
|
||
let currentMatchIndex = -1;
|
||
let matches = [];
|
||
|
||
function toggleNode(element) {
|
||
const node = element.parentElement;
|
||
const children = node.querySelector(':scope > .tree-children');
|
||
const toggle = element.querySelector('.tree-toggle');
|
||
|
||
if (children) {
|
||
children.classList.toggle('collapsed');
|
||
node.classList.toggle('collapsed');
|
||
|
||
if (toggle) {
|
||
toggle.textContent = children.classList.contains('collapsed') ? '▶' : '▼';
|
||
}
|
||
}
|
||
}
|
||
|
||
function expandAll() {
|
||
const children = document.querySelectorAll('.tree-children');
|
||
const nodes = document.querySelectorAll('.tree-node');
|
||
const toggles = document.querySelectorAll('.tree-toggle');
|
||
|
||
children.forEach(child => child.classList.remove('collapsed'));
|
||
nodes.forEach(node => node.classList.remove('collapsed'));
|
||
toggles.forEach(toggle => {
|
||
if (toggle.textContent === '▶') {
|
||
toggle.textContent = '▼';
|
||
}
|
||
});
|
||
}
|
||
|
||
function collapseAll() {
|
||
const children = document.querySelectorAll('.tree-children');
|
||
const nodes = document.querySelectorAll('.tree-node');
|
||
const toggles = document.querySelectorAll('.tree-toggle');
|
||
|
||
children.forEach(child => child.classList.add('collapsed'));
|
||
nodes.forEach(node => node.classList.add('collapsed'));
|
||
toggles.forEach(toggle => {
|
||
if (toggle.textContent === '▼') {
|
||
toggle.textContent = '▶';
|
||
}
|
||
});
|
||
}
|
||
|
||
function filterTree() {
|
||
const searchTerm = document.getElementById('tree-search').value.toLowerCase();
|
||
const filterType = document.getElementById('tree-filter').value;
|
||
const nodes = document.querySelectorAll('.tree-node');
|
||
|
||
matches = [];
|
||
currentMatchIndex = -1;
|
||
|
||
nodes.forEach(node => {
|
||
const nodeText = node.dataset.nodeText.toLowerCase();
|
||
const hasChildren = node.querySelector(':scope > .tree-children') !== null;
|
||
const isLeaf = !hasChildren;
|
||
|
||
let showNode = true;
|
||
|
||
// Apply search filter
|
||
if (searchTerm && !nodeText.includes(searchTerm)) {
|
||
showNode = false;
|
||
}
|
||
|
||
// Apply type filter
|
||
if (filterType === 'has-children' && !hasChildren) {
|
||
showNode = false;
|
||
} else if (filterType === 'leaf-only' && !isLeaf) {
|
||
showNode = false;
|
||
}
|
||
|
||
// Show/hide node
|
||
node.style.display = showNode ? 'block' : 'none';
|
||
|
||
// Highlight search matches
|
||
if (searchTerm && nodeText.includes(searchTerm)) {
|
||
node.classList.add('highlighted');
|
||
matches.push(node);
|
||
} else {
|
||
node.classList.remove('highlighted');
|
||
}
|
||
|
||
// Auto-expand parent nodes of matches
|
||
if (searchTerm && nodeText.includes(searchTerm)) {
|
||
let parent = node.parentElement;
|
||
while (parent && parent.classList.contains('tree-children')) {
|
||
parent.classList.remove('collapsed');
|
||
parent.parentElement.classList.remove('collapsed');
|
||
const toggle = parent.parentElement.querySelector('.tree-toggle');
|
||
if (toggle) toggle.textContent = '▼';
|
||
parent = parent.parentElement.parentElement;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
function findNextMatch() {
|
||
if (matches.length === 0) return;
|
||
|
||
currentMatchIndex = (currentMatchIndex + 1) % matches.length;
|
||
const match = matches[currentMatchIndex];
|
||
|
||
// Scroll to match
|
||
match.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||
|
||
// Highlight temporarily
|
||
match.style.backgroundColor = 'var(--accent-color)';
|
||
setTimeout(() => {
|
||
match.style.backgroundColor = '';
|
||
}, 1000);
|
||
}
|
||
|
||
function scrollToTop() {
|
||
document.getElementById('tree-container').scrollIntoView({
|
||
behavior: 'smooth',
|
||
block: 'start'
|
||
});
|
||
}
|
||
|
||
function resetFilters() {
|
||
document.getElementById('tree-search').value = '';
|
||
document.getElementById('tree-filter').value = '';
|
||
filterTree();
|
||
expandAll();
|
||
}
|
||
|
||
// Initialize on page load
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// Add keyboard shortcuts
|
||
document.addEventListener('keydown', function(e) {
|
||
if (e.ctrlKey || e.metaKey) {
|
||
switch(e.key) {
|
||
case 'f':
|
||
e.preventDefault();
|
||
document.getElementById('tree-search').focus();
|
||
break;
|
||
case 'e':
|
||
e.preventDefault();
|
||
expandAll();
|
||
break;
|
||
case 'w':
|
||
e.preventDefault();
|
||
collapseAll();
|
||
break;
|
||
}
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
{% endblock %} |