diff options
Diffstat (limited to 'gui/src')
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" /> |
