3 Commits

Author SHA1 Message Date
36944c71bf -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
2025-10-10 10:46:33 +02:00
91c0a2d9d9 - Endzeiten bei CheckOut und Startzeiten bei CheckIn komplett entfernt
- Zeiten einzeln Ein- und Ausblendbar gemacht
- Quelle für Zeilenfarbe einstellbar gemacht
- Zeiten werden jetzt bei erreichen bzw überschreiten immer markiert (nur die Zeit)
- Nicht benötigte Spalten werden automatisch ausgeblendet
- Config Datei Ausgedünnt (Bitte komplette Epirent-Settings (nicht Login) Sektion ersetzen
2025-10-10 09:36:11 +02:00
0ff0c0d55e Endlos Scrollen der Listen implementiert (falls notwendig) 2025-10-09 20:42:19 +02:00
8 changed files with 1657 additions and 632 deletions

View File

@@ -1,113 +1,250 @@
<?php
require('config.php');
require('vendor/autoload.php');
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<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>
<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">
<title>Aufgabenmonitor</title>
<!-- Bootstrap -->
<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">
<!-- 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]-->
<!-- Bootstrap -->
<link rel="stylesheet" href="vendor/twbs/bootstrap/dist/css/bootstrap.min.css">
<link href="css/sticky-footer.css" rel="stylesheet">
<script type="text/javascript">
$(document).ready(function(){
refreshAufgabenTable();
<style>
body { background-color: #000; }
.tableFixHead { overflow:auto; }
.tableFixHead thead th {
position: sticky;
top: 0;
z-index: 1;
background-color: #212529; /* .table-dark head */
}
.table-dark td, .table-dark th { vertical-align: middle; }
</style>
});
function refreshAufgabenTable(){
$('#AufgabenTableHolder').load('sources/getAufgabenTable.php', function(){
setTimeout(refreshAufgabenTable, 30000);
});
}
// function refreshCheckInTable(){
// $('#getCheckInTableHolder').load('sources/getCheckInTable.php', function(){
// setTimeout(refreshCheckInTable, 5000);
// });
// }
</script>
</head>
<body style="background-color: black;">
<div class="container-fluid">
<div class="row">
<div class="col-lg">
<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>
<script src="scripts/jquery-3.5.1.min.js"></script>
</head>
<body>
<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>
<!-- 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) -->
<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>
</body>
function isEnabled() { return !!SCROLL_FLAGS.aufgaben; }
/* ===========================
Höhe der Scroll-Container
- 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>
<?php
// (Optional hier nicht benötigt, aber gelassen falls du später etwas anzeigst)
function getTimeFromSeconds(string $timestring) {
$hours = floor($timestring / 3600);
$mins = floor($timestring / 60 % 60);
$secs = floor($timestring % 60);
$timeFormat = sprintf('%02d:%02d', $hours, $mins);
return $timeFormat;
$hours = floor($timestring / 3600);
$mins = floor($timestring / 60 % 60);
$timeFormat = sprintf('%02d:%02d', $hours, $mins);
return $timeFormat;
}
?>

View File

@@ -22,53 +22,241 @@ $Epi = new Epirent();
<link href="css/sticky-footer.css" rel="stylesheet">
<script src="https://kit.fontawesome.com/93d71de8bc.js" crossorigin="anonymous"></script>
<!-- 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">
// Dynamische Höhe: Wrapper exakt bis Viewport-Unterkante
function sizeScrollContainers() {
$('.tableFixHead').each(function () {
const rect = this.getBoundingClientRect();
const bottomMargin = 16; // kleiner Abstand zum Rand
const available = window.innerHeight - rect.top - bottomMargin;
this.style.maxHeight = (available > 120 ? available : 120) + 'px';
});
}
<script type="text/javascript">
// === 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'; ?>
};
// Lädt tbody via AJAX und erhält die Scrollposition relativ zum unteren Rand
function smartLoad($scroller, $target, url, intervalMs) {
const scroller = $scroller.get(0);
// Abstand vom unteren Rand merken (wichtiger als absolute scrollTop)
const fromBottomBefore = scroller.scrollHeight - scroller.clientHeight - scroller.scrollTop;
// 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']
]);
$target.load(url, function () {
// Nach dem Ersetzen: dieselbe Distanz zum unteren Rand wiederherstellen
const newScrollTop = scroller.scrollHeight - scroller.clientHeight - fromBottomBefore;
// Begrenzen, falls weniger Inhalt
scroller.scrollTop = Math.max(0, newScrollTop);
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() {
const nodes = [
document.querySelector('#checkout-scroll'),
document.querySelector('#checkin-scroll'),
document.querySelector('#aufgaben-scroll')
].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';
}
});
}
// === Scroll-/Interaktionsstatus je Scroller ===
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 (!isEnabledByEl(el)) return; // nur wenn Feature aktiv
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;
}
// === Loop aufbauen: nahtloses Doppel nur bei Bedarf & nur wenn aktiv ===
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; // deaktiviert -> keine Loops
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; // Höhe des Original-Inhalts als Loop-Länge
}
// === Auto-Scroll (nahtlos) ===
function startAutoScroll($scroller, speedPx = 1, stepMs = 40) {
const el = $scroller.get(0);
if (!isEnabledByEl(el)) return;
const st = ensureState(el);
if (st.autoTimer) return; // schon aktiv
if (st.loopHeight <= 0) return; // kein Overflow => nicht scrollen
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, 80);
else stopAutoScroll($scroller);
}
// === AJAX-Reload: relative Position innerhalb der Loop erhalten ===
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);
startAutoScroll($scroller, 1, 80);
} else {
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
stopAutoScroll($scroller);
}
} else {
$table.find('tbody.__loopClone').remove();
stopAutoScroll($scroller);
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
}
setTimeout(() => smartLoad($scroller, $table, $target, url, intervalMs), intervalMs);
});
}
// === Initialisierung ===
$(document).ready(function () {
sizeScrollContainers();
$(window).on('resize', function () {
sizeScrollContainers();
['#checkout', '#checkin', '#aufgaben'].forEach(prefix => {
const $scroller = $(`${prefix}-scroll`);
const $table = $(`${prefix}-table`);
buildSeamlessLoop($table, $scroller);
maybeStartAutoScroll($scroller);
});
});
// Guards nur für aktivierte Scroller
[['#checkout-scroll','checkout'], ['#checkin-scroll','checkin'], ['#aufgaben-scroll','aufgaben']].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');
initOne('#aufgaben-scroll', '#aufgaben-table', '#AufgabenTableHolder', 'sources/getAufgabenTable.php', 30000, 'aufgaben');
});
</script>
// Nächstes Update planen
setTimeout(function () {
smartLoad($scroller, $target, url, intervalMs);
}, intervalMs);
});
}
$(document).ready(function () {
sizeScrollContainers();
$(window).on('resize', sizeScrollContainers);
// Initial laden + Auto-Refresh, jeweils eigener Scroller
smartLoad($('#checkout-scroll'), $('#getCheckOutTableHolder'), 'sources/getCheckOutTable.php', 5000);
smartLoad($('#checkin-scroll'), $('#getCheckInTableHolder'), 'sources/getCheckInTable.php', 5000);
smartLoad($('#aufgaben-scroll'), $('#AufgabenTableHolder'), 'sources/getAufgabenTable.php', 30000);
});
</script>
<style>
/* Scroll-Wrapper je Tabelle */
@@ -98,7 +286,7 @@ $Epi = new Epirent();
}
</style>
</head>
<body style="background-color: black;">
<body style="margin-bottom:0px; background-color: black;">
<div class="container-fluid">
<div class="row">
@@ -111,25 +299,31 @@ $Epi = new Epirent();
<th scope="col">#</th>
<th scope="col">Kunde</th>
<th scope="col">Event</th>
<th scope="col">
<?php
if (!HideDispoTimes) {
echo "Dispo-Start";
if(ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout||ShowPackagingTimeOnCheckout || ShowDeliveryTimeOnCheckout){
echo '<th scope="col">';
}
if (UsePackingNoteDateForCheckout) {
echo "<br><i>Packen-Start</i></th>";
} else {
echo "<br><i>VB-Start</i></th>";
}
if (!HideCheckInTimeOnCheckout) {
if(!HideDispoTimes){
echo "<th scope='col'>Dispo-Ende<br><i>VB-Ende</i></th>";
}else{
echo "<th scope='col'><i>VB-Ende</i></th>";
}
if(ShowCheckoutTimeOnCheckout){
echo "Dispo-Start";
}
if(ShowVorbereitungTimeOnCheckout){
if(ShowCheckoutTimeOnCheckout){echo "<br>";}
echo "VB-Start";
}
if(ShowPackagingTimeOnCheckout){
if(ShowCheckoutTimeOnCheckout||ShowVorbereitungTimeOnCheckout){echo "<br>";}
echo "Packen";
}
if(ShowDeliveryTimeOnCheckout){
if(ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout||ShowPackagingTimeOnCheckout){echo "<br>";}
echo "Liefern";
}
if(ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout||ShowPackagingTimeOnCheckout || ShowDeliveryTimeOnCheckout){
echo '</th>';
}
?>
<th scope="col">Status</th>
<?php
@@ -152,24 +346,31 @@ $Epi = new Epirent();
<tr>
<th scope="col">#</th>
<th scope="col">Kunde</th>
<th scope="col">Event</th>
<th scope="col">Event</th> <?php
if(ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin ||ShowRePackagingTimeOnCheckin || ShowReDeliveryTimeOnCheckin){
echo '<th scope="col">';
}
<?php
if (!HideCheckOutTimeOnCheckin) {
echo "<th scope='col'>Dispo-Start<br><i>RP-Start</i></th>";
}
if(ShowCheckInTimeOnCheckin){
echo "Dispo-Ende";
}
if(ShowNachbereitungTimeOnCheckin){
if(ShowCheckInTimeOnCheckin){echo "<br>";}
echo "Nachbereitung";
}
if(ShowRePackagingTimeOnCheckin){
if(ShowCheckInTimeOnCheckin||ShowNachbereitungTimeOnCheckin){echo "<br>";}
echo "Zurückpacken";
}
if(ShowReDeliveryTimeOnCheckin){
if(ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin||ShowRePackagingTimeOnCheckin){echo "<br>";}
echo "Rückliefern";
}
if(ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin||ShowRePackagingTimeOnCheckin || ShowReDeliveryTimeOnCheckin){
echo '</th>';
}
if(!HideDispoTimes){
echo "<th scope='col'>Dispo-Ende";
}else{
echo "<th scope='col'>";
}
if (UsePackingNoteDateForCheckin) {
echo "<br><i>Rücklieferung</i></th>";
} else {
echo "<br><i>RP-Ende</i></th>";
}
?>
?>
@@ -218,7 +419,6 @@ if (UsePackingNoteDateForCheckin) {
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<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>
</body>

View File

@@ -1,7 +1,4 @@
<?php
error_reporting(E_ALL);
require('config.php');
require('EpiApi.php');
require('vendor/autoload.php');
@@ -12,180 +9,383 @@ $Epi = new Epirent();
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<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>Packmonitor</title>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Packmonitor</title>
<!-- Bootstrap -->
<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">
<script src="https://kit.fontawesome.com/93d71de8bc.js" crossorigin="anonymous"></script>
<!-- Bootstrap -->
<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">
<script src="https://kit.fontawesome.com/93d71de8bc.js" crossorigin="anonymous"></script>
<!-- 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">
/* ===========================
Feature-Toggles aus PHP
=========================== */
const SCROLL_FLAGS = {
checkout: <?php echo (defined('EnableScrollingCheckOut') && EnableScrollingCheckOut) ? 'true' : 'false'; ?>,
checkin: <?php echo (defined('EnableScrollingCheckIn') && EnableScrollingCheckIn) ? 'true' : 'false'; ?>
};
<script type="text/javascript">
$(document).ready(function () {
refreshCheckOutTable();
refreshCheckInTable();
});
const SCROLLER_KEYS = new Map([
['checkout-scroll', 'checkout'],
['checkin-scroll', 'checkin']
]);
function refreshCheckOutTable() {
$('#getCheckOutTableHolder').load('sources/getCheckOutTable.php', function () {
setTimeout(refreshCheckOutTable, 5000);
});
}
function refreshCheckInTable() {
$('#getCheckInTableHolder').load('sources/getCheckInTable.php', function () {
setTimeout(refreshCheckInTable, 5000);
});
}
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;
</script>
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);
</head>
<body style="background-color: black;">
const bottomMargin = 16;
const minH = 120;
<div class="container-fluid">
<div class="row">
<div class="col-lg">
<h2 class="text-light">Check-Out
<?php
if (CheckOut_FutureDays != -1) {
echo "in den nächsten " . CheckOut_FutureDays . " Tagen";
}
?></h2>
<table class="table table-dark">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Kunde</th>
<th scope="col">Event</th>
<th scope="col">
<?php
if (!HideDispoTimes) {
echo "Dispo-Start";
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
=========================== */
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 (!isEnabledByEl(el)) 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 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;
}
if (UsePackingNoteDateForCheckout) {
echo "<br><i>Packen-Start</i></th>";
} else {
echo "<br><i>VB-Start</i></th>";
}
if (!HideCheckInTimeOnCheckout) {
if(!HideDispoTimes){
echo "<th scope='col'>Dispo-Ende<br><i>VB-Ende</i></th>";
}else{
echo "<th scope='col'><i>VB-Ende</i></th>";
}
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);
startAutoScroll($scroller, 1, 40);
} else {
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
stopAutoScroll($scroller);
}
} else {
$table.find('tbody.__loopClone').remove();
stopAutoScroll($scroller);
if (scEl.scrollTop !== 0) requestAnimationFrame(() => { scEl.scrollTop = 0; });
}
setTimeout(() => smartLoad($scroller, $table, $target, url, intervalMs), intervalMs);
});
}
/* ===========================
Initialisierung
=========================== */
$(document).ready(function () {
sizeScrollContainers();
$(window).on('resize', function () {
sizeScrollContainers();
['#checkout', '#checkin'].forEach(prefix => {
const $scroller = $(`${prefix}-scroll`);
const $table = $(`${prefix}-table`);
buildSeamlessLoop($table, $scroller);
maybeStartAutoScroll($scroller);
});
});
// 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>
/* Scroll-Wrapper je Tabelle */
.tableFixHead {
overflow-y: auto;
/* Höhe wird per JS dynamisch gesetzt, damit genau bis zum Viewport-Ende gescrollt wird */
max-height: 60vh; /* Fallback */
border: 1px solid rgba(255,255,255,.1);
border-radius: .25rem;
}
/* Sticky Header */
.tableFixHead thead th {
position: sticky;
top: 0;
z-index: 2; /* über Body-Zellen */
}
/* Saubere Hintergrundfarbe für sticky Header (Bootstrap .table-dark) */
.table-dark thead th {
background-color: #212529; /* gleiche Farbe wie .table-dark header */
}
/* Optional: dünne Trennlinien */
.table-dark tbody tr + tr td {
border-top: 1px solid rgba(255,255,255,.08);
}
body { background-color: black; margin-bottom: 0; }
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<!-- Check-Out -->
<div class="col-sm">
<h2 class="text-light">Check-Out</h2>
<div class="tableFixHead" id="checkout-scroll">
<table class="table table-dark mb-0" id="checkout-table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Kunde</th>
<th scope="col">Event</th>
<?php
if(ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout||ShowPackagingTimeOnCheckout || ShowDeliveryTimeOnCheckout){
echo '<th scope="col">';
}
if(ShowCheckoutTimeOnCheckout){
echo "Dispo-Start";
}
if(ShowVorbereitungTimeOnCheckout){
if(ShowCheckoutTimeOnCheckout){echo "<br>";}
echo "VB-Start";
}
if(ShowPackagingTimeOnCheckout){
if(ShowCheckoutTimeOnCheckout||ShowVorbereitungTimeOnCheckout){echo "<br>";}
echo "Packen";
}
if(ShowDeliveryTimeOnCheckout){
if(ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout||ShowPackagingTimeOnCheckout){echo "<br>";}
echo "Liefern";
}
if(ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout||ShowPackagingTimeOnCheckout || ShowDeliveryTimeOnCheckout){
echo '</th>';
}
?>
<th scope="col">Status</th>
<?php
if(ShowShippingIcons){
echo "<th scope='col'>Shipping</th>";
}
?>
</tr>
</thead>
<tbody id="getCheckOutTableHolder">
<th scope="col">Status</th>
<?php
if(ShowShippingIcons){
echo "<th scope='col'>Shipping</th>";
}
?>
</tr>
</thead>
<tbody id="getCheckOutTableHolder"></tbody>
</table>
</div>
</div>
</tbody>
</table>
</div>
<div class="col-lg">
<h2 class="text-light">Check-In
<?php
if (CheckIn_FutureDays != -1) {
echo "in den nächsten " . CheckIn_FutureDays . " Tagen";
}
?></h2>
<table class="table table-dark">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Kunde</th>
<th scope="col">Event</th>
<?php
if (!HideCheckOutTimeOnCheckin) {
echo "<th scope='col'>Dispo-Start<br><i>RP-Start</i></th>";
}
if(!HideDispoTimes){
echo "<th scope='col'>Dispo-Ende";
}else{
echo "<th scope='col'>";
}
if (UsePackingNoteDateForCheckin) {
echo "<br><i>Rücklieferung</i></th>";
} else {
echo "<br><i>RP-Ende</i></th>";
}
?>
<th scope="col">Status</th>
<?php
if(ShowShippingIcons){
echo "<th scope='col'>Shipping</th>";
}
?>
</tr>
</thead>
<tbody id="getCheckInTableHolder">
</tbody>
</table>
</div>
</div>
</header>
<!-- Check-In -->
<div class="col-sm">
<h2 class="text-light">Check-In</h2>
<div class="tableFixHead" id="checkin-scroll">
<table class="table table-dark mb-0" id="checkin-table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Kunde</th>
<th scope="col">Event</th>
<?php
if(ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin ||ShowRePackagingTimeOnCheckin || ShowReDeliveryTimeOnCheckin){
echo '<th scope="col">';
}
if(ShowCheckInTimeOnCheckin){
echo "Dispo-Ende";
}
if(ShowNachbereitungTimeOnCheckin){
if(ShowCheckInTimeOnCheckin){echo "<br>";}
echo "Nachbereitung";
}
if(ShowRePackagingTimeOnCheckin){
if(ShowCheckInTimeOnCheckin||ShowNachbereitungTimeOnCheckin){echo "<br>";}
echo "Zurückpacken";
}
if(ShowReDeliveryTimeOnCheckin){
if(ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin||ShowRePackagingTimeOnCheckin){echo "<br>";}
echo "Rückliefern";
}
if(ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin||ShowRePackagingTimeOnCheckin || ShowReDeliveryTimeOnCheckin){
echo '</th>';
}
?>
<th scope="col">Status</th>
<?php
if(ShowShippingIcons){
echo "<th scope='col'>Shipping</th>";
}
?>
</tr>
</thead>
<tbody id="getCheckInTableHolder"></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<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>
</body>
<script src="vendor/twbs/bootstrap/dist/js/bootstrap.min.js" crossorigin="anonymous"></script>
</body>
</html>
<?php
function getTimeFromSeconds(string $timestring) {
$hours = floor($timestring / 3600);
$mins = floor($timestring / 60 % 60);
$secs = floor($timestring % 60);
$timeFormat = sprintf('%02d:%02d', $hours, $mins);
return $timeFormat;
$hours = floor($timestring / 3600);
$mins = floor($timestring / 60 % 60);
return sprintf('%02d:%02d', $hours, $mins);
}
?>

413
dist/editconfig.php vendored Normal file
View 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>

View File

@@ -1,38 +1,59 @@
<?php
//EPIRENT CONNECTION EINSTELLUNGEN//
// @section: Epirent Verbindung
define('Epirent_Server', 'xxx');
define('Epirent_Connectionprotocol', 'http');
define('Epirent_Port', '8080');
define('Epirent_Token', 'xxx');
define('Epirent_Mandant', '0'); //NORMALERWEISE 0
//CREWBRAIN CONNECTION EINSTELLUNGEN-NUR AUSFÜLLEN WENN GEWÜNSCHT//
// @hr
// @section: Crewbrain Verbindung
// @note: Nur ausfüllen wenn gewünscht
define('CrewBrain_Username', 'xxx');
define('CrewBrain_password', 'xxx');
define('CrewBrain_Server', 'xxx.crewbrain.com');
define('CrewBrain_Connectionprotocol', 'https');
define('CrewBrain_TaskListID', 6);
//CREWBRAIN SPEZIFISCHE EINSTELLUNGEN (AUFGABENLISTE)//
define('Enable_QR_Code_CrewBrainAufgaben', true);
//EPIRENT SPEZIFISCHE EINSTELLUNGEN (CHECK IN & CHECK OUT)
// @hr
// @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_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('Rueckpacken_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'); //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)
define('EnableScrollingCheckOut', true); //Aktiviert das automatische Scrollen der CheckOut Liste
define('EnableScrollingCheckIn', true); //Aktiviert das automatische Scrollen der CheckIn Liste
define('EnableScrollingAufgaben', true); //Aktiviert das automatische Scrollen der Aufgabenliste
// @note: -------------------- CheckOutRowMarkSource: Konfig Zusände --------------------
// @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('UsePackingNoteDateForCheckout', true); // Nutzt statt den Zeitabscnitten aus dem Auftrag die Informationen aus dem Packschein für den Checkout. Wenn die UseDispo Variablen false sind, werden diese Variablen für das Rowmarking genutzt falls "true".
define('UsePackingNoteDateForCheckin', true); // Nutzt statt den Zeitabscnitten aus dem Auftrag die Informationen aus dem Packschein für den Checkout. Wenn die UseDispo Variablen false sind, werden diese Variablen für das Rowmarking genutzt falls "true".
// @note: -------------------- CheckInRowMarkSource: Konfig Zusände --------------------
// @note: 1 = $packingjob->date_end (Dispo Ende)
// @note: 2 = $NachbereitungssTimeDetail->date_start (Nachbereitung Start)
// @note: 3 = $RePackagingTimeDetail->date_start (Rückpacken Zeit AUS AUFTRAG)
// @note: 4 = $PackingNoteDetail->date_redelivery (ReDelivery Zeit)
define('CheckInRowMarkSource', 4);
define('CheckOut_UseDispoStartForRowMarking', false); //else: Use Same Variable as "Vorbereitung Zeitvariable" | Konfiguration, welche Zeit für die Zeilenmarkierung beim CheckOut 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('HideCheckInTimeOnCheckout', true); //Versteckt die CheckIn Zeit im Checkout
define('HideCheckOutTimeOnCheckin', true); //Versteckt die CheckOut Zeit im CheckIn
define('HideDispoTimes', true); //Versteckt Dispo-Zeiten Komplett aus den Listen
define('ShowCheckoutTimeOnCheckout', true); //Zeigt die Checkout Zeit im Checkout
define('ShowVorbereitungTimeOnCheckout', true); //Zeigt die Vorbereitungs Zeitvariable im Checkout
define('ShowPackagingTimeOnCheckout', true); //Zeigt die Packenzeit im Checkout
define('ShowDeliveryTimeOnCheckout', true); //Zeigt die Lieferzeit im Checkout
define('ShowTimesOnCheckout', true); //Aktiviert das anzeigen der Uhrzeit im Checkout
// @hr
define('ShowCheckInTimeOnCheckin', true); //Zeigt die CheckIn Zeit im CheckIn
define('ShowNachbereitungTimeOnCheckin', true); //Zeigt die Nachbereitungs Zeitvariable im CheckIn
define('ShowRePackagingTimeOnCheckin', true); //Zeigt die Rückpackzeit im Checkin <br><b>Achtung: Zeit nur Im Auftrag festlegbar</b>
define('ShowReDeliveryTimeOnCheckin', true); //Zeigt die geplante Rücklieferung im Checkin
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('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.
@@ -46,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('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('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

View File

@@ -10,215 +10,250 @@ use chillerlan\QRCode\{
require('../vendor/autoload.php');
$options = new QROptions([
'imageBase64' => false,
'qrCodeHeight' => 75,
'qrCodeWidth' => 75,
'version' => -1,
'quietzoneSize' => 1
]);
]);
$Epi = new Epirent();
$result = $Epi->requestEpiApi('/v1/packingnote/open?isco=False&cl=' . Epirent_Mandant);
$data_output = json_decode($result)->payload;
/** ---------- Helpers & Marking (analog Checkout) ---------- */
const APP_TZ = 'Europe/Berlin';
function dt(?string $s): ?DateTimeImmutable {
if (!$s) return null;
try {
return new DateTimeImmutable($s, new DateTimeZone(APP_TZ));
} catch (Throwable $e) {
return null;
}
}
function dayStart(DateTimeImmutable $d): DateTimeImmutable {
return $d->setTime(0, 0, 0);
}
function rowClassForDate(?DateTimeImmutable $markDate, ?DateTimeImmutable $today): string {
if (!$markDate || !$today) return '';
if ($markDate == $today) return 'text-dark bg-warning';
if ($markDate < $today) return 'bg-danger';
return '';
}
/**
* Row-Marking Quelle (Check-In):
* 1 = $packingjob->date_end
* 2 = $NachbereitungsTimeDetail->date_start
* 3 = $RePackagingTimeDetail->date_start
* 4 = $PackingNoteDetail->date_redelivery
* Fallback: $packingjob->date_end
*/
function resolveRowMarkDateCheckIn(
$packingjob,
$NachbereitungsTimeDetail,
$RePackagingTimeDetail,
$PackingNoteDetail,
int $source
): ?DateTimeImmutable {
$candidate = null;
switch ($source) {
case 1: $candidate = dt($packingjob->date_end ?? null); break;
case 2: $candidate = dt($NachbereitungsTimeDetail->date_start ?? null); break;
case 3: $candidate = dt($RePackagingTimeDetail->date_start ?? null); break;
case 4: $candidate = dt($PackingNoteDetail->date_redelivery ?? null); break;
default: $candidate = null; break;
}
if (!$candidate) {
$candidate = dt($packingjob->date_end ?? null);
}
return $candidate ? dayStart($candidate) : null;
}
function echoMarkedTimeLine(?string $dateStr, ?int $timeSeconds, DateTimeImmutable $today, $ReturnTime): void {
if (!$dateStr) return;
$d = dt($dateStr);
if (!$d) return;
$mark = dayStart($d);
$cls = rowClassForDate($mark, $today);
echo $cls ? "<span class=\"{$cls}\">" : "";
echo date_format(new \DateTime($dateStr), 'd.m.Y');
if ($ReturnTime) {
echo " " . getTimeFromSeconds((string)$timeSeconds);
}
echo $cls ? "</span>" : "";
}
/** ---------- Sortierung nach Ende ---------- */
if (SortCheckIn == 2) {
// Prüfen, ob $data_output ein Array ist
if (is_array($data_output)) {
usort($data_output, function ($a, $b) {
// Konvertiere time_start von Millisekunden in Sekunden
$timeStartA = $a->time_end / 1000; // Zeit in Sekunden
$timeStartB = $b->time_end / 1000;
// Kombiniere date_start mit time_start
$datetimeA = strtotime($a->date_end) + $timeStartA;
$datetimeB = strtotime($b->date_end) + $timeStartB;
// Vergleich für die Sortierung
$timeEndA = $a->time_end / 1000;
$timeEndB = $b->time_end / 1000;
$datetimeA = strtotime($a->date_end) + $timeEndA;
$datetimeB = strtotime($b->date_end) + $timeEndB;
return $datetimeA <=> $datetimeB;
});
// Sortierte Daten ausgeben oder weiterverarbeiten
// print_r($data_output);
} else {
echo "Daten konnten nicht verarbeitet werden.";
}
}
/** ---------- Tabelle ---------- */
foreach ($data_output as $packingjob) {
if ($packingjob->is_archived != true) {
//get OrderDetails
// OrderDetails
$result = $Epi->requestEpiApi('/v1/order/' . $packingjob->order_pk . '?cl=' . Epirent_Mandant);
$orderdetail_output = json_decode($result)->payload[0];
// get PackingNote Details
// PackingNote Details
$PackingNoteDetailResult = $Epi->requestEpiApi('/v1/packingnote/' . $packingjob->primary_key . '?cl=' . Epirent_Mandant);
$PackingNote_data_output = json_decode($PackingNoteDetailResult)->payload[0];
$PackingNoteDetail = json_decode($PackingNoteDetailResult)->payload[0];
$NachbereitungsTimeDetail;
// Zeit-Slots aus dem Schedule
$NachbereitungsTimeDetail = null;
$RePackagingTimeDetail = null;
foreach ($orderdetail_output->order_schedule as $scheduledetail) {
if (UsePackingNoteDateForCheckin) {
$tempTimeObject = new stdClass();
$tempTimeObject->date_end = $PackingNote_data_output->date_redelivery;
$tempTimeObject->time_end = $PackingNote_data_output->time_redelivery;
print($PackingNote_data_output->time_redelivery);
$NachbereitungsTimeDetail = $tempTimeObject;
} else {
if ($scheduledetail->name == Rueckpacken_Zeitvariable) {
if ($scheduledetail->name == Nachbereitung_Zeitvariable) {
$NachbereitungsTimeDetail = $scheduledetail;
}
if ($scheduledetail->name == Rückpacken_Zeitvariable) {
$RePackagingTimeDetail = $scheduledetail;
}
}
//End Of get Order Details
if (CheckIn_UseDispoEndForRowMarking || ($NachbereitungsTimeDetail->date_start == null)) {
$date = new DateTime($packingjob->date_end);
} else {
$date = new DateTime($NachbereitungsTimeDetail->date_start);
// --- Row-Marking bestimmen (konfigurierbar, analog Checkout) ---
$today = dayStart(new DateTimeImmutable('today', new DateTimeZone(APP_TZ)));
$limit = null;
if (CheckIn_FutureDays != -1) {
$limit = $today->modify('+' . (int)CheckIn_FutureDays . ' day');
}
$date->setTime(0, 0, 0);
$today = new DateTime();
$today->setTime(0, 0, 0);
$todayFilter = new DateTime();
$todayFilter->setTime(0, 0, 0);
if (CheckIn_FutureDays == -1 || $date <= ($todayFilter->modify('+' . CheckIn_FutureDays . ' day'))) {
//prüfe, ob entweder unbegrenzte (-1) Anzeige Aktiv ist, oder das Datum kleiner oder Gleich heute + Zukunftsspanne ist
if ($date == $today) {
// Override-Flag wie bei Checkout: Dispo-Ende erzwingen
$source = CheckIn_UseDispoEndForRowMarking ? 1 : (int)CheckInRowMarkSource;
echo "<tr class='text-dark bg-warning'>";
} else if ($date < $today) {
echo "<tr class=' bg-danger'>";
} else {
echo "<tr>";
}
$markDate = resolveRowMarkDateCheckIn(
$packingjob,
$NachbereitungsTimeDetail,
$RePackagingTimeDetail,
$PackingNoteDetail,
$source
);
if (CheckIn_FutureDays == -1 || ($markDate && (!$limit || $markDate <= $limit))) {
$trClass = rowClassForDate($markDate, $today);
echo $trClass ? "<tr class='{$trClass}'>" : "<tr>";
// QR / Kopfspalten
if (Enable_QR_Code_CheckIn) {
echo "<td>" . '<div style="width: 5vb;">' . (new QRCode($options))->render($packingjob->packingnote_no) . "</div></td>";
echo "<td><div style=\"width: 5vb;\">" . (new QRCode($options))->render($packingjob->packingnote_no) . "</div></td>";
} else {
echo "<td>" . $packingjob->packingnote_no . "</td>";
}
echo "<td>" . $packingjob->contact->name . "</td>";
echo "<td>" . $packingjob->event . "</td>";
if(!HideCheckOutTimeOnCheckin){
if ($NachbereitungsTimeDetail->date_start != null) {
// Zeitspalte öffnen, wenn mindestens eine Anzeige aktiv ist
if (ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin || ShowRePackagingTimeOnCheckin || ShowReDeliveryTimeOnCheckin) {
echo "<td>";
if(!HideDispoTimes){
echo "<small>" . date_format(new \DateTime($packingjob->date_start), 'd.m.Y') . " " . getTimeFromSeconds($packingjob->time_start) . "</small><br>";
}
echo "<i>" . date_format(new \DateTime($NachbereitungsTimeDetail->date_start), 'd.m.Y') . " " . getTimeFromSeconds($NachbereitungsTimeDetail->time_start) . "</i></td>";
} else if(!HideDispoTimes){
echo "<td>" . date_format(new \DateTime($packingjob->date_start), 'd.m.Y') . " " . getTimeFromSeconds($packingjob->time_start) . "</td>";
}
}
if ($NachbereitungsTimeDetail->date_end != null) {
echo "<td>";
if(!HideDispoTimes){
echo "<small>" . date_format(new \DateTime($packingjob->date_end), 'd.m.Y') . " " . getTimeFromSeconds($packingjob->time_end) . "</small><br>";
}
echo "<i>" . date_format(new \DateTime($NachbereitungsTimeDetail->date_end), 'd.m.Y') . " " . getTimeFromSeconds($NachbereitungsTimeDetail->time_end) . "</i></td>";
} else if(!HideDispoTimes){
echo "<td>" . date_format(new \DateTime($packingjob->date_end), 'd.m.Y') . " " . getTimeFromSeconds($packingjob->time_end) . "</td>";
// Dispo-Ende (Check-In Termin) — ENDE korrekt verwendet
if (ShowCheckInTimeOnCheckin && $packingjob->date_end != null) {
echoMarkedTimeLine($packingjob->date_end, (int)$packingjob->time_end, $today, ShowTimesOnCheckin);
}
// Nachbereitung (START) — wie von dir bereits genutzt
if (ShowNachbereitungTimeOnCheckin && $NachbereitungsTimeDetail && $NachbereitungsTimeDetail->date_start != null) {
if (ShowCheckInTimeOnCheckin) echo "<br>";
echoMarkedTimeLine($NachbereitungsTimeDetail->date_start, (int)$NachbereitungsTimeDetail->time_start, $today, ShowTimesOnCheckin);
}
// Rückpacken (START)
if (ShowRePackagingTimeOnCheckin && $RePackagingTimeDetail && $RePackagingTimeDetail->date_start != null) {
if (ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin) echo "<br>";
echoMarkedTimeLine($RePackagingTimeDetail->date_start, (int)$RePackagingTimeDetail->time_start, $today, ShowTimesOnCheckin);
}
// Rücklieferung (REDELIVERY) — delivery ↔ redelivery gespiegelt
if (ShowReDeliveryTimeOnCheckin && $PackingNoteDetail && $PackingNoteDetail->date_redelivery != null) {
if (ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin || ShowRePackagingTimeOnCheckin) echo "<br>";
echoMarkedTimeLine($PackingNoteDetail->date_redelivery, (int)$PackingNoteDetail->time_redelivery, $today, ShowTimesOnCheckin);
}
if (ShowCheckInTimeOnCheckin || ShowNachbereitungTimeOnCheckin || ShowRePackagingTimeOnCheckin || ShowReDeliveryTimeOnCheckin) {
echo "</td>";
}
// Fortschritt
echo "<td>";
if ($packingjob->is_all_in ==0) {
if ($packingjob->is_all_in == 0) {
echo "<span class='badge badge-success'>";
} else {
echo '<div class="progress"><div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: '.(( $packingjob->pieces_sum_total - abs($packingjob->is_all_out) - abs($packingjob->is_all_in)) / ($packingjob->pieces_sum_total - $packingjob ->is_all_out)) *100 .'%" aria-valuenow="' .( $packingjob->pieces_sum_total - abs($packingjob->is_all_out) - abs($packingjob->is_all_in)) . '" aria-valuemin="0" aria-valuemax="' . ($packingjob->pieces_sum_total - $packingjob ->is_all_out). '"></div></div>';
echo '<div class="progress"><div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: ' .
(( $packingjob->pieces_sum_total - abs($packingjob->is_all_out) - abs($packingjob->is_all_in)) / ($packingjob->pieces_sum_total - $packingjob->is_all_out)) * 100 .
'%" aria-valuenow="' . ( $packingjob->pieces_sum_total - abs($packingjob->is_all_out) - abs($packingjob->is_all_in)) .
'" aria-valuemin="0" aria-valuemax="' . ($packingjob->pieces_sum_total - $packingjob->is_all_out) . '"></div></div>';
echo "<span class='badge badge-info'>";
}
echo ( $packingjob->pieces_sum_total - abs($packingjob->is_all_out) - abs($packingjob->is_all_in)) . "/" . ($packingjob->pieces_sum_total - $packingjob ->is_all_out) . " (" . $packingjob->pieces_sum_total.")";
echo ( $packingjob->pieces_sum_total - abs($packingjob->is_all_out) - abs($packingjob->is_all_in)) . "/" . ($packingjob->pieces_sum_total - $packingjob->is_all_out) . " (" . $packingjob->pieces_sum_total . ")";
echo "</span></td>";
if(ShowShippingIcons){
// Shipping-Icons (inbound)
if (ShowShippingIcons) {
if (UseShippingStatus) {
if (($PackingNoteDetail->status == ShippingInOrganizedStatus) || ($PackingNoteDetail->status == ShippingOrganizedStatus)) {
echo "<td style='color:#66FF00; text-align:center;'>";
} else {
echo "<td style='text-align:center;'>";
}
if(UseShippingStatus){
if(($PackingNote_data_output->status==ShippingInOrganizedStatus)||($PackingNote_data_output->status==ShippingOrganizedStatus)){
echo "<td style='color:#66FF00; text-align:center;'>";
}else{
echo "<td style='text-align:center;'>";
}
if($PackingNote_data_output->is_self_redeliver){
echo '<i class="fa-solid fa-person-walking fa-lg"></i>';
} else{
if(preg_match('/'.KurierContainsText.'/i',$PackingNote_data_output->shipping_in)){
echo '<i class="fa-solid fa-hand-holding-dollar fa-lg"></i>';
}
if(preg_match('/'.SpeditionContainsText.'/i',$PackingNote_data_output->shipping_in)){
echo '<i class="fa-solid fa-truck fa-lg"></i>';
}
if(preg_match('/'.DHLContainsText.'/i',$PackingNote_data_output->shipping_in)){
echo '<i class="fa-brands fa-dhl fa-2xl"></i>';
}
if(preg_match('/'.LKWContainsText.'/i',$PackingNote_data_output->shipping_in)){
echo '<i class="fa-solid fa-industry fa-lg"></i><i class="fa-solid fa-truck fa-lg"></i>';
}
if(preg_match('/'.TransporterContainsText.'/i',$PackingNote_data_output->shipping_in)){
echo '<i class="fa-solid fa-industry fa-lg"></i><i class="fa-solid fa-van-shuttle fa-lg"></i>';
}
if(preg_match('/'.PKWContainsText.'/i',$PackingNote_data_output->shipping_in)){
echo '<i class="fa-solid fa-industry fa-lg"></i><i class="fa-solid fa-car fa-lg"></i>';
if ($PackingNoteDetail->is_self_redeliver) {
echo '<i class="fa-solid fa-person-walking fa-lg"></i>';
} else {
if (preg_match('/' . KurierContainsText . '/i', $PackingNoteDetail->shipping_in)) {
echo '<i class="fa-solid fa-hand-holding-dollar fa-lg"></i>';
}
if (preg_match('/' . SpeditionContainsText . '/i', $PackingNoteDetail->shipping_in)) {
echo '<i class="fa-solid fa-truck fa-lg"></i>';
}
if (preg_match('/' . DHLContainsText . '/i', $PackingNoteDetail->shipping_in)) {
echo '<i class="fa-brands fa-dhl fa-2xl"></i>';
}
if (preg_match('/' . LKWContainsText . '/i', $PackingNoteDetail->shipping_in)) {
echo '<i class="fa-solid fa-industry fa-lg"></i><i class="fa-solid fa-truck fa-lg"></i>';
}
if (preg_match('/' . TransporterContainsText . '/i', $PackingNoteDetail->shipping_in)) {
echo '<i class="fa-solid fa-industry fa-lg"></i><i class="fa-solid fa-van-shuttle fa-lg"></i>';
}
if (preg_match('/' . PKWContainsText . '/i', $PackingNoteDetail->shipping_in)) {
echo '<i class="fa-solid fa-industry fa-lg"></i><i class="fa-solid fa-car fa-lg"></i>';
}
}
echo "</td>";
}
echo "</td>";
}
echo "</tr>";
}
echo "</tr>";
}
}
}
}
function getTimeFromSeconds(string $timestring) {
$hours = floor($timestring / 3600);
$mins = floor($timestring / 60 % 60);
$secs = floor($timestring % 60);
$mins = floor($timestring / 60 % 60);
$secs = floor($timestring % 60);
$timeFormat = sprintf('%02d:%02d', $hours, $mins);
return $timeFormat;
}

View File

@@ -1,5 +1,6 @@
<?php
error_reporting(E_ALL);
require('../config.php');
require('../EpiApi.php');
@@ -19,23 +20,64 @@ $options = new QROptions([
'quietzoneSize' => 1
]);
$Epi = new Epirent();
if(UseDeliveredForCheckOutCompleted){
$result = $Epi->requestEpiApi('/v1/packingnote/open?isco=False&cl=' . Epirent_Mandant);
const APP_TZ = 'Europe/Berlin';
}else{
$result = $Epi->requestEpiApi('/v1/packingnote/open?isci=False&cl=' . Epirent_Mandant);
/** Hilfsfunktionen für die Row-Marking-Logik (ohne weitere Änderungen am Rest) */
function dt(?string $s): ?DateTimeImmutable {
if (!$s)
return null;
try {
return new DateTimeImmutable($s, new DateTimeZone(APP_TZ));
} catch (Throwable $e) {
return null;
}
}
function dayStart(DateTimeImmutable $d): DateTimeImmutable {
return $d->setTime(0, 0, 0);
}
function resolveRowMarkDate($packingjob, $VorbereitungsTimeDetail, $PackingNoteDetail, int $source): ?DateTimeImmutable {
$candidate = null;
switch ($source) {
case 1: $candidate = dt($packingjob->date_start ?? null);
break;
case 2: $candidate = dt($VorbereitungsTimeDetail->date_start ?? null);
break;
case 3: $candidate = dt($PackingNoteDetail->date_packing ?? null);
break;
case 4: $candidate = dt($PackingNoteDetail->date_delivery ?? null);
break;
default: $candidate = null;
break;
}
if (!$candidate) {
$candidate = dt($packingjob->date_start ?? null);
}
return $candidate ? dayStart($candidate) : null;
}
function rowClassForDate(?DateTimeImmutable $markDate, ?DateTimeImmutable $today): string {
if (!$markDate || !$today)
return '';
if ($markDate == $today)
return 'text-dark bg-warning';
if ($markDate < $today)
return 'bg-danger';
return '';
}
if (UseDeliveredForCheckOutCompleted) {
$result = $Epi->requestEpiApi('/v1/packingnote/open?isco=False&cl=' . Epirent_Mandant);
} else {
$result = $Epi->requestEpiApi('/v1/packingnote/open?isci=False&cl=' . Epirent_Mandant);
}
$data_output = json_decode($result)->payload;
if (SortCheckOut == 2) {
// Prüfen, ob $data_output ein Array ist
if (is_array($data_output)) {
usort($data_output, function ($a, $b) {
@@ -60,70 +102,54 @@ if (SortCheckOut == 2) {
foreach ($data_output as $packingjob) {
$PackingNoteDetail = null;
$PackingNote_data_output;
if($packingjob->is_archived != true && UseDeliveredForCheckOutCompleted){
$PackingNoteDetailResult = $Epi->requestEpiApi('/v1/packingnote/' . $packingjob->primary_key . '?cl=' . Epirent_Mandant);
$PackingNote_data_output = json_decode($PackingNoteDetailResult)->payload[0];
}
if ($packingjob->is_archived != true && (!UseDeliveredForCheckOutCompleted || ($PackingNote_data_output->date_delivered == "0000-00-00" && $PackingNote_data_output->time_delivered == 0))) {
if ($packingjob->is_archived != true && UseDeliveredForCheckOutCompleted) {
$PackingNoteDetailResult = $Epi->requestEpiApi('/v1/packingnote/' . $packingjob->primary_key . '?cl=' . Epirent_Mandant);
$PackingNoteDetail = json_decode($PackingNoteDetailResult)->payload[0];
}
if (
$packingjob->is_archived != true
&& (
!UseDeliveredForCheckOutCompleted
|| ($PackingNoteDetail->date_delivered !== "0000-00-00" || (int)$PackingNoteDetail->time_delivered !== 0)
)
&& (int)$PackingNoteDetail->is_all_out !== 0
) {
//get OrderDetails
$result = $Epi->requestEpiApi('/v1/order/' . $packingjob->order_pk . '?cl=' . Epirent_Mandant);
$orderdetail_output = json_decode($result)->payload[0];
// get PackingNote Details
if(!UseDeliveredForCheckOutCompleted){
$PackingNoteDetailResult = $Epi->requestEpiApi('/v1/packingnote/' . $packingjob->primary_key . '?cl=' . Epirent_Mandant);
$PackingNote_data_output = json_decode($PackingNoteDetailResult)->payload[0];
// get PackingNote Details, aber nur wenn nicht schon vor der schleife geholt.
if (!UseDeliveredForCheckOutCompleted) {
$PackingNoteDetailResult = $Epi->requestEpiApi('/v1/packingnote/' . $packingjob->primary_key . '?cl=' . Epirent_Mandant);
$PackingNoteDetail = json_decode($PackingNoteDetailResult)->payload[0];
}
$VorbereitungsTimeDetail;
$VorbereitungsTimeDetail = null;
foreach ($orderdetail_output->order_schedule as $scheduledetail) {
if (UsePackingNoteDateForCheckout) {
$tempTimeObject = new stdClass();
$tempTimeObject->date_start = $PackingNote_data_output->date_packing;
$tempTimeObject->time_start = $PackingNote_data_output->time_packing;
$VorbereitungsTimeDetail = $tempTimeObject;
} else {
if ($scheduledetail->name == Vorbereitungs_Zeitvariable) {
$VorbereitungsTimeDetail = $scheduledetail;
}
if ($scheduledetail->name == Vorbereitungs_Zeitvariable) {
$VorbereitungsTimeDetail = $scheduledetail;
}
}
//End Of get Order Details
if (CheckOut_UseDispoStartForRowMarking || ($VorbereitungsTimeDetail->date_start == null)) {
$date = new DateTime($packingjob->date_start);
} else if(UseDeliveredForCheckOutCompleted ){
{
$date = new DateTime($PackingNote_data_output->date_delivery);
}
}else{
$date = new DateTime($VorbereitungsTimeDetail->date_start);
// --- Row-Marking Datum bestimmen (konfigurierbar) ---
$today = dayStart(new DateTimeImmutable('today', new DateTimeZone(APP_TZ)));
$todayFilter = $today; // für die Window-Berechnung
$limit = null;
if (CheckOut_FutureDays != -1) {
$limit = $todayFilter->modify('+' . (int) CheckOut_FutureDays . ' day');
}
$date->setTime(0, 0, 0);
$today = new DateTime();
$today->setTime(0, 0, 0);
$markDate = resolveRowMarkDate($packingjob, $VorbereitungsTimeDetail, $PackingNoteDetail, (int) CheckOutRowMarkSource);
$todayFilter = new DateTime();
$todayFilter->setTime(0, 0, 0);
if (CheckOut_FutureDays == -1 || $date <= ($todayFilter->modify('+' . CheckOut_FutureDays . ' day'))) {
//prüfe, ob entweder unbegrenzte (-1) Anzeige Aktiv ist, oder das Datum kleiner oder Gleich heute + Zukunftsspanne ist
if ($date == $today) {
if (CheckOut_FutureDays == -1 || ($markDate && $markDate <= $limit)) {
$trClass = rowClassForDate($markDate, $today);
echo "<tr class='text-dark bg-warning'>";
} else if ($date < $today) {
echo "<tr class=' bg-danger'>";
if ($trClass) {
echo "<tr class='{$trClass}'>";
} else {
echo "<tr>";
}
@@ -136,62 +162,38 @@ if($packingjob->is_archived != true && UseDeliveredForCheckOutCompleted){
echo "<td>" . $packingjob->contact->name . "</td>";
echo "<td>" . $packingjob->event . "</td>";
if (ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout || ShowPackagingTimeOnCheckout || ShowDeliveryTimeOnCheckout) { echo "<td>";}
if(UseDeliveredForCheckOutCompleted){
if (CheckOut_UseDispoStartForRowMarking || ($VorbereitungsTimeDetail->date_start == null)) {
$date = new DateTime($packingjob->date_start);
} else {
$date = new DateTime($VorbereitungsTimeDetail->date_start);
if (ShowCheckoutTimeOnCheckout && $packingjob->date_start != null) {
echoMarkedTimeLine($packingjob->date_start, (int) $packingjob->time_start, $today, ShowTimesOnCheckout);
}
if (ShowVorbereitungTimeOnCheckout && $VorbereitungsTimeDetail && $VorbereitungsTimeDetail->date_start != null) {
if (ShowCheckoutTimeOnCheckout) {
echo "<br>";
}
if ($date == $today) {
echo "<td class='text-dark bg-warning'>";
} else if ($date < $today) {
echo "<td class=' bg-danger'>";
} else {
echo "<td>";
echoMarkedTimeLine($VorbereitungsTimeDetail->date_start, (int) $VorbereitungsTimeDetail->time_start, $today, ShowTimesOnCheckout);
}
if (ShowPackagingTimeOnCheckout && $PackingNoteDetail && $PackingNoteDetail->date_packing != null) {
if (ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout) {
echo "<br>";
}
echoMarkedTimeLine($PackingNoteDetail->date_packing, (int) $PackingNoteDetail->time_packing, $today, ShowTimesOnCheckout);
}
if (ShowDeliveryTimeOnCheckout && $PackingNoteDetail && $PackingNoteDetail->date_delivery != null) {
if (ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout || ShowPackagingTimeOnCheckout) {
echo "<br>";
}
echoMarkedTimeLine($PackingNoteDetail->date_delivery, (int) $PackingNoteDetail->time_delivery, $today, ShowTimesOnCheckout);
}
}else{
if (ShowCheckoutTimeOnCheckout || ShowVorbereitungTimeOnCheckout || ShowPackagingTimeOnCheckout || ShowDeliveryTimeOnCheckout) {
echo "</td>";
}
echo "<td>";
}
if ($VorbereitungsTimeDetail->date_start != null) {
if(!HideDispoTimes){
echo "<small>".date_format(new \DateTime($packingjob->date_start), 'd.m.Y') . " " . getTimeFromSeconds($packingjob->time_start) . "</small><br>";
}
echo "<i>" . date_format(new \DateTime($VorbereitungsTimeDetail->date_start), 'd.m.Y') . " " . getTimeFromSeconds($VorbereitungsTimeDetail->time_start) . "</i></td>";
} else if(!HideDispoTimes){
date_format(new \DateTime($packingjob->date_start), 'd.m.Y') . " " . getTimeFromSeconds($packingjob->time_start) . "</td>";
}
if (!HideCheckInTimeOnCheckout) {
if (($VorbereitungsTimeDetail->date_end != null)) {
if(!HideDispoTimes){
echo "<small>" . date_format(new \DateTime($packingjob->date_end), 'd.m.Y') . " " . getTimeFromSeconds($packingjob->time_end) . "</small><br>";
}
echo "<i>" . date_format(new \DateTime($VorbereitungsTimeDetail->date_end), 'd.m.Y') . " " . getTimeFromSeconds($VorbereitungsTimeDetail->time_end) . "</i></td>";
} else if(!HideDispoTimes){
echo date_format(new \DateTime($packingjob->date_end), 'd.m.Y') . " " . getTimeFromSeconds($packingjob->time_end) . "</td>";
}
}
echo "<td>";
if ($packingjob->is_all_out == 0) {
echo "<span class='badge badge-success'>";
} else {
@@ -204,51 +206,64 @@ if($packingjob->is_archived != true && UseDeliveredForCheckOutCompleted){
echo ($packingjob->pieces_sum_total - abs($packingjob->is_all_out)) . "/" . $packingjob->pieces_sum_total;
echo "</span></td>";
if(ShowShippingIcons){
if (ShowShippingIcons) {
if(UseShippingStatus){
if(($PackingNote_data_output->status==ShippingOutOrganizedStatus)||($PackingNote_data_output->status==ShippingOrganizedStatus)){
echo "<td style='color:#66FF00; text-align:center;'>";
}else{
echo "<td style='text-align:center;'>";
if (UseShippingStatus) {
if (($PackingNoteDetail->status == ShippingOutOrganizedStatus) || ($PackingNoteDetail->status == ShippingOrganizedStatus)) {
echo "<td style='color:#66FF00; text-align:center;'>";
} else {
echo "<td style='text-align:center;'>";
}
}
if($PackingNote_data_output->is_self_pickup){
echo '<i class="fa-solid fa-person-walking fa-lg"></i>';
} else{
if(preg_match('/'.KurierContainsText.'/i',$PackingNote_data_output->shipping_out)){
echo '<i class="fa-solid fa-hand-holding-dollar fa-lg"></i>';
if ($PackingNoteDetail->is_self_pickup) {
echo '<i class="fa-solid fa-person-walking fa-lg"></i>';
} else {
if (preg_match('/' . KurierContainsText . '/i', $PackingNoteDetail->shipping_out)) {
echo '<i class="fa-solid fa-hand-holding-dollar fa-lg"></i>';
}
if (preg_match('/' . SpeditionContainsText . '/i', $PackingNoteDetail->shipping_out)) {
echo '<i class="fa-solid fa-truck fa-lg"></i>';
}
if (preg_match('/' . DHLContainsText . '/i', $PackingNoteDetail->shipping_out)) {
echo '<i class="fa-brands fa-dhl fa-2xl"></i>';
}
if (preg_match('/' . LKWContainsText . '/i', $PackingNoteDetail->shipping_out)) {
echo '<i class="fa-solid fa-industry fa-lg"></i><i class="fa-solid fa-truck fa-lg"></i>';
}
if (preg_match('/' . TransporterContainsText . '/i', $PackingNoteDetail->shipping_out)) {
echo '<i class="fa-solid fa-industry fa-lg"></i><i class="fa-solid fa-van-shuttle fa-lg"></i>';
}
if (preg_match('/' . PKWContainsText . '/i', $PackingNoteDetail->shipping_out)) {
echo '<i class="fa-solid fa-industry fa-lg"></i><i class="fa-solid fa-car fa-lg"></i>';
}
}
if(preg_match('/'.SpeditionContainsText.'/i',$PackingNote_data_output->shipping_out)){
echo '<i class="fa-solid fa-truck fa-lg"></i>';
}
if(preg_match('/'.DHLContainsText.'/i',$PackingNote_data_output->shipping_out)){
echo '<i class="fa-brands fa-dhl fa-2xl"></i>';
}
if(preg_match('/'.LKWContainsText.'/i',$PackingNote_data_output->shipping_out)){
echo '<i class="fa-solid fa-industry fa-lg"></i><i class="fa-solid fa-truck fa-lg"></i>';
}
if(preg_match('/'.TransporterContainsText.'/i',$PackingNote_data_output->shipping_out)){
echo '<i class="fa-solid fa-industry fa-lg"></i><i class="fa-solid fa-van-shuttle fa-lg"></i>';
}
if(preg_match('/'.PKWContainsText.'/i',$PackingNote_data_output->shipping_out)){
echo '<i class="fa-solid fa-industry fa-lg"></i><i class="fa-solid fa-car fa-lg"></i>';
}
}
echo "</td>";
echo "</td>";
}
echo "</tr>";
}
echo "</tr>";
}
echo "</tr>";
}
}
}
function echoMarkedTimeLine(?string $dateStr, ?int $timeSeconds, DateTimeImmutable $today, $ReturnTime): void {
if (!$dateStr)
return;
$d = dt($dateStr);
if (!$d)
return;
$mark = dayStart($d);
$cls = rowClassForDate($mark, $today);
echo $cls ? "<span class=\"{$cls}\">" : "";
echo date_format(new \DateTime($dateStr), 'd.m.Y');
if($ReturnTime){echo " ".getTimeFromSeconds((string) $timeSeconds);}
echo $cls ? "</span>" : "";
}
function getTimeFromSeconds(string $timestring) {
$hours = floor($timestring / 3600);

View File

@@ -25,6 +25,11 @@
<div class="sb-nav-link-icon"><i class="fas fa-list"></i></div>
Pack- & Aufgabenmonitor
</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 class="sb-sidenav-footer">