Overview
The Stats module calculates and displays comprehensive statistics about reviews, including best/worst dishes, recent highlights, total review counts, and overall averages. It provides real-time analytics updates.
Methods
init()
Initializes the statistics module and performs initial calculation.
Behavior:
- Calls
update() to calculate and display initial statistics
Example:
import { Stats } from './modules/stats.js';
Stats.init();
update()
Recalculates and updates all statistics display.
Calculated Statistics:
- Total review count
- Overall average rating
- Best dish (highest average rating)
- Worst dish (lowest average rating)
- Recent best dish (last 30 days)
Behavior:
- Gets current reviews from DataStore
- Shows “No hay datos aún” if no reviews exist
- Calculates all statistics
- Updates
#statsContent with formatted HTML
Example:
// Update stats after data changes
Stats.update();
getBestDish(reviews)
Finds the dish with the highest average rating.
const best = Stats.getBestDish(reviews);
Review object with the highest average rating
Algorithm:
- Uses
Array.reduce() to find maximum
- Compares using
Utils.calculateAverageRating()
- Returns the review with highest rating
Example:
const reviews = DataStore.reviews;
const bestDish = Stats.getBestDish(reviews);
console.log(`Best: ${bestDish.dish} at ${bestDish.restaurant}`);
console.log(`Rating: ${Utils.calculateAverageRating(bestDish)}/10`);
getWorstDish(reviews)
Finds the dish with the lowest average rating.
const worst = Stats.getWorstDish(reviews);
Review object with the lowest average rating
Algorithm:
- Uses
Array.reduce() to find minimum
- Compares using
Utils.calculateAverageRating()
- Returns the review with lowest rating
Example:
const worstDish = Stats.getWorstDish(reviews);
console.log(`Worst: ${worstDish.dish} at ${worstDish.restaurant}`);
console.log(`Rating: ${Utils.calculateAverageRating(worstDish)}/10`);
getRecentBest(reviews)
Finds the best dish from the last 30 days.
const recentBest = Stats.getRecentBest(reviews);
Review object with highest rating from last 30 days, or first review if none found
Algorithm:
- Calculate date 30 days ago
- Filter reviews from last 30 days using
Utils.parseDate()
- Find review with highest rating in filtered set
- Falls back to first review if no recent reviews exist
Example:
const recentBest = Stats.getRecentBest(reviews);
console.log(`Recent Best: ${recentBest.dish}`);
console.log(`Date: ${recentBest.date}`);
console.log(`Rating: ${Utils.calculateAverageRating(recentBest)}/10`);
Date Filtering:
const now = new Date();
const thirtyDaysAgo = new Date(now.setDate(now.getDate() - 30));
const recentReviews = reviews.filter(review => {
const reviewDate = Utils.parseDate(review.date);
return reviewDate >= thirtyDaysAgo;
});
getOverallAverage(reviews)
Calculates the overall average rating across all reviews and reviewers.
const avg = Stats.getOverallAverage(reviews);
Average rating formatted to 1 decimal place (e.g., “8.5”)
Algorithm:
- Extract all individual ratings from all reviewers
- Calculate sum of all ratings
- Divide by total number of ratings
- Format to 1 decimal place
Example:
const avgRating = Stats.getOverallAverage(reviews);
console.log(`Overall Average: ${avgRating}/10`);
Implementation:
const allRatings = reviews.flatMap(review =>
Object.values(review.reviewers).map(r => r.rating)
);
const sum = allRatings.reduce((a, b) => a + b, 0);
return (sum / allRatings.length).toFixed(1);
Statistics Display
The update() method generates HTML with the following sections:
Total Reviews Section
<div class="text-center mb-6">
<div class="text-3xl font-bold text-purple-600">{totalReviews}</div>
<div class="text-sm text-gray-600">Reseñas totales</div>
<div class="text-lg font-semibold text-gray-700 mt-1">{avgRating}/10</div>
<div class="text-xs text-gray-500">Promedio general</div>
</div>
Best Dish Card
<div class="bg-green-50 rounded-xl p-4 mb-4">
<div class="flex items-center mb-2">
<i class="fas fa-crown text-yellow-500 mr-2"></i>
<h4 class="font-bold text-green-800">Mejor Plato</h4>
</div>
<div class="text-sm">
<div class="font-semibold text-green-700">{dish}</div>
<div class="text-green-600">{restaurant}</div>
<div class="flex items-center mt-1">
<i class="fas fa-star text-yellow-400 mr-1"></i>
<span class="font-bold">{rating}/10</span>
</div>
</div>
</div>
Worst Dish Card
<div class="bg-red-50 rounded-xl p-4 mb-4">
<div class="flex items-center mb-2">
<i class="fas fa-thumbs-down text-red-500 mr-2"></i>
<h4 class="font-bold text-red-800">Peor Plato</h4>
</div>
<div class="text-sm">
<div class="font-semibold text-red-700">{dish}</div>
<div class="text-red-600">{restaurant}</div>
<div class="flex items-center mt-1">
<i class="fas fa-star text-yellow-400 mr-1"></i>
<span class="font-bold">{rating}/10</span>
</div>
</div>
</div>
Recent Best Card
<div class="bg-blue-50 rounded-xl p-4">
<div class="flex items-center mb-2">
<i class="fas fa-calendar-star text-blue-500 mr-2"></i>
<h4 class="font-bold text-blue-800">Destacado Reciente</h4>
</div>
<div class="text-sm">
<div class="font-semibold text-blue-700">{dish}</div>
<div class="text-blue-600">{restaurant}</div>
<div class="flex items-center justify-between mt-1">
<div class="flex items-center">
<i class="fas fa-star text-yellow-400 mr-1"></i>
<span class="font-bold">{rating}/10</span>
</div>
<span class="text-xs text-blue-500">{date}</span>
</div>
</div>
</div>
HTML Structure Required
<div id="statsContent">
<!-- Statistics content dynamically generated here -->
</div>
Empty State
When no reviews exist:
<p class="text-gray-500 text-center">No hay datos aún</p>
Color Scheme
- Background:
bg-green-50
- Text:
text-green-700, text-green-800
- Icon: Yellow crown
- Background:
bg-red-50
- Text:
text-red-700, text-red-800
- Icon: Red thumbs down
- Background:
bg-blue-50
- Text:
text-blue-700, text-blue-800
- Icon: Blue calendar star
- Primary:
text-purple-600
- Secondary:
text-gray-600, text-gray-700
Integration with UI Module
Stats automatically update when reviews change:
// In UI.renderReviews()
Stats.update();
// On real-time updates
document.addEventListener('reviewsUpdated', () => {
Stats.update();
});
Complete Usage Example
import { Stats } from './modules/stats.js';
import { DataStore } from './modules/datastore.js';
// Initialize
Stats.init();
// Manual update
Stats.update();
// Get individual statistics
const reviews = DataStore.reviews;
const bestDish = Stats.getBestDish(reviews);
console.log('Best:', bestDish.dish, bestDish.restaurant);
const worstDish = Stats.getWorstDish(reviews);
console.log('Worst:', worstDish.dish, worstDish.restaurant);
const recentBest = Stats.getRecentBest(reviews);
console.log('Recent Best:', recentBest.dish);
const avgRating = Stats.getOverallAverage(reviews);
console.log('Average:', avgRating);
Advanced Example
// Create custom stats display
function displayCustomStats() {
const reviews = DataStore.reviews;
if (reviews.length === 0) {
console.log('No reviews yet');
return;
}
console.log('=== Statistics ===');
console.log(`Total Reviews: ${reviews.length}`);
console.log(`Average Rating: ${Stats.getOverallAverage(reviews)}/10`);
const best = Stats.getBestDish(reviews);
console.log(`\nBest Dish:`);
console.log(` ${best.dish} - ${best.restaurant}`);
console.log(` Rating: ${Utils.calculateAverageRating(best)}/10`);
const worst = Stats.getWorstDish(reviews);
console.log(`\nWorst Dish:`);
console.log(` ${worst.dish} - ${worst.restaurant}`);
console.log(` Rating: ${Utils.calculateAverageRating(worst)}/10`);
const recent = Stats.getRecentBest(reviews);
console.log(`\nRecent Highlight:`);
console.log(` ${recent.dish} - ${recent.restaurant}`);
console.log(` Date: ${recent.date}`);
console.log(` Rating: ${Utils.calculateAverageRating(recent)}/10`);
}
// Display stats
displayCustomStats();
Rating Calculations
All rating calculations use Utils.calculateAverageRating():
// Example review
const review = {
reviewers: {
gian: { rating: 9, review: "Great!" },
yami: { rating: 7, review: "Good" }
}
};
// Calculate average
const avg = Utils.calculateAverageRating(review);
// Returns: "8.0" (formatted string)
Dependencies
For accessing current reviews data
For calculateAverageRating() and parseDate() functions
- Statistics recalculated on every
update() call
- Uses efficient
reduce() operations
- No caching (assumes small dataset)
- For large datasets, consider memoization
Future Enhancements
Possible additions:
// Most reviewed restaurant
getMostReviewedRestaurant(reviews) {
const counts = {};
reviews.forEach(r => {
counts[r.restaurant] = (counts[r.restaurant] || 0) + 1;
});
return Object.entries(counts)
.sort((a, b) => b[1] - a[1])[0];
}
// Average by restaurant
getRestaurantAverages(reviews) {
const byRestaurant = {};
reviews.forEach(review => {
const restaurant = review.restaurant;
if (!byRestaurant[restaurant]) {
byRestaurant[restaurant] = [];
}
byRestaurant[restaurant].push(
parseFloat(Utils.calculateAverageRating(review))
);
});
return Object.entries(byRestaurant).map(([restaurant, ratings]) => ({
restaurant,
average: (ratings.reduce((a,b) => a+b) / ratings.length).toFixed(1)
}));
}
Source Code
Source: workspace/source/src/js/modules/stats.js:1