Files
vexplor/frontend/hooks/pop/useConnectionResolver.ts
SeongHyun Kim 1acd9fc3b2 feat(pop-search): 모달 뷰 전면 개선 - 아이콘 뷰, 가나다/ABC 필터 탭, 컬럼 라벨
모달 타입 통합 (modal-table/card/icon-grid -> modal 1종):
- normalizeInputType()으로 레거시 저장값 호환
- 캔버스 모달 모드 완전 제거 (ModalMode, modalCanvasId, returnEvent)
- SearchInputType 9종으로 정리

모달 뷰 실제 구현:
- TableView / IconView 분리 렌더링 (displayStyle 반영)
- 아이콘 뷰: 이름 첫 글자 컬러 카드 + 초성 그룹 헤더
- getIconColor() 결정적 해시 색상 (16색 팔레트)

가나다/ABC 필터 탭:
- ModalFilterTab 타입 + getGroupKey() 한글 초성 추출
- 쌍자음 합침 (ㄲ->ㄱ, ㄸ->ㄷ 등)
- 모달 상단 토글 버튼으로 초성/알파벳 섹션 그룹화

디자이너 설정 개선:
- 컬럼 헤더 라벨 커스터마이징 (columnLabels)
- 필터 탭 활성화 체크박스 (가나다/ABC)
- card 스타일 제거, 정렬 옵션 제거
- 검색 방식 (포함/시작/같음) 유지

시나리오 A 모달 선택 필터링:
- ConnectionEditor 필터 컬럼에 DB 전체 컬럼 표시
- pop-string-list 복수 필터 AND 지원
- useConnectionResolver 페이로드 구조 정규화

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 12:52:29 +09:00

71 lines
2.3 KiB
TypeScript

/**
* useConnectionResolver - 런타임 컴포넌트 연결 해석기
*
* PopViewerWithModals에서 사용.
* layout.dataFlow.connections를 읽고, 소스 컴포넌트의 __comp_output__ 이벤트를
* 타겟 컴포넌트의 __comp_input__ 이벤트로 자동 변환/중계한다.
*
* 이벤트 규칙:
* 소스: __comp_output__${sourceComponentId}__${outputKey}
* 타겟: __comp_input__${targetComponentId}__${inputKey}
*/
import { useEffect, useRef } from "react";
import { usePopEvent } from "./usePopEvent";
import type { PopDataConnection } from "@/components/pop/designer/types/pop-layout";
interface UseConnectionResolverOptions {
screenId: string;
connections: PopDataConnection[];
}
export function useConnectionResolver({
screenId,
connections,
}: UseConnectionResolverOptions): void {
const { publish, subscribe } = usePopEvent(screenId);
// 연결 목록을 ref로 저장하여 콜백 안정성 확보
const connectionsRef = useRef(connections);
connectionsRef.current = connections;
useEffect(() => {
if (!connections || connections.length === 0) return;
const unsubscribers: (() => void)[] = [];
// 소스별로 그룹핑하여 구독 생성
const sourceGroups = new Map<string, PopDataConnection[]>();
for (const conn of connections) {
const sourceEvent = `__comp_output__${conn.sourceComponent}__${conn.sourceOutput || conn.sourceField}`;
const existing = sourceGroups.get(sourceEvent) || [];
existing.push(conn);
sourceGroups.set(sourceEvent, existing);
}
for (const [sourceEvent, conns] of sourceGroups) {
const unsub = subscribe(sourceEvent, (payload: unknown) => {
for (const conn of conns) {
const targetEvent = `__comp_input__${conn.targetComponent}__${conn.targetInput || conn.targetField}`;
// 항상 통일된 구조로 감싸서 전달: { value, filterConfig?, _connectionId }
const enrichedPayload = {
value: payload,
filterConfig: conn.filterConfig,
_connectionId: conn.id,
};
publish(targetEvent, enrichedPayload);
}
});
unsubscribers.push(unsub);
}
return () => {
for (const unsub of unsubscribers) {
unsub();
}
};
}, [screenId, connections, subscribe, publish]);
}