From 7104c0fb3fff2985525ee62c9f7e6b34e8edf8a5 Mon Sep 17 00:00:00 2001 From: "Prince NZAMUWE(Fudji Bokande)" <134228300+princenzmw@users.noreply.github.com> Date: Fri, 19 Jul 2024 00:32:03 +0200 Subject: [PATCH] fixing some bugs for better appearance --- src/App.tsx | 22 +- src/components/admin/AdminLayout.tsx | 6 +- src/components/chat/Chat.tsx | 8 +- src/components/dashboard/BuyersTable.tsx | 168 +++++++++++ src/components/dashboard/UsersTable.tsx | 3 +- src/components/dashboard/VendorsTable.tsx | 7 +- src/components/footer/Footer.tsx | 6 +- src/components/navbar/Navbar.tsx | 2 +- src/pages/admin/Buyers.tsx | 61 +++- src/pages/admin/Messages.tsx | 3 - src/pages/admin/Sellers.tsx | 3 +- src/pages/admin/Settings.tsx | 341 +++++++++++++++++++++- src/pages/admin/UserManagement.tsx | 3 +- src/services/userApi.ts | 10 +- 14 files changed, 598 insertions(+), 45 deletions(-) create mode 100644 src/components/dashboard/BuyersTable.tsx delete mode 100644 src/pages/admin/Messages.tsx diff --git a/src/App.tsx b/src/App.tsx index 4404ac7..60f760e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,16 +17,14 @@ 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'; @@ -140,12 +138,12 @@ const App = () => { element: , }, { - path: 'messages', - element: , + path: 'users', + element: , }, { path: 'settings', - element: , + element: , }, ], }, @@ -173,17 +171,9 @@ const App = () => { path: 'customers', element: , }, - { - path: 'messages', - element: , - }, - { - path: 'users', - element: , - }, { path: 'settings', - element: , + element: , }, ], }, 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 = () => { )}
Buyers
; -} +import React, { useEffect } from 'react'; +import Table from '../../components/dashboard/BuyersTable'; +import { BiLoader } from 'react-icons/bi'; +import { useGetBuyersQuery } from '../../services/userApi'; +import { User as Buyer } from '../../types/Types'; + +const Buyers: React.FC = () => { + const { + data: buyersData, + isLoading: buyersLoading, + isError: buyersError, + refetch: refetchBuyers, + } = useGetBuyersQuery(undefined, { + pollingInterval: 30000, + }); + + const loading = buyersLoading; + const error = buyersError; + + const buyers = buyersData?.message || []; + + useEffect(() => { + refetchBuyers(); + }, [refetchBuyers]); + + const columns = [ + { key: 'photoUrl', label: 'Photo', isImage: true, sortable: false }, + { key: 'firstName', label: 'First Name', sortable: true }, + { key: 'lastName', label: 'Last Name', sortable: true }, + { key: 'email', label: 'Email', sortable: true }, + { key: 'phoneNumber', label: 'Phone', sortable: true }, + { key: 'gender', label: 'Gender', sortable: true }, + { + key: 'Role.name', + label: 'Role', + render: (buyer: Buyer) => buyer.Role.name.toUpperCase(), + sortable: false, + }, + ]; + + if (loading) + return ( +
+ +
+ ); + if (error) { + return
Error fetching data. Please try again.
; + } + + return ( +
+ + + ); +}; + +export default Buyers; \ No newline at end of file diff --git a/src/pages/admin/Messages.tsx b/src/pages/admin/Messages.tsx deleted file mode 100644 index 365ea40..0000000 --- a/src/pages/admin/Messages.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Messages() { - return
Messages
; -} diff --git a/src/pages/admin/Sellers.tsx b/src/pages/admin/Sellers.tsx index f8bee62..5bb9d02 100644 --- a/src/pages/admin/Sellers.tsx +++ b/src/pages/admin/Sellers.tsx @@ -66,7 +66,7 @@ const Sellers: React.FC = () => { { key: 'Role.name', label: 'Role', - render: (seller: Seller) => seller.Role.name, + render: (seller: Seller) => seller.Role.name.toUpperCase(), sortable: false, }, { @@ -96,7 +96,6 @@ const Sellers: React.FC = () => { return (
-

Seller Management

{selectedSeller && ( Settings; -} +// src/pages/admin/Settings.tsx + +import React, { useState } from 'react'; +import { FiSave, FiUser, FiLock, FiBell, FiGlobe, FiShield, FiRefreshCcw } from 'react-icons/fi'; +import { ToastContainer, toast } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; + +const Settings: React.FC = () => { + const defaultGeneralSettings = { + siteName: 'Mavericks', + siteUrl: 'https://mavericks.nijohn.dev', + adminEmail: 'admin.mavericks@gmail.com', + dateFormat: 'MM/DD/YYYY', + timeFormat: '12-hour', + theme: 'light', + profilePicture: '', + }; + + const defaultSecuritySettings = { + twoFactorAuth: false, + passwordExpiration: 90, + loginAttempts: 5, + minPasswordLength: 8, + specialCharRequirement: true, + }; + + const defaultNotificationSettings = { + emailNotifications: true, + pushNotifications: false, + smsNotifications: false, + newsletterFrequency: 'weekly', + }; + + const [generalSettings, setGeneralSettings] = useState(defaultGeneralSettings); + const [securitySettings, setSecuritySettings] = useState(defaultSecuritySettings); + const [notificationSettings, setNotificationSettings] = useState(defaultNotificationSettings); + + const handleGeneralSettingsChange = (e: React.ChangeEvent) => { + const value = e.target.type === 'file' ? (e.target as HTMLInputElement).files?.[0]?.name : e.target.value; + setGeneralSettings({ ...generalSettings, [e.target.name]: value }); + }; + + const handleSecuritySettingsChange = (e: React.ChangeEvent) => { + const value = e.target.type === 'checkbox' ? (e.target as HTMLInputElement).checked : e.target.value; + setSecuritySettings({ ...securitySettings, [e.target.name]: value }); + }; + + const handleNotificationSettingsChange = (e: React.ChangeEvent) => { + const value = e.target.type === 'checkbox' ? (e.target as HTMLInputElement).checked : e.target.value; + setNotificationSettings({ ...notificationSettings, [e.target.name]: value }); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + console.log('Settings saved:', { generalSettings, securitySettings, notificationSettings }); + toast.success('Settings saved successfully!'); + }; + + const handleReset = () => { + setGeneralSettings(defaultGeneralSettings); + setSecuritySettings(defaultSecuritySettings); + setNotificationSettings(defaultNotificationSettings); + toast.info('Settings reset to default values!'); + }; + + return ( +
+

Admin Settings

+
+
+
+

+ General Settings +

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+

+ Security Settings +

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+

+ Notification Settings +

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+ + +
+ + +
+ ); +}; + +export default Settings; diff --git a/src/pages/admin/UserManagement.tsx b/src/pages/admin/UserManagement.tsx index d3d022d..56485c8 100644 --- a/src/pages/admin/UserManagement.tsx +++ b/src/pages/admin/UserManagement.tsx @@ -68,7 +68,7 @@ const UserManagement: React.FC = () => { { key: 'role', label: 'Role', - render: (user: User) => user.Role.name + render: (user: User) => user.Role.name.toUpperCase(), }, { key: 'action', @@ -96,7 +96,6 @@ const UserManagement: React.FC = () => { return (
-

User Management

({ + query: () => ({ + url: 'users/role/buyer', + headers: { + Authorization: `Bearer ${localStorage.getItem('token')}`, + }, + }), + }), updateUserRole: builder.mutation({ query: ({ userId, roleId }) => ({ url: `users/role/${userId}`, @@ -39,4 +47,4 @@ export const userApi = mavericksApi.injectEndpoints({ overrideExisting: false, }); -export const { useGetUserByIdQuery, useGetUsersQuery,useGetSellersQuery, useUpdateUserRoleMutation } = userApi; +export const { useGetUserByIdQuery, useGetUsersQuery,useGetSellersQuery, useGetBuyersQuery, useUpdateUserRoleMutation } = userApi;