// Generated, do not edit. sha256:064fe66c277773dbc29669a7a0cdf7abab018774c71a0f8e1d2a5cceea3a4d22 import { listSelectableAccounts } from '../data/accounts.js'; import { dayNl, localTime, toUtcIso } from '../../shared/time.js'; import { listAssets, listBookableAssets } from '../data/assets.js'; import { tableViewCfg } from '../data/preferences.js'; import { getPriceAt } from '../data/price-history.js'; import { listValuations, valuationDiffs, updateValuation, deleteValuation, createValuation, acceptValuationGap } from '../data/valuations.js'; import { t } from '../i18n/index.js'; import { assetLabelHtml } from '../ui/asset-label.js'; import * as dialog from '../ui/dialog.js'; import { esc } from '../ui/escape.js'; import { renderTable } from '../ui/table.js'; import { takeRowFocus } from '../ui/row-focus.js'; import { fmtBase, fmtBasePrice, fmtUnit, isBaseAsset, val, toSubunit, fromSubunit } from '../ui/format.js'; import { baseCurrency } from '../app-settings.js'; import { ensurePriceHistoryFor, historicalPointForVia, primarySourceKey } from '../price/providers.js'; import { bookDifference, acceptDifference } from '../actions/valuation-widget.js'; import { TARGETS, targetHash } from '../../shared/config.js'; import { emptyStateHtml } from '../ui/empty-state.js'; import { setPageHint } from '../ui/page-hint.js'; let tableApi = null; let onlyAccount = null; let onlyAsset = null; let onlyKinds = null; const assetAllowed = a => (onlyAsset == null || a.id === onlyAsset) && (onlyKinds == null || onlyKinds.includes(a.kind)); export async function render(el, { accountId = null, assetId = null, assetKinds = null } = {}) { onlyAccount = accountId; onlyAsset = assetId; onlyKinds = assetKinds; if ((await listAssets()).length === 0) { el.innerHTML = ` ${emptyStateHtml(t('valuations.none'), { why: t('valuations.needAsset'), href: targetHash(TARGETS.ASSETS), linkText: t('common.manageAssets'), })}`; return; } el.innerHTML = `
`; if (onlyAccount == null) setPageHint(el, { title: t('nav.valuations'), rows: [t('valuations.rateNote')] }); buildTable(el); } async function assetOpts() { return (await listBookableAssets()).filter(assetAllowed).map(a => ({ value: a.id, label: `${a.symbol}: ${a.name}` })); } async function accountOpts(huidigeId) { const accounts = (await listSelectableAccounts({ ookId: Number(huidigeId) || null })) .filter(a => onlyAccount == null || a.id === onlyAccount); return [...(onlyAccount == null ? [{ value: '', label: '-' }] : []), ...accounts.map(a => ({ value: a.id, label: a.name }))]; } const meta = r => ({ kind: r.asset_kind, decimals: r.asset_decimals, unit: r.asset_unit, subunit: r.asset_subunit, displayUnit: r.asset_display_unit }); const isRekenmunt = r => isBaseAsset({ symbol: r.asset_symbol }); async function valuationPayload(v, existing) { const asset = (await listAssets()).find(a => a.id === Number(v.assetId)); const qty = parseFloat(v.balance); if (!v.valuedOn || !asset || !Number.isFinite(qty) || qty < 0) { throw new Error(t('valuations.invalid')); } const balance = toSubunit(qty, asset); const rateAt = toUtcIso(`${v.valuedOn}T${v._time || '00:00'}:00`); let fiatRate = v.fiatRate === '' || v.fiatRate == null ? null : parseFloat(v.fiatRate); if (fiatRate == null) { try { await ensurePriceHistoryFor(asset); } catch { } const readSrc = (await primarySourceKey(asset, 'daily_history')) || (await primarySourceKey(asset, 'history_point')); const at = await getPriceAt(asset.id, Math.floor(Date.parse(rateAt) / 1000), readSrc); fiatRate = at ? at.fiatPrice : null; if (fiatRate == null) { const p = await historicalPointForVia(asset, v.valuedOn).catch(() => null); fiatRate = p ? p.price : null; } if (fiatRate == null) throw new Error(t('valuations.noRate')); } const accountId = v.accountId ?? onlyAccount; return { assetId: Number(v.assetId), valuedOn: v.valuedOn, accountId: accountId !== '' && accountId != null ? Number(accountId) : null, balance, fiatRate, rateAt, source: existing ? existing.source : 'manual', }; } export async function recordBalance(v) { return createValuation(await valuationPayload({ fiatRate: '', ...v }, null)); } async function buildTable(el) { const alleAssets = await listAssets(); const verschillen = new Map(); const rows = async () => { const rijen = (await listValuations()).filter(r => (onlyAccount == null || r.accountId === onlyAccount) && (onlyAsset == null || r.assetId === onlyAsset)); verschillen.clear(); for (const s of await valuationDiffs()) verschillen.set(s.id, s); return rijen; }; const today = new Date().toISOString().slice(0, 10); const columns = [ { key: 'valuedOn', label: t('valuations.colDate'), sortable: true, render: r => dayNl(r.valuedOn), group: { value: r => r.valuedOn.slice(0, 4), label: v => v, order: 'desc' }, edit: { type: 'date', sticky: true, value: r => r ? r.valuedOn : today } }, { key: '_time', label: t('valuations.colTime'), render: r => localTime(r.rateAt), edit: { type: 'time', value: r => r && r.rateAt ? localTime(r.rateAt) : '00:00' } }, { key: 'accountId', label: t('valuations.colAccount'), sortable: true, sortValue: r => r.account_name || '', render: r => r.account_name || '-', group: { value: r => r.account_name || '-', label: v => v }, edit: { type: 'select', sticky: true, options: vals => accountOpts(vals.accountId), value: r => r ? r.accountId ?? '' : '' } }, { key: 'assetId', label: t('valuations.colAsset'), sortable: true, sortValue: r => r.asset_symbol, render: r => assetLabelHtml(r), group: { value: r => r.asset_symbol, label: v => v }, edit: { type: 'select', sticky: true, reactive: true, options: assetOpts } }, { key: 'balance', label: t('valuations.colBalance'), align: 'num', sortable: true, sortValue: r => r.balance, render: r => { const fiat = (r.fiatRate && !isRekenmunt(r)) ? fmtBase(r.balance / Math.pow(10, r.asset_decimals) * r.fiatRate) : ''; return `${val(fmtUnit(r.balance, meta(r)))}${fiat ? `
${val(fiat)}
` : ''}`; }, edit: { type: 'number', attrs: { min: 0, step: 'any' }, value: r => r ? fromSubunit(r.balance, meta(r)) : '', convert: vals => alleAssets.find(a => a.id === Number(vals.assetId)) } }, { key: 'fiatRate', label: t('valuations.colRate', { munt: baseCurrency() }), align: 'num', sortable: true, sortValue: r => r.fiatRate, render: r => (r.fiatRate && !isRekenmunt(r)) ? val(fmtBasePrice(r.fiatRate)) : '-', edit: { type: 'number', attrs: { min: 0, step: 'any' }, placeholder: t('valuations.ratePlaceholder'), visible: vals => !isBaseAsset(alleAssets.find(a => a.id === Number(vals.assetId))), value: r => r && r.fiatRate ? r.fiatRate : '' } }, { key: 'source', label: t('valuations.colSource'), render: r => r.source ? `${t('enums.valuation_source.' + r.source)}` : '-' }, { key: '_diff', label: t('valuations.colDiff'), align: 'num', render: r => { const s = verschillen.get(r.id); const d = s ? s.gap : null; if (d == null) return '-'; if (s.future) return `⏳`; if (!s.reconcilable) return `⚓`; if (d === 0) return `✓`; if (s.accepted) { return ``; } return `${val(fmtUnit(d, meta(r)))} `; } }, ].filter(c => onlyAccount == null || c.key !== 'accountId'); const toPayload = valuationPayload; const rijen = await rows(); const reload = async () => tableApi.refresh(await rows()); tableApi = renderTable(el.querySelector('#tbl'), { columns, rows: rijen, ...(await tableViewCfg('valuations', { key: 'valuedOn', dir: 'desc' }, onlyAccount != null ? 'assetId' : null)), emptyText: t('valuations.none'), noneLabel: t('table.none'), collapsibleGroups: true, newLabel: t('table.new'), convertLabel: t('convert.title'), newDefaults: { accountId: onlyAccount ?? '', fiatRate: '', ...(onlyAsset != null ? { assetId: onlyAsset } : {}) }, saveLabel: t('trades.save'), cancelLabel: t('trades.cancel'), onCreate: async v => { const id = await createValuation(await toPayload(v, null)); tableApi.refresh(await rows(), { locate: id }); }, onSave: async (id, v) => { const ex = (await listValuations()).find(x => x.id === id); await updateValuation(id, await toPayload(v, ex)); reload(); }, onDelete: async id => { if (!(await dialog.confirm({ message: t('valuations.confirmDelete'), danger: true }))) return; await deleteValuation(id); reload(); }, }); el.querySelector('#tbl').addEventListener('click', async e => { const b = e.target.closest('[data-book]'); if (b) { e.preventDefault(); e.stopPropagation(); bookDifference((await rows()).find(x => x.id === Number(b.dataset.book)), reload); return; } const a = e.target.closest('[data-accept]'); if (a) { e.preventDefault(); e.stopPropagation(); acceptDifference(a.dataset.accept, reload); return; } const u = e.target.closest('[data-unaccept]'); if (u) { e.preventDefault(); e.stopPropagation(); await acceptValuationGap(Number(u.dataset.unaccept), false); reload(); } }, true); tableApi.focusRow(takeRowFocus('valuations')); }