();
+ for (const res of results) {
+ const cols = res.success && res.data?.columns;
+ if (Array.isArray(cols)) {
+ cols.forEach((c) => { if (c.columnName) allCols.add(c.columnName); });
+ }
}
+ setSubColumns([...allCols].sort());
})
.catch(() => setSubColumns([]))
.finally(() => setLoadingColumns(false));
- }, [isSubTable, subTableName]);
+ }, [isFilterConnection, selectedTargetId, mainTableName, subTableName]);
const handleSubmit = () => {
if (!selectedTargetId) return;
@@ -290,11 +297,10 @@ function SimpleConnectionForm({
label: `${srcLabel} → ${tgtLabel}`,
};
- if (isFilterConnection && isSubTable && targetColumn) {
+ if (isFilterConnection && targetColumn) {
conn.filterConfig = {
targetColumn,
filterMode: filterMode as "equals" | "contains" | "starts_with" | "range",
- isSubTable: true,
};
}
@@ -302,7 +308,6 @@ function SimpleConnectionForm({
if (!initial) {
setSelectedTargetId("");
- setIsSubTable(false);
setTargetColumn("");
setFilterMode("equals");
}
@@ -328,7 +333,6 @@ function SimpleConnectionForm({
value={selectedTargetId}
onValueChange={(v) => {
setSelectedTargetId(v);
- setIsSubTable(false);
setTargetColumn("");
}}
>
@@ -345,62 +349,47 @@ function SimpleConnectionForm({
- {isFilterConnection && selectedTargetId && subTableName && (
+ {isFilterConnection && selectedTargetId && (
-
-
{
- setIsSubTable(v === true);
- if (!v) setTargetColumn("");
- }}
- />
-
+
+
대상 컬럼
+ {loadingColumns ? (
+
+
+ 컬럼 로딩 중...
+
+ ) : (
+
+ )}
- {isSubTable && (
-
-
-
대상 컬럼
- {loadingColumns ? (
-
-
- 컬럼 로딩 중...
-
- ) : (
-
- )}
-
-
-
- 비교 방식
-
-
-
- )}
+
+ 비교 방식
+
+
+
+ 메인/하위 테이블 구분은 자동으로 판단됩니다
+
)}
diff --git a/frontend/lib/registry/pop-components/pop-card-list-v2/PopCardListV2Component.tsx b/frontend/lib/registry/pop-components/pop-card-list-v2/PopCardListV2Component.tsx
index a6c26838..56296c87 100644
--- a/frontend/lib/registry/pop-components/pop-card-list-v2/PopCardListV2Component.tsx
+++ b/frontend/lib/registry/pop-components/pop-card-list-v2/PopCardListV2Component.tsx
@@ -569,23 +569,39 @@ export function PopCardListV2Component({
);
}, [timelineSource]);
- // 외부 필터 (메인 테이블 + 하위 테이블 분기)
+ // processFlow rawData 키셋 (하위 테이블 컬럼 자동 판단용)
+ const subTableKeys = useMemo(() => {
+ for (const row of rows) {
+ const pf = row.__processFlow__ as TimelineProcessStep[] | undefined;
+ if (pf?.[0]?.rawData) return new Set(Object.keys(pf[0].rawData));
+ }
+ return new Set
();
+ }, [rows]);
+
+ // 필터 컬럼이 하위 테이블에 속하는지 자동 판단
+ const isSubTableColumn = useCallback((filter: { fieldName: string; filterConfig?: { targetColumn: string; isSubTable?: boolean } }) => {
+ if (filter.filterConfig?.isSubTable) return true;
+ const col = filter.filterConfig?.targetColumn || filter.fieldName;
+ return col ? subTableKeys.has(col) : false;
+ }, [subTableKeys]);
+
+ // 외부 필터 (자동 분류: 컬럼이 processFlow에 있으면 subFilter)
const filteredRows = useMemo(() => {
if (externalFilters.size === 0) return duplicateAcceptableCards(rows);
const allFilters = [...externalFilters.values()];
- const mainFilters = allFilters.filter((f) => !f.filterConfig?.isSubTable);
- const subFilters = allFilters.filter((f) => f.filterConfig?.isSubTable);
+ const mainFilters = allFilters.filter((f) => !isSubTableColumn(f));
+ const subFilters = allFilters.filter((f) => isSubTableColumn(f));
const afterDuplicate = applySubFilterAndDuplicate(rows, subFilters);
return applyMainFilters(afterDuplicate, mainFilters, subFilters.length > 0);
- }, [rows, externalFilters, duplicateAcceptableCards, applySubFilterAndDuplicate, applyMainFilters]);
+ }, [rows, externalFilters, duplicateAcceptableCards, applySubFilterAndDuplicate, applyMainFilters, isSubTableColumn]);
// 하위 필터 활성 여부
const hasActiveSubFilter = useMemo(() => {
if (externalFilters.size === 0) return false;
- return [...externalFilters.values()].some((f) => f.filterConfig?.isSubTable);
- }, [externalFilters]);
+ return [...externalFilters.values()].some((f) => isSubTableColumn(f));
+ }, [externalFilters, isSubTableColumn]);
// 선택 모드 일괄 처리
const handleSelectModeAction = useCallback(async (btnConfig: SelectModeButtonConfig) => {
@@ -675,12 +691,12 @@ export function PopCardListV2Component({
if (nonStatusFilters.size === 0) return duplicateAcceptableCards(rows);
const allFilters = [...nonStatusFilters.values()];
- const mainFilters = allFilters.filter((f) => !f.filterConfig?.isSubTable);
- const subFilters = allFilters.filter((f) => f.filterConfig?.isSubTable);
+ const mainFilters = allFilters.filter((f) => !isSubTableColumn(f));
+ const subFilters = allFilters.filter((f) => isSubTableColumn(f));
const afterDuplicate = applySubFilterAndDuplicate(rows, subFilters);
return applyMainFilters(afterDuplicate, mainFilters, subFilters.length > 0);
- }, [rows, filteredRows, externalFilters, duplicateAcceptableCards, applySubFilterAndDuplicate, applyMainFilters]);
+ }, [rows, filteredRows, externalFilters, duplicateAcceptableCards, applySubFilterAndDuplicate, applyMainFilters, isSubTableColumn]);
// 카운트 집계용 rows 발행 (status-bar 필터 제외)
// originalCount: 복제 카드를 제외한 원본 카드 수
diff --git a/frontend/lib/registry/pop-components/pop-search/PopSearchComponent.tsx b/frontend/lib/registry/pop-components/pop-search/PopSearchComponent.tsx
index 01ecc173..f4457e39 100644
--- a/frontend/lib/registry/pop-components/pop-search/PopSearchComponent.tsx
+++ b/frontend/lib/registry/pop-components/pop-search/PopSearchComponent.tsx
@@ -28,6 +28,7 @@ import {
import { format, startOfWeek, endOfWeek, startOfMonth, endOfMonth } from "date-fns";
import { ko } from "date-fns/locale";
import { usePopEvent } from "@/hooks/pop";
+import { useAuth } from "@/hooks/useAuth";
import { dataApi } from "@/lib/api/data";
import type {
PopSearchConfig,
@@ -67,9 +68,11 @@ export function PopSearchComponent({
}: PopSearchComponentProps) {
const config = { ...DEFAULT_CONFIG, ...(rawConfig || {}) };
const { publish, subscribe, setSharedData } = usePopEvent(screenId || "");
+ const { user } = useAuth();
const [value, setValue] = useState(config.defaultValue ?? "");
const [modalDisplayText, setModalDisplayText] = useState("");
const [simpleModalOpen, setSimpleModalOpen] = useState(false);
+ const initialValueAppliedRef = useRef(false);
const normalizedType = normalizeInputType(config.inputType as string);
const isModalType = normalizedType === "modal";
@@ -107,6 +110,21 @@ export function PopSearchComponent({
[fieldKey, publish, setSharedData, componentId, resolveFilterMode, config.filterColumns]
);
+ // 초기값 고정 세팅: 사용자 프로필에서 자동으로 값 설정
+ useEffect(() => {
+ if (initialValueAppliedRef.current) return;
+ if (!config.initialValueSource || config.initialValueSource.type !== "user_profile") return;
+ if (!user) return;
+
+ const col = config.initialValueSource.column;
+ const profileValue = (user as Record)[col];
+ if (profileValue != null && profileValue !== "") {
+ initialValueAppliedRef.current = true;
+ const timer = setTimeout(() => emitFilterChanged(profileValue), 100);
+ return () => clearTimeout(timer);
+ }
+ }, [user, config.initialValueSource, emitFilterChanged]);
+
useEffect(() => {
if (!componentId) return;
const unsub = subscribe(
@@ -238,12 +256,6 @@ function SearchInputRenderer({ config, value, onChange, modalDisplayText, onModa
return ;
case "modal":
return ;
- case "status-chip":
- return (
-
- pop-status-bar 컴포넌트를 사용하세요
-
- );
default:
return ;
}
diff --git a/frontend/lib/registry/pop-components/pop-search/PopSearchConfig.tsx b/frontend/lib/registry/pop-components/pop-search/PopSearchConfig.tsx
index b0752146..e8fb977a 100644
--- a/frontend/lib/registry/pop-components/pop-search/PopSearchConfig.tsx
+++ b/frontend/lib/registry/pop-components/pop-search/PopSearchConfig.tsx
@@ -209,6 +209,39 @@ function StepBasicSettings({ cfg, update }: StepProps) {
)}
+ {/* 초기값 고정 세팅 */}
+
+
+
+ {cfg.initialValueSource && (
+
+ 화면 진입 시 로그인 사용자의 {cfg.initialValueSource.column} 값으로 자동 필터링됩니다
+
+ )}
+
+
);
}
@@ -231,15 +264,6 @@ function StepDetailSettings({ cfg, update, allComponents, connections, component
return ;
case "modal":
return ;
- case "status-chip":
- return (
-
-
- 상태 칩은 pop-status-bar 컴포넌트로 분리되었습니다.
- 새로운 "상태 바" 컴포넌트를 사용해주세요.
-
-
- );
case "toggle":
return (
diff --git a/frontend/lib/registry/pop-components/pop-search/types.ts b/frontend/lib/registry/pop-components/pop-search/types.ts
index 6b284b60..decaff69 100644
--- a/frontend/lib/registry/pop-components/pop-search/types.ts
+++ b/frontend/lib/registry/pop-components/pop-search/types.ts
@@ -1,25 +1,20 @@
// ===== pop-search 전용 타입 =====
// 단일 필드 검색 컴포넌트. 그리드 한 칸 = 검색 필드 하나.
-/** 검색 필드 입력 타입 (10종) */
+/** 검색 필드 입력 타입 */
export type SearchInputType =
| "text"
| "number"
| "date"
| "date-preset"
| "select"
- | "multi-select"
- | "combo"
| "modal"
- | "toggle"
- | "status-chip";
+ | "toggle";
-/** 레거시 입력 타입 (DB에 저장된 기존 값 호환용) */
-export type LegacySearchInputType = "modal-table" | "modal-card" | "modal-icon-grid";
-
-/** 레거시 타입 -> modal로 정규화 */
+/** 레거시 입력 타입 정규화 (DB 호환) */
export function normalizeInputType(t: string): SearchInputType {
if (t === "modal-table" || t === "modal-card" || t === "modal-icon-grid") return "modal";
+ if (t === "status-chip" || t === "multi-select" || t === "combo") return "text";
return t as SearchInputType;
}
@@ -38,15 +33,6 @@ export interface SelectOption {
label: string;
}
-/** 셀렉트 옵션 데이터 소스 (DB에서 동적 로딩) */
-export interface SelectDataSource {
- tableName: string;
- valueColumn: string;
- labelColumn: string;
- sortColumn?: string;
- sortDirection?: "asc" | "desc";
-}
-
/** 모달 보여주기 방식: 테이블 or 아이콘 */
export type ModalDisplayStyle = "table" | "icon";
@@ -79,22 +65,9 @@ export interface ModalSelectConfig {
distinct?: boolean;
}
-/** @deprecated status-chip은 pop-status-bar로 분리됨. 레거시 호환용. */
-export type StatusChipStyle = "tab" | "pill";
-
-/** @deprecated status-chip은 pop-status-bar로 분리됨. 레거시 호환용. */
-export interface StatusChipConfig {
- showCount?: boolean;
- countColumn?: string;
- allowAll?: boolean;
- allLabel?: string;
- chipStyle?: StatusChipStyle;
- useSubCount?: boolean;
-}
-
/** pop-search 전체 설정 */
export interface PopSearchConfig {
- inputType: SearchInputType | LegacySearchInputType;
+ inputType: SearchInputType | string;
fieldName: string;
placeholder?: string;
defaultValue?: unknown;
@@ -103,9 +76,8 @@ export interface PopSearchConfig {
debounceMs?: number;
triggerOnEnter?: boolean;
- // select/multi-select 전용
+ // select 전용
options?: SelectOption[];
- optionsDataSource?: SelectDataSource;
// date 전용
dateSelectionMode?: DateSelectionMode;
@@ -117,9 +89,6 @@ export interface PopSearchConfig {
// modal 전용
modalConfig?: ModalSelectConfig;
- // status-chip 전용
- statusChipConfig?: StatusChipConfig;
-
// 라벨
labelText?: string;
labelVisible?: boolean;
@@ -129,6 +98,12 @@ export interface PopSearchConfig {
// 필터 대상 컬럼 복수 선택 (fieldName은 대표 컬럼, filterColumns는 전체 대상)
filterColumns?: string[];
+
+ // 초기값 고정 세팅 (사용자 프로필에서 자동으로 값 설정)
+ initialValueSource?: {
+ type: "user_profile";
+ column: string;
+ };
}
/** 기본 설정값 (레지스트리 + 컴포넌트 공유) */
@@ -157,17 +132,8 @@ export const SEARCH_INPUT_TYPE_LABELS: Record = {
date: "날짜",
"date-preset": "날짜 프리셋",
select: "단일 선택",
- "multi-select": "다중 선택",
- combo: "자동완성",
modal: "모달",
toggle: "토글",
- "status-chip": "상태 칩 (대시보드)",
-};
-
-/** 상태 칩 스타일 라벨 (설정 패널용) */
-export const STATUS_CHIP_STYLE_LABELS: Record = {
- tab: "탭 (큰 숫자)",
- pill: "알약 (작은 뱃지)",
};
/** 모달 보여주기 방식 라벨 */