415 lines
19 KiB
HTML

{% extends "base.html" %}
{% block title %}Neuer Druckauftrag - MYP Platform{% 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>
<!-- Job Creation Form -->
<div class="mercedes-card rounded-xl p-8">
<form id="jobForm" onsubmit="handleJobSubmission(event)" class="space-y-8">
<!-- File Upload Section -->
<div>
<h2 class="text-xl font-bold text-mercedes-black mb-4">Datei hochladen</h2>
<div id="file-upload-area" class="border-2 border-dashed border-mercedes-silver rounded-xl p-8 text-center hover:border-mercedes-blue transition-colors duration-200 cursor-pointer">
<input type="file" id="file-input" name="file" accept=".stl,.gcode,.3mf,.obj" class="hidden" onchange="handleFileSelect(event)">
<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>
</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>
</div>
<button type="button" onclick="clearFile()" class="text-mercedes-red hover:text-red-700 text-sm transition-colors duration-200">
Datei entfernen
</button>
</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" onclick="resetForm()"
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>
let selectedFile = null;
let printers = [];
// Initialize
document.addEventListener('DOMContentLoaded', function() {
loadPrinters();
setupFileUpload();
});
// Load available printers
async function loadPrinters() {
try {
// Versuche zuerst Status-Check API für bessere Verfügbarkeitsprüfung
let response;
try {
response = await apiCall('/api/printers/status');
printers = Array.isArray(response) ? response : (response.printers || []);
} catch (statusError) {
console.log('Status-API fehlgeschlagen, verwende normale API:', statusError);
response = await apiCall('/api/printers');
printers = response.printers || [];
}
const select = document.getElementById('printer-select');
select.innerHTML = '<option value="">Drucker auswählen...</option>';
console.log('Geladene Drucker:', printers);
// Filtere verfügbare Drucker (status: 'available' oder active: true)
const availablePrinters = printers.filter(p => {
return p.status === 'available' || p.active === true;
});
console.log('Verfügbare Drucker:', availablePrinters);
if (availablePrinters.length === 0) {
// Zeige alle Drucker an, falls keine als verfügbar markiert sind
if (printers.length > 0) {
printers.forEach(printer => {
const option = document.createElement('option');
option.value = printer.id;
option.textContent = `${printer.name} (${printer.location || printer.model || 'Unbekanntes Modell'}) - Status unbekannt`;
select.appendChild(option);
});
showFlashMessage(`${printers.length} Drucker geladen (Status unbekannt)`, 'warning');
} else {
select.innerHTML = '<option value="">Keine Drucker verfügbar</option>';
select.disabled = true;
showFlashMessage('Keine Drucker in der Datenbank gefunden', 'error');
}
return;
}
availablePrinters.forEach(printer => {
const option = document.createElement('option');
option.value = printer.id;
// Status-Indikator hinzufügen
const statusText = printer.status === 'available' ? '✅ Verfügbar' : '⚠️ Status unbekannt';
const location = printer.location || printer.model || 'Unbekanntes Modell';
option.textContent = `${printer.name} (${location}) - ${statusText}`;
select.appendChild(option);
});
showFlashMessage(`${availablePrinters.length} verfügbare Drucker geladen`, 'success');
} catch (error) {
console.error('Error loading printers:', error);
showFlashMessage('Fehler beim Laden der Drucker', 'error');
}
}
// Setup file upload
function setupFileUpload() {
const uploadArea = document.getElementById('file-upload-area');
const fileInput = document.getElementById('file-input');
// Click to select file
uploadArea.addEventListener('click', () => {
if (!selectedFile) {
fileInput.click();
}
});
// Drag and drop
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('border-mercedes-blue', 'bg-blue-50');
});
uploadArea.addEventListener('dragleave', (e) => {
e.preventDefault();
uploadArea.classList.remove('border-mercedes-blue', 'bg-blue-50');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('border-mercedes-blue', 'bg-blue-50');
const files = e.dataTransfer.files;
if (files.length > 0) {
handleFileSelect({ target: { files } });
}
});
}
// Handle file selection
function handleFileSelect(event) {
const file = event.target.files[0];
if (!file) return;
// Validate file type
const allowedTypes = ['.stl', '.gcode', '.3mf', '.obj'];
const fileExtension = '.' + file.name.split('.').pop().toLowerCase();
if (!allowedTypes.includes(fileExtension)) {
showFlashMessage('Ungültiger Dateityp. Erlaubt sind: STL, GCODE, 3MF, OBJ', 'error');
return;
}
// Validate file size (100MB max)
const maxSize = 100 * 1024 * 1024; // 100MB in bytes
if (file.size > maxSize) {
showFlashMessage('Datei ist zu groß. Maximum: 100MB', 'error');
return;
}
selectedFile = file;
showFilePreview(file);
validateForm();
}
// Show file preview
function showFilePreview(file) {
document.getElementById('upload-placeholder').classList.add('hidden');
document.getElementById('file-preview').classList.remove('hidden');
document.getElementById('file-name').textContent = file.name;
document.getElementById('file-size').textContent = formatFileSize(file.size);
}
// Clear selected file
function clearFile() {
selectedFile = null;
document.getElementById('file-input').value = '';
document.getElementById('upload-placeholder').classList.remove('hidden');
document.getElementById('file-preview').classList.add('hidden');
validateForm();
}
// Validate form
function validateForm() {
const printerSelected = document.getElementById('printer-select').value;
const submitButton = document.getElementById('submit-button');
const isValid = selectedFile && printerSelected;
submitButton.disabled = !isValid;
}
// Form validation on change
document.getElementById('printer-select').addEventListener('change', validateForm);
// Handle form submission
async function handleJobSubmission(event) {
event.preventDefault();
if (!selectedFile) {
showFlashMessage('Bitte wählen Sie eine Datei aus', 'error');
return;
}
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);
// Show loading state
const submitButton = document.getElementById('submit-button');
const submitText = document.getElementById('submit-text');
const submitSpinner = document.getElementById('submit-spinner');
submitButton.disabled = true;
submitText.textContent = 'Wird erstellt...';
submitSpinner.classList.remove('hidden');
try {
const response = await fetch('/api/jobs', {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
showFlashMessage('Druckauftrag erfolgreich erstellt', 'success');
// Redirect to job details or jobs list
setTimeout(() => {
window.location.href = `/job/${result.job_id}`;
}, 1500);
} else {
throw new Error(result.message || 'Unbekannter Fehler');
}
} catch (error) {
console.error('Error creating job:', error);
showFlashMessage('Fehler beim Erstellen des Auftrags: ' + error.message, 'error');
// Reset button state
submitButton.disabled = false;
submitText.textContent = 'Auftrag erstellen';
submitSpinner.classList.add('hidden');
validateForm();
}
}
// Reset form
function resetForm() {
if (confirm('Sind Sie sicher, dass Sie das Formular zurücksetzen möchten?')) {
document.getElementById('jobForm').reset();
clearFile();
validateForm();
}
}
</script>
{% endblock %}