Files
AIPortPilot/frontend/src/lib/company-scope.tsx
T
selfrelease c3232f73c8 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
2026-07-20 08:09:33 +08:00

110 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** 企业视角切换 Context — 全局企业筛选器。 */
"use client";
import { createContext, useContext, useEffect, useState, type ReactNode } from "react";
import { listCompanies, type Company } from "@/lib/companies";
interface CompanyScopeValue {
/** 选中的企业 IDnull 表示"全部企业" */
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");
}
// 派发全局事件,让监听了 useScopeEffect 的页面重新加载数据
window.dispatchEvent(new CustomEvent("company-scope-change", { detail: { companyId: id } }));
}
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;
}
/**
* 监听企业视角变化,自动触发回调重新加载数据。
*
* 各页面在 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]);
}