Skip to content

Commit

Permalink
Create Goals and GoalsAdd subpage, add and delete goal feature for Is…
Browse files Browse the repository at this point in the history
…sue #14
  • Loading branch information
shangguanwang committed Sep 17, 2023
1 parent c0e7a84 commit 29dfb45
Show file tree
Hide file tree
Showing 13 changed files with 195 additions and 20 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ FlowFi is a personal financial planner that allows users to monitor their financ
- Home Dashboard - overview
- Assets - track assets by total amount and types
- Debt - manage debt by total amount and types
- Income - plan monthly income
- Goals - work towards your saving goals
- Expenses - record monthly spending

## Built with 🛠️
Expand Down
6 changes: 4 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import './App.css';
import {Layout, Home, Assets, Debt, Income, Expenses, AssetsAdd, Landing, Register} from './views';
import {Layout, Home, Assets, Debt, Goals, Expenses, AssetsAdd, Landing, Register} from './views';
import AssetsEdit from './views/AssetsEdit';
import DebtAdd from './views/Debt/DebtAdd';
import DebtEdit from './views/Debt/DebtEdit';
import GoalsAdd from './views/Goals/GoalsAdd';

function App() {
//const isLoggedIn = useSelector((state:RootState) => state.auth.isLoggedIn); // comment out for now
Expand All @@ -24,7 +25,8 @@ function App() {
<Route path="/debt" element={<Debt/>}/>
<Route path="debt/add" element={<DebtAdd />}/>
<Route path="debt/edit/:id" element={<DebtEdit />}/>
<Route path="/income" element={<Income/>}/>
<Route path="/goals" element={<Goals/>}/>
<Route path="/goals/add" element={<GoalsAdd />}/>
<Route path="/expenses" element={<Expenses/>}/>
</Route>):
(<>
Expand Down
13 changes: 13 additions & 0 deletions src/api/firebase.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ export async function addDebtData({ Name, Amount, debtType, debtApr }) {
debtApr: debtApr,
});
}
/**
* Add a new asset to the user's Goals list in Firestore
* @param {Name, Amount, Due} formData
*/
export async function addGoalsData({ Name, Amount, Due }) {
const userDocRef = doc(db, "users", "testuser");
const assetsCollectionRef = collection(userDocRef, "goals");
return addDoc(assetsCollectionRef, {
Name: Name,
Amount: Amount,
Due: Due,
});
}

