Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into feature/v2-unified-renewal

This commit is contained in:
kjs
2026-01-15 09:22:31 +09:00
194 changed files with 52224 additions and 4678 deletions

View File

@@ -27,6 +27,7 @@ import { useScreenContextOptional } from "@/contexts/ScreenContext";
import { useSplitPanelContext, SplitPanelPosition } from "@/contexts/SplitPanelContext";
import { applyMappingRules } from "@/lib/utils/dataMapping";
import { apiClient } from "@/lib/api/client";
import { useScreenMultiLang } from "@/contexts/ScreenMultiLangContext";
export interface ButtonPrimaryComponentProps extends ComponentRendererProps {
config?: ButtonPrimaryConfig;
@@ -107,6 +108,7 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
const { isPreviewMode } = useScreenPreview(); // 프리뷰 모드 확인
const screenContext = useScreenContextOptional(); // 화면 컨텍스트
const splitPanelContext = useSplitPanelContext(); // 분할 패널 컨텍스트
const { getTranslatedText } = useScreenMultiLang(); // 다국어 컨텍스트
// 🆕 ScreenContext에서 splitPanelPosition 가져오기 (중첩 화면에서도 작동)
const splitPanelPosition = screenContext?.splitPanelPosition;
@@ -299,6 +301,20 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
// 🆕 modalDataStore에서 선택된 데이터 확인 (분할 패널 등에서 저장됨)
const [modalStoreData, setModalStoreData] = useState<Record<string, any[]>>({});
// 🆕 splitPanelContext?.selectedLeftData를 로컬 상태로 추적 (리렌더링 보장)
const [trackedSelectedLeftData, setTrackedSelectedLeftData] = useState<Record<string, any> | null>(null);
// splitPanelContext?.selectedLeftData 변경 감지 및 로컬 상태 동기화
useEffect(() => {
const newData = splitPanelContext?.selectedLeftData ?? null;
setTrackedSelectedLeftData(newData);
// console.log("🔄 [ButtonPrimary] selectedLeftData 변경 감지:", {
// label: component.label,
// hasData: !!newData,
// dataKeys: newData ? Object.keys(newData) : [],
// });
}, [splitPanelContext?.selectedLeftData, component.label]);
// modalDataStore 상태 구독 (실시간 업데이트)
useEffect(() => {
const actionConfig = component.componentConfig?.action;
@@ -357,8 +373,8 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
// 2. 분할 패널 좌측 선택 데이터 확인
if (rowSelectionSource === "auto" || rowSelectionSource === "splitPanelLeft") {
// SplitPanelContext에서 확인
if (splitPanelContext?.selectedLeftData && Object.keys(splitPanelContext.selectedLeftData).length > 0) {
// SplitPanelContext에서 확인 (trackedSelectedLeftData 사용으로 리렌더링 보장)
if (trackedSelectedLeftData && Object.keys(trackedSelectedLeftData).length > 0) {
if (!hasSelection) {
hasSelection = true;
selectionCount = 1;
@@ -406,7 +422,7 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
component.label,
selectedRows,
selectedRowsData,
splitPanelContext?.selectedLeftData,
trackedSelectedLeftData,
flowSelectedData,
splitPanelContext,
modalStoreData,
@@ -472,15 +488,50 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
...component.componentConfig, // 🔥 화면 디자이너에서 저장된 action 등 포함
} as ButtonPrimaryConfig;
// 🎨 동적 색상 설정 (속성편집 모달의 "색상" 필드와 연동)
const getLabelColor = () => {
if (isDeleteAction()) {
return component.style?.labelColor || "#ef4444"; // 빨간색 기본값 (Tailwind red-500)
// 🎨 동적 색상 설정 (webTypeConfig 우선, 레거시 style.labelColor 지원)
const getButtonBackgroundColor = () => {
// 1순위: webTypeConfig.backgroundColor (화면설정 모달에서 저장)
if (component.webTypeConfig?.backgroundColor) {
return component.webTypeConfig.backgroundColor;
}
return component.style?.labelColor || "#212121"; // 검은색 기본값 (shadcn/ui primary)
// 2순위: componentConfig.backgroundColor
if (componentConfig.backgroundColor) {
return componentConfig.backgroundColor;
}
// 3순위: style.backgroundColor
if (component.style?.backgroundColor) {
return component.style.backgroundColor;
}
// 4순위: style.labelColor (레거시)
if (component.style?.labelColor) {
return component.style.labelColor;
}
// 기본값: 삭제 버튼이면 빨강, 아니면 파랑
if (isDeleteAction()) {
return "#ef4444"; // 빨간색 (Tailwind red-500)
}
return "#3b82f6"; // 파란색 (Tailwind blue-500)
};
const buttonColor = getLabelColor();
const getButtonTextColor = () => {
// 1순위: webTypeConfig.textColor (화면설정 모달에서 저장)
if (component.webTypeConfig?.textColor) {
return component.webTypeConfig.textColor;
}
// 2순위: componentConfig.textColor
if (componentConfig.textColor) {
return componentConfig.textColor;
}
// 3순위: style.color
if (component.style?.color) {
return component.style.color;
}
// 기본값: 흰색
return "#ffffff";
};
const buttonColor = getButtonBackgroundColor();
const buttonTextColor = getButtonTextColor();
// 액션 설정 처리 - DB에서 문자열로 저장된 액션을 객체로 변환
const processedConfig = { ...componentConfig };
@@ -1092,7 +1143,7 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
: undefined,
} as ButtonActionContext;
// 확인이 필요한 액션인지 확인
// 확인이 필요한 액션인지 확인 (save/delete만 확인 다이얼로그 표시)
if (confirmationRequiredActions.includes(processedConfig.action.type)) {
// 확인 다이얼로그 표시
setPendingAction({
@@ -1229,8 +1280,8 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
minHeight: "40px",
border: "none",
borderRadius: "0.5rem",
backgroundColor: finalDisabled ? "#e5e7eb" : buttonColor, // 🔧 background → backgroundColor로 변경
color: finalDisabled ? "#9ca3af" : "white",
backgroundColor: finalDisabled ? "#e5e7eb" : buttonColor,
color: finalDisabled ? "#9ca3af" : buttonTextColor, // 🔧 webTypeConfig.textColor 지원
// 🔧 크기 설정 적용 (sm/md/lg)
fontSize: componentConfig.size === "sm" ? "0.75rem" : componentConfig.size === "lg" ? "1rem" : "0.875rem",
fontWeight: "600",
@@ -1249,7 +1300,10 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
...userStyle,
};
const buttonContent = processedConfig.text !== undefined ? processedConfig.text : component.label || "버튼";
// 다국어 적용: componentConfig.langKey가 있으면 번역 텍스트 사용
const langKey = (component as any).componentConfig?.langKey;
const originalButtonText = processedConfig.text !== undefined ? processedConfig.text : component.label || "버튼";
const buttonContent = getTranslatedText(langKey, originalButtonText);
return (
<>