Merge branch 'feature/v2-renewal' of http://39.117.244.52:3000/kjs/ERP-node into jskim-node
This commit is contained in:
parent
eb27f01616
commit
efc4768ed7
|
|
@ -237,6 +237,8 @@ export const V2PropertiesPanel: React.FC<V2PropertiesPanelProps> = ({
|
||||||
const extraProps: Record<string, any> = {};
|
const extraProps: Record<string, any> = {};
|
||||||
if (componentId === "v2-select") {
|
if (componentId === "v2-select") {
|
||||||
extraProps.inputType = inputType;
|
extraProps.inputType = inputType;
|
||||||
|
extraProps.tableName = selectedComponent.tableName || currentTable?.tableName || currentTableName;
|
||||||
|
extraProps.columnName = selectedComponent.columnName || currentConfig.fieldKey || currentConfig.columnName;
|
||||||
}
|
}
|
||||||
if (componentId === "v2-list") {
|
if (componentId === "v2-list") {
|
||||||
extraProps.currentTableName = currentTableName;
|
extraProps.currentTableName = currentTableName;
|
||||||
|
|
|
||||||
|
|
@ -20,41 +20,111 @@ interface ColumnOption {
|
||||||
columnLabel: string;
|
columnLabel: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CategoryValueOption {
|
||||||
|
valueCode: string;
|
||||||
|
valueLabel: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface V2SelectConfigPanelProps {
|
interface V2SelectConfigPanelProps {
|
||||||
config: Record<string, any>;
|
config: Record<string, any>;
|
||||||
onChange: (config: Record<string, any>) => void;
|
onChange: (config: Record<string, any>) => void;
|
||||||
/** 컬럼의 inputType (entity 타입인 경우에만 엔티티 소스 표시) */
|
/** 컬럼의 inputType (entity/category 타입 확인용) */
|
||||||
inputType?: string;
|
inputType?: string;
|
||||||
|
/** 현재 테이블명 (카테고리 값 조회용) */
|
||||||
|
tableName?: string;
|
||||||
|
/** 현재 컬럼명 (카테고리 값 조회용) */
|
||||||
|
columnName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({ config, onChange, inputType }) => {
|
export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({
|
||||||
// 엔티티 타입인지 확인
|
config,
|
||||||
|
onChange,
|
||||||
|
inputType,
|
||||||
|
tableName,
|
||||||
|
columnName,
|
||||||
|
}) => {
|
||||||
const isEntityType = inputType === "entity";
|
const isEntityType = inputType === "entity";
|
||||||
// 엔티티 테이블의 컬럼 목록
|
const isCategoryType = inputType === "category";
|
||||||
|
|
||||||
const [entityColumns, setEntityColumns] = useState<ColumnOption[]>([]);
|
const [entityColumns, setEntityColumns] = useState<ColumnOption[]>([]);
|
||||||
const [loadingColumns, setLoadingColumns] = useState(false);
|
const [loadingColumns, setLoadingColumns] = useState(false);
|
||||||
|
|
||||||
// 설정 업데이트 핸들러
|
// 카테고리 값 목록
|
||||||
|
const [categoryValues, setCategoryValues] = useState<CategoryValueOption[]>([]);
|
||||||
|
const [loadingCategoryValues, setLoadingCategoryValues] = useState(false);
|
||||||
|
|
||||||
const updateConfig = (field: string, value: any) => {
|
const updateConfig = (field: string, value: any) => {
|
||||||
onChange({ ...config, [field]: value });
|
onChange({ ...config, [field]: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 카테고리 타입이면 source를 자동으로 category로 설정
|
||||||
|
useEffect(() => {
|
||||||
|
if (isCategoryType && config.source !== "category") {
|
||||||
|
onChange({ ...config, source: "category" });
|
||||||
|
}
|
||||||
|
}, [isCategoryType]);
|
||||||
|
|
||||||
|
// 카테고리 값 로드
|
||||||
|
const loadCategoryValues = useCallback(async (catTable: string, catColumn: string) => {
|
||||||
|
if (!catTable || !catColumn) {
|
||||||
|
setCategoryValues([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoadingCategoryValues(true);
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get(`/table-categories/${catTable}/${catColumn}/values`);
|
||||||
|
const data = response.data;
|
||||||
|
if (data.success && data.data) {
|
||||||
|
const flattenTree = (items: any[], depth: number = 0): CategoryValueOption[] => {
|
||||||
|
const result: CategoryValueOption[] = [];
|
||||||
|
for (const item of items) {
|
||||||
|
result.push({
|
||||||
|
valueCode: item.valueCode,
|
||||||
|
valueLabel: depth > 0 ? `${" ".repeat(depth)}${item.valueLabel}` : item.valueLabel,
|
||||||
|
});
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
result.push(...flattenTree(item.children, depth + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
setCategoryValues(flattenTree(data.data));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("카테고리 값 조회 실패:", error);
|
||||||
|
setCategoryValues([]);
|
||||||
|
} finally {
|
||||||
|
setLoadingCategoryValues(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 카테고리 소스일 때 값 로드
|
||||||
|
useEffect(() => {
|
||||||
|
if (config.source === "category") {
|
||||||
|
const catTable = config.categoryTable || tableName;
|
||||||
|
const catColumn = config.categoryColumn || columnName;
|
||||||
|
if (catTable && catColumn) {
|
||||||
|
loadCategoryValues(catTable, catColumn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [config.source, config.categoryTable, config.categoryColumn, tableName, columnName, loadCategoryValues]);
|
||||||
|
|
||||||
// 엔티티 테이블 변경 시 컬럼 목록 조회
|
// 엔티티 테이블 변경 시 컬럼 목록 조회
|
||||||
const loadEntityColumns = useCallback(async (tableName: string) => {
|
const loadEntityColumns = useCallback(async (tblName: string) => {
|
||||||
if (!tableName) {
|
if (!tblName) {
|
||||||
setEntityColumns([]);
|
setEntityColumns([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoadingColumns(true);
|
setLoadingColumns(true);
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.get(`/table-management/tables/${tableName}/columns?size=500`);
|
const response = await apiClient.get(`/table-management/tables/${tblName}/columns?size=500`);
|
||||||
const data = response.data.data || response.data;
|
const data = response.data.data || response.data;
|
||||||
const columns = data.columns || data || [];
|
const columns = data.columns || data || [];
|
||||||
|
|
||||||
const columnOptions: ColumnOption[] = columns.map((col: any) => {
|
const columnOptions: ColumnOption[] = columns.map((col: any) => {
|
||||||
const name = col.columnName || col.column_name || col.name;
|
const name = col.columnName || col.column_name || col.name;
|
||||||
// displayName 우선 사용
|
|
||||||
const label = col.displayName || col.display_name || col.columnLabel || col.column_label || name;
|
const label = col.displayName || col.display_name || col.columnLabel || col.column_label || name;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -72,7 +142,6 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({ config
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 엔티티 테이블이 변경되면 컬럼 목록 로드
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (config.source === "entity" && config.entityTable) {
|
if (config.source === "entity" && config.entityTable) {
|
||||||
loadEntityColumns(config.entityTable);
|
loadEntityColumns(config.entityTable);
|
||||||
|
|
@ -98,6 +167,9 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({ config
|
||||||
updateConfig("options", newOptions);
|
updateConfig("options", newOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 현재 source 결정 (카테고리 타입이면 강제 category)
|
||||||
|
const effectiveSource = isCategoryType ? "category" : config.source || "static";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* 선택 모드 */}
|
{/* 선택 모드 */}
|
||||||
|
|
@ -125,21 +197,102 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({ config
|
||||||
{/* 데이터 소스 */}
|
{/* 데이터 소스 */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-xs font-medium">데이터 소스</Label>
|
<Label className="text-xs font-medium">데이터 소스</Label>
|
||||||
<Select value={config.source || "static"} onValueChange={(value) => updateConfig("source", value)}>
|
{isCategoryType ? (
|
||||||
<SelectTrigger className="h-8 text-xs">
|
<div className="bg-muted flex h-8 items-center rounded-md px-3">
|
||||||
<SelectValue placeholder="소스 선택" />
|
<span className="text-xs font-medium text-emerald-600">카테고리 (자동 설정)</span>
|
||||||
</SelectTrigger>
|
</div>
|
||||||
<SelectContent>
|
) : (
|
||||||
<SelectItem value="static">정적 옵션</SelectItem>
|
<Select value={config.source || "static"} onValueChange={(value) => updateConfig("source", value)}>
|
||||||
<SelectItem value="code">공통 코드</SelectItem>
|
<SelectTrigger className="h-8 text-xs">
|
||||||
{/* 엔티티 타입일 때만 엔티티 옵션 표시 */}
|
<SelectValue placeholder="소스 선택" />
|
||||||
{isEntityType && <SelectItem value="entity">엔티티</SelectItem>}
|
</SelectTrigger>
|
||||||
</SelectContent>
|
<SelectContent>
|
||||||
</Select>
|
<SelectItem value="static">정적 옵션</SelectItem>
|
||||||
|
<SelectItem value="code">공통 코드</SelectItem>
|
||||||
|
<SelectItem value="category">카테고리</SelectItem>
|
||||||
|
{isEntityType && <SelectItem value="entity">엔티티</SelectItem>}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 카테고리 설정 */}
|
||||||
|
{effectiveSource === "category" && (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label className="text-xs font-medium">카테고리 정보</Label>
|
||||||
|
<div className="bg-muted rounded-md p-2">
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div>
|
||||||
|
<p className="text-muted-foreground text-[10px]">테이블</p>
|
||||||
|
<p className="text-xs font-medium">{config.categoryTable || tableName || "-"}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-muted-foreground text-[10px]">컬럼</p>
|
||||||
|
<p className="text-xs font-medium">{config.categoryColumn || columnName || "-"}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 카테고리 값 로딩 중 */}
|
||||||
|
{loadingCategoryValues && (
|
||||||
|
<div className="text-muted-foreground flex items-center gap-2 text-xs">
|
||||||
|
<Loader2 className="h-3 w-3 animate-spin" />
|
||||||
|
카테고리 값 로딩 중...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 카테고리 값 목록 표시 */}
|
||||||
|
{categoryValues.length > 0 && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="text-xs font-medium">카테고리 값 ({categoryValues.length}개)</Label>
|
||||||
|
<div className="bg-muted max-h-32 space-y-0.5 overflow-y-auto rounded-md p-1.5">
|
||||||
|
{categoryValues.map((cv) => (
|
||||||
|
<div key={cv.valueCode} className="flex items-center gap-2 px-1.5 py-0.5">
|
||||||
|
<span className="text-muted-foreground shrink-0 font-mono text-[10px]">{cv.valueCode}</span>
|
||||||
|
<span className="truncate text-xs">{cv.valueLabel}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 기본값 설정 */}
|
||||||
|
{categoryValues.length > 0 && (
|
||||||
|
<div className="border-t pt-2">
|
||||||
|
<Label className="text-xs font-medium">기본값</Label>
|
||||||
|
<Select
|
||||||
|
value={config.defaultValue || "_none_"}
|
||||||
|
onValueChange={(value) => updateConfig("defaultValue", value === "_none_" ? "" : value)}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="mt-1 h-8 text-xs">
|
||||||
|
<SelectValue placeholder="기본값 선택" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="_none_">선택 안함</SelectItem>
|
||||||
|
{categoryValues.map((cv) => (
|
||||||
|
<SelectItem key={cv.valueCode} value={cv.valueCode}>
|
||||||
|
{cv.valueLabel}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<p className="text-muted-foreground mt-1 text-[10px]">화면 로드 시 자동 선택될 카테고리 값</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 카테고리 값 없음 안내 */}
|
||||||
|
{!loadingCategoryValues && categoryValues.length === 0 && (
|
||||||
|
<p className="text-[10px] text-amber-600">
|
||||||
|
카테고리 값이 없습니다. 테이블 카테고리 관리에서 값을 추가해주세요.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 정적 옵션 관리 */}
|
{/* 정적 옵션 관리 */}
|
||||||
{(config.source || "static") === "static" && (
|
{effectiveSource === "static" && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Label className="text-xs font-medium">옵션 목록</Label>
|
<Label className="text-xs font-medium">옵션 목록</Label>
|
||||||
|
|
@ -199,8 +352,8 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({ config
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 공통 코드 설정 - 테이블 타입 관리에서 설정되므로 정보만 표시 */}
|
{/* 공통 코드 설정 */}
|
||||||
{config.source === "code" && (
|
{effectiveSource === "code" && (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label className="text-xs font-medium">코드 그룹</Label>
|
<Label className="text-xs font-medium">코드 그룹</Label>
|
||||||
{config.codeGroup ? (
|
{config.codeGroup ? (
|
||||||
|
|
@ -212,7 +365,7 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({ config
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 엔티티(참조 테이블) 설정 */}
|
{/* 엔티티(참조 테이블) 설정 */}
|
||||||
{config.source === "entity" && (
|
{effectiveSource === "entity" && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-xs font-medium">참조 테이블</Label>
|
<Label className="text-xs font-medium">참조 테이블</Label>
|
||||||
|
|
@ -228,7 +381,6 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({ config
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 컬럼 로딩 중 표시 */}
|
|
||||||
{loadingColumns && (
|
{loadingColumns && (
|
||||||
<div className="text-muted-foreground flex items-center gap-2 text-xs">
|
<div className="text-muted-foreground flex items-center gap-2 text-xs">
|
||||||
<Loader2 className="h-3 w-3 animate-spin" />
|
<Loader2 className="h-3 w-3 animate-spin" />
|
||||||
|
|
@ -236,7 +388,6 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({ config
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 컬럼 선택 - 테이블이 설정되어 있고 컬럼 목록이 있는 경우 Select로 표시 */}
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-xs font-medium">값 컬럼 (코드)</Label>
|
<Label className="text-xs font-medium">값 컬럼 (코드)</Label>
|
||||||
|
|
@ -296,18 +447,17 @@ export const V2SelectConfigPanel: React.FC<V2SelectConfigPanelProps> = ({ config
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 컬럼이 없는 경우 안내 */}
|
|
||||||
{config.entityTable && !loadingColumns && entityColumns.length === 0 && (
|
{config.entityTable && !loadingColumns && entityColumns.length === 0 && (
|
||||||
<p className="text-[10px] text-amber-600">
|
<p className="text-[10px] text-amber-600">
|
||||||
테이블 컬럼을 조회할 수 없습니다. 테이블 타입 관리에서 참조 테이블을 설정해주세요.
|
테이블 컬럼을 조회할 수 없습니다. 테이블 타입 관리에서 참조 테이블을 설정해주세요.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 자동 채움 안내 */}
|
|
||||||
{config.entityTable && entityColumns.length > 0 && (
|
{config.entityTable && entityColumns.length > 0 && (
|
||||||
<div className="border-t pt-3">
|
<div className="border-t pt-3">
|
||||||
<p className="text-muted-foreground text-[10px]">
|
<p className="text-muted-foreground text-[10px]">
|
||||||
같은 폼에 참조 테이블({config.entityTable})의 컬럼이 배치되어 있으면, 엔티티 선택 시 해당 필드가 자동으로 채워집니다.
|
같은 폼에 참조 테이블({config.entityTable})의 컬럼이 배치되어 있으면, 엔티티 선택 시 해당 필드가 자동으로
|
||||||
|
채워집니다.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue