diff --git a/src/components/StatCard.jsx b/src/components/StatCard.jsx new file mode 100644 index 0000000..e6a0f8b --- /dev/null +++ b/src/components/StatCard.jsx @@ -0,0 +1,36 @@ +import React from "react"; +import { Card } from "./ui/card"; + +export const StatCard = ({ + title, + value, + icon: Icon, + iconBgClass, + dataTestId, +}) => ( + +
+
+ +
+ +
+

+ {value} +

+
+
+

+ {title} +

+
+); diff --git a/src/components/ui/card.jsx b/src/components/ui/card.jsx index 2985cca..853f881 100644 --- a/src/components/ui/card.jsx +++ b/src/components/ui/card.jsx @@ -1,50 +1,65 @@ -import * as React from "react" +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const Card = React.forwardRef(({ className, ...props }, ref) => (
-)) -Card.displayName = "Card" + className={cn( + "rounded-xl border bg-card text-card-foreground shadow", + className, + )} + {...props} + /> +)); +Card.displayName = "Card"; const CardHeader = React.forwardRef(({ className, ...props }, ref) => (
-)) -CardHeader.displayName = "CardHeader" + {...props} + /> +)); +CardHeader.displayName = "CardHeader"; const CardTitle = React.forwardRef(({ className, ...props }, ref) => (
-)) -CardTitle.displayName = "CardTitle" + {...props} + /> +)); +CardTitle.displayName = "CardTitle"; const CardDescription = React.forwardRef(({ className, ...props }, ref) => (
-)) -CardDescription.displayName = "CardDescription" + {...props} + /> +)); +CardDescription.displayName = "CardDescription"; const CardContent = React.forwardRef(({ className, ...props }, ref) => (
-)) -CardContent.displayName = "CardContent" +)); +CardContent.displayName = "CardContent"; const CardFooter = React.forwardRef(({ className, ...props }, ref) => (
-)) -CardFooter.displayName = "CardFooter" + {...props} + /> +)); +CardFooter.displayName = "CardFooter"; -export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/src/pages/admin/AdminDashboard.js b/src/pages/admin/AdminDashboard.js index 6c2d934..fef95b2 100644 --- a/src/pages/admin/AdminDashboard.js +++ b/src/pages/admin/AdminDashboard.js @@ -4,13 +4,16 @@ import api from '../../utils/api'; import { Card } from '../../components/ui/card'; import { Button } from '../../components/ui/button'; import { Badge } from '../../components/ui/badge'; -import { Users, Calendar, Clock, CheckCircle, Mail, AlertCircle, Globe } from 'lucide-react'; +import { Users, Calendar, Clock, CheckCircle, Mail, AlertCircle, Globe, CircleMinus } from 'lucide-react'; +import { StatCard } from '../../components/StatCard'; + const AdminDashboard = () => { const [stats, setStats] = useState({ totalMembers: 0, pendingValidations: 0, - activeMembers: 0 + activeMembers: 0, + inactiveMembers: 0 }); const [usersNeedingAttention, setUsersNeedingAttention] = useState([]); const [loading, setLoading] = useState(true); @@ -29,7 +32,8 @@ const AdminDashboard = () => { pendingValidations: users.filter(u => ['pending_email', 'pending_validation', 'pre_validated', 'payment_pending'].includes(u.status) ).length, - activeMembers: users.filter(u => u.status === 'active' && u.role === 'member').length + activeMembers: users.filter(u => u.status === 'active' && u.role === 'member').length, + inactiveMembers: users.filter(u => u.status === 'inactive' && u.role === 'member').length }); // Find users who have received 3+ reminders (may need personal outreach) @@ -76,52 +80,42 @@ const AdminDashboard = () => {
{/* Stats Grid */} -
- -
-

- {loading ? '-' : stats.totalMembers} -

-
-
- -
-
-
-

Total Members

-
+
+
+ Quick Overview +
+
+ + + + - -
-

- {loading ? '-' : stats.pendingValidations} -

-
-
- -
-
- -
- -

Pending Validations

-
- - -
-

- {loading ? '-' : stats.activeMembers} -

-
-
- -
-
- -
-

Active Members

