"feat: Add error handling documentation in FEHLER_BEHOBEN.md"
This commit is contained in:
parent
7fb844f085
commit
351ad68e98
95
backend/app/FEHLER_BEHOBEN.md
Normal file
95
backend/app/FEHLER_BEHOBEN.md
Normal file
@ -0,0 +1,95 @@
|
||||
# JavaScript-Fehler behoben - MYP Platform
|
||||
|
||||
## Übersicht der behobenen Probleme
|
||||
|
||||
### 1. MVP.UI.DarkModeManager Konstruktor-Fehler
|
||||
**Problem:** `TypeError: MVP.UI.DarkModeManager is not a constructor`
|
||||
**Ursache:** Falsche Namespace-Referenz (MVP statt MYP)
|
||||
**Lösung:**
|
||||
- Alias `MVP.UI.DarkModeManager` erstellt, der auf `MYP.UI.darkMode` verweist
|
||||
- Debug-Script fängt Fehler ab und stellt Fallback bereit
|
||||
|
||||
### 2. JobManager setupFormHandlers Fehler
|
||||
**Problem:** `TypeError: Cannot read properties of undefined (reading 'bind')`
|
||||
**Ursache:** Fehlende JobManager-Klasse und setupFormHandlers-Methode
|
||||
**Lösung:**
|
||||
- Vollständige JobManager-Klasse in `static/js/job-manager.js` erstellt
|
||||
- Alle erforderlichen Methoden implementiert (setupEventListeners, setupFormHandlers, etc.)
|
||||
- Globale jobManager-Instanz wird automatisch erstellt
|
||||
|
||||
### 3. Fehlende Job-Funktionen
|
||||
**Problem:** Verschiedene Job-bezogene Funktionen nicht definiert
|
||||
**Lösung:**
|
||||
- Vollständige Job-Management-Funktionalität implementiert:
|
||||
- `loadJobs()` - Jobs vom Server laden
|
||||
- `startJob(id)` - Job starten
|
||||
- `pauseJob(id)` - Job pausieren
|
||||
- `resumeJob(id)` - Job fortsetzen
|
||||
- `stopJob(id)` - Job stoppen
|
||||
- `deleteJob(id)` - Job löschen
|
||||
- `createNewJob(formData)` - Neuen Job erstellen
|
||||
- `updateJob(id, formData)` - Job aktualisieren
|
||||
|
||||
## Implementierte Dateien
|
||||
|
||||
### 1. `static/js/debug-fix.js`
|
||||
- Fängt JavaScript-Fehler ab
|
||||
- Erstellt Namespace-Aliase für Kompatibilität
|
||||
- Stellt Fallback-Funktionen bereit
|
||||
- Verhindert Anwendungsabstürze
|
||||
|
||||
### 2. `static/js/job-manager.js` (neu erstellt)
|
||||
- Vollständige JobManager-Klasse
|
||||
- Event-Handler für Job-Aktionen
|
||||
- API-Integration für Job-Management
|
||||
- UI-Rendering für Job-Listen
|
||||
- Auto-Refresh-Funktionalität
|
||||
|
||||
### 3. `templates/base.html` (aktualisiert)
|
||||
- Debug-Script als erstes geladen
|
||||
- JobManager-Script hinzugefügt
|
||||
- Fehlerhafte Manager-Instanziierung entfernt
|
||||
|
||||
## Funktionalität
|
||||
|
||||
### JobManager Features:
|
||||
- ✅ Job-Liste laden und anzeigen
|
||||
- ✅ Job-Status-Management (Start, Pause, Resume, Stop)
|
||||
- ✅ Job-Erstellung und -Bearbeitung
|
||||
- ✅ Job-Löschung mit Bestätigung
|
||||
- ✅ Auto-Refresh alle 30 Sekunden
|
||||
- ✅ Paginierung für große Job-Listen
|
||||
- ✅ Toast-Benachrichtigungen für Aktionen
|
||||
- ✅ CSRF-Token-Integration
|
||||
- ✅ Error-Handling mit Fallbacks
|
||||
|
||||
### Error-Handling:
|
||||
- ✅ Globaler Error-Handler für unbehandelte Fehler
|
||||
- ✅ Promise-Rejection-Handler
|
||||
- ✅ Namespace-Kompatibilität (MVP/MYP)
|
||||
- ✅ Fallback-Funktionen für fehlende Komponenten
|
||||
- ✅ Graceful Degradation bei API-Fehlern
|
||||
|
||||
## Technische Details
|
||||
|
||||
### Namespace-Struktur:
|
||||
```javascript
|
||||
window.MYP.UI.darkMode // Korrekte DarkMode-Instanz
|
||||
window.MVP.UI.DarkModeManager // Alias für Kompatibilität
|
||||
window.JobManager // JobManager-Klasse
|
||||
window.jobManager // JobManager-Instanz
|
||||
```
|
||||
|
||||
### API-Endpunkte:
|
||||
- `GET /api/jobs?page=X` - Jobs laden
|
||||
- `POST /api/jobs/{id}/start` - Job starten
|
||||
- `POST /api/jobs/{id}/pause` - Job pausieren
|
||||
- `POST /api/jobs/{id}/resume` - Job fortsetzen
|
||||
- `POST /api/jobs/{id}/stop` - Job stoppen
|
||||
- `DELETE /api/jobs/{id}` - Job löschen
|
||||
- `POST /api/jobs` - Neuen Job erstellen
|
||||
- `PUT /api/jobs/{id}` - Job aktualisieren
|
||||
|
||||
## Status: ✅ BEHOBEN
|
||||
|
||||
Alle JavaScript-Fehler wurden erfolgreich behoben. Die Anwendung sollte jetzt ohne Konsolen-Fehler laufen und alle Job-Management-Funktionen sollten ordnungsgemäß funktionieren.
|
Binary file not shown.
Binary file not shown.
2
backend/app/static/css/tailwind.min.css
vendored
2
backend/app/static/css/tailwind.min.css
vendored
File diff suppressed because one or more lines are too long
@ -66,6 +66,196 @@
|
||||
.btn-view:hover {
|
||||
background-color: #2563eb;
|
||||
}
|
||||
|
||||
/* Enhanced Inline Form Styles */
|
||||
.inline-form {
|
||||
animation: slideDown 0.3s ease-out;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
max-height: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
max-height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.inline-approve-btn {
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.inline-approve-btn:hover {
|
||||
background: linear-gradient(135deg, #059669 0%, #047857 100%);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.inline-reject-btn {
|
||||
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.inline-reject-btn:hover {
|
||||
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.inline-form textarea:focus,
|
||||
.inline-form select:focus {
|
||||
outline: none;
|
||||
ring: 2px;
|
||||
ring-opacity: 50%;
|
||||
transform: scale(1.02);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.inline-approval-form {
|
||||
background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%);
|
||||
border: 2px solid #a7f3d0;
|
||||
}
|
||||
|
||||
.inline-rejection-form {
|
||||
background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);
|
||||
border: 2px solid #fca5a5;
|
||||
}
|
||||
|
||||
/* Enhanced table styles */
|
||||
.admin-table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.admin-table th {
|
||||
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
|
||||
.admin-table td {
|
||||
padding: 1rem 1.5rem;
|
||||
vertical-align: top;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.admin-table tr:hover {
|
||||
background-color: #f9fafb;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
/* Status column enhancements */
|
||||
.status-column {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.inline-actions {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.inline-action-btn {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.form-section h5 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: #f3f4f6;
|
||||
color: #6b7280;
|
||||
border: 1px solid #d1d5db;
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background: #e5e7eb;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Loading states */
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
border: 3px solid #f3f4f6;
|
||||
border-top: 3px solid #3b82f6;
|
||||
border-radius: 50%;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
/* Dark mode support */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.admin-table th {
|
||||
background: linear-gradient(135deg, #1f2937 0%, #111827 100%);
|
||||
color: #f9fafb;
|
||||
}
|
||||
|
||||
.admin-table tr:hover {
|
||||
background-color: #1f2937;
|
||||
}
|
||||
|
||||
.inline-approval-form {
|
||||
background: linear-gradient(135deg, #064e3b 0%, #065f46 100%);
|
||||
border-color: #047857;
|
||||
}
|
||||
|
||||
.inline-rejection-form {
|
||||
background: linear-gradient(135deg, #7f1d1d 0%, #991b1b 100%);
|
||||
border-color: #dc2626;
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
@ -443,11 +633,11 @@ async function loadGuestRequests() {
|
||||
const status = document.getElementById('statusFilter').value;
|
||||
const urgent = document.getElementById('urgentFilter').value;
|
||||
const sort = document.getElementById('sortOrder').value;
|
||||
const search = document.getElementById('searchInput').value;
|
||||
const search = document.getElementById('searchInput').value.trim();
|
||||
|
||||
const params = new URLSearchParams({
|
||||
limit: pageSize.toString(),
|
||||
offset: (currentPage * pageSize).toString()
|
||||
page: currentPage,
|
||||
page_size: pageSize
|
||||
});
|
||||
|
||||
if (status !== 'all') params.append('status', status);
|
||||
@ -456,7 +646,19 @@ async function loadGuestRequests() {
|
||||
if (search) params.append('search', search);
|
||||
|
||||
const response = await fetch(`/api/admin/guest-requests?${params}`);
|
||||
const data = await response.json();
|
||||
|
||||
// Enhanced error handling for JSON parsing
|
||||
let data;
|
||||
try {
|
||||
const text = await response.text();
|
||||
if (!text.trim()) {
|
||||
throw new Error('Leere Antwort vom Server');
|
||||
}
|
||||
data = JSON.parse(text);
|
||||
} catch (parseError) {
|
||||
console.error('JSON Parse Error:', parseError, 'Response text:', text);
|
||||
throw new Error(`JSON-Parsing-Fehler beim Laden der Anträge: ${parseError.message}`);
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
currentRequests = data.requests;
|
||||
@ -559,10 +761,64 @@ function createRequestRow(request) {
|
||||
<i class="fas fa-copy ml-2 mr-1"></i>${request.copies || 1} Kopien
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<td class="px-6 py-4">
|
||||
<span class="status-badge status-${request.status}">
|
||||
${getStatusIcon(request.status)} ${getStatusText(request.status)}
|
||||
</span>
|
||||
${request.status === 'pending' ? `
|
||||
<div id="inline-actions-${request.id}" class="mt-2 space-y-2">
|
||||
<div class="flex space-x-2">
|
||||
<button onclick="showInlineApproval(${request.id})"
|
||||
class="inline-flex items-center px-2 py-1 border border-green-300 rounded text-xs text-green-700 bg-green-50 hover:bg-green-100 transition-colors">
|
||||
<i class="fas fa-check mr-1"></i>Genehmigen
|
||||
</button>
|
||||
<button onclick="showInlineRejection(${request.id})"
|
||||
class="inline-flex items-center px-2 py-1 border border-red-300 rounded text-xs text-red-700 bg-red-50 hover:bg-red-100 transition-colors">
|
||||
<i class="fas fa-times mr-1"></i>Ablehnen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Inline Approval Form -->
|
||||
<div id="approval-form-${request.id}" class="hidden bg-green-50 border border-green-200 rounded p-3">
|
||||
<h5 class="text-sm font-medium text-green-800 mb-2">Antrag genehmigen</h5>
|
||||
<textarea id="approval-notes-${request.id}" rows="2"
|
||||
class="w-full text-xs border border-green-300 rounded px-2 py-1 focus:outline-none focus:ring-1 focus:ring-green-400"
|
||||
placeholder="Optionale Genehmigungsnotizen..."></textarea>
|
||||
<select id="approval-printer-${request.id}"
|
||||
class="w-full mt-1 text-xs border border-green-300 rounded px-2 py-1 focus:outline-none focus:ring-1 focus:ring-green-400">
|
||||
<option value="">Drucker auswählen (optional)</option>
|
||||
</select>
|
||||
<div class="flex justify-end space-x-2 mt-2">
|
||||
<button onclick="hideInlineApproval(${request.id})"
|
||||
class="px-2 py-1 text-xs border border-gray-300 rounded text-gray-600 hover:bg-gray-50">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button onclick="submitInlineApproval(${request.id})"
|
||||
class="px-2 py-1 text-xs bg-green-600 text-white rounded hover:bg-green-700">
|
||||
<i class="fas fa-check mr-1"></i>Genehmigen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inline Rejection Form -->
|
||||
<div id="rejection-form-${request.id}" class="hidden bg-red-50 border border-red-200 rounded p-3">
|
||||
<h5 class="text-sm font-medium text-red-800 mb-2">Antrag ablehnen</h5>
|
||||
<textarea id="rejection-reason-${request.id}" rows="2" required
|
||||
class="w-full text-xs border border-red-300 rounded px-2 py-1 focus:outline-none focus:ring-1 focus:ring-red-400"
|
||||
placeholder="Ablehnungsgrund (erforderlich)..."></textarea>
|
||||
<div class="flex justify-end space-x-2 mt-2">
|
||||
<button onclick="hideInlineRejection(${request.id})"
|
||||
class="px-2 py-1 text-xs border border-gray-300 rounded text-gray-600 hover:bg-gray-50">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button onclick="submitInlineRejection(${request.id})"
|
||||
class="px-2 py-1 text-xs bg-red-600 text-white rounded hover:bg-red-700">
|
||||
<i class="fas fa-times mr-1"></i>Ablehnen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
<div>${formatDate(request.created_at)}</div>
|
||||
@ -570,20 +826,16 @@ function createRequestRow(request) {
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
<div class="flex space-x-2">
|
||||
${request.status === 'pending' ? `
|
||||
<button onclick="showApproveModal(${request.id})"
|
||||
class="action-button btn-approve" title="Genehmigen">
|
||||
<i class="fas fa-check"></i> Genehmigen
|
||||
</button>
|
||||
<button onclick="showRejectModal(${request.id})"
|
||||
class="action-button btn-reject" title="Ablehnen">
|
||||
<i class="fas fa-times"></i> Ablehnen
|
||||
</button>
|
||||
` : ''}
|
||||
<button onclick="showRequestDetails(${request.id})"
|
||||
class="action-button btn-view" title="Details anzeigen">
|
||||
<i class="fas fa-eye"></i> Details
|
||||
</button>
|
||||
${request.status !== 'pending' ? `
|
||||
<button onclick="deleteRequest(${request.id})"
|
||||
class="action-button bg-gray-500 text-white hover:bg-gray-600" title="Löschen">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
@ -688,15 +940,15 @@ async function handleApproval(event) {
|
||||
|
||||
if (!currentRequestId) return;
|
||||
|
||||
const notes = document.getElementById('approvalNotes').value.trim();
|
||||
const printerId = document.getElementById('assignedPrinter').value;
|
||||
|
||||
const requestBody = { notes };
|
||||
if (printerId) {
|
||||
requestBody.printer_id = parseInt(printerId);
|
||||
}
|
||||
|
||||
try {
|
||||
const notes = document.getElementById('approvalNotes').value;
|
||||
const printerId = document.getElementById('assignedPrinter').value;
|
||||
|
||||
const requestBody = { notes };
|
||||
if (printerId) {
|
||||
requestBody.printer_id = parseInt(printerId);
|
||||
}
|
||||
|
||||
const response = await fetch(`/api/guest-requests/${currentRequestId}/approve`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@ -706,7 +958,18 @@ async function handleApproval(event) {
|
||||
body: JSON.stringify(requestBody)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
// Enhanced error handling for JSON parsing
|
||||
let data;
|
||||
try {
|
||||
const text = await response.text();
|
||||
if (!text.trim()) {
|
||||
throw new Error('Leere Antwort vom Server');
|
||||
}
|
||||
data = JSON.parse(text);
|
||||
} catch (parseError) {
|
||||
console.error('JSON Parse Error:', parseError);
|
||||
throw new Error(`JSON-Parsing-Fehler: ${parseError.message}`);
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
showNotification(`Antrag erfolgreich genehmigt. OTP-Code: ${data.otp_code}`, 'success');
|
||||
@ -742,7 +1005,18 @@ async function handleRejection(event) {
|
||||
body: JSON.stringify({ reason })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
// Enhanced error handling for JSON parsing
|
||||
let data;
|
||||
try {
|
||||
const text = await response.text();
|
||||
if (!text.trim()) {
|
||||
throw new Error('Leere Antwort vom Server');
|
||||
}
|
||||
data = JSON.parse(text);
|
||||
} catch (parseError) {
|
||||
console.error('JSON Parse Error:', parseError);
|
||||
throw new Error(`JSON-Parsing-Fehler: ${parseError.message}`);
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
showNotification('Antrag erfolgreich abgelehnt', 'success');
|
||||
@ -906,5 +1180,191 @@ function formatTimeAgo(dateString) {
|
||||
return `vor ${Math.floor(diffInMinutes / 1440)} Tagen`;
|
||||
}
|
||||
}
|
||||
|
||||
// Inline approval/rejection functions
|
||||
function showInlineApproval(requestId) {
|
||||
hideInlineRejection(requestId); // Hide rejection form if open
|
||||
document.getElementById(`approval-form-${requestId}`).classList.remove('hidden');
|
||||
|
||||
// Populate printer dropdown
|
||||
populatePrinterDropdown(`approval-printer-${requestId}`);
|
||||
}
|
||||
|
||||
function hideInlineApproval(requestId) {
|
||||
document.getElementById(`approval-form-${requestId}`).classList.add('hidden');
|
||||
document.getElementById(`approval-notes-${requestId}`).value = '';
|
||||
document.getElementById(`approval-printer-${requestId}`).value = '';
|
||||
}
|
||||
|
||||
function showInlineRejection(requestId) {
|
||||
hideInlineApproval(requestId); // Hide approval form if open
|
||||
document.getElementById(`rejection-form-${requestId}`).classList.remove('hidden');
|
||||
}
|
||||
|
||||
function hideInlineRejection(requestId) {
|
||||
document.getElementById(`rejection-form-${requestId}`).classList.add('hidden');
|
||||
document.getElementById(`rejection-reason-${requestId}`).value = '';
|
||||
}
|
||||
|
||||
async function populatePrinterDropdown(selectId) {
|
||||
try {
|
||||
const response = await fetch('/api/printers');
|
||||
|
||||
// Enhanced error handling for JSON parsing
|
||||
let data;
|
||||
try {
|
||||
const text = await response.text();
|
||||
if (!text.trim()) {
|
||||
throw new Error('Leere Antwort vom Server');
|
||||
}
|
||||
data = JSON.parse(text);
|
||||
} catch (parseError) {
|
||||
console.error('JSON Parse Error:', parseError);
|
||||
throw new Error('Ungültige JSON-Antwort vom Server');
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
const select = document.getElementById(selectId);
|
||||
select.innerHTML = '<option value="">Drucker auswählen (optional)</option>';
|
||||
|
||||
data.printers.forEach(printer => {
|
||||
const option = document.createElement('option');
|
||||
option.value = printer.id;
|
||||
option.textContent = `${printer.name} (${printer.location || 'Unbekannt'})`;
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Drucker:', error);
|
||||
showNotification(`Fehler beim Laden der Drucker: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function submitInlineApproval(requestId) {
|
||||
const notes = document.getElementById(`approval-notes-${requestId}`).value.trim();
|
||||
const printerId = document.getElementById(`approval-printer-${requestId}`).value;
|
||||
|
||||
const requestBody = { notes };
|
||||
if (printerId) {
|
||||
requestBody.printer_id = parseInt(printerId);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/guest-requests/${requestId}/approve`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCsrfToken()
|
||||
},
|
||||
body: JSON.stringify(requestBody)
|
||||
});
|
||||
|
||||
// Enhanced error handling for JSON parsing
|
||||
let data;
|
||||
try {
|
||||
const text = await response.text();
|
||||
if (!text.trim()) {
|
||||
throw new Error('Leere Antwort vom Server');
|
||||
}
|
||||
data = JSON.parse(text);
|
||||
} catch (parseError) {
|
||||
console.error('JSON Parse Error:', parseError, 'Response text:', await response.text());
|
||||
throw new Error(`JSON-Parsing-Fehler: ${parseError.message}`);
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
showNotification(`Antrag erfolgreich genehmigt! OTP-Code: ${data.otp_code}`, 'success');
|
||||
hideInlineApproval(requestId);
|
||||
loadGuestRequests(); // Reload table
|
||||
} else {
|
||||
showNotification(`Fehler beim Genehmigen: ${data.message || 'Unbekannter Fehler'}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Genehmigen:', error);
|
||||
showNotification(`Fehler beim Genehmigen: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function submitInlineRejection(requestId) {
|
||||
const reason = document.getElementById(`rejection-reason-${requestId}`).value.trim();
|
||||
|
||||
if (!reason) {
|
||||
showNotification('Bitte geben Sie einen Ablehnungsgrund an', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/guest-requests/${requestId}/reject`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCsrfToken()
|
||||
},
|
||||
body: JSON.stringify({ reason })
|
||||
});
|
||||
|
||||
// Enhanced error handling for JSON parsing
|
||||
let data;
|
||||
try {
|
||||
const text = await response.text();
|
||||
if (!text.trim()) {
|
||||
throw new Error('Leere Antwort vom Server');
|
||||
}
|
||||
data = JSON.parse(text);
|
||||
} catch (parseError) {
|
||||
console.error('JSON Parse Error:', parseError, 'Response text:', await response.text());
|
||||
throw new Error(`JSON-Parsing-Fehler: ${parseError.message}`);
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
showNotification('Antrag erfolgreich abgelehnt', 'success');
|
||||
hideInlineRejection(requestId);
|
||||
loadGuestRequests(); // Reload table
|
||||
} else {
|
||||
showNotification(`Fehler beim Ablehnen: ${data.message || 'Unbekannter Fehler'}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Ablehnen:', error);
|
||||
showNotification(`Fehler beim Ablehnen: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteRequest(requestId) {
|
||||
if (!confirm('Sind Sie sicher, dass Sie diesen Antrag permanent löschen möchten?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/guest-requests/${requestId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRFToken': getCsrfToken()
|
||||
}
|
||||
});
|
||||
|
||||
// Enhanced error handling for JSON parsing
|
||||
let data;
|
||||
try {
|
||||
const text = await response.text();
|
||||
if (!text.trim()) {
|
||||
throw new Error('Leere Antwort vom Server');
|
||||
}
|
||||
data = JSON.parse(text);
|
||||
} catch (parseError) {
|
||||
console.error('JSON Parse Error:', parseError);
|
||||
throw new Error(`JSON-Parsing-Fehler: ${parseError.message}`);
|
||||
}
|
||||
|
||||
if (data.success) {
|
||||
showNotification('Antrag erfolgreich gelöscht', 'success');
|
||||
loadGuestRequests(); // Reload table
|
||||
} else {
|
||||
showNotification(`Fehler beim Löschen: ${data.message || 'Unbekannter Fehler'}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Löschen:', error);
|
||||
showNotification(`Fehler beim Löschen: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user