Versie 0.2.0
This commit is contained in:
commit
e77d8fd373
495 files changed
+81026
No files matched your search
@@ -0,0 +1,375 @@
|
||||
// Generated, do not edit. sha256:d029e4f661a0e4d29fa7970f6f2aff7a15cdf1a9757dd86f22ff6a54e12c7c53
|
||||
import { listSelectableAccounts } from '../data/accounts.js';
|
||||
import { localDate, localDateNl, localTime, toUtcIso } from '../../shared/time.js';
|
||||
import { listAssets, listBookableAssets } from '../data/assets.js';
|
||||
import { listTrades, 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 { ENUMS, CONFIG, TARGETS, targetHash } from '../../shared/config.js';
|
||||
import { renderTable } from '../ui/table.js';
|
||||
import { takeRowFocus } from '../ui/row-focus.js';
|
||||
import { fmtBase, fmtBasePrice, fmtUnit, val, toSubunit, fromSubunit, priceToBase, priceFromBase, isMetal } from '../ui/format.js';
|
||||
import { totalFromPrice, priceFromTotal, amountFromTotal } from '../../shared/units.js';
|
||||
import { priceUnitLabel } from '../../shared/units.js';
|
||||
import { matchDot, wireMatchTriggers } from '../ui/match-popover.js';
|
||||
import { spotOnDate } from '../ui/fiat.js';
|
||||
import { ARROW_DOWN, ARROW_UP } from '../ui/icons.js';
|
||||
import { esc } from '../ui/escape.js';
|
||||
import { emptyStateHtml } from '../ui/empty-state.js';
|
||||
import { setPageHint } from '../ui/page-hint.js';
|
||||
import { bookingOriginHtml } from '../ui/price-origin.js';
|
||||
import { listPriceHistory } from '../data/price-history.js';
|
||||
import { herkomstContext } from '../wealth/data.js';
|
||||
import { provenanceOn, originFrom } from '../../shared/price-provenance.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('trades.none'), {
|
||||
why: t('trades.needAsset'), href: targetHash(TARGETS.ASSETS), linkText: t('common.manageAssets'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
el.innerHTML = `<div id="tbl"></div>`;
|
||||
if (onlyAccount == null) setPageHint(el, { title: t('nav.trades'), rows: [t('trades.fiatNote')] });
|
||||
buildTable(el);
|
||||
}
|
||||
|
||||
function typeOpts() {
|
||||
return ENUMS.EVENT_TYPE.filter(x => x === 'buy' || x === 'sell')
|
||||
.map(x => ({ value: x, label: t('enums.event_type.' + x) }));
|
||||
}
|
||||
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) {
|
||||
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 }))];
|
||||
}
|
||||
async function productOpts(assetId) {
|
||||
const id = Number(assetId);
|
||||
const opts = [{ value: '', label: t('trades.looseWeight') }];
|
||||
if (Number.isInteger(id)) for (const p of await listMetalProductsForAsset(id)) opts.push({ value: p.id, label: p.name });
|
||||
return opts;
|
||||
}
|
||||
|
||||
async function dagHerkomsten(trades) {
|
||||
const uit = new Map();
|
||||
const perAsset = new Map();
|
||||
for (const tr of trades) {
|
||||
if (tr.priceOrigin !== 'market_day' || tr.fiatPrice == null) continue;
|
||||
if (!perAsset.has(tr.assetId)) perAsset.set(tr.assetId, []);
|
||||
perAsset.get(tr.assetId).push(tr);
|
||||
}
|
||||
if (!perAsset.size) return uit;
|
||||
const ctx = await herkomstContext();
|
||||
for (const [assetId, lijst] of perAsset) {
|
||||
let rijen = [];
|
||||
try { rijen = await listPriceHistory(assetId); } catch { continue; }
|
||||
for (const tr of lijst) {
|
||||
const d = provenanceOn(rijen, tr.occurredAt.slice(0, 10),
|
||||
{ assetKind: tr.asset_kind, manualProviderId: ctx.manualId, usedPrice: tr.fiatPrice });
|
||||
if (d && d.fiatPrice != null && Math.abs(d.fiatPrice - tr.fiatPrice) <= 1e-9 * Math.max(1, Math.abs(tr.fiatPrice))) {
|
||||
uit.set(tr.id, originFrom(d, ctx.names));
|
||||
}
|
||||
}
|
||||
}
|
||||
return uit;
|
||||
}
|
||||
|
||||
async function buildTable(el) {
|
||||
let dagHerkomst = new Map();
|
||||
const rows = async () => {
|
||||
const lijst = (await listTrades())
|
||||
.filter(tr => (onlyAccount == null || (tr.type === 'buy' ? tr.toAccountId : tr.fromAccountId) === onlyAccount)
|
||||
&& (onlyAsset == null || tr.assetId === onlyAsset))
|
||||
.map(tr => ({ ...tr, loc: tr.type === 'buy' ? tr.to_name : tr.from_name }));
|
||||
try { dagHerkomst = await dagHerkomsten(lijst); } catch { dagHerkomst = new Map(); }
|
||||
return lijst;
|
||||
};
|
||||
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 btc = await onchainAsset();
|
||||
let linkMap = await eventLinkMap();
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const alleAssets = await listAssets();
|
||||
const alleProducten = await listMetalProducts();
|
||||
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 : ''; };
|
||||
|
||||
async function resolvePrice(v) {
|
||||
const asset = alleAssets.find(a => a.id === Number(v.assetId));
|
||||
if (!asset || !v.occurredAt) return;
|
||||
if (v.priceOrigin === 'none') { v.fiatPrice = 0; return; }
|
||||
const spot = await spotOnDate(asset, v.occurredAt);
|
||||
if (v.fiatPrice === '' || v.fiatPrice == null) {
|
||||
if (spot == null) throw new Error(t('trades.noSpotForDate'));
|
||||
v.fiatPrice = spot;
|
||||
v.priceOrigin = 'market_day';
|
||||
return;
|
||||
}
|
||||
const entered = parseFloat(v.fiatPrice);
|
||||
if (spot != null && spot > 0 && Number.isFinite(entered)) {
|
||||
const dev = Math.abs(entered - spot) / spot;
|
||||
if (dev > CONFIG.METALS.SPOT_DEVIATION_WARN) {
|
||||
const ok = await dialog.confirm({
|
||||
message: `De ingevoerde prijs (${fmtBasePrice(entered)}) wijkt ${Math.round(dev * 100)}% af van de spot op ${v.occurredAt} (${fmtBasePrice(spot)}). Toch opslaan?`,
|
||||
});
|
||||
if (!ok) throw new Error(t('trades.saveCancelled'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const convertAsset = vals => (vals.productId !== '' && vals.productId != null) ? null : (assetById(vals.assetId) || null);
|
||||
|
||||
function productCell(r) {
|
||||
if (r.productId && r.product_name) return `${esc(r.product_name)} <span class="t-muted-cell">×${r.productCount}</span>`;
|
||||
if (r.asset_kind === 'metal') return `<span class="t-muted-cell">${t('trades.looseWeight')}</span>`;
|
||||
return '';
|
||||
}
|
||||
|
||||
function herkomstCel(r) {
|
||||
const hints = {
|
||||
paid: 'trades.originPaidHint', market_day: 'trades.originMarketDay',
|
||||
market_moment: 'trades.originMarketMoment', none: 'trades.originNoneHint',
|
||||
};
|
||||
if (!r.priceOrigin) {
|
||||
return `<span class="t-muted-cell" title="${esc(t('trades.originUnknownHint'))}">?</span>`;
|
||||
}
|
||||
if (r.priceOrigin === 'paid') return '';
|
||||
return `<span class="t-muted-cell" title="${esc(t(hints[r.priceOrigin]))}">${esc(t('enums.event_price_origin.' + r.priceOrigin))}</span>`;
|
||||
}
|
||||
|
||||
function herkomstOpts(vals) {
|
||||
const opts = [
|
||||
{ value: '', label: t('trades.originPick') },
|
||||
{ value: 'paid', label: t('trades.originPickPaid') },
|
||||
{ value: 'none', label: t('trades.originPickGift') },
|
||||
];
|
||||
const huidig = vals && vals.priceOrigin;
|
||||
if (huidig && !opts.some(o => o.value === huidig)) {
|
||||
opts.push({ value: huidig, label: t('enums.event_price_origin.' + huidig) });
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{ key: 'occurredAt', label: t('trades.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('trades.time'),
|
||||
render: r => localTime(r.occurredAt),
|
||||
edit: { type: 'time', value: r => r ? localTime(r.occurredAt) : '12:00' } },
|
||||
{ key: 'loc', label: t('trades.colAccount'), sortable: true,
|
||||
render: r => r.loc || '-', group: { value: r => r.loc || '-', label: v => v },
|
||||
edit: { type: 'select', sticky: true, options: vals => accountOpts(vals.loc),
|
||||
value: r => r ? (r.type === 'sell' ? r.fromAccountId : r.toAccountId) ?? '' : '' } },
|
||||
{ key: 'assetId', label: t('trades.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('trades.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: 'type', label: t('trades.colType'), sortable: true,
|
||||
render: r => r.type === 'buy'
|
||||
? `<span class="badge badge-accent badge-icon" title="${t('enums.event_type.buy')}" aria-label="${t('enums.event_type.buy')}">${ARROW_DOWN}</span>`
|
||||
: `<span class="badge badge-neutral badge-icon" title="${t('enums.event_type.sell')}" aria-label="${t('enums.event_type.sell')}">${ARROW_UP}</span>`,
|
||||
group: { value: r => r.type, label: v => t('enums.event_type.' + v) },
|
||||
edit: { type: 'select', reactive: true, options: typeOpts } },
|
||||
{ key: 'productId', label: t('trades.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('trades.colAmount'), align: 'num', sortable: true, sortValue: r => r.amount,
|
||||
render: r => val(fmtUnit(r.amount, meta(r))),
|
||||
edit: { type: 'number', reactive: true, attrs: { min: 0, step: 'any' },
|
||||
value: r => r ? (r.productCount != null ? r.productCount : fromSubunit(r.amount, meta(r))) : '',
|
||||
convert: convertAsset } },
|
||||
{ key: 'priceOrigin', label: t('trades.colOrigin'), sortable: true,
|
||||
sortValue: r => r.priceOrigin || '',
|
||||
render: herkomstCel,
|
||||
group: { value: r => r.priceOrigin || '', label: v => (v ? t('enums.event_price_origin.' + v) : t('trades.originUnknown')) },
|
||||
edit: { type: 'select', reactive: true, options: herkomstOpts,
|
||||
value: r => r ? (r.priceOrigin ?? '') : '' } },
|
||||
{ key: 'fiatTotal', label: t('trades.colTotal'), align: 'num', sortable: true,
|
||||
sortValue: r => totalFromPrice(r.amount, meta(r).decimals, r.fiatPrice, r.fiatFee, r.type) ?? -1,
|
||||
render: r => {
|
||||
const tot = totalFromPrice(r.amount, meta(r).decimals, r.fiatPrice, r.fiatFee, r.type);
|
||||
return tot == null ? '-' : val(fmtBase(tot));
|
||||
},
|
||||
edit: { type: 'number', reactive: true, attrs: { min: 0, step: 'any' },
|
||||
visible: vals => vals.priceOrigin !== 'none',
|
||||
value: r => {
|
||||
const tot = r ? totalFromPrice(r.amount, meta(r).decimals, r.fiatPrice, r.fiatFee, r.type) : null;
|
||||
return tot == null ? '' : Math.round(tot * 100) / 100;
|
||||
} } },
|
||||
{ key: 'fiatPrice', label: t('trades.colPrice'), align: 'num', sortable: true, sortValue: r => r.fiatPrice,
|
||||
render: r => `${val(fmtBasePrice(priceFromBase(r.fiatPrice, meta(r))))} <span class="t-muted-cell">/${priceUnitLabel(meta(r))}</span>`
|
||||
+ bookingOriginHtml(r, { ...meta(r), symbol: r.asset_symbol }, dagHerkomst.get(r.id) || null),
|
||||
edit: { type: 'number', reactive: true, attrs: { min: 0, step: 'any' },
|
||||
visible: vals => vals.priceOrigin !== 'none',
|
||||
value: r => r ? priceFromBase(r.fiatPrice, meta(r)) : '' } },
|
||||
{ key: 'fiatFee', label: t('trades.colFee'), align: 'num', sortable: true, sortValue: r => r.fiatFee ?? -1,
|
||||
render: r => r.fiatFee != null ? val(fmtBase(r.fiatFee)) : '-',
|
||||
edit: { type: 'number', reactive: true, attrs: { min: 0, step: 'any' },
|
||||
visible: vals => vals.priceOrigin !== 'none',
|
||||
value: r => r && r.fiatFee != null ? r.fiatFee : '' } },
|
||||
{ key: 'note', label: t('trades.colNote'), text: true,
|
||||
render: r => `<span class="t-muted-cell">${esc(r.note || '')}</span>`,
|
||||
edit: { type: 'textarea', expand: true } },
|
||||
{ key: 'source', label: t('trades.colSource'), sortable: true,
|
||||
render: r => `<span class="t-muted-cell">${r.source ? t('enums.event_source.' + r.source) : '-'}</span>`,
|
||||
group: { value: r => r.source || 'manual', label: v => t('enums.event_source.' + v) } },
|
||||
].filter(c => onlyAccount == null || c.key !== 'loc');
|
||||
|
||||
function hoeveelheidBasis(v, asset) {
|
||||
if (!asset) return null;
|
||||
const wantProduct = isMetal(asset) && v.productId !== '' && v.productId != null;
|
||||
if (wantProduct) {
|
||||
const product = alleProducten.find(p => p.id === Number(v.productId) && p.assetId === asset.id);
|
||||
const cnt = parseInt(v.amount, 10);
|
||||
if (!product || !Number.isInteger(cnt) || cnt <= 0) return null;
|
||||
return product.fineGrams * cnt;
|
||||
}
|
||||
const qty = parseFloat(v.amount);
|
||||
if (!Number.isFinite(qty) || qty <= 0) return null;
|
||||
return toSubunit(qty, asset);
|
||||
}
|
||||
|
||||
function afgeleid(v, sleutel, aangeraakt = new Set()) {
|
||||
if (v.priceOrigin === 'none') return { fiatPrice: '0', fiatTotal: '0', fiatFee: '' };
|
||||
const asset = assetById(v.assetId);
|
||||
if (!asset) return null;
|
||||
const dec = asset.decimals;
|
||||
const rond = n => (n == null ? '' : String(Math.round(n * 100) / 100));
|
||||
const gevuld = x => x !== '' && x != null && Number.isFinite(Number(x));
|
||||
|
||||
const opProduct = isMetal(asset) && v.productId !== '' && v.productId != null;
|
||||
if (!opProduct && !aangeraakt.has('amount') && gevuld(v.fiatTotal) && gevuld(v.fiatPrice)) {
|
||||
const basisUitGeld = amountFromTotal(
|
||||
v.fiatTotal, priceToBase(parseFloat(v.fiatPrice), asset), dec, v.fiatFee, v.type);
|
||||
if (basisUitGeld !== null) {
|
||||
const getoond = fromSubunit(Math.round(basisUitGeld), asset);
|
||||
return { amount: getoond === null ? '' : String(getoond) };
|
||||
}
|
||||
}
|
||||
|
||||
const basis = hoeveelheidBasis(v, asset);
|
||||
if (basis === null) return null;
|
||||
|
||||
const naarPrijs = sleutel === 'fiatTotal'
|
||||
|| (gevuld(v.fiatTotal) && (sleutel === 'fiatFee' || !gevuld(v.fiatPrice)));
|
||||
if (naarPrijs) {
|
||||
const prijs = priceFromTotal(v.fiatTotal, basis, dec, v.fiatFee, v.type);
|
||||
return prijs === null ? null : { fiatPrice: rond(priceFromBase(prijs, asset)) };
|
||||
}
|
||||
const totaal = totalFromPrice(basis, dec, priceToBase(parseFloat(v.fiatPrice), asset), v.fiatFee, v.type);
|
||||
return totaal === null ? null : { fiatTotal: rond(totaal) };
|
||||
}
|
||||
|
||||
function herkomst(v, existing, priceBasis) {
|
||||
const gekozen = v.priceOrigin ?? '';
|
||||
const opgeslagen = existing ? (existing.priceOrigin ?? '') : '';
|
||||
if (gekozen !== opgeslagen) return gekozen === '' ? null : gekozen;
|
||||
if (existing && existing.fiatPrice != null) {
|
||||
const zelfde = Math.abs(priceBasis - existing.fiatPrice) <= 1e-9 * Math.max(1, Math.abs(existing.fiatPrice));
|
||||
if (zelfde) return existing.priceOrigin ?? null;
|
||||
}
|
||||
return gekozen === '' ? 'paid' : gekozen;
|
||||
}
|
||||
|
||||
async function toPayload(v, existing) {
|
||||
const asset = alleAssets.find(a => a.id === Number(v.assetId));
|
||||
const price = parseFloat(v.fiatPrice);
|
||||
if (!v.occurredAt || !asset || !Number.isFinite(price)) throw new Error(t('trades.fillDateAssetPrice'));
|
||||
|
||||
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('trades.pickValidProduct'));
|
||||
const cnt = parseInt(v.amount, 10);
|
||||
if (!Number.isInteger(cnt) || cnt <= 0) throw new Error(t('trades.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('trades.fillQuantity'));
|
||||
amount = toSubunit(qty, asset);
|
||||
}
|
||||
const fiatPrice = priceToBase(price, asset);
|
||||
const locVal = v.loc ?? onlyAccount;
|
||||
const loc = locVal !== '' && locVal != null ? Number(locVal) : null;
|
||||
return {
|
||||
priceOrigin: herkomst(v, existing, fiatPrice),
|
||||
assetId: asset.id, type: v.type,
|
||||
occurredAt: toUtcIso(`${v.occurredAt}T${v._time || '00:00'}:00`),
|
||||
amount, fiatPrice,
|
||||
fiatFee: v.fiatFee === '' || v.fiatFee == null ? null : parseFloat(v.fiatFee),
|
||||
unitFee: existing ? existing.unitFee ?? null : null,
|
||||
productId, productCount,
|
||||
fromAccountId: v.type === 'sell' ? loc : null,
|
||||
toAccountId: v.type === 'buy' ? loc : null,
|
||||
source: existing ? existing.source : 'manual',
|
||||
note: (v.note || '').trim() || null,
|
||||
};
|
||||
}
|
||||
const reload = async () => tableApi.refresh(await rows());
|
||||
|
||||
tableApi = renderTable(el.querySelector('#tbl'), {
|
||||
columns, rows: await rows(),
|
||||
onEditChange: (vals, sleutel, aangeraakt) => afgeleid(vals, sleutel, aangeraakt),
|
||||
...(await tableViewCfg('trades', { key: 'occurredAt', dir: 'desc' }, onlyAccount != null ? 'assetId' : null)),
|
||||
emptyText: t('trades.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 net = items.reduce((s, r) => s + (r.type === 'buy' ? r.amount : -r.amount), 0);
|
||||
return `${net < 0 ? '\u2212' : ''}${val(fmtUnit(Math.abs(net), meta(first)))}`;
|
||||
},
|
||||
newLabel: t('table.new'),
|
||||
convertLabel: t('convert.title'),
|
||||
newDefaults: { type: 'buy', loc: onlyAccount ?? '', fiatFee: '' },
|
||||
newOptions: [
|
||||
{ key: 'crypto', label: t('trades.addCrypto'), defaults: { asset_kind: 'crypto', type: 'buy', assetId: firstOf('crypto') } },
|
||||
{ key: 'metal', label: t('trades.addMetal'), defaults: { asset_kind: 'metal', type: 'buy', assetId: firstOf('metal') } },
|
||||
{ key: 'fiat', label: t('trades.addFiat'), defaults: { asset_kind: 'fiat', type: 'buy', assetId: firstOf('fiat') } },
|
||||
].filter(o => o.defaults.assetId !== ''),
|
||||
saveLabel: t('trades.save'), cancelLabel: t('trades.cancel'),
|
||||
onCreate: async v => { await resolvePrice(v); const id = await createEvent(await toPayload(v, null)); tableApi.refresh(await rows(), { locate: id }); },
|
||||
onSave: async (id, v) => {
|
||||
await resolvePrice(v);
|
||||
const ex = (await listTrades()).find(x => x.id === id);
|
||||
await updateEvent(id, await toPayload(v, ex)); reload();
|
||||
},
|
||||
onDelete: async id => {
|
||||
if (!(await dialog.confirm({ message: t('trades.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('trades'));
|
||||
}
|
||||
Reference in new issue
Block a user