feat: add Vendoo UX foundation behavior

This commit is contained in:
Masterluke77
2026-07-11 02:55:52 +02:00
parent 685b9786a5
commit 0cbc8b8995
+246
View File
@@ -0,0 +1,246 @@
(() => {
'use strict';
const NAV_SECTIONS = [
{
id: 'start',
label: 'Start',
tabs: ['dashboard', 'today', 'generator'],
},
{
id: 'commerce',
label: 'Verkaufen',
tabs: ['history', 'quality-center', 'publish', 'inventory'],
},
{
id: 'content',
label: 'Inhalte & AI',
tabs: ['media-library', 'image-factory', 'flux-studio', 'templates'],
},
{
id: 'system',
label: 'Werkzeuge & System',
collapsible: true,
tabs: ['extensions', 'settings', 'admin', 'fees', 'trash'],
},
];
const VIEW_META = {
dashboard: { title: 'Übersicht', kicker: 'Start' },
today: { title: 'Aufgaben', kicker: 'Arbeitsbereich' },
generator: { title: 'Neuer Artikel', kicker: 'Erstellen' },
history: { title: 'Artikel', kicker: 'Verkaufen' },
'quality-center': { title: 'Qualität', kicker: 'Optimieren' },
publish: { title: 'Veröffentlichen', kicker: 'Marktplätze' },
inventory: { title: 'Lager', kicker: 'Bestand' },
templates: { title: 'Vorlagen', kicker: 'Inhalte' },
'flux-studio': { title: 'FLUX Studio', kicker: 'AI-Bilder' },
'media-library': { title: 'Medien', kicker: 'Bilder' },
'image-factory': { title: 'Bildwerkstatt', kicker: 'Produktion' },
extensions: { title: 'Erweiterungen', kicker: 'Browser' },
settings: { title: 'Einstellungen', kicker: 'System' },
admin: { title: 'Verwaltung', kicker: 'Benutzer & Betrieb' },
fees: { title: 'Gebühren', kicker: 'Kalkulation' },
trash: { title: 'Papierkorb', kicker: 'Wiederherstellen' },
};
const NAV_LABELS = {
dashboard: 'Übersicht',
today: 'Aufgaben',
generator: 'Neuer Artikel',
history: 'Artikel',
'quality-center': 'Qualität',
publish: 'Veröffentlichen',
inventory: 'Lager',
templates: 'Vorlagen',
'flux-studio': 'FLUX Studio',
'media-library': 'Medien',
'image-factory': 'Bildwerkstatt',
extensions: 'Erweiterungen',
settings: 'Einstellungen',
admin: 'Verwaltung',
fees: 'Gebühren',
trash: 'Papierkorb',
};
function getNavItem(tab) {
return document.querySelector(`.sidebar-nav .nav-item[data-tab="${tab}"]`);
}
function createSection(section) {
const element = document.createElement(section.collapsible ? 'details' : 'section');
element.className = `ux-nav-section ux-nav-section-${section.id}`;
element.dataset.uxNavSection = section.id;
if (section.collapsible) {
const stored = localStorage.getItem('vendoo_ux_system_nav_open');
element.open = stored === 'true' || section.tabs.some(tab => getNavItem(tab)?.classList.contains('active'));
const summary = document.createElement('summary');
summary.textContent = section.label;
element.append(summary);
element.addEventListener('toggle', () => {
localStorage.setItem('vendoo_ux_system_nav_open', String(element.open));
});
} else {
const label = document.createElement('div');
label.className = 'ux-nav-section-label';
label.textContent = section.label;
element.append(label);
}
const items = document.createElement('div');
items.className = 'ux-nav-section-items';
items.dataset.uxNavItems = section.id;
element.append(items);
return element;
}
function reorganizeNavigation() {
const nav = document.querySelector('.sidebar-nav');
if (!nav || nav.querySelector('.ux-nav-sections')) return;
const itemsByTab = new Map();
nav.querySelectorAll('.nav-item[data-tab]').forEach(item => {
itemsByTab.set(item.dataset.tab, item);
const label = item.querySelector('span');
if (label && NAV_LABELS[item.dataset.tab]) label.textContent = NAV_LABELS[item.dataset.tab];
item.setAttribute('aria-current', item.classList.contains('active') ? 'page' : 'false');
});
const shell = document.createElement('div');
shell.className = 'ux-nav-sections';
for (const section of NAV_SECTIONS) {
const sectionElement = createSection(section);
const container = sectionElement.querySelector('.ux-nav-section-items');
for (const tab of section.tabs) {
const item = itemsByTab.get(tab);
if (item) container.append(item);
}
shell.append(sectionElement);
}
const assigned = new Set(NAV_SECTIONS.flatMap(section => section.tabs));
const remaining = [...itemsByTab.entries()].filter(([tab]) => !assigned.has(tab));
if (remaining.length) {
const other = createSection({ id: 'other', label: 'Weitere', collapsible: true, tabs: [] });
const container = other.querySelector('.ux-nav-section-items');
remaining.forEach(([, item]) => container.append(item));
shell.append(other);
}
nav.querySelectorAll(':scope > .nav-group').forEach(group => group.remove());
nav.append(shell);
}
function activateView(tab) {
const item = getNavItem(tab);
if (!item || item.classList.contains('hidden')) return;
const systemSection = item.closest('details.ux-nav-section');
if (systemSection) systemSection.open = true;
item.click();
}
function createDashboardCommandCenter() {
const dashboard = document.getElementById('dashboard');
const layout = dashboard?.querySelector('.dashboard-layout');
if (!dashboard || !layout || dashboard.querySelector('.ux-command-center')) return;
const panel = document.createElement('section');
panel.className = 'ux-command-center';
panel.setAttribute('aria-label', 'Schnellstart');
panel.innerHTML = `
<div class="ux-command-copy">
<span>Schnellstart</span>
<h3>Was möchtest du als Nächstes erledigen?</h3>
<p>Die wichtigsten Wege sind direkt erreichbar. Detailauswertungen bleiben darunter verfügbar, ohne den Einstieg zu überladen.</p>
</div>
<div class="ux-command-actions">
<button type="button" class="ux-command-action primary" data-ux-target="generator">+ Neuer Artikel</button>
<button type="button" class="ux-command-action" data-ux-target="today">Aufgaben öffnen</button>
<button type="button" class="ux-command-action" data-ux-target="history">Artikel verwalten</button>
<button type="button" class="ux-command-action" data-ux-target="publish">Veröffentlichen</button>
</div>`;
layout.before(panel);
}
function simplifyDashboardRail() {
const rail = document.querySelector('#dashboard .dashboard-rail');
if (!rail || rail.querySelector('.ux-dashboard-more')) return;
const cards = [...rail.children];
const secondaryCards = cards.filter((_, index) => index > 0);
if (!secondaryCards.length) return;
const details = document.createElement('details');
details.className = 'ux-dashboard-more';
const summary = document.createElement('summary');
summary.textContent = 'Aktivität, Hinweise und Systemstatus';
const content = document.createElement('div');
content.className = 'ux-dashboard-more-content';
secondaryCards.forEach(card => content.append(card));
details.append(summary, content);
rail.append(details);
}
function updateContext() {
const active = document.querySelector('.sidebar-nav .nav-item.active[data-tab]');
const tab = active?.dataset.tab || document.querySelector('.tab-content.active')?.id || 'dashboard';
const meta = VIEW_META[tab] || { title: active?.textContent?.trim() || 'Vendoo', kicker: 'Arbeitsbereich' };
const title = document.getElementById('topbar-title');
const kicker = document.getElementById('topbar-kicker');
if (title) title.textContent = meta.title;
if (kicker) kicker.textContent = meta.kicker;
document.querySelectorAll('.sidebar-nav .nav-item[data-tab]').forEach(item => {
item.setAttribute('aria-current', item === active ? 'page' : 'false');
});
const system = active?.closest('details.ux-nav-section');
if (system) system.open = true;
}
function improveSearchPresentation() {
const search = document.getElementById('topbar-search-btn');
const label = search?.querySelector('span');
const shortcut = search?.querySelector('kbd');
if (label) label.textContent = 'Suchen, Seite öffnen oder Aktion starten …';
if (shortcut) shortcut.textContent = /Mac|iPhone|iPad/.test(navigator.platform) ? '⌘ K' : 'Strg K';
}
function bindUxActions() {
document.addEventListener('click', event => {
const target = event.target.closest?.('[data-ux-target]');
if (!target) return;
activateView(target.dataset.uxTarget);
});
}
function observeNavigation() {
const nav = document.querySelector('.sidebar-nav');
if (!nav) return;
const observer = new MutationObserver(mutations => {
if (mutations.some(mutation => mutation.type === 'attributes' && mutation.attributeName === 'class')) {
updateContext();
}
});
nav.querySelectorAll('.nav-item[data-tab]').forEach(item => observer.observe(item, { attributes: true, attributeFilter: ['class'] }));
window.addEventListener('beforeunload', () => observer.disconnect(), { once: true });
}
function initializeUxFoundation() {
reorganizeNavigation();
createDashboardCommandCenter();
simplifyDashboardRail();
improveSearchPresentation();
bindUxActions();
observeNavigation();
updateContext();
document.body.classList.add('ux-shell-ready');
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeUxFoundation, { once: true });
} else {
initializeUxFoundation();
}
})();