Skip to content

Commit 3f0c0cd

Browse files
authored
Merge pull request #41 from atlp-rwanda/some-bugs-fix
fixing some bugs for better appearance
2 parents 3fb8f9d + 7104c0f commit 3f0c0cd

File tree

14 files changed

+598
-45
lines changed

14 files changed

+598
-45
lines changed

src/App.tsx

+6-16
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,14 @@ import AdminPage from './pages/admin';
1717
import Category from './pages/admin/Category';
1818
import Sellers from './pages/admin/Sellers';
1919
import Buyers from './pages/admin/Buyers';
20-
import Messages from './pages/admin/Messages';
2120
import UserManagement from './pages/admin/UserManagement';
2221
import NotFoundPage from './pages/NotFoundPage';
23-
import Settings from './pages/admin/Settings';
22+
import SellerSettings from './pages/seller/Settings';
2423
import SellersPage from './pages/seller';
2524
import Orders from './pages/seller/Orders';
2625
import Products from './pages/seller/Products';
2726
import Customers from './pages/seller/Customers';
28-
import SellerMessages from './pages/seller/Messages';
29-
import SellerSettings from './pages/seller/Settings';
27+
import AdminSettings from './pages/admin/Settings';
3028
import AddNewProduct from './pages/seller/AddNewProduct';
3129
import RestrictedSellerRoute from './components/dashboard/RestrictedSellerLayout';
3230

@@ -140,12 +138,12 @@ const App = () => {
140138
element: <Buyers />,
141139
},
142140
{
143-
path: 'messages',
144-
element: <SellerMessages />,
141+
path: 'users',
142+
element: <UserManagement />,
145143
},
146144
{
147145
path: 'settings',
148-
element: <SellerSettings />,
146+
element: <AdminSettings />,
149147
},
150148
],
151149
},
@@ -173,17 +171,9 @@ const App = () => {
173171
path: 'customers',
174172
element: <Customers />,
175173
},
176-
{
177-
path: 'messages',
178-
element: <Messages />,
179-
},
180-
{
181-
path: 'users',
182-
element: <UserManagement />,
183-
},
184174
{
185175
path: 'settings',
186-
element: <Settings />,
176+
element: <SellerSettings />,
187177
},
188178
],
189179
},

src/components/admin/AdminLayout.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { Outlet } from 'react-router-dom';
22
import Sidebar from '../dashboard/Sidebar';
33
import { RxDashboard } from 'react-icons/rx';
4-
import { FaCog, FaRegListAlt, FaUserFriends } from 'react-icons/fa';
5-
import { FaRegEnvelope, FaUserTie } from 'react-icons/fa6';
4+
import { FaCog, FaRegListAlt, FaUserFriends, FaUsers } from 'react-icons/fa';
5+
import { FaUserTie } from 'react-icons/fa6';
66

