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:
4
backend/package-lock.json
generated
4
backend/package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user