diff --git a/app/client/app/components/sections/dataTable.tsx b/app/client/app/components/sections/dataTable.tsx new file mode 100644 index 00000000..77e7b520 --- /dev/null +++ b/app/client/app/components/sections/dataTable.tsx @@ -0,0 +1,381 @@ +"use client" + +import type React from "react" +import { useState, useRef, useEffect } from "react" +import { MoreVertical, ChevronDown } from "lucide-react" +import { Pagination } from "../ui/pagination" + +export type Column = { + key: keyof T + header: string + width?: string + render?: (value: T[keyof T], item: T) => React.ReactNode + align?: "left" | "center" | "right" +} + +type DataTableProps = { + statusFilter?: boolean +} + +export function StatusBadge({ status }: { status: string }) { + const getStatusStyles = () => { + switch (status) { + case "Verified User": + case "Verified Land": + return "bg-green-100 text-green-600" + case "Registration": + return "bg-gray-100 text-gray-600" + case "Transfer": + return "bg-blue-100 text-blue-600" + case "Flagged": + return "bg-red-100 text-red-600" + default: + return "bg-gray-100 text-gray-600" + } + } + + return {status} +} + +export default function DataTable({ statusFilter = true }: DataTableProps) { + const [currentFilter, setCurrentFilter] = useState("Status") + const [showStatusDropdown, setShowStatusDropdown] = useState(false) + const [rowsPerPage, setRowsPerPage] = useState("5") + const [showRowsDropdown, setShowRowsDropdown] = useState(false) + const [showActionMenu, setShowActionMenu] = useState(null) + const [currentPage, setCurrentPage] = useState(1) + + const actionMenuRef = useRef(null) + + // Close menu when clicking outside + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (actionMenuRef.current && !actionMenuRef.current.contains(event.target as Node)) { + setShowActionMenu(null) + } + } + + document.addEventListener("mousedown", handleClickOutside) + return () => { + document.removeEventListener("mousedown", handleClickOutside) + } + }, []) + + const statusOptions = ["Status", "Verified", "Registration", "Transfer", "Flagged"] + const rowsOptions = ["5", "10", "20", "50"] + + const columns: Column[] = [ + { key: "id", header: "NO", width: "w-[60px]" }, + { key: "landId", header: "LAND ID" }, + { + key: "owner", + header: "OWNERS NAME", + render: (value) => ( +
+
+ +
+ {value} +
+ ), + }, + { + key: "price", + header: "PRICE", + render: (value) => ( +
+
+ +
+ {value} +
+ ), + }, + { key: "date", header: "DATE" }, + { + key: "status", + header: "STATUS", + render: (value) => , + }, + ] + + const actionMenu = { + label: "Approved/Bought", + items: ["View", "Approve", "Reject", "Flag"], + primaryItem: "View", + } + + const data = [ + { + id: 1, + landId: "56037-XDER", + owner: "Xeusthegreat", + price: "0.2345", + date: "20/11/24", + status: "Verified User", + }, + { + id: 2, + landId: "56037-XDER", + owner: "Xeusthegreat", + price: "0.2345", + date: "20/11/24", + status: "Registration", + }, + { + id: 3, + landId: "56037-XDER", + owner: "Xeusthegreat", + price: "0.2345", + date: "20/11/24", + status: "Transfer", + }, + { + id: 4, + landId: "56037-XDER", + owner: "Xeusthegreat", + price: "0.2345", + date: "20/11/24", + status: "Flagged", + }, + { + id: 5, + landId: "56037-XDER", + owner: "Xeusthegreat", + price: "0.2345", + date: "20/11/24", + status: "Verified Land", + }, + ] + + const toggleActionMenu = (id: number) => { + setShowActionMenu(showActionMenu === id ? null : id) + } + + return ( +
+ {statusFilter && ( +
+
+
+
+ + + + +
+ +
+
+ +
+
setShowStatusDropdown(!showStatusDropdown)} + > + {currentFilter} + +
+ {showStatusDropdown && ( +
+ {statusOptions.map((option) => ( +
{ + setCurrentFilter(option) + setShowStatusDropdown(false) + }} + > + {option} +
+ ))} +
+ )} +
+
+ )} + + {/* Mobile Card View */} +
+ {data.map((item) => ( +
+ {columns.map((column) => ( +
+ {column.header} + + {column.render + ? column.render(item[column.key], item) + : item[column.key]} + +
+ ))} + {actionMenu && ( +
+ + {showActionMenu === item.id && ( +
+
+ {actionMenu.label} +
+ {actionMenu.items.map((menuItem, i) => ( +
setShowActionMenu(null)} + > + {menuItem} +
+ ))} +
+ )} +
+ )} +
+ ))} +
+ + {/* Desktop Table View */} +
+ + + + {columns.map((column) => ( + + ))} + {actionMenu && ( + + )} + + + + {data.map((item, index) => ( + + {columns.map((column) => ( + + ))} + {actionMenu && ( + + )} + + ))} + +
+ {column.header} + + ACTIONS +
+ {column.render + ? column.render(item[column.key], item) + : item[column.key]} + +
+ + + {showActionMenu === index && ( +
+
+ {actionMenu.label} +
+ {actionMenu.items.map((item, i) => ( +
setShowActionMenu(null)} + > + {item} +
+ ))} +
+ )} +
+
+
+ +
+
+
+
setShowRowsDropdown(!showRowsDropdown)} + > + {rowsPerPage} + +
+ {showRowsDropdown && ( +
+ {rowsOptions.map((option) => ( +
{ + setRowsPerPage(option) + setShowRowsDropdown(false) + }} + > + {option} +
+ ))} +
+ )} +
+
+ +
+ +
+
+
+ ) +} \ No newline at end of file diff --git a/app/client/app/components/ui/pagination.tsx b/app/client/app/components/ui/pagination.tsx new file mode 100644 index 00000000..5d1d7ec5 --- /dev/null +++ b/app/client/app/components/ui/pagination.tsx @@ -0,0 +1,63 @@ +"use client" + +import { ChevronLeft, ChevronRight } from "lucide-react" + +interface PaginationProps { + currentPage: number + totalPages: number + onPageChange: (page: number) => void +} + +export function Pagination({ currentPage = 1, totalPages = 4, onPageChange }: PaginationProps) { + const handlePrevious = () => { + if (currentPage > 1) { + onPageChange(currentPage - 1) + } + } + + const handleNext = () => { + if (currentPage < totalPages) { + onPageChange(currentPage + 1) + } + } + + return ( +
+ + + {Array.from({ length: totalPages }).map((_, index) => { + const pageNumber = index + 1 + const isActive = pageNumber === currentPage + + return ( + + ) + })} + + +
+ ) +} diff --git a/app/client/app/dashboard/inspector/page.tsx b/app/client/app/dashboard/inspector/page.tsx index 73cee813..ef3280b6 100644 --- a/app/client/app/dashboard/inspector/page.tsx +++ b/app/client/app/dashboard/inspector/page.tsx @@ -1,9 +1,34 @@ -import React from 'react' +"use client" -const page = () => { +import DataTable from "../../components/sections/dataTable" + +export default function Dashboard() { return ( -
Inspector Dashboard
+
+

Overview

+
+ + + + +
+
+ +
+
) } -export default page \ No newline at end of file +function StatCard({ count, label, iconSrc }: { count: number; label: string; iconSrc: string }) { + return ( +
+
+ {label} +
+
+

{count}

+

{label}

+
+
+ ); +} \ No newline at end of file diff --git a/app/client/app/dashboard/owner/components/bestSeller.tsx b/app/client/app/dashboard/owner/components/bestSeller.tsx new file mode 100644 index 00000000..e55ed95a --- /dev/null +++ b/app/client/app/dashboard/owner/components/bestSeller.tsx @@ -0,0 +1,120 @@ +'use client' +import React from 'react' +import { ChevronRight } from 'lucide-react' + +// Types +export type BestSellerItem = { + id: number + landId: string + buyer: string + price: number + date: string +} + +// Shared "View all" button with chevron +export const ViewAllButton = () => { + return ( + + ) +} + +// Reusable Table Component +export const Table = ({ + headers, + rows, + title, + actionButton +}: { + headers: { key: string; label: string }[] + rows: Record[] + title?: string + actionButton?: React.ReactNode +}) => { + return ( +
+ {title && ( +
+

{title}

+ {actionButton} +
+ )} + +
+ + + + {headers.map((header) => ( + + ))} + + + + {rows.map((row, rowIndex) => ( + + {headers.map((header) => ( + + ))} + + ))} + +
+ {header.label} +
+ {row[header.key]} +
+
+
+ ) +} + +// Best Seller Table Component using the reusable Table +export const BestSellerTable = ({ items }: { items: BestSellerItem[] }) => { + const headers = [ + { key: 'id', label: 'NO' }, + { key: 'landId', label: 'LAND ID' }, + { key: 'buyer', label: 'BUYER/ LAND NAME' }, + { key: 'price', label: 'PRICE' }, + { key: 'date', label: 'DATE' } + ] + + const rows = items.map(item => ({ + ...item, + buyer: ( +
+
+ +
+ {item.buyer} +
+ ), + price: ( +
+
+ +
+ {item.price} +
+ ) + })) + + return ( + } + /> + ) +} + +export const generateBestSellerData = (): BestSellerItem[] => { + return [ + { id: 1, landId: '56037-XDER', buyer: 'Tress1234', price: 0.2345, date: '20/11/24' }, + { id: 2, landId: '56037-XDER', buyer: 'Tress1234', price: 0.2345, date: '20/11/24' }, + { id: 3, landId: '56037-XDER', buyer: 'Tress1234', price: 0.2345, date: '20/11/24' }, + { id: 4, landId: '56037-XDER', buyer: 'Tress1234', price: 0.2345, date: '20/11/24' }, + ] +} \ No newline at end of file diff --git a/app/client/app/dashboard/owner/components/recentActivity.tsx b/app/client/app/dashboard/owner/components/recentActivity.tsx new file mode 100644 index 00000000..0dbdb80d --- /dev/null +++ b/app/client/app/dashboard/owner/components/recentActivity.tsx @@ -0,0 +1,88 @@ +'use client' +import React from 'react' +import { ChevronRight } from 'lucide-react' + + +export type RecentActivity = { + id: number + type: 'approval' | 'received' | 'purchased' + name: string + description: string + status?: 'Approved' | 'Rejected' + amount?: string + date?: string +} + +// Shared "View all" button with chevron +export const ViewAllButton = () => { + return ( + + ) +} + +// Recent Activities Component +export const RecentActivities = ({ activities }: { activities: RecentActivity[] }) => { + return ( +
+
+

