[agent-pipeline] pipe-20260329143602-hu6g round-1
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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 });
|
||||
}}
|
||||
|
||||
@@ -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) {
|
||||
// 커스텀 모달 화면 열기
|
||||
|
||||
+14
-14
@@ -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"])
|
||||
|
||||
+7
-7
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user