This commit is contained in:
2026-04-06 15:54:31 +09:00
parent 87498b9940
commit de24fb09d5
21 changed files with 899 additions and 899 deletions
@@ -387,21 +387,21 @@
<select id="selectUserList" parameterType="map" resultType="map">
SELECT
SABUN
, USER_ID AS "userId"
, USER_NAME AS "userName"
, COALESCE(USER_NAME_ENG, '') AS "userNameEng"
, COALESCE(DEPT_CODE, '') AS "deptCode"
, COALESCE(DEPT_NAME, '') AS "deptName"
, COALESCE(POSITION_CODE, '') AS "positionCode"
, COALESCE(POSITION_NAME, '') AS "positionName"
, USER_ID
, USER_NAME
, COALESCE(USER_NAME_ENG, '') AS USER_NAME_ENG
, COALESCE(DEPT_CODE, '') AS DEPT_CODE
, COALESCE(DEPT_NAME, '') AS DEPT_NAME
, COALESCE(POSITION_CODE, '') AS POSITION_CODE
, COALESCE(POSITION_NAME, '') AS POSITION_NAME
, COALESCE(EMAIL, '') AS EMAIL
, COALESCE(TEL, '') AS TEL
, COALESCE(CELL_PHONE, '') AS "cellPhone"
, COALESCE(USER_TYPE, '') AS "userType"
, COALESCE(USER_TYPE_NAME, '') AS "userTypeName"
, COALESCE(TO_CHAR(CREATED_DATE, 'YYYY-MM-DD'), '') AS "regDate"
, COALESCE(CELL_PHONE, '') AS CELL_PHONE
, COALESCE(USER_TYPE, '') AS USER_TYPE
, COALESCE(USER_TYPE_NAME, '') AS USER_TYPE_NAME
, COALESCE(TO_CHAR(CREATED_DATE, 'YYYY-MM-DD'), '') AS REG_DATE
, STATUS
, COMPANY_CODE AS "companyCode"
, COMPANY_CODE
, COALESCE(LOCALE, '') AS LOCALE
FROM USER_INFO
WHERE 1=1
@@ -8,7 +8,7 @@
컬럼: flow_id (PK, serial), flow_name, flow_description, flow_data (JSONB),
company_code, CREATED_DATE, UPDATED_DATE
Node 코드와 동일한 camelCase 응답 키를 위해 AS "camelCase" 별칭 사용
snake_case 컬럼명 그대로 반환 (alias 없음)
-->
<!-- ────────────────────────────────────────────────────── -->
@@ -16,12 +16,12 @@
<!-- ────────────────────────────────────────────────────── -->
<select id="getNodeFlowList" parameterType="map" resultType="map">
SELECT
FLOW_ID AS "flowId"
, FLOW_NAME AS "flowName"
, FLOW_DESCRIPTION AS "flowDescription"
, COMPANY_CODE AS "companyCode"
, CREATED_DATE AS "createdAt"
, UPDATED_DATE AS "updatedAt"
FLOW_ID
, FLOW_NAME
, FLOW_DESCRIPTION
, COMPANY_CODE
, CREATED_DATE
, UPDATED_DATE
FROM NODE_FLOWS
WHERE 1=1
<if test="company_code != null and company_code != &quot;*&quot;">
@@ -35,12 +35,12 @@
<!-- ────────────────────────────────────────────────────── -->
<select id="getNodeFlowInfo" parameterType="map" resultType="map">
SELECT
FLOW_ID AS "flowId"
, FLOW_NAME AS "flowName"
, FLOW_DESCRIPTION AS "flowDescription"
, FLOW_DATA AS "flowData"
, CREATED_DATE AS "createdAt"
, UPDATED_DATE AS "updatedAt"
FLOW_ID
, FLOW_NAME
, FLOW_DESCRIPTION
, FLOW_DATA
, CREATED_DATE
, UPDATED_DATE
FROM NODE_FLOWS
WHERE FLOW_ID = #{flow_id}
<include refid="common.companyCodeFilter"/>
@@ -51,9 +51,9 @@
<!-- ────────────────────────────────────────────────────── -->
<select id="getNodeFlowData" parameterType="map" resultType="map">
SELECT
FLOW_ID AS "flowId"
, FLOW_NAME AS "flowName"
, FLOW_DATA AS "flowData"
FLOW_ID
, FLOW_NAME
, FLOW_DATA
FROM NODE_FLOWS
WHERE FLOW_ID = #{flow_id}
</select>
@@ -63,9 +63,9 @@
<!-- ────────────────────────────────────────────────────── -->
<select id="getNodeFlowForAudit" parameterType="map" resultType="map">
SELECT
FLOW_NAME AS "flowName"
, FLOW_DESCRIPTION AS "flowDescription"
, COMPANY_CODE AS "companyCode"
FLOW_NAME
, FLOW_DESCRIPTION
, COMPANY_CODE
FROM NODE_FLOWS
WHERE FLOW_ID = #{flow_id}
</select>
@@ -74,7 +74,7 @@
<!-- 생성 -->
<!-- ────────────────────────────────────────────────────── -->
<insert id="insertNodeFlow" parameterType="map"
useGeneratedKeys="true" keyProperty="flowId" keyColumn="flow_id">
useGeneratedKeys="true" keyProperty="flow_id" keyColumn="flow_id">
INSERT INTO NODE_FLOWS (
FLOW_NAME
, FLOW_DESCRIPTION
@@ -122,30 +122,30 @@
<select id="getScreenSplitPanelInfo" parameterType="map" resultType="map">
SELECT
SSP.ID,
SSP.SCREEN_ID AS "screenId",
SSP.LEFT_EMBEDDING_ID AS "leftEmbeddingId",
SSP.RIGHT_EMBEDDING_ID AS "rightEmbeddingId",
SSP.DATA_TRANSFER_ID AS "dataTransferId",
SSP.LAYOUT_CONFIG AS "layoutConfig",
SSP.COMPANY_CODE AS "companyCode",
SSP.CREATED_DATE AS "createdAt",
SSP.UPDATED_DATE AS "updatedAt",
LE.PARENT_SCREEN_ID AS "leParentScreenId",
LE.CHILD_SCREEN_ID AS "leChildScreenId",
LE.POSITION AS "lePosition",
LE.MODE AS "leMode",
LE.CONFIG AS "leConfig",
RE.PARENT_SCREEN_ID AS "reParentScreenId",
RE.CHILD_SCREEN_ID AS "reChildScreenId",
RE.POSITION AS "rePosition",
RE.MODE AS "reMode",
RE.CONFIG AS "reConfig",
SDT.SOURCE_SCREEN_ID AS "sourceScreenId",
SDT.TARGET_SCREEN_ID AS "targetScreenId",
SDT.SOURCE_COMPONENT_ID AS "sourceComponentId",
SDT.SOURCE_COMPONENT_TYPE AS "sourceComponentType",
SDT.DATA_RECEIVERS AS "dataReceivers",
SDT.BUTTON_CONFIG AS "buttonConfig"
SSP.SCREEN_ID,
SSP.LEFT_EMBEDDING_ID,
SSP.RIGHT_EMBEDDING_ID,
SSP.DATA_TRANSFER_ID,
SSP.LAYOUT_CONFIG,
SSP.COMPANY_CODE,
SSP.CREATED_DATE,
SSP.UPDATED_DATE,
LE.PARENT_SCREEN_ID AS LE_PARENT_SCREEN_ID,
LE.CHILD_SCREEN_ID AS LE_CHILD_SCREEN_ID,
LE.POSITION AS LE_POSITION,
LE.MODE AS LE_MODE,
LE.CONFIG AS LE_CONFIG,
RE.PARENT_SCREEN_ID AS RE_PARENT_SCREEN_ID,
RE.CHILD_SCREEN_ID AS RE_CHILD_SCREEN_ID,
RE.POSITION AS RE_POSITION,
RE.MODE AS RE_MODE,
RE.CONFIG AS RE_CONFIG,
SDT.SOURCE_SCREEN_ID,
SDT.TARGET_SCREEN_ID,
SDT.SOURCE_COMPONENT_ID,
SDT.SOURCE_COMPONENT_TYPE,
SDT.DATA_RECEIVERS,
SDT.BUTTON_CONFIG
FROM SCREEN_SPLIT_PANEL SSP
LEFT JOIN SCREEN_EMBEDDING LE ON SSP.LEFT_EMBEDDING_ID = LE.ID
LEFT JOIN SCREEN_EMBEDDING RE ON SSP.RIGHT_EMBEDDING_ID = RE.ID
@@ -58,8 +58,8 @@
<select id="selectGroupScreensByGroupIds" parameterType="map" resultType="map">
SELECT
SGS.ID,
SGS.GROUP_ID AS "groupId",
SGS.SCREEN_ID AS "screen_id",
SGS.GROUP_ID,
SGS.SCREEN_ID,
SD.SCREEN_NAME,
SGS.SCREEN_ROLE,
SGS.DISPLAY_ORDER,
@@ -9,56 +9,56 @@
<select id="getCategoryColumnList" parameterType="map" resultType="map">
SELECT
tc.table_name AS "tableName",
tc.column_name AS "columnName",
tc.column_name AS "columnLabel",
COUNT(cv.value_id) AS "valueCount"
TC.TABLE_NAME
, TC.COLUMN_NAME
, TC.COLUMN_NAME AS column_label
, COUNT(CV.VALUE_ID) AS value_count
FROM table_type_columns tc
FROM TABLE_TYPE_COLUMNS TC
LEFT JOIN category_values cv
ON tc.table_name = cv.table_name
AND tc.column_name = cv.column_name
AND cv.is_active = TRUE
LEFT JOIN CATEGORY_VALUES CV
ON TC.TABLE_NAME = CV.TABLE_NAME
AND TC.COLUMN_NAME = CV.COLUMN_NAME
AND CV.IS_ACTIVE = TRUE
<if test='company_code != null and company_code != "*"'>
AND (cv.company_code = #{company_code} OR cv.company_code = '*')
AND (CV.COMPANY_CODE = #{company_code} OR CV.COMPANY_CODE = '*')
</if>
WHERE tc.table_name = #{table_name}
AND tc.input_type = 'category'
WHERE TC.TABLE_NAME = #{table_name}
AND TC.INPUT_TYPE = 'category'
GROUP BY tc.table_name, tc.column_name, tc.display_order
GROUP BY TC.TABLE_NAME, TC.COLUMN_NAME, TC.DISPLAY_ORDER
ORDER BY tc.display_order, tc.column_name
ORDER BY TC.DISPLAY_ORDER, TC.COLUMN_NAME
</select>
<select id="getAllCategoryColumnList" parameterType="map" resultType="map">
SELECT
tc.table_name AS "tableName",
tc.column_name AS "columnName",
tc.column_name AS "columnLabel",
COALESCE(cv_count.cnt, 0) AS "valueCount"
TC.TABLE_NAME
, TC.COLUMN_NAME
, TC.COLUMN_NAME AS column_label
, COALESCE(CV_COUNT.cnt, 0) AS value_count
FROM (
SELECT DISTINCT table_name, column_name, MIN(display_order) AS display_order
FROM table_type_columns
WHERE input_type = 'category'
GROUP BY table_name, column_name
) tc
SELECT DISTINCT TABLE_NAME, COLUMN_NAME, MIN(DISPLAY_ORDER) AS display_order
FROM TABLE_TYPE_COLUMNS
WHERE INPUT_TYPE = 'category'
GROUP BY TABLE_NAME, COLUMN_NAME
) TC
LEFT JOIN (
SELECT table_name, column_name, COUNT(*) AS cnt
FROM category_values
WHERE is_active = TRUE
SELECT TABLE_NAME, COLUMN_NAME, COUNT(*) AS cnt
FROM CATEGORY_VALUES
WHERE IS_ACTIVE = TRUE
<if test='company_code != null and company_code != "*"'>
AND (company_code = #{company_code} OR company_code = '*')
AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
</if>
GROUP BY table_name, column_name
) cv_count
ON tc.table_name = cv_count.table_name
AND tc.column_name = cv_count.column_name
GROUP BY TABLE_NAME, COLUMN_NAME
) CV_COUNT
ON TC.TABLE_NAME = CV_COUNT.TABLE_NAME
AND TC.COLUMN_NAME = CV_COUNT.COLUMN_NAME
ORDER BY tc.table_name, tc.display_order, tc.column_name
ORDER BY TC.TABLE_NAME, TC.DISPLAY_ORDER, TC.COLUMN_NAME
</select>
<!-- ══════════════════════════════════════════════════════════════
@@ -67,98 +67,98 @@
<select id="getCategoryValueList" parameterType="map" resultType="map">
SELECT
value_id AS value_id,
table_name AS "tableName",
column_name AS "columnName",
value_code AS "valueCode",
value_label AS "valueLabel",
value_order AS "valueOrder",
parent_value_id AS parent_value_id,
depth,
description,
color,
icon,
is_active AS "isActive",
is_default AS "isDefault",
company_code AS "companyCode",
menu_objid AS "menuObjid",
CREATED_DATE AS "createdAt",
UPDATED_DATE AS "updatedAt",
created_by AS "createdBy",
updated_by AS "updatedBy"
VALUE_ID
, TABLE_NAME
, COLUMN_NAME
, VALUE_CODE
, VALUE_LABEL
, VALUE_ORDER
, PARENT_VALUE_ID
, DEPTH
, DESCRIPTION
, COLOR
, ICON
, IS_ACTIVE
, IS_DEFAULT
, COMPANY_CODE
, MENU_OBJID
, CREATED_DATE
, UPDATED_DATE
, CREATED_BY
, UPDATED_BY
FROM category_values
FROM CATEGORY_VALUES
WHERE table_name = #{table_name}
AND column_name = #{column_name}
WHERE TABLE_NAME = #{table_name}
AND COLUMN_NAME = #{column_name}
<choose>
<when test='company_code != null and company_code != "*"'>
AND (company_code = #{company_code} OR company_code = '*')
AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
</when>
<otherwise>
AND company_code = '*'
AND COMPANY_CODE = '*'
</otherwise>
</choose>
<if test="include_inactive == null or include_inactive == false">
AND is_active = TRUE
AND IS_ACTIVE = TRUE
</if>
ORDER BY value_order, value_label
ORDER BY VALUE_ORDER, VALUE_LABEL
</select>
<select id="getCategoryValueInfo" parameterType="map" resultType="map">
SELECT
value_id AS "valueId",
table_name AS "tableName",
column_name AS "columnName",
value_code AS "valueCode",
value_label AS "valueLabel",
value_order AS "valueOrder",
parent_value_id AS "parentValueId",
depth,
description,
color,
icon,
is_active AS "isActive",
is_default AS "isDefault",
company_code AS "companyCode",
menu_objid AS "menuObjid",
CREATED_DATE AS "createdAt",
UPDATED_DATE AS "updatedAt",
created_by AS "createdBy",
updated_by AS "updatedBy"
VALUE_ID
, TABLE_NAME
, COLUMN_NAME
, VALUE_CODE
, VALUE_LABEL
, VALUE_ORDER
, PARENT_VALUE_ID
, DEPTH
, DESCRIPTION
, COLOR
, ICON
, IS_ACTIVE
, IS_DEFAULT
, COMPANY_CODE
, MENU_OBJID
, CREATED_DATE
, UPDATED_DATE
, CREATED_BY
, UPDATED_BY
FROM category_values
FROM CATEGORY_VALUES
WHERE value_id = #{value_id}
WHERE VALUE_ID = #{value_id}
</select>
<!-- 사용 여부 확인용: table_name, column_name, value_code, value_label 반환 -->
<select id="getCategoryValueUsageInfo" parameterType="map" resultType="map">
SELECT
table_name AS table_name,
column_name AS column_name,
value_code AS value_code,
value_label AS value_label
TABLE_NAME
, COLUMN_NAME
, VALUE_CODE
, VALUE_LABEL
FROM category_values
FROM CATEGORY_VALUES
WHERE value_id = #{value_id}
WHERE VALUE_ID = #{value_id}
<if test='company_code != null and company_code != "*"'>
AND (company_code = #{company_code} OR company_code = '*')
AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
</if>
</select>
<!-- 수정 시 라벨 중복 체크용: table_name, column_name, company_code 반환 -->
<select id="getCategoryValueLabelInfo" parameterType="map" resultType="map">
SELECT
table_name AS table_name,
column_name AS column_name,
company_code AS company_code
TABLE_NAME
, COLUMN_NAME
, COMPANY_CODE
FROM category_values
FROM CATEGORY_VALUES
WHERE value_id = #{value_id}
WHERE VALUE_ID = #{value_id}
</select>
<!-- ══════════════════════════════════════════════════════════════
@@ -166,44 +166,44 @@
══════════════════════════════════════════════════════════════ -->
<select id="countDuplicateCode" parameterType="map" resultType="int">
SELECT COUNT(*) FROM category_values
WHERE table_name = #{table_name}
AND column_name = #{column_name}
AND value_code = #{value_code}
AND menu_objid = #{menu_objid}
SELECT COUNT(*) FROM CATEGORY_VALUES
WHERE TABLE_NAME = #{table_name}
AND COLUMN_NAME = #{column_name}
AND VALUE_CODE = #{value_code}
AND MENU_OBJID = #{menu_objid}
<if test='company_code != null and company_code != "*"'>
AND (company_code = #{company_code} OR company_code = '*')
AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
</if>
</select>
<select id="countDuplicateLabel" parameterType="map" resultType="int">
SELECT COUNT(*) FROM category_values
WHERE table_name = #{table_name}
AND column_name = #{column_name}
AND value_label = #{value_label}
AND is_active = TRUE
SELECT COUNT(*) FROM CATEGORY_VALUES
WHERE TABLE_NAME = #{table_name}
AND COLUMN_NAME = #{column_name}
AND VALUE_LABEL = #{value_label}
AND IS_ACTIVE = TRUE
<if test='company_code != null and company_code != "*"'>
AND (company_code = #{company_code} OR company_code = '*')
AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
</if>
</select>
<!-- 수정 시 자기 자신 제외 라벨 중복 체크 (항상 company_code 필터) -->
<select id="countDuplicateLabelExcludeSelf" parameterType="map" resultType="int">
SELECT COUNT(*) FROM category_values
WHERE table_name = #{table_name}
AND column_name = #{column_name}
AND value_label = #{value_label}
AND (company_code = #{company_code} OR company_code = '*')
AND is_active = TRUE
AND value_id != #{value_id}
SELECT COUNT(*) FROM CATEGORY_VALUES
WHERE TABLE_NAME = #{table_name}
AND COLUMN_NAME = #{column_name}
AND VALUE_LABEL = #{value_label}
AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
AND IS_ACTIVE = TRUE
AND VALUE_ID != #{value_id}
</select>
<insert id="insertCategoryValue" parameterType="map"
useGeneratedKeys="true" keyProperty="valueId" keyColumn="value_id">
INSERT INTO category_values (
table_name, column_name, value_code, value_label, value_order,
parent_value_id, depth, description, color, icon,
is_active, is_default, company_code, menu_objid, created_by
INSERT INTO CATEGORY_VALUES (
TABLE_NAME, COLUMN_NAME, VALUE_CODE, VALUE_LABEL, VALUE_ORDER,
PARENT_VALUE_ID, DEPTH, DESCRIPTION, COLOR, ICON,
IS_ACTIVE, IS_DEFAULT, COMPANY_CODE, MENU_OBJID, CREATED_BY
) VALUES (
#{table_name}, #{column_name}, #{value_code}, #{value_label}, #{value_order},
#{parent_value_id}, #{depth}, #{description}, #{color}, #{icon},
@@ -212,21 +212,21 @@
</insert>
<update id="updateCategoryValue" parameterType="map">
UPDATE category_values
UPDATE CATEGORY_VALUES
<set>
<if test="value_label != null">value_label = #{value_label},</if>
<if test="value_order != null">value_order = #{value_order},</if>
<if test="description != null">description = #{description},</if>
<if test="color != null">color = #{color},</if>
<if test="icon != null">icon = #{icon},</if>
<if test="is_active != null">is_active = #{is_active},</if>
<if test="is_default != null">is_default = #{is_default},</if>
<if test="value_label != null">VALUE_LABEL = #{value_label},</if>
<if test="value_order != null">VALUE_ORDER = #{value_order},</if>
<if test="description != null">DESCRIPTION = #{description},</if>
<if test="color != null">COLOR = #{color},</if>
<if test="icon != null">ICON = #{icon},</if>
<if test="is_active != null">IS_ACTIVE = #{is_active},</if>
<if test="is_default != null">IS_DEFAULT = #{is_default},</if>
UPDATED_DATE = NOW(),
updated_by = #{user_id}
UPDATED_BY = #{user_id}
</set>
WHERE value_id = #{value_id}
WHERE VALUE_ID = #{value_id}
<if test='company_code != null and company_code != "*"'>
AND (company_code = #{company_code} OR company_code = '*')
AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
</if>
</update>
@@ -251,52 +251,52 @@
WHERE ${safeColumnName} = #{value_code}
<if test='company_code != null and company_code != "*"'>
AND (company_code = #{company_code} OR company_code = '*')
AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
</if>
</select>
<select id="getMenuListUsingTable" parameterType="map" resultType="map">
SELECT DISTINCT
mi.objid AS "menuObjid",
mi.menu_name_kor AS menu_name,
mi.menu_url AS "menuUrl"
MI.OBJID AS menu_objid
, MI.MENU_NAME_KOR AS menu_name
, MI.MENU_URL
FROM menu_info mi
FROM MENU_INFO MI
INNER JOIN screen_menu_assignments sma ON sma.menu_objid = mi.objid
INNER JOIN screen_definitions sd ON sd.screen_id = sma.screen_id
INNER JOIN SCREEN_MENU_ASSIGNMENTS SMA ON SMA.MENU_OBJID = MI.OBJID
INNER JOIN SCREEN_DEFINITIONS SD ON SD.SCREEN_ID = SMA.SCREEN_ID
WHERE sd.table_name = #{table_name}
AND (mi.company_code = #{company_code} OR mi.company_code = '*')
WHERE SD.TABLE_NAME = #{table_name}
AND (MI.COMPANY_CODE = #{company_code} OR MI.COMPANY_CODE = '*')
ORDER BY mi.menu_name_kor
ORDER BY MI.MENU_NAME_KOR
</select>
<!-- 재귀 CTE 로 모든 하위 value_id 수집 -->
<select id="getChildValueIdList" parameterType="map" resultType="map">
WITH RECURSIVE category_tree AS (
SELECT value_id
FROM category_values
WHERE parent_value_id = #{value_id}
SELECT VALUE_ID
FROM CATEGORY_VALUES
WHERE PARENT_VALUE_ID = #{value_id}
<if test='company_code != null and company_code != "*"'>
AND (company_code = #{company_code} OR company_code = '*')
AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
</if>
UNION ALL
SELECT cv.value_id
FROM category_values cv
INNER JOIN category_tree ct ON cv.parent_value_id = ct.value_id
SELECT CV.VALUE_ID
FROM CATEGORY_VALUES CV
INNER JOIN category_tree CT ON CV.PARENT_VALUE_ID = CT.VALUE_ID
<if test='company_code != null and company_code != "*"'>
WHERE (cv.company_code = #{company_code} OR cv.company_code = '*')
WHERE (CV.COMPANY_CODE = #{company_code} OR CV.COMPANY_CODE = '*')
</if>
)
SELECT value_id AS value_id FROM category_tree
SELECT VALUE_ID FROM category_tree
</select>
<delete id="deleteValueById" parameterType="map">
DELETE FROM category_values
WHERE value_id = #{value_id}
DELETE FROM CATEGORY_VALUES
WHERE VALUE_ID = #{value_id}
<if test='company_code != null and company_code != "*"'>
AND (company_code = #{company_code} OR company_code = '*')
AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
</if>
</delete>
@@ -305,26 +305,26 @@
══════════════════════════════════════════════════════════════ -->
<update id="bulkSoftDeleteValues" parameterType="map">
UPDATE category_values
SET is_active = FALSE,
UPDATE CATEGORY_VALUES
SET IS_ACTIVE = FALSE,
UPDATED_DATE = NOW(),
updated_by = #{user_id}
WHERE value_id IN
UPDATED_BY = #{user_id}
WHERE VALUE_ID IN
<foreach collection="valueIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
<if test='company_code != null and company_code != "*"'>
AND (company_code = #{company_code} OR company_code = '*')
AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
</if>
</update>
<update id="updateValueOrder" parameterType="map">
UPDATE category_values
SET value_order = #{value_order},
UPDATE CATEGORY_VALUES
SET VALUE_ORDER = #{value_order},
UPDATED_DATE = NOW()
WHERE value_id = #{value_id}
WHERE VALUE_ID = #{value_id}
<if test='company_code != null and company_code != "*"'>
AND (company_code = #{company_code} OR company_code = '*')
AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
</if>
</update>
@@ -334,34 +334,34 @@
<select id="getColumnMappingList" parameterType="map" resultType="map">
SELECT
logical_column_name AS logical_column_name,
physical_column_name AS physical_column_name
LOGICAL_COLUMN_NAME
, PHYSICAL_COLUMN_NAME
FROM category_column_mapping
FROM CATEGORY_COLUMN_MAPPING
WHERE table_name = #{table_name}
AND menu_objid = #{menu_objid}
WHERE TABLE_NAME = #{table_name}
AND MENU_OBJID = #{menu_objid}
<if test='company_code != null and company_code != "*"'>
AND (company_code = #{company_code} OR company_code = '*')
AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
</if>
</select>
<select id="getLogicalColumnList" parameterType="map" resultType="map">
SELECT
mapping_id AS "mappingId",
logical_column_name AS "logicalColumnName",
physical_column_name AS "physicalColumnName",
description
MAPPING_ID
, LOGICAL_COLUMN_NAME
, PHYSICAL_COLUMN_NAME
, DESCRIPTION
FROM category_column_mapping
FROM CATEGORY_COLUMN_MAPPING
WHERE table_name = #{table_name}
AND menu_objid = #{menu_objid}
WHERE TABLE_NAME = #{table_name}
AND MENU_OBJID = #{menu_objid}
<if test='company_code != null and company_code != "*"'>
AND (company_code = #{company_code} OR company_code = '*')
AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
</if>
ORDER BY logical_column_name
ORDER BY LOGICAL_COLUMN_NAME
</select>
<select id="checkPhysicalColumnExists" parameterType="map" resultType="int">
@@ -373,46 +373,46 @@
<!-- UPSERT: ON CONFLICT (table_name, logical_column_name, menu_objid, company_code) -->
<insert id="upsertColumnMapping" parameterType="map">
INSERT INTO category_column_mapping (
table_name, logical_column_name, physical_column_name,
menu_objid, company_code, description, created_by, updated_by
INSERT INTO CATEGORY_COLUMN_MAPPING (
TABLE_NAME, LOGICAL_COLUMN_NAME, PHYSICAL_COLUMN_NAME,
MENU_OBJID, COMPANY_CODE, DESCRIPTION, CREATED_BY, UPDATED_BY
) VALUES (
#{table_name}, #{logical_column_name}, #{physical_column_name},
#{menu_objid}, #{company_code}, #{description}, #{user_id}, #{user_id}
)
ON CONFLICT (table_name, logical_column_name, menu_objid, company_code)
DO UPDATE SET
physical_column_name = EXCLUDED.physical_column_name,
description = EXCLUDED.description,
PHYSICAL_COLUMN_NAME = EXCLUDED.PHYSICAL_COLUMN_NAME,
DESCRIPTION = EXCLUDED.DESCRIPTION,
UPDATED_DATE = NOW(),
updated_by = EXCLUDED.updated_by
UPDATED_BY = EXCLUDED.UPDATED_BY
</insert>
<select id="getColumnMappingInfo" parameterType="map" resultType="map">
SELECT *
FROM category_column_mapping
FROM CATEGORY_COLUMN_MAPPING
WHERE table_name = #{table_name}
AND logical_column_name = #{logical_column_name}
AND menu_objid = #{menu_objid}
AND (company_code = #{company_code} OR company_code = '*')
WHERE TABLE_NAME = #{table_name}
AND LOGICAL_COLUMN_NAME = #{logical_column_name}
AND MENU_OBJID = #{menu_objid}
AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
</select>
<delete id="deleteColumnMappingById" parameterType="map">
DELETE FROM category_column_mapping
WHERE mapping_id = #{mapping_id}
DELETE FROM CATEGORY_COLUMN_MAPPING
WHERE MAPPING_ID = #{mapping_id}
<if test='company_code != null and company_code != "*"'>
AND (company_code = #{company_code} OR company_code = '*')
AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
</if>
</delete>
<delete id="deleteColumnMappingsByColumn" parameterType="map">
DELETE FROM category_column_mapping
WHERE table_name = #{table_name}
AND logical_column_name = #{column_name}
DELETE FROM CATEGORY_COLUMN_MAPPING
WHERE TABLE_NAME = #{table_name}
AND LOGICAL_COLUMN_NAME = #{column_name}
<if test='company_code != null and company_code != "*"'>
AND (company_code = #{company_code} OR company_code = '*')
AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
</if>
</delete>
@@ -422,17 +422,17 @@
<select id="getLabelListByCodes" parameterType="map" resultType="map">
SELECT DISTINCT
value_code AS value_code,
value_label AS value_label
VALUE_CODE
, VALUE_LABEL
FROM category_values
FROM CATEGORY_VALUES
WHERE value_code IN
WHERE VALUE_CODE IN
<foreach collection="valueCodes" item="code" open="(" separator="," close=")">
#{code}
</foreach>
<if test='company_code != null and company_code != "*"'>
AND (company_code = #{company_code} OR company_code = '*')
AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*')
</if>
</select>
@@ -448,23 +448,23 @@
<select id="getSecondLevelMenuList" parameterType="map" resultType="map">
SELECT
m1.objid AS "menuObjid",
m1.menu_name_kor AS "menuName",
m0.menu_name_kor AS "parentMenuName",
m1.screen_code AS "screenCode"
M1.OBJID AS menu_objid
, M1.MENU_NAME_KOR AS menu_name
, M0.MENU_NAME_KOR AS parent_menu_name
, M1.SCREEN_CODE AS screen_code
FROM menu_info m1
FROM MENU_INFO M1
INNER JOIN menu_info m0 ON m1.parent_obj_id = m0.objid
INNER JOIN MENU_INFO M0 ON M1.PARENT_OBJ_ID = M0.OBJID
WHERE m1.menu_type = '1'
AND m1.status = 'active'
AND m0.parent_obj_id = '0'
WHERE M1.MENU_TYPE = '1'
AND M1.STATUS = 'active'
AND M0.PARENT_OBJ_ID = '0'
<if test='has_company_code and company_code != null and company_code != "*"'>
AND (m1.company_code = #{company_code} OR m1.company_code = '*')
AND (M1.COMPANY_CODE = #{company_code} OR M1.COMPANY_CODE = '*')
</if>
ORDER BY m0.seq, m1.seq
ORDER BY M0.SEQ, M1.SEQ
</select>
</mapper>
@@ -8,11 +8,11 @@
═══════════════════════════════════════════════════ -->
<select id="getTableList" resultType="map">
SELECT
T.TABLE_NAME AS "tableName"
, COALESCE(TL.TABLE_LABEL, T.TABLE_NAME) AS "displayName"
, COALESCE(TL.DESCRIPTION, '') AS "description"
T.TABLE_NAME
, COALESCE(TL.TABLE_LABEL, T.TABLE_NAME) AS DISPLAY_NAME
, COALESCE(TL.DESCRIPTION, '') AS DESCRIPTION
, (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = T.TABLE_NAME AND TABLE_SCHEMA = 'public') AS "columnCount"
WHERE TABLE_NAME = T.TABLE_NAME AND TABLE_SCHEMA = 'public') AS COLUMN_COUNT
FROM INFORMATION_SCHEMA.TABLES T
LEFT JOIN TABLE_LABELS TL
ON T.TABLE_NAME = TL.TABLE_NAME
@@ -38,33 +38,33 @@
═══════════════════════════════════════════════════ -->
<select id="getColumnList" parameterType="map" resultType="map">
SELECT
C.COLUMN_NAME AS "columnName"
, COALESCE(CL.COLUMN_LABEL, C.COLUMN_NAME) AS "displayName"
, C.DATA_TYPE AS "dataType"
, C.DATA_TYPE AS "dbType"
, COALESCE(CL.INPUT_TYPE, 'text') AS "webType"
, COALESCE(CL.INPUT_TYPE, 'direct') AS "inputType"
, COALESCE(CL.DETAIL_SETTINGS::TEXT, '') AS "detailSettings"
, COALESCE(CL.DESCRIPTION, '') AS "description"
C.COLUMN_NAME
, COALESCE(CL.COLUMN_LABEL, C.COLUMN_NAME) AS DISPLAY_NAME
, C.DATA_TYPE
, C.DATA_TYPE AS DB_TYPE
, COALESCE(CL.INPUT_TYPE, 'text') AS WEB_TYPE
, COALESCE(CL.INPUT_TYPE, 'direct') AS INPUT_TYPE
, COALESCE(CL.DETAIL_SETTINGS::TEXT, '') AS DETAIL_SETTINGS
, COALESCE(CL.DESCRIPTION, '') AS DESCRIPTION
, CASE
WHEN CL.IS_NULLABLE IS NOT NULL
THEN CASE WHEN CL.IS_NULLABLE = 'N' THEN 'NO' ELSE 'YES' END
ELSE C.IS_NULLABLE
END AS "isNullable"
, CASE WHEN CL.IS_UNIQUE = 'Y' THEN 'YES' ELSE 'NO' END AS "isUnique"
, CASE WHEN PK.COLUMN_NAME IS NOT NULL THEN TRUE ELSE FALSE END AS "isPrimaryKey"
, C.COLUMN_DEFAULT AS "defaultValue"
, C.CHARACTER_MAXIMUM_LENGTH AS "maxLength"
, C.NUMERIC_PRECISION AS "numericPrecision"
, C.NUMERIC_SCALE AS "numericScale"
, CL.CODE_CATEGORY AS "codeCategory"
, CL.CODE_VALUE AS "codeValue"
, CL.REFERENCE_TABLE AS "referenceTable"
, CL.REFERENCE_COLUMN AS "referenceColumn"
, CL.DISPLAY_COLUMN AS "displayColumn"
, CL.DISPLAY_ORDER AS "displayOrder"
, CL.IS_VISIBLE AS "isVisible"
, DCL.COLUMN_LABEL AS "displayColumnLabel"
END AS IS_NULLABLE
, CASE WHEN CL.IS_UNIQUE = 'Y' THEN 'YES' ELSE 'NO' END AS IS_UNIQUE
, CASE WHEN PK.COLUMN_NAME IS NOT NULL THEN TRUE ELSE FALSE END AS IS_PRIMARY_KEY
, C.COLUMN_DEFAULT AS DEFAULT_VALUE
, C.CHARACTER_MAXIMUM_LENGTH AS MAX_LENGTH
, C.NUMERIC_PRECISION
, C.NUMERIC_SCALE
, CL.CODE_CATEGORY
, CL.CODE_VALUE
, CL.REFERENCE_TABLE
, CL.REFERENCE_COLUMN
, CL.DISPLAY_COLUMN
, CL.DISPLAY_ORDER
, CL.IS_VISIBLE
, DCL.COLUMN_LABEL AS DISPLAY_COLUMN_LABEL
FROM INFORMATION_SCHEMA.COLUMNS C
LEFT JOIN TABLE_TYPE_COLUMNS CL
ON C.TABLE_NAME = CL.TABLE_NAME AND C.COLUMN_NAME = CL.COLUMN_NAME AND CL.COMPANY_CODE = '*'
@@ -91,33 +91,33 @@
═══════════════════════════════════════════════════ -->
<select id="getColumnListWithCompany" parameterType="map" resultType="map">
SELECT
C.COLUMN_NAME AS "columnName"
, COALESCE(NULLIF(TTC.COLUMN_LABEL, C.COLUMN_NAME), CL.COLUMN_LABEL, C.COLUMN_NAME) AS "displayName"
, C.DATA_TYPE AS "dataType"
, C.DATA_TYPE AS "dbType"
, COALESCE(TTC.INPUT_TYPE, CL.INPUT_TYPE, 'text') AS "webType"
, COALESCE(TTC.INPUT_TYPE, CL.INPUT_TYPE, 'direct') AS "inputType"
, COALESCE(TTC.DETAIL_SETTINGS::TEXT, CL.DETAIL_SETTINGS::TEXT, '') AS "detailSettings"
, COALESCE(TTC.DESCRIPTION, CL.DESCRIPTION, '') AS "description"
C.COLUMN_NAME
, COALESCE(NULLIF(TTC.COLUMN_LABEL, C.COLUMN_NAME), CL.COLUMN_LABEL, C.COLUMN_NAME) AS DISPLAY_NAME
, C.DATA_TYPE
, C.DATA_TYPE AS DB_TYPE
, COALESCE(TTC.INPUT_TYPE, CL.INPUT_TYPE, 'text') AS WEB_TYPE
, COALESCE(TTC.INPUT_TYPE, CL.INPUT_TYPE, 'direct') AS INPUT_TYPE
, COALESCE(TTC.DETAIL_SETTINGS::TEXT, CL.DETAIL_SETTINGS::TEXT, '') AS DETAIL_SETTINGS
, COALESCE(TTC.DESCRIPTION, CL.DESCRIPTION, '') AS DESCRIPTION
, CASE
WHEN COALESCE(TTC.IS_NULLABLE, CL.IS_NULLABLE) IS NOT NULL
THEN CASE WHEN COALESCE(TTC.IS_NULLABLE, CL.IS_NULLABLE) = 'N' THEN 'NO' ELSE 'YES' END
ELSE C.IS_NULLABLE
END AS "isNullable"
, CASE WHEN COALESCE(TTC.IS_UNIQUE, CL.IS_UNIQUE) = 'Y' THEN 'YES' ELSE 'NO' END AS "isUnique"
, CASE WHEN PK.COLUMN_NAME IS NOT NULL THEN TRUE ELSE FALSE END AS "isPrimaryKey"
, C.COLUMN_DEFAULT AS "defaultValue"
, C.CHARACTER_MAXIMUM_LENGTH AS "maxLength"
, C.NUMERIC_PRECISION AS "numericPrecision"
, C.NUMERIC_SCALE AS "numericScale"
, COALESCE(TTC.CODE_CATEGORY, CL.CODE_CATEGORY) AS "codeCategory"
, COALESCE(TTC.CODE_VALUE, CL.CODE_VALUE) AS "codeValue"
, COALESCE(TTC.REFERENCE_TABLE, CL.REFERENCE_TABLE) AS "referenceTable"
, COALESCE(TTC.REFERENCE_COLUMN, CL.REFERENCE_COLUMN) AS "referenceColumn"
, COALESCE(TTC.DISPLAY_COLUMN, CL.DISPLAY_COLUMN) AS "displayColumn"
, COALESCE(TTC.DISPLAY_ORDER, CL.DISPLAY_ORDER) AS "displayOrder"
, COALESCE(TTC.IS_VISIBLE, CL.IS_VISIBLE) AS "isVisible"
, DCL.COLUMN_LABEL AS "displayColumnLabel"
END AS IS_NULLABLE
, CASE WHEN COALESCE(TTC.IS_UNIQUE, CL.IS_UNIQUE) = 'Y' THEN 'YES' ELSE 'NO' END AS IS_UNIQUE
, CASE WHEN PK.COLUMN_NAME IS NOT NULL THEN TRUE ELSE FALSE END AS IS_PRIMARY_KEY
, C.COLUMN_DEFAULT AS DEFAULT_VALUE
, C.CHARACTER_MAXIMUM_LENGTH AS MAX_LENGTH
, C.NUMERIC_PRECISION
, C.NUMERIC_SCALE
, COALESCE(TTC.CODE_CATEGORY, CL.CODE_CATEGORY) AS CODE_CATEGORY
, COALESCE(TTC.CODE_VALUE, CL.CODE_VALUE) AS CODE_VALUE
, COALESCE(TTC.REFERENCE_TABLE, CL.REFERENCE_TABLE) AS REFERENCE_TABLE
, COALESCE(TTC.REFERENCE_COLUMN, CL.REFERENCE_COLUMN) AS REFERENCE_COLUMN
, COALESCE(TTC.DISPLAY_COLUMN, CL.DISPLAY_COLUMN) AS DISPLAY_COLUMN
, COALESCE(TTC.DISPLAY_ORDER, CL.DISPLAY_ORDER) AS DISPLAY_ORDER
, COALESCE(TTC.IS_VISIBLE, CL.IS_VISIBLE) AS IS_VISIBLE
, DCL.COLUMN_LABEL AS DISPLAY_COLUMN_LABEL
FROM INFORMATION_SCHEMA.COLUMNS C
LEFT JOIN TABLE_TYPE_COLUMNS CL
ON C.TABLE_NAME = CL.TABLE_NAME AND C.COLUMN_NAME = CL.COLUMN_NAME AND CL.COMPANY_CODE = '*'
@@ -148,27 +148,27 @@
═══════════════════════════════════════════════════ -->
<select id="getTableSchemaList" parameterType="map" resultType="map">
SELECT
C.COLUMN_NAME AS "columnName"
, C.COLUMN_NAME AS "displayName"
, C.DATA_TYPE AS "dataType"
, C.UDT_NAME AS "dbType"
, C.IS_NULLABLE AS "isNullable"
, C.COLUMN_DEFAULT AS "defaultValue"
, C.CHARACTER_MAXIMUM_LENGTH AS "maxLength"
, C.NUMERIC_PRECISION AS "numericPrecision"
, C.NUMERIC_SCALE AS "numericScale"
C.COLUMN_NAME
, C.COLUMN_NAME AS DISPLAY_NAME
, C.DATA_TYPE
, C.UDT_NAME AS DB_TYPE
, C.IS_NULLABLE
, C.COLUMN_DEFAULT AS DEFAULT_VALUE
, C.CHARACTER_MAXIMUM_LENGTH AS MAX_LENGTH
, C.NUMERIC_PRECISION
, C.NUMERIC_SCALE
, CASE
WHEN C.COLUMN_NAME IN (
SELECT KCU.COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU
WHERE KCU.TABLE_NAME = #{table_name} AND KCU.CONSTRAINT_NAME LIKE '%_pkey'
) THEN TRUE ELSE FALSE
END AS "isPrimaryKey"
END AS IS_PRIMARY_KEY
, COL_DESCRIPTION(
(SELECT OID FROM PG_CLASS
WHERE RELNAME = #{table_name}
AND RELNAMESPACE = (SELECT OID FROM PG_NAMESPACE WHERE NSPNAME = 'public')),
C.ORDINAL_POSITION
) AS "columnComment"
) AS COLUMN_COMMENT
FROM INFORMATION_SCHEMA.COLUMNS C
WHERE C.TABLE_NAME = #{table_name}
AND C.TABLE_SCHEMA = 'public'
@@ -184,7 +184,7 @@
WHERE TABLE_NAME = #{table_name}
AND TABLE_SCHEMA = 'public'
AND TABLE_TYPE = 'BASE TABLE'
) AS "exists"
) AS EXISTS
</select>
<!-- ═══════════════════════════════════════════════════
@@ -192,11 +192,11 @@
═══════════════════════════════════════════════════ -->
<select id="getTableLabelInfo" parameterType="map" resultType="map">
SELECT
TABLE_NAME AS "tableName"
, TABLE_LABEL AS "tableLabel"
TABLE_NAME
, TABLE_LABEL
, DESCRIPTION
, CREATED_DATE AS "createdDate"
, UPDATED_DATE AS "updatedDate"
, CREATED_DATE
, UPDATED_DATE
FROM TABLE_LABELS
WHERE TABLE_NAME = #{table_name}
</select>
@@ -245,20 +245,20 @@
<select id="getColumnLabelInfo" parameterType="map" resultType="map">
SELECT
ID
, TABLE_NAME AS "tableName"
, COLUMN_NAME AS "columnName"
, COLUMN_LABEL AS "columnLabel"
, INPUT_TYPE AS "webType"
, DETAIL_SETTINGS::TEXT AS "detailSettings"
, TABLE_NAME
, COLUMN_NAME
, COLUMN_LABEL
, INPUT_TYPE AS WEB_TYPE
, DETAIL_SETTINGS::TEXT AS DETAIL_SETTINGS
, DESCRIPTION
, DISPLAY_ORDER AS "displayOrder"
, IS_VISIBLE AS "isVisible"
, CODE_CATEGORY AS "codeCategory"
, CODE_VALUE AS "codeValue"
, REFERENCE_TABLE AS "referenceTable"
, REFERENCE_COLUMN AS "referenceColumn"
, CREATED_DATE AS "createdDate"
, UPDATED_DATE AS "updatedDate"
, DISPLAY_ORDER
, IS_VISIBLE
, CODE_CATEGORY
, CODE_VALUE
, REFERENCE_TABLE
, REFERENCE_COLUMN
, CREATED_DATE
, UPDATED_DATE
FROM TABLE_TYPE_COLUMNS
WHERE TABLE_NAME = #{table_name}
AND COLUMN_NAME = #{column_name}
@@ -365,14 +365,14 @@
═══════════════════════════════════════════════════ -->
<select id="getColumnInputTypeList" parameterType="map" resultType="map">
SELECT DISTINCT ON (TTC.COLUMN_NAME)
TTC.COLUMN_NAME AS "columnName"
, COALESCE(TTC.COLUMN_LABEL, TTC.COLUMN_NAME) AS "displayName"
, TTC.INPUT_TYPE AS "inputType"
, COALESCE(TTC.DETAIL_SETTINGS::TEXT, '{}') AS "detailSettings"
, TTC.IS_NULLABLE AS "isNullable"
, IC.DATA_TYPE AS "dataType"
, TTC.COMPANY_CODE AS "companyCode"
, TTC.CATEGORY_REF AS "categoryRef"
TTC.COLUMN_NAME
, COALESCE(TTC.COLUMN_LABEL, TTC.COLUMN_NAME) AS DISPLAY_NAME
, TTC.INPUT_TYPE
, COALESCE(TTC.DETAIL_SETTINGS::TEXT, '{}') AS DETAIL_SETTINGS
, TTC.IS_NULLABLE
, IC.DATA_TYPE
, TTC.COMPANY_CODE
, TTC.CATEGORY_REF
FROM TABLE_TYPE_COLUMNS TTC
LEFT JOIN INFORMATION_SCHEMA.COLUMNS IC
ON TTC.TABLE_NAME = IC.TABLE_NAME AND TTC.COLUMN_NAME = IC.COLUMN_NAME
@@ -521,12 +521,12 @@
═══════════════════════════════════════════════════ -->
<select id="getCategoryColumnListByCompany" parameterType="map" resultType="map">
SELECT DISTINCT ON (TTC.TABLE_NAME, TTC.COLUMN_NAME)
TTC.TABLE_NAME AS "tableName"
, TTC.COLUMN_NAME AS "columnName"
, COALESCE(TTC.COLUMN_LABEL, TTC.COLUMN_NAME) AS "columnLabel"
, TTC.CATEGORY_REF AS "categoryRef"
, TTC.COMPANY_CODE AS "companyCode"
, TL.TABLE_LABEL AS "tableLabel"
TTC.TABLE_NAME
, TTC.COLUMN_NAME
, COALESCE(TTC.COLUMN_LABEL, TTC.COLUMN_NAME) AS COLUMN_LABEL
, TTC.CATEGORY_REF
, TTC.COMPANY_CODE
, TL.TABLE_LABEL
FROM TABLE_TYPE_COLUMNS TTC
LEFT JOIN TABLE_LABELS TL
ON TTC.TABLE_NAME = TL.TABLE_NAME
@@ -541,11 +541,11 @@
═══════════════════════════════════════════════════ -->
<select id="getNumberingColumnListByCompany" parameterType="map" resultType="map">
SELECT DISTINCT ON (TTC.TABLE_NAME, TTC.COLUMN_NAME)
TTC.TABLE_NAME AS "tableName"
, TTC.COLUMN_NAME AS "columnName"
, COALESCE(TTC.COLUMN_LABEL, TTC.COLUMN_NAME) AS "columnLabel"
, TTC.COMPANY_CODE AS "companyCode"
, TL.TABLE_LABEL AS "tableLabel"
TTC.TABLE_NAME
, TTC.COLUMN_NAME
, COALESCE(TTC.COLUMN_LABEL, TTC.COLUMN_NAME) AS COLUMN_LABEL
, TTC.COMPANY_CODE
, TL.TABLE_LABEL
FROM TABLE_TYPE_COLUMNS TTC
LEFT JOIN TABLE_LABELS TL
ON TTC.TABLE_NAME = TL.TABLE_NAME
@@ -560,11 +560,11 @@
═══════════════════════════════════════════════════ -->
<select id="getCategoryColumnListByMenu" parameterType="map" resultType="map">
SELECT DISTINCT ON (TTC.TABLE_NAME, TTC.COLUMN_NAME)
TTC.TABLE_NAME AS "tableName"
, TTC.COLUMN_NAME AS "columnName"
, COALESCE(TTC.COLUMN_LABEL, TTC.COLUMN_NAME) AS "columnLabel"
, TTC.CATEGORY_REF AS "categoryRef"
, CCM.MENU_OBJID AS "menuObjid"
TTC.TABLE_NAME
, TTC.COLUMN_NAME
, COALESCE(TTC.COLUMN_LABEL, TTC.COLUMN_NAME) AS COLUMN_LABEL
, TTC.CATEGORY_REF
, CCM.MENU_OBJID
FROM TABLE_TYPE_COLUMNS TTC
JOIN CATEGORY_COLUMN_MAPPING CCM
ON TTC.TABLE_NAME = CCM.TABLE_NAME
@@ -582,11 +582,11 @@
═══════════════════════════════════════════════════ -->
<select id="getEntityRelationList" parameterType="map" resultType="map">
SELECT
LEFT_COL.COLUMN_NAME AS "leftColumn"
, LEFT_COL.REFERENCE_TABLE AS "rightTable"
, LEFT_COL.REFERENCE_COLUMN AS "rightColumn"
, LEFT_COL.DISPLAY_COLUMN AS "displayColumn"
, COALESCE(LEFT_COL.COLUMN_LABEL, LEFT_COL.COLUMN_NAME) AS "columnLabel"
LEFT_COL.COLUMN_NAME AS LEFT_COLUMN
, LEFT_COL.REFERENCE_TABLE AS RIGHT_TABLE
, LEFT_COL.REFERENCE_COLUMN AS RIGHT_COLUMN
, LEFT_COL.DISPLAY_COLUMN
, COALESCE(LEFT_COL.COLUMN_LABEL, LEFT_COL.COLUMN_NAME) AS COLUMN_LABEL
FROM TABLE_TYPE_COLUMNS LEFT_COL
WHERE LEFT_COL.TABLE_NAME = #{left_table}
AND LEFT_COL.REFERENCE_TABLE = #{right_table}
@@ -600,12 +600,12 @@
═══════════════════════════════════════════════════ -->
<select id="getReferencedByTableList" parameterType="map" resultType="map">
SELECT DISTINCT
TTC.TABLE_NAME AS "tableName"
, TTC.COLUMN_NAME AS "columnName"
, COALESCE(TTC.COLUMN_LABEL, TTC.COLUMN_NAME) AS "columnLabel"
, TTC.REFERENCE_COLUMN AS "referenceColumn"
, TTC.DISPLAY_COLUMN AS "displayColumn"
, COALESCE(TL.TABLE_LABEL, TTC.TABLE_NAME) AS "tableLabel"
TTC.TABLE_NAME
, TTC.COLUMN_NAME
, COALESCE(TTC.COLUMN_LABEL, TTC.COLUMN_NAME) AS COLUMN_LABEL
, TTC.REFERENCE_COLUMN
, TTC.DISPLAY_COLUMN
, COALESCE(TL.TABLE_LABEL, TTC.TABLE_NAME) AS TABLE_LABEL
FROM TABLE_TYPE_COLUMNS TTC
LEFT JOIN TABLE_LABELS TL
ON TTC.TABLE_NAME = TL.TABLE_NAME
@@ -620,11 +620,11 @@
═══════════════════════════════════════════════════ -->
<select id="getLogConfigInfo" parameterType="map" resultType="map">
SELECT
TABLE_NAME AS "tableName"
, IS_ACTIVE AS "isActive"
, LOG_COLUMNS AS "logColumns"
, CREATED_DATE AS "createdDate"
, UPDATED_DATE AS "updatedDate"
TABLE_NAME
, IS_ACTIVE
, LOG_COLUMNS
, CREATED_DATE
, UPDATED_DATE
FROM LOG_TABLES
WHERE TABLE_NAME = #{table_name}
</select>
@@ -655,8 +655,8 @@
═══════════════════════════════════════════════════ -->
<select id="checkDatabaseConnection" resultType="map">
SELECT
1 AS "result"
, NOW() AS "timestamp"
1 AS RESULT
, NOW() AS TIMESTAMP
</select>
<!-- ═══════════════════════════════════════════════════
@@ -118,7 +118,7 @@ export default function TableManagementPage() {
// PK/인덱스 관리 상태
const [constraints, setConstraints] = useState<{
primaryKey: { name: string; columns: string[] };
indexes: Array<{ name: string; columns: string[]; isUnique: boolean }>;
indexes: Array<{ name: string; columns: string[]; is_unique: boolean }>;
}>({ primaryKey: { name: "", columns: [] }, indexes: [] });
const [pkDialogOpen, setPkDialogOpen] = useState(false);
const [pendingPkColumns, setPendingPkColumns] = useState<string[]>([]);
@@ -217,11 +217,11 @@ export default function TableManagementPage() {
const referenceTableOptions = [
{ value: "none", label: getTextFromUI(TABLE_MANAGEMENT_KEYS.LABEL_NONE, "선택 안함") },
...tables.map((table) => ({
value: table.tableName,
value: table.table_name,
label:
table.displayName && table.displayName !== table.tableName
? `${table.displayName} (${table.tableName})`
: table.tableName,
table.display_name && table.display_name !== table.table_name
? `${table.display_name} (${table.table_name})`
: table.table_name,
})),
];
@@ -317,21 +317,21 @@ export default function TableManagementPage() {
console.log("📥 원본 API 응답:", {
hasColumns: !!(data.columns || data),
firstColumn: (data.columns || data)[0],
statusColumn: (data.columns || data).find((col: any) => col.columnName === "status"),
statusColumn: (data.columns || data).find((col: any) => col.column_name === "status"),
});
// 컬럼 데이터에 기본값 설정
const processedColumns = (data.columns || data).map((col: any) => {
let hierarchyRole: "large" | "medium" | "small" | undefined = undefined;
if (col.detailSettings && typeof col.detailSettings === "string") {
if (col.detail_settings && typeof col.detail_settings === "string") {
try {
const parsed = JSON.parse(col.detailSettings);
const parsed = JSON.parse(col.detail_settings);
if (
parsed.hierarchyRole === "large" ||
parsed.hierarchyRole === "medium" ||
parsed.hierarchyRole === "small"
parsed.hierarchy_role === "large" ||
parsed.hierarchy_role === "medium" ||
parsed.hierarchy_role === "small"
) {
hierarchyRole = parsed.hierarchyRole;
hierarchyRole = parsed.hierarchy_role;
}
} catch {
// JSON 파싱 실패 시 무시
@@ -340,11 +340,11 @@ export default function TableManagementPage() {
return {
...col,
inputType: col.inputType || "text",
isUnique: col.isUnique || "NO",
categoryMenus: col.categoryMenus || [],
hierarchyRole,
categoryRef: col.categoryRef || null,
input_type: col.input_type || "text",
is_unique: col.is_unique || "NO",
category_menus: col.category_menus || [],
hierarchy_role: hierarchyRole,
category_ref: col.category_ref || null,
};
});
@@ -395,8 +395,8 @@ export default function TableManagementPage() {
setTypeFilter(null);
// 선택된 테이블 정보에서 라벨 설정
const tableInfo = tables.find((table) => table.tableName === tableName);
setTableLabel(tableInfo?.displayName || tableName);
const tableInfo = tables.find((table) => table.table_name === tableName);
setTableLabel(tableInfo?.display_name || tableName);
setTableDescription(tableInfo?.description || "");
loadColumnTypes(tableName, 1, pageSize);
@@ -410,31 +410,31 @@ export default function TableManagementPage() {
(columnName: string, newInputType: string) => {
setColumns((prev) =>
prev.map((col) => {
if (col.columnName === columnName) {
if (col.column_name === columnName) {
const inputTypeOption = memoizedInputTypeOptions.find((option) => option.value === newInputType);
const updated: typeof col = {
...col,
inputType: newInputType,
detailSettings: inputTypeOption?.description || col.detailSettings,
input_type: newInputType,
detail_settings: inputTypeOption?.description || col.detail_settings,
};
// 엔티티가 아닌 타입으로 변경 시 참조 설정 초기화
if (newInputType !== "entity") {
updated.referenceTable = undefined;
updated.referenceColumn = undefined;
updated.displayColumn = undefined;
updated.reference_table = undefined;
updated.reference_column = undefined;
updated.display_column = undefined;
}
// 코드가 아닌 타입으로 변경 시 코드 설정 초기화
if (newInputType !== "code") {
updated.codeCategory = undefined;
updated.codeValue = undefined;
updated.hierarchyRole = undefined;
updated.code_category = undefined;
updated.code_value = undefined;
updated.hierarchy_role = undefined;
}
// 카테고리가 아닌 타입으로 변경 시 카테고리 참조 초기화
if (newInputType !== "category") {
updated.categoryRef = undefined;
updated.category_ref = undefined;
}
return updated;
@@ -451,14 +451,14 @@ export default function TableManagementPage() {
(columnName: string, settingType: string, value: string) => {
setColumns((prev) =>
prev.map((col) => {
if (col.columnName === columnName) {
let newDetailSettings = col.detailSettings;
let codeCategory = col.codeCategory;
let codeValue = col.codeValue;
let referenceTable = col.referenceTable;
let referenceColumn = col.referenceColumn;
let displayColumn = col.displayColumn;
let hierarchyRole = col.hierarchyRole;
if (col.column_name === columnName) {
let newDetailSettings = col.detail_settings;
let codeCategory = col.code_category;
let codeValue = col.code_value;
let referenceTable = col.reference_table;
let referenceColumn = col.reference_column;
let displayColumn = col.display_column;
let hierarchyRole = col.hierarchy_role;
if (settingType === "code") {
if (value === "none") {
@@ -470,8 +470,8 @@ export default function TableManagementPage() {
// 기존 hierarchyRole 유지하면서 JSON 형식으로 저장
const existingHierarchyRole = hierarchyRole;
newDetailSettings = JSON.stringify({
codeCategory: value,
hierarchyRole: existingHierarchyRole,
code_category: value,
hierarchy_role: existingHierarchyRole,
});
codeCategory = value;
codeValue = value;
@@ -481,16 +481,16 @@ export default function TableManagementPage() {
hierarchyRole = value === "none" ? undefined : (value as "large" | "medium" | "small");
// detailSettings를 JSON으로 업데이트
let existingSettings: Record<string, any> = {};
if (typeof col.detailSettings === "string" && col.detailSettings.trim().startsWith("{")) {
if (typeof col.detail_settings === "string" && col.detail_settings.trim().startsWith("{")) {
try {
existingSettings = JSON.parse(col.detailSettings);
existingSettings = JSON.parse(col.detail_settings);
} catch {
existingSettings = {};
}
}
newDetailSettings = JSON.stringify({
...existingSettings,
hierarchyRole: hierarchyRole,
hierarchy_role: hierarchyRole,
});
} else if (settingType === "entity") {
if (value === "none") {
@@ -504,31 +504,31 @@ export default function TableManagementPage() {
referenceTable = value;
// 🎯 참조 컬럼을 소스 컬럼명과 동일하게 설정 (일반적인 경우)
// 예: user_info.dept_code -> dept_info.dept_code
referenceColumn = col.columnName;
referenceColumn = col.column_name;
// 참조 테이블의 컬럼 정보 로드
loadReferenceTableColumns(value);
}
} else if (settingType === "entity_reference_column") {
// 🎯 Entity 참조 컬럼 변경 (조인할 컬럼)
referenceColumn = value;
const tableOption = referenceTableOptions.find((option) => option.value === col.referenceTable);
const tableOption = referenceTableOptions.find((option) => option.value === col.reference_table);
newDetailSettings = tableOption ? `참조테이블: ${tableOption.label}` : "";
} else if (settingType === "entity_display_column") {
// 🎯 Entity 표시 컬럼 변경
displayColumn = value;
const tableOption = referenceTableOptions.find((option) => option.value === col.referenceTable);
const tableOption = referenceTableOptions.find((option) => option.value === col.reference_table);
newDetailSettings = tableOption ? `참조테이블: ${tableOption.label} (${value})` : "";
}
return {
...col,
detailSettings: newDetailSettings,
codeCategory,
codeValue,
referenceTable,
referenceColumn,
displayColumn,
hierarchyRole,
detail_settings: newDetailSettings,
code_category: codeCategory,
code_value: codeValue,
reference_table: referenceTable,
reference_column: referenceColumn,
display_column: displayColumn,
hierarchy_role: hierarchyRole,
};
}
return col;
@@ -542,10 +542,10 @@ export default function TableManagementPage() {
const handleLabelChange = useCallback((columnName: string, newLabel: string) => {
setColumns((prev) =>
prev.map((col) => {
if (col.columnName === columnName) {
if (col.column_name === columnName) {
return {
...col,
displayName: newLabel,
display_name: newLabel,
};
}
return col;
@@ -574,14 +574,14 @@ export default function TableManagementPage() {
try {
// 🎯 Entity 타입인 경우 detailSettings에 엔티티 설정을 JSON으로 포함
let finalDetailSettings = column.detailSettings || "";
let finalDetailSettings = column.detail_settings || "";
if (column.inputType === "entity" && column.referenceTable) {
if (column.input_type === "entity" && column.reference_table) {
// 기존 detailSettings를 파싱하거나 새로 생성
let existingSettings: Record<string, unknown> = {};
if (typeof column.detailSettings === "string" && column.detailSettings.trim().startsWith("{")) {
if (typeof column.detail_settings === "string" && column.detail_settings.trim().startsWith("{")) {
try {
existingSettings = JSON.parse(column.detailSettings);
existingSettings = JSON.parse(column.detail_settings);
} catch {
existingSettings = {};
}
@@ -590,9 +590,9 @@ export default function TableManagementPage() {
// 엔티티 설정 추가
const entitySettings = {
...existingSettings,
entityTable: column.referenceTable,
entityCodeColumn: column.referenceColumn || "id",
entityLabelColumn: column.displayColumn || "name",
entityTable: column.reference_table,
entityCodeColumn: column.reference_column || "id",
entityLabelColumn: column.display_column || "name",
placeholder: (existingSettings.placeholder as string) || "항목을 선택하세요",
searchable: existingSettings.searchable ?? true,
};
@@ -602,7 +602,7 @@ export default function TableManagementPage() {
}
// 🎯 Code 타입인 경우 hierarchyRole을 detailSettings에 포함
if (column.inputType === "code" && column.hierarchyRole) {
if (column.input_type === "code" && column.hierarchy_role) {
let existingSettings: Record<string, unknown> = {};
if (typeof finalDetailSettings === "string" && finalDetailSettings.trim().startsWith("{")) {
try {
@@ -614,7 +614,7 @@ export default function TableManagementPage() {
const codeSettings = {
...existingSettings,
hierarchyRole: column.hierarchyRole,
hierarchy_role: column.hierarchy_role,
};
finalDetailSettings = JSON.stringify(codeSettings);
@@ -622,26 +622,26 @@ export default function TableManagementPage() {
}
const columnSetting = {
columnName: column.columnName,
columnLabel: column.displayName,
inputType: column.inputType || "text",
detailSettings: finalDetailSettings,
codeCategory: column.codeCategory || "",
codeValue: column.codeValue || "",
referenceTable: column.referenceTable || "",
referenceColumn: column.referenceColumn || "",
displayColumn: column.displayColumn || "",
categoryRef: column.categoryRef || null,
column_name: column.column_name,
column_label: column.display_name,
input_type: column.input_type || "text",
detail_settings: finalDetailSettings,
code_category: column.code_category || "",
code_value: column.code_value || "",
reference_table: column.reference_table || "",
reference_column: column.reference_column || "",
display_column: column.display_column || "",
category_ref: column.category_ref || null,
};
// console.log("저장할 컬럼 설정:", columnSetting);
console.log("💾 저장할 컬럼 정보:", {
columnName: column.columnName,
inputType: column.inputType,
categoryMenus: column.categoryMenus,
hasCategoryMenus: !!column.categoryMenus,
categoryMenusLength: column.categoryMenus?.length || 0,
columnName: column.column_name,
inputType: column.input_type,
categoryMenus: column.category_menus,
hasCategoryMenus: !!column.category_menus,
categoryMenusLength: column.category_menus?.length || 0,
});
const response = await apiClient.post(`/table-management/tables/${selectedTable}/columns/settings`, [
@@ -653,44 +653,44 @@ export default function TableManagementPage() {
// 🆕 Category 타입인 경우 컬럼 매핑 처리
console.log("🔍 카테고리 조건 체크:", {
isCategory: column.inputType === "category",
hasCategoryMenus: !!column.categoryMenus,
length: column.categoryMenus?.length || 0,
isCategory: column.input_type === "category",
hasCategoryMenus: !!column.category_menus,
length: column.category_menus?.length || 0,
});
if (column.inputType === "category" && !column.categoryRef) {
if (column.input_type === "category" && !column.category_ref) {
// 참조가 아닌 자체 카테고리만 메뉴 매핑 처리
console.log("기존 카테고리 메뉴 매핑 삭제 시작:", {
tableName: selectedTable,
columnName: column.columnName,
columnName: column.column_name,
});
try {
const deleteResponse = await deleteColumnMappingsByColumn(selectedTable, column.columnName);
const deleteResponse = await deleteColumnMappingsByColumn(selectedTable, column.column_name);
console.log("🗑️ 기존 매핑 삭제 결과:", deleteResponse);
} catch (error) {
console.error("❌ 기존 매핑 삭제 실패:", error);
}
// 2. 새로운 매핑 추가 (선택된 메뉴가 있는 경우만)
if (column.categoryMenus && column.categoryMenus.length > 0) {
if (column.category_menus && column.category_menus.length > 0) {
console.log("📥 카테고리 메뉴 매핑 시작:", {
columnName: column.columnName,
categoryMenus: column.categoryMenus,
count: column.categoryMenus.length,
columnName: column.column_name,
categoryMenus: column.category_menus,
count: column.category_menus.length,
});
let successCount = 0;
let failCount = 0;
for (const menuObjid of column.categoryMenus) {
for (const menuObjid of column.category_menus) {
try {
const mappingResponse = await createColumnMapping({
tableName: selectedTable,
logicalColumnName: column.columnName,
physicalColumnName: column.columnName,
logicalColumnName: column.column_name,
physicalColumnName: column.column_name,
menuObjid,
description: `${column.displayName} (메뉴별 카테고리)`,
description: `${column.display_name} (메뉴별 카테고리)`,
});
if (mappingResponse.success) {
@@ -720,7 +720,7 @@ export default function TableManagementPage() {
}
// 원본 데이터 업데이트
setOriginalColumns((prev) => prev.map((col) => (col.columnName === column.columnName ? column : col)));
setOriginalColumns((prev) => prev.map((col) => (col.column_name === column.column_name ? column : col)));
// 저장 후 데이터 확인을 위해 다시 로드
setTimeout(() => {
@@ -762,10 +762,10 @@ export default function TableManagementPage() {
if (columns.length > 0) {
const columnSettings = columns.map((column) => {
// detailSettings 계산
let finalDetailSettings = column.detailSettings || "";
let finalDetailSettings = column.detail_settings || "";
// 🆕 Entity 타입인 경우 detailSettings에 엔티티 설정 포함
if (column.inputType === "entity" && column.referenceTable) {
if (column.input_type === "entity" && column.reference_table) {
let existingSettings: Record<string, unknown> = {};
if (typeof finalDetailSettings === "string" && finalDetailSettings.trim().startsWith("{")) {
try {
@@ -776,15 +776,15 @@ export default function TableManagementPage() {
}
const entitySettings = {
...existingSettings,
entityTable: column.referenceTable,
entityCodeColumn: column.referenceColumn || "id",
entityLabelColumn: column.displayColumn || "name",
entityTable: column.reference_table,
entityCodeColumn: column.reference_column || "id",
entityLabelColumn: column.display_column || "name",
};
finalDetailSettings = JSON.stringify(entitySettings);
}
// 🆕 Code 타입인 경우 hierarchyRole을 detailSettings에 포함
if (column.inputType === "code" && column.hierarchyRole) {
if (column.input_type === "code" && column.hierarchy_role) {
let existingSettings: Record<string, unknown> = {};
if (typeof finalDetailSettings === "string" && finalDetailSettings.trim().startsWith("{")) {
try {
@@ -795,23 +795,23 @@ export default function TableManagementPage() {
}
const codeSettings = {
...existingSettings,
hierarchyRole: column.hierarchyRole,
hierarchy_role: column.hierarchy_role,
};
finalDetailSettings = JSON.stringify(codeSettings);
}
return {
columnName: column.columnName,
columnLabel: column.displayName,
inputType: column.inputType || "text",
detailSettings: finalDetailSettings,
column_name: column.column_name,
column_label: column.display_name,
input_type: column.input_type || "text",
detail_settings: finalDetailSettings,
description: column.description || "",
codeCategory: column.codeCategory || "",
codeValue: column.codeValue || "",
referenceTable: column.referenceTable || "",
referenceColumn: column.referenceColumn || "",
displayColumn: column.displayColumn || "",
categoryRef: column.categoryRef || null,
code_category: column.code_category || "",
code_value: column.code_value || "",
reference_table: column.reference_table || "",
reference_column: column.reference_column || "",
display_column: column.display_column || "",
category_ref: column.category_ref || null,
};
});
@@ -825,14 +825,14 @@ export default function TableManagementPage() {
if (response.data.success) {
// 자체 카테고리 컬럼만 메뉴 매핑 처리 (참조 컬럼 제외)
const categoryColumns = columns.filter((col) => col.inputType === "category" && !col.categoryRef);
const categoryColumns = columns.filter((col) => col.input_type === "category" && !col.category_ref);
console.log("📥 전체 저장: 카테고리 컬럼 확인", {
totalColumns: columns.length,
categoryColumns: categoryColumns.length,
categoryColumnsData: categoryColumns.map((col) => ({
columnName: col.columnName,
categoryMenus: col.categoryMenus,
columnName: col.column_name,
categoryMenus: col.category_menus,
})),
});
@@ -844,32 +844,32 @@ export default function TableManagementPage() {
// 1. 먼저 기존 매핑 모두 삭제
console.log("🗑️ 기존 카테고리 메뉴 매핑 삭제:", {
tableName: selectedTable,
columnName: column.columnName,
columnName: column.column_name,
});
try {
const deleteResponse = await deleteColumnMappingsByColumn(selectedTable, column.columnName);
const deleteResponse = await deleteColumnMappingsByColumn(selectedTable, column.column_name);
console.log("🗑️ 기존 매핑 삭제 결과:", deleteResponse);
} catch (error) {
console.error("❌ 기존 매핑 삭제 실패:", error);
}
// 2. 새로운 매핑 추가 (선택된 메뉴가 있는 경우만)
if (column.categoryMenus && column.categoryMenus.length > 0) {
for (const menuObjid of column.categoryMenus) {
if (column.category_menus && column.category_menus.length > 0) {
for (const menuObjid of column.category_menus) {
try {
console.log("🔄 매핑 API 호출:", {
tableName: selectedTable,
columnName: column.columnName,
columnName: column.column_name,
menuObjid,
});
const mappingResponse = await createColumnMapping({
tableName: selectedTable,
logicalColumnName: column.columnName,
physicalColumnName: column.columnName,
logicalColumnName: column.column_name,
physicalColumnName: column.column_name,
menuObjid,
description: `${column.displayName} (메뉴별 카테고리)`,
description: `${column.display_name} (메뉴별 카테고리)`,
});
console.log("✅ 매핑 API 응답:", mappingResponse);
@@ -950,13 +950,13 @@ export default function TableManagementPage() {
const filteredTables = useMemo(() => {
const filtered = tables.filter(
(table) =>
table.tableName.toLowerCase().includes(searchTerm.toLowerCase()) ||
table.displayName.toLowerCase().includes(searchTerm.toLowerCase()),
table.table_name.toLowerCase().includes(searchTerm.toLowerCase()) ||
table.display_name.toLowerCase().includes(searchTerm.toLowerCase()),
);
const isKorean = (str: string) => /^[가-힣ㄱ-ㅎ]/.test(str);
return filtered.sort((a, b) => {
const nameA = a.displayName || a.tableName;
const nameB = b.displayName || b.tableName;
const nameA = a.display_name || a.table_name;
const nameB = b.display_name || b.table_name;
const aKo = isKorean(nameA);
const bKo = isKorean(nameB);
if (aKo && !bKo) return -1;
@@ -966,7 +966,7 @@ export default function TableManagementPage() {
}, [tables, searchTerm]);
// 선택된 테이블 정보
const selectedTableInfo = tables.find((table) => table.tableName === selectedTable);
const selectedTableInfo = tables.find((table) => table.table_name === selectedTable);
useEffect(() => {
loadTables();
@@ -978,13 +978,13 @@ export default function TableManagementPage() {
useEffect(() => {
if (columns.length > 0) {
const entityColumns = columns.filter(
(col) => col.inputType === "entity" && col.referenceTable && col.referenceTable !== "none",
(col) => col.input_type === "entity" && col.reference_table && col.reference_table !== "none",
);
entityColumns.forEach((col) => {
if (col.referenceTable) {
// console.log(`🎯 기존 Entity 컬럼 발견, 참조 테이블 컬럼 로드: ${col.columnName} -> ${col.referenceTable}`);
loadReferenceTableColumns(col.referenceTable);
if (col.reference_table) {
// console.log(`🎯 기존 Entity 컬럼 발견, 참조 테이블 컬럼 로드: ${col.column_name} -> ${col.reference_table}`);
loadReferenceTableColumns(col.reference_table);
}
});
}
@@ -1073,7 +1073,7 @@ export default function TableManagementPage() {
(columnName: string) => {
const isPk = constraints.primaryKey.columns.includes(columnName);
const hasIndex = constraints.indexes.some(
(idx) => !idx.isUnique && idx.columns.length === 1 && idx.columns[0] === columnName,
(idx) => !idx.is_unique && idx.columns.length === 1 && idx.columns[0] === columnName,
);
return { isPk, hasIndex };
},
@@ -1095,8 +1095,8 @@ export default function TableManagementPage() {
toast.success(response.data.message);
setColumns((prev) =>
prev.map((col) =>
col.columnName === columnName
? { ...col, isUnique: newUnique ? "YES" : "NO" }
col.column_name === columnName
? { ...col, is_unique: newUnique ? "YES" : "NO" }
: col,
),
);
@@ -1133,8 +1133,8 @@ export default function TableManagementPage() {
// 컬럼 상태 로컬 업데이트
setColumns((prev) =>
prev.map((col) =>
col.columnName === columnName
? { ...col, isNullable: newNullable ? "YES" : "NO" }
col.column_name === columnName
? { ...col, is_nullable: newNullable ? "YES" : "NO" }
: col,
),
);
@@ -1211,10 +1211,10 @@ export default function TableManagementPage() {
if (checked) {
const filteredTables = tables.filter(
(table) =>
table.tableName.toLowerCase().includes(searchTerm.toLowerCase()) ||
(table.displayName && table.displayName.toLowerCase().includes(searchTerm.toLowerCase())),
table.table_name.toLowerCase().includes(searchTerm.toLowerCase()) ||
(table.display_name && table.display_name.toLowerCase().includes(searchTerm.toLowerCase())),
);
setSelectedTableIds(new Set(filteredTables.map((table) => table.tableName)));
setSelectedTableIds(new Set(filteredTables.map((table) => table.table_name)));
} else {
setSelectedTableIds(new Set());
}
@@ -1377,7 +1377,7 @@ export default function TableManagementPage() {
<Checkbox
checked={
filteredTables.length > 0 &&
filteredTables.every((table) => selectedTableIds.has(table.tableName))
filteredTables.every((table) => selectedTableIds.has(table.table_name))
}
onCheckedChange={handleSelectAll}
aria-label="전체 선택"
@@ -1414,14 +1414,14 @@ export default function TableManagementPage() {
</div>
) : (
filteredTables.map((table, idx) => {
const isActive = selectedTable === table.tableName;
const isActive = selectedTable === table.table_name;
const prevTable = idx > 0 ? filteredTables[idx - 1] : null;
const isKo = /^[가-힣ㄱ-ㅎ]/.test(table.displayName || table.tableName);
const prevIsKo = prevTable ? /^[가-힣ㄱ-ㅎ]/.test(prevTable.displayName || prevTable.tableName) : null;
const isKo = /^[가-힣ㄱ-ㅎ]/.test(table.display_name || table.table_name);
const prevIsKo = prevTable ? /^[가-힣ㄱ-ㅎ]/.test(prevTable.display_name || prevTable.table_name) : null;
const showDivider = idx === 0 || (prevIsKo !== null && isKo !== prevIsKo);
return (
<div key={table.tableName}>
<div key={table.table_name}>
{showDivider && (
<div className="text-muted-foreground/60 mt-2 mb-1 px-2 text-[9px] font-bold uppercase tracking-widest">
{isKo ? "한글" : "ENGLISH"}
@@ -1434,13 +1434,13 @@ export default function TableManagementPage() {
? "bg-accent text-foreground"
: "text-foreground/80 hover:bg-accent/50",
)}
onClick={() => handleTableSelect(table.tableName)}
onClick={() => handleTableSelect(table.table_name)}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handleTableSelect(table.tableName);
handleTableSelect(table.table_name);
}
}}
>
@@ -1449,9 +1449,9 @@ export default function TableManagementPage() {
)}
{isSuperAdmin && (
<Checkbox
checked={selectedTableIds.has(table.tableName)}
onCheckedChange={(checked) => handleTableCheck(table.tableName, checked as boolean)}
aria-label={`${table.displayName || table.tableName} 선택`}
checked={selectedTableIds.has(table.table_name)}
onCheckedChange={(checked) => handleTableCheck(table.table_name, checked as boolean)}
aria-label={`${table.display_name || table.table_name} 선택`}
className="h-3.5 w-3.5 flex-shrink-0"
onClick={(e) => e.stopPropagation()}
/>
@@ -1462,11 +1462,11 @@ export default function TableManagementPage() {
"truncate text-[16px] leading-tight",
isActive ? "font-bold" : "font-medium",
)}>
{table.displayName || table.tableName}
{table.display_name || table.table_name}
</span>
</div>
<div className="text-muted-foreground truncate font-mono text-[12px] leading-tight tracking-tight">
{table.tableName}
{table.table_name}
</div>
</div>
<span className={cn(
@@ -1475,7 +1475,7 @@ export default function TableManagementPage() {
? "bg-primary/15 text-primary"
: "text-muted-foreground",
)}>
{table.columnCount}
{table.column_count}
</span>
</div>
</div>
@@ -1563,21 +1563,21 @@ export default function TableManagementPage() {
selectedColumn={selectedColumn}
onSelectColumn={setSelectedColumn}
onColumnChange={(columnName, field, value) => {
if (field === "isUnique") {
const currentColumn = columns.find((c) => c.columnName === columnName);
if (field === "is_unique") {
const currentColumn = columns.find((c) => c.column_name === columnName);
if (currentColumn) {
handleUniqueToggle(columnName, currentColumn.isUnique || "NO");
handleUniqueToggle(columnName, currentColumn.is_unique || "NO");
}
return;
}
if (field === "isNullable") {
const currentColumn = columns.find((c) => c.columnName === columnName);
if (field === "is_nullable") {
const currentColumn = columns.find((c) => c.column_name === columnName);
if (currentColumn) {
handleNullableToggle(columnName, currentColumn.isNullable || "YES");
handleNullableToggle(columnName, currentColumn.is_nullable || "YES");
}
return;
}
const idx = columns.findIndex((c) => c.columnName === columnName);
const idx = columns.findIndex((c) => c.column_name === columnName);
if (idx >= 0) handleColumnChange(idx, field, value);
}}
constraints={constraints}
@@ -1600,23 +1600,23 @@ export default function TableManagementPage() {
{selectedColumn && (
<div className="w-[380px] min-w-[380px] flex-shrink-0 overflow-hidden">
<ColumnDetailPanel
column={columns.find((c) => c.columnName === selectedColumn) ?? null}
column={columns.find((c) => c.column_name === selectedColumn) ?? null}
tables={tables}
referenceTableColumns={referenceTableColumns}
secondLevelMenus={secondLevelMenus}
numberingRules={[]}
onColumnChange={(field, value) => {
if (!selectedColumn) return;
if (field === "inputType") {
if (field === "input_type") {
handleInputTypeChange(selectedColumn, value as string);
return;
}
if (field === "referenceTable" && value) {
if (field === "reference_table" && value) {
loadReferenceTableColumns(value as string);
}
setColumns((prev) =>
prev.map((c) =>
c.columnName === selectedColumn ? { ...c, [field]: value } : c,
c.column_name === selectedColumn ? { ...c, [field]: value } : c,
),
);
}}
@@ -1648,11 +1648,11 @@ export default function TableManagementPage() {
// 테이블 목록 새로고침
await loadTables();
// 새로 생성된 테이블 자동 선택 및 컬럼 로드
if (result.data?.tableName) {
setSelectedTable(result.data.tableName);
if (result.data?.table_name) {
setSelectedTable(result.data.table_name);
setCurrentPage(1);
setColumns([]);
await loadColumnTypes(result.data.tableName, 1, pageSize);
await loadColumnTypes(result.data.table_name, 1, pageSize);
}
// 선택 초기화
setSelectedTableIds(new Set());
@@ -1789,11 +1789,11 @@ export default function TableManagementPage() {
{pendingPkColumns.length > 0 ? (
<div className="mt-2 flex flex-wrap gap-2">
{pendingPkColumns.map((col) => {
const colInfo = columns.find((c) => c.columnName === col);
const colInfo = columns.find((c) => c.column_name === col);
return (
<Badge key={col} variant="secondary" className="text-xs">
{colInfo?.displayName && colInfo.displayName !== col
? `${colInfo.displayName} (${col})`
{colInfo?.display_name && colInfo.display_name !== col
? `${colInfo.display_name} (${col})`
: col}
</Badge>
);
@@ -53,22 +53,22 @@ export function ColumnDetailPanel({
const [entityTableOpen, setEntityTableOpen] = React.useState(false);
const [entityColumnOpen, setEntityColumnOpen] = React.useState(false);
const typeConf = column ? INPUT_TYPE_COLORS[column.inputType || "text"] : null;
const refColumns = column?.referenceTable
? referenceTableColumns[column.referenceTable] ?? []
const typeConf = column ? INPUT_TYPE_COLORS[column.input_type || "text"] : null;
const refColumns = column?.reference_table
? referenceTableColumns[column.reference_table] ?? []
: [];
React.useEffect(() => {
if (column?.referenceTable && column.referenceTable !== "none") {
onLoadReferenceColumns?.(column.referenceTable);
if (column?.reference_table && column.reference_table !== "none") {
onLoadReferenceColumns?.(column.reference_table);
}
}, [column?.referenceTable, onLoadReferenceColumns]);
}, [column?.reference_table, onLoadReferenceColumns]);
const advancedCount = useMemo(() => {
if (!column) return 0;
let n = 0;
if (column.defaultValue != null && column.defaultValue !== "") n++;
if (column.maxLength != null && column.maxLength > 0) n++;
if (column.default_value != null && column.default_value !== "") n++;
if (column.max_length != null && column.max_length > 0) n++;
return n;
}, [column]);
@@ -81,11 +81,11 @@ export function ColumnDetailPanel({
: [
{ value: "none", label: "없음" },
...tables.map((t) => ({
value: t.tableName,
value: t.table_name,
label:
t.displayName && t.displayName !== t.tableName
? `${t.displayName} (${t.tableName})`
: t.tableName,
t.display_name && t.display_name !== t.table_name
? `${t.display_name} (${t.table_name})`
: t.table_name,
})),
];
@@ -114,9 +114,9 @@ export function ColumnDetailPanel({
</span>
)}
<span className="truncate text-sm font-medium">
{column.displayName && column.displayName !== column.columnName
? `${column.displayName} (${column.columnName})`
: column.columnName}
{column.display_name && column.display_name !== column.column_name
? `${column.display_name} (${column.column_name})`
: column.column_name}
</span>
</div>
<Button type="button" variant="ghost" size="icon" className="h-8 w-8 shrink-0" onClick={onClose} aria-label="닫기">
@@ -133,12 +133,12 @@ export function ColumnDetailPanel({
</div>
<div className="grid grid-cols-3 gap-1.5">
{Object.entries(INPUT_TYPE_COLORS).map(([type, conf]) => {
const isSelected = (column.inputType || "text") === type;
const isSelected = (column.input_type || "text") === type;
return (
<button
key={type}
type="button"
onClick={() => onColumnChange("inputType", type)}
onClick={() => onColumnChange("input_type", type)}
className={cn(
"flex flex-col items-center gap-1 rounded-lg border px-1.5 py-2.5 text-center transition-all",
isSelected
@@ -168,7 +168,7 @@ export function ColumnDetailPanel({
</section>
{/* [섹션 2] 타입별 상세 설정 */}
{column.inputType === "entity" && (
{column.input_type === "entity" && (
<section className="space-y-3">
<div className="flex items-center gap-2">
<Settings2 className="h-4 w-4 text-muted-foreground" />
@@ -185,8 +185,8 @@ export function ColumnDetailPanel({
role="combobox"
className="h-9 w-full justify-between text-xs"
>
{column.referenceTable && column.referenceTable !== "none"
? refTableOpts.find((o) => o.value === column.referenceTable)?.label ?? column.referenceTable
{column.reference_table && column.reference_table !== "none"
? refTableOpts.find((o) => o.value === column.reference_table)?.label ?? column.reference_table
: "테이블 선택..."}
<ChevronsUpDown className="ml-2 h-3 w-3 opacity-50" />
</Button>
@@ -204,14 +204,14 @@ export function ColumnDetailPanel({
key={opt.value}
value={`${opt.label} ${opt.value}`}
onSelect={() => {
onColumnChange("referenceTable", opt.value === "none" ? undefined : opt.value);
onColumnChange("reference_table", opt.value === "none" ? undefined : opt.value);
if (opt.value !== "none") onLoadReferenceColumns?.(opt.value);
setEntityTableOpen(false);
}}
className="text-xs"
>
<Check
className={cn("mr-2 h-3 w-3", column.referenceTable === opt.value ? "opacity-100" : "opacity-0")}
className={cn("mr-2 h-3 w-3", column.reference_table === opt.value ? "opacity-100" : "opacity-0")}
/>
{hasKorean ? (
<div className="flex flex-col">
@@ -232,7 +232,7 @@ export function ColumnDetailPanel({
</div>
{/* 조인 컬럼 */}
{column.referenceTable && column.referenceTable !== "none" && (
{column.reference_table && column.reference_table !== "none" && (
<div className="space-y-1">
<Label className="text-[11px] font-medium text-muted-foreground"> ()</Label>
<Popover open={entityColumnOpen} onOpenChange={setEntityColumnOpen}>
@@ -243,12 +243,12 @@ export function ColumnDetailPanel({
disabled={refColumns.length === 0}
className="h-9 w-full justify-between text-xs"
>
{column.referenceColumn && column.referenceColumn !== "none"
{column.reference_column && column.reference_column !== "none"
? (() => {
const matched = refColumns.find((c) => c.columnName === column.referenceColumn);
return matched?.displayName && matched.displayName !== column.referenceColumn
? `${matched.displayName} (${column.referenceColumn})`
: column.referenceColumn;
const matched = refColumns.find((c) => c.column_name === column.reference_column);
return matched?.display_name && matched.display_name !== column.reference_column
? `${matched.display_name} (${column.reference_column})`
: column.reference_column;
})()
: "컬럼 선택..."}
<ChevronsUpDown className="ml-2 h-3 w-3 opacity-50" />
@@ -263,20 +263,20 @@ export function ColumnDetailPanel({
<CommandItem
value="none"
onSelect={() => {
onColumnChange("referenceColumn", undefined);
onColumnChange("reference_column", undefined);
setEntityColumnOpen(false);
}}
className="text-xs"
>
<Check className={cn("mr-2 h-3 w-3", !column.referenceColumn ? "opacity-100" : "opacity-0")} />
<Check className={cn("mr-2 h-3 w-3", !column.reference_column ? "opacity-100" : "opacity-0")} />
</CommandItem>
{refColumns.map((refCol) => (
<CommandItem
key={refCol.columnName}
value={`${refCol.displayName ?? ""} ${refCol.columnName}`}
key={refCol.column_name}
value={`${refCol.display_name ?? ""} ${refCol.column_name}`}
onSelect={() => {
onColumnChange("referenceColumn", refCol.columnName);
onColumnChange("reference_column", refCol.column_name);
setEntityColumnOpen(false);
}}
className="text-xs"
@@ -284,16 +284,16 @@ export function ColumnDetailPanel({
<Check
className={cn(
"mr-2 h-3 w-3",
column.referenceColumn === refCol.columnName ? "opacity-100" : "opacity-0",
column.reference_column === refCol.column_name ? "opacity-100" : "opacity-0",
)}
/>
{refCol.displayName && refCol.displayName !== refCol.columnName ? (
{refCol.display_name && refCol.display_name !== refCol.column_name ? (
<div className="flex flex-col">
<span className="font-medium">{refCol.displayName}</span>
<span className="text-[10px] text-muted-foreground">{refCol.columnName}</span>
<span className="font-medium">{refCol.display_name}</span>
<span className="text-[10px] text-muted-foreground">{refCol.column_name}</span>
</div>
) : (
<span>{refCol.columnName}</span>
<span>{refCol.column_name}</span>
)}
</CommandItem>
))}
@@ -306,21 +306,21 @@ export function ColumnDetailPanel({
)}
{/* 참조 요약 미니맵 */}
{column.referenceTable && column.referenceTable !== "none" && column.referenceColumn && (
{column.reference_table && column.reference_table !== "none" && column.reference_column && (
<div className="flex items-center gap-2 rounded-md bg-violet-50 px-3 py-2">
<span className="text-[11px] font-semibold text-violet-600">
{(() => {
const tbl = refTableOpts.find((o) => o.value === column.referenceTable);
return tbl?.label ?? column.referenceTable;
const tbl = refTableOpts.find((o) => o.value === column.reference_table);
return tbl?.label ?? column.reference_table;
})()}
</span>
<span className="text-muted-foreground text-[10px]"></span>
<span className="text-[11px] font-semibold text-violet-600">
{(() => {
const col = refColumns.find((c) => c.columnName === column.referenceColumn);
return col?.displayName && col.displayName !== column.referenceColumn
? `${col.displayName} (${column.referenceColumn})`
: column.referenceColumn;
const col = refColumns.find((c) => c.column_name === column.reference_column);
return col?.display_name && col.display_name !== column.reference_column
? `${col.display_name} (${column.reference_column})`
: column.reference_column;
})()}
</span>
</div>
@@ -328,7 +328,7 @@ export function ColumnDetailPanel({
</section>
)}
{column.inputType === "code" && (
{column.input_type === "code" && (
<section className="space-y-2">
<div className="flex items-center gap-2">
<Settings2 className="h-4 w-4 text-muted-foreground" />
@@ -338,8 +338,8 @@ export function ColumnDetailPanel({
<div className="space-y-1.5">
<Label className="text-xs text-muted-foreground"> </Label>
<Select
value={column.codeCategory ?? "none"}
onValueChange={(v) => onColumnChange("codeCategory", v === "none" ? undefined : v)}
value={column.code_category ?? "none"}
onValueChange={(v) => onColumnChange("code_category", v === "none" ? undefined : v)}
>
<SelectTrigger className="h-9 text-xs">
<SelectValue placeholder="코드 선택" />
@@ -353,13 +353,13 @@ export function ColumnDetailPanel({
</SelectContent>
</Select>
</div>
{column.codeCategory && column.codeCategory !== "none" && (
{column.code_category && column.code_category !== "none" && (
<div className="space-y-1.5">
<Label className="text-xs text-muted-foreground"> </Label>
<Select
value={column.hierarchyRole ?? "none"}
value={column.hierarchy_role ?? "none"}
onValueChange={(v) =>
onColumnChange("hierarchyRole", v === "none" ? undefined : (v as "large" | "medium" | "small"))
onColumnChange("hierarchy_role", v === "none" ? undefined : (v as "large" | "medium" | "small"))
}
>
<SelectTrigger className="h-9 text-xs">
@@ -378,7 +378,7 @@ export function ColumnDetailPanel({
</section>
)}
{column.inputType === "category" && (
{column.input_type === "category" && (
<section className="space-y-2">
<div className="flex items-center gap-2">
<Settings2 className="h-4 w-4 text-muted-foreground" />
@@ -387,8 +387,8 @@ export function ColumnDetailPanel({
<div className="space-y-1.5">
<Label className="text-xs text-muted-foreground"> (.)</Label>
<Input
value={column.categoryRef ?? ""}
onChange={(e) => onColumnChange("categoryRef", e.target.value || null)}
value={column.category_ref ?? ""}
onChange={(e) => onColumnChange("category_ref", e.target.value || null)}
placeholder="테이블명.컬럼명"
className="h-9 text-xs"
/>
@@ -396,7 +396,7 @@ export function ColumnDetailPanel({
</section>
)}
{column.inputType === "numbering" && (
{column.input_type === "numbering" && (
<section className="space-y-2">
<div className="flex items-center gap-2">
<Settings2 className="h-4 w-4 text-muted-foreground" />
@@ -416,9 +416,9 @@ export function ColumnDetailPanel({
<Label className="text-sm font-medium"> </Label>
</div>
<Input
value={column.displayName ?? ""}
onChange={(e) => onColumnChange("displayName", e.target.value)}
placeholder={column.columnName}
value={column.display_name ?? ""}
onChange={(e) => onColumnChange("display_name", e.target.value)}
placeholder={column.column_name}
className="h-9 text-sm"
/>
</section>
@@ -436,8 +436,8 @@ export function ColumnDetailPanel({
<p className="text-xs text-muted-foreground"> .</p>
</div>
<Switch
checked={column.isNullable === "NO"}
onCheckedChange={(checked) => onColumnChange("isNullable", checked ? "NO" : "YES")}
checked={column.is_nullable === "NO"}
onCheckedChange={(checked) => onColumnChange("is_nullable", checked ? "NO" : "YES")}
aria-label="필수 입력"
/>
</div>
@@ -484,8 +484,8 @@ export function ColumnDetailPanel({
<div className="space-y-1.5">
<Label className="text-xs text-muted-foreground"></Label>
<Input
value={column.defaultValue ?? ""}
onChange={(e) => onColumnChange("defaultValue", e.target.value)}
value={column.default_value ?? ""}
onChange={(e) => onColumnChange("default_value", e.target.value)}
placeholder="기본값"
className="h-9 text-xs"
/>
@@ -494,10 +494,10 @@ export function ColumnDetailPanel({
<Label className="text-xs text-muted-foreground"> </Label>
<Input
type="number"
value={column.maxLength ?? ""}
value={column.max_length ?? ""}
onChange={(e) => {
const v = e.target.value;
onColumnChange("maxLength", v === "" ? undefined : Number(v));
onColumnChange("max_length", v === "" ? undefined : Number(v));
}}
placeholder="숫자"
className="h-9 text-xs"
@@ -11,7 +11,7 @@ import type { ReferenceTableColumn } from "@/lib/api/entityJoin";
export interface ColumnGridConstraints {
primaryKey: { columns: string[] };
indexes: Array<{ columns: string[]; isUnique: boolean }>;
indexes: Array<{ columns: string[]; is_unique: boolean }>;
}
export interface ColumnGridProps {
@@ -35,7 +35,7 @@ function getIndexState(
): { isPk: boolean; hasIndex: boolean } {
const isPk = constraints.primaryKey.columns.includes(columnName);
const hasIndex = constraints.indexes.some(
(idx) => !idx.isUnique && idx.columns.length === 1 && idx.columns[0] === columnName,
(idx) => !idx.is_unique && idx.columns.length === 1 && idx.columns[0] === columnName,
);
return { isPk, hasIndex };
}
@@ -68,7 +68,7 @@ export function ColumnGrid({
/** typeFilter 적용 후 그룹별로 정렬 */
const filteredAndGrouped = useMemo(() => {
const filtered =
typeFilter != null ? columns.filter((c) => (c.inputType || "text") === typeFilter) : columns;
typeFilter != null ? columns.filter((c) => (c.input_type || "text") === typeFilter) : columns;
const groups = { basic: [] as ColumnTypeInfo[], reference: [] as ColumnTypeInfo[], meta: [] as ColumnTypeInfo[] };
for (const col of filtered) {
const group = getColumnGroup(col);
@@ -116,20 +116,20 @@ export function ColumnGrid({
</Badge>
</div>
{list.map((column) => {
const typeConf = INPUT_TYPE_COLORS[column.inputType || "text"] || INPUT_TYPE_COLORS.text;
const idxState = getIdxState(column.columnName);
const isSelected = selectedColumn === column.columnName;
const typeConf = INPUT_TYPE_COLORS[column.input_type || "text"] || INPUT_TYPE_COLORS.text;
const idxState = getIdxState(column.column_name);
const isSelected = selectedColumn === column.column_name;
return (
<div
key={column.columnName}
key={column.column_name}
role="button"
tabIndex={0}
onClick={() => onSelectColumn(column.columnName)}
onClick={() => onSelectColumn(column.column_name)}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
onSelectColumn(column.columnName);
onSelectColumn(column.column_name);
}
}}
className={cn(
@@ -145,15 +145,15 @@ export function ColumnGrid({
{/* 라벨 + 컬럼명 (한글라벨 (영어명) 동시 표시) */}
<div className="min-w-0">
<div className="truncate text-sm font-medium">
{column.displayName && column.displayName !== column.columnName
? `${column.displayName} (${column.columnName})`
: column.columnName}
{column.display_name && column.display_name !== column.column_name
? `${column.display_name} (${column.column_name})`
: column.column_name}
</div>
</div>
{/* 참조/설정 칩 */}
<div className="flex min-w-0 flex-wrap gap-1">
{column.inputType === "entity" && column.referenceTable && column.referenceTable !== "none" && (
{column.input_type === "entity" && column.reference_table && column.reference_table !== "none" && (
<>
<Badge
variant="outline"
@@ -161,51 +161,51 @@ export function ColumnGrid({
title={
tables
? (() => {
const t = tables.find((tb) => tb.tableName === column.referenceTable);
return t?.displayName && t.displayName !== t.tableName
? `${t.displayName} (${column.referenceTable})`
: column.referenceTable;
const t = tables.find((tb) => tb.table_name === column.reference_table);
return t?.display_name && t.display_name !== t.table_name
? `${t.display_name} (${column.reference_table})`
: column.reference_table;
})()
: column.referenceTable
: column.reference_table
}
>
{column.referenceTable}
{column.reference_table}
</Badge>
<span className="text-muted-foreground text-xs"></span>
<Badge
variant="outline"
className="text-xs font-normal"
title={
referenceTableColumns?.[column.referenceTable]
referenceTableColumns?.[column.reference_table]
? (() => {
const refCols = referenceTableColumns[column.referenceTable];
const c = refCols.find((rc) => rc.columnName === (column.referenceColumn ?? ""));
return c?.displayName && c.displayName !== c.columnName
? `${c.displayName} (${column.referenceColumn})`
: column.referenceColumn ?? "—";
const refCols = referenceTableColumns[column.reference_table];
const c = refCols.find((rc) => rc.column_name === (column.reference_column ?? ""));
return c?.display_name && c.display_name !== c.column_name
? `${c.display_name} (${column.reference_column})`
: column.reference_column ?? "—";
})()
: column.referenceColumn ?? "—"
: column.reference_column ?? "—"
}
>
{column.referenceColumn || "—"}
{column.reference_column || "—"}
</Badge>
</>
)}
{column.inputType === "code" && (
{column.input_type === "code" && (
<span className="text-muted-foreground truncate text-xs">
{column.codeCategory ?? "—"} · {column.defaultValue ?? ""}
{column.code_category ?? "—"} · {column.default_value ?? ""}
</span>
)}
{column.inputType === "numbering" && column.numberingRuleId && (
{column.input_type === "numbering" && column.numbering_rule_id && (
<Badge variant="outline" className="text-xs font-normal">
{column.numberingRuleId}
{column.numbering_rule_id}
</Badge>
)}
{column.inputType !== "entity" &&
column.inputType !== "code" &&
column.inputType !== "numbering" &&
(column.defaultValue ? (
<span className="text-muted-foreground truncate text-xs">{column.defaultValue}</span>
{column.input_type !== "entity" &&
column.input_type !== "code" &&
column.input_type !== "numbering" &&
(column.default_value ? (
<span className="text-muted-foreground truncate text-xs">{column.default_value}</span>
) : (
<span className="text-muted-foreground/60 text-xs"></span>
))}
@@ -229,7 +229,7 @@ export function ColumnGrid({
)}
onClick={(e) => {
e.stopPropagation();
onPkToggle?.(column.columnName, !idxState.isPk);
onPkToggle?.(column.column_name, !idxState.isPk);
}}
title="Primary Key 토글"
>
@@ -239,13 +239,13 @@ export function ColumnGrid({
type="button"
className={cn(
"rounded border px-1.5 py-0.5 text-[10px] font-bold transition-colors",
column.isNullable === "NO"
column.is_nullable === "NO"
? "border-amber-200 bg-amber-50 text-amber-600"
: "border-border text-muted-foreground/40 hover:border-amber-200 hover:text-amber-400",
)}
onClick={(e) => {
e.stopPropagation();
onColumnChange(column.columnName, "isNullable", column.isNullable === "NO" ? "YES" : "NO");
onColumnChange(column.column_name, "is_nullable", column.is_nullable === "NO" ? "YES" : "NO");
}}
title="Not Null 토글"
>
@@ -261,7 +261,7 @@ export function ColumnGrid({
)}
onClick={(e) => {
e.stopPropagation();
onIndexToggle?.(column.columnName, !idxState.hasIndex);
onIndexToggle?.(column.column_name, !idxState.hasIndex);
}}
title="Index 토글"
>
@@ -271,13 +271,13 @@ export function ColumnGrid({
type="button"
className={cn(
"rounded border px-1.5 py-0.5 text-[10px] font-bold transition-colors",
column.isUnique === "YES"
column.is_unique === "YES"
? "border-violet-200 bg-violet-50 text-violet-600"
: "border-border text-muted-foreground/40 hover:border-violet-200 hover:text-violet-400",
)}
onClick={(e) => {
e.stopPropagation();
onColumnChange(column.columnName, "isUnique", column.isUnique === "YES" ? "NO" : "YES");
onColumnChange(column.column_name, "is_unique", column.is_unique === "YES" ? "NO" : "YES");
}}
title="Unique 토글"
>
@@ -293,7 +293,7 @@ export function ColumnGrid({
className="h-8 w-8"
onClick={(e) => {
e.stopPropagation();
onSelectColumn(column.columnName);
onSelectColumn(column.column_name);
}}
aria-label="상세 설정"
>
@@ -15,7 +15,7 @@ export interface TypeOverviewStripProps {
function countByInputType(columns: ColumnTypeInfo[]): Record<string, number> {
const counts: Record<string, number> = {};
for (const col of columns) {
const t = col.inputType || "text";
const t = col.input_type || "text";
counts[t] = (counts[t] || 0) + 1;
}
return counts;
+28 -28
View File
@@ -4,40 +4,40 @@
*/
export interface TableInfo {
tableName: string;
displayName: string;
table_name: string;
display_name: string;
description: string;
columnCount: number;
column_count: number;
}
export interface ColumnTypeInfo {
columnName: string;
displayName: string;
inputType: string;
detailSettings: string;
column_name: string;
display_name: string;
input_type: string;
detail_settings: string;
description: string;
isNullable: string;
isUnique: string;
defaultValue?: string;
maxLength?: number;
numericPrecision?: number;
numericScale?: number;
codeCategory?: string;
codeValue?: string;
referenceTable?: string;
referenceColumn?: string;
displayColumn?: string;
categoryMenus?: number[];
hierarchyRole?: "large" | "medium" | "small";
numberingRuleId?: string;
categoryRef?: string | null;
is_nullable: string;
is_unique: string;
default_value?: string;
max_length?: number;
numeric_precision?: number;
numeric_scale?: number;
code_category?: string;
code_value?: string;
reference_table?: string;
reference_column?: string;
display_column?: string;
category_menus?: number[];
hierarchy_role?: "large" | "medium" | "small";
numbering_rule_id?: string;
category_ref?: string | null;
}
export interface SecondLevelMenu {
menuObjid: number;
menuName: string;
parentMenuName: string;
screenCode?: string;
menu_objid: number;
menu_name: string;
parent_menu_name: string;
screen_code?: string;
}
/** 컬럼 그룹 분류 */
@@ -73,7 +73,7 @@ export const INPUT_TYPE_COLORS: Record<string, TypeColorConfig> = {
/** 컬럼 그룹 판별 */
export function getColumnGroup(col: ColumnTypeInfo): ColumnGroup {
const metaCols = ["id", "created_date", "updated_date", "writer", "company_code"];
if (metaCols.includes(col.columnName)) return "meta";
if (["entity", "code", "category"].includes(col.inputType)) return "reference";
if (metaCols.includes(col.column_name)) return "meta";
if (["entity", "code", "category"].includes(col.input_type)) return "reference";
return "basic";
}
+24 -24
View File
@@ -46,11 +46,11 @@ interface FlowSummary {
}
interface NodeFlow {
flowId: number;
flowName: string;
flowDescription: string;
createdAt: string;
updatedAt: string;
flow_id: number;
flow_name: string;
flow_description: string;
created_date: string;
updated_date: string;
summary: FlowSummary;
}
@@ -182,10 +182,10 @@ function FlowCard({
{/* 카드 바디 */}
<div className="px-4 pb-3 pt-3.5">
<h3 className="mb-1 truncate text-sm font-semibold tracking-tight text-zinc-100">
{flow.flowName}
{flow.flow_name}
</h3>
<p className="mb-3 line-clamp-2 min-h-[2.5rem] text-[11px] leading-relaxed text-zinc-500">
{flow.flowDescription || "설명이 아직 없어요"}
{flow.flow_description || "설명이 아직 없어요"}
</p>
{/* 노드 타입 칩 */}
@@ -206,7 +206,7 @@ function FlowCard({
{/* 카드 푸터 */}
<div className="flex items-center justify-between border-t border-zinc-800/40 px-4 py-2.5">
<span className="font-mono text-[11px] text-zinc-600">
{relativeTime(flow.updatedAt)}
{relativeTime(flow.updated_date)}
</span>
<div className="flex gap-0.5">
<button
@@ -284,13 +284,13 @@ export default function DataFlowList({ onLoadFlow }: DataFlowListProps) {
const handleCopy = async (flow: NodeFlow) => {
try {
setLoading(true);
const response = await apiClient.get(`/dataflow/node-flows/${flow.flowId}`);
const response = await apiClient.get(`/dataflow/node-flows/${flow.flow_id}`);
if (!response.data.success) throw new Error(response.data.message || "플로우 조회 실패");
const copyResponse = await apiClient.post("/dataflow/node-flows", {
flowName: `${flow.flowName} (복사본)`,
flowDescription: flow.flowDescription,
flowData: response.data.data.flowData,
flow_name: `${flow.flow_name} (복사본)`,
flow_description: flow.flow_description,
flow_data: response.data.data.flow_data,
});
if (copyResponse.data.success) {
@@ -313,9 +313,9 @@ export default function DataFlowList({ onLoadFlow }: DataFlowListProps) {
if (!selectedFlow) return;
try {
setLoading(true);
const response = await apiClient.delete(`/dataflow/node-flows/${selectedFlow.flowId}`);
const response = await apiClient.delete(`/dataflow/node-flows/${selectedFlow.flow_id}`);
if (response.data.success) {
toast.success(`"${selectedFlow.flowName}" 플로우를 삭제했어요`);
toast.success(`"${selectedFlow.flow_name}" 플로우를 삭제했어요`);
await loadFlows();
} else {
throw new Error(response.data.message || "플로우 삭제 실패");
@@ -336,8 +336,8 @@ export default function DataFlowList({ onLoadFlow }: DataFlowListProps) {
() =>
flows.filter(
(f) =>
f.flowName.toLowerCase().includes(searchTerm.toLowerCase()) ||
(f.flowDescription || "").toLowerCase().includes(searchTerm.toLowerCase()),
f.flow_name.toLowerCase().includes(searchTerm.toLowerCase()) ||
(f.flow_description || "").toLowerCase().includes(searchTerm.toLowerCase()),
),
[flows, searchTerm],
);
@@ -478,9 +478,9 @@ export default function DataFlowList({ onLoadFlow }: DataFlowListProps) {
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
{filteredFlows.map((flow) => (
<FlowCard
key={flow.flowId}
key={flow.flow_id}
flow={flow}
onOpen={() => onLoadFlow(flow.flowId)}
onOpen={() => onLoadFlow(flow.flow_id)}
onCopy={() => handleCopy(flow)}
onDelete={() => handleDelete(flow)}
/>
@@ -503,19 +503,19 @@ export default function DataFlowList({ onLoadFlow }: DataFlowListProps) {
<div className="space-y-2">
{filteredFlows.map((flow) => (
<div
key={flow.flowId}
key={flow.flow_id}
className="group flex cursor-pointer items-center gap-4 rounded-lg border border-zinc-800 bg-zinc-900/80 px-4 py-3 transition-all hover:border-violet-500/40 hover:bg-zinc-900"
onClick={() => onLoadFlow(flow.flowId)}
onClick={() => onLoadFlow(flow.flow_id)}
>
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-violet-500/10">
<Network className="h-5 w-5 text-violet-400" />
</div>
<div className="min-w-0 flex-1">
<h3 className="truncate text-sm font-semibold text-zinc-100">
{flow.flowName}
{flow.flow_name}
</h3>
<p className="truncate text-xs text-zinc-500">
{flow.flowDescription || "설명이 아직 없어요"}
{flow.flow_description || "설명이 아직 없어요"}
</p>
</div>
<div className="hidden items-center gap-1.5 lg:flex">
@@ -534,7 +534,7 @@ export default function DataFlowList({ onLoadFlow }: DataFlowListProps) {
})}
</div>
<span className="hidden font-mono text-[11px] text-zinc-600 sm:block">
{relativeTime(flow.updatedAt)}
{relativeTime(flow.updated_date)}
</span>
<div className="flex gap-0.5" onClick={(e) => e.stopPropagation()}>
<button
@@ -563,7 +563,7 @@ export default function DataFlowList({ onLoadFlow }: DataFlowListProps) {
<DialogHeader>
<DialogTitle className="text-base sm:text-lg"> ?</DialogTitle>
<DialogDescription className="text-xs sm:text-sm">
&ldquo;{selectedFlow?.flowName}&rdquo; .
&ldquo;{selectedFlow?.flow_name}&rdquo; .
<br />
<span className="text-destructive font-medium">
, .
@@ -29,10 +29,10 @@ import { tableManagementApi } from "@/lib/api/tableManagement";
import { apiClient } from "@/lib/api/client";
interface SecondLevelMenu {
menuObjid: number;
menuName: string;
parentMenuName: string;
screenCode?: string;
menu_objid: number;
menu_name: string;
parent_menu_name: string;
screen_code?: string;
}
interface AddCategoryColumnDialogProps {
@@ -56,8 +56,8 @@ export function AddCategoryColumnDialog({
const [physicalColumns, setPhysicalColumns] = useState<string[]>([]);
const [secondLevelMenus, setSecondLevelMenus] = useState<SecondLevelMenu[]>([]);
const [selectedMenus, setSelectedMenus] = useState<number[]>([]);
const [logicalColumnName, setLogicalColumnName] = useState("");
const [physicalColumnName, setPhysicalColumnName] = useState("");
const [logical_column_name, set_logical_column_name] = useState("");
const [physical_column_name, set_physical_column_name] = useState("");
const [description, setDescription] = useState("");
// 다이얼로그 열릴 때 데이터 로드
@@ -99,22 +99,22 @@ export function AddCategoryColumnDialog({
};
// 메뉴 선택/해제
const toggleMenu = (menuObjid: number) => {
const toggleMenu = (menu_objid: number) => {
setSelectedMenus((prev) =>
prev.includes(menuObjid)
? prev.filter((id) => id !== menuObjid)
: [...prev, menuObjid]
prev.includes(menu_objid)
? prev.filter((id) => id !== menu_objid)
: [...prev, menu_objid]
);
};
const handleSave = async () => {
// 입력 검증
if (!logicalColumnName.trim()) {
if (!logical_column_name.trim()) {
toast.error("논리적 컬럼명을 입력해주세요");
return;
}
if (!physicalColumnName) {
if (!physical_column_name) {
toast.error("실제 컬럼을 선택해주세요");
return;
}
@@ -128,12 +128,12 @@ export function AddCategoryColumnDialog({
try {
// 선택된 각 메뉴에 대해 매핑 생성
const promises = selectedMenus.map((menuObjid) =>
const promises = selectedMenus.map((menu_objid) =>
createColumnMapping({
tableName,
logicalColumnName: logicalColumnName.trim(),
physicalColumnName,
menuObjid,
logicalColumnName: logical_column_name.trim(),
physicalColumnName: physical_column_name,
menuObjid: menu_objid,
description: description.trim() || undefined,
})
);
@@ -165,8 +165,8 @@ export function AddCategoryColumnDialog({
};
const resetForm = () => {
setLogicalColumnName("");
setPhysicalColumnName("");
set_logical_column_name("");
set_physical_column_name("");
setDescription("");
setSelectedMenus([]);
};
@@ -196,7 +196,7 @@ export function AddCategoryColumnDialog({
<Label className="text-xs sm:text-sm">
() *
</Label>
<Select value={physicalColumnName} onValueChange={setPhysicalColumnName}>
<Select value={physical_column_name} onValueChange={set_physical_column_name}>
<SelectTrigger className="h-8 text-xs sm:h-10 sm:text-sm">
<SelectValue placeholder="컬럼 선택" />
</SelectTrigger>
@@ -219,8 +219,8 @@ export function AddCategoryColumnDialog({
( ) *
</Label>
<Input
value={logicalColumnName}
onChange={(e) => setLogicalColumnName(e.target.value)}
value={logical_column_name}
onChange={(e) => set_logical_column_name(e.target.value)}
placeholder="예: status_stock, status_sales"
className="h-8 text-xs sm:h-10 sm:text-sm"
/>
@@ -239,18 +239,18 @@ export function AddCategoryColumnDialog({
<p className="text-xs text-muted-foreground"> ...</p>
) : (
secondLevelMenus.map((menu) => (
<div key={menu.menuObjid} className="flex items-center gap-2">
<div key={menu.menu_objid} className="flex items-center gap-2">
<Checkbox
id={`menu-${menu.menuObjid}`}
checked={selectedMenus.includes(menu.menuObjid)}
onCheckedChange={() => toggleMenu(menu.menuObjid)}
id={`menu-${menu.menu_objid}`}
checked={selectedMenus.includes(menu.menu_objid)}
onCheckedChange={() => toggleMenu(menu.menu_objid)}
className="h-4 w-4"
/>
<label
htmlFor={`menu-${menu.menuObjid}`}
htmlFor={`menu-${menu.menu_objid}`}
className="text-xs sm:text-sm cursor-pointer flex-1"
>
{menu.parentMenuName} {menu.menuName}
{menu.parent_menu_name} {menu.menu_name}
</label>
</div>
))
@@ -290,7 +290,7 @@ export function AddCategoryColumnDialog({
</Button>
<Button
onClick={handleSave}
disabled={!logicalColumnName || !physicalColumnName || selectedMenus.length === 0 || loading}
disabled={!logical_column_name || !physical_column_name || selectedMenus.length === 0 || loading}
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
>
{loading ? "추가 중..." : "추가"}
@@ -8,12 +8,12 @@ import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
export interface CategoryColumn {
tableName: string;
tableLabel?: string;
columnName: string;
columnLabel: string;
inputType: string;
valueCount?: number;
table_name: string;
table_label?: string;
column_name: string;
column_label: string;
input_type: string;
value_count?: number;
}
interface CategoryColumnListProps {
@@ -49,18 +49,18 @@ export function CategoryColumnList({
// 검색어로 필터링된 컬럼 목록
const filteredColumns = useMemo(() => {
if (!searchQuery.trim()) return columns;
const query = searchQuery.toLowerCase();
return columns.filter((col) => {
const columnName = (col.columnName || "").toLowerCase();
const columnLabel = (col.columnLabel || "").toLowerCase();
const tableName = (col.tableName || "").toLowerCase();
const tableLabel = (col.tableLabel || "").toLowerCase();
return columnName.includes(query) ||
columnLabel.includes(query) ||
tableName.includes(query) ||
tableLabel.includes(query);
const column_name = (col.column_name || "").toLowerCase();
const column_label = (col.column_label || "").toLowerCase();
const table_name = (col.table_name || "").toLowerCase();
const table_label = (col.table_label || "").toLowerCase();
return column_name.includes(query) ||
column_label.includes(query) ||
table_name.includes(query) ||
table_label.includes(query);
});
}, [columns, searchQuery]);
@@ -70,7 +70,7 @@ export function CategoryColumnList({
const groupMap = new Map<string, CategoryColumn[]>();
for (const col of filteredColumns) {
const key = col.tableName;
const key = col.table_name;
if (!groupMap.has(key)) {
groupMap.set(key, []);
}
@@ -80,7 +80,7 @@ export function CategoryColumnList({
for (const [tblName, cols] of groupMap) {
groups.push({
tableName: tblName,
tableLabel: cols[0]?.tableLabel || tblName,
tableLabel: cols[0]?.table_label || tblName,
columns: cols,
});
}
@@ -91,12 +91,12 @@ export function CategoryColumnList({
// 선택된 컬럼이 있는 그룹을 자동 펼침
useEffect(() => {
if (!selectedColumn) return;
const tableName = selectedColumn.split(".")[0];
if (tableName) {
const tblName = selectedColumn.split(".")[0];
if (tblName) {
setExpandedGroups((prev) => {
if (prev.has(tableName)) return prev;
if (prev.has(tblName)) return prev;
const next = new Set(prev);
next.add(tableName);
next.add(tblName);
return next;
});
}
@@ -112,7 +112,7 @@ export function CategoryColumnList({
setIsLoading(true);
try {
console.log("🔍 테이블 기반 카테고리 컬럼 조회 시작", { tableName });
// table_type_columns에서 input_type='category'인 컬럼 조회
const response = await apiClient.get(`/screen-management/tables/${tableName}/columns`);
@@ -127,8 +127,8 @@ export function CategoryColumnList({
// category 타입 중 자체 카테고리만 필터링 (참조 컬럼 제외)
const categoryColumns = allColumns.filter(
(col: any) => (col.inputType === "category" || col.input_type === "category")
&& !col.categoryRef && !col.category_ref
(col: any) => col.input_type === "category"
&& !col.category_ref
);
console.log("✅ 카테고리 컬럼 필터링 완료:", {
@@ -139,27 +139,27 @@ export function CategoryColumnList({
// 값 개수 조회 (테스트 테이블 사용)
const columnsWithCount = await Promise.all(
categoryColumns.map(async (col: any) => {
const colName = col.columnName || col.column_name;
const colLabel = col.columnLabel || col.column_label || colName;
const colName = col.column_name;
const colLabel = col.column_label || colName;
let valueCount = 0;
let value_count = 0;
try {
// 테스트 테이블에서 조회
const treeResponse = await apiClient.get(`/category-tree/test/${tableName}/${colName}`);
if (treeResponse.data.success && treeResponse.data.data) {
valueCount = countTreeNodes(treeResponse.data.data);
value_count = countTreeNodes(treeResponse.data.data);
}
} catch (error) {
console.error(`항목 개수 조회 실패 (${tableName}.${colName}):`, error);
}
return {
tableName,
tableLabel: tableName,
columnName: colName,
columnLabel: colLabel,
inputType: "category",
valueCount,
table_name: tableName,
table_label: tableName,
column_name: colName,
column_label: colLabel,
input_type: "category",
value_count,
};
}),
);
@@ -169,7 +169,7 @@ export function CategoryColumnList({
if (columnsWithCount.length > 0 && !selectedColumn) {
const firstCol = columnsWithCount[0];
onColumnSelect(`${firstCol.tableName}.${firstCol.columnName}`, firstCol.columnLabel, firstCol.tableName);
onColumnSelect(`${firstCol.table_name}.${firstCol.column_name}`, firstCol.column_label, firstCol.table_name);
}
} catch (error) {
console.error("❌ 테이블 기반 카테고리 컬럼 조회 실패:", error);
@@ -195,9 +195,9 @@ export function CategoryColumnList({
setIsLoading(true);
try {
console.log("🔍 회사 기준 카테고리 컬럼 조회 시작", { menuObjid });
// 회사 기준 카테고리 컬럼 조회 (menuObjid는 선택사항)
const url = menuObjid
const url = menuObjid
? `/table-management/menu/${menuObjid}/category-columns`
: `/table-management/category-columns`;
const response = await apiClient.get(url);
@@ -221,37 +221,37 @@ export function CategoryColumnList({
console.log("✅ 카테고리 컬럼 파싱 완료:", {
count: categoryColumns.length,
columns: categoryColumns.map((c: any) => ({
table: c.tableName,
column: c.columnName,
label: c.columnLabel,
table: c.table_name,
column: c.column_name,
label: c.column_label,
})),
});
// 각 컬럼의 값 개수 가져오기
const columnsWithCount = await Promise.all(
categoryColumns.map(async (col: any) => {
const colTable = col.tableName;
const colName = col.columnName;
const colLabel = col.columnLabel || colName;
const colTable = col.table_name;
const colName = col.column_name;
const colLabel = col.column_label || colName;
let valueCount = 0;
let value_count = 0;
try {
const valuesResult = await getCategoryValues(colTable, colName, false, menuObjid);
const valuesData = (valuesResult as any).data;
if (valuesResult.success && valuesData) {
valueCount = valuesData.length;
value_count = valuesData.length;
}
} catch (error) {
console.error(`항목 개수 조회 실패 (${colTable}.${colName}):`, error);
}
return {
tableName: colTable,
tableLabel: col.tableLabel || colTable, // 테이블 라벨 추가
columnName: colName,
columnLabel: colLabel,
inputType: col.inputType,
valueCount,
table_name: colTable,
table_label: col.table_label || colTable,
column_name: colName,
column_label: colLabel,
input_type: col.input_type,
value_count,
};
}),
);
@@ -268,7 +268,7 @@ export function CategoryColumnList({
if (columnsWithCount.length > 0 && !selectedColumn) {
const firstCol = columnsWithCount[0];
onColumnSelect(`${firstCol.tableName}.${firstCol.columnName}`, firstCol.columnLabel, firstCol.tableName);
onColumnSelect(`${firstCol.table_name}.${firstCol.column_name}`, firstCol.column_label, firstCol.table_name);
}
} catch (error) {
console.error("❌ 카테고리 컬럼 조회 실패:", error);
@@ -338,7 +338,7 @@ export function CategoryColumnList({
</div>
) : null}
{groupedColumns.map((group) => {
const totalValues = group.columns.reduce((sum, c) => sum + (c.valueCount ?? 0), 0);
const totalValues = group.columns.reduce((sum, c) => sum + (c.value_count ?? 0), 0);
const isActive = selectedTable === group.tableName;
return (
<button
@@ -407,9 +407,9 @@ export function CategoryColumnList({
) : null}
{groupedColumns.map((group) => {
const isExpanded = expandedGroups.has(group.tableName);
const totalValues = group.columns.reduce((sum, c) => sum + (c.valueCount ?? 0), 0);
const totalValues = group.columns.reduce((sum, c) => sum + (c.value_count ?? 0), 0);
const hasSelectedInGroup = group.columns.some(
(c) => selectedColumn === `${c.tableName}.${c.columnName}`,
(c) => selectedColumn === `${c.table_name}.${c.column_name}`,
);
// 그룹이 1개뿐이면 드롭다운 없이 바로 표시
@@ -417,12 +417,12 @@ export function CategoryColumnList({
return (
<div key={group.tableName} className="space-y-1.5">
{group.columns.map((column) => {
const uniqueKey = `${column.tableName}.${column.columnName}`;
const uniqueKey = `${column.table_name}.${column.column_name}`;
const isSelected = selectedColumn === uniqueKey;
return (
<div
key={uniqueKey}
onClick={() => onColumnSelect(uniqueKey, column.columnLabel || column.columnName, column.tableName)}
onClick={() => onColumnSelect(uniqueKey, column.column_label || column.column_name, column.table_name)}
className={`cursor-pointer rounded-lg border px-4 py-2 transition-all ${
isSelected ? "border-primary bg-primary/10 shadow-sm" : "hover:bg-muted/50"
}`}
@@ -432,11 +432,11 @@ export function CategoryColumnList({
className={`h-4 w-4 ${isSelected ? "text-primary" : "text-muted-foreground"}`}
/>
<div className="flex-1">
<h4 className="text-sm font-semibold">{column.columnLabel || column.columnName}</h4>
<p className="text-muted-foreground text-xs">{column.tableLabel || column.tableName}</p>
<h4 className="text-sm font-semibold">{column.column_label || column.column_name}</h4>
<p className="text-muted-foreground text-xs">{column.table_label || column.table_name}</p>
</div>
<span className="text-muted-foreground text-xs font-medium">
{column.valueCount !== undefined ? `${column.valueCount}` : "..."}
{column.value_count !== undefined ? `${column.value_count}` : "..."}
</span>
</div>
</div>
@@ -483,12 +483,12 @@ export function CategoryColumnList({
{isExpanded && (
<div className="space-y-1 border-t px-2 py-2">
{group.columns.map((column) => {
const uniqueKey = `${column.tableName}.${column.columnName}`;
const uniqueKey = `${column.table_name}.${column.column_name}`;
const isSelected = selectedColumn === uniqueKey;
return (
<div
key={uniqueKey}
onClick={() => onColumnSelect(uniqueKey, column.columnLabel || column.columnName, column.tableName)}
onClick={() => onColumnSelect(uniqueKey, column.column_label || column.column_name, column.table_name)}
className={`cursor-pointer rounded-md px-3 py-1.5 transition-all ${
isSelected ? "bg-primary/10 font-semibold text-primary" : "hover:bg-muted/50"
}`}
@@ -497,9 +497,9 @@ export function CategoryColumnList({
<FolderTree
className={`h-3.5 w-3.5 ${isSelected ? "text-primary" : "text-muted-foreground"}`}
/>
<span className="flex-1 text-xs">{column.columnLabel || column.columnName}</span>
<span className="flex-1 text-xs">{column.column_label || column.column_name}</span>
<span className="text-muted-foreground text-[10px]">
{column.valueCount !== undefined ? `${column.valueCount}` : "..."}
{column.value_count !== undefined ? `${column.value_count}` : "..."}
</span>
</div>
</div>
@@ -237,8 +237,8 @@ export const V2TableListConfigPanel: React.FC<V2TableListConfigPanelProps> = ({
const [entityDisplayConfigs, setEntityDisplayConfigs] = useState<
Record<string, {
sourceColumns: Array<{ columnName: string; displayName: string; dataType: string }>;
joinColumns: Array<{ columnName: string; displayName: string; dataType: string }>;
sourceColumns: Array<{ column_name: string; display_name: string; data_type: string }>;
joinColumns: Array<{ column_name: string; display_name: string; data_type: string }>;
selectedColumns: string[];
separator: string;
}>
@@ -529,7 +529,7 @@ export const V2TableListConfigPanel: React.FC<V2TableListConfigPanelProps> = ({
const sourceResult = await entityJoinApi.getReferenceTableColumns(sourceTable);
const sourceColumns = sourceResult.columns || [];
let joinColumns: Array<{ columnName: string; displayName: string; dataType: string }> = [];
let joinColumns: Array<{ column_name: string; display_name: string; data_type: string }> = [];
if (joinTable) {
try {
const joinResult = await entityJoinApi.getReferenceTableColumns(joinTable);
@@ -1165,17 +1165,17 @@ export const V2TableListConfigPanel: React.FC<V2TableListConfigPanelProps> = ({
>
{entityDisplayConfigs[column.columnName].sourceColumns.map((col) => (
<CommandItem
key={`source-${col.columnName}`}
onSelect={() => toggleEntityDisplayColumn(column.columnName, col.columnName)}
key={`source-${col.column_name}`}
onSelect={() => toggleEntityDisplayColumn(column.columnName, col.column_name)}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-4 w-4",
entityDisplayConfigs[column.columnName].selectedColumns.includes(col.columnName) ? "opacity-100" : "opacity-0",
entityDisplayConfigs[column.columnName].selectedColumns.includes(col.column_name) ? "opacity-100" : "opacity-0",
)}
/>
{col.displayName}
{col.display_name}
</CommandItem>
))}
</CommandGroup>
@@ -1184,17 +1184,17 @@ export const V2TableListConfigPanel: React.FC<V2TableListConfigPanelProps> = ({
<CommandGroup heading={`참조 테이블: ${column.entityDisplayConfig?.joinTable}`}>
{entityDisplayConfigs[column.columnName].joinColumns.map((col) => (
<CommandItem
key={`join-${col.columnName}`}
onSelect={() => toggleEntityDisplayColumn(column.columnName, col.columnName)}
key={`join-${col.column_name}`}
onSelect={() => toggleEntityDisplayColumn(column.columnName, col.column_name)}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-4 w-4",
entityDisplayConfigs[column.columnName].selectedColumns.includes(col.columnName) ? "opacity-100" : "opacity-0",
entityDisplayConfigs[column.columnName].selectedColumns.includes(col.column_name) ? "opacity-100" : "opacity-0",
)}
/>
{col.displayName}
{col.display_name}
</CommandItem>
))}
</CommandGroup>
+37 -38
View File
@@ -15,7 +15,7 @@ export const queryKeys = {
tables: {
all: ["tables"] as const,
columns: (tableName: string) => ["tables", "columns", tableName] as const,
codeCategory: (tableName: string, columnName: string) => ["tables", "codeCategory", tableName, columnName] as const,
code_category: (tableName: string, columnName: string) => ["tables", "code_category", tableName, columnName] as const,
},
categories: {
all: ["categories"] as const,
@@ -26,15 +26,15 @@ export const queryKeys = {
// 테이블 컬럼의 코드 카테고리 조회
export function useTableCodeCategory(tableName?: string, columnName?: string) {
return useQuery({
queryKey: queryKeys.tables.codeCategory(tableName || "", columnName || ""),
queryKey: queryKeys.tables.code_category(tableName || "", columnName || ""),
queryFn: async () => {
if (!tableName || !columnName) return null;
const columns = await tableTypeApi.getColumns(tableName);
const targetColumn = columns.find((col) => col.columnName === columnName);
const targetColumn = columns.find((col) => col.column_name === columnName);
const codeCategory =
targetColumn?.codeCategory && targetColumn.codeCategory !== "none" ? targetColumn.codeCategory : null;
targetColumn?.code_category && targetColumn.code_category !== "none" ? targetColumn.code_category : null;
return codeCategory;
},
@@ -46,9 +46,9 @@ export function useTableCodeCategory(tableName?: string, columnName?: string) {
// 🆕 테이블 컬럼의 계층구조 설정 조회 (대분류/중분류/소분류)
interface ColumnHierarchyInfo {
hierarchyRole?: "large" | "medium" | "small";
hierarchyParentField?: string;
codeCategory?: string;
hierarchy_role?: "large" | "medium" | "small";
hierarchy_parent_field?: string;
code_category?: string;
}
export function useTableColumnHierarchy(tableName?: string, columnName?: string) {
@@ -58,59 +58,59 @@ export function useTableColumnHierarchy(tableName?: string, columnName?: string)
if (!tableName || !columnName) return null;
const columns = await tableTypeApi.getColumns(tableName);
const targetColumn = columns.find((col) => col.columnName === columnName);
const targetColumn = columns.find((col) => col.column_name === columnName);
if (!targetColumn) return null;
// detailSettings에서 hierarchyRole 추출
let hierarchyRole: ColumnHierarchyInfo["hierarchyRole"];
let hierarchyParentField: string | undefined;
// detail_settings에서 hierarchy_role 추출
let hierarchy_role: ColumnHierarchyInfo["hierarchy_role"];
let hierarchy_parent_field: string | undefined;
if (targetColumn.detailSettings) {
if (targetColumn.detail_settings) {
try {
const settings =
typeof targetColumn.detailSettings === "string"
? JSON.parse(targetColumn.detailSettings)
: targetColumn.detailSettings;
typeof targetColumn.detail_settings === "string"
? JSON.parse(targetColumn.detail_settings)
: targetColumn.detail_settings;
hierarchyRole = settings.hierarchyRole;
hierarchyParentField = settings.hierarchyParentField;
hierarchy_role = settings.hierarchy_role;
hierarchy_parent_field = settings.hierarchy_parent_field;
} catch {
// JSON 파싱 실패 시 무시
}
}
// hierarchyParentField가 없으면 같은 codeCategory를 가진 상위 역할 컬럼을 자동으로 찾음
if (hierarchyRole && !hierarchyParentField && targetColumn.codeCategory) {
// hierarchy_parent_field가 없으면 같은 code_category를 가진 상위 역할 컬럼을 자동으로 찾음
if (hierarchy_role && !hierarchy_parent_field && targetColumn.code_category) {
const roleOrder = { large: 0, medium: 1, small: 2 };
const currentOrder = roleOrder[hierarchyRole];
const currentOrder = roleOrder[hierarchy_role];
if (currentOrder > 0) {
// 같은 codeCategory를 가진 컬럼들 중에서 상위 역할을 찾음
// 같은 code_category를 가진 컬럼들 중에서 상위 역할을 찾음
const parentRole = currentOrder === 1 ? "large" : "medium";
const parentColumn = columns.find((col) => {
if (col.codeCategory !== targetColumn.codeCategory) return false;
if (col.code_category !== targetColumn.code_category) return false;
try {
const colSettings =
typeof col.detailSettings === "string" ? JSON.parse(col.detailSettings) : col.detailSettings;
return colSettings?.hierarchyRole === parentRole;
typeof col.detail_settings === "string" ? JSON.parse(col.detail_settings) : col.detail_settings;
return colSettings?.hierarchy_role === parentRole;
} catch {
return false;
}
});
if (parentColumn) {
hierarchyParentField = parentColumn.columnName;
hierarchy_parent_field = parentColumn.column_name;
}
}
}
return {
hierarchyRole,
hierarchyParentField,
codeCategory: targetColumn.codeCategory,
hierarchy_role,
hierarchy_parent_field,
code_category: targetColumn.code_category,
};
},
enabled: !!(tableName && columnName),
@@ -135,9 +135,8 @@ export function useCodeOptions(codeCategory?: string, enabled: boolean = true, m
if (response.success && response.data) {
const options = response.data.map((code: any) => {
const actualValue = code.code || code.CODE || code.value || code.code_value || code.codeValue;
const actualValue = code.code || code.CODE || code.value || code.code_value;
const actualLabel =
code.codeName ||
code.code_name ||
code.name ||
code.CODE_NAME ||
@@ -151,7 +150,7 @@ export function useCodeOptions(codeCategory?: string, enabled: boolean = true, m
// 계층구조 정보 포함
const depth = code.depth ?? 1;
const parentCodeValue = code.parentCodeValue || code.parent_code_value || null;
const parentCodeValue = code.parent_code_value || null;
return {
value: actualValue,
@@ -207,10 +206,10 @@ export function useUpdateCode() {
return useMutation({
mutationFn: ({ categoryCode, codeValue, data }: { categoryCode: string; codeValue: string; data: any }) =>
commonCodeApi.codes.update(categoryCode, codeValue, data),
onSuccess: (_, variables) => {
onSuccess: (_, { categoryCode, codeValue }) => {
// 해당 코드 상세 쿼리 무효화
queryClient.invalidateQueries({
queryKey: queryKeys.codes.detail(variables.categoryCode, variables.codeValue),
queryKey: queryKeys.codes.detail(categoryCode, codeValue),
});
// 해당 카테고리의 모든 코드 관련 쿼리 무효화
queryClient.invalidateQueries({
@@ -218,7 +217,7 @@ export function useUpdateCode() {
});
// 무한 스크롤 쿼리도 명시적으로 무효화
queryClient.invalidateQueries({
queryKey: queryKeys.codes.infiniteList(variables.categoryCode),
queryKey: queryKeys.codes.infiniteList(categoryCode),
});
},
});
@@ -231,17 +230,17 @@ export function useDeleteCode() {
return useMutation({
mutationFn: ({ categoryCode, codeValue }: { categoryCode: string; codeValue: string }) =>
commonCodeApi.codes.delete(categoryCode, codeValue),
onSuccess: (_, variables) => {
onSuccess: (_, { categoryCode, codeValue }) => {
// 해당 코드 관련 쿼리 무효화 및 캐시 제거
queryClient.invalidateQueries({
queryKey: queryKeys.codes.all,
});
// 무한 스크롤 쿼리도 명시적으로 무효화
queryClient.invalidateQueries({
queryKey: queryKeys.codes.infiniteList(variables.categoryCode),
queryKey: queryKeys.codes.infiniteList(categoryCode),
});
queryClient.removeQueries({
queryKey: queryKeys.codes.detail(variables.categoryCode, variables.codeValue),
queryKey: queryKeys.codes.detail(categoryCode, codeValue),
});
},
});
@@ -272,7 +271,7 @@ export function useReorderCodes() {
// 기존 데이터를 복사하고 sort_order만 업데이트
const updatedCodes = [...previousCodesArray].map((code: any) => {
const newCodeData = codes.find((c) => c.codeValue === code.code_value);
const newCodeData = codes.find(({ codeValue }) => codeValue === code.code_value);
return newCodeData ? { ...code, sort_order: newCodeData.sortOrder } : code;
});
+4 -3
View File
@@ -26,9 +26,10 @@ export interface EntityJoinResponse {
}
export interface ReferenceTableColumn {
columnName: string;
displayName: string;
dataType: string;
column_name: string;
display_name: string;
data_type: string;
input_type?: string;
}
export interface CacheStatus {
@@ -52,9 +52,9 @@ interface ColumnInfo {
// 참조 테이블 컬럼 정보
interface ReferenceColumnInfo {
columnName: string;
displayName: string;
dataType: string;
column_name: string;
display_name: string;
data_type: string;
}
interface ColumnConfigModalProps {
@@ -736,26 +736,26 @@ export const ColumnConfigModal: React.FC<ColumnConfigModalProps> = ({
<ScrollArea className="max-h-40">
<div className="space-y-2 pr-4">
{entityInfo.referenceColumns.map((col) => {
const isSelected = (editingColumn.entityReference?.displayColumns || []).includes(col.columnName);
const isSelected = (editingColumn.entityReference?.displayColumns || []).includes(col.column_name);
return (
<div
key={col.columnName}
<div
key={col.column_name}
className={cn(
"flex items-center gap-2 p-2 rounded-md cursor-pointer hover:bg-muted/50 transition-colors",
isSelected && "bg-muted"
)}
onClick={() => toggleEntityDisplayColumn(col.columnName)}
onClick={() => toggleEntityDisplayColumn(col.column_name)}
>
<Checkbox
checked={isSelected}
onCheckedChange={() => toggleEntityDisplayColumn(col.columnName)}
onCheckedChange={() => toggleEntityDisplayColumn(col.column_name)}
/>
<div className="flex-1 min-w-0">
<span className="text-sm font-medium truncate block">
{col.displayName || col.columnName}
{col.display_name || col.column_name}
</span>
<span className="text-xs text-muted-foreground truncate block">
{col.columnName} ({col.dataType})
{col.column_name} ({col.data_type})
</span>
</div>
</div>
@@ -772,13 +772,13 @@ export const ColumnConfigModal: React.FC<ColumnConfigModalProps> = ({
</p>
<div className="flex flex-wrap gap-1">
{(editingColumn.entityReference?.displayColumns || []).map((colName) => {
const colInfo = entityInfo.referenceColumns.find(c => c.columnName === colName);
const colInfo = entityInfo.referenceColumns.find(c => c.column_name === colName);
return (
<span
key={colName}
className="inline-flex items-center px-2 py-0.5 rounded-full text-xs bg-primary/10 text-primary"
>
{colInfo?.displayName || colName}
{colInfo?.display_name || colName}
</span>
);
})}
@@ -159,8 +159,8 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
Record<
string,
{
sourceColumns: Array<{ columnName: string; displayName: string; dataType: string }>;
joinColumns: Array<{ columnName: string; displayName: string; dataType: string }>;
sourceColumns: Array<{ column_name: string; display_name: string; data_type: string }>;
joinColumns: Array<{ column_name: string; display_name: string; data_type: string }>;
selectedColumns: string[];
separator: string;
}
@@ -691,7 +691,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
const sourceColumns = sourceResult.columns || [];
// joinTable이 있으면 조인 테이블 컬럼도 로드
let joinColumns: Array<{ columnName: string; displayName: string; dataType: string }> = [];
let joinColumns: Array<{ column_name: string; display_name: string; data_type: string }> = [];
if (joinTable) {
try {
const joinResult = await entityJoinApi.getReferenceTableColumns(joinTable);
@@ -1049,21 +1049,21 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
>
{entityDisplayConfigs[column.columnName].sourceColumns.map((col) => (
<CommandItem
key={`source-${col.columnName}`}
onSelect={() => toggleEntityDisplayColumn(column.columnName, col.columnName)}
key={`source-${col.column_name}`}
onSelect={() => toggleEntityDisplayColumn(column.columnName, col.column_name)}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-4 w-4",
entityDisplayConfigs[column.columnName].selectedColumns.includes(
col.columnName,
col.column_name,
)
? "opacity-100"
: "opacity-0",
)}
/>
{col.displayName}
{col.display_name}
</CommandItem>
))}
</CommandGroup>
@@ -1072,21 +1072,21 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
<CommandGroup heading={`참조 테이블: ${column.entityDisplayConfig?.joinTable}`}>
{entityDisplayConfigs[column.columnName].joinColumns.map((col) => (
<CommandItem
key={`join-${col.columnName}`}
onSelect={() => toggleEntityDisplayColumn(column.columnName, col.columnName)}
key={`join-${col.column_name}`}
onSelect={() => toggleEntityDisplayColumn(column.columnName, col.column_name)}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-4 w-4",
entityDisplayConfigs[column.columnName].selectedColumns.includes(
col.columnName,
col.column_name,
)
? "opacity-100"
: "opacity-0",
)}
/>
{col.displayName}
{col.display_name}
</CommandItem>
))}
</CommandGroup>
@@ -58,7 +58,7 @@ export function V2CategoryManagerComponent({
const handleColumnsLoaded = useCallback((loaded: CategoryColumn[]) => {
setColumns(loaded);
if (loaded.length > 0) {
setSelectedTable((prev) => prev ?? loaded[0].tableName);
setSelectedTable((prev) => prev ?? loaded[0].table_name);
}
}, []);
@@ -77,14 +77,14 @@ export function V2CategoryManagerComponent({
const stats = useMemo(() => {
const columnCount = columns.length;
const totalValues = columns.reduce((sum, c) => sum + (c.valueCount ?? 0), 0);
const tableCount = new Set(columns.map((c) => c.tableName)).size;
const totalValues = columns.reduce((sum, c) => sum + (c.value_count ?? 0), 0);
const tableCount = new Set(columns.map((c) => c.table_name)).size;
const inactiveCount = 0;
return { columnCount, totalValues, tableCount, inactiveCount };
}, [columns]);
const columnsForSelectedTable = useMemo(
() => (selectedTable ? columns.filter((c) => c.tableName === selectedTable) : []),
() => (selectedTable ? columns.filter((c) => c.table_name === selectedTable) : []),
[columns, selectedTable],
);
@@ -219,14 +219,14 @@ export function V2CategoryManagerComponent({
{/* 칩 바 */}
<div className="flex flex-wrap gap-1.5 border-b bg-background px-4 py-3">
{columnsForSelectedTable.map((col) => {
const uniqueKey = `${col.tableName}.${col.columnName}`;
const uniqueKey = `${col.table_name}.${col.column_name}`;
const isActive = selectedColumn?.uniqueKey === uniqueKey;
return (
<button
key={uniqueKey}
type="button"
onClick={() =>
handleColumnSelect(uniqueKey, col.columnLabel || col.columnName, col.tableName)
handleColumnSelect(uniqueKey, col.column_label || col.column_name, col.table_name)
}
className={cn(
"inline-flex items-center gap-1.5 rounded-full border px-[10px] py-[5px] text-[11px] font-semibold transition-colors",
@@ -235,7 +235,7 @@ export function V2CategoryManagerComponent({
: "border-border bg-muted/50 hover:border-primary hover:bg-primary/5 hover:text-primary",
)}
>
<span>{col.columnLabel || col.columnName}</span>
<span>{col.column_label || col.column_name}</span>
<Badge
variant="secondary"
className={cn(
@@ -243,7 +243,7 @@ export function V2CategoryManagerComponent({
isActive ? "bg-primary/15 text-primary" : "bg-muted text-muted-foreground",
)}
>
{col.valueCount ?? 0}
{col.value_count ?? 0}
</Badge>
</button>
);
@@ -176,8 +176,8 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
Record<
string,
{
sourceColumns: Array<{ columnName: string; displayName: string; dataType: string }>;
joinColumns: Array<{ columnName: string; displayName: string; dataType: string }>;
sourceColumns: Array<{ column_name: string; display_name: string; data_type: string }>;
joinColumns: Array<{ column_name: string; display_name: string; data_type: string }>;
selectedColumns: string[];
separator: string;
}
@@ -711,7 +711,7 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
const sourceColumns = sourceResult.columns || [];
// joinTable이 있으면 조인 테이블 컬럼도 로드
let joinColumns: Array<{ columnName: string; displayName: string; dataType: string }> = [];
let joinColumns: Array<{ column_name: string; display_name: string; data_type: string }> = [];
if (joinTable) {
try {
const joinResult = await entityJoinApi.getReferenceTableColumns(joinTable);
@@ -1248,21 +1248,21 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
>
{entityDisplayConfigs[column.columnName].sourceColumns.map((col) => (
<CommandItem
key={`source-${col.columnName}`}
onSelect={() => toggleEntityDisplayColumn(column.columnName, col.columnName)}
key={`source-${col.column_name}`}
onSelect={() => toggleEntityDisplayColumn(column.columnName, col.column_name)}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-4 w-4",
entityDisplayConfigs[column.columnName].selectedColumns.includes(
col.columnName,
col.column_name,
)
? "opacity-100"
: "opacity-0",
)}
/>
{col.displayName}
{col.display_name}
</CommandItem>
))}
</CommandGroup>
@@ -1271,21 +1271,21 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
<CommandGroup heading={`참조 테이블: ${column.entityDisplayConfig?.joinTable}`}>
{entityDisplayConfigs[column.columnName].joinColumns.map((col) => (
<CommandItem
key={`join-${col.columnName}`}
onSelect={() => toggleEntityDisplayColumn(column.columnName, col.columnName)}
key={`join-${col.column_name}`}
onSelect={() => toggleEntityDisplayColumn(column.columnName, col.column_name)}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-4 w-4",
entityDisplayConfigs[column.columnName].selectedColumns.includes(
col.columnName,
col.column_name,
)
? "opacity-100"
: "opacity-0",
)}
/>
{col.displayName}
{col.display_name}
</CommandItem>
))}
</CommandGroup>