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