:Qrge branch 'jskim-node' of http://39.117.244.52:3000/kjs/ERP-node into jskim-node
This commit is contained in:
parent
5abce62d89
commit
6d2cdc1782
|
|
@ -1046,6 +1046,7 @@
|
||||||
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
|
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.27.1",
|
"@babel/code-frame": "^7.27.1",
|
||||||
"@babel/generator": "^7.28.3",
|
"@babel/generator": "^7.28.3",
|
||||||
|
|
@ -2373,6 +2374,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz",
|
||||||
"integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==",
|
"integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cluster-key-slot": "1.1.2",
|
"cluster-key-slot": "1.1.2",
|
||||||
"generic-pool": "3.9.0",
|
"generic-pool": "3.9.0",
|
||||||
|
|
@ -3485,6 +3487,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.17.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.17.tgz",
|
||||||
"integrity": "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==",
|
"integrity": "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
|
|
@ -3721,6 +3724,7 @@
|
||||||
"integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
|
"integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "6.21.0",
|
"@typescript-eslint/scope-manager": "6.21.0",
|
||||||
"@typescript-eslint/types": "6.21.0",
|
"@typescript-eslint/types": "6.21.0",
|
||||||
|
|
@ -3938,6 +3942,7 @@
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
|
|
@ -4463,6 +4468,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.8.3",
|
"baseline-browser-mapping": "^2.8.3",
|
||||||
"caniuse-lite": "^1.0.30001741",
|
"caniuse-lite": "^1.0.30001741",
|
||||||
|
|
@ -5673,6 +5679,7 @@
|
||||||
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
|
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.6.1",
|
"@eslint-community/regexpp": "^4.6.1",
|
||||||
|
|
@ -5951,6 +5958,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||||
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "~1.3.8",
|
"accepts": "~1.3.8",
|
||||||
"array-flatten": "1.1.1",
|
"array-flatten": "1.1.1",
|
||||||
|
|
@ -7486,6 +7494,7 @@
|
||||||
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
|
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jest/core": "^29.7.0",
|
"@jest/core": "^29.7.0",
|
||||||
"@jest/types": "^29.6.3",
|
"@jest/types": "^29.6.3",
|
||||||
|
|
@ -8455,7 +8464,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -9343,6 +9351,7 @@
|
||||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
|
||||||
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
|
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pg-connection-string": "^2.9.1",
|
"pg-connection-string": "^2.9.1",
|
||||||
"pg-pool": "^3.10.1",
|
"pg-pool": "^3.10.1",
|
||||||
|
|
@ -10198,7 +10207,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
}
|
}
|
||||||
|
|
@ -11006,6 +11014,7 @@
|
||||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cspotcode/source-map-support": "^0.8.0",
|
"@cspotcode/source-map-support": "^0.8.0",
|
||||||
"@tsconfig/node10": "^1.0.7",
|
"@tsconfig/node10": "^1.0.7",
|
||||||
|
|
@ -11111,6 +11120,7 @@
|
||||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|
|
||||||
|
|
@ -181,20 +181,92 @@ export async function getDistinctColumnValues(req: AuthenticatedRequest, res: Re
|
||||||
? `WHERE ${whereConditions.join(" AND ")}`
|
? `WHERE ${whereConditions.join(" AND ")}`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
// DISTINCT 쿼리 실행
|
// 1단계: DISTINCT 값 조회
|
||||||
const query = `
|
const distinctQuery = `
|
||||||
SELECT DISTINCT "${columnName}" as value, "${effectiveLabelColumn}" as label
|
SELECT DISTINCT "${columnName}" as value, "${effectiveLabelColumn}" as label
|
||||||
FROM "${tableName}"
|
FROM "${tableName}"
|
||||||
${whereClause}
|
${whereClause}
|
||||||
ORDER BY "${effectiveLabelColumn}" ASC
|
ORDER BY "${effectiveLabelColumn}" ASC
|
||||||
LIMIT 500
|
LIMIT 500
|
||||||
`;
|
`;
|
||||||
|
const result = await pool.query(distinctQuery, params);
|
||||||
|
|
||||||
const result = await pool.query(query, params);
|
// 2단계: 카테고리/코드 라벨 변환 (값이 있을 때만)
|
||||||
|
if (result.rows.length > 0) {
|
||||||
|
const rawValues = result.rows.map((r: any) => r.value);
|
||||||
|
const labelMap: Record<string, string> = {};
|
||||||
|
|
||||||
|
// category_values에서 라벨 조회
|
||||||
|
try {
|
||||||
|
const cvCompanyCondition = companyCode !== "*"
|
||||||
|
? `AND (company_code = $4 OR company_code = '*')`
|
||||||
|
: "";
|
||||||
|
const cvParams = companyCode !== "*"
|
||||||
|
? [tableName, columnName, rawValues, companyCode]
|
||||||
|
: [tableName, columnName, rawValues];
|
||||||
|
|
||||||
|
const cvResult = await pool.query(
|
||||||
|
`SELECT value_code, value_label FROM category_values
|
||||||
|
WHERE table_name = $1 AND column_name = $2
|
||||||
|
AND value_code = ANY($3) AND is_active = true
|
||||||
|
${cvCompanyCondition}`,
|
||||||
|
cvParams
|
||||||
|
);
|
||||||
|
cvResult.rows.forEach((r: any) => {
|
||||||
|
labelMap[r.value_code] = r.value_label;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// category_values 조회 실패 시 무시
|
||||||
|
}
|
||||||
|
|
||||||
|
// code_info에서 라벨 조회 (code_category 기반)
|
||||||
|
try {
|
||||||
|
const ttcResult = await pool.query(
|
||||||
|
`SELECT code_category FROM table_type_columns
|
||||||
|
WHERE table_name = $1 AND column_name = $2 AND code_category IS NOT NULL
|
||||||
|
LIMIT 1`,
|
||||||
|
[tableName, columnName]
|
||||||
|
);
|
||||||
|
const codeCategory = ttcResult.rows[0]?.code_category;
|
||||||
|
|
||||||
|
if (codeCategory) {
|
||||||
|
const ciCompanyCondition = companyCode !== "*"
|
||||||
|
? `AND (company_code = $3 OR company_code = '*')`
|
||||||
|
: "";
|
||||||
|
const ciParams = companyCode !== "*"
|
||||||
|
? [codeCategory, rawValues, companyCode]
|
||||||
|
: [codeCategory, rawValues];
|
||||||
|
|
||||||
|
const ciResult = await pool.query(
|
||||||
|
`SELECT code_value, code_name FROM code_info
|
||||||
|
WHERE code_category = $1 AND code_value = ANY($2) AND is_active = 'Y'
|
||||||
|
${ciCompanyCondition}`,
|
||||||
|
ciParams
|
||||||
|
);
|
||||||
|
ciResult.rows.forEach((r: any) => {
|
||||||
|
if (!labelMap[r.code_value]) {
|
||||||
|
labelMap[r.code_value] = r.code_name;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// code_info 조회 실패 시 무시
|
||||||
|
}
|
||||||
|
|
||||||
|
// 라벨 매핑 적용
|
||||||
|
if (Object.keys(labelMap).length > 0) {
|
||||||
|
result.rows.forEach((row: any) => {
|
||||||
|
if (labelMap[row.value]) {
|
||||||
|
row.label = labelMap[row.value];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.info("컬럼 DISTINCT 값 조회 성공", {
|
logger.info("컬럼 DISTINCT 값 조회 성공", {
|
||||||
tableName,
|
tableName,
|
||||||
columnName,
|
columnName,
|
||||||
|
columnInputType: columnInputType || "none",
|
||||||
labelColumn: effectiveLabelColumn,
|
labelColumn: effectiveLabelColumn,
|
||||||
companyCode,
|
companyCode,
|
||||||
hasFilters: !!filtersParam,
|
hasFilters: !!filtersParam,
|
||||||
|
|
|
||||||
|
|
@ -1341,56 +1341,79 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
const getLeftColumnUniqueValues = useCallback(
|
const getLeftColumnUniqueValues = useCallback(
|
||||||
async (columnName: string) => {
|
async (columnName: string) => {
|
||||||
const leftTableName = componentConfig.leftPanel?.tableName;
|
const leftTableName = componentConfig.leftPanel?.tableName;
|
||||||
if (!leftTableName || leftData.length === 0) return [];
|
if (!leftTableName) return [];
|
||||||
|
|
||||||
// 현재 로드된 데이터에서 고유값 추출
|
// 1단계: 카테고리 API 시도 (DB에서 라벨 조회)
|
||||||
const uniqueValues = new Set<string>();
|
try {
|
||||||
|
const { apiClient } = await import("@/lib/api/client");
|
||||||
|
const response = await apiClient.get(`/table-categories/${leftTableName}/${columnName}/values`);
|
||||||
|
if (response.data.success && response.data.data && response.data.data.length > 0) {
|
||||||
|
return response.data.data.map((item: any) => ({
|
||||||
|
value: item.valueCode,
|
||||||
|
label: item.valueLabel,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 카테고리 API 실패 시 다음 단계로
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2단계: DISTINCT API (백엔드 라벨 변환 포함)
|
||||||
|
try {
|
||||||
|
const { apiClient } = await import("@/lib/api/client");
|
||||||
|
const response = await apiClient.get(`/entity/${leftTableName}/distinct/${columnName}`);
|
||||||
|
if (response.data.success && response.data.data && response.data.data.length > 0) {
|
||||||
|
return response.data.data.map((item: any) => ({
|
||||||
|
value: String(item.value),
|
||||||
|
label: String(item.label),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// DISTINCT API 실패 시 다음 단계로
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3단계: 로컬 데이터에서 고유값 추출 (최종 fallback)
|
||||||
|
if (leftData.length === 0) return [];
|
||||||
|
|
||||||
|
const uniqueValuesMap = new Map<string, string>();
|
||||||
|
|
||||||
leftData.forEach((item) => {
|
leftData.forEach((item) => {
|
||||||
// 🆕 조인 컬럼 처리 (item_info.standard → item_code_standard 또는 item_id_standard)
|
|
||||||
let value: any;
|
let value: any;
|
||||||
|
|
||||||
if (columnName.includes(".")) {
|
if (columnName.includes(".")) {
|
||||||
// 조인 컬럼: getEntityJoinValue와 동일한 로직 적용
|
|
||||||
const [refTable, fieldName] = columnName.split(".");
|
const [refTable, fieldName] = columnName.split(".");
|
||||||
const inferredSourceColumn = refTable.replace("_info", "_code").replace("_mng", "_id");
|
const inferredSourceColumn = refTable.replace("_info", "_code").replace("_mng", "_id");
|
||||||
|
|
||||||
// 정확한 키로 먼저 시도
|
|
||||||
const exactKey = `${inferredSourceColumn}_${fieldName}`;
|
const exactKey = `${inferredSourceColumn}_${fieldName}`;
|
||||||
value = item[exactKey];
|
value = item[exactKey];
|
||||||
|
|
||||||
// 🆕 item_id 패턴 시도
|
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
const idPatternKey = `${refTable.replace("_info", "_id").replace("_mng", "_id")}_${fieldName}`;
|
const idPatternKey = `${refTable.replace("_info", "_id").replace("_mng", "_id")}_${fieldName}`;
|
||||||
value = item[idPatternKey];
|
value = item[idPatternKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 기본 별칭 패턴 시도 (item_code_name 또는 item_id_name)
|
|
||||||
if (value === undefined && (fieldName === "item_name" || fieldName === "name")) {
|
if (value === undefined && (fieldName === "item_name" || fieldName === "name")) {
|
||||||
const aliasKey = `${inferredSourceColumn}_name`;
|
const aliasKey = `${inferredSourceColumn}_name`;
|
||||||
value = item[aliasKey];
|
value = item[aliasKey];
|
||||||
// item_id_name 패턴도 시도
|
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
const idAliasKey = `${refTable.replace("_info", "_id").replace("_mng", "_id")}_name`;
|
const idAliasKey = `${refTable.replace("_info", "_id").replace("_mng", "_id")}_name`;
|
||||||
value = item[idAliasKey];
|
value = item[idAliasKey];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 일반 컬럼
|
|
||||||
value = item[columnName];
|
value = item[columnName];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value !== null && value !== undefined && value !== "") {
|
if (value !== null && value !== undefined && value !== "") {
|
||||||
// _name 필드 우선 사용 (category/entity type)
|
const strValue = String(value);
|
||||||
const displayValue = item[`${columnName}_name`] || value;
|
const nameField = item[`${columnName}_name`];
|
||||||
uniqueValues.add(String(displayValue));
|
const label = nameField || strValue;
|
||||||
|
uniqueValuesMap.set(strValue, label);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return Array.from(uniqueValues).map((value) => ({
|
return Array.from(uniqueValuesMap.entries())
|
||||||
value: value,
|
.map(([value, label]) => ({ value, label }))
|
||||||
label: value,
|
.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
[componentConfig.leftPanel?.tableName, leftData],
|
[componentConfig.leftPanel?.tableName, leftData],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -919,66 +919,63 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
|
|
||||||
// 컬럼의 고유 값 조회 함수
|
// 컬럼의 고유 값 조회 함수
|
||||||
const getColumnUniqueValues = async (columnName: string) => {
|
const getColumnUniqueValues = async (columnName: string) => {
|
||||||
const meta = columnMeta[columnName];
|
|
||||||
const inputType = meta?.inputType || "text";
|
|
||||||
|
|
||||||
// 카테고리 타입인 경우 전체 정의된 값 조회 (백엔드 API)
|
|
||||||
if (inputType === "category") {
|
|
||||||
try {
|
|
||||||
// API 클라이언트 사용 (쿠키 인증 자동 처리)
|
|
||||||
const { apiClient } = await import("@/lib/api/client");
|
const { apiClient } = await import("@/lib/api/client");
|
||||||
|
|
||||||
|
// 1단계: 카테고리 API 시도 (columnMeta 무관하게 항상 시도)
|
||||||
|
try {
|
||||||
const response = await apiClient.get(`/table-categories/${tableConfig.selectedTable}/${columnName}/values`);
|
const response = await apiClient.get(`/table-categories/${tableConfig.selectedTable}/${columnName}/values`);
|
||||||
|
|
||||||
if (response.data.success && response.data.data) {
|
|
||||||
const categoryOptions = response.data.data.map((item: any) => ({
|
|
||||||
value: item.valueCode, // 카멜케이스
|
|
||||||
label: item.valueLabel, // 카멜케이스
|
|
||||||
}));
|
|
||||||
|
|
||||||
return categoryOptions;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// 에러 시 현재 데이터 기반으로 fallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 백엔드 DISTINCT API로 전체 고유값 조회 (페이징과 무관하게 모든 값 반환)
|
|
||||||
try {
|
|
||||||
const { apiClient } = await import("@/lib/api/client");
|
|
||||||
const response = await apiClient.get(`/entity/${tableConfig.selectedTable}/distinct/${columnName}`);
|
|
||||||
|
|
||||||
if (response.data.success && response.data.data && response.data.data.length > 0) {
|
if (response.data.success && response.data.data && response.data.data.length > 0) {
|
||||||
return response.data.data.map((item: any) => ({
|
return response.data.data.map((item: any) => ({
|
||||||
|
value: item.valueCode,
|
||||||
|
label: item.valueLabel,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 카테고리 API 실패 시 다음 단계로
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2단계: DISTINCT API (백엔드에서 category_values/code_info 라벨 변환 포함)
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get(`/entity/${tableConfig.selectedTable}/distinct/${columnName}`);
|
||||||
|
if (response.data.success && response.data.data && response.data.data.length > 0) {
|
||||||
|
let options = response.data.data.map((item: any) => ({
|
||||||
value: String(item.value),
|
value: String(item.value),
|
||||||
label: String(item.label),
|
label: String(item.label),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// 프론트엔드 카테고리 매핑으로 추가 라벨 변환
|
||||||
|
const mapping = categoryMappings[columnName];
|
||||||
|
if (mapping && Object.keys(mapping).length > 0) {
|
||||||
|
options = options.map((opt) => ({
|
||||||
|
value: opt.value,
|
||||||
|
label: mapping[opt.value]?.label || opt.label,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// DISTINCT API 실패 시 현재 데이터 기반으로 fallback
|
// DISTINCT API 실패 시 다음 단계로
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallback: 현재 로드된 데이터에서 고유 값 추출
|
// 3단계: 현재 로드된 데이터에서 고유 값 추출 (최종 fallback)
|
||||||
const isLabelType = ["category", "entity", "code"].includes(inputType);
|
|
||||||
const labelField = isLabelType ? `${columnName}_name` : columnName;
|
|
||||||
|
|
||||||
const uniqueValuesMap = new Map<string, string>();
|
const uniqueValuesMap = new Map<string, string>();
|
||||||
|
const mapping = categoryMappings[columnName];
|
||||||
|
|
||||||
data.forEach((row) => {
|
data.forEach((row) => {
|
||||||
const value = row[columnName];
|
const value = row[columnName];
|
||||||
if (value !== null && value !== undefined && value !== "") {
|
if (value !== null && value !== undefined && value !== "") {
|
||||||
const label = isLabelType && row[labelField] ? row[labelField] : String(value);
|
const strValue = String(value);
|
||||||
uniqueValuesMap.set(String(value), label);
|
const nameField = row[`${columnName}_name`];
|
||||||
|
const mappedLabel = mapping?.[strValue]?.label;
|
||||||
|
const label = mappedLabel || nameField || strValue;
|
||||||
|
uniqueValuesMap.set(strValue, label);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = Array.from(uniqueValuesMap.entries())
|
return Array.from(uniqueValuesMap.entries())
|
||||||
.map(([value, label]) => ({
|
.map(([value, label]) => ({ value, label }))
|
||||||
value: value,
|
|
||||||
label: label,
|
|
||||||
}))
|
|
||||||
.sort((a, b) => a.label.localeCompare(b.label));
|
.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const registration = {
|
const registration = {
|
||||||
|
|
@ -1031,6 +1028,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
tableConfig.columns,
|
tableConfig.columns,
|
||||||
columnLabels,
|
columnLabels,
|
||||||
columnMeta, // columnMeta가 변경되면 재등록 (inputType 정보 필요)
|
columnMeta, // columnMeta가 변경되면 재등록 (inputType 정보 필요)
|
||||||
|
categoryMappings, // 카테고리 매핑 변경 시 재등록 (필터 라벨 변환용)
|
||||||
columnWidths,
|
columnWidths,
|
||||||
tableLabel,
|
tableLabel,
|
||||||
data, // 데이터 자체가 변경되면 재등록 (고유 값 조회용)
|
data, // 데이터 자체가 변경되면 재등록 (고유 값 조회용)
|
||||||
|
|
|
||||||
|
|
@ -393,12 +393,14 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [currentTable?.tableName, filterMode, screenId, currentTableTabId, JSON.stringify(presetFilters)]);
|
}, [currentTable?.tableName, filterMode, screenId, currentTableTabId, JSON.stringify(presetFilters)]);
|
||||||
|
|
||||||
// select 옵션 초기 로드 (한 번만 실행, 이후 유지)
|
// select 옵션 로드 (getColumnUniqueValues 변경 시 재로드 - columnMeta 갱신 반영)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!currentTable?.getColumnUniqueValues || activeFilters.length === 0) {
|
if (!currentTable?.getColumnUniqueValues || activeFilters.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cancelled = false;
|
||||||
|
|
||||||
const loadSelectOptions = async () => {
|
const loadSelectOptions = async () => {
|
||||||
const selectFilters = activeFilters.filter((f) => f.filterType === "select");
|
const selectFilters = activeFilters.filter((f) => f.filterType === "select");
|
||||||
|
|
||||||
|
|
@ -406,26 +408,28 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newOptions: Record<string, Array<{ label: string; value: string }>> = { ...selectOptions };
|
const newOptions: Record<string, Array<{ label: string; value: string }>> = {};
|
||||||
|
|
||||||
for (const filter of selectFilters) {
|
for (const filter of selectFilters) {
|
||||||
// 이미 로드된 옵션이 있으면 스킵 (초기값 유지)
|
|
||||||
if (newOptions[filter.columnName] && newOptions[filter.columnName].length > 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const options = await currentTable.getColumnUniqueValues(filter.columnName);
|
const options = await currentTable.getColumnUniqueValues(filter.columnName);
|
||||||
|
if (options && options.length > 0) {
|
||||||
newOptions[filter.columnName] = options;
|
newOptions[filter.columnName] = options;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ [TableSearchWidget] select 옵션 로드 실패:", filter.columnName, error);
|
console.error("❌ [TableSearchWidget] select 옵션 로드 실패:", filter.columnName, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setSelectOptions(newOptions);
|
|
||||||
|
if (!cancelled && Object.keys(newOptions).length > 0) {
|
||||||
|
setSelectOptions((prev) => ({ ...prev, ...newOptions }));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadSelectOptions();
|
loadSelectOptions();
|
||||||
}, [activeFilters, currentTable?.tableName, currentTable?.getColumnUniqueValues]); // dataCount 제거, tableName으로 변경
|
|
||||||
|
return () => { cancelled = true; };
|
||||||
|
}, [activeFilters, currentTable?.tableName, currentTable?.getColumnUniqueValues]);
|
||||||
|
|
||||||
// 높이 변화 감지 및 알림 (실제 화면에서만)
|
// 높이 변화 감지 및 알림 (실제 화면에서만)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -1683,56 +1683,79 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
const getLeftColumnUniqueValues = useCallback(
|
const getLeftColumnUniqueValues = useCallback(
|
||||||
async (columnName: string) => {
|
async (columnName: string) => {
|
||||||
const leftTableName = componentConfig.leftPanel?.tableName;
|
const leftTableName = componentConfig.leftPanel?.tableName;
|
||||||
if (!leftTableName || leftData.length === 0) return [];
|
if (!leftTableName) return [];
|
||||||
|
|
||||||
// 현재 로드된 데이터에서 고유값 추출
|
// 1단계: 카테고리 API 시도 (DB에서 라벨 조회)
|
||||||
const uniqueValues = new Set<string>();
|
try {
|
||||||
|
const { apiClient } = await import("@/lib/api/client");
|
||||||
|
const response = await apiClient.get(`/table-categories/${leftTableName}/${columnName}/values`);
|
||||||
|
if (response.data.success && response.data.data && response.data.data.length > 0) {
|
||||||
|
return response.data.data.map((item: any) => ({
|
||||||
|
value: item.valueCode,
|
||||||
|
label: item.valueLabel,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 카테고리 API 실패 시 다음 단계로
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2단계: DISTINCT API (백엔드 라벨 변환 포함)
|
||||||
|
try {
|
||||||
|
const { apiClient } = await import("@/lib/api/client");
|
||||||
|
const response = await apiClient.get(`/entity/${leftTableName}/distinct/${columnName}`);
|
||||||
|
if (response.data.success && response.data.data && response.data.data.length > 0) {
|
||||||
|
return response.data.data.map((item: any) => ({
|
||||||
|
value: String(item.value),
|
||||||
|
label: String(item.label),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// DISTINCT API 실패 시 다음 단계로
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3단계: 로컬 데이터에서 고유값 추출 (최종 fallback)
|
||||||
|
if (leftData.length === 0) return [];
|
||||||
|
|
||||||
|
const uniqueValuesMap = new Map<string, string>();
|
||||||
|
|
||||||
leftData.forEach((item) => {
|
leftData.forEach((item) => {
|
||||||
// 🆕 조인 컬럼 처리 (item_info.standard → item_code_standard 또는 item_id_standard)
|
|
||||||
let value: any;
|
let value: any;
|
||||||
|
|
||||||
if (columnName.includes(".")) {
|
if (columnName.includes(".")) {
|
||||||
// 조인 컬럼: getEntityJoinValue와 동일한 로직 적용
|
|
||||||
const [refTable, fieldName] = columnName.split(".");
|
const [refTable, fieldName] = columnName.split(".");
|
||||||
const inferredSourceColumn = refTable.replace("_info", "_code").replace("_mng", "_id");
|
const inferredSourceColumn = refTable.replace("_info", "_code").replace("_mng", "_id");
|
||||||
|
|
||||||
// 정확한 키로 먼저 시도
|
|
||||||
const exactKey = `${inferredSourceColumn}_${fieldName}`;
|
const exactKey = `${inferredSourceColumn}_${fieldName}`;
|
||||||
value = item[exactKey];
|
value = item[exactKey];
|
||||||
|
|
||||||
// 🆕 item_id 패턴 시도
|
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
const idPatternKey = `${refTable.replace("_info", "_id").replace("_mng", "_id")}_${fieldName}`;
|
const idPatternKey = `${refTable.replace("_info", "_id").replace("_mng", "_id")}_${fieldName}`;
|
||||||
value = item[idPatternKey];
|
value = item[idPatternKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 기본 별칭 패턴 시도 (item_code_name 또는 item_id_name)
|
|
||||||
if (value === undefined && (fieldName === "item_name" || fieldName === "name")) {
|
if (value === undefined && (fieldName === "item_name" || fieldName === "name")) {
|
||||||
const aliasKey = `${inferredSourceColumn}_name`;
|
const aliasKey = `${inferredSourceColumn}_name`;
|
||||||
value = item[aliasKey];
|
value = item[aliasKey];
|
||||||
// item_id_name 패턴도 시도
|
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
const idAliasKey = `${refTable.replace("_info", "_id").replace("_mng", "_id")}_name`;
|
const idAliasKey = `${refTable.replace("_info", "_id").replace("_mng", "_id")}_name`;
|
||||||
value = item[idAliasKey];
|
value = item[idAliasKey];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 일반 컬럼
|
|
||||||
value = item[columnName];
|
value = item[columnName];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value !== null && value !== undefined && value !== "") {
|
if (value !== null && value !== undefined && value !== "") {
|
||||||
// _name 필드 우선 사용 (category/entity type)
|
const strValue = String(value);
|
||||||
const displayValue = item[`${columnName}_name`] || value;
|
const nameField = item[`${columnName}_name`];
|
||||||
uniqueValues.add(String(displayValue));
|
const label = nameField || strValue;
|
||||||
|
uniqueValuesMap.set(strValue, label);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return Array.from(uniqueValues).map((value) => ({
|
return Array.from(uniqueValuesMap.entries())
|
||||||
value: value,
|
.map(([value, label]) => ({ value, label }))
|
||||||
label: value,
|
.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
[componentConfig.leftPanel?.tableName, leftData],
|
[componentConfig.leftPanel?.tableName, leftData],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1038,66 +1038,64 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
|
|
||||||
// 컬럼의 고유 값 조회 함수
|
// 컬럼의 고유 값 조회 함수
|
||||||
const getColumnUniqueValues = async (columnName: string) => {
|
const getColumnUniqueValues = async (columnName: string) => {
|
||||||
const meta = columnMeta[columnName];
|
|
||||||
const inputType = meta?.inputType || "text";
|
|
||||||
|
|
||||||
// 카테고리 타입인 경우 전체 정의된 값 조회 (백엔드 API)
|
|
||||||
if (inputType === "category") {
|
|
||||||
try {
|
|
||||||
// API 클라이언트 사용 (쿠키 인증 자동 처리)
|
|
||||||
const { apiClient } = await import("@/lib/api/client");
|
const { apiClient } = await import("@/lib/api/client");
|
||||||
|
|
||||||
|
// 1단계: 카테고리 API 시도 (columnMeta 무관하게 항상 시도)
|
||||||
|
try {
|
||||||
const response = await apiClient.get(`/table-categories/${tableConfig.selectedTable}/${columnName}/values`);
|
const response = await apiClient.get(`/table-categories/${tableConfig.selectedTable}/${columnName}/values`);
|
||||||
|
|
||||||
if (response.data.success && response.data.data) {
|
|
||||||
const categoryOptions = response.data.data.map((item: any) => ({
|
|
||||||
value: item.valueCode, // 카멜케이스
|
|
||||||
label: item.valueLabel, // 카멜케이스
|
|
||||||
}));
|
|
||||||
|
|
||||||
return categoryOptions;
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
// 에러 시 현재 데이터 기반으로 fallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 백엔드 DISTINCT API로 전체 고유값 조회 (페이징과 무관하게 모든 값 반환)
|
|
||||||
try {
|
|
||||||
const { apiClient } = await import("@/lib/api/client");
|
|
||||||
const response = await apiClient.get(`/entity/${tableConfig.selectedTable}/distinct/${columnName}`);
|
|
||||||
|
|
||||||
if (response.data.success && response.data.data && response.data.data.length > 0) {
|
if (response.data.success && response.data.data && response.data.data.length > 0) {
|
||||||
return response.data.data.map((item: any) => ({
|
return response.data.data.map((item: any) => ({
|
||||||
|
value: item.valueCode,
|
||||||
|
label: item.valueLabel,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 카테고리 API 실패 시 다음 단계로
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2단계: DISTINCT API (백엔드에서 category_values/code_info 라벨 변환 포함)
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get(`/entity/${tableConfig.selectedTable}/distinct/${columnName}`);
|
||||||
|
if (response.data.success && response.data.data && response.data.data.length > 0) {
|
||||||
|
let options = response.data.data.map((item: any) => ({
|
||||||
value: String(item.value),
|
value: String(item.value),
|
||||||
label: String(item.label),
|
label: String(item.label),
|
||||||
}));
|
}));
|
||||||
}
|
|
||||||
} catch (error: any) {
|
// 프론트엔드 카테고리 매핑으로 추가 라벨 변환
|
||||||
// DISTINCT API 실패 시 현재 데이터 기반으로 fallback
|
const mapping = categoryMappings[columnName];
|
||||||
|
if (mapping && Object.keys(mapping).length > 0) {
|
||||||
|
options = options.map((opt) => ({
|
||||||
|
value: opt.value,
|
||||||
|
label: mapping[opt.value]?.label || opt.label,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// fallback: 현재 로드된 데이터에서 고유 값 추출
|
return options;
|
||||||
const isLabelType = ["category", "entity", "code"].includes(inputType);
|
}
|
||||||
const labelField = isLabelType ? `${columnName}_name` : columnName;
|
} catch {
|
||||||
|
// DISTINCT API 실패 시 다음 단계로
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3단계: 현재 로드된 데이터에서 고유 값 추출 (최종 fallback)
|
||||||
const uniqueValuesMap = new Map<string, string>();
|
const uniqueValuesMap = new Map<string, string>();
|
||||||
|
const mapping = categoryMappings[columnName];
|
||||||
|
|
||||||
data.forEach((row) => {
|
data.forEach((row) => {
|
||||||
const value = row[columnName];
|
const value = row[columnName];
|
||||||
if (value !== null && value !== undefined && value !== "") {
|
if (value !== null && value !== undefined && value !== "") {
|
||||||
const label = isLabelType && row[labelField] ? row[labelField] : String(value);
|
const strValue = String(value);
|
||||||
uniqueValuesMap.set(String(value), label);
|
// _name 필드 또는 카테고리 매핑에서 라벨 가져오기
|
||||||
|
const nameField = row[`${columnName}_name`];
|
||||||
|
const mappedLabel = mapping?.[strValue]?.label;
|
||||||
|
const label = mappedLabel || nameField || strValue;
|
||||||
|
uniqueValuesMap.set(strValue, label);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = Array.from(uniqueValuesMap.entries())
|
return Array.from(uniqueValuesMap.entries())
|
||||||
.map(([value, label]) => ({
|
.map(([value, label]) => ({ value, label }))
|
||||||
value: value,
|
|
||||||
label: label,
|
|
||||||
}))
|
|
||||||
.sort((a, b) => a.label.localeCompare(b.label));
|
.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const registration = {
|
const registration = {
|
||||||
|
|
@ -1150,6 +1148,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
||||||
tableConfig.columns,
|
tableConfig.columns,
|
||||||
columnLabels,
|
columnLabels,
|
||||||
columnMeta, // columnMeta가 변경되면 재등록 (inputType 정보 필요)
|
columnMeta, // columnMeta가 변경되면 재등록 (inputType 정보 필요)
|
||||||
|
categoryMappings, // 카테고리 매핑 변경 시 재등록 (필터 라벨 변환용)
|
||||||
columnWidths,
|
columnWidths,
|
||||||
tableLabel,
|
tableLabel,
|
||||||
data, // 데이터 자체가 변경되면 재등록 (고유 값 조회용)
|
data, // 데이터 자체가 변경되면 재등록 (고유 값 조회용)
|
||||||
|
|
|
||||||
|
|
@ -437,12 +437,14 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [currentTable?.tableName, filterMode, screenId, currentTableTabId, JSON.stringify(presetFilters)]);
|
}, [currentTable?.tableName, filterMode, screenId, currentTableTabId, JSON.stringify(presetFilters)]);
|
||||||
|
|
||||||
// select 옵션 로드 (데이터 변경 시 빈 옵션 재조회)
|
// select 옵션 로드 (getColumnUniqueValues 변경 시 재로드 - columnMeta 갱신 반영)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!currentTable?.getColumnUniqueValues || activeFilters.length === 0) {
|
if (!currentTable?.getColumnUniqueValues || activeFilters.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cancelled = false;
|
||||||
|
|
||||||
const loadSelectOptions = async () => {
|
const loadSelectOptions = async () => {
|
||||||
const selectFilters = activeFilters.filter((f) => f.filterType === "select");
|
const selectFilters = activeFilters.filter((f) => f.filterType === "select");
|
||||||
|
|
||||||
|
|
@ -465,21 +467,14 @@ export function TableSearchWidget({ component, screenId, onHeightChange }: Table
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasNewOptions) {
|
if (!cancelled && hasNewOptions) {
|
||||||
setSelectOptions((prev) => {
|
setSelectOptions((prev) => ({ ...prev, ...loadedOptions }));
|
||||||
// 이미 로드된 옵션은 유지, 새로 로드된 옵션만 병합
|
|
||||||
const merged = { ...prev };
|
|
||||||
for (const [key, value] of Object.entries(loadedOptions)) {
|
|
||||||
if (!merged[key] || merged[key].length === 0) {
|
|
||||||
merged[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return merged;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadSelectOptions();
|
loadSelectOptions();
|
||||||
|
|
||||||
|
return () => { cancelled = true; };
|
||||||
}, [activeFilters, currentTable?.tableName, currentTable?.getColumnUniqueValues, currentTable?.dataCount]);
|
}, [activeFilters, currentTable?.tableName, currentTable?.getColumnUniqueValues, currentTable?.dataCount]);
|
||||||
|
|
||||||
// 높이 변화 감지 및 알림 (실제 화면에서만)
|
// 높이 변화 감지 및 알림 (실제 화면에서만)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue