QoL tweaks: Cash Flow cards, auto-primary accounts, investment projections, Sankey filters

- Dashboard: Remove tenant name/role subtitle
- Cash Flow: Replace Operating/Reserve net cards with inflow vs outflow
  breakdown showing In/Out amounts and signed net; replace Ending Cash
  card with AI Financial Health status from saved recommendation
- Accounts: Auto-set first asset account per fund_type as primary on creation
- Investments: Add 5th summary card for projected annual interest earnings
- Sankey: Add Actuals/Budget/Forecast data source toggle and
  All Funds/Operating/Reserve fund filter SegmentedControls with
  backend support for budget-based and forecast (actuals+budget) queries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 14:22:37 -05:00
parent f1e66966f3
commit 07347a644f
7 changed files with 250 additions and 50 deletions

View File

@@ -1,6 +1,6 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import {
Title, Group, Stack, Text, Card, Loader, Center, Select, SimpleGrid,
Title, Group, Stack, Text, Card, Loader, Center, Select, SimpleGrid, SegmentedControl,
} from '@mantine/core';
import { useQuery } from '@tanstack/react-query';
import {
@@ -52,6 +52,8 @@ export function SankeyPage() {
const containerRef = useRef<HTMLDivElement | null>(null);
const [dimensions, setDimensions] = useState({ width: 900, height: 500 });
const [year, setYear] = useState(new Date().getFullYear().toString());
const [source, setSource] = useState('actuals');
const [fundFilter, setFundFilter] = useState('all');
const yearOptions = Array.from({ length: 5 }, (_, i) => {
const y = new Date().getFullYear() - 2 + i;
@@ -59,9 +61,12 @@ export function SankeyPage() {
});
const { data, isLoading, isError } = useQuery<CashFlowData>({
queryKey: ['sankey', year],
queryKey: ['sankey', year, source, fundFilter],
queryFn: async () => {
const { data } = await api.get(`/reports/cash-flow-sankey?year=${year}`);
const params = new URLSearchParams({ year });
if (source !== 'actuals') params.set('source', source);
if (fundFilter !== 'all') params.set('fundType', fundFilter);
const { data } = await api.get(`/reports/cash-flow-sankey?${params}`);
return data;
},
});
@@ -191,6 +196,31 @@ export function SankeyPage() {
<Select data={yearOptions} value={year} onChange={(v) => v && setYear(v)} w={120} />
</Group>
<Group>
<Text size="sm" fw={500}>Data source:</Text>
<SegmentedControl
size="sm"
value={source}
onChange={setSource}
data={[
{ label: 'Actuals', value: 'actuals' },
{ label: 'Budget', value: 'budget' },
{ label: 'Forecast', value: 'forecast' },
]}
/>
<Text size="sm" fw={500} ml="md">Fund:</Text>
<SegmentedControl
size="sm"
value={fundFilter}
onChange={setFundFilter}
data={[
{ label: 'All Funds', value: 'all' },
{ label: 'Operating', value: 'operating' },
{ label: 'Reserve', value: 'reserve' },
]}
/>
</Group>
<SimpleGrid cols={{ base: 1, sm: 3 }}>
<Card withBorder p="md">
<Text size="xs" c="dimmed" tt="uppercase" fw={700}>Total Income</Text>