diff --git a/components/Graph/Graph.jsx b/components/Graph/Graph.jsx
new file mode 100644
index 0000000..7380a9d
--- /dev/null
+++ b/components/Graph/Graph.jsx
@@ -0,0 +1,58 @@
+import React from "react";
+import { Line } from "react-chartjs-2";
+import {
+ Chart,
+ CategoryScale,
+ LinearScale,
+ PointElement,
+ LineElement,
+ Title,
+ Tooltip,
+ Legend,
+} from "chart.js";
+import styles from "./Graph.module.sass";
+
+export default function Graph(props) {
+ Chart.register(
+ CategoryScale,
+ LinearScale,
+ PointElement,
+ LineElement,
+ Title,
+ Tooltip,
+ Legend
+ );
+
+ const accounts = [
+ ...new Set(
+ props.transactions
+ .map((txn) => {
+ return txn.parts.map((part) => {
+ return part.account;
+ });
+ })
+ .flat()
+ ),
+ ];
+
+ return (
+
+ {
+ return {
+ label: account,
+ data: [1, 2, 4],
+ // backgroundColor: getComputedStyle(
+ // document.documentElement
+ // ).getPropertyValue("--color-blue"),
+ // borderColor: document.documentElement.style.getPropertyValue("--color-blue"),
+ };
+ }),
+ }}
+ />
+
+ );
+}
diff --git a/components/Graph/Graph.module.sass b/components/Graph/Graph.module.sass
new file mode 100644
index 0000000..8746e67
--- /dev/null
+++ b/components/Graph/Graph.module.sass
@@ -0,0 +1,9 @@
+.graph
+ border-radius: var(--card-radius)
+ background-color: var(--color-white)
+ margin: 10px
+ padding: 20px
+ width: 60%
+ height: 20vh
+ font-family: "Montserrat"
+ font-weight: 300
\ No newline at end of file
diff --git a/components/Overview/Overview.jsx b/components/Overview/Overview.jsx
new file mode 100644
index 0000000..8fcb384
--- /dev/null
+++ b/components/Overview/Overview.jsx
@@ -0,0 +1,16 @@
+import React from "react";
+import styles from "./Overview.module.sass";
+
+export default function Overview(props) {
+ const accounts = [...new Set(props.transactions.map((txn) => {
+ return txn.parts.map((part) => {
+ return part.account;
+ });
+ }).flat())];
+console.log(accounts)
+ return
+
+ {JSON.stringify(props.stats, null, 2)}
+
+
;
+}
diff --git a/components/Overview/Overview.module.sass b/components/Overview/Overview.module.sass
new file mode 100644
index 0000000..28d6120
--- /dev/null
+++ b/components/Overview/Overview.module.sass
@@ -0,0 +1,9 @@
+.overview_card
+ border-radius: var(--card-radius)
+ background-color: var(--color-white)
+ margin: 10px
+ padding: 20px
+ width: 36%
+ height: 20vh
+ font-family: "Montserrat"
+ font-weight: 300
\ No newline at end of file
diff --git a/components/Transaction/Transaction.jsx b/components/Transaction/Transaction.jsx
new file mode 100644
index 0000000..4d2d293
--- /dev/null
+++ b/components/Transaction/Transaction.jsx
@@ -0,0 +1,27 @@
+import React from "react";
+import styles from "./Transaction.module.sass"
+
+export default function Transaction(props) {
+ // console.log(props);
+ return (
+
+ {props.date}
+ {props.type}
+ {props.payee}
+ {props.descr}
+ {props.parts &&
+ props.parts.map((part, index) => {
+ return (
+ <>
+
+ {part.extract ? : }
+
+ {part.account || " "}
+ {part.amount || " "}
+ {part.currency || " "}
+ >
+ );
+ })}
+
+ );
+}
diff --git a/components/Transaction/Transaction.module.sass b/components/Transaction/Transaction.module.sass
new file mode 100644
index 0000000..ecd6f81
--- /dev/null
+++ b/components/Transaction/Transaction.module.sass
@@ -0,0 +1,46 @@
+.transaction_card
+ border-radius: var(--card-radius)
+ background-color: var(--color-white)
+ margin: 10px
+ padding: 20px
+ width: 100%
+ font-family: "Montserrat"
+ font-weight: 300
+ display: grid
+ grid-template-columns: .5fr 2fr 1fr 3fr 3fr 3fr
+
+ p
+ margin: 0
+ margin-left: 30px
+ display: flex
+ justify-content: space-around
+ justify-items: flex-begin
+ width: calc(100% - 30px)
+
+.transaction_date,
+.transaction_payee,
+.transaction_type,
+.transaction_descr
+ margin-bottom: 10px
+ padding-bottom: 10px
+ font-size: 1.2rem
+ border-bottom: 1px solid var(--color-blue)
+
+.transaction_date
+ grid-column: 1 / span 2
+.transaction_payee
+ grid-column: 4 / span 1
+.transaction_type
+ grid-column: 3 / span 1
+.transaction_descr
+ grid-column: 5 / span 2
+
+.transaction_amount
+ grid-column: 5 / span 1
+ text-align: right
+ padding-right: 10px
+.transaction_account
+ grid-column: 2 / span 3
+.transaction_extract
+ grid_column: 1 / span 1
+ color: var(--color-blue)
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index d48237f..35fc654 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,8 +8,10 @@
"name": "bc-next",
"version": "0.1.0",
"dependencies": {
+ "chart.js": "^3.7.1",
"next": "12.1.0",
"react": "17.0.2",
+ "react-chartjs-2": "^4.0.1",
"react-dom": "17.0.2"
},
"devDependencies": {
@@ -647,6 +649,11 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/chart.js": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.7.1.tgz",
+ "integrity": "sha512-8knRegQLFnPQAheZV8MjxIXc5gQEfDFD897BJgv/klO/vtIyFFmgMXrNfgrXpbTr/XbTturxRgxIXx/Y+ASJBA=="
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2321,6 +2328,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-chartjs-2": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.0.1.tgz",
+ "integrity": "sha512-q8bgWzKoFvBvD7YcjT/hXG8jt55TaMAuJ1dmI3tKFJ7CijUWYz4pIfOhkTI6PBTwqu/pmeWsClBRd/7HiWzN1g==",
+ "peerDependencies": {
+ "chart.js": "^3.5.0",
+ "react": "^16.8.0 || ^17.0.0"
+ }
+ },
"node_modules/react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
@@ -3224,6 +3240,11 @@
"supports-color": "^7.1.0"
}
},
+ "chart.js": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.7.1.tgz",
+ "integrity": "sha512-8knRegQLFnPQAheZV8MjxIXc5gQEfDFD897BJgv/klO/vtIyFFmgMXrNfgrXpbTr/XbTturxRgxIXx/Y+ASJBA=="
+ },
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -4456,6 +4477,12 @@
"object-assign": "^4.1.1"
}
},
+ "react-chartjs-2": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.0.1.tgz",
+ "integrity": "sha512-q8bgWzKoFvBvD7YcjT/hXG8jt55TaMAuJ1dmI3tKFJ7CijUWYz4pIfOhkTI6PBTwqu/pmeWsClBRd/7HiWzN1g==",
+ "requires": {}
+ },
"react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
diff --git a/package.json b/package.json
index 7b92c5e..ada748f 100644
--- a/package.json
+++ b/package.json
@@ -9,8 +9,10 @@
"lint": "next lint"
},
"dependencies": {
+ "chart.js": "^3.7.1",
"next": "12.1.0",
"react": "17.0.2",
+ "react-chartjs-2": "^4.0.1",
"react-dom": "17.0.2"
},
"devDependencies": {
diff --git a/pages/_app.js b/pages/_app.js
index 1e1cec9..7aac241 100644
--- a/pages/_app.js
+++ b/pages/_app.js
@@ -1,7 +1,14 @@
-import '../styles/globals.css'
+import '../styles/globals.scss'
+import Script from 'next/script'
+import Head from 'next/head'
function MyApp({ Component, pageProps }) {
- return
+ return (
+ <>
+
+
+ >
+ )
}
export default MyApp
diff --git a/pages/api/hello.js b/pages/api/hello.js
index df63de8..54c840c 100644
--- a/pages/api/hello.js
+++ b/pages/api/hello.js
@@ -3,3 +3,63 @@
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' })
}
+
+export function parseBeanCount(content) {
+ // console.log(content)
+ const lines = content.split(/\r?\n/)
+
+ var statements = []
+ var statement = { parts: [], type: null, _zs: 0 }
+
+ // stats
+ var stats = {}
+ stats.time = {}
+ stats.time.interval = [null, null]
+
+ for (const [ix, line] of lines.entries()) {
+
+ if (line.match(/^\s*$/)) {
+ if (statement.type !== null) {
+ if (statement.parts.every(({amount}) => {return amount === ''})) {
+ throw(`missing transaction amount (:${ix+1})`)
+ }
+ statements.push(statement)
+ statement = { parts: [], type: null, _zs: 0 }
+ }
+ } else if (statement.type === null) {
+ try {
+ const { date, type, payee, descr } = line.match(/(?\d{4}-\d{2}-\d{2}) (?(txn|\!|\*)) ("|')(?[\w\s\d]*)("|') ("|')(?[\w\d\s]*)("|')/).groups
+ statement.type = type
+ statement.date = date
+ statement.payee = payee
+ statement.descr = descr;
+
+ const d = new Date(date)
+ if (d && stats.time.interval[0] && stats.time.interval) {
+ stats.time.interval = [(d < stats.time.interval[0]) ? d :stats.time.interval[0], (d > stats.time.interval[1]) ? d : stats.time.interval[1]]
+ } else if (d) {
+ stats.time.interval = [d, d]
+ }
+ } catch (error) {
+ console.error(`could not parse an operation (line ${ix + 1})`)
+ throw(`Could not parse beancount file (:${ix+1}, ${error}])`)
+ }
+ } else {
+ const groups = line.match(/\s+(?[:\w\d]+)(\s+(?[-.\d]+)\s+(?\w+))?/).groups
+ const amount = parseFloat(groups.amount) || null
+ const currency = groups.currency || null
+ console.log(amount, statement._zs)
+ const extract = groups.amount < 0
+ statement.parts.push({
+ ...groups, amount, extract, currency
+ })
+ statement._zs += amount
+ }
+ }
+
+
+ stats.time.range = (stats.time.interval[1] - stats.time.interval[0]) / 1000 / 60 / 60 / 24 // now in days
+ stats.time.interval = [stats.time.interval[0].toISOString().slice(0,10), stats.time.interval[1].toISOString().slice(0,10)]
+
+ return { statements, stats }
+}
\ No newline at end of file
diff --git a/pages/index.js b/pages/index.js
index dc4b640..195f759 100644
--- a/pages/index.js
+++ b/pages/index.js
@@ -1,69 +1,47 @@
-import Head from 'next/head'
-import Image from 'next/image'
-import styles from '../styles/Home.module.css'
+import Head from "next/head";
+import Image from "next/image";
+import styles from "../styles/Home.module.css";
+import { parseBeanCount } from "./api/hello";
+import Transaction from "../components/Transaction/Transaction";
+import Graph from "../components/Graph/Graph";
+import Overview from "../components/Overview/Overview";
-export default function Home() {
+export default function Home({statements, stats}) {
return (
-
Create Next App
-
+
+
-
-
-
- Get started by editing{' '}
- pages/index.js
-
-
+
-
- )
+ );
}
+
+
+export async function getStaticProps() {
+ const { statements, stats } = parseBeanCount(`
+ 2014-05-05 txn 'Cafe Mogador' 'Lamb tagine with wine'
+ Liabilities:CreditCard:CapitalOne -37.5 USD
+ Expenses:Restaurant
+
+ 2013-06-04 * 'Cafe A' 'hi'
+ Liabilities:CapitalOne -37.45 USD
+ Expenses:Restaurant
+ `)
+ return {
+ props: { statements, stats }
+ }
+}
\ No newline at end of file
diff --git a/styles/Home.module.css b/styles/Home.module.css
index 32a57d5..dceac79 100644
--- a/styles/Home.module.css
+++ b/styles/Home.module.css
@@ -70,7 +70,8 @@
align-items: center;
justify-content: center;
flex-wrap: wrap;
- max-width: 800px;
+ width: 80vw;
+ max-width: 1000px;
}
.card {
diff --git a/styles/globals.css b/styles/globals.css
deleted file mode 100644
index e5e2dcc..0000000
--- a/styles/globals.css
+++ /dev/null
@@ -1,16 +0,0 @@
-html,
-body {
- padding: 0;
- margin: 0;
- font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
- Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
-}
-
-a {
- color: inherit;
- text-decoration: none;
-}
-
-* {
- box-sizing: border-box;
-}
diff --git a/styles/globals.scss b/styles/globals.scss
new file mode 100644
index 0000000..77f1526
--- /dev/null
+++ b/styles/globals.scss
@@ -0,0 +1,26 @@
+
+html,
+body {
+ padding: 0;
+ margin: 0;
+ font-family: "Font Awesome 6", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
+ Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+ background-color: var(--color-gray)
+}
+
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+* {
+ box-sizing: border-box;
+ --color-white: #ffffff;
+ --color-gray: #f0f0f0;
+ --color-blue: #b4dadb;
+ --color-accent: #457b9d;
+ --color-warn: #a31420;
+ --color-dark: #1d3557;
+
+ --card-radius: 10px;
+}