플로우 페이지네이션 안보임
This commit is contained in:
@@ -435,7 +435,7 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
||||
{/* 컴포넌트 타입별 렌더링 */}
|
||||
<div
|
||||
ref={isFlowWidget ? contentRef : undefined}
|
||||
className={isFlowWidget ? "h-auto w-full" : "h-full w-full"}
|
||||
className="h-full w-full"
|
||||
>
|
||||
{/* 영역 타입 */}
|
||||
{type === "area" && renderArea(component, children)}
|
||||
|
||||
@@ -17,7 +17,7 @@ export interface ToolbarButton {
|
||||
|
||||
interface LeftUnifiedToolbarProps {
|
||||
buttons: ToolbarButton[];
|
||||
panelStates: Record<string, { isOpen: boolean }>;
|
||||
panelStates: Record<string, { isOpen: boolean; badge?: number }>;
|
||||
onTogglePanel: (panelId: string) => void;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ export const LeftUnifiedToolbar: React.FC<LeftUnifiedToolbarProps> = ({ buttons,
|
||||
|
||||
const renderButton = (button: ToolbarButton) => {
|
||||
const isActive = panelStates[button.id]?.isOpen || false;
|
||||
const badge = panelStates[button.id]?.badge;
|
||||
|
||||
return (
|
||||
<Button
|
||||
@@ -45,6 +46,11 @@ export const LeftUnifiedToolbar: React.FC<LeftUnifiedToolbarProps> = ({ buttons,
|
||||
<div className="relative">
|
||||
{button.icon}
|
||||
{isActive && <div className="absolute -top-1 -right-1 h-2 w-2 rounded-full bg-white" />}
|
||||
{badge !== undefined && badge > 0 && (
|
||||
<div className="absolute -top-2 -right-2 flex h-5 w-5 items-center justify-center rounded-full bg-red-500 text-[10px] font-bold text-white shadow-md">
|
||||
{badge > 99 ? "99+" : badge}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-[10px] font-medium">{button.label}</span>
|
||||
</Button>
|
||||
|
||||
@@ -36,7 +36,13 @@ interface FlowWidgetProps {
|
||||
onFlowRefresh?: () => void; // 새로고침 완료 콜백
|
||||
}
|
||||
|
||||
export function FlowWidget({ component, onStepClick, onSelectedDataChange, flowRefreshKey, onFlowRefresh }: FlowWidgetProps) {
|
||||
export function FlowWidget({
|
||||
component,
|
||||
onStepClick,
|
||||
onSelectedDataChange,
|
||||
flowRefreshKey,
|
||||
onFlowRefresh,
|
||||
}: FlowWidgetProps) {
|
||||
// 🆕 전역 상태 관리
|
||||
const setSelectedStep = useFlowStepStore((state) => state.setSelectedStep);
|
||||
const resetFlow = useFlowStepStore((state) => state.resetFlow);
|
||||
@@ -55,6 +61,10 @@ export function FlowWidget({ component, onStepClick, onSelectedDataChange, flowR
|
||||
const [stepDataLoading, setStepDataLoading] = useState(false);
|
||||
const [selectedRows, setSelectedRows] = useState<Set<number>>(new Set());
|
||||
|
||||
// 🆕 스텝 데이터 페이지네이션 상태
|
||||
const [stepDataPage, setStepDataPage] = useState(1);
|
||||
const [stepDataPageSize] = useState(20);
|
||||
|
||||
// 오딧 로그 상태
|
||||
const [auditLogs, setAuditLogs] = useState<FlowAuditLog[]>([]);
|
||||
const [auditLogsLoading, setAuditLogsLoading] = useState(false);
|
||||
@@ -73,7 +83,6 @@ export function FlowWidget({ component, onStepClick, onSelectedDataChange, flowR
|
||||
// 🆕 플로우 컴포넌트 ID (버튼이 이 플로우를 참조할 때 사용)
|
||||
const flowComponentId = component.id;
|
||||
|
||||
|
||||
// 선택된 스텝의 데이터를 다시 로드하는 함수
|
||||
const refreshStepData = async () => {
|
||||
if (!flowId) return;
|
||||
@@ -82,7 +91,7 @@ export function FlowWidget({ component, onStepClick, onSelectedDataChange, flowR
|
||||
// 스텝 카운트는 항상 업데이트 (선택된 스텝 유무와 관계없이)
|
||||
const countsResponse = await getAllStepCounts(flowId);
|
||||
console.log("📊 스텝 카운트 API 응답:", countsResponse);
|
||||
|
||||
|
||||
if (countsResponse.success && countsResponse.data) {
|
||||
// Record 형태로 변환
|
||||
const countsMap: Record<number, number> = {};
|
||||
@@ -90,10 +99,10 @@ export function FlowWidget({ component, onStepClick, onSelectedDataChange, flowR
|
||||
countsResponse.data.forEach((item: any) => {
|
||||
countsMap[item.stepId] = item.count;
|
||||
});
|
||||
} else if (typeof countsResponse.data === 'object') {
|
||||
} else if (typeof countsResponse.data === "object") {
|
||||
Object.assign(countsMap, countsResponse.data);
|
||||
}
|
||||
|
||||
|
||||
console.log("✅ 스텝 카운트 업데이트:", countsMap);
|
||||
setStepCounts(countsMap);
|
||||
}
|
||||
@@ -101,7 +110,7 @@ export function FlowWidget({ component, onStepClick, onSelectedDataChange, flowR
|
||||
// 선택된 스텝이 있으면 해당 스텝의 데이터도 새로고침
|
||||
if (selectedStepId) {
|
||||
setStepDataLoading(true);
|
||||
|
||||
|
||||
const response = await getStepDataList(flowId, selectedStepId, 1, 100);
|
||||
|
||||
if (!response.success) {
|
||||
@@ -224,6 +233,7 @@ export function FlowWidget({ component, onStepClick, onSelectedDataChange, flowR
|
||||
setStepData([]);
|
||||
setStepDataColumns([]);
|
||||
setSelectedRows(new Set());
|
||||
setStepDataPage(1); // 🆕 페이지 리셋
|
||||
onSelectedDataChange?.([], null);
|
||||
|
||||
console.log("🔄 [FlowWidget] 단계 선택 해제:", { flowComponentId, stepId });
|
||||
@@ -235,6 +245,7 @@ export function FlowWidget({ component, onStepClick, onSelectedDataChange, flowR
|
||||
setSelectedStep(flowComponentId, stepId); // 🆕 전역 상태 업데이트
|
||||
setStepDataLoading(true);
|
||||
setSelectedRows(new Set());
|
||||
setStepDataPage(1); // 🆕 페이지 리셋
|
||||
onSelectedDataChange?.([], stepId);
|
||||
|
||||
console.log("✅ [FlowWidget] 단계 선택:", { flowComponentId, stepId, stepName });
|
||||
@@ -272,7 +283,7 @@ export function FlowWidget({ component, onStepClick, onSelectedDataChange, flowR
|
||||
newSelected.add(rowIndex);
|
||||
}
|
||||
setSelectedRows(newSelected);
|
||||
|
||||
|
||||
// 선택된 데이터를 상위로 전달
|
||||
const selectedData = Array.from(newSelected).map((index) => stepData[index]);
|
||||
console.log("🌊 FlowWidget - 체크박스 토글, 상위로 전달:", {
|
||||
@@ -294,13 +305,12 @@ export function FlowWidget({ component, onStepClick, onSelectedDataChange, flowR
|
||||
newSelected = new Set(stepData.map((_, index) => index));
|
||||
}
|
||||
setSelectedRows(newSelected);
|
||||
|
||||
|
||||
// 선택된 데이터를 상위로 전달
|
||||
const selectedData = Array.from(newSelected).map((index) => stepData[index]);
|
||||
onSelectedDataChange?.(selectedData, selectedStepId);
|
||||
};
|
||||
|
||||
|
||||
// 오딧 로그 로드
|
||||
const loadAuditLogs = async () => {
|
||||
if (!flowId) return;
|
||||
@@ -330,6 +340,10 @@ export function FlowWidget({ component, onStepClick, onSelectedDataChange, flowR
|
||||
const paginatedAuditLogs = auditLogs.slice((auditPage - 1) * auditPageSize, auditPage * auditPageSize);
|
||||
const totalAuditPages = Math.ceil(auditLogs.length / auditPageSize);
|
||||
|
||||
// 🆕 페이지네이션된 스텝 데이터
|
||||
const paginatedStepData = stepData.slice((stepDataPage - 1) * stepDataPageSize, stepDataPage * stepDataPageSize);
|
||||
const totalStepDataPages = Math.ceil(stepData.length / stepDataPageSize);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-8">
|
||||
@@ -371,9 +385,9 @@ export function FlowWidget({ component, onStepClick, onSelectedDataChange, flowR
|
||||
: "flex flex-col items-center gap-4";
|
||||
|
||||
return (
|
||||
<div className="@container min-h-full w-full p-2 sm:p-4 lg:p-6">
|
||||
<div className="@container flex h-full w-full flex-col p-2 sm:p-4 lg:p-6">
|
||||
{/* 플로우 제목 */}
|
||||
<div className="mb-3 sm:mb-4">
|
||||
<div className="mb-3 flex-shrink-0 sm:mb-4">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<h3 className="text-foreground text-base font-semibold sm:text-lg lg:text-xl">{flowData.name}</h3>
|
||||
|
||||
@@ -566,7 +580,7 @@ export function FlowWidget({ component, onStepClick, onSelectedDataChange, flowR
|
||||
</div>
|
||||
|
||||
{/* 플로우 스텝 목록 */}
|
||||
<div className={containerClass}>
|
||||
<div className={`${containerClass} flex-shrink-0`}>
|
||||
{steps.map((step, index) => (
|
||||
<React.Fragment key={step.id}>
|
||||
{/* 스텝 카드 */}
|
||||
@@ -633,132 +647,212 @@ export function FlowWidget({ component, onStepClick, onSelectedDataChange, flowR
|
||||
|
||||
{/* 선택된 스텝의 데이터 리스트 */}
|
||||
{selectedStepId !== null && (
|
||||
<div className="bg-muted/30 mt-4 w-full rounded-lg p-4 sm:mt-6 sm:rounded-xl sm:p-5 lg:mt-8 lg:p-6">
|
||||
<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="mb-4 sm:mb-6">
|
||||
<div className="flex-1">
|
||||
<h4 className="text-foreground text-base font-semibold sm:text-lg">
|
||||
{steps.find((s) => s.id === selectedStepId)?.stepName}
|
||||
</h4>
|
||||
<p className="text-muted-foreground mt-1 text-xs sm:text-sm">
|
||||
총 {stepData.length}건의 데이터
|
||||
{selectedRows.size > 0 && (
|
||||
<span className="text-primary ml-2 font-medium">({selectedRows.size}건 선택됨)</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<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}
|
||||
</h4>
|
||||
<p className="text-muted-foreground mt-1 text-xs sm:text-sm">
|
||||
총 {stepData.length}건의 데이터
|
||||
{selectedRows.size > 0 && (
|
||||
<span className="text-primary ml-2 font-medium">({selectedRows.size}건 선택됨)</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 데이터 테이블 */}
|
||||
{stepDataLoading ? (
|
||||
<div className="flex items-center justify-center py-8 sm: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-xs sm:ml-3 sm:text-sm">데이터 로딩 중...</span>
|
||||
</div>
|
||||
) : stepData.length === 0 ? (
|
||||
<div className="bg-card flex flex-col items-center justify-center rounded-lg border-2 border-dashed py-8 sm:py-12">
|
||||
<svg
|
||||
className="text-muted-foreground/50 mb-2 h-10 w-10 sm:mb-3 sm:h-12 sm: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-xs sm:text-sm">데이터가 없습니다</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* 모바일: 카드 뷰 (컨테이너 640px 미만) */}
|
||||
<div className="space-y-3 @sm:hidden">
|
||||
{stepData.map((row, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`bg-card rounded-lg border p-3 transition-colors ${
|
||||
selectedRows.has(index) ? "border-primary bg-primary/5" : "border-border"
|
||||
}`}
|
||||
>
|
||||
{/* 체크박스 헤더 */}
|
||||
{allowDataMove && (
|
||||
<div className="mb-2 flex items-center justify-between border-b pb-2">
|
||||
<span className="text-muted-foreground text-xs font-medium">선택</span>
|
||||
<Checkbox checked={selectedRows.has(index)} onCheckedChange={() => toggleRowSelection(index)} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 데이터 필드들 */}
|
||||
<div className="space-y-2">
|
||||
{stepDataColumns.map((col) => (
|
||||
<div key={col} className="flex justify-between gap-2">
|
||||
<span className="text-muted-foreground text-xs font-medium">{col}:</span>
|
||||
<span className="text-foreground truncate text-xs">
|
||||
{row[col] !== null && row[col] !== undefined ? (
|
||||
String(row[col])
|
||||
) : (
|
||||
<span className="text-muted-foreground">-</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</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>
|
||||
|
||||
{/* 데스크톱: 테이블 뷰 (컨테이너 640px 이상) */}
|
||||
<div className="bg-card hidden overflow-x-auto rounded-lg border shadow-sm @sm:block">
|
||||
<Table>
|
||||
<TableHeader className="bg-muted/50">
|
||||
<TableRow className="hover:bg-transparent">
|
||||
{allowDataMove && (
|
||||
<TableHead className="w-12">
|
||||
<Checkbox
|
||||
checked={selectedRows.size === stepData.length && stepData.length > 0}
|
||||
onCheckedChange={toggleAllRows}
|
||||
/>
|
||||
</TableHead>
|
||||
)}
|
||||
{stepDataColumns.map((col) => (
|
||||
<TableHead key={col} className="text-xs font-semibold whitespace-nowrap sm:text-sm">
|
||||
{col}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{stepData.map((row, index) => (
|
||||
<TableRow
|
||||
key={index}
|
||||
className={`transition-colors ${selectedRows.has(index) ? "bg-primary/5" : "hover:bg-muted/50"}`}
|
||||
) : 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">
|
||||
{paginatedStepData.map((row, pageIndex) => {
|
||||
const actualIndex = (stepDataPage - 1) * stepDataPageSize + pageIndex;
|
||||
return (
|
||||
<div
|
||||
key={actualIndex}
|
||||
className={`bg-card rounded-md border p-3 transition-colors ${
|
||||
selectedRows.has(actualIndex) ? "bg-primary/5 border-primary/30" : ""
|
||||
}`}
|
||||
>
|
||||
{allowDataMove && (
|
||||
<TableCell className="w-12">
|
||||
<div className="mb-2 flex items-center justify-between border-b pb-2">
|
||||
<span className="text-muted-foreground text-xs font-medium">선택</span>
|
||||
<Checkbox
|
||||
checked={selectedRows.has(index)}
|
||||
onCheckedChange={() => toggleRowSelection(index)}
|
||||
checked={selectedRows.has(actualIndex)}
|
||||
onCheckedChange={() => toggleRowSelection(actualIndex)}
|
||||
/>
|
||||
</TableCell>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-1.5">
|
||||
{stepDataColumns.map((col) => (
|
||||
<div key={col} className="flex justify-between gap-2 text-xs">
|
||||
<span className="text-muted-foreground font-medium">{col}:</span>
|
||||
<span className="text-foreground truncate">
|
||||
{row[col] !== null && row[col] !== undefined ? (
|
||||
String(row[col])
|
||||
) : (
|
||||
<span className="text-muted-foreground">-</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</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) => (
|
||||
<TableCell key={col} className="font-mono text-xs whitespace-nowrap sm:text-sm">
|
||||
{row[col] !== null && row[col] !== undefined ? (
|
||||
String(row[col])
|
||||
) : (
|
||||
<span className="text-muted-foreground">-</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<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>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</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>
|
||||
|
||||
{/* 페이지네이션 푸터 */}
|
||||
{!stepDataLoading && stepData.length > 0 && totalStepDataPages > 1 && (
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user