/** Get Data from user's list in Firestore
* @param {collectionName} === "assets", "debts"
Expand Down
14 changes: 14 additions & 0 deletions src/redux/goalsSlice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createSlice } from "@reduxjs/toolkit";

const goalsSlice = createSlice({
name:'goals',
initialState: [],
reducers: {
setGoalsData: (state, action)=>{
return action.payload;
}
}
})

export const {setGoalsData} = goalsSlice.actions;
export default goalsSlice.reducer;
2 changes: 2 additions & 0 deletions src/redux/store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { configureStore } from "@reduxjs/toolkit";
import authSlice from "../features/auth/authSlice";
import assetsReducer from "./assetsSlice";
import debtReducer from "./debtSlice";
import goalsReducer from "./goalsSlice"

const store = configureStore({
reducer: {
auth: authSlice.reducer,
assets: assetsReducer,
debt: debtReducer,
goals: goalsReducer,
}
});

Expand Down
7 changes: 7 additions & 0 deletions src/state/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ export interface DebtFormType {
debtApr: number;
}

// interface for the goalsFormData object
export interface GoalFormType{
id?:string;
Name: string;
Amount: number;
Due: Date;
}
// user info
export interface userInfoType {
email: string;
Expand Down
4 changes: 2 additions & 2 deletions src/views/Assets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import NumberCard from '../components/layout/NumberCard';
export const Assets = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const assetsData = useSelector((store: RootState)=>store.assets);
const assetsData:AssetsFormType[] = useSelector((store: RootState)=>store.assets);
//calculate assets total
const totalAmount = calculateAssetTotal(assetsData);
const totalAmount:number = calculateAssetTotal(assetsData);

//define a delete function
const handleDelete = (id:string) => {
Expand Down
1 change: 0 additions & 1 deletion src/views/AssetsAdd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export const AssetsAdd = () => {
e.preventDefault();
try {
await addAssetsData(formData);

} catch(error){
console.log(error);
}
Expand Down
98 changes: 98 additions & 0 deletions src/views/Goals/Goals.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React, { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import AddButton from '../../components/layout/AddButton';
import { getData, deleteData } from '../../api/firebase';
import { DatagridColumnType, GoalFormType } from '../../state/types';
//import redux
import { RootState } from '../../redux/store';
import { setGoalsData } from '../../redux/goalsSlice';
import { useDispatch, useSelector } from 'react-redux';
//import MUI
import Box from '@mui/material/Box';
import { DataGrid, GridCellParams, GridActionsCellItem} from '@mui/x-data-grid';
import DeleteIcon from '@mui/icons-material/Delete';
import EditIcon from '@mui/icons-material/Edit';


export const Goals = () => {
const goalsData: GoalFormType[] = useSelector((store: RootState)=>store.goals);
const dispatch = useDispatch();
const navigate = useNavigate();
//define a delete function
const handleDelete = (id:string) => {
deleteData(id, "goals");
// delete the row from data grid
const updatedGoals = goalsData.filter((item)=>item["id"] !== id);
dispatch(setGoalsData(updatedGoals));
}
//define an edit function that directs users to a separate page to edit a particular debt item
const handleEdit = (id:string) => {
const goalsToEdit = goalsData.find((item)=>item["id"] === id);

if(goalsToEdit){
navigate(`/goals/edit/${id}`, {state: goalsToEdit});
}
}
const goalsColumns: DatagridColumnType[] = [
{
field: "Name",
headerName: "Name",
width: 150,
},
{
field: "Amount",
headerName: "Amount",
width: 150,
renderCell: (params: GridCellParams)=> `$${params.value}`, //add a dollar sign
},
{
field: "Due",
headerName: "Due Date",
width: 150,
},
{field: "actions",
headerName: "Actions",
type:"actions",
width: 100,
getActions: (params) => {
const id = String(params.id);

return[
<GridActionsCellItem
icon={<EditIcon />}
label="Edit"
onClick={()=>handleEdit(id)}
/>,
<GridActionsCellItem
icon={<DeleteIcon />}
label="Delete"
onClick={()=>handleDelete(id)}
/> ]
}
}
]
// fetch data from Firebase when the component mounts
useEffect(()=>{
const fetchData = async() => {
const data: GoalFormType[] = await getData("goals");
const formattedData: GoalFormType[] = data.map((item)=>({
...item,
Amount: Number(item.Amount), // ensure Amount has type number
}))
dispatch(setGoalsData(formattedData));
};
fetchData();
},[dispatch]);

return (
<div className="subpage">
<h1>Goals</h1>
<Box mt="1rem" p="0 0.5rem" sx={{ width: '40%'}}>
<DataGrid autoHeight rows={goalsData} columns={goalsColumns} hideFooter={true}/>
</Box>
<AddButton linkurl="/goals/add" btntxt="Add Goal" />
</div>
)
}

export default Goals
51 changes: 51 additions & 0 deletions src/views/Goals/GoalsAdd.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import SubmitButton from '../../components/layout/SubmitButton';
import FormRow from '../../components/FormRow';
import { GoalFormType } from '../../state/types';
import { TextField } from '@mui/material';
import { addGoalsData } from '../../api';

const GoalsAdd = () => {
const [formData, setFormData] = useState<GoalFormType>({
Name: '',
Amount: 0,
Due: new Date(),
})

// handleAdd() function updates formData as we receive user input
const handleAdd =(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const {name, value, type} = e.target;
setFormData(prevData => ({
...prevData,
[name]: type==='number'?parseFloat(value):value,
}))
}
// useNavigate() redirects users back to Assets page upon successful form submission
const navigate = useNavigate();

// handleSubmit() sends formData to the database
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
try {
await addGoalsData(formData);
} catch(error){
console.log(error);
}
navigate("/goals"); //redirects back to Goals page
};
return (
<div className="subpage">
<h1>Add Goal</h1>
<form onSubmit={handleSubmit}>
<FormRow type="text" name="Name" value={formData.Name} handleChange={handleAdd} textLabel="Name"/>
<FormRow type="number" name="Amount" value={formData.Amount} handleChange={handleAdd} textLabel="Amount"/>
<br/>
<TextField type="date" label="Due Date" name="Due" value={formData.Due} onChange={handleAdd} fullWidth required></TextField>
<SubmitButton textLabel="Save" />
</form>
</div>
)
}

export default GoalsAdd
11 changes: 0 additions & 11 deletions src/views/Income.tsx

This file was deleted.

4 changes: 2 additions & 2 deletions src/views/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ export const Layout = () => {
<AccountBalanceIcon />
Debt
</NavLink>
<NavLink to="/income" className="Nav-link">
<NavLink to="/goals" className="Nav-link">
<MonetizationOnIcon />
Income
Goals
</NavLink>
<NavLink to="/expenses" className="Nav-link">
<AccountBalanceWalletIcon />
Expand Down
2 changes: 1 addition & 1 deletion src/views/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ export * from "./Home";
export * from "./Assets";
export * from "./AssetsAdd";
export * from "./Debt/Debt";
export * from "./Income";
export * from "./Goals/Goals";
export * from "./Expenses";

0 comments on commit 29dfb45

Please sign in to comment.