Compare commits
No commits in common. "a73b37f5580c0252ce00ae331407141c45d99fcc" and "963e0c2d247a980d9c6335fc7e056ef020c889ce" have entirely different histories.
a73b37f558
...
963e0c2d24
|
|
@ -26,14 +26,7 @@ export const dataApi = {
|
||||||
size: number;
|
size: number;
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
}> => {
|
}> => {
|
||||||
// filters를 평탄화하여 쿼리 파라미터로 전달 (백엔드 ...filters 형식에 맞춤)
|
const response = await apiClient.get(`/data/${tableName}`, { params });
|
||||||
const { filters, ...restParams } = params || {};
|
|
||||||
const flattenedParams = {
|
|
||||||
...restParams,
|
|
||||||
...(filters || {}), // filters 객체를 평탄화
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await apiClient.get(`/data/${tableName}`, { params: flattenedParams });
|
|
||||||
const raw = response.data || {};
|
const raw = response.data || {};
|
||||||
const items: any[] = (raw.data ?? raw.items ?? raw.rows ?? []) as any[];
|
const items: any[] = (raw.data ?? raw.items ?? raw.rows ?? []) as any[];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,9 +85,6 @@ import "./tax-invoice-list/TaxInvoiceListRenderer"; // 세금계산서 목록,
|
||||||
// 🆕 메일 수신자 선택 컴포넌트
|
// 🆕 메일 수신자 선택 컴포넌트
|
||||||
import "./mail-recipient-selector/MailRecipientSelectorRenderer"; // 내부 인원 선택 + 외부 이메일 입력
|
import "./mail-recipient-selector/MailRecipientSelectorRenderer"; // 내부 인원 선택 + 외부 이메일 입력
|
||||||
|
|
||||||
// 🆕 연관 데이터 버튼 컴포넌트
|
|
||||||
import "./related-data-buttons/RelatedDataButtonsRenderer"; // 좌측 선택 데이터 기반 연관 테이블 버튼 표시
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 컴포넌트 초기화 함수
|
* 컴포넌트 초기화 함수
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,162 +0,0 @@
|
||||||
# RelatedDataButtons 컴포넌트
|
|
||||||
|
|
||||||
좌측 패널에서 선택한 데이터를 기반으로 연관 테이블의 데이터를 버튼으로 표시하는 컴포넌트
|
|
||||||
|
|
||||||
## 개요
|
|
||||||
|
|
||||||
- **ID**: `related-data-buttons`
|
|
||||||
- **카테고리**: data
|
|
||||||
- **웹타입**: container
|
|
||||||
- **버전**: 1.0.0
|
|
||||||
|
|
||||||
## 사용 사례
|
|
||||||
|
|
||||||
### 품목별 라우팅 버전 관리
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────┐
|
|
||||||
│ 알루미늄 프레임 [+ 라우팅 버전 추가] │
|
|
||||||
│ ITEM001 │
|
|
||||||
│ ┌──────────────┐ ┌─────────┐ │
|
|
||||||
│ │ 기본 라우팅 ★ │ │ 개선버전 │ │
|
|
||||||
│ └──────────────┘ └─────────┘ │
|
|
||||||
└─────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## 데이터 흐름
|
|
||||||
|
|
||||||
```
|
|
||||||
1. 좌측 패널: item_info 선택
|
|
||||||
↓ SplitPanelContext.selectedLeftData
|
|
||||||
2. RelatedDataButtons: item_code로 item_routing_version 조회
|
|
||||||
↓ 버튼 클릭 시 이벤트 발생
|
|
||||||
3. 하위 테이블: routing_version_id로 item_routing_detail 필터링
|
|
||||||
```
|
|
||||||
|
|
||||||
## 설정 옵션
|
|
||||||
|
|
||||||
### 소스 매핑 (sourceMapping)
|
|
||||||
|
|
||||||
| 속성 | 타입 | 설명 |
|
|
||||||
|------|------|------|
|
|
||||||
| sourceTable | string | 좌측 패널 테이블명 (예: item_info) |
|
|
||||||
| sourceColumn | string | 필터에 사용할 컬럼 (예: item_code) |
|
|
||||||
|
|
||||||
### 헤더 표시 (headerDisplay)
|
|
||||||
|
|
||||||
| 속성 | 타입 | 설명 |
|
|
||||||
|------|------|------|
|
|
||||||
| show | boolean | 헤더 표시 여부 |
|
|
||||||
| titleColumn | string | 제목으로 표시할 컬럼 (예: item_name) |
|
|
||||||
| subtitleColumn | string | 부제목으로 표시할 컬럼 (예: item_code) |
|
|
||||||
|
|
||||||
### 버튼 데이터 소스 (buttonDataSource)
|
|
||||||
|
|
||||||
| 속성 | 타입 | 설명 |
|
|
||||||
|------|------|------|
|
|
||||||
| tableName | string | 조회할 테이블명 (예: item_routing_version) |
|
|
||||||
| filterColumn | string | 필터링할 컬럼명 (예: item_code) |
|
|
||||||
| displayColumn | string | 버튼에 표시할 컬럼명 (예: version_name) |
|
|
||||||
| valueColumn | string | 선택 시 전달할 값 컬럼 (기본: id) |
|
|
||||||
| orderColumn | string | 정렬 컬럼 |
|
|
||||||
| orderDirection | "ASC" \| "DESC" | 정렬 방향 |
|
|
||||||
|
|
||||||
### 버튼 스타일 (buttonStyle)
|
|
||||||
|
|
||||||
| 속성 | 타입 | 설명 |
|
|
||||||
|------|------|------|
|
|
||||||
| variant | string | 기본 버튼 스타일 (default, outline, secondary, ghost) |
|
|
||||||
| activeVariant | string | 선택 시 버튼 스타일 |
|
|
||||||
| size | string | 버튼 크기 (sm, default, lg) |
|
|
||||||
| defaultIndicator.column | string | 기본 버전 판단 컬럼 |
|
|
||||||
| defaultIndicator.showStar | boolean | 별표 아이콘 표시 여부 |
|
|
||||||
|
|
||||||
### 추가 버튼 (addButton)
|
|
||||||
|
|
||||||
| 속성 | 타입 | 설명 |
|
|
||||||
|------|------|------|
|
|
||||||
| show | boolean | 추가 버튼 표시 여부 |
|
|
||||||
| label | string | 버튼 라벨 |
|
|
||||||
| position | "header" \| "inline" | 버튼 위치 |
|
|
||||||
| modalScreenId | number | 연결할 모달 화면 ID |
|
|
||||||
|
|
||||||
### 이벤트 설정 (events)
|
|
||||||
|
|
||||||
| 속성 | 타입 | 설명 |
|
|
||||||
|------|------|------|
|
|
||||||
| targetTable | string | 필터링할 하위 테이블명 |
|
|
||||||
| targetFilterColumn | string | 하위 테이블의 필터 컬럼명 |
|
|
||||||
|
|
||||||
## 이벤트
|
|
||||||
|
|
||||||
### related-button-select
|
|
||||||
|
|
||||||
버튼 선택 시 발생하는 커스텀 이벤트
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
window.addEventListener("related-button-select", (e: CustomEvent) => {
|
|
||||||
const { targetTable, filterColumn, filterValue, selectedData } = e.detail;
|
|
||||||
// 하위 테이블 필터링 처리
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 사용 예시
|
|
||||||
|
|
||||||
### 품목별 라우팅 버전 화면
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const config: RelatedDataButtonsConfig = {
|
|
||||||
sourceMapping: {
|
|
||||||
sourceTable: "item_info",
|
|
||||||
sourceColumn: "item_code",
|
|
||||||
},
|
|
||||||
headerDisplay: {
|
|
||||||
show: true,
|
|
||||||
titleColumn: "item_name",
|
|
||||||
subtitleColumn: "item_code",
|
|
||||||
},
|
|
||||||
buttonDataSource: {
|
|
||||||
tableName: "item_routing_version",
|
|
||||||
filterColumn: "item_code",
|
|
||||||
displayColumn: "version_name",
|
|
||||||
valueColumn: "id",
|
|
||||||
},
|
|
||||||
buttonStyle: {
|
|
||||||
variant: "outline",
|
|
||||||
activeVariant: "default",
|
|
||||||
defaultIndicator: {
|
|
||||||
column: "is_default",
|
|
||||||
showStar: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
events: {
|
|
||||||
targetTable: "item_routing_detail",
|
|
||||||
targetFilterColumn: "routing_version_id",
|
|
||||||
},
|
|
||||||
addButton: {
|
|
||||||
show: true,
|
|
||||||
label: "+ 라우팅 버전 추가",
|
|
||||||
position: "header",
|
|
||||||
},
|
|
||||||
autoSelectFirst: true,
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## 분할 패널과 함께 사용
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────┬──────────────────────────────────────────────┐
|
|
||||||
│ │ [RelatedDataButtons 컴포넌트] │
|
|
||||||
│ 품목 목록 │ 품목명 표시 + 버전 버튼들 │
|
|
||||||
│ (좌측 패널) ├──────────────────────────────────────────────┤
|
|
||||||
│ │ [DataTable 컴포넌트] │
|
|
||||||
│ item_info │ 공정 순서 테이블 (item_routing_detail) │
|
|
||||||
│ │ related-button-select 이벤트로 필터링 │
|
|
||||||
└─────────────────┴──────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## 개발자 정보
|
|
||||||
|
|
||||||
- **생성일**: 2024-12
|
|
||||||
- **경로**: `lib/registry/components/related-data-buttons/`
|
|
||||||
|
|
||||||
|
|
@ -1,280 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useState, useEffect, useCallback } from "react";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Plus, Star, Loader2 } from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { useSplitPanelContext } from "@/contexts/SplitPanelContext";
|
|
||||||
import { dataApi } from "@/lib/api/data";
|
|
||||||
import type { RelatedDataButtonsConfig, ButtonItem } from "./types";
|
|
||||||
|
|
||||||
interface RelatedDataButtonsComponentProps {
|
|
||||||
config: RelatedDataButtonsConfig;
|
|
||||||
className?: string;
|
|
||||||
style?: React.CSSProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RelatedDataButtonsComponent: React.FC<RelatedDataButtonsComponentProps> = ({
|
|
||||||
config,
|
|
||||||
className,
|
|
||||||
style,
|
|
||||||
}) => {
|
|
||||||
const [buttons, setButtons] = useState<ButtonItem[]>([]);
|
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [masterData, setMasterData] = useState<Record<string, any> | null>(null);
|
|
||||||
|
|
||||||
// SplitPanel Context 연결
|
|
||||||
const splitPanelContext = useSplitPanelContext();
|
|
||||||
|
|
||||||
// 좌측 패널에서 선택된 데이터 감지
|
|
||||||
useEffect(() => {
|
|
||||||
if (!splitPanelContext?.selectedLeftData) {
|
|
||||||
setMasterData(null);
|
|
||||||
setButtons([]);
|
|
||||||
setSelectedId(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setMasterData(splitPanelContext.selectedLeftData);
|
|
||||||
}, [splitPanelContext?.selectedLeftData]);
|
|
||||||
|
|
||||||
// 버튼 데이터 로드
|
|
||||||
const loadButtons = useCallback(async () => {
|
|
||||||
if (!masterData || !config.buttonDataSource?.tableName) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const filterValue = masterData[config.sourceMapping.sourceColumn];
|
|
||||||
if (!filterValue) {
|
|
||||||
setButtons([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const { tableName, filterColumn, displayColumn, valueColumn, orderColumn, orderDirection } = config.buttonDataSource;
|
|
||||||
|
|
||||||
const response = await dataApi.getTableData(tableName, {
|
|
||||||
filters: { [filterColumn]: filterValue },
|
|
||||||
sortBy: orderColumn || "created_date",
|
|
||||||
sortOrder: (orderDirection?.toLowerCase() || "asc") as "asc" | "desc",
|
|
||||||
size: 50,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.data && response.data.length > 0) {
|
|
||||||
const defaultConfig = config.buttonStyle?.defaultIndicator;
|
|
||||||
|
|
||||||
const items: ButtonItem[] = response.data.map((row: Record<string, any>) => {
|
|
||||||
let isDefault = false;
|
|
||||||
if (defaultConfig?.column) {
|
|
||||||
const val = row[defaultConfig.column];
|
|
||||||
const checkValue = defaultConfig.value || "Y";
|
|
||||||
isDefault = val === checkValue || val === true || val === "true";
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: row.id || row[valueColumn || "id"],
|
|
||||||
displayText: row[displayColumn] || row.id,
|
|
||||||
value: row[valueColumn || "id"],
|
|
||||||
isDefault,
|
|
||||||
rawData: row,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
setButtons(items);
|
|
||||||
|
|
||||||
// 자동 선택: 기본 항목 또는 첫 번째 항목
|
|
||||||
if (config.autoSelectFirst && items.length > 0) {
|
|
||||||
const defaultItem = items.find(item => item.isDefault);
|
|
||||||
const targetItem = defaultItem || items[0];
|
|
||||||
setSelectedId(targetItem.id);
|
|
||||||
emitSelection(targetItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("RelatedDataButtons 데이터 로드 실패:", error);
|
|
||||||
setButtons([]);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}, [masterData, config.buttonDataSource, config.sourceMapping, config.buttonStyle, config.autoSelectFirst]);
|
|
||||||
|
|
||||||
// masterData 변경 시 버튼 로드
|
|
||||||
useEffect(() => {
|
|
||||||
if (masterData) {
|
|
||||||
setSelectedId(null); // 마스터 변경 시 선택 초기화
|
|
||||||
loadButtons();
|
|
||||||
}
|
|
||||||
}, [masterData, loadButtons]);
|
|
||||||
|
|
||||||
// 선택 이벤트 발생
|
|
||||||
const emitSelection = useCallback((item: ButtonItem) => {
|
|
||||||
if (!config.events?.targetTable || !config.events?.targetFilterColumn) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 커스텀 이벤트 발생 (하위 테이블 필터링용)
|
|
||||||
window.dispatchEvent(new CustomEvent("related-button-select", {
|
|
||||||
detail: {
|
|
||||||
targetTable: config.events.targetTable,
|
|
||||||
filterColumn: config.events.targetFilterColumn,
|
|
||||||
filterValue: item.value,
|
|
||||||
selectedData: item.rawData,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
console.log("📌 RelatedDataButtons 선택 이벤트:", {
|
|
||||||
targetTable: config.events.targetTable,
|
|
||||||
filterColumn: config.events.targetFilterColumn,
|
|
||||||
filterValue: item.value,
|
|
||||||
});
|
|
||||||
}, [config.events]);
|
|
||||||
|
|
||||||
// 버튼 클릭 핸들러
|
|
||||||
const handleButtonClick = useCallback((item: ButtonItem) => {
|
|
||||||
setSelectedId(item.id);
|
|
||||||
emitSelection(item);
|
|
||||||
}, [emitSelection]);
|
|
||||||
|
|
||||||
// 추가 버튼 클릭
|
|
||||||
const handleAddClick = useCallback(() => {
|
|
||||||
if (!config.addButton?.modalScreenId) return;
|
|
||||||
|
|
||||||
const filterValue = masterData?.[config.sourceMapping.sourceColumn];
|
|
||||||
|
|
||||||
window.dispatchEvent(new CustomEvent("open-screen-modal", {
|
|
||||||
detail: {
|
|
||||||
screenId: config.addButton.modalScreenId,
|
|
||||||
initialData: {
|
|
||||||
[config.buttonDataSource.filterColumn]: filterValue,
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
|
||||||
loadButtons(); // 모달 성공 후 새로고침
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}, [config.addButton, config.buttonDataSource.filterColumn, config.sourceMapping.sourceColumn, masterData, loadButtons]);
|
|
||||||
|
|
||||||
// 버튼 variant 계산
|
|
||||||
const getButtonVariant = useCallback((item: ButtonItem): "default" | "outline" | "secondary" | "ghost" => {
|
|
||||||
if (selectedId === item.id) {
|
|
||||||
return config.buttonStyle?.activeVariant || "default";
|
|
||||||
}
|
|
||||||
return config.buttonStyle?.variant || "outline";
|
|
||||||
}, [selectedId, config.buttonStyle]);
|
|
||||||
|
|
||||||
// 마스터 데이터 없음
|
|
||||||
if (!masterData) {
|
|
||||||
return (
|
|
||||||
<div className={cn("rounded-lg border bg-card p-4", className)} style={style}>
|
|
||||||
<p className="text-sm text-muted-foreground text-center">
|
|
||||||
좌측에서 항목을 선택하세요
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const headerConfig = config.headerDisplay;
|
|
||||||
const addButtonConfig = config.addButton;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={cn("rounded-lg border bg-card", className)} style={style}>
|
|
||||||
{/* 헤더 영역 */}
|
|
||||||
{headerConfig?.show !== false && (
|
|
||||||
<div className="flex items-start justify-between p-4 pb-3">
|
|
||||||
<div>
|
|
||||||
{/* 제목 (품목명 등) */}
|
|
||||||
{headerConfig?.titleColumn && masterData[headerConfig.titleColumn] && (
|
|
||||||
<h3 className="text-lg font-semibold">
|
|
||||||
{masterData[headerConfig.titleColumn]}
|
|
||||||
</h3>
|
|
||||||
)}
|
|
||||||
{/* 부제목 (품목코드 등) */}
|
|
||||||
{headerConfig?.subtitleColumn && masterData[headerConfig.subtitleColumn] && (
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{masterData[headerConfig.subtitleColumn]}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 헤더 위치 추가 버튼 */}
|
|
||||||
{addButtonConfig?.show && addButtonConfig?.position === "header" && (
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
size="sm"
|
|
||||||
onClick={handleAddClick}
|
|
||||||
className="bg-blue-600 hover:bg-blue-700"
|
|
||||||
>
|
|
||||||
<Plus className="mr-1 h-4 w-4" />
|
|
||||||
{addButtonConfig.label || "버전 추가"}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 버튼 영역 */}
|
|
||||||
<div className="px-4 pb-4">
|
|
||||||
{loading ? (
|
|
||||||
<div className="flex items-center justify-center py-4">
|
|
||||||
<Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
) : buttons.length === 0 ? (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{config.emptyMessage || "데이터가 없습니다"}
|
|
||||||
</p>
|
|
||||||
{/* 인라인 추가 버튼 (데이터 없을 때) */}
|
|
||||||
{addButtonConfig?.show && addButtonConfig?.position !== "header" && (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={handleAddClick}
|
|
||||||
className="border-dashed"
|
|
||||||
>
|
|
||||||
<Plus className="mr-1 h-4 w-4" />
|
|
||||||
{addButtonConfig.label || "추가"}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
|
||||||
{buttons.map((item) => (
|
|
||||||
<Button
|
|
||||||
key={item.id}
|
|
||||||
variant={getButtonVariant(item)}
|
|
||||||
size={config.buttonStyle?.size || "default"}
|
|
||||||
onClick={() => handleButtonClick(item)}
|
|
||||||
className={cn(
|
|
||||||
"relative",
|
|
||||||
selectedId === item.id && "ring-2 ring-primary ring-offset-1"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{/* 기본 버전 별표 */}
|
|
||||||
{item.isDefault && config.buttonStyle?.defaultIndicator?.showStar && (
|
|
||||||
<Star className="mr-1.5 h-3.5 w-3.5 fill-yellow-400 text-yellow-400" />
|
|
||||||
)}
|
|
||||||
{item.displayText}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* 인라인 추가 버튼 */}
|
|
||||||
{addButtonConfig?.show && addButtonConfig?.position !== "header" && (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size={config.buttonStyle?.size || "default"}
|
|
||||||
onClick={handleAddClick}
|
|
||||||
className="border-dashed"
|
|
||||||
>
|
|
||||||
<Plus className="mr-1 h-4 w-4" />
|
|
||||||
{addButtonConfig.label || "추가"}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RelatedDataButtonsComponent;
|
|
||||||
|
|
@ -1,558 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
||||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
|
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Check, ChevronsUpDown } from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { tableManagementApi, getTableColumns } from "@/lib/api/tableManagement";
|
|
||||||
import type { RelatedDataButtonsConfig } from "./types";
|
|
||||||
|
|
||||||
interface TableInfo {
|
|
||||||
tableName: string;
|
|
||||||
displayName?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ColumnInfo {
|
|
||||||
columnName: string;
|
|
||||||
columnLabel?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RelatedDataButtonsConfigPanelProps {
|
|
||||||
config: RelatedDataButtonsConfig;
|
|
||||||
onChange: (config: RelatedDataButtonsConfig) => void;
|
|
||||||
tables?: TableInfo[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RelatedDataButtonsConfigPanel: React.FC<RelatedDataButtonsConfigPanelProps> = ({
|
|
||||||
config,
|
|
||||||
onChange,
|
|
||||||
tables: propTables = [],
|
|
||||||
}) => {
|
|
||||||
const [allTables, setAllTables] = useState<TableInfo[]>([]);
|
|
||||||
const [sourceTableColumns, setSourceTableColumns] = useState<ColumnInfo[]>([]);
|
|
||||||
const [buttonTableColumns, setButtonTableColumns] = useState<ColumnInfo[]>([]);
|
|
||||||
|
|
||||||
// Popover 상태
|
|
||||||
const [sourceTableOpen, setSourceTableOpen] = useState(false);
|
|
||||||
const [buttonTableOpen, setButtonTableOpen] = useState(false);
|
|
||||||
|
|
||||||
// 전체 테이블 로드
|
|
||||||
useEffect(() => {
|
|
||||||
const loadTables = async () => {
|
|
||||||
try {
|
|
||||||
const response = await tableManagementApi.getTableList();
|
|
||||||
if (response.success && response.data) {
|
|
||||||
setAllTables(response.data.map((t: any) => ({
|
|
||||||
tableName: t.tableName || t.table_name,
|
|
||||||
displayName: t.tableLabel || t.table_label || t.displayName,
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("테이블 목록 로드 실패:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
loadTables();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 소스 테이블 컬럼 로드
|
|
||||||
useEffect(() => {
|
|
||||||
const loadColumns = async () => {
|
|
||||||
if (!config.sourceMapping?.sourceTable) {
|
|
||||||
setSourceTableColumns([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const response = await getTableColumns(config.sourceMapping.sourceTable);
|
|
||||||
if (response.success && response.data?.columns) {
|
|
||||||
setSourceTableColumns(response.data.columns.map((c: any) => ({
|
|
||||||
columnName: c.columnName || c.column_name,
|
|
||||||
columnLabel: c.columnLabel || c.column_label || c.displayName,
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("소스 테이블 컬럼 로드 실패:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
loadColumns();
|
|
||||||
}, [config.sourceMapping?.sourceTable]);
|
|
||||||
|
|
||||||
// 버튼 테이블 컬럼 로드
|
|
||||||
useEffect(() => {
|
|
||||||
const loadColumns = async () => {
|
|
||||||
if (!config.buttonDataSource?.tableName) {
|
|
||||||
setButtonTableColumns([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const response = await getTableColumns(config.buttonDataSource.tableName);
|
|
||||||
if (response.success && response.data?.columns) {
|
|
||||||
setButtonTableColumns(response.data.columns.map((c: any) => ({
|
|
||||||
columnName: c.columnName || c.column_name,
|
|
||||||
columnLabel: c.columnLabel || c.column_label || c.displayName,
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("버튼 테이블 컬럼 로드 실패:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
loadColumns();
|
|
||||||
}, [config.buttonDataSource?.tableName]);
|
|
||||||
|
|
||||||
// 설정 업데이트 헬퍼
|
|
||||||
const updateConfig = useCallback((updates: Partial<RelatedDataButtonsConfig>) => {
|
|
||||||
onChange({ ...config, ...updates });
|
|
||||||
}, [config, onChange]);
|
|
||||||
|
|
||||||
const updateSourceMapping = useCallback((updates: Partial<RelatedDataButtonsConfig["sourceMapping"]>) => {
|
|
||||||
onChange({
|
|
||||||
...config,
|
|
||||||
sourceMapping: { ...config.sourceMapping, ...updates },
|
|
||||||
});
|
|
||||||
}, [config, onChange]);
|
|
||||||
|
|
||||||
const updateHeaderDisplay = useCallback((updates: Partial<NonNullable<RelatedDataButtonsConfig["headerDisplay"]>>) => {
|
|
||||||
onChange({
|
|
||||||
...config,
|
|
||||||
headerDisplay: { ...config.headerDisplay, ...updates } as any,
|
|
||||||
});
|
|
||||||
}, [config, onChange]);
|
|
||||||
|
|
||||||
const updateButtonDataSource = useCallback((updates: Partial<RelatedDataButtonsConfig["buttonDataSource"]>) => {
|
|
||||||
onChange({
|
|
||||||
...config,
|
|
||||||
buttonDataSource: { ...config.buttonDataSource, ...updates },
|
|
||||||
});
|
|
||||||
}, [config, onChange]);
|
|
||||||
|
|
||||||
const updateButtonStyle = useCallback((updates: Partial<NonNullable<RelatedDataButtonsConfig["buttonStyle"]>>) => {
|
|
||||||
onChange({
|
|
||||||
...config,
|
|
||||||
buttonStyle: { ...config.buttonStyle, ...updates },
|
|
||||||
});
|
|
||||||
}, [config, onChange]);
|
|
||||||
|
|
||||||
const updateAddButton = useCallback((updates: Partial<NonNullable<RelatedDataButtonsConfig["addButton"]>>) => {
|
|
||||||
onChange({
|
|
||||||
...config,
|
|
||||||
addButton: { ...config.addButton, ...updates },
|
|
||||||
});
|
|
||||||
}, [config, onChange]);
|
|
||||||
|
|
||||||
const updateEvents = useCallback((updates: Partial<NonNullable<RelatedDataButtonsConfig["events"]>>) => {
|
|
||||||
onChange({
|
|
||||||
...config,
|
|
||||||
events: { ...config.events, ...updates },
|
|
||||||
});
|
|
||||||
}, [config, onChange]);
|
|
||||||
|
|
||||||
const tables = allTables.length > 0 ? allTables : propTables;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
{/* 소스 매핑 (좌측 패널 연결) */}
|
|
||||||
<div className="space-y-3">
|
|
||||||
<Label className="text-sm font-semibold">소스 테이블 (좌측 패널)</Label>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs">테이블</Label>
|
|
||||||
<Popover open={sourceTableOpen} onOpenChange={setSourceTableOpen}>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button variant="outline" role="combobox" className="w-full justify-between">
|
|
||||||
{config.sourceMapping?.sourceTable || "테이블 선택"}
|
|
||||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-full p-0">
|
|
||||||
<Command>
|
|
||||||
<CommandInput placeholder="테이블 검색..." />
|
|
||||||
<CommandEmpty>테이블을 찾을 수 없습니다.</CommandEmpty>
|
|
||||||
<CommandGroup className="max-h-[200px] overflow-auto">
|
|
||||||
{tables.map((table) => (
|
|
||||||
<CommandItem
|
|
||||||
key={table.tableName}
|
|
||||||
value={`${table.displayName || ""} ${table.tableName}`}
|
|
||||||
onSelect={() => {
|
|
||||||
updateSourceMapping({ sourceTable: table.tableName });
|
|
||||||
setSourceTableOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Check className={cn("mr-2 h-4 w-4", config.sourceMapping?.sourceTable === table.tableName ? "opacity-100" : "opacity-0")} />
|
|
||||||
{table.displayName || table.tableName}
|
|
||||||
{table.displayName && <span className="ml-2 text-xs text-gray-500">({table.tableName})</span>}
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
</Command>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs">필터 컬럼 (버튼 테이블 조회 시 사용)</Label>
|
|
||||||
<Select
|
|
||||||
value={config.sourceMapping?.sourceColumn || ""}
|
|
||||||
onValueChange={(value) => updateSourceMapping({ sourceColumn: value })}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="컬럼 선택" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{sourceTableColumns.map((col) => (
|
|
||||||
<SelectItem key={col.columnName} value={col.columnName}>
|
|
||||||
{col.columnLabel || col.columnName}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 헤더 표시 설정 */}
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Label className="text-sm font-semibold">헤더 표시</Label>
|
|
||||||
<Switch
|
|
||||||
checked={config.headerDisplay?.show !== false}
|
|
||||||
onCheckedChange={(checked) => updateHeaderDisplay({ show: checked })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{config.headerDisplay?.show !== false && (
|
|
||||||
<div className="space-y-2 pl-2 border-l-2 border-gray-200">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs">제목 컬럼</Label>
|
|
||||||
<Select
|
|
||||||
value={config.headerDisplay?.titleColumn || ""}
|
|
||||||
onValueChange={(value) => updateHeaderDisplay({ titleColumn: value })}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="제목 컬럼 선택" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{sourceTableColumns.map((col) => (
|
|
||||||
<SelectItem key={col.columnName} value={col.columnName}>
|
|
||||||
{col.columnLabel || col.columnName}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs">부제목 컬럼 (선택)</Label>
|
|
||||||
<Select
|
|
||||||
value={config.headerDisplay?.subtitleColumn || "__none__"}
|
|
||||||
onValueChange={(value) => updateHeaderDisplay({ subtitleColumn: value === "__none__" ? "" : value })}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="부제목 컬럼 선택" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="__none__">없음</SelectItem>
|
|
||||||
{sourceTableColumns.map((col) => (
|
|
||||||
<SelectItem key={col.columnName} value={col.columnName}>
|
|
||||||
{col.columnLabel || col.columnName}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 버튼 데이터 소스 */}
|
|
||||||
<div className="space-y-3">
|
|
||||||
<Label className="text-sm font-semibold">버튼 데이터 소스</Label>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs">테이블</Label>
|
|
||||||
<Popover open={buttonTableOpen} onOpenChange={setButtonTableOpen}>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button variant="outline" role="combobox" className="w-full justify-between">
|
|
||||||
{config.buttonDataSource?.tableName || "테이블 선택"}
|
|
||||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-full p-0">
|
|
||||||
<Command>
|
|
||||||
<CommandInput placeholder="테이블 검색..." />
|
|
||||||
<CommandEmpty>테이블을 찾을 수 없습니다.</CommandEmpty>
|
|
||||||
<CommandGroup className="max-h-[200px] overflow-auto">
|
|
||||||
{tables.map((table) => (
|
|
||||||
<CommandItem
|
|
||||||
key={table.tableName}
|
|
||||||
value={`${table.displayName || ""} ${table.tableName}`}
|
|
||||||
onSelect={() => {
|
|
||||||
updateButtonDataSource({ tableName: table.tableName });
|
|
||||||
setButtonTableOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Check className={cn("mr-2 h-4 w-4", config.buttonDataSource?.tableName === table.tableName ? "opacity-100" : "opacity-0")} />
|
|
||||||
{table.displayName || table.tableName}
|
|
||||||
{table.displayName && <span className="ml-2 text-xs text-gray-500">({table.tableName})</span>}
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
</Command>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs">필터 컬럼</Label>
|
|
||||||
<Select
|
|
||||||
value={config.buttonDataSource?.filterColumn || ""}
|
|
||||||
onValueChange={(value) => updateButtonDataSource({ filterColumn: value })}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="선택" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{buttonTableColumns.map((col) => (
|
|
||||||
<SelectItem key={col.columnName} value={col.columnName}>
|
|
||||||
{col.columnLabel || col.columnName}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs">표시 컬럼</Label>
|
|
||||||
<Select
|
|
||||||
value={config.buttonDataSource?.displayColumn || ""}
|
|
||||||
onValueChange={(value) => updateButtonDataSource({ displayColumn: value })}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="선택" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{buttonTableColumns.map((col) => (
|
|
||||||
<SelectItem key={col.columnName} value={col.columnName}>
|
|
||||||
{col.columnLabel || col.columnName}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 버튼 스타일 */}
|
|
||||||
<div className="space-y-3">
|
|
||||||
<Label className="text-sm font-semibold">버튼 스타일</Label>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs">기본 스타일</Label>
|
|
||||||
<Select
|
|
||||||
value={config.buttonStyle?.variant || "outline"}
|
|
||||||
onValueChange={(value: any) => updateButtonStyle({ variant: value })}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="default">Default</SelectItem>
|
|
||||||
<SelectItem value="outline">Outline</SelectItem>
|
|
||||||
<SelectItem value="secondary">Secondary</SelectItem>
|
|
||||||
<SelectItem value="ghost">Ghost</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs">선택 시 스타일</Label>
|
|
||||||
<Select
|
|
||||||
value={config.buttonStyle?.activeVariant || "default"}
|
|
||||||
onValueChange={(value: any) => updateButtonStyle({ activeVariant: value })}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="default">Default</SelectItem>
|
|
||||||
<SelectItem value="outline">Outline</SelectItem>
|
|
||||||
<SelectItem value="secondary">Secondary</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 기본 표시 설정 */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs">기본 버전 표시 컬럼</Label>
|
|
||||||
<Select
|
|
||||||
value={config.buttonStyle?.defaultIndicator?.column || "__none__"}
|
|
||||||
onValueChange={(value) => updateButtonStyle({
|
|
||||||
defaultIndicator: {
|
|
||||||
...config.buttonStyle?.defaultIndicator,
|
|
||||||
column: value === "__none__" ? "" : value,
|
|
||||||
showStar: config.buttonStyle?.defaultIndicator?.showStar ?? true,
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="없음" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="__none__">없음</SelectItem>
|
|
||||||
{buttonTableColumns.map((col) => (
|
|
||||||
<SelectItem key={col.columnName} value={col.columnName}>
|
|
||||||
{col.columnLabel || col.columnName}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{config.buttonStyle?.defaultIndicator?.column && (
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Switch
|
|
||||||
checked={config.buttonStyle?.defaultIndicator?.showStar ?? true}
|
|
||||||
onCheckedChange={(checked) => updateButtonStyle({
|
|
||||||
defaultIndicator: {
|
|
||||||
...config.buttonStyle?.defaultIndicator,
|
|
||||||
column: config.buttonStyle?.defaultIndicator?.column || "",
|
|
||||||
showStar: checked,
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<Label className="text-xs">별표 표시</Label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 이벤트 설정 (하위 테이블 연동) */}
|
|
||||||
<div className="space-y-3">
|
|
||||||
<Label className="text-sm font-semibold">하위 테이블 연동</Label>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs">대상 테이블</Label>
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button variant="outline" role="combobox" className="w-full justify-between">
|
|
||||||
{config.events?.targetTable || "테이블 선택"}
|
|
||||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-full p-0">
|
|
||||||
<Command>
|
|
||||||
<CommandInput placeholder="테이블 검색..." />
|
|
||||||
<CommandEmpty>테이블을 찾을 수 없습니다.</CommandEmpty>
|
|
||||||
<CommandGroup className="max-h-[200px] overflow-auto">
|
|
||||||
{tables.map((table) => (
|
|
||||||
<CommandItem
|
|
||||||
key={table.tableName}
|
|
||||||
value={`${table.displayName || ""} ${table.tableName}`}
|
|
||||||
onSelect={() => {
|
|
||||||
updateEvents({ targetTable: table.tableName });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Check className={cn("mr-2 h-4 w-4", config.events?.targetTable === table.tableName ? "opacity-100" : "opacity-0")} />
|
|
||||||
{table.displayName || table.tableName}
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
</Command>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs">필터 컬럼 (버튼 값 컬럼 → 대상 테이블 컬럼)</Label>
|
|
||||||
<Input
|
|
||||||
value={config.events?.targetFilterColumn || ""}
|
|
||||||
onChange={(e) => updateEvents({ targetFilterColumn: e.target.value })}
|
|
||||||
placeholder="예: routing_version_id"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 추가 버튼 설정 */}
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Label className="text-sm font-semibold">추가 버튼</Label>
|
|
||||||
<Switch
|
|
||||||
checked={config.addButton?.show ?? false}
|
|
||||||
onCheckedChange={(checked) => updateAddButton({ show: checked })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{config.addButton?.show && (
|
|
||||||
<div className="space-y-2 pl-2 border-l-2 border-gray-200">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs">버튼 라벨</Label>
|
|
||||||
<Input
|
|
||||||
value={config.addButton?.label || ""}
|
|
||||||
onChange={(e) => updateAddButton({ label: e.target.value })}
|
|
||||||
placeholder="+ 버전 추가"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs">위치</Label>
|
|
||||||
<Select
|
|
||||||
value={config.addButton?.position || "header"}
|
|
||||||
onValueChange={(value: any) => updateAddButton({ position: value })}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="header">헤더 우측</SelectItem>
|
|
||||||
<SelectItem value="inline">버튼들과 함께</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs">모달 화면 ID</Label>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
value={config.addButton?.modalScreenId || ""}
|
|
||||||
onChange={(e) => updateAddButton({ modalScreenId: parseInt(e.target.value) || undefined })}
|
|
||||||
placeholder="화면 ID"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 기타 설정 */}
|
|
||||||
<div className="space-y-3">
|
|
||||||
<Label className="text-sm font-semibold">기타 설정</Label>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Switch
|
|
||||||
checked={config.autoSelectFirst ?? true}
|
|
||||||
onCheckedChange={(checked) => updateConfig({ autoSelectFirst: checked })}
|
|
||||||
/>
|
|
||||||
<Label className="text-xs">첫 번째 항목 자동 선택</Label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs">빈 상태 메시지</Label>
|
|
||||||
<Input
|
|
||||||
value={config.emptyMessage || ""}
|
|
||||||
onChange={(e) => updateConfig({ emptyMessage: e.target.value })}
|
|
||||||
placeholder="데이터가 없습니다"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RelatedDataButtonsConfigPanel;
|
|
||||||
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import { AutoRegisteringComponentRenderer } from "../../AutoRegisteringComponentRenderer";
|
|
||||||
import { RelatedDataButtonsDefinition } from "./index";
|
|
||||||
import { RelatedDataButtonsComponent } from "./RelatedDataButtonsComponent";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RelatedDataButtons 렌더러
|
|
||||||
* 자동 등록 시스템을 사용하여 컴포넌트를 레지스트리에 등록
|
|
||||||
*/
|
|
||||||
export class RelatedDataButtonsRenderer extends AutoRegisteringComponentRenderer {
|
|
||||||
static componentDefinition = RelatedDataButtonsDefinition;
|
|
||||||
|
|
||||||
render(): React.ReactElement {
|
|
||||||
const { component } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RelatedDataButtonsComponent
|
|
||||||
config={component?.config || RelatedDataButtonsDefinition.defaultConfig}
|
|
||||||
className={component?.className}
|
|
||||||
style={component?.style}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 자동 등록 실행
|
|
||||||
RelatedDataButtonsRenderer.registerSelf();
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
import type { ComponentConfig } from "@/lib/registry/types";
|
|
||||||
|
|
||||||
export const relatedDataButtonsConfig: ComponentConfig = {
|
|
||||||
id: "related-data-buttons",
|
|
||||||
name: "연관 데이터 버튼",
|
|
||||||
description: "좌측 패널에서 선택한 데이터를 기반으로 연관 테이블의 데이터를 버튼으로 표시합니다. 예: 품목 선택 → 라우팅 버전 버튼들",
|
|
||||||
category: "data",
|
|
||||||
webType: "container",
|
|
||||||
version: "1.0.0",
|
|
||||||
icon: "LayoutList",
|
|
||||||
defaultConfig: {
|
|
||||||
sourceMapping: {
|
|
||||||
sourceTable: "",
|
|
||||||
sourceColumn: "",
|
|
||||||
},
|
|
||||||
headerDisplay: {
|
|
||||||
show: true,
|
|
||||||
titleColumn: "",
|
|
||||||
subtitleColumn: "",
|
|
||||||
},
|
|
||||||
buttonDataSource: {
|
|
||||||
tableName: "",
|
|
||||||
filterColumn: "",
|
|
||||||
displayColumn: "",
|
|
||||||
valueColumn: "id",
|
|
||||||
orderColumn: "created_date",
|
|
||||||
orderDirection: "ASC",
|
|
||||||
},
|
|
||||||
buttonStyle: {
|
|
||||||
variant: "outline",
|
|
||||||
activeVariant: "default",
|
|
||||||
size: "default",
|
|
||||||
defaultIndicator: {
|
|
||||||
column: "",
|
|
||||||
showStar: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
addButton: {
|
|
||||||
show: false,
|
|
||||||
label: "+ 버전 추가",
|
|
||||||
position: "header",
|
|
||||||
},
|
|
||||||
events: {
|
|
||||||
targetTable: "",
|
|
||||||
targetFilterColumn: "",
|
|
||||||
},
|
|
||||||
autoSelectFirst: true,
|
|
||||||
emptyMessage: "데이터가 없습니다",
|
|
||||||
},
|
|
||||||
configPanelComponent: "RelatedDataButtonsConfigPanel",
|
|
||||||
rendererComponent: "RelatedDataButtonsRenderer",
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import { createComponentDefinition } from "../../utils/createComponentDefinition";
|
|
||||||
import { ComponentCategory } from "@/types/component";
|
|
||||||
import { RelatedDataButtonsComponent } from "./RelatedDataButtonsComponent";
|
|
||||||
import { RelatedDataButtonsConfigPanel } from "./RelatedDataButtonsConfigPanel";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RelatedDataButtons 컴포넌트 정의
|
|
||||||
* 좌측 패널에서 선택한 데이터를 기반으로 연관 테이블의 데이터를 버튼으로 표시
|
|
||||||
*/
|
|
||||||
export const RelatedDataButtonsDefinition = createComponentDefinition({
|
|
||||||
id: "related-data-buttons",
|
|
||||||
name: "연관 데이터 버튼",
|
|
||||||
nameEng: "Related Data Buttons",
|
|
||||||
description: "좌측 패널에서 선택한 데이터를 기반으로 연관 테이블의 데이터를 버튼으로 표시합니다. 예: 품목 선택 → 라우팅 버전 버튼들",
|
|
||||||
category: ComponentCategory.DATA,
|
|
||||||
webType: "container",
|
|
||||||
component: RelatedDataButtonsComponent,
|
|
||||||
defaultConfig: {
|
|
||||||
sourceMapping: {
|
|
||||||
sourceTable: "",
|
|
||||||
sourceColumn: "",
|
|
||||||
},
|
|
||||||
headerDisplay: {
|
|
||||||
show: true,
|
|
||||||
titleColumn: "",
|
|
||||||
subtitleColumn: "",
|
|
||||||
},
|
|
||||||
buttonDataSource: {
|
|
||||||
tableName: "",
|
|
||||||
filterColumn: "",
|
|
||||||
displayColumn: "",
|
|
||||||
valueColumn: "id",
|
|
||||||
orderColumn: "created_date",
|
|
||||||
orderDirection: "ASC",
|
|
||||||
},
|
|
||||||
buttonStyle: {
|
|
||||||
variant: "outline",
|
|
||||||
activeVariant: "default",
|
|
||||||
size: "default",
|
|
||||||
defaultIndicator: {
|
|
||||||
column: "",
|
|
||||||
showStar: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
addButton: {
|
|
||||||
show: false,
|
|
||||||
label: "+ 버전 추가",
|
|
||||||
position: "header",
|
|
||||||
},
|
|
||||||
events: {
|
|
||||||
targetTable: "",
|
|
||||||
targetFilterColumn: "",
|
|
||||||
},
|
|
||||||
autoSelectFirst: true,
|
|
||||||
emptyMessage: "데이터가 없습니다",
|
|
||||||
},
|
|
||||||
defaultSize: { width: 400, height: 120 },
|
|
||||||
configPanel: RelatedDataButtonsConfigPanel,
|
|
||||||
icon: "LayoutList",
|
|
||||||
tags: ["버튼", "연관데이터", "마스터디테일", "라우팅"],
|
|
||||||
version: "1.0.0",
|
|
||||||
author: "개발팀",
|
|
||||||
});
|
|
||||||
|
|
||||||
// 타입 내보내기
|
|
||||||
export type { RelatedDataButtonsConfig, ButtonItem } from "./types";
|
|
||||||
export { RelatedDataButtonsComponent } from "./RelatedDataButtonsComponent";
|
|
||||||
export { RelatedDataButtonsConfigPanel } from "./RelatedDataButtonsConfigPanel";
|
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
/**
|
|
||||||
* RelatedDataButtons 컴포넌트 타입 정의
|
|
||||||
*
|
|
||||||
* 좌측 패널에서 선택한 데이터의 정보를 표시하고,
|
|
||||||
* 연관 테이블의 데이터를 버튼으로 표시하는 컴포넌트
|
|
||||||
*
|
|
||||||
* 예시: 품목 선택 → 품목명/코드 표시 + 라우팅 버전 버튼들
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 헤더 표시 설정 (선택된 마스터 데이터 정보)
|
|
||||||
*/
|
|
||||||
export interface HeaderDisplayConfig {
|
|
||||||
show?: boolean; // 헤더 표시 여부
|
|
||||||
titleColumn: string; // 제목으로 표시할 컬럼 (예: item_name)
|
|
||||||
subtitleColumn?: string; // 부제목으로 표시할 컬럼 (예: item_code)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 버튼 데이터 소스 설정
|
|
||||||
*/
|
|
||||||
export interface ButtonDataSourceConfig {
|
|
||||||
tableName: string; // 조회할 테이블명 (예: item_routing_version)
|
|
||||||
filterColumn: string; // 필터링할 컬럼명 (예: item_code)
|
|
||||||
displayColumn: string; // 버튼에 표시할 컬럼명 (예: version_name)
|
|
||||||
valueColumn?: string; // 선택 시 전달할 값 컬럼 (기본: id)
|
|
||||||
orderColumn?: string; // 정렬 컬럼
|
|
||||||
orderDirection?: "ASC" | "DESC"; // 정렬 방향
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 버튼 스타일 설정
|
|
||||||
*/
|
|
||||||
export interface ButtonStyleConfig {
|
|
||||||
variant?: "default" | "outline" | "secondary" | "ghost";
|
|
||||||
activeVariant?: "default" | "outline" | "secondary";
|
|
||||||
size?: "sm" | "default" | "lg";
|
|
||||||
// 기본 버전 표시 설정
|
|
||||||
defaultIndicator?: {
|
|
||||||
column: string; // 기본 여부 판단 컬럼 (예: is_default)
|
|
||||||
value?: string; // 기본 값 (기본: "Y" 또는 true)
|
|
||||||
showStar?: boolean; // 별표 아이콘 표시
|
|
||||||
badgeText?: string; // 뱃지 텍스트 (예: "기본")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 추가 버튼 설정
|
|
||||||
*/
|
|
||||||
export interface AddButtonConfig {
|
|
||||||
show?: boolean;
|
|
||||||
label?: string; // 기본: "+ 버전 추가"
|
|
||||||
modalScreenId?: number;
|
|
||||||
position?: "header" | "inline"; // header: 헤더 우측, inline: 버튼들과 함께
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 이벤트 설정 (하위 테이블 연동)
|
|
||||||
*/
|
|
||||||
export interface EventConfig {
|
|
||||||
// 선택 시 하위 테이블 필터링
|
|
||||||
targetTable?: string; // 필터링할 테이블명 (예: item_routing_detail)
|
|
||||||
targetFilterColumn?: string; // 필터 컬럼명 (예: routing_version_id)
|
|
||||||
// 커스텀 이벤트
|
|
||||||
customEventName?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 메인 설정
|
|
||||||
*/
|
|
||||||
export interface RelatedDataButtonsConfig {
|
|
||||||
// 소스 매핑 (좌측 패널 연결)
|
|
||||||
sourceMapping: {
|
|
||||||
sourceTable: string; // 좌측 패널 테이블명
|
|
||||||
sourceColumn: string; // 필터에 사용할 컬럼 (예: item_code)
|
|
||||||
};
|
|
||||||
|
|
||||||
// 헤더 표시 설정
|
|
||||||
headerDisplay?: HeaderDisplayConfig;
|
|
||||||
|
|
||||||
// 버튼 데이터 소스
|
|
||||||
buttonDataSource: ButtonDataSourceConfig;
|
|
||||||
|
|
||||||
// 버튼 스타일
|
|
||||||
buttonStyle?: ButtonStyleConfig;
|
|
||||||
|
|
||||||
// 추가 버튼
|
|
||||||
addButton?: AddButtonConfig;
|
|
||||||
|
|
||||||
// 이벤트 설정
|
|
||||||
events?: EventConfig;
|
|
||||||
|
|
||||||
// 자동 선택
|
|
||||||
autoSelectFirst?: boolean; // 첫 번째 (또는 기본) 항목 자동 선택
|
|
||||||
|
|
||||||
// 빈 상태 메시지
|
|
||||||
emptyMessage?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 버튼 아이템 데이터
|
|
||||||
*/
|
|
||||||
export interface ButtonItem {
|
|
||||||
id: string;
|
|
||||||
displayText: string;
|
|
||||||
value: string;
|
|
||||||
isDefault: boolean;
|
|
||||||
rawData: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
@ -1522,9 +1522,9 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
|
||||||
{availableRightTables.map((table) => (
|
{availableRightTables.map((table) => (
|
||||||
<CommandItem
|
<CommandItem
|
||||||
key={table.tableName}
|
key={table.tableName}
|
||||||
value={`${table.displayName || ""} ${table.tableName}`}
|
value={table.tableName}
|
||||||
onSelect={() => {
|
onSelect={(value) => {
|
||||||
updateRightPanel({ tableName: table.tableName });
|
updateRightPanel({ tableName: value });
|
||||||
setRightTableOpen(false);
|
setRightTableOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,10 @@
|
||||||
import { WebType } from "./unified-core";
|
import { WebType } from "./unified-core";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 핵심 입력 타입
|
* 9개 핵심 입력 타입
|
||||||
*/
|
*/
|
||||||
export type BaseInputType =
|
export type BaseInputType =
|
||||||
| "text" // 텍스트
|
| "text" // 텍스트
|
||||||
| "textarea" // 텍스트 에리어 (여러 줄)
|
|
||||||
| "number" // 숫자
|
| "number" // 숫자
|
||||||
| "date" // 날짜
|
| "date" // 날짜
|
||||||
| "code" // 코드
|
| "code" // 코드
|
||||||
|
|
@ -35,18 +34,16 @@ export interface DetailTypeOption {
|
||||||
* 입력 타입별 세부 타입 매핑
|
* 입력 타입별 세부 타입 매핑
|
||||||
*/
|
*/
|
||||||
export const INPUT_TYPE_DETAIL_TYPES: Record<BaseInputType, DetailTypeOption[]> = {
|
export const INPUT_TYPE_DETAIL_TYPES: Record<BaseInputType, DetailTypeOption[]> = {
|
||||||
// 텍스트 → text, email, tel, url, password
|
// 텍스트 → text, email, tel, url, textarea, password
|
||||||
text: [
|
text: [
|
||||||
{ value: "text", label: "일반 텍스트", description: "기본 텍스트 입력" },
|
{ value: "text", label: "일반 텍스트", description: "기본 텍스트 입력" },
|
||||||
{ value: "email", label: "이메일", description: "이메일 주소 입력" },
|
{ value: "email", label: "이메일", description: "이메일 주소 입력" },
|
||||||
{ value: "tel", label: "전화번호", description: "전화번호 입력" },
|
{ value: "tel", label: "전화번호", description: "전화번호 입력" },
|
||||||
{ value: "url", label: "URL", description: "웹사이트 주소 입력" },
|
{ value: "url", label: "URL", description: "웹사이트 주소 입력" },
|
||||||
|
{ value: "textarea", label: "여러 줄 텍스트", description: "긴 텍스트 입력" },
|
||||||
{ value: "password", label: "비밀번호", description: "비밀번호 입력 (마스킹)" },
|
{ value: "password", label: "비밀번호", description: "비밀번호 입력 (마스킹)" },
|
||||||
],
|
],
|
||||||
|
|
||||||
// 텍스트 에리어 → textarea
|
|
||||||
textarea: [{ value: "textarea", label: "텍스트 에리어", description: "여러 줄 텍스트 입력" }],
|
|
||||||
|
|
||||||
// 숫자 → number, decimal, currency, percentage
|
// 숫자 → number, decimal, currency, percentage
|
||||||
number: [
|
number: [
|
||||||
{ value: "number", label: "정수", description: "정수 숫자 입력" },
|
{ value: "number", label: "정수", description: "정수 숫자 입력" },
|
||||||
|
|
@ -105,13 +102,8 @@ export const INPUT_TYPE_DETAIL_TYPES: Record<BaseInputType, DetailTypeOption[]>
|
||||||
* 웹타입에서 기본 입력 타입 추출
|
* 웹타입에서 기본 입력 타입 추출
|
||||||
*/
|
*/
|
||||||
export function getBaseInputType(webType: WebType): BaseInputType {
|
export function getBaseInputType(webType: WebType): BaseInputType {
|
||||||
// textarea (별도 타입으로 분리)
|
|
||||||
if (webType === "textarea") {
|
|
||||||
return "textarea";
|
|
||||||
}
|
|
||||||
|
|
||||||
// text 계열
|
// text 계열
|
||||||
if (["text", "email", "tel", "url", "password"].includes(webType)) {
|
if (["text", "email", "tel", "url", "textarea", "password"].includes(webType)) {
|
||||||
return "text";
|
return "text";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,7 +167,6 @@ export function getDefaultDetailType(baseInputType: BaseInputType): WebType {
|
||||||
*/
|
*/
|
||||||
export const BASE_INPUT_TYPE_OPTIONS: Array<{ value: BaseInputType; label: string; description: string }> = [
|
export const BASE_INPUT_TYPE_OPTIONS: Array<{ value: BaseInputType; label: string; description: string }> = [
|
||||||
{ value: "text", label: "텍스트", description: "텍스트 입력 필드" },
|
{ value: "text", label: "텍스트", description: "텍스트 입력 필드" },
|
||||||
{ value: "textarea", label: "텍스트 에리어", description: "여러 줄 텍스트 입력" },
|
|
||||||
{ value: "number", label: "숫자", description: "숫자 입력 필드" },
|
{ value: "number", label: "숫자", description: "숫자 입력 필드" },
|
||||||
{ value: "date", label: "날짜", description: "날짜/시간 선택" },
|
{ value: "date", label: "날짜", description: "날짜/시간 선택" },
|
||||||
{ value: "code", label: "코드", description: "공통 코드 선택" },
|
{ value: "code", label: "코드", description: "공통 코드 선택" },
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,9 @@
|
||||||
* 주의: 이 파일을 수정할 때는 반드시 백엔드 타입도 함께 업데이트 해야 합니다.
|
* 주의: 이 파일을 수정할 때는 반드시 백엔드 타입도 함께 업데이트 해야 합니다.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 핵심 입력 타입
|
// 9개 핵심 입력 타입
|
||||||
export type InputType =
|
export type InputType =
|
||||||
| "text" // 텍스트
|
| "text" // 텍스트
|
||||||
| "textarea" // 텍스트 에리어 (여러 줄 입력)
|
|
||||||
| "number" // 숫자
|
| "number" // 숫자
|
||||||
| "date" // 날짜
|
| "date" // 날짜
|
||||||
| "code" // 코드
|
| "code" // 코드
|
||||||
|
|
@ -43,13 +42,6 @@ export const INPUT_TYPE_OPTIONS: InputTypeOption[] = [
|
||||||
category: "basic",
|
category: "basic",
|
||||||
icon: "Type",
|
icon: "Type",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
value: "textarea",
|
|
||||||
label: "텍스트 에리어",
|
|
||||||
description: "여러 줄 텍스트 입력",
|
|
||||||
category: "basic",
|
|
||||||
icon: "AlignLeft",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
value: "number",
|
value: "number",
|
||||||
label: "숫자",
|
label: "숫자",
|
||||||
|
|
@ -138,11 +130,6 @@ export const INPUT_TYPE_DEFAULT_CONFIGS: Record<InputType, Record<string, any>>
|
||||||
maxLength: 500,
|
maxLength: 500,
|
||||||
placeholder: "텍스트를 입력하세요",
|
placeholder: "텍스트를 입력하세요",
|
||||||
},
|
},
|
||||||
textarea: {
|
|
||||||
maxLength: 2000,
|
|
||||||
rows: 4,
|
|
||||||
placeholder: "내용을 입력하세요",
|
|
||||||
},
|
|
||||||
number: {
|
number: {
|
||||||
min: 0,
|
min: 0,
|
||||||
step: 1,
|
step: 1,
|
||||||
|
|
@ -176,17 +163,13 @@ export const INPUT_TYPE_DEFAULT_CONFIGS: Record<InputType, Record<string, any>>
|
||||||
radio: {
|
radio: {
|
||||||
inline: false,
|
inline: false,
|
||||||
},
|
},
|
||||||
image: {
|
|
||||||
placeholder: "이미지를 선택하세요",
|
|
||||||
accept: "image/*",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 레거시 웹 타입 → 입력 타입 매핑
|
// 레거시 웹 타입 → 입력 타입 매핑
|
||||||
export const WEB_TYPE_TO_INPUT_TYPE: Record<string, InputType> = {
|
export const WEB_TYPE_TO_INPUT_TYPE: Record<string, InputType> = {
|
||||||
// 텍스트 관련
|
// 텍스트 관련
|
||||||
text: "text",
|
text: "text",
|
||||||
textarea: "textarea",
|
textarea: "text",
|
||||||
email: "text",
|
email: "text",
|
||||||
tel: "text",
|
tel: "text",
|
||||||
url: "text",
|
url: "text",
|
||||||
|
|
@ -221,7 +204,6 @@ export const WEB_TYPE_TO_INPUT_TYPE: Record<string, InputType> = {
|
||||||
// 입력 타입 → 웹 타입 역매핑 (화면관리 시스템 호환용)
|
// 입력 타입 → 웹 타입 역매핑 (화면관리 시스템 호환용)
|
||||||
export const INPUT_TYPE_TO_WEB_TYPE: Record<InputType, string> = {
|
export const INPUT_TYPE_TO_WEB_TYPE: Record<InputType, string> = {
|
||||||
text: "text",
|
text: "text",
|
||||||
textarea: "textarea",
|
|
||||||
number: "number",
|
number: "number",
|
||||||
date: "date",
|
date: "date",
|
||||||
code: "code",
|
code: "code",
|
||||||
|
|
@ -230,7 +212,6 @@ export const INPUT_TYPE_TO_WEB_TYPE: Record<InputType, string> = {
|
||||||
select: "select",
|
select: "select",
|
||||||
checkbox: "checkbox",
|
checkbox: "checkbox",
|
||||||
radio: "radio",
|
radio: "radio",
|
||||||
image: "image",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 입력 타입 변환 함수
|
// 입력 타입 변환 함수
|
||||||
|
|
@ -245,11 +226,6 @@ export const INPUT_TYPE_VALIDATION_RULES: Record<InputType, Record<string, any>>
|
||||||
trim: true,
|
trim: true,
|
||||||
maxLength: 500,
|
maxLength: 500,
|
||||||
},
|
},
|
||||||
textarea: {
|
|
||||||
type: "string",
|
|
||||||
trim: true,
|
|
||||||
maxLength: 2000,
|
|
||||||
},
|
|
||||||
number: {
|
number: {
|
||||||
type: "number",
|
type: "number",
|
||||||
allowFloat: true,
|
allowFloat: true,
|
||||||
|
|
@ -282,8 +258,4 @@ export const INPUT_TYPE_VALIDATION_RULES: Record<InputType, Record<string, any>>
|
||||||
type: "string",
|
type: "string",
|
||||||
options: true,
|
options: true,
|
||||||
},
|
},
|
||||||
image: {
|
|
||||||
type: "string",
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue