diff --git a/backend-spring/src/main/java/com/erp/constants/InputTypeConstants.java b/backend-spring/src/main/java/com/erp/constants/InputTypeConstants.java new file mode 100644 index 00000000..59955af4 --- /dev/null +++ b/backend-spring/src/main/java/com/erp/constants/InputTypeConstants.java @@ -0,0 +1,13 @@ +package com.erp.constants; + +import java.util.Set; + +public final class InputTypeConstants { + private InputTypeConstants() {} + + /** 사용자가 직접 선택 가능한 INPUT_TYPE 8종 (INSERT/UPDATE-type 검증용) */ + public static final Set USER_SELECTABLE_INPUT_TYPES = Set.of( + "text", "number", "date", "code", "entity", + "numbering", "file", "image" + ); +} diff --git a/backend-spring/src/main/java/com/erp/constants/InputTypeContext.java b/backend-spring/src/main/java/com/erp/constants/InputTypeContext.java new file mode 100644 index 00000000..3eeb66df --- /dev/null +++ b/backend-spring/src/main/java/com/erp/constants/InputTypeContext.java @@ -0,0 +1,8 @@ +package com.erp.constants; + +public enum InputTypeContext { + USER_INSERT, + USER_UPDATE_TYPE, + USER_UPDATE_OTHER, + SYSTEM_NORMALIZE +} diff --git a/backend-spring/src/main/java/com/erp/controller/TableManagementController.java b/backend-spring/src/main/java/com/erp/controller/TableManagementController.java index 76346b8a..475a1044 100644 --- a/backend-spring/src/main/java/com/erp/controller/TableManagementController.java +++ b/backend-spring/src/main/java/com/erp/controller/TableManagementController.java @@ -75,7 +75,11 @@ public class TableManagementController { @PutMapping("/tables/{tableName}/label") public ResponseEntity> updateTableLabel( @PathVariable String tableName, - @RequestBody Map body) { + @RequestBody Map body, + @RequestAttribute("role") String role) { + if (!isAdmin(role)) { + return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다.")); + } String displayName = (String) body.get("display_name"); String description = (String) body.get("description"); if (displayName == null || displayName.isBlank()) { @@ -105,7 +109,11 @@ public class TableManagementController { @PathVariable String tableName, @PathVariable String columnName, @RequestBody Map settings, + @RequestAttribute("role") String role, @RequestAttribute("company_code") String companyCode) { + if (!isAdmin(role)) { + return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다.")); + } return doUpdateColumnSettings(tableName, columnName, settings, companyCode); } @@ -115,7 +123,11 @@ public class TableManagementController { @PathVariable String tableName, @PathVariable String columnName, @RequestBody Map settings, + @RequestAttribute("role") String role, @RequestAttribute("company_code") String companyCode) { + if (!isAdmin(role)) { + return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다.")); + } return doUpdateColumnSettings(tableName, columnName, settings, companyCode); } @@ -136,7 +148,11 @@ public class TableManagementController { public ResponseEntity> updateAllColumnSettingsPost( @PathVariable String tableName, @RequestBody List> columnSettings, + @RequestAttribute("role") String role, @RequestAttribute("company_code") String companyCode) { + if (!isAdmin(role)) { + return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다.")); + } return doUpdateAllColumnSettings(tableName, columnSettings, companyCode); } @@ -145,7 +161,11 @@ public class TableManagementController { public ResponseEntity> updateAllColumnSettingsBatch( @PathVariable String tableName, @RequestBody List> columnSettings, + @RequestAttribute("role") String role, @RequestAttribute("company_code") String companyCode) { + if (!isAdmin(role)) { + return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다.")); + } return doUpdateAllColumnSettings(tableName, columnSettings, companyCode); } @@ -166,7 +186,11 @@ public class TableManagementController { public ResponseEntity> updateColumnWebType( @PathVariable String tableName, @PathVariable String columnName, - @RequestBody Map body) { + @RequestBody Map body, + @RequestAttribute("role") String role) { + if (!isAdmin(role)) { + return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다.")); + } String webType = (String) body.get("web_type"); if (webType == null || webType.isBlank()) { return ResponseEntity.status(400).body(ApiResponse.error("웹 타입이 필요합니다.")); @@ -183,7 +207,11 @@ public class TableManagementController { @PathVariable String tableName, @PathVariable String columnName, @RequestBody Map body, + @RequestAttribute("role") String role, @RequestAttribute("company_code") String companyCode) { + if (!isAdmin(role)) { + return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다.")); + } String inputType = (String) body.get("input_type"); if (tableName == null || columnName == null || inputType == null || inputType.isBlank()) { return ResponseEntity.status(400).body(ApiResponse.error("테이블명, 컬럼명, 입력 타입이 모두 필요합니다.")); @@ -241,7 +269,11 @@ public class TableManagementController { @PutMapping("/tables/{tableName}/primary-key") public ResponseEntity> setTablePrimaryKey( @PathVariable String tableName, - @RequestBody Map body) { + @RequestBody Map body, + @RequestAttribute("role") String role) { + if (!isSuperAdmin(role)) { + return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다.")); + } @SuppressWarnings("unchecked") List columns = (List) body.get("columns"); if (tableName == null || columns == null || columns.isEmpty()) { @@ -256,7 +288,11 @@ public class TableManagementController { @PostMapping("/tables/{tableName}/indexes") public ResponseEntity> toggleTableIndex( @PathVariable String tableName, - @RequestBody Map body) { + @RequestBody Map body, + @RequestAttribute("role") String role) { + if (!isSuperAdmin(role)) { + return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다.")); + } String columnName = (String) body.get("column_name"); String indexType = (String) body.get("index_type"); String action = (String) body.get("action"); @@ -281,7 +317,11 @@ public class TableManagementController { @PathVariable String tableName, @PathVariable String columnName, @RequestBody Map body, + @RequestAttribute("role") String role, @RequestAttribute("company_code") String companyCode) { + if (!isSuperAdmin(role)) { + return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다.")); + } Object nullableObj = body.get("nullable"); if (tableName == null || columnName == null || !(nullableObj instanceof Boolean)) { return ResponseEntity.status(400).body(ApiResponse.error("tableName, columnName, nullable(boolean)이 필요합니다.")); @@ -299,7 +339,11 @@ public class TableManagementController { @PathVariable String tableName, @PathVariable String columnName, @RequestBody Map body, + @RequestAttribute("role") String role, @RequestAttribute("company_code") String companyCode) { + if (!isSuperAdmin(role)) { + return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다.")); + } Object uniqueObj = body.get("unique"); if (tableName == null || columnName == null || !(uniqueObj instanceof Boolean)) { return ResponseEntity.status(400).body(ApiResponse.error("tableName, columnName, unique(boolean)이 필요합니다.")); @@ -366,7 +410,11 @@ public class TableManagementController { public ResponseEntity>> addTableData( @PathVariable String tableName, @RequestBody Map data, + @RequestAttribute("role") String role, @RequestAttribute("company_code") String companyCode) { + if (!isAdmin(role)) { + return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다.")); + } if (data == null || data.isEmpty()) { return ResponseEntity.status(400).body(ApiResponse.error("추가할 데이터가 필요합니다.")); } @@ -399,7 +447,11 @@ public class TableManagementController { public ResponseEntity> editTableData( @PathVariable String tableName, @RequestBody Map body, + @RequestAttribute("role") String role, @RequestAttribute("company_code") String companyCode) { + if (!isAdmin(role)) { + return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다.")); + } @SuppressWarnings("unchecked") Map originalData = (Map) body.get("original_data"); @SuppressWarnings("unchecked") @@ -433,7 +485,11 @@ public class TableManagementController { @DeleteMapping("/tables/{tableName}/delete") public ResponseEntity> deleteTableData( @PathVariable String tableName, - @RequestBody Object body) { + @RequestBody Object body, + @RequestAttribute("role") String role) { + if (!isAdmin(role)) { + return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다.")); + } List> dataList; if (body instanceof List) { @SuppressWarnings("unchecked") @@ -457,7 +513,11 @@ public class TableManagementController { @PostMapping("/tables/{tableName}/log") public ResponseEntity> createLogTable( @PathVariable String tableName, - @RequestBody Map body) { + @RequestBody Map body, + @RequestAttribute("role") String role) { + if (!isSuperAdmin(role)) { + return ResponseEntity.status(403).body(ApiResponse.error("최고 관리자 권한이 필요합니다.")); + } @SuppressWarnings("unchecked") List logColumns = (List) body.get("log_columns"); boolean isActive = Boolean.TRUE.equals(body.get("is_active")); @@ -487,7 +547,11 @@ public class TableManagementController { @PostMapping("/tables/{tableName}/log/toggle") public ResponseEntity> toggleLogTable( @PathVariable String tableName, - @RequestBody Map body) { + @RequestBody Map body, + @RequestAttribute("role") String role) { + if (!isAdmin(role)) { + return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다.")); + } boolean isActive = Boolean.TRUE.equals(body.get("is_active")); tableManagementService.toggleLogTable(tableName, isActive); return ResponseEntity.ok(ApiResponse.success(null, @@ -544,7 +608,11 @@ public class TableManagementController { @PostMapping("/multi-table-save") public ResponseEntity>> multiTableSave( @RequestBody Map payload, + @RequestAttribute("role") String role, @RequestAttribute("company_code") String companyCode) { + if (!isAdmin(role)) { + return ResponseEntity.status(403).body(ApiResponse.error("관리자 권한이 필요합니다.")); + } return ResponseEntity.ok(ApiResponse.success( tableManagementService.multiTableSave(payload, companyCode), "다중 테이블 저장이 완료되었습니다.")); @@ -575,4 +643,16 @@ public class TableManagementController { return ResponseEntity.ok(ApiResponse.success( tableManagementService.checkDatabaseConnection(), "데이터베이스 연결 상태를 확인했습니다.")); } + + // ────────────────────────────────────────────────────────── + // 권한 헬퍼 + // ────────────────────────────────────────────────────────── + + private boolean isAdmin(String role) { + return isSuperAdmin(role) || "COMPANY_ADMIN".equals(role); + } + + private boolean isSuperAdmin(String roleOrCode) { + return "*".equals(roleOrCode) || "SUPER_ADMIN".equals(roleOrCode); + } } diff --git a/backend-spring/src/main/java/com/erp/service/DdlService.java b/backend-spring/src/main/java/com/erp/service/DdlService.java index 543983c2..745b810e 100644 --- a/backend-spring/src/main/java/com/erp/service/DdlService.java +++ b/backend-spring/src/main/java/com/erp/service/DdlService.java @@ -1,6 +1,7 @@ package com.erp.service; import com.erp.common.BaseService; +import com.erp.constants.InputTypeConstants; import lombok.extern.slf4j.Slf4j; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; @@ -39,12 +40,6 @@ public class DdlService extends BaseService { "id", "created_date", "updated_date", "company_code" ); - /** 사용자가 신규 추가하는 컬럼에 허용되는 INPUT_TYPE 8종 (백엔드 백스톱) */ - private static final Set USER_SELECTABLE_INPUT_TYPES = Set.of( - "text", "number", "date", "code", "entity", - "numbering", "file", "image" - ); - public DdlService(JdbcTemplate jdbcTemplate, PlatformTransactionManager transactionManager) { this.jdbcTemplate = jdbcTemplate; this.transactionTemplate = new TransactionTemplate(transactionManager); @@ -146,9 +141,9 @@ public class DdlService extends BaseService { transactionTemplate.execute(status -> { jdbcTemplate.execute(ddlQuery); String inputType = convertToInputType(column); - if (!USER_SELECTABLE_INPUT_TYPES.contains(inputType)) { + if (!InputTypeConstants.USER_SELECTABLE_INPUT_TYPES.contains(inputType)) { throw new IllegalArgumentException( - "INPUT_TYPE 은 다음 8개 중 하나여야 합니다: " + USER_SELECTABLE_INPUT_TYPES + "INPUT_TYPE 은 다음 8개 중 하나여야 합니다: " + InputTypeConstants.USER_SELECTABLE_INPUT_TYPES + " (받은 값: " + inputType + ")" ); } @@ -421,9 +416,9 @@ public class DdlService extends BaseService { for (int i = 0; i < columns.size(); i++) { Map col = columns.get(i); String inputType = convertToInputType(col); - if (!USER_SELECTABLE_INPUT_TYPES.contains(inputType)) { + if (!InputTypeConstants.USER_SELECTABLE_INPUT_TYPES.contains(inputType)) { throw new IllegalArgumentException( - "INPUT_TYPE 은 다음 8개 중 하나여야 합니다: " + USER_SELECTABLE_INPUT_TYPES + "INPUT_TYPE 은 다음 8개 중 하나여야 합니다: " + InputTypeConstants.USER_SELECTABLE_INPUT_TYPES + " (받은 값: " + inputType + ")" ); } @@ -532,6 +527,9 @@ public class DdlService extends BaseService { case "radio" -> "radio"; case "code" -> "code"; case "entity" -> "entity"; + case "file" -> "file"; + case "image" -> "image"; + case "numbering" -> "numbering"; default -> "text"; }; } diff --git a/backend-spring/src/main/java/com/erp/service/TableManagementService.java b/backend-spring/src/main/java/com/erp/service/TableManagementService.java index 832e3d4a..af045a51 100644 --- a/backend-spring/src/main/java/com/erp/service/TableManagementService.java +++ b/backend-spring/src/main/java/com/erp/service/TableManagementService.java @@ -1,6 +1,8 @@ package com.erp.service; import com.erp.common.BaseService; +import com.erp.constants.InputTypeConstants; +import com.erp.constants.InputTypeContext; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -26,10 +28,14 @@ public class TableManagementService extends BaseService { private static final String NS = "tableManagement."; - /** 사용자가 직접 선택 가능한 INPUT_TYPE 8종 (INSERT/UPDATE-type 검증용) */ - private static final Set USER_SELECTABLE_INPUT_TYPES = Set.of( - "text", "number", "date", "code", "entity", - "numbering", "file", "image" + /** 로그 테이블 컬럼 정의에 허용하는 PostgreSQL data_type 화이트리스트. + * information_schema.columns.data_type 값과 정확히 일치해야 한다. */ + private static final Set ALLOWED_LOG_COLUMN_TYPES = Set.of( + "varchar", "text", "char", "character", "character varying", + "integer", "bigint", "smallint", "numeric", "decimal", "real", "double precision", + "boolean", "date", "timestamp", "timestamp without time zone", "timestamp with time zone", + "time", "time without time zone", "time with time zone", + "uuid", "json", "jsonb", "bytea" ); // ────────────────────────────────────────────────── @@ -151,9 +157,12 @@ public class TableManagementService extends BaseService { Map settings, String companyCode) { ensureTableInLabels(tableName); - boolean inputTypeChanged = settings.containsKey("input_type"); - String ctx = inputTypeChanged ? "user-update-type" : "user-update-other"; - String inputType = normalizeInputType((String) settings.get("input_type"), ctx); + Object rawInputType = settings.get("input_type"); + boolean inputTypeChanged = settings.containsKey("input_type") && rawInputType != null; + InputTypeContext ctx = inputTypeChanged + ? InputTypeContext.USER_UPDATE_TYPE + : InputTypeContext.USER_UPDATE_OTHER; + String inputType = normalizeInputType((String) rawInputType, ctx); Map params = new HashMap<>(); params.put("table_name", tableName); params.put("column_name", columnName); @@ -210,7 +219,7 @@ public class TableManagementService extends BaseService { public void updateColumnInputType(String tableName, String columnName, String inputType, String companyCode, Map detailSettings) { - String finalType = normalizeInputType(inputType, "user-update-type"); + String finalType = normalizeInputType(inputType, InputTypeContext.USER_UPDATE_TYPE); Map params = new HashMap<>(); params.put("table_name", tableName); params.put("column_name", columnName); @@ -611,9 +620,14 @@ public class TableManagementService extends BaseService { @Transactional public void createLogTable(String tableName, List logColumns, boolean isActive) { - String logTableName = tableName + "_log"; - String safeLog = sanitize(logTableName); String safeOrig = sanitize(tableName); + if (safeOrig.isBlank()) { + throw new IllegalArgumentException("유효하지 않은 테이블명입니다."); + } + String safeLog = sanitize(safeOrig + "_log"); + if (safeLog.isBlank()) { + throw new IllegalArgumentException("유효하지 않은 로그 테이블명입니다."); + } // 원본 테이블 컬럼 정보 조회 Map colTypes = getColumnTypes(safeOrig); @@ -625,13 +639,32 @@ public class TableManagementService extends BaseService { colDefs.add("log_date TIMESTAMP DEFAULT NOW()"); colDefs.add("log_user VARCHAR(100)"); - List targetCols = (logColumns != null && !logColumns.isEmpty()) - ? logColumns.stream().map(this::sanitize).filter(c -> !c.isBlank()).collect(Collectors.toList()) + List requestedCols = (logColumns != null && !logColumns.isEmpty()) + ? logColumns : new ArrayList<>(colTypes.keySet()); - for (String col : targetCols) { - String type = colTypes.getOrDefault(col, "TEXT"); - colDefs.add(String.format("\"%s\" %s", col, type)); + // 실제 SQL 에 들어간 컬럼만 메타에 저장 (skip 된 것은 log_columns 설정에서도 빠짐) + List persistedCols = new ArrayList<>(); + for (String col : requestedCols) { + if (col == null) continue; + String safeCol = sanitize(col); + if (safeCol.isBlank()) continue; // sanitize 결과 빈 식별자 차단 + if (!colTypes.containsKey(col)) continue; // 원본 테이블에 없는 컬럼 skip + + String rawType = colTypes.get(col); + String normalized = (rawType == null ? "" : rawType.toLowerCase(Locale.ROOT).trim()); + if (!ALLOWED_LOG_COLUMN_TYPES.contains(normalized)) { + // 알 수 없는 type 은 text 로 fallback (안전 default) + log.warn("로그 테이블 컬럼 타입 화이트리스트 미일치 → text 로 대체: table={}, col={}, type={}", + safeOrig, safeCol, rawType); + normalized = "text"; + } + colDefs.add(String.format("\"%s\" %s", safeCol, normalized)); + persistedCols.add(safeCol); + } + + if (persistedCols.isEmpty()) { + throw new IllegalArgumentException("log 생성할 컬럼이 없습니다."); } String createSql = String.format( @@ -642,7 +675,7 @@ public class TableManagementService extends BaseService { Map params = new HashMap<>(); params.put("table_name", tableName); params.put("is_active", isActive); - params.put("log_columns", String.join(",", targetCols)); + params.put("log_columns", String.join(",", persistedCols)); sqlSession.update(NS + "upsertLogConfig", params); log.info("로그 테이블 생성: {}", safeLog); @@ -872,19 +905,18 @@ public class TableManagementService extends BaseService { /** * context 에 따라 INPUT_TYPE 정규화 및 검증. - * @param context "user-insert" | "user-update-type" | "user-update-other" | "system-normalize" */ - private String normalizeInputType(String value, String context) { - if ("user-insert".equals(context) || "user-update-type".equals(context)) { - if (value == null || !USER_SELECTABLE_INPUT_TYPES.contains(value)) { + private String normalizeInputType(String value, InputTypeContext context) { + if (context == InputTypeContext.USER_INSERT || context == InputTypeContext.USER_UPDATE_TYPE) { + if (value == null || !InputTypeConstants.USER_SELECTABLE_INPUT_TYPES.contains(value)) { throw new IllegalArgumentException( - "INPUT_TYPE 은 다음 8개 중 하나여야 합니다: " + USER_SELECTABLE_INPUT_TYPES + "INPUT_TYPE 은 다음 8개 중 하나여야 합니다: " + InputTypeConstants.USER_SELECTABLE_INPUT_TYPES + " (받은 값: " + value + ")" ); } return value; } - // user-update-other / system-normalize: 기존 동작 그대로 + // USER_UPDATE_OTHER / SYSTEM_NORMALIZE: 기존 동작 그대로 return normalizeInputType(value); } diff --git a/frontend/components/admin/table-type/TypeOverviewStrip.tsx b/frontend/components/admin/table-type/TypeOverviewStrip.tsx index 7c991dac..c63ed1f3 100644 --- a/frontend/components/admin/table-type/TypeOverviewStrip.tsx +++ b/frontend/components/admin/table-type/TypeOverviewStrip.tsx @@ -22,11 +22,24 @@ function countByInputType(columns: ColumnTypeInfo[]): Record { return counts; } -/** 도넛 차트용 비율 (0~1) 배열 및 라벨 순서 (8개 사용자 선택 가능 타입 한정) */ -function getDonutSegments(counts: Record, total: number): Array<{ type: string; ratio: number }> { - return (USER_SELECTABLE_INPUT_TYPE_ORDER as readonly string[]) +/** 도넛 차트용 segment (8개 base + legacy 그룹) */ +function getDonutSegments( + counts: Record, + total: number, +): Array<{ type: string; ratio: number; isLegacy: boolean }> { + const baseSegments = (USER_SELECTABLE_INPUT_TYPE_ORDER as readonly string[]) .filter((type) => (counts[type] || 0) > 0) - .map((type) => ({ type, ratio: (counts[type] || 0) / total })); + .map((type) => ({ type, ratio: (counts[type] || 0) / total, isLegacy: false })); + + const legacyCount = Object.entries(counts) + .filter(([type]) => !(USER_SELECTABLE_INPUT_TYPE_ORDER as readonly string[]).includes(type)) + .reduce((sum, [, count]) => sum + count, 0); + + if (legacyCount > 0) { + baseSegments.push({ type: "__legacy__", ratio: legacyCount / total, isLegacy: true }); + } + + return baseSegments; } export function TypeOverviewStrip({ @@ -44,16 +57,25 @@ export function TypeOverviewStrip({ /** stroke-dasharray: 비율만큼 둘레에 할당 (둘레 100 기준) */ const circumference = 100; let offset = 0; - const segmentPaths = segments.map(({ type, ratio }) => { + const LEGACY_CONF = { + color: "text-amber-600", + bgColor: "bg-amber-50", + barColor: "bg-amber-400", + label: "Legacy", + desc: "구버전 타입", + iconChar: "?", + }; + const segmentPaths = segments.map(({ type, ratio, isLegacy }) => { const length = ratio * circumference; const dashArray = `${length} ${circumference - length}`; const dashOffset = -offset; offset += length; - const conf = INPUT_TYPE_COLORS[type] || { color: "text-muted-foreground", bgColor: "bg-muted" }; + const conf = isLegacy ? LEGACY_CONF : (INPUT_TYPE_COLORS[type] || { color: "text-muted-foreground", bgColor: "bg-muted" }); return { type, dashArray, dashOffset, + isLegacy, ...conf, }; }); @@ -84,7 +106,7 @@ export function TypeOverviewStrip({ - {/* 타입 칩 목록 (8개 사용자 선택 가능 타입 한정, 클릭 시 필터 토글) */} + {/* 타입 칩 목록 (8개 base + legacy 그룹, 클릭 시 필터 토글) */}
{(USER_SELECTABLE_INPUT_TYPE_ORDER as readonly string[]) .filter((type) => (counts[type] || 0) > 0) @@ -109,6 +131,29 @@ export function TypeOverviewStrip({ ); })} + {/* Legacy 칩 — 8개 외 구버전 타입 합산 */} + {(() => { + const legacyCount = Object.entries(counts) + .filter(([type]) => !(USER_SELECTABLE_INPUT_TYPE_ORDER as readonly string[]).includes(type)) + .reduce((sum, [, count]) => sum + count, 0); + if (legacyCount === 0) return null; + const isActive = activeFilter === null || activeFilter === "__legacy__"; + return ( + + ); + })()}
); diff --git a/frontend/components/screen/TableSettingModal.tsx b/frontend/components/screen/TableSettingModal.tsx index 62e18d34..57c64ca1 100644 --- a/frontend/components/screen/TableSettingModal.tsx +++ b/frontend/components/screen/TableSettingModal.tsx @@ -307,7 +307,7 @@ export function TableSettingModal({ const initialEdits: Record> = {}; columnsData.forEach((col) => { // reference_table이 설정되어 있으면 input_type은 entity여야 함 - let effectiveInputType = col.input_type || "direct"; + let effectiveInputType = col.input_type || "text"; if (col.reference_table && effectiveInputType !== "entity") { effectiveInputType = "entity"; }