This commit is contained in:
2026-02-02 19:12:40 +01:00
parent c6421049c8
commit 952c353118
17 changed files with 982 additions and 513 deletions

View File

@@ -234,6 +234,14 @@
</div>
</div>
<div class="form-group" style="margin-top: 20px;">
<h3 style="margin-bottom: 10px;">Check-in URL-Konfiguration</h3>
<p style="margin-bottom: 15px; color: #666;">Definieren Sie die Basis-URL für alle Check-in und Check-out URLs. Diese URL wird vor <code>/api</code> verwendet. Beispiel: https://example.com:3334</p>
<label for="checkinRootUrl">Check-in Root URL</label>
<input type="text" id="checkinRootUrl" name="checkin_root_url" class="form-control" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;" placeholder="https://example.com:3334" value="<%= (typeof options !== 'undefined' && options && options.checkin_root_url) ? options.checkin_root_url : '' %>">
<small style="color: #666; display: block; margin-top: 5px;">Lassen Sie dieses Feld leer, um die URL automatisch aus der aktuellen Seite zu generieren.</small>
</div>
<button type="submit" class="btn btn-primary">Optionen speichern</button>
</form>
</div>
@@ -289,7 +297,7 @@
<div class="form-row">
<div class="form-group">
<label for="ldapUsernameAttr">Username-Attribut</label>
<input type="text" id="ldapUsernameAttr" name="username_attribute" placeholder="cn" value="cn">
<input type="text" id="ldapUsernameAttr" name="username_attribute" placeholder="sAMAccountName" value="sAMAccountName">
</div>
<div class="form-group">

View File

