Skip to content

Commit

Permalink
Merge pull request #204 from LX6T/feature/transactions-graph
Browse files Browse the repository at this point in the history
[Feature] Finished graph implementation: Added custom date ranges, x-axis date formatting and colours to the graph
  • Loading branch information
Ben-G-Man authored Oct 8, 2024
2 parents 16526ac + 097353c commit a1e4512
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 172 deletions.
78 changes: 78 additions & 0 deletions frontend/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 frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"@testing-library/user-event": "^13.5.0",
"axios": "^1.7.3",
"react": "^18.3.1",
"react-datepicker": "^7.4.0",
"react-dom": "^18.3.1",
"react-icons": "^5.3.0",
"react-router-dom": "^6.26.0",
Expand Down
108 changes: 76 additions & 32 deletions frontend/src/components/metrics/BalanceGraph.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState, useContext, useEffect } from 'react';
import { add, format, differenceInCalendarDays } from "date-fns";
import {
ScatterChart,
Scatter,
Expand All @@ -14,54 +15,84 @@ export default function BalanceGraph() {

const {
balance,
transactions
transactions,
fromDate,
toDate
} = useContext(TransactionContext);



const [balanceData, setBalanceData] = useState([]);
const [domain, setDomain] = useState([]);
const [ticks, setTicks] = useState([]);

const parseDate = (dateString) => {
// Parse the date string
const date = new Date(dateString);

// Create a new date object for the start of the year
const startOfYear = new Date(date.getFullYear(), 0, 0);

// Calculate the difference in milliseconds
const diff = date - startOfYear;

// Convert milliseconds to days and return the day of the year
const oneDay = 1000 * 60 * 60 * 24;
const parseDate = Math.floor(diff / oneDay);

return parseDate;
}
const dateFormatter = (date) => {
return format(new Date(date), "dd-MM-yyyy");
};

useEffect(() => {

let pastBalance = balance
const newBalanceData = transactions.reduce((acc, transaction) => {
console.log(balance)
console.log(transactions)

setDomain([fromDate.getTime(), toDate.getTime()])
setTicks(getEvenSpacedPoints(fromDate, toDate, 5))

let lastTime = toDate
let lastBalance = balance
const verticalSegments = []
const flatSegments = transactions.reduce((acc, transaction) => {

// Parse the transaction amount to a number
const amount = parseFloat(transaction.amount);
const date = parseDate(transaction.created_at.substring(0, 10))

acc.push({ x: date, y: pastBalance });

// Parse the date string
const date = new Date(transaction.created_at);
console.log(date)

const flatSegment = [];
flatSegment.push({x: lastTime.getTime(), y: lastBalance})
flatSegment.push({x: date.getTime(), y: lastBalance})
acc.push({points: flatSegment, colour: "#000000"})

// Update the balance
pastBalance -= amount;
const newBalance = lastBalance - amount;

const verticalSegment = [];
const verticalSegmentColour = (lastBalance > newBalance) ? "#22c55e" : "#ef4444";
verticalSegment.push({x: date.getTime(), y: lastBalance})
verticalSegment.push({x: date.getTime(), y: newBalance})
verticalSegments.push({points: verticalSegment, colour: verticalSegmentColour})

// Push the new object with the creation date and updated balance
acc.push({ x: date, y: pastBalance });
// Update the balance
lastBalance -= amount;
lastTime = date;

return acc;
}, []);

newBalanceData.push({x: 0, y: pastBalance})
const lastSegment = [];
lastSegment.push({x: lastTime.getTime(), y: lastBalance})
lastSegment.push({x: fromDate.getTime(), y: lastBalance})
flatSegments.push({points: lastSegment, colour: "#000000"})

setBalanceData(newBalanceData)
}, [balance, transactions])
setBalanceData(flatSegments.concat(verticalSegments))

}, [balance, transactions, fromDate, toDate])

const getEvenSpacedPoints = (startDate, endDate, num) => {
const diffDays = differenceInCalendarDays(endDate, startDate);

let current = startDate,
step = Math.round(diffDays / (num - 1));

const ticks = [startDate.getTime()];

for (let i = 1; i < num - 1; i++) {
ticks.push(add(current, { days: i * step }).getTime());
}

ticks.push(endDate.getTime());
return ticks;
};

return (
<ResponsiveContainer width="100%" height={400}>
Expand All @@ -74,10 +105,23 @@ export default function BalanceGraph() {
}}
>
<CartesianGrid />
<XAxis type="number" dataKey="x" name="date"/>
<XAxis
name="date"
dataKey="x"
hasTick
scale="time"
tickFormatter={dateFormatter}
type="number"
domain={domain}
ticks={ticks}
/>
{/* <XAxis type="number" scale="time" dataKey="x" name="date" domain={[fromDate.getDate(), toDate.getDate()]} tickFormatter={dateFormatter}/> */}
<YAxis type="number" dataKey="y" name="balance" unit="$" />
<Tooltip cursor={{ strokeDasharray: '3 3' }} />
<Scatter data={balanceData} fill="#ff0000" line />

{balanceData.map((segment, i) => <Scatter data={segment.points} fill={segment.colour} line key={i} />)}
{/* <Scatter data={balanceData} fill="#ff0000" line /> */}

</ScatterChart>
</ResponsiveContainer>
);
Expand Down
12 changes: 8 additions & 4 deletions frontend/src/components/transactions/Transaction.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ const Transaction = ({ transaction }) => {
const [isAmountNegative, setIsAmountNegative] = useState(false);
const [convertedAmount, setConvertedAmount] = useState(transaction.amount);
const [showDetails, setShowDetails] = useState(false);
const { currency, handleSelect, currencySymbol } =
useContext(TransactionContext);
const { currency, handleSelect, currencySymbol } = useContext(TransactionContext);

const transactionDate = new Date(transaction.created_at.substring(0, 10));
const dateString = ((transactionDate.getDate() < 10) ? "0" : "") + transactionDate.getDate() + "-" +
((transactionDate.getMonth() + 1 < 10) ? "0" : "") + (transactionDate.getMonth() + 1) + "-" +
transactionDate.getFullYear()

// useEffect to check if each transaction is negative and then convert the currency
useEffect(() => {
Expand All @@ -23,7 +27,7 @@ const Transaction = ({ transaction }) => {
setIsAmountNegative(converted < 0);
};
convert();
}, [currency, transaction.amount, convertCurrency]);
}, [currency, transaction.amount]);

const handleCheckboxChange = (e) => {
handleSelect(transaction.id, e.target.checked);
Expand All @@ -44,7 +48,7 @@ const Transaction = ({ transaction }) => {
{transaction.title}
</p>

<p className="self-end">{transaction.created_at.substring(0, 10)}</p>
<p className="self-end">{dateString}</p>

</button>
</div>
Expand Down
Loading

0 comments on commit a1e4512

Please sign in to comment.