initial webapp
This commit is contained in:
1 parent
6c989c338c
commit
d32f2b3a66
18 files changed
+1145
-16
No files matched your search
@@ -0,0 +1,108 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var UPLOAD_URL = "/api/upload";
|
||||
|
||||
function $(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
function setStatus(message) {
|
||||
var status = $("status");
|
||||
status.textContent = message || "";
|
||||
}
|
||||
|
||||
function setError(message) {
|
||||
var error = $("error");
|
||||
error.textContent = message || "";
|
||||
}
|
||||
|
||||
function clearResult() {
|
||||
var result = $("result");
|
||||
var rowsInfo = $("rows-info");
|
||||
var tableOutput = $("table-output");
|
||||
rowsInfo.textContent = "";
|
||||
tableOutput.textContent = "";
|
||||
result.hidden = true;
|
||||
}
|
||||
|
||||
function showResult(rows, table) {
|
||||
var result = $("result");
|
||||
var rowsInfo = $("rows-info");
|
||||
var tableOutput = $("table-output");
|
||||
rowsInfo.textContent = "Rows: " + rows;
|
||||
tableOutput.textContent = table || "";
|
||||
result.hidden = false;
|
||||
}
|
||||
|
||||
function uploadFile(file) {
|
||||
var formData = new FormData();
|
||||
formData.append("csv", file);
|
||||
|
||||
return fetch(UPLOAD_URL, {
|
||||
method: "POST",
|
||||
body: formData
|
||||
}).then(function (response) {
|
||||
return response.json().then(function (data) {
|
||||
return { ok: response.ok, status: response.status, data: data };
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
setError("");
|
||||
clearResult();
|
||||
|
||||
var fileInput = $("csv");
|
||||
if (!fileInput.files || fileInput.files.length === 0) {
|
||||
setError("Please choose a CSV file before uploading.");
|
||||
return;
|
||||
}
|
||||
|
||||
var file = fileInput.files[0];
|
||||
var button = $("upload-button");
|
||||
button.disabled = true;
|
||||
setStatus("Uploading " + file.name + "...");
|
||||
|
||||
uploadFile(file).then(function (result) {
|
||||
button.disabled = false;
|
||||
|
||||
if (!result.ok) {
|
||||
setStatus("");
|
||||
var serverMsg = result.data && result.data.error ? result.data.error : "";
|
||||
var msg = "Upload failed (HTTP " + result.status + ").";
|
||||
if (serverMsg) {
|
||||
msg += " " + serverMsg;
|
||||
}
|
||||
setError(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.data && typeof result.data.error === "string" && result.data.error.length > 0) {
|
||||
setStatus("");
|
||||
setError(result.data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
var table = result.data && typeof result.data.table === "string" ? result.data.table : "";
|
||||
var rows = result.data && typeof result.data.rows === "number" ? result.data.rows : 0;
|
||||
|
||||
setStatus("Upload complete.");
|
||||
showResult(rows, table);
|
||||
}).catch(function (err) {
|
||||
button.disabled = false;
|
||||
setStatus("");
|
||||
var msg = err && err.message ? err.message : String(err);
|
||||
setError("Network error while uploading: " + msg);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
var form = $("upload-form");
|
||||
if (form) {
|
||||
form.addEventListener("submit", handleSubmit);
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>any2anexoj web</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">
|
||||
<h1>any2anexoj web</h1>
|
||||
<p class="lede">Upload a Trading212-style CSV and view the rendered tax report.</p>
|
||||
|
||||
<form id="upload-form" class="upload-form" novalidate>
|
||||
<label class="file-label" for="csv">Choose CSV file</label>
|
||||
<input type="file" id="csv" name="csv" accept=".csv,text/csv">
|
||||
|
||||
<button type="submit" id="upload-button">Upload</button>
|
||||
</form>
|
||||
|
||||
<div id="status" class="status" role="status" aria-live="polite"></div>
|
||||
<div id="error" class="error" role="alert" aria-live="assertive"></div>
|
||||
|
||||
<section id="result" class="result" hidden>
|
||||
<h2 id="result-heading">Result</h2>
|
||||
<p id="rows-info" class="rows-info"></p>
|
||||
<pre id="table-output" class="table-output"></pre>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="/static/app.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,168 @@
|
||||
:root {
|
||||
--bg: #fafafa;
|
||||
--fg: #1f2328;
|
||||
--muted: #57606a;
|
||||
--accent: #0969da;
|
||||
--accent-hover: #0860c7;
|
||||
--border: #d0d7de;
|
||||
--error-bg: #ffebe9;
|
||||
--error-fg: #82071e;
|
||||
--status-fg: #1f2328;
|
||||
--code-bg: #f6f8fa;
|
||||
--code-fg: #1f2328;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
"Helvetica Neue", Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem 1.5rem 4rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0 0 0.5rem;
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0 0 0.75rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.lede {
|
||||
margin: 0 0 2rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.upload-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 1rem;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.file-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
font: inherit;
|
||||
flex: 1 1 240px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
font: inherit;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: var(--accent);
|
||||
color: #ffffff;
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover:not(:disabled) {
|
||||
background-color: var(--accent-hover);
|
||||
border-color: var(--accent-hover);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.status {
|
||||
margin: 0.5rem 0 0;
|
||||
color: var(--status-fg);
|
||||
min-height: 1.25rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.error {
|
||||
margin: 0.5rem 0 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background-color: var(--error-bg);
|
||||
color: var(--error-fg);
|
||||
border: 1px solid #ffcecb;
|
||||
border-radius: 6px;
|
||||
font-size: 0.95rem;
|
||||
display: none;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.error:not(:empty) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.result {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.result[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rows-info {
|
||||
margin: 0 0 0.75rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.table-output {
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
background-color: var(--code-bg);
|
||||
color: var(--code-fg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
|
||||
"Liberation Mono", monospace;
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.4;
|
||||
overflow-x: auto;
|
||||
overflow-y: visible;
|
||||
white-space: pre;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.container {
|
||||
padding: 1.25rem 1rem 3rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.upload-form {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user