[agent-pipeline] pipe-20260311124806-lfrk round-1

This commit is contained in:
DDD1542 2026-03-11 21:51:37 +09:00
parent 24630dd60b
commit 1bbce43ec1
1 changed files with 343 additions and 339 deletions

View File

@ -2,14 +2,17 @@
/** /**
* V2Input * V2Input
* . * UX: 기본 -> -> ()
*/ */
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox"; import { Switch } from "@/components/ui/switch";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import { Settings, ChevronDown, Loader2 } from "lucide-react";
import { cn } from "@/lib/utils";
import { AutoGenerationType, AutoGenerationConfig } from "@/types/screen"; import { AutoGenerationType, AutoGenerationConfig } from "@/types/screen";
import { AutoGenerationUtils } from "@/lib/utils/autoGeneration"; import { AutoGenerationUtils } from "@/lib/utils/autoGeneration";
import { getAvailableNumberingRules } from "@/lib/api/numberingRule"; import { getAvailableNumberingRules } from "@/lib/api/numberingRule";
@ -29,6 +32,7 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
const [selectedMenuObjid, setSelectedMenuObjid] = useState<number | undefined>(() => { const [selectedMenuObjid, setSelectedMenuObjid] = useState<number | undefined>(() => {
return config.autoGeneration?.selectedMenuObjid || menuObjid; return config.autoGeneration?.selectedMenuObjid || menuObjid;
}); });
const [advancedOpen, setAdvancedOpen] = useState(false);
const updateConfig = (field: string, value: any) => { const updateConfig = (field: string, value: any) => {
onChange({ ...config, [field]: value }); onChange({ ...config, [field]: value });
@ -76,19 +80,18 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
loadRules(); loadRules();
}, [selectedMenuObjid, config.autoGeneration?.type]); }, [selectedMenuObjid, config.autoGeneration?.type]);
const inputType = config.inputType || config.type || "text";
return ( return (
<div className="space-y-1"> <div className="space-y-4">
{/* INPUT TYPE 섹션 */} {/* ─── 1단계: 입력 타입 선택 ─── */}
<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">INPUT TYPE</h4> <p className="text-sm font-medium"> </p>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<div className="w-[140px]">
<Select <Select
value={config.inputType || config.type || "text"} value={inputType}
onValueChange={(value) => updateConfig("inputType", value)} onValueChange={(value) => updateConfig("inputType", value)}
> >
<SelectTrigger className="h-7 text-xs"> <SelectTrigger className="h-8 text-sm">
<SelectValue placeholder="입력 타입 선택" /> <SelectValue placeholder="입력 타입 선택" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -101,45 +104,53 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
<SelectItem value="numbering"> ()</SelectItem> <SelectItem value="numbering"> ()</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> <p className="text-[11px] text-muted-foreground"> </p>
</div>
</div> </div>
{/* NUMBERING 섹션 - 채번 타입 전용 */} {/* ─── 채번 타입 전용 안내 ─── */}
{config.inputType === "numbering" && ( {inputType === "numbering" && (
<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">NUMBERING</h4> <div className="rounded-md border border-primary/20 bg-primary/5 p-3">
<div className="rounded-md border border-primary/20 bg-primary/5 p-2 mb-2"> <p className="text-xs text-primary">
<p className="text-[10px] text-primary"> <strong> </strong> .
<strong> </strong> . .
.
</p> </p>
</div> </div>
<div className="flex items-center justify-between py-1.5"> <div className="flex items-center justify-between py-1">
<span className="text-xs text-muted-foreground"> ()</span> <div>
<Checkbox <p className="text-sm"></p>
<p className="text-[11px] text-muted-foreground"> </p>
</div>
<Switch
checked={config.readonly !== false} checked={config.readonly !== false}
onCheckedChange={(checked) => updateConfig("readonly", checked)} onCheckedChange={(checked) => updateConfig("readonly", checked)}
/> />
</div> </div>
<p className="text-muted-foreground text-[10px] mt-0.5">
</p>
</div> </div>
)} )}
{/* 채번 타입이 아닌 경우에만 추가 설정 표시 */} {/* ─── 채번 타입이 아닌 경우: 기본 설정 ─── */}
{config.inputType !== "numbering" && ( {inputType !== "numbering" && (
<> <>
{/* FORMAT 섹션 */} {/* 기본 설정 영역 */}
{(config.inputType === "text" || !config.inputType) && ( <div className="rounded-lg border bg-muted/30 p-4 space-y-3">
<div className="border-b border-border/50 pb-3 mb-3"> {/* 안내 텍스트 (placeholder) */}
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">FORMAT</h4> <div className="flex items-center justify-between py-1">
<div className="flex items-center justify-between py-1.5"> <span className="text-xs text-muted-foreground"> </span>
<Input
value={config.placeholder || ""}
onChange={(e) => updateConfig("placeholder", e.target.value)}
placeholder="입력 안내"
className="h-7 w-[160px] text-xs"
/>
</div>
{/* 입력 형식 - 텍스트 타입 전용 */}
{(inputType === "text" || !config.inputType) && (
<div className="flex items-center justify-between py-1">
<span className="text-xs text-muted-foreground"> </span> <span className="text-xs text-muted-foreground"> </span>
<div className="w-[140px]">
<Select value={config.format || "none"} onValueChange={(value) => updateConfig("format", value)}> <Select value={config.format || "none"} onValueChange={(value) => updateConfig("format", value)}>
<SelectTrigger className="h-7 text-xs"> <SelectTrigger className="h-7 w-[160px] text-xs">
<SelectValue placeholder="형식 선택" /> <SelectValue placeholder="형식 선택" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -152,30 +163,26 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
</div>
</div>
)} )}
{/* PLACEHOLDER 섹션 */} {/* 입력 마스크 */}
<div className="border-b border-border/50 pb-3 mb-3"> <div className="flex items-center justify-between py-1">
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">PLACEHOLDER</h4> <div>
<div className="flex items-center justify-between py-1.5"> <span className="text-xs text-muted-foreground"> </span>
<span className="text-xs text-muted-foreground"> </span> <p className="text-[10px] text-muted-foreground mt-0.5"># = , A = , * = </p>
<div className="w-[140px]"> </div>
<Input <Input
value={config.placeholder || ""} value={config.mask || ""}
onChange={(e) => updateConfig("placeholder", e.target.value)} onChange={(e) => updateConfig("mask", e.target.value)}
placeholder="입력 안내" placeholder="###-####-####"
className="h-7 text-xs" className="h-7 w-[160px] text-xs"
/> />
</div> </div>
</div>
</div>
{/* RANGE 섹션 - 숫자/슬라이더 전용 */} {/* 숫자/슬라이더: 범위 설정 */}
{(config.inputType === "number" || config.inputType === "slider") && ( {(inputType === "number" || inputType === "slider") && (
<div className="border-b border-border/50 pb-3 mb-3"> <div className="space-y-2 pt-1">
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">RANGE</h4> <p className="text-xs text-muted-foreground"> </p>
<div className="flex gap-2"> <div className="flex gap-2">
<div className="flex-1"> <div className="flex-1">
<Label className="text-[10px] text-muted-foreground"></Label> <Label className="text-[10px] text-muted-foreground"></Label>
@ -211,49 +218,50 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
</div> </div>
)} )}
{/* TEXTAREA 섹션 */} {/* 여러 줄 텍스트: 줄 수 */}
{config.inputType === "textarea" && ( {inputType === "textarea" && (
<div className="border-b border-border/50 pb-3 mb-3"> <div className="flex items-center justify-between py-1">
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">TEXTAREA</h4>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span> <span className="text-xs text-muted-foreground"> </span>
<div className="w-[140px]">
<Input <Input
type="number" type="number"
value={config.rows || 3} value={config.rows || 3}
onChange={(e) => updateConfig("rows", parseInt(e.target.value) || 3)} onChange={(e) => updateConfig("rows", parseInt(e.target.value) || 3)}
min={2} min={2}
max={20} max={20}
className="h-7 text-xs" className="h-7 w-[160px] text-xs"
/> />
</div> </div>
</div>
</div>
)} )}
</div>
{/* INPUT MASK 섹션 */} {/* ─── 고급 설정: 자동 생성 (Collapsible) ─── */}
<div className="border-b border-border/50 pb-3 mb-3"> <Collapsible open={advancedOpen} onOpenChange={setAdvancedOpen}>
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">INPUT MASK</h4> <CollapsibleTrigger asChild>
<div className="flex items-center justify-between py-1.5"> <button
<span className="text-xs text-muted-foreground"></span> type="button"
<div className="w-[140px]"> 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"
<Input >
value={config.mask || ""} <div className="flex items-center gap-2">
onChange={(e) => updateConfig("mask", e.target.value)} <Settings className="h-4 w-4 text-muted-foreground" />
placeholder="###-####-####" <span className="text-sm font-medium"> </span>
className="h-7 text-xs" </div>
<ChevronDown
className={cn(
"h-4 w-4 text-muted-foreground transition-transform duration-200",
advancedOpen && "rotate-180"
)}
/> />
</button>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="rounded-b-lg border border-t-0 p-4 space-y-3">
{/* 자동 생성 토글 */}
<div className="flex items-center justify-between py-1">
<div>
<p className="text-sm"> </p>
<p className="text-[11px] text-muted-foreground"> </p>
</div> </div>
</div> <Switch
<p className="text-muted-foreground text-[10px] mt-0.5"># = , A = , * = </p>
</div>
{/* AUTO GENERATION 섹션 */}
<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">AUTO GENERATION</h4>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground"> </span>
<Checkbox
checked={config.autoGeneration?.enabled || false} checked={config.autoGeneration?.enabled || false}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
const currentConfig = config.autoGeneration || { type: "none", enabled: false }; const currentConfig = config.autoGeneration || { type: "none", enabled: false };
@ -266,11 +274,10 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
</div> </div>
{config.autoGeneration?.enabled && ( {config.autoGeneration?.enabled && (
<div className="mt-1 space-y-1"> <div className="space-y-3 ml-1 border-l-2 border-primary/20 pl-3">
{/* 자동생성 타입 */} {/* 자동 생성 타입 */}
<div className="flex items-center justify-between py-1.5"> <div>
<span className="text-xs text-muted-foreground"></span> <p className="mb-1.5 text-xs text-muted-foreground"> </p>
<div className="w-[140px]">
<Select <Select
value={config.autoGeneration?.type || "none"} value={config.autoGeneration?.type || "none"}
onValueChange={(value: AutoGenerationType) => { onValueChange={(value: AutoGenerationType) => {
@ -281,7 +288,7 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
}); });
}} }}
> >
<SelectTrigger className="h-7 text-xs"> <SelectTrigger className="h-8 text-sm">
<SelectValue placeholder="자동생성 타입 선택" /> <SelectValue placeholder="자동생성 타입 선택" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -298,22 +305,20 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
</div>
{config.autoGeneration?.type && config.autoGeneration.type !== "none" && ( {config.autoGeneration?.type && config.autoGeneration.type !== "none" && (
<p className="text-muted-foreground text-[10px] mt-0.5"> <p className="text-[11px] text-muted-foreground">
{AutoGenerationUtils.getTypeDescription(config.autoGeneration.type)} {AutoGenerationUtils.getTypeDescription(config.autoGeneration.type)}
</p> </p>
)} )}
{/* 채번 규칙 선택 */} {/* 채번 규칙 선택 */}
{config.autoGeneration?.type === "numbering_rule" && ( {config.autoGeneration?.type === "numbering_rule" && (
<div className="mt-1 space-y-1"> <div className="space-y-3">
<div className="flex items-center justify-between py-1.5"> <div>
<span className="text-xs text-muted-foreground"> <p className="mb-1.5 text-xs text-muted-foreground">
<span className="text-destructive">*</span> <span className="text-destructive">*</span>
</span> </p>
<div className="w-[140px]">
<Select <Select
value={selectedMenuObjid?.toString() || ""} value={selectedMenuObjid?.toString() || ""}
onValueChange={(value) => { onValueChange={(value) => {
@ -326,7 +331,7 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
}} }}
disabled={loadingMenus} disabled={loadingMenus}
> >
<SelectTrigger className="h-7 text-xs"> <SelectTrigger className="h-8 text-sm">
<SelectValue placeholder={loadingMenus ? "로딩 중..." : "메뉴 선택"} /> <SelectValue placeholder={loadingMenus ? "로딩 중..." : "메뉴 선택"} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -344,14 +349,18 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
</div>
{selectedMenuObjid ? ( {selectedMenuObjid ? (
<div className="flex items-center justify-between py-1.5"> <div>
<span className="text-xs text-muted-foreground"> <p className="mb-1.5 text-xs text-muted-foreground">
<span className="text-destructive">*</span> <span className="text-destructive">*</span>
</span> </p>
<div className="w-[140px]"> {loadingRules ? (
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<Loader2 className="h-3 w-3 animate-spin" />
...
</div>
) : (
<Select <Select
value={config.autoGeneration?.options?.numberingRuleId || ""} value={config.autoGeneration?.options?.numberingRuleId || ""}
onValueChange={(value) => { onValueChange={(value) => {
@ -363,10 +372,9 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
}, },
}); });
}} }}
disabled={loadingRules}
> >
<SelectTrigger className="h-7 text-xs"> <SelectTrigger className="h-8 text-sm">
<SelectValue placeholder={loadingRules ? "로딩 중..." : "규칙 선택"} /> <SelectValue placeholder="규칙 선택" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{numberingRules.length === 0 ? ( {numberingRules.length === 0 ? (
@ -382,24 +390,23 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
)} )}
</SelectContent> </SelectContent>
</Select> </Select>
</div> )}
</div> </div>
) : ( ) : (
<div className="rounded-md border border-amber-200 bg-amber-50 p-2 text-[10px] text-amber-800"> <div className="rounded-md border border-amber-200 bg-amber-50 p-2.5 text-xs text-amber-800">
</div> </div>
)} )}
</div> </div>
)} )}
{/* 자동생성 옵션 (랜덤/순차용) */} {/* 랜덤/순차 옵션 */}
{config.autoGeneration?.type && {config.autoGeneration?.type &&
["random_string", "random_number", "sequence"].includes(config.autoGeneration.type) && ( ["random_string", "random_number", "sequence"].includes(config.autoGeneration.type) && (
<div className="mt-1 space-y-1"> <div className="space-y-3">
{["random_string", "random_number"].includes(config.autoGeneration.type) && ( {["random_string", "random_number"].includes(config.autoGeneration.type) && (
<div className="flex items-center justify-between py-1.5"> <div className="flex items-center justify-between py-1">
<span className="text-xs text-muted-foreground"></span> <span className="text-xs text-muted-foreground"></span>
<div className="w-[140px]">
<Input <Input
type="number" type="number"
min="1" min="1"
@ -414,15 +421,13 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
}, },
}); });
}} }}
className="h-7 text-xs" className="h-7 w-[120px] text-xs"
/> />
</div> </div>
</div>
)} )}
<div className="flex items-center justify-between py-1.5"> <div className="flex items-center justify-between py-1">
<span className="text-xs text-muted-foreground"></span> <span className="text-xs text-muted-foreground"></span>
<div className="w-[140px]">
<Input <Input
value={config.autoGeneration?.options?.prefix || ""} value={config.autoGeneration?.options?.prefix || ""}
onChange={(e) => { onChange={(e) => {
@ -435,14 +440,12 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
}); });
}} }}
placeholder="예: INV-" placeholder="예: INV-"
className="h-7 text-xs" className="h-7 w-[120px] text-xs"
/> />
</div> </div>
</div>
<div className="flex items-center justify-between py-1.5"> <div className="flex items-center justify-between py-1">
<span className="text-xs text-muted-foreground"></span> <span className="text-xs text-muted-foreground"></span>
<div className="w-[140px]">
<Input <Input
value={config.autoGeneration?.options?.suffix || ""} value={config.autoGeneration?.options?.suffix || ""}
onChange={(e) => { onChange={(e) => {
@ -454,14 +457,13 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
}, },
}); });
}} }}
className="h-7 text-xs" className="h-7 w-[120px] text-xs"
/> />
</div> </div>
</div>
<div className="py-1.5"> <div>
<span className="text-[10px] text-muted-foreground"></span> <span className="text-xs text-muted-foreground"></span>
<div className="mt-1 rounded border bg-muted p-1.5 text-xs font-mono"> <div className="mt-1 rounded-md border bg-muted p-2 text-xs font-mono">
{AutoGenerationUtils.generatePreviewValue(config.autoGeneration)} {AutoGenerationUtils.generatePreviewValue(config.autoGeneration)}
</div> </div>
</div> </div>
@ -470,6 +472,8 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
</div> </div>
)} )}
</div> </div>
</CollapsibleContent>
</Collapsible>
</> </>
)} )}
</div> </div>