ERP-node/frontend/lib/registry/components/repeater-field-group/useSubDataLookup.ts

228 lines
6.7 KiB
TypeScript
Raw Normal View History

"use client";
import { useState, useEffect, useCallback, useRef } from "react";
import { apiClient } from "@/lib/api/client";
import { SubDataLookupConfig, SubDataState } from "@/types/repeater";
const LOG_PREFIX = {
INFO: "[SubDataLookup]",
DEBUG: "[SubDataLookup]",
WARN: "[SubDataLookup]",
ERROR: "[SubDataLookup]",
};
export interface UseSubDataLookupProps {
config: SubDataLookupConfig;
linkValue: string | number | null; // 상위 항목의 연결 값 (예: item_code)
itemIndex: number; // 상위 항목 인덱스
enabled?: boolean; // 기능 활성화 여부
}
export interface UseSubDataLookupReturn {
data: any[]; // 조회된 하위 데이터
isLoading: boolean; // 로딩 상태
error: string | null; // 에러 메시지
selectedItem: any | null; // 선택된 하위 항목
setSelectedItem: (item: any | null) => void; // 선택 항목 설정
isInputEnabled: boolean; // 조건부 입력 활성화 여부
maxValue: number | null; // 최대 입력 가능 값
isExpanded: boolean; // 확장 상태
setIsExpanded: (expanded: boolean) => void; // 확장 상태 설정
refetch: () => void; // 데이터 재조회
getSelectionSummary: () => string; // 선택 요약 텍스트
}
/**
*
* /
*/
export function useSubDataLookup(props: UseSubDataLookupProps): UseSubDataLookupReturn {
const { config, linkValue, itemIndex, enabled = true } = props;
// 상태
const [data, setData] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [selectedItem, setSelectedItem] = useState<any | null>(null);
const [isExpanded, setIsExpanded] = useState(false);
// 이전 linkValue 추적 (중복 호출 방지)
const prevLinkValueRef = useRef<string | number | null>(null);
// 데이터 조회 함수
const fetchData = useCallback(async () => {
// 비활성화 또는 linkValue 없으면 스킵
if (!enabled || !config?.enabled || !linkValue) {
console.log(`${LOG_PREFIX.DEBUG} 조회 스킵:`, {
enabled,
configEnabled: config?.enabled,
linkValue,
itemIndex,
});
setData([]);
setSelectedItem(null);
return;
}
const { tableName, linkColumn, additionalFilters } = config.lookup;
if (!tableName || !linkColumn) {
console.warn(`${LOG_PREFIX.WARN} 필수 설정 누락:`, { tableName, linkColumn });
return;
}
console.log(`${LOG_PREFIX.INFO} 하위 데이터 조회 시작:`, {
tableName,
linkColumn,
linkValue,
itemIndex,
});
setIsLoading(true);
setError(null);
try {
// 검색 조건 구성 - 정확한 값 매칭을 위해 equals 연산자 사용
const searchCondition: Record<string, any> = {
[linkColumn]: { value: linkValue, operator: "equals" },
...additionalFilters,
};
console.log(`${LOG_PREFIX.DEBUG} API 요청 조건:`, {
tableName,
linkColumn,
linkValue,
searchCondition,
});
const response = await apiClient.post(`/table-management/tables/${tableName}/data`, {
page: 1,
size: 100,
search: searchCondition,
autoFilter: { enabled: true },
});
if (response.data?.success) {
const items = response.data?.data?.data || response.data?.data || [];
console.log(`${LOG_PREFIX.DEBUG} API 응답:`, {
dataCount: items.length,
firstItem: items[0],
tableName,
});
setData(items);
} else {
console.warn(`${LOG_PREFIX.WARN} API 응답 실패:`, response.data);
setData([]);
setError("데이터 조회에 실패했습니다");
}
} catch (err: any) {
console.error(`${LOG_PREFIX.ERROR} 하위 데이터 조회 실패:`, {
error: err.message,
config,
linkValue,
});
setError(err.message || "데이터 조회 중 오류가 발생했습니다");
setData([]);
} finally {
setIsLoading(false);
}
}, [enabled, config, linkValue, itemIndex]);
// linkValue 변경 시 데이터 조회
useEffect(() => {
// 같은 값이면 스킵
if (prevLinkValueRef.current === linkValue) {
return;
}
prevLinkValueRef.current = linkValue;
// linkValue가 없으면 초기화
if (!linkValue) {
setData([]);
setSelectedItem(null);
setIsExpanded(false);
return;
}
fetchData();
}, [linkValue, fetchData]);
// 조건부 입력 활성화 여부 계산
const isInputEnabled = useCallback((): boolean => {
if (!config?.enabled || !selectedItem) {
return false;
}
const { requiredFields, requiredMode = "all" } = config.selection;
if (!requiredFields || requiredFields.length === 0) {
// 필수 필드가 없으면 선택만 하면 활성화
return true;
}
// 선택된 항목에서 필수 필드 값 확인
if (requiredMode === "any") {
// 하나라도 있으면 OK
return requiredFields.some((field) => {
const value = selectedItem[field];
return value !== undefined && value !== null && value !== "";
});
} else {
// 모두 있어야 OK
return requiredFields.every((field) => {
const value = selectedItem[field];
return value !== undefined && value !== null && value !== "";
});
}
}, [config, selectedItem]);
// 최대값 계산
const getMaxValue = useCallback((): number | null => {
if (!config?.enabled || !selectedItem) {
return null;
}
const { maxValueField } = config.conditionalInput;
if (!maxValueField) {
return null;
}
const maxValue = selectedItem[maxValueField];
return typeof maxValue === "number" ? maxValue : parseFloat(maxValue) || null;
}, [config, selectedItem]);
// 선택 요약 텍스트 생성
const getSelectionSummary = useCallback((): string => {
if (!selectedItem) {
return "선택 안됨";
}
const { displayColumns, columnLabels } = config.lookup;
const parts: string[] = [];
displayColumns.forEach((col) => {
const value = selectedItem[col];
if (value !== undefined && value !== null && value !== "") {
const label = columnLabels?.[col] || col;
parts.push(`${label}: ${value}`);
}
});
return parts.length > 0 ? parts.join(", ") : "선택됨";
}, [selectedItem, config?.lookup]);
return {
data,
isLoading,
error,
selectedItem,
setSelectedItem,
isInputEnabled: isInputEnabled(),
maxValue: getMaxValue(),
isExpanded,
setIsExpanded,
refetch: fetchData,
getSelectionSummary,
};
}