Files
vendoo/public/article-overview-ux.js

317 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(() => {
'use strict';
const VIEW_STORAGE_KEY = 'vendoo_article_overview_view_v1';
const STATUS_FILTERS = [
{ value: '', label: 'Alle' },
{ value: 'active', label: 'Aktiv' },
{ value: 'reserved', label: 'Reserviert' },
{ value: 'sold', label: 'Verkauft' },
];
const CELL_LABELS = {
platform: 'Plattform',
provider: 'AI',
price: 'Preis',
status: 'Status',
location: 'Lagerort',
published: 'Veröffentlichung',
};
let historyObserver = null;
let metaObserver = null;
let refreshQueued = false;
function historySection() {
return document.getElementById('history');
}
function createButton(label, className, attributes = {}) {
const button = document.createElement('button');
button.type = 'button';
button.className = className;
button.textContent = label;
for (const [name, value] of Object.entries(attributes)) button.setAttribute(name, value);
return button;
}
function currentView() {
const stored = localStorage.getItem(VIEW_STORAGE_KEY);
return stored === 'list' || stored === 'cards' ? stored : 'cards';
}
function applyView(mode, { persist = true } = {}) {
const section = historySection();
if (!section) return;
const normalized = mode === 'list' ? 'list' : 'cards';
section.dataset.articleView = normalized;
section.querySelectorAll('[data-article-view-choice]').forEach(button => {
const active = button.dataset.articleViewChoice === normalized;
button.classList.toggle('is-active', active);
button.setAttribute('aria-pressed', String(active));
});
if (persist) localStorage.setItem(VIEW_STORAGE_KEY, normalized);
}
function setStatusFilter(value) {
const select = document.getElementById('filter-status');
if (!select) return;
select.value = value;
select.dispatchEvent(new Event('change', { bubbles: true }));
queueRefresh();
}
function createOverviewToolbar() {
const section = historySection();
const filterbar = section?.querySelector('.history-filterbar');
if (!section || !filterbar || section.querySelector('.ux-article-toolbar')) return;
const toolbar = document.createElement('section');
toolbar.className = 'ux-article-toolbar';
toolbar.setAttribute('aria-label', 'Artikelansicht und Schnellfilter');
const summary = document.createElement('div');
summary.className = 'ux-article-summary';
const summaryTitle = document.createElement('strong');
summaryTitle.textContent = 'Artikelübersicht';
const summaryCount = document.createElement('span');
summaryCount.id = 'ux-article-result-count';
summaryCount.textContent = 'Artikel werden geladen …';
summary.append(summaryTitle, summaryCount);
const filters = document.createElement('div');
filters.className = 'ux-status-filters';
filters.setAttribute('role', 'group');
filters.setAttribute('aria-label', 'Status schnell filtern');
for (const filter of STATUS_FILTERS) {
const button = createButton(filter.label, 'ux-status-filter', {
'data-article-status': filter.value,
'aria-pressed': 'false',
});
button.addEventListener('click', () => setStatusFilter(filter.value));
filters.append(button);
}
const viewSwitch = document.createElement('div');
viewSwitch.className = 'ux-article-view-switch';
viewSwitch.setAttribute('role', 'group');
viewSwitch.setAttribute('aria-label', 'Darstellung wählen');
const cards = createButton('Karten', 'ux-view-choice', {
'data-article-view-choice': 'cards',
'aria-pressed': 'false',
});
const list = createButton('Liste', 'ux-view-choice', {
'data-article-view-choice': 'list',
'aria-pressed': 'false',
});
cards.addEventListener('click', () => applyView('cards'));
list.addEventListener('click', () => applyView('list'));
viewSwitch.append(cards, list);
toolbar.append(summary, filters, viewSwitch);
const activeFilters = document.createElement('div');
activeFilters.className = 'ux-active-filters';
activeFilters.id = 'ux-active-filters';
activeFilters.setAttribute('aria-live', 'polite');
filterbar.before(toolbar);
filterbar.after(activeFilters);
}
function enhanceBatchActions() {
const actions = document.querySelector('#history .history-batch-actions');
if (!actions || actions.querySelector('.ux-batch-primary')) return;
const primary = document.createElement('div');
primary.className = 'ux-batch-primary';
const more = document.createElement('details');
more.className = 'ux-batch-more';
const summary = document.createElement('summary');
summary.textContent = 'Weitere Aktionen';
const secondary = document.createElement('div');
secondary.className = 'ux-batch-secondary';
more.append(summary, secondary);
const primaryIds = ['history-publish-selected', 'history-edit-selected', 'history-bulk-status'];
const secondaryIds = ['history-price-selected', 'history-duplicate-selected', 'history-print-labels', 'history-delete-selected'];
for (const id of primaryIds) {
const button = document.getElementById(id);
if (button) primary.append(button);
}
for (const id of secondaryIds) {
const button = document.getElementById(id);
if (button) secondary.append(button);
}
actions.append(primary, more);
}
function annotateRows() {
const container = document.getElementById('history-list');
if (!container) return;
container.querySelectorAll('.history-row').forEach(row => {
for (const [column, label] of Object.entries(CELL_LABELS)) {
row.querySelector(`[data-column="${column}"]`)?.setAttribute('data-ux-label', label);
}
const title = row.querySelector('.history-article-title')?.textContent?.trim() || 'Artikel';
row.tabIndex = 0;
row.setAttribute('aria-label', `${title} öffnen`);
row.dataset.publishState = row.querySelector('.history-publish-empty') ? 'none' : 'available';
if (row.dataset.uxKeyboardBound !== 'true') {
row.dataset.uxKeyboardBound = 'true';
row.addEventListener('keydown', event => {
if ((event.key === 'Enter' || event.key === ' ') && event.target === row) {
event.preventDefault();
row.querySelector('.history-article-title')?.click();
}
});
}
});
}
function syncStatusButtons() {
const value = document.getElementById('filter-status')?.value || '';
document.querySelectorAll('[data-article-status]').forEach(button => {
const active = button.dataset.articleStatus === value;
button.classList.toggle('is-active', active);
button.setAttribute('aria-pressed', String(active));
});
}
function selectedOptionText(select) {
if (!select || !select.value) return '';
return select.options[select.selectedIndex]?.textContent?.trim() || select.value;
}
function createFilterChip(label, onClear) {
const chip = document.createElement('span');
chip.className = 'ux-filter-chip';
const text = document.createElement('span');
text.textContent = label;
const clear = createButton('×', 'ux-filter-chip-clear', { 'aria-label': `${label} entfernen` });
clear.addEventListener('click', onClear);
chip.append(text, clear);
return chip;
}
function clearControl(control, value = '') {
if (!control) return;
control.value = value;
control.dispatchEvent(new Event(control.tagName === 'INPUT' ? 'input' : 'change', { bubbles: true }));
queueRefresh();
}
function updateActiveFilters() {
const container = document.getElementById('ux-active-filters');
if (!container) return;
container.replaceChildren();
const query = document.getElementById('search-input');
const platform = document.getElementById('filter-platform');
const status = document.getElementById('filter-status');
const period = document.getElementById('history-period');
const dateFrom = document.getElementById('filter-date-from');
const dateTo = document.getElementById('filter-date-to');
const chips = [];
if (query?.value.trim()) chips.push(createFilterChip(`Suche: ${query.value.trim()}`, () => clearControl(query)));
if (platform?.value) chips.push(createFilterChip(`Plattform: ${selectedOptionText(platform)}`, () => clearControl(platform)));
if (status?.value) chips.push(createFilterChip(`Status: ${selectedOptionText(status)}`, () => clearControl(status)));
if (period?.value && period.value !== '90' && period.value !== 'custom') {
chips.push(createFilterChip(`Zeitraum: ${selectedOptionText(period)}`, () => clearControl(period, '90')));
}
if (dateFrom?.value) chips.push(createFilterChip(`Von: ${dateFrom.value}`, () => clearControl(dateFrom)));
if (dateTo?.value) chips.push(createFilterChip(`Bis: ${dateTo.value}`, () => clearControl(dateTo)));
if (!chips.length) {
container.classList.add('is-empty');
return;
}
container.classList.remove('is-empty');
const label = document.createElement('strong');
label.textContent = 'Aktive Filter';
container.append(label, ...chips);
}
function updateResultCount() {
const target = document.getElementById('ux-article-result-count');
if (!target) return;
const info = document.getElementById('history-total-info')?.textContent?.trim() || '';
const match = info.match(/von\s+(\d+)\s+Eintr/i);
const visible = document.querySelectorAll('#history-list .history-row').length;
if (match) target.textContent = `${match[1]} Artikel gefunden`;
else target.textContent = visible === 1 ? '1 Artikel gefunden' : `${visible} Artikel gefunden`;
}
function syncBatchState() {
const section = historySection();
const countText = document.getElementById('history-selected-count')?.textContent || '0';
const count = Number.parseInt(countText, 10) || 0;
section?.toggleAttribute('data-has-selection', count > 0);
const more = section?.querySelector('.ux-batch-more');
if (!count && more) more.open = false;
}
function refreshPresentation() {
refreshQueued = false;
annotateRows();
syncStatusButtons();
updateActiveFilters();
updateResultCount();
syncBatchState();
}
function queueRefresh() {
if (refreshQueued) return;
refreshQueued = true;
window.requestAnimationFrame(refreshPresentation);
}
function bindFilterState() {
const ids = ['search-input', 'filter-platform', 'filter-status', 'history-period', 'filter-date-from', 'filter-date-to'];
for (const id of ids) {
const control = document.getElementById(id);
if (!control || control.dataset.uxArticleBound === 'true') continue;
control.dataset.uxArticleBound = 'true';
control.addEventListener('input', queueRefresh);
control.addEventListener('change', queueRefresh);
}
document.getElementById('history-reset-filters')?.addEventListener('click', () => window.setTimeout(queueRefresh, 0));
const search = document.getElementById('search-input');
if (search) search.placeholder = 'Titel, SKU, Marke oder Beschreibung durchsuchen';
}
function observeArticleOverview() {
const list = document.getElementById('history-list');
const shell = document.querySelector('#history .history-table-shell');
if (list) {
historyObserver = new MutationObserver(queueRefresh);
historyObserver.observe(list, { childList: true, subtree: true });
}
if (shell) {
metaObserver = new MutationObserver(queueRefresh);
metaObserver.observe(shell, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ['class'] });
}
window.addEventListener('beforeunload', () => {
historyObserver?.disconnect();
metaObserver?.disconnect();
}, { once: true });
}
function initializeArticleOverview() {
const section = historySection();
if (!section || section.classList.contains('ux-article-overview-ready')) return;
createOverviewToolbar();
enhanceBatchActions();
bindFilterState();
applyView(currentView(), { persist: false });
observeArticleOverview();
refreshPresentation();
section.classList.add('ux-article-overview-ready');
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeArticleOverview, { once: true });
} else {
initializeArticleOverview();
}
})();