Skip to content

Commit

Permalink
Merge pull request #39 from Robert-M-Lucas/dev-SCRUM-66
Browse files Browse the repository at this point in the history
Dev scrum 66
  • Loading branch information
Robert-M-Lucas authored Apr 27, 2024
2 parents 6f57bbc + 9da080f commit e65fee2
Show file tree
Hide file tree
Showing 19 changed files with 782 additions and 182 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"bootstrap": "^5.3.3",
"firebase": "^10.11.0",
"lodash": "^4.17.21",
"multi-range-slider-react": "^2.0.7",
"papaparse": "^5.4.1",
"react": "^18.2.0",
"react-bootstrap": "^2.10.2",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function Header() {
{/* Links to transactions page with table of expenses */}
<li><Link to="/transactions" className={`header-item ${isLoggedIn ? "header-item" : "text-muted bg-transparent"}`}>Transactions</Link></li>

<li><Link to="/test" className="header-item">Firestore Test</Link></li>
<li><Link to="/tiles" className="header-item">Firestore Test</Link></li>

{displayName && <li><span className="username">{displayName}</span></li>}

Expand Down
185 changes: 65 additions & 120 deletions src/pages/dashboard/DashboardPage.tsx
Original file line number Diff line number Diff line change
@@ -1,175 +1,120 @@
import "./styles.css";
import "react-tiles-dnd/esm/index.css";
import { TilesContainer, RenderTileFunction } from "react-tiles-dnd";
import useWindowDimensions from "../../hooks/WindowDimensionsHook.tsx";
import {Header} from "../../components/Header.tsx";
import {ReactNode, useEffect, useState} from "react";
import { getTransactionsFilterOrderBy, Transaction } from "../../utils/transaction.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 {getCurrentBalance} from "../../utils/transaction_utils.ts";
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 Graphs from "./graphs/Graphs.tsx"
import {getTileSize, TileElement} from "./TileUtils.ts";
import {finalGraphData, readTransactions} from "./graphs/GraphUtils.ts";
import {signInWithGoogle} from "../../utils/authentication.ts";
import totalTile from "./total tile/TotalTile.tsx";
import {getUserPrefs, UserPrefs} from "../../utils/user_prefs.ts";
import {User} from "firebase/auth";
import goalsTile from "./goals tile/GoalsTile.tsx";
import {RenderTileFunction, TilesContainer} from "react-tiles-dnd";

export default function Dashboard() {
const [balance, setBalance] = useState(0);
const [transactionPoints, setPoints] = useState<transactionPoint[][]>([[]]);
const [authResolved, setAuthResolved] = useState(false);
const [fetchResolved, setFetchResolved] = useState(false);
const [showCSVModal, setShowCSVModal] = useState(false);
const [showTransactionModal, setShowTransactionModal] = useState(false);
// const [balance, setBalance] = useState(0);
const [transactionPoints, setPoints] = useState<finalGraphData | null>(null);
const [transactions, setTransactions] = useState<Transaction[]>([]);
const [authResolved, setAuthResolved] = useState<Boolean>(false);
const [userPrefs, setUserPrefs] = useState<UserPrefs | null>(null);
// const draggable = useRef(true);
// const [showCSVModal, setShowCSVModal] = useState(false);
// const [showTransactionModal, setShowTransactionModal] = useState(false);
const [update, setUpdate] = useState(0)

const forceUpdate = () => {
setUpdate(update + 1);
setUserPrefs(null);
};

const tileSize = (tile: typeof transactionTiles[0]) => ({
colSpan: tile.cols,
rowSpan: tile.rows
});
const cumulateTransactions = (points: transactionPoint[]): transactionPoint[] => {
let total = 0;
return points.map(value => {
total += value.amount;
return {date: value.date, amount: total};
})
}
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}`;
}
const splitTransactions = (data: transactionPoint[]): void => {
const moneyIn: transactionPoint[] = []
const moneyOut: transactionPoint[] = []
data.forEach(t => {
if (t.amount > 0) {
moneyIn.push(t)
} else {
moneyOut.push(t)
}
})
setPoints([cumulateTransactions(data), cumulateTransactions(moneyIn), cumulateTransactions(moneyOut)])
console.log(transactionPoints)
}
const readTransactions = (data: Transaction[]): void => {
const result: transactionPoint[] = []
data.forEach(t => {
result.push({amount: t.amount, date: getDateString(t.dateTime)})
})
splitTransactions(result)
}
const fetchTransactions = async (user: User) => {
try {
const transactions = await getTransactionsFilterOrderBy(user, orderBy("dateTime", "desc"))
readTransactions(transactions)
setFetchResolved(true);
} catch (error) {}
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));
fetchTransactions(auth.currentUser).then(() => console.log("Fetched Transactions", transactionPoints));
fetchTransactions(auth.currentUser).then();
getUserPrefs(auth.currentUser).then((prefs) => setUserPrefs(prefs));
}
},[auth.currentUser])

},[auth.currentUser, update]);

if (!authResolved) {
auth.authStateReady().then(() => setAuthResolved(true));
return <>
<Header/>
<FullscreenCenter>
<div className="text-center">
<h1>Waiting for Auth</h1>
<h1>Waiting for Authentication</h1>
</div>
</FullscreenCenter>
</>;
}
if (!fetchResolved) {
auth.authStateReady().then(() => setAuthResolved(true));

if (auth.currentUser === null) {
auth.onAuthStateChanged(() => {
setUpdate(update + 1);
});
return <>
<Header/>
<FullscreenCenter>
<div className="text-center">
<h1>Not Logged In</h1>
<button type="button" className="login-with-google-btn" onClick={signInWithGoogle}>
Sign in with Google
</button>
</div>
</FullscreenCenter>
</>;
}

if (!transactionPoints || !userPrefs) {
return <>
<Header/>
<FullscreenCenter>
<div className="text-center">
<h1>Fetching</h1>
<h1>Fetching {transactionPoints ? "" : "transactions"}{!transactionPoints && !userPrefs ? "," : ""} {userPrefs ? "" : "goals"}</h1>
</div>
</FullscreenCenter>
</>;
}

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, forceUpdate)), 2, 2, 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<typeof transactionTiles[0]> = ({ data, isDragging }) => (
const renderTile: RenderTileFunction<TileElement> = ({ data, isDragging }) => (
<div style={{padding: ".75rem", width: "100%"}}>
<div className={`tile card ${isDragging ? "dragging" : ""}`}
style={{width: "100%", height: "100%"}}>
{data.d.isGraph() ? <Graphs data={data.d.forceGetGraph()}/> : data.d.forceGetTSX()()}
{data.isGraph() ? <Graphs data={data.forceGetGraph()}/> : data.forceGetTSX()()}
</div>
</div>
);

return (
<div className="vh-100 d-flex flex-column">
<Header/>
<div>
<Button variant="primary" onClick={() => setShowCSVModal(true)}>Upload CSV</Button>
<CSVUpload show={showCSVModal} setShow={setShowCSVModal}/>

<Button variant="primary" onClick={() => setShowTransactionModal(true)}>Add Transaction</Button>
<InputTransaction show={showTransactionModal} setShow={setShowTransactionModal}/>
</div>
<div className="App ps-5 pe-5 mt-3">
<button onClick={() => {
console.log(transactionPoints)
}}>Console Log Transactions
</button>
{balance}
<TilesContainer
data={transactionTiles}
renderTile={renderFirebase}
tileSize={tileSize}
renderTile={renderTile}
tileSize={getTileSize}
ratio={1}
columns={columns}
></TilesContainer>
Expand Down
37 changes: 0 additions & 37 deletions src/pages/dashboard/GraphUtils.ts

This file was deleted.

43 changes: 43 additions & 0 deletions src/pages/dashboard/TileUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {ReactNode} from "react";
import {transactionPoint} from "./graphs/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};
}
Loading

0 comments on commit e65fee2

Please sign in to comment.