mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
adding pagination.
This commit is contained in:
@@ -3,35 +3,46 @@ import {Page, PageActions, PageContent, PageDescription, PageHeader, PageTitle}
|
|||||||
import {Button} from "@/components/ui/button";
|
import {Button} from "@/components/ui/button";
|
||||||
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
|
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
|
||||||
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||||
import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@/components/ui/table";
|
import {TablePagination} from "@/components/wrappers/table/table-pagination";
|
||||||
import {StatusBadge} from "@/components/wrappers/status-badge";
|
import {columns} from "@/features/backup/columns";
|
||||||
|
import {DataTableWithPagination} from "@/components/wrappers/table/data-table-with-pagination";
|
||||||
|
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{}>) {
|
export default async function RoutePage(props: PageParams<{}>) {
|
||||||
|
|
||||||
const backups = [
|
const backups = [
|
||||||
{'id': 'backup-1', 'createdAt': '2023-11-27T11:26:54.870914Z', 'status': 'waiting'},
|
{'id': 'backup-1', 'createdAt': '2023-11-27T11:26:54.870914Z', 'status': 'pending'},
|
||||||
{'id': 'backup-2', 'createdAt': '2023-12-03T11:26:54.870927Z', 'status': 'failed'},
|
{'id': 'backup-2', 'createdAt': '2023-12-03T11:26:54.870927Z', 'status': 'failed'},
|
||||||
{'id': 'backup-3', 'createdAt': '2023-12-12T11:26:54.870937Z', 'status': 'success'},
|
{'id': 'backup-3', 'createdAt': '2023-12-12T11:26:54.870937Z', 'status': 'success'},
|
||||||
{'id': 'backup-4', 'createdAt': '2023-11-23T11:26:54.870946Z', 'status': 'ongoing'},
|
{'id': 'backup-4', 'createdAt': '2023-11-23T11:26:54.870946Z', 'status': 'processing'},
|
||||||
{'id': 'backup-5', 'createdAt': '2023-12-09T11:26:54.870955Z', 'status': 'waiting'},
|
{'id': 'backup-5', 'createdAt': '2023-12-09T11:26:54.870955Z', 'status': 'pending'},
|
||||||
{'id': 'backup-6', 'createdAt': '2023-11-29T11:26:54.870964Z', 'status': 'failed'},
|
{'id': 'backup-6', 'createdAt': '2023-11-29T11:26:54.870964Z', 'status': 'failed'},
|
||||||
{'id': 'backup-7', 'createdAt': '2023-12-07T11:26:54.870973Z', 'status': 'success'},
|
{'id': 'backup-7', 'createdAt': '2023-12-07T11:26:54.870973Z', 'status': 'success'},
|
||||||
{'id': 'backup-8', 'createdAt': '2023-11-18T11:26:54.870982Z', 'status': 'ongoing'},
|
{'id': 'backup-8', 'createdAt': '2023-11-18T11:26:54.870982Z', 'status': 'processing'},
|
||||||
{'id': 'backup-9', 'createdAt': '2023-12-01T11:26:54.870991Z', 'status': 'waiting'},
|
{'id': 'backup-9', 'createdAt': '2023-12-01T11:26:54.870991Z', 'status': 'pending'},
|
||||||
{'id': 'backup-10', 'createdAt': '2023-11-25T11:26:54.871000Z', 'status': 'failed'}
|
{'id': 'backup-10', 'createdAt': '2023-11-25T11:26:54.871000Z', 'status': 'failed'},
|
||||||
|
{'id': 'backup-1', 'createdAt': '2023-11-27T11:26:54.870914Z', 'status': 'pending'},
|
||||||
|
{'id': 'backup-2', 'createdAt': '2023-12-03T11:26:54.870927Z', 'status': 'failed'},
|
||||||
|
{'id': 'backup-3', 'createdAt': '2023-12-12T11:26:54.870937Z', 'status': 'success'},
|
||||||
|
{'id': 'backup-4', 'createdAt': '2023-11-23T11:26:54.870946Z', 'status': 'processing'},
|
||||||
|
{'id': 'backup-5', 'createdAt': '2023-12-09T11:26:54.870955Z', 'status': 'pending'},
|
||||||
|
{'id': 'backup-6', 'createdAt': '2023-11-29T11:26:54.870964Z', 'status': 'failed'},
|
||||||
|
{'id': 'backup-7', 'createdAt': '2023-12-07T11:26:54.870973Z', 'status': 'success'},
|
||||||
|
{'id': 'backup-8', 'createdAt': '2023-11-18T11:26:54.870982Z', 'status': 'processing'},
|
||||||
|
{'id': 'backup-9', 'createdAt': '2023-12-01T11:26:54.870991Z', 'status': 'pending'},
|
||||||
|
{'id': 'backup-11', 'createdAt': '2023-11-25T11:26:54.871000Z', 'status': 'failed'}
|
||||||
]
|
]
|
||||||
|
|
||||||
const restores = [
|
const restores = [
|
||||||
{'id': 'restore-1', 'backupId': 'backup-1', 'createdAt': '2023-11-27T11:26:54.870914Z', 'status': 'waiting'},
|
{'id': 'restore-1', 'backupId': 'backup-1', 'createdAt': '2023-11-27T11:26:54.870914Z', 'status': 'pending'},
|
||||||
{'id': 'restore-2', 'backupId': 'backup-2', 'createdAt': '2023-12-03T11:26:54.870927Z', 'status': 'failed'},
|
{'id': 'restore-2', 'backupId': 'backup-2', 'createdAt': '2023-12-03T11:26:54.870927Z', 'status': 'failed'},
|
||||||
{'id': 'restore-3', 'backupId': 'backup-3', 'createdAt': '2023-12-12T11:26:54.870937Z', 'status': 'success'},
|
{'id': 'restore-3', 'backupId': 'backup-3', 'createdAt': '2023-12-12T11:26:54.870937Z', 'status': 'success'},
|
||||||
{'id': 'restore-4', 'backupId': 'backup-4', 'createdAt': '2023-11-23T11:26:54.870946Z', 'status': 'ongoing'},
|
{'id': 'restore-4', 'backupId': 'backup-4', 'createdAt': '2023-11-23T11:26:54.870946Z', 'status': 'processing'},
|
||||||
{'id': 'restore-5', 'backupId': 'backup-5', 'createdAt': '2023-12-09T11:26:54.870955Z', 'status': 'waiting'},
|
{'id': 'restore-5', 'backupId': 'backup-5', 'createdAt': '2023-12-09T11:26:54.870955Z', 'status': 'pending'},
|
||||||
{'id': 'restore-6', 'backupId': 'backup-6', 'createdAt': '2023-11-29T11:26:54.870964Z', 'status': 'failed'},
|
{'id': 'restore-6', 'backupId': 'backup-6', 'createdAt': '2023-11-29T11:26:54.870964Z', 'status': 'failed'},
|
||||||
{'id': 'restore-7', 'backupId': 'backup-7', 'createdAt': '2023-12-07T11:26:54.870973Z', 'status': 'success'},
|
{'id': 'restore-7', 'backupId': 'backup-7', 'createdAt': '2023-12-07T11:26:54.870973Z', 'status': 'success'},
|
||||||
{'id': 'restore-8', 'backupId': 'backup-8', 'createdAt': '2023-11-18T11:26:54.870982Z', 'status': 'ongoing'},
|
{'id': 'restore-8', 'backupId': 'backup-8', 'createdAt': '2023-11-18T11:26:54.870982Z', 'status': 'processing'},
|
||||||
{'id': 'restore-9', 'backupId': 'backup-9', 'createdAt': '2023-12-01T11:26:54.870991Z', 'status': 'waiting'},
|
{'id': 'restore-9', 'backupId': 'backup-9', 'createdAt': '2023-12-01T11:26:54.870991Z', 'status': 'pending'},
|
||||||
{'id': 'restore-10', 'backupId': 'backup-10', 'createdAt': '2023-11-25T11:26:54.871000Z', 'status': 'failed'}
|
{'id': 'restore-10', 'backupId': 'backup-10', 'createdAt': '2023-11-25T11:26:54.871000Z', 'status': 'failed'}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -77,51 +88,11 @@ export default async function RoutePage(props: PageParams<{}>) {
|
|||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent className="flex flex-col gap-6" value="backup">
|
<TabsContent className="flex flex-col gap-6" value="backup">
|
||||||
<Table>
|
<DataTableWithPagination columns={columns} data={backups}/>
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>Reference</TableHead>
|
|
||||||
<TableHead>Date</TableHead>
|
|
||||||
<TableHead>Status</TableHead>
|
|
||||||
<TableHead></TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{backups.map((backup) => (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>{backup.id}</TableCell>
|
|
||||||
<TableCell>{backup.createdAt}</TableCell>
|
|
||||||
<TableCell><StatusBadge status={backup.status}/></TableCell>
|
|
||||||
<TableCell></TableCell>
|
|
||||||
</TableRow>
|
|
||||||
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent className="flex flex-col gap-6" value="restore">
|
<TabsContent className="flex flex-col gap-6" value="restore">
|
||||||
<Table>
|
<DataTableWithPagination columns={columns} data={restores}/>
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>Backup reference</TableHead>
|
|
||||||
<TableHead>Date</TableHead>
|
|
||||||
<TableHead>Status</TableHead>
|
|
||||||
<TableHead></TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{restores.map((restore) => (
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>{restore.backupId}</TableCell>
|
|
||||||
<TableCell>{restore.createdAt}</TableCell>
|
|
||||||
<TableCell><StatusBadge status={restore.status}/></TableCell>
|
|
||||||
<TableCell></TableCell>
|
|
||||||
</TableRow>
|
|
||||||
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.1.3",
|
"@radix-ui/react-tooltip": "^1.1.3",
|
||||||
"@t3-oss/env-nextjs": "^0.11.1",
|
"@t3-oss/env-nextjs": "^0.11.1",
|
||||||
"@tanstack/react-query": "^5.59.18",
|
"@tanstack/react-query": "^5.59.18",
|
||||||
|
"@tanstack/react-table": "^8.20.5",
|
||||||
"argon2": "^0.41.1",
|
"argon2": "^0.41.1",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "Status" AS ENUM ('waiting', 'ongoing', 'failed', 'success');
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Agent" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"description" TEXT,
|
||||||
|
"last_contact" TIMESTAMP(3),
|
||||||
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "Agent_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Database" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"agent_id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"description" TEXT,
|
||||||
|
"backup_policy" TEXT,
|
||||||
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "Database_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Backup" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"database_id" TEXT NOT NULL,
|
||||||
|
"status" "Status" NOT NULL DEFAULT 'waiting',
|
||||||
|
"file" TEXT,
|
||||||
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "Backup_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Restore" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"backup_id" TEXT NOT NULL,
|
||||||
|
"status" "Status" NOT NULL DEFAULT 'waiting',
|
||||||
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "Restore_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
@@ -1,27 +1,23 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, {useState} from 'react'
|
import React, {useState} from 'react'
|
||||||
import {Card} from "@/components/ui/card"
|
|
||||||
import {cn} from "@/lib/utils";
|
import {cn} from "@/lib/utils";
|
||||||
import {
|
|
||||||
Pagination, PaginationContent,
|
import {PaginationNavigation} from "@/components/wrappers/pagination/pagination-navigation";
|
||||||
PaginationEllipsis,
|
|
||||||
PaginationItem,
|
|
||||||
PaginationLink,
|
|
||||||
PaginationNext, PaginationPrevious
|
|
||||||
} from "@/components/ui/pagination";
|
|
||||||
|
|
||||||
export type cardsWithPaginationProps = {
|
export type cardsWithPaginationProps = {
|
||||||
|
className?: string
|
||||||
data: Array<{}>;
|
data: Array<{}>;
|
||||||
cardItem: React.ComponentType;
|
cardItem: React.ComponentType;
|
||||||
cardsPerPage?: number
|
cardsPerPage?: number
|
||||||
numberOfColumns?: number
|
numberOfColumns?: number
|
||||||
|
maxVisiblePages?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const CardsWithPagination = (props: cardsWithPaginationProps) => {
|
export const CardsWithPagination = (props: cardsWithPaginationProps) => {
|
||||||
|
|
||||||
const {data, cardItem, cardsPerPage = 5, numberOfColumns = 1} = props
|
const {className, data, cardItem, cardsPerPage = 5, numberOfColumns = 1, maxVisiblePages = 3} = props
|
||||||
|
|
||||||
const CardItem = cardItem
|
const CardItem = cardItem
|
||||||
|
|
||||||
@@ -32,119 +28,34 @@ export const CardsWithPagination = (props: cardsWithPaginationProps) => {
|
|||||||
const indexOfFirstCard = indexOfLastCard - cardsPerPage
|
const indexOfFirstCard = indexOfLastCard - cardsPerPage
|
||||||
const currentCards = data.slice(indexOfFirstCard, indexOfLastCard)
|
const currentCards = data.slice(indexOfFirstCard, indexOfLastCard)
|
||||||
|
|
||||||
const handlePageChange = (pageNumber: number) => {
|
const goToPage = (pageNumber: number) => {
|
||||||
setCurrentPage(pageNumber)
|
setCurrentPage(pageNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderPaginationItems = () => {
|
const goToPrevPage = () => {
|
||||||
const items = []
|
goToPage(Math.max(1, currentPage - 1))
|
||||||
const maxVisiblePages = 3
|
|
||||||
|
|
||||||
if (totalPages <= maxVisiblePages) {
|
|
||||||
for (let i = 1; i <= totalPages; i++) {
|
|
||||||
items.push(
|
|
||||||
<PaginationItem key={i}>
|
|
||||||
<PaginationLink
|
|
||||||
onClick={() => handlePageChange(i)}
|
|
||||||
isActive={currentPage === i}
|
|
||||||
>
|
|
||||||
{i}
|
|
||||||
</PaginationLink>
|
|
||||||
</PaginationItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (currentPage <= 2) {
|
|
||||||
for (let i = 1; i <= maxVisiblePages; i++) {
|
|
||||||
items.push(
|
|
||||||
<PaginationItem key={i}>
|
|
||||||
<PaginationLink
|
|
||||||
onClick={() => handlePageChange(i)}
|
|
||||||
isActive={currentPage === i}
|
|
||||||
>
|
|
||||||
{i}
|
|
||||||
</PaginationLink>
|
|
||||||
</PaginationItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
items.push(
|
|
||||||
<PaginationItem key="ellipsis1">
|
|
||||||
<PaginationEllipsis/>
|
|
||||||
</PaginationItem>
|
|
||||||
)
|
|
||||||
} else if (currentPage >= totalPages - 1) {
|
|
||||||
items.push(
|
|
||||||
<PaginationItem key="ellipsis2">
|
|
||||||
<PaginationEllipsis/>
|
|
||||||
</PaginationItem>
|
|
||||||
)
|
|
||||||
for (let i = totalPages - 2; i <= totalPages; i++) {
|
|
||||||
items.push(
|
|
||||||
<PaginationItem key={i}>
|
|
||||||
<PaginationLink
|
|
||||||
onClick={() => handlePageChange(i)}
|
|
||||||
isActive={currentPage === i}
|
|
||||||
>
|
|
||||||
{i}
|
|
||||||
</PaginationLink>
|
|
||||||
</PaginationItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
items.push(
|
|
||||||
<PaginationItem key="ellipsis3">
|
|
||||||
<PaginationEllipsis/>
|
|
||||||
</PaginationItem>
|
|
||||||
)
|
|
||||||
for (let i = currentPage - 1; i <= currentPage + 1; i++) {
|
|
||||||
items.push(
|
|
||||||
<PaginationItem key={i}>
|
|
||||||
<PaginationLink
|
|
||||||
onClick={() => handlePageChange(i)}
|
|
||||||
isActive={currentPage === i}
|
|
||||||
>
|
|
||||||
{i}
|
|
||||||
</PaginationLink>
|
|
||||||
</PaginationItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
items.push(
|
|
||||||
<PaginationItem key="ellipsis4">
|
|
||||||
<PaginationEllipsis/>
|
|
||||||
</PaginationItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return items
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const goToNextPage = () => {
|
||||||
|
goToPage(Math.min(totalPages, currentPage + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<div className={cn("", className)}>
|
||||||
<div className={cn(`grid auto-rows-min gap-4 md:grid-cols-${numberOfColumns}`)}>
|
<div className={cn(`grid auto-rows-min gap-4 md:grid-cols-${numberOfColumns}`)}>
|
||||||
{currentCards.map((card, key) => (
|
{currentCards.map((card, key) => (
|
||||||
<CardItem key={key} {...card}/>
|
<CardItem key={key} {...card}/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Pagination className="mt-8">
|
<PaginationNavigation
|
||||||
<PaginationContent>
|
className="mt-8 justify-end"
|
||||||
<PaginationItem>
|
totalPages={totalPages}
|
||||||
<PaginationPrevious
|
currentPage={currentPage}
|
||||||
onClick={() => handlePageChange(Math.max(1, currentPage - 1))}
|
goToPage={goToPage}
|
||||||
aria-disabled={currentPage === 1}
|
goToPrevPage={goToPrevPage}
|
||||||
tabIndex={currentPage === 1 ? -1 : 0}
|
goToNextPage={goToNextPage}
|
||||||
/>
|
maxVisiblePages={maxVisiblePages}/>
|
||||||
</PaginationItem>
|
|
||||||
{renderPaginationItems()}
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationNext
|
|
||||||
onClick={() => handlePageChange(Math.min(totalPages, currentPage + 1))}
|
|
||||||
aria-disabled={currentPage === totalPages}
|
|
||||||
tabIndex={currentPage === totalPages ? -1 : 0}
|
|
||||||
/>
|
|
||||||
</PaginationItem>
|
|
||||||
</PaginationContent>
|
|
||||||
</Pagination>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import {PaginationEllipsis, PaginationItem, PaginationLink} from "@/components/ui/pagination";
|
||||||
|
|
||||||
|
|
||||||
|
export type paginationItemsProps = {
|
||||||
|
totalPages: number
|
||||||
|
currentPage: number
|
||||||
|
handlePageChange: (page: number) => void
|
||||||
|
maxVisiblePages?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const PaginationIndexes = (props: paginationItemsProps) => {
|
||||||
|
|
||||||
|
const {totalPages, currentPage, handlePageChange, maxVisiblePages = 3} = props
|
||||||
|
|
||||||
|
const items = []
|
||||||
|
|
||||||
|
if (totalPages <= maxVisiblePages) {
|
||||||
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
|
items.push(
|
||||||
|
<PaginationItem key={i}>
|
||||||
|
<PaginationLink
|
||||||
|
onClick={() => handlePageChange(i)}
|
||||||
|
isActive={currentPage === i}
|
||||||
|
>
|
||||||
|
{i}
|
||||||
|
</PaginationLink>
|
||||||
|
</PaginationItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (currentPage <= 2) {
|
||||||
|
for (let i = 1; i <= maxVisiblePages; i++) {
|
||||||
|
items.push(
|
||||||
|
<PaginationItem key={i}>
|
||||||
|
<PaginationLink
|
||||||
|
onClick={() => handlePageChange(i)}
|
||||||
|
isActive={currentPage === i}
|
||||||
|
>
|
||||||
|
{i}
|
||||||
|
</PaginationLink>
|
||||||
|
</PaginationItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
items.push(
|
||||||
|
<PaginationItem key="ellipsis1">
|
||||||
|
<PaginationEllipsis/>
|
||||||
|
</PaginationItem>
|
||||||
|
)
|
||||||
|
} else if (currentPage >= totalPages - 1) {
|
||||||
|
items.push(
|
||||||
|
<PaginationItem key="ellipsis2">
|
||||||
|
<PaginationEllipsis/>
|
||||||
|
</PaginationItem>
|
||||||
|
)
|
||||||
|
for (let i = totalPages - 2; i <= totalPages; i++) {
|
||||||
|
items.push(
|
||||||
|
<PaginationItem key={i}>
|
||||||
|
<PaginationLink
|
||||||
|
onClick={() => handlePageChange(i)}
|
||||||
|
isActive={currentPage === i}
|
||||||
|
>
|
||||||
|
{i}
|
||||||
|
</PaginationLink>
|
||||||
|
</PaginationItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
items.push(
|
||||||
|
<PaginationItem key="ellipsis3">
|
||||||
|
<PaginationEllipsis/>
|
||||||
|
</PaginationItem>
|
||||||
|
)
|
||||||
|
for (let i = currentPage - 1; i <= currentPage + 1; i++) {
|
||||||
|
items.push(
|
||||||
|
<PaginationItem key={i}>
|
||||||
|
<PaginationLink
|
||||||
|
onClick={() => handlePageChange(i)}
|
||||||
|
isActive={currentPage === i}
|
||||||
|
>
|
||||||
|
{i}
|
||||||
|
</PaginationLink>
|
||||||
|
</PaginationItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
items.push(
|
||||||
|
<PaginationItem key="ellipsis4">
|
||||||
|
<PaginationEllipsis/>
|
||||||
|
</PaginationItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationNext,
|
||||||
|
PaginationPrevious
|
||||||
|
} from "@/components/ui/pagination";
|
||||||
|
import {PaginationIndexes} from "@/components/wrappers/pagination/pagination-indexes";
|
||||||
|
import {cn} from "@/lib/utils";
|
||||||
|
|
||||||
|
|
||||||
|
export type paginationNavigationProps = {
|
||||||
|
className?: string
|
||||||
|
totalPages: number
|
||||||
|
currentPage: number
|
||||||
|
goToPage: (page: number) => void
|
||||||
|
goToPrevPage: () => void
|
||||||
|
goToNextPage: () => void
|
||||||
|
maxVisiblePages?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const PaginationNavigation = (props: paginationNavigationProps) => {
|
||||||
|
|
||||||
|
const {className, totalPages, currentPage, goToPage, goToPrevPage, goToNextPage, maxVisiblePages = 3} = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pagination className={cn("w-auto m-0", className)}>
|
||||||
|
<PaginationContent>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationPrevious onClick={goToPrevPage}/>
|
||||||
|
</PaginationItem>
|
||||||
|
<PaginationIndexes totalPages={totalPages} currentPage={currentPage} handlePageChange={goToPage}
|
||||||
|
maxVisiblePages={maxVisiblePages}/>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationNext onClick={goToNextPage}/>
|
||||||
|
</PaginationItem>
|
||||||
|
</PaginationContent>
|
||||||
|
</Pagination>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,23 +1,33 @@
|
|||||||
import React from "types-react";
|
|
||||||
import {Badge} from "@/components/ui/badge";
|
import {Badge} from "@/components/ui/badge";
|
||||||
|
import {cn} from "@/lib/utils";
|
||||||
|
|
||||||
export type cardsWithPaginationProps = {
|
export type statusBadgeProps = {
|
||||||
status: "waiting" | "ongoing" | "failed" | "success";
|
status: "pending" | "processing" | "success" | "failed"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const StatusBadge = ({status}: cardsWithPaginationProps) => {
|
export const StatusBadge = ({status}: statusBadgeProps) => {
|
||||||
|
|
||||||
|
let style = "";
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'waiting':
|
case 'pending':
|
||||||
return <Badge variant="outline" className="text-yellow-500 border-2 border-yellow-500">waiting</Badge>
|
style = "text-yellow-500 border-yellow-500"
|
||||||
case 'ongoing':
|
break
|
||||||
return <Badge variant="outline" className="text-orange-500 border-2 border-orange-500">ongoing</Badge>
|
case 'processing':
|
||||||
|
style = "text-orange-500 border-orange-500"
|
||||||
|
break
|
||||||
case 'failed':
|
case 'failed':
|
||||||
return <Badge variant="outline" className="text-red-500 border-2 border-red-500">failed</Badge>
|
style = "text-red-500 border-red-500"
|
||||||
|
break
|
||||||
case 'success':
|
case 'success':
|
||||||
return <Badge variant="outline" className="text-green-500 border-2 border-green-500">success</Badge>
|
style = "text-green-500 border-green-500"
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
throw Error
|
throw Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Badge variant="outline" className={cn("w-20 border-2 justify-center")}>{status}</Badge>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import {useState} from "react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ColumnDef, getCoreRowModel, getPaginationRowModel, getSortedRowModel, SortingState, useReactTable
|
||||||
|
} from "@tanstack/react-table"
|
||||||
|
import {DataTable} from "@/components/wrappers/table/data-table";
|
||||||
|
import {TablePagination} from "@/components/wrappers/table/table-pagination";
|
||||||
|
|
||||||
|
interface DataTableProps<TData, TValue> {
|
||||||
|
columns: ColumnDef<TData, TValue>[]
|
||||||
|
data: TData[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DataTableWithPagination<TData, TValue>({columns, data}: DataTableProps<TData, TValue>) {
|
||||||
|
|
||||||
|
const [sorting, setSorting] = useState<SortingState>([])
|
||||||
|
|
||||||
|
const table = useReactTable({
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<DataTable table={table}/>
|
||||||
|
<TablePagination table={table}/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@/components/ui/table";
|
||||||
|
import {flexRender} from "@tanstack/react-table";
|
||||||
|
|
||||||
|
export type dataTableProps = {
|
||||||
|
table: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DataTable = ({table}: dataTableProps) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<TableRow key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map((header) => {
|
||||||
|
return (
|
||||||
|
<TableHead key={header.id}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext()
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{table.getRowModel().rows?.length ? (
|
||||||
|
table.getRowModel().rows.map((row) => (
|
||||||
|
<TableRow
|
||||||
|
key={row.id}
|
||||||
|
data-state={row.getIsSelected() && "selected"}
|
||||||
|
>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<TableCell key={cell.id}>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={table.columns.length} className="h-24 text-center">
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import {PaginationNavigation} from "@/components/wrappers/pagination/pagination-navigation";
|
||||||
|
|
||||||
|
|
||||||
|
export type paginationNavigationProps = {
|
||||||
|
className?: string
|
||||||
|
table: any
|
||||||
|
maxVisiblePages?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const TablePaginationNavigation = (props: paginationNavigationProps) => {
|
||||||
|
|
||||||
|
const {className, table, maxVisiblePages = 3} = props
|
||||||
|
|
||||||
|
|
||||||
|
const totalPages = table.getPageCount()
|
||||||
|
const currentPage = table.getState().pagination.pageIndex + 1
|
||||||
|
|
||||||
|
const goToPage = (page: number) => {
|
||||||
|
table.setPageIndex(page - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToPrevPage = () => {
|
||||||
|
if (table.getCanPreviousPage()) table.previousPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
const goToNextPage = () => {
|
||||||
|
if (table.getCanNextPage()) table.nextPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PaginationNavigation
|
||||||
|
className={className}
|
||||||
|
totalPages={totalPages}
|
||||||
|
currentPage={currentPage}
|
||||||
|
goToPage={goToPage}
|
||||||
|
goToPrevPage={goToPrevPage}
|
||||||
|
goToNextPage={goToNextPage}
|
||||||
|
maxVisiblePages={maxVisiblePages}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select";
|
||||||
|
import {cn} from "@/lib/utils";
|
||||||
|
|
||||||
|
export type tablePaginationSizeProps = {
|
||||||
|
className?: string
|
||||||
|
table: any
|
||||||
|
pageSizeOptions?: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const TablePaginationSize = (props: tablePaginationSizeProps) => {
|
||||||
|
|
||||||
|
const {className, table, pageSizeOptions = [10, 20, 30, 40, 50]} = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn("flex items-center space-x-2", className)}>
|
||||||
|
<p className="whitespace-nowrap text-sm font-medium">Rows per page</p>
|
||||||
|
<Select
|
||||||
|
value={`${table.getState().pagination.pageSize}`}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
table.setPageSize(Number(value))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="h-8 w-[4.5rem]">
|
||||||
|
<SelectValue placeholder={table.getState().pagination.pageSize}/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent side="top">
|
||||||
|
{pageSizeOptions.map((pageSize) => (
|
||||||
|
<SelectItem key={pageSize} value={`${pageSize}`}>
|
||||||
|
{pageSize}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import {TablePaginationNavigation} from "@/components/wrappers/table/table-pagination-navigation";
|
||||||
|
import {TablePaginationSize} from "@/components/wrappers/table/table-pagination-size";
|
||||||
|
import {cn} from "@/lib/utils";
|
||||||
|
|
||||||
|
interface tablePaginationProps {
|
||||||
|
className?: string
|
||||||
|
table: any
|
||||||
|
maxVisiblePages?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TablePagination(props: tablePaginationProps) {
|
||||||
|
|
||||||
|
const {className, table, maxVisiblePages = 3} = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn("flex justify-between mt-8", className)}>
|
||||||
|
<TablePaginationSize table={table}/>
|
||||||
|
<TablePaginationNavigation table={table} maxVisiblePages={maxVisiblePages}/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import {ColumnDef} from "@tanstack/react-table"
|
||||||
|
import {StatusBadge} from "@/components/wrappers/status-badge";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel, DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import {Button} from "@/components/ui/button";
|
||||||
|
import {MoreHorizontal} from "lucide-react";
|
||||||
|
|
||||||
|
export type Backup = {
|
||||||
|
id: string
|
||||||
|
createdAt: string
|
||||||
|
status: "pending" | "processing" | "success" | "failed"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const columns: ColumnDef<Backup>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: "id",
|
||||||
|
header: "Reference",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "createdAt",
|
||||||
|
header: "Created At",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "status",
|
||||||
|
header: "Status",
|
||||||
|
cell: ({row}) => {
|
||||||
|
return <StatusBadge status={row.getValue("status")}/>
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "actions",
|
||||||
|
cell: ({row}) => {
|
||||||
|
const payment = row.original
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||||
|
<span className="sr-only">Open menu</span>
|
||||||
|
<MoreHorizontal className="h-4 w-4"/>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => navigator.clipboard.writeText(payment.id)}
|
||||||
|
>
|
||||||
|
Copy payment ID
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator/>
|
||||||
|
<DropdownMenuItem>View customer</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>View payment details</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import {ColumnDef} from "@tanstack/react-table"
|
||||||
|
import {StatusBadge} from "@/components/wrappers/status-badge";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel, DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import {Button} from "@/components/ui/button";
|
||||||
|
import {MoreHorizontal} from "lucide-react";
|
||||||
|
|
||||||
|
export type Restore = {
|
||||||
|
id: string
|
||||||
|
createdAt: string
|
||||||
|
status: "pending" | "processing" | "success" | "failed"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const columns: ColumnDef<Restore>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: "id",
|
||||||
|
header: "Reference",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "createdAt",
|
||||||
|
header: "Created At",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "status",
|
||||||
|
header: "Status",
|
||||||
|
cell: ({row}) => {
|
||||||
|
return <StatusBadge status={row.getValue("status")}/>
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "actions",
|
||||||
|
cell: ({row}) => {
|
||||||
|
const payment = row.original
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||||
|
<span className="sr-only">Open menu</span>
|
||||||
|
<MoreHorizontal className="h-4 w-4"/>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => navigator.clipboard.writeText(payment.id)}
|
||||||
|
>
|
||||||
|
Copy payment ID
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator/>
|
||||||
|
<DropdownMenuItem>View customer</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>View payment details</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user