화면 비율조정 수정
This commit is contained in:
parent
7d6281d289
commit
25cd23c1fb
|
|
@ -52,9 +52,8 @@ export default function ScreenViewPage() {
|
|||
modalDescription?: string;
|
||||
}>({});
|
||||
|
||||
// 자동 스케일 조정 (사용자 화면 크기에 맞춤)
|
||||
const [scale, setScale] = useState(1);
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
const [scale, setScale] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
const initComponents = async () => {
|
||||
|
|
@ -140,32 +139,37 @@ export default function ScreenViewPage() {
|
|||
}
|
||||
}, [screenId]);
|
||||
|
||||
// 자동 스케일 조정 useEffect (항상 화면에 꽉 차게)
|
||||
// 캔버스 비율 조정 (사용자 화면에 맞게 자동 스케일)
|
||||
useEffect(() => {
|
||||
const updateScale = () => {
|
||||
if (containerRef.current && layout) {
|
||||
const screenWidth = layout?.screenResolution?.width || 1200;
|
||||
const designWidth = layout?.screenResolution?.width || 1200;
|
||||
const designHeight = layout?.screenResolution?.height || 800;
|
||||
|
||||
const containerWidth = containerRef.current.offsetWidth;
|
||||
const availableWidth = containerWidth - 32; // 좌우 패딩 16px * 2
|
||||
const containerHeight = containerRef.current.offsetHeight;
|
||||
|
||||
// 항상 화면에 맞춰서 스케일 조정 (늘리거나 줄임)
|
||||
const newScale = availableWidth / screenWidth;
|
||||
// 가로/세로 비율 중 작은 것을 선택 (화면에 맞게)
|
||||
const scaleX = containerWidth / designWidth;
|
||||
const scaleY = containerHeight / designHeight;
|
||||
const newScale = Math.min(scaleX, scaleY);
|
||||
|
||||
console.log("📏 스케일 계산 (화면 꽉 차게):", {
|
||||
screenWidth,
|
||||
console.log("📏 캔버스 스케일 계산:", {
|
||||
designWidth,
|
||||
designHeight,
|
||||
containerWidth,
|
||||
availableWidth,
|
||||
scale: newScale,
|
||||
containerHeight,
|
||||
scaleX,
|
||||
scaleY,
|
||||
finalScale: newScale,
|
||||
});
|
||||
|
||||
setScale(newScale);
|
||||
}
|
||||
};
|
||||
|
||||
// 초기 측정 (DOM이 완전히 렌더링된 후)
|
||||
const timer = setTimeout(() => {
|
||||
updateScale();
|
||||
}, 100);
|
||||
// 초기 측정
|
||||
const timer = setTimeout(updateScale, 100);
|
||||
|
||||
window.addEventListener("resize", updateScale);
|
||||
return () => {
|
||||
|
|
@ -207,17 +211,16 @@ export default function ScreenViewPage() {
|
|||
const screenHeight = layout?.screenResolution?.height || 800;
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="bg-background flex h-full w-full flex-col overflow-hidden">
|
||||
<div ref={containerRef} className="bg-background flex h-full w-full items-start justify-start overflow-hidden">
|
||||
{/* 절대 위치 기반 렌더링 */}
|
||||
{layout && layout.components.length > 0 ? (
|
||||
<div
|
||||
className="bg-background relative flex-1"
|
||||
className="bg-background relative origin-top-left"
|
||||
style={{
|
||||
width: screenWidth,
|
||||
height: "100%",
|
||||
width: layout?.screenResolution?.width || 1200,
|
||||
height: layout?.screenResolution?.height || 800,
|
||||
transform: `scale(${scale})`,
|
||||
transformOrigin: "top left",
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -457,7 +457,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
|
|||
</aside>
|
||||
|
||||
{/* 가운데 컨텐츠 영역 - 스크롤 가능 */}
|
||||
<main className="h-[calc(100vh-3.5rem)] min-w-0 flex-1 overflow-auto bg-white p-4">{children}</main>
|
||||
<main className="h-[calc(100vh-3.5rem)] min-w-0 flex-1 overflow-auto bg-white">{children}</main>
|
||||
</div>
|
||||
|
||||
{/* 프로필 수정 모달 */}
|
||||
|
|
|
|||
|
|
@ -334,7 +334,7 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
|||
console.log("🔍 InteractiveScreenViewer 최종 flowComponent:", flowComponent);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<div className="w-full">
|
||||
<FlowWidget component={flowComponent as any} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ interface RealtimePreviewProps {
|
|||
onZoneComponentDrop?: (e: React.DragEvent, zoneId: string, layoutId: string) => void; // 존별 드롭 핸들러
|
||||
onZoneClick?: (zoneId: string) => void; // 존 클릭 핸들러
|
||||
onConfigChange?: (config: any) => void; // 설정 변경 핸들러
|
||||
|
||||
|
||||
// 버튼 액션을 위한 props
|
||||
screenId?: number;
|
||||
tableName?: string;
|
||||
|
|
@ -47,7 +47,7 @@ interface RealtimePreviewProps {
|
|||
onRefresh?: () => void;
|
||||
flowRefreshKey?: number;
|
||||
onFlowRefresh?: () => void;
|
||||
|
||||
|
||||
// 폼 데이터 관련 props
|
||||
formData?: Record<string, any>;
|
||||
onFormDataChange?: (fieldName: string, value: any) => void;
|
||||
|
|
@ -115,24 +115,24 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
|||
// 플로우 위젯의 실제 높이 측정
|
||||
React.useEffect(() => {
|
||||
const isFlowWidget = component.type === "component" && (component as any).componentType === "flow-widget";
|
||||
|
||||
|
||||
if (isFlowWidget && contentRef.current) {
|
||||
const measureHeight = () => {
|
||||
if (contentRef.current) {
|
||||
// getBoundingClientRect()로 실제 렌더링된 높이 측정
|
||||
const rect = contentRef.current.getBoundingClientRect();
|
||||
const measured = rect.height;
|
||||
|
||||
|
||||
// scrollHeight도 함께 확인하여 더 큰 값 사용
|
||||
const scrollHeight = contentRef.current.scrollHeight;
|
||||
const rawHeight = Math.max(measured, scrollHeight);
|
||||
|
||||
|
||||
// 40px 단위로 올림
|
||||
const finalHeight = Math.ceil(rawHeight / 40) * 40;
|
||||
|
||||
|
||||
if (finalHeight > 0 && Math.abs(finalHeight - (actualHeight || 0)) > 10) {
|
||||
setActualHeight(finalHeight);
|
||||
|
||||
|
||||
// 컴포넌트의 실제 size.height도 업데이트 (중복 업데이트 방지)
|
||||
if (onConfigChange && finalHeight !== lastUpdatedHeight.current && finalHeight !== component.size?.height) {
|
||||
lastUpdatedHeight.current = finalHeight;
|
||||
|
|
@ -142,11 +142,11 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
|||
newHeight: finalHeight,
|
||||
});
|
||||
// size는 별도 속성이므로 직접 업데이트
|
||||
const event = new CustomEvent('updateComponentSize', {
|
||||
const event = new CustomEvent("updateComponentSize", {
|
||||
detail: {
|
||||
componentId: component.id,
|
||||
height: finalHeight
|
||||
}
|
||||
height: finalHeight,
|
||||
},
|
||||
});
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
|
|
@ -276,10 +276,10 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
|||
>
|
||||
{/* 동적 컴포넌트 렌더링 */}
|
||||
<div
|
||||
ref={component.type === "component" && (component as any).componentType === "flow-widget" ? contentRef : undefined}
|
||||
className={`${component.type === "component" && (component as any).componentType === "flow-widget" ? "h-auto" : "h-full"} w-full max-w-full ${
|
||||
component.componentConfig?.type === "table-list" ? "overflow-hidden" : "overflow-visible"
|
||||
}`}
|
||||
ref={
|
||||
component.type === "component" && (component as any).componentType === "flow-widget" ? contentRef : undefined
|
||||
}
|
||||
className={`${component.type === "component" && (component as any).componentType === "flow-widget" ? "h-auto" : "h-full"} w-full max-w-full overflow-visible`}
|
||||
>
|
||||
<DynamicComponentRenderer
|
||||
component={component}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { getFlowById, getAllStepCounts, getStepDataList, getFlowAuditLogs } from
|
|||
import type { FlowDefinition, FlowStep, FlowAuditLog } from "@/types/flow";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Dialog,
|
||||
|
|
@ -63,7 +64,7 @@ export function FlowWidget({
|
|||
|
||||
// 🆕 스텝 데이터 페이지네이션 상태
|
||||
const [stepDataPage, setStepDataPage] = useState(1);
|
||||
const [stepDataPageSize] = useState(20);
|
||||
const [stepDataPageSize, setStepDataPageSize] = useState(10);
|
||||
|
||||
// 오딧 로그 상태
|
||||
const [auditLogs, setAuditLogs] = useState<FlowAuditLog[]>([]);
|
||||
|
|
@ -385,7 +386,7 @@ export function FlowWidget({
|
|||
: "flex flex-col items-center gap-4";
|
||||
|
||||
return (
|
||||
<div className="@container flex h-full w-full flex-col p-2 sm:p-4 lg:p-6">
|
||||
<div className="@container flex w-full flex-col p-2 sm:p-4 lg:p-6">
|
||||
{/* 플로우 제목 */}
|
||||
<div className="mb-3 flex-shrink-0 sm:mb-4">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
|
|
@ -647,8 +648,8 @@ export function FlowWidget({
|
|||
|
||||
{/* 선택된 스텝의 데이터 리스트 */}
|
||||
{selectedStepId !== null && (
|
||||
<div className="bg-muted/30 mt-4 flex min-h-0 w-full flex-1 flex-col rounded-lg border sm:mt-6 lg:mt-8">
|
||||
{/* 헤더 */}
|
||||
<div className="bg-muted/30 mt-4 flex w-full flex-col rounded-lg border sm:mt-6 lg:mt-8">
|
||||
{/* 헤더 - 자동 높이 */}
|
||||
<div className="bg-background flex-shrink-0 border-b px-4 py-3 sm:px-6 sm:py-4">
|
||||
<h4 className="text-foreground text-base font-semibold sm:text-lg">
|
||||
{steps.find((s) => s.id === selectedStepId)?.stepName}
|
||||
|
|
@ -661,34 +662,34 @@ export function FlowWidget({
|
|||
</p>
|
||||
</div>
|
||||
|
||||
{/* 데이터 영역 - 스크롤 가능 */}
|
||||
<div className="min-h-0 flex-1 overflow-auto">
|
||||
{stepDataLoading ? (
|
||||
<div className="flex h-full items-center justify-center py-12">
|
||||
<Loader2 className="text-primary h-6 w-6 animate-spin sm:h-8 sm:w-8" />
|
||||
<span className="text-muted-foreground ml-2 text-sm">데이터 로딩 중...</span>
|
||||
</div>
|
||||
) : stepData.length === 0 ? (
|
||||
<div className="flex h-full flex-col items-center justify-center py-12">
|
||||
<svg
|
||||
className="text-muted-foreground/50 mb-3 h-12 w-12"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.5}
|
||||
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-muted-foreground text-sm">데이터가 없습니다</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* 모바일: 카드 뷰 */}
|
||||
<div className="space-y-2 p-3 @sm:hidden">
|
||||
{/* 데이터 영역 - 고정 높이 + 스크롤 */}
|
||||
{stepDataLoading ? (
|
||||
<div className="flex h-64 items-center justify-center">
|
||||
<Loader2 className="text-primary h-6 w-6 animate-spin sm:h-8 sm:w-8" />
|
||||
<span className="text-muted-foreground ml-2 text-sm">데이터 로딩 중...</span>
|
||||
</div>
|
||||
) : stepData.length === 0 ? (
|
||||
<div className="flex h-64 flex-col items-center justify-center">
|
||||
<svg
|
||||
className="text-muted-foreground/50 mb-3 h-12 w-12"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.5}
|
||||
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-muted-foreground text-sm">데이터가 없습니다</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* 모바일: 카드 뷰 - 고정 높이 + 스크롤 */}
|
||||
<div className="overflow-y-auto @sm:hidden" style={{ height: "450px" }}>
|
||||
<div className="space-y-2 p-3">
|
||||
{paginatedStepData.map((row, pageIndex) => {
|
||||
const actualIndex = (stepDataPage - 1) * stepDataPageSize + pageIndex;
|
||||
return (
|
||||
|
|
@ -725,132 +726,159 @@ export function FlowWidget({
|
|||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 데스크톱: 테이블 뷰 */}
|
||||
<div className="hidden @sm:block">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-muted/50 hover:bg-muted/50">
|
||||
{allowDataMove && (
|
||||
<TableHead className="bg-muted/50 sticky top-0 left-0 z-20 w-12 border-b px-3 py-2 text-center shadow-sm">
|
||||
<Checkbox
|
||||
checked={selectedRows.size === stepData.length && stepData.length > 0}
|
||||
onCheckedChange={toggleAllRows}
|
||||
/>
|
||||
</TableHead>
|
||||
)}
|
||||
{stepDataColumns.map((col) => (
|
||||
<TableHead
|
||||
key={col}
|
||||
className="bg-muted/50 sticky top-0 z-10 border-b px-3 py-2 text-xs font-semibold whitespace-nowrap sm:text-sm"
|
||||
>
|
||||
{col}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{paginatedStepData.map((row, pageIndex) => {
|
||||
const actualIndex = (stepDataPage - 1) * stepDataPageSize + pageIndex;
|
||||
return (
|
||||
<TableRow
|
||||
key={actualIndex}
|
||||
className={`hover:bg-muted/50 ${selectedRows.has(actualIndex) ? "bg-primary/5" : ""}`}
|
||||
>
|
||||
{allowDataMove && (
|
||||
<TableCell className="bg-background sticky left-0 z-10 border-b px-3 py-2 text-center">
|
||||
<Checkbox
|
||||
checked={selectedRows.has(actualIndex)}
|
||||
onCheckedChange={() => toggleRowSelection(actualIndex)}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
{stepDataColumns.map((col) => (
|
||||
<TableCell key={col} className="border-b px-3 py-2 text-xs whitespace-nowrap sm:text-sm">
|
||||
{row[col] !== null && row[col] !== undefined ? (
|
||||
String(row[col])
|
||||
) : (
|
||||
<span className="text-muted-foreground">-</span>
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{/* 데스크톱: 테이블 뷰 - 고정 높이 + 스크롤 */}
|
||||
<div className="hidden overflow-auto @sm:block" style={{ height: "450px" }}>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="bg-muted/50 hover:bg-muted/50">
|
||||
{allowDataMove && (
|
||||
<TableHead className="bg-muted/50 sticky top-0 left-0 z-20 w-12 border-b px-3 py-2 text-center shadow-sm">
|
||||
<Checkbox
|
||||
checked={selectedRows.size === stepData.length && stepData.length > 0}
|
||||
onCheckedChange={toggleAllRows}
|
||||
/>
|
||||
</TableHead>
|
||||
)}
|
||||
{stepDataColumns.map((col) => (
|
||||
<TableHead
|
||||
key={col}
|
||||
className="bg-muted/50 sticky top-0 z-10 border-b px-3 py-2 text-xs font-semibold whitespace-nowrap sm:text-sm"
|
||||
>
|
||||
{col}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{paginatedStepData.map((row, pageIndex) => {
|
||||
const actualIndex = (stepDataPage - 1) * stepDataPageSize + pageIndex;
|
||||
return (
|
||||
<TableRow
|
||||
key={actualIndex}
|
||||
className={`hover:bg-muted/50 ${selectedRows.has(actualIndex) ? "bg-primary/5" : ""}`}
|
||||
>
|
||||
{allowDataMove && (
|
||||
<TableCell className="bg-background sticky left-0 z-10 border-b px-3 py-2 text-center">
|
||||
<Checkbox
|
||||
checked={selectedRows.has(actualIndex)}
|
||||
onCheckedChange={() => toggleRowSelection(actualIndex)}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
{stepDataColumns.map((col) => (
|
||||
<TableCell key={col} className="border-b px-3 py-2 text-xs whitespace-nowrap sm:text-sm">
|
||||
{row[col] !== null && row[col] !== undefined ? (
|
||||
String(row[col])
|
||||
) : (
|
||||
<span className="text-muted-foreground">-</span>
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 페이지네이션 푸터 */}
|
||||
{!stepDataLoading && stepData.length > 0 && totalStepDataPages > 1 && (
|
||||
{/* 페이지네이션 - 항상 하단에 고정 */}
|
||||
{!stepDataLoading && stepData.length > 0 && (
|
||||
<div className="bg-background flex-shrink-0 border-t px-4 py-3 sm:px-6">
|
||||
<div className="flex flex-col items-center justify-between gap-3 sm:flex-row">
|
||||
<div className="text-muted-foreground text-xs sm:text-sm">
|
||||
페이지 {stepDataPage} / {totalStepDataPages} (총 {stepData.length}건)
|
||||
{/* 왼쪽: 페이지 정보 + 페이지 크기 선택 */}
|
||||
<div className="flex flex-col items-center gap-2 sm:flex-row sm:gap-4">
|
||||
<div className="text-muted-foreground text-xs sm:text-sm">
|
||||
페이지 {stepDataPage} / {totalStepDataPages} (총 {stepData.length}건)
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-muted-foreground text-xs">표시 개수:</span>
|
||||
<Select
|
||||
value={stepDataPageSize.toString()}
|
||||
onValueChange={(value) => {
|
||||
setStepDataPageSize(Number(value));
|
||||
setStepDataPage(1); // 페이지 크기 변경 시 첫 페이지로
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-8 w-20 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="10">10개</SelectItem>
|
||||
<SelectItem value="20">20개</SelectItem>
|
||||
<SelectItem value="50">50개</SelectItem>
|
||||
<SelectItem value="100">100개</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<Pagination>
|
||||
<PaginationContent>
|
||||
<PaginationItem>
|
||||
<PaginationPrevious
|
||||
onClick={() => setStepDataPage((p) => Math.max(1, p - 1))}
|
||||
className={stepDataPage === 1 ? "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)}
|
||||
isActive={stepDataPage === page}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{page}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
))
|
||||
) : (
|
||||
<>
|
||||
{Array.from({ length: totalStepDataPages }, (_, i) => i + 1)
|
||||
.filter((page) => {
|
||||
return (
|
||||
page === 1 ||
|
||||
page === totalStepDataPages ||
|
||||
(page >= stepDataPage - 2 && page <= stepDataPage + 2)
|
||||
);
|
||||
})
|
||||
.map((page, idx, arr) => (
|
||||
<React.Fragment key={page}>
|
||||
{idx > 0 && arr[idx - 1] !== page - 1 && (
|
||||
|
||||
{/* 오른쪽: 페이지네이션 */}
|
||||
{totalStepDataPages > 1 && (
|
||||
<Pagination>
|
||||
<PaginationContent>
|
||||
<PaginationItem>
|
||||
<PaginationPrevious
|
||||
onClick={() => setStepDataPage((p) => Math.max(1, p - 1))}
|
||||
className={stepDataPage === 1 ? "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)}
|
||||
isActive={stepDataPage === page}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{page}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
))
|
||||
) : (
|
||||
<>
|
||||
{Array.from({ length: totalStepDataPages }, (_, i) => i + 1)
|
||||
.filter((page) => {
|
||||
return (
|
||||
page === 1 ||
|
||||
page === totalStepDataPages ||
|
||||
(page >= stepDataPage - 2 && page <= stepDataPage + 2)
|
||||
);
|
||||
})
|
||||
.map((page, idx, arr) => (
|
||||
<React.Fragment key={page}>
|
||||
{idx > 0 && arr[idx - 1] !== page - 1 && (
|
||||
<PaginationItem>
|
||||
<span className="text-muted-foreground px-2">...</span>
|
||||
</PaginationItem>
|
||||
)}
|
||||
<PaginationItem>
|
||||
<span className="text-muted-foreground px-2">...</span>
|
||||
<PaginationLink
|
||||
onClick={() => setStepDataPage(page)}
|
||||
isActive={stepDataPage === page}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{page}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
)}
|
||||
<PaginationItem>
|
||||
<PaginationLink
|
||||
onClick={() => setStepDataPage(page)}
|
||||
isActive={stepDataPage === page}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{page}
|
||||
</PaginationLink>
|
||||
</PaginationItem>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
<PaginationItem>
|
||||
<PaginationNext
|
||||
onClick={() => setStepDataPage((p) => Math.min(totalStepDataPages, p + 1))}
|
||||
className={
|
||||
stepDataPage === totalStepDataPages ? "pointer-events-none opacity-50" : "cursor-pointer"
|
||||
}
|
||||
/>
|
||||
</PaginationItem>
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
<PaginationItem>
|
||||
<PaginationNext
|
||||
onClick={() => setStepDataPage((p) => Math.min(totalStepDataPages, p + 1))}
|
||||
className={
|
||||
stepDataPage === totalStepDataPages ? "pointer-events-none opacity-50" : "cursor-pointer"
|
||||
}
|
||||
/>
|
||||
</PaginationItem>
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Reference in New Issue