Compare commits
2 Commits
184adffdcb
...
23f7b89cc5
| Author | SHA1 | Date |
|---|---|---|
|
|
23f7b89cc5 | |
|
|
4996dd5562 |
|
|
@ -24,6 +24,12 @@ export default function ScreenViewPage() {
|
|||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [formData, setFormData] = useState<Record<string, unknown>>({});
|
||||
|
||||
// 테이블에서 선택된 행 데이터 (버튼 액션에 전달)
|
||||
const [selectedRowsData, setSelectedRowsData] = useState<any[]>([]);
|
||||
|
||||
// 테이블 새로고침을 위한 키 (값이 변경되면 테이블이 리렌더링됨)
|
||||
const [tableRefreshKey, setTableRefreshKey] = useState(0);
|
||||
|
||||
// 편집 모달 상태
|
||||
const [editModalOpen, setEditModalOpen] = useState(false);
|
||||
const [editModalConfig, setEditModalConfig] = useState<{
|
||||
|
|
@ -172,6 +178,19 @@ export default function ScreenViewPage() {
|
|||
isSelected={false}
|
||||
isDesignMode={false}
|
||||
onClick={() => {}}
|
||||
screenId={screenId}
|
||||
tableName={screen?.tableName}
|
||||
selectedRowsData={selectedRowsData}
|
||||
onSelectedRowsChange={(_, selectedData) => {
|
||||
console.log("🔍 화면에서 선택된 행 데이터:", selectedData);
|
||||
setSelectedRowsData(selectedData);
|
||||
}}
|
||||
refreshKey={tableRefreshKey}
|
||||
onRefresh={() => {
|
||||
console.log("🔄 테이블 새로고침 요청됨");
|
||||
setTableRefreshKey((prev) => prev + 1);
|
||||
setSelectedRowsData([]); // 선택 해제
|
||||
}}
|
||||
>
|
||||
{/* 자식 컴포넌트들 */}
|
||||
{(component.type === "group" || component.type === "container" || component.type === "area") &&
|
||||
|
|
@ -195,6 +214,19 @@ export default function ScreenViewPage() {
|
|||
isSelected={false}
|
||||
isDesignMode={false}
|
||||
onClick={() => {}}
|
||||
screenId={screenId}
|
||||
tableName={screen?.tableName}
|
||||
selectedRowsData={selectedRowsData}
|
||||
onSelectedRowsChange={(_, selectedData) => {
|
||||
console.log("🔍 화면에서 선택된 행 데이터 (자식):", selectedData);
|
||||
setSelectedRowsData(selectedData);
|
||||
}}
|
||||
refreshKey={tableRefreshKey}
|
||||
onRefresh={() => {
|
||||
console.log("🔄 테이블 새로고침 요청됨 (자식)");
|
||||
setTableRefreshKey((prev) => prev + 1);
|
||||
setSelectedRowsData([]); // 선택 해제
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
|||
const [localFormData, setLocalFormData] = useState<Record<string, any>>({});
|
||||
const [dateValues, setDateValues] = useState<Record<string, Date | undefined>>({});
|
||||
|
||||
// 테이블에서 선택된 행 데이터 (버튼 액션에 전달)
|
||||
const [selectedRowsData, setSelectedRowsData] = useState<any[]>([]);
|
||||
|
||||
// 팝업 화면 상태
|
||||
const [popupScreen, setPopupScreen] = useState<{
|
||||
screenId: number;
|
||||
|
|
@ -186,6 +189,11 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
|||
onFormDataChange={handleFormDataChange}
|
||||
screenId={screenInfo?.id}
|
||||
tableName={screenInfo?.tableName}
|
||||
selectedRowsData={selectedRowsData}
|
||||
onSelectedRowsChange={(selectedRows, selectedData) => {
|
||||
console.log("🔍 테이블에서 선택된 행 데이터:", selectedData);
|
||||
setSelectedRowsData(selectedData);
|
||||
}}
|
||||
onRefresh={() => {
|
||||
console.log("🔄 버튼에서 테이블 새로고침 요청됨");
|
||||
// 테이블 컴포넌트는 자체적으로 loadData 호출
|
||||
|
|
|
|||
|
|
@ -34,6 +34,14 @@ interface RealtimePreviewProps {
|
|||
onZoneComponentDrop?: (e: React.DragEvent, zoneId: string, layoutId: string) => void; // 존별 드롭 핸들러
|
||||
onZoneClick?: (zoneId: string) => void; // 존 클릭 핸들러
|
||||
onConfigChange?: (config: any) => void; // 설정 변경 핸들러
|
||||
|
||||
// 버튼 액션을 위한 props
|
||||
screenId?: number;
|
||||
tableName?: string;
|
||||
selectedRowsData?: any[];
|
||||
onSelectedRowsChange?: (selectedRows: any[], selectedRowsData: any[]) => void;
|
||||
refreshKey?: number;
|
||||
onRefresh?: () => void;
|
||||
}
|
||||
|
||||
// 동적 위젯 타입 아이콘 (레지스트리에서 조회)
|
||||
|
|
@ -77,6 +85,12 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
|||
onZoneComponentDrop,
|
||||
onZoneClick,
|
||||
onConfigChange,
|
||||
screenId,
|
||||
tableName,
|
||||
selectedRowsData,
|
||||
onSelectedRowsChange,
|
||||
refreshKey,
|
||||
onRefresh,
|
||||
}) => {
|
||||
const { id, type, position, size, style: componentStyle } = component;
|
||||
|
||||
|
|
@ -178,6 +192,12 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
|||
onZoneComponentDrop={onZoneComponentDrop}
|
||||
onZoneClick={onZoneClick}
|
||||
onConfigChange={onConfigChange}
|
||||
screenId={screenId}
|
||||
tableName={tableName}
|
||||
selectedRowsData={selectedRowsData}
|
||||
onSelectedRowsChange={onSelectedRowsChange}
|
||||
refreshKey={refreshKey}
|
||||
onRefresh={onRefresh}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -66,12 +66,18 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({ component,
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [component.id]);
|
||||
|
||||
// 화면 목록 가져오기
|
||||
// 화면 목록 가져오기 (전체 목록)
|
||||
useEffect(() => {
|
||||
const fetchScreens = async () => {
|
||||
try {
|
||||
setScreensLoading(true);
|
||||
const response = await apiClient.get("/screen-management/screens");
|
||||
// 전체 목록을 가져오기 위해 size를 큰 값으로 설정
|
||||
const response = await apiClient.get("/screen-management/screens", {
|
||||
params: {
|
||||
page: 1,
|
||||
size: 9999, // 매우 큰 값으로 설정하여 전체 목록 가져오기
|
||||
},
|
||||
});
|
||||
|
||||
if (response.data.success && Array.isArray(response.data.data)) {
|
||||
const screenList = response.data.data.map((screen: any) => ({
|
||||
|
|
@ -194,17 +200,11 @@ export const ButtonConfigPanel: React.FC<ButtonConfigPanelProps> = ({ component,
|
|||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="save">저장</SelectItem>
|
||||
<SelectItem value="cancel">취소</SelectItem>
|
||||
<SelectItem value="delete">삭제</SelectItem>
|
||||
<SelectItem value="edit">수정</SelectItem>
|
||||
<SelectItem value="add">추가</SelectItem>
|
||||
<SelectItem value="search">검색</SelectItem>
|
||||
<SelectItem value="reset">초기화</SelectItem>
|
||||
<SelectItem value="submit">제출</SelectItem>
|
||||
<SelectItem value="close">닫기</SelectItem>
|
||||
<SelectItem value="modal">모달 열기</SelectItem>
|
||||
<SelectItem value="edit">편집</SelectItem>
|
||||
<SelectItem value="navigate">페이지 이동</SelectItem>
|
||||
<SelectItem value="control">제어 (조건 체크만)</SelectItem>
|
||||
<SelectItem value="modal">모달 열기</SelectItem>
|
||||
<SelectItem value="control">제어 흐름</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -64,6 +64,15 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
|||
selectedRowsData,
|
||||
...props
|
||||
}) => {
|
||||
console.log("🔵 ButtonPrimaryComponent 렌더링, 받은 props:", {
|
||||
componentId: component.id,
|
||||
hasSelectedRowsData: !!selectedRowsData,
|
||||
selectedRowsDataLength: selectedRowsData?.length,
|
||||
selectedRowsData,
|
||||
tableName,
|
||||
screenId,
|
||||
});
|
||||
|
||||
// 확인 다이얼로그 상태
|
||||
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
||||
const [pendingAction, setPendingAction] = useState<{
|
||||
|
|
@ -204,7 +213,7 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
|||
}
|
||||
|
||||
// 확인 다이얼로그가 필요한 액션 타입들
|
||||
const confirmationRequiredActions: ButtonActionType[] = ["save", "submit", "delete"];
|
||||
const confirmationRequiredActions: ButtonActionType[] = ["save", "delete"];
|
||||
|
||||
// 실제 액션 실행 함수
|
||||
const executeAction = async (actionConfig: any, context: ButtonActionContext) => {
|
||||
|
|
@ -221,8 +230,9 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
|||
// 추가 안전장치: 모든 로딩 토스트 제거
|
||||
toast.dismiss();
|
||||
|
||||
// edit 액션을 제외하고만 로딩 토스트 표시
|
||||
if (actionConfig.type !== "edit") {
|
||||
// UI 전환 액션(edit, modal, navigate)을 제외하고만 로딩 토스트 표시
|
||||
const silentActions = ["edit", "modal", "navigate"];
|
||||
if (!silentActions.includes(actionConfig.type)) {
|
||||
console.log("📱 로딩 토스트 표시 시작");
|
||||
currentLoadingToastRef.current = toast.loading(
|
||||
actionConfig.type === "save"
|
||||
|
|
@ -237,9 +247,16 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
|||
},
|
||||
);
|
||||
console.log("📱 로딩 토스트 ID:", currentLoadingToastRef.current);
|
||||
} else {
|
||||
console.log("🔕 UI 전환 액션은 로딩 토스트 표시 안함:", actionConfig.type);
|
||||
}
|
||||
|
||||
console.log("⚡ ButtonActionExecutor.executeAction 호출 시작");
|
||||
console.log("🔍 actionConfig 확인:", {
|
||||
type: actionConfig.type,
|
||||
successMessage: actionConfig.successMessage,
|
||||
errorMessage: actionConfig.errorMessage,
|
||||
});
|
||||
const success = await ButtonActionExecutor.executeAction(actionConfig, context);
|
||||
console.log("⚡ ButtonActionExecutor.executeAction 완료, success:", success);
|
||||
|
||||
|
|
@ -252,37 +269,70 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
|||
|
||||
// 실패한 경우 오류 처리
|
||||
if (!success) {
|
||||
// UI 전환 액션(edit, modal, navigate)은 에러도 조용히 처리
|
||||
const silentActions = ["edit", "modal", "navigate"];
|
||||
if (silentActions.includes(actionConfig.type)) {
|
||||
console.log("🔕 UI 전환 액션 실패지만 에러 토스트 표시 안함:", actionConfig.type);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("❌ 액션 실패, 오류 토스트 표시");
|
||||
const errorMessage =
|
||||
actionConfig.errorMessage ||
|
||||
(actionConfig.type === "save"
|
||||
// 기본 에러 메시지 결정
|
||||
const defaultErrorMessage =
|
||||
actionConfig.type === "save"
|
||||
? "저장 중 오류가 발생했습니다."
|
||||
: actionConfig.type === "delete"
|
||||
? "삭제 중 오류가 발생했습니다."
|
||||
: actionConfig.type === "submit"
|
||||
? "제출 중 오류가 발생했습니다."
|
||||
: "처리 중 오류가 발생했습니다.");
|
||||
: "처리 중 오류가 발생했습니다.";
|
||||
|
||||
// 커스텀 메시지 사용 조건:
|
||||
// 1. 커스텀 메시지가 있고
|
||||
// 2. (액션 타입이 save이거나 OR 메시지에 "저장"이 포함되지 않은 경우)
|
||||
const useCustomMessage =
|
||||
actionConfig.errorMessage &&
|
||||
(actionConfig.type === "save" || !actionConfig.errorMessage.includes("저장"));
|
||||
|
||||
const errorMessage = useCustomMessage ? actionConfig.errorMessage : defaultErrorMessage;
|
||||
|
||||
console.log("🔍 에러 메시지 결정:", {
|
||||
actionType: actionConfig.type,
|
||||
customMessage: actionConfig.errorMessage,
|
||||
useCustom: useCustomMessage,
|
||||
finalMessage: errorMessage
|
||||
});
|
||||
|
||||
toast.error(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// 성공한 경우에만 성공 토스트 표시
|
||||
// edit 액션은 조용히 처리 (모달 열기만 하므로 토스트 불필요)
|
||||
if (actionConfig.type !== "edit") {
|
||||
const successMessage =
|
||||
actionConfig.successMessage ||
|
||||
(actionConfig.type === "save"
|
||||
// edit, modal, navigate 액션은 조용히 처리 (UI 전환만 하므로 토스트 불필요)
|
||||
if (actionConfig.type !== "edit" && actionConfig.type !== "modal" && actionConfig.type !== "navigate") {
|
||||
// 기본 성공 메시지 결정
|
||||
const defaultSuccessMessage =
|
||||
actionConfig.type === "save"
|
||||
? "저장되었습니다."
|
||||
: actionConfig.type === "delete"
|
||||
? "삭제되었습니다."
|
||||
: actionConfig.type === "submit"
|
||||
? "제출되었습니다."
|
||||
: "완료되었습니다.");
|
||||
: "완료되었습니다.";
|
||||
|
||||
// 커스텀 메시지 사용 조건:
|
||||
// 1. 커스텀 메시지가 있고
|
||||
// 2. (액션 타입이 save이거나 OR 메시지에 "저장"이 포함되지 않은 경우)
|
||||
const useCustomMessage =
|
||||
actionConfig.successMessage &&
|
||||
(actionConfig.type === "save" || !actionConfig.successMessage.includes("저장"));
|
||||
|
||||
const successMessage = useCustomMessage ? actionConfig.successMessage : defaultSuccessMessage;
|
||||
|
||||
console.log("🎉 성공 토스트 표시:", successMessage);
|
||||
toast.success(successMessage);
|
||||
} else {
|
||||
console.log("🔕 edit 액션은 조용히 처리 (토스트 없음)");
|
||||
console.log("🔕 UI 전환 액션은 조용히 처리 (토스트 없음):", actionConfig.type);
|
||||
}
|
||||
|
||||
console.log("✅ 버튼 액션 실행 성공:", actionConfig.type);
|
||||
|
|
@ -357,6 +407,13 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
|||
requiresConfirmation: confirmationRequiredActions.includes(processedConfig.action.type),
|
||||
});
|
||||
|
||||
// 삭제 액션인데 선택된 데이터가 없으면 경고 메시지 표시하고 중단
|
||||
if (processedConfig.action.type === "delete" && (!selectedRowsData || selectedRowsData.length === 0)) {
|
||||
console.log("⚠️ 삭제할 데이터가 선택되지 않았습니다.");
|
||||
toast.warning("삭제할 항목을 먼저 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
const context: ButtonActionContext = {
|
||||
formData: formData || {},
|
||||
originalData: originalData || {}, // 부분 업데이트용 원본 데이터 추가
|
||||
|
|
@ -370,6 +427,15 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
|||
selectedRowsData,
|
||||
};
|
||||
|
||||
console.log("🔍 버튼 액션 실행 전 context 확인:", {
|
||||
hasSelectedRowsData: !!selectedRowsData,
|
||||
selectedRowsDataLength: selectedRowsData?.length,
|
||||
selectedRowsData,
|
||||
tableName,
|
||||
screenId,
|
||||
formData,
|
||||
});
|
||||
|
||||
// 확인이 필요한 액션인지 확인
|
||||
if (confirmationRequiredActions.includes(processedConfig.action.type)) {
|
||||
console.log("📋 확인 다이얼로그 표시 중...");
|
||||
|
|
|
|||
|
|
@ -11,18 +11,11 @@ import type { ExtendedControlContext } from "@/types/control-management";
|
|||
*/
|
||||
export type ButtonActionType =
|
||||
| "save" // 저장
|
||||
| "cancel" // 취소
|
||||
| "delete" // 삭제
|
||||
| "edit" // 편집
|
||||
| "add" // 추가
|
||||
| "search" // 검색
|
||||
| "reset" // 초기화
|
||||
| "submit" // 제출
|
||||
| "close" // 닫기
|
||||
| "popup" // 팝업 열기
|
||||
| "navigate" // 페이지 이동
|
||||
| "modal" // 모달 열기
|
||||
| "newWindow"; // 새 창 열기
|
||||
| "control"; // 제어 흐름
|
||||
|
||||
/**
|
||||
* 버튼 액션 설정
|
||||
|
|
@ -92,42 +85,18 @@ export class ButtonActionExecutor {
|
|||
case "save":
|
||||
return await this.handleSave(config, context);
|
||||
|
||||
case "submit":
|
||||
return await this.handleSubmit(config, context);
|
||||
|
||||
case "delete":
|
||||
return await this.handleDelete(config, context);
|
||||
|
||||
case "reset":
|
||||
return this.handleReset(config, context);
|
||||
|
||||
case "cancel":
|
||||
return this.handleCancel(config, context);
|
||||
|
||||
case "navigate":
|
||||
return this.handleNavigate(config, context);
|
||||
|
||||
case "modal":
|
||||
return this.handleModal(config, context);
|
||||
|
||||
case "newWindow":
|
||||
return this.handleNewWindow(config, context);
|
||||
|
||||
case "popup":
|
||||
return this.handlePopup(config, context);
|
||||
|
||||
case "search":
|
||||
return this.handleSearch(config, context);
|
||||
|
||||
case "add":
|
||||
return this.handleAdd(config, context);
|
||||
|
||||
case "edit":
|
||||
return this.handleEdit(config, context);
|
||||
|
||||
case "close":
|
||||
return this.handleClose(config, context);
|
||||
|
||||
case "control":
|
||||
return this.handleControl(config, context);
|
||||
|
||||
|
|
@ -515,9 +484,9 @@ export class ButtonActionExecutor {
|
|||
});
|
||||
|
||||
window.dispatchEvent(modalEvent);
|
||||
toast.success("모달 화면이 열렸습니다.");
|
||||
// 모달 열기는 조용히 처리 (토스트 불필요)
|
||||
} else {
|
||||
toast.error("모달로 열 화면이 지정되지 않았습니다.");
|
||||
console.error("모달로 열 화면이 지정되지 않았습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1421,26 +1390,12 @@ export const DEFAULT_BUTTON_ACTIONS: Record<ButtonActionType, Partial<ButtonActi
|
|||
successMessage: "저장되었습니다.",
|
||||
errorMessage: "저장 중 오류가 발생했습니다.",
|
||||
},
|
||||
submit: {
|
||||
type: "submit",
|
||||
validateForm: true,
|
||||
successMessage: "제출되었습니다.",
|
||||
errorMessage: "제출 중 오류가 발생했습니다.",
|
||||
},
|
||||
delete: {
|
||||
type: "delete",
|
||||
confirmMessage: "정말 삭제하시겠습니까?",
|
||||
successMessage: "삭제되었습니다.",
|
||||
errorMessage: "삭제 중 오류가 발생했습니다.",
|
||||
},
|
||||
reset: {
|
||||
type: "reset",
|
||||
confirmMessage: "초기화하시겠습니까?",
|
||||
successMessage: "초기화되었습니다.",
|
||||
},
|
||||
cancel: {
|
||||
type: "cancel",
|
||||
},
|
||||
navigate: {
|
||||
type: "navigate",
|
||||
},
|
||||
|
|
@ -1448,29 +1403,11 @@ export const DEFAULT_BUTTON_ACTIONS: Record<ButtonActionType, Partial<ButtonActi
|
|||
type: "modal",
|
||||
modalSize: "md",
|
||||
},
|
||||
newWindow: {
|
||||
type: "newWindow",
|
||||
popupWidth: 800,
|
||||
popupHeight: 600,
|
||||
},
|
||||
popup: {
|
||||
type: "popup",
|
||||
popupWidth: 600,
|
||||
popupHeight: 400,
|
||||
},
|
||||
search: {
|
||||
type: "search",
|
||||
successMessage: "검색을 실행했습니다.",
|
||||
},
|
||||
add: {
|
||||
type: "add",
|
||||
successMessage: "추가되었습니다.",
|
||||
},
|
||||
edit: {
|
||||
type: "edit",
|
||||
successMessage: "편집되었습니다.",
|
||||
},
|
||||
close: {
|
||||
type: "close",
|
||||
control: {
|
||||
type: "control",
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue