220 lines
8.7 KiB
JavaScript
220 lines
8.7 KiB
JavaScript
|
|
(function () {
|
||
|
|
'use strict';
|
||
|
|
|
||
|
|
var root = document.querySelector('.task-cert-admin-monitoring');
|
||
|
|
if (!root) { return; }
|
||
|
|
|
||
|
|
var csrfToken = root.getAttribute('data-csrf-token') || '';
|
||
|
|
var resetAssignmentUrl = root.getAttribute('data-reset-assignment-url') || '';
|
||
|
|
var resetCycleUrl = root.getAttribute('data-reset-cycle-url') || '';
|
||
|
|
var approvalUrl = root.getAttribute('data-approval-url') || '';
|
||
|
|
var taskId = parseInt(root.getAttribute('data-task-id') || '0', 10);
|
||
|
|
|
||
|
|
// ── Toast ─────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
var toastContainer = document.getElementById('task-cert-toast-container');
|
||
|
|
|
||
|
|
function showToast(message, type) {
|
||
|
|
if (!toastContainer) { return; }
|
||
|
|
var cls = type === 'error' ? 'task-cert-error' : 'task-cert-success';
|
||
|
|
var toast = document.createElement('div');
|
||
|
|
toast.className = 'task-cert-toast ' + cls;
|
||
|
|
toast.textContent = String(message || '');
|
||
|
|
toast.addEventListener('click', function () {
|
||
|
|
removeToast(toast);
|
||
|
|
});
|
||
|
|
toastContainer.appendChild(toast);
|
||
|
|
|
||
|
|
// Trigger fade-in on next frame
|
||
|
|
requestAnimationFrame(function () {
|
||
|
|
toast.classList.add('is-visible');
|
||
|
|
});
|
||
|
|
|
||
|
|
var autoHideMs = type === 'error' ? 7000 : 4000;
|
||
|
|
setTimeout(function () {
|
||
|
|
removeToast(toast);
|
||
|
|
}, autoHideMs);
|
||
|
|
}
|
||
|
|
|
||
|
|
function removeToast(toast) {
|
||
|
|
if (!toast || !toast.parentNode) { return; }
|
||
|
|
toast.classList.remove('is-visible');
|
||
|
|
setTimeout(function () {
|
||
|
|
if (toast.parentNode) {
|
||
|
|
toast.parentNode.removeChild(toast);
|
||
|
|
}
|
||
|
|
}, 250);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Expose globally so other admin scripts (mark_completed) can reuse
|
||
|
|
window.TaskCertAdminActions = window.TaskCertAdminActions || {};
|
||
|
|
window.TaskCertAdminActions.showToast = showToast;
|
||
|
|
|
||
|
|
// ── Fetch-Helper ──────────────────────────────────────────────
|
||
|
|
|
||
|
|
function postJson(url, body) {
|
||
|
|
return fetch(url, {
|
||
|
|
method: 'POST',
|
||
|
|
credentials: 'same-origin',
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
body: JSON.stringify(body)
|
||
|
|
}).then(function (res) {
|
||
|
|
return res.json().then(function (data) {
|
||
|
|
return { ok: res.ok, status: res.status, data: data };
|
||
|
|
}).catch(function () {
|
||
|
|
return { ok: false, status: res.status, data: { success: false, error: 'Ungültige Server-Antwort' } };
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function reloadWithDelay() {
|
||
|
|
setTimeout(function () {
|
||
|
|
location.reload();
|
||
|
|
}, 800);
|
||
|
|
}
|
||
|
|
|
||
|
|
function setButtonBusy(btn, busy, busyLabel) {
|
||
|
|
if (!btn) { return; }
|
||
|
|
if (busy) {
|
||
|
|
btn._originalLabel = btn.textContent;
|
||
|
|
btn.textContent = busyLabel || 'Wird verarbeitet...';
|
||
|
|
btn.disabled = true;
|
||
|
|
} else {
|
||
|
|
if (btn._originalLabel) { btn.textContent = btn._originalLabel; }
|
||
|
|
btn.disabled = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Action: Reset Assignment ──────────────────────────────────
|
||
|
|
|
||
|
|
function handleResetAssignment(btn) {
|
||
|
|
var assignmentId = parseInt(btn.getAttribute('data-assignment-id') || '0', 10);
|
||
|
|
if (!assignmentId || !taskId || !resetAssignmentUrl) { return; }
|
||
|
|
|
||
|
|
if (!window.confirm('Einreichungen und Zertifikat für diesen Zeitraum wirklich zurücksetzen?')) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
setButtonBusy(btn, true, 'Wird zurückgesetzt...');
|
||
|
|
|
||
|
|
postJson(resetAssignmentUrl, {
|
||
|
|
csrf_token: csrfToken,
|
||
|
|
task_id: taskId,
|
||
|
|
assignment_id: assignmentId
|
||
|
|
}).then(function (result) {
|
||
|
|
if (result.data && result.data.success) {
|
||
|
|
showToast('Zeitraum wurde zurückgesetzt.', 'success');
|
||
|
|
reloadWithDelay();
|
||
|
|
} else {
|
||
|
|
setButtonBusy(btn, false);
|
||
|
|
showToast((result.data && result.data.error) || 'Fehler beim Zurücksetzen.', 'error');
|
||
|
|
}
|
||
|
|
}).catch(function (err) {
|
||
|
|
setButtonBusy(btn, false);
|
||
|
|
showToast('Netzwerkfehler: ' + (err && err.message ? err.message : 'Unbekannt'), 'error');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Action: Reset Cycle ───────────────────────────────────────
|
||
|
|
|
||
|
|
function handleResetCycle(btn) {
|
||
|
|
var btnTaskId = parseInt(btn.getAttribute('data-task-id') || '0', 10) || taskId;
|
||
|
|
if (!btnTaskId || !resetCycleUrl) { return; }
|
||
|
|
|
||
|
|
if (!window.confirm('Wirklich ALLE Einreichungen und Zertifikate im aktuellen Zyklus zurücksetzen? Diese Aktion betrifft alle Mitarbeiter in diesem Zeitraum.')) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
setButtonBusy(btn, true, 'Wird zurückgesetzt...');
|
||
|
|
|
||
|
|
postJson(resetCycleUrl, {
|
||
|
|
csrf_token: csrfToken,
|
||
|
|
task_id: btnTaskId
|
||
|
|
}).then(function (result) {
|
||
|
|
if (result.data && result.data.success) {
|
||
|
|
var stats = (result.data && result.data.stats) || {};
|
||
|
|
var n = parseInt(stats.assignment_count || 0, 10);
|
||
|
|
showToast('Zyklus wurde zurückgesetzt (' + n + ' Zuweisungen).', 'success');
|
||
|
|
reloadWithDelay();
|
||
|
|
} else {
|
||
|
|
setButtonBusy(btn, false);
|
||
|
|
showToast((result.data && result.data.error) || 'Fehler beim Zurücksetzen.', 'error');
|
||
|
|
}
|
||
|
|
}).catch(function (err) {
|
||
|
|
setButtonBusy(btn, false);
|
||
|
|
showToast('Netzwerkfehler: ' + (err && err.message ? err.message : 'Unbekannt'), 'error');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Action: Approval ──────────────────────────────────────────
|
||
|
|
|
||
|
|
function handleApproval(btn) {
|
||
|
|
var wrapper = btn.closest('.task-cert-inline-approval-form');
|
||
|
|
if (!wrapper) { return; }
|
||
|
|
|
||
|
|
var submissionId = parseInt(wrapper.getAttribute('data-submission-id') || '0', 10);
|
||
|
|
var decision = btn.getAttribute('data-decision') || '';
|
||
|
|
if (!submissionId || !decision || !approvalUrl) { return; }
|
||
|
|
|
||
|
|
var noteEl = wrapper.querySelector('.task-cert-approval-note');
|
||
|
|
var note = noteEl ? noteEl.value.trim() : '';
|
||
|
|
|
||
|
|
if (decision === 'reject' && !window.confirm('Einreichung wirklich ablehnen?')) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Disable both buttons in this row
|
||
|
|
var siblings = wrapper.querySelectorAll('.task-cert-btn-approval');
|
||
|
|
for (var i = 0; i < siblings.length; i++) {
|
||
|
|
siblings[i].disabled = true;
|
||
|
|
}
|
||
|
|
setButtonBusy(btn, true, decision === 'approve' ? 'Freigabe...' : 'Ablehnen...');
|
||
|
|
|
||
|
|
postJson(approvalUrl, {
|
||
|
|
csrf_token: csrfToken,
|
||
|
|
submission_id: submissionId,
|
||
|
|
decision: decision,
|
||
|
|
note: note
|
||
|
|
}).then(function (result) {
|
||
|
|
if (result.data && result.data.success) {
|
||
|
|
var msg = decision === 'approve' ? 'Einreichung freigegeben.' : 'Einreichung abgelehnt.';
|
||
|
|
showToast(msg, 'success');
|
||
|
|
reloadWithDelay();
|
||
|
|
} else {
|
||
|
|
for (var j = 0; j < siblings.length; j++) { siblings[j].disabled = false; }
|
||
|
|
setButtonBusy(btn, false);
|
||
|
|
showToast((result.data && result.data.error) || 'Fehler bei der Bearbeitung.', 'error');
|
||
|
|
}
|
||
|
|
}).catch(function (err) {
|
||
|
|
for (var k = 0; k < siblings.length; k++) { siblings[k].disabled = false; }
|
||
|
|
setButtonBusy(btn, false);
|
||
|
|
showToast('Netzwerkfehler: ' + (err && err.message ? err.message : 'Unbekannt'), 'error');
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Event-Delegation ──────────────────────────────────────────
|
||
|
|
|
||
|
|
document.addEventListener('click', function (e) {
|
||
|
|
var resetAssignBtn = e.target.closest('.task-cert-btn-reset-assignment');
|
||
|
|
if (resetAssignBtn) {
|
||
|
|
e.preventDefault();
|
||
|
|
handleResetAssignment(resetAssignBtn);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
var resetCycleBtn = e.target.closest('.task-cert-btn-reset-cycle');
|
||
|
|
if (resetCycleBtn) {
|
||
|
|
e.preventDefault();
|
||
|
|
handleResetCycle(resetCycleBtn);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
var approvalBtn = e.target.closest('.task-cert-btn-approval');
|
||
|
|
if (approvalBtn) {
|
||
|
|
e.preventDefault();
|
||
|
|
handleApproval(approvalBtn);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
})();
|