Enhance Admin Dashboard and Members Directory with improved layout, pagination, and member details display.
Applied requested changes to UI
This commit is contained in:
@@ -24,6 +24,8 @@ const MembersDirectory = () => {
|
||||
const [selectedMember, setSelectedMember] = useState(null);
|
||||
const [profileDialogOpen, setProfileDialogOpen] = useState(false);
|
||||
const { toast } = useToast();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const pageSize = 12;
|
||||
|
||||
useEffect(() => {
|
||||
fetchMembers();
|
||||
@@ -33,6 +35,10 @@ const MembersDirectory = () => {
|
||||
filterMembers();
|
||||
}, [searchQuery, members]);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
}, [searchQuery, members]);
|
||||
|
||||
const fetchMembers = async () => {
|
||||
try {
|
||||
const response = await api.get('/members/directory');
|
||||
@@ -66,6 +72,14 @@ const MembersDirectory = () => {
|
||||
setFilteredMembers(filtered);
|
||||
};
|
||||
|
||||
const totalPages = Math.max(1, Math.ceil(filteredMembers.length / pageSize));
|
||||
|
||||
const pageStart = (currentPage - 1) * pageSize;
|
||||
|
||||
const paginatedMembers = filteredMembers.slice(pageStart, pageStart + pageSize);
|
||||
|
||||
const totalMembers = members.length;
|
||||
|
||||
const getInitials = (firstName, lastName) => {
|
||||
return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase();
|
||||
};
|
||||
@@ -97,9 +111,15 @@ const MembersDirectory = () => {
|
||||
if (!dateString) return null;
|
||||
return new Date(dateString).toLocaleDateString('en-US', { month: 'long', day: 'numeric' });
|
||||
};
|
||||
|
||||
const Border = ({ yaxis = false }) => {
|
||||
return (
|
||||
yaxis ?
|
||||
<div className=' border-2 w-full border-[#664FA3] my-24' />
|
||||
: <div className=' border-2 w-full border-[#664FA3] mb-24' />
|
||||
)
|
||||
}
|
||||
const MemberCard = ({ member }) => (
|
||||
<Card className="p-6 bg-white rounded-2xl border border-[#ddd8eb] hover:shadow-lg transition-all h-full">
|
||||
<Card className="p-6 bg-white rounded-3xl border border-[#ddd8eb] hover:shadow-lg transition-all h-full">
|
||||
{/* Profile Photo */}
|
||||
<div className="flex justify-center mb-4">
|
||||
{member.profile_photo_url ? (
|
||||
@@ -259,39 +279,48 @@ const MembersDirectory = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
<div className="min-h-screen bg-gradient-to-bl from-[#F9FAFB] to-[#DDD8EB]">
|
||||
<Navbar />
|
||||
|
||||
<div className="max-w-7xl mx-auto px-6 py-12">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl md:text-5xl font-semibold text-[#422268] mb-4" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Members Directory
|
||||
</h1>
|
||||
<p className="text-lg text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Connect with fellow LOAF members in our community.
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-w-7xl mx-auto py-12">
|
||||
|
||||
{/* Search Bar */}
|
||||
<div className="mb-8">
|
||||
<div className="relative max-w-xl">
|
||||
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5 text-[#664fa3]" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search by name or bio..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-12 pr-4 py-6 text-lg border-[#ddd8eb] rounded-xl focus:border-[#664fa3] focus:ring-[#664fa3]"
|
||||
style={{ fontFamily: "'Nunito Sans', sans-serif" }}
|
||||
/>
|
||||
</div>
|
||||
{searchQuery && (
|
||||
<p className="mt-3 text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Found {filteredMembers.length} {filteredMembers.length === 1 ? 'member' : 'members'}
|
||||
{/* Header and Search bar */}
|
||||
<div className='px-9'>
|
||||
|
||||
{/* Header */}
|
||||
<div className="m-8 mt-14 flex flex-col sm:flex-row justify-between items-center ">
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-[#422268] mb-4" style={{ fontFamily: "'Poppins', sans-serif" }}>
|
||||
LOAF Members
|
||||
</h1>
|
||||
<p className="text-lg " style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
<span className='text-foreground'>Number of current memebers in the directory: </span> <span className='text-[#664fa3] font-medium'>{totalMembers}</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Search Bar */}
|
||||
<div className="mb-24 mx-10">
|
||||
<div className="relative w-full ">
|
||||
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5 text-[#664fa3]" />
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search by name or bio..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-12 pr-4 py-6 text-3xl font-medium bg-background border-foreground rounded-full focus:border-[#664fa3] focus:ring-[#664fa3]"
|
||||
style={{ fontFamily: "'Nunito Sans', sans-serif" }}
|
||||
/>
|
||||
</div>
|
||||
{searchQuery && (
|
||||
<p className="mt-3 text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Found {filteredMembers.length} {filteredMembers.length === 1 ? 'member' : 'members'}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/* Border Decoration */}
|
||||
|
||||
<Border />
|
||||
|
||||
{/* Members Grid */}
|
||||
{loading ? (
|
||||
@@ -300,7 +329,7 @@ const MembersDirectory = () => {
|
||||
</div>
|
||||
) : filteredMembers.length > 0 ? (
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{filteredMembers.map((member) => (
|
||||
{paginatedMembers.map((member) => (
|
||||
<MemberCard key={member.id} member={member} />
|
||||
))}
|
||||
</div>
|
||||
@@ -318,6 +347,11 @@ const MembersDirectory = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
{/* Border Decoration */}
|
||||
<Border yaxis="true" />
|
||||
|
||||
{/* Info Card */}
|
||||
{!loading && members.length > 0 && (
|
||||
<Card className="mt-12 p-6 bg-[#F8F7FB] border-[#ddd8eb]">
|
||||
@@ -465,67 +499,113 @@ const MembersDirectory = () => {
|
||||
{/* Social Media */}
|
||||
{(selectedMember.social_media_facebook || selectedMember.social_media_instagram ||
|
||||
selectedMember.social_media_twitter || selectedMember.social_media_linkedin) && (
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-[#422268] mb-3" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Connect on Social Media
|
||||
</h3>
|
||||
<div className="flex gap-3">
|
||||
{selectedMember.social_media_facebook && (
|
||||
<a
|
||||
href={getSocialMediaLink(selectedMember.social_media_facebook)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-3 rounded-lg bg-[#F8F7FB] hover:bg-[#DDD8EB] transition-colors"
|
||||
title="Facebook"
|
||||
>
|
||||
<Facebook className="h-6 w-6 text-[#1877F2]" />
|
||||
</a>
|
||||
)}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-[#422268] mb-3" style={{ fontFamily: "'Inter', sans-serif" }}>
|
||||
Connect on Social Media
|
||||
</h3>
|
||||
<div className="flex gap-3">
|
||||
{selectedMember.social_media_facebook && (
|
||||
<a
|
||||
href={getSocialMediaLink(selectedMember.social_media_facebook)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-3 rounded-lg bg-[#F8F7FB] hover:bg-[#DDD8EB] transition-colors"
|
||||
title="Facebook"
|
||||
>
|
||||
<Facebook className="h-6 w-6 text-[#1877F2]" />
|
||||
</a>
|
||||
)}
|
||||
|
||||
{selectedMember.social_media_instagram && (
|
||||
<a
|
||||
href={getSocialMediaLink(selectedMember.social_media_instagram)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-3 rounded-lg bg-[#F8F7FB] hover:bg-[#DDD8EB] transition-colors"
|
||||
title="Instagram"
|
||||
>
|
||||
<Instagram className="h-6 w-6 text-[#E4405F]" />
|
||||
</a>
|
||||
)}
|
||||
{selectedMember.social_media_instagram && (
|
||||
<a
|
||||
href={getSocialMediaLink(selectedMember.social_media_instagram)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-3 rounded-lg bg-[#F8F7FB] hover:bg-[#DDD8EB] transition-colors"
|
||||
title="Instagram"
|
||||
>
|
||||
<Instagram className="h-6 w-6 text-[#E4405F]" />
|
||||
</a>
|
||||
)}
|
||||
|
||||
{selectedMember.social_media_twitter && (
|
||||
<a
|
||||
href={getSocialMediaLink(selectedMember.social_media_twitter)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-3 rounded-lg bg-[#F8F7FB] hover:bg-[#DDD8EB] transition-colors"
|
||||
title="Twitter/X"
|
||||
>
|
||||
<Twitter className="h-6 w-6 text-[#1DA1F2]" />
|
||||
</a>
|
||||
)}
|
||||
{selectedMember.social_media_twitter && (
|
||||
<a
|
||||
href={getSocialMediaLink(selectedMember.social_media_twitter)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-3 rounded-lg bg-[#F8F7FB] hover:bg-[#DDD8EB] transition-colors"
|
||||
title="Twitter/X"
|
||||
>
|
||||
<Twitter className="h-6 w-6 text-[#1DA1F2]" />
|
||||
</a>
|
||||
)}
|
||||
|
||||
{selectedMember.social_media_linkedin && (
|
||||
<a
|
||||
href={getSocialMediaLink(selectedMember.social_media_linkedin)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-3 rounded-lg bg-[#F8F7FB] hover:bg-[#DDD8EB] transition-colors"
|
||||
title="LinkedIn"
|
||||
>
|
||||
<Linkedin className="h-6 w-6 text-[#0A66C2]" />
|
||||
</a>
|
||||
)}
|
||||
{selectedMember.social_media_linkedin && (
|
||||
<a
|
||||
href={getSocialMediaLink(selectedMember.social_media_linkedin)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-3 rounded-lg bg-[#F8F7FB] hover:bg-[#DDD8EB] transition-colors"
|
||||
title="LinkedIn"
|
||||
>
|
||||
<Linkedin className="h-6 w-6 text-[#0A66C2]" />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
|
||||
|
||||
{/* Pagination */}
|
||||
{!loading && filteredMembers.length > 0 && (
|
||||
<div className="mt-10 flex flex-col items-center gap-4 pb-12">
|
||||
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Showing {pageStart + 1}–{Math.min(pageStart + pageSize, filteredMembers.length)} of {filteredMembers.length}
|
||||
</p>
|
||||
<div className="flex flex-wrap items-center justify-center gap-2">
|
||||
<Button
|
||||
onClick={() => setCurrentPage((page) => Math.max(1, page - 1))}
|
||||
disabled={currentPage === 1}
|
||||
className="bg-[#DDD8EB] rounded-full text-[#422268] hover:bg-[#664fa3] hover:text-white"
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
|
||||
{Array.from({ length: totalPages }, (_, index) => {
|
||||
const pageNumber = index + 1;
|
||||
const isActive = pageNumber === currentPage;
|
||||
return (
|
||||
<Button
|
||||
key={pageNumber}
|
||||
onClick={() => setCurrentPage(pageNumber)}
|
||||
className={
|
||||
isActive
|
||||
? "bg-[#664fa3] text-white hover:bg-[#422268] rounded-full"
|
||||
: "bg-[#DDD8EB] text-[#422268] hover:bg-[#664fa3] hover:text-white rounded-full"
|
||||
}
|
||||
>
|
||||
{pageNumber}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
|
||||
<Button
|
||||
onClick={() => setCurrentPage((page) => Math.min(totalPages, page + 1))}
|
||||
disabled={currentPage === totalPages}
|
||||
className="bg-[#DDD8EB] text-[#422268] hover:bg-[#664fa3] rounded-full hover:text-white"
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<MemberFooter />
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user