// Generated, do not edit. sha256:86d4e38921922eb14e5463ba3c4d6855cd2e5276d5295930d9126848cf48e05c import { t, localeTag } from '../i18n/index.js'; import { serverFoutTekst } from '../data/_rpc.js'; import { esc } from '../ui/escape.js'; import * as dialog from '../ui/dialog.js'; import { setPageHint } from '../ui/page-hint.js'; import { getSettings, updateSettings } from '../data/preferences.js'; import { firstPositionDate } from '../data/positions.js'; import { switchPortfolio, reloadWhenBack, refreshActivePortfolio } from '../portfolio.js'; import { backupDb, exportDb, restoreDb, startFresh } from './db-file.js'; import { openPopover, closePopover } from '../ui/popover.js'; import { expanders } from '../ui/expand.js'; import { PLUS, CHEVRON } from '../ui/icons.js'; import { currencyName } from '../ui/format.js'; import { CONFIG, ENUMS } from '../../shared/config.js'; import { counterText, limitFor, locked } from '../license.js'; import { counterHtml } from '../ui/lock.js'; import { baseCurrency } from '../app-settings.js'; import { openSetup } from '../onboarding/setup.js'; async function api(pad, body) { const opties = body ? { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) } : {}; const r = await fetch(pad, opties); if (r.status === 401) { location.replace(CONFIG.AUTH.LOGIN_PATH); throw new Error(t('common.sessionExpired')); } const uit = await r.json().catch(() => null); if (!r.ok) throw new Error(serverFoutTekst(uit) || `HTTP ${r.status}`); return uit; } const datum = (iso) => (iso ? new Date(iso).toLocaleDateString(localeTag()) : null); const DAG_MS = 24 * 60 * 60 * 1000; const backupTeOud = (iso) => !iso || Date.now() - new Date(iso).getTime() > CONFIG.BACKUP.REMIND_AFTER_DAYS * DAG_MS; function menuItems(p, { full, beheer }) { if (!beheer) return []; const kopie = p.heeftDatabase && !full ? [['kopieer', t('portfolios.copy')]] : []; return p.actief ? [...kopie, ['exporteer', t('dbfile.export')], ['terugzetten', t('dbfile.import')], ['inrichten', t('setup.rerun')], ['leeg', t('dbfile.fresh')]] : [...kopie, ['hernoem', t('portfolios.rename')], ['verwijder', t('portfolios.remove')]]; } function feitHtml(label, waarde, { late = false } = {}) { return `
${label}
${waarde}
`; } const soortenVan = (telling, enumNaam) => ENUMS[enumNaam.toUpperCase()].filter(k => telling?.[k]); function perSoortHtml(label, telling, enumNaam, regels) { const getal = (n) => esc(Number(n).toLocaleString(localeTag())); const soorten = soortenVan(telling, enumNaam); const totaal = soorten.reduce((s, k) => s + telling[k], 0); const lijst = soorten.map(k => `
  • ${esc(t(`enums.${enumNaam}.${k}`))}${getal(telling[k])}
  • `).join(''); return `
    ${label}
    ${getal(totaal)}
    `; } function kaartHtml(p, { full, beheer, regels }) { const k = p.kerncijfers; const getal = (n) => esc(Number(n).toLocaleString(localeTag())); const sub = !p.heeftDatabase ? t('portfolios.noDatabase') : k && k.munt ? esc(k.eersteJaar ? t('portfolios.cardSince', { munt: k.munt, jaar: k.eersteJaar }) : k.munt) : ''; const feiten = !k ? '' : `
    ${perSoortHtml(t('portfolios.factAssets'), k.assets, 'asset_kind', regels)} ${perSoortHtml(t('portfolios.factAccounts'), k.accounts, 'account_kind', regels)} ${feitHtml(t('portfolios.factEvents'), getal(k.boekingen))} ${feitHtml(t('portfolios.factValuations'), getal(k.balansen))} ${feitHtml(t('portfolios.factLastUsed'), esc(datum(p.laatstGebruikt) || t('portfolios.never')))} ${beheer ? feitHtml(t('portfolios.factBackup'), esc(datum(k.laatsteBackup) || t('portfolios.backupNever')), { late: backupTeOud(k.laatsteBackup) }) : '
    '}
    `; const backupKlasse = k && backupTeOud(k.laatsteBackup) ? 'btn-primary' : 'btn-secondary'; const hoofd = !p.actief ? `` : ` ${beheer ? `` : ''}`; const menu = menuItems(p, { full, beheer }).length ? `` : ''; return `
    ${esc(p.naam)}
    ${p.demo ? `${t('portfolios.demoBadge')}` : ''} ${p.actief ? `${t('portfolios.openNow')}` : ''}
    ${sub}
    ${feiten}
    ${hoofd}${menu}
    `; } function archiefRijHtml(a) { return `
    ${esc(a.naam)}
    ${esc(datum(a.gearchiveerd) || '')}
    `; } function authHtml(auth) { const stand = auth.bron === 'omgeving' ? t('portfolios.authEnv') : auth.ingesteld ? t('portfolios.authSet') : t('portfolios.authNone'); const knoppen = auth.wijzigbaar ? ` ${auth.ingesteld ? `` : ''}` : ''; const uitloggen = auth.ingesteld ? `` : ''; return `
    ${t('portfolios.authTitle')}
    ${stand}
    ${knoppen}${uitloggen}
    `; } function startJaarHtml({ gekozen, eersteJaar, huidigJaar }) { const AUTO = CONFIG.START_YEAR.AUTO; let inhoud; if (eersteJaar == null) { inhoud = `
    ${t('portfolios.startYearEmpty')}
    `; } else { const eersteKolom = Math.min(Math.max(eersteJaar + 1, gekozen), huidigJaar); const stand = gekozen === AUTO ? t('portfolios.startYearAutoState', { jaar: eersteKolom }) : t('portfolios.startYearState', { jaar: eersteKolom, eerste: eersteJaar }); const jaren = []; for (let y = eersteJaar; y <= huidigJaar; y++) jaren.push(y); if (gekozen !== AUTO && !jaren.includes(gekozen)) jaren.unshift(gekozen); const opties = [``] .concat(jaren.map(y => ``)); inhoud = `
    ${stand}
    `; } return `
    ${t('portfolios.startYearTitle')}
    ${inhoud}
    `; } function rendementHtml({ gekozen }) { const opties = ENUMS.MWR_METHOD .map(m => ``) .join(''); return `
    ${t('portfolios.mwrTitle')}
    `; } async function instellingenHtml() { const [auth, instellingen, eerstePositie] = await Promise.all([ api('/api/auth/status'), getSettings(), firstPositionDate(), ]); return authHtml(auth) + startJaarHtml({ gekozen: Number(instellingen.start_year) || CONFIG.START_YEAR.AUTO, eersteJaar: eerstePositie ? Number(eerstePositie.slice(0, 4)) : null, huidigJaar: new Date().getFullYear(), }) + rendementHtml({ gekozen: String(instellingen.mwr_method || 'dietz') }); } async function openInstellingen(naam) { const html = await instellingenHtml(); const klaar = dialog.detail({ title: t('portfolios.settingsOf', { naam }), html: `
    ${html}
    ` }); const host = document.querySelector('[data-pf-settings]'); host?.closest('.dialog')?.classList.add('pf-settings-dialog'); const ververs = async () => { if (host?.isConnected) host.innerHTML = await instellingenHtml(); }; const fout = (e) => dialog.alert({ title: t('dialog.error'), message: e.message }); if (host) { host.onclick = async (ev) => { const knop = ev.target.closest('[data-actie]'); if (!knop) return; try { if (knop.dataset.actie === 'auth-zet' && await authZetten()) await ververs(); if (knop.dataset.actie === 'auth-leeg' && await authLeegmaken()) await ververs(); if (knop.dataset.actie === 'auth-uit') await uitloggen(); } catch (e) { fout(e); } }; host.onchange = async (ev) => { const veld = ev.target.matches('#mwrmethode') ? { mwr_method: ev.target.value } : ev.target.matches('#startjaar') ? { start_year: Number(ev.target.value) } : null; if (!veld) return; try { await updateSettings(veld); document.dispatchEvent(new Event('settings-changed')); await ververs(); } catch (e) { fout(e); } }; } await klaar; } export async function render(el) { const [overzicht, expand] = await Promise.all([ api('/api/portfolios'), expanders('portfolios', { archive: false }), ]); refreshActivePortfolio(); const beheer = !locked('portfolios'); const max = limitFor('portfolios'); const eigen = overzicht.portefeuilles.filter(p => !p.demo).length; const full = max !== null && eigen >= max; const aangemaakt = p => p.kerncijfers?.aangemaakt || ''; const lijst = [...overzicht.portefeuilles] .sort((a, b) => aangemaakt(a).localeCompare(aangemaakt(b)) || a.naam.localeCompare(b.naam, localeTag())); const regels = Math.max(1, ...lijst.map(p => Math.max( soortenVan(p.kerncijfers?.assets, 'asset_kind').length, soortenVan(p.kerncijfers?.accounts, 'account_kind').length))); const nieuwTegel = full ? `
    ${counterHtml(counterText('portfolios', eigen))}
    ` : `
    `; const archief = beheer && overzicht.archief.length ? `
    ${CHEVRON}${t('portfolios.archiveTitle')} ${overzicht.archief.length}
    ${overzicht.archief.map(archiefRijHtml).join('')}
    ` : ''; el.innerHTML = `
    ${lijst.map(p => kaartHtml(p, { full, beheer, regels })).join('')} ${nieuwTegel}
    ${archief ? `
    ${archief}
    ` : ''}`; expand.bind(el); const instellingenUitleg = [ { heading: t('portfolios.authTitle') }, t('portfolios.authHint'), { heading: t('portfolios.startYearTitle') }, t('portfolios.startYearHint'), { heading: t('portfolios.mwrTitle') }, t('portfolios.mwrHint'), t('portfolios.mwrState_dietz'), t('portfolios.mwrState_irr'), ]; setPageHint(el, { title: t('portfolios.hintTitle'), rows: beheer ? [ { heading: t('portfolios.title') }, t('portfolios.intro'), t('portfolios.createHint'), t('portfolios.createCurrencyHint'), { heading: t('portfolios.backupTitle') }, t('dbfile.hint'), t('portfolios.backupLateHint', { dagen: CONFIG.BACKUP.REMIND_AFTER_DAYS }), ...instellingenUitleg, { heading: t('portfolios.archiveTitle') }, t('portfolios.archiveIntro'), ] : [ { heading: t('portfolios.title') }, t('portfolios.intro'), t('portfolios.demoHint'), ...instellingenUitleg, ], }); const opnieuw = () => render(el); const fout = (e) => dialog.alert({ title: t('dialog.error'), message: e.message }); async function doe(actie, naam) { try { switch (actie) { case 'open': await open(naam); break; case 'nieuw': if (await nieuw()) await opnieuw(); break; case 'kopieer': if (await kopieer(naam)) await opnieuw(); break; case 'instellingen': await openInstellingen(naam); break; case 'backup': if (await backupDb()) await opnieuw(); break; case 'exporteer': await exportDb(); await opnieuw(); break; case 'terugzetten': restoreDb(opnieuw); break; case 'leeg': await startFresh(opnieuw); break; case 'inrichten': await openSetup({ rerun: true }); await opnieuw(); break; case 'hernoem': if (await hernoem(naam)) await opnieuw(); break; case 'verwijder': if (await verwijder(naam)) await opnieuw(); break; case 'wis': if (await wis(naam)) await opnieuw(); break; } } catch (e) { fout(e); } } function openMenu(knop) { const p = lijst.find(x => x.naam === knop.dataset.menu); if (!p) return; const { el: pop, position } = openPopover(knop, { className: 'popover new-menu' }); pop.innerHTML = menuItems(p, { full, beheer }).map(([actie, label]) => ``).join(''); position(); pop.onclick = (ev) => { const item = ev.target.closest('[data-actie]'); if (!item) return; closePopover(); doe(item.dataset.actie, p.naam); }; } el.onclick = (ev) => { const menuKnop = ev.target.closest('[data-menu]'); if (menuKnop) { openMenu(menuKnop); return; } const knop = ev.target.closest('[data-actie]'); if (knop) doe(knop.dataset.actie, knop.dataset.naam); }; } async function open(naam) { if (!await dialog.confirm({ message: t('portfolios.activateConfirm', { naam }) })) return; const uit = await switchPortfolio(naam); if (!uit.supervised) { await dialog.alert({ message: t('portfolios.activateNoSupervisor', { naam }) }); return; } dialog.notice({ message: t('portfolios.activateWaiting') }); await reloadWhenBack(); } async function nieuw() { const res = await dialog.form({ title: t('portfolios.create'), fields: [ { key: 'naam', label: t('portfolios.createName'), placeholder: t('portfolios.createNameHint') }, { key: 'munt', label: t('portfolios.createCurrency'), type: 'select', options: CONFIG.PORTFOLIO.BASE_CURRENCIES.map(code => ({ value: code, label: `${currencyName(code)} (${code})` })), value: baseCurrency(), }, ], confirmLabel: t('portfolios.createConfirm'), }); if (!res) return false; const uit = await api('/api/portfolios/create', { naam: res.naam, kopieVan: null, baseCurrency: res.munt }); await dialog.alert({ message: t('portfolios.createDone', { naam: uit.naam }) }); return true; } async function kopieer(bron) { const res = await dialog.form({ title: t('portfolios.copyTitle', { naam: bron }), fields: [{ key: 'naam', label: t('portfolios.copyName'), placeholder: t('portfolios.createNameHint') }], confirmLabel: t('portfolios.copyConfirm'), }); if (!res) return false; const uit = await api('/api/portfolios/create', { naam: res.naam, kopieVan: bron, baseCurrency: null }); await dialog.alert({ message: t('portfolios.copyDone', { naam: uit.naam, bron }) }); return true; } async function hernoem(naam) { const res = await dialog.form({ title: t('portfolios.rename'), fields: [{ key: 'naar', label: t('portfolios.renameTo'), value: naam }], confirmLabel: t('portfolios.rename'), }); if (!res) return false; const uit = await api('/api/portfolios/rename', { van: naam, naar: res.naar }); await dialog.alert({ message: t('portfolios.renameDone', { naam: uit.naar }) }); return true; } async function verwijder(naam) { const res = await dialog.form({ title: t('portfolios.remove'), fields: [{ key: 'bevestiging', label: t('portfolios.removeConfirm', { naam }) }], confirmLabel: t('portfolios.remove'), }); if (!res) return false; if (res.bevestiging.trim() !== naam) { await dialog.alert({ title: t('dialog.error'), message: t('portfolios.removeMismatch') }); return false; } await api('/api/portfolios/archive', { naam }); await dialog.alert({ message: t('portfolios.removeDone', { naam }) }); return true; } async function wis(naam) { const res = await dialog.form({ title: t('portfolios.purge'), fields: [{ key: 'bevestiging', label: t('portfolios.purgeConfirm', { naam }) }], confirmLabel: t('portfolios.purge'), }); if (!res) return false; if (res.bevestiging.trim() !== naam) { await dialog.alert({ title: t('dialog.error'), message: t('portfolios.removeMismatch') }); return false; } await api('/api/portfolios/purge', { naam }); await dialog.alert({ message: t('portfolios.purgeDone', { naam }) }); return true; } async function authZetten() { const res = await dialog.form({ title: t('portfolios.authChange'), fields: [{ key: 'password', label: t('portfolios.authPassword'), type: 'password' }], confirmLabel: t('portfolios.authSave'), }); if (!res) return false; await api('/api/auth/set', { password: res.password }); await dialog.alert({ message: t('portfolios.authDone') }); return true; } async function uitloggen() { if (!await dialog.confirm({ message: t('portfolios.authLogoutConfirm') })) return; await api('/api/logout', {}); location.replace(CONFIG.AUTH.LOGIN_PATH); } async function authLeegmaken() { if (!await dialog.confirm({ message: t('portfolios.authClearConfirm'), danger: true })) return false; await api('/api/auth/clear', {}); await dialog.alert({ message: t('portfolios.authClearedDone') }); return true; }