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: