Files
awo-hamburg-intranet/module/tasks/assets/js/task_cert_admin_overview_grid.js

604 lines
22 KiB
JavaScript
Raw Normal View History

// ── 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 = '<span>' + escapeHtml(row.contact_name || '-') + '</span>';
if (row.contact_email) {
nameHtml += '<br><small>' + escapeHtml(row.contact_email) + '</small>';
}
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 += '<br><small>' + escapeHtml(row.certificate_code) + '</small>';
}
}
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 = '<span>' + escapeHtml(row.contact_name || '-') + '</span>';
if (row.contact_email) {
nameHtml += '<br><small>' + escapeHtml(row.contact_email) + '</small>';
}
var detailHtml = row.detail_html || '-';
if (detailHtml !== '-') {
detailHtml = '<details><summary>Anzeigen</summary>' + detailHtml + '</details>';
}
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');
});
}
})();