dsasd
This commit is contained in:
@@ -667,9 +667,34 @@
|
||||
]);
|
||||
}
|
||||
|
||||
function miningCheckerViewFromHref(href) {
|
||||
try {
|
||||
const url = new URL(href, window.location.origin);
|
||||
if (url.pathname !== '/module/mining-checker') {
|
||||
return null;
|
||||
}
|
||||
return url.searchParams.get('view') || 'overview';
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function syncMiningCheckerTabButtons(activeTab) {
|
||||
const buttons = Array.from(document.querySelectorAll('.module-tabs a[href*="/module/mining-checker"]'));
|
||||
for (const button of buttons) {
|
||||
const tab = miningCheckerViewFromHref(button.getAttribute('href') || '');
|
||||
if (!tab) {
|
||||
continue;
|
||||
}
|
||||
const isActive = tab === activeTab;
|
||||
button.classList.toggle('module-button--tab-active', isActive);
|
||||
button.classList.toggle('module-button--tab', !isActive);
|
||||
}
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [projectKey, setProjectKey] = useState(initialProjectKey);
|
||||
const [activeTab] = useState(initialActiveTab);
|
||||
const [activeTab, setActiveTab] = useState(initialActiveTab);
|
||||
const [payload, setPayload] = useState(() => normalizeBootstrap(null, initialProjectKey));
|
||||
const [dashboardData, setDashboardData] = useState({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -1159,14 +1184,46 @@
|
||||
|
||||
useEffect(() => {
|
||||
loadBootstrap(projectKey);
|
||||
}, [projectKey]);
|
||||
}, [projectKey, activeTab]);
|
||||
|
||||
useEffect(() => {
|
||||
syncMiningCheckerTabButtons(activeTab);
|
||||
|
||||
function handleNavigationClick(event) {
|
||||
const link = event.target instanceof Element
|
||||
? event.target.closest('.module-tabs a[href*="/module/mining-checker"]')
|
||||
: null;
|
||||
if (!link) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextTab = miningCheckerViewFromHref(link.getAttribute('href') || '');
|
||||
if (!nextTab || nextTab === activeTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
window.history.pushState({ miningCheckerView: nextTab }, '', `/module/mining-checker?view=${encodeURIComponent(nextTab)}`);
|
||||
setActiveTab(nextTab);
|
||||
}
|
||||
|
||||
function handlePopState() {
|
||||
const nextTab = miningCheckerViewFromHref(window.location.href) || 'overview';
|
||||
setActiveTab(nextTab);
|
||||
}
|
||||
|
||||
document.addEventListener('click', handleNavigationClick);
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
return () => {
|
||||
document.removeEventListener('click', handleNavigationClick);
|
||||
window.removeEventListener('popstate', handlePopState);
|
||||
};
|
||||
}, [activeTab]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === 'settings') {
|
||||
loadSchemaStatus(projectKey);
|
||||
loadModuleAuth();
|
||||
}
|
||||
}, [activeTab, projectKey]);
|
||||
}, [projectKey]);
|
||||
|
||||
useEffect(() => {
|
||||
async function loadSavedDashboards() {
|
||||
@@ -1937,8 +1994,8 @@
|
||||
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-empty' }, 'Lade Mining-Checker Daten …') : null,
|
||||
payload ? renderTab() : null,
|
||||
loading ? h('div', { key: 'loading', className: 'mc-alert mc-alert--warning' }, 'Mining-Checker Daten werden aktualisiert …') : null,
|
||||
renderTab(),
|
||||
]),
|
||||
]);
|
||||
|
||||
@@ -2077,10 +2134,41 @@
|
||||
}
|
||||
|
||||
if (activeTab === 'measurements') {
|
||||
return h('div', { className: 'mc-main-grid' }, [
|
||||
h('div', { className: 'mc-stack' }, [
|
||||
]),
|
||||
panel('Mining-History', 'Die letzten 10 Mining-Uploads inkl. Performance-Werten und OCR-Metadaten.', h('div', { className: 'mc-table-shell' }, [
|
||||
h('table', { key: 'table', className: 'mc-table' }, [
|
||||
h('thead', { key: 'thead' }, h('tr', null, [
|
||||
'Zeit', 'Coins', 'Kurs', 'Quelle', perDayLabel, 'Trend', 'Notiz', 'Aktion'
|
||||
].map((label) => h('th', { key: label }, label)))),
|
||||
h('tbody', { key: 'tbody' },
|
||||
measurements.slice(-10).reverse().map((row) => h('tr', { key: row.id }, [
|
||||
h('td', { key: 'measured' }, fmtDate(row.measured_at)),
|
||||
h('td', { key: 'coins' }, `${fmtNumber(row.coins_total, 6)} ${row.coin_currency || currentCoinCurrency}`),
|
||||
h('td', { key: 'price' }, row.price_per_coin ? `${fmtNumber(row.price_per_coin, 6)} ${row.price_currency}` : 'n/a'),
|
||||
h('td', { key: 'source' }, row.source),
|
||||
h('td', { key: 'rate' }, fmtNumber(row.doge_per_day_interval, 4)),
|
||||
h('td', { key: 'trend' }, row.trend_label),
|
||||
h('td', { key: 'note' }, row.note || row.ocr_flags.join(', ') || '—'),
|
||||
h('td', { key: 'action' }, h('button', {
|
||||
type: 'button',
|
||||
className: 'mc-button mc-button--ghost',
|
||||
onClick: () => deleteMeasurement(row.id),
|
||||
disabled: saving,
|
||||
}, 'Loeschen')),
|
||||
]))
|
||||
),
|
||||
]),
|
||||
])),
|
||||
]);
|
||||
}
|
||||
|
||||
if (activeTab === 'upload') {
|
||||
return h('div', { className: 'mc-main-grid' }, [
|
||||
h('div', { className: 'mc-stack' }, [
|
||||
renderSharedOcrPanel(),
|
||||
panel('Messpunkt manuell erfassen', 'Direkte Eingabe eines einzelnen Messpunkts mit serverseitiger Validierung.', h('form', {
|
||||
panel('Mining manuell erfassen', 'Direkte Eingabe eines einzelnen Mining-Messpunkts mit serverseitiger Validierung.', h('form', {
|
||||
className: 'mc-form',
|
||||
onSubmit: function (event) {
|
||||
event.preventDefault();
|
||||
@@ -2098,7 +2186,7 @@
|
||||
disabled: saving,
|
||||
}, saving ? 'Speichert …' : 'Messpunkt speichern'),
|
||||
])),
|
||||
panel('Import per Copy & Paste', 'Mehrere historische Messpunkte auf einmal einfuegen. Doppelte Eintraege werden ignoriert.', h('form', {
|
||||
panel('Mining-Import per Copy & Paste', 'Mehrere historische Mining-Messpunkte auf einmal einfuegen. Doppelte Eintraege werden ignoriert.', h('form', {
|
||||
className: 'mc-form',
|
||||
onSubmit: submitMeasurementImport,
|
||||
}, [
|
||||
@@ -2158,37 +2246,12 @@
|
||||
]),
|
||||
]) : null,
|
||||
]),
|
||||
panel('Messhistorie', 'Die letzten 10 Uploads inkl. Performance-Werten und OCR-Metadaten.', h('div', { className: 'mc-table-shell' }, [
|
||||
h('table', { key: 'table', className: 'mc-table' }, [
|
||||
h('thead', { key: 'thead' }, h('tr', null, [
|
||||
'Zeit', 'Coins', 'Kurs', 'Quelle', perDayLabel, 'Trend', 'Notiz', 'Aktion'
|
||||
].map((label) => h('th', { key: label }, label)))),
|
||||
h('tbody', { key: 'tbody' },
|
||||
measurements.slice(-10).reverse().map((row) => h('tr', { key: row.id }, [
|
||||
h('td', { key: 'measured' }, fmtDate(row.measured_at)),
|
||||
h('td', { key: 'coins' }, `${fmtNumber(row.coins_total, 6)} ${row.coin_currency || currentCoinCurrency}`),
|
||||
h('td', { key: 'price' }, row.price_per_coin ? `${fmtNumber(row.price_per_coin, 6)} ${row.price_currency}` : 'n/a'),
|
||||
h('td', { key: 'source' }, row.source),
|
||||
h('td', { key: 'rate' }, fmtNumber(row.doge_per_day_interval, 4)),
|
||||
h('td', { key: 'trend' }, row.trend_label),
|
||||
h('td', { key: 'note' }, row.note || row.ocr_flags.join(', ') || '—'),
|
||||
h('td', { key: 'action' }, h('button', {
|
||||
type: 'button',
|
||||
className: 'mc-button mc-button--ghost',
|
||||
onClick: () => deleteMeasurement(row.id),
|
||||
disabled: saving,
|
||||
}, 'Loeschen')),
|
||||
]))
|
||||
),
|
||||
]),
|
||||
])),
|
||||
]);
|
||||
}
|
||||
|
||||
if (activeTab === 'wallet') {
|
||||
return h('div', { className: 'mc-main-grid' }, [
|
||||
h('div', { className: 'mc-stack' }, [
|
||||
renderSharedOcrPanel(),
|
||||
panel('Wallet-Historie', `Erkannte Wallet-Snapshots. Fuer Reinvestitionen ist aktuell ${currentSettings.crypto_currency || 'DOGE'} entscheidend.`, h('div', { className: 'mc-table-shell' }, [
|
||||
h('table', { key: 'wallet-table', className: 'mc-table' }, [
|
||||
h('thead', { key: 'thead' }, h('tr', null, [
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
"title": "Mining-Checker",
|
||||
"description": "Erfassung, OCR-Auswertung und Analyse von DOGE-Mining-Messwerten als eingebettetes Modul.",
|
||||
"actions": [
|
||||
{ "label": "Nexus Übersicht", "href": "/modules" },
|
||||
{ "label": "Setup", "href": "/modules/setup/mining-checker", "variant": "secondary" }
|
||||
],
|
||||
"sections": [
|
||||
{ "key": "overview", "label": "Ueberblick" },
|
||||
{ "key": "measurements", "label": "Messpunkte" },
|
||||
{ "key": "overview", "label": "Übersicht" },
|
||||
{ "key": "upload", "label": "Upload" },
|
||||
{ "key": "measurements", "label": "Mining-History" },
|
||||
{ "key": "wallet", "label": "Wallet" },
|
||||
{ "key": "mining", "label": "Mining" },
|
||||
{ "key": "dashboards", "label": "Dashboards" },
|
||||
{ "key": "settings", "label": "Settings" }
|
||||
{ "key": "mining", "label": "Miner-Daten" },
|
||||
{ "key": "dashboards", "label": "Dashboards" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1224,7 +1224,7 @@ final class Router
|
||||
|
||||
private function bootstrapMeasurements(string $projectKey, array $settings, string $view): array
|
||||
{
|
||||
if (in_array($view, ['settings', 'dashboards', 'wallet'], true)) {
|
||||
if (in_array($view, ['upload', 'dashboards', 'wallet'], true)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -1313,7 +1313,7 @@ final class Router
|
||||
private function normalizeBootstrapView(string $view): string
|
||||
{
|
||||
$normalized = trim(strtolower($view));
|
||||
return in_array($normalized, ['overview', 'measurements', 'dashboards', 'mining', 'settings'], true)
|
||||
return in_array($normalized, ['overview', 'upload', 'measurements', 'wallet', 'dashboards', 'mining'], true)
|
||||
? $normalized
|
||||
: 'overview';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user