-
+
{/* Quick Actions */} diff --git a/src/pages/admin/AdminMembers.js b/src/pages/admin/AdminMembers.js index 1091f80..39614ba 100644 --- a/src/pages/admin/AdminMembers.js +++ b/src/pages/admin/AdminMembers.js @@ -14,12 +14,13 @@ import { DropdownMenuTrigger, } from '../../components/ui/dropdown-menu'; import { toast } from 'sonner'; -import { Users, Search, User, CreditCard, Eye, CheckCircle, Calendar, AlertCircle, Clock, Mail, UserPlus, Upload, Download, FileDown, ChevronDown } from 'lucide-react'; +import { Users, Search, User, CreditCard, Eye, CheckCircle, Calendar, AlertCircle, Clock, Mail, UserPlus, Upload, Download, FileDown, ChevronDown, CircleMinus } from 'lucide-react'; import PaymentActivationDialog from '../../components/PaymentActivationDialog'; import ConfirmationDialog from '../../components/ConfirmationDialog'; import CreateMemberDialog from '../../components/CreateMemberDialog'; import InviteStaffDialog from '../../components/InviteStaffDialog'; import WordPressImportWizard from '../../components/WordPressImportWizard'; +import { StatCard } from '@/components/StatCard'; const AdminMembers = () => { const navigate = useNavigate(); @@ -319,31 +320,45 @@ const AdminMembers = () => {
{/* Stats */} -
- -

Total Members

-

- {users.length} -

-
- -

Active

-

- {users.filter(u => u.status === 'active').length} -

-
- -

Payment Pending

-

- {users.filter(u => u.status === 'payment_pending').length} -

-
- -

Inactive

-

- {users.filter(u => u.status === 'inactive').length} -

-
+
+
+ Quick Overview +
+
+ + + + u.status === 'active').length} + icon={CheckCircle} + iconBgClass="text-[var(--green-light)]" + dataTestId="stat-active-members" + /> + u.status === 'payment_pending').length} + icon={CreditCard} + iconBgClass="text-brand-light-orange" + dataTestId="stat-payment-pending-members" + /> + u.status === 'inactive').length} + icon={CircleMinus} + iconBgClass=" text-brand-pink" + dataTestId="stat-inactive-members" + /> + + + +
{/* Filters */} @@ -394,137 +409,137 @@ const AdminMembers = () => { data-testid={`member-card-${user.id}`} >
-
- {/* Avatar */} -
- {user.first_name?.[0]}{user.last_name?.[0]} +
+ {/* Avatar */} +
+ {user.first_name?.[0]}{user.last_name?.[0]} +
+ + {/* Info */} +
+
+

+ {user.first_name} {user.last_name} +

+ {getStatusBadge(user.status)} +
+
+

Email: {user.email}

+

Phone: {user.phone}

+

Joined: {joinedDate ? new Date(joinedDate).toLocaleDateString() : 'N/A'}

+ {user.referred_by_member_name && ( +

Referred by: {user.referred_by_member_name}

+ )} +
+ + {/* Reminder Info */} + {(() => { + const reminderInfo = getReminderInfo(user); + if (reminderInfo.totalReminders > 0) { + return ( +
+
+ + + {reminderInfo.totalReminders} reminder{reminderInfo.totalReminders !== 1 ? 's' : ''} sent + {reminderInfo.totalReminders >= 3 && ( + + Needs attention + + )} + +
+
+ {reminderInfo.emailReminders > 0 && ( +

+ + {reminderInfo.emailReminders} email verification +

+ )} + {reminderInfo.eventReminders > 0 && ( +

+ + {reminderInfo.eventReminders} event attendance +

+ )} + {reminderInfo.paymentReminders > 0 && ( +

+ + {reminderInfo.paymentReminders} payment +

+ )} + {reminderInfo.renewalReminders > 0 && ( +

+ + {reminderInfo.renewalReminders} renewal +

+ )} +
+ {reminderInfo.lastReminderAt && ( +

+ Last reminder: {new Date(reminderInfo.lastReminderAt).toLocaleDateString()} at {new Date(reminderInfo.lastReminderAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} +

+ )} +
+ ); + } + return null; + })()} +
- {/* Info */} -
-
-

- {user.first_name} {user.last_name} -

- {getStatusBadge(user.status)} -
-
-

Email: {user.email}

-

Phone: {user.phone}

-

Joined: {joinedDate ? new Date(joinedDate).toLocaleDateString() : 'N/A'}

- {user.referred_by_member_name && ( -

Referred by: {user.referred_by_member_name}

+ {/* Actions */} +
+
+ + + + + {/* Show Activate Payment button for payment_pending users */} + {user.status === 'payment_pending' && ( + )}
- {/* Reminder Info */} - {(() => { - const reminderInfo = getReminderInfo(user); - if (reminderInfo.totalReminders > 0) { - return ( -
-
- - - {reminderInfo.totalReminders} reminder{reminderInfo.totalReminders !== 1 ? 's' : ''} sent - {reminderInfo.totalReminders >= 3 && ( - - Needs attention - - )} - -
-
- {reminderInfo.emailReminders > 0 && ( -

- - {reminderInfo.emailReminders} email verification -

- )} - {reminderInfo.eventReminders > 0 && ( -

- - {reminderInfo.eventReminders} event attendance -

- )} - {reminderInfo.paymentReminders > 0 && ( -

- - {reminderInfo.paymentReminders} payment -

- )} - {reminderInfo.renewalReminders > 0 && ( -

- - {reminderInfo.renewalReminders} renewal -

- )} -
- {reminderInfo.lastReminderAt && ( -

- Last reminder: {new Date(reminderInfo.lastReminderAt).toLocaleDateString()} at {new Date(reminderInfo.lastReminderAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} -

- )} -
- ); - } - return null; - })()} + {/* Status Management */} +
+ + Change Status: + + +
- - {/* Actions */} -
-
- - - - - {/* Show Activate Payment button for payment_pending users */} - {user.status === 'payment_pending' && ( - - )} -
- - {/* Status Management */} -
- - Change Status: - - -
-
-
- + ); })}