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:
@@ -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()) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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()) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 矩阵">
|
||||||
|
|||||||
@@ -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="技术/情绪/组织/市场四类弱信号采集与关联分析">
|
||||||
|
|||||||
@@ -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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user