208 lines
6.1 KiB
TypeScript
208 lines
6.1 KiB
TypeScript
import * as React from "react";
|
|
import {
|
|
Box,
|
|
Typography,
|
|
ToggleButtonGroup,
|
|
ToggleButton,
|
|
Paper
|
|
} from "@mui/material";
|
|
import { useTheme, alpha } from "@mui/material/styles";
|
|
import IconButton from "@mui/material/IconButton";
|
|
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
|
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
|
import {
|
|
ChartDataPoint,
|
|
HistoryChartProps,
|
|
} from "./HistoryChart.models";
|
|
import { formatDisplay } from "./HistoryChart.utils";
|
|
|
|
interface ViewProps extends HistoryChartProps {
|
|
activeTab: string;
|
|
setActiveTab: (v: string) => void;
|
|
currentData: ChartDataPoint[];
|
|
visibleData: ChartDataPoint[];
|
|
maxAmount: number;
|
|
visibleCount: number;
|
|
startIndex: number;
|
|
setStartIndex: React.Dispatch<React.SetStateAction<number>>;
|
|
activeDataKey: string;
|
|
}
|
|
|
|
export default function HistoryChartView(props: ViewProps) {
|
|
const {
|
|
header,
|
|
summary,
|
|
tabs,
|
|
colorScheme,
|
|
|
|
mode,
|
|
periodType,
|
|
selectedPeriodId,
|
|
comparison,
|
|
|
|
togglePeriodType,
|
|
setSelectedPeriodId,
|
|
toggleComparison,
|
|
|
|
activeTab,
|
|
setActiveTab,
|
|
currentData,
|
|
visibleData,
|
|
maxAmount,
|
|
visibleCount,
|
|
startIndex,
|
|
setStartIndex,
|
|
activeDataKey,
|
|
} = props;
|
|
|
|
const theme = useTheme();
|
|
const isDark = theme.palette.mode === "dark";
|
|
|
|
const handleTabChange = (_: React.MouseEvent<HTMLElement>, newTab: string | null) => {
|
|
if (newTab !== null) setActiveTab(newTab);
|
|
};
|
|
|
|
const canGoLeft = startIndex > 0;
|
|
const canGoRight = startIndex + visibleCount < currentData.length;
|
|
|
|
const handlePrev = () => {
|
|
if (canGoLeft) setStartIndex((prev) => prev - visibleCount);
|
|
};
|
|
|
|
const handleNext = () => {
|
|
if (canGoRight) setStartIndex((prev) => prev + visibleCount);
|
|
};
|
|
|
|
return (
|
|
<Paper
|
|
sx={{
|
|
p: { xs: 2.5, sm: 4 },
|
|
borderRadius: 4,
|
|
width: "100%",
|
|
boxShadow: "none",
|
|
border: "1px solid",
|
|
borderColor: "divider",
|
|
bgcolor: isDark ? "background.paper" : colorScheme.light,
|
|
}}
|
|
>
|
|
<Typography variant="h6" fontWeight={700} gutterBottom>
|
|
{header}
|
|
</Typography>
|
|
|
|
{summary && (
|
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
|
{summary}
|
|
</Typography>
|
|
)}
|
|
|
|
<ToggleButtonGroup value={activeTab} exclusive onChange={handleTabChange} fullWidth sx={{ mb: 4 }}>
|
|
{tabs.map((tab) => (
|
|
<ToggleButton key={tab} value={tab}>
|
|
{tab}
|
|
</ToggleButton>
|
|
))}
|
|
</ToggleButtonGroup>
|
|
|
|
<Box sx={{ display: "flex", justifyContent: "space-between", mb: 3 }}>
|
|
<ToggleButtonGroup value={periodType} exclusive onChange={togglePeriodType} size="small">
|
|
<ToggleButton value="rolling">Rolling</ToggleButton>
|
|
<ToggleButton value="calendar">Calendar</ToggleButton>
|
|
</ToggleButtonGroup>
|
|
|
|
<ToggleButton
|
|
value="compare"
|
|
selected={comparison}
|
|
onChange={toggleComparison}
|
|
size="small"
|
|
>
|
|
Compare
|
|
</ToggleButton>
|
|
</Box>
|
|
|
|
{currentData.length > 0 ? (
|
|
<Box sx={{ position: "relative", mt: 4 }}>
|
|
{canGoLeft && (
|
|
<IconButton onClick={handlePrev} size="small" sx={{ position: "absolute", left: 0, top: "50%" }}>
|
|
<ChevronLeftIcon fontSize="small" />
|
|
</IconButton>
|
|
)}
|
|
|
|
<Box sx={{ display: "flex", alignItems: "flex-end", height: 220, mt: 4 }}>
|
|
{visibleData.map((point) => {
|
|
const currentHeight = (point.amount / maxAmount) * 100;
|
|
const compareHeight = comparison
|
|
? ((point.compare?.amount ?? 0) / maxAmount) * 100
|
|
: 0;
|
|
|
|
const isSelected = selectedPeriodId === point.id;
|
|
const display = formatDisplay(point, activeDataKey, comparison);
|
|
|
|
return (
|
|
<Box
|
|
key={point.id}
|
|
onClick={() =>
|
|
setSelectedPeriodId(isSelected ? null : point.id)
|
|
}
|
|
sx={{
|
|
flex: 1,
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
alignItems: "center",
|
|
cursor: "pointer"
|
|
}}
|
|
>
|
|
<Box sx={{ display: "flex", alignItems: "flex-end", gap: 1, height: "100%" }}>
|
|
{comparison && (
|
|
<Box
|
|
sx={{
|
|
width: 8,
|
|
height: `${compareHeight}%`,
|
|
bgcolor: alpha(colorScheme.primary, 0.4),
|
|
borderRadius: "4px 4px 0 0"
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
<Box
|
|
sx={{
|
|
width: 12,
|
|
height: `${currentHeight}%`,
|
|
bgcolor: isSelected ? "warning.main" : colorScheme.primary,
|
|
borderRadius: "4px 4px 0 0"
|
|
}}
|
|
/>
|
|
</Box>
|
|
|
|
<Typography variant="caption">
|
|
{point.label}
|
|
</Typography>
|
|
|
|
{comparison && point.compare && (
|
|
<Typography variant="caption" color="text.secondary">
|
|
{point.compare.label}
|
|
</Typography>
|
|
)}
|
|
|
|
<Typography variant="caption">
|
|
{display}
|
|
</Typography>
|
|
</Box>
|
|
);
|
|
})}
|
|
</Box>
|
|
|
|
{canGoRight && (
|
|
<IconButton onClick={handleNext} size="small" sx={{ position: "absolute", right: 0, top: "50%" }}>
|
|
<ChevronRightIcon fontSize="small" />
|
|
</IconButton>
|
|
)}
|
|
</Box>
|
|
) : (
|
|
<Box sx={{ height: 200, display: "flex", alignItems: "center", justifyContent: "center" }}>
|
|
<Typography color="text.secondary">No Data Available</Typography>
|
|
</Box>
|
|
)}
|
|
</Paper>
|
|
);
|
|
}
|