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"; "use client";
import { useEffect, useState } from "react"; import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import {Sparkles } from "lucide-react"; import {Sparkles } from "lucide-react";
import { listAARs, generateAAR } from "@/lib/api-v2"; import { listAARs, generateAAR } from "@/lib/api-v2";
import { PageContainer, Card} from "@/components/shared/PageContainer"; import { PageContainer, Card} from "@/components/shared/PageContainer";
@@ -22,12 +23,12 @@ export default function AARsPage() {
const [originalPlan, setOriginalPlan] = useState(""); const [originalPlan, setOriginalPlan] = useState("");
const [actualResult, setActualResult] = useState(""); const [actualResult, setActualResult] = useState("");
useEffect(() => { useScopeEffect(() => {
listAARs() listAARs()
.then((resp) => setItems((resp.data as AARItem[]) ?? [])) .then((resp) => setItems((resp.data as AARItem[]) ?? []))
.catch(() => setItems([])) .catch(() => setItems([]))
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}, []); });
const handleGenerate = async () => { const handleGenerate = async () => {
if (!triggerEvent.trim()) { if (!triggerEvent.trim()) {
+3 -2
View File
@@ -1,6 +1,7 @@
"use client"; "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 { listAgentExecutions, reviewAgentExecution } from "@/lib/api-v2";
import { PageContainer, Badge} from "@/components/shared/PageContainer"; import { PageContainer, Badge} from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -28,7 +29,7 @@ export default function AgentsPage() {
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}; };
useEffect(() => { load(); }, []); useScopeEffect(() => { load(); });
const handleReview = async (id: string, status: string) => { const handleReview = async (id: string, status: string) => {
try { try {
@@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import {Plus } from "lucide-react"; import {Plus } from "lucide-react";
import { listAgreements } from "@/lib/api-v2"; import { listAgreements } from "@/lib/api-v2";
import { PageContainer, Badge, TableEmpty } from "@/components/shared/PageContainer"; import { PageContainer, Badge, TableEmpty } from "@/components/shared/PageContainer";
@@ -19,12 +20,12 @@ export default function AgreementsPage() {
const [items, setItems] = useState<Agreement[]>([]); const [items, setItems] = useState<Agreement[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
useEffect(() => { useScopeEffect(() => {
listAgreements() listAgreements()
.then((resp) => setItems((resp.data as Agreement[]) ?? [])) .then((resp) => setItems((resp.data as Agreement[]) ?? []))
.catch(() => setItems([])) .catch(() => setItems([]))
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}, []); });
return ( return (
<PageContainer <PageContainer
+4 -3
View File
@@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import {Plus } from "lucide-react"; import {Plus } from "lucide-react";
import { listInterventions } from "@/lib/api-v2"; import { listInterventions } from "@/lib/api-v2";
import { PageContainer, Badge, Card } from "@/components/shared/PageContainer"; import { PageContainer, Badge, Card } from "@/components/shared/PageContainer";
@@ -19,12 +20,12 @@ export default function AlphaPage() {
const [items, setItems] = useState<Intervention[]>([]); const [items, setItems] = useState<Intervention[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
useEffect(() => { useScopeEffect(() => {
listInterventions() listInterventions()
.then((resp) => setItems((resp.data as Intervention[]) ?? [])) .then((resp) => setItems((resp.data as Intervention[]) ?? []))
.catch(() => setItems([])) .catch(() => setItems([]))
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}, []); });
return ( return (
<PageContainer <PageContainer
+4 -3
View File
@@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { listBoardMeetings } from "@/lib/api-v2"; import { listBoardMeetings } from "@/lib/api-v2";
import { PageContainer, Badge} from "@/components/shared/PageContainer"; import { PageContainer, Badge} from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -18,12 +19,12 @@ export default function BoardPage() {
const [items, setItems] = useState<BoardMeeting[]>([]); const [items, setItems] = useState<BoardMeeting[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
useEffect(() => { useScopeEffect(() => {
listBoardMeetings() listBoardMeetings()
.then((resp) => setItems((resp.data as BoardMeeting[]) ?? [])) .then((resp) => setItems((resp.data as BoardMeeting[]) ?? []))
.catch(() => setItems([])) .catch(() => setItems([]))
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}, []); });
return ( return (
<PageContainer title="董事会管理" description="AI 会前材料摘要 → 决议追踪 → 提问清单生成"> <PageContainer title="董事会管理" description="AI 会前材料摘要 → 决议追踪 → 提问清单生成">
@@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { apiFetch } from "@/lib/api"; import { apiFetch } from "@/lib/api";
import { PlanCard } from "@/components/customer/PlanCard"; import { PlanCard } from "@/components/customer/PlanCard";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; 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 // eslint-disable-next-line react-hooks/set-state-in-effect
loadPlans(); loadPlans();
}, []); });
async function handleGenerate() { async function handleGenerate() {
if (!companyContext.trim()) return; if (!companyContext.trim()) return;
@@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { apiFetch } from "@/lib/api"; import { apiFetch } from "@/lib/api";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState"; 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 // eslint-disable-next-line react-hooks/set-state-in-effect
loadChurnRisks(); loadChurnRisks();
}, []); });
async function handleQBR() { async function handleQBR() {
if (!qbrData.trim()) return; if (!qbrData.trim()) return;
@@ -1,10 +1,10 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useState } from "react";
import { Building2, Heart, AlertTriangle, FileText, TrendingUp, TrendingDown, Minus } from "lucide-react"; import { Building2, Heart, AlertTriangle, FileText, TrendingUp, TrendingDown, Minus } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { getDashboardSummary, type DashboardSummary } from "@/lib/dashboard"; 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 { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState"; import { EmptyState } from "@/components/shared/EmptyState";
import { HealthScoreBadge } from "@/components/shared/HealthScoreBadge"; import { HealthScoreBadge } from "@/components/shared/HealthScoreBadge";
@@ -18,7 +18,7 @@ export default function InvestorDashboardPage() {
const [summary, setSummary] = useState<DashboardSummary | null>(null); const [summary, setSummary] = useState<DashboardSummary | null>(null);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
useEffect(() => { useScopeEffect(() => {
async function load() { async function load() {
try { try {
const resp = await getDashboardSummary(); const resp = await getDashboardSummary();
@@ -30,7 +30,7 @@ export default function InvestorDashboardPage() {
} }
} }
load(); load();
}, []); });
if (isLoading) { if (isLoading) {
return ( return (
+4 -3
View File
@@ -1,7 +1,8 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useState } from "react";
import { listMajorEvents, listInquiries } from "@/lib/api-v2"; import { listMajorEvents, listInquiries } from "@/lib/api-v2";
import { useScopeEffect } from "@/lib/company-scope";
import { PageContainer, Badge } from "@/components/shared/PageContainer"; import { PageContainer, Badge } from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState"; import { EmptyState } from "@/components/shared/EmptyState";
@@ -26,7 +27,7 @@ export default function EventsPage() {
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [tab, setTab] = useState<"events" | "inquiries">("events"); const [tab, setTab] = useState<"events" | "inquiries">("events");
useEffect(() => { useScopeEffect(() => {
Promise.all([listMajorEvents(), listInquiries()]) Promise.all([listMajorEvents(), listInquiries()])
.then(([e, i]) => { .then(([e, i]) => {
setEvents((e.data as MajorEvent[]) ?? []); setEvents((e.data as MajorEvent[]) ?? []);
@@ -37,7 +38,7 @@ export default function EventsPage() {
setInquiries([]); setInquiries([]);
}) })
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}, []); });
return ( return (
<PageContainer title="重大事项 & 追问清单" description="AI 从月报/弱信号中自动识别重大事项 + 生成追问清单"> <PageContainer title="重大事项 & 追问清单" description="AI 从月报/弱信号中自动识别重大事项 + 生成追问清单">
@@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import {Sparkles } from "lucide-react"; import {Sparkles } from "lucide-react";
import { listExitPredictions, predictExit } from "@/lib/api-v2"; import { listExitPredictions, predictExit } from "@/lib/api-v2";
import { PageContainer, Badge, Card } from "@/components/shared/PageContainer"; import { PageContainer, Badge, Card } from "@/components/shared/PageContainer";
@@ -22,12 +23,12 @@ export default function ExitSignalsPage() {
const [companyData, setCompanyData] = useState(""); const [companyData, setCompanyData] = useState("");
const [predicting, setPredicting] = useState(false); const [predicting, setPredicting] = useState(false);
useEffect(() => { useScopeEffect(() => {
listExitPredictions() listExitPredictions()
.then((resp) => setItems((resp.data as ExitPrediction[]) ?? [])) .then((resp) => setItems((resp.data as ExitPrediction[]) ?? []))
.catch(() => setItems([])) .catch(() => setItems([]))
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}, []); });
const handlePredict = async () => { const handlePredict = async () => {
if (!companyData.trim()) { if (!companyData.trim()) {
+4 -3
View File
@@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { apiFetch } from "@/lib/api"; import { apiFetch } from "@/lib/api";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState"; 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 // eslint-disable-next-line react-hooks/set-state-in-effect
loadNudges(); loadNudges();
}, []); });
async function handleSelect() { async function handleSelect() {
if (!context.trim()) return; if (!context.trim()) return;
+4 -3
View File
@@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import {Plus } from "lucide-react"; import {Plus } from "lucide-react";
import { listOKRs } from "@/lib/api-v2"; import { listOKRs } from "@/lib/api-v2";
import { PageContainer, Badge, Card } from "@/components/shared/PageContainer"; import { PageContainer, Badge, Card } from "@/components/shared/PageContainer";
@@ -27,12 +28,12 @@ export default function OKRsPage() {
const [items, setItems] = useState<OKRItem[]>([]); const [items, setItems] = useState<OKRItem[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
useEffect(() => { useScopeEffect(() => {
listOKRs() listOKRs()
.then((resp) => setItems((resp.data as OKRItem[]) ?? [])) .then((resp) => setItems((resp.data as OKRItem[]) ?? []))
.catch(() => setItems([])) .catch(() => setItems([]))
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}, []); });
return ( return (
<PageContainer <PageContainer
@@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { apiFetch } from "@/lib/api"; import { apiFetch } from "@/lib/api";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState"; 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 // eslint-disable-next-line react-hooks/set-state-in-effect
loadCircles(); loadCircles();
}, []); });
async function handleMatch() { async function handleMatch() {
if (!foundersContext.trim()) return; if (!foundersContext.trim()) return;
@@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import {Sparkles } from "lucide-react"; import {Sparkles } from "lucide-react";
import { listPreMortems, runPreMortem, listRedTeams, runRedTeam } from "@/lib/api-v2"; import { listPreMortems, runPreMortem, listRedTeams, runRedTeam } from "@/lib/api-v2";
import { PageContainer, Card, Badge } from "@/components/shared/PageContainer"; import { PageContainer, Card, Badge } from "@/components/shared/PageContainer";
@@ -33,7 +34,7 @@ export default function PreMortemsPage() {
const [input, setInput] = useState(""); const [input, setInput] = useState("");
const [perspective, setPerspective] = useState("competitor"); const [perspective, setPerspective] = useState("competitor");
useEffect(() => { useScopeEffect(() => {
Promise.all([listPreMortems(), listRedTeams()]) Promise.all([listPreMortems(), listRedTeams()])
.then(([pm, rt]) => { .then(([pm, rt]) => {
setPreMortems((pm.data as PreMortemItem[]) ?? []); setPreMortems((pm.data as PreMortemItem[]) ?? []);
@@ -44,7 +45,7 @@ export default function PreMortemsPage() {
setRedTeams([]); setRedTeams([]);
}) })
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}, []); });
const handleRun = async () => { const handleRun = async () => {
if (!input.trim()) { if (!input.trim()) {
@@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { apiFetch } from "@/lib/api"; import { apiFetch } from "@/lib/api";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState"; 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 // eslint-disable-next-line react-hooks/set-state-in-effect
loadDiagnostics(); loadDiagnostics();
}, []); });
async function handleDiagnose() { async function handleDiagnose() {
if (!productInfo.trim()) return; if (!productInfo.trim()) return;
+3 -2
View File
@@ -1,8 +1,9 @@
"use client"; "use client";
import { useEffect, useState, useCallback } from "react"; import { useState, useCallback } from "react";
import {Plus, Trash2, Send } from "lucide-react"; import {Plus, Trash2, Send } from "lucide-react";
import { listReports, submitReport, deleteReport, STATUS_LABELS, STATUS_COLORS, type MonthlyReport } from "@/lib/reports"; 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 { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState"; import { EmptyState } from "@/components/shared/EmptyState";
@@ -31,7 +32,7 @@ export default function ReportsPage() {
} }
}, [page]); }, [page]);
useEffect(() => { useScopeEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect // eslint-disable-next-line react-hooks/set-state-in-effect
loadReports(); loadReports();
}, [loadReports]); }, [loadReports]);
+3 -2
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import { useEffect, useState, useCallback } from "react"; import { useState, useCallback } from "react";
import { Filter } from "lucide-react"; import { Filter } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { import {
@@ -10,6 +10,7 @@ import {
RISK_STATUS_LABELS, RISK_STATUS_LABELS,
type RiskEvent, type RiskEvent,
} from "@/lib/risks"; } from "@/lib/risks";
import { useScopeEffect } from "@/lib/company-scope";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState"; import { EmptyState } from "@/components/shared/EmptyState";
import { RiskCard } from "@/components/risk/RiskCard"; import { RiskCard } from "@/components/risk/RiskCard";
@@ -44,7 +45,7 @@ export default function RisksPage() {
} }
}, [page, statusFilter]); }, [page, statusFilter]);
useEffect(() => { useScopeEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect // eslint-disable-next-line react-hooks/set-state-in-effect
loadRisks(); loadRisks();
}, [loadRisks]); }, [loadRisks]);
@@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { listDecisionSentinels } from "@/lib/api-v2"; import { listDecisionSentinels } from "@/lib/api-v2";
import { PageContainer, Badge} from "@/components/shared/PageContainer"; import { PageContainer, Badge} from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -18,12 +19,12 @@ export default function SentinelsPage() {
const [items, setItems] = useState<DecisionSentinel[]>([]); const [items, setItems] = useState<DecisionSentinel[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
useEffect(() => { useScopeEffect(() => {
listDecisionSentinels() listDecisionSentinels()
.then((resp) => setItems((resp.data as DecisionSentinel[]) ?? [])) .then((resp) => setItems((resp.data as DecisionSentinel[]) ?? []))
.catch(() => setItems([])) .catch(() => setItems([]))
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}, []); });
return ( return (
<PageContainer title="决策前哨" description="AI 识别关键决策岔路口 → 场景分析 → 提前预警"> <PageContainer title="决策前哨" description="AI 识别关键决策岔路口 → 场景分析 → 提前预警">
@@ -1,6 +1,7 @@
"use client"; "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 { listSynergies, authorizeSynergy } from "@/lib/api-v2";
import { PageContainer, Badge } from "@/components/shared/PageContainer"; import { PageContainer, Badge } from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -37,7 +38,7 @@ export default function SynergiesPage() {
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}; };
useEffect(() => { load(); }, []); useScopeEffect(() => { load(); });
const handleAuthorize = async (id: string) => { const handleAuthorize = async (id: string) => {
try { try {
+4 -3
View File
@@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { listTalents } from "@/lib/api-v2"; import { listTalents } from "@/lib/api-v2";
import { PageContainer, Badge } from "@/components/shared/PageContainer"; import { PageContainer, Badge } from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -20,12 +21,12 @@ export default function TalentsPage() {
const [items, setItems] = useState<Talent[]>([]); const [items, setItems] = useState<Talent[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
useEffect(() => { useScopeEffect(() => {
listTalents() listTalents()
.then((resp) => setItems((resp.data as Talent[]) ?? [])) .then((resp) => setItems((resp.data as Talent[]) ?? []))
.catch(() => setItems([])) .catch(() => setItems([]))
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}, []); });
return ( return (
<PageContainer title="人才引力场" description="人才流动预测 + 主动推荐 + 9-Box 矩阵"> <PageContainer title="人才引力场" description="人才流动预测 + 主动推荐 + 9-Box 矩阵">
+3 -2
View File
@@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import {Plus } from "lucide-react"; import {Plus } from "lucide-react";
import { listTasks, updateTask } from "@/lib/api-v2"; import { listTasks, updateTask } from "@/lib/api-v2";
import { PageContainer, Badge } from "@/components/shared/PageContainer"; import { PageContainer, Badge } from "@/components/shared/PageContainer";
@@ -42,7 +43,7 @@ export default function TasksPage() {
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}; };
useEffect(() => { load(); }, []); useScopeEffect(() => { load(); });
const handleStatusChange = async (id: string, status: string) => { const handleStatusChange = async (id: string, status: string) => {
try { try {
@@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useState } from "react";
import { useScopeEffect } from "@/lib/company-scope";
import { listWeakSignals } from "@/lib/api-v2"; import { listWeakSignals } from "@/lib/api-v2";
import { PageContainer, Badge} from "@/components/shared/PageContainer"; import { PageContainer, Badge} from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
@@ -26,12 +27,12 @@ export default function WeakSignalsPage() {
const [items, setItems] = useState<WeakSignal[]>([]); const [items, setItems] = useState<WeakSignal[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
useEffect(() => { useScopeEffect(() => {
listWeakSignals() listWeakSignals()
.then((resp) => setItems((resp.data as WeakSignal[]) ?? [])) .then((resp) => setItems((resp.data as WeakSignal[]) ?? []))
.catch(() => setItems([])) .catch(() => setItems([]))
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}, []); });
return ( return (
<PageContainer title="弱信号监控" description="技术/情绪/组织/市场四类弱信号采集与关联分析"> <PageContainer title="弱信号监控" description="技术/情绪/组织/市场四类弱信号采集与关联分析">
+22 -1
View File
@@ -51,7 +51,7 @@ export function CompanyScopeProvider({ children }: { children: ReactNode }) {
} }
}, []); }, []);
// 持久化选择 // 持久化选择 + 派发事件通知各页面重新加载
function setCompanyId(id: string | null) { function setCompanyId(id: string | null) {
setCompanyIdState(id); setCompanyIdState(id);
if (id) { if (id) {
@@ -59,6 +59,8 @@ export function CompanyScopeProvider({ children }: { children: ReactNode }) {
} else { } else {
localStorage.setItem("company_scope_id", "all"); localStorage.setItem("company_scope_id", "all");
} }
// 派发全局事件,让监听了 useScopeEffect 的页面重新加载数据
window.dispatchEvent(new CustomEvent("company-scope-change", { detail: { companyId: id } }));
} }
const companyName = companyId const companyName = companyId
@@ -86,3 +88,22 @@ export function useCompanyScope() {
} }
return ctx; return ctx;
} }
/**
*
*
* useEffect hook
* apiFetch company_id
*
* @param callback
* @param deps
*/
export function useScopeEffect(callback: () => void, deps: unknown[] = []) {
const { companyId } = useCompanyScope();
useEffect(() => {
callback();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [companyId, ...deps]);
}