화면 바로 들어가지게 함
This commit is contained in:
parent
53a0fa5c6a
commit
775fbf8903
|
|
@ -15,6 +15,7 @@ import { FlowButtonGroup } from "@/components/screen/widgets/FlowButtonGroup";
|
||||||
import { FlowVisibilityConfig } from "@/types/control-management";
|
import { FlowVisibilityConfig } from "@/types/control-management";
|
||||||
import { findAllButtonGroups } from "@/lib/utils/flowButtonGroupUtils";
|
import { findAllButtonGroups } from "@/lib/utils/flowButtonGroupUtils";
|
||||||
import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer";
|
import { DynamicComponentRenderer } from "@/lib/registry/DynamicComponentRenderer";
|
||||||
|
import { ScreenPreviewProvider } from "@/contexts/ScreenPreviewContext";
|
||||||
|
|
||||||
export default function ScreenViewPage() {
|
export default function ScreenViewPage() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|
@ -211,6 +212,7 @@ export default function ScreenViewPage() {
|
||||||
const screenHeight = layout?.screenResolution?.height || 800;
|
const screenHeight = layout?.screenResolution?.height || 800;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<ScreenPreviewProvider isPreviewMode={false}>
|
||||||
<div ref={containerRef} className="bg-background flex h-full w-full items-start justify-start overflow-hidden">
|
<div ref={containerRef} className="bg-background flex h-full w-full items-start justify-start overflow-hidden">
|
||||||
{/* 절대 위치 기반 렌더링 */}
|
{/* 절대 위치 기반 렌더링 */}
|
||||||
{layout && layout.components.length > 0 ? (
|
{layout && layout.components.length > 0 ? (
|
||||||
|
|
@ -355,7 +357,8 @@ export default function ScreenViewPage() {
|
||||||
if (buttons.length === 0) return null;
|
if (buttons.length === 0) return null;
|
||||||
|
|
||||||
const firstButton = buttons[0];
|
const firstButton = buttons[0];
|
||||||
const groupConfig = (firstButton as any).webTypeConfig?.flowVisibilityConfig as FlowVisibilityConfig;
|
const groupConfig = (firstButton as any).webTypeConfig
|
||||||
|
?.flowVisibilityConfig as FlowVisibilityConfig;
|
||||||
|
|
||||||
// 그룹의 위치는 모든 버튼 중 가장 왼쪽/위쪽 버튼의 위치 사용
|
// 그룹의 위치는 모든 버튼 중 가장 왼쪽/위쪽 버튼의 위치 사용
|
||||||
const groupPosition = buttons.reduce(
|
const groupPosition = buttons.reduce(
|
||||||
|
|
@ -508,5 +511,6 @@ export default function ScreenViewPage() {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</ScreenPreviewProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ import { toast } from "sonner";
|
||||||
import { FileUpload } from "@/components/screen/widgets/FileUpload";
|
import { FileUpload } from "@/components/screen/widgets/FileUpload";
|
||||||
import { AdvancedSearchFilters } from "./filters/AdvancedSearchFilters";
|
import { AdvancedSearchFilters } from "./filters/AdvancedSearchFilters";
|
||||||
import { SaveModal } from "./SaveModal";
|
import { SaveModal } from "./SaveModal";
|
||||||
|
import { useScreenPreview } from "@/contexts/ScreenPreviewContext";
|
||||||
|
|
||||||
// 파일 데이터 타입 정의 (AttachedFileInfo와 호환)
|
// 파일 데이터 타입 정의 (AttachedFileInfo와 호환)
|
||||||
interface FileInfo {
|
interface FileInfo {
|
||||||
|
|
@ -97,6 +98,7 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
|
||||||
style = {},
|
style = {},
|
||||||
onRefresh,
|
onRefresh,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { isPreviewMode } = useScreenPreview(); // 프리뷰 모드 확인
|
||||||
const [data, setData] = useState<Record<string, any>[]>([]);
|
const [data, setData] = useState<Record<string, any>[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [searchValues, setSearchValues] = useState<Record<string, any>>({});
|
const [searchValues, setSearchValues] = useState<Record<string, any>>({});
|
||||||
|
|
@ -411,6 +413,29 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
|
||||||
async (page: number = 1, searchParams: Record<string, any> = {}) => {
|
async (page: number = 1, searchParams: Record<string, any> = {}) => {
|
||||||
if (!component.tableName) return;
|
if (!component.tableName) return;
|
||||||
|
|
||||||
|
// 프리뷰 모드에서는 샘플 데이터만 표시
|
||||||
|
if (isPreviewMode) {
|
||||||
|
const sampleData = Array.from({ length: 3 }, (_, i) => {
|
||||||
|
const sample: Record<string, any> = { id: i + 1 };
|
||||||
|
component.columns.forEach((col) => {
|
||||||
|
if (col.type === "number") {
|
||||||
|
sample[col.key] = Math.floor(Math.random() * 1000);
|
||||||
|
} else if (col.type === "boolean") {
|
||||||
|
sample[col.key] = i % 2 === 0 ? "Y" : "N";
|
||||||
|
} else {
|
||||||
|
sample[col.key] = `샘플 ${col.label} ${i + 1}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return sample;
|
||||||
|
});
|
||||||
|
setData(sampleData);
|
||||||
|
setTotal(3);
|
||||||
|
setTotalPages(1);
|
||||||
|
setCurrentPage(1);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const result = await tableTypeApi.getTableData(component.tableName, {
|
const result = await tableTypeApi.getTableData(component.tableName, {
|
||||||
|
|
@ -1792,21 +1817,53 @@ export const InteractiveDataTable: React.FC<InteractiveDataTableProps> = ({
|
||||||
|
|
||||||
{/* CRUD 버튼들 */}
|
{/* CRUD 버튼들 */}
|
||||||
{component.enableAdd && (
|
{component.enableAdd && (
|
||||||
<Button size="sm" onClick={handleAddData} disabled={loading} className="gap-2">
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
if (isPreviewMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleAddData();
|
||||||
|
}}
|
||||||
|
disabled={loading || isPreviewMode}
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
<Plus className="h-3 w-3" />
|
<Plus className="h-3 w-3" />
|
||||||
{component.addButtonText || "추가"}
|
{component.addButtonText || "추가"}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{component.enableEdit && selectedRows.size === 1 && (
|
{component.enableEdit && selectedRows.size === 1 && (
|
||||||
<Button size="sm" onClick={handleEditData} disabled={loading} className="gap-2" variant="outline">
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
if (isPreviewMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleEditData();
|
||||||
|
}}
|
||||||
|
disabled={loading || isPreviewMode}
|
||||||
|
className="gap-2"
|
||||||
|
variant="outline"
|
||||||
|
>
|
||||||
<Edit className="h-3 w-3" />
|
<Edit className="h-3 w-3" />
|
||||||
{component.editButtonText || "수정"}
|
{component.editButtonText || "수정"}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{component.enableDelete && selectedRows.size > 0 && (
|
{component.enableDelete && selectedRows.size > 0 && (
|
||||||
<Button size="sm" variant="destructive" onClick={handleDeleteData} disabled={loading} className="gap-2">
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="destructive"
|
||||||
|
onClick={() => {
|
||||||
|
if (isPreviewMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleDeleteData();
|
||||||
|
}}
|
||||||
|
disabled={loading || isPreviewMode}
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
<Trash2 className="h-3 w-3" />
|
<Trash2 className="h-3 w-3" />
|
||||||
{component.deleteButtonText || "삭제"}
|
{component.deleteButtonText || "삭제"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ import { UnifiedColumnInfo as ColumnInfo } from "@/types";
|
||||||
import { isFileComponent } from "@/lib/utils/componentTypeUtils";
|
import { isFileComponent } from "@/lib/utils/componentTypeUtils";
|
||||||
import { buildGridClasses } from "@/lib/constants/columnSpans";
|
import { buildGridClasses } from "@/lib/constants/columnSpans";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useScreenPreview } from "@/contexts/ScreenPreviewContext";
|
||||||
|
|
||||||
interface InteractiveScreenViewerProps {
|
interface InteractiveScreenViewerProps {
|
||||||
component: ComponentData;
|
component: ComponentData;
|
||||||
|
|
@ -86,6 +87,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||||
return <div className="h-full w-full" />;
|
return <div className="h-full w-full" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { isPreviewMode } = useScreenPreview(); // 프리뷰 모드 확인
|
||||||
const { userName, user } = useAuth(); // 현재 로그인한 사용자명과 사용자 정보 가져오기
|
const { userName, user } = useAuth(); // 현재 로그인한 사용자명과 사용자 정보 가져오기
|
||||||
const [localFormData, setLocalFormData] = useState<Record<string, any>>({});
|
const [localFormData, setLocalFormData] = useState<Record<string, any>>({});
|
||||||
const [dateValues, setDateValues] = useState<Record<string, Date | undefined>>({});
|
const [dateValues, setDateValues] = useState<Record<string, Date | undefined>>({});
|
||||||
|
|
@ -211,6 +213,11 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||||
|
|
||||||
// 폼 데이터 업데이트
|
// 폼 데이터 업데이트
|
||||||
const updateFormData = (fieldName: string, value: any) => {
|
const updateFormData = (fieldName: string, value: any) => {
|
||||||
|
// 프리뷰 모드에서는 데이터 업데이트 하지 않음
|
||||||
|
if (isPreviewMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// console.log(`🔄 updateFormData: ${fieldName} = "${value}" (외부콜백: ${!!onFormDataChange})`);
|
// console.log(`🔄 updateFormData: ${fieldName} = "${value}" (외부콜백: ${!!onFormDataChange})`);
|
||||||
|
|
||||||
// 항상 로컬 상태도 업데이트
|
// 항상 로컬 상태도 업데이트
|
||||||
|
|
@ -837,6 +844,12 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
// 프리뷰 모드에서는 파일 업로드 차단
|
||||||
|
if (isPreviewMode) {
|
||||||
|
e.target.value = ""; // 파일 선택 취소
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const files = e.target.files;
|
const files = e.target.files;
|
||||||
const fieldName = widget.columnName || widget.id;
|
const fieldName = widget.columnName || widget.id;
|
||||||
|
|
||||||
|
|
@ -1155,6 +1168,11 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||||
const config = widget.webTypeConfig as ButtonTypeConfig | undefined;
|
const config = widget.webTypeConfig as ButtonTypeConfig | undefined;
|
||||||
|
|
||||||
const handleButtonClick = async () => {
|
const handleButtonClick = async () => {
|
||||||
|
// 프리뷰 모드에서는 버튼 동작 차단
|
||||||
|
if (isPreviewMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const actionType = config?.actionType || "save";
|
const actionType = config?.actionType || "save";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import { isFileComponent, isDataTableComponent, isButtonComponent } from "@/lib/
|
||||||
import { FlowButtonGroup } from "./widgets/FlowButtonGroup";
|
import { FlowButtonGroup } from "./widgets/FlowButtonGroup";
|
||||||
import { FlowVisibilityConfig } from "@/types/control-management";
|
import { FlowVisibilityConfig } from "@/types/control-management";
|
||||||
import { findAllButtonGroups } from "@/lib/utils/flowButtonGroupUtils";
|
import { findAllButtonGroups } from "@/lib/utils/flowButtonGroupUtils";
|
||||||
|
import { useScreenPreview } from "@/contexts/ScreenPreviewContext";
|
||||||
|
|
||||||
// 컴포넌트 렌더러들을 강제로 로드하여 레지스트리에 등록
|
// 컴포넌트 렌더러들을 강제로 로드하여 레지스트리에 등록
|
||||||
import "@/lib/registry/components/ButtonRenderer";
|
import "@/lib/registry/components/ButtonRenderer";
|
||||||
|
|
@ -47,6 +48,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||||
hideLabel = false,
|
hideLabel = false,
|
||||||
screenInfo,
|
screenInfo,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { isPreviewMode } = useScreenPreview(); // 프리뷰 모드 확인
|
||||||
const { userName, user } = useAuth();
|
const { userName, user } = useAuth();
|
||||||
const [localFormData, setLocalFormData] = useState<Record<string, any>>({});
|
const [localFormData, setLocalFormData] = useState<Record<string, any>>({});
|
||||||
const [dateValues, setDateValues] = useState<Record<string, Date | undefined>>({});
|
const [dateValues, setDateValues] = useState<Record<string, Date | undefined>>({});
|
||||||
|
|
@ -437,9 +439,10 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||||
const fieldName = comp.columnName || comp.id;
|
const fieldName = comp.columnName || comp.id;
|
||||||
|
|
||||||
// 화면 ID 추출 (URL에서)
|
// 화면 ID 추출 (URL에서)
|
||||||
const screenId = screenInfo?.screenId ||
|
const screenId =
|
||||||
(typeof window !== 'undefined' && window.location.pathname.includes('/screens/')
|
screenInfo?.screenId ||
|
||||||
? parseInt(window.location.pathname.split('/screens/')[1])
|
(typeof window !== "undefined" && window.location.pathname.includes("/screens/")
|
||||||
|
? parseInt(window.location.pathname.split("/screens/")[1])
|
||||||
: null);
|
: null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -455,8 +458,8 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||||
disabled: readonly,
|
disabled: readonly,
|
||||||
}}
|
}}
|
||||||
componentStyle={{
|
componentStyle={{
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: '100%',
|
height: "100%",
|
||||||
}}
|
}}
|
||||||
className="h-full w-full"
|
className="h-full w-full"
|
||||||
isInteractive={true}
|
isInteractive={true}
|
||||||
|
|
@ -465,12 +468,12 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||||
screenId, // 🎯 화면 ID 전달
|
screenId, // 🎯 화면 ID 전달
|
||||||
// 🎯 백엔드 API가 기대하는 정확한 형식으로 설정
|
// 🎯 백엔드 API가 기대하는 정확한 형식으로 설정
|
||||||
autoLink: true, // 자동 연결 활성화
|
autoLink: true, // 자동 연결 활성화
|
||||||
linkedTable: 'screen_files', // 연결 테이블
|
linkedTable: "screen_files", // 연결 테이블
|
||||||
recordId: screenId, // 레코드 ID
|
recordId: screenId, // 레코드 ID
|
||||||
columnName: fieldName, // 컬럼명 (중요!)
|
columnName: fieldName, // 컬럼명 (중요!)
|
||||||
isVirtualFileColumn: true, // 가상 파일 컬럼
|
isVirtualFileColumn: true, // 가상 파일 컬럼
|
||||||
id: formData.id,
|
id: formData.id,
|
||||||
...formData
|
...formData,
|
||||||
}}
|
}}
|
||||||
onFormDataChange={(data) => {
|
onFormDataChange={(data) => {
|
||||||
// console.log("📝 실제 화면 파일 업로드 완료:", data);
|
// console.log("📝 실제 화면 파일 업로드 완료:", data);
|
||||||
|
|
@ -486,7 +489,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||||
hasUploadedFiles: !!updates.uploadedFiles,
|
hasUploadedFiles: !!updates.uploadedFiles,
|
||||||
filesCount: updates.uploadedFiles?.length || 0,
|
filesCount: updates.uploadedFiles?.length || 0,
|
||||||
hasLastFileUpdate: !!updates.lastFileUpdate,
|
hasLastFileUpdate: !!updates.lastFileUpdate,
|
||||||
updates
|
updates,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 파일 업로드/삭제 완료 시 formData 업데이터
|
// 파일 업로드/삭제 완료 시 formData 업데이터
|
||||||
|
|
@ -495,9 +498,9 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🎯 화면설계 모드와 동기화를 위한 전역 이벤트 발생 (업로드/삭제 모두)
|
// 🎯 화면설계 모드와 동기화를 위한 전역 이벤트 발생 (업로드/삭제 모두)
|
||||||
if (updates.uploadedFiles !== undefined && typeof window !== 'undefined') {
|
if (updates.uploadedFiles !== undefined && typeof window !== "undefined") {
|
||||||
// 업로드인지 삭제인지 판단 (lastFileUpdate가 있으면 변경사항 있음)
|
// 업로드인지 삭제인지 판단 (lastFileUpdate가 있으면 변경사항 있음)
|
||||||
const action = updates.lastFileUpdate ? 'update' : 'sync';
|
const action = updates.lastFileUpdate ? "update" : "sync";
|
||||||
|
|
||||||
const eventDetail = {
|
const eventDetail = {
|
||||||
componentId: comp.id,
|
componentId: comp.id,
|
||||||
|
|
@ -505,13 +508,13 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||||
fileCount: updates.uploadedFiles.length,
|
fileCount: updates.uploadedFiles.length,
|
||||||
action: action,
|
action: action,
|
||||||
timestamp: updates.lastFileUpdate || Date.now(),
|
timestamp: updates.lastFileUpdate || Date.now(),
|
||||||
source: 'realScreen' // 실제 화면에서 온 이벤트임을 표시
|
source: "realScreen", // 실제 화면에서 온 이벤트임을 표시
|
||||||
};
|
};
|
||||||
|
|
||||||
// console.log("🚀🚀🚀 실제 화면 파일 변경 이벤트 발생:", eventDetail);
|
// console.log("🚀🚀🚀 실제 화면 파일 변경 이벤트 발생:", eventDetail);
|
||||||
|
|
||||||
const event = new CustomEvent('globalFileStateChanged', {
|
const event = new CustomEvent("globalFileStateChanged", {
|
||||||
detail: eventDetail
|
detail: eventDetail,
|
||||||
});
|
});
|
||||||
window.dispatchEvent(event);
|
window.dispatchEvent(event);
|
||||||
|
|
||||||
|
|
@ -520,16 +523,20 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||||
// 추가 지연 이벤트들 (화면설계 모드가 열려있을 때를 대비)
|
// 추가 지연 이벤트들 (화면설계 모드가 열려있을 때를 대비)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// console.log("🔄 실제 화면 추가 이벤트 발생 (지연 100ms)");
|
// console.log("🔄 실제 화면 추가 이벤트 발생 (지연 100ms)");
|
||||||
window.dispatchEvent(new CustomEvent('globalFileStateChanged', {
|
window.dispatchEvent(
|
||||||
detail: { ...eventDetail, delayed: true }
|
new CustomEvent("globalFileStateChanged", {
|
||||||
}));
|
detail: { ...eventDetail, delayed: true },
|
||||||
|
}),
|
||||||
|
);
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// console.log("🔄 실제 화면 추가 이벤트 발생 (지연 500ms)");
|
// console.log("🔄 실제 화면 추가 이벤트 발생 (지연 500ms)");
|
||||||
window.dispatchEvent(new CustomEvent('globalFileStateChanged', {
|
window.dispatchEvent(
|
||||||
detail: { ...eventDetail, delayed: true, attempt: 2 }
|
new CustomEvent("globalFileStateChanged", {
|
||||||
}));
|
detail: { ...eventDetail, delayed: true, attempt: 2 },
|
||||||
|
}),
|
||||||
|
);
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ import {
|
||||||
findAllButtonGroups,
|
findAllButtonGroups,
|
||||||
} from "@/lib/utils/flowButtonGroupUtils";
|
} from "@/lib/utils/flowButtonGroupUtils";
|
||||||
import { FlowButtonGroupDialog } from "./dialogs/FlowButtonGroupDialog";
|
import { FlowButtonGroupDialog } from "./dialogs/FlowButtonGroupDialog";
|
||||||
|
import { ScreenPreviewProvider } from "@/contexts/ScreenPreviewContext";
|
||||||
|
|
||||||
// 새로운 통합 UI 컴포넌트
|
// 새로운 통합 UI 컴포넌트
|
||||||
import { LeftUnifiedToolbar, defaultToolbarButtons } from "./toolbar/LeftUnifiedToolbar";
|
import { LeftUnifiedToolbar, defaultToolbarButtons } from "./toolbar/LeftUnifiedToolbar";
|
||||||
|
|
@ -4102,6 +4103,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<ScreenPreviewProvider isPreviewMode={true}>
|
||||||
<div className="bg-background flex h-full w-full flex-col">
|
<div className="bg-background flex h-full w-full flex-col">
|
||||||
{/* 상단 슬림 툴바 */}
|
{/* 상단 슬림 툴바 */}
|
||||||
<SlimToolbar
|
<SlimToolbar
|
||||||
|
|
@ -4400,7 +4402,8 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
// 드래그 중 시각적 피드백 (다중 선택 지원)
|
// 드래그 중 시각적 피드백 (다중 선택 지원)
|
||||||
const isDraggingThis = dragState.isDragging && dragState.draggedComponent?.id === component.id;
|
const isDraggingThis =
|
||||||
|
dragState.isDragging && dragState.draggedComponent?.id === component.id;
|
||||||
const isBeingDragged =
|
const isBeingDragged =
|
||||||
dragState.isDragging &&
|
dragState.isDragging &&
|
||||||
dragState.draggedComponents.some((dragComp) => dragComp.id === component.id);
|
dragState.draggedComponents.some((dragComp) => dragComp.id === component.id);
|
||||||
|
|
@ -4883,5 +4886,6 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</ScreenPreviewProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -448,10 +448,10 @@ export default function ScreenList({ onScreenSelect, selectedScreen, onDesignScr
|
||||||
{screens.map((screen) => (
|
{screens.map((screen) => (
|
||||||
<TableRow
|
<TableRow
|
||||||
key={screen.screenId}
|
key={screen.screenId}
|
||||||
className={`hover:bg-muted/50 border-b transition-colors ${
|
className={`hover:bg-muted/50 cursor-pointer border-b transition-colors ${
|
||||||
selectedScreen?.screenId === screen.screenId ? "border-primary/20 bg-accent" : ""
|
selectedScreen?.screenId === screen.screenId ? "border-primary/20 bg-accent" : ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleScreenSelect(screen)}
|
onClick={() => onDesignScreen(screen)}
|
||||||
>
|
>
|
||||||
<TableCell className="h-16 cursor-pointer">
|
<TableCell className="h-16 cursor-pointer">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import {
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { useScreenPreview } from "@/contexts/ScreenPreviewContext";
|
||||||
|
|
||||||
interface FlowWidgetProps {
|
interface FlowWidgetProps {
|
||||||
component: FlowComponent;
|
component: FlowComponent;
|
||||||
|
|
@ -53,6 +54,8 @@ export function FlowWidget({
|
||||||
flowRefreshKey,
|
flowRefreshKey,
|
||||||
onFlowRefresh,
|
onFlowRefresh,
|
||||||
}: FlowWidgetProps) {
|
}: FlowWidgetProps) {
|
||||||
|
const { isPreviewMode } = useScreenPreview(); // 프리뷰 모드 확인
|
||||||
|
|
||||||
// 🆕 전역 상태 관리
|
// 🆕 전역 상태 관리
|
||||||
const setSelectedStep = useFlowStepStore((state) => state.setSelectedStep);
|
const setSelectedStep = useFlowStepStore((state) => state.setSelectedStep);
|
||||||
const resetFlow = useFlowStepStore((state) => state.resetFlow);
|
const resetFlow = useFlowStepStore((state) => state.resetFlow);
|
||||||
|
|
@ -312,6 +315,57 @@ export function FlowWidget({
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
|
// 프리뷰 모드에서는 샘플 데이터만 표시
|
||||||
|
if (isPreviewMode) {
|
||||||
|
console.log("🔒 프리뷰 모드: 플로우 데이터 로드 차단 - 샘플 데이터 표시");
|
||||||
|
setFlowData({
|
||||||
|
id: flowId || 0,
|
||||||
|
flowName: flowName || "샘플 플로우",
|
||||||
|
description: "프리뷰 모드 샘플",
|
||||||
|
isActive: true,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
} as FlowDefinition);
|
||||||
|
|
||||||
|
const sampleSteps: FlowStep[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
flowId: flowId || 0,
|
||||||
|
stepName: "시작 단계",
|
||||||
|
stepOrder: 1,
|
||||||
|
stepType: "start",
|
||||||
|
stepConfig: {},
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
flowId: flowId || 0,
|
||||||
|
stepName: "진행 중",
|
||||||
|
stepOrder: 2,
|
||||||
|
stepType: "process",
|
||||||
|
stepConfig: {},
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
flowId: flowId || 0,
|
||||||
|
stepName: "완료",
|
||||||
|
stepOrder: 3,
|
||||||
|
stepType: "end",
|
||||||
|
stepConfig: {},
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
setSteps(sampleSteps);
|
||||||
|
setStepCounts({ 1: 5, 2: 3, 3: 2 });
|
||||||
|
setConnections([]);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 플로우 정보 조회
|
// 플로우 정보 조회
|
||||||
const flowResponse = await getFlowById(flowId!);
|
const flowResponse = await getFlowById(flowId!);
|
||||||
if (!flowResponse.success || !flowResponse.data) {
|
if (!flowResponse.success || !flowResponse.data) {
|
||||||
|
|
@ -413,6 +467,11 @@ export function FlowWidget({
|
||||||
|
|
||||||
// 🆕 스텝 클릭 핸들러 (전역 상태 업데이트 추가)
|
// 🆕 스텝 클릭 핸들러 (전역 상태 업데이트 추가)
|
||||||
const handleStepClick = async (stepId: number, stepName: string) => {
|
const handleStepClick = async (stepId: number, stepName: string) => {
|
||||||
|
// 프리뷰 모드에서는 스텝 클릭 차단
|
||||||
|
if (isPreviewMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 외부 콜백 실행
|
// 외부 콜백 실행
|
||||||
if (onStepClick) {
|
if (onStepClick) {
|
||||||
onStepClick(stepId, stepName);
|
onStepClick(stepId, stepName);
|
||||||
|
|
@ -485,6 +544,11 @@ export function FlowWidget({
|
||||||
|
|
||||||
// 체크박스 토글
|
// 체크박스 토글
|
||||||
const toggleRowSelection = (rowIndex: number) => {
|
const toggleRowSelection = (rowIndex: number) => {
|
||||||
|
// 프리뷰 모드에서는 행 선택 차단
|
||||||
|
if (isPreviewMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const newSelected = new Set(selectedRows);
|
const newSelected = new Set(selectedRows);
|
||||||
if (newSelected.has(rowIndex)) {
|
if (newSelected.has(rowIndex)) {
|
||||||
newSelected.delete(rowIndex);
|
newSelected.delete(rowIndex);
|
||||||
|
|
@ -675,7 +739,13 @@ export function FlowWidget({
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setIsFilterSettingOpen(true)}
|
onClick={() => {
|
||||||
|
if (isPreviewMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsFilterSettingOpen(true);
|
||||||
|
}}
|
||||||
|
disabled={isPreviewMode}
|
||||||
className="h-8 shrink-0 text-xs sm:text-sm"
|
className="h-8 shrink-0 text-xs sm:text-sm"
|
||||||
>
|
>
|
||||||
<Filter className="mr-2 h-3 w-3 sm:h-4 sm:w-4" />
|
<Filter className="mr-2 h-3 w-3 sm:h-4 sm:w-4" />
|
||||||
|
|
@ -887,17 +957,29 @@ export function FlowWidget({
|
||||||
<PaginationContent>
|
<PaginationContent>
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
<PaginationPrevious
|
<PaginationPrevious
|
||||||
onClick={() => setStepDataPage((p) => Math.max(1, p - 1))}
|
onClick={() => {
|
||||||
className={stepDataPage === 1 ? "pointer-events-none opacity-50" : "cursor-pointer"}
|
if (isPreviewMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setStepDataPage((p) => Math.max(1, p - 1));
|
||||||
|
}}
|
||||||
|
className={
|
||||||
|
stepDataPage === 1 || isPreviewMode ? "pointer-events-none opacity-50" : "cursor-pointer"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
{totalStepDataPages <= 7 ? (
|
{totalStepDataPages <= 7 ? (
|
||||||
Array.from({ length: totalStepDataPages }, (_, i) => i + 1).map((page) => (
|
Array.from({ length: totalStepDataPages }, (_, i) => i + 1).map((page) => (
|
||||||
<PaginationItem key={page}>
|
<PaginationItem key={page}>
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
onClick={() => setStepDataPage(page)}
|
onClick={() => {
|
||||||
|
if (isPreviewMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setStepDataPage(page);
|
||||||
|
}}
|
||||||
isActive={stepDataPage === page}
|
isActive={stepDataPage === page}
|
||||||
className="cursor-pointer"
|
className={isPreviewMode ? "pointer-events-none opacity-50" : "cursor-pointer"}
|
||||||
>
|
>
|
||||||
{page}
|
{page}
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
|
|
@ -922,9 +1004,14 @@ export function FlowWidget({
|
||||||
)}
|
)}
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
<PaginationLink
|
<PaginationLink
|
||||||
onClick={() => setStepDataPage(page)}
|
onClick={() => {
|
||||||
|
if (isPreviewMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setStepDataPage(page);
|
||||||
|
}}
|
||||||
isActive={stepDataPage === page}
|
isActive={stepDataPage === page}
|
||||||
className="cursor-pointer"
|
className={isPreviewMode ? "pointer-events-none opacity-50" : "cursor-pointer"}
|
||||||
>
|
>
|
||||||
{page}
|
{page}
|
||||||
</PaginationLink>
|
</PaginationLink>
|
||||||
|
|
@ -935,9 +1022,16 @@ export function FlowWidget({
|
||||||
)}
|
)}
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
<PaginationNext
|
<PaginationNext
|
||||||
onClick={() => setStepDataPage((p) => Math.min(totalStepDataPages, p + 1))}
|
onClick={() => {
|
||||||
|
if (isPreviewMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setStepDataPage((p) => Math.min(totalStepDataPages, p + 1));
|
||||||
|
}}
|
||||||
className={
|
className={
|
||||||
stepDataPage === totalStepDataPages ? "pointer-events-none opacity-50" : "cursor-pointer"
|
stepDataPage === totalStepDataPages || isPreviewMode
|
||||||
|
? "pointer-events-none opacity-50"
|
||||||
|
: "cursor-pointer"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { createContext, useContext } from "react";
|
||||||
|
|
||||||
|
interface ScreenPreviewContextType {
|
||||||
|
isPreviewMode: boolean; // true: 화면 관리(디자이너), false: 실제 화면
|
||||||
|
}
|
||||||
|
|
||||||
|
const ScreenPreviewContext = createContext<ScreenPreviewContextType>({
|
||||||
|
isPreviewMode: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useScreenPreview = () => {
|
||||||
|
return useContext(ScreenPreviewContext);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ScreenPreviewProviderProps {
|
||||||
|
isPreviewMode: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ScreenPreviewProvider: React.FC<ScreenPreviewProviderProps> = ({ isPreviewMode, children }) => {
|
||||||
|
return <ScreenPreviewContext.Provider value={{ isPreviewMode }}>{children}</ScreenPreviewContext.Provider>;
|
||||||
|
};
|
||||||
|
|
@ -22,6 +22,7 @@ import {
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
||||||
import { useCurrentFlowStep } from "@/stores/flowStepStore";
|
import { useCurrentFlowStep } from "@/stores/flowStepStore";
|
||||||
|
import { useScreenPreview } from "@/contexts/ScreenPreviewContext";
|
||||||
|
|
||||||
export interface ButtonPrimaryComponentProps extends ComponentRendererProps {
|
export interface ButtonPrimaryComponentProps extends ComponentRendererProps {
|
||||||
config?: ButtonPrimaryConfig;
|
config?: ButtonPrimaryConfig;
|
||||||
|
|
@ -73,6 +74,8 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||||
flowSelectedStepId,
|
flowSelectedStepId,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
|
const { isPreviewMode } = useScreenPreview(); // 프리뷰 모드 확인
|
||||||
|
|
||||||
// 🆕 플로우 단계별 표시 제어
|
// 🆕 플로우 단계별 표시 제어
|
||||||
const flowConfig = (component as any).webTypeConfig?.flowVisibilityConfig;
|
const flowConfig = (component as any).webTypeConfig?.flowVisibilityConfig;
|
||||||
const currentStep = useCurrentFlowStep(flowConfig?.targetFlowComponentId);
|
const currentStep = useCurrentFlowStep(flowConfig?.targetFlowComponentId);
|
||||||
|
|
@ -355,6 +358,11 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||||
const handleClick = async (e: React.MouseEvent) => {
|
const handleClick = async (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// 프리뷰 모드에서는 버튼 동작 차단
|
||||||
|
if (isPreviewMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 디자인 모드에서는 기본 onClick만 실행
|
// 디자인 모드에서는 기본 onClick만 실행
|
||||||
if (isDesignMode) {
|
if (isDesignMode) {
|
||||||
onClick?.();
|
onClick?.();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue