iloom-flatten/src/hooks/usePlanData.ts

260 lines
7.1 KiB
TypeScript
Raw Normal View History

import { useState, useEffect, useCallback, useRef } from 'react';
import { supabase } from '../supabase/client';
import { statusMap } from '../utils/constants';
import { usePlanRealtime } from './useRealtime';
import type {
ProductionPlan,
PlanWithSteps,
PlanProcessStep,
Company,
PlanFactory
} from '../types';
interface UsePlanDataOptions {
companyId: string | null;
pageSize?: number;
}
interface PlanDataState {
plans: PlanWithSteps[];
factories: Company[];
planFactories: PlanFactory[];
operatorNames: Record<string, string>;
loading: boolean;
loadingMore: boolean;
hasMore: boolean;
currentPage: number;
error: Error | null;
}
/**
* Hook
*
*/
export function usePlanData({ companyId, pageSize = 20 }: UsePlanDataOptions) {
const [state, setState] = useState<PlanDataState>({
plans: [],
factories: [],
planFactories: [],
operatorNames: {},
loading: true,
loadingMore: false,
hasMore: true,
currentPage: 1,
error: null
});
// 使用 ref 存储缓存,避免不必要的重渲染
const cacheRef = useRef<{
data: PlanDataState | null;
timestamp: number;
}>({ data: null, timestamp: 0 });
const CACHE_DURATION = 10000; // 10秒缓存
// 获取操作人名称
const fetchOperatorNames = useCallback(async (operatorIds: string[]) => {
if (operatorIds.length === 0) return {};
const { data: profilesData } = await supabase
.from('profiles')
.select('id, username')
.in('id', operatorIds);
const names: Record<string, string> = {};
profilesData?.forEach(p => {
names[p.id] = p.username;
});
return names;
}, []);
// 处理计划数据
const processPlansData = useCallback((
planData: ProductionPlan[],
stepsData: PlanProcessStep[] | null,
inventoryData: any[] | null,
priceHistoryData: any[] | null
): PlanWithSteps[] => {
return planData.map(plan => {
const inventoryRecords = (inventoryData || [])
.filter(r => r.plan_id === plan.id)
.map(r => ({ time: r.created_at, quantity: r.quantity, rolls: r.rolls }));
const priceHistory = (priceHistoryData || [])
.filter(h => h.plan_id === plan.id)
.map(h => ({
id: h.id,
old_price: h.old_price,
new_price: h.new_price,
change_amount: h.change_amount,
operator_name: h.operator_name,
change_reason: h.change_reason,
created_at: h.created_at
}));
return {
...plan,
processSteps: (stepsData || []).filter(s => s.plan_id === plan.id),
inventoryRecords,
priceHistory
};
});
}, []);
// 获取数据
const fetchData = useCallback(async (page: number = 1, append: boolean = false) => {
if (!companyId) {
setState(prev => ({ ...prev, loading: false }));
return;
}
const isFirstPage = page === 1;
if (isFirstPage) {
// 检查缓存
const now = Date.now();
if (cacheRef.current.data && now - cacheRef.current.timestamp < CACHE_DURATION) {
setState(cacheRef.current.data);
return;
}
setState(prev => ({ ...prev, loading: true, error: null }));
} else {
setState(prev => ({ ...prev, loadingMore: true }));
}
try {
const offset = (page - 1) * pageSize;
// 获取计划
const { data: planData, error: planError } = await supabase
.from('production_plans')
.select('*')
.eq('purchaser_id', companyId)
.order('created_at', { ascending: false })
.range(offset, offset + pageSize - 1);
if (planError) throw planError;
if (!planData || planData.length === 0) {
setState(prev => ({
...prev,
loading: false,
loadingMore: false,
hasMore: false,
plans: isFirstPage ? [] : prev.plans
}));
return;
}
const planIds = planData.map(p => p.id);
// 并行获取关联数据
const [
stepsResult,
inventoryResult,
priceHistoryResult,
pfResult
] = await Promise.all([
supabase.from('plan_process_steps').select('*').in('plan_id', planIds),
supabase
.from('inventory_records')
.select('plan_id, quantity, rolls, created_at')
.in('plan_id', planIds)
.order('created_at', { ascending: false })
.limit(100),
supabase
.from('production_price_history')
.select('*')
.in('plan_id', planIds)
.order('created_at', { ascending: false })
.limit(50),
supabase.from('plan_factories').select('*').in('plan_id', planIds)
]);
// 获取操作人信息
const operatorIds = [...new Set(
(stepsResult.data || [])
.filter(s => s.operator_id)
.map(s => s.operator_id)
.filter(Boolean)
)] as string[];
const operatorNames = await fetchOperatorNames(operatorIds);
// 获取工厂信息
let factories: Company[] = [];
if (pfResult.data && pfResult.data.length > 0) {
const factoryIds = pfResult.data.map(pf => pf.factory_id);
const { data: factoryData } = await supabase
.from('companies')
.select('*')
.in('id', factoryIds);
factories = factoryData || [];
}
// 处理计划数据
const plansWithSteps = processPlansData(
planData,
stepsResult.data,
inventoryResult.data,
priceHistoryResult.data
);
const newState: PlanDataState = {
plans: append ? [...state.plans, ...plansWithSteps] : plansWithSteps,
factories: isFirstPage ? factories : state.factories,
planFactories: isFirstPage ? (pfResult.data || []) : state.planFactories,
operatorNames: isFirstPage ? operatorNames : { ...state.operatorNames, ...operatorNames },
loading: false,
loadingMore: false,
hasMore: planData.length === pageSize,
currentPage: page,
error: null
};
// 更新缓存
if (isFirstPage) {
cacheRef.current = { data: newState, timestamp: Date.now() };
}
setState(newState);
} catch (err) {
setState(prev => ({
...prev,
loading: false,
loadingMore: false,
error: err instanceof Error ? err : new Error('Unknown error')
}));
}
}, [companyId, pageSize, state.plans, fetchOperatorNames, processPlansData]);
// 加载更多
const loadMore = useCallback(() => {
if (state.loadingMore || !state.hasMore) return;
fetchData(state.currentPage + 1, true);
}, [fetchData, state.currentPage, state.hasMore, state.loadingMore]);
// 刷新数据
const refetch = useCallback(() => {
cacheRef.current.timestamp = 0;
fetchData(1, false);
}, [fetchData]);
// 初始加载
useEffect(() => {
fetchData(1, false);
}, [fetchData]);
// 使用统一的实时订阅管理器
usePlanRealtime(companyId, () => {
cacheRef.current.timestamp = 0;
fetchData(1, false);
});
return {
...state,
loadMore,
refetch
};
}