Add trade fair column settings modal with visibility controls

This commit is contained in:
2026-02-21 20:14:08 +01:00
parent 4b1e935000
commit 7abb98e924
3 changed files with 420 additions and 10 deletions

View File

@@ -2,11 +2,19 @@
const tableBody = document.getElementById('tradeFairTableBody');
const searchInput = document.getElementById('tradeFairSearchInput');
const meta = document.getElementById('tradeFairMeta');
const columnSettingsBtn = document.getElementById('tradeFairColumnSettingsBtn');
const columnsModal = document.getElementById('tradeFairColumnsModal');
const columnsModalBackdrop = document.getElementById('tradeFairColumnsModalBackdrop');
const columnsModalClose = document.getElementById('tradeFairColumnsModalClose');
const columnsModalDone = document.getElementById('tradeFairColumnsDoneBtn');
const columnsModalReset = document.getElementById('tradeFairColumnsResetBtn');
const columnsList = document.getElementById('tradeFairColumnsList');
const sortButtons = Array.from(document.querySelectorAll('.bookmark-subpage__sort[data-trade-sort]'));
const table = tableBody ? tableBody.closest('table') : null;
const headerRow = table ? table.querySelector('thead tr') : null;
const SORT_STATE_KEY = 'fb_trade_fairs_sort_v1';
const COLUMN_ORDER_STATE_KEY = 'fb_trade_fairs_columns_v1';
const COLUMN_VISIBILITY_STATE_KEY = 'fb_trade_fairs_column_visibility_v1';
const LAST_OPEN_STATE_KEY = 'fb_trade_fairs_last_open_v1';
const DEFAULT_COLUMN_ORDER = [
'tage_bis_start',
@@ -33,12 +41,14 @@
}
const headerCellsByKey = new Map();
const columnLabelByKey = new Map();
sortButtons.forEach((button) => {
const key = button.dataset.tradeSort;
const th = button.closest('th');
if (!key || !th) {
return;
}
columnLabelByKey.set(key, button.textContent.trim());
th.dataset.columnKey = key;
th.draggable = true;
headerCellsByKey.set(key, th);
@@ -586,8 +596,6 @@
rang: index + 1
}));
const HIDDEN_FREE_FAIRS_COUNT = TRADE_FAIRS.length - EFFECTIVE_TRADE_FAIRS.length;
const SORT_TYPES = {
tage_bis_start: 'number',
rang: 'number',
@@ -613,6 +621,7 @@
let searchTerm = '';
let draggedColumnKey = null;
let lastOpenedByTradeFair = {};
let columnVisibility = Object.fromEntries(DEFAULT_COLUMN_ORDER.map((key) => [key, true]));
function toNumber(value) {
if (typeof value === 'number' && Number.isFinite(value)) {
@@ -807,6 +816,251 @@
}
}
function normalizeColumnVisibility(rawState) {
if (!rawState || typeof rawState !== 'object' || Array.isArray(rawState)) {
return null;
}
const normalized = {};
DEFAULT_COLUMN_ORDER.forEach((key) => {
const value = rawState[key];
normalized[key] = value !== false;
});
return normalized;
}
function loadColumnVisibility() {
try {
const raw = localStorage.getItem(COLUMN_VISIBILITY_STATE_KEY);
if (!raw) {
return;
}
const parsed = JSON.parse(raw);
const normalized = normalizeColumnVisibility(parsed);
if (!normalized) {
return;
}
columnVisibility = normalized;
} catch (_error) {
// ignore
}
}
function persistColumnVisibility() {
try {
localStorage.setItem(COLUMN_VISIBILITY_STATE_KEY, JSON.stringify(columnVisibility));
} catch (_error) {
// ignore
}
}
function isColumnVisible(columnKey) {
return columnVisibility[columnKey] !== false;
}
function getVisibleColumnOrder() {
const order = getColumnOrderFromHeader().filter((columnKey) => isColumnVisible(columnKey));
if (order.length) {
return order;
}
return ['messe'];
}
function ensureValidSortColumn() {
if (isColumnVisible(sortKey)) {
return;
}
const visibleColumns = getVisibleColumnOrder();
if (visibleColumns.includes('rang')) {
sortKey = 'rang';
sortDirection = 'asc';
persistSortState();
return;
}
sortKey = visibleColumns[0] || 'messe';
sortDirection = 'asc';
persistSortState();
}
function applyColumnVisibilityToHeader() {
headerCellsByKey.forEach((th, key) => {
const visible = isColumnVisible(key);
th.hidden = !visible;
th.style.display = visible ? '' : 'none';
});
}
function getColumnLabel(columnKey) {
const fromBase = sortButtons.find((button) => button.dataset.tradeSort === columnKey)?.dataset.baseLabel;
if (fromBase) {
return fromBase;
}
return columnLabelByKey.get(columnKey) || columnKey;
}
function moveColumnInOrder(columnKey, direction) {
const order = getColumnOrderFromHeader();
const index = order.indexOf(columnKey);
if (index === -1) {
return;
}
const targetIndex = direction < 0 ? index - 1 : index + 1;
if (targetIndex < 0 || targetIndex >= order.length) {
return;
}
const nextOrder = [...order];
const [item] = nextOrder.splice(index, 1);
nextOrder.splice(targetIndex, 0, item);
applyColumnOrder(nextOrder);
persistColumnOrder();
applyColumnVisibilityToHeader();
render();
renderColumnSettingsList();
}
function setColumnVisibility(columnKey, visible) {
const nextState = {
...columnVisibility,
[columnKey]: visible
};
const visibleCount = DEFAULT_COLUMN_ORDER.filter((key) => nextState[key] !== false).length;
if (visibleCount === 0) {
return false;
}
columnVisibility = nextState;
persistColumnVisibility();
applyColumnVisibilityToHeader();
ensureValidSortColumn();
updateSortButtonState();
render();
return true;
}
function renderColumnSettingsList() {
if (!columnsList) {
return;
}
const order = getColumnOrderFromHeader();
columnsList.innerHTML = '';
order.forEach((columnKey, index) => {
const item = document.createElement('div');
item.className = 'bookmark-columns-modal__item';
const label = document.createElement('label');
label.className = 'bookmark-columns-modal__toggle';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = isColumnVisible(columnKey);
checkbox.addEventListener('change', () => {
const ok = setColumnVisibility(columnKey, checkbox.checked);
if (!ok) {
checkbox.checked = true;
return;
}
renderColumnSettingsList();
});
const text = document.createElement('span');
text.textContent = getColumnLabel(columnKey);
label.append(checkbox, text);
const actions = document.createElement('div');
actions.className = 'bookmark-columns-modal__item-actions';
const upButton = document.createElement('button');
upButton.type = 'button';
upButton.className = 'bookmark-columns-modal__move';
upButton.textContent = '↑';
upButton.title = 'Nach oben';
upButton.disabled = index === 0;
upButton.addEventListener('click', () => {
moveColumnInOrder(columnKey, -1);
});
const downButton = document.createElement('button');
downButton.type = 'button';
downButton.className = 'bookmark-columns-modal__move';
downButton.textContent = '↓';
downButton.title = 'Nach unten';
downButton.disabled = index === order.length - 1;
downButton.addEventListener('click', () => {
moveColumnInOrder(columnKey, 1);
});
actions.append(upButton, downButton);
item.append(label, actions);
columnsList.appendChild(item);
});
}
function closeColumnSettingsModal() {
if (!columnsModal) {
return;
}
columnsModal.hidden = true;
}
function openColumnSettingsModal() {
if (!columnsModal) {
return;
}
renderColumnSettingsList();
columnsModal.hidden = false;
}
function resetColumnSettings() {
applyColumnOrder(DEFAULT_COLUMN_ORDER);
columnVisibility = Object.fromEntries(DEFAULT_COLUMN_ORDER.map((key) => [key, true]));
persistColumnOrder();
persistColumnVisibility();
applyColumnVisibilityToHeader();
ensureValidSortColumn();
updateSortButtonState();
render();
renderColumnSettingsList();
}
function setupColumnSettingsModal() {
if (!columnSettingsBtn || !columnsModal) {
return;
}
columnSettingsBtn.addEventListener('click', () => {
openColumnSettingsModal();
});
if (columnsModalBackdrop) {
columnsModalBackdrop.addEventListener('click', () => {
closeColumnSettingsModal();
});
}
if (columnsModalClose) {
columnsModalClose.addEventListener('click', () => {
closeColumnSettingsModal();
});
}
if (columnsModalDone) {
columnsModalDone.addEventListener('click', () => {
closeColumnSettingsModal();
});
}
if (columnsModalReset) {
columnsModalReset.addEventListener('click', () => {
resetColumnSettings();
});
}
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && columnsModal && !columnsModal.hidden) {
closeColumnSettingsModal();
}
});
}
function normalizeLastOpenedState(rawState) {
if (!rawState || typeof rawState !== 'object' || Array.isArray(rawState)) {
return {};
@@ -1007,6 +1261,7 @@
clearDropMarkers();
persistColumnOrder();
render();
renderColumnSettingsList();
});
th.addEventListener('dragend', () => {
@@ -1183,14 +1438,14 @@
const normalizedRows = EFFECTIVE_TRADE_FAIRS.map(normalizeRow);
const filtered = normalizedRows.filter((row) => matchesSearch(row, searchTerm));
const sorted = sortRows(filtered);
const columnOrder = getColumnOrderFromHeader();
const visibleColumnOrder = getVisibleColumnOrder();
tableBody.innerHTML = '';
if (!sorted.length) {
const emptyRow = document.createElement('tr');
const emptyCell = document.createElement('td');
emptyCell.colSpan = columnOrder.length;
emptyCell.colSpan = visibleColumnOrder.length;
emptyCell.textContent = 'Keine Messe zum Suchbegriff gefunden.';
emptyRow.appendChild(emptyCell);
tableBody.appendChild(emptyRow);
@@ -1198,7 +1453,7 @@
sorted.forEach((row) => {
const tr = document.createElement('tr');
const rowCells = createRowCells(row);
columnOrder.forEach((columnKey) => {
visibleColumnOrder.forEach((columnKey) => {
const cell = rowCells[columnKey] || createCell('k.A.', columnKey);
tr.appendChild(cell);
});
@@ -1209,10 +1464,7 @@
const activeSortButton = sortButtons.find((button) => button.dataset.tradeSort === sortKey);
const sortLabel = activeSortButton?.dataset.baseLabel || sortKey;
const hiddenInfo = HIDDEN_FREE_FAIRS_COUNT > 0
? ` | ${HIDDEN_FREE_FAIRS_COUNT} kostenlose Messe${HIDDEN_FREE_FAIRS_COUNT === 1 ? '' : 'n'} ausgeblendet`
: '';
meta.textContent = `${sorted.length} von ${EFFECTIVE_TRADE_FAIRS.length} Messen${hiddenInfo} | Sortierung: ${sortLabel} (${sortDirection === 'asc' ? 'aufsteigend' : 'absteigend'})`;
meta.textContent = `${sorted.length} von ${EFFECTIVE_TRADE_FAIRS.length} Messen | Sortierung: ${sortLabel} (${sortDirection === 'asc' ? 'aufsteigend' : 'absteigend'})`;
}
sortButtons.forEach((button) => {
@@ -1248,8 +1500,12 @@
lastOpenedByTradeFair = loadLastOpenedState();
loadColumnOrder();
loadColumnVisibility();
applyColumnVisibilityToHeader();
setupColumnDragAndDrop();
setupColumnSettingsModal();
loadSortState();
ensureValidSortColumn();
updateSortButtonState();
render();
})();