Compare commits
3 Commits
9f3437d499
...
34202be843
| Author | SHA1 | Date |
|---|---|---|
|
|
34202be843 | |
|
|
d28e703cd2 | |
|
|
77fcf1a35a |
|
|
@ -1038,6 +1038,7 @@ class NumberingRuleService {
|
||||||
|
|
||||||
if (manualParts.length > 0 && userInputCode) {
|
if (manualParts.length > 0 && userInputCode) {
|
||||||
// 프리뷰 코드를 생성해서 ____ 위치 파악
|
// 프리뷰 코드를 생성해서 ____ 위치 파악
|
||||||
|
// 🔧 category 파트도 처리하여 올바른 템플릿 생성
|
||||||
const previewParts = rule.parts
|
const previewParts = rule.parts
|
||||||
.sort((a: any, b: any) => a.order - b.order)
|
.sort((a: any, b: any) => a.order - b.order)
|
||||||
.map((part: any) => {
|
.map((part: any) => {
|
||||||
|
|
@ -1054,6 +1055,35 @@ class NumberingRuleService {
|
||||||
return autoConfig.textValue || "";
|
return autoConfig.textValue || "";
|
||||||
case "date":
|
case "date":
|
||||||
return "DATEPART"; // 날짜 자리 표시
|
return "DATEPART"; // 날짜 자리 표시
|
||||||
|
case "category": {
|
||||||
|
// 카테고리 파트: formData에서 실제 값을 가져와서 매핑된 형식 사용
|
||||||
|
const categoryKey = autoConfig.categoryKey;
|
||||||
|
const categoryMappings = autoConfig.categoryMappings || [];
|
||||||
|
|
||||||
|
if (!categoryKey || !formData) {
|
||||||
|
return "CATEGORY"; // 폴백
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnName = categoryKey.includes(".")
|
||||||
|
? categoryKey.split(".")[1]
|
||||||
|
: categoryKey;
|
||||||
|
const selectedValue = formData[columnName];
|
||||||
|
|
||||||
|
if (!selectedValue) {
|
||||||
|
return "CATEGORY"; // 폴백
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedValueStr = String(selectedValue);
|
||||||
|
const mapping = categoryMappings.find(
|
||||||
|
(m: any) => {
|
||||||
|
if (m.categoryValueId?.toString() === selectedValueStr) return true;
|
||||||
|
if (m.categoryValueLabel === selectedValueStr) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return mapping?.format || "CATEGORY";
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
@ -1165,6 +1195,68 @@ class NumberingRuleService {
|
||||||
return autoConfig.textValue || "TEXT";
|
return autoConfig.textValue || "TEXT";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "category": {
|
||||||
|
// 카테고리 기반 코드 생성 (allocateCode용)
|
||||||
|
const categoryKey = autoConfig.categoryKey; // 예: "item_info.material"
|
||||||
|
const categoryMappings = autoConfig.categoryMappings || [];
|
||||||
|
|
||||||
|
if (!categoryKey || !formData) {
|
||||||
|
logger.warn("allocateCode: 카테고리 키 또는 폼 데이터 없음", { categoryKey, hasFormData: !!formData });
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// categoryKey에서 컬럼명 추출 (예: "item_info.material" -> "material")
|
||||||
|
const columnName = categoryKey.includes(".")
|
||||||
|
? categoryKey.split(".")[1]
|
||||||
|
: categoryKey;
|
||||||
|
|
||||||
|
// 폼 데이터에서 해당 컬럼의 값 가져오기
|
||||||
|
const selectedValue = formData[columnName];
|
||||||
|
|
||||||
|
logger.info("allocateCode: 카테고리 파트 처리", {
|
||||||
|
categoryKey,
|
||||||
|
columnName,
|
||||||
|
selectedValue,
|
||||||
|
formDataKeys: Object.keys(formData),
|
||||||
|
mappingsCount: categoryMappings.length
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!selectedValue) {
|
||||||
|
logger.warn("allocateCode: 카테고리 값이 선택되지 않음", { columnName, formDataKeys: Object.keys(formData) });
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 카테고리 매핑에서 해당 값에 대한 형식 찾기
|
||||||
|
const selectedValueStr = String(selectedValue);
|
||||||
|
const mapping = categoryMappings.find(
|
||||||
|
(m: any) => {
|
||||||
|
// ID로 매칭
|
||||||
|
if (m.categoryValueId?.toString() === selectedValueStr) return true;
|
||||||
|
// 라벨로 매칭
|
||||||
|
if (m.categoryValueLabel === selectedValueStr) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mapping) {
|
||||||
|
logger.info("allocateCode: 카테고리 매핑 적용", {
|
||||||
|
selectedValue,
|
||||||
|
format: mapping.format,
|
||||||
|
categoryValueLabel: mapping.categoryValueLabel
|
||||||
|
});
|
||||||
|
return mapping.format || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.warn("allocateCode: 카테고리 매핑을 찾을 수 없음", {
|
||||||
|
selectedValue,
|
||||||
|
availableMappings: categoryMappings.map((m: any) => ({
|
||||||
|
id: m.categoryValueId,
|
||||||
|
label: m.categoryValueLabel
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logger.warn("알 수 없는 파트 타입", { partType: part.partType });
|
logger.warn("알 수 없는 파트 타입", { partType: part.partType });
|
||||||
return "";
|
return "";
|
||||||
|
|
|
||||||
|
|
@ -260,22 +260,24 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
toast.success(`규칙 ${newPart.order}가 추가되었습니다`);
|
toast.success(`규칙 ${newPart.order}가 추가되었습니다`);
|
||||||
}, [currentRule, maxRules]);
|
}, [currentRule, maxRules]);
|
||||||
|
|
||||||
const handleUpdatePart = useCallback((partId: string, updates: Partial<NumberingRulePart>) => {
|
// partOrder 기반으로 파트 업데이트 (id가 null일 수 있으므로 order 사용)
|
||||||
|
const handleUpdatePart = useCallback((partOrder: number, updates: Partial<NumberingRulePart>) => {
|
||||||
setCurrentRule((prev) => {
|
setCurrentRule((prev) => {
|
||||||
if (!prev) return null;
|
if (!prev) return null;
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
parts: prev.parts.map((part) => (part.id === partId ? { ...part, ...updates } : part)),
|
parts: prev.parts.map((part) => (part.order === partOrder ? { ...part, ...updates } : part)),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleDeletePart = useCallback((partId: string) => {
|
// partOrder 기반으로 파트 삭제 (id가 null일 수 있으므로 order 사용)
|
||||||
|
const handleDeletePart = useCallback((partOrder: number) => {
|
||||||
setCurrentRule((prev) => {
|
setCurrentRule((prev) => {
|
||||||
if (!prev) return null;
|
if (!prev) return null;
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
parts: prev.parts.filter((part) => part.id !== partId).map((part, index) => ({ ...part, order: index + 1 })),
|
parts: prev.parts.filter((part) => part.order !== partOrder).map((part, index) => ({ ...part, order: index + 1 })),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -295,8 +297,6 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const existing = savedRules.find((r) => r.ruleId === currentRule.ruleId);
|
|
||||||
|
|
||||||
// 파트별 기본 autoConfig 정의
|
// 파트별 기본 autoConfig 정의
|
||||||
const defaultAutoConfigs: Record<string, any> = {
|
const defaultAutoConfigs: Record<string, any> = {
|
||||||
sequence: { sequenceLength: 3, startFrom: 1 },
|
sequence: { sequenceLength: 3, startFrom: 1 },
|
||||||
|
|
@ -345,15 +345,30 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
const response = await saveNumberingRuleToTest(ruleToSave);
|
const response = await saveNumberingRuleToTest(ruleToSave);
|
||||||
|
|
||||||
if (response.success && response.data) {
|
if (response.success && response.data) {
|
||||||
|
// 깊은 복사하여 savedRules와 currentRule이 다른 객체를 참조하도록 함
|
||||||
|
const currentData = JSON.parse(JSON.stringify(response.data)) as NumberingRuleConfig;
|
||||||
|
|
||||||
|
// setSavedRules 내부에서 prev를 사용해서 existing 확인 (클로저 문제 방지)
|
||||||
setSavedRules((prev) => {
|
setSavedRules((prev) => {
|
||||||
if (existing) {
|
const savedData = JSON.parse(JSON.stringify(response.data)) as NumberingRuleConfig;
|
||||||
return prev.map((r) => (r.ruleId === ruleToSave.ruleId ? response.data! : r));
|
const existsInPrev = prev.some((r) => r.ruleId === ruleToSave.ruleId);
|
||||||
|
|
||||||
|
console.log("🔍 [handleSave] setSavedRules:", {
|
||||||
|
ruleId: ruleToSave.ruleId,
|
||||||
|
existsInPrev,
|
||||||
|
prevCount: prev.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existsInPrev) {
|
||||||
|
// 기존 규칙 업데이트
|
||||||
|
return prev.map((r) => (r.ruleId === ruleToSave.ruleId ? savedData : r));
|
||||||
} else {
|
} else {
|
||||||
return [...prev, response.data!];
|
// 새 규칙 추가
|
||||||
|
return [...prev, savedData];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setCurrentRule(response.data);
|
setCurrentRule(currentData);
|
||||||
setSelectedRuleId(response.data.ruleId);
|
setSelectedRuleId(response.data.ruleId);
|
||||||
|
|
||||||
await onSave?.(response.data);
|
await onSave?.(response.data);
|
||||||
|
|
@ -366,11 +381,27 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [currentRule, savedRules, onSave, currentTableName]);
|
}, [currentRule, onSave, currentTableName, menuObjid]);
|
||||||
|
|
||||||
const handleSelectRule = useCallback((rule: NumberingRuleConfig) => {
|
const handleSelectRule = useCallback((rule: NumberingRuleConfig) => {
|
||||||
|
console.log("🔍 [handleSelectRule] 규칙 선택:", {
|
||||||
|
ruleId: rule.ruleId,
|
||||||
|
ruleName: rule.ruleName,
|
||||||
|
partsCount: rule.parts?.length || 0,
|
||||||
|
parts: rule.parts?.map(p => ({ id: p.id, order: p.order, partType: p.partType })),
|
||||||
|
});
|
||||||
|
|
||||||
setSelectedRuleId(rule.ruleId);
|
setSelectedRuleId(rule.ruleId);
|
||||||
setCurrentRule(rule);
|
// 깊은 복사하여 객체 참조 분리 (좌측 목록과 편집 영역의 객체가 공유되지 않도록)
|
||||||
|
const ruleCopy = JSON.parse(JSON.stringify(rule)) as NumberingRuleConfig;
|
||||||
|
|
||||||
|
console.log("🔍 [handleSelectRule] 깊은 복사 후:", {
|
||||||
|
ruleId: ruleCopy.ruleId,
|
||||||
|
partsCount: ruleCopy.parts?.length || 0,
|
||||||
|
parts: ruleCopy.parts?.map(p => ({ id: p.id, order: p.order, partType: p.partType })),
|
||||||
|
});
|
||||||
|
|
||||||
|
setCurrentRule(ruleCopy);
|
||||||
toast.info(`"${rule.ruleName}" 규칙을 불러왔습니다`);
|
toast.info(`"${rule.ruleName}" 규칙을 불러왔습니다`);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -595,12 +626,12 @@ export const NumberingRuleDesigner: React.FC<NumberingRuleDesignerProps> = ({
|
||||||
</div>
|
</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">
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
|
||||||
{currentRule.parts.map((part) => (
|
{currentRule.parts.map((part, index) => (
|
||||||
<NumberingRuleCard
|
<NumberingRuleCard
|
||||||
key={part.id}
|
key={`part-${part.order}-${index}`}
|
||||||
part={part}
|
part={part}
|
||||||
onUpdate={(updates) => handleUpdatePart(part.id, updates)}
|
onUpdate={(updates) => handleUpdatePart(part.order, updates)}
|
||||||
onDelete={() => handleDeletePart(part.id)}
|
onDelete={() => handleDeletePart(part.order)}
|
||||||
isPreview={isPreview}
|
isPreview={isPreview}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -767,43 +767,51 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 채번 규칙이 있는 필드에 대해 allocateCode 호출
|
// 채번 규칙이 있는 필드에 대해 allocateCode 호출 (🚀 병렬 처리로 최적화)
|
||||||
if (Object.keys(fieldsWithNumbering).length > 0) {
|
if (Object.keys(fieldsWithNumbering).length > 0) {
|
||||||
console.log("🎯 [EditModal] 채번 규칙 할당 시작");
|
console.log("🎯 [EditModal] 채번 규칙 할당 시작, formData:", {
|
||||||
|
material: formData.material,
|
||||||
|
allKeys: Object.keys(formData),
|
||||||
|
});
|
||||||
const { allocateNumberingCode } = await import("@/lib/api/numberingRule");
|
const { allocateNumberingCode } = await import("@/lib/api/numberingRule");
|
||||||
|
|
||||||
let hasAllocationFailure = false;
|
// 🚀 Promise.all로 병렬 처리 (여러 채번 필드가 있을 경우 성능 향상)
|
||||||
const failedFields: string[] = [];
|
const allocationPromises = Object.entries(fieldsWithNumbering).map(
|
||||||
|
async ([fieldName, ruleId]) => {
|
||||||
for (const [fieldName, ruleId] of Object.entries(fieldsWithNumbering)) {
|
|
||||||
try {
|
|
||||||
// 🆕 사용자가 편집한 값을 전달 (수동 입력 부분 추출용)
|
|
||||||
const userInputCode = dataToSave[fieldName] as string;
|
const userInputCode = dataToSave[fieldName] as string;
|
||||||
console.log(`🔄 [EditModal] ${fieldName} 필드에 대해 allocateCode 호출: ${ruleId}, 사용자입력: ${userInputCode}`);
|
console.log(`🔄 [EditModal] ${fieldName} 필드에 대해 allocateCode 호출: ${ruleId}`);
|
||||||
const allocateResult = await allocateNumberingCode(ruleId, userInputCode, formData);
|
|
||||||
|
try {
|
||||||
if (allocateResult.success && allocateResult.data?.generatedCode) {
|
const allocateResult = await allocateNumberingCode(ruleId, userInputCode, formData);
|
||||||
const newCode = allocateResult.data.generatedCode;
|
|
||||||
console.log(`✅ [EditModal] ${fieldName} 새 코드 할당: ${userInputCode} → ${newCode}`);
|
if (allocateResult.success && allocateResult.data?.generatedCode) {
|
||||||
dataToSave[fieldName] = newCode;
|
return { fieldName, success: true, code: allocateResult.data.generatedCode };
|
||||||
} else {
|
} else {
|
||||||
console.warn(`⚠️ [EditModal] ${fieldName} 코드 할당 실패:`, allocateResult.error);
|
console.warn(`⚠️ [EditModal] ${fieldName} 코드 할당 실패:`, allocateResult.error);
|
||||||
if (!dataToSave[fieldName] || dataToSave[fieldName] === "") {
|
return { fieldName, success: false, hasExistingValue: !!(dataToSave[fieldName]) };
|
||||||
hasAllocationFailure = true;
|
|
||||||
failedFields.push(fieldName);
|
|
||||||
}
|
}
|
||||||
|
} catch (allocateError) {
|
||||||
|
console.error(`❌ [EditModal] ${fieldName} 코드 할당 오류:`, allocateError);
|
||||||
|
return { fieldName, success: false, hasExistingValue: !!(dataToSave[fieldName]) };
|
||||||
}
|
}
|
||||||
} catch (allocateError) {
|
}
|
||||||
console.error(`❌ [EditModal] ${fieldName} 코드 할당 오류:`, allocateError);
|
);
|
||||||
if (!dataToSave[fieldName] || dataToSave[fieldName] === "") {
|
|
||||||
hasAllocationFailure = true;
|
const allocationResults = await Promise.all(allocationPromises);
|
||||||
failedFields.push(fieldName);
|
|
||||||
}
|
// 결과 처리
|
||||||
|
const failedFields: string[] = [];
|
||||||
|
for (const result of allocationResults) {
|
||||||
|
if (result.success && result.code) {
|
||||||
|
console.log(`✅ [EditModal] ${result.fieldName} 새 코드 할당: ${result.code}`);
|
||||||
|
dataToSave[result.fieldName] = result.code;
|
||||||
|
} else if (!result.hasExistingValue) {
|
||||||
|
failedFields.push(result.fieldName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 채번 규칙 할당 실패 시 저장 중단
|
// 채번 규칙 할당 실패 시 저장 중단
|
||||||
if (hasAllocationFailure) {
|
if (failedFields.length > 0) {
|
||||||
const fieldNames = failedFields.join(", ");
|
const fieldNames = failedFields.join(", ");
|
||||||
toast.error(`채번 규칙 할당에 실패했습니다 (${fieldNames}). 화면 설정에서 채번 규칙을 확인해주세요.`);
|
toast.error(`채번 규칙 할당에 실패했습니다 (${fieldNames}). 화면 설정에서 채번 규칙을 확인해주세요.`);
|
||||||
console.error(`❌ [EditModal] 채번 규칙 할당 실패로 저장 중단. 실패 필드: ${fieldNames}`);
|
console.error(`❌ [EditModal] 채번 규칙 할당 실패로 저장 중단. 실패 필드: ${fieldNames}`);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createContext, useContext, useState, useCallback, useMemo, useRef } from "react";
|
import React, { createContext, useContext, useState, useCallback, useMemo, useRef } from "react";
|
||||||
import { ConditionalConfig, CascadingConfig } from "@/types/v2-components";
|
import { ConditionalConfig, CascadingConfig, LayerConfig, LayerCondition } from "@/types/v2-components";
|
||||||
import { ValidationRule } from "@/types/v2-core";
|
import { ValidationRule } from "@/types/v2-core";
|
||||||
import type {
|
import type {
|
||||||
FormStatus,
|
FormStatus,
|
||||||
|
|
@ -89,6 +89,12 @@ export interface V2FormContextValue {
|
||||||
addRepeaterRow: (fieldName: string, row: Record<string, unknown>) => void;
|
addRepeaterRow: (fieldName: string, row: Record<string, unknown>) => void;
|
||||||
updateRepeaterRow: (fieldName: string, index: number, row: Record<string, unknown>) => void;
|
updateRepeaterRow: (fieldName: string, index: number, row: Record<string, unknown>) => void;
|
||||||
deleteRepeaterRow: (fieldName: string, index: number) => void;
|
deleteRepeaterRow: (fieldName: string, index: number) => void;
|
||||||
|
|
||||||
|
// 조건부 레이어 시스템
|
||||||
|
layers: LayerConfig[];
|
||||||
|
setLayers: (layers: LayerConfig[]) => void;
|
||||||
|
evaluateLayer: (layer: LayerConfig) => boolean;
|
||||||
|
isComponentVisible: (componentId: string) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Context 생성 =====
|
// ===== Context 생성 =====
|
||||||
|
|
|
||||||
|
|
@ -360,6 +360,10 @@ export const V2Input = forwardRef<HTMLDivElement, V2InputProps>((props, ref) =>
|
||||||
// 채번 타입 자동생성 상태
|
// 채번 타입 자동생성 상태
|
||||||
const [isGeneratingNumbering, setIsGeneratingNumbering] = useState(false);
|
const [isGeneratingNumbering, setIsGeneratingNumbering] = useState(false);
|
||||||
const hasGeneratedNumberingRef = useRef(false);
|
const hasGeneratedNumberingRef = useRef(false);
|
||||||
|
|
||||||
|
// formData를 ref로 관리하여 closure 문제 해결 (채번 코드 생성 시 최신 값 사용)
|
||||||
|
const formDataRef = useRef(formData);
|
||||||
|
formDataRef.current = formData;
|
||||||
|
|
||||||
// tableName 추출 (여러 소스에서 확인)
|
// tableName 추출 (여러 소스에서 확인)
|
||||||
// 1. props에서 직접 전달받은 값
|
// 1. props에서 직접 전달받은 값
|
||||||
|
|
@ -565,8 +569,14 @@ export const V2Input = forwardRef<HTMLDivElement, V2InputProps>((props, ref) =>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 채번 코드 생성 (formData 전달하여 카테고리 값 기반 생성)
|
// 채번 코드 생성 (formDataRef.current 사용하여 최신 formData 전달)
|
||||||
const previewResponse = await previewNumberingCode(numberingRuleId, formData);
|
const currentFormData = formDataRef.current;
|
||||||
|
console.log("🔍 [V2Input] 채번 미리보기 호출:", {
|
||||||
|
numberingRuleId,
|
||||||
|
formDataKeys: Object.keys(currentFormData),
|
||||||
|
materialValue: currentFormData.material // 재질 값 로깅
|
||||||
|
});
|
||||||
|
const previewResponse = await previewNumberingCode(numberingRuleId, currentFormData);
|
||||||
|
|
||||||
if (previewResponse.success && previewResponse.data?.generatedCode) {
|
if (previewResponse.success && previewResponse.data?.generatedCode) {
|
||||||
const generatedCode = previewResponse.data.generatedCode;
|
const generatedCode = previewResponse.data.generatedCode;
|
||||||
|
|
|
||||||
|
|
@ -115,31 +115,36 @@ export function V2CategoryManagerComponent({
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef} className="flex h-full min-h-[10px] gap-0" style={{ height: config.height }}>
|
<div ref={containerRef} className="flex h-full min-h-[10px] gap-0 overflow-hidden" style={{ height: config.height }}>
|
||||||
{/* 좌측: 카테고리 컬럼 리스트 */}
|
{/* 좌측: 카테고리 컬럼 리스트 - 스크롤 가능 */}
|
||||||
{config.showColumnList && (
|
{config.showColumnList && (
|
||||||
<>
|
<>
|
||||||
<div style={{ width: `${leftWidth}%` }} className="pr-3">
|
<div
|
||||||
<CategoryColumnList
|
style={{ width: `${leftWidth}%` }}
|
||||||
tableName={effectiveTableName}
|
className="flex h-full flex-col overflow-hidden pr-3"
|
||||||
selectedColumn={selectedColumn?.uniqueKey || null}
|
>
|
||||||
onColumnSelect={handleColumnSelect}
|
<div className="flex-1 overflow-y-auto">
|
||||||
menuObjid={effectiveMenuObjid}
|
<CategoryColumnList
|
||||||
/>
|
tableName={effectiveTableName}
|
||||||
|
selectedColumn={selectedColumn?.uniqueKey || null}
|
||||||
|
onColumnSelect={handleColumnSelect}
|
||||||
|
menuObjid={effectiveMenuObjid}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 리사이저 */}
|
{/* 리사이저 */}
|
||||||
<div
|
<div
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
className="group hover:bg-accent/50 relative flex w-3 cursor-col-resize items-center justify-center border-r transition-colors"
|
className="group hover:bg-accent/50 relative flex h-full w-3 shrink-0 cursor-col-resize items-center justify-center border-r transition-colors"
|
||||||
>
|
>
|
||||||
<GripVertical className="text-muted-foreground group-hover:text-foreground h-4 w-4 transition-colors" />
|
<GripVertical className="text-muted-foreground group-hover:text-foreground h-4 w-4 transition-colors" />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 우측: 카테고리 값 관리 */}
|
{/* 우측: 카테고리 값 관리 - 고정 */}
|
||||||
<div style={{ width: config.showColumnList ? `${100 - leftWidth - 1}%` : "100%" }} className="flex flex-col pl-3">
|
<div style={{ width: config.showColumnList ? `${100 - leftWidth - 1}%` : "100%" }} className="flex h-full flex-col overflow-hidden pl-3">
|
||||||
{/* 뷰 모드 토글 */}
|
{/* 뷰 모드 토글 */}
|
||||||
{config.showViewModeToggle && (
|
{config.showViewModeToggle && (
|
||||||
<div className="mb-2 flex items-center justify-end gap-1">
|
<div className="mb-2 flex items-center justify-end gap-1">
|
||||||
|
|
@ -167,8 +172,8 @@ export function V2CategoryManagerComponent({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 카테고리 값 관리 컴포넌트 */}
|
{/* 카테고리 값 관리 컴포넌트 - 스크롤 가능 */}
|
||||||
<div className="min-h-0 flex-1">
|
<div className="min-h-0 flex-1 overflow-y-auto">
|
||||||
{selectedColumn ? (
|
{selectedColumn ? (
|
||||||
viewMode === "tree" ? (
|
viewMode === "tree" ? (
|
||||||
<CategoryValueManagerTree
|
<CategoryValueManagerTree
|
||||||
|
|
|
||||||
|
|
@ -544,3 +544,28 @@ export const LEGACY_TO_V2_MAP: Record<string, V2ComponentType> = {
|
||||||
// Button (Input의 버튼 모드)
|
// Button (Input의 버튼 모드)
|
||||||
"button-primary": "V2Input",
|
"button-primary": "V2Input",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ===== 조건부 레이어 시스템 =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 레이어 조건 설정
|
||||||
|
* 특정 필드값에 따라 레이어 활성화 여부를 결정
|
||||||
|
*/
|
||||||
|
export interface LayerCondition {
|
||||||
|
field: string; // 트리거 필드 (columnName 또는 탭ID)
|
||||||
|
operator: "=" | "!=" | "in" | "notIn" | "isEmpty" | "isNotEmpty";
|
||||||
|
value: string | string[]; // 비교값
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 레이어 설정
|
||||||
|
* 특정 조건이 충족될 때 표시되는 컴포넌트들의 그룹
|
||||||
|
*/
|
||||||
|
export interface LayerConfig {
|
||||||
|
layerId: string; // 고유 ID
|
||||||
|
layerName: string; // 표시명 (설정용)
|
||||||
|
conditions: LayerCondition[]; // 조건 목록
|
||||||
|
conditionLogic?: "AND" | "OR"; // 조건 조합 방식 (기본: AND)
|
||||||
|
targetComponents: string[]; // 표시할 컴포넌트 ID 목록
|
||||||
|
alwaysVisible?: boolean; // 항상 표시 (조건 무시)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue