diff --git a/README.md b/README.md
index 8e2693d..430c656 100644
--- a/README.md
+++ b/README.md
@@ -42,7 +42,7 @@ Start Firebase emulator(s): `firebase emulators:start`
### Test Deployment
-Creating a test deployment to Firebase:
+Creating a tiles deployment to Firebase:
```
npm run build
firebase hosting:channel:deploy [Test Deployment Name]
@@ -51,7 +51,7 @@ firebase hosting:channel:deploy [Test Deployment Name]
### Production Deployment
Making a [pull request](https://github.com/Robert-M-Lucas/budget-19/compare)
-will automatically create a test deployment to Firebase (i.e. not
+will automatically create a tiles deployment to Firebase (i.e. not
overwrite the production app). You should see a message below your new pull
request notifying you that your changes are being deployed. Click on
the link shown when this is complete to view your changes. The pull
diff --git a/package-lock.json b/package-lock.json
index aa0a8a9..c65656a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.0.0",
"dependencies": {
"@types/papaparse": "^5.3.14",
+ "@types/strftime": "^0.9.8",
"bootstrap": "^5.3.3",
"firebase": "^10.11.0",
"lodash": "^4.17.21",
@@ -19,7 +20,8 @@
"react-router-dom": "^6.23.0",
"react-tiles-dnd": "^0.1.2",
"recharts": "^2.12.5",
- "sass": "^1.72.0"
+ "sass": "^1.72.0",
+ "strftime": "^0.10.2"
},
"devDependencies": {
"@faker-js/faker": "^8.4.1",
@@ -2584,6 +2586,11 @@
"@types/send": "*"
}
},
+ "node_modules/@types/strftime": {
+ "version": "0.9.8",
+ "resolved": "https://registry.npmjs.org/@types/strftime/-/strftime-0.9.8.tgz",
+ "integrity": "sha512-QIvDlGAKyF3YJbT3QZnfC+RIvV5noyDbi+ZJ5rkaSRqxCGrYJefgXm3leZAjtoQOutZe1hCXbAg+p89/Vj4HlQ=="
+ },
"node_modules/@types/tough-cookie": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
@@ -6705,6 +6712,14 @@
"dev": true,
"optional": true
},
+ "node_modules/strftime": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/strftime/-/strftime-0.10.2.tgz",
+ "integrity": "sha512-Y6IZaTVM80chcMe7j65Gl/0nmlNdtt+KWPle5YeCAjmsBfw+id2qdaJ5MDrxUq+OmHKab+jHe7mUjU/aNMSZZg==",
+ "engines": {
+ "node": ">=0.2.0"
+ }
+ },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
diff --git a/package.json b/package.json
index caaae65..4dce34f 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
},
"dependencies": {
"@types/papaparse": "^5.3.14",
+ "@types/strftime": "^0.9.8",
"bootstrap": "^5.3.3",
"firebase": "^10.11.0",
"lodash": "^4.17.21",
@@ -23,7 +24,8 @@
"react-router-dom": "^6.23.0",
"react-tiles-dnd": "^0.1.2",
"recharts": "^2.12.5",
- "sass": "^1.72.0"
+ "sass": "^1.72.0",
+ "strftime": "^0.10.2"
},
"devDependencies": {
"@faker-js/faker": "^8.4.1",
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
index fd668e9..ec298ff 100644
--- a/src/components/Header.tsx
+++ b/src/components/Header.tsx
@@ -42,7 +42,7 @@ export function Header() {
{/* Links to transactions page with table of expenses */}
Transactions
- Firestore Test
+ Firestore Test
{displayName && {displayName}}
diff --git a/src/pages/dashboard/DashboardPage.tsx b/src/pages/dashboard/DashboardPage.tsx
index fd2714c..7004cbf 100644
--- a/src/pages/dashboard/DashboardPage.tsx
+++ b/src/pages/dashboard/DashboardPage.tsx
@@ -1,147 +1,102 @@
import "./styles.css";
import "react-tiles-dnd/esm/index.css";
-import {TilesContainer, RenderTileFunction} from "react-tiles-dnd";
+import { TilesContainer, RenderTileFunction } from "react-tiles-dnd";
import useWindowDimensions from "../../hooks/WindowDimensionsHook.tsx";
-import {ReactNode, useEffect, useState} from "react";
import {Header} from "../../components/Header.tsx";
-import {getTransactionsFilterOrderBy} from "../../utils/transaction.ts"
-import {getCurrentBalance} from "../../utils/transaction_utils.ts";
-import {getUserPrefs, UserPrefs} from "../../utils/user_prefs.ts";
-import {readTransactions} from "./GraphUtils.ts";
+import {useEffect, useState} from "react";
+import {Transaction, getTransactionsFilterOrderBy } from "../../utils/transaction.ts"
import {auth} from "../../utils/firebase.ts";
import {orderBy} from "firebase/firestore";
-import { User } from "firebase/auth";
import {FullscreenCenter} from "../../components/FullscreenCenter.tsx";
-import {Button} from "react-bootstrap";
-import {CSVUpload} from "../../components/transactions/CSVUpload.tsx";
-import {InputTransaction} from "../../components/transactions/InputTransaction.tsx";
import Graphs from "./Graphs.tsx"
-import test from "./test.tsx"
-
-type transactionPoint = { date: string; amount: number }
-type tsxContents = ReactNode;
-
-class TileElement {
- private graph?: transactionPoint[];
- private TSX?: () => tsxContents;
-
- constructor(graph: transactionPoint[] | undefined, TSX: (() => tsxContents) | undefined) {
- this.graph = graph;
- this.TSX = TSX;
- }
-
- static newGraph(graph: transactionPoint[]): TileElement {
- return new TileElement(graph, undefined);
- }
- static newTSX(TSX: () => tsxContents): TileElement {
- return new TileElement(undefined, TSX);
- }
-
- isGraph(): boolean {
- return typeof this.graph !== "undefined";
- }
-
- forceGetGraph(): transactionPoint[] {
- return this.graph!;
- }
- forceGetTSX(): () => tsxContents {
- return this.TSX!;
- }
-}
+import {getTileSize, TileElement} from "./TileUtils.ts";
+import {finalGraphData, readTransactions} from "./GraphUtils.ts";
+import {signInWithGoogle} from "../../utils/authentication.ts";
+import totalTile from "./TotalTile.tsx";
+import {getUserPrefs, UserPrefs} from "../../utils/user_prefs.ts";
+import {User} from "firebase/auth";
+import goalsTile from "./GoalsTile.tsx";
export default function Dashboard() {
- const [balance, setBalance] = useState(0);
- const [transactionPoints, setPoints] = useState([[]]);
- const [userResolved, setUserResolved] = useState(false);
+ // const [balance, setBalance] = useState(0);
+ const [transactionPoints, setPoints] = useState(null);
+ const [transactions, setTransactions] = useState([]);
const [authResolved, setAuthResolved] = useState(false);
- const [fetchResolved, setFetchResolved] = useState(false);
- const [showCSVModal, setShowCSVModal] = useState(false);
- const [showTransactionModal, setShowTransactionModal] = useState(false);
+ const [userPrefs, setUserPrefs] = useState(null);
+ // const [showCSVModal, setShowCSVModal] = useState(false);
+ // const [showTransactionModal, setShowTransactionModal] = useState(false);
+ const [update, setUpdate] = useState(0)
- const fetchTransactions = async (user: User, goal: number) => {
- try {
- const transactions = await getTransactionsFilterOrderBy(user, orderBy("dateTime", "asc"))
- console.log("Check 0", transactions)
- await readTransactions(transactions, goal).then((t) => {
- console.log("Check 1", t)
- setPoints(t)
- })
- setFetchResolved(true);
- } catch (error) { console.error("Error in fetchTransactions", error) }
- }
- const fetchUserPrefs = async (user: User): Promise => {
- try {
- const u = await getUserPrefs(user);
- setUserResolved(true);
- return u
- } catch (error) { console.error("Error in fetchUserPrefs", error) }
+ const fetchTransactions = async (user: User) => {
+ const transactions = await getTransactionsFilterOrderBy(user, orderBy("dateTime", "desc"));
+ setTransactions(transactions);
+ setPoints(readTransactions(transactions));
}
const {width} = useWindowDimensions();
- const columns = Math.max(Math.floor(width / 200), 1);
+ const columns = Math.max(Math.floor(width / 180), 1);
// Transaction Loading and Handling
useEffect(() => {
if (auth.currentUser !== null) {
- getCurrentBalance(auth.currentUser).then((b) => setBalance(b));
- fetchUserPrefs(auth.currentUser).then((u) => {
- console.log("Fetched UserPrefs", u)
- if (u) {
- fetchTransactions(auth.currentUser!, u!.goal).then(() => {
- console.log("Fetched Transactions", transactionPoints)
- });
- }
- })
-
+ fetchTransactions(auth.currentUser).then();
+ getUserPrefs(auth.currentUser).then((prefs) => setUserPrefs(prefs));
}
},[auth.currentUser])
- // Forces user to wait till everything has loaded
if (!authResolved) {
auth.authStateReady().then(() => setAuthResolved(true));
return <>
+
-
Waiting for Authentication
+ Waiting for Auth
>;
}
- if (!userResolved) {
+
+ if (auth.currentUser === null) {
+ auth.onAuthStateChanged(() => {
+ setUpdate(update + 1);
+ });
return <>
+
-
Fetching User Data
+ Not Logged In
+
>;
}
- if (!fetchResolved) {
+
+ if (!transactionPoints || !userPrefs) {
return <>
+
-
Fetching User Transactions
+ Fetching
>;
}
- // Tiles
- const tileSize = (tile: typeof transactionTiles[0]) => ({
- colSpan: tile.cols,
- rowSpan: tile.rows
- });
- const transactionTiles = [
- {d: TileElement.newGraph(transactionPoints[0]), cols:5, rows:2},
- {d: TileElement.newGraph(transactionPoints[1]), cols:5, rows:2},
- {d: TileElement.newGraph(transactionPoints[2]), cols:5, rows:2},
- {d: TileElement.newTSX(test), cols:1, rows:1}
+ const transactionTiles: TileElement[] = [
+ TileElement.newTSX(() => totalTile(transactions), 2, 1, columns),
+ TileElement.newTSX(() => (goalsTile(userPrefs)), 2, 1, columns),
+ TileElement.newGraph(transactionPoints.raw, 3, 2, columns),
+ TileElement.newGraph(transactionPoints.in, 3, 2, columns),
+ TileElement.newGraph(transactionPoints.out, 3, 2, columns),
];
- const renderFirebase: RenderTileFunction = ({ data, isDragging }) => (
+
+ const renderTile: RenderTileFunction = ({ data, isDragging }) => (
- {data.d.isGraph() ? : data.d.forceGetTSX()()}
+ {data.isGraph() ? : data.forceGetTSX()()}
);
@@ -149,23 +104,11 @@ export default function Dashboard() {
return (
-
-
-
-
-
-
-
-
- {balance}
diff --git a/src/pages/dashboard/GoalsTile.tsx b/src/pages/dashboard/GoalsTile.tsx
new file mode 100644
index 0000000..8232909
--- /dev/null
+++ b/src/pages/dashboard/GoalsTile.tsx
@@ -0,0 +1,7 @@
+import {ReactNode} from "react";
+import {UserPrefs} from "../../utils/user_prefs.ts";
+
+export default function goalsTile(userPrefs: UserPrefs): ReactNode {
+
+ return
Goal: {userPrefs.getNeedsBudget()} | {userPrefs.getWantsBudget()} | {userPrefs.getSavingsBudget()}
;
+}
\ No newline at end of file
diff --git a/src/pages/dashboard/GraphUtils.ts b/src/pages/dashboard/GraphUtils.ts
index 75fc0e2..cad8f00 100644
--- a/src/pages/dashboard/GraphUtils.ts
+++ b/src/pages/dashboard/GraphUtils.ts
@@ -1,33 +1,23 @@
import {Transaction} from "../../utils/transaction.ts";
+import strftime from "strftime";
-type transactionPoint = { date: string; amount: number; goal: number }
+export type transactionPoint = { date: string; amount: number }
+export type finalGraphData = {raw: transactionPoint[], in: transactionPoint[], out: transactionPoint[]};
-export const cumulateTransactions = (points: transactionPoint[]): transactionPoint[] => {
+function cumulateTransactions(points: transactionPoint[]): transactionPoint[] {
let total = 0;
return points.map(value => {
total += value.amount;
- return {date: value.date, amount: total, goal: value.goal};
+ value.amount = total;
+ return value;
})
}
-const getDateString = (timestamp: number): string => {
- const date = new Date(timestamp)
- const day = date.getDate().toString().padStart(2, '0'); // Ensures two digits
- const month = (date.getMonth() + 1).toString().padStart(2, '0'); // Month is 0-indexed, add 1
- const year = date.getFullYear();
- return `${day}/${month}/${year}`;
-}
-export const readTransactions = async (data: Transaction[], goal: number): Promise
=> {
- const result: transactionPoint[] = []
- data.forEach(t => {
- result.push({amount: t.amount, date: getDateString(t.dateTime), goal: 0})
- })
- // Change the last bit of data to set the goal
- const lastElem = result[result.length - 1]
- result[result.length - 1] = {amount: lastElem.amount, date: lastElem.date, goal: goal}
- return splitTransactions(result)
+function getDateString(timestamp: number): string {
+ return strftime("%d/%m/%y", new Date(timestamp))
}
-export const splitTransactions = (data: transactionPoint[]): transactionPoint[][] => {
+
+function splitTransactions (data: transactionPoint[]): finalGraphData {
const moneyIn: transactionPoint[] = []
const moneyOut: transactionPoint[] = []
data.forEach(t => {
@@ -37,5 +27,13 @@ export const splitTransactions = (data: transactionPoint[]): transactionPoint[][
moneyOut.push(t)
}
})
- return ([cumulateTransactions(data), cumulateTransactions(moneyIn), cumulateTransactions(moneyOut)])
+ return {raw: cumulateTransactions(data), in: cumulateTransactions(moneyIn), out: cumulateTransactions(moneyOut)};
+}
+
+export function readTransactions(data: Transaction[]): finalGraphData {
+ return splitTransactions(
+ data.map((t) => {
+ return {date: getDateString(t.dateTime), amount: t.amount}
+ })
+ );
}
\ No newline at end of file
diff --git a/src/pages/dashboard/Graphs.tsx b/src/pages/dashboard/Graphs.tsx
index 50682d9..ebd2a4f 100644
--- a/src/pages/dashboard/Graphs.tsx
+++ b/src/pages/dashboard/Graphs.tsx
@@ -1,6 +1,8 @@
import {Line, LineChart, ResponsiveContainer, XAxis, YAxis} from "recharts";
type transactionPoint = { date: string; amount: number; goal: number }
+import {Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis} from "recharts";
+import {transactionPoint} from "./GraphUtils.ts";
interface Props {
data: transactionPoint[];
@@ -12,6 +14,7 @@ export default function Graphs({data}: Props) {
+
diff --git a/src/pages/dashboard/TileUtils.ts b/src/pages/dashboard/TileUtils.ts
new file mode 100644
index 0000000..3fbee2b
--- /dev/null
+++ b/src/pages/dashboard/TileUtils.ts
@@ -0,0 +1,43 @@
+import {ReactNode} from "react";
+import {transactionPoint} from "./GraphUtils.ts";
+import {min} from "lodash";
+
+type tsxContents = ReactNode;
+
+export class TileElement {
+ private readonly graph?: transactionPoint[];
+ private readonly TSX?: () => tsxContents;
+ public readonly cols: number;
+ public readonly rows: number;
+
+ private constructor(graph: transactionPoint[] | undefined, TSX: (() => tsxContents) | undefined, cols: number, rows: number) {
+ this.graph = graph;
+ this.TSX = TSX;
+ this.cols = cols;
+ this.rows = rows;
+ }
+
+ static newGraph(graph: transactionPoint[], cols: number, rows: number, maxCol: number): TileElement {
+ return new TileElement(graph, undefined, min([cols, maxCol])!, rows);
+ }
+ static newTSX(TSX: () => tsxContents, cols: number, rows: number, maxCol: number): TileElement {
+ return new TileElement(undefined, TSX, min([cols, maxCol])!, rows);
+ }
+
+ isGraph(): boolean {
+ return typeof this.graph !== "undefined";
+ }
+
+ forceGetGraph(): transactionPoint[] {
+ return this.graph!;
+ }
+ forceGetTSX(): () => tsxContents {
+ return this.TSX!;
+ }
+}
+
+export type tileData = { d: TileElement; rows: number; cols: number };
+
+export function getTileSize (tile: TileElement) {
+ return {colSpan: tile.cols, rowSpan: tile.rows};
+}
\ No newline at end of file
diff --git a/src/pages/dashboard/TotalTile.tsx b/src/pages/dashboard/TotalTile.tsx
new file mode 100644
index 0000000..6bad195
--- /dev/null
+++ b/src/pages/dashboard/TotalTile.tsx
@@ -0,0 +1,38 @@
+import {ReactNode} from "react";
+import { Transaction } from "../../utils/transaction";
+import {max, min} from "lodash";
+
+export default function totalTile(transactions: Transaction[]): ReactNode {
+ const balance = transactions.reduce((prev, curr): number => prev + curr.amount, 0);
+ const income = transactions.reduce((prev, curr): number => prev + max([curr.amount, 0])!, 0);
+ const expenses = transactions.reduce((prev, curr): number => prev + min([curr.amount, 0])!, 0);
+
+ return
+ -
+
+
Balance:
+ {balance > 0 ? <>
+
£
+
{balance.toFixed(2)}
+ > : <>
+
£
+
{balance.toFixed(2)}
+ >}
+
+
+ -
+
+
Income:
+
£
+
{income.toFixed(2)}
+
+
+ -
+
+
Expenses:
+
£
+
{expenses.toFixed(2)}
+
+
+
;
+}
\ No newline at end of file
diff --git a/src/pages/dashboard/test.tsx b/src/pages/dashboard/test.tsx
deleted file mode 100644
index 13cba03..0000000
--- a/src/pages/dashboard/test.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function test() {
- return (
- <>
- Hi
- >
- );
-}
\ No newline at end of file
diff --git a/src/pages/test firestore/TestFirestore.tsx b/src/pages/test firestore/TestFirestore.tsx
index 2655ca2..899c941 100644
--- a/src/pages/test firestore/TestFirestore.tsx
+++ b/src/pages/test firestore/TestFirestore.tsx
@@ -15,6 +15,7 @@ import {Header} from "../../components/Header.tsx";
import { orderBy } from "firebase/firestore";
import {User} from "firebase/auth";
import {getUserPrefs, setUserPrefs, UserPrefs} from "../../utils/user_prefs.ts";
+import {round} from "lodash";
function writeSampleData() {
if (auth.currentUser === null) {
@@ -95,11 +96,14 @@ export function TestFirestorePage() {
UserPrefs
{
userPrefs ? <>
- Goal: {userPrefs.goal}
+ Goal: {userPrefs.getNeedsBudget()} | {userPrefs.getWantsBudget()} | {userPrefs.getSavingsBudget()}
> : Loading
}
diff --git a/src/pages/transactions/TransactionPage.tsx b/src/pages/transactions/TransactionPage.tsx
new file mode 100644
index 0000000..f7ecfde
--- /dev/null
+++ b/src/pages/transactions/TransactionPage.tsx
@@ -0,0 +1,177 @@
+import {useState} from "react";
+import {auth} from "../../utils/firebase.ts";
+import {getTransactionsFilterOrderBy, Transaction} from "../../utils/transaction.ts";
+import {FullscreenCenter} from "../../components/FullscreenCenter.tsx";
+import {User} from "firebase/auth";
+import {Header} from "../../components/Header.tsx";
+import {signInWithGoogle} from "../../utils/authentication.ts";
+import {limit, orderBy, startAfter} from "firebase/firestore";
+import strftime from "strftime";
+
+// CSS
+import "./transactionsTable.scss";
+
+export function TransactionPage() {
+ const [transactions, setTransactions] = useState(null);
+ const [currentPage, setCurrentPage] = useState(0);
+ const [authResolved, setAuthResolved] = useState(false);
+ const [update, setUpdate] = useState(0);
+ const [pageStarts, setPageStarts] = useState([Infinity]);
+ const itemsPerPage = 14;
+
+
+ if (!authResolved) {
+ auth.authStateReady().then(() => setAuthResolved(true));
+ return <>
+
+
+
Waiting for Auth
+
+
+ >;
+ }
+
+ if (auth.currentUser === null) {
+ auth.onAuthStateChanged((new_user: User | null) => {
+ if (new_user !== null) {
+ setUpdate(update + 1);
+ }
+ });
+ return <>
+
+
+
+
Not Logged In
+
+
+
+ >;
+ }
+
+ if (!transactions) {
+ getTransactionsFilterOrderBy(auth.currentUser, orderBy("dateTime", "desc"), limit(itemsPerPage), startAfter(pageStarts[pageStarts.length - 1]))
+ .then((pageTransactions) => {
+ setTransactions(pageTransactions);
+ });
+
+ return <>
+
+
+
+
+ Name |
+ Category |
+ Emoji |
+ Date |
+ Amount |
+ Notes |
+
+
+
+
+ Fetching... |
+ Fetching... |
+ Fetching... |
+ Fetching... |
+ Fetching... |
+ Fetching... |
+
+
+
+ >;
+ }
+
+
+ // adjusted dylan.s code to use getTransactionPage instead of getTransactions
+ // useEffect(() => {
+ // getTransactionsPage(auth.currentUser, itemsPerPage, currentPage)
+ // .then((pageTransactions) => {
+ // console.log("Fetched transactions:");
+ // console.log(pageTransactions);
+ // setTransactions(pageTransactions);
+ // });
+ // }, [currentPage]);
+
+ return <>
+
+
+
+
+
+ Name |
+ Category |
+ Emoji |
+ Date |
+ Amount |
+ Description |
+
+
+
+ {transactions.map((transaction) => (
+
+ // maps every transaction as a row in the table
+ ))}
+
+
+ { transactions.length == 0 &&
+
[No More Data]
+ }
+
+ {/* conditional previous page button, only displayed if page number > 1 */}
+ {currentPage > 0 && (
+
+ )}
+ {/* spacers to push buttons to the edges */}
+
+
Page {currentPage + 1}
+
+ {transactions.length === itemsPerPage &&
+
+ }
+
+
+
+ >;
+}
+
+function TransactionItem({data}: { data: Transaction }) {
+ return (
+
+ {data.name} |
+ {data.category} |
+ {data.emoji} |
+ {strftime("%d/%m/%y - %H:%M", new Date(data.dateTime))} |
+ {data.amount > 0 ?
+
+
+ £
+ {data.amount.toFixed(2)}
+
+ | :
+
+
+ £
+ {data.amount.toFixed(2)}
+
+ |
+ }
+ {data.description} |
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/transactions/transactionsTable.css b/src/pages/transactions/transactionsTable.css
new file mode 100644
index 0000000..2eb9216
--- /dev/null
+++ b/src/pages/transactions/transactionsTable.css
@@ -0,0 +1,69 @@
+/* page background */
+body {
+ background-color: #e6f2ff;
+}
+
+/* table */
+table {
+ width: 100%;
+ border-collapse: separate;
+ border-spacing: 0;
+ border-radius: 10px; /* rounded borders */
+ background-color: #ffffff;
+ overflow: hidden; /* hide overflow from rounded borders */
+}
+
+th {
+ padding: 12px 15px;
+ background-color: #f2f2f2;
+ border-bottom: 1px solid transparent; /* transparent border for rows */
+}
+
+td {
+ padding: 12px 15px;
+ text-align: left;
+ border-bottom: 1px solid transparent; /* transparent border for rows */
+}
+
+/* highlight on hover for table rows */
+tbody tr:hover {
+ background-color: #f5f5f5;
+}
+
+/* Pagination */
+.pagination {
+ margin-top: 20px;
+ display: flex;
+ align-items: center; /* align items vertically */
+}
+
+.pagination button {
+ padding: 8px 12px;
+ border: 1px solid #007bff;
+ background-color: #007bff;
+ color: white;
+ cursor: pointer;
+ border-radius: 5px;
+}
+
+.pagination .spacer {
+ flex-grow: 1; /* grow to fill available space */
+}
+
+.page-counter {
+ margin: 0 10px; /* add margin to separate from buttons */
+}
+
+/* hover effect for buttons */
+.pagination button:hover {
+ background-color: #0056b3;
+}
+
+/* disabled state for buttons */
+.pagination button:disabled {
+ background-color: #cccccc;
+ border-color: #cccccc;
+ cursor: not-allowed;
+}
+
+/*# sourceMappingURL=transactionsTable.css.map */
diff --git a/src/pages/transactions/transactionsTable.css.map b/src/pages/transactions/transactionsTable.css.map
new file mode 100644
index 0000000..30fecee
--- /dev/null
+++ b/src/pages/transactions/transactionsTable.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["transactionsTable.scss"],"names":[],"mappings":"AAAA;AACA;EACE;;;AAGF;AACA;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAKF;EACE;EACA;EACA;;;AAGF;AACA;EACE;;;AAGF;AACA;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;AACA;EACE;;;AAGF;AACA;EACE;EACA;EACA","file":"transactionsTable.css"}
\ No newline at end of file
diff --git a/src/pages/transactions/transactionsTable.scss b/src/pages/transactions/transactionsTable.scss
new file mode 100644
index 0000000..b8e0aeb
--- /dev/null
+++ b/src/pages/transactions/transactionsTable.scss
@@ -0,0 +1,69 @@
+/* page background */
+body {
+ background-color: #e6f2ff;
+}
+
+/* table */
+table {
+ width: 100%;
+ border-collapse: separate;
+ border-spacing: 0;
+ border-radius: 10px; /* rounded borders */
+ background-color: #ffffff;
+ overflow: hidden; /* hide overflow from rounded borders */
+}
+
+th {
+ padding: 12px 15px;
+ background-color: #f2f2f2;
+ border-bottom: 1px solid transparent; /* transparent border for rows */
+ //border-top-left-radius: 10px; /* rounded top-left corner */
+ //border-top-right-radius: 10px; /* rounded top-right corner */
+}
+
+td {
+ padding: 12px 15px;
+ text-align: left;
+ border-bottom: 1px solid transparent; /* transparent border for rows */
+}
+
+/* highlight on hover for table rows */
+tbody tr:hover {
+ background-color: #f5f5f5;
+}
+
+/* Pagination */
+.pagination {
+ margin-top: 20px;
+ display: flex;
+ align-items: center; /* align items vertically */
+}
+
+.pagination button {
+ padding: 8px 12px;
+ border: 1px solid #007bff;
+ background-color: #007bff;
+ color: white;
+ cursor: pointer;
+ border-radius: 5px;
+}
+
+.pagination .spacer {
+ flex-grow: 1; /* grow to fill available space */
+}
+
+.page-counter {
+ margin: 0 10px; /* add margin to separate from buttons */
+}
+
+/* hover effect for buttons */
+.pagination button:hover {
+ background-color: #0056b3;
+}
+
+/* disabled state for buttons */
+.pagination button:disabled {
+ background-color: #cccccc;
+ border-color: #cccccc;
+ cursor: not-allowed;
+}
\ No newline at end of file
diff --git a/src/router.tsx b/src/router.tsx
index 283b71b..f5b2c09 100644
--- a/src/router.tsx
+++ b/src/router.tsx
@@ -7,93 +7,10 @@ import {SampleSidebarHeader} from "./pages/samples/sidebar_header/SampleSidebarH
import {SampleModal} from "./pages/samples/modal/SampleModal.tsx";
import GraphDashboard from "./pages/dashboard/DashboardPage.tsx";
import {TestFirestorePage} from "./pages/test firestore/TestFirestore.tsx";
+import {TransactionPage} from "./pages/transactions/TransactionPage.tsx";
// ? Routing - see https://reactrouter.com/en/main
-// const tileset_defult: Array<{ text: string; rows: number; cols: number }> = [
-// { text: "Tile 1", cols: 1, rows: 1 },
-// { text: "Tile 2", cols: 1, rows: 1 },
-// { text: "Tile 3", cols: 2, rows: 2 },
-// { text: "Tile 4", cols: 2, rows: 2 },
-// { text: "Tile 5", cols: 1, rows: 1 },
-// { text: "Tile 6", cols: 1, rows: 1 },
-// { text: "Tile 7", cols: 1, rows: 1 },
-// { text: "Tile 8", cols: 1, rows: 1 },
-// { text: "Tile 9", cols: 2, rows: 1 },
-// ];
-
-const tileset_column: Array<{ text: string; rows: number; cols: number }> = [
- { text: "Tile 1", cols: 1, rows: 1 },
- { text: "Tile 2", cols: 1, rows: 2 },
- { text: "Tile 3", cols: 2, rows: 2 },
- { text: "Tile 4", cols: 2, rows: 2 },
- { text: "Tile 5", cols: 1, rows: 1 },
- { text: "Tile 6", cols: 1, rows: 1 },
- { text: "Tile 7", cols: 1, rows: 1 },
- { text: "Tile 8", cols: 1, rows: 1 },
- { text: "Tile 9", cols: 2, rows: 1 },
-];
-
-const tileset_many: Array<{ text: string; rows: number; cols: number }> = [
- { text: "Tile 1", cols: 1, rows: 1 },
- { text: "Tile 2", cols: 2, rows: 1 },
- { text: "Tile 3", cols: 2, rows: 2 },
- { text: "Tile 4", cols: 2, rows: 2 },
- { text: "Tile 5", cols: 1, rows: 1 },
- { text: "Tile 6", cols: 1, rows: 1 },
- { text: "Tile 7", cols: 1, rows: 1 },
- { text: "Tile 8", cols: 1, rows: 1 },
- { text: "Tile 9", cols: 2, rows: 1 },
- { text: "Tile 11", cols: 1, rows: 1 },
- { text: "Tile 12", cols: 2, rows: 1 },
- { text: "Tile 13", cols: 2, rows: 2 },
- { text: "Tile 14", cols: 2, rows: 2 },
- { text: "Tile 15", cols: 1, rows: 1 },
- {text: "Tile 16", cols: 1, rows:1 },
- { text: "Tile 17", cols: 1, rows: 1 },
- { text: "Tile 18", cols: 1, rows: 1 },
- { text: "Tile 19", cols: 2, rows: 1 },
- { text: "Tile 21", cols: 1, rows: 1 },
- { text: "Tile 22", cols: 2, rows: 1 },
- { text: "Tile 23", cols: 2, rows: 2 },
- { text: "Tile 24", cols: 2, rows: 2 },
- { text: "Tile 25", cols: 1, rows: 1 },
- { text: "Tile 26", cols: 1, rows: 1 },
- { text: "Tile 27", cols: 1, rows: 1 },
- { text: "Tile 28", cols: 1, rows: 1 },
- { text: "Tile 29", cols: 2, rows: 1 },
- { text: "Tile 31", cols: 1, rows: 1 },
- { text: "Tile 32", cols: 2, rows: 1 },
- { text: "Tile 33", cols: 2, rows: 2 },
- { text: "Tile 34", cols: 2, rows: 2 },
- { text: "Tile 35", cols: 1, rows: 1 },
- { text: "Tile 36", cols: 1, rows: 1 },
- { text: "Tile 37", cols: 1, rows: 1 },
- { text: "Tile 38", cols: 1, rows: 1 },
- { text: "Tile 39", cols: 2, rows: 1 },
-];
-
-const tileset_weird: Array<{ text: string; rows: number; cols: number }> = [
- { text: "Tile 1", cols: 1, rows: 3 },
- { text: "Tile 2", cols: 2, rows: 3 },
- { text: "Tile 3", cols: 3, rows: 2 },
- { text: "Tile 4", cols: 3, rows: 2 },
- { text: "Tile 5", cols: 3, rows: 1 },
- { text: "Tile 6", cols: 1, rows: 1 },
- { text: "Tile 7", cols: 1, rows: 1 },
- { text: "Tile 8", cols: 1, rows: 1 },
- { text: "Tile 9", cols: 2, rows: 1 },
- { text: "Tile 11", cols: 1, rows: 1 },
- { text: "Tile 12", cols: 2, rows: 1 },
- { text: "Tile 13", cols: 2, rows: 2 },
- { text: "Tile 14", cols: 2, rows: 2 },
- { text: "Tile 15", cols: 1, rows: 1 },
- { text: "Tile 16", cols: 1, rows: 1 },
- { text: "Tile 17", cols: 3, rows: 3 },
- { text: "Tile 18", cols: 1, rows: 1 },
- { text: "Tile 19", cols: 2, rows: 1 },
-];
-
export const router = createBrowserRouter([
{
path: "/",
@@ -102,17 +19,14 @@ export const router = createBrowserRouter([
},
{
path: "/dash",
- element: ,
+ element: ,
},
{
path: "/transactions",
- element: ,
+ element: ,
},
- // -->
-
-
{
- path: "/user-test",
+ path: "/user-tiles",
element: ,
errorElement:<_404Page/>
},
@@ -120,18 +34,6 @@ export const router = createBrowserRouter([
path: "/graphs",
element: ,
},
- {
- path: "/dash-2",
- element: ,
- },
- {
- path: "/dash-3",
- element: ,
- },
- {
- path: "/dash-4",
- element: ,
- },
{
path: "/sample_sidebar",
element: ,
@@ -145,7 +47,7 @@ export const router = createBrowserRouter([
element: ,
},
{
- path: "/test",
+ path: "/tiles",
element: ,
},
]);
\ No newline at end of file
diff --git a/src/utils/transaction_utils.ts b/src/utils/transaction_utils.ts
index 53dd236..69fa01d 100644
--- a/src/utils/transaction_utils.ts
+++ b/src/utils/transaction_utils.ts
@@ -1,6 +1,6 @@
import {User} from "firebase/auth";
import {getTransactions, getTransactionsFilterOrderBy, Transaction} from "./transaction.ts";
-import {limit, startAfter, where} from "firebase/firestore";
+import {where} from "firebase/firestore";
export async function getCurrentBalance(user: User): Promise {
const transactions = await getTransactions(user);
@@ -24,9 +24,4 @@ const DAY_MILLIS = 8.64e+7;
export async function getLastDayTransaction(user: User): Promise {
return await getTransactionsFilterOrderBy(user, where("dateTime", ">", Date.now() - DAY_MILLIS));
-}
-
-// Returns `pageSize` transactions for the given `user` with the `docName` attribute set
-export async function getTransactionsPage(user: User, pageSize: number, page: number): Promise {
- return await getTransactionsFilterOrderBy(user, startAfter(page * pageSize), limit(pageSize));
}
\ No newline at end of file
diff --git a/src/utils/user_prefs.ts b/src/utils/user_prefs.ts
index 2aa65b5..24ebba6 100644
--- a/src/utils/user_prefs.ts
+++ b/src/utils/user_prefs.ts
@@ -1,17 +1,52 @@
import {collection, doc, DocumentSnapshot, getDoc, setDoc, SnapshotOptions} from "firebase/firestore";
import {User} from "firebase/auth";
import {db} from "./firebase.ts";
+import {round} from "lodash";
export class UserPrefs {
- public goal: number;
+ private readonly needsBudget: number;
+ private readonly wantsBudget: number;
- constructor(goal: number) {
- this.goal = goal;
+ private constructor(needsBudget: number, wantsBudget: number) {
+ if (needsBudget > 1) {
+ needsBudget = 1;
+ }
+
+ if (needsBudget + wantsBudget > 1) {
+ wantsBudget = 1 - needsBudget;
+ }
+
+ this.needsBudget = round(needsBudget, 2);
+ this.wantsBudget = round(wantsBudget, 2);
+ }
+
+ static newChecked(needsBudget: number, wantsBudget: number): UserPrefs | Error {
+ if (needsBudget > 1) {
+ return new Error("needsBudget > 1!");
+ }
+
+ if (needsBudget + wantsBudget > 1) {
+ return new Error("needsBudget + wantsBudget > 1!");
+ }
+
+ return new UserPrefs(needsBudget, wantsBudget);
}
static default(): UserPrefs {
- return new UserPrefs(100);
+ return new UserPrefs(0.5, 0.3);
+ }
+
+ getNeedsBudget(): number {
+ return this.needsBudget;
+ }
+
+ getWantsBudget(): number {
+ return this.wantsBudget;
+ }
+
+ getSavingsBudget(): number {
+ return round(1 - this.wantsBudget - this.needsBudget, 2);
}
// Utility method for creating `Transactions`
@@ -20,7 +55,8 @@ export class UserPrefs {
if (!data) {
throw Error("No data returned for snapshot!");
}
- return new UserPrefs(data.goal);
+
+ return new UserPrefs(round(data.needsBudget, 2), round(data.wantsBudget, 2));
}
toSendObject(): object {