-Feature: Ab sofort werden alle Configdateien über eine Example.config.php definiert. über das Dashboard kann nun über den neuen Punkt "Config" die eigentliche config.php bearbeitet werden. Änderungen durch Programmupdates werden jetzt automatisch in der example.config.php definiert und beim nächsten Speichern der config-datei über die Website angepasst.
-Feature: Scrollen der Listen können einzeln abeschalten werden
This commit is contained in:
@@ -1,113 +1,250 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
require('config.php');
|
require('config.php');
|
||||||
|
|
||||||
require('vendor/autoload.php');
|
require('vendor/autoload.php');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
|
<title>Aufgabenmonitor</title>
|
||||||
<title>Aufgabenmonitor</title>
|
|
||||||
|
|
||||||
<!-- Bootstrap -->
|
<!-- Bootstrap -->
|
||||||
<link rel="stylesheet" href="vendor/twbs/bootstrap/dist/css/bootstrap.min.css" >
|
<link rel="stylesheet" href="vendor/twbs/bootstrap/dist/css/bootstrap.min.css">
|
||||||
<script src="scripts/jquery-3.5.1.min.js"></script>
|
<link href="css/sticky-footer.css" rel="stylesheet">
|
||||||
<link href="css/sticky-footer.css" rel="stylesheet">
|
|
||||||
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
|
||||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
|
||||||
<!--[if lt IE 9]>
|
|
||||||
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
|
|
||||||
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
|
||||||
<![endif]-->
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(document).ready(function(){
|
|
||||||
refreshAufgabenTable();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
function refreshAufgabenTable(){
|
<style>
|
||||||
$('#AufgabenTableHolder').load('sources/getAufgabenTable.php', function(){
|
body { background-color: #000; }
|
||||||
setTimeout(refreshAufgabenTable, 30000);
|
.tableFixHead { overflow:auto; }
|
||||||
});
|
.tableFixHead thead th {
|
||||||
}
|
position: sticky;
|
||||||
// function refreshCheckInTable(){
|
top: 0;
|
||||||
// $('#getCheckInTableHolder').load('sources/getCheckInTable.php', function(){
|
z-index: 1;
|
||||||
// setTimeout(refreshCheckInTable, 5000);
|
background-color: #212529; /* .table-dark head */
|
||||||
// });
|
}
|
||||||
// }
|
.table-dark td, .table-dark th { vertical-align: middle; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body style="background-color: black;">
|
|
||||||
|
|
||||||
<div class="container-fluid">
|
<script src="scripts/jquery-3.5.1.min.js"></script>
|
||||||
<div class="row">
|
</head>
|
||||||
<div class="col-lg">
|
<body>
|
||||||
<h2 class="text-light">Aufgaben</h2>
|
|
||||||
<table class="table table-dark">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">#</th>
|
|
||||||
<th scope="col">Bearbeiter</th>
|
|
||||||
<th scope="col">Aufgabe</th>
|
|
||||||
<th scope="col">Zieldatum</th>
|
|
||||||
<th scope="col">Priorität</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="AufgabenTableHolder">
|
|
||||||
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</header>
|
|
||||||
|
|
||||||
|
<div class="container-fluid py-3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<h2 class="text-light mb-3">Aufgaben</h2>
|
||||||
|
|
||||||
|
<!-- SCROLL CONTAINER -->
|
||||||
|
<div id="aufgaben-scroll" class="tableFixHead">
|
||||||
|
<table id="aufgaben-table" class="table table-dark table-striped table-hover mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" style="width:6%">#</th>
|
||||||
|
<th scope="col" style="width:24%">Bearbeiter</th>
|
||||||
|
<th scope="col" style="width:40%">Aufgabe</th>
|
||||||
|
<th scope="col" style="width:15%">Zieldatum</th>
|
||||||
|
<th scope="col" style="width:15%">Priorität</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="AufgabenTableHolder">
|
||||||
|
<!-- wird via AJAX gefüllt -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- /SCROLL CONTAINER -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Bootstrap JS -->
|
||||||
|
<script src="vendor/twbs/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
|
<!-- === Scroll/Reload Script (wie bei CheckIn/CheckOut) === -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
/* ===========================
|
||||||
|
Feature-Toggle aus PHP
|
||||||
|
=========================== */
|
||||||
|
const SCROLL_FLAGS = {
|
||||||
|
aufgaben: <?php echo (defined('EnableScrollingAufgaben') && EnableScrollingAufgaben) ? 'true' : 'false'; ?>
|
||||||
|
};
|
||||||
|
|
||||||
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
|
function isEnabled() { return !!SCROLL_FLAGS.aufgaben; }
|
||||||
<script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>
|
|
||||||
<!-- Include all compiled plugins (below), or include individual files as needed -->
|
/* ===========================
|
||||||
<script src="vendor/twbs/bootstrap/dist/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
|
Höhe der Scroll-Container
|
||||||
</body>
|
- Voller Viewport ab Oberkante
|
||||||
|
=========================== */
|
||||||
|
function sizeScrollContainers() {
|
||||||
|
const node = document.querySelector('#aufgaben-scroll');
|
||||||
|
if (!node) return;
|
||||||
|
const rect = node.getBoundingClientRect();
|
||||||
|
const bottomMargin = 16;
|
||||||
|
const minH = 120;
|
||||||
|
const available = window.innerHeight - rect.top - bottomMargin;
|
||||||
|
node.style.maxHeight = Math.max(minH, available) + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Scroll-/Interaktionsstatus
|
||||||
|
=========================== */
|
||||||
|
const scrollState = new WeakMap(); // { userActive:boolean, autoTimer:number|null, loopHeight:number }
|
||||||
|
|
||||||
|
function ensureState(el) {
|
||||||
|
if (!scrollState.get(el)) scrollState.set(el, { userActive: false, autoTimer: null, loopHeight: 0 });
|
||||||
|
return scrollState.get(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachScrollGuards($scroller) {
|
||||||
|
const el = $scroller.get(0);
|
||||||
|
if (!el || el.__guardsBound) return;
|
||||||
|
if (!isEnabled()) return;
|
||||||
|
const state = ensureState(el);
|
||||||
|
|
||||||
|
const markActive = () => {
|
||||||
|
state.userActive = true;
|
||||||
|
clearTimeout(state._quietT);
|
||||||
|
state._quietT = setTimeout(() => { state.userActive = false; }, 800);
|
||||||
|
stopAutoScroll($scroller);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scroller.on('wheel touchstart touchmove keydown mousedown mouseenter', markActive);
|
||||||
|
$scroller.on('mouseleave', () => setTimeout(() => maybeStartAutoScroll($scroller), 600));
|
||||||
|
el.__guardsBound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Nahtloser Loop (Clone)
|
||||||
|
=========================== */
|
||||||
|
function buildSeamlessLoop($table, $scroller) {
|
||||||
|
$table.find('tbody.__loopClone').remove();
|
||||||
|
|
||||||
|
const scEl = $scroller.get(0);
|
||||||
|
const st = ensureState(scEl);
|
||||||
|
st.loopHeight = 0;
|
||||||
|
|
||||||
|
if (!isEnabled()) return;
|
||||||
|
|
||||||
|
const $main = $table.find('tbody').first();
|
||||||
|
if ($main.length === 0 || $main.children().length === 0) return;
|
||||||
|
|
||||||
|
const needScroll = $main.get(0).offsetHeight > scEl.clientHeight + 1;
|
||||||
|
if (!needScroll) return;
|
||||||
|
|
||||||
|
const $clone = $main.clone(false, false).addClass('__loopClone').attr('aria-hidden', 'true');
|
||||||
|
$table.append($clone);
|
||||||
|
st.loopHeight = $main.get(0).offsetHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Auto-Scroll
|
||||||
|
=========================== */
|
||||||
|
function startAutoScroll($scroller, speedPx = 1, stepMs = 40) {
|
||||||
|
const el = $scroller.get(0);
|
||||||
|
if (!isEnabled()) return;
|
||||||
|
|
||||||
|
const st = ensureState(el);
|
||||||
|
if (st.autoTimer) return;
|
||||||
|
if (st.loopHeight <= 0) return;
|
||||||
|
|
||||||
|
const tick = () => {
|
||||||
|
if (st.userActive) { stopAutoScroll($scroller); return; }
|
||||||
|
el.scrollTop += speedPx;
|
||||||
|
if (el.scrollTop >= st.loopHeight) el.scrollTop -= st.loopHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
st.autoTimer = setInterval(tick, stepMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopAutoScroll($scroller) {
|
||||||
|
const el = $scroller.get(0);
|
||||||
|
const st = ensureState(el);
|
||||||
|
if (st.autoTimer) {
|
||||||
|
clearInterval(st.autoTimer);
|
||||||
|
st.autoTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeStartAutoScroll($scroller) {
|
||||||
|
const el = $scroller.get(0);
|
||||||
|
if (!isEnabled()) { stopAutoScroll($scroller); return; }
|
||||||
|
const st = ensureState(el);
|
||||||
|
if (st.userActive) return;
|
||||||
|
if (st.loopHeight > 0) startAutoScroll($scroller, 1, 80);
|
||||||
|
else stopAutoScroll($scroller);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Smart AJAX Reload
|
||||||
|
=========================== */
|
||||||
|
function smartLoad($scroller, $table, $target, url, intervalMs) {
|
||||||
|
const scEl = $scroller.get(0);
|
||||||
|
const st = ensureState(scEl);
|
||||||
|
|
||||||
|
const oldLoop = st.loopHeight > 0 ? st.loopHeight : Math.max(1, scEl.scrollHeight - scEl.clientHeight);
|
||||||
|
const posInLoop = st.loopHeight > 0 ? (scEl.scrollTop % oldLoop) : scEl.scrollTop;
|
||||||
|
const posRatio = Math.min(1, posInLoop / oldLoop);
|
||||||
|
|
||||||
|
$target.load(url, function () {
|
||||||
|
buildSeamlessLoop($table, $scroller);
|
||||||
|
|
||||||
|
if (st.loopHeight > 0 && isEnabled()) {
|
||||||
|
const newPos = Math.floor(posRatio * st.loopHeight);
|
||||||
|
if (Math.abs(scEl.scrollTop - newPos) > 1) {
|
||||||
|
requestAnimationFrame(() => { scEl.scrollTop = newPos; });
|
||||||
|
}
|
||||||
|
stopAutoScroll($scroller);
|
||||||
|
startAutoScroll($scroller, 1, 80);
|
||||||
|
} else {
|
||||||
|
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
|
||||||
|
stopAutoScroll($scroller);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => smartLoad($scroller, $table, $target, url, intervalMs), intervalMs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Initialisierung
|
||||||
|
=========================== */
|
||||||
|
$(document).ready(function () {
|
||||||
|
sizeScrollContainers();
|
||||||
|
$(window).on('resize', function () {
|
||||||
|
sizeScrollContainers();
|
||||||
|
const $scroller = $('#aufgaben-scroll');
|
||||||
|
const $table = $('#aufgaben-table');
|
||||||
|
buildSeamlessLoop($table, $scroller);
|
||||||
|
maybeStartAutoScroll($scroller);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Guards nur aktivieren, wenn Scrolling erlaubt
|
||||||
|
if (isEnabled()) attachScrollGuards($('#aufgaben-scroll'));
|
||||||
|
|
||||||
|
// Erstladen
|
||||||
|
(function init() {
|
||||||
|
const $scroller = $('#aufgaben-scroll');
|
||||||
|
const $table = $('#aufgaben-table');
|
||||||
|
const $target = $('#AufgabenTableHolder');
|
||||||
|
const url = 'sources/getAufgabenTable.php';
|
||||||
|
const interval = 30000;
|
||||||
|
|
||||||
|
$target.load(url, function () {
|
||||||
|
buildSeamlessLoop($table, $scroller);
|
||||||
|
maybeStartAutoScroll($scroller);
|
||||||
|
setTimeout(() => smartLoad($scroller, $table, $target, url, interval), interval);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
|
// (Optional – hier nicht benötigt, aber gelassen falls du später etwas anzeigst)
|
||||||
function getTimeFromSeconds(string $timestring) {
|
function getTimeFromSeconds(string $timestring) {
|
||||||
|
$hours = floor($timestring / 3600);
|
||||||
$hours = floor($timestring / 3600);
|
$mins = floor($timestring / 60 % 60);
|
||||||
$mins = floor($timestring / 60 % 60);
|
$timeFormat = sprintf('%02d:%02d', $hours, $mins);
|
||||||
$secs = floor($timestring % 60);
|
return $timeFormat;
|
||||||
|
|
||||||
$timeFormat = sprintf('%02d:%02d', $hours, $mins);
|
|
||||||
|
|
||||||
return $timeFormat;
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -23,15 +23,71 @@ $Epi = new Epirent();
|
|||||||
<script src="https://kit.fontawesome.com/93d71de8bc.js" crossorigin="anonymous"></script>
|
<script src="https://kit.fontawesome.com/93d71de8bc.js" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
// === Höhe der Scroll-Container bis Viewport-Ende anpassen ===
|
// === PHP-Flags in JS bringen ===
|
||||||
|
const SCROLL_FLAGS = {
|
||||||
|
checkout: <?php echo (defined('EnableScrollingCheckOut') && EnableScrollingCheckOut) ? 'true' : 'false'; ?>,
|
||||||
|
checkin: <?php echo (defined('EnableScrollingCheckIn') && EnableScrollingCheckIn) ? 'true' : 'false'; ?>,
|
||||||
|
aufgaben: <?php echo (defined('EnableScrollingAufgaben') && EnableScrollingAufgaben) ? 'true' : 'false'; ?>
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mapping: DOM-ID -> Flag-Key
|
||||||
|
const SCROLLER_KEYS = new Map([
|
||||||
|
['checkout-scroll', 'checkout'],
|
||||||
|
['checkin-scroll', 'checkin'],
|
||||||
|
['aufgaben-scroll', 'aufgaben']
|
||||||
|
]);
|
||||||
|
const TABLE_KEYS = new Map([
|
||||||
|
['checkout-table', 'checkout'],
|
||||||
|
['checkin-table', 'checkin'],
|
||||||
|
['aufgaben-table', 'aufgaben']
|
||||||
|
]);
|
||||||
|
|
||||||
|
function keyForEl(el, map) {
|
||||||
|
if (!el || !el.id) return null;
|
||||||
|
return map.get(el.id) || null;
|
||||||
|
}
|
||||||
|
function isEnabledByEl(el) {
|
||||||
|
const k = keyForEl(el, SCROLLER_KEYS);
|
||||||
|
return !!(k && SCROLL_FLAGS[k]);
|
||||||
|
}
|
||||||
|
function isEnabledByKey(key) {
|
||||||
|
return !!SCROLL_FLAGS[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Höhe der Scroll-Container: nebeneinander = volle Höhe; gestapelt = Drittel ===
|
||||||
function sizeScrollContainers() {
|
function sizeScrollContainers() {
|
||||||
$('.tableFixHead').each(function () {
|
const nodes = [
|
||||||
const rect = this.getBoundingClientRect();
|
document.querySelector('#checkout-scroll'),
|
||||||
const bottomMargin = 16;
|
document.querySelector('#checkin-scroll'),
|
||||||
const available = window.innerHeight - rect.top - bottomMargin;
|
document.querySelector('#aufgaben-scroll')
|
||||||
this.style.maxHeight = (available > 120 ? available : 120) + 'px';
|
].filter(Boolean);
|
||||||
|
|
||||||
|
if (nodes.length === 0) return;
|
||||||
|
|
||||||
|
// Ermitteln, ob alles in einer Zeile (nebeneinander) oder gestapelt
|
||||||
|
const tops = nodes.map(n => n.getBoundingClientRect().top);
|
||||||
|
const ROW_TOL = 8; // px
|
||||||
|
const uniqueRows = [];
|
||||||
|
tops.forEach(t => {
|
||||||
|
const exists = uniqueRows.some(rt => Math.abs(rt - t) <= ROW_TOL);
|
||||||
|
if (!exists) uniqueRows.push(t);
|
||||||
|
});
|
||||||
|
const isOneRow = uniqueRows.length === 1;
|
||||||
|
|
||||||
|
const bottomMargin = 16;
|
||||||
|
const minH = 120;
|
||||||
|
|
||||||
|
nodes.forEach(node => {
|
||||||
|
// Wenn Scrolling für diesen Bereich deaktiviert ist, Höhe trotzdem setzen (wie gefordert)
|
||||||
|
const rect = node.getBoundingClientRect();
|
||||||
|
if (isOneRow) {
|
||||||
|
const available = window.innerHeight - rect.top - bottomMargin;
|
||||||
|
node.style.maxHeight = Math.max(minH, available) + 'px';
|
||||||
|
} else {
|
||||||
|
const third = Math.floor(window.innerHeight / 3) - bottomMargin;
|
||||||
|
node.style.maxHeight = Math.max(minH, third) + 'px';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +102,7 @@ $Epi = new Epirent();
|
|||||||
function attachScrollGuards($scroller) {
|
function attachScrollGuards($scroller) {
|
||||||
const el = $scroller.get(0);
|
const el = $scroller.get(0);
|
||||||
if (!el || el.__guardsBound) return;
|
if (!el || el.__guardsBound) return;
|
||||||
|
if (!isEnabledByEl(el)) return; // nur wenn Feature aktiv
|
||||||
const state = ensureState(el);
|
const state = ensureState(el);
|
||||||
|
|
||||||
const markActive = () => {
|
const markActive = () => {
|
||||||
@@ -60,45 +117,40 @@ $Epi = new Epirent();
|
|||||||
el.__guardsBound = true;
|
el.__guardsBound = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Loop aufbauen: nahtloses Doppel nur bei Bedarf ===
|
// === Loop aufbauen: nahtloses Doppel nur bei Bedarf & nur wenn aktiv ===
|
||||||
// Klont den ersten <tbody> als __loopClone ans Tabellenende, wenn Inhalt > Sichtbereich.
|
|
||||||
function buildSeamlessLoop($table, $scroller) {
|
function buildSeamlessLoop($table, $scroller) {
|
||||||
$table.find('tbody.__loopClone').remove();
|
$table.find('tbody.__loopClone').remove();
|
||||||
|
|
||||||
const $main = $table.find('tbody').first();
|
|
||||||
const scEl = $scroller.get(0);
|
const scEl = $scroller.get(0);
|
||||||
|
const enabled = isEnabledByEl(scEl);
|
||||||
const st = ensureState(scEl);
|
const st = ensureState(scEl);
|
||||||
|
st.loopHeight = 0;
|
||||||
|
|
||||||
if ($main.length === 0 || $main.children().length === 0) {
|
if (!enabled) return; // deaktiviert -> keine Loops
|
||||||
st.loopHeight = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mainH = $main.get(0).offsetHeight;
|
const $main = $table.find('tbody').first();
|
||||||
const needScroll = mainH > scEl.clientHeight + 1;
|
if ($main.length === 0 || $main.children().length === 0) return;
|
||||||
|
|
||||||
if (!needScroll) {
|
const needScroll = $main.get(0).offsetHeight > scEl.clientHeight + 1;
|
||||||
st.loopHeight = 0;
|
if (!needScroll) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const $clone = $main.clone(false, false).addClass('__loopClone').attr('aria-hidden', 'true');
|
const $clone = $main.clone(false, false).addClass('__loopClone').attr('aria-hidden', 'true');
|
||||||
$table.append($clone);
|
$table.append($clone);
|
||||||
st.loopHeight = mainH; // Höhe des Original-Inhalts als Loop-Länge
|
st.loopHeight = $main.get(0).offsetHeight; // Höhe des Original-Inhalts als Loop-Länge
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Auto-Scroll (nahtlos) ===
|
// === Auto-Scroll (nahtlos) ===
|
||||||
function startAutoScroll($scroller, speedPx = 1, stepMs = 40) {
|
function startAutoScroll($scroller, speedPx = 1, stepMs = 40) {
|
||||||
const el = $scroller.get(0);
|
const el = $scroller.get(0);
|
||||||
const st = ensureState(el);
|
if (!isEnabledByEl(el)) return;
|
||||||
|
|
||||||
|
const st = ensureState(el);
|
||||||
if (st.autoTimer) return; // schon aktiv
|
if (st.autoTimer) return; // schon aktiv
|
||||||
if (st.loopHeight <= 0) return; // kein Overflow => nicht scrollen
|
if (st.loopHeight <= 0) return; // kein Overflow => nicht scrollen
|
||||||
|
|
||||||
const tick = () => {
|
const tick = () => {
|
||||||
if (st.userActive) { stopAutoScroll($scroller); return; }
|
if (st.userActive) { stopAutoScroll($scroller); return; }
|
||||||
el.scrollTop += speedPx;
|
el.scrollTop += speedPx;
|
||||||
// Nahtlos: sobald wir das Ende des Original-Blocks überschreiten, ziehen wir loopHeight ab
|
|
||||||
if (el.scrollTop >= st.loopHeight) {
|
if (el.scrollTop >= st.loopHeight) {
|
||||||
el.scrollTop -= st.loopHeight;
|
el.scrollTop -= st.loopHeight;
|
||||||
}
|
}
|
||||||
@@ -118,6 +170,7 @@ $Epi = new Epirent();
|
|||||||
|
|
||||||
function maybeStartAutoScroll($scroller) {
|
function maybeStartAutoScroll($scroller) {
|
||||||
const el = $scroller.get(0);
|
const el = $scroller.get(0);
|
||||||
|
if (!isEnabledByEl(el)) { stopAutoScroll($scroller); return; }
|
||||||
const st = ensureState(el);
|
const st = ensureState(el);
|
||||||
if (st.userActive) return;
|
if (st.userActive) return;
|
||||||
if (st.loopHeight > 0) startAutoScroll($scroller, 1, 80);
|
if (st.loopHeight > 0) startAutoScroll($scroller, 1, 80);
|
||||||
@@ -125,33 +178,33 @@ $Epi = new Epirent();
|
|||||||
}
|
}
|
||||||
|
|
||||||
// === AJAX-Reload: relative Position innerhalb der Loop erhalten ===
|
// === AJAX-Reload: relative Position innerhalb der Loop erhalten ===
|
||||||
// Wir merken die Position modulo loopHeight; nach dem Reload setzen wir proportional um.
|
|
||||||
function smartLoad($scroller, $table, $target, url, intervalMs) {
|
function smartLoad($scroller, $table, $target, url, intervalMs) {
|
||||||
const scEl = $scroller.get(0);
|
const scEl = $scroller.get(0);
|
||||||
const st = ensureState(scEl);
|
const st = ensureState(scEl);
|
||||||
|
const enabled = isEnabledByEl(scEl);
|
||||||
|
|
||||||
const oldLoop = st.loopHeight > 0 ? st.loopHeight : Math.max(1, scEl.scrollHeight - scEl.clientHeight);
|
const oldLoop = st.loopHeight > 0 ? st.loopHeight : Math.max(1, scEl.scrollHeight - scEl.clientHeight);
|
||||||
const posInLoop = st.loopHeight > 0 ? (scEl.scrollTop % oldLoop) : scEl.scrollTop;
|
const posInLoop = st.loopHeight > 0 ? (scEl.scrollTop % oldLoop) : scEl.scrollTop;
|
||||||
const posRatio = Math.min(1, posInLoop / oldLoop);
|
const posRatio = Math.min(1, posInLoop / oldLoop);
|
||||||
|
|
||||||
$target.load(url, function () {
|
$target.load(url, function () {
|
||||||
// Loop neu bewerten/aufbauen
|
if (enabled) {
|
||||||
buildSeamlessLoop($table, $scroller);
|
buildSeamlessLoop($table, $scroller);
|
||||||
|
if (st.loopHeight > 0) {
|
||||||
if (st.loopHeight > 0) {
|
const newPos = Math.floor(posRatio * st.loopHeight);
|
||||||
// Neue relative Position setzen (proportional)
|
if (Math.abs(scEl.scrollTop - newPos) > 1) {
|
||||||
const newPos = Math.floor(posRatio * st.loopHeight);
|
requestAnimationFrame(() => { scEl.scrollTop = newPos; });
|
||||||
if (Math.abs(scEl.scrollTop - newPos) > 1) {
|
}
|
||||||
// rAF für flüssiges Setzen außerhalb des load()-Layouts
|
stopAutoScroll($scroller);
|
||||||
requestAnimationFrame(() => { scEl.scrollTop = newPos; });
|
startAutoScroll($scroller, 1, 80);
|
||||||
|
} else {
|
||||||
|
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
|
||||||
|
stopAutoScroll($scroller);
|
||||||
}
|
}
|
||||||
// Auto-Scroll (wieder) starten
|
|
||||||
stopAutoScroll($scroller);
|
|
||||||
startAutoScroll($scroller, 1, 80);
|
|
||||||
} else {
|
} else {
|
||||||
// kein Overflow -> ganz oben und Auto-Scroll aus
|
$table.find('tbody.__loopClone').remove();
|
||||||
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
|
|
||||||
stopAutoScroll($scroller);
|
stopAutoScroll($scroller);
|
||||||
|
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => smartLoad($scroller, $table, $target, url, intervalMs), intervalMs);
|
setTimeout(() => smartLoad($scroller, $table, $target, url, intervalMs), intervalMs);
|
||||||
@@ -161,9 +214,9 @@ $Epi = new Epirent();
|
|||||||
// === Initialisierung ===
|
// === Initialisierung ===
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
sizeScrollContainers();
|
sizeScrollContainers();
|
||||||
|
|
||||||
$(window).on('resize', function () {
|
$(window).on('resize', function () {
|
||||||
sizeScrollContainers();
|
sizeScrollContainers();
|
||||||
// Nach Layoutwechsel neu entscheiden
|
|
||||||
['#checkout', '#checkin', '#aufgaben'].forEach(prefix => {
|
['#checkout', '#checkin', '#aufgaben'].forEach(prefix => {
|
||||||
const $scroller = $(`${prefix}-scroll`);
|
const $scroller = $(`${prefix}-scroll`);
|
||||||
const $table = $(`${prefix}-table`);
|
const $table = $(`${prefix}-table`);
|
||||||
@@ -172,32 +225,39 @@ $Epi = new Epirent();
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Guards pro Scroller
|
// Guards nur für aktivierte Scroller
|
||||||
attachScrollGuards($('#checkout-scroll'));
|
[['#checkout-scroll','checkout'], ['#checkin-scroll','checkin'], ['#aufgaben-scroll','aufgaben']].forEach(([sel,key]) => {
|
||||||
attachScrollGuards($('#checkin-scroll'));
|
if (isEnabledByKey(key)) attachScrollGuards($(sel));
|
||||||
attachScrollGuards($('#aufgaben-scroll'));
|
});
|
||||||
|
|
||||||
// Erstladen je Tabelle: laden -> Loop bauen -> ggf. Auto-Scroll -> zyklisch refreshen
|
// Erstladen je Tabelle
|
||||||
function initOne(scrollerSel, tableSel, tbodySel, url, ms) {
|
function initOne(scrollerSel, tableSel, tbodySel, url, ms, key) {
|
||||||
const $scroller = $(scrollerSel);
|
const $scroller = $(scrollerSel);
|
||||||
const $table = $(tableSel);
|
const $table = $(tableSel);
|
||||||
const $target = $(tbodySel);
|
const $target = $(tbodySel);
|
||||||
|
const enabled = isEnabledByKey(key);
|
||||||
|
|
||||||
$target.load(url, function () {
|
$target.load(url, function () {
|
||||||
buildSeamlessLoop($table, $scroller);
|
if (enabled) {
|
||||||
maybeStartAutoScroll($scroller);
|
buildSeamlessLoop($table, $scroller);
|
||||||
|
maybeStartAutoScroll($scroller);
|
||||||
|
} else {
|
||||||
|
$table.find('tbody.__loopClone').remove();
|
||||||
|
stopAutoScroll($scroller);
|
||||||
|
}
|
||||||
setTimeout(() => smartLoad($scroller, $table, $target, url, ms), ms);
|
setTimeout(() => smartLoad($scroller, $table, $target, url, ms), ms);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initOne('#checkout-scroll', '#checkout-table', '#getCheckOutTableHolder', 'sources/getCheckOutTable.php', 5000);
|
initOne('#checkout-scroll', '#checkout-table', '#getCheckOutTableHolder', 'sources/getCheckOutTable.php', 5000, 'checkout');
|
||||||
initOne('#checkin-scroll', '#checkin-table', '#getCheckInTableHolder', 'sources/getCheckInTable.php', 5000);
|
initOne('#checkin-scroll', '#checkin-table', '#getCheckInTableHolder', 'sources/getCheckInTable.php', 5000, 'checkin');
|
||||||
initOne('#aufgaben-scroll', '#aufgaben-table', '#AufgabenTableHolder', 'sources/getAufgabenTable.php', 30000);
|
initOne('#aufgaben-scroll', '#aufgaben-table', '#AufgabenTableHolder', 'sources/getAufgabenTable.php', 30000, 'aufgaben');
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Scroll-Wrapper je Tabelle */
|
/* Scroll-Wrapper je Tabelle */
|
||||||
.tableFixHead {
|
.tableFixHead {
|
||||||
|
|||||||
367
Packmonitor.php
367
Packmonitor.php
@@ -21,175 +21,228 @@ $Epi = new Epirent();
|
|||||||
<link href="css/sticky-footer.css" rel="stylesheet">
|
<link href="css/sticky-footer.css" rel="stylesheet">
|
||||||
<script src="https://kit.fontawesome.com/93d71de8bc.js" crossorigin="anonymous"></script>
|
<script src="https://kit.fontawesome.com/93d71de8bc.js" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
// === Höhe der Scroll-Container bis Viewport-Ende anpassen ===
|
/* ===========================
|
||||||
function sizeScrollContainers() {
|
Feature-Toggles aus PHP
|
||||||
$('.tableFixHead').each(function () {
|
=========================== */
|
||||||
const rect = this.getBoundingClientRect();
|
const SCROLL_FLAGS = {
|
||||||
const bottomMargin = 16;
|
checkout: <?php echo (defined('EnableScrollingCheckOut') && EnableScrollingCheckOut) ? 'true' : 'false'; ?>,
|
||||||
const available = window.innerHeight - rect.top - bottomMargin;
|
checkin: <?php echo (defined('EnableScrollingCheckIn') && EnableScrollingCheckIn) ? 'true' : 'false'; ?>
|
||||||
this.style.maxHeight = (available > 120 ? available : 120) + 'px';
|
};
|
||||||
});
|
|
||||||
|
const SCROLLER_KEYS = new Map([
|
||||||
|
['checkout-scroll', 'checkout'],
|
||||||
|
['checkin-scroll', 'checkin']
|
||||||
|
]);
|
||||||
|
|
||||||
|
function keyForEl(el) { return el && el.id ? SCROLLER_KEYS.get(el.id) : null; }
|
||||||
|
function isEnabledByEl(el) { const k = keyForEl(el); return !!(k && SCROLL_FLAGS[k]); }
|
||||||
|
function isEnabledByKey(key) { return !!SCROLL_FLAGS[key]; }
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Höhe der Scroll-Container
|
||||||
|
- 2 nebeneinander: volle Höhe bis Viewport-Ende
|
||||||
|
- gestapelt: je 1/2 Fensterhöhe
|
||||||
|
=========================== */
|
||||||
|
function sizeScrollContainers() {
|
||||||
|
const nodes = [
|
||||||
|
document.querySelector('#checkout-scroll'),
|
||||||
|
document.querySelector('#checkin-scroll')
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
if (!nodes.length) return;
|
||||||
|
|
||||||
|
const tops = nodes.map(n => n.getBoundingClientRect().top);
|
||||||
|
const ROW_TOL = 8;
|
||||||
|
const firstTop = tops[0];
|
||||||
|
const isOneRow = tops.every(t => Math.abs(t - firstTop) <= ROW_TOL);
|
||||||
|
|
||||||
|
const bottomMargin = 16;
|
||||||
|
const minH = 120;
|
||||||
|
|
||||||
|
nodes.forEach(node => {
|
||||||
|
const rect = node.getBoundingClientRect();
|
||||||
|
if (isOneRow) {
|
||||||
|
const available = window.innerHeight - rect.top - bottomMargin;
|
||||||
|
node.style.maxHeight = Math.max(minH, available) + 'px';
|
||||||
|
} else {
|
||||||
|
const half = Math.floor(window.innerHeight / 2) - bottomMargin;
|
||||||
|
node.style.maxHeight = Math.max(minH, half) + 'px';
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// === Scroll-/Interaktionsstatus je Scroller ===
|
/* ===========================
|
||||||
const scrollState = new WeakMap(); // { userActive:boolean, autoTimer:number|null, loopHeight:number }
|
Scroll-/Interaktionsstatus
|
||||||
|
=========================== */
|
||||||
|
const scrollState = new WeakMap(); // { userActive:boolean, autoTimer:number|null, loopHeight:number }
|
||||||
|
|
||||||
function ensureState(el) {
|
function ensureState(el) {
|
||||||
if (!scrollState.get(el)) scrollState.set(el, { userActive: false, autoTimer: null, loopHeight: 0 });
|
if (!scrollState.get(el)) scrollState.set(el, { userActive: false, autoTimer: null, loopHeight: 0 });
|
||||||
return scrollState.get(el);
|
return scrollState.get(el);
|
||||||
}
|
}
|
||||||
|
|
||||||
function attachScrollGuards($scroller) {
|
function attachScrollGuards($scroller) {
|
||||||
const el = $scroller.get(0);
|
const el = $scroller.get(0);
|
||||||
if (!el || el.__guardsBound) return;
|
if (!el || el.__guardsBound) return;
|
||||||
const state = ensureState(el);
|
if (!isEnabledByEl(el)) return;
|
||||||
|
const state = ensureState(el);
|
||||||
|
|
||||||
const markActive = () => {
|
const markActive = () => {
|
||||||
state.userActive = true;
|
state.userActive = true;
|
||||||
clearTimeout(state._quietT);
|
clearTimeout(state._quietT);
|
||||||
state._quietT = setTimeout(() => { state.userActive = false; }, 800);
|
state._quietT = setTimeout(() => { state.userActive = false; }, 800);
|
||||||
|
stopAutoScroll($scroller);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scroller.on('wheel touchstart touchmove keydown mousedown mouseenter', markActive);
|
||||||
|
$scroller.on('mouseleave', () => setTimeout(() => maybeStartAutoScroll($scroller), 600));
|
||||||
|
el.__guardsBound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Nahtloser Loop (Clone)
|
||||||
|
=========================== */
|
||||||
|
function buildSeamlessLoop($table, $scroller) {
|
||||||
|
$table.find('tbody.__loopClone').remove();
|
||||||
|
|
||||||
|
const scEl = $scroller.get(0);
|
||||||
|
const enabled = isEnabledByEl(scEl);
|
||||||
|
const st = ensureState(scEl);
|
||||||
|
st.loopHeight = 0;
|
||||||
|
|
||||||
|
if (!enabled) return;
|
||||||
|
|
||||||
|
const $main = $table.find('tbody').first();
|
||||||
|
if ($main.length === 0 || $main.children().length === 0) return;
|
||||||
|
|
||||||
|
const needScroll = $main.get(0).offsetHeight > scEl.clientHeight + 1;
|
||||||
|
if (!needScroll) return;
|
||||||
|
|
||||||
|
const $clone = $main.clone(false, false).addClass('__loopClone').attr('aria-hidden', 'true');
|
||||||
|
$table.append($clone);
|
||||||
|
st.loopHeight = $main.get(0).offsetHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Auto-Scroll
|
||||||
|
=========================== */
|
||||||
|
function startAutoScroll($scroller, speedPx = 1, stepMs = 40) {
|
||||||
|
const el = $scroller.get(0);
|
||||||
|
if (!isEnabledByEl(el)) return;
|
||||||
|
|
||||||
|
const st = ensureState(el);
|
||||||
|
if (st.autoTimer) return;
|
||||||
|
if (st.loopHeight <= 0) return;
|
||||||
|
|
||||||
|
const tick = () => {
|
||||||
|
if (st.userActive) { stopAutoScroll($scroller); return; }
|
||||||
|
el.scrollTop += speedPx;
|
||||||
|
if (el.scrollTop >= st.loopHeight) el.scrollTop -= st.loopHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
st.autoTimer = setInterval(tick, stepMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopAutoScroll($scroller) {
|
||||||
|
const el = $scroller.get(0);
|
||||||
|
const st = ensureState(el);
|
||||||
|
if (st.autoTimer) {
|
||||||
|
clearInterval(st.autoTimer);
|
||||||
|
st.autoTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeStartAutoScroll($scroller) {
|
||||||
|
const el = $scroller.get(0);
|
||||||
|
if (!isEnabledByEl(el)) { stopAutoScroll($scroller); return; }
|
||||||
|
const st = ensureState(el);
|
||||||
|
if (st.userActive) return;
|
||||||
|
if (st.loopHeight > 0) startAutoScroll($scroller, 1, 40);
|
||||||
|
else stopAutoScroll($scroller);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================
|
||||||
|
Smart AJAX Reload
|
||||||
|
=========================== */
|
||||||
|
function smartLoad($scroller, $table, $target, url, intervalMs) {
|
||||||
|
const scEl = $scroller.get(0);
|
||||||
|
const st = ensureState(scEl);
|
||||||
|
const enabled = isEnabledByEl(scEl);
|
||||||
|
|
||||||
|
const oldLoop = st.loopHeight > 0 ? st.loopHeight : Math.max(1, scEl.scrollHeight - scEl.clientHeight);
|
||||||
|
const posInLoop = st.loopHeight > 0 ? (scEl.scrollTop % oldLoop) : scEl.scrollTop;
|
||||||
|
const posRatio = Math.min(1, posInLoop / oldLoop);
|
||||||
|
|
||||||
|
$target.load(url, function () {
|
||||||
|
if (enabled) {
|
||||||
|
buildSeamlessLoop($table, $scroller);
|
||||||
|
if (st.loopHeight > 0) {
|
||||||
|
const newPos = Math.floor(posRatio * st.loopHeight);
|
||||||
|
if (Math.abs(scEl.scrollTop - newPos) > 1) {
|
||||||
|
requestAnimationFrame(() => { scEl.scrollTop = newPos; });
|
||||||
|
}
|
||||||
stopAutoScroll($scroller);
|
stopAutoScroll($scroller);
|
||||||
};
|
startAutoScroll($scroller, 1, 40);
|
||||||
|
} else {
|
||||||
$scroller.on('wheel touchstart touchmove keydown mousedown mouseenter', markActive);
|
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
|
||||||
$scroller.on('mouseleave', () => setTimeout(() => maybeStartAutoScroll($scroller), 600));
|
stopAutoScroll($scroller);
|
||||||
el.__guardsBound = true;
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
// === Loop aufbauen: nahtloses Doppel nur bei Bedarf ===
|
|
||||||
// Klont den ersten <tbody> als __loopClone ans Tabellenende, wenn Inhalt > Sichtbereich.
|
|
||||||
function buildSeamlessLoop($table, $scroller) {
|
|
||||||
$table.find('tbody.__loopClone').remove();
|
$table.find('tbody.__loopClone').remove();
|
||||||
|
stopAutoScroll($scroller);
|
||||||
const $main = $table.find('tbody').first();
|
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
|
||||||
const scEl = $scroller.get(0);
|
|
||||||
const st = ensureState(scEl);
|
|
||||||
|
|
||||||
if ($main.length === 0 || $main.children().length === 0) {
|
|
||||||
st.loopHeight = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mainH = $main.get(0).offsetHeight;
|
|
||||||
const needScroll = mainH > scEl.clientHeight + 1;
|
|
||||||
|
|
||||||
if (!needScroll) {
|
|
||||||
st.loopHeight = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const $clone = $main.clone(false, false).addClass('__loopClone').attr('aria-hidden', 'true');
|
|
||||||
$table.append($clone);
|
|
||||||
st.loopHeight = mainH; // Höhe des Original-Inhalts als Loop-Länge
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Auto-Scroll (nahtlos) ===
|
setTimeout(() => smartLoad($scroller, $table, $target, url, intervalMs), intervalMs);
|
||||||
function startAutoScroll($scroller, speedPx = 1, stepMs = 40) {
|
});
|
||||||
const el = $scroller.get(0);
|
}
|
||||||
const st = ensureState(el);
|
|
||||||
|
|
||||||
if (st.autoTimer) return; // schon aktiv
|
/* ===========================
|
||||||
if (st.loopHeight <= 0) return; // kein Overflow => nicht scrollen
|
Initialisierung
|
||||||
|
=========================== */
|
||||||
|
$(document).ready(function () {
|
||||||
|
sizeScrollContainers();
|
||||||
|
|
||||||
const tick = () => {
|
$(window).on('resize', function () {
|
||||||
if (st.userActive) { stopAutoScroll($scroller); return; }
|
sizeScrollContainers();
|
||||||
el.scrollTop += speedPx;
|
['#checkout', '#checkin'].forEach(prefix => {
|
||||||
// Nahtlos: sobald wir das Ende des Original-Blocks überschreiten, ziehen wir loopHeight ab
|
const $scroller = $(`${prefix}-scroll`);
|
||||||
if (el.scrollTop >= st.loopHeight) {
|
const $table = $(`${prefix}-table`);
|
||||||
el.scrollTop -= st.loopHeight;
|
buildSeamlessLoop($table, $scroller);
|
||||||
}
|
maybeStartAutoScroll($scroller);
|
||||||
};
|
|
||||||
|
|
||||||
st.autoTimer = setInterval(tick, stepMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopAutoScroll($scroller) {
|
|
||||||
const el = $scroller.get(0);
|
|
||||||
const st = ensureState(el);
|
|
||||||
if (st.autoTimer) {
|
|
||||||
clearInterval(st.autoTimer);
|
|
||||||
st.autoTimer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function maybeStartAutoScroll($scroller) {
|
|
||||||
const el = $scroller.get(0);
|
|
||||||
const st = ensureState(el);
|
|
||||||
if (st.userActive) return;
|
|
||||||
if (st.loopHeight > 0) startAutoScroll($scroller, 1, 40);
|
|
||||||
else stopAutoScroll($scroller);
|
|
||||||
}
|
|
||||||
|
|
||||||
// === AJAX-Reload: relative Position innerhalb der Loop erhalten ===
|
|
||||||
// Wir merken die Position modulo loopHeight; nach dem Reload setzen wir proportional um.
|
|
||||||
function smartLoad($scroller, $table, $target, url, intervalMs) {
|
|
||||||
const scEl = $scroller.get(0);
|
|
||||||
const st = ensureState(scEl);
|
|
||||||
|
|
||||||
const oldLoop = st.loopHeight > 0 ? st.loopHeight : Math.max(1, scEl.scrollHeight - scEl.clientHeight);
|
|
||||||
const posInLoop = st.loopHeight > 0 ? (scEl.scrollTop % oldLoop) : scEl.scrollTop;
|
|
||||||
const posRatio = Math.min(1, posInLoop / oldLoop);
|
|
||||||
|
|
||||||
$target.load(url, function () {
|
|
||||||
// Loop neu bewerten/aufbauen
|
|
||||||
buildSeamlessLoop($table, $scroller);
|
|
||||||
|
|
||||||
if (st.loopHeight > 0) {
|
|
||||||
// Neue relative Position setzen (proportional)
|
|
||||||
const newPos = Math.floor(posRatio * st.loopHeight);
|
|
||||||
if (Math.abs(scEl.scrollTop - newPos) > 1) {
|
|
||||||
// rAF für flüssiges Setzen außerhalb des load()-Layouts
|
|
||||||
requestAnimationFrame(() => { scEl.scrollTop = newPos; });
|
|
||||||
}
|
|
||||||
// Auto-Scroll (wieder) starten
|
|
||||||
stopAutoScroll($scroller);
|
|
||||||
startAutoScroll($scroller, 1, 40);
|
|
||||||
} else {
|
|
||||||
// kein Overflow -> ganz oben und Auto-Scroll aus
|
|
||||||
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
|
|
||||||
stopAutoScroll($scroller);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => smartLoad($scroller, $table, $target, url, intervalMs), intervalMs);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Initialisierung ===
|
|
||||||
$(document).ready(function () {
|
|
||||||
sizeScrollContainers();
|
|
||||||
$(window).on('resize', function () {
|
|
||||||
sizeScrollContainers();
|
|
||||||
// Nach Layoutwechsel neu entscheiden (nur 2 Tabellen)
|
|
||||||
['#checkout', '#checkin'].forEach(prefix => {
|
|
||||||
const $scroller = $(`${prefix}-scroll`);
|
|
||||||
const $table = $(`${prefix}-table`);
|
|
||||||
buildSeamlessLoop($table, $scroller);
|
|
||||||
maybeStartAutoScroll($scroller);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Guards pro Scroller (nur 2)
|
|
||||||
attachScrollGuards($('#checkout-scroll'));
|
|
||||||
attachScrollGuards($('#checkin-scroll'));
|
|
||||||
|
|
||||||
// Erstladen je Tabelle: laden -> Loop bauen -> ggf. Auto-Scroll -> zyklisch refreshen
|
|
||||||
function initOne(scrollerSel, tableSel, tbodySel, url, ms) {
|
|
||||||
const $scroller = $(scrollerSel);
|
|
||||||
const $table = $(tableSel);
|
|
||||||
const $target = $(tbodySel);
|
|
||||||
|
|
||||||
$target.load(url, function () {
|
|
||||||
buildSeamlessLoop($table, $scroller);
|
|
||||||
maybeStartAutoScroll($scroller);
|
|
||||||
setTimeout(() => smartLoad($scroller, $table, $target, url, ms), ms);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
initOne('#checkout-scroll', '#checkout-table', '#getCheckOutTableHolder', 'sources/getCheckOutTable.php', 5000);
|
|
||||||
initOne('#checkin-scroll', '#checkin-table', '#getCheckInTableHolder', 'sources/getCheckInTable.php', 5000);
|
|
||||||
});
|
});
|
||||||
</script>
|
});
|
||||||
|
|
||||||
|
// Guards nur für aktivierte Scroller
|
||||||
|
[['#checkout-scroll','checkout'], ['#checkin-scroll','checkin']].forEach(([sel,key]) => {
|
||||||
|
if (isEnabledByKey(key)) attachScrollGuards($(sel));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Erstladen je Tabelle
|
||||||
|
function initOne(scrollerSel, tableSel, tbodySel, url, ms, key) {
|
||||||
|
const $scroller = $(scrollerSel);
|
||||||
|
const $table = $(tableSel);
|
||||||
|
const $target = $(tbodySel);
|
||||||
|
const enabled = isEnabledByKey(key);
|
||||||
|
|
||||||
|
$target.load(url, function () {
|
||||||
|
if (enabled) {
|
||||||
|
buildSeamlessLoop($table, $scroller);
|
||||||
|
maybeStartAutoScroll($scroller);
|
||||||
|
} else {
|
||||||
|
$table.find('tbody.__loopClone').remove();
|
||||||
|
stopAutoScroll($scroller);
|
||||||
|
}
|
||||||
|
setTimeout(() => smartLoad($scroller, $table, $target, url, ms), ms);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initOne('#checkout-scroll', '#checkout-table', '#getCheckOutTableHolder', 'sources/getCheckOutTable.php', 5000, 'checkout');
|
||||||
|
initOne('#checkin-scroll', '#checkin-table', '#getCheckInTableHolder', 'sources/getCheckInTable.php', 5000, 'checkin');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Scroll-Wrapper je Tabelle */
|
/* Scroll-Wrapper je Tabelle */
|
||||||
|
|||||||
413
dist/editconfig.php
vendored
Normal file
413
dist/editconfig.php
vendored
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config-Editor für EPI Webview
|
||||||
|
* - Liest ../config.php (falls vorhanden) und ../example.config.php
|
||||||
|
* - Zeigt editierbare Felder für alle Keys aus example.config.php
|
||||||
|
* - Zeigt veraltete (nur in config.php existierende) Keys rot & disabled
|
||||||
|
* - Speichert Werte zurück nach ../config.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
// ---- Dateipfade anpassen falls nötig ----
|
||||||
|
$CONFIG_FILE = realpath(__DIR__ . '/../config.php') ?: (__DIR__ . '/../config.php');
|
||||||
|
$EXAMPLE_FILE = realpath(__DIR__ . '/../example.config.php') ?: (__DIR__ . '/../example.config.php');
|
||||||
|
|
||||||
|
// ---- Hilfsfunktionen: Parsing ----
|
||||||
|
function parse_define_file(string $file): array {
|
||||||
|
if (!is_file($file)) return ['defines' => [], 'order' => [], 'ui' => []];
|
||||||
|
|
||||||
|
$lines = file($file, FILE_IGNORE_NEW_LINES);
|
||||||
|
$defines = [];
|
||||||
|
$order = [];
|
||||||
|
$ui = [];
|
||||||
|
|
||||||
|
// BOM entfernen, falls vorhanden
|
||||||
|
if (isset($lines[0])) {
|
||||||
|
$lines[0] = preg_replace('/^\xEF\xBB\xBF/', '', $lines[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regex
|
||||||
|
$reDefine = '/^\s*define\(\s*([\'"])([^\'"]+)\1\s*,\s*(.+?)\s*\)\s*;\s*(?:(?:\/\/|#)\s*(.*))?$/u';
|
||||||
|
$reSection = '/^\s*\/\/\s*@section\s*:\s*(.+)\s*$/iu';
|
||||||
|
$reHR = '/^\s*\/\/\s*@hr\s*$/iu';
|
||||||
|
$reNote = '/^\s*\/\/\s*@note\s*:\s*(.+)\s*$/iu';
|
||||||
|
|
||||||
|
foreach ($lines as $ln) {
|
||||||
|
// Meta-Kommentare zuerst prüfen (UI-Struktur)
|
||||||
|
if (preg_match($reSection, $ln, $m)) {
|
||||||
|
$ui[] = ['kind' => 'section', 'title' => trim($m[1])];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (preg_match($reHR, $ln)) {
|
||||||
|
$ui[] = ['kind' => 'hr'];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (preg_match($reNote, $ln, $m)) {
|
||||||
|
$ui[] = ['kind' => 'note', 'text' => trim($m[1])];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// define(…) Zeilen
|
||||||
|
if (!preg_match($reDefine, $ln, $m)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $m[2];
|
||||||
|
$rawValueExpr = trim($m[3]);
|
||||||
|
$inlineC = isset($m[4]) ? trim($m[4]) : '';
|
||||||
|
|
||||||
|
// Typ bestimmen
|
||||||
|
$type = 'string';
|
||||||
|
$valForForm = '';
|
||||||
|
$raw = rtrim($rawValueExpr, ',');
|
||||||
|
|
||||||
|
if (preg_match('/^(true|false)$/i', $raw)) {
|
||||||
|
$type = 'bool';
|
||||||
|
$valForForm = (strtolower($raw) === 'true');
|
||||||
|
} elseif (preg_match('/^[+-]?\d+$/', $raw)) {
|
||||||
|
$type = 'int';
|
||||||
|
$valForForm = (int)$raw;
|
||||||
|
} elseif (preg_match('/^([\'"])(.*)\1$/s', $raw, $mm)) {
|
||||||
|
$type = 'string';
|
||||||
|
$valForForm = stripcslashes($mm[2]);
|
||||||
|
} else {
|
||||||
|
$type = 'string';
|
||||||
|
$valForForm = $raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
$defines[$name] = [
|
||||||
|
'name' => $name,
|
||||||
|
'type' => $type,
|
||||||
|
'value' => $valForForm,
|
||||||
|
'raw' => $rawValueExpr,
|
||||||
|
'comment' => $inlineC,
|
||||||
|
'line' => $ln,
|
||||||
|
];
|
||||||
|
$order[] = $name;
|
||||||
|
// Wichtig: define auch in die UI-Reihenfolge aufnehmen
|
||||||
|
$ui[] = ['kind' => 'define', 'name' => $name];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['defines' => $defines, 'order' => $order, 'ui' => $ui];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function php_export_define_value(string $type, $value): string {
|
||||||
|
// Baut den rechten Teil von define('X', <HIER>);
|
||||||
|
if ($type === 'bool') {
|
||||||
|
return $value ? 'true' : 'false';
|
||||||
|
}
|
||||||
|
if ($type === 'int') {
|
||||||
|
return (string)intval($value);
|
||||||
|
}
|
||||||
|
// String:
|
||||||
|
// einfache Quotes nehmen und escapen
|
||||||
|
$escaped = str_replace("'", "\\'", (string)$value);
|
||||||
|
return "'" . $escaped . "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
function h(?string $s): string { return htmlspecialchars((string)$s ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
|
||||||
|
|
||||||
|
// ---- Dateien parsen ----
|
||||||
|
$example = parse_define_file($EXAMPLE_FILE);
|
||||||
|
$current = parse_define_file($CONFIG_FILE);
|
||||||
|
|
||||||
|
// Keys klassifizieren
|
||||||
|
$exampleKeys = array_keys($example['defines']);
|
||||||
|
$currentKeys = array_keys($current['defines']);
|
||||||
|
|
||||||
|
$deprecatedKeys = array_values(array_diff($currentKeys, $exampleKeys)); // nur in config.php
|
||||||
|
$newKeys = array_values(array_diff($exampleKeys, $currentKeys)); // nur in example (neu)
|
||||||
|
$sharedKeys = array_values(array_intersect($exampleKeys, $currentKeys));
|
||||||
|
|
||||||
|
// Beim Rendern: Reihenfolge aus example beibehalten
|
||||||
|
$renderKeys = $example['order'];
|
||||||
|
|
||||||
|
// ---- POST: Speichern ----
|
||||||
|
$message = '';
|
||||||
|
$err = '';
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['__save_config']) && isset($_POST['cfg']) && is_array($_POST['cfg'])) {
|
||||||
|
|
||||||
|
$incoming = $_POST['cfg']; // ['NAME' => valueString/checkbox etc.]
|
||||||
|
|
||||||
|
// Build neuer config.php Inhalt:
|
||||||
|
$out = [];
|
||||||
|
$out[] = "<?php";
|
||||||
|
$out[] = "// Diese Datei wurde durch den Epi Webview Config-Editor erzeugt/aktualisiert.";
|
||||||
|
$out[] = "// Bearbeite Werte bevorzugt über die Weboberfläche.";
|
||||||
|
$out[] = "";
|
||||||
|
|
||||||
|
// --- Inhalt in der Reihenfolge & Struktur der example.config.php ---
|
||||||
|
foreach ($example['ui'] as $item) {
|
||||||
|
if ($item['kind'] === 'section') {
|
||||||
|
$out[] = "// @section: " . $item['title'];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($item['kind'] === 'hr') {
|
||||||
|
$out[] = "// @hr";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($item['kind'] === 'note') {
|
||||||
|
$out[] = "// @note: " . $item['text'];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($item['kind'] !== 'define') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$label = $item['name'];
|
||||||
|
$ex = $example['defines'][$label] ?? null;
|
||||||
|
if (!$ex) continue;
|
||||||
|
|
||||||
|
$type = $ex['type']; // Typ an example orientieren
|
||||||
|
$tooltip = $ex['comment'] ?? '';
|
||||||
|
|
||||||
|
// Wert aus POST übernehmen (Checkbox-Fix: existiert das Feld?)
|
||||||
|
if ($type === 'bool') {
|
||||||
|
$bool = array_key_exists($label, $incoming);
|
||||||
|
$valueForDefine = php_export_define_value('bool', $bool);
|
||||||
|
} elseif ($type === 'int') {
|
||||||
|
$raw = $incoming[$label] ?? '';
|
||||||
|
$valueForDefine = php_export_define_value('int', $raw);
|
||||||
|
} else {
|
||||||
|
$raw = $incoming[$label] ?? '';
|
||||||
|
$valueForDefine = php_export_define_value('string', $raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
$line = "define('{$label}', {$valueForDefine});";
|
||||||
|
if (!empty($tooltip)) {
|
||||||
|
$line .= " // " . $tooltip; // Kommentare als Tooltip auch in config.php mitführen
|
||||||
|
}
|
||||||
|
$out[] = $line;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated Block (nur in aktueller config.php vorhanden, nicht in example)
|
||||||
|
if (!empty($deprecatedKeys)) {
|
||||||
|
$out[] = "";
|
||||||
|
$out[] = "// --- Veraltete/entfernte Parameter (nur Referenz, werden nicht mehr genutzt) ---";
|
||||||
|
foreach ($deprecatedKeys as $key) {
|
||||||
|
$c = $current['defines'][$key];
|
||||||
|
$valueForDefine = php_export_define_value($c['type'], $c['value']);
|
||||||
|
$line = "// define('{$c['name']}', {$valueForDefine});";
|
||||||
|
if (!empty($c['comment'])) {
|
||||||
|
$line .= " // " . $c['comment'];
|
||||||
|
}
|
||||||
|
$out[] = $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$out[] = "?>";
|
||||||
|
$content = implode("\n", $out) . "\n";
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (false === file_put_contents($CONFIG_FILE, $content)) {
|
||||||
|
$err = "Konnte config.php nicht schreiben: " . h($CONFIG_FILE);
|
||||||
|
} else {
|
||||||
|
$message = "Konfiguration gespeichert.";
|
||||||
|
// Nach dem Speichern neu parsen, damit UI aktuell ist
|
||||||
|
$current = parse_define_file($CONFIG_FILE);
|
||||||
|
$currentKeys = array_keys($current['defines']);
|
||||||
|
$deprecatedKeys = array_values(array_diff($currentKeys, $exampleKeys));
|
||||||
|
$sharedKeys = array_values(array_intersect($exampleKeys, $currentKeys));
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$err = "Fehler beim Schreiben: " . h($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ---- Werte für Formular vorbereiten ----
|
||||||
|
// Für jeden example-Key: Formularwert = aus aktueller config (falls vorhanden), sonst example default
|
||||||
|
function valueForForm(array $example, array $current, string $key) {
|
||||||
|
$ex = $example['defines'][$key] ?? null;
|
||||||
|
$cu = $current['defines'][$key] ?? null;
|
||||||
|
$type = $ex ? $ex['type'] : ($cu ? $cu['type'] : 'string');
|
||||||
|
$comment = $ex && $ex['comment'] !== '' ? $ex['comment'] : ($cu['comment'] ?? '');
|
||||||
|
|
||||||
|
if ($cu) {
|
||||||
|
$val = $cu['value'];
|
||||||
|
} else {
|
||||||
|
$val = $ex['value'];
|
||||||
|
}
|
||||||
|
return [$type, $val, $comment];
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||||
|
<meta name="description" content="Konfiguration bearbeiten" />
|
||||||
|
<meta name="author" content="" />
|
||||||
|
<title>Konfiguration – Epi Webview</title>
|
||||||
|
|
||||||
|
<link href="css/styles.css" rel="stylesheet" />
|
||||||
|
<link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet" crossorigin="anonymous" />
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/js/all.min.js" crossorigin="anonymous"></script>
|
||||||
|
<script src="js/jquery-3.5.1.min.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.deprecated { color: #c62828; }
|
||||||
|
.cfg-help { margin-left: .35rem; cursor: help; }
|
||||||
|
.form-group { margin-bottom: .75rem; }
|
||||||
|
.small-muted { font-size:.85rem; color:#6c757d; }
|
||||||
|
.checkbox-inline { display:flex; align-items:center; gap:.5rem; }
|
||||||
|
.cfg-label { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(function(){
|
||||||
|
// Tooltips (Bootstrap 4)
|
||||||
|
$('[data-toggle="tooltip"]').tooltip();
|
||||||
|
|
||||||
|
// Side UI nachladen wie in deinem Template
|
||||||
|
loadSidenav();
|
||||||
|
loadFooter();
|
||||||
|
});
|
||||||
|
function loadSidenav(){
|
||||||
|
$('#layoutSidenav_nav').load('../sources/getSidenav.php');
|
||||||
|
}
|
||||||
|
function loadFooter(){
|
||||||
|
$('#footerholder').load('../sources/getFooter.php');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body class="sb-nav-fixed">
|
||||||
|
<nav class="sb-topnav navbar navbar-expand navbar-dark bg-dark">
|
||||||
|
<a class="navbar-brand" href="index.php">Epi Webview</a>
|
||||||
|
<button class="btn btn-link btn-sm order-1 order-lg-0" id="sidebarToggle" type="button"><i class="fas fa-bars"></i></button>
|
||||||
|
<ul class="navbar-nav ml-auto ml-md-0"></ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div id="layoutSidenav">
|
||||||
|
<div id="layoutSidenav_nav"></div>
|
||||||
|
<div id="layoutSidenav_content">
|
||||||
|
<main>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<h1 class="mt-4">Konfiguration</h1>
|
||||||
|
<ol class="breadcrumb mb-4">
|
||||||
|
<li class="breadcrumb-item active">Dashboard / Konfiguration</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<?php if ($message): ?>
|
||||||
|
<div class="alert alert-success"><?= h($message) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($err): ?>
|
||||||
|
<div class="alert alert-danger"><?= h($err) ?></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<i class="fas fa-sliders-h mr-1"></i>
|
||||||
|
Einstellungen
|
||||||
|
<span class="small-muted">( <code><?=h(basename($CONFIG_FILE))?></code> Source: <code><?=h(basename($EXAMPLE_FILE))?></code>)</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post">
|
||||||
|
<input type="hidden" name="__save_config" value="1" />
|
||||||
|
|
||||||
|
<?php foreach ($example['ui'] as $item): ?>
|
||||||
|
<?php
|
||||||
|
if ($item['kind'] === 'section') {
|
||||||
|
echo '<h5 class="mt-3">'.h($item['title']).'</h5>';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($item['kind'] === 'hr') {
|
||||||
|
echo '<hr />';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($item['kind'] === 'note') {
|
||||||
|
echo '<p class="small-muted">'.h($item['text']).'</p>';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($item['kind'] !== 'define') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = $item['name'];
|
||||||
|
// Nur Keys rendern, die in example vorkommen
|
||||||
|
if (!isset($example['defines'][$key])) continue;
|
||||||
|
|
||||||
|
[$type, $val, $tooltip] = valueForForm($example, $current, $key);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="cfg-label" for="cfg_<?=h($key)?>">
|
||||||
|
<?= h($key) ?>
|
||||||
|
<?php if ($tooltip): ?>
|
||||||
|
<i class="far fa-question-circle cfg-help"
|
||||||
|
data-toggle="tooltip" data-placement="right"
|
||||||
|
title="<?=h($tooltip)?>"></i>
|
||||||
|
<?php endif; ?>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<?php if ($type === 'bool'): ?>
|
||||||
|
<div class="checkbox-inline">
|
||||||
|
<input type="checkbox"
|
||||||
|
id="cfg_<?=h($key)?>"
|
||||||
|
name="cfg[<?=h($key)?>]"
|
||||||
|
value="1" <?= $val ? 'checked' : '' ?> />
|
||||||
|
<span><?= $val ? 'Aktiviert' : 'Deaktiviert' ?></span>
|
||||||
|
</div>
|
||||||
|
<?php elseif ($type === 'int'): ?>
|
||||||
|
<input type="number" step="1" class="form-control"
|
||||||
|
id="cfg_<?=h($key)?>"
|
||||||
|
name="cfg[<?=h($key)?>]"
|
||||||
|
value="<?=h((string)$val)?>" />
|
||||||
|
<?php else: ?>
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
id="cfg_<?=h($key)?>"
|
||||||
|
name="cfg[<?=h($key)?>]"
|
||||||
|
value="<?=h((string)$val)?>" />
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php if (!empty($deprecatedKeys)): ?>
|
||||||
|
<hr />
|
||||||
|
<h5 class="deprecated">Veraltete Parameter (nur in <code><?=h(basename($CONFIG_FILE))?></code>, nicht mehr in <code><?=h(basename($EXAMPLE_FILE))?></code>)</h5>
|
||||||
|
<?php foreach ($deprecatedKeys as $key): ?>
|
||||||
|
<?php $c = $current['defines'][$key]; ?>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="cfg-label deprecated" for="dep_<?=h($key)?>">
|
||||||
|
<?= h($key) ?>
|
||||||
|
<?php if (!empty($c['comment'])): ?>
|
||||||
|
<i class="far fa-question-circle cfg-help" data-toggle="tooltip" data-placement="right" title="<?=h($c['comment'])?>"></i>
|
||||||
|
<?php endif; ?>
|
||||||
|
</label>
|
||||||
|
<input type="text" class="form-control is-invalid" id="dep_<?=h($key)?>" value="<?=
|
||||||
|
h($c['type']==='bool' ? ($c['value']?'true':'false') : (string)$c['value'])
|
||||||
|
?>" disabled />
|
||||||
|
<div class="invalid-feedback">Dieser Parameter ist in der aktuellen Version nicht mehr vorgesehen.</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<button type="submit" class="btn btn-primary"><i class="fas fa-save mr-1"></i> Speichern</button>
|
||||||
|
<span class="small-muted ml-2">Speichert nach <code><?=h($CONFIG_FILE)?></code></span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<div id="footerholder"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
|
||||||
|
<script src="js/scripts.js"></script>
|
||||||
|
<script>
|
||||||
|
// Sidebar Toggle (falls benötigt)
|
||||||
|
$('#sidebarToggle').on('click', function(){ $('body').toggleClass('sb-sidenav-toggled'); });
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,58 +1,59 @@
|
|||||||
<?php
|
<?php
|
||||||
//EPIRENT CONNECTION EINSTELLUNGEN//
|
// @section: Epirent – Verbindung
|
||||||
define('Epirent_Server', 'xxx');
|
define('Epirent_Server', 'xxx');
|
||||||
define('Epirent_Connectionprotocol', 'http');
|
define('Epirent_Connectionprotocol', 'http');
|
||||||
define('Epirent_Port', '8080');
|
define('Epirent_Port', '8080');
|
||||||
define('Epirent_Token', 'xxx');
|
define('Epirent_Token', 'xxx');
|
||||||
define('Epirent_Mandant', '0'); //NORMALERWEISE 0
|
define('Epirent_Mandant', '0'); //NORMALERWEISE 0
|
||||||
|
// @hr
|
||||||
//CREWBRAIN CONNECTION EINSTELLUNGEN-NUR AUSFÜLLEN WENN GEWÜNSCHT//
|
// @section: Crewbrain Verbindung
|
||||||
|
// @note: Nur ausfüllen wenn gewünscht
|
||||||
define('CrewBrain_Username', 'xxx');
|
define('CrewBrain_Username', 'xxx');
|
||||||
define('CrewBrain_password', 'xxx');
|
define('CrewBrain_password', 'xxx');
|
||||||
define('CrewBrain_Server', 'xxx.crewbrain.com');
|
define('CrewBrain_Server', 'xxx.crewbrain.com');
|
||||||
define('CrewBrain_Connectionprotocol', 'https');
|
define('CrewBrain_Connectionprotocol', 'https');
|
||||||
define('CrewBrain_TaskListID', 6);
|
define('CrewBrain_TaskListID', 6);
|
||||||
|
|
||||||
//CREWBRAIN SPEZIFISCHE EINSTELLUNGEN (AUFGABENLISTE)//
|
|
||||||
define('Enable_QR_Code_CrewBrainAufgaben', true);
|
define('Enable_QR_Code_CrewBrainAufgaben', true);
|
||||||
|
// @hr
|
||||||
//EPIRENT SPEZIFISCHE EINSTELLUNGEN (CHECK IN & CHECK OUT)
|
// @section: Epirent-Spezifische Einstellungen
|
||||||
define('Enable_QR_Code_CheckOut', false); //Zeigt statt der Packscheinnummer einen Scanbaren QR Code für den CheckOut an
|
define('Enable_QR_Code_CheckOut', false); //Zeigt statt der Packscheinnummer einen Scanbaren QR Code für den CheckOut an
|
||||||
define('Enable_QR_Code_CheckIn', false); //Zeigt statt der Packscheinnummer einen Scanbaren QR Code für den CheckIn an
|
define('Enable_QR_Code_CheckIn', false); //Zeigt statt der Packscheinnummer einen Scanbaren QR Code für den CheckIn an
|
||||||
define('Vorbereitungs_Zeitvariable', 'Packen'); //Name des zu verwendenden Zeitabschnitts, der Zusätzlich zur DispoZeit beim Check Out Angezeigt werden soll
|
define('Vorbereitungs_Zeitvariable', 'Packen'); //Name des zu verwendenden Zeitabschnitts, der Zusätzlich zur DispoZeit beim Check Out Angezeigt werden soll
|
||||||
define('Nachbereitung_Zeitvariable', 'Rückpacken'); //Name des zu verwendenden Zeitabschnitts, der Zusätzlich zur DispoZeit beim Check In Angezeigt werden soll
|
define('Nachbereitung_Zeitvariable', 'Rückpacken'); //Name des zu verwendenden Zeitabschnitts aus dem Auftrag, der die Nachbereitung angibt
|
||||||
define('Rückpacken_Zeitvariable', 'Rückpacken');
|
define('Rückpacken_Zeitvariable', 'Rückpacken'); //Name des zu verwendenden Zeitabschnitts aus dem Auftrag, der das Rückpacken angibt.
|
||||||
|
define('UseDeliveredForCheckOutCompleted', true); //Nutzt statt dem erfolgreichen abschließen eines Packscheins durch ausbuchen das erfolgreiche Liefern (Geliefert != 00.00.00)
|
||||||
/** -------------------- Row-Marking: Konfig Zusände --------------------
|
define('EnableScrollingCheckOut', true); //Aktiviert das automatische Scrollen der CheckOut Liste
|
||||||
* 1 = $packingjob->date_start (Dispo Start)
|
define('EnableScrollingCheckIn', true); //Aktiviert das automatische Scrollen der CheckIn Liste
|
||||||
* 2 = $VorbereitungsTimeDetail->date_start (Vorbereitung Start)
|
define('EnableScrollingAufgaben', true); //Aktiviert das automatische Scrollen der Aufgabenliste
|
||||||
* 3 = $PackingNoteDetail->date_packing (Packen Zeit)
|
// @note: -------------------- CheckOutRowMarkSource: Konfig Zusände --------------------
|
||||||
* 4 = $PackingNoteDetail->date_delivery (Delivery Zeit)
|
// @note: 1 = $packingjob->date_start (Dispo Start)
|
||||||
*/
|
// @note: 2 = $VorbereitungsTimeDetail->date_start (Vorbereitung Start)
|
||||||
|
// @note: 3 = $PackingNoteDetail->date_packing (Packen Zeit)
|
||||||
|
// @note: 4 = $PackingNoteDetail->date_delivery (Delivery Zeit)
|
||||||
define('CheckOutRowMarkSource', 4);
|
define('CheckOutRowMarkSource', 4);
|
||||||
|
|
||||||
/** -------------------- Row-Marking: Konfig Zusände --------------------
|
// @note: -------------------- CheckInRowMarkSource: Konfig Zusände --------------------
|
||||||
* 1 = $packingjob->date_end (Dispo Ende)
|
// @note: 1 = $packingjob->date_end (Dispo Ende)
|
||||||
* 2 = $NachbereitungssTimeDetail->date_start (Nachbereitung Start)
|
// @note: 2 = $NachbereitungssTimeDetail->date_start (Nachbereitung Start)
|
||||||
* 3 = $RePackagingTimeDetail->date_start (Rückpacken Zeit AUS AUFTRAG)
|
// @note: 3 = $RePackagingTimeDetail->date_start (Rückpacken Zeit AUS AUFTRAG)
|
||||||
* 4 = $PackingNoteDetail->date_redelivery (ReDelivery Zeit)
|
// @note: 4 = $PackingNoteDetail->date_redelivery (ReDelivery Zeit)
|
||||||
*/
|
|
||||||
define('CheckInRowMarkSource', 4);
|
define('CheckInRowMarkSource', 4);
|
||||||
|
|
||||||
define('CheckIn_UseDispoEndForRowMarking', false); //else: Use Same Variable as "Rueckpacken Zeitvariable" | Konfiguration, welche Zeit für die Zeilenmarkierung beim Check In Verwendet werden soll
|
define('CheckIn_UseDispoEndForRowMarking', false); //else: Use Same Variable as "Rueckpacken Zeitvariable" | Konfiguration, welche Zeit für die Zeilenmarkierung beim Check In Verwendet werden soll
|
||||||
|
|
||||||
define('ShowCheckoutTimeOnCheckout', true);
|
define('ShowCheckoutTimeOnCheckout', true); //Zeigt die Checkout Zeit im Checkout
|
||||||
define('ShowVorbereitungTimeOnCheckout', true);
|
define('ShowVorbereitungTimeOnCheckout', true); //Zeigt die Vorbereitungs Zeitvariable im Checkout
|
||||||
define('ShowPackagingTimeOnCheckout', true);
|
define('ShowPackagingTimeOnCheckout', true); //Zeigt die Packenzeit im Checkout
|
||||||
define('ShowDeliveryTimeOnCheckout', true);
|
define('ShowDeliveryTimeOnCheckout', true); //Zeigt die Lieferzeit im Checkout
|
||||||
define('ShowTimesOnCheckout', true);
|
define('ShowTimesOnCheckout', true); //Aktiviert das anzeigen der Uhrzeit im Checkout
|
||||||
|
// @hr
|
||||||
define('ShowCheckInTimeOnCheckin', true);
|
define('ShowCheckInTimeOnCheckin', true); //Zeigt die CheckIn Zeit im CheckIn
|
||||||
define('ShowNachbereitungTimeOnCheckin', true);
|
define('ShowNachbereitungTimeOnCheckin', true); //Zeigt die Nachbereitungs Zeitvariable im CheckIn
|
||||||
define('ShowRePackagingTimeOnCheckin', true);
|
define('ShowRePackagingTimeOnCheckin', true); //Zeigt die Rückpackzeit im Checkin <br><b>Achtung: Zeit nur Im Auftrag festlegbar</b>
|
||||||
define('ShowReDeliveryTimeOnCheckin', true);
|
define('ShowReDeliveryTimeOnCheckin', true); //Zeigt die geplante Rücklieferung im Checkin
|
||||||
define('ShowTimesOnCheckin', true);
|
define('ShowTimesOnCheckin', true); //Aktiviert das anzeigen der Uhrzeit im CheckIn
|
||||||
|
|
||||||
|
// @section: Epirent-Spezifische Einstellungen - Shipping
|
||||||
define('ShowShippingIcons', true); //Zeigt Lieferung / Rücklieferungs Icons an
|
define('ShowShippingIcons', true); //Zeigt Lieferung / Rücklieferungs Icons an
|
||||||
define('KurierContainsText', 'Kurier'); //Text, der in der Versandart enthalten sein muss (enthält), damit diese als KURIER erkannt wird.
|
define('KurierContainsText', 'Kurier'); //Text, der in der Versandart enthalten sein muss (enthält), damit diese als KURIER erkannt wird.
|
||||||
define('SpeditionContainsText', 'Spedition'); //Text, der in der Versandart enthalten sein muss (enthält), damit diese als SPEDITION erkannt wird.
|
define('SpeditionContainsText', 'Spedition'); //Text, der in der Versandart enthalten sein muss (enthält), damit diese als SPEDITION erkannt wird.
|
||||||
@@ -66,8 +67,7 @@ define('ShippingOutOrganizedStatus', 'Hinversand OK'); //Packschein Status (Epir
|
|||||||
define('ShippingInOrganizedStatus', 'Rückversand OK'); //Packschein Status (EpirentDropdown) der Gematcht werden muss, dass der Rückversand Organisiert / Bestellt wurde
|
define('ShippingInOrganizedStatus', 'Rückversand OK'); //Packschein Status (EpirentDropdown) der Gematcht werden muss, dass der Rückversand Organisiert / Bestellt wurde
|
||||||
define('ShippingOrganizedStatus', 'Versand OK'); //Packschein Status (EpirentDropdown) der Gematcht werden muss, dass der komplette Versand Organisiert / Bestellt wurde
|
define('ShippingOrganizedStatus', 'Versand OK'); //Packschein Status (EpirentDropdown) der Gematcht werden muss, dass der komplette Versand Organisiert / Bestellt wurde
|
||||||
|
|
||||||
define('UseDeliveredForCheckOutCompleted', true); //Nutzt statt dem erfolgreichen abschließen eines Packscheins durch ausbuchen das erfolgreiche Liefern (Geliefert != 00.00.00)
|
// @section: Epirent-Spezifische Einstellungen - Anzeigesortierung
|
||||||
|
|
||||||
define('CheckOut_FutureDays', -1); // Konfiguration, wie viele Tage in der Zukunft der CheckOut angezeigt werden soll. '-1' zeigt alle an. Abhängig von der Variablen CheckOut_UseDispoStartForRowMarking
|
define('CheckOut_FutureDays', -1); // Konfiguration, wie viele Tage in der Zukunft der CheckOut angezeigt werden soll. '-1' zeigt alle an. Abhängig von der Variablen CheckOut_UseDispoStartForRowMarking
|
||||||
define('CheckIn_FutureDays', -1); // Konfiguration, wie viele Tage in der Zukunft der CheckIn angezeigt werden soll. '-1' zeigt alle an. Abhängig von der Variablen CheckIn_UseDispoEndForRowMarking
|
define('CheckIn_FutureDays', -1); // Konfiguration, wie viele Tage in der Zukunft der CheckIn angezeigt werden soll. '-1' zeigt alle an. Abhängig von der Variablen CheckIn_UseDispoEndForRowMarking
|
||||||
define('SortCheckOut', 2); // Konfiguration, welcher Datensatz für die Sortierung Verwendet werden soll. Möglichkeiten '1': Packscheinnummer / '2': Dispostart
|
define('SortCheckOut', 2); // Konfiguration, welcher Datensatz für die Sortierung Verwendet werden soll. Möglichkeiten '1': Packscheinnummer / '2': Dispostart
|
||||||
|
|||||||
@@ -25,6 +25,11 @@
|
|||||||
<div class="sb-nav-link-icon"><i class="fas fa-list"></i></div>
|
<div class="sb-nav-link-icon"><i class="fas fa-list"></i></div>
|
||||||
Pack- & Aufgabenmonitor
|
Pack- & Aufgabenmonitor
|
||||||
</a>
|
</a>
|
||||||
|
<div class="sb-sidenav-menu-heading">Config</div>
|
||||||
|
<a class="nav-link" href=editconfig.php>
|
||||||
|
<div class="sb-nav-link-icon"><i class="fas fa-tachometer-alt"></i></div>
|
||||||
|
Config
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sb-sidenav-footer">
|
<div class="sb-sidenav-footer">
|
||||||
|
|||||||
Reference in New Issue
Block a user