// Generated, do not edit. sha256:691f92a0fa91d8a9f654cd1214fd7139438481faeed67130424bba664831b55c import { listSelectableAccounts } from '../data/accounts.js'; import { localDate, localDateNl, localTime, toUtcIso } from '../../shared/time.js'; import { listAssets, listBookableAssets } from '../data/assets.js'; import { listTransfers, createEvent, updateEvent, deleteEvent } from '../data/events.js'; import { listMetalProducts, listMetalProductsForAsset } from '../data/metals.js'; import { tableViewCfg } from '../data/preferences.js'; import { eventLinkMap, onchainAsset } from '../data/tx-links.js'; import { t } from '../i18n/index.js'; import { assetLabelHtml } from '../ui/asset-label.js'; import * as dialog from '../ui/dialog.js'; import { renderTable } from '../ui/table.js'; import { takeRowFocus } from '../ui/row-focus.js'; import { fmtUnit, val, subline, toSubunit, fromSubunit, isMetal } from '../ui/format.js'; import { matchDot, wireMatchTriggers } from '../ui/match-popover.js'; import { loadHistorical, fiatHistorical, fiatHistoricalOrigin } from '../ui/fiat.js'; import { originHtml } from '../ui/price-origin.js'; import { copyButton } from '../ui/copy-button.js'; import { TARGETS, targetHash } from '../../shared/config.js'; import { esc } from '../ui/escape.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)); const shortAddr = a => (a && a.length > 16) ? `${a.slice(0, 8)}…${a.slice(-6)}` : (a || ''); 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('transfers.none'), { why: t('transfers.needAsset'), href: targetHash(TARGETS.ASSETS), linkText: t('common.manageAssets'), })}`; return; } el.innerHTML = `
`; if (onlyAccount == null) setPageHint(el, { title: t('nav.transfers'), rows: [t('transfers.fiatNote')] }); buildTable(el); } async function assetOptsOfKind(kind) { return (await listBookableAssets()).filter(a => (!kind || a.kind === kind) && assetAllowed(a)) .map(a => ({ value: a.id, label: `${a.symbol}: ${a.name}` })); } async function accountOpts(huidigeId) { return [{ value: '', label: '-' }, ...(await listSelectableAccounts({ ookId: Number(huidigeId) || null })).map(a => ({ value: a.id, label: a.name }))]; } async function productOpts(assetId) { const id = Number(assetId); const opts = [{ value: '', label: t('transfers.looseWeight') }]; if (Number.isInteger(id)) for (const p of await listMetalProductsForAsset(id)) opts.push({ value: p.id, label: p.name }); return opts; } async function buildTable(el) { const rows = async () => (await listTransfers()) .filter(r => (onlyAccount == null || r.fromAccountId === onlyAccount || r.toAccountId === onlyAccount) && (onlyAsset == null || r.assetId === onlyAsset)); const meta = r => ({ id: r.assetId, kind: r.asset_kind, decimals: r.asset_decimals, unit: r.asset_unit, subunit: r.asset_subunit, displayUnit: r.asset_display_unit }); const herkomst = r => originHtml(fiatHistoricalOrigin(meta(r), localDate(r.occurredAt)), { ...meta(r), symbol: r.asset_symbol }); const btc = await onchainAsset(); let linkMap = await eventLinkMap(); const today = new Date().toISOString().slice(0, 10); const alleAssets = await listAssets(); const assetById = id => alleAssets.find(a => a.id === Number(id)); const firstOf = kind => { const a = alleAssets.find(x => x.kind === kind && assetAllowed(x)); return a ? a.id : ''; }; const convertAsset = vals => (vals.productId !== '' && vals.productId != null) ? null : (assetById(vals.assetId) || null); const dirOf = r => (r.toAccountId === onlyAccount && r.fromAccountId !== onlyAccount ? 'in' : 'out'); const otherOf = r => (dirOf(r) === 'in' ? { id: r.fromAccountId, name: r.from_name } : { id: r.toAccountId, name: r.to_name }); function productCell(r) { if (r.productId && r.product_name) return `${esc(r.product_name)} ×${r.productCount}`; if (r.asset_kind === 'metal') return `${t('transfers.looseWeight')}`; return ''; } const columns = [ { key: 'occurredAt', label: t('transfers.colDate'), sortable: true, render: r => `${btc && r.assetId === btc.id ? matchDot({ matched: linkMap.has(r.id), method: linkMap.get(r.id), kind: 'event', id: r.id }) : ''}${localDateNl(r.occurredAt)}`, group: { value: r => localDate(r.occurredAt).slice(0, 4), label: v => v, order: 'desc' }, edit: { type: 'date', sticky: true, attrs: { max: today }, value: r => r ? localDate(r.occurredAt) : today } }, { key: '_time', label: t('transfers.time'), render: r => localTime(r.occurredAt), edit: { type: 'time', value: r => r ? localTime(r.occurredAt) : '12:00' } }, { key: 'assetId', label: t('transfers.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: vals => assetOptsOfKind(vals.asset_kind) } }, { key: 'asset_kind', label: t('transfers.colKind'), sortable: true, render: r => t('enums.asset_kind.' + r.asset_kind), group: { value: r => r.asset_kind, label: v => t('enums.asset_kind.' + v) }, edit: { value: r => r ? r.asset_kind : '', visible: () => false } }, { key: 'productId', label: t('transfers.colProduct'), sortable: true, sortValue: r => r.product_name || '', render: productCell, edit: { type: 'select', sticky: true, reactive: true, visible: vals => vals.asset_kind === 'metal', options: vals => productOpts(vals.assetId), value: r => r ? (r.productId ?? '') : '' } }, { key: 'amount', label: t('transfers.colAmount'), align: 'num', sortable: true, sortValue: r => r.amount, render: r => { const fiat = fiatHistorical(r.amount, meta(r), localDate(r.occurredAt)); return `${val(fmtUnit(r.amount, meta(r)))}${subline(fiat ? val(fiat) + herkomst(r) : '')}`; }, edit: { type: 'number', attrs: { min: 0, step: 'any' }, value: r => r ? (r.productCount != null ? r.productCount : fromSubunit(r.amount, meta(r))) : '', convert: convertAsset } }, ...(onlyAccount == null ? [ { key: 'fromAccountId', label: t('transfers.colFrom'), sortable: true, sortValue: r => r.from_name || '', render: r => r.from_name || '-', group: { value: r => r.from_name || '-', label: v => v }, edit: { type: 'select', sticky: true, options: vals => accountOpts(vals.fromAccountId) } }, { key: 'toAccountId', label: t('transfers.colTo'), sortable: true, sortValue: r => r.to_name || '', render: r => r.to_name || '-', group: { value: r => r.to_name || '-', label: v => v }, edit: { type: 'select', sticky: true, options: vals => accountOpts(vals.toAccountId) } }, ] : [ { key: '_dir', label: t('transfers.colDirection'), sortable: true, sortValue: r => dirOf(r), render: r => t(dirOf(r) === 'in' ? 'transfers.dirIn' : 'transfers.dirOut'), group: { value: r => dirOf(r), label: v => t(v === 'in' ? 'transfers.dirIn' : 'transfers.dirOut') }, edit: { type: 'select', sticky: true, value: r => (r ? dirOf(r) : 'in'), options: () => [{ value: 'in', label: t('transfers.dirIn') }, { value: 'out', label: t('transfers.dirOut') }] } }, { key: '_other', label: t('transfers.colOther'), sortable: true, sortValue: r => otherOf(r).name || '', render: r => esc(otherOf(r).name || '-'), group: { value: r => otherOf(r).name || '-', label: v => v }, edit: { type: 'select', sticky: true, options: vals => accountOpts(vals._other), value: r => (r ? otherOf(r).id ?? '' : '') } }, ]), { key: 'unitFee', label: t('transfers.colFee'), align: 'num', sortable: true, sortValue: r => r.unitFee ?? -1, render: r => { if (r.unitFee == null) return `-${subline('')}`; const fiat = fiatHistorical(r.unitFee, meta(r), localDate(r.occurredAt)); return `${val(fmtUnit(r.unitFee, meta(r)))}${subline(fiat ? val(fiat) + herkomst(r) : '')}`; }, edit: { type: 'number', attrs: { min: 0, step: 'any' }, value: r => r && r.unitFee != null ? fromSubunit(r.unitFee, meta(r)) : '', convert: convertAsset } }, { key: 'priceOrigin', label: t('transfers.colOrigin'), render: r => (r.priceOrigin === 'none' ? `${t('transfers.originNone')}` : `-`), group: { value: r => (r.priceOrigin === 'none' ? t('transfers.originNone') : '-'), label: v => v }, edit: { type: 'select', value: r => (r && r.priceOrigin === 'none' ? 'none' : ''), options: () => ([ { value: '', label: t('transfers.originPick') }, { value: 'none', label: t('transfers.originNonePick') }, ]) } }, { key: 'note', label: t('transfers.colNote'), text: true, render: r => `${esc(r.note || '')}`, edit: { type: 'textarea', expand: true } }, { key: 'externalAddress', label: t('transfers.colAddress'), render: r => r.externalAddress ? `${esc(shortAddr(r.externalAddress))}${copyButton(r.externalAddress)}` : '', edit: { type: 'text', expand: true, placeholder: t('transfers.addressPlaceholder') } }, { key: 'source', label: t('transfers.colSource'), sortable: true, render: r => `${r.source ? t('enums.event_source.' + r.source) : '-'}`, group: { value: r => r.source || 'manual', label: v => t('enums.event_source.' + v) } }, ]; async function toPayload(v, existing) { const asset = alleAssets.find(a => a.id === Number(v.assetId)); if (!v.occurredAt || !asset) throw new Error(t('transfers.fillDateAsset')); let productId = null, productCount = null, amount; const wantProduct = isMetal(asset) && v.productId !== '' && v.productId != null; if (wantProduct) { const product = (await listMetalProducts()).find(p => p.id === Number(v.productId) && p.assetId === asset.id); if (!product) throw new Error(t('transfers.pickValidProduct')); const cnt = parseInt(v.amount, 10); if (!Number.isInteger(cnt) || cnt <= 0) throw new Error(t('transfers.fillCount')); productId = product.id; productCount = cnt; amount = product.fineGrams * cnt; } else { const qty = parseFloat(v.amount); if (!Number.isFinite(qty) || qty <= 0) throw new Error(t('transfers.fillDateAssetQuantity')); amount = toSubunit(qty, asset); } const idOf = x => (x !== '' && x != null ? Number(x) : null); let from = idOf(v.fromAccountId), to = idOf(v.toAccountId); if (onlyAccount != null) { const other = idOf(v._other); [from, to] = v._dir === 'in' ? [other, onlyAccount] : [onlyAccount, other]; } if (!from && !to) throw new Error(t('transfers.fillOneAccount')); if (onlyAccount != null && from !== onlyAccount && to !== onlyAccount) throw new Error(t('transfers.fillThisAccount')); return { assetId: asset.id, type: 'transfer', occurredAt: toUtcIso(`${v.occurredAt}T${v._time || '00:00'}:00`), amount, fiatPrice: null, fiatFee: existing ? existing.fiatFee ?? null : null, unitFee: v.unitFee === '' || v.unitFee == null ? null : toSubunit(parseFloat(v.unitFee), asset), productId, productCount, fromAccountId: from, toAccountId: to, source: existing ? existing.source : 'manual', note: (v.note || '').trim() || null, externalAddress: (v.externalAddress || '').trim() || null, priceOrigin: v.priceOrigin === 'none' ? 'none' : null, }; } const reload = async () => tableApi.refresh(await rows()); const historieRijen = await rows(); loadHistorical(historieRijen.map(r => ({ assetId: r.assetId, dateKey: localDate(r.occurredAt) }))) .then(async () => { if (tableApi) tableApi.refresh(await rows()); }).catch(() => {}); tableApi = renderTable(el.querySelector('#tbl'), { columns, rows: await rows(), ...(await tableViewCfg('transfers', { key: 'occurredAt', dir: 'desc' }, onlyAccount != null ? 'assetId' : null)), emptyText: t('transfers.none'), noneLabel: t('table.none'), collapsibleGroups: true, groupSummary: items => { const first = items[0]; if (!first) return ''; if (!items.every(r => r.assetId === first.assetId)) return ''; const tot = items.reduce((s, r) => s + (r.amount || 0), 0); return val(fmtUnit(tot, meta(first))); }, newLabel: t('table.new'), convertLabel: t('convert.title'), newDefaults: { fromAccountId: '', toAccountId: '', unitFee: '' }, newOptions: [ { key: 'crypto', label: t('transfers.addCrypto'), defaults: { asset_kind: 'crypto', assetId: firstOf('crypto') } }, { key: 'metal', label: t('transfers.addMetal'), defaults: { asset_kind: 'metal', assetId: firstOf('metal') } }, { key: 'fiat', label: t('transfers.addFiat'), defaults: { asset_kind: 'fiat', assetId: firstOf('fiat') } }, ].filter(o => o.defaults.assetId !== ''), saveLabel: t('transfers.save'), cancelLabel: t('transfers.cancel'), onCreate: async v => { const id = await createEvent(await toPayload(v, null)); tableApi.refresh(await rows(), { locate: id }); }, onSave: async (id, v) => { const ex = (await listTransfers()).find(x => x.id === id); await updateEvent(id, await toPayload(v, ex)); reload(); }, onDelete: async id => { if (!(await dialog.confirm({ message: t('transfers.confirmDelete'), danger: true }))) return; await deleteEvent(id); reload(); }, }); wireMatchTriggers(el.querySelector('#tbl'), (kind, id) => ({ kind, id, assetId: btc ? btc.id : null, onChange: async () => { linkMap = await eventLinkMap(); tableApi.refresh(await rows()); }, })); tableApi.focusRow(takeRowFocus('transfers')); }