Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixing some bugs for better appearance #41

Merged
merged 1 commit into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 6 additions & 16 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -140,12 +138,12 @@ const App = () => {
element: <Buyers />,
},
{
path: 'messages',
element: <SellerMessages />,
path: 'users',
element: <UserManagement />,
},
{
path: 'settings',
element: <SellerSettings />,
element: <AdminSettings />,
},
],
},
Expand Down Expand Up @@ -173,17 +171,9 @@ const App = () => {
path: 'customers',
element: <Customers />,
},
{
path: 'messages',
element: <Messages />,
},
{
path: 'users',
element: <UserManagement />,
},
{
path: 'settings',
element: <Settings />,
element: <SellerSettings />,
},
],
},
Expand Down
6 changes: 3 additions & 3 deletions src/components/admin/AdminLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
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 = [
{ name: 'Dashboard', path: '/admin', icon: <RxDashboard className='mr-3' /> },
{ name: 'Categories', path: 'categories', icon: <FaRegListAlt className='mr-3' /> },
{ name: 'Sellers', path: 'sellers', icon: <FaUserFriends className='mr-3' /> },
{ name: 'Buyers', path: 'buyers', icon: <FaUserTie className='mr-3' /> },
{ name: 'Messages', path: 'messages', icon: <FaRegEnvelope className='mr-3' /> },
{ name: 'Users', path: 'users', icon: <FaUsers className='mr-3' /> },
{ name: 'Settings', path: 'settings', icon: <FaCog className='mr-3' /> },
];

