fix(scope): all pages react to company scope change via useScopeEffect

- Created useScopeEffect hook that auto-triggers reload on scope change
- Replaced useEffect([], []) with useScopeEffect in 20+ pages
- Risks/Reports/Dashboard also use useScopeEffect
- Removed redundant per-page useCompanyScope where only effect was needed
This commit is contained in:
selfrelease
2026-07-20 08:09:33 +08:00
parent 4265f4bc4c
commit c3232f73c8
23 changed files with 105 additions and 63 deletions
+4 -3
View File
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import {Sparkles } from "lucide-react";
import { listAARs, generateAAR } from "@/lib/api-v2";
import { PageContainer, Card} from "@/components/shared/PageContainer";
@@ -22,12 +23,12 @@ export default function AARsPage() {
const [originalPlan, setOriginalPlan] = useState("");
const [actualResult, setActualResult] = useState("");
useEffect(() => {
useScopeEffect(() => {
listAARs()
.then((resp) => setItems((resp.data as AARItem[]) ?? []))
.catch(() => setItems([]))
.finally(() => setIsLoading(false));
}, []);
});
const handleGenerate = async () => {
if (!triggerEvent.trim()) {
+3 -2
View File
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { listAgentExecutions, reviewAgentExecution } from "@/lib/api-v2";
import { PageContainer, Badge} from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -28,7 +29,7 @@ export default function AgentsPage() {
.finally(() => setIsLoading(false));
};
useEffect(() => { load(); }, []);
useScopeEffect(() => { load(); });
const handleReview = async (id: string, status: string) => {
try {
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import {Plus } from "lucide-react";
import { listAgreements } from "@/lib/api-v2";
import { PageContainer, Badge, TableEmpty } from "@/components/shared/PageContainer";
@@ -19,12 +20,12 @@ export default function AgreementsPage() {
const [items, setItems] = useState<Agreement[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
useScopeEffect(() => {
listAgreements()
.then((resp) => setItems((resp.data as Agreement[]) ?? []))
.catch(() => setItems([]))
.finally(() => setIsLoading(false));
}, []);
});
return (
<PageContainer
+4 -3
View File
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import {Plus } from "lucide-react";
import { listInterventions } from "@/lib/api-v2";
import { PageContainer, Badge, Card } from "@/components/shared/PageContainer";
@@ -19,12 +20,12 @@ export default function AlphaPage() {
const [items, setItems] = useState<Intervention[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
useScopeEffect(() => {
listInterventions()
.then((resp) => setItems((resp.data as Intervention[]) ?? []))
.catch(() => setItems([]))
.finally(() => setIsLoading(false));
}, []);
});
return (
<PageContainer
+4 -3
View File
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { listBoardMeetings } from "@/lib/api-v2";
import { PageContainer, Badge} from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -18,12 +19,12 @@ export default function BoardPage() {
const [items, setItems] = useState<BoardMeeting[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
useScopeEffect(() => {
listBoardMeetings()
.then((resp) => setItems((resp.data as BoardMeeting[]) ?? []))
.catch(() => setItems([]))
.finally(() => setIsLoading(false));
}, []);
});
return (
<PageContainer title="董事会管理" description="AI 会前材料摘要 → 决议追踪 → 提问清单生成">
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { apiFetch } from "@/lib/api";
import { PlanCard } from "@/components/customer/PlanCard";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -34,10 +35,10 @@ export default function CustomerGrowthPage() {
}
}
useEffect(() => {
useScopeEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
loadPlans();
}, []);
});
async function handleGenerate() {
if (!companyContext.trim()) return;
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { apiFetch } from "@/lib/api";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState";
@@ -50,10 +51,10 @@ export default function CustomerSuccessPage() {
}
}
useEffect(() => {
useScopeEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
loadChurnRisks();
}, []);
});
async function handleQBR() {
if (!qbrData.trim()) return;
@@ -1,10 +1,10 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { Building2, Heart, AlertTriangle, FileText, TrendingUp, TrendingDown, Minus } from "lucide-react";
import Link from "next/link";
import { getDashboardSummary, type DashboardSummary } from "@/lib/dashboard";
import { useCompanyScope } from "@/lib/company-scope";
import { useCompanyScope, useScopeEffect } from "@/lib/company-scope";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState";
import { HealthScoreBadge } from "@/components/shared/HealthScoreBadge";
@@ -18,7 +18,7 @@ export default function InvestorDashboardPage() {
const [summary, setSummary] = useState<DashboardSummary | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
useScopeEffect(() => {
async function load() {
try {
const resp = await getDashboardSummary();
@@ -30,7 +30,7 @@ export default function InvestorDashboardPage() {
}
}
load();
}, []);
});
if (isLoading) {
return (
+4 -3
View File
@@ -1,7 +1,8 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { listMajorEvents, listInquiries } from "@/lib/api-v2";
import { useScopeEffect } from "@/lib/company-scope";
import { PageContainer, Badge } from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState";
@@ -26,7 +27,7 @@ export default function EventsPage() {
const [isLoading, setIsLoading] = useState(true);
const [tab, setTab] = useState<"events" | "inquiries">("events");
useEffect(() => {
useScopeEffect(() => {
Promise.all([listMajorEvents(), listInquiries()])
.then(([e, i]) => {
setEvents((e.data as MajorEvent[]) ?? []);
@@ -37,7 +38,7 @@ export default function EventsPage() {
setInquiries([]);
})
.finally(() => setIsLoading(false));
}, []);
});
return (
<PageContainer title="重大事项 & 追问清单" description="AI 从月报/弱信号中自动识别重大事项 + 生成追问清单">
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import {Sparkles } from "lucide-react";
import { listExitPredictions, predictExit } from "@/lib/api-v2";
import { PageContainer, Badge, Card } from "@/components/shared/PageContainer";
@@ -22,12 +23,12 @@ export default function ExitSignalsPage() {
const [companyData, setCompanyData] = useState("");
const [predicting, setPredicting] = useState(false);
useEffect(() => {
useScopeEffect(() => {
listExitPredictions()
.then((resp) => setItems((resp.data as ExitPrediction[]) ?? []))
.catch(() => setItems([]))
.finally(() => setIsLoading(false));
}, []);
});
const handlePredict = async () => {
if (!companyData.trim()) {
+4 -3
View File
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { apiFetch } from "@/lib/api";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState";
@@ -41,10 +42,10 @@ export default function NudgesPage() {
}
}
useEffect(() => {
useScopeEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
loadNudges();
}, []);
});
async function handleSelect() {
if (!context.trim()) return;
+4 -3
View File
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import {Plus } from "lucide-react";
import { listOKRs } from "@/lib/api-v2";
import { PageContainer, Badge, Card } from "@/components/shared/PageContainer";
@@ -27,12 +28,12 @@ export default function OKRsPage() {
const [items, setItems] = useState<OKRItem[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
useScopeEffect(() => {
listOKRs()
.then((resp) => setItems((resp.data as OKRItem[]) ?? []))
.catch(() => setItems([]))
.finally(() => setIsLoading(false));
}, []);
});
return (
<PageContainer
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { apiFetch } from "@/lib/api";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState";
@@ -49,10 +50,10 @@ export default function PeerCirclesPage() {
}
}
useEffect(() => {
useScopeEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
loadCircles();
}, []);
});
async function handleMatch() {
if (!foundersContext.trim()) return;
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import {Sparkles } from "lucide-react";
import { listPreMortems, runPreMortem, listRedTeams, runRedTeam } from "@/lib/api-v2";
import { PageContainer, Card, Badge } from "@/components/shared/PageContainer";
@@ -33,7 +34,7 @@ export default function PreMortemsPage() {
const [input, setInput] = useState("");
const [perspective, setPerspective] = useState("competitor");
useEffect(() => {
useScopeEffect(() => {
Promise.all([listPreMortems(), listRedTeams()])
.then(([pm, rt]) => {
setPreMortems((pm.data as PreMortemItem[]) ?? []);
@@ -44,7 +45,7 @@ export default function PreMortemsPage() {
setRedTeams([]);
})
.finally(() => setIsLoading(false));
}, []);
});
const handleRun = async () => {
if (!input.trim()) {
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { apiFetch } from "@/lib/api";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState";
@@ -46,10 +47,10 @@ export default function ProductDiagnosticsPage() {
}
}
useEffect(() => {
useScopeEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
loadDiagnostics();
}, []);
});
async function handleDiagnose() {
if (!productInfo.trim()) return;
+3 -2
View File
@@ -1,8 +1,9 @@
"use client";
import { useEffect, useState, useCallback } from "react";
import { useState, useCallback } from "react";
import {Plus, Trash2, Send } from "lucide-react";
import { listReports, submitReport, deleteReport, STATUS_LABELS, STATUS_COLORS, type MonthlyReport } from "@/lib/reports";
import { useScopeEffect } from "@/lib/company-scope";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState";
@@ -31,7 +32,7 @@ export default function ReportsPage() {
}
}, [page]);
useEffect(() => {
useScopeEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
loadReports();
}, [loadReports]);
+3 -2
View File
@@ -1,6 +1,6 @@
"use client";
import { useEffect, useState, useCallback } from "react";
import { useState, useCallback } from "react";
import { Filter } from "lucide-react";
import { toast } from "sonner";
import {
@@ -10,6 +10,7 @@ import {
RISK_STATUS_LABELS,
type RiskEvent,
} from "@/lib/risks";
import { useScopeEffect } from "@/lib/company-scope";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState";
import { RiskCard } from "@/components/risk/RiskCard";
@@ -44,7 +45,7 @@ export default function RisksPage() {
}
}, [page, statusFilter]);
useEffect(() => {
useScopeEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
loadRisks();
}, [loadRisks]);
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { listDecisionSentinels } from "@/lib/api-v2";
import { PageContainer, Badge} from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -18,12 +19,12 @@ export default function SentinelsPage() {
const [items, setItems] = useState<DecisionSentinel[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
useScopeEffect(() => {
listDecisionSentinels()
.then((resp) => setItems((resp.data as DecisionSentinel[]) ?? []))
.catch(() => setItems([]))
.finally(() => setIsLoading(false));
}, []);
});
return (
<PageContainer title="决策前哨" description="AI 识别关键决策岔路口 → 场景分析 → 提前预警">
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { listSynergies, authorizeSynergy } from "@/lib/api-v2";
import { PageContainer, Badge } from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -37,7 +38,7 @@ export default function SynergiesPage() {
.finally(() => setIsLoading(false));
};
useEffect(() => { load(); }, []);
useScopeEffect(() => { load(); });
const handleAuthorize = async (id: string) => {
try {
+4 -3
View File
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { listTalents } from "@/lib/api-v2";
import { PageContainer, Badge } from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -20,12 +21,12 @@ export default function TalentsPage() {
const [items, setItems] = useState<Talent[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
useScopeEffect(() => {
listTalents()
.then((resp) => setItems((resp.data as Talent[]) ?? []))
.catch(() => setItems([]))
.finally(() => setIsLoading(false));
}, []);
});
return (
<PageContainer title="人才引力场" description="人才流动预测 + 主动推荐 + 9-Box 矩阵">
+3 -2
View File
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import {Plus } from "lucide-react";
import { listTasks, updateTask } from "@/lib/api-v2";
import { PageContainer, Badge } from "@/components/shared/PageContainer";
@@ -42,7 +43,7 @@ export default function TasksPage() {
.finally(() => setIsLoading(false));
};
useEffect(() => { load(); }, []);
useScopeEffect(() => { load(); });
const handleStatusChange = async (id: string, status: string) => {
try {
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { listWeakSignals } from "@/lib/api-v2";
import { PageContainer, Badge} from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -26,12 +27,12 @@ export default function WeakSignalsPage() {
const [items, setItems] = useState<WeakSignal[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
useScopeEffect(() => {
listWeakSignals()
.then((resp) => setItems((resp.data as WeakSignal[]) ?? []))
.catch(() => setItems([]))
.finally(() => setIsLoading(false));
}, []);
});
return (
<PageContainer title="弱信号监控" description="技术/情绪/组织/市场四类弱信号采集与关联分析">