[agent-pipeline] pipe-20260317063830-0nfs round-3

This commit is contained in:
DDD1542 2026-03-17 17:12:54 +09:00
parent 265f46f8d4
commit 9409f1308f
8 changed files with 44 additions and 221 deletions

View File

@ -26,7 +26,7 @@ export const NumberingRuleCard: React.FC<NumberingRuleCardProps> = ({
tableName,
}) => {
return (
<div className="config-field flex-1 rounded-lg border border-border bg-muted/50 p-3 sm:p-4">
<div className="config-field flex-1 rounded-[8px] border border-border bg-muted/50 px-3 py-3 sm:px-4 sm:py-4">
<div className="mb-3 flex items-center justify-between sm:mb-4">
<Badge variant="outline" className="text-xs sm:text-sm">
{part.order}

View File

@ -404,7 +404,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
})}
<button
type="button"
className="pipe-add flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-[10px] border-2 border-dashed border-border text-muted-foreground transition-colors hover:border-primary hover:bg-primary/5 hover:text-primary"
className="pipe-add flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full border-2 border-dashed border-border text-muted-foreground transition-colors hover:border-primary hover:bg-primary/5 hover:text-primary"
onClick={handleAddPart}
disabled={currentRule.parts.length >= maxRules || isPreview || loading}
aria-label="규칙 추가"

View File