77
export default function AdminLayout() {
88
const adminSidebarLinks = [
99
{ name: 'Dashboard', path: '/admin', icon: <RxDashboard className='mr-3' /> },
1010
{ name: 'Categories', path: 'categories', icon: <FaRegListAlt className='mr-3' /> },
1111
{ name: 'Sellers', path: 'sellers', icon: <FaUserFriends className='mr-3' /> },
1212
{ name: 'Buyers', path: 'buyers', icon: <FaUserTie className='mr-3' /> },
13-
{ name: 'Messages', path: 'messages', icon: <FaRegEnvelope className='mr-3' /> },
13+
{ name: 'Users', path: 'users', icon: <FaUsers className='mr-3' /> },
1414
{ name: 'Settings', path: 'settings', icon: <FaCog className='mr-3' /> },
1515
];
1616

src/components/chat/Chat.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@ const Chat: React.FC = () => {
8181
</div>
8282

8383
{showChat && (
84-
<div className='fixed inset-0 z-40 flex items-center justify-end mx-2 sm:mr-6'>
85-
<div className='w-9/10 max-w-sm h-3/4 rounded-2xl shadow-xl relative bg-grayColor overflow-hidden '>
86-
<div className='header bg-darkGreen flex justify-between items-center px-6 py-3 text-white'>
87-
<div className='flex items-center gap-4'>
84+
<div className='fixed inset-0 z-40 flex items-center justify-end mx-2 sm:mr-6'>
85+
<div className='w-9/10 max-w-sm h-3/4 rounded-2xl shadow-xl relative bg-grayColor overflow-hidden pt-10'>
86+
<div className='header bg-darkGreen flex justify-between items-center px-6 py-3 text-whiteColor'>
87+
<div className='flex items-center gap-4'>
8888
<img
8989
src={`${profileImage !== null ? profileImage : chatAvatar}`}
9090
alt='Mavericks'
+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import React, { useState, useMemo } from 'react';
2+
3+
interface Column {
4+
key: string;
5+
label: string;
6+
isImage?: boolean;
7+
render?: (item: any) => React.ReactNode;
8+
sortable: boolean;
9+
}
10+
11+
interface TableProps {
12+
data: any[];
13+
columns: Column[];
14+
itemsPerPage: number;
15+
}
16+
17+
const getInitials = (firstName: string, lastName: string) =>
18+
`${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
19+
20+
const getRandomColor = () => {
21+
const letters = '0123456789ABCDEF';
22+
let color = '#';
23+
for (let i = 0; i < 6; i++) {
24+
color += letters[Math.floor(Math.random() * 16)];
25+
}
26+
return color;
27+
};
28+
29+
const Table: React.FC<TableProps> = ({ data, columns, itemsPerPage }) => {
30+
const [currentPage, setCurrentPage] = useState(1);
31+
const [sortColumn, setSortColumn] = useState<string | null>(null);
32+
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
33+
const [searchTerm, setSearchTerm] = useState('');
34+
35+
const filteredData = useMemo(
36+
() =>
37+
data.filter(item =>
38+
columns.some(column => String(item[column.key]).toLowerCase().includes(searchTerm.toLowerCase()))
39+
),
40+
[data, columns, searchTerm]
41+
);
42+
43+
const sortedData = useMemo(() => {
44+
if (!sortColumn) return filteredData;
45+
return [...filteredData].sort((a, b) => {
46+
if (a[sortColumn] < b[sortColumn]) return sortDirection === 'asc' ? -1 : 1;
47+
if (a[sortColumn] > b[sortColumn]) return sortDirection === 'asc' ? 1 : -1;
48+
return 0;
49+
});
50+
}, [filteredData, sortColumn, sortDirection]);
51+
52+
const paginatedData = useMemo(() => {
53+
const startIndex = (currentPage - 1) * itemsPerPage;
54+
return sortedData.slice(startIndex, startIndex + itemsPerPage);
55+
}, [sortedData, currentPage, itemsPerPage]);
56+
57+
const totalPages = Math.ceil(sortedData.length / itemsPerPage);
58+
59+
const handleSort = (column: string) => {
60+
if (column === sortColumn) {
61+
setSortDirection(prev => (prev === 'asc' ? 'desc' : 'asc'));
62+
} else {
63+
setSortColumn(column);
64+
setSortDirection('asc');
65+
}
66+
};
67+
68+
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
69+
setSearchTerm(e.target.value);
70+
setCurrentPage(1);
71+
};
72+
73+
const renderCell = (item: any, column: Column) => {
74+
if (column.isImage) {
75+
return item[column.key] ? (
76+
<img
77+
src={item[column.key]}
78+
alt={`${item.firstName} ${item.lastName}`}
79+
className='ml-2 w-10 h-10 rounded-full object-cover'
80+
/>
81+
) : (
82+
<div
83+
className='ml-2 w-10 h-10 flex items-center justify-center text-whiteColor font-bold text-lg rounded-full uppercase'
84+
style={{ backgroundColor: getRandomColor() }}
85+
>
86+
{getInitials(item.firstName, item.lastName)}
87+
</div>
88+
);
89+
}
90+
if (column.render) return column.render(item);
91+
return column.key.includes('.')
92+
? column.key.split('.').reduce((obj, key) => obj && obj[key], item)
93+
: item[column.key];
94+
};
95+
96+
return (
97+
<div className='overflow-x-auto pl-4'>
98+
<div className='mb-2 flex justify-between items-center'>
99+
<h1 className='text-2xl font-bold'>Buyers Page</h1>
100+
<input
101+
type='text'
102+
placeholder='Search buyer...'
103+
value={searchTerm}
104+
onChange={handleSearch}
105+
className='px-2 py-1 border rounded outline-none'
106+
/>
107+
</div>
108+
<div className='rounded-lg overflow-hidden'>
109+
<table className='min-w-full bg-whiteColor px-10'>
110+
<thead className='bg-darkGreen'>
111+
<tr>
112+
{columns.map(column => (
113+
<th
114+
key={column.key}
115+
onClick={() => column.sortable && handleSort(column.key)}
116+
className={`px-4 py-4 text-sm font-bold text-whiteColor uppercase tracking-wider ${
117+
column.sortable ? 'cursor-pointer' : ''
118+
} ${column.isImage ? 'text-center' : 'text-left'}`}
119+
>
120+
{column.label}
121+
{column.sortable && sortColumn === column.key && <span>{sortDirection === 'asc' ? ' ▲' : ' ▼'}</span>}
122+
</th>
123+
))}
124+
</tr>
125+
</thead>
126+
<tbody>
127+
{paginatedData.map((item, index) => (
128+
<tr key={index} className={index % 2 === 0 ? 'bg-[#F3F4F6]' : 'bg-whiteColor'}>
129+
{columns.map(column => (
130+
<td
131+
key={column.key}
132+
className={`px-4 py-2 whitespace-nowrap text-sm ${column.isImage ? 'text-center' : 'text-left'}`}
133+
>
134+
{renderCell(item, column)}
135+
</td>
136+
))}
137+
</tr>
138+
))}
139+
</tbody>
140+
</table>
141+
</div>
142+
<div className='mt-4 flex flex-col sm:flex-row justify-between items-center'>
143+
<div className='mb-2 sm:mb-0 text-sm'>
144+
Showing {(currentPage - 1) * itemsPerPage + 1} to {Math.min(currentPage * itemsPerPage, sortedData.length)} of{' '}
145+
{sortedData.length} buyers
146+
</div>
147+
<div className='flex justify-center'>
148+
<button
149+
onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))}
150+
disabled={currentPage === 1}
151+
className='px-3 py-1 border rounded-lg border-darkGreen mr-2 text-sm'
152+
>
153+
Previous
154+
</button>
155+
<button
156+
onClick={() => setCurrentPage(prev => Math.min(prev + 1, totalPages))}
157+
disabled={currentPage === totalPages}
158+
className='px-3 py-1 border rounded-lg border-darkGreen text-sm'
159+
>
160+
Next
161+
</button>
162+
</div>
163+
</div>
164+
</div>
165+
);
166+
};
167+
168+
export default Table;

src/components/dashboard/UsersTable.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ const Table: React.FC<TableProps> = ({ data, columns, itemsPerPage }) => {
9494

9595
return (
9696
<div className='overflow-x-auto pl-4'>
97-
<div className='mb-2'>
97+
<div className='mb-2 flex justify-between items-center'>
98+
<h1 className='text-2xl font-bold'>Users Management</h1>
9899
<input
99100
type='text'
100101
placeholder='Search user...'

src/components/dashboard/VendorsTable.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ const Table: React.FC<TableProps> = ({ data, columns, itemsPerPage }) => {
9494

9595
return (
9696
<div className='overflow-x-auto pl-4'>
97-
<div className='mb-2'>
97+
<div className='mb-2 flex justify-between items-center'>
98+
<h1 className='text-2xl font-bold'>Sellers Management</h1>
9899
<input
99100
type='text'
100101
placeholder='Search seller...'
@@ -104,13 +105,13 @@ const Table: React.FC<TableProps> = ({ data, columns, itemsPerPage }) => {
104105
/>
105106
</div>
106107
<table className='min-w-full bg-whiteColor px-10'>
107-
<thead className='bg-[#F9FAFB]'>
108+
<thead className='bg-[#000000a1]'>
108109
<tr>
109110
{columns.map(column => (
110111
<th
111112
key={column.key}
112113
onClick={() => !column.isImage && handleSort(column.key)}
113-
className={`pl-4 py-4 text-left text-sm font-bold text-[#6B7280] uppercase tracking-wider ${!column.isImage ? 'cursor-pointer' : ''}`}
114+
className={`pl-4 py-4 text-left text-sm font-bold text-whiteColor uppercase tracking-wider ${!column.isImage ? 'cursor-pointer' : ''}`}
114115
>
115116
{column.label}
116117
{!column.isImage && sortColumn === column.key && <span>{sortDirection === 'asc' ? ' ▲' : ' ▼'}</span>}

src/components/footer/Footer.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ function Footer() {
1313
<div className='leading-none text-xs md:text-base break-words flex flex-col gap-3 font-light flex-grow'>
1414
<p>K309 St , Makuza plaza, Nyarugenge , Kigali, Rwanda</p>
1515
16-
<p>+250 788888888</p>
16+
<p>+250 788 888 888</p>
1717
</div>
1818
<div className='flex gap-2'>
1919
<a href='#' target='_blank'>
@@ -66,12 +66,12 @@ function Footer() {
6666
Join
6767
</button>
6868
</div>
69-
<span className='text-xs text-redColor text-left'>Email is not valid</span>
69+
{/* <span className='text-xs text-redColor text-left'>Email is not valid</span> */}
7070
</form>
7171
</div>
7272
</div>
7373
<p className='p-3 md:p-4 xl:px-10 2xl:w-[1440px] text-xs text-center xl:text-left '>
74-
&copy; 2024 Mavericks Shop. All rights reserved.
74+
&copy; {new Date().getFullYear()} Mavericks Shop. All rights reserved.
7575
</p>
7676
</div>
7777
</>

src/components/navbar/Navbar.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ const Navbar: React.FC = () => {
114114
)}
115115
<div
116116
className={`flex flex-col bg-blackColor md:bg-whiteColor md:text-blackColor text-whiteColor font-roboto w-full 2xl:items-center top-0 ${wish || cartOpen ? 'sticky' : ''
117-
} z-10`}
117+
} sticky z-10`}
118118
>
119119
<div
120120
className='flex justify-between gap-2 flex-wrap p-3 md:p-4 xl:px-10 2xl:w-[1440px] relative'

src/pages/admin/Buyers.tsx

+58-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,58 @@
1-
export default function Buyers() {
2-
return <div>Buyers</div>;
3-
}
1+
import React, { useEffect } from 'react';
2+
import Table from '../../components/dashboard/BuyersTable';
3+
import { BiLoader } from 'react-icons/bi';
4+
import { useGetBuyersQuery } from '../../services/userApi';
5+
import { User as Buyer } from '../../types/Types';
6+
7+
const Buyers: React.FC = () => {
8+
const {
9+
data: buyersData,
10+
isLoading: buyersLoading,
11+
isError: buyersError,
12+
refetch: refetchBuyers,
13+
} = useGetBuyersQuery(undefined, {
14+
pollingInterval: 30000,
15+
});
16+
17+
const loading = buyersLoading;
18+
const error = buyersError;
19+
20+
const buyers = buyersData?.message || [];
21+
22+
useEffect(() => {
23+
refetchBuyers();
24+
}, [refetchBuyers]);
25+
26+
const columns = [
27+
{ key: 'photoUrl', label: 'Photo', isImage: true, sortable: false },
28+
{ key: 'firstName', label: 'First Name', sortable: true },
29+
{ key: 'lastName', label: 'Last Name', sortable: true },
30+
{ key: 'email', label: 'Email', sortable: true },
31+
{ key: 'phoneNumber', label: 'Phone', sortable: true },
32+
{ key: 'gender', label: 'Gender', sortable: true },
33+
{
34+
key: 'Role.name',
35+
label: 'Role',
36+
render: (buyer: Buyer) => buyer.Role.name.toUpperCase(),
37+
sortable: false,
38+
},
39+
];
40+
41+
if (loading)
42+
return (
43+
<div className='text-center content-center h-screen py-4'>
44+
<BiLoader className='animate-spin w-full h-full max-h-12 text-[#3B82F6]' />
45+
</div>
46+
);
47+
if (error) {
48+
return <div className='text-center py-4 text-redColor'>Error fetching data. Please try again.</div>;
49+
}
50+
51+
return (
52+
<div className='container mx-auto p-4 lg:pl-56'>
53+
<Table data={buyers} columns={columns} itemsPerPage={10} />
54+
</div>
55+
);
56+
};
57+
58+
export default Buyers;

src/pages/admin/Messages.tsx

-3
This file was deleted.

0 commit comments

Comments
 (0)