Expand Down
8 changes: 4 additions & 4 deletions src/components/chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ const Chat: React.FC = () => {
</div>

{showChat && (
<div className='fixed inset-0 z-40 flex items-center justify-end mx-2 sm:mr-6'>
<div className='w-9/10 max-w-sm h-3/4 rounded-2xl shadow-xl relative bg-grayColor overflow-hidden '>
<div className='header bg-darkGreen flex justify-between items-center px-6 py-3 text-white'>
<div className='flex items-center gap-4'>
<div className='fixed inset-0 z-40 flex items-center justify-end mx-2 sm:mr-6'>
<div className='w-9/10 max-w-sm h-3/4 rounded-2xl shadow-xl relative bg-grayColor overflow-hidden pt-10'>
<div className='header bg-darkGreen flex justify-between items-center px-6 py-3 text-whiteColor'>
<div className='flex items-center gap-4'>
<img
src={`${profileImage !== null ? profileImage : chatAvatar}`}
alt='Mavericks'
Expand Down
168 changes: 168 additions & 0 deletions src/components/dashboard/BuyersTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import React, { useState, useMemo } from 'react';

interface Column {
key: string;
label: string;
isImage?: boolean;
render?: (item: any) => 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<TableProps> = ({ data, columns, itemsPerPage }) => {
const [currentPage, setCurrentPage] = useState(1);
const [sortColumn, setSortColumn] = useState<string | null>(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<HTMLInputElement>) => {
setSearchTerm(e.target.value);
setCurrentPage(1);
};

const renderCell = (item: any, column: Column) => {
if (column.isImage) {
return item[column.key] ? (
<img
src={item[column.key]}
alt={`${item.firstName} ${item.lastName}`}
className='ml-2 w-10 h-10 rounded-full object-cover'
/>
) : (
<div
className='ml-2 w-10 h-10 flex items-center justify-center text-whiteColor font-bold text-lg rounded-full uppercase'
style={{ backgroundColor: getRandomColor() }}
>
{getInitials(item.firstName, item.lastName)}
</div>
);
}
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 (
<div className='overflow-x-auto pl-4'>
<div className='mb-2 flex justify-between items-center'>
<h1 className='text-2xl font-bold'>Buyers Page</h1>
<input
type='text'
placeholder='Search buyer...'
value={searchTerm}
onChange={handleSearch}
className='px-2 py-1 border rounded outline-none'
/>
</div>
<div className='rounded-lg overflow-hidden'>
<table className='min-w-full bg-whiteColor px-10'>
<thead className='bg-darkGreen'>
<tr>
{columns.map(column => (
<th
key={column.key}
onClick={() => 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 && <span>{sortDirection === 'asc' ? ' ▲' : ' ▼'}</span>}
</th>
))}
</tr>
</thead>
<tbody>
{paginatedData.map((item, index) => (
<tr key={index} className={index % 2 === 0 ? 'bg-[#F3F4F6]' : 'bg-whiteColor'}>
{columns.map(column => (
<td
key={column.key}
className={`px-4 py-2 whitespace-nowrap text-sm ${column.isImage ? 'text-center' : 'text-left'}`}
>
{renderCell(item, column)}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
<div className='mt-4 flex flex-col sm:flex-row justify-between items-center'>
<div className='mb-2 sm:mb-0 text-sm'>
Showing {(currentPage - 1) * itemsPerPage + 1} to {Math.min(currentPage * itemsPerPage, sortedData.length)} of{' '}
{sortedData.length} buyers
</div>
<div className='flex justify-center'>
<button
onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))}
disabled={currentPage === 1}
className='px-3 py-1 border rounded-lg border-darkGreen mr-2 text-sm'
>
Previous
</button>
<button
onClick={() => setCurrentPage(prev => Math.min(prev + 1, totalPages))}
disabled={currentPage === totalPages}
className='px-3 py-1 border rounded-lg border-darkGreen text-sm'
>
Next
</button>
</div>
</div>
</div>
);
};

export default Table;
3 changes: 2 additions & 1 deletion src/components/dashboard/UsersTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ const Table: React.FC<TableProps> = ({ data, columns, itemsPerPage }) => {

return (
<div className='overflow-x-auto pl-4'>
<div className='mb-2'>
<div className='mb-2 flex justify-between items-center'>
<h1 className='text-2xl font-bold'>Users Management</h1>
<input
type='text'
placeholder='Search user...'
Expand Down
7 changes: 4 additions & 3 deletions src/components/dashboard/VendorsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ const Table: React.FC<TableProps> = ({ data, columns, itemsPerPage }) => {

return (
<div className='overflow-x-auto pl-4'>
<div className='mb-2'>
<div className='mb-2 flex justify-between items-center'>
<h1 className='text-2xl font-bold'>Sellers Management</h1>
<input
type='text'
placeholder='Search seller...'
Expand All @@ -104,13 +105,13 @@ const Table: React.FC<TableProps> = ({ data, columns, itemsPerPage }) => {
/>
</div>
<table className='min-w-full bg-whiteColor px-10'>
<thead className='bg-[#F9FAFB]'>
<thead className='bg-[#000000a1]'>
<tr>
{columns.map(column => (
<th
key={column.key}
onClick={() => !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 && <span>{sortDirection === 'asc' ? ' ▲' : ' ▼'}</span>}
Expand Down
6 changes: 3 additions & 3 deletions src/components/footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function Footer() {
<div className='leading-none text-xs md:text-base break-words flex flex-col gap-3 font-light flex-grow'>
<p>K309 St , Makuza plaza, Nyarugenge , Kigali, Rwanda</p>
<p>[email protected]</p>
<p>+250 788888888</p>
<p>+250 788 888 888</p>
</div>
<div className='flex gap-2'>
<a href='#' target='_blank'>
Expand Down Expand Up @@ -66,12 +66,12 @@ function Footer() {
Join
</button>
</div>
<span className='text-xs text-redColor text-left'>Email is not valid</span>
{/* <span className='text-xs text-redColor text-left'>Email is not valid</span> */}
</form>
</div>
</div>
<p className='p-3 md:p-4 xl:px-10 2xl:w-[1440px] text-xs text-center xl:text-left '>
&copy; 2024 Mavericks Shop. All rights reserved.
&copy; {new Date().getFullYear()} Mavericks Shop. All rights reserved.
</p>
</div>
</>
Expand Down
2 changes: 1 addition & 1 deletion src/components/navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ const Navbar: React.FC = () => {
)}
<div
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' : ''
} z-10`}
} sticky z-10`}
>
<div
className='flex justify-between gap-2 flex-wrap p-3 md:p-4 xl:px-10 2xl:w-[1440px] relative'
Expand Down
61 changes: 58 additions & 3 deletions src/pages/admin/Buyers.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,58 @@
export default function Buyers() {
return <div>Buyers</div>;
}
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 (
<div className='text-center content-center h-screen py-4'>
<BiLoader className='animate-spin w-full h-full max-h-12 text-[#3B82F6]' />
</div>
);
if (error) {
return <div className='text-center py-4 text-redColor'>Error fetching data. Please try again.</div>;
}

return (
<div className='container mx-auto p-4 lg:pl-56'>
<Table data={buyers} columns={columns} itemsPerPage={10} />
</div>
);
};

export default Buyers;
3 changes: 0 additions & 3 deletions src/pages/admin/Messages.tsx

This file was deleted.

Loading
Loading