[agent-pipeline] pipe-20260311130333-zqic round-1
This commit is contained in:
parent
1d9ed6b36b
commit
ae852ed4ad
|
|
@ -11,11 +11,12 @@
|
||||||
import React, { useState, useEffect, useMemo, useCallback } from "react";
|
import React, { useState, useEffect, useMemo, useCallback } from "react";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
// Separator 제거 - 팔란티어 스타일 섹션 헤더 사용
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||||
import {
|
import {
|
||||||
Database,
|
Database,
|
||||||
Link2,
|
Link2,
|
||||||
|
|
@ -31,7 +32,10 @@ import {
|
||||||
Wand2,
|
Wand2,
|
||||||
Check,
|
Check,
|
||||||
ChevronsUpDown,
|
ChevronsUpDown,
|
||||||
ListTree,
|
Settings,
|
||||||
|
Rows3,
|
||||||
|
Columns3,
|
||||||
|
MousePointerClick,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import {
|
import {
|
||||||
Command,
|
Command,
|
||||||
|
|
@ -55,10 +59,7 @@ import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
V2RepeaterConfig,
|
V2RepeaterConfig,
|
||||||
RepeaterColumnConfig,
|
RepeaterColumnConfig,
|
||||||
RepeaterEntityJoin,
|
|
||||||
DEFAULT_REPEATER_CONFIG,
|
DEFAULT_REPEATER_CONFIG,
|
||||||
RENDER_MODE_OPTIONS,
|
|
||||||
MODAL_SIZE_OPTIONS,
|
|
||||||
} from "@/types/v2-repeater";
|
} from "@/types/v2-repeater";
|
||||||
|
|
||||||
// 테이블 엔티티 관계 정보
|
// 테이블 엔티티 관계 정보
|
||||||
|
|
@ -759,7 +760,7 @@ export const V2RepeaterConfigPanel: React.FC<V2RepeaterConfigPanelProps> = ({
|
||||||
}, [currentTableColumns, config.dataSource?.foreignKey]);
|
}, [currentTableColumns, config.dataSource?.foreignKey]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-1">
|
<div className="space-y-4">
|
||||||
<Tabs defaultValue="basic" className="w-full">
|
<Tabs defaultValue="basic" className="w-full">
|
||||||
<TabsList className="grid w-full grid-cols-3">
|
<TabsList className="grid w-full grid-cols-3">
|
||||||
<TabsTrigger value="basic" className="text-xs">기본</TabsTrigger>
|
<TabsTrigger value="basic" className="text-xs">기본</TabsTrigger>
|
||||||
|
|
@ -768,63 +769,69 @@ export const V2RepeaterConfigPanel: React.FC<V2RepeaterConfigPanelProps> = ({
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
{/* 기본 설정 탭 */}
|
{/* 기본 설정 탭 */}
|
||||||
<TabsContent value="basic" className="mt-4 space-y-1">
|
<TabsContent value="basic" className="mt-4 space-y-4">
|
||||||
{/* 렌더링 모드 */}
|
{/* 렌더링 모드 - 카드 선택 */}
|
||||||
<div className="border-b border-border/50 pb-3 mb-3">
|
<div className="space-y-2">
|
||||||
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">RENDER MODE</h4>
|
<p className="text-sm font-medium">리피터를 어떤 방식으로 사용하나요?</p>
|
||||||
<Select
|
<div className="grid grid-cols-3 gap-2">
|
||||||
value={config.renderMode}
|
{[
|
||||||
onValueChange={(value) => {
|
{ value: "inline", icon: Rows3, title: "직접 입력", description: "테이블 컬럼에 바로 입력해요" },
|
||||||
const newMode = value as any;
|
{ value: "modal", icon: Columns3, title: "모달 선택", description: "엔티티를 검색해서 추가해요" },
|
||||||
const currentMode = config.renderMode;
|
{ value: "button", icon: MousePointerClick, title: "버튼 연결", description: "버튼으로 관련 화면을 열어요" },
|
||||||
|
].map((card) => {
|
||||||
// 모달 → 인라인 모드로 변경 시: isSourceDisplay 컬럼 제거 및 모달 설정 초기화
|
const Icon = card.icon;
|
||||||
if (currentMode === "modal" && newMode === "inline") {
|
const isSelected = config.renderMode === card.value;
|
||||||
const filteredColumns = config.columns.filter((col) => !col.isSourceDisplay);
|
return (
|
||||||
updateConfig({
|
<button
|
||||||
renderMode: newMode,
|
key={card.value}
|
||||||
columns: filteredColumns,
|
type="button"
|
||||||
dataSource: {
|
onClick={() => {
|
||||||
...config.dataSource,
|
const newMode = card.value as any;
|
||||||
sourceTable: undefined,
|
const currentMode = config.renderMode;
|
||||||
foreignKey: undefined,
|
if (currentMode === "modal" && newMode === "inline") {
|
||||||
referenceKey: undefined,
|
const filteredColumns = config.columns.filter((col) => !col.isSourceDisplay);
|
||||||
displayColumn: undefined,
|
updateConfig({
|
||||||
},
|
renderMode: newMode,
|
||||||
modal: {
|
columns: filteredColumns,
|
||||||
...config.modal,
|
dataSource: {
|
||||||
searchFields: [],
|
...config.dataSource,
|
||||||
sourceDisplayColumns: [],
|
sourceTable: undefined,
|
||||||
},
|
foreignKey: undefined,
|
||||||
});
|
referenceKey: undefined,
|
||||||
} else {
|
displayColumn: undefined,
|
||||||
updateConfig({ renderMode: newMode });
|
},
|
||||||
}
|
modal: {
|
||||||
}}
|
...config.modal,
|
||||||
>
|
searchFields: [],
|
||||||
<SelectTrigger className="h-7 text-xs">
|
sourceDisplayColumns: [],
|
||||||
<SelectValue placeholder="모드 선택" />
|
},
|
||||||
</SelectTrigger>
|
});
|
||||||
<SelectContent>
|
} else {
|
||||||
{RENDER_MODE_OPTIONS.map((opt) => (
|
updateConfig({ renderMode: newMode });
|
||||||
<SelectItem key={opt.value} value={opt.value}>
|
}
|
||||||
<div className="flex flex-col">
|
}}
|
||||||
<span>{opt.label}</span>
|
className={cn(
|
||||||
<span className="text-[10px] text-muted-foreground/70">
|
"flex flex-col items-center justify-center rounded-lg border p-3 text-center transition-all min-h-[80px]",
|
||||||
{opt.value === "inline" && "현재 테이블 컬럼 직접 입력"}
|
isSelected
|
||||||
{opt.value === "modal" && "엔티티 선택 후 추가 정보 입력"}
|
? "border-primary bg-primary/5 ring-1 ring-primary/20"
|
||||||
{opt.value === "button" && "버튼으로 관련 화면 열기"}
|
: "border-border hover:border-primary/50 hover:bg-muted/50"
|
||||||
</span>
|
)}
|
||||||
</div>
|
>
|
||||||
</SelectItem>
|
<Icon className="h-5 w-5 mb-1.5 text-primary" />
|
||||||
))}
|
<span className="text-xs font-medium leading-tight">{card.title}</span>
|
||||||
</SelectContent>
|
<span className="text-[10px] text-muted-foreground leading-tight mt-0.5">{card.description}</span>
|
||||||
</Select>
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 저장 대상 테이블 */}
|
{/* 저장 대상 테이블 */}
|
||||||
<div className="border-b border-border/50 pb-3 mb-3">
|
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
|
||||||
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">SAVE TABLE</h4>
|
<div className="flex items-center gap-2">
|
||||||
|
<Database className="h-4 w-4 text-primary" />
|
||||||
|
<span className="text-sm font-medium">데이터를 어디에 저장하나요?</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 현재 선택된 테이블 표시 (기존 테이블 UI와 동일한 스타일) */}
|
{/* 현재 선택된 테이블 표시 (기존 테이블 UI와 동일한 스타일) */}
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
|
|
@ -1009,32 +1016,33 @@ export const V2RepeaterConfigPanel: React.FC<V2RepeaterConfigPanelProps> = ({
|
||||||
{config.useCustomTable && config.mainTableName && !currentTableName && (
|
{config.useCustomTable && config.mainTableName && !currentTableName && (
|
||||||
<div className="rounded border border-primary/20 bg-primary/10 p-2">
|
<div className="rounded border border-primary/20 bg-primary/10 p-2">
|
||||||
<p className="text-[10px] text-primary">
|
<p className="text-[10px] text-primary">
|
||||||
독립 저장 모드: 화면 테이블 없이 직접 저장합니다.
|
독립 저장 모드: 화면 테이블 없이 직접 저장해요
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 현재 화면 정보 (메인 테이블이 설정된 경우에만 표시) */}
|
||||||
|
{currentTableName && (
|
||||||
|
<div className="rounded-md border bg-background p-3">
|
||||||
|
<p className="text-xs text-muted-foreground">화면 메인 테이블</p>
|
||||||
|
<p className="mt-0.5 text-sm font-medium">{currentTableName}</p>
|
||||||
|
<p className="text-[10px] text-muted-foreground mt-0.5">
|
||||||
|
컬럼 {currentTableColumns.length}개 / 엔티티 {entityColumns.length}개
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 현재 화면 정보 (메인 테이블이 설정된 경우에만 표시) */}
|
|
||||||
{currentTableName && (
|
|
||||||
<div className="border-b border-border/50 pb-3 mb-3">
|
|
||||||
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">MAIN TABLE</h4>
|
|
||||||
<div className="rounded border border-border bg-muted p-2">
|
|
||||||
<p className="text-xs text-foreground font-medium">{currentTableName}</p>
|
|
||||||
<p className="text-[10px] text-muted-foreground">
|
|
||||||
컬럼 {currentTableColumns.length}개 / 엔티티 {entityColumns.length}개
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 모달 모드: 엔티티 컬럼 선택 */}
|
{/* 모달 모드: 엔티티 컬럼 선택 */}
|
||||||
{isModalMode && (
|
{isModalMode && (
|
||||||
<>
|
<>
|
||||||
<div className="border-b border-border/50 pb-3 mb-3">
|
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
|
||||||
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">ENTITY SELECT</h4>
|
<div className="flex items-center gap-2">
|
||||||
<p className="text-[10px] text-muted-foreground">
|
<Link2 className="h-4 w-4 text-primary" />
|
||||||
모달에서 검색할 엔티티를 선택하세요 (FK만 저장됨)
|
<span className="text-sm font-medium">어떤 엔티티를 검색하나요?</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-[11px] text-muted-foreground">
|
||||||
|
모달에서 검색할 엔티티를 선택하면 FK만 저장돼요
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{entityColumns.length > 0 ? (
|
{entityColumns.length > 0 ? (
|
||||||
|
|
@ -1060,222 +1068,253 @@ export const V2RepeaterConfigPanel: React.FC<V2RepeaterConfigPanelProps> = ({
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
) : (
|
) : (
|
||||||
<div className="rounded border border-border bg-muted p-2">
|
<div className="rounded-md border-2 border-dashed p-4 text-center">
|
||||||
<p className="text-[10px] text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{loadingColumns
|
{loadingColumns
|
||||||
? "로딩 중..."
|
? "컬럼 정보를 불러오고 있어요..."
|
||||||
: !targetTableForColumns
|
: !targetTableForColumns
|
||||||
? "저장 테이블을 먼저 선택하세요"
|
? "저장 테이블을 먼저 선택해주세요"
|
||||||
: "엔티티 타입 컬럼이 없습니다"}
|
: "엔티티 타입 컬럼이 없어요"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 선택된 엔티티 정보 */}
|
{/* 선택된 엔티티 정보 */}
|
||||||
{config.dataSource?.sourceTable && (
|
{config.dataSource?.sourceTable && (
|
||||||
<div className="rounded border border-emerald-200 bg-emerald-50 p-2 space-y-1">
|
<div className="rounded-md border bg-background p-3 space-y-1">
|
||||||
<p className="text-xs text-emerald-700 font-medium">선택된 엔티티</p>
|
<p className="text-xs text-muted-foreground">선택된 엔티티</p>
|
||||||
<div className="text-[10px] text-emerald-600">
|
<p className="text-sm font-medium">{config.dataSource.sourceTable}</p>
|
||||||
<p>검색 테이블: {config.dataSource.sourceTable}</p>
|
<p className="text-[11px] text-muted-foreground">
|
||||||
<p>저장 컬럼: {config.dataSource.foreignKey} (FK)</p>
|
{config.dataSource.foreignKey} 컬럼에 FK로 저장돼요
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 소스 디테일 자동 조회 설정 */}
|
{/* 기능 옵션 - 토스식 Switch + 설명 */}
|
||||||
<div className="border-b border-border/50 pb-3 mb-3">
|
<div className="rounded-lg border bg-muted/30 p-4 space-y-1">
|
||||||
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">SOURCE DETAIL</h4>
|
<span className="text-sm font-medium">기능 옵션</span>
|
||||||
<div className="flex items-center justify-between py-1.5">
|
<div className="space-y-2">
|
||||||
<span className="text-xs text-muted-foreground flex items-center gap-1">
|
<div className="flex items-center justify-between py-1">
|
||||||
<ListTree className="h-3 w-3" />
|
<div>
|
||||||
소스 디테일 자동 조회
|
<p className="text-sm">추가 버튼</p>
|
||||||
</span>
|
<p className="text-[11px] text-muted-foreground">새로운 행을 추가할 수 있어요</p>
|
||||||
<Checkbox
|
|
||||||
id="enableSourceDetail"
|
|
||||||
checked={!!config.sourceDetailConfig}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
if (checked) {
|
|
||||||
updateConfig({
|
|
||||||
sourceDetailConfig: {
|
|
||||||
tableName: "",
|
|
||||||
foreignKey: "",
|
|
||||||
parentKey: "",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
updateConfig({ sourceDetailConfig: undefined });
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p className="text-[10px] text-muted-foreground">
|
|
||||||
모달에서 전달받은 마스터 데이터의 디테일 행을 자동으로 조회하여 리피터에 채웁니다.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{config.sourceDetailConfig && (
|
|
||||||
<div className="space-y-2 rounded border border-violet-200 bg-violet-50 p-2">
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-[10px]">디테일 테이블</Label>
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
role="combobox"
|
|
||||||
className="h-7 w-full justify-between text-xs"
|
|
||||||
>
|
|
||||||
{config.sourceDetailConfig.tableName
|
|
||||||
? (allTables.find(t => t.tableName === config.sourceDetailConfig!.tableName)?.displayName || config.sourceDetailConfig.tableName)
|
|
||||||
: "테이블 선택..."
|
|
||||||
}
|
|
||||||
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
|
|
||||||
</Button>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent
|
|
||||||
className="p-0"
|
|
||||||
style={{ width: "var(--radix-popover-trigger-width)" }}
|
|
||||||
align="start"
|
|
||||||
>
|
|
||||||
<Command>
|
|
||||||
<CommandInput placeholder="테이블 검색..." className="text-xs" />
|
|
||||||
<CommandList className="max-h-48">
|
|
||||||
<CommandEmpty className="text-xs py-3 text-center">테이블을 찾을 수 없습니다.</CommandEmpty>
|
|
||||||
<CommandGroup>
|
|
||||||
{allTables.map((table) => (
|
|
||||||
<CommandItem
|
|
||||||
key={table.tableName}
|
|
||||||
value={`${table.tableName} ${table.displayName}`}
|
|
||||||
onSelect={() => {
|
|
||||||
updateConfig({
|
|
||||||
sourceDetailConfig: {
|
|
||||||
...config.sourceDetailConfig!,
|
|
||||||
tableName: table.tableName,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="text-xs"
|
|
||||||
>
|
|
||||||
<Check className={cn("mr-2 h-3 w-3", config.sourceDetailConfig!.tableName === table.tableName ? "opacity-100" : "opacity-0")} />
|
|
||||||
<span>{table.displayName}</span>
|
|
||||||
</CommandItem>
|
|
||||||
))}
|
|
||||||
</CommandGroup>
|
|
||||||
</CommandList>
|
|
||||||
</Command>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
</div>
|
||||||
|
<Switch
|
||||||
<div className="grid grid-cols-2 gap-2">
|
checked={config.features?.showAddButton ?? true}
|
||||||
<div className="space-y-1">
|
onCheckedChange={(checked) => updateFeatures("showAddButton", checked)}
|
||||||
<Label className="text-[10px]">디테일 FK 컬럼</Label>
|
/>
|
||||||
<Input
|
|
||||||
value={config.sourceDetailConfig.foreignKey || ""}
|
|
||||||
onChange={(e) =>
|
|
||||||
updateConfig({
|
|
||||||
sourceDetailConfig: {
|
|
||||||
...config.sourceDetailConfig!,
|
|
||||||
foreignKey: e.target.value,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
placeholder="예: order_no"
|
|
||||||
className="h-7 text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-[10px]">마스터 키 컬럼</Label>
|
|
||||||
<Input
|
|
||||||
value={config.sourceDetailConfig.parentKey || ""}
|
|
||||||
onChange={(e) =>
|
|
||||||
updateConfig({
|
|
||||||
sourceDetailConfig: {
|
|
||||||
...config.sourceDetailConfig!,
|
|
||||||
parentKey: e.target.value,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
placeholder="예: order_no"
|
|
||||||
className="h-7 text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="text-[10px] text-violet-600">
|
|
||||||
마스터에서 [{config.sourceDetailConfig.parentKey || "?"}] 추출 →
|
|
||||||
{" "}{config.sourceDetailConfig.tableName || "?"}.{config.sourceDetailConfig.foreignKey || "?"} 로 조회
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="flex items-center justify-between py-1">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm">삭제 버튼</p>
|
||||||
|
<p className="text-[11px] text-muted-foreground">선택한 행을 삭제할 수 있어요</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={config.features?.showDeleteButton ?? true}
|
||||||
|
onCheckedChange={(checked) => updateFeatures("showDeleteButton", checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between py-1">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm">인라인 편집</p>
|
||||||
|
<p className="text-[11px] text-muted-foreground">셀을 클릭하면 바로 수정할 수 있어요</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={config.features?.inlineEdit ?? false}
|
||||||
|
onCheckedChange={(checked) => updateFeatures("inlineEdit", checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between py-1">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm">다중 선택</p>
|
||||||
|
<p className="text-[11px] text-muted-foreground">여러 행을 동시에 선택할 수 있어요</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={config.features?.multiSelect ?? true}
|
||||||
|
onCheckedChange={(checked) => updateFeatures("multiSelect", checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between py-1">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm">행 번호</p>
|
||||||
|
<p className="text-[11px] text-muted-foreground">각 행에 순번을 표시해요</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={config.features?.showRowNumber ?? false}
|
||||||
|
onCheckedChange={(checked) => updateFeatures("showRowNumber", checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between py-1">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm">행 선택</p>
|
||||||
|
<p className="text-[11px] text-muted-foreground">체크박스로 행을 선택할 수 있어요</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={config.features?.selectable ?? false}
|
||||||
|
onCheckedChange={(checked) => updateFeatures("selectable", checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 기능 옵션 */}
|
{/* 고급 설정 - Collapsible (소스 디테일 등) */}
|
||||||
<div className="border-b border-border/50 pb-3 mb-3">
|
<Collapsible>
|
||||||
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">FEATURES</h4>
|
<CollapsibleTrigger asChild>
|
||||||
<div className="flex items-center justify-between py-1.5">
|
<button
|
||||||
<span className="text-xs text-muted-foreground">추가 버튼</span>
|
type="button"
|
||||||
<Checkbox
|
className="flex w-full items-center justify-between rounded-lg border bg-muted/30 px-4 py-2.5 text-left transition-colors hover:bg-muted/50"
|
||||||
id="showAddButton"
|
>
|
||||||
checked={config.features?.showAddButton ?? true}
|
<div className="flex items-center gap-2">
|
||||||
onCheckedChange={(checked) => updateFeatures("showAddButton", !!checked)}
|
<Settings className="h-4 w-4 text-muted-foreground" />
|
||||||
/>
|
<span className="text-sm font-medium">고급 설정</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between py-1.5">
|
<ChevronDown className="h-4 w-4 text-muted-foreground transition-transform duration-200" />
|
||||||
<span className="text-xs text-muted-foreground">삭제 버튼</span>
|
</button>
|
||||||
<Checkbox
|
</CollapsibleTrigger>
|
||||||
id="showDeleteButton"
|
<CollapsibleContent>
|
||||||
checked={config.features?.showDeleteButton ?? true}
|
<div className="rounded-b-lg border border-t-0 p-4 space-y-3">
|
||||||
onCheckedChange={(checked) => updateFeatures("showDeleteButton", !!checked)}
|
{/* 소스 디테일 자동 조회 */}
|
||||||
/>
|
<div className="flex items-center justify-between py-1">
|
||||||
</div>
|
<div>
|
||||||
<div className="flex items-center justify-between py-1.5">
|
<p className="text-sm">소스 디테일 자동 조회</p>
|
||||||
<span className="text-xs text-muted-foreground">인라인 편집</span>
|
<p className="text-[11px] text-muted-foreground">마스터 데이터의 디테일 행을 자동으로 채워요</p>
|
||||||
<Checkbox
|
</div>
|
||||||
id="inlineEdit"
|
<Switch
|
||||||
checked={config.features?.inlineEdit ?? false}
|
checked={!!config.sourceDetailConfig}
|
||||||
onCheckedChange={(checked) => updateFeatures("inlineEdit", !!checked)}
|
onCheckedChange={(checked) => {
|
||||||
/>
|
if (checked) {
|
||||||
</div>
|
updateConfig({
|
||||||
<div className="flex items-center justify-between py-1.5">
|
sourceDetailConfig: {
|
||||||
<span className="text-xs text-muted-foreground">다중 선택</span>
|
tableName: "",
|
||||||
<Checkbox
|
foreignKey: "",
|
||||||
id="multiSelect"
|
parentKey: "",
|
||||||
checked={config.features?.multiSelect ?? true}
|
},
|
||||||
onCheckedChange={(checked) => updateFeatures("multiSelect", !!checked)}
|
});
|
||||||
/>
|
} else {
|
||||||
</div>
|
updateConfig({ sourceDetailConfig: undefined });
|
||||||
<div className="flex items-center justify-between py-1.5">
|
}
|
||||||
<span className="text-xs text-muted-foreground">행 번호</span>
|
}}
|
||||||
<Checkbox
|
/>
|
||||||
id="showRowNumber"
|
</div>
|
||||||
checked={config.features?.showRowNumber ?? false}
|
|
||||||
onCheckedChange={(checked) => updateFeatures("showRowNumber", !!checked)}
|
{config.sourceDetailConfig && (
|
||||||
/>
|
<div className="ml-4 border-l-2 border-primary/20 pl-3 space-y-2">
|
||||||
</div>
|
<div className="space-y-1">
|
||||||
<div className="flex items-center justify-between py-1.5">
|
<p className="text-xs text-muted-foreground">디테일 테이블</p>
|
||||||
<span className="text-xs text-muted-foreground">행 선택</span>
|
<Popover>
|
||||||
<Checkbox
|
<PopoverTrigger asChild>
|
||||||
id="selectable"
|
<Button
|
||||||
checked={config.features?.selectable ?? false}
|
variant="outline"
|
||||||
onCheckedChange={(checked) => updateFeatures("selectable", !!checked)}
|
role="combobox"
|
||||||
/>
|
className="h-7 w-full justify-between text-xs"
|
||||||
</div>
|
>
|
||||||
</div>
|
{config.sourceDetailConfig.tableName
|
||||||
|
? (allTables.find(t => t.tableName === config.sourceDetailConfig!.tableName)?.displayName || config.sourceDetailConfig.tableName)
|
||||||
|
: "테이블 선택..."
|
||||||
|
}
|
||||||
|
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
className="p-0"
|
||||||
|
style={{ width: "var(--radix-popover-trigger-width)" }}
|
||||||
|
align="start"
|
||||||
|
>
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="테이블 검색..." className="text-xs" />
|
||||||
|
<CommandList className="max-h-48">
|
||||||
|
<CommandEmpty className="text-xs py-3 text-center">테이블을 찾을 수 없습니다.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{allTables.map((table) => (
|
||||||
|
<CommandItem
|
||||||
|
key={table.tableName}
|
||||||
|
value={`${table.tableName} ${table.displayName}`}
|
||||||
|
onSelect={() => {
|
||||||
|
updateConfig({
|
||||||
|
sourceDetailConfig: {
|
||||||
|
...config.sourceDetailConfig!,
|
||||||
|
tableName: table.tableName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
<Check className={cn("mr-2 h-3 w-3", config.sourceDetailConfig!.tableName === table.tableName ? "opacity-100" : "opacity-0")} />
|
||||||
|
<span>{table.displayName}</span>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="text-xs text-muted-foreground">디테일 FK 컬럼</p>
|
||||||
|
<Input
|
||||||
|
value={config.sourceDetailConfig.foreignKey || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateConfig({
|
||||||
|
sourceDetailConfig: {
|
||||||
|
...config.sourceDetailConfig!,
|
||||||
|
foreignKey: e.target.value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
placeholder="예: order_no"
|
||||||
|
className="h-7 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="text-xs text-muted-foreground">마스터 키 컬럼</p>
|
||||||
|
<Input
|
||||||
|
value={config.sourceDetailConfig.parentKey || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateConfig({
|
||||||
|
sourceDetailConfig: {
|
||||||
|
...config.sourceDetailConfig!,
|
||||||
|
parentKey: e.target.value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
placeholder="예: order_no"
|
||||||
|
className="h-7 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-[11px] text-muted-foreground">
|
||||||
|
마스터에서 [{config.sourceDetailConfig.parentKey || "?"}] 추출 →
|
||||||
|
{" "}{config.sourceDetailConfig.tableName || "?"}.{config.sourceDetailConfig.foreignKey || "?"} 로 조회해요
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</Collapsible>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* 컬럼 설정 탭 */}
|
{/* 컬럼 설정 탭 */}
|
||||||
<TabsContent value="columns" className="mt-4 space-y-1">
|
<TabsContent value="columns" className="mt-4 space-y-4">
|
||||||
{/* 통합 컬럼 선택 */}
|
{/* 통합 컬럼 선택 */}
|
||||||
<div className="border-b border-border/50 pb-3 mb-3">
|
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
|
||||||
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">COLUMN SELECT</h4>
|
<div className="flex items-center gap-2">
|
||||||
<p className="text-[10px] text-muted-foreground">
|
<Database className="h-4 w-4 text-primary" />
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{isModalMode ? "어떤 컬럼을 표시하고 입력받을까요?" : "어떤 컬럼을 입력받을까요?"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-[11px] text-muted-foreground">
|
||||||
{isModalMode
|
{isModalMode
|
||||||
? "표시할 컬럼과 입력 컬럼을 선택하세요. 아이콘으로 표시/입력 구분"
|
? "소스 테이블 컬럼은 표시용, 저장 테이블 컬럼은 입력용이에요"
|
||||||
: "입력받을 컬럼을 선택하세요"
|
: "체크한 컬럼이 리피터에 입력 필드로 표시돼요"
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* 모달 모드: 소스 테이블 컬럼 (표시용) */}
|
{/* 모달 모드: 소스 테이블 컬럼 (표시용) */}
|
||||||
{isModalMode && config.dataSource?.sourceTable && (
|
{isModalMode && config.dataSource?.sourceTable && (
|
||||||
|
|
@ -1353,11 +1392,11 @@ export const V2RepeaterConfigPanel: React.FC<V2RepeaterConfigPanelProps> = ({
|
||||||
{/* 선택된 컬럼 상세 설정 */}
|
{/* 선택된 컬럼 상세 설정 */}
|
||||||
{config.columns.length > 0 && (
|
{config.columns.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="border-b border-border/50 pb-3 mb-3">
|
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
|
||||||
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">
|
<div className="flex items-center justify-between">
|
||||||
SELECTED COLUMNS ({config.columns.length})
|
<span className="text-sm font-medium">선택된 컬럼 ({config.columns.length})</span>
|
||||||
<span className="ml-2 font-normal normal-case tracking-normal">드래그로 순서 변경</span>
|
<span className="text-[11px] text-muted-foreground">드래그로 순서 변경</span>
|
||||||
</h4>
|
</div>
|
||||||
<div className="max-h-48 space-y-1 overflow-y-auto">
|
<div className="max-h-48 space-y-1 overflow-y-auto">
|
||||||
{config.columns.map((col, index) => (
|
{config.columns.map((col, index) => (
|
||||||
<div key={col.key} className="space-y-1">
|
<div key={col.key} className="space-y-1">
|
||||||
|
|
@ -1684,11 +1723,14 @@ export const V2RepeaterConfigPanel: React.FC<V2RepeaterConfigPanelProps> = ({
|
||||||
{/* 계산 규칙 */}
|
{/* 계산 규칙 */}
|
||||||
{(isModalMode || isInlineMode) && config.columns.length > 0 && (
|
{(isModalMode || isInlineMode) && config.columns.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="border-b border-border/50 pb-3 mb-3">
|
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">CALCULATION RULES</h4>
|
<div className="flex items-center gap-2">
|
||||||
<Button type="button" variant="outline" size="sm" onClick={addCalculationRule} className="h-6 text-xs">
|
<Calculator className="h-4 w-4 text-primary" />
|
||||||
<Calculator className="mr-1 h-3 w-3" />
|
<span className="text-sm font-medium">계산 규칙</span>
|
||||||
|
</div>
|
||||||
|
<Button type="button" variant="outline" size="sm" onClick={addCalculationRule} className="h-7 px-2 text-xs">
|
||||||
|
<Plus className="mr-1 h-3 w-3" />
|
||||||
추가
|
추가
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1785,9 +1827,11 @@ export const V2RepeaterConfigPanel: React.FC<V2RepeaterConfigPanelProps> = ({
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{calculationRules.length === 0 && (
|
{calculationRules.length === 0 && (
|
||||||
<p className="text-muted-foreground py-1 text-center text-[10px]">
|
<div className="text-center py-4 text-muted-foreground">
|
||||||
계산 규칙이 없습니다
|
<Calculator className="mx-auto mb-2 h-6 w-6 opacity-30" />
|
||||||
</p>
|
<p className="text-xs">아직 계산 규칙이 없어요</p>
|
||||||
|
<p className="text-[10px] mt-0.5">위의 추가 버튼으로 수식을 만들어보세요</p>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1796,22 +1840,34 @@ export const V2RepeaterConfigPanel: React.FC<V2RepeaterConfigPanelProps> = ({
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* Entity 조인 설정 탭 */}
|
{/* Entity 조인 설정 탭 */}
|
||||||
<TabsContent value="entityJoin" className="mt-4 space-y-1">
|
<TabsContent value="entityJoin" className="mt-4 space-y-4">
|
||||||
<div className="border-b border-border/50 pb-3 mb-3">
|
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
|
||||||
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">ENTITY JOIN</h4>
|
<div className="flex items-center gap-2">
|
||||||
<p className="text-muted-foreground text-[10px]">
|
<Link2 className="h-4 w-4 text-primary" />
|
||||||
FK 컬럼을 기반으로 참조 테이블의 데이터를 자동으로 조회하여 표시합니다
|
<span className="text-sm font-medium">연결된 테이블 데이터 표시</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-[11px] text-muted-foreground">
|
||||||
|
FK 컬럼을 기반으로 참조 테이블의 데이터를 자동으로 가져와서 표시해요
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{loadingEntityJoins ? (
|
{loadingEntityJoins ? (
|
||||||
<p className="text-muted-foreground py-2 text-center text-xs">로딩 중...</p>
|
<div className="text-muted-foreground flex items-center gap-2 text-xs">
|
||||||
|
<div className="h-3 w-3 animate-spin rounded-full border-2 border-primary border-t-transparent" />
|
||||||
|
조인 가능한 컬럼을 찾고 있어요...
|
||||||
|
</div>
|
||||||
) : entityJoinData.joinTables.length === 0 ? (
|
) : entityJoinData.joinTables.length === 0 ? (
|
||||||
<div className="rounded-md border border-dashed p-4 text-center">
|
<div className="rounded-md border-2 border-dashed p-4 text-center">
|
||||||
<p className="text-muted-foreground text-xs">
|
<Link2 className="mx-auto mb-2 h-8 w-8 opacity-30 text-muted-foreground" />
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
{entityJoinTargetTable
|
{entityJoinTargetTable
|
||||||
? `${entityJoinTargetTable} 테이블에 Entity 조인 가능한 컬럼이 없습니다`
|
? "조인 가능한 컬럼이 없어요"
|
||||||
: "저장 테이블을 먼저 설정해주세요"}
|
: "저장 테이블을 먼저 설정해주세요"}
|
||||||
</p>
|
</p>
|
||||||
|
{entityJoinTargetTable && (
|
||||||
|
<p className="text-xs text-muted-foreground mt-0.5">
|
||||||
|
테이블 타입 관리에서 엔티티 관계를 설정해보세요
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
|
|
@ -1873,8 +1929,8 @@ export const V2RepeaterConfigPanel: React.FC<V2RepeaterConfigPanelProps> = ({
|
||||||
|
|
||||||
{/* 현재 설정된 Entity 조인 목록 */}
|
{/* 현재 설정된 Entity 조인 목록 */}
|
||||||
{config.entityJoins && config.entityJoins.length > 0 && (
|
{config.entityJoins && config.entityJoins.length > 0 && (
|
||||||
<div className="space-y-1">
|
<div className="space-y-2 border-t pt-3">
|
||||||
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">CONFIGURED JOINS</h4>
|
<span className="text-xs font-medium">설정된 조인 ({config.entityJoins.length})</span>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
{config.entityJoins.map((join, idx) => (
|
{config.entityJoins.map((join, idx) => (
|
||||||
<div key={idx} className="flex items-center gap-1 rounded border bg-muted/30 px-2 py-1 text-[10px]">
|
<div key={idx} className="flex items-center gap-1 rounded border bg-muted/30 px-2 py-1 text-[10px]">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue