카테고리값 자동감지

This commit is contained in:
kjs
2026-01-12 16:08:02 +09:00
parent 9cc5bbbf05
commit 87189c792e
7 changed files with 365 additions and 135 deletions

View File

@@ -398,6 +398,9 @@ export function TableSectionRenderer({
// 소스 테이블의 컬럼 라벨 (API에서 동적 로드)
const [sourceColumnLabels, setSourceColumnLabels] = useState<Record<string, string>>({});
// 카테고리 타입 컬럼의 옵션 (column.type === "category")
const [categoryOptionsMap, setCategoryOptionsMap] = useState<Record<string, { value: string; label: string }[]>>({});
// 외부 데이터(groupedData) 처리: 데이터 전달 모달열기 액션으로 전달받은 데이터를 초기 테이블 데이터로 설정
useEffect(() => {
// 외부 데이터 소스가 활성화되지 않았거나, groupedData가 없으면 스킵
@@ -511,6 +514,46 @@ export function TableSectionRenderer({
loadColumnLabels();
}, [tableConfig.source.tableName, tableConfig.source.columnLabels]);
// 카테고리 타입 컬럼의 옵션 로드
useEffect(() => {
const loadCategoryOptions = async () => {
const sourceTableName = tableConfig.source.tableName;
if (!sourceTableName) return;
if (!tableConfig.columns) return;
// 카테고리 타입인 컬럼만 필터링
const categoryColumns = tableConfig.columns.filter((col) => col.type === "category");
if (categoryColumns.length === 0) return;
const newOptionsMap: Record<string, { value: string; label: string }[]> = {};
for (const col of categoryColumns) {
// 소스 필드 또는 필드명으로 카테고리 값 조회
const actualColumnName = col.sourceField || col.field;
if (!actualColumnName) continue;
try {
const { getCategoryValues } = await import("@/lib/api/tableCategoryValue");
const result = await getCategoryValues(sourceTableName, actualColumnName, false);
if (result && result.success && Array.isArray(result.data)) {
const options = result.data.map((item: any) => ({
value: item.valueCode || item.value_code || item.value || "",
label: item.valueLabel || item.displayLabel || item.display_label || item.label || item.valueCode || item.value_code || item.value || "",
}));
newOptionsMap[col.field] = options;
}
} catch (error) {
console.error(`카테고리 옵션 로드 실패 (${col.field}):`, error);
}
}
setCategoryOptionsMap((prev) => ({ ...prev, ...newOptionsMap }));
};
loadCategoryOptions();
}, [tableConfig.source.tableName, tableConfig.columns]);
// 조건부 테이블: 동적 옵션 로드 (optionSource 설정이 있는 경우)
useEffect(() => {
if (!isConditionalMode) return;
@@ -952,9 +995,15 @@ export function TableSectionRenderer({
baseColumn.selectOptions = dynamicSelectOptionsMap[col.field];
}
// 카테고리 타입인 경우 옵션 적용 및 select 타입으로 변환
if (col.type === "category" && categoryOptionsMap[col.field]) {
baseColumn.type = "select"; // RepeaterTable에서 select로 렌더링
baseColumn.selectOptions = categoryOptionsMap[col.field];
}
return baseColumn;
});
}, [tableConfig.columns, dynamicSelectOptionsMap]);
}, [tableConfig.columns, dynamicSelectOptionsMap, categoryOptionsMap]);
// 원본 계산 규칙 (조건부 계산 포함)
const originalCalculationRules: TableCalculationRule[] = useMemo(

View File

@@ -308,12 +308,29 @@ export function UniversalFormModalConfigPanel({
column_comment?: string;
inputType?: string;
input_type?: string;
}) => ({
name: c.columnName || c.column_name || "",
type: c.dataType || c.data_type || "text",
label: c.displayName || c.columnComment || c.column_comment || c.columnName || c.column_name || "",
inputType: c.inputType || c.input_type || "text",
}),
isNullable?: string;
is_nullable?: string;
}) => {
const colName = c.columnName || c.column_name || "";
const dataType = c.dataType || c.data_type || "text";
const inputType = c.inputType || c.input_type || "text";
const displayName = c.displayName || c.columnComment || c.column_comment || colName;
const isNullable = c.isNullable || c.is_nullable || "YES";
return {
// camelCase (기존 호환성)
name: colName,
type: dataType,
label: displayName,
inputType: inputType,
// snake_case (TableSectionSettingsModal 호환성)
column_name: colName,
data_type: dataType,
is_nullable: isNullable,
comment: displayName,
input_type: inputType,
};
},
),
}));
}

View File

