카드 컴포넌트 중간커밋

This commit is contained in:
kjs 2025-12-01 18:39:01 +09:00
parent 617655a42a
commit fb16e224f0
8 changed files with 374 additions and 47 deletions

View File

@ -17,6 +17,7 @@ import { toast } from "sonner";
import { useAuth } from "@/hooks/useAuth";
import { TableOptionsProvider } from "@/contexts/TableOptionsContext";
import { TableSearchWidgetHeightProvider } from "@/contexts/TableSearchWidgetHeightContext";
import { useSplitPanelContext } from "@/contexts/SplitPanelContext";
interface ScreenModalState {
isOpen: boolean;
@ -32,6 +33,7 @@ interface ScreenModalProps {
export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
const { userId, userName, user } = useAuth();
const splitPanelContext = useSplitPanelContext();
const [modalState, setModalState] = useState<ScreenModalState>({
isOpen: false,
@ -152,6 +154,14 @@ export const ScreenModal: React.FC<ScreenModalProps> = ({ className }) => {
setFormData(editData);
setOriginalData(editData); // 🆕 원본 데이터 저장 (UPDATE 판단용)
} else {
// 🆕 신규 등록 모드: 분할 패널 부모 데이터가 있으면 미리 설정
const parentData = splitPanelContext?.getMappedParentData() || {};
if (Object.keys(parentData).length > 0) {
console.log("🔗 [ScreenModal] 분할 패널 부모 데이터 초기값 설정:", parentData);
setFormData(parentData);
} else {
setFormData({});
}
setOriginalData(null); // 신규 등록 모드
}

View File

@ -33,6 +33,7 @@ export function ScreenSplitPanel({ screenId, config, initialFormData }: ScreenSp
leftScreenId: config?.leftScreenId,
rightScreenId: config?.rightScreenId,
configSplitRatio,
parentDataMapping: config?.parentDataMapping,
configKeys: config ? Object.keys(config) : [],
});
@ -125,6 +126,7 @@ export function ScreenSplitPanel({ screenId, config, initialFormData }: ScreenSp
splitPanelId={splitPanelId}
leftScreenId={config?.leftScreenId || null}
rightScreenId={config?.rightScreenId || null}
parentDataMapping={config?.parentDataMapping || []}
>
<div className="flex h-full">
{/* 좌측 패널 */}

View File

@ -17,6 +17,15 @@ export interface SplitPanelDataReceiver {
receiveData: (data: any[], mode: "append" | "replace" | "merge") => Promise<void>;
}
/**
*
*
*/
export interface ParentDataMapping {
sourceColumn: string; // 좌측 화면의 컬럼명 (예: equipment_code)
targetColumn: string; // 우측 화면 저장 시 사용할 컬럼명 (예: equipment_code)
}
/**
*
*/
@ -54,6 +63,16 @@ interface SplitPanelContextValue {
addItemIds: (ids: string[]) => void;
removeItemIds: (ids: string[]) => void;
clearItemIds: () => void;
// 🆕 좌측 선택 데이터 관리 (우측 화면 저장 시 부모 키 전달용)
selectedLeftData: Record<string, any> | null;
setSelectedLeftData: (data: Record<string, any> | null) => void;
// 🆕 부모 데이터 매핑 설정
parentDataMapping: ParentDataMapping[];
// 🆕 매핑된 부모 데이터 가져오기 (우측 화면 저장 시 사용)
getMappedParentData: () => Record<string, any>;
}
const SplitPanelContext = createContext<SplitPanelContextValue | null>(null);
@ -62,6 +81,7 @@ interface SplitPanelProviderProps {
splitPanelId: string;
leftScreenId: number | null;
rightScreenId: number | null;
parentDataMapping?: ParentDataMapping[]; // 🆕 부모 데이터 매핑 설정
children: React.ReactNode;
}
@ -72,6 +92,7 @@ export function SplitPanelProvider({
splitPanelId,
leftScreenId,
rightScreenId,
parentDataMapping = [],
children,
}: SplitPanelProviderProps) {
// 좌측/우측 화면의 데이터 수신자 맵
@ -83,6 +104,9 @@ export function SplitPanelProvider({
// 🆕 우측에 추가된 항목 ID 상태
const [addedItemIds, setAddedItemIds] = useState<Set<string>>(new Set());
// 🆕 좌측에서 선택된 데이터 상태
const [selectedLeftData, setSelectedLeftData] = useState<Record<string, any> | null>(null);
/**
*
@ -232,6 +256,40 @@ export function SplitPanelProvider({
logger.debug(`[SplitPanelContext] 항목 ID 초기화`);
}, []);
/**
* 🆕
*/
const handleSetSelectedLeftData = useCallback((data: Record<string, any> | null) => {
logger.info(`[SplitPanelContext] 좌측 선택 데이터 설정:`, {
hasData: !!data,
dataKeys: data ? Object.keys(data) : [],
});
setSelectedLeftData(data);
}, []);
/**
* 🆕
*
*/
const getMappedParentData = useCallback((): Record<string, any> => {
if (!selectedLeftData || parentDataMapping.length === 0) {
return {};
}
const mappedData: Record<string, any> = {};
for (const mapping of parentDataMapping) {
const value = selectedLeftData[mapping.sourceColumn];
if (value !== undefined && value !== null) {
mappedData[mapping.targetColumn] = value;
logger.debug(`[SplitPanelContext] 부모 데이터 매핑: ${mapping.sourceColumn}${mapping.targetColumn} = ${value}`);
}
}
logger.info(`[SplitPanelContext] 매핑된 부모 데이터:`, mappedData);
return mappedData;
}, [selectedLeftData, parentDataMapping]);
// 🆕 useMemo로 value 객체 메모이제이션 (무한 루프 방지)
const value = React.useMemo<SplitPanelContextValue>(() => ({
splitPanelId,
@ -247,6 +305,11 @@ export function SplitPanelProvider({
addItemIds,
removeItemIds,
clearItemIds,
// 🆕 좌측 선택 데이터 관련
selectedLeftData,
setSelectedLeftData: handleSetSelectedLeftData,
parentDataMapping,
getMappedParentData,
}), [
splitPanelId,
leftScreenId,
@ -260,6 +323,10 @@ export function SplitPanelProvider({
addItemIds,
removeItemIds,
clearItemIds,
selectedLeftData,
handleSetSelectedLeftData,
parentDataMapping,
getMappedParentData,
]);
return (

View File

@ -692,6 +692,15 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
effectiveScreenId,
});
// 🆕 분할 패널 부모 데이터 가져오기 (우측 화면에서 저장 시 좌측 선택 데이터 포함)
let splitPanelParentData: Record<string, any> | undefined;
if (splitPanelContext && splitPanelPosition === "right") {
splitPanelParentData = splitPanelContext.getMappedParentData();
if (Object.keys(splitPanelParentData).length > 0) {
console.log("🔗 [ButtonPrimaryComponent] 분할 패널 부모 데이터 포함:", splitPanelParentData);
}
}
const context: ButtonActionContext = {
formData: formData || {},
originalData: originalData, // 🔧 빈 객체 대신 undefined 유지 (UPDATE 판단에 사용)
@ -720,6 +729,8 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
flowSelectedStepId,
// 🆕 컴포넌트별 설정 (parentDataMapping 등)
componentConfigs,
// 🆕 분할 패널 부모 데이터 (좌측 화면에서 선택된 데이터)
splitPanelParentData,
} as ButtonActionContext;
// 확인이 필요한 액션인지 확인

View File

@ -43,6 +43,9 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
const [loadedTableColumns, setLoadedTableColumns] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
// 선택된 카드 상태
const [selectedCardId, setSelectedCardId] = useState<string | number | null>(null);
// 상세보기 모달 상태
const [viewModalOpen, setViewModalOpen] = useState(false);
const [selectedData, setSelectedData] = useState<any>(null);
@ -261,26 +264,19 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
borderRadius: "12px", // 컨테이너 자체도 라운드 처리
};
// 카드 스타일 - 통일된 디자인 시스템 적용
// 카드 스타일 - 컴팩트한 디자인
const cardStyle: React.CSSProperties = {
backgroundColor: "white",
border: "2px solid #e5e7eb", // 더 명확한 테두리
borderRadius: "12px", // 통일된 라운드 처리
padding: "24px", // 더 여유로운 패딩
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)", // 더 깊은 그림자
transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)", // 부드러운 트랜지션
border: "1px solid #e5e7eb",
borderRadius: "8px",
padding: "16px",
boxShadow: "0 1px 3px rgba(0, 0, 0, 0.08)",
transition: "all 0.2s ease",
overflow: "hidden",
display: "flex",
flexDirection: "column",
position: "relative",
minHeight: "240px", // 최소 높이 더 증가
cursor: isDesignMode ? "pointer" : "default",
// 호버 효과를 위한 추가 스타일
"&:hover": {
transform: "translateY(-2px)",
boxShadow: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)",
borderColor: "#f59e0b", // 호버 시 오렌지 테두리
}
};
// 텍스트 자르기 함수
@ -328,6 +324,14 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
};
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);
}
@ -421,67 +425,75 @@ 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;
return (
<div
key={data.id || index}
style={cardStyle}
className="card-hover group cursor-pointer"
key={cardId}
style={{
...cardStyle,
borderColor: isCardSelected ? "#000" : "#e5e7eb",
borderWidth: isCardSelected ? "2px" : "1px",
boxShadow: isCardSelected
? "0 4px 6px -1px rgba(0, 0, 0, 0.15)"
: "0 1px 3px rgba(0, 0, 0, 0.08)",
}}
className="card-hover group cursor-pointer transition-all duration-150"
onClick={() => handleCardClick(data)}
>
{/* 카드 이미지 - 통일된 디자인 */}
{/* 카드 이미지 */}
{componentConfig.cardStyle?.showImage && componentConfig.columnMapping?.imageColumn && (
<div className="mb-4 flex justify-center">
<div className="flex h-20 w-20 items-center justify-center rounded-full bg-gradient-to-br from-primary/10 to-primary/20 shadow-sm border-2 border-background">
<span className="text-2xl text-primary">👤</span>
<div className="mb-2 flex justify-center">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-primary/10">
<span className="text-lg text-primary">👤</span>
</div>
</div>
)}
{/* 카드 타이틀 - 통일된 디자인 */}
{componentConfig.cardStyle?.showTitle && (
<div className="mb-3">
<h3 className="text-xl font-bold text-foreground leading-tight">{titleValue}</h3>
{/* 카드 타이틀 + 서브타이틀 (가로 배치) */}
{(componentConfig.cardStyle?.showTitle || componentConfig.cardStyle?.showSubtitle) && (
<div className="mb-2 flex items-center gap-2 flex-wrap">
{componentConfig.cardStyle?.showTitle && (
<h3 className="text-base font-semibold text-foreground leading-tight">{titleValue}</h3>
)}
{componentConfig.cardStyle?.showSubtitle && subtitleValue && (
<span className="text-xs font-medium text-primary bg-primary/10 px-2 py-0.5 rounded-full">{subtitleValue}</span>
)}
</div>
)}
{/* 카드 서브타이틀 - 통일된 디자인 */}
{componentConfig.cardStyle?.showSubtitle && (
<div className="mb-3">
<p className="text-sm font-semibold text-primary bg-primary/10 px-3 py-1 rounded-full inline-block">{subtitleValue}</p>
</div>
)}
{/* 카드 설명 - 통일된 디자인 */}
{/* 카드 설명 */}
{componentConfig.cardStyle?.showDescription && (
<div className="mb-4 flex-1">
<p className="text-sm leading-relaxed text-foreground bg-muted p-3 rounded-lg">
<div className="mb-2 flex-1">
<p className="text-xs text-muted-foreground leading-relaxed">
{truncateText(descriptionValue, componentConfig.cardStyle?.maxDescriptionLength || 100)}
</p>
</div>
)}
{/* 추가 표시 컬럼들 - 통일된 디자인 */}
{/* 추가 표시 컬럼들 - 가로 배치 */}
{componentConfig.columnMapping?.displayColumns &&
componentConfig.columnMapping.displayColumns.length > 0 && (
<div className="space-y-2 border-t border-border pt-4">
<div className="flex flex-wrap items-center gap-x-3 gap-y-0.5 border-t border-border pt-2 text-xs">
{componentConfig.columnMapping.displayColumns.map((columnName, idx) => {
const value = getColumnValue(data, columnName);
if (!value) return null;
return (
<div key={idx} className="flex justify-between items-center text-sm bg-background/50 px-3 py-2 rounded-lg border border-border">
<span className="text-muted-foreground font-medium capitalize">{getColumnLabel(columnName)}:</span>
<span className="font-semibold text-foreground bg-muted px-2 py-1 rounded-md text-xs">{value}</span>
<div key={idx} className="flex items-center gap-1">
<span className="text-muted-foreground">{getColumnLabel(columnName)}:</span>
<span className="font-medium text-foreground">{value}</span>
</div>
);
})}
</div>
)}
{/* 카드 액션 (선택사항) */}
<div className="mt-3 flex justify-end space-x-2">
{/* 카드 액션 */}
<div className="mt-2 flex justify-end space-x-2">
<button
className="text-xs font-medium text-blue-600 hover:text-blue-800 transition-colors"
className="text-xs text-blue-600 hover:text-blue-800 transition-colors"
onClick={(e) => {
e.stopPropagation();
handleCardView(data);
@ -490,7 +502,7 @@ export const CardDisplayComponent: React.FC<CardDisplayComponentProps> = ({
</button>
<button
className="text-xs font-medium text-muted-foreground hover:text-foreground transition-colors"
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
onClick={(e) => {
e.stopPropagation();
handleCardEdit(data);

View File

@ -9,11 +9,13 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import { Settings, Layout, ArrowRight, Database, Loader2, Check, ChevronsUpDown } from "lucide-react";
import { Settings, Layout, ArrowRight, Database, Loader2, Check, ChevronsUpDown, Plus, Trash2, Link2 } from "lucide-react";
import { screenApi } from "@/lib/api/screen";
import { getTableColumns } from "@/lib/api/tableManagement";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import type { ParentDataMapping } from "@/contexts/SplitPanelContext";
interface ScreenSplitPanelConfigPanelProps {
config: any;
@ -29,6 +31,10 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
const [leftOpen, setLeftOpen] = useState(false);
const [rightOpen, setRightOpen] = useState(false);
// 좌측 화면의 테이블 컬럼 목록
const [leftScreenColumns, setLeftScreenColumns] = useState<Array<{ columnName: string; columnLabel: string }>>([]);
const [isLoadingColumns, setIsLoadingColumns] = useState(false);
const [localConfig, setLocalConfig] = useState({
screenId: config.screenId || 0,
leftScreenId: config.leftScreenId || 0,
@ -37,6 +43,7 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
resizable: config.resizable ?? true,
buttonLabel: config.buttonLabel || "데이터 전달",
buttonPosition: config.buttonPosition || "center",
parentDataMapping: config.parentDataMapping || [] as ParentDataMapping[],
...config,
});
@ -51,10 +58,51 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
resizable: config.resizable ?? true,
buttonLabel: config.buttonLabel || "데이터 전달",
buttonPosition: config.buttonPosition || "center",
parentDataMapping: config.parentDataMapping || [],
...config,
});
}, [config]);
// 좌측 화면이 변경되면 해당 화면의 테이블 컬럼 로드
useEffect(() => {
const loadLeftScreenColumns = async () => {
if (!localConfig.leftScreenId) {
setLeftScreenColumns([]);
return;
}
try {
setIsLoadingColumns(true);
// 좌측 화면 정보 조회
const screenData = await screenApi.getScreen(localConfig.leftScreenId);
if (!screenData?.tableName) {
console.warn("좌측 화면에 테이블이 설정되지 않았습니다.");
setLeftScreenColumns([]);
return;
}
// 테이블 컬럼 조회
const columnsResponse = await getTableColumns(screenData.tableName);
if (columnsResponse.success && columnsResponse.data?.columns) {
const columns = columnsResponse.data.columns.map((col: any) => ({
columnName: col.column_name || col.columnName,
columnLabel: col.column_label || col.columnLabel || col.column_name || col.columnName,
}));
setLeftScreenColumns(columns);
console.log("📋 좌측 화면 컬럼 로드 완료:", columns.length);
}
} catch (error) {
console.error("좌측 화면 컬럼 로드 실패:", error);
setLeftScreenColumns([]);
} finally {
setIsLoadingColumns(false);
}
};
loadLeftScreenColumns();
}, [localConfig.leftScreenId]);
// 화면 목록 로드
useEffect(() => {
const loadScreens = async () => {
@ -94,10 +142,36 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
}
};
// 부모 데이터 매핑 추가
const addParentDataMapping = () => {
const newMapping: ParentDataMapping = {
sourceColumn: "",
targetColumn: "",
};
const newMappings = [...(localConfig.parentDataMapping || []), newMapping];
updateConfig("parentDataMapping", newMappings);
};
// 부모 데이터 매핑 수정
const updateParentDataMapping = (index: number, field: keyof ParentDataMapping, value: string) => {
const newMappings = [...(localConfig.parentDataMapping || [])];
newMappings[index] = {
...newMappings[index],
[field]: value,
};
updateConfig("parentDataMapping", newMappings);
};
// 부모 데이터 매핑 삭제
const removeParentDataMapping = (index: number) => {
const newMappings = (localConfig.parentDataMapping || []).filter((_: any, i: number) => i !== index);
updateConfig("parentDataMapping", newMappings);
};
return (
<div className="space-y-4">
<Tabs defaultValue="layout" className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="layout" className="gap-2">
<Layout className="h-4 w-4" />
@ -106,6 +180,10 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
<Database className="h-4 w-4" />
</TabsTrigger>
<TabsTrigger value="dataMapping" className="gap-2">
<Link2 className="h-4 w-4" />
</TabsTrigger>
</TabsList>
{/* 레이아웃 탭 */}
@ -295,7 +373,7 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
<div className="rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-900 dark:bg-amber-950">
<p className="text-xs text-amber-800 dark:text-amber-200">
💡 <strong> :</strong> ,
방법: ,
"transferData" .
<br />
(), , .
@ -306,6 +384,119 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
</CardContent>
</Card>
</TabsContent>
{/* 데이터 전달 탭 */}
<TabsContent value="dataMapping" className="space-y-4">
<Card>
<CardHeader>
<CardTitle className="text-base"> </CardTitle>
<CardDescription className="text-xs">
, / .
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{!localConfig.leftScreenId ? (
<div className="rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-900 dark:bg-amber-950">
<p className="text-xs text-amber-800 dark:text-amber-200">
"화면 설정" .
</p>
</div>
) : isLoadingColumns ? (
<div className="flex items-center justify-center py-4">
<Loader2 className="text-muted-foreground h-5 w-5 animate-spin" />
<span className="text-muted-foreground ml-2 text-xs"> ...</span>
</div>
) : leftScreenColumns.length === 0 ? (
<div className="rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-900 dark:bg-amber-950">
<p className="text-xs text-amber-800 dark:text-amber-200">
.
</p>
</div>
) : (
<>
{/* 매핑 목록 */}
<div className="space-y-3">
{(localConfig.parentDataMapping || []).map((mapping: ParentDataMapping, index: number) => (
<div key={index} className="flex items-center gap-2 rounded-lg border bg-gray-50 p-3 dark:bg-gray-900">
<div className="flex-1 space-y-2">
<div className="flex items-center gap-2">
<div className="flex-1">
<Label className="text-xs text-gray-600"> ()</Label>
<Select
value={mapping.sourceColumn}
onValueChange={(value) => updateParentDataMapping(index, "sourceColumn", value)}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="컬럼 선택" />
</SelectTrigger>
<SelectContent>
{leftScreenColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName} className="text-xs">
{col.columnLabel} ({col.columnName})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<ArrowRight className="mt-5 h-4 w-4 text-gray-400" />
<div className="flex-1">
<Label className="text-xs text-gray-600"> ( )</Label>
<Input
value={mapping.targetColumn}
onChange={(e) => updateParentDataMapping(index, "targetColumn", e.target.value)}
placeholder="저장할 컬럼명"
className="h-8 text-xs"
/>
</div>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => removeParentDataMapping(index)}
className="h-8 w-8 p-0 text-red-500 hover:bg-red-50 hover:text-red-600"
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
))}
</div>
{/* 매핑 추가 버튼 */}
<Button
variant="outline"
size="sm"
onClick={addParentDataMapping}
className="w-full"
>
<Plus className="mr-2 h-4 w-4" />
</Button>
{/* 안내 메시지 */}
<div className="rounded-lg border border-blue-200 bg-blue-50 p-3 dark:border-blue-900 dark:bg-blue-950">
<p className="text-xs text-blue-800 dark:text-blue-200">
<strong> :</strong>
<br />
좌측: 설비 (equipment_mng)
<br />
우측: 점검항목
<br />
<br />
:
<br />
- 소스: equipment_code 타겟: equipment_code
<br />
<br />
,
equipment_code가 .
</p>
</div>
</>
)}
</CardContent>
</Card>
</TabsContent>
</Tabs>
{/* 설정 요약 */}
@ -343,6 +534,14 @@ export function ScreenSplitPanelConfigPanel({ config = {}, onChange }: ScreenSpl
<span className="text-muted-foreground"> :</span>
<span className="font-medium">{localConfig.resizable ? "가능" : "불가능"}</span>
</div>
<div className="flex justify-between">
<span className="text-muted-foreground"> :</span>
<span className="font-medium">
{(localConfig.parentDataMapping || []).length > 0
? `${localConfig.parentDataMapping.length}개 설정`
: "미설정"}
</span>
</div>
</div>
</CardContent>
</Card>

View File

@ -1466,6 +1466,22 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
handleRowSelection(rowKey, !isCurrentlySelected);
// 🆕 분할 패널 컨텍스트에 선택된 데이터 저장 (좌측 화면인 경우)
if (splitPanelContext && splitPanelPosition === "left") {
if (!isCurrentlySelected) {
// 선택된 경우: 데이터 저장
splitPanelContext.setSelectedLeftData(row);
console.log("🔗 [TableList] 분할 패널 좌측 데이터 저장:", {
row,
parentDataMapping: splitPanelContext.parentDataMapping,
});
} else {
// 선택 해제된 경우: 데이터 초기화
splitPanelContext.setSelectedLeftData(null);
console.log("🔗 [TableList] 분할 패널 좌측 데이터 초기화");
}
}
console.log("행 클릭:", { row, index, isSelected: !isCurrentlySelected });
};

View File

@ -215,6 +215,9 @@ export interface ButtonActionContext {
// 🆕 컴포넌트별 설정 (parentDataMapping 등)
componentConfigs?: Record<string, any>; // 컴포넌트 ID → 컴포넌트 설정
// 🆕 분할 패널 부모 데이터 (좌측 화면에서 선택된 데이터)
splitPanelParentData?: Record<string, any>;
}
/**
@ -502,8 +505,15 @@ export class ButtonActionExecutor {
// console.log("✅ 채번 규칙 할당 완료");
// console.log("📦 최종 formData:", JSON.stringify(formData, null, 2));
// 🆕 분할 패널 부모 데이터 병합 (좌측 화면에서 선택된 데이터)
const splitPanelData = context.splitPanelParentData || {};
if (Object.keys(splitPanelData).length > 0) {
console.log("🔗 [handleSave] 분할 패널 부모 데이터 병합:", splitPanelData);
}
const dataWithUserInfo = {
...formData,
...splitPanelData, // 분할 패널 부모 데이터 먼저 적용
...formData, // 폼 데이터가 우선 (덮어쓰기 가능)
writer: formData.writer || writerValue, // ✅ 입력값 우선, 없으면 userId
created_by: writerValue, // created_by는 항상 로그인한 사람
updated_by: writerValue, // updated_by는 항상 로그인한 사람