286 lines
13 KiB
JavaScript
286 lines
13 KiB
JavaScript
(() => {
|
|
'use strict';
|
|
|
|
const ROOT_ID = 'today';
|
|
const PANEL_STATE_KEY = 'vendoo_today_workspace_panels';
|
|
|
|
const createNode = (tag, className = '', text = '') => {
|
|
const node = document.createElement(tag);
|
|
if (className) node.className = className;
|
|
if (text) node.textContent = text;
|
|
return node;
|
|
};
|
|
|
|
const countValue = id => {
|
|
const value = Number.parseInt(document.getElementById(id)?.textContent || '0', 10);
|
|
return Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
};
|
|
|
|
function readPanelState() {
|
|
try {
|
|
const parsed = JSON.parse(localStorage.getItem(PANEL_STATE_KEY) || 'null');
|
|
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function savePanelState(state) {
|
|
try { localStorage.setItem(PANEL_STATE_KEY, JSON.stringify(state)); } catch {}
|
|
}
|
|
|
|
function focusTodayFilter(root, filter) {
|
|
const target = root.querySelector(`.today-filter-tabs [data-today-filter="${filter}"]`)
|
|
|| root.querySelector(`.today-summary-grid [data-today-filter="${filter}"]`);
|
|
target?.click();
|
|
}
|
|
|
|
function scrollToPanel(panel) {
|
|
panel?.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
window.setTimeout(() => panel?.querySelector('button, input, select, [tabindex="0"]')?.focus({ preventScroll: true }), 180);
|
|
}
|
|
|
|
function createCommandCenter(root) {
|
|
if (root.querySelector('.ux-today-command')) return;
|
|
const heading = root.querySelector('.studio-heading');
|
|
const summary = root.querySelector('.today-summary-grid');
|
|
if (!heading || !summary) return;
|
|
|
|
const command = createNode('section', 'ux-today-command');
|
|
const copy = createNode('div', 'ux-today-command-copy');
|
|
const date = createNode('span', 'ux-today-date');
|
|
date.textContent = new Intl.DateTimeFormat('de-DE', { weekday: 'long', day: '2-digit', month: 'long' }).format(new Date());
|
|
const title = createNode('h3', '', 'Tagesarbeit wird vorbereitet');
|
|
title.dataset.uxTodayTitle = 'true';
|
|
const description = createNode('p', '', 'Vendoo priorisiert offene Aufgaben, Veröffentlichungen und Artikel für dich.');
|
|
description.dataset.uxTodayCopy = 'true';
|
|
copy.append(date, title, description);
|
|
|
|
const actions = createNode('div', 'ux-today-command-actions');
|
|
const primary = createNode('button', 'vd-button primary ux-today-primary', 'Nächsten Schritt öffnen');
|
|
primary.type = 'button';
|
|
primary.dataset.uxTodayPrimary = 'true';
|
|
actions.append(primary);
|
|
|
|
const shortcuts = createNode('div', 'ux-today-shortcuts');
|
|
const shortcutData = [
|
|
['generator', 'Neuer Artikel'],
|
|
['quality-center', 'Qualität prüfen'],
|
|
['publish', 'Publish-Center'],
|
|
];
|
|
shortcutData.forEach(([target, label]) => {
|
|
const button = createNode('button', 'ux-today-shortcut', label);
|
|
button.type = 'button';
|
|
button.dataset.tabTarget = target;
|
|
shortcuts.append(button);
|
|
});
|
|
actions.append(shortcuts);
|
|
|
|
const main = createNode('div', 'ux-today-command-main');
|
|
main.append(copy, actions);
|
|
command.append(main, summary);
|
|
heading.after(command);
|
|
|
|
primary.addEventListener('click', () => runPrimaryAction(root));
|
|
}
|
|
|
|
function structureFilters(root) {
|
|
if (root.querySelector('.ux-today-filter-details')) return;
|
|
const filterbar = root.querySelector('.today-filterbar');
|
|
if (!filterbar) return;
|
|
const details = createNode('details', 'ux-today-filter-details');
|
|
const summary = createNode('summary', '', 'Ansicht & Sortierung');
|
|
const hint = createNode('span', '', 'Filter und Reihenfolge anpassen');
|
|
summary.append(hint);
|
|
filterbar.before(details);
|
|
details.append(summary, filterbar);
|
|
}
|
|
|
|
function createPanelToggle(panel, key, label, defaultCollapsed) {
|
|
if (!panel || panel.querySelector('[data-ux-today-panel-toggle]')) return;
|
|
const header = panel.querySelector('.work-panel-header');
|
|
if (!header) return;
|
|
const stored = readPanelState();
|
|
const collapsed = typeof stored[key] === 'boolean' ? stored[key] : defaultCollapsed;
|
|
panel.classList.toggle('ux-today-panel-collapsed', collapsed);
|
|
|
|
const button = createNode('button', 'ux-today-panel-toggle', collapsed ? `${label} öffnen` : `${label} einklappen`);
|
|
button.type = 'button';
|
|
button.dataset.uxTodayPanelToggle = key;
|
|
button.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
|
|
header.append(button);
|
|
|
|
button.addEventListener('click', () => {
|
|
const nextCollapsed = !panel.classList.contains('ux-today-panel-collapsed');
|
|
panel.classList.toggle('ux-today-panel-collapsed', nextCollapsed);
|
|
button.textContent = nextCollapsed ? `${label} öffnen` : `${label} einklappen`;
|
|
button.setAttribute('aria-expanded', nextCollapsed ? 'false' : 'true');
|
|
const next = readPanelState();
|
|
next[key] = nextCollapsed;
|
|
savePanelState(next);
|
|
});
|
|
}
|
|
|
|
function structurePanels(root) {
|
|
const workspace = root.querySelector('.studio-workspace');
|
|
const middle = root.querySelector('.today-middle-grid');
|
|
const ready = root.querySelector('.today-ready-panel');
|
|
const scheduled = root.querySelector('.today-scheduled-panel');
|
|
const attention = root.querySelector('.attention-panel');
|
|
const activity = root.querySelector('.today-activity-panel');
|
|
if (!workspace || !middle || !ready || !scheduled || !attention || !activity) return;
|
|
|
|
middle.classList.add('ux-today-flow-contents');
|
|
attention.classList.add('ux-today-priority-attention');
|
|
ready.classList.add('ux-today-priority-ready');
|
|
scheduled.classList.add('ux-today-secondary-scheduled');
|
|
activity.classList.add('ux-today-secondary-activity');
|
|
|
|
createPanelToggle(scheduled, 'scheduled', 'Planungen', countValue('today-count-scheduled') === 0);
|
|
createPanelToggle(activity, 'activity', 'Aktivität', true);
|
|
}
|
|
|
|
function createInspectorToggle(root) {
|
|
if (root.querySelector('[data-ux-today-inspector-toggle]')) return;
|
|
const inspector = root.querySelector('#studio-inspector');
|
|
const layout = root.querySelector('.studio-layout-v2');
|
|
if (!inspector || !layout) return;
|
|
|
|
const toggle = createNode('button', 'vd-button ux-today-inspector-toggle', 'Artikelkontext anzeigen');
|
|
toggle.type = 'button';
|
|
toggle.dataset.uxTodayInspectorToggle = 'true';
|
|
toggle.setAttribute('aria-controls', 'studio-inspector');
|
|
toggle.setAttribute('aria-expanded', 'false');
|
|
layout.before(toggle);
|
|
|
|
toggle.addEventListener('click', () => {
|
|
const open = !root.classList.contains('ux-today-inspector-open');
|
|
root.classList.toggle('ux-today-inspector-open', open);
|
|
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
toggle.textContent = open ? 'Artikelkontext schließen' : 'Artikelkontext anzeigen';
|
|
if (open) inspector.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
});
|
|
}
|
|
|
|
function runPrimaryAction(root) {
|
|
const attention = countValue('today-count-attention');
|
|
const ready = countValue('today-count-ready');
|
|
const scheduled = countValue('today-count-scheduled');
|
|
|
|
if (attention > 0) {
|
|
focusTodayFilter(root, 'attention');
|
|
scrollToPanel(root.querySelector('.attention-panel'));
|
|
return;
|
|
}
|
|
if (ready > 0) {
|
|
focusTodayFilter(root, 'ready');
|
|
scrollToPanel(root.querySelector('.today-ready-panel'));
|
|
return;
|
|
}
|
|
if (scheduled > 0) {
|
|
const panel = root.querySelector('.today-scheduled-panel');
|
|
panel?.classList.remove('ux-today-panel-collapsed');
|
|
const toggle = panel?.querySelector('[data-ux-today-panel-toggle]');
|
|
toggle?.setAttribute('aria-expanded', 'true');
|
|
if (toggle) toggle.textContent = 'Planungen einklappen';
|
|
focusTodayFilter(root, 'scheduled');
|
|
scrollToPanel(panel);
|
|
return;
|
|
}
|
|
document.getElementById('topbar-new-btn')?.click();
|
|
}
|
|
|
|
function updateCommandCenter(root) {
|
|
const counts = {
|
|
ready: countValue('today-count-ready'),
|
|
scheduled: countValue('today-count-scheduled'),
|
|
published: countValue('today-count-published'),
|
|
sold: countValue('today-count-sold'),
|
|
attention: countValue('today-count-attention'),
|
|
};
|
|
const title = root.querySelector('[data-ux-today-title]');
|
|
const copy = root.querySelector('[data-ux-today-copy]');
|
|
const primary = root.querySelector('[data-ux-today-primary]');
|
|
const inspectorContent = root.querySelector('#studio-inspector-content');
|
|
const inspectorToggle = root.querySelector('[data-ux-today-inspector-toggle]');
|
|
|
|
root.classList.toggle('ux-today-has-attention', counts.attention > 0);
|
|
root.classList.toggle('ux-today-no-attention', counts.attention === 0);
|
|
root.dataset.uxTodayPriority = counts.attention > 0 ? 'attention' : counts.ready > 0 ? 'ready' : counts.scheduled > 0 ? 'scheduled' : 'clear';
|
|
|
|
if (counts.attention > 0) {
|
|
if (title) title.textContent = `${counts.attention} ${counts.attention === 1 ? 'Problem zuerst klären' : 'Probleme zuerst klären'}`;
|
|
if (copy) copy.textContent = 'Blocker und fehlerhafte Vorgänge stehen heute vor Routinearbeit.';
|
|
if (primary) primary.textContent = 'Probleme anzeigen';
|
|
} else if (counts.ready > 0) {
|
|
if (title) title.textContent = `${counts.ready} ${counts.ready === 1 ? 'Artikel ist' : 'Artikel sind'} bereit`;
|
|
if (copy) copy.textContent = 'Diese Listings können geprüft, veröffentlicht oder weiterbearbeitet werden.';
|
|
if (primary) primary.textContent = 'Bereite Artikel prüfen';
|
|
} else if (counts.scheduled > 0) {
|
|
if (title) title.textContent = `${counts.scheduled} ${counts.scheduled === 1 ? 'Veröffentlichung läuft' : 'Veröffentlichungen laufen'}`;
|
|
if (copy) copy.textContent = 'Aktuell gibt es keine Blocker. Behalte nur die geplanten Vorgänge im Blick.';
|
|
if (primary) primary.textContent = 'Planungen öffnen';
|
|
} else {
|
|
if (title) title.textContent = 'Deine Tagesarbeit ist aufgeräumt';
|
|
if (copy) copy.textContent = `${counts.published} live · ${counts.sold} verkauft. Du kannst direkt mit einem neuen Artikel starten.`;
|
|
if (primary) primary.textContent = 'Neuen Artikel erstellen';
|
|
}
|
|
|
|
root.querySelectorAll('.today-summary-grid [data-today-filter]').forEach(button => {
|
|
const filter = button.dataset.todayFilter || 'all';
|
|
const value = filter === 'ready' ? counts.ready
|
|
: filter === 'scheduled' ? counts.scheduled
|
|
: filter === 'published' ? counts.published
|
|
: filter === 'sold' ? counts.sold
|
|
: filter === 'attention' ? counts.attention : 0;
|
|
button.setAttribute('aria-label', `${button.querySelector('small')?.textContent || filter}: ${value}`);
|
|
});
|
|
|
|
const hasContext = Boolean(inspectorContent && !inspectorContent.classList.contains('hidden'));
|
|
root.classList.toggle('ux-today-has-context', hasContext);
|
|
if (inspectorToggle && !root.classList.contains('ux-today-inspector-open')) {
|
|
inspectorToggle.textContent = hasContext ? 'Ausgewählten Artikel anzeigen' : 'Artikelkontext anzeigen';
|
|
}
|
|
}
|
|
|
|
function setupKeyboardNavigation(root) {
|
|
const controls = [...root.querySelectorAll('.today-summary-grid [data-today-filter]')];
|
|
controls.forEach(button => button.addEventListener('keydown', event => {
|
|
if (!['ArrowLeft', 'ArrowRight'].includes(event.key)) return;
|
|
const index = controls.indexOf(button);
|
|
if (index < 0) return;
|
|
event.preventDefault();
|
|
const direction = event.key === 'ArrowRight' ? 1 : -1;
|
|
controls[(index + direction + controls.length) % controls.length]?.focus();
|
|
}));
|
|
}
|
|
|
|
function observeTodayState(root) {
|
|
const targets = [
|
|
'today-count-ready', 'today-count-scheduled', 'today-count-published', 'today-count-sold',
|
|
'today-count-attention', 'attention-count', 'today-ready-total', 'today-scheduled-total',
|
|
].map(id => document.getElementById(id)).filter(Boolean);
|
|
const inspectorContent = root.querySelector('#studio-inspector-content');
|
|
if (inspectorContent) targets.push(inspectorContent);
|
|
const observer = new MutationObserver(() => window.requestAnimationFrame(() => updateCommandCenter(root)));
|
|
targets.forEach(target => observer.observe(target, { childList: true, characterData: true, subtree: true, attributes: target === inspectorContent, attributeFilter: target === inspectorContent ? ['class'] : undefined }));
|
|
window.addEventListener('beforeunload', () => observer.disconnect(), { once: true });
|
|
}
|
|
|
|
function initTodayWorkspace() {
|
|
const root = document.getElementById(ROOT_ID);
|
|
if (!root || root.dataset.uxTodayWorkspace === 'ready') return;
|
|
createCommandCenter(root);
|
|
structureFilters(root);
|
|
structurePanels(root);
|
|
createInspectorToggle(root);
|
|
setupKeyboardNavigation(root);
|
|
updateCommandCenter(root);
|
|
observeTodayState(root);
|
|
root.dataset.uxTodayWorkspace = 'ready';
|
|
root.classList.add('ux-today-workspace-ready');
|
|
}
|
|
|
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initTodayWorkspace, { once: true });
|
|
else initTodayWorkspace();
|
|
})(); |