Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
5c171d3
Server: implemented debt and saving endpoints
HuyTruong24 Mar 19, 2026
cfd0ca4
More
HuyTruong24 Mar 19, 2026
049a57c
Server: commented out new endpoints in auth service
HuyTruong24 Mar 20, 2026
7c44997
Server: implemented debt payoff calculation but it's not working prop…
HuyTruong24 Mar 20, 2026
df24cdb
Adjusted to using connection pool from db.ts to be used for sql quer…
Mar 21, 2026
0d265c7
Created page for projection
Mar 22, 2026
0097c4c
Setup the graph for projection page and API calls for debt
Mar 23, 2026
25fae78
Server: Fixed endpoints for fetching and creating user debts
HuyTruong24 Mar 23, 2026
0c749c4
Added handling of when there's is not data for the graph
Mar 23, 2026
8524130
Merge branch 'feature-user-projection' of https://github.com/MeasureO…
Mar 23, 2026
99f110f
Server: Fixed endpoints for fetching and creating saving account
HuyTruong24 Mar 23, 2026
1c91ca3
Server: Finished predicted debt payoff calculation
HuyTruong24 Mar 24, 2026
8f7b80e
Server: Implemented compound interest logic
HuyTruong24 Mar 25, 2026
6f1931b
Server: Implemented fetching historical transactions of saving financ…
HuyTruong24 Mar 25, 2026
002cada
Setup the rest of the projected page, finished the api calls and fix …
Mar 25, 2026
3f648f4
Server: Implemented unit tests for saving queries
HuyTruong24 Mar 25, 2026
93becf4
Server: More unit tests
HuyTruong24 Mar 26, 2026
7d6b76d
Projection graph for debt is workings
Mar 26, 2026
1dc66b2
Merge branch
Mar 26, 2026
bb81c36
Typo fix
Mar 26, 2026
3f2057b
Server: finished compound interest computing and did some cleanups
HuyTruong24 Mar 26, 2026
8b08ac7
Made so the graph can handle mutliples lines and handles getting savi…
Mar 27, 2026
9016eee
Server: modified compound interest logic
HuyTruong24 Mar 27, 2026
bd639ce
Server: Added more unit tests
HuyTruong24 Mar 27, 2026
b80af85
Saving projection graph works, also adjusted how forms look
Mar 27, 2026
28e0554
Merge branch 'feature-user-projection' of https://github.com/MeasureO…
Mar 27, 2026
e7e811d
Server: added user flow diagram
HuyTruong24 Mar 27, 2026
45772b3
Merge branch 'development' into feature-user-projection
TebelR Mar 27, 2026
e4a9025
Fixed issues with quries - previous queries treated userID as profile…
TebelR Mar 27, 2026
41cd8ee
Merge branch 'development' into feature-user-projection
TebelR Mar 27, 2026
e53039e
bug fix
TebelR Mar 27, 2026
f5eac3b
bug fix
TebelR Mar 27, 2026
335259f
Merge branch 'feature-user-projection' of https://github.com/MeasureO…
TebelR Mar 27, 2026
4fe5ba0
bug fix - reverted schema to old vairant for fin account type
TebelR Mar 27, 2026
ebce152
temoporarily disabling integration tests due to a db error
TebelR Mar 27, 2026
07d5f70
disablign integration tests that rely on db
TebelR Mar 27, 2026
388930a
bug fix
TebelR Mar 27, 2026
f773440
fixed interest displaying a decimal after switching types and some bu…
Mar 27, 2026
4fb0ffc
Fixed interest
Mar 27, 2026
f7533dc
Fixed bugs due to the merge
Mar 27, 2026
21b2bd4
Select accounts bug fix
Mar 27, 2026
f7de7e4
Merge branch 'development' into feature-user-projection
JackieMei3 Mar 27, 2026
505df03
Merge branch 'feature-user-projection' of https://github.com/MeasureO…
Mar 28, 2026
e67a1a7
Move handlenumberchange function to handleInput and added tests
Mar 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions app/client/src/components/AccountForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export default function PopupForm({
name="type"
className="formSelect"
onChange={(event) =>
setAccountType(event.target.value as typeofAccount)
{setAccountType(event.target.value as typeofAccount); setFormInput({ ...formInput, ["subType"]: "" })}
}
>
<option value="">Select Account type</option>
Expand All @@ -229,7 +229,7 @@ export default function PopupForm({
/>
<br></br>

{accountType === accountCategory.SAVING ? (
{accountType === "SAVINGS" ? (
<>
<label htmlFor="subType">Type of saving account</label>
<select
Expand All @@ -239,7 +239,6 @@ export default function PopupForm({
className="formSelect"
onChange={(event) => {
handleChange(event);
setFormInput({ ...formInput, ["subType"]: "" });
}}
>
<option value="">Select saving type</option>
Expand All @@ -253,7 +252,7 @@ export default function PopupForm({
</>
) : null}

{accountType === accountCategory.CREDIT_CARD ? (
{accountType === "CREDIT_CARD" ? (
<>
<label htmlFor="subType">Type of credit:</label>
<select
Expand Down
5 changes: 4 additions & 1 deletion app/client/src/components/AccountListCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useState } from "react";
import AccountPopup from "./AccountForm.tsx";
import { type Account } from "../types/AccountType.ts";
import type { AuthSession } from "@/types/authTypes.ts";
import { accountCategory } from "@/enum/AccountCategory.ts";

interface cardProp {
account: Account;
Expand Down Expand Up @@ -47,7 +48,9 @@ export default function Card({

<div>
<p>
{account.type + " " + (account.subtype ? " " + account.subtype : "")}
{accountCategory[account.type as keyof typeof accountCategory] +
" " +
(account.subtype ? " " + account.subtype : "")}
</p>
<p>{Number(account.balance).toFixed(2)}</p>
</div>
Expand Down
9 changes: 8 additions & 1 deletion app/client/src/components/SelectAccount.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Account } from "@/types/AccountType";
import React from "react";
import { accountCategory } from "@/enum/AccountCategory.ts";

interface selectProp {
accounts: Account[];
Expand All @@ -26,7 +27,13 @@ export default function SelectAccount({
{accounts &&
accounts.map((account) => (
<option key={account.id} value={account.id}>
{account.name} ({account.type})
{account.name} (
{
accountCategory[
account.type.toUpperCase() as keyof typeof accountCategory
]
}
)
</option>
))}
</select>
Expand Down
2 changes: 1 addition & 1 deletion app/client/src/enum/AccountCategory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const accountCategory = {
SAVING: "Savings",
SAVINGS: "Savings",
CHEQUING: "Chequing",
INVESTMENT: "Investment",
CREDIT_CARD:"Credit Card",
Expand Down
42 changes: 8 additions & 34 deletions app/client/src/pages/ProjectionPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ import {
import ProjectionGraph from "@/components/ProjectionGraph";
import { TbGraph } from "react-icons/tb";
import NoItemState from "@/components/NoItemState";
import { handleCurrencyChange, handleCurrencyBlur } from "@/utils/handleInput";
import {
handleCurrencyChange,
handleCurrencyBlur,
handleNumberChange,
} from "@/utils/handleInput";
import type {
projectionDebtRequest,
projectionSavingRequest,
Expand Down Expand Up @@ -106,7 +110,7 @@ function ProjectionPage({ session }: ProjectionProp) {
category: accountCategory.CREDIT_CARD,
remainingAmount: inputAmount,
minimumPayment: inputMinPay,
interestRate: inputInterest / 100,
interestRate: inputInterest,
nextDueDate: nextDueDate,
period: inputPeriod,
};
Expand Down Expand Up @@ -163,7 +167,7 @@ function ProjectionPage({ session }: ProjectionProp) {
financial_account_id: selectedAccount,
balance: inputAmount,
monthly_deposit: inputMinPay,
annual_interest_rate: inputInterest / 100,
annual_interest_rate: inputInterest,
time_frame: inputPeriod,
};

Expand Down Expand Up @@ -233,7 +237,7 @@ function ProjectionPage({ session }: ProjectionProp) {
if (savingRequest) {
//Assigns the fields to what the projection of the saving account used
setSelectedAccount(savingRequest.financial_account_id);
setAmount(savingRequest.balance.toFixed(2));
setAmount((savingRequest.balance * 100).toFixed(2));
setInterest(savingRequest.annual_interest_rate.toString());
setMinPay(savingRequest.monthly_deposit.toFixed(2));
setPeriod(savingRequest.time_frame.toString());
Expand Down Expand Up @@ -270,36 +274,6 @@ function ProjectionPage({ session }: ProjectionProp) {
}
};

//Handles on change of percentage
const handleNumberChange = (
event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>,
setNumber: React.Dispatch<React.SetStateAction<string>>,
min: number,
max: number,
) => {
let input = event.target.value;
let changeInterest;
const pattern = /^\d*\.?\d{0,2}$/;

console.log(input);
console.log(pattern.test(input));

//Determine if the input follows the format/pattern
if (pattern.test(input) || input === "") {
input = input.replace(/^0+(?=\d)/, "");
changeInterest = Number(input);

if (changeInterest > max) {
changeInterest = max;
}

if (changeInterest < min) {
changeInterest = min;
}
console.log(changeInterest);
setNumber(changeInterest.toString());
}
};
//Get the saving accounts
useEffect(() => {
if (selectedType === "Saving") {
Expand Down
31 changes: 31 additions & 0 deletions app/client/src/utils/handleInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,34 @@ export const handleCurrencyBlur = (
setCurrency(parseFloat(currency).toFixed(2));
}
};

//Handles on change of whole numbers
export const handleNumberChange = (
event: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>,
setNumber: React.Dispatch<React.SetStateAction<string>>,
min: number,
max: number,
) => {
let input = event.target.value;
let changeNumber;
const pattern = /^\d*\.?\d{0,2}$/;

console.log(input);
console.log(pattern.test(input));

//Determine if the input follows the format/pattern
if (pattern.test(input) || input === "") {
input = input.replace(/^0+(?=\d)/, "");
changeNumber = Number(input);

if (changeNumber > max) {
changeNumber = max;
}

if (changeNumber < min) {
changeNumber = min;
}
console.log(changeNumber);
setNumber(changeNumber.toString());
}
};
66 changes: 66 additions & 0 deletions app/client/test/handleInput.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, it, expect, vi } from "vitest";
import {
handleCurrencyChange,
handleCurrencyBlur,
handleNumberChange,
} from "../src/utils/handleInput";
//tests for handleCurrencyChange and handleCurrencyBlur functions
function mockEvent(value: string) {
Expand Down Expand Up @@ -135,3 +136,68 @@ it("does not strip zeros inside the number", () => {

expect(setCurrency).toHaveBeenCalledWith("101");
});

describe("handleNumberChange", () => {
it("accepts valid number input", () => {
const setNumber = vi.fn();
const event = mockEvent("12");

handleNumberChange(event, setNumber, 0, 100);

expect(setNumber).toHaveBeenCalledWith("12");
});

it("Doesn't accept decimals", () => {
const setNumber = vi.fn();
const event = mockEvent("12.");

handleNumberChange(event, setNumber, 0, 100);

expect(setNumber).not.toHaveBeenCalledWith();
});

it("Empty string", () => {
const setNumber = vi.fn();
const event = mockEvent("");

handleNumberChange(event, setNumber, 0, 100);

expect(setNumber).toHaveBeenCalledWith("");
});

it("strip leading 0s", () => {
const setNumber = vi.fn();
const event = mockEvent("0000123");

handleNumberChange(event, setNumber, 0, 100);

expect(setNumber).toHaveBeenCalledWith("123");
});

it("rejects letters in input", () => {
const setNumber = vi.fn();
const event = mockEvent("12A");

handleNumberChange(event, setNumber, 0, 100);

expect(setNumber).not.toHaveBeenCalledWith();
});

it("Sets the value to the maximum", () => {
const setNumber = vi.fn();
const event = mockEvent("120");

handleNumberChange(event, setNumber, 0, 100);

expect(setNumber).not.toHaveBeenCalledWith("100");
});

it("Sets value to the minimum", () => {
const setNumber = vi.fn();
const event = mockEvent("1");

handleNumberChange(event, setNumber, 10, 100);

expect(setNumber).not.toHaveBeenCalledWith("10");
});
});
1 change: 1 addition & 0 deletions app/server/services/ts/user/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ process.on("SIGTERM", () => cleanup);

app.get("/charts/expenses", async (req, res) => {
process.on("SIGTERM", () => server.close());
})

app.get(
"/charts/expenses",
Expand Down
2 changes: 1 addition & 1 deletion app/server/services/ts/user/src/queries/debt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export async function findDebtsBy(db: Pool, userId: string) : Promise<DebtInfoRe
JOIN finus.profile p ON pfa.profile_id = p.id
JOIN finus.finusAccount_profile uap ON p.id = uap.profile_id
WHERE uap.account_id = ?
AND fa.type = 'Credit Card'
AND fa.type = 'CREDIT_CARD'
AND fa.subtype = 'Loan'
GROUP BY fa.id
`;
Expand Down
2 changes: 1 addition & 1 deletion app/server/services/ts/user/src/queries/saving.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export async function findSavingsBy(db: Pool, userId: string) : Promise<SavingIn
JOIN finus.profile p ON pfa.profile_id = p.id
JOIN finus.finusAccount_profile uap ON p.id = uap.profile_id
WHERE uap.account_id = ?
AND fa.type = 'Savings'
AND fa.type = 'SAVINGS'
GROUP BY fa.id
`;

Expand Down
10 changes: 5 additions & 5 deletions app/server/services/ts/user/src/types/FinancialAccountType.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export enum FinancialAccountType {
SAVINGS = 'Savings',
CREDIT = 'Credit Card',
INVESTMENT = 'Investment',
CHEQUING = 'Chequing',
UNCONFIRMED = 'Unconfirmed',
SAVINGS = 'savings',
CREDIT = 'credit_card',
INVESTMENT = 'investment',
CHEQUING = 'chequing',
UNCONFIRMED = 'unconfirmed',
}