Recent Activities

+ +
+ +
+ {activities.map((activity) => ( +
+
+ recent activity profile +
+ +
+
{activity.name}
+
{activity.description}
+
+ + {activity.status && ( +
+ {activity.status} +
+ )} +
+ ))} +
+
+ ) +} + +export const generateRecentActivitiesData = (): RecentActivity[] => { + return [ + { + id: 1, + type: 'approval', + name: 'Land name or id', + description: 'Land Approval', + status: 'Approved' + }, + { + id: 2, + type: 'received', + name: 'ETH Received', + description: '0.025 ETH received by you' + }, + { + id: 3, + type: 'approval', + name: 'Land name or id', + description: 'Land Approval', + status: 'Rejected' + }, + { + id: 4, + type: 'purchased', + name: 'Name of land', + description: 'Purchased by you for 0.3 ETH' + } + ] +} \ No newline at end of file diff --git a/app/client/app/dashboard/owner/page.tsx b/app/client/app/dashboard/owner/page.tsx index 5e27ad77..4fdb6763 100644 --- a/app/client/app/dashboard/owner/page.tsx +++ b/app/client/app/dashboard/owner/page.tsx @@ -4,10 +4,14 @@ import RegisterLandForm from "@/app/components/sections/registerLandForm"; import Overview from "./components/overview"; import Button from "@/app/components/ui/button"; import { X } from "lucide-react"; +import { RecentActivities, generateRecentActivitiesData} from "./components/recentActivity"; +import { BestSellerTable, generateBestSellerData} from "./components/bestSeller"; -//import Overview from '@/app/components/dashboard/Overview'; const OwnerDashboard = () => { + const recentActivities = generateRecentActivitiesData(); + const bestSellerData = generateBestSellerData() + const [isRegisterModalOpen, setIsRegisterModalOpen] = React.useState(false); async function handleRegisterButtonCLick() { @@ -17,7 +21,7 @@ const OwnerDashboard = () => { if (isRegisterModalOpen) return ( -
+
+
+
+ +
+
+ +
+
); diff --git a/app/client/public/card.svg b/app/client/public/card.svg new file mode 100644 index 00000000..d6614fe5 --- /dev/null +++ b/app/client/public/card.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/client/public/ethIcon.svg b/app/client/public/ethIcon.svg new file mode 100644 index 00000000..ae924925 --- /dev/null +++ b/app/client/public/ethIcon.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/app/client/public/pro.svg b/app/client/public/pro.svg new file mode 100644 index 00000000..fea60d0b --- /dev/null +++ b/app/client/public/pro.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/client/public/recentPro.svg b/app/client/public/recentPro.svg new file mode 100644 index 00000000..62248fb9 --- /dev/null +++ b/app/client/public/recentPro.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/package.json b/package.json index f26c8e69..81d60d44 100644 --- a/package.json +++ b/package.json @@ -24,5 +24,6 @@ }, "lint-staged": { "**/*.{js,ts,tsx}": "pnpm exec eslint --fix" - } + }, + "packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6" }