Files
hbm-foundry-system/src/sheets/money-converter-dialog.ts
T

161 lines
7.6 KiB
TypeScript

import { fetchNbpRate, FALLBACK_RATES } from '../logic/nbp';
const { DialogV2 } = foundry.applications.api as unknown as {
DialogV2: any;
};
class MoneyConverterDialog extends DialogV2 {
rates: Record<string, number>;
constructor(options: any, rates: Record<string, number>) {
super(options);
this.rates = rates;
}
protected override _onRender(context: any, options: any) {
super._onRender(context, options);
const html = this.element;
const leftInput = html.querySelector('#hbm-conv-left') as HTMLInputElement;
const rightInput = html.querySelector('#hbm-conv-right') as HTMLInputElement;
const leftSelect = html.querySelector('#hbm-sf') as HTMLSelectElement;
const rightSelect = html.querySelector('#hbm-st') as HTMLSelectElement;
if (!leftInput || !rightInput || !leftSelect || !rightSelect) return;
const calcRight = () => {
const v = parseFloat(leftInput.value);
if (Number.isNaN(v)) {
rightInput.value = '';
return;
}
const lk = leftSelect.value;
const rk = rightSelect.value;
const res = v * this.rates[lk] / this.rates[rk];
rightInput.value = isFinite(res) ? res.toFixed(4) : '';
};
const calcLeft = () => {
const v = parseFloat(rightInput.value);
if (Number.isNaN(v)) {
leftInput.value = '';
return;
}
const lk = leftSelect.value;
const rk = rightSelect.value;
const res = v * this.rates[rk] / this.rates[lk];
leftInput.value = isFinite(res) ? res.toFixed(4) : '';
};
leftInput.addEventListener('input', calcRight);
leftSelect.addEventListener('change', calcRight);
rightInput.addEventListener('input', calcLeft);
rightSelect.addEventListener('change', calcRight);
}
}
export async function showMoneyConverter(actor: any): Promise<void> {
const pln = actor.system.details?.money ?? 0;
const currentYear = actor.system.details?.currentYear ?? 2026;
const [eurData, usdData] = await Promise.all([fetchNbpRate('eur', currentYear), fetchNbpRate('usd', currentYear)]);
const eurRate = eurData?.rate ?? FALLBACK_RATES.eur!;
const usdRate = usdData?.rate ?? FALLBACK_RATES.usd!;
const rateDate = eurData?.date ?? usdData?.date ?? 'default';
const isLive = !!(eurData || usdData);
const CURRENCIES: Record<string, { labelKey: string; toPln: number; noteKey: string; noteParam?: Record<string, any> }> = {
PLN: { labelKey: 'HBM.moneyConverter.currencies.PLN', toPln: 1, noteKey: '' },
EUR: { labelKey: 'HBM.moneyConverter.currencies.EUR', toPln: eurRate, noteKey: 'HBM.moneyConverter.notes.nbp', noteParam: { date: rateDate } },
USD: { labelKey: 'HBM.moneyConverter.currencies.USD', toPln: usdRate, noteKey: 'HBM.moneyConverter.notes.nbp', noteParam: { date: rateDate } },
TH: { labelKey: 'HBM.moneyConverter.currencies.TH', toPln: eurRate, noteKey: 'HBM.moneyConverter.notes.th' },
FC: { labelKey: 'HBM.moneyConverter.currencies.FC', toPln: 2.0, noteKey: 'HBM.moneyConverter.notes.fc' },
ST: { labelKey: 'HBM.moneyConverter.currencies.ST', toPln: 240, noteKey: 'HBM.moneyConverter.notes.st' },
ZK: { labelKey: 'HBM.moneyConverter.currencies.ZK', toPln: 2880, noteKey: 'HBM.moneyConverter.notes.zk' },
PL: { labelKey: 'HBM.moneyConverter.currencies.PL', toPln: 34560, noteKey: 'HBM.moneyConverter.notes.pl' },
};
const currKeys = Object.keys(CURRENCIES);
const optHtml = (sel: string) => currKeys.map(k => {
const label = game.i18n.localize(CURRENCIES[k].labelKey);
return `<option value="${k}"${k === sel ? ' selected' : ''}>${label}</option>`;
}).join('');
const ratesRows = currKeys.map(k => {
const c = CURRENCIES[k];
const label = game.i18n.localize(c.labelKey);
const note = c.noteKey ? game.i18n.format(c.noteKey, c.noteParam || {}) : '';
const fromPln = k === 'PLN' ? '1.0000' : (1 / c.toPln).toPrecision(4);
return `<tr>
<td>${label}</td>
<td style="text-align:right;font-family:monospace;">${c.toPln === 1 ? '1.0000' : c.toPln.toFixed(4)}</td>
<td style="text-align:right;font-family:monospace;">${fromPln}</td>
<td style="color:#777;font-size:0.75rem;">${note}</td>
</tr>`;
}).join('');
const rates = Object.fromEntries(currKeys.map(k => [k, CURRENCIES[k].toPln]));
const liveText = isLive
? game.i18n.format('HBM.moneyConverter.liveRates', { date: rateDate })
: game.i18n.localize('HBM.moneyConverter.defaultRates');
const initialEur = isFinite(pln / eurRate) ? (pln / eurRate).toFixed(4) : '';
const content = `
<div class="hbm money-converter" style="padding:10px;font-family:'Signika',sans-serif;display:flex;flex-direction:column;gap:12px;">
<p style="margin:0;font-size:0.78rem;color:${isLive ? '#2a7a3e' : '#888'};">
<em>${liveText}</em>
</p>
<div style="background:var(--hbm-card-bg,#1a1a2e);border:1px solid var(--hbm-border,#333);border-radius:6px;padding:8px 12px;">
<span style="font-weight:bold;">${game.i18n.localize('HBM.moneyConverter.actorCash')}</span>
<span style="font-family:monospace;font-size:1.1rem;margin-left:6px;">${pln} PLN</span>
<small style="color:#888;margin-left:6px;">(${game.i18n.format('HBM.moneyConverter.campaignYear', { year: currentYear })})</small>
</div>
<div style="background:var(--hbm-card-bg,#1a1a2e);border:1px solid var(--hbm-accent,#6060c0);border-radius:6px;padding:10px 12px;display:flex;flex-direction:column;gap:8px;">
<strong style="font-size:0.9rem;">${game.i18n.localize('HBM.moneyConverter.calculator')}</strong>
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
<input id="hbm-conv-left" type="number" value="${pln}" min="0" step="any"
style="width:6.5rem;font-size:1rem;text-align:right;padding:3px 6px;background:transparent;color:var(--hbm-fg,#eee);border:1px solid var(--hbm-border,#555);border-radius:4px;" />
<select id="hbm-sf" style="flex:1;min-width:8rem;">
${optHtml('PLN')}
</select>
<span style="font-size:1.2rem;color:var(--hbm-accent,#8080ff);">⇄</span>
<input id="hbm-conv-right" type="number" value="${initialEur}" min="0" step="any"
style="width:6.5rem;font-size:1rem;text-align:right;padding:3px 6px;background:transparent;color:var(--hbm-fg,#eee);border:1px solid var(--hbm-border,#555);border-radius:4px;" />
<select id="hbm-st" style="flex:1;min-width:8rem;">
${optHtml('EUR')}
</select>
</div>
</div>
<details>
<summary style="cursor:pointer;font-size:0.85rem;color:var(--hbm-accent-dim,#999);">▸ ${game.i18n.localize('HBM.moneyConverter.exchangeRatesTable')}</summary>
<table style="width:100%;border-collapse:collapse;font-size:0.8rem;margin-top:8px;">
<thead><tr style="border-bottom:1px solid var(--hbm-border,#555);">
<th style="text-align:left;">${game.i18n.localize('HBM.moneyConverter.currency')}</th>
<th style="text-align:right;">${game.i18n.localize('HBM.moneyConverter.ratePln')}</th>
<th style="text-align:right;">${game.i18n.localize('HBM.moneyConverter.onePlnEquals')}</th>
<th>${game.i18n.localize('HBM.moneyConverter.note')}</th>
</tr></thead>
<tbody>${ratesRows}</tbody>
</table>
</details>
</div>
`;
const dialog = new MoneyConverterDialog({
window: { title: game.i18n.localize('HBM.ui.moneyConverterTitle') },
content,
buttons: [
{
action: 'ok',
label: game.i18n.localize('HBM.ui.confirm') || 'Ok',
default: true
}
],
rejectClose: false
}, rates);
dialog.render(true);
}