/**
* Mercedes-Benz MYP Admin Guest Requests Management
* Moderne Verwaltung von Gastaufträgen mit Live-Updates
*/
// Globale Variablen
let currentRequests = [];
let filteredRequests = [];
let currentPage = 0;
let totalPages = 0;
let totalRequests = 0;
let refreshInterval = null;
let csrfToken = '';
// API Base URL Detection - Korrigierte Version für CSP-Kompatibilität
function detectApiBaseUrl() {
// Für lokale Entwicklung und CSP-Kompatibilität immer relative URLs verwenden
// Das verhindert CSP-Probleme mit connect-src
return ''; // Leerer String für relative URLs
}
const API_BASE_URL = detectApiBaseUrl();
// Initialisierung beim Laden der Seite
document.addEventListener('DOMContentLoaded', function() {
// CSRF Token abrufen
csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '';
// Event Listeners initialisieren
initEventListeners();
// Daten initial laden
loadGuestRequests();
// Auto-Refresh starten
startAutoRefresh();
console.log('🎯 Admin Guest Requests Management geladen');
});
/**
* Event Listeners initialisieren
*/
function initEventListeners() {
// Search Input
const searchInput = document.getElementById('search-requests');
if (searchInput) {
searchInput.addEventListener('input', debounce(handleSearch, 300));
}
// Status Filter
const statusFilter = document.getElementById('status-filter');
if (statusFilter) {
statusFilter.addEventListener('change', handleFilterChange);
}
// Sort Order
const sortOrder = document.getElementById('sort-order');
if (sortOrder) {
sortOrder.addEventListener('change', handleSortChange);
}
// Action Buttons
const refreshBtn = document.getElementById('refresh-btn');
if (refreshBtn) {
refreshBtn.addEventListener('click', () => {
loadGuestRequests();
showNotification('🔄 Gastaufträge aktualisiert', 'info');
});
}
const exportBtn = document.getElementById('export-btn');
if (exportBtn) {
exportBtn.addEventListener('click', handleExport);
}
const bulkActionsBtn = document.getElementById('bulk-actions-btn');
if (bulkActionsBtn) {
bulkActionsBtn.addEventListener('click', showBulkActionsModal);
}
// Select All Checkbox
const selectAllCheckbox = document.getElementById('select-all');
if (selectAllCheckbox) {
selectAllCheckbox.addEventListener('change', handleSelectAll);
}
}
/**
* Gastaufträge von der API laden
*/
async function loadGuestRequests() {
try {
showLoading(true);
const url = `${API_BASE_URL}/api/admin/guest-requests`;
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.success) {
currentRequests = data.requests || [];
totalRequests = data.total || 0;
// Statistiken aktualisieren
updateStats(data.stats || {});
// Tabelle aktualisieren
applyFiltersAndSort();
console.log(`✅ ${currentRequests.length} Gastaufträge geladen`);
} else {
throw new Error(data.message || 'Fehler beim Laden der Gastaufträge');
}
} catch (error) {
console.error('Fehler beim Laden der Gastaufträge:', error);
showNotification('❌ Fehler beim Laden der Gastaufträge: ' + error.message, 'error');
showEmptyState();
} finally {
showLoading(false);
}
}
/**
* Statistiken aktualisieren
*/
function updateStats(stats) {
const elements = {
'pending-count': stats.pending || 0,
'approved-count': stats.approved || 0,
'rejected-count': stats.rejected || 0,
'total-count': stats.total || 0
};
Object.entries(elements).forEach(([id, value]) => {
const element = document.getElementById(id);
if (element) {
animateCounter(element, value);
}
});
}
/**
* Counter mit Animation
*/
function animateCounter(element, targetValue) {
const currentValue = parseInt(element.textContent) || 0;
const difference = targetValue - currentValue;
const steps = 20;
const stepValue = difference / steps;
let step = 0;
const interval = setInterval(() => {
step++;
const value = Math.round(currentValue + (stepValue * step));
element.textContent = value;
if (step >= steps) {
clearInterval(interval);
element.textContent = targetValue;
}
}, 50);
}
/**
* Filter und Sortierung anwenden
*/
function applyFiltersAndSort() {
let requests = [...currentRequests];
// Status Filter
const statusFilter = document.getElementById('status-filter')?.value;
if (statusFilter && statusFilter !== 'all') {
requests = requests.filter(req => req.status === statusFilter);
}
// Such-Filter
const searchTerm = document.getElementById('search-requests')?.value.toLowerCase();
if (searchTerm) {
requests = requests.filter(req =>
req.name?.toLowerCase().includes(searchTerm) ||
req.email?.toLowerCase().includes(searchTerm) ||
req.file_name?.toLowerCase().includes(searchTerm) ||
req.reason?.toLowerCase().includes(searchTerm)
);
}
// Sortierung
const sortOrder = document.getElementById('sort-order')?.value;
requests.sort((a, b) => {
switch (sortOrder) {
case 'oldest':
return new Date(a.created_at) - new Date(b.created_at);
case 'priority':
return getPriorityValue(b) - getPriorityValue(a);
case 'newest':
default:
return new Date(b.created_at) - new Date(a.created_at);
}
});
filteredRequests = requests;
renderRequestsTable();
}
/**
* Prioritätswert für Sortierung berechnen
*/
function getPriorityValue(request) {
const now = new Date();
const created = new Date(request.created_at);
const hoursOld = (now - created) / (1000 * 60 * 60);
let priority = 0;
// Status-basierte Priorität
if (request.status === 'pending') priority += 10;
else if (request.status === 'approved') priority += 5;
// Alter-basierte Priorität
if (hoursOld > 24) priority += 5;
else if (hoursOld > 8) priority += 3;
else if (hoursOld > 2) priority += 1;
return priority;
}
/**
* Requests-Tabelle rendern
*/
function renderRequestsTable() {
const tableBody = document.getElementById('requests-table-body');
const emptyState = document.getElementById('empty-state');
if (!tableBody) return;
if (filteredRequests.length === 0) {
tableBody.innerHTML = '';
showEmptyState();
return;
}
hideEmptyState();
const requestsHtml = filteredRequests.map(request => createRequestRow(request)).join('');
tableBody.innerHTML = requestsHtml;
// Event Listeners für neue Rows hinzufügen
addRowEventListeners();
}
/**
* Request Row HTML erstellen
*/
function createRequestRow(request) {
const statusColor = getStatusColor(request.status);
const priorityLevel = getPriorityLevel(request);
const timeAgo = getTimeAgo(request.created_at);
return `
|
${request.name ? request.name[0].toUpperCase() : 'G'}
${escapeHtml(request.name || 'Unbekannt')}
${escapeHtml(request.email || 'Keine E-Mail')}
|
${escapeHtml(request.file_name || 'Keine Datei')}
${request.duration_minutes ? `${request.duration_minutes} Min.` : 'Unbekannte Dauer'}
${request.copies ? ` • ${request.copies} Kopien` : ''}
${request.reason ? `${escapeHtml(request.reason)} ` : ''}
|
${getStatusText(request.status)}
|
${timeAgo}
${formatDateTime(request.created_at)}
|
${getPriorityBadge(priorityLevel)}
${request.is_urgent ? '🔥 Dringend' : ''}
|
${request.status === 'pending' ? `
` : ''}
|
`;
}
/**
* Status-Helper-Funktionen
*/
function getStatusColor(status) {
const colors = {
'pending': 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300',
'approved': 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300',
'rejected': 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300',
'expired': 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300'
};
return colors[status] || 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-300';
}
function getStatusDot(status) {
const dots = {
'pending': 'bg-yellow-400 dark:bg-yellow-300',
'approved': 'bg-green-400 dark:bg-green-300',
'rejected': 'bg-red-400 dark:bg-red-300',
'expired': 'bg-gray-400 dark:bg-gray-300'
};
return dots[status] || 'bg-gray-400 dark:bg-gray-300';
}
function getStatusText(status) {
const texts = {
'pending': 'Wartend',
'approved': 'Genehmigt',
'rejected': 'Abgelehnt',
'expired': 'Abgelaufen'
};
return texts[status] || status;
}
function getPriorityLevel(request) {
const priority = getPriorityValue(request);
if (priority >= 15) return 'high';
if (priority >= 8) return 'medium';
return 'low';
}
function getPriorityBadge(level) {
const badges = {
'high': '🔴 Hoch',
'medium': '🟡 Mittel',
'low': '🟢 Niedrig'
};
return badges[level] || badges['low'];
}
/**
* CRUD-Operationen
*/
async function approveRequest(requestId) {
if (!confirm('Möchten Sie diesen Gastauftrag wirklich genehmigen?')) return;
try {
showLoading(true);
const url = `${API_BASE_URL}/api/guest-requests/${requestId}/approve`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify({}) // Leeres JSON-Objekt senden
});
const data = await response.json();
if (data.success) {
showNotification('✅ Gastauftrag erfolgreich genehmigt', 'success');
loadGuestRequests();
} else {
throw new Error(data.message || 'Fehler beim Genehmigen');
}
} catch (error) {
console.error('Fehler beim Genehmigen:', error);
showNotification('❌ Fehler beim Genehmigen: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
async function rejectRequest(requestId) {
const reason = prompt('Grund für die Ablehnung:');
if (!reason) return;
try {
showLoading(true);
const url = `${API_BASE_URL}/api/guest-requests/${requestId}/reject`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify({ reason })
});
const data = await response.json();
if (data.success) {
showNotification('✅ Gastauftrag erfolgreich abgelehnt', 'success');
loadGuestRequests();
} else {
throw new Error(data.message || 'Fehler beim Ablehnen');
}
} catch (error) {
console.error('Fehler beim Ablehnen:', error);
showNotification('❌ Fehler beim Ablehnen: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
async function deleteRequest(requestId) {
if (!confirm('Möchten Sie diesen Gastauftrag wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.')) return;
try {
showLoading(true);
const url = `${API_BASE_URL}/api/guest-requests/${requestId}`;
const response = await fetch(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
}
});
const data = await response.json();
if (data.success) {
showNotification('✅ Gastauftrag erfolgreich gelöscht', 'success');
loadGuestRequests();
} else {
throw new Error(data.message || 'Fehler beim Löschen');
}
} catch (error) {
console.error('Fehler beim Löschen:', error);
showNotification('❌ Fehler beim Löschen: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
/**
* Detail-Modal Funktionen
*/
function showRequestDetail(requestId) {
const request = currentRequests.find(req => req.id === requestId);
if (!request) return;
const modal = document.getElementById('detail-modal');
const content = document.getElementById('modal-content');
content.innerHTML = `
Antragsteller
Name: ${escapeHtml(request.name || 'Unbekannt')}
E-Mail: ${escapeHtml(request.email || 'Keine E-Mail')}
Erstellt am: ${formatDateTime(request.created_at)}
Auftrag Details
Datei: ${escapeHtml(request.file_name || 'Keine Datei')}
Dauer: ${request.duration_minutes || 'Unbekannt'} Minuten
Kopien: ${request.copies || 1}
Status: ${getStatusText(request.status)}
${request.reason ? `
Begründung
${escapeHtml(request.reason)}
` : ''}
${request.status === 'pending' ? `
` : ''}
`;
modal.classList.remove('hidden');
}
function closeDetailModal() {
const modal = document.getElementById('detail-modal');
modal.classList.add('hidden');
}
/**
* Bulk Actions
*/
function showBulkActionsModal() {
const selectedIds = getSelectedRequestIds();
if (selectedIds.length === 0) {
showNotification('⚠️ Bitte wählen Sie mindestens einen Gastauftrag aus', 'warning');
return;
}
const modal = document.getElementById('bulk-modal');
modal.classList.remove('hidden');
}
function closeBulkModal() {
const modal = document.getElementById('bulk-modal');
modal.classList.add('hidden');
}
async function performBulkAction(action) {
const selectedIds = getSelectedRequestIds();
if (selectedIds.length === 0) return;
const confirmMessages = {
'approve': `Möchten Sie ${selectedIds.length} Gastaufträge genehmigen?`,
'reject': `Möchten Sie ${selectedIds.length} Gastaufträge ablehnen?`,
'delete': `Möchten Sie ${selectedIds.length} Gastaufträge löschen?`
};
if (!confirm(confirmMessages[action])) return;
try {
showLoading(true);
closeBulkModal();
const promises = selectedIds.map(async (id) => {
const url = `${API_BASE_URL}/api/guest-requests/${id}/${action}`;
const method = action === 'delete' ? 'DELETE' : 'POST';
return fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
}
});
});
const results = await Promise.allSettled(promises);
const successCount = results.filter(r => r.status === 'fulfilled' && r.value.ok).length;
showNotification(`✅ ${successCount} von ${selectedIds.length} Aktionen erfolgreich`, 'success');
loadGuestRequests();
// Alle Checkboxen zurücksetzen
document.getElementById('select-all').checked = false;
document.querySelectorAll('.request-checkbox').forEach(cb => cb.checked = false);
} catch (error) {
console.error('Fehler bei Bulk-Aktion:', error);
showNotification('❌ Fehler bei der Bulk-Aktion: ' + error.message, 'error');
} finally {
showLoading(false);
}
}
function getSelectedRequestIds() {
const checkboxes = document.querySelectorAll('.request-checkbox:checked');
return Array.from(checkboxes).map(cb => parseInt(cb.value));
}
/**
* Event Handlers
*/
function handleSearch() {
applyFiltersAndSort();
}
function handleFilterChange() {
applyFiltersAndSort();
}
function handleSortChange() {
applyFiltersAndSort();
}
function handleSelectAll(event) {
const checkboxes = document.querySelectorAll('.request-checkbox');
checkboxes.forEach(checkbox => {
checkbox.checked = event.target.checked;
});
}
function handleExport() {
const selectedIds = getSelectedRequestIds();
const exportData = selectedIds.length > 0 ?
filteredRequests.filter(req => selectedIds.includes(req.id)) :
filteredRequests;
if (exportData.length === 0) {
showNotification('⚠️ Keine Daten zum Exportieren verfügbar', 'warning');
return;
}
exportToCSV(exportData);
}
/**
* Export-Funktionen
*/
function exportToCSV(data) {
const headers = ['ID', 'Name', 'E-Mail', 'Datei', 'Status', 'Erstellt', 'Dauer (Min)', 'Kopien', 'Begründung'];
const rows = data.map(req => [
req.id,
req.name || '',
req.email || '',
req.file_name || '',
getStatusText(req.status),
formatDateTime(req.created_at),
req.duration_minutes || '',
req.copies || '',
req.reason || ''
]);
const csvContent = [headers, ...rows]
.map(row => row.map(field => `"${String(field).replace(/"/g, '""')}"`).join(','))
.join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
if (link.download !== undefined) {
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `gastauftraege_${new Date().toISOString().split('T')[0]}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
showNotification('📄 CSV-Export erfolgreich erstellt', 'success');
}
/**
* Auto-Refresh
*/
function startAutoRefresh() {
// Refresh alle 30 Sekunden
refreshInterval = setInterval(() => {
loadGuestRequests();
}, 30000);
}
function stopAutoRefresh() {
if (refreshInterval) {
clearInterval(refreshInterval);
refreshInterval = null;
}
}
/**
* Utility-Funktionen
*/
function addRowEventListeners() {
// Falls notwendig, können hier zusätzliche Event Listener hinzugefügt werden
}
function showLoading(show) {
const loadingElement = document.getElementById('table-loading');
const tableBody = document.getElementById('requests-table-body');
if (loadingElement) {
loadingElement.classList.toggle('hidden', !show);
}
if (show && tableBody) {
tableBody.innerHTML = '';
}
}
function showEmptyState() {
const emptyState = document.getElementById('empty-state');
if (emptyState) {
emptyState.classList.remove('hidden');
}
}
function hideEmptyState() {
const emptyState = document.getElementById('empty-state');
if (emptyState) {
emptyState.classList.add('hidden');
}
}
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 px-6 py-4 rounded-xl shadow-2xl z-50 transform transition-all duration-500 translate-x-full ${
type === 'success' ? 'bg-green-500 text-white' :
type === 'error' ? 'bg-red-500 text-white' :
type === 'warning' ? 'bg-yellow-500 text-black' :
'bg-blue-500 text-white'
}`;
notification.innerHTML = `
${type === 'success' ? '✅' :
type === 'error' ? '❌' :
type === 'warning' ? '⚠️' : 'ℹ️'}
${message}
`;
document.body.appendChild(notification);
// Animation einblenden
setTimeout(() => {
notification.classList.remove('translate-x-full');
}, 100);
// Nach 5 Sekunden entfernen
setTimeout(() => {
notification.classList.add('translate-x-full');
setTimeout(() => notification.remove(), 5000);
}, 5000);
}
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
function escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text ? String(text).replace(/[&<>"']/g, m => map[m]) : '';
}
function formatDateTime(dateString) {
if (!dateString) return 'Unbekannt';
const date = new Date(dateString);
return date.toLocaleString('de-DE', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
}
function getTimeAgo(dateString) {
if (!dateString) return 'Unbekannt';
const now = new Date();
const date = new Date(dateString);
const diffMs = now - date;
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffDays = Math.floor(diffHours / 24);
if (diffDays > 0) {
return `vor ${diffDays} Tag${diffDays === 1 ? '' : 'en'}`;
} else if (diffHours > 0) {
return `vor ${diffHours} Stunde${diffHours === 1 ? '' : 'n'}`;
} else {
const diffMinutes = Math.floor(diffMs / (1000 * 60));
return `vor ${Math.max(1, diffMinutes)} Minute${diffMinutes === 1 ? '' : 'n'}`;
}
}
// Globale Funktionen für onclick-Handler
window.showRequestDetail = showRequestDetail;
window.approveRequest = approveRequest;
window.rejectRequest = rejectRequest;
window.deleteRequest = deleteRequest;
window.closeDetailModal = closeDetailModal;
window.closeBulkModal = closeBulkModal;
window.performBulkAction = performBulkAction;
console.log('📋 Admin Guest Requests JavaScript vollständig geladen');