feat(scope): add company scope switcher — two views (by-task all companies / by-company all tasks)

- CompanyScopeContext + Provider with localStorage persistence
- Sidebar company selector (desktop + mobile)
- apiFetch auto-injects company_id on GET requests
- Dashboard summary API supports company_id filter
- Milestones/Financial/DigitalTwins pages use scope instead of manual input
- HealthHeatmap/HealthTrends components react to scope changes
This commit is contained in:
selfrelease
2026-07-20 07:47:53 +08:00
parent 8b8926bde3
commit e81aa6828e
10 changed files with 306 additions and 53 deletions
@@ -3,6 +3,7 @@
import { useEffect, useState } from "react";
import {Sparkles } from "lucide-react";
import { listDigitalTwins, buildDigitalTwin, simulateTwin } from "@/lib/api-v2";
import { useCompanyScope } from "@/lib/company-scope";
import { PageContainer, Card, Badge } from "@/components/shared/PageContainer";
import { LoadingSpinner } from "@/components/shared/LoadingSpinner";
import { EmptyState } from "@/components/shared/EmptyState";
@@ -20,6 +21,7 @@ interface SimulationResult {
}
export default function DigitalTwinsPage() {
const { companyId } = useCompanyScope();
const [items, setItems] = useState<DigitalTwinItem[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [companyData, setCompanyData] = useState("");
@@ -27,11 +29,17 @@ export default function DigitalTwinsPage() {
const [simResult, setSimResult] = useState<SimulationResult | null>(null);
useEffect(() => {
listDigitalTwins("")
.then(() => setItems([]))
if (!companyId) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setItems([]);
setIsLoading(false);
return;
}
listDigitalTwins(companyId)
.then((resp) => setItems((resp.data as DigitalTwinItem[]) ?? []))
.catch(() => setItems([]))
.finally(() => setIsLoading(false));
}, []);
}, [companyId]);
const handleBuild = async () => {
if (!companyData.trim()) {
@@ -62,6 +70,10 @@ export default function DigitalTwinsPage() {
return (
<PageContainer title="数字孪生" description="企业模型 + 场景模拟 + 精度追踪">
{!companyId ? (
<EmptyState title="请先在侧边栏选择企业" description="数字孪生需要指定具体企业,请在左上角企业选择器中选择" />
) : (
<>
<Card>
<h3 className="font-medium text-gray-900"></h3>
<textarea
@@ -124,6 +136,8 @@ export default function DigitalTwinsPage() {
))}
</div>
)}
</>
)}
</PageContainer>
);
}