// ── CSV-Export Hilfsfunktionen (global) ────────────────────────── var TaskCertCsvExport = (function () { 'use strict'; function escapeCsvCell(val) { var str = String(val == null ? '' : val); if (str.indexOf('"') !== -1 || str.indexOf(';') !== -1 || str.indexOf('\n') !== -1) { return '"' + str.replace(/"/g, '""') + '"'; } return str; } function buildCsv(headers, rows) { var lines = [headers.map(escapeCsvCell).join(';')]; for (var i = 0; i < rows.length; i++) { lines.push(rows[i].map(escapeCsvCell).join(';')); } return lines.join('\r\n'); } function download(csvString, filename) { var bom = '\uFEFF'; var blob = new Blob([bom + csvString], { type: 'text/csv;charset=utf-8;' }); var url = URL.createObjectURL(blob); var link = document.createElement('a'); link.href = url; link.download = filename; link.style.display = 'none'; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); } return { escapeCsvCell: escapeCsvCell, buildCsv: buildCsv, download: download }; })(); // ══════════════════════════════════════════════════════════════════ // Übersicht-Grid // ══════════════════════════════════════════════════════════════════ (function () { 'use strict'; var container = document.getElementById('task-cert-overview-grid'); if (!container) { return; } var dataEl = document.getElementById('task-cert-overview-grid-data'); if (!dataEl) { return; } var allRows = []; try { allRows = JSON.parse(dataEl.textContent || '[]'); } catch (e) { container.textContent = 'Fehler beim Laden der Tabellendaten.'; return; } if (!window.gridjs || !window.gridjs.Grid) { container.textContent = 'Grid.js konnte nicht geladen werden.'; return; } var html = gridjs.html; var statusFilterEl = document.getElementById('task-cert-overview-status-filter'); var searchInputEl = document.getElementById('task-cert-overview-search'); var initialStatusFilter = container.getAttribute('data-initial-status-filter') || ''; // ── Hilfsfunktionen ────────────────────────────────────────── function escapeHtml(str) { if (!str) { return ''; } var div = document.createElement('div'); div.appendChild(document.createTextNode(String(str))); return div.innerHTML; } function stripHtmlTags(val) { if (!val) { return ''; } if (typeof val === 'object' && val.props && val.props.content) { var tmp = document.createElement('div'); tmp.innerHTML = val.props.content; return (tmp.textContent || tmp.innerText || '').trim(); } return String(val); } function formatDateDisplay(isoValue) { if (!isoValue) { return '-'; } try { var d = new Date(isoValue); if (isNaN(d.getTime())) { return isoValue; } var day = ('0' + d.getDate()).slice(-2); var month = ('0' + (d.getMonth() + 1)).slice(-2); var year = d.getFullYear(); var hours = ('0' + d.getHours()).slice(-2); var minutes = ('0' + d.getMinutes()).slice(-2); return day + '.' + month + '.' + year + ' ' + hours + ':' + minutes; } catch (e) { return isoValue; } } // ── Sort-Comparators ───────────────────────────────────────── function compareHtmlText(a, b) { var ta = stripHtmlTags(a); var tb = stripHtmlTags(b); return ta.localeCompare(tb, 'de'); } function compareDateValues(a, b) { var sa = (a && a._sort) || ''; var sb = (b && b._sort) || ''; if (sa === sb) { return 0; } if (sa === '') { return 1; } if (sb === '') { return -1; } return sa < sb ? -1 : 1; } function compareNumeric(a, b) { var ta = stripHtmlTags(a); var tb = stripHtmlTags(b); var na = ta === '-' || ta === '' ? -1 : parseFloat(ta) || 0; var nb = tb === '-' || tb === '' ? -1 : parseFloat(tb) || 0; return na - nb; } function compareStrings(a, b) { var sa = String(a || ''); var sb = String(b || ''); if (sa === '-') { sa = ''; } if (sb === '-') { sb = ''; } return sa.localeCompare(sb, 'de'); } // ── Zell-Helfer ────────────────────────────────────────────── function dateCell(isoValue) { return { _sort: isoValue || '', _display: formatDateDisplay(isoValue) }; } function dateCellFormatter(cell) { if (cell && typeof cell === 'object' && cell._display !== undefined) { return cell._display; } return cell || '-'; } // ── Suchtext-Cache ─────────────────────────────────────────── function buildSearchText(row) { return [ row.contact_name || '', row.contact_email || '', row.status_label || '', row.cycle_key || '', row.certificate_label || '', row.certificate_code || '', formatDateDisplay(row.assigned_at_sort), formatDateDisplay(row.due_at_sort), formatDateDisplay(row.completed_at_sort), formatDateDisplay(row.overdue_at_sort), formatDateDisplay(row.last_activity_sort), formatDateDisplay(row.valid_until_sort) ].join(' ').toLowerCase(); } allRows.forEach(function (row) { row._searchText = buildSearchText(row); }); // ── Grid-Daten bauen ───────────────────────────────────────── function buildGridData(rows) { return rows.map(function (row) { var nameHtml = '' + escapeHtml(row.contact_name || '-') + ''; if (row.contact_email) { nameHtml += '
' + escapeHtml(row.contact_email) + ''; } var statusText = row.status_label || '-'; var escalationHtml = '-'; if (row.grace_badge_html) { escalationHtml = row.grace_badge_html; } else if (row.escalation_level !== null && row.escalation_level !== undefined && row.escalation_level !== '') { escalationHtml = String(row.escalation_level); } var certHtml = '-'; if (row.certificate_label) { certHtml = escapeHtml(row.certificate_label); if (row.certificate_code) { certHtml += '
' + escapeHtml(row.certificate_code) + ''; } } var actionHtml = row._row_html_action || '-'; return [ html(nameHtml), statusText, dateCell(row.assigned_at_sort), dateCell(row.due_at_sort), dateCell(row.completed_at_sort), dateCell(row.overdue_at_sort), html(escalationHtml), dateCell(row.last_activity_sort), html(certHtml), dateCell(row.valid_until_sort), row.cycle_key || '-', html(actionHtml) ]; }); } // ── Filter-Logik ───────────────────────────────────────────── var currentStatusFilter = initialStatusFilter; var currentSearchTerm = ''; function getFilteredRows() { var rows = allRows; if (currentStatusFilter) { rows = rows.filter(function (row) { return row.status === currentStatusFilter; }); } if (currentSearchTerm) { var term = currentSearchTerm.toLowerCase(); rows = rows.filter(function (row) { return row._searchText.indexOf(term) !== -1; }); } return rows; } function applyFilters() { grid.updateConfig({ data: buildGridData(getFilteredRows()) }).forceRender(); } // ── Grid initialisieren ────────────────────────────────────── var grid = new gridjs.Grid({ columns: [ { name: 'Mitarbeiter', width: '180px', sort: { compare: compareHtmlText } }, { name: 'Status', width: '130px', sort: { compare: compareStrings } }, { name: 'Zugewiesen', width: '130px', formatter: dateCellFormatter, sort: { compare: compareDateValues } }, { name: 'Fällig', width: '130px', formatter: dateCellFormatter, sort: { compare: compareDateValues } }, { name: 'Abgeschlossen', width: '130px', formatter: dateCellFormatter, sort: { compare: compareDateValues } }, { name: 'Überfällig seit', width: '130px', formatter: dateCellFormatter, sort: { compare: compareDateValues } }, { name: 'Eskalation', width: '90px', sort: { compare: compareNumeric } }, { name: 'Letzte Akt.', width: '130px', formatter: dateCellFormatter, sort: { compare: compareDateValues } }, { name: 'Zertifikat', width: '120px', sort: { compare: compareHtmlText } }, { name: 'Gültig bis', width: '130px', formatter: dateCellFormatter, sort: { compare: compareDateValues } }, { name: 'Zyklus', width: '90px', sort: { compare: compareStrings } }, { name: 'Aktion', width: '130px', sort: false } ], data: buildGridData(getFilteredRows()), search: false, sort: { multiColumn: false }, pagination: { enabled: true, limit: 25 }, language: { pagination: { previous: 'Zurück', next: 'Weiter', showing: 'Zeige', of: 'von', to: 'bis', results: 'Einträge' }, noRecordsFound: 'Keine Einträge gefunden.', loading: 'Laden...', error: 'Fehler beim Laden der Daten.' }, className: { table: 'task-cert-gridjs-table', th: 'task-cert-gridjs-th', td: 'task-cert-gridjs-td' }, autoWidth: false }); grid.render(container); // ── Event-Handler ──────────────────────────────────────────── if (statusFilterEl) { if (initialStatusFilter) { statusFilterEl.value = initialStatusFilter; } statusFilterEl.addEventListener('change', function () { currentStatusFilter = this.value; applyFilters(); }); } if (searchInputEl) { var searchTimeout = null; searchInputEl.addEventListener('input', function () { var val = this.value.trim(); if (searchTimeout) { clearTimeout(searchTimeout); } searchTimeout = setTimeout(function () { currentSearchTerm = val; applyFilters(); }, 200); }); } // CSV-Export Übersicht var exportBtn = document.getElementById('task-cert-overview-export'); if (exportBtn) { exportBtn.addEventListener('click', function () { var rows = getFilteredRows(); var headers = ['Mitarbeiter', 'E-Mail', 'Status', 'Zugewiesen', 'Fällig', 'Abgeschlossen', 'Überfällig seit', 'Eskalation', 'Letzte Aktivität', 'Zertifikat', 'Gültig bis', 'Zyklus']; var csvRows = rows.map(function (row) { return [ row.contact_name || '', row.contact_email || '', row.status_label || '', formatDateDisplay(row.assigned_at_sort), formatDateDisplay(row.due_at_sort), formatDateDisplay(row.completed_at_sort), formatDateDisplay(row.overdue_at_sort), row.escalation_level != null ? row.escalation_level : '', formatDateDisplay(row.last_activity_sort), row.certificate_label || '', formatDateDisplay(row.valid_until_sort), row.cycle_key || '' ]; }); var csv = TaskCertCsvExport.buildCsv(headers, csvRows); TaskCertCsvExport.download(csv, 'uebersicht-export.csv'); }); } })(); // ══════════════════════════════════════════════════════════════════ // Antworten-Grid // ══════════════════════════════════════════════════════════════════ (function () { 'use strict'; var container = document.getElementById('task-cert-answers-grid'); if (!container) { return; } var dataEl = document.getElementById('task-cert-answers-grid-data'); if (!dataEl) { return; } var allRows = []; try { allRows = JSON.parse(dataEl.textContent || '[]'); } catch (e) { container.textContent = 'Fehler beim Laden der Tabellendaten.'; return; } if (!window.gridjs || !window.gridjs.Grid) { container.textContent = 'Grid.js konnte nicht geladen werden.'; return; } var html = gridjs.html; var searchInputEl = document.getElementById('task-cert-answers-search'); function escapeHtml(str) { if (!str) { return ''; } var div = document.createElement('div'); div.appendChild(document.createTextNode(String(str))); return div.innerHTML; } function formatDateDisplay(isoValue) { if (!isoValue) { return '-'; } try { var d = new Date(isoValue); if (isNaN(d.getTime())) { return isoValue; } var day = ('0' + d.getDate()).slice(-2); var month = ('0' + (d.getMonth() + 1)).slice(-2); var year = d.getFullYear(); var hours = ('0' + d.getHours()).slice(-2); var minutes = ('0' + d.getMinutes()).slice(-2); return day + '.' + month + '.' + year + ' ' + hours + ':' + minutes; } catch (e) { return isoValue; } } function compareStrings(a, b) { var sa = String(a || ''); var sb = String(b || ''); if (sa === '-') { sa = ''; } if (sb === '-') { sb = ''; } return sa.localeCompare(sb, 'de'); } function compareDateValues(a, b) { var sa = (a && a._sort) || ''; var sb = (b && b._sort) || ''; if (sa === sb) { return 0; } if (sa === '') { return 1; } if (sb === '') { return -1; } return sa < sb ? -1 : 1; } function compareHtmlText(a, b) { var ta = stripHtmlTags(a); var tb = stripHtmlTags(b); return ta.localeCompare(tb, 'de'); } function compareNumeric(a, b) { var ta = stripHtmlTags(a); var tb = stripHtmlTags(b); var na = ta === '-' || ta === '' ? -1 : parseFloat(ta) || 0; var nb = tb === '-' || tb === '' ? -1 : parseFloat(tb) || 0; return na - nb; } function stripHtmlTags(val) { if (!val) { return ''; } if (typeof val === 'object' && val.props && val.props.content) { var tmp = document.createElement('div'); tmp.innerHTML = val.props.content; return (tmp.textContent || tmp.innerText || '').trim(); } return String(val); } function dateCell(isoValue) { return { _sort: isoValue || '', _display: formatDateDisplay(isoValue) }; } function dateCellFormatter(cell) { if (cell && typeof cell === 'object' && cell._display !== undefined) { return cell._display; } return cell || '-'; } // Suchtext-Cache allRows.forEach(function (row) { row._searchText = [ row.contact_name || '', row.contact_email || '', row.method_label || '', row.status_label || '', row.score || '', formatDateDisplay(row.submitted_at_sort) ].join(' ').toLowerCase(); }); function buildGridData(rows) { return rows.map(function (row) { var nameHtml = '' + escapeHtml(row.contact_name || '-') + ''; if (row.contact_email) { nameHtml += '
' + escapeHtml(row.contact_email) + ''; } var detailHtml = row.detail_html || '-'; if (detailHtml !== '-') { detailHtml = '
Anzeigen' + detailHtml + '
'; } return [ html(nameHtml), row.method_label || '-', row.status_label || '-', dateCell(row.submitted_at_sort), row.score || '-', html(detailHtml) ]; }); } var currentSearchTerm = ''; function getFilteredRows() { var rows = allRows; if (currentSearchTerm) { var term = currentSearchTerm.toLowerCase(); rows = rows.filter(function (row) { return row._searchText.indexOf(term) !== -1; }); } return rows; } function applyFilters() { answersGrid.updateConfig({ data: buildGridData(getFilteredRows()) }).forceRender(); } var answersGrid = new gridjs.Grid({ columns: [ { name: 'Mitarbeiter', width: '180px', sort: { compare: compareHtmlText } }, { name: 'Methode', width: '130px', sort: { compare: compareStrings } }, { name: 'Status', width: '130px', sort: { compare: compareStrings } }, { name: 'Eingereicht', width: '140px', formatter: dateCellFormatter, sort: { compare: compareDateValues } }, { name: 'Punkte', width: '80px', sort: { compare: compareStrings } }, { name: 'Details', width: '250px', sort: false } ], data: buildGridData(getFilteredRows()), search: false, sort: { multiColumn: false }, pagination: { enabled: true, limit: 25 }, language: { pagination: { previous: 'Zurück', next: 'Weiter', showing: 'Zeige', of: 'von', to: 'bis', results: 'Einträge' }, noRecordsFound: 'Keine Einträge gefunden.', loading: 'Laden...', error: 'Fehler beim Laden der Daten.' }, className: { table: 'task-cert-gridjs-table', th: 'task-cert-gridjs-th', td: 'task-cert-gridjs-td' }, autoWidth: false }); answersGrid.render(container); if (searchInputEl) { var searchTimeout = null; searchInputEl.addEventListener('input', function () { var val = this.value.trim(); if (searchTimeout) { clearTimeout(searchTimeout); } searchTimeout = setTimeout(function () { currentSearchTerm = val; applyFilters(); }, 200); }); } // CSV-Export Antworten (mit Antwort-Details als einzelne Spalten) var exportBtn = document.getElementById('task-cert-answers-export'); if (exportBtn) { exportBtn.addEventListener('click', function () { var rows = getFilteredRows(); // Alle einzigartigen Antwort-Labels sammeln (Reihenfolge beibehalten) var fieldLabels = []; var fieldLabelSet = {}; for (var i = 0; i < rows.length; i++) { var fields = rows[i].answer_fields || {}; for (var label in fields) { if (fields.hasOwnProperty(label) && !fieldLabelSet[label]) { fieldLabelSet[label] = true; fieldLabels.push(label); } } } var headers = ['Mitarbeiter', 'E-Mail', 'Methode', 'Status', 'Eingereicht', 'Punkte']; headers = headers.concat(fieldLabels); var csvRows = rows.map(function (row) { var base = [ row.contact_name || '', row.contact_email || '', row.method_label || '', row.status_label || '', formatDateDisplay(row.submitted_at_sort), row.score || '' ]; var fields = row.answer_fields || {}; for (var j = 0; j < fieldLabels.length; j++) { base.push(fields[fieldLabels[j]] || ''); } return base; }); var csv = TaskCertCsvExport.buildCsv(headers, csvRows); TaskCertCsvExport.download(csv, 'antworten-export.csv'); }); } })();