feat(SplitPanelLayout2): 좌우 패널 수정/삭제 기능 및 모달 자동 닫기 구현

- 좌측 패널에 수정/삭제 버튼 기능 추가
- 좌측 패널 설정에 개별 수정/삭제 UI 추가
- 삭제 API 호출을 백엔드 라우트에 맞게 수정 (DELETE /tables/{tableName}/delete)
- UniversalFormModal 저장 완료 후 closeEditModal 이벤트 발생하여 모달 자동 닫기
- ModalConfig에 showSaveButton 타입 추가
This commit is contained in:
SeongHyun Kim
2025-12-24 14:01:38 +09:00
parent 171ed6e938
commit 486e5ee29b
5 changed files with 341 additions and 75 deletions

View File

@@ -86,6 +86,7 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [itemToDelete, setItemToDelete] = useState<any>(null);
const [isBulkDelete, setIsBulkDelete] = useState(false);
const [deleteTargetPanel, setDeleteTargetPanel] = useState<"left" | "right">("right");
// 탭 상태 (좌측/우측 각각)
const [leftActiveTab, setLeftActiveTab] = useState<string | null>(null);
@@ -637,9 +638,6 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
}
}
console.log("[SplitPanelLayout2] 모달로 전달할 데이터:", initialData);
console.log("[SplitPanelLayout2] 모달 screenId:", config.rightPanel?.addModalScreenId);
// EditModal 열기 이벤트 발생
const event = new CustomEvent("openEditModal", {
detail: {
@@ -665,11 +663,16 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
loadRightData,
]);
// 기본키 컬럼명 가져오기
// 기본키 컬럼명 가져오기 (우측 패널)
const getPrimaryKeyColumn = useCallback(() => {
return config.rightPanel?.primaryKeyColumn || "id";
}, [config.rightPanel?.primaryKeyColumn]);
// 기본키 컬럼명 가져오기 (좌측 패널)
const getLeftPrimaryKeyColumn = useCallback(() => {
return config.leftPanel?.primaryKeyColumn || config.leftPanel?.hierarchyConfig?.idColumn || "id";
}, [config.leftPanel?.primaryKeyColumn, config.leftPanel?.hierarchyConfig?.idColumn]);
// 우측 패널 수정 버튼 클릭
const handleEditItem = useCallback(
(item: any) => {
@@ -697,15 +700,54 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
},
});
window.dispatchEvent(event);
console.log("[SplitPanelLayout2] 수정 모달 열기:", item);
console.log("[SplitPanelLayout2] 우측 수정 모달 열기:", item);
},
[config.rightPanel?.editModalScreenId, config.rightPanel?.addModalScreenId, selectedLeftItem, loadRightData],
);
// 좌측 패널 수정 버튼 클릭
const handleLeftEditItem = useCallback(
(item: any) => {
// 수정용 모달 화면 ID 결정 (editModalScreenId 우선, 없으면 addModalScreenId 사용)
const modalScreenId = config.leftPanel?.editModalScreenId || config.leftPanel?.addModalScreenId;
if (!modalScreenId) {
toast.error("연결된 모달 화면이 없습니다.");
return;
}
// EditModal 열기 이벤트 발생 (수정 모드)
const event = new CustomEvent("openEditModal", {
detail: {
screenId: modalScreenId,
title: "수정",
modalSize: "lg",
editData: item, // 기존 데이터 전달
isCreateMode: false, // 수정 모드
onSave: () => {
loadLeftData();
},
},
});
window.dispatchEvent(event);
console.log("[SplitPanelLayout2] 좌측 수정 모달 열기:", item);
},
[config.leftPanel?.editModalScreenId, config.leftPanel?.addModalScreenId, loadLeftData],
);
// 우측 패널 삭제 버튼 클릭 (확인 다이얼로그 표시)
const handleDeleteClick = useCallback((item: any) => {
setItemToDelete(item);
setIsBulkDelete(false);
setDeleteTargetPanel("right");
setDeleteDialogOpen(true);
}, []);
// 좌측 패널 삭제 버튼 클릭 (확인 다이얼로그 표시)
const handleLeftDeleteClick = useCallback((item: any) => {
setItemToDelete(item);
setIsBulkDelete(false);
setDeleteTargetPanel("left");
setDeleteDialogOpen(true);
}, []);
@@ -716,41 +758,54 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
return;
}
setIsBulkDelete(true);
setDeleteTargetPanel("right");
setDeleteDialogOpen(true);
}, [selectedRightItems.size]);
// 실제 삭제 실행
const executeDelete = useCallback(async () => {
if (!config.rightPanel?.tableName) {
// 대상 패널에 따라 테이블명과 기본키 컬럼 결정
const tableName = deleteTargetPanel === "left"
? config.leftPanel?.tableName
: config.rightPanel?.tableName;
const pkColumn = deleteTargetPanel === "left"
? getLeftPrimaryKeyColumn()
: getPrimaryKeyColumn();
if (!tableName) {
toast.error("테이블 설정이 없습니다.");
return;
}
const pkColumn = getPrimaryKeyColumn();
try {
if (isBulkDelete) {
// 일괄 삭제
const idsToDelete = Array.from(selectedRightItems);
console.log("[SplitPanelLayout2] 일괄 삭제:", idsToDelete);
// 일괄 삭제 - 선택된 항목들의 데이터를 body로 전달
const itemsToDelete = rightData.filter((item) => selectedRightItems.has(item[pkColumn] as string | number));
console.log("[SplitPanelLayout2] 일괄 삭제:", itemsToDelete);
for (const id of idsToDelete) {
await apiClient.delete(`/table-management/tables/${config.rightPanel.tableName}/data/${id}`);
}
// 백엔드 API는 body로 삭제할 데이터를 받음
await apiClient.delete(`/table-management/tables/${tableName}/delete`, {
data: itemsToDelete,
});
toast.success(`${idsToDelete.length}개 항목이 삭제되었습니다.`);
setSelectedRightItems(new Set());
toast.success(`${itemsToDelete.length}개 항목이 삭제되었습니다.`);
setSelectedRightItems(new Set<string | number>());
} else if (itemToDelete) {
// 단일 삭제
const itemId = itemToDelete[pkColumn];
console.log("[SplitPanelLayout2] 단일 삭제:", itemId);
// 단일 삭제 - 해당 항목 데이터를 body로 전달
console.log(`[SplitPanelLayout2] ${deleteTargetPanel === "left" ? "좌측" : "우측"} 단일 삭제:`, itemToDelete);
await apiClient.delete(`/table-management/tables/${config.rightPanel.tableName}/data/${itemId}`);
await apiClient.delete(`/table-management/tables/${tableName}/delete`, {
data: itemToDelete,
});
toast.success("항목이 삭제되었습니다.");
}
// 데이터 새로고침
if (selectedLeftItem) {
if (deleteTargetPanel === "left") {
loadLeftData();
setSelectedLeftItem(null); // 좌측 선택 초기화
setRightData([]); // 우측 데이터도 초기화
} else if (selectedLeftItem) {
loadRightData(selectedLeftItem);
}
} catch (error: any) {
@@ -762,13 +817,18 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
setIsBulkDelete(false);
}
}, [
deleteTargetPanel,
config.leftPanel?.tableName,
config.rightPanel?.tableName,
getLeftPrimaryKeyColumn,
getPrimaryKeyColumn,
isBulkDelete,
selectedRightItems,
itemToDelete,
selectedLeftItem,
loadLeftData,
loadRightData,
rightData,
]);
// 개별 체크박스 선택/해제
@@ -825,7 +885,29 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
const selectedId = Array.from(selectedRightItems)[0];
const item = rightData.find((d) => d[pkColumn] === selectedId);
if (item) {
handleEditItem(item);
// 액션 버튼에 모달 화면이 설정되어 있으면 해당 화면 사용
const modalScreenId = btn.modalScreenId || config.rightPanel?.editModalScreenId || config.rightPanel?.addModalScreenId;
if (!modalScreenId) {
toast.error("연결된 모달 화면이 없습니다.");
return;
}
const event = new CustomEvent("openEditModal", {
detail: {
screenId: modalScreenId,
title: btn.label || "수정",
modalSize: "lg",
editData: item,
isCreateMode: false,
onSave: () => {
if (selectedLeftItem) {
loadRightData(selectedLeftItem);
}
},
},
});
window.dispatchEvent(event);
}
} else if (selectedRightItems.size > 1) {
toast.error("수정할 항목을 1개만 선택해주세요.");
@@ -860,6 +942,57 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
],
);
// 좌측 패널 액션 버튼 클릭 핸들러
const handleLeftActionButton = useCallback(
(btn: ActionButtonConfig) => {
switch (btn.action) {
case "add":
// 액션 버튼에 설정된 modalScreenId 우선 사용
const modalScreenId = btn.modalScreenId || config.leftPanel?.addModalScreenId;
if (!modalScreenId) {
toast.error("연결된 모달 화면이 없습니다.");
return;
}
// EditModal 열기 이벤트 발생
const event = new CustomEvent("openEditModal", {
detail: {
screenId: modalScreenId,
title: btn.label || "추가",
modalSize: "lg",
editData: {},
isCreateMode: true,
onSave: () => {
loadLeftData();
},
},
});
window.dispatchEvent(event);
console.log("[SplitPanelLayout2] 좌측 액션 버튼 모달 열기:", modalScreenId);
break;
case "edit":
// 좌측 패널에서 수정 (필요시 구현)
console.log("[SplitPanelLayout2] 좌측 수정 액션:", btn);
break;
case "delete":
// 좌측 패널에서 삭제 (필요시 구현)
console.log("[SplitPanelLayout2] 좌측 삭제 액션:", btn);
break;
case "custom":
console.log("[SplitPanelLayout2] 좌측 커스텀 액션:", btn);
break;
default:
break;
}
},
[config.leftPanel?.addModalScreenId, loadLeftData],
);
// 컬럼 라벨 로드
const loadColumnLabels = useCallback(
async (tableName: string, setLabels: (labels: Record<string, string>) => void) => {
@@ -1012,10 +1145,10 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
(checked: boolean) => {
if (checked) {
const pkColumn = getPrimaryKeyColumn();
const allIds = new Set(filteredRightData.map((item) => item[pkColumn]));
const allIds = new Set<string | number>(filteredRightData.map((item) => item[pkColumn] as string | number));
setSelectedRightItems(allIds);
} else {
setSelectedRightItems(new Set());
setSelectedRightItems(new Set<string | number>());
}
},
[filteredRightData, getPrimaryKeyColumn],
@@ -1348,6 +1481,27 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
</div>
)}
</div>
{/* 좌측 패널 수정/삭제 버튼 */}
{(config.leftPanel?.showEditButton || config.leftPanel?.showDeleteButton) && (
<div className="flex gap-1" onClick={(e) => e.stopPropagation()}>
{config.leftPanel?.showEditButton && (
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={() => handleLeftEditItem(item)}>
<Edit className="h-4 w-4" />
</Button>
)}
{config.leftPanel?.showDeleteButton && (
<Button
variant="ghost"
size="icon"
className="text-destructive hover:text-destructive h-8 w-8"
onClick={() => handleLeftDeleteClick(item)}
>
<Trash2 className="h-4 w-4" />
</Button>
)}
</div>
)}
</div>
{/* 자식 항목 */}
@@ -1360,11 +1514,6 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
);
};
// 왼쪽 패널 기본키 컬럼명 가져오기
const getLeftPrimaryKeyColumn = useCallback(() => {
return config.leftPanel?.primaryKeyColumn || config.leftPanel?.hierarchyConfig?.idColumn || "id";
}, [config.leftPanel?.primaryKeyColumn, config.leftPanel?.hierarchyConfig?.idColumn]);
// 왼쪽 패널 테이블 렌더링
const renderLeftTable = () => {
const displayColumns = config.leftPanel?.displayColumns || [];
@@ -1586,8 +1735,8 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
const showCheckbox = config.rightPanel?.showCheckbox ?? true; // 테이블 모드는 기본 체크박스 표시
const pkColumn = getPrimaryKeyColumn();
const allSelected =
filteredRightData.length > 0 && filteredRightData.every((item) => selectedRightItems.has(item[pkColumn]));
const someSelected = filteredRightData.some((item) => selectedRightItems.has(item[pkColumn]));
filteredRightData.length > 0 && filteredRightData.every((item) => selectedRightItems.has(item[pkColumn] as string | number));
const someSelected = filteredRightData.some((item) => selectedRightItems.has(item[pkColumn] as string | number));
return (
<div className="rounded-md border">
@@ -1633,7 +1782,7 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
</TableRow>
) : (
filteredRightData.map((item, index) => {
const itemId = item[pkColumn];
const itemId = item[pkColumn] as string | number;
return (
<TableRow key={index} className="hover:bg-muted/50">
{showCheckbox && (
@@ -1962,11 +2111,7 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
size="sm"
variant={btn.variant || "default"}
className="h-8 text-sm"
onClick={() => {
if (btn.action === "add") {
handleLeftAddClick();
}
}}
onClick={() => handleLeftActionButton(btn)}
>
{btn.icon && <span className="mr-1">{btn.icon}</span>}
{btn.label || "버튼"}

View File

@@ -992,6 +992,42 @@ export const SplitPanelLayout2ConfigPanel: React.FC<SplitPanelLayout2ConfigPanel
</div>
</div>
{/* 개별 수정/삭제 버튼 (좌측) */}
<div className="border-t pt-3">
<Label className="text-xs font-medium"> /</Label>
<p className="text-muted-foreground mb-2 text-[10px]"> / </p>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label className="text-xs"> </Label>
<Switch
checked={config.leftPanel?.showEditButton || false}
onCheckedChange={(checked) => updateConfig("leftPanel.showEditButton", checked)}
/>
</div>
<div className="flex items-center justify-between">
<Label className="text-xs"> </Label>
<Switch
checked={config.leftPanel?.showDeleteButton || false}
onCheckedChange={(checked) => updateConfig("leftPanel.showDeleteButton", checked)}
/>
</div>
{(config.leftPanel?.showEditButton || config.leftPanel?.showDeleteButton) && (
<div className="mt-2">
<Label className="text-xs"> </Label>
<ColumnSelect
columns={leftColumns}
value={config.leftPanel?.primaryKeyColumn || ""}
onValueChange={(value) => updateConfig("leftPanel.primaryKeyColumn", value)}
placeholder="기본키 컬럼 선택 (기본: id)"
/>
<p className="text-muted-foreground mt-1 text-[10px]">
/
</p>
</div>
)}
</div>
</div>
{/* 탭 설정 (좌측) */}
<div className="border-t pt-3">
<div className="flex items-center justify-between">
@@ -1274,6 +1310,42 @@ export const SplitPanelLayout2ConfigPanel: React.FC<SplitPanelLayout2ConfigPanel
</div>
</div>
{/* 개별 수정/삭제 버튼 */}
<div className="border-t pt-3">
<Label className="text-xs font-medium"> /</Label>
<p className="text-muted-foreground mb-2 text-[10px]"> / </p>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label className="text-xs"> </Label>
<Switch
checked={config.rightPanel?.showEditButton || false}
onCheckedChange={(checked) => updateConfig("rightPanel.showEditButton", checked)}
/>
</div>
<div className="flex items-center justify-between">
<Label className="text-xs"> </Label>
<Switch
checked={config.rightPanel?.showDeleteButton || false}
onCheckedChange={(checked) => updateConfig("rightPanel.showDeleteButton", checked)}
/>
</div>
{(config.rightPanel?.showEditButton || config.rightPanel?.showDeleteButton) && (
<div className="mt-2">
<Label className="text-xs"> </Label>
<ColumnSelect
columns={rightColumns}
value={config.rightPanel?.primaryKeyColumn || ""}
onValueChange={(value) => updateConfig("rightPanel.primaryKeyColumn", value)}
placeholder="기본키 컬럼 선택 (기본: id)"
/>
<p className="text-muted-foreground mt-1 text-[10px]">
/
</p>
</div>
)}
</div>
</div>
{/* 탭 설정 (우측) */}
<div className="border-t pt-3">
<div className="flex items-center justify-between">
@@ -1348,39 +1420,6 @@ export const SplitPanelLayout2ConfigPanel: React.FC<SplitPanelLayout2ConfigPanel
onCheckedChange={(checked) => updateConfig("rightPanel.showCheckbox", checked)}
/>
</div>
{/* 수정/삭제 버튼 */}
<div className="space-y-2">
<Label className="text-xs font-medium"> /</Label>
<div className="flex items-center justify-between">
<Label className="text-xs"> </Label>
<Switch
checked={config.rightPanel?.showEditButton || false}
onCheckedChange={(checked) => updateConfig("rightPanel.showEditButton", checked)}
/>
</div>
<div className="flex items-center justify-between">
<Label className="text-xs"> </Label>
<Switch
checked={config.rightPanel?.showDeleteButton || false}
onCheckedChange={(checked) => updateConfig("rightPanel.showDeleteButton", checked)}
/>
</div>
</div>
{/* 기본키 컬럼 */}
<div>
<Label className="text-xs"> </Label>
<ColumnSelect
columns={rightColumns}
value={config.rightPanel?.primaryKeyColumn || ""}
onValueChange={(value) => updateConfig("rightPanel.primaryKeyColumn", value)}
placeholder="기본키 컬럼 선택 (기본: id)"
/>
<p className="text-muted-foreground mt-1 text-[10px]">
/ ( id )
</p>
</div>
</div>
</details>
</div>

View File

@@ -158,6 +158,9 @@ export interface LeftPanelConfig {
showAddButton?: boolean; // 추가 버튼 표시 (하위 호환성)
addButtonLabel?: string; // 추가 버튼 라벨 (하위 호환성)
addModalScreenId?: number; // 추가 모달 화면 ID (하위 호환성)
showEditButton?: boolean; // 수정 버튼 표시
showDeleteButton?: boolean; // 삭제 버튼 표시
editModalScreenId?: number; // 수정 모달 화면 ID
actionButtons?: ActionButtonConfig[]; // 복수 액션 버튼 배열
displayMode?: "card" | "table"; // 표시 모드 (card: 카드형, table: 테이블형)
primaryKeyColumn?: string; // 기본키 컬럼명 (선택용, 기본: id)

View File

@@ -1223,12 +1223,11 @@ export function UniversalFormModalComponent({
if (subTableConfig.options?.saveMainAsFirst) {
mainFieldMappings = [];
// 메인 섹션(비반복)의 필드들을 서브 테이블에 매핑
// 서브 테이블의 fieldMappings에서 targetColumn을 찾아서 매핑
// fieldMappings에 정의된 targetColumn만 매핑 (서브 테이블에 존재하는 컬럼만)
for (const mapping of subTableConfig.fieldMappings || []) {
if (mapping.targetColumn) {
// 메인 데이터에서 동일한 컬럼명이 있으면 매핑
if (mainData[mapping.targetColumn] !== undefined) {
if (mainData[mapping.targetColumn] !== undefined && mainData[mapping.targetColumn] !== null && mainData[mapping.targetColumn] !== "") {
mainFieldMappings.push({
formField: mapping.targetColumn,
targetColumn: mapping.targetColumn,
@@ -1239,7 +1238,7 @@ export function UniversalFormModalComponent({
config.sections.forEach((section) => {
if (section.repeatable || section.type === "table") return;
const matchingField = (section.fields || []).find((f) => f.columnName === mapping.targetColumn);
if (matchingField && mainData[matchingField.columnName] !== undefined) {
if (matchingField && mainData[matchingField.columnName] !== undefined && mainData[matchingField.columnName] !== null && mainData[matchingField.columnName] !== "") {
mainFieldMappings!.push({
formField: matchingField.columnName,
targetColumn: mapping.targetColumn,
@@ -1374,6 +1373,11 @@ export function UniversalFormModalComponent({
if (onSave) {
onSave({ ...formData, _saveCompleted: true });
}
// 저장 완료 후 모달 닫기 이벤트 발생
if (config.saveConfig.afterSave?.closeModal !== false) {
window.dispatchEvent(new CustomEvent("closeEditModal"));
}
} catch (error: any) {
console.error("저장 실패:", error);
// axios 에러의 경우 서버 응답 메시지 추출
@@ -1492,6 +1496,22 @@ export function UniversalFormModalComponent({
return `${valueVal} - ${displayVal}`;
case "name_code":
return `${displayVal} (${valueVal})`;
case "custom":
// 커스텀 형식: {컬럼명}을 실제 값으로 치환
if (lfg.customDisplayFormat) {
let result = lfg.customDisplayFormat;
// {컬럼명} 패턴을 찾아서 실제 값으로 치환
const matches = result.match(/\{([^}]+)\}/g);
if (matches) {
matches.forEach((match) => {
const columnName = match.slice(1, -1); // { } 제거
const columnValue = row[columnName];
result = result.replace(match, columnValue !== undefined && columnValue !== null ? String(columnValue) : "");
});
}
return result;
}
return String(displayVal);
case "name_only":
default:
return String(displayVal);

View File

@@ -81,7 +81,10 @@ export interface FormFieldConfig {
enabled?: boolean; // 사용 여부
sourceTable?: string; // 소스 테이블 (예: dept_info)
displayColumn?: string; // 표시할 컬럼 (예: dept_name) - 드롭다운에 보여줄 텍스트
displayFormat?: "name_only" | "code_name" | "name_code"; // 표시 형식
displayFormat?: "name_only" | "code_name" | "name_code" | "custom"; // 표시 형식
// 커스텀 표시 형식 (displayFormat이 "custom"일 때 사용)
// 형식: {컬럼명} 으로 치환됨 (예: "{item_name} ({item_number})" → "철판 (ITEM-001)")
customDisplayFormat?: string;
mappings?: LinkedFieldMapping[]; // 저장할 컬럼 매핑 (첫 번째 매핑의 sourceColumn이 드롭다운 값으로 사용됨)
};
@@ -254,6 +257,53 @@ export interface TableSectionConfig {
multiSelect?: boolean; // 다중 선택 허용 (기본: true)
maxHeight?: string; // 테이블 최대 높이 (기본: "400px")
};
// 7. 조건부 테이블 설정 (고급)
conditionalTable?: ConditionalTableConfig;
}
/**
* 조건부 테이블 설정
* 조건(검사유형 등)에 따라 다른 데이터를 표시하고 저장합니다.
*
* 사용 예시:
* - 품목검사정보: 검사유형(입고/공정/출고/재고/최종)별로 검사항목 관리
* - BOM 관리: 품목유형별 자재 구성
* - 공정 관리: 공정유형별 작업 항목
*/
export interface ConditionalTableConfig {
enabled: boolean;
// 트리거 UI 타입
// - checkbox: 체크박스로 다중 선택 (선택된 조건들을 탭으로 표시)
// - dropdown: 드롭다운으로 단일 선택
// - tabs: 모든 옵션을 탭으로 표시
triggerType: "checkbox" | "dropdown" | "tabs";
// 조건 값을 저장할 컬럼 (예: inspection_type)
// 저장 시 각 행에 이 컬럼으로 조건 값이 자동 저장됨
conditionColumn: string;
// 조건 옵션 목록
options: ConditionalTableOption[];
// 옵션을 테이블에서 동적으로 로드할 경우
optionSource?: {
enabled: boolean;
tableName: string; // 예: inspection_type_code
valueColumn: string; // 예: type_code
labelColumn: string; // 예: type_name
filterCondition?: string; // 예: is_active = 'Y'
};
}
/**
* 조건부 테이블 옵션
*/
export interface ConditionalTableOption {
id: string;
value: string; // 저장될 값 (예: "입고검사")
label: string; // 표시 라벨 (예: "입고검사")
}
/**
@@ -650,6 +700,7 @@ export interface ModalConfig {
showCloseButton?: boolean;
// 버튼 설정
showSaveButton?: boolean; // 저장 버튼 표시 (기본: true)
saveButtonText?: string; // 저장 버튼 텍스트 (기본: "저장")
cancelButtonText?: string; // 취소 버튼 텍스트 (기본: "취소")
showResetButton?: boolean; // 초기화 버튼 표시
@@ -748,6 +799,7 @@ export const LINKED_FIELD_DISPLAY_FORMAT_OPTIONS = [
{ value: "name_only", label: "이름만 (예: 영업부)" },
{ value: "code_name", label: "코드 - 이름 (예: SALES - 영업부)" },
{ value: "name_code", label: "이름 (코드) (예: 영업부 (SALES))" },
{ value: "custom", label: "커스텀 형식 (직접 입력)" },
] as const;
// ============================================
@@ -815,3 +867,10 @@ export const LOOKUP_CONDITION_SOURCE_OPTIONS = [
{ value: "sectionField", label: "다른 섹션" },
{ value: "externalTable", label: "외부 테이블" },
] as const;
// 조건부 테이블 트리거 타입 옵션
export const CONDITIONAL_TABLE_TRIGGER_OPTIONS = [
{ value: "checkbox", label: "체크박스 (다중 선택)" },
{ value: "dropdown", label: "드롭다운 (단일 선택)" },
{ value: "tabs", label: "탭 (전체 표시)" },
] as const;