Improve event list layout: prioritize upcoming events and compact card design
- Sort events with upcoming first, then completed/cancelled at bottom - Redesign event cards with horizontal layout to reduce vertical space - Move event details (date, time, duration, location, participants) into single row - Improve mobile responsiveness with flex-wrap - Keep quick register button visible for upcoming events
This commit is contained in:
@@ -85,30 +85,41 @@ export function EventList({ events, onEventClick, onRegister, isLoading = false
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-3">
|
||||
{events.map((event) => (
|
||||
<div
|
||||
key={event.id}
|
||||
className="bg-white rounded-lg shadow hover:shadow-lg transition-all cursor-pointer border-2 border-transparent hover:border-blue-200"
|
||||
onClick={() => onEventClick(event)}
|
||||
>
|
||||
<div className="p-6">
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-1">{event.title}</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-4">
|
||||
{/* Header Row */}
|
||||
<div className="flex items-start justify-between gap-4 mb-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h3 className="text-lg font-semibold text-gray-900 truncate">{event.title}</h3>
|
||||
{getStatusBadge(event.status)}
|
||||
<span className="text-sm text-gray-500">
|
||||
{event.participantCount} {event.participantCount === 1 ? 'participant' : 'participants'}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 line-clamp-1">{event.description}</p>
|
||||
</div>
|
||||
{event.status === 'upcoming' && onRegister && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onRegister(event.id);
|
||||
}}
|
||||
className="bg-blue-600 text-white py-2 px-4 rounded-lg hover:bg-blue-700 transition-colors font-medium text-sm whitespace-nowrap"
|
||||
>
|
||||
Quick Register
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 mb-4">
|
||||
{/* Info Row - Horizontal Layout */}
|
||||
<div className="flex flex-wrap items-center gap-x-6 gap-y-2 text-sm text-gray-600">
|
||||
{/* Date and Time */}
|
||||
<div className="flex items-center text-sm text-gray-600">
|
||||
<svg className="h-5 w-5 mr-2 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<div className="flex items-center">
|
||||
<svg className="h-4 w-4 mr-1.5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
@@ -116,14 +127,14 @@ export function EventList({ events, onEventClick, onRegister, isLoading = false
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
<span className="whitespace-nowrap">
|
||||
{formatEventDate(event.scheduledDate)} at {formatEventTime(event.scheduledDate)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Duration */}
|
||||
<div className="flex items-center text-sm text-gray-600">
|
||||
<svg className="h-5 w-5 mr-2 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<div className="flex items-center">
|
||||
<svg className="h-4 w-4 mr-1.5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
@@ -135,8 +146,8 @@ export function EventList({ events, onEventClick, onRegister, isLoading = false
|
||||
</div>
|
||||
|
||||
{/* Location */}
|
||||
<div className="flex items-center text-sm text-gray-600">
|
||||
<svg className="h-5 w-5 mr-2 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<div className="flex items-center min-w-0 flex-1">
|
||||
<svg className="h-4 w-4 mr-1.5 text-gray-400 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
@@ -145,52 +156,46 @@ export function EventList({ events, onEventClick, onRegister, isLoading = false
|
||||
/>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
<span>{event.locationName}</span>
|
||||
<span className="truncate">{event.locationName}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Description Preview */}
|
||||
<p className="text-sm text-gray-600 line-clamp-2 mb-4">{event.description}</p>
|
||||
{/* Participants */}
|
||||
<div className="flex items-center">
|
||||
<svg className="h-4 w-4 mr-1.5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
|
||||
/>
|
||||
</svg>
|
||||
<span>{event.participantCount} {event.participantCount === 1 ? 'participant' : 'participants'}</span>
|
||||
</div>
|
||||
|
||||
{/* Completed Event Info */}
|
||||
{event.status === 'completed' && event.litterCollected !== undefined && (
|
||||
<div className="bg-green-50 border border-green-200 rounded-lg p-3 mb-4">
|
||||
<div className="flex items-center text-sm text-green-800">
|
||||
<svg className="h-5 w-5 mr-2 text-green-600" fill="currentColor" viewBox="0 0 20 20">
|
||||
{/* Completed Event Info */}
|
||||
{event.status === 'completed' && event.litterCollected !== undefined && (
|
||||
<div className="flex items-center text-green-700 font-medium">
|
||||
<svg className="h-4 w-4 mr-1.5 text-green-600" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span className="font-medium">{event.litterCollected} kg of litter collected</span>
|
||||
<span>{event.litterCollected} kg collected</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* Action Area */}
|
||||
<div className="flex items-center justify-between pt-4 border-t border-gray-100">
|
||||
{/* View Details Link */}
|
||||
<button
|
||||
onClick={() => onEventClick(event)}
|
||||
className="text-blue-600 hover:text-blue-700 font-medium text-sm flex items-center gap-1"
|
||||
className="text-blue-600 hover:text-blue-700 font-medium flex items-center gap-1 ml-auto"
|
||||
>
|
||||
View Details
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{event.status === 'upcoming' && onRegister && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onRegister(event.id);
|
||||
}}
|
||||
className="bg-blue-600 text-white py-2 px-4 rounded-lg hover:bg-blue-700 transition-colors font-medium text-sm"
|
||||
>
|
||||
Quick Register
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -75,8 +75,18 @@ export function EventsPage() {
|
||||
filtered = filtered.filter(event => new Date(event.scheduledDate) <= endDate);
|
||||
}
|
||||
|
||||
// Sort by date (upcoming first, then by date)
|
||||
// Sort: upcoming events first (by date), then completed/cancelled at bottom (by date)
|
||||
filtered.sort((a, b) => {
|
||||
// Priority order: upcoming > completed > cancelled
|
||||
const statusPriority = { upcoming: 0, completed: 1, cancelled: 2 };
|
||||
const priorityA = statusPriority[a.status];
|
||||
const priorityB = statusPriority[b.status];
|
||||
|
||||
if (priorityA !== priorityB) {
|
||||
return priorityA - priorityB;
|
||||
}
|
||||
|
||||
// Within same status, sort by date
|
||||
const dateA = new Date(a.scheduledDate).getTime();
|
||||
const dateB = new Date(b.scheduledDate).getTime();
|
||||
return dateA - dateB;
|
||||
|
||||
Reference in New Issue
Block a user