diff --git a/src/components/dashboard/Officer_EventManagement/Attendees.tsx b/src/components/dashboard/Officer_EventManagement/Attendees.tsx index db5882e..b3c50e9 100644 --- a/src/components/dashboard/Officer_EventManagement/Attendees.tsx +++ b/src/components/dashboard/Officer_EventManagement/Attendees.tsx @@ -2,6 +2,39 @@ import { useEffect, useState } from 'react'; import { Get } from '../../pocketbase/Get'; import { Authentication } from '../../pocketbase/Authentication'; +// Add HighlightText component +const HighlightText = ({ text, searchTerms }: { text: string | number | null | undefined, searchTerms: string[] }) => { + // Convert input to string and handle null/undefined + const textStr = String(text ?? ''); + + if (!searchTerms.length || !textStr) return <>{textStr}; + + try { + const escapedTerms = searchTerms.map(term => + term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + ); + const parts = textStr.split(new RegExp(`(${escapedTerms.join('|')})`, 'gi')); + + return ( + <> + {parts.map((part, i) => { + const isMatch = searchTerms.some(term => + part.toLowerCase().includes(term.toLowerCase()) + ); + return isMatch ? ( + {part} + ) : ( + {part} + ); + })} + + ); + } catch (error) { + console.error('Error in HighlightText:', error); + return <>{textStr}; + } +}; + interface AttendeeEntry { user_id: string; time_checked_in: string; @@ -12,19 +45,29 @@ interface User { id: string; name: string; email: string; + pid: string; + member_id: string; + member_type: string; + graduation_year: string; + major: string; } interface Event { id: string; + event_name: string; attendees: AttendeeEntry[]; } export default function Attendees() { const [eventId, setEventId] = useState(''); + const [eventName, setEventName] = useState(''); const [users, setUsers] = useState>(new Map()); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [attendeesList, setAttendeesList] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); + const [filteredAttendees, setFilteredAttendees] = useState([]); + const [processedSearchTerms, setProcessedSearchTerms] = useState([]); const get = Get.getInstance(); const auth = Authentication.getInstance(); @@ -34,6 +77,7 @@ export default function Attendees() { const handleUpdateAttendees = (e: CustomEvent<{ eventId: string; eventName: string }>) => { console.log('Received updateAttendees event:', e.detail); setEventId(e.detail.eventId); + setEventName(e.detail.eventName); }; // Add event listener @@ -45,6 +89,92 @@ export default function Attendees() { }; }, []); + // Filter attendees when search term or attendees list changes + useEffect(() => { + if (!searchTerm.trim()) { + setFilteredAttendees(attendeesList); + setProcessedSearchTerms([]); + return; + } + + const terms = searchTerm.toLowerCase().split(/\s+/).filter(Boolean); + setProcessedSearchTerms(terms); + + const filtered = attendeesList.filter(attendee => { + const user = users.get(attendee.user_id); + if (!user) return false; + + const searchableValues = [ + user.name, + user.email, + user.pid, + user.member_id, + user.member_type, + user.graduation_year, + user.major, + attendee.food, + new Date(attendee.time_checked_in).toLocaleString(), + ].map(value => (value || '').toString().toLowerCase()); + + return terms.every(term => + searchableValues.some(value => value.includes(term)) + ); + }); + + setFilteredAttendees(filtered); + }, [searchTerm, attendeesList, users]); + + // Function to download attendees as CSV + const downloadAttendeesCSV = () => { + // Create CSV headers + const headers = [ + 'Name', + 'Email', + 'PID', + 'Member ID', + 'Member Type', + 'Graduation Year', + 'Major', + 'Check-in Time', + 'Food Choice' + ]; + + // Create CSV rows + const rows = attendeesList.map(attendee => { + const user = users.get(attendee.user_id); + const checkInTime = new Date(attendee.time_checked_in).toLocaleString(); + return [ + user?.name || 'Unknown User', + user?.email || 'N/A', + user?.pid || 'N/A', + user?.member_id || 'N/A', + user?.member_type || 'N/A', + user?.graduation_year || 'N/A', + user?.major || 'N/A', + checkInTime, + attendee.food || 'N/A' + ]; + }); + + // Combine headers and rows + const csvContent = [ + headers.join(','), + ...rows.map(row => row.map(cell => `"${cell}"`).join(',')) + ].join('\n'); + + // Create blob and download + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + const link = document.createElement('a'); + const url = URL.createObjectURL(blob); + + link.setAttribute('href', url); + link.setAttribute('download', `${eventName.replace(/[^a-z0-9]/gi, '_').toLowerCase()}_attendees.csv`); + link.style.visibility = 'hidden'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + // Fetch event data when eventId changes useEffect(() => { const fetchEventData = async () => { @@ -168,35 +298,88 @@ export default function Attendees() { } return ( -
-
- Total Attendees: {attendeesList.length} -
- - - - - - - - - - - {attendeesList.map((attendee, index) => { - const user = users.get(attendee.user_id); - const checkInTime = new Date(attendee.time_checked_in).toLocaleString(); +
+
+ {/* Search and Actions Row */} +
+
+
+
+ + + +
+ setSearchTerm(e.target.value)} + /> +
+
+ +
- return ( -
- - - - - - ); - })} - -
NameEmailCheck-in TimeFood Choice
{user?.name || 'Unknown User'}{user?.email || 'N/A'}{checkInTime}{attendee.food || 'N/A'}
+ {/* Stats Row */} +
+
+ Total Attendees: {attendeesList.length} +
+ {searchTerm && ( +
+ Showing: {filteredAttendees.length} matches +
+ )} +
+
+ + {/* Updated table with highlighting */} +
+ + + + + + + + + + + + + + + + {filteredAttendees.map((attendee, index) => { + const user = users.get(attendee.user_id); + const checkInTime = new Date(attendee.time_checked_in).toLocaleString(); + + return ( + + + + + + + + + + + + ); + })} + +
NameEmailPIDMember IDMember TypeGraduation YearMajorCheck-in TimeFood Choice
+
); }