diff --git a/prisma/migrations/20241110103733_2024_11_10/migration.sql b/prisma/migrations/20241110103733_2024_11_10/migration.sql
new file mode 100644
index 00000000..37a8421c
--- /dev/null
+++ b/prisma/migrations/20241110103733_2024_11_10/migration.sql
@@ -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")
+);
diff --git a/src/components/wrappers/cards-with-pagination.tsx b/src/components/wrappers/cards-with-pagination.tsx
index d8a6ffa8..e3c2e892 100644
--- a/src/components/wrappers/cards-with-pagination.tsx
+++ b/src/components/wrappers/cards-with-pagination.tsx
@@ -1,27 +1,23 @@
'use client'
import React, {useState} from 'react'
-import {Card} from "@/components/ui/card"
import {cn} from "@/lib/utils";
-import {
- Pagination, PaginationContent,
- PaginationEllipsis,
- PaginationItem,
- PaginationLink,
- PaginationNext, PaginationPrevious
-} from "@/components/ui/pagination";
+
+import {PaginationNavigation} from "@/components/wrappers/pagination/pagination-navigation";
export type cardsWithPaginationProps = {
+ className?: string
data: Array<{}>;
cardItem: React.ComponentType;
cardsPerPage?: number
numberOfColumns?: number
+ maxVisiblePages?: number
}
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
@@ -32,119 +28,34 @@ export const CardsWithPagination = (props: cardsWithPaginationProps) => {
const indexOfFirstCard = indexOfLastCard - cardsPerPage
const currentCards = data.slice(indexOfFirstCard, indexOfLastCard)
- const handlePageChange = (pageNumber: number) => {
+ const goToPage = (pageNumber: number) => {
setCurrentPage(pageNumber)
}
- const renderPaginationItems = () => {
- const items = []
- const maxVisiblePages = 3
-
- if (totalPages <= maxVisiblePages) {
- for (let i = 1; i <= totalPages; i++) {
- items.push(
-
- handlePageChange(i)}
- isActive={currentPage === i}
- >
- {i}
-
-
- )
- }
- } else {
- if (currentPage <= 2) {
- for (let i = 1; i <= maxVisiblePages; i++) {
- items.push(
-
- handlePageChange(i)}
- isActive={currentPage === i}
- >
- {i}
-
-
- )
- }
- items.push(
-
-
-
- )
- } else if (currentPage >= totalPages - 1) {
- items.push(
-
-
-
- )
- for (let i = totalPages - 2; i <= totalPages; i++) {
- items.push(
-
- handlePageChange(i)}
- isActive={currentPage === i}
- >
- {i}
-
-
- )
- }
- } else {
- items.push(
-
-
-
- )
- for (let i = currentPage - 1; i <= currentPage + 1; i++) {
- items.push(
-
- handlePageChange(i)}
- isActive={currentPage === i}
- >
- {i}
-
-
- )
- }
- items.push(
-
-
-
- )
- }
- }
-
- return items
+ const goToPrevPage = () => {
+ goToPage(Math.max(1, currentPage - 1))
}
+ const goToNextPage = () => {
+ goToPage(Math.min(totalPages, currentPage + 1))
+ }
+
+
return (
-
+
{currentCards.map((card, key) => (
))}
-
-
-
- handlePageChange(Math.max(1, currentPage - 1))}
- aria-disabled={currentPage === 1}
- tabIndex={currentPage === 1 ? -1 : 0}
- />
-
- {renderPaginationItems()}
-
- handlePageChange(Math.min(totalPages, currentPage + 1))}
- aria-disabled={currentPage === totalPages}
- tabIndex={currentPage === totalPages ? -1 : 0}
- />
-
-
-
+
)
}
\ No newline at end of file
diff --git a/src/components/wrappers/pagination/pagination-indexes.tsx b/src/components/wrappers/pagination/pagination-indexes.tsx
new file mode 100644
index 00000000..0f9e3c55
--- /dev/null
+++ b/src/components/wrappers/pagination/pagination-indexes.tsx
@@ -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(
+
+ handlePageChange(i)}
+ isActive={currentPage === i}
+ >
+ {i}
+
+
+ )
+ }
+ } else {
+ if (currentPage <= 2) {
+ for (let i = 1; i <= maxVisiblePages; i++) {
+ items.push(
+
+ handlePageChange(i)}
+ isActive={currentPage === i}
+ >
+ {i}
+
+
+ )
+ }
+ items.push(
+
+
+
+ )
+ } else if (currentPage >= totalPages - 1) {
+ items.push(
+
+
+
+ )
+ for (let i = totalPages - 2; i <= totalPages; i++) {
+ items.push(
+
+ handlePageChange(i)}
+ isActive={currentPage === i}
+ >
+ {i}
+
+
+ )
+ }
+ } else {
+ items.push(
+
+
+
+ )
+ for (let i = currentPage - 1; i <= currentPage + 1; i++) {
+ items.push(
+
+ handlePageChange(i)}
+ isActive={currentPage === i}
+ >
+ {i}
+
+
+ )
+ }
+ items.push(
+
+
+
+ )
+ }
+ }
+
+ return items
+}
\ No newline at end of file
diff --git a/src/components/wrappers/pagination/pagination-navigation.tsx b/src/components/wrappers/pagination/pagination-navigation.tsx
new file mode 100644
index 00000000..69b9249c
--- /dev/null
+++ b/src/components/wrappers/pagination/pagination-navigation.tsx
@@ -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 (
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/wrappers/status-badge.tsx b/src/components/wrappers/status-badge.tsx
index 58d62627..e68a004f 100644
--- a/src/components/wrappers/status-badge.tsx
+++ b/src/components/wrappers/status-badge.tsx
@@ -1,22 +1,33 @@
import {Badge} from "@/components/ui/badge";
+import {cn} from "@/lib/utils";
-export type cardsWithPaginationProps = {
- status: "waiting" | "ongoing" | "failed" | "success";
+export type statusBadgeProps = {
+ status: "pending" | "processing" | "success" | "failed"
}
-export const StatusBadge = ({status}: cardsWithPaginationProps) => {
+export const StatusBadge = ({status}: statusBadgeProps) => {
+
+ let style = "";
switch (status) {
- case 'waiting':
- return
waiting
- case 'ongoing':
- return
ongoing
+ case 'pending':
+ style = "text-yellow-500 border-yellow-500"
+ break
+ case 'processing':
+ style = "text-orange-500 border-orange-500"
+ break
case 'failed':
- return
failed
+ style = "text-red-500 border-red-500"
+ break
case 'success':
- return
success
+ style = "text-green-500 border-green-500"
+ break
default:
throw Error
}
+
+ return (
+
{status}
+ )
}
\ No newline at end of file
diff --git a/src/components/wrappers/table/data-table-with-pagination.tsx b/src/components/wrappers/table/data-table-with-pagination.tsx
new file mode 100644
index 00000000..b3f39fea
--- /dev/null
+++ b/src/components/wrappers/table/data-table-with-pagination.tsx
@@ -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
{
+ columns: ColumnDef[]
+ data: TData[]
+}
+
+export function DataTableWithPagination({columns, data}: DataTableProps) {
+
+ const [sorting, setSorting] = useState([])
+
+ const table = useReactTable({
+ data,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ onSortingChange: setSorting,
+ getSortedRowModel: getSortedRowModel(),
+ state: {
+ sorting,
+ },
+ })
+
+ return (
+
+ )
+}
diff --git a/src/components/wrappers/table/data-table.tsx b/src/components/wrappers/table/data-table.tsx
new file mode 100644
index 00000000..62662140
--- /dev/null
+++ b/src/components/wrappers/table/data-table.tsx
@@ -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.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => {
+ return (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+
+ )
+ })}
+
+ ))}
+
+
+ {table.getRowModel().rows?.length ? (
+ table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+
+ ))}
+
+ ))
+ ) : (
+
+
+ No results.
+
+
+ )}
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/wrappers/table/table-pagination-navigation.tsx b/src/components/wrappers/table/table-pagination-navigation.tsx
new file mode 100644
index 00000000..17de947d
--- /dev/null
+++ b/src/components/wrappers/table/table-pagination-navigation.tsx
@@ -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 (
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/wrappers/table/table-pagination-size.tsx b/src/components/wrappers/table/table-pagination-size.tsx
new file mode 100644
index 00000000..071ede60
--- /dev/null
+++ b/src/components/wrappers/table/table-pagination-size.tsx
@@ -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 (
+
+
Rows per page
+
+
+ )
+
+}
\ No newline at end of file
diff --git a/src/components/wrappers/table/table-pagination.tsx b/src/components/wrappers/table/table-pagination.tsx
new file mode 100644
index 00000000..d4b9abf9
--- /dev/null
+++ b/src/components/wrappers/table/table-pagination.tsx
@@ -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 (
+
+ )
+}
diff --git a/src/features/backup/columns.tsx b/src/features/backup/columns.tsx
new file mode 100644
index 00000000..796902f6
--- /dev/null
+++ b/src/features/backup/columns.tsx
@@ -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[] = [
+ {
+ accessorKey: "id",
+ header: "Reference",
+ },
+ {
+ accessorKey: "createdAt",
+ header: "Created At",
+ },
+ {
+ accessorKey: "status",
+ header: "Status",
+ cell: ({row}) => {
+ return
+ },
+ },
+ {
+ id: "actions",
+ cell: ({row}) => {
+ const payment = row.original
+
+ return (
+
+
+
+
+
+ Actions
+ navigator.clipboard.writeText(payment.id)}
+ >
+ Copy payment ID
+
+
+ View customer
+ View payment details
+
+
+ )
+ },
+ },
+]
\ No newline at end of file
diff --git a/src/features/restore/columns.tsx b/src/features/restore/columns.tsx
new file mode 100644
index 00000000..3d923844
--- /dev/null
+++ b/src/features/restore/columns.tsx
@@ -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[] = [
+ {
+ accessorKey: "id",
+ header: "Reference",
+ },
+ {
+ accessorKey: "createdAt",
+ header: "Created At",
+ },
+ {
+ accessorKey: "status",
+ header: "Status",
+ cell: ({row}) => {
+ return
+ },
+ },
+ {
+ id: "actions",
+ cell: ({row}) => {
+ const payment = row.original
+
+ return (
+
+
+
+
+
+ Actions
+ navigator.clipboard.writeText(payment.id)}
+ >
+ Copy payment ID
+
+
+ View customer
+ View payment details
+
+
+ )
+ },
+ },
+]
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 2eda0a4c..c94a8afa 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1123,6 +1123,18 @@
dependencies:
"@tanstack/query-core" "5.59.20"
+"@tanstack/react-table@^8.20.5":
+ version "8.20.5"
+ resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.20.5.tgz#19987d101e1ea25ef5406dce4352cab3932449d8"
+ integrity sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==
+ dependencies:
+ "@tanstack/table-core" "8.20.5"
+
+"@tanstack/table-core@8.20.5":
+ version "8.20.5"
+ resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.20.5.tgz#3974f0b090bed11243d4107283824167a395cf1d"
+ integrity sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==
+
"@types/cookie@0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5"
@@ -1595,9 +1607,9 @@ camelcase-css@^2.0.1:
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
caniuse-lite@^1.0.30001579:
- version "1.0.30001680"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz#5380ede637a33b9f9f1fc6045ea99bd142f3da5e"
- integrity sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==
+ version "1.0.30001679"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001679.tgz#18c573b72f72ba70822194f6c39e7888597f9e32"
+ integrity sha512-j2YqID/YwpLnKzCmBOS4tlZdWprXm3ZmQLBH9ZBXFOhoxLA46fwyBvx6toCBWBmnuwUY/qB3kEU6gFx8qgCroA==
chalk@^4.0.0:
version "4.1.2"
@@ -3377,7 +3389,7 @@ path-scurry@^1.11.1:
lru-cache "^10.2.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
-picocolors@^1.0.0, picocolors@^1.1.1:
+picocolors@^1.0.0, picocolors@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
@@ -3456,12 +3468,12 @@ postcss@8.4.31:
source-map-js "^1.0.2"
postcss@^8, postcss@^8.4.23:
- version "8.4.48"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.48.tgz#765f3f8abaa2a2b065cdddbc57ad4cb5a76e515f"
- integrity sha512-GCRK8F6+Dl7xYniR5a4FYbpBzU8XnZVeowqsQFYdcXuSbChgiks7qybSkbvnaeqv0G0B+dd9/jJgH8kkLDQeEA==
+ version "8.4.47"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365"
+ integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==
dependencies:
nanoid "^3.3.7"
- picocolors "^1.1.1"
+ picocolors "^1.1.0"
source-map-js "^1.2.1"
preact-render-to-string@5.2.3: