Skip to content

Commit

Permalink
Merge branch 'master' into SCRUM-54
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/pages/dashboard/DashboardPage.tsx
  • Loading branch information
joesumargo committed Apr 26, 2024
2 parents 2179a62 + 10fe8f1 commit 25b137f
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 195 deletions.
82 changes: 41 additions & 41 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,52 @@
import useWindowDimensions from "../hooks/WindowDimensionsHook.tsx";
import { Link, useNavigate } from "react-router-dom";
import { auth } from "../utils/firebase.ts";
import { useEffect, useState } from "react";
import { User } from "firebase/auth";
import "../assets/css/Header.css"
import {Link, useNavigate} from "react-router-dom";
import {auth} from "../utils/firebase.ts";
import {useState} from "react";
import {User} from "firebase/auth";

export function Header() {
const { width, height } = useWindowDimensions();
const aspect_ratio = (width == 0? 1 : width) / (height == 0? 1 : height);
const aspect_ratio = (width == 0 ? 1 : width) / (height == 0 ? 1 : height);
const use_narrow = aspect_ratio < 0.7;

const [userName, setUserName] = useState<string | null>(null);
auth.onAuthStateChanged((new_user: User | null) => {
if (new_user === null) { setUserName(null); }
else { setUserName(new_user?.displayName) }
});

const navigate = useNavigate();

const handleLogout = async () => {

const [isLoggedIn, setIsLoggedIn] = useState<boolean>(!!auth.currentUser?.uid);
// displayName is optional, an account may not have a displayName but is logged in
const [displayName, setDisplayName] = useState<string | null | undefined>(auth.currentUser?.displayName);

// only attach the event listener once, otherwise we get an infinite loop
useEffect(() => {
auth.onAuthStateChanged((user: User | null) => {
setIsLoggedIn(!!user?.uid);
setDisplayName(user?.displayName);
});
}, []);

async function handleLogout() {
await auth.signOut();

navigate("/", { replace: true });
};

return (
<header className="header">
{/* App Name reloads home page */}
<a href="/" className="site-title">
<h3>{use_narrow? "B-19" : "Budget-19"}</h3>
</a>

{/* Pages */}
<ul className="header-nav">
{/* Links to dashboard with tiles */}
<li><Link to="/dash" className={`header-item ${userName ? "header-item" : "text-muted bg-transparent"}`}>Dashboard</Link></li>

{/* Links to transactions page with table of expenses */}
<li><Link to="/transactions" className={`header-item ${userName ? "header-item" : "text-muted bg-transparent"}`}>Transactions</Link></li>

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

{userName && (
<li>
<span className="username">{userName}</span>
<button type="button" className="logout-btn" onClick={handleLogout}>Logout</button>
</li>
)}
</ul>
</header>
);
}

return <header className="header">
{/* App Name reloads home page */}
<h3><Link to="/">{use_narrow ? "B-19" : "Budget-19"}</Link></h3>

{/* Pages */}
<ul className="header-nav">
{/* Links to dashboard with tiles */}
<li><Link to="/dash" className={`header-item ${isLoggedIn ? "header-item" : "text-muted bg-transparent"}`}>Dashboard</Link></li>

{/* 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>

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

{isLoggedIn && <li><button type="button" className="logout-btn" onClick={handleLogout}>Logout</button></li>}
</ul>
</header>
}
3 changes: 1 addition & 2 deletions src/components/transactions/InputTransaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Button, Modal, Form, Alert } from "react-bootstrap";
import { auth } from "../../utils/firebase";
import { writeNewTransaction } from "../../utils/transaction.ts";

export function InputTransaction({ show, setShow}: { show: boolean, setShow: React.Dispatch<React.SetStateAction<boolean>> }) {
export function InputTransaction({ show, setShow }: { show: boolean, setShow: React.Dispatch<React.SetStateAction<boolean>> }) {
const [name, setName] = useState<string>("");
const [category, setCategory] = useState<string>("Income");

Expand Down Expand Up @@ -49,7 +49,6 @@ export function InputTransaction({ show, setShow}: { show: boolean, setShow: Rea
setTimeout(() => setSuccessMsg(null), 10000);

setName("");
setCategory("");
setAmount("");
setDescription("");
setNotes("");
Expand Down
4 changes: 2 additions & 2 deletions src/components/transactions/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class Transaction {
this.isValid = false;
this.invalidField = "date";
} else {
this.date = date;
this.date = `${year}-${month}-${day}`;
}

return this;
Expand Down Expand Up @@ -156,7 +156,7 @@ export class Transaction {
this.amount as number,
this.category as string,
this.currency as string,
new Date((this.date as string) + (this.time as string)).getTime(),
new Date((this.date as string) + " " + (this.time as string)).getTime(),
this.description as string,
this.emoji as string,
this.name as string,
Expand Down
198 changes: 48 additions & 150 deletions src/pages/dashboard/DashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,167 +2,65 @@ import "./styles.css";
import "react-tiles-dnd/esm/index.css";
import { TilesContainer, RenderTileFunction } from "react-tiles-dnd";
import useWindowDimensions from "../../hooks/WindowDimensionsHook.tsx";
import {Link} from "react-router-dom";
import {Header} from "../../components/Header.tsx";
import React, {ReactNode, useEffect, useState} from "react";
import { getTransactionsFilterOrderBy, Transaction } 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"
import {Sidebar} from "../../components/Sidebar.tsx";
import { Button } from "react-bootstrap";
import { CSVUpload } from "../../components/transactions/CSVUpload.tsx";
import { InputTransaction } from "../../components/transactions/InputTransaction.tsx";
import { useState } from "react";

type transactionPoint = { date: string; amount: number }
type tsxContents = ReactNode;
// ? Lot of code obtained from here for testing: https://codesandbox.io/p/sandbox/react-tiles-dnd-responsive-bd0ly?file=%2Fsrc%2Findex.tsx

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!;
}
interface Props {
tiles: Array<{ text: string; rows: number; cols: number }>
}

export default function Dashboard() {
const [balance, setBalance] = useState(0);
const [transactionPoints, setPoints] = useState<transactionPoint[][]>([[]]);
const [authResolved, setAuthResolved] = useState(false);
const [showCSVModal, setShowCSVModal] = useState(false);
const [showTransactionModal, setShowTransactionModal] = useState(false);

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)
} catch (error) {}
}

const render: RenderTileFunction<{ text: string; rows: number; cols: number }> = ({ data, isDragging }) => (
<div style={{ padding: ".75rem", width: "100%" }}>
<div className={`tile card ${isDragging ? "dragging" : ""}`}
style={{ width: "100%", height: "100%" }} >
{data.text} {isDragging ? "DRAGGING" : null}
</div>
</div>
);
const tileSize = (tile: { text: string; rows: number; cols: number }) => ({
colSpan: tile.cols,
rowSpan: tile.rows
});

export default function Dashboard(props: Props) {
const {width} = useWindowDimensions();
const columns = Math.max(Math.floor(width / 200), 1);

const [showCSVModal, setShowCSVModal] = useState(false);
const [showTransactionModal, setShowTransactionModal] = useState(false);

// 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));
}
},[auth.currentUser])


if (!authResolved) {
auth.authStateReady().then(() => setAuthResolved(true));
return <>
<FullscreenCenter>
<div className="text-center">
<h1>Waiting for Auth</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 renderFirebase: RenderTileFunction<typeof transactionTiles[0]> = ({ data, isDragging }) => (
<div style={{padding: ".75rem", width: "100%"}}>
<div className={`tile card ${isDragging ? "dragging" : ""}`}
style={{width: "100%", height: "100%"}}>
{console.log(data.d)}
{data.d.isGraph() ? <Graphs data={data.d.forceGetGraph()}/> : data.d.forceGetTSX()()}
</div>
</div>
);

return (
return <>
<div className="vh-100 d-flex flex-column">
<Header user="testUser"/>
<div>
<Header/>

{/* TODO: MOVE TO CORRECT POSITION ON DASHBOARD */}
<div>
<Button variant="primary" onClick={() => setShowCSVModal(true)}>Upload CSV</Button>
<CSVUpload show={showCSVModal} setShow={setShowCSVModal}/>
<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}
ratio={1}
columns={columns}
></TilesContainer>
<InputTransaction show={showTransactionModal} setShow={setShowTransactionModal} />
</div>
</div>
);
<Sidebar>
<div className="App ps-5 pe-5 mt-3">
<h1>Testing Tiles</h1>
<p><Link to={"/"}>Go back</Link></p>
<TilesContainer
data={props.tiles}
renderTile={render}
tileSize={tileSize}
ratio={1}
columns={columns}
/>
</div>
</Sidebar>
</div>
</>
}

0 comments on commit 25b137f

Please sign in to comment.