@ -128,7 +128,7 @@ export const NumberingRulePreview: React.FC<NumberingRulePreviewProps> = ({
if (variant === "strip") {
const globalSep = config.separator ?? "-";
return (
<div className="rounded-lg bg-gradient-to-b from-muted to-card px-4 py-4 sm:px-6 sm:py-5">
<div className="rounded-lg border border-border bg-gradient-to-b from-muted to-card px-4 py-4 sm:px-6 sm:py-5">
<div className="font-mono text-[22px] font-extrabold tracking-tight sm:text-[28px]">
{partItems.length === 0 ? (
<span className="text-muted-foreground"> </span>

View File

@ -30,6 +30,8 @@ interface CategoryValueManagerProps {
columnLabel: string;
onValueCountChange?: (count: number) => void;
menuObjid?: number; // 현재 메뉴 OBJID (메뉴 스코프)
/** 편집기 헤더 오른쪽에 표시할 내용 (예: 트리/목록 세그먼트) */
headerRight?: React.ReactNode;
}
export const CategoryValueManager: React.FC<CategoryValueManagerProps> = ({
@ -38,6 +40,7 @@ export const CategoryValueManager: React.FC<CategoryValueManagerProps> = ({
columnLabel,
onValueCountChange,
menuObjid,
headerRight,
}) => {
const { toast } = useToast();
const [values, setValues] = useState<TableCategoryValue[]>([]);
@ -284,7 +287,7 @@ export const CategoryValueManager: React.FC<CategoryValueManagerProps> = ({
return (
<div className="flex h-full flex-col">
{/* 헤더 */}
{/* 편집기 헤더: 컬럼명 + 값 수 + 비활성 토글 + 새 값 추가 + headerRight(트리·목록 세그먼트 등) */}
<div className="border-b p-4">
<div className="mb-4 flex items-center justify-between">
<div>
@ -308,11 +311,11 @@ export const CategoryValueManager: React.FC<CategoryValueManagerProps> = ({
</label>
</div>
<Button onClick={() => setIsAddDialogOpen(true)} size="sm">
<Plus className="mr-2 h-4 w-4" />
</Button>
{headerRight != null ? <div className="flex items-center">{headerRight}</div> : null}
</div>
</div>

View File

@ -60,6 +60,8 @@ interface CategoryValueManagerTreeProps {
columnName: string;
columnLabel: string;
onValueCountChange?: (count: number) => void;
/** 편집기 헤더 오른쪽에 표시할 내용 (예: 트리/목록 세그먼트) */
headerRight?: React.ReactNode;
}
// 트리 노드 컴포넌트
@ -272,6 +274,7 @@ export const CategoryValueManagerTree: React.FC<CategoryValueManagerTreeProps> =
columnName,
columnLabel,
onValueCountChange,
headerRight,
}) => {
// 상태
const [tree, setTree] = useState<CategoryValue[]>([]);
@ -634,7 +637,7 @@ export const CategoryValueManagerTree: React.FC<CategoryValueManagerTreeProps> =
return (
<div className="flex h-full flex-col">
{/* 편집기 헤더: 컬럼명 + 값 수 Badge + 선택 Badge + 액션 버튼 */}
{/* 편집기 헤더: 컬럼명 + 값 수 Badge + 비활성/전체펼침/대분류추가 + headerRight(트리·목록 세그먼트 등) */}
<div className="mb-3 flex items-center justify-between border-b pb-3">
<div className="flex items-center gap-2">
<h3 className="text-base font-semibold">{columnLabel} </h3>
@ -668,6 +671,7 @@ export const CategoryValueManagerTree: React.FC<CategoryValueManagerTreeProps> =
<Plus className="h-3.5 w-3.5" />
</Button>
{headerRight != null ? <div className="flex items-center">{headerRight}</div> : null}
</div>
</div>

View File

@ -88,33 +88,33 @@ export function V2CategoryManagerComponent({
[columns, selectedTable],
);
/** 편집기 헤더에 표시할 트리/목록 세그먼트 (보기 방식 토글) */
const viewModeSegment =
config.showViewModeToggle ? (
<div className="flex rounded-md border p-0.5">
<Button
variant="ghost"
size="sm"
className={cn("h-7 gap-1.5 px-2.5 text-xs", viewMode === "tree" && "bg-accent")}
onClick={() => setViewMode("tree")}
>
<TreeDeciduous className="h-3.5 w-3.5" />
</Button>
<Button
variant="ghost"
size="sm"
className={cn("h-7 gap-1.5 px-2.5 text-xs", viewMode === "list" && "bg-accent")}
onClick={() => setViewMode("list")}
>
<LayoutList className="h-3.5 w-3.5" />
</Button>
</div>
) : null;
const rightContent = (
<>
{config.showViewModeToggle && (
<div className="mb-2 flex items-center justify-end gap-1">
<span className="text-muted-foreground mr-2 text-xs"> :</span>
<div className="flex rounded-md border p-0.5">
<Button
variant="ghost"
size="sm"
className={cn("h-7 gap-1.5 px-2.5 text-xs", viewMode === "tree" && "bg-accent")}
onClick={() => setViewMode("tree")}
>
<TreeDeciduous className="h-3.5 w-3.5" />
</Button>
<Button
variant="ghost"
size="sm"
className={cn("h-7 gap-1.5 px-2.5 text-xs", viewMode === "list" && "bg-accent")}
onClick={() => setViewMode("list")}
>
<LayoutList className="h-3.5 w-3.5" />
</Button>
</div>
</div>
)}
<div className="min-h-0 flex-1 overflow-y-auto">
{selectedColumn ? (
viewMode === "tree" ? (
@ -123,6 +123,7 @@ export function V2CategoryManagerComponent({
tableName={selectedColumn.tableName}
columnName={selectedColumn.columnName}
columnLabel={selectedColumn.columnLabel}
headerRight={viewModeSegment}
/>
) : (
<CategoryValueManager
@ -131,6 +132,7 @@ export function V2CategoryManagerComponent({
columnName={selectedColumn.columnName}
columnLabel={selectedColumn.columnLabel}
menuObjid={effectiveMenuObjid}
headerRight={viewModeSegment}
/>
)
) : (

View File

@ -207,19 +207,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
}) => {
const componentConfig = (component.componentConfig || {}) as SplitPanelLayoutConfig;
// 🐛 디버깅: 로드 시 rightPanel.components 확인
const rightComps = componentConfig.rightPanel?.components || [];
const finishedTimeline = rightComps.find((c: any) => c.id === "finished_timeline");
if (finishedTimeline) {
const fm = finishedTimeline.componentConfig?.fieldMapping;
console.log("🔍 [SplitPanelLayout] finished_timeline fieldMapping:", {
componentId: finishedTimeline.id,
fieldMapping: fm ? JSON.stringify(fm) : "undefined",
fieldMappingKeys: fm ? Object.keys(fm) : [],
fieldMappingId: fm?.id,
fullComponentConfig: JSON.stringify(finishedTimeline.componentConfig || {}, null, 2),
});
}
// 🆕 프리뷰용 회사 코드 오버라이드 (최고 관리자만 사용 가능)
const companyCode = (props as any).companyCode as string | undefined;
@ -635,14 +622,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
} = splitPanelContext;
const splitPanelId = `split-panel-${component.id}`;
// 디버깅: Context 연결 상태 확인
console.log("🔗 [SplitPanelLayout] Context 연결 상태:", {
componentId: component.id,
splitPanelId,
hasRegisterFunc: typeof ctxRegisterSplitPanel === "function",
splitPanelsSize: splitPanelContext.splitPanels?.size ?? "없음",
});
// Context에 분할 패널 등록 (좌표 정보 포함) - 마운트 시 1회만 실행
const ctxRegisterRef = useRef(ctxRegisterSplitPanel);
const ctxUnregisterRef = useRef(ctxUnregisterSplitPanel);
@ -666,15 +645,9 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
isDragging: false,
};
console.log("📦 [SplitPanelLayout] Context에 분할 패널 등록:", {
splitPanelId,
panelInfo,
});
ctxRegisterRef.current(splitPanelId, panelInfo);
return () => {
console.log("📦 [SplitPanelLayout] Context에서 분할 패널 해제:", splitPanelId);
ctxUnregisterRef.current(splitPanelId);
};
// 마운트/언마운트 시에만 실행, 위치/크기 변경은 별도 업데이트로 처리
@ -731,10 +704,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
isDragging: false,
initialLeftWidthPercent: leftWidth,
});
console.log("🛑 [SplitPanelLayout] 드래그 종료 - 버튼 위치 고정:", {
splitPanelId,
finalLeftWidthPercent: leftWidth,
});
}
prevIsDraggingRef.current = isDragging;
@ -742,11 +711,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
// 🆕 그룹별 합산된 데이터 계산
const summedLeftData = useMemo(() => {
console.log("🔍 [그룹합산] leftGroupSumConfig:", leftGroupSumConfig);
// 그룹핑이 비활성화되었거나 그룹 기준 컬럼이 없으면 원본 데이터 반환
if (!leftGroupSumConfig?.enabled || !leftGroupSumConfig?.groupByColumn) {
console.log("🔍 [그룹합산] 그룹핑 비활성화 - 원본 데이터 반환");
return leftData;
}
@ -759,7 +725,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
const [refTable, fieldName] = columnName.split(".");
const inferredSourceColumn = refTable.replace("_info", "_code").replace("_mng", "_id");
const exactKey = `${inferredSourceColumn}_${fieldName}`;
console.log("🔍 [그룹합산] 조인 컬럼 키 변환:", { columnName, exactKey, hasKey: item[exactKey] !== undefined });
if (item[exactKey] !== undefined) return exactKey;
if (fieldName === "item_name" || fieldName === "name") {
const aliasKey = `${inferredSourceColumn}_name`;
@ -812,14 +777,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
}
});
const result = Array.from(groupMap.values());
console.log("🔗 [분할패널] 그룹별 합산 결과:", {
원본개수: leftData.length,
그룹개수: result.length,
그룹기준: groupByColumn,
});
return result;
return Array.from(groupMap.values());
}, [leftData, leftGroupSumConfig]);
// 컴포넌트 스타일
@ -1262,8 +1220,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
leftTableName,
);
console.log("🔗 [분할패널] 좌측 additionalJoinColumns:", leftJoinColumns);
const result = await entityJoinApi.getTableDataWithJoins(leftTableName, {
page: 1,
size: 100,
@ -1274,12 +1230,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
companyCodeOverride: companyCode,
});
// 🔍 디버깅: API 응답 데이터의 키 확인
if (result.data && result.data.length > 0) {
console.log("🔗 [분할패널] API 응답 첫 번째 데이터 키:", Object.keys(result.data[0]));
console.log("🔗 [분할패널] API 응답 첫 번째 데이터:", result.data[0]);
}
// 좌측 패널 dataFilter 클라이언트 사이드 적용
let filteredLeftData = result.data || [];
const leftDataFilter = componentConfig.leftPanel?.dataFilter;
@ -1453,11 +1403,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
// 🆕 그룹 합산된 항목인 경우: 원본 데이터들로 우측 패널 표시
if (leftItem._originalItems && leftItem._originalItems.length > 0) {
console.log("🔗 [분할패널] 그룹 합산 항목 - 원본 개수:", leftItem._originalItems.length);
// 정렬 기준 컬럼 (복합키의 leftColumn들)
const sortColumns = keys?.map((k: any) => k.leftColumn).filter(Boolean) || [];
console.log("🔗 [분할패널] 정렬 기준 컬럼:", sortColumns);
// 정렬 함수
const sortByKeys = (data: any[]) => {
@ -1476,7 +1423,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
// 원본 데이터를 그대로 우측 패널에 표시 (이력 테이블과 동일 테이블인 경우)
if (leftTable === rightTableName) {
const sortedData = sortByKeys(leftItem._originalItems);
console.log("🔗 [분할패널] 동일 테이블 - 정렬된 원본 데이터:", sortedData.length);
setRightData(sortedData);
return;
}
@ -1514,9 +1460,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
}
}
// 정렬 적용
const sortedResults = sortByKeys(allResults);
console.log("🔗 [분할패널] 그룹 합산 - 우측 패널 정렬된 데이터:", sortedResults.length);
setRightData(sortedResults);
return;
}
@ -1534,17 +1478,11 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
}
});
console.log("🔗 [분할패널] 복합키 조건:", searchConditions);
// 🆕 우측 패널 config의 Entity 조인 컬럼 추출
const rightJoinColumns = extractAdditionalJoinColumns(
componentConfig.rightPanel?.columns,
rightTableName,
);
if (rightJoinColumns) {
console.log("🔗 [분할패널] 우측 패널 additionalJoinColumns:", rightJoinColumns);
}
// 엔티티 조인 API로 데이터 조회
const result = await entityJoinApi.getTableDataWithJoins(rightTableName, {
search: searchConditions,
@ -1554,8 +1492,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
additionalJoinColumns: rightJoinColumns,
});
console.log("🔗 [분할패널] 복합키 조회 결과:", result);
setRightData(result.data || []);
} else {
// 단일키 (하위 호환성) → entityJoinApi 사용으로 전환 (entity 조인 컬럼 지원)
@ -1566,8 +1502,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
const leftValue = leftItem[leftColumn];
const { entityJoinApi } = await import("@/lib/api/entityJoin");
console.log("🔗 [분할패널] 단일키 조건:", { leftColumn, rightColumn, leftValue, rightTableName });
// 단일키를 복합키 형식으로 변환 (entity 컬럼이므로 equals 연산자 필수)
const searchConditions: Record<string, any> = {};
searchConditions[rightColumn] = { value: leftValue, operator: "equals" };
@ -1577,10 +1511,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
componentConfig.rightPanel?.columns,
rightTableName,
);
if (rightJoinColumnsLegacy) {
console.log("🔗 [분할패널] 단일키 모드 additionalJoinColumns:", rightJoinColumnsLegacy);
}
const result = await entityJoinApi.getTableDataWithJoins(rightTableName, {
search: searchConditions,
enableEntityJoin: true,
@ -1631,10 +1561,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
// 탭 config의 Entity 조인 컬럼 추출
const tabJoinColumns = extractAdditionalJoinColumns(tabConfig.columns, tabTableName);
if (tabJoinColumns) {
console.log(`🔗 [분할패널] 탭 ${tabIndex} additionalJoinColumns:`, tabJoinColumns);
}
let resultData: any[] = [];
// 탭의 dataFilter (API 전달용)
@ -1830,7 +1756,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
if (leftTableName && !isDesignMode) {
import("@/stores/modalDataStore").then(({ useModalDataStore }) => {
useModalDataStore.getState().setData(leftTableName, [item]);
console.log(`✅ 분할 패널 좌측 선택: ${leftTableName}`, item);
});
}
},
@ -2051,7 +1976,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
}
});
setLeftColumnLabels(labels);
console.log("✅ 좌측 컬럼 라벨 로드:", labels);
} catch (error) {
console.error("좌측 테이블 컬럼 라벨 로드 실패:", error);
}
@ -2214,8 +2138,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
});
});
console.log("🔍 우측 패널 카테고리 로드 대상 테이블:", Array.from(tablesToLoad));
// 각 테이블에 대해 카테고리 매핑 로드
for (const tableName of tablesToLoad) {
try {
@ -2245,9 +2167,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
// 🆕 컬럼명만으로도 접근할 수 있도록 추가 저장 (모든 테이블)
// 기존 매핑이 있으면 병합, 없으면 새로 생성
mappings[columnName] = { ...(mappings[columnName] || {}), ...valueMap };
console.log(`✅ 우측 카테고리 매핑 로드 [${mappingKey}]:`, valueMap);
console.log(`✅ 우측 카테고리 매핑 (컬럼명만) [${columnName}]:`, mappings[columnName]);
}
} catch (error) {
console.error(`우측 카테고리 값 조회 실패 [${tableName}.${columnName}]:`, error);
@ -2302,10 +2221,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
}),
);
console.log("✅ [SplitPanel] 좌측 추가 모달 화면 열기:", {
screenId: addButtonConfig.modalScreenId,
tableName: leftTableName,
});
return;
}
}
@ -2372,11 +2287,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
}),
);
console.log("✅ [SplitPanel] 추가 모달 화면 열기:", {
screenId: addButtonConfig.modalScreenId,
tableName: currentTableName,
parentData,
});
return;
}
}
@ -2445,11 +2355,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
}),
);
console.log("✅ [SplitPanel] 좌측 수정 모달 화면 열기:", {
screenId: editButtonConfig.modalScreenId,
tableName: leftTableName,
primaryKeyValue,
});
return;
}
}
@ -2501,14 +2406,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
}
}
console.log("✅ 수정 모달 열기:", {
tableName: rightTableName,
primaryKeyName,
primaryKeyValue,
screenId: modalScreenId,
fullItem: item,
});
// modalDataStore에도 저장 (호환성 유지)
import("@/stores/modalDataStore").then(({ useModalDataStore }) => {
useModalDataStore.getState().setData(rightTableName, [item]);
@ -2517,12 +2414,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
// 🆕 groupByColumns 추출
const groupByColumns = componentConfig.rightPanel?.editButton?.groupByColumns || [];
console.log("🔧 [SplitPanel] 수정 버튼 클릭 - groupByColumns 확인:", {
groupByColumns,
editButtonConfig: componentConfig.rightPanel?.editButton,
hasGroupByColumns: groupByColumns.length > 0,
});
// ScreenModal 열기 이벤트 발생 (URL 파라미터로 ID + groupByColumns 전달)
window.dispatchEvent(
new CustomEvent("openScreenModal", {
@ -2540,13 +2431,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
}),
);
console.log("✅ [SplitPanel] openScreenModal 이벤트 발생:", {
screenId: modalScreenId,
editId: primaryKeyValue,
tableName: rightTableName,
groupByColumns: groupByColumns.length > 0 ? JSON.stringify(groupByColumns) : "없음",
});
return;
}
}
@ -2606,8 +2490,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
cleanData.company_code = companyCode;
}
console.log("📝 [SplitPanel] 커스텀 우측 패널 저장:", { tableName, primaryKey, data: cleanData });
const response = await dataApi.updateRecord(tableName, primaryKey, cleanData);
if (response.success) {
@ -2743,8 +2625,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
}
try {
console.log("📝 데이터 수정:", { tableName, primaryKey, data: editModalFormData });
// 프론트엔드 전용 필드 제거 (children, level 등)
const cleanData = { ...editModalFormData };
delete cleanData.children;
@ -2761,7 +2641,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
rightColumn: componentConfig.rightPanel.relation.rightColumn,
oldLeftValue: editModalItem[componentConfig.rightPanel.relation.leftColumn],
};
console.log("🔗 조인 관계 정보 추가:", updatePayload._relationInfo);
}
const result = await dataApi.updateRecord(tableName, primaryKey, updatePayload);
@ -2831,7 +2710,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
// 우측 패널 + 중계 테이블 모드인 경우
if (deleteModalPanel === "right" && componentConfig.rightPanel?.addConfig?.targetTable) {
tableName = componentConfig.rightPanel.addConfig.targetTable;
console.log("🔗 중계 테이블 모드: 삭제 대상 테이블 =", tableName);
}
}
@ -2841,9 +2719,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
if (!primaryKey && deleteModalItem && typeof deleteModalItem === "object") {
// id가 없는 경우에만 전체 객체 전달 (복합키 테이블)
primaryKey = deleteModalItem;
console.log("🔑 복합키: 전체 객체 전달", Object.keys(primaryKey));
} else {
console.log("🔑 단일키 삭제: id =", primaryKey, "테이블 =", tableName);
}
if (!tableName || !primaryKey) {
@ -2856,16 +2731,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
}
try {
console.log("🗑️ 데이터 삭제:", { tableName, primaryKey });
// 🔍 중복 제거 설정 디버깅
console.log("🔍 중복 제거 디버깅:", {
panel: deleteModalPanel,
dataFilter: componentConfig.rightPanel?.dataFilter,
deduplication: componentConfig.rightPanel?.dataFilter?.deduplication,
enabled: componentConfig.rightPanel?.dataFilter?.deduplication?.enabled,
});
let result;
// 🔧 중복 제거가 활성화된 경우, groupByColumn 기준으로 모든 관련 레코드 삭제
@ -2875,7 +2740,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
if (groupByColumn && deleteModalItem[groupByColumn]) {
const groupValue = deleteModalItem[groupByColumn];
console.log(`🔗 중복 제거 활성화: ${groupByColumn} = ${groupValue} 기준으로 모든 레코드 삭제`);
// groupByColumn 값으로 필터링하여 삭제
const filterConditions: Record<string, any> = {
@ -2889,8 +2753,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
filterConditions[rightColumn] = selectedLeftItem[leftColumn];
}
console.log("🗑️ 그룹 삭제 조건:", filterConditions);
// 그룹 삭제 API 호출
result = await dataApi.deleteGroupRecords(tableName, filterConditions);
} else {
@ -3022,7 +2884,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
if (addConfig.leftPanelColumn && addConfig.targetColumn && selectedLeftItem) {
const leftValue = selectedLeftItem[addConfig.leftPanelColumn];
finalData[addConfig.targetColumn] = leftValue;
console.log(`🔗 좌측 패널 값 자동 채움: ${addConfig.targetColumn} = ${leftValue}`);
}
// 자동 채움 컬럼 추가
@ -3030,7 +2891,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
Object.entries(addConfig.autoFillColumns).forEach(([key, value]) => {
finalData[key] = value;
});
console.log("🔧 자동 채움 컬럼:", addConfig.autoFillColumns);
}
} else {
// 일반 테이블 모드
@ -3066,8 +2926,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
}
try {
console.log("📝 데이터 추가:", { tableName, data: finalData });
const result = await dataApi.createRecord(tableName, finalData);
if (result.success) {
@ -3210,7 +3068,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
useEffect(() => {
const handleRefreshTable = () => {
if (!isDesignMode) {
console.log("🔄 [SplitPanel] refreshTable 이벤트 수신 - 데이터 새로고침");
loadLeftData();
// 현재 활성 탭 데이터 새로고침 (좌측 미선택 시에도 전체 데이터 로드)
if (activeTabIndex === 0) {
@ -3510,7 +3367,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
}}
// 🆕 중첩된 탭 내부 컴포넌트 선택 핸들러 - 부모 분할 패널 정보 포함
onSelectTabComponent={(tabId: string, compId: string, tabComp: any) => {
console.log("🔍 [SplitPanel-Left] onSelectTabComponent 호출:", { tabId, compId, tabComp, parentSplitPanelId: component.id });
// 탭 내 컴포넌트 선택 상태 업데이트
setNestedTabSelectedCompId(compId);
// 부모 분할 패널 정보와 함께 전역 이벤트 발생
@ -3606,12 +3462,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
(() => {
// 🆕 그룹별 합산된 데이터 사용
const dataSource = summedLeftData;
console.log(
"🔍 [테이블모드 렌더링] dataSource 개수:",
dataSource.length,
"leftGroupSumConfig:",
leftGroupSumConfig,
);
// 🔧 로컬 검색 필터 적용
const filteredData = leftSearchQuery
@ -3898,12 +3748,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
(() => {
// 🆕 그룹별 합산된 데이터 사용
const dataToDisplay = summedLeftData;
console.log(
"🔍 [렌더링] dataToDisplay 개수:",
dataToDisplay.length,
"leftGroupSumConfig:",
leftGroupSumConfig,
);
// 검색 필터링 (클라이언트 사이드)
const filteredLeftData = leftSearchQuery
@ -3930,13 +3774,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
const configuredColumns = componentConfig.leftPanel?.columns || [];
let displayFields: { label: string; value: any }[] = [];
// 디버그 로그
if (index === 0) {
console.log("🔍 좌측 패널 표시 로직:");
console.log(" - 설정된 표시 컬럼:", configuredColumns);
console.log(" - item keys:", Object.keys(item));
}
if (configuredColumns.length > 0) {
// 🔧 "표시할 컬럼 선택"에서 설정한 컬럼 사용
displayFields = configuredColumns.slice(0, 2).map((col: any) => {
@ -3960,10 +3797,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
value: displayValue,
};
});
if (index === 0) {
console.log(" ✅ 설정된 컬럼 기반 표시:", displayFields);
}
} else {
// 설정된 컬럼이 없으면 자동으로 첫 2개 필드 표시
const keys = Object.keys(item).filter(
@ -3984,10 +3817,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
value: displayValue,
};
});
if (index === 0) {
console.log(" ⚠️ 설정된 컬럼 없음, 자동 선택:", displayFields);
}
}
const displayTitle = displayFields[0]?.value || item.name || item.title || `항목 ${index + 1}`;
@ -4801,7 +4630,6 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
_isKeyColumn: true, // 구분용 플래그
}));
columnsToShow = [...keyColsToAdd, ...columnsToShow];
console.log("🔗 [우측패널] 그룹모드 - 키 컬럼 추가:", missingKeyColumns);
}
}
} else {
@ -5120,39 +4948,24 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
let displayEntries: [string, any, string][] = [];
if (rightColumns && rightColumns.length > 0) {
console.log("🔍 [디버깅] 상세 모드 표시 로직:");
console.log(" 📋 rightData 전체:", rightData);
console.log(" 📋 rightData keys:", Object.keys(rightData));
console.log(
" ⚙️ 설정된 컬럼:",
rightColumns.map((c) => `${c.name} (${c.label})`),
);
// 설정된 컬럼만 표시 (showInDetail이 false가 아닌 것만)
displayEntries = rightColumns
.filter((col) => col.showInDetail !== false)
.map((col) => {
// 🆕 엔티티 조인 컬럼 처리 (예: item_info.item_name → item_name)
let value = rightData[col.name];
console.log(` 🔎 컬럼 "${col.name}": 직접 접근 = ${value}`);
if (value === undefined && col.name.includes(".")) {
const columnName = col.name.split(".").pop();
value = rightData[columnName || ""];
console.log(` → 변환 후 "${columnName}" 접근 = ${value}`);
}
return [col.name, value, col.label] as [string, any, string];
})
; // 설정된 컬럼은 null/empty여도 항상 표시
console.log(" ✅ 최종 표시할 항목:", displayEntries.length, "개");
});
} else {
// 설정 없으면 모든 컬럼 표시
displayEntries = Object.entries(rightData)
.filter(([_, value]) => value !== null && value !== undefined && value !== "")
.map(([key, value]) => [key, value, ""] as [string, any, string]);
console.log(" ⚠️ 컬럼 설정 없음, 모든 컬럼 표시");
}
const hasDetailEditButton = !isDesignMode && (componentConfig.rightPanel?.editButton?.enabled ?? true);

View File

@ -381,6 +381,7 @@ export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
onEditSave?.();
};
// category/code 타입: select는 반드시 h-8(32px)로 행 높이 유지
if (hasCategoryOptions) {
const selectOptions = Object.entries(categoryOptions).map(([value, info]) => ({
value,
@ -393,7 +394,7 @@ export const SingleTableWithSticky: React.FC<SingleTableWithStickyProps> = ({
onChange={(e) => onEditingValueChange?.(e.target.value)}
onKeyDown={onEditKeyDown}
onBlur={handleBlurSave}
className={commonInputClass}
className={cn(commonInputClass, "h-8")}
onClick={(e) => e.stopPropagation()}
>
<option value=""></option>