-
Notifications
You must be signed in to change notification settings - Fork 3
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
feature/df 80 getbudgetbyparams #19
Changes from 13 commits
0baa781
8e7c735
f464056
3807694
979ad4d
e1536c0
4f33361
4516a09
72e4ce2
cbfcd34
4214f34
03e9ae1
4df1494
df95a16
e85c626
88c0a3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,28 +5,33 @@ import Dashboard from '../Dashboard/Dashboard' | |
// import transactionsFixtureData from "../sample-data/TransactionsData.json" | ||
// import incomeData from "../sample-data/IncomeData.json" | ||
// import expensesData from "../sample-data/ExpensesData.json" | ||
// import budgetsData from "../sample-data/BudgetsData.json" | ||
import './App.css'; | ||
import { useGetIncomes } from '../apollo-client/queries/getIncomes'; | ||
import { useGetExpenses } from '../apollo-client/queries/getExpenses'; | ||
import { useGetTransactions } from '../apollo-client/queries/getTransactions'; | ||
import { useGetCashFlow } from '../apollo-client/queries/getCashFlow'; | ||
|
||
import { useGetBudgetsByParams } from '../apollo-client/queries/getBudgetsByParams'; | ||
|
||
const App = () => { | ||
const [transactions, setTransactions] = useState([]); | ||
const [totalIncome, setTotalIncome] = useState(0); | ||
const [totalExpenses, setTotalExpenses] = useState(0); | ||
const [cashFlow, setCashFlow] = useState(null); | ||
const [budgets, setBudgets] = useState(null); | ||
|
||
// Hardcoded user, will pull from getUser endpoint soon | ||
const month = "2024-02"; | ||
// const category = "Travel"; | ||
const userName = "Powdered Toast Man"; | ||
const email = "[email protected]" | ||
localStorage.setItem('email', '[email protected]'); | ||
localStorage.setItem('email', email); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line can be removed since the localStorage object is not being retrieved anywhere and |
||
|
||
const { totalIncomeData } = useGetIncomes(email); | ||
const { totalExpensesData } = useGetExpenses(email); | ||
const { transactionsData } = useGetTransactions(email); | ||
const { cashFlowData } = useGetCashFlow(email); | ||
// const { budgetsData } = useGetBudgetsByParams(month, category, email); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like all other useQuery hooks that retrieve data when the App mounts, the useGetBudgetByParams should follow the DDAU design convention. |
||
|
||
useEffect(() => { | ||
if (totalIncomeData) { | ||
|
@@ -40,7 +45,8 @@ const App = () => { | |
if (totalExpensesData) setTotalExpenses(totalExpensesData); | ||
if (transactionsData) setTransactions(transactionsData); | ||
if (cashFlowData) setCashFlow(cashFlowData); | ||
}, [totalIncomeData, totalExpensesData, transactionsData, cashFlowData]); | ||
// if (budgetsData) setBudgets(budgetsData); | ||
}, [totalIncomeData, totalExpensesData, transactionsData, cashFlowData]); | ||
|
||
return ( | ||
<main className='app'> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,34 @@ | ||
import * as React from 'react'; | ||
import { PieChart } from '@mui/x-charts/PieChart'; | ||
|
||
const data = [ | ||
// Default data for fallback | ||
const defaultData = [ | ||
{ id: 0, value: 25 }, | ||
{ id: 1, value: 15 }, | ||
{ id: 1, value: 75 }, | ||
]; | ||
|
||
export default function PieActiveArc() { | ||
export default function BasicPie({ data }) { | ||
// Validate incoming data - simple example | ||
const isValidData = data && Array.isArray(data) && data.length > 0 && data.every(d => d.hasOwnProperty('value') && typeof d.value === 'number'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When the budgetData is properly drilled to this component, the chained conditionals can likely be reduced to something similar in the BarChart MUI component. |
||
|
||
// Use incoming data if valid, otherwise use default | ||
const pieData = isValidData ? data : defaultData; | ||
|
||
return ( | ||
<PieChart | ||
series={[ | ||
{ | ||
data, | ||
highlightScope: { faded: 'global', highlighted: 'item' }, | ||
faded: { innerRadius: 30, additionalRadius: -30, color: 'gray' }, | ||
cx: 140, | ||
innerRadius: 60, | ||
outerRadius: 100, | ||
paddingAngle: 5, | ||
cornerRadius: 5, | ||
}, | ||
]} | ||
height={200} | ||
/> | ||
<PieChart | ||
series={[ | ||
{ | ||
data: pieData, | ||
highlightScope: { faded: 'global', highlighted: 'item' }, | ||
faded: { innerRadius: 30, additionalRadius: -30, color: 'gray' }, | ||
cx: 140, | ||
innerRadius: 60, | ||
outerRadius: 100, | ||
paddingAngle: 5, | ||
cornerRadius: 5, | ||
}, | ||
]} | ||
height={200} | ||
/> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,48 +1,148 @@ | ||
import React from 'react' | ||
import React, { useState, useRef, useEffect, useMemo } from 'react' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove unused hook. |
||
import './Budget.css' | ||
import BasicPie from './BasicPie' | ||
import './budgetSelectModal.css' | ||
import DropDownIcon from '../../assets/icons/dropdown-icon.svg' | ||
import EllipsePurple from '../../assets/icons/Ellipse-purple.svg' | ||
import EllipseBlue from '../../assets/icons/Ellipse-blue.svg' | ||
import PlusIcon from '../../assets/icons/plus-icon.svg' | ||
import { useGetBudgetsByParams } from "../apollo-client/queries/getBudgetsByParams"; | ||
import { useGetBudgetCategories } from "../apollo-client/queries/getBudgetCategories"; | ||
|
||
const Budget = () => { | ||
return ( | ||
const email = "[email protected]"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This entire blocked dedicated to fetching budgets and budgetCategories should be be properly drilled through from App. Those states should not be set inside this child component. |
||
const { loading: loadingCategories, error: errorCategories, budgetCategoriesData } = useGetBudgetCategories(email); | ||
console.log("Fetched budgetCategoriesData:", budgetCategoriesData); | ||
const categories = loadingCategories || errorCategories ? [] : budgetCategoriesData || []; | ||
|
||
const [category, setCategory] = useState(); | ||
const [month, setMonth] = useState(getCurrentMonth()); | ||
const { loading, error, budgetsData } = useGetBudgetsByParams(month, category, email); | ||
// debugger; | ||
if (error) { | ||
console.error("Error fetching data:", error); | ||
} | ||
console.log("Fetched budgetData:", budgetsData); | ||
const pctRemaining = Math.round(budgetsData?.budgets[0]?.pctRemaining) || 'Loading...'; | ||
const amount = budgetsData?.budgets[0]?.amount || 'Loading...'; | ||
const amountRemaining = Math.round(budgetsData?.budgets[0]?.amountRemaining) || 'Loading...'; | ||
|
||
// State to manage dropdown visibility | ||
const [isDropdownVisible, setIsDropdownVisible] = useState(false); | ||
const [dropDownStyle, setDropdownStyle] = useState({}); // State to hold modal's dynamic style | ||
// references | ||
const dropdownRef = useRef(null); // Ref for the dropdown icon to position the modal | ||
const modalRef = useRef(null); // Add a ref for the modal | ||
|
||
// Handler functions for updating state | ||
const handleCategoryChange = (selectedCategory) => { | ||
setCategory(selectedCategory); | ||
setIsDropdownVisible(false); // Hide the modal | ||
}; | ||
// Handler to toggle dropdown visibility | ||
const toggleDropdownVisibility = () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider refactoring this to utilize a BasicSelect MUI component, there is no need to make these kinds of functions. |
||
setIsDropdownVisible(!isDropdownVisible); | ||
|
||
if (dropdownRef.current) { | ||
const { bottom, right } = dropdownRef.current.getBoundingClientRect(); | ||
const rightOffset = window.innerWidth - right; // Calculate the right offset from the viewport | ||
|
||
setDropdownStyle({ | ||
position: 'absolute', | ||
top: `${bottom}px`, | ||
right: `${rightOffset}px`, | ||
// Adjustments might be needed based on actual layout and styling | ||
}); | ||
} | ||
}; | ||
const handleMonthChange = (event) => { | ||
setMonth(event.target.value); | ||
}; | ||
// Utility function to get current month, implementation depends on your needs | ||
function getCurrentMonth() { | ||
const date = new Date(); | ||
const year = date.getFullYear(); // Get current year | ||
let month = date.getMonth() + 1; // Get current month (0-11, hence +1) | ||
month = month < 10 ? `0${month}` : month; // Ensure month is in two digits | ||
return `${year}-${month}`; // Concatenate to get "YYYY-MM" format | ||
} | ||
|
||
useEffect(() => { | ||
if (categories.length > 0) { | ||
setCategory(categories[0]); | ||
} | ||
}, [categories]); | ||
|
||
useEffect(() => { | ||
const handleClickOutside = (event) => { | ||
if (modalRef.current && !modalRef.current.contains(event.target) && | ||
dropdownRef.current && !dropdownRef.current.contains(event.target)) { | ||
setIsDropdownVisible(false); | ||
} | ||
}; | ||
|
||
document.addEventListener('mousedown', handleClickOutside); | ||
return () => { | ||
document.removeEventListener('mousedown', handleClickOutside); | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The BasicSelect component will remove the need for EventListeners like this. VanillaJS in React is not maintainable/conventional |
||
}, []); // This effect does not depend on `categories` | ||
|
||
return ( | ||
<aside className='budget'> | ||
<header className='budget-header'> | ||
<h2>Budget</h2> | ||
<img className='budget-dropdown-button' src={DropDownIcon} alt='dropdown icon' /> | ||
</header> | ||
<summary className='budget-pie-chart'> | ||
<BasicPie /> | ||
</summary> | ||
<section className='budget-percentage-breakdown'> | ||
<div className='percentage-container'> | ||
<div className='percentage-description'> | ||
<img src={EllipsePurple} alt='purple ellipse' /> | ||
<p>Daily payment</p> | ||
<h2>{category}</h2> | ||
<div ref={dropdownRef} onClick={toggleDropdownVisibility}> | ||
<img className='budget-dropdown-button' src={DropDownIcon} alt='dropdown icon' /> | ||
</div> | ||
<p className='percentage'>25%</p> | ||
</div> | ||
<div className='percentage-container'> | ||
{/* Conditionally render select dropdown */} | ||
{isDropdownVisible && ( | ||
<div ref={modalRef} className="select-modal" style={dropDownStyle}> | ||
{/* Dynamically generated modal content with options */} | ||
{categories.length > 0 ? ( | ||
categories.map((category, index) => ( | ||
<div key={index} onClick={() => handleCategoryChange(category)}>{category}</div> | ||
)) | ||
) : ( | ||
<div>No categories found.</div> // Or handle the empty state differently | ||
)} | ||
</div> | ||
)} | ||
</header> | ||
<summary className='budget-pie-chart'> | ||
<BasicPie | ||
data={[ | ||
{ id: 0, value: (100 - pctRemaining) }, | ||
{ id: 1, value: pctRemaining }, | ||
]} | ||
/> | ||
</summary> | ||
<section className='budget-percentage-breakdown'> | ||
<div className='percentage-container'> | ||
<div className='percentage-description'> | ||
<img src={EllipsePurple} alt='purple ellipse'/> | ||
<p>Budget Remaining</p> | ||
</div> | ||
<p className='percentage'>{pctRemaining}%</p> | ||
</div> | ||
<div className='percentage-container'> | ||
<div className='percentage-description'> | ||
<img src={EllipseBlue} alt='purple ellipse' /> | ||
<p>Hobby</p> | ||
<p>Budget Used</p> | ||
</div> | ||
<p className='percentage'>15%</p> | ||
<p className='percentage'>{100 - pctRemaining}%</p> | ||
</div> | ||
</section> | ||
<section className='budget-details-container'> | ||
<div className='budget-details'> | ||
<h3 className='budget-details-h3'>Set Budget</h3> | ||
<h3 className='budget-details-h3'>Budgeted Amount</h3> | ||
<div className='budget-details-flex'> | ||
<p className='budget-details-amount'>$200</p> | ||
<p className='budget-details-amount'>${amount}</p> | ||
</div> | ||
</div> | ||
<div className='budget-details'> | ||
<h3 className='budget-details-h3'>Remaining Budget</h3> | ||
<div className='budget-details-flex'> | ||
<p className='budget-details-amount'>$200</p> | ||
<p className='budget-details-amount'>${amountRemaining}</p> | ||
</div> | ||
</div> | ||
</section> | ||
|
@@ -53,7 +153,6 @@ const Budget = () => { | |
</div> | ||
</section> | ||
</aside> | ||
) | ||
} | ||
|
||
export default Budget | ||
) | ||
} | ||
export default Budget |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
.select-modal { | ||
cursor: pointer; | ||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | ||
background-color: rgba(255, 255, 255, 0.77); | ||
padding: 10px 0; | ||
border-radius: 4px; | ||
z-index: 100; /* Ensure it sits above other content */ | ||
} | ||
|
||
.select-modal div { | ||
padding: 7px 7px; /* Apply horizontal padding here for content alignment */ | ||
margin: 0; /* Remove margin to allow hover to fill from edge to edge */ | ||
width: 100%; /* Adjust width to account for padding */ | ||
box-sizing: border-box; /* Ensure padding is included in the width calculation */ | ||
} | ||
|
||
.select-modal div:hover { | ||
background-color: rgba(86, 77, 201, 0.7); /* Updated color for visibility */ | ||
/* Ensures hover effect extends to full width of each option */ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { gql, useQuery } from "@apollo/client"; | ||
|
||
export const GET_BUDGET_CATEGORIES = gql` | ||
query GetBudgetCategories($email: String!) { | ||
user(email: $email) { | ||
budgetCategories | ||
} | ||
}` | ||
|
||
export const useGetBudgetCategories= (email) => { | ||
const { loading, error, data } = useQuery(GET_BUDGET_CATEGORIES, { | ||
variables: { email: email }, | ||
fetchPolicy: "no-cache" | ||
}); | ||
let budgetCategoriesData = null; | ||
if (!loading && data) { | ||
budgetCategoriesData = data?.user?.budgetCategories || []; | ||
} | ||
|
||
return { loading, error, budgetCategoriesData }; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has an issue with being displayed on Transactions as $5.00 if the user actually wrote $500.00.
Could you look into this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not able to address this today...whoever coded the original logic might be able to lend some advice but there is certainly a disconnect between prop passing and the mutation/queries. This referenced line of code ensures the proper value is sent back through the mutation. Not sure if the issue is closer to data manipulation on the query (for transactions) or just on how it's displayed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's my thought. @trpubz your branch still does not appear have the correct
useCreateIncome
oruseCreateExpense
mutation that is onmain
andmain
seems to be functioning correctly updating state just not posting since the mutation merge yesterday.Here's how the FE app is handling floats for example with
totalIncome
:The
getIncomes
hook fetches and returns theincome
as a floatThen to avoid rounding errors, the
useEffect
handling settingincome
state coverts the float into a cents integer:Then whenever any amount needs to be displayed in the App (income, expenses, transaction etc.) it's formatted into a USD currency string:
This means we need to adhere to how floats are handled on
main
.A few more thoughts:
main
like theuseMutation
fixes from Friday.