Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev scrum 66 #38

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading