[agent-pipeline] pipe-20260329143602-hu6g round-1

This commit is contained in:
DDD1542
2026-03-30 00:00:35 +09:00
parent 1405a19d1c
commit c24dd533b2
21 changed files with 196 additions and 196 deletions
@@ -74,12 +74,12 @@ export interface GroupCopyInfo {
sourceGroupId: number;
groupName: string;
groupCode: string;
screenIds: number[];
screen_ids: number[];
children: Array<{
sourceGroupId: number;
groupName: string;
groupCode: string;
screenIds: number[];
screen_ids: number[];
}>;
}
@@ -279,7 +279,7 @@ function TreeNode({
sourceGroupId: child.id,
groupName: child.group_name,
groupCode: child.group_code,
screenIds: (child.screens || [])
screen_ids: (child.screens || [])
.map((gs) => gs.screen_id)
.filter((id) => screensMap.has(id)),
}));
@@ -287,7 +287,7 @@ function TreeNode({
sourceGroupId: g.id,
groupName: g.group_name,
groupCode: g.group_code,
screenIds: directScreenIds,
screen_ids: directScreenIds,
children,
};
};
@@ -340,7 +340,7 @@ export function PopDeployModal({
</div>
{/* 메인 카테고리의 직접 화면 */}
{groupEntries
.filter((e) => groupInfo.screenIds.includes(e.screen_id))
.filter((e) => groupInfo.screen_ids.includes(e.screen_id))
.map((entry) => (
<div
key={entry.screen_id}
@@ -376,7 +376,7 @@ export function PopDeployModal({
</div>
{groupEntries
.filter((e) =>
child.screenIds.includes(e.screen_id),
child.screen_ids.includes(e.screen_id),
)
.map((entry) => (
<div
+19 -19
View File
@@ -24,7 +24,7 @@ import { useTabStore } from "@/stores/tabStore";
interface EditModalState {
isOpen: boolean;
screenId: number | null;
screen_id: number | null;
title: string;
description?: string;
modalSize: "sm" | "md" | "lg" | "xl";
@@ -88,7 +88,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
const isTabActive = !tabId || tabId === activeTabId;
const [modalState, setModalState] = useState<EditModalState>({
isOpen: false,
screenId: null,
screen_id: null,
title: "",
description: "",
modalSize: "md",
@@ -370,7 +370,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
setModalState({
isOpen: true,
screenId,
screen_id: screenId,
title,
description: description || "",
modalSize: modalSize || "lg",
@@ -462,15 +462,15 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
// 화면 데이터 로딩
useEffect(() => {
if (modalState.isOpen && modalState.screenId) {
loadScreenData(modalState.screenId);
if (modalState.isOpen && modalState.screen_id) {
loadScreenData(modalState.screen_id);
// 🆕 그룹 데이터 조회 (groupByColumns가 있는 경우)
if (modalState.groupByColumns && modalState.groupByColumns.length > 0 && modalState.tableName) {
loadGroupData();
}
}
}, [modalState.isOpen, modalState.screenId, modalState.groupByColumns, modalState.tableName]);
}, [modalState.isOpen, modalState.screen_id, modalState.groupByColumns, modalState.tableName]);
// 🆕 그룹 데이터 조회 함수
const loadGroupData = async () => {
@@ -791,7 +791,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
const handleClose = () => {
setModalState({
isOpen: false,
screenId: null,
screen_id: null,
title: "",
description: "",
modalSize: "md",
@@ -843,7 +843,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
groupData,
originalGroupData,
tableName: screenData.screenInfo.table_name,
screenId: modalState.screenId,
screenId: modalState.screen_id,
});
// 🆕 날짜 필드 정규화 함수 (YYYY-MM-DD 형식으로 변환)
@@ -950,7 +950,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
try {
const response = await dynamicFormApi.saveFormData({
screen_id: modalState.screenId || 0,
screen_id: modalState.screen_id || 0,
table_name: screenData.screenInfo.table_name,
data: insertData,
});
@@ -1090,7 +1090,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
const response = await dynamicFormApi.deleteFormDataFromTable(
deletedItem.id,
screenData.screenInfo.table_name,
modalState.screenId || screenData.screenInfo?.id,
modalState.screen_id || screenData.screenInfo?.id,
);
if (response.success) {
@@ -1165,7 +1165,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
// 제어로직 실행
await ButtonActionExecutor.executeAfterSaveControl(controlConfig, {
formData: modalState.editData,
screenId: modalState.buttonContext?.screenId || modalState.screenId,
screenId: modalState.buttonContext?.screen_id || modalState.screen_id,
tableName: modalState.buttonContext?.tableName || screenData?.screenInfo?.table_name,
userId: userId,
companyCode: companyCode,
@@ -1334,7 +1334,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
console.log("[EditModal] 최종 저장 데이터:", masterDataToSave);
const response = await dynamicFormApi.saveFormData({
screen_id: modalState.screenId!,
screen_id: modalState.screen_id!,
table_name: screenData.screenInfo.table_name,
data: masterDataToSave,
});
@@ -1373,7 +1373,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
await ButtonActionExecutor.executeAfterSaveControl(controlConfig, {
formData,
screenId: modalState.buttonContext?.screenId || modalState.screenId,
screenId: modalState.buttonContext?.screen_id || modalState.screen_id,
tableName: modalState.buttonContext?.tableName || screenData?.screenInfo?.table_name,
userId: userId,
companyCode: companyCode,
@@ -1499,7 +1499,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
await ButtonActionExecutor.executeAfterSaveControl(controlConfig, {
formData,
screenId: modalState.buttonContext?.screenId || modalState.screenId,
screenId: modalState.buttonContext?.screen_id || modalState.screen_id,
tableName: modalState.buttonContext?.tableName || screenData?.screenInfo?.table_name,
userId: userId,
companyCode: companyCode,
@@ -1611,7 +1611,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
</div>
) : screenData ? (
<ScreenContextProvider
screenId={modalState.screenId || undefined}
screenId={modalState.screen_id || undefined}
tableName={screenData.screenInfo?.table_name}
>
<div
@@ -1674,7 +1674,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
...formData,
...(groupData.length > 0 ? groupData[0] : {}),
tableName: screenData.screenInfo?.table_name,
screenId: modalState.screenId,
screenId: modalState.screen_id,
};
return (
@@ -1704,7 +1704,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
}
}}
screenInfo={{
id: modalState.screenId!,
id: modalState.screen_id!,
tableName: screenData.screenInfo?.table_name,
}}
menuObjid={modalState.menuObjid}
@@ -1735,7 +1735,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
...formData,
...(groupData.length > 0 ? groupData[0] : {}),
tableName: screenData.screenInfo?.table_name,
screenId: modalState.screenId,
screenId: modalState.screen_id,
};
const groupedDataProp = groupData.length > 0 ? groupData : undefined;
@@ -1767,7 +1767,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
}
}}
screenInfo={{
id: modalState.screenId!,
id: modalState.screen_id!,
tableName: screenData.screenInfo?.table_name,
}}
menuObjid={modalState.menuObjid}
@@ -194,7 +194,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
// 팝업 화면 상태
const [popupScreen, setPopupScreen] = useState<{
screenId: number;
screen_id: number;
title: string;
size: string;
} | null>(null);
@@ -400,8 +400,8 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
// 화면 레이아웃과 화면 정보를 병렬로 가져오기
const [layout, screen] = await Promise.all([
screenApi.getLayout(popupScreen.screenId),
screenApi.getScreen(popupScreen.screenId)
screenApi.getLayout(popupScreen.screen_id),
screenApi.getScreen(popupScreen.screen_id)
]);
console.log("📊 팝업 화면 로드 완료:", {
@@ -416,7 +416,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
setPopupLayout(layout.components || []);
setPopupScreenResolution(layout.screenResolution || null);
setPopupScreenInfo({
id: popupScreen.screenId,
id: popupScreen.screen_id,
tableName: screen.table_name
});
@@ -54,9 +54,9 @@ interface InteractiveScreenViewerProps {
onRefresh?: () => void;
onFlowRefresh?: () => void;
// 외부에서 전달받는 사용자 정보 (ScreenModal 등에서 사용)
userId?: string;
userName?: string;
companyCode?: string;
user_id?: string;
user_name?: string;
company_code?: string;
// 그룹 데이터 (EditModal에서 전달)
groupedData?: Record<string, any>[];
// 비활성화할 필드 목록 (EditModal에서 전달)
@@ -81,9 +81,9 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
onSave,
onRefresh,
onFlowRefresh,
userId: externalUserId,
userName: externalUserName,
companyCode: externalCompanyCode,
user_id: externalUserId,
user_name: externalUserName,
company_code: externalCompanyCode,
groupedData,
disabledFields = [],
isInModal = false,
@@ -123,7 +123,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
// 팝업 화면 상태
const [popupScreen, setPopupScreen] = useState<{
screenId: number;
screen_id: number;
title: string;
size: string;
} | null>(null);
@@ -140,11 +140,11 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
// 🆕 분할 패널에서 매핑된 부모 데이터 가져오기
// disableAutoDataTransfer가 true이면 자동 전달 비활성화 (버튼 클릭으로만 전달)
const splitPanelMappedData = React.useMemo(() => {
if (splitPanelContext && !splitPanelContext.disableAutoDataTransfer) {
if (splitPanelContext && !splitPanelContext.disable_auto_data_transfer) {
return splitPanelContext.getMappedParentData();
}
return {};
}, [splitPanelContext, splitPanelContext?.selectedLeftData, splitPanelContext?.disableAutoDataTransfer]);
}, [splitPanelContext, splitPanelContext?.selected_left_data, splitPanelContext?.disable_auto_data_transfer]);
// formData 결정 (외부에서 전달받은 것이 있으면 우선 사용, 분할 패널 데이터도 병합)
const formData = React.useMemo(() => {
@@ -272,7 +272,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
// type: "component" 또는 type: "widget" 모두 처리
if (comp.type === "widget" || comp.type === "component") {
const widget = comp as any;
const fieldName = widget.columnName || widget.id;
const fieldName = widget.column_name || widget.id;
// autoFill 처리 (테이블 조회 기반 자동 입력)
if (widget.autoFill?.enabled || (comp as any).autoFill?.enabled) {
@@ -306,10 +306,10 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
// 팝업 화면 레이아웃 로드
React.useEffect(() => {
if (popupScreen?.screenId) {
loadPopupScreen(popupScreen.screenId);
if (popupScreen?.screen_id) {
loadPopupScreen(popupScreen.screen_id);
}
}, [popupScreen?.screenId]);
}, [popupScreen?.screen_id]);
const loadPopupScreen = async (screenId: number) => {
try {
@@ -461,7 +461,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
}
const widget = comp as WidgetComponent;
const { widgetType, label, placeholder, required, readonly, columnName } = widget;
const { widget_type: widgetType, label, placeholder, required, readonly, column_name: columnName } = widget as any;
const fieldName = columnName || comp.id;
const currentValue = formData[fieldName] || "";
@@ -511,7 +511,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
onSave: onSave, // 🆕 EditModal의 handleSave 콜백 전달
groupedData: groupedData, // 🆕 그룹 데이터 전달 (RepeatScreenModal용)
}}
config={widget.webTypeConfig}
config={widget.web_type_config}
onEvent={(event: string, data: any) => {
// 이벤트 처리
// console.log(`Widget event: ${event}`, data);
@@ -555,7 +555,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
// 버튼 렌더링
const renderButton = (comp: ComponentData) => {
const config = (comp as any).webTypeConfig as any;
const config = (comp as any).web_type_config as any;
const { label } = comp;
// 버튼 액션 핸들러들
@@ -629,7 +629,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
c.url?.includes("v2-media") ||
c.url?.includes("file-upload")
)
.map((c: any) => c.columnName || c.componentConfig?.columnName)
.map((c: any) => c.column_name || c.componentConfig?.column_name)
.filter(Boolean)
);
@@ -687,9 +687,9 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
};
const handlePopupAction = () => {
if (config?.popupScreenId) {
if (config?.popup_screen_id) {
setPopupScreen({
screenId: config.popupScreenId,
screen_id: config.popup_screen_id,
title: config.popupTitle || "팝업 화면",
size: config.popupSize || "medium",
});
@@ -699,8 +699,8 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
const handleNavigateAction = () => {
const navigateType = config?.navigateType || "url";
if (navigateType === "screen" && config?.navigateScreenId) {
const screenPath = `/screens/${config.navigateScreenId}`;
if (navigateType === "screen" && config?.navigate_screen_id) {
const screenPath = `/screens/${config.navigate_screen_id}`;
if (config.navigateTarget === "_blank") {
window.open(screenPath, "_blank");
@@ -784,8 +784,8 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
case "leftPanel":
// 분할 패널 좌측 선택 데이터에서 값 가져오기
if (mapping.sourceColumn && splitPanelContext?.selectedLeftData) {
value = splitPanelContext.selectedLeftData[mapping.sourceColumn];
if (mapping.sourceColumn && splitPanelContext?.selected_left_data) {
value = splitPanelContext.selected_left_data[mapping.sourceColumn];
}
break;
@@ -819,8 +819,8 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
}
// 3. 좌측 패널 선택 데이터에서 자동 매핑 (컬럼명이 같고 대상 테이블에 있는 경우)
if (splitPanelContext?.selectedLeftData && targetTableColumns.length > 0) {
const leftData = splitPanelContext.selectedLeftData;
if (splitPanelContext?.selected_left_data && targetTableColumns.length > 0) {
const leftData = splitPanelContext.selected_left_data;
console.log("📍 좌측 패널 자동 매핑 시작:", leftData);
for (const [key, val] of Object.entries(leftData)) {
@@ -1003,7 +1003,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
// 파일 컴포넌트 렌더링
const renderFileComponent = (comp: FileComponent) => {
const { label, readonly } = comp;
const fieldName = comp.columnName || comp.id;
const fieldName = comp.column_name || comp.id;
// 화면 ID 추출 (URL에서)
const screenId =
@@ -1018,10 +1018,10 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
<FileUploadComponent
component={comp}
componentConfig={{
...comp.fileConfig,
multiple: comp.fileConfig?.multiple !== false,
accept: (comp.fileConfig?.accept as string) || "*/*",
maxSize: (comp.fileConfig?.maxSize || 10) * 1024 * 1024, // MB to bytes
...comp.file_config,
multiple: comp.file_config?.multiple !== false,
accept: (comp.file_config?.accept as string) || "*/*",
maxSize: (comp.file_config?.max_size || 10) * 1024 * 1024, // MB to bytes
disabled: readonly,
}}
componentStyle={{
@@ -1126,21 +1126,21 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
const isV2InputComponent =
compType === "v2-input" || compType === "v2-select" || compType === "v2-date";
const hasVisibleLabel = isV2InputComponent &&
style?.labelDisplay !== false &&
(style?.labelText || (component as any).label);
style?.label_display !== false &&
(style?.label_text || (component as any).label);
// 라벨 위치에 따라 오프셋 계산 (좌/우 배치 시 세로 오프셋 불필요)
const labelPos = style?.labelPosition || "top";
const labelPos = style?.label_position || "top";
const isVerticalLabel = labelPos === "top" || labelPos === "bottom";
const labelFontSize = style?.labelFontSize ? parseInt(String(style.labelFontSize)) : 14;
const labelMarginBottom = style?.labelMarginBottom ? parseInt(String(style.labelMarginBottom)) : 4;
const labelFontSize = style?.label_font_size ? parseInt(String(style.label_font_size)) : 14;
const labelMarginBottom = style?.label_margin_bottom ? parseInt(String(style.label_margin_bottom)) : 4;
const labelOffset = (hasVisibleLabel && isVerticalLabel) ? (labelFontSize + labelMarginBottom + 2) : 0;
// 수평 라벨 관련 (componentStyle 계산보다 먼저 선언)
const needsExternalLabel = hasVisibleLabel && labelPos !== "top";
const isHorizLabel = labelPos === "left" || labelPos === "right";
const labelText = style?.labelText || (component as any).label || "";
const labelGapValue = style?.labelGap || "8px";
const labelText = style?.label_text || (component as any).label || "";
const labelGapValue = style?.label_gap || "8px";
const calculateCanvasSplitX = (): { x: number; w: number } => {
const compType = (component as any).componentType || "";
@@ -1305,11 +1305,11 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
htmlFor={component.id}
className="text-sm font-medium leading-none"
style={{
fontSize: style?.labelFontSize || "14px",
color: getAdaptiveLabelColor(style?.labelColor),
fontWeight: style?.labelFontWeight || "500",
fontSize: style?.label_font_size || "14px",
color: getAdaptiveLabelColor(style?.label_color),
fontWeight: style?.label_font_weight || "500",
...(isHorizLabel ? { whiteSpace: "nowrap" as const, display: "flex", alignItems: "center" } : {}),
...(labelPos === "bottom" ? { marginTop: style?.labelMarginBottom || "4px" } : {}),
...(labelPos === "bottom" ? { marginTop: style?.label_margin_bottom || "4px" } : {}),
}}
>
{labelText}{((component as any).required || (component as any).componentConfig?.required || isColumnRequiredByMeta((component as any).tableName, (component as any).columnName)) && (
@@ -1323,8 +1323,8 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
...splitAdjustedComponent,
style: {
...splitAdjustedComponent.style,
labelDisplay: false,
labelPosition: "top" as const,
label_display: false,
label_position: "top" as const,
...(isHorizLabel ? {
width: "100%",
height: "100%",
@@ -1360,9 +1360,9 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
...(labelPos === "left"
? { right: "100%", marginRight: labelGapValue }
: { left: "100%", marginLeft: labelGapValue }),
fontSize: style?.labelFontSize || "14px",
color: getAdaptiveLabelColor(style?.labelColor),
fontWeight: style?.labelFontWeight || "500",
fontSize: style?.label_font_size || "14px",
color: getAdaptiveLabelColor(style?.label_color),
fontWeight: style?.label_font_weight || "500",
whiteSpace: "nowrap",
}}
>
+3 -3
View File
@@ -16,7 +16,7 @@ import { useAuth } from "@/hooks/useAuth";
interface SaveModalProps {
isOpen: boolean;
onClose: () => void;
screenId?: number;
screen_id?: number;
modalSize?: "sm" | "md" | "lg" | "xl" | "full";
initialData?: any; // 수정 모드일 때 기존 데이터
onSaveSuccess?: () => void; // 저장 성공 시 콜백 (테이블 새로고침용)
@@ -31,7 +31,7 @@ interface SaveModalProps {
export const SaveModal: React.FC<SaveModalProps> = ({
isOpen,
onClose,
screenId,
screen_id: screenId,
modalSize = "lg",
initialData,
onSaveSuccess,
@@ -223,7 +223,7 @@ export const SaveModal: React.FC<SaveModalProps> = ({
writer: dataToSave.writer || writerValue, // ✅ 입력값 우선, 없으면 userId
created_by: writerValue, // created_by는 항상 로그인한 사람
updated_by: writerValue, // updated_by는 항상 로그인한 사람
company_code: dataToSave.company_code || companyCodeValue, // ✅ 입력값 우선, 없으면 user.companyCode
company_code: dataToSave.company_code || companyCodeValue, // ✅ 입력값 우선, 없으면 user.company_code
};
// 🆕 리피터 데이터(배열)를 마스터 저장에서 제외 (V2Repeater가 별도로 저장)
@@ -113,11 +113,11 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
const [settingModalNode, setSettingModalNode] = useState<{
nodeType: "screen" | "table";
nodeId: string;
screenId: number;
screenName: string;
screen_id: number;
screen_name: string;
tableName?: string;
tableLabel?: string;
companyCode?: string; // 프리뷰용 회사 코드
company_code?: string; // 프리뷰용 회사 코드
// 기존 설정 정보 (화면 디자이너에서 추출)
existingConfig?: {
joinColumnRefs?: Array<{
@@ -420,7 +420,7 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
screenList.forEach((scr: any, idx) => {
const isMain = screen && scr.screen_id === screen.screen_id;
const summary = layoutSummaries[scr.screen_id];
const roleLabel = getRoleLabel(scr.screenRole);
const roleLabel = getRoleLabel(scr.screen_role);
// 포커스 여부 결정 (그룹 모드 & 개별 화면 모드 모두 지원)
const isInGroup = !!selectedGroup;
@@ -443,7 +443,7 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
position: { x: screenStartX + idx * (NODE_WIDTH + NODE_GAP), y: SCREEN_Y },
data: {
label: scr.screen_name,
subLabel: selectedGroup ? `${roleLabel} (#${scr.displayOrder || idx + 1})` : (isMain ? "메인 화면" : "연결 화면"),
subLabel: selectedGroup ? `${roleLabel} (#${scr.display_order || idx + 1})` : (isMain ? "메인 화면" : "연결 화면"),
type: "screen",
isMain: selectedGroup ? idx === 0 : !!isMain,
tableName: scr.table_name,
@@ -452,7 +452,7 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
isInGroup,
isFocused,
isFaded,
screenRole: scr.screenRole,
screenRole: scr.screen_role,
},
});
});
@@ -1257,11 +1257,11 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
setSettingModalNode({
nodeType: "screen",
nodeId: node.id,
screenId: screenId,
screenName: nodeData.label || `화면 ${screenId}`,
screen_id: screenId,
screen_name: nodeData.label || `화면 ${screenId}`,
tableName: mainTable,
tableLabel: nodeData.subLabel,
companyCode: selectedGroup?.company_code, // 프리뷰용 회사 코드
company_code: selectedGroup?.company_code, // 프리뷰용 회사 코드
// 화면의 테이블 정보 전달
existingConfig: {
mainTable: mainTable,
@@ -1289,8 +1289,8 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
setSettingModalNode({
nodeType: "table",
nodeId: node.id,
screenId: screenId ? parseInt(screenId) : 0,
screenName: nodeData.subLabel || tableName,
screen_id: screenId ? parseInt(screenId) : 0,
screen_name: nodeData.subLabel || tableName,
tableName: tableName,
tableLabel: nodeData.label,
// 기존 설정 정보 전달
@@ -1317,25 +1317,25 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
setIsSettingModalOpen(true);
return;
}
// 서브 테이블 노드 더블클릭
if (node.id.startsWith("subtable-")) {
const tableName = node.id.replace("subtable-", "");
const nodeData = node.data as unknown as TableNodeData;
// 이 서브 테이블을 사용하는 화면 찾기
const screenId = Object.entries(screenSubTableMap).find(
([_, tables]) => tables.includes(tableName)
)?.[0];
// 백엔드에서 받은 데이터에서 기존 설정 정보 추출
const existingConfigFromData = getTableExistingConfig(tableName);
setSettingModalNode({
nodeType: "table",
nodeId: node.id,
screenId: screenId ? parseInt(screenId) : 0,
screenName: nodeData.subLabel || tableName,
screen_id: screenId ? parseInt(screenId) : 0,
screen_name: nodeData.subLabel || tableName,
tableName: tableName,
tableLabel: nodeData.label,
// 기존 설정 정보 전달
@@ -2510,10 +2510,10 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
<ScreenSettingModal
isOpen={isSettingModalOpen}
onClose={handleSettingModalClose}
screenId={settingModalNode.screenId}
screenName={settingModalNode.screenName}
screenId={settingModalNode.screen_id}
screenName={settingModalNode.screen_name}
groupId={selectedGroup?.id}
companyCode={settingModalNode.companyCode}
companyCode={settingModalNode.company_code}
mainTable={settingModalNode.existingConfig?.mainTable}
mainTableLabel={settingModalNode.tableLabel}
filterTables={settingModalNode.existingConfig?.filterTables}
@@ -2530,7 +2530,7 @@ function ScreenRelationFlowInner({ screen, selectedGroup, initialFocusedScreenId
onClose={handleSettingModalClose}
tableName={settingModalNode.tableName || ""}
tableLabel={settingModalNode.tableLabel}
screenId={settingModalNode.screenId}
screenId={settingModalNode.screen_id}
joinColumnRefs={settingModalNode.existingConfig?.joinColumnRefs}
referencedBy={settingModalNode.existingConfig?.referencedBy}
columns={settingModalNode.existingConfig?.columns?.map((col) => ({
@@ -112,9 +112,9 @@ interface ColumnInfo {
}
interface ScreenUsingTable {
screenId: number;
screenName: string;
screenCode?: string;
screen_id: number;
screen_name: string;
screen_code?: string;
tableRole: string; // main, filter, join
}
@@ -362,9 +362,9 @@ export function TableSettingModal({
// 메인 테이블로 사용하는 경우
if (screen.table_name === tableName) {
usingScreens.push({
screenId: screen.screen_id,
screenName: screen.screen_name,
screenCode: screen.screen_code,
screen_id: screen.screen_id,
screen_name: screen.screen_name,
screen_code: screen.screen_code,
tableRole: "main",
});
}
@@ -1329,16 +1329,16 @@ function ScreensTab({ screensUsingTable, loading }: ScreensTabProps) {
<div className="space-y-2">
{screensUsingTable.map((screen) => (
<div
key={screen.screenId}
key={screen.screen_id}
className="flex items-center justify-between rounded-lg border bg-background p-3 hover:bg-muted/50"
>
<div className="flex items-center gap-3">
<Monitor className="h-4 w-4 text-primary" />
<div>
<p className="text-sm font-medium">{screen.screenName}</p>
{screen.screenCode && (
<p className="text-sm font-medium">{screen.screen_name}</p>
{screen.screen_code && (
<p className="text-xs text-muted-foreground font-mono">
{screen.screenCode}
{screen.screen_code}
</p>
)}
</div>
@@ -634,7 +634,7 @@ export const ActionTab: React.FC<ButtonTabProps> = ({
// 현재 화면의 회사 코드가 있으면 필터링 파라미터로 전달
if (currentScreenCompanyCode) {
params.companyCode = currentScreenCompanyCode;
params.company_code = currentScreenCompanyCode;
}
const response = await apiClient.get("/screen-management/screens", {
@@ -3883,7 +3883,7 @@ const MasterDetailExcelUploadConfig: React.FC<{
const [loading, setLoading] = useState(false);
const [masterColumns, setMasterColumns] = useState<
Array<{
columnName: string;
column_name: string;
columnLabel: string;
inputType: string;
referenceTable?: string;
@@ -3931,7 +3931,7 @@ const MasterDetailExcelUploadConfig: React.FC<{
const response = await apiClient.get(`/table-management/tables/${masterTable}/columns`);
if (response.data?.success && response.data?.data?.columns) {
const cols = response.data.data.columns.map((col: any) => ({
columnName: col.column_name,
column_name: col.column_name,
columnLabel: col.display_name || col.column_label || col.column_name,
inputType: col.input_type || "text",
referenceTable: col.reference_table,
@@ -4109,22 +4109,22 @@ const MasterDetailExcelUploadConfig: React.FC<{
</p>
<div className="max-h-40 space-y-1 overflow-y-auto rounded-md border p-2">
{masterColumns
.filter((col) => col.columnName !== relationInfo.masterKeyColumn) // 채번으로 자동 생성되는 키는 제외
.filter((col) => col.column_name !== relationInfo.masterKeyColumn) // 채번으로 자동 생성되는 키는 제외
.map((col) => {
const selectedFields = masterDetailConfig.masterSelectFields || [];
const isSelected = selectedFields.some((f: any) => f.columnName === col.columnName);
const isSelected = selectedFields.some((f: any) => f.column_name === col.column_name);
return (
<div key={col.columnName} className="flex items-center gap-2">
<div key={col.column_name} className="flex items-center gap-2">
<input
type="checkbox"
id={`master-field-${col.columnName}`}
id={`master-field-${col.column_name}`}
checked={isSelected}
onChange={(e) => {
const checked = e.target.checked;
let newFields = [...selectedFields];
if (checked) {
newFields.push({
columnName: col.columnName,
column_name: col.column_name,
columnLabel: col.columnLabel,
inputType: col.inputType,
referenceTable: col.referenceTable,
@@ -4133,15 +4133,15 @@ const MasterDetailExcelUploadConfig: React.FC<{
required: true,
});
} else {
newFields = newFields.filter((f: any) => f.columnName !== col.columnName);
newFields = newFields.filter((f: any) => f.column_name !== col.column_name);
}
updateMasterDetailConfig({ masterSelectFields: newFields });
}}
className="h-4 w-4 rounded border-input"
/>
<label htmlFor={`master-field-${col.columnName}`} className="flex-1 cursor-pointer text-xs">
<label htmlFor={`master-field-${col.column_name}`} className="flex-1 cursor-pointer text-xs">
{col.columnLabel}
<span className="text-muted-foreground ml-1">({col.columnName})</span>
<span className="text-muted-foreground ml-1">({col.column_name})</span>
{col.inputType === "entity" && <span className="ml-1 text-primary">[]</span>}
</label>
</div>
@@ -4163,13 +4163,13 @@ const MasterDetailExcelUploadConfig: React.FC<{
.map((field: any) => {
const availableColumns = refTableColumns[field.referenceTable] || [];
return (
<div key={`display-${field.columnName}`} className="flex items-center gap-2">
<div key={`display-${field.column_name}`} className="flex items-center gap-2">
<span className="w-24 truncate text-xs">{field.columnLabel}:</span>
<Select
value={field.displayColumn || ""}
onValueChange={(value) => {
const newFields = masterDetailConfig.masterSelectFields.map((f: any) =>
f.columnName === field.columnName ? { ...f, displayColumn: value } : f,
f.column_name === field.column_name ? { ...f, displayColumn: value } : f,
);
updateMasterDetailConfig({ masterSelectFields: newFields });
}}
+4 -4
View File
@@ -77,7 +77,7 @@ export const entityJoinApi = {
filterColumn?: string;
filterValue?: any;
}; // 🆕 제외 필터 (다른 테이블에 이미 존재하는 데이터 제외)
companyCodeOverride?: string; // 🆕 프리뷰용 회사 코드 오버라이드 (최고 관리자만 사용 가능)
company_code_override?: string; // 🆕 프리뷰용 회사 코드 오버라이드 (최고 관리자만 사용 가능)
} = {},
): Promise<EntityJoinResponse> => {
// 🔒 멀티테넌시: company_code 자동 필터링 활성화
@@ -85,7 +85,7 @@ export const entityJoinApi = {
enabled: boolean;
filterColumn: string;
userField: string;
companyCodeOverride?: string;
company_code_override?: string;
} = {
enabled: true,
filterColumn: "company_code",
@@ -93,8 +93,8 @@ export const entityJoinApi = {
};
// 🆕 프리뷰 모드에서 회사 코드 오버라이드 (최고 관리자만 백엔드에서 허용)
if (params.companyCodeOverride) {
autoFilter.companyCodeOverride = params.companyCodeOverride;
if (params.company_code_override) {
autoFilter.company_code_override = params.company_code_override;
}
const response = await apiClient.get(`/table-management/tables/${tableName}/data-with-joins`, {
@@ -27,9 +27,9 @@ class ScreenSplitPanelRenderer extends AutoRegisteringComponentRenderer {
height: 600,
},
defaultConfig: {
screenId: 0,
leftScreenId: 0,
rightScreenId: 0,
screen_id: 0,
left_screen_id: 0,
right_screen_id: 0,
splitRatio: 50,
resizable: true,
buttonLabel: "데이터 전달",
@@ -74,7 +74,7 @@ class ScreenSplitPanelRenderer extends AutoRegisteringComponentRenderer {
return (
<div style={{ width: "100%", height: "100%", ...style }}>
<ScreenSplitPanel
screenId={screenId || finalConfig.screenId}
screenId={screenId || finalConfig.screen_id}
config={finalConfig}
initialFormData={formData} // 🆕 수정 데이터 전달
groupedData={groupedData} // 🆕 그룹 데이터 전달 (수정 모드에서 원본 데이터 추적용)
@@ -1733,7 +1733,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
const panelConfig = panel === "left" ? componentConfig.leftPanel : componentConfig.rightPanel;
const addModalConfig = panelConfig?.addModal;
if (addModalConfig?.screenId) {
if (addModalConfig?.screen_id) {
if (panel === "right" && !selectedLeftItem) {
toast({
title: "항목을 선택해주세요",
@@ -1764,12 +1764,12 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
}
}
console.log("🆕 [추가모달] screenId 기반 모달 열기:", { screenId: addModalConfig.screenId, tableName, parentData, parentDataStr: JSON.stringify(parentData) });
console.log("🆕 [추가모달] screen_id 기반 모달 열기:", { screenId: addModalConfig.screen_id, tableName, parentData, parentDataStr: JSON.stringify(parentData) });
window.dispatchEvent(
new CustomEvent("openScreenModal", {
detail: {
screenId: addModalConfig.screenId,
screenId: addModalConfig.screen_id,
urlParams,
splitPanelParentData: parentData,
},
@@ -1830,7 +1830,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
});
if (editButtonConfig?.mode === "modal") {
const modalScreenId = editButtonConfig.modalScreenId;
const modalScreenId = editButtonConfig.modal_screen_id;
if (modalScreenId) {
// 커스텀 모달 화면 열기
@@ -122,7 +122,7 @@ const ScreenSelector: React.FC<{
onChange: (screenId?: number) => void;
}> = ({ value, onChange }) => {
const [open, setOpen] = useState(false);
const [screens, setScreens] = useState<Array<{ screenId: number; screenName: string; screenCode: string }>>([]);
const [screens, setScreens] = useState<Array<{ screen_id: number; screen_name: string; screen_code: string }>>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
@@ -132,7 +132,7 @@ const ScreenSelector: React.FC<{
const { screenApi } = await import("@/lib/api/screen");
const response = await screenApi.getScreens({ page: 1, size: 1000 });
setScreens(
response.data.map((s) => ({ screenId: s.screen_id, screenName: s.screen_name, screenCode: s.screen_code })),
response.data.map((s) => ({ screen_id: s.screen_id, screen_name: s.screen_name, screen_code: s.screen_code })),
);
} catch (error) {
console.error("화면 목록 로드 실패:", error);
@@ -143,7 +143,7 @@ const ScreenSelector: React.FC<{
loadScreens();
}, []);
const selectedScreen = screens.find((s) => s.screenId === value);
const selectedScreen = screens.find((s) => s.screen_id === value);
return (
<Popover open={open} onOpenChange={setOpen}>
@@ -155,7 +155,7 @@ const ScreenSelector: React.FC<{
className="h-8 w-full justify-between text-xs"
disabled={loading}
>
{loading ? "로딩 중..." : selectedScreen ? selectedScreen.screenName : "화면 선택"}
{loading ? "로딩 중..." : selectedScreen ? selectedScreen.screen_name : "화면 선택"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
@@ -167,18 +167,18 @@ const ScreenSelector: React.FC<{
<CommandGroup className="max-h-[300px] overflow-auto">
{screens.map((screen) => (
<CommandItem
key={screen.screenId}
value={`${screen.screenName.toLowerCase()} ${screen.screenCode.toLowerCase()} ${screen.screenId}`}
key={screen.screen_id}
value={`${screen.screen_name.toLowerCase()} ${screen.screen_code.toLowerCase()} ${screen.screen_id}`}
onSelect={() => {
onChange(screen.screenId === value ? undefined : screen.screenId);
onChange(screen.screen_id === value ? undefined : screen.screen_id);
setOpen(false);
}}
className="text-xs"
>
<Check className={cn("mr-2 h-4 w-4", value === screen.screenId ? "opacity-100" : "opacity-0")} />
<Check className={cn("mr-2 h-4 w-4", value === screen.screen_id ? "opacity-100" : "opacity-0")} />
<div className="flex flex-col">
<span className="font-medium">{screen.screenName}</span>
<span className="text-muted-foreground text-[10px]">{screen.screenCode}</span>
<span className="font-medium">{screen.screen_name}</span>
<span className="text-muted-foreground text-[10px]">{screen.screen_code}</span>
</div>
</CommandItem>
))}
@@ -901,10 +901,10 @@ const AdditionalTabConfigPanel: React.FC<AdditionalTabConfigPanelProps> = ({
<div className="space-y-1">
<Label className="text-[10px]"> </Label>
<ScreenSelector
value={tab.editButton?.modalScreenId}
value={tab.editButton?.modal_screen_id}
onChange={(screenId) => {
updateTab({
editButton: { ...tab.editButton, enabled: true, mode: "modal", modalScreenId: screenId },
editButton: { ...tab.editButton, enabled: true, mode: "modal", modal_screen_id: screenId },
});
}}
/>
@@ -2335,12 +2335,12 @@ export const SplitPanelLayoutConfigPanel: React.FC<SplitPanelLayoutConfigPanelPr
<div>
<Label className="text-xs"> </Label>
<ScreenSelector
value={config.rightPanel?.editButton?.modalScreenId}
value={config.rightPanel?.editButton?.modal_screen_id}
onChange={(screenId) =>
updateRightPanel({
editButton: {
...config.rightPanel?.editButton!,
modalScreenId: screenId,
modal_screen_id: screenId,
},
})
}
@@ -93,7 +93,7 @@ export interface AdditionalTabConfig {
editButton?: {
enabled: boolean;
mode: "auto" | "modal";
modalScreenId?: number;
modal_screen_id?: number;
buttonLabel?: string;
buttonVariant?: "default" | "outline" | "ghost";
groupByColumns?: string[];
@@ -263,7 +263,7 @@ export interface SplitPanelLayoutConfig {
editButton?: {
enabled: boolean; // 수정 버튼 표시 여부 (기본: true)
mode: "auto" | "modal"; // auto: 자동 편집 (인라인), modal: 커스텀 모달
modalScreenId?: number; // 모달로 열 화면 ID (mode: "modal"일 때)
modal_screen_id?: number; // 모달로 열 화면 ID (mode: "modal"일 때)
buttonLabel?: string; // 버튼 라벨 (기본: "수정")
buttonVariant?: "default" | "outline" | "ghost"; // 버튼 스타일 (기본: "outline")
groupByColumns?: string[]; // 🆕 그룹핑 기준 컬럼들 (예: ["customer_id", "item_id"])
@@ -2448,14 +2448,14 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
// 좌측 패널 추가 시, addButton 모달 모드 확인
if (panel === "left") {
const addButtonConfig = componentConfig.leftPanel?.addButton;
if (addButtonConfig?.mode === "modal" && addButtonConfig?.modalScreenId) {
if (addButtonConfig?.mode === "modal" && addButtonConfig?.modal_screen_id) {
const leftTableName = componentConfig.leftPanel?.tableName || "";
// ScreenModal 열기 이벤트 발생
window.dispatchEvent(
new CustomEvent("openScreenModal", {
detail: {
screenId: addButtonConfig.modalScreenId,
screenId: addButtonConfig.modal_screen_id,
urlParams: {
mode: "add",
tableName: leftTableName,
@@ -2475,7 +2475,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
? componentConfig.rightPanel?.addButton
: (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.addButton;
if (addButtonConfig?.mode === "modal" && addButtonConfig?.modalScreenId) {
if (addButtonConfig?.mode === "modal" && addButtonConfig?.modal_screen_id) {
if (!selectedLeftItem) {
toast({
title: "항목을 선택해주세요",
@@ -2521,7 +2521,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
window.dispatchEvent(
new CustomEvent("openScreenModal", {
detail: {
screenId: addButtonConfig.modalScreenId,
screenId: addButtonConfig.modal_screen_id,
urlParams: {
mode: "add",
tableName: currentTableName,
@@ -2564,7 +2564,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
// 좌측 패널 수정 버튼 설정 확인 (모달 모드)
if (panel === "left") {
const editButtonConfig = componentConfig.leftPanel?.editButton;
if (editButtonConfig?.mode === "modal" && editButtonConfig?.modalScreenId) {
if (editButtonConfig?.mode === "modal" && editButtonConfig?.modal_screen_id) {
const leftTableName = componentConfig.leftPanel?.tableName || "";
// Primary Key 찾기 - 실제 DB의 id 컬럼 값을 우선 사용
@@ -2589,7 +2589,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
window.dispatchEvent(
new CustomEvent("openScreenModal", {
detail: {
screenId: editButtonConfig.modalScreenId,
screenId: editButtonConfig.modal_screen_id,
urlParams: {
mode: "edit",
editId: primaryKeyValue,
@@ -2616,7 +2616,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
: (componentConfig.rightPanel?.additionalTabs?.[activeTabIndex - 1] as any)?.tableName || "";
if (editButtonConfig?.mode === "modal") {
const modalScreenId = editButtonConfig?.modalScreenId;
const modalScreenId = editButtonConfig?.modal_screen_id;
if (modalScreenId) {
// 커스텀 모달 화면 열기
@@ -118,7 +118,7 @@ export interface AdditionalTabConfig {
editButton?: {
enabled: boolean;
mode: "auto" | "modal";
modalScreenId?: number;
modal_screen_id?: number;
buttonLabel?: string;
buttonVariant?: "default" | "outline" | "ghost";
groupByColumns?: string[];
@@ -128,7 +128,7 @@ export interface AdditionalTabConfig {
addButton?: {
enabled: boolean;
mode: "auto" | "modal";
modalScreenId?: number;
modal_screen_id?: number;
buttonLabel?: string;
};
@@ -163,7 +163,7 @@ export interface SplitPanelLayoutConfig {
editButton?: {
enabled: boolean;
mode: "auto" | "modal"; // auto: 내장 편집, modal: 커스텀 모달 화면
modalScreenId?: number; // 모달로 열 화면 ID (mode: "modal"일 때)
modal_screen_id?: number; // 모달로 열 화면 ID (mode: "modal"일 때)
buttonLabel?: string; // 버튼 라벨 (기본: "수정")
};
@@ -171,7 +171,7 @@ export interface SplitPanelLayoutConfig {
addButton?: {
enabled: boolean;
mode: "auto" | "modal";
modalScreenId?: number;
modal_screen_id?: number;
buttonLabel?: string;
};
@@ -338,7 +338,7 @@ export interface SplitPanelLayoutConfig {
editButton?: {
enabled: boolean; // 수정 버튼 표시 여부 (기본: true)
mode: "auto" | "modal"; // auto: 자동 편집 (인라인), modal: 커스텀 모달
modalScreenId?: number; // 모달로 열 화면 ID (mode: "modal"일 때)
modal_screen_id?: number; // 모달로 열 화면 ID (mode: "modal"일 때)
buttonLabel?: string; // 버튼 라벨 (기본: "수정")
buttonVariant?: "default" | "outline" | "ghost"; // 버튼 스타일 (기본: "outline")
groupByColumns?: string[]; // 🆕 그룹핑 기준 컬럼들 (예: ["customer_id", "item_id"])
@@ -348,7 +348,7 @@ export interface SplitPanelLayoutConfig {
addButton?: {
enabled: boolean;
mode: "auto" | "modal";
modalScreenId?: number;
modal_screen_id?: number;
buttonLabel?: string;
};
@@ -3,7 +3,7 @@ import { TableSearchWidget } from "./TableSearchWidget";
export class TableSearchWidgetRenderer {
static render(component: any, props?: any) {
return <TableSearchWidget component={component} screenId={props?.screenId} />;
return <TableSearchWidget component={component} screenId={props?.screen_id} />;
}
}
@@ -279,7 +279,7 @@ export function PopCardListV2Component({
const handleCardSelect = useCallback((row: RowData) => {
if (effectiveConfig?.cardClickAction === "modal-open" && effectiveConfig?.cardClickModalConfig?.screenId) {
if (effectiveConfig?.cardClickAction === "modal-open" && effectiveConfig?.cardClickModalConfig?.screen_id) {
const mc = effectiveConfig.cardClickModalConfig;
if (mc.condition && mc.condition.type !== "always") {
const processFlow = row.__processFlow__ as { isCurrent: boolean; status?: string }[] | undefined;
@@ -290,7 +290,7 @@ export function PopCardListV2Component({
if (String(row[mc.condition.column || ""] ?? "") !== mc.condition.value) return;
}
}
openPopModal(mc.screenId, row);
openPopModal(mc.screen_id, row);
return;
}
if (!componentId) return;
@@ -539,12 +539,12 @@ export function PopCardListV2Component({
return;
}
if (btnConfig.clickMode === "modal-open" && btnConfig.modalScreenId) {
if (btnConfig.clickMode === "modal-open" && btnConfig.modal_screen_id) {
const selectedRows = filteredRows.filter((r) => {
const rowId = String(r.id ?? r.pk ?? "");
return selectedRowIds.has(rowId);
});
openPopModal(btnConfig.modalScreenId, selectedRows[0] || {});
openPopModal(btnConfig.modal_screen_id, selectedRows[0] || {});
return;
}
}, [selectedRowIds, filteredRows, exitSelectMode]);
@@ -894,14 +894,14 @@ export function PopCardListV2Component({
useEffect(() => {
if (isCartListMode) {
const cartListMode = config!.cartListMode!;
if (!cartListMode.sourceScreenId) { setLoading(false); setRows([]); return; }
if (!cartListMode.source_screen_id) { setLoading(false); setRows([]); return; }
const fetchCartData = async () => {
setLoading(true);
setError(null);
try {
try {
const layoutJson = await screenApi.getLayoutPop(cartListMode.sourceScreenId!);
const layoutJson = await screenApi.getLayoutPop(cartListMode.source_screen_id!);
const componentsMap = layoutJson?.components || {};
const componentList = Object.values(componentsMap) as any[];
const matched = cartListMode.sourceComponentId
@@ -911,7 +911,7 @@ export function PopCardListV2Component({
} catch { /* 레이아웃 로드 실패 시 자체 config 폴백 */ }
const cartFilters: Record<string, unknown> = { status: cartListMode.statusFilter || "in_cart" };
if (cartListMode.sourceScreenId) cartFilters.screen_id = String(cartListMode.sourceScreenId);
if (cartListMode.source_screen_id) cartFilters.screen_id = String(cartListMode.source_screen_id);
const result = await dataApi.getTableData("cart_items", { size: 500, filters: cartFilters });
setRows((result.data || []).map(parseCartRow));
} catch (err) {
@@ -999,7 +999,7 @@ export function PopCardListV2Component({
return (
<div ref={containerRef} className={`flex h-full w-full flex-col ${className || ""}`}>
{isCartListMode && !config?.cartListMode?.sourceScreenId ? (
{isCartListMode && !config?.cartListMode?.source_screen_id ? (
<div className="flex flex-1 items-center justify-center rounded-md border border-dashed bg-muted/30 p-4">
<p className="text-sm text-muted-foreground"> .</p>
</div>
@@ -1174,7 +1174,7 @@ export function PopCardListV2Component({
}}>
<DialogContent className="flex h-dvh w-screen max-w-none flex-col gap-0 rounded-none border-none p-0 [&>button]:z-50">
<DialogHeader className="flex shrink-0 flex-row items-center justify-between border-b px-4 py-2">
<DialogTitle className="text-base">{effectiveConfig?.cardClickModalConfig?.modalTitle || "상세 작업"}</DialogTitle>
<DialogTitle className="text-base">{effectiveConfig?.cardClickModalConfig?.modal_title || "상세 작업"}</DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-auto">
{popModalLayout && (
@@ -1565,8 +1565,8 @@ function CardV2({
}
return;
}
} else if (action.type === "modal-open" && action.modalScreenId) {
onOpenPopModal?.(action.modalScreenId, actionRow);
} else if (action.type === "modal-open" && action.modal_screen_id) {
onOpenPopModal?.(action.modal_screen_id, actionRow);
}
}
},
@@ -77,7 +77,7 @@ export interface PopIconAction {
type: "navigate";
navigate: {
mode: NavigateMode;
screenId?: string;
screen_id?: string;
url?: string;
};
}
@@ -283,11 +283,11 @@ export function PopIconComponent({
// 디자인 모드: 확인 다이얼로그 표시
if (isDesignMode) {
if (navigate.mode === "screen") {
if (!navigate.screenId) {
if (!navigate.screen_id) {
toast.error("화면 ID가 설정되지 않았습니다.");
return;
}
const cleanScreenId = extractScreenId(navigate.screenId);
const cleanScreenId = extractScreenId(navigate.screen_id);
setPendingNavigate({ mode: "screen", target: cleanScreenId });
setShowNavigateDialog(true);
} else if (navigate.mode === "url") {
@@ -306,8 +306,8 @@ export function PopIconComponent({
// 실제 모드: 직접 실행
switch (navigate.mode) {
case "screen":
if (navigate.screenId) {
const cleanScreenId = extractScreenId(navigate.screenId);
if (navigate.screen_id) {
const cleanScreenId = extractScreenId(navigate.screen_id);
window.location.href = `/pop/screens/${cleanScreenId}`;
}
break;
@@ -902,10 +902,10 @@ function ActionSettings({ config, onUpdate }: PopIconConfigPanelProps) {
<>
{navigate.mode === "screen" && (
<Input
value={navigate.screenId || ""}
value={navigate.screen_id || ""}
onChange={(e) => onUpdate({
...config,
action: { type: "navigate", navigate: { ...navigate, screenId: e.target.value } }
action: { type: "navigate", navigate: { ...navigate, screen_id: e.target.value } }
})}
placeholder="화면 ID"
className="h-8 text-xs mt-2"
@@ -930,8 +930,8 @@ function ActionSettings({ config, onUpdate }: PopIconConfigPanelProps) {
size="sm"
className="w-full h-8 text-xs mt-3"
onClick={() => {
if (navigate.mode === "screen" && navigate.screenId) {
const cleanScreenId = extractScreenId(navigate.screenId);
if (navigate.mode === "screen" && navigate.screen_id) {
const cleanScreenId = extractScreenId(navigate.screen_id);
window.open(`/pop/screens/${cleanScreenId}`, "_blank");
} else if (navigate.mode === "url" && navigate.url) {
window.open(navigate.url, "_blank");
@@ -173,8 +173,8 @@ export interface PopActionConfig {
targetScreenId?: string;
params?: Record<string, string>;
// modal
modalScreenId?: string;
modalTitle?: string;
modal_screen_id?: string;
modal_title?: string;
// save/delete
targetTable?: string;
confirmMessage?: string;
@@ -612,7 +612,7 @@ export interface CardResponsiveConfig {
export interface CartListModeConfig {
enabled: boolean;
sourceScreenId?: number;
source_screen_id?: number;
sourceComponentId?: string;
statusFilter?: string;
}
@@ -886,7 +886,7 @@ export interface SelectModeButtonConfig {
targetTable?: string;
updates?: ActionButtonUpdate[];
confirmMessage?: string;
modalScreenId?: string;
modal_screen_id?: string;
quantityInput?: QuantityInputConfig;
}
@@ -905,7 +905,7 @@ export interface ActionButtonClickAction {
updates?: ActionButtonUpdate[];
confirmMessage?: string;
selectModeButtons?: SelectModeButtonConfig[];
modalScreenId?: string;
modal_screen_id?: string;
joinConfig?: {
sourceColumn: string;
targetColumn: string;
@@ -951,8 +951,8 @@ export interface CardGridConfigV2 {
export type V2CardClickAction = "none" | "publish" | "navigate" | "modal-open";
export interface V2CardClickModalConfig {
screenId: string;
modalTitle?: string;
screen_id: string;
modal_title?: string;
condition?: {
type: "timeline-status" | "column-value" | "always";
value?: string;
+5 -5
View File
@@ -310,11 +310,11 @@ export interface ButtonActionConfig {
export interface ButtonActionContext {
formData: Record<string, any>;
originalData?: Record<string, any>; // 부분 업데이트용 원본 데이터
screenId?: number;
tableName?: string;
userId?: string; // 🆕 현재 로그인한 사용자 ID
userName?: string; // 🆕 현재 로그인한 사용자 이름
companyCode?: string; // 🆕 현재 사용자의 회사 코드
screen_id?: number;
table_name?: string;
user_id?: string; // 🆕 현재 로그인한 사용자 ID
user_name?: string; // 🆕 현재 로그인한 사용자 이름
company_code?: string; // 🆕 현재 사용자의 회사 코드
onFormDataChange?: (fieldName: string, value: any) => void;
onClose?: () => void;
onRefresh?: () => void;