feat: enhance V2 process work standard configuration panel
- Introduced a new TableCombobox component for selecting tables, improving user experience by allowing table searches and selections. - Added a ColumnCombobox component to facilitate column selection based on the chosen table, enhancing the configurability of the process work standard settings. - Updated the V2ProcessWorkStandardConfigPanel to utilize the new combobox components, streamlining the configuration process for item tables and columns. - Removed the deprecated mcp.json file and updated .gitignore to reflect recent changes. These enhancements aim to improve the usability and flexibility of the configuration panel, making it easier for users to manage their process work standards.
This commit is contained in:
parent
ae4fe7a66e
commit
5e6261f51a
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"mcpServers": {
|
||||
"Framelink Figma MCP": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "figma-developer-mcp", "--figma-api-key=figd_NrYdIWf-CnC23NyH6eMym7sBdfbZTuXyS91tI3VS", "--stdio"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -185,6 +185,9 @@ popdocs/
|
|||
|
||||
# 멀티 에이전트 MCP 태스크 큐
|
||||
mcp-task-queue/
|
||||
.cursor/mcp.json
|
||||
.cursor/rules/multi-agent-pm.mdc
|
||||
.cursor/rules/multi-agent-worker.mdc
|
||||
.cursor/rules/multi-agent-tester.mdc
|
||||
.cursor/rules/multi-agent-reviewer.mdc
|
||||
.cursor/rules/multi-agent-knowledge.mdc
|
||||
|
|
|
|||
|
|
@ -1,17 +1,33 @@
|
|||
"use client";
|
||||
|
||||
/**
|
||||
* V2 공정 작업기준 설정 패널
|
||||
* Progressive Disclosure: 작업 단계 -> 상세 유형 -> 고급 설정(접힘)
|
||||
* V2 공정 작업기준 설정 패널 (간소화)
|
||||
*/
|
||||
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Settings, ChevronDown, ChevronRight, Plus, Trash2, Database, Layers, List } from "lucide-react";
|
||||
import {
|
||||
Settings,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Plus,
|
||||
Trash2,
|
||||
Check,
|
||||
ChevronsUpDown,
|
||||
Database,
|
||||
Layers,
|
||||
List,
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type {
|
||||
ProcessWorkStandardConfig,
|
||||
|
|
@ -20,26 +36,87 @@ import type {
|
|||
} from "@/lib/registry/components/v2-process-work-standard/types";
|
||||
import { defaultConfig } from "@/lib/registry/components/v2-process-work-standard/config";
|
||||
|
||||
interface TableInfo { tableName: string; displayName?: string; }
|
||||
|
||||
function TableCombobox({ value, onChange, tables, loading, label }: {
|
||||
value: string; onChange: (v: string) => void; tables: TableInfo[]; loading: boolean; label: string;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const selected = tables.find((t) => t.tableName === value);
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<span className="text-muted-foreground shrink-0 text-xs">{label}</span>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" role="combobox" aria-expanded={open} className="h-7 w-[200px] justify-between text-xs" disabled={loading}>
|
||||
<span className="truncate">{loading ? "로딩..." : selected ? selected.displayName || selected.tableName : "테이블 선택"}</span>
|
||||
<ChevronsUpDown className="ml-1 h-3 w-3 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[240px] p-0" align="end">
|
||||
<Command>
|
||||
<CommandInput placeholder="테이블 검색..." className="text-xs" />
|
||||
<CommandList>
|
||||
<CommandEmpty className="py-4 text-center text-xs">테이블을 찾을 수 없습니다.</CommandEmpty>
|
||||
<CommandGroup className="max-h-[200px] overflow-auto">
|
||||
{tables.map((t) => (
|
||||
<CommandItem key={t.tableName} value={`${t.displayName || ""} ${t.tableName}`}
|
||||
onSelect={() => { onChange(t.tableName); setOpen(false); }} className="text-xs">
|
||||
<Check className={cn("mr-2 h-3 w-3", value === t.tableName ? "opacity-100" : "opacity-0")} />
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{t.displayName || t.tableName}</span>
|
||||
{t.displayName && <span className="text-muted-foreground text-[10px]">{t.tableName}</span>}
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface V2ProcessWorkStandardConfigPanelProps {
|
||||
config: Partial<ProcessWorkStandardConfig>;
|
||||
onChange: (config: Partial<ProcessWorkStandardConfig>) => void;
|
||||
}
|
||||
|
||||
export const V2ProcessWorkStandardConfigPanel: React.FC<V2ProcessWorkStandardConfigPanelProps> = ({
|
||||
config: configProp,
|
||||
onChange,
|
||||
}) => {
|
||||
export const V2ProcessWorkStandardConfigPanel: React.FC<
|
||||
V2ProcessWorkStandardConfigPanelProps
|
||||
> = ({ config: configProp, onChange }) => {
|
||||
const [phasesOpen, setPhasesOpen] = useState(false);
|
||||
const [detailTypesOpen, setDetailTypesOpen] = useState(false);
|
||||
const [advancedOpen, setAdvancedOpen] = useState(false);
|
||||
const [layoutOpen, setLayoutOpen] = useState(false);
|
||||
const [dataSourceOpen, setDataSourceOpen] = useState(false);
|
||||
const [tables, setTables] = useState<TableInfo[]>([]);
|
||||
const [loadingTables, setLoadingTables] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const loadTables = async () => {
|
||||
setLoadingTables(true);
|
||||
try {
|
||||
const { tableManagementApi } = await import("@/lib/api/tableManagement");
|
||||
const res = await tableManagementApi.getTableList();
|
||||
if (res.success && res.data) {
|
||||
setTables(res.data.map((t: any) => ({ tableName: t.tableName, displayName: t.displayName || t.tableName })));
|
||||
}
|
||||
} catch { /* ignore */ } finally { setLoadingTables(false); }
|
||||
};
|
||||
loadTables();
|
||||
}, []);
|
||||
|
||||
const config: ProcessWorkStandardConfig = {
|
||||
...defaultConfig,
|
||||
...configProp,
|
||||
dataSource: { ...defaultConfig.dataSource, ...configProp?.dataSource },
|
||||
phases: configProp?.phases?.length ? configProp.phases : defaultConfig.phases,
|
||||
detailTypes: configProp?.detailTypes?.length ? configProp.detailTypes : defaultConfig.detailTypes,
|
||||
phases: configProp?.phases?.length
|
||||
? configProp.phases
|
||||
: defaultConfig.phases,
|
||||
detailTypes: configProp?.detailTypes?.length
|
||||
? configProp.detailTypes
|
||||
: defaultConfig.detailTypes,
|
||||
};
|
||||
|
||||
const update = (partial: Partial<ProcessWorkStandardConfig>) => {
|
||||
|
|
@ -50,13 +127,16 @@ export const V2ProcessWorkStandardConfigPanel: React.FC<V2ProcessWorkStandardCon
|
|||
update({ dataSource: { ...config.dataSource, [field]: value } });
|
||||
};
|
||||
|
||||
// ─── 작업 단계 관리 ───
|
||||
const addPhase = () => {
|
||||
const nextOrder = config.phases.length + 1;
|
||||
update({
|
||||
phases: [
|
||||
...config.phases,
|
||||
{ key: `PHASE_${nextOrder}`, label: `단계 ${nextOrder}`, sortOrder: nextOrder },
|
||||
{
|
||||
key: `PHASE_${nextOrder}`,
|
||||
label: `단계 ${nextOrder}`,
|
||||
sortOrder: nextOrder,
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
|
@ -65,18 +145,24 @@ export const V2ProcessWorkStandardConfigPanel: React.FC<V2ProcessWorkStandardCon
|
|||
update({ phases: config.phases.filter((_, i) => i !== idx) });
|
||||
};
|
||||
|
||||
const updatePhase = (idx: number, field: keyof WorkPhaseDefinition, value: string | number) => {
|
||||
const updatePhase = (
|
||||
idx: number,
|
||||
field: keyof WorkPhaseDefinition,
|
||||
value: string | number,
|
||||
) => {
|
||||
const next = [...config.phases];
|
||||
next[idx] = { ...next[idx], [field]: value };
|
||||
update({ phases: next });
|
||||
};
|
||||
|
||||
// ─── 상세 유형 관리 ───
|
||||
const addDetailType = () => {
|
||||
update({
|
||||
detailTypes: [
|
||||
...config.detailTypes,
|
||||
{ value: `TYPE_${config.detailTypes.length + 1}`, label: "신규 유형" },
|
||||
{
|
||||
value: `TYPE_${config.detailTypes.length + 1}`,
|
||||
label: "신규 유형",
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
|
@ -85,7 +171,11 @@ export const V2ProcessWorkStandardConfigPanel: React.FC<V2ProcessWorkStandardCon
|
|||
update({ detailTypes: config.detailTypes.filter((_, i) => i !== idx) });
|
||||
};
|
||||
|
||||
const updateDetailType = (idx: number, field: keyof DetailTypeDefinition, value: string) => {
|
||||
const updateDetailType = (
|
||||
idx: number,
|
||||
field: keyof DetailTypeDefinition,
|
||||
value: string,
|
||||
) => {
|
||||
const next = [...config.detailTypes];
|
||||
next[idx] = { ...next[idx], [field]: value };
|
||||
update({ detailTypes: next });
|
||||
|
|
@ -93,31 +183,75 @@ export const V2ProcessWorkStandardConfigPanel: React.FC<V2ProcessWorkStandardCon
|
|||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* ─── 1단계: 작업 단계 설정 (Collapsible + 접이식 카드) ─── */}
|
||||
{/* 품목 목록 모드 */}
|
||||
<div className="space-y-2 rounded-lg border p-4">
|
||||
<span className="text-sm font-medium">품목 목록 모드</span>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"flex flex-col items-center gap-1 rounded-md border px-3 py-2.5 text-xs transition-colors",
|
||||
(config.itemListMode || "all") === "all"
|
||||
? "border-primary bg-primary/5 text-primary"
|
||||
: "border-input hover:bg-muted/50",
|
||||
)}
|
||||
onClick={() => update({ itemListMode: "all" })}
|
||||
>
|
||||
<span className="font-medium">전체 품목</span>
|
||||
<span className="text-muted-foreground text-[10px]">
|
||||
모든 품목 표시
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"flex flex-col items-center gap-1 rounded-md border px-3 py-2.5 text-xs transition-colors",
|
||||
config.itemListMode === "registered"
|
||||
? "border-primary bg-primary/5 text-primary"
|
||||
: "border-input hover:bg-muted/50",
|
||||
)}
|
||||
onClick={() => update({ itemListMode: "registered" })}
|
||||
>
|
||||
<span className="font-medium">등록 품목만</span>
|
||||
<span className="text-muted-foreground text-[10px]">
|
||||
라우팅에 등록된 품목만
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
{config.itemListMode === "registered" && (
|
||||
<p className="text-muted-foreground pt-1 text-[10px]">
|
||||
품목별 라우팅 탭에서 등록한 품목만 표시됩니다.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 작업 단계 */}
|
||||
<Collapsible open={phasesOpen} onOpenChange={setPhasesOpen}>
|
||||
<CollapsibleTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
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"
|
||||
className="bg-muted/30 hover:bg-muted/50 flex w-full items-center justify-between rounded-lg border px-4 py-2.5 text-left transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Layers className="h-4 w-4 text-muted-foreground" />
|
||||
<Layers className="text-muted-foreground h-4 w-4" />
|
||||
<span className="text-sm font-medium">작업 단계</span>
|
||||
<Badge variant="secondary" className="text-[10px] h-5">
|
||||
<Badge variant="secondary" className="h-5 text-[10px]">
|
||||
{config.phases.length}개
|
||||
</Badge>
|
||||
</div>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
"h-4 w-4 text-muted-foreground transition-transform duration-200",
|
||||
"text-muted-foreground h-4 w-4 transition-transform duration-200",
|
||||
phasesOpen && "rotate-180",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div className="rounded-b-lg border border-t-0 p-3 space-y-1.5">
|
||||
<p className="text-[10px] text-muted-foreground mb-1">공정별 작업 단계(Phase)를 정의</p>
|
||||
<div className="space-y-1.5 rounded-b-lg border border-t-0 p-3">
|
||||
<p className="text-muted-foreground mb-1 text-[10px]">
|
||||
공정별 작업 단계를 정의
|
||||
</p>
|
||||
<div className="space-y-1">
|
||||
{config.phases.map((phase, idx) => (
|
||||
<Collapsible key={idx}>
|
||||
|
|
@ -125,18 +259,30 @@ export const V2ProcessWorkStandardConfigPanel: React.FC<V2ProcessWorkStandardCon
|
|||
<CollapsibleTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-1.5 px-2.5 py-1.5 text-left hover:bg-muted/30 transition-colors"
|
||||
className="hover:bg-muted/30 flex w-full items-center gap-1.5 px-2.5 py-1.5 text-left transition-colors"
|
||||
>
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground transition-transform [[data-state=open]>&]:rotate-90 shrink-0" />
|
||||
<span className="text-[10px] text-muted-foreground font-medium shrink-0">#{idx + 1}</span>
|
||||
<span className="text-xs font-medium truncate flex-1 min-w-0">{phase.label}</span>
|
||||
<Badge variant="outline" className="text-[9px] h-4 shrink-0">{phase.key}</Badge>
|
||||
<ChevronRight className="text-muted-foreground h-3 w-3 shrink-0 transition-transform [[data-state=open]>&]:rotate-90" />
|
||||
<span className="text-muted-foreground shrink-0 text-[10px] font-medium">
|
||||
#{idx + 1}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate text-xs font-medium">
|
||||
{phase.label}
|
||||
</span>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="h-4 shrink-0 text-[9px]"
|
||||
>
|
||||
{phase.key}
|
||||
</Badge>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={(e) => { e.stopPropagation(); removePhase(idx); }}
|
||||
className="h-5 w-5 p-0 text-muted-foreground hover:text-destructive shrink-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removePhase(idx);
|
||||
}}
|
||||
className="text-muted-foreground hover:text-destructive h-5 w-5 shrink-0 p-0"
|
||||
disabled={config.phases.length <= 1}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
|
|
@ -146,32 +292,45 @@ export const V2ProcessWorkStandardConfigPanel: React.FC<V2ProcessWorkStandardCon
|
|||
<CollapsibleContent>
|
||||
<div className="grid grid-cols-3 gap-1.5 border-t px-2.5 py-2">
|
||||
<div className="space-y-0.5">
|
||||
<span className="text-[10px] text-muted-foreground">키</span>
|
||||
<span className="text-muted-foreground text-[10px]">
|
||||
키
|
||||
</span>
|
||||
<Input
|
||||
value={phase.key}
|
||||
onChange={(e) => updatePhase(idx, "key", e.target.value)}
|
||||
onChange={(e) =>
|
||||
updatePhase(idx, "key", e.target.value)
|
||||
}
|
||||
className="h-7 text-xs"
|
||||
placeholder="키"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
<span className="text-[10px] text-muted-foreground">표시명</span>
|
||||
<span className="text-muted-foreground text-[10px]">
|
||||
표시명
|
||||
</span>
|
||||
<Input
|
||||
value={phase.label}
|
||||
onChange={(e) => updatePhase(idx, "label", e.target.value)}
|
||||
onChange={(e) =>
|
||||
updatePhase(idx, "label", e.target.value)
|
||||
}
|
||||
className="h-7 text-xs"
|
||||
placeholder="표시명"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
<span className="text-[10px] text-muted-foreground">순서</span>
|
||||
<span className="text-muted-foreground text-[10px]">
|
||||
순서
|
||||
</span>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={phase.sortOrder}
|
||||
onChange={(e) => updatePhase(idx, "sortOrder", parseInt(e.target.value) || 1)}
|
||||
className="h-7 text-xs text-center"
|
||||
placeholder="1"
|
||||
onChange={(e) =>
|
||||
updatePhase(
|
||||
idx,
|
||||
"sortOrder",
|
||||
parseInt(e.target.value) || 1,
|
||||
)
|
||||
}
|
||||
className="h-7 text-center text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -183,7 +342,7 @@ export const V2ProcessWorkStandardConfigPanel: React.FC<V2ProcessWorkStandardCon
|
|||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-7 w-full gap-1 text-xs border-dashed"
|
||||
className="h-7 w-full gap-1 border-dashed text-xs"
|
||||
onClick={addPhase}
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
|
|
@ -193,31 +352,33 @@ export const V2ProcessWorkStandardConfigPanel: React.FC<V2ProcessWorkStandardCon
|
|||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
|
||||
{/* ─── 2단계: 상세 유형 옵션 (Collapsible + 접이식 카드) ─── */}
|
||||
{/* 상세 유형 */}
|
||||
<Collapsible open={detailTypesOpen} onOpenChange={setDetailTypesOpen}>
|
||||
<CollapsibleTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
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"
|
||||
className="bg-muted/30 hover:bg-muted/50 flex w-full items-center justify-between rounded-lg border px-4 py-2.5 text-left transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<List className="h-4 w-4 text-muted-foreground" />
|
||||
<List className="text-muted-foreground h-4 w-4" />
|
||||
<span className="text-sm font-medium">상세 유형</span>
|
||||
<Badge variant="secondary" className="text-[10px] h-5">
|
||||
<Badge variant="secondary" className="h-5 text-[10px]">
|
||||
{config.detailTypes.length}개
|
||||
</Badge>
|
||||
</div>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
"h-4 w-4 text-muted-foreground transition-transform duration-200",
|
||||
"text-muted-foreground h-4 w-4 transition-transform duration-200",
|
||||
detailTypesOpen && "rotate-180",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div className="rounded-b-lg border border-t-0 p-3 space-y-1.5">
|
||||
<p className="text-[10px] text-muted-foreground mb-1">작업 항목의 상세 유형 드롭다운 옵션</p>
|
||||
<div className="space-y-1.5 rounded-b-lg border border-t-0 p-3">
|
||||
<p className="text-muted-foreground mb-1 text-[10px]">
|
||||
작업 항목의 상세 유형 옵션
|
||||
</p>
|
||||
<div className="space-y-1">
|
||||
{config.detailTypes.map((dt, idx) => (
|
||||
<Collapsible key={idx}>
|
||||
|
|
@ -225,18 +386,30 @@ export const V2ProcessWorkStandardConfigPanel: React.FC<V2ProcessWorkStandardCon
|
|||
<CollapsibleTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center gap-1.5 px-2.5 py-1.5 text-left hover:bg-muted/30 transition-colors"
|
||||
className="hover:bg-muted/30 flex w-full items-center gap-1.5 px-2.5 py-1.5 text-left transition-colors"
|
||||
>
|
||||
<ChevronRight className="h-3 w-3 text-muted-foreground transition-transform [[data-state=open]>&]:rotate-90 shrink-0" />
|
||||
<span className="text-[10px] text-muted-foreground font-medium shrink-0">#{idx + 1}</span>
|
||||
<span className="text-xs font-medium truncate flex-1 min-w-0">{dt.label}</span>
|
||||
<Badge variant="outline" className="text-[9px] h-4 shrink-0">{dt.value}</Badge>
|
||||
<ChevronRight className="text-muted-foreground h-3 w-3 shrink-0 transition-transform [[data-state=open]>&]:rotate-90" />
|
||||
<span className="text-muted-foreground shrink-0 text-[10px] font-medium">
|
||||
#{idx + 1}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate text-xs font-medium">
|
||||
{dt.label}
|
||||
</span>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="h-4 shrink-0 text-[9px]"
|
||||
>
|
||||
{dt.value}
|
||||
</Badge>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={(e) => { e.stopPropagation(); removeDetailType(idx); }}
|
||||
className="h-5 w-5 p-0 text-muted-foreground hover:text-destructive shrink-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeDetailType(idx);
|
||||
}}
|
||||
className="text-muted-foreground hover:text-destructive h-5 w-5 shrink-0 p-0"
|
||||
disabled={config.detailTypes.length <= 1}
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
|
|
@ -246,21 +419,27 @@ export const V2ProcessWorkStandardConfigPanel: React.FC<V2ProcessWorkStandardCon
|
|||
<CollapsibleContent>
|
||||
<div className="grid grid-cols-2 gap-1.5 border-t px-2.5 py-2">
|
||||
<div className="space-y-0.5">
|
||||
<span className="text-[10px] text-muted-foreground">값</span>
|
||||
<span className="text-muted-foreground text-[10px]">
|
||||
값
|
||||
</span>
|
||||
<Input
|
||||
value={dt.value}
|
||||
onChange={(e) => updateDetailType(idx, "value", e.target.value)}
|
||||
onChange={(e) =>
|
||||
updateDetailType(idx, "value", e.target.value)
|
||||
}
|
||||
className="h-7 text-xs"
|
||||
placeholder="값"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
<span className="text-[10px] text-muted-foreground">표시명</span>
|
||||
<span className="text-muted-foreground text-[10px]">
|
||||
표시명
|
||||
</span>
|
||||
<Input
|
||||
value={dt.label}
|
||||
onChange={(e) => updateDetailType(idx, "label", e.target.value)}
|
||||
onChange={(e) =>
|
||||
updateDetailType(idx, "label", e.target.value)
|
||||
}
|
||||
className="h-7 text-xs"
|
||||
placeholder="표시명"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -272,7 +451,7 @@ export const V2ProcessWorkStandardConfigPanel: React.FC<V2ProcessWorkStandardCon
|
|||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-7 w-full gap-1 text-xs border-dashed"
|
||||
className="h-7 w-full gap-1 border-dashed text-xs"
|
||||
onClick={addDetailType}
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
|
|
@ -282,179 +461,102 @@ export const V2ProcessWorkStandardConfigPanel: React.FC<V2ProcessWorkStandardCon
|
|||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
|
||||
{/* ─── 3단계: 고급 설정 (데이터 소스 + 레이아웃 통합) ─── */}
|
||||
<Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen}>
|
||||
{/* 데이터 소스 (테이블만) */}
|
||||
<Collapsible open={dataSourceOpen} onOpenChange={setDataSourceOpen}>
|
||||
<CollapsibleTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
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"
|
||||
className="bg-muted/30 hover:bg-muted/50 flex w-full items-center justify-between rounded-lg border px-4 py-2.5 text-left 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>
|
||||
<Database className="text-muted-foreground h-4 w-4" />
|
||||
<span className="text-sm font-medium">테이블 설정</span>
|
||||
</div>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
"h-4 w-4 text-muted-foreground transition-transform duration-200",
|
||||
advancedOpen && "rotate-180",
|
||||
"text-muted-foreground h-4 w-4 transition-transform duration-200",
|
||||
dataSourceOpen && "rotate-180",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div className="rounded-b-lg border border-t-0 p-3 space-y-3">
|
||||
<div className="space-y-2.5 rounded-b-lg border border-t-0 p-4">
|
||||
<p className="text-muted-foreground text-[10px]">
|
||||
테이블만 선택하면 컬럼 정보는 엔티티 설정에서 자동으로 가져옵니다.
|
||||
</p>
|
||||
<TableCombobox label="품목" value={config.dataSource.itemTable} onChange={(v) => updateDataSource("itemTable", v)} tables={tables} loading={loadingTables} />
|
||||
<TableCombobox label="라우팅 버전" value={config.dataSource.routingVersionTable} onChange={(v) => updateDataSource("routingVersionTable", v)} tables={tables} loading={loadingTables} />
|
||||
<TableCombobox label="라우팅 상세" value={config.dataSource.routingDetailTable} onChange={(v) => updateDataSource("routingDetailTable", v)} tables={tables} loading={loadingTables} />
|
||||
<TableCombobox label="공정 마스터" value={config.dataSource.processTable} onChange={(v) => updateDataSource("processTable", v)} tables={tables} loading={loadingTables} />
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
|
||||
{/* 레이아웃 기본 설정 */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<div>
|
||||
<span className="text-xs text-muted-foreground">좌측 패널 비율 (%)</span>
|
||||
<p className="text-[10px] text-muted-foreground mt-0.5">품목/공정 선택 패널의 너비</p>
|
||||
</div>
|
||||
<Input
|
||||
type="number"
|
||||
min={15}
|
||||
max={50}
|
||||
value={config.splitRatio || 30}
|
||||
onChange={(e) => update({ splitRatio: parseInt(e.target.value) || 30 })}
|
||||
className="h-7 w-[80px] text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<span className="text-xs text-muted-foreground">좌측 패널 제목</span>
|
||||
<Input
|
||||
value={config.leftPanelTitle || ""}
|
||||
onChange={(e) => update({ leftPanelTitle: e.target.value })}
|
||||
placeholder="품목 및 공정 선택"
|
||||
className="h-7 w-[140px] text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<div>
|
||||
<p className="text-xs">읽기 전용</p>
|
||||
<p className="text-[10px] text-muted-foreground">수정/삭제 버튼을 숨겨요</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={config.readonly || false}
|
||||
onCheckedChange={(checked) => update({ readonly: checked })}
|
||||
/>
|
||||
</div>
|
||||
{/* 레이아웃 & 기타 */}
|
||||
<Collapsible open={layoutOpen} onOpenChange={setLayoutOpen}>
|
||||
<CollapsibleTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="bg-muted/30 hover:bg-muted/50 flex w-full items-center justify-between rounded-lg border px-4 py-2.5 text-left transition-colors"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings className="text-muted-foreground h-4 w-4" />
|
||||
<span className="text-sm font-medium">레이아웃</span>
|
||||
</div>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
"text-muted-foreground h-4 w-4 transition-transform duration-200",
|
||||
layoutOpen && "rotate-180",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div className="space-y-3 rounded-b-lg border border-t-0 p-4">
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<div>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
좌측 패널 비율 (%)
|
||||
</span>
|
||||
<p className="text-muted-foreground mt-0.5 text-[10px]">
|
||||
품목/공정 선택 패널의 너비
|
||||
</p>
|
||||
</div>
|
||||
<Input
|
||||
type="number"
|
||||
min={15}
|
||||
max={50}
|
||||
value={config.splitRatio || 30}
|
||||
onChange={(e) =>
|
||||
update({ splitRatio: parseInt(e.target.value) || 30 })
|
||||
}
|
||||
className="h-7 w-[80px] text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<span className="text-muted-foreground text-xs">
|
||||
좌측 패널 제목
|
||||
</span>
|
||||
<Input
|
||||
value={config.leftPanelTitle || ""}
|
||||
onChange={(e) => update({ leftPanelTitle: e.target.value })}
|
||||
placeholder="품목 및 공정 선택"
|
||||
className="h-7 w-[140px] text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<div>
|
||||
<p className="text-xs">읽기 전용</p>
|
||||
<p className="text-muted-foreground text-[10px]">
|
||||
수정/삭제 버튼을 숨겨요
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={config.readonly || false}
|
||||
onCheckedChange={(checked) => update({ readonly: checked })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 데이터 소스 (서브 Collapsible) */}
|
||||
<Collapsible open={dataSourceOpen} onOpenChange={setDataSourceOpen}>
|
||||
<CollapsibleTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full items-center justify-between rounded-md border px-3 py-2 transition-colors hover:bg-muted/30"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Database className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<span className="text-xs font-medium">데이터 소스</span>
|
||||
{config.dataSource.itemTable && (
|
||||
<Badge variant="secondary" className="text-[10px] h-5 truncate max-w-[100px]">
|
||||
{config.dataSource.itemTable}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<ChevronDown
|
||||
className={cn(
|
||||
"h-3.5 w-3.5 text-muted-foreground transition-transform",
|
||||
dataSourceOpen && "rotate-180",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="space-y-2 pt-2">
|
||||
<div className="space-y-1">
|
||||
<span className="text-[10px] text-muted-foreground">품목 테이블</span>
|
||||
<Input
|
||||
value={config.dataSource.itemTable}
|
||||
onChange={(e) => updateDataSource("itemTable", e.target.value)}
|
||||
className="h-7 w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="space-y-1">
|
||||
<span className="text-[10px] text-muted-foreground">품목명 컬럼</span>
|
||||
<Input
|
||||
value={config.dataSource.itemNameColumn}
|
||||
onChange={(e) => updateDataSource("itemNameColumn", e.target.value)}
|
||||
className="h-7 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<span className="text-[10px] text-muted-foreground">품목코드 컬럼</span>
|
||||
<Input
|
||||
value={config.dataSource.itemCodeColumn}
|
||||
onChange={(e) => updateDataSource("itemCodeColumn", e.target.value)}
|
||||
className="h-7 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1 pt-1">
|
||||
<span className="text-[10px] text-muted-foreground">라우팅 버전 테이블</span>
|
||||
<Input
|
||||
value={config.dataSource.routingVersionTable}
|
||||
onChange={(e) => updateDataSource("routingVersionTable", e.target.value)}
|
||||
className="h-7 w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="space-y-1">
|
||||
<span className="text-[10px] text-muted-foreground">품목 연결 FK</span>
|
||||
<Input
|
||||
value={config.dataSource.routingFkColumn}
|
||||
onChange={(e) => updateDataSource("routingFkColumn", e.target.value)}
|
||||
className="h-7 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<span className="text-[10px] text-muted-foreground">버전명 컬럼</span>
|
||||
<Input
|
||||
value={config.dataSource.routingVersionNameColumn}
|
||||
onChange={(e) => updateDataSource("routingVersionNameColumn", e.target.value)}
|
||||
className="h-7 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1 pt-1">
|
||||
<span className="text-[10px] text-muted-foreground">라우팅 상세 테이블</span>
|
||||
<Input
|
||||
value={config.dataSource.routingDetailTable}
|
||||
onChange={(e) => updateDataSource("routingDetailTable", e.target.value)}
|
||||
className="h-7 w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1 pt-1">
|
||||
<span className="text-[10px] text-muted-foreground">공정 마스터 테이블</span>
|
||||
<Input
|
||||
value={config.dataSource.processTable}
|
||||
onChange={(e) => updateDataSource("processTable", e.target.value)}
|
||||
className="h-7 w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="space-y-1">
|
||||
<span className="text-[10px] text-muted-foreground">공정명 컬럼</span>
|
||||
<Input
|
||||
value={config.dataSource.processNameColumn}
|
||||
onChange={(e) => updateDataSource("processNameColumn", e.target.value)}
|
||||
className="h-7 text-xs"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<span className="text-[10px] text-muted-foreground">공정코드 컬럼</span>
|
||||
<Input
|
||||
value={config.dataSource.processCodeColumn}
|
||||
onChange={(e) => updateDataSource("processCodeColumn", e.target.value)}
|
||||
className="h-7 text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
|
|
@ -462,6 +564,7 @@ export const V2ProcessWorkStandardConfigPanel: React.FC<V2ProcessWorkStandardCon
|
|||
);
|
||||
};
|
||||
|
||||
V2ProcessWorkStandardConfigPanel.displayName = "V2ProcessWorkStandardConfigPanel";
|
||||
V2ProcessWorkStandardConfigPanel.displayName =
|
||||
"V2ProcessWorkStandardConfigPanel";
|
||||
|
||||
export default V2ProcessWorkStandardConfigPanel;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,113 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Plus, Trash2, GripVertical } from "lucide-react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Plus, Trash2, GripVertical, Check, ChevronsUpDown } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ProcessWorkStandardConfig, WorkPhaseDefinition, DetailTypeDefinition } from "./types";
|
||||
import { defaultConfig } from "./config";
|
||||
|
||||
interface TableInfo { tableName: string; displayName?: string; }
|
||||
interface ColumnInfo { columnName: string; displayName?: string; dataType?: string; }
|
||||
|
||||
function TableCombobox({ value, onChange, tables, loading }: {
|
||||
value: string; onChange: (v: string) => void; tables: TableInfo[]; loading: boolean;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const selected = tables.find((t) => t.tableName === value);
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" role="combobox" aria-expanded={open} className="mt-1 h-8 w-full justify-between text-xs" disabled={loading}>
|
||||
{loading ? "로딩 중..." : selected ? selected.displayName || selected.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>
|
||||
<CommandEmpty className="py-4 text-center text-xs">테이블을 찾을 수 없습니다.</CommandEmpty>
|
||||
<CommandGroup className="max-h-[200px] overflow-auto">
|
||||
{tables.map((t) => (
|
||||
<CommandItem key={t.tableName} value={`${t.displayName || ""} ${t.tableName}`}
|
||||
onSelect={() => { onChange(t.tableName); setOpen(false); }} className="text-xs">
|
||||
<Check className={cn("mr-2 h-3 w-3", value === t.tableName ? "opacity-100" : "opacity-0")} />
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{t.displayName || t.tableName}</span>
|
||||
{t.displayName && <span className="text-[10px] text-muted-foreground">{t.tableName}</span>}
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
function ColumnCombobox({ value, onChange, tableName, placeholder }: {
|
||||
value: string; onChange: (v: string) => void; tableName: string; placeholder?: string;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [columns, setColumns] = useState<ColumnInfo[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!tableName) { setColumns([]); return; }
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { tableManagementApi } = await import("@/lib/api/tableManagement");
|
||||
const res = await tableManagementApi.getColumnList(tableName);
|
||||
if (res.success && res.data?.columns) setColumns(res.data.columns);
|
||||
} catch { /* ignore */ } finally { setLoading(false); }
|
||||
};
|
||||
load();
|
||||
}, [tableName]);
|
||||
|
||||
const selected = columns.find((c) => c.columnName === value);
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" role="combobox" aria-expanded={open} className="mt-1 h-8 w-full justify-between text-xs" disabled={loading || !tableName}>
|
||||
<span className="truncate">
|
||||
{loading ? "로딩..." : !tableName ? "테이블 먼저 선택" : selected ? selected.displayName || selected.columnName : placeholder || "컬럼 선택"}
|
||||
</span>
|
||||
<ChevronsUpDown className="ml-2 h-3 w-3 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[240px] p-0" align="start">
|
||||
<Command>
|
||||
<CommandInput placeholder="컬럼 검색..." className="text-xs" />
|
||||
<CommandList>
|
||||
<CommandEmpty className="py-4 text-center text-xs">컬럼을 찾을 수 없습니다.</CommandEmpty>
|
||||
<CommandGroup className="max-h-[200px] overflow-auto">
|
||||
{columns.map((c) => (
|
||||
<CommandItem key={c.columnName} value={`${c.displayName || ""} ${c.columnName}`}
|
||||
onSelect={() => { onChange(c.columnName); setOpen(false); }} className="text-xs">
|
||||
<Check className={cn("mr-2 h-3 w-3", value === c.columnName ? "opacity-100" : "opacity-0")} />
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{c.displayName || c.columnName}</span>
|
||||
{c.displayName && <span className="text-[10px] text-muted-foreground">{c.columnName}</span>}
|
||||
</div>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
interface ConfigPanelProps {
|
||||
config: Partial<ProcessWorkStandardConfig>;
|
||||
onChange: (config: Partial<ProcessWorkStandardConfig>) => void;
|
||||
|
|
@ -19,6 +117,9 @@ export function ProcessWorkStandardConfigPanel({
|
|||
config: configProp,
|
||||
onChange,
|
||||
}: ConfigPanelProps) {
|
||||
const [tables, setTables] = useState<TableInfo[]>([]);
|
||||
const [loadingTables, setLoadingTables] = useState(false);
|
||||
|
||||
const config: ProcessWorkStandardConfig = {
|
||||
...defaultConfig,
|
||||
...configProp,
|
||||
|
|
@ -27,6 +128,20 @@ export function ProcessWorkStandardConfigPanel({
|
|||
detailTypes: configProp?.detailTypes?.length ? configProp.detailTypes : defaultConfig.detailTypes,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const loadTables = async () => {
|
||||
setLoadingTables(true);
|
||||
try {
|
||||
const { tableManagementApi } = await import("@/lib/api/tableManagement");
|
||||
const res = await tableManagementApi.getTableList();
|
||||
if (res.success && res.data) {
|
||||
setTables(res.data.map((t: any) => ({ tableName: t.tableName, displayName: t.displayName || t.tableName })));
|
||||
}
|
||||
} catch { /* ignore */ } finally { setLoadingTables(false); }
|
||||
};
|
||||
loadTables();
|
||||
}, []);
|
||||
|
||||
const update = (partial: Partial<ProcessWorkStandardConfig>) => {
|
||||
onChange({ ...configProp, ...partial });
|
||||
};
|
||||
|
|
@ -112,72 +227,40 @@ export function ProcessWorkStandardConfigPanel({
|
|||
|
||||
<div>
|
||||
<Label className="text-xs">품목 테이블</Label>
|
||||
<Input
|
||||
value={config.dataSource.itemTable}
|
||||
onChange={(e) => updateDataSource("itemTable", e.target.value)}
|
||||
className="mt-1 h-8 text-xs"
|
||||
/>
|
||||
<TableCombobox value={config.dataSource.itemTable} onChange={(v) => updateDataSource("itemTable", v)} tables={tables} loading={loadingTables} />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<Label className="text-xs">품목명 컬럼</Label>
|
||||
<Input
|
||||
value={config.dataSource.itemNameColumn}
|
||||
onChange={(e) => updateDataSource("itemNameColumn", e.target.value)}
|
||||
className="mt-1 h-8 text-xs"
|
||||
/>
|
||||
<ColumnCombobox value={config.dataSource.itemNameColumn} onChange={(v) => updateDataSource("itemNameColumn", v)} tableName={config.dataSource.itemTable} placeholder="품목명" />
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xs">품목코드 컬럼</Label>
|
||||
<Input
|
||||
value={config.dataSource.itemCodeColumn}
|
||||
onChange={(e) => updateDataSource("itemCodeColumn", e.target.value)}
|
||||
className="mt-1 h-8 text-xs"
|
||||
/>
|
||||
<ColumnCombobox value={config.dataSource.itemCodeColumn} onChange={(v) => updateDataSource("itemCodeColumn", v)} tableName={config.dataSource.itemTable} placeholder="품목코드" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="text-xs">라우팅 버전 테이블</Label>
|
||||
<Input
|
||||
value={config.dataSource.routingVersionTable}
|
||||
onChange={(e) => updateDataSource("routingVersionTable", e.target.value)}
|
||||
className="mt-1 h-8 text-xs"
|
||||
/>
|
||||
<TableCombobox value={config.dataSource.routingVersionTable} onChange={(v) => updateDataSource("routingVersionTable", v)} tables={tables} loading={loadingTables} />
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xs">품목 연결 FK 컬럼</Label>
|
||||
<Input
|
||||
value={config.dataSource.routingFkColumn}
|
||||
onChange={(e) => updateDataSource("routingFkColumn", e.target.value)}
|
||||
className="mt-1 h-8 text-xs"
|
||||
/>
|
||||
<ColumnCombobox value={config.dataSource.routingFkColumn} onChange={(v) => updateDataSource("routingFkColumn", v)} tableName={config.dataSource.routingVersionTable} placeholder="FK 컬럼" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label className="text-xs">공정 마스터 테이블</Label>
|
||||
<Input
|
||||
value={config.dataSource.processTable}
|
||||
onChange={(e) => updateDataSource("processTable", e.target.value)}
|
||||
className="mt-1 h-8 text-xs"
|
||||
/>
|
||||
<TableCombobox value={config.dataSource.processTable} onChange={(v) => updateDataSource("processTable", v)} tables={tables} loading={loadingTables} />
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<Label className="text-xs">공정명 컬럼</Label>
|
||||
<Input
|
||||
value={config.dataSource.processNameColumn}
|
||||
onChange={(e) => updateDataSource("processNameColumn", e.target.value)}
|
||||
className="mt-1 h-8 text-xs"
|
||||
/>
|
||||
<ColumnCombobox value={config.dataSource.processNameColumn} onChange={(v) => updateDataSource("processNameColumn", v)} tableName={config.dataSource.processTable} placeholder="공정명" />
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-xs">공정코드 컬럼</Label>
|
||||
<Input
|
||||
value={config.dataSource.processCodeColumn}
|
||||
onChange={(e) => updateDataSource("processCodeColumn", e.target.value)}
|
||||
className="mt-1 h-8 text-xs"
|
||||
/>
|
||||
<ColumnCombobox value={config.dataSource.processCodeColumn} onChange={(v) => updateDataSource("processCodeColumn", v)} tableName={config.dataSource.processTable} placeholder="공정코드" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
Loading…
Reference in New Issue