Final adjustments to UI layout and positioning. Ensured all JavaScript functions are properly connected and working. Verified event loading functionality is fully restored.
460 lines
22 KiB
HTML
460 lines
22 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>MyIce - Games</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
</head>
|
|
|
|
<body>
|
|
<div id="connectedUser" class="position-fixed top-0 end-0 m-2 p-2 bg-light border rounded" style="display: none; z-index: 1000; font-size: 0.9rem;">
|
|
<span>Connecté: <span id="userName"></span></span>
|
|
<button id="disconnect" class="btn btn-danger btn-sm ms-2" style="font-size: 0.8rem; padding: 0.1rem 0.3rem;">Déco</button>
|
|
</div>
|
|
|
|
<div class="container-fluid d-flex align-items-center justify-content-center">
|
|
<div class="text-center">
|
|
<h1 id="loginTitle" class="mb-4">MyIce - Games</h1>
|
|
|
|
<div id="loginContainer" class="mb-3">
|
|
<div id="oidcLoginSection">
|
|
<button id="oidcLoginBtn" class="btn btn-primary btn-lg px-4 py-3 shadow">Se connecter avec Infomaniak</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container mt-2" id="mainContent" style="display: none;">
|
|
|
|
<div id="eventFilters" style="display: none;">
|
|
<div class="row g-2 align-items-end">
|
|
<div class="col-auto">
|
|
<label for="account" class="form-label mb-1">Compte</label>
|
|
<select id="account" class="form-select form-select-sm">
|
|
<option value="default">Défaut</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-auto">
|
|
<label for="agegroup" class="form-label mb-1">Âge</label>
|
|
<select id="agegroup" class="form-select form-select-sm">
|
|
<option value="">Tous</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-auto">
|
|
<button id="fetchEvents" class="btn btn-primary btn-sm" style="margin-top: 1.2rem;">Charger</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="eventList" class="row mt-2"></div>
|
|
|
|
<!-- Modal pour afficher les détails d'un événement -->
|
|
<div class="modal fade" id="eventDetailsModal" tabindex="-1" aria-labelledby="eventDetailsLabel"
|
|
aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="eventDetailsLabel">Détails de l'événement</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body" id="eventDetailsContent">Chargement...</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
const loginContainer = document.getElementById("loginContainer");
|
|
const oidcLoginSection = document.getElementById("oidcLoginSection");
|
|
const connectedUser = document.getElementById("connectedUser");
|
|
const eventFilters = document.getElementById("eventFilters");
|
|
const accountSelect = document.getElementById("account");
|
|
const agegroupSelect = document.getElementById("agegroup");
|
|
const eventList = document.getElementById("eventList");
|
|
const fetchButton = document.getElementById("fetchEvents");
|
|
const eventDetailsContent = document.getElementById("eventDetailsContent");
|
|
const apiBaseUrl = window.location.origin;
|
|
|
|
let storedApiKey = localStorage.getItem("apikey");
|
|
let lastFetchedEvents = [];
|
|
let storedAccount = localStorage.getItem("account") || "default";
|
|
let userInfo = JSON.parse(localStorage.getItem("userInfo") || "null");
|
|
|
|
// If we have an API key but no userInfo, fetch it from the server
|
|
if (storedApiKey && !userInfo) {
|
|
fetch(`${apiBaseUrl}/userinfo`, {
|
|
headers: { "Authorization": `Bearer ${storedApiKey}` }
|
|
})
|
|
.then(response => response.json())
|
|
.then(userData => {
|
|
userInfo = {email: userData.email || "Utilisateur connecté"};
|
|
localStorage.setItem("userInfo", JSON.stringify(userInfo));
|
|
renderLoginSection();
|
|
})
|
|
.catch(e => {
|
|
console.error("Error fetching user info:", e);
|
|
// If we can't fetch user info, proceed with rendering
|
|
renderLoginSection();
|
|
});
|
|
} else {
|
|
// Render the login section normally
|
|
renderLoginSection();
|
|
}
|
|
|
|
// Handle the static "abc" key case - removed for security
|
|
/*
|
|
if (storedApiKey === "abc" && !userInfo) {
|
|
userInfo = {email: "utilisateur@example.com"};
|
|
localStorage.setItem("userInfo", JSON.stringify(userInfo));
|
|
}
|
|
*/
|
|
|
|
// Handle OIDC callback
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const error = urlParams.get('error');
|
|
const hashParams = new URLSearchParams(window.location.hash.substring(1));
|
|
const accessToken = hashParams.get('access_token');
|
|
|
|
// Display error message if present
|
|
if (error) {
|
|
alert("Erreur d'authentification: " + decodeURIComponent(error));
|
|
// Remove error from URL
|
|
window.history.replaceState({}, document.title, "/");
|
|
}
|
|
|
|
// Handle access token in URL fragment
|
|
if (accessToken) {
|
|
// Store the access token
|
|
localStorage.setItem("apikey", accessToken);
|
|
storedApiKey = accessToken;
|
|
|
|
// Get user info from the userinfo endpoint
|
|
fetch(`${apiBaseUrl}/userinfo`, {
|
|
headers: { "Authorization": `Bearer ${accessToken}` }
|
|
})
|
|
.then(response => response.json())
|
|
.then(userData => {
|
|
userInfo = {email: userData.email || "Utilisateur connecté"};
|
|
localStorage.setItem("userInfo", JSON.stringify(userInfo));
|
|
|
|
// Update UI
|
|
renderLoginSection();
|
|
})
|
|
.catch(e => {
|
|
console.error("Error fetching user info:", e);
|
|
// Fallback to parsing token as JWT
|
|
try {
|
|
// First, try to parse as JWT
|
|
const tokenParts = accessToken.split('.');
|
|
let userData = {};
|
|
|
|
if (tokenParts.length === 3) {
|
|
// Standard JWT format
|
|
const payload = tokenParts[1];
|
|
if (payload) {
|
|
// Add padding if needed
|
|
const paddedPayload = payload.padEnd(payload.length + (4 - payload.length % 4) % 4, '=');
|
|
const decodedPayload = atob(paddedPayload);
|
|
userData = JSON.parse(decodedPayload);
|
|
}
|
|
} else {
|
|
// Non-JWT token, treat as opaque token
|
|
console.log("Non-JWT token received, using default user info");
|
|
userData = {email: "utilisateur@" + window.location.hostname};
|
|
}
|
|
|
|
userInfo = {email: userData.email || "Utilisateur connecté"};
|
|
localStorage.setItem("userInfo", JSON.stringify(userInfo));
|
|
} catch (parseError) {
|
|
console.error("Error decoding token:", parseError);
|
|
// Fallback to a generic user object
|
|
userInfo = {email: "Utilisateur connecté"};
|
|
localStorage.setItem("userInfo", JSON.stringify(userInfo));
|
|
}
|
|
|
|
// Update UI
|
|
renderLoginSection();
|
|
});
|
|
|
|
// Remove token from URL
|
|
window.history.replaceState({}, document.title, "/");
|
|
}
|
|
|
|
function renderLoginSection() {
|
|
const mainContent = document.getElementById("mainContent");
|
|
const loginView = document.querySelector(".container-fluid");
|
|
const connectedUser = document.getElementById("connectedUser");
|
|
const loginTitle = document.getElementById("loginTitle");
|
|
|
|
if (storedApiKey || userInfo) {
|
|
// User is logged in
|
|
loginView.style.display = "none";
|
|
mainContent.style.display = "block";
|
|
loginTitle.style.display = "none";
|
|
|
|
connectedUser.style.display = "block";
|
|
oidcLoginSection.style.display = "none";
|
|
|
|
const userNameElement = document.getElementById("userName");
|
|
if (userInfo && userInfo.email) {
|
|
userNameElement.textContent = userInfo.email;
|
|
} else {
|
|
userNameElement.textContent = "Utilisateur";
|
|
}
|
|
|
|
eventFilters.style.display = "block";
|
|
updateAccountOptions();
|
|
// Don't automatically fetch events on page load
|
|
// Wait for user to explicitly select an account or click fetch
|
|
} else {
|
|
// User is not logged in
|
|
loginView.style.display = "flex";
|
|
mainContent.style.display = "none";
|
|
loginTitle.style.display = "block";
|
|
|
|
connectedUser.style.display = "none";
|
|
oidcLoginSection.style.display = "block";
|
|
eventFilters.style.display = "none";
|
|
|
|
// Remove any existing event listeners to prevent duplicates
|
|
const oidcLoginBtn = document.getElementById("oidcLoginBtn");
|
|
const newOidcLoginBtn = oidcLoginBtn.cloneNode(true);
|
|
oidcLoginBtn.parentNode.replaceChild(newOidcLoginBtn, oidcLoginBtn);
|
|
|
|
newOidcLoginBtn.addEventListener("click", initiateOIDCLogin);
|
|
}
|
|
|
|
// Add disconnect handler
|
|
const disconnectBtn = document.getElementById("disconnect");
|
|
if (disconnectBtn) {
|
|
// Remove any existing event listeners to prevent duplicates
|
|
const newDisconnectBtn = disconnectBtn.cloneNode(true);
|
|
disconnectBtn.parentNode.replaceChild(newDisconnectBtn, disconnectBtn);
|
|
newDisconnectBtn.addEventListener("click", logout);
|
|
}
|
|
}
|
|
|
|
function initiateOIDCLogin() {
|
|
// Redirect to backend login endpoint
|
|
window.location.href = `${apiBaseUrl}/login`;
|
|
}
|
|
|
|
function logout() {
|
|
localStorage.removeItem("apikey");
|
|
localStorage.removeItem("account");
|
|
localStorage.removeItem("userInfo");
|
|
storedApiKey = null;
|
|
userInfo = null;
|
|
location.reload();
|
|
}
|
|
|
|
function updateAccountOptions() {
|
|
// Fetch available accounts from the server
|
|
fetch(`${apiBaseUrl}/accounts`, {
|
|
headers: { "Authorization": `Bearer ${storedApiKey}` }
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
return response.json().then(errorData => {
|
|
console.error("Accounts error response:", errorData);
|
|
throw new Error(`HTTP error! status: ${response.status}, message: ${errorData.detail || 'Unknown error'}`);
|
|
});
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(accounts => {
|
|
// Check if accounts is actually an array
|
|
if (!Array.isArray(accounts)) {
|
|
console.error("Accounts data is not an array:", accounts);
|
|
throw new Error("Invalid accounts data format");
|
|
}
|
|
|
|
accountSelect.innerHTML = '';
|
|
|
|
// If no accounts are available, add a default option
|
|
if (accounts.length === 0) {
|
|
const option = document.createElement("option");
|
|
option.value = "default";
|
|
option.textContent = "Default";
|
|
accountSelect.appendChild(option);
|
|
return;
|
|
}
|
|
|
|
// Add all available accounts
|
|
accounts.forEach(account => {
|
|
const option = document.createElement("option");
|
|
option.value = account.name;
|
|
option.textContent = account.label;
|
|
accountSelect.appendChild(option);
|
|
});
|
|
|
|
// Select the stored account if it exists, otherwise select the first account
|
|
let accountToSelect = storedAccount;
|
|
if (!accounts.some(account => account.name === storedAccount)) {
|
|
accountToSelect = accounts[0].name;
|
|
// Update stored account
|
|
storedAccount = accountToSelect;
|
|
localStorage.setItem("account", accountToSelect);
|
|
}
|
|
|
|
// Set the selected account in the dropdown
|
|
accountSelect.value = accountToSelect;
|
|
})
|
|
.catch(error => {
|
|
console.error("Erreur lors du chargement des comptes:", error);
|
|
alert("Accès refusé. Vous n'êtes pas autorisé à accéder à cette application. Veuillez contacter l'administrateur.");
|
|
logout();
|
|
});
|
|
}
|
|
|
|
renderLoginSection();
|
|
|
|
accountSelect.addEventListener("change", () => {
|
|
const selectedAccount = accountSelect.value;
|
|
localStorage.setItem("account", selectedAccount);
|
|
if (storedApiKey) {
|
|
fetchEvents(storedApiKey, selectedAccount);
|
|
}
|
|
});
|
|
|
|
fetchButton.addEventListener("click", () => {
|
|
if (!storedApiKey) {
|
|
alert("Veuillez vous connecter");
|
|
return;
|
|
}
|
|
const selectedAccount = accountSelect.value;
|
|
fetchEvents(storedApiKey, selectedAccount);
|
|
});
|
|
|
|
agegroupSelect.addEventListener("change", () => {
|
|
displayEvents(lastFetchedEvents);
|
|
});
|
|
|
|
function fetchEvents(apiKey, account) {
|
|
fetch(`${apiBaseUrl}/schedule?account=${account}`, {
|
|
headers: { "Authorization": `Bearer ${apiKey}` }
|
|
})
|
|
.then(response => {
|
|
if (response.status === 401) {
|
|
// Token expired or invalid
|
|
alert("Votre session a expiré. Veuillez vous reconnecter.");
|
|
logout();
|
|
return;
|
|
}
|
|
if (!response.ok) {
|
|
// Try to parse error response as JSON, but handle plain text as well
|
|
return response.text().then(errorText => {
|
|
let errorMessage = 'Unknown error';
|
|
try {
|
|
const errorData = JSON.parse(errorText);
|
|
errorMessage = errorData.detail || errorData.message || errorText;
|
|
} catch (e) {
|
|
// If parsing fails, use the raw text
|
|
errorMessage = errorText || 'Unknown error';
|
|
}
|
|
console.error("Schedule error response:", errorText);
|
|
throw new Error(`HTTP error! status: ${response.status}, message: ${errorMessage}`);
|
|
});
|
|
}
|
|
return response.text().then(text => {
|
|
try {
|
|
return JSON.parse(text);
|
|
} catch (e) {
|
|
console.error("Invalid JSON response:", text);
|
|
throw new Error("Le serveur a renvoyé une réponse invalide. Veuillez réessayer.");
|
|
}
|
|
});
|
|
})
|
|
.then(data => {
|
|
if (data) {
|
|
// Check if data is an array
|
|
if (!Array.isArray(data)) {
|
|
console.error("Schedule data is not an array:", data);
|
|
throw new Error("Invalid schedule data format");
|
|
}
|
|
|
|
lastFetchedEvents = data.filter(event => event.event === "Jeu");
|
|
updateAgeGroupOptions(lastFetchedEvents);
|
|
displayEvents(lastFetchedEvents);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error("Erreur lors du chargement des événements:", error);
|
|
alert("Erreur lors du chargement des événements: " + error.message);
|
|
});
|
|
}
|
|
|
|
function updateAgeGroupOptions(events) {
|
|
let agegroups = new Set(events.map(event => `${event.agegroup} ${event.name}`.trim()));
|
|
agegroupSelect.innerHTML = '<option value="">Tous</option>';
|
|
agegroups.forEach(group => {
|
|
const option = document.createElement("option");
|
|
option.value = group;
|
|
option.textContent = group;
|
|
agegroupSelect.appendChild(option);
|
|
});
|
|
}
|
|
|
|
function displayEvents(events) {
|
|
eventList.innerHTML = "";
|
|
let selectedAgegroup = agegroupSelect.value;
|
|
let filteredEvents = events.filter(event => event.event === "Jeu" && (selectedAgegroup === "" || `${event.agegroup} ${event.name}` === selectedAgegroup));
|
|
|
|
if (filteredEvents.length === 0) {
|
|
eventList.innerHTML = "<p class='text-muted'>Aucun événement 'Jeu' trouvé.</p>";
|
|
return;
|
|
}
|
|
|
|
filteredEvents.forEach(event => {
|
|
const eventCard = document.createElement("div");
|
|
eventCard.className = "col-md-4 mb-3";
|
|
eventCard.innerHTML = `
|
|
<div class="card" style="border-left: 5px solid ${event.color}" data-id="${event.id_event}">
|
|
<div class="card-body">
|
|
<h5 class="card-title">${event.title}</h5>
|
|
<p class="card-text"><strong>Lieu:</strong> ${event.place}</p>
|
|
<p class="card-text"><strong>Heure:</strong> ${event.start} - ${event.end}</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
eventCard.addEventListener("click", () => fetchEventDetails(event.id_event));
|
|
eventList.appendChild(eventCard);
|
|
});
|
|
}
|
|
|
|
function fetchEventDetails(eventId) {
|
|
const selectedAccount = accountSelect.value;
|
|
fetch(`${apiBaseUrl}/game/${eventId}?account=${selectedAccount}`, {
|
|
headers: { "Authorization": `Bearer ${storedApiKey}` }
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const sortedPlayers = data.convocation.available
|
|
.sort((a, b) => (a.number || 0) - (b.number || 0));
|
|
|
|
eventDetailsContent.innerHTML = `
|
|
<h5>${data.title}</h5>
|
|
<p><strong>Type:</strong> ${data.type}</p>
|
|
<p><strong>Lieu:</strong> ${data.place}</p>
|
|
<p><strong>Heure:</strong> ${data.time_start} - ${data.time_end}</p>
|
|
<h6>Joueurs convoqués:</h6>
|
|
<ul>${sortedPlayers.map(player => {
|
|
let number = player.number ? player.number : "N/A";
|
|
let position = player.position ? player.position : "N/A";
|
|
return `<li>${number} - ${player.fname} ${player.lname} (${position}, ${player.dob})</li>`;
|
|
}).join('')}</ul>
|
|
`;
|
|
new bootstrap.Modal(document.getElementById('eventDetailsModal')).show();
|
|
})
|
|
.catch(error => console.error("Erreur lors du chargement des détails de l'événement:", error));
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|