diff --git a/app/components/BottomSheet.tsx b/app/components/BottomSheet.tsx index 30b1ba4..5c751c6 100644 --- a/app/components/BottomSheet.tsx +++ b/app/components/BottomSheet.tsx @@ -8,6 +8,8 @@ type BottomSheetProps = { className?: string; setShow: React.Dispatch>; closeButtonSize?: "sm" | "lg"; + addToHistoryStack?: boolean; + onCloseClick?: () => void; children?: any; }; @@ -16,10 +18,12 @@ export const BottomSheet = ({ className, setShow, closeButtonSize, + addToHistoryStack = true, + onCloseClick = () => {}, children, }: BottomSheetProps) => { useEffect(() => { - if (show) { + if (show && addToHistoryStack) { history.pushState(null, document.title, window.location.href); window.onpopstate = async () => { window.onpopstate = () => {}; @@ -49,7 +53,7 @@ export const BottomSheet = ({ {/* Bottom Sheet */}
{ + typeof onCloseClick === "function" && onCloseClick(); setShow(false); window.onpopstate = () => {}; history.back(); diff --git a/app/components/MonthYearSelector.tsx b/app/components/MonthYearSelector.tsx index a84e329..b8faee3 100644 --- a/app/components/MonthYearSelector.tsx +++ b/app/components/MonthYearSelector.tsx @@ -23,6 +23,7 @@ export default function MonthYearSelector({ submitAction, isSubscriptionRequired, context, + onSubmit, }: { startMonth: number; startYear: number; @@ -32,6 +33,7 @@ export default function MonthYearSelector({ submitAction: string; isSubscriptionRequired: boolean; context: AppContext; + onSubmit?: () => void; }) { const [compareSettingContainer] = useAutoAnimate(); const [showCompareSettingButton, setShowCompareSettingButton] = useState(true); @@ -45,7 +47,11 @@ export default function MonthYearSelector({ } }, []); return ( -
+ {showCompareSettingButton && (
@@ -155,6 +161,7 @@ export default function MonthYearSelector({ className="btn-secondary w-full whitespace-nowrap" onClick={(e) => { e.preventDefault(); + typeof onSubmit === "function" && onSubmit(); isMobileDevice() && setShowCompareSetting(false); submit(e.currentTarget, { method: "GET", diff --git a/app/modules/reports/reports.service.ts b/app/modules/reports/reports.service.ts index 9ab273b..41da6fa 100644 --- a/app/modules/reports/reports.service.ts +++ b/app/modules/reports/reports.service.ts @@ -53,9 +53,7 @@ type TrendReportResponse = { date: string; budget: Prisma.Decimal; expense: Prisma.Decimal; - income: Prisma.Decimal; incomeEarned: Prisma.Decimal; - investment: Prisma.Decimal; investmentDone: Prisma.Decimal; }[]; totalExpense: Prisma.Decimal; @@ -67,20 +65,20 @@ export type ExpenseTrendReportResponse = Omit< TrendReportResponse, "totalIncomeEarned" | "totalInvestmentDone" > & { - categoryExpensesByCategory: { [key: string]: CategoryExpenseDate[] }; - expensesByPaymentMode: { [key: string]: CategoryExpenseDate[] }; + categoryExpensesByCategory: { [key: string]: ExpensePerDate[] }; + expensesByPaymentMode: { [key: string]: ExpensePerDate[] }; }; export type InvestmentTrendReportResponse = Omit< TrendReportResponse, "totalExpense" | "totalIncomeEarned" > & { - categoryInvestmentsByCategory: { [key: string]: CategoryInvestmentDate[] }; - investmentsByPaymentMode: { [key: string]: CategoryInvestmentDate[] }; + categoryInvestmentsByCategory: { [key: string]: InvestmentPerDate[] }; + investmentsByPaymentMode: { [key: string]: InvestmentPerDate[] }; }; export type IncomeTrendReportResponse = TrendReportResponse & { - categoryIncomeByCategory: { [key: string]: CategoryIncomeDate[] }; + categoryIncomeByCategory: { [key: string]: IncomePerDate[] }; }; export async function getThisMonthReport( @@ -286,8 +284,12 @@ export async function getExpenseTrendReport( return { targets, totalExpense, - categoryExpensesByCategory: Object.fromEntries(groupBy(categoryExpenses, "category")), - expensesByPaymentMode: Object.fromEntries(groupBy(expensesByPaymentMode, "category")), + categoryExpensesByCategory: Object.fromEntries( + groupBy(categoryExpenses, "category", true) + ), + expensesByPaymentMode: Object.fromEntries( + groupBy(expensesByPaymentMode, "category", true) + ), }; } @@ -328,10 +330,10 @@ export async function getInvestmentTrendReport( targets, totalInvestmentDone, categoryInvestmentsByCategory: Object.fromEntries( - groupBy(categoryInvestments, "category") + groupBy(categoryInvestments, "category", true) ), investmentsByPaymentMode: Object.fromEntries( - groupBy(investmentsByPaymentMode, "category") + groupBy(investmentsByPaymentMode, "category", true) ), }; } @@ -379,7 +381,9 @@ export async function getIncomeTrendReport( totalExpense, totalIncomeEarned, totalInvestmentDone, - categoryIncomeByCategory: Object.fromEntries(groupBy(categoryIncomes, "category")), + categoryIncomeByCategory: Object.fromEntries( + groupBy(categoryIncomes, "category", true) + ), }; } @@ -565,9 +569,12 @@ type CategoryAmount = CategoryDate & { [prop in K]: T; }; -export type CategoryExpenseDate = CategoryAmount; -export type CategoryInvestmentDate = CategoryAmount; -export type CategoryIncomeDate = CategoryAmount; +export type ExpensePerDate = Omit, "category">; +export type InvestmentPerDate = Omit< + CategoryAmount, + "category" +>; +export type IncomePerDate = Omit, "category">; async function getAmountPerCategoryForTimeRange( userId: string, @@ -641,9 +648,7 @@ async function getTargets(userId: string, startDate: Date, endDate: Date) { date: true, budget: true, expense: true, - income: true, incomeEarned: true, - investment: true, investmentDone: true, }, where: { diff --git a/app/root.tsx b/app/root.tsx index b7aff02..a395dfe 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -19,7 +19,7 @@ import { import styles from "./styles/app.css"; import Report from "./components/Report"; import GenericError from "./components/GenericError"; -import type { ReactElement } from "react"; +import type { ReactElement, ReactNode } from "react"; import { useEffect, useRef, useState } from "react"; import { Spacer } from "./components/Spacer"; import { isNotNullAndEmpty } from "./utils/text.utils"; @@ -154,7 +154,7 @@ export const shouldRevalidate: ShouldRevalidateFunction = ({ export type DialogProps = { title: string; - message: string; + message: ReactNode | string; showDialog: boolean; positiveButton?: string; negativeButton?: string; @@ -173,6 +173,8 @@ export type BottomSheetProps = { show: boolean; content: ReactElement; closeButtonSize?: "sm" | "lg"; + addToHistoryStack?: boolean; + onCloseClick?: () => void; }; export type AppContext = { @@ -532,7 +534,9 @@ export default function App() { { setBottomSheetProps((prev) => ({ ...prev, show: !prev.show })); }} diff --git a/app/routes/reports/trend/expense.tsx b/app/routes/reports/trend/expense.tsx index 16cf36f..420a1ae 100644 --- a/app/routes/reports/trend/expense.tsx +++ b/app/routes/reports/trend/expense.tsx @@ -58,10 +58,10 @@ export default function ExpenseTrendReport() { const trendingReportContext = useOutletContext(); const expenseDistribution = Object.entries(categoryExpensesByCategory) - .map(([category, categoryExpenses]) => { + .map(([category, expensePerDate]) => { return { name: category.toString(), - value: sum(categoryExpenses.map((c) => c.expense)), + value: sum(expensePerDate.map((c) => c.expense)), }; }) .sort((a, b) => (calculate(b.value).minus(a.value).gt(0) ? 1 : -1)); diff --git a/app/utils/array.utils.ts b/app/utils/array.utils.ts index f57d2a9..103eddc 100644 --- a/app/utils/array.utils.ts +++ b/app/utils/array.utils.ts @@ -1,9 +1,15 @@ import { formatDate_MMMM_YYYY } from "./date.utils"; +import { isNullOrEmpty } from "./text.utils"; -export function groupBy(array: T[], key: keyof T) { +export function groupBy(array: T[], key: keyof T, includeKeyInObject = false) { let map = new Map(); - for (const item of array) { + for (let item of array) { const searchKey = (item[key] as unknown as string).trim(); + if (isNullOrEmpty(searchKey)) continue; + if (includeKeyInObject) { + item = { ...item }; + delete item[key]; + } if (map.has(searchKey)) { map.get(searchKey)!.push(item); } else {