Implement chain submissions API and update frontend to utilize new endpoint; enhance submissions page with feature flag for API selection, participant progress display, and improved filtering logic.

This commit is contained in:
2025-12-13 20:32:23 +03:00
parent 18e2ccb6bc
commit 04836ea6ce
5 changed files with 286 additions and 44 deletions

View File

@@ -461,4 +461,116 @@ router.get('/challenge/user/:userId/submissions', (req, res) => {
respond(res, filtered);
});
// GET /api/challenge/chain/:chainId/submissions
router.get('/challenge/chain/:chainId/submissions', (req, res) => {
const chains = getChains();
const submissions = getSubmissions();
const users = getUsers();
const chainId = req.params.chainId;
const userId = req.query.userId;
const status = req.query.status;
const limit = parseInt(req.query.limit) || 100;
const offset = parseInt(req.query.offset) || 0;
// Найти цепочку
const chain = chains.find(c => c.id === chainId);
if (!chain) {
return respondError(res, 'Chain not found', 404);
}
// Получить taskIds из цепочки
const taskIds = new Set(chain.tasks.map(t => t.id));
// Фильтровать submissions по taskIds цепочки
let filteredSubmissions = submissions.filter(s => {
const taskId = typeof s.task === 'object' ? s.task.id : s.task;
return taskIds.has(taskId);
});
// Применить фильтр по userId если указан
if (userId) {
filteredSubmissions = filteredSubmissions.filter(s => {
const subUserId = typeof s.user === 'object' ? s.user.id : s.user;
return subUserId === userId;
});
}
// Применить фильтр по status если указан
if (status) {
filteredSubmissions = filteredSubmissions.filter(s => s.status === status);
}
// Получить уникальных участников
const participantMap = new Map();
filteredSubmissions.forEach(sub => {
const subUserId = typeof sub.user === 'object' ? sub.user.id : sub.user;
const subUserNickname = typeof sub.user === 'object' ? sub.user.nickname : '';
// Найти nickname если не заполнен
let nickname = subUserNickname;
if (!nickname) {
const user = users.find(u => u.id === subUserId);
nickname = user ? user.nickname : subUserId;
}
if (!participantMap.has(subUserId)) {
participantMap.set(subUserId, {
userId: subUserId,
nickname: nickname,
completedTasks: new Set(),
totalTasks: chain.tasks.length,
});
}
// Если статус accepted, добавляем taskId в completedTasks
if (sub.status === 'accepted') {
const taskId = typeof sub.task === 'object' ? sub.task.id : sub.task;
participantMap.get(subUserId).completedTasks.add(taskId);
}
});
// Преобразовать в массив и рассчитать прогресс
const participants = Array.from(participantMap.values()).map(p => ({
userId: p.userId,
nickname: p.nickname,
completedTasks: p.completedTasks.size,
totalTasks: p.totalTasks,
progressPercent: p.totalTasks > 0
? Math.round((p.completedTasks.size / p.totalTasks) * 100)
: 0,
}));
// Сортировать submissions по дате (новые сначала)
filteredSubmissions.sort((a, b) =>
new Date(b.submittedAt) - new Date(a.submittedAt)
);
// Применить пагинацию
const total = filteredSubmissions.length;
const paginatedSubmissions = filteredSubmissions.slice(offset, offset + limit);
// Формируем ответ
const response = {
chain: {
id: chain.id,
name: chain.name,
tasks: chain.tasks.map(t => ({
id: t.id,
title: t.title,
})),
},
participants: participants,
submissions: paginatedSubmissions,
pagination: {
total: total,
limit: limit,
offset: offset,
},
};
respond(res, response);
});
module.exports = router;