Add summary statistics endpoint and UI integration

- Introduced a new API endpoint `GET /stats/summary` to retrieve detailed smoking statistics for users, including daily and global averages.
- Updated the API client to support fetching summary statistics.
- Enhanced the statistics page with a new tab for summary statistics, featuring key metrics and visualizations for user comparison.
- Implemented error handling and loading states for the summary statistics fetch operation.
- Refactored the statistics page to separate daily and summary statistics into distinct components for improved organization and readability.
This commit is contained in:
Primakov Alexandr Alexandrovich
2025-11-17 14:30:40 +03:00
parent 71ee0c1c0e
commit 34e994478e
5 changed files with 831 additions and 141 deletions

View File

@@ -5,9 +5,16 @@ const timer = (time = 300) => (req, res, next) => setTimeout(next, time);
router.use(timer());
// In-memory storage for demo
const users = [];
const users = [
{
id: '1',
login: 'testuser',
password: 'test1234',
created: new Date('2024-01-01T00:00:00.000Z').toISOString()
}
];
const cigarettes = [];
let userIdCounter = 1;
let userIdCounter = 2;
let cigaretteIdCounter = 1;
// Simple token generation (for demo purposes only)
@@ -191,4 +198,108 @@ router.get('/stats/daily', authMiddleware, (req, res) => {
});
});
// GET /stats/summary
router.get('/stats/summary', authMiddleware, (req, res) => {
const { from, to } = req.query;
// Default: 30 days ago to now
const fromDate = from ? new Date(from) : new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const toDate = to ? new Date(to) : new Date();
// Helper function to get day name in Russian
const getDayName = (dayOfWeek) => {
const names = ['', 'Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота'];
return names[dayOfWeek];
};
// Helper function to calculate stats for a set of cigarettes
const calculateStats = (cigs) => {
// Daily stats
const dailyStats = {};
cigs.forEach(c => {
const date = c.smokedAt.split('T')[0];
dailyStats[date] = (dailyStats[date] || 0) + 1;
});
const daily = Object.entries(dailyStats)
.map(([date, count]) => ({ date, count }))
.sort((a, b) => a.date.localeCompare(b.date));
// Weekday stats (1=Sunday, 2=Monday, ..., 7=Saturday)
const weekdayStats = {};
cigs.forEach(c => {
const date = new Date(c.smokedAt);
const dayOfWeek = date.getDay() + 1; // JS getDay() returns 0-6, we want 1-7
weekdayStats[dayOfWeek] = (weekdayStats[dayOfWeek] || 0) + 1;
});
// Count occurrences of each weekday in the period
const weekdayCounts = {};
let current = new Date(fromDate);
while (current <= toDate) {
const dayOfWeek = current.getDay() + 1;
weekdayCounts[dayOfWeek] = (weekdayCounts[dayOfWeek] || 0) + 1;
current.setDate(current.getDate() + 1);
}
const weekday = Object.entries(weekdayStats)
.map(([dayOfWeek, count]) => {
const dow = parseInt(dayOfWeek);
const occurrences = weekdayCounts[dow] || 1;
return {
dayOfWeek: dow,
dayName: getDayName(dow),
count,
average: (count / occurrences).toFixed(2)
};
})
.sort((a, b) => a.dayOfWeek - b.dayOfWeek);
const total = cigs.length;
const daysWithData = Object.keys(dailyStats).length;
const averagePerDay = daysWithData > 0 ? total / daysWithData : 0;
return {
daily,
averagePerDay: parseFloat(averagePerDay.toFixed(2)),
weekday,
total,
daysWithData
};
};
// User cigarettes
const userCigarettes = cigarettes.filter(c => {
if (c.userId !== req.userId) return false;
const smokedDate = new Date(c.smokedAt);
return smokedDate >= fromDate && smokedDate <= toDate;
});
// Global cigarettes (all users)
const globalCigarettes = cigarettes.filter(c => {
const smokedDate = new Date(c.smokedAt);
return smokedDate >= fromDate && smokedDate <= toDate;
});
// Count active users
const activeUsers = new Set(globalCigarettes.map(c => c.userId)).size;
// Calculate stats
const userStats = calculateStats(userCigarettes);
const globalStats = calculateStats(globalCigarettes);
globalStats.activeUsers = activeUsers;
res.json({
success: true,
body: {
user: userStats,
global: globalStats,
period: {
from: fromDate.toISOString(),
to: toDate.toISOString()
}
}
});
});
module.exports = router;