common component props

This commit is contained in:
2026-05-18 14:18:36 +05:30
parent 8bea3d06f6
commit ceaeca70cc
14 changed files with 191 additions and 259 deletions

View File

@@ -1,5 +1,8 @@
import { mergeBucketPeriods, periodIdToKey } from "../report.helpers";
import { GroupKey, ReportData } from "../../features/report";
import {
extractFilteredTransactions,
aggregateTransactions,
} from "../report.helpers";
export interface PayeeItem {
name: string;
@@ -12,54 +15,17 @@ export function extractTopPayees(
selectedPeriodId?: string | null,
selectedGroupKey?: GroupKey | null
): { items: PayeeItem[]; total: number } {
const payeeMap = new Map<string, number>();
const txns = extractFilteredTransactions(reportData, selectedPeriodId, selectedGroupKey);
let targetPeriods = [];
if (selectedPeriodId) {
const key = periodIdToKey(selectedPeriodId);
const periods = mergeBucketPeriods(reportData.buckets, key);
const selected = periods.find((p) => p.id === selectedPeriodId);
if (selected) {
targetPeriods.push(selected);
const { items, total } = aggregateTransactions(txns, (txn) => {
if (txn.payee && txn.payee.name) {
return [txn.payee.name];
}
} else {
// If no specific period is selected, aggregate over the "all" period bucket
targetPeriods = mergeBucketPeriods(reportData.buckets, "all");
}
for (const p of targetPeriods) {
let txns = p.metric.transactions || [];
if (selectedGroupKey?.tags && selectedGroupKey.tags.length > 0) {
txns = txns.filter(txn => {
if (!txn.tags) return false;
const txnTags = txn.tags.map(t => typeof t === "string" ? t : t.name);
return selectedGroupKey.tags!.every(selectedTag => txnTags.includes(selectedTag));
});
}
for (const txn of txns) {
if (txn.payee && txn.payee.name) {
const current = payeeMap.get(txn.payee.name) || 0;
payeeMap.set(txn.payee.name, current + txn.amount);
}
}
}
let items: PayeeItem[] = [];
let total = 0;
for (const [name, amount] of payeeMap.entries()) {
items.push({ name, amount });
total += amount;
}
// Sort descending by amount
items.sort((a, b) => b.amount - a.amount);
return [];
});
return {
items: items.slice(0, 4), // Top 4
items,
total,
};
}

View File

@@ -1,30 +1,24 @@
import * as React from "react";
import { Box, Paper, Typography } from "@mui/material";
import { ReportData, GroupKey } from "../../features/report";
import { ComponentProps } from "../report.props";
import ProgressCard from "./ProgressCard";
import { extractTopPayees } from "./TopPayees.adapter";
type Props = {
reportData: ReportData;
flow: "outflows" | "inflows";
header: string;
selectedPeriodId?: string | null;
selectedGroupKey?: GroupKey | null;
setSelectedGroupKey?: (key: GroupKey | null) => void;
interface Props extends ComponentProps {
compact?: boolean;
isFetching?: boolean;
};
}
export default function TopPayees({
reportData,
flow,
state,
stateSetters,
header,
selectedPeriodId,
selectedGroupKey,
setSelectedGroupKey,
compact = true,
isFetching,
}: Props) {
const { flow, selectedPeriodId, selectedGroupKey } = state;
const { setSelectedGroupKey } = stateSetters;
const { items, total } = React.useMemo(() => {
return extractTopPayees(reportData, flow, selectedPeriodId, selectedGroupKey);
}, [reportData, flow, selectedPeriodId, selectedGroupKey]);

View File

@@ -1,11 +1,9 @@
import { ReportData } from "../../features/report";
import { ReportData, GroupKey } from "../../features/report";
import {
mergeBucketPeriods,
periodIdToKey,
extractFilteredTransactions,
aggregateTransactions,
} from "../report.helpers";
import { GroupKey } from "../../features/report";
export interface TagItem {
tag: string;
amount: number;
@@ -17,55 +15,17 @@ export function extractTopTags(
selectedPeriodId?: string | null,
selectedGroupKey?: GroupKey | null
): { items: TagItem[]; total: number } {
const tagMap = new Map<string, number>();
const txns = extractFilteredTransactions(reportData, selectedPeriodId, selectedGroupKey);
let periodKey: ReturnType<typeof periodIdToKey> = "all";
if (selectedPeriodId) {
periodKey = periodIdToKey(selectedPeriodId);
}
const periods = mergeBucketPeriods(reportData.buckets, periodKey);
let period = periods[0];
if (selectedPeriodId) {
period = periods.find(p => p.id === selectedPeriodId) || period;
} else if (periods.length > 0) {
period = periods.reduce((latest, p) =>
new Date(p.start).getTime() > new Date(latest.start).getTime()
? p
: latest
);
}
if (period && period.metric && period.metric.transactions) {
let txns = period.metric.transactions;
if (selectedGroupKey?.payee && selectedGroupKey.payee.length > 0) {
txns = txns.filter(txn =>
txn.payee?.name && selectedGroupKey.payee!.includes(txn.payee.name)
);
const { items, total } = aggregateTransactions(txns, (txn) => {
if (txn.tags && txn.tags.length > 0) {
return txn.tags.map((t) => (typeof t === "string" ? t : t.name));
}
return ["Untagged"];
});
for (const txn of txns) {
if (txn.tags && txn.tags.length > 0) {
for (const tagObj of txn.tags) {
const tagName = typeof tagObj === "string" ? tagObj : tagObj.name;
tagMap.set(tagName, (tagMap.get(tagName) || 0) + txn.amount);
}
} else {
tagMap.set("Untagged", (tagMap.get("Untagged") || 0) + txn.amount);
}
}
}
const arr = Array.from(tagMap.entries()).map(([tag, amount]) => ({
tag,
amount,
}));
arr.sort((a, b) => b.amount - a.amount);
const top = arr.slice(0, 4);
const total = top.reduce((sum, t) => sum + t.amount, 0);
return { items: top, total };
return {
items: items.map((item) => ({ tag: item.name, amount: item.amount })),
total,
};
}

View File

@@ -1,30 +1,24 @@
import * as React from "react";
import { Box, Paper, Typography } from "@mui/material";
import { ReportData, GroupKey } from "../../features/report";
import { ComponentProps } from "../report.props";
import ProgressCard from "./ProgressCard";
import { extractTopTags } from "./TopTags.adapter";
type Props = {
reportData: ReportData;
flow: "outflows" | "inflows";
header: string;
selectedPeriodId?: string | null;
selectedGroupKey?: GroupKey | null;
setSelectedGroupKey?: (key: GroupKey | null) => void;
interface Props extends ComponentProps {
compact?: boolean;
isFetching?: boolean;
};
}
export default function TopTags({
reportData,
flow,
state,
stateSetters,
header,
selectedPeriodId,
selectedGroupKey,
setSelectedGroupKey,
compact = true,
isFetching,
}: Props) {
const { flow, selectedPeriodId, selectedGroupKey } = state;
const { setSelectedGroupKey } = stateSetters;
const { items, total } = React.useMemo(() => {
return extractTopTags(reportData, flow, selectedPeriodId, selectedGroupKey);
}, [reportData, flow, selectedPeriodId, selectedGroupKey]);