Miner-Upgrade
All checks were successful
Deploy / deploy-staging (push) Successful in 7s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-05-09 00:58:48 +02:00
parent ee5a46254f
commit fc95898a9d
11 changed files with 976 additions and 131 deletions

View File

@@ -344,6 +344,7 @@
purchased_miners: [],
measurement_rates: [],
},
wallet_snapshots: Array.isArray(normalized.wallet_snapshots) ? normalized.wallet_snapshots : [],
measurements: Array.isArray(normalized.measurements) ? normalized.measurements : [],
targets: Array.isArray(normalized.targets) ? normalized.targets : [],
dashboards: Array.isArray(normalized.dashboards) ? normalized.dashboards : [],
@@ -352,7 +353,15 @@
latest_measurement: null,
baseline: normalized.settings || null,
targets: Array.isArray(normalized.targets) ? normalized.targets : [],
payouts: { total_count: 0, total_coins: 0, current_visible_coins: null, current_effective_coins: null },
payouts: {
total_count: 0,
total_coins: 0,
current_visible_coins: null,
current_effective_coins: null,
wallet_balances: {},
wallet_balance_current_asset: null,
holdings_current_asset: null,
},
current_hashrate_mh: null,
miner_offers: [],
},
@@ -380,6 +389,7 @@
: {};
return {
kind: normalized.kind === 'wallet' ? 'wallet' : 'measurement',
suggested: {
measured_at: suggested.measured_at || '',
coins_total: suggested.coins_total ?? '',
@@ -388,6 +398,29 @@
note: suggested.note || '',
source: suggested.source || 'image_ocr',
},
suggested_wallet: normalized.suggested_wallet && typeof normalized.suggested_wallet === 'object'
? {
measured_at: normalized.suggested_wallet.measured_at || '',
total_value_amount: normalized.suggested_wallet.total_value_amount ?? '',
total_value_currency: normalized.suggested_wallet.total_value_currency || '',
wallet_balance: normalized.suggested_wallet.wallet_balance ?? '',
wallet_currency: normalized.suggested_wallet.wallet_currency || '',
balances_json: normalized.suggested_wallet.balances_json && typeof normalized.suggested_wallet.balances_json === 'object'
? normalized.suggested_wallet.balances_json
: {},
note: normalized.suggested_wallet.note || '',
source: normalized.suggested_wallet.source || 'image_ocr',
}
: {
measured_at: '',
total_value_amount: '',
total_value_currency: '',
wallet_balance: '',
wallet_currency: '',
balances_json: {},
note: '',
source: 'image_ocr',
},
confidence: typeof normalized.confidence === 'number' ? normalized.confidence : 0,
flags: Array.isArray(normalized.flags) ? normalized.flags : [],
image_path: normalized.image_path || '',
@@ -790,6 +823,7 @@
];
const currentCostPlans = Array.isArray(currentSettings.cost_plans) ? currentSettings.cost_plans : [];
const currentPayouts = Array.isArray(currentSettings.payouts) ? currentSettings.payouts : [];
const currentWalletSnapshots = Array.isArray(payload?.wallet_snapshots) ? payload.wallet_snapshots : [];
const currentMinerOffers = Array.isArray(currentSettings.miner_offers) ? currentSettings.miner_offers : [];
const currentPurchasedMiners = Array.isArray(currentSettings.purchased_miners) ? currentSettings.purchased_miners : [];
const renewableOfferIds = new Set(
@@ -925,13 +959,13 @@
setPurchaseMinerForm((current) => ({
...current,
offer_id: current.offer_id || String(selectedOffer.id),
currency: current.currency || selectedOffer.effective_price_currency || selectedOffer.base_price_currency || 'USD',
currency: current.currency || (!selectedOffer.auto_renew ? (currentSettings.crypto_currency || 'DOGE') : (selectedOffer.effective_price_currency || selectedOffer.base_price_currency || 'USD')),
total_cost_amount: current.total_cost_amount || (selectedOffer.effective_price_amount !== null && selectedOffer.effective_price_amount !== undefined ? String(selectedOffer.effective_price_amount) : ''),
reference_price_amount: current.reference_price_amount || (selectedOffer.reference_price_amount !== null && selectedOffer.reference_price_amount !== undefined ? String(selectedOffer.reference_price_amount) : ''),
reference_price_currency: current.reference_price_currency || selectedOffer.reference_price_currency || '',
auto_renew: current.auto_renew || !!selectedOffer.auto_renew,
}));
}, [purchaseMinerModalOpen, availableMinerOffers, purchaseMinerForm.offer_id]);
}, [purchaseMinerModalOpen, availableMinerOffers, purchaseMinerForm.offer_id, currentSettings.crypto_currency]);
function measurementFxRate(measurementId, fromCurrency, toCurrency) {
const from = String(fromCurrency || '').toUpperCase();
@@ -1167,6 +1201,7 @@
}, [payload, projectKey]);
const overviewCharts = useMemo(() => {
const chartCoinCurrency = String(currentSettings.crypto_currency || 'DOGE').toUpperCase();
const comparisonRows = measurements.filter((row) => {
const miningRate = Number(row.doge_per_hour_per_mh_interval);
const price = Number(row.effective_price_per_coin ?? row.price_per_coin);
@@ -1185,7 +1220,7 @@
miningVsPrice: baseMining && basePrice ? [
{
key: 'mining-rate',
label: 'Mining/h je MH/s Index',
label: `${chartCoinCurrency}/h je MH/s Index`,
color: '#2dd4bf',
data: comparisonRows.map((row) => ({
x: fmtDate(row.measured_at),
@@ -1194,7 +1229,7 @@
},
{
key: 'doge-price',
label: 'DOGE-Kurs Index',
label: `${chartCoinCurrency}-Kurs Index`,
color: '#f59e0b',
data: comparisonRows.map((row) => ({
x: fmtDate(row.measured_at),
@@ -1203,7 +1238,7 @@
},
] : [],
};
}, [measurements]);
}, [currentSettings.crypto_currency, measurements]);
async function submitMeasurement(fromPreview) {
const preview = normalizeOcrPreview(ocrPreview);
@@ -1242,6 +1277,35 @@
}
}
async function submitWalletSnapshotFromPreview() {
const preview = normalizeOcrPreview(ocrPreview);
const raw = {
...preview.suggested_wallet,
image_path: preview.image_path,
ocr_raw_text: preview.raw_text,
ocr_confidence: preview.confidence,
ocr_flags: preview.flags,
};
setSaving(true);
setError('');
setMessage('');
try {
await request(`${apiBase}/projects/${encodeURIComponent(projectKey)}/wallet-snapshots`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(raw),
});
setMessage('Wallet-Snapshot gespeichert.');
setOcrPreview(null);
await loadBootstrap(projectKey);
} catch (err) {
setError(err.message);
} finally {
setSaving(false);
}
}
async function deleteMeasurement(id) {
if (!id) {
return;
@@ -1320,6 +1384,7 @@
body.append('image', nextForm.image);
body.append('date_context', nextForm.date_context);
body.append('ocr_hint_text', nextForm.ocr_hint_text);
body.append('wallet_currency_hint', currentSettings.crypto_currency || 'DOGE');
const data = await request(`${apiBase}/projects/${encodeURIComponent(projectKey)}/ocr-preview`, {
method: 'POST',
body,
@@ -1534,7 +1599,7 @@
body: JSON.stringify(payoutForm),
});
setMessage('Auszahlung gespeichert.');
setPayoutForm({ payout_at: '', coins_amount: '', payout_currency: 'DOGE', note: '' });
setPayoutForm({ payout_at: '', coins_amount: '', payout_currency: currentSettings.crypto_currency || 'DOGE', note: '' });
setPayoutModalOpen(false);
await loadBootstrap(projectKey);
} catch (err) {
@@ -1792,6 +1857,75 @@
setReportCurrencyOverride('');
setCookie('mining_checker_report_currency', '', 0);
}
function renderSharedOcrPanel() {
const preview = normalizeOcrPreview(ocrPreview);
const isWalletPreview = preview.kind === 'wallet';
const hasUsableOcrSuggestion = isWalletPreview
? (preview.suggested_wallet.wallet_balance !== '' && preview.suggested_wallet.wallet_balance !== null)
: (preview.suggested.coins_total !== '' && preview.suggested.coins_total !== null);
const ocrStatus = getOcrStatusMessage(preview);
return panel('OCR Upload', 'Screenshot auswaehlen, Ergebnis direkt pruefen und speichern.', [
h('div', {
key: 'ocr-form',
className: 'mc-form',
}, [
fileField('Screenshot', (file) => loadOcrPreview(file, { image: file })),
]),
saving && ocrForm.image
? h('div', { key: 'ocr-loading', className: 'mc-empty' }, 'Analysiere Screenshot …')
: null,
ocrPreview
? h('div', { key: 'ocr-preview', className: 'mc-form' }, [
h('div', { key: 'badges', className: 'mc-inline-row' }, [
h(Badge, { key: 'kind', tone: isWalletPreview ? 'info' : 'success' }, isWalletPreview ? 'Wallet' : 'Mining'),
h(Badge, { key: 'confidence', tone: preview.confidence >= 0.75 ? 'success' : 'warn' }, `confidence ${fmtNumber(preview.confidence, 4)}`),
]),
isWalletPreview
? h('div', { key: 'wallet-form', className: 'mc-two-col' }, [
displayField('Erkannter Typ', 'Wallet-Snapshot'),
displayField('Mining-Waehrung im Wallet', `${fmtNumber(preview.suggested_wallet.wallet_balance, 8)} ${preview.suggested_wallet.wallet_currency || currentSettings.crypto_currency || 'DOGE'}`),
displayField('Gesamtwert', preview.suggested_wallet.total_value_amount !== '' && preview.suggested_wallet.total_value_amount !== null
? `${fmtNumber(preview.suggested_wallet.total_value_amount, 4)} ${preview.suggested_wallet.total_value_currency || ''}`.trim()
: 'n/a'),
displayField('Erkannte Wallet-Assets', Object.entries(preview.suggested_wallet.balances_json || {})
.slice(0, 8)
.map(([code, amount]) => `${fmtNumber(amount, 8)} ${code}`)
.join(' · ') || 'n/a'),
])
: h('div', { key: 'measurement-form', className: 'mc-two-col' }, [
displayField('Erkannter Typ', 'Mining-Messpunkt'),
displayField('Datum/Zeit', 'Wird beim Speichern auf den aktuellen Bestätigungszeitpunkt gesetzt.'),
displayField('Coins total', fmtNumber(preview.suggested.coins_total, 6)),
displayField('Kurs', fmtNumber(preview.suggested.price_per_coin, 6)),
displayField('Waehrung', preview.suggested.price_currency || 'n/a'),
]),
ocrStatus
? h('div', {
key: 'ocr-status',
className: cx(
'mc-alert',
ocrStatus.tone === 'error' ? 'mc-alert--error' : 'mc-alert--warning'
),
}, ocrStatus.text)
: null,
!hasUsableOcrSuggestion
? h('div', { key: 'ocr-warning', className: 'mc-alert mc-alert--error' },
'Kein verwertbarer OCR-Vorschlag erkannt. Bitte Bild erneut hochladen oder die Daten manuell erfassen.')
: null,
h('button', {
key: 'confirm',
type: 'button',
className: 'mc-button mc-button--primary',
onClick: () => isWalletPreview ? submitWalletSnapshotFromPreview() : submitMeasurement(true),
disabled: saving || !hasUsableOcrSuggestion,
}, saving ? 'Speichert …' : 'Ergebnis speichern'),
])
: h('div', { key: 'ocr-empty', className: 'mc-empty' },
'Noch kein Screenshot ausgewaehlt.'),
]);
}
return h('div', {
className: 'mc-grid-bg',
}, [
@@ -1804,6 +1938,9 @@
]);
function renderTab() {
const currentCoinCurrency = String((latest && latest.coin_currency) || currentSettings.crypto_currency || 'DOGE').toUpperCase();
const perDayLabel = `${currentCoinCurrency} pro Tag`;
if (activeTab === 'overview') {
const latestValue = latest ? convertMeasurementMoney(latest, latest.current_value, reportCurrency) : null;
const latestPriceSource = latest && latest.effective_price_per_coin !== null && latest.effective_price_per_coin !== undefined
@@ -1822,18 +1959,14 @@
const breakEvenDaysOverall = latest && latest.break_even_days_overall !== null && latest.break_even_days_overall !== undefined
? Number(latest.break_even_days_overall)
: null;
const investedCapital = latest ? convertMeasurementMoney(latest, latest.invested_capital, reportCurrency) : null;
const investedCapital = latest ? convertMeasurementMoney(latest, latest.cash_invested_capital ?? latest.invested_capital, reportCurrency) : null;
const reinvestedCapital = latest ? convertMeasurementMoney(latest, latest.reinvested_capital, reportCurrency) : null;
const walletValue = latest ? convertMeasurementMoney(latest, latest.wallet_value, reportCurrency) : null;
const totalHoldingsValue = latest ? convertMeasurementMoney(latest, latest.total_holdings_value, reportCurrency) : null;
const breakEvenReached = breakEvenRemainingAmount !== null && breakEvenRemainingAmount <= 0;
const breakEvenDate = (() => {
if (!latest || breakEvenDaysOverall === null || !Number.isFinite(breakEvenDaysOverall)) {
return null;
}
const baseTimestamp = Date.parse(String(latest.measured_at || ''));
if (!Number.isFinite(baseTimestamp)) {
return null;
}
return new Date(baseTimestamp + (breakEvenDaysOverall * 24 * 60 * 60 * 1000));
})();
const breakEvenEta = latest && latest.break_even_eta_at ? fmtDate(latest.break_even_eta_at) : null;
const walletBalanceCurrentAsset = payload?.summary?.payouts?.wallet_balance_current_asset;
const holdingsCurrentAsset = payload?.summary?.payouts?.holdings_current_asset;
return h('div', { className: 'mc-stack' }, [
panel('Berichtswährung', 'Bestimmt die Währung für Kennzahlen im Überblick. Standard kommt aus den Settings, diese Auswahl gilt nur für den aktuellen Besuch.', [
@@ -1855,19 +1988,21 @@
h('div', { key: 'stats', className: 'mc-stats-grid' }, [
h(StatCard, {
key: 'coins',
label: 'Coins sichtbar',
label: `${currentCoinCurrency} im Miner`,
value: latest ? fmtNumber(latest.coins_total_visible || latest.coins_total, 6) : 'n/a',
sub: latest ? `Stand ${fmtDate(latest.measured_at)}` : '',
}),
h(StatCard, {
key: 'coins-effective',
label: 'Coins effektiv',
value: payload?.summary?.payouts ? fmtNumber(payload.summary.payouts.current_effective_coins, 6) : 'n/a',
sub: payload?.summary?.payouts ? `Ausgezahlt ${fmtNumber(payload.summary.payouts.total_coins, 6)} DOGE` : '',
key: 'holdings',
label: `Theoretischer Bestand ${currentCoinCurrency}`,
value: payload?.summary?.payouts ? fmtNumber(holdingsCurrentAsset, 6) : 'n/a',
sub: payload?.summary?.payouts
? `Wallet ${fmtNumber(walletBalanceCurrentAsset, 6)} ${currentCoinCurrency} · Miner ${fmtNumber(latest?.coins_total_visible || latest?.coins_total, 6)} ${currentCoinCurrency}`
: '',
}),
h(StatCard, {
key: 'perday',
label: 'DOGE pro Tag',
label: perDayLabel,
value: latest ? fmtNumber(latest.doge_per_day_interval, 4) : 'n/a',
sub: payload?.summary?.current_hashrate_mh ? `Hashrate ${fmtNumber(payload.summary.current_hashrate_mh, 4)} MH/s` : (latest ? `Trend ${latest.trend_label}` : ''),
}),
@@ -1884,17 +2019,19 @@
label: 'Theoretischer Tagesgewinn',
value: dailyProfit !== null ? fmtMoney(dailyProfit, reportCurrency) : 'n/a',
sub: dailyCost !== null
? `Tageskosten ${fmtMoney(dailyCost, reportCurrency)} · Break-even ${breakEvenPrice !== null ? `${fmtNumber(breakEvenPrice, 6)} ${reportCurrency}` : 'n/a'}`
? `Tageskosten ${fmtMoney(dailyCost, reportCurrency)} · Walletwert ${walletValue !== null ? fmtMoney(walletValue, reportCurrency) : 'n/a'}`
: 'Kein aktiver Miner fuer diese Waehrung',
}),
h(StatCard, {
key: 'break-even-point',
label: 'Break-even',
value: breakEvenDaysOverall !== null
label: 'Cash-Break-even',
value: breakEvenReached
? 'Erreicht'
: breakEvenDaysOverall !== null
? `${fmtNumber(breakEvenDaysOverall, 2)} Tage`
: (breakEvenReached ? 'Erreicht' : (investedCapital === null ? 'Keine Mietbasis' : 'n/a')),
: (investedCapital === null ? 'Keine Mietbasis' : 'Nicht erreichbar'),
sub: investedCapital !== null
? `${breakEvenDate ? `Theoretisch ${fmtDate(breakEvenDate.toISOString())} · ` : ''}Basis ${fmtMoney(investedCapital, reportCurrency)}${dailyRevenue !== null ? ` · Tagesumsatz ${fmtMoney(dailyRevenue, reportCurrency)}` : ''}`
? `${breakEvenEta ? `ETA ${breakEvenEta} · ` : ''}Cash ${fmtMoney(investedCapital, reportCurrency)}${reinvestedCapital !== null ? ` · Reinvest ${fmtMoney(reinvestedCapital, reportCurrency)}` : ''}${totalHoldingsValue !== null ? ` · Bestand ${fmtMoney(totalHoldingsValue, reportCurrency)}` : ''}`
: (breakEvenPrice !== null
? `Break-even-Kurs ${fmtNumber(breakEvenPrice, 6)} ${reportCurrency}`
: (investedCapital === null
@@ -1904,14 +2041,14 @@
]),
h('div', { key: 'charts', className: 'mc-overview-grid' }, [
panel('Mining-Verlauf', 'Coins total der letzten 15 Tage.', h(SimpleChart, { type: 'line', data: overviewCharts.mining })),
panel('Performance-Verlauf', 'DOGE-pro-Tag-Raten der letzten 15 Tage.', h(SimpleChart, { type: 'area', data: overviewCharts.performance })),
panel('Performance-Verlauf', `${perDayLabel}-Raten der letzten 15 Tage.`, h(SimpleChart, { type: 'area', data: overviewCharts.performance })),
panel('Kurs-Verlauf', 'Preiswerte der letzten 15 Tage.', h(SimpleChart, { type: 'line', data: overviewCharts.pricing })),
panel('Mining vs. Kurs', 'Index-Vergleich der letzten 15 Tage. Mining wird auf DOGE pro Stunde je MH/s normalisiert, beide Reihen starten bei 100.', h(SimpleChart, {
panel('Mining vs. Kurs', `Index-Vergleich der letzten 15 Tage. Mining wird auf ${currentCoinCurrency} pro Stunde je MH/s normalisiert, beide Reihen starten bei 100.`, h(SimpleChart, {
type: 'line',
series: overviewCharts.miningVsPrice,
})),
]),
panel('Zielmonitor', 'Rest-DOGE und Resttage werden gegen den letzten verfuegbaren Kurs je Zielwaehrung berechnet.',
panel('Zielmonitor', `Rest-${currentCoinCurrency} und Resttage werden gegen den letzten verfuegbaren Kurs je Zielwaehrung berechnet.`,
h('div', { className: 'mc-target-grid' },
currentTargets.map((target, index) => h('div', {
key: index,
@@ -1924,8 +2061,8 @@
h('div', { key: 'body', className: 'mc-text mc-target-grid' }, [
h('div', { key: 'amount' }, `Ziel: ${fmtMoney(target.target_amount_fiat, target.currency)}`),
h('div', { key: 'price' }, `Letzter Kurs: ${target.latest_price_for_currency ? fmtNumber(target.latest_price_for_currency, 6) + ' ' + target.currency : 'n/a'}`),
h('div', { key: 'doge' }, `Benoetigte DOGE: ${fmtNumber(target.required_doge, 6)}`),
h('div', { key: 'remaining' }, `Rest-DOGE: ${fmtNumber(target.remaining_doge, 6)}`),
h('div', { key: 'doge' }, `Benoetigte ${currentCoinCurrency}: ${fmtNumber(target.required_doge, 6)}`),
h('div', { key: 'remaining' }, `Rest-${currentCoinCurrency}: ${fmtNumber(target.remaining_doge, 6)}`),
h('div', { key: 'days' }, `Resttage: ${fmtNumber(target.remaining_days, 4)}`),
]),
]))
@@ -1937,56 +2074,7 @@
if (activeTab === 'measurements') {
return h('div', { className: 'mc-main-grid' }, [
h('div', { className: 'mc-stack' }, [
(function () {
const preview = normalizeOcrPreview(ocrPreview);
const hasUsableOcrSuggestion = preview.suggested.coins_total !== '' && preview.suggested.coins_total !== null;
const ocrStatus = getOcrStatusMessage(preview);
return panel('OCR Upload', 'Screenshot auswaehlen, Ergebnis direkt pruefen und speichern.', [
h('div', {
key: 'ocr-form',
className: 'mc-form',
}, [
fileField('Screenshot', (file) => loadOcrPreview(file, { image: file })),
]),
saving && ocrForm.image
? h('div', { key: 'ocr-loading', className: 'mc-empty' }, 'Analysiere Screenshot …')
: null,
ocrPreview
? h('div', { key: 'ocr-preview', className: 'mc-form' }, [
h('div', { key: 'badges', className: 'mc-inline-row' }, [
h(Badge, { key: 'confidence', tone: preview.confidence >= 0.75 ? 'success' : 'warn' }, `confidence ${fmtNumber(preview.confidence, 4)}`),
]),
h('div', { key: 'form', className: 'mc-two-col' }, [
displayField('Datum/Zeit', 'Wird beim Speichern auf den aktuellen Bestätigungszeitpunkt gesetzt.'),
displayField('Coins total', fmtNumber(preview.suggested.coins_total, 6)),
displayField('Kurs', fmtNumber(preview.suggested.price_per_coin, 6)),
displayField('Waehrung', preview.suggested.price_currency || 'n/a'),
]),
ocrStatus
? h('div', {
key: 'ocr-status',
className: cx(
'mc-alert',
ocrStatus.tone === 'error' ? 'mc-alert--error' : 'mc-alert--warning'
),
}, ocrStatus.text)
: null,
!hasUsableOcrSuggestion
? h('div', { key: 'ocr-warning', className: 'mc-alert mc-alert--error' },
'Kein verwertbarer OCR-Vorschlag erkannt. Bitte Bild erneut hochladen oder den Messpunkt manuell erfassen.')
: null,
h('button', {
key: 'confirm',
type: 'button',
className: 'mc-button mc-button--primary',
onClick: () => submitMeasurement(true),
disabled: saving || !hasUsableOcrSuggestion,
}, saving ? 'Speichert …' : 'Ergebnis speichern'),
])
: h('div', { key: 'ocr-empty', className: 'mc-empty' },
'Noch kein Screenshot ausgewaehlt.'),
]);
})(),
renderSharedOcrPanel(),
panel('Messpunkt manuell erfassen', 'Direkte Eingabe eines einzelnen Messpunkts mit serverseitiger Validierung.', h('form', {
className: 'mc-form',
onSubmit: function (event) {
@@ -2068,12 +2156,12 @@
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', 'DOGE/Tag', 'Trend', 'Notiz', 'Aktion'
'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)),
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)),
@@ -2092,6 +2180,33 @@
]);
}
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, [
'Zeit', 'Mining-Waehrung', 'Bestand', 'Gesamtwert', 'Quelle', 'Assets'
].map((label) => h('th', { key: label }, label)))),
h('tbody', { key: 'tbody' },
currentWalletSnapshots.length
? currentWalletSnapshots.map((row) => h('tr', { key: row.id }, [
h('td', { key: 'measured' }, fmtDate(row.measured_at)),
h('td', { key: 'currency' }, row.wallet_currency || currentSettings.crypto_currency || 'DOGE'),
h('td', { key: 'balance' }, row.wallet_balance !== null && row.wallet_balance !== undefined ? `${fmtNumber(row.wallet_balance, 8)} ${row.wallet_currency || ''}`.trim() : 'n/a'),
h('td', { key: 'value' }, row.total_value_amount !== null && row.total_value_amount !== undefined ? `${fmtNumber(row.total_value_amount, 4)} ${row.total_value_currency || ''}`.trim() : 'n/a'),
h('td', { key: 'source' }, row.source || 'manual'),
h('td', { key: 'assets' }, Object.entries(row.balances_json || {}).slice(0, 6).map(([code, amount]) => `${fmtNumber(amount, 8)} ${code}`).join(' · ') || 'n/a'),
]))
: [h('tr', { key: 'empty' }, h('td', { colSpan: 6 }, 'Noch keine Wallet-Snapshots gespeichert.'))]
),
]),
])),
]),
]);
}
if (activeTab === 'dashboards') {
return h('div', { className: 'mc-main-grid' }, [
panel('Dashboard-Builder V1', 'Chart-Typ, X/Y-Feld, Aggregation und einfache Filter werden gespeichert.', h('form', {
@@ -2194,7 +2309,15 @@
key: 'add-payout',
type: 'button',
className: 'mc-button mc-button--secondary',
onClick: () => setPayoutModalOpen(true),
onClick: () => {
setPayoutForm((previous) => ({
payout_at: previous.payout_at || nowDateTimeLocalValue(),
coins_amount: previous.coins_amount || '',
payout_currency: currentCoinCurrency,
note: previous.note || '',
}));
setPayoutModalOpen(true);
},
}, 'Auszahlung erfassen'),
]),
h('div', { key: 'payout-list', className: 'mc-table-shell' }, [
@@ -2254,7 +2377,7 @@
])
: null,
]),
h('td', { key: 'day' }, offer.expected_doge_per_day !== null ? `${fmtNumber(offer.expected_doge_per_day, 6)} DOGE` : 'n/a'),
h('td', { key: 'day' }, offer.expected_doge_per_day !== null ? `${fmtNumber(offer.expected_doge_per_day, 6)} ${currentCoinCurrency}` : 'n/a'),
h('td', { key: 'break' }, offer.break_even_days !== null ? `${fmtNumber(offer.break_even_days, 2)} Tage` : 'n/a'),
h('td', { key: 'rec' }, [
h('div', { key: 'rec-main' }, offer.recommendation),
@@ -2326,11 +2449,11 @@
}),
h(StatCard, {
key: 'scenario-doge',
label: 'DOGE pro Tag Neu',
label: `${currentCoinCurrency} pro Tag Neu`,
value: selectedMinerScenario.scenario_doge_per_day !== null ? fmtNumber(selectedMinerScenario.scenario_doge_per_day, 4) : 'n/a',
sub: selectedMinerScenario.scenario_current_doge_per_day !== null
? `Aktuell ${fmtNumber(selectedMinerScenario.scenario_current_doge_per_day, 4)}`
: 'Keine aktuelle DOGE/Tag-Basis',
: `Keine aktuelle ${currentCoinCurrency}/Tag-Basis`,
}),
h(StatCard, {
key: 'scenario-break-even',
@@ -2445,7 +2568,7 @@
h('form', { key: 'form', className: 'mc-form', onSubmit: submitPayout }, [
inputField('Auszahlungszeitpunkt', 'datetime-local', payoutForm.payout_at, (value) => setPayoutForm({ ...payoutForm, payout_at: value })),
inputField('Coins', 'number', payoutForm.coins_amount, (value) => setPayoutForm({ ...payoutForm, coins_amount: value }), '0.000001'),
selectField('Waehrung', payoutForm.payout_currency, ['DOGE'].concat(selectableCurrencies.map((currency) => currency.code).filter((code) => code !== 'DOGE')), (value) => setPayoutForm({ ...payoutForm, payout_currency: value })),
selectField('Waehrung', payoutForm.payout_currency, [currentCoinCurrency].concat(selectableCurrencies.map((currency) => currency.code).filter((code) => code !== currentCoinCurrency)), (value) => setPayoutForm({ ...payoutForm, payout_currency: value })),
textareaField('Notiz', payoutForm.note, (value) => setPayoutForm({ ...payoutForm, note: value })),
h('div', { className: 'mc-inline-row' }, [
h('button', { type: 'button', className: 'mc-button mc-button--ghost', onClick: () => setPayoutModalOpen(false) }, 'Abbrechen'),