"feat: Add error handling documentation in FEHLER_BEHOBEN.md"

This commit is contained in:
Till Tomczak 2025-05-29 20:20:12 +02:00
parent 7fb844f085
commit 351ad68e98
5 changed files with 581 additions and 26 deletions

View 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.

File diff suppressed because one or more lines are too long

View File

@ -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 %}