feat(dashboard): add online count for events
Show real-time count of users currently in each event chat room. - Backend: Export getEventsOnlineCounts from socket module - Dashboard API: Include onlineCount for each active event - Frontend: Display online count with animated green dot indicator
This commit is contained in:
@@ -7,6 +7,7 @@ const express = require('express');
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { authenticate } = require('../middleware/auth');
|
const { authenticate } = require('../middleware/auth');
|
||||||
const { PrismaClient } = require('@prisma/client');
|
const { PrismaClient } = require('@prisma/client');
|
||||||
|
const { getEventsOnlineCounts } = require('../socket');
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
@@ -38,6 +39,15 @@ router.get('/', authenticate, async (req, res, next) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Get online counts for all events (safely handle if socket not initialized)
|
||||||
|
let onlineCounts = {};
|
||||||
|
try {
|
||||||
|
const eventIds = eventParticipants.map(ep => ep.event.id);
|
||||||
|
onlineCounts = getEventsOnlineCounts(eventIds);
|
||||||
|
} catch (_) {
|
||||||
|
// Socket may not be initialized (e.g., during tests)
|
||||||
|
}
|
||||||
|
|
||||||
// Get user's heats for each event
|
// Get user's heats for each event
|
||||||
const activeEvents = await Promise.all(
|
const activeEvents = await Promise.all(
|
||||||
eventParticipants.map(async (ep) => {
|
eventParticipants.map(async (ep) => {
|
||||||
@@ -72,6 +82,7 @@ router.get('/', authenticate, async (req, res, next) => {
|
|||||||
startDate: ep.event.startDate,
|
startDate: ep.event.startDate,
|
||||||
endDate: ep.event.endDate,
|
endDate: ep.event.endDate,
|
||||||
participantsCount: ep.event.participantsCount,
|
participantsCount: ep.event.participantsCount,
|
||||||
|
onlineCount: onlineCounts[ep.event.id] || 0,
|
||||||
myHeats: heats.map((h) => ({
|
myHeats: heats.map((h) => ({
|
||||||
id: h.id,
|
id: h.id,
|
||||||
competitionType: h.competitionType,
|
competitionType: h.competitionType,
|
||||||
|
|||||||
@@ -461,4 +461,21 @@ function initializeSocket(httpServer) {
|
|||||||
return io;
|
return io;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { initializeSocket, getIO };
|
// Get count of online users for a specific event
|
||||||
|
function getEventOnlineCount(eventId) {
|
||||||
|
if (!activeUsers.has(eventId)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return activeUsers.get(eventId).size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get online counts for multiple events
|
||||||
|
function getEventsOnlineCounts(eventIds) {
|
||||||
|
const counts = {};
|
||||||
|
for (const eventId of eventIds) {
|
||||||
|
counts[eventId] = getEventOnlineCount(eventId);
|
||||||
|
}
|
||||||
|
return counts;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { initializeSocket, getIO, getEventOnlineCount, getEventsOnlineCounts };
|
||||||
|
|||||||
@@ -326,6 +326,12 @@ const EventCard = ({ event }) => {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Users className="w-4 h-4 flex-shrink-0" />
|
<Users className="w-4 h-4 flex-shrink-0" />
|
||||||
<span>{event.participantsCount} participants</span>
|
<span>{event.participantsCount} participants</span>
|
||||||
|
{event.onlineCount > 0 && (
|
||||||
|
<span className="text-green-600 flex items-center gap-1">
|
||||||
|
<span className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
|
||||||
|
{event.onlineCount} online
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,59 @@ describe('DashboardPage', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should display online count when users are online', async () => {
|
||||||
|
dashboardAPI.getData.mockResolvedValue({
|
||||||
|
activeEvents: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
slug: 'test-event',
|
||||||
|
name: 'Test Event',
|
||||||
|
location: 'Warsaw',
|
||||||
|
startDate: '2025-12-01',
|
||||||
|
endDate: '2025-12-03',
|
||||||
|
participantsCount: 50,
|
||||||
|
onlineCount: 5,
|
||||||
|
myHeats: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
activeMatches: [],
|
||||||
|
matchRequests: { incoming: [], outgoing: [] },
|
||||||
|
});
|
||||||
|
|
||||||
|
renderWithRouter(<DashboardPage />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('5 online')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not display online count when zero', async () => {
|
||||||
|
dashboardAPI.getData.mockResolvedValue({
|
||||||
|
activeEvents: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
slug: 'test-event',
|
||||||
|
name: 'Test Event',
|
||||||
|
location: 'Warsaw',
|
||||||
|
startDate: '2025-12-01',
|
||||||
|
endDate: '2025-12-03',
|
||||||
|
participantsCount: 50,
|
||||||
|
onlineCount: 0,
|
||||||
|
myHeats: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
activeMatches: [],
|
||||||
|
matchRequests: { incoming: [], outgoing: [] },
|
||||||
|
});
|
||||||
|
|
||||||
|
renderWithRouter(<DashboardPage />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Test Event')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
expect(screen.queryByText(/online/)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it('should navigate to event chat when clicking Enter Chat', async () => {
|
it('should navigate to event chat when clicking Enter Chat', async () => {
|
||||||
dashboardAPI.getData.mockResolvedValue({
|
dashboardAPI.getData.mockResolvedValue({
|
||||||
activeEvents: [
|
activeEvents: [
|
||||||
|
|||||||
Reference in New Issue
Block a user