Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@angular/forms": "^22.2.0",
"@angular/platform-browser": "^22.2.0",
"@angular/router": "^22.2.0",
"@fontsource-variable/inter": "^5.3.0",
"@ng-icons/core": "^36.1.0",
"@ng-icons/lucide": "^36.1.0",
"@ngx-translate/core": "^18.0.0",
Expand Down
2 changes: 1 addition & 1 deletion web/src/app/features/annual/annual.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export class AnnualComponent {
),
bar(
t('kpi.variableExpenses'),
'#fb7185',
SERIES_COLORS.expensesLight,
m.map((r) => active(r, r.month.variableExpenses)),
),
bar(
Expand Down
6 changes: 3 additions & 3 deletions web/src/app/features/dashboard/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ import { StatusBadgeComponent } from '../../shared/status-badge';
<dl class="mt-3 divide-y text-sm">
<div class="flex items-center justify-between gap-3 py-2">
<dt class="flex items-center gap-2 text-muted-foreground">
<ng-icon name="lucideGauge" [style.color]="colors.net" aria-hidden="true" />
<ng-icon name="lucideGauge" [style.color]="colors.saved" aria-hidden="true" />
{{ 'kpi.avgSavingsRate' | translate }}
</dt>
<dd class="num font-medium">{{ o.averageMonthlySavingsRate | pct }}</dd>
Expand Down Expand Up @@ -983,9 +983,9 @@ export class DashboardComponent {
type: 'line',
connectNulls: false,
symbol: 'circle',
symbolSize: 6,
symbolSize: 5,
data: months.map((r) => active(r.month, r.month.netBalance)),
lineStyle: { width: 2, color: SERIES_COLORS.net },
lineStyle: { width: 1.5, type: 'dashed', color: SERIES_COLORS.net },
itemStyle: { color: SERIES_COLORS.net },
z: 3,
},
Expand Down
2 changes: 1 addition & 1 deletion web/src/app/features/net-worth/net-worth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ export class NetWorthComponent {
showSymbol: series.length < 3,
areaStyle: { opacity: 0.12 },
data: series.map((p) => [p.date, p.netWorth]),
itemStyle: { color: SERIES_COLORS.net },
itemStyle: { color: SERIES_COLORS.wealth },
},
{
name: this.i18n.instant('netWorth.liabilities'),
Expand Down
15 changes: 4 additions & 11 deletions web/src/app/features/portfolio/portfolio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { Toasts } from '../../core/toast';
import { ChartComponent } from '../../shared/chart';
import {
SERIES_COLORS,
ASSET_CLASS_COLORS,
baseChart,
categoryAxis,
moneyAxis,
Expand Down Expand Up @@ -64,15 +65,7 @@ import {

type SortKey = 'value' | 'gain' | 'today' | 'weight';

const CLASS_COLORS: Record<AssetClass, string> = {
Stock: '#8b5cf6',
Etf: '#10b981',
Bond: '#0891b2',
Fund: '#84cc16',
Crypto: '#f59e0b',
Cash: '#a1a1aa',
Other: '#a855f7',
};
const CLASS_COLORS: Record<AssetClass, string> = ASSET_CLASS_COLORS;

/**
* Read-only view of broker data: nothing on this page can change a broker account. Coins held elsewhere (an
Expand Down Expand Up @@ -857,15 +850,15 @@ export class PortfolioComponent {
showSymbol: false,
areaStyle: { opacity: 0.1 },
data: series.map((p) => [p.date, p.value]),
itemStyle: { color: SERIES_COLORS.net },
itemStyle: { color: SERIES_COLORS.value },
},
{
name: this.i18n.instant('portfolio.contributions'),
type: 'line',
showSymbol: false,
step: 'end',
data: series.map((p) => [p.date, p.netContributions]),
itemStyle: { color: '#a1a1aa' },
itemStyle: { color: SERIES_COLORS.muted },
lineStyle: { type: 'dashed' },
},
],
Expand Down
8 changes: 6 additions & 2 deletions web/src/app/shared/category-label.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ describe('categoryLabel', () => {
});

it("shows the owner's name for a renamed built-in category", () => {
expect(categoryLabel(i18n, { key: 'gym', name: 'Ginásio & Padel', renamed: true })).toBe('Ginásio & Padel');
expect(categoryLabel(i18n, { key: 'gym', name: 'Ginásio & Padel', renamed: true })).toBe(
'Ginásio & Padel',
);
});

it('shows custom categories by name', () => {
expect(categoryLabel(i18n, { key: 'custom-side-project', name: 'Side project' })).toBe('Side project');
expect(categoryLabel(i18n, { key: 'custom-side-project', name: 'Side project' })).toBe(
'Side project',
);
});
});
27 changes: 26 additions & 1 deletion web/src/app/shared/chart-options.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
import type { EChartsOption } from 'echarts';

/**
* One meaning, one colour, on every page: money in and wealth are the brand green, spending is rose, investing is
* violet, saving is teal. Balances and references are neutral so they never compete with the series they summarise.
*/
export const SERIES_COLORS = {
income: '#059669',
expenses: '#e11d48',
/** Second expense shade (e.g. variable vs fixed). */
expensesLight: '#fb7185',
invested: '#7c3aed',
saved: '#0891b2',
budget: '#a1a1aa',
net: '#f59e0b',
/** Net balance line drawn over income/expense bars: neutral, it summarises them. */
net: '#71717a',
/** Portfolio value: the investing colour. */
value: '#7c3aed',
/** Net worth: the brand green. */
wealth: '#059669',
muted: '#a1a1aa',
};

/** Asset classes, from the same family: shares violet, funds indigo, bonds teal, crypto amber, cash grey. */
export const ASSET_CLASS_COLORS = {
Stock: '#7c3aed',
Etf: '#059669',
Bond: '#0891b2',
Fund: '#4f46e5',
Crypto: '#d97706',
Cash: '#a1a1aa',
Other: '#db2777',
} as const;

export const CHART_FONT = "'Inter Variable', Inter, ui-sans-serif, system-ui, sans-serif";

const axisLabel = { color: '#a1a1aa', fontSize: 11 };

export function moneyAxis(locale: string): EChartsOption['yAxis'] {
Expand Down
7 changes: 6 additions & 1 deletion web/src/app/shared/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { NgIcon } from '@ng-icons/core';
import { TranslatePipe } from '@ngx-translate/core';
import { Prefs } from '../core/prefs';
import { APP_ICONS } from './icons';
import { CHART_FONT } from './chart-options';

/** True when no series has a single non-zero value: nothing worth drawing (e.g. a year without movements). */
export function chartIsEmpty(option: EChartsOption): boolean {
Expand Down Expand Up @@ -104,7 +105,11 @@ export class ChartComponent implements OnDestroy {
});
(this.chart as unknown as { __theme?: string }).__theme = themeKey;
}
this.chart.setOption({ backgroundColor: 'transparent', ...this.option() }, true);
// The app's font (ECharts draws its own text and does not inherit CSS).
this.chart.setOption(
{ backgroundColor: 'transparent', textStyle: { fontFamily: CHART_FONT }, ...this.option() },
true,
);
}

ngOnDestroy() {
Expand Down
6 changes: 1 addition & 5 deletions web/src/app/shared/progress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ export class ProgressComponent {
readonly barColor = computed(() => {
if (this.tone() === 'brand') return 'var(--primary)';
const v = this.value();
return v > 1
? 'var(--color-rose-500)'
: v >= 0.9
? 'var(--color-amber-500)'
: 'var(--primary)';
return v > 1 ? 'var(--color-rose-500)' : v >= 0.9 ? 'var(--color-amber-500)' : 'var(--primary)';
});
}
12 changes: 10 additions & 2 deletions web/src/app/shell/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,16 @@ interface NavItem {
variant="ghost"
size="icon"
(click)="toggleTheme()"
[attr.aria-label]="'settings.theme' | translate"
[hlmTooltip]="'settings.theme' | translate"
[attr.aria-label]="
('settings.theme' | translate) +
': ' +
('settings.themes.' + prefs.theme() | translate)
"
[hlmTooltip]="
('settings.theme' | translate) +
': ' +
('settings.themes.' + prefs.theme() | translate)
"
>
<ng-icon
[name]="
Expand Down
6 changes: 5 additions & 1 deletion web/src/styles.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
@layer theme, base, components, utilities;
/* Inter, bundled with the app (served from 'self', so the CSP needs nothing): the same type on every system. */
@import '@fontsource-variable/inter';
@import 'tailwindcss/theme.css' layer(theme);
@import 'tailwindcss/preflight.css' layer(base);
@import 'tailwindcss/utilities.css';
Expand All @@ -7,7 +9,9 @@
@import '@spartan-ng/brain/hlm-tailwind-preset.css';

@theme {
--font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
--font-sans:
'Inter Variable', 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto,
sans-serif;
--color-income: #10b981;
--color-expense: #e11d48;
--color-invest: #7c3aed;
Expand Down
Loading