Add visibility toggle for picks features and rename Mes choix to Mes picks
- Renamed 'Mes choix' to 'Mes picks' across all pages and JavaScript files - Added visibility toggle in control panel (Réglages > Visibilité) to control: - Display of 'Mes picks' button in navigation - Display of 'Bon picks' column in user statistics - Combined both visibility options into single toggle for simplicity - Added backend API endpoints for visibility configuration - Implemented localStorage caching for instant visibility updates - Fixed flash of content issues when navigating between pages - Updated stats endpoint to calculate correct picks on-the-fly from current winners - Renamed 'Bonnes réponses' to 'Bon picks' in user statistics table
This commit is contained in:
+1
-5
@@ -248,13 +248,9 @@ router.post("/calculate-scores", verifyToken, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Get scores for all users (admin only)
|
||||
// Get scores for all users (available to all authenticated users)
|
||||
router.get("/scores", verifyToken, async (req, res) => {
|
||||
try {
|
||||
if (!isAdmin(req.user)) {
|
||||
return res.status(403).json({ message: "You do not have admin privileges" });
|
||||
}
|
||||
|
||||
const year = parseOscarYear(req.query.year) || await getOrInitActiveYear();
|
||||
|
||||
const picks = await OscarPick.find({ year })
|
||||
|
||||
@@ -15,6 +15,7 @@ const OSCAR_DATES_BY_YEAR_KEY = "oscar_date_by_year";
|
||||
const APP_VERSION_KEY = "app_version_control";
|
||||
const PLAYER_ADMIN_STATUS_UI_KEY = "player_admin_status_ui";
|
||||
const POINTS_CONFIG_KEY = "points_config";
|
||||
const VISIBILITY_CONFIG_KEY = "visibility_config";
|
||||
|
||||
const crypto = require("crypto");
|
||||
|
||||
@@ -36,6 +37,11 @@ const DEFAULT_POINTS_CONFIG = Object.freeze({
|
||||
pointsPerCorrectPick: 1,
|
||||
});
|
||||
|
||||
const DEFAULT_VISIBILITY_CONFIG = Object.freeze({
|
||||
showPicksButton: true,
|
||||
showBonPicksColumn: true,
|
||||
});
|
||||
|
||||
function parseOscarYear(raw) {
|
||||
if (raw === undefined || raw === null || raw === "") return null;
|
||||
const n = Number(raw);
|
||||
@@ -762,6 +768,13 @@ function normalizePointsConfig(rawValue) {
|
||||
return { pointsPerMovie, pointsPerCorrectPick };
|
||||
}
|
||||
|
||||
function normalizeVisibilityConfig(rawValue) {
|
||||
const v = rawValue && typeof rawValue === "object" && !Array.isArray(rawValue) ? rawValue : {};
|
||||
const showPicksButton = typeof v.showPicksButton === "boolean" ? v.showPicksButton : DEFAULT_VISIBILITY_CONFIG.showPicksButton;
|
||||
const showBonPicksColumn = typeof v.showBonPicksColumn === "boolean" ? v.showBonPicksColumn : DEFAULT_VISIBILITY_CONFIG.showBonPicksColumn;
|
||||
return { showPicksButton, showBonPicksColumn };
|
||||
}
|
||||
|
||||
// Public: get points configuration
|
||||
router.get("/points-config", async (req, res) => {
|
||||
try {
|
||||
@@ -800,5 +813,43 @@ router.put("/points-config", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Public: get visibility configuration
|
||||
router.get("/visibility-config", async (req, res) => {
|
||||
try {
|
||||
const existing = await Setting.findOne({ key: VISIBILITY_CONFIG_KEY }).select("value");
|
||||
const normalized = normalizeVisibilityConfig(existing?.value);
|
||||
return res.status(200).json(normalized);
|
||||
} catch (err) {
|
||||
return res.status(200).json(DEFAULT_VISIBILITY_CONFIG);
|
||||
}
|
||||
});
|
||||
|
||||
// Admin-only: set visibility configuration
|
||||
router.put("/visibility-config", async (req, res) => {
|
||||
const token = req.headers.authorization?.split(" ")[1];
|
||||
if (!token) return res.status(401).json({ message: "Authentication token is required" });
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
if (!decoded.admin) {
|
||||
return res.status(403).json({ message: "You do not have admin privileges" });
|
||||
}
|
||||
|
||||
const normalized = normalizeVisibilityConfig(req.body);
|
||||
const updated = await Setting.findOneAndUpdate(
|
||||
{ key: VISIBILITY_CONFIG_KEY },
|
||||
{ $set: { value: normalized } },
|
||||
{ upsert: true, new: true }
|
||||
).select("value");
|
||||
|
||||
return res.status(200).json(normalizeVisibilityConfig(updated?.value));
|
||||
} catch (err) {
|
||||
if (err instanceof jwt.JsonWebTokenError) {
|
||||
return res.status(401).json({ message: "Invalid or expired token" });
|
||||
}
|
||||
return res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
|
||||
+26
-3
@@ -7,6 +7,7 @@ const Movie = require("../models/Movie");
|
||||
const Setting = require("../models/Setting");
|
||||
const PlaybackProgress = require("../models/PlaybackProgress");
|
||||
const OscarPick = require("../models/OscarPick");
|
||||
const OscarCategory = require("../models/OscarCategory");
|
||||
const axios = require("axios");
|
||||
|
||||
const router = express.Router();
|
||||
@@ -153,16 +154,38 @@ router.get("/stats", verifyToken, async (req, res) => {
|
||||
}
|
||||
: { pointsPerMovie: 1, pointsPerCorrectPick: 1 };
|
||||
|
||||
// Get all picks for this year with scores
|
||||
// Get all categories with winners marked for this year
|
||||
const categories = await OscarCategory.find({ year }).lean();
|
||||
const categoryWinners = new Map();
|
||||
categories.forEach(cat => {
|
||||
const winner = cat.nominees.find(n => n.isWinner);
|
||||
if (winner) {
|
||||
categoryWinners.set(cat.categoryNumber, winner.name);
|
||||
}
|
||||
});
|
||||
|
||||
// Get all picks for this year and calculate correct picks on-the-fly
|
||||
let picksByUserId = new Map();
|
||||
try {
|
||||
const allPicks = await OscarPick.find({ year })
|
||||
.select("userId score")
|
||||
.select("userId picks")
|
||||
.lean();
|
||||
|
||||
allPicks.forEach(pick => {
|
||||
const userId = String(pick.userId);
|
||||
picksByUserId.set(userId, pick.score !== null && pick.score !== undefined ? pick.score : 0);
|
||||
let correctCount = 0;
|
||||
|
||||
// Calculate correct picks based on current winners
|
||||
if (Array.isArray(pick.picks)) {
|
||||
pick.picks.forEach(p => {
|
||||
const correctWinner = categoryWinners.get(p.categoryNumber);
|
||||
if (correctWinner && p.selectedNominee === correctWinner) {
|
||||
correctCount++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
picksByUserId.set(userId, correctCount);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Error fetching picks for stats:", err);
|
||||
|
||||
Reference in New Issue
Block a user