-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fixing some bugs for better appearance
- Loading branch information
1 parent
3fb8f9d
commit 7104c0f
Showing
14 changed files
with
598 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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'> | ||
|
@@ -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 '> | ||
© 2024 Mavericks Shop. All rights reserved. | ||
© {new Date().getFullYear()} Mavericks Shop. All rights reserved. | ||
</p> | ||
</div> | ||
</> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.