diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx index e955fddc..85e502a0 100644 --- a/frontend/components/screen/EditModal.tsx +++ b/frontend/components/screen/EditModal.tsx @@ -62,7 +62,7 @@ export const EditModal: React.FC = ({ className }) => { // 폼 데이터 상태 (편집 데이터로 초기화됨) const [formData, setFormData] = useState>({}); const [originalData, setOriginalData] = useState>({}); - + // 🆕 그룹 데이터 상태 (같은 order_no의 모든 품목) const [groupData, setGroupData] = useState[]>([]); const [originalGroupData, setOriginalGroupData] = useState[]>([]); @@ -118,7 +118,8 @@ export const EditModal: React.FC = ({ className }) => { // 전역 모달 이벤트 리스너 useEffect(() => { const handleOpenEditModal = (event: CustomEvent) => { - const { screenId, title, description, modalSize, editData, onSave, groupByColumns, tableName, isCreateMode } = event.detail; + const { screenId, title, description, modalSize, editData, onSave, groupByColumns, tableName, isCreateMode } = + event.detail; setModalState({ isOpen: true, @@ -136,8 +137,8 @@ export const EditModal: React.FC = ({ className }) => { setFormData(editData || {}); // 🆕 isCreateMode가 true이면 originalData를 빈 객체로 설정 (INSERT 모드) // originalData가 비어있으면 INSERT, 있으면 UPDATE로 처리됨 - setOriginalData(isCreateMode ? {} : (editData || {})); - + setOriginalData(isCreateMode ? {} : editData || {}); + if (isCreateMode) { console.log("[EditModal] 생성 모드로 열림, 초기값:", editData); } @@ -170,7 +171,7 @@ export const EditModal: React.FC = ({ className }) => { useEffect(() => { if (modalState.isOpen && modalState.screenId) { loadScreenData(modalState.screenId); - + // 🆕 그룹 데이터 조회 (groupByColumns가 있는 경우) if (modalState.groupByColumns && modalState.groupByColumns.length > 0 && modalState.tableName) { loadGroupData(); @@ -308,7 +309,7 @@ export const EditModal: React.FC = ({ className }) => { // universal-form-modal 등에서 자체 저장 완료 후 호출된 경우 스킵 if (saveData?._saveCompleted) { console.log("[EditModal] 자체 저장 완료된 컴포넌트에서 호출됨 - 저장 스킵"); - + // 부모 컴포넌트의 onSave 콜백 실행 (테이블 새로고침) if (modalState.onSave) { try { @@ -317,7 +318,7 @@ export const EditModal: React.FC = ({ className }) => { console.error("onSave 콜백 에러:", callbackError); } } - + handleClose(); return; } @@ -342,13 +343,13 @@ export const EditModal: React.FC = ({ className }) => { // 🆕 날짜 필드 정규화 함수 (YYYY-MM-DD 형식으로 변환) const normalizeDateField = (value: any): string | null => { if (!value) return null; - + // ISO 8601 형식 (2025-11-26T00:00:00.000Z) 또는 Date 객체 if (value instanceof Date || typeof value === "string") { try { const date = new Date(value); if (isNaN(date.getTime())) return null; - + // YYYY-MM-DD 형식으로 변환 const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); @@ -359,7 +360,7 @@ export const EditModal: React.FC = ({ className }) => { return null; } } - + return null; }; @@ -380,7 +381,7 @@ export const EditModal: React.FC = ({ className }) => { const insertData: Record = { ...currentData }; console.log("📦 [신규 품목] 복사 직후 insertData:", insertData); console.log("📋 [신규 품목] insertData 키 목록:", Object.keys(insertData)); - + delete insertData.id; // id는 자동 생성되므로 제거 // 🆕 날짜 필드 정규화 (YYYY-MM-DD 형식으로 변환) @@ -464,9 +465,7 @@ export const EditModal: React.FC = ({ className }) => { for (const currentData of groupData) { if (currentData.id) { // id 기반 매칭 (인덱스 기반 X) - const originalItemData = originalGroupData.find( - (orig) => orig.id === currentData.id - ); + const originalItemData = originalGroupData.find((orig) => orig.id === currentData.id); if (!originalItemData) { console.warn(`원본 데이터를 찾을 수 없습니다 (id: ${currentData.id})`); @@ -476,13 +475,13 @@ export const EditModal: React.FC = ({ className }) => { // 🆕 값 정규화 함수 (타입 통일) const normalizeValue = (val: any, fieldName?: string): any => { if (val === null || val === undefined || val === "") return null; - + // 날짜 필드인 경우 YYYY-MM-DD 형식으로 정규화 if (fieldName && dateFields.includes(fieldName)) { const normalizedDate = normalizeDateField(val); return normalizedDate; } - + if (typeof val === "string" && !isNaN(Number(val))) { // 숫자로 변환 가능한 문자열은 숫자로 return Number(val); @@ -539,9 +538,7 @@ export const EditModal: React.FC = ({ className }) => { // 3️⃣ 삭제된 품목 제거 (원본에는 있지만 현재 데이터에는 없는 항목) const currentIds = new Set(groupData.map((item) => item.id).filter(Boolean)); - const deletedItems = originalGroupData.filter( - (orig) => orig.id && !currentIds.has(orig.id) - ); + const deletedItems = originalGroupData.filter((orig) => orig.id && !currentIds.has(orig.id)); for (const deletedItem of deletedItems) { console.log("🗑️ 품목 삭제:", deletedItem); @@ -549,7 +546,7 @@ export const EditModal: React.FC = ({ className }) => { try { const response = await dynamicFormApi.deleteFormDataFromTable( deletedItem.id, - screenData.screenInfo.tableName + screenData.screenInfo.tableName, ); if (response.success) { @@ -592,11 +589,11 @@ export const EditModal: React.FC = ({ className }) => { // originalData가 비어있으면 INSERT, 있으면 UPDATE const isCreateMode = Object.keys(originalData).length === 0; - + if (isCreateMode) { // INSERT 모드 console.log("[EditModal] INSERT 모드 - 새 데이터 생성:", formData); - + const response = await dynamicFormApi.saveFormData({ screenId: modalState.screenId!, tableName: screenData.screenInfo.tableName, @@ -701,10 +698,7 @@ export const EditModal: React.FC = ({ className }) => { return ( - +
{modalState.title || "데이터 수정"} @@ -717,7 +711,7 @@ export const EditModal: React.FC = ({ className }) => {
-
+
{loading ? (
@@ -751,7 +745,6 @@ export const EditModal: React.FC = ({ className }) => { }, }; - const groupedDataProp = groupData.length > 0 ? groupData : undefined; // 🔑 첨부파일 컴포넌트가 행(레코드) 단위로 파일을 저장할 수 있도록 tableName 추가 @@ -760,7 +753,7 @@ export const EditModal: React.FC = ({ className }) => { tableName: screenData.screenInfo?.tableName, // 테이블명 추가 screenId: modalState.screenId, // 화면 ID 추가 }; - + // 🔍 디버깅: enrichedFormData 확인 console.log("🔑 [EditModal] enrichedFormData 생성:", { "screenData.screenInfo": screenData.screenInfo, @@ -775,6 +768,7 @@ export const EditModal: React.FC = ({ className }) => { component={adjustedComponent} allComponents={screenData.components} formData={enrichedFormData} + originalData={originalData} // 🆕 원본 데이터 전달 (수정 모드에서 UniversalFormModal 초기화용) onFormDataChange={(fieldName, value) => { // 🆕 그룹 데이터가 있으면 처리 if (groupData.length > 0) { @@ -787,14 +781,14 @@ export const EditModal: React.FC = ({ className }) => { prev.map((item) => ({ ...item, [fieldName]: value, - })) + })), ); } } else { - setFormData((prev) => ({ - ...prev, - [fieldName]: value, - })); + setFormData((prev) => ({ + ...prev, + [fieldName]: value, + })); } }} screenInfo={{ diff --git a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx index ae6a01d8..b453729d 100644 --- a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx +++ b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx @@ -47,7 +47,7 @@ export interface ButtonPrimaryComponentProps extends ComponentRendererProps { // 테이블 선택된 행 정보 (다중 선택 액션용) selectedRows?: any[]; selectedRowsData?: any[]; - + // 테이블 정렬 정보 (엑셀 다운로드용) sortBy?: string; sortOrder?: "asc" | "desc"; @@ -57,10 +57,10 @@ export interface ButtonPrimaryComponentProps extends ComponentRendererProps { // 플로우 선택된 데이터 정보 (플로우 위젯 선택 액션용) flowSelectedData?: any[]; flowSelectedStepId?: number | null; - + // 🆕 같은 화면의 모든 컴포넌트 (TableList 자동 감지용) allComponents?: any[]; - + // 🆕 부모창에서 전달된 그룹 데이터 (모달에서 부모 데이터 접근용) groupedData?: Record[]; } @@ -109,11 +109,11 @@ export const ButtonPrimaryComponent: React.FC = ({ const splitPanelContext = useSplitPanelContext(); // 분할 패널 컨텍스트 // 🆕 ScreenContext에서 splitPanelPosition 가져오기 (중첩 화면에서도 작동) const splitPanelPosition = screenContext?.splitPanelPosition; - + // 🆕 tableName이 props로 전달되지 않으면 ScreenContext에서 가져오기 const effectiveTableName = tableName || screenContext?.tableName; const effectiveScreenId = screenId || screenContext?.screenId; - + // 🆕 props에서 onSave 추출 (명시적으로 선언되지 않은 경우 ...props에서 추출) const propsOnSave = (props as any).onSave as (() => Promise) | undefined; const finalOnSave = onSave || propsOnSave; @@ -169,10 +169,10 @@ export const ButtonPrimaryComponent: React.FC = ({ if (!shouldFetchStatus) return; let isMounted = true; - + const fetchStatus = async () => { if (!isMounted) return; - + try { const response = await apiClient.post(`/table-management/tables/${statusTableName}/data`, { page: 1, @@ -180,12 +180,12 @@ export const ButtonPrimaryComponent: React.FC = ({ search: { [statusKeyField]: userId }, autoFilter: true, }); - + if (!isMounted) return; - + const rows = response.data?.data?.data || response.data?.data?.rows || response.data?.rows || []; const firstRow = Array.isArray(rows) ? rows[0] : null; - + if (response.data?.success && firstRow) { const newStatus = firstRow[statusFieldName]; if (newStatus !== vehicleStatus) { @@ -206,10 +206,10 @@ export const ButtonPrimaryComponent: React.FC = ({ // 즉시 실행 setStatusLoading(true); fetchStatus(); - + // 2초마다 갱신 const interval = setInterval(fetchStatus, 2000); - + return () => { isMounted = false; clearInterval(interval); @@ -219,22 +219,22 @@ export const ButtonPrimaryComponent: React.FC = ({ // 버튼 비활성화 조건 계산 const isOperationButtonDisabled = useMemo(() => { const actionConfig = component.componentConfig?.action; - + if (actionConfig?.type !== "operation_control") return false; // 1. 출발지/도착지 필수 체크 if (actionConfig?.requireLocationFields) { const departureField = actionConfig.trackingDepartureField || "departure"; const destinationField = actionConfig.trackingArrivalField || "destination"; - + const departure = formData?.[departureField]; const destination = formData?.[destinationField]; - - // console.log("🔍 [ButtonPrimary] 출발지/도착지 체크:", { - // departureField, destinationField, departure, destination, - // buttonLabel: component.label + + // console.log("🔍 [ButtonPrimary] 출발지/도착지 체크:", { + // departureField, destinationField, departure, destination, + // buttonLabel: component.label // }); - + if (!departure || departure === "" || !destination || destination === "") { // console.log("🚫 [ButtonPrimary] 출발지/도착지 미선택 → 비활성화:", component.label); return true; @@ -246,20 +246,20 @@ export const ButtonPrimaryComponent: React.FC = ({ const statusField = actionConfig.statusCheckField || "status"; // API 조회 결과를 우선 사용 (실시간 DB 상태 반영) const currentStatus = vehicleStatus || formData?.[statusField]; - + const conditionType = actionConfig.statusConditionType || "enableOn"; const conditionValues = (actionConfig.statusConditionValues || "") .split(",") .map((v: string) => v.trim()) .filter((v: string) => v); - // console.log("🔍 [ButtonPrimary] 상태 조건 체크:", { + // console.log("🔍 [ButtonPrimary] 상태 조건 체크:", { // statusField, // formDataStatus: formData?.[statusField], // apiStatus: vehicleStatus, - // currentStatus, - // conditionType, - // conditionValues, + // currentStatus, + // conditionType, + // conditionValues, // buttonLabel: component.label, // }); @@ -274,7 +274,7 @@ export const ButtonPrimaryComponent: React.FC = ({ // console.log("🚫 [ButtonPrimary] 상태값 없음 → 비활성화:", component.label); return true; } - + if (conditionValues.length > 0) { if (conditionType === "enableOn") { // 이 상태일 때만 활성화 @@ -539,7 +539,7 @@ export const ButtonPrimaryComponent: React.FC = ({ */ const handleTransferDataAction = async (actionConfig: any) => { const dataTransferConfig = actionConfig.dataTransfer; - + if (!dataTransferConfig) { toast.error("데이터 전달 설정이 없습니다."); return; @@ -553,15 +553,15 @@ export const ButtonPrimaryComponent: React.FC = ({ try { // 1. 소스 컴포넌트에서 데이터 가져오기 let sourceProvider = screenContext.getDataProvider(dataTransferConfig.sourceComponentId); - + // 🆕 소스 컴포넌트를 찾을 수 없으면, 현재 화면에서 테이블 리스트 자동 탐색 // (조건부 컨테이너의 다른 섹션으로 전환했을 때 이전 컴포넌트 ID가 남아있는 경우 대응) if (!sourceProvider) { console.log(`⚠️ [ButtonPrimary] 지정된 소스 컴포넌트를 찾을 수 없음: ${dataTransferConfig.sourceComponentId}`); console.log(`🔍 [ButtonPrimary] 현재 화면에서 DataProvider 자동 탐색...`); - + const allProviders = screenContext.getAllDataProviders(); - + // 테이블 리스트 우선 탐색 for (const [id, provider] of allProviders) { if (provider.componentType === "table-list") { @@ -570,16 +570,18 @@ export const ButtonPrimaryComponent: React.FC = ({ break; } } - + // 테이블 리스트가 없으면 첫 번째 DataProvider 사용 if (!sourceProvider && allProviders.size > 0) { const firstEntry = allProviders.entries().next().value; if (firstEntry) { sourceProvider = firstEntry[1]; - console.log(`✅ [ButtonPrimary] 첫 번째 DataProvider 사용: ${firstEntry[0]} (${sourceProvider.componentType})`); + console.log( + `✅ [ButtonPrimary] 첫 번째 DataProvider 사용: ${firstEntry[0]} (${sourceProvider.componentType})`, + ); } } - + if (!sourceProvider) { toast.error("데이터를 제공할 수 있는 컴포넌트를 찾을 수 없습니다."); return; @@ -587,12 +589,12 @@ export const ButtonPrimaryComponent: React.FC = ({ } const rawSourceData = sourceProvider.getSelectedData(); - + // 🆕 배열이 아닌 경우 배열로 변환 - const sourceData = Array.isArray(rawSourceData) ? rawSourceData : (rawSourceData ? [rawSourceData] : []); - + const sourceData = Array.isArray(rawSourceData) ? rawSourceData : rawSourceData ? [rawSourceData] : []; + console.log("📦 소스 데이터:", { rawSourceData, sourceData, isArray: Array.isArray(rawSourceData) }); - + if (!sourceData || sourceData.length === 0) { toast.warning("선택된 데이터가 없습니다."); return; @@ -600,31 +602,32 @@ export const ButtonPrimaryComponent: React.FC = ({ // 1.5. 추가 데이터 소스 처리 (예: 조건부 컨테이너의 카테고리 값) let additionalData: Record = {}; - + // 방법 1: additionalSources 설정에서 가져오기 if (dataTransferConfig.additionalSources && Array.isArray(dataTransferConfig.additionalSources)) { for (const additionalSource of dataTransferConfig.additionalSources) { const additionalProvider = screenContext.getDataProvider(additionalSource.componentId); - + if (additionalProvider) { const additionalValues = additionalProvider.getSelectedData(); - + if (additionalValues && additionalValues.length > 0) { // 첫 번째 값 사용 (조건부 컨테이너는 항상 1개) const firstValue = additionalValues[0]; - + // fieldName이 지정되어 있으면 그 필드만 추출 if (additionalSource.fieldName) { - additionalData[additionalSource.fieldName] = firstValue[additionalSource.fieldName] || firstValue.condition || firstValue; + additionalData[additionalSource.fieldName] = + firstValue[additionalSource.fieldName] || firstValue.condition || firstValue; } else { // fieldName이 없으면 전체 객체 병합 additionalData = { ...additionalData, ...firstValue }; } - + console.log("📦 추가 데이터 수집 (additionalSources):", { sourceId: additionalSource.componentId, fieldName: additionalSource.fieldName, - value: additionalData[additionalSource.fieldName || 'all'], + value: additionalData[additionalSource.fieldName || "all"], }); } } @@ -639,7 +642,7 @@ export const ButtonPrimaryComponent: React.FC = ({ const conditionalValue = formData.__conditionalContainerValue; const conditionalLabel = formData.__conditionalContainerLabel; const controlField = formData.__conditionalContainerControlField; // 🆕 제어 필드명 직접 사용 - + // 🆕 controlField가 있으면 그것을 필드명으로 사용 (자동 매핑!) if (controlField) { additionalData[controlField] = conditionalValue; @@ -651,7 +654,7 @@ export const ButtonPrimaryComponent: React.FC = ({ } else { // controlField가 없으면 기존 방식: formData에서 같은 값을 가진 키 찾기 for (const [key, value] of Object.entries(formData)) { - if (value === conditionalValue && !key.startsWith('__')) { + if (value === conditionalValue && !key.startsWith("__")) { additionalData[key] = conditionalValue; console.log("📦 조건부 컨테이너 값 자동 포함:", { fieldName: key, @@ -661,12 +664,12 @@ export const ButtonPrimaryComponent: React.FC = ({ break; } } - + // 못 찾았으면 기본 필드명 사용 - if (!Object.keys(additionalData).some(k => !k.startsWith('__'))) { - additionalData['condition_type'] = conditionalValue; + if (!Object.keys(additionalData).some((k) => !k.startsWith("__"))) { + additionalData["condition_type"] = conditionalValue; console.log("📦 조건부 컨테이너 값 (기본 필드명):", { - fieldName: 'condition_type', + fieldName: "condition_type", value: conditionalValue, }); } @@ -698,7 +701,7 @@ export const ButtonPrimaryComponent: React.FC = ({ // 4. 매핑 규칙 적용 + 추가 데이터 병합 const mappedData = sourceData.map((row) => { const mappedRow = applyMappingRules(row, dataTransferConfig.mappingRules || []); - + // 추가 데이터를 모든 행에 포함 return { ...mappedRow, @@ -718,7 +721,7 @@ export const ButtonPrimaryComponent: React.FC = ({ if (dataTransferConfig.targetType === "component") { // 같은 화면의 컴포넌트로 전달 const targetReceiver = screenContext.getDataReceiver(dataTransferConfig.targetComponentId); - + if (!targetReceiver) { toast.error(`타겟 컴포넌트를 찾을 수 없습니다: ${dataTransferConfig.targetComponentId}`); return; @@ -730,7 +733,7 @@ export const ButtonPrimaryComponent: React.FC = ({ mode: dataTransferConfig.mode || "append", mappingRules: dataTransferConfig.mappingRules || [], }); - + toast.success(`${sourceData.length}개 항목이 전달되었습니다.`); } else if (dataTransferConfig.targetType === "splitPanel") { // 🆕 분할 패널의 반대편 화면으로 전달 @@ -738,17 +741,18 @@ export const ButtonPrimaryComponent: React.FC = ({ toast.error("분할 패널 컨텍스트를 찾을 수 없습니다. 이 버튼이 분할 패널 내부에 있는지 확인하세요."); return; } - + // 🆕 useSplitPanelPosition 훅으로 위치 가져오기 (중첩된 화면에서도 작동) - // screenId로 찾는 것은 직접 임베드된 화면에서만 작동하므로, + // screenId로 찾는 것은 직접 임베드된 화면에서만 작동하므로, // SplitPanelPositionProvider로 전달된 위치를 우선 사용 - const currentPosition = splitPanelPosition || (screenId ? splitPanelContext.getPositionByScreenId(screenId) : null); - + const currentPosition = + splitPanelPosition || (screenId ? splitPanelContext.getPositionByScreenId(screenId) : null); + if (!currentPosition) { toast.error("분할 패널 내 위치를 확인할 수 없습니다. screenId: " + screenId); return; } - + console.log("📦 분할 패널 데이터 전달:", { currentPosition, splitPanelPositionFromHook: splitPanelPosition, @@ -756,14 +760,14 @@ export const ButtonPrimaryComponent: React.FC = ({ leftScreenId: splitPanelContext.leftScreenId, rightScreenId: splitPanelContext.rightScreenId, }); - + const result = await splitPanelContext.transferToOtherSide( currentPosition, mappedData, dataTransferConfig.targetComponentId, // 특정 컴포넌트 지정 (선택사항) - dataTransferConfig.mode || "append" + dataTransferConfig.mode || "append", ); - + if (result.success) { toast.success(result.message); } else { @@ -782,7 +786,6 @@ export const ButtonPrimaryComponent: React.FC = ({ if (dataTransferConfig.clearAfterTransfer) { sourceProvider.clearSelection(); } - } catch (error: any) { console.error("❌ 데이터 전달 실패:", error); toast.error(error.message || "데이터 전달 중 오류가 발생했습니다."); @@ -816,16 +819,20 @@ export const ButtonPrimaryComponent: React.FC = ({ // 2. groupedData (부모창에서 모달로 전달된 데이터) // 3. modalDataStore (분할 패널 등에서 선택한 데이터) let effectiveSelectedRowsData = selectedRowsData; - + // groupedData가 있으면 우선 사용 (모달에서 부모 데이터 접근) - if ((!effectiveSelectedRowsData || effectiveSelectedRowsData.length === 0) && groupedData && groupedData.length > 0) { + if ( + (!effectiveSelectedRowsData || effectiveSelectedRowsData.length === 0) && + groupedData && + groupedData.length > 0 + ) { effectiveSelectedRowsData = groupedData; console.log("🔗 [ButtonPrimaryComponent] groupedData에서 부모창 데이터 가져옴:", { count: groupedData.length, data: groupedData, }); } - + // modalDataStore에서 선택된 데이터 가져오기 (분할 패널 등에서 선택한 데이터) if ((!effectiveSelectedRowsData || effectiveSelectedRowsData.length === 0) && effectiveTableName) { try { @@ -833,11 +840,17 @@ export const ButtonPrimaryComponent: React.FC = ({ const dataRegistry = useModalDataStore.getState().dataRegistry; const modalData = dataRegistry[effectiveTableName]; if (modalData && modalData.length > 0) { - effectiveSelectedRowsData = modalData; + // modalDataStore는 {id, originalData, additionalData} 형태로 저장됨 + // originalData를 추출하여 실제 행 데이터를 가져옴 + effectiveSelectedRowsData = modalData.map((item: any) => { + // originalData가 있으면 그것을 사용, 없으면 item 자체 사용 (하위 호환성) + return item.originalData || item; + }); console.log("🔗 [ButtonPrimaryComponent] modalDataStore에서 선택된 데이터 가져옴:", { tableName: effectiveTableName, count: modalData.length, - data: modalData, + rawData: modalData, + extractedData: effectiveSelectedRowsData, }); } } catch (error) { @@ -847,7 +860,8 @@ export const ButtonPrimaryComponent: React.FC = ({ // 삭제 액션인데 선택된 데이터가 없으면 경고 메시지 표시하고 중단 const hasDataToDelete = - (effectiveSelectedRowsData && effectiveSelectedRowsData.length > 0) || (flowSelectedData && flowSelectedData.length > 0); + (effectiveSelectedRowsData && effectiveSelectedRowsData.length > 0) || + (flowSelectedData && flowSelectedData.length > 0); if (processedConfig.action.type === "delete" && !hasDataToDelete) { toast.warning("삭제할 항목을 먼저 선택해주세요."); @@ -1064,15 +1078,14 @@ export const ButtonPrimaryComponent: React.FC = ({ alignItems: "center", justifyContent: "center", // 🔧 크기에 따른 패딩 조정 - padding: - componentConfig.size === "sm" ? "0 0.75rem" : componentConfig.size === "lg" ? "0 1.25rem" : "0 1rem", + padding: componentConfig.size === "sm" ? "0 0.75rem" : componentConfig.size === "lg" ? "0 1.25rem" : "0 1rem", margin: "0", lineHeight: "1.25", boxShadow: finalDisabled ? "none" : "0 1px 2px 0 rgba(0, 0, 0, 0.05)", // 디자인 모드와 인터랙티브 모드 모두에서 사용자 스타일 적용 (width/height 제외) - ...(component.style ? Object.fromEntries( - Object.entries(component.style).filter(([key]) => key !== 'width' && key !== 'height') - ) : {}), + ...(component.style + ? Object.fromEntries(Object.entries(component.style).filter(([key]) => key !== "width" && key !== "height")) + : {}), }; const buttonContent = processedConfig.text !== undefined ? processedConfig.text : component.label || "버튼"; @@ -1094,7 +1107,7 @@ export const ButtonPrimaryComponent: React.FC = ({