Files

87 lines
4.5 KiB
JavaScript
Raw Permalink Normal View History

2026-10-29 11:57:29 +01:00
// Generated, do not edit. sha256:ae09cffb6bcb813ac103c0c39e8227c38347409ffb83175ba705ade666f63970
import { listMetalAssets, listMetalProducts, createMetalProduct, updateMetalProduct, deleteMetalProduct } from '../data/metals.js';
import { tableViewCfg } from '../data/preferences.js';
import { t, localeTag } from '../i18n/index.js';
import * as dialog from '../ui/dialog.js';
import { ENUMS, CONFIG } from '../../shared/config.js';
import { renderTable } from '../ui/table.js';
import { toBaseByFactor, fromBaseByFactor } from '../ui/format.js';
import { assetLabelHtml } from '../ui/asset-label.js';
import { esc } from '../ui/escape.js';
let tableApi = null;
export async function mount(host) {
await buildTable(host);
}
async function assetOpts() { return (await listMetalAssets()).map(a => ({ value: a.id, label: `${a.symbol}: ${a.name}` })); }
function formOpts() { return ENUMS.METAL_FORM.map(v => ({ value: v, label: t('enums.metal_form.' + v) })); }
const DEC = CONFIG.METALS.DISPLAY_DECIMALS;
const fmtG = grams => grams.toLocaleString(localeTag(), { maximumFractionDigits: DEC });
async function buildTable(el) {
const metalAssets = await listMetalAssets();
const columns = [
{ key: 'assetId', label: t('metalProducts.asset'), sortable: true, sortValue: r => r.asset_symbol,
render: r => assetLabelHtml({ asset_name: r.asset_name, asset_symbol: r.asset_symbol }),
group: { value: r => r.asset_symbol, label: v => v },
edit: { type: 'select', options: assetOpts } },
{ key: 'name', label: t('metalProducts.name'), sortable: true, text: true,
edit: { type: 'text', placeholder: 'Krugerrand 1 oz' } },
{ key: 'form', label: t('metalProducts.form'), sortable: true,
render: r => t('enums.metal_form.' + r.form),
group: { value: r => r.form, label: v => t('enums.metal_form.' + v) },
edit: { type: 'select', options: formOpts } },
{ key: 'fineGrams', label: t('metalProducts.fineWeight'), align: 'num', sortable: true, sortValue: r => r.fineGrams,
render: r => `${fmtG(fromBaseByFactor(r.fineGrams, 'g'))} g <span class="t-muted-cell">· ${fmtG(fromBaseByFactor(r.fineGrams, 'ozt'))} ozt</span>`,
edit: {
render: vals => {
const grams = Number.isFinite(Number(vals.fineGrams)) && vals.fineGrams !== '' && vals.fineGrams != null
? fromBaseByFactor(vals.fineGrams, 'g') : '';
const opt = u => `<option value="${u}">${u}</option>`;
return `<div data-field="fineGrams" style="display:flex;gap:.4rem;align-items:center">
<input class="input" data-mp-weight type="number" min="0" step="any" value="${esc(grams)}" style="min-width:90px">
<select class="input" data-mp-unit style="width:auto">${['g', 'ozt', 'kg'].map(opt).join('')}</select>
</div>`;
},
read: host => {
if (!host) return null;
const n = parseFloat(host.querySelector('[data-mp-weight]').value);
const u = host.querySelector('[data-mp-unit]').value;
if (!Number.isFinite(n) || n <= 0) return null;
return toBaseByFactor(n, u);
},
} },
];
function toPayload(v) {
const assetId = Number(v.assetId);
const name = String(v.name || '').trim();
const fineGrams = v.fineGrams;
if (!Number.isInteger(assetId) || !name) throw new Error(t('metalProducts.pickMetalAndName'));
if (!Number.isInteger(fineGrams) || fineGrams <= 0) throw new Error(t('metalProducts.fillFineGrams'));
return { assetId, name, form: v.form, fineGrams };
}
tableApi = renderTable(el, {
columns, rows: await listMetalProducts(),
title: t('metalProducts.title'),
...(await tableViewCfg('metal-products', { key: 'assetId', dir: 'asc' })),
emptyText: t('metalProducts.none'),
noneLabel: t('table.none'),
collapsibleGroups: true,
newLabel: t('table.new'),
newDefaults: { assetId: metalAssets[0] ? metalAssets[0].id : '', form: 'coin' },
saveLabel: t('metalProducts.save'), cancelLabel: t('metalProducts.cancel'),
onCreate: async v => { const id = await createMetalProduct(toPayload(v)); tableApi.refresh(await listMetalProducts(), { locate: id }); },
onSave: async (id, v) => { await updateMetalProduct(id, toPayload(v)); tableApi.refresh(await listMetalProducts()); },
onDelete: async id => {
if (!(await dialog.confirm({ message: t('metalProducts.confirmDelete'), danger: true }))) return;
try { await deleteMetalProduct(id); tableApi.refresh(await listMetalProducts()); }
catch (e) { dialog.alert({ title: t('dialog.error'), message: e.message }); }
},
});
}