fix: investment scenario detail blank screen and auto-renew refresh

Move useMemo hook above early returns to satisfy React Rules of Hooks,
fixing blank screen when navigating to scenario detail. Also re-fetch
scenario after projection updates so auto-renew renewal records appear
automatically without requiring manual navigation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-24 15:04:14 -04:00
parent 2b331bb3ef
commit 121b8138e3
3 changed files with 21 additions and 10 deletions

View File

@@ -1,12 +1,12 @@
{ {
"name": "hoa-ledgeriq-backend", "name": "hoa-ledgeriq-backend",
"version": "2026.3.17", "version": "2026.3.19",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "hoa-ledgeriq-backend", "name": "hoa-ledgeriq-backend",
"version": "2026.3.17", "version": "2026.3.19",
"dependencies": { "dependencies": {
"@nestjs/common": "^10.4.15", "@nestjs/common": "^10.4.15",
"@nestjs/config": "^3.3.0", "@nestjs/config": "^3.3.0",

View File

@@ -1,12 +1,12 @@
{ {
"name": "hoa-ledgeriq-frontend", "name": "hoa-ledgeriq-frontend",
"version": "2026.3.17", "version": "2026.3.19",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "hoa-ledgeriq-frontend", "name": "hoa-ledgeriq-frontend",
"version": "2026.3.17", "version": "2026.3.19",
"dependencies": { "dependencies": {
"@mantine/core": "^7.15.3", "@mantine/core": "^7.15.3",
"@mantine/dates": "^7.15.3", "@mantine/dates": "^7.15.3",

View File

@@ -40,7 +40,7 @@ export function InvestmentScenarioDetailPage() {
}, },
}); });
const { data: projection, isLoading: projLoading } = useQuery({ const { data: projection, isLoading: projLoading, dataUpdatedAt: projUpdatedAt } = useQuery({
queryKey: ['board-planning-projection', id], queryKey: ['board-planning-projection', id],
queryFn: async () => { queryFn: async () => {
const { data } = await api.get(`/board-planning/scenarios/${id}/projection`); const { data } = await api.get(`/board-planning/scenarios/${id}/projection`);
@@ -49,6 +49,17 @@ export function InvestmentScenarioDetailPage() {
enabled: !!id, enabled: !!id,
}); });
// When projection refreshes (which may create auto-renew records on the backend),
// re-fetch the scenario so the investments list picks up any new renewal records.
const [lastProjUpdate, setLastProjUpdate] = useState(0);
if (projUpdatedAt && projUpdatedAt !== lastProjUpdate) {
setLastProjUpdate(projUpdatedAt);
if (lastProjUpdate > 0) {
// Only re-fetch after a real update (not the initial load)
queryClient.invalidateQueries({ queryKey: ['board-planning-scenario', id] });
}
}
const addMutation = useMutation({ const addMutation = useMutation({
mutationFn: (dto: any) => api.post(`/board-planning/scenarios/${id}/investments`, dto), mutationFn: (dto: any) => api.post(`/board-planning/scenarios/${id}/investments`, dto),
onSuccess: () => { onSuccess: () => {
@@ -100,13 +111,10 @@ export function InvestmentScenarioDetailPage() {
}, },
}); });
if (isLoading) return <Center h={400}><Loader size="lg" /></Center>; // Compute shared time range for aligned charts (must be above early returns to satisfy Rules of Hooks)
if (!scenario) return <Center h={400}><Text>Scenario not found</Text></Center>; const investments = scenario?.investments || [];
const investments = scenario.investments || [];
const summary = projection?.summary; const summary = projection?.summary;
// Compute shared time range for aligned charts
const { sharedStartDate, sharedEndDate } = useMemo(() => { const { sharedStartDate, sharedEndDate } = useMemo(() => {
const allDates: Date[] = []; const allDates: Date[] = [];
@@ -134,6 +142,9 @@ export function InvestmentScenarioDetailPage() {
}; };
}, [investments, projection]); }, [investments, projection]);
if (isLoading) return <Center h={400}><Loader size="lg" /></Center>;
if (!scenario) return <Center h={400}><Text>Scenario not found</Text></Center>;
// Build a lookup of per-investment interest from the projection // Build a lookup of per-investment interest from the projection
const interestDetailMap: Record<string, { interest: number; principal: number }> = {}; const interestDetailMap: Record<string, { interest: number; principal: number }> = {};
if (summary?.investment_interest_details) { if (summary?.investment_interest_details) {