Merge branch 'jskim-node' of http://39.117.244.52:3000/kjs/ERP-node into gbpark-node

; Please enter a commit message to explain why this merge is necessary,
; especially if it merges an updated upstream into a topic branch.
;
; Lines starting with ';' will be ignored, and an empty message aborts
; the commit.
This commit is contained in:
DDD1542
2026-02-12 16:33:00 +09:00
13 changed files with 1668 additions and 223 deletions

View File

@@ -194,6 +194,17 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
const [selectedLeftItem, setSelectedLeftItem] = useState<any>(null);
const [expandedRightItems, setExpandedRightItems] = useState<Set<string | number>>(new Set()); // 확장된 우측 아이템
const [customLeftSelectedData, setCustomLeftSelectedData] = useState<Record<string, any>>({}); // 커스텀 모드: 좌측 선택 데이터
// 커스텀 모드: 탭/버튼 간 공유할 selectedRowsData 자체 관리 (항상 로컬 상태 사용)
const [localSelectedRowsData, setLocalSelectedRowsData] = useState<any[]>([]);
const handleLocalSelectedRowsChange = useCallback(
(selectedRows: any[], selectedRowsDataNew: any[], sortBy?: string, sortOrder?: "asc" | "desc", columnOrder?: string[]) => {
setLocalSelectedRowsData(selectedRowsDataNew);
if ((props as any).onSelectedRowsChange) {
(props as any).onSelectedRowsChange(selectedRows, selectedRowsDataNew, sortBy, sortOrder, columnOrder);
}
},
[(props as any).onSelectedRowsChange],
);
const [leftSearchQuery, setLeftSearchQuery] = useState("");
const [rightSearchQuery, setRightSearchQuery] = useState("");
const [isLoadingLeft, setIsLoadingLeft] = useState(false);
@@ -1553,6 +1564,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
if (isSameItem) {
// 선택 해제 → 전체 데이터 로드
setSelectedLeftItem(null);
setCustomLeftSelectedData({}); // 커스텀 모드 우측 폼 데이터 초기화
setExpandedRightItems(new Set());
setTabsData({});
if (activeTabIndex === 0) {
@@ -1573,6 +1585,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
}
setSelectedLeftItem(item);
setCustomLeftSelectedData(item); // 커스텀 모드 우측 폼에 선택된 데이터 전달
setExpandedRightItems(new Set()); // 좌측 항목 변경 시 우측 확장 초기화
setTabsData({}); // 모든 탭 데이터 초기화
@@ -1980,6 +1993,88 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
// 추가 버튼 핸들러
const handleAddClick = useCallback(
(panel: "left" | "right") => {
// 좌측 패널 추가 시, addButton 모달 모드 확인
if (panel === "left") {
const addButtonConfig = componentConfig.leftPanel?.addButton;
if (addButtonConfig?.mode === "modal" && addButtonConfig?.modalScreenId) {
const leftTableName = componentConfig.leftPanel?.tableName || "";
// ScreenModal 열기 이벤트 발생
window.dispatchEvent(
new CustomEvent("openScreenModal", {
detail: {
screenId: addButtonConfig.modalScreenId,
urlParams: {
mode: "add",
tableName: leftTableName,
},
},
}),
);
console.log("✅ [SplitPanel] 좌측 추가 모달 화면 열기:", {
screenId: addButtonConfig.modalScreenId,
tableName: leftTableName,
});
return;
}
}
// 우측 패널 추가 시, addButton 모달 모드 확인
if (panel === "right") {
const addButtonConfig =
activeTabIndex === 0
? componentConfig.rightPanel?.addButton
: (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.addButton;
if (addButtonConfig?.mode === "modal" && addButtonConfig?.modalScreenId) {
// 커스텀 모달 화면 열기
const currentTableName =
activeTabIndex === 0
? componentConfig.rightPanel?.tableName || ""
: (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.tableName || "";
// 좌측 선택 데이터를 modalDataStore에 저장 (추가 화면에서 참조 가능)
if (selectedLeftItem && componentConfig.leftPanel?.tableName) {
import("@/stores/modalDataStore").then(({ useModalDataStore }) => {
useModalDataStore.getState().setData(componentConfig.leftPanel!.tableName!, [selectedLeftItem]);
});
}
// ScreenModal 열기 이벤트 발생
window.dispatchEvent(
new CustomEvent("openScreenModal", {
detail: {
screenId: addButtonConfig.modalScreenId,
urlParams: {
mode: "add",
tableName: currentTableName,
// 좌측 선택 항목의 연결 키 값 전달
...(selectedLeftItem && (() => {
const relation = activeTabIndex === 0
? componentConfig.rightPanel?.relation
: (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.relation;
const leftColumn = relation?.keys?.[0]?.leftColumn || relation?.leftColumn;
const rightColumn = relation?.keys?.[0]?.rightColumn || relation?.foreignKey;
if (leftColumn && rightColumn && selectedLeftItem[leftColumn] !== undefined) {
return { [rightColumn]: selectedLeftItem[leftColumn] };
}
return {};
})()),
},
},
}),
);
console.log("✅ [SplitPanel] 추가 모달 화면 열기:", {
screenId: addButtonConfig.modalScreenId,
tableName: currentTableName,
});
return;
}
}
// 기존 내장 추가 모달 로직
setAddModalPanel(panel);
// 우측 패널 추가 시, 좌측에서 선택된 항목의 조인 컬럼 값을 자동으로 채움
@@ -1999,12 +2094,59 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
setShowAddModal(true);
},
[selectedLeftItem, componentConfig],
[selectedLeftItem, componentConfig, activeTabIndex],
);
// 수정 버튼 핸들러
const handleEditClick = useCallback(
(panel: "left" | "right", item: any) => {
// 좌측 패널 수정 버튼 설정 확인 (모달 모드)
if (panel === "left") {
const editButtonConfig = componentConfig.leftPanel?.editButton;
if (editButtonConfig?.mode === "modal" && editButtonConfig?.modalScreenId) {
const leftTableName = componentConfig.leftPanel?.tableName || "";
// Primary Key 찾기 - 실제 DB의 id 컬럼 값을 우선 사용
let primaryKeyValue = item.id || item.ID;
if (primaryKeyValue === undefined || primaryKeyValue === null) {
// id가 없으면 sourceColumn 시도, 마지막으로 첫 번째 키
const sourceColumn = componentConfig.leftPanel?.itemAddConfig?.sourceColumn || "id";
primaryKeyValue = item[sourceColumn];
if (primaryKeyValue === undefined || primaryKeyValue === null) {
const firstKey = Object.keys(item)[0];
primaryKeyValue = item[firstKey];
}
}
// modalDataStore에 저장
import("@/stores/modalDataStore").then(({ useModalDataStore }) => {
useModalDataStore.getState().setData(leftTableName, [item]);
});
// ScreenModal 열기 이벤트 발생
window.dispatchEvent(
new CustomEvent("openScreenModal", {
detail: {
screenId: editButtonConfig.modalScreenId,
urlParams: {
mode: "edit",
editId: primaryKeyValue,
tableName: leftTableName,
},
},
}),
);
console.log("✅ [SplitPanel] 좌측 수정 모달 화면 열기:", {
screenId: editButtonConfig.modalScreenId,
tableName: leftTableName,
primaryKeyValue,
});
return;
}
}
// 우측 패널 수정 버튼 설정 확인 (탭별 설정 지원)
if (panel === "right") {
const editButtonConfig =
@@ -2112,6 +2254,81 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
[componentConfig, activeTabIndex],
);
// 커스텀 모드 우측 패널 저장 (인라인 편집 데이터)
const handleCustomRightSave = useCallback(async () => {
if (!selectedLeftItem || !customLeftSelectedData || Object.keys(customLeftSelectedData).length === 0) {
toast({
title: "저장 실패",
description: "저장할 데이터가 없습니다. 좌측에서 항목을 선택해주세요.",
variant: "destructive",
});
return;
}
const tableName = componentConfig.rightPanel?.tableName || componentConfig.leftPanel?.tableName;
if (!tableName) {
toast({
title: "저장 실패",
description: "테이블 정보가 없습니다.",
variant: "destructive",
});
return;
}
// Primary Key 찾기 - 실제 DB의 id 컬럼 값을 사용 (sourceColumn은 관계 연결용이므로 PK로 사용하지 않음)
const primaryKey = selectedLeftItem.id || selectedLeftItem.ID;
if (!primaryKey) {
toast({
title: "저장 실패",
description: "Primary Key를 찾을 수 없습니다.",
variant: "destructive",
});
return;
}
try {
// 프론트엔드 전용 필드 제거
const cleanData = { ...customLeftSelectedData };
delete cleanData.children;
delete cleanData.level;
delete cleanData._originalItems;
// company_code 자동 추가
if (companyCode && !cleanData.company_code) {
cleanData.company_code = companyCode;
}
console.log("📝 [SplitPanel] 커스텀 우측 패널 저장:", { tableName, primaryKey, data: cleanData });
const response = await dataApi.updateRecord(tableName, primaryKey, cleanData);
if (response.success) {
toast({
title: "저장 완료",
description: "데이터가 저장되었습니다.",
});
// 좌측 데이터 새로고침 (변경된 항목 반영)
loadLeftData();
// selectedLeftItem도 업데이트
setSelectedLeftItem(customLeftSelectedData);
} else {
toast({
title: "저장 실패",
description: response.error || "데이터 저장에 실패했습니다.",
variant: "destructive",
});
}
} catch (error) {
console.error("커스텀 우측 패널 저장 오류:", error);
toast({
title: "저장 오류",
description: "데이터 저장 중 오류가 발생했습니다.",
variant: "destructive",
});
}
}, [selectedLeftItem, customLeftSelectedData, componentConfig, companyCode, toast, loadLeftData]);
// 수정 모달 저장
const handleEditModalSave = useCallback(async () => {
const tableName =
@@ -2304,7 +2521,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
if (deleteModalPanel === "left") {
loadLeftData();
// 삭제된 항목이 선택되어 있었으면 선택 해제
if (selectedLeftItem && selectedLeftItem[sourceColumn] === primaryKey) {
const deletedId = deleteModalItem?.id || deleteModalItem?.ID;
if (selectedLeftItem && (selectedLeftItem.id === deletedId || selectedLeftItem.ID === deletedId)) {
setSelectedLeftItem(null);
setRightData(null);
}
@@ -2743,6 +2961,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
const displayHeight = isResizingComp && resizeSize ? resizeSize.height : (comp.size?.height || 100);
// 컴포넌트 데이터를 DynamicComponentRenderer 형식으로 변환
// componentConfig의 주요 속성을 최상위로 펼침 (일반 화면의 overrides 플래트닝과 동일)
const componentData = {
id: comp.id,
type: "component" as const,
@@ -2752,6 +2971,11 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
size: { width: displayWidth, height: displayHeight },
componentConfig: comp.componentConfig || {},
style: comp.style || {},
// 파일 업로드/미디어 등이 component.tableName, component.columnName을 직접 참조하므로 펼침
tableName: comp.componentConfig?.tableName,
columnName: comp.componentConfig?.columnName,
webType: comp.componentConfig?.webType,
inputType: comp.inputType || comp.componentConfig?.inputType,
};
if (isDesignMode) {
@@ -2920,8 +3144,17 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
<DynamicComponentRenderer
component={componentData as any}
isDesignMode={false}
isInteractive={true}
formData={{}}
tableName={componentConfig.leftPanel?.tableName}
menuObjid={(props as any).menuObjid}
screenId={(props as any).screenId}
userId={(props as any).userId}
userName={(props as any).userName}
companyCode={companyCode}
allComponents={(props as any).allComponents}
selectedRowsData={localSelectedRowsData}
onSelectedRowsChange={handleLocalSelectedRowsChange}
onFormDataChange={(data: any) => {
// 커스텀 모드: 좌측 카드/테이블 선택 시 데이터 캡처
if (data?.selectedRowsData && data.selectedRowsData.length > 0) {
@@ -3317,29 +3550,33 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
{/* 항목별 버튼들 */}
{!isDesignMode && (
<div className="flex flex-shrink-0 items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100">
{/* 수정 버튼 */}
<button
onClick={(e) => {
e.stopPropagation();
handleEditClick("left", item);
}}
className="rounded p-1 transition-colors hover:bg-gray-200"
title="수정"
>
<Pencil className="h-4 w-4 text-gray-600" />
</button>
{/* 수정 버튼 (showEdit 활성화 시에만 표시) */}
{(componentConfig.leftPanel?.showEdit !== false) && (
<button
onClick={(e) => {
e.stopPropagation();
handleEditClick("left", item);
}}
className="rounded p-1 transition-colors hover:bg-gray-200"
title="수정"
>
<Pencil className="h-4 w-4 text-gray-600" />
</button>
)}
{/* 삭제 버튼 */}
<button
onClick={(e) => {
e.stopPropagation();
handleDeleteClick("left", item);
}}
className="rounded p-1 transition-colors hover:bg-red-100"
title="삭제"
>
<Trash2 className="h-4 w-4 text-red-600" />
</button>
{/* 삭제 버튼 (showDelete 활성화 시에만 표시) */}
{(componentConfig.leftPanel?.showDelete !== false) && (
<button
onClick={(e) => {
e.stopPropagation();
handleDeleteClick("left", item);
}}
className="rounded p-1 transition-colors hover:bg-red-100"
title="삭제"
>
<Trash2 className="h-4 w-4 text-red-600" />
</button>
)}
{/* 항목별 추가 버튼 */}
{componentConfig.leftPanel?.showItemAddButton && (
@@ -3456,6 +3693,13 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
</div>
{!isDesignMode && (
<div className="flex items-center gap-2">
{/* 커스텀 모드 기본정보 탭: 저장 버튼 */}
{activeTabIndex === 0 && componentConfig.rightPanel?.displayMode === "custom" && selectedLeftItem && (
<Button size="sm" variant="default" onClick={handleCustomRightSave}>
<Save className="mr-1 h-4 w-4" />
</Button>
)}
{activeTabIndex === 0
? componentConfig.rightPanel?.showAdd && (
<Button size="sm" variant="outline" onClick={() => handleAddClick("right")}>
@@ -3736,6 +3980,15 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
})()
) : componentConfig.rightPanel?.displayMode === "custom" ? (
// 🆕 커스텀 모드: 패널 안에 자유롭게 컴포넌트 배치
// 실행 모드에서 좌측 미선택 시 안내 메시지 표시
!isDesignMode && !selectedLeftItem ? (
<div className="flex h-full items-center justify-center">
<div className="text-muted-foreground text-center text-sm">
<p className="mb-2"> </p>
<p className="text-xs"> </p>
</div>
</div>
) : (
<div
className="relative h-full w-full"
data-split-panel-container="true"
@@ -3757,6 +4010,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
const displayHeight = isResizingComp && resizeSize ? resizeSize.height : (comp.size?.height || 100);
// 컴포넌트 데이터를 DynamicComponentRenderer 형식으로 변환
// componentConfig의 주요 속성을 최상위로 펼침 (일반 화면의 overrides 플래트닝과 동일)
const componentData = {
id: comp.id,
type: "component" as const,
@@ -3766,6 +4020,11 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
size: { width: displayWidth, height: displayHeight },
componentConfig: comp.componentConfig || {},
style: comp.style || {},
// 파일 업로드/미디어 등이 component.tableName, component.columnName을 직접 참조하므로 펼침
tableName: comp.componentConfig?.tableName,
columnName: comp.componentConfig?.columnName,
webType: comp.componentConfig?.webType,
inputType: comp.inputType || comp.componentConfig?.inputType,
};
if (isDesignMode) {
@@ -3923,8 +4182,20 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
<DynamicComponentRenderer
component={componentData as any}
isDesignMode={false}
isInteractive={true}
formData={customLeftSelectedData}
onFormDataChange={(fieldName: string, value: any) => {
setCustomLeftSelectedData((prev: Record<string, any>) => ({ ...prev, [fieldName]: value }));
}}
tableName={componentConfig.rightPanel?.tableName || componentConfig.leftPanel?.tableName}
menuObjid={(props as any).menuObjid}
screenId={(props as any).screenId}
userId={(props as any).userId}
userName={(props as any).userName}
companyCode={companyCode}
allComponents={(props as any).allComponents}
selectedRowsData={localSelectedRowsData}
onSelectedRowsChange={handleLocalSelectedRowsChange}
/>
</div>
);
@@ -3944,6 +4215,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
</div>
)}
</div>
)
) : isLoadingRight ? (
// 로딩 중
<div className="flex h-full items-center justify-center">

View File

@@ -1066,6 +1066,62 @@ const AdditionalTabConfigPanel: React.FC<AdditionalTabConfigPanelProps> = ({
</div>
)}
{/* ===== 10-1. 추가 버튼 설정 ===== */}
{tab.showAdd && (
<div className="space-y-3 rounded-lg border border-border/50 bg-muted/40 p-3">
<h3 className="border-l-2 border-l-primary/40 pl-2 text-xs font-semibold"> </h3>
<div className="space-y-2">
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<Select
value={tab.addButton?.mode || "auto"}
onValueChange={(value: "auto" | "modal") => {
updateTab({
addButton: { ...tab.addButton, enabled: true, mode: value },
});
}}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="auto"> ( )</SelectItem>
<SelectItem value="modal"> </SelectItem>
</SelectContent>
</Select>
</div>
{tab.addButton?.mode === "modal" && (
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<ScreenSelector
value={tab.addButton?.modalScreenId}
onChange={(screenId) => {
updateTab({
addButton: { ...tab.addButton, enabled: true, mode: "modal", modalScreenId: screenId },
});
}}
/>
</div>
)}
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<Input
value={tab.addButton?.buttonLabel || ""}
onChange={(e) => {
updateTab({
addButton: { ...tab.addButton, enabled: true, buttonLabel: e.target.value || undefined },
});
}}
placeholder="추가"
className="h-7 text-xs"
/>
</div>
</div>
</div>
)}
{/* ===== 11. 삭제 버튼 설정 ===== */}
{tab.showDelete && (
<div className="space-y-3 rounded-lg border border-border/50 bg-muted/40 p-3">
@@ -2072,6 +2128,169 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
menuObjid={menuObjid} // 🆕 메뉴 OBJID 전달
/>
</div>
{/* 좌측 패널 버튼 설정 */}
<div className="space-y-4 rounded-lg border border-border/50 bg-muted/40 p-4">
<h3 className="border-l-2 border-l-primary/40 pl-2 text-sm font-semibold"> </h3>
{/* 버튼 표시 체크박스 */}
<div className="grid grid-cols-4 gap-2">
<div className="flex items-center gap-1">
<Checkbox
id="left-showSearch"
checked={config.leftPanel?.showSearch ?? false}
onCheckedChange={(checked) => updateLeftPanel({ showSearch: !!checked })}
/>
<label htmlFor="left-showSearch" className="text-xs"></label>
</div>
<div className="flex items-center gap-1">
<Checkbox
id="left-showAdd"
checked={config.leftPanel?.showAdd ?? false}
onCheckedChange={(checked) => updateLeftPanel({ showAdd: !!checked })}
/>
<label htmlFor="left-showAdd" className="text-xs"></label>
</div>
<div className="flex items-center gap-1">
<Checkbox
id="left-showEdit"
checked={config.leftPanel?.showEdit ?? true}
onCheckedChange={(checked) => updateLeftPanel({ showEdit: !!checked })}
/>
<label htmlFor="left-showEdit" className="text-xs"></label>
</div>
<div className="flex items-center gap-1">
<Checkbox
id="left-showDelete"
checked={config.leftPanel?.showDelete ?? true}
onCheckedChange={(checked) => updateLeftPanel({ showDelete: !!checked })}
/>
<label htmlFor="left-showDelete" className="text-xs"></label>
</div>
</div>
{/* 추가 버튼 상세 설정 */}
{config.leftPanel?.showAdd && (
<div className="space-y-3 rounded-lg border border-border/50 bg-muted/40 p-3">
<h3 className="border-l-2 border-l-primary/40 pl-2 text-xs font-semibold"> </h3>
<div className="space-y-2">
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<Select
value={config.leftPanel?.addButton?.mode || "auto"}
onValueChange={(value: "auto" | "modal") =>
updateLeftPanel({
addButton: { ...config.leftPanel?.addButton, enabled: true, mode: value },
})
}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="auto"> ( )</SelectItem>
<SelectItem value="modal"> </SelectItem>
</SelectContent>
</Select>
</div>
{config.leftPanel?.addButton?.mode === "modal" && (
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<ScreenSelector
value={config.leftPanel?.addButton?.modalScreenId}
onChange={(screenId) =>
updateLeftPanel({
addButton: { ...config.leftPanel?.addButton, enabled: true, mode: "modal", modalScreenId: screenId },
})
}
/>
</div>
)}
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<Input
value={config.leftPanel?.addButton?.buttonLabel || ""}
onChange={(e) =>
updateLeftPanel({
addButton: {
...config.leftPanel?.addButton,
enabled: true,
mode: config.leftPanel?.addButton?.mode || "auto",
buttonLabel: e.target.value || undefined,
},
})
}
placeholder="추가"
className="h-7 text-xs"
/>
</div>
</div>
</div>
)}
{/* 수정 버튼 상세 설정 */}
{(config.leftPanel?.showEdit ?? true) && (
<div className="space-y-3 rounded-lg border border-border/50 bg-muted/40 p-3">
<h3 className="border-l-2 border-l-primary/40 pl-2 text-xs font-semibold"> </h3>
<div className="space-y-2">
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<Select
value={config.leftPanel?.editButton?.mode || "auto"}
onValueChange={(value: "auto" | "modal") =>
updateLeftPanel({
editButton: { ...config.leftPanel?.editButton, enabled: true, mode: value },
})
}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="auto"> ()</SelectItem>
<SelectItem value="modal"> </SelectItem>
</SelectContent>
</Select>
</div>
{config.leftPanel?.editButton?.mode === "modal" && (
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<ScreenSelector
value={config.leftPanel?.editButton?.modalScreenId}
onChange={(screenId) =>
updateLeftPanel({
editButton: { ...config.leftPanel?.editButton, enabled: true, mode: "modal", modalScreenId: screenId },
})
}
/>
</div>
)}
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<Input
value={config.leftPanel?.editButton?.buttonLabel || ""}
onChange={(e) =>
updateLeftPanel({
editButton: {
...config.leftPanel?.editButton,
enabled: true,
mode: config.leftPanel?.editButton?.mode || "auto",
buttonLabel: e.target.value || undefined,
},
})
}
placeholder="수정"
className="h-7 text-xs"
/>
</div>
</div>
</div>
)}
</div>
</div>
</DialogContent>
</Dialog>
@@ -2776,6 +2995,85 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
)}
</div>
{/* 🆕 우측 패널 추가 버튼 설정 */}
{config.rightPanel?.showAdd && (
<div className="space-y-3 rounded-lg border border-border/50 bg-muted/40 p-4">
<div className="flex items-center justify-between">
<div>
<h3 className="border-l-2 border-l-primary/40 pl-2 text-sm font-semibold"> </h3>
<p className="text-muted-foreground text-xs"> </p>
</div>
</div>
<div className="space-y-3 border-l-2 pl-4">
<div>
<Label className="text-xs"> </Label>
<Select
value={config.rightPanel?.addButton?.mode || "auto"}
onValueChange={(value: "auto" | "modal") =>
updateRightPanel({
addButton: {
...config.rightPanel?.addButton,
mode: value,
enabled: true,
},
})
}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="auto"> ( )</SelectItem>
<SelectItem value="modal"> </SelectItem>
</SelectContent>
</Select>
<p className="text-muted-foreground mt-1 text-[10px]">
{config.rightPanel?.addButton?.mode === "modal"
? "지정한 화면을 모달로 열어 데이터를 추가합니다"
: "내장 폼으로 데이터를 추가합니다"}
</p>
</div>
{config.rightPanel?.addButton?.mode === "modal" && (
<div>
<Label className="text-xs"> </Label>
<ScreenSelector
value={config.rightPanel?.addButton?.modalScreenId}
onChange={(screenId) =>
updateRightPanel({
addButton: {
...config.rightPanel?.addButton!,
modalScreenId: screenId,
},
})
}
/>
</div>
)}
<div>
<Label className="text-xs"> </Label>
<Input
value={config.rightPanel?.addButton?.buttonLabel || "추가"}
onChange={(e) =>
updateRightPanel({
addButton: {
...config.rightPanel?.addButton!,
buttonLabel: e.target.value,
enabled: true,
mode: config.rightPanel?.addButton?.mode || "auto",
},
})
}
placeholder="추가"
className="h-8 text-xs"
/>
</div>
</div>
</div>
)}
{/* 🆕 우측 패널 삭제 버튼 설정 */}
<div className="space-y-3 rounded-lg border border-border/50 bg-muted/40 p-4">
<div className="flex items-center justify-between">

View File

@@ -115,6 +115,14 @@ export interface AdditionalTabConfig {
groupByColumns?: string[];
};
// 추가 버튼 설정 (모달 화면 연결 지원)
addButton?: {
enabled: boolean;
mode: "auto" | "modal"; // auto: 내장 폼, modal: 커스텀 모달 화면
modalScreenId?: number; // 모달로 열 화면 ID (mode: "modal"일 때)
buttonLabel?: string; // 버튼 라벨 (기본: "추가")
};
deleteButton?: {
enabled: boolean;
buttonLabel?: string;
@@ -141,6 +149,23 @@ export interface SplitPanelLayoutConfig {
showAdd?: boolean;
showEdit?: boolean; // 수정 버튼
showDelete?: boolean; // 삭제 버튼
// 수정 버튼 설정 (모달 화면 연결 지원)
editButton?: {
enabled: boolean;
mode: "auto" | "modal"; // auto: 내장 편집, modal: 커스텀 모달 화면
modalScreenId?: number; // 모달로 열 화면 ID (mode: "modal"일 때)
buttonLabel?: string; // 버튼 라벨 (기본: "수정")
};
// 추가 버튼 설정 (모달 화면 연결 지원)
addButton?: {
enabled: boolean;
mode: "auto" | "modal"; // auto: 내장 폼, modal: 커스텀 모달 화면
modalScreenId?: number; // 모달로 열 화면 ID (mode: "modal"일 때)
buttonLabel?: string; // 버튼 라벨 (기본: "추가")
};
columns?: Array<{
name: string;
label: string;
@@ -307,6 +332,14 @@ export interface SplitPanelLayoutConfig {
groupByColumns?: string[]; // 🆕 그룹핑 기준 컬럼들 (예: ["customer_id", "item_id"])
};
// 🆕 추가 버튼 설정 (모달 화면 연결 지원)
addButton?: {
enabled: boolean; // 추가 버튼 표시 여부 (기본: true)
mode: "auto" | "modal"; // auto: 내장 폼, modal: 커스텀 모달 화면
modalScreenId?: number; // 모달로 열 화면 ID (mode: "modal"일 때)
buttonLabel?: string; // 버튼 라벨 (기본: "추가")
};
// 🆕 삭제 버튼 설정
deleteButton?: {
enabled: boolean; // 삭제 버튼 표시 여부 (기본: true)