comparison

This commit is contained in:
2026-04-06 18:39:19 +05:30
parent f320f6ff31
commit e6c7778c08
3 changed files with 194 additions and 80 deletions

View File

@@ -9,13 +9,26 @@ export interface ChartDataPoint {
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[]>;
period: "rolling" | "calendar",
data: ChartData;
period: "rolling" | "calendar";
onPeriodChange: (mode: "rolling" | "calendar") => void;
comparison: boolean;
setComparison: (mode: boolean) => void;
}
export default function HistoryChart({
@@ -25,6 +38,8 @@ export default function HistoryChart({
data,
period,
onPeriodChange,
comparison,
setComparison,
}: HistoryChartProps) {
const [activeTab, setActiveTab] = React.useState<string>(tabs[0] || "");
@@ -34,54 +49,69 @@ export default function HistoryChart({
}
};
const activeDataKey = activeTab.toLowerCase();
let rawData;
const activeDataKey = activeTab.toLowerCase() as keyof ChartData;
let rawData: ChartDataPoint[] = [];
if (activeDataKey === "daily") {
rawData = data.daily;
rawData = data.daily || [];
} else {
// @ts-ignore
rawData = data[activeDataKey]?.[period] || [];
const section = data[activeDataKey];
rawData = section?.[period] || [];
}
const currentData = rawData;
const maxAmount =
currentData.length > 0
? Math.max(...currentData.map((d: { amount: any; }) => 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 === "monthly") {
if (amount >= 100000) {
return `${(amount / 100000).toFixed(2)} L`;
}
if (amount >= 100000) return `${(amount / 100000).toFixed(2)} L`;
return `${amount.toLocaleString("en-IN")}`;
}
if (tab === "weekly") {
if (amount >= 1000) {
return `${(amount / 1000).toFixed(1)} K`;
}
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
@@ -89,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": {
@@ -99,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) => (
@@ -113,6 +151,7 @@ export default function HistoryChart({
))}
</ToggleButtonGroup>
{/* Period Toggle */}
<ToggleButtonGroup
value={period}
exclusive
@@ -121,66 +160,89 @@ export default function HistoryChart({
sx={{ mb: 2 }}
>
<ToggleButton value="rolling">Rolling</ToggleButton>
<ToggleButton
value="calendar"
disabled={activeTab.toLowerCase() === "daily"}
>
<ToggleButton value="calendar" disabled={activeDataKey === "daily"}>
Calendar
</ToggleButton>
</ToggleButtonGroup>
{/* Chart Area */}
<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' }}>
{currentData.map((point: { amount: number; id: string, highlighted: boolean }) => {
const heightPerc = (point.amount / maxAmount) * 100;
<Box sx={{ display: "flex", alignItems: "flex-end", height: 220, mt: 4 }}>
{currentData.map((point) => {
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>