import React, { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { authService, userService, membershipService, paymentService, eventService, User, Membership, Payment, Event, EventRSVP } from '../services/membershipService'; import MembershipSetup from '../components/MembershipSetup'; import ProfileMenu from '../components/ProfileMenu'; import ProfileEdit from '../components/ProfileEdit'; const Dashboard: React.FC = () => { const navigate = useNavigate(); const [user, setUser] = useState(null); const [memberships, setMemberships] = useState([]); const [payments, setPayments] = useState([]); const [allPayments, setAllPayments] = useState([]); const [allMemberships, setAllMemberships] = useState([]); const [allUsers, setAllUsers] = useState([]); const [loading, setLoading] = useState(true); const [showMembershipSetup, setShowMembershipSetup] = useState(false); const [showProfileEdit, setShowProfileEdit] = useState(false); const [searchTerm, setSearchTerm] = useState(''); const [selectedUser, setSelectedUser] = useState(null); const [showUserDetails, setShowUserDetails] = useState(false); const [isEditingUser, setIsEditingUser] = useState(false); const [editFormData, setEditFormData] = useState>({}); const [upcomingEvents, setUpcomingEvents] = useState([]); const [allEvents, setAllEvents] = useState([]); const [eventRSVPs, setEventRSVPs] = useState([]); const [eventRSVPCounts, setEventRSVPCounts] = useState<{[eventId: number]: {attending: number, maybe: number, not_attending: number}}>({}); const [rsvpLoading, setRsvpLoading] = useState<{[eventId: number]: boolean}>({}); const [showEventModal, setShowEventModal] = useState(false); const [editingEvent, setEditingEvent] = useState(null); const [eventFormData, setEventFormData] = useState({ title: '', description: '', event_date: '', event_time: '', location: '', max_attendees: '' }); useEffect(() => { if (!authService.isAuthenticated()) { navigate('/login'); return; } loadData(); }, []); const mergeRSVPStatus = (events: Event[], rsvps: EventRSVP[]): Event[] => { return events.map(event => { const rsvp = rsvps.find(r => r.event_id === event.id); return { ...event, rsvp_status: rsvp ? rsvp.status : undefined }; }); }; const loadData = async () => { try { const [userData, membershipData, paymentData] = await Promise.all([ userService.getCurrentUser(), membershipService.getMyMemberships(), paymentService.getMyPayments() ]); setUser(userData); setMemberships(membershipData); setPayments(paymentData); // Load upcoming events and user's RSVPs const [eventsData, rsvpsData] = await Promise.all([ eventService.getUpcomingEvents(), eventService.getMyRSVPs() ]); // Merge RSVP status with events const eventsWithRSVP = mergeRSVPStatus(eventsData, rsvpsData); setUpcomingEvents(eventsWithRSVP); // Load admin data if user is admin if (userData.role === 'admin' || userData.role === 'super_admin') { const [allPaymentsData, allMembershipsData, allUsersData, allEventsData] = await Promise.all([ paymentService.getAllPayments(), membershipService.getAllMemberships(), userService.getAllUsers(), eventService.getAllEvents() ]); setAllPayments(allPaymentsData); setAllMemberships(allMembershipsData); setAllUsers(allUsersData); setAllEvents(allEventsData); // Load RSVP counts for all events await loadEventRSVPCounts(allEventsData); } } catch (error) { console.error('Failed to load data:', error); } finally { setLoading(false); } }; const loadEventRSVPCounts = async (events: Event[]) => { const counts: {[eventId: number]: {attending: number, maybe: number, not_attending: number}} = {}; for (const event of events) { try { const rsvps = await eventService.getEventRSVPs(event.id); counts[event.id] = { attending: rsvps.filter(r => r.status === 'attending').length, maybe: rsvps.filter(r => r.status === 'maybe').length, not_attending: rsvps.filter(r => r.status === 'not_attending').length }; } catch (error) { console.error(`Failed to load RSVPs for event ${event.id}:`, error); counts[event.id] = { attending: 0, maybe: 0, not_attending: 0 }; } } setEventRSVPCounts(counts); }; const handleMembershipSetup = () => { setShowMembershipSetup(true); }; const handleMembershipCreated = () => { setShowMembershipSetup(false); loadData(); // Reload data to show the new membership }; const handleCancelMembershipSetup = () => { setShowMembershipSetup(false); }; const handleProfileEdit = () => { setShowProfileEdit(true); }; const handleProfileSave = (updatedUser: User) => { setUser(updatedUser); setShowProfileEdit(false); }; const handleProfileCancel = () => { setShowProfileEdit(false); }; const getUserName = (userId: number): string => { const user = allUsers.find(u => u.id === userId); return user ? `${user.first_name} ${user.last_name}` : `User #${userId}`; }; const handleApprovePayment = async (paymentId: number, membershipId?: number) => { try { // Approve the payment await paymentService.updatePayment(paymentId, { status: 'completed' }); // If there's an associated membership, activate it if (membershipId) { await membershipService.updateMembership(membershipId, { status: 'active' }); } // Reload data await loadData(); } catch (error) { console.error('Failed to approve payment:', error); alert('Failed to approve payment. Please try again.'); } }; const handleUpdateUserRole = async (userId: number, newRole: string) => { try { await userService.updateUser(userId, { role: newRole }); // Reload data to reflect changes await loadData(); } catch (error) { console.error('Failed to update user role:', error); alert('Failed to update user role. Please try again.'); } }; const getStatusClass = (status: string) => { switch (status.toLowerCase()) { case 'active': return 'status-active'; case 'pending': return 'status-pending'; case 'expired': case 'cancelled': return 'status-expired'; default: return ''; } }; const filteredUsers = allUsers.filter(user => { const fullName = `${user.first_name} ${user.last_name}`.toLowerCase(); const email = user.email.toLowerCase(); const search = searchTerm.toLowerCase(); return fullName.includes(search) || email.includes(search); }); const handleUserClick = (user: User) => { setSelectedUser(user); setEditFormData({ first_name: user.first_name, last_name: user.last_name, email: user.email, phone: user.phone || '', address: user.address || '' }); setShowUserDetails(true); setIsEditingUser(false); }; const handleCloseUserDetails = () => { setSelectedUser(null); setShowUserDetails(false); setIsEditingUser(false); setEditFormData({}); }; const handleEditUser = () => { setIsEditingUser(true); }; const handleCancelEdit = () => { setIsEditingUser(false); if (selectedUser) { setEditFormData({ first_name: selectedUser.first_name, last_name: selectedUser.last_name, email: selectedUser.email, phone: selectedUser.phone || '', address: selectedUser.address || '' }); } }; const handleFormChange = (field: string, value: string) => { setEditFormData(prev => ({ ...prev, [field]: value })); }; const handleSaveUser = async () => { if (!selectedUser) return; try { await userService.updateUser(selectedUser.id, editFormData); // Refresh data await loadData(); setIsEditingUser(false); // Update selected user with new data const updatedUser = allUsers.find(u => u.id === selectedUser.id); if (updatedUser) { setSelectedUser(updatedUser); } } catch (error) { console.error('Failed to update user:', error); alert('Failed to update user. Please try again.'); } }; const handleRSVP = async (eventId: number, status: 'attending' | 'not_attending' | 'maybe') => { // Set loading state for this event setRsvpLoading(prev => ({ ...prev, [eventId]: true })); // Optimistically update the UI setUpcomingEvents(prevEvents => prevEvents.map(event => event.id === eventId ? { ...event, rsvp_status: status } : event ) ); try { await eventService.createOrUpdateRSVP(eventId, { status }); // Reload RSVPs and merge with events to get the latest data const [eventsData, rsvpsData] = await Promise.all([ eventService.getUpcomingEvents(), eventService.getMyRSVPs() ]); const eventsWithRSVP = mergeRSVPStatus(eventsData, rsvpsData); setUpcomingEvents(eventsWithRSVP); } catch (error) { console.error('Failed to update RSVP:', error); alert('Failed to update RSVP. Please try again.'); // Revert optimistic update on error const [eventsData, rsvpsData] = await Promise.all([ eventService.getUpcomingEvents(), eventService.getMyRSVPs() ]); const eventsWithRSVP = mergeRSVPStatus(eventsData, rsvpsData); setUpcomingEvents(eventsWithRSVP); } finally { // Clear loading state setRsvpLoading(prev => ({ ...prev, [eventId]: false })); } }; const handlePublishEvent = async (eventId: number) => { try { await eventService.updateEvent(eventId, { status: 'published' }); // Reload events to reflect the change const eventsData = await eventService.getAllEvents(); setAllEvents(eventsData); // Reload RSVP counts await loadEventRSVPCounts(eventsData); } catch (error) { console.error('Failed to publish event:', error); alert('Failed to publish event. Please try again.'); } }; const handleCancelEvent = async (eventId: number) => { if (!confirm('Are you sure you want to cancel this event?')) { return; } try { await eventService.updateEvent(eventId, { status: 'cancelled' }); // Reload events to reflect the change const eventsData = await eventService.getAllEvents(); setAllEvents(eventsData); // Reload RSVP counts await loadEventRSVPCounts(eventsData); } catch (error) { console.error('Failed to cancel event:', error); alert('Failed to cancel event. Please try again.'); } }; const handleCreateEvent = () => { setEditingEvent(null); setEventFormData({ title: '', description: '', event_date: '', event_time: '', location: '', max_attendees: '' }); setShowEventModal(true); }; const handleEditEvent = (event: Event) => { setEditingEvent(event); // Convert event_date to YYYY-MM-DD format for date input const dateObj = new Date(event.event_date); const formattedDate = dateObj.toISOString().split('T')[0]; setEventFormData({ title: event.title, description: event.description || '', event_date: formattedDate, event_time: event.event_time || '', location: event.location || '', max_attendees: event.max_attendees?.toString() || '' }); setShowEventModal(true); }; const handleEventFormChange = (field: string, value: string) => { setEventFormData(prev => ({ ...prev, [field]: value })); }; const handleSaveEvent = async () => { try { const eventData = { title: eventFormData.title, description: eventFormData.description || undefined, event_date: eventFormData.event_date, event_time: eventFormData.event_time || undefined, location: eventFormData.location || undefined, max_attendees: eventFormData.max_attendees ? parseInt(eventFormData.max_attendees) : undefined }; if (editingEvent) { // Update existing event await eventService.updateEvent(editingEvent.id, eventData); } else { // Create new event await eventService.createEvent(eventData); } // Reload events const eventsData = await eventService.getAllEvents(); setAllEvents(eventsData); await loadEventRSVPCounts(eventsData); // Close modal setShowEventModal(false); setEditingEvent(null); } catch (error) { console.error('Failed to save event:', error); alert('Failed to save event. Please try again.'); } }; const handleCloseEventModal = () => { setShowEventModal(false); setEditingEvent(null); }; const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' }); }; if (loading) { return
Loading...
; } if (showMembershipSetup) { return ( <>
); } const activeMembership = memberships.find(m => m.status === 'active') || memberships[0]; return ( <>

Welcome, {user?.first_name}!

{/* Membership Card */} {activeMembership ? (

Your Membership

{activeMembership.tier.name}

Membership Number: {activeMembership.id}

Status: {activeMembership.status.toUpperCase()}

Annual Fee: £{activeMembership.tier.annual_fee.toFixed(2)}

Valid From: {formatDate(activeMembership.start_date)}

Valid Until: {formatDate(activeMembership.end_date)}

Auto Renew: {activeMembership.auto_renew ? 'Yes' : 'No'}

Benefits:

{activeMembership.tier.benefits}

) : (

Set Up Your Membership

Choose from our membership tiers to get started with SASA benefits.

Available tiers include Personal, Aircraft Owners, and Corporate memberships.

)} {/* Upcoming Events */}

Upcoming Events

{upcomingEvents.length > 0 ? (
{upcomingEvents.map(event => (

{event.title}

{formatDate(event.event_date)} at {event.event_time}

{event.location && (

📍 {event.location}

)}
{event.description && (

{event.description}

)} {event.rsvp_status && (
Your RSVP: {event.rsvp_status.replace('_', ' ')}
)}
))}
) : (

No upcoming events at this time.

)}
{/* Payment History */}

Payment History

{payments.length > 0 ? (
{payments.map(payment => ( ))}
Date Amount Method Status
{payment.payment_date ? formatDate(payment.payment_date) : 'Pending'} £{payment.amount.toFixed(2)} {payment.payment_method} {payment.status.toUpperCase()}
) : (

No payment history available.

)}
{/* Admin Section */} {(user?.role === 'admin' || user?.role === 'super_admin') && (

Admin Panel - Pending Approvals

{/* Pending Payments */} {allPayments.filter(p => p.status === 'pending').length > 0 && (

Pending Payments

{allPayments.filter(p => p.status === 'pending').map(payment => { const membership = allMemberships.find(m => m.id === payment.membership_id); return ( ); })}
User Amount Method Membership Actions
{getUserName(payment.user_id)} £{payment.amount.toFixed(2)} {payment.payment_method} {membership ? `${membership.tier.name} (${membership.status})` : 'N/A'}
)} {/* Pending Memberships */} {allMemberships.filter(m => m.status === 'pending').length > 0 && (

Pending Memberships

{allMemberships.filter(m => m.status === 'pending').map(membership => ( ))}
User Tier Start Date Status
{getUserName(membership.user_id)} {membership.tier.name} {formatDate(membership.start_date)} {membership.status.toUpperCase()}
)} {allPayments.filter(p => p.status === 'pending').length === 0 && allMemberships.filter(m => m.status === 'pending').length === 0 && (

No pending approvals at this time.

)}
)} {/* User Management Section */} {(user?.role === 'admin' || user?.role === 'super_admin') && (

User Management

{/* Search Input */}
setSearchTerm(e.target.value)} style={{ width: '100%', padding: '8px 12px', border: '1px solid #ddd', borderRadius: '4px', fontSize: '14px' }} />
{filteredUsers.map(u => { const userMembership = allMemberships.find(m => m.user_id === u.id && m.status === 'active'); return ( handleUserClick(u)} > ); })}
Name Email Membership # Role Status Joined Actions
{u.first_name} {u.last_name} {u.email} {userMembership ? userMembership.id : 'N/A'} {u.role.toUpperCase()} {u.is_active ? 'ACTIVE' : 'INACTIVE'} {formatDate(u.created_at)} {u.role === 'member' && ( )} {u.role === 'admin' && u.id !== user?.id && ( )} {u.role === 'super_admin' && ( Super Admin )}
)}
{showProfileEdit && user && ( )} {/* Event Management Section for Admins */} {(user?.role === 'admin' || user?.role === 'super_admin') && (

Event Management

{/* Create New Event Button */}
{/* Events List */}
{allEvents.map(event => ( ))}
Event Date & Time Location Status RSVPs Actions
{event.title} {event.description && (
{event.description}
)}
{formatDate(event.event_date)}
{event.event_time}
{event.location || 'TBD'} {event.status.toUpperCase()} {eventRSVPCounts[event.id] ? (
Attending: {eventRSVPCounts[event.id].attending}
Maybe: {eventRSVPCounts[event.id].maybe}
Not: {eventRSVPCounts[event.id].not_attending}
) : ( Loading... )}
{event.status === 'draft' && ( )} {event.status === 'published' && ( )}
{allEvents.length === 0 && (

No events created yet.

)}
)} {/* User Details Modal */} {showUserDetails && selectedUser && (

User Details

{!isEditingUser && (user?.role === 'admin' || user?.role === 'super_admin') && ( )}
{/* User Profile */}

Profile Information

{isEditingUser ? (
handleFormChange('first_name', e.target.value)} style={{ padding: '6px 8px', border: '1px solid #ddd', borderRadius: '4px', fontSize: '14px' }} />
handleFormChange('last_name', e.target.value)} style={{ padding: '6px 8px', border: '1px solid #ddd', borderRadius: '4px', fontSize: '14px' }} />
handleFormChange('email', e.target.value)} style={{ padding: '6px 8px', border: '1px solid #ddd', borderRadius: '4px', fontSize: '14px' }} />
handleFormChange('phone', e.target.value)} style={{ padding: '6px 8px', border: '1px solid #ddd', borderRadius: '4px', fontSize: '14px' }} />