import { useState, useEffect, useCallback, useRef } from 'react'; import { supabase } from '../supabase/client'; import { statusMap } from '../utils/constants'; import { CACHE_CONFIG, PAGINATION_CONFIG } from '../config/app'; import { usePlanRealtime } from './useRealtime'; import type { ProductionPlan, PlanWithSteps, PlanProcessStep, Company, PlanFactory, YarnRatio, Product } from '../types'; interface UsePlanDataOptions { companyId: string | null; pageSize?: number; } interface PlanDataState { plans: PlanWithSteps[]; factories: Company[]; planFactories: PlanFactory[]; operatorNames: Record; yarnRatios: Record; loading: boolean; loadingMore: boolean; hasMore: boolean; currentPage: number; error: Error | null; } /** * 优化的计划数据获取 Hook * 支持分页加载和缓存 */ export function usePlanData({ companyId, pageSize = PAGINATION_CONFIG.defaultPageSize }: UsePlanDataOptions) { const [state, setState] = useState({ plans: [], factories: [], planFactories: [], operatorNames: {}, yarnRatios: {}, 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 = CACHE_CONFIG.planDataDuration; // 获取操作人名称 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 = {}; profilesData?.forEach(p => { names[p.id] = p.username; }); return names; }, []); // 处理计划数据 const processPlansData = useCallback(( planData: ProductionPlan[], stepsData: PlanProcessStep[] | null, inventoryData: any[] | null, priceHistoryData: any[] | null, yarnRatiosData: any[] | null, productsData: Product[] | 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 })); const yarnRatios = (yarnRatiosData || []) .filter(y => y.plan_id === plan.id) .map(y => ({ id: y.id, plan_id: y.plan_id, yarn_name: y.yarn_name, ratio: y.ratio, amount_per_meter: y.amount_per_meter, total_amount: y.total_amount, yarn_type: y.yarn_type, created_at: y.created_at })); // 查找关联的产品信息(通过fabric_code匹配) const product = (productsData || []).find(p => p.fabric_code === plan.fabric_code && p.company_id === plan.purchaser_id ); return { ...plan, processSteps: (stepsData || []).filter(s => s.plan_id === plan.id), inventoryRecords, priceHistory, yarnRatios, // 从产品表获取的字段 image_url: product?.image_url || null, warp_weight: product?.warp_weight || null, weft_weight: product?.weft_weight || null, total_weight: product?.total_weight || null, weight: product?.weight || null }; }); }, []); // 获取数据 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); // 获取计划对应的采购商ID列表 const purchaserIds = [...new Set(planData.map(p => p.purchaser_id))]; // 并行获取关联数据 const [ stepsResult, inventoryResult, priceHistoryResult, pfResult, yarnRatiosResult, productsResult ] = 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), supabase.from('yarn_ratios').select('*').in('plan_id', planIds), // 获取采购商的产品信息(用于获取克重、图片等) supabase .from('products') .select('*') .in('company_id', purchaserIds) ]); // 获取操作人信息 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); // 获取工厂信息(包括所有纺织厂和水洗厂,不仅限于有关联的) const { data: allFactories } = await supabase .from('companies') .select('*') .in('role', ['textile', 'washing']); const factories = allFactories || []; // 处理计划数据 const plansWithSteps = processPlansData( planData, stepsResult.data, inventoryResult.data, priceHistoryResult.data, yarnRatiosResult.data, productsResult.data ); // 构建纱线配比映射 const yarnRatiosMap: Record = {}; (yarnRatiosResult.data || []).forEach((y: any) => { if (!yarnRatiosMap[y.plan_id]) yarnRatiosMap[y.plan_id] = []; yarnRatiosMap[y.plan_id].push(y as YarnRatio); }); setState(prev => { const newState: PlanDataState = { plans: append ? [...prev.plans, ...plansWithSteps] : plansWithSteps, factories: isFirstPage ? factories : prev.factories, planFactories: isFirstPage ? (pfResult.data || []) : prev.planFactories, operatorNames: isFirstPage ? operatorNames : { ...prev.operatorNames, ...operatorNames }, yarnRatios: isFirstPage ? yarnRatiosMap : { ...prev.yarnRatios, ...yarnRatiosMap }, loading: false, loadingMore: false, hasMore: planData.length === pageSize, currentPage: page, error: null }; // 更新缓存 if (isFirstPage) { cacheRef.current = { data: newState, timestamp: Date.now() }; } return newState; }); } catch (err) { setState(prev => ({ ...prev, loading: false, loadingMore: false, error: err instanceof Error ? err : new Error('Unknown error') })); } }, [companyId, pageSize, 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 }; }