화면간 데이터 전달기능 구현

This commit is contained in:
kjs
2025-12-02 18:03:52 +09:00
parent 44c76d80b7
commit 3b875f20b1
14 changed files with 886 additions and 171 deletions

View File

@@ -1,6 +1,6 @@
"use client";
import React, { useEffect, useState, useMemo } from "react";
import React, { useEffect, useState, useMemo, useCallback } from "react";
import { ComponentRendererProps } from "@/types/component";
import { CardDisplayConfig } from "./types";
import { tableTypeApi } from "@/lib/api/screen";
@@ -8,6 +8,9 @@ import { filterDOMProps } from "@/lib/utils/domPropsFilter";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { useScreenContextOptional } from "@/contexts/ScreenContext";
import { useSplitPanelContext } from "@/contexts/SplitPanelContext";
import { useModalDataStore } from "@/stores/modalDataStore";
export interface CardDisplayComponentProps extends ComponentRendererProps {
config?: CardDisplayConfig;
@@ -38,13 +41,18 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
tableColumns = [],
...props
}) => {
// 컨텍스트 (선택적 - 디자인 모드에서는 없을 수 있음)
const screenContext = useScreenContextOptional();
const splitPanelContext = useSplitPanelContext();
const splitPanelPosition = screenContext?.splitPanelPosition;
// 테이블 데이터 상태 관리
const [loadedTableData, setLoadedTableData] = useState<any[]>([]);
const [loadedTableColumns, setLoadedTableColumns] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
// 선택된 카드 상태
const [selectedCardId, setSelectedCardId] = useState<string | number | null>(null);
// 선택된 카드 상태 (Set으로 변경하여 테이블 리스트와 동일하게)
const [selectedRows, setSelectedRows] = useState<Set<string>>(new Set());
// 상세보기 모달 상태
const [viewModalOpen, setViewModalOpen] = useState(false);
@@ -199,38 +207,132 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
// 표시할 데이터 결정 (로드된 테이블 데이터 우선 사용)
const displayData = useMemo(() => {
// console.log("📋 CardDisplay: displayData 결정 중", {
// dataSource: componentConfig.dataSource,
// loadedTableDataLength: loadedTableData.length,
// tableDataLength: tableData.length,
// staticDataLength: componentConfig.staticData?.length || 0,
// });
// 로드된 테이블 데이터가 있으면 항상 우선 사용 (dataSource 설정 무시)
if (loadedTableData.length > 0) {
// console.log("📋 CardDisplay: 로드된 테이블 데이터 사용", loadedTableData.slice(0, 2));
return loadedTableData;
}
// props로 전달받은 테이블 데이터가 있으면 사용
if (tableData.length > 0) {
// console.log("📋 CardDisplay: props 테이블 데이터 사용", tableData.slice(0, 2));
return tableData;
}
if (componentConfig.staticData && componentConfig.staticData.length > 0) {
// console.log("📋 CardDisplay: 정적 데이터 사용", componentConfig.staticData.slice(0, 2));
return componentConfig.staticData;
}
// 데이터가 없으면 빈 배열 반환
// console.log("📋 CardDisplay: 표시할 데이터가 없음");
return [];
}, [componentConfig.dataSource, loadedTableData, tableData, componentConfig.staticData]);
// 실제 사용할 테이블 컬럼 정보 (로드된 컬럼 우선 사용)
const actualTableColumns = loadedTableColumns.length > 0 ? loadedTableColumns : tableColumns;
// 카드 ID 가져오기 함수 (훅은 조기 리턴 전에 선언)
const getCardKey = useCallback((data: any, index: number): string => {
return String(data.id || data.objid || data.ID || index);
}, []);
// 카드 선택 핸들러 (테이블 리스트와 동일한 로직)
const handleCardSelection = useCallback((cardKey: string, data: any, checked: boolean) => {
const newSelectedRows = new Set(selectedRows);
if (checked) {
newSelectedRows.add(cardKey);
} else {
newSelectedRows.delete(cardKey);
}
setSelectedRows(newSelectedRows);
// 선택된 카드 데이터 계산
const selectedRowsData = displayData.filter((item, index) =>
newSelectedRows.has(getCardKey(item, index))
);
// onFormDataChange 호출
if (onFormDataChange) {
onFormDataChange({
selectedRows: Array.from(newSelectedRows),
selectedRowsData,
});
}
// modalDataStore에 선택된 데이터 저장
const tableNameToUse = componentConfig.dataSource?.tableName || tableName;
if (tableNameToUse && selectedRowsData.length > 0) {
const modalItems = selectedRowsData.map((row, idx) => ({
id: getCardKey(row, idx),
originalData: row,
additionalData: {},
}));
useModalDataStore.getState().setData(tableNameToUse, modalItems);
console.log("✅ [CardDisplay] modalDataStore에 데이터 저장:", {
dataSourceId: tableNameToUse,
count: modalItems.length,
});
} else if (tableNameToUse && selectedRowsData.length === 0) {
useModalDataStore.getState().clearData(tableNameToUse);
console.log("🗑️ [CardDisplay] modalDataStore 데이터 제거:", tableNameToUse);
}
// 분할 패널 컨텍스트에 선택된 데이터 저장 (좌측 화면인 경우)
if (splitPanelContext && splitPanelPosition === "left") {
if (checked) {
splitPanelContext.setSelectedLeftData(data);
console.log("🔗 [CardDisplay] 분할 패널 좌측 데이터 저장:", {
data,
parentDataMapping: splitPanelContext.parentDataMapping,
});
} else if (newSelectedRows.size === 0) {
splitPanelContext.setSelectedLeftData(null);
console.log("🔗 [CardDisplay] 분할 패널 좌측 데이터 초기화");
}
}
}, [selectedRows, displayData, getCardKey, onFormDataChange, componentConfig.dataSource?.tableName, tableName, splitPanelContext, splitPanelPosition]);
const handleCardClick = useCallback((data: any, index: number) => {
const cardKey = getCardKey(data, index);
const isCurrentlySelected = selectedRows.has(cardKey);
// 선택 토글
handleCardSelection(cardKey, data, !isCurrentlySelected);
if (componentConfig.onCardClick) {
componentConfig.onCardClick(data);
}
}, [getCardKey, selectedRows, handleCardSelection, componentConfig.onCardClick]);
// DataProvidable 인터페이스 구현 (테이블 리스트와 동일)
const dataProvider = useMemo(() => ({
componentId: component.id,
componentType: "card-display" as const,
getSelectedData: () => {
const selectedData = displayData.filter((item, index) =>
selectedRows.has(getCardKey(item, index))
);
return selectedData;
},
getAllData: () => {
return displayData;
},
clearSelection: () => {
setSelectedRows(new Set());
},
}), [component.id, displayData, selectedRows, getCardKey]);
// ScreenContext에 데이터 제공자로 등록
useEffect(() => {
if (screenContext && component.id) {
screenContext.registerDataProvider(component.id, dataProvider);
return () => {
screenContext.unregisterDataProvider(component.id);
};
}
}, [screenContext, component.id, dataProvider]);
// 로딩 중인 경우 로딩 표시
if (loading) {
return (
@@ -323,20 +425,6 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
onClick?.();
};
const handleCardClick = (data: any) => {
const cardId = data.id || data.objid || data.ID;
// 이미 선택된 카드를 다시 클릭하면 선택 해제
if (selectedCardId === cardId) {
setSelectedCardId(null);
} else {
setSelectedCardId(cardId);
}
if (componentConfig.onCardClick) {
componentConfig.onCardClick(data);
}
};
// DOM 안전한 props만 필터링 (filterDOMProps 유틸리티 사용)
const safeDomProps = filterDOMProps(props);
@@ -425,12 +513,12 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
? getColumnValue(data, componentConfig.columnMapping.imageColumn)
: data.avatar || data.image || "";
const cardId = data.id || data.objid || data.ID || index;
const isCardSelected = selectedCardId === cardId;
const cardKey = getCardKey(data, index);
const isCardSelected = selectedRows.has(cardKey);
return (
<div
key={cardId}
key={cardKey}
style={{
...cardStyle,
borderColor: isCardSelected ? "#000" : "#e5e7eb",
@@ -440,7 +528,7 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
: "0 1px 3px rgba(0, 0, 0, 0.08)",
}}
className="card-hover group cursor-pointer transition-all duration-150"
onClick={() => handleCardClick(data)}
onClick={() => handleCardClick(data, index)}
>
{/* 카드 이미지 */}
{componentConfig.cardStyle?.showImage && componentConfig.columnMapping?.imageColumn && (