"feat: Implement File Management System documentation and related updates"
This commit is contained in:
662
backend/app/static/js/job-manager.js
Normal file
662
backend/app/static/js/job-manager.js
Normal file
@@ -0,0 +1,662 @@
|
||||
/**
|
||||
* 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();
|
||||
|
||||
this.jobs = data.jobs || [];
|
||||
this.currentPage = data.current_page || 1;
|
||||
this.totalPages = data.total_pages || 1;
|
||||
|
||||
this.renderJobs();
|
||||
this.updatePagination();
|
||||
|
||||
console.log(`✅ ${this.jobs.length} Jobs erfolgreich geladen`);
|
||||
|
||||
} 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.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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const jobsHTML = this.jobs.map(job => this.renderJobCard(job)).join('');
|
||||
jobsList.innerHTML = jobsHTML;
|
||||
|
||||
console.log(`📋 ${this.jobs.length} Jobs gerendert`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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');
|
||||
})();
|
Reference in New Issue
Block a user