diff --git a/modules/mining-checker/assets/css/app.css b/modules/mining-checker/assets/css/app.css index c5759ad9..32c363e8 100644 --- a/modules/mining-checker/assets/css/app.css +++ b/modules/mining-checker/assets/css/app.css @@ -36,6 +36,87 @@ padding: 0; } +#mining-checker-app .mc-app-shell { + display: grid; + grid-template-columns: minmax(240px, 300px) minmax(0, 1fr); + gap: 18px; + align-items: start; +} + +#mining-checker-app .mc-sidebar, +#mining-checker-app .mc-hero-panel { + border: 1px solid var(--mc-line); + border-radius: 18px; + background: var(--mc-surface); + box-shadow: 0 10px 24px rgba(15, 23, 42, 0.06); +} + +#mining-checker-app .mc-sidebar { + padding: 20px; + display: grid; + gap: 18px; + position: sticky; + top: 0; +} + +#mining-checker-app .mc-sidebar-brand, +#mining-checker-app .mc-nav-list, +#mining-checker-app .mc-main { + display: grid; + gap: 14px; +} + +#mining-checker-app .mc-sidebar-title { + font-size: 1.35rem; +} + +#mining-checker-app .mc-nav-button { + display: grid; + gap: 6px; + width: 100%; + padding: 14px 16px; + text-align: left; + border-radius: 16px; + border: 1px solid var(--mc-line); + background: #fff; + color: var(--mc-text); + cursor: pointer; + transition: 160ms ease; +} + +#mining-checker-app .mc-nav-button:hover { + border-color: var(--mc-line-strong); + transform: translateY(-1px); +} + +#mining-checker-app .mc-nav-button.is-active { + border-color: var(--mc-accent); + background: #eff6ff; + box-shadow: inset 0 0 0 1px rgba(37, 99, 235, 0.08); +} + +#mining-checker-app .mc-nav-button strong { + color: var(--mc-text); +} + +#mining-checker-app .mc-nav-meta { + font-size: 0.85rem; + line-height: 1.45; + color: var(--mc-text-muted); +} + +#mining-checker-app .mc-hero-panel { + padding: 20px 22px; + display: flex; + justify-content: space-between; + gap: 16px; + align-items: flex-start; +} + +#mining-checker-app .mc-inline-row--wrap { + flex-wrap: wrap; +} + #mining-checker-app .mc-stack { display: grid; gap: 16px; @@ -260,6 +341,20 @@ font-weight: 700; } +@media (max-width: 980px) { + #mining-checker-app .mc-app-shell { + grid-template-columns: minmax(0, 1fr); + } + + #mining-checker-app .mc-sidebar { + position: static; + } + + #mining-checker-app .mc-hero-panel { + flex-direction: column; + } +} + #mining-checker-app .mc-button--secondary { background: #ffffff; border-color: #cbd5e1; diff --git a/modules/mining-checker/assets/js/app.js b/modules/mining-checker/assets/js/app.js index e81d2881..d980f899 100644 --- a/modules/mining-checker/assets/js/app.js +++ b/modules/mining-checker/assets/js/app.js @@ -48,6 +48,34 @@ })(); const initialDebugMode = normalizedOptions.moduleDebugEnabled === true || (document.body && document.body.dataset.moduleDebugEnabled === '1'); + const fallbackSections = [ + ['overview', 'Übersicht'], + ['upload', 'Upload'], + ['measurements', 'Mining-History'], + ['wallet', 'Wallet'], + ['mining', 'Miner-Daten'], + ['dashboards', 'Dashboards'], + ['settings', 'Settings'], + ]; + const sectionEntries = (() => { + const ordered = []; + const seen = new Set(); + const register = (key, label) => { + const normalizedKey = String(key || '').trim(); + const normalizedLabel = String(label || '').trim(); + if (!normalizedKey || !normalizedLabel || seen.has(normalizedKey)) { + return; + } + seen.add(normalizedKey); + ordered.push([normalizedKey, normalizedLabel]); + }; + + configuredSections.forEach((entry) => register(entry[0], entry[1])); + fallbackSections.forEach((entry) => register(entry[0], entry[1])); + + return ordered; + })(); + const sectionMap = new Map(sectionEntries); function getCookie(name) { const pattern = `; ${document.cookie}`; const parts = pattern.split(`; ${name}=`); @@ -262,6 +290,36 @@ return `${map.year}-${map.month}-${map.day}`; } + function currentSectionLabel(sectionId) { + return sectionMap.get(String(sectionId || '').trim()) || 'Bereich'; + } + + function currentSectionSummary(sectionId) { + if (sectionId === 'overview') { + return 'Kernkennzahlen, ROI, Break-even und Zielmonitor.'; + } + if (sectionId === 'upload') { + return 'Neue Mining-Daten per OCR, Import oder manueller Eingabe erfassen.'; + } + if (sectionId === 'measurements') { + return 'Historie der letzten Uploads mit Performance- und Trendwerten.'; + } + if (sectionId === 'wallet') { + return 'Wallet-Snapshots und erkannte Asset-Bestände auswerten.'; + } + if (sectionId === 'mining') { + return 'Miner, Angebote, Auszahlungen und operative Daten verwalten.'; + } + if (sectionId === 'dashboards') { + return 'Gespeicherte Auswertungen und Visualisierungen anzeigen.'; + } + if (sectionId === 'settings') { + return 'Schema, Modulrechte, DB-Checks und Basis-Settings steuern.'; + } + + return 'Bereich des Mining-Checkers'; + } + function fmtDate(value) { if (!value) { return 'n/a'; @@ -2233,11 +2291,55 @@ return h('div', { className: 'mc-grid-bg', }, [ - h('div', { key: 'shell', className: 'mc-shell mc-stack' }, [ - error ? h('div', { key: 'error', className: 'mc-alert mc-alert--error' }, error) : null, - message ? h('div', { key: 'message', className: 'mc-alert mc-alert--success' }, message) : null, - loading ? h('div', { key: 'loading', className: 'mc-alert mc-alert--warning' }, 'Mining-Checker Daten werden aktualisiert …') : null, - renderTab(), + h('div', { key: 'shell', className: 'mc-shell' }, [ + h('div', { key: 'frame', className: 'mc-app-shell' }, [ + h('aside', { key: 'sidebar', className: 'mc-sidebar' }, [ + h('div', { key: 'brand', className: 'mc-sidebar-brand' }, [ + h('div', { key: 'kicker', className: 'mc-kicker' }, 'Modul'), + h('h1', { key: 'title', className: 'mc-sidebar-title' }, 'Mining-Checker'), + h('p', { key: 'copy', className: 'mc-text' }, 'Erfassung, OCR-Auswertung und Analyse von Mining-Messwerten in einer gemeinsamen Modulansicht.'), + ]), + h('div', { key: 'nav', className: 'mc-nav-list' }, + sectionEntries.map(([key, label]) => h('button', { + key, + type: 'button', + className: cx('mc-nav-button', activeTab === key && 'is-active'), + onClick: () => { + if (key === activeTab) { + return; + } + window.history.pushState({ miningCheckerView: key }, '', `/module/mining-checker?view=${encodeURIComponent(key)}`); + setActiveTab(key); + }, + }, [ + h('strong', { key: 'label' }, label), + h('span', { key: 'meta', className: 'mc-nav-meta' }, currentSectionSummary(key)), + ])) + ), + ]), + h('main', { key: 'main', className: 'mc-main' }, [ + h('section', { key: 'hero', className: 'mc-hero-panel' }, [ + h('div', { key: 'hero-copy', className: 'mc-hero-copy' }, [ + h('div', { key: 'hero-kicker', className: 'mc-kicker' }, 'Mining-Checker'), + h('h2', { key: 'hero-title', className: 'mc-section-title' }, currentSectionLabel(activeTab)), + h('p', { key: 'hero-text', className: 'mc-text' }, currentSectionSummary(activeTab)), + ]), + h('div', { key: 'hero-badges', className: 'mc-inline-row mc-inline-row--wrap' }, [ + h(Badge, { key: 'project', tone: 'info' }, `Projekt ${projectKey}`), + h(Badge, { key: 'report-currency', tone: 'info' }, `Report ${reportCurrency}`), + latest && latest.measured_at + ? h(Badge, { key: 'latest', tone: 'success' }, `Letzter Upload ${fmtDate(latest.measured_at)}`) + : h(Badge, { key: 'latest-empty', tone: 'warn' }, 'Noch kein Upload'), + ]), + ]), + h('div', { key: 'content', className: 'mc-stack' }, [ + error ? h('div', { key: 'error', className: 'mc-alert mc-alert--error' }, error) : null, + message ? h('div', { key: 'message', className: 'mc-alert mc-alert--success' }, message) : null, + loading ? h('div', { key: 'loading', className: 'mc-alert mc-alert--warning' }, 'Mining-Checker Daten werden aktualisiert …') : null, + renderTab(), + ]), + ]), + ]), ]), ]);