Compare commits
5 Commits
5f0fa91075
...
e6c7778c08
| Author | SHA1 | Date | |
|---|---|---|---|
| e6c7778c08 | |||
| f320f6ff31 | |||
| fc88703a38 | |||
| 787324260c | |||
| 8a866566ba |
@@ -36,8 +36,9 @@ export default function Dashboard() {
|
||||
income: null
|
||||
});
|
||||
|
||||
const [mode, setMode] =
|
||||
React.useState<"expense" | "income">("expense");
|
||||
const [mode, setMode] = React.useState<"expense" | "income">("expense");
|
||||
const [period, setPeriod] = React.useState<"rolling" | "calendar">("rolling");
|
||||
const [comparison, setComparison] = React.useState(false);
|
||||
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
@@ -130,8 +131,12 @@ export default function Dashboard() {
|
||||
<HistoryChart
|
||||
header={`${mode === "expense" ? "Expense" : "Income"} Breakdown`}
|
||||
summary="Interactive chronological tracking"
|
||||
tabs={["Week", "Month", "Year"]}
|
||||
data={currentData?.chartData || {}}
|
||||
tabs={["Daily", "Weekly", "Monthly"]}
|
||||
data={currentData.chartData}
|
||||
period={period}
|
||||
onPeriodChange={setPeriod}
|
||||
comparison={comparison}
|
||||
setComparison={setComparison}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -4,15 +4,31 @@ import { Box, Typography, ToggleButtonGroup, ToggleButton, Paper } from "@mui/ma
|
||||
export interface ChartDataPoint {
|
||||
id: string;
|
||||
amount: number;
|
||||
compareAmount?: number;
|
||||
count?: number;
|
||||
highlighted?: boolean;
|
||||
}
|
||||
|
||||
export interface ChartSeries {
|
||||
rolling: ChartDataPoint[];
|
||||
calendar: ChartDataPoint[];
|
||||
}
|
||||
|
||||
export interface ChartData {
|
||||
daily: ChartDataPoint[];
|
||||
weekly: ChartSeries;
|
||||
monthly: ChartSeries;
|
||||
}
|
||||
|
||||
export interface HistoryChartProps {
|
||||
header: string;
|
||||
summary?: string;
|
||||
tabs: string[];
|
||||
data: Record<string, ChartDataPoint[]>;
|
||||
data: ChartData;
|
||||
period: "rolling" | "calendar";
|
||||
onPeriodChange: (mode: "rolling" | "calendar") => void;
|
||||
comparison: boolean;
|
||||
setComparison: (mode: boolean) => void;
|
||||
}
|
||||
|
||||
export default function HistoryChart({
|
||||
@@ -20,6 +36,10 @@ export default function HistoryChart({
|
||||
summary,
|
||||
tabs,
|
||||
data,
|
||||
period,
|
||||
onPeriodChange,
|
||||
comparison,
|
||||
setComparison,
|
||||
}: HistoryChartProps) {
|
||||
const [activeTab, setActiveTab] = React.useState<string>(tabs[0] || "");
|
||||
|
||||
@@ -29,48 +49,69 @@ export default function HistoryChart({
|
||||
}
|
||||
};
|
||||
|
||||
const activeDataKey = activeTab.toLowerCase();
|
||||
const rawData = data[activeDataKey] || data[activeTab] || [];
|
||||
const currentData = [...rawData].reverse();
|
||||
const activeDataKey = activeTab.toLowerCase() as keyof ChartData;
|
||||
|
||||
let rawData: ChartDataPoint[] = [];
|
||||
|
||||
if (activeDataKey === "daily") {
|
||||
rawData = data.daily || [];
|
||||
} else {
|
||||
const section = data[activeDataKey];
|
||||
rawData = section?.[period] || [];
|
||||
}
|
||||
|
||||
const currentData = rawData;
|
||||
|
||||
const maxAmount =
|
||||
currentData.length > 0
|
||||
? Math.max(...currentData.map((d) => d.amount), 1)
|
||||
? Math.max(
|
||||
...currentData.flatMap((d) =>
|
||||
comparison ? [d.amount, d.compareAmount || 0] : [d.amount]
|
||||
),
|
||||
1
|
||||
)
|
||||
: 1;
|
||||
|
||||
// ✅ Formatter (₹ + adaptive units)
|
||||
const formatAmount = (amount: number) => {
|
||||
const tab = activeTab.toLowerCase();
|
||||
|
||||
if (amount === 0) return "";
|
||||
|
||||
if (tab === "year") {
|
||||
if (amount >= 100000) {
|
||||
return `₹ ${(amount / 100000).toFixed(2)} L`;
|
||||
}
|
||||
if (tab === "monthly") {
|
||||
if (amount >= 100000) return `₹ ${(amount / 100000).toFixed(2)} L`;
|
||||
return `₹ ${amount.toLocaleString("en-IN")}`;
|
||||
}
|
||||
|
||||
if (tab === "month") {
|
||||
if (amount >= 1000) {
|
||||
return `₹ ${(amount / 1000).toFixed(1)} K`;
|
||||
}
|
||||
if (tab === "weekly") {
|
||||
if (amount >= 1000) return `₹ ${(amount / 1000).toFixed(1)} K`;
|
||||
return `₹ ${amount.toLocaleString("en-IN")}`;
|
||||
}
|
||||
|
||||
return `₹ ${amount.toLocaleString("en-IN")}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper sx={{ p: { xs: 2, sm: 4 }, borderRadius: 4, width: "100%", boxShadow: 'none', border: '1px solid', borderColor: 'divider' }}>
|
||||
<Paper
|
||||
sx={{
|
||||
p: { xs: 2, sm: 4 },
|
||||
borderRadius: 4,
|
||||
width: "100%",
|
||||
boxShadow: "none",
|
||||
border: "1px solid",
|
||||
borderColor: "divider"
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" fontWeight={700} gutterBottom>
|
||||
{header}
|
||||
</Typography>
|
||||
|
||||
{summary && (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||
{summary}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{/* Tabs */}
|
||||
<ToggleButtonGroup
|
||||
value={activeTab}
|
||||
exclusive
|
||||
@@ -78,7 +119,10 @@ export default function HistoryChart({
|
||||
fullWidth
|
||||
sx={{
|
||||
mb: 4,
|
||||
bgcolor: (theme) => theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.02)',
|
||||
bgcolor: (theme) =>
|
||||
theme.palette.mode === "dark"
|
||||
? "rgba(255,255,255,0.05)"
|
||||
: "rgba(0,0,0,0.02)",
|
||||
borderRadius: 8,
|
||||
p: 0.5,
|
||||
"& .MuiToggleButton-root": {
|
||||
@@ -88,11 +132,16 @@ export default function HistoryChart({
|
||||
fontWeight: 600,
|
||||
color: "text.secondary",
|
||||
"&.Mui-selected": {
|
||||
bgcolor: (theme) => theme.palette.mode === 'dark' ? 'primary.dark' : 'primary.light',
|
||||
color: (theme) => theme.palette.mode === 'dark' ? 'primary.contrastText' : 'primary.main',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.05)',
|
||||
},
|
||||
},
|
||||
bgcolor: (theme) =>
|
||||
theme.palette.mode === "dark"
|
||||
? "primary.dark"
|
||||
: "primary.light",
|
||||
color: (theme) =>
|
||||
theme.palette.mode === "dark"
|
||||
? "primary.contrastText"
|
||||
: "primary.main"
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{tabs.map((tab) => (
|
||||
@@ -102,58 +151,98 @@ export default function HistoryChart({
|
||||
))}
|
||||
</ToggleButtonGroup>
|
||||
|
||||
{/* Chart Area */}
|
||||
{/* Period Toggle */}
|
||||
<ToggleButtonGroup
|
||||
value={period}
|
||||
exclusive
|
||||
onChange={(_, v) => v && onPeriodChange(v)}
|
||||
size="small"
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
<ToggleButton value="rolling">Rolling</ToggleButton>
|
||||
<ToggleButton value="calendar" disabled={activeDataKey === "daily"}>
|
||||
Calendar
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
|
||||
<ToggleButtonGroup
|
||||
value={comparison ? "on" : "off"}
|
||||
exclusive
|
||||
onChange={(_, v) => setComparison(v === "on")}
|
||||
size="small"
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
<ToggleButton value="off">Single</ToggleButton>
|
||||
<ToggleButton value="on">Compare</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
|
||||
{/* Chart */}
|
||||
{currentData.length > 0 ? (
|
||||
<Box sx={{ display: "flex", alignItems: "flex-end", height: 200, mt: 4, position: 'relative' }}>
|
||||
<Box sx={{ display: "flex", alignItems: "flex-end", height: 220, mt: 4 }}>
|
||||
{currentData.map((point) => {
|
||||
const heightPerc = (point.amount / maxAmount) * 100;
|
||||
const currentHeight = (point.amount / maxAmount) * 100;
|
||||
const compareHeight = ((point.compareAmount || 0) / maxAmount) * 100;
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={
|
||||
activeTab.toLowerCase() === "month"
|
||||
? point.id.replace(" - ", "\n")
|
||||
: point.id
|
||||
}
|
||||
key={point.id}
|
||||
sx={{
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
height: "100%",
|
||||
height: "100%"
|
||||
}}
|
||||
>
|
||||
{/* Values */}
|
||||
<Typography variant="caption" sx={{ mb: 1, fontSize: "0.65rem" }}>
|
||||
{formatAmount(point.amount)}
|
||||
</Typography>
|
||||
|
||||
{/* Bars */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "flex-end",
|
||||
gap: comparison ? 0.5 : 0,
|
||||
height: "100%"
|
||||
}}
|
||||
>
|
||||
{/* Compare */}
|
||||
{comparison && (
|
||||
<Box
|
||||
sx={{
|
||||
width: 6,
|
||||
height: `${compareHeight}%`,
|
||||
bgcolor: "grey.400",
|
||||
borderRadius: 2
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Current */}
|
||||
<Box
|
||||
sx={{
|
||||
width: 10,
|
||||
height: `${currentHeight}%`,
|
||||
bgcolor: point.highlighted ? "error.main" : "primary.main",
|
||||
borderRadius: 2
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Label */}
|
||||
<Typography
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
sx={{
|
||||
mt: 1,
|
||||
fontWeight: 500,
|
||||
fontSize: '0.7rem',
|
||||
textAlign: 'center',
|
||||
whiteSpace: 'pre-line'
|
||||
fontSize: "0.7rem",
|
||||
textAlign: "center",
|
||||
whiteSpace: "pre-line"
|
||||
}}
|
||||
>
|
||||
{point.amount > 0 ? formatAmount(point.amount) : ""}
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
width: "40%",
|
||||
minWidth: 12,
|
||||
maxWidth: 32,
|
||||
height: `${heightPerc}%`,
|
||||
minHeight: "4px",
|
||||
bgcolor: point.highlighted ? "error.main" : "grey.300",
|
||||
borderRadius: 4,
|
||||
transition: "height 0.5s cubic-bezier(0.4, 0, 0.2, 1)",
|
||||
...(point.highlighted && {
|
||||
boxShadow: (theme) => `0 4px 12px ${theme.palette.error.main}40`,
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
|
||||
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, fontWeight: 500, fontSize: '0.7rem' }}>
|
||||
{point.id}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
@@ -32,6 +32,9 @@ const getStartOfWeek = (d: Date) => {
|
||||
return startOfDay(date);
|
||||
};
|
||||
|
||||
const shiftDate = (d: Date, days: number) =>
|
||||
new Date(d.getTime() + days * 86400000);
|
||||
|
||||
// ---------------- LATEST ----------------
|
||||
export async function fetchLatestTransactions(
|
||||
type: "expense" | "income"
|
||||
@@ -71,13 +74,23 @@ export async function fetchLatestTransactions(
|
||||
}
|
||||
|
||||
// ---------------- TYPES ----------------
|
||||
export interface ChartSeries {
|
||||
rolling: ChartDataPoint[];
|
||||
calendar: ChartDataPoint[];
|
||||
}
|
||||
|
||||
export interface ChartData {
|
||||
daily: ChartDataPoint[];
|
||||
weekly: ChartSeries;
|
||||
monthly: ChartSeries;
|
||||
}
|
||||
|
||||
export interface AggregatedDashboardData {
|
||||
chartData: Record<string, ChartDataPoint[]>;
|
||||
chartData: ChartData;
|
||||
totalAmount: number;
|
||||
topPayees: Array<{ payeeName: string; amount: number }>;
|
||||
}
|
||||
|
||||
// ---------------- AGGREGATION ----------------
|
||||
// ---------------- AGGREGATION ----------------
|
||||
export async function fetchAggregatedData(
|
||||
type: "expense" | "income"
|
||||
@@ -95,65 +108,122 @@ export async function fetchAggregatedData(
|
||||
|
||||
const normalize = (amt: number) => Math.abs(amt);
|
||||
|
||||
// ---------------- WEEK ----------------
|
||||
const weekBuckets: Record<string, number> = {
|
||||
Mon: 0, Tue: 0, Wed: 0, Thu: 0,
|
||||
Fri: 0, Sat: 0, Sun: 0
|
||||
// ---------------- DAILY ----------------
|
||||
const dailyBuckets: Record<string, any> = {
|
||||
Mon: { amount: 0, compare: 0 },
|
||||
Tue: { amount: 0, compare: 0 },
|
||||
Wed: { amount: 0, compare: 0 },
|
||||
Thu: { amount: 0, compare: 0 },
|
||||
Fri: { amount: 0, compare: 0 },
|
||||
Sat: { amount: 0, compare: 0 },
|
||||
Sun: { amount: 0, compare: 0 }
|
||||
};
|
||||
|
||||
const weekStart = getStartOfWeek(now);
|
||||
const weekEnd = endOfDay(new Date(weekStart.getTime() + 6 * 86400000));
|
||||
const prevWeekStart = shiftDate(weekStart, -7);
|
||||
const prevWeekEnd = shiftDate(weekEnd, -7);
|
||||
|
||||
// ---------------- MONTH (rolling 5 weeks, Mon–Sun aligned) ----------------
|
||||
const monthBuckets: {
|
||||
label: string;
|
||||
start: Date;
|
||||
end: Date;
|
||||
amount: number;
|
||||
}[] = [];
|
||||
// ---------------- WEEKLY ----------------
|
||||
const weeklyRolling: any[] = [];
|
||||
const weeklyCalendar: any[] = [];
|
||||
|
||||
const currentWeekStart = getStartOfWeek(now);
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
// rolling (last 5 weeks)
|
||||
for (let i = 4; i >= 0; i--) {
|
||||
const start = new Date(currentWeekStart.getTime() - i * 7 * 86400000);
|
||||
const end = endOfDay(new Date(start.getTime() + 6 * 86400000));
|
||||
|
||||
monthBuckets.push({
|
||||
weeklyRolling.push({
|
||||
label: `${format(start)} - ${format(end)}`,
|
||||
start,
|
||||
end,
|
||||
amount: 0
|
||||
amount: 0,
|
||||
compare: 0,
|
||||
prevStart: shiftDate(start, -7),
|
||||
prevEnd: shiftDate(end, -7)
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------- YEAR (rolling 12 months) ----------------
|
||||
const yearBuckets: {
|
||||
label: string;
|
||||
start: Date;
|
||||
end: Date;
|
||||
amount: number;
|
||||
}[] = [];
|
||||
// calendar weeks
|
||||
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const firstWeekStart = getStartOfWeek(startOfMonth);
|
||||
|
||||
const totalWeeks = Math.ceil(
|
||||
(endOfMonth.getTime() - firstWeekStart.getTime()) / (7 * 86400000)
|
||||
) + 1;
|
||||
|
||||
for (let i = 0; i < totalWeeks; i++) {
|
||||
const start = new Date(firstWeekStart.getTime() + i * 7 * 86400000);
|
||||
const end = endOfDay(new Date(start.getTime() + 6 * 86400000));
|
||||
|
||||
weeklyCalendar.push({
|
||||
label: `${format(start)} - ${format(end)}`,
|
||||
start,
|
||||
end,
|
||||
amount: 0,
|
||||
compare: 0,
|
||||
prevStart: shiftDate(start, -7),
|
||||
prevEnd: shiftDate(end, -7)
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------- MONTHLY ----------------
|
||||
const monthlyRolling: any[] = [];
|
||||
const monthlyCalendar: any[] = [];
|
||||
|
||||
// rolling (last 12 months)
|
||||
for (let i = 11; i >= 0; i--) {
|
||||
const d = new Date(now);
|
||||
d.setMonth(d.getMonth() - i);
|
||||
|
||||
const start = new Date(d.getFullYear(), d.getMonth(), 1);
|
||||
|
||||
const end =
|
||||
i === 0
|
||||
? endOfDay(now) // current month → till now
|
||||
? endOfDay(now)
|
||||
: endOfDay(new Date(d.getFullYear(), d.getMonth() + 1, 0));
|
||||
|
||||
const label = `${d.toLocaleString("default", {
|
||||
month: "short"
|
||||
})}-${String(d.getFullYear()).slice(2)}`;
|
||||
const prevStart = new Date(start);
|
||||
prevStart.setMonth(prevStart.getMonth() - 1);
|
||||
const prevEnd = new Date(end);
|
||||
prevEnd.setMonth(prevEnd.getMonth() - 1);
|
||||
|
||||
yearBuckets.push({
|
||||
label,
|
||||
monthlyRolling.push({
|
||||
label: `${d.toLocaleString("default", { month: "short" })}-${String(
|
||||
d.getFullYear()
|
||||
).slice(2)}`,
|
||||
start,
|
||||
end,
|
||||
amount: 0
|
||||
amount: 0,
|
||||
compare: 0,
|
||||
prevStart,
|
||||
prevEnd
|
||||
});
|
||||
}
|
||||
|
||||
// calendar (Jan–Dec)
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const start = new Date(now.getFullYear(), i, 1);
|
||||
const end = endOfDay(new Date(now.getFullYear(), i + 1, 0));
|
||||
|
||||
const prevStart = new Date(start);
|
||||
prevStart.setFullYear(prevStart.getFullYear() - 1);
|
||||
const prevEnd = new Date(end);
|
||||
prevEnd.setFullYear(prevEnd.getFullYear() - 1);
|
||||
|
||||
monthlyCalendar.push({
|
||||
label: `${start.toLocaleString("default", { month: "short" })}-${String(
|
||||
start.getFullYear()
|
||||
).slice(2)}`,
|
||||
start,
|
||||
end,
|
||||
amount: 0,
|
||||
compare: 0,
|
||||
prevStart,
|
||||
prevEnd
|
||||
});
|
||||
}
|
||||
|
||||
@@ -173,50 +243,72 @@ export async function fetchAggregatedData(
|
||||
const payee = item.payee?.name || item.payee || "Unknown";
|
||||
payeeMap[payee] = (payeeMap[payee] || 0) + amt;
|
||||
|
||||
// WEEK
|
||||
// DAILY
|
||||
if (d >= weekStart && d <= weekEnd) {
|
||||
const day = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][d.getDay()];
|
||||
if (weekBuckets[day] !== undefined) {
|
||||
weekBuckets[day] += amt;
|
||||
if (dailyBuckets[day]) {
|
||||
dailyBuckets[day].amount += amt;
|
||||
}
|
||||
}
|
||||
|
||||
// MONTH (rolling weeks)
|
||||
for (const b of monthBuckets) {
|
||||
if (d >= b.start && d <= b.end) {
|
||||
b.amount += amt;
|
||||
if (d >= prevWeekStart && d <= prevWeekEnd) {
|
||||
const day = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][d.getDay()];
|
||||
if (dailyBuckets[day]) {
|
||||
dailyBuckets[day].compare += amt;
|
||||
}
|
||||
}
|
||||
|
||||
// YEAR (rolling months)
|
||||
for (const b of yearBuckets) {
|
||||
if (d >= b.start && d <= b.end) {
|
||||
b.amount += amt;
|
||||
// WEEKLY
|
||||
for (const b of weeklyRolling) {
|
||||
if (d >= b.start && d <= b.end) b.amount += amt;
|
||||
if (d >= b.prevStart && d <= b.prevEnd) b.compare += amt;
|
||||
}
|
||||
for (const b of weeklyCalendar) {
|
||||
if (d >= b.start && d <= b.end) b.amount += amt;
|
||||
if (d >= b.prevStart && d <= b.prevEnd) b.compare += amt;
|
||||
}
|
||||
|
||||
// MONTHLY
|
||||
for (const b of monthlyRolling) {
|
||||
if (d >= b.start && d <= b.end) b.amount += amt;
|
||||
if (d >= b.prevStart && d <= b.prevEnd) b.compare += amt;
|
||||
}
|
||||
for (const b of monthlyCalendar) {
|
||||
if (d >= b.start && d <= b.end) b.amount += amt;
|
||||
if (d >= b.prevStart && d <= b.prevEnd) b.compare += amt;
|
||||
}
|
||||
}
|
||||
|
||||
const toPoints = (b: any): ChartDataPoint[] =>
|
||||
Array.isArray(b)
|
||||
? b.map((x) => ({
|
||||
const toPoints = (arr: any[]): ChartDataPoint[] =>
|
||||
arr.map((x) => ({
|
||||
id: x.label,
|
||||
amount: x.amount
|
||||
}))
|
||||
: Object.entries(b).map(([k, v]: any) => ({
|
||||
id: k,
|
||||
amount: v
|
||||
amount: x.amount,
|
||||
compareAmount: x.compare
|
||||
}));
|
||||
|
||||
const chartData = {
|
||||
week: toPoints(weekBuckets),
|
||||
month: toPoints(monthBuckets),
|
||||
year: toPoints(yearBuckets)
|
||||
const chartData: ChartData = {
|
||||
daily: Object.entries(dailyBuckets).map(([k, v]: any) => ({
|
||||
id: k,
|
||||
amount: v.amount,
|
||||
compareAmount: v.compare
|
||||
})),
|
||||
weekly: {
|
||||
rolling: toPoints(weeklyRolling),
|
||||
calendar: toPoints(weeklyCalendar)
|
||||
},
|
||||
monthly: {
|
||||
rolling: toPoints(monthlyRolling),
|
||||
calendar: toPoints(monthlyCalendar)
|
||||
}
|
||||
};
|
||||
|
||||
// highlight max
|
||||
Object.values(chartData).forEach(group => {
|
||||
let max = group[0];
|
||||
for (const g of group) {
|
||||
// highlight max (current only)
|
||||
Object.values(chartData).forEach((group: any) => {
|
||||
const arr = Array.isArray(group) ? group : group.rolling;
|
||||
if (!arr?.length) return;
|
||||
|
||||
let max = arr[0];
|
||||
for (const g of arr) {
|
||||
if (g.amount > max.amount) max = g;
|
||||
}
|
||||
if (max.amount > 0) max.highlighted = true;
|
||||
|
||||
Reference in New Issue
Block a user