Skip to content

Commit

Permalink
feat: Basic transaction submission
Browse files Browse the repository at this point in the history
  • Loading branch information
kajyr committed Nov 4, 2021
1 parent c34afd6 commit 7a1f2b4
Show file tree
Hide file tree
Showing 16 changed files with 268 additions and 40 deletions.
2 changes: 0 additions & 2 deletions backend/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import path from 'path';
const PUBLIC_PATH = path.join(__dirname, "..", "public");

async function build(opts = {}) {
const nodeEnv = (process.env.NODE_ENV || "").toLowerCase();

const app = await fastify(opts);
app.register(require("fastify-log"));
app.register(require("fastify-routes"));
Expand Down
12 changes: 10 additions & 2 deletions backend/dal/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
const path = require("path");
const fullFile = process.env.LEDGER_FILE;
import path from 'path';
import { formatTransaction, Transaction } from 'pta-js';

import fs from 'fs/promises';

export const fullFile = path.resolve(process.env.K_LEDGER_FILE || "");

export const filename = path.basename(fullFile);

export function addTransaction(data: Transaction): Promise<void> {
return fs.appendFile(fullFile, `\n${formatTransaction(data)}`, "utf8");
}
12 changes: 7 additions & 5 deletions backend/journal/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const RESOURCE_ID = "dives";
import { Transaction } from 'pta-js';

import { addTransaction } from '../dal/';

export default function (fastify, opts, done) {
const routes = [
Expand All @@ -13,12 +15,12 @@ export default function (fastify, opts, done) {
method: "POST",
path: `/api/journal`,
handler: async function (request, reply) {
/* const { guid, ...item } = request.body;
if (!guid) {
return reply.status(405).send({ error: "Missing item id" });
const trx = request.body as Transaction;
if (!trx.date) {
return reply.status(405).send({ error: "Missing date" });
}

*/
await addTransaction(trx);
return reply.status(200).send();
},
},
Expand Down
2 changes: 2 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
"dependencies": {
"@types/node": "^16.11.6",
"colors": "^1.1.2",
"dayjs": "^1.10.7",
"dotenv": "^10.0.0",
"fastify": "^3.15.0",
"fastify-log": "^1.2.1",
"fastify-routes": "^3.0.1",
"fastify-static": "^4.0.1",
"luxon": "^2.0.2",
"pino-pretty": "^7.2.0",
"pta-js": "file:./../../pta-js",
"ts-node": "^10.4.0",
"tslib": "^2.2.0",
"typescript": "^4.2.4"
Expand Down
4 changes: 3 additions & 1 deletion backend/server.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
require("dotenv");
require("dotenv").config();

import colors from 'colors/safe';

import app from './app';
import { fullFile } from './dal';
import { Server } from './types';

const PORT = 4445;
Expand Down Expand Up @@ -50,6 +51,7 @@ const init = async () => {

//@ts-ignore This is added by a fastify plugin
logRoutes(server.routes);
console.log("Journal file:", fullFile);
};

init();
13 changes: 8 additions & 5 deletions backend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"

"@sinclair/typebox@^0.16.5":
version "0.16.7"
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.16.7.tgz#3187369b2ff237f57477ef155be4c992e592fa7c"
integrity sha512-d12AkLZJXD30hXBhJSgf33RqGO0NMHIDzsQPYfp6WGoaSuMnSGIUanII2OUbeZFnD/j3Nbl2zifgO2+5tPClCQ==

"@sindresorhus/is@^0.14.0":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
Expand Down Expand Up @@ -517,6 +512,11 @@ dateformat@^4.6.3:
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5"
integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==

dayjs@^1.10.7:
version "1.10.7"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==

debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
Expand Down Expand Up @@ -1405,6 +1405,9 @@ pstree.remy@^1.1.7:
resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==

"pta-js@file:../../pta-js":
version "0.1.0"

pump@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
Expand Down
6 changes: 4 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"name": "frontend",
"version": "2.1.2",
"version": "0.0.1",
"main": "src/index.tsx",
"repository": "https://github.com/kajyr/diario.blue",
"author": "Carlo Panzi <carlo.panzi@gmail.com>",
"license": "MIT",
"dependencies": {
"@mantine/core": "^3.0.5",
"@mantine/dates": "^3.1.4",
"@mantine/hooks": "^3.0.5",
"@types/node": "^16.11.6",
"@types/react": "^17.0.3",
Expand All @@ -15,8 +16,9 @@
"@typescript-eslint/eslint-plugin": "^5.3.0",
"@typescript-eslint/parser": "^5.3.0",
"buffer": "^6.0.3",
"dayjs": "^1.10.7",
"html-webpack-plugin": "^5.3.1",
"luxon": "^2.0.2",
"pta-js": "file:./../../pta-js",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-query": "^3.31.0",
Expand Down
5 changes: 1 addition & 4 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { FC } from 'react';
import { useQuery } from 'react-query';
import { Route, Routes } from 'react-router-dom';

import Loader from 'atoms/Loader';

Expand All @@ -21,9 +20,7 @@ const App: FC = () => {
return (
<>
<Page filename={data.file}>
<Routes>
<Route path="/" element={<Dashboard />} />
</Routes>
<Dashboard />
</Page>
</>
);
Expand Down
26 changes: 26 additions & 0 deletions frontend/src/helpers/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export async function callApi(url: string, options: RequestInit = {}) {
const fetchOptions: RequestInit = {
credentials: "same-origin",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
...options,
};

const response = await fetch(url, fetchOptions);
// IE11 polyfill for response.ok
if (!("ok" in response)) {
//@ts-ignore
response.ok = response.status >= 200 && response.status < 300;
}
return response;
}

export function callJsonApi<T>(url, options): Promise<T | undefined> {
return callApi(url, options).then((response) => {
if (response.ok) {
return response.json() as Promise<T>;
}
});
}
12 changes: 12 additions & 0 deletions frontend/src/helpers/clear-transaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import dayjs from 'dayjs';
import { Transaction } from 'pta-js';

// Removes empty entries
function clearTransaction(trx: Transaction): Transaction {
return {
...trx,
entries: trx.entries.filter((e) => !(e.account == "" && e.amount === "")),
};
}

export default clearTransaction;
4 changes: 2 additions & 2 deletions frontend/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ render(
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<Routes>
<Route path="/404" element={<FourOhFour />} />
<Route path="/*" element={<App />} />
<Route path="*" element={<FourOhFour />} />
<Route path="/" element={<App />} />
</Routes>
</BrowserRouter>
</QueryClientProvider>
Expand Down
7 changes: 0 additions & 7 deletions frontend/src/pages/Dashboard.tsx

This file was deleted.

29 changes: 29 additions & 0 deletions frontend/src/pages/dashboard/confirmation-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { FC } from 'react';

import clearTransaction from 'helpers/clear-transaction';

import { Button, Code, Group, Modal } from '@mantine/core';

import { formatTransaction, Transaction } from 'pta-js';

const ConfirmationModal: FC<{
data: Transaction;
onCancel: () => void;
onConfirm: (data: Transaction) => void;
}> = ({ data, onCancel, onConfirm }) => {
const clean = clearTransaction(data);
const trxstr = formatTransaction(clean);
return (
<Modal opened={true} onClose={onCancel} title="Confirm transaction">
<Code block>{trxstr}</Code>
<Group position="right" style={{ marginTop: 25 }}>
<Button variant="white" onClick={onCancel}>
Cancel
</Button>
<Button onClick={() => onConfirm(clean)}>Confirm</Button>
</Group>
</Modal>
);
};

export default ConfirmationModal;
144 changes: 144 additions & 0 deletions frontend/src/pages/dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import React, { FC, useState } from 'react';

import { callApi } from 'helpers/api';
import clearTransaction from 'helpers/clear-transaction';

import { Button, Group, LoadingOverlay, Paper, TextInput, Title } from '@mantine/core';
import { DatePicker } from '@mantine/dates';
import { useForm } from '@mantine/hooks';

import { Transaction } from 'pta-js';

import ConfirmationModal from './confirmation-modal';

const EMPTY_ENTRY = { account: "", amount: "" };

const Dashboard: FC = () => {
const { onSubmit, values, setFieldValue, setValues, reset } = useForm({
initialValues: {
date: new Date(),
description: "",
entries: [EMPTY_ENTRY],
},
});

const [modalOpen, setModalOpen] = useState(false);
const [showOverlay, setOverlay] = useState(false);

const handleSubmit = () => {
setModalOpen(true);
};

function addRow() {
setValues((state) => ({
...state,
entries: state.entries.concat({ ...EMPTY_ENTRY }),
}));
}

function updateRow(idx: number, field: string, value: string) {
setValues((state) => ({
...state,
entries: state.entries.map((e, i) => {
if (i !== idx) {
return e;
}

return { ...e, [field]: value };
}),
}));
}

function removeRow(idx: number) {
return () => {
setValues((state) => ({
...state,
entries: state.entries.filter((e, i) => i !== idx),
}));
};
}

async function handleModalConfirm(data: Transaction) {
setModalOpen(false);
setOverlay(true);
const response = await callApi("/api/journal", {
method: "POST",
body: JSON.stringify(data),
});
setOverlay(false);
if (response.ok) {
reset();
}
}

return (
<div>
<Paper padding="md" shadow="sm" component="section">
<form
onSubmit={onSubmit(handleSubmit)}
style={{ position: "relative" }}
>
<LoadingOverlay visible={showOverlay} />
<Title order={2}>Add</Title>
<DatePicker
placeholder="Pick a date"
label="Date"
value={values.date}
onChange={(date) => date && setFieldValue("date", date)}
/>
<TextInput
label="Description"
value={values.description}
onChange={(event) =>
setFieldValue("description", event.currentTarget.value)
}
/>
{values.entries.map((entry, i) => (
<Group style={{ marginTop: 25, alignItems: "center" }} key={i}>
<TextInput
label="Account"
value={entry.account}
onChange={(event) =>
updateRow(i, "account", event.currentTarget.value)
}
/>
<TextInput
label="Amount"
value={entry.amount}
onChange={(event) =>
updateRow(i, "amount", event.currentTarget.value)
}
/>
<Button compact style={{ marginTop: "30px" }} onClick={addRow}>
+
</Button>
{i !== 0 && (
<Button
compact
style={{ marginTop: "30px" }}
onClick={removeRow(i)}
>
-
</Button>
)}
</Group>
))}
<Group position="right" style={{ marginTop: 25 }}>
<Button color="blue" type="submit">
Add
</Button>
</Group>
</form>
</Paper>
{modalOpen && (
<ConfirmationModal
data={values}
onCancel={() => setModalOpen(false)}
onConfirm={handleModalConfirm}
/>
)}
</div>
);
};

export default Dashboard;
Loading

0 comments on commit 7a1f2b4

Please sign in to comment.