html
head
title Actual Importer
body
h1 Import transactions
form#prepareForm
p
label(for="file") CSV File:
input(type="file" name="file" required)
p
label(for="profile") Profile:
select(name="profile" required)
each profile in profiles
option(value=profile selected=(defaultProfile === profile))= profile
p
label(for="server") Server:
select(name="server" required)
each server in servers
option(value=server selected=(defaultServer === server))= server
button(type="submit") Load
div#prepare
div#result
script.
const form = document.querySelector("#prepareForm");
function renderTable(transactions) {
const div = document.createElement('div');
div.innerHTML = `
Pending transactions (${transactions.length})
`;
const table = document.createElement('table');
const thead = document.createElement('thead');
const tr = document.createElement('tr');
tr.innerHTML = `
#
Import
ID
Kind
Date
From
To
Amount
Title
`;
thead.appendChild(tr);
table.appendChild(thead);
const tbody = document.createElement('tbody');
transactions.forEach((transaction, index) => {
const row = document.createElement('tr');
row.innerHTML = `
${index+1}
${transaction.id}
${transaction.kind}
${transaction.date}
${transaction.from}
${transaction.to}
${transaction.amount}
${transaction.title}
`;
tbody.appendChild(row);
});
table.appendChild(tbody);
div.appendChild(table);
return div;
}
function renderSkipped(skipped) {
const div = document.createElement('div');
div.innerHTML = `
Skipped CSV rows
The ::: is CSV field delimiter
${skipped.map(d => d.join(" ::: ")).join("\n")}
`
return div;
}
function renderResult(result) {
const resultWrapper = document.querySelector('#result');
resultWrapper.innerHTML = `
Import status
Added: ${result.added.length}
Updated: ${result.updated.length}
Errors: ${result?.errors?.length ?? 0}
${(result.errors?.length ?? 0) > 0
? `Errors:
${result.errors?.map(e => `${e.message} `)} `
: ""}
`;
}
async function submitTransactions(config, transactions, opts) {
const filtered = transactions.filter((transaction, index) => !!document.querySelector(`#transaction${index}`).checked);
if (filtered.length === 0) {
return;
}
try {
const response = await fetch("/submit", {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
server: config.get('server'),
transactions: filtered,
opts
}),
});
const result = await response.json();
renderResult(result);
} catch (e) {
console.error(e);
}
}
function renderPrepare(config, data) {
document.querySelector('#result').innerHTML = '';
const prepare = document.querySelector("#prepare");
prepare.innerHTML = '';
const title = document.createElement("h2");
title.innerHTML = "Prepare transactions to submit";
prepare.appendChild(title);
prepare.appendChild(renderTable(data.transactions));
prepare.appendChild(renderSkipped(data.skipped));
const addForm = document.createElement("form");
addForm.innerHTML = `
Add
Learn categories
Run transfers
Add
`;
addForm.addEventListener("submit", async (e) => {
e.preventDefault();
const options = new FormData(addForm);
const opts = {
mode: 'add',
learnCategories: !!options.get('learn'),
runTransfers: !!options.get('transfers')
};
await submitTransactions(config, data.transactions, opts);
prepare.innerHTML = '';
});
const importForm = document.createElement("form");
importForm.innerHTML = `
Import
Import
`;
importForm.addEventListener("submit", async (e) => {
e.preventDefault();
await submitTransactions(config, data.transactions, { mode: 'import' });
prepare.innerHTML = '';
});
if (data.transactions.length > 0) {
prepare.appendChild(addForm);
prepare.appendChild(importForm);
} else {
const message = document.createElement('p');
message.innerHTML = `
No transactions to import
`;
prepare.appendChild(message);
}
}
async function handlePrepare(e) {
e.preventDefault();
try {
const config = new FormData(form);
const response = await fetch("/prepare", {
method: "POST",
body: config,
});
const data = await response.json();
renderPrepare(config, data);
} catch (e) {
console.error(e);
}
}
form.addEventListener("submit", handlePrepare);