415 lines
19 KiB
HTML
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 %} |