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:
@@ -0,0 +1,88 @@
|
||||
/** 企业视角切换 Context — 全局企业筛选器。 */
|
||||
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext, useEffect, useState, type ReactNode } from "react";
|
||||
import { listCompanies, type Company } from "@/lib/companies";
|
||||
|
||||
interface CompanyScopeValue {
|
||||
/** 选中的企业 ID,null 表示"全部企业" */
|
||||
companyId: string | null;
|
||||
/** 选中的企业名称,null 表示"全部企业" */
|
||||
companyName: string | null;
|
||||
/** 企业列表 */
|
||||
companies: Company[];
|
||||
/** 切换企业 */
|
||||
setCompanyId: (id: string | null) => void;
|
||||
/** 是否加载中 */
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const CompanyScopeContext = createContext<CompanyScopeValue | undefined>(undefined);
|
||||
|
||||
/**
|
||||
* 企业视角 Provider。
|
||||
*
|
||||
* 在侧边栏顶部提供企业选择器,选中后所有页面自动按该企业过滤数据。
|
||||
* 选择"全部企业"时恢复全局视图。
|
||||
*/
|
||||
export function CompanyScopeProvider({ children }: { children: ReactNode }) {
|
||||
const [companyId, setCompanyIdState] = useState<string | null>(null);
|
||||
const [companies, setCompanies] = useState<Company[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
// 加载企业列表
|
||||
useEffect(() => {
|
||||
listCompanies({ page_size: 100 })
|
||||
.then((resp) => {
|
||||
if (resp.data?.items) setCompanies(resp.data.items);
|
||||
})
|
||||
.catch(() => {
|
||||
// ignore
|
||||
})
|
||||
.finally(() => setIsLoading(false));
|
||||
}, []);
|
||||
|
||||
// 从 localStorage 恢复选择
|
||||
useEffect(() => {
|
||||
const saved = localStorage.getItem("company_scope_id");
|
||||
if (saved && saved !== "all") {
|
||||
setCompanyIdState(saved);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 持久化选择
|
||||
function setCompanyId(id: string | null) {
|
||||
setCompanyIdState(id);
|
||||
if (id) {
|
||||
localStorage.setItem("company_scope_id", id);
|
||||
} else {
|
||||
localStorage.setItem("company_scope_id", "all");
|
||||
}
|
||||
}
|
||||
|
||||
const companyName = companyId
|
||||
? companies.find((c) => c.id === companyId)?.name ?? null
|
||||
: null;
|
||||
|
||||
return (
|
||||
<CompanyScopeContext.Provider
|
||||
value={{ companyId, companyName, companies, setCompanyId, isLoading }}
|
||||
>
|
||||
{children}
|
||||
</CompanyScopeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用企业视角 Context。
|
||||
*
|
||||
* 返回当前选中的企业 ID,用于各页面过滤数据。
|
||||
*/
|
||||
export function useCompanyScope() {
|
||||
const ctx = useContext(CompanyScopeContext);
|
||||
if (!ctx) {
|
||||
throw new Error("useCompanyScope 必须在 CompanyScopeProvider 内使用");
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
Reference in New Issue
Block a user