Skip to content

Commit

Permalink
Refactor MonthlyReport to Report
Browse files Browse the repository at this point in the history
Instead of having a singel report carrying information for multiple expenses, now
each report can only have to component associated to it.
  • Loading branch information
giovannibaratta committed May 24, 2024
1 parent 3f4f9c1 commit 66b94d9
Show file tree
Hide file tree
Showing 11 changed files with 615 additions and 304 deletions.
39 changes: 15 additions & 24 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import "./App.css"
import {CarComponent} from "./components/CarComponent.tsx"
import {CostsProjectionComponent, Report} from "./components/CostsProjectionComponent.tsx"
import {CostsProjectionComponent} from "./components/CostsProjectionComponent.tsx"
import {
Box,
Container,
Expand Down Expand Up @@ -35,6 +35,7 @@ import {Info} from "./components/InfoComponent.tsx"
import {MiscellaneousCostsComponent} from "./components/MiscellaneousComponent.tsx"
import MiscellaneousServicesTwoToneIcon from "@mui/icons-material/MiscellaneousServicesTwoTone"
import {buildMiscellaneousExpensesCalculator} from "./model/miscellaneous.ts"
import { Report } from "./model/monthly-report.ts"

const ONE_YEAR_IN_MS = 1 * 1000 * 60 * 60 * 24 * 365

Expand All @@ -46,9 +47,6 @@ function App() {
const incomeState = useAppSelector(state => state.income)
const houseState = useAppSelector(state => state.house)
const miscellaneousState = useAppSelector(state => state.miscellaneous)

const records: Report[] = []

const now = new Date(Date.now())
const simulationStartDate = getFirstDayOfNextMonthsFrom(now, 1)
let simulationCurrentDate = simulationStartDate
Expand Down Expand Up @@ -122,32 +120,25 @@ function App() {
canoneRai: miscellaneousState.canoneRai
})

const reports : Report[] = []

while (simulationCurrentDate.getTime() < simulationEndingDate.getTime()) {

const period = {
month: simulationCurrentDate.getMonth(),
year: simulationCurrentDate.getFullYear()
}

const carReport = carExpensesCalculator.computeMonthlyReport(period)
const incomeReport = incomeCalculator.computeMonthlyReport(period)
const houseReport = houseCalculator.computeMonthlyReport(period)
const furnitureReport = furnitureCalculator.computeMonthlyReport(period)
const houseAgencyReport = houseAgencyCalculator.computeMonthlyReport(period)
const miscellaneuousCostsReport = miscellaneousCostsCalculator.computeMonthlyReport(period)

const record: Report = {
date: simulationCurrentDate,
income: incomeReport.totalIncome,
totalMonthExpenses: carReport.totalExpenses +
incomeReport.totalExpenses +
houseReport.totalExpenses +
furnitureReport.totalExpenses +
houseAgencyReport.totalExpenses +
miscellaneuousCostsReport.totalExpenses
}
const periodReports = [
...carExpensesCalculator.generateReports(period),
...incomeCalculator.generateReports(period),
...houseCalculator.generateReports(period),
...furnitureCalculator.generateReports(period),
...houseAgencyCalculator.generateReports(period),
...miscellaneousCostsCalculator.generateReports(period)
]

records.push(record)
reports.push(...periodReports)
simulationCurrentDate = getFirstDayOfNextMonthsFrom(simulationCurrentDate, settingsState.resolutionInMonths)
}

Expand Down Expand Up @@ -260,8 +251,8 @@ function App() {
<Route path="/" element={<Info />} />
<Route path="/summary" element={<SummaryComponent simulationStartDate={simulationStartDate}
simulationEndingDate={simulationEndingDate}
records={records} />} />
<Route path="/projection" element={<CostsProjectionComponent data={records} />} />
records={reports} />} />
<Route path="/projection" element={<CostsProjectionComponent data={reports}/>} />
<Route path="/income" element={<IncomeComponent />} />
<Route path="/car" element={<CarComponent disabled={false} />} />
<Route path="/house" element={<HouseComponent />} />
Expand Down
83 changes: 65 additions & 18 deletions src/components/CostsProjectionComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {Paper, SxProps, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Theme} from "@mui/material"
import { Paper, SxProps, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Theme } from "@mui/material"
import { green, orange, red } from "@mui/material/colors"
import React from "react"
import {formatNumberToEuro} from "../utils/print.ts"
import {green, orange, red} from "@mui/material/colors"
import { Period, Report } from "../model/monthly-report.ts"
import { formatNumberToEuro } from "../utils/print.ts"

const DELTA_MEDIUM_THRESHOLD = 0
const DELTA_CRITICAL_THRESHOLD = -500
Expand Down Expand Up @@ -33,32 +34,78 @@ interface CostProjectionComponentProps {
data: ReadonlyArray<Report>
}

export interface Report {
readonly date: Date
readonly income: number
readonly totalMonthExpenses: number
interface Row {
balance: number
date: Date
income: number
totalMonthExpenses: number
delta: number
}

const generateRows = (data: ReadonlyArray<Report>) => {
const generateRows = (data: Map<Period, Report[]>) => {
const rows : Row[] = []

const periods = Array.from(data.keys())
periods.sort((a, b) => a.year - b.year || a.month - b.month)

let balance = 0

return data.map(it => {
for(const period of periods){
const reports = data.get(period)

const totalIncome = reports?.filter(it => it.type === "Income").reduce((previousValue, currentValue) => previousValue + currentValue.amount, 0) ?? 0
const totalMonthExpenses = reports?.filter(it => it.type === "Expense").reduce((previousValue, currentValue) => previousValue + currentValue.amount, 0) ?? 0
const delta = totalIncome - totalMonthExpenses

balance += it.income - it.totalMonthExpenses
balance += delta

if(reports){
rows.push({
balance: balance,
date: new Date(period.year, period.month),
income: totalIncome,
totalMonthExpenses: totalMonthExpenses,
delta
})

return {
balance,
date: it.date,
income: it.income,
totalMonthExpenses: it.totalMonthExpenses,
delta: it.income - it.totalMonthExpenses
}
})
}

return rows
}

const prepareDataForRendering = (reports: ReadonlyArray<Report>) => {

// Build a map where the key in the category and the value is a list of components for the given
// category. We could extract these lists from the types but in this way we are sure to fill
// only the ones that have a value.
const categoryComponentsMap = new Map<string, Set<string>>()
// Rearrange all the reports for a given period to ease the computation when displaying the data
const periodReportsMap = new Map<Period, Report[]>()

for (const report of reports) {
// Update the component categories map
const category = report.category
const components = categoryComponentsMap.get(category) ?? new Set()
components.add(report.component)
categoryComponentsMap.set(category, components)

// Aggregate the periods
const period = report.period
const reportsForPeriod = periodReportsMap.get(period) ?? []
reportsForPeriod.push(report)
periodReportsMap.set(period, reportsForPeriod)
}

return { periodReportsMap, categoryComponentsMap }
}

export const CostsProjectionComponent: React.FC<CostProjectionComponentProps> = (props: CostProjectionComponentProps) => {

const rows = generateRows(props.data)
const { data } = props

const { periodReportsMap } = prepareDataForRendering(data)
const rows = generateRows(periodReportsMap)

return (
<TableContainer component={Paper}>
Expand Down
20 changes: 13 additions & 7 deletions src/components/SummaryComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
import {Paper} from "@mui/material"
import {Report} from "./CostsProjectionComponent.tsx"
import React from "react"
import {formatNumberToEuro} from "../utils/print.ts"
import { Report } from "../model/monthly-report.ts"

export interface SummaryComponentProps {
simulationStartDate: Date,
simulationEndingDate: Date,
records: ReadonlyArray<Report>
}

interface Summary {
readonly totalExpenses: number,
readonly totalIncome: number
}

export const SummaryComponent: React.FC<SummaryComponentProps> = (props: SummaryComponentProps) => {

const {records, simulationEndingDate, simulationStartDate} = props

const summary = records.reduce<{
readonly totalExpenses: number,
readonly totalIncome: number
}>((acc, cur: Report) => {
const summary = records.reduce<Summary>((acc, cur: Report) => {

const recordExpenses = cur.type === "Expense" ? cur.amount : 0
const recordIncome = cur.type === "Income" ? cur.amount : 0

return {
totalExpenses: acc.totalExpenses + cur.totalMonthExpenses,
totalIncome: acc.totalIncome + cur.income
totalExpenses: acc.totalExpenses + recordExpenses,
totalIncome: acc.totalIncome + recordIncome
}
}, {totalExpenses: 0, totalIncome: 0})

Expand Down
Loading

0 comments on commit 66b94d9

Please sign in to comment.