iloom-flatten/src/hooks/usePlanData.ts

312 lines
9.0 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,
YarnRatio,
Product
} from '../types';
interface UsePlanDataOptions {
companyId: string | null;
pageSize?: number;
}
interface PlanDataState {
plans: PlanWithSteps[];
factories: Company[];
planFactories: PlanFactory[];
operatorNames: Record<string, string>;
yarnRatios: Record<string, YarnRatio[]>;
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: {},
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 = 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,
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);
// 获取工厂信息
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,
yarnRatiosResult.data,
productsResult.data
);
// 构建纱线配比映射
const yarnRatiosMap: Record<string, YarnRatio[]> = {};
(yarnRatiosResult.data || []).forEach((y: any) => {
if (!yarnRatiosMap[y.plan_id]) yarnRatiosMap[y.plan_id] = [];
yarnRatiosMap[y.plan_id].push(y as YarnRatio);
});
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 },
yarnRatios: isFirstPage ? yarnRatiosMap : { ...state.yarnRatios, ...yarnRatiosMap },
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
};
}