Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
bd7d414
Create SpendOverTime widget for the Home page
mhawryluk Mar 16, 2026
696da7e
Small fix
mhawryluk Mar 16, 2026
af76772
Fix checking if user is approver for insight visibility
mhawryluk Mar 16, 2026
d25d232
Fix test
mhawryluk Mar 16, 2026
2bc78a4
Implement review suggestions
mhawryluk Mar 16, 2026
f764d7f
Update disableDynamicHeight comment
mhawryluk Mar 17, 2026
c53d5a6
Remove unused pieChartChartContainer style
mhawryluk Mar 17, 2026
3848551
Merge branch 'main' into feat/spend-over-time-widget
mhawryluk Mar 17, 2026
b10869e
Merge branch 'main' into feat/spend-over-time-widget
mhawryluk Mar 17, 2026
8a87c83
Fix View button's width
mhawryluk Mar 17, 2026
8cda86e
Change cursor to pointer when hovering over clickable elements on charts
mhawryluk Mar 17, 2026
94b63d9
Use selector for policies in SpendOverTimeSection
mhawryluk Mar 17, 2026
a68275e
Decompose SpendOverTimeSection
mhawryluk Mar 17, 2026
23614a4
Don't show SpendOverTime widget when there are no transactions
mhawryluk Mar 17, 2026
3c44bec
Merge branch 'main' into feat/spend-over-time-widget
mhawryluk Mar 17, 2026
670d119
Add a comment about SpendOverTimeSection visibility
mhawryluk Mar 18, 2026
e9b467e
Change left icon's right margin in small buttons
mhawryluk Mar 19, 2026
acc185c
Don't save last search params onyx value in search action when invoke…
mhawryluk Mar 20, 2026
245cb16
Divide SpendOverTimeSection into multiple files
mhawryluk Mar 20, 2026
4cf1b21
Center widget title with the right content button
mhawryluk Mar 20, 2026
c140832
Add unit tests for isPolicyEligibleForSpendOverTime
mhawryluk Mar 20, 2026
b0075da
Merge branch 'main' into feat/spend-over-time-widget
mhawryluk Mar 20, 2026
e5a0a17
Fix after merge
mhawryluk Mar 20, 2026
07262cf
Fix types after merge
mhawryluk Mar 20, 2026
e25a2d6
Remove disableDynamicHeight from charts
mhawryluk Mar 20, 2026
d24d8b5
Merge branch 'main' into feat/spend-over-time-widget
mhawryluk Mar 20, 2026
40de436
Merge branch 'main' into feat/spend-over-time-widget
mhawryluk Mar 24, 2026
265a77e
Fix import
mhawryluk Mar 24, 2026
d7e6aa8
Fix isSubmittedTo
mhawryluk Mar 24, 2026
3d87ada
Invoke getSuggestedSearches during render in SpendOverTimeSection
mhawryluk Mar 24, 2026
783e33c
Rename effect event
mhawryluk Mar 24, 2026
dcd32da
Merge branch 'main' into feat/spend-over-time-widget
mhawryluk Mar 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ function Button({
<View style={[isContentCentered ? styles.justifyContentCenter : styles.justifyContentBetween, styles.flexRow, iconWrapperStyles, styles.mw100]}>
<View style={[styles.alignItemsCenter, styles.flexRow, styles.flexShrink1]}>
{!!icon && (
<View style={[extraSmall ? styles.mr1 : styles.mr2, !text && styles.mr0, iconStyles, isLoading && styles.opacity0]}>
<View style={[extraSmall || small ? styles.mr1 : styles.mr2, !text && styles.mr0, iconStyles, isLoading && styles.opacity0]}>
<Icon
src={icon}
fill={isHovered ? (iconHoverFill ?? defaultFill) : (iconFill ?? defaultFill)}
Expand Down
109 changes: 50 additions & 59 deletions src/components/Charts/BarChart/BarChartContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {useSharedValue} from 'react-native-reanimated';
import type {CartesianChartRenderArg, ChartBounds, PointsArray, Scale} from 'victory-native';
import {Bar, CartesianChart} from 'victory-native';
import ActivityIndicator from '@components/ActivityIndicator';
import ChartHeader from '@components/Charts/components/ChartHeader';
import ChartTooltip from '@components/Charts/components/ChartTooltip';
import ChartXAxisLabels from '@components/Charts/components/ChartXAxisLabels';
import {
Expand All @@ -24,7 +23,6 @@ import type {ComputeGeometryFn, HitTestArgs} from '@components/Charts/hooks';
import {useChartInteractions, useChartLabelFormats, useChartLabelLayout, useDynamicYDomain, useLabelHitTesting, useTooltipData} from '@components/Charts/hooks';
import type {CartesianChartProps, ChartDataPoint} from '@components/Charts/types';
import {calculateMinDomainPadding, DEFAULT_CHART_COLOR, getChartColor, rotatedLabelYOffset} from '@components/Charts/utils';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan';
Expand Down Expand Up @@ -75,10 +73,9 @@ type BarChartProps = CartesianChartProps & {
useSingleColor?: boolean;
};

function BarChartContent({data, title, titleIcon, isLoading, yAxisUnit, yAxisUnitPosition = 'left', useSingleColor = false, onBarPress}: BarChartProps) {
function BarChartContent({data, isLoading, yAxisUnit, yAxisUnitPosition = 'left', useSingleColor = false, onBarPress}: BarChartProps) {
const theme = useTheme();
const styles = useThemeStyles();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const font = useFont(fontSource, variables.iconSizeExtraSmall);
const [chartWidth, setChartWidth] = useState(0);
const [barAreaWidth, setBarAreaWidth] = useState(0);
Expand Down Expand Up @@ -175,7 +172,7 @@ function BarChartContent({data, title, titleIcon, isLoading, yAxisUnit, yAxisUni
return args.cursorX >= barLeft && args.cursorX <= barRight && args.cursorY >= barTop && args.cursorY <= barBottom;
};

const {customGestures, setPointPositions, activeDataIndex, isTooltipActive, initialTooltipPosition} = useChartInteractions({
const {customGestures, setPointPositions, activeDataIndex, isTooltipActive, isOverClickableTarget, initialTooltipPosition} = useChartInteractions({
handlePress: handleBarPress,
checkIsOver: checkIsOverBar,
isCursorOverLabel,
Expand Down Expand Up @@ -238,7 +235,7 @@ function BarChartContent({data, title, titleIcon, isLoading, yAxisUnit, yAxisUni
if (isLoading || !font) {
const reasonAttributes: SkeletonSpanReasonAttributes = {context: 'BarChartContent', isLoading, isFontLoading: !font};
return (
<View style={[styles.barChartContainer, styles.highlightBG, shouldUseNarrowLayout ? styles.p5 : styles.p8, styles.justifyContentCenter, styles.alignItemsCenter]}>
<View style={styles.chartActivityIndicator}>
<ActivityIndicator
size="large"
reasonAttributes={reasonAttributes}
Expand All @@ -252,59 +249,53 @@ function BarChartContent({data, title, titleIcon, isLoading, yAxisUnit, yAxisUni
}

return (
<View style={[styles.barChartContainer, styles.highlightBG, shouldUseNarrowLayout ? styles.p5 : styles.p8]}>
<ChartHeader
title={title}
titleIcon={titleIcon}
/>
<GestureDetector gesture={customGestures}>
<View
style={[styles.barChartChartContainer, dynamicChartStyle]}
onLayout={handleLayout}
>
{chartWidth > 0 && (
<CartesianChart
xKey="x"
padding={chartPadding}
yKeys={['y']}
domainPadding={domainPadding}
onChartBoundsChange={handleChartBoundsChange}
onScaleChange={handleScaleChange}
renderOutside={renderOutside}
xAxis={{
tickCount: data.length,
lineWidth: X_AXIS_LINE_WIDTH,
}}
yAxis={[
{
font,
labelColor: theme.textSupporting,
formatYLabel: formatValue,
tickCount: Y_AXIS_TICK_COUNT,
lineWidth: Y_AXIS_LINE_WIDTH,
lineColor: theme.border,
labelOffset: AXIS_LABEL_GAP,
domain: yAxisDomain,
},
]}
frame={{lineWidth: 0}}
data={chartData}
>
{({points, chartBounds}) => <>{points.y.map((point) => renderBar(point, chartBounds, points.y.length))}</>}
</CartesianChart>
)}
{isTooltipActive && !!tooltipData && (
<ChartTooltip
label={tooltipData.label}
amount={tooltipData.amount}
percentage={tooltipData.percentage}
chartWidth={chartWidth}
initialTooltipPosition={initialTooltipPosition}
/>
)}
</View>
</GestureDetector>
</View>
<GestureDetector gesture={customGestures}>
<View
style={[styles.chartContent, dynamicChartStyle, isOverClickableTarget && styles.cursorPointer]}
onLayout={handleLayout}
>
{chartWidth > 0 && (
<CartesianChart
xKey="x"
padding={chartPadding}
yKeys={['y']}
domainPadding={domainPadding}
onChartBoundsChange={handleChartBoundsChange}
onScaleChange={handleScaleChange}
renderOutside={renderOutside}
xAxis={{
tickCount: data.length,
lineWidth: X_AXIS_LINE_WIDTH,
}}
yAxis={[
{
font,
labelColor: theme.textSupporting,
formatYLabel: formatValue,
tickCount: Y_AXIS_TICK_COUNT,
lineWidth: Y_AXIS_LINE_WIDTH,
lineColor: theme.border,
labelOffset: AXIS_LABEL_GAP,
domain: yAxisDomain,
},
]}
frame={{lineWidth: 0}}
data={chartData}
>
{({points, chartBounds}) => points.y.map((point) => renderBar(point, chartBounds, points.y.length))}
</CartesianChart>
)}
{isTooltipActive && !!tooltipData && (
<ChartTooltip
label={tooltipData.label}
amount={tooltipData.amount}
percentage={tooltipData.percentage}
chartWidth={chartWidth}
initialTooltipPosition={initialTooltipPosition}
/>
)}
</View>
</GestureDetector>
);
}

Expand Down
4 changes: 1 addition & 3 deletions src/components/Charts/BarChart/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function BarChart(props: BarChartProps) {
getComponent={getBarChartContent}
componentProps={props}
fallback={
<View style={[styles.flex1, styles.justifyContentCenter, styles.alignItemsCenter, styles.highlightBG, styles.br4, styles.p5]}>
<View style={styles.chartWebFallback}>
<ActivityIndicator
size="large"
reasonAttributes={reasonAttributes}
Expand All @@ -28,6 +28,4 @@ function BarChart(props: BarChartProps) {
);
}

BarChart.displayName = 'BarChart';

export default BarChart;
123 changes: 57 additions & 66 deletions src/components/Charts/LineChart/LineChartContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {useSharedValue} from 'react-native-reanimated';
import type {CartesianChartRenderArg, ChartBounds, Scale} from 'victory-native';
import {CartesianChart, Line} from 'victory-native';
import ActivityIndicator from '@components/ActivityIndicator';
import ChartHeader from '@components/Charts/components/ChartHeader';
import ChartTooltip from '@components/Charts/components/ChartTooltip';
import ChartXAxisLabels from '@components/Charts/components/ChartXAxisLabels';
import LeftFrameLine from '@components/Charts/components/LeftFrameLine';
Expand All @@ -26,7 +25,6 @@ import type {ComputeGeometryFn, HitTestArgs} from '@components/Charts/hooks';
import {useChartInteractions, useChartLabelFormats, useChartLabelLayout, useDynamicYDomain, useLabelHitTesting, useTooltipData} from '@components/Charts/hooks';
import type {CartesianChartProps, ChartDataPoint} from '@components/Charts/types';
import {calculateMinDomainPadding, DEFAULT_CHART_COLOR, measureTextWidth, rotatedLabelYOffset} from '@components/Charts/utils';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan';
Expand Down Expand Up @@ -74,10 +72,9 @@ type LineChartProps = CartesianChartProps & {
onPointPress?: (dataPoint: ChartDataPoint, index: number) => void;
};

function LineChartContent({data, title, titleIcon, isLoading, yAxisUnit, yAxisUnitPosition = 'left', onPointPress}: LineChartProps) {
function LineChartContent({data, isLoading, yAxisUnit, yAxisUnitPosition = 'left', onPointPress}: LineChartProps) {
const theme = useTheme();
const styles = useThemeStyles();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const font = useFont(fontSource, variables.iconSizeExtraSmall);
const [chartWidth, setChartWidth] = useState(0);
const [plotAreaWidth, setPlotAreaWidth] = useState(0);
Expand Down Expand Up @@ -181,7 +178,7 @@ function LineChartContent({data, title, titleIcon, isLoading, yAxisUnit, yAxisUn
return Math.sqrt(dx * dx + dy * dy) <= DOT_RADIUS + DOT_HOVER_EXTRA_RADIUS;
};

const {customGestures, setPointPositions, activeDataIndex, isTooltipActive, initialTooltipPosition} = useChartInteractions({
const {customGestures, setPointPositions, activeDataIndex, isTooltipActive, isOverClickableTarget, initialTooltipPosition} = useChartInteractions({
handlePress: handlePointPress,
checkIsOver: checkIsOverDot,
isCursorOverLabel,
Expand Down Expand Up @@ -235,7 +232,7 @@ function LineChartContent({data, title, titleIcon, isLoading, yAxisUnit, yAxisUn
if (isLoading || !font) {
const reasonAttributes: SkeletonSpanReasonAttributes = {context: 'LineChartContent', isLoading, isFontLoading: !font};
return (
<View style={[styles.lineChartContainer, styles.highlightBG, shouldUseNarrowLayout ? styles.p5 : styles.p8, styles.justifyContentCenter, styles.alignItemsCenter]}>
<View style={styles.chartActivityIndicator}>
<ActivityIndicator
size="large"
reasonAttributes={reasonAttributes}
Expand All @@ -249,66 +246,60 @@ function LineChartContent({data, title, titleIcon, isLoading, yAxisUnit, yAxisUn
}

return (
<View style={[styles.lineChartContainer, styles.highlightBG, shouldUseNarrowLayout ? styles.p5 : styles.p8]}>
<ChartHeader
title={title}
titleIcon={titleIcon}
/>
<GestureDetector gesture={customGestures}>
<View
style={[styles.lineChartChartContainer, dynamicChartStyle]}
onLayout={handleLayout}
>
{chartWidth > 0 && (
<CartesianChart
xKey="x"
padding={chartPadding}
yKeys={['y']}
domainPadding={domainPadding}
onChartBoundsChange={handleChartBoundsChange}
onScaleChange={handleScaleChange}
renderOutside={renderOutside}
xAxis={{
tickCount: data.length,
lineWidth: X_AXIS_LINE_WIDTH,
}}
yAxis={[
{
font,
labelColor: theme.textSupporting,
formatYLabel: formatValue,
tickCount: Y_AXIS_TICK_COUNT,
lineWidth: Y_AXIS_LINE_WIDTH,
lineColor: theme.border,
labelOffset: AXIS_LABEL_GAP,
domain: yAxisDomain,
},
]}
frame={{lineWidth: 0}}
data={chartData}
>
{({points}) => (
<Line
points={points.y}
color={DEFAULT_CHART_COLOR}
strokeWidth={2}
curveType="linear"
/>
)}
</CartesianChart>
)}
{isTooltipActive && !!tooltipData && (
<ChartTooltip
label={tooltipData.label}
amount={tooltipData.amount}
percentage={tooltipData.percentage}
chartWidth={chartWidth}
initialTooltipPosition={initialTooltipPosition}
/>
)}
</View>
</GestureDetector>
</View>
<GestureDetector gesture={customGestures}>
<View
style={[styles.chartContent, dynamicChartStyle, isOverClickableTarget && styles.cursorPointer]}
onLayout={handleLayout}
>
{chartWidth > 0 && (
<CartesianChart
xKey="x"
padding={chartPadding}
yKeys={['y']}
domainPadding={domainPadding}
onChartBoundsChange={handleChartBoundsChange}
onScaleChange={handleScaleChange}
renderOutside={renderOutside}
xAxis={{
tickCount: data.length,
lineWidth: X_AXIS_LINE_WIDTH,
}}
yAxis={[
{
font,
labelColor: theme.textSupporting,
formatYLabel: formatValue,
tickCount: Y_AXIS_TICK_COUNT,
lineWidth: Y_AXIS_LINE_WIDTH,
lineColor: theme.border,
labelOffset: AXIS_LABEL_GAP,
domain: yAxisDomain,
},
]}
frame={{lineWidth: 0}}
data={chartData}
>
{({points}) => (
<Line
points={points.y}
color={DEFAULT_CHART_COLOR}
strokeWidth={2}
curveType="linear"
/>
)}
</CartesianChart>
)}
{isTooltipActive && !!tooltipData && (
<ChartTooltip
label={tooltipData.label}
amount={tooltipData.amount}
percentage={tooltipData.percentage}
chartWidth={chartWidth}
initialTooltipPosition={initialTooltipPosition}
/>
)}
</View>
</GestureDetector>
);
}

Expand Down
7 changes: 3 additions & 4 deletions src/components/Charts/LineChart/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@ import useThemeStyles from '@hooks/useThemeStyles';
import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan';
import type {LineChartProps} from './LineChartContent';

const getLineChartContent = () => import('./LineChartContent');
function LineChart(props: LineChartProps) {
const styles = useThemeStyles();
const reasonAttributes: SkeletonSpanReasonAttributes = {context: 'LineChart.SkiaWebLoading'};

return (
<WithSkiaWeb
opts={{locateFile: (file: string) => `/${file}`}}
getComponent={() => import('./LineChartContent')}
getComponent={getLineChartContent}
componentProps={props}
fallback={
<View style={[styles.flex1, styles.justifyContentCenter, styles.alignItemsCenter, styles.highlightBG, styles.br4, styles.p5]}>
<View style={styles.chartWebFallback}>
<ActivityIndicator
size="large"
reasonAttributes={reasonAttributes}
Expand All @@ -27,6 +28,4 @@ function LineChart(props: LineChartProps) {
);
}

LineChart.displayName = 'LineChart';

export default LineChart;
Loading
Loading