778 lines
30 KiB
JavaScript
778 lines
30 KiB
JavaScript
/**
|
|
* MYP Platform Job Manager
|
|
* Verwaltung und Steuerung von 3D-Druckaufträgen
|
|
* Version: 1.0.0
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
/**
|
|
* Job Manager Klasse für 3D-Druckaufträge
|
|
*/
|
|
class JobManager {
|
|
constructor() {
|
|
this.jobs = [];
|
|
this.currentPage = 1;
|
|
this.totalPages = 1;
|
|
this.isLoading = false;
|
|
this.refreshInterval = null;
|
|
this.autoRefreshEnabled = false;
|
|
|
|
console.log('🔧 JobManager wird initialisiert...');
|
|
}
|
|
|
|
/**
|
|
* JobManager initialisieren
|
|
*/
|
|
async init() {
|
|
try {
|
|
console.log('🚀 JobManager-Initialisierung gestartet');
|
|
|
|
// Event-Listener einrichten
|
|
this.setupEventListeners();
|
|
|
|
// Formular-Handler einrichten
|
|
this.setupFormHandlers();
|
|
|
|
// Anfängliche Jobs laden
|
|
await this.loadJobs();
|
|
|
|
// Auto-Refresh starten falls aktiviert
|
|
if (this.autoRefreshEnabled) {
|
|
this.startAutoRefresh();
|
|
}
|
|
|
|
console.log('✅ JobManager erfolgreich initialisiert');
|
|
} catch (error) {
|
|
console.error('❌ Fehler bei JobManager-Initialisierung:', error);
|
|
this.showToast('Fehler beim Initialisieren des Job-Managers', 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Event-Listener für Job-Aktionen einrichten
|
|
*/
|
|
setupEventListeners() {
|
|
console.log('📡 Event-Listener werden eingerichtet...');
|
|
|
|
// Job-Aktionen über data-Attribute
|
|
document.addEventListener('click', (e) => {
|
|
const target = e.target.closest('[data-job-action]');
|
|
if (!target) return;
|
|
|
|
const action = target.getAttribute('data-job-action');
|
|
const jobId = target.getAttribute('data-job-id');
|
|
|
|
if (!jobId) {
|
|
console.warn('⚠️ Job-ID fehlt für Aktion:', action);
|
|
return;
|
|
}
|
|
|
|
switch (action) {
|
|
case 'start':
|
|
this.startJob(jobId);
|
|
break;
|
|
case 'pause':
|
|
this.pauseJob(jobId);
|
|
break;
|
|
case 'resume':
|
|
this.resumeJob(jobId);
|
|
break;
|
|
case 'stop':
|
|
this.stopJob(jobId);
|
|
break;
|
|
case 'delete':
|
|
this.deleteJob(jobId);
|
|
break;
|
|
case 'details':
|
|
this.openJobDetails(jobId);
|
|
break;
|
|
default:
|
|
console.warn('⚠️ Unbekannte Job-Aktion:', action);
|
|
}
|
|
});
|
|
|
|
// Refresh-Button
|
|
const refreshBtn = document.getElementById('refresh-jobs');
|
|
if (refreshBtn) {
|
|
refreshBtn.addEventListener('click', () => this.loadJobs());
|
|
}
|
|
|
|
// Auto-Refresh Toggle
|
|
const autoRefreshToggle = document.getElementById('auto-refresh-toggle');
|
|
if (autoRefreshToggle) {
|
|
autoRefreshToggle.addEventListener('change', (e) => {
|
|
this.autoRefreshEnabled = e.target.checked;
|
|
if (this.autoRefreshEnabled) {
|
|
this.startAutoRefresh();
|
|
} else {
|
|
this.stopAutoRefresh();
|
|
}
|
|
});
|
|
}
|
|
|
|
console.log('✅ Event-Listener erfolgreich eingerichtet');
|
|
}
|
|
|
|
/**
|
|
* Formular-Handler für Job-Erstellung einrichten
|
|
*/
|
|
setupFormHandlers() {
|
|
console.log('📝 Formular-Handler werden eingerichtet...');
|
|
|
|
const newJobForm = document.getElementById('new-job-form');
|
|
if (newJobForm) {
|
|
newJobForm.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
await this.createNewJob(new FormData(newJobForm));
|
|
});
|
|
}
|
|
|
|
const editJobForm = document.getElementById('edit-job-form');
|
|
if (editJobForm) {
|
|
editJobForm.addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
const jobId = editJobForm.getAttribute('data-job-id');
|
|
await this.updateJob(jobId, new FormData(editJobForm));
|
|
});
|
|
}
|
|
|
|
console.log('✅ Formular-Handler erfolgreich eingerichtet');
|
|
}
|
|
|
|
/**
|
|
* Jobs von Server laden
|
|
*/
|
|
async loadJobs(page = 1) {
|
|
if (this.isLoading) {
|
|
console.log('⚠️ Jobs werden bereits geladen...');
|
|
return;
|
|
}
|
|
|
|
this.isLoading = true;
|
|
this.showLoadingState(true);
|
|
|
|
try {
|
|
console.log(`📥 Lade Jobs (Seite ${page})...`);
|
|
|
|
const response = await fetch(`/api/jobs?page=${page}`, {
|
|
headers: {
|
|
'X-CSRFToken': this.getCSRFToken(),
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
// VERBESSERTE NULL-CHECKS für jobs-Daten
|
|
if (data && typeof data === 'object') {
|
|
// Sicherstellen, dass jobs immer ein Array ist
|
|
this.jobs = Array.isArray(data.jobs) ? data.jobs : [];
|
|
this.currentPage = Number(data.current_page) || 1;
|
|
this.totalPages = Number(data.total_pages) || 1;
|
|
|
|
console.log(`✅ ${this.jobs.length} Jobs erfolgreich geladen`, this.jobs);
|
|
} else {
|
|
console.warn('⚠️ Unerwartete API-Response-Struktur:', data);
|
|
this.jobs = [];
|
|
this.currentPage = 1;
|
|
this.totalPages = 1;
|
|
}
|
|
|
|
// Sichere Rendering-Aufrufe
|
|
this.renderJobs();
|
|
this.updatePagination();
|
|
|
|
console.log(`✅ ${this.jobs.length} Jobs erfolgreich geladen und gerendert`);
|
|
|
|
} catch (error) {
|
|
console.error('❌ Fehler beim Laden der Jobs:', error);
|
|
this.showToast('Fehler beim Laden der Jobs', 'error');
|
|
|
|
// Fallback: Leere Jobs-Liste anzeigen
|
|
this.jobs = [];
|
|
this.currentPage = 1;
|
|
this.totalPages = 1;
|
|
this.renderJobs();
|
|
} finally {
|
|
this.isLoading = false;
|
|
this.showLoadingState(false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Job starten
|
|
*/
|
|
async startJob(jobId) {
|
|
try {
|
|
console.log(`▶️ Starte Job ${jobId}...`);
|
|
|
|
const response = await fetch(`/api/jobs/${jobId}/start`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': this.getCSRFToken(),
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
this.showToast('Job erfolgreich gestartet', 'success');
|
|
await this.loadJobs();
|
|
|
|
} catch (error) {
|
|
console.error('❌ Fehler beim Starten des Jobs:', error);
|
|
this.showToast('Fehler beim Starten des Jobs', 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Job pausieren
|
|
*/
|
|
async pauseJob(jobId) {
|
|
try {
|
|
console.log(`⏸️ Pausiere Job ${jobId}...`);
|
|
|
|
const response = await fetch(`/api/jobs/${jobId}/pause`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': this.getCSRFToken(),
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
this.showToast('Job erfolgreich pausiert', 'success');
|
|
await this.loadJobs();
|
|
|
|
} catch (error) {
|
|
console.error('❌ Fehler beim Pausieren des Jobs:', error);
|
|
this.showToast('Fehler beim Pausieren des Jobs', 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Job fortsetzen
|
|
*/
|
|
async resumeJob(jobId) {
|
|
try {
|
|
console.log(`▶️ Setze Job ${jobId} fort...`);
|
|
|
|
const response = await fetch(`/api/jobs/${jobId}/resume`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': this.getCSRFToken(),
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
this.showToast('Job erfolgreich fortgesetzt', 'success');
|
|
await this.loadJobs();
|
|
|
|
} catch (error) {
|
|
console.error('❌ Fehler beim Fortsetzen des Jobs:', error);
|
|
this.showToast('Fehler beim Fortsetzen des Jobs', 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Job stoppen
|
|
*/
|
|
async stopJob(jobId) {
|
|
if (!confirm('Möchten Sie diesen Job wirklich stoppen?')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
console.log(`⏹️ Stoppe Job ${jobId}...`);
|
|
|
|
const response = await fetch(`/api/jobs/${jobId}/stop`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': this.getCSRFToken(),
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
this.showToast('Job erfolgreich gestoppt', 'success');
|
|
await this.loadJobs();
|
|
|
|
} catch (error) {
|
|
console.error('❌ Fehler beim Stoppen des Jobs:', error);
|
|
this.showToast('Fehler beim Stoppen des Jobs', 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Job löschen
|
|
*/
|
|
async deleteJob(jobId) {
|
|
if (!confirm('Möchten Sie diesen Job wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
console.log(`🗑️ Lösche Job ${jobId}...`);
|
|
|
|
const response = await fetch(`/api/jobs/${jobId}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'X-CSRFToken': this.getCSRFToken(),
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
this.showToast('Job erfolgreich gelöscht', 'success');
|
|
await this.loadJobs();
|
|
|
|
} catch (error) {
|
|
console.error('❌ Fehler beim Löschen des Jobs:', error);
|
|
this.showToast('Fehler beim Löschen des Jobs', 'error');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Job-Details öffnen
|
|
*/
|
|
openJobDetails(jobId) {
|
|
console.log(`📄 Öffne Details für Job ${jobId}...`);
|
|
|
|
// Modal für Job-Details öffnen oder zu Detail-Seite navigieren
|
|
const detailsUrl = `/jobs/${jobId}`;
|
|
|
|
// Prüfen ob Modal verfügbar ist
|
|
const detailsModal = document.getElementById(`job-details-${jobId}`);
|
|
if (detailsModal && typeof window.MYP !== 'undefined' && window.MYP.UI && window.MYP.UI.modal) {
|
|
window.MYP.UI.modal.open(`job-details-${jobId}`);
|
|
} else {
|
|
// Fallback: Zur Detail-Seite navigieren
|
|
window.location.href = detailsUrl;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Jobs in der UI rendern
|
|
*/
|
|
renderJobs() {
|
|
const jobsList = document.getElementById('jobs-list');
|
|
if (!jobsList) {
|
|
console.warn('⚠️ Jobs-Liste Element nicht gefunden');
|
|
return;
|
|
}
|
|
|
|
// VERBESSERTE SICHERHEITSCHECKS
|
|
if (!Array.isArray(this.jobs)) {
|
|
console.warn('⚠️ this.jobs ist kein Array:', this.jobs);
|
|
this.jobs = [];
|
|
}
|
|
|
|
if (this.jobs.length === 0) {
|
|
jobsList.innerHTML = `
|
|
<div class="text-center py-12">
|
|
<div class="text-gray-400 dark:text-gray-600 text-6xl mb-4">📭</div>
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">Keine Jobs vorhanden</h3>
|
|
<p class="text-gray-500 dark:text-gray-400">Es wurden noch keine Druckaufträge erstellt.</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const jobsHTML = this.jobs.map(job => {
|
|
// Sicherstellen, dass job ein gültiges Objekt ist
|
|
if (!job || typeof job !== 'object') {
|
|
console.warn('⚠️ Ungültiges Job-Objekt:', job);
|
|
return '';
|
|
}
|
|
return this.renderJobCard(job);
|
|
}).filter(html => html !== '').join('');
|
|
|
|
jobsList.innerHTML = jobsHTML;
|
|
|
|
console.log(`📋 ${this.jobs.length} Jobs gerendert`);
|
|
} catch (error) {
|
|
console.error('❌ Fehler beim Rendern der Jobs:', error);
|
|
jobsList.innerHTML = `
|
|
<div class="text-center py-12">
|
|
<div class="text-red-400 dark:text-red-600 text-6xl mb-4">⚠️</div>
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">Fehler beim Laden</h3>
|
|
<p class="text-gray-500 dark:text-gray-400">Es gab einen Fehler beim Darstellen der Jobs.</p>
|
|
<button onclick="window.jobManager.loadJobs()" class="mt-4 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
|
|
Erneut versuchen
|
|
</button>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Einzelne Job-Karte rendern
|
|
*/
|
|
renderJobCard(job) {
|
|
const statusClass = this.getJobStatusClass(job.status);
|
|
const statusText = this.getJobStatusText(job.status);
|
|
|
|
return `
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6">
|
|
<div class="flex items-start justify-between">
|
|
<div class="flex-1">
|
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">${job.name || 'Unbenannter Job'}</h3>
|
|
<div class="flex items-center space-x-4 text-sm text-gray-500 dark:text-gray-400 mb-4">
|
|
<span>ID: ${job.id}</span>
|
|
<span>•</span>
|
|
<span>Drucker: ${job.printer_name || 'Unbekannt'}</span>
|
|
<span>•</span>
|
|
<span>Erstellt: ${new Date(job.created_at).toLocaleDateString('de-DE')}</span>
|
|
</div>
|
|
<div class="flex items-center space-x-2 mb-4">
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${statusClass}">
|
|
${statusText}
|
|
</span>
|
|
${job.progress ? `<span class="text-sm text-gray-500 dark:text-gray-400">${job.progress}%</span>` : ''}
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-col space-y-2 ml-4">
|
|
${this.renderJobActions(job)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Job-Aktionen rendern
|
|
*/
|
|
renderJobActions(job) {
|
|
const actions = [];
|
|
|
|
// Details-Button immer verfügbar
|
|
actions.push(`
|
|
<button data-job-action="details" data-job-id="${job.id}"
|
|
class="inline-flex items-center px-3 py-2 border border-gray-300 dark:border-gray-600 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
|
Details
|
|
</button>
|
|
`);
|
|
|
|
// Status-abhängige Aktionen
|
|
switch (job.status) {
|
|
case 'pending':
|
|
case 'ready':
|
|
actions.push(`
|
|
<button data-job-action="start" data-job-id="${job.id}"
|
|
class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500">
|
|
Starten
|
|
</button>
|
|
`);
|
|
break;
|
|
|
|
case 'running':
|
|
case 'printing':
|
|
actions.push(`
|
|
<button data-job-action="pause" data-job-id="${job.id}"
|
|
class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-yellow-600 hover:bg-yellow-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500">
|
|
Pausieren
|
|
</button>
|
|
`);
|
|
actions.push(`
|
|
<button data-job-action="stop" data-job-id="${job.id}"
|
|
class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
|
|
Stoppen
|
|
</button>
|
|
`);
|
|
break;
|
|
|
|
case 'paused':
|
|
actions.push(`
|
|
<button data-job-action="resume" data-job-id="${job.id}"
|
|
class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500">
|
|
Fortsetzen
|
|
</button>
|
|
`);
|
|
actions.push(`
|
|
<button data-job-action="stop" data-job-id="${job.id}"
|
|
class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
|
|
Stoppen
|
|
</button>
|
|
`);
|
|
break;
|
|
|
|
case 'completed':
|
|
case 'failed':
|
|
case 'cancelled':
|
|
actions.push(`
|
|
<button data-job-action="delete" data-job-id="${job.id}"
|
|
class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
|
|
Löschen
|
|
</button>
|
|
`);
|
|
break;
|
|
}
|
|
|
|
return actions.join('');
|
|
}
|
|
|
|
/**
|
|
* CSS-Klasse für Job-Status
|
|
*/
|
|
getJobStatusClass(status) {
|
|
const statusClasses = {
|
|
'pending': 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300',
|
|
'ready': 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300',
|
|
'running': 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300',
|
|
'printing': 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300',
|
|
'paused': 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300',
|
|
'completed': 'bg-emerald-100 text-emerald-800 dark:bg-emerald-900 dark:text-emerald-300',
|
|
'failed': 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300',
|
|
'cancelled': 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300'
|
|
};
|
|
|
|
return statusClasses[status] || statusClasses['pending'];
|
|
}
|
|
|
|
/**
|
|
* Anzeigetext für Job-Status
|
|
*/
|
|
getJobStatusText(status) {
|
|
const statusTexts = {
|
|
'pending': 'Wartend',
|
|
'ready': 'Bereit',
|
|
'running': 'Läuft',
|
|
'printing': 'Druckt',
|
|
'paused': 'Pausiert',
|
|
'completed': 'Abgeschlossen',
|
|
'failed': 'Fehlgeschlagen',
|
|
'cancelled': 'Abgebrochen'
|
|
};
|
|
|
|
return statusTexts[status] || 'Unbekannt';
|
|
}
|
|
|
|
/**
|
|
* Loading-Zustand anzeigen/verstecken
|
|
*/
|
|
showLoadingState(show) {
|
|
const loadingEl = document.getElementById('jobs-loading');
|
|
const jobsList = document.getElementById('jobs-list');
|
|
|
|
if (loadingEl) {
|
|
loadingEl.style.display = show ? 'block' : 'none';
|
|
}
|
|
|
|
if (jobsList) {
|
|
jobsList.style.opacity = show ? '0.5' : '1';
|
|
jobsList.style.pointerEvents = show ? 'none' : 'auto';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* CSRF-Token abrufen
|
|
*/
|
|
getCSRFToken() {
|
|
const token = document.querySelector('meta[name="csrf-token"]');
|
|
return token ? token.getAttribute('content') : '';
|
|
}
|
|
|
|
/**
|
|
* Toast-Nachricht anzeigen
|
|
*/
|
|
showToast(message, type = 'info') {
|
|
if (typeof window.showToast === 'function') {
|
|
window.showToast(message, type);
|
|
} else {
|
|
console.log(`${type.toUpperCase()}: ${message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Auto-Refresh starten
|
|
*/
|
|
startAutoRefresh() {
|
|
this.stopAutoRefresh(); // Vorherigen Refresh stoppen
|
|
|
|
this.refreshInterval = setInterval(() => {
|
|
if (!this.isLoading) {
|
|
this.loadJobs(this.currentPage);
|
|
}
|
|
}, 30000); // Alle 30 Sekunden
|
|
|
|
console.log('🔄 Auto-Refresh gestartet (30s Intervall)');
|
|
}
|
|
|
|
/**
|
|
* Auto-Refresh stoppen
|
|
*/
|
|
stopAutoRefresh() {
|
|
if (this.refreshInterval) {
|
|
clearInterval(this.refreshInterval);
|
|
this.refreshInterval = null;
|
|
console.log('⏹️ Auto-Refresh gestoppt');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Paginierung aktualisieren
|
|
*/
|
|
updatePagination() {
|
|
const paginationEl = document.getElementById('jobs-pagination');
|
|
if (!paginationEl) return;
|
|
|
|
if (this.totalPages <= 1) {
|
|
paginationEl.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
paginationEl.style.display = 'flex';
|
|
|
|
let paginationHTML = '';
|
|
|
|
// Vorherige Seite
|
|
if (this.currentPage > 1) {
|
|
paginationHTML += `
|
|
<button onclick="jobManager.loadJobs(${this.currentPage - 1})"
|
|
class="px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-l-md hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-400 dark:hover:bg-gray-700">
|
|
Zurück
|
|
</button>
|
|
`;
|
|
}
|
|
|
|
// Seitenzahlen
|
|
for (let i = 1; i <= this.totalPages; i++) {
|
|
const isActive = i === this.currentPage;
|
|
paginationHTML += `
|
|
<button onclick="jobManager.loadJobs(${i})"
|
|
class="px-3 py-2 text-sm font-medium ${isActive
|
|
? 'text-blue-600 bg-blue-50 border-blue-300 dark:bg-blue-900 dark:text-blue-300'
|
|
: 'text-gray-500 bg-white border-gray-300 hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-400 dark:hover:bg-gray-700'
|
|
} border">
|
|
${i}
|
|
</button>
|
|
`;
|
|
}
|
|
|
|
// Nächste Seite
|
|
if (this.currentPage < this.totalPages) {
|
|
paginationHTML += `
|
|
<button onclick="jobManager.loadJobs(${this.currentPage + 1})"
|
|
class="px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-r-md hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-400 dark:hover:bg-gray-700">
|
|
Weiter
|
|
</button>
|
|
`;
|
|
}
|
|
|
|
paginationEl.innerHTML = paginationHTML;
|
|
}
|
|
|
|
/**
|
|
* Neuen Job erstellen
|
|
*/
|
|
async createNewJob(formData) {
|
|
try {
|
|
console.log('📝 Erstelle neuen Job...');
|
|
|
|
const response = await fetch('/api/jobs', {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': this.getCSRFToken()
|
|
},
|
|
body: formData
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
this.showToast('Job erfolgreich erstellt', 'success');
|
|
|
|
// Jobs neu laden
|
|
await this.loadJobs();
|
|
|
|
// Formular zurücksetzen
|
|
const form = document.getElementById('new-job-form');
|
|
if (form) {
|
|
form.reset();
|
|
}
|
|
|
|
return result;
|
|
|
|
} catch (error) {
|
|
console.error('❌ Fehler beim Erstellen des Jobs:', error);
|
|
this.showToast('Fehler beim Erstellen des Jobs', 'error');
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Job aktualisieren
|
|
*/
|
|
async updateJob(jobId, formData) {
|
|
try {
|
|
console.log(`📝 Aktualisiere Job ${jobId}...`);
|
|
|
|
const response = await fetch(`/api/jobs/${jobId}`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'X-CSRFToken': this.getCSRFToken()
|
|
},
|
|
body: formData
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
this.showToast('Job erfolgreich aktualisiert', 'success');
|
|
|
|
// Jobs neu laden
|
|
await this.loadJobs();
|
|
|
|
return result;
|
|
|
|
} catch (error) {
|
|
console.error('❌ Fehler beim Aktualisieren des Jobs:', error);
|
|
this.showToast('Fehler beim Aktualisieren des Jobs', 'error');
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
// JobManager global verfügbar machen
|
|
window.JobManager = JobManager;
|
|
|
|
// JobManager-Instanz erstellen wenn DOM bereit ist
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
if (typeof window.jobManager === 'undefined') {
|
|
window.jobManager = new JobManager();
|
|
|
|
// Nur initialisieren wenn wir uns auf einer Jobs-Seite befinden
|
|
if (document.getElementById('jobs-list') || document.querySelector('[data-job-action]')) {
|
|
window.jobManager.init();
|
|
}
|
|
}
|
|
});
|
|
|
|
console.log('✅ JobManager-Modul geladen');
|
|
})();
|