iloom-flatten/src/hooks/usePlanData.ts
Chever John b1c2c33aa2
refactor: replace Supabase SDK with self-hosted REST API client layer
WHY: migrating from Supabase BaaS to self-hosted iloom backend
(Go microservices) deployed on K3s. Supabase client and realtime
subscriptions no longer applicable.

HOW:
- add src/api/ layer (client.ts, auth.ts, purchaser.ts, textile.ts,
  washing.ts, websocket.ts) targeting iloom-gateway REST endpoints
- rewrite all hooks (usePlanData, useInventoryData, useDashboardData,
  useRealtime, etc.) to use new API client instead of Supabase queries
- rewrite all page/component data fetching and mutations accordingly
- delete src/supabase/client.ts, remove @supabase/supabase-js dep
- add Dockerfile (multi-stage node build + nginx) and nginx.conf
  with reverse proxy to iloom-gateway for /api/ and /ws/ routes
- adjust webpack.config.js for API_URL environment variable injection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-14 12:38:47 +08:00

148 lines
4.1 KiB
TypeScript

import { useState, useEffect, useCallback, useRef } from 'react';
import { purchaserApi } from '../api';
import type { PlanDetail } from '../api/purchaser';
import { CACHE_CONFIG, PAGINATION_CONFIG } from '../config/app';
import { usePlanRealtime } from './useRealtime';
import type {
PlanWithSteps,
Company,
PlanFactory,
YarnRatio,
} 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;
}
function mapPlanDetail(plan: PlanDetail): PlanWithSteps {
return {
...plan,
processSteps: plan.process_steps,
inventoryRecords: plan.inventory_records,
priceHistory: plan.price_history,
};
}
export function usePlanData({ companyId, pageSize = PAGINATION_CONFIG.defaultPageSize }: UsePlanDataOptions) {
const [state, setState] = useState<PlanDataState>({
plans: [],
factories: [],
planFactories: [],
operatorNames: {},
yarnRatios: {},
loading: true,
loadingMore: false,
hasMore: true,
currentPage: 1,
error: null
});
const cacheRef = useRef<{
data: PlanDataState | null;
timestamp: number;
}>({ data: null, timestamp: 0 });
const CACHE_DURATION = CACHE_CONFIG.planDataDuration;
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 res = await purchaserApi.listPlans({ page, page_size: pageSize });
const { plans: planDetails, factories, plan_factories, operator_names } = res.data;
const plansWithSteps = planDetails.map(mapPlanDetail);
const yarnRatiosMap: Record<string, YarnRatio[]> = {};
planDetails.forEach(p => {
if (p.yarn_ratios && p.yarn_ratios.length > 0) {
yarnRatiosMap[p.id] = p.yarn_ratios;
}
});
setState(prev => {
const newState: PlanDataState = {
plans: append ? [...prev.plans, ...plansWithSteps] : plansWithSteps,
factories: isFirstPage ? factories : prev.factories,
planFactories: isFirstPage ? plan_factories : prev.planFactories,
operatorNames: isFirstPage ? operator_names : { ...prev.operatorNames, ...operator_names },
yarnRatios: isFirstPage ? yarnRatiosMap : { ...prev.yarnRatios, ...yarnRatiosMap },
loading: false,
loadingMore: false,
hasMore: res.meta?.has_more ?? planDetails.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]);
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
};
}