Files
vendoo/public/today-workspace.js
T

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();
})();