Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into lhj
; Please enter a commit message to explain why this merge is necessary, ; especially if it merges an updated upstream into a topic branch. ; ; Lines starting with ';' will be ignored, and an empty message aborts ; the commit.
This commit is contained in:
@@ -109,9 +109,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
if (!prev) return null;
|
||||
return {
|
||||
...prev,
|
||||
parts: prev.parts
|
||||
.filter((part) => part.id !== partId)
|
||||
.map((part, index) => ({ ...part, order: index + 1 })),
|
||||
parts: prev.parts.filter((part) => part.id !== partId).map((part, index) => ({ ...part, order: index + 1 })),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -132,7 +130,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
setLoading(true);
|
||||
try {
|
||||
const existing = savedRules.find((r) => r.ruleId === currentRule.ruleId);
|
||||
|
||||
|
||||
let response;
|
||||
if (existing) {
|
||||
response = await updateNumberingRule(currentRule.ruleId, currentRule);
|
||||
@@ -170,29 +168,32 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
toast.info(`"${rule.ruleName}" 규칙을 불러왔습니다`);
|
||||
}, []);
|
||||
|
||||
const handleDeleteSavedRule = useCallback(async (ruleId: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await deleteNumberingRule(ruleId);
|
||||
|
||||
if (response.success) {
|
||||
setSavedRules((prev) => prev.filter((r) => r.ruleId !== ruleId));
|
||||
|
||||
if (selectedRuleId === ruleId) {
|
||||
setSelectedRuleId(null);
|
||||
setCurrentRule(null);
|
||||
const handleDeleteSavedRule = useCallback(
|
||||
async (ruleId: string) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await deleteNumberingRule(ruleId);
|
||||
|
||||
if (response.success) {
|
||||
setSavedRules((prev) => prev.filter((r) => r.ruleId !== ruleId));
|
||||
|
||||
if (selectedRuleId === ruleId) {
|
||||
setSelectedRuleId(null);
|
||||
setCurrentRule(null);
|
||||
}
|
||||
|
||||
toast.success("규칙이 삭제되었습니다");
|
||||
} else {
|
||||
toast.error(response.error || "삭제 실패");
|
||||
}
|
||||
|
||||
toast.success("규칙이 삭제되었습니다");
|
||||
} else {
|
||||
toast.error(response.error || "삭제 실패");
|
||||
} catch (error: any) {
|
||||
toast.error(`삭제 실패: ${error.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(`삭제 실패: ${error.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [selectedRuleId]);
|
||||
},
|
||||
[selectedRuleId],
|
||||
);
|
||||
|
||||
const handleNewRule = useCallback(() => {
|
||||
const newRule: NumberingRuleConfig = {
|
||||
@@ -207,7 +208,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
|
||||
setSelectedRuleId(newRule.ruleId);
|
||||
setCurrentRule(newRule);
|
||||
|
||||
|
||||
toast.success("새 규칙이 생성되었습니다");
|
||||
}, []);
|
||||
|
||||
@@ -228,35 +229,29 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
) : (
|
||||
<h2 className="text-sm font-semibold sm:text-base">{leftTitle}</h2>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6"
|
||||
onClick={() => setEditingLeftTitle(true)}
|
||||
>
|
||||
<Button variant="ghost" size="icon" className="h-6 w-6" onClick={() => setEditingLeftTitle(true)}>
|
||||
<Edit2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button onClick={handleNewRule} variant="outline" className="h-9 w-full text-sm">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
새 규칙 생성
|
||||
<Plus className="mr-2 h-4 w-4" />새 규칙 생성
|
||||
</Button>
|
||||
|
||||
<div className="flex-1 space-y-2 overflow-y-auto">
|
||||
{loading ? (
|
||||
<div className="flex h-32 items-center justify-center">
|
||||
<p className="text-xs text-muted-foreground">로딩 중...</p>
|
||||
<p className="text-muted-foreground text-xs">로딩 중...</p>
|
||||
</div>
|
||||
) : savedRules.length === 0 ? (
|
||||
<div className="flex h-32 items-center justify-center rounded-lg border border-dashed border-border bg-muted/50">
|
||||
<p className="text-xs text-muted-foreground">저장된 규칙이 없습니다</p>
|
||||
<div className="border-border bg-muted/50 flex h-32 items-center justify-center rounded-lg border border-dashed">
|
||||
<p className="text-muted-foreground text-xs">저장된 규칙이 없습니다</p>
|
||||
</div>
|
||||
) : (
|
||||
savedRules.map((rule) => (
|
||||
<Card
|
||||
key={rule.ruleId}
|
||||
className={`cursor-pointer border-border transition-colors hover:bg-accent ${
|
||||
className={`border-border hover:bg-accent cursor-pointer transition-colors ${
|
||||
selectedRuleId === rule.ruleId ? "border-primary bg-primary/5" : "bg-card"
|
||||
}`}
|
||||
onClick={() => handleSelectRule(rule)}
|
||||
@@ -265,9 +260,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<CardTitle className="text-sm font-medium">{rule.ruleName}</CardTitle>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
규칙 {rule.parts.length}개
|
||||
</p>
|
||||
<p className="text-muted-foreground mt-1 text-xs">규칙 {rule.parts.length}개</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -278,7 +271,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
handleDeleteSavedRule(rule.ruleId);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="h-3 w-3 text-destructive" />
|
||||
<Trash2 className="text-destructive h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
@@ -292,19 +285,15 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
</div>
|
||||
|
||||
{/* 구분선 */}
|
||||
<div className="h-full w-px bg-border"></div>
|
||||
<div className="bg-border h-full w-px"></div>
|
||||
|
||||
{/* 우측: 편집 영역 */}
|
||||
<div className="flex flex-1 flex-col gap-4">
|
||||
{!currentRule ? (
|
||||
<div className="flex h-full flex-col items-center justify-center">
|
||||
<div className="text-center">
|
||||
<p className="mb-2 text-lg font-medium text-muted-foreground">
|
||||
규칙을 선택해주세요
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
좌측에서 규칙을 선택하거나 새로 생성하세요
|
||||
</p>
|
||||
<p className="text-muted-foreground mb-2 text-lg font-medium">규칙을 선택해주세요</p>
|
||||
<p className="text-muted-foreground text-sm">좌측에서 규칙을 선택하거나 새로 생성하세요</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -322,12 +311,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
) : (
|
||||
<h2 className="text-sm font-semibold sm:text-base">{rightTitle}</h2>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6"
|
||||
onClick={() => setEditingRightTitle(true)}
|
||||
>
|
||||
<Button variant="ghost" size="icon" className="h-6 w-6" onClick={() => setEditingRightTitle(true)}>
|
||||
<Edit2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -336,9 +320,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
<Label className="text-sm font-medium">규칙명</Label>
|
||||
<Input
|
||||
value={currentRule.ruleName}
|
||||
onChange={(e) =>
|
||||
setCurrentRule((prev) => ({ ...prev!, ruleName: e.target.value }))
|
||||
}
|
||||
onChange={(e) => setCurrentRule((prev) => ({ ...prev!, ruleName: e.target.value }))}
|
||||
className="h-9"
|
||||
placeholder="예: 프로젝트 코드"
|
||||
/>
|
||||
@@ -348,9 +330,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
<Label className="text-sm font-medium">적용 범위</Label>
|
||||
<Select
|
||||
value={currentRule.scopeType || "global"}
|
||||
onValueChange={(value: "global" | "menu") =>
|
||||
setCurrentRule((prev) => ({ ...prev!, scopeType: value }))
|
||||
}
|
||||
onValueChange={(value: "global" | "menu") => setCurrentRule((prev) => ({ ...prev!, scopeType: value }))}
|
||||
disabled={isPreview}
|
||||
>
|
||||
<SelectTrigger className="h-9">
|
||||
@@ -361,9 +341,9 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
<SelectItem value="menu">메뉴별</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="mt-1 text-[10px] text-muted-foreground sm:text-xs">
|
||||
{currentRule.scopeType === "menu"
|
||||
? "이 규칙이 설정된 상위 메뉴의 모든 하위 메뉴에서 사용 가능합니다"
|
||||
<p className="text-muted-foreground mt-1 text-[10px] sm:text-xs">
|
||||
{currentRule.scopeType === "menu"
|
||||
? "⚠️ 현재 화면이 속한 2레벨 메뉴와 그 하위 메뉴(3레벨 이상)에서만 사용됩니다. 형제 메뉴와 구분하여 채번 규칙을 관리할 때 유용합니다."
|
||||
: "회사 내 모든 메뉴에서 사용 가능한 전역 규칙입니다"}
|
||||
</p>
|
||||
</div>
|
||||
@@ -380,16 +360,14 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold">코드 구성</h3>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
<span className="text-muted-foreground text-xs">
|
||||
{currentRule.parts.length}/{maxRules}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{currentRule.parts.length === 0 ? (
|
||||
<div className="flex h-32 items-center justify-center rounded-lg border border-dashed border-border bg-muted/50">
|
||||
<p className="text-xs text-muted-foreground sm:text-sm">
|
||||
규칙을 추가하여 코드를 구성하세요
|
||||
</p>
|
||||
<div className="border-border bg-muted/50 flex h-32 items-center justify-center rounded-lg border border-dashed">
|
||||
<p className="text-muted-foreground text-xs sm:text-sm">규칙을 추가하여 코드를 구성하세요</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
|
||||
@@ -416,11 +394,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
규칙 추가
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={isPreview || loading}
|
||||
className="h-9 flex-1 text-sm"
|
||||
>
|
||||
<Button onClick={handleSave} disabled={isPreview || loading} className="h-9 flex-1 text-sm">
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
{loading ? "저장 중..." : "저장"}
|
||||
</Button>
|
||||
|
||||
@@ -84,7 +84,7 @@ export const EnhancedInteractiveScreenViewer: React.FC<EnhancedInteractiveScreen
|
||||
|
||||
// 자동값 생성 함수
|
||||
const generateAutoValue = useCallback(
|
||||
(autoValueType: string): string => {
|
||||
async (autoValueType: string, ruleId?: string): Promise<string> => {
|
||||
const now = new Date();
|
||||
switch (autoValueType) {
|
||||
case "current_datetime":
|
||||
@@ -99,6 +99,20 @@ export const EnhancedInteractiveScreenViewer: React.FC<EnhancedInteractiveScreen
|
||||
return crypto.randomUUID();
|
||||
case "sequence":
|
||||
return `SEQ_${Date.now()}`;
|
||||
case "numbering_rule":
|
||||
// 채번 규칙 사용
|
||||
if (ruleId) {
|
||||
try {
|
||||
const { generateNumberingCode } = await import("@/lib/api/numberingRule");
|
||||
const response = await generateNumberingCode(ruleId);
|
||||
if (response.success && response.data) {
|
||||
return response.data.generatedCode;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("채번 규칙 코드 생성 실패:", error);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@@ -129,24 +143,32 @@ export const EnhancedInteractiveScreenViewer: React.FC<EnhancedInteractiveScreen
|
||||
// 자동값 설정
|
||||
useEffect(() => {
|
||||
const widgetComponents = allComponents.filter((c) => c.type === "widget") as WidgetComponent[];
|
||||
const autoValueUpdates: Record<string, any> = {};
|
||||
|
||||
const loadAutoValues = async () => {
|
||||
const autoValueUpdates: Record<string, any> = {};
|
||||
|
||||
for (const widget of widgetComponents) {
|
||||
const fieldName = widget.columnName || widget.id;
|
||||
const currentValue = finalFormData[fieldName];
|
||||
for (const widget of widgetComponents) {
|
||||
const fieldName = widget.columnName || widget.id;
|
||||
const currentValue = finalFormData[fieldName];
|
||||
|
||||
// 자동값이 설정되어 있고 현재 값이 없는 경우
|
||||
if (widget.inputType === "auto" && widget.autoValueType && !currentValue) {
|
||||
const autoValue = generateAutoValue(widget.autoValueType);
|
||||
if (autoValue) {
|
||||
autoValueUpdates[fieldName] = autoValue;
|
||||
// 자동값이 설정되어 있고 현재 값이 없는 경우
|
||||
if (widget.inputType === "auto" && widget.autoValueType && !currentValue) {
|
||||
const autoValue = await generateAutoValue(
|
||||
widget.autoValueType,
|
||||
(widget as any).numberingRuleId // 채번 규칙 ID
|
||||
);
|
||||
if (autoValue) {
|
||||
autoValueUpdates[fieldName] = autoValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(autoValueUpdates).length > 0) {
|
||||
setLocalFormData((prev) => ({ ...prev, ...autoValueUpdates }));
|
||||
}
|
||||
if (Object.keys(autoValueUpdates).length > 0) {
|
||||
setLocalFormData((prev) => ({ ...prev, ...autoValueUpdates }));
|
||||
}
|
||||
};
|
||||
|
||||
loadAutoValues();
|
||||
}, [allComponents, finalFormData, generateAutoValue]);
|
||||
|
||||
// 향상된 저장 핸들러
|
||||
|
||||
@@ -136,7 +136,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||
: null;
|
||||
|
||||
// 자동값 생성 함수
|
||||
const generateAutoValue = useCallback((autoValueType: string): string => {
|
||||
const generateAutoValue = useCallback(async (autoValueType: string, ruleId?: string): Promise<string> => {
|
||||
const now = new Date();
|
||||
switch (autoValueType) {
|
||||
case "current_datetime":
|
||||
@@ -152,6 +152,20 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||
return crypto.randomUUID();
|
||||
case "sequence":
|
||||
return `SEQ_${Date.now()}`;
|
||||
case "numbering_rule":
|
||||
// 채번 규칙 사용
|
||||
if (ruleId) {
|
||||
try {
|
||||
const { generateNumberingCode } = await import("@/lib/api/numberingRule");
|
||||
const response = await generateNumberingCode(ruleId);
|
||||
if (response.success && response.data) {
|
||||
return response.data.generatedCode;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("채번 규칙 코드 생성 실패:", error);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -610,16 +610,16 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
const columnIndex = Math.round(effectiveX / (columnWidth + (gap || 16)));
|
||||
const snappedX = padding + columnIndex * (columnWidth + (gap || 16));
|
||||
|
||||
// Y 좌표는 20px 단위로 스냅
|
||||
// Y 좌표는 10px 단위로 스냅
|
||||
const effectiveY = newComp.position.y - padding;
|
||||
const rowIndex = Math.round(effectiveY / 20);
|
||||
const snappedY = padding + rowIndex * 20;
|
||||
const rowIndex = Math.round(effectiveY / 10);
|
||||
const snappedY = padding + rowIndex * 10;
|
||||
|
||||
// 크기도 외부 격자와 동일하게 스냅
|
||||
const fullColumnWidth = columnWidth + (gap || 16); // 외부 격자와 동일한 크기
|
||||
const widthInColumns = Math.max(1, Math.round(newComp.size.width / fullColumnWidth));
|
||||
const snappedWidth = widthInColumns * fullColumnWidth - (gap || 16); // gap 제거하여 실제 컴포넌트 크기
|
||||
const snappedHeight = Math.max(40, Math.round(newComp.size.height / 20) * 20);
|
||||
const snappedHeight = Math.max(10, Math.round(newComp.size.height / 10) * 10);
|
||||
|
||||
newComp.position = {
|
||||
x: Math.max(padding, snappedX), // 패딩만큼 최소 여백 확보
|
||||
|
||||
@@ -961,27 +961,27 @@ const PropertiesPanelComponent: React.FC<PropertiesPanelProps> = ({
|
||||
|
||||
<div className="col-span-2">
|
||||
<Label htmlFor="height" className="text-sm font-medium">
|
||||
최소 높이 (40px 단위)
|
||||
최소 높이 (10px 단위)
|
||||
</Label>
|
||||
<div className="mt-1 flex items-center space-x-2">
|
||||
<Input
|
||||
id="height"
|
||||
type="number"
|
||||
min="1"
|
||||
max="20"
|
||||
value={Math.round((localInputs.height || 40) / 40)}
|
||||
max="100"
|
||||
value={Math.round((localInputs.height || 10) / 10)}
|
||||
onChange={(e) => {
|
||||
const rows = Math.max(1, Math.min(20, Number(e.target.value)));
|
||||
const newHeight = rows * 40;
|
||||
const units = Math.max(1, Math.min(100, Number(e.target.value)));
|
||||
const newHeight = units * 10;
|
||||
setLocalInputs((prev) => ({ ...prev, height: newHeight.toString() }));
|
||||
onUpdateProperty("size.height", newHeight);
|
||||
}}
|
||||
className="flex-1"
|
||||
/>
|
||||
<span className="text-sm text-gray-500">행 = {localInputs.height || 40}px</span>
|
||||
<span className="text-sm text-gray-500">단위 = {localInputs.height || 10}px</span>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-gray-500">
|
||||
1행 = 40px (현재 {Math.round((localInputs.height || 40) / 40)}행) - 내부 콘텐츠에 맞춰 늘어남
|
||||
1단위 = 10px (현재 {Math.round((localInputs.height || 10) / 10)}단위) - 내부 콘텐츠에 맞춰 늘어남
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -364,11 +364,11 @@ export const UnifiedPropertiesPanel: React.FC<UnifiedPropertiesPanelProps> = ({
|
||||
value={selectedComponent.size?.height || 0}
|
||||
onChange={(e) => {
|
||||
const value = parseInt(e.target.value) || 0;
|
||||
const roundedValue = Math.max(40, Math.round(value / 40) * 40);
|
||||
const roundedValue = Math.max(10, Math.round(value / 10) * 10);
|
||||
handleUpdate("size.height", roundedValue);
|
||||
}}
|
||||
step={40}
|
||||
placeholder="40"
|
||||
step={10}
|
||||
placeholder="10"
|
||||
className="h-6 w-full px-2 py-0 text-xs"
|
||||
style={{ fontSize: "12px" }}
|
||||
style={{ fontSize: "12px" }}
|
||||
|
||||
@@ -7,6 +7,8 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { TextTypeConfig } from "@/types/screen";
|
||||
import { getAvailableNumberingRules } from "@/lib/api/numberingRule";
|
||||
import { NumberingRuleConfig } from "@/types/numbering-rule";
|
||||
|
||||
interface TextTypeConfigPanelProps {
|
||||
config: TextTypeConfig;
|
||||
@@ -26,9 +28,14 @@ export const TextTypeConfigPanel: React.FC<TextTypeConfigPanelProps> = ({ config
|
||||
autoInput: false,
|
||||
autoValueType: "current_datetime" as const,
|
||||
customValue: "",
|
||||
numberingRuleId: "",
|
||||
...config,
|
||||
};
|
||||
|
||||
// 채번 규칙 목록 상태
|
||||
const [numberingRules, setNumberingRules] = useState<NumberingRuleConfig[]>([]);
|
||||
const [loadingRules, setLoadingRules] = useState(false);
|
||||
|
||||
// 로컬 상태로 실시간 입력 관리
|
||||
const [localValues, setLocalValues] = useState({
|
||||
minLength: safeConfig.minLength?.toString() || "",
|
||||
@@ -41,8 +48,33 @@ export const TextTypeConfigPanel: React.FC<TextTypeConfigPanelProps> = ({ config
|
||||
autoInput: safeConfig.autoInput,
|
||||
autoValueType: safeConfig.autoValueType,
|
||||
customValue: safeConfig.customValue,
|
||||
numberingRuleId: safeConfig.numberingRuleId,
|
||||
});
|
||||
|
||||
// 채번 규칙 목록 로드
|
||||
useEffect(() => {
|
||||
const loadRules = async () => {
|
||||
setLoadingRules(true);
|
||||
try {
|
||||
// TODO: 현재 메뉴 objid를 화면 정보에서 가져와야 함
|
||||
// 지금은 menuObjid 없이 호출 (global 규칙만 조회)
|
||||
const response = await getAvailableNumberingRules();
|
||||
if (response.success && response.data) {
|
||||
setNumberingRules(response.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("채번 규칙 목록 로드 실패:", error);
|
||||
} finally {
|
||||
setLoadingRules(false);
|
||||
}
|
||||
};
|
||||
|
||||
// autoValueType이 numbering_rule일 때만 로드
|
||||
if (localValues.autoValueType === "numbering_rule") {
|
||||
loadRules();
|
||||
}
|
||||
}, [localValues.autoValueType]);
|
||||
|
||||
// config가 변경될 때 로컬 상태 동기화
|
||||
useEffect(() => {
|
||||
setLocalValues({
|
||||
@@ -56,6 +88,7 @@ export const TextTypeConfigPanel: React.FC<TextTypeConfigPanelProps> = ({ config
|
||||
autoInput: safeConfig.autoInput,
|
||||
autoValueType: safeConfig.autoValueType,
|
||||
customValue: safeConfig.customValue,
|
||||
numberingRuleId: safeConfig.numberingRuleId,
|
||||
});
|
||||
}, [
|
||||
safeConfig.minLength,
|
||||
@@ -68,6 +101,7 @@ export const TextTypeConfigPanel: React.FC<TextTypeConfigPanelProps> = ({ config
|
||||
safeConfig.autoInput,
|
||||
safeConfig.autoValueType,
|
||||
safeConfig.customValue,
|
||||
safeConfig.numberingRuleId,
|
||||
]);
|
||||
|
||||
const updateConfig = (key: keyof TextTypeConfig, value: any) => {
|
||||
@@ -90,16 +124,10 @@ export const TextTypeConfigPanel: React.FC<TextTypeConfigPanelProps> = ({ config
|
||||
autoInput: key === "autoInput" ? value : localValues.autoInput,
|
||||
autoValueType: key === "autoValueType" ? value : localValues.autoValueType,
|
||||
customValue: key === "customValue" ? value : localValues.customValue,
|
||||
numberingRuleId: key === "numberingRuleId" ? value : localValues.numberingRuleId,
|
||||
};
|
||||
|
||||
const newConfig = JSON.parse(JSON.stringify(currentValues));
|
||||
// console.log("📝 TextTypeConfig 업데이트:", {
|
||||
// key,
|
||||
// value,
|
||||
// oldConfig: safeConfig,
|
||||
// newConfig,
|
||||
// localValues,
|
||||
// });
|
||||
|
||||
setTimeout(() => {
|
||||
onConfigChange(newConfig);
|
||||
@@ -236,11 +264,45 @@ export const TextTypeConfigPanel: React.FC<TextTypeConfigPanelProps> = ({ config
|
||||
<SelectItem value="current_user">현재 사용자</SelectItem>
|
||||
<SelectItem value="uuid">고유 ID (UUID)</SelectItem>
|
||||
<SelectItem value="sequence">순번</SelectItem>
|
||||
<SelectItem value="numbering_rule">채번 규칙</SelectItem>
|
||||
<SelectItem value="custom">사용자 정의</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{localValues.autoValueType === "numbering_rule" && (
|
||||
<div>
|
||||
<Label htmlFor="numberingRuleId" className="text-sm font-medium">
|
||||
채번 규칙 선택 <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Select
|
||||
value={localValues.numberingRuleId}
|
||||
onValueChange={(value) => updateConfig("numberingRuleId", value)}
|
||||
disabled={loadingRules}
|
||||
>
|
||||
<SelectTrigger className="mt-1 h-6 w-full px-2 py-0 text-xs" style={{ fontSize: "12px" }}>
|
||||
<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} ({rule.ruleId})
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-muted-foreground mt-1 text-[10px]">
|
||||
현재 메뉴에서 사용 가능한 채번 규칙만 표시됩니다
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{localValues.autoValueType === "custom" && (
|
||||
<div>
|
||||
<Label htmlFor="customValue" className="text-sm font-medium">
|
||||
|
||||
Reference in New Issue
Block a user