[agent-pipeline] pipe-20260311071246-rhvz round-3
This commit is contained in:
parent
d4f2a3cf04
commit
d1d5f651cc
|
|
@ -9,7 +9,6 @@ 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 { Separator } from "@/components/ui/separator";
|
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { AutoGenerationType, AutoGenerationConfig } from "@/types/screen";
|
import { AutoGenerationType, AutoGenerationConfig } from "@/types/screen";
|
||||||
import { AutoGenerationUtils } from "@/lib/utils/autoGeneration";
|
import { AutoGenerationUtils } from "@/lib/utils/autoGeneration";
|
||||||
|
|
@ -19,44 +18,33 @@ import { NumberingRuleConfig } from "@/types/numbering-rule";
|
||||||
interface V2InputConfigPanelProps {
|
interface V2InputConfigPanelProps {
|
||||||
config: Record<string, any>;
|
config: Record<string, any>;
|
||||||
onChange: (config: Record<string, any>) => void;
|
onChange: (config: Record<string, any>) => void;
|
||||||
menuObjid?: number; // 메뉴 OBJID (채번 규칙 필터링용)
|
menuObjid?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config, onChange, menuObjid }) => {
|
export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config, onChange, menuObjid }) => {
|
||||||
// 채번 규칙 목록 상태
|
|
||||||
const [numberingRules, setNumberingRules] = useState<NumberingRuleConfig[]>([]);
|
const [numberingRules, setNumberingRules] = useState<NumberingRuleConfig[]>([]);
|
||||||
const [loadingRules, setLoadingRules] = useState(false);
|
const [loadingRules, setLoadingRules] = useState(false);
|
||||||
|
|
||||||
// 부모 메뉴 목록 상태 (채번규칙 사용을 위한 선택)
|
|
||||||
const [parentMenus, setParentMenus] = useState<any[]>([]);
|
const [parentMenus, setParentMenus] = useState<any[]>([]);
|
||||||
const [loadingMenus, setLoadingMenus] = useState(false);
|
const [loadingMenus, setLoadingMenus] = useState(false);
|
||||||
|
|
||||||
// 선택된 메뉴 OBJID
|
|
||||||
const [selectedMenuObjid, setSelectedMenuObjid] = useState<number | undefined>(() => {
|
const [selectedMenuObjid, setSelectedMenuObjid] = useState<number | undefined>(() => {
|
||||||
return config.autoGeneration?.selectedMenuObjid || menuObjid;
|
return config.autoGeneration?.selectedMenuObjid || menuObjid;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 설정 업데이트 핸들러
|
|
||||||
const updateConfig = (field: string, value: any) => {
|
const updateConfig = (field: string, value: any) => {
|
||||||
onChange({ ...config, [field]: value });
|
onChange({ ...config, [field]: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
// 부모 메뉴 목록 로드 (사용자 메뉴의 레벨 2만)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadMenus = async () => {
|
const loadMenus = async () => {
|
||||||
setLoadingMenus(true);
|
setLoadingMenus(true);
|
||||||
try {
|
try {
|
||||||
const { apiClient } = await import("@/lib/api/client");
|
const { apiClient } = await import("@/lib/api/client");
|
||||||
const response = await apiClient.get("/admin/menus");
|
const response = await apiClient.get("/admin/menus");
|
||||||
|
|
||||||
if (response.data.success && response.data.data) {
|
if (response.data.success && response.data.data) {
|
||||||
const allMenus = response.data.data;
|
const allMenus = response.data.data;
|
||||||
|
|
||||||
// 사용자 메뉴(menu_type='1')의 레벨 2만 필터링
|
|
||||||
const level2UserMenus = allMenus.filter((menu: any) =>
|
const level2UserMenus = allMenus.filter((menu: any) =>
|
||||||
menu.menu_type === '1' && menu.lev === 2
|
menu.menu_type === '1' && menu.lev === 2
|
||||||
);
|
);
|
||||||
|
|
||||||
setParentMenus(level2UserMenus);
|
setParentMenus(level2UserMenus);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -68,22 +56,13 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
|
||||||
loadMenus();
|
loadMenus();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 채번 규칙 목록 로드 (선택된 메뉴 기준)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadRules = async () => {
|
const loadRules = async () => {
|
||||||
if (config.autoGeneration?.type !== "numbering_rule") {
|
if (config.autoGeneration?.type !== "numbering_rule") return;
|
||||||
return;
|
if (!selectedMenuObjid) { setNumberingRules([]); return; }
|
||||||
}
|
|
||||||
|
|
||||||
if (!selectedMenuObjid) {
|
|
||||||
setNumberingRules([]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoadingRules(true);
|
setLoadingRules(true);
|
||||||
try {
|
try {
|
||||||
const response = await getAvailableNumberingRules(selectedMenuObjid);
|
const response = await getAvailableNumberingRules(selectedMenuObjid);
|
||||||
|
|
||||||
if (response.success && response.data) {
|
if (response.success && response.data) {
|
||||||
setNumberingRules(response.data);
|
setNumberingRules(response.data);
|
||||||
}
|
}
|
||||||
|
|
@ -94,61 +73,56 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
|
||||||
setLoadingRules(false);
|
setLoadingRules(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadRules();
|
loadRules();
|
||||||
}, [selectedMenuObjid, config.autoGeneration?.type]);
|
}, [selectedMenuObjid, config.autoGeneration?.type]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-1">
|
||||||
{/* 입력 타입 */}
|
{/* INPUT TYPE 섹션 */}
|
||||||
<div className="space-y-2">
|
<div className="border-b border-border/50 pb-3 mb-3">
|
||||||
<Label className="text-xs font-medium">입력 타입</Label>
|
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">INPUT TYPE</h4>
|
||||||
<Select
|
<div className="flex items-center justify-between py-1.5">
|
||||||
value={config.inputType || config.type || "text"}
|
<span className="text-xs text-muted-foreground">입력 타입</span>
|
||||||
onValueChange={(value) => updateConfig("inputType", value)}
|
<div className="w-[140px]">
|
||||||
>
|
<Select
|
||||||
<SelectTrigger className="h-8 text-xs">
|
value={config.inputType || config.type || "text"}
|
||||||
<SelectValue placeholder="입력 타입 선택" />
|
onValueChange={(value) => updateConfig("inputType", value)}
|
||||||
</SelectTrigger>
|
>
|
||||||
<SelectContent>
|
<SelectTrigger className="h-7 text-xs">
|
||||||
<SelectItem value="text">텍스트</SelectItem>
|
<SelectValue placeholder="입력 타입 선택" />
|
||||||
<SelectItem value="number">숫자</SelectItem>
|
</SelectTrigger>
|
||||||
<SelectItem value="password">비밀번호</SelectItem>
|
<SelectContent>
|
||||||
<SelectItem value="textarea">여러 줄 텍스트</SelectItem>
|
<SelectItem value="text">텍스트</SelectItem>
|
||||||
<SelectItem value="slider">슬라이더</SelectItem>
|
<SelectItem value="number">숫자</SelectItem>
|
||||||
<SelectItem value="color">색상 선택</SelectItem>
|
<SelectItem value="password">비밀번호</SelectItem>
|
||||||
<SelectItem value="numbering">채번 (자동생성)</SelectItem>
|
<SelectItem value="textarea">여러 줄 텍스트</SelectItem>
|
||||||
</SelectContent>
|
<SelectItem value="slider">슬라이더</SelectItem>
|
||||||
</Select>
|
<SelectItem value="color">색상 선택</SelectItem>
|
||||||
|
<SelectItem value="numbering">채번 (자동생성)</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 채번 타입 전용 설정 */}
|
{/* NUMBERING 섹션 - 채번 타입 전용 */}
|
||||||
{config.inputType === "numbering" && (
|
{config.inputType === "numbering" && (
|
||||||
<div className="space-y-3">
|
<div className="border-b border-border/50 pb-3 mb-3">
|
||||||
<Separator />
|
<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/10 p-3">
|
<div className="rounded-md border border-primary/20 bg-primary/5 p-2 mb-2">
|
||||||
<p className="text-xs font-medium text-primary">채번 타입 안내</p>
|
<p className="text-[10px] text-primary">
|
||||||
<p className="mt-1 text-[10px] text-primary">
|
|
||||||
채번 규칙은 <strong>테이블 관리</strong>에서 컬럼별로 설정됩니다.
|
채번 규칙은 <strong>테이블 관리</strong>에서 컬럼별로 설정됩니다.
|
||||||
<br />
|
|
||||||
화면에 배치된 컬럼의 채번 규칙이 자동으로 적용됩니다.
|
화면에 배치된 컬럼의 채번 규칙이 자동으로 적용됩니다.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center justify-between py-1.5">
|
||||||
{/* 채번 필드는 기본적으로 읽기전용 */}
|
<span className="text-xs text-muted-foreground">읽기전용 (권장)</span>
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="numberingReadonly"
|
|
||||||
checked={config.readonly !== false}
|
checked={config.readonly !== false}
|
||||||
onCheckedChange={(checked) => {
|
onCheckedChange={(checked) => updateConfig("readonly", checked)}
|
||||||
updateConfig("readonly", checked);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Label htmlFor="numberingReadonly" className="text-xs font-medium cursor-pointer">
|
|
||||||
읽기전용 (권장)
|
|
||||||
</Label>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-muted-foreground text-[10px] pl-6">
|
<p className="text-muted-foreground text-[10px] mt-0.5">
|
||||||
채번 필드는 자동으로 생성되므로 읽기전용을 권장합니다
|
채번 필드는 자동으로 생성되므로 읽기전용을 권장합니다
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -157,327 +131,345 @@ export const V2InputConfigPanel: React.FC<V2InputConfigPanelProps> = ({ config,
|
||||||
{/* 채번 타입이 아닌 경우에만 추가 설정 표시 */}
|
{/* 채번 타입이 아닌 경우에만 추가 설정 표시 */}
|
||||||
{config.inputType !== "numbering" && (
|
{config.inputType !== "numbering" && (
|
||||||
<>
|
<>
|
||||||
<Separator />
|
{/* FORMAT 섹션 */}
|
||||||
|
|
||||||
{/* 형식 (텍스트/숫자용) */}
|
|
||||||
{(config.inputType === "text" || !config.inputType) && (
|
{(config.inputType === "text" || !config.inputType) && (
|
||||||
<div className="space-y-2">
|
<div className="border-b border-border/50 pb-3 mb-3">
|
||||||
<Label className="text-xs font-medium">입력 형식</Label>
|
<h4 className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground py-2">FORMAT</h4>
|
||||||
<Select value={config.format || "none"} onValueChange={(value) => updateConfig("format", value)}>
|
<div className="flex items-center justify-between py-1.5">
|
||||||
<SelectTrigger className="h-8 text-xs">
|
<span className="text-xs text-muted-foreground">입력 형식</span>
|
||||||
<SelectValue placeholder="형식 선택" />
|
<div className="w-[140px]">
|
||||||
</SelectTrigger>
|
<Select value={config.format || "none"} onValueChange={(value) => updateConfig("format", value)}>
|
||||||
<SelectContent>
|
<SelectTrigger className="h-7 text-xs">
|
||||||
<SelectItem value="none">제한 없음</SelectItem>
|
<SelectValue placeholder="형식 선택" />
|
||||||
<SelectItem value="email">이메일</SelectItem>
|
|
||||||
<SelectItem value="tel">전화번호</SelectItem>
|
|
||||||
<SelectItem value="url">URL</SelectItem>
|
|
||||||
<SelectItem value="currency">통화</SelectItem>
|
|
||||||
<SelectItem value="biz_no">사업자번호</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 플레이스홀더 */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs font-medium">플레이스홀더</Label>
|
|
||||||
<Input
|
|
||||||
value={config.placeholder || ""}
|
|
||||||
onChange={(e) => updateConfig("placeholder", e.target.value)}
|
|
||||||
placeholder="입력 안내 텍스트"
|
|
||||||
className="h-8 text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 숫자/슬라이더 전용 설정 */}
|
|
||||||
{(config.inputType === "number" || config.inputType === "slider") && (
|
|
||||||
<>
|
|
||||||
<Separator />
|
|
||||||
<div className="grid grid-cols-3 gap-2">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs font-medium">최소값</Label>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
value={config.min ?? ""}
|
|
||||||
onChange={(e) => updateConfig("min", e.target.value ? Number(e.target.value) : undefined)}
|
|
||||||
placeholder="0"
|
|
||||||
className="h-8 text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs font-medium">최대값</Label>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
value={config.max ?? ""}
|
|
||||||
onChange={(e) => updateConfig("max", e.target.value ? Number(e.target.value) : undefined)}
|
|
||||||
placeholder="100"
|
|
||||||
className="h-8 text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs font-medium">단계</Label>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
value={config.step ?? ""}
|
|
||||||
onChange={(e) => updateConfig("step", e.target.value ? Number(e.target.value) : undefined)}
|
|
||||||
placeholder="1"
|
|
||||||
className="h-8 text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 여러 줄 텍스트 전용 설정 */}
|
|
||||||
{config.inputType === "textarea" && (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs font-medium">줄 수</Label>
|
|
||||||
<Input
|
|
||||||
type="number"
|
|
||||||
value={config.rows || 3}
|
|
||||||
onChange={(e) => updateConfig("rows", parseInt(e.target.value) || 3)}
|
|
||||||
min={2}
|
|
||||||
max={20}
|
|
||||||
className="h-8 text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 마스크 입력 (선택) */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs font-medium">입력 마스크 (선택)</Label>
|
|
||||||
<Input
|
|
||||||
value={config.mask || ""}
|
|
||||||
onChange={(e) => updateConfig("mask", e.target.value)}
|
|
||||||
placeholder="예: ###-####-####"
|
|
||||||
className="h-8 text-xs"
|
|
||||||
/>
|
|
||||||
<p className="text-muted-foreground text-[10px]"># = 숫자, A = 문자, * = 모든 문자</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
{/* 자동생성 기능 */}
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id="autoGenerationEnabled"
|
|
||||||
checked={config.autoGeneration?.enabled || false}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
const currentConfig = config.autoGeneration || { type: "none", enabled: false };
|
|
||||||
updateConfig("autoGeneration", {
|
|
||||||
...currentConfig,
|
|
||||||
enabled: checked as boolean,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Label htmlFor="autoGenerationEnabled" className="text-xs font-medium cursor-pointer">
|
|
||||||
자동생성 활성화
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 자동생성 타입 선택 */}
|
|
||||||
{config.autoGeneration?.enabled && (
|
|
||||||
<div className="space-y-3 pl-6">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs font-medium">자동생성 타입</Label>
|
|
||||||
<Select
|
|
||||||
value={config.autoGeneration?.type || "none"}
|
|
||||||
onValueChange={(value: AutoGenerationType) => {
|
|
||||||
const currentConfig = config.autoGeneration || { type: "none", enabled: false };
|
|
||||||
updateConfig("autoGeneration", {
|
|
||||||
...currentConfig,
|
|
||||||
type: value,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="h-8 text-xs">
|
|
||||||
<SelectValue placeholder="자동생성 타입 선택" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="none">자동생성 없음</SelectItem>
|
|
||||||
<SelectItem value="uuid">UUID 생성</SelectItem>
|
|
||||||
<SelectItem value="current_user">현재 사용자 ID</SelectItem>
|
|
||||||
<SelectItem value="current_time">현재 시간</SelectItem>
|
|
||||||
<SelectItem value="sequence">순차 번호</SelectItem>
|
|
||||||
<SelectItem value="numbering_rule">채번 규칙</SelectItem>
|
|
||||||
<SelectItem value="random_string">랜덤 문자열</SelectItem>
|
|
||||||
<SelectItem value="random_number">랜덤 숫자</SelectItem>
|
|
||||||
<SelectItem value="company_code">회사 코드</SelectItem>
|
|
||||||
<SelectItem value="department">부서 코드</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
{/* 선택된 타입 설명 */}
|
|
||||||
{config.autoGeneration?.type && config.autoGeneration.type !== "none" && (
|
|
||||||
<p className="text-muted-foreground text-[10px]">
|
|
||||||
{AutoGenerationUtils.getTypeDescription(config.autoGeneration.type)}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 채번 규칙 선택 */}
|
|
||||||
{config.autoGeneration?.type === "numbering_rule" && (
|
|
||||||
<>
|
|
||||||
{/* 부모 메뉴 선택 */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label className="text-xs font-medium">
|
|
||||||
대상 메뉴 <span className="text-destructive">*</span>
|
|
||||||
</Label>
|
|
||||||
<Select
|
|
||||||
value={selectedMenuObjid?.toString() || ""}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
const menuId = parseInt(value);
|
|
||||||
setSelectedMenuObjid(menuId);
|
|
||||||
|
|
||||||
updateConfig("autoGeneration", {
|
|
||||||
...config.autoGeneration,
|
|
||||||
selectedMenuObjid: menuId,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
disabled={loadingMenus}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="h-8 text-xs">
|
|
||||||
<SelectValue placeholder={loadingMenus ? "메뉴 로딩 중..." : "채번규칙을 사용할 메뉴 선택"} />
|
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{parentMenus.length === 0 ? (
|
<SelectItem value="none">제한 없음</SelectItem>
|
||||||
<SelectItem value="no-menus" disabled>
|
<SelectItem value="email">이메일</SelectItem>
|
||||||
사용 가능한 메뉴가 없습니다
|
<SelectItem value="tel">전화번호</SelectItem>
|
||||||
</SelectItem>
|
<SelectItem value="url">URL</SelectItem>
|
||||||
) : (
|
<SelectItem value="currency">통화</SelectItem>
|
||||||
parentMenus.map((menu) => (
|
<SelectItem value="biz_no">사업자번호</SelectItem>
|
||||||
<SelectItem key={menu.objid} value={menu.objid.toString()}>
|
|
||||||
{menu.menu_name_kor}
|
|
||||||
</SelectItem>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 채번 규칙 선택 */}
|
{/* PLACEHOLDER 섹션 */}
|
||||||
{selectedMenuObjid ? (
|
<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">PLACEHOLDER</h4>
|
||||||
<Label className="text-xs font-medium">
|
<div className="flex items-center justify-between py-1.5">
|
||||||
채번 규칙 <span className="text-destructive">*</span>
|
<span className="text-xs text-muted-foreground">안내 텍스트</span>
|
||||||
</Label>
|
<div className="w-[140px]">
|
||||||
|
<Input
|
||||||
|
value={config.placeholder || ""}
|
||||||
|
onChange={(e) => updateConfig("placeholder", e.target.value)}
|
||||||
|
placeholder="입력 안내"
|
||||||
|
className="h-7 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* RANGE 섹션 - 숫자/슬라이더 전용 */}
|
||||||
|
{(config.inputType === "number" || config.inputType === "slider") && (
|
||||||
|
<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">RANGE</h4>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex-1">
|
||||||
|
<Label className="text-[10px] text-muted-foreground">최소값</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={config.min ?? ""}
|
||||||
|
onChange={(e) => updateConfig("min", e.target.value ? Number(e.target.value) : undefined)}
|
||||||
|
placeholder="0"
|
||||||
|
className="h-7 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<Label className="text-[10px] text-muted-foreground">최대값</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={config.max ?? ""}
|
||||||
|
onChange={(e) => updateConfig("max", e.target.value ? Number(e.target.value) : undefined)}
|
||||||
|
placeholder="100"
|
||||||
|
className="h-7 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<Label className="text-[10px] text-muted-foreground">단계</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={config.step ?? ""}
|
||||||
|
onChange={(e) => updateConfig("step", e.target.value ? Number(e.target.value) : undefined)}
|
||||||
|
placeholder="1"
|
||||||
|
className="h-7 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* TEXTAREA 섹션 */}
|
||||||
|
{config.inputType === "textarea" && (
|
||||||
|
<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">TEXTAREA</h4>
|
||||||
|
<div className="flex items-center justify-between py-1.5">
|
||||||
|
<span className="text-xs text-muted-foreground">줄 수</span>
|
||||||
|
<div className="w-[140px]">
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={config.rows || 3}
|
||||||
|
onChange={(e) => updateConfig("rows", parseInt(e.target.value) || 3)}
|
||||||
|
min={2}
|
||||||
|
max={20}
|
||||||
|
className="h-7 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* INPUT MASK 섹션 */}
|
||||||
|
<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">INPUT MASK</h4>
|
||||||
|
<div className="flex items-center justify-between py-1.5">
|
||||||
|
<span className="text-xs text-muted-foreground">마스크</span>
|
||||||
|
<div className="w-[140px]">
|
||||||
|
<Input
|
||||||
|
value={config.mask || ""}
|
||||||
|
onChange={(e) => updateConfig("mask", e.target.value)}
|
||||||
|
placeholder="###-####-####"
|
||||||
|
className="h-7 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
const currentConfig = config.autoGeneration || { type: "none", enabled: false };
|
||||||
|
updateConfig("autoGeneration", {
|
||||||
|
...currentConfig,
|
||||||
|
enabled: checked as boolean,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{config.autoGeneration?.enabled && (
|
||||||
|
<div className="mt-1 space-y-1">
|
||||||
|
{/* 자동생성 타입 */}
|
||||||
|
<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.autoGeneration?.options?.numberingRuleId || ""}
|
value={config.autoGeneration?.type || "none"}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value: AutoGenerationType) => {
|
||||||
|
const currentConfig = config.autoGeneration || { type: "none", enabled: false };
|
||||||
updateConfig("autoGeneration", {
|
updateConfig("autoGeneration", {
|
||||||
...config.autoGeneration,
|
...currentConfig,
|
||||||
options: {
|
type: value,
|
||||||
...config.autoGeneration?.options,
|
|
||||||
numberingRuleId: value,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
disabled={loadingRules}
|
|
||||||
>
|
>
|
||||||
<SelectTrigger className="h-8 text-xs">
|
<SelectTrigger className="h-7 text-xs">
|
||||||
<SelectValue placeholder={loadingRules ? "규칙 로딩 중..." : "채번 규칙 선택"} />
|
<SelectValue placeholder="자동생성 타입 선택" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{numberingRules.length === 0 ? (
|
<SelectItem value="none">자동생성 없음</SelectItem>
|
||||||
<SelectItem value="no-rules" disabled>
|
<SelectItem value="uuid">UUID 생성</SelectItem>
|
||||||
사용 가능한 규칙이 없습니다
|
<SelectItem value="current_user">현재 사용자 ID</SelectItem>
|
||||||
</SelectItem>
|
<SelectItem value="current_time">현재 시간</SelectItem>
|
||||||
) : (
|
<SelectItem value="sequence">순차 번호</SelectItem>
|
||||||
numberingRules.map((rule) => (
|
<SelectItem value="numbering_rule">채번 규칙</SelectItem>
|
||||||
<SelectItem key={rule.ruleId} value={rule.ruleId}>
|
<SelectItem value="random_string">랜덤 문자열</SelectItem>
|
||||||
{rule.ruleName}
|
<SelectItem value="random_number">랜덤 숫자</SelectItem>
|
||||||
</SelectItem>
|
<SelectItem value="company_code">회사 코드</SelectItem>
|
||||||
))
|
<SelectItem value="department">부서 코드</SelectItem>
|
||||||
)}
|
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</div>
|
||||||
<div className="rounded-md border border-amber-200 bg-amber-50 p-2 text-xs text-amber-800">
|
|
||||||
먼저 대상 메뉴를 선택하세요
|
{config.autoGeneration?.type && config.autoGeneration.type !== "none" && (
|
||||||
|
<p className="text-muted-foreground text-[10px] mt-0.5">
|
||||||
|
{AutoGenerationUtils.getTypeDescription(config.autoGeneration.type)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 채번 규칙 선택 */}
|
||||||
|
{config.autoGeneration?.type === "numbering_rule" && (
|
||||||
|
<div className="mt-1 space-y-1">
|
||||||
|
<div className="flex items-center justify-between py-1.5">
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
대상 메뉴 <span className="text-destructive">*</span>
|
||||||
|
</span>
|
||||||
|
<div className="w-[140px]">
|
||||||
|
<Select
|
||||||
|
value={selectedMenuObjid?.toString() || ""}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
const menuId = parseInt(value);
|
||||||
|
setSelectedMenuObjid(menuId);
|
||||||
|
updateConfig("autoGeneration", {
|
||||||
|
...config.autoGeneration,
|
||||||
|
selectedMenuObjid: menuId,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={loadingMenus}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="h-7 text-xs">
|
||||||
|
<SelectValue placeholder={loadingMenus ? "로딩 중..." : "메뉴 선택"} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{parentMenus.length === 0 ? (
|
||||||
|
<SelectItem value="no-menus" disabled>
|
||||||
|
사용 가능한 메뉴가 없습니다
|
||||||
|
</SelectItem>
|
||||||
|
) : (
|
||||||
|
parentMenus.map((menu) => (
|
||||||
|
<SelectItem key={menu.objid} value={menu.objid.toString()}>
|
||||||
|
{menu.menu_name_kor}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{selectedMenuObjid ? (
|
||||||
|
<div className="flex items-center justify-between py-1.5">
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
채번 규칙 <span className="text-destructive">*</span>
|
||||||
|
</span>
|
||||||
|
<div className="w-[140px]">
|
||||||
|
<Select
|
||||||
|
value={config.autoGeneration?.options?.numberingRuleId || ""}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
updateConfig("autoGeneration", {
|
||||||
|
...config.autoGeneration,
|
||||||
|
options: {
|
||||||
|
...config.autoGeneration?.options,
|
||||||
|
numberingRuleId: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={loadingRules}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="h-7 text-xs">
|
||||||
|
<SelectValue placeholder={loadingRules ? "로딩 중..." : "규칙 선택"} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{numberingRules.length === 0 ? (
|
||||||
|
<SelectItem value="no-rules" disabled>
|
||||||
|
사용 가능한 규칙이 없습니다
|
||||||
|
</SelectItem>
|
||||||
|
) : (
|
||||||
|
numberingRules.map((rule) => (
|
||||||
|
<SelectItem key={rule.ruleId} value={rule.ruleId}>
|
||||||
|
{rule.ruleName}
|
||||||
|
</SelectItem>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="rounded-md border border-amber-200 bg-amber-50 p-2 text-[10px] text-amber-800">
|
||||||
|
먼저 대상 메뉴를 선택하세요
|
||||||
|
</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="space-y-2">
|
<div className="mt-1 space-y-1">
|
||||||
{/* 길이 설정 */}
|
{["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="space-y-1">
|
<span className="text-xs text-muted-foreground">길이</span>
|
||||||
<Label className="text-xs font-medium">길이</Label>
|
<div className="w-[140px]">
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
max="50"
|
max="50"
|
||||||
value={config.autoGeneration?.options?.length || 8}
|
value={config.autoGeneration?.options?.length || 8}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
updateConfig("autoGeneration", {
|
updateConfig("autoGeneration", {
|
||||||
...config.autoGeneration,
|
...config.autoGeneration,
|
||||||
options: {
|
options: {
|
||||||
...config.autoGeneration?.options,
|
...config.autoGeneration?.options,
|
||||||
length: parseInt(e.target.value) || 8,
|
length: parseInt(e.target.value) || 8,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className="h-8 text-xs"
|
className="h-7 text-xs"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between py-1.5">
|
||||||
|
<span className="text-xs text-muted-foreground">접두사</span>
|
||||||
|
<div className="w-[140px]">
|
||||||
|
<Input
|
||||||
|
value={config.autoGeneration?.options?.prefix || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
updateConfig("autoGeneration", {
|
||||||
|
...config.autoGeneration,
|
||||||
|
options: {
|
||||||
|
...config.autoGeneration?.options,
|
||||||
|
prefix: e.target.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
placeholder="예: INV-"
|
||||||
|
className="h-7 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between py-1.5">
|
||||||
|
<span className="text-xs text-muted-foreground">접미사</span>
|
||||||
|
<div className="w-[140px]">
|
||||||
|
<Input
|
||||||
|
value={config.autoGeneration?.options?.suffix || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
updateConfig("autoGeneration", {
|
||||||
|
...config.autoGeneration,
|
||||||
|
options: {
|
||||||
|
...config.autoGeneration?.options,
|
||||||
|
suffix: e.target.value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="h-7 text-xs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="py-1.5">
|
||||||
|
<span className="text-[10px] text-muted-foreground">미리보기</span>
|
||||||
|
<div className="mt-1 rounded border bg-muted p-1.5 text-xs font-mono">
|
||||||
|
{AutoGenerationUtils.generatePreviewValue(config.autoGeneration)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
{/* 접두사 */}
|
)}
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs font-medium">접두사</Label>
|
|
||||||
<Input
|
|
||||||
value={config.autoGeneration?.options?.prefix || ""}
|
|
||||||
onChange={(e) => {
|
|
||||||
updateConfig("autoGeneration", {
|
|
||||||
...config.autoGeneration,
|
|
||||||
options: {
|
|
||||||
...config.autoGeneration?.options,
|
|
||||||
prefix: e.target.value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
placeholder="예: INV-"
|
|
||||||
className="h-8 text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 접미사 */}
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs font-medium">접미사</Label>
|
|
||||||
<Input
|
|
||||||
value={config.autoGeneration?.options?.suffix || ""}
|
|
||||||
onChange={(e) => {
|
|
||||||
updateConfig("autoGeneration", {
|
|
||||||
...config.autoGeneration,
|
|
||||||
options: {
|
|
||||||
...config.autoGeneration?.options,
|
|
||||||
suffix: e.target.value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
className="h-8 text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 미리보기 */}
|
|
||||||
<div className="space-y-1">
|
|
||||||
<Label className="text-xs font-medium">미리보기</Label>
|
|
||||||
<div className="rounded border bg-muted p-2 text-xs font-mono">
|
|
||||||
{AutoGenerationUtils.generatePreviewValue(config.autoGeneration)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue