Add total litter collected display to user profile

- Updated backend userService to calculate total litter collected across all attended events
- Added totalLitterCollected field to UserProfile interface
- Added new statistics card showing total litter collected in kg
- Updated profile grid layout to accommodate 5 statistics cards
- Displays litter collected with 1 decimal place precision
This commit is contained in:
2025-11-18 19:03:43 +00:00
parent 3d7b84f376
commit 3a6db92699
22 changed files with 1462 additions and 128 deletions

View File

@@ -405,4 +405,133 @@ export const eventController = {
});
}
},
// Get event attendees
async getEventAttendees(req: AuthRequest, res: Response) {
try {
const { id } = req.params;
const attendees = await eventService.getEventAttendees(id);
res.json(attendees);
} catch (error: any) {
console.error('Error fetching event attendees:', error);
res.status(500).json({
code: 'FETCH_ATTENDEES_ERROR',
message: 'Failed to fetch event attendees',
details: error.message,
timestamp: new Date().toISOString(),
});
}
},
// Update attendee status
async updateAttendeeStatus(req: AuthRequest, res: Response) {
try {
const clerkId = req.userId;
if (!clerkId) {
return res.status(401).json({
code: 'UNAUTHORIZED',
message: 'User not authenticated',
timestamp: new Date().toISOString(),
});
}
const userId = await findOrCreateUser(clerkId);
const { id, attendeeId } = req.params;
const event = await eventService.getEventById(id);
if (!event) {
return res.status(404).json({
code: 'EVENT_NOT_FOUND',
message: 'Event not found',
timestamp: new Date().toISOString(),
});
}
// Check if user is the organizer
if (event.organizerId !== userId) {
return res.status(403).json({
code: 'FORBIDDEN',
message: 'Only the event organizer can update attendee status',
timestamp: new Date().toISOString(),
});
}
const { attended, litterCollected, contributionNote } = req.body;
const updatedAttendee = await eventService.updateAttendeeStatus(id, attendeeId, {
attended,
litterCollected: litterCollected !== undefined ? parseFloat(litterCollected) : undefined,
contributionNote,
});
res.json(updatedAttendee);
} catch (error: any) {
console.error('Error updating attendee status:', error);
res.status(500).json({
code: 'UPDATE_ATTENDEE_ERROR',
message: 'Failed to update attendee status',
details: error.message,
timestamp: new Date().toISOString(),
});
}
},
// Bulk update attendees
async bulkUpdateAttendees(req: AuthRequest, res: Response) {
try {
const clerkId = req.userId;
if (!clerkId) {
return res.status(401).json({
code: 'UNAUTHORIZED',
message: 'User not authenticated',
timestamp: new Date().toISOString(),
});
}
const userId = await findOrCreateUser(clerkId);
const { id } = req.params;
const event = await eventService.getEventById(id);
if (!event) {
return res.status(404).json({
code: 'EVENT_NOT_FOUND',
message: 'Event not found',
timestamp: new Date().toISOString(),
});
}
// Check if user is the organizer
if (event.organizerId !== userId) {
return res.status(403).json({
code: 'FORBIDDEN',
message: 'Only the event organizer can update attendees',
timestamp: new Date().toISOString(),
});
}
const { attendees } = req.body;
if (!Array.isArray(attendees)) {
return res.status(400).json({
code: 'VALIDATION_ERROR',
message: 'attendees must be an array',
timestamp: new Date().toISOString(),
});
}
await eventService.bulkUpdateAttendees(id, attendees);
// Fetch updated attendees
const updatedAttendees = await eventService.getEventAttendees(id);
res.json(updatedAttendees);
} catch (error: any) {
console.error('Error bulk updating attendees:', error);
res.status(500).json({
code: 'BULK_UPDATE_ATTENDEES_ERROR',
message: 'Failed to bulk update attendees',
details: error.message,
timestamp: new Date().toISOString(),
});
}
},
};