diff --git a/src/Dashboard.tsx b/src/Dashboard.tsx index ffcf1e9..215a350 100644 --- a/src/Dashboard.tsx +++ b/src/Dashboard.tsx @@ -161,7 +161,6 @@ export default function Dashboard() { config={configuration} data={data} isFetching={report.isFetching} - onFlowChange={handleFlowChange} /> ); diff --git a/src/components/Dashboard/Dashboard.models.ts b/src/components/Dashboard/Dashboard.models.ts index 87f9224..87fe839 100644 --- a/src/components/Dashboard/Dashboard.models.ts +++ b/src/components/Dashboard/Dashboard.models.ts @@ -19,27 +19,23 @@ export interface DashboardState { export interface DashboardStateSetters { setSelectedPeriodId: (id: DashboardSelectedPeriodId) => void; setSelectedGroupKey: (groupKey: GroupKey | null) => void; + toggleFlow: () => void; togglePeriodType: () => void; toggleComparison: () => void; } export interface DashboardSection { id: string; - title?: string; - summary?: string; + title: string; component: React.ComponentType; + summary?: string; settings?: Record; - isList?: boolean; - style?: { - size?: number; - [key: string]: any; - }; } export interface ColorDefinition { primary: string; - background?: string; - text?: string; + background: string; + text: string; } export interface ThemeAwarePalette { @@ -49,14 +45,28 @@ export interface ThemeAwarePalette { export interface DashboardConfig { sections: DashboardSection[]; - style?: { - palette?: Record; + style: { + palette: Record; }; } export interface DashboardProps { config: DashboardConfig; data: ReportData; - isFetching?: boolean; - onFlowChange?: (state: DashboardState) => void; + isFetching: boolean; +} + + +export interface ComponentProps extends DashboardSection { + reportData: ReportData; + + state: DashboardState; + stateSetters: DashboardStateSetters; + isFetching: boolean; + + colorScheme: { + primary: string; + light: string; + text: string; + }; } diff --git a/src/components/Dashboard/Dashboard.tsx b/src/components/Dashboard/Dashboard.tsx index 50fb4b9..0c5ff56 100644 --- a/src/components/Dashboard/Dashboard.tsx +++ b/src/components/Dashboard/Dashboard.tsx @@ -1,8 +1,23 @@ import * as React from "react"; -import DashboardView from "./Dashboard.view"; +import { + Box, + Container, + Grid, + ToggleButton, + ToggleButtonGroup, + Button +} from "@mui/material"; +import { useTheme, alpha } from "@mui/material/styles"; import { DashboardProps, DashboardState, DashboardStateSetters } from "./Dashboard.models"; -export default function Dashboard(props: DashboardProps) { +export default function Dashboard({ + config, + data, + isFetching, +}: DashboardProps) { + const theme = useTheme(); + const themeMode = theme.palette.mode; + const [state, setState] = React.useState({ flow: "outflows", periodType: "rolling", @@ -11,20 +26,11 @@ export default function Dashboard(props: DashboardProps) { comparison: false, }); - const toggleFlow = ( - event: React.MouseEvent, - newFlow: "outflows" | "inflows" | null - ) => { - if (newFlow === null) return; - - setState(prev => { - if (prev.flow === newFlow) return prev; - - const next = { ...prev, flow: newFlow }; - props.onFlowChange?.(next); - - return next; - }); + const toggleFlow = () => { + setState(prev => ({ + ...prev, + flow: prev.flow === "outflows" ? "inflows" : "outflows", + })); }; const togglePeriodType = () => { @@ -52,17 +58,119 @@ export default function Dashboard(props: DashboardProps) { const stateSetters: DashboardStateSetters = { togglePeriodType, toggleComparison, + toggleFlow, setSelectedPeriodId, setSelectedGroupKey, }; + const { flow, selectedGroupKey } = state; + + const colors = React.useMemo(() => { + const palette = config.style.palette[flow]; + const modeColors = palette[themeMode]; + + return { + primary: modeColors.primary, + light: modeColors.background || alpha(modeColors.primary, 0.1), + text: + modeColors.text || + (themeMode === "light" ? theme.palette.text.primary : "#fff"), + }; + + // if (modeColors) { + // return { + // primary: modeColors.primary, + // light: modeColors.background || alpha(modeColors.primary, 0.1), + // text: + // modeColors.text || + // (themeMode === "light" ? theme.palette.text.primary : "#fff"), + // }; + // } + // + // const themeColor = + // flow === "outflows" ? theme.palette.error : theme.palette.success; + // + // return { + // primary: themeColor.main, + // light: alpha(themeColor.main, themeMode === "light" ? 0.08 : 0.15), + // text: themeColor.main, + // }; + }, [config.style?.palette, flow, themeMode, theme.palette]); + return ( - + + + + Outflows + Inflows + + + {selectedGroupKey && Object.keys(selectedGroupKey).length > 0 && ( + + )} + + + + {config.sections.map((section) => { + const Component = section.component; + + return ( + + + + ); + })} + + ); } diff --git a/src/components/Dashboard/Dashboard.view.tsx b/src/components/Dashboard/Dashboard.view.tsx deleted file mode 100644 index dba16b8..0000000 --- a/src/components/Dashboard/Dashboard.view.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import * as React from "react"; -import { - Box, - Container, - Grid, - Typography, - ToggleButton, - ToggleButtonGroup, - Button -} from "@mui/material"; -import { useTheme, alpha } from "@mui/material/styles"; -import { GroupKey } from "../../features/report"; -import { DashboardProps, DashboardState, DashboardStateSetters } from "./Dashboard.models"; - -interface ViewProps extends DashboardProps { - state: DashboardState; - setState: React.Dispatch>; - toggleFlow: (event: React.MouseEvent, newFlow: "outflows" | "inflows" | null) => void; - stateSetters: DashboardStateSetters; -} - -export default function DashboardView({ - config, - data, - state, - setState, - toggleFlow, - stateSetters, -}: ViewProps) { - const theme = useTheme(); - const themeMode = theme.palette.mode; - const { flow, selectedGroupKey } = state; - const { setSelectedGroupKey } = stateSetters; - - // Resolve colors with fallbacks - const colors = React.useMemo(() => { - const palette = config.style?.palette?.[flow]; - const modeColors = palette ? palette[themeMode] : null; - - if (modeColors) { - return { - primary: modeColors.primary, - light: modeColors.background || alpha(modeColors.primary, 0.1), - text: modeColors.text || (themeMode === 'light' ? theme.palette.text.primary : '#fff') - }; - } - - // Fallback to standard theme colors - const themeColor = flow === 'outflows' ? theme.palette.error : theme.palette.success; - return { - primary: themeColor.main, - light: alpha(themeColor.main, themeMode === 'light' ? 0.08 : 0.15), - text: themeColor.main - }; - }, [config.style?.palette, flow, themeMode, theme.palette]); - - return ( - - - - Outflows - Inflows - - - {selectedGroupKey && Object.keys(selectedGroupKey).length > 0 && ( - - )} - - - - {config.sections.map((section) => { - const Component = section.component; - - return ( - - - - ); - })} - - - ); -} diff --git a/src/components/HistoryChart/HistoryChart.models.ts b/src/components/HistoryChart/HistoryChart.models.ts index 79b5561..08f69f6 100644 --- a/src/components/HistoryChart/HistoryChart.models.ts +++ b/src/components/HistoryChart/HistoryChart.models.ts @@ -1,5 +1,3 @@ -import { ComponentProps } from "../report.props"; - export interface _ChartDataPoint { id: string; label: string; @@ -10,7 +8,3 @@ export interface _ChartDataPoint { export interface ChartDataPoint extends _ChartDataPoint { compare?: _ChartDataPoint; } - -export interface HistoryChartProps extends ComponentProps { - tabs: string[]; -} diff --git a/src/components/HistoryChart/HistoryChart.props.ts b/src/components/HistoryChart/HistoryChart.props.ts new file mode 100644 index 0000000..0e1cc61 --- /dev/null +++ b/src/components/HistoryChart/HistoryChart.props.ts @@ -0,0 +1,21 @@ +import * as React from "react"; +import { ComponentProps } from "../Dashboard"; +import { ChartDataPoint } from "./HistoryChart.models"; + +export interface HistoryChartProps extends ComponentProps { + settings: { + tabs: string[]; + }; +} + +export interface HistoryChartViewProps extends HistoryChartProps { + activeTab: string; + setActiveTab: (v: string) => void; + currentData: ChartDataPoint[]; + visibleData: ChartDataPoint[]; + maxAmount: number; + visibleCount: number; + startIndex: number; + setStartIndex: React.Dispatch>; + activeDataKey: string; +} diff --git a/src/components/HistoryChart/HistoryChart.tsx b/src/components/HistoryChart/HistoryChart.tsx index 5dca757..95291c6 100644 --- a/src/components/HistoryChart/HistoryChart.tsx +++ b/src/components/HistoryChart/HistoryChart.tsx @@ -1,18 +1,22 @@ import * as React from "react"; -import { HistoryChartProps } from "./HistoryChart.models"; import HistoryChartView from "./HistoryChart.view"; import { buildChartData, tabToKey } from "./HistoryChart.adapter"; +import { HistoryChartProps } from "./HistoryChart.props"; + export default function HistoryChart(props: HistoryChartProps) { const { - tabs, + settings, reportData, state, stateSetters, + + isFetching, } = props; const { flow, comparison, selectedPeriodId } = state; const { setSelectedPeriodId } = stateSetters; + const { tabs } = settings; const [activeTab, setActiveTab] = React.useState(tabs[0] || ""); const [startIndex, setStartIndex] = React.useState(0); diff --git a/src/components/HistoryChart/HistoryChart.view.tsx b/src/components/HistoryChart/HistoryChart.view.tsx index 5622a78..c02923c 100644 --- a/src/components/HistoryChart/HistoryChart.view.tsx +++ b/src/components/HistoryChart/HistoryChart.view.tsx @@ -11,43 +11,31 @@ 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"; + HistoryChartViewProps, +} from "./HistoryChart.props"; 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>; - activeDataKey: string; -} +export default function HistoryChartView({ + title, + summary, + settings, -export default function HistoryChartView(props: ViewProps) { - const { - header, - summary, - tabs, - colorScheme, + state, + stateSetters, + isFetching, - state, - stateSetters, + colorScheme, - activeTab, - setActiveTab, - currentData, - visibleData, - maxAmount, - visibleCount, - startIndex, - setStartIndex, - activeDataKey, - } = props; + activeTab, + setActiveTab, + currentData, + visibleData, + maxAmount, + visibleCount, + startIndex, + setStartIndex, + activeDataKey, +}: HistoryChartViewProps) { const { flow, periodType, selectedPeriodId, comparison } = state; const { togglePeriodType, setSelectedPeriodId, toggleComparison } = stateSetters; @@ -89,13 +77,13 @@ export default function HistoryChartView(props: ViewProps) { border: "1px solid", borderColor: "divider", bgcolor: isDark ? "background.paper" : colorScheme.light, - opacity: props.isFetching ? 0.6 : 1, + opacity: isFetching ? 0.6 : 1, transition: "opacity 0.3s ease", - pointerEvents: props.isFetching ? "none" : "auto", + pointerEvents: isFetching ? "none" : "auto", }} > - {header} + {title} {summary && ( @@ -105,7 +93,7 @@ export default function HistoryChartView(props: ViewProps) { )} - {tabs.map((tab) => ( + {settings.tabs.map((tab) => ( {tab} diff --git a/src/components/ProgressCard/ProgressCard.props.ts b/src/components/ProgressCard/ProgressCard.props.ts new file mode 100644 index 0000000..5ff8517 --- /dev/null +++ b/src/components/ProgressCard/ProgressCard.props.ts @@ -0,0 +1,14 @@ +import { ComponentProps } from "../Dashboard"; + +export interface ProgressCardProps extends ComponentProps { + settings: { + compact: boolean; + }; +} + +export interface ProgressCardViewProps extends ProgressCardProps { + progressAmount: number; + totalAmount: number; + selected: boolean; + onClick: () => void; +} diff --git a/src/components/ProgressCard/ProgressCard.tsx b/src/components/ProgressCard/ProgressCard.tsx deleted file mode 100644 index b42c2e4..0000000 --- a/src/components/ProgressCard/ProgressCard.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import * as React from "react"; -import ProgressCardView from "./ProgressCard.view"; -import { ProgressCardProps } from "./ProgressCard.models"; -import { getPercentage, formatCurrency } from "../report.helpers"; - -export default function ProgressCard(props: ProgressCardProps) { - const { progressAmount, totalAmount, compact = false } = props; - - const percentage = getPercentage(progressAmount, totalAmount); - - const formattedProgress = formatCurrency(progressAmount); - const formattedTotal = formatCurrency(totalAmount); - - return ( - - ); -} diff --git a/src/components/ProgressCard/ProgressCard.view.tsx b/src/components/ProgressCard/ProgressCard.view.tsx index 34472bf..8d04a74 100644 --- a/src/components/ProgressCard/ProgressCard.view.tsx +++ b/src/components/ProgressCard/ProgressCard.view.tsx @@ -8,93 +8,83 @@ import { linearProgressClasses } from "@mui/material"; import { useTheme, alpha } from "@mui/material/styles"; -import { ProgressCardProps } from "./ProgressCard.models"; - -interface ViewProps extends ProgressCardProps { - percentage: number; - formattedProgress: string; - formattedTotal: string; - selected?: boolean; - onClick?: () => void; -} +import { getPercentage, formatCurrency } from "../report.helpers"; +import { ProgressCardViewProps } from "./ProgressCard.props"; export default function ProgressCardView({ - header, - colorTheme = "info", - percentage, - formattedProgress, - formattedTotal, - compact = false, + title, + settings, + + isFetching, + + colorScheme, + + progressAmount, + totalAmount, selected, onClick, -}: ViewProps) { +}: ProgressCardViewProps) { const theme = useTheme(); const isDark = theme.palette.mode === "dark"; + const percentage = getPercentage(progressAmount, totalAmount); + const formattedProgress = formatCurrency(progressAmount); + const formattedTotal = formatCurrency(totalAmount); + return ( { - const baseColor = theme.palette[colorTheme]?.main || theme.palette.primary.main; - const lightColor = theme.palette[colorTheme]?.light || theme.palette.primary.light; - return isDark - ? `linear-gradient(135deg, ${alpha(baseColor, 0.9)} 0%, ${alpha(baseColor, 0.3)} 100%)` - : `linear-gradient(135deg, ${baseColor} 0%, ${lightColor} 100%)`; - }, + bgcolor: isDark ? "background.paper" : colorScheme.light, color: "#fff", display: "flex", flexDirection: "column", - alignItems: compact ? "flex-start" : "center", + alignItems: settings.compact ? "flex-start" : "center", justifyContent: "center", position: "relative", overflow: "hidden", - border: selected + border: selected ? `2px solid #fff` - : isDark ? "1px solid rgba(255,255,255,0.1)" : "none", - boxShadow: (theme) => { - const baseShadow = `0 ${compact ? 6 : 12}px ${compact ? 12 : 24}px -10px ${ - isDark - ? "rgba(0,0,0,0.5)" - : theme.palette[colorTheme]?.main || theme.palette.primary.main - }`; - return selected - ? `${baseShadow}, 0 0 0 2px ${theme.palette.background.paper}, 0 0 0 4px ${theme.palette[colorTheme]?.main || theme.palette.primary.main}` - : baseShadow; - }, - opacity: arguments[0].isFetching ? 0.6 : 1, - pointerEvents: arguments[0].isFetching ? "none" : "auto", + : isDark + ? "1px solid rgba(255,255,255,0.1)" + : "none", + boxShadow: "none", + opacity: isFetching ? 0.6 : 1, + pointerEvents: isFetching ? "none" : "auto", }} > - - {header} + {title} - + {formattedProgress} @@ -108,24 +98,24 @@ export default function ProgressCardView({ /> of {formattedTotal} - + diff --git a/src/components/ProgressCard/TopPayees.tsx b/src/components/ProgressCard/TopPayees.tsx index 3103496..37786d5 100644 --- a/src/components/ProgressCard/TopPayees.tsx +++ b/src/components/ProgressCard/TopPayees.tsx @@ -1,21 +1,19 @@ import * as React from "react"; import { Box, Paper, Typography } from "@mui/material"; -import { ComponentProps } from "../report.props"; -import ProgressCard from "./ProgressCard"; +import ProgressCardView from "./ProgressCard.view"; import { extractTopPayees } from "./TopPayees.adapter"; +import { ProgressCardProps } from "./ProgressCard.props"; -interface Props extends ComponentProps { - compact?: boolean; -} +export default function TopPayees(props: ProgressCardProps) { + const { + title, -export default function TopPayees({ - reportData, - state, - stateSetters, - header, - compact = true, - isFetching, -}: Props) { + reportData, + state, + stateSetters, + + isFetching, + } = props const { flow, selectedPeriodId, selectedGroupKey } = state; const { setSelectedGroupKey } = stateSetters; @@ -39,7 +37,7 @@ export default function TopPayees({ }} > - {header} + {title} {items.map((item) => { - const isSelected = selectedGroupKey?.payee?.includes(item.name); + const isSelected = !!selectedGroupKey?.payee?.includes(item.name); return ( - { if (setSelectedGroupKey) { let newKey = selectedGroupKey ? { ...selectedGroupKey } : {}; diff --git a/src/components/ProgressCard/TopTags.tsx b/src/components/ProgressCard/TopTags.tsx index c3aa263..402ba7a 100644 --- a/src/components/ProgressCard/TopTags.tsx +++ b/src/components/ProgressCard/TopTags.tsx @@ -1,21 +1,19 @@ import * as React from "react"; import { Box, Paper, Typography } from "@mui/material"; -import { ComponentProps } from "../report.props"; -import ProgressCard from "./ProgressCard"; +import ProgressCardView from "./ProgressCard.view"; import { extractTopTags } from "./TopTags.adapter"; +import { ProgressCardProps } from "./ProgressCard.props"; -interface Props extends ComponentProps { - compact?: boolean; -} +export default function TopTags(props: ProgressCardProps) { + const { + title, -export default function TopTags({ - reportData, - state, - stateSetters, - header, - compact = true, - isFetching, -}: Props) { + reportData, + state, + stateSetters, + + isFetching, + } = props const { flow, selectedPeriodId, selectedGroupKey } = state; const { setSelectedGroupKey } = stateSetters; @@ -39,7 +37,7 @@ export default function TopTags({ }} > - {header} + {title} {items.map((item) => { - const isSelected = selectedGroupKey?.tags?.includes(item.tag); + const isSelected = !!selectedGroupKey?.tags?.includes(item.tag); return ( - { if (setSelectedGroupKey) { let newKey = selectedGroupKey ? { ...selectedGroupKey } : {}; diff --git a/src/components/report.props.ts b/src/components/report.props.ts deleted file mode 100644 index 1f7e04e..0000000 --- a/src/components/report.props.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ReportData } from "../features/report"; -import { DashboardState, DashboardStateSetters } from "./Dashboard"; - -export interface ComponentProps { - reportData: ReportData; - state: DashboardState; - stateSetters: DashboardStateSetters; - isFetching?: boolean; - header: string; - summary?: string; - accentColor?: string; - colorScheme?: { - primary: string; - light: string; - text: string; - }; -} diff --git a/src/dashboard-config.ts b/src/dashboard-config.ts index 8f2b7f6..43dc137 100644 --- a/src/dashboard-config.ts +++ b/src/dashboard-config.ts @@ -14,9 +14,6 @@ export const configuration: DashboardConfig = { settings: { tabs: ["Weekly", "Monthly"], }, - style: { - size: 12, - }, }, { id: "top-categories", @@ -25,9 +22,6 @@ export const configuration: DashboardConfig = { settings: { compact: true, }, - style: { - size: 12, - }, }, { id: "top-payees", @@ -36,17 +30,11 @@ export const configuration: DashboardConfig = { settings: { compact: true, }, - style: { - size: 12, - }, }, { id: "items", title: 'Recent Transactions', component: LatestItems, - style: { - size: 12, - }, }, ], style: {