diff --git a/src/App.tsx b/src/App.tsx index 4404ac7..20c2c89 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,19 +17,16 @@ import AdminPage from './pages/admin'; import Category from './pages/admin/Category'; import Sellers from './pages/admin/Sellers'; import Buyers from './pages/admin/Buyers'; -import Messages from './pages/admin/Messages'; import UserManagement from './pages/admin/UserManagement'; import NotFoundPage from './pages/NotFoundPage'; -import Settings from './pages/admin/Settings'; +import SellerSettings from './pages/seller/Settings'; import SellersPage from './pages/seller'; import Orders from './pages/seller/Orders'; import Products from './pages/seller/Products'; import Customers from './pages/seller/Customers'; -import SellerMessages from './pages/seller/Messages'; -import SellerSettings from './pages/seller/Settings'; +import AdminSettings from './pages/admin/Settings'; import AddNewProduct from './pages/seller/AddNewProduct'; import RestrictedSellerRoute from './components/dashboard/RestrictedSellerLayout'; - import CategoriesPage from './pages/CategoriesPage'; import ResetPassword from './pages/ResetPassword'; import NewPassword from './pages/NewPassword'; @@ -140,12 +137,12 @@ const App = () => { element: , }, { - path: 'messages', - element: , + path: 'users', + element: , }, { path: 'settings', - element: , + element: , }, ], }, @@ -173,17 +170,9 @@ const App = () => { path: 'customers', element: , }, - { - path: 'messages', - element: , - }, - { - path: 'users', - element: , - }, { path: 'settings', - element: , + element: , }, ], }, diff --git a/src/components/OrderTables.tsx b/src/components/OrderTables.tsx new file mode 100644 index 0000000..74d4b9d --- /dev/null +++ b/src/components/OrderTables.tsx @@ -0,0 +1,162 @@ +import React, { useState } from 'react'; +import { Button } from './ui/button'; +import { useGetOrdersQuery } from "../services/ordersApi"; +import { Order } from "../types/Types"; +import { CiSearch } from 'react-icons/ci'; + +const statusColors: { [key: string]: string } = { + 'delivered': 'bg-[#d1fae5] text-[#065f46]', + 'cancelled': 'bg-[#fee2e2] text-[#991b1b]', + 'pending': 'bg-[#e9d5ff] text-[#6b21a8]', + 'paid': 'bg-[#fef3c7] text-[#854d0e]', +}; + +const itemsPerPage = 15; + +const TransactionTable: React.FC = () => { + const [selectedStatus, setSelectedStatus] = useState('all'); + const [currentPage, setCurrentPage] = useState(1); + const [searchQuery, setSearchQuery] = useState(''); + const { data: ordersData, isLoading, isError } = useGetOrdersQuery(); + const orders = ordersData?.data || []; + + const handleStatusChange = (status: string) => { + setSelectedStatus(status); + setCurrentPage(1); + }; + + const handleSearchChange = (event: React.ChangeEvent) => { + setSearchQuery(event.target.value); + setCurrentPage(1); + }; + + const filteredTransactions = selectedStatus === 'all' + ? orders + : orders.filter((transaction: Order) => transaction.status.toLowerCase() === selectedStatus); + + const searchedTransactions = searchQuery + ? filteredTransactions.filter((transaction: Order) => + transaction.orderItems.some((item) => + item.products.name.toLowerCase().includes(searchQuery.toLowerCase()) + ) + ) + : filteredTransactions; + + const totalPages = Math.ceil(searchedTransactions.length / itemsPerPage); + + const currentTransactions = searchedTransactions.slice( + (currentPage - 1) * itemsPerPage, + currentPage * itemsPerPage + ); + + const handlePageChange = (page: number) => { + if (page >= 1 && page <= totalPages) { + setCurrentPage(page); + } + }; + + if (isLoading) { + return
Loading...
; + } + + if (isError) { + return
Error loading orders.
; + } + + return ( +
+
+
+
+

Orders Details

+

Manage orders

+
+
+ + +
+
+ +
+ + + + + + + + + + + + + {currentTransactions.map((transaction: Order) => ( + + {transaction.orderItems.map((orderItem, itemIndex) => ( + + + + + + + + + ))} + + ))} + +
Order IDCUSTOMERPRODUCTDATESTATUSPRICE
#{(transaction.id).slice(0, 3)}{transaction.user?.firstName} {transaction.user?.lastName}{orderItem.products.name} + {new Date(transaction.createdAt).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: '2-digit', + })} + + + {transaction.status} + + {orderItem.price.toFixed(1)}
+
+
+
{`${searchedTransactions.length} out of ${orders.length} orders`}
+
+ + Page {currentPage} of {totalPages} + +
+
+
+
+ ); +}; + +export default TransactionTable; \ No newline at end of file diff --git a/src/components/admin/AdminLayout.tsx b/src/components/admin/AdminLayout.tsx index 9eaa4c2..4df446d 100644 --- a/src/components/admin/AdminLayout.tsx +++ b/src/components/admin/AdminLayout.tsx @@ -1,8 +1,8 @@ import { Outlet } from 'react-router-dom'; import Sidebar from '../dashboard/Sidebar'; import { RxDashboard } from 'react-icons/rx'; -import { FaCog, FaRegListAlt, FaUserFriends } from 'react-icons/fa'; -import { FaRegEnvelope, FaUserTie } from 'react-icons/fa6'; +import { FaCog, FaRegListAlt, FaUserFriends, FaUsers } from 'react-icons/fa'; +import { FaUserTie } from 'react-icons/fa6'; export default function AdminLayout() { const adminSidebarLinks = [ @@ -10,7 +10,7 @@ export default function AdminLayout() { { name: 'Categories', path: 'categories', icon: }, { name: 'Sellers', path: 'sellers', icon: }, { name: 'Buyers', path: 'buyers', icon: }, - { name: 'Messages', path: 'messages', icon: }, + { name: 'Users', path: 'users', icon: }, { name: 'Settings', path: 'settings', icon: }, ]; diff --git a/src/components/chat/Chat.tsx b/src/components/chat/Chat.tsx index b972b1c..1e4dd9d 100644 --- a/src/components/chat/Chat.tsx +++ b/src/components/chat/Chat.tsx @@ -81,10 +81,10 @@ const Chat: React.FC = () => { {showChat && ( -
-
-
-
+
+
+
+
Mavericks React.ReactNode; + sortable: boolean; +} + +interface TableProps { + data: any[]; + columns: Column[]; + itemsPerPage: number; +} + +const getInitials = (firstName: string, lastName: string) => + `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase(); + +const getRandomColor = () => { + const letters = '0123456789ABCDEF'; + let color = '#'; + for (let i = 0; i < 6; i++) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; +}; + +const Table: React.FC = ({ data, columns, itemsPerPage }) => { + const [currentPage, setCurrentPage] = useState(1); + const [sortColumn, setSortColumn] = useState(null); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); + const [searchTerm, setSearchTerm] = useState(''); + + const filteredData = useMemo( + () => + data.filter(item => + columns.some(column => String(item[column.key]).toLowerCase().includes(searchTerm.toLowerCase())) + ), + [data, columns, searchTerm] + ); + + const sortedData = useMemo(() => { + if (!sortColumn) return filteredData; + return [...filteredData].sort((a, b) => { + if (a[sortColumn] < b[sortColumn]) return sortDirection === 'asc' ? -1 : 1; + if (a[sortColumn] > b[sortColumn]) return sortDirection === 'asc' ? 1 : -1; + return 0; + }); + }, [filteredData, sortColumn, sortDirection]); + + const paginatedData = useMemo(() => { + const startIndex = (currentPage - 1) * itemsPerPage; + return sortedData.slice(startIndex, startIndex + itemsPerPage); + }, [sortedData, currentPage, itemsPerPage]); + + const totalPages = Math.ceil(sortedData.length / itemsPerPage); + + const handleSort = (column: string) => { + if (column === sortColumn) { + setSortDirection(prev => (prev === 'asc' ? 'desc' : 'asc')); + } else { + setSortColumn(column); + setSortDirection('asc'); + } + }; + + const handleSearch = (e: React.ChangeEvent) => { + setSearchTerm(e.target.value); + setCurrentPage(1); + }; + + const renderCell = (item: any, column: Column) => { + if (column.isImage) { + return item[column.key] ? ( + {`${item.firstName} + ) : ( +
+ {getInitials(item.firstName, item.lastName)} +
+ ); + } + if (column.render) return column.render(item); + return column.key.includes('.') + ? column.key.split('.').reduce((obj, key) => obj && obj[key], item) + : item[column.key]; + }; + + return ( +
+
+

Buyers Page

+ +
+
+ + + + {columns.map(column => ( + + ))} + + + + {paginatedData.map((item, index) => ( + + {columns.map(column => ( + + ))} + + ))} + +
column.sortable && handleSort(column.key)} + className={`px-4 py-4 text-sm font-bold text-whiteColor uppercase tracking-wider ${ + column.sortable ? 'cursor-pointer' : '' + } ${column.isImage ? 'text-center' : 'text-left'}`} + > + {column.label} + {column.sortable && sortColumn === column.key && {sortDirection === 'asc' ? ' ▲' : ' ▼'}} +
+ {renderCell(item, column)} +
+
+
+
+ Showing {(currentPage - 1) * itemsPerPage + 1} to {Math.min(currentPage * itemsPerPage, sortedData.length)} of{' '} + {sortedData.length} buyers +
+
+ + +
+
+
+ ); +}; + +export default Table; diff --git a/src/components/dashboard/UsersTable.tsx b/src/components/dashboard/UsersTable.tsx index da4cd38..d79612f 100644 --- a/src/components/dashboard/UsersTable.tsx +++ b/src/components/dashboard/UsersTable.tsx @@ -94,7 +94,8 @@ const Table: React.FC = ({ data, columns, itemsPerPage }) => { return (
-
+
+

Users Management

= ({ data, columns, itemsPerPage }) => { return (
-
+
+

Sellers Management

= ({ data, columns, itemsPerPage }) => { />
- + {columns.map(column => (
!column.isImage && handleSort(column.key)} - className={`pl-4 py-4 text-left text-sm font-bold text-[#6B7280] uppercase tracking-wider ${!column.isImage ? 'cursor-pointer' : ''}`} + className={`pl-4 py-4 text-left text-sm font-bold text-whiteColor uppercase tracking-wider ${!column.isImage ? 'cursor-pointer' : ''}`} > {column.label} {!column.isImage && sortColumn === column.key && {sortDirection === 'asc' ? ' ▲' : ' ▼'}} diff --git a/src/components/footer/Footer.tsx b/src/components/footer/Footer.tsx index b156c9f..a7d989d 100644 --- a/src/components/footer/Footer.tsx +++ b/src/components/footer/Footer.tsx @@ -13,7 +13,7 @@ function Footer() {

K309 St , Makuza plaza, Nyarugenge , Kigali, Rwanda

andela.mavericks@gmail.com

-

+250 788888888

+

+250 788 888 888

- Email is not valid + {/* Email is not valid */}

- © 2024 Mavericks Shop. All rights reserved. + © {new Date().getFullYear()} Mavericks Shop. All rights reserved.

diff --git a/src/components/navbar/Navbar.tsx b/src/components/navbar/Navbar.tsx index c006e33..3636db0 100644 --- a/src/components/navbar/Navbar.tsx +++ b/src/components/navbar/Navbar.tsx @@ -114,7 +114,7 @@ const Navbar: React.FC = () => { )}
{ + variant?: keyof typeof buttonVariants + size?: keyof typeof buttonVariants +} + +const Button = React.forwardRef( + ({ className, variant = "default", ...props }, ref) => { + const { base, variant: variantClass, size: sizeClass } = buttonVariants[variant] + return ( +