summaryrefslogtreecommitdiff
path: root/gui/src
diff options
context:
space:
mode:
Diffstat (limited to 'gui/src')
-rw-r--r--gui/src/ajax/backend.ts66
-rw-r--r--gui/src/components/BudgetUpdatesTable/BudgetUpdatesTable.css1
-rw-r--r--gui/src/components/BudgetUpdatesTable/BudgetUpdatesTable.tsx36
-rw-r--r--gui/src/components/histogram/Histogram.scss84
-rw-r--r--gui/src/components/histogram/Histogram.tsx80
-rw-r--r--gui/src/index.css72
-rw-r--r--gui/src/index.tsx9
-rw-r--r--gui/src/types/BalanceToBudgetRatio.spec.ts307
-rw-r--r--gui/src/types/account.ts33
-rw-r--r--gui/src/types/balanceMap.ts17
-rw-r--r--gui/src/types/balanceToBudgetRatio.ts83
-rw-r--r--gui/src/types/budget.ts12
-rw-r--r--gui/src/types/budgetMap.ts16
-rw-r--r--gui/src/types/budgetUpdate.ts31
-rw-r--r--gui/src/types/category.ts48
-rw-r--r--gui/src/types/currency/currency.ts6
-rw-r--r--gui/src/types/currency/gbp.ts54
-rw-r--r--gui/src/types/group.ts8
-rw-r--r--gui/src/util/groupCategories.ts12
-rw-r--r--gui/src/views/App.css5
-rw-r--r--gui/src/views/App.tsx19
-rw-r--r--gui/src/views/budgetUpdatesTable/BudgetUpdatesTableView.css10
-rw-r--r--gui/src/views/budgetUpdatesTable/BudgetUpdatesTableView.tsx32
-rw-r--r--gui/src/views/histogram/HistogramView.tsx31
-rw-r--r--gui/src/vite-env.d.ts1
25 files changed, 0 insertions, 1073 deletions
diff --git a/gui/src/ajax/backend.ts b/gui/src/ajax/backend.ts
deleted file mode 100644
index 4a2462f..0000000
--- a/gui/src/ajax/backend.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { BalanceMap, newCategoryBalanceMapFromDto } from "../types/balanceMap";
-import { BudgetMap, newBudgetMapFromDto } from "../types/budgetMap";
-import { BudgetUpdate, newBudgetUpdateFromDto } from "../types/budgetUpdate";
-import { Category, newCategoryFromDto } from "../types/category";
-
-const backendUrl = "http://localhost:8000";
-
-function makeGetter<T>(params: FetcherMakerParams<T>): Getter<T> {
- const parser =
- "parser" in params
- ? params.parser
- : (dto: unknown[]) => <T>dto.map(params.elementParser);
-
- return async function () {
- const response = await fetch(`${backendUrl}${params.path}`);
- const dto = await response.json();
-
- try {
- return parser(dto);
- } catch (err) {
- console.error(`Error parsing DTO. ${err?.toString() ?? ""}`);
- return null;
- }
- };
-}
-
-export const getAllBudgetUpdates: Getter<BudgetUpdate[]> = makeGetter<
- BudgetUpdate[]
->({
- path: "/budget-update",
- elementParser: newBudgetUpdateFromDto,
-});
-
-export const getAllCategories: Getter<Category[]> = async () => {
- const balances = await getAllCategoryBalances();
- const budgets = await getAllCurrentCategoryBudgets();
- return makeGetter<Category[]>({
- path: "/category",
- elementParser: newCategoryFromDto(balances, budgets),
- })();
-};
-
-export const getAllCategoryBalances: Getter<BalanceMap<Category>> = makeGetter<
- BalanceMap<Category>
->({
- path: "/balance/category",
- parser: newCategoryBalanceMapFromDto,
-});
-
-export const getAllCurrentCategoryBudgets: Getter<BudgetMap<Category>> =
- makeGetter<BudgetMap<Category>>({
- path: "/budget/category?current",
- parser: newBudgetMapFromDto,
- });
-
-type FetcherMakerParams<T> = {
- path: string;
-} & AnyParser<T>;
-
-type Parser<T> = { parser: (dto: unknown) => T | null };
-type ElementParser<T> = { elementParser: (dto: unknown) => T | null };
-type AnyParser<T> = T extends any[]
- ? Parser<T> | ElementParser<T[number]>
- : Parser<T>;
-
-type Getter<T> = () => Promise<T | null>;
diff --git a/gui/src/components/BudgetUpdatesTable/BudgetUpdatesTable.css b/gui/src/components/BudgetUpdatesTable/BudgetUpdatesTable.css
deleted file mode 100644
index 8b13789..0000000
--- a/gui/src/components/BudgetUpdatesTable/BudgetUpdatesTable.css
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/gui/src/components/BudgetUpdatesTable/BudgetUpdatesTable.tsx b/gui/src/components/BudgetUpdatesTable/BudgetUpdatesTable.tsx
deleted file mode 100644
index 479d69f..0000000
--- a/gui/src/components/BudgetUpdatesTable/BudgetUpdatesTable.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { BudgetUpdate } from '../../types/budgetUpdate';
-import './BudgetUpdatesTable.css';
-
-function BudgetUpdatesTable({ budgetUpdates }: Props) {
- return (
- <table>
- <thead>
- <tr>
- <td>Date</td>
- <td>Quantity</td>
- <td>Period</td>
- </tr>
- </thead>
-
- <tbody>
- { budgetUpdates.map(Row) }
- </tbody>
- </table>
- );
-}
-
-function Row(budgetUpdate: BudgetUpdate) {
- return (
- <tr>
- <td>{budgetUpdate.date.format("DD Mm YYYY")}</td>
- <td>{budgetUpdate.newQuantity.toString()}</td>
- <td>{budgetUpdate.newPeriod}</td>
- </tr>
- )
-}
-
-type Props = {
- budgetUpdates: BudgetUpdate[];
-}
-
-export { BudgetUpdatesTable };
diff --git a/gui/src/components/histogram/Histogram.scss b/gui/src/components/histogram/Histogram.scss
deleted file mode 100644
index e3f4604..0000000
--- a/gui/src/components/histogram/Histogram.scss
+++ /dev/null
@@ -1,84 +0,0 @@
-table {
- border-collapse: collapse;
-}
-
-:is(thead, .ratio__text) {
- opacity: 0;
- height: 0;
- width: 0;
-}
-
-tr {
- display: grid;
- gap: 0.3rem;
- grid-template-columns: 10rem 1fr min-content;
- justify-content: start;
-}
-
-td.name {
- text-align: right;
-}
-
-td.ratio {
- width: 10rem;
- display: grid;
- grid-template-columns: 5rem 5rem;
- grid-template-rows: 1fr;
- align-items: center;
-
- &::before {
- content: '';
- display: block;
- height: 100%;
- grid-row: 1 / 2;
- z-index: -1;
- }
-
- &:is(.ratio--save, .ratio--spend)::before {
- grid-column: 1 / 2;
- border-right: 1px dashed white;
- position: relative;
- right: -1px;
- }
-
- &.ratio--arrears::before {
- grid-column: 2 / 3;
- border-left: 1px dashed white;
- left: -1px;
- }
-}
-
-.ratio__bar {
- display: block;
- width: calc(3rem * var(--normal-abs-ratio) + 1px);
- height: 33%;
- background-color: var(--category-highlight-color);
- grid-row: 1 / 2;
-
- :is(.ratio--save, .ratio--spend) & {
- grid-column: 2 / 3;
- }
-
- .ratio--arrears & {
- grid-column: 1 / 2;
- position: relative;
- right: -1px;
- justify-self: end;
- }
-}
-
-td.balance {
- color: var(--category-highlight-color);
-}
-
-.ratio--arrears {
- --category-highlight-color: crimson;
-}
-
-.ratio--save {
- --category-highlight-color: white;
-}
-
-.ratio--spend {
- --category-highlight-color: dodgerblue;
-}
diff --git a/gui/src/components/histogram/Histogram.tsx b/gui/src/components/histogram/Histogram.tsx
deleted file mode 100644
index 1dd8c65..0000000
--- a/gui/src/components/histogram/Histogram.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import { BalanceToBudgetRatio } from "../../types/balanceToBudgetRatio";
-import { Category } from "../../types/category";
-import { Group } from "../../types/group";
-import { groupCategories } from "../../util/groupCategories";
-import './Histogram.scss';
-
-function Histogram({ categories }: Props) {
- if (categories === null) {
- return (
- <p>No categories to show</p>
- );
- }
-
- const balanceToBudgetRatioNormaliser = BalanceToBudgetRatio.makeNormaliser(...categories.map(c => c.balanceToBudgetRatio));
- for (const category of categories) {
- category.balanceToBudgetRatio = balanceToBudgetRatioNormaliser(category.balanceToBudgetRatio);
- }
-
- const groups = [...groupCategories(categories)];
-
- return (
- <table>
- <thead>
- <tr>
- <th>Category</th>
- <th>Balance-to-Budget Ratio</th>
- <th>Balance</th>
- </tr>
- </thead>
- {
- groups.map(g => <RowGroup group={g} />)
- }
- </table>
- );
-}
-
-function RowGroup({ group }: GroupProps) {
- return (
- <tbody>
- <tr role="heading" aria-level="3">{group.name}</tr>
- {
- group.categories
- .sort((c1, c2) => BalanceToBudgetRatio.compare(c1.balanceToBudgetRatio, c2.balanceToBudgetRatio))
- .map((category) => Row({ category }))
- }
- </tbody>
- );
-}
-
-function Row({ category }: RowProps) {
- return (
- <tr>
- <td class="name">{category.name}</td>
- <td
- class={`ratio ratio--${category.balanceToBudgetRatio?.recommendedAction ?? ''}`}
- style={`--normal-abs-ratio: ${Math.abs(category.balanceToBudgetRatio?.normalisedRatio ?? 0)};`}
- >
- <span class="ratio__text">
- {category.balanceToBudgetRatio?.toString() ?? ''}
- </span>
- <span class="ratio__bar"></span>
- </td>
- <td class="balance">{category.currentBalance.toString()}</td>
- </tr>
- );
-}
-
-type Props = {
- categories: Category[] | null,
-};
-
-type GroupProps = {
- group: Group;
-}
-
-type RowProps = {
- category: Category,
-};
-
-export default Histogram;
diff --git a/gui/src/index.css b/gui/src/index.css
deleted file mode 100644
index 075ca0d..0000000
--- a/gui/src/index.css
+++ /dev/null
@@ -1,72 +0,0 @@
-:root {
- font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
- line-height: 1.5;
- font-weight: 400;
-
- color-scheme: light dark;
- color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
-
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-a {
- font-weight: 500;
- color: #646cff;
- text-decoration: inherit;
-}
-a:hover {
- color: #535bf2;
-}
-
-body {
- display: flex;
- place-items: center;
- min-width: 320px;
- min-height: 100vh;
-}
-
-h1 {
- font-size: 3.2em;
- line-height: 1.1;
-}
-
-button {
- border-radius: 8px;
- border: 1px solid transparent;
- padding: 0.6em 1.2em;
- font-size: 1em;
- font-weight: 500;
- font-family: inherit;
- background-color: #1a1a1a;
- cursor: pointer;
- transition: border-color 0.25s;
-}
-button:hover {
- border-color: #646cff;
-}
-button:focus,
-button:focus-visible {
- outline: 4px auto -webkit-focus-ring-color;
-}
-
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
- }
- a:hover {
- color: #747bff;
- }
- button {
- background-color: #f9f9f9;
- }
-}
-
-* {
- margin: 0;
- padding: 0;
-}
diff --git a/gui/src/index.tsx b/gui/src/index.tsx
deleted file mode 100644
index d2f18a0..0000000
--- a/gui/src/index.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-/* @refresh reload */
-import { render } from 'solid-js/web'
-
-import './index.css'
-import App from './views/App'
-
-const root = document.getElementById('root')
-
-render(() => <App />, root!)
diff --git a/gui/src/types/BalanceToBudgetRatio.spec.ts b/gui/src/types/BalanceToBudgetRatio.spec.ts
deleted file mode 100644
index 3a956ea..0000000
--- a/gui/src/types/BalanceToBudgetRatio.spec.ts
+++ /dev/null
@@ -1,307 +0,0 @@
-import { describe, expect, it } from "vitest";
-import { BalanceToBudgetRatio } from "./balanceToBudgetRatio";
-import { Gbp } from "./currency/gbp";
-
-describe("balanceToBudgetRatio", () => {
- it("new() returns null when budget is null", () => {
- const result = BalanceToBudgetRatio.new(new Gbp(1, 0, 1), null);
- expect(result).toBeNull();
- });
-
- it("toString() rounds to 2 decimal places", () => {
- const mockCurrencyPi = { valueOf: () => Math.PI };
- const mockCurrencyUnit = { valueOf: () => 1 };
- const sut = BalanceToBudgetRatio.new(mockCurrencyPi, {
- quantity: mockCurrencyUnit,
- period: 42,
- });
-
- const result = sut?.toString();
-
- expect(result).toBe("3.14");
- });
-
- it("new() sets normalisedRatio to 1 when balance and budget are equal and positive", () => {
- const twoFifty = new Gbp(2, 50, 1);
- const result = BalanceToBudgetRatio.new(twoFifty, {
- quantity: twoFifty,
- period: 42,
- });
- expect(result?.normalisedRatio).toBeCloseTo(1.0);
- });
-
- it("new() sets normalisedRatio to 2 when balance is twice budget", () => {
- const twoFifty = new Gbp(2, 50, 1);
- const oneTwentyFive = new Gbp(1, 25, 1);
- const result = BalanceToBudgetRatio.new(twoFifty, {
- quantity: oneTwentyFive,
- period: 42,
- });
- expect(result?.normalisedRatio).toBeCloseTo(2.0);
- });
-
- it("new() sets normalisedRatio to -2 when a negative balance is -1 * twice budget", () => {
- const minusTwoFifty = new Gbp(2, 50, -1);
- const oneTwentyFive = new Gbp(1, 25, 1);
- const result = BalanceToBudgetRatio.new(minusTwoFifty, {
- quantity: oneTwentyFive,
- period: 42,
- });
- expect(result?.normalisedRatio).toBeCloseTo(-2.0);
- });
-
- it("new() sets normalisedRatio to -0.5 when balance is -1 * half a negative budget", () => {
- const oneTwentyFive = new Gbp(1, 25, 1);
- const minusTwoFifty = new Gbp(2, 50, -1);
- const result = BalanceToBudgetRatio.new(oneTwentyFive, {
- quantity: minusTwoFifty,
- period: 42,
- });
- expect(result?.normalisedRatio).toBeCloseTo(-0.5);
- });
-
- it("new() sets normalisedRatio to 1 when balance and budget are equal and negative", () => {
- const minusTwoFifty = new Gbp(2, 50, -1);
- const result = BalanceToBudgetRatio.new(minusTwoFifty, {
- quantity: minusTwoFifty,
- period: 42,
- });
- expect(result?.normalisedRatio).toBeCloseTo(1.0);
- });
-
- it('new() sets recommended action to "arrears" when ratio is less than 0', () => {
- const oneTwentyFive = new Gbp(1, 25, 1);
- const minusTwoFifty = new Gbp(2, 50, -1);
-
- const result = BalanceToBudgetRatio.new(oneTwentyFive, {
- quantity: minusTwoFifty,
- period: 42,
- });
-
- expect(result?.recommendedAction).toBe("arrears");
- });
-
- it('new() sets recommended action to "save" when ratio is between 0 and 1', () => {
- const oneTwentyFive = new Gbp(1, 25, 1);
- const twoFifty = new Gbp(2, 50, 1);
-
- const result = BalanceToBudgetRatio.new(oneTwentyFive, {
- quantity: twoFifty,
- period: 42,
- });
-
- expect(result?.recommendedAction).toBe("save");
- });
-
- it('new() sets recommended action to "spend" when ratio is larger than 1', () => {
- const oneTwentyFive = new Gbp(1, 25, 1);
- const twoFifty = new Gbp(2, 50, 1);
-
- const result = BalanceToBudgetRatio.new(twoFifty, {
- quantity: oneTwentyFive,
- period: 42,
- });
-
- expect(result?.recommendedAction).toBe("spend");
- });
-
- it("normalises to a factor of 2 when all ratios are positive", () => {
- const oneTwentyFive = new Gbp(1, 25, 1);
- const twoFifty = new Gbp(2, 50, 1);
- const twoOverOne = BalanceToBudgetRatio.new(twoFifty, {
- quantity: oneTwentyFive,
- period: 42,
- });
- const twoOverTwo = BalanceToBudgetRatio.new(twoFifty, {
- quantity: twoFifty,
- period: 42,
- });
- const oneOverOne = BalanceToBudgetRatio.new(oneTwentyFive, {
- quantity: oneTwentyFive,
- period: 42,
- });
- const oneOverTwo = BalanceToBudgetRatio.new(oneTwentyFive, {
- quantity: twoFifty,
- period: 42,
- });
-
- const normaliser = BalanceToBudgetRatio.makeNormaliser(
- twoOverOne,
- twoOverTwo,
- oneOverOne,
- oneOverTwo,
- );
-
- expect(normaliser(twoOverOne).normalisedRatio).toBeCloseTo(1.0);
- expect(normaliser(twoOverTwo).normalisedRatio).toBeCloseTo(0.5);
- expect(normaliser(oneOverOne).normalisedRatio).toBeCloseTo(0.5);
- expect(normaliser(oneOverTwo).normalisedRatio).toBeCloseTo(0.25);
- });
-
- it("normalises to a factor of 2 when all ratios are negative", () => {
- const oneTwentyFive = new Gbp(1, 25, 1);
- const twoFifty = new Gbp(2, 50, 1);
- const minusOneTwentyFive = new Gbp(1, 25, -1);
- const minusTwoFifty = new Gbp(2, 50, -1);
- const minusTwoOverOne = BalanceToBudgetRatio.new(minusTwoFifty, {
- quantity: oneTwentyFive,
- period: 42,
- });
- const minusTwoOverTwo = BalanceToBudgetRatio.new(minusTwoFifty, {
- quantity: twoFifty,
- period: 42,
- });
- const minusOneOverOne = BalanceToBudgetRatio.new(minusOneTwentyFive, {
- quantity: oneTwentyFive,
- period: 42,
- });
- const minusOneOverTwo = BalanceToBudgetRatio.new(minusOneTwentyFive, {
- quantity: twoFifty,
- period: 42,
- });
-
- const normaliser = BalanceToBudgetRatio.makeNormaliser(
- minusTwoOverOne,
- minusTwoOverTwo,
- minusOneOverOne,
- minusOneOverTwo,
- );
-
- expect(normaliser(minusTwoOverOne).normalisedRatio).toBeCloseTo(-1.0);
- expect(normaliser(minusTwoOverTwo).normalisedRatio).toBeCloseTo(-0.5);
- expect(normaliser(minusOneOverOne).normalisedRatio).toBeCloseTo(-0.5);
- expect(normaliser(minusOneOverTwo).normalisedRatio).toBeCloseTo(-0.25);
- });
-
- it("normalises to a factor of 2 when ratios are a mix of positive and negative", () => {
- const oneTwentyFive = new Gbp(1, 25, 1);
- const twoFifty = new Gbp(2, 50, 1);
- const minusOneTwentyFive = new Gbp(1, 25, -1);
- const minusTwoFifty = new Gbp(2, 50, -1);
- const twoOverOne = BalanceToBudgetRatio.new(twoFifty, {
- quantity: oneTwentyFive,
- period: 42,
- });
- const twoOverTwo = BalanceToBudgetRatio.new(twoFifty, {
- quantity: twoFifty,
- period: 42,
- });
- const oneOverOne = BalanceToBudgetRatio.new(oneTwentyFive, {
- quantity: oneTwentyFive,
- period: 42,
- });
- const oneOverTwo = BalanceToBudgetRatio.new(oneTwentyFive, {
- quantity: twoFifty,
- period: 42,
- });
- const minusTwoOverOne = BalanceToBudgetRatio.new(minusTwoFifty, {
- quantity: oneTwentyFive,
- period: 42,
- });
- const minusTwoOverTwo = BalanceToBudgetRatio.new(minusTwoFifty, {
- quantity: twoFifty,
- period: 42,
- });
- const minusOneOverOne = BalanceToBudgetRatio.new(minusOneTwentyFive, {
- quantity: oneTwentyFive,
- period: 42,
- });
- const minusOneOverTwo = BalanceToBudgetRatio.new(minusOneTwentyFive, {
- quantity: twoFifty,
- period: 42,
- });
-
- const normaliser = BalanceToBudgetRatio.makeNormaliser(
- twoOverOne,
- twoOverTwo,
- oneOverOne,
- oneOverTwo,
- minusTwoOverOne,
- minusTwoOverTwo,
- minusOneOverOne,
- minusOneOverTwo,
- );
-
- expect(normaliser(twoOverOne).normalisedRatio).toBeCloseTo(1.0);
- expect(normaliser(twoOverTwo).normalisedRatio).toBeCloseTo(0.5);
- expect(normaliser(oneOverOne).normalisedRatio).toBeCloseTo(0.5);
- expect(normaliser(oneOverTwo).normalisedRatio).toBeCloseTo(0.25);
- expect(normaliser(minusTwoOverOne).normalisedRatio).toBeCloseTo(-1.0);
- expect(normaliser(minusTwoOverTwo).normalisedRatio).toBeCloseTo(-0.5);
- expect(normaliser(minusOneOverOne).normalisedRatio).toBeCloseTo(-0.5);
- expect(normaliser(minusOneOverTwo).normalisedRatio).toBeCloseTo(-0.25);
- });
-
- it("sorts large positive ratios before small positive ratios", () => {
- const oneTwentyFive = new Gbp(1, 25, 1);
- const twoFifty = new Gbp(2, 50, 1);
- const twoOverTwo = BalanceToBudgetRatio.new(twoFifty, {
- quantity: twoFifty,
- period: 42,
- });
- const oneOverTwo = BalanceToBudgetRatio.new(oneTwentyFive, {
- quantity: twoFifty,
- period: 42,
- });
-
- const resultLeftways = BalanceToBudgetRatio.compare(twoOverTwo, oneOverTwo);
- const resultRightways = BalanceToBudgetRatio.compare(
- oneOverTwo,
- twoOverTwo,
- );
-
- expect(resultLeftways).toBeCloseTo(-resultRightways);
- expect(resultLeftways).toBeLessThan(0);
- });
-
- it("sorts small negative ratios before large negative ratios", () => {
- const twoFifty = new Gbp(2, 50, 1);
- const minusOneTwentyFive = new Gbp(1, 25, -1);
- const minusTwoFifty = new Gbp(2, 50, -1);
- const minusTwoOverTwo = BalanceToBudgetRatio.new(minusTwoFifty, {
- quantity: twoFifty,
- period: 42,
- });
- const minusOneOverTwo = BalanceToBudgetRatio.new(minusOneTwentyFive, {
- quantity: twoFifty,
- period: 42,
- });
-
- const resultLeftways = BalanceToBudgetRatio.compare(
- minusOneOverTwo,
- minusTwoOverTwo,
- );
- const resultRightways = BalanceToBudgetRatio.compare(
- minusTwoOverTwo,
- minusOneOverTwo,
- );
-
- expect(resultLeftways).toBeCloseTo(-resultRightways);
- expect(resultLeftways).toBeLessThan(0);
- });
-
- it("sorts positive ratios before negative ratios", () => {
- const twoFifty = new Gbp(2, 50, 1);
- const minusTwoFifty = new Gbp(2, 50, -1);
- const twoOverTwo = BalanceToBudgetRatio.new(twoFifty, {
- quantity: twoFifty,
- period: 42,
- });
- const minusTwoOverTwo = BalanceToBudgetRatio.new(minusTwoFifty, {
- quantity: twoFifty,
- period: 42,
- });
-
- const resultLeftways = BalanceToBudgetRatio.compare(
- twoOverTwo,
- minusTwoOverTwo,
- );
- const resultRightways = BalanceToBudgetRatio.compare(
- minusTwoOverTwo,
- twoOverTwo,
- );
-
- expect(resultLeftways).toBeCloseTo(-resultRightways);
- expect(resultLeftways).toBeLessThan(0);
- });
-});
diff --git a/gui/src/types/account.ts b/gui/src/types/account.ts
deleted file mode 100644
index a463304..0000000
--- a/gui/src/types/account.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import moment, { Moment } from "moment";
-import Currency from "./currency/currency";
-import Gbp from "./currency/gbp";
-
-function newAccountFromDto(dto: unknown): Account {
- const dtoDangerous = dto as AccountDto;
- return {
- id: dtoDangerous.id,
- name: dtoDangerous.name,
- openingBalance: new Gbp(
- Math.floor(Math.abs(dtoDangerous.opening_balance) / 100),
- Math.abs(dtoDangerous.opening_balance) % 100,
- dtoDangerous.opening_balance < 0 ? -1 : 1,
- ),
- openingDate: moment(dtoDangerous.opening_date),
- };
-}
-
-interface AccountDto {
- readonly id: number;
- readonly name: string;
- readonly opening_balance: number;
- readonly opening_date: string;
-}
-
-interface Account {
- readonly id: number;
- readonly name: string;
- readonly openingBalance: Currency;
- readonly openingDate: Moment;
-}
-
-export { type Account, newAccountFromDto };
diff --git a/gui/src/types/balanceMap.ts b/gui/src/types/balanceMap.ts
deleted file mode 100644
index 760843f..0000000
--- a/gui/src/types/balanceMap.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Category } from "./category";
-import Currency from "./currency/currency";
-import { newGbpFromDto } from "./currency/gbp";
-
-type BalanceMap<T extends { id: any }> = {
- [id in T["id"]]: Currency;
-};
-
-function newCategoryBalanceMapFromDto(dto: unknown): BalanceMap<Category> {
- return Object.fromEntries(
- (dto as { category_id: Category["id"]; balance: number }[]).map(
- ({ category_id, balance }) => [category_id, newGbpFromDto(balance)],
- ),
- );
-}
-
-export { type BalanceMap, newCategoryBalanceMapFromDto };
diff --git a/gui/src/types/balanceToBudgetRatio.ts b/gui/src/types/balanceToBudgetRatio.ts
deleted file mode 100644
index 2f8000c..0000000
--- a/gui/src/types/balanceToBudgetRatio.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { Budget } from "./budget";
-import Currency from "./currency/currency";
-
-class BalanceToBudgetRatio {
- toString(): string {
- return this._ratio?.toFixed(2) ?? "";
- }
-
- public readonly recommendedAction: "arrears" | "save" | "spend" | null;
- public readonly normalisedRatio: number | null;
-
- private constructor(
- private readonly _ratio: number | null,
- private readonly _normalisationFactor: number,
- ) {
- if (this._ratio === null) {
- this.normalisedRatio = null;
- this.recommendedAction = null;
- return;
- }
-
- this.normalisedRatio = this._ratio * this._normalisationFactor;
-
- if (this._ratio < 0) {
- this.recommendedAction = "arrears";
- } else if (this._ratio >= 1) {
- this.recommendedAction = "spend";
- } else {
- this.recommendedAction = "save";
- }
- }
-
- static new(balance: Currency, budget: Budget | null) {
- if (budget === null || budget.quantity === 0 || budget.quantity === -0) {
- return null;
- }
-
- return new BalanceToBudgetRatio(
- balance.valueOf() / budget.quantity.valueOf(),
- 1,
- );
- }
-
- static makeNormaliser(
- ...balanceToBudgetRatios: (BalanceToBudgetRatio | null)[]
- ): (r: BalanceToBudgetRatio | null) => BalanceToBudgetRatio {
- const validRatios = balanceToBudgetRatios.filter(
- (r) =>
- r &&
- r._ratio &&
- !isNaN(r._ratio) &&
- Infinity > r._ratio &&
- -Infinity < r._ratio,
- );
-
- const maxAbsRatio = Math.max(
- ...validRatios.map((r) => Math.abs(r?._ratio ?? 0)),
- );
-
- const normalisationFactor = 1 / maxAbsRatio;
- return (r) =>
- new BalanceToBudgetRatio(r?._ratio ?? null, normalisationFactor);
- }
-
- static compare(
- lhs: BalanceToBudgetRatio | null,
- rhs: BalanceToBudgetRatio | null,
- ): number {
- if (lhs === null || lhs._ratio === null) {
- if (rhs === null || rhs._ratio === null) {
- return 0;
- }
- return 1;
- }
- if (rhs === null || rhs._ratio === null) {
- return -1;
- }
-
- return rhs._ratio - lhs._ratio;
- }
-}
-
-export { BalanceToBudgetRatio };
diff --git a/gui/src/types/budget.ts b/gui/src/types/budget.ts
deleted file mode 100644
index 5a19a1b..0000000
--- a/gui/src/types/budget.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import Currency from "./currency/currency";
-
-interface Budget {
- quantity: Currency;
- period: number;
-}
-
-function newBudgetFromDto(dto: unknown) {
- return dto as Budget;
-}
-
-export { type Budget, newBudgetFromDto };
diff --git a/gui/src/types/budgetMap.ts b/gui/src/types/budgetMap.ts
deleted file mode 100644
index 5fa09f2..0000000
--- a/gui/src/types/budgetMap.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Budget, newBudgetFromDto } from "./budget";
-import { Category } from "./category";
-
-type BudgetMap<T extends { id: any }> = {
- [id in T["id"]]: Budget | null;
-};
-
-function newCategoryBudgetMapFromDto(dto: unknown): BudgetMap<Category> {
- return Object.fromEntries(
- (dto as { category_id: number; budget: unknown }[]).map(
- ({ category_id, budget }) => [category_id, newBudgetFromDto(budget)],
- ),
- );
-}
-
-export { type BudgetMap, newCategoryBudgetMapFromDto as newBudgetMapFromDto };
diff --git a/gui/src/types/budgetUpdate.ts b/gui/src/types/budgetUpdate.ts
deleted file mode 100644
index 0696f8a..0000000
--- a/gui/src/types/budgetUpdate.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import moment, { Moment } from "moment";
-import Currency from "./currency/currency";
-
-interface BudgetUpdate {
- id: number;
- categoryId: number;
- date: Moment;
- newQuantity: Currency;
- newPeriod: number;
-}
-
-interface BudgetUpdateDto {
- id: number;
- category_id: number;
- date: string;
- new_budget: number;
- new_period: number;
-}
-
-function newBudgetUpdateFromDto(dto: unknown): BudgetUpdate {
- const typedDto = dto as BudgetUpdateDto;
- return {
- id: typedDto.id,
- categoryId: typedDto.category_id,
- date: moment(typedDto.date),
- newQuantity: typedDto.new_budget,
- newPeriod: typedDto.new_period,
- };
-}
-
-export { type BudgetUpdate, type BudgetUpdateDto, newBudgetUpdateFromDto };
diff --git a/gui/src/types/category.ts b/gui/src/types/category.ts
deleted file mode 100644
index 9c2a651..0000000
--- a/gui/src/types/category.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { BalanceMap } from "./balanceMap";
-import { BalanceToBudgetRatio } from "./balanceToBudgetRatio";
-import { Budget } from "./budget";
-import { BudgetMap } from "./budgetMap";
-import Currency from "./currency/currency";
-
-interface Category {
- id: number;
- name: string;
- groupName: string;
- currentBalance: Currency;
- currentBudget: Budget | null;
- balanceToBudgetRatio: BalanceToBudgetRatio | null;
-}
-
-interface CategoryDto {
- id: number;
- name: string;
- group: string;
-}
-
-function newCategoryFromDto(
- balances: BalanceMap<Category> | null,
- budgets: BudgetMap<Category> | null,
-): (dto: unknown) => Category | null {
- if (balances === null || budgets === null) {
- return () => null;
- }
-
- return function (dto: unknown): Category {
- const dtoDangerous = dto as CategoryDto;
- const currentBalance = balances[dtoDangerous.id];
- const currentBudget = budgets[dtoDangerous.id];
- return {
- id: dtoDangerous.id,
- name: dtoDangerous.name,
- groupName: dtoDangerous.group,
- currentBalance,
- currentBudget,
- balanceToBudgetRatio: BalanceToBudgetRatio.new(
- currentBalance,
- currentBudget,
- ),
- };
- };
-}
-
-export { type Category, newCategoryFromDto };
diff --git a/gui/src/types/currency/currency.ts b/gui/src/types/currency/currency.ts
deleted file mode 100644
index 7c15324..0000000
--- a/gui/src/types/currency/currency.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-interface Currency {
- valueOf(): number;
- toString(): string;
-}
-
-export default Currency;
diff --git a/gui/src/types/currency/gbp.ts b/gui/src/types/currency/gbp.ts
deleted file mode 100644
index b88aae3..0000000
--- a/gui/src/types/currency/gbp.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import Currency from "./currency";
-
-class Gbp implements Currency {
- private readonly sign: 1 | -1;
- private readonly pounds: number;
- private readonly pence: number;
-
- constructor(pounds: number, pence: number, sign?: 1 | -1) {
- sign = sign ?? 1;
-
- if (Math.round(pounds) !== pounds) {
- throw new Error(
- `Cannot construct GBP with non-whole number of pounds: ${pounds}`,
- );
- }
- if (Math.round(pence) !== pence) {
- throw new Error(
- `Cannot construct GBP with non-whole number of pence: ${pence}`,
- );
- }
- if (pounds < 0) {
- throw new Error(
- `Cannot construct GBP with negative number of pounds: ${pounds}`,
- );
- }
- if (pence < 0) {
- throw new Error(
- `Cannot construct GBP with negative number of pounds: ${pounds}`,
- );
- }
-
- this.sign = sign;
- this.pounds = pounds;
- this.pence = pence;
- }
-
- valueOf(): number {
- return this.sign * (this.pounds * 100 + this.pence);
- }
-
- toString(): string {
- return `£${this.pounds}·${this.pence < 10 ? `0${this.pence}` : this.pence}`;
- }
-}
-
-function newGbpFromDto(dto: unknown): Gbp {
- const signedPence = dto as number;
- const pounds = Math.floor(Math.abs(signedPence) / 100);
- const pence = Math.abs(signedPence) % 100;
- const sign = signedPence < 0 ? -1 : 1;
- return new Gbp(pounds, pence, sign);
-}
-
-export { Gbp, newGbpFromDto };
diff --git a/gui/src/types/group.ts b/gui/src/types/group.ts
deleted file mode 100644
index 4a04019..0000000
--- a/gui/src/types/group.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { Category } from "./category";
-
-interface Group {
- name: string;
- categories: Category[];
-}
-
-export { type Group };
diff --git a/gui/src/util/groupCategories.ts b/gui/src/util/groupCategories.ts
deleted file mode 100644
index a0336ea..0000000
--- a/gui/src/util/groupCategories.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Category } from "../types/category";
-import { Group } from "../types/group";
-
-export function* groupCategories(categories: Category[]): Generator<Group> {
- const groupNames = new Set(categories.map(c => c.groupName));
- for (const name of groupNames) {
- yield {
- name,
- categories: categories.filter(c => c.groupName === name)
- };
- }
-}
diff --git a/gui/src/views/App.css b/gui/src/views/App.css
deleted file mode 100644
index 9775b76..0000000
--- a/gui/src/views/App.css
+++ /dev/null
@@ -1,5 +0,0 @@
-div {
- display: flex;
- justify-content: space-evenly;
- width: 100%;
-}
diff --git a/gui/src/views/App.tsx b/gui/src/views/App.tsx
deleted file mode 100644
index 80eb051..0000000
--- a/gui/src/views/App.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { createResource } from 'solid-js';
-import './App.css'
-import { getAllBudgetUpdates, getAllCategories } from '../ajax/backend';
-import { HistogramView } from './histogram/HistogramView';
-import { BudgetUpdatesTableView } from './budgetUpdatesTable/BudgetUpdatesTableView';
-
-function App() {
- const [categories] = createResource(getAllCategories);
- const [budgetUpdates] = createResource(getAllBudgetUpdates);
-
- return (
- <div>
- <HistogramView categories={categories} />
- <BudgetUpdatesTableView budgetUpdates={budgetUpdates} categories={categories} />
- </div>
- );
-}
-
-export default App
diff --git a/gui/src/views/budgetUpdatesTable/BudgetUpdatesTableView.css b/gui/src/views/budgetUpdatesTable/BudgetUpdatesTableView.css
deleted file mode 100644
index e862976..0000000
--- a/gui/src/views/budgetUpdatesTable/BudgetUpdatesTableView.css
+++ /dev/null
@@ -1,10 +0,0 @@
-div {
- display: flex;
- flex-direction: column;
- gap: 1rem;
-}
-
-form {
- display: flex;
- gap: 1rem;
-}
diff --git a/gui/src/views/budgetUpdatesTable/BudgetUpdatesTableView.tsx b/gui/src/views/budgetUpdatesTable/BudgetUpdatesTableView.tsx
deleted file mode 100644
index 465ea92..0000000
--- a/gui/src/views/budgetUpdatesTable/BudgetUpdatesTableView.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { createSignal, Match, Resource, Switch } from 'solid-js';
-import { BudgetUpdate } from '../../types/budgetUpdate';
-import { BudgetUpdatesTable } from '../../components/BudgetUpdatesTable/BudgetUpdatesTable';
-import { Category } from '../../types/category';
-import './BudgetUpdatesTableView.css';
-
-export function BudgetUpdatesTableView({ budgetUpdates, categories }: Props) {
- const [newCategoryId, setCategoryId] = createSignal<number>();
-
- const filteredAndSortedBudgetUpdates: BudgetUpdate[] = budgetUpdates()
- ?.filter((bu) => bu.categoryId === newCategoryId())
- ?.sort((bu1, bu2) => bu2.date.diff(bu1.date))
- ?? [];
-
- return (
- <div>
- <form action="none">
- <label for="category">Category</label>
- <select id="category" onChange={(e) => setCategoryId(Number(e.target.value))}>
- {(categories() ?? []).map((c) =>
- <option value={c.id}>{c.name}</option>)}
- </select>
- </form>
- <BudgetUpdatesTable budgetUpdates={filteredAndSortedBudgetUpdates as BudgetUpdate[]} />
- </div>
- );
-}
-
-type Props = {
- budgetUpdates: Resource<BudgetUpdate[] | null>;
- categories: Resource<Category[] | null>;
-}
diff --git a/gui/src/views/histogram/HistogramView.tsx b/gui/src/views/histogram/HistogramView.tsx
deleted file mode 100644
index 30e64ca..0000000
--- a/gui/src/views/histogram/HistogramView.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import { Match, Resource, Switch } from 'solid-js';
-import Histogram from '../../components/histogram/Histogram'
-import { Category } from '../../types/category';
-
-export function HistogramView({ categories }: Props) {
- return (
- <Switch>
- <Match when={categories.state === 'ready'}>
- <Histogram categories={(categories as Extract<Resource<Category[]>, { state: 'ready' }>)()} />
- </Match>
- <Match when={categories.state === 'pending'}>
- <p>Loading...</p>
- </Match>
- <Match when={categories.state === 'unresolved'}>
- <p>Unresolved!</p>
- </Match>
- <Match when={categories.state === 'errored'}>
- <p>Error!</p>
- <p>{JSON.stringify(categories.error, null, 2)}</p>
- <p>{JSON.stringify(categories(), null, 2)}</p>
- </Match>
- <Match when={categories.state === 'refreshing'}>
- <p>Refreshing!</p>
- </Match>
- </Switch>
- );
-}
-
-type Props = {
- categories: Resource<Category[] | null>;
-}
diff --git a/gui/src/vite-env.d.ts b/gui/src/vite-env.d.ts
deleted file mode 100644
index 11f02fe..0000000
--- a/gui/src/vite-env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-/// <reference types="vite/client" />