@@ -99,28 +99,59 @@
Automatische Zeiterfassung
</h3>
<div id="timeCaptureContent" style="display: none; margin-top: 15px;">
<!-- URL-Erfassung -->
<!-- Interne URLs -->
<div style="margin-bottom: 20px;">
<h4 style="font-size: 13px; margin-bottom: 10px; color: #555; display: flex; align-items: center; gap: 5px;">
Zeiterfassung per URL
<span class="help-icon" onclick="showHelpModal('url-help')" style="cursor: pointer; color: #3498db; font-size: 14px; font-weight: bold; width: 18px; height: 18px; border-radius: 50%; background: #e8f4f8; display: inline-flex; align-items: center; justify-content: center; line-height: 1;">?</span>
<h4 style="font-size: 13px; margin-bottom: 10px; color: #555; cursor: pointer; user-select: none; display: flex; align-items: center; gap: 8px;" onclick="toggleInternalUrls()">
<span class="toggle-icon-internal-urls" style="display: inline-block; transition: transform 0.3s; font-size: 10px;">▶</span>
Interne URLs
<span class="help-icon" onclick="event.stopPropagation(); showHelpModal('url-help')" style="cursor: pointer; color: #3498db; font-size: 14px; font-weight: bold; width: 18px; height: 18px; border-radius: 50%; background: #e8f4f8; display: inline-flex; align-items: center; justify-content: center; line-height: 1; margin-left: auto;">?</span>
</h4>
<div class="form-group" style="margin-bottom: 15px;">
<label style="font-size: 12px; color: #666; margin-bottom: 5px;">Check-in URL</label>
<div style="display: flex; gap: 5px;">
<input type="text" id="checkinUrl" readonly style="flex: 1; padding: 8px; font-size: 11px; border: 1px solid #ddd; border-radius: 4px; background: #f8f9fa;">
<button onclick="copyToClipboard('checkinUrl')" class="btn btn-sm btn-secondary" style="padding: 8px 12px;">Kopieren</button>
<div id="internalUrlsContent" style="display: none; margin-top: 10px;">
<div class="form-group" style="margin-bottom: 15px;">
<label style="font-size: 12px; color: #666; margin-bottom: 5px;">Check-in URL</label>
<div style="display: flex; gap: 5px;">
<input type="text" id="checkinUrlInternal" readonly style="flex: 1; padding: 8px; font-size: 11px; border: 1px solid #ddd; border-radius: 4px; background: #f8f9fa;">
<button onclick="copyToClipboard('checkinUrlInternal')" class="btn btn-sm btn-secondary" style="padding: 8px 12px;">Kopieren</button>
</div>
</div>
<div class="form-group" style="margin-bottom: 15px;">
<label style="font-size: 12px; color: #666; margin-bottom: 5px;">Check-out URL</label>
<div style="display: flex; gap: 5px;">
<input type="text" id="checkoutUrlInternal" readonly style="flex: 1; padding: 8px; font-size: 11px; border: 1px solid #ddd; border-radius: 4px; background: #f8f9fa;">
<button onclick="copyToClipboard('checkoutUrlInternal')" class="btn btn-sm btn-secondary" style="padding: 8px 12px;">Kopieren</button>
</div>
</div>
<div style="margin-top: 12px;">
<a href="/api/dashboard/qr-pdf/internal" class="btn btn-sm btn-secondary" style="padding: 8px 12px; text-decoration: none; display: inline-block;" download>QR-Code-PDF herunterladen</a>
</div>
</div>
<div class="form-group" style="margin-bottom: 15px;">
<label style="font-size: 12px; color: #666; margin-bottom: 5px;">Check-out URL</label>
<div style="display: flex; gap: 5px;">
<input type="text" id="checkoutUrl" readonly style="flex: 1; padding: 8px; font-size: 11px; border: 1px solid #ddd; border-radius: 4px; background: #f8f9fa;">
<button onclick="copyToClipboard('checkoutUrl')" class="btn btn-sm btn-secondary" style="padding: 8px 12px;">Kopieren</button>
</div>
<!-- Externe URLs -->
<div id="externalUrlsSection" style="margin-bottom: 20px; display: none;">
<h4 style="font-size: 13px; margin-bottom: 10px; color: #555; cursor: pointer; user-select: none; display: flex; align-items: center; gap: 8px;" onclick="toggleExternalUrls()">
<span class="toggle-icon-external-urls" style="display: inline-block; transition: transform 0.3s; font-size: 10px;">▶</span>
Externe URLs
<span class="help-icon" onclick="event.stopPropagation(); showHelpModal('url-help')" style="cursor: pointer; color: #3498db; font-size: 14px; font-weight: bold; width: 18px; height: 18px; border-radius: 50%; background: #e8f4f8; display: inline-flex; align-items: center; justify-content: center; line-height: 1; margin-left: auto;">?</span>
</h4>
<div id="externalUrlsContent" style="display: none; margin-top: 10px;">
<div class="form-group" style="margin-bottom: 15px;">
<label style="font-size: 12px; color: #666; margin-bottom: 5px;">Check-in URL</label>
<div style="display: flex; gap: 5px;">
<input type="text" id="checkinUrlExternal" readonly style="flex: 1; padding: 8px; font-size: 11px; border: 1px solid #ddd; border-radius: 4px; background: #f8f9fa;">
<button onclick="copyToClipboard('checkinUrlExternal')" class="btn btn-sm btn-secondary" style="padding: 8px 12px;">Kopieren</button>
</div>
</div>
<div class="form-group" style="margin-bottom: 15px;">
<label style="font-size: 12px; color: #666; margin-bottom: 5px;">Check-out URL</label>
<div style="display: flex; gap: 5px;">
<input type="text" id="checkoutUrlExternal" readonly style="flex: 1; padding: 8px; font-size: 11px; border: 1px solid #ddd; border-radius: 4px; background: #f8f9fa;">
<button onclick="copyToClipboard('checkoutUrlExternal')" class="btn btn-sm btn-secondary" style="padding: 8px 12px;">Kopieren</button>
</div>
</div>
<div style="margin-top: 12px;">
<a href="/api/dashboard/qr-pdf/external" class="btn btn-sm btn-secondary" style="padding: 8px 12px; text-decoration: none; display: inline-block;" download>QR-Code-PDF herunterladen</a>
</div>
</div>
<div style="margin-top: 12px;">
<a href="/api/dashboard/qr-pdf" class="btn btn-sm btn-secondary" style="padding: 8px 12px; text-decoration: none; display: inline-block;" download>QR-Code-PDF herunterladen</a>
</div>
</div>
@@ -180,6 +211,38 @@
}
}
// Interne URLs ein-/ausklappen
function toggleInternalUrls() {
const content = document.getElementById('internalUrlsContent');
const icon = document.querySelector('.toggle-icon-internal-urls');
if (content && icon) {
if (content.style.display === 'none') {
content.style.display = 'block';
icon.style.transform = 'rotate(90deg)';
} else {
content.style.display = 'none';
icon.style.transform = 'rotate(0deg)';
}
}
}
// Externe URLs ein-/ausklappen
function toggleExternalUrls() {
const content = document.getElementById('externalUrlsContent');
const icon = document.querySelector('.toggle-icon-external-urls');
if (content && icon) {
if (content.style.display === 'none') {
content.style.display = 'block';
icon.style.transform = 'rotate(90deg)';
} else {
content.style.display = 'none';
icon.style.transform = 'rotate(0deg)';
}
}
}
// URL-Kopier-Funktion
function copyToClipboard(inputId) {
const input = document.getElementById(inputId);
@@ -201,24 +264,56 @@
}
// URLs mit aktueller Domain aktualisieren (Port 3334 für Check-in)
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded', async function() {
const userId = '<%= user.id %>';
// Interne URLs: Konstruiere aus window.location.origin (Port 3334 für Check-in)
const baseUrl = window.location.origin;
// Check-in URLs verwenden Port 3334
// Ersetze Port in URL oder füge Port hinzu falls nicht vorhanden
let checkinBaseUrl;
let internalBaseUrl;
if (baseUrl.match(/:\d+$/)) {
// Port vorhanden - ersetze ihn
checkinBaseUrl = baseUrl.replace(/:\d+$/, ':3334');
internalBaseUrl = baseUrl.replace(/:\d+$/, ':3334');
} else {
// Kein Port - füge Port hinzu
const url = new URL(baseUrl);
checkinBaseUrl = `${url.protocol}//${url.hostname}:3334`;
internalBaseUrl = `${url.protocol}//${url.hostname}:3334`;
}
// Setze interne URLs
const checkinInputInternal = document.getElementById('checkinUrlInternal');
const checkoutInputInternal = document.getElementById('checkoutUrlInternal');
if (checkinInputInternal) checkinInputInternal.value = `${internalBaseUrl}/api/checkin/${userId}`;
if (checkoutInputInternal) checkoutInputInternal.value = `${internalBaseUrl}/api/checkout/${userId}`;
// Externe URLs: Versuche Root URL von API zu laden
let externalBaseUrl = null;
try {
const response = await fetch('/api/checkin-root-url');
const result = await response.json();
if (result.root_url && result.root_url.trim() !== '') {
externalBaseUrl = result.root_url.trim();
// Stelle sicher, dass kein trailing slash vorhanden ist
externalBaseUrl = externalBaseUrl.replace(/\/+$/, '');
}
} catch (error) {
console.warn('Fehler beim Laden der externen Root URL:', error);
}
// Zeige externe Sektion nur an, wenn Root URL konfiguriert ist
const externalSection = document.getElementById('externalUrlsSection');
if (externalBaseUrl) {
// Setze externe URLs
const checkinInputExternal = document.getElementById('checkinUrlExternal');
const checkoutInputExternal = document.getElementById('checkoutUrlExternal');
if (checkinInputExternal) checkinInputExternal.value = `${externalBaseUrl}/api/checkin/${userId}`;
if (checkoutInputExternal) checkoutInputExternal.value = `${externalBaseUrl}/api/checkout/${userId}`;
// Zeige externe Sektion an
if (externalSection) externalSection.style.display = 'block';
} else {
// Verstecke externe Sektion
if (externalSection) externalSection.style.display = 'none';
}
const checkinInput = document.getElementById('checkinUrl');
const checkoutInput = document.getElementById('checkoutUrl');
if (checkinInput) checkinInput.value = `${checkinBaseUrl}/api/checkin/${userId}`;
if (checkoutInput) checkoutInput.value = `${checkinBaseUrl}/api/checkout/${userId}`;
// Rollenwechsel-Handler
const roleSwitcher = document.getElementById('roleSwitcher');