Files
AtreidesAssetsFree/client/pages/assets.js
T
2026-10-29 11:57:29 +01:00

157 lines
7.9 KiB
JavaScript

// Generated, do not edit. sha256:7de3dc53eab20f070a6ac8c7162fee1bd0695f724c6a13e314a994181782906e
import { listAssets, listBookableAssets, createAsset, updateAsset, deleteAsset } from '../data/assets.js';
import { hasMetalAssets } from '../data/metals.js';
import { tableViewCfg } from '../data/preferences.js';
import { t } from '../i18n/index.js';
import * as dialog from '../ui/dialog.js';
import { esc } from '../ui/escape.js';
import { ENUMS, CONFIG, balanceCaptureOn } from '../../shared/config.js';
import { REIKWIJDTE, categorieVoorAsset } from '../tax/constants.js';
import { renderTable } from '../ui/table.js';
import { takeRowFocus } from '../ui/row-focus.js';
import * as metalProducts from './metal-products.js';
import { capacityCfg } from '../ui/lock.js';
import { baseCurrency } from '../app-settings.js';
let tableApi = null;
export async function render(el) {
el.innerHTML = `
<div id="tbl"></div>
<div id="mp-section"></div>`;
await buildTable(el);
if (await hasMetalAssets()) {
const sec = el.querySelector('#mp-section');
sec.innerHTML = `<div id="mp-tbl" class="mt-lg"></div>`;
metalProducts.mount(sec.querySelector('#mp-tbl'));
}
}
function kindOpts() { return ENUMS.ASSET_KIND.map(v => ({ value: v, label: t('enums.asset_kind.' + v) })); }
function displayUnitOpts() { return ENUMS.ASSET_DISPLAY_UNIT.map(v => ({ value: v, label: t('enums.displayUnit.' + v) })); }
function taxCategorieOpts(vals = {}) {
const derived = categorieVoorAsset(vals);
return [{ value: '', label: derived
? t('assets.taxCategoryAutoIs', { waarde: t('enums.tax_category.' + derived) })
: t('assets.taxCategoryAuto') },
...ENUMS.TAX_CATEGORY.filter(v => v !== derived || v === vals.taxCategory)
.map(v => ({ value: v, label: t('enums.tax_category.' + v) }))];
}
function taxScopeOpts(vals = {}) {
return [{ value: '', label: t('assets.taxScopeAuto') },
...ENUMS.TAX_SCOPE.filter(v => v !== REIKWIJDTE.ALL || v === vals.taxScope)
.map(v => ({ value: v, label: t('enums.tax_scope.' + v) }))];
}
function balanceCaptureOpts() {
return [{ value: '', label: t('assets.taxCategoryAuto') },
{ value: '1', label: t('assets.balanceCaptureOn') }, { value: '0', label: t('assets.balanceCaptureOff') }];
}
const captureValue = v => (v === '' || v == null ? null : Number(v));
async function buildTable(el) {
const columns = [
{ key: 'name', label: t('assets.name'), sortable: true, text: true,
edit: { type: 'text', placeholder: 'Bitcoin' } },
{ key: 'symbol', label: t('assets.symbol'), sortable: true,
render: a => `<strong>${esc(a.symbol)}</strong>`,
edit: { type: 'text', placeholder: 'BTC' } },
{ key: 'kind', label: t('assets.kind'), sortable: true,
render: a => t('enums.asset_kind.' + a.kind),
group: { value: a => a.kind, label: v => t('enums.asset_kind.' + v) },
edit: { type: 'select', options: kindOpts, reactive: true } },
{ key: 'unit', label: t('assets.unit'), sortable: true,
render: a => `${esc(a.unit)}${a.subunit ? ` <span class="t-muted-cell">/ ${esc(a.subunit)}</span>` : ''}`,
edit: { type: 'text', placeholder: 'BTC / g / ozt' } },
{ key: 'subunit', label: t('assets.subunit'),
render: a => (a.subunit ? esc(a.subunit) : '-'),
edit: { type: 'text', placeholder: 'sat / mg' } },
{ key: 'displayUnit', label: t('assets.displayUnit'), sortable: true,
render: a => {
if (!a.subunit) return `<strong>${esc(a.unit)}</strong>`;
const subChosen = a.displayUnit === 'subunit';
const part = (txt, chosen) => chosen ? `<strong>${txt}</strong>` : `<span class="t-muted-cell">${txt}</span>`;
return `${part(esc(a.unit), !subChosen)} <span class="t-muted-cell">/</span> ${part(esc(a.subunit), subChosen)}`;
},
edit: { type: 'select', options: displayUnitOpts } },
{ key: 'decimals', label: t('assets.decimals'), align: 'num', sortable: true,
edit: { type: 'number', attrs: { min: 0, step: 1 } } },
{ key: 'taxCategory', label: t('assets.taxCategory'), sortable: true,
render: a => a.taxCategory
? t('enums.tax_category.' + a.taxCategory)
: `<span class="t-muted-cell">${categorieVoorAsset(a)
? t('assets.taxCategoryAutoIs', { waarde: t('enums.tax_category.' + categorieVoorAsset(a)) })
: t('assets.taxCategoryAuto')}</span>`,
group: { value: a => a.taxCategory || '', label: v => v ? t('enums.tax_category.' + v) : t('assets.taxCategoryAuto') },
edit: { type: 'select', options: taxCategorieOpts } },
{ key: 'taxScope', label: t('assets.taxScope'), sortable: true,
render: a => a.taxScope
? t('enums.tax_scope.' + a.taxScope)
: `<span class="t-muted-cell">${t('assets.taxScopeAuto')}</span>`,
group: { value: a => a.taxScope || '', label: v => v ? t('enums.tax_scope.' + v) : t('assets.taxScopeAuto') },
edit: { type: 'select', options: taxScopeOpts } },
{ key: 'balanceCapture', label: t('assets.balanceCapture'), sortable: true,
render: a => a.balanceCapture == null
? `<span class="t-muted-cell">${t(balanceCaptureOn(a) ? 'assets.balanceCaptureAutoOn' : 'assets.balanceCaptureAutoOff')}</span>`
: a.balanceCapture === 1
? `<span class="badge badge-accent">${t('assets.balanceCaptureOn')}</span>`
: `<span class="badge badge-neutral">${t('assets.balanceCaptureOff')}</span>`,
edit: { type: 'select', options: balanceCaptureOpts } },
];
function toPayload(v) {
const symbol = String(v.symbol || '').trim();
const name = String(v.name || '').trim();
const unit = String(v.unit || '').trim();
if (!symbol || !name || !unit) throw new Error(t('assets.fillSymbolNameUnit'));
if (v.kind === 'metal') {
const M = CONFIG.METALS;
if (!M.UNITS[unit]) {
throw new Error(t('assets.metalUnitUnknown', { eenheden: Object.keys(M.UNITS).join(', ') }));
}
return {
symbol, name, kind: 'metal',
unit, subunit: M.BASE_UNIT, decimals: M.BASE_DECIMALS,
displayUnit: v.displayUnit === 'subunit' ? 'subunit' : 'unit',
taxCategory: v.taxCategory || null,
taxScope: v.taxScope || null,
balanceCapture: captureValue(v.balanceCapture),
};
}
const decimals = parseInt(v.decimals, 10);
if (!Number.isInteger(decimals) || decimals < 0) throw new Error(t('assets.fillDecimals'));
const subunit = (v.subunit || '').trim() || null;
const displayUnit = (subunit && v.displayUnit === 'subunit') ? 'subunit' : 'unit';
return {
symbol, name, kind: v.kind,
unit, subunit, decimals, displayUnit,
taxCategory: v.taxCategory || null,
taxScope: v.taxScope || null,
balanceCapture: captureValue(v.balanceCapture),
};
}
const rijen = await listAssets();
const bookable = async () => (await listBookableAssets()).filter(r => r.symbol !== baseCurrency()).length;
let used = await bookable();
tableApi = renderTable(el.querySelector('#tbl'), {
columns, rows: rijen,
...capacityCfg('assets', () => used),
...(await tableViewCfg('assets', { key: 'symbol', dir: 'asc' })),
emptyText: t('assets.none'),
noneLabel: t('table.none'),
collapsibleGroups: true,
newLabel: t('table.new'),
newDefaults: { kind: 'crypto', decimals: '', displayUnit: 'unit', taxCategory: '', taxScope: '', balanceCapture: '' },
saveLabel: t('assets.save'), cancelLabel: t('assets.cancel'),
onCreate: async v => { const id = await createAsset(toPayload(v)); used = await bookable(); tableApi.refresh(await listAssets(), { locate: id }); },
onSave: async (id, v) => { await updateAsset(id, toPayload(v)); tableApi.refresh(await listAssets()); },
onDelete: async id => {
if (!(await dialog.confirm({ message: t('assets.confirmDelete'), danger: true }))) return;
try { await deleteAsset(id); used = await bookable(); tableApi.refresh(await listAssets()); } catch (e) { dialog.alert({ title: t('dialog.error'), message: e.message }); }
},
});
tableApi.focusRow(takeRowFocus('assets'));
}