Initial commit
This commit is contained in:
commit
53a0522417
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Datenbankverbindung (enthält Passwörter)
|
||||||
|
ajax/db_connection.php
|
||||||
|
|
||||||
|
# Logdateien
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Backup-Dateien
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
# Datenbankdumps
|
||||||
|
*.sql
|
||||||
|
|
||||||
|
# Versionierte Backup-Dateien
|
||||||
|
*.20*
|
||||||
477
Classifications.php
Normal file
477
Classifications.php
Normal file
@ -0,0 +1,477 @@
|
|||||||
|
<?php
|
||||||
|
include "templates/Header.html";
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Hidden Fields -->
|
||||||
|
<input type="hidden" id="locale" value="de-DE">
|
||||||
|
<input type="hidden" id="ID" value="">
|
||||||
|
<input type="hidden" id="authType" value="Classification">
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="main-content">
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<!-- Content Card -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="d-flex justify-between align-center mb-20">
|
||||||
|
<h1 class="card-title" style="margin: 0;">🗂️ Klassifikationsverwaltung</h1>
|
||||||
|
<button id="add" class="btn btn-primary" onclick="newClassificationsShow()">
|
||||||
|
<i class="fas fa-plus"></i> Neue Klassifikation
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bootstrap Table -->
|
||||||
|
<table
|
||||||
|
id="table"
|
||||||
|
data-toolbar="#toolbar"
|
||||||
|
data-search="true"
|
||||||
|
data-query-params-type="limit"
|
||||||
|
data-show-refresh="false"
|
||||||
|
data-show-toggle="false"
|
||||||
|
data-show-fullscreen="false"
|
||||||
|
data-show-columns="false"
|
||||||
|
data-show-columns-toggle-all="false"
|
||||||
|
data-detail-view="false"
|
||||||
|
data-show-export="false"
|
||||||
|
data-click-to-select="false"
|
||||||
|
data-trim-on-search="false"
|
||||||
|
data-minimum-count-columns="2"
|
||||||
|
data-show-pagination-switch="false"
|
||||||
|
data-page-size="25"
|
||||||
|
data-pagination="true"
|
||||||
|
data-id-field="ID"
|
||||||
|
data-page-list="[10, 25, 50, 100, all]"
|
||||||
|
data-show-footer="false"
|
||||||
|
data-side-pagination="server"
|
||||||
|
data-server-sort="false"
|
||||||
|
data-url="/Thesaurus/ajax/getClassificationData.php"
|
||||||
|
data-response-handler="responseHandler">
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Records Content -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="records_content"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var $table = $('#table')
|
||||||
|
var $remove = $('#remove')
|
||||||
|
var selections = []
|
||||||
|
|
||||||
|
function getIdSelections() {
|
||||||
|
return $.map($table.bootstrapTable('getSelections'), function (row) {
|
||||||
|
return row.ID
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function responseHandler(res) {
|
||||||
|
$.each(res.rows, function (i, row) {
|
||||||
|
row.state = $.inArray(row.ID, selections) !== -1
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
function detailFormatter(index, row) {
|
||||||
|
var html = []
|
||||||
|
$.each(row, function (key, value) {
|
||||||
|
if (key !== "ID" && key !== "state" && key != "Text" && key != "Descriptor") {
|
||||||
|
if (value !== null && value !== undefined && String(value).length > 0)
|
||||||
|
html.push('<p><b>' + key + ':</b> ' + value + '</p>')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return html.join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyFormatter(value, row, index) {
|
||||||
|
if (row.Descriptor === true || row.Descriptor === 'true' || row.Descriptor === 1) {
|
||||||
|
return '<button class="btn btn-sm btn-outline-secondary copy-btn" style="padding: 2px 6px; line-height: 1;" ' +
|
||||||
|
'onclick="event.stopPropagation(); copyTermToClipboard(\'' + row.Text.replace(/'/g, "\\'") + '\', this)" ' +
|
||||||
|
'title="Begriff kopieren">' +
|
||||||
|
'<i class="fas fa-copy" style="font-size: 0.75rem;"></i>' +
|
||||||
|
'</button>';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function operateFormatter(value, row, index) {
|
||||||
|
return [
|
||||||
|
'<div class="table-actions">',
|
||||||
|
'<a class="relations btn btn-sm btn-secondary" href="javascript:void(0)" title="Relationen anzeigen">',
|
||||||
|
'<i class="fas fa-table"></i>',
|
||||||
|
'</a>',
|
||||||
|
'<a class="modify btn btn-sm btn-primary" href="javascript:void(0)" title="Bearbeiten">',
|
||||||
|
'<i class="fas fa-edit"></i>',
|
||||||
|
'</a>',
|
||||||
|
'<a class="remove btn btn-sm btn-danger" href="javascript:void(0)" title="Löschen">',
|
||||||
|
'<i class="fas fa-trash"></i>',
|
||||||
|
'</a>',
|
||||||
|
'</div>'
|
||||||
|
].join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
window.operateEvents = {
|
||||||
|
'click .relations': function (e, value, row, index) {
|
||||||
|
ShowRelationsModal(row.ID, 'Classification')
|
||||||
|
},
|
||||||
|
'click .modify': function (e, value, row, index) {
|
||||||
|
ModifyTerm([row.ID]),
|
||||||
|
$table.bootstrapTable('refresh')
|
||||||
|
},
|
||||||
|
'click .remove': function (e, value, row, index) {
|
||||||
|
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$table.bootstrapTable('remove', {
|
||||||
|
field: 'ID',
|
||||||
|
values: [row.ID]
|
||||||
|
})
|
||||||
|
DeleteTerm([row.ID])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function totalTextFormatter(data) {
|
||||||
|
return 'Total'
|
||||||
|
}
|
||||||
|
|
||||||
|
function totalNameFormatter(data) {
|
||||||
|
return data.length
|
||||||
|
}
|
||||||
|
|
||||||
|
function initTableClassification() {
|
||||||
|
$table.bootstrapTable('destroy').bootstrapTable({
|
||||||
|
locale: $('#locale').val(),
|
||||||
|
columns: [
|
||||||
|
[{
|
||||||
|
title: 'Klassifikation',
|
||||||
|
colspan: 4,
|
||||||
|
align: 'center'
|
||||||
|
}],
|
||||||
|
[{
|
||||||
|
field: 'copy',
|
||||||
|
title: '',
|
||||||
|
align: 'center',
|
||||||
|
width: 40,
|
||||||
|
clickToSelect: false,
|
||||||
|
formatter: copyFormatter
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'Notation',
|
||||||
|
title: 'Notation',
|
||||||
|
sortable: true,
|
||||||
|
align: 'left',
|
||||||
|
width: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'Text',
|
||||||
|
title: 'Bezeichnung',
|
||||||
|
sortable: true,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'operate',
|
||||||
|
title: 'Funktionen',
|
||||||
|
align: 'center',
|
||||||
|
width: 160,
|
||||||
|
clickToSelect: false,
|
||||||
|
events: window.operateEvents,
|
||||||
|
formatter: operateFormatter
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
$table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table',
|
||||||
|
function () {
|
||||||
|
$remove.prop('disabled', !$table.bootstrapTable('getSelections').length)
|
||||||
|
selections = getIdSelections()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
$table.on('all.bs.table', function (e, name, args) {
|
||||||
|
console.log(name, args)
|
||||||
|
})
|
||||||
|
|
||||||
|
$remove.click(function () {
|
||||||
|
var ids = getIdSelections()
|
||||||
|
$table.bootstrapTable('remove', {
|
||||||
|
field: 'ID',
|
||||||
|
values: ids
|
||||||
|
})
|
||||||
|
$remove.prop('disabled', true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReadDetails(RecordID) {
|
||||||
|
$('#ID').val(RecordID);
|
||||||
|
console.log('✅ RecordID gesetzt in #ID:', RecordID);
|
||||||
|
$('#search_relation').data('record-id', RecordID);
|
||||||
|
getData(1, 1, RecordID);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData(number, size, idvalue) {
|
||||||
|
var Type = document.getElementById("authType").value;
|
||||||
|
if (typeof idvalue == "undefined") {
|
||||||
|
var search = $("div.bootstrap-table > div.fixed-table-toolbar > div.search > input.form-control").val();
|
||||||
|
var id = 0
|
||||||
|
} else {
|
||||||
|
var id = idvalue
|
||||||
|
var search = ''
|
||||||
|
}
|
||||||
|
$.get('/Thesaurus/ajax/getClassificationData.php', {
|
||||||
|
id: id,
|
||||||
|
search: search,
|
||||||
|
offset: (number - 1) * size,
|
||||||
|
authType: Type,
|
||||||
|
limit: size
|
||||||
|
}, function (res) {
|
||||||
|
$table.bootstrapTable('load', res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
$table.on('page-change.bs.table', function (e, number, size, id) {
|
||||||
|
getData(number, size, id)
|
||||||
|
})
|
||||||
|
var options = $table.bootstrapTable('getOptions')
|
||||||
|
getData(options.pageNumber, options.pageSize)
|
||||||
|
})
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
initTableClassification()
|
||||||
|
$('#locale').change(initTableClassification)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Globale searchRelation Funktion
|
||||||
|
window.searchRelation = function() {
|
||||||
|
const $input = $('#search_relation');
|
||||||
|
|
||||||
|
if ($input.length === 0) {
|
||||||
|
console.warn('⚠️ Element #search_relation nicht gefunden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entferne alte Event-Handler um Duplikate zu vermeiden
|
||||||
|
$input.off('input blur focus');
|
||||||
|
|
||||||
|
console.log('✓ Autocomplete initialisiert für:', $input.attr('id'));
|
||||||
|
|
||||||
|
let currentRequest = null;
|
||||||
|
|
||||||
|
$input.on('input', function() {
|
||||||
|
const query = $(this).val().trim();
|
||||||
|
console.log('🔍 Input erkannt:', query);
|
||||||
|
|
||||||
|
if (currentRequest) {
|
||||||
|
currentRequest.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.length < 2) {
|
||||||
|
console.log('⚠️ Zu kurz (minimum 2 Zeichen)');
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('📡 Sende AJAX Anfrage...');
|
||||||
|
|
||||||
|
currentRequest = $.ajax({
|
||||||
|
url: '/Thesaurus/ajax/Autocomplete.php',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
query: query,
|
||||||
|
authType: 'Classification'
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(data) {
|
||||||
|
console.log('✅ Daten erhalten:', data.length, 'Einträge');
|
||||||
|
showAutocompleteResults(data, $input);
|
||||||
|
currentRequest = null;
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
if (status !== 'abort') {
|
||||||
|
console.error('❌ AJAX Fehler:', error);
|
||||||
|
}
|
||||||
|
currentRequest = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$input.on('blur', function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
$input.on('focus', function() {
|
||||||
|
const query = $(this).val().trim();
|
||||||
|
if (query.length >= 2) {
|
||||||
|
$(this).trigger('input');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(window).on('scroll resize', function() {
|
||||||
|
if ($('.autocomplete-dropdown').length > 0) {
|
||||||
|
const offset = $input.offset();
|
||||||
|
const inputHeight = $input.outerHeight();
|
||||||
|
const scrollTop = $(window).scrollTop();
|
||||||
|
const scrollLeft = $(window).scrollLeft();
|
||||||
|
|
||||||
|
$('.autocomplete-dropdown').css({
|
||||||
|
top: offset.top - scrollTop + inputHeight,
|
||||||
|
left: offset.left - scrollLeft
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function showAutocompleteResults(data, $input) {
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
console.log('⚠️ Keine Ergebnisse');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const $dropdown = $('<div class="autocomplete-dropdown"></div>');
|
||||||
|
|
||||||
|
data.forEach(function(item) {
|
||||||
|
const match = item.match(/^(.+?)\s*\(ID:\s*(\d+)\)$/);
|
||||||
|
const label = match ? match[1].trim() : item;
|
||||||
|
const value = match ? match[2] : item;
|
||||||
|
|
||||||
|
const $item = $('<a class="dropdown-item" href="#"></a>')
|
||||||
|
.text(label)
|
||||||
|
.data('value', value)
|
||||||
|
.data('label', label)
|
||||||
|
.data('fulltext', item);
|
||||||
|
|
||||||
|
$item.on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const selectedItem = {
|
||||||
|
label: $(this).data('label'),
|
||||||
|
value: $(this).data('value'),
|
||||||
|
fullText: $(this).data('fulltext')
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('✅ Ausgewählt:', selectedItem);
|
||||||
|
|
||||||
|
$input.val(selectedItem.label);
|
||||||
|
$input.data('selected-id', selectedItem.value);
|
||||||
|
$input.trigger('autocomplete:select', [selectedItem]);
|
||||||
|
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
|
||||||
|
// Speichere die Werte in Data-Attributen auf dem Button
|
||||||
|
const recordID = $('#ID').val();
|
||||||
|
console.log('🔍 recordID:', recordID);
|
||||||
|
console.log('🔍 selectedItem.value:', selectedItem.value);
|
||||||
|
|
||||||
|
$('#classification-anlegen').data('record-id', recordID);
|
||||||
|
$('#classification-anlegen').data('relation-id', selectedItem.value);
|
||||||
|
|
||||||
|
console.log('✅ Data-Attribute gesetzt auf #classification-anlegen');
|
||||||
|
});
|
||||||
|
|
||||||
|
$dropdown.append($item);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('body').append($dropdown);
|
||||||
|
|
||||||
|
const offset = $input.offset();
|
||||||
|
const inputHeight = $input.outerHeight();
|
||||||
|
const inputWidth = $input.outerWidth();
|
||||||
|
const scrollTop = $(window).scrollTop();
|
||||||
|
const scrollLeft = $(window).scrollLeft();
|
||||||
|
|
||||||
|
$dropdown.css({
|
||||||
|
position: 'fixed',
|
||||||
|
top: offset.top - scrollTop + inputHeight,
|
||||||
|
left: offset.left - scrollLeft,
|
||||||
|
width: inputWidth,
|
||||||
|
'min-width': inputWidth,
|
||||||
|
'z-index': 9999
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('📋 Dropdown angezeigt mit', data.length, 'Einträgen');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Automatische Initialisierung
|
||||||
|
$(document).ready(function() {
|
||||||
|
console.log('jQuery bereit');
|
||||||
|
|
||||||
|
$('.modal').on('shown.bs.modal', function () {
|
||||||
|
console.log('📂 Modal geöffnet');
|
||||||
|
if ($('#search_relation').length > 0) {
|
||||||
|
console.log('✅ search_relation gefunden, initialisiere...');
|
||||||
|
searchRelation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($('#search_relation').length > 0) {
|
||||||
|
console.log('✅ search_relation beim Laden gefunden, initialisiere...');
|
||||||
|
searchRelation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click-Handler für den "Hinzufügen" Button
|
||||||
|
$(document).on('click', '#classification-anlegen', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const recordID = $(this).data('record-id');
|
||||||
|
const relationID = $(this).data('relation-id');
|
||||||
|
|
||||||
|
console.log('🚀 Button geklickt - recordID:', recordID, '- relationID:', relationID);
|
||||||
|
|
||||||
|
if (recordID && relationID) {
|
||||||
|
CreateNewRelation(recordID, relationID, event);
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ recordID oder relationID fehlt!');
|
||||||
|
alert('Bitte zuerst einen Begriff aus der Autocomplete-Liste auswählen.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Bootstrap Modals -->
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/ClassificationsModal.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/ClassificationsModal.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/ClassificationDetailsModal.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/ClassificationDetailsModal.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/newClassification.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/newClassification.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/RelationsModal.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/RelationsModal.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Custom JS file -->
|
||||||
|
<script type="text/javascript" src="js/ClassificationScript.js"></script>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
include "templates/Footer.html";
|
||||||
|
?>
|
||||||
476
Corporates.php
Normal file
476
Corporates.php
Normal file
@ -0,0 +1,476 @@
|
|||||||
|
<?php
|
||||||
|
include "templates/Header.html";
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Hidden Fields -->
|
||||||
|
<input type="hidden" id="locale" value="de-DE">
|
||||||
|
<input type="hidden" id="ID" value="">
|
||||||
|
<input type="hidden" id="authType" value="Corporate">
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="main-content">
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<!-- Content Card -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="d-flex justify-between align-center mb-20">
|
||||||
|
<h1 class="card-title" style="margin: 0;">🏢 Körperschaftsverwaltung</h1>
|
||||||
|
<button id="add" class="btn btn-primary" onclick="newCorporatesShow()">
|
||||||
|
<i class="fas fa-plus"></i> Neue Körperschaft
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bootstrap Table -->
|
||||||
|
<table
|
||||||
|
id="table"
|
||||||
|
data-toolbar="#toolbar"
|
||||||
|
data-search="true"
|
||||||
|
data-query-params-type="limit"
|
||||||
|
data-show-refresh="false"
|
||||||
|
data-show-toggle="false"
|
||||||
|
data-show-fullscreen="false"
|
||||||
|
data-show-columns="false"
|
||||||
|
data-show-columns-toggle-all="false"
|
||||||
|
data-detail-view="false"
|
||||||
|
data-show-export="false"
|
||||||
|
data-click-to-select="false"
|
||||||
|
data-trim-on-search="false"
|
||||||
|
data-minimum-count-columns="2"
|
||||||
|
data-show-pagination-switch="false"
|
||||||
|
data-page-size="25"
|
||||||
|
data-pagination="true"
|
||||||
|
data-id-field="ID"
|
||||||
|
data-page-list="[10, 25, 50, 100, all]"
|
||||||
|
data-show-footer="false"
|
||||||
|
data-side-pagination="server"
|
||||||
|
data-server-sort="false"
|
||||||
|
data-url="/Thesaurus/ajax/getCorporateData.php"
|
||||||
|
data-response-handler="responseHandler">
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Records Content -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="records_content"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var $table = $('#table')
|
||||||
|
var $remove = $('#remove')
|
||||||
|
var selections = []
|
||||||
|
|
||||||
|
function getIdSelections() {
|
||||||
|
return $.map($table.bootstrapTable('getSelections'), function (row) {
|
||||||
|
return row.ID
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function responseHandler(res) {
|
||||||
|
$.each(res.rows, function (i, row) {
|
||||||
|
row.state = $.inArray(row.ID, selections) !== -1
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
function detailFormatter(index, row) {
|
||||||
|
var html = []
|
||||||
|
$.each(row, function (key, value) {
|
||||||
|
if (key !== "ID" && key !== "state" && key != "Text" && key != "Descriptor") {
|
||||||
|
if (value !== null && value !== undefined && String(value).length > 0)
|
||||||
|
html.push('<p><b>' + key + ':</b> ' + value + '</p>')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return html.join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyFormatter(value, row, index) {
|
||||||
|
if (row.Descriptor === true || row.Descriptor === 'true' || row.Descriptor === 1) {
|
||||||
|
return '<button class="btn btn-sm btn-outline-secondary copy-btn" style="padding: 2px 6px; line-height: 1;" ' +
|
||||||
|
'onclick="event.stopPropagation(); copyTermToClipboard(\'' + row.Text.replace(/'/g, "\\'") + '\', this)" ' +
|
||||||
|
'title="Begriff kopieren">' +
|
||||||
|
'<i class="fas fa-copy" style="font-size: 0.75rem;"></i>' +
|
||||||
|
'</button>';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function operateFormatter(value, row, index) {
|
||||||
|
return [
|
||||||
|
'<div class="table-actions">',
|
||||||
|
'<a class="relations btn btn-sm btn-secondary" href="javascript:void(0)" title="Relationen anzeigen">',
|
||||||
|
'<i class="fas fa-table"></i>',
|
||||||
|
'</a>',
|
||||||
|
'<a class="details btn btn-sm btn-info" href="javascript:void(0)" title="Semantisches Netz">',
|
||||||
|
'<i class="fas fa-project-diagram"></i>',
|
||||||
|
'</a>',
|
||||||
|
'<a class="modify btn btn-sm btn-primary" href="javascript:void(0)" title="Bearbeiten">',
|
||||||
|
'<i class="fas fa-edit"></i>',
|
||||||
|
'</a>',
|
||||||
|
'<a class="remove btn btn-sm btn-danger" href="javascript:void(0)" title="Löschen">',
|
||||||
|
'<i class="fas fa-trash"></i>',
|
||||||
|
'</a>',
|
||||||
|
'</div>'
|
||||||
|
].join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
window.operateEvents = {
|
||||||
|
'click .relations': function (e, value, row, index) {
|
||||||
|
ShowRelationsModal(row.ID, 'Corporate')
|
||||||
|
},
|
||||||
|
'click .details': function (e, value, row, index) {
|
||||||
|
ShowModalDetails(row.ID)
|
||||||
|
},
|
||||||
|
'click .modify': function (e, value, row, index) {
|
||||||
|
ModifyTerm([row.ID]),
|
||||||
|
$table.bootstrapTable('refresh')
|
||||||
|
},
|
||||||
|
'click .remove': function (e, value, row, index) {
|
||||||
|
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$table.bootstrapTable('remove', {
|
||||||
|
field: 'ID',
|
||||||
|
values: [row.ID]
|
||||||
|
})
|
||||||
|
DeleteTerm([row.ID])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function totalTextFormatter(data) {
|
||||||
|
return 'Total'
|
||||||
|
}
|
||||||
|
|
||||||
|
function totalNameFormatter(data) {
|
||||||
|
return data.length
|
||||||
|
}
|
||||||
|
|
||||||
|
function initTableCorporate() {
|
||||||
|
$table.bootstrapTable('destroy').bootstrapTable({
|
||||||
|
locale: $('#locale').val(),
|
||||||
|
columns: [
|
||||||
|
[{
|
||||||
|
title: 'Körperschaften',
|
||||||
|
colspan: 3,
|
||||||
|
align: 'center'
|
||||||
|
}],
|
||||||
|
[{
|
||||||
|
field: 'copy',
|
||||||
|
title: '',
|
||||||
|
align: 'center',
|
||||||
|
width: 40,
|
||||||
|
clickToSelect: false,
|
||||||
|
formatter: copyFormatter
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'Text',
|
||||||
|
title: 'Name',
|
||||||
|
sortable: true,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'operate',
|
||||||
|
title: 'Funktionen',
|
||||||
|
align: 'center',
|
||||||
|
width: 190,
|
||||||
|
clickToSelect: false,
|
||||||
|
events: window.operateEvents,
|
||||||
|
formatter: operateFormatter
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
$table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table',
|
||||||
|
function () {
|
||||||
|
$remove.prop('disabled', !$table.bootstrapTable('getSelections').length)
|
||||||
|
selections = getIdSelections()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
$table.on('all.bs.table', function (e, name, args) {
|
||||||
|
console.log(name, args)
|
||||||
|
})
|
||||||
|
|
||||||
|
$remove.click(function () {
|
||||||
|
var ids = getIdSelections()
|
||||||
|
$table.bootstrapTable('remove', {
|
||||||
|
field: 'ID',
|
||||||
|
values: ids
|
||||||
|
})
|
||||||
|
$remove.prop('disabled', true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReadDetails(RecordID) {
|
||||||
|
$('#ID').val(RecordID);
|
||||||
|
console.log('✅ RecordID gesetzt in #ID:', RecordID);
|
||||||
|
$('#search_relation').data('record-id', RecordID);
|
||||||
|
getData(1, 1, RecordID);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData(number, size, idvalue) {
|
||||||
|
var Type = document.getElementById("authType").value;
|
||||||
|
if (typeof idvalue == "undefined") {
|
||||||
|
var search = $("div.bootstrap-table > div.fixed-table-toolbar > div.search > input.form-control").val();
|
||||||
|
var id = 0
|
||||||
|
} else {
|
||||||
|
var id = idvalue
|
||||||
|
var search = ''
|
||||||
|
}
|
||||||
|
$.get('/Thesaurus/ajax/getCorporateData.php', {
|
||||||
|
id: id,
|
||||||
|
search: search,
|
||||||
|
offset: (number - 1) * size,
|
||||||
|
authType: Type,
|
||||||
|
limit: size
|
||||||
|
}, function (res) {
|
||||||
|
$table.bootstrapTable('load', res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
$table.on('page-change.bs.table', function (e, number, size, id) {
|
||||||
|
getData(number, size, id)
|
||||||
|
})
|
||||||
|
var options = $table.bootstrapTable('getOptions')
|
||||||
|
getData(options.pageNumber, options.pageSize)
|
||||||
|
})
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
initTableCorporate()
|
||||||
|
$('#locale').change(initTableCorporate)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Globale searchRelation Funktion
|
||||||
|
window.searchRelation = function() {
|
||||||
|
const $input = $('#search_relation');
|
||||||
|
|
||||||
|
if ($input.length === 0) {
|
||||||
|
console.warn('⚠️ Element #search_relation nicht gefunden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entferne alte Event-Handler um Duplikate zu vermeiden
|
||||||
|
$input.off('input blur focus');
|
||||||
|
|
||||||
|
console.log('✓ Autocomplete initialisiert für:', $input.attr('id'));
|
||||||
|
|
||||||
|
let currentRequest = null;
|
||||||
|
|
||||||
|
$input.on('input', function() {
|
||||||
|
const query = $(this).val().trim();
|
||||||
|
console.log('🔍 Input erkannt:', query);
|
||||||
|
|
||||||
|
if (currentRequest) {
|
||||||
|
currentRequest.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.length < 2) {
|
||||||
|
console.log('⚠️ Zu kurz (minimum 2 Zeichen)');
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('📡 Sende AJAX Anfrage...');
|
||||||
|
|
||||||
|
currentRequest = $.ajax({
|
||||||
|
url: '/Thesaurus/ajax/Autocomplete.php',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
query: query,
|
||||||
|
authType: 'Corporate'
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(data) {
|
||||||
|
console.log('✅ Daten erhalten:', data.length, 'Einträge');
|
||||||
|
showAutocompleteResults(data, $input);
|
||||||
|
currentRequest = null;
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
if (status !== 'abort') {
|
||||||
|
console.error('❌ AJAX Fehler:', error);
|
||||||
|
}
|
||||||
|
currentRequest = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$input.on('blur', function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
$input.on('focus', function() {
|
||||||
|
const query = $(this).val().trim();
|
||||||
|
if (query.length >= 2) {
|
||||||
|
$(this).trigger('input');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(window).on('scroll resize', function() {
|
||||||
|
if ($('.autocomplete-dropdown').length > 0) {
|
||||||
|
const offset = $input.offset();
|
||||||
|
const inputHeight = $input.outerHeight();
|
||||||
|
const scrollTop = $(window).scrollTop();
|
||||||
|
const scrollLeft = $(window).scrollLeft();
|
||||||
|
|
||||||
|
$('.autocomplete-dropdown').css({
|
||||||
|
top: offset.top - scrollTop + inputHeight,
|
||||||
|
left: offset.left - scrollLeft
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function showAutocompleteResults(data, $input) {
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
console.log('⚠️ Keine Ergebnisse');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const $dropdown = $('<div class="autocomplete-dropdown"></div>');
|
||||||
|
|
||||||
|
data.forEach(function(item) {
|
||||||
|
const match = item.match(/^(.+?)\s*\(ID:\s*(\d+)\)$/);
|
||||||
|
const label = match ? match[1].trim() : item;
|
||||||
|
const value = match ? match[2] : item;
|
||||||
|
|
||||||
|
const $item = $('<a class="dropdown-item" href="#"></a>')
|
||||||
|
.text(label)
|
||||||
|
.data('value', value)
|
||||||
|
.data('label', label)
|
||||||
|
.data('fulltext', item);
|
||||||
|
|
||||||
|
$item.on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const selectedItem = {
|
||||||
|
label: $(this).data('label'),
|
||||||
|
value: $(this).data('value'),
|
||||||
|
fullText: $(this).data('fulltext')
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('✅ Ausgewählt:', selectedItem);
|
||||||
|
|
||||||
|
$input.val(selectedItem.label);
|
||||||
|
$input.data('selected-id', selectedItem.value);
|
||||||
|
$input.trigger('autocomplete:select', [selectedItem]);
|
||||||
|
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
|
||||||
|
// Speichere die Werte in Data-Attributen auf dem Button
|
||||||
|
const recordID = $('#ID').val();
|
||||||
|
console.log('🔍 recordID:', recordID);
|
||||||
|
console.log('🔍 selectedItem.value:', selectedItem.value);
|
||||||
|
|
||||||
|
$('#corporate-anlegen').data('record-id', recordID);
|
||||||
|
$('#corporate-anlegen').data('relation-id', selectedItem.value);
|
||||||
|
|
||||||
|
console.log('✅ Data-Attribute gesetzt auf #corporate-anlegen');
|
||||||
|
});
|
||||||
|
|
||||||
|
$dropdown.append($item);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('body').append($dropdown);
|
||||||
|
|
||||||
|
const offset = $input.offset();
|
||||||
|
const inputHeight = $input.outerHeight();
|
||||||
|
const inputWidth = $input.outerWidth();
|
||||||
|
const scrollTop = $(window).scrollTop();
|
||||||
|
const scrollLeft = $(window).scrollLeft();
|
||||||
|
|
||||||
|
$dropdown.css({
|
||||||
|
position: 'fixed',
|
||||||
|
top: offset.top - scrollTop + inputHeight,
|
||||||
|
left: offset.left - scrollLeft,
|
||||||
|
width: inputWidth,
|
||||||
|
'min-width': inputWidth,
|
||||||
|
'z-index': 9999
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('📋 Dropdown angezeigt mit', data.length, 'Einträgen');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Automatische Initialisierung
|
||||||
|
$(document).ready(function() {
|
||||||
|
console.log('jQuery bereit');
|
||||||
|
|
||||||
|
$('.modal').on('shown.bs.modal', function () {
|
||||||
|
console.log('📂 Modal geöffnet');
|
||||||
|
if ($('#search_relation').length > 0) {
|
||||||
|
console.log('✅ search_relation gefunden, initialisiere...');
|
||||||
|
searchRelation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($('#search_relation').length > 0) {
|
||||||
|
console.log('✅ search_relation beim Laden gefunden, initialisiere...');
|
||||||
|
searchRelation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click-Handler für den "Hinzufügen" Button
|
||||||
|
$(document).on('click', '#corporate-anlegen', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const recordID = $(this).data('record-id');
|
||||||
|
const relationID = $(this).data('relation-id');
|
||||||
|
|
||||||
|
console.log('🚀 Button geklickt - recordID:', recordID, '- relationID:', relationID);
|
||||||
|
|
||||||
|
if (recordID && relationID) {
|
||||||
|
CreateNewRelation(recordID, relationID, event);
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ recordID oder relationID fehlt!');
|
||||||
|
alert('Bitte zuerst einen Begriff aus der Autocomplete-Liste auswählen.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Bootstrap Modals -->
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/CorporatesModal.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/CorporatesModal.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/CorporateDetailsModal.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/CorporateDetailsModal.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/newCorporate.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/newCorporate.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/RelationsModal.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/RelationsModal.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Custom JS file -->
|
||||||
|
<script type="text/javascript" src="js/CorporateScript.js"></script>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
include "templates/Footer.html";
|
||||||
|
?>
|
||||||
532
Persons.php
Normal file
532
Persons.php
Normal file
@ -0,0 +1,532 @@
|
|||||||
|
<?php
|
||||||
|
include "templates/Header.html";
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Hidden Fields -->
|
||||||
|
<input type="hidden" id="locale" value="de-DE">
|
||||||
|
<input type="hidden" id="ID" value="">
|
||||||
|
<input type="hidden" id="authType" value="Person">
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="main-content">
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<!-- Content Card -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="d-flex justify-between align-center mb-20">
|
||||||
|
<h1 class="card-title" style="margin: 0;">👥 Personenverwaltung</h1>
|
||||||
|
<button id="add" class="btn btn-primary" onclick="newPersonShow()">
|
||||||
|
<i class="fas fa-plus"></i> Neue Person
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bootstrap Table -->
|
||||||
|
<table
|
||||||
|
id="table"
|
||||||
|
data-toolbar="#toolbar"
|
||||||
|
data-search="true"
|
||||||
|
data-query-params-type="limit"
|
||||||
|
data-show-refresh="false"
|
||||||
|
data-show-toggle="false"
|
||||||
|
data-show-fullscreen="false"
|
||||||
|
data-show-columns="false"
|
||||||
|
data-show-columns-toggle-all="false"
|
||||||
|
data-detail-view="false"
|
||||||
|
data-show-export="false"
|
||||||
|
data-click-to-select="false"
|
||||||
|
data-trim-on-search="false"
|
||||||
|
data-minimum-count-columns="2"
|
||||||
|
data-show-pagination-switch="false"
|
||||||
|
data-page-size="25"
|
||||||
|
data-pagination="true"
|
||||||
|
data-id-field="ID"
|
||||||
|
data-page-list="[10, 25, 50, 100, all]"
|
||||||
|
data-show-footer="true"
|
||||||
|
data-side-pagination="server"
|
||||||
|
data-server-sort="false"
|
||||||
|
data-url="/Thesaurus/ajax/getPersonData.php"
|
||||||
|
data-response-handler="responseHandler">
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Records Content -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="records_content"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var $table = $('#table')
|
||||||
|
var $remove = $('#remove')
|
||||||
|
var selections = []
|
||||||
|
|
||||||
|
function getIdSelections() {
|
||||||
|
return $.map($table.bootstrapTable('getSelections'), function (row) {
|
||||||
|
return row.ID
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function responseHandler(res) {
|
||||||
|
$.each(res.rows, function (i, row) {
|
||||||
|
row.state = $.inArray(row.ID, selections) !== -1
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
function detailFormatter(index, row) {
|
||||||
|
var html = []
|
||||||
|
$.each(row, function (key, value) {
|
||||||
|
if (key !== "ID" && key !== "state" && key != "Text" && key != "Descriptor") {
|
||||||
|
if (value !== null && value !== undefined && String(value).length > 0)
|
||||||
|
html.push('<p><b>' + key + ':</b> ' + value + '</p>')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return html.join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyFormatter(value, row, index) {
|
||||||
|
if (row.Descriptor === true || row.Descriptor === 'true' || row.Descriptor === 1) {
|
||||||
|
return '<button class="btn btn-sm btn-outline-secondary copy-btn" style="padding: 2px 6px; line-height: 1;" ' +
|
||||||
|
'onclick="event.stopPropagation(); copyTermToClipboard(\'' + row.Text.replace(/'/g, "\\'") + '\', this)" ' +
|
||||||
|
'title="Begriff kopieren">' +
|
||||||
|
'<i class="fas fa-copy" style="font-size: 0.75rem;"></i>' +
|
||||||
|
'</button>';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function dspaceCountFormatter(value, row, index) {
|
||||||
|
if (row.Descriptor !== true && row.Descriptor !== 'true' && row.Descriptor !== 1) {
|
||||||
|
return '<span class="text-muted" title="Kein Deskriptor">–</span>';
|
||||||
|
}
|
||||||
|
return '<span class="dspace-count-cell" data-term="' + row.Text.replace(/"/g, '"') + '" data-authtype="Person">' +
|
||||||
|
'<span class="spinner-border spinner-border-sm text-muted" style="width: 12px; height: 12px;" role="status"></span>' +
|
||||||
|
'</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function operateFormatter(value, row, index) {
|
||||||
|
return [
|
||||||
|
'<div class="table-actions">',
|
||||||
|
'<a class="relations btn btn-sm btn-secondary" href="javascript:void(0)" title="Relationen anzeigen">',
|
||||||
|
'<i class="fas fa-table"></i>',
|
||||||
|
'</a>',
|
||||||
|
'<a class="details btn btn-sm btn-info" href="javascript:void(0)" title="Semantisches Netz">',
|
||||||
|
'<i class="fas fa-project-diagram"></i>',
|
||||||
|
'</a>',
|
||||||
|
'<a class="modify btn btn-sm btn-primary" href="javascript:void(0)" title="Bearbeiten">',
|
||||||
|
'<i class="fas fa-edit"></i>',
|
||||||
|
'</a>',
|
||||||
|
'<a class="remove btn btn-sm btn-danger" href="javascript:void(0)" title="Löschen">',
|
||||||
|
'<i class="fas fa-trash"></i>',
|
||||||
|
'</a>',
|
||||||
|
'</div>'
|
||||||
|
].join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
window.operateEvents = {
|
||||||
|
'click .relations': function (e, value, row, index) {
|
||||||
|
ShowRelationsModal(row.ID, 'Person')
|
||||||
|
},
|
||||||
|
'click .details': function (e, value, row, index) {
|
||||||
|
ShowModalDetails(row.ID)
|
||||||
|
},
|
||||||
|
'click .modify': function (e, value, row, index) {
|
||||||
|
ModifyPerson([row.ID]),
|
||||||
|
$table.bootstrapTable('refresh')
|
||||||
|
},
|
||||||
|
'click .remove': function (e, value, row, index) {
|
||||||
|
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$table.bootstrapTable('remove', {
|
||||||
|
field: 'ID',
|
||||||
|
values: [row.ID]
|
||||||
|
})
|
||||||
|
DeleteTerm([row.ID])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function totalTextFormatter(data) {
|
||||||
|
return 'Total'
|
||||||
|
}
|
||||||
|
|
||||||
|
function totalNameFormatter(data) {
|
||||||
|
return data.length
|
||||||
|
}
|
||||||
|
|
||||||
|
function initTablePerson() {
|
||||||
|
$table.bootstrapTable('destroy').bootstrapTable({
|
||||||
|
locale: $('#locale').val(),
|
||||||
|
columns: [
|
||||||
|
[{
|
||||||
|
title: 'Personen',
|
||||||
|
colspan: 4,
|
||||||
|
align: 'center'
|
||||||
|
}],
|
||||||
|
[{
|
||||||
|
field: 'copy',
|
||||||
|
title: '',
|
||||||
|
align: 'center',
|
||||||
|
width: 40,
|
||||||
|
clickToSelect: false,
|
||||||
|
formatter: copyFormatter
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'Text',
|
||||||
|
title: 'Name',
|
||||||
|
sortable: true,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'DSpaceCount',
|
||||||
|
title: 'DSpace',
|
||||||
|
align: 'center',
|
||||||
|
width: 80,
|
||||||
|
clickToSelect: false,
|
||||||
|
formatter: dspaceCountFormatter
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'operate',
|
||||||
|
title: 'Funktionen',
|
||||||
|
align: 'center',
|
||||||
|
width: 190,
|
||||||
|
clickToSelect: false,
|
||||||
|
events: window.operateEvents,
|
||||||
|
formatter: operateFormatter
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
$table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table',
|
||||||
|
function () {
|
||||||
|
$remove.prop('disabled', !$table.bootstrapTable('getSelections').length)
|
||||||
|
selections = getIdSelections()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
$table.on('all.bs.table', function (e, name, args) {
|
||||||
|
console.log(name, args)
|
||||||
|
})
|
||||||
|
|
||||||
|
$remove.click(function () {
|
||||||
|
var ids = getIdSelections()
|
||||||
|
$table.bootstrapTable('remove', {
|
||||||
|
field: 'ID',
|
||||||
|
values: ids
|
||||||
|
})
|
||||||
|
$remove.prop('disabled', true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReadDetails(RecordID) {
|
||||||
|
$('#ID').val(RecordID);
|
||||||
|
console.log('✅ RecordID gesetzt in #ID:', RecordID);
|
||||||
|
$('#search_relation').data('record-id', RecordID);
|
||||||
|
getData(1, 1, RecordID);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData(number, size, idvalue) {
|
||||||
|
var Type = document.getElementById("authType").value;
|
||||||
|
if (typeof idvalue == "undefined") {
|
||||||
|
var search = $("div.bootstrap-table > div.fixed-table-toolbar > div.search > input.form-control").val();
|
||||||
|
var id = 0
|
||||||
|
} else {
|
||||||
|
var id = idvalue
|
||||||
|
var search = ''
|
||||||
|
}
|
||||||
|
$.get('/Thesaurus/ajax/getPersonData.php', {
|
||||||
|
id: id,
|
||||||
|
search: search,
|
||||||
|
offset: (number - 1) * size,
|
||||||
|
authType: Type,
|
||||||
|
limit: size
|
||||||
|
}, function (res) {
|
||||||
|
$table.bootstrapTable('load', res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
$table.on('page-change.bs.table', function (e, number, size, id) {
|
||||||
|
getData(number, size, id)
|
||||||
|
})
|
||||||
|
var options = $table.bootstrapTable('getOptions')
|
||||||
|
getData(options.pageNumber, options.pageSize)
|
||||||
|
})
|
||||||
|
|
||||||
|
// DSpace-Counts asynchron laden nach dem Rendern der Tabelle
|
||||||
|
$(function() {
|
||||||
|
$table.on('post-body.bs.table', function () {
|
||||||
|
loadDSpaceCounts();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function loadDSpaceCounts() {
|
||||||
|
$('.dspace-count-cell').each(function() {
|
||||||
|
var $cell = $(this);
|
||||||
|
var term = $cell.data('term');
|
||||||
|
var authType = $cell.data('authtype');
|
||||||
|
|
||||||
|
if (!term || $cell.data('loaded')) return;
|
||||||
|
$cell.data('loaded', true);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/Thesaurus/ajax/getDSpaceCount.php',
|
||||||
|
type: 'GET',
|
||||||
|
data: { authType: authType, query: term },
|
||||||
|
dataType: 'json',
|
||||||
|
timeout: 15000,
|
||||||
|
success: function(data) {
|
||||||
|
var count = (data && typeof data.count !== 'undefined') ? data.count : 0;
|
||||||
|
if (count === -1) {
|
||||||
|
$cell.html('<span class="text-muted" title="DSpace nicht erreichbar"><i class="fas fa-exclamation-triangle" style="font-size: 0.75rem;"></i></span>');
|
||||||
|
} else if (count > 0) {
|
||||||
|
$cell.html('<span class="badge" style="background-color: #1a3a5c;" title="' + count + ' Datensätze in DSpace">' + count + '</span>');
|
||||||
|
} else {
|
||||||
|
$cell.html('<span class="badge bg-danger" title="Keine Datensätze in DSpace">0</span>');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
$cell.html('<span class="text-muted" title="Fehler"><i class="fas fa-exclamation-triangle" style="font-size: 0.75rem;"></i></span>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
initTablePerson()
|
||||||
|
$('#locale').change(initTablePerson)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Globale searchRelation Funktion
|
||||||
|
window.searchRelation = function() {
|
||||||
|
const $input = $('#search_relation');
|
||||||
|
|
||||||
|
if ($input.length === 0) {
|
||||||
|
console.warn('⚠️ Element #search_relation nicht gefunden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entferne alte Event-Handler um Duplikate zu vermeiden
|
||||||
|
$input.off('input blur focus');
|
||||||
|
|
||||||
|
console.log('✓ Autocomplete initialisiert für:', $input.attr('id'));
|
||||||
|
|
||||||
|
let currentRequest = null;
|
||||||
|
|
||||||
|
$input.on('input', function() {
|
||||||
|
const query = $(this).val().trim();
|
||||||
|
console.log('🔍 Input erkannt:', query);
|
||||||
|
|
||||||
|
if (currentRequest) {
|
||||||
|
currentRequest.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.length < 2) {
|
||||||
|
console.log('⚠️ Zu kurz (minimum 2 Zeichen)');
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('📡 Sende AJAX Anfrage...');
|
||||||
|
|
||||||
|
currentRequest = $.ajax({
|
||||||
|
url: '/Thesaurus/ajax/Autocomplete.php',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
query: query,
|
||||||
|
authType: 'Person'
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(data) {
|
||||||
|
console.log('✅ Daten erhalten:', data.length, 'Einträge');
|
||||||
|
showAutocompleteResults(data, $input);
|
||||||
|
currentRequest = null;
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
if (status !== 'abort') {
|
||||||
|
console.error('❌ AJAX Fehler:', error);
|
||||||
|
}
|
||||||
|
currentRequest = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$input.on('blur', function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
$input.on('focus', function() {
|
||||||
|
const query = $(this).val().trim();
|
||||||
|
if (query.length >= 2) {
|
||||||
|
$(this).trigger('input');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(window).on('scroll resize', function() {
|
||||||
|
if ($('.autocomplete-dropdown').length > 0) {
|
||||||
|
const offset = $input.offset();
|
||||||
|
const inputHeight = $input.outerHeight();
|
||||||
|
const scrollTop = $(window).scrollTop();
|
||||||
|
const scrollLeft = $(window).scrollLeft();
|
||||||
|
|
||||||
|
$('.autocomplete-dropdown').css({
|
||||||
|
top: offset.top - scrollTop + inputHeight,
|
||||||
|
left: offset.left - scrollLeft
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function showAutocompleteResults(data, $input) {
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
console.log('⚠️ Keine Ergebnisse');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const $dropdown = $('<div class="autocomplete-dropdown"></div>');
|
||||||
|
|
||||||
|
data.forEach(function(item) {
|
||||||
|
const match = item.match(/^(.+?)\s*\(ID:\s*(\d+)\)$/);
|
||||||
|
const label = match ? match[1].trim() : item;
|
||||||
|
const value = match ? match[2] : item;
|
||||||
|
|
||||||
|
const $item = $('<a class="dropdown-item" href="#"></a>')
|
||||||
|
.text(label)
|
||||||
|
.data('value', value)
|
||||||
|
.data('label', label)
|
||||||
|
.data('fulltext', item);
|
||||||
|
|
||||||
|
$item.on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const selectedItem = {
|
||||||
|
label: $(this).data('label'),
|
||||||
|
value: $(this).data('value'),
|
||||||
|
fullText: $(this).data('fulltext')
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('✅ Ausgewählt:', selectedItem);
|
||||||
|
|
||||||
|
$input.val(selectedItem.label);
|
||||||
|
$input.data('selected-id', selectedItem.value);
|
||||||
|
$input.trigger('autocomplete:select', [selectedItem]);
|
||||||
|
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
|
||||||
|
// Speichere die Werte in Data-Attributen auf dem Button
|
||||||
|
const recordID = $('#ID').val();
|
||||||
|
console.log('🔍 recordID:', recordID);
|
||||||
|
console.log('🔍 selectedItem.value:', selectedItem.value);
|
||||||
|
|
||||||
|
$('#subject-anlegen').data('record-id', recordID);
|
||||||
|
$('#subject-anlegen').data('relation-id', selectedItem.value);
|
||||||
|
|
||||||
|
console.log('✅ Data-Attribute gesetzt auf #subject-anlegen');
|
||||||
|
});
|
||||||
|
|
||||||
|
$dropdown.append($item);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('body').append($dropdown);
|
||||||
|
|
||||||
|
const offset = $input.offset();
|
||||||
|
const inputHeight = $input.outerHeight();
|
||||||
|
const inputWidth = $input.outerWidth();
|
||||||
|
const scrollTop = $(window).scrollTop();
|
||||||
|
const scrollLeft = $(window).scrollLeft();
|
||||||
|
|
||||||
|
$dropdown.css({
|
||||||
|
position: 'fixed',
|
||||||
|
top: offset.top - scrollTop + inputHeight,
|
||||||
|
left: offset.left - scrollLeft,
|
||||||
|
width: inputWidth,
|
||||||
|
'min-width': inputWidth,
|
||||||
|
'z-index': 9999
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('📋 Dropdown angezeigt mit', data.length, 'Einträgen');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Automatische Initialisierung
|
||||||
|
$(document).ready(function() {
|
||||||
|
console.log('jQuery bereit');
|
||||||
|
|
||||||
|
$('.modal').on('shown.bs.modal', function () {
|
||||||
|
console.log('📂 Modal geöffnet');
|
||||||
|
if ($('#search_relation').length > 0) {
|
||||||
|
console.log('✅ search_relation gefunden, initialisiere...');
|
||||||
|
searchRelation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($('#search_relation').length > 0) {
|
||||||
|
console.log('✅ search_relation beim Laden gefunden, initialisiere...');
|
||||||
|
searchRelation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click-Handler für den "Hinzufügen" Button
|
||||||
|
$(document).on('click', '#subject-anlegen', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const recordID = $(this).data('record-id');
|
||||||
|
const relationID = $(this).data('relation-id');
|
||||||
|
|
||||||
|
console.log('🚀 Button geklickt - recordID:', recordID, '- relationID:', relationID);
|
||||||
|
|
||||||
|
if (recordID && relationID) {
|
||||||
|
CreateNewRelation(recordID, relationID, event);
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ recordID oder relationID fehlt!');
|
||||||
|
alert('Bitte zuerst einen Begriff aus der Autocomplete-Liste auswählen.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Bootstrap Modals -->
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/PersonsModal.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/PersonsModal.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/PersonDetailsModal.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/PersonDetailsModal.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/newPerson.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/newPerson.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/RelationsModal.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/RelationsModal.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Custom JS file -->
|
||||||
|
<script type="text/javascript" src="js/PersonScript.js"></script>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
include "templates/Footer.html";
|
||||||
|
?>
|
||||||
476
Publishers.php
Normal file
476
Publishers.php
Normal file
@ -0,0 +1,476 @@
|
|||||||
|
<?php
|
||||||
|
include "templates/Header.html";
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Hidden Fields -->
|
||||||
|
<input type="hidden" id="locale" value="de-DE">
|
||||||
|
<input type="hidden" id="ID" value="">
|
||||||
|
<input type="hidden" id="authType" value="Publisher">
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="main-content">
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<!-- Content Card -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="d-flex justify-between align-center mb-20">
|
||||||
|
<h1 class="card-title" style="margin: 0;">📚 Verlagsverwaltung</h1>
|
||||||
|
<button id="add" class="btn btn-primary" onclick="newPublishersShow()">
|
||||||
|
<i class="fas fa-plus"></i> Neuer Verlag
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bootstrap Table -->
|
||||||
|
<table
|
||||||
|
id="table"
|
||||||
|
data-toolbar="#toolbar"
|
||||||
|
data-search="true"
|
||||||
|
data-query-params-type="limit"
|
||||||
|
data-show-refresh="false"
|
||||||
|
data-show-toggle="false"
|
||||||
|
data-show-fullscreen="false"
|
||||||
|
data-show-columns="false"
|
||||||
|
data-show-columns-toggle-all="false"
|
||||||
|
data-detail-view="false"
|
||||||
|
data-show-export="false"
|
||||||
|
data-click-to-select="false"
|
||||||
|
data-trim-on-search="false"
|
||||||
|
data-minimum-count-columns="2"
|
||||||
|
data-show-pagination-switch="false"
|
||||||
|
data-page-size="25"
|
||||||
|
data-pagination="true"
|
||||||
|
data-id-field="ID"
|
||||||
|
data-page-list="[10, 25, 50, 100, all]"
|
||||||
|
data-show-footer="false"
|
||||||
|
data-side-pagination="server"
|
||||||
|
data-server-sort="false"
|
||||||
|
data-url="/Thesaurus/ajax/getPublisherData.php"
|
||||||
|
data-response-handler="responseHandler">
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Records Content -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="records_content"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var $table = $('#table')
|
||||||
|
var $remove = $('#remove')
|
||||||
|
var selections = []
|
||||||
|
|
||||||
|
function getIdSelections() {
|
||||||
|
return $.map($table.bootstrapTable('getSelections'), function (row) {
|
||||||
|
return row.ID
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function responseHandler(res) {
|
||||||
|
$.each(res.rows, function (i, row) {
|
||||||
|
row.state = $.inArray(row.ID, selections) !== -1
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
function detailFormatter(index, row) {
|
||||||
|
var html = []
|
||||||
|
$.each(row, function (key, value) {
|
||||||
|
if (key !== "ID" && key !== "state" && key != "Text" && key != "Descriptor") {
|
||||||
|
if (value !== null && value !== undefined && String(value).length > 0)
|
||||||
|
html.push('<p><b>' + key + ':</b> ' + value + '</p>')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return html.join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyFormatter(value, row, index) {
|
||||||
|
if (row.Descriptor === true || row.Descriptor === 'true' || row.Descriptor === 1) {
|
||||||
|
return '<button class="btn btn-sm btn-outline-secondary copy-btn" style="padding: 2px 6px; line-height: 1;" ' +
|
||||||
|
'onclick="event.stopPropagation(); copyTermToClipboard(\'' + row.Text.replace(/'/g, "\\'") + '\', this)" ' +
|
||||||
|
'title="Begriff kopieren">' +
|
||||||
|
'<i class="fas fa-copy" style="font-size: 0.75rem;"></i>' +
|
||||||
|
'</button>';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function operateFormatter(value, row, index) {
|
||||||
|
return [
|
||||||
|
'<div class="table-actions">',
|
||||||
|
'<a class="relations btn btn-sm btn-secondary" href="javascript:void(0)" title="Relationen anzeigen">',
|
||||||
|
'<i class="fas fa-table"></i>',
|
||||||
|
'</a>',
|
||||||
|
'<a class="details btn btn-sm btn-info" href="javascript:void(0)" title="Semantisches Netz">',
|
||||||
|
'<i class="fas fa-project-diagram"></i>',
|
||||||
|
'</a>',
|
||||||
|
'<a class="modify btn btn-sm btn-primary" href="javascript:void(0)" title="Bearbeiten">',
|
||||||
|
'<i class="fas fa-edit"></i>',
|
||||||
|
'</a>',
|
||||||
|
'<a class="remove btn btn-sm btn-danger" href="javascript:void(0)" title="Löschen">',
|
||||||
|
'<i class="fas fa-trash"></i>',
|
||||||
|
'</a>',
|
||||||
|
'</div>'
|
||||||
|
].join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
window.operateEvents = {
|
||||||
|
'click .relations': function (e, value, row, index) {
|
||||||
|
ShowRelationsModal(row.ID, 'Publisher')
|
||||||
|
},
|
||||||
|
'click .details': function (e, value, row, index) {
|
||||||
|
ShowModalDetails(row.ID)
|
||||||
|
},
|
||||||
|
'click .modify': function (e, value, row, index) {
|
||||||
|
ModifyTerm([row.ID]),
|
||||||
|
$table.bootstrapTable('refresh')
|
||||||
|
},
|
||||||
|
'click .remove': function (e, value, row, index) {
|
||||||
|
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$table.bootstrapTable('remove', {
|
||||||
|
field: 'ID',
|
||||||
|
values: [row.ID]
|
||||||
|
})
|
||||||
|
DeleteTerm([row.ID])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function totalTextFormatter(data) {
|
||||||
|
return 'Total'
|
||||||
|
}
|
||||||
|
|
||||||
|
function totalNameFormatter(data) {
|
||||||
|
return data.length
|
||||||
|
}
|
||||||
|
|
||||||
|
function initTablePublisher() {
|
||||||
|
$table.bootstrapTable('destroy').bootstrapTable({
|
||||||
|
locale: $('#locale').val(),
|
||||||
|
columns: [
|
||||||
|
[{
|
||||||
|
title: 'Verlage',
|
||||||
|
colspan: 3,
|
||||||
|
align: 'center'
|
||||||
|
}],
|
||||||
|
[{
|
||||||
|
field: 'copy',
|
||||||
|
title: '',
|
||||||
|
align: 'center',
|
||||||
|
width: 40,
|
||||||
|
clickToSelect: false,
|
||||||
|
formatter: copyFormatter
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'Text',
|
||||||
|
title: 'Name',
|
||||||
|
sortable: true,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'operate',
|
||||||
|
title: 'Funktionen',
|
||||||
|
align: 'center',
|
||||||
|
width: 190,
|
||||||
|
clickToSelect: false,
|
||||||
|
events: window.operateEvents,
|
||||||
|
formatter: operateFormatter
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
$table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table',
|
||||||
|
function () {
|
||||||
|
$remove.prop('disabled', !$table.bootstrapTable('getSelections').length)
|
||||||
|
selections = getIdSelections()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
$table.on('all.bs.table', function (e, name, args) {
|
||||||
|
console.log(name, args)
|
||||||
|
})
|
||||||
|
|
||||||
|
$remove.click(function () {
|
||||||
|
var ids = getIdSelections()
|
||||||
|
$table.bootstrapTable('remove', {
|
||||||
|
field: 'ID',
|
||||||
|
values: ids
|
||||||
|
})
|
||||||
|
$remove.prop('disabled', true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReadDetails(RecordID) {
|
||||||
|
$('#ID').val(RecordID);
|
||||||
|
console.log('✅ RecordID gesetzt in #ID:', RecordID);
|
||||||
|
$('#search_relation').data('record-id', RecordID);
|
||||||
|
getData(1, 1, RecordID);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData(number, size, idvalue) {
|
||||||
|
var Type = document.getElementById("authType").value;
|
||||||
|
if (typeof idvalue == "undefined") {
|
||||||
|
var search = $("div.bootstrap-table > div.fixed-table-toolbar > div.search > input.form-control").val();
|
||||||
|
var id = 0
|
||||||
|
} else {
|
||||||
|
var id = idvalue
|
||||||
|
var search = ''
|
||||||
|
}
|
||||||
|
$.get('/Thesaurus/ajax/getPublisherData.php', {
|
||||||
|
id: id,
|
||||||
|
search: search,
|
||||||
|
offset: (number - 1) * size,
|
||||||
|
authType: Type,
|
||||||
|
limit: size
|
||||||
|
}, function (res) {
|
||||||
|
$table.bootstrapTable('load', res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
$table.on('page-change.bs.table', function (e, number, size, id) {
|
||||||
|
getData(number, size, id)
|
||||||
|
})
|
||||||
|
var options = $table.bootstrapTable('getOptions')
|
||||||
|
getData(options.pageNumber, options.pageSize)
|
||||||
|
})
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
initTablePublisher()
|
||||||
|
$('#locale').change(initTablePublisher)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Globale searchRelation Funktion
|
||||||
|
window.searchRelation = function() {
|
||||||
|
const $input = $('#search_relation');
|
||||||
|
|
||||||
|
if ($input.length === 0) {
|
||||||
|
console.warn('⚠️ Element #search_relation nicht gefunden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entferne alte Event-Handler um Duplikate zu vermeiden
|
||||||
|
$input.off('input blur focus');
|
||||||
|
|
||||||
|
console.log('✓ Autocomplete initialisiert für:', $input.attr('id'));
|
||||||
|
|
||||||
|
let currentRequest = null;
|
||||||
|
|
||||||
|
$input.on('input', function() {
|
||||||
|
const query = $(this).val().trim();
|
||||||
|
console.log('🔍 Input erkannt:', query);
|
||||||
|
|
||||||
|
if (currentRequest) {
|
||||||
|
currentRequest.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.length < 2) {
|
||||||
|
console.log('⚠️ Zu kurz (minimum 2 Zeichen)');
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('📡 Sende AJAX Anfrage...');
|
||||||
|
|
||||||
|
currentRequest = $.ajax({
|
||||||
|
url: '/Thesaurus/ajax/Autocomplete.php',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
query: query,
|
||||||
|
authType: 'Publisher'
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(data) {
|
||||||
|
console.log('✅ Daten erhalten:', data.length, 'Einträge');
|
||||||
|
showAutocompleteResults(data, $input);
|
||||||
|
currentRequest = null;
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
if (status !== 'abort') {
|
||||||
|
console.error('❌ AJAX Fehler:', error);
|
||||||
|
}
|
||||||
|
currentRequest = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$input.on('blur', function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
$input.on('focus', function() {
|
||||||
|
const query = $(this).val().trim();
|
||||||
|
if (query.length >= 2) {
|
||||||
|
$(this).trigger('input');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(window).on('scroll resize', function() {
|
||||||
|
if ($('.autocomplete-dropdown').length > 0) {
|
||||||
|
const offset = $input.offset();
|
||||||
|
const inputHeight = $input.outerHeight();
|
||||||
|
const scrollTop = $(window).scrollTop();
|
||||||
|
const scrollLeft = $(window).scrollLeft();
|
||||||
|
|
||||||
|
$('.autocomplete-dropdown').css({
|
||||||
|
top: offset.top - scrollTop + inputHeight,
|
||||||
|
left: offset.left - scrollLeft
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function showAutocompleteResults(data, $input) {
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
console.log('⚠️ Keine Ergebnisse');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const $dropdown = $('<div class="autocomplete-dropdown"></div>');
|
||||||
|
|
||||||
|
data.forEach(function(item) {
|
||||||
|
const match = item.match(/^(.+?)\s*\(ID:\s*(\d+)\)$/);
|
||||||
|
const label = match ? match[1].trim() : item;
|
||||||
|
const value = match ? match[2] : item;
|
||||||
|
|
||||||
|
const $item = $('<a class="dropdown-item" href="#"></a>')
|
||||||
|
.text(label)
|
||||||
|
.data('value', value)
|
||||||
|
.data('label', label)
|
||||||
|
.data('fulltext', item);
|
||||||
|
|
||||||
|
$item.on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const selectedItem = {
|
||||||
|
label: $(this).data('label'),
|
||||||
|
value: $(this).data('value'),
|
||||||
|
fullText: $(this).data('fulltext')
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('✅ Ausgewählt:', selectedItem);
|
||||||
|
|
||||||
|
$input.val(selectedItem.label);
|
||||||
|
$input.data('selected-id', selectedItem.value);
|
||||||
|
$input.trigger('autocomplete:select', [selectedItem]);
|
||||||
|
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
|
||||||
|
// Speichere die Werte in Data-Attributen auf dem Button
|
||||||
|
const recordID = $('#ID').val();
|
||||||
|
console.log('🔍 recordID:', recordID);
|
||||||
|
console.log('🔍 selectedItem.value:', selectedItem.value);
|
||||||
|
|
||||||
|
$('#publisher-anlegen').data('record-id', recordID);
|
||||||
|
$('#publisher-anlegen').data('relation-id', selectedItem.value);
|
||||||
|
|
||||||
|
console.log('✅ Data-Attribute gesetzt auf #publisher-anlegen');
|
||||||
|
});
|
||||||
|
|
||||||
|
$dropdown.append($item);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('body').append($dropdown);
|
||||||
|
|
||||||
|
const offset = $input.offset();
|
||||||
|
const inputHeight = $input.outerHeight();
|
||||||
|
const inputWidth = $input.outerWidth();
|
||||||
|
const scrollTop = $(window).scrollTop();
|
||||||
|
const scrollLeft = $(window).scrollLeft();
|
||||||
|
|
||||||
|
$dropdown.css({
|
||||||
|
position: 'fixed',
|
||||||
|
top: offset.top - scrollTop + inputHeight,
|
||||||
|
left: offset.left - scrollLeft,
|
||||||
|
width: inputWidth,
|
||||||
|
'min-width': inputWidth,
|
||||||
|
'z-index': 9999
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('📋 Dropdown angezeigt mit', data.length, 'Einträgen');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Automatische Initialisierung
|
||||||
|
$(document).ready(function() {
|
||||||
|
console.log('jQuery bereit');
|
||||||
|
|
||||||
|
$('.modal').on('shown.bs.modal', function () {
|
||||||
|
console.log('📂 Modal geöffnet');
|
||||||
|
if ($('#search_relation').length > 0) {
|
||||||
|
console.log('✅ search_relation gefunden, initialisiere...');
|
||||||
|
searchRelation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($('#search_relation').length > 0) {
|
||||||
|
console.log('✅ search_relation beim Laden gefunden, initialisiere...');
|
||||||
|
searchRelation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click-Handler für den "Hinzufügen" Button
|
||||||
|
$(document).on('click', '#publisher-anlegen', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const recordID = $(this).data('record-id');
|
||||||
|
const relationID = $(this).data('relation-id');
|
||||||
|
|
||||||
|
console.log('🚀 Button geklickt - recordID:', recordID, '- relationID:', relationID);
|
||||||
|
|
||||||
|
if (recordID && relationID) {
|
||||||
|
CreateNewRelation(recordID, relationID, event);
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ recordID oder relationID fehlt!');
|
||||||
|
alert('Bitte zuerst einen Begriff aus der Autocomplete-Liste auswählen.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Bootstrap Modals -->
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/PublishersModal.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/PublishersModal.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/PublisherDetailsModal.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/PublisherDetailsModal.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/newPublisher.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/newPublisher.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/RelationsModal.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/RelationsModal.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Custom JS file -->
|
||||||
|
<script type="text/javascript" src="js/PublisherScript.js"></script>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
include "templates/Footer.html";
|
||||||
|
?>
|
||||||
22
README.md
Normal file
22
README.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Thesaurus
|
||||||
|
|
||||||
|
Webbasierte Thesaurus-Verwaltung für Schlagwörter, Personen, Körperschaften, Verlage und Klassifikationen.
|
||||||
|
|
||||||
|
## Voraussetzungen
|
||||||
|
|
||||||
|
- PHP 7.4 oder höher
|
||||||
|
- MySQL/MariaDB
|
||||||
|
- Apache Webserver
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Repository klonen
|
||||||
|
2. `ajax/db_connection.php.example` kopieren und umbenennen:
|
||||||
|
```bash
|
||||||
|
cp ajax/db_connection.php.example ajax/db_connection.php
|
||||||
|
```
|
||||||
|
3. Zugangsdaten in `ajax/db_connection.php` eintragen
|
||||||
|
4. Abhängigkeiten installieren:
|
||||||
|
```bash
|
||||||
|
bash setup-libs.sh
|
||||||
|
```
|
||||||
545
Subjects.php
Normal file
545
Subjects.php
Normal file
@ -0,0 +1,545 @@
|
|||||||
|
<?php
|
||||||
|
include "templates/Header.html";
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Hidden Fields -->
|
||||||
|
<input type="hidden" id="locale" value="de-DE">
|
||||||
|
<input type="hidden" id="ID" value="">
|
||||||
|
<input type="hidden" id="authType" value="Subject">
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="main-content">
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<!-- Content Card -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="d-flex justify-between align-center mb-20">
|
||||||
|
<h1 class="card-title" style="margin: 0;">🏷️ Schlagwortverwaltung</h1>
|
||||||
|
<button id="add" class="btn btn-primary" onclick="newSubjectShow()">
|
||||||
|
<i class="fas fa-plus"></i> Neuer Deskriptor
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bootstrap Table -->
|
||||||
|
<table
|
||||||
|
id="table"
|
||||||
|
data-toolbar="#toolbar"
|
||||||
|
data-search="true"
|
||||||
|
data-query-params-type="limit"
|
||||||
|
data-show-refresh="false"
|
||||||
|
data-show-toggle="false"
|
||||||
|
data-show-fullscreen="false"
|
||||||
|
data-show-columns="false"
|
||||||
|
data-show-columns-toggle-all="false"
|
||||||
|
data-detail-view="false"
|
||||||
|
data-show-export="false"
|
||||||
|
data-click-to-select="false"
|
||||||
|
data-trim-on-search="false"
|
||||||
|
data-minimum-count-columns="2"
|
||||||
|
data-show-pagination-switch="false"
|
||||||
|
data-page-size="25"
|
||||||
|
data-pagination="true"
|
||||||
|
data-id-field="ID"
|
||||||
|
data-page-list="[10, 25, 50, 100, all]"
|
||||||
|
data-show-footer="true"
|
||||||
|
data-side-pagination="server"
|
||||||
|
data-server-sort="true"
|
||||||
|
data-url="/Thesaurus/ajax/getSubjectData.php"
|
||||||
|
data-response-handler="responseHandler">
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Records Content -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="records_content"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var $table = $('#table')
|
||||||
|
var $remove = $('#remove')
|
||||||
|
var selections = []
|
||||||
|
|
||||||
|
function getIdSelections() {
|
||||||
|
return $.map($table.bootstrapTable('getSelections'), function (row) {
|
||||||
|
return row.ID
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function responseHandler(res) {
|
||||||
|
$.each(res.rows, function (i, row) {
|
||||||
|
row.state = $.inArray(row.ID, selections) !== -1
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
function detailFormatter(index, row) {
|
||||||
|
var html = []
|
||||||
|
$.each(row, function (key, value) {
|
||||||
|
if (key !== "ID" && key !== "state" && key != "Text" && key != "Descriptor") {
|
||||||
|
if (value !== null && value !== undefined && String(value).length > 0)
|
||||||
|
html.push('<p><b>' + key + ':</b> ' + value + '</p>')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return html.join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyFormatter(value, row, index) {
|
||||||
|
if (row.Descriptor === true || row.Descriptor === 'true' || row.Descriptor === 1) {
|
||||||
|
return '<button class="btn btn-sm btn-outline-secondary copy-btn" style="padding: 2px 6px; line-height: 1;" ' +
|
||||||
|
'onclick="event.stopPropagation(); copyTermToClipboard(\'' + row.Text.replace(/'/g, "\\'") + '\', this)" ' +
|
||||||
|
'title="Begriff kopieren">' +
|
||||||
|
'<i class="fas fa-copy" style="font-size: 0.75rem;"></i>' +
|
||||||
|
'</button>';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function dspaceCountFormatter(value, row, index) {
|
||||||
|
if (row.Descriptor !== true && row.Descriptor !== 'true' && row.Descriptor !== 1) {
|
||||||
|
return '<span class="text-muted" title="Kein Deskriptor">–</span>';
|
||||||
|
}
|
||||||
|
return '<span class="dspace-count-cell" data-term="' + row.Text.replace(/"/g, '"') + '" data-authtype="Subject">' +
|
||||||
|
'<span class="spinner-border spinner-border-sm text-muted" style="width: 12px; height: 12px;" role="status"></span>' +
|
||||||
|
'</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function operateFormatter(value, row, index) {
|
||||||
|
return [
|
||||||
|
'<div class="table-actions">',
|
||||||
|
'<a class="relations btn btn-sm btn-secondary" href="javascript:void(0)" title="Relationen anzeigen">',
|
||||||
|
'<i class="fas fa-table"></i>',
|
||||||
|
'</a>',
|
||||||
|
'<a class="details btn btn-sm btn-info" href="javascript:void(0)" title="Semantisches Netz">',
|
||||||
|
'<i class="fas fa-project-diagram"></i>',
|
||||||
|
'</a>',
|
||||||
|
'<a class="modify btn btn-sm btn-primary" href="javascript:void(0)" title="Bearbeiten">',
|
||||||
|
'<i class="fas fa-edit"></i>',
|
||||||
|
'</a>',
|
||||||
|
'<a class="remove btn btn-sm btn-danger" href="javascript:void(0)" title="Löschen">',
|
||||||
|
'<i class="fas fa-trash"></i>',
|
||||||
|
'</a>',
|
||||||
|
'</div>'
|
||||||
|
].join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
window.operateEvents = {
|
||||||
|
'click .relations': function (e, value, row, index) {
|
||||||
|
ShowRelationsModal(row.ID, 'Subject')
|
||||||
|
},
|
||||||
|
'click .details': function (e, value, row, index) {
|
||||||
|
ShowModalDetails(row.ID)
|
||||||
|
},
|
||||||
|
'click .modify': function (e, value, row, index) {
|
||||||
|
ModifyTerm([row.ID]),
|
||||||
|
$table.bootstrapTable('refresh')
|
||||||
|
},
|
||||||
|
'click .remove': function (e, value, row, index) {
|
||||||
|
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$table.bootstrapTable('remove', {
|
||||||
|
field: 'ID',
|
||||||
|
values: [row.ID]
|
||||||
|
})
|
||||||
|
DeleteTerm([row.ID])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function totalTextFormatter(data) {
|
||||||
|
return 'Total'
|
||||||
|
}
|
||||||
|
|
||||||
|
function totalNameFormatter(data) {
|
||||||
|
return data.length
|
||||||
|
}
|
||||||
|
|
||||||
|
function initTableSubject() {
|
||||||
|
$table.bootstrapTable('destroy').bootstrapTable({
|
||||||
|
locale: $('#locale').val(),
|
||||||
|
columns: [
|
||||||
|
[{
|
||||||
|
title: 'Schlagworte',
|
||||||
|
colspan: 4,
|
||||||
|
align: 'center'
|
||||||
|
}],
|
||||||
|
[{
|
||||||
|
field: 'copy',
|
||||||
|
title: '',
|
||||||
|
align: 'center',
|
||||||
|
width: 40,
|
||||||
|
clickToSelect: false,
|
||||||
|
formatter: copyFormatter
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'Text',
|
||||||
|
title: 'Begriff',
|
||||||
|
sortable: true,
|
||||||
|
align: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'DSpaceCount',
|
||||||
|
title: 'DSpace',
|
||||||
|
align: 'center',
|
||||||
|
width: 80,
|
||||||
|
clickToSelect: false,
|
||||||
|
formatter: dspaceCountFormatter
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'operate',
|
||||||
|
title: 'Funktionen',
|
||||||
|
align: 'center',
|
||||||
|
width: 190,
|
||||||
|
clickToSelect: false,
|
||||||
|
events: window.operateEvents,
|
||||||
|
formatter: operateFormatter
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
$table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table',
|
||||||
|
function () {
|
||||||
|
$remove.prop('disabled', !$table.bootstrapTable('getSelections').length)
|
||||||
|
selections = getIdSelections()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
$table.on('all.bs.table', function (e, name, args) {
|
||||||
|
console.log(name, args)
|
||||||
|
})
|
||||||
|
|
||||||
|
$remove.click(function () {
|
||||||
|
var ids = getIdSelections()
|
||||||
|
$table.bootstrapTable('remove', {
|
||||||
|
field: 'ID',
|
||||||
|
values: ids
|
||||||
|
})
|
||||||
|
$remove.prop('disabled', true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReadDetails(RecordID) {
|
||||||
|
$('#ID').val(RecordID);
|
||||||
|
console.log('✅ RecordID gesetzt in #ID:', RecordID);
|
||||||
|
$('#search_relation').data('record-id', RecordID);
|
||||||
|
getData(1, 1, RecordID);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getData(number, size, idvalue) {
|
||||||
|
var Type = document.getElementById("authType").value;
|
||||||
|
if (typeof idvalue == "undefined") {
|
||||||
|
var search = $("div.bootstrap-table > div.fixed-table-toolbar > div.search > input.form-control").val();
|
||||||
|
var id = 0
|
||||||
|
} else {
|
||||||
|
var id = idvalue
|
||||||
|
var search = ''
|
||||||
|
}
|
||||||
|
$.get('/Thesaurus/ajax/getSubjectData.php', {
|
||||||
|
id: id,
|
||||||
|
search: search,
|
||||||
|
offset: (number - 1) * size,
|
||||||
|
authType: Type,
|
||||||
|
limit: size
|
||||||
|
}, function (res) {
|
||||||
|
$table.bootstrapTable('load', res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
$table.on('page-change.bs.table', function (e, number, size, id) {
|
||||||
|
getData(number, size, id)
|
||||||
|
})
|
||||||
|
var options = $table.bootstrapTable('getOptions')
|
||||||
|
getData(options.pageNumber, options.pageSize)
|
||||||
|
})
|
||||||
|
|
||||||
|
// DSpace-Counts asynchron laden nach dem Rendern der Tabelle
|
||||||
|
$(function() {
|
||||||
|
$table.on('post-body.bs.table', function () {
|
||||||
|
loadDSpaceCounts();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function loadDSpaceCounts() {
|
||||||
|
$('.dspace-count-cell').each(function() {
|
||||||
|
var $cell = $(this);
|
||||||
|
var term = $cell.data('term');
|
||||||
|
var authType = $cell.data('authtype');
|
||||||
|
|
||||||
|
if (!term || $cell.data('loaded')) return;
|
||||||
|
$cell.data('loaded', true);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/Thesaurus/ajax/getDSpaceCount.php',
|
||||||
|
type: 'GET',
|
||||||
|
data: { authType: authType, query: term },
|
||||||
|
dataType: 'json',
|
||||||
|
timeout: 15000,
|
||||||
|
success: function(data) {
|
||||||
|
var count = (data && typeof data.count !== 'undefined') ? data.count : 0;
|
||||||
|
if (count === -1) {
|
||||||
|
$cell.html('<span class="text-muted" title="DSpace nicht erreichbar"><i class="fas fa-exclamation-triangle" style="font-size: 0.75rem;"></i></span>');
|
||||||
|
} else if (count > 0) {
|
||||||
|
$cell.html('<span class="badge" style="background-color: #1a3a5c;" title="' + count + ' Datensätze in DSpace">' + count + '</span>');
|
||||||
|
} else {
|
||||||
|
$cell.html('<span class="badge bg-danger" title="Keine Datensätze in DSpace">0</span>');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
$cell.html('<span class="text-muted" title="Fehler"><i class="fas fa-exclamation-triangle" style="font-size: 0.75rem;"></i></span>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
initTableSubject()
|
||||||
|
$('#locale').change(initTableSubject)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Custom JS file - muss vor dem Autocomplete-Script geladen werden -->
|
||||||
|
<script type="text/javascript" src="js/SubjectScript.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Globale searchRelation Funktion
|
||||||
|
window.searchRelation = function() {
|
||||||
|
const $input = $('#search_relation');
|
||||||
|
|
||||||
|
if ($input.length === 0) {
|
||||||
|
console.warn('⚠️ Element #search_relation nicht gefunden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entferne alte Event-Handler um Duplikate zu vermeiden
|
||||||
|
$input.off('input blur focus');
|
||||||
|
|
||||||
|
console.log('✓ Autocomplete initialisiert für:', $input.attr('id'));
|
||||||
|
|
||||||
|
let currentRequest = null;
|
||||||
|
|
||||||
|
$input.on('input', function() {
|
||||||
|
const query = $(this).val().trim();
|
||||||
|
console.log('🔍 Input erkannt:', query);
|
||||||
|
|
||||||
|
if (currentRequest) {
|
||||||
|
currentRequest.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.length < 2) {
|
||||||
|
console.log('⚠️ Zu kurz (minimum 2 Zeichen)');
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('📡 Sende AJAX Anfrage...');
|
||||||
|
|
||||||
|
currentRequest = $.ajax({
|
||||||
|
url: '/Thesaurus/ajax/Autocomplete.php',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
query: query,
|
||||||
|
authType: 'Subject'
|
||||||
|
},
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(data) {
|
||||||
|
console.log('✅ Daten erhalten:', data.length, 'Einträge');
|
||||||
|
showAutocompleteResults(data, $input);
|
||||||
|
currentRequest = null;
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
if (status !== 'abort') {
|
||||||
|
console.error('❌ AJAX Fehler:', error);
|
||||||
|
}
|
||||||
|
currentRequest = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$input.on('blur', function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
|
|
||||||
|
$input.on('focus', function() {
|
||||||
|
const query = $(this).val().trim();
|
||||||
|
if (query.length >= 2) {
|
||||||
|
$(this).trigger('input');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(window).on('scroll resize', function() {
|
||||||
|
if ($('.autocomplete-dropdown').length > 0) {
|
||||||
|
const offset = $input.offset();
|
||||||
|
const inputHeight = $input.outerHeight();
|
||||||
|
const scrollTop = $(window).scrollTop();
|
||||||
|
const scrollLeft = $(window).scrollLeft();
|
||||||
|
|
||||||
|
$('.autocomplete-dropdown').css({
|
||||||
|
top: offset.top - scrollTop + inputHeight,
|
||||||
|
left: offset.left - scrollLeft
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function showAutocompleteResults(data, $input) {
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
console.log('⚠️ Keine Ergebnisse');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const $dropdown = $('<div class="autocomplete-dropdown"></div>');
|
||||||
|
|
||||||
|
data.forEach(function(item) {
|
||||||
|
const match = item.match(/^(.+?)\s*\(ID:\s*(\d+)\)$/);
|
||||||
|
const label = match ? match[1].trim() : item;
|
||||||
|
const value = match ? match[2] : item;
|
||||||
|
|
||||||
|
const $item = $('<a class="dropdown-item" href="#"></a>')
|
||||||
|
.text(label)
|
||||||
|
.data('value', value)
|
||||||
|
.data('label', label)
|
||||||
|
.data('fulltext', item);
|
||||||
|
|
||||||
|
$item.on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const selectedItem = {
|
||||||
|
label: $(this).data('label'),
|
||||||
|
value: $(this).data('value'),
|
||||||
|
fullText: $(this).data('fulltext')
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('✅ Ausgewählt:', selectedItem);
|
||||||
|
|
||||||
|
$input.val(selectedItem.label);
|
||||||
|
$input.data('selected-id', selectedItem.value);
|
||||||
|
$input.trigger('autocomplete:select', [selectedItem]);
|
||||||
|
|
||||||
|
$('.autocomplete-dropdown').remove();
|
||||||
|
|
||||||
|
// Speichere die Werte in Data-Attributen auf dem Button
|
||||||
|
const recordID = $('#ID').val();
|
||||||
|
const recordText = $('#new_term').val(); // Text des aktuellen Schlagworts
|
||||||
|
console.log('🔍 recordID:', recordID);
|
||||||
|
console.log('🔍 recordText:', recordText);
|
||||||
|
console.log('🔍 selectedItem.value:', selectedItem.value);
|
||||||
|
console.log('🔍 selectedItem.label:', selectedItem.label);
|
||||||
|
|
||||||
|
$('#subject-anlegen').data('record-id', recordID);
|
||||||
|
$('#subject-anlegen').data('record-text', recordText);
|
||||||
|
$('#subject-anlegen').data('relation-id', selectedItem.value);
|
||||||
|
$('#subject-anlegen').data('relation-text', selectedItem.label);
|
||||||
|
|
||||||
|
console.log('✅ Data-Attribute gesetzt auf #subject-anlegen');
|
||||||
|
});
|
||||||
|
|
||||||
|
$dropdown.append($item);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('body').append($dropdown);
|
||||||
|
|
||||||
|
const offset = $input.offset();
|
||||||
|
const inputHeight = $input.outerHeight();
|
||||||
|
const inputWidth = $input.outerWidth();
|
||||||
|
const scrollTop = $(window).scrollTop();
|
||||||
|
const scrollLeft = $(window).scrollLeft();
|
||||||
|
|
||||||
|
$dropdown.css({
|
||||||
|
position: 'fixed',
|
||||||
|
top: offset.top - scrollTop + inputHeight,
|
||||||
|
left: offset.left - scrollLeft,
|
||||||
|
width: inputWidth,
|
||||||
|
'min-width': inputWidth,
|
||||||
|
'z-index': 9999
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('📋 Dropdown angezeigt mit', data.length, 'Einträgen');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Automatische Initialisierung
|
||||||
|
$(document).ready(function() {
|
||||||
|
console.log('jQuery bereit');
|
||||||
|
|
||||||
|
$('.modal').on('shown.bs.modal', function () {
|
||||||
|
console.log('📂 Modal geöffnet');
|
||||||
|
if ($('#search_relation').length > 0) {
|
||||||
|
console.log('✅ search_relation gefunden, initialisiere...');
|
||||||
|
searchRelation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($('#search_relation').length > 0) {
|
||||||
|
console.log('✅ search_relation beim Laden gefunden, initialisiere...');
|
||||||
|
searchRelation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click-Handler für den "Hinzufügen" Button
|
||||||
|
$(document).on('click', '#subject-anlegen', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const recordID = $(this).data('record-id');
|
||||||
|
const recordText = $(this).data('record-text');
|
||||||
|
const relationID = $(this).data('relation-id');
|
||||||
|
const relationText = $(this).data('relation-text');
|
||||||
|
const relationType = $('#new_relationtype').val();
|
||||||
|
|
||||||
|
console.log('🚀 Button geklickt - recordID:', recordID, '- relationID:', relationID, '- relationType:', relationType);
|
||||||
|
|
||||||
|
if (recordID && relationID) {
|
||||||
|
// Bei Synonym-Typ: andere Speicherfunktion verwenden
|
||||||
|
if (relationType === 'SYN') {
|
||||||
|
CreateNewSynonym(recordText, relationText, recordID, event);
|
||||||
|
} else {
|
||||||
|
CreateNewRelation(recordID, relationID, event);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ recordID oder relationID fehlt!');
|
||||||
|
alert('Bitte zuerst einen Begriff aus der Autocomplete-Liste auswählen.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Bootstrap Modals -->
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/SubjectsModal.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/SubjectsModal.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/SubjectDetailsModal.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/SubjectDetailsModal.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/newSubject.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/newSubject.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$fp = fopen("templates/RelationsModal.tpl", "r");
|
||||||
|
if ($fp) {
|
||||||
|
echo (fread($fp, filesize("templates/RelationsModal.tpl")));
|
||||||
|
fclose($fp);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
include "templates/Footer.html";
|
||||||
|
?>
|
||||||
671
Treeview.php
Normal file
671
Treeview.php
Normal file
@ -0,0 +1,671 @@
|
|||||||
|
<?php
|
||||||
|
include "templates/Header.html";
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!-- Hidden Fields -->
|
||||||
|
<input type="hidden" id="locale" value="de-DE">
|
||||||
|
<input type="hidden" id="ID" value="">
|
||||||
|
<input type="hidden" id="authType" value="Classification">
|
||||||
|
|
||||||
|
<!-- jsTree CSS -->
|
||||||
|
<link rel="stylesheet" href="/libs/jstree/current/themes/default/style.min.css" onerror="this.onerror=null; this.href='https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.16/themes/default/style.min.css';" />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Treeview Container */
|
||||||
|
.treeview-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
min-height: 600px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-panel {
|
||||||
|
flex: 3;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-panel {
|
||||||
|
flex: 2;
|
||||||
|
min-width: 0;
|
||||||
|
position: sticky;
|
||||||
|
top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tree Styling */
|
||||||
|
#classification-tree {
|
||||||
|
padding: 15px;
|
||||||
|
background: var(--bg-light);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
min-height: 500px;
|
||||||
|
max-height: 75vh;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* jsTree Anpassungen für BIBB-Blau - verstärkt */
|
||||||
|
.jstree-default .jstree-clicked,
|
||||||
|
.jstree-default .jstree-wholerow-clicked {
|
||||||
|
background: #1a3a5c !important;
|
||||||
|
color: white !important;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jstree-default .jstree-clicked .jstree-icon,
|
||||||
|
.jstree-default .jstree-wholerow-clicked + .jstree-anchor .jstree-icon {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jstree-default .jstree-hovered,
|
||||||
|
.jstree-default .jstree-wholerow-hovered {
|
||||||
|
background: #e9ecef !important;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jstree-default .jstree-anchor {
|
||||||
|
color: var(--text-primary);
|
||||||
|
padding: 4px 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: none;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jstree-default .jstree-icon {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Detail Panel */
|
||||||
|
.detail-card {
|
||||||
|
background: var(--bg-light);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
padding: 20px;
|
||||||
|
min-height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-card h4 {
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-row {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-muted);
|
||||||
|
width: 120px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
color: var(--text-primary);
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-selection {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding: 50px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-selection i {
|
||||||
|
font-size: 48px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: block;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toolbar */
|
||||||
|
.tree-toolbar {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-toolbar .btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search */
|
||||||
|
.tree-search {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-search input {
|
||||||
|
width: 100%;
|
||||||
|
padding-left: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-search i.fa-search {
|
||||||
|
position: absolute;
|
||||||
|
left: 12px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: var(--text-muted);
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Suchergebnisse Dropdown */
|
||||||
|
.search-results {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 1000;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-results.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-item {
|
||||||
|
padding: 10px 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-item:hover {
|
||||||
|
background: var(--bg-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-item .result-text {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-item .result-id {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-no-results {
|
||||||
|
padding: 15px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.treeview-container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-panel, .detail-panel {
|
||||||
|
min-width: 100%;
|
||||||
|
flex: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-panel, .detail-panel {
|
||||||
|
min-width: 100%;
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-panel {
|
||||||
|
position: static; /* kein sticky bei gestapelter Ansicht */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="main-content">
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<!-- Content Card -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="d-flex justify-between align-center mb-20">
|
||||||
|
<h1 class="card-title" style="margin: 0;">🌳 Klassifikationsbaum</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Toolbar -->
|
||||||
|
<div class="tree-toolbar">
|
||||||
|
<div class="tree-search">
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
<input type="text" id="tree-search-input" class="form-control" placeholder="Klassifikation suchen..." autocomplete="off" />
|
||||||
|
<div id="search-results" class="search-results"></div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-secondary" id="btn-expand-all" title="Alle aufklappen">
|
||||||
|
<i class="fas fa-expand-alt"></i> Alle öffnen
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary" id="btn-collapse-all" title="Alle zuklappen">
|
||||||
|
<i class="fas fa-compress-alt"></i> Alle schließen
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-secondary" id="btn-refresh" title="Aktualisieren">
|
||||||
|
<i class="fas fa-sync"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Treeview Container -->
|
||||||
|
<div class="treeview-container">
|
||||||
|
<!-- Tree Panel -->
|
||||||
|
<div class="tree-panel">
|
||||||
|
<div id="classification-tree"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Detail Panel -->
|
||||||
|
<div class="detail-panel">
|
||||||
|
<div class="detail-card">
|
||||||
|
<div id="detail-content">
|
||||||
|
<div class="no-selection">
|
||||||
|
<i class="fas fa-hand-pointer"></i>
|
||||||
|
<p>Wählen Sie eine Klassifikation aus dem Baum aus, um Details anzuzeigen.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- jsTree JS -->
|
||||||
|
<script src="/libs/jstree/current/jstree.min.js"></script>
|
||||||
|
<script>if (typeof $.jstree === "undefined") { document.write('<script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.16/jstree.min.js"><\/script>'); }</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
// Tree initialisieren
|
||||||
|
initTree();
|
||||||
|
|
||||||
|
// Toolbar Events
|
||||||
|
$('#btn-expand-all').on('click', function() {
|
||||||
|
$('#classification-tree').jstree('open_all');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#btn-collapse-all').on('click', function() {
|
||||||
|
$('#classification-tree').jstree('close_all');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#btn-refresh').on('click', function() {
|
||||||
|
$('#classification-tree').jstree('refresh');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Serverseitige Suche
|
||||||
|
var searchTimeout = null;
|
||||||
|
$('#tree-search-input').on('keyup', function() {
|
||||||
|
var searchString = $(this).val().trim();
|
||||||
|
|
||||||
|
if (searchTimeout) {
|
||||||
|
clearTimeout(searchTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchString.length < 2) {
|
||||||
|
$('#search-results').removeClass('show').empty();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchTimeout = setTimeout(function() {
|
||||||
|
performSearch(searchString);
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Suche schließen bei Klick außerhalb
|
||||||
|
$(document).on('click', function(e) {
|
||||||
|
if (!$(e.target).closest('.tree-search').length) {
|
||||||
|
$('#search-results').removeClass('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Focus auf Suchfeld zeigt Ergebnisse wieder
|
||||||
|
$('#tree-search-input').on('focus', function() {
|
||||||
|
if ($('#search-results').children().length > 0) {
|
||||||
|
$('#search-results').addClass('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function initTree() {
|
||||||
|
$('#classification-tree').jstree({
|
||||||
|
'core': {
|
||||||
|
'data': {
|
||||||
|
'url': function(node) {
|
||||||
|
return '/Thesaurus/ajax/getTreeData.php';
|
||||||
|
},
|
||||||
|
'data': function(node) {
|
||||||
|
return {
|
||||||
|
'id': node.id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'themes': {
|
||||||
|
'name': 'default',
|
||||||
|
'responsive': true,
|
||||||
|
'dots': true,
|
||||||
|
'icons': true
|
||||||
|
},
|
||||||
|
'multiple': false,
|
||||||
|
'animation': 200
|
||||||
|
},
|
||||||
|
'plugins': ['wholerow', 'sort'],
|
||||||
|
'sort': function(a, b) {
|
||||||
|
var nodeA = this.get_text(a);
|
||||||
|
var nodeB = this.get_text(b);
|
||||||
|
return nodeA.localeCompare(nodeB, 'de', {numeric: true});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Node ausgewählt
|
||||||
|
$('#classification-tree').on('select_node.jstree', function(e, data) {
|
||||||
|
var node = data.node;
|
||||||
|
loadDetails(node.id, node.text, node.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tree geladen
|
||||||
|
$('#classification-tree').on('loaded.jstree', function() {
|
||||||
|
console.log('✅ Klassifikationsbaum geladen');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function performSearch(searchString) {
|
||||||
|
console.log('🔍 Suche nach:', searchString);
|
||||||
|
|
||||||
|
$.get('/Thesaurus/ajax/searchTreeData.php', {
|
||||||
|
q: searchString
|
||||||
|
}, function(data) {
|
||||||
|
var results = typeof data === 'string' ? JSON.parse(data) : data;
|
||||||
|
var html = '';
|
||||||
|
|
||||||
|
if (results && results.length > 0) {
|
||||||
|
results.forEach(function(item) {
|
||||||
|
html += '<div class="search-result-item" data-id="' + item.id + '">';
|
||||||
|
html += '<div class="result-text">' + escapeHtml(item.text) + '</div>';
|
||||||
|
html += '<div class="result-id">ID: ' + item.id + '</div>';
|
||||||
|
html += '</div>';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
html = '<div class="search-no-results"><i class="fas fa-search"></i> Keine Ergebnisse gefunden</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#search-results').html(html).addClass('show');
|
||||||
|
|
||||||
|
// Klick auf Suchergebnis
|
||||||
|
$('.search-result-item').on('click', function() {
|
||||||
|
var id = $(this).data('id');
|
||||||
|
selectAndShowNode(id);
|
||||||
|
$('#search-results').removeClass('show');
|
||||||
|
$('#tree-search-input').val('');
|
||||||
|
});
|
||||||
|
}).fail(function() {
|
||||||
|
$('#search-results').html('<div class="search-no-results">Fehler bei der Suche</div>').addClass('show');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// PATCH für Treeview.php – Funktion selectAndShowNode ersetzen (v3)
|
||||||
|
// Ersetzt die bisherige selectAndShowNode() und activateNode() komplett.
|
||||||
|
|
||||||
|
function selectAndShowNode(id) {
|
||||||
|
id = String(id);
|
||||||
|
|
||||||
|
$.get('/Thesaurus/ajax/getTreePath.php', { id: id }, function(path) {
|
||||||
|
|
||||||
|
if (!path || path.length === 0) {
|
||||||
|
console.warn('⚠️ Kein Pfad gefunden für ID:', id);
|
||||||
|
loadDetails(id, '', {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('📂 Pfad zum Knoten:', path);
|
||||||
|
|
||||||
|
// Vorfahren = alle Elemente außer dem Zielknoten selbst
|
||||||
|
var ancestors = path.slice(0, -1).map(String);
|
||||||
|
var targetId = String(path[path.length - 1]);
|
||||||
|
|
||||||
|
// Sequenziell öffnen: erst wenn ein Knoten vollständig geöffnet
|
||||||
|
// (und seine Kinder geladen) sind, wird der nächste geöffnet.
|
||||||
|
openSequential(ancestors, 0, function() {
|
||||||
|
activateNode(targetId);
|
||||||
|
});
|
||||||
|
|
||||||
|
}).fail(function() {
|
||||||
|
console.error('❌ getTreePath.php fehlgeschlagen');
|
||||||
|
loadDetails(id, '', {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Öffnet Knoten aus dem ancestors-Array nacheinander.
|
||||||
|
* Wartet nach jedem open_node auf after_open.jstree bevor weitergemacht wird.
|
||||||
|
* Knoten die bereits offen sind werden übersprungen.
|
||||||
|
*/
|
||||||
|
function openSequential(ancestors, index, callback) {
|
||||||
|
if (index >= ancestors.length) {
|
||||||
|
callback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tree = $('#classification-tree').jstree(true);
|
||||||
|
var nodeId = ancestors[index];
|
||||||
|
var node = tree.get_node(nodeId);
|
||||||
|
|
||||||
|
// Knoten bereits offen oder nicht auffindbar → nächster
|
||||||
|
if (!node || node.state.opened) {
|
||||||
|
console.log('⏭️ Überspringe (bereits offen oder nicht geladen):', nodeId);
|
||||||
|
openSequential(ancestors, index + 1, callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔓 Öffne Knoten:', nodeId);
|
||||||
|
|
||||||
|
// Einmaliger after_open-Handler nur für diesen Knoten
|
||||||
|
function onOpen(e, data) {
|
||||||
|
if (String(data.node.id) !== nodeId) return; // anderer Knoten
|
||||||
|
$('#classification-tree').off('after_open.jstree', onOpen);
|
||||||
|
console.log('✅ Geöffnet:', nodeId);
|
||||||
|
openSequential(ancestors, index + 1, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#classification-tree').on('after_open.jstree', onOpen);
|
||||||
|
|
||||||
|
// Sicherheits-Timeout pro Knoten (3 Sekunden)
|
||||||
|
setTimeout(function() {
|
||||||
|
$('#classification-tree').off('after_open.jstree', onOpen);
|
||||||
|
console.warn('⏱️ Timeout beim Öffnen von:', nodeId);
|
||||||
|
openSequential(ancestors, index + 1, callback);
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
tree.open_node(nodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wählt den Zielknoten aus und scrollt ihn in Sicht.
|
||||||
|
*/
|
||||||
|
function activateNode(id) {
|
||||||
|
var tree = $('#classification-tree').jstree(true);
|
||||||
|
console.log('🎯 Aktiviere Knoten:', id);
|
||||||
|
|
||||||
|
tree.deselect_all();
|
||||||
|
tree.select_node(id);
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
var anchor = document.getElementById(id + '_anchor');
|
||||||
|
if (anchor) {
|
||||||
|
anchor.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
console.log('📜 Gescrollt zu:', id);
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ Anchor nicht gefunden für ID:', id);
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function loadDetails(id, text, nodeData) {
|
||||||
|
console.log('📖 Lade Details für ID:', id);
|
||||||
|
|
||||||
|
// Loading anzeigen
|
||||||
|
$('#detail-content').html('<div class="text-center p-4"><div class="spinner-border text-primary" role="status"></div><p class="mt-2">Laden...</p></div>');
|
||||||
|
|
||||||
|
$.get('/Thesaurus/ajax/getDetailAuthorityData.php', {
|
||||||
|
authType: 'Classification',
|
||||||
|
id: id,
|
||||||
|
offset: 0,
|
||||||
|
limit: 1
|
||||||
|
}, function(data, status) {
|
||||||
|
if (status === 'success') {
|
||||||
|
try {
|
||||||
|
var metadata = typeof data === 'string' ? JSON.parse(data) : data;
|
||||||
|
var row = metadata['rows'] ? metadata['rows'][0] : null;
|
||||||
|
|
||||||
|
if (row) {
|
||||||
|
renderDetails(row);
|
||||||
|
} else {
|
||||||
|
// Fallback: Zeige zumindest die Basis-Infos aus dem Baum
|
||||||
|
renderBasicDetails(id, text, nodeData);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('❌ Parse-Fehler:', e);
|
||||||
|
renderBasicDetails(id, text, nodeData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).fail(function() {
|
||||||
|
renderBasicDetails(id, text, nodeData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDetails(row) {
|
||||||
|
var html = '';
|
||||||
|
|
||||||
|
html += '<h4><i class="fas fa-folder-tree me-2"></i>' + escapeHtml(row.Text || row.name || 'Unbekannt') + '</h4>';
|
||||||
|
|
||||||
|
html += '<div class="detail-row">';
|
||||||
|
html += '<span class="detail-label">ID:</span>';
|
||||||
|
html += '<span class="detail-value">' + escapeHtml(row.ID || '') + '</span>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
if (row.Notation) {
|
||||||
|
html += '<div class="detail-row">';
|
||||||
|
html += '<span class="detail-label">Notation:</span>';
|
||||||
|
html += '<span class="detail-value"><strong>' + escapeHtml(row.Notation) + '</strong></span>';
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if (row.Descriptor) {
|
||||||
|
html += '<div class="detail-row">';
|
||||||
|
html += '<span class="detail-label">Deskriptor:</span>';
|
||||||
|
html += '<span class="detail-value">' + escapeHtml(row.Descriptor) + '</span>';
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if (row.Type) {
|
||||||
|
html += '<div class="detail-row">';
|
||||||
|
html += '<span class="detail-label">Typ:</span>';
|
||||||
|
html += '<span class="detail-value">' + escapeHtml(row.Type) + '</span>';
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.Scopenote) {
|
||||||
|
html += '<div class="detail-row">';
|
||||||
|
html += '<span class="detail-label">Anmerkung:</span>';
|
||||||
|
html += '<span class="detail-value">' + escapeHtml(row.Scopenote) + '</span>';
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relationen
|
||||||
|
if (row.Relations && row.Relations.length > 0) {
|
||||||
|
html += '<hr>';
|
||||||
|
html += '<h5><i class="fas fa-link me-2"></i>Relationen</h5>';
|
||||||
|
html += '<div class="relations-list">';
|
||||||
|
|
||||||
|
row.Relations.forEach(function(rel) {
|
||||||
|
html += '<div class="relation-item" style="display: flex; align-items: center; padding: 8px 12px; background: white; border-radius: 4px; margin-bottom: 8px; border: 1px solid #dee2e6;">';
|
||||||
|
html += '<span style="font-weight: 600; color: var(--primary-color); width: 80px; flex-shrink: 0;">' + escapeHtml(rel.Relationtype || '') + '</span>';
|
||||||
|
html += '<span style="flex: 1;">';
|
||||||
|
html += '<a href="javascript:void(0)" onclick="selectAndShowNode(\'' + rel.IDRelation + '\')" style="color: var(--primary-color); cursor: pointer;">' + escapeHtml(rel.TextRelation || '') + '</a>';
|
||||||
|
html += ' <small class="text-muted">(ID: ' + escapeHtml(rel.IDRelation || '') + ')</small>';
|
||||||
|
html += '</span>';
|
||||||
|
html += '</div>';
|
||||||
|
});
|
||||||
|
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aktionen
|
||||||
|
html += '<hr>';
|
||||||
|
html += '<div class="d-flex gap-2">';
|
||||||
|
html += '<button class="btn btn-primary btn-sm" onclick="editClassification(\'' + row.ID + '\')"><i class="fas fa-edit me-1"></i>Bearbeiten</button>';
|
||||||
|
html += '<button class="btn btn-secondary btn-sm" onclick="copyToClipboard(\'' + escapeHtml(row.Text || '') + '\')"><i class="fas fa-copy me-1"></i>Name kopieren</button>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
$('#detail-content').html(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBasicDetails(id, text, nodeData) {
|
||||||
|
var html = '';
|
||||||
|
|
||||||
|
html += '<h4><i class="fas fa-folder-tree me-2"></i>' + escapeHtml(text || 'Klassifikation') + '</h4>';
|
||||||
|
|
||||||
|
html += '<div class="detail-row">';
|
||||||
|
html += '<span class="detail-label">ID:</span>';
|
||||||
|
html += '<span class="detail-value">' + escapeHtml(id) + '</span>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
if (nodeData && nodeData.name) {
|
||||||
|
html += '<div class="detail-row">';
|
||||||
|
html += '<span class="detail-label">Name:</span>';
|
||||||
|
html += '<span class="detail-value">' + escapeHtml(nodeData.name) + '</span>';
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '<hr>';
|
||||||
|
html += '<div class="d-flex gap-2">';
|
||||||
|
html += '<button class="btn btn-primary btn-sm" onclick="editClassification(\'' + id + '\')"><i class="fas fa-edit me-1"></i>Bearbeiten</button>';
|
||||||
|
html += '<button class="btn btn-secondary btn-sm" onclick="copyToClipboard(\'' + escapeHtml(text || '') + '\')"><i class="fas fa-copy me-1"></i>Name kopieren</button>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
$('#detail-content').html(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
function editClassification(id) {
|
||||||
|
window.location.href = 'Classifications.php?edit=' + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyToClipboard(text) {
|
||||||
|
var tempInput = document.createElement('input');
|
||||||
|
tempInput.value = text;
|
||||||
|
document.body.appendChild(tempInput);
|
||||||
|
tempInput.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(tempInput);
|
||||||
|
|
||||||
|
// Feedback
|
||||||
|
alert('"' + text + '" wurde in die Zwischenablage kopiert.');
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(text) {
|
||||||
|
if (!text) return '';
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
include "templates/Footer.html";
|
||||||
|
?>
|
||||||
31
ajax/Autocomplete.php
Normal file
31
ajax/Autocomplete.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Autocomplete.php - Liefert Vorschläge für Autocomplete-Felder
|
||||||
|
*/
|
||||||
|
|
||||||
|
include "db_connection.php";
|
||||||
|
require_once "libAuthorities.php";
|
||||||
|
|
||||||
|
$request = isset($_POST['query']) ? strtolower($_POST["query"]) : '';
|
||||||
|
$authType = isset($_POST['authType']) ? $_POST['authType'] : 'Subject';
|
||||||
|
|
||||||
|
if (empty($request)) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$object = new CRUD();
|
||||||
|
$response = $object->getEntryText($authType, $request);
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
foreach ($response as $entry) {
|
||||||
|
$data[] = $entry['Text'] . " (ID: " . $entry['ID'] . ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($data);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode([]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
84
ajax/UpdateTerm.php
Normal file
84
ajax/UpdateTerm.php
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* UpdateTerm.php - Aktualisiert einen bestehenden Normdaten-Eintrag
|
||||||
|
*
|
||||||
|
* Parameter (POST) - von JavaScript:
|
||||||
|
* - AnchorID: ID des Eintrags
|
||||||
|
* - authType: Typ (Subject, Person, etc.)
|
||||||
|
* - term: Neuer Anzeigetext
|
||||||
|
* - detailtype: Neuer Untertyp (optional)
|
||||||
|
* - classification: Neuer Klassifikationscode (optional)
|
||||||
|
* - scopenote: Neuer Kommentar (optional)
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
include "db_connection.php";
|
||||||
|
|
||||||
|
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||||
|
if (mysqli_connect_errno()) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Datenbankverbindung fehlgeschlagen']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_set_charset($conn, "utf8");
|
||||||
|
|
||||||
|
// Parameter aus JavaScript
|
||||||
|
$id = isset($_POST['AnchorID']) ? intval($_POST['AnchorID']) : 0;
|
||||||
|
if ($id === 0) {
|
||||||
|
$id = isset($_POST['ID']) ? intval($_POST['ID']) : 0;
|
||||||
|
}
|
||||||
|
$text = isset($_POST['term']) ? trim($_POST['term']) : '';
|
||||||
|
$detailType = isset($_POST['detailtype']) ? trim($_POST['detailtype']) : '';
|
||||||
|
$classification = isset($_POST['classification']) ? trim($_POST['classification']) : '';
|
||||||
|
$comment = isset($_POST['scopenote']) ? trim($_POST['scopenote']) : '';
|
||||||
|
|
||||||
|
if ($id === 0) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'ID muss angegeben werden']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($text)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Text darf nicht leer sein']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_begin_transaction($conn);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Anchor aktualisieren (Text, DetailType, Classification)
|
||||||
|
$updateAnchorSql = "UPDATE Anchor SET
|
||||||
|
Text = '" . mysqli_real_escape_string($conn, $text) . "',
|
||||||
|
DetailType = '" . mysqli_real_escape_string($conn, $detailType) . "',
|
||||||
|
Classification = '" . mysqli_real_escape_string($conn, $classification) . "'
|
||||||
|
WHERE ID = $id";
|
||||||
|
|
||||||
|
if (!mysqli_query($conn, $updateAnchorSql)) {
|
||||||
|
throw new Exception('Fehler beim Aktualisieren des Anchor: ' . mysqli_error($conn));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Entry aktualisieren
|
||||||
|
$updateEntrySql = "UPDATE Entry SET
|
||||||
|
Text = '" . mysqli_real_escape_string($conn, $text) . "',
|
||||||
|
CompleteText = '" . mysqli_real_escape_string($conn, $text) . "',
|
||||||
|
Comment = '" . mysqli_real_escape_string($conn, $comment) . "'
|
||||||
|
WHERE ID = $id";
|
||||||
|
|
||||||
|
if (!mysqli_query($conn, $updateEntrySql)) {
|
||||||
|
throw new Exception('Fehler beim Aktualisieren des Entry: ' . mysqli_error($conn));
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_commit($conn);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Eintrag erfolgreich aktualisiert'
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
mysqli_rollback($conn);
|
||||||
|
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_close($conn);
|
||||||
|
?>
|
||||||
27
ajax/db_connection.php.example
Normal file
27
ajax/db_connection.php.example
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Datenbankverbindung für BIBB Thesaurus
|
||||||
|
* Kopiere diese Datei als db_connection.php und trage deine Zugangsdaten ein.
|
||||||
|
*/
|
||||||
|
|
||||||
|
define('HOST', 'localhost');
|
||||||
|
define('USER', 'DEIN_DATENBANKBENUTZER');
|
||||||
|
define('PASSWORD', 'DEIN_PASSWORT');
|
||||||
|
define('DATABASE', 'TSData');
|
||||||
|
define('CHARSET', 'utf8');
|
||||||
|
|
||||||
|
function DB()
|
||||||
|
{
|
||||||
|
static $instance;
|
||||||
|
if ($instance === null) {
|
||||||
|
$opt = array(
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
PDO::ATTR_EMULATE_PREPARES => FALSE,
|
||||||
|
);
|
||||||
|
$dsn = 'mysql:host=' . HOST . ';dbname=' . DATABASE . ';charset=' . CHARSET;
|
||||||
|
$instance = new PDO($dsn, USER, PASSWORD, $opt);
|
||||||
|
}
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
|
?>
|
||||||
86
ajax/deleteRelation.php
Normal file
86
ajax/deleteRelation.php
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* deleteRelation.php - Löscht eine Relation
|
||||||
|
*
|
||||||
|
* Parameter (POST):
|
||||||
|
* - IDAnchor: ID des Ausgangseintrags
|
||||||
|
* - LinkingID: ID der Relation in der Linking-Tabelle
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
include "db_connection.php";
|
||||||
|
|
||||||
|
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||||
|
if (mysqli_connect_errno()) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Datenbankverbindung fehlgeschlagen']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_set_charset($conn, "utf8");
|
||||||
|
|
||||||
|
$idAnchor = isset($_POST['IDAnchor']) ? intval($_POST['IDAnchor']) : 0;
|
||||||
|
$linkingId = isset($_POST['LinkingID']) ? intval($_POST['LinkingID']) : 0;
|
||||||
|
|
||||||
|
if ($linkingId === 0) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'LinkingID muss angegeben werden']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zuerst Relation lesen für reziproke Löschung
|
||||||
|
$getSql = "SELECT IDAnchor, IDEntry, Relationtype FROM Linking WHERE ID = $linkingId";
|
||||||
|
$getResult = mysqli_query($conn, $getSql);
|
||||||
|
|
||||||
|
if (!$getResult || mysqli_num_rows($getResult) === 0) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Relation nicht gefunden']);
|
||||||
|
mysqli_close($conn);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$relation = mysqli_fetch_assoc($getResult);
|
||||||
|
$targetId = $relation['IDEntry'];
|
||||||
|
$relationType = $relation['Relationtype'];
|
||||||
|
|
||||||
|
// Reziproke Relation bestimmen
|
||||||
|
$reciprocalType = null;
|
||||||
|
switch ($relationType) {
|
||||||
|
case 'BT':
|
||||||
|
$reciprocalType = 'NT';
|
||||||
|
break;
|
||||||
|
case 'NT':
|
||||||
|
$reciprocalType = 'BT';
|
||||||
|
break;
|
||||||
|
case 'RT':
|
||||||
|
$reciprocalType = 'RT';
|
||||||
|
break;
|
||||||
|
case 'USE':
|
||||||
|
$reciprocalType = 'UF';
|
||||||
|
break;
|
||||||
|
case 'UF':
|
||||||
|
$reciprocalType = 'USE';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relation löschen
|
||||||
|
$deleteSql = "DELETE FROM Linking WHERE ID = $linkingId";
|
||||||
|
|
||||||
|
if (mysqli_query($conn, $deleteSql)) {
|
||||||
|
$affectedRows = mysqli_affected_rows($conn);
|
||||||
|
|
||||||
|
// Reziproke Relation ebenfalls löschen
|
||||||
|
if ($reciprocalType) {
|
||||||
|
$deleteRecSql = "DELETE FROM Linking WHERE IDAnchor = $targetId AND IDEntry = " . $relation['IDAnchor'] . " AND Relationtype = '$reciprocalType'";
|
||||||
|
mysqli_query($conn, $deleteRecSql);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Relation erfolgreich gelöscht',
|
||||||
|
'affected' => $affectedRows
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Fehler beim Löschen: ' . mysqli_error($conn)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_close($conn);
|
||||||
|
?>
|
||||||
59
ajax/deleteTerm.php
Normal file
59
ajax/deleteTerm.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* deleteTerm.php - Löscht einen Normdaten-Eintrag
|
||||||
|
*
|
||||||
|
* Parameter (POST):
|
||||||
|
* - AnchorID: ID des Eintrags (primär, gesendet von SubjectScript.js)
|
||||||
|
* - ID: ID des Eintrags (alternativ, für Rückwärtskompatibilität)
|
||||||
|
* - authType: Typ (Subject, Person, Corporate, Publisher, Classification)
|
||||||
|
*/
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
include "db_connection.php";
|
||||||
|
|
||||||
|
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||||
|
if (mysqli_connect_errno()) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Datenbankverbindung fehlgeschlagen']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
mysqli_set_charset($conn, "utf8");
|
||||||
|
|
||||||
|
// Parameter: AnchorID (von JS) oder ID (Fallback)
|
||||||
|
$id = 0;
|
||||||
|
if (isset($_POST['AnchorID']) && intval($_POST['AnchorID']) > 0) {
|
||||||
|
$id = intval($_POST['AnchorID']);
|
||||||
|
} elseif (isset($_POST['ID']) && intval($_POST['ID']) > 0) {
|
||||||
|
$id = intval($_POST['ID']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($id === 0) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Gültige ID muss angegeben werden']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaktion starten
|
||||||
|
mysqli_begin_transaction($conn);
|
||||||
|
try {
|
||||||
|
// 1. Alle Relationen löschen (als Anchor und als Entry)
|
||||||
|
mysqli_query($conn, "DELETE FROM Linking WHERE IDAnchor = $id");
|
||||||
|
mysqli_query($conn, "DELETE FROM Linking WHERE IDEntry = $id");
|
||||||
|
|
||||||
|
// 2. Entry löschen
|
||||||
|
mysqli_query($conn, "DELETE FROM Entry WHERE ID = $id");
|
||||||
|
|
||||||
|
// 3. Anchor löschen
|
||||||
|
mysqli_query($conn, "DELETE FROM Anchor WHERE ID = $id");
|
||||||
|
|
||||||
|
mysqli_commit($conn);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Eintrag erfolgreich gelöscht'
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
mysqli_rollback($conn);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Fehler beim Löschen: ' . $e->getMessage()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_close($conn);
|
||||||
|
?>
|
||||||
167
ajax/getAuthorityData.php
Normal file
167
ajax/getAuthorityData.php
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* getAuthorityData.php - Liefert Normdaten für Bootstrap Table
|
||||||
|
*
|
||||||
|
* Parameter (Bootstrap Table Server-Side):
|
||||||
|
* - limit: Anzahl Einträge pro Seite
|
||||||
|
* - offset: Start-Position
|
||||||
|
* - search: Suchbegriff
|
||||||
|
* - authType: Typ (Subject, Person, Corporate, Publisher, Classification)
|
||||||
|
*
|
||||||
|
* Rückgabe: JSON mit total und rows
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
include "db_connection.php";
|
||||||
|
|
||||||
|
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||||
|
if (mysqli_connect_errno()) {
|
||||||
|
echo json_encode(['total' => 0, 'rows' => [], 'error' => 'Datenbankverbindung fehlgeschlagen']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_set_charset($conn, "utf8");
|
||||||
|
|
||||||
|
// Parameter auslesen
|
||||||
|
$limit = isset($_GET['limit']) ? $_GET['limit'] : 25;
|
||||||
|
$offset = isset($_GET['offset']) ? intval($_GET['offset']) : 0;
|
||||||
|
|
||||||
|
// "all" oder 0 bedeutet: alle Treffer ohne Limit
|
||||||
|
if ($limit === 'all' || $limit === '0' || intval($limit) <= 0) {
|
||||||
|
$limitAll = true;
|
||||||
|
$limit = 0;
|
||||||
|
} else {
|
||||||
|
$limitAll = false;
|
||||||
|
$limit = intval($limit);
|
||||||
|
}
|
||||||
|
$search = isset($_GET['search']) ? trim($_GET['search']) : '';
|
||||||
|
$authType = isset($_GET['authType']) ? trim($_GET['authType']) : 'Subject';
|
||||||
|
|
||||||
|
// Erlaubte Typen
|
||||||
|
$allowedTypes = ['Subject', 'Person', 'Corporate', 'Publisher', 'Classification'];
|
||||||
|
if (!in_array($authType, $allowedTypes)) {
|
||||||
|
$authType = 'Subject';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basis-SQL
|
||||||
|
$baseSql = "FROM Anchor a
|
||||||
|
LEFT JOIN Entry e ON a.ID = e.ID
|
||||||
|
WHERE a.Type = '" . mysqli_real_escape_string($conn, $authType) . "'";
|
||||||
|
|
||||||
|
// Suchbedingung hinzufügen (case-insensitive)
|
||||||
|
// Scope Note (e.Comment) wird bewusst nicht durchsucht
|
||||||
|
if (!empty($search)) {
|
||||||
|
// Trunkierung auswerten
|
||||||
|
$leftTrunc = (substr($search, 0, 1) === '*');
|
||||||
|
$rightTrunc = (substr($search, -1) === '*');
|
||||||
|
|
||||||
|
// Sternchen entfernen, dann escapen
|
||||||
|
$clean = trim($search, '*');
|
||||||
|
$searchEscaped = mysqli_real_escape_string($conn, $clean);
|
||||||
|
// LIKE-Sonderzeichen in den Daten escapen
|
||||||
|
$searchEscaped = str_replace(['%', '_'], ['\\%', '\\_'], $searchEscaped);
|
||||||
|
|
||||||
|
// Pattern bauen
|
||||||
|
$pattern = ($leftTrunc ? '%' : '') .
|
||||||
|
$searchEscaped .
|
||||||
|
($rightTrunc ? '%' : '%'); // rechts immer trunkieren = bisheriges Verhalten
|
||||||
|
|
||||||
|
$baseSql .= " AND (LOWER(e.Text) LIKE LOWER('{$pattern}')
|
||||||
|
OR LOWER(e.CompleteText) LIKE LOWER('{$pattern}'))";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gesamtanzahl ermitteln
|
||||||
|
$countSql = "SELECT COUNT(*) as total " . $baseSql;
|
||||||
|
$countResult = mysqli_query($conn, $countSql);
|
||||||
|
$total = 0;
|
||||||
|
if ($countResult) {
|
||||||
|
$countRow = mysqli_fetch_assoc($countResult);
|
||||||
|
$total = intval($countRow['total']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ORDER BY je nach authType
|
||||||
|
// Classification: numerisch nach Notation (z.B. T 4.10 nach T 4.9)
|
||||||
|
// Alle anderen: deutsch-korrekt nach Unicode-Kollation (Umlaute, Groß/Klein, Sonderzeichen)
|
||||||
|
if ($authType === 'Classification') {
|
||||||
|
// Dreistufige Sortierung:
|
||||||
|
// 1. Buchstabenpräfix alphabetisch (G, S, T ...)
|
||||||
|
// 2. Erste Zahl numerisch (Hauptgruppe vor Untergruppe: NULL kommt zuerst)
|
||||||
|
// 3. Zweite Zahl numerisch (T 4.9 vor T 4.10)
|
||||||
|
$orderClause = "
|
||||||
|
SUBSTRING_INDEX(a.Classification, ' ', 1),
|
||||||
|
CAST(
|
||||||
|
NULLIF(SUBSTRING_INDEX(SUBSTRING_INDEX(a.Classification, ' ', -1), '.', 1),
|
||||||
|
a.Classification)
|
||||||
|
AS UNSIGNED
|
||||||
|
),
|
||||||
|
CAST(
|
||||||
|
NULLIF(SUBSTRING_INDEX(a.Classification, '.', -1),
|
||||||
|
a.Classification)
|
||||||
|
AS UNSIGNED
|
||||||
|
)
|
||||||
|
";
|
||||||
|
} else {
|
||||||
|
// utf8_unicode_ci: Umlaute korrekt (Ä=A, Ö=O, Ü=U),
|
||||||
|
// Sonderzeichen nach Grundbuchstabe, Groß-/Klein ignoriert
|
||||||
|
$orderClause = "e.Text COLLATE utf8_unicode_ci ASC";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Daten abrufen
|
||||||
|
$dataSql = "SELECT a.ID, a.Type, a.DetailType, a.Classification,
|
||||||
|
e.Text, e.CompleteText, e.Comment,
|
||||||
|
e.DateCreated, e.DateModified " . $baseSql . "
|
||||||
|
ORDER BY " . $orderClause;
|
||||||
|
|
||||||
|
if (!$limitAll) {
|
||||||
|
$dataSql .= " LIMIT $offset, $limit";
|
||||||
|
}
|
||||||
|
|
||||||
|
$dataResult = mysqli_query($conn, $dataSql);
|
||||||
|
|
||||||
|
$rows = [];
|
||||||
|
if ($dataResult) {
|
||||||
|
while ($row = mysqli_fetch_assoc($dataResult)) {
|
||||||
|
// Relationen zählen
|
||||||
|
$relSql = "SELECT COUNT(*) as cnt FROM Linking WHERE IDAnchor = " . intval($row['ID']);
|
||||||
|
$relResult = mysqli_query($conn, $relSql);
|
||||||
|
$relCount = 0;
|
||||||
|
if ($relResult) {
|
||||||
|
$relRow = mysqli_fetch_assoc($relResult);
|
||||||
|
$relCount = intval($relRow['cnt']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deskriptor-Status ermitteln (USE-Relation = Non-Deskriptor)
|
||||||
|
$useSql = "SELECT COUNT(*) as cnt FROM Linking WHERE IDAnchor = " . intval($row['ID']) . " AND UPPER(Relationtype) = 'USE'";
|
||||||
|
$useResult = mysqli_query($conn, $useSql);
|
||||||
|
$isDescriptor = true;
|
||||||
|
if ($useResult) {
|
||||||
|
$useRow = mysqli_fetch_assoc($useResult);
|
||||||
|
if (intval($useRow['cnt']) > 0) {
|
||||||
|
$isDescriptor = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows[] = [
|
||||||
|
'ID' => $row['ID'],
|
||||||
|
'Type' => $row['Type'],
|
||||||
|
'DetailType' => $row['DetailType'] ?? '',
|
||||||
|
'Classification' => $row['Classification'] ?? '',
|
||||||
|
'Text' => $row['Text'] ?? '',
|
||||||
|
'CompleteText' => $row['CompleteText'] ?? '',
|
||||||
|
'Comment' => $row['Comment'] ?? '',
|
||||||
|
'DateCreated' => $row['DateCreated'] ?? '',
|
||||||
|
'DateModified' => $row['DateModified'] ?? '',
|
||||||
|
'RelationCount' => $relCount,
|
||||||
|
'Descriptor' => $isDescriptor
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_close($conn);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'total' => $total,
|
||||||
|
'rows' => $rows
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
?>
|
||||||
111
ajax/getAuthorityDataRaw.php
Normal file
111
ajax/getAuthorityDataRaw.php
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* getAuthorityDataRaw.php - Liefert Daten für Autocomplete-Suche ODER einzelnen Eintrag per ID
|
||||||
|
*
|
||||||
|
* Parameter:
|
||||||
|
* - id: ID eines einzelnen Eintrags (für ModifyTerm)
|
||||||
|
* - search: Suchbegriff für Autocomplete (min. 2 Zeichen)
|
||||||
|
* - authType: Typ (Subject, Person, Corporate, Publisher, Classification)
|
||||||
|
* - limit: Max. Anzahl Ergebnisse (Standard: 10)
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
include "db_connection.php";
|
||||||
|
|
||||||
|
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||||
|
if (mysqli_connect_errno()) {
|
||||||
|
echo json_encode(['rows' => []]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_set_charset($conn, "utf8");
|
||||||
|
|
||||||
|
// Prüfen ob ID-Abfrage (für ModifyTerm)
|
||||||
|
$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
|
||||||
|
|
||||||
|
if ($id > 0) {
|
||||||
|
// Einzelnen Eintrag per ID laden
|
||||||
|
$sql = "SELECT a.ID, a.Type, a.DetailType, a.Classification,
|
||||||
|
e.Text, e.CompleteText, e.Comment as Scopenote
|
||||||
|
FROM Anchor a
|
||||||
|
LEFT JOIN Entry e ON a.ID = e.ID
|
||||||
|
WHERE a.ID = $id";
|
||||||
|
|
||||||
|
$result = mysqli_query($conn, $sql);
|
||||||
|
|
||||||
|
if ($result && mysqli_num_rows($result) > 0) {
|
||||||
|
$row = mysqli_fetch_assoc($result);
|
||||||
|
|
||||||
|
// Descriptor erstellen
|
||||||
|
$descriptor = strtolower($row['Text'] ?? '');
|
||||||
|
$descriptor = str_replace(' ', '_', $descriptor);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'rows' => [[
|
||||||
|
'ID' => $row['ID'],
|
||||||
|
'Type' => $row['Type'],
|
||||||
|
'DetailType' => $row['DetailType'] ?? '',
|
||||||
|
'Classification' => $row['Classification'] ?? '',
|
||||||
|
'Text' => $row['Text'] ?? '',
|
||||||
|
'CompleteText' => $row['CompleteText'] ?? '',
|
||||||
|
'Scopenote' => $row['Scopenote'] ?? '',
|
||||||
|
'Descriptor' => $descriptor
|
||||||
|
]]
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['rows' => []]);
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_close($conn);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sonst: Autocomplete-Suche
|
||||||
|
$search = isset($_GET['search']) ? trim($_GET['search']) : '';
|
||||||
|
$authType = isset($_GET['authType']) ? trim($_GET['authType']) : '';
|
||||||
|
$limit = isset($_GET['limit']) ? intval($_GET['limit']) : 10;
|
||||||
|
|
||||||
|
if (strlen($search) < 2) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erlaubte Typen
|
||||||
|
$allowedTypes = ['Subject', 'Person', 'Corporate', 'Publisher', 'Classification'];
|
||||||
|
|
||||||
|
// SQL aufbauen
|
||||||
|
$searchEscaped = mysqli_real_escape_string($conn, $search);
|
||||||
|
|
||||||
|
$sql = "SELECT a.ID, a.Type, a.DetailType, e.Text, e.CompleteText
|
||||||
|
FROM Anchor a
|
||||||
|
LEFT JOIN Entry e ON a.ID = e.ID
|
||||||
|
WHERE (e.Text LIKE '%{$searchEscaped}%' OR e.CompleteText LIKE '%{$searchEscaped}%')";
|
||||||
|
|
||||||
|
// Typ-Filter falls angegeben
|
||||||
|
if (!empty($authType) && in_array($authType, $allowedTypes)) {
|
||||||
|
$sql .= " AND a.Type = '" . mysqli_real_escape_string($conn, $authType) . "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " ORDER BY e.Text ASC LIMIT $limit";
|
||||||
|
|
||||||
|
$result = mysqli_query($conn, $sql);
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
if ($result) {
|
||||||
|
while ($row = mysqli_fetch_assoc($result)) {
|
||||||
|
$data[] = [
|
||||||
|
'id' => $row['ID'],
|
||||||
|
'value' => $row['Text'],
|
||||||
|
'text' => $row['Text'],
|
||||||
|
'type' => $row['Type'],
|
||||||
|
'detailType' => $row['DetailType'] ?? '',
|
||||||
|
'completeText' => $row['CompleteText'] ?? ''
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_close($conn);
|
||||||
|
|
||||||
|
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||||
|
?>
|
||||||
8
ajax/getClassificationData.php
Normal file
8
ajax/getClassificationData.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* getClassificationData.php - Wrapper für getAuthorityData.php mit authType=Classification
|
||||||
|
*/
|
||||||
|
|
||||||
|
$_GET['authType'] = 'Classification';
|
||||||
|
include 'getAuthorityData.php';
|
||||||
|
?>
|
||||||
8
ajax/getCorporateData.php
Normal file
8
ajax/getCorporateData.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* getCorporateData.php - Wrapper für getAuthorityData.php mit authType=Corporate
|
||||||
|
*/
|
||||||
|
|
||||||
|
$_GET['authType'] = 'Corporate';
|
||||||
|
include 'getAuthorityData.php';
|
||||||
|
?>
|
||||||
65
ajax/getDSpaceCount.php
Normal file
65
ajax/getDSpaceCount.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* getDSpaceCount.php - Proxy für DSpace Authority-Zählung
|
||||||
|
*
|
||||||
|
* Ruft den DSpace-API-Endpoint getAuthorityCount.php auf und gibt
|
||||||
|
* die Anzahl der Datensätze zurück, die den Begriff enthalten.
|
||||||
|
*
|
||||||
|
* Parameter:
|
||||||
|
* - authType: 'Subject' oder 'Person'
|
||||||
|
* - query: Der zu suchende Begriff
|
||||||
|
*
|
||||||
|
* Response: JSON { "query": "...", "authType": "...", "count": n }
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
// DSpace API Basis-URL
|
||||||
|
$DSPACE_API_URL = 'https://bibb-dspace.bibb.de/DSpaceAPI/getAuthorityCount.php';
|
||||||
|
|
||||||
|
// Parameter
|
||||||
|
$authType = isset($_GET['authType']) ? trim($_GET['authType']) : '';
|
||||||
|
$query = isset($_GET['query']) ? trim($_GET['query']) : '';
|
||||||
|
|
||||||
|
// Validierung
|
||||||
|
$allowedTypes = array('Subject', 'Person');
|
||||||
|
if (!in_array($authType, $allowedTypes)) {
|
||||||
|
echo json_encode(array('error' => 'Invalid authType', 'count' => 0));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($query) === 0) {
|
||||||
|
echo json_encode(array('error' => 'Missing query', 'count' => 0));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DSpace API aufrufen
|
||||||
|
$url = $DSPACE_API_URL . '?authType=' . urlencode($authType) . '&query=' . urlencode($query);
|
||||||
|
|
||||||
|
$context = stream_context_create(array(
|
||||||
|
'http' => array(
|
||||||
|
'timeout' => 15,
|
||||||
|
'header' => "Accept: application/json\r\n"
|
||||||
|
),
|
||||||
|
'ssl' => array(
|
||||||
|
'verify_peer' => false,
|
||||||
|
'verify_peer_name' => false
|
||||||
|
)
|
||||||
|
));
|
||||||
|
|
||||||
|
$response = @file_get_contents($url, false, $context);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
// Fallback: Count 0 zurückgeben, nicht die ganze Anzeige blockieren
|
||||||
|
echo json_encode(array(
|
||||||
|
'query' => $query,
|
||||||
|
'authType' => $authType,
|
||||||
|
'count' => -1,
|
||||||
|
'error' => 'DSpace API not reachable'
|
||||||
|
));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response direkt durchreichen
|
||||||
|
echo $response;
|
||||||
|
?>
|
||||||
125
ajax/getDashboardStats.php
Normal file
125
ajax/getDashboardStats.php
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* getDashboardStats.php - Liefert Statistiken für das Dashboard
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
include "db_connection.php";
|
||||||
|
|
||||||
|
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||||
|
if (mysqli_connect_errno()) {
|
||||||
|
echo json_encode(['error' => 'Datenbankverbindung fehlgeschlagen']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_set_charset($conn, "utf8");
|
||||||
|
|
||||||
|
$stats = [];
|
||||||
|
|
||||||
|
// 1. Anzahl Datensätze pro Typ
|
||||||
|
$types = ['Subject', 'Person', 'Corporate', 'Publisher', 'Classification'];
|
||||||
|
$counts = [];
|
||||||
|
|
||||||
|
foreach ($types as $type) {
|
||||||
|
$sql = "SELECT COUNT(*) as cnt FROM Anchor WHERE Type = '$type'";
|
||||||
|
$res = mysqli_query($conn, $sql);
|
||||||
|
if ($res) {
|
||||||
|
$row = mysqli_fetch_assoc($res);
|
||||||
|
$counts[$type] = intval($row['cnt']);
|
||||||
|
} else {
|
||||||
|
$counts[$type] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$stats['counts'] = $counts;
|
||||||
|
$stats['total'] = array_sum($counts);
|
||||||
|
|
||||||
|
// 2. Anzahl Relationen
|
||||||
|
$sql = "SELECT COUNT(*) as cnt FROM Linking";
|
||||||
|
$res = mysqli_query($conn, $sql);
|
||||||
|
if ($res) {
|
||||||
|
$row = mysqli_fetch_assoc($res);
|
||||||
|
$stats['relationsTotal'] = intval($row['cnt']);
|
||||||
|
} else {
|
||||||
|
$stats['relationsTotal'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Zuletzt geänderte Einträge (Top 10)
|
||||||
|
$sql = "SELECT a.ID, a.Text, a.Type, e.DateModified
|
||||||
|
FROM Anchor a
|
||||||
|
JOIN Entry e ON a.Text = e.Text
|
||||||
|
WHERE e.DateModified > '2000-01-01'
|
||||||
|
ORDER BY e.DateModified DESC
|
||||||
|
LIMIT 10";
|
||||||
|
$res = mysqli_query($conn, $sql);
|
||||||
|
$recentlyModified = [];
|
||||||
|
if ($res) {
|
||||||
|
while ($row = mysqli_fetch_assoc($res)) {
|
||||||
|
$recentlyModified[] = [
|
||||||
|
'id' => $row['ID'],
|
||||||
|
'text' => $row['Text'],
|
||||||
|
'type' => $row['Type'],
|
||||||
|
'date' => $row['DateModified']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$stats['recentlyModified'] = $recentlyModified;
|
||||||
|
|
||||||
|
// 4. Zuletzt hinzugefügte Einträge (Top 10)
|
||||||
|
$sql = "SELECT a.ID, a.Text, a.Type, e.DateCreated
|
||||||
|
FROM Anchor a
|
||||||
|
JOIN Entry e ON a.Text = e.Text
|
||||||
|
WHERE e.DateCreated > '2000-01-01'
|
||||||
|
ORDER BY e.DateCreated DESC
|
||||||
|
LIMIT 10";
|
||||||
|
$res = mysqli_query($conn, $sql);
|
||||||
|
$recentlyCreated = [];
|
||||||
|
if ($res) {
|
||||||
|
while ($row = mysqli_fetch_assoc($res)) {
|
||||||
|
$recentlyCreated[] = [
|
||||||
|
'id' => $row['ID'],
|
||||||
|
'text' => $row['Text'],
|
||||||
|
'type' => $row['Type'],
|
||||||
|
'date' => $row['DateCreated']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$stats['recentlyCreated'] = $recentlyCreated;
|
||||||
|
|
||||||
|
// 5. Qualitäts-Check: Einträge ohne Relationen (Waisen)
|
||||||
|
$orphanCounts = [];
|
||||||
|
foreach ($types as $type) {
|
||||||
|
$sql = "SELECT COUNT(*) as cnt FROM Anchor a
|
||||||
|
WHERE a.Type = '$type'
|
||||||
|
AND a.ID NOT IN (SELECT DISTINCT IDAnchor FROM Linking)";
|
||||||
|
$res = mysqli_query($conn, $sql);
|
||||||
|
if ($res) {
|
||||||
|
$row = mysqli_fetch_assoc($res);
|
||||||
|
$orphanCounts[$type] = intval($row['cnt']);
|
||||||
|
} else {
|
||||||
|
$orphanCounts[$type] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$stats['orphans'] = $orphanCounts;
|
||||||
|
$stats['orphansTotal'] = array_sum($orphanCounts);
|
||||||
|
|
||||||
|
// 6. Deskriptoren vs. Non-Deskriptoren (für Schlagworte)
|
||||||
|
$sql = "SELECT
|
||||||
|
SUM(CASE WHEN DetailType = 'Deskriptor' OR DetailType = 'deskriptor' THEN 1 ELSE 0 END) as descriptors,
|
||||||
|
SUM(CASE WHEN DetailType != 'Deskriptor' AND DetailType != 'deskriptor' THEN 1 ELSE 0 END) as nonDescriptors
|
||||||
|
FROM Anchor WHERE Type = 'Subject'";
|
||||||
|
$res = mysqli_query($conn, $sql);
|
||||||
|
if ($res) {
|
||||||
|
$row = mysqli_fetch_assoc($res);
|
||||||
|
$stats['descriptors'] = intval($row['descriptors']);
|
||||||
|
$stats['nonDescriptors'] = intval($row['nonDescriptors']);
|
||||||
|
} else {
|
||||||
|
$stats['descriptors'] = 0;
|
||||||
|
$stats['nonDescriptors'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_close($conn);
|
||||||
|
|
||||||
|
echo json_encode($stats, JSON_UNESCAPED_UNICODE);
|
||||||
|
?>
|
||||||
122
ajax/getDetailAuthorityData.php
Normal file
122
ajax/getDetailAuthorityData.php
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* getDetailAuthorityData.php - Liefert vollständige Details zu einem Eintrag
|
||||||
|
*
|
||||||
|
* Parameter:
|
||||||
|
* - id: ID des Eintrags (kleingeschrieben von JS)
|
||||||
|
* - ID: ID des Eintrags (alternativ)
|
||||||
|
* - authType: Typ (optional)
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
include "db_connection.php";
|
||||||
|
|
||||||
|
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||||
|
if (mysqli_connect_errno()) {
|
||||||
|
echo json_encode(['rows' => []]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_set_charset($conn, "utf8");
|
||||||
|
|
||||||
|
// Beide Parameter-Namen unterstützen (JS sendet 'id')
|
||||||
|
$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
|
||||||
|
if ($id === 0) {
|
||||||
|
$id = isset($_GET['ID']) ? intval($_GET['ID']) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($id === 0) {
|
||||||
|
echo json_encode(['rows' => []]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hauptdaten laden
|
||||||
|
$sql = "SELECT a.ID, a.Type, a.DetailType, a.Classification, a.Text as AnchorText,
|
||||||
|
e.Text, e.CompleteText, e.Comment, e.DateCreated, e.DateModified
|
||||||
|
FROM Anchor a
|
||||||
|
LEFT JOIN Entry e ON a.ID = e.ID
|
||||||
|
WHERE a.ID = $id";
|
||||||
|
|
||||||
|
$result = mysqli_query($conn, $sql);
|
||||||
|
|
||||||
|
if (!$result || mysqli_num_rows($result) === 0) {
|
||||||
|
echo json_encode(['rows' => []]);
|
||||||
|
mysqli_close($conn);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = mysqli_fetch_assoc($result);
|
||||||
|
|
||||||
|
// Relationen laden im Format das JS erwartet
|
||||||
|
$relSql = "SELECT l.ID as LinkingID, l.IDAnchor, l.IDEntry, l.Relationtype,
|
||||||
|
e.Text as EntryText, a.Type as EntryType
|
||||||
|
FROM Linking l
|
||||||
|
LEFT JOIN Entry e ON l.IDEntry = e.ID
|
||||||
|
LEFT JOIN Anchor a ON l.IDEntry = a.ID
|
||||||
|
WHERE l.IDAnchor = $id
|
||||||
|
ORDER BY l.Relationtype, e.Text";
|
||||||
|
|
||||||
|
$relResult = mysqli_query($conn, $relSql);
|
||||||
|
|
||||||
|
$relations = [];
|
||||||
|
if ($relResult) {
|
||||||
|
while ($relRow = mysqli_fetch_assoc($relResult)) {
|
||||||
|
$relations[] = [
|
||||||
|
'Relationtype' => $relRow['Relationtype'],
|
||||||
|
'TextRelation' => $relRow['EntryText'] ?? '',
|
||||||
|
'IDRelation' => $relRow['IDEntry']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synonyme laden (nur für Subject)
|
||||||
|
$synonymsText = '';
|
||||||
|
if ($data['Type'] === 'Subject' && !empty($data['Text'])) {
|
||||||
|
$desc = strtolower($data['Text']);
|
||||||
|
$desc = str_replace(' ', '_', $desc);
|
||||||
|
|
||||||
|
$synSql = "SELECT IDLinking FROM Synonyms WHERE Descriptor = '" . mysqli_real_escape_string($conn, $desc) . "' LIMIT 1";
|
||||||
|
$synResult = mysqli_query($conn, $synSql);
|
||||||
|
|
||||||
|
if ($synResult && mysqli_num_rows($synResult) > 0) {
|
||||||
|
$synRow = mysqli_fetch_assoc($synResult);
|
||||||
|
$idLinking = $synRow['IDLinking'];
|
||||||
|
|
||||||
|
$synAllSql = "SELECT Text FROM Synonyms WHERE IDLinking = $idLinking AND Descriptor != '" . mysqli_real_escape_string($conn, $desc) . "' ORDER BY Text";
|
||||||
|
$synAllResult = mysqli_query($conn, $synAllSql);
|
||||||
|
|
||||||
|
$synTexts = [];
|
||||||
|
if ($synAllResult) {
|
||||||
|
while ($syn = mysqli_fetch_assoc($synAllResult)) {
|
||||||
|
$synTexts[] = $syn['Text'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$synonymsText = implode(', ', $synTexts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descriptor erstellen
|
||||||
|
$descriptor = strtolower($data['Text'] ?? '');
|
||||||
|
$descriptor = str_replace(' ', '_', $descriptor);
|
||||||
|
|
||||||
|
mysqli_close($conn);
|
||||||
|
|
||||||
|
// Rückgabe im Format das JavaScript erwartet
|
||||||
|
echo json_encode([
|
||||||
|
'rows' => [[
|
||||||
|
'ID' => $data['ID'],
|
||||||
|
'Type' => $data['Type'],
|
||||||
|
'Detailtype' => $data['DetailType'] ?? '',
|
||||||
|
'Classification' => $data['Classification'] ?? '',
|
||||||
|
'Text' => $data['Text'] ?? '',
|
||||||
|
'CompleteText' => $data['CompleteText'] ?? '',
|
||||||
|
'Scopenote' => $data['Comment'] ?? '',
|
||||||
|
'Descriptor' => $descriptor,
|
||||||
|
'Synonyms' => $synonymsText,
|
||||||
|
'Relations' => $relations,
|
||||||
|
'DateCreated' => $data['DateCreated'] ?? '',
|
||||||
|
'DateModified' => $data['DateModified'] ?? ''
|
||||||
|
]]
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
?>
|
||||||
8
ajax/getPersonData.php
Normal file
8
ajax/getPersonData.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* getPersonData.php - Wrapper für getAuthorityData.php mit authType=Person
|
||||||
|
*/
|
||||||
|
|
||||||
|
$_GET['authType'] = 'Person';
|
||||||
|
include 'getAuthorityData.php';
|
||||||
|
?>
|
||||||
8
ajax/getPublisherData.php
Normal file
8
ajax/getPublisherData.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* getPublisherData.php - Wrapper für getAuthorityData.php mit authType=Publisher
|
||||||
|
*/
|
||||||
|
|
||||||
|
$_GET['authType'] = 'Publisher';
|
||||||
|
include 'getAuthorityData.php';
|
||||||
|
?>
|
||||||
104
ajax/getRelations.php
Normal file
104
ajax/getRelations.php
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* getRelations.php - Lädt Relationen für einen Eintrag und gibt HTML zurück
|
||||||
|
*
|
||||||
|
* Parameter:
|
||||||
|
* - anchorID: ID des Eintrags (JavaScript-Format)
|
||||||
|
* - IDAnchor: ID des Eintrags (alternatives Format)
|
||||||
|
* - authType: Typ des Eintrags
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: text/html; charset=utf-8');
|
||||||
|
|
||||||
|
include "db_connection.php";
|
||||||
|
|
||||||
|
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||||
|
if (mysqli_connect_errno()) {
|
||||||
|
echo '';
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_set_charset($conn, "utf8");
|
||||||
|
|
||||||
|
// Beide Parameter-Namen unterstützen (JavaScript sendet anchorID)
|
||||||
|
$idAnchor = isset($_GET['anchorID']) ? intval($_GET['anchorID']) : 0;
|
||||||
|
if ($idAnchor === 0) {
|
||||||
|
$idAnchor = isset($_GET['IDAnchor']) ? intval($_GET['IDAnchor']) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($idAnchor === 0) {
|
||||||
|
echo ''; // Leere Rückgabe bei ID 0 (neuer Eintrag)
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relationen mit verknüpften Entry-Daten laden
|
||||||
|
// NUR Relationen mit einem echten Relationstyp (keine leeren, keine Selbstreferenzen)
|
||||||
|
$sql = "SELECT l.ID as LinkingID, l.IDAnchor, l.IDEntry, l.Relationtype,
|
||||||
|
e.Text as EntryText, e.CompleteText, a.Type as EntryType
|
||||||
|
FROM Linking l
|
||||||
|
LEFT JOIN Entry e ON l.IDEntry = e.ID
|
||||||
|
LEFT JOIN Anchor a ON l.IDEntry = a.ID
|
||||||
|
WHERE l.IDAnchor = $idAnchor
|
||||||
|
AND l.Relationtype != ''
|
||||||
|
AND l.IDAnchor != l.IDEntry
|
||||||
|
ORDER BY l.Relationtype, e.Text";
|
||||||
|
|
||||||
|
$result = mysqli_query($conn, $sql);
|
||||||
|
|
||||||
|
// Prüfen ob Relationen vorhanden
|
||||||
|
if (!$result || mysqli_num_rows($result) === 0) {
|
||||||
|
echo ''; // Leere Rückgabe - Bereich wird ausgeblendet
|
||||||
|
mysqli_close($conn);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML-Tabellenzeilen generieren (analog zu Synonymen)
|
||||||
|
$html = '';
|
||||||
|
|
||||||
|
while ($row = mysqli_fetch_assoc($result)) {
|
||||||
|
$relType = htmlspecialchars($row['Relationtype']);
|
||||||
|
$entryText = htmlspecialchars($row['EntryText'] ?? '');
|
||||||
|
$linkingId = intval($row['LinkingID']);
|
||||||
|
$entryId = intval($row['IDEntry']);
|
||||||
|
|
||||||
|
// Badge-Farbe je nach Relationstyp
|
||||||
|
$badgeClass = 'bg-secondary';
|
||||||
|
$badgeTitle = $relType;
|
||||||
|
switch ($relType) {
|
||||||
|
case 'BT':
|
||||||
|
$badgeClass = 'bg-success';
|
||||||
|
$badgeTitle = 'BT - Oberbegriff';
|
||||||
|
break;
|
||||||
|
case 'NT':
|
||||||
|
$badgeClass = 'bg-danger';
|
||||||
|
$badgeTitle = 'NT - Unterbegriff';
|
||||||
|
break;
|
||||||
|
case 'RT':
|
||||||
|
$badgeClass = 'bg-warning text-dark';
|
||||||
|
$badgeTitle = 'RT - Verwandter Begriff';
|
||||||
|
break;
|
||||||
|
case 'USE':
|
||||||
|
$badgeClass = 'bg-info';
|
||||||
|
$badgeTitle = 'USE - Benutze';
|
||||||
|
break;
|
||||||
|
case 'UF':
|
||||||
|
$badgeClass = 'bg-secondary';
|
||||||
|
$badgeTitle = 'UF - Benutzt für';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= '<tr>';
|
||||||
|
$html .= '<td><span class="badge ' . $badgeClass . '" title="' . $badgeTitle . '">' . $relType . '</span></td>';
|
||||||
|
$html .= '<td>' . $entryText . '</td>';
|
||||||
|
$html .= '<td class="text-end">';
|
||||||
|
$html .= '<button class="btn btn-sm btn-outline-danger" onclick="DeleteRelation(' . $idAnchor . ', ' . $linkingId . ')" title="Relation löschen">';
|
||||||
|
$html .= '<i class="fas fa-trash"></i>';
|
||||||
|
$html .= '</button>';
|
||||||
|
$html .= '</td>';
|
||||||
|
$html .= '</tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_close($conn);
|
||||||
|
|
||||||
|
echo $html;
|
||||||
|
?>
|
||||||
8
ajax/getSubjectData.php
Normal file
8
ajax/getSubjectData.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* getSubjectData.php - Wrapper für getAuthorityData.php mit authType=Subject
|
||||||
|
*/
|
||||||
|
|
||||||
|
$_GET['authType'] = 'Subject';
|
||||||
|
include 'getAuthorityData.php';
|
||||||
|
?>
|
||||||
82
ajax/getSynonyms.php
Normal file
82
ajax/getSynonyms.php
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* getSynonyms.php - Lädt Synonyme für einen Begriff
|
||||||
|
*
|
||||||
|
* Parameter:
|
||||||
|
* - text: Der Begriff, für den Synonyme gesucht werden
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
include "db_connection.php";
|
||||||
|
|
||||||
|
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||||
|
if (mysqli_connect_errno()) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Datenbankverbindung fehlgeschlagen']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_set_charset($conn, "utf8");
|
||||||
|
|
||||||
|
$text = isset($_GET['text']) ? trim($_GET['text']) : '';
|
||||||
|
|
||||||
|
if (empty($text)) {
|
||||||
|
echo json_encode(['success' => true, 'synonyms' => [], 'html' => '']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descriptor erstellen (Kleinbuchstaben, Leerzeichen durch Unterstrich)
|
||||||
|
function prepare_desc($text) {
|
||||||
|
$desc = strtolower($text);
|
||||||
|
$desc = str_replace(' ', '_', $desc);
|
||||||
|
return $desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
$desc = prepare_desc($text);
|
||||||
|
|
||||||
|
// Erst IDLinking für den Begriff finden
|
||||||
|
$sql = "SELECT IDLinking FROM Synonyms WHERE Descriptor = '" . mysqli_real_escape_string($conn, $desc) . "' LIMIT 1";
|
||||||
|
$res = mysqli_query($conn, $sql);
|
||||||
|
|
||||||
|
$synonyms = [];
|
||||||
|
$html = '';
|
||||||
|
|
||||||
|
if ($res && mysqli_num_rows($res) > 0) {
|
||||||
|
$row = mysqli_fetch_assoc($res);
|
||||||
|
$idLinking = $row['IDLinking'];
|
||||||
|
|
||||||
|
// Alle Synonyme mit dieser IDLinking laden (außer dem Begriff selbst)
|
||||||
|
$sql2 = "SELECT ID, Text, Descriptor FROM Synonyms WHERE IDLinking = $idLinking AND Descriptor != '" . mysqli_real_escape_string($conn, $desc) . "' ORDER BY Text";
|
||||||
|
$res2 = mysqli_query($conn, $sql2);
|
||||||
|
|
||||||
|
if ($res2) {
|
||||||
|
while ($syn = mysqli_fetch_assoc($res2)) {
|
||||||
|
$synonyms[] = [
|
||||||
|
'id' => $syn['ID'],
|
||||||
|
'text' => $syn['Text'],
|
||||||
|
'descriptor' => $syn['Descriptor']
|
||||||
|
];
|
||||||
|
|
||||||
|
// HTML für Anzeige generieren
|
||||||
|
$html .= '<tr>';
|
||||||
|
$html .= '<td><span class="badge" style="background-color: #e91e63;">SYN</span></td>';
|
||||||
|
$html .= '<td>' . htmlspecialchars($syn['Text']) . '</td>';
|
||||||
|
$html .= '<td class="text-end">';
|
||||||
|
$html .= '<button class="btn btn-sm btn-outline-danger" onclick="DeleteSynonym(\'' . addslashes($text) . '\', \'' . addslashes($syn['Text']) . '\')" title="Synonym entfernen">';
|
||||||
|
$html .= '<i class="fas fa-trash"></i>';
|
||||||
|
$html .= '</button>';
|
||||||
|
$html .= '</td>';
|
||||||
|
$html .= '</tr>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'synonyms' => $synonyms,
|
||||||
|
'html' => $html,
|
||||||
|
'count' => count($synonyms)
|
||||||
|
]);
|
||||||
|
|
||||||
|
mysqli_close($conn);
|
||||||
|
?>
|
||||||
75
ajax/getTreeData.php
Normal file
75
ajax/getTreeData.php
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* getTreeData.php - Liefert Klassifikationsdaten im jsTree-Format
|
||||||
|
*
|
||||||
|
* Nutzt die bestehende Treeview-Tabelle
|
||||||
|
*
|
||||||
|
* Parameter:
|
||||||
|
* - id: Parent-ID (# oder 0 für Root-Ebene)
|
||||||
|
*
|
||||||
|
* Rückgabe: JSON-Array im jsTree-Format
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
// Datenbankverbindung
|
||||||
|
include "db_connection.php";
|
||||||
|
|
||||||
|
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||||
|
if (mysqli_connect_errno()) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_set_charset($conn, "utf8");
|
||||||
|
|
||||||
|
// Parameter auslesen
|
||||||
|
$parentId = isset($_GET['id']) ? $_GET['id'] : '#';
|
||||||
|
|
||||||
|
// Root-Ebene: parent_id ist NULL oder 0
|
||||||
|
if ($parentId === '#' || $parentId === '0' || $parentId === 0) {
|
||||||
|
$sql = "SELECT id, name, text, link as href, parent_id
|
||||||
|
FROM Treeview
|
||||||
|
WHERE parent_id IS NULL OR parent_id = 0 OR parent_id = ''
|
||||||
|
ORDER BY text";
|
||||||
|
} else {
|
||||||
|
// Unterebene: Kinder des angegebenen Parents
|
||||||
|
$parentId = intval($parentId);
|
||||||
|
$sql = "SELECT id, name, text, link as href, parent_id
|
||||||
|
FROM Treeview
|
||||||
|
WHERE parent_id = $parentId
|
||||||
|
ORDER BY text";
|
||||||
|
}
|
||||||
|
|
||||||
|
$res = mysqli_query($conn, $sql);
|
||||||
|
|
||||||
|
if (!$res) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
|
||||||
|
while ($row = mysqli_fetch_assoc($res)) {
|
||||||
|
// Prüfen ob dieser Knoten Kinder hat
|
||||||
|
$childCheckSql = "SELECT COUNT(*) as cnt FROM Treeview WHERE parent_id = " . intval($row['id']);
|
||||||
|
$childRes = mysqli_query($conn, $childCheckSql);
|
||||||
|
$childRow = mysqli_fetch_assoc($childRes);
|
||||||
|
$hasChildren = ($childRow['cnt'] > 0);
|
||||||
|
|
||||||
|
$result[] = [
|
||||||
|
'id' => strval($row['id']),
|
||||||
|
'text' => $row['text'] ? $row['text'] : $row['name'],
|
||||||
|
'children' => $hasChildren,
|
||||||
|
'data' => [
|
||||||
|
'name' => $row['name'],
|
||||||
|
'href' => $row['href']
|
||||||
|
],
|
||||||
|
'icon' => $hasChildren ? 'fas fa-folder text-warning' : 'fas fa-file text-secondary'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_close($conn);
|
||||||
|
|
||||||
|
echo json_encode($result, JSON_UNESCAPED_UNICODE);
|
||||||
|
?>
|
||||||
46
ajax/getTreePath.php
Normal file
46
ajax/getTreePath.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* getTreePath.php - Liefert den Pfad von einem Knoten bis zur Wurzel
|
||||||
|
*
|
||||||
|
* Parameter (GET):
|
||||||
|
* - id: Treeview-ID des Zielknotens
|
||||||
|
*
|
||||||
|
* Rückgabe: JSON-Array mit IDs von Root bis Zielknoten (inklusive)
|
||||||
|
* Beispiel: [98106, 98107, 98113, 98115]
|
||||||
|
*/
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
include "db_connection.php";
|
||||||
|
|
||||||
|
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||||
|
if (mysqli_connect_errno()) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
mysqli_set_charset($conn, "utf8");
|
||||||
|
|
||||||
|
$targetId = isset($_GET['id']) ? intval($_GET['id']) : 0;
|
||||||
|
if ($targetId === 0) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pfad aufbauen: vom Zielknoten nach oben bis Root (parent_id = 0 oder leer)
|
||||||
|
$path = [];
|
||||||
|
$curId = $targetId;
|
||||||
|
$limit = 20; // Schutz vor Endlosschleife bei defekten Daten
|
||||||
|
|
||||||
|
while ($curId > 0 && $limit-- > 0) {
|
||||||
|
$res = mysqli_query($conn, "SELECT id, parent_id FROM Treeview WHERE id = $curId LIMIT 1");
|
||||||
|
if (!$res) break;
|
||||||
|
$row = mysqli_fetch_assoc($res);
|
||||||
|
if (!$row) break;
|
||||||
|
|
||||||
|
array_unshift($path, intval($row['id'])); // vorne einfügen → Root zuerst
|
||||||
|
$parentId = intval($row['parent_id']);
|
||||||
|
if ($parentId === 0) break;
|
||||||
|
$curId = $parentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_close($conn);
|
||||||
|
echo json_encode($path, JSON_UNESCAPED_UNICODE);
|
||||||
|
?>
|
||||||
561
ajax/libAuthorities.php
Normal file
561
ajax/libAuthorities.php
Normal file
@ -0,0 +1,561 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* libAuthorities.php - CRUD-Klasse für Thesaurus-Datenbank
|
||||||
|
*
|
||||||
|
* Sicherheitsverbesserungen:
|
||||||
|
* - Prepared Statements mit benannten Parametern
|
||||||
|
* - Whitelist für authType
|
||||||
|
* - Input-Validierung
|
||||||
|
* - Fehlerbehandlung
|
||||||
|
*/
|
||||||
|
|
||||||
|
class CRUD
|
||||||
|
{
|
||||||
|
protected $db;
|
||||||
|
|
||||||
|
// Whitelist für erlaubte Typen
|
||||||
|
private $allowedTypes = ['Subject', 'Person', 'Corporate', 'Publisher', 'Classification'];
|
||||||
|
|
||||||
|
// Whitelist für erlaubte Sortierfelder
|
||||||
|
private $allowedSortFields = [
|
||||||
|
'Anchor.ID', 'Anchor.Text', 'Anchor.Type', 'Anchor.DetailType',
|
||||||
|
'Entry.Text', 'Entry.ID', 'Entry.DateCreated', 'Entry.DateModified'
|
||||||
|
];
|
||||||
|
|
||||||
|
function __construct()
|
||||||
|
{
|
||||||
|
$this->db = DB();
|
||||||
|
}
|
||||||
|
|
||||||
|
function __destruct()
|
||||||
|
{
|
||||||
|
$this->db = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validiert den authType gegen Whitelist
|
||||||
|
*/
|
||||||
|
private function validateAuthType($authType)
|
||||||
|
{
|
||||||
|
if (!in_array($authType, $this->allowedTypes)) {
|
||||||
|
throw new InvalidArgumentException("Ungültiger Typ: $authType");
|
||||||
|
}
|
||||||
|
return $authType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validiert und bereinigt Sortierfeld
|
||||||
|
*/
|
||||||
|
private function validateSortField($sort)
|
||||||
|
{
|
||||||
|
if (empty($sort)) {
|
||||||
|
return 'Entry.Text';
|
||||||
|
}
|
||||||
|
|
||||||
|
$sortField = trim(str_replace(['ASC', 'DESC', 'asc', 'desc'], '', $sort));
|
||||||
|
$sortField = trim($sortField);
|
||||||
|
|
||||||
|
if (!in_array($sortField, $this->allowedSortFields)) {
|
||||||
|
return 'Entry.Text';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validiert Integer-Werte
|
||||||
|
*/
|
||||||
|
private function validateInt($value, $min = 0)
|
||||||
|
{
|
||||||
|
$intVal = intval($value);
|
||||||
|
return ($intVal >= $min) ? $intVal : $min;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baut ein SQL-LIKE-Pattern aus einem Suchterm.
|
||||||
|
* Führendes * → Linkstrunkierung (%...)
|
||||||
|
* Abschließendes * → Rechtstrunkierung (...%)
|
||||||
|
* Kein * → nur Rechtstrunkierung (Standard-Verhalten)
|
||||||
|
*/
|
||||||
|
private function buildSearchPattern(string $term): string
|
||||||
|
{
|
||||||
|
$leftTrunc = str_starts_with($term, '*');
|
||||||
|
$rightTrunc = str_ends_with($term, '*');
|
||||||
|
|
||||||
|
$clean = trim($term, '*');
|
||||||
|
$clean = addcslashes($clean, '%_\\');
|
||||||
|
|
||||||
|
return ($leftTrunc ? '%' : '') .
|
||||||
|
$clean .
|
||||||
|
($rightTrunc ? '%' : '%');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dubletten-Prüfung über Anchor.Text (normalisierte Form).
|
||||||
|
*
|
||||||
|
* Anchor.Text enthält den normalisierten Descriptor (Kleinbuchstaben,
|
||||||
|
* Sonderzeichen durch '_' ersetzt, erzeugt durch prepare_desc()).
|
||||||
|
* Diese Prüfung ist robuster als ein Vergleich auf Entry.Text, weil
|
||||||
|
* sie Schreibvarianten (Groß-/Kleinschreibung, Umlaute, Leerzeichen)
|
||||||
|
* zuverlässig als Dublette erkennt.
|
||||||
|
*
|
||||||
|
* @param string $authType Typ-Whitelist-Wert ('Subject', 'Person', ...)
|
||||||
|
* @param string $normalizedText Ergebnis von prepare_desc($term)
|
||||||
|
* @return int Anzahl gefundener Einträge (0 = keine Dublette)
|
||||||
|
*/
|
||||||
|
public function checkDuplicateByNormalizedText($authType, $normalizedText)
|
||||||
|
{
|
||||||
|
$authType = $this->validateAuthType($authType);
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare(
|
||||||
|
"SELECT COUNT(*) AS count
|
||||||
|
FROM Anchor
|
||||||
|
WHERE Text = :normalizedText
|
||||||
|
AND Type = :authType"
|
||||||
|
);
|
||||||
|
$stmt->execute([
|
||||||
|
':normalizedText' => $normalizedText,
|
||||||
|
':authType' => $authType
|
||||||
|
]);
|
||||||
|
|
||||||
|
$rec = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
return $rec ? intval($rec['count']) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readAuthorityNumbersOfRecords($authType, $search, $id, $sharp)
|
||||||
|
{
|
||||||
|
error_log("DEBUG search='" . $search . "' pattern='" . $this->buildSearchPattern($search) . "'");
|
||||||
|
|
||||||
|
$authType = $this->validateAuthType($authType);
|
||||||
|
$id = $this->validateInt($id);
|
||||||
|
|
||||||
|
$params = [':authType' => $authType];
|
||||||
|
|
||||||
|
$query = "SELECT COUNT(*) as count FROM Anchor
|
||||||
|
JOIN Entry ON (Anchor.ID = Entry.ID)
|
||||||
|
WHERE Type = :authType";
|
||||||
|
|
||||||
|
if (strlen($search) > 0 && $sharp == false) {
|
||||||
|
$query .= " AND LOWER(Entry.CompleteText) LIKE LOWER(:search)";
|
||||||
|
$params[':search'] = $this->buildSearchPattern($search);
|
||||||
|
}
|
||||||
|
if (strlen($search) > 0 && $sharp == true) {
|
||||||
|
$query .= " AND LOWER(Entry.Text) = LOWER(:search)";
|
||||||
|
$params[':search'] = $search;
|
||||||
|
}
|
||||||
|
if ($id > 0) {
|
||||||
|
$query .= " AND Entry.ID = :id";
|
||||||
|
$params[':id'] = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($query);
|
||||||
|
$stmt->execute($params);
|
||||||
|
|
||||||
|
$rec = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
return $rec ? intval($rec['count']) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readAuthorityEntriesByID($authType, $id)
|
||||||
|
{
|
||||||
|
$authType = $this->validateAuthType($authType);
|
||||||
|
$id = $this->validateInt($id, 1);
|
||||||
|
|
||||||
|
$query = "SELECT Anchor.ID AS ID, Anchor.DetailType AS DetailType,
|
||||||
|
Anchor.Type AS Type, Anchor.Classification AS Classification,
|
||||||
|
Entry.Text as Text, Entry.Comment as Scopenote
|
||||||
|
FROM Anchor
|
||||||
|
JOIN Entry ON (Anchor.ID = Entry.ID)
|
||||||
|
WHERE Anchor.ID = :id AND Type = :authType";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($query);
|
||||||
|
$stmt->execute([':id' => $id, ':authType' => $authType]);
|
||||||
|
|
||||||
|
$count = $stmt->rowCount();
|
||||||
|
$ret = [];
|
||||||
|
|
||||||
|
while ($rec = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$relations = $this->getRelations($authType, $rec['ID']);
|
||||||
|
$ret[] = [
|
||||||
|
"ID" => $rec['ID'],
|
||||||
|
"Descriptor" => $relations['Descriptor'],
|
||||||
|
"Text" => $rec['Text'],
|
||||||
|
"DetailType" => $rec['DetailType'],
|
||||||
|
"Type" => $rec['Type'],
|
||||||
|
"Scopenote" => $rec['Scopenote'],
|
||||||
|
"Relations" => $relations['Data'],
|
||||||
|
"Classification" => $rec['Classification']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ["COUNT" => $count, "RECORDS" => $ret];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readAuthorityEntries($authType, $offset, $search, $id, $sort, $max)
|
||||||
|
{
|
||||||
|
error_log("DEBUG search='" . $search . "' pattern='" . $this->buildSearchPattern($search) . "'", 3, "error.log");
|
||||||
|
|
||||||
|
|
||||||
|
$authType = $this->validateAuthType($authType);
|
||||||
|
$offset = $this->validateInt($offset);
|
||||||
|
$max = $this->validateInt($max, 1);
|
||||||
|
$id = $this->validateInt($id);
|
||||||
|
$sort = $this->validateSortField($sort);
|
||||||
|
|
||||||
|
$params = [':authType' => $authType];
|
||||||
|
|
||||||
|
$query = "SELECT Anchor.ID AS ID, Anchor.DetailType AS DetailType,
|
||||||
|
Anchor.Type AS Type, Anchor.Classification AS Classification,
|
||||||
|
Entry.Text as Text, Entry.Comment as Scopenote
|
||||||
|
FROM Anchor
|
||||||
|
JOIN Entry ON (Anchor.ID = Entry.ID)
|
||||||
|
WHERE Type = :authType";
|
||||||
|
|
||||||
|
if (strlen($search) > 0) {
|
||||||
|
$query .= " AND LOWER(Entry.CompleteText) LIKE LOWER(:search)";
|
||||||
|
$params[':search'] = $this->buildSearchPattern($search);
|
||||||
|
}
|
||||||
|
if ($id > 0) {
|
||||||
|
$query .= " AND Entry.ID = :id";
|
||||||
|
$params[':id'] = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// $query .= " ORDER BY " . $sort . " LIMIT " . intval($offset) . "," . intval($max);
|
||||||
|
|
||||||
|
if ($authType === 'Classification') {
|
||||||
|
$orderClause = "
|
||||||
|
SUBSTRING_INDEX(Anchor.Text, ' ', 1),
|
||||||
|
CAST(
|
||||||
|
CASE
|
||||||
|
WHEN LOCATE(' ', Anchor.Text) > 0
|
||||||
|
THEN SUBSTRING_INDEX(SUBSTRING_INDEX(Anchor.Text, ' ', -1), '.', 1)
|
||||||
|
ELSE NULL
|
||||||
|
END AS UNSIGNED
|
||||||
|
),
|
||||||
|
CAST(
|
||||||
|
CASE
|
||||||
|
WHEN LOCATE('.', Anchor.Text) > 0
|
||||||
|
THEN SUBSTRING_INDEX(Anchor.Text, '.', -1)
|
||||||
|
ELSE NULL
|
||||||
|
END AS UNSIGNED
|
||||||
|
)
|
||||||
|
";
|
||||||
|
} else {
|
||||||
|
$orderClause = $sort . " COLLATE utf8mb4_unicode_ci";
|
||||||
|
}
|
||||||
|
|
||||||
|
error_log("DEBUG ORDER BY: " . $orderClause, 3, "/var/www/tools/html/Thesaurus/error.log");
|
||||||
|
|
||||||
|
$query .= " ORDER BY " . $orderClause . " LIMIT " . intval($offset) . "," . intval($max);
|
||||||
|
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($query);
|
||||||
|
$stmt->execute($params);
|
||||||
|
|
||||||
|
$count = $stmt->rowCount();
|
||||||
|
$ret = [];
|
||||||
|
|
||||||
|
while ($rec = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$relations = $this->getRelations($authType, $rec['ID']);
|
||||||
|
|
||||||
|
if ($authType === 'Subject') {
|
||||||
|
$synonyms = $this->getSynonyms($rec['Text']);
|
||||||
|
$ret[] = [
|
||||||
|
"ID" => $rec['ID'],
|
||||||
|
"Descriptor" => $relations['Descriptor'],
|
||||||
|
"Text" => $rec['Text'],
|
||||||
|
"DetailType" => $rec['DetailType'],
|
||||||
|
"Type" => $rec['Type'],
|
||||||
|
"Scopenote" => $rec['Scopenote'],
|
||||||
|
"Relations" => $relations['Data'],
|
||||||
|
"Classification" => $rec['Classification'],
|
||||||
|
"Synonyms" => $synonyms['Synonyms'],
|
||||||
|
"Search" => $synonyms['Search']
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$ret[] = [
|
||||||
|
"ID" => $rec['ID'],
|
||||||
|
"Descriptor" => $relations['Descriptor'],
|
||||||
|
"Text" => $rec['Text'],
|
||||||
|
"DetailType" => $rec['DetailType'],
|
||||||
|
"Type" => $rec['Type'],
|
||||||
|
"Scopenote" => $rec['Scopenote'],
|
||||||
|
"Relations" => $relations['Data'],
|
||||||
|
"Classification" => $rec['Classification']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ["COUNT" => $count, "RECORDS" => $ret];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelations($authType, $id)
|
||||||
|
{
|
||||||
|
$authType = $this->validateAuthType($authType);
|
||||||
|
$id = $this->validateInt($id, 1);
|
||||||
|
|
||||||
|
$query = "SELECT Linking.ID as LinkingID, IDEntry, Entry.Text, Entry.Comment,
|
||||||
|
Relationtype, DetailType
|
||||||
|
FROM Linking
|
||||||
|
JOIN Entry ON (Linking.IDEntry = Entry.ID)
|
||||||
|
JOIN Anchor ON (IDAnchor = Anchor.ID)
|
||||||
|
WHERE IDAnchor = :id AND Anchor.Type = :authType AND Relationtype != ''
|
||||||
|
ORDER BY Relationtype";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($query);
|
||||||
|
$stmt->execute([':id' => $id, ':authType' => $authType]);
|
||||||
|
|
||||||
|
$descriptor = true;
|
||||||
|
$ret = [];
|
||||||
|
|
||||||
|
while ($rec = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
if (strtolower($rec['Relationtype']) === "use") {
|
||||||
|
$descriptor = false;
|
||||||
|
}
|
||||||
|
$ret[] = [
|
||||||
|
"IDLinking" => $rec['LinkingID'],
|
||||||
|
"IDEntry" => $rec['IDEntry'],
|
||||||
|
"IDRelation" => $rec['IDEntry'],
|
||||||
|
"Detailtype" => $rec['DetailType'],
|
||||||
|
"TextRelation" => $rec['Text'],
|
||||||
|
"CommentRelation"=> $rec['Comment'],
|
||||||
|
"Relationtype" => $rec['Relationtype']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ["Descriptor" => $descriptor, "Data" => $ret];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSynonyms($input)
|
||||||
|
{
|
||||||
|
$desc = $this->prepare_desc($input);
|
||||||
|
|
||||||
|
$query = "SELECT IDLinking FROM Synonyms WHERE Descriptor = :desc";
|
||||||
|
$stmt = $this->db->prepare($query);
|
||||||
|
$stmt->execute([':desc' => $desc]);
|
||||||
|
|
||||||
|
$ret = '';
|
||||||
|
$retsearch = "topic:'" . addslashes($input) . "' OR ";
|
||||||
|
|
||||||
|
while ($rec = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$idSearch = intval($rec['IDLinking']);
|
||||||
|
|
||||||
|
$query2 = "SELECT Text FROM Synonyms WHERE IDLinking = :idLinking";
|
||||||
|
$stmt2 = $this->db->prepare($query2);
|
||||||
|
$stmt2->execute([':idLinking' => $idSearch]);
|
||||||
|
|
||||||
|
while ($rec2 = $stmt2->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
if ($rec2['Text'] === $input) continue;
|
||||||
|
$ret .= $rec2['Text'] . ", ";
|
||||||
|
$retsearch .= "topic:'" . addslashes($rec2['Text']) . "' OR ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($ret) == 0) {
|
||||||
|
return ["Synonyms" => '', "Search" => ''];
|
||||||
|
} else {
|
||||||
|
$synonyms = trim(substr($ret, 0, -2));
|
||||||
|
$search = trim(substr($retsearch, 0, -4));
|
||||||
|
return ["Synonyms" => $synonyms, "Search" => $search];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntryText($authType, $request)
|
||||||
|
{
|
||||||
|
$authType = $this->validateAuthType($authType);
|
||||||
|
|
||||||
|
$query = "SELECT Anchor.ID AS AnchorID, Entry.Text as Text
|
||||||
|
FROM Anchor
|
||||||
|
JOIN Entry ON (Anchor.ID = Entry.ID)
|
||||||
|
WHERE Anchor.Text LIKE LOWER(:request) AND Anchor.Type = :authType
|
||||||
|
ORDER BY Entry.Text";
|
||||||
|
|
||||||
|
$stmt = $this->db->prepare($query);
|
||||||
|
$stmt->execute([':request' => '%' . $request . '%', ':authType' => $authType]);
|
||||||
|
|
||||||
|
$ret = [];
|
||||||
|
while ($rec = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$ret[] = ["Text" => $rec['Text'], "ID" => $rec['AnchorID']];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function insertNewTerm($authType, $term, $desc, $detailtype, $classification, $scopenote, $completetext)
|
||||||
|
{
|
||||||
|
$authType = $this->validateAuthType($authType);
|
||||||
|
|
||||||
|
$query = $this->db->prepare(
|
||||||
|
"INSERT INTO Anchor (ID, Text, DetailType, Type, Classification)
|
||||||
|
VALUES (:ID, :Text, :DetailType, :Type, :Classification)"
|
||||||
|
);
|
||||||
|
|
||||||
|
$query->execute([
|
||||||
|
':ID' => null,
|
||||||
|
':Text' => $desc,
|
||||||
|
':DetailType' => $detailtype,
|
||||||
|
':Type' => $authType,
|
||||||
|
':Classification' => $classification
|
||||||
|
]);
|
||||||
|
|
||||||
|
$IDAnchor = $this->db->lastInsertId();
|
||||||
|
|
||||||
|
$query = $this->db->prepare(
|
||||||
|
"INSERT INTO Entry (ID, Text, Comment, Language, CompleteText, DateCreated, DateModified)
|
||||||
|
VALUES (:ID, :Text, :Comment, :Language, :CompleteText, :DateCreated, :DateModified)"
|
||||||
|
);
|
||||||
|
|
||||||
|
$query->execute([
|
||||||
|
':ID' => $IDAnchor,
|
||||||
|
':Text' => $term,
|
||||||
|
':Comment' => $scopenote,
|
||||||
|
':Language' => 'de',
|
||||||
|
':CompleteText' => $completetext,
|
||||||
|
':DateCreated' => date('Y-m-d H:i:s'),
|
||||||
|
':DateModified' => '0000-00-00 00:00:00'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$IDEntry = $IDAnchor;
|
||||||
|
|
||||||
|
return ["IDAnchor" => $IDAnchor, "IDEntry" => $IDEntry, "IDLinking" => 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function writeNewRelation($anchorID, $relationType, $relationID)
|
||||||
|
{
|
||||||
|
$anchorID = $this->validateInt($anchorID, 1);
|
||||||
|
$relationID = $this->validateInt($relationID, 1);
|
||||||
|
|
||||||
|
$allowedRelations = ['BT', 'NT', 'RT', 'USE', 'UF'];
|
||||||
|
if (!in_array(strtoupper($relationType), $allowedRelations)) {
|
||||||
|
throw new InvalidArgumentException("Ungültiger Relationstyp: $relationType");
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = $this->db->prepare(
|
||||||
|
"INSERT INTO Linking (ID, IDAnchor, IDEntry, Relationtype)
|
||||||
|
VALUES (:ID, :IDAnchor, :IDEntry, :Relationtype)"
|
||||||
|
);
|
||||||
|
|
||||||
|
$query->execute([
|
||||||
|
':ID' => null,
|
||||||
|
':IDAnchor' => $anchorID,
|
||||||
|
':IDEntry' => $relationID,
|
||||||
|
':Relationtype' => $relationType
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this->db->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteRelation($anchorID, $linkingID)
|
||||||
|
{
|
||||||
|
$linkingID = $this->validateInt($linkingID, 1);
|
||||||
|
|
||||||
|
$query = $this->db->prepare("DELETE FROM Linking WHERE ID = :id");
|
||||||
|
$query->execute([':id' => $linkingID]);
|
||||||
|
|
||||||
|
return $linkingID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteTerm($anchorID)
|
||||||
|
{
|
||||||
|
$anchorID = $this->validateInt($anchorID, 1);
|
||||||
|
$ret = [];
|
||||||
|
|
||||||
|
$query = $this->db->prepare("DELETE FROM Linking WHERE IDAnchor = :id");
|
||||||
|
$query->execute([':id' => $anchorID]);
|
||||||
|
$ret[] = "Linking gelöscht";
|
||||||
|
|
||||||
|
$query = $this->db->prepare("DELETE FROM Entry WHERE ID = :id");
|
||||||
|
$query->execute([':id' => $anchorID]);
|
||||||
|
$ret[] = "Entry gelöscht";
|
||||||
|
|
||||||
|
$query = $this->db->prepare("DELETE FROM Anchor WHERE ID = :id");
|
||||||
|
$query->execute([':id' => $anchorID]);
|
||||||
|
$ret[] = "Anchor gelöscht";
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateTerm($anchorID, $authType, $desc, $term, $type, $detailtype, $classification, $scopenote, $completetext)
|
||||||
|
{
|
||||||
|
$anchorID = $this->validateInt($anchorID, 1);
|
||||||
|
$authType = $this->validateAuthType($authType);
|
||||||
|
|
||||||
|
$query = $this->db->prepare(
|
||||||
|
"UPDATE Anchor
|
||||||
|
SET Text = :text, Type = :type, DetailType = :detailtype, Classification = :classification
|
||||||
|
WHERE ID = :id"
|
||||||
|
);
|
||||||
|
|
||||||
|
$query->execute([
|
||||||
|
':text' => $desc,
|
||||||
|
':type' => $authType,
|
||||||
|
':detailtype' => $detailtype,
|
||||||
|
':classification' => $classification,
|
||||||
|
':id' => $anchorID
|
||||||
|
]);
|
||||||
|
|
||||||
|
$query = $this->db->prepare(
|
||||||
|
"UPDATE Entry
|
||||||
|
SET Text = :text, Comment = :comment, Language = :language,
|
||||||
|
CompleteText = :completetext, DateModified = :datemodified
|
||||||
|
WHERE ID = :id"
|
||||||
|
);
|
||||||
|
|
||||||
|
$query->execute([
|
||||||
|
':text' => $term,
|
||||||
|
':comment' => $scopenote,
|
||||||
|
':language' => 'de',
|
||||||
|
':completetext' => $completetext,
|
||||||
|
':datemodified' => date('Y-m-d H:i:s'),
|
||||||
|
':id' => $anchorID
|
||||||
|
]);
|
||||||
|
|
||||||
|
return "OK";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStatistics($dateStart, $dateEnd)
|
||||||
|
{
|
||||||
|
if (!preg_match('/^\d{4}-\d{2}-\d{2}/', $dateStart) ||
|
||||||
|
!preg_match('/^\d{4}-\d{2}-\d{2}/', $dateEnd)) {
|
||||||
|
throw new InvalidArgumentException("Ungültiges Datumsformat");
|
||||||
|
}
|
||||||
|
|
||||||
|
$r = [];
|
||||||
|
$dateTypes = ["DateCreated", "DateModified"];
|
||||||
|
|
||||||
|
foreach ($this->allowedTypes as $type) {
|
||||||
|
foreach ($dateTypes as $dateType) {
|
||||||
|
$query = $this->db->prepare(
|
||||||
|
"SELECT COUNT(*) AS COUNT
|
||||||
|
FROM Anchor
|
||||||
|
JOIN Entry ON (Anchor.ID = Entry.ID)
|
||||||
|
WHERE Type = :type AND $dateType BETWEEN :dateStart AND :dateEnd"
|
||||||
|
);
|
||||||
|
|
||||||
|
$query->execute([
|
||||||
|
':type' => $type,
|
||||||
|
':dateStart' => $dateStart,
|
||||||
|
':dateEnd' => $dateEnd
|
||||||
|
]);
|
||||||
|
|
||||||
|
$rec = $query->fetch(PDO::FETCH_ASSOC);
|
||||||
|
$r[$type][$dateType] = $rec ? intval($rec['COUNT']) : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $r;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepare_desc($text)
|
||||||
|
{
|
||||||
|
$desc = is_array($text) ? $text[0] : $text;
|
||||||
|
$text = strtolower($desc);
|
||||||
|
$desc = str_replace(' ', '_', $text);
|
||||||
|
|
||||||
|
$search = ['ü', 'ä', 'ö', 'ß', '.', ',', 'Ö', 'Ü', 'Ä', '[', ']', '<', '>', '""'];
|
||||||
|
$replace = ['ue', 'ae', 'oe', 'ss', '', '', 'oe', 'ue', 'ae', '_', '_', '<', '>', '"'];
|
||||||
|
$desc = str_replace($search, $replace, $desc);
|
||||||
|
|
||||||
|
return $desc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
134
ajax/newClassification.php
Normal file
134
ajax/newClassification.php
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* newClassification.php - Erstellt einen neuen Klassifikationseintrag
|
||||||
|
*
|
||||||
|
* Nutzt die CRUD-Klasse aus libAuthorities.php.
|
||||||
|
* Nach dem Anlegen in Anchor/Entry wird automatisch ein Eintrag
|
||||||
|
* in der Treeview-Tabelle erstellt und an der korrekten Position
|
||||||
|
* im Baum eingehängt.
|
||||||
|
*/
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
include "db_connection.php";
|
||||||
|
include "libAuthorities.php";
|
||||||
|
|
||||||
|
// Parameter aus JavaScript
|
||||||
|
$term = isset($_POST['term']) ? trim($_POST['term']) : '';
|
||||||
|
$notation = isset($_POST['notation']) ? trim($_POST['notation']) : '';
|
||||||
|
$detailType = isset($_POST['detailtype']) ? trim($_POST['detailtype']) : '';
|
||||||
|
$classification = isset($_POST['classification']) ? trim($_POST['classification']) : '';
|
||||||
|
$scopenote = isset($_POST['scopenote']) ? trim($_POST['scopenote']) : '';
|
||||||
|
|
||||||
|
// Notation hat Vorrang vor Classification
|
||||||
|
if (!empty($notation)) {
|
||||||
|
$classification = $notation;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($term)) {
|
||||||
|
echo json_encode(['status' => 400, 'message' => 'Bitte Text eingeben!']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ermittelt die Eltern-Notation durch Abschneiden des letzten Segments.
|
||||||
|
*
|
||||||
|
* Beispiele:
|
||||||
|
* "T 4.10" → "T 4"
|
||||||
|
* "G 1.1.1" → "G 1.1"
|
||||||
|
* "G 1.1" → "G 1"
|
||||||
|
* "G 1" → "G"
|
||||||
|
* "G" → null (Root-Ebene)
|
||||||
|
*/
|
||||||
|
function getParentNotation($notation) {
|
||||||
|
$lastDot = strrpos($notation, '.');
|
||||||
|
if ($lastDot !== false) {
|
||||||
|
return substr($notation, 0, $lastDot);
|
||||||
|
}
|
||||||
|
$lastSpace = strrpos($notation, ' ');
|
||||||
|
if ($lastSpace !== false) {
|
||||||
|
return substr($notation, 0, $lastSpace);
|
||||||
|
}
|
||||||
|
return null; // Root
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sucht die Treeview-ID für eine gegebene Notation.
|
||||||
|
* Die Notation steht am Anfang des name-Feldes, gefolgt von einem Leerzeichen
|
||||||
|
* oder ist identisch mit dem name-Feld (bei Blatt-Knoten ohne Titel).
|
||||||
|
*
|
||||||
|
* @param mysqli $conn
|
||||||
|
* @param string $notation z.B. "T 4"
|
||||||
|
* @return int Treeview-ID oder 0 wenn nicht gefunden
|
||||||
|
*/
|
||||||
|
function findTreeviewIdByNotation($conn, $notation) {
|
||||||
|
$escaped = mysqli_real_escape_string($conn, $notation);
|
||||||
|
// Entweder exakter Match oder Notation + Leerzeichen am Anfang
|
||||||
|
$sql = "SELECT id FROM Treeview
|
||||||
|
WHERE name = '$escaped'
|
||||||
|
OR name LIKE '$escaped %'
|
||||||
|
ORDER BY id
|
||||||
|
LIMIT 1";
|
||||||
|
$res = mysqli_query($conn, $sql);
|
||||||
|
if ($res) {
|
||||||
|
$row = mysqli_fetch_assoc($res);
|
||||||
|
if ($row) return intval($row['id']);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$crud = new CRUD();
|
||||||
|
|
||||||
|
// Normalisierten Descriptor erzeugen
|
||||||
|
$desc = $crud->prepare_desc($term);
|
||||||
|
$completetext = $term;
|
||||||
|
|
||||||
|
// Dubletten-Prüfung (normalisiert)
|
||||||
|
$existing = $crud->checkDuplicateByNormalizedText('Classification', $desc);
|
||||||
|
if ($existing > 0) {
|
||||||
|
echo json_encode(['status' => 409, 'message' => 'Eine Klassifikation mit diesem Text existiert bereits']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neuen Eintrag in Anchor + Entry erstellen
|
||||||
|
$result = $crud->insertNewTerm('Classification', $term, $desc, $detailType, $classification, $scopenote, $completetext);
|
||||||
|
|
||||||
|
// --- Treeview-Eintrag anlegen ---
|
||||||
|
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||||
|
if (!mysqli_connect_errno()) {
|
||||||
|
mysqli_set_charset($conn, "utf8");
|
||||||
|
|
||||||
|
// Parent-ID ermitteln
|
||||||
|
$parentId = 0; // Default: Root
|
||||||
|
if (!empty($classification)) {
|
||||||
|
$parentNotation = getParentNotation($classification);
|
||||||
|
if ($parentNotation !== null) {
|
||||||
|
$foundId = findTreeviewIdByNotation($conn, $parentNotation);
|
||||||
|
if ($foundId > 0) {
|
||||||
|
$parentId = $foundId;
|
||||||
|
}
|
||||||
|
// Hinweis: wenn Parent nicht gefunden → landet im Root (parent_id = 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// name und text = vollständiger Term (Notation + Bezeichnung), link = '#'
|
||||||
|
$nameEscaped = mysqli_real_escape_string($conn, $term);
|
||||||
|
$insertSql = "INSERT INTO Treeview (name, text, link, parent_id)
|
||||||
|
VALUES ('$nameEscaped', '$nameEscaped', '#', '$parentId')";
|
||||||
|
mysqli_query($conn, $insertSql);
|
||||||
|
$treeviewId = mysqli_insert_id($conn);
|
||||||
|
mysqli_close($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 200,
|
||||||
|
'message' => 'Klassifikation erfolgreich erstellt',
|
||||||
|
'Anchor' => $result['IDAnchor'],
|
||||||
|
'Entry' => $result['IDEntry'],
|
||||||
|
'Linking' => $result['IDLinking'],
|
||||||
|
'TreeviewID' => $treeviewId ?? 0
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['status' => 500, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
52
ajax/newCorporate.php
Normal file
52
ajax/newCorporate.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* newCorporate.php - Erstellt einen neuen Körperschaftseintrag
|
||||||
|
*
|
||||||
|
* Nutzt die CRUD-Klasse aus libAuthorities.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
include "db_connection.php";
|
||||||
|
include "libAuthorities.php";
|
||||||
|
|
||||||
|
// Parameter aus JavaScript
|
||||||
|
$term = isset($_POST['term']) ? trim($_POST['term']) : '';
|
||||||
|
$detailType = isset($_POST['detailtype']) ? trim($_POST['detailtype']) : '';
|
||||||
|
$classification = isset($_POST['classification']) ? trim($_POST['classification']) : '';
|
||||||
|
$scopenote = isset($_POST['scopenote']) ? trim($_POST['scopenote']) : '';
|
||||||
|
|
||||||
|
if (empty($term)) {
|
||||||
|
echo json_encode(['status' => 400, 'message' => 'Bitte Namen eingeben!']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$crud = new CRUD();
|
||||||
|
|
||||||
|
// Descriptor erstellen (für Anchor.Text)
|
||||||
|
$desc = $crud->prepare_desc($term);
|
||||||
|
$completetext = $term;
|
||||||
|
|
||||||
|
// Prüfen ob bereits existiert
|
||||||
|
$existing = $crud->readAuthorityNumbersOfRecords('Corporate', $term, 0, true);
|
||||||
|
if ($existing > 0) {
|
||||||
|
echo json_encode(['status' => 409, 'message' => 'Eine Körperschaft mit diesem Namen existiert bereits']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neuen Eintrag erstellen
|
||||||
|
$result = $crud->insertNewTerm('Corporate', $term, $desc, $detailType, $classification, $scopenote, $completetext);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 200,
|
||||||
|
'message' => 'Körperschaft erfolgreich erstellt',
|
||||||
|
'Anchor' => $result['IDAnchor'],
|
||||||
|
'Entry' => $result['IDEntry'],
|
||||||
|
'Linking' => $result['IDLinking']
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['status' => 500, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
51
ajax/newPerson.php
Normal file
51
ajax/newPerson.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* newPerson.php - Erstellt einen neuen Personeneintrag
|
||||||
|
*
|
||||||
|
* Nutzt die CRUD-Klasse aus libAuthorities.php
|
||||||
|
*/
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
include "db_connection.php";
|
||||||
|
include "libAuthorities.php";
|
||||||
|
|
||||||
|
// Parameter aus JavaScript
|
||||||
|
$term = isset($_POST['term']) ? trim($_POST['term']) : '';
|
||||||
|
$detailType = isset($_POST['detailtype']) ? trim($_POST['detailtype']) : '';
|
||||||
|
$classification = isset($_POST['classification']) ? trim($_POST['classification']) : '';
|
||||||
|
$scopenote = isset($_POST['scopenote']) ? trim($_POST['scopenote']) : '';
|
||||||
|
|
||||||
|
if (empty($term)) {
|
||||||
|
echo json_encode(['status' => 400, 'message' => 'Bitte Namen eingeben!']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$crud = new CRUD();
|
||||||
|
|
||||||
|
// Normalisierten Descriptor-Text erzeugen (entspricht Anchor.Text in der DB)
|
||||||
|
$desc = $crud->prepare_desc($term);
|
||||||
|
$completetext = $term;
|
||||||
|
|
||||||
|
// Dubletten-Prüfung gegen Anchor.Text (normalisierte Form)
|
||||||
|
// Verhindert Fehlalarme durch abweichende Groß-/Kleinschreibung oder Sonderzeichen
|
||||||
|
$existing = $crud->checkDuplicateByNormalizedText('Person', $desc);
|
||||||
|
if ($existing > 0) {
|
||||||
|
echo json_encode(['status' => 409, 'message' => 'Eine Person mit diesem Namen existiert bereits']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neuen Eintrag erstellen
|
||||||
|
$result = $crud->insertNewTerm('Person', $term, $desc, $detailType, $classification, $scopenote, $completetext);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 200,
|
||||||
|
'message' => 'Person erfolgreich erstellt',
|
||||||
|
'Anchor' => $result['IDAnchor'],
|
||||||
|
'Entry' => $result['IDEntry'],
|
||||||
|
'Linking' => $result['IDLinking']
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['status' => 500, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
52
ajax/newPublisher.php
Normal file
52
ajax/newPublisher.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* newPublisher.php - Erstellt einen neuen Verlagseintrag
|
||||||
|
*
|
||||||
|
* Nutzt die CRUD-Klasse aus libAuthorities.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
include "db_connection.php";
|
||||||
|
include "libAuthorities.php";
|
||||||
|
|
||||||
|
// Parameter aus JavaScript
|
||||||
|
$term = isset($_POST['term']) ? trim($_POST['term']) : '';
|
||||||
|
$detailType = isset($_POST['detailtype']) ? trim($_POST['detailtype']) : '';
|
||||||
|
$classification = isset($_POST['classification']) ? trim($_POST['classification']) : '';
|
||||||
|
$scopenote = isset($_POST['scopenote']) ? trim($_POST['scopenote']) : '';
|
||||||
|
|
||||||
|
if (empty($term)) {
|
||||||
|
echo json_encode(['status' => 400, 'message' => 'Bitte Namen eingeben!']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$crud = new CRUD();
|
||||||
|
|
||||||
|
// Descriptor erstellen (für Anchor.Text)
|
||||||
|
$desc = $crud->prepare_desc($term);
|
||||||
|
$completetext = $term;
|
||||||
|
|
||||||
|
// Prüfen ob bereits existiert
|
||||||
|
$existing = $crud->readAuthorityNumbersOfRecords('Publisher', $term, 0, true);
|
||||||
|
if ($existing > 0) {
|
||||||
|
echo json_encode(['status' => 409, 'message' => 'Ein Verlag mit diesem Namen existiert bereits']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neuen Eintrag erstellen
|
||||||
|
$result = $crud->insertNewTerm('Publisher', $term, $desc, $detailType, $classification, $scopenote, $completetext);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 200,
|
||||||
|
'message' => 'Verlag erfolgreich erstellt',
|
||||||
|
'Anchor' => $result['IDAnchor'],
|
||||||
|
'Entry' => $result['IDEntry'],
|
||||||
|
'Linking' => $result['IDLinking']
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['status' => 500, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
50
ajax/newSubject.php
Normal file
50
ajax/newSubject.php
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* newSubject.php - Erstellt einen neuen Schlagwort-Eintrag
|
||||||
|
*
|
||||||
|
* Nutzt die CRUD-Klasse aus libAuthorities.php
|
||||||
|
*/
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
include "db_connection.php";
|
||||||
|
include "libAuthorities.php";
|
||||||
|
|
||||||
|
// Parameter aus JavaScript (SubjectScript.js → CreateNewEntry)
|
||||||
|
$term = isset($_POST['term']) ? trim($_POST['term']) : '';
|
||||||
|
$detailType = isset($_POST['detailtype']) ? trim($_POST['detailtype']) : '';
|
||||||
|
$classification = isset($_POST['classification']) ? trim($_POST['classification']) : '';
|
||||||
|
$scopenote = isset($_POST['scopenote']) ? trim($_POST['scopenote']) : '';
|
||||||
|
|
||||||
|
if (empty($term)) {
|
||||||
|
echo json_encode(['status' => 400, 'message' => 'Bitte Schlagwort eingeben!']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$crud = new CRUD();
|
||||||
|
|
||||||
|
// Normalisierten Descriptor-Text erzeugen (entspricht Anchor.Text in der DB)
|
||||||
|
$desc = $crud->prepare_desc($term);
|
||||||
|
$completetext = $term;
|
||||||
|
|
||||||
|
// Dubletten-Prüfung gegen Anchor.Text (normalisierte Form)
|
||||||
|
$existing = $crud->checkDuplicateByNormalizedText('Subject', $desc);
|
||||||
|
if ($existing > 0) {
|
||||||
|
echo json_encode(['status' => 409, 'message' => 'Ein Schlagwort mit diesem Begriff existiert bereits']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neuen Eintrag erstellen
|
||||||
|
$result = $crud->insertNewTerm('Subject', $term, $desc, $detailType, $classification, $scopenote, $completetext);
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'status' => 200,
|
||||||
|
'message' => 'Schlagwort erfolgreich erstellt',
|
||||||
|
'Anchor' => $result['IDAnchor'],
|
||||||
|
'Entry' => $result['IDEntry'],
|
||||||
|
'Linking' => $result['IDLinking']
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo json_encode(['status' => 500, 'message' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
123
ajax/saveSynonym.php
Normal file
123
ajax/saveSynonym.php
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* saveSynonym.php - Speichert ein neues Synonym
|
||||||
|
*
|
||||||
|
* Parameter:
|
||||||
|
* - text1: Erster Begriff (bereits existierend)
|
||||||
|
* - text2: Zweiter Begriff (neues Synonym)
|
||||||
|
* - action: 'add' oder 'remove'
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
include "db_connection.php";
|
||||||
|
|
||||||
|
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||||
|
if (mysqli_connect_errno()) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Datenbankverbindung fehlgeschlagen']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_set_charset($conn, "utf8");
|
||||||
|
|
||||||
|
$text1 = isset($_POST['text1']) ? trim($_POST['text1']) : '';
|
||||||
|
$text2 = isset($_POST['text2']) ? trim($_POST['text2']) : '';
|
||||||
|
$action = isset($_POST['action']) ? $_POST['action'] : 'add';
|
||||||
|
|
||||||
|
if (empty($text1) || empty($text2)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Beide Begriffe müssen angegeben werden']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descriptor erstellen (Kleinbuchstaben, Leerzeichen durch Unterstrich)
|
||||||
|
function prepare_desc($text) {
|
||||||
|
$desc = strtolower($text);
|
||||||
|
$desc = str_replace(' ', '_', $desc);
|
||||||
|
return $desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
$desc1 = prepare_desc($text1);
|
||||||
|
$desc2 = prepare_desc($text2);
|
||||||
|
|
||||||
|
if ($action === 'add') {
|
||||||
|
// Prüfen ob text1 bereits eine IDLinking hat
|
||||||
|
$sql = "SELECT IDLinking FROM Synonyms WHERE Descriptor = '" . mysqli_real_escape_string($conn, $desc1) . "' LIMIT 1";
|
||||||
|
$res = mysqli_query($conn, $sql);
|
||||||
|
$idLinking = null;
|
||||||
|
|
||||||
|
if ($res && mysqli_num_rows($res) > 0) {
|
||||||
|
$row = mysqli_fetch_assoc($res);
|
||||||
|
$idLinking = $row['IDLinking'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfen ob text2 bereits eine IDLinking hat
|
||||||
|
$sql2 = "SELECT IDLinking FROM Synonyms WHERE Descriptor = '" . mysqli_real_escape_string($conn, $desc2) . "' LIMIT 1";
|
||||||
|
$res2 = mysqli_query($conn, $sql2);
|
||||||
|
$idLinking2 = null;
|
||||||
|
|
||||||
|
if ($res2 && mysqli_num_rows($res2) > 0) {
|
||||||
|
$row2 = mysqli_fetch_assoc($res2);
|
||||||
|
$idLinking2 = $row2['IDLinking'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall 1: Beide haben noch keine IDLinking - neue Gruppe erstellen
|
||||||
|
if ($idLinking === null && $idLinking2 === null) {
|
||||||
|
// Höchste IDLinking finden und +1
|
||||||
|
$sqlMax = "SELECT MAX(IDLinking) as maxId FROM Synonyms";
|
||||||
|
$resMax = mysqli_query($conn, $sqlMax);
|
||||||
|
$rowMax = mysqli_fetch_assoc($resMax);
|
||||||
|
$newIdLinking = ($rowMax['maxId'] ?? 0) + 1;
|
||||||
|
|
||||||
|
// Beide Begriffe einfügen
|
||||||
|
$sqlInsert1 = "INSERT INTO Synonyms (IDLinking, Text, Descriptor) VALUES ($newIdLinking, '" . mysqli_real_escape_string($conn, $text1) . "', '" . mysqli_real_escape_string($conn, $desc1) . "')";
|
||||||
|
$sqlInsert2 = "INSERT INTO Synonyms (IDLinking, Text, Descriptor) VALUES ($newIdLinking, '" . mysqli_real_escape_string($conn, $text2) . "', '" . mysqli_real_escape_string($conn, $desc2) . "')";
|
||||||
|
|
||||||
|
mysqli_query($conn, $sqlInsert1);
|
||||||
|
mysqli_query($conn, $sqlInsert2);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Neue Synonym-Gruppe erstellt', 'idLinking' => $newIdLinking]);
|
||||||
|
}
|
||||||
|
// Fall 2: text1 hat bereits eine IDLinking - text2 zur Gruppe hinzufügen
|
||||||
|
else if ($idLinking !== null && $idLinking2 === null) {
|
||||||
|
$sqlInsert = "INSERT INTO Synonyms (IDLinking, Text, Descriptor) VALUES ($idLinking, '" . mysqli_real_escape_string($conn, $text2) . "', '" . mysqli_real_escape_string($conn, $desc2) . "')";
|
||||||
|
mysqli_query($conn, $sqlInsert);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Synonym zur bestehenden Gruppe hinzugefügt', 'idLinking' => $idLinking]);
|
||||||
|
}
|
||||||
|
// Fall 3: text2 hat bereits eine IDLinking - text1 zur Gruppe hinzufügen
|
||||||
|
else if ($idLinking === null && $idLinking2 !== null) {
|
||||||
|
$sqlInsert = "INSERT INTO Synonyms (IDLinking, Text, Descriptor) VALUES ($idLinking2, '" . mysqli_real_escape_string($conn, $text1) . "', '" . mysqli_real_escape_string($conn, $desc1) . "')";
|
||||||
|
mysqli_query($conn, $sqlInsert);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Begriff zur bestehenden Synonym-Gruppe hinzugefügt', 'idLinking' => $idLinking2]);
|
||||||
|
}
|
||||||
|
// Fall 4: Beide haben bereits IDLinking - Gruppen zusammenführen
|
||||||
|
else {
|
||||||
|
if ($idLinking == $idLinking2) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Diese Begriffe sind bereits Synonyme']);
|
||||||
|
} else {
|
||||||
|
// Alle Einträge von idLinking2 auf idLinking umstellen
|
||||||
|
$sqlUpdate = "UPDATE Synonyms SET IDLinking = $idLinking WHERE IDLinking = $idLinking2";
|
||||||
|
mysqli_query($conn, $sqlUpdate);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Synonym-Gruppen zusammengeführt', 'idLinking' => $idLinking]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ($action === 'remove') {
|
||||||
|
// Synonym entfernen (nur den einen Eintrag löschen)
|
||||||
|
$sqlDelete = "DELETE FROM Synonyms WHERE Descriptor = '" . mysqli_real_escape_string($conn, $desc2) . "'";
|
||||||
|
mysqli_query($conn, $sqlDelete);
|
||||||
|
|
||||||
|
if (mysqli_affected_rows($conn) > 0) {
|
||||||
|
echo json_encode(['success' => true, 'message' => 'Synonym entfernt']);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Synonym nicht gefunden']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Unbekannte Aktion']);
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_close($conn);
|
||||||
|
?>
|
||||||
75
ajax/searchTreeData.php
Normal file
75
ajax/searchTreeData.php
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* searchTreeData.php - Sucht in der Treeview-Tabelle
|
||||||
|
*
|
||||||
|
* Parameter:
|
||||||
|
* - q: Suchbegriff (führendes * = Linkstrunkierung, abschließendes * = Rechtstrunkierung)
|
||||||
|
* Ohne * → automatische Rechtstrunkierung (bisheriges Verhalten)
|
||||||
|
*
|
||||||
|
* Rückgabe: JSON-Array mit gefundenen Einträgen
|
||||||
|
*/
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
include "db_connection.php";
|
||||||
|
|
||||||
|
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||||
|
if (mysqli_connect_errno()) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
mysqli_set_charset($conn, "utf8");
|
||||||
|
|
||||||
|
$searchTerm = isset($_GET['q']) ? trim($_GET['q']) : '';
|
||||||
|
|
||||||
|
if (strlen($searchTerm) < 2) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trunkierung auswerten
|
||||||
|
$leftTrunc = (substr($searchTerm, 0, 1) === '*');
|
||||||
|
$rightTrunc = (substr($searchTerm, -1) === '*');
|
||||||
|
|
||||||
|
// Sternchen entfernen, dann escapen
|
||||||
|
$clean = trim($searchTerm, '*');
|
||||||
|
if (strlen($clean) < 2) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$escaped = mysqli_real_escape_string($conn, $clean);
|
||||||
|
// LIKE-Sonderzeichen in den Daten escapen
|
||||||
|
$escaped = str_replace(['%', '_'], ['\\%', '\\_'], $escaped);
|
||||||
|
|
||||||
|
// Pattern bauen:
|
||||||
|
// *Begriff → %Begriff% (links- und rechtstrunkiert)
|
||||||
|
// Begriff* → Begriff% (nur rechtstrunkiert)
|
||||||
|
// *Begriff* → %Begriff% (beidseitig)
|
||||||
|
// Begriff → Begriff% (Standard: Rechtstrunkierung)
|
||||||
|
$pattern = ($leftTrunc ? '%' : '') .
|
||||||
|
$escaped .
|
||||||
|
($rightTrunc ? '%' : '%'); // rechts immer trunkieren = bisheriges Verhalten
|
||||||
|
|
||||||
|
$sql = "SELECT id, name, text, parent_id
|
||||||
|
FROM Treeview
|
||||||
|
WHERE LOWER(text) LIKE LOWER('$pattern') OR LOWER(name) LIKE LOWER('$pattern')
|
||||||
|
ORDER BY text
|
||||||
|
LIMIT 50";
|
||||||
|
|
||||||
|
$res = mysqli_query($conn, $sql);
|
||||||
|
if (!$res) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
while ($row = mysqli_fetch_assoc($res)) {
|
||||||
|
$result[] = [
|
||||||
|
'id' => strval($row['id']),
|
||||||
|
'text' => $row['text'] ? $row['text'] : $row['name'],
|
||||||
|
'parent_id' => $row['parent_id']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_close($conn);
|
||||||
|
echo json_encode($result, JSON_UNESCAPED_UNICODE);
|
||||||
|
?>
|
||||||
113
ajax/writeNewRelation.php
Normal file
113
ajax/writeNewRelation.php
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* writeNewRelation.php - Speichert eine neue Relation
|
||||||
|
*
|
||||||
|
* Parameter (POST):
|
||||||
|
* - IDAnchor: ID des Ausgangseintrags
|
||||||
|
* - IDEntry: ID des Zieleintrags
|
||||||
|
* - Relationtype: Art der Relation (BT, NT, RT, USE, UF)
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
include "db_connection.php";
|
||||||
|
|
||||||
|
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||||
|
if (mysqli_connect_errno()) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Datenbankverbindung fehlgeschlagen']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_set_charset($conn, "utf8");
|
||||||
|
|
||||||
|
// Parameter aus JavaScript (AnchorID, relationType, relationID)
|
||||||
|
$idAnchor = isset($_POST['AnchorID']) ? intval($_POST['AnchorID']) : 0;
|
||||||
|
if ($idAnchor === 0) {
|
||||||
|
$idAnchor = isset($_POST['IDAnchor']) ? intval($_POST['IDAnchor']) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$idEntry = isset($_POST['relationID']) ? intval($_POST['relationID']) : 0;
|
||||||
|
if ($idEntry === 0) {
|
||||||
|
$idEntry = isset($_POST['IDEntry']) ? intval($_POST['IDEntry']) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$relationtype = isset($_POST['relationType']) ? trim($_POST['relationType']) : '';
|
||||||
|
if (empty($relationtype)) {
|
||||||
|
$relationtype = isset($_POST['Relationtype']) ? trim($_POST['Relationtype']) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($idAnchor === 0 || $idEntry === 0) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'IDAnchor und IDEntry müssen angegeben werden']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($relationtype)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Relationtype muss angegeben werden']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erlaubte Relationstypen
|
||||||
|
$allowedTypes = ['BT', 'NT', 'RT', 'USE', 'UF'];
|
||||||
|
if (!in_array($relationtype, $allowedTypes)) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Ungültiger Relationstyp']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfen ob Relation bereits existiert
|
||||||
|
$checkSql = "SELECT ID FROM Linking WHERE IDAnchor = $idAnchor AND IDEntry = $idEntry AND Relationtype = '" . mysqli_real_escape_string($conn, $relationtype) . "'";
|
||||||
|
$checkResult = mysqli_query($conn, $checkSql);
|
||||||
|
|
||||||
|
if ($checkResult && mysqli_num_rows($checkResult) > 0) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Diese Relation existiert bereits']);
|
||||||
|
mysqli_close($conn);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relation einfügen
|
||||||
|
$insertSql = "INSERT INTO Linking (IDAnchor, IDEntry, Relationtype) VALUES ($idAnchor, $idEntry, '" . mysqli_real_escape_string($conn, $relationtype) . "')";
|
||||||
|
|
||||||
|
if (mysqli_query($conn, $insertSql)) {
|
||||||
|
$newId = mysqli_insert_id($conn);
|
||||||
|
|
||||||
|
// Reziproke Relation erstellen (BT <-> NT, RT <-> RT)
|
||||||
|
$reciprocalType = null;
|
||||||
|
switch ($relationtype) {
|
||||||
|
case 'BT':
|
||||||
|
$reciprocalType = 'NT';
|
||||||
|
break;
|
||||||
|
case 'NT':
|
||||||
|
$reciprocalType = 'BT';
|
||||||
|
break;
|
||||||
|
case 'RT':
|
||||||
|
$reciprocalType = 'RT';
|
||||||
|
break;
|
||||||
|
case 'USE':
|
||||||
|
$reciprocalType = 'UF';
|
||||||
|
break;
|
||||||
|
case 'UF':
|
||||||
|
$reciprocalType = 'USE';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($reciprocalType) {
|
||||||
|
// Prüfen ob reziproke Relation schon existiert
|
||||||
|
$checkRecSql = "SELECT ID FROM Linking WHERE IDAnchor = $idEntry AND IDEntry = $idAnchor AND Relationtype = '$reciprocalType'";
|
||||||
|
$checkRecResult = mysqli_query($conn, $checkRecSql);
|
||||||
|
|
||||||
|
if (!$checkRecResult || mysqli_num_rows($checkRecResult) === 0) {
|
||||||
|
$insertRecSql = "INSERT INTO Linking (IDAnchor, IDEntry, Relationtype) VALUES ($idEntry, $idAnchor, '$reciprocalType')";
|
||||||
|
mysqli_query($conn, $insertRecSql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Relation erfolgreich erstellt',
|
||||||
|
'id' => $newId
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Fehler beim Speichern: ' . mysqli_error($conn)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_close($conn);
|
||||||
|
?>
|
||||||
115
dspace-api/getAuthorityCount.php
Normal file
115
dspace-api/getAuthorityCount.php
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* getAuthorityCount.php - Zählt DSpace-Datensätze für einen Authority-Begriff
|
||||||
|
*
|
||||||
|
* Parameter:
|
||||||
|
* - authType: 'Subject' oder 'Person'
|
||||||
|
* - query: Der zu suchende Begriff (exakte Übereinstimmung)
|
||||||
|
*
|
||||||
|
* Response: JSON mit count
|
||||||
|
*
|
||||||
|
* Beispiel:
|
||||||
|
* curl "https://bibb-dspace.bibb.de/DSpaceAPI/getAuthorityCount.php?authType=Subject&query=Berufsbildung"
|
||||||
|
* => {"query":"Berufsbildung","authType":"Subject","count":42}
|
||||||
|
*/
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
|
||||||
|
// IP-Prüfung
|
||||||
|
require_once __DIR__ . '/checkAccess.php';
|
||||||
|
|
||||||
|
// Parameter
|
||||||
|
$authType = isset($_GET['authType']) ? trim($_GET['authType']) : '';
|
||||||
|
$query = isset($_GET['query']) ? trim($_GET['query']) : '';
|
||||||
|
|
||||||
|
// Validierung
|
||||||
|
$allowedTypes = array('Subject', 'Person');
|
||||||
|
if (!in_array($authType, $allowedTypes)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(array('error' => 'Invalid authType. Allowed: Subject, Person'));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($query) === 0) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(array('error' => 'Missing parameter: query'));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solr-Feld je nach authType
|
||||||
|
switch ($authType) {
|
||||||
|
case 'Subject':
|
||||||
|
$queryField = 'dc.subject.other';
|
||||||
|
break;
|
||||||
|
case 'Person':
|
||||||
|
$queryField = 'dc.contributor.author';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query bereinigen
|
||||||
|
$query = urldecode($query);
|
||||||
|
$query = str_replace("'", "", $query);
|
||||||
|
|
||||||
|
// Solr-Abfrage
|
||||||
|
$solr_baseurl = "http://localhost:8080/solr/search/query?";
|
||||||
|
$solr_query = $queryField . ':' . $query;
|
||||||
|
$rows = "&rows=10000";
|
||||||
|
$fl = "&fl=" . $queryField;
|
||||||
|
$solr_URL = $solr_baseurl . "wt=phps" . $fl . $rows . "&q=" . urlencode($solr_query . " AND withdrawn:false");
|
||||||
|
|
||||||
|
$context = stream_context_create(array(
|
||||||
|
'http' => array('timeout' => 10)
|
||||||
|
));
|
||||||
|
|
||||||
|
$response = @file_get_contents($solr_URL, false, $context);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(array('error' => 'Solr query failed'));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = unserialize($response);
|
||||||
|
|
||||||
|
if (!isset($result['response']['docs'])) {
|
||||||
|
echo json_encode(array(
|
||||||
|
'query' => $query,
|
||||||
|
'authType' => $authType,
|
||||||
|
'count' => 0
|
||||||
|
));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exakte Treffer zählen (analog zu DSpaceCountModel)
|
||||||
|
$count = 0;
|
||||||
|
foreach ($result['response']['docs'] as $entry) {
|
||||||
|
foreach ($entry as $hit) {
|
||||||
|
if (!is_array($hit)) {
|
||||||
|
$hit = array($hit);
|
||||||
|
}
|
||||||
|
foreach ($hit as $h) {
|
||||||
|
if ($authType === "Subject") {
|
||||||
|
if (strcmp($query, trim($h)) === 0) {
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Person: Authority-ID in eckigen Klammern abschneiden
|
||||||
|
$name = trim(substr($h, 0, strpos($h, "[")));
|
||||||
|
if (strlen($name) === 0) {
|
||||||
|
$name = trim($h);
|
||||||
|
}
|
||||||
|
if (strcmp($query, $name) === 0) {
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(array(
|
||||||
|
'query' => $query,
|
||||||
|
'authType' => $authType,
|
||||||
|
'count' => $count
|
||||||
|
));
|
||||||
|
?>
|
||||||
46
getTreePath.php
Normal file
46
getTreePath.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* getTreePath.php - Liefert den Pfad von einem Knoten bis zur Wurzel
|
||||||
|
*
|
||||||
|
* Parameter (GET):
|
||||||
|
* - id: Treeview-ID des Zielknotens
|
||||||
|
*
|
||||||
|
* Rückgabe: JSON-Array mit IDs von Root bis Zielknoten (inklusive)
|
||||||
|
* Beispiel: [98106, 98107, 98113, 98115]
|
||||||
|
*/
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
include "db_connection.php";
|
||||||
|
|
||||||
|
$conn = mysqli_connect(HOST, USER, PASSWORD, DATABASE);
|
||||||
|
if (mysqli_connect_errno()) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
mysqli_set_charset($conn, "utf8");
|
||||||
|
|
||||||
|
$targetId = isset($_GET['id']) ? intval($_GET['id']) : 0;
|
||||||
|
if ($targetId === 0) {
|
||||||
|
echo json_encode([]);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pfad aufbauen: vom Zielknoten nach oben bis Root (parent_id = 0 oder leer)
|
||||||
|
$path = [];
|
||||||
|
$curId = $targetId;
|
||||||
|
$limit = 20; // Schutz vor Endlosschleife bei defekten Daten
|
||||||
|
|
||||||
|
while ($curId > 0 && $limit-- > 0) {
|
||||||
|
$res = mysqli_query($conn, "SELECT id, parent_id FROM Treeview WHERE id = $curId LIMIT 1");
|
||||||
|
if (!$res) break;
|
||||||
|
$row = mysqli_fetch_assoc($res);
|
||||||
|
if (!$row) break;
|
||||||
|
|
||||||
|
array_unshift($path, intval($row['id'])); // vorne einfügen → Root zuerst
|
||||||
|
$parentId = intval($row['parent_id']);
|
||||||
|
if ($parentId === 0) break;
|
||||||
|
$curId = $parentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
mysqli_close($conn);
|
||||||
|
echo json_encode($path, JSON_UNESCAPED_UNICODE);
|
||||||
|
?>
|
||||||
539
index.php
Normal file
539
index.php
Normal file
@ -0,0 +1,539 @@
|
|||||||
|
<?php
|
||||||
|
include "templates/Header.html";
|
||||||
|
?>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Dashboard Styles */
|
||||||
|
.dashboard-section {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid var(--primary-color);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Statistik-Karten */
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
text-align: center;
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-color);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card.subject { border-top: 4px solid #28a745; }
|
||||||
|
.stat-card.subject .stat-icon { color: #28a745; }
|
||||||
|
|
||||||
|
.stat-card.person { border-top: 4px solid #007bff; }
|
||||||
|
.stat-card.person .stat-icon { color: #007bff; }
|
||||||
|
|
||||||
|
.stat-card.corporate { border-top: 4px solid #fd7e14; }
|
||||||
|
.stat-card.corporate .stat-icon { color: #fd7e14; }
|
||||||
|
|
||||||
|
.stat-card.publisher { border-top: 4px solid #6f42c1; }
|
||||||
|
.stat-card.publisher .stat-icon { color: #6f42c1; }
|
||||||
|
|
||||||
|
.stat-card.classification { border-top: 4px solid #20c997; }
|
||||||
|
.stat-card.classification .stat-icon { color: #20c997; }
|
||||||
|
|
||||||
|
.stat-card.total { border-top: 4px solid var(--primary-color); }
|
||||||
|
.stat-card.total .stat-icon { color: var(--primary-color); }
|
||||||
|
|
||||||
|
/* Zwei-Spalten Layout */
|
||||||
|
.dashboard-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Listen-Karten */
|
||||||
|
.list-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card-header {
|
||||||
|
background: var(--bg-light);
|
||||||
|
padding: 12px 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card-body {
|
||||||
|
padding: 0;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item {
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-bottom: 1px solid var(--border-light);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item:hover {
|
||||||
|
background: var(--bg-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item-text {
|
||||||
|
flex: 1;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-primary);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item-type {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--bg-light);
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item-date {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item-link {
|
||||||
|
color: var(--primary-color);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Qualitäts-Indikatoren */
|
||||||
|
.quality-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-item {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-label {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-value {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-value.warning {
|
||||||
|
color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-value.ok {
|
||||||
|
color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading */
|
||||||
|
.loading-spinner {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 40px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner i {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navigation Cards */
|
||||||
|
.nav-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 25px 20px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--text-primary);
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-card:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-card-icon {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-card-title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="main-content">
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<div class="card dashboard-section">
|
||||||
|
<h2 class="section-title">
|
||||||
|
<i class="fas fa-th-large"></i> Normdateien verwalten
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="nav-grid">
|
||||||
|
<a href="Subjects.php" class="nav-card">
|
||||||
|
<div class="nav-card-icon">🏷️</div>
|
||||||
|
<div class="nav-card-title">Schlagworte</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="Persons.php" class="nav-card">
|
||||||
|
<div class="nav-card-icon">👥</div>
|
||||||
|
<div class="nav-card-title">Personen</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="Corporates.php" class="nav-card">
|
||||||
|
<div class="nav-card-icon">🏢</div>
|
||||||
|
<div class="nav-card-title">Körperschaften</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="Publishers.php" class="nav-card">
|
||||||
|
<div class="nav-card-icon">📚</div>
|
||||||
|
<div class="nav-card-title">Verlage</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="Classifications.php" class="nav-card">
|
||||||
|
<div class="nav-card-icon">🗂️</div>
|
||||||
|
<div class="nav-card-title">Klassifikation</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="Treeview.php" class="nav-card">
|
||||||
|
<div class="nav-card-icon">🌳</div>
|
||||||
|
<div class="nav-card-title">Klassifikationsbaum</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Übersicht Card -->
|
||||||
|
<div class="card dashboard-section">
|
||||||
|
<h2 class="section-title">
|
||||||
|
<i class="fas fa-chart-bar"></i> Übersicht
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<!-- Statistik-Karten -->
|
||||||
|
<div id="stats-container">
|
||||||
|
<div class="loading-spinner">
|
||||||
|
<i class="fas fa-circle-notch fa-spin"></i>
|
||||||
|
<span>Lade Statistiken...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Aktivität & Qualität -->
|
||||||
|
<div class="dashboard-row dashboard-section">
|
||||||
|
|
||||||
|
<!-- Zuletzt geändert -->
|
||||||
|
<div class="list-card">
|
||||||
|
<div class="list-card-header">
|
||||||
|
<i class="fas fa-clock"></i> Zuletzt geändert
|
||||||
|
</div>
|
||||||
|
<div class="list-card-body" id="recently-modified">
|
||||||
|
<div class="loading-spinner">
|
||||||
|
<i class="fas fa-circle-notch fa-spin"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Zuletzt hinzugefügt -->
|
||||||
|
<div class="list-card">
|
||||||
|
<div class="list-card-header">
|
||||||
|
<i class="fas fa-plus-circle"></i> Zuletzt hinzugefügt
|
||||||
|
</div>
|
||||||
|
<div class="list-card-body" id="recently-created">
|
||||||
|
<div class="loading-spinner">
|
||||||
|
<i class="fas fa-circle-notch fa-spin"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Qualitäts-Check -->
|
||||||
|
<div class="card dashboard-section">
|
||||||
|
<h2 class="section-title">
|
||||||
|
<i class="fas fa-check-circle"></i> Qualitäts-Check
|
||||||
|
</h2>
|
||||||
|
<p class="text-muted mb-15">Einträge ohne Relationen (Waisen)</p>
|
||||||
|
<div id="quality-container">
|
||||||
|
<div class="loading-spinner">
|
||||||
|
<i class="fas fa-circle-notch fa-spin"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
loadDashboardStats();
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadDashboardStats() {
|
||||||
|
$.get('/Thesaurus/ajax/getDashboardStats.php', function(data) {
|
||||||
|
var stats = typeof data === 'string' ? JSON.parse(data) : data;
|
||||||
|
|
||||||
|
if (stats.error) {
|
||||||
|
$('#stats-container').html('<div class="text-center text-muted p-4">Fehler beim Laden der Statistiken</div>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Statistik-Karten rendern
|
||||||
|
renderStatsCards(stats);
|
||||||
|
|
||||||
|
// Zuletzt geändert
|
||||||
|
renderRecentList('#recently-modified', stats.recentlyModified);
|
||||||
|
|
||||||
|
// Zuletzt hinzugefügt
|
||||||
|
renderRecentList('#recently-created', stats.recentlyCreated);
|
||||||
|
|
||||||
|
// Qualitäts-Check
|
||||||
|
renderQualityCheck(stats);
|
||||||
|
|
||||||
|
}).fail(function() {
|
||||||
|
$('#stats-container').html('<div class="text-center text-muted p-4">Fehler beim Laden der Statistiken</div>');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderStatsCards(stats) {
|
||||||
|
var html = '<div class="stats-grid">';
|
||||||
|
|
||||||
|
html += '<div class="stat-card subject">';
|
||||||
|
html += '<div class="stat-icon">🏷️</div>';
|
||||||
|
html += '<div class="stat-value">' + formatNumber(stats.counts.Subject) + '</div>';
|
||||||
|
html += '<div class="stat-label">Schlagworte</div>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
html += '<div class="stat-card person">';
|
||||||
|
html += '<div class="stat-icon">👥</div>';
|
||||||
|
html += '<div class="stat-value">' + formatNumber(stats.counts.Person) + '</div>';
|
||||||
|
html += '<div class="stat-label">Personen</div>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
html += '<div class="stat-card corporate">';
|
||||||
|
html += '<div class="stat-icon">🏢</div>';
|
||||||
|
html += '<div class="stat-value">' + formatNumber(stats.counts.Corporate) + '</div>';
|
||||||
|
html += '<div class="stat-label">Körperschaften</div>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
html += '<div class="stat-card publisher">';
|
||||||
|
html += '<div class="stat-icon">📚</div>';
|
||||||
|
html += '<div class="stat-value">' + formatNumber(stats.counts.Publisher) + '</div>';
|
||||||
|
html += '<div class="stat-label">Verlage</div>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
html += '<div class="stat-card classification">';
|
||||||
|
html += '<div class="stat-icon">🗂️</div>';
|
||||||
|
html += '<div class="stat-value">' + formatNumber(stats.counts.Classification) + '</div>';
|
||||||
|
html += '<div class="stat-label">Klassifikationen</div>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
html += '<div class="stat-card total">';
|
||||||
|
html += '<div class="stat-icon"><i class="fas fa-database"></i></div>';
|
||||||
|
html += '<div class="stat-value">' + formatNumber(stats.total) + '</div>';
|
||||||
|
html += '<div class="stat-label">Gesamt</div>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
// Zusätzliche Info-Zeile
|
||||||
|
html += '<div class="d-flex gap-4 mt-15 text-muted" style="font-size: 0.9rem;">';
|
||||||
|
html += '<span><i class="fas fa-link me-1"></i> ' + formatNumber(stats.relationsTotal) + ' Relationen</span>';
|
||||||
|
html += '<span><i class="fas fa-check me-1"></i> ' + formatNumber(stats.descriptors) + ' Deskriptoren</span>';
|
||||||
|
html += '<span><i class="fas fa-times me-1"></i> ' + formatNumber(stats.nonDescriptors) + ' Non-Deskriptoren</span>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
$('#stats-container').html(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderRecentList(container, items) {
|
||||||
|
if (!items || items.length === 0) {
|
||||||
|
$(container).html('<div class="text-center text-muted p-4">Keine Einträge</div>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var html = '';
|
||||||
|
items.forEach(function(item) {
|
||||||
|
var typeLabel = getTypeLabel(item.type);
|
||||||
|
var link = getTypeLink(item.type, item.id);
|
||||||
|
var date = formatDate(item.date);
|
||||||
|
|
||||||
|
html += '<div class="list-item">';
|
||||||
|
html += '<a href="' + link + '" class="list-item-text list-item-link" title="' + escapeHtml(item.text) + '">' + escapeHtml(item.text) + '</a>';
|
||||||
|
html += '<div class="list-item-meta">';
|
||||||
|
html += '<span class="list-item-type">' + typeLabel + '</span>';
|
||||||
|
html += '<span class="list-item-date">' + date + '</span>';
|
||||||
|
html += '</div>';
|
||||||
|
html += '</div>';
|
||||||
|
});
|
||||||
|
|
||||||
|
$(container).html(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderQualityCheck(stats) {
|
||||||
|
var html = '<div class="quality-grid">';
|
||||||
|
|
||||||
|
var types = [
|
||||||
|
{ key: 'Subject', label: 'Schlagworte', icon: '🏷️' },
|
||||||
|
{ key: 'Person', label: 'Personen', icon: '👥' },
|
||||||
|
{ key: 'Corporate', label: 'Körperschaften', icon: '🏢' },
|
||||||
|
{ key: 'Publisher', label: 'Verlage', icon: '📚' },
|
||||||
|
{ key: 'Classification', label: 'Klassifikation', icon: '🗂️' }
|
||||||
|
];
|
||||||
|
|
||||||
|
types.forEach(function(type) {
|
||||||
|
var count = stats.orphans[type.key] || 0;
|
||||||
|
var valueClass = count > 0 ? 'warning' : 'ok';
|
||||||
|
|
||||||
|
html += '<div class="quality-item">';
|
||||||
|
html += '<span class="quality-label">' + type.icon + ' ' + type.label + '</span>';
|
||||||
|
html += '<span class="quality-value ' + valueClass + '">' + formatNumber(count) + '</span>';
|
||||||
|
html += '</div>';
|
||||||
|
});
|
||||||
|
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
// Gesamt-Waisen
|
||||||
|
var totalOrphans = stats.orphansTotal || 0;
|
||||||
|
html += '<div class="mt-15 text-muted" style="font-size: 0.9rem;">';
|
||||||
|
html += '<i class="fas fa-exclamation-triangle me-1"></i> ';
|
||||||
|
html += 'Gesamt: <strong>' + formatNumber(totalOrphans) + '</strong> Einträge ohne Relationen';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
$('#quality-container').html(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypeLabel(type) {
|
||||||
|
var labels = {
|
||||||
|
'Subject': 'Schlagwort',
|
||||||
|
'Person': 'Person',
|
||||||
|
'Corporate': 'Körperschaft',
|
||||||
|
'Publisher': 'Verlag',
|
||||||
|
'Classification': 'Klassifikation'
|
||||||
|
};
|
||||||
|
return labels[type] || type;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTypeLink(type, id) {
|
||||||
|
var links = {
|
||||||
|
'Subject': 'Subjects.php?edit=',
|
||||||
|
'Person': 'Persons.php?edit=',
|
||||||
|
'Corporate': 'Corporates.php?edit=',
|
||||||
|
'Publisher': 'Publishers.php?edit=',
|
||||||
|
'Classification': 'Classifications.php?edit='
|
||||||
|
};
|
||||||
|
return (links[type] || 'Subjects.php?edit=') + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatNumber(num) {
|
||||||
|
if (!num) return '0';
|
||||||
|
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDate(dateStr) {
|
||||||
|
if (!dateStr || dateStr === '0000-00-00 00:00:00') return '-';
|
||||||
|
var date = new Date(dateStr);
|
||||||
|
return date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(text) {
|
||||||
|
if (!text) return '';
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
include "templates/Footer.html";
|
||||||
|
?>
|
||||||
639
js/ClassificationScript.js
Normal file
639
js/ClassificationScript.js
Normal file
@ -0,0 +1,639 @@
|
|||||||
|
/**
|
||||||
|
* Created by Roland on 2021/07/01.
|
||||||
|
* Updated for BIBB Layout 2024
|
||||||
|
*/
|
||||||
|
// CLASSIFICATIONS
|
||||||
|
|
||||||
|
// Show blank screen to add new Classification
|
||||||
|
function newClassificationsShow() {
|
||||||
|
|
||||||
|
// Sichere Element-Zugriffe mit Null-Checks
|
||||||
|
var saveBtn = document.getElementById("newClassificationSave");
|
||||||
|
var dismissBtn = document.getElementById("newClassificationDismiss");
|
||||||
|
var modifyBtn = document.getElementById("ClassificationModifySave");
|
||||||
|
var termField = document.getElementById("new_term");
|
||||||
|
var notationField = document.getElementById("new_notation");
|
||||||
|
var scopenoteField = document.getElementById("new_scopenote");
|
||||||
|
var relationLabel = document.getElementById("new_relation_label");
|
||||||
|
var relationDismissBtn = document.getElementById("newRelationClassificationDismiss");
|
||||||
|
var relationTable = document.getElementById("relation_table");
|
||||||
|
var errorDiv = document.getElementById("errorNewClassification");
|
||||||
|
|
||||||
|
if (saveBtn) saveBtn.style.display = 'inline';
|
||||||
|
if (dismissBtn) dismissBtn.style.display = 'inline';
|
||||||
|
if (modifyBtn) modifyBtn.style.display = 'none';
|
||||||
|
if (termField) termField.disabled = false;
|
||||||
|
if (notationField) notationField.disabled = false;
|
||||||
|
if (scopenoteField) scopenoteField.disabled = false;
|
||||||
|
|
||||||
|
// Relationen-Bereich verstecken bei neuer Klassifikation
|
||||||
|
if (relationLabel) relationLabel.style.display = 'none';
|
||||||
|
if (relationDismissBtn) relationDismissBtn.style.display = 'none';
|
||||||
|
showRelations(0,0,0);
|
||||||
|
if (relationTable) relationTable.style.display = 'none';
|
||||||
|
if (errorDiv) errorDiv.style.display = 'none';
|
||||||
|
|
||||||
|
$("#new_term").val("");
|
||||||
|
$("#new_notation").val("");
|
||||||
|
$("#new_scopenote").val("");
|
||||||
|
|
||||||
|
$("#NewClassificationModal").modal("show");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert new classification record
|
||||||
|
function CreateNewEntry(type) {
|
||||||
|
// get values
|
||||||
|
|
||||||
|
var term = $("#new_term").val();
|
||||||
|
term = term.trim();
|
||||||
|
var notation = $("#new_notation").val();
|
||||||
|
notation = notation.trim();
|
||||||
|
var detailtype = "";
|
||||||
|
var classification = "";
|
||||||
|
var scopenote = $("#new_scopenote").val();
|
||||||
|
scopenote = scopenote.trim();
|
||||||
|
|
||||||
|
if (term.length == 0) {
|
||||||
|
var a = document.getElementById("errorNewClassification");
|
||||||
|
a.classList.add('alert-danger');
|
||||||
|
a.textContent = 'Bitte Klassifikation eingeben!';
|
||||||
|
a.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = document.getElementById("errorNewClassification");
|
||||||
|
a.classList.remove('alert-danger');
|
||||||
|
a.textContent = '';
|
||||||
|
a.style.display = 'none';
|
||||||
|
|
||||||
|
$.post("ajax/newClassification.php", {
|
||||||
|
term : term,
|
||||||
|
type : type,
|
||||||
|
notation : notation,
|
||||||
|
detailtype : detailtype,
|
||||||
|
classification: classification,
|
||||||
|
scopenote : scopenote
|
||||||
|
}, function (data, status) {
|
||||||
|
|
||||||
|
var dt = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
var e = document.getElementById("errorNewClassification");
|
||||||
|
|
||||||
|
if (dt['status'] == 200)
|
||||||
|
{
|
||||||
|
var AnchorID = dt["Anchor"];
|
||||||
|
var EntryID = dt["Entry"];
|
||||||
|
var LinkingID = dt["Linking"];
|
||||||
|
|
||||||
|
var b = document.getElementById("newClassificationSave");
|
||||||
|
b.style.display='none';
|
||||||
|
var c = document.getElementById("newClassificationDismiss");
|
||||||
|
c.style.display='none';
|
||||||
|
document.getElementById("new_term").disabled = true;
|
||||||
|
document.getElementById("new_notation").disabled = true;
|
||||||
|
document.getElementById("new_scopenote").disabled = true;
|
||||||
|
$table.bootstrapTable('refresh');
|
||||||
|
|
||||||
|
e.classList.remove('alert-danger');
|
||||||
|
e.classList.add('alert-success');
|
||||||
|
e.textContent = 'Neue Klassifikation aufgenommen';
|
||||||
|
e.style.display = 'block';
|
||||||
|
|
||||||
|
// ID setzen für Relationen
|
||||||
|
$('#ID').val(AnchorID);
|
||||||
|
|
||||||
|
// Relationen-Bereich anzeigen nach Speichern
|
||||||
|
document.getElementById("new_relation_label").style.display = 'block';
|
||||||
|
document.getElementById("newRelationClassificationDismiss").style.display = 'inline';
|
||||||
|
showRelations(AnchorID,EntryID,LinkingID);
|
||||||
|
} else {
|
||||||
|
e.classList.remove('alert-success');
|
||||||
|
e.classList.add('alert-danger');
|
||||||
|
e.textContent = dt['message'];
|
||||||
|
e.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show relations of a classification record
|
||||||
|
function showRelations(Anchor,Entry,Linking)
|
||||||
|
{
|
||||||
|
$.get("ajax/getRelations.php", {
|
||||||
|
anchorID:Anchor,
|
||||||
|
authType:"Classification"
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
$(".new_modal_content").html(data);
|
||||||
|
// Tabelle nur anzeigen wenn Relationen vorhanden
|
||||||
|
var relationTable = document.getElementById("relation_table");
|
||||||
|
if (relationTable) {
|
||||||
|
if (data && data.trim().length > 0) {
|
||||||
|
relationTable.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
relationTable.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$("#NewClassificationModal").modal("show");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// create new relation for a classification record
|
||||||
|
function CreateNewRelation(AnchorID, relationID, event) {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🚀 CreateNewRelation:', { AnchorID, relationID });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Validierung
|
||||||
|
if (!AnchorID || !relationID) {
|
||||||
|
showError('Fehlende Parameter: AnchorID oder relationID');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var relationTypeElement = document.getElementById("new_relationtype");
|
||||||
|
|
||||||
|
if (!relationTypeElement) {
|
||||||
|
showError('Relationstyp-Element nicht gefunden');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var relationType = relationTypeElement.value;
|
||||||
|
|
||||||
|
if (!relationType || relationType.trim() === '') {
|
||||||
|
showError('Bitte wählen Sie einen Relationstyp aus');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading anzeigen
|
||||||
|
showLoading();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "ajax/writeNewRelation.php",
|
||||||
|
type: "POST",
|
||||||
|
data: {
|
||||||
|
AnchorID: AnchorID,
|
||||||
|
relationType: relationType,
|
||||||
|
relationID: relationID
|
||||||
|
},
|
||||||
|
dataType: "text",
|
||||||
|
timeout: 10000,
|
||||||
|
|
||||||
|
success: function(response) {
|
||||||
|
console.log("✅ Success! Response:", response);
|
||||||
|
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(response);
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
showError('<strong>Serverfehler:</strong><br>' + data.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.success === true) {
|
||||||
|
showSuccess('Neue Relation erfolgreich aufgenommen!');
|
||||||
|
|
||||||
|
if (typeof showRelations === 'function') {
|
||||||
|
setTimeout(function() {
|
||||||
|
showRelations(AnchorID, 0, 0);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForm();
|
||||||
|
} else {
|
||||||
|
showError('Unerwartete Antwort vom Server');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (parseError) {
|
||||||
|
console.log("⚠️ Keine JSON-Antwort, prüfe Plain-Text");
|
||||||
|
|
||||||
|
if (response && response.length > 0 && !isNaN(response.trim())) {
|
||||||
|
showSuccess('Neue Relation erfolgreich aufgenommen! (ID: ' + response.trim() + ')');
|
||||||
|
|
||||||
|
if (typeof showRelations === 'function') {
|
||||||
|
setTimeout(function() {
|
||||||
|
showRelations(AnchorID, 0, 0);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForm();
|
||||||
|
} else {
|
||||||
|
showError('<strong>Unerwartete Antwort:</strong><br>' +
|
||||||
|
'<code>' + response.substring(0, 200) + '</code>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error("❌ AJAX Error:", { status, error, xhr });
|
||||||
|
|
||||||
|
var errorMessage = '<strong>Fehler beim Speichern der Relation</strong><br><br>';
|
||||||
|
|
||||||
|
if (xhr.status === 0) {
|
||||||
|
errorMessage += '❌ Keine Verbindung zum Server möglich<br>';
|
||||||
|
errorMessage += '<small>Prüfen Sie Ihre Internetverbindung</small>';
|
||||||
|
} else if (xhr.status === 404) {
|
||||||
|
errorMessage += '❌ Datei nicht gefunden (404)<br>';
|
||||||
|
errorMessage += '<small>ajax/writeNewRelation.php existiert nicht</small>';
|
||||||
|
} else if (xhr.status === 500) {
|
||||||
|
errorMessage += '❌ Interner Serverfehler (500)<br>';
|
||||||
|
if (xhr.responseText) {
|
||||||
|
errorMessage += '<br><details><summary>Details anzeigen</summary><pre>' +
|
||||||
|
xhr.responseText.substring(0, 500) + '</pre></details>';
|
||||||
|
}
|
||||||
|
} else if (status === 'timeout') {
|
||||||
|
errorMessage += '❌ Zeitüberschreitung<br>';
|
||||||
|
errorMessage += '<small>Der Server hat nicht rechtzeitig geantwortet</small>';
|
||||||
|
} else if (status === 'parsererror') {
|
||||||
|
errorMessage += '❌ Fehler beim Verarbeiten der Server-Antwort<br>';
|
||||||
|
errorMessage += '<small>Die Server-Antwort war ungültig</small>';
|
||||||
|
} else {
|
||||||
|
errorMessage += '❌ ' + status + ' (' + xhr.status + ')';
|
||||||
|
if (error) {
|
||||||
|
errorMessage += '<br><small>' + error + '</small>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showError(errorMessage);
|
||||||
|
},
|
||||||
|
|
||||||
|
complete: function(xhr, status) {
|
||||||
|
console.log("🏁 Request abgeschlossen:", status);
|
||||||
|
if (status !== 'success') {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
hideLoading();
|
||||||
|
console.error("💥 Exception:", e);
|
||||||
|
showError('<strong>JavaScript-Fehler:</strong><br>' + e.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(message) {
|
||||||
|
var errorElement = document.getElementById("errorNewClassification");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-danger';
|
||||||
|
errorElement.innerHTML = '<i class="fas fa-exclamation-circle me-2"></i>' + message.replace(/\n/g, '<br>');
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
fadeOutAlert();
|
||||||
|
}, 8000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSuccess(message) {
|
||||||
|
var errorElement = document.getElementById("errorNewClassification");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-success';
|
||||||
|
errorElement.innerHTML = '<i class="fas fa-check-circle me-2"></i>' + message;
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
fadeOutAlert();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showWarning(message) {
|
||||||
|
var errorElement = document.getElementById("errorNewClassification");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-warning';
|
||||||
|
errorElement.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>' + message;
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
fadeOutAlert();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLoading() {
|
||||||
|
var errorElement = document.getElementById("errorNewClassification");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-info';
|
||||||
|
errorElement.innerHTML = '<div class="d-flex align-items-center"><div class="spinner-border spinner-border-sm me-3" role="status"><span class="visually-hidden">Laden...</span></div><div>Speichere Relation...</div></div>';
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideLoading() {
|
||||||
|
var errorElement = document.getElementById("errorNewClassification");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideAlert() {
|
||||||
|
var errorElement = document.getElementById("errorNewClassification");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fadeOutAlert() {
|
||||||
|
var errorElement = document.getElementById("errorNewClassification");
|
||||||
|
if (errorElement && errorElement.style.display !== 'none') {
|
||||||
|
$(errorElement).fadeOut(500, function() {
|
||||||
|
errorElement.style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
var relationTextElement = document.getElementById("search_relation");
|
||||||
|
if (relationTextElement) {
|
||||||
|
relationTextElement.value = '';
|
||||||
|
}
|
||||||
|
$('#classification-anlegen').removeData('record-id');
|
||||||
|
$('#classification-anlegen').removeData('relation-id');
|
||||||
|
hideAlert();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Delete relation for a classification
|
||||||
|
function DeleteRelation(AnchorID, LinkingID)
|
||||||
|
{
|
||||||
|
if (!confirm('Möchten Sie diese Relation wirklich löschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.post("ajax/deleteRelation.php", {
|
||||||
|
AnchorID : AnchorID,
|
||||||
|
LinkingID : LinkingID
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
var e = document.getElementById("errorNewClassification");
|
||||||
|
e.classList.add('alert-success');
|
||||||
|
e.textContent = ' Relation gelöscht';
|
||||||
|
e.style.display = 'block';
|
||||||
|
|
||||||
|
showRelations(AnchorID,0,0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the whole classification entry
|
||||||
|
function DeleteTerm(AnchorID)
|
||||||
|
{
|
||||||
|
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.post("ajax/deleteTerm.php", {
|
||||||
|
AnchorID: AnchorID[0]
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
|
||||||
|
if (status === "success" && metadata.success === true) {
|
||||||
|
// Tabelle neu laden – Zeile ist serverseitig wirklich gelöscht
|
||||||
|
$table.bootstrapTable('refresh');
|
||||||
|
} else {
|
||||||
|
// Serverseitiger Fehler: Zeile wieder einblenden und Meldung anzeigen
|
||||||
|
$table.bootstrapTable('refresh');
|
||||||
|
alert('Fehler beim Löschen: ' + (metadata.error || 'Unbekannter Fehler'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify classification and its relations
|
||||||
|
function ModifyTerm(AnchorID)
|
||||||
|
{
|
||||||
|
$("#ID").val(AnchorID[0]);
|
||||||
|
|
||||||
|
// Sichere Element-Zugriffe mit Null-Checks
|
||||||
|
var termField = document.getElementById("new_term");
|
||||||
|
var notationField = document.getElementById("new_notation");
|
||||||
|
var scopenoteField = document.getElementById("new_scopenote");
|
||||||
|
var relationLabel = document.getElementById("new_relation_label");
|
||||||
|
var relationTable = document.getElementById("relation_table");
|
||||||
|
var saveBtn = document.getElementById("newClassificationSave");
|
||||||
|
var dismissBtn = document.getElementById("newClassificationDismiss");
|
||||||
|
var modifyBtn = document.getElementById("ClassificationModifySave");
|
||||||
|
var relationDismissBtn = document.getElementById("newRelationClassificationDismiss");
|
||||||
|
var errorDiv = document.getElementById("errorNewClassification");
|
||||||
|
|
||||||
|
// Name-Feld deaktivieren (darf nicht geändert werden)
|
||||||
|
if (termField) termField.disabled = true;
|
||||||
|
|
||||||
|
// Andere Felder aktivieren
|
||||||
|
if (notationField) notationField.disabled = false;
|
||||||
|
if (scopenoteField) scopenoteField.disabled = false;
|
||||||
|
|
||||||
|
// Relationen-Bereich sofort sichtbar machen bei Bearbeitung!
|
||||||
|
if (relationLabel) relationLabel.style.display = 'block';
|
||||||
|
if (relationTable) relationTable.style.display = 'block';
|
||||||
|
|
||||||
|
// Buttons konfigurieren
|
||||||
|
if (saveBtn) saveBtn.style.display = 'none';
|
||||||
|
if (dismissBtn) dismissBtn.style.display = 'inline';
|
||||||
|
if (modifyBtn) modifyBtn.style.display = 'inline';
|
||||||
|
if (relationDismissBtn) relationDismissBtn.style.display = 'none';
|
||||||
|
|
||||||
|
// Alert verstecken
|
||||||
|
if (errorDiv) errorDiv.style.display = 'none';
|
||||||
|
|
||||||
|
$("#NewClassificationModalHeadline").text("Klassifikation ändern");
|
||||||
|
|
||||||
|
$.get("/Thesaurus/ajax/getAuthorityDataRaw.php", {
|
||||||
|
id : AnchorID[0],
|
||||||
|
authType :"Classification"
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
console.log('📥 getAuthorityDataRaw status:', status);
|
||||||
|
console.log('📥 getAuthorityDataRaw data:', data);
|
||||||
|
if (status == "success")
|
||||||
|
{
|
||||||
|
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
console.log('📥 metadata:', metadata);
|
||||||
|
dt = (metadata['rows'][0]);
|
||||||
|
console.log('📥 dt:', dt);
|
||||||
|
if (dt) {
|
||||||
|
$("#new_term").val(dt.Text);
|
||||||
|
$("#new_id").val(dt.ID);
|
||||||
|
$("#new_notation").val(dt.Notation);
|
||||||
|
$("#new_descriptor").val(dt.Descriptor);
|
||||||
|
$("#new_scopenote").val(dt.Scopenote);
|
||||||
|
console.log('✅ Felder befüllt mit:', dt.Text);
|
||||||
|
} else {
|
||||||
|
console.error('❌ Keine Daten in rows[0]');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
).fail(function(xhr, status, error) {
|
||||||
|
console.error('❌ AJAX Fehler:', status, error);
|
||||||
|
});
|
||||||
|
|
||||||
|
$.get("ajax/getRelations.php", {
|
||||||
|
anchorID:AnchorID[0],
|
||||||
|
authType: "Classification"
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
$(".new_modal_content").html(data);
|
||||||
|
$("#NewClassificationModal").modal("show");
|
||||||
|
|
||||||
|
// Autocomplete neu initialisieren nach Modal-Öffnung
|
||||||
|
setTimeout(function() {
|
||||||
|
if (typeof searchRelation === 'function') {
|
||||||
|
console.log('🔄 Autocomplete wird neu initialisiert...');
|
||||||
|
searchRelation();
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewrite records after updating a classification entry
|
||||||
|
function UpdateEntry(authType)
|
||||||
|
{
|
||||||
|
var AnchorID = $("#ID").val();
|
||||||
|
var term = $("#new_term").val();
|
||||||
|
term = term.trim();
|
||||||
|
var notation = $("#new_notation").val();
|
||||||
|
notation = notation.trim();
|
||||||
|
var type = 'Classification';
|
||||||
|
var detailtype = '';
|
||||||
|
var classification = '';
|
||||||
|
var scopenote = $("#new_scopenote").val();
|
||||||
|
scopenote = scopenote.trim();
|
||||||
|
|
||||||
|
if (term.length == 0) {
|
||||||
|
var a = document.getElementById("errorNewClassification");
|
||||||
|
a.classList.add('alert-danger');
|
||||||
|
a.textContent = 'Bitte Klassifikation eingeben!';
|
||||||
|
a.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = document.getElementById("errorNewClassification");
|
||||||
|
a.classList.remove('alert-danger');
|
||||||
|
a.textContent = '';
|
||||||
|
a.style.display = 'none';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$.post("ajax/UpdateTerm.php", {
|
||||||
|
AnchorID: AnchorID,
|
||||||
|
authType: "Classification",
|
||||||
|
term: term,
|
||||||
|
notation: notation,
|
||||||
|
type: type,
|
||||||
|
detailtype: detailtype,
|
||||||
|
classification: classification,
|
||||||
|
scopenote: scopenote
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
document.getElementById("relation_table").style.display = 'block'
|
||||||
|
document.getElementById("newClassificationSave").style.display = 'none'
|
||||||
|
document.getElementById("newClassificationDismiss").style.display = 'inline'
|
||||||
|
document.getElementById("ClassificationModifySave").style.display = 'none'
|
||||||
|
document.getElementById("newRelationClassificationDismiss").style.display = 'none'
|
||||||
|
var e = document.getElementById("errorNewClassification");
|
||||||
|
e.classList.add('alert-success');
|
||||||
|
e.textContent = ' Klassifikation geändert ';
|
||||||
|
e.style.display = 'block';
|
||||||
|
|
||||||
|
$("#NewClassificationModalHeadline").text("Klassifikation ändern");
|
||||||
|
$("#NewClassificationModal").modal("show");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$table.bootstrapTable('refresh')
|
||||||
|
}
|
||||||
|
|
||||||
|
function ShowModalDetails(id) {
|
||||||
|
|
||||||
|
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
|
||||||
|
authType : 'Classification',
|
||||||
|
offset : 0,
|
||||||
|
id : id,
|
||||||
|
sort : 'Anchor.Text',
|
||||||
|
limit : 1
|
||||||
|
},
|
||||||
|
function(data, status) {
|
||||||
|
if (status == "success") {
|
||||||
|
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
var row = metadata['rows'];
|
||||||
|
//Assign existing values to the modal popup fields
|
||||||
|
$("#display_term").val(row[0].Text);
|
||||||
|
$("#display_id").val(row[0].ID);
|
||||||
|
$("#display_notation").val(row[0].Notation);
|
||||||
|
$("#display_descriptor").val(row[0].Descriptor);
|
||||||
|
$("#display_scopenote").val(row[0].Scopenote);
|
||||||
|
|
||||||
|
var html = []
|
||||||
|
var txt = ''
|
||||||
|
$.each(row[0].Relations, function (key, value) {
|
||||||
|
txt = txt +'<div class="col-sm-12">';
|
||||||
|
txt = txt +'<div class="col-sm-2"></div>';
|
||||||
|
txt = txt + '<div class="col-sm-3"><input type="text" class="form-control" disabled id="display_relationtype_' +key +'" /></div>';
|
||||||
|
txt = txt + '<div class="col-sm-5"><input type="text" class="form-control" disabled id="display_textrelation_' +key +'" /></div>';
|
||||||
|
txt = txt + '<div class="col-sm-2"><input type="text" class="form-control" disabled id="display_IDRelation_' +key +'" /></div>';
|
||||||
|
txt = txt +'</div>';
|
||||||
|
})
|
||||||
|
$(".modal_content").html(txt);
|
||||||
|
$.each(row[0].Relations, function (key, value) {
|
||||||
|
$("#display_relationtype_" +key).val(value['Relationtype']);
|
||||||
|
$("#display_textrelation_" +key).val(value['TextRelation']);
|
||||||
|
$("#display_IDRelation_" +key).val(value['IDRelation']);
|
||||||
|
})
|
||||||
|
$("#display_relations").val(txt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$("#DetailsModal").modal("show");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function refreshPage()
|
||||||
|
{
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function CopyLink(value)
|
||||||
|
{
|
||||||
|
var tempInput = document.createElement("input");
|
||||||
|
tempInput.value = value;
|
||||||
|
document.body.appendChild(tempInput);
|
||||||
|
tempInput.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(tempInput);
|
||||||
|
}
|
||||||
633
js/CorporateScript.js
Normal file
633
js/CorporateScript.js
Normal file
@ -0,0 +1,633 @@
|
|||||||
|
/**
|
||||||
|
* Created by Roland on 2021/07/01.
|
||||||
|
* Updated for BIBB Layout 2024
|
||||||
|
*/
|
||||||
|
// CORPORATES
|
||||||
|
|
||||||
|
// Show blank screen to add new Corporate
|
||||||
|
function newCorporatesShow() {
|
||||||
|
|
||||||
|
var b = document.getElementById("newCorporateSave");
|
||||||
|
b.style.display='inline';
|
||||||
|
var c = document.getElementById("newCorporateDismiss");
|
||||||
|
c.style.display='inline';
|
||||||
|
document.getElementById("CorporateModifySave").style.display = 'none';
|
||||||
|
document.getElementById("new_term").disabled = false;
|
||||||
|
document.getElementById("new_type").disabled = false;
|
||||||
|
document.getElementById("new_scopenote").disabled = false;
|
||||||
|
|
||||||
|
// Relationen-Bereich verstecken bei neuer Körperschaft
|
||||||
|
document.getElementById("new_relation_label").style.display = 'none';
|
||||||
|
document.getElementById("newRelationCorporateDismiss").style.display = 'none';
|
||||||
|
showRelations(0,0,0);
|
||||||
|
var a = document.getElementById("relation_table");
|
||||||
|
if (a != undefined) {
|
||||||
|
document.getElementById("relation_table").style.display = 'none';
|
||||||
|
}
|
||||||
|
var e = document.getElementById("errorNewCorporate");
|
||||||
|
e.style.display = 'none';
|
||||||
|
|
||||||
|
$("#new_term").val("");
|
||||||
|
$("#new_scopenote").val("");
|
||||||
|
|
||||||
|
$("#NewCorporateModal").modal("show");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert new corporate record
|
||||||
|
function CreateNewEntry(type) {
|
||||||
|
// get values
|
||||||
|
|
||||||
|
var term = $("#new_term").val();
|
||||||
|
term = term.trim();
|
||||||
|
var type = $("#new_type").val();
|
||||||
|
type = type.trim();
|
||||||
|
var detailtype = "";
|
||||||
|
var classification = "";
|
||||||
|
var scopenote = $("#new_scopenote").val();
|
||||||
|
scopenote = scopenote.trim();
|
||||||
|
|
||||||
|
if (term.length == 0) {
|
||||||
|
var a = document.getElementById("errorNewCorporate");
|
||||||
|
a.classList.add('alert-danger');
|
||||||
|
a.textContent = 'Bitte Körperschaft eingeben!';
|
||||||
|
a.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = document.getElementById("errorNewCorporate");
|
||||||
|
a.classList.remove('alert-danger');
|
||||||
|
a.textContent = '';
|
||||||
|
a.style.display = 'none';
|
||||||
|
|
||||||
|
$.post("ajax/newCorporate.php", {
|
||||||
|
term : term,
|
||||||
|
type : type,
|
||||||
|
detailtype : detailtype,
|
||||||
|
classification: classification,
|
||||||
|
scopenote : scopenote
|
||||||
|
}, function (data, status) {
|
||||||
|
|
||||||
|
var dt = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
var e = document.getElementById("errorNewCorporate");
|
||||||
|
|
||||||
|
if (dt['status'] == 200)
|
||||||
|
{
|
||||||
|
var AnchorID = dt["Anchor"];
|
||||||
|
var EntryID = dt["Entry"];
|
||||||
|
var LinkingID = dt["Linking"];
|
||||||
|
|
||||||
|
var b = document.getElementById("newCorporateSave");
|
||||||
|
b.style.display='none';
|
||||||
|
var c = document.getElementById("newCorporateDismiss");
|
||||||
|
c.style.display='none';
|
||||||
|
document.getElementById("new_term").disabled = true;
|
||||||
|
document.getElementById("new_type").disabled = true;
|
||||||
|
document.getElementById("new_scopenote").disabled = true;
|
||||||
|
$table.bootstrapTable('refresh');
|
||||||
|
|
||||||
|
e.classList.remove('alert-danger');
|
||||||
|
e.classList.add('alert-success');
|
||||||
|
e.textContent = 'Neue Körperschaft aufgenommen';
|
||||||
|
e.style.display = 'block';
|
||||||
|
|
||||||
|
// ID setzen für Relationen
|
||||||
|
$('#ID').val(AnchorID);
|
||||||
|
|
||||||
|
// Relationen-Bereich anzeigen nach Speichern
|
||||||
|
document.getElementById("new_relation_label").style.display = 'block';
|
||||||
|
document.getElementById("newRelationCorporateDismiss").style.display = 'inline';
|
||||||
|
showRelations(AnchorID,EntryID,LinkingID);
|
||||||
|
} else {
|
||||||
|
e.classList.remove('alert-success');
|
||||||
|
e.classList.add('alert-danger');
|
||||||
|
e.textContent = dt['message'];
|
||||||
|
e.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show relations of a corporate record
|
||||||
|
function showRelations(Anchor,Entry,Linking)
|
||||||
|
{
|
||||||
|
$.get("ajax/getRelations.php", {
|
||||||
|
anchorID:Anchor,
|
||||||
|
authType:"Corporate"
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
$(".new_modal_content").html(data);
|
||||||
|
// Tabelle nur anzeigen wenn Relationen vorhanden
|
||||||
|
var relationTable = document.getElementById("relation_table");
|
||||||
|
if (relationTable) {
|
||||||
|
if (data && data.trim().length > 0) {
|
||||||
|
relationTable.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
relationTable.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$("#NewCorporateModal").modal("show");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// create new relation for a corporate record
|
||||||
|
function CreateNewRelation(AnchorID, relationID, event) {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🚀 CreateNewRelation:', { AnchorID, relationID });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Validierung
|
||||||
|
if (!AnchorID || !relationID) {
|
||||||
|
showError('Fehlende Parameter: AnchorID oder relationID');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var relationTypeElement = document.getElementById("new_relationtype");
|
||||||
|
|
||||||
|
if (!relationTypeElement) {
|
||||||
|
showError('Relationstyp-Element nicht gefunden');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var relationType = relationTypeElement.value;
|
||||||
|
|
||||||
|
if (!relationType || relationType.trim() === '') {
|
||||||
|
showError('Bitte wählen Sie einen Relationstyp aus');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading anzeigen
|
||||||
|
showLoading();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "ajax/writeNewRelation.php",
|
||||||
|
type: "POST",
|
||||||
|
data: {
|
||||||
|
AnchorID: AnchorID,
|
||||||
|
relationType: relationType,
|
||||||
|
relationID: relationID
|
||||||
|
},
|
||||||
|
dataType: "text",
|
||||||
|
timeout: 10000,
|
||||||
|
|
||||||
|
success: function(response) {
|
||||||
|
console.log("✅ Success! Response:", response);
|
||||||
|
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(response);
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
showError('<strong>Serverfehler:</strong><br>' + data.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.success === true) {
|
||||||
|
showSuccess('Neue Relation erfolgreich aufgenommen!');
|
||||||
|
|
||||||
|
if (typeof showRelations === 'function') {
|
||||||
|
setTimeout(function() {
|
||||||
|
showRelations(AnchorID, 0, 0);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForm();
|
||||||
|
} else {
|
||||||
|
showError('Unerwartete Antwort vom Server');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (parseError) {
|
||||||
|
console.log("⚠️ Keine JSON-Antwort, prüfe Plain-Text");
|
||||||
|
|
||||||
|
if (response && response.length > 0 && !isNaN(response.trim())) {
|
||||||
|
showSuccess('Neue Relation erfolgreich aufgenommen! (ID: ' + response.trim() + ')');
|
||||||
|
|
||||||
|
if (typeof showRelations === 'function') {
|
||||||
|
setTimeout(function() {
|
||||||
|
showRelations(AnchorID, 0, 0);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForm();
|
||||||
|
} else {
|
||||||
|
showError('<strong>Unerwartete Antwort:</strong><br>' +
|
||||||
|
'<code>' + response.substring(0, 200) + '</code>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error("❌ AJAX Error:", { status, error, xhr });
|
||||||
|
|
||||||
|
var errorMessage = '<strong>Fehler beim Speichern der Relation</strong><br><br>';
|
||||||
|
|
||||||
|
if (xhr.status === 0) {
|
||||||
|
errorMessage += '❌ Keine Verbindung zum Server möglich<br>';
|
||||||
|
errorMessage += '<small>Prüfen Sie Ihre Internetverbindung</small>';
|
||||||
|
} else if (xhr.status === 404) {
|
||||||
|
errorMessage += '❌ Datei nicht gefunden (404)<br>';
|
||||||
|
errorMessage += '<small>ajax/writeNewRelation.php existiert nicht</small>';
|
||||||
|
} else if (xhr.status === 500) {
|
||||||
|
errorMessage += '❌ Interner Serverfehler (500)<br>';
|
||||||
|
if (xhr.responseText) {
|
||||||
|
errorMessage += '<br><details><summary>Details anzeigen</summary><pre>' +
|
||||||
|
xhr.responseText.substring(0, 500) + '</pre></details>';
|
||||||
|
}
|
||||||
|
} else if (status === 'timeout') {
|
||||||
|
errorMessage += '❌ Zeitüberschreitung<br>';
|
||||||
|
errorMessage += '<small>Der Server hat nicht rechtzeitig geantwortet</small>';
|
||||||
|
} else if (status === 'parsererror') {
|
||||||
|
errorMessage += '❌ Fehler beim Verarbeiten der Server-Antwort<br>';
|
||||||
|
errorMessage += '<small>Die Server-Antwort war ungültig</small>';
|
||||||
|
} else {
|
||||||
|
errorMessage += '❌ ' + status + ' (' + xhr.status + ')';
|
||||||
|
if (error) {
|
||||||
|
errorMessage += '<br><small>' + error + '</small>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showError(errorMessage);
|
||||||
|
},
|
||||||
|
|
||||||
|
complete: function(xhr, status) {
|
||||||
|
console.log("🏁 Request abgeschlossen:", status);
|
||||||
|
if (status !== 'success') {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
hideLoading();
|
||||||
|
console.error("💥 Exception:", e);
|
||||||
|
showError('<strong>JavaScript-Fehler:</strong><br>' + e.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(message) {
|
||||||
|
var errorElement = document.getElementById("errorNewCorporate");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-danger';
|
||||||
|
errorElement.innerHTML = '<i class="fas fa-exclamation-circle me-2"></i>' + message.replace(/\n/g, '<br>');
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
fadeOutAlert();
|
||||||
|
}, 8000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSuccess(message) {
|
||||||
|
var errorElement = document.getElementById("errorNewCorporate");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-success';
|
||||||
|
errorElement.innerHTML = '<i class="fas fa-check-circle me-2"></i>' + message;
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
fadeOutAlert();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showWarning(message) {
|
||||||
|
var errorElement = document.getElementById("errorNewCorporate");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-warning';
|
||||||
|
errorElement.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>' + message;
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
fadeOutAlert();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLoading() {
|
||||||
|
var errorElement = document.getElementById("errorNewCorporate");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-info';
|
||||||
|
errorElement.innerHTML = '<div class="d-flex align-items-center"><div class="spinner-border spinner-border-sm me-3" role="status"><span class="visually-hidden">Laden...</span></div><div>Speichere Relation...</div></div>';
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideLoading() {
|
||||||
|
var errorElement = document.getElementById("errorNewCorporate");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideAlert() {
|
||||||
|
var errorElement = document.getElementById("errorNewCorporate");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fadeOutAlert() {
|
||||||
|
var errorElement = document.getElementById("errorNewCorporate");
|
||||||
|
if (errorElement && errorElement.style.display !== 'none') {
|
||||||
|
$(errorElement).fadeOut(500, function() {
|
||||||
|
errorElement.style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
var relationTextElement = document.getElementById("search_relation");
|
||||||
|
if (relationTextElement) {
|
||||||
|
relationTextElement.value = '';
|
||||||
|
}
|
||||||
|
$('#corporate-anlegen').removeData('record-id');
|
||||||
|
$('#corporate-anlegen').removeData('relation-id');
|
||||||
|
hideAlert();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Delete relation for a corporate
|
||||||
|
function DeleteRelation(AnchorID, LinkingID)
|
||||||
|
{
|
||||||
|
if (!confirm('Möchten Sie diese Relation wirklich löschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.post("ajax/deleteRelation.php", {
|
||||||
|
AnchorID : AnchorID,
|
||||||
|
LinkingID : LinkingID
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
var e = document.getElementById("errorNewCorporate");
|
||||||
|
e.classList.add('alert-success');
|
||||||
|
e.textContent = ' Relation gelöscht';
|
||||||
|
e.style.display = 'block';
|
||||||
|
|
||||||
|
showRelations(AnchorID,0,0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the whole corporate entry
|
||||||
|
function DeleteTerm(AnchorID)
|
||||||
|
{
|
||||||
|
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.post("ajax/deleteTerm.php", {
|
||||||
|
AnchorID: AnchorID[0]
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
|
||||||
|
if (status === "success" && metadata.success === true) {
|
||||||
|
// Tabelle neu laden – Zeile ist serverseitig wirklich gelöscht
|
||||||
|
$table.bootstrapTable('refresh');
|
||||||
|
} else {
|
||||||
|
// Serverseitiger Fehler: Zeile wieder einblenden und Meldung anzeigen
|
||||||
|
$table.bootstrapTable('refresh');
|
||||||
|
alert('Fehler beim Löschen: ' + (metadata.error || 'Unbekannter Fehler'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Modify corporate and its relations
|
||||||
|
function ModifyTerm(AnchorID)
|
||||||
|
{
|
||||||
|
// Name-Feld deaktivieren (darf nicht geändert werden)
|
||||||
|
document.getElementById("new_term").disabled = true;
|
||||||
|
|
||||||
|
// Andere Felder aktivieren
|
||||||
|
document.getElementById("new_type").disabled = false;
|
||||||
|
document.getElementById("new_scopenote").disabled = false;
|
||||||
|
|
||||||
|
$("#ID").val(AnchorID[0]);
|
||||||
|
|
||||||
|
// Relationen-Bereich sofort sichtbar machen bei Bearbeitung!
|
||||||
|
document.getElementById("new_relation_label").style.display = 'block';
|
||||||
|
document.getElementById("relation_table").style.display = 'block';
|
||||||
|
|
||||||
|
// Buttons konfigurieren
|
||||||
|
document.getElementById("newCorporateSave").style.display = 'none';
|
||||||
|
document.getElementById("newCorporateDismiss").style.display = 'inline';
|
||||||
|
document.getElementById("CorporateModifySave").style.display = 'inline';
|
||||||
|
document.getElementById("newRelationCorporateDismiss").style.display = 'none';
|
||||||
|
|
||||||
|
// Alert verstecken
|
||||||
|
document.getElementById("errorNewCorporate").style.display = 'none';
|
||||||
|
|
||||||
|
$("#NewCorporateModalHeadline").text("Körperschaft ändern");
|
||||||
|
|
||||||
|
$.get("/Thesaurus/ajax/getAuthorityDataRaw.php", {
|
||||||
|
id : AnchorID[0],
|
||||||
|
authType :"Corporate"
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
console.log('📥 getAuthorityDataRaw status:', status);
|
||||||
|
console.log('📥 getAuthorityDataRaw data:', data);
|
||||||
|
if (status == "success")
|
||||||
|
{
|
||||||
|
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
console.log('📥 metadata:', metadata);
|
||||||
|
dt = (metadata['rows'][0]);
|
||||||
|
console.log('📥 dt:', dt);
|
||||||
|
if (dt) {
|
||||||
|
$("#new_term").val(dt.Text);
|
||||||
|
$("#new_id").val(dt.ID);
|
||||||
|
$("#new_descriptor").val(dt.Descriptor);
|
||||||
|
$("#new_type").val(dt.Type);
|
||||||
|
$("#new_scopenote").val(dt.Scopenote);
|
||||||
|
console.log('✅ Felder befüllt mit:', dt.Text);
|
||||||
|
} else {
|
||||||
|
console.error('❌ Keine Daten in rows[0]');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
).fail(function(xhr, status, error) {
|
||||||
|
console.error('❌ AJAX Fehler:', status, error);
|
||||||
|
});
|
||||||
|
|
||||||
|
$.get("ajax/getRelations.php", {
|
||||||
|
anchorID:AnchorID[0],
|
||||||
|
authType: "Corporate"
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
$(".new_modal_content").html(data);
|
||||||
|
$("#NewCorporateModal").modal("show");
|
||||||
|
|
||||||
|
// Autocomplete neu initialisieren nach Modal-Öffnung
|
||||||
|
setTimeout(function() {
|
||||||
|
if (typeof searchRelation === 'function') {
|
||||||
|
console.log('🔄 Autocomplete wird neu initialisiert...');
|
||||||
|
searchRelation();
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewrite records after updating a corporate entry
|
||||||
|
function UpdateEntry(authType)
|
||||||
|
{
|
||||||
|
var AnchorID = $("#ID").val();
|
||||||
|
var term = $("#new_term").val();
|
||||||
|
term = term.trim();
|
||||||
|
var type = $("#new_type").val();
|
||||||
|
type = type.trim();
|
||||||
|
var detailtype = '';
|
||||||
|
var classification = '';
|
||||||
|
var scopenote = $("#new_scopenote").val();
|
||||||
|
scopenote = scopenote.trim();
|
||||||
|
|
||||||
|
if (term.length == 0) {
|
||||||
|
var a = document.getElementById("errorNewCorporate");
|
||||||
|
a.classList.add('alert-danger');
|
||||||
|
a.textContent = 'Bitte Körperschaft eingeben!';
|
||||||
|
a.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = document.getElementById("errorNewCorporate");
|
||||||
|
a.classList.remove('alert-danger');
|
||||||
|
a.textContent = '';
|
||||||
|
a.style.display = 'none';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$.post("ajax/UpdateTerm.php", {
|
||||||
|
AnchorID: AnchorID,
|
||||||
|
authType: "Corporate",
|
||||||
|
term: term,
|
||||||
|
type: type,
|
||||||
|
detailtype: detailtype,
|
||||||
|
classification: classification,
|
||||||
|
scopenote: scopenote
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
document.getElementById("relation_table").style.display = 'block'
|
||||||
|
document.getElementById("newCorporateSave").style.display = 'none'
|
||||||
|
document.getElementById("newCorporateDismiss").style.display = 'inline'
|
||||||
|
document.getElementById("CorporateModifySave").style.display = 'none'
|
||||||
|
document.getElementById("newRelationCorporateDismiss").style.display = 'none'
|
||||||
|
var e = document.getElementById("errorNewCorporate");
|
||||||
|
e.classList.add('alert-success');
|
||||||
|
e.textContent = ' Körperschaft geändert ';
|
||||||
|
e.style.display = 'block';
|
||||||
|
|
||||||
|
$("#NewCorporateModalHeadline").text("Körperschaft ändern");
|
||||||
|
$("#NewCorporateModal").modal("show");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$table.bootstrapTable('refresh')
|
||||||
|
}
|
||||||
|
|
||||||
|
function ShowModalDetails(id) {
|
||||||
|
|
||||||
|
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
|
||||||
|
authType : 'Corporate',
|
||||||
|
offset : 0,
|
||||||
|
id : id,
|
||||||
|
sort : 'Anchor.Text',
|
||||||
|
limit : 1
|
||||||
|
},
|
||||||
|
function(data, status) {
|
||||||
|
if (status == "success") {
|
||||||
|
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
var row = metadata['rows'];
|
||||||
|
|
||||||
|
if (!row || row.length === 0) {
|
||||||
|
console.error('❌ Keine Daten gefunden für ID:', id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Assign existing values to the modal popup fields
|
||||||
|
$("#display_term").val(row[0].Text);
|
||||||
|
$("#display_id").val(row[0].ID);
|
||||||
|
$("#display_id_show").val(row[0].ID);
|
||||||
|
$("#display_descriptor").val(row[0].Descriptor);
|
||||||
|
$("#display_type").val(row[0].Type);
|
||||||
|
$("#display_scopenote").val(row[0].Scopenote);
|
||||||
|
|
||||||
|
var html = []
|
||||||
|
var txt = ''
|
||||||
|
$.each(row[0].Relations, function (key, value) {
|
||||||
|
txt = txt +'<div class="col-sm-12">';
|
||||||
|
txt = txt +'<div class="col-sm-2"></div>';
|
||||||
|
txt = txt + '<div class="col-sm-3"><input type="text" class="form-control" disabled id="display_relationtype_' +key +'" /></div>';
|
||||||
|
txt = txt + '<div class="col-sm-5"><input type="text" class="form-control" disabled id="display_textrelation_' +key +'" /></div>';
|
||||||
|
txt = txt + '<div class="col-sm-2"><input type="text" class="form-control" disabled id="display_IDRelation_' +key +'" /></div>';
|
||||||
|
txt = txt +'</div>';
|
||||||
|
})
|
||||||
|
$(".modal_content").html(txt);
|
||||||
|
$.each(row[0].Relations, function (key, value) {
|
||||||
|
$("#display_relationtype_" +key).val(value['Relationtype']);
|
||||||
|
$("#display_textrelation_" +key).val(value['TextRelation']);
|
||||||
|
$("#display_IDRelation_" +key).val(value['IDRelation']);
|
||||||
|
})
|
||||||
|
$("#display_relations").val(txt);
|
||||||
|
|
||||||
|
// Modal anzeigen
|
||||||
|
$("#DetailsModal").modal("show");
|
||||||
|
|
||||||
|
// Semantisches Netz erstellen (nach Modal sichtbar)
|
||||||
|
setTimeout(function() {
|
||||||
|
if (typeof createSemanticNetwork === 'function') {
|
||||||
|
createSemanticNetwork(row[0].Text, row[0].ID, row[0].Relations);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function refreshPage()
|
||||||
|
{
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function CopyLink(value)
|
||||||
|
{
|
||||||
|
var tempInput = document.createElement("input");
|
||||||
|
tempInput.value = value;
|
||||||
|
document.body.appendChild(tempInput);
|
||||||
|
tempInput.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(tempInput);
|
||||||
|
}
|
||||||
629
js/PersonScript.js
Normal file
629
js/PersonScript.js
Normal file
@ -0,0 +1,629 @@
|
|||||||
|
/**
|
||||||
|
* Created by Roland on 2021/07/01.
|
||||||
|
* Updated for BIBB Layout 2024
|
||||||
|
*/
|
||||||
|
// PERSONS
|
||||||
|
|
||||||
|
// Show blank screen to add new Person
|
||||||
|
function newPersonShow() {
|
||||||
|
|
||||||
|
var b = document.getElementById("newSubjectSave");
|
||||||
|
b.style.display='inline';
|
||||||
|
var c = document.getElementById("newSubjectDismiss");
|
||||||
|
c.style.display='inline';
|
||||||
|
document.getElementById("SubjectModifySave").style.display = 'none';
|
||||||
|
document.getElementById("new_term").disabled = false;
|
||||||
|
document.getElementById("new_scopenote").disabled = false;
|
||||||
|
|
||||||
|
// Relationen-Bereich verstecken bei neuer Person
|
||||||
|
document.getElementById("new_relation_label").style.display = 'none';
|
||||||
|
document.getElementById("newRelationSubjectDismiss").style.display = 'none';
|
||||||
|
showRelations(0,0,0);
|
||||||
|
var a = document.getElementById("relation_table");
|
||||||
|
if (a != undefined) {
|
||||||
|
document.getElementById("relation_table").style.display = 'none';
|
||||||
|
}
|
||||||
|
var e = document.getElementById("errorNewSubject");
|
||||||
|
e.style.display = 'none';
|
||||||
|
|
||||||
|
$("#new_term").val("");
|
||||||
|
$("#new_scopenote").val("");
|
||||||
|
|
||||||
|
$("#NewSubjectModal").modal("show");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert new person record
|
||||||
|
function CreateNewEntry(type) {
|
||||||
|
// get values
|
||||||
|
|
||||||
|
var term = $("#new_term").val();
|
||||||
|
term = term.trim();
|
||||||
|
var detailtype = '';
|
||||||
|
var classification = '';
|
||||||
|
var scopenote = $("#new_scopenote").val();
|
||||||
|
scopenote = scopenote.trim();
|
||||||
|
|
||||||
|
if (term.length == 0) {
|
||||||
|
var a = document.getElementById("errorNewSubject");
|
||||||
|
a.classList.add('alert-danger');
|
||||||
|
a.textContent = 'Bitte Name eingeben!';
|
||||||
|
a.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = document.getElementById("errorNewSubject");
|
||||||
|
a.classList.remove('alert-danger');
|
||||||
|
a.textContent = '';
|
||||||
|
a.style.display = 'none';
|
||||||
|
|
||||||
|
$.post("ajax/newPerson.php", {
|
||||||
|
term : term,
|
||||||
|
type : type,
|
||||||
|
detailtype : detailtype,
|
||||||
|
classification: classification,
|
||||||
|
scopenote : scopenote
|
||||||
|
}, function (data, status) {
|
||||||
|
|
||||||
|
var dt = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
var e = document.getElementById("errorNewSubject");
|
||||||
|
|
||||||
|
if (dt['status'] == 200)
|
||||||
|
{
|
||||||
|
var AnchorID = dt["Anchor"];
|
||||||
|
var EntryID = dt["Entry"];
|
||||||
|
var LinkingID = dt["Linking"];
|
||||||
|
|
||||||
|
var b = document.getElementById("newSubjectSave");
|
||||||
|
b.style.display='none';
|
||||||
|
var c = document.getElementById("newSubjectDismiss");
|
||||||
|
c.style.display='none';
|
||||||
|
document.getElementById("new_term").disabled = true;
|
||||||
|
document.getElementById("new_scopenote").disabled = true;
|
||||||
|
$table.bootstrapTable('refresh');
|
||||||
|
|
||||||
|
e.classList.remove('alert-danger');
|
||||||
|
e.classList.add('alert-success');
|
||||||
|
e.textContent = 'Neuen Namen aufgenommen';
|
||||||
|
e.style.display = 'block';
|
||||||
|
|
||||||
|
// ID setzen für Relationen
|
||||||
|
$('#ID').val(AnchorID);
|
||||||
|
|
||||||
|
// Relationen-Bereich anzeigen nach Speichern
|
||||||
|
document.getElementById("new_relation_label").style.display = 'block';
|
||||||
|
document.getElementById("newRelationSubjectDismiss").style.display = 'inline';
|
||||||
|
showRelations(AnchorID,EntryID,LinkingID);
|
||||||
|
} else {
|
||||||
|
e.classList.remove('alert-success');
|
||||||
|
e.classList.add('alert-danger');
|
||||||
|
e.textContent = dt['message'];
|
||||||
|
e.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show relations of a person record
|
||||||
|
function showRelations(Anchor,Entry,Linking)
|
||||||
|
{
|
||||||
|
$.get("ajax/getRelations.php", {
|
||||||
|
anchorID:Anchor,
|
||||||
|
authType:"Person"
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
$(".new_modal_content").html(data);
|
||||||
|
// Tabelle nur anzeigen wenn Relationen vorhanden
|
||||||
|
var relationTable = document.getElementById("relation_table");
|
||||||
|
if (relationTable) {
|
||||||
|
if (data && data.trim().length > 0) {
|
||||||
|
relationTable.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
relationTable.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$("#NewSubjectModal").modal("show");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// create new relation for a person record
|
||||||
|
function CreateNewRelation(AnchorID, relationID, event) {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🚀 CreateNewRelation:', { AnchorID, relationID });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Validierung
|
||||||
|
if (!AnchorID || !relationID) {
|
||||||
|
showError('Fehlende Parameter: AnchorID oder relationID');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var relationTypeElement = document.getElementById("new_relationtype");
|
||||||
|
|
||||||
|
if (!relationTypeElement) {
|
||||||
|
showError('Relationstyp-Element nicht gefunden');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var relationType = relationTypeElement.value;
|
||||||
|
|
||||||
|
if (!relationType || relationType.trim() === '') {
|
||||||
|
showError('Bitte wählen Sie einen Relationstyp aus');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading anzeigen
|
||||||
|
showLoading();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "ajax/writeNewRelation.php",
|
||||||
|
type: "POST",
|
||||||
|
data: {
|
||||||
|
AnchorID: AnchorID,
|
||||||
|
relationType: relationType,
|
||||||
|
relationID: relationID
|
||||||
|
},
|
||||||
|
dataType: "text",
|
||||||
|
timeout: 10000,
|
||||||
|
|
||||||
|
success: function(response) {
|
||||||
|
console.log("✅ Success! Response:", response);
|
||||||
|
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(response);
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
showError('<strong>Serverfehler:</strong><br>' + data.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.success === true) {
|
||||||
|
showSuccess('Neue Relation erfolgreich aufgenommen!');
|
||||||
|
|
||||||
|
if (typeof showRelations === 'function') {
|
||||||
|
setTimeout(function() {
|
||||||
|
showRelations(AnchorID, 0, 0);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForm();
|
||||||
|
} else {
|
||||||
|
showError('Unerwartete Antwort vom Server');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (parseError) {
|
||||||
|
console.log("⚠️ Keine JSON-Antwort, prüfe Plain-Text");
|
||||||
|
|
||||||
|
if (response && response.length > 0 && !isNaN(response.trim())) {
|
||||||
|
showSuccess('Neue Relation erfolgreich aufgenommen! (ID: ' + response.trim() + ')');
|
||||||
|
|
||||||
|
if (typeof showRelations === 'function') {
|
||||||
|
setTimeout(function() {
|
||||||
|
showRelations(AnchorID, 0, 0);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForm();
|
||||||
|
} else {
|
||||||
|
showError('<strong>Unerwartete Antwort:</strong><br>' +
|
||||||
|
'<code>' + response.substring(0, 200) + '</code>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error("❌ AJAX Error:", { status, error, xhr });
|
||||||
|
|
||||||
|
var errorMessage = '<strong>Fehler beim Speichern der Relation</strong><br><br>';
|
||||||
|
|
||||||
|
if (xhr.status === 0) {
|
||||||
|
errorMessage += '❌ Keine Verbindung zum Server möglich<br>';
|
||||||
|
errorMessage += '<small>Prüfen Sie Ihre Internetverbindung</small>';
|
||||||
|
} else if (xhr.status === 404) {
|
||||||
|
errorMessage += '❌ Datei nicht gefunden (404)<br>';
|
||||||
|
errorMessage += '<small>ajax/writeNewRelation.php existiert nicht</small>';
|
||||||
|
} else if (xhr.status === 500) {
|
||||||
|
errorMessage += '❌ Interner Serverfehler (500)<br>';
|
||||||
|
if (xhr.responseText) {
|
||||||
|
errorMessage += '<br><details><summary>Details anzeigen</summary><pre>' +
|
||||||
|
xhr.responseText.substring(0, 500) + '</pre></details>';
|
||||||
|
}
|
||||||
|
} else if (status === 'timeout') {
|
||||||
|
errorMessage += '❌ Zeitüberschreitung<br>';
|
||||||
|
errorMessage += '<small>Der Server hat nicht rechtzeitig geantwortet</small>';
|
||||||
|
} else if (status === 'parsererror') {
|
||||||
|
errorMessage += '❌ Fehler beim Verarbeiten der Server-Antwort<br>';
|
||||||
|
errorMessage += '<small>Die Server-Antwort war ungültig</small>';
|
||||||
|
} else {
|
||||||
|
errorMessage += '❌ ' + status + ' (' + xhr.status + ')';
|
||||||
|
if (error) {
|
||||||
|
errorMessage += '<br><small>' + error + '</small>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showError(errorMessage);
|
||||||
|
},
|
||||||
|
|
||||||
|
complete: function(xhr, status) {
|
||||||
|
console.log("🏁 Request abgeschlossen:", status);
|
||||||
|
if (status !== 'success') {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
hideLoading();
|
||||||
|
console.error("💥 Exception:", e);
|
||||||
|
showError('<strong>JavaScript-Fehler:</strong><br>' + e.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(message) {
|
||||||
|
var errorElement = document.getElementById("errorNewSubject");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-danger';
|
||||||
|
errorElement.innerHTML = '<i class="fas fa-exclamation-circle me-2"></i>' + message.replace(/\n/g, '<br>');
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
fadeOutAlert();
|
||||||
|
}, 8000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSuccess(message) {
|
||||||
|
var errorElement = document.getElementById("errorNewSubject");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-success';
|
||||||
|
errorElement.innerHTML = '<i class="fas fa-check-circle me-2"></i>' + message;
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
fadeOutAlert();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showWarning(message) {
|
||||||
|
var errorElement = document.getElementById("errorNewSubject");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-warning';
|
||||||
|
errorElement.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>' + message;
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
fadeOutAlert();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLoading() {
|
||||||
|
var errorElement = document.getElementById("errorNewSubject");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-info';
|
||||||
|
errorElement.innerHTML = '<div class="d-flex align-items-center"><div class="spinner-border spinner-border-sm me-3" role="status"><span class="visually-hidden">Laden...</span></div><div>Speichere Relation...</div></div>';
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideLoading() {
|
||||||
|
var errorElement = document.getElementById("errorNewSubject");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideAlert() {
|
||||||
|
var errorElement = document.getElementById("errorNewSubject");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fadeOutAlert() {
|
||||||
|
var errorElement = document.getElementById("errorNewSubject");
|
||||||
|
if (errorElement && errorElement.style.display !== 'none') {
|
||||||
|
$(errorElement).fadeOut(500, function() {
|
||||||
|
errorElement.style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
var relationTextElement = document.getElementById("search_relation");
|
||||||
|
if (relationTextElement) {
|
||||||
|
relationTextElement.value = '';
|
||||||
|
}
|
||||||
|
$('#subject-anlegen').removeData('record-id');
|
||||||
|
$('#subject-anlegen').removeData('relation-id');
|
||||||
|
hideAlert();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Delete relation for a person
|
||||||
|
function DeleteRelation(AnchorID, LinkingID)
|
||||||
|
{
|
||||||
|
if (!confirm('Möchten Sie diese Relation wirklich löschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.post("ajax/deleteRelation.php", {
|
||||||
|
AnchorID : AnchorID,
|
||||||
|
LinkingID : LinkingID
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
var e = document.getElementById("errorNewSubject");
|
||||||
|
e.classList.add('alert-success');
|
||||||
|
e.textContent = ' Relation gelöscht';
|
||||||
|
e.style.display = 'block';
|
||||||
|
|
||||||
|
showRelations(AnchorID,0,0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the whole person entry
|
||||||
|
function DeleteTerm(AnchorID)
|
||||||
|
{
|
||||||
|
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.post("ajax/deleteTerm.php", {
|
||||||
|
AnchorID: AnchorID[0]
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
|
||||||
|
if (status === "success" && metadata.success === true) {
|
||||||
|
// Tabelle neu laden – Zeile ist serverseitig wirklich gelöscht
|
||||||
|
$table.bootstrapTable('refresh');
|
||||||
|
} else {
|
||||||
|
// Serverseitiger Fehler: Zeile wieder einblenden und Meldung anzeigen
|
||||||
|
$table.bootstrapTable('refresh');
|
||||||
|
alert('Fehler beim Löschen: ' + (metadata.error || 'Unbekannter Fehler'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Modify person and its relations
|
||||||
|
function ModifyPerson(AnchorID)
|
||||||
|
{
|
||||||
|
// Name-Feld deaktivieren (darf nicht geändert werden)
|
||||||
|
document.getElementById("new_term").disabled = true;
|
||||||
|
|
||||||
|
// Andere Felder aktivieren
|
||||||
|
document.getElementById("new_scopenote").disabled = false;
|
||||||
|
|
||||||
|
$("#ID").val(AnchorID[0]);
|
||||||
|
|
||||||
|
// Relationen-Bereich sofort sichtbar machen bei Bearbeitung!
|
||||||
|
document.getElementById("new_relation_label").style.display = 'block';
|
||||||
|
document.getElementById("relation_table").style.display = 'block';
|
||||||
|
|
||||||
|
// Buttons konfigurieren
|
||||||
|
document.getElementById("newSubjectSave").style.display = 'none';
|
||||||
|
document.getElementById("newSubjectDismiss").style.display = 'inline';
|
||||||
|
document.getElementById("SubjectModifySave").style.display = 'inline';
|
||||||
|
document.getElementById("newRelationSubjectDismiss").style.display = 'none';
|
||||||
|
|
||||||
|
// Alert verstecken
|
||||||
|
document.getElementById("errorNewSubject").style.display = 'none';
|
||||||
|
|
||||||
|
$("#NewSubjectModalHeadline").text("Person ändern");
|
||||||
|
|
||||||
|
$.get("/Thesaurus/ajax/getAuthorityDataRaw.php", {
|
||||||
|
id : AnchorID[0],
|
||||||
|
authType :"Person"
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
console.log('📥 getAuthorityDataRaw status:', status);
|
||||||
|
console.log('📥 getAuthorityDataRaw data:', data);
|
||||||
|
if (status == "success")
|
||||||
|
{
|
||||||
|
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
console.log('📥 metadata:', metadata);
|
||||||
|
dt = (metadata['rows'][0]);
|
||||||
|
console.log('📥 dt:', dt);
|
||||||
|
if (dt) {
|
||||||
|
$("#new_term").val(dt.Text);
|
||||||
|
$("#new_id").val(dt.ID);
|
||||||
|
$("#new_descriptor").val(dt.Descriptor);
|
||||||
|
$("#new_scopenote").val(dt.Scopenote);
|
||||||
|
console.log('✅ Felder befüllt mit:', dt.Text);
|
||||||
|
} else {
|
||||||
|
console.error('❌ Keine Daten in rows[0]');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
).fail(function(xhr, status, error) {
|
||||||
|
console.error('❌ AJAX Fehler:', status, error);
|
||||||
|
});
|
||||||
|
|
||||||
|
$.get("ajax/getRelations.php", {
|
||||||
|
anchorID:AnchorID[0],
|
||||||
|
authType: "Person"
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
$(".new_modal_content").html(data);
|
||||||
|
$("#NewSubjectModal").modal("show");
|
||||||
|
|
||||||
|
// Autocomplete neu initialisieren nach Modal-Öffnung
|
||||||
|
setTimeout(function() {
|
||||||
|
if (typeof searchRelation === 'function') {
|
||||||
|
console.log('🔄 Autocomplete wird neu initialisiert...');
|
||||||
|
searchRelation();
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewrite records after updating a person entry
|
||||||
|
function UpdateEntry(authType)
|
||||||
|
{
|
||||||
|
var AnchorID = $("#ID").val();
|
||||||
|
var term = $("#new_term").val();
|
||||||
|
term = term.trim();
|
||||||
|
var type = 'Person';
|
||||||
|
type = type.trim();
|
||||||
|
var detailtype = '';
|
||||||
|
detailtype = detailtype.trim();
|
||||||
|
var classification = ''
|
||||||
|
classification = classification.trim();
|
||||||
|
var scopenote = $("#new_scopenote").val();
|
||||||
|
scopenote = scopenote.trim();
|
||||||
|
|
||||||
|
if (term.length == 0) {
|
||||||
|
var a = document.getElementById("errorNewSubject");
|
||||||
|
a.classList.add('alert-danger');
|
||||||
|
a.textContent = 'Bitte Name eingeben!';
|
||||||
|
a.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = document.getElementById("errorNewSubject");
|
||||||
|
a.classList.remove('alert-danger');
|
||||||
|
a.textContent = '';
|
||||||
|
a.style.display = 'none';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$.post("ajax/UpdateTerm.php", {
|
||||||
|
AnchorID: AnchorID,
|
||||||
|
authType: "Person",
|
||||||
|
term: term,
|
||||||
|
type: type,
|
||||||
|
detailtype: detailtype,
|
||||||
|
classification: classification,
|
||||||
|
scopenote: scopenote
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
document.getElementById("relation_table").style.display = 'block'
|
||||||
|
document.getElementById("newSubjectSave").style.display = 'none'
|
||||||
|
document.getElementById("newSubjectDismiss").style.display = 'inline'
|
||||||
|
document.getElementById("SubjectModifySave").style.display = 'none'
|
||||||
|
document.getElementById("newRelationSubjectDismiss").style.display = 'none'
|
||||||
|
var e = document.getElementById("errorNewSubject");
|
||||||
|
e.classList.add('alert-success');
|
||||||
|
e.textContent = ' Personenname geändert ';
|
||||||
|
e.style.display = 'block';
|
||||||
|
|
||||||
|
$("#NewSubjectModalHeadline").text("Name ändern");
|
||||||
|
$("#NewSubjectModal").modal("show");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$table.bootstrapTable('refresh')
|
||||||
|
}
|
||||||
|
|
||||||
|
function ShowModalDetails(id) {
|
||||||
|
|
||||||
|
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
|
||||||
|
authType : 'Person',
|
||||||
|
offset : 0,
|
||||||
|
id : id,
|
||||||
|
sort : 'Anchor.Text',
|
||||||
|
limit : 1
|
||||||
|
},
|
||||||
|
function(data, status) {
|
||||||
|
if (status == "success") {
|
||||||
|
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
var row = metadata['rows'];
|
||||||
|
|
||||||
|
if (!row || row.length === 0) {
|
||||||
|
console.error('❌ Keine Daten gefunden für ID:', id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Assign existing values to the modal popup fields
|
||||||
|
$("#display_term").val(row[0].Text);
|
||||||
|
$("#display_id").val(row[0].ID);
|
||||||
|
$("#display_id_show").val(row[0].ID);
|
||||||
|
$("#display_descriptor").val(row[0].Descriptor);
|
||||||
|
$("#display_type").val(row[0].Type);
|
||||||
|
$("#display_scopenote").val(row[0].Scopenote);
|
||||||
|
|
||||||
|
var html = []
|
||||||
|
var txt = ''
|
||||||
|
$.each(row[0].Relations, function (key, value) {
|
||||||
|
txt = txt +'<div class="col-sm-12">';
|
||||||
|
txt = txt +'<div class="col-sm-2"></div>';
|
||||||
|
txt = txt + '<div class="col-sm-3"><input type="text" class="form-control" disabled id="display_relationtype_' +key +'" /></div>';
|
||||||
|
txt = txt + '<div class="col-sm-5"><input type="text" class="form-control" disabled id="display_textrelation_' +key +'" /></div>';
|
||||||
|
txt = txt + '<div class="col-sm-2"><input type="text" class="form-control" disabled id="display_IDRelation_' +key +'" /></div>';
|
||||||
|
txt = txt +'</div>';
|
||||||
|
})
|
||||||
|
$(".modal_content").html(txt);
|
||||||
|
$.each(row[0].Relations, function (key, value) {
|
||||||
|
$("#display_relationtype_" +key).val(value['Relationtype']);
|
||||||
|
$("#display_textrelation_" +key).val(value['TextRelation']);
|
||||||
|
$("#display_IDRelation_" +key).val(value['IDRelation']);
|
||||||
|
})
|
||||||
|
$("#display_relations").val(txt);
|
||||||
|
|
||||||
|
// Modal anzeigen
|
||||||
|
$("#DetailsModal").modal("show");
|
||||||
|
|
||||||
|
// Semantisches Netz erstellen (nach Modal sichtbar)
|
||||||
|
setTimeout(function() {
|
||||||
|
if (typeof createSemanticNetwork === 'function') {
|
||||||
|
createSemanticNetwork(row[0].Text, row[0].ID, row[0].Relations);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function refreshPage()
|
||||||
|
{
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function CopyLink(value)
|
||||||
|
{
|
||||||
|
var tempInput = document.createElement("input");
|
||||||
|
tempInput.value = value;
|
||||||
|
document.body.appendChild(tempInput);
|
||||||
|
tempInput.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(tempInput);
|
||||||
|
}
|
||||||
634
js/PublisherScript.js
Normal file
634
js/PublisherScript.js
Normal file
@ -0,0 +1,634 @@
|
|||||||
|
/**
|
||||||
|
* Created by Roland on 2021/07/01.
|
||||||
|
* Updated for BIBB Layout 2024
|
||||||
|
*/
|
||||||
|
// PUBLISHERS
|
||||||
|
|
||||||
|
// Show blank screen to add new Publisher
|
||||||
|
function newPublishersShow() {
|
||||||
|
|
||||||
|
var b = document.getElementById("newPublisherSave");
|
||||||
|
b.style.display='inline';
|
||||||
|
var c = document.getElementById("newPublisherDismiss");
|
||||||
|
c.style.display='inline';
|
||||||
|
document.getElementById("PublisherModifySave").style.display = 'none';
|
||||||
|
document.getElementById("new_term").disabled = false;
|
||||||
|
document.getElementById("new_type").disabled = false;
|
||||||
|
document.getElementById("new_scopenote").disabled = false;
|
||||||
|
|
||||||
|
// Relationen-Bereich verstecken bei neuem Verlag
|
||||||
|
document.getElementById("new_relation_label").style.display = 'none';
|
||||||
|
document.getElementById("newRelationPublisherDismiss").style.display = 'none';
|
||||||
|
showRelations(0,0,0);
|
||||||
|
var a = document.getElementById("relation_table");
|
||||||
|
if (a != undefined) {
|
||||||
|
document.getElementById("relation_table").style.display = 'none';
|
||||||
|
}
|
||||||
|
var e = document.getElementById("errorNewPublisher");
|
||||||
|
e.style.display = 'none';
|
||||||
|
|
||||||
|
$("#new_term").val("");
|
||||||
|
$("#new_scopenote").val("");
|
||||||
|
|
||||||
|
$("#NewPublisherModal").modal("show");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert new publisher record
|
||||||
|
function CreateNewEntry(type) {
|
||||||
|
// get values
|
||||||
|
|
||||||
|
var term = $("#new_term").val();
|
||||||
|
term = term.trim();
|
||||||
|
var type = $("#new_type").val();
|
||||||
|
type = type.trim();
|
||||||
|
var detailtype = "";
|
||||||
|
var classification = "";
|
||||||
|
var scopenote = $("#new_scopenote").val();
|
||||||
|
scopenote = scopenote.trim();
|
||||||
|
|
||||||
|
if (term.length == 0) {
|
||||||
|
var a = document.getElementById("errorNewPublisher");
|
||||||
|
a.classList.add('alert-danger');
|
||||||
|
a.textContent = 'Bitte Verlag eingeben!';
|
||||||
|
a.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = document.getElementById("errorNewPublisher");
|
||||||
|
a.classList.remove('alert-danger');
|
||||||
|
a.textContent = '';
|
||||||
|
a.style.display = 'none';
|
||||||
|
|
||||||
|
$.post("ajax/newPublisher.php", {
|
||||||
|
term : term,
|
||||||
|
type : type,
|
||||||
|
detailtype : detailtype,
|
||||||
|
classification: classification,
|
||||||
|
scopenote : scopenote
|
||||||
|
}, function (data, status) {
|
||||||
|
|
||||||
|
var dt = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
var e = document.getElementById("errorNewPublisher");
|
||||||
|
|
||||||
|
if (dt['status'] == 200)
|
||||||
|
{
|
||||||
|
var AnchorID = dt["Anchor"];
|
||||||
|
var EntryID = dt["Entry"];
|
||||||
|
var LinkingID = dt["Linking"];
|
||||||
|
|
||||||
|
var b = document.getElementById("newPublisherSave");
|
||||||
|
b.style.display='none';
|
||||||
|
var c = document.getElementById("newPublisherDismiss");
|
||||||
|
c.style.display='none';
|
||||||
|
document.getElementById("new_term").disabled = true;
|
||||||
|
document.getElementById("new_type").disabled = true;
|
||||||
|
document.getElementById("new_scopenote").disabled = true;
|
||||||
|
$table.bootstrapTable('refresh');
|
||||||
|
|
||||||
|
e.classList.remove('alert-danger');
|
||||||
|
e.classList.add('alert-success');
|
||||||
|
e.textContent = 'Neuen Verlag aufgenommen';
|
||||||
|
e.style.display = 'block';
|
||||||
|
|
||||||
|
// ID setzen für Relationen
|
||||||
|
$('#ID').val(AnchorID);
|
||||||
|
|
||||||
|
// Relationen-Bereich anzeigen nach Speichern
|
||||||
|
document.getElementById("new_relation_label").style.display = 'block';
|
||||||
|
document.getElementById("newRelationPublisherDismiss").style.display = 'inline';
|
||||||
|
showRelations(AnchorID,EntryID,LinkingID);
|
||||||
|
} else {
|
||||||
|
e.classList.remove('alert-success');
|
||||||
|
e.classList.add('alert-danger');
|
||||||
|
e.textContent = dt['message'];
|
||||||
|
e.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show relations of a publisher record
|
||||||
|
function showRelations(Anchor,Entry,Linking)
|
||||||
|
{
|
||||||
|
$.get("ajax/getRelations.php", {
|
||||||
|
anchorID:Anchor,
|
||||||
|
authType:"Publisher"
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
$(".new_modal_content").html(data);
|
||||||
|
// Tabelle nur anzeigen wenn Relationen vorhanden
|
||||||
|
var relationTable = document.getElementById("relation_table");
|
||||||
|
if (relationTable) {
|
||||||
|
if (data && data.trim().length > 0) {
|
||||||
|
relationTable.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
relationTable.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$("#NewPublisherModal").modal("show");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// create new relation for a publisher record
|
||||||
|
function CreateNewRelation(AnchorID, relationID, event) {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🚀 CreateNewRelation:', { AnchorID, relationID });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Validierung
|
||||||
|
if (!AnchorID || !relationID) {
|
||||||
|
showError('Fehlende Parameter: AnchorID oder relationID');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var relationTypeElement = document.getElementById("new_relationtype");
|
||||||
|
|
||||||
|
if (!relationTypeElement) {
|
||||||
|
showError('Relationstyp-Element nicht gefunden');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var relationType = relationTypeElement.value;
|
||||||
|
|
||||||
|
if (!relationType || relationType.trim() === '') {
|
||||||
|
showError('Bitte wählen Sie einen Relationstyp aus');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading anzeigen
|
||||||
|
showLoading();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "ajax/writeNewRelation.php",
|
||||||
|
type: "POST",
|
||||||
|
data: {
|
||||||
|
AnchorID: AnchorID,
|
||||||
|
relationType: relationType,
|
||||||
|
relationID: relationID
|
||||||
|
},
|
||||||
|
dataType: "text",
|
||||||
|
timeout: 10000,
|
||||||
|
|
||||||
|
success: function(response) {
|
||||||
|
console.log("✅ Success! Response:", response);
|
||||||
|
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(response);
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
showError('<strong>Serverfehler:</strong><br>' + data.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.success === true) {
|
||||||
|
showSuccess('Neue Relation erfolgreich aufgenommen!');
|
||||||
|
|
||||||
|
if (typeof showRelations === 'function') {
|
||||||
|
setTimeout(function() {
|
||||||
|
showRelations(AnchorID, 0, 0);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForm();
|
||||||
|
} else {
|
||||||
|
showError('Unerwartete Antwort vom Server');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (parseError) {
|
||||||
|
console.log("⚠️ Keine JSON-Antwort, prüfe Plain-Text");
|
||||||
|
|
||||||
|
if (response && response.length > 0 && !isNaN(response.trim())) {
|
||||||
|
showSuccess('Neue Relation erfolgreich aufgenommen! (ID: ' + response.trim() + ')');
|
||||||
|
|
||||||
|
if (typeof showRelations === 'function') {
|
||||||
|
setTimeout(function() {
|
||||||
|
showRelations(AnchorID, 0, 0);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForm();
|
||||||
|
} else {
|
||||||
|
showError('<strong>Unerwartete Antwort:</strong><br>' +
|
||||||
|
'<code>' + response.substring(0, 200) + '</code>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error("❌ AJAX Error:", { status, error, xhr });
|
||||||
|
|
||||||
|
var errorMessage = '<strong>Fehler beim Speichern der Relation</strong><br><br>';
|
||||||
|
|
||||||
|
if (xhr.status === 0) {
|
||||||
|
errorMessage += '❌ Keine Verbindung zum Server möglich<br>';
|
||||||
|
errorMessage += '<small>Prüfen Sie Ihre Internetverbindung</small>';
|
||||||
|
} else if (xhr.status === 404) {
|
||||||
|
errorMessage += '❌ Datei nicht gefunden (404)<br>';
|
||||||
|
errorMessage += '<small>ajax/writeNewRelation.php existiert nicht</small>';
|
||||||
|
} else if (xhr.status === 500) {
|
||||||
|
errorMessage += '❌ Interner Serverfehler (500)<br>';
|
||||||
|
if (xhr.responseText) {
|
||||||
|
errorMessage += '<br><details><summary>Details anzeigen</summary><pre>' +
|
||||||
|
xhr.responseText.substring(0, 500) + '</pre></details>';
|
||||||
|
}
|
||||||
|
} else if (status === 'timeout') {
|
||||||
|
errorMessage += '❌ Zeitüberschreitung<br>';
|
||||||
|
errorMessage += '<small>Der Server hat nicht rechtzeitig geantwortet</small>';
|
||||||
|
} else if (status === 'parsererror') {
|
||||||
|
errorMessage += '❌ Fehler beim Verarbeiten der Server-Antwort<br>';
|
||||||
|
errorMessage += '<small>Die Server-Antwort war ungültig</small>';
|
||||||
|
} else {
|
||||||
|
errorMessage += '❌ ' + status + ' (' + xhr.status + ')';
|
||||||
|
if (error) {
|
||||||
|
errorMessage += '<br><small>' + error + '</small>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showError(errorMessage);
|
||||||
|
},
|
||||||
|
|
||||||
|
complete: function(xhr, status) {
|
||||||
|
console.log("🏁 Request abgeschlossen:", status);
|
||||||
|
if (status !== 'success') {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
hideLoading();
|
||||||
|
console.error("💥 Exception:", e);
|
||||||
|
showError('<strong>JavaScript-Fehler:</strong><br>' + e.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(message) {
|
||||||
|
var errorElement = document.getElementById("errorNewPublisher");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-danger';
|
||||||
|
errorElement.innerHTML = '<i class="fas fa-exclamation-circle me-2"></i>' + message.replace(/\n/g, '<br>');
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
fadeOutAlert();
|
||||||
|
}, 8000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSuccess(message) {
|
||||||
|
var errorElement = document.getElementById("errorNewPublisher");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-success';
|
||||||
|
errorElement.innerHTML = '<i class="fas fa-check-circle me-2"></i>' + message;
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
fadeOutAlert();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showWarning(message) {
|
||||||
|
var errorElement = document.getElementById("errorNewPublisher");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-warning';
|
||||||
|
errorElement.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>' + message;
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
fadeOutAlert();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLoading() {
|
||||||
|
var errorElement = document.getElementById("errorNewPublisher");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-info';
|
||||||
|
errorElement.innerHTML = '<div class="d-flex align-items-center"><div class="spinner-border spinner-border-sm me-3" role="status"><span class="visually-hidden">Laden...</span></div><div>Speichere Relation...</div></div>';
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideLoading() {
|
||||||
|
var errorElement = document.getElementById("errorNewPublisher");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideAlert() {
|
||||||
|
var errorElement = document.getElementById("errorNewPublisher");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fadeOutAlert() {
|
||||||
|
var errorElement = document.getElementById("errorNewPublisher");
|
||||||
|
if (errorElement && errorElement.style.display !== 'none') {
|
||||||
|
$(errorElement).fadeOut(500, function() {
|
||||||
|
errorElement.style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
var relationTextElement = document.getElementById("search_relation");
|
||||||
|
if (relationTextElement) {
|
||||||
|
relationTextElement.value = '';
|
||||||
|
}
|
||||||
|
$('#publisher-anlegen').removeData('record-id');
|
||||||
|
$('#publisher-anlegen').removeData('relation-id');
|
||||||
|
hideAlert();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Delete relation for a publisher
|
||||||
|
function DeleteRelation(AnchorID, LinkingID)
|
||||||
|
{
|
||||||
|
if (!confirm('Möchten Sie diese Relation wirklich löschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.post("ajax/deleteRelation.php", {
|
||||||
|
AnchorID : AnchorID,
|
||||||
|
LinkingID : LinkingID
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
var e = document.getElementById("errorNewPublisher");
|
||||||
|
e.classList.add('alert-success');
|
||||||
|
e.textContent = ' Relation gelöscht';
|
||||||
|
e.style.display = 'block';
|
||||||
|
|
||||||
|
showRelations(AnchorID,0,0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the whole publisher entry
|
||||||
|
function DeleteTerm(AnchorID)
|
||||||
|
{
|
||||||
|
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.post("ajax/deleteTerm.php", {
|
||||||
|
AnchorID: AnchorID[0]
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
|
||||||
|
if (status === "success" && metadata.success === true) {
|
||||||
|
// Tabelle neu laden – Zeile ist serverseitig wirklich gelöscht
|
||||||
|
$table.bootstrapTable('refresh');
|
||||||
|
} else {
|
||||||
|
// Serverseitiger Fehler: Zeile wieder einblenden und Meldung anzeigen
|
||||||
|
$table.bootstrapTable('refresh');
|
||||||
|
alert('Fehler beim Löschen: ' + (metadata.error || 'Unbekannter Fehler'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Modify publisher and its relations
|
||||||
|
function ModifyTerm(AnchorID)
|
||||||
|
{
|
||||||
|
// Name-Feld deaktivieren (darf nicht geändert werden)
|
||||||
|
document.getElementById("new_term").disabled = true;
|
||||||
|
|
||||||
|
// Andere Felder aktivieren
|
||||||
|
document.getElementById("new_type").disabled = false;
|
||||||
|
document.getElementById("new_scopenote").disabled = false;
|
||||||
|
|
||||||
|
$("#ID").val(AnchorID[0]);
|
||||||
|
|
||||||
|
// Relationen-Bereich sofort sichtbar machen bei Bearbeitung!
|
||||||
|
document.getElementById("new_relation_label").style.display = 'block';
|
||||||
|
document.getElementById("relation_table").style.display = 'block';
|
||||||
|
|
||||||
|
// Buttons konfigurieren
|
||||||
|
document.getElementById("newPublisherSave").style.display = 'none';
|
||||||
|
document.getElementById("newPublisherDismiss").style.display = 'inline';
|
||||||
|
document.getElementById("PublisherModifySave").style.display = 'inline';
|
||||||
|
document.getElementById("newRelationPublisherDismiss").style.display = 'none';
|
||||||
|
|
||||||
|
// Alert verstecken
|
||||||
|
document.getElementById("errorNewPublisher").style.display = 'none';
|
||||||
|
|
||||||
|
$("#NewPublisherModalHeadline").text("Verlag ändern");
|
||||||
|
|
||||||
|
$.get("/Thesaurus/ajax/getAuthorityDataRaw.php", {
|
||||||
|
id : AnchorID[0],
|
||||||
|
authType :"Publisher"
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
console.log('📥 getAuthorityDataRaw status:', status);
|
||||||
|
console.log('📥 getAuthorityDataRaw data:', data);
|
||||||
|
if (status == "success")
|
||||||
|
{
|
||||||
|
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
console.log('📥 metadata:', metadata);
|
||||||
|
dt = (metadata['rows'][0]);
|
||||||
|
console.log('📥 dt:', dt);
|
||||||
|
if (dt) {
|
||||||
|
$("#new_term").val(dt.Text);
|
||||||
|
$("#new_id").val(dt.ID);
|
||||||
|
$("#new_descriptor").val(dt.Descriptor);
|
||||||
|
$("#new_type").val(dt.Type);
|
||||||
|
$("#new_scopenote").val(dt.Scopenote);
|
||||||
|
console.log('✅ Felder befüllt mit:', dt.Text);
|
||||||
|
} else {
|
||||||
|
console.error('❌ Keine Daten in rows[0]');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
).fail(function(xhr, status, error) {
|
||||||
|
console.error('❌ AJAX Fehler:', status, error);
|
||||||
|
});
|
||||||
|
|
||||||
|
$.get("ajax/getRelations.php", {
|
||||||
|
anchorID:AnchorID[0],
|
||||||
|
authType: "Publisher"
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
$(".new_modal_content").html(data);
|
||||||
|
$("#NewPublisherModal").modal("show");
|
||||||
|
|
||||||
|
// Autocomplete neu initialisieren nach Modal-Öffnung
|
||||||
|
setTimeout(function() {
|
||||||
|
if (typeof searchRelation === 'function') {
|
||||||
|
console.log('🔄 Autocomplete wird neu initialisiert...');
|
||||||
|
searchRelation();
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewrite records after updating a publisher entry
|
||||||
|
function UpdateEntry(authType)
|
||||||
|
{
|
||||||
|
var AnchorID = $("#ID").val();
|
||||||
|
var term = $("#new_term").val();
|
||||||
|
term = term.trim();
|
||||||
|
var type = $("#new_type").val();
|
||||||
|
type = type.trim();
|
||||||
|
var detailtype = '';
|
||||||
|
var classification = '';
|
||||||
|
var scopenote = $("#new_scopenote").val();
|
||||||
|
scopenote = scopenote.trim();
|
||||||
|
|
||||||
|
if (term.length == 0) {
|
||||||
|
var a = document.getElementById("errorNewPublisher");
|
||||||
|
a.classList.add('alert-danger');
|
||||||
|
a.textContent = 'Bitte Verlag eingeben!';
|
||||||
|
a.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = document.getElementById("errorNewPublisher");
|
||||||
|
a.classList.remove('alert-danger');
|
||||||
|
a.textContent = '';
|
||||||
|
a.style.display = 'none';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$.post("ajax/UpdateTerm.php", {
|
||||||
|
AnchorID: AnchorID,
|
||||||
|
authType: "Publisher",
|
||||||
|
term: term,
|
||||||
|
type: type,
|
||||||
|
detailtype: detailtype,
|
||||||
|
classification: classification,
|
||||||
|
scopenote: scopenote
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
document.getElementById("relation_table").style.display = 'block'
|
||||||
|
document.getElementById("newPublisherSave").style.display = 'none'
|
||||||
|
document.getElementById("newPublisherDismiss").style.display = 'inline'
|
||||||
|
document.getElementById("PublisherModifySave").style.display = 'none'
|
||||||
|
document.getElementById("newRelationPublisherDismiss").style.display = 'none'
|
||||||
|
var e = document.getElementById("errorNewPublisher");
|
||||||
|
e.classList.add('alert-success');
|
||||||
|
e.textContent = ' Verlag geändert ';
|
||||||
|
e.style.display = 'block';
|
||||||
|
|
||||||
|
$("#NewPublisherModalHeadline").text("Verlag ändern");
|
||||||
|
$("#NewPublisherModal").modal("show");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$table.bootstrapTable('refresh')
|
||||||
|
}
|
||||||
|
|
||||||
|
function ShowModalDetails(id) {
|
||||||
|
|
||||||
|
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
|
||||||
|
authType : 'Publisher',
|
||||||
|
offset : 0,
|
||||||
|
id : id,
|
||||||
|
sort : 'Anchor.Text',
|
||||||
|
limit : 1
|
||||||
|
},
|
||||||
|
function(data, status) {
|
||||||
|
if (status == "success") {
|
||||||
|
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
var row = metadata['rows'];
|
||||||
|
|
||||||
|
if (!row || row.length === 0) {
|
||||||
|
console.error('❌ Keine Daten gefunden für ID:', id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Assign existing values to the modal popup fields
|
||||||
|
$("#display_term").val(row[0].Text);
|
||||||
|
$("#display_id").val(row[0].ID);
|
||||||
|
$("#display_id_show").val(row[0].ID);
|
||||||
|
$("#display_descriptor").val(row[0].Descriptor);
|
||||||
|
$("#display_type").val(row[0].Type);
|
||||||
|
$("#display_scopenote").val(row[0].Scopenote);
|
||||||
|
|
||||||
|
var html = []
|
||||||
|
var txt = ''
|
||||||
|
$.each(row[0].Relations, function (key, value) {
|
||||||
|
txt = txt +'<div class="col-sm-12">';
|
||||||
|
txt = txt +'<div class="col-sm-2"></div>';
|
||||||
|
txt = txt + '<div class="col-sm-3"><input type="text" class="form-control" disabled id="display_relationtype_' +key +'" /></div>';
|
||||||
|
txt = txt + '<div class="col-sm-5"><input type="text" class="form-control" disabled id="display_textrelation_' +key +'" /></div>';
|
||||||
|
txt = txt + '<div class="col-sm-2"><input type="text" class="form-control" disabled id="display_IDRelation_' +key +'" /></div>';
|
||||||
|
txt = txt +'</div>';
|
||||||
|
})
|
||||||
|
$(".modal_content").html(txt);
|
||||||
|
$.each(row[0].Relations, function (key, value) {
|
||||||
|
$("#display_relationtype_" +key).val(value['Relationtype']);
|
||||||
|
$("#display_textrelation_" +key).val(value['TextRelation']);
|
||||||
|
$("#display_IDRelation_" +key).val(value['IDRelation']);
|
||||||
|
})
|
||||||
|
$("#display_relations").val(txt);
|
||||||
|
|
||||||
|
// Modal anzeigen
|
||||||
|
$("#DetailsModal").modal("show");
|
||||||
|
|
||||||
|
// Semantisches Netz erstellen (nach Modal sichtbar)
|
||||||
|
setTimeout(function() {
|
||||||
|
if (typeof createSemanticNetwork === 'function') {
|
||||||
|
createSemanticNetwork(row[0].Text, row[0].ID, row[0].Relations);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function refreshPage()
|
||||||
|
{
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function CopyLink(value)
|
||||||
|
{
|
||||||
|
var tempInput = document.createElement("input");
|
||||||
|
var words = value.split('(');
|
||||||
|
tempInput.value = words[0];
|
||||||
|
document.body.appendChild(tempInput);
|
||||||
|
tempInput.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(tempInput);
|
||||||
|
}
|
||||||
882
js/SubjectScript.js
Normal file
882
js/SubjectScript.js
Normal file
@ -0,0 +1,882 @@
|
|||||||
|
/**
|
||||||
|
* Created by Roland on 2021/07/01.
|
||||||
|
*/
|
||||||
|
// SUBJECTS
|
||||||
|
|
||||||
|
// Show blank screen to add new Subject
|
||||||
|
function newSubjectShow() {
|
||||||
|
|
||||||
|
var b = document.getElementById("newSubjectSave");
|
||||||
|
b.style.display='inline';
|
||||||
|
var c = document.getElementById("newSubjectDismiss");
|
||||||
|
c.style.display='inline';
|
||||||
|
document.getElementById("SubjectModifySave").style.display = 'none'
|
||||||
|
document.getElementById("new_term").disabled = false;
|
||||||
|
document.getElementById("new_type").disabled = false;
|
||||||
|
document.getElementById("new_detailtype").disabled = false;
|
||||||
|
document.getElementById("new_classification").disabled = false;
|
||||||
|
document.getElementById("new_scopenote").disabled = false;
|
||||||
|
|
||||||
|
// Relationen-Bereich verstecken bei neuem Schlagwort
|
||||||
|
document.getElementById("new_relation_label").style.display = 'none';
|
||||||
|
document.getElementById("newRelationSubjectDismiss").style.display = 'none';
|
||||||
|
|
||||||
|
// Synonyme-Bereich verstecken bei neuem Schlagwort
|
||||||
|
var synSection = document.getElementById("synonyms_section");
|
||||||
|
if (synSection) {
|
||||||
|
synSection.style.display = 'none';
|
||||||
|
var tbody = document.getElementById("synonyms_body");
|
||||||
|
if (tbody) tbody.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
showRelations(0,0,0);
|
||||||
|
var a = document.getElementById("relation_table");
|
||||||
|
if (a != undefined) {
|
||||||
|
document.getElementById("relation_table").style.display = 'none';
|
||||||
|
}
|
||||||
|
var e = document.getElementById("errorNewSubject");
|
||||||
|
e.style.display = 'none';
|
||||||
|
|
||||||
|
$("#new_term").val("");
|
||||||
|
$("#new_classification").val("");
|
||||||
|
$("#new_scopenote").val("");
|
||||||
|
|
||||||
|
$("#NewSubjectModal").modal("show");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert new subject record
|
||||||
|
function CreateNewEntry(type) {
|
||||||
|
// get values
|
||||||
|
|
||||||
|
var term = $("#new_term").val();
|
||||||
|
term = term.trim();
|
||||||
|
var type = $("#new_type").val();
|
||||||
|
type = type.trim();
|
||||||
|
var detailtype = $("#new_detailtype").val();
|
||||||
|
detailtype = detailtype.trim();
|
||||||
|
var classification = $("#new_classification").val();
|
||||||
|
classification = classification.trim();
|
||||||
|
var scopenote = $("#new_scopenote").val();
|
||||||
|
scopenote = scopenote.trim();
|
||||||
|
|
||||||
|
if (term.length == 0) {
|
||||||
|
var a = document.getElementById("errorNewSubject");
|
||||||
|
a.classList.add('alert-danger');
|
||||||
|
a.textContent = 'Bitte Schlagwort eingeben!';
|
||||||
|
a.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = document.getElementById("errorNewSubject");
|
||||||
|
a.classList.remove('alert-danger');
|
||||||
|
a.textContent = '';
|
||||||
|
a.style.display = 'none';
|
||||||
|
|
||||||
|
$.post("ajax/newSubject.php", {
|
||||||
|
term : term,
|
||||||
|
type : type,
|
||||||
|
detailtype : detailtype,
|
||||||
|
classification: classification,
|
||||||
|
scopenote : scopenote
|
||||||
|
}, function (data, status) {
|
||||||
|
|
||||||
|
var dt = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
var e = document.getElementById("errorNewSubject");
|
||||||
|
|
||||||
|
if (dt['status'] == 200)
|
||||||
|
{
|
||||||
|
var AnchorID = dt["Anchor"];
|
||||||
|
var EntryID = dt["Entry"];
|
||||||
|
var LinkingID = dt["Linking"];
|
||||||
|
|
||||||
|
// alert ("ANCHOR: " +AnchorID);
|
||||||
|
var b = document.getElementById("newSubjectSave");
|
||||||
|
b.style.display='none';
|
||||||
|
var c = document.getElementById("newSubjectDismiss");
|
||||||
|
c.style.display='none';
|
||||||
|
document.getElementById("new_term").disabled = true;
|
||||||
|
document.getElementById("new_type").disabled = true;
|
||||||
|
document.getElementById("new_detailtype").disabled = true;
|
||||||
|
document.getElementById("new_classification").disabled = true;
|
||||||
|
document.getElementById("new_scopenote").disabled = true;
|
||||||
|
$table.bootstrapTable('refresh');
|
||||||
|
|
||||||
|
e.classList.remove('alert-danger');
|
||||||
|
e.classList.add('alert-success');
|
||||||
|
e.textContent = 'Neues Schlagwort aufgenommen';
|
||||||
|
e.style.display = 'block';
|
||||||
|
|
||||||
|
// ID setzen für Relationen
|
||||||
|
$('#ID').val(AnchorID);
|
||||||
|
|
||||||
|
// Relationen-Bereich anzeigen nach Speichern
|
||||||
|
document.getElementById("new_relation_label").style.display = 'block';
|
||||||
|
document.getElementById("newRelationSubjectDismiss").style.display = 'inline';
|
||||||
|
showRelations(AnchorID,EntryID,LinkingID);
|
||||||
|
} else {
|
||||||
|
e.classList.remove('alert-success');
|
||||||
|
e.classList.add('alert-danger');
|
||||||
|
e.textContent = dt['message'];
|
||||||
|
e.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show relations of a subect record
|
||||||
|
function showRelations(Anchor,Entry,Linking)
|
||||||
|
{
|
||||||
|
// alert ("showRelations AnchorID : " + Anchor);
|
||||||
|
$.get("ajax/getRelations.php", {
|
||||||
|
anchorID:Anchor,
|
||||||
|
authType: "Subject"
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
$(".new_modal_content").html(data);
|
||||||
|
// Tabelle nur anzeigen wenn Relationen vorhanden
|
||||||
|
var relationTable = document.getElementById("relation_table");
|
||||||
|
if (relationTable) {
|
||||||
|
if (data && data.trim().length > 0) {
|
||||||
|
relationTable.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
relationTable.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$("#NewSubjectModal").modal("show");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// create new relation for a subject record
|
||||||
|
|
||||||
|
function CreateNewRelation(AnchorID, relationID, event) {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🚀 CreateNewRelation:', { AnchorID, relationID });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Validierung
|
||||||
|
if (!AnchorID || !relationID) {
|
||||||
|
showError('Fehlende Parameter: AnchorID oder relationID');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var relationTypeElement = document.getElementById("new_relationtype");
|
||||||
|
|
||||||
|
if (!relationTypeElement) {
|
||||||
|
showError('Relationstyp-Element nicht gefunden');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var relationType = relationTypeElement.value;
|
||||||
|
|
||||||
|
if (!relationType || relationType.trim() === '') {
|
||||||
|
showError('Bitte wählen Sie einen Relationstyp aus');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading anzeigen
|
||||||
|
showLoading();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "ajax/writeNewRelation.php",
|
||||||
|
type: "POST",
|
||||||
|
data: {
|
||||||
|
AnchorID: AnchorID,
|
||||||
|
relationType: relationType,
|
||||||
|
relationID: relationID
|
||||||
|
},
|
||||||
|
dataType: "text",
|
||||||
|
timeout: 10000,
|
||||||
|
|
||||||
|
success: function(response) {
|
||||||
|
console.log("✅ Success! Response:", response);
|
||||||
|
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(response);
|
||||||
|
|
||||||
|
if (data.error) {
|
||||||
|
showError('<strong>Serverfehler:</strong><br>' + data.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.success === true) {
|
||||||
|
showSuccess('Neue Relation erfolgreich aufgenommen!');
|
||||||
|
|
||||||
|
// Relations neu laden nach kurzer Verzögerung
|
||||||
|
if (typeof showRelations === 'function') {
|
||||||
|
setTimeout(function() {
|
||||||
|
showRelations(AnchorID, 0, 0);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForm();
|
||||||
|
} else {
|
||||||
|
showError('Unerwartete Antwort vom Server');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (parseError) {
|
||||||
|
console.log("⚠️ Keine JSON-Antwort, prüfe Plain-Text");
|
||||||
|
|
||||||
|
// Fallback für alte API (nur ID zurück)
|
||||||
|
if (response && response.length > 0 && !isNaN(response.trim())) {
|
||||||
|
showSuccess('Neue Relation erfolgreich aufgenommen! (ID: ' + response.trim() + ')');
|
||||||
|
|
||||||
|
if (typeof showRelations === 'function') {
|
||||||
|
setTimeout(function() {
|
||||||
|
showRelations(AnchorID, 0, 0);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForm();
|
||||||
|
} else {
|
||||||
|
showError('<strong>Unerwartete Antwort:</strong><br>' +
|
||||||
|
'<code>' + response.substring(0, 200) + '</code>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error("❌ AJAX Error:", { status, error, xhr });
|
||||||
|
|
||||||
|
var errorMessage = '<strong>Fehler beim Speichern der Relation</strong><br><br>';
|
||||||
|
|
||||||
|
if (xhr.status === 0) {
|
||||||
|
errorMessage += '❌ Keine Verbindung zum Server möglich<br>';
|
||||||
|
errorMessage += '<small>Prüfen Sie Ihre Internetverbindung</small>';
|
||||||
|
} else if (xhr.status === 404) {
|
||||||
|
errorMessage += '❌ Datei nicht gefunden (404)<br>';
|
||||||
|
errorMessage += '<small>ajax/writeNewRelation.php existiert nicht</small>';
|
||||||
|
} else if (xhr.status === 500) {
|
||||||
|
errorMessage += '❌ Interner Serverfehler (500)<br>';
|
||||||
|
if (xhr.responseText) {
|
||||||
|
errorMessage += '<br><details><summary>Details anzeigen</summary><pre>' +
|
||||||
|
xhr.responseText.substring(0, 500) + '</pre></details>';
|
||||||
|
}
|
||||||
|
} else if (status === 'timeout') {
|
||||||
|
errorMessage += '❌ Zeitüberschreitung<br>';
|
||||||
|
errorMessage += '<small>Der Server hat nicht rechtzeitig geantwortet</small>';
|
||||||
|
} else if (status === 'parsererror') {
|
||||||
|
errorMessage += '❌ Fehler beim Verarbeiten der Server-Antwort<br>';
|
||||||
|
errorMessage += '<small>Die Server-Antwort war ungültig</small>';
|
||||||
|
} else {
|
||||||
|
errorMessage += '❌ ' + status + ' (' + xhr.status + ')';
|
||||||
|
if (error) {
|
||||||
|
errorMessage += '<br><small>' + error + '</small>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showError(errorMessage);
|
||||||
|
},
|
||||||
|
|
||||||
|
complete: function(xhr, status) {
|
||||||
|
console.log("🏁 Request abgeschlossen:", status);
|
||||||
|
// Loading nur ausblenden wenn kein Erfolg (Success zeigt eigene Meldung)
|
||||||
|
if (status !== 'success') {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
hideLoading();
|
||||||
|
console.error("💥 Exception:", e);
|
||||||
|
showError('<strong>JavaScript-Fehler:</strong><br>' + e.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neue Funktion für Synonyme
|
||||||
|
function CreateNewSynonym(text1, text2, anchorID, event) {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔗 CreateNewSynonym:', { text1, text2 });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Validierung
|
||||||
|
if (!text1 || !text2) {
|
||||||
|
showError('Fehlende Parameter: text1 oder text2');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text1.trim() === text2.trim()) {
|
||||||
|
showError('Ein Begriff kann nicht sein eigenes Synonym sein');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading anzeigen
|
||||||
|
showLoading();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "/Thesaurus/ajax/saveSynonym.php",
|
||||||
|
type: "POST",
|
||||||
|
data: {
|
||||||
|
text1: text1,
|
||||||
|
text2: text2,
|
||||||
|
action: 'add'
|
||||||
|
},
|
||||||
|
dataType: "json",
|
||||||
|
timeout: 10000,
|
||||||
|
|
||||||
|
success: function(response) {
|
||||||
|
console.log("✅ Synonym Success! Response:", response);
|
||||||
|
|
||||||
|
if (response.error) {
|
||||||
|
showError('<strong>Fehler:</strong><br>' + response.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.success === true) {
|
||||||
|
showSuccess('Synonym erfolgreich hinzugefügt!');
|
||||||
|
|
||||||
|
// Synonyme neu laden
|
||||||
|
var termText = $('#new_term').val();
|
||||||
|
if (termText) {
|
||||||
|
loadSynonyms(termText);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relations neu laden nach kurzer Verzögerung
|
||||||
|
if (typeof showRelations === 'function' && anchorID) {
|
||||||
|
setTimeout(function() {
|
||||||
|
showRelations(anchorID, 0, 0);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetForm();
|
||||||
|
} else {
|
||||||
|
showError('Unerwartete Antwort vom Server');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error("❌ AJAX Error:", { status, error, xhr });
|
||||||
|
|
||||||
|
var errorMessage = '<strong>Fehler beim Speichern des Synonyms</strong><br><br>';
|
||||||
|
|
||||||
|
if (xhr.status === 0) {
|
||||||
|
errorMessage += '❌ Keine Verbindung zum Server möglich';
|
||||||
|
} else if (xhr.status === 404) {
|
||||||
|
errorMessage += '❌ Datei nicht gefunden (404)<br>';
|
||||||
|
errorMessage += '<small>ajax/saveSynonym.php existiert nicht</small>';
|
||||||
|
} else if (xhr.status === 500) {
|
||||||
|
errorMessage += '❌ Interner Serverfehler (500)';
|
||||||
|
} else {
|
||||||
|
errorMessage += '❌ ' + status + ' (' + xhr.status + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
showError(errorMessage);
|
||||||
|
},
|
||||||
|
|
||||||
|
complete: function() {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
hideLoading();
|
||||||
|
console.error("💥 Exception:", e);
|
||||||
|
showError('<strong>JavaScript-Fehler:</strong><br>' + e.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synonyme für einen Begriff laden
|
||||||
|
function loadSynonyms(text) {
|
||||||
|
if (!text) return;
|
||||||
|
|
||||||
|
console.log('📋 Lade Synonyme für:', text);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "/Thesaurus/ajax/getSynonyms.php",
|
||||||
|
type: "GET",
|
||||||
|
data: { text: text },
|
||||||
|
dataType: "json",
|
||||||
|
success: function(response) {
|
||||||
|
console.log('✅ Synonyme geladen:', response);
|
||||||
|
|
||||||
|
var section = document.getElementById('synonyms_section');
|
||||||
|
var tbody = document.getElementById('synonyms_body');
|
||||||
|
var noSyn = document.getElementById('no_synonyms');
|
||||||
|
|
||||||
|
if (response.success && response.count > 0) {
|
||||||
|
// Synonyme vorhanden - Bereich anzeigen
|
||||||
|
if (section) section.style.display = 'block';
|
||||||
|
if (tbody) tbody.innerHTML = response.html;
|
||||||
|
if (noSyn) noSyn.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
// Keine Synonyme - Bereich komplett ausblenden
|
||||||
|
if (section) section.style.display = 'none';
|
||||||
|
if (tbody) tbody.innerHTML = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error('❌ Fehler beim Laden der Synonyme:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synonym löschen
|
||||||
|
function DeleteSynonym(text1, text2) {
|
||||||
|
if (!confirm('Möchten Sie das Synonym "' + text2 + '" wirklich entfernen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🗑️ Lösche Synonym:', { text1, text2 });
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "/Thesaurus/ajax/saveSynonym.php",
|
||||||
|
type: "POST",
|
||||||
|
data: {
|
||||||
|
text1: text1,
|
||||||
|
text2: text2,
|
||||||
|
action: 'remove'
|
||||||
|
},
|
||||||
|
dataType: "json",
|
||||||
|
success: function(response) {
|
||||||
|
console.log('✅ Synonym gelöscht:', response);
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
showSuccess('Synonym entfernt!');
|
||||||
|
// Synonyme neu laden
|
||||||
|
loadSynonyms(text1);
|
||||||
|
} else {
|
||||||
|
showError(response.error || 'Fehler beim Löschen');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error('❌ Fehler beim Löschen:', error);
|
||||||
|
showError('Fehler beim Löschen des Synonyms');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(message) {
|
||||||
|
var errorElement = document.getElementById("errorNewSubject");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-danger';
|
||||||
|
errorElement.innerHTML = '<i class="fas fa-exclamation-circle me-2"></i>' + message.replace(/\n/g, '<br>');
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
// Scroll zum Alert
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
||||||
|
// Optional: Auto-Hide nach 8 Sekunden
|
||||||
|
setTimeout(function() {
|
||||||
|
fadeOutAlert();
|
||||||
|
}, 8000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSuccess(message) {
|
||||||
|
var errorElement = document.getElementById("errorNewSubject");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-success';
|
||||||
|
errorElement.innerHTML = '<i class="fas fa-check-circle me-2"></i>' + message;
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
// Scroll zum Alert
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
||||||
|
// Auto-Hide nach 3 Sekunden
|
||||||
|
setTimeout(function() {
|
||||||
|
fadeOutAlert();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showWarning(message) {
|
||||||
|
var errorElement = document.getElementById("errorNewSubject");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-warning';
|
||||||
|
errorElement.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>' + message;
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
// Scroll zum Alert
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
||||||
|
// Auto-Hide nach 5 Sekunden
|
||||||
|
setTimeout(function() {
|
||||||
|
fadeOutAlert();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLoading() {
|
||||||
|
var errorElement = document.getElementById("errorNewSubject");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.className = 'alert alert-info';
|
||||||
|
errorElement.innerHTML = '<div class="d-flex align-items-center"><div class="spinner-border spinner-border-sm me-3" role="status"><span class="visually-hidden">Laden...</span></div><div>Speichere Relation...</div></div>';
|
||||||
|
errorElement.style.display = 'block';
|
||||||
|
|
||||||
|
// Scroll zum Alert
|
||||||
|
errorElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideLoading() {
|
||||||
|
var errorElement = document.getElementById("errorNewSubject");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideAlert() {
|
||||||
|
var errorElement = document.getElementById("errorNewSubject");
|
||||||
|
if (errorElement) {
|
||||||
|
errorElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fadeOutAlert() {
|
||||||
|
var errorElement = document.getElementById("errorNewSubject");
|
||||||
|
if (errorElement && errorElement.style.display !== 'none') {
|
||||||
|
$(errorElement).fadeOut(500, function() {
|
||||||
|
errorElement.style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
var relationTextElement = document.getElementById("search_relation");
|
||||||
|
if (relationTextElement) {
|
||||||
|
relationTextElement.value = '';
|
||||||
|
}
|
||||||
|
// Data-Attribute zurücksetzen
|
||||||
|
$('#subject-anlegen').removeData('record-id');
|
||||||
|
$('#subject-anlegen').removeData('record-text');
|
||||||
|
$('#subject-anlegen').removeData('relation-id');
|
||||||
|
$('#subject-anlegen').removeData('relation-text');
|
||||||
|
hideAlert();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Delete relation for a subjects
|
||||||
|
function DeleteRelation(AnchorID, LinkingID)
|
||||||
|
{
|
||||||
|
if (!confirm('Möchten Sie diese Relation wirklich löschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.post("ajax/deleteRelation.php", {
|
||||||
|
AnchorID : AnchorID,
|
||||||
|
LinkingID : LinkingID
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
var e = document.getElementById("errorNewSubject");
|
||||||
|
e.classList.add('alert-success');
|
||||||
|
e.textContent = ' Relation gelöscht';
|
||||||
|
e.style.display = 'block';
|
||||||
|
|
||||||
|
showRelations(AnchorID,0,0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the whole subject entry
|
||||||
|
|
||||||
|
function DeleteTerm(AnchorID)
|
||||||
|
{
|
||||||
|
if (!confirm('Möchten Sie diesen Eintrag wirklich löschen?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.post("ajax/deleteTerm.php", {
|
||||||
|
AnchorID: AnchorID[0]
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
|
||||||
|
if (status === "success" && metadata.success === true) {
|
||||||
|
// Tabelle neu laden – Zeile ist serverseitig wirklich gelöscht
|
||||||
|
$table.bootstrapTable('refresh');
|
||||||
|
} else {
|
||||||
|
// Serverseitiger Fehler: Zeile wieder einblenden und Meldung anzeigen
|
||||||
|
$table.bootstrapTable('refresh');
|
||||||
|
alert('Fehler beim Löschen: ' + (metadata.error || 'Unbekannter Fehler'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify subject and its relations
|
||||||
|
function ModifyTerm(AnchorID)
|
||||||
|
{
|
||||||
|
// Schlagwort-Feld deaktivieren (darf nicht geändert werden)
|
||||||
|
document.getElementById("new_term").disabled = true;
|
||||||
|
|
||||||
|
// Andere Felder aktivieren
|
||||||
|
document.getElementById("new_type").disabled = false;
|
||||||
|
document.getElementById("new_detailtype").disabled = false;
|
||||||
|
document.getElementById("new_classification").disabled = false;
|
||||||
|
document.getElementById("new_scopenote").disabled = false;
|
||||||
|
|
||||||
|
$("#ID").val(AnchorID[0]);
|
||||||
|
|
||||||
|
// Relationen-Bereich sofort sichtbar machen bei Bearbeitung!
|
||||||
|
document.getElementById("new_relation_label").style.display = 'block';
|
||||||
|
document.getElementById("relation_table").style.display = 'block';
|
||||||
|
|
||||||
|
// Synonyme-Bereich sichtbar machen
|
||||||
|
var synSection = document.getElementById("synonyms_section");
|
||||||
|
if (synSection) synSection.style.display = 'block';
|
||||||
|
|
||||||
|
// Buttons konfigurieren
|
||||||
|
document.getElementById("newSubjectSave").style.display = 'none';
|
||||||
|
document.getElementById("newSubjectDismiss").style.display = 'inline';
|
||||||
|
document.getElementById("SubjectModifySave").style.display = 'inline';
|
||||||
|
document.getElementById("newRelationSubjectDismiss").style.display = 'none';
|
||||||
|
|
||||||
|
// Alert verstecken
|
||||||
|
document.getElementById("errorNewSubject").style.display = 'none';
|
||||||
|
|
||||||
|
$("#NewSubjectModalHeadline").text("Schlagwort ändern");
|
||||||
|
|
||||||
|
$.get("/Thesaurus/ajax/getAuthorityDataRaw.php", {
|
||||||
|
id : AnchorID[0]
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
console.log('📥 getAuthorityDataRaw status:', status);
|
||||||
|
console.log('📥 getAuthorityDataRaw data:', data);
|
||||||
|
if (status == "success")
|
||||||
|
{
|
||||||
|
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
console.log('📥 metadata:', metadata);
|
||||||
|
dt = (metadata['rows'][0]);
|
||||||
|
console.log('📥 dt:', dt);
|
||||||
|
if (dt) {
|
||||||
|
$("#new_term").val(dt.Text);
|
||||||
|
$("#new_id").val(dt.ID);
|
||||||
|
$("#new_detailtype").val(dt.DetailType);
|
||||||
|
$("#new_classification").val(dt.Classification);
|
||||||
|
$("#new_descriptor").val(dt.Descriptor);
|
||||||
|
$("#new_type").val(dt.Type);
|
||||||
|
$("#new_scopenote").val(dt.Scopenote);
|
||||||
|
console.log('✅ Felder befüllt mit:', dt.Text);
|
||||||
|
|
||||||
|
// Synonyme laden
|
||||||
|
if (typeof loadSynonyms === 'function') {
|
||||||
|
loadSynonyms(dt.Text);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('❌ Keine Daten in rows[0]');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
).fail(function(xhr, status, error) {
|
||||||
|
console.error('❌ AJAX Fehler:', status, error);
|
||||||
|
});
|
||||||
|
|
||||||
|
$.get("ajax/getRelations.php", {
|
||||||
|
anchorID:AnchorID[0],
|
||||||
|
authType: "Subject"
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
$(".new_modal_content").html(data);
|
||||||
|
$("#NewSubjectModal").modal("show");
|
||||||
|
|
||||||
|
// Autocomplete neu initialisieren nach Modal-Öffnung
|
||||||
|
setTimeout(function() {
|
||||||
|
if (typeof searchRelation === 'function') {
|
||||||
|
console.log('🔄 Autocomplete wird neu initialisiert...');
|
||||||
|
searchRelation();
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewrite records after updating a subject entry
|
||||||
|
function UpdateEntry(authType)
|
||||||
|
{
|
||||||
|
var AnchorID = $("#ID").val();
|
||||||
|
var term = $("#new_term").val();
|
||||||
|
term = term.trim();
|
||||||
|
var type = $("#new_type").val();
|
||||||
|
type = type.trim();
|
||||||
|
var detailtype = $("#new_detailtype").val();
|
||||||
|
detailtype = detailtype.trim();
|
||||||
|
var classification = $("#new_classification").val();
|
||||||
|
classification = classification.trim();
|
||||||
|
var scopenote = $("#new_scopenote").val();
|
||||||
|
scopenote = scopenote.trim();
|
||||||
|
|
||||||
|
if (term.length == 0) {
|
||||||
|
var a = document.getElementById("errorNewSubject");
|
||||||
|
a.classList.add('alert-danger');
|
||||||
|
a.textContent = 'Bitte Schlagwort eingeben!';
|
||||||
|
a.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = document.getElementById("errorNewSubject");
|
||||||
|
a.classList.remove('alert-danger');
|
||||||
|
a.textContent = '';
|
||||||
|
a.style.display = 'none';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
$.post("ajax/UpdateTerm.php", {
|
||||||
|
AnchorID: AnchorID,
|
||||||
|
authType: authType,
|
||||||
|
term: term,
|
||||||
|
type: type,
|
||||||
|
detailtype: detailtype,
|
||||||
|
classification: classification,
|
||||||
|
scopenote: scopenote
|
||||||
|
},
|
||||||
|
function (data, status) {
|
||||||
|
if (status=="success")
|
||||||
|
{
|
||||||
|
document.getElementById("relation_table").style.display = 'block'
|
||||||
|
document.getElementById("newSubjectSave").style.display = 'none'
|
||||||
|
document.getElementById("newSubjectDismiss").style.display = 'inline'
|
||||||
|
document.getElementById("SubjectModifySave").style.display = 'none'
|
||||||
|
document.getElementById("newRelationSubjectDismiss").style.display = 'none'
|
||||||
|
var e = document.getElementById("errorNewSubject");
|
||||||
|
e.classList.add('alert-success');
|
||||||
|
e.textContent = ' Schlagwort geändert ';
|
||||||
|
e.style.display = 'block';
|
||||||
|
|
||||||
|
$("#NewSubjectModalHeadline").text("Schlagwort ändern");
|
||||||
|
$("#NewSubjectModal").modal("show");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$table.bootstrapTable('refresh')
|
||||||
|
}
|
||||||
|
|
||||||
|
function ShowModalDetails(id) {
|
||||||
|
|
||||||
|
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
|
||||||
|
authType : 'Subject',
|
||||||
|
offset : 0,
|
||||||
|
id : id,
|
||||||
|
sort : 'Anchor.Text',
|
||||||
|
limit : 1
|
||||||
|
},
|
||||||
|
function(data, status) {
|
||||||
|
if (status == "success") {
|
||||||
|
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
var row = metadata['rows'];
|
||||||
|
|
||||||
|
if (!row || row.length === 0) {
|
||||||
|
console.error('❌ Keine Daten gefunden für ID:', id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Assign existing values to the modal popup fields
|
||||||
|
$("#display_term").val(row[0].Text);
|
||||||
|
$("#display_id").val(row[0].ID);
|
||||||
|
$("#display_id_show").val(row[0].ID);
|
||||||
|
$("#display_detailtype").val(row[0].Detailtype);
|
||||||
|
$("#display_classification").val(row[0].Classification);
|
||||||
|
$("#display_descriptor").val(row[0].Descriptor);
|
||||||
|
$("#display_type").val(row[0].Type);
|
||||||
|
$("#display_scopenote").val(row[0].Scopenote);
|
||||||
|
$("#display_synonyms").val(row[0].Synonyms || '');
|
||||||
|
|
||||||
|
// Alte tabellarische Darstellung (versteckt)
|
||||||
|
var html = []
|
||||||
|
var txt = ''
|
||||||
|
$.each(row[0].Relations, function (key, value) {
|
||||||
|
txt = txt +'<div class="col-sm-12">';
|
||||||
|
txt = txt +'<div class="col-sm-2"></div>';
|
||||||
|
txt = txt + '<div class="col-sm-3"><input type="text" class="form-control" disabled id="display_relationtype_' +key +'" /></div>';
|
||||||
|
txt = txt + '<div class="col-sm-5"><input type="text" class="form-control" disabled id="display_textrelation_' +key +'" /></div>';
|
||||||
|
txt = txt + '<div class="col-sm-2"><input type="text" class="form-control" disabled id="display_IDRelation_' +key +'" /></div>';
|
||||||
|
txt = txt +'</div>';
|
||||||
|
})
|
||||||
|
$(".modal_content").html(txt);
|
||||||
|
$.each(row[0].Relations, function (key, value) {
|
||||||
|
$("#display_relationtype_" +key).val(value['Relationtype']);
|
||||||
|
$("#display_textrelation_" +key).val(value['TextRelation']);
|
||||||
|
$("#display_IDRelation_" +key).val(value['IDRelation']);
|
||||||
|
})
|
||||||
|
$("#display_relations").val(txt);
|
||||||
|
|
||||||
|
// Modal anzeigen
|
||||||
|
$("#DetailsModal").modal("show");
|
||||||
|
|
||||||
|
// Semantisches Netz erstellen (nach Modal sichtbar)
|
||||||
|
setTimeout(function() {
|
||||||
|
if (typeof createSemanticNetwork === 'function') {
|
||||||
|
var synonyms = row[0].Synonyms || '';
|
||||||
|
|
||||||
|
// Falls keine Synonyme im Response, versuche sie separat zu laden
|
||||||
|
if (!synonyms || synonyms.trim() === '') {
|
||||||
|
console.log('📝 Keine Synonyme im Response, lade separat...');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "/Thesaurus/ajax/getSynonyms.php",
|
||||||
|
type: "GET",
|
||||||
|
data: { text: row[0].Text },
|
||||||
|
dataType: "json",
|
||||||
|
async: false, // Synchron um auf Ergebnis zu warten
|
||||||
|
success: function(synResponse) {
|
||||||
|
if (synResponse.success && synResponse.synonyms && synResponse.synonyms.length > 0) {
|
||||||
|
synonyms = synResponse.synonyms.map(function(s) { return s.text; }).join(', ');
|
||||||
|
console.log('✅ Synonyme separat geladen:', synonyms);
|
||||||
|
// Auch das Textfeld aktualisieren
|
||||||
|
$("#display_synonyms").val(synonyms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createSemanticNetwork(row[0].Text, row[0].ID, row[0].Relations, synonyms);
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ createSemanticNetwork Funktion nicht gefunden');
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function CopyLink(value)
|
||||||
|
{
|
||||||
|
var tempInput = document.createElement("input");
|
||||||
|
tempInput.value = value;
|
||||||
|
document.body.appendChild(tempInput);
|
||||||
|
tempInput.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(tempInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function refreshPage()
|
||||||
|
{
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
305
migrate_tsdata.sh
Executable file
305
migrate_tsdata.sh
Executable file
@ -0,0 +1,305 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# TSData Migration Script
|
||||||
|
# Migriert Daten von DSpace (MySQL 5.5) nach Tools (MariaDB 10.5)
|
||||||
|
#
|
||||||
|
# Verwendung:
|
||||||
|
# 1. Skript auf DSpace ausführen: ./migrate_tsdata.sh export
|
||||||
|
# 2. SQL-Datei auf Tools kopieren
|
||||||
|
# 3. Skript auf Tools ausführen: ./migrate_tsdata.sh import /pfad/zur/datei.sql
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Konfiguration
|
||||||
|
DB_NAME="TSData"
|
||||||
|
DB_USER="root"
|
||||||
|
BACKUP_DIR="/tmp/tsdata_migration"
|
||||||
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||||
|
EXPORT_FILE="${BACKUP_DIR}/TSData_export_${TIMESTAMP}.sql"
|
||||||
|
|
||||||
|
# Farben für Output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo -e "${GREEN}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verzeichnis erstellen
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# EXPORT (auf DSpace ausführen)
|
||||||
|
#######################################
|
||||||
|
do_export() {
|
||||||
|
log_info "Starte Export auf DSpace..."
|
||||||
|
|
||||||
|
# Prüfen ob Datenbank existiert
|
||||||
|
if ! mysql -u "$DB_USER" -p -e "USE $DB_NAME" 2>/dev/null; then
|
||||||
|
log_error "Datenbank $DB_NAME nicht gefunden!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Erstelle Daten-Export (ohne Struktur)..."
|
||||||
|
|
||||||
|
# Export mit vollständigen INSERT-Statements
|
||||||
|
mysqldump -u "$DB_USER" -p "$DB_NAME" \
|
||||||
|
--no-create-info \
|
||||||
|
--complete-insert \
|
||||||
|
--skip-triggers \
|
||||||
|
--single-transaction \
|
||||||
|
--quick \
|
||||||
|
--lock-tables=false \
|
||||||
|
--set-charset \
|
||||||
|
--default-character-set=utf8 \
|
||||||
|
> "$EXPORT_FILE"
|
||||||
|
|
||||||
|
# Wrapper für SQL-Mode hinzufügen
|
||||||
|
TEMP_FILE="${EXPORT_FILE}.tmp"
|
||||||
|
cat > "$TEMP_FILE" << 'HEADER'
|
||||||
|
-- TSData Migration Export
|
||||||
|
-- Erstellt am: TIMESTAMP_PLACEHOLDER
|
||||||
|
-- Quelle: DSpace Server
|
||||||
|
|
||||||
|
SET @OLD_SQL_MODE=@@SQL_MODE;
|
||||||
|
SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO,ALLOW_INVALID_DATES';
|
||||||
|
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS;
|
||||||
|
SET FOREIGN_KEY_CHECKS=0;
|
||||||
|
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS;
|
||||||
|
SET UNIQUE_CHECKS=0;
|
||||||
|
|
||||||
|
-- DateModified '0000-00-00' Werte korrigieren (für MariaDB Strict Mode)
|
||||||
|
-- Dies geschieht nach dem Import
|
||||||
|
|
||||||
|
HEADER
|
||||||
|
|
||||||
|
sed -i "s/TIMESTAMP_PLACEHOLDER/$(date)/" "$TEMP_FILE"
|
||||||
|
cat "$EXPORT_FILE" >> "$TEMP_FILE"
|
||||||
|
|
||||||
|
cat >> "$TEMP_FILE" << 'FOOTER'
|
||||||
|
|
||||||
|
SET SQL_MODE=@OLD_SQL_MODE;
|
||||||
|
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
|
||||||
|
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
|
||||||
|
|
||||||
|
-- Export Ende
|
||||||
|
FOOTER
|
||||||
|
|
||||||
|
mv "$TEMP_FILE" "$EXPORT_FILE"
|
||||||
|
|
||||||
|
# Statistiken anzeigen
|
||||||
|
log_info "Export abgeschlossen: $EXPORT_FILE"
|
||||||
|
log_info "Dateigröße: $(du -h "$EXPORT_FILE" | cut -f1)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log_info "Datensätze pro Tabelle:"
|
||||||
|
mysql -u "$DB_USER" -p -N -e "
|
||||||
|
SELECT 'Anchor', COUNT(*) FROM $DB_NAME.Anchor
|
||||||
|
UNION ALL SELECT 'Entry', COUNT(*) FROM $DB_NAME.Entry
|
||||||
|
UNION ALL SELECT 'EntryNew', COUNT(*) FROM $DB_NAME.EntryNew
|
||||||
|
UNION ALL SELECT 'IDSynonym', COUNT(*) FROM $DB_NAME.IDSynonym
|
||||||
|
UNION ALL SELECT 'Linking', COUNT(*) FROM $DB_NAME.Linking
|
||||||
|
UNION ALL SELECT 'Synonyms', COUNT(*) FROM $DB_NAME.Synonyms
|
||||||
|
UNION ALL SELECT 'Treeview', COUNT(*) FROM $DB_NAME.Treeview;
|
||||||
|
" 2>/dev/null | column -t
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log_info "Nächster Schritt: Datei auf Tools-Server kopieren:"
|
||||||
|
echo " scp $EXPORT_FILE user@tools.bibb.de:/tmp/"
|
||||||
|
}
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# IMPORT (auf Tools ausführen)
|
||||||
|
#######################################
|
||||||
|
do_import() {
|
||||||
|
IMPORT_FILE="$1"
|
||||||
|
|
||||||
|
if [ -z "$IMPORT_FILE" ]; then
|
||||||
|
log_error "Keine Import-Datei angegeben!"
|
||||||
|
echo "Verwendung: $0 import /pfad/zur/export_datei.sql"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$IMPORT_FILE" ]; then
|
||||||
|
log_error "Datei nicht gefunden: $IMPORT_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Starte Import auf Tools..."
|
||||||
|
|
||||||
|
# Backup der bestehenden Daten erstellen
|
||||||
|
BACKUP_FILE="${BACKUP_DIR}/TSData_backup_tools_${TIMESTAMP}.sql"
|
||||||
|
log_info "Erstelle Backup der bestehenden Daten: $BACKUP_FILE"
|
||||||
|
|
||||||
|
mysqldump -u "$DB_USER" -p "$DB_NAME" \
|
||||||
|
--single-transaction \
|
||||||
|
--quick \
|
||||||
|
> "$BACKUP_FILE"
|
||||||
|
|
||||||
|
log_info "Backup erstellt: $(du -h "$BACKUP_FILE" | cut -f1)"
|
||||||
|
|
||||||
|
# Bestätigung anfordern
|
||||||
|
echo ""
|
||||||
|
log_warn "ACHTUNG: Alle bestehenden Daten in $DB_NAME werden gelöscht!"
|
||||||
|
read -p "Fortfahren? (ja/nein): " CONFIRM
|
||||||
|
|
||||||
|
if [ "$CONFIRM" != "ja" ]; then
|
||||||
|
log_info "Abgebrochen."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Tabellen leeren
|
||||||
|
log_info "Leere bestehende Tabellen..."
|
||||||
|
mysql -u "$DB_USER" -p "$DB_NAME" << 'EOF'
|
||||||
|
SET FOREIGN_KEY_CHECKS=0;
|
||||||
|
TRUNCATE TABLE Synonyms;
|
||||||
|
TRUNCATE TABLE Linking;
|
||||||
|
TRUNCATE TABLE EntryNew;
|
||||||
|
TRUNCATE TABLE Entry;
|
||||||
|
TRUNCATE TABLE Anchor;
|
||||||
|
TRUNCATE TABLE IDSynonym;
|
||||||
|
TRUNCATE TABLE Treeview;
|
||||||
|
SET FOREIGN_KEY_CHECKS=1;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Daten importieren
|
||||||
|
log_info "Importiere Daten aus $IMPORT_FILE..."
|
||||||
|
mysql -u "$DB_USER" -p "$DB_NAME" < "$IMPORT_FILE"
|
||||||
|
|
||||||
|
# DateModified '0000-00-00' Werte korrigieren
|
||||||
|
log_info "Korrigiere ungültige Datumswerte..."
|
||||||
|
mysql -u "$DB_USER" -p "$DB_NAME" << 'EOF'
|
||||||
|
UPDATE Entry
|
||||||
|
SET DateModified = DateCreated
|
||||||
|
WHERE DateModified = '0000-00-00 00:00:00'
|
||||||
|
OR DateModified IS NULL;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Verifizierung
|
||||||
|
echo ""
|
||||||
|
log_info "Import abgeschlossen. Verifizierung:"
|
||||||
|
mysql -u "$DB_USER" -p -N -e "
|
||||||
|
SELECT 'Anchor', COUNT(*) FROM $DB_NAME.Anchor
|
||||||
|
UNION ALL SELECT 'Entry', COUNT(*) FROM $DB_NAME.Entry
|
||||||
|
UNION ALL SELECT 'EntryNew', COUNT(*) FROM $DB_NAME.EntryNew
|
||||||
|
UNION ALL SELECT 'IDSynonym', COUNT(*) FROM $DB_NAME.IDSynonym
|
||||||
|
UNION ALL SELECT 'Linking', COUNT(*) FROM $DB_NAME.Linking
|
||||||
|
UNION ALL SELECT 'Synonyms', COUNT(*) FROM $DB_NAME.Synonyms
|
||||||
|
UNION ALL SELECT 'Treeview', COUNT(*) FROM $DB_NAME.Treeview;
|
||||||
|
" 2>/dev/null | column -t
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
log_info "Migration erfolgreich abgeschlossen!"
|
||||||
|
log_info "Backup liegt unter: $BACKUP_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# VERIFY (Datenintegrität prüfen)
|
||||||
|
#######################################
|
||||||
|
do_verify() {
|
||||||
|
log_info "Prüfe Datenintegrität..."
|
||||||
|
|
||||||
|
mysql -u "$DB_USER" -p "$DB_NAME" << 'EOF'
|
||||||
|
-- Prüfe ob alle Linking-Einträge gültige Anchor-IDs haben
|
||||||
|
SELECT 'Linking -> Anchor (fehlend)' AS Check_Type, COUNT(*) AS Anzahl
|
||||||
|
FROM Linking l
|
||||||
|
LEFT JOIN Anchor a ON l.IDAnchor = a.ID
|
||||||
|
WHERE a.ID IS NULL
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
-- Prüfe ob alle Linking-Einträge gültige Entry-IDs haben
|
||||||
|
SELECT 'Linking -> Entry (fehlend)', COUNT(*)
|
||||||
|
FROM Linking l
|
||||||
|
LEFT JOIN Entry e ON l.IDEntry = e.ID
|
||||||
|
WHERE e.ID IS NULL
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
-- Prüfe auf ungültige Datumswerte in Entry
|
||||||
|
SELECT 'Entry mit ungültigem DateModified', COUNT(*)
|
||||||
|
FROM Entry
|
||||||
|
WHERE DateModified = '0000-00-00 00:00:00';
|
||||||
|
EOF
|
||||||
|
|
||||||
|
log_info "Integritätsprüfung abgeschlossen."
|
||||||
|
}
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# ROLLBACK (Backup wiederherstellen)
|
||||||
|
#######################################
|
||||||
|
do_rollback() {
|
||||||
|
BACKUP_FILE="$1"
|
||||||
|
|
||||||
|
if [ -z "$BACKUP_FILE" ]; then
|
||||||
|
log_info "Verfügbare Backups:"
|
||||||
|
ls -la ${BACKUP_DIR}/TSData_backup_tools_*.sql 2>/dev/null || echo "Keine Backups gefunden."
|
||||||
|
echo ""
|
||||||
|
echo "Verwendung: $0 rollback /pfad/zum/backup.sql"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$BACKUP_FILE" ]; then
|
||||||
|
log_error "Backup-Datei nicht gefunden: $BACKUP_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_warn "Stelle Backup wieder her: $BACKUP_FILE"
|
||||||
|
read -p "Fortfahren? (ja/nein): " CONFIRM
|
||||||
|
|
||||||
|
if [ "$CONFIRM" != "ja" ]; then
|
||||||
|
log_info "Abgebrochen."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mysql -u "$DB_USER" -p "$DB_NAME" < "$BACKUP_FILE"
|
||||||
|
log_info "Rollback abgeschlossen."
|
||||||
|
}
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# MAIN
|
||||||
|
#######################################
|
||||||
|
case "$1" in
|
||||||
|
export)
|
||||||
|
do_export
|
||||||
|
;;
|
||||||
|
import)
|
||||||
|
do_import "$2"
|
||||||
|
;;
|
||||||
|
verify)
|
||||||
|
do_verify
|
||||||
|
;;
|
||||||
|
rollback)
|
||||||
|
do_rollback "$2"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "TSData Migration Script"
|
||||||
|
echo ""
|
||||||
|
echo "Verwendung: $0 {export|import|verify|rollback} [optionen]"
|
||||||
|
echo ""
|
||||||
|
echo "Befehle:"
|
||||||
|
echo " export Daten aus DSpace exportieren"
|
||||||
|
echo " import <datei> Daten in Tools importieren"
|
||||||
|
echo " verify Datenintegrität prüfen"
|
||||||
|
echo " rollback <backup> Backup wiederherstellen"
|
||||||
|
echo ""
|
||||||
|
echo "Workflow:"
|
||||||
|
echo " 1. Auf DSpace: ./migrate_tsdata.sh export"
|
||||||
|
echo " 2. Kopieren: scp /tmp/tsdata_migration/TSData_export_*.sql user@tools:/tmp/"
|
||||||
|
echo " 3. Auf Tools: ./migrate_tsdata.sh import /tmp/TSData_export_*.sql"
|
||||||
|
echo " 4. Prüfen: ./migrate_tsdata.sh verify"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
263
setup-libs.sh
Executable file
263
setup-libs.sh
Executable file
@ -0,0 +1,263 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# setup-libs.sh - Richtet lokale JavaScript/CSS Libraries ein
|
||||||
|
#
|
||||||
|
# Verwendung:
|
||||||
|
# chmod +x setup-libs.sh
|
||||||
|
# sudo ./setup-libs.sh
|
||||||
|
#
|
||||||
|
# Struktur:
|
||||||
|
# /var/www/html/libs/
|
||||||
|
# ├── bootstrap/
|
||||||
|
# │ ├── 5.3.0/
|
||||||
|
# │ └── current -> 5.3.0
|
||||||
|
# ├── jquery/
|
||||||
|
# │ ├── 3.6.0/
|
||||||
|
# │ └── current -> 3.6.0
|
||||||
|
# ...
|
||||||
|
#
|
||||||
|
# Nutzung in HTML:
|
||||||
|
# <link href="/libs/bootstrap/current/css/bootstrap.min.css">
|
||||||
|
# <script src="/libs/jquery/current/jquery.min.js">
|
||||||
|
#
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Konfiguration
|
||||||
|
LIBS_BASE="/var/www/html/tools/libs"
|
||||||
|
WGET_OPTS="-q --show-progress"
|
||||||
|
|
||||||
|
# Farben für Ausgabe
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
echo -e "${BLUE}========================================${NC}"
|
||||||
|
echo -e "${BLUE} Lokale Libraries Setup${NC}"
|
||||||
|
echo -e "${BLUE}========================================${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Prüfe Root-Rechte
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo -e "${YELLOW}Hinweis: Ohne Root-Rechte wird nach $HOME/libs installiert${NC}"
|
||||||
|
LIBS_BASE="$HOME/libs"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}Zielverzeichnis: ${LIBS_BASE}${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Basisverzeichnis erstellen
|
||||||
|
mkdir -p "$LIBS_BASE"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 1. Bootstrap 5.3.0
|
||||||
|
# ============================================
|
||||||
|
echo -e "${BLUE}[1/7] Bootstrap 5.3.0${NC}"
|
||||||
|
BOOTSTRAP_VERSION="5.3.0"
|
||||||
|
BOOTSTRAP_DIR="$LIBS_BASE/bootstrap/$BOOTSTRAP_VERSION"
|
||||||
|
mkdir -p "$BOOTSTRAP_DIR/css" "$BOOTSTRAP_DIR/js"
|
||||||
|
|
||||||
|
# CSS
|
||||||
|
if [ ! -f "$BOOTSTRAP_DIR/css/bootstrap.min.css" ]; then
|
||||||
|
wget $WGET_OPTS -O "$BOOTSTRAP_DIR/css/bootstrap.min.css" \
|
||||||
|
"https://cdn.jsdelivr.net/npm/bootstrap@${BOOTSTRAP_VERSION}/dist/css/bootstrap.min.css"
|
||||||
|
wget $WGET_OPTS -O "$BOOTSTRAP_DIR/css/bootstrap.min.css.map" \
|
||||||
|
"https://cdn.jsdelivr.net/npm/bootstrap@${BOOTSTRAP_VERSION}/dist/css/bootstrap.min.css.map" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# JS
|
||||||
|
if [ ! -f "$BOOTSTRAP_DIR/js/bootstrap.bundle.min.js" ]; then
|
||||||
|
wget $WGET_OPTS -O "$BOOTSTRAP_DIR/js/bootstrap.bundle.min.js" \
|
||||||
|
"https://cdn.jsdelivr.net/npm/bootstrap@${BOOTSTRAP_VERSION}/dist/js/bootstrap.bundle.min.js"
|
||||||
|
wget $WGET_OPTS -O "$BOOTSTRAP_DIR/js/bootstrap.bundle.min.js.map" \
|
||||||
|
"https://cdn.jsdelivr.net/npm/bootstrap@${BOOTSTRAP_VERSION}/dist/js/bootstrap.bundle.min.js.map" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Symlink
|
||||||
|
ln -sfn "$BOOTSTRAP_VERSION" "$LIBS_BASE/bootstrap/current"
|
||||||
|
echo -e "${GREEN} ✓ Bootstrap $BOOTSTRAP_VERSION installiert${NC}"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 2. jQuery 3.6.0
|
||||||
|
# ============================================
|
||||||
|
echo -e "${BLUE}[2/7] jQuery 3.6.0${NC}"
|
||||||
|
JQUERY_VERSION="3.6.0"
|
||||||
|
JQUERY_DIR="$LIBS_BASE/jquery/$JQUERY_VERSION"
|
||||||
|
mkdir -p "$JQUERY_DIR"
|
||||||
|
|
||||||
|
if [ ! -f "$JQUERY_DIR/jquery.min.js" ]; then
|
||||||
|
wget $WGET_OPTS -O "$JQUERY_DIR/jquery.min.js" \
|
||||||
|
"https://code.jquery.com/jquery-${JQUERY_VERSION}.min.js"
|
||||||
|
wget $WGET_OPTS -O "$JQUERY_DIR/jquery.min.map" \
|
||||||
|
"https://code.jquery.com/jquery-${JQUERY_VERSION}.min.map" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
ln -sfn "$JQUERY_VERSION" "$LIBS_BASE/jquery/current"
|
||||||
|
echo -e "${GREEN} ✓ jQuery $JQUERY_VERSION installiert${NC}"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 3. Bootstrap Table 1.21.0
|
||||||
|
# ============================================
|
||||||
|
echo -e "${BLUE}[3/7] Bootstrap Table 1.21.0${NC}"
|
||||||
|
BSTABLE_VERSION="1.21.0"
|
||||||
|
BSTABLE_DIR="$LIBS_BASE/bootstrap-table/$BSTABLE_VERSION"
|
||||||
|
mkdir -p "$BSTABLE_DIR/css" "$BSTABLE_DIR/js" "$BSTABLE_DIR/locale"
|
||||||
|
|
||||||
|
if [ ! -f "$BSTABLE_DIR/css/bootstrap-table.min.css" ]; then
|
||||||
|
wget $WGET_OPTS -O "$BSTABLE_DIR/css/bootstrap-table.min.css" \
|
||||||
|
"https://unpkg.com/bootstrap-table@${BSTABLE_VERSION}/dist/bootstrap-table.min.css"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$BSTABLE_DIR/js/bootstrap-table.min.js" ]; then
|
||||||
|
wget $WGET_OPTS -O "$BSTABLE_DIR/js/bootstrap-table.min.js" \
|
||||||
|
"https://unpkg.com/bootstrap-table@${BSTABLE_VERSION}/dist/bootstrap-table.min.js"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$BSTABLE_DIR/locale/bootstrap-table-de-DE.min.js" ]; then
|
||||||
|
wget $WGET_OPTS -O "$BSTABLE_DIR/locale/bootstrap-table-de-DE.min.js" \
|
||||||
|
"https://unpkg.com/bootstrap-table@${BSTABLE_VERSION}/dist/locale/bootstrap-table-de-DE.min.js"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ln -sfn "$BSTABLE_VERSION" "$LIBS_BASE/bootstrap-table/current"
|
||||||
|
echo -e "${GREEN} ✓ Bootstrap Table $BSTABLE_VERSION installiert${NC}"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 4. Font Awesome 6.4.0
|
||||||
|
# ============================================
|
||||||
|
echo -e "${BLUE}[4/7] Font Awesome 6.4.0${NC}"
|
||||||
|
FA_VERSION="6.4.0"
|
||||||
|
FA_DIR="$LIBS_BASE/fontawesome/$FA_VERSION"
|
||||||
|
mkdir -p "$FA_DIR"
|
||||||
|
|
||||||
|
if [ ! -f "$FA_DIR/css/all.min.css" ]; then
|
||||||
|
# Font Awesome als ZIP herunterladen und entpacken
|
||||||
|
TEMP_DIR=$(mktemp -d)
|
||||||
|
wget $WGET_OPTS -O "$TEMP_DIR/fontawesome.zip" \
|
||||||
|
"https://use.fontawesome.com/releases/v${FA_VERSION}/fontawesome-free-${FA_VERSION}-web.zip"
|
||||||
|
unzip -q "$TEMP_DIR/fontawesome.zip" -d "$TEMP_DIR"
|
||||||
|
cp -r "$TEMP_DIR/fontawesome-free-${FA_VERSION}-web/css" "$FA_DIR/"
|
||||||
|
cp -r "$TEMP_DIR/fontawesome-free-${FA_VERSION}-web/webfonts" "$FA_DIR/"
|
||||||
|
rm -rf "$TEMP_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ln -sfn "$FA_VERSION" "$LIBS_BASE/fontawesome/current"
|
||||||
|
echo -e "${GREEN} ✓ Font Awesome $FA_VERSION installiert${NC}"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 5. vis.js Network 9.1.2
|
||||||
|
# ============================================
|
||||||
|
echo -e "${BLUE}[5/7] vis.js Network 9.1.2${NC}"
|
||||||
|
VIS_VERSION="9.1.2"
|
||||||
|
VIS_DIR="$LIBS_BASE/vis-network/$VIS_VERSION"
|
||||||
|
mkdir -p "$VIS_DIR"
|
||||||
|
|
||||||
|
if [ ! -f "$VIS_DIR/vis-network.min.js" ]; then
|
||||||
|
wget $WGET_OPTS -O "$VIS_DIR/vis-network.min.js" \
|
||||||
|
"https://unpkg.com/vis-network@${VIS_VERSION}/standalone/umd/vis-network.min.js"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ln -sfn "$VIS_VERSION" "$LIBS_BASE/vis-network/current"
|
||||||
|
echo -e "${GREEN} ✓ vis.js Network $VIS_VERSION installiert${NC}"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 6. jsTree 3.3.16
|
||||||
|
# ============================================
|
||||||
|
echo -e "${BLUE}[6/7] jsTree 3.3.16${NC}"
|
||||||
|
JSTREE_VERSION="3.3.16"
|
||||||
|
JSTREE_DIR="$LIBS_BASE/jstree/$JSTREE_VERSION"
|
||||||
|
mkdir -p "$JSTREE_DIR/themes/default"
|
||||||
|
|
||||||
|
if [ ! -f "$JSTREE_DIR/jstree.min.js" ]; then
|
||||||
|
wget $WGET_OPTS -O "$JSTREE_DIR/jstree.min.js" \
|
||||||
|
"https://cdnjs.cloudflare.com/ajax/libs/jstree/${JSTREE_VERSION}/jstree.min.js"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$JSTREE_DIR/themes/default/style.min.css" ]; then
|
||||||
|
wget $WGET_OPTS -O "$JSTREE_DIR/themes/default/style.min.css" \
|
||||||
|
"https://cdnjs.cloudflare.com/ajax/libs/jstree/${JSTREE_VERSION}/themes/default/style.min.css"
|
||||||
|
|
||||||
|
# Theme-Bilder
|
||||||
|
wget $WGET_OPTS -O "$JSTREE_DIR/themes/default/32px.png" \
|
||||||
|
"https://cdnjs.cloudflare.com/ajax/libs/jstree/${JSTREE_VERSION}/themes/default/32px.png" 2>/dev/null || true
|
||||||
|
wget $WGET_OPTS -O "$JSTREE_DIR/themes/default/40px.png" \
|
||||||
|
"https://cdnjs.cloudflare.com/ajax/libs/jstree/${JSTREE_VERSION}/themes/default/40px.png" 2>/dev/null || true
|
||||||
|
wget $WGET_OPTS -O "$JSTREE_DIR/themes/default/throbber.gif" \
|
||||||
|
"https://cdnjs.cloudflare.com/ajax/libs/jstree/${JSTREE_VERSION}/themes/default/throbber.gif" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
ln -sfn "$JSTREE_VERSION" "$LIBS_BASE/jstree/current"
|
||||||
|
echo -e "${GREEN} ✓ jsTree $JSTREE_VERSION installiert${NC}"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 7. Bootstrap Icons 1.11.0 (optional)
|
||||||
|
# ============================================
|
||||||
|
echo -e "${BLUE}[7/8] Bootstrap Icons 1.11.0${NC}"
|
||||||
|
BSI_VERSION="1.11.0"
|
||||||
|
BSI_DIR="$LIBS_BASE/bootstrap-icons/$BSI_VERSION"
|
||||||
|
mkdir -p "$BSI_DIR"
|
||||||
|
|
||||||
|
if [ ! -f "$BSI_DIR/bootstrap-icons.min.css" ]; then
|
||||||
|
wget $WGET_OPTS -O "$BSI_DIR/bootstrap-icons.min.css" \
|
||||||
|
"https://cdn.jsdelivr.net/npm/bootstrap-icons@${BSI_VERSION}/font/bootstrap-icons.min.css"
|
||||||
|
|
||||||
|
# Fonts-Verzeichnis
|
||||||
|
mkdir -p "$BSI_DIR/fonts"
|
||||||
|
wget $WGET_OPTS -O "$BSI_DIR/fonts/bootstrap-icons.woff" \
|
||||||
|
"https://cdn.jsdelivr.net/npm/bootstrap-icons@${BSI_VERSION}/font/fonts/bootstrap-icons.woff" 2>/dev/null || true
|
||||||
|
wget $WGET_OPTS -O "$BSI_DIR/fonts/bootstrap-icons.woff2" \
|
||||||
|
"https://cdn.jsdelivr.net/npm/bootstrap-icons@${BSI_VERSION}/font/fonts/bootstrap-icons.woff2" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
ln -sfn "$BSI_VERSION" "$LIBS_BASE/bootstrap-icons/current"
|
||||||
|
echo -e "${GREEN} ✓ Bootstrap Icons $BSI_VERSION installiert${NC}"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 8. Bootstrap 3 Typeahead 4.0.2
|
||||||
|
# ============================================
|
||||||
|
echo -e "${BLUE}[8/8] Bootstrap 3 Typeahead 4.0.2${NC}"
|
||||||
|
TYPEAHEAD_VERSION="4.0.2"
|
||||||
|
TYPEAHEAD_DIR="$LIBS_BASE/bootstrap3-typeahead/$TYPEAHEAD_VERSION"
|
||||||
|
mkdir -p "$TYPEAHEAD_DIR"
|
||||||
|
|
||||||
|
if [ ! -f "$TYPEAHEAD_DIR/bootstrap3-typeahead.min.js" ]; then
|
||||||
|
wget $WGET_OPTS -O "$TYPEAHEAD_DIR/bootstrap3-typeahead.min.js" \
|
||||||
|
"https://cdnjs.cloudflare.com/ajax/libs/bootstrap-3-typeahead/${TYPEAHEAD_VERSION}/bootstrap3-typeahead.min.js"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ln -sfn "$TYPEAHEAD_VERSION" "$LIBS_BASE/bootstrap3-typeahead/current"
|
||||||
|
echo -e "${GREEN} ✓ Bootstrap 3 Typeahead $TYPEAHEAD_VERSION installiert${NC}"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Berechtigungen setzen
|
||||||
|
# ============================================
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}Setze Berechtigungen...${NC}"
|
||||||
|
if [ "$EUID" -eq 0 ]; then
|
||||||
|
chown -R www-data:www-data "$LIBS_BASE"
|
||||||
|
fi
|
||||||
|
chmod -R 755 "$LIBS_BASE"
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# Übersicht anzeigen
|
||||||
|
# ============================================
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}========================================${NC}"
|
||||||
|
echo -e "${GREEN} Installation abgeschlossen!${NC}"
|
||||||
|
echo -e "${GREEN}========================================${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Struktur:"
|
||||||
|
echo ""
|
||||||
|
find "$LIBS_BASE" -maxdepth 3 -type d | head -30
|
||||||
|
echo ""
|
||||||
|
echo "Symlinks:"
|
||||||
|
ls -la "$LIBS_BASE"/*/current 2>/dev/null || true
|
||||||
|
echo ""
|
||||||
|
echo -e "${YELLOW}Hinweis: Passen Sie die Header.html entsprechend an.${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Beispiel-Pfade:"
|
||||||
|
echo " /libs/bootstrap/current/css/bootstrap.min.css"
|
||||||
|
echo " /libs/jquery/current/jquery.min.js"
|
||||||
|
echo " /libs/fontawesome/current/css/all.min.css"
|
||||||
|
echo ""
|
||||||
840
styles.css
Normal file
840
styles.css
Normal file
@ -0,0 +1,840 @@
|
|||||||
|
/* styles.css - BIBB Thesaurus (ORCID zu DSpace Layout) */
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Variablen & Reset
|
||||||
|
=========================== */
|
||||||
|
:root {
|
||||||
|
--primary-color: #1a3a5c;
|
||||||
|
--primary-light: #2a5a8c;
|
||||||
|
--primary-dark: #0f2840;
|
||||||
|
--secondary-color: #006699;
|
||||||
|
--accent-color: #00a3cc;
|
||||||
|
--success-color: #28a745;
|
||||||
|
--warning-color: #ffc107;
|
||||||
|
--danger-color: #dc3545;
|
||||||
|
--info-color: #17a2b8;
|
||||||
|
|
||||||
|
--text-primary: #333;
|
||||||
|
--text-secondary: #666;
|
||||||
|
--text-muted: #999;
|
||||||
|
|
||||||
|
--bg-gradient: linear-gradient(135deg, #e8eef5 0%, #f0f4f8 100%);
|
||||||
|
--bg-light: #f8f9fa;
|
||||||
|
--bg-lighter: #fafbfc;
|
||||||
|
--border-color: #e0e0e0;
|
||||||
|
--border-light: #f0f0f0;
|
||||||
|
|
||||||
|
--shadow-sm: 0 2px 4px rgba(0,0,0,0.08);
|
||||||
|
--shadow-md: 0 4px 12px rgba(0,0,0,0.1);
|
||||||
|
--shadow-lg: 0 8px 24px rgba(0,0,0,0.12);
|
||||||
|
|
||||||
|
--radius-sm: 4px;
|
||||||
|
--radius-md: 8px;
|
||||||
|
--radius-lg: 12px;
|
||||||
|
|
||||||
|
--transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--text-primary);
|
||||||
|
background: var(--bg-gradient);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Layout Container
|
||||||
|
=========================== */
|
||||||
|
.page-wrapper {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 40px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Header & Navigation
|
||||||
|
=========================== */
|
||||||
|
.site-header {
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
padding: 0;
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header .container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-logo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-decoration: none;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-title {
|
||||||
|
font-size: 1.4em;
|
||||||
|
font-weight: 700;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-subtitle {
|
||||||
|
font-size: 0.85em;
|
||||||
|
opacity: 0.8;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav ul {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: var(--transition);
|
||||||
|
padding: 10px 18px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
display: block;
|
||||||
|
font-size: 0.95em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav a:hover,
|
||||||
|
.main-nav a.active {
|
||||||
|
background: rgba(255,255,255,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Cards
|
||||||
|
=========================== */
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
padding: 35px 40px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 1.6em;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-description {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 1em;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Nav Cards (Feature Cards)
|
||||||
|
=========================== */
|
||||||
|
.nav-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||||
|
gap: 25px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 25px 30px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-left: 4px solid var(--border-color);
|
||||||
|
transition: var(--transition);
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-card:hover {
|
||||||
|
border-left-color: var(--primary-color);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-card-icon {
|
||||||
|
font-size: 2.5em;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-card h3 {
|
||||||
|
font-size: 1.15em;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-card p {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Section Headers
|
||||||
|
=========================== */
|
||||||
|
.section-title {
|
||||||
|
font-size: 1.3em;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 30px 0 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Project Items / List Items
|
||||||
|
=========================== */
|
||||||
|
.project-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-item {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 20px 25px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-left: 4px solid var(--primary-color);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-item:hover {
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-info h4 {
|
||||||
|
font-size: 1.1em;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin: 0 0 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-meta {
|
||||||
|
font-size: 0.85em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Buttons
|
||||||
|
=========================== */
|
||||||
|
.btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: var(--primary-light);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--secondary-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background: var(--success-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success:hover {
|
||||||
|
background: #218838;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background: var(--danger-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
background: #c82333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-info {
|
||||||
|
background: var(--info-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-info:hover {
|
||||||
|
background: #138496;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline {
|
||||||
|
background: transparent;
|
||||||
|
border: 2px solid var(--primary-color);
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline:hover {
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
padding: 6px 14px;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-lg {
|
||||||
|
padding: 14px 28px;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Tables - Bootstrap Table Anpassungen
|
||||||
|
=========================== */
|
||||||
|
.fixed-table-container {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-table-toolbar {
|
||||||
|
background: var(--bg-light);
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-table-toolbar .search input {
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
padding: 10px 15px;
|
||||||
|
font-size: 0.95em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-table-toolbar .search input:focus {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 3px rgba(26,58,92,0.1);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bootstrap-table .table {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bootstrap-table .table thead th {
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
border: none;
|
||||||
|
padding: 14px 12px;
|
||||||
|
font-size: 0.95em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bootstrap-table .table tbody tr {
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bootstrap-table .table tbody tr:hover {
|
||||||
|
background: rgba(26,58,92,0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bootstrap-table .table tbody td {
|
||||||
|
padding: 14px 12px;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-color: var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Suchfeld doppelt so breit */
|
||||||
|
.bootstrap-table .search input.form-control {
|
||||||
|
width: 400px; /* Standard ist ~200px, Wert nach Bedarf anpassen */
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-table-pagination {
|
||||||
|
padding: 15px 20px;
|
||||||
|
background: var(--bg-light);
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-item.active .page-link {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-link {
|
||||||
|
color: var(--primary-color);
|
||||||
|
padding: 8px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-link:hover {
|
||||||
|
color: var(--primary-dark);
|
||||||
|
background: var(--bg-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Action Buttons in Tabelle */
|
||||||
|
.table-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-actions .btn {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-actions .btn:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Forms
|
||||||
|
=========================== */
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 15px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: 1em;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 3px rgba(26,58,92,0.1);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:disabled {
|
||||||
|
background: var(--bg-light);
|
||||||
|
cursor: not-allowed;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 15px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: 1em;
|
||||||
|
background: white;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select:focus {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 3px rgba(26,58,92,0.1);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.form-control {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Modals
|
||||||
|
=========================== */
|
||||||
|
.modal-content {
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||||
|
padding: 18px 25px;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header .modal-title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.15em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header .btn-close {
|
||||||
|
filter: brightness(0) invert(1);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header .btn-close:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
padding: 18px 25px;
|
||||||
|
border-top: 1px solid var(--border-light);
|
||||||
|
background: var(--bg-lighter);
|
||||||
|
border-radius: 0 0 var(--radius-lg) var(--radius-lg);
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Alerts
|
||||||
|
=========================== */
|
||||||
|
.alert {
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert i {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
background: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-danger {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning {
|
||||||
|
background: #fff3cd;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info {
|
||||||
|
background: #d1ecf1;
|
||||||
|
color: #0c5460;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Autocomplete Dropdown
|
||||||
|
=========================== */
|
||||||
|
.autocomplete-dropdown {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 9999;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-dropdown .dropdown-item {
|
||||||
|
padding: 12px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--text-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
transition: var(--transition);
|
||||||
|
border-bottom: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-dropdown .dropdown-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-dropdown .dropdown-item:hover,
|
||||||
|
.autocomplete-dropdown .dropdown-item.active {
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Relations Table
|
||||||
|
=========================== */
|
||||||
|
.relations-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relations-table th {
|
||||||
|
background: var(--bg-light);
|
||||||
|
padding: 12px 15px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom: 2px solid var(--border-color);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.relations-table td {
|
||||||
|
padding: 12px 15px;
|
||||||
|
border-bottom: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.relations-table tr:hover {
|
||||||
|
background: rgba(26,58,92,0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Stats Grid
|
||||||
|
=========================== */
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
background: var(--bg-light);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
text-align: center;
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number {
|
||||||
|
font-size: 2.2em;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-color);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Footer
|
||||||
|
=========================== */
|
||||||
|
.site-footer {
|
||||||
|
background: var(--primary-dark);
|
||||||
|
color: white;
|
||||||
|
padding: 20px 0;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer .container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
gap: 25px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links a {
|
||||||
|
color: rgba(255,255,255,0.7);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: var(--transition);
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links a:hover {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-copyright {
|
||||||
|
color: rgba(255,255,255,0.6);
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Utilities
|
||||||
|
=========================== */
|
||||||
|
.text-center { text-align: center; }
|
||||||
|
.text-right { text-align: right; }
|
||||||
|
.text-muted { color: var(--text-muted); }
|
||||||
|
|
||||||
|
.mt-10 { margin-top: 10px; }
|
||||||
|
.mt-20 { margin-top: 20px; }
|
||||||
|
.mt-30 { margin-top: 30px; }
|
||||||
|
|
||||||
|
.mb-10 { margin-bottom: 10px; }
|
||||||
|
.mb-20 { margin-bottom: 20px; }
|
||||||
|
.mb-30 { margin-bottom: 30px; }
|
||||||
|
|
||||||
|
.d-none { display: none; }
|
||||||
|
.d-block { display: block; }
|
||||||
|
.d-flex { display: flex; }
|
||||||
|
|
||||||
|
.justify-between { justify-content: space-between; }
|
||||||
|
.align-center { align-items: center; }
|
||||||
|
|
||||||
|
.gap-10 { gap: 10px; }
|
||||||
|
.gap-20 { gap: 20px; }
|
||||||
|
|
||||||
|
.w-100 { width: 100%; }
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Responsive
|
||||||
|
=========================== */
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.site-header .container {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav ul {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.site-footer .container {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-item {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-actions {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-actions .btn {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.card-title {
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Print Styles
|
||||||
|
=========================== */
|
||||||
|
@media print {
|
||||||
|
.site-header,
|
||||||
|
.site-footer,
|
||||||
|
.btn,
|
||||||
|
.table-actions {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
box-shadow: none;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
55
templates/ClassificationDetailsModal.tpl
Normal file
55
templates/ClassificationDetailsModal.tpl
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<!-- Modal - Klassifikation Details (Read-Only) -->
|
||||||
|
<div class="modal fade" id="DetailsModal" tabindex="-1" aria-labelledby="DetailsModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg" style="max-width: 1024px;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="DetailsModalHeadline">
|
||||||
|
<i class="fas fa-folder-tree"></i> Klassifikation - Details
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
|
||||||
|
<!-- Formular (Read-Only) -->
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="display_notation">Notation</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<input type="text" id="display_notation" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="display_term">Bezeichnung</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" id="display_term" class="form-control" disabled />
|
||||||
|
<input type="hidden" id="display_id" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="display_descriptor">Deskriptor</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" id="display_descriptor" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label" for="display_scopenote">Anmerkung</label>
|
||||||
|
<textarea class="form-control" id="display_scopenote" rows="3" disabled></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Relationen-Bereich -->
|
||||||
|
<hr>
|
||||||
|
<h5 class="mb-3"><i class="fas fa-link"></i> Relationen</h5>
|
||||||
|
<div class="modal_content"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times"></i> schliessen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
1
templates/ClassificationsModal.tpl
Normal file
1
templates/ClassificationsModal.tpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!-- ClassificationsModal - Platzhalter für zukünftige Erweiterungen -->
|
||||||
317
templates/CorporateDetailsModal.tpl
Normal file
317
templates/CorporateDetailsModal.tpl
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
<!-- Modal - Körperschaft Details mit semantischem Netz -->
|
||||||
|
<div class="modal fade" id="DetailsModal" tabindex="-1" aria-labelledby="DetailsModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-xl" style="max-width: 1200px;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="DetailsModalHeadline">
|
||||||
|
<i class="fas fa-building"></i> Detailanzeige Körperschaft
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<!-- Linke Spalte: Stammdaten -->
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="detail-info-panel">
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label fw-bold">Name</label>
|
||||||
|
<input type="text" id="display_term" class="form-control" disabled />
|
||||||
|
<input type="hidden" id="display_id" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label fw-bold">Deskriptor</label>
|
||||||
|
<input type="text" id="display_descriptor" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label fw-bold">Type</label>
|
||||||
|
<input type="text" id="display_type" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label fw-bold">ID</label>
|
||||||
|
<input type="text" id="display_id_show" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label fw-bold">Scopenote</label>
|
||||||
|
<textarea class="form-control" id="display_scopenote" rows="4" disabled></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Rechte Spalte: Semantisches Netz -->
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="network-panel">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<h6 class="mb-0"><i class="fas fa-project-diagram me-2"></i>Semantisches Netz</h6>
|
||||||
|
<div class="network-toolbar">
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="networkFit()" title="Ansicht anpassen">
|
||||||
|
<i class="fas fa-expand"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomIn()" title="Vergrößern">
|
||||||
|
<i class="fas fa-search-plus"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomOut()" title="Verkleinern">
|
||||||
|
<i class="fas fa-search-minus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="network-container"></div>
|
||||||
|
<div class="network-legend">
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #1a3a5c;"></span> Zentrale Körperschaft</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #28a745;"></span> Früher verwendet</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #dc3545;"></span> Später verwendet</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #fd7e14;"></span> Alternativer Name</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #6c757d;"></span> USE/USEDFOR</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal_content" style="display: none;"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times"></i> schliessen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#network-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 450px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-panel {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-info-panel {
|
||||||
|
background: var(--bg-light);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-toolbar {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-toolbar .btn {
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-legend {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid var(--border-light);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-dot {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#DetailsModal .modal-body {
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script src="/libs/vis-network/current/vis-network.min.js"></script>
|
||||||
|
<script>if (typeof vis === "undefined") { document.write('<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"><\/script>'); }</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var network = null;
|
||||||
|
var networkData = null;
|
||||||
|
|
||||||
|
function createSemanticNetwork(centralTerm, centralId, relations) {
|
||||||
|
console.log('🕸️ Erstelle semantisches Netz für Körperschaft:', centralTerm);
|
||||||
|
|
||||||
|
var nodes = [];
|
||||||
|
var edges = [];
|
||||||
|
|
||||||
|
nodes.push({
|
||||||
|
id: centralId,
|
||||||
|
label: wrapLabel(centralTerm, 20),
|
||||||
|
shape: 'ellipse',
|
||||||
|
color: {
|
||||||
|
background: '#1a3a5c',
|
||||||
|
border: '#0d1f30',
|
||||||
|
highlight: { background: '#2a5a8c', border: '#1a3a5c' },
|
||||||
|
hover: { background: '#2a5a8c', border: '#1a3a5c' }
|
||||||
|
},
|
||||||
|
font: { color: '#ffffff', size: 14, face: 'Arial', bold: true },
|
||||||
|
size: 35,
|
||||||
|
borderWidth: 3,
|
||||||
|
shadow: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Farben für Körperschafts-Relationstypen
|
||||||
|
var relationColors = {
|
||||||
|
'USEDEARLIER': { node: '#28a745', edge: '#28a745', label: 'Früher verwendet' },
|
||||||
|
'USEDLATER': { node: '#dc3545', edge: '#dc3545', label: 'Später verwendet' },
|
||||||
|
'ALTERNATIVENAME': { node: '#fd7e14', edge: '#fd7e14', label: 'Alternativer Name' },
|
||||||
|
'USE': { node: '#6c757d', edge: '#6c757d', label: 'Benutze' },
|
||||||
|
'USEDFOR': { node: '#6c757d', edge: '#6c757d', label: 'Benutzt für' }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (relations && relations.length > 0) {
|
||||||
|
relations.forEach(function(rel, index) {
|
||||||
|
var relType = rel.Relationtype || 'USE';
|
||||||
|
var relColor = relationColors[relType] || { node: '#17a2b8', edge: '#17a2b8' };
|
||||||
|
var relId = rel.IDRelation || ('rel_' + index);
|
||||||
|
var relText = rel.TextRelation || 'Unbekannt';
|
||||||
|
|
||||||
|
nodes.push({
|
||||||
|
id: relId,
|
||||||
|
label: wrapLabel(relText, 18),
|
||||||
|
shape: 'ellipse',
|
||||||
|
color: {
|
||||||
|
background: relColor.node,
|
||||||
|
border: shadeColor(relColor.node, -20),
|
||||||
|
highlight: { background: shadeColor(relColor.node, 20), border: relColor.node },
|
||||||
|
hover: { background: shadeColor(relColor.node, 20), border: relColor.node }
|
||||||
|
},
|
||||||
|
font: { color: '#ffffff', size: 12, face: 'Arial' },
|
||||||
|
size: 25,
|
||||||
|
borderWidth: 2,
|
||||||
|
shadow: true,
|
||||||
|
title: relType + ': ' + relText + ' (ID: ' + relId + ')'
|
||||||
|
});
|
||||||
|
|
||||||
|
var arrowDirection = {};
|
||||||
|
if (relType === 'USEDEARLIER') {
|
||||||
|
arrowDirection = { from: { enabled: true, scaleFactor: 1 } };
|
||||||
|
} else if (relType === 'USEDLATER') {
|
||||||
|
arrowDirection = { to: { enabled: true, scaleFactor: 1 } };
|
||||||
|
}
|
||||||
|
|
||||||
|
edges.push({
|
||||||
|
from: centralId,
|
||||||
|
to: relId,
|
||||||
|
label: relType,
|
||||||
|
color: { color: relColor.edge, highlight: relColor.edge, hover: relColor.edge },
|
||||||
|
width: 2,
|
||||||
|
arrows: arrowDirection,
|
||||||
|
font: { size: 10, color: '#666', strokeWidth: 3, strokeColor: '#ffffff' },
|
||||||
|
smooth: { type: 'curvedCW', roundness: 0.2 }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var container = document.getElementById('network-container');
|
||||||
|
|
||||||
|
networkData = {
|
||||||
|
nodes: new vis.DataSet(nodes),
|
||||||
|
edges: new vis.DataSet(edges)
|
||||||
|
};
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
nodes: { shape: 'ellipse', scaling: { min: 20, max: 40 } },
|
||||||
|
edges: { smooth: { type: 'curvedCW', roundness: 0.2 } },
|
||||||
|
physics: {
|
||||||
|
enabled: true,
|
||||||
|
barnesHut: {
|
||||||
|
gravitationalConstant: -3000,
|
||||||
|
centralGravity: 0.3,
|
||||||
|
springLength: 150,
|
||||||
|
springConstant: 0.04,
|
||||||
|
damping: 0.09
|
||||||
|
},
|
||||||
|
stabilization: { enabled: true, iterations: 200, updateInterval: 25 }
|
||||||
|
},
|
||||||
|
interaction: { hover: true, tooltipDelay: 200, zoomView: true, dragView: true },
|
||||||
|
layout: { improvedLayout: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
network = new vis.Network(container, networkData, options);
|
||||||
|
|
||||||
|
network.on('click', function(params) {
|
||||||
|
if (params.nodes.length > 0) {
|
||||||
|
var clickedNodeId = params.nodes[0];
|
||||||
|
if (clickedNodeId != centralId) {
|
||||||
|
ShowModalDetails(clickedNodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
network.once('stabilizationIterationsDone', function() {
|
||||||
|
network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapLabel(text, maxLength) {
|
||||||
|
if (!text) return '';
|
||||||
|
if (text.length <= maxLength) return text;
|
||||||
|
var words = text.split(' ');
|
||||||
|
var lines = [];
|
||||||
|
var currentLine = '';
|
||||||
|
words.forEach(function(word) {
|
||||||
|
if ((currentLine + ' ' + word).trim().length <= maxLength) {
|
||||||
|
currentLine = (currentLine + ' ' + word).trim();
|
||||||
|
} else {
|
||||||
|
if (currentLine) lines.push(currentLine);
|
||||||
|
currentLine = word;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (currentLine) lines.push(currentLine);
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function shadeColor(color, percent) {
|
||||||
|
var R = parseInt(color.substring(1,3), 16);
|
||||||
|
var G = parseInt(color.substring(3,5), 16);
|
||||||
|
var B = parseInt(color.substring(5,7), 16);
|
||||||
|
R = parseInt(R * (100 + percent) / 100);
|
||||||
|
G = parseInt(G * (100 + percent) / 100);
|
||||||
|
B = parseInt(B * (100 + percent) / 100);
|
||||||
|
R = (R < 255) ? R : 255;
|
||||||
|
G = (G < 255) ? G : 255;
|
||||||
|
B = (B < 255) ? B : 255;
|
||||||
|
var RR = ((R.toString(16).length == 1) ? "0" + R.toString(16) : R.toString(16));
|
||||||
|
var GG = ((G.toString(16).length == 1) ? "0" + G.toString(16) : G.toString(16));
|
||||||
|
var BB = ((B.toString(16).length == 1) ? "0" + B.toString(16) : B.toString(16));
|
||||||
|
return "#" + RR + GG + BB;
|
||||||
|
}
|
||||||
|
|
||||||
|
function networkFit() {
|
||||||
|
if (network) network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||||||
|
}
|
||||||
|
|
||||||
|
function networkZoomIn() {
|
||||||
|
if (network) network.moveTo({ scale: network.getScale() * 1.3, animation: { duration: 300 } });
|
||||||
|
}
|
||||||
|
|
||||||
|
function networkZoomOut() {
|
||||||
|
if (network) network.moveTo({ scale: network.getScale() / 1.3, animation: { duration: 300 } });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
1
templates/CorporatesModal.tpl
Normal file
1
templates/CorporatesModal.tpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!-- CorporatesModal - Platzhalter für zukünftige Erweiterungen -->
|
||||||
16
templates/Footer.html
Normal file
16
templates/Footer.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<!-- Site Footer -->
|
||||||
|
<footer class="site-footer">
|
||||||
|
<div class="container">
|
||||||
|
<div class="footer-copyright">
|
||||||
|
© <?php echo date('Y'); ?> Bundesinstitut für Berufsbildung (BIBB)
|
||||||
|
</div>
|
||||||
|
<ul class="footer-links">
|
||||||
|
<li><a href="https://www.bibb.de/de/impressum_8945.php" target="_blank">Impressum</a></li>
|
||||||
|
<li><a href="https://www.bibb.de/de/datenschutzerklaerung_81271.php" target="_blank">Datenschutz</a></li>
|
||||||
|
<li><a href="https://www.bibb.de/de/kontakt_3922.php" target="_blank">Kontakt</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
709
templates/Header.html
Normal file
709
templates/Header.html
Normal file
@ -0,0 +1,709 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>BIBB Thesaurus-Verwaltung</title>
|
||||||
|
|
||||||
|
<!-- ============================================
|
||||||
|
LOKALE LIBRARIES MIT CDN-FALLBACK
|
||||||
|
|
||||||
|
Pfade verweisen auf /libs/[library]/current/
|
||||||
|
Falls lokal nicht verfügbar, wird CDN geladen.
|
||||||
|
|
||||||
|
Setup: ./setup-libs.sh ausführen
|
||||||
|
============================================ -->
|
||||||
|
|
||||||
|
<!-- Bootstrap 5 CSS (lokal mit Fallback) -->
|
||||||
|
<link href="/libs/bootstrap/current/css/bootstrap.min.css" rel="stylesheet"
|
||||||
|
onerror="this.onerror=null; this.href='https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css';">
|
||||||
|
|
||||||
|
<!-- Font Awesome 6 (lokal mit Fallback) -->
|
||||||
|
<link rel="stylesheet" href="/libs/fontawesome/current/css/all.min.css"
|
||||||
|
onerror="this.onerror=null; this.href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css';">
|
||||||
|
|
||||||
|
<!-- Bootstrap Table CSS (lokal mit Fallback) -->
|
||||||
|
<link rel="stylesheet" href="/libs/bootstrap-table/current/css/bootstrap-table.min.css"
|
||||||
|
onerror="this.onerror=null; this.href='https://unpkg.com/bootstrap-table@1.21.4/dist/bootstrap-table.min.css';">
|
||||||
|
|
||||||
|
<!-- jsTree CSS (lokal mit Fallback) -->
|
||||||
|
<link rel="stylesheet" href="/libs/jstree/current/themes/default/style.min.css"
|
||||||
|
onerror="this.onerror=null; this.href='https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.16/themes/default/style.min.css';">
|
||||||
|
|
||||||
|
<!-- BIBB Custom Styles -->
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<link rel="stylesheet" href="./styles.css">
|
||||||
|
<link rel="stylesheet" href="../styles.css">
|
||||||
|
|
||||||
|
<!-- Kritische Styles als Fallback direkt eingebettet -->
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--primary-color: #1a3a5c;
|
||||||
|
--primary-light: #2a5a8c;
|
||||||
|
--primary-dark: #0f2840;
|
||||||
|
--secondary-color: #006699;
|
||||||
|
--text-primary: #333;
|
||||||
|
--text-secondary: #666;
|
||||||
|
--text-muted: #999;
|
||||||
|
--bg-gradient: linear-gradient(135deg, #e8eef5 0%, #f0f4f8 100%);
|
||||||
|
--bg-light: #f8f9fa;
|
||||||
|
--border-color: #e0e0e0;
|
||||||
|
--border-light: #f0f0f0;
|
||||||
|
--shadow-md: 0 4px 12px rgba(0,0,0,0.1);
|
||||||
|
--radius-md: 8px;
|
||||||
|
--radius-lg: 12px;
|
||||||
|
--transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--text-primary);
|
||||||
|
background: var(--bg-gradient);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-wrapper {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 40px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.site-header {
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
padding: 0;
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-header .container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-logo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-decoration: none;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-title {
|
||||||
|
font-size: 1.4em;
|
||||||
|
font-weight: 700;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-subtitle {
|
||||||
|
font-size: 0.85em;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav ul {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 10px 18px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: block;
|
||||||
|
font-size: 0.95em;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav a:hover {
|
||||||
|
background: rgba(255,255,255,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown Menu Styles */
|
||||||
|
.main-nav .nav-item {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav .dropdown-menu {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
min-width: 200px;
|
||||||
|
padding: 8px 0;
|
||||||
|
z-index: 1000;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav .nav-item:hover .dropdown-menu,
|
||||||
|
.main-nav .nav-item.show .dropdown-menu {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav .dropdown-menu li {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav .dropdown-menu .dropdown-item {
|
||||||
|
color: var(--text-primary) !important;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-weight: 400;
|
||||||
|
border-radius: 0;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-nav .dropdown-menu .dropdown-item:hover {
|
||||||
|
background: var(--bg-light) !important;
|
||||||
|
color: var(--primary-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cards */
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
padding: 35px 40px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 1.6em;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin: 0 0 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-description {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 1em;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nav Cards Grid */
|
||||||
|
.nav-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||||
|
gap: 25px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 25px 30px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-left: 4px solid var(--border-color);
|
||||||
|
transition: var(--transition);
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-card:hover {
|
||||||
|
border-left-color: var(--primary-color);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-card-icon {
|
||||||
|
font-size: 2.5em;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-card h3 {
|
||||||
|
font-size: 1.15em;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-card p {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section Title */
|
||||||
|
.section-title {
|
||||||
|
font-size: 1.3em;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 30px 0 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stats Grid */
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
background: var(--bg-light);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
text-align: center;
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-number {
|
||||||
|
font-size: 2.2em;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-color);
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.site-footer {
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
padding: 20px 0;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer .container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer a {
|
||||||
|
color: rgba(255,255,255,0.8);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-footer a:hover {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.btn-primary {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: var(--primary-light);
|
||||||
|
border-color: var(--primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success {
|
||||||
|
background-color: #28a745;
|
||||||
|
border-color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success:hover {
|
||||||
|
background-color: #218838;
|
||||||
|
border-color: #1e7e34;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background-color: #dc3545;
|
||||||
|
border-color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
background-color: #c82333;
|
||||||
|
border-color: #bd2130;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Relations Table */
|
||||||
|
#relation_table,
|
||||||
|
.new_modal_content {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#relation_table table,
|
||||||
|
.new_modal_content table {
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#relation_table table thead th,
|
||||||
|
.new_modal_content table thead th {
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#relation_table table thead tr:first-child th:first-child,
|
||||||
|
.new_modal_content table thead tr:first-child th:first-child {
|
||||||
|
border-top-left-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
#relation_table table thead tr:first-child th:last-child,
|
||||||
|
.new_modal_content table thead tr:first-child th:last-child {
|
||||||
|
border-top-right-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
#relation_table table tbody tr:last-child td:first-child,
|
||||||
|
.new_modal_content table tbody tr:last-child td:first-child {
|
||||||
|
border-bottom-left-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
#relation_table table tbody tr:last-child td:last-child,
|
||||||
|
.new_modal_content table tbody tr:last-child td:last-child {
|
||||||
|
border-bottom-right-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
#relation_table .table,
|
||||||
|
.new_modal_content .table {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Autocomplete Dropdown Styles */
|
||||||
|
.autocomplete-dropdown {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-dropdown .dropdown-item {
|
||||||
|
display: block;
|
||||||
|
padding: 10px 15px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px solid var(--border-light);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-dropdown .dropdown-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-dropdown .dropdown-item:hover {
|
||||||
|
background: var(--bg-light);
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bootstrap Table Pagination in BIBB-Blau */
|
||||||
|
.pagination .page-link {
|
||||||
|
color: var(--primary-color);
|
||||||
|
border-color: var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination .page-link:hover {
|
||||||
|
color: white;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination .page-item.active .page-link {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination .page-item.disabled .page-link {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Kopier-Icons und Links in Tabelle */
|
||||||
|
.fixed-table-container a,
|
||||||
|
.bootstrap-table a,
|
||||||
|
.fixed-table-body a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-table-container a:hover,
|
||||||
|
.bootstrap-table a:hover,
|
||||||
|
.fixed-table-body a:hover {
|
||||||
|
color: var(--primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toolbar Icons */
|
||||||
|
.fixed-table-toolbar .btn-secondary,
|
||||||
|
.fixed-table-toolbar .btn-default {
|
||||||
|
color: var(--primary-color);
|
||||||
|
border-color: var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-table-toolbar .btn-secondary:hover,
|
||||||
|
.fixed-table-toolbar .btn-default:hover {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown und Page-Size Selector */
|
||||||
|
.fixed-table-pagination .page-list .btn,
|
||||||
|
.fixed-table-pagination select,
|
||||||
|
.fixed-table-pagination .page-list .dropdown-toggle,
|
||||||
|
.fixed-table-pagination .page-list button {
|
||||||
|
color: var(--primary-color);
|
||||||
|
border-color: var(--border-color);
|
||||||
|
background-color: var(--bg-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-table-pagination .page-list .dropdown-menu {
|
||||||
|
background-color: var(--bg-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-table-pagination .page-list .dropdown-item {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-table-pagination .page-list .dropdown-item:hover,
|
||||||
|
.fixed-table-pagination .page-list .dropdown-item.active {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Detail-View Toggle Icons */
|
||||||
|
.detail-icon {
|
||||||
|
color: var(--primary-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bearbeiten/Löschen Icons in Tabelle weiß */
|
||||||
|
.table-actions .btn i,
|
||||||
|
.table-actions .btn-primary i,
|
||||||
|
.table-actions .btn-danger i {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
padding: 6px 14px;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tables */
|
||||||
|
.bootstrap-table .table thead th {
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
border: none;
|
||||||
|
padding: 14px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-table-container {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-table-toolbar {
|
||||||
|
background: var(--bg-light);
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modals */
|
||||||
|
.modal-header {
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
border-radius: 12px 12px 0 0;
|
||||||
|
padding: 18px 25px;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header .btn-close {
|
||||||
|
filter: brightness(0) invert(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forms */
|
||||||
|
.form-control:focus, .form-select:focus {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 3px rgba(26,58,92,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Utilities */
|
||||||
|
.d-flex { display: flex; }
|
||||||
|
.justify-between { justify-content: space-between; }
|
||||||
|
.align-center { align-items: center; }
|
||||||
|
.mb-20 { margin-bottom: 20px; }
|
||||||
|
.mt-20 { margin-top: 20px; }
|
||||||
|
.mt-30 { margin-top: 30px; }
|
||||||
|
.w-100 { width: 100%; }
|
||||||
|
|
||||||
|
/* Table Actions */
|
||||||
|
.table-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-actions .btn {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.site-header .container {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.nav-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.site-footer .container {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
padding: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- jQuery (lokal mit CDN-Fallback) -->
|
||||||
|
<script src="/libs/jquery/current/jquery.min.js"></script>
|
||||||
|
<script>window.jQuery || document.write('<script src="https://code.jquery.com/jquery-3.6.0.min.js"><\/script>')</script>
|
||||||
|
|
||||||
|
<!-- Bootstrap 5 JS (lokal mit CDN-Fallback) -->
|
||||||
|
<script src="/libs/bootstrap/current/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
if (typeof bootstrap === 'undefined') {
|
||||||
|
document.write('<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"><\/script>');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
if ($.fn.bootstrapTable && $.fn.bootstrapTable.locales) {
|
||||||
|
|
||||||
|
// Deutsch
|
||||||
|
if ($.fn.bootstrapTable.locales['de-DE']) {
|
||||||
|
$.fn.bootstrapTable.locales['de-DE'].formatColumnsToggle = function () {
|
||||||
|
return 'Spalten';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: globaler Default (greift wenn Locale nicht explizit gesetzt)
|
||||||
|
$.extend($.fn.bootstrapTable.defaults, {
|
||||||
|
formatColumnsToggle: function () { return 'Spalten'; }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Läuft nach jedem Table-Init und prüft ob der Columns-Button Einträge hat.
|
||||||
|
// Ist das Dropdown leer (z. B. in Modulen ohne data-show-columns-Konfiguration),
|
||||||
|
// wird der gesamte Button-Container versteckt.
|
||||||
|
$(document).on('init.bs.table', 'table', function () {
|
||||||
|
var $table = $(this);
|
||||||
|
|
||||||
|
// Kurze Verzögerung damit Bootstrap Table seinen DOM vollständig gerendert hat
|
||||||
|
setTimeout(function () {
|
||||||
|
var $btnGroup = $table.closest('.bootstrap-table')
|
||||||
|
.find('.columns-btn-group, .btn-group.columns');
|
||||||
|
|
||||||
|
if ($btnGroup.length > 0) {
|
||||||
|
// Prüfe ob der Dropdown-Inhalt tatsächlich Einträge hat
|
||||||
|
var itemCount = $btnGroup.find('li').length;
|
||||||
|
if (itemCount === 0) {
|
||||||
|
$btnGroup.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Bootstrap Typeahead (lokal mit CDN-Fallback) -->
|
||||||
|
<script src="/libs/bootstrap3-typeahead/current/bootstrap3-typeahead.min.js"></script>
|
||||||
|
<script>
|
||||||
|
if (typeof $.fn.typeahead === 'undefined') {
|
||||||
|
document.write('<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-3-typeahead/4.0.2/bootstrap3-typeahead.min.js"><\/script>');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Bootstrap Table JS (lokal mit CDN-Fallback) -->
|
||||||
|
<script src="/libs/bootstrap-table/current/js/bootstrap-table.min.js"></script>
|
||||||
|
<script>
|
||||||
|
if (typeof $.fn.bootstrapTable === 'undefined') {
|
||||||
|
document.write('<script src="https://unpkg.com/bootstrap-table@1.21.4/dist/bootstrap-table.min.js"><\/script>');
|
||||||
|
document.write('<script src="https://unpkg.com/bootstrap-table@1.21.4/dist/locale/bootstrap-table-de-DE.min.js"><\/script>');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script src="/libs/bootstrap-table/current/locale/bootstrap-table-de-DE.min.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="page-wrapper">
|
||||||
|
|
||||||
|
<!-- Site Header -->
|
||||||
|
<header class="site-header">
|
||||||
|
<div class="container">
|
||||||
|
<a href="index.php" class="site-logo">
|
||||||
|
<span class="site-title">BIBB Normdateienverwaltung</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<nav class="main-nav">
|
||||||
|
<ul>
|
||||||
|
<li><a href="index.php">Startseite</a></li>
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown">Normdateien</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a class="dropdown-item" href="Subjects.php">Schlagworte</a></li>
|
||||||
|
<li><a class="dropdown-item" href="Persons.php">Personen</a></li>
|
||||||
|
<li><a class="dropdown-item" href="Corporates.php">Körperschaften</a></li>
|
||||||
|
<li><a class="dropdown-item" href="Publishers.php">Verlage</a></li>
|
||||||
|
<li><a class="dropdown-item" href="Classifications.php">Klassifikation</a></li>
|
||||||
|
<li><a class="dropdown-item" href="Treeview.php">Klassifikationsbaum</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><a href="Statistics.php">Statistik</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
318
templates/PersonDetailsModal.tpl
Normal file
318
templates/PersonDetailsModal.tpl
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
<!-- Modal - Person Details mit semantischem Netz -->
|
||||||
|
<div class="modal fade" id="DetailsModal" tabindex="-1" aria-labelledby="DetailsModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-xl" style="max-width: 1200px;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="DetailsModalHeadline">
|
||||||
|
<i class="fas fa-user"></i> Detailanzeige Person
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<!-- Linke Spalte: Stammdaten -->
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="detail-info-panel">
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label fw-bold">Name</label>
|
||||||
|
<input type="text" id="display_term" class="form-control" disabled />
|
||||||
|
<input type="hidden" id="display_id" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label fw-bold">Deskriptor</label>
|
||||||
|
<input type="text" id="display_descriptor" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label fw-bold">Type</label>
|
||||||
|
<input type="text" id="display_type" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label fw-bold">ID</label>
|
||||||
|
<input type="text" id="display_id_show" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label fw-bold">Scopenote</label>
|
||||||
|
<textarea class="form-control" id="display_scopenote" rows="4" disabled></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Rechte Spalte: Semantisches Netz -->
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="network-panel">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<h6 class="mb-0"><i class="fas fa-project-diagram me-2"></i>Semantisches Netz</h6>
|
||||||
|
<div class="network-toolbar">
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="networkFit()" title="Ansicht anpassen">
|
||||||
|
<i class="fas fa-expand"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomIn()" title="Vergrößern">
|
||||||
|
<i class="fas fa-search-plus"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomOut()" title="Verkleinern">
|
||||||
|
<i class="fas fa-search-minus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="network-container"></div>
|
||||||
|
<div class="network-legend">
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #1a3a5c;"></span> Zentrale Person</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #28a745;"></span> Früherer Name</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #dc3545;"></span> Späterer Name</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #6c757d;"></span> USE/USEDFOR</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Versteckter Container für alte Relationen-Tabelle -->
|
||||||
|
<div class="modal_content" style="display: none;"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times"></i> schliessen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#network-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 450px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-panel {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-info-panel {
|
||||||
|
background: var(--bg-light);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-toolbar {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-toolbar .btn {
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-legend {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid var(--border-light);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-dot {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#DetailsModal .modal-body {
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- vis.js Network Bibliothek -->
|
||||||
|
<script src="/libs/vis-network/current/vis-network.min.js"></script>
|
||||||
|
<script>if (typeof vis === "undefined") { document.write('<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"><\/script>'); }</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var network = null;
|
||||||
|
var networkData = null;
|
||||||
|
|
||||||
|
function createSemanticNetwork(centralTerm, centralId, relations) {
|
||||||
|
console.log('🕸️ Erstelle semantisches Netz für Person:', centralTerm);
|
||||||
|
|
||||||
|
var nodes = [];
|
||||||
|
var edges = [];
|
||||||
|
|
||||||
|
// Zentraler Knoten
|
||||||
|
nodes.push({
|
||||||
|
id: centralId,
|
||||||
|
label: wrapLabel(centralTerm, 20),
|
||||||
|
shape: 'ellipse',
|
||||||
|
color: {
|
||||||
|
background: '#1a3a5c',
|
||||||
|
border: '#0d1f30',
|
||||||
|
highlight: { background: '#2a5a8c', border: '#1a3a5c' },
|
||||||
|
hover: { background: '#2a5a8c', border: '#1a3a5c' }
|
||||||
|
},
|
||||||
|
font: { color: '#ffffff', size: 14, face: 'Arial', bold: true },
|
||||||
|
size: 35,
|
||||||
|
borderWidth: 3,
|
||||||
|
shadow: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Farben für Personen-Relationstypen
|
||||||
|
var relationColors = {
|
||||||
|
'FORMERNAME': { node: '#28a745', edge: '#28a745', label: 'Früherer Name' },
|
||||||
|
'LATERNAME': { node: '#dc3545', edge: '#dc3545', label: 'Späterer Name' },
|
||||||
|
'USE': { node: '#6c757d', edge: '#6c757d', label: 'Benutze' },
|
||||||
|
'USEDFOR': { node: '#6c757d', edge: '#6c757d', label: 'Benutzt für' }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (relations && relations.length > 0) {
|
||||||
|
relations.forEach(function(rel, index) {
|
||||||
|
var relType = rel.Relationtype || 'USE';
|
||||||
|
var relColor = relationColors[relType] || { node: '#fd7e14', edge: '#fd7e14' };
|
||||||
|
var relId = rel.IDRelation || ('rel_' + index);
|
||||||
|
var relText = rel.TextRelation || 'Unbekannt';
|
||||||
|
|
||||||
|
nodes.push({
|
||||||
|
id: relId,
|
||||||
|
label: wrapLabel(relText, 18),
|
||||||
|
shape: 'ellipse',
|
||||||
|
color: {
|
||||||
|
background: relColor.node,
|
||||||
|
border: shadeColor(relColor.node, -20),
|
||||||
|
highlight: { background: shadeColor(relColor.node, 20), border: relColor.node },
|
||||||
|
hover: { background: shadeColor(relColor.node, 20), border: relColor.node }
|
||||||
|
},
|
||||||
|
font: { color: '#ffffff', size: 12, face: 'Arial' },
|
||||||
|
size: 25,
|
||||||
|
borderWidth: 2,
|
||||||
|
shadow: true,
|
||||||
|
title: relType + ': ' + relText + ' (ID: ' + relId + ')'
|
||||||
|
});
|
||||||
|
|
||||||
|
var arrowDirection = {};
|
||||||
|
if (relType === 'FORMERNAME') {
|
||||||
|
arrowDirection = { from: { enabled: true, scaleFactor: 1 } };
|
||||||
|
} else if (relType === 'LATERNAME') {
|
||||||
|
arrowDirection = { to: { enabled: true, scaleFactor: 1 } };
|
||||||
|
}
|
||||||
|
|
||||||
|
edges.push({
|
||||||
|
from: centralId,
|
||||||
|
to: relId,
|
||||||
|
label: relType,
|
||||||
|
color: { color: relColor.edge, highlight: relColor.edge, hover: relColor.edge },
|
||||||
|
width: 2,
|
||||||
|
arrows: arrowDirection,
|
||||||
|
font: { size: 10, color: '#666', strokeWidth: 3, strokeColor: '#ffffff' },
|
||||||
|
smooth: { type: 'curvedCW', roundness: 0.2 }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var container = document.getElementById('network-container');
|
||||||
|
|
||||||
|
networkData = {
|
||||||
|
nodes: new vis.DataSet(nodes),
|
||||||
|
edges: new vis.DataSet(edges)
|
||||||
|
};
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
nodes: { shape: 'ellipse', scaling: { min: 20, max: 40 } },
|
||||||
|
edges: { smooth: { type: 'curvedCW', roundness: 0.2 } },
|
||||||
|
physics: {
|
||||||
|
enabled: true,
|
||||||
|
barnesHut: {
|
||||||
|
gravitationalConstant: -3000,
|
||||||
|
centralGravity: 0.3,
|
||||||
|
springLength: 150,
|
||||||
|
springConstant: 0.04,
|
||||||
|
damping: 0.09
|
||||||
|
},
|
||||||
|
stabilization: { enabled: true, iterations: 200, updateInterval: 25 }
|
||||||
|
},
|
||||||
|
interaction: { hover: true, tooltipDelay: 200, zoomView: true, dragView: true },
|
||||||
|
layout: { improvedLayout: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
network = new vis.Network(container, networkData, options);
|
||||||
|
|
||||||
|
network.on('click', function(params) {
|
||||||
|
if (params.nodes.length > 0) {
|
||||||
|
var clickedNodeId = params.nodes[0];
|
||||||
|
if (clickedNodeId != centralId) {
|
||||||
|
ShowModalDetails(clickedNodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
network.once('stabilizationIterationsDone', function() {
|
||||||
|
network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapLabel(text, maxLength) {
|
||||||
|
if (!text) return '';
|
||||||
|
if (text.length <= maxLength) return text;
|
||||||
|
var words = text.split(' ');
|
||||||
|
var lines = [];
|
||||||
|
var currentLine = '';
|
||||||
|
words.forEach(function(word) {
|
||||||
|
if ((currentLine + ' ' + word).trim().length <= maxLength) {
|
||||||
|
currentLine = (currentLine + ' ' + word).trim();
|
||||||
|
} else {
|
||||||
|
if (currentLine) lines.push(currentLine);
|
||||||
|
currentLine = word;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (currentLine) lines.push(currentLine);
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function shadeColor(color, percent) {
|
||||||
|
var R = parseInt(color.substring(1,3), 16);
|
||||||
|
var G = parseInt(color.substring(3,5), 16);
|
||||||
|
var B = parseInt(color.substring(5,7), 16);
|
||||||
|
R = parseInt(R * (100 + percent) / 100);
|
||||||
|
G = parseInt(G * (100 + percent) / 100);
|
||||||
|
B = parseInt(B * (100 + percent) / 100);
|
||||||
|
R = (R < 255) ? R : 255;
|
||||||
|
G = (G < 255) ? G : 255;
|
||||||
|
B = (B < 255) ? B : 255;
|
||||||
|
var RR = ((R.toString(16).length == 1) ? "0" + R.toString(16) : R.toString(16));
|
||||||
|
var GG = ((G.toString(16).length == 1) ? "0" + G.toString(16) : G.toString(16));
|
||||||
|
var BB = ((B.toString(16).length == 1) ? "0" + B.toString(16) : B.toString(16));
|
||||||
|
return "#" + RR + GG + BB;
|
||||||
|
}
|
||||||
|
|
||||||
|
function networkFit() {
|
||||||
|
if (network) network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||||||
|
}
|
||||||
|
|
||||||
|
function networkZoomIn() {
|
||||||
|
if (network) network.moveTo({ scale: network.getScale() * 1.3, animation: { duration: 300 } });
|
||||||
|
}
|
||||||
|
|
||||||
|
function networkZoomOut() {
|
||||||
|
if (network) network.moveTo({ scale: network.getScale() / 1.3, animation: { duration: 300 } });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
1
templates/PersonsModal.tpl
Normal file
1
templates/PersonsModal.tpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!-- PersonsModal - Platzhalter für zukünftige Erweiterungen -->
|
||||||
317
templates/PublisherDetailsModal.tpl
Normal file
317
templates/PublisherDetailsModal.tpl
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
<!-- Modal - Verlag Details mit semantischem Netz -->
|
||||||
|
<div class="modal fade" id="DetailsModal" tabindex="-1" aria-labelledby="DetailsModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-xl" style="max-width: 1200px;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="DetailsModalHeadline">
|
||||||
|
<i class="fas fa-book"></i> Detailanzeige Verlag
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<!-- Linke Spalte: Stammdaten -->
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="detail-info-panel">
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label fw-bold">Name</label>
|
||||||
|
<input type="text" id="display_term" class="form-control" disabled />
|
||||||
|
<input type="hidden" id="display_id" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label fw-bold">Deskriptor</label>
|
||||||
|
<input type="text" id="display_descriptor" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label fw-bold">Type</label>
|
||||||
|
<input type="text" id="display_type" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label fw-bold">ID</label>
|
||||||
|
<input type="text" id="display_id_show" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label fw-bold">Scopenote</label>
|
||||||
|
<textarea class="form-control" id="display_scopenote" rows="4" disabled></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Rechte Spalte: Semantisches Netz -->
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="network-panel">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<h6 class="mb-0"><i class="fas fa-project-diagram me-2"></i>Semantisches Netz</h6>
|
||||||
|
<div class="network-toolbar">
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="networkFit()" title="Ansicht anpassen">
|
||||||
|
<i class="fas fa-expand"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomIn()" title="Vergrößern">
|
||||||
|
<i class="fas fa-search-plus"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomOut()" title="Verkleinern">
|
||||||
|
<i class="fas fa-search-minus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="network-container"></div>
|
||||||
|
<div class="network-legend">
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #1a3a5c;"></span> Zentraler Verlag</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #28a745;"></span> Früher verwendet</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #dc3545;"></span> Später verwendet</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #fd7e14;"></span> Alternativer Name</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #6c757d;"></span> USE/USEDFOR</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal_content" style="display: none;"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times"></i> schliessen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#network-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 450px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-panel {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-info-panel {
|
||||||
|
background: var(--bg-light);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-toolbar {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-toolbar .btn {
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-legend {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid var(--border-light);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-dot {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#DetailsModal .modal-body {
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script src="/libs/vis-network/current/vis-network.min.js"></script>
|
||||||
|
<script>if (typeof vis === "undefined") { document.write('<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"><\/script>'); }</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var network = null;
|
||||||
|
var networkData = null;
|
||||||
|
|
||||||
|
function createSemanticNetwork(centralTerm, centralId, relations) {
|
||||||
|
console.log('🕸️ Erstelle semantisches Netz für Verlag:', centralTerm);
|
||||||
|
|
||||||
|
var nodes = [];
|
||||||
|
var edges = [];
|
||||||
|
|
||||||
|
nodes.push({
|
||||||
|
id: centralId,
|
||||||
|
label: wrapLabel(centralTerm, 20),
|
||||||
|
shape: 'ellipse',
|
||||||
|
color: {
|
||||||
|
background: '#1a3a5c',
|
||||||
|
border: '#0d1f30',
|
||||||
|
highlight: { background: '#2a5a8c', border: '#1a3a5c' },
|
||||||
|
hover: { background: '#2a5a8c', border: '#1a3a5c' }
|
||||||
|
},
|
||||||
|
font: { color: '#ffffff', size: 14, face: 'Arial', bold: true },
|
||||||
|
size: 35,
|
||||||
|
borderWidth: 3,
|
||||||
|
shadow: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Farben für Verlags-Relationstypen
|
||||||
|
var relationColors = {
|
||||||
|
'USEDEARLIER': { node: '#28a745', edge: '#28a745', label: 'Früher verwendet' },
|
||||||
|
'USEDLATER': { node: '#dc3545', edge: '#dc3545', label: 'Später verwendet' },
|
||||||
|
'ALTERNATIVENAME': { node: '#fd7e14', edge: '#fd7e14', label: 'Alternativer Name' },
|
||||||
|
'USE': { node: '#6c757d', edge: '#6c757d', label: 'Benutze' },
|
||||||
|
'USEDFOR': { node: '#6c757d', edge: '#6c757d', label: 'Benutzt für' }
|
||||||
|
};
|
||||||
|
|
||||||
|
if (relations && relations.length > 0) {
|
||||||
|
relations.forEach(function(rel, index) {
|
||||||
|
var relType = rel.Relationtype || 'USE';
|
||||||
|
var relColor = relationColors[relType] || { node: '#17a2b8', edge: '#17a2b8' };
|
||||||
|
var relId = rel.IDRelation || ('rel_' + index);
|
||||||
|
var relText = rel.TextRelation || 'Unbekannt';
|
||||||
|
|
||||||
|
nodes.push({
|
||||||
|
id: relId,
|
||||||
|
label: wrapLabel(relText, 18),
|
||||||
|
shape: 'ellipse',
|
||||||
|
color: {
|
||||||
|
background: relColor.node,
|
||||||
|
border: shadeColor(relColor.node, -20),
|
||||||
|
highlight: { background: shadeColor(relColor.node, 20), border: relColor.node },
|
||||||
|
hover: { background: shadeColor(relColor.node, 20), border: relColor.node }
|
||||||
|
},
|
||||||
|
font: { color: '#ffffff', size: 12, face: 'Arial' },
|
||||||
|
size: 25,
|
||||||
|
borderWidth: 2,
|
||||||
|
shadow: true,
|
||||||
|
title: relType + ': ' + relText + ' (ID: ' + relId + ')'
|
||||||
|
});
|
||||||
|
|
||||||
|
var arrowDirection = {};
|
||||||
|
if (relType === 'USEDEARLIER') {
|
||||||
|
arrowDirection = { from: { enabled: true, scaleFactor: 1 } };
|
||||||
|
} else if (relType === 'USEDLATER') {
|
||||||
|
arrowDirection = { to: { enabled: true, scaleFactor: 1 } };
|
||||||
|
}
|
||||||
|
|
||||||
|
edges.push({
|
||||||
|
from: centralId,
|
||||||
|
to: relId,
|
||||||
|
label: relType,
|
||||||
|
color: { color: relColor.edge, highlight: relColor.edge, hover: relColor.edge },
|
||||||
|
width: 2,
|
||||||
|
arrows: arrowDirection,
|
||||||
|
font: { size: 10, color: '#666', strokeWidth: 3, strokeColor: '#ffffff' },
|
||||||
|
smooth: { type: 'curvedCW', roundness: 0.2 }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var container = document.getElementById('network-container');
|
||||||
|
|
||||||
|
networkData = {
|
||||||
|
nodes: new vis.DataSet(nodes),
|
||||||
|
edges: new vis.DataSet(edges)
|
||||||
|
};
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
nodes: { shape: 'ellipse', scaling: { min: 20, max: 40 } },
|
||||||
|
edges: { smooth: { type: 'curvedCW', roundness: 0.2 } },
|
||||||
|
physics: {
|
||||||
|
enabled: true,
|
||||||
|
barnesHut: {
|
||||||
|
gravitationalConstant: -3000,
|
||||||
|
centralGravity: 0.3,
|
||||||
|
springLength: 150,
|
||||||
|
springConstant: 0.04,
|
||||||
|
damping: 0.09
|
||||||
|
},
|
||||||
|
stabilization: { enabled: true, iterations: 200, updateInterval: 25 }
|
||||||
|
},
|
||||||
|
interaction: { hover: true, tooltipDelay: 200, zoomView: true, dragView: true },
|
||||||
|
layout: { improvedLayout: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
network = new vis.Network(container, networkData, options);
|
||||||
|
|
||||||
|
network.on('click', function(params) {
|
||||||
|
if (params.nodes.length > 0) {
|
||||||
|
var clickedNodeId = params.nodes[0];
|
||||||
|
if (clickedNodeId != centralId) {
|
||||||
|
ShowModalDetails(clickedNodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
network.once('stabilizationIterationsDone', function() {
|
||||||
|
network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapLabel(text, maxLength) {
|
||||||
|
if (!text) return '';
|
||||||
|
if (text.length <= maxLength) return text;
|
||||||
|
var words = text.split(' ');
|
||||||
|
var lines = [];
|
||||||
|
var currentLine = '';
|
||||||
|
words.forEach(function(word) {
|
||||||
|
if ((currentLine + ' ' + word).trim().length <= maxLength) {
|
||||||
|
currentLine = (currentLine + ' ' + word).trim();
|
||||||
|
} else {
|
||||||
|
if (currentLine) lines.push(currentLine);
|
||||||
|
currentLine = word;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (currentLine) lines.push(currentLine);
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function shadeColor(color, percent) {
|
||||||
|
var R = parseInt(color.substring(1,3), 16);
|
||||||
|
var G = parseInt(color.substring(3,5), 16);
|
||||||
|
var B = parseInt(color.substring(5,7), 16);
|
||||||
|
R = parseInt(R * (100 + percent) / 100);
|
||||||
|
G = parseInt(G * (100 + percent) / 100);
|
||||||
|
B = parseInt(B * (100 + percent) / 100);
|
||||||
|
R = (R < 255) ? R : 255;
|
||||||
|
G = (G < 255) ? G : 255;
|
||||||
|
B = (B < 255) ? B : 255;
|
||||||
|
var RR = ((R.toString(16).length == 1) ? "0" + R.toString(16) : R.toString(16));
|
||||||
|
var GG = ((G.toString(16).length == 1) ? "0" + G.toString(16) : G.toString(16));
|
||||||
|
var BB = ((B.toString(16).length == 1) ? "0" + B.toString(16) : B.toString(16));
|
||||||
|
return "#" + RR + GG + BB;
|
||||||
|
}
|
||||||
|
|
||||||
|
function networkFit() {
|
||||||
|
if (network) network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||||||
|
}
|
||||||
|
|
||||||
|
function networkZoomIn() {
|
||||||
|
if (network) network.moveTo({ scale: network.getScale() * 1.3, animation: { duration: 300 } });
|
||||||
|
}
|
||||||
|
|
||||||
|
function networkZoomOut() {
|
||||||
|
if (network) network.moveTo({ scale: network.getScale() / 1.3, animation: { duration: 300 } });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
1
templates/PublishersModal.tpl
Normal file
1
templates/PublishersModal.tpl
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!-- PublishersModal - Platzhalter für zukünftige Erweiterungen -->
|
||||||
349
templates/RelationsModal.tpl
Normal file
349
templates/RelationsModal.tpl
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
<!-- Modal - Relationen tabellarisch -->
|
||||||
|
<div class="modal fade" id="RelationsModal" tabindex="-1" aria-labelledby="RelationsModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg" style="max-width: 900px;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="RelationsModalHeadline">
|
||||||
|
<i class="fas fa-table"></i> Relationen
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
|
||||||
|
<!-- Term-Info -->
|
||||||
|
<div class="relations-modal-info mb-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label fw-bold">Begriff</label>
|
||||||
|
<input type="text" id="relations_modal_term" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label fw-bold">ID</label>
|
||||||
|
<input type="text" id="relations_modal_id" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label fw-bold">Deskriptor</label>
|
||||||
|
<input type="text" id="relations_modal_descriptor" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label fw-bold">DSpace</label>
|
||||||
|
<div id="relations_modal_dspace" class="form-control" style="background: #e9ecef;">
|
||||||
|
<span class="text-muted">–</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lade-Anzeige -->
|
||||||
|
<div id="relations_modal_loading" class="text-center py-4" style="display: none;">
|
||||||
|
<div class="spinner-border spinner-border-sm text-primary me-2" role="status"></div>
|
||||||
|
<span class="text-muted">Lade Relationen...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Relationen-Tabelle -->
|
||||||
|
<div id="relations_modal_content" style="display: none;">
|
||||||
|
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
|
||||||
|
<table class="table table-sm table-hover table-bordered mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 120px;">Typ</th>
|
||||||
|
<th>Begriff</th>
|
||||||
|
<th style="width: 80px;" class="text-center">ID</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="relations_modal_body">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Keine Relationen -->
|
||||||
|
<div id="relations_modal_empty" class="text-center py-4 text-muted" style="display: none;">
|
||||||
|
<i class="fas fa-info-circle me-1"></i> Keine Relationen vorhanden.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Synonyme-Bereich (nur bei Subjects) -->
|
||||||
|
<div id="relations_modal_synonyms" style="display: none; margin-top: 15px;">
|
||||||
|
<h6 class="mb-2"><i class="fas fa-equals me-1" style="color: #e91e63;"></i> Synonyme</h6>
|
||||||
|
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
|
||||||
|
<table class="table table-sm table-hover table-bordered mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 120px;">Typ</th>
|
||||||
|
<th>Begriff</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="relations_modal_synonyms_body">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times"></i> schliessen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.relations-modal-info {
|
||||||
|
background: var(--bg-light, #f8f9fa);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid var(--border-color, #e0e0e0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#RelationsModal .badge {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 4px 8px;
|
||||||
|
min-width: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#RelationsModal .badge.bg-syn {
|
||||||
|
background-color: #e91e63;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#RelationsModal .relation-link {
|
||||||
|
color: var(--primary-color, #1a3a5c);
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#RelationsModal .relation-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: var(--primary-light, #2a5a8c);
|
||||||
|
}
|
||||||
|
|
||||||
|
#RelationsModal .modal-body {
|
||||||
|
max-height: 75vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* Relationen-Modal öffnen und Daten laden
|
||||||
|
*/
|
||||||
|
function ShowRelationsModal(id, authType) {
|
||||||
|
console.log('📋 Lade Relationen für ID:', id, 'Typ:', authType);
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
$('#relations_modal_term').val('');
|
||||||
|
$('#relations_modal_id').val(id);
|
||||||
|
$('#relations_modal_descriptor').val('');
|
||||||
|
$('#relations_modal_body').html('');
|
||||||
|
$('#relations_modal_synonyms_body').html('');
|
||||||
|
$('#relations_modal_content').hide();
|
||||||
|
$('#relations_modal_empty').hide();
|
||||||
|
$('#relations_modal_synonyms').hide();
|
||||||
|
$('#relations_modal_loading').show();
|
||||||
|
$('#RelationsModalHeadline').html('<i class="fas fa-table"></i> Relationen');
|
||||||
|
|
||||||
|
// Modal öffnen
|
||||||
|
$('#RelationsModal').modal('show');
|
||||||
|
|
||||||
|
// Daten laden
|
||||||
|
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
|
||||||
|
authType: authType,
|
||||||
|
offset: 0,
|
||||||
|
id: id,
|
||||||
|
sort: 'Anchor.Text',
|
||||||
|
limit: 1
|
||||||
|
}, function(data, status) {
|
||||||
|
if (status === "success") {
|
||||||
|
var metadata = (typeof data === 'string') ? JSON.parse(data) : data;
|
||||||
|
var row = metadata['rows'];
|
||||||
|
|
||||||
|
if (!row || row.length === 0) {
|
||||||
|
$('#relations_modal_loading').hide();
|
||||||
|
$('#relations_modal_empty').show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var entry = row[0];
|
||||||
|
|
||||||
|
// Info-Felder befüllen
|
||||||
|
$('#relations_modal_term').val(entry.Text || '');
|
||||||
|
$('#relations_modal_id').val(entry.ID || id);
|
||||||
|
$('#relations_modal_descriptor').val(entry.Descriptor === false ? 'Nein' : 'Ja');
|
||||||
|
|
||||||
|
// DSpace-Count laden (nur bei Subject/Person und Deskriptor)
|
||||||
|
var $dspaceField = $('#relations_modal_dspace');
|
||||||
|
$dspaceField.html('<span class="text-muted">–</span>');
|
||||||
|
|
||||||
|
if ((authType === 'Subject' || authType === 'Person') && entry.Descriptor !== false) {
|
||||||
|
$dspaceField.html('<span class="spinner-border spinner-border-sm text-muted" style="width: 12px; height: 12px;" role="status"></span>');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/Thesaurus/ajax/getDSpaceCount.php',
|
||||||
|
type: 'GET',
|
||||||
|
data: { authType: authType, query: entry.Text },
|
||||||
|
dataType: 'json',
|
||||||
|
timeout: 15000,
|
||||||
|
success: function(data) {
|
||||||
|
var count = (data && typeof data.count !== 'undefined') ? data.count : 0;
|
||||||
|
if (count === -1) {
|
||||||
|
$dspaceField.html('<span class="text-muted" title="DSpace nicht erreichbar"><i class="fas fa-exclamation-triangle"></i></span>');
|
||||||
|
} else if (count > 0) {
|
||||||
|
$dspaceField.html('<span class="badge" style="background-color: #1a3a5c;">' + count + ' Datensätze</span>');
|
||||||
|
} else {
|
||||||
|
$dspaceField.html('<span class="badge bg-danger">0</span>');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
$dspaceField.html('<span class="text-muted"><i class="fas fa-exclamation-triangle"></i></span>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Badge-Konfiguration
|
||||||
|
var badgeConfig = {
|
||||||
|
'BT': { css: 'bg-success', title: 'BT - Oberbegriff' },
|
||||||
|
'NT': { css: 'bg-danger', title: 'NT - Unterbegriff' },
|
||||||
|
'RT': { css: 'bg-warning text-dark', title: 'RT - Verwandter Begriff' },
|
||||||
|
'USE': { css: 'bg-info', title: 'USE - Benutze' },
|
||||||
|
'UF': { css: 'bg-secondary', title: 'UF - Benutzt für' },
|
||||||
|
'USEDFOR': { css: 'bg-secondary', title: 'USEDFOR - Benutzt für' },
|
||||||
|
'FORMERNAME': { css: 'bg-success', title: 'Früherer Name' },
|
||||||
|
'LATERNAME': { css: 'bg-danger', title: 'Späterer Name' },
|
||||||
|
'USEDEARLIER': { css: 'bg-success', title: 'Früher verwendet' },
|
||||||
|
'USEDLATER': { css: 'bg-danger', title: 'Später verwendet' },
|
||||||
|
'ALTERNATIVENAME': { css: 'bg-warning text-dark', title: 'Alternativer Name' },
|
||||||
|
'SYN': { css: 'bg-syn', title: 'SYN - Synonym' }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Relationen rendern
|
||||||
|
var relations = entry.Relations || [];
|
||||||
|
var html = '';
|
||||||
|
|
||||||
|
if (relations.length > 0) {
|
||||||
|
relations.forEach(function(rel) {
|
||||||
|
var relId = rel.IDRelation || '';
|
||||||
|
|
||||||
|
// Selbst-Relation überspringen
|
||||||
|
if (relId == id) return;
|
||||||
|
|
||||||
|
var relType = rel.Relationtype || 'RT';
|
||||||
|
var badge = badgeConfig[relType] || { css: 'bg-secondary', title: relType };
|
||||||
|
var relText = _escRelModal(rel.TextRelation || '');
|
||||||
|
var relId = rel.IDRelation || '';
|
||||||
|
|
||||||
|
html += '<tr>';
|
||||||
|
html += '<td><span class="badge ' + badge.css + '" title="' + badge.title + '">' + relType + '</span></td>';
|
||||||
|
html += '<td><a class="relation-link" href="javascript:void(0)" onclick="ShowRelationsModal(\'' + relId + '\', \'' + authType + '\')" title="Relationen anzeigen">' + relText + '</a></td>';
|
||||||
|
html += '<td class="text-center text-muted"><small>' + relId + '</small></td>';
|
||||||
|
html += '</tr>';
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#relations_modal_body').html(html);
|
||||||
|
if (html.length > 0) {
|
||||||
|
$('#relations_modal_content').show();
|
||||||
|
} else {
|
||||||
|
$('#relations_modal_empty').show();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$('#relations_modal_empty').show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synonyme (nur bei Subjects)
|
||||||
|
if (authType === 'Subject') {
|
||||||
|
var synonyms = entry.Synonyms || '';
|
||||||
|
|
||||||
|
// Falls leer, separat laden
|
||||||
|
if (!synonyms || synonyms.trim() === '') {
|
||||||
|
$.ajax({
|
||||||
|
url: "/Thesaurus/ajax/getSynonyms.php",
|
||||||
|
type: "GET",
|
||||||
|
data: { text: entry.Text },
|
||||||
|
dataType: "json",
|
||||||
|
success: function(synResponse) {
|
||||||
|
if (synResponse.success && synResponse.synonyms && synResponse.synonyms.length > 0) {
|
||||||
|
synonyms = synResponse.synonyms.map(function(s) { return s.text; }).join(', ');
|
||||||
|
_renderSynonymsInModal(synonyms, badgeConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_renderSynonymsInModal(synonyms, badgeConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#relations_modal_loading').hide();
|
||||||
|
}
|
||||||
|
}).fail(function() {
|
||||||
|
$('#relations_modal_loading').hide();
|
||||||
|
$('#relations_modal_empty').html('<i class="fas fa-exclamation-circle me-1 text-danger"></i> Fehler beim Laden der Relationen.').show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _renderSynonymsInModal(synonyms, badgeConfig) {
|
||||||
|
if (!synonyms || synonyms.trim() === '') return;
|
||||||
|
|
||||||
|
var synArray = synonyms.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s.length > 0; });
|
||||||
|
if (synArray.length === 0) return;
|
||||||
|
|
||||||
|
var badge = badgeConfig['SYN'];
|
||||||
|
var html = '';
|
||||||
|
|
||||||
|
synArray.forEach(function(synText) {
|
||||||
|
html += '<tr>';
|
||||||
|
html += '<td><span class="badge ' + badge.css + '" title="' + badge.title + '">SYN</span></td>';
|
||||||
|
html += '<td>' + _escRelModal(synText) + '</td>';
|
||||||
|
html += '</tr>';
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#relations_modal_synonyms_body').html(html);
|
||||||
|
$('#relations_modal_synonyms').show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _escRelModal(text) {
|
||||||
|
if (!text) return '';
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text in die Zwischenablage kopieren (modern mit Fallback)
|
||||||
|
*/
|
||||||
|
function copyTermToClipboard(text, btnElement) {
|
||||||
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
|
navigator.clipboard.writeText(text).then(function() {
|
||||||
|
_showCopyFeedback(btnElement);
|
||||||
|
}).catch(function() {
|
||||||
|
_fallbackCopy(text, btnElement);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_fallbackCopy(text, btnElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _fallbackCopy(text, btnElement) {
|
||||||
|
var tempInput = document.createElement("input");
|
||||||
|
tempInput.value = text;
|
||||||
|
document.body.appendChild(tempInput);
|
||||||
|
tempInput.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(tempInput);
|
||||||
|
_showCopyFeedback(btnElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _showCopyFeedback(btnElement) {
|
||||||
|
if (!btnElement) return;
|
||||||
|
var $btn = $(btnElement).closest('.copy-btn');
|
||||||
|
var $icon = $btn.find('i');
|
||||||
|
$icon.removeClass('fa-copy').addClass('fa-check');
|
||||||
|
$btn.addClass('btn-success').removeClass('btn-outline-secondary');
|
||||||
|
setTimeout(function() {
|
||||||
|
$icon.removeClass('fa-check').addClass('fa-copy');
|
||||||
|
$btn.removeClass('btn-success').addClass('btn-outline-secondary');
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
745
templates/SubjectDetailsModal.tpl
Normal file
745
templates/SubjectDetailsModal.tpl
Normal file
@ -0,0 +1,745 @@
|
|||||||
|
<!-- Modal - Schlagwort Details mit semantischem Netz (2 Ebenen) -->
|
||||||
|
<div class="modal fade" id="DetailsModal" tabindex="-1" aria-labelledby="DetailsModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-xl" style="max-width: 1400px;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="DetailsModalHeadline">
|
||||||
|
<i class="fas fa-tag"></i> Detailanzeige
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<!-- Linke Spalte: Stammdaten -->
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="detail-info-panel">
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label fw-bold">Schlagwort</label>
|
||||||
|
<input type="text" id="display_term" class="form-control" disabled />
|
||||||
|
<input type="hidden" id="display_id" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label fw-bold">Deskriptor</label>
|
||||||
|
<input type="text" id="display_descriptor" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label fw-bold">Type</label>
|
||||||
|
<input type="text" id="display_type" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<label class="form-label fw-bold">ID</label>
|
||||||
|
<input type="text" id="display_id_show" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label fw-bold">Detailtype</label>
|
||||||
|
<input type="text" id="display_detailtype" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label fw-bold">Klassifikation</label>
|
||||||
|
<input type="text" id="display_classification" class="form-control" disabled />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label fw-bold">Scopenote</label>
|
||||||
|
<textarea class="form-control" id="display_scopenote" rows="3" disabled></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label fw-bold"><i class="fas fa-equals me-1" style="color: #e91e63;"></i>Synonyme</label>
|
||||||
|
<textarea class="form-control" id="display_synonyms" rows="2" disabled style="font-size: 0.9rem;"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lade-Status -->
|
||||||
|
<div id="network-status" class="mt-3">
|
||||||
|
<small class="text-muted">
|
||||||
|
<i class="fas fa-circle-notch fa-spin me-1"></i>
|
||||||
|
<span id="status-text">Lade Netzwerk...</span>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Rechte Spalte: Semantisches Netz -->
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div class="network-panel">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<h6 class="mb-0"><i class="fas fa-project-diagram me-2"></i>Semantisches Netz (2 Ebenen)</h6>
|
||||||
|
<div class="network-toolbar">
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="networkFit()" title="Ansicht anpassen">
|
||||||
|
<i class="fas fa-expand"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomIn()" title="Vergrößern">
|
||||||
|
<i class="fas fa-search-plus"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="networkZoomOut()" title="Verkleinern">
|
||||||
|
<i class="fas fa-search-minus"></i>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="toggleSecondLevel()" title="2. Ebene ein/aus" id="btn-level">
|
||||||
|
<i class="fas fa-layer-group"></i> <span id="btn-level-text">2</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="togglePhysics()" title="Physik/Dynamik ein/aus" id="btn-physics">
|
||||||
|
<i class="fas fa-atom"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="network-container"></div>
|
||||||
|
<div class="network-legend">
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #1a3a5c;"></span> Zentraler Begriff</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #28a745;"></span> Oberbegriff (BT)</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #dc3545;"></span> Unterbegriff (NT)</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #fd7e14;"></span> Verwandt (RT)</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #e91e63;"></span> Synonym (SYN)</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #6c757d;"></span> USE/UF</span>
|
||||||
|
<span class="legend-item"><span class="legend-dot" style="background: #adb5bd; opacity: 0.7;"></span> 2. Ebene</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Versteckter Container für alte Relationen-Tabelle (falls benötigt) -->
|
||||||
|
<div class="modal_content" style="display: none;"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times"></i> schliessen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Netzwerk-Container - größer für 2 Ebenen */
|
||||||
|
#network-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 550px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-panel {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-info-panel {
|
||||||
|
background: var(--bg-light);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-toolbar {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-toolbar .btn {
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network-legend {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid var(--border-light);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-dot {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal extra breit für 2 Ebenen */
|
||||||
|
#DetailsModal .modal-body {
|
||||||
|
max-height: 85vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#network-status {
|
||||||
|
padding: 8px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- vis.js Network Bibliothek -->
|
||||||
|
<script src="/libs/vis-network/current/vis-network.min.js"></script>
|
||||||
|
<script>if (typeof vis === "undefined") { document.write('<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"><\/script>'); }</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var network = null;
|
||||||
|
var networkData = null;
|
||||||
|
var nodesDataSet = null;
|
||||||
|
var edgesDataSet = null;
|
||||||
|
var centralNodeId = null;
|
||||||
|
var firstLevelNodeIds = [];
|
||||||
|
var secondLevelNodeIds = [];
|
||||||
|
var secondLevelEdgeIds = [];
|
||||||
|
var secondLevelVisible = true;
|
||||||
|
var secondLevelLoaded = false;
|
||||||
|
var physicsEnabled = true;
|
||||||
|
|
||||||
|
// Farben für verschiedene Relationstypen
|
||||||
|
var relationColors = {
|
||||||
|
'BT': { node: '#28a745', edge: '#28a745', label: 'Oberbegriff' },
|
||||||
|
'BROADERTERM': { node: '#28a745', edge: '#28a745', label: 'Oberbegriff' },
|
||||||
|
'NT': { node: '#dc3545', edge: '#dc3545', label: 'Unterbegriff' },
|
||||||
|
'NARROWERTERM': { node: '#dc3545', edge: '#dc3545', label: 'Unterbegriff' },
|
||||||
|
'RT': { node: '#fd7e14', edge: '#fd7e14', label: 'Verwandt' },
|
||||||
|
'RELATEDTERM': { node: '#fd7e14', edge: '#fd7e14', label: 'Verwandt' },
|
||||||
|
'USE': { node: '#6c757d', edge: '#6c757d', label: 'Benutze' },
|
||||||
|
'UF': { node: '#6c757d', edge: '#6c757d', label: 'Benutzt für' },
|
||||||
|
'USEDFOR': { node: '#6c757d', edge: '#6c757d', label: 'Benutzt für' },
|
||||||
|
'SYN': { node: '#e91e63', edge: '#e91e63', label: 'Synonym' },
|
||||||
|
'SYNONYM': { node: '#e91e63', edge: '#e91e63', label: 'Synonym' }
|
||||||
|
};
|
||||||
|
|
||||||
|
function createSemanticNetwork(centralTerm, centralId, relations, synonyms) {
|
||||||
|
console.log('🕸️ Erstelle semantisches Netz (2 Ebenen) für:', centralTerm);
|
||||||
|
console.log('📝 Synonyme:', synonyms);
|
||||||
|
|
||||||
|
centralNodeId = centralId;
|
||||||
|
firstLevelNodeIds = [];
|
||||||
|
secondLevelNodeIds = [];
|
||||||
|
secondLevelEdgeIds = [];
|
||||||
|
secondLevelVisible = true;
|
||||||
|
secondLevelLoaded = false;
|
||||||
|
physicsEnabled = true;
|
||||||
|
|
||||||
|
// Buttons zurücksetzen
|
||||||
|
var btn = document.getElementById('btn-level');
|
||||||
|
var btnText = document.getElementById('btn-level-text');
|
||||||
|
if (btn) {
|
||||||
|
btn.classList.remove('btn-warning');
|
||||||
|
btn.classList.add('btn-outline-secondary');
|
||||||
|
}
|
||||||
|
if (btnText) btnText.textContent = '2';
|
||||||
|
|
||||||
|
var btnPhysics = document.getElementById('btn-physics');
|
||||||
|
if (btnPhysics) {
|
||||||
|
btnPhysics.classList.remove('btn-warning');
|
||||||
|
btnPhysics.classList.add('btn-outline-secondary');
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatus('Baue erste Ebene auf...');
|
||||||
|
|
||||||
|
// DataSets erstellen
|
||||||
|
nodesDataSet = new vis.DataSet();
|
||||||
|
edgesDataSet = new vis.DataSet();
|
||||||
|
|
||||||
|
// Zentraler Knoten
|
||||||
|
nodesDataSet.add({
|
||||||
|
id: centralId,
|
||||||
|
label: wrapLabel(centralTerm, 20),
|
||||||
|
shape: 'ellipse',
|
||||||
|
color: {
|
||||||
|
background: '#1a3a5c',
|
||||||
|
border: '#0d1f30',
|
||||||
|
highlight: { background: '#2a5a8c', border: '#1a3a5c' },
|
||||||
|
hover: { background: '#2a5a8c', border: '#1a3a5c' }
|
||||||
|
},
|
||||||
|
font: { color: '#ffffff', size: 14, face: 'Arial', bold: true },
|
||||||
|
size: 40,
|
||||||
|
borderWidth: 3,
|
||||||
|
shadow: true,
|
||||||
|
level: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// Erste Ebene hinzufügen
|
||||||
|
if (relations && relations.length > 0) {
|
||||||
|
relations.forEach(function(rel, index) {
|
||||||
|
var relType = rel.Relationtype || 'RT';
|
||||||
|
var relColor = relationColors[relType] || relationColors['RT'];
|
||||||
|
var relId = rel.IDRelation || ('rel_' + index);
|
||||||
|
var relText = rel.TextRelation || 'Unbekannt';
|
||||||
|
|
||||||
|
// ID für späteres Nachladen speichern
|
||||||
|
if (relId && relId !== centralId) {
|
||||||
|
firstLevelNodeIds.push({
|
||||||
|
id: relId,
|
||||||
|
text: relText,
|
||||||
|
type: relType
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Knoten für die Relation (1. Ebene)
|
||||||
|
nodesDataSet.add({
|
||||||
|
id: relId,
|
||||||
|
label: wrapLabel(relText, 18),
|
||||||
|
shape: 'ellipse',
|
||||||
|
color: {
|
||||||
|
background: relColor.node,
|
||||||
|
border: shadeColor(relColor.node, -20),
|
||||||
|
highlight: { background: shadeColor(relColor.node, 20), border: relColor.node },
|
||||||
|
hover: { background: shadeColor(relColor.node, 20), border: relColor.node }
|
||||||
|
},
|
||||||
|
font: { color: '#ffffff', size: 12, face: 'Arial' },
|
||||||
|
size: 28,
|
||||||
|
borderWidth: 2,
|
||||||
|
shadow: true,
|
||||||
|
title: relType + ': ' + relText + ' (ID: ' + relId + ')',
|
||||||
|
level: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
// Kante zum zentralen Knoten
|
||||||
|
var arrowDirection = getArrowDirection(relType);
|
||||||
|
|
||||||
|
edgesDataSet.add({
|
||||||
|
from: centralId,
|
||||||
|
to: relId,
|
||||||
|
label: relType,
|
||||||
|
color: { color: relColor.edge, highlight: relColor.edge, hover: relColor.edge },
|
||||||
|
width: 2,
|
||||||
|
arrows: arrowDirection,
|
||||||
|
font: { size: 10, color: '#666', strokeWidth: 3, strokeColor: '#ffffff' },
|
||||||
|
smooth: { type: 'curvedCW', roundness: 0.2 }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synonyme hinzufügen (kommaseparierter String)
|
||||||
|
console.log('📝 Synonyme Typ:', typeof synonyms);
|
||||||
|
console.log('📝 Synonyme Wert:', synonyms);
|
||||||
|
console.log('📝 Synonyme Länge:', synonyms ? synonyms.length : 'null/undefined');
|
||||||
|
|
||||||
|
if (synonyms && typeof synonyms === 'string' && synonyms.trim().length > 0) {
|
||||||
|
var synColor = relationColors['SYN'];
|
||||||
|
var synArray = synonyms.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s.length > 0; });
|
||||||
|
|
||||||
|
console.log('📝 Synonym-Array:', synArray);
|
||||||
|
|
||||||
|
synArray.forEach(function(synText, index) {
|
||||||
|
var synId = 'syn_' + index + '_' + synText.replace(/\s+/g, '_');
|
||||||
|
|
||||||
|
console.log('➕ Füge Synonym hinzu:', synText, '→ ID:', synId);
|
||||||
|
|
||||||
|
// Knoten für das Synonym
|
||||||
|
nodesDataSet.add({
|
||||||
|
id: synId,
|
||||||
|
label: wrapLabel(synText, 18),
|
||||||
|
shape: 'ellipse',
|
||||||
|
color: {
|
||||||
|
background: synColor.node,
|
||||||
|
border: shadeColor(synColor.node, -20),
|
||||||
|
highlight: { background: shadeColor(synColor.node, 20), border: synColor.node },
|
||||||
|
hover: { background: shadeColor(synColor.node, 20), border: synColor.node }
|
||||||
|
},
|
||||||
|
font: { color: '#ffffff', size: 12, face: 'Arial' },
|
||||||
|
size: 28,
|
||||||
|
borderWidth: 2,
|
||||||
|
shadow: true,
|
||||||
|
title: 'Synonym: ' + synText,
|
||||||
|
level: 1,
|
||||||
|
isSynonym: true,
|
||||||
|
synonymText: synText
|
||||||
|
});
|
||||||
|
|
||||||
|
// Kante zum zentralen Knoten (bidirektional, keine Pfeile)
|
||||||
|
edgesDataSet.add({
|
||||||
|
from: centralId,
|
||||||
|
to: synId,
|
||||||
|
label: 'SYN',
|
||||||
|
color: { color: synColor.edge, highlight: synColor.edge, hover: synColor.edge },
|
||||||
|
width: 2,
|
||||||
|
arrows: { to: { enabled: false }, from: { enabled: false } },
|
||||||
|
font: { size: 10, color: '#666', strokeWidth: 3, strokeColor: '#ffffff' },
|
||||||
|
smooth: { type: 'curvedCW', roundness: 0.2 },
|
||||||
|
dashes: [5, 5]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('✅ ' + synArray.length + ' Synonyme hinzugefügt');
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ Keine Synonyme vorhanden oder leer');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Netzwerk-Container
|
||||||
|
var container = document.getElementById('network-container');
|
||||||
|
|
||||||
|
// Netzwerk-Daten
|
||||||
|
networkData = {
|
||||||
|
nodes: nodesDataSet,
|
||||||
|
edges: edgesDataSet
|
||||||
|
};
|
||||||
|
|
||||||
|
// Optionen
|
||||||
|
var options = {
|
||||||
|
nodes: {
|
||||||
|
shape: 'ellipse',
|
||||||
|
scaling: { min: 15, max: 40 }
|
||||||
|
},
|
||||||
|
edges: {
|
||||||
|
smooth: { type: 'curvedCW', roundness: 0.2 }
|
||||||
|
},
|
||||||
|
physics: {
|
||||||
|
enabled: true,
|
||||||
|
barnesHut: {
|
||||||
|
gravitationalConstant: -4000,
|
||||||
|
centralGravity: 0.2,
|
||||||
|
springLength: 180,
|
||||||
|
springConstant: 0.03,
|
||||||
|
damping: 0.09
|
||||||
|
},
|
||||||
|
stabilization: {
|
||||||
|
enabled: true,
|
||||||
|
iterations: 300,
|
||||||
|
updateInterval: 25
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interaction: {
|
||||||
|
hover: true,
|
||||||
|
tooltipDelay: 200,
|
||||||
|
zoomView: true,
|
||||||
|
dragView: true
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
improvedLayout: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Netzwerk erstellen
|
||||||
|
network = new vis.Network(container, networkData, options);
|
||||||
|
|
||||||
|
// Klick auf Knoten
|
||||||
|
network.on('click', function(params) {
|
||||||
|
if (params.nodes.length > 0) {
|
||||||
|
var clickedNodeId = params.nodes[0];
|
||||||
|
if (clickedNodeId != centralNodeId) {
|
||||||
|
console.log('🖱️ Knoten geklickt:', clickedNodeId);
|
||||||
|
ShowModalDetails(clickedNodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stabilisierung abgeschlossen - dann zweite Ebene laden
|
||||||
|
network.once('stabilizationIterationsDone', function() {
|
||||||
|
console.log('✅ Erste Ebene stabilisiert');
|
||||||
|
network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||||||
|
|
||||||
|
// Zweite Ebene laden
|
||||||
|
setTimeout(function() {
|
||||||
|
loadSecondLevel();
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSecondLevel() {
|
||||||
|
if (firstLevelNodeIds.length === 0) {
|
||||||
|
updateStatus('Fertig - keine weiteren Relationen');
|
||||||
|
hideStatus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatus('Lade 2. Ebene (0/' + firstLevelNodeIds.length + ')...');
|
||||||
|
|
||||||
|
var loadedCount = 0;
|
||||||
|
var totalCount = firstLevelNodeIds.length;
|
||||||
|
|
||||||
|
// Für jeden Knoten der ersten Ebene die Relationen laden
|
||||||
|
firstLevelNodeIds.forEach(function(nodeInfo, index) {
|
||||||
|
setTimeout(function() {
|
||||||
|
loadRelationsForNode(nodeInfo.id, nodeInfo.text, function() {
|
||||||
|
loadedCount++;
|
||||||
|
updateStatus('Lade 2. Ebene (' + loadedCount + '/' + totalCount + ')...');
|
||||||
|
|
||||||
|
if (loadedCount >= totalCount) {
|
||||||
|
secondLevelLoaded = true;
|
||||||
|
updateStatus('Fertig - ' + nodesDataSet.length + ' Begriffe geladen');
|
||||||
|
|
||||||
|
// Netzwerk neu stabilisieren
|
||||||
|
network.stabilize(100);
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
network.fit({ animation: { duration: 500, easingFunction: 'easeInOutQuad' } });
|
||||||
|
hideStatus();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, index * 100); // Gestaffeltes Laden um Server nicht zu überlasten
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadRelationsForNode(nodeId, nodeText, callback) {
|
||||||
|
$.get("/Thesaurus/ajax/getDetailAuthorityData.php", {
|
||||||
|
authType: 'Subject',
|
||||||
|
offset: 0,
|
||||||
|
id: nodeId,
|
||||||
|
sort: 'Anchor.Text',
|
||||||
|
limit: 1
|
||||||
|
}, function(data, status) {
|
||||||
|
if (status === "success") {
|
||||||
|
try {
|
||||||
|
var metadata = JSON.parse(data);
|
||||||
|
var row = metadata['rows'];
|
||||||
|
|
||||||
|
if (row && row[0] && row[0].Relations) {
|
||||||
|
row[0].Relations.forEach(function(rel) {
|
||||||
|
var relId = rel.IDRelation;
|
||||||
|
var relText = rel.TextRelation;
|
||||||
|
var relType = rel.Relationtype || 'RT';
|
||||||
|
|
||||||
|
// Nicht den zentralen Knoten oder bereits existierende Knoten
|
||||||
|
if (relId && relId != centralNodeId) {
|
||||||
|
var existingNode = nodesDataSet.get(relId);
|
||||||
|
|
||||||
|
if (!existingNode) {
|
||||||
|
// Neuer Knoten der 2. Ebene (kleiner, blasser)
|
||||||
|
var relColor = relationColors[relType] || relationColors['RT'];
|
||||||
|
var fadedColor = fadeColor(relColor.node, 0.6);
|
||||||
|
|
||||||
|
nodesDataSet.add({
|
||||||
|
id: relId,
|
||||||
|
label: wrapLabel(relText, 15),
|
||||||
|
shape: 'ellipse',
|
||||||
|
color: {
|
||||||
|
background: fadedColor,
|
||||||
|
border: shadeColor(fadedColor, -15),
|
||||||
|
highlight: { background: relColor.node, border: shadeColor(relColor.node, -20) },
|
||||||
|
hover: { background: relColor.node, border: shadeColor(relColor.node, -20) }
|
||||||
|
},
|
||||||
|
font: { color: '#ffffff', size: 10, face: 'Arial' },
|
||||||
|
size: 18,
|
||||||
|
borderWidth: 1,
|
||||||
|
shadow: false,
|
||||||
|
title: '2. Ebene - ' + relType + ': ' + relText + ' (ID: ' + relId + ')',
|
||||||
|
level: 2
|
||||||
|
});
|
||||||
|
|
||||||
|
// ID für Toggle speichern
|
||||||
|
secondLevelNodeIds.push(relId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kante hinzufügen (wenn noch nicht vorhanden)
|
||||||
|
var edgeId = nodeId + '_' + relId;
|
||||||
|
var reverseEdgeId = relId + '_' + nodeId;
|
||||||
|
|
||||||
|
if (!edgesDataSet.get(edgeId) && !edgesDataSet.get(reverseEdgeId)) {
|
||||||
|
var relColor = relationColors[relType] || relationColors['RT'];
|
||||||
|
var arrowDirection = getArrowDirection(relType);
|
||||||
|
|
||||||
|
edgesDataSet.add({
|
||||||
|
id: edgeId,
|
||||||
|
from: nodeId,
|
||||||
|
to: relId,
|
||||||
|
color: { color: fadeColor(relColor.edge, 0.5), highlight: relColor.edge, hover: relColor.edge },
|
||||||
|
width: 1,
|
||||||
|
arrows: arrowDirection,
|
||||||
|
dashes: true,
|
||||||
|
smooth: { type: 'curvedCW', roundness: 0.3 }
|
||||||
|
});
|
||||||
|
|
||||||
|
// ID für Toggle speichern
|
||||||
|
secondLevelEdgeIds.push(edgeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('⚠️ Fehler beim Parsen für Node', nodeId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback) callback();
|
||||||
|
}).fail(function() {
|
||||||
|
console.warn('⚠️ AJAX-Fehler für Node', nodeId);
|
||||||
|
if (callback) callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getArrowDirection(relType) {
|
||||||
|
if (relType === 'BT' || relType === 'BROADERTERM') {
|
||||||
|
return { to: { enabled: true, scaleFactor: 1 } };
|
||||||
|
} else if (relType === 'NT' || relType === 'NARROWERTERM') {
|
||||||
|
return { from: { enabled: true, scaleFactor: 1 } };
|
||||||
|
}
|
||||||
|
return { to: { enabled: false }, from: { enabled: false } };
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStatus(text) {
|
||||||
|
$('#status-text').text(text);
|
||||||
|
$('#network-status').show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideStatus() {
|
||||||
|
setTimeout(function() {
|
||||||
|
$('#network-status').fadeOut();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hilfsfunktion: Farbe verblassen
|
||||||
|
function fadeColor(color, opacity) {
|
||||||
|
var R = parseInt(color.substring(1,3), 16);
|
||||||
|
var G = parseInt(color.substring(3,5), 16);
|
||||||
|
var B = parseInt(color.substring(5,7), 16);
|
||||||
|
|
||||||
|
// Mit Weiß mischen für verblassten Effekt
|
||||||
|
R = Math.round(R + (255 - R) * (1 - opacity));
|
||||||
|
G = Math.round(G + (255 - G) * (1 - opacity));
|
||||||
|
B = Math.round(B + (255 - B) * (1 - opacity));
|
||||||
|
|
||||||
|
var RR = R.toString(16).padStart(2, '0');
|
||||||
|
var GG = G.toString(16).padStart(2, '0');
|
||||||
|
var BB = B.toString(16).padStart(2, '0');
|
||||||
|
|
||||||
|
return "#" + RR + GG + BB;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hilfsfunktion: Label umbrechen
|
||||||
|
function wrapLabel(text, maxLength) {
|
||||||
|
if (!text) return '';
|
||||||
|
if (text.length <= maxLength) return text;
|
||||||
|
|
||||||
|
var words = text.split(' ');
|
||||||
|
var lines = [];
|
||||||
|
var currentLine = '';
|
||||||
|
|
||||||
|
words.forEach(function(word) {
|
||||||
|
if ((currentLine + ' ' + word).trim().length <= maxLength) {
|
||||||
|
currentLine = (currentLine + ' ' + word).trim();
|
||||||
|
} else {
|
||||||
|
if (currentLine) lines.push(currentLine);
|
||||||
|
currentLine = word;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (currentLine) lines.push(currentLine);
|
||||||
|
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hilfsfunktion: Farbe aufhellen/abdunkeln
|
||||||
|
function shadeColor(color, percent) {
|
||||||
|
var R = parseInt(color.substring(1,3), 16);
|
||||||
|
var G = parseInt(color.substring(3,5), 16);
|
||||||
|
var B = parseInt(color.substring(5,7), 16);
|
||||||
|
|
||||||
|
R = parseInt(R * (100 + percent) / 100);
|
||||||
|
G = parseInt(G * (100 + percent) / 100);
|
||||||
|
B = parseInt(B * (100 + percent) / 100);
|
||||||
|
|
||||||
|
R = (R < 255) ? R : 255;
|
||||||
|
G = (G < 255) ? G : 255;
|
||||||
|
B = (B < 255) ? B : 255;
|
||||||
|
R = (R > 0) ? R : 0;
|
||||||
|
G = (G > 0) ? G : 0;
|
||||||
|
B = (B > 0) ? B : 0;
|
||||||
|
|
||||||
|
var RR = ((R.toString(16).length == 1) ? "0" + R.toString(16) : R.toString(16));
|
||||||
|
var GG = ((G.toString(16).length == 1) ? "0" + G.toString(16) : G.toString(16));
|
||||||
|
var BB = ((B.toString(16).length == 1) ? "0" + B.toString(16) : B.toString(16));
|
||||||
|
|
||||||
|
return "#" + RR + GG + BB;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Netzwerk-Steuerung
|
||||||
|
function networkFit() {
|
||||||
|
if (network) {
|
||||||
|
network.fit({
|
||||||
|
animation: { duration: 500, easingFunction: 'easeInOutQuad' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function networkZoomIn() {
|
||||||
|
if (network) {
|
||||||
|
var scale = network.getScale() * 1.3;
|
||||||
|
network.moveTo({ scale: scale, animation: { duration: 300 } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function networkZoomOut() {
|
||||||
|
if (network) {
|
||||||
|
var scale = network.getScale() / 1.3;
|
||||||
|
network.moveTo({ scale: scale, animation: { duration: 300 } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSecondLevel() {
|
||||||
|
if (!network || !secondLevelLoaded) {
|
||||||
|
console.log('⚠️ 2. Ebene noch nicht geladen');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
secondLevelVisible = !secondLevelVisible;
|
||||||
|
|
||||||
|
var btn = document.getElementById('btn-level');
|
||||||
|
var btnText = document.getElementById('btn-level-text');
|
||||||
|
|
||||||
|
if (secondLevelVisible) {
|
||||||
|
// 2. Ebene einblenden
|
||||||
|
secondLevelNodeIds.forEach(function(nodeId) {
|
||||||
|
nodesDataSet.update({ id: nodeId, hidden: false });
|
||||||
|
});
|
||||||
|
secondLevelEdgeIds.forEach(function(edgeId) {
|
||||||
|
edgesDataSet.update({ id: edgeId, hidden: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
btn.classList.remove('btn-warning');
|
||||||
|
btn.classList.add('btn-outline-secondary');
|
||||||
|
btnText.textContent = '2';
|
||||||
|
|
||||||
|
console.log('✅ 2. Ebene eingeblendet');
|
||||||
|
} else {
|
||||||
|
// 2. Ebene ausblenden
|
||||||
|
secondLevelNodeIds.forEach(function(nodeId) {
|
||||||
|
nodesDataSet.update({ id: nodeId, hidden: true });
|
||||||
|
});
|
||||||
|
secondLevelEdgeIds.forEach(function(edgeId) {
|
||||||
|
edgesDataSet.update({ id: edgeId, hidden: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
btn.classList.remove('btn-outline-secondary');
|
||||||
|
btn.classList.add('btn-warning');
|
||||||
|
btnText.textContent = '1';
|
||||||
|
|
||||||
|
console.log('✅ 2. Ebene ausgeblendet');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ansicht anpassen
|
||||||
|
setTimeout(function() {
|
||||||
|
network.fit({ animation: { duration: 300, easingFunction: 'easeInOutQuad' } });
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePhysics() {
|
||||||
|
if (!network) return;
|
||||||
|
|
||||||
|
physicsEnabled = !physicsEnabled;
|
||||||
|
network.setOptions({ physics: { enabled: physicsEnabled } });
|
||||||
|
|
||||||
|
var btn = document.getElementById('btn-physics');
|
||||||
|
if (physicsEnabled) {
|
||||||
|
btn.classList.remove('btn-warning');
|
||||||
|
btn.classList.add('btn-outline-secondary');
|
||||||
|
console.log('✅ Physik aktiviert');
|
||||||
|
} else {
|
||||||
|
btn.classList.remove('btn-outline-secondary');
|
||||||
|
btn.classList.add('btn-warning');
|
||||||
|
console.log('✅ Physik deaktiviert - Knoten können manuell verschoben werden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
2
templates/SubjectsModal.tpl
Normal file
2
templates/SubjectsModal.tpl
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<!-- Modal - Subjects Modal (für weitere Funktionen) -->
|
||||||
|
<!-- Dieses Template kann bei Bedarf erweitert werden -->
|
||||||
111
templates/newClassification.tpl
Normal file
111
templates/newClassification.tpl
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<!-- Modal - Neue Klassifikation -->
|
||||||
|
<div class="modal fade" id="NewClassificationModal" tabindex="-1" aria-labelledby="NewClassificationModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg" style="max-width: 1024px;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="NewClassificationModalHeadline">
|
||||||
|
<i class="fas fa-folder-tree"></i> Klassifikation
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<!-- Alert für Meldungen -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div id="errorNewClassification" class="alert" style="display: none;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Formular -->
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="new_notation">Notation</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<input type="text" id="new_notation" class="form-control" placeholder="z.B. 1.2.3" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="new_term">Bezeichnung</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" id="new_term" class="form-control" />
|
||||||
|
<input type="hidden" id="new_id" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label" for="new_scopenote">Anmerkung</label>
|
||||||
|
<textarea class="form-control" id="new_scopenote" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Relationen-Bereich -->
|
||||||
|
<div id="new_relation_label" style="display: none;">
|
||||||
|
<hr>
|
||||||
|
<h5 class="mb-3"><i class="fas fa-link"></i> Relationen</h5>
|
||||||
|
|
||||||
|
<!-- Neue Relation hinzufügen -->
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="new_relationtype">Relationstyp</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<select class="form-select" id="new_relationtype">
|
||||||
|
<option value="BT">BT - Oberbegriff</option>
|
||||||
|
<option value="NT">NT - Unterbegriff</option>
|
||||||
|
<option value="RT">RT - Verwandter Begriff</option>
|
||||||
|
<option value="USE">USE - Benutze</option>
|
||||||
|
<option value="UF">UF - Benutzt für</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="search_relation">Relation suchen</label>
|
||||||
|
<div class="col-sm-7">
|
||||||
|
<input type="text" id="search_relation" class="form-control" placeholder="Klassifikation eingeben..." />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<button type="button" class="btn btn-success w-100" id="classification-anlegen">
|
||||||
|
<i class="fas fa-plus"></i> Hinzufügen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Vorhandene Relationen -->
|
||||||
|
<div id="relation_table" style="display: none;">
|
||||||
|
<hr>
|
||||||
|
<h6><i class="fas fa-link me-1" style="color: #17a2b8;"></i> Vorhandene Relationen</h6>
|
||||||
|
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
|
||||||
|
<table class="table table-sm table-hover table-bordered mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 80px;">Typ</th>
|
||||||
|
<th>Begriff</th>
|
||||||
|
<th style="width: 80px;" class="text-end">Aktion</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="new_modal_content">
|
||||||
|
<!-- Relationen werden hier per AJAX eingefügt -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button id="newClassificationSave" type="button" class="btn btn-success" title="New Classification" onclick="CreateNewEntry('Classification')">
|
||||||
|
<i class="fas fa-save"></i> speichern
|
||||||
|
</button>
|
||||||
|
<button id="ClassificationModifySave" type="button" class="btn btn-success" style="display: none;" title="Modify Classification" onclick="UpdateEntry('Classification')">
|
||||||
|
<i class="fas fa-sync"></i> aktualisieren
|
||||||
|
</button>
|
||||||
|
<button id="newClassificationDismiss" type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times"></i> schliessen
|
||||||
|
</button>
|
||||||
|
<button id="newRelationClassificationDismiss" type="button" class="btn btn-secondary" style="display: none;" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times"></i> schliessen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
115
templates/newCorporate.tpl
Normal file
115
templates/newCorporate.tpl
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<!-- Modal - Neue Körperschaft -->
|
||||||
|
<div class="modal fade" id="NewCorporateModal" tabindex="-1" aria-labelledby="NewCorporateModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg" style="max-width: 1024px;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="NewCorporateModalHeadline">
|
||||||
|
<i class="fas fa-building"></i> Körperschaft
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<!-- Alert für Meldungen -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div id="errorNewCorporate" class="alert" style="display: none;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Formular -->
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="new_term">Name</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" id="new_term" class="form-control" />
|
||||||
|
<input type="hidden" id="new_id" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="new_type">Type</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<select class="form-select" id="new_type">
|
||||||
|
<option value="Corporates">Körperschaft</option>
|
||||||
|
<option value="Conference">Konferenz</option>
|
||||||
|
<option value="Project">Projekt</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label" for="new_scopenote">Anmerkung</label>
|
||||||
|
<textarea class="form-control" id="new_scopenote" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Relationen-Bereich -->
|
||||||
|
<div id="new_relation_label" style="display: none;">
|
||||||
|
<hr>
|
||||||
|
<h5 class="mb-3"><i class="fas fa-link"></i> Relationen</h5>
|
||||||
|
|
||||||
|
<!-- Neue Relation hinzufügen -->
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="new_relationtype">Relationstyp</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<select class="form-select" id="new_relationtype">
|
||||||
|
<option value="USEDEARLIER">Früher verwendet</option>
|
||||||
|
<option value="USEDLATER">Später verwendet</option>
|
||||||
|
<option value="ALTERNATIVENAME">Alternativer Name</option>
|
||||||
|
<option value="USE">Benutze</option>
|
||||||
|
<option value="USEDFOR">Benutzt für</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="search_relation">Relation suchen</label>
|
||||||
|
<div class="col-sm-7">
|
||||||
|
<input type="text" id="search_relation" class="form-control" placeholder="Körperschaft eingeben..." />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<button type="button" class="btn btn-success w-100" id="corporate-anlegen">
|
||||||
|
<i class="fas fa-plus"></i> Hinzufügen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Vorhandene Relationen -->
|
||||||
|
<div id="relation_table" style="display: none;">
|
||||||
|
<hr>
|
||||||
|
<h6><i class="fas fa-link me-1" style="color: #17a2b8;"></i> Vorhandene Relationen</h6>
|
||||||
|
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
|
||||||
|
<table class="table table-sm table-hover table-bordered mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 80px;">Typ</th>
|
||||||
|
<th>Begriff</th>
|
||||||
|
<th style="width: 80px;" class="text-end">Aktion</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="new_modal_content">
|
||||||
|
<!-- Relationen werden hier per AJAX eingefügt -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button id="newCorporateSave" type="button" class="btn btn-success" title="New Corporate" onclick="CreateNewEntry('Corporates')">
|
||||||
|
<i class="fas fa-save"></i> speichern
|
||||||
|
</button>
|
||||||
|
<button id="CorporateModifySave" type="button" class="btn btn-success" style="display: none;" title="Modify Corporate" onclick="UpdateEntry('Corporate')">
|
||||||
|
<i class="fas fa-sync"></i> aktualisieren
|
||||||
|
</button>
|
||||||
|
<button id="newCorporateDismiss" type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times"></i> schliessen
|
||||||
|
</button>
|
||||||
|
<button id="newRelationCorporateDismiss" type="button" class="btn btn-secondary" style="display: none;" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times"></i> schliessen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
103
templates/newPerson.tpl
Normal file
103
templates/newPerson.tpl
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<!-- Modal - Neue Person -->
|
||||||
|
<div class="modal fade" id="NewSubjectModal" tabindex="-1" aria-labelledby="NewSubjectModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg" style="max-width: 1024px;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="NewSubjectModalHeadline">
|
||||||
|
<i class="fas fa-user"></i> Person
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<!-- Alert für Meldungen -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div id="errorNewSubject" class="alert" style="display: none;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Formular -->
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="new_term">Name</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" id="new_term" class="form-control" />
|
||||||
|
<input type="hidden" id="new_id" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label" for="new_scopenote">Anmerkung</label>
|
||||||
|
<textarea class="form-control" id="new_scopenote" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Relationen-Bereich -->
|
||||||
|
<div id="new_relation_label" style="display: none;">
|
||||||
|
<hr>
|
||||||
|
<h5 class="mb-3"><i class="fas fa-link"></i> Relationen</h5>
|
||||||
|
|
||||||
|
<!-- Neue Relation hinzufügen -->
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="new_relationtype">Relationstyp</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<select class="form-select" id="new_relationtype">
|
||||||
|
<option value="FORMERNAME">Früherer Name</option>
|
||||||
|
<option value="LATERNAME">Späterer Name</option>
|
||||||
|
<option value="USE">Benutze</option>
|
||||||
|
<option value="USEDFOR">Benutzt für</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="search_relation">Relation suchen</label>
|
||||||
|
<div class="col-sm-7">
|
||||||
|
<input type="text" id="search_relation" class="form-control" placeholder="Name eingeben..." />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<button type="button" class="btn btn-success w-100" id="subject-anlegen">
|
||||||
|
<i class="fas fa-plus"></i> Hinzufügen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Vorhandene Relationen -->
|
||||||
|
<div id="relation_table" style="display: none;">
|
||||||
|
<hr>
|
||||||
|
<h6><i class="fas fa-link me-1" style="color: #17a2b8;"></i> Vorhandene Relationen</h6>
|
||||||
|
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
|
||||||
|
<table class="table table-sm table-hover table-bordered mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 80px;">Typ</th>
|
||||||
|
<th>Begriff</th>
|
||||||
|
<th style="width: 80px;" class="text-end">Aktion</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="new_modal_content">
|
||||||
|
<!-- Relationen werden hier per AJAX eingefügt -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button id="newSubjectSave" type="button" class="btn btn-success" title="New Person" onclick="CreateNewEntry('Person')">
|
||||||
|
<i class="fas fa-save"></i> speichern
|
||||||
|
</button>
|
||||||
|
<button id="SubjectModifySave" type="button" class="btn btn-success" style="display: none;" title="Modify Person" onclick="UpdateEntry('Person')">
|
||||||
|
<i class="fas fa-sync"></i> aktualisieren
|
||||||
|
</button>
|
||||||
|
<button id="newSubjectDismiss" type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times"></i> schliessen
|
||||||
|
</button>
|
||||||
|
<button id="newRelationSubjectDismiss" type="button" class="btn btn-secondary" style="display: none;" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times"></i> schliessen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
114
templates/newPublisher.tpl
Normal file
114
templates/newPublisher.tpl
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<!-- Modal - Neuer Verlag -->
|
||||||
|
<div class="modal fade" id="NewPublisherModal" tabindex="-1" aria-labelledby="NewPublisherModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg" style="max-width: 1024px;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="NewPublisherModalHeadline">
|
||||||
|
<i class="fas fa-book"></i> Verlag
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<!-- Alert für Meldungen -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div id="errorNewPublisher" class="alert" style="display: none;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Formular -->
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="new_term">Name</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" id="new_term" class="form-control" />
|
||||||
|
<input type="hidden" id="new_id" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="new_type">Type</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<select class="form-select" id="new_type">
|
||||||
|
<option value="Publisher">Verlag</option>
|
||||||
|
<option value="Imprint">Imprint</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label" for="new_scopenote">Anmerkung</label>
|
||||||
|
<textarea class="form-control" id="new_scopenote" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Relationen-Bereich -->
|
||||||
|
<div id="new_relation_label" style="display: none;">
|
||||||
|
<hr>
|
||||||
|
<h5 class="mb-3"><i class="fas fa-link"></i> Relationen</h5>
|
||||||
|
|
||||||
|
<!-- Neue Relation hinzufügen -->
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="new_relationtype">Relationstyp</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<select class="form-select" id="new_relationtype">
|
||||||
|
<option value="USEDEARLIER">Früher verwendet</option>
|
||||||
|
<option value="USEDLATER">Später verwendet</option>
|
||||||
|
<option value="ALTERNATIVENAME">Alternativer Name</option>
|
||||||
|
<option value="USE">Benutze</option>
|
||||||
|
<option value="USEDFOR">Benutzt für</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="search_relation">Relation suchen</label>
|
||||||
|
<div class="col-sm-7">
|
||||||
|
<input type="text" id="search_relation" class="form-control" placeholder="Verlag eingeben..." />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<button type="button" class="btn btn-success w-100" id="publisher-anlegen">
|
||||||
|
<i class="fas fa-plus"></i> Hinzufügen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Vorhandene Relationen -->
|
||||||
|
<div id="relation_table" style="display: none;">
|
||||||
|
<hr>
|
||||||
|
<h6><i class="fas fa-link me-1" style="color: #17a2b8;"></i> Vorhandene Relationen</h6>
|
||||||
|
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
|
||||||
|
<table class="table table-sm table-hover table-bordered mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 80px;">Typ</th>
|
||||||
|
<th>Begriff</th>
|
||||||
|
<th style="width: 80px;" class="text-end">Aktion</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="new_modal_content">
|
||||||
|
<!-- Relationen werden hier per AJAX eingefügt -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button id="newPublisherSave" type="button" class="btn btn-success" title="New Publisher" onclick="CreateNewEntry('Publisher')">
|
||||||
|
<i class="fas fa-save"></i> speichern
|
||||||
|
</button>
|
||||||
|
<button id="PublisherModifySave" type="button" class="btn btn-success" style="display: none;" title="Modify Publisher" onclick="UpdateEntry('Publisher')">
|
||||||
|
<i class="fas fa-sync"></i> aktualisieren
|
||||||
|
</button>
|
||||||
|
<button id="newPublisherDismiss" type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times"></i> schliessen
|
||||||
|
</button>
|
||||||
|
<button id="newRelationPublisherDismiss" type="button" class="btn btn-secondary" style="display: none;" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times"></i> schliessen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
157
templates/newSubject.tpl
Normal file
157
templates/newSubject.tpl
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
<!-- Modal - Neues Schlagwort -->
|
||||||
|
<div class="modal fade" id="NewSubjectModal" tabindex="-1" aria-labelledby="NewSubjectModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg" style="max-width: 1024px;">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="NewSubjectModalHeadline">
|
||||||
|
<i class="fas fa-tag"></i> Schlagwort
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<!-- Alert für Meldungen -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div id="errorNewSubject" class="alert" style="display: none;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Formular -->
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="new_term">Schlagwort</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" id="new_term" class="form-control" />
|
||||||
|
<input type="hidden" id="new_id" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="new_type">Type</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<select class="form-select" id="new_type">
|
||||||
|
<option value="Subject">Schlagwort</option>
|
||||||
|
<option value="Person">Personenname</option>
|
||||||
|
<option value="Corporates">Körperschaft</option>
|
||||||
|
<option value="Publisher">Verlag</option>
|
||||||
|
<option value="Classification">Klassifikation</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="new_detailtype">Detailtype</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<select class="form-select" id="new_detailtype">
|
||||||
|
<option value="Sachschlagwort">Sachschlagwort</option>
|
||||||
|
<option value="geografisches Schlagwort">geografisches Schlagwort</option>
|
||||||
|
<option value="Formschlagwort">Formschlagwort</option>
|
||||||
|
<option value="Zeitschlagwort">Zeitschlagwort</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="new_classification">Klassifikation</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" id="new_classification" class="form-control" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label class="form-label" for="new_scopenote">Scopenote</label>
|
||||||
|
<textarea class="form-control" id="new_scopenote" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Relationen-Bereich -->
|
||||||
|
<div id="new_relation_label" style="display: none;">
|
||||||
|
<hr>
|
||||||
|
<h5 class="mb-3"><i class="fas fa-link"></i> Relationen</h5>
|
||||||
|
|
||||||
|
<!-- Neue Relation hinzufügen -->
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="new_relationtype">Relationstyp</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<select class="form-select" id="new_relationtype">
|
||||||
|
<option value="BT">BT - Oberbegriff</option>
|
||||||
|
<option value="NT">NT - Unterbegriff</option>
|
||||||
|
<option value="RT">RT - Verwandter Begriff</option>
|
||||||
|
<option value="USE">USE - Benutze</option>
|
||||||
|
<option value="UF">UF - Benutzt für</option>
|
||||||
|
<option value="SYN">SYN - Synonym</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label" for="search_relation">Relation suchen</label>
|
||||||
|
<div class="col-sm-7">
|
||||||
|
<input type="text" id="search_relation" class="form-control" placeholder="Begriff eingeben..." />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<button type="button" class="btn btn-success w-100" id="subject-anlegen">
|
||||||
|
<i class="fas fa-plus"></i> Hinzufügen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Vorhandene Relationen -->
|
||||||
|
<div id="relation_table" style="display: none;">
|
||||||
|
<hr>
|
||||||
|
<h6><i class="fas fa-link me-1" style="color: #17a2b8;"></i> Vorhandene Relationen</h6>
|
||||||
|
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
|
||||||
|
<table class="table table-sm table-hover table-bordered mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 80px;">Typ</th>
|
||||||
|
<th>Begriff</th>
|
||||||
|
<th style="width: 80px;" class="text-end">Aktion</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="new_modal_content">
|
||||||
|
<!-- Relationen werden hier per AJAX eingefügt -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Synonyme-Bereich -->
|
||||||
|
<div id="synonyms_section" style="display: none; margin-top: 20px;">
|
||||||
|
<hr>
|
||||||
|
<h6><i class="fas fa-equals me-1" style="color: #e91e63;"></i> Synonyme</h6>
|
||||||
|
<div class="table-responsive" style="border: 1px solid #dee2e6; border-radius: 0.375rem; overflow: hidden;">
|
||||||
|
<table class="table table-sm table-hover table-bordered mb-0" id="synonyms_table">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 80px;">Typ</th>
|
||||||
|
<th>Begriff</th>
|
||||||
|
<th style="width: 80px;" class="text-end">Aktion</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="synonyms_body">
|
||||||
|
<!-- Synonyme werden hier per AJAX eingefügt -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button id="newSubjectSave" type="button" class="btn btn-success" title="New Subject" onclick="CreateNewEntry('Subject')">
|
||||||
|
<i class="fas fa-save"></i> speichern
|
||||||
|
</button>
|
||||||
|
<button id="SubjectModifySave" type="button" class="btn btn-success" style="display: none;" title="Modify Subject" onclick="UpdateEntry('Subject')">
|
||||||
|
<i class="fas fa-sync"></i> aktualisieren
|
||||||
|
</button>
|
||||||
|
<button id="newSubjectDismiss" type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times"></i> schliessen
|
||||||
|
</button>
|
||||||
|
<button id="newRelationSubjectDismiss" type="button" class="btn btn-secondary" style="display: none;" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times"></i> schliessen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user