87 lines
4.5 KiB
JavaScript
87 lines
4.5 KiB
JavaScript
// 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 }); }
|
||
|
|
},
|
||
|
|
});
|
||
|
|
}
|