@@ -48,12 +48,12 @@ interface TableColumnSettingsModalProps {
onOpenChange: (open: boolean) => void;
column: TableColumnConfig;
sourceTableName: string; // 소스 테이블명
sourceTableColumns: { column_name: string; data_type: string; comment?: string }[];
sourceTableColumns: { column_name: string; data_type: string; comment?: string; input_type?: string }[];
formFields: { columnName: string; label: string; sectionId?: string; sectionTitle?: string }[]; // formData 필드 목록 (섹션 정보 포함)
sections: { id: string; title: string }[]; // 섹션 목록
onSave: (updatedColumn: TableColumnConfig) => void;
tables: { table_name: string; comment?: string }[];
tableColumns: Record<string, { column_name: string; data_type: string; is_nullable: string; comment?: string }[]>;
tableColumns: Record<string, { column_name: string; data_type: string; is_nullable: string; comment?: string; input_type?: string }[]>;
onLoadTableColumns: (tableName: string) => void;
}
@@ -103,6 +103,18 @@ export function TableColumnSettingsModal({
return tableColumns[externalTableName] || [];
}, [tableColumns, externalTableName]);
// 소스 필드 기준으로 카테고리 타입인지 확인
const actualSourceField = localColumn.sourceField || localColumn.field;
const sourceColumnInfo = sourceTableColumns.find((c) => c.column_name === actualSourceField);
const isCategoryColumn = sourceColumnInfo?.input_type === "category";
// 카테고리 컬럼인 경우 타입을 자동으로 category로 설정
useEffect(() => {
if (isCategoryColumn && localColumn.type !== "category") {
updateColumn({ type: "category" });
}
}, [isCategoryColumn, localColumn.type]);
// 컬럼 업데이트 함수
const updateColumn = (updates: Partial<TableColumnConfig>) => {
setLocalColumn((prev) => ({ ...prev, ...updates }));
@@ -574,10 +586,11 @@ export function TableColumnSettingsModal({
<div>
<Label className="text-xs"></Label>
<Select
value={localColumn.type}
value={isCategoryColumn ? "category" : localColumn.type}
onValueChange={(value: any) => updateColumn({ type: value })}
disabled={isCategoryColumn}
>
<SelectTrigger className="h-8 text-xs mt-1">
<SelectTrigger className={cn("h-8 text-xs mt-1", isCategoryColumn && "opacity-70 cursor-not-allowed")}>
<SelectValue />
</SelectTrigger>
<SelectContent>
@@ -588,6 +601,9 @@ export function TableColumnSettingsModal({
))}
</SelectContent>
</Select>
{isCategoryColumn && (
<p className="text-[10px] text-blue-600 mt-0.5"> </p>
)}
</div>
<div>
<Label className="text-xs"></Label>

View File

@@ -706,15 +706,15 @@ interface ColumnSettingItemProps {
col: TableColumnConfig;
index: number;
totalCount: number;
saveTableColumns: { column_name: string; data_type: string; is_nullable: string; comment?: string }[];
saveTableColumns: { column_name: string; data_type: string; is_nullable: string; comment?: string; input_type?: string }[];
displayColumns: string[]; // 검색 설정에서 선택한 표시 컬럼 목록
sourceTableColumns: { column_name: string; data_type: string; is_nullable: string; comment?: string }[]; // 소스 테이블 컬럼
sourceTableColumns: { column_name: string; data_type: string; is_nullable: string; comment?: string; input_type?: string }[]; // 소스 테이블 컬럼
sourceTableName: string; // 소스 테이블명
externalTableColumns: { column_name: string; data_type: string; is_nullable: string; comment?: string }[]; // 외부 데이터 테이블 컬럼
externalTableColumns: { column_name: string; data_type: string; is_nullable: string; comment?: string; input_type?: string }[]; // 외부 데이터 테이블 컬럼
externalTableName?: string; // 외부 데이터 테이블명
externalDataEnabled?: boolean; // 외부 데이터 소스 활성화 여부
tables: { table_name: string; comment?: string }[]; // 전체 테이블 목록
tableColumns: Record<string, { column_name: string; data_type: string; is_nullable: string; comment?: string }[]>; // 테이블별 컬럼
tableColumns: Record<string, { column_name: string; data_type: string; is_nullable: string; comment?: string; input_type?: string }[]>; // 테이블별 컬럼
sections: { id: string; title: string }[]; // 섹션 목록
formFields: { columnName: string; label: string; sectionId?: string }[]; // 폼 필드 목록
tableConfig: TableSectionConfig; // 현재 행 필드 목록 표시용
@@ -755,6 +755,18 @@ function ColumnSettingItem({
const [parentFieldSearchOpen, setParentFieldSearchOpen] = useState(false);
const [lookupTableOpenMap, setLookupTableOpenMap] = useState<Record<string, boolean>>({});
// 소스 필드 기준으로 카테고리 타입인지 확인
const actualSourceField = col.sourceField || col.field;
const sourceColumnInfo = sourceTableColumns.find((c) => c.column_name === actualSourceField);
const isCategoryColumn = sourceColumnInfo?.input_type === "category";
// 카테고리 컬럼인 경우 타입을 자동으로 category로 설정
useEffect(() => {
if (isCategoryColumn && col.type !== "category") {
onUpdate({ type: "category" });
}
}, [isCategoryColumn, col.type, onUpdate]);
// 조회 옵션 추가
const addLookupOption = () => {
const newOption: LookupOption = {
@@ -1117,8 +1129,12 @@ function ColumnSettingItem({
{/* 타입 */}
<div>
<Label className="text-xs"></Label>
<Select value={col.type} onValueChange={(value: any) => onUpdate({ type: value })}>
<SelectTrigger className="h-8 text-xs mt-1">
<Select
value={isCategoryColumn ? "category" : col.type}
onValueChange={(value: any) => onUpdate({ type: value })}
disabled={isCategoryColumn}
>
<SelectTrigger className={cn("h-8 text-xs mt-1", isCategoryColumn && "opacity-70 cursor-not-allowed")}>
<SelectValue />
</SelectTrigger>
<SelectContent>
@@ -1129,6 +1145,9 @@ function ColumnSettingItem({
))}
</SelectContent>
</Select>
{isCategoryColumn && (
<p className="text-[10px] text-blue-600 mt-0.5"> </p>
)}
</div>
{/* 너비 */}

View File

@@ -899,6 +899,7 @@ export const TABLE_COLUMN_TYPE_OPTIONS = [
{ value: "number", label: "숫자" },
{ value: "date", label: "날짜" },
{ value: "select", label: "선택(드롭다운)" },
{ value: "category", label: "카테고리" },
] as const;
// 값 매핑 타입 옵션