2025-06-04 10:03:22 +02:00

850 lines
32 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}Neuer Druckauftrag - MYP Platform{% endblock %}
{% block extra_css %}
<style>
/* Mercedes-Benz Corporate Design */
.text-mercedes-black { color: #000000; }
.text-mercedes-gray { color: #6b7280; }
.text-mercedes-silver { color: #9ca3af; }
.text-mercedes-blue { color: #0073ce; }
.text-mercedes-green { color: #008c32; }
.text-mercedes-red { color: #dc2626; }
.bg-mercedes-black { background-color: #000000; }
.bg-mercedes-silver { background-color: #e5e7eb; }
.bg-mercedes-blue { background-color: #0073ce; }
.bg-mercedes-green { background-color: #008c32; }
.border-mercedes-silver { border-color: #d1d5db; }
.border-mercedes-blue { border-color: #0073ce; }
.hover\:border-mercedes-blue:hover { border-color: #0073ce; }
.focus\:ring-mercedes-blue:focus {
--tw-ring-color: #0073ce;
--tw-ring-opacity: 0.5;
}
.focus\:border-mercedes-blue:focus { border-color: #0073ce; }
/* Mercedes Card Effect */
.mercedes-card {
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
border: 1px solid #e5e7eb;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
transition: all 0.3s ease;
}
.dark .mercedes-card {
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
border-color: #334155;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
}
.mercedes-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
/* Mercedes Button */
.mercedes-button {
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
position: relative;
overflow: hidden;
}
.mercedes-button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.mercedes-button:hover::before {
left: 100%;
}
/* Enhanced File Upload Area - KRITISCHER FIX */
#file-upload-area {
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border: 2px dashed #cbd5e1;
position: relative;
overflow: hidden;
cursor: pointer !important;
user-select: none;
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
}
.dark #file-upload-area {
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
border-color: #475569;
}
#file-upload-area:hover {
border-color: #0073ce;
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
transform: scale(1.01);
}
#file-upload-area.drag-over {
border-color: #0073ce;
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
transform: scale(1.02);
}
.dark #file-upload-area.drag-over {
background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%);
}
/* WICHTIG: Sicherstellen dass alle Child-Elemente klickbar sind */
#file-upload-area * {
pointer-events: none;
}
/* File Preview Animation */
#file-preview {
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Progress Ring for Upload */
.progress-ring {
width: 60px;
height: 60px;
transform: rotate(-90deg);
}
.progress-ring__circle {
stroke: #0073ce;
stroke-linecap: round;
stroke-width: 4;
fill: transparent;
r: 26;
cx: 30;
cy: 30;
stroke-dasharray: 163.36;
stroke-dashoffset: 163.36;
transition: stroke-dashoffset 0.3s ease;
}
/* Form Enhancements */
.form-input {
transition: all 0.2s ease;
border: 1px solid #d1d5db;
}
.form-input:focus {
border-color: #0073ce;
box-shadow: 0 0 0 3px rgba(0, 115, 206, 0.1);
transform: translateY(-1px);
}
.dark .form-input {
background-color: #1e293b;
border-color: #475569;
color: #f8fafc;
}
.dark .form-input:focus {
border-color: #0ea5e9;
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1);
}
/* Loading Overlay */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.loading-content {
background: white;
padding: 2rem;
border-radius: 12px;
text-align: center;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}
.dark .loading-content {
background: #1e293b;
color: #f8fafc;
}
/* Upload Area Debug */
.upload-debug {
border: 2px solid red !important;
background: rgba(255, 0, 0, 0.1) !important;
}
</style>
{% endblock %}
{% block content %}
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Header -->
<div class="mb-8">
<div class="flex items-center justify-between">
<div>
<h1 class="text-3xl font-bold text-mercedes-black">Neuer Druckauftrag</h1>
<p class="mt-2 text-mercedes-gray">Erstellen Sie einen neuen 3D-Druckauftrag</p>
</div>
<a href="/jobs" class="bg-mercedes-silver hover:bg-gray-400 text-mercedes-black px-4 py-2 rounded-lg mercedes-button transition-all duration-200">
<svg class="h-5 w-5 inline mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
Zurück zu Aufträgen
</a>
</div>
</div>
<!-- Debug Info -->
<div id="debug-info" class="mb-4 p-3 bg-blue-100 border border-blue-200 rounded-lg hidden">
<h3 class="font-bold text-blue-800">Debug Information:</h3>
<p id="debug-text" class="text-blue-700"></p>
</div>
<!-- Job Creation Form -->
<div class="mercedes-card rounded-xl p-8">
<form id="jobForm" class="space-y-8">
<!-- File Upload Section -->
<div>
<h2 class="text-xl font-bold text-mercedes-black mb-4">
3D-Datei hochladen
<span class="text-sm font-normal text-mercedes-gray">(STL, GCODE, 3MF, OBJ)</span>
</h2>
<!-- Hidden File Input -->
<input type="file" id="file-input" name="file" accept=".stl,.gcode,.3mf,.obj" class="hidden">
<!-- Klickbarer Upload-Bereich -->
<div id="file-upload-area" class="border-2 border-dashed border-mercedes-silver rounded-xl p-8 text-center hover:border-mercedes-blue transition-all duration-200">
<div id="upload-placeholder" class="space-y-4">
<svg class="h-16 w-16 text-mercedes-silver mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg>
<div>
<p class="text-lg font-medium text-mercedes-black">Datei hier ablegen oder klicken zum Auswählen</p>
<p class="text-sm text-mercedes-gray mt-2">Unterstützte Formate: STL, GCODE, 3MF, OBJ (max. 100MB)</p>
<button type="button" id="upload-trigger" class="mt-4 bg-mercedes-blue hover:bg-blue-700 text-white px-6 py-2 rounded-lg mercedes-button transition-all duration-200">
📁 Datei auswählen
</button>
</div>
</div>
<div id="file-preview" class="hidden space-y-4">
<svg class="h-16 w-16 text-mercedes-green mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div>
<p id="file-name" class="text-lg font-medium text-mercedes-black"></p>
<p id="file-size" class="text-sm text-mercedes-gray"></p>
<p id="file-type" class="text-xs text-mercedes-blue font-medium mt-1"></p>
</div>
<button type="button" id="clear-file-btn" class="text-mercedes-red hover:text-red-700 text-sm transition-colors duration-200">
🗑️ Datei entfernen
</button>
</div>
</div>
<!-- Status-Anzeige -->
<div id="upload-status" class="mt-4 hidden">
<div class="bg-green-100 border border-green-200 rounded-lg p-3">
<p class="text-green-800 text-sm">✅ Datei erfolgreich ausgewählt</p>
</div>
</div>
</div>
<!-- Job Settings Section -->
<div>
<h2 class="text-xl font-bold text-mercedes-black mb-4">Druckeinstellungen</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Printer Selection -->
<div>
<label for="printer-select" class="block text-sm font-medium text-mercedes-black mb-2">
Drucker auswählen *
</label>
<select id="printer-select" name="printer_id" required
class="w-full px-3 py-2 border border-mercedes-silver rounded-lg focus:ring-2 focus:ring-mercedes-blue focus:border-mercedes-blue">
<option value="">Drucker auswählen...</option>
</select>
<p class="mt-1 text-xs text-mercedes-gray">Nur verfügbare Drucker werden angezeigt</p>
</div>
<!-- Priority -->
<div>
<label for="priority-select" class="block text-sm font-medium text-mercedes-black mb-2">
Priorität
</label>
<select id="priority-select" name="priority"
class="w-full px-3 py-2 border border-mercedes-silver rounded-lg focus:ring-2 focus:ring-mercedes-blue focus:border-mercedes-blue">
<option value="normal">Normal</option>
<option value="high">Hoch</option>
<option value="urgent">Dringend</option>
</select>
</div>
<!-- Material -->
<div>
<label for="material-input" class="block text-sm font-medium text-mercedes-black mb-2">
Material
</label>
<input type="text" id="material-input" name="material" placeholder="z.B. PLA, ABS, PETG"
class="w-full px-3 py-2 border border-mercedes-silver rounded-lg focus:ring-2 focus:ring-mercedes-blue focus:border-mercedes-blue">
</div>
<!-- Color -->
<div>
<label for="color-input" class="block text-sm font-medium text-mercedes-black mb-2">
Farbe
</label>
<input type="text" id="color-input" name="color" placeholder="z.B. Weiß, Schwarz, Rot"
class="w-full px-3 py-2 border border-mercedes-silver rounded-lg focus:ring-2 focus:ring-mercedes-blue focus:border-mercedes-blue">
</div>
<!-- Layer Height -->
<div>
<label for="layer-height-select" class="block text-sm font-medium text-mercedes-black mb-2">
Schichthöhe
</label>
<select id="layer-height-select" name="layer_height"
class="w-full px-3 py-2 border border-mercedes-silver rounded-lg focus:ring-2 focus:ring-mercedes-blue focus:border-mercedes-blue">
<option value="">Standard</option>
<option value="0.1">0.1mm (Hoch)</option>
<option value="0.2">0.2mm (Normal)</option>
<option value="0.3">0.3mm (Schnell)</option>
</select>
</div>
<!-- Infill -->
<div>
<label for="infill-select" class="block text-sm font-medium text-mercedes-black mb-2">
Füllung
</label>
<select id="infill-select" name="infill"
class="w-full px-3 py-2 border border-mercedes-silver rounded-lg focus:ring-2 focus:ring-mercedes-blue focus:border-mercedes-blue">
<option value="">Standard</option>
<option value="10">10% (Leicht)</option>
<option value="20">20% (Normal)</option>
<option value="50">50% (Stabil)</option>
<option value="100">100% (Massiv)</option>
</select>
</div>
</div>
</div>
<!-- Notes Section -->
<div>
<h2 class="text-xl font-bold text-mercedes-black mb-4">Zusätzliche Informationen</h2>
<div>
<label for="notes-textarea" class="block text-sm font-medium text-mercedes-black mb-2">
Notizen
</label>
<textarea id="notes-textarea" name="notes" rows="4"
placeholder="Besondere Anweisungen oder Hinweise für den Druck..."
class="w-full px-3 py-2 border border-mercedes-silver rounded-lg focus:ring-2 focus:ring-mercedes-blue focus:border-mercedes-blue"></textarea>
</div>
</div>
<!-- Submit Section -->
<div class="flex items-center justify-between pt-6 border-t border-mercedes-silver">
<div class="text-sm text-mercedes-gray">
<p>* Pflichtfelder</p>
</div>
<div class="flex space-x-4">
<button type="button" id="reset-btn"
class="bg-mercedes-silver hover:bg-gray-400 text-mercedes-black px-6 py-2 rounded-lg mercedes-button transition-all duration-200">
Zurücksetzen
</button>
<button type="submit" id="submit-button" disabled
class="bg-mercedes-green hover:bg-green-700 text-white px-6 py-2 rounded-lg mercedes-button transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed">
<span id="submit-text">Auftrag erstellen</span>
<svg id="submit-spinner" class="hidden animate-spin h-5 w-5 inline ml-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
// Globale Variablen
let selectedFile = null;
let printers = [];
let isUploading = false;
// Debug-Funktion
function debugLog(message) {
console.log('🔧 DEBUG:', message);
const debugInfo = document.getElementById('debug-info');
const debugText = document.getElementById('debug-text');
if (debugInfo && debugText) {
debugText.textContent = message;
debugInfo.classList.remove('hidden');
setTimeout(() => debugInfo.classList.add('hidden'), 3000);
}
}
// API-Hilfsfunktion mit besserer Fehlerbehandlung
async function apiCall(url, options = {}) {
try {
const response = await fetch(url, {
headers: {
'X-Requested-With': 'XMLHttpRequest',
...options.headers
},
...options
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('API-Fehler:', error);
throw error;
}
}
// Toast-Benachrichtigung (Fallback falls nicht verfügbar)
function showToast(message, type = 'info') {
if (window.showToast) {
window.showToast(message, type);
} else {
// Fallback: Einfache Alert
const colors = {
'success': '✅',
'error': '❌',
'warning': '⚠️',
'info': ''
};
console.log(`${colors[type] || ''} ${message}`);
// Temporäre Anzeige in der Seite
const toast = document.createElement('div');
toast.className = `fixed top-4 right-4 p-4 rounded-lg text-white z-50 ${type === 'error' ? 'bg-red-500' : type === 'success' ? 'bg-green-500' : type === 'warning' ? 'bg-yellow-500' : 'bg-blue-500'}`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.remove();
}, 3000);
}
}
// Dokumentbereitschaft
document.addEventListener('DOMContentLoaded', function() {
debugLog('Seite geladen - Initialisierung startet...');
try {
initializeFileUpload();
loadPrinters();
setupFormValidation();
setupEventListeners();
debugLog('Initialisierung erfolgreich abgeschlossen');
showToast('Upload-Interface bereit', 'success');
} catch (error) {
debugLog('Fehler bei Initialisierung: ' + error.message);
showToast('Fehler beim Laden der Seite', 'error');
}
});
// Datei-Upload initialisieren - HAUPTFUNKTION
function initializeFileUpload() {
const uploadArea = document.getElementById('file-upload-area');
const fileInput = document.getElementById('file-input');
const uploadTrigger = document.getElementById('upload-trigger');
const clearFileBtn = document.getElementById('clear-file-btn');
if (!uploadArea || !fileInput) {
throw new Error('Upload-Elemente nicht gefunden');
}
debugLog('Upload-Elemente gefunden, Event-Listener werden registriert...');
// HAUPTKLICK-EVENT - Direkt auf Upload-Area
uploadArea.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
debugLog('Upload-Area geklickt');
if (!selectedFile && !isUploading) {
debugLog('Datei-Dialog wird geöffnet...');
fileInput.click();
}
});
// Zusätzlicher Klick-Handler für Upload-Button
if (uploadTrigger) {
uploadTrigger.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
debugLog('Upload-Button geklickt');
fileInput.click();
});
}
// Datei-Input Change-Event
fileInput.addEventListener('change', function(e) {
debugLog('Datei-Input geändert');
handleFileSelection(e.target.files[0]);
});
// Drag & Drop
uploadArea.addEventListener('dragover', function(e) {
e.preventDefault();
uploadArea.classList.add('drag-over');
});
uploadArea.addEventListener('dragleave', function(e) {
e.preventDefault();
uploadArea.classList.remove('drag-over');
});
uploadArea.addEventListener('drop', function(e) {
e.preventDefault();
uploadArea.classList.remove('drag-over');
debugLog('Datei dropped');
const files = e.dataTransfer.files;
if (files.length > 0) {
handleFileSelection(files[0]);
}
});
// Clear-Button
if (clearFileBtn) {
clearFileBtn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
clearSelectedFile();
});
}
debugLog('Alle Upload-Event-Listener registriert');
}
// Datei-Auswahl behandeln
function handleFileSelection(file) {
if (!file) {
debugLog('Keine Datei ausgewählt');
return;
}
debugLog(`Datei ausgewählt: ${file.name} (${formatFileSize(file.size)})`);
// Datei validieren
const validation = validateFile(file);
if (!validation.valid) {
showToast(validation.message, 'error');
return;
}
selectedFile = file;
showFilePreview(file);
updateFormValidation();
showToast(`Datei "${file.name}" erfolgreich ausgewählt`, 'success');
}
// Datei validieren
function validateFile(file) {
const allowedExtensions = ['.stl', '.gcode', '.3mf', '.obj'];
const maxSize = 100 * 1024 * 1024; // 100MB
const extension = '.' + file.name.split('.').pop().toLowerCase();
if (!allowedExtensions.includes(extension)) {
return {
valid: false,
message: `Ungültiger Dateityp "${extension}". Erlaubt: ${allowedExtensions.join(', ')}`
};
}
if (file.size > maxSize) {
return {
valid: false,
message: `Datei zu groß (${formatFileSize(file.size)}). Maximum: 100MB`
};
}
if (file.size < 100) {
return {
valid: false,
message: 'Datei ist zu klein (weniger als 100 Bytes)'
};
}
return { valid: true };
}
// Datei-Vorschau anzeigen
function showFilePreview(file) {
const placeholder = document.getElementById('upload-placeholder');
const preview = document.getElementById('file-preview');
const fileName = document.getElementById('file-name');
const fileSize = document.getElementById('file-size');
const fileType = document.getElementById('file-type');
const uploadStatus = document.getElementById('upload-status');
if (placeholder) placeholder.classList.add('hidden');
if (preview) preview.classList.remove('hidden');
if (fileName) fileName.textContent = file.name;
if (fileSize) fileSize.textContent = formatFileSize(file.size);
if (fileType) {
const extension = '.' + file.name.split('.').pop().toLowerCase();
fileType.textContent = getFileTypeDescription(extension);
}
if (uploadStatus) uploadStatus.classList.remove('hidden');
debugLog('Datei-Vorschau aktualisiert');
}
// Ausgewählte Datei löschen
function clearSelectedFile() {
selectedFile = null;
const placeholder = document.getElementById('upload-placeholder');
const preview = document.getElementById('file-preview');
const fileInput = document.getElementById('file-input');
const uploadStatus = document.getElementById('upload-status');
if (placeholder) placeholder.classList.remove('hidden');
if (preview) preview.classList.add('hidden');
if (fileInput) fileInput.value = '';
if (uploadStatus) uploadStatus.classList.add('hidden');
updateFormValidation();
showToast('Datei entfernt', 'info');
debugLog('Datei entfernt');
}
// Drucker laden
async function loadPrinters() {
const select = document.getElementById('printer-select');
try {
debugLog('Lade Drucker...');
select.innerHTML = '<option value="">Lade Drucker...</option>';
select.disabled = true;
const response = await apiCall('/api/printers');
printers = response.printers || [];
debugLog(`${printers.length} Drucker geladen`);
select.innerHTML = '<option value="">Drucker auswählen...</option>';
select.disabled = false;
if (printers.length === 0) {
select.innerHTML = '<option value="">Keine Drucker verfügbar</option>';
select.disabled = true;
showToast('Keine Drucker verfügbar', 'warning');
return;
}
printers.forEach(printer => {
const option = document.createElement('option');
option.value = printer.id;
option.textContent = `${printer.name} (${printer.model || 'Unbekannt'})`;
select.appendChild(option);
});
showToast(`${printers.length} Drucker geladen`, 'success');
} catch (error) {
debugLog('Fehler beim Laden der Drucker: ' + error.message);
select.innerHTML = '<option value="">Fehler beim Laden</option>';
select.disabled = true;
showToast('Fehler beim Laden der Drucker', 'error');
}
}
// Event-Listener einrichten
function setupEventListeners() {
const resetBtn = document.getElementById('reset-btn');
const form = document.getElementById('jobForm');
if (resetBtn) {
resetBtn.addEventListener('click', function() {
if (confirm('Möchten Sie alle Eingaben zurücksetzen?')) {
form.reset();
clearSelectedFile();
showToast('Formular zurückgesetzt', 'info');
}
});
}
if (form) {
form.addEventListener('submit', handleFormSubmission);
}
// Drucker-Auswahl Change-Event
const printerSelect = document.getElementById('printer-select');
if (printerSelect) {
printerSelect.addEventListener('change', updateFormValidation);
}
}
// Formular-Validierung
function setupFormValidation() {
updateFormValidation();
}
function updateFormValidation() {
const printerSelected = document.getElementById('printer-select').value;
const submitButton = document.getElementById('submit-button');
const isValid = selectedFile && printerSelected && !isUploading;
if (submitButton) {
submitButton.disabled = !isValid;
if (isValid) {
submitButton.classList.remove('opacity-50', 'cursor-not-allowed');
submitButton.classList.add('hover:bg-green-700');
} else {
submitButton.classList.add('opacity-50', 'cursor-not-allowed');
submitButton.classList.remove('hover:bg-green-700');
}
}
}
// Formular-Übermittlung
async function handleFormSubmission(e) {
e.preventDefault();
if (!selectedFile) {
showToast('Bitte wählen Sie eine Datei aus', 'error');
return;
}
if (isUploading) {
showToast('Upload bereits in Bearbeitung', 'warning');
return;
}
isUploading = true;
updateFormValidation();
try {
debugLog('Formular wird übermittelt...');
showLoadingOverlay('Druckauftrag wird erstellt...');
const formData = new FormData();
formData.append('file', selectedFile);
formData.append('printer_id', document.getElementById('printer-select').value);
formData.append('priority', document.getElementById('priority-select').value);
formData.append('material', document.getElementById('material-input').value);
formData.append('color', document.getElementById('color-input').value);
formData.append('layer_height', document.getElementById('layer-height-select').value);
formData.append('infill', document.getElementById('infill-select').value);
formData.append('notes', document.getElementById('notes-textarea').value);
const response = await fetch('/api/jobs', {
method: 'POST',
body: formData
});
const result = await response.json();
hideLoadingOverlay();
if (response.ok && result.success) {
showToast('Druckauftrag erfolgreich erstellt!', 'success');
debugLog(`Job erstellt: ID ${result.job_id}`);
setTimeout(() => {
window.location.href = '/jobs';
}, 1500);
} else {
throw new Error(result.message || 'Unbekannter Fehler');
}
} catch (error) {
hideLoadingOverlay();
debugLog('Fehler beim Erstellen des Jobs: ' + error.message);
showToast('Fehler beim Erstellen des Auftrags: ' + error.message, 'error');
} finally {
isUploading = false;
updateFormValidation();
}
}
// Hilfsfunktionen
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function getFileTypeDescription(extension) {
const descriptions = {
'.stl': 'STL - 3D Mesh Datei',
'.gcode': 'G-Code - Druckfertiger Code',
'.3mf': '3MF - 3D Manufacturing Format',
'.obj': 'OBJ - Wavefront 3D Object'
};
return descriptions[extension] || 'Unbekannter Dateityp';
}
function showLoadingOverlay(message) {
const overlay = document.createElement('div');
overlay.id = 'loading-overlay';
overlay.className = 'loading-overlay';
overlay.innerHTML = `
<div class="loading-content">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto"></div>
<h3 class="text-lg font-semibold mt-4">${message}</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 mt-2">Bitte warten Sie...</p>
</div>
`;
document.body.appendChild(overlay);
}
function hideLoadingOverlay() {
const overlay = document.getElementById('loading-overlay');
if (overlay) {
overlay.remove();
}
}
</script>
{% endblock %}