feat(screen): PC/POP 화면관리 데이터 분리 (excludePop 필터)

PC 화면관리에서 POP 전용 화면과 그룹이 함께 표시되어 혼동을 주는
문제를 해결하기 위해 excludePop 필터를 도입한다.
[백엔드]
- getScreensByCompany: NOT EXISTS 서브쿼리로 screen_layouts_pop에
  레이아웃이 있는 화면 제외, 테이블 별칭 sd로 통일
- getScreenGroups: hierarchy_path 기반으로 POP 그룹 제외
  (hierarchy_path IS NULL OR NOT LIKE 'POP/%')
- 두 API 모두 excludePop 미전달 시 기존 동작 100% 유지
[프론트엔드]
- screenApi.getScreens, getScreenGroups에 excludePop 파라미터 추가
- PC 화면관리 페이지, ScreenGroupTreeView, ScreenList에서
  excludePop: true 전달
This commit is contained in:
SeongHyun Kim
2026-03-04 14:01:19 +09:00
parent 15e22ba401
commit 37d93d82b1
8 changed files with 32 additions and 16 deletions

View File

@@ -20,7 +20,7 @@ const pool = getPool();
export const getScreenGroups = async (req: AuthenticatedRequest, res: Response) => {
try {
const companyCode = req.user?.companyCode || "*";
const { page = 1, size = 20, searchTerm } = req.query;
const { page = 1, size = 20, searchTerm, excludePop } = req.query;
const offset = (parseInt(page as string) - 1) * parseInt(size as string);
let whereClause = "WHERE 1=1";
@@ -34,6 +34,11 @@ export const getScreenGroups = async (req: AuthenticatedRequest, res: Response)
paramIndex++;
}
// POP 그룹 제외 (PC 화면관리용)
if (excludePop === "true") {
whereClause += ` AND (hierarchy_path IS NULL OR (hierarchy_path NOT LIKE 'POP/%' AND hierarchy_path != 'POP'))`;
}
// 검색어 필터링
if (searchTerm) {
whereClause += ` AND (group_name ILIKE $${paramIndex} OR group_code ILIKE $${paramIndex} OR description ILIKE $${paramIndex})`;

View File

@@ -6,7 +6,7 @@ import { AuthenticatedRequest } from "../types/auth";
export const getScreens = async (req: AuthenticatedRequest, res: Response) => {
try {
const userCompanyCode = (req.user as any).companyCode;
const { page = 1, size = 20, searchTerm, companyCode } = req.query;
const { page = 1, size = 20, searchTerm, companyCode, excludePop } = req.query;
// 쿼리 파라미터로 companyCode가 전달되면 해당 회사의 화면 조회 (최고 관리자 전용)
// 아니면 현재 사용자의 companyCode 사용
@@ -24,7 +24,8 @@ export const getScreens = async (req: AuthenticatedRequest, res: Response) => {
targetCompanyCode,
parseInt(page as string),
parseInt(size as string),
searchTerm as string // 검색어 전달
searchTerm as string,
{ excludePop: excludePop === "true" },
);
res.json({

View File

@@ -108,42 +108,49 @@ export class ScreenManagementService {
companyCode: string,
page: number = 1,
size: number = 20,
searchTerm?: string, // 검색어 추가
searchTerm?: string,
options?: { excludePop?: boolean },
): Promise<PaginatedResponse<ScreenDefinition>> {
const offset = (page - 1) * size;
// WHERE 절 동적 생성
const whereConditions: string[] = ["is_active != 'D'"];
const whereConditions: string[] = ["sd.is_active != 'D'"];
const params: any[] = [];
if (companyCode !== "*") {
whereConditions.push(`company_code = $${params.length + 1}`);
whereConditions.push(`sd.company_code = $${params.length + 1}`);
params.push(companyCode);
}
// 검색어 필터링 추가 (화면명, 화면 코드, 테이블명 검색)
if (searchTerm && searchTerm.trim() !== "") {
whereConditions.push(`(
screen_name ILIKE $${params.length + 1} OR
screen_code ILIKE $${params.length + 1} OR
table_name ILIKE $${params.length + 1}
sd.screen_name ILIKE $${params.length + 1} OR
sd.screen_code ILIKE $${params.length + 1} OR
sd.table_name ILIKE $${params.length + 1}
)`);
params.push(`%${searchTerm.trim()}%`);
}
// POP 화면 제외 필터: screen_layouts_pop에 레이아웃이 있는 화면 제외
if (options?.excludePop) {
whereConditions.push(
`NOT EXISTS (SELECT 1 FROM screen_layouts_pop slp WHERE slp.screen_id = sd.screen_id)`
);
}
const whereSQL = whereConditions.join(" AND ");
// 페이징 쿼리 (Raw Query)
const [screens, totalResult] = await Promise.all([
query<any>(
`SELECT * FROM screen_definitions
`SELECT sd.* FROM screen_definitions sd
WHERE ${whereSQL}
ORDER BY created_date DESC
ORDER BY sd.created_date DESC
LIMIT $${params.length + 1} OFFSET $${params.length + 2}`,
[...params, size, offset],
),
query<{ count: string }>(
`SELECT COUNT(*)::text as count FROM screen_definitions
`SELECT COUNT(*)::text as count FROM screen_definitions sd
WHERE ${whereSQL}`,
params,
),