feat: 채번 규칙 테이블 기반 자동 필터링 구현
- 채번 규칙 scope_type을 table로 단순화 - 화면의 테이블명을 자동으로 감지하여 채번 규칙 필터링 - TextInputConfigPanel에 screenTableName prop 추가 - getAvailableNumberingRulesForScreen API로 테이블 기반 조회 - NumberingRuleDesigner에서 자동으로 테이블명 설정 - webTypeConfigConverter 유틸리티 추가 (기존 화면 호환성) - AutoGenerationConfig 타입 개선 (enabled, options.numberingRuleId) - 채번 규칙 선택 UI에서 ID 제거, 설명 추가 - 불필요한 console.log 제거 Backend: - numberingRuleService: 테이블 기반 필터링 로직 구현 - numberingRuleController: available-for-screen 엔드포인트 수정 Frontend: - TextInputConfigPanel: 테이블명 기반 채번 규칙 로드 - NumberingRuleDesigner: 적용 범위 UI 제거, 테이블명 자동 설정 - ScreenDesigner: webTypeConfig → autoGeneration 변환 로직 통합 - DetailSettingsPanel: autoGeneration 속성 매핑 개선
This commit is contained in:
@@ -25,6 +25,7 @@ interface NumberingRuleDesignerProps {
|
||||
maxRules?: number;
|
||||
isPreview?: boolean;
|
||||
className?: string;
|
||||
currentTableName?: string; // 현재 화면의 테이블명 (자동 감지용)
|
||||
}
|
||||
|
||||
export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
@@ -34,6 +35,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
maxRules = 6,
|
||||
isPreview = false,
|
||||
className = "",
|
||||
currentTableName,
|
||||
}) => {
|
||||
const [savedRules, setSavedRules] = useState<NumberingRuleConfig[]>([]);
|
||||
const [selectedRuleId, setSelectedRuleId] = useState<string | null>(null);
|
||||
@@ -131,17 +133,32 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
try {
|
||||
const existing = savedRules.find((r) => r.ruleId === currentRule.ruleId);
|
||||
|
||||
// 저장 전에 현재 화면의 테이블명 자동 설정
|
||||
const ruleToSave = {
|
||||
...currentRule,
|
||||
scopeType: "table" as const, // 항상 table로 고정
|
||||
tableName: currentTableName || currentRule.tableName || "", // 현재 테이블명 자동 설정
|
||||
};
|
||||
|
||||
console.log("💾 채번 규칙 저장:", {
|
||||
currentTableName,
|
||||
"currentRule.tableName": currentRule.tableName,
|
||||
"ruleToSave.tableName": ruleToSave.tableName,
|
||||
"ruleToSave.scopeType": ruleToSave.scopeType,
|
||||
ruleToSave
|
||||
});
|
||||
|
||||
let response;
|
||||
if (existing) {
|
||||
response = await updateNumberingRule(currentRule.ruleId, currentRule);
|
||||
response = await updateNumberingRule(ruleToSave.ruleId, ruleToSave);
|
||||
} else {
|
||||
response = await createNumberingRule(currentRule);
|
||||
response = await createNumberingRule(ruleToSave);
|
||||
}
|
||||
|
||||
if (response.success && response.data) {
|
||||
setSavedRules((prev) => {
|
||||
if (existing) {
|
||||
return prev.map((r) => (r.ruleId === currentRule.ruleId ? response.data! : r));
|
||||
return prev.map((r) => (r.ruleId === ruleToSave.ruleId ? response.data! : r));
|
||||
} else {
|
||||
return [...prev, response.data!];
|
||||
}
|
||||
@@ -160,7 +177,7 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [currentRule, savedRules, onSave]);
|
||||
}, [currentRule, savedRules, onSave, currentTableName]);
|
||||
|
||||
const handleSelectRule = useCallback((rule: NumberingRuleConfig) => {
|
||||
setSelectedRuleId(rule.ruleId);
|
||||
@@ -196,6 +213,8 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
);
|
||||
|
||||
const handleNewRule = useCallback(() => {
|
||||
console.log("📋 새 규칙 생성 - currentTableName:", currentTableName);
|
||||
|
||||
const newRule: NumberingRuleConfig = {
|
||||
ruleId: `rule-${Date.now()}`,
|
||||
ruleName: "새 채번 규칙",
|
||||
@@ -203,14 +222,17 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
separator: "-",
|
||||
resetPeriod: "none",
|
||||
currentSequence: 1,
|
||||
scopeType: "menu",
|
||||
scopeType: "table", // 기본값을 table로 설정
|
||||
tableName: currentTableName || "", // 현재 화면의 테이블명 자동 설정
|
||||
};
|
||||
|
||||
console.log("📋 생성된 규칙 정보:", newRule);
|
||||
|
||||
setSelectedRuleId(newRule.ruleId);
|
||||
setCurrentRule(newRule);
|
||||
|
||||
toast.success("새 규칙이 생성되었습니다");
|
||||
}, []);
|
||||
}, [currentTableName]);
|
||||
|
||||
return (
|
||||
<div className={`flex h-full gap-4 ${className}`}>
|
||||
@@ -312,20 +334,36 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex-1 space-y-2">
|
||||
<Label className="text-sm font-medium">규칙명</Label>
|
||||
<Input
|
||||
value={currentRule.ruleName}
|
||||
onChange={(e) => setCurrentRule((prev) => ({ ...prev!, ruleName: e.target.value }))}
|
||||
className="h-9"
|
||||
placeholder="예: 프로젝트 코드"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 space-y-2">
|
||||
<Label className="text-sm font-medium">미리보기</Label>
|
||||
<NumberingRulePreview config={currentRule} />
|
||||
<div className="space-y-3">
|
||||
{/* 첫 번째 줄: 규칙명 + 미리보기 */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex-1 space-y-2">
|
||||
<Label className="text-sm font-medium">규칙명</Label>
|
||||
<Input
|
||||
value={currentRule.ruleName}
|
||||
onChange={(e) => setCurrentRule((prev) => ({ ...prev!, ruleName: e.target.value }))}
|
||||
className="h-9"
|
||||
placeholder="예: 프로젝트 코드"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 space-y-2">
|
||||
<Label className="text-sm font-medium">미리보기</Label>
|
||||
<NumberingRulePreview config={currentRule} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 두 번째 줄: 자동 감지된 테이블 정보 표시 */}
|
||||
{currentTableName && (
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm font-medium">적용 테이블</Label>
|
||||
<div className="flex h-9 items-center rounded-md border border-input bg-muted px-3 text-sm text-muted-foreground">
|
||||
{currentTableName}
|
||||
</div>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
이 규칙은 현재 화면의 테이블({currentTableName})에 자동으로 적용됩니다
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
|
||||
@@ -214,22 +214,11 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
||||
if (component.componentConfig?.type === "table-list") {
|
||||
// 디자인 해상도 기준으로 픽셀 반환
|
||||
const screenWidth = 1920; // 기본 디자인 해상도
|
||||
console.log("📏 [getWidth] table-list 픽셀 사용:", {
|
||||
componentId: id,
|
||||
label: component.label,
|
||||
width: `${screenWidth}px`,
|
||||
});
|
||||
return `${screenWidth}px`;
|
||||
}
|
||||
|
||||
// 모든 컴포넌트는 size.width 픽셀 사용
|
||||
const width = `${size?.width || 100}px`;
|
||||
console.log("📐 [getWidth] 픽셀 기준 통일:", {
|
||||
componentId: id,
|
||||
label: component.label,
|
||||
width,
|
||||
sizeWidth: size?.width,
|
||||
});
|
||||
return width;
|
||||
};
|
||||
|
||||
@@ -286,33 +275,7 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
||||
if (outerDivRef.current && innerDivRef.current) {
|
||||
const outerRect = outerDivRef.current.getBoundingClientRect();
|
||||
const innerRect = innerDivRef.current.getBoundingClientRect();
|
||||
const computedOuter = window.getComputedStyle(outerDivRef.current);
|
||||
const computedInner = window.getComputedStyle(innerDivRef.current);
|
||||
|
||||
console.log("📐 [DOM 실제 크기 상세]:", {
|
||||
componentId: id,
|
||||
label: component.label,
|
||||
gridColumns: (component as any).gridColumns,
|
||||
"1. baseStyle.width": baseStyle.width,
|
||||
"2. 외부 div (파란 테두리)": {
|
||||
width: `${outerRect.width}px`,
|
||||
height: `${outerRect.height}px`,
|
||||
computedWidth: computedOuter.width,
|
||||
computedHeight: computedOuter.height,
|
||||
},
|
||||
"3. 내부 div (컨텐츠 래퍼)": {
|
||||
width: `${innerRect.width}px`,
|
||||
height: `${innerRect.height}px`,
|
||||
computedWidth: computedInner.width,
|
||||
computedHeight: computedInner.height,
|
||||
className: innerDivRef.current.className,
|
||||
inlineStyle: innerDivRef.current.getAttribute("style"),
|
||||
},
|
||||
"4. 너비 비교": {
|
||||
"외부 / 내부": `${outerRect.width}px / ${innerRect.width}px`,
|
||||
비율: `${((innerRect.width / outerRect.width) * 100).toFixed(2)}%`,
|
||||
},
|
||||
});
|
||||
// 크기 측정 완료
|
||||
}
|
||||
}, [id, component.label, (component as any).gridColumns, baseStyle.width]);
|
||||
|
||||
|
||||
@@ -899,9 +899,14 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||
layoutToUse = safeMigrateLayout(response, canvasWidth);
|
||||
}
|
||||
|
||||
// 🔄 webTypeConfig를 autoGeneration으로 변환
|
||||
const { convertLayoutComponents } = await import("@/lib/utils/webTypeConfigConverter");
|
||||
const convertedComponents = convertLayoutComponents(layoutToUse.components);
|
||||
|
||||
// 기본 격자 설정 보장 (격자 표시와 스냅 기본 활성화)
|
||||
const layoutWithDefaultGrid = {
|
||||
...layoutToUse,
|
||||
components: convertedComponents, // 변환된 컴포넌트 사용
|
||||
gridSettings: {
|
||||
columns: layoutToUse.gridSettings?.columns || 12, // DB 값 우선, 없으면 기본값 12
|
||||
gap: layoutToUse.gridSettings?.gap ?? 16, // DB 값 우선, 없으면 기본값 16
|
||||
|
||||
@@ -752,17 +752,27 @@ export const DetailSettingsPanel: React.FC<DetailSettingsPanelProps> = ({
|
||||
// console.log("🎨 selectedComponent 전체:", selectedComponent);
|
||||
|
||||
const handleConfigChange = (newConfig: WebTypeConfig) => {
|
||||
// console.log("🔧 WebTypeConfig 업데이트:", {
|
||||
// widgetType: widget.widgetType,
|
||||
// oldConfig: currentConfig,
|
||||
// newConfig,
|
||||
// componentId: widget.id,
|
||||
// isEqual: JSON.stringify(currentConfig) === JSON.stringify(newConfig),
|
||||
// });
|
||||
|
||||
// 강제 새 객체 생성으로 React 변경 감지 보장
|
||||
const freshConfig = { ...newConfig };
|
||||
onUpdateProperty(widget.id, "webTypeConfig", freshConfig);
|
||||
|
||||
// TextTypeConfig의 자동입력 설정을 autoGeneration으로도 매핑
|
||||
const textConfig = newConfig as any;
|
||||
if (textConfig.autoInput && textConfig.autoValueType === "numbering_rule" && textConfig.numberingRuleId) {
|
||||
onUpdateProperty(widget.id, "autoGeneration", {
|
||||
type: "numbering_rule",
|
||||
enabled: true,
|
||||
options: {
|
||||
numberingRuleId: textConfig.numberingRuleId,
|
||||
},
|
||||
});
|
||||
} else if (textConfig.autoInput === false) {
|
||||
// 자동입력이 비활성화되면 autoGeneration도 비활성화
|
||||
onUpdateProperty(widget.id, "autoGeneration", {
|
||||
type: "none",
|
||||
enabled: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 1순위: DB에서 지정된 설정 패널 사용
|
||||
@@ -776,7 +786,13 @@ export const DetailSettingsPanel: React.FC<DetailSettingsPanelProps> = ({
|
||||
|
||||
if (ConfigPanelComponent) {
|
||||
// console.log(`🎨 ✅ ConfigPanelComponent 렌더링 시작`);
|
||||
return <ConfigPanelComponent config={currentConfig} onConfigChange={handleConfigChange} />;
|
||||
return (
|
||||
<ConfigPanelComponent
|
||||
config={currentConfig}
|
||||
onConfigChange={handleConfigChange}
|
||||
tableName={currentTableName} // 화면 테이블명 전달
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
// console.log(`🎨 ❌ ConfigPanelComponent가 null - WebTypeConfigPanel 사용`);
|
||||
return (
|
||||
|
||||
@@ -7,15 +7,24 @@ 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 { getAvailableNumberingRules, getAvailableNumberingRulesForScreen } from "@/lib/api/numberingRule";
|
||||
import { NumberingRuleConfig } from "@/types/numbering-rule";
|
||||
|
||||
interface TextTypeConfigPanelProps {
|
||||
config: TextTypeConfig;
|
||||
onConfigChange: (config: TextTypeConfig) => void;
|
||||
tableName?: string; // 화면의 테이블명 (선택)
|
||||
menuObjid?: number; // 메뉴 objid (선택)
|
||||
}
|
||||
|
||||
export const TextTypeConfigPanel: React.FC<TextTypeConfigPanelProps> = ({ config, onConfigChange }) => {
|
||||
export const TextTypeConfigPanel: React.FC<TextTypeConfigPanelProps> = ({
|
||||
config,
|
||||
onConfigChange,
|
||||
tableName,
|
||||
menuObjid,
|
||||
}) => {
|
||||
console.log("🔍 TextTypeConfigPanel 마운트:", { tableName, menuObjid, config });
|
||||
|
||||
// 기본값이 설정된 config 사용
|
||||
const safeConfig = {
|
||||
minLength: undefined,
|
||||
@@ -54,16 +63,46 @@ export const TextTypeConfigPanel: React.FC<TextTypeConfigPanelProps> = ({ config
|
||||
// 채번 규칙 목록 로드
|
||||
useEffect(() => {
|
||||
const loadRules = async () => {
|
||||
console.log("🔄 채번 규칙 로드 시작:", {
|
||||
autoValueType: localValues.autoValueType,
|
||||
tableName,
|
||||
hasTableName: !!tableName,
|
||||
});
|
||||
|
||||
setLoadingRules(true);
|
||||
try {
|
||||
// TODO: 현재 메뉴 objid를 화면 정보에서 가져와야 함
|
||||
// 지금은 menuObjid 없이 호출 (global 규칙만 조회)
|
||||
const response = await getAvailableNumberingRules();
|
||||
let response;
|
||||
|
||||
// 테이블명이 있으면 테이블 기반 필터링 사용
|
||||
if (tableName) {
|
||||
console.log("📋 테이블 기반 채번 규칙 조회 API 호출:", { tableName });
|
||||
response = await getAvailableNumberingRulesForScreen(tableName);
|
||||
console.log("📋 API 응답:", response);
|
||||
} else {
|
||||
// 테이블명이 없으면 빈 배열 (테이블 필수)
|
||||
console.warn("⚠️ 테이블명이 없어 채번 규칙을 조회할 수 없습니다");
|
||||
setNumberingRules([]);
|
||||
setLoadingRules(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.success && response.data) {
|
||||
setNumberingRules(response.data);
|
||||
console.log("✅ 채번 규칙 로드 성공:", {
|
||||
count: response.data.length,
|
||||
rules: response.data.map((r: any) => ({
|
||||
ruleId: r.ruleId,
|
||||
ruleName: r.ruleName,
|
||||
tableName: r.tableName,
|
||||
})),
|
||||
});
|
||||
} else {
|
||||
console.warn("⚠️ 채번 규칙 조회 실패:", response.error);
|
||||
setNumberingRules([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("채번 규칙 목록 로드 실패:", error);
|
||||
console.error("❌ 채번 규칙 목록 로드 실패:", error);
|
||||
setNumberingRules([]);
|
||||
} finally {
|
||||
setLoadingRules(false);
|
||||
}
|
||||
@@ -71,9 +110,12 @@ export const TextTypeConfigPanel: React.FC<TextTypeConfigPanelProps> = ({ config
|
||||
|
||||
// autoValueType이 numbering_rule일 때만 로드
|
||||
if (localValues.autoValueType === "numbering_rule") {
|
||||
console.log("✅ autoValueType === 'numbering_rule', 규칙 로드 시작");
|
||||
loadRules();
|
||||
} else {
|
||||
console.log("⏭️ autoValueType !== 'numbering_rule', 규칙 로드 스킵:", localValues.autoValueType);
|
||||
}
|
||||
}, [localValues.autoValueType]);
|
||||
}, [localValues.autoValueType, tableName]);
|
||||
|
||||
// config가 변경될 때 로컬 상태 동기화
|
||||
useEffect(() => {
|
||||
|
||||
@@ -22,7 +22,7 @@ export async function getNumberingRules(): Promise<ApiResponse<NumberingRuleConf
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴별 사용 가능한 채번 규칙 조회
|
||||
* 메뉴별 사용 가능한 채번 규칙 조회 (기존 방식, 하위 호환성 유지)
|
||||
* @param menuObjid 현재 메뉴의 objid (선택)
|
||||
* @returns 사용 가능한 채번 규칙 목록
|
||||
*/
|
||||
@@ -40,6 +40,27 @@ export async function getAvailableNumberingRules(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 화면용 채번 규칙 조회 (테이블 기반 필터링 - 간소화)
|
||||
* @param tableName 화면의 테이블명 (필수)
|
||||
* @returns 해당 테이블의 채번 규칙 목록
|
||||
*/
|
||||
export async function getAvailableNumberingRulesForScreen(
|
||||
tableName: string
|
||||
): Promise<ApiResponse<NumberingRuleConfig[]>> {
|
||||
try {
|
||||
const response = await apiClient.get("/numbering-rules/available-for-screen", {
|
||||
params: { tableName },
|
||||
});
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || "화면용 규칙 조회 실패",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function getNumberingRuleById(ruleId: string): Promise<ApiResponse<NumberingRuleConfig>> {
|
||||
try {
|
||||
const response = await apiClient.get(`/numbering-rules/${ruleId}`);
|
||||
|
||||
@@ -148,19 +148,8 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
||||
const tableName = (component as any).tableName;
|
||||
const columnName = (component as any).columnName;
|
||||
|
||||
console.log("🔍 DynamicComponentRenderer 컴포넌트 타입 확인:", {
|
||||
componentId: component.id,
|
||||
componentType,
|
||||
inputType,
|
||||
webType,
|
||||
tableName,
|
||||
columnName,
|
||||
componentConfig: (component as any).componentConfig,
|
||||
});
|
||||
|
||||
// 카테고리 셀렉트: webType이 "category"이고 tableName과 columnName이 있는 경우만
|
||||
if ((inputType === "category" || webType === "category") && tableName && columnName) {
|
||||
console.log("✅ 카테고리 타입 감지 → CategorySelectComponent 렌더링");
|
||||
try {
|
||||
const { CategorySelectComponent } = require("@/lib/registry/components/category-select/CategorySelectComponent");
|
||||
const fieldName = columnName || component.id;
|
||||
@@ -303,14 +292,6 @@ export const DynamicComponentRenderer: React.FC<DynamicComponentRendererProps> =
|
||||
componentType === "split-panel-layout" ||
|
||||
componentType?.includes("layout");
|
||||
|
||||
console.log("🔍 [DynamicComponentRenderer] 높이 처리:", {
|
||||
componentId: component.id,
|
||||
componentType,
|
||||
isLayoutComponent,
|
||||
hasHeight: !!component.style?.height,
|
||||
height: component.style?.height
|
||||
});
|
||||
|
||||
const { height: _height, ...styleWithoutHeight } = component.style || {};
|
||||
|
||||
// 숨김 값 추출
|
||||
|
||||
@@ -74,20 +74,12 @@ export const CategorySelectComponent: React.FC<
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
console.log("📦 카테고리 값 조회:", { tableName, columnName });
|
||||
|
||||
const response = await getCategoryValues(tableName, columnName);
|
||||
|
||||
if (response.success && response.data) {
|
||||
// 활성화된 값만 필터링
|
||||
const activeValues = response.data.filter((v) => v.isActive !== false);
|
||||
setCategoryValues(activeValues);
|
||||
|
||||
console.log("✅ 카테고리 값 조회 성공:", {
|
||||
total: response.data.length,
|
||||
active: activeValues.length,
|
||||
values: activeValues,
|
||||
});
|
||||
} else {
|
||||
setError("카테고리 값을 불러올 수 없습니다");
|
||||
console.error("❌ 카테고리 값 조회 실패:", response);
|
||||
|
||||
@@ -8,19 +8,24 @@ interface NumberingRuleWrapperProps {
|
||||
config: NumberingRuleComponentConfig;
|
||||
onChange?: (config: NumberingRuleComponentConfig) => void;
|
||||
isPreview?: boolean;
|
||||
tableName?: string; // 현재 화면의 테이블명
|
||||
}
|
||||
|
||||
export const NumberingRuleWrapper: React.FC<NumberingRuleWrapperProps> = ({
|
||||
config,
|
||||
onChange,
|
||||
isPreview = false,
|
||||
tableName,
|
||||
}) => {
|
||||
console.log("📋 NumberingRuleWrapper: 테이블명 전달", { tableName, config });
|
||||
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<NumberingRuleDesigner
|
||||
maxRules={config.maxRules || 6}
|
||||
isPreview={isPreview}
|
||||
className="h-full"
|
||||
currentTableName={tableName} // 테이블명 전달
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -100,16 +100,6 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
|
||||
const currentFormValue = formData?.[component.columnName];
|
||||
const currentComponentValue = component.value;
|
||||
|
||||
console.log("🔧 TextInput 자동생성 체크:", {
|
||||
componentId: component.id,
|
||||
columnName: component.columnName,
|
||||
autoGenType: testAutoGeneration.type,
|
||||
ruleId: testAutoGeneration.options?.numberingRuleId,
|
||||
currentFormValue,
|
||||
currentComponentValue,
|
||||
autoGeneratedValue,
|
||||
isInteractive,
|
||||
});
|
||||
|
||||
// 자동생성된 값이 없고, 현재 값도 없을 때만 생성
|
||||
if (!autoGeneratedValue && !currentFormValue && !currentComponentValue) {
|
||||
|
||||
@@ -8,19 +8,20 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
||||
import { TextInputConfig } from "./types";
|
||||
import { AutoGenerationType, AutoGenerationConfig } from "@/types/screen";
|
||||
import { AutoGenerationUtils } from "@/lib/utils/autoGeneration";
|
||||
import { getAvailableNumberingRules } from "@/lib/api/numberingRule";
|
||||
import { getAvailableNumberingRules, getAvailableNumberingRulesForScreen } from "@/lib/api/numberingRule";
|
||||
import { NumberingRuleConfig } from "@/types/numbering-rule";
|
||||
|
||||
export interface TextInputConfigPanelProps {
|
||||
config: TextInputConfig;
|
||||
onChange: (config: Partial<TextInputConfig>) => void;
|
||||
screenTableName?: string; // 🆕 현재 화면의 테이블명
|
||||
}
|
||||
|
||||
/**
|
||||
* TextInput 설정 패널
|
||||
* 컴포넌트의 설정값들을 편집할 수 있는 UI 제공
|
||||
*/
|
||||
export const TextInputConfigPanel: React.FC<TextInputConfigPanelProps> = ({ config, onChange }) => {
|
||||
export const TextInputConfigPanel: React.FC<TextInputConfigPanelProps> = ({ config, onChange, screenTableName }) => {
|
||||
// 채번 규칙 목록 상태
|
||||
const [numberingRules, setNumberingRules] = useState<NumberingRuleConfig[]>([]);
|
||||
const [loadingRules, setLoadingRules] = useState(false);
|
||||
@@ -30,9 +31,20 @@ export const TextInputConfigPanel: React.FC<TextInputConfigPanelProps> = ({ conf
|
||||
const loadRules = async () => {
|
||||
setLoadingRules(true);
|
||||
try {
|
||||
const response = await getAvailableNumberingRules();
|
||||
let response;
|
||||
|
||||
// 🆕 테이블명이 있으면 테이블 기반 필터링, 없으면 전체 조회
|
||||
if (screenTableName) {
|
||||
console.log("🔍 TextInputConfigPanel: 테이블 기반 채번 규칙 로드", { screenTableName });
|
||||
response = await getAvailableNumberingRulesForScreen(screenTableName);
|
||||
} else {
|
||||
console.log("🔍 TextInputConfigPanel: 전체 채번 규칙 로드 (테이블명 없음)");
|
||||
response = await getAvailableNumberingRules();
|
||||
}
|
||||
|
||||
if (response.success && response.data) {
|
||||
setNumberingRules(response.data);
|
||||
console.log("✅ 채번 규칙 로드 완료:", response.data.length, "개");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("채번 규칙 목록 로드 실패:", error);
|
||||
@@ -45,7 +57,7 @@ export const TextInputConfigPanel: React.FC<TextInputConfigPanelProps> = ({ conf
|
||||
if (config.autoGeneration?.type === "numbering_rule") {
|
||||
loadRules();
|
||||
}
|
||||
}, [config.autoGeneration?.type]);
|
||||
}, [config.autoGeneration?.type, screenTableName]);
|
||||
|
||||
const handleChange = (key: keyof TextInputConfig, value: any) => {
|
||||
onChange({ [key]: value });
|
||||
@@ -174,7 +186,12 @@ export const TextInputConfigPanel: React.FC<TextInputConfigPanelProps> = ({ conf
|
||||
) : (
|
||||
numberingRules.map((rule) => (
|
||||
<SelectItem key={rule.ruleId} value={rule.ruleId}>
|
||||
{rule.ruleName} ({rule.ruleId})
|
||||
{rule.ruleName}
|
||||
{rule.description && (
|
||||
<span className="text-muted-foreground ml-2 text-xs">
|
||||
- {rule.description}
|
||||
</span>
|
||||
)}
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
|
||||
@@ -19,6 +19,8 @@ import { DashboardConfigPanel } from "@/components/screen/config-panels/Dashboar
|
||||
export type ConfigPanelComponent = React.ComponentType<{
|
||||
config: any;
|
||||
onConfigChange: (config: any) => void;
|
||||
tableName?: string; // 화면 테이블명 (선택)
|
||||
menuObjid?: number; // 메뉴 objid (선택)
|
||||
}>;
|
||||
|
||||
// ButtonConfigPanel 래퍼 (config/onConfigChange → component/onUpdateProperty 변환)
|
||||
|
||||
58
frontend/lib/utils/webTypeConfigConverter.ts
Normal file
58
frontend/lib/utils/webTypeConfigConverter.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* WebTypeConfig와 AutoGeneration 간 변환 유틸리티
|
||||
*/
|
||||
|
||||
import { ComponentData } from "@/types/screen";
|
||||
|
||||
/**
|
||||
* webTypeConfig의 자동입력 설정을 autoGeneration으로 변환
|
||||
*/
|
||||
export function convertWebTypeConfigToAutoGeneration(component: ComponentData): ComponentData {
|
||||
// webTypeConfig가 없으면 변환 불필요
|
||||
if (!component.webTypeConfig) {
|
||||
return component;
|
||||
}
|
||||
|
||||
const config = component.webTypeConfig as any;
|
||||
|
||||
// 자동입력이 활성화되어 있는지 확인
|
||||
if (!config.autoInput || !config.autoValueType) {
|
||||
return component;
|
||||
}
|
||||
|
||||
// 이미 autoGeneration이 올바르게 설정되어 있으면 변환 불필요
|
||||
if (
|
||||
component.autoGeneration &&
|
||||
component.autoGeneration.type === config.autoValueType &&
|
||||
component.autoGeneration.options?.numberingRuleId === config.numberingRuleId
|
||||
) {
|
||||
return component;
|
||||
}
|
||||
|
||||
// autoGeneration 객체 생성
|
||||
const autoGeneration: any = {
|
||||
type: config.autoValueType,
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
// 채번 규칙인 경우 options.numberingRuleId 설정
|
||||
if (config.autoValueType === "numbering_rule" && config.numberingRuleId) {
|
||||
autoGeneration.options = {
|
||||
numberingRuleId: config.numberingRuleId,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
...component,
|
||||
autoGeneration,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 레이아웃의 모든 컴포넌트에 대해 webTypeConfig → autoGeneration 변환 적용
|
||||
*/
|
||||
export function convertLayoutComponents(components: ComponentData[]): ComponentData[] {
|
||||
return components.map(convertWebTypeConfigToAutoGeneration);
|
||||
}
|
||||
|
||||
@@ -132,7 +132,16 @@ export type AutoGenerationType = "table" | "form" | "mixed";
|
||||
*/
|
||||
export interface AutoGenerationConfig {
|
||||
type: AutoGenerationType;
|
||||
enabled?: boolean;
|
||||
tableName?: string;
|
||||
includeSearch?: boolean;
|
||||
includePagination?: boolean;
|
||||
options?: {
|
||||
length?: number; // 랜덤 문자열/숫자 길이
|
||||
prefix?: string; // 접두사
|
||||
suffix?: string; // 접미사
|
||||
format?: string; // 시간 형식 (current_time용)
|
||||
startValue?: number; // 시퀀스 시작값
|
||||
numberingRuleId?: string; // 채번 규칙 ID (numbering_rule 타입용)
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user