feat: Implement advanced filtering capabilities in entity search
- Added a new helper function `applyFilters` to handle dynamic filter conditions for entity search queries. - Enhanced the `getDistinctColumnValues` and `getEntityOptions` endpoints to support JSON array filters, allowing for more flexible data retrieval based on specified conditions. - Updated the frontend components to integrate filter conditions, improving user interaction and data management in selection components. - Introduced new filter options in the V2Select component, enabling users to define and apply various filter criteria dynamically.
This commit is contained in:
@@ -3,16 +3,115 @@ import { AuthenticatedRequest } from "../types/auth";
|
||||
import { getPool } from "../database/db";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
/**
|
||||
* 필터 조건을 WHERE절에 적용하는 공통 헬퍼
|
||||
* filters JSON 배열: [{ column, operator, value }]
|
||||
*/
|
||||
function applyFilters(
|
||||
filtersJson: string | undefined,
|
||||
existingColumns: Set<string>,
|
||||
whereConditions: string[],
|
||||
params: any[],
|
||||
startParamIndex: number,
|
||||
tableName: string,
|
||||
): number {
|
||||
let paramIndex = startParamIndex;
|
||||
|
||||
if (!filtersJson) return paramIndex;
|
||||
|
||||
let filters: Array<{ column: string; operator: string; value: unknown }>;
|
||||
try {
|
||||
filters = JSON.parse(filtersJson as string);
|
||||
} catch {
|
||||
logger.warn("filters JSON 파싱 실패", { tableName, filtersJson });
|
||||
return paramIndex;
|
||||
}
|
||||
|
||||
if (!Array.isArray(filters)) return paramIndex;
|
||||
|
||||
for (const filter of filters) {
|
||||
const { column, operator = "=", value } = filter;
|
||||
if (!column || !existingColumns.has(column)) {
|
||||
logger.warn("필터 컬럼 미존재 제외", { tableName, column });
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (operator) {
|
||||
case "=":
|
||||
whereConditions.push(`"${column}" = $${paramIndex}`);
|
||||
params.push(value);
|
||||
paramIndex++;
|
||||
break;
|
||||
case "!=":
|
||||
whereConditions.push(`"${column}" != $${paramIndex}`);
|
||||
params.push(value);
|
||||
paramIndex++;
|
||||
break;
|
||||
case ">":
|
||||
case "<":
|
||||
case ">=":
|
||||
case "<=":
|
||||
whereConditions.push(`"${column}" ${operator} $${paramIndex}`);
|
||||
params.push(value);
|
||||
paramIndex++;
|
||||
break;
|
||||
case "in": {
|
||||
const inVals = Array.isArray(value) ? value : String(value).split(",").map(v => v.trim());
|
||||
if (inVals.length > 0) {
|
||||
const ph = inVals.map((_, i) => `$${paramIndex + i}`).join(", ");
|
||||
whereConditions.push(`"${column}" IN (${ph})`);
|
||||
params.push(...inVals);
|
||||
paramIndex += inVals.length;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "notIn": {
|
||||
const notInVals = Array.isArray(value) ? value : String(value).split(",").map(v => v.trim());
|
||||
if (notInVals.length > 0) {
|
||||
const ph = notInVals.map((_, i) => `$${paramIndex + i}`).join(", ");
|
||||
whereConditions.push(`"${column}" NOT IN (${ph})`);
|
||||
params.push(...notInVals);
|
||||
paramIndex += notInVals.length;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "like":
|
||||
whereConditions.push(`"${column}"::text ILIKE $${paramIndex}`);
|
||||
params.push(`%${value}%`);
|
||||
paramIndex++;
|
||||
break;
|
||||
case "isNull":
|
||||
whereConditions.push(`"${column}" IS NULL`);
|
||||
break;
|
||||
case "isNotNull":
|
||||
whereConditions.push(`"${column}" IS NOT NULL`);
|
||||
break;
|
||||
default:
|
||||
whereConditions.push(`"${column}" = $${paramIndex}`);
|
||||
params.push(value);
|
||||
paramIndex++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return paramIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블 컬럼의 DISTINCT 값 조회 API (inputType: select 용)
|
||||
* GET /api/entity/:tableName/distinct/:columnName
|
||||
*
|
||||
* 해당 테이블의 해당 컬럼에서 DISTINCT 값을 조회하여 선택박스 옵션으로 반환
|
||||
*
|
||||
* Query Params:
|
||||
* - labelColumn: 별도의 라벨 컬럼 (선택)
|
||||
* - filters: JSON 배열 형태의 필터 조건 (선택)
|
||||
* 예: [{"column":"status","operator":"=","value":"active"}]
|
||||
*/
|
||||
export async function getDistinctColumnValues(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const { tableName, columnName } = req.params;
|
||||
const { labelColumn } = req.query; // 선택적: 별도의 라벨 컬럼
|
||||
const { labelColumn, filters: filtersParam } = req.query;
|
||||
|
||||
// 유효성 검증
|
||||
if (!tableName || tableName === "undefined" || tableName === "null") {
|
||||
@@ -68,6 +167,16 @@ export async function getDistinctColumnValues(req: AuthenticatedRequest, res: Re
|
||||
whereConditions.push(`"${columnName}" IS NOT NULL`);
|
||||
whereConditions.push(`"${columnName}" != ''`);
|
||||
|
||||
// 필터 조건 적용
|
||||
paramIndex = applyFilters(
|
||||
filtersParam as string | undefined,
|
||||
existingColumns,
|
||||
whereConditions,
|
||||
params,
|
||||
paramIndex,
|
||||
tableName,
|
||||
);
|
||||
|
||||
const whereClause = whereConditions.length > 0
|
||||
? `WHERE ${whereConditions.join(" AND ")}`
|
||||
: "";
|
||||
@@ -88,6 +197,7 @@ export async function getDistinctColumnValues(req: AuthenticatedRequest, res: Re
|
||||
columnName,
|
||||
labelColumn: effectiveLabelColumn,
|
||||
companyCode,
|
||||
hasFilters: !!filtersParam,
|
||||
rowCount: result.rowCount,
|
||||
});
|
||||
|
||||
@@ -111,11 +221,14 @@ export async function getDistinctColumnValues(req: AuthenticatedRequest, res: Re
|
||||
* Query Params:
|
||||
* - value: 값 컬럼 (기본: id)
|
||||
* - label: 표시 컬럼 (기본: name)
|
||||
* - fields: 추가 반환 컬럼 (콤마 구분)
|
||||
* - filters: JSON 배열 형태의 필터 조건 (선택)
|
||||
* 예: [{"column":"status","operator":"=","value":"active"}]
|
||||
*/
|
||||
export async function getEntityOptions(req: AuthenticatedRequest, res: Response) {
|
||||
try {
|
||||
const { tableName } = req.params;
|
||||
const { value = "id", label = "name", fields } = req.query;
|
||||
const { value = "id", label = "name", fields, filters: filtersParam } = req.query;
|
||||
|
||||
// tableName 유효성 검증
|
||||
if (!tableName || tableName === "undefined" || tableName === "null") {
|
||||
@@ -163,6 +276,16 @@ export async function getEntityOptions(req: AuthenticatedRequest, res: Response)
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
// 필터 조건 적용
|
||||
paramIndex = applyFilters(
|
||||
filtersParam as string | undefined,
|
||||
existingColumns,
|
||||
whereConditions,
|
||||
params,
|
||||
paramIndex,
|
||||
tableName,
|
||||
);
|
||||
|
||||
const whereClause = whereConditions.length > 0
|
||||
? `WHERE ${whereConditions.join(" AND ")}`
|
||||
: "";
|
||||
@@ -195,6 +318,7 @@ export async function getEntityOptions(req: AuthenticatedRequest, res: Response)
|
||||
valueColumn,
|
||||
labelColumn: effectiveLabelColumn,
|
||||
companyCode,
|
||||
hasFilters: !!filtersParam,
|
||||
rowCount: result.rowCount,
|
||||
extraFields: extraColumns ? true : false,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user