[agent-pipeline] pipe-20260311130333-zqic round-3

This commit is contained in:
DDD1542 2026-03-11 22:26:52 +09:00
parent d2c8f5f8f5
commit 6f311148a5
1 changed files with 353 additions and 341 deletions

View File

@ -3,13 +3,12 @@
/**
* BOM
*
* V2BomItemEditorConfigPanel :
* - : 디테일 + +
* UX:
* - : 디테일 (/)
* - : 소스 + +
*/
import React, { useState, useEffect, useMemo, useCallback } from "react";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
@ -17,11 +16,12 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Switch } from "@/components/ui/switch";
import { Checkbox } from "@/components/ui/checkbox";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import {
Database,
Link2,
@ -35,6 +35,7 @@ import {
Check,
ChevronsUpDown,
GitBranch,
Settings,
} from "lucide-react";
import {
Command,
@ -399,7 +400,6 @@ export function V2BomTreeConfigPanel({
});
};
// FK/시스템 컬럼 제외한 표시 가능 컬럼
const displayableColumns = useMemo(() => {
const fkColumn = config.dataSource?.foreignKey;
const systemCols = ["id", "created_at", "updated_at", "created_by", "updated_by", "company_code", "created_date"];
@ -408,7 +408,6 @@ export function V2BomTreeConfigPanel({
);
}, [detailTableColumns, config.dataSource?.foreignKey]);
// FK 후보 컬럼
const fkCandidateColumns = useMemo(() => {
const systemCols = ["created_at", "updated_at", "created_by", "updated_by", "company_code", "created_date"];
return detailTableColumns.filter((c) => !systemCols.includes(c.columnName));
@ -429,8 +428,11 @@ export function V2BomTreeConfigPanel({
{/* ─── 기본 설정 탭 ─── */}
<TabsContent value="basic" className="mt-4 space-y-4">
{/* 디테일 테이블 */}
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
<div className="flex items-center gap-2">
<Database className="h-4 w-4 text-primary" />
<span className="text-sm font-medium">BOM ?</span>
</div>
<div
className={cn(
@ -479,7 +481,7 @@ export function V2BomTreeConfigPanel({
<CommandInput placeholder="테이블 검색..." className="text-xs" />
<CommandList className="max-h-60">
<CommandEmpty className="py-3 text-center text-xs">
.
.
</CommandEmpty>
{relatedTables.length > 0 && (
@ -538,29 +540,38 @@ export function V2BomTreeConfigPanel({
</Command>
</PopoverContent>
</Popover>
{/* 화면 메인 테이블 참고 */}
{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">
{detailTableColumns.length} / {entityColumns.length}
</p>
</div>
)}
</div>
<Separator />
{/* 트리 구조 설정 */}
<div className="space-y-2">
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
<div className="flex items-center gap-2">
<GitBranch className="h-4 w-4" />
<Label className="text-xs font-medium"> </Label>
<GitBranch className="h-4 w-4 text-primary" />
<span className="text-sm font-medium"> ?</span>
</div>
<p className="text-muted-foreground text-[10px]">
FK와 - FK를
<p className="text-[11px] text-muted-foreground">
FK와 - FK를
</p>
{fkCandidateColumns.length > 0 ? (
<div className="space-y-2">
<div className="space-y-1">
<Label className="text-[10px]">FK ( )</Label>
<div className="space-y-3">
<div>
<p className="mb-1.5 text-xs text-muted-foreground">FK ( )</p>
<Select
value={config.foreignKey || ""}
onValueChange={(value) => updateConfig({ foreignKey: value })}
>
<SelectTrigger className="h-8 text-xs">
<SelectTrigger className="h-8 text-sm">
<SelectValue placeholder="FK 컬럼 선택" />
</SelectTrigger>
<SelectContent>
@ -577,13 +588,13 @@ export function V2BomTreeConfigPanel({
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label className="text-[10px]"> ( FK)</Label>
<div>
<p className="mb-1.5 text-xs text-muted-foreground"> ( FK)</p>
<Select
value={config.parentKey || ""}
onValueChange={(value) => updateConfig({ parentKey: value })}
>
<SelectTrigger className="h-8 text-xs">
<SelectTrigger className="h-8 text-sm">
<SelectValue placeholder="부모 키 컬럼 선택" />
</SelectTrigger>
<SelectContent>
@ -602,21 +613,23 @@ export function V2BomTreeConfigPanel({
</div>
</div>
) : (
<div className="rounded border border-border bg-muted p-2">
<p className="text-[10px] text-muted-foreground">
{loadingColumns ? "로딩 중..." : "디테일 테이블을 먼저 선택하세요"}
<div className="rounded-md border-2 border-dashed p-4 text-center">
<GitBranch className="mx-auto mb-2 h-8 w-8 opacity-30 text-muted-foreground" />
<p className="text-sm text-muted-foreground">
{loadingColumns ? "컬럼 정보를 불러오고 있어요..." : "디테일 테이블을 먼저 선택해주세요"}
</p>
</div>
)}
</div>
<Separator />
{/* 엔티티 선택 (품목 참조) */}
<div className="space-y-2">
<Label className="text-xs font-medium"> ( )</Label>
<p className="text-muted-foreground text-[10px]">
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
<div className="flex items-center gap-2">
<Link2 className="h-4 w-4 text-primary" />
<span className="text-sm font-medium"> ?</span>
</div>
<p className="text-[11px] text-muted-foreground">
</p>
{entityColumns.length > 0 ? (
@ -625,7 +638,7 @@ export function V2BomTreeConfigPanel({
onValueChange={handleEntityColumnSelect}
disabled={!config.detailTable}
>
<SelectTrigger className="h-8 text-xs">
<SelectTrigger className="h-8 text-sm">
<SelectValue placeholder="엔티티 컬럼 선택" />
</SelectTrigger>
<SelectContent>
@ -642,234 +655,236 @@ export function V2BomTreeConfigPanel({
</SelectContent>
</Select>
) : (
<div className="rounded border border-border bg-muted p-2">
<p className="text-[10px] text-muted-foreground">
<div className="rounded-md border-2 border-dashed p-4 text-center">
<p className="text-sm text-muted-foreground">
{loadingColumns
? "로딩 중..."
? "컬럼 정보를 불러오고 있어요..."
: !config.detailTable
? "디테일 테이블을 먼저 선택세요"
: "엔티티 타입 컬럼이 없습니다"}
? "디테일 테이블을 먼저 선택해주세요"
: "엔티티 타입 컬럼이 없어요"}
</p>
</div>
)}
{config.dataSource?.sourceTable && (
<div className="space-y-1 rounded border border-emerald-200 bg-emerald-50 p-2">
<p className="text-xs font-medium text-emerald-700"> </p>
<div className="text-[10px] text-emerald-600">
<p> : {config.dataSource.sourceTable}</p>
<p>FK : {config.dataSource.foreignKey}</p>
</div>
<div className="rounded-md border bg-background p-3 space-y-1">
<p className="text-xs text-muted-foreground"> </p>
<p className="text-sm font-medium">{config.dataSource.sourceTable}</p>
<p className="text-[11px] text-muted-foreground">
{config.dataSource.foreignKey}
</p>
</div>
)}
</div>
<Separator />
{/* 이력/버전 테이블 설정 */}
<div className="space-y-2">
<Label className="text-xs font-medium">/ </Label>
<p className="text-muted-foreground text-[10px]">
BOM
</p>
{/* 표시 옵션 - Switch + 설명 텍스트 */}
<div className="rounded-lg border bg-muted/30 p-4 space-y-1">
<span className="text-sm font-medium"> </span>
<div className="space-y-2">
<div className="space-y-1">
<div className="flex items-center gap-2">
<Checkbox
id="tree-showHistory"
checked={config.features?.showHistory ?? true}
onCheckedChange={(checked) => updateFeatures("showHistory", !!checked)}
/>
<Label htmlFor="tree-showHistory" className="text-[10px]"> </Label>
<div className="flex items-center justify-between py-1">
<div>
<p className="text-sm"> /</p>
<p className="text-[11px] text-muted-foreground"> </p>
</div>
{(config.features?.showHistory ?? true) && (
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
disabled={loadingTables}
className="h-8 w-full justify-between text-xs"
>
{config.historyTable
? allTables.find((t) => t.tableName === config.historyTable)?.displayName || config.historyTable
: "이력 테이블 선택..."}
<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="py-3 text-center text-xs">
.
</CommandEmpty>
<CommandGroup>
{allTables.map((table) => (
<CommandItem
key={table.tableName}
value={`${table.tableName} ${table.displayName}`}
onSelect={() => updateConfig({ historyTable: table.tableName })}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-3 w-3",
config.historyTable === table.tableName ? "opacity-100" : "opacity-0",
)}
/>
<Database className="mr-2 h-3 w-3 text-muted-foreground/70" />
<span>{table.displayName}</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)}
</div>
<div className="space-y-1">
<div className="flex items-center gap-2">
<Checkbox
id="tree-showVersion"
checked={config.features?.showVersion ?? true}
onCheckedChange={(checked) => updateFeatures("showVersion", !!checked)}
/>
<Label htmlFor="tree-showVersion" className="text-[10px]"> </Label>
</div>
{(config.features?.showVersion ?? true) && (
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
disabled={loadingTables}
className="h-8 w-full justify-between text-xs"
>
{config.versionTable
? allTables.find((t) => t.tableName === config.versionTable)?.displayName || config.versionTable
: "버전 테이블 선택..."}
<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="py-3 text-center text-xs">
.
</CommandEmpty>
<CommandGroup>
{allTables.map((table) => (
<CommandItem
key={table.tableName}
value={`${table.tableName} ${table.displayName}`}
onSelect={() => updateConfig({ versionTable: table.tableName })}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-3 w-3",
config.versionTable === table.tableName ? "opacity-100" : "opacity-0",
)}
/>
<Database className="mr-2 h-3 w-3 text-muted-foreground/70" />
<span>{table.displayName}</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
)}
</div>
</div>
</div>
<Separator />
{/* 표시 옵션 */}
<div className="space-y-3">
<Label className="text-xs font-medium"> </Label>
<div className="grid grid-cols-2 gap-2">
<div className="flex items-center space-x-2">
<Checkbox
id="tree-showExpandAll"
<Switch
checked={config.features?.showExpandAll ?? true}
onCheckedChange={(checked) => updateFeatures("showExpandAll", !!checked)}
onCheckedChange={(checked) => updateFeatures("showExpandAll", checked)}
/>
<label htmlFor="tree-showExpandAll" className="text-xs">
/
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="tree-showHeader"
<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?.showHeader ?? true}
onCheckedChange={(checked) => updateFeatures("showHeader", !!checked)}
onCheckedChange={(checked) => updateFeatures("showHeader", checked)}
/>
<label htmlFor="tree-showHeader" className="text-xs">
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="tree-showQuantity"
<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?.showQuantity ?? true}
onCheckedChange={(checked) => updateFeatures("showQuantity", !!checked)}
onCheckedChange={(checked) => updateFeatures("showQuantity", checked)}
/>
<label htmlFor="tree-showQuantity" className="text-xs">
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="tree-showLossRate"
<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?.showLossRate ?? true}
onCheckedChange={(checked) => updateFeatures("showLossRate", !!checked)}
onCheckedChange={(checked) => updateFeatures("showLossRate", checked)}
/>
<label htmlFor="tree-showLossRate" className="text-xs">
</label>
</div>
</div>
</div>
{/* 메인 화면 테이블 참고 */}
{currentTableName && (
<>
<Separator />
<div className="space-y-2">
<Label className="text-xs font-medium"> ()</Label>
<div className="rounded border border-border bg-muted p-2">
<p className="text-xs font-medium text-foreground">{currentTableName}</p>
<p className="text-[10px] text-muted-foreground">
{detailTableColumns.length} / {entityColumns.length}
</p>
{/* 고급 설정 (이력/버전 관리) - Collapsible */}
<Collapsible>
<CollapsibleTrigger asChild>
<button className="flex w-full items-center justify-between rounded-lg border bg-muted/30 px-4 py-2.5 text-left hover:bg-muted/50 transition-colors">
<div className="flex items-center gap-2">
<Settings className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium"> </span>
</div>
<ChevronDown className="h-4 w-4 text-muted-foreground" />
</button>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="rounded-b-lg border border-t-0 p-4 space-y-4">
{/* 이력 관리 */}
<div className="space-y-2">
<div className="flex items-center justify-between py-1">
<div>
<p className="text-sm"> </p>
<p className="text-[11px] text-muted-foreground">BOM </p>
</div>
<Switch
checked={config.features?.showHistory ?? true}
onCheckedChange={(checked) => updateFeatures("showHistory", checked)}
/>
</div>
{(config.features?.showHistory ?? true) && (
<div className="ml-4 border-l-2 border-primary/20 pl-3">
<p className="mb-1.5 text-xs text-muted-foreground"> </p>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
disabled={loadingTables}
className="h-8 w-full justify-between text-xs"
>
{config.historyTable
? allTables.find((t) => t.tableName === config.historyTable)?.displayName || config.historyTable
: "이력 테이블 선택..."}
<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="py-3 text-center text-xs">
.
</CommandEmpty>
<CommandGroup>
{allTables.map((table) => (
<CommandItem
key={table.tableName}
value={`${table.tableName} ${table.displayName}`}
onSelect={() => updateConfig({ historyTable: table.tableName })}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-3 w-3",
config.historyTable === table.tableName ? "opacity-100" : "opacity-0",
)}
/>
<Database className="mr-2 h-3 w-3 text-muted-foreground/70" />
<span>{table.displayName}</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
)}
</div>
{/* 버전 관리 */}
<div className="space-y-2">
<div className="flex items-center justify-between py-1">
<div>
<p className="text-sm"> </p>
<p className="text-[11px] text-muted-foreground">BOM </p>
</div>
<Switch
checked={config.features?.showVersion ?? true}
onCheckedChange={(checked) => updateFeatures("showVersion", checked)}
/>
</div>
{(config.features?.showVersion ?? true) && (
<div className="ml-4 border-l-2 border-primary/20 pl-3">
<p className="mb-1.5 text-xs text-muted-foreground"> </p>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
disabled={loadingTables}
className="h-8 w-full justify-between text-xs"
>
{config.versionTable
? allTables.find((t) => t.tableName === config.versionTable)?.displayName || config.versionTable
: "버전 테이블 선택..."}
<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="py-3 text-center text-xs">
.
</CommandEmpty>
<CommandGroup>
{allTables.map((table) => (
<CommandItem
key={table.tableName}
value={`${table.tableName} ${table.displayName}`}
onSelect={() => updateConfig({ versionTable: table.tableName })}
className="text-xs"
>
<Check
className={cn(
"mr-2 h-3 w-3",
config.versionTable === table.tableName ? "opacity-100" : "opacity-0",
)}
/>
<Database className="mr-2 h-3 w-3 text-muted-foreground/70" />
<span>{table.displayName}</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
)}
</div>
</div>
</>
)}
</CollapsibleContent>
</Collapsible>
</TabsContent>
{/* ─── 컬럼 설정 탭 ─── */}
<TabsContent value="columns" className="mt-4 space-y-4">
<div className="space-y-2">
<Label className="text-xs font-medium"> </Label>
<p className="text-muted-foreground text-[10px]">
/
{/* 통합 컬럼 선택 */}
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
<div className="flex items-center gap-2">
<Database className="h-4 w-4 text-primary" />
<span className="text-sm font-medium"> ?</span>
</div>
<p className="text-[11px] text-muted-foreground">
,
</p>
{/* 소스 테이블 컬럼 (표시용) */}
@ -882,7 +897,7 @@ export function V2BomTreeConfigPanel({
{loadingSourceColumns ? (
<p className="text-muted-foreground py-2 text-xs"> ...</p>
) : sourceTableColumns.length === 0 ? (
<p className="text-muted-foreground py-2 text-xs"> </p>
<p className="text-muted-foreground py-2 text-xs"> </p>
) : (
<div className="max-h-28 space-y-0.5 overflow-y-auto rounded-md border border-primary/20 bg-primary/10/30 p-2">
{sourceTableColumns.map((column) => (
@ -917,7 +932,7 @@ export function V2BomTreeConfigPanel({
{loadingColumns ? (
<p className="text-muted-foreground py-2 text-xs"> ...</p>
) : displayableColumns.length === 0 ? (
<p className="text-muted-foreground py-2 text-xs"> </p>
<p className="text-muted-foreground py-2 text-xs"> </p>
) : (
<div className="max-h-36 space-y-0.5 overflow-y-auto rounded-md border p-2">
{displayableColumns.map((column) => (
@ -945,123 +960,120 @@ export function V2BomTreeConfigPanel({
{/* 선택된 컬럼 상세 */}
{config.columns.length > 0 && (
<>
<Separator />
<div className="space-y-2">
<Label className="text-xs font-medium">
({config.columns.length})
<span className="text-muted-foreground ml-2 font-normal"> </span>
</Label>
<div className="max-h-48 space-y-1 overflow-y-auto">
{config.columns.map((col, index) => (
<div key={col.key} className="space-y-1">
<div
className={cn(
"flex items-center gap-2 rounded-md border p-2",
col.isSourceDisplay
? "border-primary/20 bg-primary/10/50"
: "border-border bg-muted/30",
col.hidden && "opacity-50",
)}
draggable
onDragStart={(e) => e.dataTransfer.setData("columnIndex", String(index))}
onDragOver={(e) => e.preventDefault()}
onDrop={(e) => {
e.preventDefault();
const fromIndex = parseInt(e.dataTransfer.getData("columnIndex"), 10);
if (fromIndex !== index) {
const newColumns = [...config.columns];
const [movedCol] = newColumns.splice(fromIndex, 1);
newColumns.splice(index, 0, movedCol);
updateConfig({ columns: newColumns });
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm font-medium"> ({config.columns.length})</span>
<span className="text-[11px] text-muted-foreground"> </span>
</div>
<div className="max-h-48 space-y-1 overflow-y-auto">
{config.columns.map((col, index) => (
<div key={col.key} className="space-y-1">
<div
className={cn(
"flex items-center gap-2 rounded-md border p-2",
col.isSourceDisplay
? "border-primary/20 bg-primary/10/50"
: "border-border bg-muted/30",
col.hidden && "opacity-50",
)}
draggable
onDragStart={(e) => e.dataTransfer.setData("columnIndex", String(index))}
onDragOver={(e) => e.preventDefault()}
onDrop={(e) => {
e.preventDefault();
const fromIndex = parseInt(e.dataTransfer.getData("columnIndex"), 10);
if (fromIndex !== index) {
const newColumns = [...config.columns];
const [movedCol] = newColumns.splice(fromIndex, 1);
newColumns.splice(index, 0, movedCol);
updateConfig({ columns: newColumns });
}
}}
>
<GripVertical className="text-muted-foreground h-3 w-3 cursor-grab flex-shrink-0" />
{!col.isSourceDisplay && (
<button
type="button"
onClick={() =>
setExpandedColumn(expandedColumn === col.key ? null : col.key)
}
className="rounded p-0.5 hover:bg-muted/80"
>
{expandedColumn === col.key ? (
<ChevronDown className="h-3 w-3 text-muted-foreground" />
) : (
<ChevronRight className="h-3 w-3 text-muted-foreground" />
)}
</button>
)}
{col.isSourceDisplay ? (
<Link2 className="h-3 w-3 flex-shrink-0 text-primary" />
) : (
<Database className="text-muted-foreground h-3 w-3 flex-shrink-0" />
)}
<Input
value={col.title}
onChange={(e) => updateColumnProp(col.key, "title", e.target.value)}
placeholder="제목"
className="h-6 flex-1 text-xs"
/>
{!col.isSourceDisplay && (
<button
type="button"
onClick={() => updateColumnProp(col.key, "hidden", !col.hidden)}
className={cn(
"rounded p-1 hover:bg-muted/80",
col.hidden ? "text-muted-foreground/70" : "text-muted-foreground",
)}
title={col.hidden ? "히든" : "표시됨"}
>
{col.hidden ? (
<EyeOff className="h-3 w-3" />
) : (
<Eye className="h-3 w-3" />
)}
</button>
)}
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => {
if (col.isSourceDisplay) {
toggleSourceDisplayColumn({ columnName: col.key, displayName: col.title });
} else {
toggleDetailColumn({ columnName: col.key, displayName: col.title });
}
}}
className="text-destructive h-6 w-6 p-0"
>
<GripVertical className="text-muted-foreground h-3 w-3 cursor-grab flex-shrink-0" />
{!col.isSourceDisplay && (
<button
type="button"
onClick={() =>
setExpandedColumn(expandedColumn === col.key ? null : col.key)
}
className="rounded p-0.5 hover:bg-muted/80"
>
{expandedColumn === col.key ? (
<ChevronDown className="h-3 w-3 text-muted-foreground" />
) : (
<ChevronRight className="h-3 w-3 text-muted-foreground" />
)}
</button>
)}
{col.isSourceDisplay ? (
<Link2 className="h-3 w-3 flex-shrink-0 text-primary" title="소스 표시" />
) : (
<Database className="text-muted-foreground h-3 w-3 flex-shrink-0" />
)}
<Input
value={col.title}
onChange={(e) => updateColumnProp(col.key, "title", e.target.value)}
placeholder="제목"
className="h-6 flex-1 text-xs"
/>
{!col.isSourceDisplay && (
<button
type="button"
onClick={() => updateColumnProp(col.key, "hidden", !col.hidden)}
className={cn(
"rounded p-1 hover:bg-muted/80",
col.hidden ? "text-muted-foreground/70" : "text-muted-foreground",
)}
title={col.hidden ? "히든" : "표시됨"}
>
{col.hidden ? (
<EyeOff className="h-3 w-3" />
) : (
<Eye className="h-3 w-3" />
)}
</button>
)}
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => {
if (col.isSourceDisplay) {
toggleSourceDisplayColumn({ columnName: col.key, displayName: col.title });
} else {
toggleDetailColumn({ columnName: col.key, displayName: col.title });
}
}}
className="text-destructive h-6 w-6 p-0"
>
<Trash2 className="h-3 w-3" />
</Button>
</div>
{/* 확장 상세 */}
{!col.isSourceDisplay && expandedColumn === col.key && (
<div className="ml-6 space-y-2 rounded-md border border-dashed border-input bg-muted p-2">
<div className="space-y-1">
<Label className="text-[10px] text-muted-foreground"> </Label>
<Input
value={col.width || "auto"}
onChange={(e) => updateColumnProp(col.key, "width", e.target.value)}
placeholder="auto, 100px, 20%"
className="h-6 text-xs"
/>
</div>
</div>
)}
<Trash2 className="h-3 w-3" />
</Button>
</div>
))}
</div>
{/* 확장 상세 */}
{!col.isSourceDisplay && expandedColumn === col.key && (
<div className="ml-6 space-y-2 rounded-md border border-dashed border-input bg-muted p-2">
<div className="space-y-1">
<p className="text-[10px] text-muted-foreground"> </p>
<Input
value={col.width || "auto"}
onChange={(e) => updateColumnProp(col.key, "width", e.target.value)}
placeholder="auto, 100px, 20%"
className="h-6 text-xs"
/>
</div>
</div>
)}
</div>
))}
</div>
</>
</div>
)}
</TabsContent>
</Tabs>