Fix net worth chart config UX and color scheme issues (#250)
## Summary - **Clickable toggle rows**: The full row in the chart settings popover now toggles the checkbox, not just the checkbox itself. - **Chart stacking order**: When toggling "Include loans" or "Include real estate", the chart now re-mounts so accounts appear in the correct sorted position (biggest at bottom) instead of being appended on top. - **Scheme-aware liability dot**: The red liability indicator dot in the chart tooltip now respects the user's chart color scheme (neutral, blue, pink) instead of always using the destructive/red color.
This commit is contained in:
parent
0a535fbf47
commit
755452d6ce
|
|
@ -109,16 +109,17 @@ export function ChartSettingsPopover({
|
|||
{allToggles.map((toggle) => (
|
||||
<div
|
||||
key={toggle.id}
|
||||
className="flex items-start justify-between gap-4"
|
||||
className="flex cursor-pointer items-start justify-between gap-4"
|
||||
onClick={() => toggle.onChange(!toggle.checked)}
|
||||
>
|
||||
<div className="space-y-1">
|
||||
<Label
|
||||
htmlFor={toggle.id}
|
||||
className="text-sm leading-5 font-medium"
|
||||
className="pointer-events-none text-sm leading-5 font-medium"
|
||||
>
|
||||
{toggle.label}
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="pointer-events-none text-xs text-muted-foreground">
|
||||
{toggle.description}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
import { ChartConfig } from '@/components/ui/chart';
|
||||
import { StackedAreaChart } from '@/components/ui/stacked-area-chart';
|
||||
import { StackedBarChart } from '@/components/ui/stacked-bar-chart';
|
||||
import { useChartColors } from '@/hooks/use-chart-color-scheme';
|
||||
import { useChartViews } from '@/hooks/use-chart-views';
|
||||
import {
|
||||
NetWorthEvolutionData,
|
||||
|
|
@ -135,6 +136,7 @@ export function NetWorthChart({
|
|||
const { props } = usePage<SharedData>();
|
||||
const locale = useLocale();
|
||||
const isMobile = useIsMobile();
|
||||
const { liabilityDotColor } = useChartColors();
|
||||
const [granularity, setGranularity] = useState<ChartGranularity>('monthly');
|
||||
const [dailyData, setDailyData] = useState<NetWorthEvolutionData | null>(
|
||||
null,
|
||||
|
|
@ -598,6 +600,7 @@ export function NetWorthChart({
|
|||
{chartViews.currentView === 'stacked' &&
|
||||
(granularity === 'daily' ? (
|
||||
<StackedAreaChart
|
||||
key={dataKeys.join(',')}
|
||||
data={scaledChartData.slice(1)}
|
||||
dataKeys={dataKeys}
|
||||
config={chartConfig}
|
||||
|
|
@ -610,12 +613,16 @@ export function NetWorthChart({
|
|||
showLegend={showLegend}
|
||||
netWorthMode={
|
||||
hasLiabilities
|
||||
? { liabilityTypeLabel: __('Loan') }
|
||||
? {
|
||||
liabilityTypeLabel: __('Loan'),
|
||||
liabilityDotColor,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<StackedBarChart
|
||||
key={dataKeys.join(',')}
|
||||
data={scaledChartData.slice(1)}
|
||||
dataKeys={dataKeys}
|
||||
config={chartConfig}
|
||||
|
|
@ -628,7 +635,10 @@ export function NetWorthChart({
|
|||
showLegend={showLegend}
|
||||
netWorthMode={
|
||||
hasLiabilities
|
||||
? { liabilityTypeLabel: __('Loan') }
|
||||
? {
|
||||
liabilityTypeLabel: __('Loan'),
|
||||
liabilityDotColor,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@ interface ChartTooltipContentProps {
|
|||
/** When set, tooltip shows liability rows and net-worth total instead of simple sum. */
|
||||
netWorthMode?: {
|
||||
liabilityTypeLabel: string;
|
||||
liabilityDotColor?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -377,7 +378,10 @@ const ChartTooltipContent = React.forwardRef<
|
|||
{hasLiabilities && displayCurrency && liabilities.map((liability, index) => (
|
||||
<div key={index} className="flex justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="size-2.5 rounded-xs bg-destructive" />
|
||||
<div
|
||||
className="size-2.5 shrink-0 rounded-xs"
|
||||
style={{ backgroundColor: netWorthMode?.liabilityDotColor ?? 'var(--color-destructive)' }}
|
||||
/>
|
||||
<span className="text-muted-foreground font-medium">
|
||||
{netWorthMode?.liabilityTypeLabel}: {liability.name}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export interface StackedAreaChartProps<T extends Record<string, unknown>> {
|
|||
className?: string;
|
||||
showLegend?: boolean;
|
||||
minBarWidth?: number;
|
||||
netWorthMode?: { liabilityTypeLabel: string };
|
||||
netWorthMode?: { liabilityTypeLabel: string; liabilityDotColor?: string };
|
||||
}
|
||||
|
||||
export function StackedAreaChart<T extends Record<string, unknown>>({
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ export interface StackedBarChartProps<T extends Record<string, unknown>> {
|
|||
className?: string;
|
||||
showLegend?: boolean;
|
||||
minBarWidth?: number;
|
||||
netWorthMode?: { liabilityTypeLabel: string };
|
||||
netWorthMode?: { liabilityTypeLabel: string; liabilityDotColor?: string };
|
||||
}
|
||||
|
||||
export function StackedBarChart<T extends Record<string, unknown>>({
|
||||
|
|
|
|||
|
|
@ -96,6 +96,10 @@ export function useChartColors() {
|
|||
? 'var(--cashflow-expense)'
|
||||
: 'var(--color-chart-5)';
|
||||
|
||||
const liabilityDotColor = isColorful
|
||||
? 'var(--color-destructive)'
|
||||
: 'var(--color-chart-5)';
|
||||
|
||||
const CHART_COLORS = [
|
||||
'var(--chart-1)',
|
||||
'var(--chart-2)',
|
||||
|
|
@ -119,6 +123,7 @@ export function useChartColors() {
|
|||
equityLineColor,
|
||||
cashflowIncomeColor,
|
||||
cashflowExpenseColor,
|
||||
liabilityDotColor,
|
||||
categoryBarColor,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,8 +117,8 @@ test('budget show returns previous period when it exists', function () {
|
|||
|
||||
// Create a previous period (last month)
|
||||
$budget->periods()->create([
|
||||
'start_date' => now()->subMonth()->startOfMonth(),
|
||||
'end_date' => now()->subMonth()->endOfMonth(),
|
||||
'start_date' => now()->subMonthNoOverflow()->startOfMonth(),
|
||||
'end_date' => now()->subMonthNoOverflow()->endOfMonth(),
|
||||
'allocated_amount' => 30000,
|
||||
'carried_over_amount' => 0,
|
||||
]);
|
||||
|
|
@ -138,7 +138,7 @@ test('budget show returns previous period when it exists', function () {
|
|||
->component('budgets/show')
|
||||
->has('currentPeriod')
|
||||
->has('previousPeriod')
|
||||
->where('previousPeriod.start_date', now()->subMonth()->startOfMonth()->toJSON())
|
||||
->where('previousPeriod.start_date', now()->subMonthNoOverflow()->startOfMonth()->toJSON())
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -252,8 +252,8 @@ test('budget show can navigate to a specific period via query param', function (
|
|||
|
||||
// Create a previous period
|
||||
$previousPeriod = $budget->periods()->create([
|
||||
'start_date' => now()->subMonth()->startOfMonth(),
|
||||
'end_date' => now()->subMonth()->endOfMonth(),
|
||||
'start_date' => now()->subMonthNoOverflow()->startOfMonth(),
|
||||
'end_date' => now()->subMonthNoOverflow()->endOfMonth(),
|
||||
'allocated_amount' => 20000,
|
||||
'carried_over_amount' => 0,
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -313,7 +313,7 @@ test('net worth evolution converts foreign currency accounts using cached exchan
|
|||
'currency_code' => 'EUR',
|
||||
]);
|
||||
|
||||
$lastMonth = now()->subMonth();
|
||||
$lastMonth = now()->subMonthNoOverflow();
|
||||
$endOfMonth = $lastMonth->copy()->endOfMonth();
|
||||
|
||||
AccountBalance::factory()->create([
|
||||
|
|
@ -369,7 +369,7 @@ test('net worth evolution uses last balance of each month per account', function
|
|||
'currency_code' => 'USD',
|
||||
]);
|
||||
|
||||
$lastMonth = now()->subMonth();
|
||||
$lastMonth = now()->subMonthNoOverflow();
|
||||
|
||||
AccountBalance::factory()->create([
|
||||
'account_id' => $account->id,
|
||||
|
|
|
|||
Loading…
Reference in New Issue