diff --git a/modules/mining-checker/assets/js/app.js b/modules/mining-checker/assets/js/app.js index 5ddc3c9f..aa32f877 100644 --- a/modules/mining-checker/assets/js/app.js +++ b/modules/mining-checker/assets/js/app.js @@ -923,6 +923,7 @@ note: '', }); const [payoutModalOpen, setPayoutModalOpen] = useState(false); + const [walletTransferRows, setWalletTransferRows] = useState([]); const [walletWithdrawalForm, setWalletWithdrawalForm] = useState({ withdrawal_at: '', coins_amount: '', @@ -930,6 +931,7 @@ note: '', }); const [walletWithdrawalModalOpen, setWalletWithdrawalModalOpen] = useState(false); + const [walletWithdrawalRows, setWalletWithdrawalRows] = useState([]); const [minerOfferForm, setMinerOfferForm] = useState({ label: '', runtime_months: '', @@ -989,8 +991,8 @@ { code: 'USDC', name: 'USD Coin' }, ]; const currentCostPlans = Array.isArray(currentSettings.cost_plans) ? currentSettings.cost_plans : []; - const currentPayouts = Array.isArray(currentSettings.payouts) ? currentSettings.payouts : []; - const currentWalletWithdrawals = Array.isArray(currentSettings.wallet_withdrawals) ? currentSettings.wallet_withdrawals : []; + const currentPayouts = walletTransferRows.length ? walletTransferRows : (Array.isArray(currentSettings.payouts) ? currentSettings.payouts : []); + const currentWalletWithdrawals = walletWithdrawalRows.length ? walletWithdrawalRows : (Array.isArray(currentSettings.wallet_withdrawals) ? currentSettings.wallet_withdrawals : []); 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 : []; @@ -1013,7 +1015,7 @@ const selectableCurrencies = preferredSelectableCurrencies.length ? preferredSelectableCurrencies : currencies; const evaluatedMinerOffers = Array.isArray(payload?.summary?.miner_offers) ? payload.summary.miner_offers : []; const availableMinerOffers = evaluatedMinerOffers.filter((offer) => isOfferAvailableForWallet(offer, currentMiningCurrency, currentWalletMiningBalance)); - const filteredMinerOffers = availableMinerOffers.filter((offer) => { + const filteredMinerOffers = evaluatedMinerOffers.filter((offer) => { const speedMin = minerOfferFilters.speed_min === '' ? null : Number(minerOfferFilters.speed_min); const speedUnit = String(minerOfferFilters.speed_unit || 'auto'); const priceMax = minerOfferFilters.price_max === '' ? null : Number(minerOfferFilters.price_max); @@ -1445,6 +1447,14 @@ loadFxHistory(projectKey); }, [activeTab, projectKey]); + useEffect(() => { + if (activeTab !== 'wallet') { + return; + } + loadWalletTransfers(projectKey); + loadWalletWithdrawals(projectKey); + }, [activeTab, projectKey]); + useEffect(() => { syncMiningCheckerTabButtons(activeTab); @@ -1918,6 +1928,7 @@ timeoutMs: 8000, }); applySavedPayout(savedPayout); + setWalletTransferRows((previous) => previous.filter((row) => String(row.id) !== String(savedPayout.id)).concat(savedPayout).sort((left, right) => String(left?.payout_at || '').localeCompare(String(right?.payout_at || '')))); invalidateProjectBootstrapCache(projectKey); setMessage('Wallet-Transfer gespeichert.'); setPayoutForm({ payout_at: '', coins_amount: '', payout_currency: currentSettings.crypto_currency || 'DOGE', note: '' }); @@ -1946,6 +1957,7 @@ await request(`${apiBase}/projects/${encodeURIComponent(projectKey)}/payouts/${encodeURIComponent(transfer.id)}`, { method: 'DELETE', }); + setWalletTransferRows((previous) => previous.filter((row) => String(row.id) !== String(transfer.id))); invalidateProjectBootstrapCache(projectKey); await reloadBootstrapAfterMutation('Wallet-Transfer geloescht.'); } catch (err) { @@ -1955,6 +1967,15 @@ } } + async function loadWalletTransfers(key) { + try { + const result = await request(`${apiBase}/projects/${encodeURIComponent(key)}/payouts`, { timeoutMs: 6000 }); + setWalletTransferRows(Array.isArray(result) ? result : []); + } catch (_err) { + setWalletTransferRows([]); + } + } + async function submitWalletWithdrawal(event) { event.preventDefault(); setSaving(true); @@ -1968,6 +1989,7 @@ timeoutMs: 8000, }); invalidateProjectBootstrapCache(projectKey); + await loadWalletWithdrawals(projectKey); setMessage('Wallet-Auszahlung gespeichert.'); setWalletWithdrawalForm({ withdrawal_at: '', @@ -1984,6 +2006,15 @@ } } + async function loadWalletWithdrawals(key) { + try { + const result = await request(`${apiBase}/projects/${encodeURIComponent(key)}/wallet-withdrawals`, { timeoutMs: 6000 }); + setWalletWithdrawalRows(Array.isArray(result) ? result : []); + } catch (_err) { + setWalletWithdrawalRows([]); + } + } + async function deleteWalletWithdrawal(withdrawal) { if (!withdrawal || !withdrawal.id) { return; @@ -2001,6 +2032,7 @@ await request(`${apiBase}/projects/${encodeURIComponent(projectKey)}/wallet-withdrawals/${encodeURIComponent(withdrawal.id)}`, { method: 'DELETE', }); + setWalletWithdrawalRows((previous) => previous.filter((row) => String(row.id) !== String(withdrawal.id))); invalidateProjectBootstrapCache(projectKey); await reloadBootstrapAfterMutation('Wallet-Auszahlung geloescht.'); } catch (err) { @@ -2936,7 +2968,7 @@ ]), ]), panel('Miner-Angebote', currentWalletMiningBalance !== null - ? `Hier werden nur Basis-Miner gepflegt. Krypto-Angebote werden gegen deinen aktuellen Wallet-Bestand (${fmtNumber(currentWalletMiningBalance, 8)} ${currentMiningCurrency}) gefiltert.` + ? `Hier werden nur Basis-Miner gepflegt. Die Verfuegbarkeit von Krypto-Angeboten wird gegen deinen aktuellen NC-Wallet-Bestand (${fmtNumber(currentWalletMiningBalance, 8)} ${currentMiningCurrency}) bewertet.` : 'Hier werden nur Basis-Miner gepflegt. Alle vorgegebenen Geschwindigkeiten und Preise werden daraus automatisch abgeleitet.', [ h('div', { key: 'actions', className: 'mc-inline-row' }, [ @@ -2954,7 +2986,7 @@ { value: 'kh', label: 'kH/s' }, ], (value) => setMinerOfferFilters({ ...minerOfferFilters, speed_unit: value || 'auto' })), inputField(`Max. Basispreis (${reportCurrency})`, 'number', minerOfferFilters.price_max, (value) => setMinerOfferFilters({ ...minerOfferFilters, price_max: value }), '0.0001'), - selectField('Laufzeit', minerOfferFilters.runtime_months, [{ value: '', label: 'Alle Laufzeiten' }].concat(Array.from(new Set(availableMinerOffers.map((offer) => String(offer.runtime_months || '')).filter(Boolean))).sort((a, b) => Number(a) - Number(b)).map((value) => ({ + selectField('Laufzeit', minerOfferFilters.runtime_months, [{ value: '', label: 'Alle Laufzeiten' }].concat(Array.from(new Set(evaluatedMinerOffers.map((offer) => String(offer.runtime_months || '')).filter(Boolean))).sort((a, b) => Number(a) - Number(b)).map((value) => ({ value, label: `${value} Monate`, }))), (value) => setMinerOfferFilters({ ...minerOfferFilters, runtime_months: value })), @@ -2987,6 +3019,7 @@ ? h('div', { key: 'rec-ref', className: 'mc-kicker' }, `Basis ${fmtNumber(offer.base_price_amount, 6)} ${offer.base_price_currency}`) : null, offer.payment_type ? h('div', { key: 'paytype', className: 'mc-kicker' }, offer.payment_type === 'crypto' ? `Zahlung in Krypto (${currentSettings.crypto_currency || 'DOGE'})` : `Zahlung in FIAT (${offer.base_price_currency || 'EUR'})`) : null, + h('div', { key: 'availability', className: 'mc-kicker' }, isOfferAvailableForWallet(offer, currentMiningCurrency, currentWalletMiningBalance) ? 'Im NC Wallet verfuegbar' : 'Nicht genug NC Wallet Bestand'), h('div', { key: 'renew', className: 'mc-kicker' }, offer.auto_renew ? 'Automatische Verlängerung' : 'Laeuft aus'), ]), h('td', { key: 'action' }, [ @@ -3034,6 +3067,7 @@ }); setPurchaseMinerModalOpen(true); }, + disabled: !isOfferAvailableForWallet(offer, currentMiningCurrency, currentWalletMiningBalance), }, 'Mieten'), ]), ])) diff --git a/modules/mining-checker/src/Infrastructure/MiningRepository.php b/modules/mining-checker/src/Infrastructure/MiningRepository.php index e081e166..ef3ff16a 100644 --- a/modules/mining-checker/src/Infrastructure/MiningRepository.php +++ b/modules/mining-checker/src/Infrastructure/MiningRepository.php @@ -395,7 +395,18 @@ final class MiningRepository 'SELECT * FROM ' . $this->table('payouts') . ' WHERE project_key = :project_key AND owner_sub = :owner_sub ORDER BY payout_at ASC, id ASC' ); $stmt->execute(['project_key' => $projectKey, 'owner_sub' => $this->ownerSub]); - return $this->normalizeRows($stmt->fetchAll() ?: []); + $rows = $this->normalizeRows($stmt->fetchAll() ?: []); + if ($rows !== []) { + return $rows; + } + + // Legacy fallback: older payout rows may exist without a user scope assignment. + $legacyStmt = $this->pdo->prepare( + 'SELECT * FROM ' . $this->table('payouts') . ' WHERE project_key = :project_key ORDER BY payout_at ASC, id ASC' + ); + $legacyStmt->execute(['project_key' => $projectKey]); + + return $this->normalizeRows($legacyStmt->fetchAll() ?: []); } public function savePayout(string $projectKey, array $payload): array